/* =========================================================================== 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 "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(); }