Index: ps/trunk/source/gui/CSlider.h =================================================================== --- ps/trunk/source/gui/CSlider.h (revision 22586) +++ ps/trunk/source/gui/CSlider.h (revision 22587) @@ -1,65 +1,65 @@ /* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 0 A.D. If not, see . */ #ifndef INCLUDED_CSLIDER #define INCLUDED_CSLIDER #include "GUI.h" class CSlider : public IGUIObject { GUI_OBJECT(CSlider) public: - CSlider(); + CSlider(CGUI* pGUI); virtual ~CSlider(); protected: /** * @see IGUIObject#HandleMessage() */ virtual void HandleMessage(SGUIMessage& Message); virtual void Draw(); /** * Change settings and send the script event */ void UpdateValue(); CRect GetButtonRect(); /** * @return ratio between the value of the slider and its actual size in the GUI */ float GetSliderRatio() const; void IncrementallyChangeValue(const float value); float m_MinValue, m_MaxValue, m_Value; private: bool m_IsPressed; CPos m_Mouse; float m_ButtonSide; }; #endif // INCLUDED_CSLIDER Index: ps/trunk/source/gui/CText.h =================================================================== --- ps/trunk/source/gui/CText.h (revision 22586) +++ ps/trunk/source/gui/CText.h (revision 22587) @@ -1,69 +1,69 @@ -/* Copyright (C) 2015 Wildfire Games. +/* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 0 A.D. If not, see . */ #ifndef INCLUDED_CTEXT #define INCLUDED_CTEXT #include "GUI.h" /** * Text field that just displays static text. * * @see IGUIObject */ class CText : public IGUIScrollBarOwner, public IGUITextOwner { GUI_OBJECT(CText) public: - CText(); + CText(CGUI* pGUI); virtual ~CText(); /** * @see IGUIObject#ResetStates() */ virtual void ResetStates() { IGUIScrollBarOwner::ResetStates(); } /** * Test if mouse position is over an icon */ virtual bool MouseOverIcon(); protected: /** * Sets up text, should be called every time changes has been * made that can change the visual. */ void SetupText(); /** * @see IGUIObject#HandleMessage() */ virtual void HandleMessage(SGUIMessage& Message); /** * Draws the Text */ virtual void Draw(); /** * Placement of text. Ignored when scrollbars are active. */ CPos m_TextPos; }; #endif // INCLUDED_CTEXT Index: ps/trunk/source/gui/CTooltip.h =================================================================== --- ps/trunk/source/gui/CTooltip.h (revision 22586) +++ ps/trunk/source/gui/CTooltip.h (revision 22587) @@ -1,45 +1,45 @@ -/* Copyright (C) 2015 Wildfire Games. +/* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 0 A.D. If not, see . */ #ifndef INCLUDED_CTOOLTIP #define INCLUDED_CTOOLTIP #include "IGUITextOwner.h" /** * Dynamic tooltips. Similar to CText. */ class CTooltip : public IGUITextOwner { GUI_OBJECT(CTooltip) public: - CTooltip(); + CTooltip(CGUI* pGUI); virtual ~CTooltip(); protected: void SetupText(); /** * @see IGUIObject#HandleMessage() */ virtual void HandleMessage(SGUIMessage& Message); virtual void Draw(); }; #endif // INCLUDED_CTOOLTIP Index: ps/trunk/source/gui/IGUIObject.cpp =================================================================== --- ps/trunk/source/gui/IGUIObject.cpp (revision 22586) +++ ps/trunk/source/gui/IGUIObject.cpp (revision 22587) @@ -1,575 +1,573 @@ /* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 0 A.D. If not, see . */ #include "precompiled.h" #include "GUI.h" #include "gui/scripting/JSInterface_GUITypes.h" #include "gui/scripting/JSInterface_IGUIObject.h" #include "ps/GameSetup/Config.h" #include "ps/CLogger.h" #include "ps/Profile.h" #include "scriptinterface/ScriptInterface.h" template void SGUISetting::Init(IGUIObject& pObject, const CStr& Name) { m_pSetting = new T(); m_FromJSVal = [Name, &pObject](JSContext* cx, JS::HandleValue v) { T value; if (!ScriptInterface::FromJSVal(cx, v, value)) return false; GUI::SetSetting(&pObject, Name, value); return true; }; m_ToJSVal = [Name, this](JSContext* cx, JS::MutableHandleValue v) { ScriptInterface::ToJSVal(cx, v, *static_cast(m_pSetting)); }; } -IGUIObject::IGUIObject() - : m_pGUI(NULL), m_pParent(NULL), m_MouseHovering(false), m_LastClickTime() +IGUIObject::IGUIObject(CGUI* pGUI) + : m_pGUI(pGUI), m_pParent(NULL), m_MouseHovering(false), m_LastClickTime() { AddSetting(GUIST_bool, "enabled"); AddSetting(GUIST_bool, "hidden"); AddSetting(GUIST_CClientArea, "size"); AddSetting(GUIST_CStr, "style"); AddSetting(GUIST_CStr, "hotkey"); AddSetting(GUIST_float, "z"); AddSetting(GUIST_bool, "absolute"); AddSetting(GUIST_bool, "ghost"); AddSetting(GUIST_float, "aspectratio"); AddSetting(GUIST_CStrW, "tooltip"); AddSetting(GUIST_CStr, "tooltip_style"); // Setup important defaults GUI::SetSetting(this, "hidden", false); GUI::SetSetting(this, "ghost", false); GUI::SetSetting(this, "enabled", true); GUI::SetSetting(this, "absolute", true); } IGUIObject::~IGUIObject() { for (const std::pair& p : m_Settings) switch (p.second.m_Type) { // delete() needs to know the type of the variable - never delete a void* #define TYPE(t) case GUIST_##t: delete (t*)p.second.m_pSetting; break; #include "GUItypes.h" #undef TYPE default: debug_warn(L"Invalid setting type"); } - if (m_pGUI) + if (!m_ScriptHandlers.empty()) JS_RemoveExtraGCRootsTracer(m_pGUI->GetScriptInterface()->GetJSRuntime(), Trace, this); } //------------------------------------------------------------------- // Functions //------------------------------------------------------------------- -void IGUIObject::SetGUI(CGUI* const& pGUI) -{ - if (!m_pGUI) - JS_AddExtraGCRootsTracer(pGUI->GetScriptInterface()->GetJSRuntime(), Trace, this); - m_pGUI = pGUI; -} void IGUIObject::AddChild(IGUIObject* pChild) { // ENSURE(pChild); pChild->SetParent(this); m_Children.push_back(pChild); // If this (not the child) object is already attached // to a CGUI, it pGUI pointer will be non-null. // This will mean we'll have to check if we're using // names already used. if (pChild->GetGUI()) { try { // Atomic function, if it fails it won't // have changed anything //UpdateObjects(); pChild->GetGUI()->UpdateObjects(); } catch (PSERROR_GUI&) { // If anything went wrong, reverse what we did and throw // an exception telling it never added a child m_Children.erase(m_Children.end()-1); throw; } } // else do nothing } void IGUIObject::AddToPointersMap(map_pObjects& ObjectMap) { // Just don't do anything about the top node if (m_pParent == NULL) return; // Now actually add this one // notice we won't add it if it's doesn't have any parent // (i.e. being the base object) if (m_Name.empty()) { throw PSERROR_GUI_ObjectNeedsName(); } if (ObjectMap.count(m_Name) > 0) { throw PSERROR_GUI_NameAmbiguity(m_Name.c_str()); } else { ObjectMap[m_Name] = this; } } void IGUIObject::Destroy() { // Is there anything besides the children to destroy? } void IGUIObject::AddSetting(const EGUISettingType& Type, const CStr& Name) { // Is name already taken? if (m_Settings.count(Name) >= 1) return; // Construct, and set type m_Settings[Name].m_Type = Type; switch (Type) { #define TYPE(type) \ case GUIST_##type: \ m_Settings[Name].Init(*this, Name);\ break; // Construct the setting. #include "GUItypes.h" #undef TYPE default: debug_warn(L"IGUIObject::AddSetting failed, type not recognized!"); break; } } bool IGUIObject::MouseOver() { if (!GetGUI()) throw PSERROR_GUI_OperationNeedsGUIObject(); return m_CachedActualSize.PointInside(GetMousePos()); } bool IGUIObject::MouseOverIcon() { return false; } CPos IGUIObject::GetMousePos() const { if (GetGUI()) return GetGUI()->m_MousePos; return CPos(); } void IGUIObject::UpdateMouseOver(IGUIObject* const& pMouseOver) { if (pMouseOver == this) { if (!m_MouseHovering) SendEvent(GUIM_MOUSE_ENTER, "mouseenter"); m_MouseHovering = true; SendEvent(GUIM_MOUSE_OVER, "mousemove"); } else { if (m_MouseHovering) { m_MouseHovering = false; SendEvent(GUIM_MOUSE_LEAVE, "mouseleave"); } } } bool IGUIObject::SettingExists(const CStr& Setting) const { // Because GetOffsets will direct dynamically defined // classes with polymorphism to respective ms_SettingsInfo // we need to make no further updates on this function // in derived classes. //return (GetSettingsInfo().count(Setting) >= 1); return (m_Settings.count(Setting) >= 1); } PSRETURN IGUIObject::SetSetting(const CStr& Setting, const CStrW& Value, const bool& SkipMessage) { if (!SettingExists(Setting)) return PSRETURN_GUI_InvalidSetting; SGUISetting set = m_Settings[Setting]; #define TYPE(type) \ else if (set.m_Type == GUIST_##type) \ { \ type _Value; \ if (!GUI::ParseString(Value, _Value)) \ return PSRETURN_GUI_UnableToParse; \ GUI::SetSetting(this, Setting, _Value, SkipMessage); \ } if (0) ; #include "GUItypes.h" #undef TYPE else { // Why does it always fail? //return PS_FAIL; return LogInvalidSettings(Setting); } return PSRETURN_OK; } PSRETURN IGUIObject::GetSettingType(const CStr& Setting, EGUISettingType& Type) const { if (!SettingExists(Setting)) return LogInvalidSettings(Setting); if (m_Settings.find(Setting) == m_Settings.end()) return LogInvalidSettings(Setting); Type = m_Settings.find(Setting)->second.m_Type; return PSRETURN_OK; } void IGUIObject::ChooseMouseOverAndClosest(IGUIObject*& pObject) { if (!MouseOver()) return; // Check if we've got competition at all if (pObject == NULL) { pObject = this; return; } // Or if it's closer if (GetBufferedZ() >= pObject->GetBufferedZ()) { pObject = this; return; } } IGUIObject* IGUIObject::GetParent() const { // Important, we're not using GetParent() for these // checks, that could screw it up if (m_pParent) { if (m_pParent->m_pParent == NULL) return NULL; } return m_pParent; } void IGUIObject::ResetStates() { // Notify the gui that we aren't hovered anymore UpdateMouseOver(nullptr); } void IGUIObject::UpdateCachedSize() { bool absolute; GUI::GetSetting(this, "absolute", absolute); float aspectratio = 0.f; GUI::GetSetting(this, "aspectratio", aspectratio); CClientArea ca; GUI::GetSetting(this, "size", ca); // If absolute="false" and the object has got a parent, // use its cached size instead of the screen. Notice // it must have just been cached for it to work. if (absolute == false && m_pParent && !IsRootObject()) m_CachedActualSize = ca.GetClientArea(m_pParent->m_CachedActualSize); else m_CachedActualSize = ca.GetClientArea(CRect(0.f, 0.f, g_xres / g_GuiScale, g_yres / g_GuiScale)); // In a few cases, GUI objects have to resize to fill the screen // but maintain a constant aspect ratio. // Adjust the size to be the max possible, centered in the original size: if (aspectratio) { if (m_CachedActualSize.GetWidth() > m_CachedActualSize.GetHeight()*aspectratio) { float delta = m_CachedActualSize.GetWidth() - m_CachedActualSize.GetHeight()*aspectratio; m_CachedActualSize.left += delta/2.f; m_CachedActualSize.right -= delta/2.f; } else { float delta = m_CachedActualSize.GetHeight() - m_CachedActualSize.GetWidth()/aspectratio; m_CachedActualSize.bottom -= delta/2.f; m_CachedActualSize.top += delta/2.f; } } } void IGUIObject::LoadStyle(CGUI& GUIinstance, const CStr& StyleName) { // Fetch style if (GUIinstance.m_Styles.count(StyleName) == 1) { LoadStyle(GUIinstance.m_Styles[StyleName]); } else { debug_warn(L"IGUIObject::LoadStyle failed"); } } void IGUIObject::LoadStyle(const SGUIStyle& Style) { // Iterate settings, it won't be able to set them all probably, but that doesn't matter for (const std::pair& p : Style.m_SettingsDefaults) { // Try set setting in object SetSetting(p.first, p.second); // It doesn't matter if it fail, it's not suppose to be able to set every setting. // since it's generic. // The beauty with styles is that it can contain more settings // than exists for the objects using it. So if the SetSetting // fails, don't care. } } float IGUIObject::GetBufferedZ() const { bool absolute; GUI::GetSetting(this, "absolute", absolute); float Z; GUI::GetSetting(this, "z", Z); if (absolute) return Z; else { if (GetParent()) return GetParent()->GetBufferedZ() + Z; else { // In philosophy, a parentless object shouldn't be able to have a relative sizing, // but we'll accept it so that absolute can be used as default without a complaint. // Also, you could consider those objects children to the screen resolution. return Z; } } } void IGUIObject::RegisterScriptHandler(const CStr& Action, const CStr& Code, CGUI* pGUI) { if(!GetGUI()) throw PSERROR_GUI_OperationNeedsGUIObject(); JSContext* cx = pGUI->GetScriptInterface()->GetContext(); JSAutoRequest rq(cx); JS::RootedValue globalVal(cx, pGUI->GetGlobalObject()); JS::RootedObject globalObj(cx, &globalVal.toObject()); const int paramCount = 1; const char* paramNames[paramCount] = { "mouse" }; // Location to report errors from CStr CodeName = GetName()+" "+Action; // Generate a unique name static int x = 0; char buf[64]; sprintf_s(buf, ARRAY_SIZE(buf), "__eventhandler%d (%s)", x++, Action.c_str()); JS::CompileOptions options(cx); options.setFileAndLine(CodeName.c_str(), 0); options.setCompileAndGo(true); JS::RootedFunction func(cx); JS::AutoObjectVector emptyScopeChain(cx); if (!JS::CompileFunction(cx, emptyScopeChain, options, buf, paramCount, paramNames, Code.c_str(), Code.length(), &func)) { LOGERROR("RegisterScriptHandler: Failed to compile the script for %s", Action.c_str()); return; } JS::RootedObject funcObj(cx, JS_GetFunctionObject(func)); SetScriptHandler(Action, funcObj); } void IGUIObject::SetScriptHandler(const CStr& Action, JS::HandleObject Function) { - // m_ScriptHandlers is only rooted after SetGUI() has been called (which sets up the GC trace callbacks), - // so we can't safely store objects in it if the GUI hasn't been set yet. ENSURE(m_pGUI && "A GUI must be associated with the GUIObject before adding ScriptHandlers!"); + + if (m_ScriptHandlers.empty()) + JS_AddExtraGCRootsTracer(m_pGUI->GetScriptInterface()->GetJSRuntime(), Trace, this); + m_ScriptHandlers[Action] = JS::Heap(Function); } InReaction IGUIObject::SendEvent(EGUIMessageType type, const CStr& EventName) { PROFILE2_EVENT("gui event"); PROFILE2_ATTR("type: %s", EventName.c_str()); PROFILE2_ATTR("object: %s", m_Name.c_str()); SGUIMessage msg(type); HandleMessage(msg); ScriptEvent(EventName); return (msg.skipped ? IN_PASS : IN_HANDLED); } void IGUIObject::ScriptEvent(const CStr& Action) { std::map >::iterator it = m_ScriptHandlers.find(Action); if (it == m_ScriptHandlers.end()) return; JSContext* cx = m_pGUI->GetScriptInterface()->GetContext(); JSAutoRequest rq(cx); // Set up the 'mouse' parameter JS::RootedValue mouse(cx); m_pGUI->GetScriptInterface()->CreateObject( &mouse, "x", m_pGUI->m_MousePos.x, "y", m_pGUI->m_MousePos.y, "buttons", m_pGUI->m_MouseButtons); JS::AutoValueVector paramData(cx); paramData.append(mouse); JS::RootedObject obj(cx, GetJSObject()); JS::RootedValue handlerVal(cx, JS::ObjectValue(*it->second)); JS::RootedValue result(cx); bool ok = JS_CallFunctionValue(cx, obj, handlerVal, paramData, &result); if (!ok) { // We have no way to propagate the script exception, so just ignore it // and hope the caller checks JS_IsExceptionPending } } void IGUIObject::ScriptEvent(const CStr& Action, JS::HandleValueArray paramData) { std::map >::iterator it = m_ScriptHandlers.find(Action); if (it == m_ScriptHandlers.end()) return; JSContext* cx = m_pGUI->GetScriptInterface()->GetContext(); JSAutoRequest rq(cx); JS::RootedObject obj(cx, GetJSObject()); JS::RootedValue handlerVal(cx, JS::ObjectValue(*it->second)); JS::RootedValue result(cx); if (!JS_CallFunctionValue(cx, obj, handlerVal, paramData, &result)) JS_ReportError(cx, "Errors executing script action \"%s\"", Action.c_str()); } JSObject* IGUIObject::GetJSObject() { JSContext* cx = m_pGUI->GetScriptInterface()->GetContext(); JSAutoRequest rq(cx); // Cache the object when somebody first asks for it, because otherwise // we end up doing far too much object allocation. TODO: Would be nice to // not have these objects hang around forever using up memory, though. if (!m_JSObject.initialized()) { m_JSObject.init(cx, m_pGUI->GetScriptInterface()->CreateCustomObject("GUIObject")); JS_SetPrivate(m_JSObject.get(), this); } return m_JSObject.get(); } CStr IGUIObject::GetPresentableName() const { // __internal(), must be at least 13 letters to be able to be // an internal name if (m_Name.length() <= 12) return m_Name; if (m_Name.substr(0, 10) == "__internal") return CStr("[unnamed object]"); else return m_Name; } void IGUIObject::SetFocus() { GetGUI()->m_FocusedObject = this; } bool IGUIObject::IsFocused() const { return GetGUI()->m_FocusedObject == this; } bool IGUIObject::IsRootObject() const { return GetGUI() != 0 && m_pParent == GetGUI()->m_BaseObject; } void IGUIObject::TraceMember(JSTracer* trc) { + // Please ensure to adapt the Tracer enabling and disabling in accordance with the GC things traced! + for (std::pair>& handler : m_ScriptHandlers) JS_CallObjectTracer(trc, &handler.second, "IGUIObject::m_ScriptHandlers"); } PSRETURN IGUIObject::LogInvalidSettings(const CStr8& Setting) const { LOGWARNING("IGUIObject: setting %s was not found on an object", Setting.c_str()); return PSRETURN_GUI_InvalidSetting; } Index: ps/trunk/source/gui/IGUIScrollBar.cpp =================================================================== --- ps/trunk/source/gui/IGUIScrollBar.cpp (revision 22586) +++ ps/trunk/source/gui/IGUIScrollBar.cpp (revision 22587) @@ -1,194 +1,196 @@ -/* Copyright (C) 2015 Wildfire Games. +/* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 0 A.D. If not, see . */ #include "precompiled.h" #include "GUI.h" #include "maths/MathUtil.h" -IGUIScrollBar::IGUIScrollBar() : m_pStyle(NULL), m_pGUI(NULL), +IGUIScrollBar::IGUIScrollBar(CGUI* m_pGUI) + : m_pGUI(m_pGUI), + m_pStyle(NULL), m_X(300.f), m_Y(300.f), m_ScrollRange(1.f), m_ScrollSpace(0.f), // MaxPos: not 0, due to division. m_Length(200.f), m_Width(20.f), m_BarSize(0.f), m_Pos(0.f), m_ButtonPlusPressed(false), m_ButtonMinusPressed(false), m_ButtonPlusHovered(false), m_ButtonMinusHovered(false), m_BarHovered(false), m_BarPressed(false) { } IGUIScrollBar::~IGUIScrollBar() { } void IGUIScrollBar::SetupBarSize() { if (!GetStyle()) return; float min = GetStyle()->m_MinimumBarSize; float max = GetStyle()->m_MaximumBarSize; float length = m_Length; // Check for edge buttons if (GetStyle()->m_UseEdgeButtons) length -= GetStyle()->m_Width * 2.f; // Check min and max are valid if (min > length) min = 0.f; if (max < min) max = length; // Clamp size to not exceed a minimum or maximum. m_BarSize = clamp(length * std::min((float)m_ScrollSpace / (float)m_ScrollRange, 1.f), min, max); } const SGUIScrollBarStyle* IGUIScrollBar::GetStyle() const { if (!m_pHostObject) return NULL; return m_pHostObject->GetScrollBarStyle(m_ScrollBarStyle); } CGUI* IGUIScrollBar::GetGUI() const { if (!m_pHostObject) return NULL; return m_pHostObject->GetGUI(); } void IGUIScrollBar::UpdatePosBoundaries() { if (m_Pos < 0.f || m_ScrollRange < m_ScrollSpace) // <= scrolling not applicable m_Pos = 0.f; else if (m_Pos > GetMaxPos()) m_Pos = GetMaxPos(); } void IGUIScrollBar::HandleMessage(SGUIMessage& Message) { switch (Message.type) { case GUIM_MOUSE_MOTION: { // TODO Gee: Optimizations needed! CPos mouse = m_pHostObject->GetMousePos(); // If bar is being dragged if (m_BarPressed) { SetPosFromMousePos(mouse); UpdatePosBoundaries(); } // check if components are being hovered m_BarHovered = GetBarRect().PointInside(mouse); m_ButtonMinusHovered = HoveringButtonMinus(mouse); m_ButtonPlusHovered = HoveringButtonPlus(mouse); if (!m_ButtonMinusHovered) m_ButtonMinusPressed = false; if (!m_ButtonPlusHovered) m_ButtonPlusPressed = false; break; } case GUIM_MOUSE_PRESS_LEFT: { if (!m_pHostObject) break; CPos mouse = m_pHostObject->GetMousePos(); // if bar is pressed if (GetBarRect().PointInside(mouse)) { m_BarPressed = true; m_BarPressedAtPos = mouse; m_PosWhenPressed = m_Pos; } // if button-minus is pressed else if (m_ButtonMinusHovered) { m_ButtonMinusPressed = true; ScrollMinus(); } // if button-plus is pressed else if (m_ButtonPlusHovered) { m_ButtonPlusPressed = true; ScrollPlus(); } // Pressing the background of the bar, to scroll // notice the if-sentence alone does not admit that, // it must be after the above if/elses else { if (GetOuterRect().PointInside(mouse)) { // Scroll plus or minus a lot, this might change, it doesn't // have to be fancy though. if (mouse.y < GetBarRect().top) ScrollMinusPlenty(); else ScrollPlusPlenty(); // Simulate mouse movement to see if bar now is hovered SGUIMessage msg(GUIM_MOUSE_MOTION); HandleMessage(msg); } } break; } case GUIM_MOUSE_RELEASE_LEFT: m_ButtonMinusPressed = false; m_ButtonPlusPressed = false; break; case GUIM_MOUSE_WHEEL_UP: { ScrollMinus(); // Since the scroll was changed, let's simulate a mouse movement // to check if scrollbar now is hovered SGUIMessage msg(GUIM_MOUSE_MOTION); HandleMessage(msg); break; } case GUIM_MOUSE_WHEEL_DOWN: { ScrollPlus(); // Since the scroll was changed, let's simulate a mouse movement // to check if scrollbar now is hovered SGUIMessage msg(GUIM_MOUSE_MOTION); HandleMessage(msg); break; } default: break; } } Index: ps/trunk/source/gui/IGUIScrollBarOwner.h =================================================================== --- ps/trunk/source/gui/IGUIScrollBarOwner.h (revision 22586) +++ ps/trunk/source/gui/IGUIScrollBarOwner.h (revision 22587) @@ -1,87 +1,87 @@ -/* Copyright (C) 2015 Wildfire Games. +/* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 0 A.D. If not, see . */ #ifndef INCLUDED_IGUISCROLLBAROWNER #define INCLUDED_IGUISCROLLBAROWNER #include "GUI.h" struct SGUIScrollBarStyle; class IGUIScrollBar; /** * Base-class this if you want an object to contain * one, or several, scroll-bars. * * @see IGUIObject * @see IGUIScrollBar */ class IGUIScrollBarOwner : virtual public IGUIObject { friend class IGUIScrollBar; public: - IGUIScrollBarOwner(); + IGUIScrollBarOwner(CGUI* pGUI); virtual ~IGUIScrollBarOwner(); virtual void Draw(); /** * @see IGUIObject#HandleMessage() */ virtual void HandleMessage(SGUIMessage& Message); /** * @see IGUIObject#ResetStates() */ virtual void ResetStates(); /** * Interface for the m_ScrollBar to use. */ virtual const SGUIScrollBarStyle* GetScrollBarStyle(const CStr& style) const; /** * Add a scroll-bar */ virtual void AddScrollBar(IGUIScrollBar* scrollbar); /** * Get Scroll Bar reference (it should be transparent it's actually * pointers). */ virtual IGUIScrollBar& GetScrollBar(const int& index) { return *m_ScrollBars[index]; } /** * Get the position of the scroll bar at @param index. * Equivalent to GetScrollbar(index).GetPos(). */ virtual float GetScrollBarPos(const int index) const; protected: /** * Predominately you will only have one, but you can have * as many as you like. */ std::vector m_ScrollBars; }; #endif // INCLUDED_IGUISCROLLBAROWNER Index: ps/trunk/source/gui/IGUIScrollBar.h =================================================================== --- ps/trunk/source/gui/IGUIScrollBar.h (revision 22586) +++ ps/trunk/source/gui/IGUIScrollBar.h (revision 22587) @@ -1,452 +1,446 @@ -/* Copyright (C) 2017 Wildfire Games. +/* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 0 A.D. If not, see . */ /* A GUI ScrollBar --Overview-- A GUI Scrollbar, this class doesn't present all functionality to the scrollbar, it just controls the drawing and a wrapper for interaction with it. --Usage-- Used in everywhere scrollbars are needed, like in a combobox for instance. --More info-- Check GUI.h */ #ifndef INCLUDED_IGUISCROLLBAR #define INCLUDED_IGUISCROLLBAR #include "GUI.h" /** * The GUI Scroll-bar style. Tells us how scroll-bars look and feel. * * A scroll-bar style can choose whether to support horizontal, vertical * or both. * * @see IGUIScrollBar */ struct SGUIScrollBarStyle { //-------------------------------------------------------- /** @name General Settings */ //-------------------------------------------------------- //@{ /** * Width of bar, also both sides of the edge buttons. */ float m_Width; /** * Scrollable with the wheel. */ bool m_ScrollWheel; /** * How much (in percent, 0.1f = 10%) to scroll each time * the wheel is admitted, or the buttons are pressed. */ float m_ScrollSpeed; /** * Whether or not the edge buttons should appear or not. Sometimes * you actually don't want them, like perhaps in a combo box. */ bool m_ScrollButtons; /** * Sometimes there is *a lot* to scroll, but to prevent the scroll "bar" * from being almost invisible (or ugly), you can set a minimum in pixel * size. */ float m_MinimumBarSize; /** * Sometimes you would like your scroll bar to have a fixed maximum size * so that the texture does not get too stretched, you can set a maximum * in pixels. */ float m_MaximumBarSize; /** * True if you want edge buttons, i.e. buttons that can be pressed in order * to scroll. */ bool m_UseEdgeButtons; //@} //-------------------------------------------------------- /** @name Vertical Sprites */ //-------------------------------------------------------- //@{ CGUISpriteInstance m_SpriteButtonTop; CGUISpriteInstance m_SpriteButtonTopPressed; CGUISpriteInstance m_SpriteButtonTopDisabled; CGUISpriteInstance m_SpriteButtonTopOver; CGUISpriteInstance m_SpriteButtonBottom; CGUISpriteInstance m_SpriteButtonBottomPressed; CGUISpriteInstance m_SpriteButtonBottomDisabled; CGUISpriteInstance m_SpriteButtonBottomOver; CGUISpriteInstance m_SpriteBarVertical; CGUISpriteInstance m_SpriteBarVerticalOver; CGUISpriteInstance m_SpriteBarVerticalPressed; CGUISpriteInstance m_SpriteBackVertical; //@} //-------------------------------------------------------- /** @name Horizontal Sprites */ //-------------------------------------------------------- //@{ CGUISpriteInstance m_SpriteButtonLeft; CGUISpriteInstance m_SpriteButtonLeftPressed; CGUISpriteInstance m_SpriteButtonLeftDisabled; CGUISpriteInstance m_SpriteButtonRight; CGUISpriteInstance m_SpriteButtonRightPressed; CGUISpriteInstance m_SpriteButtonRightDisabled; CGUISpriteInstance m_SpriteBackHorizontal; CGUISpriteInstance m_SpriteBarHorizontal; //@} }; /** * The GUI Scroll-bar, used everywhere there is a scroll-bar in the game. * * To include a scroll-bar to an object, inherent the object from * IGUIScrollBarOwner and call AddScrollBar() to add the scroll-bars. * * It's also important that the scrollbar is located within the parent * object's mouse over area. Otherwise the input won't be sent to the * scroll-bar. * * The class does not provide all functionality to the scroll-bar, many * things the parent of the scroll-bar, must provide. Like a combo-box. */ class IGUIScrollBar { public: - IGUIScrollBar(); + IGUIScrollBar(CGUI* m_pGUI); virtual ~IGUIScrollBar(); public: /** * Draw the scroll-bar */ virtual void Draw() = 0; /** * If an object that contains a scrollbar has got messages, send * them to the scroll-bar and it will see if the message regarded * itself. * * @see IGUIObject#HandleMessage() */ virtual void HandleMessage(SGUIMessage& Message) = 0; /** * Set m_Pos with g_mouse_x/y input, i.e. when draggin. */ virtual void SetPosFromMousePos(const CPos& mouse) = 0; /** * Hovering the scroll minus button * * @param mouse current mouse position * @return True if mouse positions are hovering the button */ virtual bool HoveringButtonMinus(const CPos& UNUSED(mouse)) { return false; } /** * Hovering the scroll plus button * * @param mouse current mouse position * @return True if mouse positions are hovering the button */ virtual bool HoveringButtonPlus(const CPos& UNUSED(mouse)) { return false; } /** * Get scroll-position */ float GetPos() const { return m_Pos; } /** * Set scroll-position by hand */ virtual void SetPos(float f) { m_Pos = f; UpdatePosBoundaries(); } /** * Get the value of m_Pos that corresponds to the bottom of the scrollable region */ float GetMaxPos() const { return std::max(0.f, m_ScrollRange - m_ScrollSpace); } /** * Scrollbars without height shouldn't be visible */ bool IsVisible() const { return GetMaxPos() != 0.f; } /** * Increase scroll one step */ virtual void ScrollPlus() { m_Pos += 30.f; UpdatePosBoundaries(); } /** * Decrease scroll one step */ virtual void ScrollMinus() { m_Pos -= 30.f; UpdatePosBoundaries(); } /** * Increase scroll three steps */ virtual void ScrollPlusPlenty() { m_Pos += 90.f; UpdatePosBoundaries(); } /** * Decrease scroll three steps */ virtual void ScrollMinusPlenty() { m_Pos -= 90.f; UpdatePosBoundaries(); } /** * Set host object, must be done almost at creation of scroll bar. * @param pOwner Pointer to host object. */ void SetHostObject(IGUIScrollBarOwner* pOwner) { m_pHostObject = pOwner; } /** * Get GUI pointer * @return CGUI pointer */ CGUI* GetGUI() const; /** - * Set GUI pointer - * @param pGUI pointer to CGUI object. - */ - void SetGUI(CGUI* pGUI) { m_pGUI = pGUI; } - - /** * Set Width * @param width Width */ void SetWidth(float width) { m_Width = width; } /** * Set X Position * @param x Position in this axis */ void SetX(float x) { m_X = x; } /** * Set Y Position * @param y Position in this axis */ void SetY(float y) { m_Y = y; } /** * Set Z Position * @param z Position in this axis */ void SetZ(float z) { m_Z = z; } /** * Set Length of scroll bar * @param length Length */ void SetLength(float length) { m_Length = length; } /** * Set content length * @param range Maximum scrollable range */ void SetScrollRange(float range) { m_ScrollRange = std::max(range, 1.f); SetupBarSize(); UpdatePosBoundaries(); } /** * Set space that is visible in the scrollable control. * @param space Visible area in the scrollable control. */ void SetScrollSpace(float space) { m_ScrollSpace = space; SetupBarSize(); UpdatePosBoundaries(); } /** * Set bar pressed * @param b True if bar is pressed */ void SetBarPressed(bool b) { m_BarPressed = b; } /** * Set Scroll bar style string * @param style String with scroll bar style reference name */ void SetScrollBarStyle(const CStr& style) { m_ScrollBarStyle = style; } /** * Get style used by the scrollbar * @return Scroll bar style struct. */ const SGUIScrollBarStyle* GetStyle() const; /** * Get the rectangle of the actual BAR. not the whole scroll-bar. * @return Rectangle, CRect */ virtual CRect GetBarRect() const = 0; /** * Get the rectangle of the outline of the scrollbar, every component of the * scroll-bar should be inside this area. * @return Rectangle, CRect */ virtual CRect GetOuterRect() const = 0; protected: /** * Sets up bar size */ void SetupBarSize(); /** * Call every time m_Pos has been updated. */ void UpdatePosBoundaries(); protected: //@} //-------------------------------------------------------- /** @name Settings */ //-------------------------------------------------------- //@{ /** * Width of the scroll bar */ float m_Width; /** * Absolute X Position */ float m_X; /** * Absolute Y Position */ float m_Y; /** * Absolute Z Position */ float m_Z; /** * Total length of scrollbar, including edge buttons. */ float m_Length; /** * Content that can be scrolled, in pixels */ float m_ScrollRange; /** * Content that can be viewed at a time, in pixels */ float m_ScrollSpace; /** * Use input from the scroll-wheel? True or false. */ float m_BarSize; /** * Scroll bar style reference name */ CStr m_ScrollBarStyle; /** * Pointer to scroll bar style used. */ SGUIScrollBarStyle *m_pStyle; /** * Host object, prerequisite! */ IGUIScrollBarOwner *m_pHostObject; /** * Reference to CGUI object, these cannot work stand-alone */ CGUI *m_pGUI; /** * Mouse position when bar was pressed */ CPos m_BarPressedAtPos; //@} //-------------------------------------------------------- /** @name States */ //-------------------------------------------------------- //@{ /** * If the bar is currently being pressed and dragged. */ bool m_BarPressed; /** * Bar being hovered or not */ bool m_BarHovered; /** * Scroll buttons hovered */ bool m_ButtonMinusHovered, m_ButtonPlusHovered; /** * Scroll buttons pressed */ bool m_ButtonMinusPressed, m_ButtonPlusPressed; /** * Position of scroll bar, 0 means scrolled all the way to one side. * It is measured in pixels, it is up to the host to make it actually * apply in pixels. */ float m_Pos; /** * Position from 0.f to 1.f it had when the bar was pressed. */ float m_PosWhenPressed; //@} }; #endif // INCLUDED_IGUISCROLLBAR Index: ps/trunk/source/gui/MiniMap.cpp =================================================================== --- ps/trunk/source/gui/MiniMap.cpp (revision 22586) +++ ps/trunk/source/gui/MiniMap.cpp (revision 22587) @@ -1,708 +1,709 @@ /* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 0 A.D. If not, see . */ #include "precompiled.h" #include #include "MiniMap.h" #include "graphics/GameView.h" #include "graphics/LOSTexture.h" #include "graphics/MiniPatch.h" #include "graphics/Terrain.h" #include "graphics/TerrainTextureEntry.h" #include "graphics/TerrainTextureManager.h" #include "graphics/TerritoryTexture.h" #include "gui/GUI.h" #include "gui/GUIManager.h" #include "lib/bits.h" #include "lib/external_libraries/libsdl.h" #include "lib/ogl.h" #include "lib/timer.h" #include "ps/ConfigDB.h" #include "ps/Filesystem.h" #include "ps/Game.h" #include "ps/GameSetup/Config.h" #include "ps/Profile.h" #include "ps/World.h" #include "ps/XML/Xeromyces.h" #include "renderer/Renderer.h" #include "renderer/WaterManager.h" #include "scriptinterface/ScriptInterface.h" #include "simulation2/Simulation2.h" #include "simulation2/components/ICmpMinimap.h" #include "simulation2/system/ParamNode.h" extern bool g_GameRestarted; // Set max drawn entities to UINT16_MAX for now, which is more than enough // TODO: we should be cleverer about drawing them to reduce clutter const u16 MAX_ENTITIES_DRAWN = 65535; static unsigned int ScaleColor(unsigned int color, float x) { unsigned int r = unsigned(float(color & 0xff) * x); unsigned int g = unsigned(float((color>>8) & 0xff) * x); unsigned int b = unsigned(float((color>>16) & 0xff) * x); return (0xff000000 | b | g<<8 | r<<16); } -CMiniMap::CMiniMap() : +CMiniMap::CMiniMap(CGUI* pGUI) : + IGUIObject(pGUI), m_TerrainTexture(0), m_TerrainData(0), m_MapSize(0), m_Terrain(0), m_TerrainDirty(true), m_MapScale(1.f), m_EntitiesDrawn(0), m_IndexArray(GL_STATIC_DRAW), m_VertexArray(GL_DYNAMIC_DRAW), m_NextBlinkTime(0.0), m_PingDuration(25.0), m_BlinkState(false), m_WaterHeight(0.0) { AddSetting(GUIST_CStrW, "tooltip"); AddSetting(GUIST_CStr, "tooltip_style"); m_Clicking = false; m_MouseHovering = false; // Register Relax NG validator CXeromyces::AddValidator(g_VFS, "pathfinder", "simulation/data/pathfinder.rng"); // Get the maximum height for unit passage in water. CParamNode externalParamNode; CParamNode::LoadXML(externalParamNode, L"simulation/data/pathfinder.xml", "pathfinder"); const CParamNode pathingSettings = externalParamNode.GetChild("Pathfinder").GetChild("PassabilityClasses"); if (pathingSettings.GetChild("default").IsOk() && pathingSettings.GetChild("default").GetChild("MaxWaterDepth").IsOk()) m_ShallowPassageHeight = pathingSettings.GetChild("default").GetChild("MaxWaterDepth").ToFloat(); else m_ShallowPassageHeight = 0.0f; m_AttributePos.type = GL_FLOAT; m_AttributePos.elems = 2; m_VertexArray.AddAttribute(&m_AttributePos); m_AttributeColor.type = GL_UNSIGNED_BYTE; m_AttributeColor.elems = 4; m_VertexArray.AddAttribute(&m_AttributeColor); m_VertexArray.SetNumVertices(MAX_ENTITIES_DRAWN); m_VertexArray.Layout(); m_IndexArray.SetNumVertices(MAX_ENTITIES_DRAWN); m_IndexArray.Layout(); VertexArrayIterator index = m_IndexArray.GetIterator(); for (u16 i = 0; i < MAX_ENTITIES_DRAWN; ++i) *index++ = i; m_IndexArray.Upload(); m_IndexArray.FreeBackingStore(); VertexArrayIterator attrPos = m_AttributePos.GetIterator(); VertexArrayIterator attrColor = m_AttributeColor.GetIterator(); for (u16 i = 0; i < MAX_ENTITIES_DRAWN; ++i) { (*attrColor)[0] = 0; (*attrColor)[1] = 0; (*attrColor)[2] = 0; (*attrColor)[3] = 0; ++attrColor; (*attrPos)[0] = -10000.0f; (*attrPos)[1] = -10000.0f; ++attrPos; } m_VertexArray.Upload(); double blinkDuration = 1.0; // Tests won't have config initialised if (CConfigDB::IsInitialised()) { CFG_GET_VAL("gui.session.minimap.pingduration", m_PingDuration); CFG_GET_VAL("gui.session.minimap.blinkduration", blinkDuration); } m_HalfBlinkDuration = blinkDuration/2; } CMiniMap::~CMiniMap() { Destroy(); } void CMiniMap::HandleMessage(SGUIMessage& Message) { switch (Message.type) { case GUIM_MOUSE_PRESS_LEFT: if (m_MouseHovering) { SetCameraPos(); m_Clicking = true; } break; case GUIM_MOUSE_RELEASE_LEFT: if (m_MouseHovering && m_Clicking) SetCameraPos(); m_Clicking = false; break; case GUIM_MOUSE_DBLCLICK_LEFT: if (m_MouseHovering && m_Clicking) SetCameraPos(); m_Clicking = false; break; case GUIM_MOUSE_ENTER: m_MouseHovering = true; break; case GUIM_MOUSE_LEAVE: m_Clicking = false; m_MouseHovering = false; break; case GUIM_MOUSE_RELEASE_RIGHT: CMiniMap::FireWorldClickEvent(SDL_BUTTON_RIGHT, 1); break; case GUIM_MOUSE_DBLCLICK_RIGHT: CMiniMap::FireWorldClickEvent(SDL_BUTTON_RIGHT, 2); break; case GUIM_MOUSE_MOTION: if (m_MouseHovering && m_Clicking) SetCameraPos(); break; case GUIM_MOUSE_WHEEL_DOWN: case GUIM_MOUSE_WHEEL_UP: Message.Skip(); break; default: break; } } bool CMiniMap::MouseOver() { // Get the mouse position. CPos mousePos = GetMousePos(); // Get the position of the center of the minimap. CPos minimapCenter = CPos(m_CachedActualSize.left + m_CachedActualSize.GetWidth() / 2.0, m_CachedActualSize.bottom - m_CachedActualSize.GetHeight() / 2.0); // Take the magnitude of the difference of the mouse position and minimap center. double distFromCenter = sqrt(pow((mousePos.x - minimapCenter.x), 2) + pow((mousePos.y - minimapCenter.y), 2)); // If the distance is less then the radius of the minimap (half the width) the mouse is over the minimap. if (distFromCenter < m_CachedActualSize.GetWidth() / 2.0) return true; else return false; } void CMiniMap::GetMouseWorldCoordinates(float& x, float& z) { // Determine X and Z according to proportion of mouse position and minimap CPos mousePos = GetMousePos(); float px = (mousePos.x - m_CachedActualSize.left) / m_CachedActualSize.GetWidth(); float py = (m_CachedActualSize.bottom - mousePos.y) / m_CachedActualSize.GetHeight(); float angle = GetAngle(); // Scale world coordinates for shrunken square map x = TERRAIN_TILE_SIZE * m_MapSize * (m_MapScale * (cos(angle)*(px-0.5) - sin(angle)*(py-0.5)) + 0.5); z = TERRAIN_TILE_SIZE * m_MapSize * (m_MapScale * (cos(angle)*(py-0.5) + sin(angle)*(px-0.5)) + 0.5); } void CMiniMap::SetCameraPos() { CTerrain* terrain = g_Game->GetWorld()->GetTerrain(); CVector3D target; GetMouseWorldCoordinates(target.X, target.Z); target.Y = terrain->GetExactGroundLevel(target.X, target.Z); g_Game->GetView()->MoveCameraTarget(target); } float CMiniMap::GetAngle() { CVector3D cameraIn = m_Camera->m_Orientation.GetIn(); return -atan2(cameraIn.X, cameraIn.Z); } void CMiniMap::FireWorldClickEvent(int UNUSED(button), int UNUSED(clicks)) { JSContext* cx = g_GUI->GetActiveGUI()->GetScriptInterface()->GetContext(); JSAutoRequest rq(cx); float x, z; GetMouseWorldCoordinates(x, z); JS::RootedValue coords(cx); g_GUI->GetActiveGUI()->GetScriptInterface()->CreateObject(&coords, "x", x, "z", z); JS::AutoValueVector paramData(cx); paramData.append(coords); ScriptEvent("worldclick", paramData); } // This sets up and draws the rectangle on the minimap // which represents the view of the camera in the world. void CMiniMap::DrawViewRect(CMatrix3D transform) { // Compute the camera frustum intersected with a fixed-height plane. // Use the water height as a fixed base height, which should be the lowest we can go float h = g_Renderer.GetWaterManager()->m_WaterHeight; const float width = m_CachedActualSize.GetWidth(); const float height = m_CachedActualSize.GetHeight(); const float invTileMapSize = 1.0f / float(TERRAIN_TILE_SIZE * m_MapSize); CVector3D hitPt[4]; hitPt[0] = m_Camera->GetWorldCoordinates(0, g_Renderer.GetHeight(), h); hitPt[1] = m_Camera->GetWorldCoordinates(g_Renderer.GetWidth(), g_Renderer.GetHeight(), h); hitPt[2] = m_Camera->GetWorldCoordinates(g_Renderer.GetWidth(), 0, h); hitPt[3] = m_Camera->GetWorldCoordinates(0, 0, h); float ViewRect[4][2]; for (int i = 0; i < 4; ++i) { // convert to minimap space ViewRect[i][0] = (width * hitPt[i].X * invTileMapSize); ViewRect[i][1] = (height * hitPt[i].Z * invTileMapSize); } float viewVerts[] = { ViewRect[0][0], -ViewRect[0][1], ViewRect[1][0], -ViewRect[1][1], ViewRect[2][0], -ViewRect[2][1], ViewRect[3][0], -ViewRect[3][1] }; // Enable Scissoring to restrict the rectangle to only the minimap. glScissor( m_CachedActualSize.left * g_GuiScale, g_Renderer.GetHeight() - m_CachedActualSize.bottom * g_GuiScale, width * g_GuiScale, height * g_GuiScale); glEnable(GL_SCISSOR_TEST); glLineWidth(2.0f); CShaderDefines lineDefines; lineDefines.Add(str_MINIMAP_LINE, str_1); CShaderTechniquePtr tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, g_Renderer.GetSystemShaderDefines(), lineDefines); tech->BeginPass(); CShaderProgramPtr shader = tech->GetShader(); shader->Uniform(str_transform, transform); shader->Uniform(str_color, 1.0f, 0.3f, 0.3f, 1.0f); shader->VertexPointer(2, GL_FLOAT, 0, viewVerts); shader->AssertPointersBound(); if (!g_Renderer.m_SkipSubmit) glDrawArrays(GL_LINE_LOOP, 0, 4); tech->EndPass(); glLineWidth(1.0f); glDisable(GL_SCISSOR_TEST); } struct MinimapUnitVertex { u8 r, g, b, a; float x, y; }; // Adds a vertex to the passed VertexArray static void inline addVertex(const MinimapUnitVertex& v, VertexArrayIterator& attrColor, VertexArrayIterator& attrPos) { (*attrColor)[0] = v.r; (*attrColor)[1] = v.g; (*attrColor)[2] = v.b; (*attrColor)[3] = v.a; ++attrColor; (*attrPos)[0] = v.x; (*attrPos)[1] = v.y; ++attrPos; } void CMiniMap::DrawTexture(CShaderProgramPtr shader, float coordMax, float angle, float x, float y, float x2, float y2, float z) { // Rotate the texture coordinates (0,0)-(coordMax,coordMax) around their center point (m,m) // Scale square maps to fit in circular minimap area const float s = sin(angle) * m_MapScale; const float c = cos(angle) * m_MapScale; const float m = coordMax / 2.f; float quadTex[] = { m*(-c + s + 1.f), m*(-c + -s + 1.f), m*(c + s + 1.f), m*(-c + s + 1.f), m*(c + -s + 1.f), m*(c + s + 1.f), m*(c + -s + 1.f), m*(c + s + 1.f), m*(-c + -s + 1.f), m*(c + -s + 1.f), m*(-c + s + 1.f), m*(-c + -s + 1.f) }; float quadVerts[] = { x, y, z, x2, y, z, x2, y2, z, x2, y2, z, x, y2, z, x, y, z }; shader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, 0, quadTex); shader->VertexPointer(3, GL_FLOAT, 0, quadVerts); shader->AssertPointersBound(); if (!g_Renderer.m_SkipSubmit) glDrawArrays(GL_TRIANGLES, 0, 6); } // TODO: render the minimap in a framebuffer and just draw the frambuffer texture // most of the time, updating the framebuffer twice a frame. // Here it updates as ping-pong either texture or vertex array each sec to lower gpu stalling // (those operations cause a gpu sync, which slows down the way gpu works) void CMiniMap::Draw() { PROFILE3("render minimap"); // The terrain isn't actually initialized until the map is loaded, which // happens when the game is started, so abort until then. if (!(GetGUI() && g_Game && g_Game->IsGameStarted())) return; CSimulation2* sim = g_Game->GetSimulation2(); CmpPtr cmpRangeManager(*sim, SYSTEM_ENTITY); ENSURE(cmpRangeManager); // Set our globals in case they hadn't been set before m_Camera = g_Game->GetView()->GetCamera(); m_Terrain = g_Game->GetWorld()->GetTerrain(); m_Width = (u32)(m_CachedActualSize.right - m_CachedActualSize.left); m_Height = (u32)(m_CachedActualSize.bottom - m_CachedActualSize.top); m_MapSize = m_Terrain->GetVerticesPerSide(); m_TextureSize = (GLsizei)round_up_to_pow2((size_t)m_MapSize); m_MapScale = (cmpRangeManager->GetLosCircular() ? 1.f : 1.414f); if (!m_TerrainTexture || g_GameRestarted) CreateTextures(); // only update 2x / second // (note: since units only move a few pixels per second on the minimap, // we can get away with infrequent updates; this is slow) // TODO: Update all but camera at same speed as simulation static double last_time; const double cur_time = timer_Time(); const bool doUpdate = cur_time - last_time > 0.5; if (doUpdate) { last_time = cur_time; if (m_TerrainDirty || m_WaterHeight != g_Renderer.GetWaterManager()->m_WaterHeight) RebuildTerrainTexture(); } const float x = m_CachedActualSize.left, y = m_CachedActualSize.bottom; const float x2 = m_CachedActualSize.right, y2 = m_CachedActualSize.top; const float z = GetBufferedZ(); const float texCoordMax = (float)(m_MapSize - 1) / (float)m_TextureSize; const float angle = GetAngle(); const float unitScale = (cmpRangeManager->GetLosCircular() ? 1.f : m_MapScale/2.f); // Disable depth updates to prevent apparent z-fighting-related issues // with some drivers causing units to get drawn behind the texture. glDepthMask(0); CShaderProgramPtr shader; CShaderTechniquePtr tech; CShaderDefines baseDefines; baseDefines.Add(str_MINIMAP_BASE, str_1); tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, g_Renderer.GetSystemShaderDefines(), baseDefines); tech->BeginPass(); shader = tech->GetShader(); // Draw the main textured quad shader->BindTexture(str_baseTex, m_TerrainTexture); const CMatrix3D baseTransform = GetDefaultGuiMatrix(); CMatrix3D baseTextureTransform; baseTextureTransform.SetIdentity(); shader->Uniform(str_transform, baseTransform); shader->Uniform(str_textureTransform, baseTextureTransform); DrawTexture(shader, texCoordMax, angle, x, y, x2, y2, z); // Draw territory boundaries glEnable(GL_BLEND); CTerritoryTexture& territoryTexture = g_Game->GetView()->GetTerritoryTexture(); shader->BindTexture(str_baseTex, territoryTexture.GetTexture()); const CMatrix3D* territoryTransform = territoryTexture.GetMinimapTextureMatrix(); shader->Uniform(str_transform, baseTransform); shader->Uniform(str_textureTransform, *territoryTransform); DrawTexture(shader, 1.0f, angle, x, y, x2, y2, z); tech->EndPass(); // Draw the LOS quad in black, using alpha values from the LOS texture CLOSTexture& losTexture = g_Game->GetView()->GetLOSTexture(); CShaderDefines losDefines; losDefines.Add(str_MINIMAP_LOS, str_1); tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, g_Renderer.GetSystemShaderDefines(), losDefines); tech->BeginPass(); shader = tech->GetShader(); shader->BindTexture(str_baseTex, losTexture.GetTexture()); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); const CMatrix3D* losTransform = losTexture.GetMinimapTextureMatrix(); shader->Uniform(str_transform, baseTransform); shader->Uniform(str_textureTransform, *losTransform); DrawTexture(shader, 1.0f, angle, x, y, x2, y2, z); tech->EndPass(); glDisable(GL_BLEND); PROFILE_START("minimap units"); CShaderDefines pointDefines; pointDefines.Add(str_MINIMAP_POINT, str_1); tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, g_Renderer.GetSystemShaderDefines(), pointDefines); tech->BeginPass(); shader = tech->GetShader(); shader->Uniform(str_transform, baseTransform); shader->Uniform(str_pointSize, 3.f); CMatrix3D unitMatrix; unitMatrix.SetIdentity(); // Center the minimap on the origin of the axis of rotation. unitMatrix.Translate(-(x2 - x) / 2.f, -(y2 - y) / 2.f, 0.f); // Rotate the map. unitMatrix.RotateZ(angle); // Scale square maps to fit. unitMatrix.Scale(unitScale, unitScale, 1.f); // Move the minimap back to it's starting position. unitMatrix.Translate((x2 - x) / 2.f, (y2 - y) / 2.f, 0.f); // Move the minimap to it's final location. unitMatrix.Translate(x, y, z); // Apply the gui matrix. unitMatrix *= GetDefaultGuiMatrix(); // Load the transform into the shader. shader->Uniform(str_transform, unitMatrix); const float sx = (float)m_Width / ((m_MapSize - 1) * TERRAIN_TILE_SIZE); const float sy = (float)m_Height / ((m_MapSize - 1) * TERRAIN_TILE_SIZE); CSimulation2::InterfaceList ents = sim->GetEntitiesWithInterface(IID_Minimap); if (doUpdate) { VertexArrayIterator attrPos = m_AttributePos.GetIterator(); VertexArrayIterator attrColor = m_AttributeColor.GetIterator(); m_EntitiesDrawn = 0; MinimapUnitVertex v; std::vector pingingVertices; pingingVertices.reserve(MAX_ENTITIES_DRAWN / 2); if (cur_time > m_NextBlinkTime) { m_BlinkState = !m_BlinkState; m_NextBlinkTime = cur_time + m_HalfBlinkDuration; } entity_pos_t posX, posZ; for (CSimulation2::InterfaceList::const_iterator it = ents.begin(); it != ents.end(); ++it) { ICmpMinimap* cmpMinimap = static_cast(it->second); if (cmpMinimap->GetRenderData(v.r, v.g, v.b, posX, posZ)) { ICmpRangeManager::ELosVisibility vis = cmpRangeManager->GetLosVisibility(it->first, g_Game->GetSimulation2()->GetSimContext().GetCurrentDisplayedPlayer()); if (vis != ICmpRangeManager::VIS_HIDDEN) { v.a = 255; v.x = posX.ToFloat() * sx; v.y = -posZ.ToFloat() * sy; // Check minimap pinging to indicate something if (m_BlinkState && cmpMinimap->CheckPing(cur_time, m_PingDuration)) { v.r = 255; // ping color is white v.g = 255; v.b = 255; pingingVertices.push_back(v); } else { addVertex(v, attrColor, attrPos); ++m_EntitiesDrawn; } } } } // Add the pinged vertices at the end, so they are drawn on top for (size_t v = 0; v < pingingVertices.size(); ++v) { addVertex(pingingVertices[v], attrColor, attrPos); ++m_EntitiesDrawn; } ENSURE(m_EntitiesDrawn < MAX_ENTITIES_DRAWN); m_VertexArray.Upload(); } m_VertexArray.PrepareForRendering(); if (m_EntitiesDrawn > 0) { #if !CONFIG2_GLES if (g_Renderer.GetRenderPath() == CRenderer::RP_SHADER) glEnable(GL_VERTEX_PROGRAM_POINT_SIZE); #endif u8* indexBase = m_IndexArray.Bind(); u8* base = m_VertexArray.Bind(); const GLsizei stride = (GLsizei)m_VertexArray.GetStride(); shader->VertexPointer(2, GL_FLOAT, stride, base + m_AttributePos.offset); shader->ColorPointer(4, GL_UNSIGNED_BYTE, stride, base + m_AttributeColor.offset); shader->AssertPointersBound(); if (!g_Renderer.m_SkipSubmit) glDrawElements(GL_POINTS, (GLsizei)(m_EntitiesDrawn), GL_UNSIGNED_SHORT, indexBase); g_Renderer.GetStats().m_DrawCalls++; CVertexBuffer::Unbind(); #if !CONFIG2_GLES if (g_Renderer.GetRenderPath() == CRenderer::RP_SHADER) glDisable(GL_VERTEX_PROGRAM_POINT_SIZE); #endif } tech->EndPass(); DrawViewRect(unitMatrix); PROFILE_END("minimap units"); // Reset depth mask glDepthMask(1); } void CMiniMap::CreateTextures() { Destroy(); // Create terrain texture glGenTextures(1, &m_TerrainTexture); g_Renderer.BindTexture(0, m_TerrainTexture); // Initialise texture with solid black, for the areas we don't // overwrite with glTexSubImage2D later u32* texData = new u32[m_TextureSize * m_TextureSize]; for (ssize_t i = 0; i < m_TextureSize * m_TextureSize; ++i) texData[i] = 0xFF000000; glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_TextureSize, m_TextureSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, texData); delete[] texData; m_TerrainData = new u32[(m_MapSize - 1) * (m_MapSize - 1)]; glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); // Rebuild and upload both of them RebuildTerrainTexture(); } void CMiniMap::RebuildTerrainTexture() { u32 x = 0; u32 y = 0; u32 w = m_MapSize - 1; u32 h = m_MapSize - 1; m_WaterHeight = g_Renderer.GetWaterManager()->m_WaterHeight; m_TerrainDirty = false; for (u32 j = 0; j < h; ++j) { u32* dataPtr = m_TerrainData + ((y + j) * (m_MapSize - 1)) + x; for (u32 i = 0; i < w; ++i) { float avgHeight = ( m_Terrain->GetVertexGroundLevel((int)i, (int)j) + m_Terrain->GetVertexGroundLevel((int)i+1, (int)j) + m_Terrain->GetVertexGroundLevel((int)i, (int)j+1) + m_Terrain->GetVertexGroundLevel((int)i+1, (int)j+1) ) / 4.0f; if (avgHeight < m_WaterHeight && avgHeight > m_WaterHeight - m_ShallowPassageHeight) { // shallow water *dataPtr++ = 0xffc09870; } else if (avgHeight < m_WaterHeight) { // Set water as constant color for consistency on different maps *dataPtr++ = 0xffa07850; } else { int hmap = ((int)m_Terrain->GetHeightMap()[(y + j) * m_MapSize + x + i]) >> 8; int val = (hmap / 3) + 170; u32 color = 0xFFFFFFFF; CMiniPatch* mp = m_Terrain->GetTile(x + i, y + j); if (mp) { CTerrainTextureEntry* tex = mp->GetTextureEntry(); if (tex) { // If the texture can't be loaded yet, set the dirty flags // so we'll try regenerating the terrain texture again soon if(!tex->GetTexture()->TryLoad()) m_TerrainDirty = true; color = tex->GetBaseColor(); } } *dataPtr++ = ScaleColor(color, float(val) / 255.0f); } } } // Upload the texture g_Renderer.BindTexture(0, m_TerrainTexture); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_MapSize - 1, m_MapSize - 1, GL_RGBA, GL_UNSIGNED_BYTE, m_TerrainData); } void CMiniMap::Destroy() { if (m_TerrainTexture) { glDeleteTextures(1, &m_TerrainTexture); m_TerrainTexture = 0; } SAFE_ARRAY_DELETE(m_TerrainData); } Index: ps/trunk/source/gui/CButton.cpp =================================================================== --- ps/trunk/source/gui/CButton.cpp (revision 22586) +++ ps/trunk/source/gui/CButton.cpp (revision 22587) @@ -1,121 +1,122 @@ /* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 0 A.D. If not, see . */ #include "precompiled.h" #include "CButton.h" #include "gui/CGUIColor.h" #include "lib/ogl.h" -CButton::CButton() +CButton::CButton(CGUI* pGUI) + : IGUIObject(pGUI) { AddSetting(GUIST_float, "buffer_zone"); AddSetting(GUIST_CGUIString, "caption"); AddSetting(GUIST_int, "cell_id"); AddSetting(GUIST_CStrW, "font"); AddSetting(GUIST_CStrW, "sound_disabled"); AddSetting(GUIST_CStrW, "sound_enter"); AddSetting(GUIST_CStrW, "sound_leave"); AddSetting(GUIST_CStrW, "sound_pressed"); AddSetting(GUIST_CStrW, "sound_released"); AddSetting(GUIST_CGUISpriteInstance, "sprite"); AddSetting(GUIST_CGUISpriteInstance, "sprite_over"); AddSetting(GUIST_CGUISpriteInstance, "sprite_pressed"); AddSetting(GUIST_CGUISpriteInstance, "sprite_disabled"); AddSetting(GUIST_EAlign, "text_align"); AddSetting(GUIST_EVAlign, "text_valign"); AddSetting(GUIST_CGUIColor, "textcolor"); AddSetting(GUIST_CGUIColor, "textcolor_over"); AddSetting(GUIST_CGUIColor, "textcolor_pressed"); AddSetting(GUIST_CGUIColor, "textcolor_disabled"); AddSetting(GUIST_CStrW, "tooltip"); AddSetting(GUIST_CStr, "tooltip_style"); // Add text AddText(new SGUIText()); } CButton::~CButton() { } void CButton::SetupText() { if (!GetGUI()) return; ENSURE(m_GeneratedTexts.size() == 1); CStrW font; if (GUI::GetSetting(this, "font", font) != PSRETURN_OK || font.empty()) // Use the default if none is specified // TODO Gee: (2004-08-14) Default should not be hard-coded, but be in styles! font = L"default"; CGUIString caption; GUI::GetSetting(this, "caption", caption); float buffer_zone = 0.f; GUI::GetSetting(this, "buffer_zone", buffer_zone); *m_GeneratedTexts[0] = GetGUI()->GenerateText(caption, font, m_CachedActualSize.GetWidth(), buffer_zone, this); CalculateTextPosition(m_CachedActualSize, m_TextPos, *m_GeneratedTexts[0]); } void CButton::HandleMessage(SGUIMessage& Message) { // Important IGUIButtonBehavior::HandleMessage(Message); IGUITextOwner::HandleMessage(Message); } void CButton::Draw() { float bz = GetBufferedZ(); CGUISpriteInstance* sprite; CGUISpriteInstance* sprite_over; CGUISpriteInstance* sprite_pressed; CGUISpriteInstance* sprite_disabled; int cell_id; // Statically initialise some strings, so we don't have to do // lots of allocation every time this function is called static const CStr strSprite("sprite"); static const CStr strSpriteOver("sprite_over"); static const CStr strSpritePressed("sprite_pressed"); static const CStr strSpriteDisabled("sprite_disabled"); static const CStr strCellId("cell_id"); GUI::GetSettingPointer(this, strSprite, sprite); GUI::GetSettingPointer(this, strSpriteOver, sprite_over); GUI::GetSettingPointer(this, strSpritePressed, sprite_pressed); GUI::GetSettingPointer(this, strSpriteDisabled, sprite_disabled); GUI::GetSetting(this, strCellId, cell_id); DrawButton(m_CachedActualSize, bz, *sprite, *sprite_over, *sprite_pressed, *sprite_disabled, cell_id); CGUIColor color = ChooseColor(); DrawText(0, color, m_TextPos, bz+0.1f); } Index: ps/trunk/source/gui/CButton.h =================================================================== --- ps/trunk/source/gui/CButton.h (revision 22586) +++ ps/trunk/source/gui/CButton.h (revision 22587) @@ -1,65 +1,65 @@ -/* Copyright (C) 2015 Wildfire Games. +/* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 0 A.D. If not, see . */ #ifndef INCLUDED_CBUTTON #define INCLUDED_CBUTTON #include "GUI.h" /** * Button * * @see IGUIObject * @see IGUIButtonBehavior */ class CButton : public IGUIButtonBehavior, public IGUITextOwner { GUI_OBJECT(CButton) public: - CButton(); + CButton(CGUI* pGUI); virtual ~CButton(); /** * @see IGUIObject#ResetStates() */ virtual void ResetStates() { IGUIButtonBehavior::ResetStates(); } /** * @see IGUIObject#HandleMessage() */ virtual void HandleMessage(SGUIMessage& Message); /** * Draws the Button */ virtual void Draw(); protected: /** * Sets up text, should be called every time changes has been * made that can change the visual. */ void SetupText(); /** * Placement of text. */ CPos m_TextPos; }; #endif // INCLUDED_CBUTTON Index: ps/trunk/source/gui/CChart.cpp =================================================================== --- ps/trunk/source/gui/CChart.cpp (revision 22586) +++ ps/trunk/source/gui/CChart.cpp (revision 22587) @@ -1,331 +1,332 @@ /* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 0 A.D. If not, see . */ #include "precompiled.h" #include "CChart.h" #include "gui/CGUIColor.h" #include "graphics/ShaderManager.h" #include "i18n/L10n.h" #include "lib/ogl.h" #include "ps/CLogger.h" #include "renderer/Renderer.h" #include "third_party/cppformat/format.h" #include -CChart::CChart() +CChart::CChart(CGUI* pGUI) + : IGUIObject(pGUI) { AddSetting(GUIST_CGUIColor, "axis_color"); AddSetting(GUIST_float, "axis_width"); AddSetting(GUIST_float, "buffer_zone"); AddSetting(GUIST_CStrW, "font"); AddSetting(GUIST_CStrW, "format_x"); AddSetting(GUIST_CStrW, "format_y"); AddSetting(GUIST_CGUIList, "series_color"); AddSetting(GUIST_CGUISeries, "series"); AddSetting(GUIST_EAlign, "text_align"); GUI::GetSetting(this, "axis_width", m_AxisWidth); GUI::GetSetting(this, "format_x", m_FormatX); GUI::GetSetting(this, "format_y", m_FormatY); } CChart::~CChart() { } void CChart::HandleMessage(SGUIMessage& Message) { // TODO: implement zoom switch (Message.type) { case GUIM_SETTINGS_UPDATED: { GUI::GetSetting(this, "axis_width", m_AxisWidth); GUI::GetSetting(this, "format_x", m_FormatX); GUI::GetSetting(this, "format_y", m_FormatY); UpdateSeries(); break; } } } void CChart::DrawLine(const CShaderProgramPtr& shader, const CGUIColor& color, const std::vector& vertices) const { shader->Uniform(str_color, color); shader->VertexPointer(3, GL_FLOAT, 0, &vertices[0]); shader->AssertPointersBound(); glEnable(GL_LINE_SMOOTH); glLineWidth(1.1f); if (!g_Renderer.m_SkipSubmit) glDrawArrays(GL_LINE_STRIP, 0, vertices.size() / 3); glLineWidth(1.0f); glDisable(GL_LINE_SMOOTH); } void CChart::DrawTriangleStrip(const CShaderProgramPtr& shader, const CGUIColor& color, const std::vector& vertices) const { shader->Uniform(str_color, color); shader->VertexPointer(3, GL_FLOAT, 0, &vertices[0]); shader->AssertPointersBound(); if (!g_Renderer.m_SkipSubmit) glDrawArrays(GL_TRIANGLE_STRIP, 0, vertices.size() / 3); } void CChart::DrawAxes(const CShaderProgramPtr& shader) const { const float bz = GetBufferedZ(); CRect rect = GetChartRect(); std::vector vertices; vertices.reserve(30); #define ADD(x, y) vertices.push_back(x); vertices.push_back(y); vertices.push_back(bz + 0.5f); ADD(m_CachedActualSize.right, m_CachedActualSize.bottom); ADD(rect.right + m_AxisWidth, rect.bottom); ADD(m_CachedActualSize.left, m_CachedActualSize.bottom); ADD(rect.left, rect.bottom); ADD(m_CachedActualSize.left, m_CachedActualSize.top); ADD(rect.left, rect.top - m_AxisWidth); #undef ADD CGUIColor axis_color(0.5f, 0.5f, 0.5f, 1.f); GUI::GetSetting(this, "axis_color", axis_color); DrawTriangleStrip(shader, axis_color, vertices); } void CChart::Draw() { PROFILE3("render chart"); if (!GetGUI()) return; if (m_Series.empty()) return; const float bz = GetBufferedZ(); CRect rect = GetChartRect(); const float width = rect.GetWidth(); const float height = rect.GetHeight(); // Disable depth updates to prevent apparent z-fighting-related issues // with some drivers causing units to get drawn behind the texture. glDepthMask(0); // Setup the render state CMatrix3D transform = GetDefaultGuiMatrix(); CShaderDefines lineDefines; CShaderTechniquePtr tech = g_Renderer.GetShaderManager().LoadEffect(str_gui_solid, g_Renderer.GetSystemShaderDefines(), lineDefines); tech->BeginPass(); CShaderProgramPtr shader = tech->GetShader(); shader->Uniform(str_transform, transform); CVector2D scale(width / (m_RightTop.X - m_LeftBottom.X), height / (m_RightTop.Y - m_LeftBottom.Y)); for (const CChartData& data : m_Series) { if (data.m_Points.empty()) continue; std::vector vertices; for (const CVector2D& point : data.m_Points) { if (fabs(point.X) != std::numeric_limits::infinity() && fabs(point.Y) != std::numeric_limits::infinity()) { vertices.push_back(rect.left + (point.X - m_LeftBottom.X) * scale.X); vertices.push_back(rect.bottom - (point.Y - m_LeftBottom.Y) * scale.Y); vertices.push_back(bz + 0.5f); } else { DrawLine(shader, data.m_Color, vertices); vertices.clear(); } } if (!vertices.empty()) DrawLine(shader, data.m_Color, vertices); } if (m_AxisWidth > 0) DrawAxes(shader); tech->EndPass(); // Reset depth mask glDepthMask(1); for (size_t i = 0; i < m_TextPositions.size(); ++i) DrawText(i, CGUIColor(1.f, 1.f, 1.f, 1.f), m_TextPositions[i], bz + 0.5f); } CRect CChart::GetChartRect() const { return CRect( m_CachedActualSize.TopLeft() + CPos(m_AxisWidth, m_AxisWidth), m_CachedActualSize.BottomRight() - CPos(m_AxisWidth, m_AxisWidth) ); } void CChart::UpdateSeries() { CGUISeries* pSeries; GUI::GetSettingPointer(this, "series", pSeries); CGUIList* pSeriesColor; GUI::GetSettingPointer(this, "series_color", pSeriesColor); m_Series.clear(); m_Series.resize(pSeries->m_Series.size()); for (size_t i = 0; i < pSeries->m_Series.size(); ++i) { CChartData& data = m_Series[i]; if (i < pSeriesColor->m_Items.size() && !GUI::ParseColor(pSeriesColor->m_Items[i].GetOriginalString(), data.m_Color, 0)) LOGWARNING("GUI: Error parsing 'series_color' (\"%s\")", utf8_from_wstring(pSeriesColor->m_Items[i].GetOriginalString())); data.m_Points = pSeries->m_Series[i]; } UpdateBounds(); SetupText(); } void CChart::SetupText() { if (!GetGUI()) return; for (SGUIText* t : m_GeneratedTexts) delete t; m_GeneratedTexts.clear(); m_TextPositions.clear(); if (m_Series.empty()) return; CStrW font; if (GUI::GetSetting(this, "font", font) != PSRETURN_OK || font.empty()) font = L"default"; float buffer_zone = 0.f; GUI::GetSetting(this, "buffer_zone", buffer_zone); // Add Y-axis GUI::GetSetting(this, "format_y", m_FormatY); const float height = GetChartRect().GetHeight(); // TODO: split values depend on the format; if (m_EqualY) { // We don't need to generate many items for equal values AddFormattedValue(m_FormatY, m_RightTop.Y, font, buffer_zone); m_TextPositions.emplace_back(GetChartRect().TopLeft()); } else for (int i = 0; i < 3; ++i) { AddFormattedValue(m_FormatY, m_RightTop.Y - (m_RightTop.Y - m_LeftBottom.Y) / 3.f * i, font, buffer_zone); m_TextPositions.emplace_back(GetChartRect().TopLeft() + CPos(0.f, height / 3.f * i)); } // Add X-axis GUI::GetSetting(this, "format_x", m_FormatX); const float width = GetChartRect().GetWidth(); if (m_EqualX) { CSize text_size = AddFormattedValue(m_FormatX, m_RightTop.X, font, buffer_zone); m_TextPositions.emplace_back(GetChartRect().BottomRight() - text_size); } else for (int i = 0; i < 3; ++i) { CSize text_size = AddFormattedValue(m_FormatX, m_RightTop.X - (m_RightTop.X - m_LeftBottom.X) / 3 * i, font, buffer_zone); m_TextPositions.emplace_back(GetChartRect().BottomRight() - text_size - CPos(width / 3 * i, 0.f)); } } CSize CChart::AddFormattedValue(const CStrW& format, const float value, const CStrW& font, const float buffer_zone) { // TODO: we need to catch cases with equal formatted values. CGUIString gui_str; if (format == L"DECIMAL2") { wchar_t buffer[64]; swprintf(buffer, 64, L"%.2f", value); gui_str.SetValue(buffer); } else if (format == L"INTEGER") { wchar_t buffer[64]; swprintf(buffer, 64, L"%d", std::lround(value)); gui_str.SetValue(buffer); } else if (format == L"DURATION_SHORT") { const int seconds = value; wchar_t buffer[64]; swprintf(buffer, 64, L"%d:%02d", seconds / 60, seconds % 60); gui_str.SetValue(buffer); } else if (format == L"PERCENTAGE") { wchar_t buffer[64]; swprintf(buffer, 64, L"%d%%", std::lround(value)); gui_str.SetValue(buffer); } else { LOGERROR("Unsupported chart format: " + format.EscapeToPrintableASCII()); return CSize(); } SGUIText* text = new SGUIText(); *text = GetGUI()->GenerateText(gui_str, font, 0, buffer_zone, this); AddText(text); return text->m_Size; } void CChart::UpdateBounds() { if (m_Series.empty() || m_Series[0].m_Points.empty()) { m_LeftBottom = m_RightTop = CVector2D(0.f, 0.f); return; } m_LeftBottom = m_RightTop = m_Series[0].m_Points[0]; for (const CChartData& data : m_Series) for (const CVector2D& point : data.m_Points) { if (fabs(point.X) != std::numeric_limits::infinity() && point.X < m_LeftBottom.X) m_LeftBottom.X = point.X; if (fabs(point.Y) != std::numeric_limits::infinity() && point.Y < m_LeftBottom.Y) m_LeftBottom.Y = point.Y; if (fabs(point.X) != std::numeric_limits::infinity() && point.X > m_RightTop.X) m_RightTop.X = point.X; if (fabs(point.Y) != std::numeric_limits::infinity() && point.Y > m_RightTop.Y) m_RightTop.Y = point.Y; } m_EqualY = m_RightTop.Y == m_LeftBottom.Y; if (m_EqualY) m_RightTop.Y += 1; m_EqualX = m_RightTop.X == m_LeftBottom.X; if (m_EqualX) m_RightTop.X += 1; } Index: ps/trunk/source/gui/CChart.h =================================================================== --- ps/trunk/source/gui/CChart.h (revision 22586) +++ ps/trunk/source/gui/CChart.h (revision 22587) @@ -1,94 +1,94 @@ /* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 0 A.D. If not, see . */ #ifndef INCLUDED_CCHART #define INCLUDED_CCHART #include "gui/GUI.h" #include "gui/IGUITextOwner.h" #include "maths/Vector2D.h" #include struct CChartData { CGUIColor m_Color; std::vector m_Points; }; /** * Chart for a data visualization as lines or points * * @see IGUIObject */ class CChart : public IGUITextOwner { GUI_OBJECT(CChart) public: - CChart(); + CChart(CGUI* pGUI); virtual ~CChart(); protected: /** * @see IGUIObject#HandleMessage() */ virtual void HandleMessage(SGUIMessage& Message); /** * Draws the Chart */ virtual void Draw(); virtual CRect GetChartRect() const; void UpdateSeries(); void SetupText(); std::vector m_Series; CVector2D m_LeftBottom, m_RightTop; CStrW m_FormatX, m_FormatY; std::vector m_TextPositions; float m_AxisWidth; bool m_EqualX, m_EqualY; private: /** * Helper functions */ void DrawLine(const CShaderProgramPtr& shader, const CGUIColor& color, const std::vector& vertices) const; // Draws the triangle sequence so that the each next triangle has a common edge with the previous one. // If we need to draw n triangles, we need only n + 2 points. void DrawTriangleStrip(const CShaderProgramPtr& shader, const CGUIColor& color, const std::vector& vertices) const; // Represents axes as triangles and draws them with DrawTriangleStrip. void DrawAxes(const CShaderProgramPtr& shader) const; CSize AddFormattedValue(const CStrW& format, const float value, const CStrW& font, const float buffer_zone); void UpdateBounds(); }; #endif // INCLUDED_CCHART Index: ps/trunk/source/gui/CCheckBox.cpp =================================================================== --- ps/trunk/source/gui/CCheckBox.cpp (revision 22586) +++ ps/trunk/source/gui/CCheckBox.cpp (revision 22587) @@ -1,149 +1,150 @@ /* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 0 A.D. If not, see . */ #include "precompiled.h" #include "CCheckBox.h" #include "gui/CGUIColor.h" #include "graphics/FontMetrics.h" #include "ps/CLogger.h" #include "ps/CStrIntern.h" /** * TODO: Since there is no call to DrawText, the checkbox won't render any text. * Thus the font, caption, textcolor and other settings have no effect. */ -CCheckBox::CCheckBox() +CCheckBox::CCheckBox(CGUI* pGUI) + : IGUIObject(pGUI) { AddSetting(GUIST_float, "buffer_zone"); AddSetting(GUIST_CGUIString, "caption"); AddSetting(GUIST_int, "cell_id"); AddSetting(GUIST_bool, "checked"); AddSetting(GUIST_CStrW, "font"); AddSetting(GUIST_CStrW, "sound_disabled"); AddSetting(GUIST_CStrW, "sound_enter"); AddSetting(GUIST_CStrW, "sound_leave"); AddSetting(GUIST_CStrW, "sound_pressed"); AddSetting(GUIST_CStrW, "sound_released"); AddSetting(GUIST_CGUISpriteInstance, "sprite"); AddSetting(GUIST_CGUISpriteInstance, "sprite_over"); AddSetting(GUIST_CGUISpriteInstance, "sprite_pressed"); AddSetting(GUIST_CGUISpriteInstance, "sprite_disabled"); AddSetting(GUIST_CGUISpriteInstance, "sprite2"); AddSetting(GUIST_CGUISpriteInstance, "sprite2_over"); AddSetting(GUIST_CGUISpriteInstance, "sprite2_pressed"); AddSetting(GUIST_CGUISpriteInstance, "sprite2_disabled"); AddSetting(GUIST_float, "square_side"); AddSetting(GUIST_CGUIColor, "textcolor"); AddSetting(GUIST_CGUIColor, "textcolor_over"); AddSetting(GUIST_CGUIColor, "textcolor_pressed"); AddSetting(GUIST_CGUIColor, "textcolor_disabled"); AddSetting(GUIST_CStrW, "tooltip"); AddSetting(GUIST_CStr, "tooltip_style"); AddText(new SGUIText()); } CCheckBox::~CCheckBox() { } void CCheckBox::SetupText() { if (!GetGUI()) return; ENSURE(m_GeneratedTexts.size() == 1); CStrW font; if (GUI::GetSetting(this, "font", font) != PSRETURN_OK || font.empty()) // Use the default if none is specified // TODO Gee: (2004-08-14) Default should not be hard-coded, but be in styles! font = L"default"; float square_side; GUI::GetSetting(this, "square_side", square_side); CGUIString caption; GUI::GetSetting(this, "caption", caption); float buffer_zone = 0.f; GUI::GetSetting(this, "buffer_zone", buffer_zone); *m_GeneratedTexts[0] = GetGUI()->GenerateText(caption, font, m_CachedActualSize.GetWidth()-square_side, 0.f, this); } void CCheckBox::HandleMessage(SGUIMessage& Message) { // Important IGUIButtonBehavior::HandleMessage(Message); IGUITextOwner::HandleMessage(Message); switch (Message.type) { case GUIM_PRESSED: { bool checked; // Switch to opposite. GUI::GetSetting(this, "checked", checked); checked = !checked; GUI::SetSetting(this, "checked", checked); break; } default: break; } } void CCheckBox::Draw() { float bz = GetBufferedZ(); bool checked; int cell_id; CGUISpriteInstance* sprite; CGUISpriteInstance* sprite_over; CGUISpriteInstance* sprite_pressed; CGUISpriteInstance* sprite_disabled; GUI::GetSetting(this, "checked", checked); GUI::GetSetting(this, "cell_id", cell_id); if (checked) { GUI::GetSettingPointer(this, "sprite2", sprite); GUI::GetSettingPointer(this, "sprite2_over", sprite_over); GUI::GetSettingPointer(this, "sprite2_pressed", sprite_pressed); GUI::GetSettingPointer(this, "sprite2_disabled", sprite_disabled); } else { GUI::GetSettingPointer(this, "sprite", sprite); GUI::GetSettingPointer(this, "sprite_over", sprite_over); GUI::GetSettingPointer(this, "sprite_pressed", sprite_pressed); GUI::GetSettingPointer(this, "sprite_disabled", sprite_disabled); } DrawButton(m_CachedActualSize, bz, *sprite, *sprite_over, *sprite_pressed, *sprite_disabled, cell_id); } Index: ps/trunk/source/gui/CCheckBox.h =================================================================== --- ps/trunk/source/gui/CCheckBox.h (revision 22586) +++ ps/trunk/source/gui/CCheckBox.h (revision 22587) @@ -1,61 +1,61 @@ -/* Copyright (C) 2015 Wildfire Games. +/* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 0 A.D. If not, see . */ #ifndef INCLUDED_CCHECKBOX #define INCLUDED_CCHECKBOX #include "GUI.h" /** * CheckBox * * @see IGUIObject * @see IGUISettingsObject * @see IGUIButtonBehavior */ class CCheckBox : public IGUIButtonBehavior, public IGUITextOwner { GUI_OBJECT(CCheckBox) public: - CCheckBox(); + CCheckBox(CGUI* pGUI); virtual ~CCheckBox(); /** * @see IGUIObject#ResetStates() */ virtual void ResetStates() { IGUIButtonBehavior::ResetStates(); } /** * @see IGUIObject#HandleMessage() */ virtual void HandleMessage(SGUIMessage& Message); /** * Draws the control */ virtual void Draw(); protected: /** * Sets up text, should be called every time changes has been * made that can change the visual. */ void SetupText(); }; #endif // INCLUDED_CCHECKBOX Index: ps/trunk/source/gui/CDropDown.cpp =================================================================== --- ps/trunk/source/gui/CDropDown.cpp (revision 22586) +++ ps/trunk/source/gui/CDropDown.cpp (revision 22587) @@ -1,564 +1,565 @@ /* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 0 A.D. If not, see . */ #include "precompiled.h" #include "CDropDown.h" #include "gui/CGUIColor.h" #include "lib/external_libraries/libsdl.h" #include "lib/ogl.h" #include "lib/timer.h" #include "ps/CLogger.h" #include "soundmanager/ISoundManager.h" -CDropDown::CDropDown() - : m_Open(false), m_HideScrollBar(false), m_ElementHighlight(-1) +CDropDown::CDropDown(CGUI* pGUI) + : CList(pGUI), IGUIObject(pGUI), + m_Open(false), m_HideScrollBar(false), m_ElementHighlight(-1) { AddSetting(GUIST_float, "button_width"); AddSetting(GUIST_float, "dropdown_size"); AddSetting(GUIST_float, "dropdown_buffer"); AddSetting(GUIST_uint, "minimum_visible_items"); // AddSetting(GUIST_CStrW, "font"); AddSetting(GUIST_CStrW, "sound_closed"); AddSetting(GUIST_CStrW, "sound_disabled"); AddSetting(GUIST_CStrW, "sound_enter"); AddSetting(GUIST_CStrW, "sound_leave"); AddSetting(GUIST_CStrW, "sound_opened"); AddSetting(GUIST_CGUISpriteInstance, "sprite"); // Background that sits around the size AddSetting(GUIST_CGUISpriteInstance, "sprite_disabled"); AddSetting(GUIST_CGUISpriteInstance, "sprite_list"); // Background of the drop down list AddSetting(GUIST_CGUISpriteInstance, "sprite2"); // Button that sits to the right AddSetting(GUIST_CGUISpriteInstance, "sprite2_over"); AddSetting(GUIST_CGUISpriteInstance, "sprite2_pressed"); AddSetting(GUIST_CGUISpriteInstance, "sprite2_disabled"); AddSetting(GUIST_EVAlign, "text_valign"); // Add these in CList! And implement TODO //AddSetting(GUIST_CGUIColor, "textcolor_over"); //AddSetting(GUIST_CGUIColor, "textcolor_pressed"); AddSetting(GUIST_CGUIColor, "textcolor_selected"); AddSetting(GUIST_CGUIColor, "textcolor_disabled"); // Scrollbar is forced to be true. GUI::SetSetting(this, "scrollbar", true); } CDropDown::~CDropDown() { } void CDropDown::SetupText() { SetupListRect(); CList::SetupText(); } void CDropDown::UpdateCachedSize() { CList::UpdateCachedSize(); SetupText(); } void CDropDown::HandleMessage(SGUIMessage& Message) { // Important switch (Message.type) { case GUIM_SETTINGS_UPDATED: { // Update cached list rect if (Message.value == "size" || Message.value == "absolute" || Message.value == "dropdown_size" || Message.value == "dropdown_buffer" || Message.value == "minimum_visible_items" || Message.value == "scrollbar_style" || Message.value == "button_width") { SetupListRect(); } break; } case GUIM_MOUSE_MOTION: { if (!m_Open) break; CPos mouse = GetMousePos(); if (!GetListRect().PointInside(mouse)) break; bool scrollbar; CGUIList* pList; GUI::GetSetting(this, "scrollbar", scrollbar); GUI::GetSettingPointer(this, "list", pList); float scroll = 0.f; if (scrollbar) scroll = GetScrollBar(0).GetPos(); CRect rect = GetListRect(); mouse.y += scroll; int set = -1; for (int i = 0; i < (int)pList->m_Items.size(); ++i) { if (mouse.y >= rect.top + m_ItemsYPositions[i] && mouse.y < rect.top + m_ItemsYPositions[i+1] && // mouse is not over scroll-bar (m_HideScrollBar || mouse.x < GetScrollBar(0).GetOuterRect().left || mouse.x > GetScrollBar(0).GetOuterRect().right)) { set = i; } } if (set != -1) { m_ElementHighlight = set; //UpdateAutoScroll(); } break; } case GUIM_MOUSE_ENTER: { bool enabled; GUI::GetSetting(this, "enabled", enabled); if (!enabled) break; CStrW soundPath; if (g_SoundManager && GUI::GetSetting(this, "sound_enter", soundPath) == PSRETURN_OK && !soundPath.empty()) g_SoundManager->PlayAsUI(soundPath.c_str(), false); break; } case GUIM_MOUSE_LEAVE: { GUI::GetSetting(this, "selected", m_ElementHighlight); bool enabled; GUI::GetSetting(this, "enabled", enabled); if (!enabled) break; CStrW soundPath; if (g_SoundManager && GUI::GetSetting(this, "sound_leave", soundPath) == PSRETURN_OK && !soundPath.empty()) g_SoundManager->PlayAsUI(soundPath.c_str(), false); break; } // We can't inherent this routine from CList, because we need to include // a mouse click to open the dropdown, also the coordinates are changed. case GUIM_MOUSE_PRESS_LEFT: { bool enabled; GUI::GetSetting(this, "enabled", enabled); if (!enabled) { CStrW soundPath; if (g_SoundManager && GUI::GetSetting(this, "sound_disabled", soundPath) == PSRETURN_OK && !soundPath.empty()) g_SoundManager->PlayAsUI(soundPath.c_str(), false); break; } if (!m_Open) { CGUIList* pList; GUI::GetSettingPointer(this, "list", pList); if (pList->m_Items.empty()) return; m_Open = true; GetScrollBar(0).SetZ(GetBufferedZ()); GUI::GetSetting(this, "selected", m_ElementHighlight); // Start at the position of the selected item, if possible. GetScrollBar(0).SetPos(m_ItemsYPositions.empty() ? 0 : m_ItemsYPositions[m_ElementHighlight] - 60); CStrW soundPath; if (g_SoundManager && GUI::GetSetting(this, "sound_opened", soundPath) == PSRETURN_OK && !soundPath.empty()) g_SoundManager->PlayAsUI(soundPath.c_str(), false); return; // overshadow } else { CPos mouse = GetMousePos(); // If the regular area is pressed, then abort, and close. if (m_CachedActualSize.PointInside(mouse)) { m_Open = false; GetScrollBar(0).SetZ(GetBufferedZ()); CStrW soundPath; if (g_SoundManager && GUI::GetSetting(this, "sound_closed", soundPath) == PSRETURN_OK && !soundPath.empty()) g_SoundManager->PlayAsUI(soundPath.c_str(), false); return; // overshadow } if (m_HideScrollBar || mouse.x < GetScrollBar(0).GetOuterRect().left || mouse.x > GetScrollBar(0).GetOuterRect().right || mouse.y < GetListRect().top) { m_Open = false; GetScrollBar(0).SetZ(GetBufferedZ()); } } break; } case GUIM_MOUSE_WHEEL_DOWN: { bool enabled; GUI::GetSetting(this, "enabled", enabled); // Don't switch elements by scrolling when open, causes a confusing interaction between this and the scrollbar. if (m_Open || !enabled) break; GUI::GetSetting(this, "selected", m_ElementHighlight); if (m_ElementHighlight + 1 >= (int)m_ItemsYPositions.size() - 1) break; ++m_ElementHighlight; GUI::SetSetting(this, "selected", m_ElementHighlight); break; } case GUIM_MOUSE_WHEEL_UP: { bool enabled; GUI::GetSetting(this, "enabled", enabled); // Don't switch elements by scrolling when open, causes a confusing interaction between this and the scrollbar. if (m_Open || !enabled) break; GUI::GetSetting(this, "selected", m_ElementHighlight); if (m_ElementHighlight - 1 < 0) break; m_ElementHighlight--; GUI::SetSetting(this, "selected", m_ElementHighlight); break; } case GUIM_LOST_FOCUS: { if (m_Open) { CStrW soundPath; if (g_SoundManager && GUI::GetSetting(this, "sound_closed", soundPath) == PSRETURN_OK && !soundPath.empty()) g_SoundManager->PlayAsUI(soundPath.c_str(), false); } m_Open = false; break; } case GUIM_LOAD: SetupListRect(); break; default: break; } // Important that this is after, so that overshadowed implementations aren't processed CList::HandleMessage(Message); // As HandleMessage functions return void, we need to manually verify // whether the child list's items were modified. if (CList::GetModified()) SetupText(); } InReaction CDropDown::ManuallyHandleEvent(const SDL_Event_* ev) { InReaction result = IN_PASS; bool update_highlight = false; if (ev->ev.type == SDL_KEYDOWN) { int szChar = ev->ev.key.keysym.sym; switch (szChar) { case '\r': m_Open = false; result = IN_HANDLED; break; case SDLK_HOME: case SDLK_END: case SDLK_UP: case SDLK_DOWN: case SDLK_PAGEUP: case SDLK_PAGEDOWN: if (!m_Open) return IN_PASS; // Set current selected item to highlighted, before // then really processing these in CList::ManuallyHandleEvent() GUI::SetSetting(this, "selected", m_ElementHighlight); update_highlight = true; break; default: // If we have inputed a character try to get the closest element to it. // TODO: not too nice and doesn't deal with dashes. if (m_Open && ((szChar >= SDLK_a && szChar <= SDLK_z) || szChar == SDLK_SPACE || (szChar >= SDLK_0 && szChar <= SDLK_9) || (szChar >= SDLK_KP_0 && szChar <= SDLK_KP_9))) { // arbitrary 1 second limit to add to string or start fresh. // maximal amount of characters is 100, which imo is far more than enough. if (timer_Time() - m_TimeOfLastInput > 1.0 || m_InputBuffer.length() >= 100) m_InputBuffer = szChar; else m_InputBuffer += szChar; m_TimeOfLastInput = timer_Time(); CGUIList* pList; GUI::GetSettingPointer(this, "list", pList); // let's look for the closest element // basically it's alphabetic order and "as many letters as we can get". int closest = -1; int bestIndex = -1; int difference = 1250; for (int i = 0; i < (int)pList->m_Items.size(); ++i) { int indexOfDifference = 0; int diff = 0; for (size_t j = 0; j < m_InputBuffer.length(); ++j) { diff = std::abs((int)(pList->m_Items[i].GetRawString().LowerCase()[j]) - (int)m_InputBuffer[j]); if (diff == 0) indexOfDifference = j+1; else break; } if (indexOfDifference > bestIndex || (indexOfDifference >= bestIndex && diff < difference)) { bestIndex = indexOfDifference; closest = i; difference = diff; } } // let's select the closest element. There should basically always be one. if (closest != -1) { GUI::SetSetting(this, "selected", closest); update_highlight = true; GetScrollBar(0).SetPos(m_ItemsYPositions[closest] - 60); } result = IN_HANDLED; } break; } } if (CList::ManuallyHandleEvent(ev) == IN_HANDLED) result = IN_HANDLED; if (update_highlight) GUI::GetSetting(this, "selected", m_ElementHighlight); return result; } void CDropDown::SetupListRect() { extern int g_yres; extern float g_GuiScale; float size, buffer, yres; yres = g_yres / g_GuiScale; u32 minimumVisibleItems; GUI::GetSetting(this, "dropdown_size", size); GUI::GetSetting(this, "dropdown_buffer", buffer); GUI::GetSetting(this, "minimum_visible_items", minimumVisibleItems); if (m_ItemsYPositions.empty()) { m_CachedListRect = CRect(m_CachedActualSize.left, m_CachedActualSize.bottom + buffer, m_CachedActualSize.right, m_CachedActualSize.bottom + buffer + size); m_HideScrollBar = false; } // Too many items so use a scrollbar else if (m_ItemsYPositions.back() > size) { // Place items below if at least some items can be placed below if (m_CachedActualSize.bottom + buffer + size <= yres) m_CachedListRect = CRect(m_CachedActualSize.left, m_CachedActualSize.bottom + buffer, m_CachedActualSize.right, m_CachedActualSize.bottom + buffer + size); else if ((m_ItemsYPositions.size() > minimumVisibleItems && yres - m_CachedActualSize.bottom - buffer >= m_ItemsYPositions[minimumVisibleItems]) || m_CachedActualSize.top < yres - m_CachedActualSize.bottom) m_CachedListRect = CRect(m_CachedActualSize.left, m_CachedActualSize.bottom + buffer, m_CachedActualSize.right, yres); // Not enough space below, thus place items above else m_CachedListRect = CRect(m_CachedActualSize.left, std::max(0.f, m_CachedActualSize.top - buffer - size), m_CachedActualSize.right, m_CachedActualSize.top - buffer); m_HideScrollBar = false; } else { // Enough space below, no scrollbar needed if (m_CachedActualSize.bottom + buffer + m_ItemsYPositions.back() <= yres) { m_CachedListRect = CRect(m_CachedActualSize.left, m_CachedActualSize.bottom + buffer, m_CachedActualSize.right, m_CachedActualSize.bottom + buffer + m_ItemsYPositions.back()); m_HideScrollBar = true; } // Enough space below for some items, but not all, so place items below and use a scrollbar else if ((m_ItemsYPositions.size() > minimumVisibleItems && yres - m_CachedActualSize.bottom - buffer >= m_ItemsYPositions[minimumVisibleItems]) || m_CachedActualSize.top < yres - m_CachedActualSize.bottom) { m_CachedListRect = CRect(m_CachedActualSize.left, m_CachedActualSize.bottom + buffer, m_CachedActualSize.right, yres); m_HideScrollBar = false; } // Not enough space below, thus place items above. Hide the scrollbar accordingly else { m_CachedListRect = CRect(m_CachedActualSize.left, std::max(0.f, m_CachedActualSize.top - buffer - m_ItemsYPositions.back()), m_CachedActualSize.right, m_CachedActualSize.top - buffer); m_HideScrollBar = m_CachedActualSize.top > m_ItemsYPositions.back() + buffer; } } } CRect CDropDown::GetListRect() const { return m_CachedListRect; } bool CDropDown::MouseOver() { if(!GetGUI()) throw PSERROR_GUI_OperationNeedsGUIObject(); if (m_Open) { CRect rect(m_CachedActualSize.left, std::min(m_CachedActualSize.top, GetListRect().top), m_CachedActualSize.right, std::max(m_CachedActualSize.bottom, GetListRect().bottom)); return rect.PointInside(GetMousePos()); } else return m_CachedActualSize.PointInside(GetMousePos()); } void CDropDown::Draw() { if (!GetGUI()) return; float bz = GetBufferedZ(); float dropdown_size, button_width; GUI::GetSetting(this, "dropdown_size", dropdown_size); GUI::GetSetting(this, "button_width", button_width); CGUISpriteInstance* sprite; CGUISpriteInstance* sprite2; CGUISpriteInstance* sprite2_second; int cell_id, selected = 0; CGUIColor color; bool enabled; GUI::GetSetting(this, "enabled", enabled); GUI::GetSettingPointer(this, "sprite2", sprite2); GUI::GetSetting(this, "cell_id", cell_id); GUI::GetSetting(this, "selected", selected); GUI::GetSetting(this, enabled ? "textcolor_selected" : "textcolor_disabled", color); GUI::GetSettingPointer(this, enabled ? "sprite" : "sprite_disabled", sprite); GetGUI()->DrawSprite(*sprite, cell_id, bz, m_CachedActualSize); if (button_width > 0.f) { CRect rect(m_CachedActualSize.right-button_width, m_CachedActualSize.top, m_CachedActualSize.right, m_CachedActualSize.bottom); if (!enabled) { GUI::GetSettingPointer(this, "sprite2_disabled", sprite2_second); GetGUI()->DrawSprite(GUI<>::FallBackSprite(*sprite2_second, *sprite2), cell_id, bz+0.05f, rect); } else if (m_Open) { GUI::GetSettingPointer(this, "sprite2_pressed", sprite2_second); GetGUI()->DrawSprite(GUI<>::FallBackSprite(*sprite2_second, *sprite2), cell_id, bz+0.05f, rect); } else if (m_MouseHovering) { GUI::GetSettingPointer(this, "sprite2_over", sprite2_second); GetGUI()->DrawSprite(GUI<>::FallBackSprite(*sprite2_second, *sprite2), cell_id, bz+0.05f, rect); } else GetGUI()->DrawSprite(*sprite2, cell_id, bz+0.05f, rect); } if (selected != -1) // TODO: Maybe check validity completely? { CRect cliparea(m_CachedActualSize.left, m_CachedActualSize.top, m_CachedActualSize.right-button_width, m_CachedActualSize.bottom); CPos pos(m_CachedActualSize.left, m_CachedActualSize.top); DrawText(selected, color, pos, bz+0.1f, cliparea); } bool* scrollbar = NULL; bool old; GUI::GetSettingPointer(this, "scrollbar", scrollbar); old = *scrollbar; if (m_Open) { if (m_HideScrollBar) *scrollbar = false; DrawList(m_ElementHighlight, "sprite_list", "sprite_selectarea", "textcolor"); if (m_HideScrollBar) *scrollbar = old; } } // When a dropdown list is opened, it needs to be visible above all the other // controls on the page. The only way I can think of to do this is to increase // its z value when opened, so that it's probably on top. float CDropDown::GetBufferedZ() const { float bz = CList::GetBufferedZ(); if (m_Open) return std::min(bz + 500.f, 1000.f); // TODO - don't use magic number for max z value else return bz; } Index: ps/trunk/source/gui/CDropDown.h =================================================================== --- ps/trunk/source/gui/CDropDown.h (revision 22586) +++ ps/trunk/source/gui/CDropDown.h (revision 22587) @@ -1,130 +1,130 @@ -/* Copyright (C) 2017 Wildfire Games. +/* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 0 A.D. If not, see . */ /* GUI Object - Drop Down (list) --Overview-- Works just like a list-box, but it hides all the elements that aren't selected. They can be brought up by pressing the control. --More info-- Check GUI.h */ #ifndef INCLUDED_CDROPDOWN #define INCLUDED_CDROPDOWN #include "GUI.h" #include "CList.h" /** * Drop Down * * The control can be pressed, but we will not inherent * this behavior from IGUIButtonBehavior, because when * you press this control, the list with elements will * immediately appear, and not first after release * (which is the whole gist of the IGUIButtonBehavior). */ class CDropDown : public CList { GUI_OBJECT(CDropDown) public: - CDropDown(); + CDropDown(CGUI* pGUI); virtual ~CDropDown(); // virtual void ResetStates() { IGUIButtonBehavior::ResetStates(); } /** * @see IGUIObject#HandleMessage() */ virtual void HandleMessage(SGUIMessage& Message); /** * Handle events manually to catch keyboard inputting. */ virtual InReaction ManuallyHandleEvent(const SDL_Event_* ev); /** * Draws the Button */ virtual void Draw(); // This is one of the few classes we actually need to redefine this function // this is because the size of the control changes whether it is open // or closed. virtual bool MouseOver(); virtual float GetBufferedZ() const; protected: /** * If the size changed, the texts have to be updated as * the word wrapping depends on the size. */ virtual void UpdateCachedSize(); /** * Sets up text, should be called every time changes has been * made that can change the visual. */ void SetupText(); // Sets up the cached GetListRect. Decided whether it should // have a scrollbar, and so on. virtual void SetupListRect(); // Specify a new List rectangle. virtual CRect GetListRect() const; /** * Placement of text. */ CPos m_TextPos; // Is the dropdown opened? bool m_Open; // I didn't cache this at first, but it's just as easy as caching // m_CachedActualSize, so I thought, what the heck it's used a lot. CRect m_CachedListRect; // Hide scrollbar when it's not needed bool m_HideScrollBar; // Not necessarily the element that is selected, this is just // which element should be highlighted. When opening the dropdown // it is set to "selected", but then when moving the mouse it will // change. int m_ElementHighlight; // Stores any text entered by the user for quick access to an element // (ie if you type "acro" it will take you to acropolis). std::string m_InputBuffer; // used to know if we want to restart anew or add to m_inputbuffer. double m_TimeOfLastInput; }; #endif // INCLUDED_CDROPDOWN Index: ps/trunk/source/gui/CGUI.cpp =================================================================== --- ps/trunk/source/gui/CGUI.cpp (revision 22586) +++ ps/trunk/source/gui/CGUI.cpp (revision 22587) @@ -1,1771 +1,1764 @@ /* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 0 A.D. If not, see . */ #include "precompiled.h" #include #include #include "GUI.h" // Types - when including them into the engine. #include "CButton.h" #include "CChart.h" #include "CCheckBox.h" #include "CDropDown.h" #include "CImage.h" #include "CInput.h" #include "CList.h" #include "COList.h" #include "CProgressBar.h" #include "CRadioButton.h" #include "CSlider.h" #include "CText.h" #include "CTooltip.h" #include "MiniMap.h" #include "graphics/FontMetrics.h" #include "graphics/ShaderManager.h" #include "graphics/TextRenderer.h" #include "i18n/L10n.h" #include "lib/bits.h" #include "lib/input.h" #include "lib/sysdep/sysdep.h" #include "lib/timer.h" #include "lib/utf8.h" #include "ps/CLogger.h" #include "ps/Filesystem.h" #include "ps/GameSetup/Config.h" #include "ps/Globals.h" #include "ps/Hotkey.h" #include "ps/Profile.h" #include "ps/Pyrogenesis.h" #include "ps/XML/Xeromyces.h" #include "renderer/Renderer.h" #include "scripting/ScriptFunctions.h" #include "scriptinterface/ScriptInterface.h" extern int g_yres; const double SELECT_DBLCLICK_RATE = 0.5; const u32 MAX_OBJECT_DEPTH = 100; // Max number of nesting for GUI includes. Used to detect recursive inclusion InReaction CGUI::HandleEvent(const SDL_Event_* ev) { InReaction ret = IN_PASS; if (ev->ev.type == SDL_HOTKEYDOWN || ev->ev.type == SDL_HOTKEYUP) { const char* hotkey = static_cast(ev->ev.user.data1); std::map >::iterator it = m_HotkeyObjects.find(hotkey); if (it != m_HotkeyObjects.end()) for (IGUIObject* const& obj : it->second) { // Update hotkey status before sending the event, // else the status will be outdated when processing the GUI event. HotkeyInputHandler(ev); ret = IN_HANDLED; if (ev->ev.type == SDL_HOTKEYDOWN) obj->SendEvent(GUIM_PRESSED, "press"); else obj->SendEvent(GUIM_RELEASED, "release"); } } else if (ev->ev.type == SDL_MOUSEMOTION) { // Yes the mouse position is stored as float to avoid // constant conversions when operating in a // float-based environment. m_MousePos = CPos((float)ev->ev.motion.x / g_GuiScale, (float)ev->ev.motion.y / g_GuiScale); SGUIMessage msg(GUIM_MOUSE_MOTION); GUI::RecurseObject(GUIRR_HIDDEN | GUIRR_GHOST, m_BaseObject, &IGUIObject::HandleMessage, msg); } // Update m_MouseButtons. (BUTTONUP is handled later.) else if (ev->ev.type == SDL_MOUSEBUTTONDOWN) { switch (ev->ev.button.button) { case SDL_BUTTON_LEFT: case SDL_BUTTON_RIGHT: case SDL_BUTTON_MIDDLE: m_MouseButtons |= Bit(ev->ev.button.button); break; default: break; } } // Update m_MousePos (for delayed mouse button events) CPos oldMousePos = m_MousePos; if (ev->ev.type == SDL_MOUSEBUTTONDOWN || ev->ev.type == SDL_MOUSEBUTTONUP) { m_MousePos = CPos((float)ev->ev.button.x / g_GuiScale, (float)ev->ev.button.y / g_GuiScale); } // Only one object can be hovered IGUIObject* pNearest = NULL; // TODO Gee: (2004-09-08) Big TODO, don't do the below if the SDL_Event is something like a keypress! try { PROFILE("mouse events"); // TODO Gee: Optimizations needed! // these two recursive function are quite overhead heavy. // pNearest will after this point at the hovered object, possibly NULL pNearest = FindObjectUnderMouse(); // Now we'll call UpdateMouseOver on *all* objects, // we'll input the one hovered, and they will each // update their own data and send messages accordingly GUI::RecurseObject(GUIRR_HIDDEN | GUIRR_GHOST, m_BaseObject, &IGUIObject::UpdateMouseOver, pNearest); if (ev->ev.type == SDL_MOUSEBUTTONDOWN) { switch (ev->ev.button.button) { case SDL_BUTTON_LEFT: // Focus the clicked object (or focus none if nothing clicked on) SetFocusedObject(pNearest); if (pNearest) ret = pNearest->SendEvent(GUIM_MOUSE_PRESS_LEFT, "mouseleftpress"); break; case SDL_BUTTON_RIGHT: if (pNearest) ret = pNearest->SendEvent(GUIM_MOUSE_PRESS_RIGHT, "mouserightpress"); break; default: break; } } else if (ev->ev.type == SDL_MOUSEWHEEL && pNearest) { if (ev->ev.wheel.y < 0) ret = pNearest->SendEvent(GUIM_MOUSE_WHEEL_DOWN, "mousewheeldown"); else if (ev->ev.wheel.y > 0) ret = pNearest->SendEvent(GUIM_MOUSE_WHEEL_UP, "mousewheelup"); } else if (ev->ev.type == SDL_MOUSEBUTTONUP) { switch (ev->ev.button.button) { case SDL_BUTTON_LEFT: if (pNearest) { double timeElapsed = timer_Time() - pNearest->m_LastClickTime[SDL_BUTTON_LEFT]; pNearest->m_LastClickTime[SDL_BUTTON_LEFT] = timer_Time(); if (timeElapsed < SELECT_DBLCLICK_RATE) ret = pNearest->SendEvent(GUIM_MOUSE_DBLCLICK_LEFT, "mouseleftdoubleclick"); else ret = pNearest->SendEvent(GUIM_MOUSE_RELEASE_LEFT, "mouseleftrelease"); } break; case SDL_BUTTON_RIGHT: if (pNearest) { double timeElapsed = timer_Time() - pNearest->m_LastClickTime[SDL_BUTTON_RIGHT]; pNearest->m_LastClickTime[SDL_BUTTON_RIGHT] = timer_Time(); if (timeElapsed < SELECT_DBLCLICK_RATE) ret = pNearest->SendEvent(GUIM_MOUSE_DBLCLICK_RIGHT, "mouserightdoubleclick"); else ret = pNearest->SendEvent(GUIM_MOUSE_RELEASE_RIGHT, "mouserightrelease"); } break; } // Reset all states on all visible objects GUI<>::RecurseObject(GUIRR_HIDDEN, m_BaseObject, &IGUIObject::ResetStates); // Since the hover state will have been reset, we reload it. GUI::RecurseObject(GUIRR_HIDDEN | GUIRR_GHOST, m_BaseObject, &IGUIObject::UpdateMouseOver, pNearest); } } catch (PSERROR_GUI& e) { UNUSED2(e); debug_warn(L"CGUI::HandleEvent error"); // TODO Gee: Handle } // BUTTONUP's effect on m_MouseButtons is handled after // everything else, so that e.g. 'press' handlers (activated // on button up) see which mouse button had been pressed. if (ev->ev.type == SDL_MOUSEBUTTONUP) { switch (ev->ev.button.button) { case SDL_BUTTON_LEFT: case SDL_BUTTON_RIGHT: case SDL_BUTTON_MIDDLE: m_MouseButtons &= ~Bit(ev->ev.button.button); break; default: break; } } // Restore m_MousePos (for delayed mouse button events) if (ev->ev.type == SDL_MOUSEBUTTONDOWN || ev->ev.type == SDL_MOUSEBUTTONUP) m_MousePos = oldMousePos; // Handle keys for input boxes if (GetFocusedObject()) { if ((ev->ev.type == SDL_KEYDOWN && ev->ev.key.keysym.sym != SDLK_ESCAPE && !g_keys[SDLK_LCTRL] && !g_keys[SDLK_RCTRL] && !g_keys[SDLK_LALT] && !g_keys[SDLK_RALT]) || ev->ev.type == SDL_HOTKEYDOWN || ev->ev.type == SDL_TEXTINPUT || ev->ev.type == SDL_TEXTEDITING) { ret = GetFocusedObject()->ManuallyHandleEvent(ev); } // else will return IN_PASS because we never used the button. } return ret; } void CGUI::TickObjects() { CStr action = "tick"; GUI::RecurseObject(0, m_BaseObject, &IGUIObject::ScriptEvent, action); m_Tooltip.Update(FindObjectUnderMouse(), m_MousePos, this); } void CGUI::SendEventToAll(const CStr& EventName) { // janwas 2006-03-03: spoke with Ykkrosh about EventName case. // when registering, case is converted to lower - this avoids surprise // if someone were to get the case wrong and then not notice their // handler is never called. however, until now, the other end // (sending events here) wasn't converting to lower case, // leading to a similar problem. // now fixed; case is irrelevant since all are converted to lower. GUI::RecurseObject(0, m_BaseObject, &IGUIObject::ScriptEvent, EventName.LowerCase()); } void CGUI::SendEventToAll(const CStr& EventName, JS::HandleValueArray paramData) { GUI::RecurseObject(0, m_BaseObject, &IGUIObject::ScriptEvent, EventName.LowerCase(), paramData); } CGUI::CGUI(const shared_ptr& runtime) : m_MouseButtons(0), m_FocusedObject(NULL), m_InternalNameNumber(0) { m_ScriptInterface.reset(new ScriptInterface("Engine", "GUIPage", runtime)); m_ScriptInterface->SetCallbackData(this); GuiScriptingInit(*m_ScriptInterface); m_ScriptInterface->LoadGlobalScripts(); - m_BaseObject = new CGUIDummyObject; - m_BaseObject->SetGUI(this); + + m_BaseObject = new CGUIDummyObject(this); } CGUI::~CGUI() { Destroy(); if (m_BaseObject) delete m_BaseObject; } IGUIObject* CGUI::ConstructObject(const CStr& str) { if (m_ObjectTypes.count(str) > 0) - return (*m_ObjectTypes[str])(); - else - { - // Error reporting will be handled with the NULL return. - return NULL; - } + return (*m_ObjectTypes[str])(this); + + // Error reporting will be handled with the nullptr return. + return nullptr; } void CGUI::Initialize() { // Add base types! // You can also add types outside the GUI to extend the flexibility of the GUI. // Pyrogenesis though will have all the object types inserted from here. AddObjectType("empty", &CGUIDummyObject::ConstructObject); AddObjectType("button", &CButton::ConstructObject); AddObjectType("image", &CImage::ConstructObject); AddObjectType("text", &CText::ConstructObject); AddObjectType("checkbox", &CCheckBox::ConstructObject); AddObjectType("radiobutton", &CRadioButton::ConstructObject); AddObjectType("progressbar", &CProgressBar::ConstructObject); AddObjectType("minimap", &CMiniMap::ConstructObject); AddObjectType("input", &CInput::ConstructObject); AddObjectType("list", &CList::ConstructObject); AddObjectType("olist", &COList::ConstructObject); AddObjectType("dropdown", &CDropDown::ConstructObject); AddObjectType("tooltip", &CTooltip::ConstructObject); AddObjectType("chart", &CChart::ConstructObject); AddObjectType("slider", &CSlider::ConstructObject); } void CGUI::Draw() { // Clear the depth buffer, so the GUI is // drawn on top of everything else glClear(GL_DEPTH_BUFFER_BIT); try { // Recurse IGUIObject::Draw() with restriction: hidden // meaning all hidden objects won't call Draw (nor will it recurse its children) GUI<>::RecurseObject(GUIRR_HIDDEN, m_BaseObject, &IGUIObject::Draw); } catch (PSERROR_GUI& e) { LOGERROR("GUI draw error: %s", e.what()); } } void CGUI::DrawSprite(const CGUISpriteInstance& Sprite, int CellID, const float& Z, const CRect& Rect, const CRect& UNUSED(Clipping)) { // If the sprite doesn't exist (name == ""), don't bother drawing anything if (Sprite.IsEmpty()) return; // TODO: Clipping? Sprite.Draw(Rect, CellID, m_Sprites, Z); } void CGUI::Destroy() { // We can use the map to delete all // now we don't want to cancel all if one Destroy fails for (const std::pair& p : m_pAllObjects) { try { p.second->Destroy(); } catch (PSERROR_GUI& e) { UNUSED2(e); debug_warn(L"CGUI::Destroy error"); // TODO Gee: Handle } delete p.second; } m_pAllObjects.clear(); for (const std::pair& p : m_Sprites) delete p.second; m_Sprites.clear(); m_Icons.clear(); } void CGUI::UpdateResolution() { // Update ALL cached GUI<>::RecurseObject(0, m_BaseObject, &IGUIObject::UpdateCachedSize); } void CGUI::AddObject(IGUIObject* pObject) { try { - // Add CGUI pointer - GUI::RecurseObject(0, pObject, &IGUIObject::SetGUI, this); - m_BaseObject->AddChild(pObject); // Cache tree GUI<>::RecurseObject(0, pObject, &IGUIObject::UpdateCachedSize); SGUIMessage msg(GUIM_LOAD); GUI::RecurseObject(0, pObject, &IGUIObject::HandleMessage, msg); } catch (PSERROR_GUI&) { throw; } } void CGUI::UpdateObjects() { // We'll fill a temporary map until we know everything succeeded map_pObjects AllObjects; try { // Fill freshly GUI::RecurseObject(0, m_BaseObject, &IGUIObject::AddToPointersMap, AllObjects); } catch (PSERROR_GUI&) { throw; } // Else actually update the real one m_pAllObjects.swap(AllObjects); } bool CGUI::ObjectExists(const CStr& Name) const { return m_pAllObjects.count(Name) != 0; } IGUIObject* CGUI::FindObjectByName(const CStr& Name) const { map_pObjects::const_iterator it = m_pAllObjects.find(Name); if (it == m_pAllObjects.end()) return NULL; else return it->second; } IGUIObject* CGUI::FindObjectUnderMouse() const { IGUIObject* pNearest = NULL; GUI::RecurseObject(GUIRR_HIDDEN | GUIRR_GHOST, m_BaseObject, &IGUIObject::ChooseMouseOverAndClosest, pNearest); return pNearest; } void CGUI::SetFocusedObject(IGUIObject* pObject) { if (pObject == m_FocusedObject) return; if (m_FocusedObject) { SGUIMessage msg(GUIM_LOST_FOCUS); m_FocusedObject->HandleMessage(msg); } m_FocusedObject = pObject; if (m_FocusedObject) { SGUIMessage msg(GUIM_GOT_FOCUS); m_FocusedObject->HandleMessage(msg); } } const SGUIScrollBarStyle* CGUI::GetScrollBarStyle(const CStr& style) const { std::map::const_iterator it = m_ScrollBarStyles.find(style); if (it == m_ScrollBarStyles.end()) return nullptr; return &it->second; } // private struct used only in GenerateText(...) struct SGenerateTextImage { float m_YFrom, // The image's starting location in Y m_YTo, // The image's end location in Y m_Indentation; // The image width in other words // Some help functions // TODO Gee: CRect => CPoint ? void SetupSpriteCall(const bool Left, SGUIText::SSpriteCall& SpriteCall, const float width, const float y, const CSize& Size, const CStr& TextureName, const float BufferZone, const int CellID) { // TODO Gee: Temp hardcoded values SpriteCall.m_Area.top = y+BufferZone; SpriteCall.m_Area.bottom = y+BufferZone + Size.cy; if (Left) { SpriteCall.m_Area.left = BufferZone; SpriteCall.m_Area.right = Size.cx+BufferZone; } else { SpriteCall.m_Area.left = width-BufferZone - Size.cx; SpriteCall.m_Area.right = width-BufferZone; } SpriteCall.m_CellID = CellID; SpriteCall.m_Sprite = TextureName; m_YFrom = SpriteCall.m_Area.top-BufferZone; m_YTo = SpriteCall.m_Area.bottom+BufferZone; m_Indentation = Size.cx+BufferZone*2; } }; SGUIText CGUI::GenerateText(const CGUIString& string, const CStrW& FontW, const float& Width, const float& BufferZone, const IGUIObject* pObject) { SGUIText Text; CStrIntern Font(FontW.ToUTF8()); if (string.m_Words.empty()) return Text; float x = BufferZone, y = BufferZone; // drawing pointer int from = 0; bool done = false; bool FirstLine = true; // Necessary because text in the first line is shorter // (it doesn't count the line spacing) // Images on the left or the right side. std::vector Images[2]; int pos_last_img = -1; // Position in the string where last img (either left or right) were encountered. // in order to avoid duplicate processing. // Easier to read. bool WordWrapping = (Width != 0); // get the alignment type for the control we are computing the text for since // we are computing the horizontal alignment in this method in order to not have // to run through the TextCalls a second time in the CalculateTextPosition method again EAlign align = EAlign_Left; if (pObject->SettingExists("text_align")) GUI::GetSetting(pObject, "text_align", align); // Go through string word by word for (int i = 0; i < (int)string.m_Words.size()-1 && !done; ++i) { // Pre-process each line one time, so we know which floating images // will be added for that line. // Generated stuff is stored in Feedback. CGUIString::SFeedback Feedback; // Preliminary line_height, used for word-wrapping with floating images. float prelim_line_height = 0.f; // Width and height of all text calls generated. string.GenerateTextCall(this, Feedback, Font, string.m_Words[i], string.m_Words[i+1], FirstLine); // Loop through our images queues, to see if images has been added. // Check if this has already been processed. // Also, floating images are only applicable if Word-Wrapping is on if (WordWrapping && i > pos_last_img) { // Loop left/right for (int j = 0; j < 2; ++j) { for (const CStr& imgname : Feedback.m_Images[j]) { SGUIText::SSpriteCall SpriteCall; SGenerateTextImage Image; // Y is if no other floating images is above, y. Else it is placed // after the last image, like a stack downwards. float _y; if (!Images[j].empty()) _y = std::max(y, Images[j].back().m_YTo); else _y = y; // Get Size from Icon database SGUIIcon icon = GetIcon(imgname); CSize size = icon.m_Size; Image.SetupSpriteCall((j == CGUIString::SFeedback::Left), SpriteCall, Width, _y, size, icon.m_SpriteName, BufferZone, icon.m_CellID); // Check if image is the lowest thing. Text.m_Size.cy = std::max(Text.m_Size.cy, Image.m_YTo); Images[j].push_back(Image); Text.m_SpriteCalls.push_back(std::move(SpriteCall)); } } } pos_last_img = std::max(pos_last_img, i); x += Feedback.m_Size.cx; prelim_line_height = std::max(prelim_line_height, Feedback.m_Size.cy); // If Width is 0, then there's no word-wrapping, disable NewLine. if ((WordWrapping && (x > Width-BufferZone || Feedback.m_NewLine)) || i == (int)string.m_Words.size()-2) { // Change 'from' to 'i', but first keep a copy of its value. int temp_from = from; from = i; static const int From = 0, To = 1; //int width_from=0, width_to=width; float width_range[2]; width_range[From] = BufferZone; width_range[To] = Width - BufferZone; // Floating images are only applicable if word-wrapping is enabled. if (WordWrapping) { // Decide width of the line. We need to iterate our floating images. // this won't be exact because we're assuming the line_height // will be as our preliminary calculation said. But that may change, // although we'd have to add a couple of more loops to try straightening // this problem out, and it is very unlikely to happen noticeably if one // structures his text in a stylistically pure fashion. Even if not, it // is still quite unlikely it will happen. // Loop through left and right side, from and to. for (int j = 0; j < 2; ++j) { for (const SGenerateTextImage& img : Images[j]) { // We're working with two intervals here, the image's and the line height's. // let's find the union of these two. float union_from, union_to; union_from = std::max(y, img.m_YFrom); union_to = std::min(y+prelim_line_height, img.m_YTo); // The union is not empty if (union_to > union_from) { if (j == From) width_range[From] = std::max(width_range[From], img.m_Indentation); else width_range[To] = std::min(width_range[To], Width - img.m_Indentation); } } } } // Reset X for the next loop x = width_range[From]; // Now we'll do another loop to figure out the height and width of // the line (the height of the largest character and the width is // the sum of all of the individual widths). This // couldn't be determined in the first loop (main loop) // because it didn't regard images, so we don't know // if all characters processed, will actually be involved // in that line. float line_height = 0.f; float line_width = 0.f; for (int j = temp_from; j <= i; ++j) { // We don't want to use Feedback now, so we'll have to use // another. CGUIString::SFeedback Feedback2; // Don't attach object, it'll suppress the errors // we want them to be reported in the final GenerateTextCall() // so that we don't get duplicates. string.GenerateTextCall(this, Feedback2, Font, string.m_Words[j], string.m_Words[j+1], FirstLine); // Append X value. x += Feedback2.m_Size.cx; if (WordWrapping && x > width_range[To] && j!=temp_from && !Feedback2.m_NewLine) { // The calculated width of each word includes the space between the current // word and the next. When we're wrapping, we need subtract the width of the // space after the last word on the line before the wrap. CFontMetrics currentFont(Font); line_width -= currentFont.GetCharacterWidth(*L" "); break; } // Let line_height be the maximum m_Height we encounter. line_height = std::max(line_height, Feedback2.m_Size.cy); // If the current word is an explicit new line ("\n"), // break now before adding the width of this character. // ("\n" doesn't have a glyph, thus is given the same width as // the "missing glyph" character by CFont::GetCharacterWidth().) if (WordWrapping && Feedback2.m_NewLine) break; line_width += Feedback2.m_Size.cx; } float dx = 0.f; // compute offset based on what kind of alignment switch (align) { case EAlign_Left: // don't add an offset dx = 0.f; break; case EAlign_Center: dx = ((width_range[To] - width_range[From]) - line_width) / 2; break; case EAlign_Right: dx = width_range[To] - line_width; break; default: debug_warn(L"Broken EAlign in CGUI::GenerateText()"); break; } // Reset x once more x = width_range[From]; // Move down, because font drawing starts from the baseline y += line_height; // Do the real processing now for (int j = temp_from; j <= i; ++j) { // We don't want to use Feedback now, so we'll have to use // another one. CGUIString::SFeedback Feedback2; // Defaults string.GenerateTextCall(this, Feedback2, Font, string.m_Words[j], string.m_Words[j+1], FirstLine, pObject); // Iterate all and set X/Y values // Since X values are not set, we need to make an internal // iteration with an increment that will append the internal // x, that is what x_pointer is for. float x_pointer = 0.f; for (SGUIText::STextCall& tc : Feedback2.m_TextCalls) { tc.m_Pos = CPos(dx + x + x_pointer, y); x_pointer += tc.m_Size.cx; if (tc.m_pSpriteCall) tc.m_pSpriteCall->m_Area += tc.m_Pos - CSize(0, tc.m_pSpriteCall->m_Area.GetHeight()); } // Append X value. x += Feedback2.m_Size.cx; // The first word overrides the width limit, what we // do, in those cases, are just drawing that word even // though it'll extend the object. if (WordWrapping) // only if word-wrapping is applicable { if (Feedback2.m_NewLine) { from = j+1; // Sprite call can exist within only a newline segment, // therefore we need this. Text.m_SpriteCalls.insert( Text.m_SpriteCalls.end(), std::make_move_iterator(Feedback2.m_SpriteCalls.begin()), std::make_move_iterator(Feedback2.m_SpriteCalls.end())); break; } else if (x > width_range[To] && j == temp_from) { from = j+1; // do not break, since we want it to be added to m_TextCalls } else if (x > width_range[To]) { from = j; break; } } // Add the whole Feedback2.m_TextCalls to our m_TextCalls. Text.m_TextCalls.insert( Text.m_TextCalls.end(), std::make_move_iterator(Feedback2.m_TextCalls.begin()), std::make_move_iterator(Feedback2.m_TextCalls.end())); Text.m_SpriteCalls.insert( Text.m_SpriteCalls.end(), std::make_move_iterator(Feedback2.m_SpriteCalls.begin()), std::make_move_iterator(Feedback2.m_SpriteCalls.end())); if (j == (int)string.m_Words.size()-2) done = true; } // Reset X x = BufferZone; // Update dimensions Text.m_Size.cx = std::max(Text.m_Size.cx, line_width + BufferZone * 2); Text.m_Size.cy = std::max(Text.m_Size.cy, y + BufferZone); FirstLine = false; // Now if we entered as from = i, then we want // i being one minus that, so that it will become // the same i in the next loop. The difference is that // we're on a new line now. i = from-1; } } return Text; } void CGUI::DrawText(SGUIText& Text, const CGUIColor& DefaultColor, const CPos& pos, const float& z, const CRect& clipping) { CShaderTechniquePtr tech = g_Renderer.GetShaderManager().LoadEffect(str_gui_text); tech->BeginPass(); bool isClipped = (clipping != CRect()); if (isClipped) { glEnable(GL_SCISSOR_TEST); glScissor( clipping.left * g_GuiScale, g_yres - clipping.bottom * g_GuiScale, clipping.GetWidth() * g_GuiScale, clipping.GetHeight() * g_GuiScale); } CTextRenderer textRenderer(tech->GetShader()); textRenderer.SetClippingRect(clipping); textRenderer.Translate(0.0f, 0.0f, z); for (const SGUIText::STextCall& tc : Text.m_TextCalls) { // If this is just a placeholder for a sprite call, continue if (tc.m_pSpriteCall) continue; CGUIColor color = tc.m_UseCustomColor ? tc.m_Color : DefaultColor; textRenderer.Color(color); textRenderer.Font(tc.m_Font); textRenderer.Put((float)(int)(pos.x + tc.m_Pos.x), (float)(int)(pos.y + tc.m_Pos.y), &tc.m_String); } textRenderer.Render(); for (const SGUIText::SSpriteCall& sc : Text.m_SpriteCalls) DrawSprite(sc.m_Sprite, sc.m_CellID, z, sc.m_Area + pos); if (isClipped) glDisable(GL_SCISSOR_TEST); tech->EndPass(); } bool CGUI::GetPreDefinedColor(const CStr& name, CGUIColor& Output) const { std::map::const_iterator cit = m_PreDefinedColors.find(name); if (cit == m_PreDefinedColors.end()) return false; Output = cit->second; return true; } /** * @callgraph */ void CGUI::LoadXmlFile(const VfsPath& Filename, boost::unordered_set& Paths) { Paths.insert(Filename); CXeromyces XeroFile; if (XeroFile.Load(g_VFS, Filename, "gui") != PSRETURN_OK) return; XMBElement node = XeroFile.GetRoot(); CStr root_name(XeroFile.GetElementString(node.GetNodeName())); try { if (root_name == "objects") { Xeromyces_ReadRootObjects(node, &XeroFile, Paths); // Re-cache all values so these gets cached too. //UpdateResolution(); } else if (root_name == "sprites") Xeromyces_ReadRootSprites(node, &XeroFile); else if (root_name == "styles") Xeromyces_ReadRootStyles(node, &XeroFile); else if (root_name == "setup") Xeromyces_ReadRootSetup(node, &XeroFile); else debug_warn(L"CGUI::LoadXmlFile error"); } catch (PSERROR_GUI& e) { LOGERROR("Errors loading GUI file %s (%u)", Filename.string8(), e.getCode()); return; } } //=================================================================== // XML Reading Xeromyces Specific Sub-Routines //=================================================================== void CGUI::Xeromyces_ReadRootObjects(XMBElement Element, CXeromyces* pFile, boost::unordered_set& Paths) { int el_script = pFile->GetElementID("script"); std::vector > subst; // Iterate main children // they should all be or