Browse Source

Replace Module Browser sidebar with header. Add multiple tag selection (WIP), zoom (WIP), and sorting options (WIP).

tags/v2.0.0
Andrew Belt 3 years ago
parent
commit
b986ff5882
4 changed files with 293 additions and 222 deletions
  1. +40
    -35
      src/app/MenuBar.cpp
  2. +251
    -185
      src/app/ModuleBrowser.cpp
  3. +1
    -1
      src/engine/Engine.cpp
  4. +1
    -1
      src/ui/TextField.cpp

+ 40
- 35
src/app/MenuBar.cpp View File

@@ -28,6 +28,7 @@

namespace rack {
namespace app {
namespace menuBar {


struct MenuButton : ui::Button {
@@ -61,7 +62,7 @@ struct NotificationIcon : widget::Widget {
struct UrlItem : ui::MenuItem {
std::string url;
void onAction(const event::Action& e) override {
std::thread t([ = ] {
std::thread t([=] {
system::openBrowser(url);
});
t.detach();
@@ -71,7 +72,7 @@ struct UrlItem : ui::MenuItem {
struct FolderItem : ui::MenuItem {
std::string path;
void onAction(const event::Action& e) override {
std::thread t([ = ] {
std::thread t([=] {
system::openFolder(path);
});
t.detach();
@@ -611,7 +612,7 @@ struct LogInItem : ui::MenuItem {
isLoggingIn = true;
std::string email = emailField->text;
std::string password = passwordField->text;
std::thread t([ = ] {
std::thread t([=] {
library::logIn(email, password);
isLoggingIn = false;
});
@@ -954,50 +955,54 @@ struct HelpButton : MenuButton {


struct MenuBar : widget::OpaqueWidget {
void draw(const DrawArgs& args) override {
bndMenuBackground(args.vg, 0.0, 0.0, box.size.x, box.size.y, BND_CORNER_ALL);
bndBevel(args.vg, 0.0, 0.0, box.size.x, box.size.y);
MenuBar() {
const float margin = 5;
box.size.y = BND_WIDGET_HEIGHT + 2 * margin;

Widget::draw(args);
}
};
ui::SequentialLayout* layout = new ui::SequentialLayout;
layout->box.pos = math::Vec(margin, margin);
layout->spacing = math::Vec(0, 0);
addChild(layout);

FileButton* fileButton = new FileButton;
fileButton->text = "File";
layout->addChild(fileButton);

widget::Widget* createMenuBar() {
MenuBar* menuBar = new MenuBar;
EditButton* editButton = new EditButton;
editButton->text = "Edit";
layout->addChild(editButton);

const float margin = 5;
menuBar->box.size.y = BND_WIDGET_HEIGHT + 2 * margin;
ViewButton* viewButton = new ViewButton;
viewButton->text = "View";
layout->addChild(viewButton);

ui::SequentialLayout* layout = new ui::SequentialLayout;
layout->box.pos = math::Vec(margin, margin);
layout->spacing = math::Vec(0, 0);
menuBar->addChild(layout);
EngineButton* engineButton = new EngineButton;
engineButton->text = "Engine";
layout->addChild(engineButton);

FileButton* fileButton = new FileButton;
fileButton->text = "File";
layout->addChild(fileButton);
LibraryButton* libraryButton = new LibraryButton;
libraryButton->text = "Library";
layout->addChild(libraryButton);

EditButton* editButton = new EditButton;
editButton->text = "Edit";
layout->addChild(editButton);
HelpButton* helpButton = new HelpButton;
helpButton->text = "Help";
layout->addChild(helpButton);
}

ViewButton* viewButton = new ViewButton;
viewButton->text = "View";
layout->addChild(viewButton);
void draw(const DrawArgs& args) override {
bndMenuBackground(args.vg, 0.0, 0.0, box.size.x, box.size.y, BND_CORNER_ALL);
bndBevel(args.vg, 0.0, 0.0, box.size.x, box.size.y);

EngineButton* engineButton = new EngineButton;
engineButton->text = "Engine";
layout->addChild(engineButton);
Widget::draw(args);
}
};

LibraryButton* libraryButton = new LibraryButton;
libraryButton->text = "Library";
layout->addChild(libraryButton);

HelpButton* helpButton = new HelpButton;
helpButton->text = "Help";
layout->addChild(helpButton);
} // namespace menuBar


widget::Widget* createMenuBar() {
menuBar::MenuBar* menuBar = new menuBar::MenuBar;
return menuBar;
}



+ 251
- 185
src/app/ModuleBrowser.cpp View File

@@ -1,5 +1,6 @@
#include <set>
#include <algorithm>
#include <thread>

#include <app/ModuleBrowser.hpp>
#include <widget/OpaqueWidget.hpp>
@@ -10,13 +11,12 @@
#include <ui/SequentialLayout.hpp>
#include <ui/MarginLayout.hpp>
#include <ui/Label.hpp>
#include <ui/Slider.hpp>
#include <ui/TextField.hpp>
#include <ui/MenuOverlay.hpp>
#include <ui/List.hpp>
#include <ui/MenuItem.hpp>
#include <ui/Button.hpp>
#include <ui/RadioButton.hpp>
#include <ui/ChoiceButton.hpp>
#include <ui/RadioButton.hpp>
#include <ui/Tooltip.hpp>
#include <app/ModuleWidget.hpp>
#include <app/Scene.hpp>
@@ -27,12 +27,14 @@
#include <string.hpp>
#include <history.hpp>
#include <settings.hpp>
#include <system.hpp>
#include <tag.hpp>
#include <FuzzySearchDatabase.hpp>


namespace rack {
namespace app {
namespace moduleBrowser {


static fuzzysearch::Database<plugin::Model*> modelDb;
@@ -244,19 +246,6 @@ struct ModelBox : widget::OpaqueWidget {
};


struct BrandItem : ui::MenuItem {
void onAction(const event::Action& e) override;
void step() override;
};


struct TagItem : ui::MenuItem {
int tagId;
void onAction(const event::Action& e) override;
void step() override;
};


struct BrowserSearchField : ui::TextField {
void step() override {
// Steal focus when step is called
@@ -285,131 +274,185 @@ struct ClearButton : ui::Button {
};


struct BrowserSidebar : widget::Widget {
BrowserSearchField* searchField;
ClearButton* clearButton;
ui::Label* tagLabel;
ui::List* tagList;
ui::ScrollWidget* tagScroll;
ui::Label* brandLabel;
ui::List* brandList;
ui::ScrollWidget* brandScroll;

BrowserSidebar() {
// Search
searchField = new BrowserSearchField;
addChild(searchField);
struct BrandItem : ui::MenuItem {
std::string brand;
void onAction(const event::Action& e) override;
void step() override;
};

// Clear filters
clearButton = new ClearButton;
clearButton->text = "Reset filters";
addChild(clearButton);

// Tag label
tagLabel = new ui::Label;
// tagLabel->fontSize = 16;
tagLabel->color = nvgRGB(0x80, 0x80, 0x80);
tagLabel->text = "Tags";
addChild(tagLabel);

// Tag list
tagScroll = new ui::ScrollWidget;
addChild(tagScroll);

tagList = new ui::List;
tagScroll->container->addChild(tagList);

for (int tagId = 0; tagId < (int) tag::tagAliases.size(); tagId++) {
TagItem* item = new TagItem;
item->text = tag::tagAliases[tagId][0];
item->tagId = tagId;
tagList->addChild(item);
}

// Brand label
brandLabel = new ui::Label;
// brandLabel->fontSize = 16;
brandLabel->color = nvgRGB(0x80, 0x80, 0x80);
brandLabel->text = "Brands";
addChild(brandLabel);
struct BrandButton : ui::ChoiceButton {
void onAction(const event::Action& e) override {
// // Collect brands from all plugins
// std::set<std::string, string::CaseInsensitiveCompare> brands;
// for (plugin::Plugin* plugin : plugin::plugins) {
// brands.insert(plugin->brand);
// }

// for (const std::string& brand : brands) {
// BrandItem* item = new BrandItem;
// item->text = brand;
// brandList->addChild(item);
// }
}

// Brand list
brandScroll = new ui::ScrollWidget;
addChild(brandScroll);
void step() override;
};

brandList = new ui::List;
brandScroll->container->addChild(brandList);

// Collect brands from all plugins
std::set<std::string, string::CaseInsensitiveCompare> brands;
for (plugin::Plugin* plugin : plugin::plugins) {
brands.insert(plugin->brand);
}
struct TagItem : ui::MenuItem {
int tagId;
void onAction(const event::Action& e) override;
void step() override;
};

for (const std::string& brand : brands) {
BrandItem* item = new BrandItem;
item->text = brand;
brandList->addChild(item);
}

struct TagButton : ui::ChoiceButton {
void onAction(const event::Action& e) override {
// for (int tagId = 0; tagId < (int) tag::tagAliases.size(); tagId++) {
// TagItem* item = new TagItem;
// item->text = tag::tagAliases[tagId][0];
// item->tagId = tagId;
// tagList->addChild(item);
// }
}

void step() override {
searchField->box.size.x = box.size.x;
clearButton->box.pos = searchField->box.getBottomLeft();
clearButton->box.size.x = box.size.x;
void step() override;
};

float listHeight = (box.size.y - clearButton->box.getBottom()) / 2;
listHeight = std::floor(listHeight);

tagLabel->box.pos = clearButton->box.getBottomLeft();
tagLabel->box.size.x = box.size.x;
tagScroll->box.pos = tagLabel->box.getBottomLeft();
tagScroll->box.size.y = listHeight - tagLabel->box.size.y;
tagScroll->box.size.x = box.size.x;
tagList->box.size.x = tagScroll->box.size.x;
struct ZoomQuantity : Quantity {
void setValue(float value) override {
settings::zoom = value;
}
float getValue() override {
return settings::zoom;
}
float getMinValue() override {
return -2.0;
}
float getMaxValue() override {
return 2.0;
}
float getDefaultValue() override {
return 0.0;
}
float getDisplayValue() override {
return std::round(std::pow(2.f, getValue()) * 100);
}
void setDisplayValue(float displayValue) override {
setValue(std::log2(displayValue / 100));
}
std::string getLabel() override {
return "Zoom";
}
std::string getUnit() override {
return "%";
}
};

brandLabel->box.pos = tagScroll->box.getBottomLeft();
brandLabel->box.size.x = box.size.x;
brandScroll->box.pos = brandLabel->box.getBottomLeft();
brandScroll->box.size.y = listHeight - brandLabel->box.size.y;
brandScroll->box.size.x = box.size.x;
brandList->box.size.x = brandScroll->box.size.x;

Widget::step();
struct ZoomSlider : ui::Slider {
ZoomSlider() {
quantity = new ZoomQuantity;
}
~ZoomSlider() {
delete quantity;
}
};


struct SortButton : ui::ChoiceButton {
void onAction(const event::Action& e) override {}
void step() override {}
};


struct UrlButton : ui::Button {
std::string url;
void onAction(const event::Action& e) override {
std::thread t([=] {
system::openBrowser(url);
});
t.detach();
}
};


struct ModuleBrowser : widget::OpaqueWidget {
BrowserSidebar* sidebar;
ui::SequentialLayout* headerLayout;
BrowserSearchField* searchField;
BrandButton* brandButton;
TagButton* tagButton;
ClearButton* clearButton;

ui::ScrollWidget* modelScroll;
ui::Label* modelLabel;
ui::MarginLayout* modelMargin;
ui::SequentialLayout* modelContainer;

std::string search;
std::string brand;
int tagId = -1;
std::vector<int> tagIds = {};

ModuleBrowser() {
sidebar = new BrowserSidebar;
sidebar->box.size.x = 200;
addChild(sidebar);
float margin = 10;

// Header
headerLayout = new ui::SequentialLayout;
headerLayout->box.pos = math::Vec(0, 0);
headerLayout->box.size.y = BND_WIDGET_HEIGHT + 2 * margin;
headerLayout->margin = math::Vec(margin, margin);
headerLayout->spacing = math::Vec(margin, margin);
addChild(headerLayout);

searchField = new BrowserSearchField;
searchField->box.size.x = 150;
searchField->placeholder = "Search modules";
headerLayout->addChild(searchField);

brandButton = new BrandButton;
brandButton->box.size.x = 150;
headerLayout->addChild(brandButton);

tagButton = new TagButton;
tagButton->box.size.x = 150;
headerLayout->addChild(tagButton);

modelLabel = new ui::Label;
// modelLabel->fontSize = 16;
// modelLabel->box.size.x = 400;
addChild(modelLabel);
clearButton = new ClearButton;
clearButton->box.size.x = 100;
clearButton->text = "Reset filters";
headerLayout->addChild(clearButton);

widget::Widget* spacer1 = new widget::Widget;
spacer1->box.size.x = 20;
headerLayout->addChild(spacer1);

ZoomSlider* zoomSlider = new ZoomSlider;
zoomSlider->box.size.x = 150;
headerLayout->addChild(zoomSlider);

SortButton* sortButton = new SortButton;
sortButton->box.size.x = 150;
sortButton->text = "Sort: Most used";
headerLayout->addChild(sortButton);

UrlButton* libraryButton = new UrlButton;
libraryButton->box.size.x = 150;
libraryButton->text = "Browse VCV Library";
libraryButton->url = "https://library.vcvrack.com/";
headerLayout->addChild(libraryButton);

// Model container
modelScroll = new ui::ScrollWidget;
modelScroll->box.pos.y = BND_WIDGET_HEIGHT;
addChild(modelScroll);

modelMargin = new ui::MarginLayout;
modelMargin->margin = math::Vec(10, 10);
modelMargin->margin = math::Vec(margin, 0);
modelScroll->container->addChild(modelMargin);

modelContainer = new ui::SequentialLayout;
modelContainer->spacing = math::Vec(10, 10);
modelContainer->spacing = math::Vec(margin, margin);
modelMargin->addChild(modelContainer);

resetModelBoxes();
@@ -442,11 +485,9 @@ struct ModuleBrowser : widget::OpaqueWidget {
void step() override {
box = parent->box.zeroPos().grow(math::Vec(-40, -40));

sidebar->box.size.y = box.size.y;

modelLabel->box.pos = sidebar->box.getTopRight().plus(math::Vec(5, 5));
headerLayout->box.size.x = box.size.x;

modelScroll->box.pos = sidebar->box.getTopRight().plus(math::Vec(0, 30));
modelScroll->box.pos = headerLayout->box.getBottomLeft();
modelScroll->box.size = box.size.minus(modelScroll->box.pos);
modelMargin->box.size.x = modelScroll->box.size.x;
modelMargin->box.size.y = modelContainer->getChildrenBoundingBox().size.y + 2 * modelMargin->margin.y;
@@ -463,7 +504,7 @@ struct ModuleBrowser : widget::OpaqueWidget {
// Reset scroll position
modelScroll->offset = math::Vec();

auto isModelVisible = [&](plugin::Model* model, const std::string& brand, int tagId) -> bool {
auto isModelVisible = [&](plugin::Model* model, const std::string& brand, std::vector<int> tagIds) -> bool {
// Filter brand
if (brand != "") {
if (model->plugin->brand != brand)
@@ -471,7 +512,7 @@ struct ModuleBrowser : widget::OpaqueWidget {
}

// Filter tag
if (tagId >= 0) {
for (int tagId : tagIds) {
auto it = std::find(model->tags.begin(), model->tags.end(), tagId);
if (it == model->tags.end())
return false;
@@ -484,7 +525,7 @@ struct ModuleBrowser : widget::OpaqueWidget {
for (Widget* w : modelContainer->children) {
ModelBox* m = dynamic_cast<ModelBox*>(w);
assert(m);
m->visible = isModelVisible(m->model, brand, tagId);
m->visible = isModelVisible(m->model, brand, tagIds);
}

std::map<plugin::Model*, float> prefilteredModelScores;
@@ -537,91 +578,60 @@ 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, int tagId) -> bool {
auto hasVisibleModel = [&](const std::string& brand, std::vector<int> tagIds) -> bool {
for (auto& pair : prefilteredModelScores) {
plugin::Model* model = pair.first;
if (isModelVisible(model, brand, tagId))
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<BrandItem*>(w);
assert(item);
item->disabled = !hasVisibleModel(item->text, tagId);
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<TagItem*>(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++;
}
modelLabel->text = string::f("Modules (%d) Click and drag a module to place it in the rack.", modelsLen);
// // Enable brand and tag items that are available in visible ModelBoxes
// int brandsLen = 0;
// for (Widget* w : sidebar->brandList->children) {
// BrandItem* item = dynamic_cast<BrandItem*>(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<TagItem*>(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() {
search = "";
sidebar->searchField->setText("");
brand = "";
tagId = -1;
searchField->setText("");
brand = "VCV";
tagIds = {};
refresh();
}

void onShow(const event::Show& e) override {
refresh();
OpaqueWidget::onShow(e);
}
};


// Implementations to resolve dependencies


inline void BrandItem::onAction(const event::Action& e) {
ModuleBrowser* browser = getAncestorOfType<ModuleBrowser>();
if (browser->brand == text)
browser->brand = "";
else
browser->brand = text;
browser->refresh();
}

inline void BrandItem::step() {
MenuItem::step();
ModuleBrowser* browser = getAncestorOfType<ModuleBrowser>();
active = (browser->brand == text);
}

inline void TagItem::onAction(const event::Action& e) {
ModuleBrowser* browser = getAncestorOfType<ModuleBrowser>();
if (browser->tagId == tagId)
browser->tagId = -1;
else
browser->tagId = tagId;
browser->refresh();
}

inline void TagItem::step() {
MenuItem::step();
inline void ClearButton::onAction(const event::Action& e) {
ModuleBrowser* browser = getAncestorOfType<ModuleBrowser>();
active = (browser->tagId == tagId);
browser->clear();
}

inline void BrowserSearchField::onSelectKey(const event::SelectKey& e) {
@@ -631,6 +641,7 @@ inline void BrowserSearchField::onSelectKey(const event::SelectKey& e) {
overlay->hide();
e.consume(this);
}
// Backspace when the field is empty to clear filters.
if (e.key == GLFW_KEY_BACKSPACE) {
if (text == "") {
ModuleBrowser* browser = getAncestorOfType<ModuleBrowser>();
@@ -655,7 +666,7 @@ inline void BrowserSearchField::onAction(const event::Action& e) {
ModelBox* mb = NULL;
ModuleBrowser* browser = getAncestorOfType<ModuleBrowser>();
for (Widget* w : browser->modelContainer->children) {
if (w->visible) {
if (w->isVisible()) {
mb = dynamic_cast<ModelBox*>(w);
break;
}
@@ -666,19 +677,74 @@ inline void BrowserSearchField::onAction(const event::Action& e) {
}
}

inline void ClearButton::onAction(const event::Action& e) {
inline void BrandItem::onAction(const event::Action& e) {
ModuleBrowser* browser = getAncestorOfType<ModuleBrowser>();
browser->clear();
if (browser->brand == brand)
browser->brand = "";
else
browser->brand = brand;
browser->refresh();
}

inline void BrandItem::step() {
ModuleBrowser* browser = getAncestorOfType<ModuleBrowser>();
rightText = CHECKMARK(browser->brand == brand);
MenuItem::step();
}

inline void BrandButton::step() {
ModuleBrowser* browser = getAncestorOfType<ModuleBrowser>();
text = "Brand";
if (!browser->brand.empty()) {
text += ": ";
text += browser->brand;
}
ChoiceButton::step();
}

inline void TagItem::onAction(const event::Action& e) {
ModuleBrowser* browser = getAncestorOfType<ModuleBrowser>();
auto it = std::find(browser->tagIds.begin(), browser->tagIds.end(), tagId);
bool isSelected = (it != browser->tagIds.end());
if (isSelected)
browser->tagIds = {};
else
browser->tagIds = {tagId};
browser->refresh();
}

inline void TagItem::step() {
ModuleBrowser* browser = getAncestorOfType<ModuleBrowser>();
auto it = std::find(browser->tagIds.begin(), browser->tagIds.end(), tagId);
bool isSelected = (it != browser->tagIds.end());
rightText = CHECKMARK(isSelected);
MenuItem::step();
}

inline void TagButton::step() {
ModuleBrowser* browser = getAncestorOfType<ModuleBrowser>();
text = "Tags";
if (!browser->tagIds.empty()) {
text += ": ";
bool firstTag = true;
for (int tagId : browser->tagIds) {
if (!firstTag)
text += ", ";
text += tag::getTag(tagId);
firstTag = false;
}
}
ChoiceButton::step();
}


// Global functions
} // namespace moduleBrowser


widget::Widget* moduleBrowserCreate() {
BrowserOverlay* overlay = new BrowserOverlay;
moduleBrowser::BrowserOverlay* overlay = new moduleBrowser::BrowserOverlay;

ModuleBrowser* browser = new ModuleBrowser;
moduleBrowser::ModuleBrowser* browser = new moduleBrowser::ModuleBrowser;
overlay->addChild(browser);

return overlay;


+ 1
- 1
src/engine/Engine.cpp View File

@@ -599,7 +599,7 @@ void Engine::step(int frames) {
double endTime = system::getTime();
float duration = endTime - internal->stepTime;
float stepDuration = internal->stepFrames * internal->sampleTime;
DEBUG("%d %f / %f = %f%%", internal->stepFrames, duration, stepDuration, duration / stepDuration * 100.f);
// DEBUG("%d %f / %f = %f%%", internal->stepFrames, duration, stepDuration, duration / stepDuration * 100.f);
}




+ 1
- 1
src/ui/TextField.cpp View File

@@ -23,7 +23,7 @@ void TextField::draw(const DrawArgs& args) {
int end = std::max(cursor, selection);
bndTextField(args.vg, 0.0, 0.0, box.size.x, box.size.y, BND_CORNER_NONE, state, -1, text.c_str(), begin, end);
// Draw placeholder text
if (text.empty() && state != BND_ACTIVE) {
if (text.empty()) {
bndIconLabelCaret(args.vg, 0.0, 0.0, box.size.x, box.size.y, -1, bndGetTheme()->textFieldTheme.itemColor, 13, placeholder.c_str(), bndGetTheme()->textFieldTheme.itemColor, 0, -1);
}



Loading…
Cancel
Save