diff options
author | Kae <80987908+Novaenia@users.noreply.github.com> | 2023-06-20 14:33:09 +1000 |
---|---|---|
committer | Kae <80987908+Novaenia@users.noreply.github.com> | 2023-06-20 14:33:09 +1000 |
commit | 6352e8e3196f78388b6c771073f9e03eaa612673 (patch) | |
tree | e23772f79a7fbc41bc9108951e9e136857484bf4 /source/windowing/StarScrollArea.cpp | |
parent | 6741a057e5639280d85d0f88ba26f000baa58f61 (diff) |
everything everywhere
all at once
Diffstat (limited to 'source/windowing/StarScrollArea.cpp')
-rw-r--r-- | source/windowing/StarScrollArea.cpp | 445 |
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); +} + +} |