| @@ -7,9 +7,6 @@ | |||||
| namespace rack { | namespace rack { | ||||
| static const float KNOB_SENSITIVITY = 0.0015f; | |||||
| /** Implements vertical dragging behavior for ParamWidgets */ | /** Implements vertical dragging behavior for ParamWidgets */ | ||||
| struct Knob : ParamWidget { | struct Knob : ParamWidget { | ||||
| /** Multiplier for mouse movement to adjust knob value */ | /** Multiplier for mouse movement to adjust knob value */ | ||||
| @@ -1,11 +1,23 @@ | |||||
| #pragma once | #pragma once | ||||
| #include "app/common.hpp" | #include "app/common.hpp" | ||||
| #include "ui/ScrollWidget.hpp" | |||||
| #include "ui/SequentialLayout.hpp" | |||||
| namespace rack { | 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(); | json_t *moduleBrowserToJson(); | ||||
| void moduleBrowserFromJson(json_t *rootJ); | void moduleBrowserFromJson(json_t *rootJ); | ||||
| @@ -5,6 +5,7 @@ | |||||
| #include "ui/ScrollWidget.hpp" | #include "ui/ScrollWidget.hpp" | ||||
| #include "app/RackWidget.hpp" | #include "app/RackWidget.hpp" | ||||
| #include "app/Toolbar.hpp" | #include "app/Toolbar.hpp" | ||||
| #include "app/ModuleBrowser.hpp" | |||||
| namespace rack { | namespace rack { | ||||
| @@ -16,6 +17,7 @@ struct Scene : OpaqueWidget { | |||||
| ZoomWidget *zoomWidget; | ZoomWidget *zoomWidget; | ||||
| RackWidget *rackWidget; | RackWidget *rackWidget; | ||||
| Toolbar *toolbar; | Toolbar *toolbar; | ||||
| ModuleBrowser *moduleBrowser; | |||||
| // Version checking | // Version checking | ||||
| bool devMode = false; | bool devMode = false; | ||||
| @@ -221,10 +221,10 @@ struct Zoom : Event { | |||||
| struct State { | struct State { | ||||
| Widget *rootWidget = NULL; | |||||
| /** State widgets | /** State widgets | ||||
| Don't set these directly unless you know what you're doing. Use the set*() methods instead. | Don't set these directly unless you know what you're doing. Use the set*() methods instead. | ||||
| */ | */ | ||||
| Widget *rootWidget = NULL; | |||||
| Widget *hoveredWidget = NULL; | Widget *hoveredWidget = NULL; | ||||
| Widget *draggedWidget = NULL; | Widget *draggedWidget = NULL; | ||||
| Widget *dragHoveredWidget = NULL; | Widget *dragHoveredWidget = NULL; | ||||
| @@ -7,14 +7,15 @@ namespace rack { | |||||
| struct Label : virtual Widget { | struct Label : virtual Widget { | ||||
| std::string text; | |||||
| float fontSize; | |||||
| NVGcolor color; | |||||
| enum Alignment { | enum Alignment { | ||||
| LEFT_ALIGNMENT, | LEFT_ALIGNMENT, | ||||
| CENTER_ALIGNMENT, | CENTER_ALIGNMENT, | ||||
| RIGHT_ALIGNMENT, | RIGHT_ALIGNMENT, | ||||
| }; | }; | ||||
| std::string text; | |||||
| float fontSize; | |||||
| NVGcolor color; | |||||
| Alignment alignment = LEFT_ALIGNMENT; | Alignment alignment = LEFT_ALIGNMENT; | ||||
| Label() { | 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 | #pragma once | ||||
| #include "widgets/OpaqueWidget.hpp" | |||||
| #include "ui/common.hpp" | #include "ui/common.hpp" | ||||
| #include "event.hpp" | |||||
| #include "context.hpp" | |||||
| #include "widgets/OpaqueWidget.hpp" | |||||
| #include "ui/ScrollBar.hpp" | |||||
| namespace rack { | 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 */ | /** Handles a container with ScrollBar */ | ||||
| struct ScrollWidget : OpaqueWidget { | struct ScrollWidget : OpaqueWidget { | ||||
| Widget *container; | Widget *container; | ||||
| @@ -48,111 +14,13 @@ struct ScrollWidget : OpaqueWidget { | |||||
| ScrollBar *verticalScrollBar; | ScrollBar *verticalScrollBar; | ||||
| math::Vec offset; | 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 | } // namespace rack | ||||
| @@ -4,6 +4,9 @@ | |||||
| namespace rack { | namespace rack { | ||||
| static const float KNOB_SENSITIVITY = 0.0015f; | |||||
| void Knob::onButton(const event::Button &e) { | void Knob::onButton(const event::Button &e) { | ||||
| float r = box.size.x / 2; | float r = box.size.x / 2; | ||||
| math::Vec c = box.size.div(2); | math::Vec c = box.size.div(2); | ||||
| @@ -1,17 +1,11 @@ | |||||
| #include "app/ModuleBrowser.hpp" | #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/Label.hpp" | ||||
| #include "ui/MenuOverlay.hpp" | |||||
| #include "app/ModuleWidget.hpp" | |||||
| #include "app/Scene.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 "plugin.hpp" | ||||
| #include "context.hpp" | #include "context.hpp" | ||||
| @@ -27,75 +21,122 @@ static std::string sAuthorFilter; | |||||
| static std::string sTagFilter; | static std::string sTagFilter; | ||||
| struct ModuleWidgetWrapper : ObstructWidget { | |||||
| struct ModuleBox : OpaqueWidget { | |||||
| Model *model; | 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 | // Create module | ||||
| ModuleWidget *moduleWidget = model->createModuleWidget(); | ModuleWidget *moduleWidget = model->createModuleWidget(); | ||||
| assert(moduleWidget); | assert(moduleWidget); | ||||
| context()->scene->rackWidget->addModuleAtMouse(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 | // 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 *moduleBrowserToJson() { | ||||
| json_t *rootJ = json_object(); | json_t *rootJ = json_object(); | ||||
| @@ -284,7 +284,7 @@ void ModuleWidget::draw(NVGcontext *vg) { | |||||
| if (module && module->bypass) { | if (module && module->bypass) { | ||||
| nvgGlobalAlpha(vg, 0.5); | 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); | Widget::draw(vg); | ||||
| // Power meter | // Power meter | ||||
| @@ -312,7 +312,7 @@ void ModuleWidget::draw(NVGcontext *vg) { | |||||
| nvgFill(vg); | nvgFill(vg); | ||||
| } | } | ||||
| nvgResetScissor(vg); | |||||
| // nvgResetScissor(vg); | |||||
| } | } | ||||
| void ModuleWidget::drawShadow(NVGcontext *vg) { | void ModuleWidget::drawShadow(NVGcontext *vg) { | ||||
| @@ -566,7 +566,7 @@ void RackWidget::onButton(const event::Button &e) { | |||||
| OpaqueWidget::onButton(e); | OpaqueWidget::onButton(e); | ||||
| if (e.getConsumed() == this) { | if (e.getConsumed() == this) { | ||||
| if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_RIGHT) { | 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; | toolbar = new Toolbar; | ||||
| addChild(toolbar); | addChild(toolbar); | ||||
| scrollWidget->box.pos.y = toolbar->box.size.y; | scrollWidget->box.pos.y = toolbar->box.size.y; | ||||
| moduleBrowser = new ModuleBrowser; | |||||
| moduleBrowser->visible = false; | |||||
| addChild(moduleBrowser); | |||||
| } | } | ||||
| Scene::~Scene() { | Scene::~Scene() { | ||||
| @@ -45,6 +49,7 @@ void Scene::step() { | |||||
| OpaqueWidget::step(); | OpaqueWidget::step(); | ||||
| zoomWidget->box.size = rackWidget->box.size.mult(zoomWidget->zoom); | zoomWidget->box.size = rackWidget->box.size.mult(zoomWidget->zoom); | ||||
| moduleBrowser->box.size = box.size; | |||||
| // Request latest version from server | // Request latest version from server | ||||
| if (!devMode && checkVersion && !checkedVersion) { | if (!devMode && checkVersion && !checkedVersion) { | ||||
| @@ -112,7 +117,7 @@ void Scene::onHoverKey(const event::HoverKey &e) { | |||||
| } break; | } break; | ||||
| case GLFW_KEY_ENTER: | case GLFW_KEY_ENTER: | ||||
| case GLFW_KEY_KP_ENTER: { | case GLFW_KEY_KP_ENTER: { | ||||
| moduleBrowserCreate(); | |||||
| moduleBrowser->visible = true; | |||||
| e.consume(this); | e.consume(this); | ||||
| } break; | } break; | ||||
| case GLFW_KEY_F11: { | case GLFW_KEY_F11: { | ||||
| @@ -560,7 +560,7 @@ Toolbar::Toolbar() { | |||||
| } | } | ||||
| void Toolbar::draw(NVGcontext *vg) { | 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); | bndBevel(vg, 0.0, 0.0, box.size.x, box.size.y); | ||||
| Widget::draw(vg); | Widget::draw(vg); | ||||
| @@ -79,12 +79,12 @@ int main(int argc, char *argv[]) { | |||||
| plugin::init(devMode); | plugin::init(devMode); | ||||
| // Initialize app | // Initialize app | ||||
| context()->engine = new Engine; | |||||
| context()->event = new event::State; | context()->event = new event::State; | ||||
| context()->window = new Window; | |||||
| context()->engine = new Engine; | |||||
| context()->scene = new Scene; | context()->scene = new Scene; | ||||
| context()->scene->devMode = devMode; | context()->scene->devMode = devMode; | ||||
| context()->event->rootWidget = context()->scene; | context()->event->rootWidget = context()->scene; | ||||
| context()->window = new Window; | |||||
| settings::load(asset::user("settings.json")); | settings::load(asset::user("settings.json")); | ||||
| if (patchFile.empty()) { | if (patchFile.empty()) { | ||||
| @@ -120,10 +120,10 @@ int main(int argc, char *argv[]) { | |||||
| context()->scene = NULL; | context()->scene = NULL; | ||||
| delete context()->event; | delete context()->event; | ||||
| context()->event = NULL; | context()->event = NULL; | ||||
| delete context()->window; | |||||
| context()->window = NULL; | |||||
| delete context()->engine; | delete context()->engine; | ||||
| context()->engine = NULL; | context()->engine = NULL; | ||||
| delete context()->window; | |||||
| context()->window = NULL; | |||||
| // Destroy environment | // Destroy environment | ||||
| plugin::destroy(); | 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 | |||||