/* =========================================================================== Doom 3 BFG Edition GPL Source Code Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Doom 3 BFG Edition Source Code. If not, see . In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. =========================================================================== */ #pragma hdrstop #include "../idlib/precompiled.h" #include "DeviceContext.h" #include "../renderer/GuiModel.h" extern idCVar in_useJoystick; // bypass rendersystem to directly work on guiModel extern idGuiModel * tr_guiModel; idVec4 idDeviceContext::colorPurple; idVec4 idDeviceContext::colorOrange; idVec4 idDeviceContext::colorYellow; idVec4 idDeviceContext::colorGreen; idVec4 idDeviceContext::colorBlue; idVec4 idDeviceContext::colorRed; idVec4 idDeviceContext::colorBlack; idVec4 idDeviceContext::colorWhite; idVec4 idDeviceContext::colorNone; void idDeviceContext::Init() { xScale = 1.0f; yScale = 1.0f; xOffset = 0.0f; yOffset = 0.0f; whiteImage = declManager->FindMaterial("guis/assets/white.tga"); whiteImage->SetSort( SS_GUI ); activeFont = renderSystem->RegisterFont( "" ); colorPurple = idVec4(1, 0, 1, 1); colorOrange = idVec4(1, 1, 0, 1); colorYellow = idVec4(0, 1, 1, 1); colorGreen = idVec4(0, 1, 0, 1); colorBlue = idVec4(0, 0, 1, 1); colorRed = idVec4(1, 0, 0, 1); colorWhite = idVec4(1, 1, 1, 1); colorBlack = idVec4(0, 0, 0, 1); colorNone = idVec4(0, 0, 0, 0); cursorImages[CURSOR_ARROW] = declManager->FindMaterial("ui/assets/guicursor_arrow.tga"); cursorImages[CURSOR_HAND] = declManager->FindMaterial("ui/assets/guicursor_hand.tga"); cursorImages[CURSOR_HAND_JOY1] = declManager->FindMaterial("ui/assets/guicursor_hand_cross.tga"); cursorImages[CURSOR_HAND_JOY2] = declManager->FindMaterial("ui/assets/guicursor_hand_circle.tga"); cursorImages[CURSOR_HAND_JOY3] = declManager->FindMaterial("ui/assets/guicursor_hand_square.tga"); cursorImages[CURSOR_HAND_JOY4] = declManager->FindMaterial("ui/assets/guicursor_hand_triangle.tga"); cursorImages[CURSOR_HAND_JOY1] = declManager->FindMaterial("ui/assets/guicursor_hand_a.tga"); cursorImages[CURSOR_HAND_JOY2] = declManager->FindMaterial("ui/assets/guicursor_hand_b.tga"); cursorImages[CURSOR_HAND_JOY3] = declManager->FindMaterial("ui/assets/guicursor_hand_x.tga"); cursorImages[CURSOR_HAND_JOY4] = declManager->FindMaterial("ui/assets/guicursor_hand_y.tga"); scrollBarImages[SCROLLBAR_HBACK] = declManager->FindMaterial("ui/assets/scrollbarh.tga"); scrollBarImages[SCROLLBAR_VBACK] = declManager->FindMaterial("ui/assets/scrollbarv.tga"); scrollBarImages[SCROLLBAR_THUMB] = declManager->FindMaterial("ui/assets/scrollbar_thumb.tga"); scrollBarImages[SCROLLBAR_RIGHT] = declManager->FindMaterial("ui/assets/scrollbar_right.tga"); scrollBarImages[SCROLLBAR_LEFT] = declManager->FindMaterial("ui/assets/scrollbar_left.tga"); scrollBarImages[SCROLLBAR_UP] = declManager->FindMaterial("ui/assets/scrollbar_up.tga"); scrollBarImages[SCROLLBAR_DOWN] = declManager->FindMaterial("ui/assets/scrollbar_down.tga"); cursorImages[CURSOR_ARROW]->SetSort( SS_GUI ); cursorImages[CURSOR_HAND]->SetSort( SS_GUI ); scrollBarImages[SCROLLBAR_HBACK]->SetSort( SS_GUI ); scrollBarImages[SCROLLBAR_VBACK]->SetSort( SS_GUI ); scrollBarImages[SCROLLBAR_THUMB]->SetSort( SS_GUI ); scrollBarImages[SCROLLBAR_RIGHT]->SetSort( SS_GUI ); scrollBarImages[SCROLLBAR_LEFT]->SetSort( SS_GUI ); scrollBarImages[SCROLLBAR_UP]->SetSort( SS_GUI ); scrollBarImages[SCROLLBAR_DOWN]->SetSort( SS_GUI ); cursor = CURSOR_ARROW; enableClipping = true; overStrikeMode = true; mat.Identity(); matIsIdentity = true; origin.Zero(); initialized = true; } void idDeviceContext::Shutdown() { clipRects.Clear(); Clear(); } void idDeviceContext::Clear() { initialized = false; } idDeviceContext::idDeviceContext() { Clear(); } void idDeviceContext::SetTransformInfo(const idVec3 &org, const idMat3 &m) { origin = org; mat = m; matIsIdentity = mat.IsIdentity(); } // // added method void idDeviceContext::GetTransformInfo(idVec3& org, idMat3& m ) { m = mat; org = origin; } // void idDeviceContext::EnableClipping(bool b) { enableClipping = b; }; void idDeviceContext::PopClipRect() { if (clipRects.Num()) { clipRects.RemoveIndex(clipRects.Num()-1); } } void idDeviceContext::PushClipRect(idRectangle r) { clipRects.Append(r); } bool idDeviceContext::ClippedCoords(float *x, float *y, float *w, float *h, float *s1, float *t1, float *s2, float *t2) { if ( enableClipping == false || clipRects.Num() == 0 ) { return false; } int c = clipRects.Num(); while( --c > 0 ) { idRectangle *clipRect = &clipRects[c]; float ox = *x; float oy = *y; float ow = *w; float oh = *h; if ( ow <= 0.0f || oh <= 0.0f ) { break; } if (*x < clipRect->x) { *w -= clipRect->x - *x; *x = clipRect->x; } else if (*x > clipRect->x + clipRect->w) { *x = *w = *y = *h = 0; } if (*y < clipRect->y) { *h -= clipRect->y - *y; *y = clipRect->y; } else if (*y > clipRect->y + clipRect->h) { *x = *w = *y = *h = 0; } if (*w > clipRect->w) { *w = clipRect->w - *x + clipRect->x; } else if (*x + *w > clipRect->x + clipRect->w) { *w = clipRect->Right() - *x; } if (*h > clipRect->h) { *h = clipRect->h - *y + clipRect->y; } else if (*y + *h > clipRect->y + clipRect->h) { *h = clipRect->Bottom() - *y; } if ( s1 && s2 && t1 && t2 && ow > 0.0f ) { float ns1, ns2, nt1, nt2; // upper left float u = ( *x - ox ) / ow; ns1 = *s1 * ( 1.0f - u ) + *s2 * ( u ); // upper right u = ( *x + *w - ox ) / ow; ns2 = *s1 * ( 1.0f - u ) + *s2 * ( u ); // lower left u = ( *y - oy ) / oh; nt1 = *t1 * ( 1.0f - u ) + *t2 * ( u ); // lower right u = ( *y + *h - oy ) / oh; nt2 = *t1 * ( 1.0f - u ) + *t2 * ( u ); // set values *s1 = ns1; *s2 = ns2; *t1 = nt1; *t2 = nt2; } } return (*w == 0 || *h == 0) ? true : false; } /* ============= DrawStretchPic ============= */ void idDeviceContext::DrawWinding( idWinding & w, const idMaterial * mat ) { idPlane p; p.Normal().Set( 1.0f, 0.0f, 0.0f ); p.SetDist( 0.0f ); w.ClipInPlace( p ); p.Normal().Set( -1.0f, 0.0f, 0.0f ); p.SetDist( -SCREEN_WIDTH ); w.ClipInPlace( p ); p.Normal().Set( 0.0f, 1.0f, 0.0f ); p.SetDist( 0.0f ); w.ClipInPlace( p ); p.Normal().Set( 0.0f, -1.0f, 0.0f ); p.SetDist( -SCREEN_HEIGHT ); w.ClipInPlace( p ); if ( w.GetNumPoints() < 3 ) { return; } int numIndexes = 0; triIndex_t tempIndexes[(MAX_POINTS_ON_WINDING-2)*3]; for ( int j = 2; j < w.GetNumPoints(); j++ ) { tempIndexes[numIndexes++] = 0; tempIndexes[numIndexes++] = j - 1; tempIndexes[numIndexes++] = j; } assert( numIndexes == ( w.GetNumPoints() - 2 ) * 3 ); idDrawVert * verts = renderSystem->AllocTris( w.GetNumPoints(), tempIndexes, numIndexes, mat ); if ( verts == NULL ) { return; } uint32 currentColor = renderSystem->GetColor(); for ( int j = 0 ; j < w.GetNumPoints() ; j++ ) { verts[j].xyz.x = xOffset + w[j].x * xScale; verts[j].xyz.y = yOffset + w[j].y * yScale; verts[j].xyz.z = w[j].z; verts[j].SetTexCoord( w[j].s, w[j].t ); verts[j].SetColor( currentColor ); verts[j].ClearColor2(); verts[j].SetNormal( 0.0f, 0.0f, 1.0f ); verts[j].SetTangent( 1.0f, 0.0f, 0.0f ); verts[j].SetBiTangent( 0.0f, 1.0f, 0.0f ); } } void idDeviceContext::DrawStretchPic(float x, float y, float w, float h, float s1, float t1, float s2, float t2, const idMaterial *shader) { if ( matIsIdentity ) { renderSystem->DrawStretchPic( xOffset + x * xScale, yOffset + y * yScale, w * xScale, h * yScale, s1, t1, s2, t2, shader ); return; } idFixedWinding winding; winding.AddPoint( idVec5( x, y, 0.0f, s1, t1 ) ); winding.AddPoint( idVec5( x+w, y, 0.0f, s2, t1 ) ); winding.AddPoint( idVec5( x+w, y+h, 0.0f, s2, t2 ) ); winding.AddPoint( idVec5( x, y+h, 0.0f, s1, t2 ) ); for ( int i = 0; i < winding.GetNumPoints(); i++ ) { winding[i].ToVec3() -= origin; winding[i].ToVec3() *= mat; winding[i].ToVec3() += origin; } DrawWinding( winding, shader ); } void idDeviceContext::DrawMaterial(float x, float y, float w, float h, const idMaterial *mat, const idVec4 &color, float scalex, float scaley) { renderSystem->SetColor(color); float s0, s1, t0, t1; // // handle negative scales as well if ( scalex < 0 ) { w *= -1; scalex *= -1; } if ( scaley < 0 ) { h *= -1; scaley *= -1; } // if( w < 0 ) { // flip about vertical w = -w; s0 = 1 * scalex; s1 = 0; } else { s0 = 0; s1 = 1 * scalex; } if( h < 0 ) { // flip about horizontal h = -h; t0 = 1 * scaley; t1 = 0; } else { t0 = 0; t1 = 1 * scaley; } if ( ClippedCoords( &x, &y, &w, &h, &s0, &t0, &s1, &t1 ) ) { return; } DrawStretchPic( x, y, w, h, s0, t0, s1, t1, mat); } void idDeviceContext::DrawMaterialRotated(float x, float y, float w, float h, const idMaterial *mat, const idVec4 &color, float scalex, float scaley, float angle) { renderSystem->SetColor(color); float s0, s1, t0, t1; // // handle negative scales as well if ( scalex < 0 ) { w *= -1; scalex *= -1; } if ( scaley < 0 ) { h *= -1; scaley *= -1; } // if( w < 0 ) { // flip about vertical w = -w; s0 = 1 * scalex; s1 = 0; } else { s0 = 0; s1 = 1 * scalex; } if( h < 0 ) { // flip about horizontal h = -h; t0 = 1 * scaley; t1 = 0; } else { t0 = 0; t1 = 1 * scaley; } if ( angle == 0.0f && ClippedCoords( &x, &y, &w, &h, &s0, &t0, &s1, &t1 ) ) { return; } DrawStretchPicRotated( x, y, w, h, s0, t0, s1, t1, mat, angle); } void idDeviceContext::DrawStretchPicRotated(float x, float y, float w, float h, float s1, float t1, float s2, float t2, const idMaterial *shader, float angle) { idFixedWinding winding; winding.AddPoint( idVec5( x, y, 0.0f, s1, t1 ) ); winding.AddPoint( idVec5( x+w, y, 0.0f, s2, t1 ) ); winding.AddPoint( idVec5( x+w, y+h, 0.0f, s2, t2 ) ); winding.AddPoint( idVec5( x, y+h, 0.0f, s1, t2 ) ); for ( int i = 0; i < winding.GetNumPoints(); i++ ) { winding[i].ToVec3() -= origin; winding[i].ToVec3() *= mat; winding[i].ToVec3() += origin; } //Generate a translation so we can translate to the center of the image rotate and draw idVec3 origTrans; origTrans.x = x+(w/2); origTrans.y = y+(h/2); origTrans.z = 0; //Rotate the verts about the z axis before drawing them idMat3 rotz; rotz.Identity(); float sinAng, cosAng; idMath::SinCos( angle, sinAng, cosAng ); rotz[0][0] = cosAng; rotz[0][1] = sinAng; rotz[1][0] = -sinAng; rotz[1][1] = cosAng; for (int i = 0; i < winding.GetNumPoints(); i++) { winding[i].ToVec3() -= origTrans; winding[i].ToVec3() *= rotz; winding[i].ToVec3() += origTrans; } DrawWinding( winding, shader ); } void idDeviceContext::DrawFilledRect( float x, float y, float w, float h, const idVec4 &color) { if ( color.w == 0.0f ) { return; } renderSystem->SetColor(color); if (ClippedCoords(&x, &y, &w, &h, NULL, NULL, NULL, NULL)) { return; } DrawStretchPic( x, y, w, h, 0, 0, 0, 0, whiteImage); } void idDeviceContext::DrawRect( float x, float y, float w, float h, float size, const idVec4 &color) { if ( color.w == 0.0f ) { return; } renderSystem->SetColor(color); if (ClippedCoords(&x, &y, &w, &h, NULL, NULL, NULL, NULL)) { return; } DrawStretchPic( x, y, size, h, 0, 0, 0, 0, whiteImage ); DrawStretchPic( x + w - size, y, size, h, 0, 0, 0, 0, whiteImage ); DrawStretchPic( x, y, w, size, 0, 0, 0, 0, whiteImage ); DrawStretchPic( x, y + h - size, w, size, 0, 0, 0, 0, whiteImage ); } void idDeviceContext::DrawMaterialRect( float x, float y, float w, float h, float size, const idMaterial *mat, const idVec4 &color) { if ( color.w == 0.0f ) { return; } renderSystem->SetColor(color); DrawMaterial( x, y, size, h, mat, color ); DrawMaterial( x + w - size, y, size, h, mat, color ); DrawMaterial( x, y, w, size, mat, color ); DrawMaterial( x, y + h - size, w, size, mat, color ); } void idDeviceContext::SetCursor(int n) { if ( n > CURSOR_ARROW && n < CURSOR_COUNT ) { keyBindings_t binds = idKeyInput::KeyBindingsFromBinding( "_use", true ); keyNum_t keyNum = K_NONE; if ( in_useJoystick.GetBool() ) { keyNum = idKeyInput::StringToKeyNum( binds.gamepad.c_str() ); } if ( keyNum != K_NONE ) { if ( keyNum == K_JOY1 ) { cursor = CURSOR_HAND_JOY1; } else if ( keyNum == K_JOY2 ) { cursor = CURSOR_HAND_JOY2; } else if ( keyNum == K_JOY3 ) { cursor = CURSOR_HAND_JOY3; } else if ( keyNum == K_JOY4 ) { cursor = CURSOR_HAND_JOY4; } } else { cursor = CURSOR_HAND; } } else { cursor = CURSOR_ARROW; } } void idDeviceContext::DrawCursor(float *x, float *y, float size) { if (*x < 0) { *x = 0; } if (*x >= VIRTUAL_WIDTH) { *x = VIRTUAL_WIDTH; } if (*y < 0) { *y = 0; } if (*y >= VIRTUAL_HEIGHT) { *y = VIRTUAL_HEIGHT; } renderSystem->SetColor(colorWhite); DrawStretchPic( *x, *y, size, size, 0, 0, 1, 1, cursorImages[cursor]); } /* ======================================================================================================================= ======================================================================================================================= */ void idDeviceContext::PaintChar( float x, float y, const scaledGlyphInfo_t & glyphInfo ) { y -= glyphInfo.top; x += glyphInfo.left; float w = glyphInfo.width; float h = glyphInfo.height; float s = glyphInfo.s1; float t = glyphInfo.t1; float s2 = glyphInfo.s2; float t2 = glyphInfo.t2; const idMaterial * hShader = glyphInfo.material; if ( ClippedCoords( &x, &y, &w, &h, &s, &t, &s2, &t2) ) { return; } DrawStretchPic(x, y, w, h, s, t, s2, t2, hShader); } int idDeviceContext::DrawText(float x, float y, float scale, idVec4 color, const char *text, float adjust, int limit, int style, int cursor) { int len; idVec4 newColor; idStr drawText = text; int charIndex = 0; if ( text && color.w != 0.0f ) { renderSystem->SetColor(color); memcpy(&newColor[0], &color[0], sizeof(idVec4)); len = drawText.Length(); if (limit > 0 && len > limit) { len = limit; } float prevGlyphSkip = 0.0f; while ( charIndex < len ) { uint32 textChar = drawText.UTF8Char( charIndex ); if ( idStr::IsColor( drawText.c_str() + charIndex ) ) { if ( drawText[ charIndex++ ] == C_COLOR_DEFAULT ) { newColor = color; } else { newColor = idStr::ColorForIndex( charIndex ); newColor[3] = color[3]; } if (cursor == charIndex-1 || cursor == charIndex) { float backup = 0.0f; if ( prevGlyphSkip > 0.0f ) { backup = ( prevGlyphSkip + adjust) / 5.0f; } if ( cursor == charIndex-1 ) { backup *= 2.0f; } else { renderSystem->SetColor(newColor); } DrawEditCursor(x - backup, y, scale); } renderSystem->SetColor(newColor); continue; } else { scaledGlyphInfo_t glyphInfo; activeFont->GetScaledGlyph( scale, textChar, glyphInfo ); prevGlyphSkip = glyphInfo.xSkip; PaintChar( x, y, glyphInfo ); if (cursor == charIndex-1) { DrawEditCursor(x, y, scale); } x += glyphInfo.xSkip + adjust; } } if (cursor == len) { DrawEditCursor(x, y, scale); } } return drawText.Length(); } void idDeviceContext::SetSize( float width, float height ) { xScale = VIRTUAL_WIDTH / width; yScale = VIRTUAL_HEIGHT / height; } void idDeviceContext::SetOffset( float x, float y ) { xOffset = x; yOffset = y; } int idDeviceContext::CharWidth( const char c, float scale ) { return idMath::Ftoi( activeFont->GetGlyphWidth( scale, c ) ); } int idDeviceContext::TextWidth( const char *text, float scale, int limit ) { if ( text == NULL ) { return 0; } int i; float width = 0; if ( limit > 0 ) { for ( i = 0; text[i] != '\0' && i < limit; i++ ) { if ( idStr::IsColor( text + i ) ) { i++; } else { width += activeFont->GetGlyphWidth( scale, ((const unsigned char *)text)[i] ); } } } else { for ( i = 0; text[i] != '\0'; i++ ) { if ( idStr::IsColor( text + i ) ) { i++; } else { width += activeFont->GetGlyphWidth( scale, ((const unsigned char *)text)[i] ); } } } return idMath::Ftoi( width ); } int idDeviceContext::TextHeight(const char *text, float scale, int limit) { return idMath::Ftoi( activeFont->GetLineHeight( scale ) ); } int idDeviceContext::MaxCharWidth(float scale) { return idMath::Ftoi( activeFont->GetMaxCharWidth( scale ) ); } int idDeviceContext::MaxCharHeight(float scale) { return idMath::Ftoi( activeFont->GetLineHeight( scale ) ); } const idMaterial *idDeviceContext::GetScrollBarImage(int index) { if (index >= SCROLLBAR_HBACK && index < SCROLLBAR_COUNT) { return scrollBarImages[index]; } return scrollBarImages[SCROLLBAR_HBACK]; } // this only supports left aligned text idRegion *idDeviceContext::GetTextRegion(const char *text, float textScale, idRectangle rectDraw, float xStart, float yStart) { return NULL; } void idDeviceContext::DrawEditCursor( float x, float y, float scale ) { if ( (int)( idLib::frameNumber >> 4 ) & 1 ) { return; } char cursorChar = (overStrikeMode) ? '_' : '|'; scaledGlyphInfo_t glyphInfo; activeFont->GetScaledGlyph( scale, cursorChar, glyphInfo ); PaintChar( x, y, glyphInfo ); } int idDeviceContext::DrawText( const char *text, float textScale, int textAlign, idVec4 color, idRectangle rectDraw, bool wrap, int cursor, bool calcOnly, idList *breaks, int limit ) { int count = 0; int charIndex = 0; int lastBreak = 0; float y = 0.0f; float textWidth = 0.0f; float textWidthAtLastBreak = 0.0f; float charSkip = MaxCharWidth( textScale ) + 1; float lineSkip = MaxCharHeight( textScale ); bool lineBreak = false; bool wordBreak = false; idStr drawText = text; idStr textBuffer; if (!calcOnly && !(text && *text)) { if (cursor == 0) { renderSystem->SetColor(color); DrawEditCursor(rectDraw.x, lineSkip + rectDraw.y, textScale); } return idMath::Ftoi( rectDraw.w / charSkip ); } y = lineSkip + rectDraw.y; if ( breaks ) { breaks->Append(0); } while ( charIndex < drawText.Length() ) { uint32 textChar = drawText.UTF8Char( charIndex ); // See if we need to start a new line. if ( textChar == '\n' || textChar == '\r' || charIndex == drawText.Length() ) { lineBreak = true; if ( charIndex < drawText.Length() ) { // New line character and we still have more text to read. char nextChar = drawText[ charIndex + 1 ]; if ( ( textChar == '\n' && nextChar == '\r' ) || ( textChar == '\r' && nextChar == '\n' ) ) { // Just absorb extra newlines. textChar = drawText.UTF8Char( charIndex ); } } } // Check for escape colors if not then simply get the glyph width. if ( textChar == C_COLOR_ESCAPE && charIndex < drawText.Length() ) { textBuffer.AppendUTF8Char( textChar ); textChar = drawText.UTF8Char( charIndex ); } // If the character isn't a new line then add it to the text buffer. if ( textChar != '\n' && textChar != '\r' ) { textWidth += activeFont->GetGlyphWidth( textScale, textChar ); textBuffer.AppendUTF8Char( textChar ); } if ( !lineBreak && ( textWidth > rectDraw.w ) ) { // The next character will cause us to overflow, if we haven't yet found a suitable // break spot, set it to be this character if ( textBuffer.Length() > 0 && lastBreak == 0 ) { lastBreak = textBuffer.Length(); textWidthAtLastBreak = textWidth; } wordBreak = true; } else if ( lineBreak || ( wrap && ( textChar == ' ' || textChar == '\t' ) ) ) { // The next character is in view, so if we are a break character, store our position lastBreak = textBuffer.Length(); textWidthAtLastBreak = textWidth; } // We need to go to a new line if ( lineBreak || wordBreak ) { float x = rectDraw.x; if ( textWidthAtLastBreak > 0 ) { textWidth = textWidthAtLastBreak; } // Align text if needed if (textAlign == ALIGN_RIGHT) { x = rectDraw.x + rectDraw.w - textWidth; } else if (textAlign == ALIGN_CENTER) { x = rectDraw.x + (rectDraw.w - textWidth) / 2; } if ( wrap || lastBreak > 0 ) { // This is a special case to handle breaking in the middle of a word. // if we didn't do this, the cursor would appear on the end of this line // and the beginning of the next. if ( wordBreak && cursor >= lastBreak && lastBreak == textBuffer.Length() ) { cursor++; } } // Draw what's in the current text buffer. if (!calcOnly) { if ( lastBreak > 0 ) { count += DrawText(x, y, textScale, color, textBuffer.Left( lastBreak ).c_str(), 0, 0, 0, cursor); textBuffer = textBuffer.Right( textBuffer.Length() - lastBreak ); } else { count += DrawText(x, y, textScale, color, textBuffer.c_str(), 0, 0, 0, cursor); textBuffer.Clear(); } } if ( cursor < lastBreak ) { cursor = -1; } else if ( cursor >= 0 ) { cursor -= ( lastBreak + 1 ); } // If wrap is disabled return at this point. if ( !wrap ) { return lastBreak; } // If we've hit the allowed character limit then break. if ( limit && count > limit ) { break; } y += lineSkip + 5; if ( !calcOnly && y > rectDraw.Bottom() ) { break; } // If breaks were requested then make a note of this one. if (breaks) { breaks->Append( drawText.Length() - charIndex ); } // Reset necessary parms for next line. lastBreak = 0; textWidth = 0; textWidthAtLastBreak = 0; lineBreak = false; wordBreak = false; // Reassess the remaining width for ( int i = 0; i < textBuffer.Length(); ) { if ( textChar != C_COLOR_ESCAPE ) { textWidth += activeFont->GetGlyphWidth( textScale, textBuffer.UTF8Char( i ) ); } } continue; } } return idMath::Ftoi( rectDraw.w / charSkip ); } /* ============= idRectangle::String ============= */ char *idRectangle::String() const { static int index = 0; static char str[ 8 ][ 48 ]; char *s; // use an array so that multiple toString's won't collide s = str[ index ]; index = (index + 1)&7; sprintf( s, "%.2f %.2f %.2f %.2f", x, y, w, h ); return s; } /* ================================================================================================ OPTIMIZED VERSIONS ================================================================================================ */ // this is only called for the cursor and debug strings, and it should // scope properly with push/pop clipRect void idDeviceContextOptimized::EnableClipping(bool b) { if ( b == enableClipping ) { return; } enableClipping = b; if ( !enableClipping ) { PopClipRect(); } else { // the actual value of the rect is irrelvent PushClipRect( idRectangle( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT ) ); // allow drawing beyond the normal bounds for debug text // this also allows the cursor to draw outside, so we might want // to make this exactly the screen bounds, since we aren't likely // to ever turn on the gui debug text again... clipX1 = -SCREEN_WIDTH; clipX2 = SCREEN_WIDTH * 2; clipY1 = -SCREEN_HEIGHT; clipY2 = SCREEN_HEIGHT * 2; } }; void idDeviceContextOptimized::PopClipRect() { if (clipRects.Num()) { clipRects.SetNum( clipRects.Num()-1 ); // don't resize the list, just change num } if ( clipRects.Num() > 0 ) { const idRectangle & clipRect = clipRects[ clipRects.Num() - 1 ]; clipX1 = clipRect.x; clipY1 = clipRect.y; clipX2 = clipRect.x + clipRect.w; clipY2 = clipRect.y + clipRect.h; } else { clipX1 = 0; clipY1 = 0; clipX2 = SCREEN_WIDTH; clipY2 = SCREEN_HEIGHT; } } static const idRectangle baseScreenRect( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT ); void idDeviceContextOptimized::PushClipRect(idRectangle r) { const idRectangle & prev = ( clipRects.Num() == 0 ) ? baseScreenRect : clipRects[clipRects.Num()-1]; // instead of storing the rect, store the intersection of the rect // with the previous rect, so ClippedCoords() only has to test against one rect idRectangle intersection = prev; intersection.ClipAgainst( r, false ); clipRects.Append( intersection ); const idRectangle & clipRect = clipRects[ clipRects.Num() - 1 ]; clipX1 = clipRect.x; clipY1 = clipRect.y; clipX2 = clipRect.x + clipRect.w; clipY2 = clipRect.y + clipRect.h; } bool idDeviceContextOptimized::ClippedCoords(float *x, float *y, float *w, float *h, float *s1, float *t1, float *s2, float *t2) { const float ox = *x; const float oy = *y; const float ow = *w; const float oh = *h; // presume visible first if ( ox >= clipX1 && oy >= clipY1 && ox + ow <= clipX2 && oy + oh <= clipY2 ) { return false; } // do clipping if ( ox < clipX1 ) { *w -= clipX1 - ox; *x = clipX1; } else if ( ox > clipX2 ) { return true; } if ( oy < clipY1) { *h -= clipY1 - oy; *y = clipY1; } else if ( oy > clipY2) { return true; } if ( *x + *w > clipX2 ) { *w = clipX2 - *x; } if ( *y + *h > clipY2 ) { *h = clipY2 - *y; } if ( *w <= 0 || *h <= 0 ) { return true; } // filled rects won't pass in texcoords if ( s1 ) { float ns1, ns2, nt1, nt2; // upper left float u = ( *x - ox ) / ow; ns1 = *s1 * ( 1.0f - u ) + *s2 * ( u ); // upper right u = ( *x + *w - ox ) / ow; ns2 = *s1 * ( 1.0f - u ) + *s2 * ( u ); // lower left u = ( *y - oy ) / oh; nt1 = *t1 * ( 1.0f - u ) + *t2 * ( u ); // lower right u = ( *y + *h - oy ) / oh; nt2 = *t1 * ( 1.0f - u ) + *t2 * ( u ); // set values *s1 = ns1; *s2 = ns2; *t1 = nt1; *t2 = nt2; } // still needs to be drawn return false; } /* ============= idDeviceContextOptimized::DrawText ============= */ static triIndex_t quadPicIndexes[6] = { 3, 0, 2, 2, 0, 1 }; int idDeviceContextOptimized::DrawText(float x, float y, float scale, idVec4 color, const char *text, float adjust, int limit, int style, int cursor) { if ( !matIsIdentity || cursor != -1 ) { // fallback to old code return idDeviceContext::DrawText( x, y, scale, color, text, adjust, limit, style, cursor ); } idStr drawText = text; if ( drawText.Length() == 0 ) { return 0; } if ( color.w == 0.0f ) { return 0; } const uint32 currentColor = PackColor( color ); uint32 currentColorNativeByteOrder = LittleLong( currentColor ); int len = drawText.Length(); if (limit > 0 && len > limit) { len = limit; } int charIndex = 0; while ( charIndex < drawText.Length() ) { uint32 textChar = drawText.UTF8Char( charIndex ); if ( textChar == C_COLOR_ESCAPE ) { // I'm not sure if inline text color codes are used anywhere in the game, // they may only be needed for multi-color user names idVec4 newColor; uint32 colorIndex = drawText.UTF8Char( charIndex ); if ( colorIndex == C_COLOR_DEFAULT ) { newColor = color; } else { newColor = idStr::ColorForIndex( colorIndex ); newColor[3] = color[3]; } renderSystem->SetColor(newColor); currentColorNativeByteOrder = LittleLong( PackColor( newColor ) ); continue; } scaledGlyphInfo_t glyphInfo; activeFont->GetScaledGlyph( scale, textChar, glyphInfo ); // PaintChar( x, y, glyphInfo ); float drawY = y - glyphInfo.top; float drawX = x + glyphInfo.left; float w = glyphInfo.width; float h = glyphInfo.height; float s = glyphInfo.s1; float t = glyphInfo.t1; float s2 = glyphInfo.s2; float t2 = glyphInfo.t2; if ( !ClippedCoords( &drawX, &drawY, &w, &h, &s, &t, &s2, &t2) ) { float x1 = xOffset + drawX * xScale; float x2 = xOffset + ( drawX + w ) * xScale; float y1 = yOffset + drawY * yScale; float y2 = yOffset + ( drawY + h ) * yScale; idDrawVert * verts = tr_guiModel->AllocTris( 4, quadPicIndexes, 6, glyphInfo.material, 0, STEREO_DEPTH_TYPE_NONE ); if ( verts != NULL ) { verts[0].xyz[0] = x1; verts[0].xyz[1] = y1; verts[0].xyz[2] = 0.0f; verts[0].SetTexCoord( s, t ); verts[0].SetNativeOrderColor( currentColorNativeByteOrder ); verts[0].ClearColor2(); verts[1].xyz[0] = x2; verts[1].xyz[1] = y1; verts[1].xyz[2] = 0.0f; verts[1].SetTexCoord( s2, t ); verts[1].SetNativeOrderColor( currentColorNativeByteOrder ); verts[1].ClearColor2(); verts[2].xyz[0] = x2; verts[2].xyz[1] = y2; verts[2].xyz[2] = 0.0f; verts[2].SetTexCoord( s2, t2 ); verts[2].SetNativeOrderColor( currentColorNativeByteOrder ); verts[2].ClearColor2(); verts[3].xyz[0] = x1; verts[3].xyz[1] = y2; verts[3].xyz[2] = 0.0f; verts[3].SetTexCoord( s, t2 ); verts[3].SetNativeOrderColor( currentColorNativeByteOrder ); verts[3].ClearColor2(); } } x += glyphInfo.xSkip + adjust; } return drawText.Length(); }