diff --git a/include/app/Knob.hpp b/include/app/Knob.hpp index a219f0cf..9e9111c5 100644 --- a/include/app/Knob.hpp +++ b/include/app/Knob.hpp @@ -7,9 +7,6 @@ namespace rack { -static const float KNOB_SENSITIVITY = 0.0015f; - - /** Implements vertical dragging behavior for ParamWidgets */ struct Knob : ParamWidget { /** Multiplier for mouse movement to adjust knob value */ diff --git a/include/app/ModuleBrowser.hpp b/include/app/ModuleBrowser.hpp index b6a3686e..57927481 100644 --- a/include/app/ModuleBrowser.hpp +++ b/include/app/ModuleBrowser.hpp @@ -1,11 +1,23 @@ #pragma once #include "app/common.hpp" +#include "ui/ScrollWidget.hpp" +#include "ui/SequentialLayout.hpp" namespace rack { -void moduleBrowserCreate(); +struct ModuleBrowser : OpaqueWidget { + ScrollWidget *moduleScroll; + SequentialLayout *moduleLayout; + + ModuleBrowser(); + void step() override; + void draw(NVGcontext *vg) override; + void onHoverKey(const event::HoverKey &e) override; +}; + + json_t *moduleBrowserToJson(); void moduleBrowserFromJson(json_t *rootJ); diff --git a/include/app/Scene.hpp b/include/app/Scene.hpp index f8ad45f2..d4b904d3 100644 --- a/include/app/Scene.hpp +++ b/include/app/Scene.hpp @@ -5,6 +5,7 @@ #include "ui/ScrollWidget.hpp" #include "app/RackWidget.hpp" #include "app/Toolbar.hpp" +#include "app/ModuleBrowser.hpp" namespace rack { @@ -16,6 +17,7 @@ struct Scene : OpaqueWidget { ZoomWidget *zoomWidget; RackWidget *rackWidget; Toolbar *toolbar; + ModuleBrowser *moduleBrowser; // Version checking bool devMode = false; diff --git a/include/event.hpp b/include/event.hpp index 416ae212..966b078f 100644 --- a/include/event.hpp +++ b/include/event.hpp @@ -221,10 +221,10 @@ struct Zoom : Event { struct State { + Widget *rootWidget = NULL; /** State widgets Don't set these directly unless you know what you're doing. Use the set*() methods instead. */ - Widget *rootWidget = NULL; Widget *hoveredWidget = NULL; Widget *draggedWidget = NULL; Widget *dragHoveredWidget = NULL; diff --git a/include/ui/Label.hpp b/include/ui/Label.hpp index ef34bd25..42d15008 100644 --- a/include/ui/Label.hpp +++ b/include/ui/Label.hpp @@ -7,14 +7,15 @@ namespace rack { struct Label : virtual Widget { - std::string text; - float fontSize; - NVGcolor color; enum Alignment { LEFT_ALIGNMENT, CENTER_ALIGNMENT, RIGHT_ALIGNMENT, }; + + std::string text; + float fontSize; + NVGcolor color; Alignment alignment = LEFT_ALIGNMENT; Label() { diff --git a/include/ui/ScrollBar.hpp b/include/ui/ScrollBar.hpp new file mode 100644 index 00000000..f9662e7e --- /dev/null +++ b/include/ui/ScrollBar.hpp @@ -0,0 +1,28 @@ +#pragma once +#include "widgets/OpaqueWidget.hpp" +#include "ui/common.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(); + void draw(NVGcontext *vg) override; + void onDragStart(const event::DragStart &e) override; + void onDragMove(const event::DragMove &e) override; + void onDragEnd(const event::DragEnd &e) override; +}; + + +} // namespace rack diff --git a/include/ui/ScrollWidget.hpp b/include/ui/ScrollWidget.hpp index 4db2fbfe..a9650f2b 100644 --- a/include/ui/ScrollWidget.hpp +++ b/include/ui/ScrollWidget.hpp @@ -1,46 +1,12 @@ #pragma once -#include "widgets/OpaqueWidget.hpp" #include "ui/common.hpp" -#include "event.hpp" -#include "context.hpp" +#include "widgets/OpaqueWidget.hpp" +#include "ui/ScrollBar.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(const event::DragStart &e) override { - state = BND_ACTIVE; - context()->window->cursorLock(); - } - - void onDragMove(const event::DragMove &e) override; - - void onDragEnd(const event::DragEnd &e) override { - state = BND_DEFAULT; - context()->window->cursorUnlock(); - } -}; - - /** Handles a container with ScrollBar */ struct ScrollWidget : OpaqueWidget { Widget *container; @@ -48,111 +14,13 @@ struct ScrollWidget : OpaqueWidget { 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(const 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(const event::HoverScroll &e) override { - offset = offset.minus(e.scrollDelta); - e.consume(this); - } + ScrollWidget(); + void scrollTo(math::Rect r); + void draw(NVGcontext *vg) override; + void step() override; + void onHover(const event::Hover &e) override; + void onHoverScroll(const event::HoverScroll &e) override; }; -inline void ScrollBar::onDragMove(const 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 diff --git a/src/app/Knob.cpp b/src/app/Knob.cpp index 01df7724..bf3033de 100644 --- a/src/app/Knob.cpp +++ b/src/app/Knob.cpp @@ -4,6 +4,9 @@ namespace rack { +static const float KNOB_SENSITIVITY = 0.0015f; + + void Knob::onButton(const event::Button &e) { float r = box.size.x / 2; math::Vec c = box.size.div(2); diff --git a/src/app/ModuleBrowser.cpp b/src/app/ModuleBrowser.cpp index f57981db..c8ec7d42 100644 --- a/src/app/ModuleBrowser.cpp +++ b/src/app/ModuleBrowser.cpp @@ -1,17 +1,11 @@ #include "app/ModuleBrowser.hpp" -// TODO clean up -#include "window.hpp" -#include "helpers.hpp" -#include "event.hpp" -#include "ui/Quantity.hpp" -#include "ui/RadioButton.hpp" +#include "widgets/OpaqueWidget.hpp" +#include "widgets/TransparentWidget.hpp" +#include "widgets/ZoomWidget.hpp" #include "ui/Label.hpp" +#include "ui/MenuOverlay.hpp" +#include "app/ModuleWidget.hpp" #include "app/Scene.hpp" -#include "ui/List.hpp" -#include "ui/TextField.hpp" -#include "ui/SequentialLayout.hpp" -#include "widgets/ObstructWidget.hpp" -#include "widgets/ZoomWidget.hpp" #include "plugin.hpp" #include "context.hpp" @@ -27,75 +21,122 @@ static std::string sAuthorFilter; static std::string sTagFilter; - -struct ModuleWidgetWrapper : ObstructWidget { +struct ModuleBox : OpaqueWidget { Model *model; - void onDragDrop(const event::DragDrop &e) override { - if (e.origin == this) { + void setModel(Model *model) { + this->model = model; + + Widget *transparentWidget = new TransparentWidget; + addChild(transparentWidget); + + ZoomWidget *zoomWidget = new ZoomWidget; + zoomWidget->setZoom(0.5); + transparentWidget->addChild(zoomWidget); + + ModuleWidget *moduleWidget = model->createModuleWidgetNull(); + zoomWidget->addChild(moduleWidget); + + box.size = math::Vec(moduleWidget->box.size.x, RACK_GRID_SIZE.y).mult(zoomWidget->zoom).ceil(); + + math::Vec p; + p.y = box.size.y; + box.size.y += 40.0; + box.size.x = std::max(box.size.x, 70.f); + + Label *nameLabel = new Label; + nameLabel->text = model->name; + nameLabel->box.pos = p; + p.y += nameLabel->box.size.y; + addChild(nameLabel); + + Label *pluginLabel = new Label; + pluginLabel->text = model->plugin->name; + pluginLabel->box.pos = p; + p.y += pluginLabel->box.size.y; + addChild(pluginLabel); + } + + void draw(NVGcontext *vg) override { + OpaqueWidget::draw(vg); + if (context()->event->hoveredWidget == this) { + nvgBeginPath(vg); + nvgRect(vg, 0.0, 0.0, box.size.x, box.size.y); + nvgFillColor(vg, nvgRGBAf(1, 1, 1, 0.25)); + nvgFill(vg); + } + } + + void onButton(const event::Button &e) override { + if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT) { // Create module ModuleWidget *moduleWidget = model->createModuleWidget(); assert(moduleWidget); context()->scene->rackWidget->addModuleAtMouse(moduleWidget); + // This is a bit nonstandard/unsupported usage, but pretend the moduleWidget was clicked so it can be dragged in the RackWidget + e.consume(moduleWidget); // Close Module Browser - MenuOverlay *menuOverlay = getAncestorOfType(); - menuOverlay->requestedDelete = true; + ModuleBrowser *moduleBrowser = getAncestorOfType(); + moduleBrowser->visible = false; } + OpaqueWidget::onButton(e); } }; -struct ModuleBrowser : OpaqueWidget { - SequentialLayout *moduleLayout; - ModuleBrowser() { - moduleLayout = new SequentialLayout; - moduleLayout->spacing = math::Vec(10, 10); - addChild(moduleLayout); +ModuleBrowser::ModuleBrowser() { + moduleScroll = new ScrollWidget; + addChild(moduleScroll); - for (Plugin *plugin : plugin::plugins) { - for (Model *model : plugin->models) { - ModuleWidgetWrapper *wrapper = new ModuleWidgetWrapper; - wrapper->model = model; - moduleLayout->addChild(wrapper); + moduleLayout = new SequentialLayout; + moduleLayout->spacing = math::Vec(10, 10); + moduleScroll->container->addChild(moduleLayout); - ZoomWidget *zoomWidget = new ZoomWidget; - zoomWidget->setZoom(0.5); - wrapper->addChild(zoomWidget); - - ModuleWidget *moduleWidget = model->createModuleWidgetNull(); - zoomWidget->addChild(moduleWidget); - wrapper->box.size = moduleWidget->box.size.mult(zoomWidget->zoom); - } + for (Plugin *plugin : plugin::plugins) { + for (Model *model : plugin->models) { + ModuleBox *moduleBox = new ModuleBox; + moduleBox->setModel(model); + moduleLayout->addChild(moduleBox); } } +} - void step() override { - assert(parent); - - box = parent->box.zeroPos().grow(math::Vec(-50, -50)); - moduleLayout->box.size = box.size; +void ModuleBrowser::step() { + // TODO resize sidebar + float sidebarWidth = 300.0; - OpaqueWidget::step(); - } + moduleScroll->box.pos.x = sidebarWidth; + moduleScroll->box.size.x = box.size.x - sidebarWidth; + moduleScroll->box.size.y = box.size.y; + moduleLayout->box.size.x = moduleScroll->box.size.x; + moduleLayout->box.size.y = moduleLayout->getChildrenBoundingBox().getBottomRight().y; - void draw(NVGcontext *vg) override { - bndTooltipBackground(vg, 0.0, 0.0, box.size.x, box.size.y); - Widget::draw(vg); - } -}; + OpaqueWidget::step(); +} +void ModuleBrowser::draw(NVGcontext *vg) { + bndMenuBackground(vg, 0.0, 0.0, box.size.x, box.size.y, 0); + Widget::draw(vg); +} +void ModuleBrowser::onHoverKey(const event::HoverKey &e) { + if (e.action == GLFW_PRESS) { + switch (e.key) { + case GLFW_KEY_ESCAPE: { + // Close menu + this->visible = false; + e.consume(this); + } break; + } + } -// Global functions + if (!e.getConsumed()) + OpaqueWidget::onHoverKey(e); +} -void moduleBrowserCreate() { - MenuOverlay *overlay = new MenuOverlay; - ModuleBrowser *moduleBrowser = new ModuleBrowser; - overlay->addChild(moduleBrowser); - context()->scene->addChild(overlay); -} +// Global functions json_t *moduleBrowserToJson() { json_t *rootJ = json_object(); diff --git a/src/app/ModuleWidget.cpp b/src/app/ModuleWidget.cpp index 3fb566ab..b85700df 100644 --- a/src/app/ModuleWidget.cpp +++ b/src/app/ModuleWidget.cpp @@ -284,7 +284,7 @@ void ModuleWidget::draw(NVGcontext *vg) { if (module && module->bypass) { nvgGlobalAlpha(vg, 0.5); } - nvgScissor(vg, 0, 0, box.size.x, box.size.y); + // nvgScissor(vg, 0, 0, box.size.x, box.size.y); Widget::draw(vg); // Power meter @@ -312,7 +312,7 @@ void ModuleWidget::draw(NVGcontext *vg) { nvgFill(vg); } - nvgResetScissor(vg); + // nvgResetScissor(vg); } void ModuleWidget::drawShadow(NVGcontext *vg) { diff --git a/src/app/RackWidget.cpp b/src/app/RackWidget.cpp index 167f2e32..e234e37f 100644 --- a/src/app/RackWidget.cpp +++ b/src/app/RackWidget.cpp @@ -566,7 +566,7 @@ void RackWidget::onButton(const event::Button &e) { OpaqueWidget::onButton(e); if (e.getConsumed() == this) { if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_RIGHT) { - moduleBrowserCreate(); + context()->scene->moduleBrowser->visible = true; } } } diff --git a/src/app/Scene.cpp b/src/app/Scene.cpp index 60955171..72674478 100644 --- a/src/app/Scene.cpp +++ b/src/app/Scene.cpp @@ -26,6 +26,10 @@ Scene::Scene() { toolbar = new Toolbar; addChild(toolbar); scrollWidget->box.pos.y = toolbar->box.size.y; + + moduleBrowser = new ModuleBrowser; + moduleBrowser->visible = false; + addChild(moduleBrowser); } Scene::~Scene() { @@ -45,6 +49,7 @@ void Scene::step() { OpaqueWidget::step(); zoomWidget->box.size = rackWidget->box.size.mult(zoomWidget->zoom); + moduleBrowser->box.size = box.size; // Request latest version from server if (!devMode && checkVersion && !checkedVersion) { @@ -112,7 +117,7 @@ void Scene::onHoverKey(const event::HoverKey &e) { } break; case GLFW_KEY_ENTER: case GLFW_KEY_KP_ENTER: { - moduleBrowserCreate(); + moduleBrowser->visible = true; e.consume(this); } break; case GLFW_KEY_F11: { diff --git a/src/app/Toolbar.cpp b/src/app/Toolbar.cpp index 3b30d26b..b256a651 100644 --- a/src/app/Toolbar.cpp +++ b/src/app/Toolbar.cpp @@ -560,7 +560,7 @@ Toolbar::Toolbar() { } void Toolbar::draw(NVGcontext *vg) { - bndMenuBackground(vg, 0.0, 0.0, box.size.x, box.size.y, 0); + bndMenuBackground(vg, 0.0, 0.0, box.size.x, box.size.y, BND_CORNER_ALL); bndBevel(vg, 0.0, 0.0, box.size.x, box.size.y); Widget::draw(vg); diff --git a/src/main.cpp b/src/main.cpp index a40083f7..c76307fa 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -79,12 +79,12 @@ int main(int argc, char *argv[]) { plugin::init(devMode); // Initialize app - context()->engine = new Engine; context()->event = new event::State; + context()->window = new Window; + context()->engine = new Engine; context()->scene = new Scene; context()->scene->devMode = devMode; context()->event->rootWidget = context()->scene; - context()->window = new Window; settings::load(asset::user("settings.json")); if (patchFile.empty()) { @@ -120,10 +120,10 @@ int main(int argc, char *argv[]) { context()->scene = NULL; delete context()->event; context()->event = NULL; - delete context()->window; - context()->window = NULL; delete context()->engine; context()->engine = NULL; + delete context()->window; + context()->window = NULL; // Destroy environment plugin::destroy(); diff --git a/src/ui/ScrollBar.cpp b/src/ui/ScrollBar.cpp new file mode 100644 index 00000000..138eb668 --- /dev/null +++ b/src/ui/ScrollBar.cpp @@ -0,0 +1,41 @@ +#include "ui/ScrollBar.hpp" +#include "ui/ScrollWidget.hpp" +#include "context.hpp" +#include "window.hpp" + + +namespace rack { + + +static const float SCROLLBAR_SENSITIVITY = 2.f; + + +ScrollBar::ScrollBar() { + box.size = math::Vec(BND_SCROLLBAR_WIDTH, BND_SCROLLBAR_HEIGHT); +} + +void ScrollBar::draw(NVGcontext *vg) { + bndScrollBar(vg, 0.0, 0.0, box.size.x, box.size.y, state, offset, size); +} + +void ScrollBar::onDragStart(const event::DragStart &e) { + state = BND_ACTIVE; + context()->window->cursorLock(); +} + +void ScrollBar::onDragMove(const event::DragMove &e) { + ScrollWidget *scrollWidget = dynamic_cast(parent); + assert(scrollWidget); + if (orientation == HORIZONTAL) + scrollWidget->offset.x += SCROLLBAR_SENSITIVITY * e.mouseDelta.x; + else + scrollWidget->offset.y += SCROLLBAR_SENSITIVITY * e.mouseDelta.y; +} + +void ScrollBar::onDragEnd(const event::DragEnd &e) { + state = BND_DEFAULT; + context()->window->cursorUnlock(); +} + + +} // namespace rack diff --git a/src/ui/ScrollWidget.cpp b/src/ui/ScrollWidget.cpp new file mode 100644 index 00000000..d0598b0e --- /dev/null +++ b/src/ui/ScrollWidget.cpp @@ -0,0 +1,105 @@ +#include "ui/ScrollWidget.hpp" +#include "context.hpp" +#include "event.hpp" + + +namespace rack { + + +ScrollWidget::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 ScrollWidget::scrollTo(math::Rect r) { + math::Rect bound = math::Rect::fromMinMax(r.getBottomRight().minus(box.size), r.pos); + offset = offset.clampBetween(bound); +} + +void ScrollWidget::draw(NVGcontext *vg) { + nvgScissor(vg, 0, 0, box.size.x, box.size.y); + Widget::draw(vg); + nvgResetScissor(vg); +} + +void ScrollWidget::step() { + 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 ScrollWidget::onHover(const event::Hover &e) { + // 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 ScrollWidget::onHoverScroll(const event::HoverScroll &e) { + offset = offset.minus(e.scrollDelta); + e.consume(this); +} + + +} // namespace rack