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.

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