Browse Source

Add favorite score to settings, based on @modlfo's scoring algorithm. Sort by favorite score in Module Browser.

tags/v1.0.0
Andrew Belt 5 years ago
parent
commit
c2ffcbbfd7
3 changed files with 67 additions and 111 deletions
  1. +5
    -3
      include/settings.hpp
  2. +42
    -95
      src/app/ModuleBrowser.cpp
  3. +20
    -13
      src/settings.cpp

+ 5
- 3
include/settings.hpp View File

@@ -1,9 +1,10 @@
#pragma once #pragma once
#include "common.hpp" #include "common.hpp"
#include "math.hpp" #include "math.hpp"
#include "plugin/Model.hpp"
#include "color.hpp" #include "color.hpp"
#include <set>
#include <vector>
#include <map>
#include <tuple>
#include <jansson.h> #include <jansson.h>




@@ -38,7 +39,8 @@ extern float frameRateLimit;
extern bool frameRateSync; extern bool frameRateSync;
extern bool skipLoadOnLaunch; extern bool skipLoadOnLaunch;
extern std::string patchPath; extern std::string patchPath;
extern std::set<plugin::Model*> favoriteModels;
// (plugin, model) -> score
extern std::map<std::tuple<std::string, std::string>, float> favoriteScores;
extern std::vector<NVGcolor> cableColors; extern std::vector<NVGcolor> cableColors;


json_t *toJson(); json_t *toJson();


+ 42
- 95
src/app/ModuleBrowser.cpp View File

