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