NS/main/source/ui/UIManager.cpp

773 lines
23 KiB
C++

#include "ui/UIComponent.h"
#include "ui/UIManager.h"
#include "ui/UIFactory.h"
#include "ui/UITags.h"
#include "VGUI_Menu.h"
#include "VGUI_App.h"
#include "cl_dll/hud.h"
#include "cl_dll/cl_util.h"
#include "textrep/TRFactory.h"
#include "cl_dll/vgui_SchemeManager.h"
#include "VGUI_TextPanel.h"
#include "VGUI_Label.h"
#include "cl_dll/vgui_TeamFortressViewport.h"
#include "ui/GammaAwareComponent.h"
#include "ui/ReloadableComponent.h"
//using vgui::Label;
//extern CImageLabel *gTestLabel;
extern vgui::BitmapTGA *vgui_LoadTGA( const char* pImageName, bool bInvertAlpha = true);
const int kTranslation = 1000;
// Stupid messy externs
extern "C"
{
void* VGui_GetPanel();
}
extern int g_iVisibleMouse;
UIManager::UIManager(UIFactory* inFactory)
{
this->mEditMode = false;
this->mDraggingLMB = false;
this->mUsingVGUI = false;
this->mLMBDownX = this->mLMBDownY = -1;
this->mLastMouseX = this->mLastMouseY = -1;
this->mComponentMouseOver = NULL;
this->mBlankCursor = NULL;
this->mFactory = inFactory;
this->mGammaSlope = 1.0f;
}
UIManager::~UIManager(void)
{
delete this->mFactory;
this->mFactory = NULL;
delete this->mBlankCursor;
this->mBlankCursor = NULL;
}
void UIManager::AddInputSignal(InputSignal* inInputSignal)
{
UIComponentListType::iterator theCompIter;
for(theCompIter = this->mComponentList.begin(); theCompIter != this->mComponentList.end(); theCompIter++)
{
UIComponent* theComponent = *theCompIter;
ASSERT(theComponent);
Panel* theVGUIComponent = theComponent->GetComponentPointer();
ASSERT(theVGUIComponent);
theVGUIComponent->addInputSignal(inInputSignal);
}
}
bool UIManager::Clear(void)
{
bool theSuccess = false;
// Make sure we aren't in edit mode
if(!this->mEditMode)
{
// Delete every component in the list
UIComponentListType::iterator theCompIter;
for(theCompIter = this->mComponentList.begin(); theCompIter != this->mComponentList.end(); theCompIter++)
{
delete *theCompIter;
theSuccess = true;
}
// Delete the list
this->mComponentList.clear();
}
else
{
// TODO: Emit error indicating manager can't be cleared in edit mode
}
return theSuccess;
}
UIComponent* UIManager::GetComponentFromPanel(Panel* inPanel)
{
UIComponent* theComponent = NULL;
UIComponentListType::iterator theCompIter;
for(theCompIter = this->mComponentList.begin(); theCompIter != this->mComponentList.end(); theCompIter++)
{
if((*theCompIter)->GetComponentPointer() == inPanel)
{
theComponent = *theCompIter;
break;
}
}
return theComponent;
}
UIComponent* UIManager::GetComponentNamed(const string& inName)
{
UIComponent* theResult = NULL;
UIComponentListType::iterator theCompIter;
for(theCompIter = this->mComponentList.begin(); theCompIter != this->mComponentList.end(); theCompIter++)
{
if((*theCompIter)->GetName() == inName)
{
theResult = *theCompIter;
break;
}
}
return theResult;
}
const UIComponent* UIManager::GetComponentNamed(const string& inName) const
{
return NULL;
}
// TODO: Add list of components that are "hidden" and fail if the component is already hidden
bool UIManager::HideComponent(const string& inName)
{
bool theSuccess = false;
vgui::Panel* thePanel = NULL;
if(this->GetVGUIComponentNamed(inName, thePanel))
{
ASSERT(thePanel != NULL);
//this->TranslateComponent(thePanel, true);
thePanel->setVisible(false);
theSuccess = true;
}
return theSuccess;
}
void UIManager::HideComponents()
{
typedef vector<UIComponent*> UIComponentListType;
for(UIComponentListType::iterator theIter = this->mComponentList.begin(); theIter != this->mComponentList.end(); theIter++)
{
this->HideComponent((*theIter)->GetName());
}
}
bool UIManager::Initialize(const TRDescriptionList& inDesc, CSchemeManager* inSchemeManager)
{
bool theSuccess = false;
// Clear out everything in case we have already been used once
this->Clear();
// Now loop through entities found
for(TRDescriptionList::const_iterator theListIter = inDesc.begin(); theListIter != inDesc.end(); theListIter++)
{
// See if the factory knows how to create such a thing. It is giving the memory to us forever so take care of it.
UIComponent* theCurrentComponent = this->mFactory->BuildComponent(*theListIter, inSchemeManager);
// Tell it to set all the tags it knows about
if(theCurrentComponent)
{
// Check for named root tag, look up that component and set it
Panel* theRoot = NULL;
string theRootName;
if(theListIter->GetTagValue("root", theRootName))
{
UIComponent* theUIComponent = NULL;
theUIComponent = this->GetComponentNamed(theRootName);
if(theUIComponent)
{
theRoot = theUIComponent->GetComponentPointer();
}
}
// If none specified or it couldn't be found, use default
if(!theRoot)
{
theRoot = (Panel*)VGui_GetPanel();
}
// Set the root
theCurrentComponent->GetComponentPointer()->setParent(theRoot);
// Add to menu if specified
string theMenuName;
if(theListIter->GetTagValue(UITagMenuAddItem, theMenuName))
{
Menu* theParentMenu = NULL;
if(this->GetVGUIComponentNamed(theMenuName, theParentMenu))
{
theParentMenu->addMenuItem(theCurrentComponent->GetComponentPointer());
}
}
// Set up scheme if specified
if(inSchemeManager)
{
this->SetSchemeValues(*theListIter, theCurrentComponent, inSchemeManager);
}
// <sigh> If we are currently using the regular VGUI instead of the manager, translate
// this component out of the way so it doesn't suck up input
if(this->mUsingVGUI)
{
this->TranslateComponent(theCurrentComponent->GetComponentPointer(), true);
}
// If gamma aware, tell it immediately
GammaAwareComponent* theGammaAwareComponent = dynamic_cast<GammaAwareComponent*>(theCurrentComponent->GetComponentPointer());
if(theGammaAwareComponent)
{
theGammaAwareComponent->NotifyGammaChange(this->mGammaSlope);
}
// Save it. It is now part of the world.
this->mComponentList.push_back(theCurrentComponent);
// Return success if we found at least one component
theSuccess = true;
}
}
// Build default blank cursor
this->mBlankCursor = new Cursor(vgui_LoadTGA("blank"), 0, 0);
// Register for notification for all input events
//this->AddInputSignal(this);
return theSuccess;
}
bool UIManager::InMouseMode(void) const
{
return (g_iVisibleMouse ? true : false);
}
void UIManager::NotifyGammaChange(float inGammaSlope)
{
UIComponentListType::iterator theCompIter;
for(theCompIter = this->mComponentList.begin(); theCompIter != this->mComponentList.end(); theCompIter++)
{
GammaAwareComponent* theGammaAwareComponent = dynamic_cast<GammaAwareComponent*>((*theCompIter)->GetComponentPointer());
if(theGammaAwareComponent)
{
theGammaAwareComponent->NotifyGammaChange(inGammaSlope);
}
}
this->mGammaSlope = inGammaSlope;
}
bool UIManager::Save(const string& outFilename, const string& outHeader)
{
// Build description list
TRDescriptionList theDescriptionList;
UIComponentListType::iterator theCompIter;
for(theCompIter = this->mComponentList.begin(); theCompIter != this->mComponentList.end(); theCompIter++)
{
theDescriptionList.push_back((*theCompIter)->GetDescription());
}
// Write it out!
TRFactory::WriteDescriptions(outFilename, theDescriptionList, outHeader);
return true;
}
bool UIManager::SetLMBActionAbsolute(const TRTag& inTag)
{
return true;
}
bool UIManager::SetLMBActionRelative(const TRTag& inTag)
{
return true;
}
void UIManager::SetMouseVisibility(bool inState)
{
// 2021 - Check if we need to run code. Prevents showcursor from incrementing or decrementing outside of useful range.
if (g_iVisibleMouse != inState)
{
// To change whether the mouse is visible, just change this variable
g_iVisibleMouse = inState;
// Update cursor
if(g_iVisibleMouse)
{
//ClientCmd("say Entering mouse mode.");
//App::getInstance()->setCursorOveride(App::getInstance()->getScheme()->getCursor(Scheme::SchemeCursor::scu_arrow));
// Remove above line and put this line back in for sprite cursors
App::getInstance()->setCursorOveride(this->mBlankCursor);
// OS cursor show/hide fix for m_rawinput 0. Disabled until we can find a way to check if options menu is open. Otherwise cursor is hidden in options menu when commanding or spectating.
// If uncommenting this, remove Showcursor code in PieMenuHandler.
//#ifdef WIN32
// // 2021 - Prevent windows OS cursor from appearing over sprite cursor.
// // Uncomment below to track windows showcursor value because you can only increment or decrement by 1 with no min or max on the value so it can get stuck in a high or low range.
// // If the cursor breaks, create a for loop to increment or decrement the value as necessary to fix it. https://devblogs.microsoft.com/oldnewthing/20091217-00/?p=15643
// /*int sc = */ShowCursor(FALSE);
// //gEngfuncs.Con_Printf("showcursor:%d\n", sc);
//#endif
}
else
{
//ClientCmd("say Exiting mouse mode.");
// Move mouse to center of screen so mouse look isn't changed
// Only do this when in full screen
App::getInstance()->setCursorPos(ScreenWidth()/2, ScreenHeight()/2);
// Hide cursor again
App::getInstance()->setCursorOveride( App::getInstance()->getScheme()->getCursor(Scheme::scu_none) );
// OS cursor show/hide fix for m_rawinput 0.
//#ifdef WIN32
// ShowCursor(TRUE);
//#endif
}
//App::getInstance()->setCursorOveride( App::getInstance()->getScheme()->getCursor(Scheme::SchemeCursor::scu_none) );
}
}
// Set up default scheme values if a scheme was specified (overrides other tags specified)
void UIManager::SetSchemeValues(const TRDescription& inDesc, UIComponent* inComponent, CSchemeManager* inSchemeManager)
{
std::string theSchemeName;
if(inDesc.GetTagValue(UITagScheme, theSchemeName))
{
// Get the vgui panel inside
Panel* thePanelPointer = inComponent->GetComponentPointer();
// Get the scheme specified in the layout
const char* theSchemeCString = theSchemeName.c_str();
SchemeHandle_t theSchemeHandle = inSchemeManager->getSchemeHandle(theSchemeCString);
int r, g, b, a;
// Set fg color
inSchemeManager->getFgColor(theSchemeHandle, r, g, b, a);
thePanelPointer->setFgColor(r, g, b, a);
// Set bg color
inSchemeManager->getBgColor(theSchemeHandle, r, g, b, a);
thePanelPointer->setBgColor(r, g, b, a);
// Set font if applicable
vgui::Font* theFont = inSchemeManager->getFont(theSchemeHandle);
vgui::TextPanel* theTextPanel = dynamic_cast<vgui::TextPanel*>(thePanelPointer);
if(theFont && theTextPanel)
{
theTextPanel->setFont(theFont);
}
}
}
bool UIManager::SetRMBActionAbsolute(const TRTag& inTag)
{
return true;
}
bool UIManager::SetRMBActionRelative(const TRTag& inTag)
{
return true;
}
void UIManager::SetUsingVGUI(bool inState)
{
if(inState)
{
if(!this->mUsingVGUI)
{
// Translate all components away
this->TranslateComponents(true);
this->mUsingVGUI = true;
}
}
else
{
if(this->mUsingVGUI)
{
// Translate everything back
this->TranslateComponents(false);
this->mUsingVGUI = false;
}
}
}
bool UIManager::TranslateComponent(const string& inName, bool inAway)
{
bool theSuccess = false;
UIComponent* theComponent = this->GetComponentNamed(inName);
if(theComponent)
{
this->TranslateComponent(theComponent->GetComponentPointer(), inAway);
theSuccess = true;
}
return theSuccess;
}
void UIManager::TranslateComponent(vgui::Panel* inPanel, bool inAway)
{
int theX, theY;
inPanel->getPos(theX, theY);
int theAmount = kTranslation*(inAway ? 1 : -1);
inPanel->setPos(theX + theAmount, theY + theAmount);
}
void UIManager::TranslateComponents(bool inAway)
{
UIComponentListType::iterator theCompIter;
for(theCompIter = this->mComponentList.begin(); theCompIter != this->mComponentList.end(); theCompIter++)
{
vgui::Panel* theCurrentPanel = (*theCompIter)->GetComponentPointer();
this->TranslateComponent(theCurrentPanel, inAway);
}
}
bool UIManager::ToggleEditMode(void)
{
bool theWasInEditMode = this->mEditMode;
this->mEditMode = !this->mEditMode;
// if(this->mEditMode)
// {
// ClientCmd("say Entering edit mode.");
// }
// else
// {
// ClientCmd("say Exiting edit mode.");
// }
// If we enter edit mode, disable all components. If we leave, reenable them.
this->SetEnabledState(!this->mEditMode);
//CLIENT_PRINTF( pEntity, print_console, UTIL_VarArgs( "\"fov\" is \"%d\"\n", (int)GetClassPtr((CBasePlayer *)pev)->m_iFOV ) );
if(!this->mEditMode)
{
// Reset graphical layout variables
this->mComponentMouseOver = NULL;
this->mDraggingLMB = false;
this->mLastMouseX = this->mLastMouseY = -1;
this->mLMBDownX = this->mLMBDownY = -1;
}
return theWasInEditMode;
}
// TODO: Add list of components that are "hidden" and fail if the component is not currently hidden
bool UIManager::UnhideComponent(const string& inName)
{
bool theSuccess = false;
vgui::Panel* thePanel = NULL;
if(this->GetVGUIComponentNamed(inName, thePanel))
{
ASSERT(thePanel != NULL);
//this->TranslateComponent(thePanel, false);
thePanel->setVisible(true);
theSuccess = true;
}
return theSuccess;
}
void UIManager::Update(float inCurrentTime)
{
UIComponentListType::iterator theCompIter;
for(theCompIter = this->mComponentList.begin(); theCompIter != this->mComponentList.end(); theCompIter++)
{
(*theCompIter)->Update(inCurrentTime);
}
// int r, g, b, a;
// r = g = b = a = 0;
// //gTestLabel->getFgColor(r, g, b, a);
// vgui::Color theColor;
// gTestLabel->m_pTGA->getColor(theColor);
// theColor.getColor(r, g, b, a);
// r = (r + 1) % 255;
// theColor.setColor(r, g, b, a);
// gTestLabel->m_pTGA->setColor(theColor);
// //gTestLabel->setFgColor(r, g, b, a);
//
// gTestLabel->getBgColor(r, g, b, a);
// a = (a + 1) % 255;
// gTestLabel->setBgColor(r, g, b, a);
}
void UIManager::VidInit(void)
{
UIComponentListType::iterator theCompIter;
for(theCompIter = this->mComponentList.begin(); theCompIter != this->mComponentList.end(); theCompIter++)
{
ReloadableComponent* theReloadableComponent = dynamic_cast<ReloadableComponent*>((*theCompIter)->GetComponentPointer());
if(theReloadableComponent)
{
theReloadableComponent->VidInit();
}
}
}
void UIManager::ToggleMouse(void)
{
this->SetMouseVisibility(!g_iVisibleMouse);
}
////////////////////////////////
// Functions from InputSignal //
////////////////////////////////
void UIManager::cursorMoved(int inX, int inY, Panel* inPanel)
{
if(!inPanel)
{
ClientCmd("say cursorMoved with null inPanel! Yeah!\n");
}
// x,y are local to the upper left of the panel.
int theLocalX = inX;
int theLocalY = inY;
// Get screen coordinates
int theScreenX, theScreenY;
this->GetScreenCoords(theScreenX, theScreenY);
// This function should only be called when mouse visible and we are in edit mode
if(this->mEditMode)
{
// We can't assume that a cursorEntered will always get called before a cursor moved, so
// call cursorEntered if we don't have a component yet (happens when toggling between the two modes
if(!this->mComponentMouseOver)
{
this->cursorEntered(inPanel);
}
// Number of pixels to move with the mouse button down before we start dragging
const int START_DRAG_PIXEL_DIST = 6;
const int MIN_DRAG_PIXEL_DIST = 4;
static int theStartDragLocalXOffset = 0;
static int theStartDragLocalYOffset = 0;
// Check how far we have moved, are we dragging yet?
if((!this->mDraggingLMB) &&
(this->mLMBDownX != -1) &&
(this->mLMBDownY != -1))
{
int theXDiff = theScreenX - this->mLMBDownX;
int theYDiff = theScreenY - this->mLMBDownY;
// Requires more movement in diagonal direction, probably not
// worth the extra complexity to change
if((abs(theXDiff) > START_DRAG_PIXEL_DIST) ||
(abs(theYDiff) > START_DRAG_PIXEL_DIST))
{
//ClientCmd("say Starting drag");
// Save the offset from the component's upper left. Preserve
// this when for more intuitive dragging (so dragging doesn't
// suddenly move the component relative to the mouse).
theStartDragLocalXOffset = theLocalX;
theStartDragLocalYOffset = theLocalY;
//inPanel->setAsMouseArena(true);
this->mDraggingLMB = true;
}
}
// Are we dragging?
if(this->mDraggingLMB)
{
// If so, set the component's new position. The position corresponds to the upper
// left corner of the component, so subtract out the local offset.
// The component's new position is equal to the current screen pos of the mouse, MINUS
// the local component offset which we started the drag. If we were dragging the component
// around by the upper left corner, this would be like setting the component's position equal
// to whatever the mouse was at during a mouse move. If we were dragging it by the center,
// it would set the component's position to the current screen mouse pos minus half the component
// width and height. Make sense?
//ClientCmd("say Setting new component position");
int theChangeX = theScreenX - theStartDragLocalXOffset;
int theChangeY = theScreenY - theStartDragLocalYOffset;
// Make sure we move at least a few pixels, because we're bound by control and we
// could've grabbed it near one of the edges
theChangeX = max(theChangeX, MIN_DRAG_PIXEL_DIST);
theChangeY = max(theChangeY, MIN_DRAG_PIXEL_DIST);
this->SetPanelPosition(inPanel, theChangeX, theChangeY);
}
}
// Update new mouse position
this->mLastMouseX = theScreenX;
this->mLastMouseY = theScreenY;
}
void UIManager::SetPanelPosition(Panel* inPanel, int inX, int inY)
{
// Look up the component
UIComponent* theUIComp = this->GetComponentFromPanel(inPanel);
if(theUIComp)
{
// Update run-time version
// Set the vgui panel's position (pixel coords)
// Clip so component never goes off the screen in any way
int theCompWidth = 0;
int theCompHeight = 0;
inPanel->getSize(theCompWidth, theCompHeight);
int theClippedScreenX = max(min(inX, ScreenWidth() - theCompWidth), 0);
int theClippedScreenY = max(min(inY, ScreenHeight() - theCompHeight), 0);
inPanel->setPos(theClippedScreenX, theClippedScreenY);
// repaint parent if present
Panel* inPanelParent = inPanel->getParent();
if(inPanelParent != NULL)
{
inPanelParent->repaint();
}
// Convert to normalized coords for text representation.
float theClippedNormX = (float)theClippedScreenX/ScreenWidth();
float theClippedNormY = (float)theClippedScreenY/ScreenHeight();
// Change the text represntation so it has the update coordinates (normalized coords)
// This means it can save out again with the player's changes!
TRDescription& theCompDesc = theUIComp->GetDescription();
theCompDesc.SetTagValue(UITagXPos, theClippedNormX);
theCompDesc.SetTagValue(UITagYPos, theClippedNormY);
}
else
{
//ClientCmd("say Can't find UIComponent that you're dragging, can't save changes to it.");
}
}
void UIManager::cursorEntered(Panel* inPanel)
{
// Handle stacking of ui components. Only enter component if we aren't already on a component
if(!this->mComponentMouseOver)
{
if(!this->mDraggingLMB)
{
//ClientCmd("say Cursor entered component");
this->mComponentMouseOver = inPanel;
}
}
}
void UIManager::cursorExited(Panel* inPanel)
{
// Only leave the component we are currently on for stacking purposes
if(this->mComponentMouseOver == inPanel)
{
if(!this->mDraggingLMB)
{
//ClientCmd("say Cursor exited component");
this->mComponentMouseOver = NULL;
}
}
}
void UIManager::mousePressed(MouseCode inCode, Panel* inPanel)
{
// Track dragging state
if(inCode == MOUSE_LEFT)
{
if(this->mComponentMouseOver)
{
//ClientCmd("say Left mouse pressed on component");
this->mLMBDownX = this->mLastMouseX;
this->mLMBDownY = this->mLastMouseY;
}
}
}
void UIManager::mouseDoublePressed(MouseCode inCode, Panel* inPanel)
{
}
// Take into account the mouse offset into the component
// Note: This doesn't work in windowed mode, as it returns the cursor position in pixels,
// and the desktop resolution is different than HL resolution in windowed mode. If there
// are function to tell if we are in windowed mode, and to find the upper left x,y this
// could be fixed.
void UIManager::GetScreenCoords(int& outLocalX, int& outLocalY)
{
int theX, theY;
App::getInstance()->getCursorPos(theX, theY);
// int theLocalX = theX;
// int theLocalY = theY;
// inPanel->screenToLocal(theLocalX, theLocalY);
// theX += ioLocalX;
// theY += ioLocalY;
outLocalX = theX;
outLocalY = theY;
}
void UIManager::SetEnabledState(bool inState)
{
UIComponentListType::iterator theCompIter;
for(theCompIter = this->mComponentList.begin(); theCompIter != this->mComponentList.end(); theCompIter++)
{
(*theCompIter)->GetComponentPointer()->setEnabled(inState);
}
}
void UIManager::mouseReleased(MouseCode code, Panel* inPanel)
{
// Track dragging state
if(code == MOUSE_LEFT)
{
//ClientCmd("say Left mouse released on component");
//ALERT(at_console, "Down xy = -1\n");
this->mLMBDownX = -1;
this->mLMBDownY = -1;
this->mDraggingLMB = false;
//App::getInstance()->setMouseArena(NULL);
//inPanel->setAsMouseArena(false);
//App::getInstance()->setMouseArena(NULL);
}
}
void UIManager::mouseWheeled(int delta,Panel* panel)
{
}
void UIManager::keyPressed(KeyCode code,Panel* panel)
{
}
void UIManager::keyTyped(KeyCode code,Panel* panel)
{
}
void UIManager::keyReleased(KeyCode code,Panel* panel)
{
}
void UIManager::keyFocusTicked(Panel* panel)
{
}