@@ -48,14 +48,7 @@ static float modelScore(plugin::Model *model, const std::string &search) {
return score; 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 // Filter search query
if (search != "") { if (search != "") {
float score = modelScore(model, search); float score = modelScore(model, search);
@@ -85,6 +78,25 @@ static bool isModelVisible(plugin::Model *model, bool favorites, const std::stri
return true; 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 <typename K, typename V>
V get_default(const std::map<K, V> &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 { struct BrowserOverlay : widget::OpaqueWidget {
void step() override { 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; static const float MODEL_BOX_ZOOM = 0.5f;




struct ModelBox : widget::OpaqueWidget { struct ModelBox : widget::OpaqueWidget {
plugin::Model *model; plugin::Model *model;
widget::Widget *previewWidget; widget::Widget *previewWidget;
ModelFavoriteButton *favoriteButton;
ui::Tooltip *tooltip = NULL; ui::Tooltip *tooltip = NULL;
/** Lazily created */ /** Lazily created */
widget::FramebufferWidget *previewFb = NULL; widget::FramebufferWidget *previewFb = NULL;
@@ -164,13 +145,6 @@ struct ModelBox : widget::OpaqueWidget {
previewWidget = new widget::TransparentWidget; previewWidget = new widget::TransparentWidget;
previewWidget->box.size.y = std::ceil(RACK_GRID_HEIGHT * MODEL_BOX_ZOOM); previewWidget->box.size.y = std::ceil(RACK_GRID_HEIGHT * MODEL_BOX_ZOOM);
addChild(previewWidget); addChild(previewWidget);

// Favorite button
favoriteButton = new ModelFavoriteButton;
dynamic_cast<ModelFavoriteQuantity*>(favoriteButton->quantity)->model = model;
favoriteButton->box.pos.y = box.size.y;
box.size.y += favoriteButton->box.size.y;
addChild(favoriteButton);
} }


void createPreview() { void createPreview() {
@@ -192,7 +166,6 @@ struct ModelBox : widget::OpaqueWidget {
zoomWidget->box.size.y = RACK_GRID_HEIGHT * MODEL_BOX_ZOOM; zoomWidget->box.size.y = RACK_GRID_HEIGHT * MODEL_BOX_ZOOM;
previewWidget->box.size.x = std::ceil(zoomWidget->box.size.x); 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; 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 { struct BrowserSidebar : widget::Widget {
BrowserSearchField *searchField; BrowserSearchField *searchField;
ClearButton *clearButton; ClearButton *clearButton;
ShowFavoritesButton *favoriteButton;
ui::Label *brandLabel; ui::Label *brandLabel;
ui::List *brandList; ui::List *brandList;
ui::ScrollWidget *brandScroll; ui::ScrollWidget *brandScroll;
@@ -357,10 +308,6 @@ struct BrowserSidebar : widget::Widget {
clearButton->text = "Reset filters"; clearButton->text = "Reset filters";
addChild(clearButton); addChild(clearButton);


favoriteButton = new ShowFavoritesButton;
dynamic_cast<ShowFavoritesQuantity*>(favoriteButton->quantity)->widget = favoriteButton;
addChild(favoriteButton);

brandLabel = new ui::Label; brandLabel = new ui::Label;
// brandLabel->fontSize = 16; // brandLabel->fontSize = 16;
brandLabel->color = nvgRGB(0x80, 0x80, 0x80); brandLabel->color = nvgRGB(0x80, 0x80, 0x80);
@@ -409,13 +356,11 @@ struct BrowserSidebar : widget::Widget {
searchField->box.size.x = box.size.x; searchField->box.size.x = box.size.x;
clearButton->box.pos = searchField->box.getBottomLeft(); clearButton->box.pos = searchField->box.getBottomLeft();
clearButton->box.size.x = box.size.x; 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); listHeight = std::floor(listHeight);


brandLabel->box.pos = favoriteButton->box.getBottomLeft();
brandLabel->box.pos = clearButton->box.getBottomLeft();
brandLabel->box.size.x = box.size.x; brandLabel->box.size.x = box.size.x;
brandScroll->box.pos = brandLabel->box.getBottomLeft(); brandScroll->box.pos = brandLabel->box.getBottomLeft();
brandScroll->box.size.y = listHeight - brandLabel->box.size.y; brandScroll->box.size.y = listHeight - brandLabel->box.size.y;
@@ -444,7 +389,6 @@ struct ModuleBrowser : widget::OpaqueWidget {
std::string search; std::string search;
std::string brand; std::string brand;
std::string tag; std::string tag;
bool favorites = false;


ModuleBrowser() { ModuleBrowser() {
sidebar = new BrowserSidebar; sidebar = new BrowserSidebar;
@@ -508,17 +452,24 @@ struct ModuleBrowser : widget::OpaqueWidget {
for (Widget *w : modelContainer->children) { for (Widget *w : modelContainer->children) {
ModelBox *m = dynamic_cast<ModelBox*>(w); ModelBox *m = dynamic_cast<ModelBox*>(w);
assert(m); assert(m);
m->visible = isModelVisible(m->model, favorites, search, brand, tag);
m->visible = isModelVisible(m->model, search, brand, tag);
} }


// Sort ModelBoxes // Sort ModelBoxes
if (search.empty()) { 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) { modelContainer->children.sort([&](Widget *w1, Widget *w2) {
ModelBox *m1 = dynamic_cast<ModelBox*>(w1); ModelBox *m1 = dynamic_cast<ModelBox*>(w1);
ModelBox *m2 = dynamic_cast<ModelBox*>(w2); ModelBox *m2 = dynamic_cast<ModelBox*>(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) if (m1->model->plugin->name != m2->model->plugin->name)
return 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; return m1->model->name < m2->model->name;
}); });
} }
@@ -530,7 +481,7 @@ struct ModuleBrowser : widget::OpaqueWidget {
assert(m); assert(m);
if (!m->visible) if (!m->visible)
continue; continue;
scores[m] = modelScore(m->model, search);;
scores[m] = modelScore(m->model, search);
} }
// Sort by score // Sort by score
modelContainer->children.sort([&](Widget *w1, Widget *w2) { modelContainer->children.sort([&](Widget *w1, Widget *w2) {
@@ -541,18 +492,18 @@ struct ModuleBrowser : widget::OpaqueWidget {


// Filter the brand and tag lists // 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<plugin::Model*> filteredModels; std::vector<plugin::Model*> filteredModels;
for (Widget *w : modelContainer->children) { for (Widget *w : modelContainer->children) {
ModelBox *m = dynamic_cast<ModelBox*>(w); ModelBox *m = dynamic_cast<ModelBox*>(w);
assert(m); assert(m);
if (isModelVisible(m->model, favorites, search, "", ""))
if (isModelVisible(m->model, search, "", ""))
filteredModels.push_back(m->model); filteredModels.push_back(m->model);
} }


auto hasModel = [&](const std::string &brand, const std::string &tag) -> bool { auto hasModel = [&](const std::string &brand, const std::string &tag) -> bool {
for (plugin::Model *model : filteredModels) { for (plugin::Model *model : filteredModels) {
if (isModelVisible(model, false, "", brand, tag))
if (isModelVisible(model, "", brand, tag))
return true; return true;
} }
return false; return false;
@@ -595,6 +546,10 @@ struct ModuleBrowser : widget::OpaqueWidget {
tag = ""; tag = "";
refresh(); 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->name = "create module";
h->setModule(moduleWidget); h->setModule(moduleWidget);
APP->history->push(h); 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(); browser->clear();
} }


inline void ShowFavoritesQuantity::setValue(float value) {
ModuleBrowser *browser = widget->getAncestorOfType<ModuleBrowser>();
browser->favorites = (bool) value;
browser->refresh();
}

inline float ShowFavoritesQuantity::getValue() {
ModuleBrowser *browser = widget->getAncestorOfType<ModuleBrowser>();
return browser->favorites;
}



// Global functions // Global functions




+ 20
- 13
src/settings.cpp View File

@@ -34,7 +34,7 @@ float frameRateLimit = 70.0;
bool frameRateSync = true; bool frameRateSync = true;
bool skipLoadOnLaunch = false; bool skipLoadOnLaunch = false;
std::string patchPath; std::string patchPath;
std::set<plugin::Model*> favoriteModels = {};
std::map<std::tuple<std::string, std::string>, float> favoriteScores;
std::vector<NVGcolor> cableColors = { std::vector<NVGcolor> cableColors = {
nvgRGB(0xc9, 0xb7, 0x0e), // yellow nvgRGB(0xc9, 0xb7, 0x0e), // yellow
nvgRGB(0x0c, 0x8e, 0x15), // green nvgRGB(0x0c, 0x8e, 0x15), // green
@@ -89,11 +89,15 @@ json_t *toJson() {
json_object_set_new(rootJ, "patchPath", json_string(patchPath.c_str())); json_object_set_new(rootJ, "patchPath", json_string(patchPath.c_str()));


json_t *favoriteModelsJ = json_array(); 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); json_object_set_new(rootJ, "favoriteModels", favoriteModelsJ);


@@ -198,7 +202,7 @@ void fromJson(json_t *rootJ) {
favoriteModelsJ = json_object_get(rootJ, "favorites"); favoriteModelsJ = json_object_get(rootJ, "favorites");
} }
if (favoriteModelsJ) { if (favoriteModelsJ) {
favoriteModels.clear();
favoriteScores.clear();
size_t i; size_t i;
json_t *favoriteJ; json_t *favoriteJ;
json_array_foreach(favoriteModelsJ, i, favoriteJ) { json_array_foreach(favoriteModelsJ, i, favoriteJ) {
@@ -206,12 +210,15 @@ void fromJson(json_t *rootJ) {
json_t *modelJ = json_object_get(favoriteJ, "model"); json_t *modelJ = json_object_get(favoriteJ, "model");
if (!pluginJ || !modelJ) if (!pluginJ || !modelJ)
continue; 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;
} }
} }




Loading…
Cancel
Save