Веб-сайт самохостера Lotigara

summaryrefslogtreecommitdiff
path: root/source/windowing/StarScrollArea.cpp
diff options
context:
space:
mode:
authorKae <80987908+Novaenia@users.noreply.github.com>2023-06-20 14:33:09 +1000
committerKae <80987908+Novaenia@users.noreply.github.com>2023-06-20 14:33:09 +1000
commit6352e8e3196f78388b6c771073f9e03eaa612673 (patch)
treee23772f79a7fbc41bc9108951e9e136857484bf4 /source/windowing/StarScrollArea.cpp
parent6741a057e5639280d85d0f88ba26f000baa58f61 (diff)
everything everywhere
all at once
Diffstat (limited to 'source/windowing/StarScrollArea.cpp')
-rw-r--r--source/windowing/StarScrollArea.cpp445
1 files changed, 445 insertions, 0 deletions
diff --git a/source/windowing/StarScrollArea.cpp b/source/windowing/StarScrollArea.cpp
new file mode 100644
index 0000000..d9f537a
--- /dev/null
+++ b/source/windowing/StarScrollArea.cpp
@@ -0,0 +1,445 @@
+#include "StarScrollArea.hpp"
+#include "StarRoot.hpp"
+#include "StarAssets.hpp"
+
+namespace Star {
+
+// TODO: I hate these hardcoded values. Please smite with fire.
+static int const ScrollAreaBorder = 9;
+static int const ScrollButtonStackSize = 6;
+static int const ScrollThumbSize = 3;
+static int const ScrollThumbOverhead = ScrollThumbSize + ScrollThumbSize;
+static int const ScrollBarTrackOverhead = ScrollButtonStackSize + ScrollButtonStackSize + ScrollThumbOverhead;
+static int64_t const ScrollAdvanceTimer = 100;
+
+ScrollThumb::ScrollThumb(GuiDirection direction) {
+ m_hovered = false;
+ m_pressed = false;
+ m_direction = direction;
+ auto assets = Root::singleton().assets();
+ setImages(assets->json("/interface.config:scrollArea.thumbs"));
+}
+
+void ScrollThumb::setImages(ImageStretchSet const& base, ImageStretchSet const& hover, ImageStretchSet const& pressed) {
+ m_baseThumb = base;
+ m_hoverThumb = hover;
+ m_pressedThumb = pressed;
+}
+
+void ScrollThumb::setImages(Json const& images) {
+ String directionString;
+ if (m_direction == GuiDirection::Vertical) {
+ directionString = "vertical";
+ } else {
+ directionString = "horizontal";
+ }
+
+ m_baseThumb.begin = images.get(directionString).get("base").getString("begin");
+ m_baseThumb.end = images.get(directionString).get("base").getString("end");
+ m_baseThumb.inner = images.get(directionString).get("base").getString("inner");
+
+ m_hoverThumb.begin = images.get(directionString).get("hover").getString("begin");
+ m_hoverThumb.end = images.get(directionString).get("hover").getString("end");
+ m_hoverThumb.inner = images.get(directionString).get("hover").getString("inner");
+
+ m_pressedThumb.begin = images.get(directionString).get("pressed").getString("begin");
+ m_pressedThumb.end = images.get(directionString).get("pressed").getString("end");
+ m_pressedThumb.inner = images.get(directionString).get("pressed").getString("inner");
+}
+
+bool ScrollThumb::isHovered() const {
+ return m_hovered;
+}
+
+bool ScrollThumb::isPressed() const {
+ return m_pressed;
+}
+
+void ScrollThumb::setHovered(bool hovered) {
+ m_hovered = hovered;
+}
+
+void ScrollThumb::setPressed(bool pressed) {
+ m_pressed = pressed;
+}
+
+void ScrollThumb::mouseOver() {
+ setHovered(true);
+}
+
+void ScrollThumb::mouseOut() {
+ setHovered(false);
+}
+
+void ScrollThumb::renderImpl() {
+ ImageStretchSet workingSet = m_baseThumb;
+ if (isHovered())
+ workingSet = m_hoverThumb;
+ if (isPressed())
+ workingSet = m_pressedThumb;
+
+ if (workingSet.fullyPopulated()) {
+ context()->drawImageStretchSet(workingSet,
+ RectF::withSize(Vec2F(m_parent->screenPosition() + position()), Vec2F(size())),
+ m_direction);
+ }
+}
+
+Vec2U ScrollThumb::baseSize() const {
+ return context()->textureSize(m_baseThumb.begin);
+}
+
+ScrollBar::ScrollBar(GuiDirection direction, WidgetCallbackFunc forwardFunc, WidgetCallbackFunc backwardFunc)
+ : m_direction(direction) {
+ m_forward = make_shared<ButtonWidget>();
+ m_forward->setCallback(forwardFunc);
+ m_forward->setSustainCallbackOnDownHold(true);
+ m_forward->setPressedOffset({0, 0});
+
+ m_backward = make_shared<ButtonWidget>();
+ m_backward->setCallback(backwardFunc);
+ m_backward->setSustainCallbackOnDownHold(true);
+ m_backward->setPressedOffset({0, 0});
+
+ m_thumb = make_shared<ScrollThumb>(m_direction);
+
+ auto assets = Root::singleton().assets();
+ setButtonImages(assets->json("/interface.config:scrollArea.buttons"));
+
+ addChild("thumb", m_thumb);
+ addChild("forward", m_forward);
+ addChild("backward", m_backward);
+}
+
+void ScrollBar::setButtonImages(Json const& images) {
+ String directionString;
+ if (m_direction == GuiDirection::Vertical) {
+ directionString = "vertical";
+ } else {
+ directionString = "horizontal";
+ }
+
+ m_forward->setImages(images.get(directionString).get("forward").getString("base"),
+ images.get(directionString).get("forward").getString("hover"),
+ images.get(directionString).get("forward").getString("pressed"));
+
+ m_backward->setImages(images.get(directionString).get("backward").getString("base"),
+ images.get(directionString).get("backward").getString("hover"),
+ images.get(directionString).get("backward").getString("pressed"));
+}
+
+Vec2I ScrollBar::size() const {
+ if (m_parent)
+ return m_parent->size();
+ return {};
+}
+
+int ScrollBar::trackSize() const {
+ if (m_parent) {
+ auto scrollArea = convert<ScrollArea>(m_parent);
+ auto size = scrollArea->size();
+ if (m_direction == GuiDirection::Vertical) {
+ if (scrollArea->horizontalScroll()) {
+ return size[1] - (ScrollBarTrackOverhead + ScrollAreaBorder);
+ } else {
+ return size[1] - ScrollBarTrackOverhead;
+ }
+ } else {
+ return size[0] - ScrollBarTrackOverhead;
+ }
+ }
+
+ throw GuiException("Somehow have a Scroll Bar without a parent.");
+}
+
+float ScrollBar::sizeRatio() const {
+ if (m_parent) {
+ auto scrollArea = convert<ScrollArea>(m_parent);
+ if (m_direction == GuiDirection::Vertical) {
+ return scrollArea->contentSize()[1] / (float)(scrollArea->areaSize()[1]);
+ } else {
+ return scrollArea->contentSize()[0] / (float)(scrollArea->areaSize()[0]);
+ }
+ }
+
+ throw GuiException("Somehow have a Scroll Bar without a parent.");
+}
+
+float ScrollBar::scrollRatio() const {
+ if (m_parent) {
+ auto scrollArea = convert<ScrollArea>(m_parent);
+ if (m_direction == GuiDirection::Vertical) {
+ if (scrollArea->maxScrollPosition()[1] == 0)
+ return 0;
+ return scrollArea->scrollOffset()[1] / (float)(scrollArea->maxScrollPosition()[1]);
+ } else {
+ if (scrollArea->maxScrollPosition()[0] == 0)
+ return 0;
+ return scrollArea->scrollOffset()[0] / (float)(scrollArea->maxScrollPosition()[0]);
+ }
+ }
+ return 0;
+}
+
+ButtonWidgetPtr ScrollBar::forwardButton() const {
+ return m_forward;
+}
+
+ButtonWidgetPtr ScrollBar::backwardButton() const {
+ return m_backward;
+}
+
+ScrollThumbPtr ScrollBar::thumb() const {
+ return m_thumb;
+}
+
+void ScrollBar::drawChildren() {
+ if (m_parent) {
+ auto scrollArea = convert<ScrollArea>(m_parent);
+
+ float ratio = sizeRatio();
+ if (ratio < 1)
+ ratio = 1;
+
+ int innerSize = (int)max(0.0f, ceil(trackSize() / ratio));
+ int offsetBegin = (int)ceil((trackSize() - innerSize) * scrollRatio());
+ innerSize += ScrollThumbOverhead;
+
+ if (m_direction == GuiDirection::Vertical) {
+ if (scrollArea->horizontalScroll()) {
+ m_forward->setPosition(m_parent->size() - Vec2I(ScrollAreaBorder, ScrollButtonStackSize));
+ m_backward->setPosition({m_parent->size()[0] - ScrollAreaBorder, ScrollAreaBorder});
+ m_thumb->setPosition({m_parent->size()[0] - ScrollAreaBorder, ScrollAreaBorder + ScrollButtonStackSize + offsetBegin});
+ } else {
+ m_forward->setPosition(m_parent->size() - Vec2I(ScrollAreaBorder, ScrollButtonStackSize));
+ m_backward->setPosition({m_parent->size()[0] - ScrollAreaBorder, 0});
+ m_thumb->setPosition({m_parent->size()[0] - ScrollAreaBorder, ScrollButtonStackSize + offsetBegin});
+ }
+ m_thumb->setSize(Vec2I(m_thumb->baseSize()[0], innerSize));
+ } else {
+ m_forward->setPosition({m_parent->size()[0] - ScrollButtonStackSize, 0});
+ m_backward->setPosition({0, 0});
+ m_thumb->setPosition({ScrollButtonStackSize + offsetBegin, 0});
+ m_thumb->setSize(Vec2I(innerSize, m_thumb->baseSize()[1]));
+ }
+
+ for (auto child : m_members) {
+ child->render(m_drawingArea);
+ }
+ }
+}
+
+Vec2I ScrollBar::offsetFromThumbPosition(Vec2I const& thumbPosition) const {
+ auto scrollArea = convert<ScrollArea>(m_parent);
+ if (m_direction == GuiDirection::Vertical) {
+ int scrollSpan = trackSize() - m_thumb->size()[1];
+ int scrollOffset = clamp((thumbPosition[1] - ScrollButtonStackSize), 0, scrollSpan);
+ float scrollRatio = (float)scrollOffset / (float)scrollSpan;
+ return {scrollArea->scrollOffset()[0], ceil(scrollArea->maxScrollPosition()[1] * scrollRatio)};
+ } else {
+ int scrollSpan = trackSize() - m_thumb->size()[0];
+ int scrollOffset = clamp((thumbPosition[0] - ScrollButtonStackSize), 0, scrollSpan);
+ float scrollRatio = (float)scrollOffset / (float)scrollSpan;
+ return {(int)ceil(scrollArea->maxScrollPosition()[0] * scrollRatio), scrollArea->scrollOffset()[1]};
+ }
+}
+
+ScrollArea::ScrollArea() {
+ auto assets = Root::singleton().assets();
+ m_buttonAdvance = assets->json("/interface.config:scrollArea.buttonAdvance").toInt();
+ m_advanceLimiter = Time::monotonicMilliseconds();
+
+ WidgetCallbackFunc vAdvance = [this](Widget*) { scrollAreaBy({0, advanceFactorHelper()}); };
+ WidgetCallbackFunc vRetreat = [this](Widget*) { scrollAreaBy({0, -advanceFactorHelper()}); };
+ WidgetCallbackFunc hAdvance = [this](Widget*) { scrollAreaBy({advanceFactorHelper(), 0}); };
+ WidgetCallbackFunc hRetreat = [this](Widget*) { scrollAreaBy({-advanceFactorHelper(), 0}); };
+
+ m_vBar = make_shared<ScrollBar>(GuiDirection::Vertical, vAdvance, vRetreat);
+ m_hBar = make_shared<ScrollBar>(GuiDirection::Horizontal, hAdvance, hRetreat);
+
+ m_dragActive = false;
+
+ addChild("vScrollBar", m_vBar);
+ addChild("hScrollBar", m_hBar);
+
+ m_horizontalScroll = false;
+ m_verticalScroll = true;
+}
+
+void ScrollArea::setButtonImages(Json const& images) {
+ m_vBar->setButtonImages(images);
+ m_hBar->setButtonImages(images);
+}
+
+void ScrollArea::setThumbImages(Json const& images) {
+ m_vBar->thumb()->setImages(images);
+ m_hBar->thumb()->setImages(images);
+}
+
+RectI ScrollArea::contentBoundRect() const {
+ RectI res = RectI::null();
+ for (auto child : m_members) {
+ if (child == m_vBar || child == m_hBar) // scroll bars don't count
+ continue;
+ if (!child->active()) // neither do hidden members
+ continue;
+ res.combine(child->relativeBoundRect());
+ }
+ res.setMax({res.max()[0], res.max()[1] + 1});
+ return res;
+}
+
+Vec2I ScrollArea::contentSize() const {
+ return contentBoundRect().size();
+}
+
+Vec2I ScrollArea::areaSize() const {
+ Vec2I s = size();
+ if (horizontalScroll())
+ s[1] -= ScrollAreaBorder;
+ if (verticalScroll())
+ s[0] -= ScrollAreaBorder;
+ return s;
+}
+
+void ScrollArea::scrollAreaBy(Vec2I const& offset) {
+ m_scrollOffset += offset;
+}
+
+Vec2I ScrollArea::scrollOffset() const {
+ return m_scrollOffset;
+}
+
+bool ScrollArea::horizontalScroll() const {
+ return m_horizontalScroll;
+}
+
+void ScrollArea::setHorizontalScroll(bool horizontal) {
+ m_horizontalScroll = horizontal;
+}
+
+bool ScrollArea::verticalScroll() const {
+ return m_verticalScroll;
+}
+
+void ScrollArea::setVerticalScroll(bool vertical) {
+ m_verticalScroll = vertical;
+}
+
+bool ScrollArea::sendEvent(InputEvent const& event) {
+ if (!m_visible)
+ return false;
+
+ if (m_dragActive) {
+ if (event.is<MouseButtonUpEvent>()) {
+ blur();
+ m_dragActive = false;
+ m_vBar->thumb()->setPressed(false);
+ m_hBar->thumb()->setPressed(false);
+ return true;
+ } else if (event.is<MouseMoveEvent>()) {
+ auto thumbDragPosition = *context()->mousePosition(event) - screenPosition() - m_dragOffset;
+ if (m_dragDirection == GuiDirection::Vertical) {
+ m_scrollOffset = m_vBar->offsetFromThumbPosition(thumbDragPosition);
+ } else {
+ m_scrollOffset = m_hBar->offsetFromThumbPosition(thumbDragPosition);
+ }
+ return true;
+ }
+ }
+
+ auto mousePos = context()->mousePosition(event);
+ if (mousePos && !inMember(*mousePos))
+ return false;
+
+ if (event.is<MouseButtonDownEvent>()) {
+ if (m_vBar->thumb()->inMember(*mousePos)) {
+ focus();
+ m_dragOffset = *mousePos - screenPosition() - m_vBar->thumb()->position();
+ m_dragDirection = GuiDirection::Vertical;
+ m_dragActive = true;
+ m_vBar->thumb()->setPressed(true);
+ return true;
+ } else if (m_hBar->thumb()->inMember(*mousePos)) {
+ focus();
+ m_dragOffset = *mousePos - screenPosition() - m_hBar->thumb()->position();
+ m_dragDirection = GuiDirection::Horizontal;
+ m_dragActive = true;
+ m_hBar->thumb()->setPressed(true);
+ return true;
+ }
+ }
+
+ if (Widget::sendEvent(event))
+ return true;
+
+ if (auto mouseWheel = event.ptr<MouseWheelEvent>()) {
+ if (mouseWheel->mouseWheel == MouseWheel::Up)
+ scrollAreaBy({0, m_buttonAdvance * 3});
+ else
+ scrollAreaBy({0, -m_buttonAdvance * 3});
+ return true;
+ }
+
+ return true;
+}
+
+void ScrollArea::update() {
+ if (!m_visible)
+ return;
+
+ auto maxScroll = maxScrollPosition();
+
+ // keep vertical scroll bars same distance from the *top* on resize
+ if (m_verticalScroll && maxScroll != m_lastMaxScroll)
+ m_scrollOffset += (maxScroll - m_lastMaxScroll);
+
+ m_scrollOffset = m_scrollOffset.piecewiseClamp(Vec2I(), maxScroll);
+ m_lastMaxScroll = maxScroll;
+}
+
+Vec2I ScrollArea::maxScrollPosition() const {
+ return Vec2I().piecewiseMax(contentSize() - size());
+}
+
+void ScrollArea::drawChildren() {
+ RectI innerDrawingArea = m_drawingArea;
+
+ if (horizontalScroll())
+ innerDrawingArea.setYMin(ScrollAreaBorder);
+ if (verticalScroll())
+ innerDrawingArea.setXMax(innerDrawingArea.xMax() - ScrollAreaBorder);
+
+ auto contentBoundRect = ScrollArea::contentBoundRect();
+ auto contentOffset = contentBoundRect.min();
+ auto contentSize = contentBoundRect.size();
+
+ auto offset = contentOffset + scrollOffset();
+
+ auto areaSize = ScrollArea::areaSize();
+
+ if (contentSize[1] < areaSize[1])
+ offset[1] = offset[1] - (areaSize[1] - contentSize[1]);
+
+ for (auto child : m_members) {
+ if (child == m_vBar || child == m_hBar)
+ continue;
+ child->setDrawingOffset(-offset);
+ child->render(innerDrawingArea);
+ }
+
+ if (m_horizontalScroll)
+ m_hBar->render(m_drawingArea);
+ if (m_verticalScroll)
+ m_vBar->render(m_drawingArea);
+}
+
+int ScrollArea::advanceFactorHelper() {
+ auto t = Time::monotonicMilliseconds() - m_advanceLimiter;
+ m_advanceLimiter = Time::monotonicMilliseconds();
+ if ((t > ScrollAdvanceTimer) || (t < 0))
+ t = ScrollAdvanceTimer;
+ return (int)std::ceil((m_buttonAdvance * t) / (float)ScrollAdvanceTimer);
+}
+
+}