From b605c2c25843fb3a1cc43ec05026df0d38f64ad4 Mon Sep 17 00:00:00 2001 From: Andrew Belt Date: Sat, 14 Aug 2021 05:48:42 -0400 Subject: [PATCH] Don't close menu when Ctrl-clicking items. Add argument to MenuItem helpers to disable this behavior. --- include/helpers.hpp | 87 +++++++++++++++++++++++++++++---------- include/ui/MenuItem.hpp | 7 +++- src/app/MenuBar.cpp | 16 ------- src/app/ModuleBrowser.cpp | 5 +-- src/app/ModuleWidget.cpp | 2 +- src/app/RackWidget.cpp | 14 ++++--- src/ui/MenuItem.cpp | 21 ++++------ 7 files changed, 91 insertions(+), 61 deletions(-) diff --git a/include/helpers.hpp b/include/helpers.hpp index f0ce4f06..1e897b4b 100644 --- a/include/helpers.hpp +++ b/include/helpers.hpp @@ -204,17 +204,22 @@ Example: )); */ template -TMenuItem* createMenuItem(std::string text, std::string rightText, std::function action, bool disabled = false) { +TMenuItem* createMenuItem(std::string text, std::string rightText, std::function action, bool disabled = false, bool alwaysConsume = false) { struct Item : TMenuItem { std::function action; + bool alwaysConsume; + void onAction(const event::Action& e) override { action(); + if (alwaysConsume) + e.consume(this); } }; Item* item = createMenuItem(text, rightText); item->action = action; item->disabled = disabled; + item->alwaysConsume = alwaysConsume; return item; } @@ -231,17 +236,29 @@ Example: } )); */ -inline ui::MenuItem* createCheckMenuItem(std::string text, std::function checked, std::function action) { - struct Item : ui::MenuItem { +template +ui::MenuItem* createCheckMenuItem(std::string text, std::function checked, std::function action, bool disabled = false, bool alwaysConsume = false) { + struct Item : TMenuItem { + std::function checked; std::function action; + bool alwaysConsume; + void step() override { + this->rightText = CHECKMARK(checked()); + TMenuItem::step(); + } void onAction(const event::Action& e) override { action(); + if (alwaysConsume) + e.consume(this); } }; - Item* item = createMenuItem(text, CHECKMARK(checked())); + Item* item = createMenuItem(text); + item->checked = checked; item->action = action; + item->disabled = disabled; + item->alwaysConsume = alwaysConsume; return item; } @@ -258,20 +275,29 @@ Example: } )); */ -inline ui::MenuItem* createBoolMenuItem(std::string text, std::function getter, std::function setter) { - struct Item : ui::MenuItem { +template +ui::MenuItem* createBoolMenuItem(std::string text, std::function getter, std::function setter, bool disabled = false, bool alwaysConsume = false) { + struct Item : TMenuItem { + std::function getter; std::function setter; - bool val; + bool alwaysConsume; + void step() override { + this->rightText = CHECKMARK(getter()); + TMenuItem::step(); + } void onAction(const event::Action& e) override { - setter(val); + setter(!getter()); + if (alwaysConsume) + e.consume(this); } }; - bool currVal = getter(); - Item* item = createMenuItem(text, CHECKMARK(currVal)); + Item* item = createMenuItem(text); + item->getter = getter; item->setter = setter; - item->val = !currVal; + item->disabled = disabled; + item->alwaysConsume = alwaysConsume; return item; } @@ -300,8 +326,9 @@ Example: } )); */ -inline ui::MenuItem* createSubmenuItem(std::string text, std::string rightText, std::function createMenu, bool disabled = false) { - struct Item : ui::MenuItem { +template +ui::MenuItem* createSubmenuItem(std::string text, std::string rightText, std::function createMenu, bool disabled = false) { + struct Item : TMenuItem { std::function createMenu; ui::Menu* createChildMenu() override { @@ -311,7 +338,7 @@ inline ui::MenuItem* createSubmenuItem(std::string text, std::string rightText, } }; - Item* item = createMenuItem(text, rightText + (rightText.empty() ? "" : " ") + RIGHT_ARROW); + Item* item = createMenuItem(text, rightText + (rightText.empty() ? "" : " ") + RIGHT_ARROW); item->createMenu = createMenu; item->disabled = disabled; return item; @@ -331,40 +358,58 @@ Example: } )); */ -inline ui::MenuItem* createIndexSubmenuItem(std::string text, std::vector labels, std::function getter, std::function setter) { +template +ui::MenuItem* createIndexSubmenuItem(std::string text, std::vector labels, std::function getter, std::function setter, bool disabled = false, bool alwaysConsume = false) { struct IndexItem : ui::MenuItem { + std::function getter; std::function setter; size_t index; + bool alwaysConsume; + void step() override { + size_t currIndex = getter(); + this->rightText = CHECKMARK(currIndex == index); + MenuItem::step(); + } void onAction(const event::Action& e) override { setter(index); + if (alwaysConsume) + e.consume(this); } }; - struct Item : ui::MenuItem { + struct Item : TMenuItem { std::function getter; std::function setter; std::vector labels; + bool alwaysConsume; + void step() override { + size_t currIndex = getter(); + std::string label = (currIndex < labels.size()) ? labels[currIndex] : ""; + this->rightText = label + " " + RIGHT_ARROW; + TMenuItem::step(); + } ui::Menu* createChildMenu() override { ui::Menu* menu = new ui::Menu; - size_t currIndex = getter(); for (size_t i = 0; i < labels.size(); i++) { - IndexItem* item = createMenuItem(labels[i], CHECKMARK(currIndex == i)); + IndexItem* item = createMenuItem(labels[i]); + item->getter = getter; item->setter = setter; item->index = i; + item->alwaysConsume = alwaysConsume; menu->addChild(item); } return menu; } }; - size_t currIndex = getter(); - std::string label = (currIndex < labels.size()) ? labels[currIndex] : ""; - Item* item = createMenuItem(text, label + " " + RIGHT_ARROW); + Item* item = createMenuItem(text); item->getter = getter; item->setter = setter; item->labels = labels; + item->disabled = disabled; + item->alwaysConsume = alwaysConsume; return item; } diff --git a/include/ui/MenuItem.hpp b/include/ui/MenuItem.hpp index b496bcbd..e248372a 100644 --- a/include/ui/MenuItem.hpp +++ b/include/ui/MenuItem.hpp @@ -13,16 +13,19 @@ struct MenuItem : MenuEntry { std::string text; std::string rightText; bool disabled = false; - bool active = false; void draw(const DrawArgs& args) override; void step() override; void onEnter(const EnterEvent& e) override; void onDragDrop(const DragDropEvent& e) override; - void doAction(); + void doAction(bool consume = true); virtual Menu* createChildMenu() { return NULL; } + /** Override to handle behavior when user clicks the menu item. + Event is consumed by default. Unconsume to prevent the menu from being closed. + If Ctrl (Cmd on Mac) is held, the event is *not* pre-consumed, so if your menu must be closed, always consume the event. + */ void onAction(const ActionEvent& e) override; }; diff --git a/src/app/MenuBar.cpp b/src/app/MenuBar.cpp index 95ad4a0b..02e13e2a 100644 --- a/src/app/MenuBar.cpp +++ b/src/app/MenuBar.cpp @@ -344,22 +344,6 @@ struct HaloBrightnessSlider : ui::Slider { } }; -struct FrameRateValueItem : ui::MenuItem { - int frameSwapInterval; - void onAction(const ActionEvent& e) override { - settings::frameSwapInterval = frameSwapInterval; - } -}; - -struct FrameRateItem : ui::MenuItem { - ui::Menu* createChildMenu() override { - ui::Menu* menu = new ui::Menu; - - return menu; - } -}; - - struct ViewButton : MenuButton { void onAction(const ActionEvent& e) override { ui::Menu* menu = createMenu(); diff --git a/src/app/ModuleBrowser.cpp b/src/app/ModuleBrowser.cpp index 909de09b..7559b389 100644 --- a/src/app/ModuleBrowser.cpp +++ b/src/app/ModuleBrowser.cpp @@ -843,9 +843,8 @@ inline void TagItem::onAction(const ActionEvent& e) { bool isSelected = (it != browser->tagIds.end()); if (tagId >= 0) { - // Actual tag - int mods = APP->window->getMods(); - if ((mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { + // Specific tag + if (!e.isConsumed()) { // Multi select if (isSelected) browser->tagIds.erase(tagId); diff --git a/src/app/ModuleWidget.cpp b/src/app/ModuleWidget.cpp index 13dff3e2..bf7a061a 100644 --- a/src/app/ModuleWidget.cpp +++ b/src/app/ModuleWidget.cpp @@ -1091,7 +1091,7 @@ void ModuleWidget::createContextMenu() { if (!weakThis) return; weakThis->removeAction(); - })); + }, false, true)); appendContextMenu(menu); } diff --git a/src/app/RackWidget.cpp b/src/app/RackWidget.cpp index b7790f39..06f0ea8e 100644 --- a/src/app/RackWidget.cpp +++ b/src/app/RackWidget.cpp @@ -1122,15 +1122,17 @@ void RackWidget::appendSelectionContextMenu(ui::Menu* menu) { int n = getNumSelected(); menu->addChild(createMenuLabel(string::f("%d selected %s", n, n == 1 ? "module" : "modules"))); + // Enable alwaysConsume of menu items if the number of selected modules changes + // Select all menu->addChild(createMenuItem("Select all", RACK_MOD_CTRL_NAME "+A", [=]() { selectAll(); - })); + }, false, true)); // Deselect menu->addChild(createMenuItem("Deselect", RACK_MOD_CTRL_NAME "+" RACK_MOD_SHIFT_NAME "+A", [=]() { deselect(); - }, n == 0)); + }, n == 0, true)); // Copy menu->addChild(createMenuItem("Copy", RACK_MOD_CTRL_NAME "+C", [=]() { @@ -1140,12 +1142,12 @@ void RackWidget::appendSelectionContextMenu(ui::Menu* menu) { // Paste menu->addChild(createMenuItem("Paste", RACK_MOD_CTRL_NAME "+V", [=]() { pasteClipboardAction(); - })); + }, false, true)); // Load menu->addChild(createMenuItem("Import selection", "", [=]() { loadSelectionDialog(); - })); + }, false, true)); // Save menu->addChild(createMenuItem("Save selection as", "", [=]() { @@ -1174,7 +1176,7 @@ void RackWidget::appendSelectionContextMenu(ui::Menu* menu) { bypassText += " " CHECKMARK_STRING; menu->addChild(createMenuItem("Bypass", bypassText, [=]() { bypassSelectionAction(!bypassed); - }, n == 0)); + }, n == 0, true)); // Duplicate menu->addChild(createMenuItem("Duplicate", RACK_MOD_CTRL_NAME "+D", [=]() { @@ -1184,7 +1186,7 @@ void RackWidget::appendSelectionContextMenu(ui::Menu* menu) { // Delete menu->addChild(createMenuItem("Delete", "Backspace/Delete", [=]() { deleteSelectionAction(); - }, n == 0)); + }, n == 0, true)); } void RackWidget::clearCables() { diff --git a/src/ui/MenuItem.cpp b/src/ui/MenuItem.cpp index 16e3ec5c..3b96189e 100644 --- a/src/ui/MenuItem.cpp +++ b/src/ui/MenuItem.cpp @@ -17,9 +17,6 @@ void MenuItem::draw(const DrawArgs& args) { if (parentMenu && parentMenu->activeEntry == this) state = BND_ACTIVE; - if (active) - state = BND_ACTIVE; - // Main text and background if (!disabled) bndMenuItem(args.vg, 0.0, 0.0, box.size.x, box.size.y, state, -1, text.c_str()); @@ -58,24 +55,24 @@ void MenuItem::onEnter(const EnterEvent& e) { } void MenuItem::onDragDrop(const DragDropEvent& e) { - if (e.origin != this) - return; - doAction(); + if (e.origin == this && !disabled) { + int mods = APP->window->getMods(); + doAction((mods & RACK_MOD_MASK) != RACK_MOD_CTRL); + } } -void MenuItem::doAction() { - if (disabled) - return; - +void MenuItem::doAction(bool consume) { widget::EventContext cAction; ActionEvent eAction; eAction.context = &cAction; - // Consume event by default, but allow action to un-consume it to prevent the menu from being removed. - eAction.consume(this); + if (consume) { + eAction.consume(this); + } onAction(eAction); if (!cAction.consumed) return; + // Close menu MenuOverlay* overlay = getAncestorOfType(); if (overlay) { overlay->requestDelete();