diff options
author | Thorbjørn Lindeijer <bjorn@lindeijer.nl> | 2025-04-10 12:09:31 +0200 |
---|---|---|
committer | Thorbjørn Lindeijer <bjorn@lindeijer.nl> | 2025-04-25 14:05:30 +0000 |
commit | cd5aebcdd3443b1be8fe5fe177df058879512307 (patch) | |
tree | 9cc6dfd3f8b992b39cbee5868622158785d46d13 | |
parent | c363961327621ceeb47ead653bb20c5da4e9a726 (diff) | |
download | mana-cd5aebcdd3443b1be8fe5fe177df058879512307.tar.gz mana-cd5aebcdd3443b1be8fe5fe177df058879512307.tar.bz2 mana-cd5aebcdd3443b1be8fe5fe177df058879512307.tar.xz mana-cd5aebcdd3443b1be8fe5fe177df058879512307.zip |
GUI: Added support for fixed-size scrollarea markers
Due to getVerticalMarkerDimension and getHorizontalMarkerDimension not
being virtual, this unfortunately required copying a lot of code from
Guichan to make sure it calls our versions of these functions.
Also addressed a small issue where gcn::ScrollArea::checkPolicies was
not taking the frame size of the content into account.
-rw-r--r-- | data/graphics/gui/jewelry/theme.xml | 32 | ||||
-rw-r--r-- | src/gui/widgets/scrollarea.cpp | 322 | ||||
-rw-r--r-- | src/gui/widgets/scrollarea.h | 23 |
3 files changed, 354 insertions, 23 deletions
diff --git a/data/graphics/gui/jewelry/theme.xml b/data/graphics/gui/jewelry/theme.xml index ba85278d..ea173658 100644 --- a/data/graphics/gui/jewelry/theme.xml +++ b/data/graphics/gui/jewelry/theme.xml @@ -254,33 +254,33 @@ </state> </skin> - <skin type="ScrollAreaVBar"> + <skin type="ScrollAreaVBar" width="20"> <state> - <img src="window.png" x="51" y="186" width="18" height="34" left="18" top="6" bottom="6" fill="repeat" /> + <img src="window.png" x="51" y="186" width="18" height="34" left="18" top="6" bottom="6" fill="repeat" offsetX="1" /> </state> </skin> - <skin type="ScrollAreaHBar"> + <skin type="ScrollAreaHBar" height="20"> <state> - <img src="window.png" x="69" y="181" width="34" height="18" left="6" top="18" right="6" fill="repeat" /> + <img src="window.png" x="69" y="181" width="34" height="18" left="6" top="18" right="6" fill="repeat" offsetY="1" /> </state> </skin> - <skin type="ScrollAreaHMarker"> + <skin type="ScrollAreaHMarker" width="20" height="20"> <state hovered="true"> - <img src="window.png" x="26" y="165" width="18" height="18" left="8" right="9" top="18" /> + <img src="window.png" x="26" y="165" width="18" height="18" offsetX="1" offsetY="1" /> </state> <state> - <img src="window.png" x="3" y="165" width="18" height="18" left="8" right="9" top="18" /> + <img src="window.png" x="3" y="165" width="18" height="18" offsetX="1" offsetY="1" /> </state> </skin> - <skin type="ScrollAreaVMarker"> + <skin type="ScrollAreaVMarker" width="20" height="20"> <state hovered="true"> - <img src="window.png" x="26" y="165" width="18" height="18" left="18" top="8" bottom="9" /> + <img src="window.png" x="26" y="165" width="18" height="18" offsetX="1" offsetY="1" /> </state> <state> - <img src="window.png" x="3" y="165" width="18" height="18" left="18" top="8" bottom="9" /> + <img src="window.png" x="3" y="165" width="18" height="18" offsetX="1" offsetY="1" /> </state> </skin> @@ -311,28 +311,28 @@ <skin type="ButtonUp"> <state selected="true"> - <img src="window.png" x="17" y="148" width="12" height="12" offsetX="3" offsetY="3" /> + <img src="window.png" x="17" y="148" width="12" height="12" offsetX="4" offsetY="4" /> </state> <state> - <img src="window.png" x="1" y="148" width="12" height="12" offsetX="3" offsetY="3" /> + <img src="window.png" x="1" y="148" width="12" height="12" offsetX="4" offsetY="4" /> </state> </skin> <skin type="ButtonDown"> <state selected="true"> - <img src="window.png" x="17" y="132" width="12" height="12" offsetX="3" offsetY="3" /> + <img src="window.png" x="17" y="132" width="12" height="12" offsetX="4" offsetY="4" /> </state> <state> - <img src="window.png" x="1" y="132" width="12" height="12" offsetX="3" offsetY="3" /> + <img src="window.png" x="1" y="132" width="12" height="12" offsetX="4" offsetY="4" /> </state> </skin> <skin type="ButtonLeft"> <state selected="true"> - <img src="window.png" x="17" y="100" width="12" height="12" offsetX="3" offsetY="3" /> + <img src="window.png" x="17" y="100" width="12" height="12" offsetX="4" offsetY="4" /> </state> <state> - <img src="window.png" x="1" y="100" width="12" height="12" offsetX="3" offsetY="3" /> + <img src="window.png" x="1" y="100" width="12" height="12" offsetX="4" offsetY="4" /> </state> </skin> diff --git a/src/gui/widgets/scrollarea.cpp b/src/gui/widgets/scrollarea.cpp index c4d55072..a7e94971 100644 --- a/src/gui/widgets/scrollarea.cpp +++ b/src/gui/widgets/scrollarea.cpp @@ -25,6 +25,8 @@ #include "gui/gui.h" +#include <guichan/exception.hpp> + ScrollArea::ScrollArea() { init(); @@ -48,9 +50,9 @@ void ScrollArea::init() auto theme = gui->getTheme(); - int minWidth = theme->getSkin(SkinType::ScrollAreaVBar).getMinWidth(); - if (minWidth > 0) - setScrollbarWidth(minWidth); + int scrollBarWidth = theme->getSkin(SkinType::ScrollAreaVBar).width; + if (scrollBarWidth > 0) + setScrollbarWidth(scrollBarWidth); if (auto content = getContent()) content->setFrameSize(theme->getSkin(SkinType::ScrollArea).padding); @@ -198,6 +200,9 @@ void ScrollArea::drawHBar(gcn::Graphics *graphics) void ScrollArea::drawVMarker(gcn::Graphics *graphics) { WidgetState state(getVerticalMarkerDimension()); + if (state.height == 0) + return; + if (mHasMouse && (mX > (getWidth() - getScrollbarWidth()))) state.flags |= STATE_HOVERED; @@ -207,6 +212,9 @@ void ScrollArea::drawVMarker(gcn::Graphics *graphics) void ScrollArea::drawHMarker(gcn::Graphics *graphics) { WidgetState state(getHorizontalMarkerDimension()); + if (state.width == 0) + return; + if (mHasMouse && (mY > (getHeight() - getScrollbarWidth()))) state.flags |= STATE_HOVERED; @@ -225,6 +233,108 @@ void ScrollArea::drawButton(gcn::Graphics *graphics, gui->getTheme()->drawSkin(static_cast<Graphics *>(graphics), skinType, state); } +/** + * Code copied from gcn::ScrollArea::checkPolicies to make sure it takes the + * frame size of the content into account. + */ +void ScrollArea::checkPolicies() +{ + int w = getWidth(); + int h = getHeight(); + + mHBarVisible = false; + mVBarVisible = false; + + if (!getContent()) + { + mHBarVisible = (mHPolicy == SHOW_ALWAYS); + mVBarVisible = (mVPolicy == SHOW_ALWAYS); + return; + } + + const int contentFrameSize = getContent()->getFrameSize(); + w -= 2 * contentFrameSize; + h -= 2 * contentFrameSize; + + if (mHPolicy == SHOW_AUTO && + mVPolicy == SHOW_AUTO) + { + if (getContent()->getWidth() <= w + && getContent()->getHeight() <= h) + { + mHBarVisible = false; + mVBarVisible = false; + } + + if (getContent()->getWidth() > w) + { + mHBarVisible = true; + } + + if ((getContent()->getHeight() > h) + || (mHBarVisible && getContent()->getHeight() > h - mScrollbarWidth)) + { + mVBarVisible = true; + } + + if (mVBarVisible && getContent()->getWidth() > w - mScrollbarWidth) + { + mHBarVisible = true; + } + + return; + } + + switch (mHPolicy) + { + case SHOW_NEVER: + mHBarVisible = false; + break; + + case SHOW_ALWAYS: + mHBarVisible = true; + break; + + case SHOW_AUTO: + if (mVPolicy == SHOW_NEVER) + { + mHBarVisible = getContent()->getWidth() > w; + } + else // (mVPolicy == SHOW_ALWAYS) + { + mHBarVisible = getContent()->getWidth() > w - mScrollbarWidth; + } + break; + + default: + throw GCN_EXCEPTION("Horizontal scroll policy invalid."); + } + + switch (mVPolicy) + { + case SHOW_NEVER: + mVBarVisible = false; + break; + + case SHOW_ALWAYS: + mVBarVisible = true; + break; + + case SHOW_AUTO: + if (mHPolicy == SHOW_NEVER) + { + mVBarVisible = getContent()->getHeight() > h; + } + else // (mHPolicy == SHOW_ALWAYS) + { + mVBarVisible = getContent()->getHeight() > h - mScrollbarWidth; + } + break; + default: + throw GCN_EXCEPTION("Vertical scroll policy invalid."); + } +} + void ScrollArea::mouseMoved(gcn::MouseEvent& event) { mX = event.getX(); @@ -240,3 +350,209 @@ void ScrollArea::mouseExited(gcn::MouseEvent& event) { mHasMouse = false; } + +/** + * Code copied from gcn::ScrollArea::mousePressed to make it call our custom + * getVerticalMarkerDimension and getHorizontalMarkerDimension functions. + */ +void ScrollArea::mousePressed(gcn::MouseEvent &mouseEvent) +{ + int x = mouseEvent.getX(); + int y = mouseEvent.getY(); + + if (getUpButtonDimension().isPointInRect(x, y)) + { + setVerticalScrollAmount(getVerticalScrollAmount() + - mUpButtonScrollAmount); + mUpButtonPressed = true; + } + else if (getDownButtonDimension().isPointInRect(x, y)) + { + setVerticalScrollAmount(getVerticalScrollAmount() + + mDownButtonScrollAmount); + mDownButtonPressed = true; + } + else if (getLeftButtonDimension().isPointInRect(x, y)) + { + setHorizontalScrollAmount(getHorizontalScrollAmount() + - mLeftButtonScrollAmount); + mLeftButtonPressed = true; + } + else if (getRightButtonDimension().isPointInRect(x, y)) + { + setHorizontalScrollAmount(getHorizontalScrollAmount() + + mRightButtonScrollAmount); + mRightButtonPressed = true; + } + else if (getVerticalMarkerDimension().isPointInRect(x, y)) + { + mIsHorizontalMarkerDragged = false; + mIsVerticalMarkerDragged = true; + + mVerticalMarkerDragOffset = y - getVerticalMarkerDimension().y; + } + else if (getVerticalBarDimension().isPointInRect(x,y)) + { + if (y < getVerticalMarkerDimension().y) + { + setVerticalScrollAmount(getVerticalScrollAmount() + - (int)(getChildrenArea().height * 0.95)); + } + else + { + setVerticalScrollAmount(getVerticalScrollAmount() + + (int)(getChildrenArea().height * 0.95)); + } + } + else if (getHorizontalMarkerDimension().isPointInRect(x, y)) + { + mIsHorizontalMarkerDragged = true; + mIsVerticalMarkerDragged = false; + + mHorizontalMarkerDragOffset = x - getHorizontalMarkerDimension().x; + } + else if (getHorizontalBarDimension().isPointInRect(x,y)) + { + if (x < getHorizontalMarkerDimension().x) + { + setHorizontalScrollAmount(getHorizontalScrollAmount() + - (int)(getChildrenArea().width * 0.95)); + } + else + { + setHorizontalScrollAmount(getHorizontalScrollAmount() + + (int)(getChildrenArea().width * 0.95)); + } + } +} + +/** + * Code copied from gcn::ScrollArea::mouseDragged to make it call our custom + * getVerticalMarkerDimension and getHorizontalMarkerDimension functions. + */ +void ScrollArea::mouseDragged(gcn::MouseEvent &mouseEvent) +{ + if (mIsVerticalMarkerDragged) + { + int pos = mouseEvent.getY() - getVerticalBarDimension().y - mVerticalMarkerDragOffset; + int length = getVerticalMarkerDimension().height; + + gcn::Rectangle barDim = getVerticalBarDimension(); + + if ((barDim.height - length) > 0) + { + setVerticalScrollAmount((getVerticalMaxScroll() * pos) + / (barDim.height - length)); + } + else + { + setVerticalScrollAmount(0); + } + } + + if (mIsHorizontalMarkerDragged) + { + int pos = mouseEvent.getX() - getHorizontalBarDimension().x - mHorizontalMarkerDragOffset; + int length = getHorizontalMarkerDimension().width; + + gcn::Rectangle barDim = getHorizontalBarDimension(); + + if ((barDim.width - length) > 0) + { + setHorizontalScrollAmount((getHorizontalMaxScroll() * pos) + / (barDim.width - length)); + } + else + { + setHorizontalScrollAmount(0); + } + } + + mouseEvent.consume(); +} + +static void getMarkerValues(int barSize, + int maxScroll, int scrollAmount, + int contentHeight, int viewHeight, + int fixedMarkerSize, int minMarkerSize, + int &markerSize, int &markerPos) +{ + markerSize = barSize; + markerPos = 0; + + if (fixedMarkerSize == 0) + { + if (contentHeight != 0 && contentHeight > viewHeight) + markerSize = std::max((barSize * viewHeight) / contentHeight, minMarkerSize); + } + else + { + if (viewHeight > contentHeight) + markerSize = 0; + else + markerSize = fixedMarkerSize; + } + + // Hide the marker when it doesn't fit + if (markerSize > barSize) + markerSize = 0; + + if (maxScroll != 0) + markerPos = ((barSize - markerSize) * scrollAmount + maxScroll / 2) / maxScroll; +} + +gcn::Rectangle ScrollArea::getVerticalMarkerDimension() +{ + if (!mVBarVisible) + return gcn::Rectangle(0, 0, 0, 0); + + auto &markerSkin = gui->getTheme()->getSkin(SkinType::ScrollAreaVMarker); + const gcn::Rectangle barDim = getVerticalBarDimension(); + + int contentHeight = 0; + if (auto content = getContent()) + contentHeight = content->getHeight() + content->getFrameSize() * 2; + + int length; + int pos; + + getMarkerValues(barDim.height, + getVerticalMaxScroll(), + getVerticalScrollAmount(), + contentHeight, + getChildrenArea().height, + markerSkin.height, + mScrollbarWidth, + length, + pos); + + return gcn::Rectangle(barDim.x, barDim.y + pos, mScrollbarWidth, length); +} + +gcn::Rectangle ScrollArea::getHorizontalMarkerDimension() +{ + if (!mHBarVisible) + return gcn::Rectangle(0, 0, 0, 0); + + auto &markerSkin = gui->getTheme()->getSkin(SkinType::ScrollAreaHMarker); + const gcn::Rectangle barDim = getHorizontalBarDimension(); + + int contentWidth = 0; + if (auto content = getContent()) + contentWidth = content->getWidth() + content->getFrameSize() * 2; + + int length; + int pos; + + getMarkerValues(barDim.width, + getHorizontalMaxScroll(), + getHorizontalScrollAmount(), + contentWidth, + getChildrenArea().width, + markerSkin.width, + mScrollbarWidth, + length, + pos); + + return gcn::Rectangle(barDim.x + pos, barDim.y, length, mScrollbarWidth); +} diff --git a/src/gui/widgets/scrollarea.h b/src/gui/widgets/scrollarea.h index 40f1adc1..7cdcfa19 100644 --- a/src/gui/widgets/scrollarea.h +++ b/src/gui/widgets/scrollarea.h @@ -32,6 +32,8 @@ * content. However, it won't delete a previously set content widget when * setContent is called! * + * Also overrides several functions to support fixed-size scroll bar markers. + * * \ingroup GUI */ class ScrollArea : public gcn::ScrollArea @@ -74,7 +76,7 @@ class ScrollArea : public gcn::ScrollArea /** * Applies clipping to the contents. */ - void drawChildren(gcn::Graphics* graphics) override; + void drawChildren(gcn::Graphics *graphics) override; /** * Sets whether the widget should draw its background or not. @@ -89,17 +91,20 @@ class ScrollArea : public gcn::ScrollArea /** * Called when the mouse moves in the widget area. */ - void mouseMoved(gcn::MouseEvent& event) override; + void mouseMoved(gcn::MouseEvent &event) override; /** * Called when the mouse enteres the widget area. */ - void mouseEntered(gcn::MouseEvent& event) override; + void mouseEntered(gcn::MouseEvent &event) override; /** * Called when the mouse leaves the widget area. */ - void mouseExited(gcn::MouseEvent& event) override; + void mouseExited(gcn::MouseEvent &event) override; + + void mousePressed(gcn::MouseEvent &mouseEvent) override; + void mouseDragged(gcn::MouseEvent &mouseEvent) override; protected: /** @@ -122,6 +127,16 @@ class ScrollArea : public gcn::ScrollArea bool pressed, const gcn::Rectangle &dim); + void checkPolicies() override; + + /** + * Shadowing these functions from gcn::ScrollArea with versions that + * supports fixed-size scroll bar markers. We need to make sure we + * always use these versions. + */ + gcn::Rectangle getVerticalMarkerDimension(); + gcn::Rectangle getHorizontalMarkerDimension(); + int mX = 0; int mY = 0; bool mHasMouse = false; |