@@ -204,17 +204,22 @@ Example: | |||||
)); | )); | ||||
*/ | */ | ||||
template <class TMenuItem = ui::MenuItem> | template <class TMenuItem = ui::MenuItem> | ||||
TMenuItem* createMenuItem(std::string text, std::string rightText, std::function<void()> action, bool disabled = false) { | |||||
TMenuItem* createMenuItem(std::string text, std::string rightText, std::function<void()> action, bool disabled = false, bool alwaysConsume = false) { | |||||
struct Item : TMenuItem { | struct Item : TMenuItem { | ||||
std::function<void()> action; | std::function<void()> action; | ||||
bool alwaysConsume; | |||||
void onAction(const event::Action& e) override { | void onAction(const event::Action& e) override { | ||||
action(); | action(); | ||||
if (alwaysConsume) | |||||
e.consume(this); | |||||
} | } | ||||
}; | }; | ||||
Item* item = createMenuItem<Item>(text, rightText); | Item* item = createMenuItem<Item>(text, rightText); | ||||
item->action = action; | item->action = action; | ||||
item->disabled = disabled; | item->disabled = disabled; | ||||
item->alwaysConsume = alwaysConsume; | |||||
return item; | return item; | ||||
} | } | ||||
@@ -231,17 +236,29 @@ Example: | |||||
} | } | ||||
)); | )); | ||||
*/ | */ | ||||
inline ui::MenuItem* createCheckMenuItem(std::string text, std::function<bool()> checked, std::function<void()> action) { | |||||
struct Item : ui::MenuItem { | |||||
template <class TMenuItem = ui::MenuItem> | |||||
ui::MenuItem* createCheckMenuItem(std::string text, std::function<bool()> checked, std::function<void()> action, bool disabled = false, bool alwaysConsume = false) { | |||||
struct Item : TMenuItem { | |||||
std::function<bool()> checked; | |||||
std::function<void()> action; | std::function<void()> action; | ||||
bool alwaysConsume; | |||||
void step() override { | |||||
this->rightText = CHECKMARK(checked()); | |||||
TMenuItem::step(); | |||||
} | |||||
void onAction(const event::Action& e) override { | void onAction(const event::Action& e) override { | ||||
action(); | action(); | ||||
if (alwaysConsume) | |||||
e.consume(this); | |||||
} | } | ||||
}; | }; | ||||
Item* item = createMenuItem<Item>(text, CHECKMARK(checked())); | |||||
Item* item = createMenuItem<Item>(text); | |||||
item->checked = checked; | |||||
item->action = action; | item->action = action; | ||||
item->disabled = disabled; | |||||
item->alwaysConsume = alwaysConsume; | |||||
return item; | return item; | ||||
} | } | ||||
@@ -258,20 +275,29 @@ Example: | |||||
} | } | ||||
)); | )); | ||||
*/ | */ | ||||
inline ui::MenuItem* createBoolMenuItem(std::string text, std::function<bool()> getter, std::function<void(bool state)> setter) { | |||||
struct Item : ui::MenuItem { | |||||
template <class TMenuItem = ui::MenuItem> | |||||
ui::MenuItem* createBoolMenuItem(std::string text, std::function<bool()> getter, std::function<void(bool state)> setter, bool disabled = false, bool alwaysConsume = false) { | |||||
struct Item : TMenuItem { | |||||
std::function<bool()> getter; | |||||
std::function<void(size_t)> setter; | std::function<void(size_t)> setter; | ||||
bool val; | |||||
bool alwaysConsume; | |||||
void step() override { | |||||
this->rightText = CHECKMARK(getter()); | |||||
TMenuItem::step(); | |||||
} | |||||
void onAction(const event::Action& e) override { | void onAction(const event::Action& e) override { | ||||
setter(val); | |||||
setter(!getter()); | |||||
if (alwaysConsume) | |||||
e.consume(this); | |||||
} | } | ||||
}; | }; | ||||
bool currVal = getter(); | |||||
Item* item = createMenuItem<Item>(text, CHECKMARK(currVal)); | |||||
Item* item = createMenuItem<Item>(text); | |||||
item->getter = getter; | |||||
item->setter = setter; | item->setter = setter; | ||||
item->val = !currVal; | |||||
item->disabled = disabled; | |||||
item->alwaysConsume = alwaysConsume; | |||||
return item; | return item; | ||||
} | } | ||||
@@ -300,8 +326,9 @@ Example: | |||||
} | } | ||||
)); | )); | ||||
*/ | */ | ||||
inline ui::MenuItem* createSubmenuItem(std::string text, std::string rightText, std::function<void(ui::Menu* menu)> createMenu, bool disabled = false) { | |||||
struct Item : ui::MenuItem { | |||||
template <class TMenuItem = ui::MenuItem> | |||||
ui::MenuItem* createSubmenuItem(std::string text, std::string rightText, std::function<void(ui::Menu* menu)> createMenu, bool disabled = false) { | |||||
struct Item : TMenuItem { | |||||
std::function<void(ui::Menu* menu)> createMenu; | std::function<void(ui::Menu* menu)> createMenu; | ||||
ui::Menu* createChildMenu() override { | ui::Menu* createChildMenu() override { | ||||
@@ -311,7 +338,7 @@ inline ui::MenuItem* createSubmenuItem(std::string text, std::string rightText, | |||||
} | } | ||||
}; | }; | ||||
Item* item = createMenuItem<Item>(text, rightText + (rightText.empty() ? "" : " ") + RIGHT_ARROW); | |||||
Item* item = createMenuItem<Item>(text, rightText + (rightText.empty() ? "" : " ") + RIGHT_ARROW); | |||||
item->createMenu = createMenu; | item->createMenu = createMenu; | ||||
item->disabled = disabled; | item->disabled = disabled; | ||||
return item; | return item; | ||||
@@ -331,40 +358,58 @@ Example: | |||||
} | } | ||||
)); | )); | ||||
*/ | */ | ||||
inline ui::MenuItem* createIndexSubmenuItem(std::string text, std::vector<std::string> labels, std::function<size_t()> getter, std::function<void(size_t val)> setter) { | |||||
template <class TMenuItem = ui::MenuItem> | |||||
ui::MenuItem* createIndexSubmenuItem(std::string text, std::vector<std::string> labels, std::function<size_t()> getter, std::function<void(size_t val)> setter, bool disabled = false, bool alwaysConsume = false) { | |||||
struct IndexItem : ui::MenuItem { | struct IndexItem : ui::MenuItem { | ||||
std::function<size_t()> getter; | |||||
std::function<void(size_t)> setter; | std::function<void(size_t)> setter; | ||||
size_t index; | 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 { | void onAction(const event::Action& e) override { | ||||
setter(index); | setter(index); | ||||
if (alwaysConsume) | |||||
e.consume(this); | |||||
} | } | ||||
}; | }; | ||||
struct Item : ui::MenuItem { | |||||
struct Item : TMenuItem { | |||||
std::function<size_t()> getter; | std::function<size_t()> getter; | ||||
std::function<void(size_t)> setter; | std::function<void(size_t)> setter; | ||||
std::vector<std::string> labels; | std::vector<std::string> 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* createChildMenu() override { | ||||
ui::Menu* menu = new ui::Menu; | ui::Menu* menu = new ui::Menu; | ||||
size_t currIndex = getter(); | |||||
for (size_t i = 0; i < labels.size(); i++) { | for (size_t i = 0; i < labels.size(); i++) { | ||||
IndexItem* item = createMenuItem<IndexItem>(labels[i], CHECKMARK(currIndex == i)); | |||||
IndexItem* item = createMenuItem<IndexItem>(labels[i]); | |||||
item->getter = getter; | |||||
item->setter = setter; | item->setter = setter; | ||||
item->index = i; | item->index = i; | ||||
item->alwaysConsume = alwaysConsume; | |||||
menu->addChild(item); | menu->addChild(item); | ||||
} | } | ||||
return menu; | return menu; | ||||
} | } | ||||
}; | }; | ||||
size_t currIndex = getter(); | |||||
std::string label = (currIndex < labels.size()) ? labels[currIndex] : ""; | |||||
Item* item = createMenuItem<Item>(text, label + " " + RIGHT_ARROW); | |||||
Item* item = createMenuItem<Item>(text); | |||||
item->getter = getter; | item->getter = getter; | ||||
item->setter = setter; | item->setter = setter; | ||||
item->labels = labels; | item->labels = labels; | ||||
item->disabled = disabled; | |||||
item->alwaysConsume = alwaysConsume; | |||||
return item; | return item; | ||||
} | } | ||||
@@ -13,16 +13,19 @@ struct MenuItem : MenuEntry { | |||||
std::string text; | std::string text; | ||||
std::string rightText; | std::string rightText; | ||||
bool disabled = false; | bool disabled = false; | ||||
bool active = false; | |||||
void draw(const DrawArgs& args) override; | void draw(const DrawArgs& args) override; | ||||
void step() override; | void step() override; | ||||
void onEnter(const EnterEvent& e) override; | void onEnter(const EnterEvent& e) override; | ||||
void onDragDrop(const DragDropEvent& e) override; | void onDragDrop(const DragDropEvent& e) override; | ||||
void doAction(); | |||||
void doAction(bool consume = true); | |||||
virtual Menu* createChildMenu() { | virtual Menu* createChildMenu() { | ||||
return NULL; | 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; | void onAction(const ActionEvent& e) override; | ||||
}; | }; | ||||
@@ -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 { | struct ViewButton : MenuButton { | ||||
void onAction(const ActionEvent& e) override { | void onAction(const ActionEvent& e) override { | ||||
ui::Menu* menu = createMenu(); | ui::Menu* menu = createMenu(); | ||||
@@ -843,9 +843,8 @@ inline void TagItem::onAction(const ActionEvent& e) { | |||||
bool isSelected = (it != browser->tagIds.end()); | bool isSelected = (it != browser->tagIds.end()); | ||||
if (tagId >= 0) { | 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 | // Multi select | ||||
if (isSelected) | if (isSelected) | ||||
browser->tagIds.erase(tagId); | browser->tagIds.erase(tagId); | ||||
@@ -1091,7 +1091,7 @@ void ModuleWidget::createContextMenu() { | |||||
if (!weakThis) | if (!weakThis) | ||||
return; | return; | ||||
weakThis->removeAction(); | weakThis->removeAction(); | ||||
})); | |||||
}, false, true)); | |||||
appendContextMenu(menu); | appendContextMenu(menu); | ||||
} | } | ||||
@@ -1122,15 +1122,17 @@ void RackWidget::appendSelectionContextMenu(ui::Menu* menu) { | |||||
int n = getNumSelected(); | int n = getNumSelected(); | ||||
menu->addChild(createMenuLabel(string::f("%d selected %s", n, n == 1 ? "module" : "modules"))); | 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 | // Select all | ||||
menu->addChild(createMenuItem("Select all", RACK_MOD_CTRL_NAME "+A", [=]() { | menu->addChild(createMenuItem("Select all", RACK_MOD_CTRL_NAME "+A", [=]() { | ||||
selectAll(); | selectAll(); | ||||
})); | |||||
}, false, true)); | |||||
// Deselect | // Deselect | ||||
menu->addChild(createMenuItem("Deselect", RACK_MOD_CTRL_NAME "+" RACK_MOD_SHIFT_NAME "+A", [=]() { | menu->addChild(createMenuItem("Deselect", RACK_MOD_CTRL_NAME "+" RACK_MOD_SHIFT_NAME "+A", [=]() { | ||||
deselect(); | deselect(); | ||||
}, n == 0)); | |||||
}, n == 0, true)); | |||||
// Copy | // Copy | ||||
menu->addChild(createMenuItem("Copy", RACK_MOD_CTRL_NAME "+C", [=]() { | menu->addChild(createMenuItem("Copy", RACK_MOD_CTRL_NAME "+C", [=]() { | ||||
@@ -1140,12 +1142,12 @@ void RackWidget::appendSelectionContextMenu(ui::Menu* menu) { | |||||
// Paste | // Paste | ||||
menu->addChild(createMenuItem("Paste", RACK_MOD_CTRL_NAME "+V", [=]() { | menu->addChild(createMenuItem("Paste", RACK_MOD_CTRL_NAME "+V", [=]() { | ||||
pasteClipboardAction(); | pasteClipboardAction(); | ||||
})); | |||||
}, false, true)); | |||||
// Load | // Load | ||||
menu->addChild(createMenuItem("Import selection", "", [=]() { | menu->addChild(createMenuItem("Import selection", "", [=]() { | ||||
loadSelectionDialog(); | loadSelectionDialog(); | ||||
})); | |||||
}, false, true)); | |||||
// Save | // Save | ||||
menu->addChild(createMenuItem("Save selection as", "", [=]() { | menu->addChild(createMenuItem("Save selection as", "", [=]() { | ||||
@@ -1174,7 +1176,7 @@ void RackWidget::appendSelectionContextMenu(ui::Menu* menu) { | |||||
bypassText += " " CHECKMARK_STRING; | bypassText += " " CHECKMARK_STRING; | ||||
menu->addChild(createMenuItem("Bypass", bypassText, [=]() { | menu->addChild(createMenuItem("Bypass", bypassText, [=]() { | ||||
bypassSelectionAction(!bypassed); | bypassSelectionAction(!bypassed); | ||||
}, n == 0)); | |||||
}, n == 0, true)); | |||||
// Duplicate | // Duplicate | ||||
menu->addChild(createMenuItem("Duplicate", RACK_MOD_CTRL_NAME "+D", [=]() { | menu->addChild(createMenuItem("Duplicate", RACK_MOD_CTRL_NAME "+D", [=]() { | ||||
@@ -1184,7 +1186,7 @@ void RackWidget::appendSelectionContextMenu(ui::Menu* menu) { | |||||
// Delete | // Delete | ||||
menu->addChild(createMenuItem("Delete", "Backspace/Delete", [=]() { | menu->addChild(createMenuItem("Delete", "Backspace/Delete", [=]() { | ||||
deleteSelectionAction(); | deleteSelectionAction(); | ||||
}, n == 0)); | |||||
}, n == 0, true)); | |||||
} | } | ||||
void RackWidget::clearCables() { | void RackWidget::clearCables() { | ||||
@@ -17,9 +17,6 @@ void MenuItem::draw(const DrawArgs& args) { | |||||
if (parentMenu && parentMenu->activeEntry == this) | if (parentMenu && parentMenu->activeEntry == this) | ||||
state = BND_ACTIVE; | state = BND_ACTIVE; | ||||
if (active) | |||||
state = BND_ACTIVE; | |||||
// Main text and background | // Main text and background | ||||
if (!disabled) | if (!disabled) | ||||
bndMenuItem(args.vg, 0.0, 0.0, box.size.x, box.size.y, state, -1, text.c_str()); | 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) { | 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; | widget::EventContext cAction; | ||||
ActionEvent eAction; | ActionEvent eAction; | ||||
eAction.context = &cAction; | 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); | onAction(eAction); | ||||
if (!cAction.consumed) | if (!cAction.consumed) | ||||
return; | return; | ||||
// Close menu | |||||
MenuOverlay* overlay = getAncestorOfType<MenuOverlay>(); | MenuOverlay* overlay = getAncestorOfType<MenuOverlay>(); | ||||
if (overlay) { | if (overlay) { | ||||
overlay->requestDelete(); | overlay->requestDelete(); | ||||