diff --git a/include/settings.hpp b/include/settings.hpp index b2002d5f..f76e5988 100644 --- a/include/settings.hpp +++ b/include/settings.hpp @@ -66,8 +66,8 @@ extern bool showTipsOnLaunch; extern int tipIndex; enum ModuleBrowserSort { MODULE_BROWSER_SORT_UPDATED, - MODULE_BROWSER_SORT_MOST_USED, MODULE_BROWSER_SORT_LAST_USED, + MODULE_BROWSER_SORT_MOST_USED, MODULE_BROWSER_SORT_BRAND, MODULE_BROWSER_SORT_NAME, MODULE_BROWSER_SORT_RANDOM, @@ -81,7 +81,9 @@ struct ModuleUsage { int count = 0; double lastTime = NAN; }; +// pluginSlug, moduleSlug -> ModuleUsage extern std::map> moduleUsages; +ModuleUsage* getModuleUsage(const std::string& pluginSlug, const std::string& moduleSlug); json_t* toJson(); void fromJson(json_t* rootJ); diff --git a/src/app/ModuleBrowser.cpp b/src/app/ModuleBrowser.cpp index 21353c96..614aaf84 100644 --- a/src/app/ModuleBrowser.cpp +++ b/src/app/ModuleBrowser.cpp @@ -381,8 +381,8 @@ struct TagButton : ui::ChoiceButton { static const std::string sortNames[] = { "Last updated", - "Most used", "Last used", + "Most used", "Brand", "Module name", "Random", @@ -446,7 +446,7 @@ struct ZoomButton : ui::ChoiceButton { for (float zoom = 0.f; zoom >= -2.f; zoom -= 0.5f) { ZoomItem* sortItem = new ZoomItem; - sortItem->text = string::f("%.3g%%", std::pow(2.f, zoom) * 100.f); + sortItem->text = string::f("%.2g%%", std::pow(2.f, zoom) * 100.f); sortItem->zoom = zoom; sortItem->browser = browser; menu->addChild(sortItem); @@ -455,7 +455,7 @@ struct ZoomButton : ui::ChoiceButton { void step() override { text = "Zoom: "; - text += string::f("%.3g%%", std::pow(2.f, settings::moduleBrowserZoom) * 100.f); + text += string::f("%.2g%%", std::pow(2.f, settings::moduleBrowserZoom) * 100.f); ChoiceButton::step(); } }; @@ -487,6 +487,8 @@ struct ModuleBrowser : widget::OpaqueWidget { std::string brand; std::set tagIds = {}; + std::map prefilteredModelScores; + ModuleBrowser() { float margin = 10; @@ -560,7 +562,7 @@ struct ModuleBrowser : widget::OpaqueWidget { void resetModelBoxes() { modelContainer->clearChildren(); // Iterate plugins - for (int i = 0; i < 100; i++) + // 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); @@ -582,6 +584,8 @@ struct ModuleBrowser : widget::OpaqueWidget { } void updateZoom() { + modelScroll->offset = math::Vec(); + for (Widget* w : modelContainer->children) { ModelBox* mb = reinterpret_cast(w); assert(mb); @@ -607,54 +611,106 @@ struct ModuleBrowser : widget::OpaqueWidget { Widget::draw(args); } + bool isModelVisible(plugin::Model* model, const std::string& brand, std::set tagIds) { + // Filter brand + if (!brand.empty()) { + if (model->plugin->brand != brand) + return false; + } + + // Filter tag + for (int tagId : tagIds) { + auto it = std::find(model->tags.begin(), model->tags.end(), tagId); + if (it == model->tags.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 tagIds) { + for (const auto& pair : prefilteredModelScores) { + plugin::Model* model = pair.first; + if (isModelVisible(model, brand, tagIds)) + return true; + } + return false; + }; + + template + void sortModels(F f) { + modelContainer->children.sort([&](Widget* w1, Widget* w2) { + ModelBox* m1 = reinterpret_cast(w1); + ModelBox* m2 = reinterpret_cast(w2); + return f(m1) < f(m2); + }); + } + void refresh() { // Reset scroll position modelScroll->offset = math::Vec(); - auto isModelVisible = [&](plugin::Model* model, const std::string& brand, std::set tagIds) -> bool { - // Filter brand - if (brand != "") { - if (model->plugin->brand != brand) - return false; - } - - // Filter tag - for (int tagId : tagIds) { - auto it = std::find(model->tags.begin(), model->tags.end(), tagId); - if (it == model->tags.end()) - return false; - } - - return true; - }; + prefilteredModelScores.clear(); // Filter ModelBoxes by brand and tag for (Widget* w : modelContainer->children) { - ModelBox* m = dynamic_cast(w); - assert(m); - m->visible = isModelVisible(m->model, brand, tagIds); + ModelBox* m = reinterpret_cast(w); + m->setVisible(isModelVisible(m->model, brand, tagIds)); } - std::map prefilteredModelScores; - // 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 = dynamic_cast(w); - assert(m); + ModelBox* m = reinterpret_cast(w); prefilteredModelScores[m->model] = 1.f; } // Sort ModelBoxes - modelContainer->children.sort([&](Widget * w1, Widget * w2) { - ModelBox* m1 = dynamic_cast(w1); - ModelBox* m2 = dynamic_cast(w2); - // Sort by (modifiedTimestamp descending, plugin brand) - auto t1 = std::make_tuple(-m1->model->plugin->modifiedTimestamp, m1->model->plugin->brand); - auto t2 = std::make_tuple(-m2->model->plugin->modifiedTimestamp, m2->model->plugin->brand); - return t1 < t2; - }); + if (settings::moduleBrowserSort == settings::MODULE_BROWSER_SORT_UPDATED) { + sortModels([](ModelBox* m) { + plugin::Plugin* p = m->model->plugin; + return std::make_tuple(-p->modifiedTimestamp, p->brand, m->model->name); + }); + } + else if (settings::moduleBrowserSort == settings::MODULE_BROWSER_SORT_LAST_USED) { + sortModels([](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; + return std::make_tuple(-lastTime, -p->modifiedTimestamp, p->brand); + }); + } + else if (settings::moduleBrowserSort == settings::MODULE_BROWSER_SORT_MOST_USED) { + sortModels([](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; + return std::make_tuple(-count, -lastTime, -p->modifiedTimestamp, p->brand); + }); + } + else if (settings::moduleBrowserSort == settings::MODULE_BROWSER_SORT_BRAND) { + sortModels([](ModelBox* m) { + return std::make_tuple(m->model->plugin->brand, m->model->name); + }); + } + else if (settings::moduleBrowserSort == settings::MODULE_BROWSER_SORT_NAME) { + sortModels([](ModelBox* m) { + return std::make_tuple(m->model->name, m->model->plugin->brand); + }); + } + else if (settings::moduleBrowserSort == settings::MODULE_BROWSER_SORT_RANDOM) { + std::map randomOrder; + for (Widget* w : modelContainer->children) { + ModelBox* m = reinterpret_cast(w); + randomOrder[m] = random::u64(); + } + sortModels([&](ModelBox* m) { + return get(randomOrder, m, 0); + }); + } } else { // Lazily initialize search database @@ -667,15 +723,12 @@ struct ModuleBrowser : widget::OpaqueWidget { // DEBUG("%s %s\t\t%f", result._key->plugin->slug.c_str(), result._key->slug.c_str(), result._score); } // Sort by score - modelContainer->children.sort([&](Widget *w1, Widget *w2) { - ModelBox* m1 = dynamic_cast(w1); - ModelBox* m2 = dynamic_cast(w2); - // If score was not computed, the ModelBox will not visible so the order doesn't matter. - return get(prefilteredModelScores, m1->model, 0.f) > get(prefilteredModelScores, m2->model, 0.f); + 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 = dynamic_cast(w); + ModelBox* m = reinterpret_cast(w); assert(m); if (m->visible) { if (prefilteredModelScores.find(m->model) == prefilteredModelScores.end()) @@ -683,44 +736,6 @@ struct ModuleBrowser : widget::OpaqueWidget { } } } - - // Determines if there is at least 1 visible Model with a given brand and tag - auto hasVisibleModel = [&](const std::string& brand, std::set tagIds) -> bool { - for (auto& pair : prefilteredModelScores) { - plugin::Model* model = pair.first; - if (isModelVisible(model, brand, tagIds)) - return true; - } - return false; - }; - - // // Enable brand and tag items that are available in visible ModelBoxes - // int brandsLen = 0; - // for (Widget* w : sidebar->brandList->children) { - // BrandItem* item = dynamic_cast(w); - // assert(item); - // item->disabled = !hasVisibleModel(item->text, tagIds); - // if (!item->disabled) - // brandsLen++; - // } - // sidebar->brandLabel->text = string::f("Brands (%d)", brandsLen); - - // int tagsLen = 0; - // for (Widget* w : sidebar->tagList->children) { - // TagItem* item = dynamic_cast(w); - // assert(item); - // item->disabled = !hasVisibleModel(brand, {item->tagId}); - // if (!item->disabled) - // tagsLen++; - // } - // sidebar->tagLabel->text = string::f("Tags (%d)", tagsLen); - - // // Count models - // int modelsLen = 0; - // for (Widget* w : modelContainer->children) { - // if (w->visible) - // modelsLen++; - // } } void clear() { @@ -730,6 +745,11 @@ struct ModuleBrowser : widget::OpaqueWidget { tagIds = {}; refresh(); } + + void onShow(const event::Show& e) override { + refresh(); + OpaqueWidget::onShow(e); + } }; @@ -770,7 +790,7 @@ inline void BrowserSearchField::onAction(const event::Action& e) { ModelBox* mb = NULL; for (Widget* w : browser->modelContainer->children) { if (w->isVisible()) { - mb = dynamic_cast(w); + mb = reinterpret_cast(w); break; } } diff --git a/src/settings.cpp b/src/settings.cpp index 1765497b..ff18861b 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -58,6 +58,17 @@ std::map> moduleWhitelist = {}; std::map> moduleUsages = {}; +ModuleUsage* getModuleUsage(const std::string& pluginSlug, const std::string& moduleSlug) { + auto it1 = moduleUsages.find(pluginSlug); + if (it1 == moduleUsages.end()) + return NULL; + auto it2 = it1->second.find(moduleSlug); + if (it2 == it1->second.end()) + return NULL; + return &it2->second; +} + + json_t* toJson() { json_t* rootJ = json_object();