#include "app.hpp" #include "plugin.hpp" #include #include #include namespace rack { static std::string sManufacturer; static Model *sModel = NULL; static std::string sFilter; struct ListMenu : OpaqueWidget { void draw(NVGcontext *vg) override { Widget::draw(vg); } void step() override { Widget::step(); box.size.y = 0; for (Widget *child : children) { if (!child->visible) continue; // Increase height, set position of child child->box.pos = Vec(0, box.size.y); box.size.y += child->box.size.y; child->box.size.x = box.size.x; } } }; struct UrlItem : MenuItem { std::string url; void onAction(EventAction &e) override { std::thread t(openBrowser, url); t.detach(); } }; struct MetadataMenu : ListMenu { Model *model = NULL; void step() override { if (model != sModel) { model = sModel; clearChildren(); if (model) { // Tag list if (!model->tags.empty()) { for (ModelTag tag : model->tags) { if (0 <= tag && tag < NUM_TAGS) { addChild(construct(&MenuEntry::text, gTagNames[tag])); } } addChild(construct()); } // Plugin name std::string pluginName = model->plugin->slug; if (!model->plugin->version.empty()) { pluginName += " v"; pluginName += model->plugin->version; } addChild(construct(&MenuEntry::text, pluginName)); // Plugin metadata if (!model->plugin->website.empty()) { addChild(construct(&MenuEntry::text, "Website", &UrlItem::url, model->plugin->path)); } if (!model->plugin->manual.empty()) { addChild(construct(&MenuEntry::text, "Manual", &UrlItem::url, model->plugin->manual)); } if (!model->plugin->path.empty()) { addChild(construct(&MenuEntry::text, "Browse directory", &UrlItem::url, model->plugin->path)); } } } ListMenu::step(); } }; static bool isModelMatch(Model *model, std::string search) { // Build content string std::string str; str += model->manufacturer; str += " "; str += model->name; str += " "; str += model->slug; for (ModelTag tag : model->tags) { if (0 <= tag && tag < NUM_TAGS) { str += " "; str += gTagNames[tag]; } } str = tolower(str); search = tolower(search); return (str.find(search) != std::string::npos); } struct ModelItem : MenuItem { Model *model; void onAction(EventAction &e) override { ModuleWidget *moduleWidget = model->createModuleWidget(); gRackWidget->moduleContainer->addChild(moduleWidget); // Move module nearest to the mouse position Rect box; box.size = moduleWidget->box.size; AddModuleWindow *w = getAncestorOfType(); box.pos = w->modulePos.minus(box.getCenter()); gRackWidget->requestModuleBoxNearest(moduleWidget, box); } void onMouseEnter(EventMouseEnter &e) override { sModel = model; MenuItem::onMouseEnter(e); } void onMouseLeave(EventMouseLeave &e) override { sModel = NULL; MenuItem::onMouseLeave(e); } }; struct ModelMenu : ListMenu { std::string manufacturer; std::string filter; void step() override { if (manufacturer != sManufacturer) { manufacturer = sManufacturer; filter = ""; clearChildren(); addChild(construct(&MenuLabel::text, manufacturer)); // Add models for the selected manufacturer for (Plugin *plugin : gPlugins) { for (Model *model : plugin->models) { if (model->manufacturer == manufacturer) { addChild(construct(&MenuEntry::text, model->name, &ModelItem::model, model)); } } } } if (filter != sFilter) { filter = sFilter; // Make all children invisible for (Widget *child : children) { child->visible = false; } // Make children with a matching model visible for (Widget *child : children) { ModelItem *item = dynamic_cast(child); if (!item) continue; if (isModelMatch(item->model, filter)) { item->visible = true; } } } ListMenu::step(); } }; struct ManufacturerItem : MenuItem { Model *model; void onAction(EventAction &e) override { sManufacturer = text; e.consumed = false; } }; struct ManufacturerMenu : ListMenu { std::string filter; ManufacturerMenu() { addChild(construct(&MenuLabel::text, "Manufacturers")); // Collect manufacturer names std::set manufacturers; for (Plugin *plugin : gPlugins) { for (Model *model : plugin->models) { manufacturers.insert(model->manufacturer); } } // Add menu item for each manufacturer name for (std::string manufacturer : manufacturers) { addChild(construct(&MenuEntry::text, manufacturer)); } } void step() override { if (filter != sFilter) { // Make all children invisible for (Widget *child : children) { child->visible = false; } // Make children with a matching model visible for (Widget *child : children) { MenuItem *item = dynamic_cast(child); if (!item) continue; std::string manufacturer = item->text; for (Plugin *plugin : gPlugins) { for (Model *model : plugin->models) { if (model->manufacturer == manufacturer) { if (isModelMatch(model, sFilter)) { item->visible = true; } } } } } filter = sFilter; } ListMenu::step(); } }; struct SearchModuleField : TextField { void onTextChange() override { sFilter = text; } }; AddModuleWindow::AddModuleWindow() { box.size = Vec(600, 300); title = "Add module"; float posY = BND_NODE_TITLE_HEIGHT; // Search SearchModuleField *searchField = new SearchModuleField(); searchField->box.pos.y = posY; posY += searchField->box.size.y; searchField->box.size.x = box.size.x; searchField->text = sFilter; gFocusedWidget = searchField; { EventFocus eFocus; searchField->onFocus(eFocus); searchField->onTextChange(); } addChild(searchField); // Manufacturers ManufacturerMenu *manufacturerMenu = new ManufacturerMenu(); manufacturerMenu->box.size.x = 200; ScrollWidget *manufacturerScroll = new ScrollWidget(); manufacturerScroll->container->addChild(manufacturerMenu); manufacturerScroll->box.pos = Vec(0, posY); manufacturerScroll->box.size = Vec(200, box.size.y - posY); addChild(manufacturerScroll); // Models ModelMenu *modelMenu = new ModelMenu(); modelMenu->box.size.x = 200; ScrollWidget *modelScroll = new ScrollWidget(); modelScroll->container->addChild(modelMenu); modelScroll->box.pos = Vec(200, posY); modelScroll->box.size = Vec(200, box.size.y - posY); addChild(modelScroll); // Metadata MetadataMenu *metadataMenu = new MetadataMenu(); metadataMenu->box.size.x = 200; ScrollWidget *metadataScroll = new ScrollWidget(); metadataScroll->container->addChild(metadataMenu); metadataScroll->box.pos = Vec(400, posY); metadataScroll->box.size = Vec(200, box.size.y - posY); addChild(metadataScroll); } void AddModuleWindow::step() { Widget::step(); } } // namespace rack