Skip to content

Commit

Permalink
Merge branch 'vcv-context-menu' into v2.0-dev
Browse files Browse the repository at this point in the history
  • Loading branch information
danngreen committed Oct 30, 2024
2 parents acd0b5b + c74a754 commit 83abedf
Show file tree
Hide file tree
Showing 20 changed files with 667 additions and 85 deletions.
2 changes: 1 addition & 1 deletion firmware/metamodule-plugin-sdk
4 changes: 4 additions & 0 deletions firmware/src/gui/helpers/roller_hover_text.hh
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@ public:
}
}

void force_redraw() {
sel_idx = -1;
}

void default_draw_callback() {
display_in_time(10);
}
Expand Down
17 changes: 17 additions & 0 deletions firmware/src/gui/module_menu/base_plugin_menu.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#pragma once
#include <string>
#include <vector>

namespace MetaModule
{

struct BasePluginModuleMenu {
virtual ~BasePluginModuleMenu() = default;

virtual std::vector<std::string> get_items() = 0;
virtual void back_event() = 0;
virtual void click_item(unsigned idx) = 0;
virtual bool is_done() = 0;
};

} // namespace MetaModule
32 changes: 32 additions & 0 deletions firmware/src/gui/module_menu/native_plugin_menu.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#pragma once
#include "CoreModules/CoreProcessor.hh"
#include "gui/module_menu/base_plugin_menu.hh"
#include <memory>

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<std::string> get_items() override {
return {};
}

void back_event() override {
}

void click_item(unsigned idx) override {
}

bool is_done() override {
return true;
}
};

} // namespace MetaModule
168 changes: 168 additions & 0 deletions firmware/src/gui/module_menu/plugin_module_menu.hh
Original file line number Diff line number Diff line change
@@ -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 <functional>

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<rack::engine::Module>(this_module_id)) {
plugin_menu = std::make_unique<RackModuleMenu>(rack_module->module_widget);
return populate_menu_items() > 0;

} else if (auto native_module = patch_playloader.get_plugin_module<CoreProcessor>(this_module_id)) {
plugin_menu = std::make_unique<NativeModuleMenu>(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_group_t *>(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<std::string> 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<PluginModuleMenu *>(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_group_t *>(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<PluginModuleMenu *>(event->user_data);
page->roller_hover.hide();
}

std::unique_ptr<BasePluginModuleMenu> plugin_menu;

lv_obj_t *roller = ui_ModuleViewExtraMenuRoller;

RollerHoverText roller_hover;
PatchPlayLoader &patch_playloader;

bool visible = false;
bool should_close = false;
};

} // namespace MetaModule
122 changes: 122 additions & 0 deletions firmware/src/gui/module_menu/vcv_plugin_menu.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
#pragma once
#include "console/pr_dbg.hh"
#include "gui/module_menu/base_plugin_menu.hh"
#include "gui/styles.hh"
#include <app/ModuleWidget.hpp>
#include <ui/MenuItem.hpp>

namespace MetaModule
{

struct RackModuleMenu : BasePluginModuleMenu {
RackModuleMenu(std::shared_ptr<rack::app::ModuleWidget> module_widget)
: module_widget{module_widget} {
}

std::vector<std::string> get_items() override {
std::vector<std::string> item_strings;

if (!rack_menu) {
// Initialize
rack_menu = std::make_shared<rack::ui::Menu>();
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<rack::ui::MenuItem *>(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<rack::widget::Widget *> children, std::vector<std::string> &menu) {
unsigned num_children = 0;
for (auto child : children) {
if (auto rack_item = dynamic_cast<rack::ui::MenuItem *>(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<rack::app::ModuleWidget> module_widget{};
std::shared_ptr<rack::ui::Menu> 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
Loading

0 comments on commit 83abedf

Please sign in to comment.