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.

680 lines
17KB

  1. #include "app/ModuleBrowser.hpp"
  2. #include "widget/OpaqueWidget.hpp"
  3. #include "widget/OverlayWidget.hpp"
  4. #include "widget/TransparentWidget.hpp"
  5. #include "widget/ZoomWidget.hpp"
  6. #include "ui/ScrollWidget.hpp"
  7. #include "ui/SequentialLayout.hpp"
  8. #include "ui/MarginLayout.hpp"
  9. #include "ui/Label.hpp"
  10. #include "ui/TextField.hpp"
  11. #include "ui/MenuOverlay.hpp"
  12. #include "ui/List.hpp"
  13. #include "ui/MenuItem.hpp"
  14. #include "ui/Button.hpp"
  15. #include "ui/RadioButton.hpp"
  16. #include "ui/ChoiceButton.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 <set>
  26. #include <algorithm>
  27. namespace rack {
  28. namespace app {
  29. static float modelScore(plugin::Model *model, const std::string &search) {
  30. if (search.empty())
  31. return true;
  32. std::string s;
  33. s += model->plugin->slug;
  34. s += " ";
  35. s += model->plugin->author;
  36. s += " ";
  37. s += model->plugin->name;
  38. s += " ";
  39. s += model->slug;
  40. s += " ";
  41. s += model->name;
  42. // for (const std::string &tag : model->tags) {
  43. // s += " ";
  44. // s += tag;
  45. // }
  46. float score = string::fuzzyScore(s, search);
  47. return score;
  48. }
  49. struct BrowserOverlay : widget::OverlayWidget {
  50. void step() override {
  51. box = parent->box.zeroPos();
  52. // Only step if visible, since there are potentially thousands of descendants that don't need to be stepped.
  53. if (visible)
  54. OverlayWidget::step();
  55. }
  56. void onButton(const widget::ButtonEvent &e) override {
  57. OverlayWidget::onButton(e);
  58. if (e.getConsumed() != this)
  59. return;
  60. if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT) {
  61. hide();
  62. }
  63. }
  64. };
  65. struct InfoBox : widget::Widget {
  66. void setModel(plugin::Model *model) {
  67. math::Vec pos;
  68. // Name label
  69. ui::Label *nameLabel = new ui::Label;
  70. // nameLabel->box.size.x = box.size.x;
  71. nameLabel->box.pos = pos;
  72. nameLabel->text = model->name;
  73. addChild(nameLabel);
  74. pos = nameLabel->box.getBottomLeft();
  75. // Plugin label
  76. ui::Label *pluginLabel = new ui::Label;
  77. // pluginLabel->box.size.x = box.size.x;
  78. pluginLabel->box.pos = pos;
  79. pluginLabel->text = model->plugin->name;
  80. addChild(pluginLabel);
  81. pos = pluginLabel->box.getBottomLeft();
  82. ui::Label *descriptionLabel = new ui::Label;
  83. descriptionLabel->box.size.x = box.size.x;
  84. descriptionLabel->box.pos = pos;
  85. descriptionLabel->text = model->description;
  86. addChild(descriptionLabel);
  87. pos = descriptionLabel->box.getBottomLeft();
  88. // for (const std::string &tag : model->tags) {
  89. // ui::Button *tagButton = new ui::Button;
  90. // tagButton->box.size.x = box.size.x;
  91. // tagButton->box.pos = pos;
  92. // tagButton->text = tag;
  93. // addChild(tagButton);
  94. // pos = tagButton->box.getTopLeft();
  95. // }
  96. }
  97. void draw(const DrawArgs &args) override {
  98. nvgBeginPath(args.vg);
  99. nvgRect(args.vg, 0, 0, box.size.x, box.size.y);
  100. nvgFillColor(args.vg, nvgRGBAf(1, 1, 1, 0.5));
  101. nvgFill(args.vg);
  102. Widget::draw(args);
  103. }
  104. };
  105. struct ModelFavoriteQuantity : ui::Quantity {
  106. plugin::Model *model;
  107. std::string getLabel() override {return "★";}
  108. void setValue(float value) override {
  109. if (value) {
  110. settings.favoriteModels.insert(model);
  111. }
  112. else {
  113. auto it = settings.favoriteModels.find(model);
  114. if (it != settings.favoriteModels.end())
  115. settings.favoriteModels.erase(it);
  116. }
  117. }
  118. float getValue() override {
  119. auto it = settings.favoriteModels.find(model);
  120. return (it != settings.favoriteModels.end());
  121. }
  122. };
  123. static const float MODEL_BOX_ZOOM = 0.5f;
  124. struct ModelBox : widget::OpaqueWidget {
  125. plugin::Model *model;
  126. InfoBox *infoBox;
  127. widget::Widget *previewWidget;
  128. ui::RadioButton *favoriteButton;
  129. /** Lazily created */
  130. widget::FramebufferWidget *previewFb = NULL;
  131. /** Number of frames since draw() has been called */
  132. int visibleFrames = 0;
  133. ModelBox() {
  134. // Approximate size as 10HP before we know the actual size.
  135. // 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.
  136. box.size.x = 10 * RACK_GRID_WIDTH * MODEL_BOX_ZOOM;
  137. box.size.y = RACK_GRID_HEIGHT * MODEL_BOX_ZOOM;
  138. box.size = box.size.ceil();
  139. }
  140. void setModel(plugin::Model *model) {
  141. this->model = model;
  142. previewWidget = new widget::TransparentWidget;
  143. previewWidget->box.size.y = std::ceil(RACK_GRID_HEIGHT * MODEL_BOX_ZOOM);
  144. addChild(previewWidget);
  145. infoBox = new InfoBox;
  146. infoBox->box.size = math::Vec(100, 100);
  147. // infoBox->setModel(model);
  148. infoBox->hide();
  149. addChild(infoBox);
  150. // Favorite button
  151. favoriteButton = new ui::RadioButton;
  152. ModelFavoriteQuantity *favoriteQuantity = new ModelFavoriteQuantity;
  153. favoriteQuantity->model = model;
  154. favoriteButton->quantity = favoriteQuantity;
  155. favoriteButton->box.pos.y = box.size.y;
  156. box.size.y += favoriteButton->box.size.y;
  157. addChild(favoriteButton);
  158. }
  159. void createPreview() {
  160. previewFb = new widget::FramebufferWidget;
  161. if (math::isNear(APP->window->pixelRatio, 1.0)) {
  162. // Small details draw poorly at low DPI, so oversample when drawing to the framebuffer
  163. previewFb->oversample = 2.0;
  164. }
  165. previewWidget->addChild(previewFb);
  166. widget::ZoomWidget *zoomWidget = new widget::ZoomWidget;
  167. zoomWidget->setZoom(MODEL_BOX_ZOOM);
  168. previewFb->addChild(zoomWidget);
  169. ModuleWidget *moduleWidget = model->createModuleWidgetNull();
  170. zoomWidget->addChild(moduleWidget);
  171. zoomWidget->box.size.x = moduleWidget->box.size.x * MODEL_BOX_ZOOM;
  172. zoomWidget->box.size.y = RACK_GRID_HEIGHT * MODEL_BOX_ZOOM;
  173. previewWidget->box.size.x = std::ceil(zoomWidget->box.size.x);
  174. infoBox->box.size = previewWidget->box.size;
  175. favoriteButton->box.size.x = previewWidget->box.size.x;
  176. box.size.x = previewWidget->box.size.x;
  177. }
  178. void deletePreview() {
  179. assert(previewFb);
  180. previewWidget->removeChild(previewFb);
  181. delete previewFb;
  182. previewFb = NULL;
  183. }
  184. void step() override {
  185. if (previewFb && ++visibleFrames >= 60) {
  186. deletePreview();
  187. }
  188. OpaqueWidget::step();
  189. }
  190. void draw(const DrawArgs &args) override {
  191. visibleFrames = 0;
  192. // Lazily create preview when drawn
  193. if (!previewFb) {
  194. createPreview();
  195. }
  196. // Draw shadow
  197. nvgBeginPath(args.vg);
  198. float r = 10; // Blur radius
  199. float c = 10; // Corner radius
  200. nvgRect(args.vg, -r, -r, box.size.x + 2*r, box.size.y + 2*r);
  201. NVGcolor shadowColor = nvgRGBAf(0, 0, 0, 0.5);
  202. NVGcolor transparentColor = nvgRGBAf(0, 0, 0, 0);
  203. nvgFillPaint(args.vg, nvgBoxGradient(args.vg, 0, 0, box.size.x, box.size.y, c, r, shadowColor, transparentColor));
  204. nvgFill(args.vg);
  205. OpaqueWidget::draw(args);
  206. }
  207. void onButton(const widget::ButtonEvent &e) override;
  208. void onEnter(const widget::EnterEvent &e) override {
  209. e.consume(this);
  210. infoBox->show();
  211. }
  212. void onLeave(const widget::LeaveEvent &e) override {
  213. infoBox->hide();
  214. }
  215. };
  216. struct AuthorItem : ui::MenuItem {
  217. void onAction(const widget::ActionEvent &e) override;
  218. };
  219. struct TagItem : ui::MenuItem {
  220. void onAction(const widget::ActionEvent &e) 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 widget::SelectKeyEvent &e) override {
  229. if (e.action == GLFW_PRESS) {
  230. if (e.key == GLFW_KEY_ESCAPE) {
  231. BrowserOverlay *overlay = getAncestorOfType<BrowserOverlay>();
  232. overlay->hide();
  233. e.consume(this);
  234. }
  235. }
  236. if (!e.getConsumed())
  237. ui::TextField::onSelectKey(e);
  238. }
  239. void onChange(const widget::ChangeEvent &e) override;
  240. void onHide(const widget::HideEvent &e) override {
  241. APP->event->setSelected(NULL);
  242. ui::TextField::onHide(e);
  243. }
  244. void onShow(const widget::ShowEvent &e) override {
  245. selectAll();
  246. TextField::onShow(e);
  247. }
  248. };
  249. struct ShowFavoritesQuantity : ui::Quantity {
  250. widget::Widget *widget;
  251. std::string getLabel() override {
  252. int favoritesLen = settings.favoriteModels.size();
  253. return string::f("Only show favorites (%d)", favoritesLen);
  254. }
  255. void setValue(float value) override;
  256. float getValue() override;
  257. };
  258. struct BrowserSidebar : widget::Widget {
  259. BrowserSearchField *searchField;
  260. ui::RadioButton *favoriteButton;
  261. ui::Label *authorLabel;
  262. ui::List *authorList;
  263. ui::ScrollWidget *authorScroll;
  264. ui::Label *tagLabel;
  265. ui::List *tagList;
  266. ui::ScrollWidget *tagScroll;
  267. BrowserSidebar() {
  268. searchField = new BrowserSearchField;
  269. addChild(searchField);
  270. favoriteButton = new ui::RadioButton;
  271. ShowFavoritesQuantity *favoriteQuantity = new ShowFavoritesQuantity;
  272. favoriteQuantity->widget = favoriteButton;
  273. favoriteButton->quantity = favoriteQuantity;
  274. addChild(favoriteButton);
  275. authorLabel = new ui::Label;
  276. // authorLabel->fontSize = 16;
  277. authorLabel->color = nvgRGB(0x80, 0x80, 0x80);
  278. authorLabel->text = "Authors";
  279. addChild(authorLabel);
  280. // Plugin list
  281. authorScroll = new ui::ScrollWidget;
  282. addChild(authorScroll);
  283. authorList = new ui::List;
  284. authorScroll->container->addChild(authorList);
  285. std::set<std::string, string::CaseInsensitiveCompare> authorNames;
  286. for (plugin::Plugin *plugin : plugin::plugins) {
  287. authorNames.insert(plugin->author);
  288. }
  289. for (const std::string &authorName : authorNames) {
  290. AuthorItem *item = new AuthorItem;
  291. item->text = authorName;
  292. authorList->addChild(item);
  293. }
  294. tagLabel = new ui::Label;
  295. // tagLabel->fontSize = 16;
  296. tagLabel->color = nvgRGB(0x80, 0x80, 0x80);
  297. tagLabel->text = "Tags";
  298. addChild(tagLabel);
  299. // Tag list
  300. tagScroll = new ui::ScrollWidget;
  301. addChild(tagScroll);
  302. tagList = new ui::List;
  303. tagScroll->container->addChild(tagList);
  304. for (const std::string &tag : plugin::allowedTags) {
  305. TagItem *item = new TagItem;
  306. item->text = tag;
  307. tagList->addChild(item);
  308. }
  309. }
  310. void step() override {
  311. searchField->box.size.x = box.size.x;
  312. favoriteButton->box.pos = searchField->box.getBottomLeft();
  313. favoriteButton->box.size.x = box.size.x;
  314. float listHeight = (box.size.y - favoriteButton->box.getBottom()) / 2;
  315. listHeight = std::floor(listHeight);
  316. authorLabel->box.pos = favoriteButton->box.getBottomLeft();
  317. authorLabel->box.size.x = box.size.x;
  318. authorScroll->box.pos = authorLabel->box.getBottomLeft();
  319. authorScroll->box.size.y = listHeight - authorLabel->box.size.y;
  320. authorScroll->box.size.x = box.size.x;
  321. authorList->box.size.x = authorScroll->box.size.x;
  322. tagLabel->box.pos = authorScroll->box.getBottomLeft();
  323. tagLabel->box.size.x = box.size.x;
  324. tagScroll->box.pos = tagLabel->box.getBottomLeft();
  325. tagScroll->box.size.y = listHeight - tagLabel->box.size.y;
  326. tagScroll->box.size.x = box.size.x;
  327. tagList->box.size.x = tagScroll->box.size.x;
  328. Widget::step();
  329. }
  330. };
  331. struct ModuleBrowser : widget::OpaqueWidget {
  332. BrowserSidebar *sidebar;
  333. ui::ScrollWidget *modelScroll;
  334. ui::Label *modelLabel;
  335. ui::MarginLayout *modelMargin;
  336. ui::SequentialLayout *modelContainer;
  337. std::string search;
  338. std::string author;
  339. std::string tag;
  340. bool favorites = false;
  341. ModuleBrowser() {
  342. sidebar = new BrowserSidebar;
  343. sidebar->box.size.x = 200;
  344. addChild(sidebar);
  345. modelScroll = new ui::ScrollWidget;
  346. addChild(modelScroll);
  347. modelLabel = new ui::Label;
  348. // modelLabel->fontSize = 16;
  349. modelLabel->box.pos = math::Vec(10, 10);
  350. modelScroll->container->addChild(modelLabel);
  351. modelMargin = new ui::MarginLayout;
  352. modelMargin->box.pos = modelLabel->box.getBottomLeft();
  353. modelMargin->margin = math::Vec(10, 10);
  354. modelScroll->container->addChild(modelMargin);
  355. modelContainer = new ui::SequentialLayout;
  356. modelContainer->spacing = math::Vec(10, 10);
  357. modelMargin->addChild(modelContainer);
  358. // Add ModelBoxes for each Model
  359. for (plugin::Plugin *plugin : plugin::plugins) {
  360. for (plugin::Model *model : plugin->models) {
  361. ModelBox *moduleBox = new ModelBox;
  362. moduleBox->setModel(model);
  363. modelContainer->addChild(moduleBox);
  364. }
  365. }
  366. refresh();
  367. }
  368. void step() override {
  369. box = parent->box.zeroPos().grow(math::Vec(-70, -70));
  370. sidebar->box.size.y = box.size.y;
  371. modelScroll->box.pos.x = sidebar->box.size.x;
  372. modelScroll->box.size.x = box.size.x - sidebar->box.size.x;
  373. modelScroll->box.size.y = box.size.y;
  374. modelMargin->box.size.x = modelScroll->box.size.x;
  375. modelMargin->box.size.y = modelContainer->getChildrenBoundingBox().size.y + 2 * modelMargin->margin.y;
  376. OpaqueWidget::step();
  377. }
  378. void draw(const DrawArgs &args) override {
  379. bndMenuBackground(args.vg, 0.0, 0.0, box.size.x, box.size.y, 0);
  380. Widget::draw(args);
  381. }
  382. void refresh() {
  383. // Reset scroll position
  384. modelScroll->offset = math::Vec();
  385. // Show all or only favorites
  386. for (Widget *w : modelContainer->children) {
  387. if (favorites) {
  388. ModelBox *m = dynamic_cast<ModelBox*>(w);
  389. auto it = settings.favoriteModels.find(m->model);
  390. w->visible = (it != settings.favoriteModels.end());
  391. }
  392. else {
  393. w->visible = true;
  394. }
  395. }
  396. if (search.empty()) {
  397. // Sort by plugin name and then module name
  398. modelContainer->children.sort([&](Widget *w1, Widget *w2) {
  399. ModelBox *m1 = dynamic_cast<ModelBox*>(w1);
  400. ModelBox *m2 = dynamic_cast<ModelBox*>(w2);
  401. if (m1->model->plugin->name != m2->model->plugin->name)
  402. return m1->model->plugin->name < m2->model->plugin->name;
  403. return m1->model->name < m2->model->name;
  404. });
  405. }
  406. else {
  407. std::map<Widget*, float> scores;
  408. // Compute scores and filter visibility
  409. for (Widget *w : modelContainer->children) {
  410. ModelBox *m = dynamic_cast<ModelBox*>(w);
  411. assert(m);
  412. float score = 0.f;
  413. if (m->visible) {
  414. score = modelScore(m->model, search);
  415. m->visible = (score > 0);
  416. }
  417. scores[m] = score;
  418. }
  419. // Sort by score
  420. modelContainer->children.sort([&](Widget *w1, Widget *w2) {
  421. return scores[w1] > scores[w2];
  422. });
  423. }
  424. // Filter ModelBoxes by author
  425. if (!author.empty()) {
  426. for (Widget *w : modelContainer->children) {
  427. if (!w->visible)
  428. continue;
  429. ModelBox *m = dynamic_cast<ModelBox*>(w);
  430. assert(m);
  431. if (m->model->plugin->author != author)
  432. m->visible = false;
  433. }
  434. }
  435. // Filter ModelBoxes by tag
  436. if (!tag.empty()) {
  437. for (Widget *w : modelContainer->children) {
  438. if (!w->visible)
  439. continue;
  440. ModelBox *m = dynamic_cast<ModelBox*>(w);
  441. assert(m);
  442. bool found = false;
  443. for (const std::string &tag : m->model->tags) {
  444. if (tag == this->tag) {
  445. found = true;
  446. break;
  447. }
  448. }
  449. if (!found)
  450. m->visible = false;
  451. }
  452. }
  453. std::set<std::string> enabledAuthors;
  454. std::set<std::string> enabledTags;
  455. // Get list of enabled authors and tags for sidebar
  456. for (Widget *w : modelContainer->children) {
  457. ModelBox *m = dynamic_cast<ModelBox*>(w);
  458. assert(m);
  459. if (!m->visible)
  460. continue;
  461. enabledAuthors.insert(m->model->plugin->author);
  462. for (const std::string &tag : m->model->tags) {
  463. enabledTags.insert(tag);
  464. }
  465. }
  466. // Count models
  467. int modelsLen = 0;
  468. for (Widget *w : modelContainer->children) {
  469. if (w->visible)
  470. modelsLen++;
  471. }
  472. modelLabel->text = string::f("Modules (%d)", modelsLen);
  473. // Enable author and tag items that are available in visible ModelBoxes
  474. int authorsLen = 0;
  475. for (Widget *w : sidebar->authorList->children) {
  476. AuthorItem *item = dynamic_cast<AuthorItem*>(w);
  477. assert(item);
  478. auto it = enabledAuthors.find(item->text);
  479. item->disabled = (it == enabledAuthors.end());
  480. if (!item->disabled)
  481. authorsLen++;
  482. }
  483. sidebar->authorLabel->text = string::f("Authors (%d)", authorsLen);
  484. int tagsLen = 0;
  485. for (Widget *w : sidebar->tagList->children) {
  486. TagItem *item = dynamic_cast<TagItem*>(w);
  487. assert(item);
  488. auto it = enabledTags.find(item->text);
  489. item->disabled = (it == enabledTags.end());
  490. if (!item->disabled)
  491. tagsLen++;
  492. }
  493. sidebar->tagLabel->text = string::f("Tags (%d)", tagsLen);
  494. }
  495. };
  496. // Implementations to resolve dependencies
  497. inline void ModelBox::onButton(const widget::ButtonEvent &e) {
  498. OpaqueWidget::onButton(e);
  499. if (e.getConsumed() != this)
  500. return;
  501. if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT) {
  502. // Create module
  503. ModuleWidget *moduleWidget = model->createModuleWidget();
  504. assert(moduleWidget);
  505. APP->scene->rack->addModuleAtMouse(moduleWidget);
  506. // Pretend the moduleWidget was clicked so it can be dragged in the RackWidget
  507. e.consume(moduleWidget);
  508. // Hide Module Browser
  509. BrowserOverlay *overlay = getAncestorOfType<BrowserOverlay>();
  510. overlay->hide();
  511. // Push ModuleAdd history action
  512. history::ModuleAdd *h = new history::ModuleAdd;
  513. h->name = "create module";
  514. h->setModule(moduleWidget);
  515. APP->history->push(h);
  516. }
  517. }
  518. inline void AuthorItem::onAction(const widget::ActionEvent &e) {
  519. ModuleBrowser *browser = getAncestorOfType<ModuleBrowser>();
  520. if (browser->author == text)
  521. browser->author = "";
  522. else
  523. browser->author = text;
  524. browser->refresh();
  525. }
  526. inline void TagItem::onAction(const widget::ActionEvent &e) {
  527. ModuleBrowser *browser = getAncestorOfType<ModuleBrowser>();
  528. if (browser->tag == text)
  529. browser->tag = "";
  530. else
  531. browser->tag = text;
  532. browser->refresh();
  533. }
  534. inline void BrowserSearchField::onChange(const widget::ChangeEvent &e) {
  535. ModuleBrowser *browser = getAncestorOfType<ModuleBrowser>();
  536. browser->search = string::trim(text);
  537. browser->refresh();
  538. }
  539. inline void ShowFavoritesQuantity::setValue(float value) {
  540. ModuleBrowser *browser = widget->getAncestorOfType<ModuleBrowser>();
  541. browser->favorites = (bool) value;
  542. browser->refresh();
  543. }
  544. inline float ShowFavoritesQuantity::getValue() {
  545. ModuleBrowser *browser = widget->getAncestorOfType<ModuleBrowser>();
  546. return browser->favorites;
  547. }
  548. // Global functions
  549. widget::Widget *moduleBrowserCreate() {
  550. BrowserOverlay *overlay = new BrowserOverlay;
  551. ModuleBrowser *browser = new ModuleBrowser;
  552. overlay->addChild(browser);
  553. return overlay;
  554. }
  555. } // namespace app
  556. } // namespace rack