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.

1081 lines
28KB

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