| @@ -147,6 +147,8 @@ inline void cmultf(float *cr, float *ci, float ar, float ai, float br, float bi) | |||||
| // 2D float vector | // 2D float vector | ||||
| //////////////////// | //////////////////// | ||||
| struct Rect; | |||||
| struct Vec { | struct Vec { | ||||
| float x, y; | float x, y; | ||||
| @@ -186,6 +188,7 @@ struct Vec { | |||||
| bool isZero() { | bool isZero() { | ||||
| return x == 0.0 && y == 0.0; | return x == 0.0 && y == 0.0; | ||||
| } | } | ||||
| Vec clamp(Rect bound); | |||||
| }; | }; | ||||
| @@ -237,4 +240,11 @@ struct Rect { | |||||
| }; | }; | ||||
| inline Vec Vec::clamp(Rect bound) { | |||||
| return Vec( | |||||
| clampf(x, bound.pos.x, bound.pos.x + bound.size.x), | |||||
| clampf(y, bound.pos.y, bound.pos.y + bound.size.y)); | |||||
| } | |||||
| } // namespace rack | } // namespace rack | ||||
| @@ -256,8 +256,8 @@ struct Label : Widget { | |||||
| // Deletes itself from parent when clicked | // Deletes itself from parent when clicked | ||||
| struct MenuOverlay : OpaqueWidget { | struct MenuOverlay : OpaqueWidget { | ||||
| void step(); | |||||
| void onDragDrop(Widget *origin); | void onDragDrop(Widget *origin); | ||||
| bool onScrollOpaque(Vec scrollRel) {return true;} | |||||
| }; | }; | ||||
| struct Menu : OpaqueWidget { | struct Menu : OpaqueWidget { | ||||
| @@ -266,6 +266,8 @@ struct Menu : OpaqueWidget { | |||||
| } | } | ||||
| // Resizes menu and calls addChild() | // Resizes menu and calls addChild() | ||||
| void pushChild(Widget *child); | void pushChild(Widget *child); | ||||
| void fit(); | |||||
| void step(); | |||||
| void draw(NVGcontext *vg); | void draw(NVGcontext *vg); | ||||
| bool onScrollOpaque(Vec scrollRel); | bool onScrollOpaque(Vec scrollRel); | ||||
| }; | }; | ||||
| @@ -337,31 +339,30 @@ struct Slider : OpaqueWidget, QuantityWidget { | |||||
| void onDragEnd(); | void onDragEnd(); | ||||
| }; | }; | ||||
| /** Parent must be a ScrollWidget */ | |||||
| struct ScrollBar : OpaqueWidget { | struct ScrollBar : OpaqueWidget { | ||||
| enum { VERTICAL, HORIZONTAL } orientation; | enum { VERTICAL, HORIZONTAL } orientation; | ||||
| float containerOffset = 0.0; | |||||
| float containerSize = 0.0; | |||||
| BNDwidgetState state = BND_DEFAULT; | BNDwidgetState state = BND_DEFAULT; | ||||
| ScrollBar() { | ScrollBar() { | ||||
| box.size = Vec(BND_SCROLLBAR_WIDTH, BND_SCROLLBAR_HEIGHT); | box.size = Vec(BND_SCROLLBAR_WIDTH, BND_SCROLLBAR_HEIGHT); | ||||
| } | } | ||||
| void step(); | |||||
| void draw(NVGcontext *vg); | void draw(NVGcontext *vg); | ||||
| void onDragStart(); | void onDragStart(); | ||||
| void onDragMove(Vec mouseRel); | void onDragMove(Vec mouseRel); | ||||
| void onDragEnd(); | void onDragEnd(); | ||||
| }; | }; | ||||
| // Handles a container with scrollbars | |||||
| /** Handles a container with ScrollBar */ | |||||
| struct ScrollWidget : OpaqueWidget { | struct ScrollWidget : OpaqueWidget { | ||||
| Widget *container; | Widget *container; | ||||
| ScrollBar *hScrollBar; | |||||
| ScrollBar *vScrollBar; | |||||
| ScrollBar *horizontalScrollBar; | |||||
| ScrollBar *verticalScrollBar; | |||||
| Vec offset; | |||||
| ScrollWidget(); | ScrollWidget(); | ||||
| void step(); | void step(); | ||||
| void draw(NVGcontext *vg); | |||||
| Widget *onMouseMove(Vec pos, Vec mouseRel); | |||||
| bool onScrollOpaque(Vec scrollRel); | bool onScrollOpaque(Vec scrollRel); | ||||
| }; | }; | ||||
| @@ -151,7 +151,7 @@ void cursorEnterCallback(GLFWwindow* window, int entered) { | |||||
| void scrollCallback(GLFWwindow *window, double x, double y) { | void scrollCallback(GLFWwindow *window, double x, double y) { | ||||
| Vec scrollRel = Vec(x, y); | Vec scrollRel = Vec(x, y); | ||||
| // onScroll | // onScroll | ||||
| gScene->onScroll(gMousePos, scrollRel.mult(-38.0)); | |||||
| gScene->onScroll(gMousePos, scrollRel.mult(50.0)); | |||||
| } | } | ||||
| void charCallback(GLFWwindow *window, unsigned int codepoint) { | void charCallback(GLFWwindow *window, unsigned int codepoint) { | ||||
| @@ -9,6 +9,17 @@ void Menu::pushChild(Widget *child) { | |||||
| box.size.y += child->box.size.y; | box.size.y += child->box.size.y; | ||||
| } | } | ||||
| void Menu::fit() { | |||||
| // Try to fit into the parent's box | |||||
| if (parent) | |||||
| box = box.clamp(Rect(Vec(0, 0), parent->box.size)); | |||||
| } | |||||
| void Menu::step() { | |||||
| fit(); | |||||
| Widget::step(); | |||||
| } | |||||
| void Menu::draw(NVGcontext *vg) { | void Menu::draw(NVGcontext *vg) { | ||||
| // Resize the width to the widest child | // Resize the width to the widest child | ||||
| for (Widget *child : children) { | for (Widget *child : children) { | ||||
| @@ -33,7 +44,8 @@ bool Menu::onScrollOpaque(Vec scrollRel) { | |||||
| if (!parent) | if (!parent) | ||||
| return true; | return true; | ||||
| if (!parent->box.contains(box)) | if (!parent->box.contains(box)) | ||||
| box.pos = box.pos.plus(scrollRel.neg()); | |||||
| box.pos = box.pos.plus(scrollRel); | |||||
| fit(); | |||||
| return true; | return true; | ||||
| } | } | ||||
| @@ -3,15 +3,6 @@ | |||||
| namespace rack { | namespace rack { | ||||
| void MenuOverlay::step() { | |||||
| // Try to fit all children into the overlay's box | |||||
| for (Widget *child : children) { | |||||
| child->box = child->box.clamp(Rect(Vec(0, 0), box.size)); | |||||
| } | |||||
| Widget::step(); | |||||
| } | |||||
| void MenuOverlay::onDragDrop(Widget *origin) { | void MenuOverlay::onDragDrop(Widget *origin) { | ||||
| if (origin == this) { | if (origin == this) { | ||||
| // deletes `this` | // deletes `this` | ||||
| @@ -4,17 +4,16 @@ | |||||
| namespace rack { | namespace rack { | ||||
| void ScrollBar::step() { | |||||
| float boxSize = (orientation == VERTICAL ? box.size.y : box.size.x); | |||||
| float maxOffset = containerSize - boxSize; | |||||
| containerOffset = clampf(containerOffset, 0.0, maxOffset); | |||||
| Widget::step(); | |||||
| } | |||||
| void ScrollBar::draw(NVGcontext *vg) { | void ScrollBar::draw(NVGcontext *vg) { | ||||
| float boxSize = (orientation == VERTICAL ? box.size.y : box.size.x); | |||||
| float maxOffset = containerSize - boxSize; | |||||
| float offset = containerOffset / maxOffset; | |||||
| ScrollWidget *scrollWidget = dynamic_cast<ScrollWidget*>(parent); | |||||
| assert(scrollWidget); | |||||
| Vec containerCorner = scrollWidget->container->getChildrenBoundingBox().getBottomRight(); | |||||
| float containerSize = (orientation == HORIZONTAL) ? containerCorner.x : containerCorner.y; | |||||
| float boxSize = (orientation == HORIZONTAL) ? box.size.x : box.size.y; | |||||
| float offset = (orientation == HORIZONTAL) ? scrollWidget->offset.x : scrollWidget->offset.y; | |||||
| offset = offset / (containerSize - boxSize); | |||||
| float size = boxSize / containerSize; | float size = boxSize / containerSize; | ||||
| size = clampf(size, 0.0, 1.0); | size = clampf(size, 0.0, 1.0); | ||||
| bndScrollBar(vg, 0.0, 0.0, box.size.x, box.size.y, state, offset, size); | bndScrollBar(vg, 0.0, 0.0, box.size.x, box.size.y, state, offset, size); | ||||
| @@ -26,7 +25,12 @@ void ScrollBar::onDragStart() { | |||||
| } | } | ||||
| void ScrollBar::onDragMove(Vec mouseRel) { | void ScrollBar::onDragMove(Vec mouseRel) { | ||||
| containerOffset += (orientation == VERTICAL ? mouseRel.y : mouseRel.x); | |||||
| ScrollWidget *scrollWidget = dynamic_cast<ScrollWidget*>(parent); | |||||
| assert(scrollWidget); | |||||
| if (orientation == HORIZONTAL) | |||||
| scrollWidget->offset.x += mouseRel.x; | |||||
| else | |||||
| scrollWidget->offset.y += mouseRel.y; | |||||
| } | } | ||||
| void ScrollBar::onDragEnd() { | void ScrollBar::onDragEnd() { | ||||
| @@ -1,4 +1,5 @@ | |||||
| #include "widgets.hpp" | #include "widgets.hpp" | ||||
| #include "gui.hpp" | |||||
| namespace rack { | namespace rack { | ||||
| @@ -7,42 +8,46 @@ ScrollWidget::ScrollWidget() { | |||||
| container = new Widget(); | container = new Widget(); | ||||
| addChild(container); | addChild(container); | ||||
| hScrollBar = new ScrollBar(); | |||||
| hScrollBar->orientation = ScrollBar::HORIZONTAL; | |||||
| addChild(hScrollBar); | |||||
| horizontalScrollBar = new ScrollBar(); | |||||
| horizontalScrollBar->orientation = ScrollBar::HORIZONTAL; | |||||
| addChild(horizontalScrollBar); | |||||
| vScrollBar = new ScrollBar(); | |||||
| vScrollBar->orientation = ScrollBar::VERTICAL; | |||||
| addChild(vScrollBar); | |||||
| verticalScrollBar = new ScrollBar(); | |||||
| verticalScrollBar->orientation = ScrollBar::VERTICAL; | |||||
| addChild(verticalScrollBar); | |||||
| } | } | ||||
| void ScrollWidget::step() { | void ScrollWidget::step() { | ||||
| Vec b = Vec(box.size.x - vScrollBar->box.size.x, box.size.y - hScrollBar->box.size.y); | |||||
| // Clamp scroll offset | |||||
| Vec containerCorner = container->getChildrenBoundingBox().getBottomRight(); | |||||
| offset = offset.clamp(Rect(Vec(0, 0), containerCorner.minus(box.size))); | |||||
| hScrollBar->box.pos.y = b.y; | |||||
| hScrollBar->box.size.x = b.x; | |||||
| // Resize scroll bars | |||||
| Vec inner = Vec(box.size.x - verticalScrollBar->box.size.x, box.size.y - horizontalScrollBar->box.size.y); | |||||
| horizontalScrollBar->box.pos.y = inner.y; | |||||
| horizontalScrollBar->box.size.x = inner.x; | |||||
| verticalScrollBar->box.pos.x = inner.x; | |||||
| verticalScrollBar->box.size.y = inner.y; | |||||
| vScrollBar->box.pos.x = b.x; | |||||
| vScrollBar->box.size.y = b.y; | |||||
| // Update the container's positions from the offset | |||||
| container->box.pos = offset.neg().round(); | |||||
| Widget::step(); | Widget::step(); | ||||
| } | } | ||||
| void ScrollWidget::draw(NVGcontext *vg) { | |||||
| // Update the scrollbar sizes | |||||
| Vec c = container->getChildrenBoundingBox().getBottomRight(); | |||||
| hScrollBar->containerSize = c.x; | |||||
| vScrollBar->containerSize = c.y; | |||||
| Widget *ScrollWidget::onMouseMove(Vec pos, Vec mouseRel) { | |||||
| Widget *w = Widget::onMouseMove(pos, mouseRel); | |||||
| if (w) return w; | |||||
| // Update the container's positions from the scrollbar offsets | |||||
| container->box.pos = Vec(-hScrollBar->containerOffset, -vScrollBar->containerOffset).round(); | |||||
| Widget::draw(vg); | |||||
| if (glfwGetMouseButton(gWindow, GLFW_MOUSE_BUTTON_MIDDLE) == GLFW_PRESS) { | |||||
| offset = offset.minus(mouseRel); | |||||
| return this; | |||||
| } | |||||
| return NULL; | |||||
| } | } | ||||
| bool ScrollWidget::onScrollOpaque(Vec scrollRel) { | bool ScrollWidget::onScrollOpaque(Vec scrollRel) { | ||||
| hScrollBar->containerOffset += scrollRel.x; | |||||
| vScrollBar->containerOffset += scrollRel.y; | |||||
| offset = offset.minus(scrollRel); | |||||
| return true; | return true; | ||||
| } | } | ||||