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.

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