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.

681 lines
17KB

  1. #include <app/ModuleBrowser.hpp>
  2. #include <widget/OpaqueWidget.hpp>
  3. #include <widget/TransparentWidget.hpp>
  4. #include <widget/ZoomWidget.hpp>
  5. #include <ui/ScrollWidget.hpp>
  6. #include <ui/SequentialLayout.hpp>
  7. #include <ui/MarginLayout.hpp>
  8. #include <ui/Label.hpp>
  9. #include <ui/TextField.hpp>
  10. #include <ui/MenuOverlay.hpp>
  11. #include <ui/List.hpp>
  12. #include <ui/MenuItem.hpp>
  13. #include <ui/Button.hpp>
  14. #include <ui/RadioButton.hpp>
  15. #include <ui/ChoiceButton.hpp>
  16. #include <ui/Tooltip.hpp>
  17. #include <app/ModuleWidget.hpp>
  18. #include <app/Scene.hpp>
  19. #include <plugin.hpp>
  20. #include <app.hpp>
  21. #include <engine/Engine.hpp>
  22. #include <plugin/Model.hpp>
  23. #include <string.hpp>
  24. #include <history.hpp>
  25. #include <settings.hpp>
  26. #include <tag.hpp>
  27. #include <set>
  28. #include <algorithm>
  29. namespace rack {
  30. namespace app {
  31. // Static functions
  32. static float modelScore(plugin::Model* model, const std::string& search) {
  33. if (search.empty())
  34. return 1.f;
  35. std::string s;
  36. s += model->plugin->brand;
  37. s += " ";
  38. s += model->plugin->name;
  39. s += " ";
  40. s += model->name;
  41. s += " ";
  42. s += model->slug;
  43. for (int tagId : model->tags) {
  44. // Add all aliases of a tag
  45. for (const std::string& alias : tag::tagAliases[tagId]) {
  46. s += " ";
  47. s += alias;
  48. }
  49. }
  50. float score = string::fuzzyScore(string::lowercase(s), string::lowercase(search));
  51. return score;
  52. }
  53. static bool isModelVisible(plugin::Model* model, const std::string& search, const std::string& brand, int tagId) {
  54. // Filter search query
  55. if (search != "") {
  56. float score = modelScore(model, search);
  57. if (score <= 0.f)
  58. return false;
  59. }
  60. // Filter brand
  61. if (brand != "") {
  62. if (model->plugin->brand != brand)
  63. return false;
  64. }
  65. // Filter tag
  66. if (tagId >= 0) {
  67. auto it = std::find(model->tags.begin(), model->tags.end(), tagId);
  68. if (it == model->tags.end())
  69. return false;
  70. }
  71. return true;
  72. }
  73. static ModuleWidget* chooseModel(plugin::Model* model) {
  74. // Create Module and ModuleWidget
  75. engine::Module* module = model->createModule();
  76. APP->engine->addModule(module);
  77. ModuleWidget* moduleWidget = model->createModuleWidget(module);
  78. APP->scene->rack->addModuleAtMouse(moduleWidget);
  79. // Push ModuleAdd history action
  80. history::ModuleAdd* h = new history::ModuleAdd;
  81. h->name = "create module";
  82. h->setModule(moduleWidget);
  83. APP->history->push(h);
  84. // Hide Module Browser
  85. APP->scene->moduleBrowser->hide();
  86. return moduleWidget;
  87. }
  88. template <typename K, typename V>
  89. V get_default(const std::map<K, V>& m, const K& key, const V& def) {
  90. auto it = m.find(key);
  91. if (it == m.end())
  92. return def;
  93. return it->second;
  94. }
  95. // Widgets
  96. struct BrowserOverlay : widget::OpaqueWidget {
  97. void step() override {
  98. box = parent->box.zeroPos();
  99. // Only step if visible, since there are potentially thousands of descendants that don't need to be stepped.
  100. if (visible)
  101. OpaqueWidget::step();
  102. }
  103. void onButton(const event::Button& e) override {
  104. OpaqueWidget::onButton(e);
  105. if (e.getTarget() != this)
  106. return;
  107. if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT) {
  108. hide();
  109. e.consume(this);
  110. }
  111. }
  112. };
  113. static const float MODEL_BOX_ZOOM = 0.5f;
  114. struct ModelBox : widget::OpaqueWidget {
  115. plugin::Model* model;
  116. widget::Widget* previewWidget;
  117. ui::Tooltip* tooltip = NULL;
  118. /** Lazily created */
  119. widget::FramebufferWidget* previewFb = NULL;
  120. ModelBox() {
  121. // Approximate size as 10HP before we know the actual size.
  122. // We need a nonzero size, otherwise the parent widget will consider it not in the draw bounds, so its preview will not be lazily created.
  123. box.size.x = 10 * RACK_GRID_WIDTH * MODEL_BOX_ZOOM;
  124. box.size.y = RACK_GRID_HEIGHT * MODEL_BOX_ZOOM;
  125. box.size = box.size.ceil();
  126. }
  127. void setModel(plugin::Model* model) {
  128. this->model = model;
  129. previewWidget = new widget::TransparentWidget;
  130. previewWidget->box.size.y = std::ceil(RACK_GRID_HEIGHT * MODEL_BOX_ZOOM);
  131. addChild(previewWidget);
  132. }
  133. void createPreview() {
  134. previewFb = new widget::FramebufferWidget;
  135. if (math::isNear(APP->window->pixelRatio, 1.0)) {
  136. // Small details draw poorly at low DPI, so oversample when drawing to the framebuffer
  137. previewFb->oversample = 2.0;
  138. }
  139. previewWidget->addChild(previewFb);
  140. widget::ZoomWidget* zoomWidget = new widget::ZoomWidget;
  141. zoomWidget->setZoom(MODEL_BOX_ZOOM);
  142. previewFb->addChild(zoomWidget);
  143. ModuleWidget* moduleWidget = model->createModuleWidget(NULL);
  144. zoomWidget->addChild(moduleWidget);
  145. zoomWidget->box.size.x = moduleWidget->box.size.x * MODEL_BOX_ZOOM;
  146. zoomWidget->box.size.y = RACK_GRID_HEIGHT * MODEL_BOX_ZOOM;
  147. previewWidget->box.size.x = std::ceil(zoomWidget->box.size.x);
  148. box.size.x = previewWidget->box.size.x;
  149. }
  150. void draw(const DrawArgs& args) override {
  151. // Lazily create preview when drawn
  152. if (!previewFb) {
  153. createPreview();
  154. }
  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. OpaqueWidget::draw(args);
  165. }
  166. void setTooltip(ui::Tooltip* tooltip) {
  167. if (this->tooltip) {
  168. this->tooltip->requestDelete();
  169. this->tooltip = NULL;
  170. }
  171. if (tooltip) {
  172. APP->scene->addChild(tooltip);
  173. this->tooltip = tooltip;
  174. }
  175. }
  176. void onButton(const event::Button& e) override {
  177. OpaqueWidget::onButton(e);
  178. if (e.getTarget() != this)
  179. return;
  180. if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT) {
  181. ModuleWidget* mw = chooseModel(model);
  182. // Pretend the moduleWidget was clicked so it can be dragged in the RackWidget
  183. e.consume(mw);
  184. }
  185. }
  186. void onEnter(const event::Enter& e) override {
  187. std::string text;
  188. text = model->plugin->brand;
  189. text += " " + model->name;
  190. // Tags
  191. text += "\nTags: ";
  192. for (size_t i = 0; i < model->tags.size(); i++) {
  193. if (i > 0)
  194. text += ", ";
  195. int tagId = model->tags[i];
  196. text += tag::tagAliases[tagId][0];
  197. }
  198. // Description
  199. if (model->description != "") {
  200. text += "\n" + model->description;
  201. }
  202. ui::Tooltip* tooltip = new ui::Tooltip;
  203. tooltip->text = text;
  204. setTooltip(tooltip);
  205. }
  206. void onLeave(const event::Leave& e) override {
  207. setTooltip(NULL);
  208. }
  209. void onHide(const event::Hide& e) override {
  210. // Hide tooltip
  211. setTooltip(NULL);
  212. OpaqueWidget::onHide(e);
  213. }
  214. };
  215. struct BrandItem : ui::MenuItem {
  216. void onAction(const event::Action& e) override;
  217. void step() override;
  218. };
  219. struct TagItem : ui::MenuItem {
  220. int tagId;
  221. void onAction(const event::Action& e) override;
  222. void step() override;
  223. };
  224. struct BrowserSearchField : ui::TextField {
  225. void step() override {
  226. // Steal focus when step is called
  227. APP->event->setSelected(this);
  228. TextField::step();
  229. }
  230. void onSelectKey(const event::SelectKey& e) override;
  231. void onChange(const event::Change& e) override;
  232. void onAction(const event::Action& e) override;
  233. void onHide(const event::Hide& e) override {
  234. APP->event->setSelected(NULL);
  235. ui::TextField::onHide(e);
  236. }
  237. void onShow(const event::Show& e) override {
  238. selectAll();
  239. TextField::onShow(e);
  240. }
  241. };
  242. struct ClearButton : ui::Button {
  243. void onAction(const event::Action& e) override;
  244. };
  245. struct BrowserSidebar : widget::Widget {
  246. BrowserSearchField* searchField;
  247. ClearButton* clearButton;
  248. ui::Label* tagLabel;
  249. ui::List* tagList;
  250. ui::ScrollWidget* tagScroll;
  251. ui::Label* brandLabel;
  252. ui::List* brandList;
  253. ui::ScrollWidget* brandScroll;
  254. BrowserSidebar() {
  255. // Search
  256. searchField = new BrowserSearchField;
  257. addChild(searchField);
  258. // Clear filters
  259. clearButton = new ClearButton;
  260. clearButton->text = "Reset filters";
  261. addChild(clearButton);
  262. // Tag label
  263. tagLabel = new ui::Label;
  264. // tagLabel->fontSize = 16;
  265. tagLabel->color = nvgRGB(0x80, 0x80, 0x80);
  266. tagLabel->text = "Tags";
  267. addChild(tagLabel);
  268. // Tag list
  269. tagScroll = new ui::ScrollWidget;
  270. addChild(tagScroll);
  271. tagList = new ui::List;
  272. tagScroll->container->addChild(tagList);
  273. for (int tagId = 0; tagId < (int) tag::tagAliases.size(); tagId++) {
  274. TagItem* item = new TagItem;
  275. item->text = tag::tagAliases[tagId][0];
  276. item->tagId = tagId;
  277. tagList->addChild(item);
  278. }
  279. // Brand label
  280. brandLabel = new ui::Label;
  281. // brandLabel->fontSize = 16;
  282. brandLabel->color = nvgRGB(0x80, 0x80, 0x80);
  283. brandLabel->text = "Brands";
  284. addChild(brandLabel);
  285. // Brand list
  286. brandScroll = new ui::ScrollWidget;
  287. addChild(brandScroll);
  288. brandList = new ui::List;
  289. brandScroll->container->addChild(brandList);
  290. // Collect brands from all plugins
  291. std::set<std::string, string::CaseInsensitiveCompare> brands;
  292. for (plugin::Plugin* plugin : plugin::plugins) {
  293. brands.insert(plugin->brand);
  294. }
  295. for (const std::string& brand : brands) {
  296. BrandItem* item = new BrandItem;
  297. item->text = brand;
  298. brandList->addChild(item);
  299. }
  300. }
  301. void step() override {
  302. searchField->box.size.x = box.size.x;
  303. clearButton->box.pos = searchField->box.getBottomLeft();
  304. clearButton->box.size.x = box.size.x;
  305. float listHeight = (box.size.y - clearButton->box.getBottom()) / 2;
  306. listHeight = std::floor(listHeight);
  307. tagLabel->box.pos = clearButton->box.getBottomLeft();
  308. tagLabel->box.size.x = box.size.x;
  309. tagScroll->box.pos = tagLabel->box.getBottomLeft();
  310. tagScroll->box.size.y = listHeight - tagLabel->box.size.y;
  311. tagScroll->box.size.x = box.size.x;
  312. tagList->box.size.x = tagScroll->box.size.x;
  313. brandLabel->box.pos = tagScroll->box.getBottomLeft();
  314. brandLabel->box.size.x = box.size.x;
  315. brandScroll->box.pos = brandLabel->box.getBottomLeft();
  316. brandScroll->box.size.y = listHeight - brandLabel->box.size.y;
  317. brandScroll->box.size.x = box.size.x;
  318. brandList->box.size.x = brandScroll->box.size.x;
  319. Widget::step();
  320. }
  321. };
  322. struct ModuleBrowser : widget::OpaqueWidget {
  323. BrowserSidebar* sidebar;
  324. ui::ScrollWidget* modelScroll;
  325. ui::Label* modelLabel;
  326. ui::MarginLayout* modelMargin;
  327. ui::SequentialLayout* modelContainer;
  328. std::string search;
  329. std::string brand;
  330. int tagId = -1;
  331. ModuleBrowser() {
  332. sidebar = new BrowserSidebar;
  333. sidebar->box.size.x = 200;
  334. addChild(sidebar);
  335. modelLabel = new ui::Label;
  336. // modelLabel->fontSize = 16;
  337. // modelLabel->box.size.x = 400;
  338. addChild(modelLabel);
  339. modelScroll = new ui::ScrollWidget;
  340. addChild(modelScroll);
  341. modelMargin = new ui::MarginLayout;
  342. modelMargin->margin = math::Vec(10, 10);
  343. modelScroll->container->addChild(modelMargin);
  344. modelContainer = new ui::SequentialLayout;
  345. modelContainer->spacing = math::Vec(10, 10);
  346. modelMargin->addChild(modelContainer);
  347. resetModelContainer();
  348. clear();
  349. }
  350. void resetModelContainer() {
  351. modelContainer->clearChildren();
  352. // Iterate plugins
  353. for (plugin::Plugin* plugin : plugin::plugins) {
  354. // Get module slugs from module whitelist
  355. const auto& pluginIt = settings::moduleWhitelist.find(plugin->slug);
  356. // Iterate models in plugin
  357. for (plugin::Model* model : plugin->models) {
  358. // Don't show module if plugin whitelist exists but the module is not in it.
  359. if (pluginIt != settings::moduleWhitelist.end()) {
  360. auto moduleIt = std::find(pluginIt->second.begin(), pluginIt->second.end(), model->slug);
  361. if (moduleIt == pluginIt->second.end())
  362. continue;
  363. }
  364. // Create ModelBox
  365. ModelBox* modelBox = new ModelBox;
  366. modelBox->setModel(model);
  367. modelContainer->addChild(modelBox);
  368. }
  369. }
  370. }
  371. void step() override {
  372. box = parent->box.zeroPos().grow(math::Vec(-70, -70));
  373. sidebar->box.size.y = box.size.y;
  374. modelLabel->box.pos = sidebar->box.getTopRight().plus(math::Vec(5, 5));
  375. modelScroll->box.pos = sidebar->box.getTopRight().plus(math::Vec(0, 30));
  376. modelScroll->box.size = box.size.minus(modelScroll->box.pos);
  377. modelMargin->box.size.x = modelScroll->box.size.x;
  378. modelMargin->box.size.y = modelContainer->getChildrenBoundingBox().size.y + 2 * modelMargin->margin.y;
  379. OpaqueWidget::step();
  380. }
  381. void draw(const DrawArgs& args) override {
  382. bndMenuBackground(args.vg, 0.0, 0.0, box.size.x, box.size.y, 0);
  383. Widget::draw(args);
  384. }
  385. void refresh() {
  386. // Reset scroll position
  387. modelScroll->offset = math::Vec();
  388. // Filter ModelBoxes
  389. for (Widget* w : modelContainer->children) {
  390. ModelBox* m = dynamic_cast<ModelBox*>(w);
  391. assert(m);
  392. m->visible = isModelVisible(m->model, search, brand, tagId);
  393. }
  394. // Sort ModelBoxes
  395. modelContainer->children.sort([&](Widget * w1, Widget * w2) {
  396. ModelBox* m1 = dynamic_cast<ModelBox*>(w1);
  397. ModelBox* m2 = dynamic_cast<ModelBox*>(w2);
  398. // Sort by (modifiedTimestamp descending, plugin brand)
  399. auto t1 = std::make_tuple(-m1->model->plugin->modifiedTimestamp, m1->model->plugin->brand);
  400. auto t2 = std::make_tuple(-m2->model->plugin->modifiedTimestamp, m2->model->plugin->brand);
  401. return t1 < t2;
  402. });
  403. if (search.empty()) {
  404. // We've already sorted above
  405. }
  406. else {
  407. std::map<Widget*, float> scores;
  408. // Compute scores
  409. for (Widget* w : modelContainer->children) {
  410. ModelBox* m = dynamic_cast<ModelBox*>(w);
  411. assert(m);
  412. if (!m->visible)
  413. continue;
  414. scores[m] = modelScore(m->model, search);
  415. }
  416. // // Sort by score
  417. // modelContainer->children.sort([&](Widget *w1, Widget *w2) {
  418. // // If score was not computed, scores[w] returns 0, but this doesn't matter because those widgets aren't visible.
  419. // return get_default(scores, w1, 0.f) > get_default(scores, w2, 0.f);
  420. // });
  421. }
  422. // Filter the brand and tag lists
  423. // Get modules that would be filtered by just the search query
  424. std::vector<plugin::Model*> filteredModels;
  425. for (Widget* w : modelContainer->children) {
  426. ModelBox* m = dynamic_cast<ModelBox*>(w);
  427. assert(m);
  428. if (isModelVisible(m->model, search, "", -1))
  429. filteredModels.push_back(m->model);
  430. }
  431. auto hasModel = [&](const std::string & brand, int tagId) -> bool {
  432. for (plugin::Model* model : filteredModels) {
  433. if (isModelVisible(model, "", brand, tagId))
  434. return true;
  435. }
  436. return false;
  437. };
  438. // Enable brand and tag items that are available in visible ModelBoxes
  439. int brandsLen = 0;
  440. for (Widget* w : sidebar->brandList->children) {
  441. BrandItem* item = dynamic_cast<BrandItem*>(w);
  442. assert(item);
  443. item->disabled = !hasModel(item->text, tagId);
  444. if (!item->disabled)
  445. brandsLen++;
  446. }
  447. sidebar->brandLabel->text = string::f("Brands (%d)", brandsLen);
  448. int tagsLen = 0;
  449. for (Widget* w : sidebar->tagList->children) {
  450. TagItem* item = dynamic_cast<TagItem*>(w);
  451. assert(item);
  452. item->disabled = !hasModel(brand, item->tagId);
  453. if (!item->disabled)
  454. tagsLen++;
  455. }
  456. sidebar->tagLabel->text = string::f("Tags (%d)", tagsLen);
  457. // Count models
  458. int modelsLen = 0;
  459. for (Widget* w : modelContainer->children) {
  460. if (w->visible)
  461. modelsLen++;
  462. }
  463. modelLabel->text = string::f("Modules (%d) Click and drag a module to place it in the rack.", modelsLen);
  464. }
  465. void clear() {
  466. search = "";
  467. sidebar->searchField->setText("");
  468. brand = "";
  469. tagId = -1;
  470. refresh();
  471. }
  472. void onShow(const event::Show& e) override {
  473. refresh();
  474. OpaqueWidget::onShow(e);
  475. }
  476. };
  477. // Implementations to resolve dependencies
  478. inline void BrandItem::onAction(const event::Action& e) {
  479. ModuleBrowser* browser = getAncestorOfType<ModuleBrowser>();
  480. if (browser->brand == text)
  481. browser->brand = "";
  482. else
  483. browser->brand = text;
  484. browser->refresh();
  485. }
  486. inline void BrandItem::step() {
  487. MenuItem::step();
  488. ModuleBrowser* browser = getAncestorOfType<ModuleBrowser>();
  489. active = (browser->brand == text);
  490. }
  491. inline void TagItem::onAction(const event::Action& e) {
  492. ModuleBrowser* browser = getAncestorOfType<ModuleBrowser>();
  493. if (browser->tagId == tagId)
  494. browser->tagId = -1;
  495. else
  496. browser->tagId = tagId;
  497. browser->refresh();
  498. }
  499. inline void TagItem::step() {
  500. MenuItem::step();
  501. ModuleBrowser* browser = getAncestorOfType<ModuleBrowser>();
  502. active = (browser->tagId == tagId);
  503. }
  504. inline void BrowserSearchField::onSelectKey(const event::SelectKey& e) {
  505. if (e.action == GLFW_PRESS || e.action == GLFW_REPEAT) {
  506. switch (e.key) {
  507. case GLFW_KEY_ESCAPE: {
  508. BrowserOverlay* overlay = getAncestorOfType<BrowserOverlay>();
  509. overlay->hide();
  510. e.consume(this);
  511. } break;
  512. case GLFW_KEY_BACKSPACE: {
  513. if (text == "") {
  514. ModuleBrowser* browser = getAncestorOfType<ModuleBrowser>();
  515. browser->clear();
  516. e.consume(this);
  517. }
  518. } break;
  519. }
  520. }
  521. if (!e.getTarget())
  522. ui::TextField::onSelectKey(e);
  523. }
  524. inline void BrowserSearchField::onChange(const event::Change& e) {
  525. ModuleBrowser* browser = getAncestorOfType<ModuleBrowser>();
  526. browser->search = string::trim(text);
  527. browser->refresh();
  528. }
  529. inline void BrowserSearchField::onAction(const event::Action& e) {
  530. // Get first ModelBox
  531. ModelBox* mb = NULL;
  532. ModuleBrowser* browser = getAncestorOfType<ModuleBrowser>();
  533. for (Widget* w : browser->modelContainer->children) {
  534. if (w->visible) {
  535. mb = dynamic_cast<ModelBox*>(w);
  536. break;
  537. }
  538. }
  539. if (mb) {
  540. chooseModel(mb->model);
  541. }
  542. }
  543. inline void ClearButton::onAction(const event::Action& e) {
  544. ModuleBrowser* browser = getAncestorOfType<ModuleBrowser>();
  545. browser->clear();
  546. }
  547. // Global functions
  548. widget::Widget* moduleBrowserCreate() {
  549. BrowserOverlay* overlay = new BrowserOverlay;
  550. ModuleBrowser* browser = new ModuleBrowser;
  551. overlay->addChild(browser);
  552. return overlay;
  553. }
  554. } // namespace app
  555. } // namespace rack