diff --git a/src/app/ModuleBrowser.cpp b/src/app/ModuleBrowser.cpp index deeefd5b..ae7f5d02 100644 --- a/src/app/ModuleBrowser.cpp +++ b/src/app/ModuleBrowser.cpp @@ -232,6 +232,16 @@ struct ModelBox : widget::OpaqueWidget { }; +struct AuthorItem : ui::MenuItem { + void onAction(const widget::ActionEvent &e) override; +}; + + +struct TagItem : ui::MenuItem { + void onAction(const widget::ActionEvent &e) override; +}; + + struct BrowserSearchField : ui::TextField { void step() override { // Steal focus when step is called @@ -268,8 +278,10 @@ struct BrowserSearchField : ui::TextField { struct BrowserSidebar : widget::Widget { BrowserSearchField *searchField; - ui::List *pluginList; - ui::ScrollWidget *pluginScroll; + ui::Label *authorLabel; + ui::List *authorList; + ui::ScrollWidget *authorScroll; + ui::Label *tagLabel; ui::List *tagList; ui::ScrollWidget *tagScroll; @@ -277,24 +289,34 @@ struct BrowserSidebar : widget::Widget { searchField = new BrowserSearchField; addChild(searchField); + authorLabel = new ui::Label; + authorLabel->color = nvgRGB(0x80, 0x80, 0x80); + authorLabel->text = "Authors"; + addChild(authorLabel); + // Plugin list - pluginScroll = new ui::ScrollWidget; - addChild(pluginScroll); + authorScroll = new ui::ScrollWidget; + addChild(authorScroll); - pluginList = new ui::List; - pluginScroll->container->addChild(pluginList); + authorList = new ui::List; + authorScroll->container->addChild(authorList); - std::set pluginNames; + std::set authorNames; for (plugin::Plugin *plugin : plugin::plugins) { - pluginNames.insert(plugin->name); + authorNames.insert(plugin->author); } - for (const std::string &pluginName : pluginNames) { - ui::MenuItem *item = new ui::MenuItem; - item->text = pluginName; - pluginList->addChild(item); + for (const std::string &authorName : authorNames) { + AuthorItem *item = new AuthorItem; + item->text = authorName; + authorList->addChild(item); } + tagLabel = new ui::Label; + tagLabel->color = nvgRGB(0x80, 0x80, 0x80); + tagLabel->text = "Tags"; + addChild(tagLabel); + // Tag list tagScroll = new ui::ScrollWidget; addChild(tagScroll); @@ -303,22 +325,32 @@ struct BrowserSidebar : widget::Widget { tagScroll->container->addChild(tagList); for (const std::string &tag : plugin::allowedTags) { - ui::MenuItem *item = new ui::MenuItem; + TagItem *item = new TagItem; item->text = tag; tagList->addChild(item); } } void step() override { + float listHeight = (box.size.y - searchField->box.size.y) / 2 - authorLabel->box.size.y; + listHeight = std::floor(listHeight); + searchField->box.size.x = box.size.x; - pluginScroll->box.pos = searchField->box.getBottomLeft(); - pluginScroll->box.size.y = (box.size.y - searchField->box.size.y) / 2; - pluginScroll->box.size.x = box.size.x; - pluginList->box.size.x = pluginScroll->box.size.x; - tagScroll->box.pos = pluginScroll->box.getBottomLeft().floor(); - tagScroll->box.size.y = (box.size.y - searchField->box.size.y) / 2; + + authorLabel->box.pos = searchField->box.getBottomLeft(); + authorLabel->box.size.x = box.size.x; + authorScroll->box.pos = authorLabel->box.getBottomLeft(); + authorScroll->box.size.y = listHeight; + authorScroll->box.size.x = box.size.x; + authorList->box.size.x = authorScroll->box.size.x; + + tagLabel->box.pos = authorScroll->box.getBottomLeft(); + tagLabel->box.size.x = box.size.x; + tagScroll->box.pos = tagLabel->box.getBottomLeft(); + tagScroll->box.size.y = listHeight; tagScroll->box.size.x = box.size.x; tagList->box.size.x = tagScroll->box.size.x; + Widget::step(); } }; @@ -330,6 +362,10 @@ struct ModuleBrowser : widget::OpaqueWidget { ui::MarginLayout *modelMargin; ui::SequentialLayout *modelContainer; + std::string search; + std::string author; + std::string tag; + ModuleBrowser() { sidebar = new BrowserSidebar; sidebar->box.size.x = 200; @@ -346,6 +382,7 @@ struct ModuleBrowser : widget::OpaqueWidget { modelContainer->spacing = math::Vec(10, 10); modelMargin->addChild(modelContainer); + // Add ModelBoxes for each Model for (plugin::Plugin *plugin : plugin::plugins) { for (plugin::Model *model : plugin->models) { ModelBox *moduleBox = new ModelBox; @@ -354,11 +391,11 @@ struct ModuleBrowser : widget::OpaqueWidget { } } - setSearch(""); + refreshModels(); } void step() override { - box = parent->box.zeroPos().grow(math::Vec(-50, -50)); + box = parent->box.zeroPos().grow(math::Vec(-70, -70)); sidebar->box.size.y = box.size.y; @@ -376,16 +413,16 @@ struct ModuleBrowser : widget::OpaqueWidget { Widget::draw(args); } - void setSearch(const std::string &search) { - std::string searchTrimmed = string::trim(search); + void refreshModels() { // Reset scroll position modelScroll->offset = math::Vec(); - if (searchTrimmed.empty()) { + if (search.empty()) { + // Make all ModelBoxes visible for (Widget *w : modelContainer->children) { w->visible = true; } - // If no search query, sort by plugin name and module name + // Sort by plugin name and then module name modelContainer->children.sort([&](Widget *w1, Widget *w2) { ModelBox *m1 = dynamic_cast(w1); ModelBox *m2 = dynamic_cast(w2); @@ -396,11 +433,11 @@ struct ModuleBrowser : widget::OpaqueWidget { } else { std::map scores; - // Compute scores and set visibility + // Compute scores and filter visibility for (Widget *w : modelContainer->children) { ModelBox *m = dynamic_cast(w); assert(m); - float score = modelScore(m->model, searchTrimmed); + float score = modelScore(m->model, search); scores[m] = score; m->visible = (score > 0); } @@ -409,6 +446,37 @@ struct ModuleBrowser : widget::OpaqueWidget { return scores[w1] > scores[w2]; }); } + + // Filter authors + if (!author.empty()) { + for (Widget *w : modelContainer->children) { + if (!w->visible) + continue; + ModelBox *m = dynamic_cast(w); + assert(m); + if (m->model->plugin->author != author) + m->visible = false; + } + } + + // Filter tags + if (!tag.empty()) { + for (Widget *w : modelContainer->children) { + if (!w->visible) + continue; + ModelBox *m = dynamic_cast(w); + assert(m); + bool found = false; + for (const std::string &tag : m->model->tags) { + if (tag == this->tag) { + found = true; + break; + } + } + if (!found) + m->visible = false; + } + } } }; @@ -442,12 +510,33 @@ inline void ModelBox::onButton(const widget::ButtonEvent &e) { } } -inline void BrowserSearchField::onChange(const widget::ChangeEvent &e) { + +inline void AuthorItem::onAction(const widget::ActionEvent &e) { ModuleBrowser *browser = getAncestorOfType(); - browser->setSearch(text); + if (browser->author == text) + browser->author = ""; + else + browser->author = text; + browser->refreshModels(); } +inline void TagItem::onAction(const widget::ActionEvent &e) { + ModuleBrowser *browser = getAncestorOfType(); + if (browser->tag == text) + browser->tag = ""; + else + browser->tag = text; + browser->refreshModels(); +} + + +inline void BrowserSearchField::onChange(const widget::ChangeEvent &e) { + ModuleBrowser *browser = getAncestorOfType(); + browser->search = string::trim(text); + browser->refreshModels(); +} + // Global functions