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.

1026 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 && (e.mods & RACK_MOD_MASK) == 0) {
  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. // Toggle favorite
  195. if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  196. model->setFavorite(!model->isFavorite());
  197. e.consume(this);
  198. }
  199. // Open context menu on right-click
  200. if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_RIGHT) {
  201. createContextMenu();
  202. e.consume(this);
  203. }
  204. }
  205. void onHoverKey(const HoverKeyEvent& e) override {
  206. if (e.action == GLFW_PRESS || e.action == GLFW_REPEAT) {
  207. if (e.key == GLFW_KEY_F1 && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  208. system::openBrowser(model->getManualUrl());
  209. e.consume(this);
  210. }
  211. }
  212. if (e.isConsumed())
  213. return;
  214. OpaqueWidget::onHoverKey(e);
  215. }
  216. ui::Tooltip* createTooltip() {
  217. std::string text;
  218. text += model->name;
  219. text += "\n";
  220. text += model->plugin->brand;
  221. // Description
  222. if (model->description != "") {
  223. text += "\n" + model->description;
  224. }
  225. // Tags
  226. text += "\n\nTags: ";
  227. std::vector<std::string> tags;
  228. for (int tagId : model->tagIds) {
  229. tags.push_back(tag::getTag(tagId));
  230. }
  231. text += string::join(tags, ", ");
  232. ui::Tooltip* tooltip = new ui::Tooltip;
  233. tooltip->text = text;
  234. return tooltip;
  235. }
  236. void onEnter(const EnterEvent& e) override {
  237. setTooltip(createTooltip());
  238. }
  239. void onLeave(const LeaveEvent& e) override {
  240. setTooltip(NULL);
  241. }
  242. void onHide(const HideEvent& e) override {
  243. // Hide tooltip
  244. setTooltip(NULL);
  245. OpaqueWidget::onHide(e);
  246. }
  247. void createContextMenu() {
  248. ui::Menu* menu = createMenu();
  249. menu->addChild(createMenuLabel(model->name));
  250. menu->addChild(createMenuLabel(model->plugin->brand));
  251. model->appendContextMenu(menu);
  252. }
  253. };
  254. struct BrowserSearchField : ui::TextField {
  255. Browser* browser;
  256. void step() override {
  257. // Steal focus when step is called
  258. APP->event->setSelectedWidget(this);
  259. TextField::step();
  260. }
  261. void onSelectKey(const SelectKeyEvent& e) override;
  262. void onChange(const ChangeEvent& e) override;
  263. void onAction(const ActionEvent& e) override;
  264. void onHide(const HideEvent& e) override {
  265. APP->event->setSelectedWidget(NULL);
  266. ui::TextField::onHide(e);
  267. }
  268. void onShow(const ShowEvent& e) override {
  269. selectAll();
  270. TextField::onShow(e);
  271. }
  272. };
  273. struct FavoriteQuantity : Quantity {
  274. Browser* browser;
  275. void setValue(float value) override;
  276. float getValue() override;
  277. };
  278. struct ClearButton : ui::Button {
  279. Browser* browser;
  280. void onAction(const ActionEvent& e) override;
  281. };
  282. struct BrandItem : ui::MenuItem {
  283. Browser* browser;
  284. std::string brand;
  285. void onAction(const ActionEvent& e) override;
  286. void step() override;
  287. };
  288. struct BrandButton : ui::ChoiceButton {
  289. Browser* browser;
  290. void onAction(const ActionEvent& e) override;
  291. void step() override;
  292. };
  293. struct TagItem : ui::MenuItem {
  294. Browser* browser;
  295. int tagId;
  296. void onAction(const ActionEvent& e) override;
  297. void step() override;
  298. };
  299. struct TagButton : ui::ChoiceButton {
  300. Browser* browser;
  301. void onAction(const ActionEvent& e) override;
  302. void step() override;
  303. };
  304. static const std::string sortNames[] = {
  305. "Last updated",
  306. "Last used",
  307. "Most used",
  308. "Brand",
  309. "Module name",
  310. "Random",
  311. };
  312. struct SortItem : ui::MenuItem {
  313. Browser* browser;
  314. settings::BrowserSort sort;
  315. void onAction(const ActionEvent& e) override;
  316. void step() override {
  317. rightText = CHECKMARK(settings::browserSort == sort);
  318. MenuItem::step();
  319. }
  320. };
  321. struct SortButton : ui::ChoiceButton {
  322. Browser* browser;
  323. void onAction(const ActionEvent& e) override {
  324. ui::Menu* menu = createMenu();
  325. menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y));
  326. menu->box.size.x = box.size.x;
  327. for (int sortId = 0; sortId <= settings::BROWSER_SORT_RANDOM; sortId++) {
  328. SortItem* sortItem = new SortItem;
  329. sortItem->text = sortNames[sortId];
  330. sortItem->sort = (settings::BrowserSort) sortId;
  331. sortItem->browser = browser;
  332. menu->addChild(sortItem);
  333. }
  334. }
  335. void step() override {
  336. text = "Sort: ";
  337. text += sortNames[settings::browserSort];
  338. ChoiceButton::step();
  339. }
  340. };
  341. struct ZoomItem : ui::MenuItem {
  342. Browser* browser;
  343. float zoom;
  344. void onAction(const ActionEvent& e) override;
  345. void step() override {
  346. rightText = CHECKMARK(settings::browserZoom == zoom);
  347. MenuItem::step();
  348. }
  349. };
  350. struct ZoomButton : ui::ChoiceButton {
  351. Browser* browser;
  352. void onAction(const ActionEvent& e) override {
  353. ui::Menu* menu = createMenu();
  354. menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y));
  355. menu->box.size.x = box.size.x;
  356. for (float zoom = 1.f; zoom >= -2.f; zoom -= 0.5f) {
  357. ZoomItem* sortItem = new ZoomItem;
  358. sortItem->text = string::f("%.0f%%", std::pow(2.f, zoom) * 100.f);
  359. sortItem->zoom = zoom;
  360. sortItem->browser = browser;
  361. menu->addChild(sortItem);
  362. }
  363. }
  364. void step() override {
  365. text = "Zoom: ";
  366. text += string::f("%.0f%%", std::pow(2.f, settings::browserZoom) * 100.f);
  367. ChoiceButton::step();
  368. }
  369. };
  370. struct UrlButton : ui::Button {
  371. std::string url;
  372. void onAction(const ActionEvent& e) override {
  373. system::openBrowser(url);
  374. }
  375. };
  376. struct Browser : widget::OpaqueWidget {
  377. ui::SequentialLayout* headerLayout;
  378. BrowserSearchField* searchField;
  379. BrandButton* brandButton;
  380. TagButton* tagButton;
  381. FavoriteQuantity* favoriteQuantity;
  382. ui::OptionButton* favoriteButton;
  383. ClearButton* clearButton;
  384. ui::Label* countLabel;
  385. ui::ScrollWidget* modelScroll;
  386. widget::Widget* modelMargin;
  387. ui::SequentialLayout* modelContainer;
  388. std::string search;
  389. std::string brand;
  390. std::set<int> tagIds = {};
  391. bool favorite = false;
  392. // Caches and temporary state
  393. std::map<plugin::Model*, float> prefilteredModelScores;
  394. std::map<plugin::Model*, int> modelOrders;
  395. Browser() {
  396. const float margin = 10;
  397. // Header
  398. headerLayout = new ui::SequentialLayout;
  399. headerLayout->box.pos = math::Vec(0, 0);
  400. headerLayout->box.size.y = 0;
  401. headerLayout->margin = math::Vec(margin, margin);
  402. headerLayout->spacing = math::Vec(margin, margin);
  403. addChild(headerLayout);
  404. searchField = new BrowserSearchField;
  405. searchField->box.size.x = 150;
  406. searchField->placeholder = "Search modules";
  407. searchField->browser = this;
  408. headerLayout->addChild(searchField);
  409. brandButton = new BrandButton;
  410. brandButton->box.size.x = 150;
  411. brandButton->browser = this;
  412. headerLayout->addChild(brandButton);
  413. tagButton = new TagButton;
  414. tagButton->box.size.x = 150;
  415. tagButton->browser = this;
  416. headerLayout->addChild(tagButton);
  417. favoriteQuantity = new FavoriteQuantity;
  418. favoriteQuantity->browser = this;
  419. favoriteButton = new ui::OptionButton;
  420. favoriteButton->quantity = favoriteQuantity;
  421. favoriteButton->text = "Favorites";
  422. favoriteButton->box.size.x = 70;
  423. headerLayout->addChild(favoriteButton);
  424. clearButton = new ClearButton;
  425. clearButton->box.size.x = 100;
  426. clearButton->text = "Reset filters";
  427. clearButton->browser = this;
  428. headerLayout->addChild(clearButton);
  429. countLabel = new ui::Label;
  430. countLabel->box.size.x = 100;
  431. countLabel->color.a = 0.5;
  432. headerLayout->addChild(countLabel);
  433. SortButton* sortButton = new SortButton;
  434. sortButton->box.size.x = 150;
  435. sortButton->browser = this;
  436. headerLayout->addChild(sortButton);
  437. ZoomButton* zoomButton = new ZoomButton;
  438. zoomButton->box.size.x = 100;
  439. zoomButton->browser = this;
  440. headerLayout->addChild(zoomButton);
  441. UrlButton* libraryButton = new UrlButton;
  442. libraryButton->box.size.x = 150;
  443. libraryButton->text = "Browse VCV Library";
  444. libraryButton->url = "https://library.vcvrack.com/";
  445. headerLayout->addChild(libraryButton);
  446. // Model container
  447. modelScroll = new ui::ScrollWidget;
  448. modelScroll->box.pos.y = BND_WIDGET_HEIGHT;
  449. addChild(modelScroll);
  450. modelMargin = new widget::Widget;
  451. modelScroll->container->addChild(modelMargin);
  452. modelContainer = new ui::SequentialLayout;
  453. modelContainer->margin = math::Vec(margin, 0);
  454. modelContainer->spacing = math::Vec(margin, margin);
  455. modelMargin->addChild(modelContainer);
  456. resetModelBoxes();
  457. clear();
  458. }
  459. ~Browser() {
  460. delete favoriteQuantity;
  461. }
  462. void resetModelBoxes() {
  463. modelContainer->clearChildren();
  464. modelOrders.clear();
  465. // Iterate plugins
  466. // for (int i = 0; i < 100; i++)
  467. for (plugin::Plugin* plugin : plugin::plugins) {
  468. // Iterate models in plugin
  469. int modelIndex = 0;
  470. for (plugin::Model* model : plugin->models) {
  471. // Create ModelBox
  472. ModelBox* modelBox = new ModelBox;
  473. modelBox->setModel(model);
  474. modelContainer->addChild(modelBox);
  475. modelOrders[model] = modelIndex;
  476. modelIndex++;
  477. }
  478. }
  479. }
  480. void updateZoom() {
  481. modelScroll->offset = math::Vec();
  482. for (Widget* w : modelContainer->children) {
  483. ModelBox* mb = reinterpret_cast<ModelBox*>(w);
  484. assert(mb);
  485. mb->updateZoom();
  486. }
  487. }
  488. void step() override {
  489. box = parent->box.zeroPos().grow(math::Vec(-40, -40));
  490. headerLayout->box.size.x = box.size.x;
  491. const float margin = 10;
  492. modelScroll->box.pos = headerLayout->box.getBottomLeft();
  493. modelScroll->box.size = box.size.minus(modelScroll->box.pos);
  494. modelMargin->box.size.x = modelScroll->box.size.x;
  495. modelMargin->box.size.y = modelContainer->box.size.y + margin;
  496. modelContainer->box.size.x = modelMargin->box.size.x - margin;
  497. OpaqueWidget::step();
  498. }
  499. void draw(const DrawArgs& args) override {
  500. bndMenuBackground(args.vg, 0.0, 0.0, box.size.x, box.size.y, 0);
  501. Widget::draw(args);
  502. }
  503. bool isModelVisible(plugin::Model* model, const std::string& brand, std::set<int> tagIds, bool favorite) {
  504. // Filter hidden
  505. if (model->hidden)
  506. return false;
  507. settings::ModuleInfo* mi = settings::getModuleInfo(model->plugin->slug, model->slug);
  508. if (mi && !mi->enabled)
  509. return false;
  510. // Filter favorites
  511. if (favorite) {
  512. if (!mi || !mi->favorite)
  513. return false;
  514. }
  515. // Filter brand
  516. if (!brand.empty()) {
  517. if (model->plugin->brand != brand)
  518. return false;
  519. }
  520. // Filter tag
  521. for (int tagId : tagIds) {
  522. auto it = std::find(model->tagIds.begin(), model->tagIds.end(), tagId);
  523. if (it == model->tagIds.end())
  524. return false;
  525. }
  526. return true;
  527. };
  528. // Determines if there is at least 1 visible Model with a given brand and tag
  529. bool hasVisibleModel(const std::string& brand, std::set<int> tagIds, bool favorite) {
  530. for (const auto& pair : prefilteredModelScores) {
  531. plugin::Model* model = pair.first;
  532. if (isModelVisible(model, brand, tagIds, favorite))
  533. return true;
  534. }
  535. return false;
  536. };
  537. template <typename F>
  538. void sortModels(F f) {
  539. modelContainer->children.sort([&](Widget* w1, Widget* w2) {
  540. ModelBox* m1 = reinterpret_cast<ModelBox*>(w1);
  541. ModelBox* m2 = reinterpret_cast<ModelBox*>(w2);
  542. return f(m1) < f(m2);
  543. });
  544. }
  545. void refresh() {
  546. // Reset scroll position
  547. modelScroll->offset = math::Vec();
  548. prefilteredModelScores.clear();
  549. // Filter ModelBoxes by brand and tag
  550. for (Widget* w : modelContainer->children) {
  551. ModelBox* m = reinterpret_cast<ModelBox*>(w);
  552. m->setVisible(isModelVisible(m->model, brand, tagIds, favorite));
  553. }
  554. // Filter and sort by search results
  555. if (search.empty()) {
  556. // Add all models to prefilteredModelScores with scores of 1
  557. for (Widget* w : modelContainer->children) {
  558. ModelBox* m = reinterpret_cast<ModelBox*>(w);
  559. prefilteredModelScores[m->model] = 1.f;
  560. }
  561. // Sort ModelBoxes
  562. if (settings::browserSort == settings::BROWSER_SORT_UPDATED) {
  563. sortModels([this](ModelBox* m) {
  564. plugin::Plugin* p = m->model->plugin;
  565. int modelOrder = get(modelOrders, m->model, 0);
  566. return std::make_tuple(-p->modifiedTimestamp, p->brand, p->name, modelOrder);
  567. });
  568. }
  569. else if (settings::browserSort == settings::BROWSER_SORT_LAST_USED) {
  570. sortModels([this](ModelBox* m) {
  571. plugin::Plugin* p = m->model->plugin;
  572. const settings::ModuleInfo* mi = settings::getModuleInfo(p->slug, m->model->slug);
  573. double lastAdded = mi ? mi->lastAdded : -INFINITY;
  574. int modelOrder = get(modelOrders, m->model, 0);
  575. return std::make_tuple(-lastAdded, -p->modifiedTimestamp, p->brand, p->name, modelOrder);
  576. });
  577. }
  578. else if (settings::browserSort == settings::BROWSER_SORT_MOST_USED) {
  579. sortModels([this](ModelBox* m) {
  580. plugin::Plugin* p = m->model->plugin;
  581. const settings::ModuleInfo* mi = settings::getModuleInfo(p->slug, m->model->slug);
  582. int added = mi ? mi->added : 0;
  583. double lastAdded = mi ? mi->lastAdded : -INFINITY;
  584. int modelOrder = get(modelOrders, m->model, 0);
  585. return std::make_tuple(-added, -lastAdded, -p->modifiedTimestamp, p->brand, p->name, modelOrder);
  586. });
  587. }
  588. else if (settings::browserSort == settings::BROWSER_SORT_BRAND) {
  589. sortModels([this](ModelBox* m) {
  590. plugin::Plugin* p = m->model->plugin;
  591. int modelOrder = get(modelOrders, m->model, 0);
  592. return std::make_tuple(p->brand, p->name, modelOrder);
  593. });
  594. }
  595. else if (settings::browserSort == settings::BROWSER_SORT_NAME) {
  596. sortModels([](ModelBox* m) {
  597. plugin::Plugin* p = m->model->plugin;
  598. return std::make_tuple(m->model->name, p->brand);
  599. });
  600. }
  601. else if (settings::browserSort == settings::BROWSER_SORT_RANDOM) {
  602. std::map<ModelBox*, uint64_t> randomOrder;
  603. for (Widget* w : modelContainer->children) {
  604. ModelBox* m = reinterpret_cast<ModelBox*>(w);
  605. randomOrder[m] = random::u64();
  606. }
  607. sortModels([&](ModelBox* m) {
  608. return get(randomOrder, m, 0);
  609. });
  610. }
  611. }
  612. else {
  613. // Lazily initialize search database
  614. fuzzySearchInit();
  615. // Score results against search query
  616. auto results = modelDb.search(search);
  617. // DEBUG("=============");
  618. for (auto& result : results) {
  619. prefilteredModelScores[result.key] = result.score;
  620. // DEBUG("%s %s\t\t%f", result.key->plugin->slug.c_str(), result.key->slug.c_str(), result.score);
  621. }
  622. // Sort by score
  623. sortModels([&](ModelBox* m) {
  624. return -get(prefilteredModelScores, m->model, 0.f);
  625. });
  626. // Filter by whether the score is above the threshold
  627. for (Widget* w : modelContainer->children) {
  628. ModelBox* m = reinterpret_cast<ModelBox*>(w);
  629. assert(m);
  630. if (m->isVisible()) {
  631. if (prefilteredModelScores.find(m->model) == prefilteredModelScores.end())
  632. m->hide();
  633. }
  634. }
  635. }
  636. // Count visible modules
  637. int count = 0;
  638. for (Widget* w : modelContainer->children) {
  639. if (w->isVisible())
  640. count++;
  641. }
  642. countLabel->text = string::f("%d %s", count, (count == 1) ? "module" : "modules");
  643. }
  644. void clear() {
  645. search = "";
  646. searchField->setText("");
  647. brand = "";
  648. tagIds = {};
  649. favorite = false;
  650. refresh();
  651. }
  652. void onShow(const ShowEvent& e) override {
  653. refresh();
  654. OpaqueWidget::onShow(e);
  655. }
  656. void onButton(const ButtonEvent& e) override {
  657. Widget::onButton(e);
  658. e.stopPropagating();
  659. // Consume all mouse buttons
  660. if (!e.isConsumed())
  661. e.consume(this);
  662. }
  663. };
  664. // Implementations to resolve dependencies
  665. inline void FavoriteQuantity::setValue(float value) {
  666. browser->favorite = value;
  667. browser->refresh();
  668. }
  669. inline float FavoriteQuantity::getValue() {
  670. return browser->favorite;
  671. }
  672. inline void ClearButton::onAction(const ActionEvent& e) {
  673. browser->clear();
  674. }
  675. inline void BrowserSearchField::onSelectKey(const SelectKeyEvent& e) {
  676. if (e.action == GLFW_PRESS || e.action == GLFW_REPEAT) {
  677. // Backspace when the field is empty to clear filters.
  678. if (e.key == GLFW_KEY_BACKSPACE) {
  679. if (text == "") {
  680. browser->clear();
  681. e.consume(this);
  682. }
  683. }
  684. }
  685. if (!e.getTarget())
  686. ui::TextField::onSelectKey(e);
  687. }
  688. inline void BrowserSearchField::onChange(const ChangeEvent& e) {
  689. browser->search = string::trim(text);
  690. browser->refresh();
  691. }
  692. inline void BrowserSearchField::onAction(const ActionEvent& e) {
  693. // Get first ModelBox
  694. ModelBox* mb = NULL;
  695. for (Widget* w : browser->modelContainer->children) {
  696. if (w->isVisible()) {
  697. mb = reinterpret_cast<ModelBox*>(w);
  698. break;
  699. }
  700. }
  701. if (mb) {
  702. chooseModel(mb->model);
  703. }
  704. }
  705. inline void BrandItem::onAction(const ActionEvent& e) {
  706. if (browser->brand == brand)
  707. browser->brand = "";
  708. else
  709. browser->brand = brand;
  710. browser->refresh();
  711. }
  712. inline void BrandItem::step() {
  713. rightText = CHECKMARK(browser->brand == brand);
  714. MenuItem::step();
  715. }
  716. inline void BrandButton::onAction(const ActionEvent& e) {
  717. ui::Menu* menu = createMenu();
  718. menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y));
  719. menu->box.size.x = box.size.x;
  720. BrandItem* noneItem = new BrandItem;
  721. noneItem->text = "All brands";
  722. noneItem->brand = "";
  723. noneItem->browser = browser;
  724. menu->addChild(noneItem);
  725. menu->addChild(new ui::MenuSeparator);
  726. // Collect brands from all plugins
  727. std::set<std::string, string::CaseInsensitiveCompare> brands;
  728. for (plugin::Plugin* plugin : plugin::plugins) {
  729. brands.insert(plugin->brand);
  730. }
  731. for (const std::string& brand : brands) {
  732. BrandItem* brandItem = new BrandItem;
  733. brandItem->text = brand;
  734. brandItem->brand = brand;
  735. brandItem->browser = browser;
  736. brandItem->disabled = !browser->hasVisibleModel(brand, browser->tagIds, browser->favorite);
  737. menu->addChild(brandItem);
  738. }
  739. }
  740. inline void BrandButton::step() {
  741. text = "Brand";
  742. if (!browser->brand.empty()) {
  743. text += ": ";
  744. text += browser->brand;
  745. }
  746. text = string::ellipsize(text, 21);
  747. ChoiceButton::step();
  748. }
  749. inline void TagItem::onAction(const ActionEvent& e) {
  750. auto it = browser->tagIds.find(tagId);
  751. bool isSelected = (it != browser->tagIds.end());
  752. if (tagId >= 0) {
  753. // Specific tag
  754. if (!e.isConsumed()) {
  755. // Multi select
  756. if (isSelected)
  757. browser->tagIds.erase(tagId);
  758. else
  759. browser->tagIds.insert(tagId);
  760. e.unconsume();
  761. }
  762. else {
  763. // Single select
  764. if (isSelected)
  765. browser->tagIds = {};
  766. else {
  767. browser->tagIds = {tagId};
  768. }
  769. }
  770. }
  771. else {
  772. // All tags
  773. browser->tagIds = {};
  774. }
  775. browser->refresh();
  776. }
  777. inline void TagItem::step() {
  778. // TODO Disable tags with no modules
  779. if (tagId >= 0) {
  780. auto it = browser->tagIds.find(tagId);
  781. bool isSelected = (it != browser->tagIds.end());
  782. rightText = CHECKMARK(isSelected);
  783. }
  784. else {
  785. rightText = CHECKMARK(browser->tagIds.empty());
  786. }
  787. MenuItem::step();
  788. }
  789. inline void TagButton::onAction(const ActionEvent& e) {
  790. ui::Menu* menu = createMenu();
  791. menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y));
  792. menu->box.size.x = box.size.x;
  793. TagItem* noneItem = new TagItem;
  794. noneItem->text = "All tags";
  795. noneItem->tagId = -1;
  796. noneItem->browser = browser;
  797. menu->addChild(noneItem);
  798. menu->addChild(createMenuLabel(RACK_MOD_CTRL_NAME "+click to select multiple"));
  799. menu->addChild(new ui::MenuSeparator);
  800. for (int tagId = 0; tagId < (int) tag::tagAliases.size(); tagId++) {
  801. TagItem* tagItem = new TagItem;
  802. tagItem->text = tag::getTag(tagId);
  803. tagItem->tagId = tagId;
  804. tagItem->browser = browser;
  805. tagItem->disabled = !browser->hasVisibleModel(browser->brand, {tagId}, browser->favorite);
  806. menu->addChild(tagItem);
  807. }
  808. }
  809. inline void TagButton::step() {
  810. text = "Tags";
  811. if (!browser->tagIds.empty()) {
  812. text += ": ";
  813. bool firstTag = true;
  814. for (int tagId : browser->tagIds) {
  815. if (!firstTag)
  816. text += ", ";
  817. text += tag::getTag(tagId);
  818. firstTag = false;
  819. }
  820. }
  821. text = string::ellipsize(text, 21);
  822. ChoiceButton::step();
  823. }
  824. inline void SortItem::onAction(const ActionEvent& e) {
  825. settings::browserSort = sort;
  826. browser->refresh();
  827. }
  828. inline void ZoomItem::onAction(const ActionEvent& e) {
  829. if (zoom != settings::browserZoom) {
  830. settings::browserZoom = zoom;
  831. browser->updateZoom();
  832. }
  833. }
  834. } // namespace browser
  835. widget::Widget* browserCreate() {
  836. browser::BrowserOverlay* overlay = new browser::BrowserOverlay;
  837. overlay->bgColor = nvgRGBAf(0, 0, 0, 0.33);
  838. browser::Browser* browser = new browser::Browser;
  839. overlay->addChild(browser);
  840. return overlay;
  841. }
  842. } // namespace app
  843. } // namespace rack