| @@ -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 */ | |||
| @@ -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); | |||
| @@ -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; | |||
| @@ -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; | |||
| @@ -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() { | |||
| @@ -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 | |||
| @@ -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<ScrollWidget*>(parent); | |||
| assert(scrollWidget); | |||
| if (orientation == HORIZONTAL) | |||
| scrollWidget->offset.x += e.mouseDelta.x; | |||
| else | |||
| scrollWidget->offset.y += e.mouseDelta.y; | |||
| } | |||
| } // namespace rack | |||
| @@ -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); | |||
| @@ -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>(); | |||
| menuOverlay->requestedDelete = true; | |||
| ModuleBrowser *moduleBrowser = getAncestorOfType<ModuleBrowser>(); | |||
| 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(); | |||
| @@ -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) { | |||
| @@ -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; | |||
| } | |||
| } | |||
| } | |||
| @@ -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: { | |||
| @@ -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); | |||
| @@ -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(); | |||
| @@ -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<ScrollWidget*>(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 | |||
| @@ -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 | |||