diff --git a/include/app/ModuleWidget.hpp b/include/app/ModuleWidget.hpp index 3c61df83..a676a5ff 100644 --- a/include/app/ModuleWidget.hpp +++ b/include/app/ModuleWidget.hpp @@ -30,16 +30,34 @@ struct ModuleWidget : OpaqueWidget { } ~ModuleWidget(); + void draw(NVGcontext *vg) override; + void drawShadow(NVGcontext *vg); + + void onHover(const event::Hover &e) override; + void onButton(const event::Button &e) override; + void onHoverKey(const event::HoverKey &e) override; + void onDragStart(const event::DragStart &e) override; + void onDragEnd(const event::DragEnd &e) override; + void onDragMove(const event::DragMove &e) override; + + /** Associates this ModuleWidget with the Module + Transfers ownership + */ void setModule(Module *module); + /** Convenience functions for adding special widgets (calls addChild()) */ void addInput(PortWidget *input); void addOutput(PortWidget *output); void addParam(ParamWidget *param); void setPanel(std::shared_ptr svg); + /** Overriding these is deprecated. + Use Module::dataToJson() and dataFromJson() instead + */ virtual json_t *toJson(); virtual void fromJson(json_t *rootJ); + /** Serializes/unserializes the module state */ void copyClipboard(); void pasteClipboard(); void load(std::string filename); @@ -50,33 +68,23 @@ struct ModuleWidget : OpaqueWidget { /** Disconnects cables from all ports Called when the user clicks Disconnect Cables in the context menu. */ - virtual void disconnect(); + void disconnect(); /** Resets the parameters of the module and calls the Module's randomize(). Called when the user clicks Initialize in the context menu. */ - virtual void reset(); - /** Deprecated */ - virtual void initialize() final {} + void reset(); /** Randomizes the parameters of the module and calls the Module's randomize(). Called when the user clicks Randomize in the context menu. */ - virtual void randomize(); - /** Do not subclass this to add context menu entries. Use appendContextMenu() instead */ - virtual Menu *createContextMenu(); + void randomize(); + + void removeAction(); + void bypassAction(); + void createContextMenu(); /** Override to add context menu entries to your subclass. It is recommended to add a blank MenuEntry first for spacing. */ virtual void appendContextMenu(Menu *menu) {} - - void draw(NVGcontext *vg) override; - void drawShadow(NVGcontext *vg); - - void onHover(const event::Hover &e) override; - void onButton(const event::Button &e) override; - void onHoverKey(const event::HoverKey &e) override; - void onDragStart(const event::DragStart &e) override; - void onDragEnd(const event::DragEnd &e) override; - void onDragMove(const event::DragMove &e) override; }; diff --git a/include/app/ParamWidget.hpp b/include/app/ParamWidget.hpp index f60ddf1b..9e360cd2 100644 --- a/include/app/ParamWidget.hpp +++ b/include/app/ParamWidget.hpp @@ -23,6 +23,9 @@ struct ParamWidget : OpaqueWidget { /** For legacy patch loading */ void fromJson(json_t *rootJ); + void createParamField(); + void createContextMenu(); + void resetAction(); }; diff --git a/include/window.hpp b/include/window.hpp index 81369606..498f741b 100644 --- a/include/window.hpp +++ b/include/window.hpp @@ -20,6 +20,8 @@ Use this instead of GLFW_MOD_CONTROL, since Cmd should be used on Mac in place o #define WINDOW_MOD_CTRL GLFW_MOD_CONTROL #define WINDOW_MOD_CTRL_NAME "Ctrl" #endif +#define WINDOW_MOD_SHIFT_NAME "Shift" +#define WINDOW_MOD_ALT_NAME "Alt" /** Filters actual mod keys from the mod flags. Use this if you don't care about GLFW_MOD_CAPS_LOCK and GLFW_MOD_NUM_LOCK. diff --git a/src/app/ModuleWidget.cpp b/src/app/ModuleWidget.cpp index cd6ab35c..ebdeca23 100644 --- a/src/app/ModuleWidget.cpp +++ b/src/app/ModuleWidget.cpp @@ -15,10 +15,267 @@ namespace rack { +struct ModuleDisconnectItem : MenuItem { + ModuleWidget *moduleWidget; + ModuleDisconnectItem() { + text = "Disconnect cables"; + rightText = WINDOW_MOD_CTRL_NAME "+U"; + } + void onAction(const event::Action &e) override { + moduleWidget->disconnect(); + } +}; + +struct ModuleResetItem : MenuItem { + ModuleWidget *moduleWidget; + ModuleResetItem() { + text = "Initialize"; + rightText = WINDOW_MOD_CTRL_NAME "+I"; + } + void onAction(const event::Action &e) override { + moduleWidget->reset(); + } +}; + +struct ModuleRandomizeItem : MenuItem { + ModuleWidget *moduleWidget; + ModuleRandomizeItem() { + text = "Randomize"; + rightText = WINDOW_MOD_CTRL_NAME "+R"; + } + void onAction(const event::Action &e) override { + moduleWidget->randomize(); + } +}; + +struct ModuleCopyItem : MenuItem { + ModuleWidget *moduleWidget; + ModuleCopyItem() { + text = "Copy preset"; + rightText = WINDOW_MOD_CTRL_NAME "+C"; + } + void onAction(const event::Action &e) override { + moduleWidget->copyClipboard(); + } +}; + +struct ModulePasteItem : MenuItem { + ModuleWidget *moduleWidget; + ModulePasteItem() { + text = "Paste preset"; + rightText = WINDOW_MOD_CTRL_NAME "+V"; + } + void onAction(const event::Action &e) override { + moduleWidget->pasteClipboard(); + } +}; + +struct ModuleSaveItem : MenuItem { + ModuleWidget *moduleWidget; + ModuleSaveItem() { + text = "Save preset as"; + } + void onAction(const event::Action &e) override { + moduleWidget->saveDialog(); + } +}; + +struct ModuleLoadItem : MenuItem { + ModuleWidget *moduleWidget; + ModuleLoadItem() { + text = "Load preset"; + } + void onAction(const event::Action &e) override { + moduleWidget->loadDialog(); + } +}; + +struct ModuleCloneItem : MenuItem { + ModuleWidget *moduleWidget; + ModuleCloneItem() { + text = "Duplicate"; + rightText = WINDOW_MOD_CTRL_NAME "+D"; + } + void onAction(const event::Action &e) override { + app()->scene->rackWidget->cloneModule(moduleWidget); + } +}; + +struct ModuleBypassItem : MenuItem { + ModuleWidget *moduleWidget; + ModuleBypassItem() { + text = "Bypass"; + } + void step() override { + rightText = WINDOW_MOD_CTRL_NAME "+E"; + if (!moduleWidget->module) + return; + if (moduleWidget->module->bypass) + rightText = CHECKMARK_STRING " " + rightText; + } + void onAction(const event::Action &e) override { + moduleWidget->bypassAction(); + } +}; + +struct ModuleDeleteItem : MenuItem { + ModuleWidget *moduleWidget; + ModuleDeleteItem() { + text = "Delete"; + rightText = "Backspace/Delete"; + } + void onAction(const event::Action &e) override { + moduleWidget->removeAction(); + } +}; + + ModuleWidget::~ModuleWidget() { setModule(NULL); } +void ModuleWidget::draw(NVGcontext *vg) { + if (module && module->bypass) { + nvgGlobalAlpha(vg, 0.5); + } + // nvgScissor(vg, 0, 0, box.size.x, box.size.y); + Widget::draw(vg); + + // Power meter + if (module && settings::powerMeter) { + nvgBeginPath(vg); + nvgRect(vg, + 0, box.size.y - 20, + 65, 20); + nvgFillColor(vg, nvgRGBAf(0, 0, 0, 0.75)); + nvgFill(vg); + + std::string cpuText = string::f("%.2f μs", module->cpuTime * 1e6f); + bndLabel(vg, 2.0, box.size.y - 20.0, INFINITY, INFINITY, -1, cpuText.c_str()); + + float p = math::clamp(module->cpuTime / app()->engine->getSampleTime(), 0.f, 1.f); + nvgBeginPath(vg); + nvgRect(vg, + 0, (1.f - p) * box.size.y, + 5, p * box.size.y); + nvgFillColor(vg, nvgRGBAf(1, 0, 0, 1.0)); + nvgFill(vg); + } + + // nvgResetScissor(vg); +} + +void ModuleWidget::drawShadow(NVGcontext *vg) { + nvgBeginPath(vg); + float r = 20; // Blur radius + float c = 20; // Corner radius + math::Vec b = math::Vec(-10, 30); // Offset from each corner + nvgRect(vg, b.x - r, b.y - r, box.size.x - 2*b.x + 2*r, box.size.y - 2*b.y + 2*r); + NVGcolor shadowColor = nvgRGBAf(0, 0, 0, 0.2); + NVGcolor transparentColor = nvgRGBAf(0, 0, 0, 0); + nvgFillPaint(vg, nvgBoxGradient(vg, b.x, b.y, box.size.x - 2*b.x, box.size.y - 2*b.y, c, r, shadowColor, transparentColor)); + nvgFill(vg); +} + +void ModuleWidget::onHover(const event::Hover &e) { + OpaqueWidget::onHover(e); + + // Instead of checking key-down events, delete the module even if key-repeat hasn't fired yet and the cursor is hovering over the widget. + if (glfwGetKey(app()->window->win, GLFW_KEY_DELETE) == GLFW_PRESS || glfwGetKey(app()->window->win, GLFW_KEY_BACKSPACE) == GLFW_PRESS) { + if ((app()->window->getMods() & WINDOW_MOD_MASK) == WINDOW_MOD_CTRL) { + removeAction(); + e.consume(NULL); + return; + } + } +} + +void ModuleWidget::onButton(const event::Button &e) { + OpaqueWidget::onButton(e); + + if (e.getConsumed() == this) { + if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_RIGHT) { + createContextMenu(); + } + } +} + +void ModuleWidget::onHoverKey(const event::HoverKey &e) { + if (e.action == GLFW_PRESS || e.action == GLFW_REPEAT) { + switch (e.key) { + case GLFW_KEY_I: { + if ((e.mods & WINDOW_MOD_MASK) == WINDOW_MOD_CTRL) { + reset(); + e.consume(this); + } + } break; + case GLFW_KEY_R: { + if ((e.mods & WINDOW_MOD_MASK) == WINDOW_MOD_CTRL) { + randomize(); + e.consume(this); + } + } break; + case GLFW_KEY_C: { + if ((e.mods & WINDOW_MOD_MASK) == WINDOW_MOD_CTRL) { + copyClipboard(); + e.consume(this); + } + } break; + case GLFW_KEY_V: { + if ((e.mods & WINDOW_MOD_MASK) == WINDOW_MOD_CTRL) { + pasteClipboard(); + e.consume(this); + } + } break; + case GLFW_KEY_D: { + if ((e.mods & WINDOW_MOD_MASK) == WINDOW_MOD_CTRL) { + app()->scene->rackWidget->cloneModule(this); + e.consume(this); + } + } break; + case GLFW_KEY_U: { + if ((e.mods & WINDOW_MOD_MASK) == WINDOW_MOD_CTRL) { + disconnect(); + e.consume(this); + } + } break; + case GLFW_KEY_E: { + if ((e.mods & WINDOW_MOD_MASK) == WINDOW_MOD_CTRL) { + bypassAction(); + e.consume(this); + } + } break; + } + } + + if (!e.getConsumed()) + OpaqueWidget::onHoverKey(e); +} + +void ModuleWidget::onDragStart(const event::DragStart &e) { + oldPos = box.pos; + dragPos = app()->scene->rackWidget->lastMousePos.minus(box.pos); +} + +void ModuleWidget::onDragEnd(const event::DragEnd &e) { + if (!box.pos.isEqual(oldPos)) { + // Push ModuleMove history action + history::ModuleMove *h = new history::ModuleMove; + h->moduleId = module->id; + h->oldPos = oldPos; + h->newPos = box.pos; + app()->history->push(h); + } +} + +void ModuleWidget::onDragMove(const event::DragMove &e) { + if (!settings::lockModules) { + math::Rect newBox = box; + newBox.pos = app()->scene->rackWidget->lastMousePos.minus(dragPos); + app()->scene->rackWidget->requestModuleBoxNearest(this, newBox); + } +} + void ModuleWidget::setModule(Module *module) { if (this->module) { delete this->module; @@ -244,15 +501,6 @@ void ModuleWidget::saveDialog() { save(pathStr); } -void ModuleWidget_bypassAction(ModuleWidget *moduleWidget) { - // Push ModuleBypass history action - history::ModuleBypass *h = new history::ModuleBypass; - h->bypass = !moduleWidget->module->bypass; - h->moduleId = moduleWidget->module->id; - app()->history->push(h); - h->redo(); -} - void ModuleWidget::disconnect() { for (PortWidget *input : inputs) { app()->scene->rackWidget->cableContainer->removeAllCables(input); @@ -274,282 +522,35 @@ void ModuleWidget::randomize() { } } -void ModuleWidget::draw(NVGcontext *vg) { - if (module && module->bypass) { - nvgGlobalAlpha(vg, 0.5); - } - // nvgScissor(vg, 0, 0, box.size.x, box.size.y); - Widget::draw(vg); - - // Power meter - if (module && settings::powerMeter) { - nvgBeginPath(vg); - nvgRect(vg, - 0, box.size.y - 20, - 65, 20); - nvgFillColor(vg, nvgRGBAf(0, 0, 0, 0.75)); - nvgFill(vg); - - std::string cpuText = string::f("%.2f μs", module->cpuTime * 1e6f); - bndLabel(vg, 2.0, box.size.y - 20.0, INFINITY, INFINITY, -1, cpuText.c_str()); - - float p = math::clamp(module->cpuTime / app()->engine->getSampleTime(), 0.f, 1.f); - nvgBeginPath(vg); - nvgRect(vg, - 0, (1.f - p) * box.size.y, - 5, p * box.size.y); - nvgFillColor(vg, nvgRGBAf(1, 0, 0, 1.0)); - nvgFill(vg); - } - - // nvgResetScissor(vg); -} - -void ModuleWidget::drawShadow(NVGcontext *vg) { - nvgBeginPath(vg); - float r = 20; // Blur radius - float c = 20; // Corner radius - math::Vec b = math::Vec(-10, 30); // Offset from each corner - nvgRect(vg, b.x - r, b.y - r, box.size.x - 2*b.x + 2*r, box.size.y - 2*b.y + 2*r); - NVGcolor shadowColor = nvgRGBAf(0, 0, 0, 0.2); - NVGcolor transparentColor = nvgRGBAf(0, 0, 0, 0); - nvgFillPaint(vg, nvgBoxGradient(vg, b.x, b.y, box.size.x - 2*b.x, box.size.y - 2*b.y, c, r, shadowColor, transparentColor)); - nvgFill(vg); -} - -static void ModuleWidget_removeAction(ModuleWidget *moduleWidget) { +void ModuleWidget::removeAction() { history::ComplexAction *complexAction = new history::ComplexAction; // Push ModuleRemove history action history::ModuleRemove *moduleRemove = new history::ModuleRemove; - moduleRemove->model = moduleWidget->model; - moduleRemove->moduleId = moduleWidget->module->id; - moduleRemove->pos = moduleWidget->box.pos; - moduleRemove->moduleJ = moduleWidget->toJson(); + moduleRemove->model = model; + moduleRemove->moduleId = module->id; + moduleRemove->pos = box.pos; + moduleRemove->moduleJ = toJson(); complexAction->push(moduleRemove); app()->history->push(complexAction); - app()->scene->rackWidget->removeModule(moduleWidget); - delete moduleWidget; -} - -void ModuleWidget::onHover(const event::Hover &e) { - OpaqueWidget::onHover(e); - - // Instead of checking key-down events, delete the module even if key-repeat hasn't fired yet and the cursor is hovering over the widget. - if (glfwGetKey(app()->window->win, GLFW_KEY_DELETE) == GLFW_PRESS || glfwGetKey(app()->window->win, GLFW_KEY_BACKSPACE) == GLFW_PRESS) { - if ((app()->window->getMods() & WINDOW_MOD_MASK) == WINDOW_MOD_CTRL) { - ModuleWidget_removeAction(this); - e.consume(NULL); - return; - } - } -} - -void ModuleWidget::onButton(const event::Button &e) { - OpaqueWidget::onButton(e); - - if (e.getConsumed() == this) { - if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_RIGHT) { - createContextMenu(); - } - } -} - -void ModuleWidget::onHoverKey(const event::HoverKey &e) { - if (e.action == GLFW_PRESS || e.action == GLFW_REPEAT) { - switch (e.key) { - case GLFW_KEY_I: { - if ((e.mods & WINDOW_MOD_MASK) == WINDOW_MOD_CTRL) { - reset(); - e.consume(this); - } - } break; - case GLFW_KEY_R: { - if ((e.mods & WINDOW_MOD_MASK) == WINDOW_MOD_CTRL) { - randomize(); - e.consume(this); - } - } break; - case GLFW_KEY_C: { - if ((e.mods & WINDOW_MOD_MASK) == WINDOW_MOD_CTRL) { - copyClipboard(); - e.consume(this); - } - } break; - case GLFW_KEY_V: { - if ((e.mods & WINDOW_MOD_MASK) == WINDOW_MOD_CTRL) { - pasteClipboard(); - e.consume(this); - } - } break; - case GLFW_KEY_D: { - if ((e.mods & WINDOW_MOD_MASK) == WINDOW_MOD_CTRL) { - app()->scene->rackWidget->cloneModule(this); - e.consume(this); - } - } break; - case GLFW_KEY_U: { - if ((e.mods & WINDOW_MOD_MASK) == WINDOW_MOD_CTRL) { - disconnect(); - e.consume(this); - } - } break; - case GLFW_KEY_E: { - if ((e.mods & WINDOW_MOD_MASK) == WINDOW_MOD_CTRL) { - ModuleWidget_bypassAction(this); - e.consume(this); - } - } break; - } - } - - if (!e.getConsumed()) - OpaqueWidget::onHoverKey(e); -} - -void ModuleWidget::onDragStart(const event::DragStart &e) { - oldPos = box.pos; - dragPos = app()->scene->rackWidget->lastMousePos.minus(box.pos); + app()->scene->rackWidget->removeModule(this); + delete this; } -void ModuleWidget::onDragEnd(const event::DragEnd &e) { - if (!box.pos.isEqual(oldPos)) { - // Push ModuleMove history action - history::ModuleMove *h = new history::ModuleMove; - h->moduleId = module->id; - h->oldPos = oldPos; - h->newPos = box.pos; - app()->history->push(h); - } -} - -void ModuleWidget::onDragMove(const event::DragMove &e) { - if (!settings::lockModules) { - math::Rect newBox = box; - newBox.pos = app()->scene->rackWidget->lastMousePos.minus(dragPos); - app()->scene->rackWidget->requestModuleBoxNearest(this, newBox); - } +void ModuleWidget::bypassAction() { + // Push ModuleBypass history action + history::ModuleBypass *h = new history::ModuleBypass; + h->bypass = !module->bypass; + h->moduleId = module->id; + app()->history->push(h); + h->redo(); } - -struct ModuleDisconnectItem : MenuItem { - ModuleWidget *moduleWidget; - ModuleDisconnectItem() { - text = "Disconnect cables"; - rightText = WINDOW_MOD_CTRL_NAME "+U"; - } - void onAction(const event::Action &e) override { - moduleWidget->disconnect(); - } -}; - -struct ModuleResetItem : MenuItem { - ModuleWidget *moduleWidget; - ModuleResetItem() { - text = "Initialize"; - rightText = WINDOW_MOD_CTRL_NAME "+I"; - } - void onAction(const event::Action &e) override { - moduleWidget->reset(); - } -}; - -struct ModuleRandomizeItem : MenuItem { - ModuleWidget *moduleWidget; - ModuleRandomizeItem() { - text = "Randomize"; - rightText = WINDOW_MOD_CTRL_NAME "+R"; - } - void onAction(const event::Action &e) override { - moduleWidget->randomize(); - } -}; - -struct ModuleCopyItem : MenuItem { - ModuleWidget *moduleWidget; - ModuleCopyItem() { - text = "Copy preset"; - rightText = WINDOW_MOD_CTRL_NAME "+C"; - } - void onAction(const event::Action &e) override { - moduleWidget->copyClipboard(); - } -}; - -struct ModulePasteItem : MenuItem { - ModuleWidget *moduleWidget; - ModulePasteItem() { - text = "Paste preset"; - rightText = WINDOW_MOD_CTRL_NAME "+V"; - } - void onAction(const event::Action &e) override { - moduleWidget->pasteClipboard(); - } -}; - -struct ModuleSaveItem : MenuItem { - ModuleWidget *moduleWidget; - ModuleSaveItem() { - text = "Save preset as"; - } - void onAction(const event::Action &e) override { - moduleWidget->saveDialog(); - } -}; - -struct ModuleLoadItem : MenuItem { - ModuleWidget *moduleWidget; - ModuleLoadItem() { - text = "Load preset"; - } - void onAction(const event::Action &e) override { - moduleWidget->loadDialog(); - } -}; - -struct ModuleCloneItem : MenuItem { - ModuleWidget *moduleWidget; - ModuleCloneItem() { - text = "Duplicate"; - rightText = WINDOW_MOD_CTRL_NAME "+D"; - } - void onAction(const event::Action &e) override { - app()->scene->rackWidget->cloneModule(moduleWidget); - } -}; - -struct ModuleBypassItem : MenuItem { - ModuleWidget *moduleWidget; - ModuleBypassItem() { - text = "Bypass"; - } - void step() override { - rightText = WINDOW_MOD_CTRL_NAME "+E"; - if (!moduleWidget->module) - return; - if (moduleWidget->module->bypass) - rightText = CHECKMARK_STRING " " + rightText; - } - void onAction(const event::Action &e) override { - ModuleWidget_bypassAction(moduleWidget); - } -}; - -struct ModuleDeleteItem : MenuItem { - ModuleWidget *moduleWidget; - ModuleDeleteItem() { - text = "Delete"; - rightText = "Backspace/Delete"; - } - void onAction(const event::Action &e) override { - ModuleWidget_removeAction(moduleWidget); - } -}; - -Menu *ModuleWidget::createContextMenu() { +void ModuleWidget::createContextMenu() { Menu *menu = createMenu(); + assert(model); MenuLabel *menuLabel = new MenuLabel; menuLabel->text = model->plugin->name + " " + model->name + " " + model->plugin->version; @@ -596,8 +597,6 @@ Menu *ModuleWidget::createContextMenu() { menu->addChild(deleteItem); appendContextMenu(menu); - - return menu; } diff --git a/src/app/ParamWidget.cpp b/src/app/ParamWidget.cpp index ef645634..744a08ee 100644 --- a/src/app/ParamWidget.cpp +++ b/src/app/ParamWidget.cpp @@ -7,6 +7,7 @@ #include "settings.hpp" #include "random.hpp" #include "history.hpp" +#include "helpers.hpp" namespace rack { @@ -18,6 +19,7 @@ struct ParamField : TextField { void step() override { // Keep selected app()->event->setSelected(this); + TextField::step(); } void setParamWidget(ParamWidget *paramWidget) { @@ -61,6 +63,48 @@ struct ParamField : TextField { }; +struct ParamTooltip : Tooltip { + ParamWidget *paramWidget; + + void step() override { + if (paramWidget->paramQuantity) { + // Quantity string + text = paramWidget->paramQuantity->getString(); + // Param description + std::string description = paramWidget->paramQuantity->getParam()->description; + if (!description.empty()) + text += "\n" + description; + } + // Position at bottom-right of parameter + box.pos = paramWidget->getAbsoluteOffset(box.size).round(); + } +}; + + +struct ParamResetItem : MenuItem { + ParamWidget *paramWidget; + ParamResetItem() { + text = "Initialize"; + rightText = WINDOW_MOD_ALT_NAME "+click"; + } + void onAction(const event::Action &e) override { + paramWidget->resetAction(); + } +}; + + +struct ParamFieldItem : MenuItem { + ParamWidget *paramWidget; + ParamFieldItem() { + text = "Enter value"; + rightText = WINDOW_MOD_SHIFT_NAME "+click"; + } + void onAction(const event::Action &e) override { + paramWidget->createParamField(); + } +}; + + ParamWidget::~ParamWidget() { if (paramQuantity) delete paramQuantity; @@ -77,19 +121,6 @@ void ParamWidget::step() { } } - if (tooltip) { - if (paramQuantity) { - // Quantity string - tooltip->text = paramQuantity->getString(); - // Param description - std::string description = paramQuantity->getParam()->description; - if (!description.empty()) - tooltip->text += "\n" + description; - } - // Position at bottom-right of parameter - tooltip->box.pos = getAbsoluteOffset(box.size).round(); - } - OpaqueWidget::step(); } @@ -109,48 +140,22 @@ void ParamWidget::draw(NVGcontext *vg) { // } } -void ParamWidget::fromJson(json_t *rootJ) { - json_t *valueJ = json_object_get(rootJ, "value"); - if (valueJ) { - if (paramQuantity) - paramQuantity->setValue(json_number_value(valueJ)); - } -} - void ParamWidget::onButton(const event::Button &e) { - // Right click to reset + // Right click to open context menu if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_RIGHT && (e.mods & WINDOW_MOD_MASK) == 0) { - if (paramQuantity && paramQuantity->isBounded()) { - float oldValue = paramQuantity->getValue(); - paramQuantity->reset(); - float newValue = paramQuantity->getValue(); + createContextMenu(); + e.consume(this); + } - if (oldValue != newValue) { - // Push ParamChange history action - history::ParamChange *h = new history::ParamChange; - h->moduleId = paramQuantity->module->id; - h->paramId = paramQuantity->paramId; - h->oldValue = oldValue; - h->newValue = newValue; - app()->history->push(h); - } - } - // Here's another way of doing it, but either works. - // paramQuantity->getParam()->reset(); + // Alt-click to reset + if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT && (e.mods & WINDOW_MOD_MASK) == GLFW_MOD_ALT) { + resetAction(); e.consume(this); } // Shift-click to open value entry if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT && (e.mods & WINDOW_MOD_MASK) == GLFW_MOD_SHIFT) { - // Create ParamField - MenuOverlay *overlay = new MenuOverlay; - app()->scene->addChild(overlay); - - ParamField *paramField = new ParamField; - paramField->box.size.x = 100; - paramField->box.pos = getAbsoluteOffset(box.size).round(); - paramField->setParamWidget(this); - overlay->addChild(paramField); + createParamField(); e.consume(this); } @@ -160,8 +165,10 @@ void ParamWidget::onButton(const event::Button &e) { void ParamWidget::onEnter(const event::Enter &e) { if (settings::paramTooltip && !tooltip) { - tooltip = new Tooltip; - app()->scene->addChild(tooltip); + ParamTooltip *paramTooltip = new ParamTooltip; + paramTooltip->paramWidget = this; + app()->scene->addChild(paramTooltip); + tooltip = paramTooltip; } } @@ -173,5 +180,58 @@ void ParamWidget::onLeave(const event::Leave &e) { } } +void ParamWidget::fromJson(json_t *rootJ) { + json_t *valueJ = json_object_get(rootJ, "value"); + if (valueJ) { + if (paramQuantity) + paramQuantity->setValue(json_number_value(valueJ)); + } +} + +void ParamWidget::createParamField() { + // Create ParamField + MenuOverlay *overlay = new MenuOverlay; + app()->scene->addChild(overlay); + + ParamField *paramField = new ParamField; + paramField->box.size.x = 100; + paramField->box.pos = getAbsoluteOffset(box.size).round(); + paramField->setParamWidget(this); + overlay->addChild(paramField); +} + +void ParamWidget::createContextMenu() { + Menu *menu = createMenu(); + + ParamResetItem *resetItem = new ParamResetItem; + resetItem->paramWidget = this; + menu->addChild(resetItem); + + ParamFieldItem *fieldItem = new ParamFieldItem; + fieldItem->paramWidget = this; + menu->addChild(fieldItem); +} + +void ParamWidget::resetAction() { + if (paramQuantity && paramQuantity->isBounded()) { + float oldValue = paramQuantity->getValue(); + paramQuantity->reset(); + float newValue = paramQuantity->getValue(); + + if (oldValue != newValue) { + // Push ParamChange history action + history::ParamChange *h = new history::ParamChange; + h->moduleId = paramQuantity->module->id; + h->paramId = paramQuantity->paramId; + h->oldValue = oldValue; + h->newValue = newValue; + app()->history->push(h); + } + + // Here's another way of doing it, but either works. + // paramQuantity->getParam()->reset(); + } +} + } // namespace rack