diff --git a/firmware/metamodule-plugin-sdk b/firmware/metamodule-plugin-sdk index 206abc6f7..37ebbee87 160000 --- a/firmware/metamodule-plugin-sdk +++ b/firmware/metamodule-plugin-sdk @@ -1 +1 @@ -Subproject commit 206abc6f7ce4de51fda1dbb5658fbc633029f9b8 +Subproject commit 37ebbee8756a0cdeb357faf9e54a79c3db9caf8c diff --git a/firmware/src/gui/helpers/roller_hover_text.hh b/firmware/src/gui/helpers/roller_hover_text.hh index b8fad4614..8d12a7cda 100644 --- a/firmware/src/gui/helpers/roller_hover_text.hh +++ b/firmware/src/gui/helpers/roller_hover_text.hh @@ -106,6 +106,10 @@ public: } } + void force_redraw() { + sel_idx = -1; + } + void default_draw_callback() { display_in_time(10); } diff --git a/firmware/src/gui/module_menu/base_plugin_menu.hh b/firmware/src/gui/module_menu/base_plugin_menu.hh new file mode 100644 index 000000000..199c281e7 --- /dev/null +++ b/firmware/src/gui/module_menu/base_plugin_menu.hh @@ -0,0 +1,17 @@ +#pragma once +#include +#include + +namespace MetaModule +{ + +struct BasePluginModuleMenu { + virtual ~BasePluginModuleMenu() = default; + + virtual std::vector get_items() = 0; + virtual void back_event() = 0; + virtual void click_item(unsigned idx) = 0; + virtual bool is_done() = 0; +}; + +} // namespace MetaModule diff --git a/firmware/src/gui/module_menu/native_plugin_menu.hh b/firmware/src/gui/module_menu/native_plugin_menu.hh new file mode 100644 index 000000000..07931c43d --- /dev/null +++ b/firmware/src/gui/module_menu/native_plugin_menu.hh @@ -0,0 +1,32 @@ +#pragma once +#include "CoreModules/CoreProcessor.hh" +#include "gui/module_menu/base_plugin_menu.hh" +#include + +namespace MetaModule +{ + +// Not implemented yet, just placeholder to demonstrate support for multiple plugin types +struct NativeModuleMenu : BasePluginModuleMenu { + + NativeModuleMenu(CoreProcessor *module) { + } + + ~NativeModuleMenu() override = default; + + std::vector get_items() override { + return {}; + } + + void back_event() override { + } + + void click_item(unsigned idx) override { + } + + bool is_done() override { + return true; + } +}; + +} // namespace MetaModule diff --git a/firmware/src/gui/module_menu/plugin_module_menu.hh b/firmware/src/gui/module_menu/plugin_module_menu.hh new file mode 100644 index 000000000..db8f175f5 --- /dev/null +++ b/firmware/src/gui/module_menu/plugin_module_menu.hh @@ -0,0 +1,168 @@ +#pragma once +#include "console/pr_dbg.hh" +#include "gui/helpers/lv_helpers.hh" +#include "gui/helpers/roller_hover_text.hh" +#include "gui/module_menu/base_plugin_menu.hh" +#include "gui/module_menu/native_plugin_menu.hh" +#include "gui/module_menu/vcv_plugin_menu.hh" +#include "gui/slsexport/meta5/ui.h" +#include "lvgl.h" +#include "patch_play/patch_playloader.hh" +#include "util/overloaded.hh" +#include + +namespace MetaModule +{ + +struct PluginModuleMenu { + PluginModuleMenu(PatchPlayLoader &patch_playloader) + : roller_hover(ui_ElementRollerPanel, ui_ModuleViewExtraMenuRoller) + , patch_playloader{patch_playloader} { + lv_hide(roller); + lv_obj_add_event_cb(roller, roller_click_cb, LV_EVENT_CLICKED, this); + lv_obj_add_event_cb(roller, roller_scrolled_cb, LV_EVENT_KEY, this); + lv_obj_remove_style(roller, nullptr, LV_STATE_EDITED); + lv_obj_remove_style(roller, nullptr, LV_STATE_FOCUS_KEY); + + auto roller_label = lv_obj_get_child(roller, 0); + lv_label_set_recolor(roller_label, true); + + lv_obj_set_style_text_font(roller, &ui_font_MuseoSansRounded70014, LV_PART_MAIN); + lv_obj_set_style_text_font(roller, &ui_font_MuseoSansRounded70014, LV_PART_SELECTED); + + auto hover_label = lv_obj_get_child(roller_hover.get_cont(), 0); + lv_obj_set_style_text_font(hover_label, &ui_font_MuseoSansRounded70014, LV_PART_MAIN); + } + + bool create_options_menu(unsigned this_module_id) { + if (auto rack_module = patch_playloader.get_plugin_module(this_module_id)) { + plugin_menu = std::make_unique(rack_module->module_widget); + return populate_menu_items() > 0; + + } else if (auto native_module = patch_playloader.get_plugin_module(this_module_id)) { + plugin_menu = std::make_unique(native_module); + return populate_menu_items() > 0; + } + + return false; + } + + void show() { + populate_menu_items(); + + lv_roller_set_selected(roller, 0, LV_ANIM_OFF); + lv_show(roller); + lv_group_focus_obj(roller); + if (auto *group = static_cast(lv_obj_get_group(roller))) + lv_group_set_editing(group, true); + lv_event_send(roller, LV_EVENT_PRESSED, nullptr); + + roller_hover.hide(); + + visible = true; + should_close = false; + } + + void hide() { + if (visible) { + lv_hide(roller); + roller_hover.hide(); + visible = false; + should_close = false; + } + } + + void back_event() { + if (visible) { + plugin_menu->back_event(); + + if (plugin_menu->is_done()) + should_close = true; + else + populate_menu_items(); + } + } + + void update() { + roller_hover.update(); + } + + bool wants_to_close() { + return should_close; + } + + void click(unsigned idx) { + // First item is "< Back" + if (idx == 0) { + back_event(); + } else { + plugin_menu->click_item(idx - 1); + refresh_menu_items(); + } + } + +private: + unsigned populate_menu_items() { + std::string opts = LV_SYMBOL_LEFT + std::string(" Back\n"); + + std::vector item_strings; + + if (plugin_menu) { + item_strings = plugin_menu->get_items(); + for (auto const &item : item_strings) { + opts += item + "\n"; + } + } else { + pr_err("plugin_menu not created\n"); + } + + // Remove final /n + if (opts.length() > 0) + opts.pop_back(); + + lv_roller_set_options(roller, opts.c_str(), LV_ROLLER_MODE_NORMAL); + + return item_strings.size(); + } + + // Same as populate_menu_items() but keeps current selection + void refresh_menu_items() { + auto cur_idx = lv_roller_get_selected(roller); + auto num_items = populate_menu_items() + 1; // +1 for "< Back" item + if (cur_idx < num_items) + lv_roller_set_selected(roller, cur_idx, LV_ANIM_OFF); + } + + static void roller_click_cb(lv_event_t *event) { + auto page = static_cast(event->user_data); + auto idx = lv_roller_get_selected(page->roller); + + page->click(idx); + + // keep editing focus on roller after clicking it + lv_group_focus_obj(page->roller); + if (auto *group = static_cast(lv_obj_get_group(page->roller))) + lv_group_set_editing(group, true); + lv_event_send(page->roller, LV_EVENT_PRESSED, nullptr); + + // index might be the same, but content probably changed + page->roller_hover.force_redraw(); + } + + static void roller_scrolled_cb(lv_event_t *event) { + auto page = static_cast(event->user_data); + page->roller_hover.hide(); + } + + std::unique_ptr plugin_menu; + + lv_obj_t *roller = ui_ModuleViewExtraMenuRoller; + + RollerHoverText roller_hover; + PatchPlayLoader &patch_playloader; + + bool visible = false; + bool should_close = false; +}; + +} // namespace MetaModule diff --git a/firmware/src/gui/module_menu/vcv_plugin_menu.hh b/firmware/src/gui/module_menu/vcv_plugin_menu.hh new file mode 100644 index 000000000..41266994c --- /dev/null +++ b/firmware/src/gui/module_menu/vcv_plugin_menu.hh @@ -0,0 +1,122 @@ +#pragma once +#include "console/pr_dbg.hh" +#include "gui/module_menu/base_plugin_menu.hh" +#include "gui/styles.hh" +#include +#include + +namespace MetaModule +{ + +struct RackModuleMenu : BasePluginModuleMenu { + RackModuleMenu(std::shared_ptr module_widget) + : module_widget{module_widget} { + } + + std::vector get_items() override { + std::vector item_strings; + + if (!rack_menu) { + // Initialize + rack_menu = std::make_shared(); + current_menu = rack_menu.get(); + rack_menu->parentMenu = nullptr; + + if (auto mw = module_widget.lock()) { + mw->appendContextMenu(rack_menu.get()); + } + + exited = false; + } + + if (current_menu) { + add_menu_items(current_menu->children, item_strings); + } + + return item_strings; + } + + void back_event() override { + if (current_menu && current_menu->parentMenu) { + current_menu = current_menu->parentMenu; + } else { + rack_menu.reset(); + current_menu = nullptr; + exited = true; + } + } + + bool is_done() override { + return exited; + } + + void click_item(unsigned idx) override { + if (!current_menu) + return; + + unsigned i = 0; + for (auto child : current_menu->children) { + if (auto rack_item = dynamic_cast(child)) { + if (i == idx) { + // Perform its action + rack_item->doAction(); + + // Remove any existing submenu (from a previous click) + if (current_menu->childMenu) { + current_menu->childMenu->parent->removeChild(current_menu->childMenu); + delete current_menu->childMenu; + current_menu->childMenu = nullptr; + } + + // Enter the submenu if it has one + auto submenu = rack_item->createChildMenu(); + if (submenu) { + // Add the submenu widget so we can delete it later + current_menu->childMenu = submenu; + current_menu->addChild(submenu); + + // point child to parent so we can traverse back + submenu->parentMenu = current_menu; + + // Make the submenu our current menu + current_menu = submenu; + } + break; + } + i++; + } + } + } + +private: + void add_menu_items(std::list children, std::vector &menu) { + unsigned num_children = 0; + for (auto child : children) { + if (auto rack_item = dynamic_cast(child)) { + child->step(); + + // Reasonable limit to size of menu: + if (num_children++ < 256) { + auto &item = menu.emplace_back(); + // Checkmarks go on left side + if (rack_item->rightText.ends_with(CHECKMARK_STRING)) + item = Gui::yellow_text(CHECKMARK_STRING); + + item += rack_item->text; + + if (rack_item->rightText.length() && !rack_item->rightText.ends_with(CHECKMARK_STRING)) + item += " " + Gui::yellow_text(rack_item->rightText); + } + } + } + } + + std::weak_ptr module_widget{}; + std::shared_ptr rack_menu; + + rack::ui::Menu *current_menu{}; //can't use smart pointer because must point to a raw pointer in rack API + + bool exited = false; +}; + +} // namespace MetaModule diff --git a/firmware/src/gui/pages/module_view.hh b/firmware/src/gui/pages/module_view.hh index 61df276fc..b0e3feecf 100644 --- a/firmware/src/gui/pages/module_view.hh +++ b/firmware/src/gui/pages/module_view.hh @@ -8,10 +8,12 @@ #include "gui/elements/redraw_display.hh" #include "gui/elements/redraw_light.hh" #include "gui/helpers/roller_hover_text.hh" +#include "gui/module_menu/plugin_module_menu.hh" #include "gui/pages/base.hh" #include "gui/pages/cable_drawer.hh" #include "gui/pages/module_view_action_menu.hh" #include "gui/pages/module_view_mapping_pane.hh" +#include "gui/pages/module_view_roller_helpers.hh" #include "gui/pages/module_view_settings_menu.hh" #include "gui/pages/page_list.hh" #include "gui/slsexport/meta5/ui.h" @@ -30,7 +32,8 @@ struct ModuleViewPage : PageBase { , patch{patches.get_view_patch()} , mapping_pane{patches, module_mods, params, args, page_list, notify_queue, gui_state} , action_menu{module_mods, patches, page_list, patch_playloader, notify_queue} - , roller_hover(ui_ElementRollerPanel, ui_ElementRoller) { + , roller_hover(ui_ElementRollerPanel, ui_ElementRoller) + , module_menu{patch_playloader} { init_bg(ui_MappingMenu); @@ -55,6 +58,9 @@ struct ModuleViewPage : PageBase { lv_group_add_obj(group, ui_ElementRoller); lv_group_focus_obj(ui_ElementRoller); + lv_group_add_obj(group, ui_ModuleViewExtraMenuRoller); + lv_hide(ui_ModuleViewExtraMenuRoller); + lv_group_set_wrap(group, false); lv_obj_add_event_cb(ui_ElementRoller, roller_scrolled_cb, LV_EVENT_KEY, this); @@ -123,7 +129,7 @@ struct ModuleViewPage : PageBase { void redraw_module() { reset_module_page(); size_t num_elements = moduleinfo.elements.size(); - opts.reserve(num_elements * 32); // 32 chars per roller item + opts.reserve(num_elements * 32); // estimate avg. 32 chars per roller item button.reserve(num_elements); drawn_elements.reserve(num_elements); @@ -170,13 +176,13 @@ struct ModuleViewPage : PageBase { continue; } - if (is_light_only(gui_el)) + if (ModView::is_light_only(gui_el)) continue; - if (should_skip_for_cable_mode(gui_state.new_cable, gui_el)) + if (ModView::should_skip_for_cable_mode(gui_state.new_cable, gui_el, gui_state, patch, this_module_id)) continue; - if (append_header(opts, last_type, gui_el.count)) { + if (ModView::append_header(opts, last_type, gui_el.count)) { roller_idx++; roller_drawn_el_idx.push_back(-1); } @@ -205,6 +211,16 @@ struct ModuleViewPage : PageBase { roller_idx++; } + if (is_patch_playing) { + if (module_menu.create_options_menu(this_module_id)) { + opts += Gui::orange_text("Options:") + "\n"; + opts += " >>>\n"; + roller_drawn_el_idx.push_back(-1); + roller_drawn_el_idx.push_back(ExtraMenuTag); + roller_idx += 2; + } + } + if (roller_idx <= 1) { if (gui_state.new_cable) { opts.append("No available jacks to patch\n"); @@ -269,51 +285,6 @@ struct ModuleViewPage : PageBase { drawn_element.element); } - bool is_light_only(GuiElement const &gui_el) const { - return (gui_el.count.num_lights > 0) && (gui_el.count.num_params == 0) && (gui_el.count.num_outputs == 0) && - (gui_el.count.num_inputs == 0); - } - - bool should_skip_for_cable_mode(std::optional const &new_cable, - GuiElement const &gui_el) const { - if (gui_state.new_cable.has_value()) { - uint16_t this_jack_id{}; - if (gui_el.count.num_inputs > 0) - this_jack_id = gui_el.idx.input_idx; - else if (gui_el.count.num_outputs > 0) - this_jack_id = gui_el.idx.output_idx; - else - return true; - auto this_jack_type = (gui_el.count.num_inputs > 0) ? ElementType::Input : ElementType::Output; - if (!can_finish_cable(gui_state.new_cable.value(), - patch, - Jack{.module_id = this_module_id, .jack_id = this_jack_id}, - this_jack_type, - gui_el.mapped_panel_id.has_value())) - return true; - } - return false; - } - - bool append_header(std::string &opts, ElementCount::Counts last_type, ElementCount::Counts this_type) { - if (last_type.num_params == 0 && this_type.num_params > 0) { - opts += Gui::orange_text("Params:") + "\n"; - return true; - - } else if ((last_type.num_inputs == 0 && last_type.num_outputs == 0) && - (this_type.num_inputs > 0 || this_type.num_outputs > 0)) - { - opts += Gui::orange_text("Jacks:") + "\n"; - return true; - - } else if (last_type.num_lights == 0 && this_type.num_lights > 0 && this_type.num_params == 0) { - opts += Gui::orange_text("Lights:") + "\n"; - return true; - } else { - return false; - } - } - bool is_creating_map() const { return mapping_pane.is_creating_map(); } @@ -333,6 +304,9 @@ struct ModuleViewPage : PageBase { } else if (mode == ViewMode::Mapping) { mapping_pane.back_event(); + + } else if (mode == ViewMode::ExtraMenu) { + module_menu.back_event(); } } @@ -343,6 +317,14 @@ struct ModuleViewPage : PageBase { } } + if (mode == ViewMode::ExtraMenu) { + module_menu.update(); + if (module_menu.wants_to_close()) { + module_menu.hide(); + show_roller(); + } + } + if (action_menu.is_visible()) action_menu.update(); @@ -392,6 +374,10 @@ struct ModuleViewPage : PageBase { mapping_pane.refresh(); } + if (lv_group_get_focused(group) == ui_ModuleViewActionBut || + lv_group_get_focused(group) == ui_ModuleViewSettingsBut) + roller_hover.hide(); + roller_hover.update(); } @@ -465,6 +451,7 @@ private: void show_roller() { mode = ViewMode::List; mapping_pane.hide(); + lv_show(ui_ElementRoller); lv_show(ui_ElementRollerPanel); lv_group_focus_obj(ui_ElementRoller); lv_group_set_editing(group, true); @@ -527,14 +514,24 @@ private: auto page = static_cast(event->user_data); auto cur_sel = lv_roller_get_selected(ui_ElementRoller); - if (cur_sel >= page->roller_drawn_el_idx.size()) + if (cur_sel >= page->roller_drawn_el_idx.size()) { + page->roller_hover.hide(); return; + } auto prev_sel = page->cur_selected; auto cur_idx = page->roller_drawn_el_idx[cur_sel]; + // Extra menu: + if (cur_idx == ExtraMenuTag) { + page->unhighlight_component(prev_sel); + page->cur_selected = cur_sel; + page->roller_hover.hide(); + return; + } + // Skip over headers by scrolling over them in the same direction we just scrolled - if (cur_idx < 0) { + if (cur_idx == -1) { if (prev_sel < cur_sel) { if (cur_sel < lv_roller_get_option_cnt(ui_ElementRoller) - 1) cur_sel++; @@ -597,8 +594,9 @@ private: static void roller_click_cb(lv_event_t *event) { auto page = static_cast(event->user_data); + auto roller_idx = page->cur_selected; - if (auto drawn_idx = page->get_drawn_idx(page->cur_selected)) { + if (auto drawn_idx = page->get_drawn_idx(roller_idx)) { if (page->gui_state.new_cable) { // Determine id and type of this element std::optional this_jack{}; @@ -650,6 +648,15 @@ private: page->mapping_pane.show(page->drawn_elements[*drawn_idx]); } + + //Not an element: Is it the Extra Menu? + } else if (roller_idx < page->roller_drawn_el_idx.size()) { + if (page->roller_drawn_el_idx[roller_idx] == ExtraMenuTag) { + page->mode = ViewMode::ExtraMenu; + lv_hide(ui_ElementRoller); + page->roller_hover.hide(); + page->module_menu.show(); + } } } @@ -725,9 +732,13 @@ private: lv_color_t buffer[LV_CANVAS_BUF_SIZE_TRUE_COLOR_ALPHA(240, 240)]{}; lv_draw_img_dsc_t img_dsc{}; - enum class ViewMode { List, Mapping } mode{ViewMode::List}; + enum class ViewMode { List, Mapping, ExtraMenu } mode{ViewMode::List}; RollerHoverText roller_hover; + + PluginModuleMenu module_menu; + + enum { ExtraMenuTag = -2 }; }; } // namespace MetaModule diff --git a/firmware/src/gui/pages/module_view_roller_helpers.hh b/firmware/src/gui/pages/module_view_roller_helpers.hh new file mode 100644 index 000000000..f2b10d913 --- /dev/null +++ b/firmware/src/gui/pages/module_view_roller_helpers.hh @@ -0,0 +1,63 @@ +#pragma once +#include "CoreModules/elements/element_counter.hh" +#include "gui/elements/context.hh" +#include "gui/elements/element_type.hh" +#include "gui/pages/base.hh" +#include "gui/pages/make_cable.hh" +#include "gui/styles.hh" +#include "patch/patch_data.hh" +#include "util/overloaded.hh" +#include + +namespace MetaModule::ModView +{ + +inline bool is_light_only(GuiElement const &gui_el) { + return (gui_el.count.num_lights > 0) && (gui_el.count.num_params == 0) && (gui_el.count.num_outputs == 0) && + (gui_el.count.num_inputs == 0); +} + +inline bool should_skip_for_cable_mode(std::optional const &new_cable, + GuiElement const &gui_el, + GuiState &gui_state, + PatchData *patch, + uint16_t this_module_id) { + if (gui_state.new_cable.has_value()) { + uint16_t this_jack_id{}; + if (gui_el.count.num_inputs > 0) + this_jack_id = gui_el.idx.input_idx; + else if (gui_el.count.num_outputs > 0) + this_jack_id = gui_el.idx.output_idx; + else + return true; + auto this_jack_type = (gui_el.count.num_inputs > 0) ? ElementType::Input : ElementType::Output; + if (!can_finish_cable(gui_state.new_cable.value(), + patch, + Jack{.module_id = this_module_id, .jack_id = this_jack_id}, + this_jack_type, + gui_el.mapped_panel_id.has_value())) + return true; + } + return false; +} + +inline bool append_header(std::string &opts, ElementCount::Counts last_type, ElementCount::Counts this_type) { + if (last_type.num_params == 0 && this_type.num_params > 0) { + opts += Gui::orange_text("Params:") + "\n"; + return true; + + } else if ((last_type.num_inputs == 0 && last_type.num_outputs == 0) && + (this_type.num_inputs > 0 || this_type.num_outputs > 0)) + { + opts += Gui::orange_text("Jacks:") + "\n"; + return true; + + } else if (last_type.num_lights == 0 && this_type.num_lights > 0 && this_type.num_params == 0) { + opts += Gui::orange_text("Lights:") + "\n"; + return true; + } else { + return false; + } +} + +} // namespace MetaModule::ModView diff --git a/firmware/src/gui/slsexport/meta5/screens/ui_MappingMenu.c b/firmware/src/gui/slsexport/meta5/screens/ui_MappingMenu.c index ae18986d9..ca9c72a6b 100644 --- a/firmware/src/gui/slsexport/meta5/screens/ui_MappingMenu.c +++ b/firmware/src/gui/slsexport/meta5/screens/ui_MappingMenu.c @@ -308,6 +308,63 @@ lv_obj_set_style_text_opa(ui_ElementRoller, 255, LV_PART_SELECTED| LV_STATE_FOCU lv_obj_set_style_bg_color(ui_ElementRoller, lv_color_hex(0xFD8B18), LV_PART_SELECTED | LV_STATE_FOCUSED ); lv_obj_set_style_bg_opa(ui_ElementRoller, 255, LV_PART_SELECTED| LV_STATE_FOCUSED); +ui_ModuleViewExtraMenuRoller = lv_roller_create(ui_ElementRollerPanel); +lv_roller_set_options( ui_ModuleViewExtraMenuRoller, "< Back", LV_ROLLER_MODE_NORMAL ); +lv_roller_set_selected( ui_ModuleViewExtraMenuRoller, 4, LV_ANIM_OFF); +lv_obj_set_height( ui_ModuleViewExtraMenuRoller, 186); +lv_obj_set_width( ui_ModuleViewExtraMenuRoller, lv_pct(109)); +lv_obj_set_align( ui_ModuleViewExtraMenuRoller, LV_ALIGN_BOTTOM_MID ); +lv_obj_add_state( ui_ModuleViewExtraMenuRoller, LV_STATE_FOCUSED ); /// States +lv_obj_add_flag( ui_ModuleViewExtraMenuRoller, LV_OBJ_FLAG_HIDDEN ); /// Flags +lv_obj_clear_flag( ui_ModuleViewExtraMenuRoller, LV_OBJ_FLAG_CLICKABLE | LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_CLICK_FOCUSABLE | LV_OBJ_FLAG_GESTURE_BUBBLE | LV_OBJ_FLAG_SNAPPABLE ); /// Flags +lv_obj_set_style_text_color(ui_ModuleViewExtraMenuRoller, lv_color_hex(0x999999), LV_PART_MAIN | LV_STATE_DEFAULT ); +lv_obj_set_style_text_opa(ui_ModuleViewExtraMenuRoller, 255, LV_PART_MAIN| LV_STATE_DEFAULT); +lv_obj_set_style_text_letter_space(ui_ModuleViewExtraMenuRoller, 0, LV_PART_MAIN| LV_STATE_DEFAULT); +lv_obj_set_style_text_line_space(ui_ModuleViewExtraMenuRoller, 5, LV_PART_MAIN| LV_STATE_DEFAULT); +lv_obj_set_style_text_align(ui_ModuleViewExtraMenuRoller, LV_TEXT_ALIGN_LEFT, LV_PART_MAIN| LV_STATE_DEFAULT); +lv_obj_set_style_text_font(ui_ModuleViewExtraMenuRoller, &ui_font_MuseoSansRounded70016, LV_PART_MAIN| LV_STATE_DEFAULT); +lv_obj_set_style_radius(ui_ModuleViewExtraMenuRoller, 4, LV_PART_MAIN| LV_STATE_DEFAULT); +lv_obj_set_style_bg_color(ui_ModuleViewExtraMenuRoller, lv_color_hex(0x333333), LV_PART_MAIN | LV_STATE_DEFAULT ); +lv_obj_set_style_bg_opa(ui_ModuleViewExtraMenuRoller, 0, LV_PART_MAIN| LV_STATE_DEFAULT); +lv_obj_set_style_border_color(ui_ModuleViewExtraMenuRoller, lv_color_hex(0x777777), LV_PART_MAIN | LV_STATE_DEFAULT ); +lv_obj_set_style_border_opa(ui_ModuleViewExtraMenuRoller, 255, LV_PART_MAIN| LV_STATE_DEFAULT); +lv_obj_set_style_border_width(ui_ModuleViewExtraMenuRoller, 2, LV_PART_MAIN| LV_STATE_DEFAULT); +lv_obj_set_style_border_color(ui_ModuleViewExtraMenuRoller, lv_color_hex(0xFD8B18), LV_PART_MAIN | LV_STATE_FOCUSED ); +lv_obj_set_style_border_opa(ui_ModuleViewExtraMenuRoller, 255, LV_PART_MAIN| LV_STATE_FOCUSED); +lv_obj_set_style_border_width(ui_ModuleViewExtraMenuRoller, 2, LV_PART_MAIN| LV_STATE_FOCUSED); +lv_obj_set_style_border_side(ui_ModuleViewExtraMenuRoller, LV_BORDER_SIDE_FULL, LV_PART_MAIN| LV_STATE_FOCUSED); +lv_obj_set_style_shadow_color(ui_ModuleViewExtraMenuRoller, lv_color_hex(0x000000), LV_PART_MAIN | LV_STATE_FOCUSED ); +lv_obj_set_style_shadow_opa(ui_ModuleViewExtraMenuRoller, 0, LV_PART_MAIN| LV_STATE_FOCUSED); +lv_obj_set_style_shadow_width(ui_ModuleViewExtraMenuRoller, 0, LV_PART_MAIN| LV_STATE_FOCUSED); +lv_obj_set_style_shadow_spread(ui_ModuleViewExtraMenuRoller, 0, LV_PART_MAIN| LV_STATE_FOCUSED); +lv_obj_set_style_shadow_ofs_x(ui_ModuleViewExtraMenuRoller, 0, LV_PART_MAIN| LV_STATE_FOCUSED); +lv_obj_set_style_shadow_ofs_y(ui_ModuleViewExtraMenuRoller, 0, LV_PART_MAIN| LV_STATE_FOCUSED); + +lv_obj_set_style_text_color(ui_ModuleViewExtraMenuRoller, lv_color_hex(0xFFFFFF), LV_PART_SELECTED | LV_STATE_DEFAULT ); +lv_obj_set_style_text_opa(ui_ModuleViewExtraMenuRoller, 192, LV_PART_SELECTED| LV_STATE_DEFAULT); +lv_obj_set_style_text_letter_space(ui_ModuleViewExtraMenuRoller, 0, LV_PART_SELECTED| LV_STATE_DEFAULT); +lv_obj_set_style_text_line_space(ui_ModuleViewExtraMenuRoller, 5, LV_PART_SELECTED| LV_STATE_DEFAULT); +lv_obj_set_style_text_align(ui_ModuleViewExtraMenuRoller, LV_TEXT_ALIGN_LEFT, LV_PART_SELECTED| LV_STATE_DEFAULT); +lv_obj_set_style_text_font(ui_ModuleViewExtraMenuRoller, &ui_font_MuseoSansRounded70016, LV_PART_SELECTED| LV_STATE_DEFAULT); +lv_obj_set_style_radius(ui_ModuleViewExtraMenuRoller, 0, LV_PART_SELECTED| LV_STATE_DEFAULT); +lv_obj_set_style_bg_color(ui_ModuleViewExtraMenuRoller, lv_color_hex(0xFD8B18), LV_PART_SELECTED | LV_STATE_DEFAULT ); +lv_obj_set_style_bg_opa(ui_ModuleViewExtraMenuRoller, 64, LV_PART_SELECTED| LV_STATE_DEFAULT); +lv_obj_set_style_border_color(ui_ModuleViewExtraMenuRoller, lv_color_hex(0x000000), LV_PART_SELECTED | LV_STATE_DEFAULT ); +lv_obj_set_style_border_opa(ui_ModuleViewExtraMenuRoller, 255, LV_PART_SELECTED| LV_STATE_DEFAULT); +lv_obj_set_style_border_width(ui_ModuleViewExtraMenuRoller, 0, LV_PART_SELECTED| LV_STATE_DEFAULT); +lv_obj_set_style_border_side(ui_ModuleViewExtraMenuRoller, LV_BORDER_SIDE_NONE, LV_PART_SELECTED| LV_STATE_DEFAULT); +lv_obj_set_style_shadow_color(ui_ModuleViewExtraMenuRoller, lv_color_hex(0x000000), LV_PART_SELECTED | LV_STATE_DEFAULT ); +lv_obj_set_style_shadow_opa(ui_ModuleViewExtraMenuRoller, 255, LV_PART_SELECTED| LV_STATE_DEFAULT); +lv_obj_set_style_shadow_width(ui_ModuleViewExtraMenuRoller, 0, LV_PART_SELECTED| LV_STATE_DEFAULT); +lv_obj_set_style_shadow_spread(ui_ModuleViewExtraMenuRoller, 0, LV_PART_SELECTED| LV_STATE_DEFAULT); +lv_obj_set_style_shadow_ofs_x(ui_ModuleViewExtraMenuRoller, 0, LV_PART_SELECTED| LV_STATE_DEFAULT); +lv_obj_set_style_shadow_ofs_y(ui_ModuleViewExtraMenuRoller, 0, LV_PART_SELECTED| LV_STATE_DEFAULT); +lv_obj_set_style_blend_mode(ui_ModuleViewExtraMenuRoller, LV_BLEND_MODE_NORMAL, LV_PART_SELECTED| LV_STATE_DEFAULT); +lv_obj_set_style_text_color(ui_ModuleViewExtraMenuRoller, lv_color_hex(0xFFFFFF), LV_PART_SELECTED | LV_STATE_FOCUSED ); +lv_obj_set_style_text_opa(ui_ModuleViewExtraMenuRoller, 255, LV_PART_SELECTED| LV_STATE_FOCUSED); +lv_obj_set_style_bg_color(ui_ModuleViewExtraMenuRoller, lv_color_hex(0xFD8B18), LV_PART_SELECTED | LV_STATE_FOCUSED ); +lv_obj_set_style_bg_opa(ui_ModuleViewExtraMenuRoller, 255, LV_PART_SELECTED| LV_STATE_FOCUSED); + ui_ModuleViewActionMenu = lv_obj_create(ui_ElementRollerPanel); lv_obj_set_width( ui_ModuleViewActionMenu, 158); lv_obj_set_height( ui_ModuleViewActionMenu, LV_SIZE_CONTENT); /// 168 @@ -316,7 +373,7 @@ lv_obj_set_y( ui_ModuleViewActionMenu, -3 ); lv_obj_set_align( ui_ModuleViewActionMenu, LV_ALIGN_TOP_RIGHT ); lv_obj_set_flex_flow(ui_ModuleViewActionMenu,LV_FLEX_FLOW_COLUMN); lv_obj_set_flex_align(ui_ModuleViewActionMenu, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START); -lv_obj_add_flag( ui_ModuleViewActionMenu, LV_OBJ_FLAG_HIDDEN | LV_OBJ_FLAG_FLOATING ); /// Flags +lv_obj_add_flag( ui_ModuleViewActionMenu, LV_OBJ_FLAG_FLOATING ); /// Flags lv_obj_clear_flag( ui_ModuleViewActionMenu, LV_OBJ_FLAG_CLICKABLE | LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_CLICK_FOCUSABLE | LV_OBJ_FLAG_GESTURE_BUBBLE | LV_OBJ_FLAG_SNAPPABLE | LV_OBJ_FLAG_SCROLL_ELASTIC | LV_OBJ_FLAG_SCROLL_MOMENTUM ); /// Flags lv_obj_set_scroll_dir(ui_ModuleViewActionMenu, LV_DIR_VER); lv_obj_set_style_radius(ui_ModuleViewActionMenu, 0, LV_PART_MAIN| LV_STATE_DEFAULT); diff --git a/firmware/src/gui/slsexport/meta5/ui.c b/firmware/src/gui/slsexport/meta5/ui.c index 281d4c0c9..a0f2d31fc 100644 --- a/firmware/src/gui/slsexport/meta5/ui.c +++ b/firmware/src/gui/slsexport/meta5/ui.c @@ -182,6 +182,7 @@ lv_obj_t *ui_ModuleViewSettingsButLabel; lv_obj_t *ui_ModuleViewCableCancelBut; lv_obj_t *ui_ModuleViewCabelCancelLabel; lv_obj_t *ui_ElementRoller; +lv_obj_t *ui_ModuleViewExtraMenuRoller; lv_obj_t *ui_ModuleViewActionMenu; lv_obj_t *ui_ModuleViewActionHeader; lv_obj_t *ui_ModuleViewActionMenuHeader; diff --git a/firmware/src/gui/slsexport/meta5/ui.h b/firmware/src/gui/slsexport/meta5/ui.h index f63d0e766..2857433b5 100644 --- a/firmware/src/gui/slsexport/meta5/ui.h +++ b/firmware/src/gui/slsexport/meta5/ui.h @@ -184,6 +184,7 @@ extern lv_obj_t *ui_ModuleViewSettingsButLabel; extern lv_obj_t *ui_ModuleViewCableCancelBut; extern lv_obj_t *ui_ModuleViewCabelCancelLabel; extern lv_obj_t *ui_ElementRoller; +extern lv_obj_t *ui_ModuleViewExtraMenuRoller; extern lv_obj_t *ui_ModuleViewActionMenu; extern lv_obj_t *ui_ModuleViewActionHeader; extern lv_obj_t *ui_ModuleViewActionMenuHeader; diff --git a/firmware/src/gui/styles.hh b/firmware/src/gui/styles.hh index 95778c65a..637fb10dc 100644 --- a/firmware/src/gui/styles.hh +++ b/firmware/src/gui/styles.hh @@ -92,6 +92,10 @@ struct Gui { return color_text(txt, "^aaaaaa "); } + static std::string lt_grey_text(std::string_view txt) { + return color_text(txt, "^cccccc "); + } + static inline const char *brown_highlight_html = "^A26E3E "; static inline lv_theme_t *theme; diff --git a/firmware/src/patch_play/modules_helpers.cc b/firmware/src/patch_play/modules_helpers.cc index 984069bd4..7c0f4b4f3 100644 --- a/firmware/src/patch_play/modules_helpers.cc +++ b/firmware/src/patch_play/modules_helpers.cc @@ -14,7 +14,7 @@ std::optional get_normalized_default_value(Element const &element) { [&](T const &el) requires(std::derived_from || std::derived_from) { - return std::optional{el.num_pos > 0 ? (float)el.DefaultValue / (float)el.num_pos : 0}; + return std::optional{el.num_pos > 0 ? (float)el.default_value / (float)el.num_pos : 0}; }, [&](T const &el) @@ -22,7 +22,7 @@ std::optional get_normalized_default_value(Element const &element) { requires(std::derived_from || (std::derived_from && !std::derived_from && !std::derived_from)) { - return std::optional((float)el.DefaultValue); + return std::optional((float)el.default_value); }, }, element); diff --git a/firmware/src/patch_play/patch_playloader.hh b/firmware/src/patch_play/patch_playloader.hh index 2200f721c..f0551a163 100644 --- a/firmware/src/patch_play/patch_playloader.hh +++ b/firmware/src/patch_play/patch_playloader.hh @@ -257,6 +257,14 @@ struct PatchPlayLoader { return new_audio_settings_.load(); } + template + PluginModuleType *get_plugin_module(int32_t module_idx) { + if (auto pluginmodule = dynamic_cast(player_.modules[module_idx].get())) + return pluginmodule; + else + return nullptr; + } + private: PatchPlayer &player_; FileStorageProxy &storage_; diff --git a/firmware/vcv_plugin/export/CMakeLists.txt b/firmware/vcv_plugin/export/CMakeLists.txt index 4ed07d1b7..5a79bdd6b 100644 --- a/firmware/vcv_plugin/export/CMakeLists.txt +++ b/firmware/vcv_plugin/export/CMakeLists.txt @@ -28,11 +28,17 @@ target_sources( src/app/SvgSwitch.cc src/app/SvgButton.cc src/app/SvgScrew.cc + src/engine/Engine.cpp src/engine/Module.cpp src/engine/ParamQuantity.cpp + src/plugin/Model.cpp src/plugin/Plugin.cpp + + src/ui/Menu.cc + src/ui/MenuItem.cc + src/widget/Widget.cc src/widget/SvgWidget.cc src/widget/FramebufferWidget.cc diff --git a/firmware/vcv_plugin/export/src/ui/Menu.cc b/firmware/vcv_plugin/export/src/ui/Menu.cc new file mode 100644 index 000000000..d14dd7d17 --- /dev/null +++ b/firmware/vcv_plugin/export/src/ui/Menu.cc @@ -0,0 +1,37 @@ +#include "console/pr_dbg.hh" +#include + +namespace rack::ui +{ + +Menu::Menu() = default; + +Menu::~Menu() { + setChildMenu(nullptr); +} + +void Menu::setChildMenu(Menu *menu) { + if (childMenu) { + childMenu->parent->removeChild(childMenu); + delete childMenu; + childMenu = nullptr; + } + if (menu) { + childMenu = menu; + if (!parent) + pr_err("rack::ui::Menu object has no parent! Memory will be leaked\n"); + else + parent->addChild(childMenu); + } +} + +void Menu::step() { +} + +void Menu::draw(const widget::Widget::DrawArgs &args) { +} + +void Menu::onHoverScroll(const widget::Widget::HoverScrollEvent &e) { +} + +} // namespace rack::ui diff --git a/firmware/vcv_plugin/export/src/ui/MenuItem.cc b/firmware/vcv_plugin/export/src/ui/MenuItem.cc new file mode 100644 index 000000000..aa6b87caf --- /dev/null +++ b/firmware/vcv_plugin/export/src/ui/MenuItem.cc @@ -0,0 +1,40 @@ +#include + +namespace rack::ui +{ + +void MenuItem::draw(const DrawArgs &args) { +} + +void MenuItem::drawOffset(NVGcontext *vg, float offset) { +} + +void MenuItem::step() { +} + +void MenuItem::onEnter(const EnterEvent &e) { +} + +void MenuItem::onDragDrop(const DragDropEvent &e) { +} + +void MenuItem::doAction(bool consume) { + widget::EventContext cAction; + ActionEvent eAction; + eAction.context = &cAction; + if (consume) { + eAction.consume(this); + } + onAction(eAction); +} + +void MenuItem::onAction(const ActionEvent &e) { +} + +void ColorDotMenuItem::draw(const DrawArgs &args) { +} + +void ColorDotMenuItem::step() { +} + +} // namespace rack::ui diff --git a/firmware/vcv_plugin/internal/make_element.cc b/firmware/vcv_plugin/internal/make_element.cc index 22e41ac72..d354601bb 100644 --- a/firmware/vcv_plugin/internal/make_element.cc +++ b/firmware/vcv_plugin/internal/make_element.cc @@ -146,7 +146,7 @@ static Element make_slideswitch(rack::app::SvgSlider *widget) { element.image = widget->background->svg->filename; } - element.DefaultValue = getDefaultValue(widget); + element.default_value = getDefaultValue(widget); if (widget->handle->svg->filename.length()) element.image_handle = widget->handle->svg->filename; @@ -260,7 +260,7 @@ static SlideSwitch make_slideswitch(rack::app::SvgSwitch *widget) { element.image_handle = "no-image"; element.image = widget->frames[0]->filename; - element.DefaultValue = getDefaultValue(widget); + element.default_value = getDefaultValue(widget); return element; } @@ -289,7 +289,7 @@ static FlipSwitch make_flipswitch(rack::app::SvgSwitch *widget) { element.frames[i] = widget->frames[i]->filename; } - element.DefaultValue = getDefaultValue(widget); + element.default_value = getDefaultValue(widget); return element; } @@ -343,7 +343,7 @@ static Element make_latching_mono(std::string_view image, NVGcolor c, LatchingBu LatchingButton element; element.image = image; element.color = RGB565{c.r, c.g, c.b}; - element.DefaultValue = defaultValue; + element.default_value = defaultValue; log_make_element_notes("make_latching_mono()", ""); return element; } diff --git a/firmware/vcv_ports/Befaco b/firmware/vcv_ports/Befaco index 553c86564..325bf42c8 160000 --- a/firmware/vcv_ports/Befaco +++ b/firmware/vcv_ports/Befaco @@ -1 +1 @@ -Subproject commit 553c86564a46c3b4d144e979ccd56a270e51677d +Subproject commit 325bf42c8f7b706d6b6fde77857b3d92a1a1ecba diff --git a/firmware/vcv_ports/RackCore/MIDI_CV.cpp b/firmware/vcv_ports/RackCore/MIDI_CV.cpp index 8fc3299bb..0c6f6073e 100644 --- a/firmware/vcv_ports/RackCore/MIDI_CV.cpp +++ b/firmware/vcv_ports/RackCore/MIDI_CV.cpp @@ -550,26 +550,34 @@ struct MIDI_CVWidget : ModuleWidget { else return string::f("%g octave", pwRange / 12) + (pwRange / 12 == 1 ? "" : "s"); }; - menu->addChild(createSubmenuItem("Pitch bend range", getPwRangeLabel(module->pwRange), [=](Menu *menu) { - for (size_t i = 0; i < pwRanges.size(); i++) { - menu->addChild(createCheckMenuItem( - getPwRangeLabel(pwRanges[i]), - "", - [=]() { return module->pwRange == pwRanges[i]; }, - [=]() { module->pwRange = pwRanges[i]; })); - } - })); + // METAMODULE: using lambda for rightText in createSubmenuItem for: + // - Pitch bend range + // - CLK/N divider + // - Polyphony channels + menu->addChild(createSubmenuItem( + "Pitch bend range", + [=] { return getPwRangeLabel(module->pwRange); }, + [=](Menu *menu) { + for (float pwRange : pwRanges) { + menu->addChild(createCheckMenuItem( + getPwRangeLabel(pwRange), + "", + [=]() { return module->pwRange == pwRange; }, + [=]() { module->pwRange = pwRange; })); + } + })); menu->addChild(createBoolPtrMenuItem("Smooth pitch/mod wheel", "", &module->smooth)); static const std::vector clockDivisions = {24 * 4, 24 * 2, 24, 24 / 2, 24 / 4, 24 / 8, 2, 1}; static const std::vector clockDivisionLabels = { "Whole", "Half", "Quarter", "8th", "16th", "32nd", "12 PPQN", "24 PPQN"}; - size_t clockDivisionIndex = - std::find(clockDivisions.begin(), clockDivisions.end(), module->clockDivision) - clockDivisions.begin(); - std::string clockDivisionLabel = - (clockDivisionIndex < clockDivisionLabels.size()) ? clockDivisionLabels[clockDivisionIndex] : ""; - menu->addChild(createSubmenuItem("CLK/N divider", clockDivisionLabel, [=](Menu *menu) { + auto getClockDivisionLabel = [=] { + size_t clockDivisionIndex = + std::find(clockDivisions.begin(), clockDivisions.end(), module->clockDivision) - clockDivisions.begin(); + return (clockDivisionIndex < clockDivisionLabels.size()) ? clockDivisionLabels[clockDivisionIndex] : ""; + }; + menu->addChild(createSubmenuItem("CLK/N divider", getClockDivisionLabel, [=](Menu *menu) { for (size_t i = 0; i < clockDivisions.size(); i++) { menu->addChild(createCheckMenuItem( clockDivisionLabels[i], @@ -579,14 +587,17 @@ struct MIDI_CVWidget : ModuleWidget { } })); - menu->addChild(createSubmenuItem("Polyphony channels", string::f("%d", module->channels), [=](Menu *menu) { - for (int c = 1; c <= 16; c++) { - menu->addChild(createCheckMenuItem((c == 1) ? "Monophonic" : string::f("%d", c), - "", - [=]() { return module->channels == c; }, - [=]() { module->setChannels(c); })); - } - })); + menu->addChild(createSubmenuItem( + "Polyphony channels", + [=] { return string::f("%d", module->channels); }, + [=](Menu *menu) { + for (int c = 1; c <= 16; c++) { + menu->addChild(createCheckMenuItem((c == 1) ? "Monophonic" : string::f("%d", c), + "", + [=]() { return module->channels == c; }, + [=]() { module->setChannels(c); })); + } + })); menu->addChild(createIndexPtrSubmenuItem("Polyphony mode", {