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.

1069 lines
27KB

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