| @@ -184,6 +184,18 @@ struct Vec { | |||||
| Vec() {} | Vec() {} | ||||
| Vec(float x, float y) : x(x), y(y) {} | 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. | /** Negates the vector. | ||||
| Equivalent to a reflection across the `y = -x` line. | Equivalent to a reflection across the `y = -x` line. | ||||
| */ | */ | ||||
| @@ -9,19 +9,17 @@ namespace ui { | |||||
| /** Parent must be a ScrollWidget */ | /** Parent must be a ScrollWidget */ | ||||
| struct ScrollBar : widget::OpaqueWidget { | 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(); | ||||
| ~ScrollBar(); | |||||
| void draw(const DrawArgs& args) override; | void draw(const DrawArgs& args) override; | ||||
| void onDragStart(const event::DragStart& e) override; | void onDragStart(const event::DragStart& e) override; | ||||
| void onDragMove(const event::DragMove& e) override; | |||||
| void onDragEnd(const event::DragEnd& e) override; | void onDragEnd(const event::DragEnd& e) override; | ||||
| void onDragMove(const event::DragMove& e) override; | |||||
| }; | }; | ||||
| @@ -10,10 +10,15 @@ namespace ui { | |||||
| /** Handles a container with ScrollBar */ | /** Handles a container with ScrollBar */ | ||||
| struct ScrollWidget : widget::OpaqueWidget { | struct ScrollWidget : widget::OpaqueWidget { | ||||
| struct Internal; | |||||
| Internal* internal; | |||||
| widget::Widget* container; | widget::Widget* container; | ||||
| ScrollBar* horizontalScrollBar; | ScrollBar* horizontalScrollBar; | ||||
| ScrollBar* verticalScrollBar; | ScrollBar* verticalScrollBar; | ||||
| math::Vec offset; | math::Vec offset; | ||||
| math::Rect containerBox; | |||||
| ScrollWidget(); | ScrollWidget(); | ||||
| void scrollTo(math::Rect r); | void scrollTo(math::Rect r); | ||||
| @@ -33,23 +33,21 @@ struct Widget { | |||||
| virtual ~Widget(); | virtual ~Widget(); | ||||
| math::Rect getBox(); | |||||
| void setBox(math::Rect box); | void setBox(math::Rect box); | ||||
| math::Rect getBox() { | |||||
| return box; | |||||
| } | |||||
| math::Vec getPosition(); | |||||
| void setPosition(math::Vec pos); | void setPosition(math::Vec pos); | ||||
| math::Vec getPosition() { | |||||
| return box.pos; | |||||
| } | |||||
| math::Vec getSize(); | |||||
| void setSize(math::Vec size); | 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(); | void requestDelete(); | ||||
| virtual math::Rect getChildrenBoundingBox(); | virtual math::Rect getChildrenBoundingBox(); | ||||
| @@ -8,40 +8,63 @@ namespace rack { | |||||
| namespace ui { | namespace ui { | ||||
| // Internal not currently used | |||||
| ScrollBar::ScrollBar() { | ScrollBar::ScrollBar() { | ||||
| box.size = math::Vec(BND_SCROLLBAR_WIDTH, BND_SCROLLBAR_HEIGHT); | box.size = math::Vec(BND_SCROLLBAR_WIDTH, BND_SCROLLBAR_HEIGHT); | ||||
| } | } | ||||
| ScrollBar::~ScrollBar() { | |||||
| } | |||||
| void ScrollBar::draw(const DrawArgs& args) { | void ScrollBar::draw(const DrawArgs& args) { | ||||
| ScrollWidget* sw = dynamic_cast<ScrollWidget*>(parent); | |||||
| assert(sw); | |||||
| BNDwidgetState state = BND_DEFAULT; | BNDwidgetState state = BND_DEFAULT; | ||||
| if (APP->event->hoveredWidget == this) | |||||
| if (APP->event->getHoveredWidget() == this) | |||||
| state = BND_HOVER; | state = BND_HOVER; | ||||
| if (APP->event->draggedWidget == this) | |||||
| if (APP->event->getDraggedWidget() == this) | |||||
| state = BND_ACTIVE; | 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) { | 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) { | void ScrollBar::onDragMove(const event::DragMove& e) { | ||||
| const float sensitivity = 1.f; | |||||
| ScrollWidget* sw = dynamic_cast<ScrollWidget*>(parent); | |||||
| assert(sw); | |||||
| ScrollWidget* scrollWidget = dynamic_cast<ScrollWidget*>(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; | |||||
| } | } | ||||
| @@ -6,37 +6,43 @@ namespace rack { | |||||
| namespace ui { | namespace ui { | ||||
| // Internal not currently used | |||||
| ScrollWidget::ScrollWidget() { | ScrollWidget::ScrollWidget() { | ||||
| container = new widget::Widget; | container = new widget::Widget; | ||||
| addChild(container); | addChild(container); | ||||
| horizontalScrollBar = new ScrollBar; | horizontalScrollBar = new ScrollBar; | ||||
| horizontalScrollBar->orientation = ScrollBar::HORIZONTAL; | |||||
| horizontalScrollBar->visible = false; | |||||
| horizontalScrollBar->vertical = false; | |||||
| horizontalScrollBar->hide(); | |||||
| addChild(horizontalScrollBar); | addChild(horizontalScrollBar); | ||||
| verticalScrollBar = new ScrollBar; | verticalScrollBar = new ScrollBar; | ||||
| verticalScrollBar->orientation = ScrollBar::VERTICAL; | |||||
| verticalScrollBar->visible = false; | |||||
| verticalScrollBar->vertical = true; | |||||
| verticalScrollBar->hide(); | |||||
| addChild(verticalScrollBar); | addChild(verticalScrollBar); | ||||
| } | } | ||||
| void ScrollWidget::scrollTo(math::Rect r) { | void ScrollWidget::scrollTo(math::Rect r) { | ||||
| math::Rect bound = math::Rect::fromMinMax(r.getBottomRight().minus(box.size), r.pos); | math::Rect bound = math::Rect::fromMinMax(r.getBottomRight().minus(box.size), r.pos); | ||||
| offset = offset.clampSafe(bound); | offset = offset.clampSafe(bound); | ||||
| } | } | ||||
| void ScrollWidget::draw(const DrawArgs& args) { | void ScrollWidget::draw(const DrawArgs& args) { | ||||
| nvgScissor(args.vg, RECT_ARGS(args.clipBox)); | nvgScissor(args.vg, RECT_ARGS(args.clipBox)); | ||||
| Widget::draw(args); | Widget::draw(args); | ||||
| nvgResetScissor(args.vg); | nvgResetScissor(args.vg); | ||||
| } | } | ||||
| void ScrollWidget::step() { | void ScrollWidget::step() { | ||||
| Widget::step(); | Widget::step(); | ||||
| // Clamp scroll offset | // Clamp scroll offset | ||||
| math::Rect containerBox = container->getChildrenBoundingBox(); | |||||
| containerBox = container->getChildrenBoundingBox(); | |||||
| math::Rect offsetBounds = containerBox; | math::Rect offsetBounds = containerBox; | ||||
| offsetBounds.size = offsetBounds.size.minus(box.size); | offsetBounds.size = offsetBounds.size.minus(box.size); | ||||
| offset = offset.clamp(offsetBounds); | offset = offset.clamp(offsetBounds); | ||||
| @@ -44,25 +50,19 @@ void ScrollWidget::step() { | |||||
| // Update the container's position from the offset | // Update the container's position from the offset | ||||
| container->box.pos = offset.neg().round(); | 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 | // Reposition and resize scroll bars | ||||
| math::Vec inner = box.size.minus(math::Vec(verticalScrollBar->box.size.x, horizontalScrollBar->box.size.y)); | math::Vec inner = box.size.minus(math::Vec(verticalScrollBar->box.size.x, horizontalScrollBar->box.size.y)); | ||||
| horizontalScrollBar->box.pos.y = inner.y; | horizontalScrollBar->box.pos.y = inner.y; | ||||
| verticalScrollBar->box.pos.x = inner.x; | 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) { | void ScrollWidget::onButton(const event::Button& e) { | ||||
| OpaqueWidget::onButton(e); | OpaqueWidget::onButton(e); | ||||
| if (e.isConsumed()) | if (e.isConsumed()) | ||||
| @@ -77,12 +77,14 @@ void ScrollWidget::onButton(const event::Button& e) { | |||||
| } | } | ||||
| } | } | ||||
| void ScrollWidget::onDragStart(const event::DragStart& e) { | void ScrollWidget::onDragStart(const event::DragStart& e) { | ||||
| if (e.button == GLFW_MOUSE_BUTTON_LEFT || e.button == GLFW_MOUSE_BUTTON_MIDDLE) { | if (e.button == GLFW_MOUSE_BUTTON_LEFT || e.button == GLFW_MOUSE_BUTTON_MIDDLE) { | ||||
| e.consume(this); | e.consume(this); | ||||
| } | } | ||||
| } | } | ||||
| void ScrollWidget::onDragMove(const event::DragMove& e) { | void ScrollWidget::onDragMove(const event::DragMove& e) { | ||||
| // Scroll only if the scrollbars are visible | // Scroll only if the scrollbars are visible | ||||
| if (!(horizontalScrollBar->visible || verticalScrollBar->visible)) | if (!(horizontalScrollBar->visible || verticalScrollBar->visible)) | ||||
| @@ -91,6 +93,7 @@ void ScrollWidget::onDragMove(const event::DragMove& e) { | |||||
| offset = offset.minus(e.mouseDelta); | offset = offset.minus(e.mouseDelta); | ||||
| } | } | ||||
| void ScrollWidget::onHoverScroll(const event::HoverScroll& e) { | void ScrollWidget::onHoverScroll(const event::HoverScroll& e) { | ||||
| OpaqueWidget::onHoverScroll(e); | OpaqueWidget::onHoverScroll(e); | ||||
| if (e.isConsumed()) | if (e.isConsumed()) | ||||
| @@ -112,6 +115,7 @@ void ScrollWidget::onHoverScroll(const event::HoverScroll& e) { | |||||
| e.consume(this); | e.consume(this); | ||||
| } | } | ||||
| void ScrollWidget::onHoverKey(const event::HoverKey& e) { | void ScrollWidget::onHoverKey(const event::HoverKey& e) { | ||||
| OpaqueWidget::onHoverKey(e); | OpaqueWidget::onHoverKey(e); | ||||
| if (e.isConsumed()) | if (e.isConsumed()) | ||||
| @@ -14,11 +14,23 @@ Widget::~Widget() { | |||||
| clearChildren(); | clearChildren(); | ||||
| } | } | ||||
| math::Rect Widget::getBox() { | |||||
| return box; | |||||
| } | |||||
| void Widget::setBox(math::Rect box) { | void Widget::setBox(math::Rect box) { | ||||
| setPosition(box.pos); | setPosition(box.pos); | ||||
| setSize(box.size); | setSize(box.size); | ||||
| } | } | ||||
| math::Vec Widget::getPosition() { | |||||
| return box.pos; | |||||
| } | |||||
| void Widget::setPosition(math::Vec pos) { | void Widget::setPosition(math::Vec pos) { | ||||
| if (pos.isEqual(box.pos)) | if (pos.isEqual(box.pos)) | ||||
| return; | return; | ||||
| @@ -28,6 +40,12 @@ void Widget::setPosition(math::Vec pos) { | |||||
| onReposition(eReposition); | onReposition(eReposition); | ||||
| } | } | ||||
| math::Vec Widget::getSize() { | |||||
| return box.size; | |||||
| } | |||||
| void Widget::setSize(math::Vec size) { | void Widget::setSize(math::Vec size) { | ||||
| if (size.isEqual(box.size)) | if (size.isEqual(box.size)) | ||||
| return; | return; | ||||
| @@ -37,28 +55,34 @@ void Widget::setSize(math::Vec size) { | |||||
| onResize(eResize); | 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; | 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() { | void Widget::requestDelete() { | ||||
| requestedDelete = true; | requestedDelete = true; | ||||
| } | } | ||||
| math::Rect Widget::getChildrenBoundingBox() { | math::Rect Widget::getChildrenBoundingBox() { | ||||
| math::Vec min = math::Vec(INFINITY, INFINITY); | math::Vec min = math::Vec(INFINITY, INFINITY); | ||||
| math::Vec max = 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); | return math::Rect::fromMinMax(min, max); | ||||
| } | } | ||||
| math::Vec Widget::getRelativeOffset(math::Vec v, Widget* relative) { | math::Vec Widget::getRelativeOffset(math::Vec v, Widget* relative) { | ||||
| if (this == relative) { | if (this == relative) { | ||||
| return v; | return v; | ||||
| @@ -82,6 +107,7 @@ math::Vec Widget::getRelativeOffset(math::Vec v, Widget* relative) { | |||||
| return v; | return v; | ||||
| } | } | ||||
| math::Rect Widget::getViewport(math::Rect r) { | math::Rect Widget::getViewport(math::Rect r) { | ||||
| math::Rect bound; | math::Rect bound; | ||||
| if (parent) { | if (parent) { | ||||
| @@ -94,6 +120,7 @@ math::Rect Widget::getViewport(math::Rect r) { | |||||
| return r.clamp(bound); | return r.clamp(bound); | ||||
| } | } | ||||
| void Widget::addChild(Widget* child) { | void Widget::addChild(Widget* child) { | ||||
| assert(child); | assert(child); | ||||
| assert(!child->parent); | assert(!child->parent); | ||||
| @@ -104,6 +131,7 @@ void Widget::addChild(Widget* child) { | |||||
| child->onAdd(eAdd); | child->onAdd(eAdd); | ||||
| } | } | ||||
| void Widget::addChildBottom(Widget* child) { | void Widget::addChildBottom(Widget* child) { | ||||
| assert(child); | assert(child); | ||||
| assert(!child->parent); | assert(!child->parent); | ||||
| @@ -114,6 +142,7 @@ void Widget::addChildBottom(Widget* child) { | |||||
| child->onAdd(eAdd); | child->onAdd(eAdd); | ||||
| } | } | ||||
| void Widget::removeChild(Widget* child) { | void Widget::removeChild(Widget* child) { | ||||
| assert(child); | assert(child); | ||||
| // Make sure `this` is the child's parent | // Make sure `this` is the child's parent | ||||
| @@ -131,6 +160,7 @@ void Widget::removeChild(Widget* child) { | |||||
| child->parent = NULL; | child->parent = NULL; | ||||
| } | } | ||||
| void Widget::clearChildren() { | void Widget::clearChildren() { | ||||
| for (Widget* child : children) { | for (Widget* child : children) { | ||||
| // Trigger Remove event | // Trigger Remove event | ||||
| @@ -143,6 +173,7 @@ void Widget::clearChildren() { | |||||
| children.clear(); | children.clear(); | ||||
| } | } | ||||
| void Widget::step() { | void Widget::step() { | ||||
| for (auto it = children.begin(); it != children.end();) { | for (auto it = children.begin(); it != children.end();) { | ||||
| Widget* child = *it; | Widget* child = *it; | ||||
| @@ -163,6 +194,7 @@ void Widget::step() { | |||||
| } | } | ||||
| } | } | ||||
| void Widget::draw(const DrawArgs& args) { | void Widget::draw(const DrawArgs& args) { | ||||
| // Iterate children | // Iterate children | ||||
| for (Widget* child : children) { | for (Widget* child : children) { | ||||