#pragma once #include "widgets/OpaqueWidget.hpp" #include "ui/common.hpp" #include "event.hpp" #include "context.hpp" namespace rack { /** Parent must be a ScrollWidget */ struct ScrollBar : OpaqueWidget { enum Orientation { VERTICAL, HORIZONTAL }; Orientation orientation; BNDwidgetState state = BND_DEFAULT; float offset = 0.0; float size = 0.0; ScrollBar() { box.size = math::Vec(BND_SCROLLBAR_WIDTH, BND_SCROLLBAR_HEIGHT); } void draw(NVGcontext *vg) override { bndScrollBar(vg, 0.0, 0.0, box.size.x, box.size.y, state, offset, size); } void onDragStart(event::DragStart &e) override { state = BND_ACTIVE; context()->window->cursorLock(); } void onDragMove(event::DragMove &e) override; void onDragEnd(event::DragEnd &e) override { state = BND_DEFAULT; context()->window->cursorUnlock(); } }; /** Handles a container with ScrollBar */ struct ScrollWidget : OpaqueWidget { Widget *container; ScrollBar *horizontalScrollBar; ScrollBar *verticalScrollBar; math::Vec offset; ScrollWidget() { container = new Widget; addChild(container); horizontalScrollBar = new ScrollBar; horizontalScrollBar->orientation = ScrollBar::HORIZONTAL; horizontalScrollBar->visible = false; addChild(horizontalScrollBar); verticalScrollBar = new ScrollBar; verticalScrollBar->orientation = ScrollBar::VERTICAL; verticalScrollBar->visible = false; addChild(verticalScrollBar); } void scrollTo(math::Rect r) { math::Rect bound = math::Rect::fromMinMax(r.getBottomRight().minus(box.size), r.pos); offset = offset.clampBetween(bound); } void draw(NVGcontext *vg) override { nvgScissor(vg, 0, 0, box.size.x, box.size.y); Widget::draw(vg); nvgResetScissor(vg); } void step() override { Widget::step(); // Clamp scroll offset math::Vec containerCorner = container->getChildrenBoundingBox().getBottomRight(); math::Rect containerBox = math::Rect(math::Vec(0, 0), containerCorner.minus(box.size)); offset = offset.clamp(containerBox); // Lock offset to top/left if no scrollbar will display if (containerBox.size.x < 0.0) offset.x = 0.0; if (containerBox.size.y < 0.0) offset.y = 0.0; // Update the container's positions from the offset container->box.pos = offset.neg().round(); // Update scrollbar offsets and sizes math::Vec viewportSize = container->getChildrenBoundingBox().getBottomRight(); math::Vec scrollbarOffset = offset.div(viewportSize.minus(box.size)); math::Vec scrollbarSize = box.size.div(viewportSize); 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; // Resize scroll bars math::Vec inner = math::Vec(box.size.x - verticalScrollBar->box.size.x, box.size.y - 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; } void onHover(event::Hover &e) override { // Scroll with arrow keys if (!context()->event->selectedWidget) { float arrowSpeed = 30.0; if (context()->window->isShiftPressed() && context()->window->isModPressed()) arrowSpeed /= 16.0; else if (context()->window->isShiftPressed()) arrowSpeed *= 4.0; else if (context()->window->isModPressed()) arrowSpeed /= 4.0; if (glfwGetKey(context()->window->win, GLFW_KEY_LEFT) == GLFW_PRESS) { offset.x -= arrowSpeed; } if (glfwGetKey(context()->window->win, GLFW_KEY_RIGHT) == GLFW_PRESS) { offset.x += arrowSpeed; } if (glfwGetKey(context()->window->win, GLFW_KEY_UP) == GLFW_PRESS) { offset.y -= arrowSpeed; } if (glfwGetKey(context()->window->win, GLFW_KEY_DOWN) == GLFW_PRESS) { offset.y += arrowSpeed; } } OpaqueWidget::onHover(e); } void onHoverScroll(event::HoverScroll &e) override { offset = offset.minus(e.scrollDelta); e.target = this; } }; inline void ScrollBar::onDragMove(event::DragMove &e) { ScrollWidget *scrollWidget = dynamic_cast(parent); assert(scrollWidget); if (orientation == HORIZONTAL) scrollWidget->offset.x += e.mouseDelta.x; else scrollWidget->offset.y += e.mouseDelta.y; } } // namespace rack