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.

920 lines
23KB

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