From f5601d2042db75bca9b6c195fc3f42f3617de883 Mon Sep 17 00:00:00 2001 From: Andrew Belt Date: Wed, 26 Dec 2018 06:16:47 -0500 Subject: [PATCH] Work on toolbar, event context --- include/app/ParamQuantity.hpp | 2 +- include/app/PluginManagerWidget.hpp | 18 - include/app/RackWidget.hpp | 1 - include/common.hpp | 1 + include/engine/Engine.hpp | 1 - include/event.hpp | 10 +- include/helpers.hpp | 3 +- include/logger.hpp | 1 - include/rack.hpp | 2 - include/settings.hpp | 8 + include/ui/Button.hpp | 1 - include/ui/Menu.hpp | 68 +--- include/ui/MenuItem.hpp | 61 +--- include/ui/SequentialLayout.hpp | 1 + include/ui/TextField.hpp | 202 +--------- src/app/Knob.cpp | 1 - src/app/ModuleBrowser.cpp | 1 - src/app/ModuleWidget.cpp | 6 +- src/app/ParamQuantity.cpp | 8 + src/app/PluginManagerWidget.cpp | 241 ------------ src/app/RackWidget.cpp | 2 - src/app/Scene.cpp | 1 - src/app/Toolbar.cpp | 549 +++++++++++++++++++++------- src/app/WireWidget.cpp | 7 +- src/audio.cpp | 1 - src/bridge.cpp | 1 - src/engine/Engine.cpp | 3 +- src/event.cpp | 175 +++++---- src/logger.cpp | 2 +- src/main.cpp | 1 - src/plugin.cpp | 1 - src/plugin/Model.cpp | 1 - src/plugin/Plugin.cpp | 1 - src/settings.cpp | 47 +-- src/ui/Menu.cpp | 62 ++++ src/ui/MenuItem.cpp | 69 ++++ src/ui/TextField.cpp | 206 +++++++++++ src/window.cpp | 1 - 38 files changed, 932 insertions(+), 835 deletions(-) delete mode 100644 include/app/PluginManagerWidget.hpp delete mode 100644 src/app/PluginManagerWidget.cpp create mode 100644 src/ui/Menu.cpp create mode 100644 src/ui/MenuItem.cpp create mode 100644 src/ui/TextField.cpp diff --git a/include/app/ParamQuantity.hpp b/include/app/ParamQuantity.hpp index 2d4fafec..8cf247c1 100644 --- a/include/app/ParamQuantity.hpp +++ b/include/app/ParamQuantity.hpp @@ -26,7 +26,7 @@ struct ParamQuantity : Quantity { float getMaxValue() override; float getDefaultValue() override; float getDisplayValue() override; - void setDisplayValue(float displayValue) override ; + void setDisplayValue(float displayValue) override; int getDisplayPrecision() override; std::string getLabel() override; std::string getUnit() override; diff --git a/include/app/PluginManagerWidget.hpp b/include/app/PluginManagerWidget.hpp deleted file mode 100644 index 64b0308b..00000000 --- a/include/app/PluginManagerWidget.hpp +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once -#include "app/common.hpp" -#include "widgets/Widget.hpp" - - -namespace rack { - - -struct PluginManagerWidget : virtual Widget { - Widget *loginWidget; - Widget *manageWidget; - Widget *downloadWidget; - PluginManagerWidget(); - void step() override; -}; - - -} // namespace rack diff --git a/include/app/RackWidget.hpp b/include/app/RackWidget.hpp index 93cefb99..7d4829cb 100644 --- a/include/app/RackWidget.hpp +++ b/include/app/RackWidget.hpp @@ -17,7 +17,6 @@ struct RackWidget : OpaqueWidget { WireContainer *wireContainer; std::string lastPath; math::Vec lastMousePos; - bool lockModules = false; RackWidget(); ~RackWidget(); diff --git a/include/common.hpp b/include/common.hpp index ee2e717f..9b83992a 100644 --- a/include/common.hpp +++ b/include/common.hpp @@ -1,4 +1,5 @@ #pragma once +#include "logger.hpp" // Include most of the C stdlib for convenience #include diff --git a/include/engine/Engine.hpp b/include/engine/Engine.hpp index 2900e204..e939347c 100644 --- a/include/engine/Engine.hpp +++ b/include/engine/Engine.hpp @@ -15,7 +15,6 @@ struct Engine { std::vector modules; std::vector wires; bool paused = false; - bool powerMeter = false; struct Internal; Internal *internal; diff --git a/include/event.hpp b/include/event.hpp index e4ad6e4f..82ea6547 100644 --- a/include/event.hpp +++ b/include/event.hpp @@ -153,7 +153,6 @@ struct DragMove : Event { /** Occurs every frame when the mouse is hovering over a Widget while dragging. -Must consume to allow DragEnter, DragLeave, and DragDrop to occur. */ struct DragHover : Event, Position { /** Change in mouse position since the last frame. Can be zero. */ @@ -217,6 +216,13 @@ struct Context { /** For middle-click dragging */ Widget *scrollWidget = NULL; + void setHovered(Widget *w); + void setDragged(Widget *w); + void setDragHovered(Widget *w); + void setSelected(Widget *w); + /** Prepares a widget for deletion */ + void finalizeWidget(Widget *w); + void handleButton(math::Vec pos, int button, int action, int mods); void handleHover(math::Vec pos, math::Vec mouseDelta); void handleLeave(); @@ -225,8 +231,6 @@ struct Context { void handleKey(math::Vec pos, int key, int scancode, int action, int mods); void handleDrop(math::Vec pos, std::vector paths); void handleZoom(); - /** Prepares a widget for deletion */ - void finalizeWidget(Widget *w); }; diff --git a/include/helpers.hpp b/include/helpers.hpp index 8973ef21..9b053107 100644 --- a/include/helpers.hpp +++ b/include/helpers.hpp @@ -5,6 +5,7 @@ #include "app/Port.hpp" #include "app/ParamQuantity.hpp" #include "app/ParamWidget.hpp" +#include "app/Scene.hpp" #include "engine/Module.hpp" #include "context.hpp" #include "window.hpp" @@ -154,7 +155,7 @@ inline Menu *createMenu() { MenuOverlay *menuOverlay = new MenuOverlay; menuOverlay->addChild(o); - context()->event->rootWidget->addChild(menuOverlay); + context()->scene->addChild(menuOverlay); return o; } diff --git a/include/logger.hpp b/include/logger.hpp index 0fe4b6a1..af717e91 100644 --- a/include/logger.hpp +++ b/include/logger.hpp @@ -1,5 +1,4 @@ #pragma once -#include "common.hpp" /** Example usage: diff --git a/include/rack.hpp b/include/rack.hpp index 3b61cd8f..0eb46509 100644 --- a/include/rack.hpp +++ b/include/rack.hpp @@ -3,7 +3,6 @@ #include "common.hpp" #include "math.hpp" #include "string.hpp" -#include "logger.hpp" #include "system.hpp" #include "random.hpp" #include "network.hpp" @@ -52,7 +51,6 @@ #include "app/MomentarySwitch.hpp" #include "app/MultiLightWidget.hpp" #include "app/ParamWidget.hpp" -#include "app/PluginManagerWidget.hpp" #include "app/Port.hpp" #include "app/RackRail.hpp" #include "app/Scene.hpp" diff --git a/include/settings.hpp b/include/settings.hpp index d9e043ef..bc3c0aad 100644 --- a/include/settings.hpp +++ b/include/settings.hpp @@ -10,5 +10,13 @@ void save(std::string filename); void load(std::string filename); +extern float zoom; +extern float wireOpacity; +extern float wireTension; +extern bool powerMeter; +extern bool lockModules; +extern bool checkVersion; + + } // namespace settings } // namespace rack diff --git a/include/ui/Button.hpp b/include/ui/Button.hpp index 6829c63a..d9a93f28 100644 --- a/include/ui/Button.hpp +++ b/include/ui/Button.hpp @@ -24,7 +24,6 @@ struct Button : OpaqueWidget { void draw(NVGcontext *vg) override { bndToolButton(vg, 0.0, 0.0, box.size.x, box.size.y, BND_CORNER_NONE, state, -1, text.c_str()); - Widget::draw(vg); } void onEnter(event::Enter &e) override { diff --git a/include/ui/Menu.hpp b/include/ui/Menu.hpp index 7043d116..e046d32b 100644 --- a/include/ui/Menu.hpp +++ b/include/ui/Menu.hpp @@ -13,68 +13,12 @@ struct Menu : OpaqueWidget { /** The entry which created the child menu */ MenuEntry *activeEntry = NULL; - Menu() { - box.size = math::Vec(0, 0); - } - - ~Menu() { - setChildMenu(NULL); - } - - /** Deprecated. Just use addChild(child) instead */ - DEPRECATED void pushChild(Widget *child) { - addChild(child); - } - - void setChildMenu(Menu *menu) { - if (childMenu) { - if (childMenu->parent) - childMenu->parent->removeChild(childMenu); - delete childMenu; - childMenu = NULL; - } - if (menu) { - childMenu = menu; - assert(parent); - parent->addChild(childMenu); - } - } - - void step() override { - Widget::step(); - - // Set positions of children - box.size = math::Vec(0, 0); - for (Widget *child : children) { - if (!child->visible) - continue; - // Increment height, set position of child - child->box.pos = math::Vec(0, box.size.y); - box.size.y += child->box.size.y; - // Increase width based on maximum width of child - if (child->box.size.x > box.size.x) { - box.size.x = child->box.size.x; - } - } - - // Resize widths of children - for (Widget *child : children) { - child->box.size.x = box.size.x; - } - } - - void draw(NVGcontext *vg) override { - bndMenuBackground(vg, 0.0, 0.0, box.size.x, box.size.y, BND_CORNER_NONE); - Widget::draw(vg); - } - - void onHoverScroll(event::HoverScroll &e) override { - if (!parent) - return; - if (!parent->box.contains(box)) - box.pos.y += e.scrollDelta.y; - // e.consumed = true; - } + Menu(); + ~Menu(); + void setChildMenu(Menu *menu); + void step() override; + void draw(NVGcontext *vg) override; + void onHoverScroll(event::HoverScroll &e) override; }; diff --git a/include/ui/MenuItem.hpp b/include/ui/MenuItem.hpp index 119cee2c..637a0d17 100644 --- a/include/ui/MenuItem.hpp +++ b/include/ui/MenuItem.hpp @@ -15,63 +15,14 @@ namespace rack { struct MenuItem : MenuEntry { std::string text; std::string rightText; + bool disabled = false; - void draw(NVGcontext *vg) override { - // Get state - BNDwidgetState state = (context()->event->hoveredWidget == this) ? BND_HOVER : BND_DEFAULT; - Menu *parentMenu = dynamic_cast(parent); - if (parentMenu && parentMenu->activeEntry == this) { - state = BND_ACTIVE; - } - - bndMenuItem(vg, 0.0, 0.0, box.size.x, box.size.y, state, -1, text.c_str()); - - float x = box.size.x - bndLabelWidth(vg, -1, rightText.c_str()); - NVGcolor rightColor = (state == BND_DEFAULT) ? bndGetTheme()->menuTheme.textColor : bndGetTheme()->menuTheme.textSelectedColor; - bndIconLabelValue(vg, x, 0.0, box.size.x, box.size.y, -1, rightColor, BND_LEFT, BND_LABEL_FONT_SIZE, rightText.c_str(), NULL); - } - - void step() override { - // Add 10 more pixels because measurements on high-DPI screens are sometimes too small for some reason - const float rightPadding = 10.0; - // HACK use context()->window->vg from the window. - // All this does is inspect the font, so it shouldn't modify context()->window->vg and should work when called from a FramebufferWidget for example. - box.size.x = bndLabelWidth(context()->window->vg, -1, text.c_str()) + bndLabelWidth(context()->window->vg, -1, rightText.c_str()) + rightPadding; - Widget::step(); - } - + void draw(NVGcontext *vg) override; + void step() override; + void onEnter(event::Enter &e) override; + void onDragDrop(event::DragDrop &e) override; + void doAction(); virtual Menu *createChildMenu() {return NULL;} - - void onEnter(event::Enter &e) override { - Menu *parentMenu = dynamic_cast(parent); - if (!parentMenu) - return; - - parentMenu->activeEntry = NULL; - - // Try to create child menu - Menu *childMenu = createChildMenu(); - if (childMenu) { - parentMenu->activeEntry = this; - childMenu->box.pos = parent->box.pos.plus(box.getTopRight()); - } - parentMenu->setChildMenu(childMenu); - } - - void onDragDrop(event::DragDrop &e) override { - if (e.origin != this) - return; - - event::Action eAction; - // Consume event by default, but allow action to un-consume it to prevent the menu from being removed. - eAction.target = this; - onAction(eAction); - if (!eAction.target) - return; - - Widget *overlay = getAncestorOfType(); - overlay->requestedDelete = true; - } }; diff --git a/include/ui/SequentialLayout.hpp b/include/ui/SequentialLayout.hpp index 3afee106..d98d2dbe 100644 --- a/include/ui/SequentialLayout.hpp +++ b/include/ui/SequentialLayout.hpp @@ -13,6 +13,7 @@ struct SequentialLayout : virtual Widget { VERTICAL_ORIENTATION, }; Orientation orientation = HORIZONTAL_ORIENTATION; + enum Alignment { LEFT_ALIGNMENT, CENTER_ALIGNMENT, diff --git a/include/ui/TextField.hpp b/include/ui/TextField.hpp index 8c29a11a..6abc2d1b 100644 --- a/include/ui/TextField.hpp +++ b/include/ui/TextField.hpp @@ -19,202 +19,20 @@ struct TextField : OpaqueWidget { */ int selection = 0; - TextField() { - box.size.y = BND_WIDGET_HEIGHT; - } - - void draw(NVGcontext *vg) override { - nvgScissor(vg, 0, 0, box.size.x, box.size.y); - - BNDwidgetState state; - if (this == context()->event->selectedWidget) - state = BND_ACTIVE; - else if (this == context()->event->hoveredWidget) - state = BND_HOVER; - else - state = BND_DEFAULT; - - int begin = std::min(cursor, selection); - int end = std::max(cursor, selection); - bndTextField(vg, 0.0, 0.0, box.size.x, box.size.y, BND_CORNER_NONE, state, -1, text.c_str(), begin, end); - // Draw placeholder text - if (text.empty() && state != BND_ACTIVE) { - bndIconLabelCaret(vg, 0.0, 0.0, box.size.x, box.size.y, -1, bndGetTheme()->textFieldTheme.itemColor, 13, placeholder.c_str(), bndGetTheme()->textFieldTheme.itemColor, 0, -1); - } - - nvgResetScissor(vg); - } - - void onButton(event::Button &e) override { - if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT) { - cursor = selection = getTextPosition(e.pos); - } - OpaqueWidget::onButton(e); - } - - void onHover(event::Hover &e) override { - if (this == context()->event->draggedWidget) { - int pos = getTextPosition(e.pos); - if (pos != selection) { - cursor = pos; - } - } - OpaqueWidget::onHover(e); - } - - void onEnter(event::Enter &e) override { - e.target = this; - } - - void onSelectText(event::SelectText &e) override { - if (e.codepoint < 128) { - std::string newText(1, (char) e.codepoint); - insertText(newText); - } - e.target = this; - } - - void onSelectKey(event::SelectKey &e) override { - switch (e.key) { - case GLFW_KEY_BACKSPACE: { - if (cursor == selection) { - cursor--; - if (cursor >= 0) { - text.erase(cursor, 1); - event::Change eChange; - onChange(eChange); - } - selection = cursor; - } - else { - int begin = std::min(cursor, selection); - text.erase(begin, std::abs(selection - cursor)); - event::Change eChange; - onChange(eChange); - cursor = selection = begin; - } - } break; - case GLFW_KEY_DELETE: { - if (cursor == selection) { - text.erase(cursor, 1); - event::Change eChange; - onChange(eChange); - } - else { - int begin = std::min(cursor, selection); - text.erase(begin, std::abs(selection - cursor)); - event::Change eChange; - onChange(eChange); - cursor = selection = begin; - } - } break; - case GLFW_KEY_LEFT: { - if (context()->window->isModPressed()) { - while (--cursor > 0) { - if (text[cursor] == ' ') - break; - } - } - else { - cursor--; - } - if (!context()->window->isShiftPressed()) { - selection = cursor; - } - } break; - case GLFW_KEY_RIGHT: { - if (context()->window->isModPressed()) { - while (++cursor < (int) text.size()) { - if (text[cursor] == ' ') - break; - } - } - else { - cursor++; - } - if (!context()->window->isShiftPressed()) { - selection = cursor; - } - } break; - case GLFW_KEY_HOME: { - selection = cursor = 0; - } break; - case GLFW_KEY_END: { - selection = cursor = text.size(); - } break; - case GLFW_KEY_V: { - if (context()->window->isModPressed()) { - const char *newText = glfwGetClipboardString(context()->window->win); - if (newText) - insertText(newText); - } - } break; - case GLFW_KEY_X: { - if (context()->window->isModPressed()) { - if (cursor != selection) { - int begin = std::min(cursor, selection); - std::string selectedText = text.substr(begin, std::abs(selection - cursor)); - glfwSetClipboardString(context()->window->win, selectedText.c_str()); - insertText(""); - } - } - } break; - case GLFW_KEY_C: { - if (context()->window->isModPressed()) { - if (cursor != selection) { - int begin = std::min(cursor, selection); - std::string selectedText = text.substr(begin, std::abs(selection - cursor)); - glfwSetClipboardString(context()->window->win, selectedText.c_str()); - } - } - } break; - case GLFW_KEY_A: { - if (context()->window->isModPressed()) { - selection = 0; - cursor = text.size(); - } - } break; - case GLFW_KEY_ENTER: { - if (multiline) { - insertText("\n"); - } - else { - event::Action eAction; - onAction(eAction); - } - } break; - } - - cursor = math::clamp(cursor, 0, (int) text.size()); - selection = math::clamp(selection, 0, (int) text.size()); - e.target = this; - } + TextField(); + void draw(NVGcontext *vg) override; + void onButton(event::Button &e) override; + void onHover(event::Hover &e) override; + void onEnter(event::Enter &e) override; + void onSelectText(event::SelectText &e) override; + void onSelectKey(event::SelectKey &e) override; /** Inserts text at the cursor, replacing the selection if necessary */ - void insertText(std::string text) { - if (cursor != selection) { - int begin = std::min(cursor, selection); - this->text.erase(begin, std::abs(selection - cursor)); - cursor = selection = begin; - } - this->text.insert(cursor, text); - cursor += text.size(); - selection = cursor; - event::Change eChange; - onChange(eChange); - } + void insertText(std::string text); /** Replaces the entire text */ - void setText(std::string text) { - this->text = text; - selection = cursor = text.size(); - event::Change eChange; - onChange(eChange); - } - - virtual int getTextPosition(math::Vec mousePos) { - return bndTextFieldTextPosition(context()->window->vg, 0.0, 0.0, box.size.x, box.size.y, -1, text.c_str(), mousePos.x, mousePos.y); - } + void setText(std::string text); + virtual int getTextPosition(math::Vec mousePos); }; diff --git a/src/app/Knob.cpp b/src/app/Knob.cpp index ecdc0f48..030c772a 100644 --- a/src/app/Knob.cpp +++ b/src/app/Knob.cpp @@ -6,7 +6,6 @@ namespace rack { void Knob::onDragStart(event::DragStart &e) { context()->window->cursorLock(); - e.target = this; } void Knob::onDragEnd(event::DragEnd &e) { diff --git a/src/app/ModuleBrowser.cpp b/src/app/ModuleBrowser.cpp index d8d35931..caf14fd8 100644 --- a/src/app/ModuleBrowser.cpp +++ b/src/app/ModuleBrowser.cpp @@ -12,7 +12,6 @@ #include "ui/TextField.hpp" #include "plugin.hpp" #include "context.hpp" -#include "logger.hpp" static const float itemMargin = 2.0; diff --git a/src/app/ModuleWidget.cpp b/src/app/ModuleWidget.cpp index 933d1bba..a4225ac4 100644 --- a/src/app/ModuleWidget.cpp +++ b/src/app/ModuleWidget.cpp @@ -1,11 +1,11 @@ #include "app/ModuleWidget.hpp" #include "engine/Engine.hpp" -#include "logger.hpp" #include "system.hpp" #include "asset.hpp" #include "app/Scene.hpp" #include "helpers.hpp" #include "context.hpp" +#include "settings.hpp" #include "osdialog.h" @@ -255,7 +255,7 @@ void ModuleWidget::draw(NVGcontext *vg) { Widget::draw(vg); // Power meter - if (module && context()->engine->powerMeter) { + if (module && settings::powerMeter) { nvgBeginPath(vg); nvgRect(vg, 0, box.size.y - 20, @@ -372,7 +372,7 @@ void ModuleWidget::onDragEnd(event::DragEnd &e) { } void ModuleWidget::onDragMove(event::DragMove &e) { - if (!context()->scene->rackWidget->lockModules) { + if (!settings::lockModules) { math::Rect newBox = box; newBox.pos = context()->scene->rackWidget->lastMousePos.minus(dragPos); context()->scene->rackWidget->requestModuleBoxNearest(this, newBox); diff --git a/src/app/ParamQuantity.cpp b/src/app/ParamQuantity.cpp index 49485f3d..3146bb90 100644 --- a/src/app/ParamQuantity.cpp +++ b/src/app/ParamQuantity.cpp @@ -41,6 +41,10 @@ float ParamQuantity::getDisplayValue() { // Linear return getParam()->value * getParam()->displayMultiplier; } + else if (getParam()->displayBase == 1.f) { + // Fixed (special case of exponential) + return getParam()->displayMultiplier; + } else { // Exponential return std::pow(getParam()->displayBase, getParam()->value) * getParam()->displayMultiplier; @@ -52,6 +56,10 @@ void ParamQuantity::setDisplayValue(float displayValue) { // Linear getParam()->value = displayValue / getParam()->displayMultiplier; } + else if (getParam()->displayBase == 1.f) { + // Fixed + getParam()->value = getParam()->displayMultiplier; + } else { // Exponential getParam()->value = std::log(displayValue / getParam()->displayMultiplier) / std::log(getParam()->displayBase); diff --git a/src/app/PluginManagerWidget.cpp b/src/app/PluginManagerWidget.cpp deleted file mode 100644 index 3822c3a6..00000000 --- a/src/app/PluginManagerWidget.cpp +++ /dev/null @@ -1,241 +0,0 @@ -#include -#include "system.hpp" -#include "app/PluginManagerWidget.hpp" -#include "ui/SequentialLayout.hpp" -#include "ui/Button.hpp" -#include "ui/ProgressBar.hpp" -#include "ui/TextField.hpp" -#include "ui/PasswordField.hpp" -#include "ui/Label.hpp" -#include "plugin.hpp" -#include "context.hpp" -#include "window.hpp" -#include "helpers.hpp" -#include "osdialog.h" - - -namespace rack { - - -struct RegisterButton : Button { - void onAction(event::Action &e) override { - std::thread t([&]() { - system::openBrowser("https://vcvrack.com/"); - }); - t.detach(); - } -}; - - -struct LogInButton : Button { - TextField *emailField; - TextField *passwordField; - void onAction(event::Action &e) override { - std::thread t([&]() { - plugin::logIn(emailField->text, passwordField->text); - }); - t.detach(); - passwordField->text = ""; - } -}; - - -struct StatusLabel : Label { - void step() override { - text = plugin::loginStatus; - } -}; - - -struct ManageButton : Button { - void onAction(event::Action &e) override { - std::thread t([&]() { - system::openBrowser("https://vcvrack.com/plugins.html"); - }); - t.detach(); - } -}; - - -struct SyncButton : Button { - bool checked = false; - /** Updates are available */ - bool available = false; - /** Plugins have been updated */ - bool completed = false; - - void step() override { - // Check for plugin update on first step() - if (!checked) { - std::thread t([this]() { - if (plugin::sync(true)) - available = true; - }); - t.detach(); - checked = true; - } - // Display message if we've completed updates - if (completed) { - if (osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, "All plugins have been updated. Close Rack and re-launch it to load new updates.")) { - context()->window->close(); - } - completed = false; - } - } - void draw(NVGcontext *vg) override { - Button::draw(vg); - if (available) { - // Notification circle - nvgBeginPath(vg); - nvgCircle(vg, 3, 3, 4.0); - nvgFillColor(vg, nvgRGBf(1.0, 0.0, 0.0)); - nvgFill(vg); - nvgStrokeColor(vg, nvgRGBf(0.5, 0.0, 0.0)); - nvgStroke(vg); - } - } - void onAction(event::Action &e) override { - available = false; - std::thread t([this]() { - if (plugin::sync(false)) - completed = true; - }); - t.detach(); - } -}; - - -struct LogOutButton : Button { - void onAction(event::Action &e) override { - plugin::logOut(); - } -}; - - -struct DownloadQuantity : Quantity { - float getValue() override { - return plugin::downloadProgress; - } - - float getDisplayValue() override { - return getValue() * 100.f; - } - - int getDisplayPrecision() override {return 0;} - - std::string getLabel() override { - return "Downloading " + plugin::downloadName; - } - - std::string getUnit() override {return "%";} -}; - - -struct DownloadProgressBar : ProgressBar { - DownloadProgressBar() { - quantity = new DownloadQuantity; - } -}; - - -struct CancelButton : Button { - void onAction(event::Action &e) override { - plugin::cancelDownload(); - } -}; - - -PluginManagerWidget::PluginManagerWidget() { - box.size.y = BND_WIDGET_HEIGHT; - - { - SequentialLayout *layout = createWidget(math::Vec(0, 0)); - layout->spacing = 5; - loginWidget = layout; - - Button *registerButton = new RegisterButton; - registerButton->box.size.x = 75; - registerButton->text = "Register"; - loginWidget->addChild(registerButton); - - TextField *emailField = new TextField; - emailField->box.size.x = 175; - emailField->placeholder = "Email"; - loginWidget->addChild(emailField); - - PasswordField *passwordField = new PasswordField; - passwordField->box.size.x = 175; - passwordField->placeholder = "Password"; - loginWidget->addChild(passwordField); - - LogInButton *logInButton = new LogInButton; - logInButton->box.size.x = 100; - logInButton->text = "Log in"; - logInButton->emailField = emailField; - logInButton->passwordField = passwordField; - loginWidget->addChild(logInButton); - - Label *label = new StatusLabel; - loginWidget->addChild(label); - - addChild(loginWidget); - } - - { - SequentialLayout *layout = createWidget(math::Vec(0, 0)); - layout->spacing = 5; - manageWidget = layout; - - Button *manageButton = new ManageButton; - manageButton->box.size.x = 125; - manageButton->text = "Manage plugins"; - manageWidget->addChild(manageButton); - - Button *syncButton = new SyncButton; - syncButton->box.size.x = 125; - syncButton->text = "Update plugins"; - manageWidget->addChild(syncButton); - - Button *logOutButton = new LogOutButton; - logOutButton->box.size.x = 100; - logOutButton->text = "Log out"; - manageWidget->addChild(logOutButton); - - addChild(manageWidget); - } - - { - SequentialLayout *layout = createWidget(math::Vec(0, 0)); - layout->spacing = 5; - downloadWidget = layout; - - ProgressBar *downloadProgress = new DownloadProgressBar; - downloadProgress->box.size.x = 300; - downloadWidget->addChild(downloadProgress); - - // Button *cancelButton = new CancelButton; - // cancelButton->box.size.x = 100; - // cancelButton->text = "Cancel"; - // downloadWidget->addChild(cancelButton); - - addChild(downloadWidget); - } -} - -void PluginManagerWidget::step() { - loginWidget->visible = false; - manageWidget->visible = false; - downloadWidget->visible = false; - - if (plugin::isDownloading) - downloadWidget->visible = true; - else if (plugin::isLoggedIn()) - manageWidget->visible = true; - else - loginWidget->visible = true; - - Widget::step(); -} - - -} // namespace rack diff --git a/src/app/RackWidget.cpp b/src/app/RackWidget.cpp index 1a0b0072..c4ac4acc 100644 --- a/src/app/RackWidget.cpp +++ b/src/app/RackWidget.cpp @@ -8,7 +8,6 @@ #include "settings.hpp" #include "asset.hpp" #include "system.hpp" -#include "logger.hpp" #include "plugin.hpp" #include "context.hpp" @@ -511,7 +510,6 @@ void RackWidget::onHover(event::Hover &e) { } void RackWidget::onButton(event::Button &e) { - DEBUG("what"); OpaqueWidget::onButton(e); if (e.target == this) { if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_RIGHT) { diff --git a/src/app/Scene.cpp b/src/app/Scene.cpp index 9028184b..c945c4ec 100644 --- a/src/app/Scene.cpp +++ b/src/app/Scene.cpp @@ -5,7 +5,6 @@ #include "app/ModuleBrowser.hpp" #include "app/RackScrollWidget.hpp" #include "context.hpp" -#include "logger.hpp" #include diff --git a/src/app/Toolbar.cpp b/src/app/Toolbar.cpp index dff5f65d..918ef68a 100644 --- a/src/app/Toolbar.cpp +++ b/src/app/Toolbar.cpp @@ -2,250 +2,531 @@ #include "window.hpp" #include "engine/Engine.hpp" #include "asset.hpp" -#include "ui/Tooltip.hpp" -#include "ui/IconButton.hpp" +#include "ui/Button.hpp" +#include "ui/MenuItem.hpp" #include "ui/SequentialLayout.hpp" #include "ui/Slider.hpp" -#include "app/PluginManagerWidget.hpp" +#include "ui/TextField.hpp" +#include "ui/PasswordField.hpp" +#include "ui/ProgressBar.hpp" #include "app/Scene.hpp" #include "context.hpp" +#include "settings.hpp" #include "helpers.hpp" +#include "system.hpp" +#include "plugin.hpp" +#include namespace rack { -struct TooltipIconButton : IconButton { - Tooltip *tooltip = NULL; - void onEnter(event::Enter &e) override { - if (!tooltip) { - tooltip = new Tooltip; - tooltip->box.pos = getAbsoluteOffset(math::Vec(0, BND_WIDGET_HEIGHT)); - tooltip->text = getTooltipText(); - context()->scene->addChild(tooltip); - } - IconButton::onEnter(e); +struct MenuButton : Button { + void step() override { + box.size.x = bndLabelWidth(context()->window->vg, -1, text.c_str()); + Widget::step(); } - void onLeave(event::Leave &e) override { - if (tooltip) { - context()->scene->removeChild(tooltip); - delete tooltip; - tooltip = NULL; - } - IconButton::onLeave(e); + void draw(NVGcontext *vg) override { + bndMenuItem(vg, 0.0, 0.0, box.size.x, box.size.y, state, -1, text.c_str()); } - virtual std::string getTooltipText() {return "";} }; -struct NewButton : TooltipIconButton { - NewButton() { - setSVG(SVG::load(asset::system("res/icons/noun_146097_cc.svg"))); + +struct NewItem : MenuItem { + NewItem() { + text = "New"; + rightText = "(" WINDOW_MOD_KEY_NAME "+N)"; } - std::string getTooltipText() override {return "New patch (" WINDOW_MOD_KEY_NAME "+N)";} void onAction(event::Action &e) override { context()->scene->rackWidget->reset(); } }; -struct OpenButton : TooltipIconButton { - OpenButton() { - setSVG(SVG::load(asset::system("res/icons/noun_31859_cc.svg"))); + +struct OpenItem : MenuItem { + OpenItem() { + text = "Open"; + rightText = "(" WINDOW_MOD_KEY_NAME "+O)"; } - std::string getTooltipText() override {return "Open patch (" WINDOW_MOD_KEY_NAME "+O)";} void onAction(event::Action &e) override { context()->scene->rackWidget->loadDialog(); } }; -struct SaveButton : TooltipIconButton { - SaveButton() { - setSVG(SVG::load(asset::system("res/icons/noun_1343816_cc.svg"))); + +struct SaveItem : MenuItem { + SaveItem() { + text = "Save"; + rightText = "(" WINDOW_MOD_KEY_NAME "+S)"; } - std::string getTooltipText() override {return "Save patch (" WINDOW_MOD_KEY_NAME "+S)";} void onAction(event::Action &e) override { context()->scene->rackWidget->saveDialog(); } }; -struct SaveAsButton : TooltipIconButton { - SaveAsButton() { - setSVG(SVG::load(asset::system("res/icons/noun_1343811_cc.svg"))); + +struct SaveAsItem : MenuItem { + SaveAsItem() { + text = "Save as"; + rightText = "(" WINDOW_MOD_KEY_NAME "+Shift+S)"; } - std::string getTooltipText() override {return "Save patch as (" WINDOW_MOD_KEY_NAME "+Shift+S)";} void onAction(event::Action &e) override { context()->scene->rackWidget->saveAsDialog(); } }; -struct RevertButton : TooltipIconButton { - RevertButton() { - setSVG(SVG::load(asset::system("res/icons/noun_1084369_cc.svg"))); + +struct RevertItem : MenuItem { + RevertItem() { + text = "Revert"; } - std::string getTooltipText() override {return "Revert patch";} void onAction(event::Action &e) override { context()->scene->rackWidget->revert(); } }; -struct DisconnectCablesButton : TooltipIconButton { - DisconnectCablesButton() { - setSVG(SVG::load(asset::system("res/icons/noun_1745061_cc.svg"))); + +struct DisconnectCablesItem : MenuItem { + DisconnectCablesItem() { + text = "Disconnect cables"; } - std::string getTooltipText() override {return "Disconnect cables";} void onAction(event::Action &e) override { context()->scene->rackWidget->disconnect(); } }; -struct PowerMeterButton : TooltipIconButton { - PowerMeterButton() { - setSVG(SVG::load(asset::system("res/icons/noun_305536_cc.svg"))); + +struct FileButton : MenuButton { + FileButton() { + text = "File"; } - std::string getTooltipText() override {return "Toggle power meter (see manual for explanation)";} void onAction(event::Action &e) override { - context()->engine->powerMeter ^= true; + Menu *menu = createMenu(); + menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y)); + menu->box.size.x = box.size.x; + + menu->addChild(new NewItem); + menu->addChild(new OpenItem); + menu->addChild(new SaveItem); + menu->addChild(new SaveAsItem); + menu->addChild(new RevertItem); + menu->addChild(new DisconnectCablesItem); } }; + +struct ZoomQuantity : Quantity { + void setValue(float value) override { + settings::zoom = math::clamp(value, getMinValue(), getMaxValue()); + } + float getValue() override { + return settings::zoom; + } + float getMinValue() override {return 0.25;} + float getMaxValue() override {return 2.0;} + float getDefaultValue() override {return 1.0;} + float getDisplayValue() override {return getValue() * 100.0;} + void setDisplayValue(float displayValue) override {setValue(displayValue / 100.0);} + std::string getLabel() override {return "Zoom";} + std::string getUnit() override {return "%";} + int getDisplayPrecision() override {return 0;} +}; + + +struct WireOpacityQuantity : Quantity { + void setValue(float value) override { + settings::wireOpacity = math::clamp(value, getMinValue(), getMaxValue()); + } + float getValue() override { + return settings::wireOpacity; + } + float getDefaultValue() override {return 0.5;} + float getDisplayValue() override {return getValue() * 100.0;} + void setDisplayValue(float displayValue) override {setValue(displayValue / 100.0);} + std::string getLabel() override {return "Cable opacity";} + std::string getUnit() override {return "%";} + int getDisplayPrecision() override {return 0;} +}; + + + +struct WireTensionQuantity : Quantity { + void setValue(float value) override { + settings::wireTension = math::clamp(value, getMinValue(), getMaxValue()); + } + float getValue() override { + return settings::wireTension; + } + float getDefaultValue() override {return 0.5;} + std::string getLabel() override {return "Cable tension";} + int getDisplayPrecision() override {return 2;} +}; + + +struct PowerMeterItem : MenuItem { + PowerMeterItem() { + text = "Power meter"; + rightText = CHECKMARK(settings::powerMeter); + } + void onAction(event::Action &e) override { + settings::powerMeter ^= true; + } +}; + + +struct LockModulesItem : MenuItem { + LockModulesItem() { + text = "Lock modules"; + rightText = CHECKMARK(settings::lockModules); + } + void onAction(event::Action &e) override { + settings::lockModules ^= true; + } +}; + + struct EnginePauseItem : MenuItem { + EnginePauseItem() { + text = "Pause engine"; + rightText = CHECKMARK(context()->engine->paused); + } void onAction(event::Action &e) override { context()->engine->paused ^= true; } }; -struct SampleRateItem : MenuItem { + +struct SampleRateValueItem : MenuItem { float sampleRate; + SampleRateValueItem(float sampleRate) { + this->sampleRate = sampleRate; + text = string::f("%.0f Hz", sampleRate); + rightText = CHECKMARK(context()->engine->getSampleRate() == sampleRate); + } void onAction(event::Action &e) override { context()->engine->setSampleRate(sampleRate); context()->engine->paused = false; } }; -struct SampleRateButton : TooltipIconButton { - SampleRateButton() { - setSVG(SVG::load(asset::system("res/icons/noun_1240789_cc.svg"))); + +struct SampleRateItem : MenuItem { + SampleRateItem() { + text = "Engine sample rate"; + } + Menu *createChildMenu() override { + Menu *menu = new Menu; + + menu->addChild(new EnginePauseItem); + + std::vector sampleRates = {44100, 48000, 88200, 96000, 176400, 192000}; + for (float sampleRate : sampleRates) { + menu->addChild(new SampleRateValueItem(sampleRate)); + } + return menu; + } +}; + + +struct SettingsButton : MenuButton { + SettingsButton() { + text = "Settings"; } - std::string getTooltipText() override {return "Engine sample rate";} void onAction(event::Action &e) override { Menu *menu = createMenu(); menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y)); menu->box.size.x = box.size.x; - menu->addChild(createMenuLabel("Engine sample rate")); + menu->addChild(new PowerMeterItem); + menu->addChild(new LockModulesItem); + menu->addChild(new SampleRateItem); - EnginePauseItem *pauseItem = new EnginePauseItem; - pauseItem->text = context()->engine->paused ? "Resume engine" : "Pause engine"; - menu->addChild(pauseItem); + Slider *zoomSlider = new Slider; + zoomSlider->box.size.x = 200.0; + zoomSlider->quantity = new ZoomQuantity; + menu->addChild(zoomSlider); - std::vector sampleRates = {44100, 48000, 88200, 96000, 176400, 192000}; - for (float sampleRate : sampleRates) { - SampleRateItem *item = new SampleRateItem; - item->text = string::f("%.0f Hz", sampleRate); - item->rightText = CHECKMARK(context()->engine->getSampleRate() == sampleRate); - item->sampleRate = sampleRate; - menu->addChild(item); + Slider *wireOpacitySlider = new Slider; + wireOpacitySlider->box.size.x = 200.0; + wireOpacitySlider->quantity = new WireOpacityQuantity; + menu->addChild(wireOpacitySlider); + + Slider *wireTensionSlider = new Slider; + wireTensionSlider->box.size.x = 200.0; + wireTensionSlider->quantity = new WireTensionQuantity; + menu->addChild(wireTensionSlider); + } +}; + + +struct RegisterItem : MenuItem { + RegisterItem() { + text = "Register VCV account"; + } + void onAction(event::Action &e) override { + std::thread t([&]() { + system::openBrowser("https://vcvrack.com/"); + }); + t.detach(); + } +}; + + +struct AccountEmailField : TextField { + TextField *passwordField; + AccountEmailField() { + placeholder = "Email"; + } + void onSelectKey(event::SelectKey &e) override { + if (e.action == GLFW_PRESS && e.key == GLFW_KEY_TAB) { + context()->event->selectedWidget = passwordField; + e.target = this; + return; } + TextField::onSelectKey(e); } }; -struct RackLockButton : TooltipIconButton { - RackLockButton() { - setSVG(SVG::load(asset::system("res/icons/noun_468341_cc.svg"))); + +struct AccountPasswordField : PasswordField { + MenuItem *logInItem; + AccountPasswordField() { + placeholder = "Password"; + } + void onSelectKey(event::SelectKey &e) override { + if (e.action == GLFW_PRESS && (e.key == GLFW_KEY_ENTER || e.key == GLFW_KEY_KP_ENTER)) { + logInItem->doAction(); + e.target = this; + return; + } + PasswordField::onSelectKey(e); + } +}; + + +struct LogInItem : MenuItem { + TextField *emailField; + TextField *passwordField; + LogInItem() { + text = "Log in"; } - std::string getTooltipText() override {return "Lock modules";} void onAction(event::Action &e) override { - context()->scene->rackWidget->lockModules ^= true; + std::string email = emailField->text; + std::string password = passwordField->text; + std::thread t([&, email, password]() { + plugin::logIn(email, password); + }); + t.detach(); } }; -struct WireOpacityQuantity : Quantity { - void setValue(float value) override { - // TODO + +struct ManageItem : MenuItem { + ManageItem() { + text = "Manage plugins"; } - float getValue() override { - return 0; + void onAction(event::Action &e) override { + std::thread t([&]() { + system::openBrowser("https://vcvrack.com/plugins.html"); + }); + t.detach(); } - float getDefaultValue() override {return 0.5;} - std::string getLabel() override {return "Cable opacity";} - int getDisplayPrecision() override {return 0;} }; -struct WireTensionQuantity : Quantity { - void setValue(float value) override { - // TODO +// struct SyncButton : Button { +// bool checked = false; +// /** Updates are available */ +// bool available = false; +// /** Plugins have been updated */ +// bool completed = false; + +// void step() override { +// // Check for plugin update on first step() +// if (!checked) { +// std::thread t([this]() { +// if (plugin::sync(true)) +// available = true; +// }); +// t.detach(); +// checked = true; +// } +// // Display message if we've completed updates +// if (completed) { +// if (osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, "All plugins have been updated. Close Rack and re-launch it to load new updates.")) { +// context()->window->close(); +// } +// completed = false; +// } +// } +// void onAction(event::Action &e) override { +// available = false; +// std::thread t([this]() { +// if (plugin::sync(false)) +// completed = true; +// }); +// t.detach(); +// } +// }; + + +struct LogOutItem : MenuItem { + LogOutItem() { + text = "Log out"; + } + void onAction(event::Action &e) override { + plugin::logOut(); } +}; + + +struct DownloadQuantity : Quantity { float getValue() override { - return 0; + return plugin::downloadProgress; } - float getDefaultValue() override {return 0.5;} - std::string getLabel() override {return "Cable tension";} + + float getDisplayValue() override { + return getValue() * 100.f; + } + int getDisplayPrecision() override {return 0;} + + std::string getLabel() override { + return "Downloading " + plugin::downloadName; + } + + std::string getUnit() override {return "%";} }; -struct ZoomQuantity : Quantity { - void setValue(float value) override { - context()->scene->zoomWidget->setZoom(std::round(value) / 100); +struct PluginsButton : MenuButton { + PluginsButton() { + text = "Plugins"; } - float getValue() override { - return context()->scene->zoomWidget->zoom * 100; + void onAction(event::Action &e) override { + Menu *menu = createMenu(); + menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y)); + menu->box.size.x = box.size.x; + + // TODO Design dialog box for plugin syncing + if (plugin::isDownloading) { + ProgressBar *downloadProgressBar = new ProgressBar; + downloadProgressBar->quantity = new DownloadQuantity; + menu->addChild(downloadProgressBar); + } + else if (plugin::isLoggedIn()) { + menu->addChild(new ManageItem); + menu->addChild(new LogOutItem); + } + else { + menu->addChild(new RegisterItem); + AccountEmailField *emailField = new AccountEmailField; + emailField->box.size.x = 200.0; + menu->addChild(emailField); + AccountPasswordField *passwordField = new AccountPasswordField; + passwordField->box.size.x = 200.0; + emailField->passwordField = passwordField; + menu->addChild(passwordField); + LogInItem *logInItem = new LogInItem; + logInItem->emailField = emailField; + logInItem->passwordField = passwordField; + passwordField->logInItem = logInItem; + menu->addChild(logInItem); + } + } + + void draw(NVGcontext *vg) override { + MenuButton::draw(vg); + // if (1) { + // // Notification circle + // nvgBeginPath(vg); + // nvgCircle(vg, box.size.x - 3, 3, 4.0); + // nvgFillColor(vg, nvgRGBf(1.0, 0.0, 0.0)); + // nvgFill(vg); + // nvgStrokeColor(vg, nvgRGBf(0.5, 0.0, 0.0)); + // nvgStroke(vg); + // } + } +}; + + +struct ManualItem : MenuItem { + ManualItem() { + text = "Manual"; + } + void onAction(event::Action &e) override { + std::thread t([&]() { + system::openBrowser("https://vcvrack.com/manual/"); + }); + t.detach(); + } +}; + + +struct WebsiteItem : MenuItem { + WebsiteItem() { + text = "VCVRack.com"; + } + void onAction(event::Action &e) override { + std::thread t([&]() { + system::openBrowser("https://vcvrack.com/"); + }); + t.detach(); + } +}; + + +struct CheckVersionItem : MenuItem { + CheckVersionItem() { + text = "Check version on launch"; + rightText = CHECKMARK(settings::checkVersion); + } + void onAction(event::Action &e) override { + settings::checkVersion ^= true; + } +}; + + +struct HelpButton : MenuButton { + HelpButton() { + text = "Help"; + } + void onAction(event::Action &e) override { + Menu *menu = createMenu(); + menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y)); + menu->box.size.x = box.size.x; + + menu->addChild(new ManualItem); + menu->addChild(new WebsiteItem); + menu->addChild(new CheckVersionItem); } - float getMinValue() override {return 25;} - float getMaxValue() override {return 200;} - float getDefaultValue() override {return 100;} - std::string getLabel() override {return "Zoom";} - std::string getUnit() override {return "%";} - int getDisplayPrecision() override {return 0;} }; Toolbar::Toolbar() { - box.size.y = BND_WIDGET_HEIGHT + 2*5; + const float margin = 5; + box.size.y = BND_WIDGET_HEIGHT + 2*margin; SequentialLayout *layout = new SequentialLayout; - layout->box.pos = math::Vec(5, 5); - layout->spacing = 5; + layout->box.pos = math::Vec(margin, margin); + layout->spacing = 0.0; addChild(layout); - layout->addChild(new NewButton); - layout->addChild(new OpenButton); - layout->addChild(new SaveButton); - layout->addChild(new SaveAsButton); - layout->addChild(new RevertButton); - layout->addChild(new DisconnectCablesButton); - - layout->addChild(new SampleRateButton); - layout->addChild(new PowerMeterButton); - layout->addChild(new RackLockButton); - - Slider *wireOpacitySlider = new Slider; - WireOpacityQuantity *wireOpacityQuantity = new WireOpacityQuantity; - wireOpacitySlider->quantity = wireOpacityQuantity; - wireOpacitySlider->box.size.x = 150; - layout->addChild(wireOpacitySlider); - - Slider *wireTensionSlider = new Slider; - WireTensionQuantity *wireTensionQuantity = new WireTensionQuantity; - wireTensionSlider->quantity = wireTensionQuantity; - wireTensionSlider->box.size.x = 150; - layout->addChild(wireTensionSlider); - - Slider *zoomSlider = new Slider; - ZoomQuantity *zoomQuantity = new ZoomQuantity; - zoomSlider->quantity = zoomQuantity; - zoomSlider->box.size.x = 150; - layout->addChild(zoomSlider); - - // Kind of hacky, but display the PluginManagerWidget only if the user directory is not the development directory - if (asset::user("") != "./") { - Widget *pluginManager = new PluginManagerWidget; - layout->addChild(pluginManager); - } + FileButton *fileButton = new FileButton; + layout->addChild(fileButton); + + SettingsButton *settingsButton = new SettingsButton; + layout->addChild(settingsButton); + + PluginsButton *pluginsButton = new PluginsButton; + layout->addChild(pluginsButton); + + HelpButton *helpButton = new HelpButton; + layout->addChild(helpButton); } void Toolbar::draw(NVGcontext *vg) { - bndBackground(vg, 0.0, 0.0, box.size.x, box.size.y); + bndMenuBackground(vg, 0.0, 0.0, box.size.x, box.size.y, 0); bndBevel(vg, 0.0, 0.0, box.size.x, box.size.y); Widget::draw(vg); diff --git a/src/app/WireWidget.cpp b/src/app/WireWidget.cpp index fb5651dd..70c98ea4 100644 --- a/src/app/WireWidget.cpp +++ b/src/app/WireWidget.cpp @@ -5,6 +5,7 @@ #include "window.hpp" #include "event.hpp" #include "context.hpp" +#include "settings.hpp" namespace rack { @@ -162,10 +163,8 @@ void WireWidget::fromJson(json_t *rootJ) { } void WireWidget::draw(NVGcontext *vg) { - // float opacity = gToolbar->wireOpacitySlider->value / 100.0; - // float tension = gToolbar->wireTensionSlider->value; - float opacity = 0.5; - float tension = 0.5; + float opacity = settings::wireOpacity; + float tension = settings::wireTension; WireWidget *activeWire = context()->scene->rackWidget->wireContainer->activeWire; if (activeWire) { diff --git a/src/audio.cpp b/src/audio.cpp index 3960237e..ab9e8c17 100644 --- a/src/audio.cpp +++ b/src/audio.cpp @@ -1,5 +1,4 @@ #include "audio.hpp" -#include "logger.hpp" #include "string.hpp" #include "math.hpp" #include "bridge.hpp" diff --git a/src/bridge.cpp b/src/bridge.cpp index 71c6b1fd..2d07c8f8 100644 --- a/src/bridge.cpp +++ b/src/bridge.cpp @@ -1,6 +1,5 @@ #include "bridge.hpp" #include "string.hpp" -#include "logger.hpp" #include "dsp/ringbuffer.hpp" #include diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp index 40b4e9c0..0718a289 100644 --- a/src/engine/Engine.cpp +++ b/src/engine/Engine.cpp @@ -1,4 +1,5 @@ #include "engine/Engine.hpp" +#include "settings.hpp" #include #include @@ -125,7 +126,7 @@ static void Engine_step(Engine *engine) { // Step modules for (Module *module : engine->modules) { - if (engine->powerMeter) { + if (settings::powerMeter) { auto startTime = std::chrono::high_resolution_clock::now(); module->step(); diff --git a/src/event.cpp b/src/event.cpp index c56ec9dc..f224b015 100644 --- a/src/event.cpp +++ b/src/event.cpp @@ -1,12 +1,95 @@ #include "event.hpp" #include "widgets/Widget.hpp" -#include "logger.hpp" namespace rack { namespace event { +void Context::setHovered(Widget *w) { + if (w == hoveredWidget) + return; + + if (hoveredWidget) { + // event::Leave + event::Leave eLeave; + hoveredWidget->onLeave(eLeave); + } + + hoveredWidget = w; + + if (hoveredWidget) { + // event::Enter + event::Enter eEnter; + hoveredWidget->onEnter(eEnter); + } +} + +void Context::setDragged(Widget *w) { + if (w == draggedWidget) + return; + + if (draggedWidget) { + // event::DragEnd + event::DragEnd eDragEnd; + draggedWidget->onDragEnd(eDragEnd); + } + + draggedWidget = w; + + if (draggedWidget) { + // event::DragStart + event::DragStart eDragStart; + draggedWidget->onDragStart(eDragStart); + } +} + +void Context::setDragHovered(Widget *w) { + if (w == dragHoveredWidget) + return; + + if (dragHoveredWidget) { + // event::DragLeave + event::DragLeave eDragLeave; + dragHoveredWidget->onDragLeave(eDragLeave); + } + + dragHoveredWidget = w; + + if (dragHoveredWidget) { + // event::DragEnter + event::DragEnter eDragEnter; + dragHoveredWidget->onDragEnter(eDragEnter); + } +} + +void Context::setSelected(Widget *w) { + if (w == selectedWidget) + return; + + if (selectedWidget) { + // event::Deselect + event::Deselect eDeselect; + selectedWidget->onDeselect(eDeselect); + } + + selectedWidget = w; + + if (selectedWidget) { + // event::Select + event::Select eSelect; + selectedWidget->onSelect(eSelect); + } +} + +void Context::finalizeWidget(Widget *w) { + if (hoveredWidget == w) setHovered(NULL); + if (draggedWidget == w) setDragged(NULL); + if (dragHoveredWidget == w) setDragHovered(NULL); + if (selectedWidget == w) setSelected(NULL); + if (scrollWidget == w) scrollWidget = NULL; +} + void Context::handleButton(math::Vec pos, int button, int action, int mods) { // event::Button event::Button eButton; @@ -18,48 +101,25 @@ void Context::handleButton(math::Vec pos, int button, int action, int mods) { Widget *clickedWidget = eButton.target; if (button == GLFW_MOUSE_BUTTON_LEFT) { - if (action == GLFW_PRESS && !draggedWidget && clickedWidget) { - // event::DragStart - event::DragStart eDragStart; - clickedWidget->onDragStart(eDragStart); - draggedWidget = eDragStart.target; + if (action == GLFW_PRESS) { + setDragged(clickedWidget); } - if (action == GLFW_RELEASE && draggedWidget) { - if (dragHoveredWidget) { - // event::DragLeave - event::DragLeave eDragLeave; - dragHoveredWidget->onDragLeave(eDragLeave); - } + if (action == GLFW_RELEASE) { + setDragHovered(NULL); - if (clickedWidget) { + if (clickedWidget && draggedWidget) { // event::DragDrop event::DragDrop eDragDrop; eDragDrop.origin = draggedWidget; clickedWidget->onDragDrop(eDragDrop); } - // event::DragEnd - event::DragEnd eDragEnd; - draggedWidget->onDragEnd(eDragEnd); - draggedWidget = NULL; - dragHoveredWidget = NULL; + setDragged(NULL); } - if (action == GLFW_PRESS && clickedWidget != selectedWidget) { - if (selectedWidget) { - // event::Deselect - event::Deselect eDeselect; - selectedWidget->onDeselect(eDeselect); - } - - selectedWidget = clickedWidget; - - if (selectedWidget) { - // event::Select - event::Select eSelect; - selectedWidget->onSelect(eSelect); - } + if (action == GLFW_PRESS) { + setSelected(clickedWidget); } } @@ -73,7 +133,6 @@ void Context::handleButton(math::Vec pos, int button, int action, int mods) { // } } - void Context::handleHover(math::Vec pos, math::Vec mouseDelta) { if (draggedWidget) { // event::DragMove @@ -86,23 +145,8 @@ void Context::handleHover(math::Vec pos, math::Vec mouseDelta) { eDragHover.pos = pos; eDragHover.mouseDelta = mouseDelta; rootWidget->onDragHover(eDragHover); - Widget *newDragHoveredWidget = eDragHover.target; - - if (newDragHoveredWidget != dragHoveredWidget) { - if (dragHoveredWidget) { - // event::DragLeave - event::DragLeave eDragLeave; - dragHoveredWidget->onDragLeave(eDragLeave); - } - dragHoveredWidget = newDragHoveredWidget; - - if (dragHoveredWidget) { - // event::DragEnter - event::DragEnter eDragEnter; - dragHoveredWidget->onDragEnter(eDragEnter); - } - } + setDragHovered(eDragHover.target); return; } @@ -120,32 +164,13 @@ void Context::handleHover(math::Vec pos, math::Vec mouseDelta) { eHover.pos = pos; eHover.mouseDelta = mouseDelta; rootWidget->onHover(eHover); - Widget *newHoveredWidget = eHover.target; - - if (newHoveredWidget != hoveredWidget) { - if (hoveredWidget) { - // event::Leave - event::Leave eLeave; - hoveredWidget->onLeave(eLeave); - } - hoveredWidget = newHoveredWidget; - - if (hoveredWidget) { - // event::Enter - event::Enter eEnter; - hoveredWidget->onEnter(eEnter); - } - } + setHovered(eHover.target); } void Context::handleLeave() { - if (hoveredWidget) { - // event::Leave - event::Leave eLeave; - hoveredWidget->onLeave(eLeave); - } - hoveredWidget = NULL; + setDragHovered(NULL); + setHovered(NULL); } void Context::handleScroll(math::Vec pos, math::Vec scrollDelta) { @@ -204,14 +229,6 @@ void Context::handleKey(math::Vec pos, int key, int scancode, int action, int mo rootWidget->onHoverKey(eHoverKey); } -void Context::finalizeWidget(Widget *w) { - if (hoveredWidget == w) hoveredWidget = NULL; - if (draggedWidget == w) draggedWidget = NULL; - if (dragHoveredWidget == w) dragHoveredWidget = NULL; - if (selectedWidget == w) selectedWidget = NULL; - if (scrollWidget == w) scrollWidget = NULL; -} - void Context::handleZoom() { // event::Zoom event::Zoom eZoom; diff --git a/src/logger.cpp b/src/logger.cpp index cb1bb234..97b19b3a 100644 --- a/src/logger.cpp +++ b/src/logger.cpp @@ -1,4 +1,4 @@ -#include "logger.hpp" +#include "common.hpp" #include "asset.hpp" #include diff --git a/src/main.cpp b/src/main.cpp index 03750ce2..590caee3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,6 +1,5 @@ #include "common.hpp" #include "random.hpp" -#include "logger.hpp" #include "asset.hpp" #include "rtmidi.hpp" #include "keyboard.hpp" diff --git a/src/plugin.cpp b/src/plugin.cpp index a549b22f..4864ac7b 100644 --- a/src/plugin.cpp +++ b/src/plugin.cpp @@ -1,6 +1,5 @@ #include "plugin.hpp" #include "system.hpp" -#include "logger.hpp" #include "network.hpp" #include "asset.hpp" #include "string.hpp" diff --git a/src/plugin/Model.cpp b/src/plugin/Model.cpp index da5f5a15..6f6f64d6 100644 --- a/src/plugin/Model.cpp +++ b/src/plugin/Model.cpp @@ -1,5 +1,4 @@ #include "plugin/Model.hpp" -#include "logger.hpp" namespace rack { diff --git a/src/plugin/Plugin.cpp b/src/plugin/Plugin.cpp index ac36f479..28894b1e 100644 --- a/src/plugin/Plugin.cpp +++ b/src/plugin/Plugin.cpp @@ -1,6 +1,5 @@ #include "plugin/Plugin.hpp" #include "plugin/Model.hpp" -#include "logger.hpp" namespace rack { diff --git a/src/settings.cpp b/src/settings.cpp index 349ceb8e..5d42baf1 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -1,5 +1,4 @@ #include "settings.hpp" -#include "logger.hpp" #include "window.hpp" #include "plugin.hpp" #include "app/Scene.hpp" @@ -33,18 +32,15 @@ static json_t *settingsToJson() { json_object_set_new(rootJ, "windowPos", windowPosJ); } - // opacity - float opacity = context()->scene->toolbar->wireOpacity; - json_t *opacityJ = json_real(opacity); - json_object_set_new(rootJ, "wireOpacity", opacityJ); + // wireOpacity + json_t *wireOpacityJ = json_real(wireOpacity); + json_object_set_new(rootJ, "wireOpacity", wireOpacityJ); - // tension - float tension = context()->scene->toolbar->wireTension; - json_t *tensionJ = json_real(tension); - json_object_set_new(rootJ, "wireTension", tensionJ); + // wireTension + json_t *wireTensionJ = json_real(wireTension); + json_object_set_new(rootJ, "wireTension", wireTensionJ); // zoom - float zoom = context()->scene->zoomWidget->zoom; json_t *zoomJ = json_real(zoom); json_object_set_new(rootJ, "zoom", zoomJ); @@ -69,10 +65,10 @@ static json_t *settingsToJson() { json_object_set_new(rootJ, "moduleBrowser", moduleBrowserToJson()); // powerMeter - json_object_set_new(rootJ, "powerMeter", json_boolean(context()->engine->powerMeter)); + json_object_set_new(rootJ, "powerMeter", json_boolean(powerMeter)); // checkVersion - json_object_set_new(rootJ, "checkVersion", json_boolean(context()->scene->checkVersion)); + json_object_set_new(rootJ, "checkVersion", json_boolean(checkVersion)); return rootJ; } @@ -99,21 +95,20 @@ static void settingsFromJson(json_t *rootJ) { context()->window->setWindowPos(math::Vec(x, y)); } - // opacity - json_t *opacityJ = json_object_get(rootJ, "wireOpacity"); - if (opacityJ) - context()->scene->toolbar->wireOpacity = json_number_value(opacityJ); + // wireOpacity + json_t *wireOpacityJ = json_object_get(rootJ, "wireOpacity"); + if (wireOpacityJ) + wireOpacity = json_number_value(wireOpacityJ); // tension json_t *tensionJ = json_object_get(rootJ, "wireTension"); if (tensionJ) - context()->scene->toolbar->wireTension = json_number_value(tensionJ); + wireTension = json_number_value(tensionJ); // zoom json_t *zoomJ = json_object_get(rootJ, "zoom"); - if (zoomJ) { - context()->scene->zoomWidget->setZoom(math::clamp((float) json_number_value(zoomJ), 0.25f, 4.0f)); - } + if (zoomJ) + zoom = json_number_value(zoomJ); // allowCursorLock json_t *allowCursorLockJ = json_object_get(rootJ, "allowCursorLock"); @@ -145,12 +140,12 @@ static void settingsFromJson(json_t *rootJ) { // powerMeter json_t *powerMeterJ = json_object_get(rootJ, "powerMeter"); if (powerMeterJ) - context()->engine->powerMeter = json_boolean_value(powerMeterJ); + powerMeter = json_boolean_value(powerMeterJ); // checkVersion json_t *checkVersionJ = json_object_get(rootJ, "checkVersion"); if (checkVersionJ) - context()->scene->checkVersion = json_boolean_value(checkVersionJ); + checkVersion = json_boolean_value(checkVersionJ); } @@ -188,5 +183,13 @@ void load(std::string filename) { } +float zoom = 1.0; +float wireOpacity = 0.5; +float wireTension = 0.5; +bool powerMeter = false; +bool lockModules = false; +bool checkVersion = true; + + } // namespace settings } // namespace rack diff --git a/src/ui/Menu.cpp b/src/ui/Menu.cpp new file mode 100644 index 00000000..3be22bbd --- /dev/null +++ b/src/ui/Menu.cpp @@ -0,0 +1,62 @@ +#include "ui/Menu.hpp" + +namespace rack { + + +Menu::Menu() { + box.size = math::Vec(0, 0); +} + +Menu::~Menu() { + setChildMenu(NULL); +} + +void Menu::setChildMenu(Menu *menu) { + if (childMenu) { + if (childMenu->parent) + childMenu->parent->removeChild(childMenu); + delete childMenu; + childMenu = NULL; + } + if (menu) { + childMenu = menu; + assert(parent); + parent->addChild(childMenu); + } +} + +void Menu::step() { + Widget::step(); + + // Set positions of children + box.size = math::Vec(0, 0); + for (Widget *child : children) { + if (!child->visible) + continue; + // Increment height, set position of child + child->box.pos = math::Vec(0, box.size.y); + box.size.y += child->box.size.y; + // Increase width based on maximum width of child + if (child->box.size.x > box.size.x) { + box.size.x = child->box.size.x; + } + } + + // Set widths of all children to maximum width + for (Widget *child : children) { + child->box.size.x = box.size.x; + } +} + +void Menu::draw(NVGcontext *vg) { + bndMenuBackground(vg, 0.0, 0.0, box.size.x, box.size.y, BND_CORNER_NONE); + Widget::draw(vg); +} + +void Menu::onHoverScroll(event::HoverScroll &e) { + if (parent && !parent->box.contains(box)) + box.pos.y += e.scrollDelta.y; +} + + +} // namespace rack diff --git a/src/ui/MenuItem.cpp b/src/ui/MenuItem.cpp new file mode 100644 index 00000000..ab8e6eb0 --- /dev/null +++ b/src/ui/MenuItem.cpp @@ -0,0 +1,69 @@ +#include "ui/MenuItem.hpp" + +namespace rack { + + +void MenuItem::draw(NVGcontext *vg) { + // Get state + BNDwidgetState state = (context()->event->hoveredWidget == this) ? BND_HOVER : BND_DEFAULT; + // Set active state if this MenuItem + Menu *parentMenu = dynamic_cast(parent); + if (parentMenu && parentMenu->activeEntry == this) { + state = BND_ACTIVE; + } + + bndMenuItem(vg, 0.0, 0.0, box.size.x, box.size.y, state, -1, text.c_str()); + + float x = box.size.x - bndLabelWidth(vg, -1, rightText.c_str()); + NVGcolor rightColor = (state == BND_DEFAULT) ? bndGetTheme()->menuTheme.textColor : bndGetTheme()->menuTheme.textSelectedColor; + bndIconLabelValue(vg, x, 0.0, box.size.x, box.size.y, -1, rightColor, BND_LEFT, BND_LABEL_FONT_SIZE, rightText.c_str(), NULL); +} + +void MenuItem::step() { + // Add 10 more pixels because measurements on high-DPI screens are sometimes too small for some reason + const float rightPadding = 10.0; + // HACK use context()->window->vg from the window. + // All this does is inspect the font, so it shouldn't modify context()->window->vg and should work when called from a FramebufferWidget for example. + box.size.x = bndLabelWidth(context()->window->vg, -1, text.c_str()) + bndLabelWidth(context()->window->vg, -1, rightText.c_str()) + rightPadding; + Widget::step(); +} + +void MenuItem::onEnter(event::Enter &e) { + Menu *parentMenu = dynamic_cast(parent); + if (!parentMenu) + return; + + parentMenu->activeEntry = NULL; + + // Try to create child menu + Menu *childMenu = createChildMenu(); + if (childMenu) { + parentMenu->activeEntry = this; + childMenu->box.pos = parent->box.pos.plus(box.getTopRight()); + } + parentMenu->setChildMenu(childMenu); +} + +void MenuItem::onDragDrop(event::DragDrop &e) { + if (e.origin != this) + return; + doAction(); +} + +void MenuItem::doAction() { + if (disabled) + return; + + event::Action eAction; + // Consume event by default, but allow action to un-consume it to prevent the menu from being removed. + eAction.target = this; + onAction(eAction); + if (!eAction.target) + return; + + Widget *overlay = getAncestorOfType(); + overlay->requestedDelete = true; +} + + +} // namespace rack diff --git a/src/ui/TextField.cpp b/src/ui/TextField.cpp new file mode 100644 index 00000000..f6a13e35 --- /dev/null +++ b/src/ui/TextField.cpp @@ -0,0 +1,206 @@ +#include "ui/TextField.hpp" + +namespace rack { + + +TextField::TextField() { + box.size.y = BND_WIDGET_HEIGHT; +} + +void TextField::draw(NVGcontext *vg) { + nvgScissor(vg, 0, 0, box.size.x, box.size.y); + + BNDwidgetState state; + if (this == context()->event->selectedWidget) + state = BND_ACTIVE; + else if (this == context()->event->hoveredWidget) + state = BND_HOVER; + else + state = BND_DEFAULT; + + int begin = std::min(cursor, selection); + int end = std::max(cursor, selection); + bndTextField(vg, 0.0, 0.0, box.size.x, box.size.y, BND_CORNER_NONE, state, -1, text.c_str(), begin, end); + // Draw placeholder text + if (text.empty() && state != BND_ACTIVE) { + bndIconLabelCaret(vg, 0.0, 0.0, box.size.x, box.size.y, -1, bndGetTheme()->textFieldTheme.itemColor, 13, placeholder.c_str(), bndGetTheme()->textFieldTheme.itemColor, 0, -1); + } + + nvgResetScissor(vg); +} + +void TextField::onButton(event::Button &e) { + if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT) { + cursor = selection = getTextPosition(e.pos); + } + OpaqueWidget::onButton(e); +} + +void TextField::onHover(event::Hover &e) { + if (this == context()->event->draggedWidget) { + int pos = getTextPosition(e.pos); + if (pos != selection) { + cursor = pos; + } + } + OpaqueWidget::onHover(e); +} + +void TextField::onEnter(event::Enter &e) { + e.target = this; +} + +void TextField::onSelectText(event::SelectText &e) { + if (e.codepoint < 128) { + std::string newText(1, (char) e.codepoint); + insertText(newText); + } + e.target = this; +} + +void TextField::onSelectKey(event::SelectKey &e) { + if (e.action == GLFW_PRESS) { + switch (e.key) { + case GLFW_KEY_BACKSPACE: { + if (cursor == selection) { + cursor--; + if (cursor >= 0) { + text.erase(cursor, 1); + event::Change eChange; + onChange(eChange); + } + selection = cursor; + } + else { + int begin = std::min(cursor, selection); + text.erase(begin, std::abs(selection - cursor)); + event::Change eChange; + onChange(eChange); + cursor = selection = begin; + } + } break; + case GLFW_KEY_DELETE: { + if (cursor == selection) { + text.erase(cursor, 1); + event::Change eChange; + onChange(eChange); + } + else { + int begin = std::min(cursor, selection); + text.erase(begin, std::abs(selection - cursor)); + event::Change eChange; + onChange(eChange); + cursor = selection = begin; + } + } break; + case GLFW_KEY_LEFT: { + if (context()->window->isModPressed()) { + while (--cursor > 0) { + if (text[cursor] == ' ') + break; + } + } + else { + cursor--; + } + if (!context()->window->isShiftPressed()) { + selection = cursor; + } + } break; + case GLFW_KEY_RIGHT: { + if (context()->window->isModPressed()) { + while (++cursor < (int) text.size()) { + if (text[cursor] == ' ') + break; + } + } + else { + cursor++; + } + if (!context()->window->isShiftPressed()) { + selection = cursor; + } + } break; + case GLFW_KEY_HOME: { + selection = cursor = 0; + } break; + case GLFW_KEY_END: { + selection = cursor = text.size(); + } break; + case GLFW_KEY_V: { + if (context()->window->isModPressed()) { + const char *newText = glfwGetClipboardString(context()->window->win); + if (newText) + insertText(newText); + } + } break; + case GLFW_KEY_X: { + if (context()->window->isModPressed()) { + if (cursor != selection) { + int begin = std::min(cursor, selection); + std::string selectedText = text.substr(begin, std::abs(selection - cursor)); + glfwSetClipboardString(context()->window->win, selectedText.c_str()); + insertText(""); + } + } + } break; + case GLFW_KEY_C: { + if (context()->window->isModPressed()) { + if (cursor != selection) { + int begin = std::min(cursor, selection); + std::string selectedText = text.substr(begin, std::abs(selection - cursor)); + glfwSetClipboardString(context()->window->win, selectedText.c_str()); + } + } + } break; + case GLFW_KEY_A: { + if (context()->window->isModPressed()) { + selection = 0; + cursor = text.size(); + } + } break; + case GLFW_KEY_ENTER: { + if (multiline) { + insertText("\n"); + } + else { + event::Action eAction; + onAction(eAction); + } + } break; + } + + cursor = math::clamp(cursor, 0, (int) text.size()); + selection = math::clamp(selection, 0, (int) text.size()); + e.target = this; + } +} + +/** Inserts text at the cursor, replacing the selection if necessary */ +void TextField::insertText(std::string text) { + if (cursor != selection) { + int begin = std::min(cursor, selection); + this->text.erase(begin, std::abs(selection - cursor)); + cursor = selection = begin; + } + this->text.insert(cursor, text); + cursor += text.size(); + selection = cursor; + event::Change eChange; + onChange(eChange); +} + +/** Replaces the entire text */ +void TextField::setText(std::string text) { + this->text = text; + selection = cursor = text.size(); + event::Change eChange; + onChange(eChange); +} + +int TextField::getTextPosition(math::Vec mousePos) { + return bndTextFieldTextPosition(context()->window->vg, 0.0, 0.0, box.size.x, box.size.y, -1, text.c_str(), mousePos.x, mousePos.y); +} + + +} // namespace rack diff --git a/src/window.cpp b/src/window.cpp index 9add7085..b1494cef 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -1,5 +1,4 @@ #include "window.hpp" -#include "logger.hpp" #include "asset.hpp" #include "app/Scene.hpp" #include "keyboard.hpp"