summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThorbjørn Lindeijer <bjorn@lindeijer.nl>2025-04-10 12:09:31 +0200
committerThorbjørn Lindeijer <bjorn@lindeijer.nl>2025-04-25 14:05:30 +0000
commitcd5aebcdd3443b1be8fe5fe177df058879512307 (patch)
tree9cc6dfd3f8b992b39cbee5868622158785d46d13
parentc363961327621ceeb47ead653bb20c5da4e9a726 (diff)
downloadmana-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.xml32
-rw-r--r--src/gui/widgets/scrollarea.cpp322
-rw-r--r--src/gui/widgets/scrollarea.h23
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;