dhewm3/neo/ui/DeviceContext.cpp
Daniel Gibson e41bf2b147 Scale "Menu" GUIs (incl. PDA + fullscreen vids) to 4:3
So stuff doesn't look so distorted in widescreen resolutions.
Implies that there are black bars on the left/right then..

Can be disabled with "r_scaleMenusTo43 0"

Does *not* affect the HUD (incl. crosshair) - scaling it automagically
would be very hard (or impossible), because it doesn't only render
the crosshair, healthpoints etc, but also fullscreen effects like the
screen turning red when the player is hit - and fullscreen effects
would look very shitty if they didn't cover the whole screen but had
"empty" bars on left/right.

(Mostly) fixes #188 and #189
2018-10-28 05:29:11 +01:00

1169 lines
30 KiB
C++

/*
===========================================================================
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 <http://www.gnu.org/licenses/>.
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<fontInfoEx_t> 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<int> *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;
}