From 2d253fe2fb97e3c5bea7b9ef679d1efe45078cfe Mon Sep 17 00:00:00 2001 From: Andrew Belt Date: Tue, 18 Aug 2020 15:02:01 -0400 Subject: [PATCH] Make scrollbar mouse interaction similar to modern OS behavior --- include/ui/ScrollBar.hpp | 1 + include/ui/ScrollWidget.hpp | 6 +++++ src/ui/ScrollBar.cpp | 51 ++++++++++++++++++++++++------------- src/ui/ScrollWidget.cpp | 31 +++++++++++++++++----- 4 files changed, 65 insertions(+), 24 deletions(-) diff --git a/include/ui/ScrollBar.hpp b/include/ui/ScrollBar.hpp index 94786320..0ce57659 100644 --- a/include/ui/ScrollBar.hpp +++ b/include/ui/ScrollBar.hpp @@ -17,6 +17,7 @@ struct ScrollBar : widget::OpaqueWidget { ScrollBar(); ~ScrollBar(); void draw(const DrawArgs& args) override; + void onButton(const event::Button& e) override; void onDragStart(const event::DragStart& e) override; void onDragEnd(const event::DragEnd& e) override; void onDragMove(const event::DragMove& e) override; diff --git a/include/ui/ScrollWidget.hpp b/include/ui/ScrollWidget.hpp index f6e345d0..12965b61 100644 --- a/include/ui/ScrollWidget.hpp +++ b/include/ui/ScrollWidget.hpp @@ -22,6 +22,12 @@ struct ScrollWidget : widget::OpaqueWidget { ScrollWidget(); void scrollTo(math::Rect r); + /** Returns the bound of allowed `offset` values in pixels. */ + math::Rect getContainerOffsetBound(); + /** Returns the handle position relative to the scrollbar. [0, 1]. */ + math::Vec getHandleOffset(); + /** Returns the handle size relative to the scrollbar. [0, 1]. */ + math::Vec getHandleSize(); void draw(const DrawArgs& args) override; void step() override; void onButton(const event::Button& e) override; diff --git a/src/ui/ScrollBar.cpp b/src/ui/ScrollBar.cpp index 7c2ac64b..d22ad3b1 100644 --- a/src/ui/ScrollBar.cpp +++ b/src/ui/ScrollBar.cpp @@ -30,26 +30,40 @@ void ScrollBar::draw(const DrawArgs& args) { if (APP->event->getDraggedWidget() == this) state = BND_ACTIVE; - float offsetBound = sw->containerBox.size.get(vertical) - sw->box.size.get(vertical); - // The handle position relative to the scrollbar. [0, 1] - float scrollBarOffset = (sw->offset.get(vertical) - sw->containerBox.pos.get(vertical)) / offsetBound; - // The handle size relative to the scrollbar. [0, 1] - float scrollBarSize = sw->box.size.get(vertical) / sw->containerBox.size.get(vertical); - bndScrollBar(args.vg, 0.0, 0.0, box.size.x, box.size.y, state, scrollBarOffset, scrollBarSize); + float handleOffset = sw->getHandleOffset().get(vertical); + float handleSize = sw->getHandleSize().get(vertical); + bndScrollBar(args.vg, 0.0, 0.0, box.size.x, box.size.y, state, handleOffset, handleSize); } -void ScrollBar::onDragStart(const event::DragStart& e) { - if (e.button == GLFW_MOUSE_BUTTON_LEFT) { - APP->window->cursorLock(); +void ScrollBar::onButton(const event::Button& e) { + if (e.button == GLFW_MOUSE_BUTTON_LEFT && e.action == GLFW_PRESS) { + ScrollWidget* sw = dynamic_cast(parent); + assert(sw); + + float pos = e.pos.get(vertical); + pos /= box.size.get(vertical); + float handleOffset = sw->getHandleOffset().get(vertical); + float handleSize = sw->getHandleSize().get(vertical); + float handlePos = math::rescale(handleOffset, 0.f, 1.f, handleSize / 2.f, 1.f - handleSize / 2.f); + math::Rect offsetBound = sw->getContainerOffsetBound(); + + // Check if user clicked on handle + if (std::fabs(pos - handlePos) > handleSize / 2.f) { + // Jump to absolute position of the handle + float offset = math::rescale(pos, handleSize / 2.f, 1.f - handleSize / 2.f, 0.f, 1.f); + sw->offset.get(vertical) = sw->containerBox.pos.get(vertical) + offset * (sw->containerBox.size.get(vertical) - sw->box.size.get(vertical)); + } } + OpaqueWidget::onButton(e); +} + + +void ScrollBar::onDragStart(const event::DragStart& e) { } void ScrollBar::onDragEnd(const event::DragEnd& e) { - if (e.button == GLFW_MOUSE_BUTTON_LEFT) { - APP->window->cursorUnlock(); - } } @@ -57,13 +71,14 @@ void ScrollBar::onDragMove(const event::DragMove& e) { ScrollWidget* sw = dynamic_cast(parent); assert(sw); - // TODO - // float offsetBound = sw->containerBox.size.get(vertical) - sw->box.size.get(vertical); - // float scrollBarSize = sw->box.size.get(vertical) / sw->containerBox.size.get(vertical); + // Move handle absolutely. + float mouseDelta = e.mouseDelta.get(vertical); + mouseDelta /= getAbsoluteZoom(); - const float sensitivity = 1.f; - float offsetDelta = e.mouseDelta.get(vertical); - offsetDelta *= sensitivity; + float handleSize = sw->getHandleSize().get(vertical); + float handleBound = (1.f - handleSize) * box.size.get(vertical); + float offsetBound = sw->getContainerOffsetBound().size.get(vertical); + float offsetDelta = mouseDelta * offsetBound / handleBound; sw->offset.get(vertical) += offsetDelta; } diff --git a/src/ui/ScrollWidget.cpp b/src/ui/ScrollWidget.cpp index 9b7c08a3..515e9c74 100644 --- a/src/ui/ScrollWidget.cpp +++ b/src/ui/ScrollWidget.cpp @@ -31,6 +31,24 @@ void ScrollWidget::scrollTo(math::Rect r) { } +math::Rect ScrollWidget::getContainerOffsetBound() { + math::Rect r; + r.pos = containerBox.pos; + r.size = containerBox.size.minus(box.size); + return r; +} + + +math::Vec ScrollWidget::getHandleOffset() { + return offset.minus(containerBox.pos).div(getContainerOffsetBound().size); +} + + +math::Vec ScrollWidget::getHandleSize() { + return box.size.div(containerBox.size); +} + + void ScrollWidget::draw(const DrawArgs& args) { nvgScissor(args.vg, RECT_ARGS(args.clipBox)); Widget::draw(args); @@ -41,10 +59,11 @@ void ScrollWidget::draw(const DrawArgs& args) { void ScrollWidget::step() { Widget::step(); - // Clamp scroll offset + // Set containerBox cache containerBox = container->getChildrenBoundingBox(); - math::Rect offsetBounds = containerBox; - offsetBounds.size = offsetBounds.size.minus(box.size); + + // Clamp scroll offset + math::Rect offsetBounds = getContainerOffsetBound(); offset = offset.clamp(offsetBounds); // Update the container's position from the offset @@ -69,7 +88,7 @@ void ScrollWidget::onButton(const event::Button& e) { return; // Consume right button only if the scrollbars are visible - if (!(horizontalScrollBar->visible || verticalScrollBar->visible)) + if (!(horizontalScrollBar->isVisible() || verticalScrollBar->isVisible())) return; if (e.button == GLFW_MOUSE_BUTTON_MIDDLE) { @@ -87,7 +106,7 @@ void ScrollWidget::onDragStart(const event::DragStart& e) { void ScrollWidget::onDragMove(const event::DragMove& e) { // Scroll only if the scrollbars are visible - if (!(horizontalScrollBar->visible || verticalScrollBar->visible)) + if (!(horizontalScrollBar->isVisible() || verticalScrollBar->isVisible())) return; offset = offset.minus(e.mouseDelta); @@ -100,7 +119,7 @@ void ScrollWidget::onHoverScroll(const event::HoverScroll& e) { return; // Scroll only if the scrollbars are visible - if (!(horizontalScrollBar->visible || verticalScrollBar->visible)) + if (!(horizontalScrollBar->isVisible() || verticalScrollBar->isVisible())) return; math::Vec scrollDelta = e.scrollDelta;