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.

1046 lines
26KB

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