diff --git a/include/settings.hpp b/include/settings.hpp index 80ef60a8..0183b1a3 100644 --- a/include/settings.hpp +++ b/include/settings.hpp @@ -1,9 +1,10 @@ #pragma once #include "common.hpp" #include "math.hpp" -#include "plugin/Model.hpp" #include "color.hpp" -#include +#include +#include +#include #include @@ -38,7 +39,8 @@ extern float frameRateLimit; extern bool frameRateSync; extern bool skipLoadOnLaunch; extern std::string patchPath; -extern std::set favoriteModels; +// (plugin, model) -> score +extern std::map, float> favoriteScores; extern std::vector cableColors; json_t *toJson(); diff --git a/src/app/ModuleBrowser.cpp b/src/app/ModuleBrowser.cpp index 62e1ea4d..1bff343a 100644 --- a/src/app/ModuleBrowser.cpp +++ b/src/app/ModuleBrowser.cpp @@ -48,14 +48,7 @@ static float modelScore(plugin::Model *model, const std::string &search) { return score; } -static bool isModelVisible(plugin::Model *model, bool favorites, const std::string &search, const std::string &brand, const std::string &tag) { - // Filter favorites - if (favorites) { - auto it = settings::favoriteModels.find(model); - if (it == settings::favoriteModels.end()) - return false; - } - +static bool isModelVisible(plugin::Model *model, const std::string &search, const std::string &brand, const std::string &tag) { // Filter search query if (search != "") { float score = modelScore(model, search); @@ -85,6 +78,25 @@ static bool isModelVisible(plugin::Model *model, bool favorites, const std::stri return true; } +static void stepFavoriteScore(const std::string &plugin, const std::string &model) { + // Decay all scores + const float decayLambda = 0.1; + for (auto &it : settings::favoriteScores) { + it.second *= 1 - decayLambda; + } + // Increment favorite score by 1 + settings::favoriteScores[std::make_tuple(plugin, model)] += 1; +} + + +template +V get_default(const std::map &m, const K &key, const V &def) { + auto it = m.find(key); + if (it == m.end()) + return def; + return it->second; +} + struct BrowserOverlay : widget::OpaqueWidget { void step() override { @@ -107,43 +119,12 @@ struct BrowserOverlay : widget::OpaqueWidget { }; -struct ModelFavoriteQuantity : Quantity { - plugin::Model *model; - std::string getLabel() override {return "★";} - void setValue(float value) override { - if (value) { - settings::favoriteModels.insert(model); - } - else { - auto it = settings::favoriteModels.find(model); - if (it != settings::favoriteModels.end()) - settings::favoriteModels.erase(it); - } - } - float getValue() override { - auto it = settings::favoriteModels.find(model); - return (it != settings::favoriteModels.end()); - } -}; - - -struct ModelFavoriteButton : ui::RadioButton { - ModelFavoriteButton() { - quantity = new ModelFavoriteQuantity; - } - ~ModelFavoriteButton() { - delete quantity; - } -}; - - static const float MODEL_BOX_ZOOM = 0.5f; struct ModelBox : widget::OpaqueWidget { plugin::Model *model; widget::Widget *previewWidget; - ModelFavoriteButton *favoriteButton; ui::Tooltip *tooltip = NULL; /** Lazily created */ widget::FramebufferWidget *previewFb = NULL; @@ -164,13 +145,6 @@ struct ModelBox : widget::OpaqueWidget { previewWidget = new widget::TransparentWidget; previewWidget->box.size.y = std::ceil(RACK_GRID_HEIGHT * MODEL_BOX_ZOOM); addChild(previewWidget); - - // Favorite button - favoriteButton = new ModelFavoriteButton; - dynamic_cast(favoriteButton->quantity)->model = model; - favoriteButton->box.pos.y = box.size.y; - box.size.y += favoriteButton->box.size.y; - addChild(favoriteButton); } void createPreview() { @@ -192,7 +166,6 @@ struct ModelBox : widget::OpaqueWidget { zoomWidget->box.size.y = RACK_GRID_HEIGHT * MODEL_BOX_ZOOM; previewWidget->box.size.x = std::ceil(zoomWidget->box.size.x); - favoriteButton->box.size.x = previewWidget->box.size.x; box.size.x = previewWidget->box.size.x; } @@ -317,31 +290,9 @@ struct ClearButton : ui::Button { }; -struct ShowFavoritesQuantity : Quantity { - widget::Widget *widget; - std::string getLabel() override { - int favoritesLen = settings::favoriteModels.size(); - return string::f("Only show favorites (%d)", favoritesLen); - } - void setValue(float value) override; - float getValue() override; -}; - - -struct ShowFavoritesButton : ui::RadioButton { - ShowFavoritesButton() { - quantity = new ShowFavoritesQuantity; - } - ~ShowFavoritesButton() { - delete quantity; - } -}; - - struct BrowserSidebar : widget::Widget { BrowserSearchField *searchField; ClearButton *clearButton; - ShowFavoritesButton *favoriteButton; ui::Label *brandLabel; ui::List *brandList; ui::ScrollWidget *brandScroll; @@ -357,10 +308,6 @@ struct BrowserSidebar : widget::Widget { clearButton->text = "Reset filters"; addChild(clearButton); - favoriteButton = new ShowFavoritesButton; - dynamic_cast(favoriteButton->quantity)->widget = favoriteButton; - addChild(favoriteButton); - brandLabel = new ui::Label; // brandLabel->fontSize = 16; brandLabel->color = nvgRGB(0x80, 0x80, 0x80); @@ -409,13 +356,11 @@ struct BrowserSidebar : widget::Widget { searchField->box.size.x = box.size.x; clearButton->box.pos = searchField->box.getBottomLeft(); clearButton->box.size.x = box.size.x; - favoriteButton->box.pos = clearButton->box.getBottomLeft(); - favoriteButton->box.size.x = box.size.x; - float listHeight = (box.size.y - favoriteButton->box.getBottom()) / 2; + float listHeight = (box.size.y - clearButton->box.getBottom()) / 2; listHeight = std::floor(listHeight); - brandLabel->box.pos = favoriteButton->box.getBottomLeft(); + brandLabel->box.pos = clearButton->box.getBottomLeft(); brandLabel->box.size.x = box.size.x; brandScroll->box.pos = brandLabel->box.getBottomLeft(); brandScroll->box.size.y = listHeight - brandLabel->box.size.y; @@ -444,7 +389,6 @@ struct ModuleBrowser : widget::OpaqueWidget { std::string search; std::string brand; std::string tag; - bool favorites = false; ModuleBrowser() { sidebar = new BrowserSidebar; @@ -508,17 +452,24 @@ struct ModuleBrowser : widget::OpaqueWidget { for (Widget *w : modelContainer->children) { ModelBox *m = dynamic_cast(w); assert(m); - m->visible = isModelVisible(m->model, favorites, search, brand, tag); + m->visible = isModelVisible(m->model, search, brand, tag); } // Sort ModelBoxes if (search.empty()) { - // Sort by plugin name and then module name + // Sort by favorite score and then name modelContainer->children.sort([&](Widget *w1, Widget *w2) { ModelBox *m1 = dynamic_cast(w1); ModelBox *m2 = dynamic_cast(w2); + // Sort by favorite score if either is available + float score1 = get_default(settings::favoriteScores, std::make_tuple(m1->model->plugin->slug, m1->model->slug), 0.f); + float score2 = get_default(settings::favoriteScores, std::make_tuple(m2->model->plugin->slug, m2->model->slug), 0.f); + if (score1 != score2) + return score1 > score2; + // Sort by plugin name if (m1->model->plugin->name != m2->model->plugin->name) return m1->model->plugin->name < m2->model->plugin->name; + // Sort by module name return m1->model->name < m2->model->name; }); } @@ -530,7 +481,7 @@ struct ModuleBrowser : widget::OpaqueWidget { assert(m); if (!m->visible) continue; - scores[m] = modelScore(m->model, search);; + scores[m] = modelScore(m->model, search); } // Sort by score modelContainer->children.sort([&](Widget *w1, Widget *w2) { @@ -541,18 +492,18 @@ struct ModuleBrowser : widget::OpaqueWidget { // Filter the brand and tag lists - // Get modules that would be filtered by just the favorites and search state + // Get modules that would be filtered by just the search query std::vector filteredModels; for (Widget *w : modelContainer->children) { ModelBox *m = dynamic_cast(w); assert(m); - if (isModelVisible(m->model, favorites, search, "", "")) + if (isModelVisible(m->model, search, "", "")) filteredModels.push_back(m->model); } auto hasModel = [&](const std::string &brand, const std::string &tag) -> bool { for (plugin::Model *model : filteredModels) { - if (isModelVisible(model, false, "", brand, tag)) + if (isModelVisible(model, "", brand, tag)) return true; } return false; @@ -595,6 +546,10 @@ struct ModuleBrowser : widget::OpaqueWidget { tag = ""; refresh(); } + + void onShow(const event::Show &e) override { + refresh(); + } }; @@ -627,6 +582,9 @@ inline void ModelBox::onButton(const event::Button &e) { h->name = "create module"; h->setModule(moduleWidget); APP->history->push(h); + + // Step favorite + stepFavoriteScore(model->plugin->slug, model->slug); } } @@ -672,17 +630,6 @@ inline void ClearButton::onAction(const event::Action &e) { browser->clear(); } -inline void ShowFavoritesQuantity::setValue(float value) { - ModuleBrowser *browser = widget->getAncestorOfType(); - browser->favorites = (bool) value; - browser->refresh(); -} - -inline float ShowFavoritesQuantity::getValue() { - ModuleBrowser *browser = widget->getAncestorOfType(); - return browser->favorites; -} - // Global functions diff --git a/src/settings.cpp b/src/settings.cpp index dc4afd2b..9f7c80f2 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -34,7 +34,7 @@ float frameRateLimit = 70.0; bool frameRateSync = true; bool skipLoadOnLaunch = false; std::string patchPath; -std::set favoriteModels = {}; +std::map, float> favoriteScores; std::vector cableColors = { nvgRGB(0xc9, 0xb7, 0x0e), // yellow nvgRGB(0x0c, 0x8e, 0x15), // green @@ -89,11 +89,15 @@ json_t *toJson() { json_object_set_new(rootJ, "patchPath", json_string(patchPath.c_str())); json_t *favoriteModelsJ = json_array(); - for (plugin::Model *model : favoriteModels) { - json_t *modelJ = json_object(); - json_object_set_new(modelJ, "plugin", json_string(model->plugin->slug.c_str())); - json_object_set_new(modelJ, "model", json_string(model->slug.c_str())); - json_array_append_new(favoriteModelsJ, modelJ); + for (auto &pair : favoriteScores) { + const std::string &plugin = std::get<0>(pair.first); + const std::string &model = std::get<1>(pair.first); + float score = pair.second; + json_t *favoriteJ = json_object(); + json_object_set_new(favoriteJ, "plugin", json_string(plugin.c_str())); + json_object_set_new(favoriteJ, "model", json_string(model.c_str())); + json_object_set_new(favoriteJ, "score", json_real(score)); + json_array_append_new(favoriteModelsJ, favoriteJ); } json_object_set_new(rootJ, "favoriteModels", favoriteModelsJ); @@ -198,7 +202,7 @@ void fromJson(json_t *rootJ) { favoriteModelsJ = json_object_get(rootJ, "favorites"); } if (favoriteModelsJ) { - favoriteModels.clear(); + favoriteScores.clear(); size_t i; json_t *favoriteJ; json_array_foreach(favoriteModelsJ, i, favoriteJ) { @@ -206,12 +210,15 @@ void fromJson(json_t *rootJ) { json_t *modelJ = json_object_get(favoriteJ, "model"); if (!pluginJ || !modelJ) continue; - std::string pluginSlug = json_string_value(pluginJ); - std::string modelSlug = json_string_value(modelJ); - plugin::Model *model = plugin::getModel(pluginSlug, modelSlug); - if (!model) - continue; - favoriteModels.insert(model); + std::string plugin = json_string_value(pluginJ); + std::string model = json_string_value(modelJ); + // Set default score when migrating favorites from v0.6 + float score = 1.f; + json_t *scoreJ = json_object_get(favoriteJ, "score"); + if (scoreJ) + score = json_number_value(scoreJ); + + favoriteScores[std::make_tuple(plugin, model)] = score; } }