Browse Source

Add favorite selection and filtering in Module Browser. Merge moduleWhitelist and moduleUsages into moduleInfos in settings.

tags/v2.0.0
Andrew Belt 3 years ago
parent
commit
4034d6015a
4 changed files with 209 additions and 143 deletions
  1. +10
    -8
      include/settings.hpp
  2. +108
    -40
      src/app/ModuleBrowser.cpp
  3. +30
    -29
      src/library.cpp
  4. +61
    -66
      src/settings.cpp

+ 10
- 8
include/settings.hpp View File

@@ -89,16 +89,18 @@ enum ModuleBrowserSort {
};
extern ModuleBrowserSort moduleBrowserSort;
extern float moduleBrowserZoom;
// pluginSlug -> moduleSlugs
extern std::map<std::string, std::set<std::string>> moduleWhitelist;

struct ModuleUsage {
int count = 0;
double lastTime = NAN;
struct ModuleInfo {
bool enabled = true;
bool favorite = false;
int added = 0;
double lastAdded = NAN;
};
// pluginSlug, moduleSlug -> ModuleUsage
extern std::map<std::string, std::map<std::string, ModuleUsage>> moduleUsages;
ModuleUsage* getModuleUsage(const std::string& pluginSlug, const std::string& moduleSlug);
// pluginSlug -> (moduleSlug -> ModuleInfo)
extern std::map<std::string, std::map<std::string, ModuleInfo>> moduleInfos;
/** Returns a ModuleInfo if exists for the given slugs.
*/
ModuleInfo* getModuleInfo(const std::string& pluginSlug, const std::string& moduleSlug);

void init();
json_t* toJson();


+ 108
- 40
src/app/ModuleBrowser.cpp View File

@@ -17,6 +17,7 @@
#include <ui/Button.hpp>
#include <ui/ChoiceButton.hpp>
#include <ui/RadioButton.hpp>
#include <ui/OptionButton.hpp>
#include <ui/Tooltip.hpp>
#include <app/ModuleWidget.hpp>
#include <app/Scene.hpp>
@@ -80,9 +81,9 @@ static void fuzzySearchInit() {

static ModuleWidget* chooseModel(plugin::Model* model) {
// Record usage
settings::ModuleUsage& mu = settings::moduleUsages[model->plugin->slug][model->slug];
mu.count++;
mu.lastTime = system::getUnixTime();
settings::ModuleInfo& mi = settings::moduleInfos[model->plugin->slug][model->slug];
mi.added++;
mi.lastAdded = system::getUnixTime();

// Create Module and ModuleWidget
engine::Module* module = model->createModule();
@@ -122,6 +123,7 @@ struct BrowserOverlay : ui::MenuOverlay {
}

void onAction(const ActionEvent& e) override {
// Hide instead of requestDelete()
hide();
}
};
@@ -188,14 +190,17 @@ struct ModelBox : widget::OpaqueWidget {
// Lazily create preview when drawn
createPreview();

const settings::ModuleInfo* mi = settings::getModuleInfo(model->plugin->slug, model->slug);

// Draw shadow
nvgBeginPath(args.vg);
float r = 10; // Blur radius
float c = 10; // Corner radius
float c = 5; // Corner radius
nvgRect(args.vg, -r, -r, box.size.x + 2 * r, box.size.y + 2 * r);
NVGcolor shadowColor = nvgRGBAf(0, 0, 0, 0.5);
NVGcolor transparentColor = nvgRGBAf(0, 0, 0, 0);
nvgFillPaint(args.vg, nvgBoxGradient(args.vg, 0, 0, box.size.x, box.size.y, c, r, shadowColor, transparentColor));
if (mi && mi->favorite)
shadowColor = nvgRGBAf(2, 2, 2, 1.0);
nvgFillPaint(args.vg, nvgBoxGradient(args.vg, 0, 0, box.size.x, box.size.y, c, r, shadowColor, color::BLACK_TRANSPARENT));
nvgFill(args.vg);

// To avoid blinding the user when rack brightness is low, draw framebuffer with the same brightness.
@@ -222,10 +227,6 @@ struct ModelBox : widget::OpaqueWidget {
}

void onButton(const ButtonEvent& e) override {
OpaqueWidget::onButton(e);
if (e.getTarget() != this)
return;

if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT) {
ModuleWidget* mw = chooseModel(model);

@@ -237,12 +238,19 @@ struct ModelBox : widget::OpaqueWidget {
// Disable dragging temporarily until the mouse has moved a bit.
mw->dragEnabled() = false;
}

// Open context menu on right-click
if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_RIGHT) {
createContextMenu();
e.consume(this);
}
}

ui::Tooltip* createTooltip() {
std::string text;
text = model->plugin->brand;
text += " " + model->name;
text += model->name;
text += "\n";
text += model->plugin->brand;
// Description
if (model->description != "") {
text += "\n" + model->description;
@@ -272,6 +280,29 @@ struct ModelBox : widget::OpaqueWidget {
setTooltip(NULL);
OpaqueWidget::onHide(e);
}

void createContextMenu() {
ui::Menu* menu = createMenu();

// menu->addChild(createMenuLabel(model->name));
// menu->addChild(createMenuLabel(model->plugin->brand));

menu->addChild(createBoolMenuItem("Favorite",
[=]() {
const settings::ModuleInfo* mi = settings::getModuleInfo(model->plugin->slug, model->slug);
return mi && mi->favorite;
},
[=](bool favorite) {
setFavorite(favorite);
}
));
}

void setFavorite(bool favorite) {
settings::ModuleInfo& mi = settings::moduleInfos[model->plugin->slug][model->slug];
mi.favorite = favorite;
// TODO Call user API
}
};


@@ -300,6 +331,13 @@ struct BrowserSearchField : ui::TextField {
};


struct FavoriteQuantity : Quantity {
ModuleBrowser* browser;
void setValue(float value) override;
float getValue() override;
};


struct ClearButton : ui::Button {
ModuleBrowser* browser;
void onAction(const ActionEvent& e) override;
@@ -433,6 +471,8 @@ struct ModuleBrowser : widget::OpaqueWidget {
BrowserSearchField* searchField;
BrandButton* brandButton;
TagButton* tagButton;
FavoriteQuantity* favoriteQuantity;
ui::OptionButton* favoriteButton;
ClearButton* clearButton;
ui::Label* countLabel;

@@ -443,6 +483,7 @@ struct ModuleBrowser : widget::OpaqueWidget {
std::string search;
std::string brand;
std::set<int> tagIds = {};
bool favorite = false;

// Caches and temporary state
std::map<plugin::Model*, float> prefilteredModelScores;
@@ -475,6 +516,15 @@ struct ModuleBrowser : widget::OpaqueWidget {
tagButton->browser = this;
headerLayout->addChild(tagButton);

favoriteQuantity = new FavoriteQuantity;
favoriteQuantity->browser = this;

favoriteButton = new ui::OptionButton;
favoriteButton->quantity = favoriteQuantity;
favoriteButton->text = "Favorites";
favoriteButton->box.size.x = 70;
headerLayout->addChild(favoriteButton);

clearButton = new ClearButton;
clearButton->box.size.x = 100;
clearButton->text = "Reset filters";
@@ -520,24 +570,19 @@ struct ModuleBrowser : widget::OpaqueWidget {
clear();
}

~ModuleBrowser() {
delete favoriteQuantity;
}

void resetModelBoxes() {
modelContainer->clearChildren();
modelOrders.clear();
// Iterate plugins
// 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);
// Iterate models in plugin
int modelIndex = 0;
for (plugin::Model* model : plugin->models) {
// Don't show module if plugin whitelist exists but the module is not in it.
if (pluginIt != settings::moduleWhitelist.end()) {
auto moduleIt = std::find(pluginIt->second.begin(), pluginIt->second.end(), model->slug);
if (moduleIt == pluginIt->second.end())
continue;
}

// Create ModelBox
ModelBox* modelBox = new ModelBox;
modelBox->setModel(model);
@@ -579,11 +624,21 @@ struct ModuleBrowser : widget::OpaqueWidget {
Widget::draw(args);
}

bool isModelVisible(plugin::Model* model, const std::string& brand, std::set<int> tagIds) {
bool isModelVisible(plugin::Model* model, const std::string& brand, std::set<int> tagIds, bool favorite) {
// Filter hidden
if (model->hidden)
return false;

settings::ModuleInfo* mi = settings::getModuleInfo(model->plugin->slug, model->slug);
if (mi && !mi->enabled)
return false;

// Filter favorites
if (favorite) {
if (!mi || !mi->favorite)
return false;
}

// Filter brand
if (!brand.empty()) {
if (model->plugin->brand != brand)
@@ -601,10 +656,10 @@ struct ModuleBrowser : widget::OpaqueWidget {
};

// Determines if there is at least 1 visible Model with a given brand and tag
bool hasVisibleModel(const std::string& brand, std::set<int> tagIds) {
bool hasVisibleModel(const std::string& brand, std::set<int> tagIds, bool favorite) {
for (const auto& pair : prefilteredModelScores) {
plugin::Model* model = pair.first;
if (isModelVisible(model, brand, tagIds))
if (isModelVisible(model, brand, tagIds, favorite))
return true;
}
return false;
@@ -628,7 +683,7 @@ struct ModuleBrowser : widget::OpaqueWidget {
// Filter ModelBoxes by brand and tag
for (Widget* w : modelContainer->children) {
ModelBox* m = reinterpret_cast<ModelBox*>(w);
m->setVisible(isModelVisible(m->model, brand, tagIds));
m->setVisible(isModelVisible(m->model, brand, tagIds, favorite));
}

// Filter and sort by search results
@@ -650,20 +705,20 @@ struct ModuleBrowser : widget::OpaqueWidget {
else if (settings::moduleBrowserSort == settings::MODULE_BROWSER_SORT_LAST_USED) {
sortModels([this](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;
const settings::ModuleInfo* mi = settings::getModuleInfo(p->slug, m->model->slug);
double lastAdded = mi ? mi->lastAdded : -INFINITY;
int modelOrder = get(modelOrders, m->model, 0);
return std::make_tuple(-lastTime, -p->modifiedTimestamp, p->brand, p->name, modelOrder);
return std::make_tuple(-lastAdded, -p->modifiedTimestamp, p->brand, p->name, modelOrder);
});
}
else if (settings::moduleBrowserSort == settings::MODULE_BROWSER_SORT_MOST_USED) {
sortModels([this](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;
const settings::ModuleInfo* mi = settings::getModuleInfo(p->slug, m->model->slug);
int added = mi ? mi->added : 0;
double lastAdded = mi ? mi->lastAdded : -INFINITY;
int modelOrder = get(modelOrders, m->model, 0);
return std::make_tuple(-count, -lastTime, -p->modifiedTimestamp, p->brand, p->name, modelOrder);
return std::make_tuple(-added, -lastAdded, -p->modifiedTimestamp, p->brand, p->name, modelOrder);
});
}
else if (settings::moduleBrowserSort == settings::MODULE_BROWSER_SORT_BRAND) {
@@ -721,7 +776,7 @@ struct ModuleBrowser : widget::OpaqueWidget {
if (w->isVisible())
count++;
}
countLabel->text = string::f("%d modules", count);
countLabel->text = string::f("%d %s", count, (count == 1) ? "module" : "modules");
}

void clear() {
@@ -729,6 +784,7 @@ struct ModuleBrowser : widget::OpaqueWidget {
searchField->setText("");
brand = "";
tagIds = {};
favorite = false;
refresh();
}

@@ -736,23 +792,35 @@ struct ModuleBrowser : widget::OpaqueWidget {
refresh();
OpaqueWidget::onShow(e);
}

void onButton(const ButtonEvent& e) override {
Widget::onButton(e);
e.stopPropagating();
// Consume all mouse buttons
if (!e.isConsumed())
e.consume(this);
}
};


// Implementations to resolve dependencies


inline void FavoriteQuantity::setValue(float value) {
browser->favorite = value;
browser->refresh();
}

inline float FavoriteQuantity::getValue() {
return browser->favorite;
}

inline void ClearButton::onAction(const ActionEvent& e) {
browser->clear();
}

inline void BrowserSearchField::onSelectKey(const SelectKeyEvent& e) {
if (e.action == GLFW_PRESS || e.action == GLFW_REPEAT) {
if (e.key == GLFW_KEY_ESCAPE) {
BrowserOverlay* overlay = browser->getAncestorOfType<BrowserOverlay>();
overlay->hide();
e.consume(this);
}
// Backspace when the field is empty to clear filters.
if (e.key == GLFW_KEY_BACKSPACE) {
if (text == "") {
@@ -823,7 +891,7 @@ inline void BrandButton::onAction(const ActionEvent& e) {
brandItem->text = brand;
brandItem->brand = brand;
brandItem->browser = browser;
brandItem->disabled = !browser->hasVisibleModel(brand, browser->tagIds);
brandItem->disabled = !browser->hasVisibleModel(brand, browser->tagIds, browser->favorite);
menu->addChild(brandItem);
}
}
@@ -901,7 +969,7 @@ inline void TagButton::onAction(const ActionEvent& e) {
tagItem->text = tag::getTag(tagId);
tagItem->tagId = tagId;
tagItem->browser = browser;
tagItem->disabled = !browser->hasVisibleModel(browser->brand, {tagId});
tagItem->disabled = !browser->hasVisibleModel(browser->brand, {tagId}, browser->favorite);
menu->addChild(tagItem);
}
}


+ 30
- 29
src/library.cpp View File

@@ -249,35 +249,36 @@ void checkUpdates() {
}

// Get module whitelist
{
std::string whitelistUrl = API_URL + "/modules";
json_t* whitelistResJ = network::requestJson(network::METHOD_GET, whitelistUrl, NULL, getTokenCookies());
if (!whitelistResJ) {
WARN("Request for module whitelist failed");
updateStatus = "Could not query updates";
return;
}
DEFER({json_decref(whitelistResJ);});

std::map<std::string, std::set<std::string>> moduleWhitelist;
json_t* pluginsJ = json_object_get(whitelistResJ, "plugins");

// Iterate plugins
const char* pluginSlug;
json_t* modulesJ;
json_object_foreach(pluginsJ, pluginSlug, modulesJ) {
// Iterate modules in plugin
size_t moduleIndex;
json_t* moduleSlugJ;
json_array_foreach(modulesJ, moduleIndex, moduleSlugJ) {
std::string moduleSlug = json_string_value(moduleSlugJ);
// Insert module in whitelist
moduleWhitelist[pluginSlug].insert(moduleSlug);
}
}

settings::moduleWhitelist = moduleWhitelist;
}
// TODO
// {
// std::string whitelistUrl = API_URL + "/modules";
// json_t* whitelistResJ = network::requestJson(network::METHOD_GET, whitelistUrl, NULL, getTokenCookies());
// if (!whitelistResJ) {
// WARN("Request for module whitelist failed");
// updateStatus = "Could not query updates";
// return;
// }
// DEFER({json_decref(whitelistResJ);});

// std::map<std::string, std::set<std::string>> moduleWhitelist;
// json_t* pluginsJ = json_object_get(whitelistResJ, "plugins");

// // Iterate plugins
// const char* pluginSlug;
// json_t* modulesJ;
// json_object_foreach(pluginsJ, pluginSlug, modulesJ) {
// // Iterate modules in plugin
// size_t moduleIndex;
// json_t* moduleSlugJ;
// json_array_foreach(modulesJ, moduleIndex, moduleSlugJ) {
// std::string moduleSlug = json_string_value(moduleSlugJ);
// // Insert module in whitelist
// moduleWhitelist[pluginSlug].insert(moduleSlug);
// }
// }

// settings::moduleWhitelist = moduleWhitelist;
// }

updateStatus = "";
}


+ 61
- 66
src/settings.cpp View File

@@ -64,8 +64,18 @@ int tipIndex = -1;
bool discordUpdateActivity = true;
ModuleBrowserSort moduleBrowserSort = MODULE_BROWSER_SORT_UPDATED;
float moduleBrowserZoom = -1.f;
std::map<std::string, std::set<std::string>> moduleWhitelist = {};
std::map<std::string, std::map<std::string, ModuleUsage>> moduleUsages = {};
std::map<std::string, std::map<std::string, ModuleInfo>> moduleInfos;


ModuleInfo* getModuleInfo(const std::string& pluginSlug, const std::string& moduleSlug) {
auto pluginIt = moduleInfos.find(pluginSlug);
if (pluginIt == moduleInfos.end())
return NULL;
auto moduleIt = pluginIt->second.find(moduleSlug);
if (moduleIt == pluginIt->second.end())
return NULL;
return &moduleIt->second;
}


void init() {
@@ -78,17 +88,6 @@ void init() {
}


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();

@@ -169,33 +168,34 @@ json_t* toJson() {

json_object_set_new(rootJ, "moduleBrowserZoom", json_real(moduleBrowserZoom));

json_t* moduleWhitelistJ = json_object();
for (const auto& pair : moduleWhitelist) {
json_t* moduleSlugsJ = json_array();
for (const std::string& moduleSlug : pair.second) {
json_array_append_new(moduleSlugsJ, json_string(moduleSlug.c_str()));
}
json_object_set_new(moduleWhitelistJ, pair.first.c_str(), moduleSlugsJ);
}
json_object_set_new(rootJ, "moduleWhitelist", moduleWhitelistJ);

json_t* moduleUsagesJ = json_object();
for (const auto& pair : moduleUsages) {
json_t* modulesJ = json_object();
for (const auto& modulePair : pair.second) {
const ModuleUsage& mu = modulePair.second;
if (mu.count <= 0 || !std::isfinite(mu.lastTime))
continue;
json_t* moduleUsagesJ = json_object();
json_t* moduleInfosJ = json_object();
for (const auto& pluginPair : moduleInfos) {
json_t* pluginJ = json_object();
for (const auto& modulePair : pluginPair.second) {
const ModuleInfo& m = modulePair.second;
json_t* moduleJ = json_object();
{
json_object_set_new(moduleUsagesJ, "count", json_integer(mu.count));
json_object_set_new(moduleUsagesJ, "lastTime", json_real(mu.lastTime));
// To make setting.json smaller, only set properties if not default values.
if (!m.enabled)
json_object_set_new(moduleJ, "enabled", json_boolean(m.enabled));
if (m.favorite)
json_object_set_new(moduleJ, "favorite", json_boolean(m.favorite));
if (m.added > 0)
json_object_set_new(moduleJ, "added", json_integer(m.added));
if (std::isfinite(m.lastAdded))
json_object_set_new(moduleJ, "lastAdded", json_real(m.lastAdded));
}
json_object_set_new(modulesJ, modulePair.first.c_str(), moduleUsagesJ);
if (json_object_size(moduleJ))
json_object_set_new(pluginJ, modulePair.first.c_str(), moduleJ);
else
json_decref(moduleJ);
}
json_object_set_new(moduleUsagesJ, pair.first.c_str(), modulesJ);
if (json_object_size(pluginJ))
json_object_set_new(moduleInfosJ, pluginPair.first.c_str(), pluginJ);
else
json_decref(pluginJ);
}
json_object_set_new(rootJ, "moduleUsages", moduleUsagesJ);
json_object_set_new(rootJ, "moduleInfos", moduleInfosJ);

return rootJ;
}
@@ -349,39 +349,34 @@ void fromJson(json_t* rootJ) {
if (moduleBrowserZoomJ)
moduleBrowserZoom = json_number_value(moduleBrowserZoomJ);

moduleWhitelist.clear();
json_t* moduleWhitelistJ = json_object_get(rootJ, "moduleWhitelist");
if (moduleWhitelistJ) {
const char* pluginSlug;
json_t* moduleSlugsJ;
json_object_foreach(moduleWhitelistJ, pluginSlug, moduleSlugsJ) {
auto& moduleSlugs = moduleWhitelist[pluginSlug];
size_t i;
json_t* moduleSlugJ;
json_array_foreach(moduleSlugsJ, i, moduleSlugJ) {
std::string moduleSlug = json_string_value(moduleSlugJ);
moduleSlugs.insert(moduleSlug);
}
}
}

moduleUsages.clear();
json_t* moduleUsagesJ = json_object_get(rootJ, "moduleUsages");
if (moduleUsagesJ) {
moduleInfos.clear();
json_t* moduleInfosJ = json_object_get(rootJ, "moduleInfos");
if (moduleInfosJ) {
const char* pluginSlug;
json_t* modulesJ;
json_object_foreach(moduleUsagesJ, pluginSlug, modulesJ) {
json_t* pluginJ;
json_object_foreach(moduleInfosJ, pluginSlug, pluginJ) {
const char* moduleSlug;
json_t* moduleJ;
json_object_foreach(modulesJ, moduleSlug, moduleJ) {
ModuleUsage mu;
json_t* countJ = json_object_get(moduleJ, "count");
if (countJ)
mu.count = json_integer_value(countJ);
json_t* lastTimeJ = json_object_get(moduleJ, "lastTime");
if (lastTimeJ)
mu.lastTime = json_number_value(lastTimeJ);
moduleUsages[pluginSlug][moduleSlug] = mu;
json_object_foreach(pluginJ, moduleSlug, moduleJ) {
ModuleInfo m;

json_t* enabledJ = json_object_get(moduleJ, "enabled");
if (enabledJ)
m.enabled = json_boolean_value(enabledJ);

json_t* favoriteJ = json_object_get(moduleJ, "favorite");
if (favoriteJ)
m.favorite = json_boolean_value(favoriteJ);

json_t* addedJ = json_object_get(moduleJ, "added");
if (addedJ)
m.added = json_integer_value(addedJ);

json_t* lastAddedJ = json_object_get(moduleJ, "lastAdded");
if (lastAddedJ)
m.lastAdded = json_number_value(lastAddedJ);

moduleInfos[pluginSlug][moduleSlug] = m;
}
}
}


Loading…
Cancel
Save