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.

957 lines
24KB

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