/*
===========================================================================
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 "framework/FileSystem.h"
#include "framework/DemoFile.h"
#include "framework/KeyInput.h"
#include "ui/ListGUILocal.h"
#include "ui/DeviceContext.h"
#include "ui/Window.h"
#include "ui/UserInterfaceLocal.h"
#include "renderer/tr_local.h" // glConfig for winWidth/winHeight
extern idCVar r_skipGuiShaders; // 1 = don't render any gui elements on surfaces
extern idCVar r_scaleMenusTo43; // DG: for the "scale menus to 4:3" hack
idUserInterfaceManagerLocal uiManagerLocal;
idUserInterfaceManager * uiManager = &uiManagerLocal;
/*
===============================================================================
idUserInterfaceManagerLocal
===============================================================================
*/
void idUserInterfaceManagerLocal::Init() {
screenRect = idRectangle(0, 0, 640, 480);
dc.Init();
}
void idUserInterfaceManagerLocal::Shutdown() {
guis.DeleteContents( true );
demoGuis.DeleteContents( true );
dc.Shutdown();
}
void idUserInterfaceManagerLocal::Touch( const char *name ) {
idUserInterface *gui = Alloc();
gui->InitFromFile( name );
// delete gui;
}
void idUserInterfaceManagerLocal::WritePrecacheCommands( idFile *f ) {
int c = guis.Num();
for( int i = 0; i < c; i++ ) {
char str[1024];
sprintf( str, "touchGui %s\n", guis[i]->Name() );
common->Printf( "%s", str );
f->Printf( "%s", str );
}
}
void idUserInterfaceManagerLocal::SetSize( float width, float height ) {
dc.SetSize( width, height );
}
void idUserInterfaceManagerLocal::BeginLevelLoad() {
int c = guis.Num();
for ( int i = 0; i < c; i++ ) {
if ( (guis[ i ]->GetDesktop()->GetFlags() & WIN_MENUGUI) == 0 ) {
guis[ i ]->ClearRefs();
/*
delete guis[ i ];
guis.RemoveIndex( i );
i--; c--;
*/
}
}
}
void idUserInterfaceManagerLocal::EndLevelLoad() {
int c = guis.Num();
for ( int i = 0; i < c; i++ ) {
if ( guis[i]->GetRefs() == 0 ) {
//common->Printf( "purging %s.\n", guis[i]->GetSourceFile() );
// use this to make sure no materials still reference this gui
bool remove = true;
for ( int j = 0; j < declManager->GetNumDecls( DECL_MATERIAL ); j++ ) {
const idMaterial *material = static_cast(declManager->DeclByIndex( DECL_MATERIAL, j, false ));
if ( material->GlobalGui() == guis[i] ) {
remove = false;
break;
}
}
if ( remove ) {
delete guis[ i ];
guis.RemoveIndex( i );
i--; c--;
}
}
}
// DG: this should probably be reset at this point
Sys_SetInteractiveIngameGuiActive( false, NULL );
}
void idUserInterfaceManagerLocal::Reload( bool all ) {
ID_TIME_T ts;
int c = guis.Num();
for ( int i = 0; i < c; i++ ) {
if ( !all ) {
fileSystem->ReadFile( guis[i]->GetSourceFile(), NULL, &ts );
if ( ts <= guis[i]->GetTimeStamp() ) {
continue;
}
}
guis[i]->InitFromFile( guis[i]->GetSourceFile() );
common->Printf( "reloading %s.\n", guis[i]->GetSourceFile() );
}
}
void idUserInterfaceManagerLocal::ListGuis() const {
int c = guis.Num();
common->Printf( "\n size refs name\n" );
size_t total = 0;
int copies = 0;
int unique = 0;
for ( int i = 0; i < c; i++ ) {
idUserInterfaceLocal *gui = guis[i];
size_t sz = gui->Size();
bool isUnique = guis[i]->interactive;
if ( isUnique ) {
unique++;
} else {
copies++;
}
common->Printf( "%6.1fk %4i (%s) %s ( %i transitions )\n", sz / 1024.0f, guis[i]->GetRefs(), isUnique ? "unique" : "copy", guis[i]->GetSourceFile(), guis[i]->desktop->NumTransitions() );
total += sz;
}
common->Printf( "===========\n %i total Guis ( %i copies, %i unique ), %.2f total Mbytes", c, copies, unique, total / ( 1024.0f * 1024.0f ) );
}
bool idUserInterfaceManagerLocal::CheckGui( const char *qpath ) const {
idFile *file = fileSystem->OpenFileRead( qpath );
if ( file ) {
fileSystem->CloseFile( file );
return true;
}
return false;
}
idUserInterface *idUserInterfaceManagerLocal::Alloc( void ) const {
return new idUserInterfaceLocal();
}
void idUserInterfaceManagerLocal::DeAlloc( idUserInterface *gui ) {
if ( gui ) {
int c = guis.Num();
for ( int i = 0; i < c; i++ ) {
if ( guis[i] == gui ) {
delete guis[i];
guis.RemoveIndex( i );
return;
}
}
}
}
idUserInterface *idUserInterfaceManagerLocal::FindGui( const char *qpath, bool autoLoad, bool needUnique, bool forceNOTUnique ) {
int c = guis.Num();
for ( int i = 0; i < c; i++ ) {
if ( !idStr::Icmp( guis[i]->GetSourceFile(), qpath ) ) {
if ( !forceNOTUnique && ( needUnique || guis[i]->IsInteractive() ) ) {
break;
}
guis[i]->AddRef();
return guis[i];
}
}
if ( autoLoad ) {
idUserInterface *gui = Alloc();
if ( gui->InitFromFile( qpath ) ) {
gui->SetUniqued( forceNOTUnique ? false : needUnique );
return gui;
} else {
delete gui;
}
}
return NULL;
}
idUserInterface *idUserInterfaceManagerLocal::FindDemoGui( const char *qpath ) {
int c = demoGuis.Num();
for ( int i = 0; i < c; i++ ) {
if ( !idStr::Icmp( demoGuis[i]->GetSourceFile(), qpath ) ) {
return demoGuis[i];
}
}
return NULL;
}
idListGUI * idUserInterfaceManagerLocal::AllocListGUI( void ) const {
return new idListGUILocal();
}
void idUserInterfaceManagerLocal::FreeListGUI( idListGUI *listgui ) {
delete listgui;
}
/*
===============================================================================
idUserInterfaceLocal
===============================================================================
*/
idUserInterfaceLocal::idUserInterfaceLocal() {
cursorX = cursorY = 0.0;
desktop = NULL;
loading = false;
active = false;
interactive = false;
uniqued = false;
bindHandler = NULL;
//so the reg eval in gui parsing doesn't get bogus values
time = 0;
refs = 1;
}
idUserInterfaceLocal::~idUserInterfaceLocal() {
delete desktop;
desktop = NULL;
}
const char *idUserInterfaceLocal::Name() const {
return source;
}
const char *idUserInterfaceLocal::Comment() const {
if ( desktop ) {
return desktop->GetComment();
}
return "";
}
bool idUserInterfaceLocal::IsInteractive() const {
return interactive;
}
bool idUserInterfaceLocal::InitFromFile( const char *qpath, bool rebuild, bool cache ) {
if ( !( qpath && *qpath ) ) {
// FIXME: Memory leak!!
return false;
}
loading = true;
if ( rebuild ) {
delete desktop;
desktop = new idWindow( this );
} else if ( desktop == NULL ) {
desktop = new idWindow( this );
}
source = qpath;
state.Set( "text", "Test Text!" );
idParser src( LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT | LEXFL_ALLOWMULTICHARLITERALS | LEXFL_ALLOWBACKSLASHSTRINGCONCAT );
//Load the timestamp so reload guis will work correctly
fileSystem->ReadFile(qpath, NULL, &timeStamp);
src.LoadFile( qpath );
if ( src.IsLoaded() ) {
idToken token;
while( src.ReadToken( &token ) ) {
if ( idStr::Icmp( token, "windowDef" ) == 0 ) {
desktop->SetDC( &uiManagerLocal.dc );
if ( desktop->Parse( &src, rebuild ) ) {
desktop->SetFlag( WIN_DESKTOP );
desktop->FixupParms();
}
continue;
}
}
state.Set( "name", qpath );
} else {
desktop->SetDC( &uiManagerLocal.dc );
desktop->SetFlag( WIN_DESKTOP );
desktop->name = "Desktop";
desktop->text = va( "Invalid GUI: %s", qpath );
desktop->rect = idRectangle( 0.0f, 0.0f, 640.0f, 480.0f );
desktop->drawRect = desktop->rect;
desktop->foreColor = idVec4( 1.0f, 1.0f, 1.0f, 1.0f );
desktop->backColor = idVec4( 0.0f, 0.0f, 0.0f, 1.0f );
desktop->SetupFromState();
common->Warning( "Couldn't load gui: '%s'", qpath );
loading = false;
return false;
}
interactive = desktop->Interactive();
if ( uiManagerLocal.guis.Find( this ) == NULL ) {
uiManagerLocal.guis.Append( this );
}
loading = false;
return true;
}
const char *idUserInterfaceLocal::HandleEvent( const sysEvent_t *event, int _time, bool *updateVisuals ) {
time = _time;
if ( bindHandler && event->evType == SE_KEY && event->evValue2 == 1 ) {
const char *ret = bindHandler->HandleEvent( event, updateVisuals );
bindHandler = NULL;
return ret;
}
// DG: used to translate gamepad input into events the UI system is familiar with
sysEvent_t fakedEvent = {};
if ( event->evType == SE_MOUSE || event->evType == SE_MOUSE_ABS ) {
if ( !desktop || (desktop->GetFlags() & WIN_MENUGUI) ) {
// DG: this is a fullscreen GUI, scale the mousedelta added to cursorX/Y
// by 640/w, because the GUI pretends that everything is 640x480
// even if the actual resolution is higher => mouse moved too fast
float w = glConfig.winWidth;
float h = glConfig.winHeight;
if( w <= 0.0f || h <= 0.0f ) {
w = VIRTUAL_WIDTH;
h = VIRTUAL_HEIGHT;
}
const float realW = w;
const float realH = h;
if(r_scaleMenusTo43.GetBool()) {
// in case we're scaling menus to 4:3, we need to take that into account
// when scaling the mouse events.
// no, we can't just call uiManagerLocal.dc.GetFixScaleForMenu() or sth like that,
// because when we're here dc.SetMenuScaleFix(true) is not active and it'd just return (1, 1)!
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 modify cursorX scaling, by modifying w
w *= virtualAspectRatio/aspectRatio;
} else if(aspectRatio < 1.24f) {
// portrait-mode, "thinner" than 5:4 (which is 1.25)
// => we need to scale cursorY via h
h *= aspectRatio/virtualAspectRatio;
}
}
if( event->evType == SE_MOUSE ) {
cursorX += event->evValue * (float(VIRTUAL_WIDTH)/w);
cursorY += event->evValue2 * (float(VIRTUAL_HEIGHT)/h);
} else { // SE_MOUSE_ABS
// Note: In case of scaling to 4:3, w and h are already scaled down
// to the 4:3 size that fits into the real resolution.
// Otherwise xOffset/yOffset will just be 0
float xOffset = (realW - w) * 0.5f;
float yOffset = (realH - h) * 0.5f;
// offset the mouse coordinates into 4:3 area and scale down to 640x480
// yes, result could be negative, doesn't matter, code below checks that anyway
cursorX = (event->evValue - xOffset) * (float(VIRTUAL_WIDTH)/w);
cursorY = (event->evValue2 - yOffset) * (float(VIRTUAL_HEIGHT)/h);
}
} else {
// not a fullscreen GUI but some ingame thing - no scaling needed
cursorX += event->evValue;
cursorY += event->evValue2;
}
if (cursorX < 0) {
cursorX = 0;
}
if (cursorY < 0) {
cursorY = 0;
}
}
else if ( event->evType == SE_JOYSTICK && event->evValue2 != 0 && event->evValue < 4 )
{
// evValue: axis = jEvent - J_AXIS_MIN;
// evValue2: percent (-100 to 100)
// currently uses both sticks for cursor movement
// TODO could use one stick for scrolling (maybe by generating K_UPARROW/DOWNARROW events?)
float addVal = expf( fabsf(event->evValue2 * 0.03f) ) * 0.5f;
if(event->evValue2 < 0)
addVal = -addVal;
if( event->evValue == 0 || event->evValue == 2 ) {
cursorX += addVal;
} else if( event->evValue == 1 || event->evValue == 3 ) {
cursorY += addVal;
}
if (cursorX < 0) {
cursorX = 0;
}
if (cursorY < 0) {
cursorY = 0;
}
// some things like highlighting hovered UI elements need a mouse event,
// so create a fake mouse event
fakedEvent.evType = SE_MOUSE;
// the coordinates (evValue/evValue2) aren't used, but keeping them at 0
// (as default-initialized above) shouldn't hurt either way
event = &fakedEvent;
}
else if ( event->evType == SE_KEY && event->evValue >= K_FIRST_JOY && event->evValue <= K_LAST_JOY )
{
// map some gamepad buttons to SE_KEY events that the UI already knows how to use
int key = 0;
if( idKeyInput::GetUsercmdAction( event->evValue ) == 20 /* UB_ATTACK*/ ) {
// if this button is bound to _attack (fire), treat it as left mouse button
key = K_MOUSE1;
} else {
switch(event->evValue) {
// emulate mouse buttons
case K_JOY_BTN_SOUTH: // A on xbox controller
key = K_MOUSE1;
break;
case K_JOY_BTN_EAST: // B on xbox controller
key = K_MOUSE2;
break;
// emulate cursor keys (sometimes used for scrolling or selecting in a list)
case K_JOY_DPAD_UP:
key = K_UPARROW;
break;
case K_JOY_DPAD_DOWN:
key = K_DOWNARROW;
break;
case K_JOY_DPAD_LEFT:
key = K_LEFTARROW;
break;
case K_JOY_DPAD_RIGHT:
key = K_RIGHTARROW;
break;
// enter is useful after selecting something with cursor keys (or dpad)
// in a list, like selecting a savegame - I guess left trigger is suitable for that?
// (right trigger is often used for shooting, which we use as K_MOUSE1 here)
case K_JOY_TRIGGER1:
key = K_ENTER;
break;
}
}
if (key != 0) {
fakedEvent = *event;
fakedEvent.evValue = key;
event = &fakedEvent;
}
}
if ( desktop ) {
return desktop->HandleEvent( event, updateVisuals );
}
return "";
}
void idUserInterfaceLocal::HandleNamedEvent ( const char* eventName ) {
desktop->RunNamedEvent( eventName );
}
void idUserInterfaceLocal::Redraw( int _time ) {
if ( r_skipGuiShaders.GetInteger() > 5 ) {
return;
}
if ( !loading && desktop ) {
time = _time;
uiManagerLocal.dc.PushClipRect( uiManagerLocal.screenRect );
desktop->Redraw( 0, 0 );
uiManagerLocal.dc.PopClipRect();
}
}
void idUserInterfaceLocal::DrawCursor() {
if ( !desktop || desktop->GetFlags() & WIN_MENUGUI ) {
uiManagerLocal.dc.DrawCursor(&cursorX, &cursorY, 32.0f );
} else {
uiManagerLocal.dc.DrawCursor(&cursorX, &cursorY, 64.0f );
}
}
const idDict &idUserInterfaceLocal::State() const {
return state;
}
void idUserInterfaceLocal::DeleteStateVar( const char *varName ) {
state.Delete( varName );
}
void idUserInterfaceLocal::SetStateString( const char *varName, const char *value ) {
state.Set( varName, value );
}
void idUserInterfaceLocal::SetStateBool( const char *varName, const bool value ) {
state.SetBool( varName, value );
}
void idUserInterfaceLocal::SetStateInt( const char *varName, const int value ) {
state.SetInt( varName, value );
}
void idUserInterfaceLocal::SetStateFloat( const char *varName, const float value ) {
state.SetFloat( varName, value );
}
const char* idUserInterfaceLocal::GetStateString( const char *varName, const char* defaultString ) const {
return state.GetString(varName, defaultString);
}
bool idUserInterfaceLocal::GetStateBool( const char *varName, const char* defaultString ) const {
return state.GetBool(varName, defaultString);
}
int idUserInterfaceLocal::GetStateInt( const char *varName, const char* defaultString ) const {
return state.GetInt(varName, defaultString);
}
float idUserInterfaceLocal::GetStateFloat( const char *varName, const char* defaultString ) const {
return state.GetFloat(varName, defaultString);
}
void idUserInterfaceLocal::StateChanged( int _time, bool redraw ) {
time = _time;
if (desktop) {
// DG: little hack: allow game DLLs to do
// ui->SetStateBool("scaleto43", true);
// ui->StateChanged(gameLocal.time);
// so we can force cursors.gui (crosshair) to be scaled, for example
bool scaleTo43 = false;
if(state.GetBool("scaleto43", "0", scaleTo43)) {
if(scaleTo43)
desktop->SetFlag(WIN_SCALETO43);
else
desktop->ClearFlag(WIN_SCALETO43);
}
// DG end
desktop->StateChanged( redraw );
}
if ( state.GetBool( "noninteractive" ) ) {
interactive = false;
}
else {
if (desktop) {
interactive = desktop->Interactive();
} else {
interactive = false;
}
}
}
const char *idUserInterfaceLocal::Activate(bool activate, int _time) {
time = _time;
active = activate;
if ( desktop ) {
// DG: added this hack for gamepad input - Note that it can happen that
// a UI has been made non-interactive before Activate(false, ..) is called
// and it still needs to be unregistered with Sys_SetInteractiveIngameGuiActive()!
if ( interactive || !activate ) {
Sys_SetInteractiveIngameGuiActive( activate, this );
} // DG end
activateStr = "";
desktop->Activate( activate, activateStr );
return activateStr;
}
return "";
}
void idUserInterfaceLocal::Trigger(int _time) {
time = _time;
if ( desktop ) {
desktop->Trigger();
}
}
void idUserInterfaceLocal::ReadFromDemoFile( class idDemoFile *f ) {
idStr work;
f->ReadDict( state );
source = state.GetString("name");
if (desktop == NULL) {
f->Log("creating new gui\n");
desktop = new idWindow(this);
desktop->SetFlag( WIN_DESKTOP );
desktop->SetDC( &uiManagerLocal.dc );
desktop->ReadFromDemoFile(f);
} else {
f->Log("re-using gui\n");
desktop->ReadFromDemoFile(f, false);
}
f->ReadFloat( cursorX );
f->ReadFloat( cursorY );
bool add = true;
int c = uiManagerLocal.demoGuis.Num();
for ( int i = 0; i < c; i++ ) {
if ( uiManagerLocal.demoGuis[i] == this ) {
add = false;
break;
}
}
if (add) {
uiManagerLocal.demoGuis.Append(this);
}
}
void idUserInterfaceLocal::WriteToDemoFile( class idDemoFile *f ) {
idStr work;
f->WriteDict( state );
if (desktop) {
desktop->WriteToDemoFile(f);
}
f->WriteFloat( cursorX );
f->WriteFloat( cursorY );
}
bool idUserInterfaceLocal::WriteToSaveGame( idFile *savefile ) const {
int len;
const idKeyValue *kv;
const char *string;
int num = state.GetNumKeyVals();
savefile->Write( &num, sizeof( num ) );
for( int i = 0; i < num; i++ ) {
kv = state.GetKeyVal( i );
len = kv->GetKey().Length();
string = kv->GetKey().c_str();
savefile->Write( &len, sizeof( len ) );
savefile->Write( string, len );
len = kv->GetValue().Length();
string = kv->GetValue().c_str();
savefile->Write( &len, sizeof( len ) );
savefile->Write( string, len );
}
savefile->Write( &active, sizeof( active ) );
savefile->Write( &interactive, sizeof( interactive ) );
savefile->Write( &uniqued, sizeof( uniqued ) );
savefile->Write( &time, sizeof( time ) );
len = activateStr.Length();
savefile->Write( &len, sizeof( len ) );
savefile->Write( activateStr.c_str(), len );
len = pendingCmd.Length();
savefile->Write( &len, sizeof( len ) );
savefile->Write( pendingCmd.c_str(), len );
len = returnCmd.Length();
savefile->Write( &len, sizeof( len ) );
savefile->Write( returnCmd.c_str(), len );
savefile->Write( &cursorX, sizeof( cursorX ) );
savefile->Write( &cursorY, sizeof( cursorY ) );
desktop->WriteToSaveGame( savefile );
return true;
}
bool idUserInterfaceLocal::ReadFromSaveGame( idFile *savefile ) {
int num;
int i, len;
idStr key;
idStr value;
savefile->Read( &num, sizeof( num ) );
state.Clear();
for( i = 0; i < num; i++ ) {
savefile->Read( &len, sizeof( len ) );
key.Fill( ' ', len );
savefile->Read( &key[0], len );
savefile->Read( &len, sizeof( len ) );
value.Fill( ' ', len );
savefile->Read( &value[0], len );
state.Set( key, value );
}
savefile->Read( &active, sizeof( active ) );
savefile->Read( &interactive, sizeof( interactive ) );
savefile->Read( &uniqued, sizeof( uniqued ) );
savefile->Read( &time, sizeof( time ) );
savefile->Read( &len, sizeof( len ) );
activateStr.Fill( ' ', len );
savefile->Read( &activateStr[0], len );
savefile->Read( &len, sizeof( len ) );
pendingCmd.Fill( ' ', len );
savefile->Read( &pendingCmd[0], len );
savefile->Read( &len, sizeof( len ) );
returnCmd.Fill( ' ', len );
savefile->Read( &returnCmd[0], len );
savefile->Read( &cursorX, sizeof( cursorX ) );
savefile->Read( &cursorY, sizeof( cursorY ) );
desktop->ReadFromSaveGame( savefile );
return true;
}
size_t idUserInterfaceLocal::Size() {
size_t sz = sizeof(*this) + state.Size() + source.Allocated();
if ( desktop ) {
sz += desktop->Size();
}
return sz;
}
void idUserInterfaceLocal::RecurseSetKeyBindingNames( idWindow *window ) {
int i;
idWinVar *v = window->GetWinVarByName( "bind" );
if ( v ) {
SetStateString( v->GetName(), idKeyInput::KeysFromBinding( v->GetName() ) );
}
i = 0;
while ( i < window->GetChildCount() ) {
idWindow *next = window->GetChild( i );
if ( next ) {
RecurseSetKeyBindingNames( next );
}
i++;
}
}
/*
==============
idUserInterfaceLocal::SetKeyBindingNames
==============
*/
void idUserInterfaceLocal::SetKeyBindingNames( void ) {
if ( !desktop ) {
return;
}
// walk the windows
RecurseSetKeyBindingNames( desktop );
}
/*
==============
idUserInterfaceLocal::SetCursor
==============
*/
void idUserInterfaceLocal::SetCursor( float x, float y ) {
cursorX = x;
cursorY = y;
}