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.

682 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 <plugin/Model.hpp>
  22. #include <string.hpp>
  23. #include <history.hpp>
  24. #include <settings.hpp>
  25. #include <tag.hpp>
  26. #include <set>
  27. #include <algorithm>
  28. namespace rack {
  29. namespace app {
  30. // Static functions
  31. static float modelScore(plugin::Model* model, const std::string& search) {
  32. if (search.empty())
  33. return 1.f;
  34. std::string s;
  35. s += model->plugin->brand;
  36. s += " ";
  37. s += model->plugin->name;
  38. s += " ";
  39. s += model->name;
  40. s += " ";
  41. s += model->slug;
  42. for (int tagId : model->tags) {
  43. // Add all aliases of a tag
  44. for (const std::string& alias : tag::tagAliases[tagId]) {
  45. s += " ";
  46. s += alias;
  47. }
  48. }
  49. float score = string::fuzzyScore(string::lowercase(s), string::lowercase(search));
  50. return score;
  51. }
  52. static bool isModelVisible(plugin::Model* model, const std::string& search, const std::string& brand, int tagId) {
  53. // Filter search query
  54. if (search != "") {
  55. float score = modelScore(model, search);
  56. if (score <= 0.f)
  57. return false;
  58. }
  59. // Filter brand
  60. if (brand != "") {
  61. if (model->plugin->brand != brand)
  62. return false;
  63. }
  64. // Filter tag
  65. if (tagId >= 0) {
  66. auto it = std::find(model->tags.begin(), model->tags.end(), tagId);
  67. if (it == model->tags.end())
  68. return false;
  69. }
  70. return true;
  71. }
  72. static ModuleWidget* chooseModel(plugin::Model* model) {
  73. // Create module
  74. ModuleWidget* moduleWidget = model->createModuleWidget();
  75. assert(moduleWidget);
  76. APP->scene->rack->addModuleAtMouse(moduleWidget);
  77. // Push ModuleAdd history action
  78. history::ModuleAdd* h = new history::ModuleAdd;
  79. h->name = "create module";
  80. h->setModule(moduleWidget);
  81. APP->history->push(h);
  82. // Hide Module Browser
  83. APP->scene->moduleBrowser->hide();
  84. return moduleWidget;
  85. }
  86. template <typename K, typename V>
  87. V get_default(const std::map<K, V>& m, const K& key, const V& def) {
  88. auto it = m.find(key);
  89. if (it == m.end())
  90. return def;
  91. return it->second;
  92. }
  93. // Widgets
  94. struct BrowserOverlay : widget::OpaqueWidget {
  95. void step() override {
  96. box = parent->box.zeroPos();
  97. // Only step if visible, since there are potentially thousands of descendants that don't need to be stepped.
  98. if (visible)
  99. OpaqueWidget::step();
  100. }
  101. void onButton(const event::Button& e) override {
  102. OpaqueWidget::onButton(e);
  103. if (e.getTarget() != this)
  104. return;
  105. if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT) {
  106. hide();
  107. e.consume(this);
  108. }
  109. }
  110. };
  111. static const float MODEL_BOX_ZOOM = 0.5f;
  112. struct ModelBox : widget::OpaqueWidget {
  113. plugin::Model* model;
  114. widget::Widget* previewWidget;
  115. ui::Tooltip* tooltip = NULL;
  116. /** Lazily created */
  117. widget::FramebufferWidget* previewFb = NULL;
  118. /** Number of frames since draw() has been called */
  119. int visibleFrames = 0;
  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->createModuleWidgetNull();
  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 deletePreview() {
  151. assert(previewFb);
  152. previewWidget->removeChild(previewFb);
  153. delete previewFb;
  154. previewFb = NULL;
  155. }
  156. void step() override {
  157. if (previewFb && ++visibleFrames >= 60) {
  158. deletePreview();
  159. }
  160. OpaqueWidget::step();
  161. }
  162. void draw(const DrawArgs& args) override {
  163. visibleFrames = 0;
  164. // Lazily create preview when drawn
  165. if (!previewFb) {
  166. createPreview();
  167. }
  168. // Draw shadow
  169. nvgBeginPath(args.vg);
  170. float r = 10; // Blur radius
  171. float c = 10; // Corner radius
  172. nvgRect(args.vg, -r, -r, box.size.x + 2 * r, box.size.y + 2 * r);
  173. NVGcolor shadowColor = nvgRGBAf(0, 0, 0, 0.5);
  174. NVGcolor transparentColor = nvgRGBAf(0, 0, 0, 0);
  175. nvgFillPaint(args.vg, nvgBoxGradient(args.vg, 0, 0, box.size.x, box.size.y, c, r, shadowColor, transparentColor));
  176. nvgFill(args.vg);
  177. OpaqueWidget::draw(args);
  178. }
  179. void setTooltip(ui::Tooltip* tooltip) {
  180. if (this->tooltip) {
  181. this->tooltip->parent->removeChild(this->tooltip);
  182. delete this->tooltip;
  183. this->tooltip = NULL;
  184. }
  185. if (tooltip) {
  186. APP->scene->addChild(tooltip);
  187. this->tooltip = tooltip;
  188. }
  189. }
  190. void onButton(const event::Button& e) override {
  191. OpaqueWidget::onButton(e);
  192. if (e.getTarget() != this)
  193. return;
  194. if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT) {
  195. ModuleWidget* mw = chooseModel(model);
  196. // Pretend the moduleWidget was clicked so it can be dragged in the RackWidget
  197. e.consume(mw);
  198. }
  199. }
  200. void onEnter(const event::Enter& e) override {
  201. std::string text;
  202. text = model->plugin->brand;
  203. text += " " + model->name;
  204. // Tags
  205. text += "\nTags: ";
  206. for (size_t i = 0; i < model->tags.size(); i++) {
  207. if (i > 0)
  208. text += ", ";
  209. int tagId = model->tags[i];
  210. text += tag::tagAliases[tagId][0];
  211. }
  212. // Description
  213. if (model->description != "") {
  214. text += "\n" + model->description;
  215. }
  216. ui::Tooltip* tooltip = new ui::Tooltip;
  217. tooltip->text = text;
  218. setTooltip(tooltip);
  219. }
  220. void onLeave(const event::Leave& e) override {
  221. setTooltip(NULL);
  222. }
  223. void onHide(const event::Hide& e) override {
  224. // Hide tooltip
  225. setTooltip(NULL);
  226. OpaqueWidget::onHide(e);
  227. }
  228. };
  229. struct BrandItem : ui::MenuItem {
  230. void onAction(const event::Action& e) override;
  231. void step() override;
  232. };
  233. struct TagItem : ui::MenuItem {
  234. int tagId;
  235. void onAction(const event::Action& e) override;
  236. void step() override;
  237. };
  238. struct BrowserSearchField : ui::TextField {
  239. void step() override {
  240. // Steal focus when step is called
  241. APP->event->setSelected(this);
  242. TextField::step();
  243. }
  244. void onSelectKey(const event::SelectKey& e) override;
  245. void onChange(const event::Change& e) override;
  246. void onAction(const event::Action& e) override;
  247. void onHide(const event::Hide& e) override {
  248. APP->event->setSelected(NULL);
  249. ui::TextField::onHide(e);
  250. }
  251. void onShow(const event::Show& e) override {
  252. selectAll();
  253. TextField::onShow(e);
  254. }
  255. };
  256. struct ClearButton : ui::Button {
  257. void onAction(const event::Action& e) override;
  258. };
  259. struct BrowserSidebar : widget::Widget {
  260. BrowserSearchField* searchField;
  261. ClearButton* clearButton;
  262. ui::Label* tagLabel;
  263. ui::List* tagList;
  264. ui::ScrollWidget* tagScroll;
  265. ui::Label* brandLabel;
  266. ui::List* brandList;
  267. ui::ScrollWidget* brandScroll;
  268. BrowserSidebar() {
  269. // Search
  270. searchField = new BrowserSearchField;
  271. addChild(searchField);
  272. // Clear filters
  273. clearButton = new ClearButton;
  274. clearButton->text = "Reset filters";
  275. addChild(clearButton);
  276. // Tag label
  277. tagLabel = new ui::Label;
  278. // tagLabel->fontSize = 16;
  279. tagLabel->color = nvgRGB(0x80, 0x80, 0x80);
  280. tagLabel->text = "Tags";
  281. addChild(tagLabel);
  282. // Tag list
  283. tagScroll = new ui::ScrollWidget;
  284. addChild(tagScroll);
  285. tagList = new ui::List;
  286. tagScroll->container->addChild(tagList);
  287. for (int tagId = 0; tagId < (int) tag::tagAliases.size(); tagId++) {
  288. TagItem* item = new TagItem;
  289. item->text = tag::tagAliases[tagId][0];
  290. item->tagId = tagId;
  291. tagList->addChild(item);
  292. }
  293. // Brand label
  294. brandLabel = new ui::Label;
  295. // brandLabel->fontSize = 16;
  296. brandLabel->color = nvgRGB(0x80, 0x80, 0x80);
  297. brandLabel->text = "Brands";
  298. addChild(brandLabel);
  299. // Brand list
  300. brandScroll = new ui::ScrollWidget;
  301. addChild(brandScroll);
  302. brandList = new ui::List;
  303. brandScroll->container->addChild(brandList);
  304. // Collect brands from all plugins
  305. std::set<std::string, string::CaseInsensitiveCompare> brands;
  306. for (plugin::Plugin* plugin : plugin::plugins) {
  307. brands.insert(plugin->brand);
  308. }
  309. for (const std::string& brand : brands) {
  310. BrandItem* item = new BrandItem;
  311. item->text = brand;
  312. brandList->addChild(item);
  313. }
  314. }
  315. void step() override {
  316. searchField->box.size.x = box.size.x;
  317. clearButton->box.pos = searchField->box.getBottomLeft();
  318. clearButton->box.size.x = box.size.x;
  319. float listHeight = (box.size.y - clearButton->box.getBottom()) / 2;
  320. listHeight = std::floor(listHeight);
  321. tagLabel->box.pos = clearButton->box.getBottomLeft();
  322. tagLabel->box.size.x = box.size.x;
  323. tagScroll->box.pos = tagLabel->box.getBottomLeft();
  324. tagScroll->box.size.y = listHeight - tagLabel->box.size.y;
  325. tagScroll->box.size.x = box.size.x;
  326. tagList->box.size.x = tagScroll->box.size.x;
  327. brandLabel->box.pos = tagScroll->box.getBottomLeft();
  328. brandLabel->box.size.x = box.size.x;
  329. brandScroll->box.pos = brandLabel->box.getBottomLeft();
  330. brandScroll->box.size.y = listHeight - brandLabel->box.size.y;
  331. brandScroll->box.size.x = box.size.x;
  332. brandList->box.size.x = brandScroll->box.size.x;
  333. Widget::step();
  334. }
  335. };
  336. struct ModuleBrowser : widget::OpaqueWidget {
  337. BrowserSidebar* sidebar;
  338. ui::ScrollWidget* modelScroll;
  339. ui::Label* modelLabel;
  340. ui::MarginLayout* modelMargin;
  341. ui::SequentialLayout* modelContainer;
  342. std::string search;
  343. std::string brand;
  344. int tagId = -1;
  345. ModuleBrowser() {
  346. sidebar = new BrowserSidebar;
  347. sidebar->box.size.x = 200;
  348. addChild(sidebar);
  349. modelLabel = new ui::Label;
  350. // modelLabel->fontSize = 16;
  351. // modelLabel->box.size.x = 400;
  352. addChild(modelLabel);
  353. modelScroll = new ui::ScrollWidget;
  354. addChild(modelScroll);
  355. modelMargin = new ui::MarginLayout;
  356. modelMargin->margin = math::Vec(10, 10);
  357. modelScroll->container->addChild(modelMargin);
  358. modelContainer = new ui::SequentialLayout;
  359. modelContainer->spacing = math::Vec(10, 10);
  360. modelMargin->addChild(modelContainer);
  361. // Add ModelBoxes for each Model
  362. for (plugin::Plugin* plugin : plugin::plugins) {
  363. for (plugin::Model* model : plugin->models) {
  364. ModelBox* moduleBox = new ModelBox;
  365. moduleBox->setModel(model);
  366. modelContainer->addChild(moduleBox);
  367. }
  368. }
  369. clear();
  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