dhewm3/neo/ui/DeviceContext.cpp
Daniel Gibson 565d3e3fc1 Make CstDoom3 anchored GUIs work with dhewm3's menu scale fix
In idWindow::Redraw(), I had to make sure the menu scale fix (which,
if enabled for a window, renders that in 4:3 with empty or black bars
on the side if needed for widescreen etc, instead of stretching it)
is disabled if a window uses CST anchors, because the CST anchor code
also adjusts for the display aspect ratio and if we do both, things get
distorted in the other way.

The biggest change is that idDeviceContext::DrawStretchPic(Rotated) now
has code to adjust the coordinates for both CST and the menu scale fix,
so idDeviceContext::AdjustCoords() is mostly obsolete - it's only still
used by idRenderWindow.
Unlike DstDoom3 now that extra adjustCoords argument to those Draw
functions indicates that any coordinate adjustment should be done, so
if it's set by a caller, it's set to true.

I removed idDeviceContext::AdjustCursorCoords() because it was only used
in one place anyway
2025-01-21 03:04:18 +01:00

1395 lines
37 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 = {}; // DG: initialize this
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);
}
}
// DG: Note: not sure if AdjustCoords() works entirely as it should, but it seems
// good enough for the idRenderWindow with the mars globe in the main menu
void idDeviceContext::AdjustCoords(float *x, float *y, float *w, float *h) {
// TODO: not sure about cst_*Offset
if (x) {
*x *= xScale;
*x += cst_xOffset; // DG: for CstDoom3 anchored windows
*x *= fixScaleForMenu.x; // DG: for "render menus as 4:3" hack
*x += fixOffsetForMenu.x;
}
if (y) {
*y *= yScale;
*y += cst_yOffset; // DG: for CstDoom3 anchored windows
*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
}
}
// fva/DG: added adjustCoords argument for CstDoom3 anchored GUIs and our old
// scale-menus-to-4:3-fix, it basically replaces calling AdjustCoords(&x, &y, &w, &h)
// before calling this
void idDeviceContext::DrawStretchPic(float x, float y, float w, float h, float s1, float t1, float s2, float t2, const idMaterial *shader, bool adjustCoords) {
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;
}
//#modified-fva; BEGIN
if (adjustCoords) {
for( int i=0; i<4; ++i) {
// Note: if cstAdjustCoords == false; cst_*Offset is 0, so that doesn't require special handling
float x = verts[i].xyz[0] * xScale + cst_xOffset;
float y = verts[i].xyz[1] * yScale + cst_yOffset;
verts[i].xyz[0] = x * fixScaleForMenu.x + fixOffsetForMenu.x;
verts[i].xyz[1] = y * fixScaleForMenu.y + fixOffsetForMenu.y;
}
}
//#modified-fva; END
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;
}
//#modified-fva; BEGIN
/*
AdjustCoords(&x, &y, &w, &h);
DrawStretchPic( x, y, w, h, s0, t0, s1, t1, mat);
*/
DrawStretchPic(x, y, w, h, s0, t0, s1, t1, mat, true);
//#modified-fva; END
}
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;
}
//#modified-fva; BEGIN
/*
AdjustCoords(&x, &y, &w, &h);
DrawStretchPicRotated( x, y, w, h, s0, t0, s1, t1, mat, angle);
*/
DrawStretchPicRotated(x, y, w, h, s0, t0, s1, t1, mat, angle, true);
//#modified-fva; END
}
// fva/DG: added adjustCoords argument for CstDoom3 anchored GUIs and our old
// scale-menus-to-4:3-fix, it basically replaces calling AdjustCoords(&x, &y, &w, &h)
// before calling this
void idDeviceContext::DrawStretchPicRotated(float x, float y, float w, float h, float s1, float t1, float s2, float t2, const idMaterial *shader, float angle, bool adjustCoords) {
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;
}
//#modified-fva; BEGIN
if (adjustCoords) {
for( int i=0; i<4; ++i) {
// Note: if cstAdjustCoords == false; cst_*Offset is 0, so that doesn't require special handling
float x = verts[i].xyz[0] * xScale + cst_xOffset;
float y = verts[i].xyz[1] * yScale + cst_yOffset;
verts[i].xyz[0] = x * fixScaleForMenu.x + fixOffsetForMenu.x;
verts[i].xyz[1] = y * fixScaleForMenu.y + fixOffsetForMenu.y;
}
}
//#modified-fva; END
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;
}
//#modified-fva; BEGIN
/*
AdjustCoords(&x, &y, &w, &h);
DrawStretchPic( x, y, w, h, 0, 0, 0, 0, whiteImage);
*/
DrawStretchPic(x, y, w, h, 0, 0, 0, 0, whiteImage, true);
//#modified-fva; END
}
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;
}
//#modified-fva; BEGIN
/*
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 );
*/
DrawStretchPic(x, y + size, size, h - 2.0f * size, 0, 0, 0, 0, whiteImage, true);
DrawStretchPic(x + w - size, y + size, size, h - 2.0f * size, 0, 0, 0, 0, whiteImage, true);
DrawStretchPic(x, y, w, size, 0, 0, 0, 0, whiteImage, true);
DrawStretchPic(x, y + h - size, w, size, 0, 0, 0, 0, whiteImage, true);
//#modified-fva; END
}
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: originally, this just called AdjustCoords() and then DrawStretchPic().
// It had to be adjusted to scale menus and other fullscreen GUIs to 4:3 aspect ratio
// and for the CstDoom3 anchored GUIs, so all that is now done here
// the following block used to be Adjust(Cursor)Coords()
// (no point in keeping that function when it's only used here)
*x *= xScale;
*y *= yScale;
size *= xScale;
// TODO: not sure if scaling it by both is the right thing to do..
// but OTOH, probably one of them is always 1, at least when not using an anchor
// (and why would a cursor use an anchor)
size *= yScale;
// the *actual* sizes used (but not set to *x and *y) need to apply the menu fixes
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;
}
//#modified-fva; BEGIN
/*
AdjustCoords(&x, &y, &w, &h);
DrawStretchPic(x, y, w, h, s, t, s2, t2, hShader);
*/
DrawStretchPic(x, y, w, h, s, t, s2, t2, hShader, true);
//#modified-fva; END
}
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 = 1.0f; // DG: I think this was also changed by fva
//#modified-fva; BEGIN
cst_xOffset = cst_yOffset = 0.0f;
cstAdjustCoords = false;
if ((width != vidWidth || height != vidHeight) && width > 0.0f && height > 0.0f) {
cstAdjustCoords = true;
//#modified-fva; END
xScale = vidWidth * ( 1.0f / width );
yScale = vidHeight * ( 1.0f / height );
}
}
//#modified-fva; BEGIN
// ===============
static bool CstGetVidScale(float &_xScale, float &_yScale) {
int glWidth, glHeight;
renderSystem->GetGLSettings(glWidth, glHeight);
if (glWidth <= 0 || glHeight <= 0) {
return false;
}
float glAspectRatio = (float)glWidth / (float)glHeight;
const float vidWidth = VIRTUAL_WIDTH;
const float vidHeight = VIRTUAL_HEIGHT;
const float vidAspectRatio = (float)VIRTUAL_WIDTH / (float)VIRTUAL_HEIGHT;
float modWidth = vidWidth;
float modHeight = vidHeight;
if (glAspectRatio >= vidAspectRatio) {
modWidth = modHeight * glAspectRatio;
} else {
modHeight = modWidth / glAspectRatio;
}
_xScale = vidWidth / modWidth;
_yScale = vidHeight / modHeight;
return true;
}
static void CstAdjustParmsForAnchor(int anchor, float &_xScale, float &_yScale, float &_xOffset, float &_yOffset) {
const float vidWidth = VIRTUAL_WIDTH;
const float vidHeight = VIRTUAL_HEIGHT;
switch (anchor) {
case idDeviceContext::CST_ANCHOR_TOP_LEFT: {
_xOffset = 0.0f;
_yOffset = 0.0f;
break;
}
case idDeviceContext::CST_ANCHOR_TOP_CENTER: {
_xOffset = (vidWidth * 0.5f) * (1.0f - _xScale);
_yOffset = 0.0f;
break;
}
case idDeviceContext::CST_ANCHOR_TOP_RIGHT: {
_xOffset = vidWidth * (1.0f - _xScale);
_yOffset = 0.0f;
break;
}
case idDeviceContext::CST_ANCHOR_CENTER_LEFT: {
_xOffset = 0.0f;
_yOffset = (vidHeight * 0.5f) * (1.0f - _yScale);
break;
}
case idDeviceContext::CST_ANCHOR_CENTER_CENTER: {
_xOffset = (vidWidth * 0.5f) * (1.0f - _xScale);
_yOffset = (vidHeight * 0.5f) * (1.0f - _yScale);
break;
}
case idDeviceContext::CST_ANCHOR_CENTER_RIGHT: {
_xOffset = vidWidth * (1.0f - _xScale);
_yOffset = (vidHeight * 0.5f) * (1.0f - _yScale);
break;
}
case idDeviceContext::CST_ANCHOR_BOTTOM_LEFT: {
_xOffset = 0.0f;
_yOffset = vidHeight * (1.0f - _yScale);
break;
}
case idDeviceContext::CST_ANCHOR_BOTTOM_CENTER: {
_xOffset = (vidWidth * 0.5f) * (1.0f - _xScale);
_yOffset = vidHeight * (1.0f - _yScale);
break;
}
case idDeviceContext::CST_ANCHOR_BOTTOM_RIGHT: {
_xOffset = vidWidth * (1.0f - _xScale);
_yOffset = vidHeight * (1.0f - _yScale);
break;
}
case idDeviceContext::CST_ANCHOR_TOP: {
_xScale = 1.0f; // no horizontal scaling
_xOffset = 0.0f;
_yOffset = 0.0f;
break;
}
case idDeviceContext::CST_ANCHOR_VCENTER: {
_xScale = 1.0f; // no horizontal scaling
_xOffset = 0.0f;
_yOffset = (vidHeight * 0.5f) * (1.0f - _yScale);
break;
}
case idDeviceContext::CST_ANCHOR_BOTTOM: {
_xScale = 1.0f; // no horizontal scaling
_xOffset = 0.0f;
_yOffset = vidHeight * (1.0f - _yScale);
break;
}
case idDeviceContext::CST_ANCHOR_LEFT: {
_yScale = 1.0f; // no vertical scaling
_xOffset = 0.0f;
_yOffset = 0.0f;
break;
}
case idDeviceContext::CST_ANCHOR_HCENTER: {
_yScale = 1.0f; // no vertical scaling
_xOffset = (vidWidth * 0.5f) * (1.0f - _xScale);
_yOffset = 0.0f;
break;
}
case idDeviceContext::CST_ANCHOR_RIGHT: {
_yScale = 1.0f; // no vertical scaling
_xOffset = vidWidth * (1.0f - _xScale);
_yOffset = 0.0f;
break;
}
default: {
_xOffset = 0.0f;
_yOffset = 0.0f;
break;
}
}
}
void idDeviceContext::CstSetSize(int anchor, int anchorTo, float factor) {
vidWidth = VIRTUAL_WIDTH;
vidHeight = VIRTUAL_HEIGHT;
xScale = 1.0f;
yScale = 1.0f;
cst_xOffset = 0.0f;
cst_yOffset = 0.0f;
cstAdjustCoords = false;
if (!CstGetVidScale(xScale, yScale)) {
return;
}
if (anchorTo == idDeviceContext::CST_ANCHOR_NONE) {
CstAdjustParmsForAnchor(anchor, xScale, yScale, cst_xOffset, cst_yOffset);
} else {
float from_xScale = xScale;
float from_yScale = yScale;
float from_xOffset = 0.0f;
float from_yOffset = 0.0f;
CstAdjustParmsForAnchor(anchor, from_xScale, from_yScale, from_xOffset, from_yOffset);
float to_xScale = xScale;
float to_yScale = yScale;
float to_xOffset = 0.0f;
float to_yOffset = 0.0f;
CstAdjustParmsForAnchor(anchorTo, to_xScale, to_yScale, to_xOffset, to_yOffset);
factor = idMath::ClampFloat(0.0f, 1.0f, factor);
xScale = from_xScale * (1.0f - factor) + to_xScale * factor;
yScale = from_yScale * (1.0f - factor) + to_yScale * factor;
cst_xOffset = from_xOffset * (1.0f - factor) + to_xOffset * factor;
cst_yOffset = from_yOffset * (1.0f - factor) + to_yOffset * factor;
}
cstAdjustCoords = true;
}
//#modified-fva; END
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;
}