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.

663 lines
16KB

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