#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; void IN_SetVisibleMouse(bool visible); 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 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); } // 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(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((*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. int newDesiredState = (inState) ? 1 : 0; if (g_iVisibleMouse != newDesiredState) { // To change whether the mouse is visible, just change this variable //g_iVisibleMouse = newDesiredState; //2024 - Using this to fix view spin on reactivation in HL25. SDL mouse modes are changed alongside the use of SetMouseVisibility instead of in this function so the cursor doesn't disappear in the escape menu. IN_SetVisibleMouse(newDesiredState); // 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(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((*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) { }