diff --git a/include/math.hpp b/include/math.hpp index 617f01b3..1e4a9e55 100644 --- a/include/math.hpp +++ b/include/math.hpp @@ -184,6 +184,18 @@ struct Vec { Vec() {} Vec(float x, float y) : x(x), y(y) {} + float get(int index) const { + return (index == 0) ? x : y; + } + float& get(int index) { + return (index == 0) ? x : y; + } + void set(int index, float value) { + if (index == 0) + x = value; + else + y = value; + } /** Negates the vector. Equivalent to a reflection across the `y = -x` line. */ diff --git a/include/ui/ScrollBar.hpp b/include/ui/ScrollBar.hpp index daa3d1b4..94786320 100644 --- a/include/ui/ScrollBar.hpp +++ b/include/ui/ScrollBar.hpp @@ -9,19 +9,17 @@ namespace ui { /** Parent must be a ScrollWidget */ struct ScrollBar : widget::OpaqueWidget { - enum Orientation { - VERTICAL, - HORIZONTAL - }; - Orientation orientation; - float offset = 0.0; - float size = 0.0; + struct Internal; + Internal* internal; + + bool vertical = false; ScrollBar(); + ~ScrollBar(); void draw(const DrawArgs& args) override; void onDragStart(const event::DragStart& e) override; - void onDragMove(const event::DragMove& 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 48fb147c..f6e345d0 100644 --- a/include/ui/ScrollWidget.hpp +++ b/include/ui/ScrollWidget.hpp @@ -10,10 +10,15 @@ namespace ui { /** Handles a container with ScrollBar */ struct ScrollWidget : widget::OpaqueWidget { + struct Internal; + Internal* internal; + widget::Widget* container; ScrollBar* horizontalScrollBar; ScrollBar* verticalScrollBar; + math::Vec offset; + math::Rect containerBox; ScrollWidget(); void scrollTo(math::Rect r); diff --git a/include/widget/Widget.hpp b/include/widget/Widget.hpp index 64135dda..0e5a37ef 100644 --- a/include/widget/Widget.hpp +++ b/include/widget/Widget.hpp @@ -33,23 +33,21 @@ struct Widget { virtual ~Widget(); + math::Rect getBox(); void setBox(math::Rect box); - math::Rect getBox() { - return box; - } + math::Vec getPosition(); void setPosition(math::Vec pos); - math::Vec getPosition() { - return box.pos; - } + math::Vec getSize(); void setSize(math::Vec size); - math::Vec getSize() { - return box.size; + bool isVisible(); + void setVisible(bool visible); + void show() { + setVisible(true); } - void show(); - void hide(); - bool isVisible() { - return visible; + void hide() { + setVisible(false); } + void requestDelete(); virtual math::Rect getChildrenBoundingBox(); diff --git a/src/ui/ScrollBar.cpp b/src/ui/ScrollBar.cpp index fa342132..7c2ac64b 100644 --- a/src/ui/ScrollBar.cpp +++ b/src/ui/ScrollBar.cpp @@ -8,40 +8,63 @@ namespace rack { namespace ui { +// Internal not currently used + + ScrollBar::ScrollBar() { box.size = math::Vec(BND_SCROLLBAR_WIDTH, BND_SCROLLBAR_HEIGHT); } + +ScrollBar::~ScrollBar() { +} + + void ScrollBar::draw(const DrawArgs& args) { + ScrollWidget* sw = dynamic_cast(parent); + assert(sw); + BNDwidgetState state = BND_DEFAULT; - if (APP->event->hoveredWidget == this) + if (APP->event->getHoveredWidget() == this) state = BND_HOVER; - if (APP->event->draggedWidget == this) + if (APP->event->getDraggedWidget() == this) state = BND_ACTIVE; - bndScrollBar(args.vg, 0.0, 0.0, box.size.x, box.size.y, state, offset, size); + 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); } + void ScrollBar::onDragStart(const event::DragStart& e) { - if (e.button != GLFW_MOUSE_BUTTON_LEFT) - return; + if (e.button == GLFW_MOUSE_BUTTON_LEFT) { + APP->window->cursorLock(); + } +} + - APP->window->cursorLock(); +void ScrollBar::onDragEnd(const event::DragEnd& e) { + if (e.button == GLFW_MOUSE_BUTTON_LEFT) { + APP->window->cursorUnlock(); + } } + void ScrollBar::onDragMove(const event::DragMove& e) { - const float sensitivity = 1.f; + ScrollWidget* sw = dynamic_cast(parent); + assert(sw); - ScrollWidget* scrollWidget = dynamic_cast(parent); - assert(scrollWidget); - if (orientation == HORIZONTAL) - scrollWidget->offset.x += sensitivity * e.mouseDelta.x; - else - scrollWidget->offset.y += sensitivity * e.mouseDelta.y; -} + // 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); -void ScrollBar::onDragEnd(const event::DragEnd& e) { - APP->window->cursorUnlock(); + const float sensitivity = 1.f; + float offsetDelta = e.mouseDelta.get(vertical); + offsetDelta *= sensitivity; + sw->offset.get(vertical) += offsetDelta; } diff --git a/src/ui/ScrollWidget.cpp b/src/ui/ScrollWidget.cpp index c9fc3d2a..9b7c08a3 100644 --- a/src/ui/ScrollWidget.cpp +++ b/src/ui/ScrollWidget.cpp @@ -6,37 +6,43 @@ namespace rack { namespace ui { +// Internal not currently used + + ScrollWidget::ScrollWidget() { container = new widget::Widget; addChild(container); horizontalScrollBar = new ScrollBar; - horizontalScrollBar->orientation = ScrollBar::HORIZONTAL; - horizontalScrollBar->visible = false; + horizontalScrollBar->vertical = false; + horizontalScrollBar->hide(); addChild(horizontalScrollBar); verticalScrollBar = new ScrollBar; - verticalScrollBar->orientation = ScrollBar::VERTICAL; - verticalScrollBar->visible = false; + verticalScrollBar->vertical = true; + verticalScrollBar->hide(); addChild(verticalScrollBar); } + void ScrollWidget::scrollTo(math::Rect r) { math::Rect bound = math::Rect::fromMinMax(r.getBottomRight().minus(box.size), r.pos); offset = offset.clampSafe(bound); } + void ScrollWidget::draw(const DrawArgs& args) { nvgScissor(args.vg, RECT_ARGS(args.clipBox)); Widget::draw(args); nvgResetScissor(args.vg); } + void ScrollWidget::step() { Widget::step(); // Clamp scroll offset - math::Rect containerBox = container->getChildrenBoundingBox(); + containerBox = container->getChildrenBoundingBox(); math::Rect offsetBounds = containerBox; offsetBounds.size = offsetBounds.size.minus(box.size); offset = offset.clamp(offsetBounds); @@ -44,25 +50,19 @@ void ScrollWidget::step() { // Update the container's position from the offset container->box.pos = offset.neg().round(); - // Update scrollbar offsets and sizes - math::Vec scrollbarOffset = offset.minus(containerBox.pos).div(offsetBounds.size); - math::Vec scrollbarSize = box.size.div(containerBox.size); - - horizontalScrollBar->visible = (0.0 < scrollbarSize.x && scrollbarSize.x < 1.0); - verticalScrollBar->visible = (0.0 < scrollbarSize.y && scrollbarSize.y < 1.0); - horizontalScrollBar->offset = scrollbarOffset.x; - verticalScrollBar->offset = scrollbarOffset.y; - horizontalScrollBar->size = scrollbarSize.x; - verticalScrollBar->size = scrollbarSize.y; + // Make scrollbars visible only if there is a positive range to scroll. + horizontalScrollBar->setVisible(offsetBounds.size.x > 0.f); + verticalScrollBar->setVisible(offsetBounds.size.y > 0.f); // Reposition and resize scroll bars math::Vec inner = box.size.minus(math::Vec(verticalScrollBar->box.size.x, horizontalScrollBar->box.size.y)); horizontalScrollBar->box.pos.y = inner.y; verticalScrollBar->box.pos.x = inner.x; - horizontalScrollBar->box.size.x = verticalScrollBar->visible ? inner.x : box.size.x; - verticalScrollBar->box.size.y = horizontalScrollBar->visible ? inner.y : box.size.y; + horizontalScrollBar->box.size.x = verticalScrollBar->isVisible() ? inner.x : box.size.x; + verticalScrollBar->box.size.y = horizontalScrollBar->isVisible() ? inner.y : box.size.y; } + void ScrollWidget::onButton(const event::Button& e) { OpaqueWidget::onButton(e); if (e.isConsumed()) @@ -77,12 +77,14 @@ void ScrollWidget::onButton(const event::Button& e) { } } + void ScrollWidget::onDragStart(const event::DragStart& e) { if (e.button == GLFW_MOUSE_BUTTON_LEFT || e.button == GLFW_MOUSE_BUTTON_MIDDLE) { e.consume(this); } } + void ScrollWidget::onDragMove(const event::DragMove& e) { // Scroll only if the scrollbars are visible if (!(horizontalScrollBar->visible || verticalScrollBar->visible)) @@ -91,6 +93,7 @@ void ScrollWidget::onDragMove(const event::DragMove& e) { offset = offset.minus(e.mouseDelta); } + void ScrollWidget::onHoverScroll(const event::HoverScroll& e) { OpaqueWidget::onHoverScroll(e); if (e.isConsumed()) @@ -112,6 +115,7 @@ void ScrollWidget::onHoverScroll(const event::HoverScroll& e) { e.consume(this); } + void ScrollWidget::onHoverKey(const event::HoverKey& e) { OpaqueWidget::onHoverKey(e); if (e.isConsumed()) diff --git a/src/widget/Widget.cpp b/src/widget/Widget.cpp index d69e2904..35d4f089 100644 --- a/src/widget/Widget.cpp +++ b/src/widget/Widget.cpp @@ -14,11 +14,23 @@ Widget::~Widget() { clearChildren(); } + +math::Rect Widget::getBox() { + return box; +} + + void Widget::setBox(math::Rect box) { setPosition(box.pos); setSize(box.size); } + +math::Vec Widget::getPosition() { + return box.pos; +} + + void Widget::setPosition(math::Vec pos) { if (pos.isEqual(box.pos)) return; @@ -28,6 +40,12 @@ void Widget::setPosition(math::Vec pos) { onReposition(eReposition); } + +math::Vec Widget::getSize() { + return box.size; +} + + void Widget::setSize(math::Vec size) { if (size.isEqual(box.size)) return; @@ -37,28 +55,34 @@ void Widget::setSize(math::Vec size) { onResize(eResize); } -void Widget::show() { - if (visible) - return; - visible = true; - // Trigger Show event - event::Show eShow; - onShow(eShow); + +bool Widget::isVisible() { + return visible; } -void Widget::hide() { - if (!visible) + +void Widget::setVisible(bool visible) { + if (visible == this->visible) return; - visible = false; - // Trigger Hide event - event::Hide eHide; - onHide(eHide); + this->visible = visible; + if (visible) { + // Trigger Show event + event::Show eShow; + onShow(eShow); + } + else { + // Trigger Hide event + event::Hide eHide; + onHide(eHide); + } } + void Widget::requestDelete() { requestedDelete = true; } + math::Rect Widget::getChildrenBoundingBox() { math::Vec min = math::Vec(INFINITY, INFINITY); math::Vec max = math::Vec(-INFINITY, -INFINITY); @@ -71,6 +95,7 @@ math::Rect Widget::getChildrenBoundingBox() { return math::Rect::fromMinMax(min, max); } + math::Vec Widget::getRelativeOffset(math::Vec v, Widget* relative) { if (this == relative) { return v; @@ -82,6 +107,7 @@ math::Vec Widget::getRelativeOffset(math::Vec v, Widget* relative) { return v; } + math::Rect Widget::getViewport(math::Rect r) { math::Rect bound; if (parent) { @@ -94,6 +120,7 @@ math::Rect Widget::getViewport(math::Rect r) { return r.clamp(bound); } + void Widget::addChild(Widget* child) { assert(child); assert(!child->parent); @@ -104,6 +131,7 @@ void Widget::addChild(Widget* child) { child->onAdd(eAdd); } + void Widget::addChildBottom(Widget* child) { assert(child); assert(!child->parent); @@ -114,6 +142,7 @@ void Widget::addChildBottom(Widget* child) { child->onAdd(eAdd); } + void Widget::removeChild(Widget* child) { assert(child); // Make sure `this` is the child's parent @@ -131,6 +160,7 @@ void Widget::removeChild(Widget* child) { child->parent = NULL; } + void Widget::clearChildren() { for (Widget* child : children) { // Trigger Remove event @@ -143,6 +173,7 @@ void Widget::clearChildren() { children.clear(); } + void Widget::step() { for (auto it = children.begin(); it != children.end();) { Widget* child = *it; @@ -163,6 +194,7 @@ void Widget::step() { } } + void Widget::draw(const DrawArgs& args) { // Iterate children for (Widget* child : children) {