From 8c250568e7cb9e422c7cf6799ea586dcb14b4f14 Mon Sep 17 00:00:00 2001 From: trompetin17 Date: Sat, 19 Oct 2024 13:17:30 -0500 Subject: [PATCH] Add scrollpanel widget This PR introduces a new ScrollPanel component with the following capabilities: - Scroll Orientation Support: Allows scrolling in horizontal, vertical, or both directions, providing flexibility for different use cases. - Partial Object Rendering: Supports partial rendering of objects that are only partially visible within the scroll boundaries, improving visual accuracy and performance. - Boundary-Constrained Mouse Interaction: Handles mouse events strictly within the panel's visible boundaries, preventing interaction with objects outside the scrollable area. - Minimum Internal Size (min_width, min_height): Introduces support for virtual space management, allowing the panel to maintain a minimum internal size independent of its actual on-screen dimensions. Even when the panel is resized, this ensures that the content respects a defined virtual space (with min_width and min_height), effectively simulating a larger internal canvas. This is particularly useful for large content or scenarios where a more extensive scrollable area is required than the current visible panel. --- .../modern/scroll-background-horizontal.png | 3 + ...ack.png => scroll-background-vertical.png} | 0 .../data/mods/mod/gui/common/modern/setup.xml | 6 +- .../mods/mod/gui/common/modern/sprites.xml | 28 +- binaries/data/mods/mod/gui/gui.rng | 33 ++- source/gui/CGUI.cpp | 93 +++---- source/gui/CGUI.h | 2 + source/gui/CGUIScrollBarHorizontal.cpp | 225 +++++++++++++++ source/gui/CGUIScrollBarHorizontal.h | 98 +++++++ source/gui/CGUIScrollBarVertical.cpp | 43 ++- source/gui/CGUIScrollBarVertical.h | 2 + source/gui/CGUISetting.cpp | 4 +- source/gui/GUIObjectEventBroadcaster.h | 75 +++++ source/gui/GUIObjectTypes.cpp | 5 +- source/gui/GUIStringConversions.cpp | 18 +- source/gui/IGUIScrollBar.cpp | 31 +-- source/gui/IGUIScrollBar.h | 16 +- source/gui/ObjectBases/IGUIObject.cpp | 55 +++- source/gui/ObjectBases/IGUIObject.h | 17 +- source/gui/ObjectBases/IGUIPanel.cpp | 57 ++++ source/gui/ObjectBases/IGUIPanel.h | 41 +++ source/gui/ObjectBases/IGUIScrollBarOwner.cpp | 5 +- source/gui/ObjectTypes/CButton.cpp | 7 +- source/gui/ObjectTypes/CChart.cpp | 11 +- source/gui/ObjectTypes/CCheckBox.cpp | 5 +- source/gui/ObjectTypes/CDropDown.cpp | 28 +- source/gui/ObjectTypes/CImage.cpp | 4 +- source/gui/ObjectTypes/CInput.cpp | 9 +- source/gui/ObjectTypes/CList.cpp | 3 +- source/gui/ObjectTypes/COList.cpp | 12 +- source/gui/ObjectTypes/CProgressBar.cpp | 6 +- source/gui/ObjectTypes/CScrollPanel.cpp | 257 ++++++++++++++++++ source/gui/ObjectTypes/CScrollPanel.h | 62 +++++ source/gui/ObjectTypes/CSlider.cpp | 8 +- source/gui/ObjectTypes/CText.cpp | 31 +-- source/gui/SGUIMessage.h | 14 +- source/gui/Scripting/GuiScriptConversions.cpp | 46 +++- source/gui/Scripting/JSInterface_GUIProxy.cpp | 10 +- source/gui/SettingTypes/EScrollOrientation.h | 28 ++ source/maths/Rect.cpp | 15 +- source/maths/Rect.h | 8 +- 41 files changed, 1238 insertions(+), 183 deletions(-) create mode 100644 binaries/data/mods/mod/art/textures/ui/global/modern/scroll-background-horizontal.png rename binaries/data/mods/mod/art/textures/ui/global/modern/{scrollback.png => scroll-background-vertical.png} (100%) create mode 100644 source/gui/CGUIScrollBarHorizontal.cpp create mode 100644 source/gui/CGUIScrollBarHorizontal.h create mode 100644 source/gui/GUIObjectEventBroadcaster.h create mode 100644 source/gui/ObjectBases/IGUIPanel.cpp create mode 100644 source/gui/ObjectBases/IGUIPanel.h create mode 100644 source/gui/ObjectTypes/CScrollPanel.cpp create mode 100644 source/gui/ObjectTypes/CScrollPanel.h create mode 100644 source/gui/SettingTypes/EScrollOrientation.h diff --git a/binaries/data/mods/mod/art/textures/ui/global/modern/scroll-background-horizontal.png b/binaries/data/mods/mod/art/textures/ui/global/modern/scroll-background-horizontal.png new file mode 100644 index 0000000000..93fb52a9d2 --- /dev/null +++ b/binaries/data/mods/mod/art/textures/ui/global/modern/scroll-background-horizontal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4f229151ac93b005f69b5752e716c21ee8fd22b84f9e99912e8fc84f5ed4461f +size 680 diff --git a/binaries/data/mods/mod/art/textures/ui/global/modern/scrollback.png b/binaries/data/mods/mod/art/textures/ui/global/modern/scroll-background-vertical.png similarity index 100% rename from binaries/data/mods/mod/art/textures/ui/global/modern/scrollback.png rename to binaries/data/mods/mod/art/textures/ui/global/modern/scroll-background-vertical.png diff --git a/binaries/data/mods/mod/gui/common/modern/setup.xml b/binaries/data/mods/mod/gui/common/modern/setup.xml index 9d208c0c8e..1e2a31fde0 100644 --- a/binaries/data/mods/mod/gui/common/modern/setup.xml +++ b/binaries/data/mods/mod/gui/common/modern/setup.xml @@ -11,8 +11,10 @@ minimum_bar_size = "15" maximum_bar_size = "15" show_edge_buttons = "false" - sprite_back_vertical = "ModernScrollBack" - sprite_bar_vertical = "ModernScrollBar" + sprite_back_vertical = "ModernScrollbarVerticalBackground" + sprite_slider_vertical = "ModernScrollbarVerticalSlider" + sprite_back_horizontal = "ModernScrollbarHorizontalBackground" + sprite_slider_horizontal = "ModernScrollbarHorizontalSlider" /> - + @@ -14,12 +14,34 @@ - - + + + + + + + + + + + + + + + + + + + horizontal + vertical + both + + + + + + + + + @@ -759,17 +774,29 @@ - + - + - + + + + + + + + + + + + + diff --git a/source/gui/CGUI.cpp b/source/gui/CGUI.cpp index 7b5d4b03fa..de3b158ac1 100644 --- a/source/gui/CGUI.cpp +++ b/source/gui/CGUI.cpp @@ -19,6 +19,8 @@ #include "CGUI.h" +#include "GUIObjectEventBroadcaster.h" + #include "graphics/Canvas2D.h" #include "gui/IGUIScrollBar.h" #include "gui/ObjectBases/IGUIObject.h" @@ -27,8 +29,6 @@ #include "gui/Scripting/ScriptFunctions.h" #include "gui/Scripting/JSInterface_GUIProxy.h" #include "i18n/L10n.h" -#include "lib/allocators/DynamicArena.h" -#include "lib/allocators/STLAllocators.h" #include "lib/bits.h" #include "lib/input.h" #include "lib/sysdep/sysdep.h" @@ -48,6 +48,7 @@ #include "scriptinterface/ScriptInterface.h" #include +#include #include #include @@ -63,38 +64,13 @@ const CStr CGUI::EventNameMouseRightPress = "MouseRightPress"; const CStr CGUI::EventNameMouseLeftPress = "MouseLeftPress"; const CStr CGUI::EventNameMouseWheelDown = "MouseWheelDown"; const CStr CGUI::EventNameMouseWheelUp = "MouseWheelUp"; +const CStr CGUI::EventNameMouseWheelLeft = "MouseWheelLeft"; +const CStr CGUI::EventNameMouseWheelRight = "MouseWheelRight"; const CStr CGUI::EventNameMouseLeftDoubleClick = "MouseLeftDoubleClick"; const CStr CGUI::EventNameMouseLeftRelease = "MouseLeftRelease"; const CStr CGUI::EventNameMouseRightDoubleClick = "MouseRightDoubleClick"; const CStr CGUI::EventNameMouseRightRelease = "MouseRightRelease"; -namespace -{ - -struct VisibleObject -{ - IGUIObject* object; - // Index of the object in a depth-first search inside GUI tree. - u32 index; - // Cached value of GetBufferedZ to avoid recursive calls in a deep hierarchy. - float bufferedZ; -}; - -template -void CollectVisibleObjectsRecursively(const std::vector& objects, Container* visibleObjects) -{ - for (IGUIObject* const& object : objects) - { - if (!object->IsHidden()) - { - visibleObjects->emplace_back(VisibleObject{object, static_cast(visibleObjects->size()), 0.0f}); - CollectVisibleObjectsRecursively(object->GetChildren(), visibleObjects); - } - } -} - -} // anonynous namespace - CGUI::CGUI(ScriptContext& context) : m_BaseObject(std::make_unique(*this)), m_FocusedObject(nullptr), @@ -158,7 +134,7 @@ InReaction CGUI::HandleEvent(const SDL_Event_* ev) m_MousePos = CVector2D((float)ev->ev.motion.x / g_VideoMode.GetScale(), (float)ev->ev.motion.y / g_VideoMode.GetScale()); SGUIMessage msg(GUIM_MOUSE_MOTION); - m_BaseObject->RecurseObject(&IGUIObject::IsHiddenOrGhost, &IGUIObject::HandleMessage, msg); + m_BaseObject->RecurseObject(&IGUIObject::IsHiddenOrGhostOrOutOfBoundaries, &IGUIObject::HandleMessage, msg); } // Update m_MouseButtons. (BUTTONUP is handled later.) @@ -196,7 +172,7 @@ InReaction CGUI::HandleEvent(const SDL_Event_* ev) // 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 - m_BaseObject->RecurseObject(&IGUIObject::IsHiddenOrGhost, &IGUIObject::UpdateMouseOver, static_cast(pNearest)); + m_BaseObject->RecurseObject(&IGUIObject::IsHiddenOrGhostOrOutOfBoundaries, &IGUIObject::UpdateMouseOver, static_cast(pNearest)); if (ev->ev.type == SDL_MOUSEBUTTONDOWN) { @@ -225,6 +201,11 @@ InReaction CGUI::HandleEvent(const SDL_Event_* ev) ret = pNearest->SendMouseEvent(GUIM_MOUSE_WHEEL_DOWN, EventNameMouseWheelDown); else if (ev->ev.wheel.y > 0) ret = pNearest->SendMouseEvent(GUIM_MOUSE_WHEEL_UP, EventNameMouseWheelUp); + + if (ev->ev.wheel.x < 0) + ret = pNearest->SendMouseEvent(GUIM_MOUSE_WHEEL_LEFT, EventNameMouseWheelLeft); + else if (ev->ev.wheel.x > 0) + ret = pNearest->SendMouseEvent(GUIM_MOUSE_WHEEL_RIGHT, EventNameMouseWheelRight); } else if (ev->ev.type == SDL_MOUSEBUTTONUP) { @@ -258,7 +239,7 @@ InReaction CGUI::HandleEvent(const SDL_Event_* ev) m_BaseObject->RecurseObject(&IGUIObject::IsHidden, &IGUIObject::ResetStates); // Since the hover state will have been reset, we reload it. - m_BaseObject->RecurseObject(&IGUIObject::IsHiddenOrGhost, &IGUIObject::UpdateMouseOver, static_cast(pNearest)); + m_BaseObject->RecurseObject(&IGUIObject::IsHiddenOrGhostOrOutOfBoundaries, &IGUIObject::UpdateMouseOver, static_cast(pNearest)); } } @@ -298,7 +279,7 @@ InReaction CGUI::HandleEvent(const SDL_Event_* ev) void CGUI::TickObjects() { - m_BaseObject->RecurseObject(&IGUIObject::IsHiddenOrGhost, &IGUIObject::Tick); + m_BaseObject->RecurseObject(&IGUIObject::IsHiddenOrGhostOrOutOfBoundaries, &IGUIObject::Tick); SendEventToAll(EventNameTick); m_Tooltip.Update(FindObjectUnderMouse(), m_MousePos, *this); } @@ -327,32 +308,18 @@ void CGUI::SendEventToAll(const CStr& eventName, const JS::HandleValueArray& par void CGUI::Draw(CCanvas2D& canvas) { - using Arena = Allocators::DynamicArena<128 * KiB>; - using ObjectListAllocator = ProxyAllocator; - Arena arena; - - std::vector visibleObjects((ObjectListAllocator(arena))); - CollectVisibleObjectsRecursively(m_BaseObject->GetChildren(), &visibleObjects); - for (VisibleObject& visibleObject : visibleObjects) - visibleObject.bufferedZ = visibleObject.object->GetBufferedZ(); - - std::sort(visibleObjects.begin(), visibleObjects.end(), [](const VisibleObject& visibleObject1, const VisibleObject& visibleObject2) -> bool { - if (visibleObject1.bufferedZ != visibleObject2.bufferedZ) - return visibleObject1.bufferedZ < visibleObject2.bufferedZ; - return visibleObject1.index < visibleObject2.index; - }); - - for (const VisibleObject& visibleObject : visibleObjects) - visibleObject.object->Draw(canvas); + CGUIObjectEventBroadcaster::RecurseVisibleObject(m_BaseObject.get(), &IGUIObject::Draw, canvas); } -void CGUI::DrawSprite(const CGUISpriteInstance& Sprite, CCanvas2D& canvas, const CRect& Rect, const CRect& UNUSED(Clipping)) +void CGUI::DrawSprite(const CGUISpriteInstance& Sprite, CCanvas2D& canvas, const CRect& Rect, const CRect& Clipping) { // If the sprite doesn't exist (name == ""), don't bother drawing anything if (!Sprite) return; - // TODO: Clipping? + std::optional scopedScissor; + if (Clipping != CRect()) + scopedScissor.emplace(canvas, Clipping); Sprite.Draw(*this, canvas, Rect, m_Sprites); } @@ -414,7 +381,7 @@ IGUIObject* CGUI::FindObjectByName(const CStr& Name) const IGUIObject* CGUI::FindObjectUnderMouse() { IGUIObject* pNearest = nullptr; - m_BaseObject->RecurseObject(&IGUIObject::IsHiddenOrGhost, &IGUIObject::ChooseMouseOverAndClosest, pNearest); + m_BaseObject->RecurseObject(&IGUIObject::IsHiddenOrGhostOrOutOfBoundaries, &IGUIObject::ChooseMouseOverAndClosest, pNearest); return pNearest; } @@ -1237,12 +1204,20 @@ void CGUI::Xeromyces_ReadScrollBarStyle(const XMBData& xmb, XMBElement element) scrollbar.m_SpriteButtonBottomOver = attr_value; else if (attr_name == "sprite_back_vertical") scrollbar.m_SpriteBackVertical = attr_value; - else if (attr_name == "sprite_bar_vertical") - scrollbar.m_SpriteBarVertical = attr_value; - else if (attr_name == "sprite_bar_vertical_over") - scrollbar.m_SpriteBarVerticalOver = attr_value; - else if (attr_name == "sprite_bar_vertical_pressed") - scrollbar.m_SpriteBarVerticalPressed = attr_value; + else if (attr_name == "sprite_slider_vertical") + scrollbar.m_SpriteSliderVertical = attr_value; + else if (attr_name == "sprite_slider_vertical_over") + scrollbar.m_SpriteSliderVerticalOver = attr_value; + else if (attr_name == "sprite_slider_vertical_pressed") + scrollbar.m_SpriteSliderVerticalPressed = attr_value; + else if (attr_name == "sprite_back_horizontal") + scrollbar.m_SpriteBackHorizontal = attr_value; + else if (attr_name == "sprite_slider_horizontal") + scrollbar.m_SpriteSliderHorizontal = attr_value; + else if (attr_name == "sprite_slider_horizontal_over") + scrollbar.m_SpriteSliderHorizontalOver = attr_value; + else if (attr_name == "sprite_slider_horizontal_pressed") + scrollbar.m_SpriteSliderHorizontalPressed = attr_value; } m_ScrollBarStyles.erase(name); diff --git a/source/gui/CGUI.h b/source/gui/CGUI.h index dd2d39eaf9..e4e7290970 100644 --- a/source/gui/CGUI.h +++ b/source/gui/CGUI.h @@ -662,6 +662,8 @@ private: static const CStr EventNameMouseLeftPress; static const CStr EventNameMouseWheelDown; static const CStr EventNameMouseWheelUp; + static const CStr EventNameMouseWheelLeft; + static const CStr EventNameMouseWheelRight; static const CStr EventNameMouseLeftDoubleClick; static const CStr EventNameMouseLeftRelease; static const CStr EventNameMouseRightDoubleClick; diff --git a/source/gui/CGUIScrollBarHorizontal.cpp b/source/gui/CGUIScrollBarHorizontal.cpp new file mode 100644 index 0000000000..9bf4989502 --- /dev/null +++ b/source/gui/CGUIScrollBarHorizontal.cpp @@ -0,0 +1,225 @@ +/* Copyright (C) 2024 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 "CGUIScrollBarHorizontal.h" + +#include "gui/CGUI.h" +#include "ps/CLogger.h" + +CGUIScrollBarHorizontal::CGUIScrollBarHorizontal(CGUI& pGUI) + : IGUIScrollBar(pGUI) +{ +} + +CGUIScrollBarHorizontal::~CGUIScrollBarHorizontal() +{ +} + +void CGUIScrollBarHorizontal::SetPosFromMousePos(const CVector2D& mouse) +{ + if (!GetStyle()) + return; + + // Calculate the position for the top of the item being scrolled + float emptyBackground = m_Length - m_BarSize; + + if (GetStyle()->m_UseEdgeButtons) + emptyBackground -= GetStyle()->m_Width * 2; + + m_Pos = m_PosWhenPressed + GetMaxPos() * (mouse.X - m_BarPressedAtPos.X) / emptyBackground; +} + +void CGUIScrollBarHorizontal::Draw(CCanvas2D& canvas) +{ + if (!IsVisible()) + return; + + if (!GetStyle()) + { + LOGWARNING("Attempt to draw scrollbar without a style."); + return; + } + + CRect outline = GetOuterRect(); + + m_pGUI.DrawSprite( + GetStyle()->m_SpriteBackHorizontal, + canvas, + CRect( + outline.left + (GetStyle()->m_UseEdgeButtons ? GetStyle()->m_Width : 0), + outline.top, + outline.right - (GetStyle()->m_UseEdgeButtons ? GetStyle()->m_Width : 0), + outline.bottom)); + + if (GetStyle()->m_UseEdgeButtons) + { + const CGUISpriteInstance* button_left; + const CGUISpriteInstance* button_right; + + if (m_ButtonMinusHovered) + { + if (m_ButtonMinusPressed) + button_left = &(GetStyle()->m_SpriteButtonLeftPressed ? GetStyle()->m_SpriteButtonLeftPressed : GetStyle()->m_SpriteButtonLeft); + else + button_left = &(GetStyle()->m_SpriteButtonLeftOver ? GetStyle()->m_SpriteButtonLeftOver : GetStyle()->m_SpriteButtonLeft); + } + else + button_left = &GetStyle()->m_SpriteButtonLeft; + + if (m_ButtonPlusHovered) + { + if (m_ButtonPlusPressed) + button_right = &(GetStyle()->m_SpriteButtonRightPressed ? GetStyle()->m_SpriteButtonRightPressed : GetStyle()->m_SpriteButtonRight); + else + button_right = &(GetStyle()->m_SpriteButtonRightOver ? GetStyle()->m_SpriteButtonRightOver : GetStyle()->m_SpriteButtonRight); + } + else + button_right = &GetStyle()->m_SpriteButtonRight; + + m_pGUI.DrawSprite( + *button_left, + canvas, + CRect( + outline.left, + outline.top, + outline.left + GetStyle()->m_Width, + outline.bottom)); + + m_pGUI.DrawSprite( + *button_right, + canvas, + CRect( + outline.right - GetStyle()->m_Width, + outline.top, + outline.right, + outline.bottom)); + } + + m_pGUI.DrawSprite( + GetStyle()->m_SpriteSliderHorizontal, + canvas, + GetBarRect() + ); +} + +void CGUIScrollBarHorizontal::HandleMessage(SGUIMessage& Message) +{ + switch (Message.type) + { + case GUIM_MOUSE_WHEEL_LEFT: + { + 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); + Message.Skip(false); + break; + } + + case GUIM_MOUSE_WHEEL_RIGHT: + { + 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); + Message.Skip(false); + break; + } + + default: + IGUIScrollBar::HandleMessage(Message); + break; + } +} + +CRect CGUIScrollBarHorizontal::GetBarRect() const +{ + CRect ret; + if (!GetStyle()) + return ret; + + // Get from where the scroll area begins to where it ends + float from = m_X; + float to = m_X + m_Length - m_BarSize; + + if (GetStyle()->m_UseEdgeButtons) + { + from += GetStyle()->m_Width; + to -= GetStyle()->m_Width; + } + + ret.left = from + (to - from) * m_Pos / GetMaxPos(); + ret.right = ret.left + m_BarSize; + ret.bottom = m_Y + (m_BottomAligned ? 0 : GetStyle()->m_Width); + ret.top = ret.bottom - GetStyle()->m_Width; + + return ret; +} + +CRect CGUIScrollBarHorizontal::GetOuterRect() const +{ + CRect ret; + if (!GetStyle()) + return ret; + + ret.left = m_X; + ret.right = m_X + m_Length; + ret.bottom = m_Y + (m_BottomAligned ? 0 : GetStyle()->m_Width); + ret.top = ret.bottom - GetStyle()->m_Width; + + return ret; +} + +bool CGUIScrollBarHorizontal::HoveringButtonMinus(const CVector2D& mouse) +{ + if (!GetStyle()) + return false; + + float StartY = m_BottomAligned ? m_Y - GetStyle()->m_Width : m_Y; + + return mouse.Y >= StartY && + mouse.Y <= StartY + GetStyle()->m_Width && + mouse.X >= m_X && + mouse.X <= m_X + GetStyle()->m_Width; +} + +bool CGUIScrollBarHorizontal::HoveringButtonPlus(const CVector2D& mouse) +{ + if (!GetStyle()) + return false; + + float StartY = m_BottomAligned ? m_Y - GetStyle()->m_Width : m_Y; + + return mouse.Y > StartY && + mouse.Y < StartY + GetStyle()->m_Width && + mouse.X > m_X + m_Length - GetStyle()->m_Width && + mouse.X < m_X + m_Length; +} + +void CGUIScrollBarHorizontal::SetScrollPlentyFromMousePos(const CVector2D& mouse) +{ + // Scroll plus or minus a lot, this might change, it doesn't + // have to be fancy though. + if (mouse.X < GetBarRect().left) + ScrollMinusPlenty(); + else + ScrollPlusPlenty(); +} diff --git a/source/gui/CGUIScrollBarHorizontal.h b/source/gui/CGUIScrollBarHorizontal.h new file mode 100644 index 0000000000..0921b4a8fc --- /dev/null +++ b/source/gui/CGUIScrollBarHorizontal.h @@ -0,0 +1,98 @@ +/* Copyright (C) 2024 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, this class doesn't present all functionality + to the scrollbar, it just controls the drawing and a wrapper + for interaction with it. + */ + +#ifndef INCLUDED_CGUISCROLLBARHORIZONTAL +#define INCLUDED_CGUISCROLLBARHORIZONTAL + +#include "IGUIScrollBar.h" + + /** + * Horizontal implementation of IGUIScrollBar + * + * @see IGUIScrollBar + */ +class CGUIScrollBarHorizontal : public IGUIScrollBar +{ +public: + CGUIScrollBarHorizontal(CGUI& pGUI); + virtual ~CGUIScrollBarHorizontal(); + + /** + * Draw the scroll-bar + */ + virtual void Draw(CCanvas2D& canvas); + + /** + * 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); + + /** + * Set m_Pos with g_mouse_x/y input, i.e. when dragging. + */ + virtual void SetPosFromMousePos(const CVector2D& mouse); + + virtual void SetScrollPlentyFromMousePos(const CVector2D& mouse); + + /** + * @see IGUIScrollBar#HoveringButtonMinus + */ + virtual bool HoveringButtonMinus(const CVector2D& mouse); + + /** + * @see IGUIScrollBar#HoveringButtonPlus + */ + virtual bool HoveringButtonPlus(const CVector2D& mouse); + + /** + * Set Right Aligned + * @param align Alignment + */ + void SetBottomAligned(const bool& align) { m_BottomAligned = align; } + + /** + * Get the rectangle of the actual BAR. + * @return Rectangle, CRect + */ + virtual CRect GetBarRect() const; + + /** + * 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; + +protected: + /** + * Should the scroll bar proceed to the top or to the bottom of the m_Y value. + * Notice, this has nothing to do with where the owner places it. + */ + bool m_BottomAligned; +}; + +#endif // INCLUDED_CGUISCROLLBARHORIZONTAL diff --git a/source/gui/CGUIScrollBarVertical.cpp b/source/gui/CGUIScrollBarVertical.cpp index 2a3ac5e93d..c562420f10 100644 --- a/source/gui/CGUIScrollBarVertical.cpp +++ b/source/gui/CGUIScrollBarVertical.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2021 Wildfire Games. +/* Copyright (C) 2024 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -119,7 +119,7 @@ void CGUIScrollBarVertical::Draw(CCanvas2D& canvas) } m_pGUI.DrawSprite( - GetStyle()->m_SpriteBarVertical, + GetStyle()->m_SpriteSliderVertical, canvas, GetBarRect() ); @@ -128,7 +128,34 @@ void CGUIScrollBarVertical::Draw(CCanvas2D& canvas) void CGUIScrollBarVertical::HandleMessage(SGUIMessage& Message) { - IGUIScrollBar::HandleMessage(Message); + switch (Message.type) + { + 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); + Message.Skip(false); + 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); + Message.Skip(false); + break; + } + + default: + IGUIScrollBar::HandleMessage(Message); + break; + } } CRect CGUIScrollBarVertical::GetBarRect() const @@ -194,3 +221,13 @@ bool CGUIScrollBarVertical::HoveringButtonPlus(const CVector2D& mouse) mouse.Y > m_Y + m_Length - GetStyle()->m_Width && mouse.Y < m_Y + m_Length; } + +void CGUIScrollBarVertical::SetScrollPlentyFromMousePos(const CVector2D& 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(); +} diff --git a/source/gui/CGUIScrollBarVertical.h b/source/gui/CGUIScrollBarVertical.h index 6926a6fd7b..ad8647362e 100644 --- a/source/gui/CGUIScrollBarVertical.h +++ b/source/gui/CGUIScrollBarVertical.h @@ -57,6 +57,8 @@ public: */ virtual void SetPosFromMousePos(const CVector2D& mouse); + virtual void SetScrollPlentyFromMousePos(const CVector2D& mouse); + /** * @see IGUIScrollBar#HoveringButtonMinus */ diff --git a/source/gui/CGUISetting.cpp b/source/gui/CGUISetting.cpp index 04be7b8eac..e1ff781ff0 100644 --- a/source/gui/CGUISetting.cpp +++ b/source/gui/CGUISetting.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2023 Wildfire Games. +/* Copyright (C) 2024 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -27,6 +27,7 @@ #include "gui/SettingTypes/CGUISize.h" #include "gui/SettingTypes/CGUIString.h" #include "gui/SettingTypes/EAlign.h" +#include "gui/SettingTypes/EScrollOrientation.h" #include "ps/CLogger.h" #include "ps/CStr.h" #include "scriptinterface/ScriptConversions.h" @@ -126,5 +127,6 @@ TYPE(EAlign) TYPE(EVAlign) TYPE(CGUIList) TYPE(CGUISeries) +TYPE(EScrollOrientation) #undef TYPE diff --git a/source/gui/GUIObjectEventBroadcaster.h b/source/gui/GUIObjectEventBroadcaster.h new file mode 100644 index 0000000000..9e9cc8f88b --- /dev/null +++ b/source/gui/GUIObjectEventBroadcaster.h @@ -0,0 +1,75 @@ +/* Copyright (C) 2024 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" + +#ifndef GUIOBJECTEVENTBROADCASTER +#define GUIOBJECTEVENTBROADCASTER + +#include "gui/ObjectBases/IGUIObject.h" +#include "gui/ObjectBases/IGUIPanel.h" + +namespace +{ + struct VisibleObject + { + IGUIObject* object; + // Index of the object in a depth-first search inside GUI tree. + u32 index; + // Cached value of GetBufferedZ to avoid recursive calls in a deep hierarchy. + float bufferedZ; + }; + + template + void CollectVisibleObjectsRecursively(const std::vector& objects, Container* visibleObjects) + { + for (IGUIObject* const& object : objects) + if (!object->IsHidden()) + { + visibleObjects->emplace_back(VisibleObject{ object, static_cast(visibleObjects->size()), 0.0f }); + CollectVisibleObjectsRecursively(object->GetVisibleChildren(), visibleObjects); + } + } +} + +class CGUIObjectEventBroadcaster +{ +public: + template + static void RecurseVisibleObject(IGUIObject* object, void(IGUIObject::* callbackFunction)(Args... args), Args&&... args) + { + using Arena = Allocators::DynamicArena<128 * KiB>; + using ObjectListAllocator = ProxyAllocator; + Arena arena; + + std::vector visibleObjects((ObjectListAllocator(arena))); + CollectVisibleObjectsRecursively(object->GetVisibleChildren(), &visibleObjects); + for (VisibleObject& visibleObject : visibleObjects) + visibleObject.bufferedZ = visibleObject.object->GetBufferedZ(); + + std::sort(visibleObjects.begin(), visibleObjects.end(), [](const VisibleObject& visibleObject1, const VisibleObject& visibleObject2) -> bool { + if (visibleObject1.bufferedZ != visibleObject2.bufferedZ) + return visibleObject1.bufferedZ < visibleObject2.bufferedZ; + return visibleObject1.index < visibleObject2.index; + }); + + for (const VisibleObject& visibleObject : visibleObjects) + (visibleObject.object->*callbackFunction)(std::forward(args)...); + } +}; + +#endif // !GUIOBJECTEVENTBROADCASTER diff --git a/source/gui/GUIObjectTypes.cpp b/source/gui/GUIObjectTypes.cpp index 54b52dee72..ddd4d4b7c1 100644 --- a/source/gui/GUIObjectTypes.cpp +++ b/source/gui/GUIObjectTypes.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2020 Wildfire Games. +/* Copyright (C) 2024 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -31,6 +31,7 @@ #include "gui/ObjectTypes/CProgressBar.h" #include "gui/ObjectTypes/CRadioButton.h" #include "gui/ObjectTypes/CSlider.h" +#include "gui/ObjectTypes/CScrollPanel.h" #include "gui/ObjectTypes/CText.h" #include "gui/ObjectTypes/CTooltip.h" #include "gui/Scripting/JSInterface_GUIProxy.h" @@ -42,6 +43,7 @@ void CGUI::AddObjectTypes() m_ProxyData.insert(JSI_GUIProxy::CreateData(*m_ScriptInterface)); m_ProxyData.insert(JSI_GUIProxy::CreateData(*m_ScriptInterface)); m_ProxyData.insert(JSI_GUIProxy::CreateData(*m_ScriptInterface)); + m_ProxyData.insert(JSI_GUIProxy::CreateData(*m_ScriptInterface)); AddObjectType("button", &CButton::ConstructObject); AddObjectType("chart", &CChart::ConstructObject); @@ -59,4 +61,5 @@ void CGUI::AddObjectTypes() AddObjectType("slider", &CSlider::ConstructObject); AddObjectType("text", &CText::ConstructObject); AddObjectType("tooltip", &CTooltip::ConstructObject); + AddObjectType("scrollpanel", &CScrollPanel::ConstructObject); } diff --git a/source/gui/GUIStringConversions.cpp b/source/gui/GUIStringConversions.cpp index 827fe1c6b7..eb9b9f3e68 100644 --- a/source/gui/GUIStringConversions.cpp +++ b/source/gui/GUIStringConversions.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2021 Wildfire Games. +/* Copyright (C) 2024 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -18,6 +18,7 @@ #include "precompiled.h" #include "gui/CGUI.h" +#include "gui/SettingTypes/EScrollOrientation.h" #include "gui/SettingTypes/CGUIString.h" #include "ps/CLogger.h" @@ -244,3 +245,18 @@ bool CGUI::ParseString(const CGUI* UNUSED(pGUI), const CStrW& UNUSED(V { return false; } + +template <> +bool CGUI::ParseString(const CGUI* UNUSED(pGUI), const CStrW& Value, EScrollOrientation& Output) +{ + if (Value == L"vertical") + Output = EScrollOrientation::VERTICAL; + else if (Value == L"horizontal") + Output = EScrollOrientation::HORIZONTAL; + else if (Value == L"both") + Output = EScrollOrientation::BOTH; + else + return false; + + return true; +} diff --git a/source/gui/IGUIScrollBar.cpp b/source/gui/IGUIScrollBar.cpp index 30c97b288a..f287ffe9be 100644 --- a/source/gui/IGUIScrollBar.cpp +++ b/source/gui/IGUIScrollBar.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2021 Wildfire Games. +/* Copyright (C) 2024 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -147,12 +147,8 @@ void IGUIScrollBar::HandleMessage(SGUIMessage& Message) { 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(); + SetScrollPlentyFromMousePos(mouse); + // Simulate mouse movement to see if bar now is hovered SGUIMessage msg(GUIM_MOUSE_MOTION); HandleMessage(msg); @@ -165,27 +161,6 @@ void IGUIScrollBar::HandleMessage(SGUIMessage& Message) 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; } diff --git a/source/gui/IGUIScrollBar.h b/source/gui/IGUIScrollBar.h index b4eb218ab5..e8ecf134aa 100644 --- a/source/gui/IGUIScrollBar.h +++ b/source/gui/IGUIScrollBar.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2021 Wildfire Games. +/* Copyright (C) 2024 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -111,9 +111,9 @@ struct SGUIScrollBarStyle CGUISpriteInstance m_SpriteButtonBottomDisabled; CGUISpriteInstance m_SpriteButtonBottomOver; - CGUISpriteInstance m_SpriteBarVertical; - CGUISpriteInstance m_SpriteBarVerticalOver; - CGUISpriteInstance m_SpriteBarVerticalPressed; + CGUISpriteInstance m_SpriteSliderVertical; + CGUISpriteInstance m_SpriteSliderVerticalOver; + CGUISpriteInstance m_SpriteSliderVerticalPressed; CGUISpriteInstance m_SpriteBackVertical; @@ -126,13 +126,17 @@ struct SGUIScrollBarStyle CGUISpriteInstance m_SpriteButtonLeft; CGUISpriteInstance m_SpriteButtonLeftPressed; CGUISpriteInstance m_SpriteButtonLeftDisabled; + CGUISpriteInstance m_SpriteButtonLeftOver; CGUISpriteInstance m_SpriteButtonRight; CGUISpriteInstance m_SpriteButtonRightPressed; CGUISpriteInstance m_SpriteButtonRightDisabled; + CGUISpriteInstance m_SpriteButtonRightOver; CGUISpriteInstance m_SpriteBackHorizontal; - CGUISpriteInstance m_SpriteBarHorizontal; + CGUISpriteInstance m_SpriteSliderHorizontal; + CGUISpriteInstance m_SpriteSliderHorizontalOver; + CGUISpriteInstance m_SpriteSliderHorizontalPressed; //@} }; @@ -179,6 +183,8 @@ public: */ virtual void SetPosFromMousePos(const CVector2D& mouse) = 0; + virtual void SetScrollPlentyFromMousePos(const CVector2D& mouse) = 0; + /** * Hovering the scroll minus button * diff --git a/source/gui/ObjectBases/IGUIObject.cpp b/source/gui/ObjectBases/IGUIObject.cpp index 0b3658ff93..0c1e547fac 100644 --- a/source/gui/ObjectBases/IGUIObject.cpp +++ b/source/gui/ObjectBases/IGUIObject.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2022 Wildfire Games. +/* Copyright (C) 2024 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -36,6 +36,7 @@ #include #include + const CStr IGUIObject::EventNameMouseEnter = "MouseEnter"; const CStr IGUIObject::EventNameMouseMove = "MouseMove"; const CStr IGUIObject::EventNameMouseLeave = "MouseLeave"; @@ -135,11 +136,34 @@ void IGUIObject::SettingChanged(const CStr& Setting, const bool SendMessage) { SGUIMessage msg(GUIM_SETTINGS_UPDATED, Setting); HandleMessage(msg); + + // inform to parents until get to the root object + // for now only size and hidden settings are bubbled up + if (GetParent() && (Setting == "size" || Setting == "hidden")) + { + EGUIMessageType type = Setting == "size" ? GUIM_CHILD_RESIZED : GUIM_CHILD_TOGGLE_VISIBILITY; + SGUIMessage msg(type, GetName()); + msg.Skip(true); + + IGUIObject* parent = GetParent(); + while (parent) + { + parent->HandleMessage(msg); + + if (!msg.skipped) + break; + + parent = parent->GetParent(); + } + } } } bool IGUIObject::IsMouseOver() const { + if (m_VisibleArea) + return m_VisibleArea.PointInside(m_pGUI.GetMousePos()); + return m_CachedActualSize.PointInside(m_pGUI.GetMousePos()); } @@ -236,7 +260,6 @@ CRect IGUIObject::GetComputedSize() return m_CachedActualSize; } - bool IGUIObject::ApplyStyle(const CStr& StyleName) { if (!m_pGUI.HasStyle(StyleName)) @@ -362,6 +385,8 @@ InReaction IGUIObject::SendMouseEvent(EGUIMessageType type, const CStr& eventNam PROFILE2_ATTR("object: %s", m_Name.c_str()); SGUIMessage msg(type); + if (type == GUIM_MOUSE_WHEEL_UP || type == GUIM_MOUSE_WHEEL_DOWN || type == GUIM_MOUSE_WHEEL_LEFT || type == GUIM_MOUSE_WHEEL_RIGHT) + msg.Skip(true); HandleMessage(msg); ScriptRequest rq(m_pGUI.GetScriptInterface()); @@ -381,6 +406,11 @@ InReaction IGUIObject::SendMouseEvent(EGUIMessageType type, const CStr& eventNam ignore_result(paramData.append(mouse)); ScriptEvent(eventName, paramData); + // inform to parents until get to the root object + // for now only wheel events are bubbled up + if (GetParent() && (type == GUIM_MOUSE_WHEEL_UP || type == GUIM_MOUSE_WHEEL_DOWN || type == GUIM_MOUSE_WHEEL_LEFT || type == GUIM_MOUSE_WHEEL_RIGHT) && msg.skipped) + msg.Skip(GetParent()->SendMouseEvent(type, eventName) == IN_PASS); + return msg.skipped ? IN_PASS : IN_HANDLED; } @@ -500,3 +530,24 @@ void IGUIObject::TraceMember(JSTracer* trc) for (std::pair>& handler : m_ScriptHandlers) JS::TraceEdge(trc, &handler.second, "IGUIObject::m_ScriptHandlers"); } + +void IGUIObject::DrawInArea(CCanvas2D& canvas, CRect& area) +{ + bool isInsideBoundaries = false; + RecurseObject(nullptr, &IGUIObject::SetIsInsideBoundaries, isInsideBoundaries); + if (!area.IntersectWith(m_CachedActualSize)) + return; + + CRect intersection = area.Intersection(m_CachedActualSize); + + m_VisibleArea = intersection; + + Draw(canvas); + + isInsideBoundaries = true; + RecurseObject(nullptr, &IGUIObject::SetIsInsideBoundaries, isInsideBoundaries); +} + +bool IGUIObject::IsHiddenOrGhostOrOutOfBoundaries() const { + return !m_IsInsideBoundaries || IsHiddenOrGhost(); +} diff --git a/source/gui/ObjectBases/IGUIObject.h b/source/gui/ObjectBases/IGUIObject.h index 6c47dfc4d3..380b858bbd 100644 --- a/source/gui/ObjectBases/IGUIObject.h +++ b/source/gui/ObjectBases/IGUIObject.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2021 Wildfire Games. +/* Copyright (C) 2024 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -29,6 +29,8 @@ #include "gui/SettingTypes/CGUIHotkey.h" #include "gui/SettingTypes/CGUISize.h" #include "gui/SGUIMessage.h" +#include "lib/allocators/DynamicArena.h" +#include "lib/allocators/STLAllocators.h" #include "lib/input.h" // just for IN_PASS #include "ps/CStr.h" #include "ps/XML/Xeromyces.h" @@ -40,6 +42,7 @@ class CCanvas2D; class CGUI; class CGUISize; +class CGUIObjectEventBroadcaster; class IGUIObject; class IGUIProxyObject; class IGUISetting; @@ -59,6 +62,7 @@ public: \ class IGUIObject { friend class CGUI; + friend class CGUIObjectEventBroadcaster; // For triggering message update handlers. friend class IGUISetting; @@ -189,7 +193,7 @@ public: /** * Updates and returns the size of the object. */ - CRect GetComputedSize(); + virtual CRect GetComputedSize(); virtual const CStrW& GetTooltipText() const { return m_Tooltip; } virtual const CStr& GetTooltipStyle() const { return m_TooltipStyle; } @@ -213,6 +217,10 @@ public: */ JSObject* GetJSObject(); + virtual const std::vector& GetVisibleChildren() const { return m_Children; } + void SetIsInsideBoundaries(bool& isInsideBoundaries) { m_IsInsideBoundaries = isInsideBoundaries; } + bool IsHiddenOrGhostOrOutOfBoundaries() const; + //@} protected: //-------------------------------------------------------- @@ -261,6 +269,8 @@ public: obj->RecurseObject(isRestricted, callbackFunction, args...); } + virtual void DrawInArea(CCanvas2D& canvas, CRect& area); + protected: /** * Draws the object. @@ -525,6 +535,9 @@ protected: // Cached JSObject representing this GUI object. std::unique_ptr m_JSObject; + CRect m_VisibleArea; + bool m_IsInsideBoundaries = true; + CGUISimpleSetting m_Enabled; CGUISimpleSetting m_Hidden; CGUISimpleSetting m_Size; diff --git a/source/gui/ObjectBases/IGUIPanel.cpp b/source/gui/ObjectBases/IGUIPanel.cpp new file mode 100644 index 0000000000..3ab79c64b7 --- /dev/null +++ b/source/gui/ObjectBases/IGUIPanel.cpp @@ -0,0 +1,57 @@ +/* Copyright (C) 20244 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 "IGUIPanel.h" + +#include "gui/CGUI.h" + +IGUIPanel::IGUIPanel(CGUI& pGUI) + : IGUIObject(pGUI) +{ +} + +IGUIPanel::~IGUIPanel() +{ +} + +bool IGUIPanel::IsMouseOver() const +{ + return m_CachedLayoutActualSize.PointInside(m_pGUI.GetMousePos()); +} + +void IGUIPanel::UpdateCachedSize() +{ + IGUIObject::UpdateCachedSize(); + m_CachedLayoutActualSize = m_CachedActualSize; +} + +CRect IGUIPanel::GetComputedSize() +{ + UpdateCachedSize(); + return m_CachedLayoutActualSize; +} + +const std::vector& IGUIPanel::GetVisibleChildren() const +{ + if (m_Drawing) + return m_Children; + + static std::vector emptyVector; + return emptyVector; +} diff --git a/source/gui/ObjectBases/IGUIPanel.h b/source/gui/ObjectBases/IGUIPanel.h new file mode 100644 index 0000000000..d5e84225d1 --- /dev/null +++ b/source/gui/ObjectBases/IGUIPanel.h @@ -0,0 +1,41 @@ +/* Copyright (C) 20244 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_IPANEL +#define INCLUDED_IPANEL + +#include "gui/ObjectBases/IGUIObject.h" + +class IGUIPanel : public IGUIObject +{ +public: + IGUIPanel(CGUI& pGUI); + virtual ~IGUIPanel(); + + virtual void UpdateCachedSize(); + virtual CRect GetComputedSize(); + + virtual bool IsMouseOver() const; + + virtual const std::vector& GetVisibleChildren() const; + +protected: + CRect m_CachedLayoutActualSize; + bool m_Drawing = false; +}; + +#endif // INCLUDED_IPANEL diff --git a/source/gui/ObjectBases/IGUIScrollBarOwner.cpp b/source/gui/ObjectBases/IGUIScrollBarOwner.cpp index 46d15dc79f..84b4523106 100644 --- a/source/gui/ObjectBases/IGUIScrollBarOwner.cpp +++ b/source/gui/ObjectBases/IGUIScrollBarOwner.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2021 Wildfire Games. +/* Copyright (C) 2024 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -50,7 +50,8 @@ const SGUIScrollBarStyle* IGUIScrollBarOwner::GetScrollBarStyle(const CStr& styl void IGUIScrollBarOwner::HandleMessage(SGUIMessage& msg) { for (const std::unique_ptr& scrollBar : m_ScrollBars) - scrollBar->HandleMessage(msg); + if (scrollBar->IsVisible()) + scrollBar->HandleMessage(msg); } void IGUIScrollBarOwner::Draw(CCanvas2D& canvas) diff --git a/source/gui/ObjectTypes/CButton.cpp b/source/gui/ObjectTypes/CButton.cpp index 8826c4d820..d401f9d540 100644 --- a/source/gui/ObjectTypes/CButton.cpp +++ b/source/gui/ObjectTypes/CButton.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2021 Wildfire Games. +/* Copyright (C) 2024 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -85,9 +85,10 @@ void CButton::Draw(CCanvas2D& canvas) m_pGUI.DrawSprite( GetButtonSprite(m_Sprite, m_SpriteOver, m_SpritePressed, m_SpriteDisabled), canvas, - m_CachedActualSize); + m_CachedActualSize, + m_VisibleArea); - DrawText(canvas, 0, ChooseColor(), m_TextPos); + DrawText(canvas, 0, ChooseColor(), m_TextPos, m_VisibleArea); } bool CButton::IsMouseOver() const diff --git a/source/gui/ObjectTypes/CChart.cpp b/source/gui/ObjectTypes/CChart.cpp index b97352888e..7cef684a4d 100644 --- a/source/gui/ObjectTypes/CChart.cpp +++ b/source/gui/ObjectTypes/CChart.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2022 Wildfire Games. +/* Copyright (C) 2024 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -28,6 +28,7 @@ #include "ps/Profile.h" #include +#include CChart::CChart(CGUI& pGUI) : IGUIObject(pGUI), @@ -80,6 +81,10 @@ void CChart::Draw(CCanvas2D& canvas) if (m_Series.empty()) return; + std::optional scissor; + if (m_VisibleArea) + scissor.emplace(canvas, m_VisibleArea); + CRect rect = GetChartRect(); const float width = rect.GetWidth(); const float height = rect.GetHeight(); @@ -90,7 +95,7 @@ void CChart::Draw(CCanvas2D& canvas) { if (data.m_Points.empty()) continue; - + linePoints.clear(); for (const CVector2D& point : data.m_Points) { @@ -114,7 +119,7 @@ void CChart::Draw(CCanvas2D& canvas) DrawAxes(canvas); for (size_t i = 0; i < m_TextPositions.size(); ++i) - DrawText(canvas, i, CGUIColor(1.f, 1.f, 1.f, 1.f), m_TextPositions[i]); + DrawText(canvas, i, CGUIColor(1.f, 1.f, 1.f, 1.f), m_TextPositions[i], m_VisibleArea); } CRect CChart::GetChartRect() const diff --git a/source/gui/ObjectTypes/CCheckBox.cpp b/source/gui/ObjectTypes/CCheckBox.cpp index 55727c1f34..bbb31e596d 100644 --- a/source/gui/ObjectTypes/CCheckBox.cpp +++ b/source/gui/ObjectTypes/CCheckBox.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2021 Wildfire Games. +/* Copyright (C) 2024 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -71,5 +71,6 @@ void CCheckBox::Draw(CCanvas2D& canvas) GetButtonSprite(m_SpriteChecked, m_SpriteCheckedOver, m_SpriteCheckedPressed, m_SpriteCheckedDisabled) : GetButtonSprite(m_SpriteUnchecked, m_SpriteUncheckedOver, m_SpriteUncheckedPressed, m_SpriteUncheckedDisabled), canvas, - m_CachedActualSize); + m_CachedActualSize, + m_VisibleArea); } diff --git a/source/gui/ObjectTypes/CDropDown.cpp b/source/gui/ObjectTypes/CDropDown.cpp index fae51284a5..2f52578e9c 100644 --- a/source/gui/ObjectTypes/CDropDown.cpp +++ b/source/gui/ObjectTypes/CDropDown.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2023 Wildfire Games. +/* Copyright (C) 2024 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -164,6 +164,9 @@ void CDropDown::HandleMessage(SGUIMessage& Message) if (m_List->m_Items.empty()) return; + if (m_VisibleArea && !m_VisibleArea.PointInside(m_pGUI.GetMousePos())) + return; + m_Open = true; GetScrollBar(0).SetZ(GetBufferedZ()); m_ElementHighlight = m_Selected; @@ -208,6 +211,8 @@ void CDropDown::HandleMessage(SGUIMessage& Message) if (m_Open || !m_Enabled) break; + Message.Skip(false); + m_ElementHighlight = m_Selected; if (m_ElementHighlight + 1 >= (int)m_ItemsYPositions.size() - 1) @@ -224,6 +229,8 @@ void CDropDown::HandleMessage(SGUIMessage& Message) if (m_Open || !m_Enabled) break; + Message.Skip(false); + m_ElementHighlight = m_Selected; if (m_ElementHighlight - 1 < 0) break; @@ -420,7 +427,7 @@ bool CDropDown::IsMouseOver() const return rect.PointInside(m_pGUI.GetMousePos()); } else - return m_CachedActualSize.PointInside(m_pGUI.GetMousePos()); + return IGUIObject::IsMouseOver(); } void CDropDown::Draw(CCanvas2D& canvas) @@ -428,7 +435,7 @@ void CDropDown::Draw(CCanvas2D& canvas) const CGUISpriteInstance& sprite = m_Enabled ? m_Sprite : m_SpriteDisabled; const CGUISpriteInstance& spriteOverlay = m_Enabled ? m_SpriteOverlay : m_SpriteOverlayDisabled; - m_pGUI.DrawSprite(sprite, canvas, m_CachedActualSize); + m_pGUI.DrawSprite(sprite, canvas, m_CachedActualSize, m_VisibleArea); if (m_ButtonWidth > 0.f) { @@ -437,24 +444,25 @@ void CDropDown::Draw(CCanvas2D& canvas) if (!m_Enabled) { - m_pGUI.DrawSprite(*m_Sprite2Disabled ? m_Sprite2Disabled : m_Sprite2, canvas, rect); + m_pGUI.DrawSprite(*m_Sprite2Disabled ? m_Sprite2Disabled : m_Sprite2, canvas, rect, m_VisibleArea); } else if (m_Open) { - m_pGUI.DrawSprite(*m_Sprite2Pressed ? m_Sprite2Pressed : m_Sprite2, canvas, rect); + m_pGUI.DrawSprite(*m_Sprite2Pressed ? m_Sprite2Pressed : m_Sprite2, canvas, rect, m_VisibleArea); } else if (m_MouseHovering) { - m_pGUI.DrawSprite(*m_Sprite2Over ? m_Sprite2Over : m_Sprite2, canvas, rect); + m_pGUI.DrawSprite(*m_Sprite2Over ? m_Sprite2Over : m_Sprite2, canvas, rect, m_VisibleArea); } else - m_pGUI.DrawSprite(m_Sprite2, canvas, rect); + m_pGUI.DrawSprite(m_Sprite2, canvas, rect, m_VisibleArea); } if (m_Selected != -1) // TODO: Maybe check validity completely? { - CRect cliparea(m_CachedActualSize.left, m_CachedActualSize.top, - m_CachedActualSize.right - m_ButtonWidth, m_CachedActualSize.bottom); + CRect cliparea = m_VisibleArea != CRect() ? m_VisibleArea : m_CachedActualSize; + if (cliparea.right > m_CachedActualSize.right - m_ButtonWidth) + cliparea.right = m_CachedActualSize.right - m_ButtonWidth; CVector2D pos(m_CachedActualSize.left, m_CachedActualSize.top); DrawText(canvas, m_Selected, m_Enabled ? m_TextColorSelected : m_TextColorDisabled, pos, cliparea); @@ -474,7 +482,7 @@ void CDropDown::Draw(CCanvas2D& canvas) if (m_HideScrollBar) m_ScrollBar.Set(old, false); } - m_pGUI.DrawSprite(spriteOverlay, canvas, m_CachedActualSize); + m_pGUI.DrawSprite(spriteOverlay, canvas, m_CachedActualSize, m_VisibleArea); } // When a dropdown list is opened, it needs to be visible above all the other diff --git a/source/gui/ObjectTypes/CImage.cpp b/source/gui/ObjectTypes/CImage.cpp index 3c2f941116..a7a79fa41c 100644 --- a/source/gui/ObjectTypes/CImage.cpp +++ b/source/gui/ObjectTypes/CImage.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2021 Wildfire Games. +/* Copyright (C) 2024 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -33,5 +33,5 @@ CImage::~CImage() void CImage::Draw(CCanvas2D& canvas) { - m_pGUI.DrawSprite(m_Sprite, canvas, m_CachedActualSize); + m_pGUI.DrawSprite(m_Sprite, canvas, m_CachedActualSize, m_VisibleArea); } diff --git a/source/gui/ObjectTypes/CInput.cpp b/source/gui/ObjectTypes/CInput.cpp index 3dd58ae9e8..454b7bf358 100644 --- a/source/gui/ObjectTypes/CInput.cpp +++ b/source/gui/ObjectTypes/CInput.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2022 Wildfire Games. +/* Copyright (C) 2024 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -883,7 +883,8 @@ void CInput::ResetStates() void CInput::HandleMessage(SGUIMessage& Message) { IGUIObject::HandleMessage(Message); - IGUIScrollBarOwner::HandleMessage(Message); + if (m_ScrollBar) + IGUIScrollBarOwner::HandleMessage(Message); // Cleans up operator[] usage. const CStrW& caption = *m_Caption; @@ -1486,7 +1487,7 @@ void CInput::DrawContent(CCanvas2D& canvas) void CInput::Draw(CCanvas2D& canvas) { // We'll have to setup clipping manually, since we're doing the rendering manually. - CRect cliparea(m_CachedActualSize); + CRect cliparea = m_VisibleArea ? m_VisibleArea : m_CachedActualSize; // First we'll figure out the clipping area, which is the cached actual size // substracted by an optional scrollbar @@ -1521,7 +1522,7 @@ void CInput::Draw(CCanvas2D& canvas) IGUIScrollBarOwner::Draw(canvas); // Draw the overlays last - m_pGUI.DrawSprite(m_SpriteOverlay, canvas, m_CachedActualSize); + m_pGUI.DrawSprite(m_SpriteOverlay, canvas, m_CachedActualSize, m_VisibleArea); } void CInput::DrawPlaceholderText(CCanvas2D& canvas, const CRect& clipping) diff --git a/source/gui/ObjectTypes/CList.cpp b/source/gui/ObjectTypes/CList.cpp index 40b7575397..c857c2ff48 100644 --- a/source/gui/ObjectTypes/CList.cpp +++ b/source/gui/ObjectTypes/CList.cpp @@ -155,7 +155,8 @@ void CList::UpdateCachedSize() void CList::HandleMessage(SGUIMessage& Message) { IGUIObject::HandleMessage(Message); - IGUIScrollBarOwner::HandleMessage(Message); + if (m_ScrollBar) + IGUIScrollBarOwner::HandleMessage(Message); //IGUITextOwner::HandleMessage(Message); <== placed it after the switch instead! m_Modified = false; diff --git a/source/gui/ObjectTypes/COList.cpp b/source/gui/ObjectTypes/COList.cpp index 80fdd28626..58577f5dca 100644 --- a/source/gui/ObjectTypes/COList.cpp +++ b/source/gui/ObjectTypes/COList.cpp @@ -302,7 +302,7 @@ void COList::DrawList(CCanvas2D& canvas, const int& selected, const CGUISpriteIn { CRect rect = GetListRect(); - m_pGUI.DrawSprite(sprite, canvas, rect); + m_pGUI.DrawSprite(sprite, canvas, rect, m_VisibleArea); float scroll = 0.f; if (m_ScrollBar) @@ -341,7 +341,7 @@ void COList::DrawList(CCanvas2D& canvas, const int& selected, const CGUISpriteIn } // Draw item selection - m_pGUI.DrawSprite(spriteSelectArea, canvas, rectSel); + m_pGUI.DrawSprite(spriteSelectArea, canvas, rectSel, m_VisibleArea); drawSelected = true; } } @@ -349,7 +349,7 @@ void COList::DrawList(CCanvas2D& canvas, const int& selected, const CGUISpriteIn // Draw line above column header CRect rect_head(m_CachedActualSize.left, m_CachedActualSize.top, m_CachedActualSize.right, m_CachedActualSize.top + m_HeadingHeight); - m_pGUI.DrawSprite(m_SpriteHeading, canvas, rect_head); + m_pGUI.DrawSprite(m_SpriteHeading, canvas, rect_head, m_VisibleArea); // Draw column headers float xpos = 0; @@ -386,7 +386,7 @@ void COList::DrawList(CCanvas2D& canvas, const int& selected, const CGUISpriteIn else pSprite = &*m_SpriteNotSorted; - m_pGUI.DrawSprite(*pSprite, canvas, CRect(leftTopCorner + CVector2D(width - SORT_SPRITE_DIM, 0), leftTopCorner + CVector2D(width, SORT_SPRITE_DIM))); + m_pGUI.DrawSprite(*pSprite, canvas, CRect(leftTopCorner + CVector2D(width - SORT_SPRITE_DIM, 0), leftTopCorner + CVector2D(width, SORT_SPRITE_DIM)), m_VisibleArea); } // Draw column header text @@ -453,7 +453,7 @@ void COList::DrawList(CCanvas2D& canvas, const int& selected, const CGUISpriteIn IGUIScrollBarOwner::Draw(canvas); // Draw the overlays last - m_pGUI.DrawSprite(spriteOverlay, canvas, rect); + m_pGUI.DrawSprite(spriteOverlay, canvas, rect, m_VisibleArea); if (drawSelected) - m_pGUI.DrawSprite(spriteSelectAreaOverlay, canvas, rectSel); + m_pGUI.DrawSprite(spriteSelectAreaOverlay, canvas, rectSel, m_VisibleArea); } diff --git a/source/gui/ObjectTypes/CProgressBar.cpp b/source/gui/ObjectTypes/CProgressBar.cpp index d2749acaba..062c11d8da 100644 --- a/source/gui/ObjectTypes/CProgressBar.cpp +++ b/source/gui/ObjectTypes/CProgressBar.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2021 Wildfire Games. +/* Copyright (C) 2024 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -55,10 +55,10 @@ void CProgressBar::HandleMessage(SGUIMessage& Message) void CProgressBar::Draw(CCanvas2D& canvas) { - m_pGUI.DrawSprite(m_SpriteBackground, canvas, m_CachedActualSize); + m_pGUI.DrawSprite(m_SpriteBackground, canvas, m_CachedActualSize, m_VisibleArea); // Get size of bar (notice it is drawn slightly closer, to appear above the background) CRect size = m_CachedActualSize; size.right = size.left + m_CachedActualSize.GetWidth() * (m_Progress / 100.f), - m_pGUI.DrawSprite(m_SpriteBar, canvas, size); + m_pGUI.DrawSprite(m_SpriteBar, canvas, size, m_VisibleArea); } diff --git a/source/gui/ObjectTypes/CScrollPanel.cpp b/source/gui/ObjectTypes/CScrollPanel.cpp new file mode 100644 index 0000000000..069ad7fbd6 --- /dev/null +++ b/source/gui/ObjectTypes/CScrollPanel.cpp @@ -0,0 +1,257 @@ +/* Copyright (C) 2024 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 "CScrollPanel.h" + +#include "gui/GUIObjectEventBroadcaster.h" + +#include "gui/CGUIScrollBarHorizontal.h" +#include "gui/CGUIScrollBarVertical.h" + +CScrollPanel::CScrollPanel(CGUI& pGUI) + : IGUIPanel(pGUI), + IGUIScrollBarOwner(*static_cast(this)), + m_Orientation(this, "orientation", EScrollOrientation::VERTICAL), + m_ScrollBarStyle(this, "scrollbar_style"), + m_MinWidth(this, "min_width", 0), + m_MinHeight(this, "min_height", 0) +{ + auto vbar = std::make_unique(pGUI); + vbar->SetRightAligned(true); + AddScrollBar(std::move(vbar)); + + auto hbar = std::make_unique(pGUI); + hbar->SetBottomAligned(true); + AddScrollBar(std::move(hbar)); +} + +CScrollPanel::~CScrollPanel() +{ +} + +void CScrollPanel::ResetStates() +{ + IGUIPanel::ResetStates(); + IGUIScrollBarOwner::ResetStates(); +} + +void CScrollPanel::UpdateCachedSize() +{ + IGUIPanel::UpdateCachedSize(); + Setup(); +} + +void CScrollPanel::HandleMessage(SGUIMessage& Message) +{ + IGUIScrollBar& scrollbar0 = GetScrollBar(0); + IGUIScrollBar& scrollbar1 = GetScrollBar(1); + + IGUIPanel::HandleMessage(Message); + + float vscroll = scrollbar0.GetPos(); + float hscroll = scrollbar1.GetPos(); + bool updateScrollPosition = false; + + IGUIScrollBarOwner::HandleMessage(Message); + + if (vscroll != scrollbar0.GetPos()) + { + vscroll = scrollbar0.GetPos(); + updateScrollPosition = true; + } + + if (hscroll != scrollbar1.GetPos()) + { + hscroll = scrollbar1.GetPos(); + updateScrollPosition = true; + } + + if (updateScrollPosition) + UpdateScrollPosition(vscroll, hscroll); + + switch (Message.type) + { + case GUIM_SETTINGS_UPDATED: + if (Message.value == "scrollbar_style") + { + scrollbar0.SetScrollBarStyle(m_ScrollBarStyle); + scrollbar1.SetScrollBarStyle(m_ScrollBarStyle); + Setup(); + } + + if (Message.value == "orientation" || Message.value == "size" || Message.value == "min_width" || Message.value == "min_height") + { + scrollbar0.SetPos(0); + scrollbar1.SetPos(0); + Setup(); + } + break; + + case GUIM_CHILD_RESIZED: + case GUIM_CHILD_TOGGLE_VISIBILITY: + Setup(); + Message.Skip(false); + break; + + case GUIM_LOAD: + scrollbar0.SetScrollBarStyle(m_ScrollBarStyle); + scrollbar1.SetScrollBarStyle(m_ScrollBarStyle); + + Setup(); + break; + + default: + break; + } +} + +void CScrollPanel::Draw(CCanvas2D& canvas) +{ + IGUIScrollBar& scrollbar0 = GetScrollBar(0); + IGUIScrollBar& scrollbar1 = GetScrollBar(1); + + m_Drawing = true; + IGUIScrollBarOwner::Draw(canvas); + + CRect cliparea(m_CachedLayoutActualSize); + + // substract scrollbar from cliparea + if (scrollbar0.IsVisible()) + cliparea.right -= scrollbar0.GetOuterRect().GetWidth(); + if (scrollbar1.IsVisible()) + cliparea.bottom -= scrollbar1.GetOuterRect().GetHeight(); + + CGUIObjectEventBroadcaster::RecurseVisibleObject(this, &IGUIObject::DrawInArea, canvas, cliparea); + m_Drawing = false; +} + +void CScrollPanel::Setup() +{ + IGUIScrollBar& scrollbar0 = GetScrollBar(0); + IGUIScrollBar& scrollbar1 = GetScrollBar(1); + + m_CachedActualSize = m_CachedLayoutActualSize; + + if (HasVerticalScrollBar() && m_CachedLayoutActualSize.GetHeight() < m_MinHeight) + m_CachedActualSize.bottom = m_CachedLayoutActualSize.top + m_MinHeight; + + if (HasHorizontalScrollBar() && m_CachedLayoutActualSize.GetWidth() < m_MinWidth) + m_CachedActualSize.right = m_CachedLayoutActualSize.left + m_MinWidth; + + for (IGUIObject* child : m_Children) + child->RecurseObject(&IGUIObject::IsHiddenOrGhost, &IGUIObject::UpdateCachedSize); + + float vscroll = scrollbar0.GetPos(); + float hscroll = scrollbar1.GetPos(); + float maxVRange = 0; + float maxHRange = 0; + + for (IGUIObject* child : m_Children) + { + if (child->IsHiddenOrGhost()) + continue; + CRect childSize = child->GetComputedSize(); + maxVRange = std::max(maxVRange, childSize.bottom); + maxHRange = std::max(maxHRange, childSize.right); + } + + maxVRange -= m_CachedLayoutActualSize.top; + maxHRange -= m_CachedLayoutActualSize.left; + + scrollbar0.SetScrollRange(HasVerticalScrollBar() ? maxVRange : m_CachedLayoutActualSize.GetHeight()); + scrollbar0.SetScrollSpace(m_CachedLayoutActualSize.GetHeight()); + scrollbar0.SetX(m_CachedLayoutActualSize.right); + scrollbar0.SetY(m_CachedLayoutActualSize.top); + scrollbar0.SetZ(GetBufferedZ()); + scrollbar0.SetLength(m_CachedLayoutActualSize.GetHeight()); + + scrollbar1.SetScrollRange(HasHorizontalScrollBar() ? maxHRange : m_CachedLayoutActualSize.GetWidth()); + scrollbar1.SetScrollSpace(m_CachedLayoutActualSize.GetWidth()); + scrollbar1.SetX(m_CachedLayoutActualSize.left); + scrollbar1.SetY(m_CachedLayoutActualSize.bottom); + scrollbar1.SetZ(GetBufferedZ()); + scrollbar1.SetLength(m_CachedLayoutActualSize.GetWidth()); + + if (HasVerticalScrollBar() && HasHorizontalScrollBar() && scrollbar0.IsVisible()) + { + scrollbar1.SetLength(m_CachedLayoutActualSize.GetWidth() - scrollbar0.GetOuterRect().GetWidth()); + scrollbar1.SetScrollSpace(m_CachedLayoutActualSize.GetWidth() + scrollbar0.GetOuterRect().GetWidth()); + } + + if (HasHorizontalScrollBar() && HasVerticalScrollBar() && scrollbar1.IsVisible()) + { + scrollbar0.SetLength(m_CachedLayoutActualSize.GetHeight() - scrollbar1.GetOuterRect().GetHeight()); + scrollbar0.SetScrollSpace(m_CachedLayoutActualSize.GetHeight() - scrollbar1.GetOuterRect().GetHeight()); + } + + if (HasVerticalScrollBar() && maxVRange < vscroll) + { + vscroll = maxVRange; + scrollbar0.SetPos(vscroll); + } + + if (HasHorizontalScrollBar() && maxHRange < hscroll) + { + hscroll = maxHRange; + scrollbar1.SetPos(hscroll); + } + + UpdateScrollPosition(vscroll, hscroll); +} + +void CScrollPanel::UpdateScrollPosition(float scroll, float hscroll) +{ + IGUIScrollBar& scrollbar0 = GetScrollBar(0); + IGUIScrollBar& scrollbar1 = GetScrollBar(1); + + m_CachedActualSize = m_CachedLayoutActualSize; + + if (HasVerticalScrollBar() && m_CachedLayoutActualSize.GetHeight() < m_MinHeight) + m_CachedActualSize.bottom = m_CachedLayoutActualSize.top + m_MinHeight; + + if (HasHorizontalScrollBar() && m_CachedLayoutActualSize.GetWidth() < m_MinWidth) + m_CachedActualSize.right = m_CachedLayoutActualSize.left + m_MinWidth; + + m_CachedActualSize.top -= scroll; + m_CachedActualSize.bottom -= scroll; + + m_CachedActualSize.left -= hscroll; + m_CachedActualSize.right -= hscroll; + + // upddate scroll bars size base on m_Width + if (scrollbar0.IsVisible()) + m_CachedActualSize.right -= scrollbar0.GetOuterRect().GetWidth(); + if (scrollbar1.IsVisible()) + m_CachedActualSize.bottom -= scrollbar1.GetOuterRect().GetHeight(); + + for (IGUIObject* child : m_Children) + child->RecurseObject(&IGUIObject::IsHiddenOrGhost, &IGUIObject::UpdateCachedSize); +} + +void CScrollPanel::ResetScrollPosition(EScrollOrientation orientation) +{ + IGUIScrollBar& scrollbar0 = GetScrollBar(0); + IGUIScrollBar& scrollbar1 = GetScrollBar(1); + + if (orientation == EScrollOrientation::BOTH || orientation == EScrollOrientation::VERTICAL) + scrollbar0.SetPos(0); + + if (orientation == EScrollOrientation::BOTH || orientation == EScrollOrientation::HORIZONTAL) + scrollbar1.SetPos(0); +} diff --git a/source/gui/ObjectTypes/CScrollPanel.h b/source/gui/ObjectTypes/CScrollPanel.h new file mode 100644 index 0000000000..e6ba2845ab --- /dev/null +++ b/source/gui/ObjectTypes/CScrollPanel.h @@ -0,0 +1,62 @@ +/* Copyright (C) 20244 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_CSCROLLPANEL +#define INCLUDED_CSCROLLPANEL + +#include "gui/ObjectBases/IGUIPanel.h" +#include "gui/ObjectBases/IGUIScrollBarOwner.h" +#include "gui/SettingTypes/EScrollOrientation.h" +#include "ps/CStr.h" + +class CScrollPanel : public IGUIPanel, public IGUIScrollBarOwner +{ + GUI_OBJECT(CScrollPanel) + mutable std::vector m_ModifiedChildren; // To store the modified vector +public: + CScrollPanel(CGUI& pGUI); + virtual ~CScrollPanel(); + + virtual void UpdateCachedSize(); + virtual void ResetStates(); + + void Setup(); + + void ResetScrollPosition(EScrollOrientation orientation = EScrollOrientation::BOTH); + +protected: + /** + * @see IGUIObject#HandleMessage() + */ + virtual void HandleMessage(SGUIMessage& Message); + + void UpdateScrollPosition(float vscroll, float hscroll); + + bool HasHorizontalScrollBar() const { return *m_Orientation == EScrollOrientation::HORIZONTAL || *m_Orientation == EScrollOrientation::BOTH; }; + bool HasVerticalScrollBar() const { return *m_Orientation == EScrollOrientation::VERTICAL || *m_Orientation == EScrollOrientation::BOTH; }; + + virtual void Draw(CCanvas2D& canvas); + + virtual void CreateJSObject(); + + CGUISimpleSetting m_Orientation; + CGUISimpleSetting m_ScrollBarStyle; + CGUISimpleSetting m_MinWidth; + CGUISimpleSetting m_MinHeight; +}; + +#endif // INCLUDED_CSCROLLPANEL diff --git a/source/gui/ObjectTypes/CSlider.cpp b/source/gui/ObjectTypes/CSlider.cpp index 91666a1df3..dd51f560a7 100644 --- a/source/gui/ObjectTypes/CSlider.cpp +++ b/source/gui/ObjectTypes/CSlider.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2022 Wildfire Games. +/* Copyright (C) 2024 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -78,6 +78,7 @@ void CSlider::HandleMessage(SGUIMessage& Message) { if (m_Pressed) break; + Message.Skip(false); IncrementallyChangeValue(-0.01f); break; } @@ -85,6 +86,7 @@ void CSlider::HandleMessage(SGUIMessage& Message) { if (m_Pressed) break; + Message.Skip(false); IncrementallyChangeValue(0.01f); break; } @@ -109,8 +111,8 @@ void CSlider::Draw(CCanvas2D& canvas) CRect sliderLine(m_CachedActualSize); sliderLine.left += m_ButtonSide / 2.0f; sliderLine.right -= m_ButtonSide / 2.0f; - m_pGUI.DrawSprite(IsEnabled() ? m_SpriteBar : m_SpriteBarDisabled, canvas, sliderLine); - m_pGUI.DrawSprite(IsEnabled() ? m_Sprite : m_SpriteDisabled, canvas, GetButtonRect()); + m_pGUI.DrawSprite(IsEnabled() ? m_SpriteBar : m_SpriteBarDisabled, canvas, sliderLine, m_VisibleArea); + m_pGUI.DrawSprite(IsEnabled() ? m_Sprite : m_SpriteDisabled, canvas, GetButtonRect(), m_VisibleArea); } void CSlider::UpdateValue() diff --git a/source/gui/ObjectTypes/CText.cpp b/source/gui/ObjectTypes/CText.cpp index 9110d16643..ab2dc029d3 100644 --- a/source/gui/ObjectTypes/CText.cpp +++ b/source/gui/ObjectTypes/CText.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2021 Wildfire Games. +/* Copyright (C) 2024 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -130,7 +130,8 @@ const CStrW& CText::GetTooltipText() const void CText::HandleMessage(SGUIMessage& Message) { IGUIObject::HandleMessage(Message); - IGUIScrollBarOwner::HandleMessage(Message); + if (m_ScrollBar) + IGUIScrollBarOwner::HandleMessage(Message); //IGUITextOwner::HandleMessage(Message); <== placed it after the switch instead! switch (Message.type) @@ -148,24 +149,6 @@ void CText::HandleMessage(SGUIMessage& Message) break; - case GUIM_MOUSE_WHEEL_DOWN: - { - GetScrollBar(0).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; - } - case GUIM_MOUSE_WHEEL_UP: - { - GetScrollBar(0).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_LOAD: { GetScrollBar(0).SetX(m_CachedActualSize.right); @@ -185,18 +168,16 @@ void CText::HandleMessage(SGUIMessage& Message) void CText::Draw(CCanvas2D& canvas) { - m_pGUI.DrawSprite(m_Sprite, canvas, m_CachedActualSize); + m_pGUI.DrawSprite(m_Sprite, canvas, m_CachedActualSize, m_VisibleArea); float scroll = 0.f; if (m_ScrollBar) scroll = GetScrollBar(0).GetPos(); // Clipping area (we'll have to subtract the scrollbar) - CRect cliparea; + CRect cliparea = m_VisibleArea ? m_VisibleArea : m_CachedActualSize; if (m_Clip) { - cliparea = m_CachedActualSize; - if (m_ScrollBar) { // subtract scrollbar from cliparea @@ -222,5 +203,5 @@ void CText::Draw(CCanvas2D& canvas) IGUIScrollBarOwner::Draw(canvas); // Draw the overlays last - m_pGUI.DrawSprite(m_SpriteOverlay, canvas, m_CachedActualSize); + m_pGUI.DrawSprite(m_SpriteOverlay, canvas, m_CachedActualSize, m_VisibleArea); } diff --git a/source/gui/SGUIMessage.h b/source/gui/SGUIMessage.h index c23fb5a23b..cd7e2d6917 100644 --- a/source/gui/SGUIMessage.h +++ b/source/gui/SGUIMessage.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2020 Wildfire Games. +/* Copyright (C) 2024 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -35,13 +35,15 @@ enum EGUIMessageType GUIM_MOUSE_DOWN_LEFT, GUIM_MOUSE_DOWN_RIGHT, GUIM_MOUSE_DBLCLICK_LEFT, - GUIM_MOUSE_DBLCLICK_LEFT_ITEM, // Triggered when doubleclicking on a list item + GUIM_MOUSE_DBLCLICK_LEFT_ITEM, // Triggered when doubleclicking on a list item. GUIM_MOUSE_DBLCLICK_RIGHT, GUIM_MOUSE_RELEASE_LEFT, GUIM_MOUSE_RELEASE_RIGHT, GUIM_MOUSE_WHEEL_UP, GUIM_MOUSE_WHEEL_DOWN, - GUIM_SETTINGS_UPDATED, // SGUIMessage.m_Value = name of setting + GUIM_MOUSE_WHEEL_LEFT, + GUIM_MOUSE_WHEEL_RIGHT, + GUIM_SETTINGS_UPDATED, // SGUIMessage.m_Value = name of setting. GUIM_PRESSED, GUIM_KEYDOWN, GUIM_RELEASED, @@ -55,8 +57,10 @@ enum EGUIMessageType GUIM_DOUBLE_PRESSED_MOUSE_RIGHT, GUIM_PRESSED_MOUSE_RELEASE, GUIM_PRESSED_MOUSE_RELEASE_RIGHT, - GUIM_TAB, // Used by CInput - GUIM_TEXTEDIT + GUIM_TAB, // Used by CInput. + GUIM_TEXTEDIT, + GUIM_CHILD_RESIZED, // SGUIMessage.m_Value = name of the object that changed size, used for inform to parent. + GUIM_CHILD_TOGGLE_VISIBILITY, // SGUIMessage.m_Value = name of the object that changed visibility used for inform to parent. }; /** diff --git a/source/gui/Scripting/GuiScriptConversions.cpp b/source/gui/Scripting/GuiScriptConversions.cpp index b47666d585..504d07b4f9 100644 --- a/source/gui/Scripting/GuiScriptConversions.cpp +++ b/source/gui/Scripting/GuiScriptConversions.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2021 Wildfire Games. +/* Copyright (C) 2024 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -18,6 +18,7 @@ #include "precompiled.h" #include "gui/ObjectBases/IGUIObject.h" +#include "gui/SettingTypes/EScrollOrientation.h" #include "gui/SettingTypes/CGUIColor.h" #include "gui/SettingTypes/CGUIList.h" #include "gui/SettingTypes/CGUISeries.h" @@ -310,6 +311,49 @@ template<> bool Script::FromJSVal(const ScriptRequest& rq, JS::HandleVal return true; } +template<> void Script::ToJSVal(const ScriptRequest& rq, JS::MutableHandleValue ret, const EScrollOrientation& val) +{ + std::string word; + switch (val) + { + case EScrollOrientation::HORIZONTAL: + word = "horizontal"; + break; + case EScrollOrientation::VERTICAL: + word = "vertical"; + break; + case EScrollOrientation::BOTH: + word = "both"; + break; + default: + word = "error"; + ScriptException::Raise(rq, "Invalid scroll orientation (should be 'vertical', 'horizontal' or 'both')"); + break; + } + ToJSVal(rq, ret, word); +} + +template <> bool Script::FromJSVal(const ScriptRequest& rq, JS::HandleValue v, EScrollOrientation& out) +{ + std::string word; + FromJSVal(rq, v, word); + + if (word == "horizontal") + out = EScrollOrientation::HORIZONTAL; + else if (word == "vertical") + out = EScrollOrientation::VERTICAL; + else if (word == "both") + out = EScrollOrientation::BOTH; + else + { + out = EScrollOrientation::VERTICAL; + LOGERROR("Invalid scroll orientation (should be 'vertical', 'horizontal' or 'both')"); + return false; + } + + return true; +} + template<> void Script::ToJSVal(const ScriptRequest& rq, JS::MutableHandleValue ret, const CGUISpriteInstance& val) { ToJSVal(rq, ret, val.GetName()); diff --git a/source/gui/Scripting/JSInterface_GUIProxy.cpp b/source/gui/Scripting/JSInterface_GUIProxy.cpp index f5552b08ff..a337b253e5 100644 --- a/source/gui/Scripting/JSInterface_GUIProxy.cpp +++ b/source/gui/Scripting/JSInterface_GUIProxy.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2021 Wildfire Games. +/* Copyright (C) 2024 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -23,6 +23,7 @@ #include "gui/ObjectTypes/CButton.h" #include "gui/ObjectTypes/CList.h" #include "gui/ObjectTypes/CMiniMap.h" +#include "gui/ObjectTypes/CScrollPanel.h" #include "gui/ObjectTypes/CText.h" // Called for every specialization - adds the common interface. @@ -66,3 +67,10 @@ template<> void JSI_GUIProxy::CreateFunctions(const ScriptRequest& rq, CreateFunction<&CMiniMap::Flare>(rq, cache, "flare"); } DECLARE_GUIPROXY(CMiniMap); + +// CScrollPanel +template<> void JSI_GUIProxy::CreateFunctions(const ScriptRequest& rq, GUIProxyProps* cache) +{ + CreateFunction<&CScrollPanel::ResetScrollPosition>(rq, cache, "resetScrollPosition"); +} +DECLARE_GUIPROXY(CScrollPanel); diff --git a/source/gui/SettingTypes/EScrollOrientation.h b/source/gui/SettingTypes/EScrollOrientation.h new file mode 100644 index 0000000000..5f9258049b --- /dev/null +++ b/source/gui/SettingTypes/EScrollOrientation.h @@ -0,0 +1,28 @@ +/* Copyright (C) 2024 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_ESCROLLORIENTATION +#define INCLUDED_ESCROLLORIENTATION + +enum class EScrollOrientation +{ + HORIZONTAL, + VERTICAL, + BOTH +}; + +#endif // INCLUDED_ESCROLLORIENTATION diff --git a/source/maths/Rect.cpp b/source/maths/Rect.cpp index 394a032262..ca23057255 100644 --- a/source/maths/Rect.cpp +++ b/source/maths/Rect.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2021 Wildfire Games. +/* Copyright (C) 2024 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -219,3 +219,16 @@ CRect CRect::Scale(float x, float y) const { return CRect(left * x, top * y, right * x, bottom * y); } + +bool CRect::IntersectWith(const CRect& a) const +{ + return left < a.right && right > a.left && top < a.bottom && bottom > a.top; +} + +CRect CRect::Intersection(const CRect& a) const +{ + if (!IntersectWith(a)) + return CRect(); + + return CRect(std::max(left, a.left), std::max(top, a.top), std::min(right, a.right), std::min(bottom, a.bottom)); +} diff --git a/source/maths/Rect.h b/source/maths/Rect.h index ed1df24f32..d6116699d3 100644 --- a/source/maths/Rect.h +++ b/source/maths/Rect.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2021 Wildfire Games. +/* Copyright (C) 2024 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -58,6 +58,8 @@ public: void operator-=(const CVector2D& a); void operator-=(const CSize2D& a); + operator bool() const { return right - left > 0 && bottom - top > 0; } + /** * @return Width of Rectangle */ @@ -107,6 +109,10 @@ public: CRect Scale(float x, float y) const; + bool IntersectWith(const CRect& a) const; + + CRect Intersection(const CRect& a) const; + /** * Returning CVector2D representing each corner. */