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
#include "common.hpp"
#include "math.hpp"
#include "plugin/Model.hpp"
#include "color.hpp"
#include <set>
#include <vector>
#include <map>
#include <tuple>
#include <jansson.h>


@@ -38,7 +39,8 @@ extern float frameRateLimit;
extern bool frameRateSync;
extern bool skipLoadOnLaunch;
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;

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;
}

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 <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 {
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<ModelFavoriteQuantity*>(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<ShowFavoritesQuantity*>(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<ModelBox*>(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<ModelBox*>(w1);
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)
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<plugin::Model*> filteredModels;
for (Widget *w : modelContainer->children) {
ModelBox *m = dynamic_cast<ModelBox*>(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<ModuleBrowser>();
browser->favorites = (bool) value;
browser->refresh();
}

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


// Global functions



+ 20
- 13
src/settings.cpp View File

@@ -34,7 +34,7 @@ float frameRateLimit = 70.0;
bool frameRateSync = true;
bool skipLoadOnLaunch = false;
std::string patchPath;
std::set<plugin::Model*> favoriteModels = {};
std::map<std::tuple<std::string, std::string>, float> favoriteScores;
std::vector<NVGcolor> 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;
}
}



Loading…
Cancel
Save