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.

685 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. // Set the drag position at the center of the module
  185. // TODO This doesn't work because ModuleWidget::onDragStart, which is called afterwards, overwrites this.
  186. // mw->dragPos() = mw->box.size.div(2);
  187. }
  188. }
  189. void onEnter(const event::Enter& e) override {
  190. std::string text;
  191. text = model->plugin->brand;
  192. text += " " + model->name;
  193. // Tags
  194. text += "\nTags: ";
  195. for (size_t i = 0; i < model->tags.size(); i++) {
  196. if (i > 0)
  197. text += ", ";
  198. int tagId = model->tags[i];
  199. text += tag::tagAliases[tagId][0];
  200. }
  201. // Description
  202. if (model->description != "") {
  203. text += "\n" + model->description;
  204. }
  205. ui::Tooltip* tooltip = new ui::Tooltip;
  206. tooltip->text = text;
  207. setTooltip(tooltip);
  208. }
  209. void onLeave(const event::Leave& e) override {
  210. setTooltip(NULL);
  211. }
  212. void onHide(const event::Hide& e) override {
  213. // Hide tooltip
  214. setTooltip(NULL);
  215. OpaqueWidget::onHide(e);
  216. }
  217. };
  218. struct BrandItem : ui::MenuItem {
  219. void onAction(const event::Action& e) override;
  220. void step() override;
  221. };
  222. struct TagItem : ui::MenuItem {
  223. int tagId;
  224. void onAction(const event::Action& e) override;
  225. void step() override;
  226. };
  227. struct BrowserSearchField : ui::TextField {
  228. void step() override {
  229. // Steal focus when step is called
  230. APP->event->setSelected(this);
  231. TextField::step();
  232. }
  233. void onSelectKey(const event::SelectKey& e) override;
  234. void onChange(const event::Change& e) override;
  235. void onAction(const event::Action& e) override;
  236. void onHide(const event::Hide& e) override {
  237. APP->event->setSelected(NULL);
  238. ui::TextField::onHide(e);
  239. }
  240. void onShow(const event::Show& e) override {
  241. selectAll();
  242. TextField::onShow(e);
  243. }
  244. };
  245. struct ClearButton : ui::Button {
  246. void onAction(const event::Action& e) override;
  247. };
  248. struct BrowserSidebar : widget::Widget {
  249. BrowserSearchField* searchField;
  250. ClearButton* clearButton;
  251. ui::Label* tagLabel;
  252. ui::List* tagList;
  253. ui::ScrollWidget* tagScroll;
  254. ui::Label* brandLabel;
  255. ui::List* brandList;
  256. ui::ScrollWidget* brandScroll;
  257. BrowserSidebar() {
  258. // Search
  259. searchField = new BrowserSearchField;
  260. addChild(searchField);
  261. // Clear filters
  262. clearButton = new ClearButton;
  263. clearButton->text = "Reset filters";
  264. addChild(clearButton);
  265. // Tag label
  266. tagLabel = new ui::Label;
  267. // tagLabel->fontSize = 16;
  268. tagLabel->color = nvgRGB(0x80, 0x80, 0x80);
  269. tagLabel->text = "Tags";
  270. addChild(tagLabel);
  271. // Tag list
  272. tagScroll = new ui::ScrollWidget;
  273. addChild(tagScroll);
  274. tagList = new ui::List;
  275. tagScroll->container->addChild(tagList);
  276. for (int tagId = 0; tagId < (int) tag::tagAliases.size(); tagId++) {
  277. TagItem* item = new TagItem;
  278. item->text = tag::tagAliases[tagId][0];
  279. item->tagId = tagId;
  280. tagList->addChild(item);
  281. }
  282. // Brand label
  283. brandLabel = new ui::Label;
  284. // brandLabel->fontSize = 16;
  285. brandLabel->color = nvgRGB(0x80, 0x80, 0x80);
  286. brandLabel->text = "Brands";
  287. addChild(brandLabel);
  288. // Brand list
  289. brandScroll = new ui::ScrollWidget;
  290. addChild(brandScroll);
  291. brandList = new ui::List;
  292. brandScroll->container->addChild(brandList);
  293. // Collect brands from all plugins
  294. std::set<std::string, string::CaseInsensitiveCompare> brands;
  295. for (plugin::Plugin* plugin : plugin::plugins) {
  296. brands.insert(plugin->brand);
  297. }
  298. for (const std::string& brand : brands) {
  299. BrandItem* item = new BrandItem;
  300. item->text = brand;
  301. brandList->addChild(item);
  302. }
  303. }
  304. void step() override {
  305. searchField->box.size.x = box.size.x;
  306. clearButton->box.pos = searchField->box.getBottomLeft();
  307. clearButton->box.size.x = box.size.x;
  308. float listHeight = (box.size.y - clearButton->box.getBottom()) / 2;
  309. listHeight = std::floor(listHeight);
  310. tagLabel->box.pos = clearButton->box.getBottomLeft();
  311. tagLabel->box.size.x = box.size.x;
  312. tagScroll->box.pos = tagLabel->box.getBottomLeft();
  313. tagScroll->box.size.y = listHeight - tagLabel->box.size.y;
  314. tagScroll->box.size.x = box.size.x;
  315. tagList->box.size.x = tagScroll->box.size.x;
  316. brandLabel->box.pos = tagScroll->box.getBottomLeft();
  317. brandLabel->box.size.x = box.size.x;
  318. brandScroll->box.pos = brandLabel->box.getBottomLeft();
  319. brandScroll->box.size.y = listHeight - brandLabel->box.size.y;
  320. brandScroll->box.size.x = box.size.x;
  321. brandList->box.size.x = brandScroll->box.size.x;
  322. Widget::step();
  323. }
  324. };
  325. struct ModuleBrowser : widget::OpaqueWidget {
  326. BrowserSidebar* sidebar;
  327. ui::ScrollWidget* modelScroll;
  328. ui::Label* modelLabel;
  329. ui::MarginLayout* modelMargin;
  330. ui::SequentialLayout* modelContainer;
  331. std::string search;
  332. std::string brand;
  333. int tagId = -1;
  334. ModuleBrowser() {
  335. sidebar = new BrowserSidebar;
  336. sidebar->box.size.x = 200;
  337. addChild(sidebar);
  338. modelLabel = new ui::Label;
  339. // modelLabel->fontSize = 16;
  340. // modelLabel->box.size.x = 400;
  341. addChild(modelLabel);
  342. modelScroll = new ui::ScrollWidget;
  343. addChild(modelScroll);
  344. modelMargin = new ui::MarginLayout;
  345. modelMargin->margin = math::Vec(10, 10);
  346. modelScroll->container->addChild(modelMargin);
  347. modelContainer = new ui::SequentialLayout;
  348. modelContainer->spacing = math::Vec(10, 10);
  349. modelMargin->addChild(modelContainer);
  350. resetModelContainer();
  351. clear();
  352. }
  353. void resetModelContainer() {
  354. modelContainer->clearChildren();
  355. // Iterate plugins
  356. for (plugin::Plugin* plugin : plugin::plugins) {
  357. // Get module slugs from module whitelist
  358. const auto& pluginIt = settings::moduleWhitelist.find(plugin->slug);
  359. // Iterate models in plugin
  360. for (plugin::Model* model : plugin->models) {
  361. // Don't show module if plugin whitelist exists but the module is not in it.
  362. if (pluginIt != settings::moduleWhitelist.end()) {
  363. auto moduleIt = std::find(pluginIt->second.begin(), pluginIt->second.end(), model->slug);
  364. if (moduleIt == pluginIt->second.end())
  365. continue;
  366. }
  367. // Create ModelBox
  368. ModelBox* modelBox = new ModelBox;
  369. modelBox->setModel(model);
  370. modelContainer->addChild(modelBox);
  371. }
  372. }
  373. }
  374. void step() override {
  375. box = parent->box.zeroPos().grow(math::Vec(-70, -70));
  376. sidebar->box.size.y = box.size.y;
  377. modelLabel->box.pos = sidebar->box.getTopRight().plus(math::Vec(5, 5));
  378. modelScroll->box.pos = sidebar->box.getTopRight().plus(math::Vec(0, 30));
  379. modelScroll->box.size = box.size.minus(modelScroll->box.pos);
  380. modelMargin->box.size.x = modelScroll->box.size.x;
  381. modelMargin->box.size.y = modelContainer->getChildrenBoundingBox().size.y + 2 * modelMargin->margin.y;
  382. OpaqueWidget::step();
  383. }
  384. void draw(const DrawArgs& args) override {
  385. bndMenuBackground(args.vg, 0.0, 0.0, box.size.x, box.size.y, 0);
  386. Widget::draw(args);
  387. }
  388. void refresh() {
  389. // Reset scroll position
  390. modelScroll->offset = math::Vec();
  391. // Filter ModelBoxes
  392. for (Widget* w : modelContainer->children) {
  393. ModelBox* m = dynamic_cast<ModelBox*>(w);
  394. assert(m);
  395. m->visible = isModelVisible(m->model, search, brand, tagId);
  396. }
  397. // Sort ModelBoxes
  398. modelContainer->children.sort([&](Widget * w1, Widget * w2) {
  399. ModelBox* m1 = dynamic_cast<ModelBox*>(w1);
  400. ModelBox* m2 = dynamic_cast<ModelBox*>(w2);
  401. // Sort by (modifiedTimestamp descending, plugin brand)
  402. auto t1 = std::make_tuple(-m1->model->plugin->modifiedTimestamp, m1->model->plugin->brand);
  403. auto t2 = std::make_tuple(-m2->model->plugin->modifiedTimestamp, m2->model->plugin->brand);
  404. return t1 < t2;
  405. });
  406. if (search.empty()) {
  407. // We've already sorted above
  408. }
  409. else {
  410. std::map<Widget*, float> scores;
  411. // Compute scores
  412. for (Widget* w : modelContainer->children) {
  413. ModelBox* m = dynamic_cast<ModelBox*>(w);
  414. assert(m);
  415. if (!m->visible)
  416. continue;
  417. scores[m] = modelScore(m->model, search);
  418. }
  419. // // Sort by score
  420. // modelContainer->children.sort([&](Widget *w1, Widget *w2) {
  421. // // If score was not computed, scores[w] returns 0, but this doesn't matter because those widgets aren't visible.
  422. // return get_default(scores, w1, 0.f) > get_default(scores, w2, 0.f);
  423. // });
  424. }
  425. // Filter the brand and tag lists
  426. // Get modules that would be filtered by just the search query
  427. std::vector<plugin::Model*> filteredModels;
  428. for (Widget* w : modelContainer->children) {
  429. ModelBox* m = dynamic_cast<ModelBox*>(w);
  430. assert(m);
  431. if (isModelVisible(m->model, search, "", -1))
  432. filteredModels.push_back(m->model);
  433. }
  434. auto hasModel = [&](const std::string & brand, int tagId) -> bool {
  435. for (plugin::Model* model : filteredModels) {
  436. if (isModelVisible(model, "", brand, tagId))
  437. return true;
  438. }
  439. return false;
  440. };
  441. // Enable brand and tag items that are available in visible ModelBoxes
  442. int brandsLen = 0;
  443. for (Widget* w : sidebar->brandList->children) {
  444. BrandItem* item = dynamic_cast<BrandItem*>(w);
  445. assert(item);
  446. item->disabled = !hasModel(item->text, tagId);
  447. if (!item->disabled)
  448. brandsLen++;
  449. }
  450. sidebar->brandLabel->text = string::f("Brands (%d)", brandsLen);
  451. int tagsLen = 0;
  452. for (Widget* w : sidebar->tagList->children) {
  453. TagItem* item = dynamic_cast<TagItem*>(w);
  454. assert(item);
  455. item->disabled = !hasModel(brand, item->tagId);
  456. if (!item->disabled)
  457. tagsLen++;
  458. }
  459. sidebar->tagLabel->text = string::f("Tags (%d)", tagsLen);
  460. // Count models
  461. int modelsLen = 0;
  462. for (Widget* w : modelContainer->children) {
  463. if (w->visible)
  464. modelsLen++;
  465. }
  466. modelLabel->text = string::f("Modules (%d) Click and drag a module to place it in the rack.", modelsLen);
  467. }
  468. void clear() {
  469. search = "";
  470. sidebar->searchField->setText("");
  471. brand = "";
  472. tagId = -1;
  473. refresh();
  474. }
  475. void onShow(const event::Show& e) override {
  476. refresh();
  477. OpaqueWidget::onShow(e);
  478. }
  479. };
  480. // Implementations to resolve dependencies
  481. inline void BrandItem::onAction(const event::Action& e) {
  482. ModuleBrowser* browser = getAncestorOfType<ModuleBrowser>();
  483. if (browser->brand == text)
  484. browser->brand = "";
  485. else
  486. browser->brand = text;
  487. browser->refresh();
  488. }
  489. inline void BrandItem::step() {
  490. MenuItem::step();
  491. ModuleBrowser* browser = getAncestorOfType<ModuleBrowser>();
  492. active = (browser->brand == text);
  493. }
  494. inline void TagItem::onAction(const event::Action& e) {
  495. ModuleBrowser* browser = getAncestorOfType<ModuleBrowser>();
  496. if (browser->tagId == tagId)
  497. browser->tagId = -1;
  498. else
  499. browser->tagId = tagId;
  500. browser->refresh();
  501. }
  502. inline void TagItem::step() {
  503. MenuItem::step();
  504. ModuleBrowser* browser = getAncestorOfType<ModuleBrowser>();
  505. active = (browser->tagId == tagId);
  506. }
  507. inline void BrowserSearchField::onSelectKey(const event::SelectKey& e) {
  508. if (e.action == GLFW_PRESS || e.action == GLFW_REPEAT) {
  509. switch (e.key) {
  510. case GLFW_KEY_ESCAPE: {
  511. BrowserOverlay* overlay = getAncestorOfType<BrowserOverlay>();
  512. overlay->hide();
  513. e.consume(this);
  514. } break;
  515. case GLFW_KEY_BACKSPACE: {
  516. if (text == "") {
  517. ModuleBrowser* browser = getAncestorOfType<ModuleBrowser>();
  518. browser->clear();
  519. e.consume(this);
  520. }
  521. } break;
  522. }
  523. }
  524. if (!e.getTarget())
  525. ui::TextField::onSelectKey(e);
  526. }
  527. inline void BrowserSearchField::onChange(const event::Change& e) {
  528. ModuleBrowser* browser = getAncestorOfType<ModuleBrowser>();
  529. browser->search = string::trim(text);
  530. browser->refresh();
  531. }
  532. inline void BrowserSearchField::onAction(const event::Action& e) {
  533. // Get first ModelBox
  534. ModelBox* mb = NULL;
  535. ModuleBrowser* browser = getAncestorOfType<ModuleBrowser>();
  536. for (Widget* w : browser->modelContainer->children) {
  537. if (w->visible) {
  538. mb = dynamic_cast<ModelBox*>(w);
  539. break;
  540. }
  541. }
  542. if (mb) {
  543. chooseModel(mb->model);
  544. }
  545. }
  546. inline void ClearButton::onAction(const event::Action& e) {
  547. ModuleBrowser* browser = getAncestorOfType<ModuleBrowser>();
  548. browser->clear();
  549. }
  550. // Global functions
  551. widget::Widget* moduleBrowserCreate() {
  552. BrowserOverlay* overlay = new BrowserOverlay;
  553. ModuleBrowser* browser = new ModuleBrowser;
  554. overlay->addChild(browser);
  555. return overlay;
  556. }
  557. } // namespace app
  558. } // namespace rack