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.

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