/* =========================================================================== Doom 3 GPL Source Code Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code"). Doom 3 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 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 Source Code. If not, see . In addition, the Doom 3 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 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. =========================================================================== */ #include "sys/platform.h" #include "idlib/geometry/DrawVert.h" #include "ui/DeviceContext.h" 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; idCVar gui_smallFontLimit( "gui_smallFontLimit", "0.30", CVAR_GUI | CVAR_ARCHIVE, "" ); idCVar gui_mediumFontLimit( "gui_mediumFontLimit", "0.60", CVAR_GUI | CVAR_ARCHIVE, "" ); idList idDeviceContext::fonts; int idDeviceContext::FindFont( const char *name ) { int c = fonts.Num(); for (int i = 0; i < c; i++) { if (idStr::Icmp(name, fonts[i].name) == 0) { return i; } } // If the font was not found, try to register it idStr fileName = name; fileName.Replace("fonts", va("fonts/%s", fontLang.c_str()) ); fontInfoEx_t fontInfo; int index = fonts.Append( fontInfo ); if ( renderSystem->RegisterFont( fileName, fonts[index] ) ){ idStr::Copynz( fonts[index].name, name, sizeof( fonts[index].name ) ); return index; } else { common->Printf( "Could not register font %s [%s]\n", name, fileName.c_str() ); return -1; } } void idDeviceContext::SetupFonts() { fonts.SetGranularity( 1 ); fontLang = cvarSystem->GetCVarString( "sys_lang" ); // western european languages can use the english font if ( fontLang == "french" || fontLang == "german" || fontLang == "spanish" || fontLang == "italian" ) { fontLang = "english"; } // Default font has to be added first FindFont( "fonts" ); } void idDeviceContext::SetFont( int num ) { if ( num >= 0 && num < fonts.Num() ) { activeFont = &fonts[num]; } else { activeFont = &fonts[0]; } } void idDeviceContext::Init() { xScale = 0.0; SetSize(VIRTUAL_WIDTH, VIRTUAL_HEIGHT); whiteImage = declManager->FindMaterial("guis/assets/white.tga"); whiteImage->SetSort( SS_GUI ); mbcs = false; SetupFonts(); activeFont = &fonts[0]; 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"); 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(); origin.Zero(); initialized = true; // DG: this is used for the "make sure menus are rendered as 4:3" hack fixScaleForMenu.Set(1, 1); fixOffsetForMenu.Set(0, 0); } void idDeviceContext::Shutdown() { fontName.Clear(); clipRects.Clear(); fonts.Clear(); Clear(); } void idDeviceContext::Clear() { initialized = false; useFont = NULL; activeFont = NULL; mbcs = false; } idDeviceContext::idDeviceContext() { Clear(); } void idDeviceContext::SetTransformInfo(const idVec3 &org, const idMat3 &m) { origin = org; mat = m; } // // added method void idDeviceContext::GetTransformInfo(idVec3& org, idMat3& m ) { m = mat; org = origin; } // void idDeviceContext::PopClipRect() { if (clipRects.Num()) { clipRects.RemoveIndex(clipRects.Num()-1); } } void idDeviceContext::PushClipRect(idRectangle r) { clipRects.Append(r); } void idDeviceContext::PushClipRect(float x, float y, float w, float h) { clipRects.Append(idRectangle(x, y, w, h)); } 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; } // DG: this is used for the "make sure menus are rendered as 4:3" hack void idDeviceContext::SetMenuScaleFix(bool enable) { if(enable) { float w = renderSystem->GetScreenWidth(); float h = renderSystem->GetScreenHeight(); float aspectRatio = w/h; static const float virtualAspectRatio = float(VIRTUAL_WIDTH)/float(VIRTUAL_HEIGHT); // 4:3 if(aspectRatio > 1.4f) { // widescreen (4:3 is 1.333 3:2 is 1.5, 16:10 is 1.6, 16:9 is 1.7778) // => we need to scale and offset X // All the coordinates here assume 640x480 (VIRTUAL_WIDTH x VIRTUAL_HEIGHT) // screensize, so to fit a 4:3 menu into 640x480 stretched to a widescreen, // we need do decrease the width to something smaller than 640 and center // the result with an offset float scaleX = virtualAspectRatio/aspectRatio; float offsetX = (1.0f-scaleX)*(VIRTUAL_WIDTH*0.5f); // (640 - scale*640)/2 fixScaleForMenu.Set(scaleX, 1); fixOffsetForMenu.Set(offsetX, 0); } else if(aspectRatio < 1.24f) { // portrait-mode, "thinner" than 5:4 (which is 1.25) // => we need to scale and offset Y // it's analogue to the other case, but inverted and with height and Y float scaleY = aspectRatio/virtualAspectRatio; float offsetY = (1.0f - scaleY)*(VIRTUAL_HEIGHT*0.5f); // (480 - scale*480)/2 fixScaleForMenu.Set(1, scaleY); fixOffsetForMenu.Set(0, offsetY); } } else { fixScaleForMenu.Set(1, 1); fixOffsetForMenu.Set(0, 0); } } void idDeviceContext::AdjustCoords(float *x, float *y, float *w, float *h) { if (x) { *x *= xScale; *x *= fixScaleForMenu.x; // DG: for "render menus as 4:3" hack *x += fixOffsetForMenu.x; } if (y) { *y *= yScale; *y *= fixScaleForMenu.y; // DG: for "render menus as 4:3" hack *y += fixOffsetForMenu.y; } if (w) { *w *= xScale; *w *= fixScaleForMenu.x; // DG: for "render menus as 4:3" hack } if (h) { *h *= yScale; *h *= fixScaleForMenu.y; // DG: for "render menus as 4:3" hack } } // DG: same as AdjustCoords, but ignore fixupMenus because for the cursor that must be handled seperately void idDeviceContext::AdjustCursorCoords(float *x, float *y, float *w, float *h) { if (x) { *x *= xScale; } if (y) { *y *= yScale; } if (w) { *w *= xScale; } if (h) { *h *= yScale; } } void idDeviceContext::DrawStretchPic(float x, float y, float w, float h, float s1, float t1, float s2, float t2, const idMaterial *shader) { idDrawVert verts[4]; glIndex_t indexes[6]; indexes[0] = 3; indexes[1] = 0; indexes[2] = 2; indexes[3] = 2; indexes[4] = 0; indexes[5] = 1; verts[0].xyz[0] = x; verts[0].xyz[1] = y; verts[0].xyz[2] = 0; verts[0].st[0] = s1; verts[0].st[1] = t1; verts[0].normal[0] = 0; verts[0].normal[1] = 0; verts[0].normal[2] = 1; verts[0].tangents[0][0] = 1; verts[0].tangents[0][1] = 0; verts[0].tangents[0][2] = 0; verts[0].tangents[1][0] = 0; verts[0].tangents[1][1] = 1; verts[0].tangents[1][2] = 0; verts[1].xyz[0] = x + w; verts[1].xyz[1] = y; verts[1].xyz[2] = 0; verts[1].st[0] = s2; verts[1].st[1] = t1; verts[1].normal[0] = 0; verts[1].normal[1] = 0; verts[1].normal[2] = 1; verts[1].tangents[0][0] = 1; verts[1].tangents[0][1] = 0; verts[1].tangents[0][2] = 0; verts[1].tangents[1][0] = 0; verts[1].tangents[1][1] = 1; verts[1].tangents[1][2] = 0; verts[2].xyz[0] = x + w; verts[2].xyz[1] = y + h; verts[2].xyz[2] = 0; verts[2].st[0] = s2; verts[2].st[1] = t2; verts[2].normal[0] = 0; verts[2].normal[1] = 0; verts[2].normal[2] = 1; verts[2].tangents[0][0] = 1; verts[2].tangents[0][1] = 0; verts[2].tangents[0][2] = 0; verts[2].tangents[1][0] = 0; verts[2].tangents[1][1] = 1; verts[2].tangents[1][2] = 0; verts[3].xyz[0] = x; verts[3].xyz[1] = y + h; verts[3].xyz[2] = 0; verts[3].st[0] = s1; verts[3].st[1] = t2; verts[3].normal[0] = 0; verts[3].normal[1] = 0; verts[3].normal[2] = 1; verts[3].tangents[0][0] = 1; verts[3].tangents[0][1] = 0; verts[3].tangents[0][2] = 0; verts[3].tangents[1][0] = 0; verts[3].tangents[1][1] = 1; verts[3].tangents[1][2] = 0; bool ident = !mat.IsIdentity(); if ( ident ) { verts[0].xyz -= origin; verts[0].xyz *= mat; verts[0].xyz += origin; verts[1].xyz -= origin; verts[1].xyz *= mat; verts[1].xyz += origin; verts[2].xyz -= origin; verts[2].xyz *= mat; verts[2].xyz += origin; verts[3].xyz -= origin; verts[3].xyz *= mat; verts[3].xyz += origin; } renderSystem->DrawStretchPic( &verts[0], &indexes[0], 4, 6, shader, ident ); } 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; } AdjustCoords(&x, &y, &w, &h); 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; } AdjustCoords(&x, &y, &w, &h); 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) { idDrawVert verts[4]; glIndex_t indexes[6]; indexes[0] = 3; indexes[1] = 0; indexes[2] = 2; indexes[3] = 2; indexes[4] = 0; indexes[5] = 1; verts[0].xyz[0] = x; verts[0].xyz[1] = y; verts[0].xyz[2] = 0; verts[0].st[0] = s1; verts[0].st[1] = t1; verts[0].normal[0] = 0; verts[0].normal[1] = 0; verts[0].normal[2] = 1; verts[0].tangents[0][0] = 1; verts[0].tangents[0][1] = 0; verts[0].tangents[0][2] = 0; verts[0].tangents[1][0] = 0; verts[0].tangents[1][1] = 1; verts[0].tangents[1][2] = 0; verts[1].xyz[0] = x + w; verts[1].xyz[1] = y; verts[1].xyz[2] = 0; verts[1].st[0] = s2; verts[1].st[1] = t1; verts[1].normal[0] = 0; verts[1].normal[1] = 0; verts[1].normal[2] = 1; verts[1].tangents[0][0] = 1; verts[1].tangents[0][1] = 0; verts[1].tangents[0][2] = 0; verts[1].tangents[1][0] = 0; verts[1].tangents[1][1] = 1; verts[1].tangents[1][2] = 0; verts[2].xyz[0] = x + w; verts[2].xyz[1] = y + h; verts[2].xyz[2] = 0; verts[2].st[0] = s2; verts[2].st[1] = t2; verts[2].normal[0] = 0; verts[2].normal[1] = 0; verts[2].normal[2] = 1; verts[2].tangents[0][0] = 1; verts[2].tangents[0][1] = 0; verts[2].tangents[0][2] = 0; verts[2].tangents[1][0] = 0; verts[2].tangents[1][1] = 1; verts[2].tangents[1][2] = 0; verts[3].xyz[0] = x; verts[3].xyz[1] = y + h; verts[3].xyz[2] = 0; verts[3].st[0] = s1; verts[3].st[1] = t2; verts[3].normal[0] = 0; verts[3].normal[1] = 0; verts[3].normal[2] = 1; verts[3].tangents[0][0] = 1; verts[3].tangents[0][1] = 0; verts[3].tangents[0][2] = 0; verts[3].tangents[1][0] = 0; verts[3].tangents[1][1] = 1; verts[3].tangents[1][2] = 0; bool ident = !mat.IsIdentity(); if ( ident ) { verts[0].xyz -= origin; verts[0].xyz *= mat; verts[0].xyz += origin; verts[1].xyz -= origin; verts[1].xyz *= mat; verts[1].xyz += origin; verts[2].xyz -= origin; verts[2].xyz *= mat; verts[2].xyz += origin; verts[3].xyz -= origin; verts[3].xyz *= mat; verts[3].xyz += 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 idMat4 rotz; rotz.Identity(); float sinAng = idMath::Sin(angle); float cosAng = idMath::Cos(angle); rotz[0][0] = cosAng; rotz[0][1] = sinAng; rotz[1][0] = -sinAng; rotz[1][1] = cosAng; for(int i = 0; i < 4; i++) { //Translate to origin verts[i].xyz -= origTrans; //Rotate verts[i].xyz = rotz * verts[i].xyz; //Translate back verts[i].xyz += origTrans; } renderSystem->DrawStretchPic( &verts[0], &indexes[0], 4, 6, shader, (angle == 0.0) ? false : true ); } 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; } AdjustCoords(&x, &y, &w, &h); 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; } AdjustCoords(&x, &y, &w, &h); 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) { cursor = (n < CURSOR_ARROW || n >= CURSOR_COUNT) ? CURSOR_ARROW : n; } void idDeviceContext::DrawCursor(float *x, float *y, float size) { if (*x < 0) { *x = 0; } if (*x >= vidWidth) { *x = vidWidth; } if (*y < 0) { *y = 0; } if (*y >= vidHeight) { *y = vidHeight; } renderSystem->SetColor(colorWhite); // DG: I use this instead of plain AdjustCursorCoords and the following lines // to scale menus and other fullscreen GUIs to 4:3 aspect ratio AdjustCursorCoords(x, y, &size, &size); float sizeW = size * fixScaleForMenu.x; float sizeH = size * fixScaleForMenu.y; float fixedX = *x * fixScaleForMenu.x + fixOffsetForMenu.x; float fixedY = *y * fixScaleForMenu.y + fixOffsetForMenu.y; DrawStretchPic(fixedX, fixedY, sizeW, sizeH, 0, 0, 1, 1, cursorImages[cursor]); } /* ======================================================================================================================= ======================================================================================================================= */ void idDeviceContext::PaintChar(float x,float y,float width,float height,float scale,float s,float t,float s2,float t2,const idMaterial *hShader) { float w, h; w = width * scale; h = height * scale; if (ClippedCoords(&x, &y, &w, &h, &s, &t, &s2, &t2)) { return; } AdjustCoords(&x, &y, &w, &h); DrawStretchPic(x, y, w, h, s, t, s2, t2, hShader); } void idDeviceContext::SetFontByScale(float scale) { if (scale <= gui_smallFontLimit.GetFloat()) { useFont = &activeFont->fontInfoSmall; activeFont->maxHeight = activeFont->maxHeightSmall; activeFont->maxWidth = activeFont->maxWidthSmall; } else if (scale <= gui_mediumFontLimit.GetFloat()) { useFont = &activeFont->fontInfoMedium; activeFont->maxHeight = activeFont->maxHeightMedium; activeFont->maxWidth = activeFont->maxWidthMedium; } else { useFont = &activeFont->fontInfoLarge; activeFont->maxHeight = activeFont->maxHeightLarge; activeFont->maxWidth = activeFont->maxWidthLarge; } } int idDeviceContext::DrawText(float x, float y, float scale, idVec4 color, const char *text, float adjust, int limit, int style, int cursor) { int len, count; idVec4 newColor; const glyphInfo_t *glyph; float useScale; SetFontByScale(scale); useScale = scale * useFont->glyphScale; count = 0; if ( text && color.w != 0.0f ) { const unsigned char *s = (const unsigned char*)text; renderSystem->SetColor(color); memcpy(&newColor[0], &color[0], sizeof(idVec4)); len = strlen(text); if (limit > 0 && len > limit) { len = limit; } while (s && *s && count < len) { if ( *s < GLYPH_START || *s > GLYPH_END ) { s++; continue; } glyph = &useFont->glyphs[*s]; // // int yadj = Assets.textFont.glyphs[text[i]].bottom + // Assets.textFont.glyphs[text[i]].top; float yadj = scale * // (Assets.textFont.glyphs[text[i]].imageHeight - // Assets.textFont.glyphs[text[i]].height); // if ( idStr::IsColor((const char*)s) ) { if ( *(s+1) == C_COLOR_DEFAULT ) { newColor = color; } else { newColor = idStr::ColorForIndex( *(s+1) ); newColor[3] = color[3]; } if (cursor == count || cursor == count+1) { float partialSkip = ((glyph->xSkip * useScale) + adjust) / 5.0f; if ( cursor == count ) { partialSkip *= 2.0f; } else { renderSystem->SetColor(newColor); } DrawEditCursor(x - partialSkip, y, scale); } renderSystem->SetColor(newColor); s += 2; count += 2; continue; } else { float yadj = useScale * glyph->top; PaintChar(x,y - yadj,glyph->imageWidth,glyph->imageHeight,useScale,glyph->s,glyph->t,glyph->s2,glyph->t2,glyph->glyph); if (cursor == count) { DrawEditCursor(x, y, scale); } x += (glyph->xSkip * useScale) + adjust; s++; count++; } } if (cursor == len) { DrawEditCursor(x, y, scale); } } return count; } void idDeviceContext::SetSize(float width, float height) { vidWidth = VIRTUAL_WIDTH; vidHeight = VIRTUAL_HEIGHT; xScale = yScale = 0.0f; if ( width != 0.0f && height != 0.0f ) { xScale = vidWidth * ( 1.0f / width ); yScale = vidHeight * ( 1.0f / height ); } } int idDeviceContext::CharWidth( const char c, float scale ) { glyphInfo_t *glyph; float useScale; SetFontByScale(scale); fontInfo_t *font = useFont; useScale = scale * font->glyphScale; glyph = &font->glyphs[(const unsigned char)c]; return idMath::FtoiFast( glyph->xSkip * useScale ); } int idDeviceContext::TextWidth( const char *text, float scale, int limit ) { int i, width; SetFontByScale( scale ); const glyphInfo_t *glyphs = useFont->glyphs; if ( text == NULL ) { return 0; } width = 0; if ( limit > 0 ) { for ( i = 0; text[i] != '\0' && i < limit; i++ ) { if ( idStr::IsColor( text + i ) ) { i++; } else { width += glyphs[((const unsigned char *)text)[i]].xSkip; } } } else { for ( i = 0; text[i] != '\0'; i++ ) { if ( idStr::IsColor( text + i ) ) { i++; } else { width += glyphs[((const unsigned char *)text)[i]].xSkip; } } } return idMath::FtoiFast( scale * useFont->glyphScale * width ); } int idDeviceContext::TextHeight(const char *text, float scale, int limit) { int len, count; float max; glyphInfo_t *glyph; float useScale; const char *s = text; SetFontByScale(scale); fontInfo_t *font = useFont; useScale = scale * font->glyphScale; max = 0; if (text) { len = strlen(text); if (limit > 0 && len > limit) { len = limit; } count = 0; while (s && *s && count < len) { if ( idStr::IsColor(s) ) { s += 2; continue; } else { glyph = &font->glyphs[*(const unsigned char*)s]; if (max < glyph->height) { max = glyph->height; } s++; count++; } } } return idMath::FtoiFast( max * useScale ); } int idDeviceContext::MaxCharWidth(float scale) { SetFontByScale(scale); float useScale = scale * useFont->glyphScale; return idMath::FtoiFast( activeFont->maxWidth * useScale ); } int idDeviceContext::MaxCharHeight(float scale) { SetFontByScale(scale); float useScale = scale * useFont->glyphScale; return idMath::FtoiFast( activeFont->maxHeight * useScale ); } 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) { #if 0 const char *p, *textPtr, *newLinePtr; char buff[1024]; int len, textWidth, newLine, newLineWidth; float y; float charSkip = MaxCharWidth(textScale) + 1; float lineSkip = MaxCharHeight(textScale); textWidth = 0; newLinePtr = NULL; #endif return NULL; /* if (text == NULL) { return; } textPtr = text; if (*textPtr == '\0') { return; } y = lineSkip + rectDraw.y + yStart; len = 0; buff[0] = '\0'; newLine = 0; newLineWidth = 0; p = textPtr; textWidth = 0; while (p) { if (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\0') { newLine = len; newLinePtr = p + 1; newLineWidth = textWidth; } if ((newLine && textWidth > rectDraw.w) || *p == '\n' || *p == '\0') { if (len) { float x = rectDraw.x ; buff[newLine] = '\0'; DrawText(x, y, textScale, color, buff, 0, 0, 0); if (!wrap) { return; } } if (*p == '\0') { break; } y += lineSkip + 5; p = newLinePtr; len = 0; newLine = 0; newLineWidth = 0; continue; } buff[len++] = *p++; buff[len] = '\0'; textWidth = TextWidth( buff, textScale, -1 ); } */ } void idDeviceContext::DrawEditCursor( float x, float y, float scale ) { if ( (int)( com_ticNumber >> 4 ) & 1 ) { return; } SetFontByScale(scale); float useScale = scale * useFont->glyphScale; const glyphInfo_t *glyph2 = &useFont->glyphs[overStrikeMode ? int('_') : int('|')]; float yadj = useScale * glyph2->top; PaintChar(x, y - yadj,glyph2->imageWidth,glyph2->imageHeight,useScale,glyph2->s,glyph2->t,glyph2->s2,glyph2->t2,glyph2->glyph); } int idDeviceContext::DrawText( const char *text, float textScale, int textAlign, idVec4 color, idRectangle rectDraw, bool wrap, int cursor, bool calcOnly, idList *breaks, int limit ) { const char *p, *textPtr, *newLinePtr; char buff[1024]; int len, newLine, newLineWidth, count; float y; float textWidth; float charSkip = MaxCharWidth( textScale ) + 1; float lineSkip = MaxCharHeight( textScale ); float cursorSkip = ( cursor >= 0 ? charSkip : 0 ); bool lineBreak, wordBreak; SetFontByScale( textScale ); textWidth = 0; newLinePtr = NULL; if (!calcOnly && !(text && *text)) { if (cursor == 0) { renderSystem->SetColor(color); DrawEditCursor(rectDraw.x, lineSkip + rectDraw.y, textScale); } return idMath::FtoiFast( rectDraw.w / charSkip ); } textPtr = text; y = lineSkip + rectDraw.y; len = 0; buff[0] = '\0'; newLine = 0; newLineWidth = 0; p = textPtr; if ( breaks ) { breaks->Append(0); } count = 0; textWidth = 0; lineBreak = false; wordBreak = false; while (p) { if ( *p == '\n' || *p == '\r' || *p == '\0' ) { lineBreak = true; if ((*p == '\n' && *(p + 1) == '\r') || (*p == '\r' && *(p + 1) == '\n')) { p++; } } int nextCharWidth = ( idStr::CharIsPrintable(*p) ? CharWidth( *p, textScale ) : cursorSkip ); // FIXME: this is a temp hack until the guis can be fixed not not overflow the bounding rectangles // the side-effect is that list boxes and edit boxes will draw over their scroll bars // The following line and the !linebreak in the if statement below should be removed nextCharWidth = 0; if ( !lineBreak && ( textWidth + nextCharWidth ) > 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 ( len > 0 && newLine == 0 ) { newLine = len; newLinePtr = p; newLineWidth = textWidth; } wordBreak = true; } else if ( lineBreak || ( wrap && (*p == ' ' || *p == '\t') ) ) { // The next character is in view, so if we are a break character, store our position newLine = len; newLinePtr = p + 1; newLineWidth = textWidth; } if ( lineBreak || wordBreak ) { float x = rectDraw.x; if (textAlign == ALIGN_RIGHT) { x = rectDraw.x + rectDraw.w - newLineWidth; } else if (textAlign == ALIGN_CENTER) { x = rectDraw.x + (rectDraw.w - newLineWidth) / 2; } if ( wrap || newLine > 0 ) { buff[newLine] = '\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 >= newLine && newLine == len ) { cursor++; } } if (!calcOnly) { count += DrawText(x, y, textScale, color, buff, 0, 0, 0, cursor); } if ( cursor < newLine ) { cursor = -1; } else if ( cursor >= 0 ) { cursor -= ( newLine + 1 ); } if ( !wrap ) { return newLine; } if ( ( limit && count > limit ) || *p == '\0' ) { break; } y += lineSkip + 5; if ( !calcOnly && y > rectDraw.Bottom() ) { break; } p = newLinePtr; if (breaks) { breaks->Append(p - text); } len = 0; newLine = 0; newLineWidth = 0; textWidth = 0; lineBreak = false; wordBreak = false; continue; } buff[len++] = *p++; buff[len] = '\0'; // update the width if ( *( buff + len - 1 ) != C_COLOR_ESCAPE && (len <= 1 || *( buff + len - 2 ) != C_COLOR_ESCAPE)) { textWidth += textScale * useFont->glyphScale * useFont->glyphs[ (const unsigned char)*( buff + len - 1 ) ].xSkip; } } return idMath::FtoiFast( rectDraw.w / charSkip ); } /* ============= idRectangle::String ============= */ char *idRectangle::String( void ) 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; }