|
- #include <set>
- #include <algorithm>
- #include <thread>
-
- #include <app/ModuleBrowser.hpp>
- #include <widget/OpaqueWidget.hpp>
- #include <widget/TransparentWidget.hpp>
- #include <widget/ZoomWidget.hpp>
- #include <ui/MenuOverlay.hpp>
- #include <ui/ScrollWidget.hpp>
- #include <ui/SequentialLayout.hpp>
- #include <ui/Label.hpp>
- #include <ui/Slider.hpp>
- #include <ui/TextField.hpp>
- #include <ui/MenuItem.hpp>
- #include <ui/MenuSeparator.hpp>
- #include <ui/Button.hpp>
- #include <ui/ChoiceButton.hpp>
- #include <ui/RadioButton.hpp>
- #include <ui/Tooltip.hpp>
- #include <app/ModuleWidget.hpp>
- #include <app/Scene.hpp>
- #include <plugin.hpp>
- #include <context.hpp>
- #include <engine/Engine.hpp>
- #include <plugin/Model.hpp>
- #include <string.hpp>
- #include <history.hpp>
- #include <settings.hpp>
- #include <system.hpp>
- #include <tag.hpp>
- #include <helpers.hpp>
- #include <FuzzySearchDatabase.hpp>
-
-
- namespace rack {
- namespace app {
- namespace moduleBrowser {
-
-
- static fuzzysearch::Database<plugin::Model*> modelDb;
- static bool modelDbInitialized = false;
-
-
- static void fuzzySearchInit() {
- if (modelDbInitialized)
- return;
- modelDb.setWeights({1.f, 1.f, 0.1f, 1.f, 0.5f, 0.5f});
- modelDb.setThreshold(0.25f);
-
- // Iterate plugins
- for (plugin::Plugin* plugin : plugin::plugins) {
- // Iterate model in plugin
- for (plugin::Model* model : plugin->models) {
- if (model->hidden)
- continue;
-
- // Get search fields for model
- std::string tagStr;
- for (int tagId : model->tagIds) {
- // Add all aliases of a tag
- for (const std::string& tagAlias : tag::tagAliases[tagId]) {
- tagStr += tagAlias;
- tagStr += ", ";
- }
- }
- std::vector<std::string> fields = {
- model->plugin->brand,
- model->plugin->name,
- model->plugin->description,
- model->name,
- model->description,
- tagStr,
- };
- // DEBUG("%s; %s; %s; %s; %s; %s", fields[0].c_str(), fields[1].c_str(), fields[2].c_str(), fields[3].c_str(), fields[4].c_str(), fields[5].c_str());
- modelDb.addEntry(model, fields);
- }
- }
-
- modelDbInitialized = true;
- }
-
-
- static ModuleWidget* chooseModel(plugin::Model* model) {
- // Record usage
- settings::ModuleUsage& mu = settings::moduleUsages[model->plugin->slug][model->slug];
- mu.count++;
- mu.lastTime = system::getUnixTime();
-
- // Create Module and ModuleWidget
- engine::Module* module = model->createModule();
- APP->engine->addModule(module);
-
- ModuleWidget* moduleWidget = model->createModuleWidget(module);
- APP->scene->rack->addModuleAtMouse(moduleWidget);
-
- // Load template preset
- moduleWidget->loadTemplate();
-
- // history::ModuleAdd
- history::ModuleAdd* h = new history::ModuleAdd;
- h->name = "create module";
- // This serializes the module so redoing returns to the current state.
- h->setModule(moduleWidget);
- APP->history->push(h);
-
- // Hide Module Browser
- APP->scene->moduleBrowser->hide();
-
- return moduleWidget;
- }
-
-
- // Widgets
-
-
- struct ModuleBrowser;
-
-
- struct BrowserOverlay : ui::MenuOverlay {
- void step() override {
- // Only step if visible, since there are potentially thousands of descendants that don't need to be stepped.
- if (isVisible())
- MenuOverlay::step();
- }
-
- void onAction(const ActionEvent& e) override {
- hide();
- }
- };
-
-
- struct ModelBox : widget::OpaqueWidget {
- plugin::Model* model;
- ui::Tooltip* tooltip = NULL;
- // Lazily created widgets
- widget::Widget* previewWidget = NULL;
- widget::ZoomWidget* zoomWidget = NULL;
- widget::FramebufferWidget* fb = NULL;
- ModuleWidget* moduleWidget = NULL;
-
- ModelBox() {
- updateZoom();
- }
-
- void setModel(plugin::Model* model) {
- this->model = model;
- }
-
- void updateZoom() {
- float zoom = std::pow(2.f, settings::moduleBrowserZoom);
-
- if (previewWidget) {
- fb->setDirty();
- zoomWidget->setZoom(zoom);
- box.size.x = moduleWidget->box.size.x * zoom;
- }
- else {
- // Approximate size as 12HP before we know the actual size.
- // We need a nonzero size, otherwise too many ModelBoxes will lazily render in the same frame.
- box.size.x = 12 * RACK_GRID_WIDTH * zoom;
- }
- box.size.y = RACK_GRID_HEIGHT * zoom;
- box.size = box.size.ceil();
- }
-
- void createPreview() {
- if (previewWidget)
- return;
-
- previewWidget = new widget::TransparentWidget;
- addChild(previewWidget);
-
- zoomWidget = new widget::ZoomWidget;
- previewWidget->addChild(zoomWidget);
-
- fb = new widget::FramebufferWidget;
- if (math::isNear(APP->window->pixelRatio, 1.0)) {
- // Small details draw poorly at low DPI, so oversample when drawing to the framebuffer
- fb->oversample = 2.0;
- }
- zoomWidget->addChild(fb);
-
- moduleWidget = model->createModuleWidget(NULL);
- fb->addChild(moduleWidget);
-
- updateZoom();
- }
-
- void draw(const DrawArgs& args) override {
- // Lazily create preview when drawn
- createPreview();
-
- // Draw shadow
- nvgBeginPath(args.vg);
- float r = 10; // Blur radius
- float c = 10; // Corner radius
- nvgRect(args.vg, -r, -r, box.size.x + 2 * r, box.size.y + 2 * r);
- NVGcolor shadowColor = nvgRGBAf(0, 0, 0, 0.5);
- NVGcolor transparentColor = nvgRGBAf(0, 0, 0, 0);
- nvgFillPaint(args.vg, nvgBoxGradient(args.vg, 0, 0, box.size.x, box.size.y, c, r, shadowColor, transparentColor));
- nvgFill(args.vg);
-
- // To avoid blinding the user when rack brightness is low, draw framebuffer with the same brightness.
- float b = settings::rackBrightness;
- nvgGlobalTint(args.vg, nvgRGBAf(b, b, b, 1));
-
- OpaqueWidget::draw(args);
- }
-
- void step() override {
- OpaqueWidget::step();
- }
-
- void setTooltip(ui::Tooltip* tooltip) {
- if (this->tooltip) {
- this->tooltip->requestDelete();
- this->tooltip = NULL;
- }
-
- if (tooltip) {
- APP->scene->addChild(tooltip);
- this->tooltip = tooltip;
- }
- }
-
- void onButton(const ButtonEvent& e) override {
- OpaqueWidget::onButton(e);
- if (e.getTarget() != this)
- return;
-
- if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT) {
- ModuleWidget* mw = chooseModel(model);
-
- // Pretend the moduleWidget was clicked so it can be dragged in the RackWidget
- e.consume(mw);
-
- // Set the drag position at the center of the module
- mw->dragOffset() = mw->box.size.div(2);
- // Disable dragging temporarily until the mouse has moved a bit.
- mw->dragEnabled() = false;
- }
- }
-
- ui::Tooltip* createTooltip() {
- std::string text;
- text = model->plugin->brand;
- text += " " + model->name;
- // Description
- if (model->description != "") {
- text += "\n" + model->description;
- }
- // Tags
- text += "\n\nTags: ";
- std::vector<std::string> tags;
- for (int tagId : model->tagIds) {
- tags.push_back(tag::getTag(tagId));
- }
- text += string::join(tags, ", ");
- ui::Tooltip* tooltip = new ui::Tooltip;
- tooltip->text = text;
- return tooltip;
- }
-
- void onEnter(const EnterEvent& e) override {
- setTooltip(createTooltip());
- }
-
- void onLeave(const LeaveEvent& e) override {
- setTooltip(NULL);
- }
-
- void onHide(const HideEvent& e) override {
- // Hide tooltip
- setTooltip(NULL);
- OpaqueWidget::onHide(e);
- }
- };
-
-
- struct BrowserSearchField : ui::TextField {
- ModuleBrowser* browser;
-
- void step() override {
- // Steal focus when step is called
- APP->event->setSelected(this);
- TextField::step();
- }
-
- void onSelectKey(const SelectKeyEvent& e) override;
- void onChange(const ChangeEvent& e) override;
- void onAction(const ActionEvent& e) override;
-
- void onHide(const HideEvent& e) override {
- APP->event->setSelected(NULL);
- ui::TextField::onHide(e);
- }
-
- void onShow(const ShowEvent& e) override {
- selectAll();
- TextField::onShow(e);
- }
- };
-
-
- struct ClearButton : ui::Button {
- ModuleBrowser* browser;
- void onAction(const ActionEvent& e) override;
- };
-
-
- struct BrandItem : ui::MenuItem {
- ModuleBrowser* browser;
- std::string brand;
- void onAction(const ActionEvent& e) override;
- void step() override;
- };
-
-
- struct BrandButton : ui::ChoiceButton {
- ModuleBrowser* browser;
-
- void onAction(const ActionEvent& e) override;
- void step() override;
- };
-
-
- struct TagItem : ui::MenuItem {
- ModuleBrowser* browser;
- int tagId;
- void onAction(const ActionEvent& e) override;
- void step() override;
- };
-
-
- struct TagButton : ui::ChoiceButton {
- ModuleBrowser* browser;
-
- void onAction(const ActionEvent& e) override;
- void step() override;
- };
-
-
- static const std::string sortNames[] = {
- "Last updated",
- "Last used",
- "Most used",
- "Brand",
- "Module name",
- "Random",
- };
-
-
- struct SortItem : ui::MenuItem {
- ModuleBrowser* browser;
- settings::ModuleBrowserSort sort;
- void onAction(const ActionEvent& e) override;
- void step() override {
- rightText = CHECKMARK(settings::moduleBrowserSort == sort);
- MenuItem::step();
- }
- };
-
-
- struct SortButton : ui::ChoiceButton {
- ModuleBrowser* browser;
-
- void onAction(const ActionEvent& e) override {
- ui::Menu* menu = createMenu();
- menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y));
- menu->box.size.x = box.size.x;
-
- for (int sortId = 0; sortId <= settings::MODULE_BROWSER_SORT_RANDOM; sortId++) {
- SortItem* sortItem = new SortItem;
- sortItem->text = sortNames[sortId];
- sortItem->sort = (settings::ModuleBrowserSort) sortId;
- sortItem->browser = browser;
- menu->addChild(sortItem);
- }
- }
-
- void step() override {
- text = "Sort: ";
- text += sortNames[settings::moduleBrowserSort];
- ChoiceButton::step();
- }
- };
-
-
- struct ZoomItem : ui::MenuItem {
- ModuleBrowser* browser;
- float zoom;
- void onAction(const ActionEvent& e) override;
- void step() override {
- rightText = CHECKMARK(settings::moduleBrowserZoom == zoom);
- MenuItem::step();
- }
- };
-
-
- struct ZoomButton : ui::ChoiceButton {
- ModuleBrowser* browser;
-
- void onAction(const ActionEvent& e) override {
- ui::Menu* menu = createMenu();
- menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y));
- menu->box.size.x = box.size.x;
-
- for (float zoom = 0.f; zoom >= -2.f; zoom -= 0.5f) {
- ZoomItem* sortItem = new ZoomItem;
- sortItem->text = string::f("%.0f%%", std::pow(2.f, zoom) * 100.f);
- sortItem->zoom = zoom;
- sortItem->browser = browser;
- menu->addChild(sortItem);
- }
- }
-
- void step() override {
- text = "Zoom: ";
- text += string::f("%.0f%%", std::pow(2.f, settings::moduleBrowserZoom) * 100.f);
- ChoiceButton::step();
- }
- };
-
-
- struct UrlButton : ui::Button {
- std::string url;
- void onAction(const ActionEvent& e) override {
- system::openBrowser(url);
- }
- };
-
-
- struct ModuleBrowser : widget::OpaqueWidget {
- ui::SequentialLayout* headerLayout;
- BrowserSearchField* searchField;
- BrandButton* brandButton;
- TagButton* tagButton;
- ClearButton* clearButton;
- ui::Label* countLabel;
-
- ui::ScrollWidget* modelScroll;
- widget::Widget* modelMargin;
- ui::SequentialLayout* modelContainer;
-
- std::string search;
- std::string brand;
- std::set<int> tagIds = {};
-
- // Caches and temporary state
- std::map<plugin::Model*, float> prefilteredModelScores;
- std::map<plugin::Model*, int> modelOrders;
-
- ModuleBrowser() {
- const float margin = 10;
-
- // Header
- headerLayout = new ui::SequentialLayout;
- headerLayout->box.pos = math::Vec(0, 0);
- headerLayout->box.size.y = 0;
- headerLayout->margin = math::Vec(margin, margin);
- headerLayout->spacing = math::Vec(margin, margin);
- addChild(headerLayout);
-
- searchField = new BrowserSearchField;
- searchField->box.size.x = 150;
- searchField->placeholder = "Search modules";
- searchField->browser = this;
- headerLayout->addChild(searchField);
-
- brandButton = new BrandButton;
- brandButton->box.size.x = 150;
- brandButton->browser = this;
- headerLayout->addChild(brandButton);
-
- tagButton = new TagButton;
- tagButton->box.size.x = 150;
- tagButton->browser = this;
- headerLayout->addChild(tagButton);
-
- clearButton = new ClearButton;
- clearButton->box.size.x = 100;
- clearButton->text = "Reset filters";
- clearButton->browser = this;
- headerLayout->addChild(clearButton);
-
- countLabel = new ui::Label;
- countLabel->box.size.x = 100;
- countLabel->color.a = 0.5;
- headerLayout->addChild(countLabel);
-
-
- SortButton* sortButton = new SortButton;
- sortButton->box.size.x = 150;
- sortButton->browser = this;
- headerLayout->addChild(sortButton);
-
- ZoomButton* zoomButton = new ZoomButton;
- zoomButton->box.size.x = 100;
- zoomButton->browser = this;
- headerLayout->addChild(zoomButton);
-
- UrlButton* libraryButton = new UrlButton;
- libraryButton->box.size.x = 150;
- libraryButton->text = "Browse VCV Library";
- libraryButton->url = "https://library.vcvrack.com/";
- headerLayout->addChild(libraryButton);
-
- // Model container
- modelScroll = new ui::ScrollWidget;
- modelScroll->box.pos.y = BND_WIDGET_HEIGHT;
- addChild(modelScroll);
-
- modelMargin = new widget::Widget;
- modelScroll->container->addChild(modelMargin);
-
- modelContainer = new ui::SequentialLayout;
- modelContainer->margin = math::Vec(margin, 0);
- modelContainer->spacing = math::Vec(margin, margin);
- modelMargin->addChild(modelContainer);
-
- resetModelBoxes();
- clear();
- }
-
- void resetModelBoxes() {
- modelContainer->clearChildren();
- modelOrders.clear();
- // Iterate plugins
- // for (int i = 0; i < 100; i++)
- for (plugin::Plugin* plugin : plugin::plugins) {
- // Get module slugs from module whitelist
- const auto& pluginIt = settings::moduleWhitelist.find(plugin->slug);
- // Iterate models in plugin
- int modelIndex = 0;
- for (plugin::Model* model : plugin->models) {
- if (model->hidden)
- continue;
-
- // Don't show module if plugin whitelist exists but the module is not in it.
- if (pluginIt != settings::moduleWhitelist.end()) {
- auto moduleIt = std::find(pluginIt->second.begin(), pluginIt->second.end(), model->slug);
- if (moduleIt == pluginIt->second.end())
- continue;
- }
-
- // Create ModelBox
- ModelBox* modelBox = new ModelBox;
- modelBox->setModel(model);
- modelContainer->addChild(modelBox);
-
- modelOrders[model] = modelIndex;
- modelIndex++;
- }
- }
- }
-
- void updateZoom() {
- modelScroll->offset = math::Vec();
-
- for (Widget* w : modelContainer->children) {
- ModelBox* mb = reinterpret_cast<ModelBox*>(w);
- assert(mb);
- mb->updateZoom();
- }
- }
-
- void step() override {
- box = parent->box.zeroPos().grow(math::Vec(-40, -40));
-
- headerLayout->box.size.x = box.size.x;
-
- const float margin = 10;
- modelScroll->box.pos = headerLayout->box.getBottomLeft();
- modelScroll->box.size = box.size.minus(modelScroll->box.pos);
- modelMargin->box.size.x = modelScroll->box.size.x;
- modelMargin->box.size.y = modelContainer->box.size.y + margin;
- modelContainer->box.size.x = modelMargin->box.size.x - margin;
-
- OpaqueWidget::step();
- }
-
- void draw(const DrawArgs& args) override {
- bndMenuBackground(args.vg, 0.0, 0.0, box.size.x, box.size.y, 0);
- Widget::draw(args);
- }
-
- bool isModelVisible(plugin::Model* model, const std::string& brand, std::set<int> tagIds) {
- // Filter brand
- if (!brand.empty()) {
- if (model->plugin->brand != brand)
- return false;
- }
-
- // Filter tag
- for (int tagId : tagIds) {
- auto it = std::find(model->tagIds.begin(), model->tagIds.end(), tagId);
- if (it == model->tagIds.end())
- return false;
- }
-
- return true;
- };
-
- // Determines if there is at least 1 visible Model with a given brand and tag
- bool hasVisibleModel(const std::string& brand, std::set<int> tagIds) {
- for (const auto& pair : prefilteredModelScores) {
- plugin::Model* model = pair.first;
- if (isModelVisible(model, brand, tagIds))
- return true;
- }
- return false;
- };
-
- template <typename F>
- void sortModels(F f) {
- modelContainer->children.sort([&](Widget* w1, Widget* w2) {
- ModelBox* m1 = reinterpret_cast<ModelBox*>(w1);
- ModelBox* m2 = reinterpret_cast<ModelBox*>(w2);
- return f(m1) < f(m2);
- });
- }
-
- void refresh() {
- // Reset scroll position
- modelScroll->offset = math::Vec();
-
- prefilteredModelScores.clear();
-
- // Filter ModelBoxes by brand and tag
- for (Widget* w : modelContainer->children) {
- ModelBox* m = reinterpret_cast<ModelBox*>(w);
- m->setVisible(isModelVisible(m->model, brand, tagIds));
- }
-
- // Filter and sort by search results
- if (search.empty()) {
- // Add all models to prefilteredModelScores with scores of 1
- for (Widget* w : modelContainer->children) {
- ModelBox* m = reinterpret_cast<ModelBox*>(w);
- prefilteredModelScores[m->model] = 1.f;
- }
-
- // Sort ModelBoxes
- if (settings::moduleBrowserSort == settings::MODULE_BROWSER_SORT_UPDATED) {
- sortModels([this](ModelBox* m) {
- plugin::Plugin* p = m->model->plugin;
- int modelOrder = get(modelOrders, m->model, 0);
- return std::make_tuple(-p->modifiedTimestamp, p->brand, p->name, modelOrder);
- });
- }
- else if (settings::moduleBrowserSort == settings::MODULE_BROWSER_SORT_LAST_USED) {
- sortModels([this](ModelBox* m) {
- plugin::Plugin* p = m->model->plugin;
- const settings::ModuleUsage* mu = settings::getModuleUsage(p->slug, m->model->slug);
- double lastTime = mu ? mu->lastTime : -INFINITY;
- int modelOrder = get(modelOrders, m->model, 0);
- return std::make_tuple(-lastTime, -p->modifiedTimestamp, p->brand, p->name, modelOrder);
- });
- }
- else if (settings::moduleBrowserSort == settings::MODULE_BROWSER_SORT_MOST_USED) {
- sortModels([this](ModelBox* m) {
- plugin::Plugin* p = m->model->plugin;
- const settings::ModuleUsage* mu = settings::getModuleUsage(p->slug, m->model->slug);
- int count = mu ? mu->count : 0;
- double lastTime = mu ? mu->lastTime : -INFINITY;
- int modelOrder = get(modelOrders, m->model, 0);
- return std::make_tuple(-count, -lastTime, -p->modifiedTimestamp, p->brand, p->name, modelOrder);
- });
- }
- else if (settings::moduleBrowserSort == settings::MODULE_BROWSER_SORT_BRAND) {
- sortModels([this](ModelBox* m) {
- plugin::Plugin* p = m->model->plugin;
- int modelOrder = get(modelOrders, m->model, 0);
- return std::make_tuple(p->brand, p->name, modelOrder);
- });
- }
- else if (settings::moduleBrowserSort == settings::MODULE_BROWSER_SORT_NAME) {
- sortModels([](ModelBox* m) {
- plugin::Plugin* p = m->model->plugin;
- return std::make_tuple(m->model->name, p->brand);
- });
- }
- else if (settings::moduleBrowserSort == settings::MODULE_BROWSER_SORT_RANDOM) {
- std::map<ModelBox*, uint64_t> randomOrder;
- for (Widget* w : modelContainer->children) {
- ModelBox* m = reinterpret_cast<ModelBox*>(w);
- randomOrder[m] = random::u64();
- }
- sortModels([&](ModelBox* m) {
- return get(randomOrder, m, 0);
- });
- }
- }
- else {
- // Lazily initialize search database
- fuzzySearchInit();
- // Score results against search query
- auto results = modelDb.search(search);
- // DEBUG("=============");
- for (auto& result : results) {
- prefilteredModelScores[result.key] = result.score;
- // DEBUG("%s %s\t\t%f", result.key->plugin->slug.c_str(), result.key->slug.c_str(), result.score);
- }
- // Sort by score
- sortModels([&](ModelBox* m) {
- return -get(prefilteredModelScores, m->model, 0.f);
- });
- // Filter by whether the score is above the threshold
- for (Widget* w : modelContainer->children) {
- ModelBox* m = reinterpret_cast<ModelBox*>(w);
- assert(m);
- if (m->isVisible()) {
- if (prefilteredModelScores.find(m->model) == prefilteredModelScores.end())
- m->hide();
- }
- }
- }
-
- // Count visible modules
- int count = 0;
- for (Widget* w : modelContainer->children) {
- if (w->isVisible())
- count++;
- }
- countLabel->text = string::f("%d modules", count);
- }
-
- void clear() {
- search = "";
- searchField->setText("");
- brand = "";
- tagIds = {};
- refresh();
- }
-
- void onShow(const ShowEvent& e) override {
- refresh();
- OpaqueWidget::onShow(e);
- }
- };
-
-
- // Implementations to resolve dependencies
-
-
- inline void ClearButton::onAction(const ActionEvent& e) {
- browser->clear();
- }
-
- inline void BrowserSearchField::onSelectKey(const SelectKeyEvent& e) {
- if (e.action == GLFW_PRESS || e.action == GLFW_REPEAT) {
- if (e.key == GLFW_KEY_ESCAPE) {
- BrowserOverlay* overlay = browser->getAncestorOfType<BrowserOverlay>();
- overlay->hide();
- e.consume(this);
- }
- // Backspace when the field is empty to clear filters.
- if (e.key == GLFW_KEY_BACKSPACE) {
- if (text == "") {
- browser->clear();
- e.consume(this);
- }
- }
- }
-
- if (!e.getTarget())
- ui::TextField::onSelectKey(e);
- }
-
- inline void BrowserSearchField::onChange(const ChangeEvent& e) {
- browser->search = string::trim(text);
- browser->refresh();
- }
-
- inline void BrowserSearchField::onAction(const ActionEvent& e) {
- // Get first ModelBox
- ModelBox* mb = NULL;
- for (Widget* w : browser->modelContainer->children) {
- if (w->isVisible()) {
- mb = reinterpret_cast<ModelBox*>(w);
- break;
- }
- }
-
- if (mb) {
- chooseModel(mb->model);
- }
- }
-
- inline void BrandItem::onAction(const ActionEvent& e) {
- if (browser->brand == brand)
- browser->brand = "";
- else
- browser->brand = brand;
- browser->refresh();
- }
-
- inline void BrandItem::step() {
- rightText = CHECKMARK(browser->brand == brand);
- MenuItem::step();
- }
-
- inline void BrandButton::onAction(const ActionEvent& e) {
- ui::Menu* menu = createMenu();
- menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y));
- menu->box.size.x = box.size.x;
-
- BrandItem* noneItem = new BrandItem;
- noneItem->text = "All brands";
- noneItem->brand = "";
- noneItem->browser = browser;
- menu->addChild(noneItem);
-
- menu->addChild(new ui::MenuSeparator);
-
- // Collect brands from all plugins
- std::set<std::string, string::CaseInsensitiveCompare> brands;
- for (plugin::Plugin* plugin : plugin::plugins) {
- brands.insert(plugin->brand);
- }
-
- for (const std::string& brand : brands) {
- BrandItem* brandItem = new BrandItem;
- brandItem->text = brand;
- brandItem->brand = brand;
- brandItem->browser = browser;
- brandItem->disabled = !browser->hasVisibleModel(brand, browser->tagIds);
- menu->addChild(brandItem);
- }
- }
-
- inline void BrandButton::step() {
- text = "Brand";
- if (!browser->brand.empty()) {
- text += ": ";
- text += browser->brand;
- }
- text = string::ellipsize(text, 21);
- ChoiceButton::step();
- }
-
- inline void TagItem::onAction(const ActionEvent& e) {
- auto it = browser->tagIds.find(tagId);
- bool isSelected = (it != browser->tagIds.end());
-
- if (tagId >= 0) {
- // Actual tag
- int mods = APP->window->getMods();
- if ((mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
- // Multi select
- if (isSelected)
- browser->tagIds.erase(tagId);
- else
- browser->tagIds.insert(tagId);
- e.unconsume();
- }
- else {
- // Single select
- if (isSelected)
- browser->tagIds = {};
- else {
- browser->tagIds = {tagId};
- }
- }
- }
- else {
- // All tags
- browser->tagIds = {};
- }
-
- browser->refresh();
- }
-
- inline void TagItem::step() {
- // TODO Disable tags with no modules
- if (tagId >= 0) {
- auto it = browser->tagIds.find(tagId);
- bool isSelected = (it != browser->tagIds.end());
- rightText = CHECKMARK(isSelected);
- }
- else {
- rightText = CHECKMARK(browser->tagIds.empty());
- }
- MenuItem::step();
- }
-
- inline void TagButton::onAction(const ActionEvent& e) {
- ui::Menu* menu = createMenu();
- menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y));
- menu->box.size.x = box.size.x;
-
- TagItem* noneItem = new TagItem;
- noneItem->text = "All tags";
- noneItem->tagId = -1;
- noneItem->browser = browser;
- menu->addChild(noneItem);
-
- menu->addChild(createMenuLabel(RACK_MOD_CTRL_NAME "+click to select multiple"));
- menu->addChild(new ui::MenuSeparator);
-
- for (int tagId = 0; tagId < (int) tag::tagAliases.size(); tagId++) {
- TagItem* tagItem = new TagItem;
- tagItem->text = tag::getTag(tagId);
- tagItem->tagId = tagId;
- tagItem->browser = browser;
- tagItem->disabled = !browser->hasVisibleModel(browser->brand, {tagId});
- menu->addChild(tagItem);
- }
- }
-
- inline void TagButton::step() {
- text = "Tags";
- if (!browser->tagIds.empty()) {
- text += ": ";
- bool firstTag = true;
- for (int tagId : browser->tagIds) {
- if (!firstTag)
- text += ", ";
- text += tag::getTag(tagId);
- firstTag = false;
- }
- }
- text = string::ellipsize(text, 21);
- ChoiceButton::step();
- }
-
- inline void SortItem::onAction(const ActionEvent& e) {
- settings::moduleBrowserSort = sort;
- browser->refresh();
- }
-
- inline void ZoomItem::onAction(const ActionEvent& e) {
- if (zoom != settings::moduleBrowserZoom) {
- settings::moduleBrowserZoom = zoom;
- browser->updateZoom();
- }
- }
-
-
- } // namespace moduleBrowser
-
-
- widget::Widget* moduleBrowserCreate() {
- moduleBrowser::BrowserOverlay* overlay = new moduleBrowser::BrowserOverlay;
- overlay->bgColor = nvgRGBAf(0, 0, 0, 0.33);
-
- moduleBrowser::ModuleBrowser* browser = new moduleBrowser::ModuleBrowser;
- overlay->addChild(browser);
-
- return overlay;
- }
-
-
- } // namespace app
- } // namespace rack
|