You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1029 lines
25KB

  1. #include <set>
  2. #include <algorithm>
  3. #include <thread>
  4. #include <app/Browser.hpp>
  5. #include <widget/OpaqueWidget.hpp>
  6. #include <widget/TransparentWidget.hpp>
  7. #include <widget/ZoomWidget.hpp>
  8. #include <ui/MenuOverlay.hpp>
  9. #include <ui/ScrollWidget.hpp>
  10. #include <ui/SequentialLayout.hpp>
  11. #include <ui/Label.hpp>
  12. #include <ui/Slider.hpp>
  13. #include <ui/TextField.hpp>
  14. #include <ui/MenuItem.hpp>
  15. #include <ui/MenuSeparator.hpp>
  16. #include <ui/Button.hpp>
  17. #include <ui/ChoiceButton.hpp>
  18. #include <ui/RadioButton.hpp>
  19. #include <ui/OptionButton.hpp>
  20. #include <ui/Tooltip.hpp>
  21. #include <app/ModuleWidget.hpp>
  22. #include <app/Scene.hpp>
  23. #include <plugin.hpp>
  24. #include <context.hpp>
  25. #include <engine/Engine.hpp>
  26. #include <plugin/Model.hpp>
  27. #include <string.hpp>
  28. #include <history.hpp>
  29. #include <settings.hpp>
  30. #include <system.hpp>
  31. #include <tag.hpp>
  32. #include <helpers.hpp>
  33. #include <FuzzySearchDatabase.hpp>
  34. #include <componentlibrary.hpp>
  35. namespace rack {
  36. namespace app {
  37. namespace browser {
  38. static fuzzysearch::Database<plugin::Model*> modelDb;
  39. static void modelDbInit() {
  40. modelDb = fuzzysearch::Database<plugin::Model*>();
  41. modelDb.setWeights({1.f, 1.f, 0.1f, 1.f, 0.5f, 0.5f});
  42. modelDb.setThreshold(0.25f);
  43. // Iterate plugins
  44. for (plugin::Plugin* plugin : plugin::plugins) {
  45. // Iterate model in plugin
  46. for (plugin::Model* model : plugin->models) {
  47. // Get search fields for model
  48. std::string tagStr;
  49. for (int tagId : model->tagIds) {
  50. // Add all aliases of a tag
  51. for (const std::string& tagAlias : tag::tagAliases[tagId]) {
  52. tagStr += tagAlias;
  53. tagStr += ", ";
  54. }
  55. }
  56. std::vector<std::string> fields = {
  57. model->plugin->brand,
  58. model->plugin->name,
  59. model->plugin->description,
  60. model->name,
  61. model->description,
  62. tagStr,
  63. };
  64. // DEBUG("%s; %s; %s; %s; %s; %s", fields[0].c_str(), fields[1].c_str(), fields[2].c_str(), fields[3].c_str(), fields[4].c_str(), fields[5].c_str());
  65. modelDb.addEntry(model, fields);
  66. }
  67. }
  68. }
  69. static ModuleWidget* chooseModel(plugin::Model* model) {
  70. // Record usage
  71. settings::ModuleInfo& mi = settings::moduleInfos[model->plugin->slug][model->slug];
  72. mi.added++;
  73. mi.lastAdded = system::getUnixTime();
  74. // Create Module and ModuleWidget
  75. engine::Module* module = model->createModule();
  76. APP->engine->addModule(module);
  77. ModuleWidget* moduleWidget = model->createModuleWidget(module);
  78. APP->scene->rack->addModuleAtMouse(moduleWidget);
  79. // Load template preset
  80. moduleWidget->loadTemplate();
  81. // history::ModuleAdd
  82. history::ModuleAdd* h = new history::ModuleAdd;
  83. h->name = "create module";
  84. // This serializes the module so redoing returns to the current state.
  85. h->setModule(moduleWidget);
  86. APP->history->push(h);
  87. // Hide Module Browser
  88. APP->scene->browser->hide();
  89. return moduleWidget;
  90. }
  91. // Widgets
  92. struct Browser;
  93. struct BrowserOverlay : ui::MenuOverlay {
  94. void step() override {
  95. // Only step if visible, since there are potentially thousands of descendants that don't need to be stepped.
  96. if (isVisible())
  97. MenuOverlay::step();
  98. }
  99. void onAction(const ActionEvent& e) override {
  100. // Hide instead of requestDelete()
  101. hide();
  102. }
  103. };
  104. struct ModuleWidgetContainer : widget::Widget {
  105. void draw(const DrawArgs& args) override {
  106. Widget::draw(args);
  107. Widget::drawLayer(args, 1);
  108. }
  109. };
  110. struct ModelBox : widget::OpaqueWidget {
  111. plugin::Model* model;
  112. ui::Tooltip* tooltip = NULL;
  113. // Lazily created widgets
  114. widget::Widget* previewWidget = NULL;
  115. widget::ZoomWidget* zoomWidget = NULL;
  116. widget::FramebufferWidget* fb = NULL;
  117. ModuleWidgetContainer* mwc = NULL;
  118. ModuleWidget* moduleWidget = NULL;
  119. ModelBox() {
  120. updateZoom();
  121. }
  122. void setModel(plugin::Model* model) {
  123. this->model = model;
  124. }
  125. void updateZoom() {
  126. float zoom = std::pow(2.f, settings::browserZoom);
  127. if (previewWidget) {
  128. fb->setDirty();
  129. zoomWidget->setZoom(zoom);
  130. box.size.x = moduleWidget->box.size.x * zoom;
  131. }
  132. else {
  133. // Approximate size as 12HP before we know the actual size.
  134. // We need a nonzero size, otherwise too many ModelBoxes will lazily render in the same frame.
  135. box.size.x = 12 * RACK_GRID_WIDTH * zoom;
  136. }
  137. box.size.y = RACK_GRID_HEIGHT * zoom;
  138. box.size = box.size.ceil();
  139. }
  140. void createPreview() {
  141. if (previewWidget)
  142. return;
  143. previewWidget = new widget::TransparentWidget;
  144. addChild(previewWidget);
  145. zoomWidget = new widget::ZoomWidget;
  146. previewWidget->addChild(zoomWidget);
  147. fb = new widget::FramebufferWidget;
  148. if (APP->window->pixelRatio < 2.0) {
  149. // Small details draw poorly at low DPI, so oversample when drawing to the framebuffer
  150. fb->oversample = 2.0;
  151. }
  152. zoomWidget->addChild(fb);
  153. mwc = new ModuleWidgetContainer;
  154. fb->addChild(mwc);
  155. moduleWidget = model->createModuleWidget(NULL);
  156. mwc->addChild(moduleWidget);
  157. mwc->box.size = moduleWidget->box.size;
  158. updateZoom();
  159. }
  160. void draw(const DrawArgs& args) override {
  161. // Lazily create preview when drawn
  162. createPreview();
  163. // Draw shadow
  164. nvgBeginPath(args.vg);
  165. float r = 10; // Blur radius
  166. float c = 5; // Corner radius
  167. math::Rect shadowBox = box.zeroPos().grow(math::Vec(r, r));
  168. nvgRect(args.vg, RECT_ARGS(shadowBox));
  169. NVGcolor shadowColor = nvgRGBAf(0, 0, 0, 0.5);
  170. nvgFillPaint(args.vg, nvgBoxGradient(args.vg, 0, 0, box.size.x, box.size.y, c, r, shadowColor, color::BLACK_TRANSPARENT));
  171. nvgFill(args.vg);
  172. // To avoid blinding the user when rack brightness is low, draw framebuffer with the same brightness.
  173. float b = math::clamp(settings::rackBrightness + 0.2f, 0.f, 1.f);
  174. nvgGlobalTint(args.vg, nvgRGBAf(b, b, b, 1));
  175. OpaqueWidget::draw(args);
  176. // Draw favorite border
  177. const settings::ModuleInfo* mi = settings::getModuleInfo(model->plugin->slug, model->slug);
  178. if (mi && mi->favorite) {
  179. nvgBeginPath(args.vg);
  180. math::Rect borderBox = box.zeroPos().shrink(math::Vec(1, 1));
  181. nvgRect(args.vg, RECT_ARGS(borderBox));
  182. nvgStrokeWidth(args.vg, 2);
  183. nvgStrokeColor(args.vg, componentlibrary::SCHEME_YELLOW);
  184. nvgStroke(args.vg);
  185. }
  186. }
  187. void step() override {
  188. OpaqueWidget::step();
  189. }
  190. void setTooltip(ui::Tooltip* tooltip) {
  191. if (this->tooltip) {
  192. this->tooltip->requestDelete();
  193. this->tooltip = NULL;
  194. }
  195. if (tooltip) {
  196. APP->scene->addChild(tooltip);
  197. this->tooltip = tooltip;
  198. }
  199. }
  200. void onButton(const ButtonEvent& e) override {
  201. if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT && (e.mods & RACK_MOD_MASK) == 0) {
  202. ModuleWidget* mw = chooseModel(model);
  203. // Pretend the moduleWidget was clicked so it can be dragged in the RackWidget
  204. e.consume(mw);
  205. // Set the drag position at the center of the module
  206. mw->dragOffset() = mw->box.size.div(2);
  207. // Disable dragging temporarily until the mouse has moved a bit.
  208. mw->dragEnabled() = false;
  209. }
  210. // Toggle favorite
  211. if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  212. model->setFavorite(!model->isFavorite());
  213. e.consume(this);
  214. }
  215. // Open context menu on right-click
  216. if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_RIGHT) {
  217. createContextMenu();
  218. e.consume(this);
  219. }
  220. }
  221. void onHoverKey(const HoverKeyEvent& e) override {
  222. if (e.action == GLFW_PRESS || e.action == GLFW_REPEAT) {
  223. if (e.key == GLFW_KEY_F1 && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  224. system::openBrowser(model->getManualUrl());
  225. e.consume(this);
  226. }
  227. }
  228. if (e.isConsumed())
  229. return;
  230. OpaqueWidget::onHoverKey(e);
  231. }
  232. ui::Tooltip* createTooltip() {
  233. std::string text;
  234. text += model->name;
  235. text += "\n";
  236. text += model->plugin->brand;
  237. // Description
  238. if (model->description != "") {
  239. text += "\n" + model->description;
  240. }
  241. // Tags
  242. text += "\n\nTags: ";
  243. std::vector<std::string> tags;
  244. for (int tagId : model->tagIds) {
  245. tags.push_back(tag::getTag(tagId));
  246. }
  247. text += string::join(tags, ", ");
  248. ui::Tooltip* tooltip = new ui::Tooltip;
  249. tooltip->text = text;
  250. return tooltip;
  251. }
  252. void onEnter(const EnterEvent& e) override {
  253. setTooltip(createTooltip());
  254. }
  255. void onLeave(const LeaveEvent& e) override {
  256. setTooltip(NULL);
  257. }
  258. void onHide(const HideEvent& e) override {
  259. // Hide tooltip
  260. setTooltip(NULL);
  261. OpaqueWidget::onHide(e);
  262. }
  263. void createContextMenu() {
  264. ui::Menu* menu = createMenu();
  265. menu->addChild(createMenuLabel(model->name));
  266. menu->addChild(createMenuLabel(model->plugin->brand));
  267. model->appendContextMenu(menu, true);
  268. }
  269. };
  270. struct BrowserSearchField : ui::TextField {
  271. Browser* browser;
  272. void step() override {
  273. // Steal focus when step is called
  274. APP->event->setSelectedWidget(this);
  275. TextField::step();
  276. }
  277. void onSelectKey(const SelectKeyEvent& e) override;
  278. void onChange(const ChangeEvent& e) override;
  279. void onAction(const ActionEvent& e) override;
  280. void onHide(const HideEvent& e) override {
  281. APP->event->setSelectedWidget(NULL);
  282. ui::TextField::onHide(e);
  283. }
  284. void onShow(const ShowEvent& e) override {
  285. selectAll();
  286. TextField::onShow(e);
  287. }
  288. };
  289. struct FavoriteQuantity : Quantity {
  290. Browser* browser;
  291. void setValue(float value) override;
  292. float getValue() override;
  293. };
  294. struct ClearButton : ui::Button {
  295. Browser* browser;
  296. void onAction(const ActionEvent& e) override;
  297. };
  298. struct BrandItem : ui::MenuItem {
  299. Browser* browser;
  300. std::string brand;
  301. void onAction(const ActionEvent& e) override;
  302. void step() override;
  303. };
  304. struct BrandButton : ui::ChoiceButton {
  305. Browser* browser;
  306. void onAction(const ActionEvent& e) override;
  307. void step() override;
  308. };
  309. struct TagItem : ui::MenuItem {
  310. Browser* browser;
  311. int tagId;
  312. void onAction(const ActionEvent& e) override;
  313. void step() override;
  314. };
  315. struct TagButton : ui::ChoiceButton {
  316. Browser* browser;
  317. void onAction(const ActionEvent& e) override;
  318. void step() override;
  319. };
  320. static const std::string sortNames[] = {
  321. "Last updated",
  322. "Last used",
  323. "Most used",
  324. "Brand",
  325. "Module name",
  326. "Random",
  327. };
  328. struct SortButton : ui::ChoiceButton {
  329. Browser* browser;
  330. void onAction(const ActionEvent& e) override;
  331. void step() override {
  332. text = "Sort: ";
  333. text += sortNames[settings::browserSort];
  334. ChoiceButton::step();
  335. }
  336. };
  337. struct ZoomButton : ui::ChoiceButton {
  338. Browser* browser;
  339. void onAction(const ActionEvent& e) override;
  340. void step() override {
  341. text = "Zoom: ";
  342. text += string::f("%.0f%%", std::pow(2.f, settings::browserZoom) * 100.f);
  343. ChoiceButton::step();
  344. }
  345. };
  346. struct UrlButton : ui::Button {
  347. std::string url;
  348. void onAction(const ActionEvent& e) override {
  349. system::openBrowser(url);
  350. }
  351. };
  352. struct Browser : widget::OpaqueWidget {
  353. ui::SequentialLayout* headerLayout;
  354. BrowserSearchField* searchField;
  355. BrandButton* brandButton;
  356. TagButton* tagButton;
  357. FavoriteQuantity* favoriteQuantity;
  358. ui::OptionButton* favoriteButton;
  359. ClearButton* clearButton;
  360. ui::Label* countLabel;
  361. ui::ScrollWidget* modelScroll;
  362. widget::Widget* modelMargin;
  363. ui::SequentialLayout* modelContainer;
  364. std::string search;
  365. std::string brand;
  366. std::set<int> tagIds = {};
  367. bool favorite = false;
  368. // Caches and temporary state
  369. std::map<plugin::Model*, float> prefilteredModelScores;
  370. std::map<plugin::Model*, int> modelOrders;
  371. Browser() {
  372. const float margin = 10;
  373. // Header
  374. headerLayout = new ui::SequentialLayout;
  375. headerLayout->box.pos = math::Vec(0, 0);
  376. headerLayout->box.size.y = 0;
  377. headerLayout->margin = math::Vec(margin, margin);
  378. headerLayout->spacing = math::Vec(margin, margin);
  379. addChild(headerLayout);
  380. searchField = new BrowserSearchField;
  381. searchField->box.size.x = 150;
  382. searchField->placeholder = "Search modules";
  383. searchField->browser = this;
  384. headerLayout->addChild(searchField);
  385. brandButton = new BrandButton;
  386. brandButton->box.size.x = 150;
  387. brandButton->browser = this;
  388. headerLayout->addChild(brandButton);
  389. tagButton = new TagButton;
  390. tagButton->box.size.x = 150;
  391. tagButton->browser = this;
  392. headerLayout->addChild(tagButton);
  393. favoriteQuantity = new FavoriteQuantity;
  394. favoriteQuantity->browser = this;
  395. favoriteButton = new ui::OptionButton;
  396. favoriteButton->quantity = favoriteQuantity;
  397. favoriteButton->text = "Favorites";
  398. favoriteButton->box.size.x = 70;
  399. headerLayout->addChild(favoriteButton);
  400. clearButton = new ClearButton;
  401. clearButton->box.size.x = 100;
  402. clearButton->text = "Reset filters";
  403. clearButton->browser = this;
  404. headerLayout->addChild(clearButton);
  405. countLabel = new ui::Label;
  406. countLabel->box.size.x = 100;
  407. countLabel->color.a = 0.5;
  408. headerLayout->addChild(countLabel);
  409. SortButton* sortButton = new SortButton;
  410. sortButton->box.size.x = 150;
  411. sortButton->browser = this;
  412. headerLayout->addChild(sortButton);
  413. ZoomButton* zoomButton = new ZoomButton;
  414. zoomButton->box.size.x = 100;
  415. zoomButton->browser = this;
  416. headerLayout->addChild(zoomButton);
  417. UrlButton* libraryButton = new UrlButton;
  418. libraryButton->box.size.x = 150;
  419. libraryButton->text = "Browse VCV Library";
  420. libraryButton->url = "https://library.vcvrack.com/";
  421. headerLayout->addChild(libraryButton);
  422. // Model container
  423. modelScroll = new ui::ScrollWidget;
  424. modelScroll->box.pos.y = BND_WIDGET_HEIGHT;
  425. addChild(modelScroll);
  426. modelMargin = new widget::Widget;
  427. modelScroll->container->addChild(modelMargin);
  428. modelContainer = new ui::SequentialLayout;
  429. modelContainer->margin = math::Vec(margin, 0);
  430. modelContainer->spacing = math::Vec(margin, margin);
  431. modelMargin->addChild(modelContainer);
  432. resetModelBoxes();
  433. clear();
  434. }
  435. ~Browser() {
  436. delete favoriteQuantity;
  437. }
  438. void resetModelBoxes() {
  439. modelContainer->clearChildren();
  440. modelOrders.clear();
  441. // Iterate plugins
  442. // for (int i = 0; i < 100; i++)
  443. for (plugin::Plugin* plugin : plugin::plugins) {
  444. // Iterate models in plugin
  445. int modelIndex = 0;
  446. for (plugin::Model* model : plugin->models) {
  447. // Create ModelBox
  448. ModelBox* modelBox = new ModelBox;
  449. modelBox->setModel(model);
  450. modelContainer->addChild(modelBox);
  451. modelOrders[model] = modelIndex;
  452. modelIndex++;
  453. }
  454. }
  455. }
  456. void updateZoom() {
  457. modelScroll->offset = math::Vec();
  458. for (Widget* w : modelContainer->children) {
  459. ModelBox* mb = reinterpret_cast<ModelBox*>(w);
  460. assert(mb);
  461. mb->updateZoom();
  462. }
  463. }
  464. void step() override {
  465. box = parent->box.zeroPos().grow(math::Vec(-40, -40));
  466. headerLayout->box.size.x = box.size.x;
  467. const float margin = 10;
  468. modelScroll->box.pos = headerLayout->box.getBottomLeft();
  469. modelScroll->box.size = box.size.minus(modelScroll->box.pos);
  470. modelMargin->box.size.x = modelScroll->box.size.x;
  471. modelMargin->box.size.y = modelContainer->box.size.y + margin;
  472. modelContainer->box.size.x = modelMargin->box.size.x - margin;
  473. OpaqueWidget::step();
  474. }
  475. void draw(const DrawArgs& args) override {
  476. bndMenuBackground(args.vg, 0.0, 0.0, box.size.x, box.size.y, 0);
  477. Widget::draw(args);
  478. }
  479. bool isModelVisible(plugin::Model* model, const std::string& brand, std::set<int> tagIds, bool favorite) {
  480. // Filter hidden
  481. if (model->hidden)
  482. return false;
  483. // Filter moduleInfo setting if enabled is false
  484. settings::ModuleInfo* mi = settings::getModuleInfo(model->plugin->slug, model->slug);
  485. if (mi && !mi->enabled)
  486. return false;
  487. // Filter if not whitelisted by library
  488. if (!settings::isModuleWhitelisted(model->plugin->slug, model->slug))
  489. return false;
  490. // Filter favorites
  491. if (favorite) {
  492. if (!mi || !mi->favorite)
  493. return false;
  494. }
  495. // Filter brand
  496. if (!brand.empty()) {
  497. if (model->plugin->brand != brand)
  498. return false;
  499. }
  500. // Filter tag
  501. for (int tagId : tagIds) {
  502. auto it = std::find(model->tagIds.begin(), model->tagIds.end(), tagId);
  503. if (it == model->tagIds.end())
  504. return false;
  505. }
  506. return true;
  507. };
  508. // Determines if there is at least 1 visible Model with a given brand and tag
  509. bool hasVisibleModel(const std::string& brand, std::set<int> tagIds, bool favorite) {
  510. for (const auto& pair : prefilteredModelScores) {
  511. plugin::Model* model = pair.first;
  512. if (isModelVisible(model, brand, tagIds, favorite))
  513. return true;
  514. }
  515. return false;
  516. };
  517. template <typename F>
  518. void sortModels(F f) {
  519. modelContainer->children.sort([&](Widget* w1, Widget* w2) {
  520. ModelBox* m1 = reinterpret_cast<ModelBox*>(w1);
  521. ModelBox* m2 = reinterpret_cast<ModelBox*>(w2);
  522. return f(m1) < f(m2);
  523. });
  524. }
  525. void refresh() {
  526. // Reset scroll position
  527. modelScroll->offset = math::Vec();
  528. prefilteredModelScores.clear();
  529. // Filter ModelBoxes by brand and tag
  530. for (Widget* w : modelContainer->children) {
  531. ModelBox* m = reinterpret_cast<ModelBox*>(w);
  532. m->setVisible(isModelVisible(m->model, brand, tagIds, favorite));
  533. }
  534. // Filter and sort by search results
  535. if (search.empty()) {
  536. // Add all models to prefilteredModelScores with scores of 1
  537. for (Widget* w : modelContainer->children) {
  538. ModelBox* m = reinterpret_cast<ModelBox*>(w);
  539. prefilteredModelScores[m->model] = 1.f;
  540. }
  541. // Sort ModelBoxes
  542. if (settings::browserSort == settings::BROWSER_SORT_UPDATED) {
  543. sortModels([this](ModelBox* m) {
  544. plugin::Plugin* p = m->model->plugin;
  545. int modelOrder = get(modelOrders, m->model, 0);
  546. return std::make_tuple(-p->modifiedTimestamp, p->brand, p->name, modelOrder);
  547. });
  548. }
  549. else if (settings::browserSort == settings::BROWSER_SORT_LAST_USED) {
  550. sortModels([this](ModelBox* m) {
  551. plugin::Plugin* p = m->model->plugin;
  552. const settings::ModuleInfo* mi = settings::getModuleInfo(p->slug, m->model->slug);
  553. double lastAdded = mi ? mi->lastAdded : -INFINITY;
  554. int modelOrder = get(modelOrders, m->model, 0);
  555. return std::make_tuple(-lastAdded, -p->modifiedTimestamp, p->brand, p->name, modelOrder);
  556. });
  557. }
  558. else if (settings::browserSort == settings::BROWSER_SORT_MOST_USED) {
  559. sortModels([this](ModelBox* m) {
  560. plugin::Plugin* p = m->model->plugin;
  561. const settings::ModuleInfo* mi = settings::getModuleInfo(p->slug, m->model->slug);
  562. int added = mi ? mi->added : 0;
  563. double lastAdded = mi ? mi->lastAdded : -INFINITY;
  564. int modelOrder = get(modelOrders, m->model, 0);
  565. return std::make_tuple(-added, -lastAdded, -p->modifiedTimestamp, p->brand, p->name, modelOrder);
  566. });
  567. }
  568. else if (settings::browserSort == settings::BROWSER_SORT_BRAND) {
  569. sortModels([this](ModelBox* m) {
  570. plugin::Plugin* p = m->model->plugin;
  571. int modelOrder = get(modelOrders, m->model, 0);
  572. return std::make_tuple(p->brand, p->name, modelOrder);
  573. });
  574. }
  575. else if (settings::browserSort == settings::BROWSER_SORT_NAME) {
  576. sortModels([](ModelBox* m) {
  577. plugin::Plugin* p = m->model->plugin;
  578. return std::make_tuple(m->model->name, p->brand);
  579. });
  580. }
  581. else if (settings::browserSort == settings::BROWSER_SORT_RANDOM) {
  582. std::map<ModelBox*, uint64_t> randomOrder;
  583. for (Widget* w : modelContainer->children) {
  584. ModelBox* m = reinterpret_cast<ModelBox*>(w);
  585. randomOrder[m] = random::u64();
  586. }
  587. sortModels([&](ModelBox* m) {
  588. return get(randomOrder, m, 0);
  589. });
  590. }
  591. }
  592. else {
  593. // Score results against search query
  594. auto results = modelDb.search(search);
  595. // DEBUG("=============");
  596. for (auto& result : results) {
  597. prefilteredModelScores[result.key] = result.score;
  598. // DEBUG("%s %s\t\t%f", result.key->plugin->slug.c_str(), result.key->slug.c_str(), result.score);
  599. }
  600. // Sort by score
  601. sortModels([&](ModelBox* m) {
  602. return -get(prefilteredModelScores, m->model, 0.f);
  603. });
  604. // Filter by whether the score is above the threshold
  605. for (Widget* w : modelContainer->children) {
  606. ModelBox* m = reinterpret_cast<ModelBox*>(w);
  607. assert(m);
  608. if (m->isVisible()) {
  609. if (prefilteredModelScores.find(m->model) == prefilteredModelScores.end())
  610. m->hide();
  611. }
  612. }
  613. }
  614. // Count visible modules
  615. int count = 0;
  616. for (Widget* w : modelContainer->children) {
  617. if (w->isVisible())
  618. count++;
  619. }
  620. countLabel->text = string::f("%d %s", count, (count == 1) ? "module" : "modules");
  621. }
  622. void clear() {
  623. search = "";
  624. searchField->setText("");
  625. brand = "";
  626. tagIds = {};
  627. favorite = false;
  628. refresh();
  629. }
  630. void onShow(const ShowEvent& e) override {
  631. refresh();
  632. OpaqueWidget::onShow(e);
  633. }
  634. void onButton(const ButtonEvent& e) override {
  635. Widget::onButton(e);
  636. e.stopPropagating();
  637. // Consume all mouse buttons
  638. if (!e.isConsumed())
  639. e.consume(this);
  640. }
  641. };
  642. // Implementations to resolve dependencies
  643. inline void FavoriteQuantity::setValue(float value) {
  644. browser->favorite = value;
  645. browser->refresh();
  646. }
  647. inline float FavoriteQuantity::getValue() {
  648. return browser->favorite;
  649. }
  650. inline void ClearButton::onAction(const ActionEvent& e) {
  651. browser->clear();
  652. }
  653. inline void BrowserSearchField::onSelectKey(const SelectKeyEvent& e) {
  654. if (e.action == GLFW_PRESS || e.action == GLFW_REPEAT) {
  655. // Backspace when the field is empty to clear filters.
  656. if (e.key == GLFW_KEY_BACKSPACE) {
  657. if (text == "") {
  658. browser->clear();
  659. e.consume(this);
  660. }
  661. }
  662. }
  663. if (!e.getTarget())
  664. ui::TextField::onSelectKey(e);
  665. }
  666. inline void BrowserSearchField::onChange(const ChangeEvent& e) {
  667. browser->search = string::trim(text);
  668. browser->refresh();
  669. }
  670. inline void BrowserSearchField::onAction(const ActionEvent& e) {
  671. // Get first ModelBox
  672. ModelBox* mb = NULL;
  673. for (Widget* w : browser->modelContainer->children) {
  674. if (w->isVisible()) {
  675. mb = reinterpret_cast<ModelBox*>(w);
  676. break;
  677. }
  678. }
  679. if (mb) {
  680. chooseModel(mb->model);
  681. }
  682. }
  683. inline void BrandItem::onAction(const ActionEvent& e) {
  684. if (browser->brand == brand)
  685. browser->brand = "";
  686. else
  687. browser->brand = brand;
  688. browser->refresh();
  689. }
  690. inline void BrandItem::step() {
  691. rightText = CHECKMARK(browser->brand == brand);
  692. MenuItem::step();
  693. }
  694. inline void BrandButton::onAction(const ActionEvent& e) {
  695. ui::Menu* menu = createMenu();
  696. menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y));
  697. menu->box.size.x = box.size.x;
  698. BrandItem* noneItem = new BrandItem;
  699. noneItem->text = "All brands";
  700. noneItem->brand = "";
  701. noneItem->browser = browser;
  702. menu->addChild(noneItem);
  703. menu->addChild(new ui::MenuSeparator);
  704. // Collect brands from all plugins
  705. std::set<std::string, string::CaseInsensitiveCompare> brands;
  706. for (plugin::Plugin* plugin : plugin::plugins) {
  707. brands.insert(plugin->brand);
  708. }
  709. for (const std::string& brand : brands) {
  710. BrandItem* brandItem = new BrandItem;
  711. brandItem->text = brand;
  712. brandItem->brand = brand;
  713. brandItem->browser = browser;
  714. brandItem->disabled = !browser->hasVisibleModel(brand, browser->tagIds, browser->favorite);
  715. menu->addChild(brandItem);
  716. }
  717. }
  718. inline void BrandButton::step() {
  719. text = "Brand";
  720. if (!browser->brand.empty()) {
  721. text += ": ";
  722. text += browser->brand;
  723. }
  724. text = string::ellipsize(text, 21);
  725. ChoiceButton::step();
  726. }
  727. inline void TagItem::onAction(const ActionEvent& e) {
  728. auto it = browser->tagIds.find(tagId);
  729. bool isSelected = (it != browser->tagIds.end());
  730. if (tagId >= 0) {
  731. // Specific tag
  732. if (!e.isConsumed()) {
  733. // Multi select
  734. if (isSelected)
  735. browser->tagIds.erase(tagId);
  736. else
  737. browser->tagIds.insert(tagId);
  738. e.unconsume();
  739. }
  740. else {
  741. // Single select
  742. if (isSelected)
  743. browser->tagIds = {};
  744. else {
  745. browser->tagIds = {tagId};
  746. }
  747. }
  748. }
  749. else {
  750. // All tags
  751. browser->tagIds = {};
  752. }
  753. browser->refresh();
  754. }
  755. inline void TagItem::step() {
  756. // TODO Disable tags with no modules
  757. if (tagId >= 0) {
  758. auto it = browser->tagIds.find(tagId);
  759. bool isSelected = (it != browser->tagIds.end());
  760. rightText = CHECKMARK(isSelected);
  761. }
  762. else {
  763. rightText = CHECKMARK(browser->tagIds.empty());
  764. }
  765. MenuItem::step();
  766. }
  767. inline void TagButton::onAction(const ActionEvent& e) {
  768. ui::Menu* menu = createMenu();
  769. menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y));
  770. menu->box.size.x = box.size.x;
  771. TagItem* noneItem = new TagItem;
  772. noneItem->text = "All tags";
  773. noneItem->tagId = -1;
  774. noneItem->browser = browser;
  775. menu->addChild(noneItem);
  776. menu->addChild(createMenuLabel(RACK_MOD_CTRL_NAME "+click to select multiple"));
  777. menu->addChild(new ui::MenuSeparator);
  778. for (int tagId = 0; tagId < (int) tag::tagAliases.size(); tagId++) {
  779. TagItem* tagItem = new TagItem;
  780. tagItem->text = tag::getTag(tagId);
  781. tagItem->tagId = tagId;
  782. tagItem->browser = browser;
  783. tagItem->disabled = !browser->hasVisibleModel(browser->brand, {tagId}, browser->favorite);
  784. menu->addChild(tagItem);
  785. }
  786. }
  787. inline void TagButton::step() {
  788. text = "Tags";
  789. if (!browser->tagIds.empty()) {
  790. text += ": ";
  791. bool firstTag = true;
  792. for (int tagId : browser->tagIds) {
  793. if (!firstTag)
  794. text += ", ";
  795. text += tag::getTag(tagId);
  796. firstTag = false;
  797. }
  798. }
  799. text = string::ellipsize(text, 21);
  800. ChoiceButton::step();
  801. }
  802. inline void SortButton::onAction(const ActionEvent& e) {
  803. ui::Menu* menu = createMenu();
  804. menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y));
  805. menu->box.size.x = box.size.x;
  806. for (int sortId = 0; sortId <= settings::BROWSER_SORT_RANDOM; sortId++) {
  807. menu->addChild(createCheckMenuItem(sortNames[sortId], "",
  808. [=]() {return settings::browserSort == sortId;},
  809. [=]() {
  810. settings::browserSort = (settings::BrowserSort) sortId;
  811. browser->refresh();
  812. }
  813. ));
  814. }
  815. }
  816. inline void ZoomButton::onAction(const ActionEvent& e) {
  817. ui::Menu* menu = createMenu();
  818. menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y));
  819. menu->box.size.x = box.size.x;
  820. for (float zoom = 1.f; zoom >= -2.f; zoom -= 0.5f) {
  821. menu->addChild(createCheckMenuItem(string::f("%.0f%%", std::pow(2.f, zoom) * 100.f), "",
  822. [=]() {return settings::browserZoom == zoom;},
  823. [=]() {
  824. if (zoom == settings::browserZoom)
  825. return;
  826. settings::browserZoom = zoom;
  827. browser->updateZoom();
  828. }
  829. ));
  830. }
  831. }
  832. } // namespace browser
  833. void browserInit() {
  834. browser::modelDbInit();
  835. }
  836. widget::Widget* browserCreate() {
  837. browser::BrowserOverlay* overlay = new browser::BrowserOverlay;
  838. overlay->bgColor = nvgRGBAf(0, 0, 0, 0.33);
  839. browser::Browser* browser = new browser::Browser;
  840. overlay->addChild(browser);
  841. return overlay;
  842. }
  843. } // namespace app
  844. } // namespace rack