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.

473 lines
12KB

  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/ChoiceButton.hpp"
  15. #include "app/ModuleWidget.hpp"
  16. #include "app/Scene.hpp"
  17. #include "plugin.hpp"
  18. #include "app.hpp"
  19. #include "plugin/Model.hpp"
  20. #include "string.hpp"
  21. #include "history.hpp"
  22. #include <set>
  23. #include <algorithm>
  24. namespace rack {
  25. namespace app {
  26. static std::set<plugin::Model*> sFavoriteModels;
  27. bool isMatch(const std::string &s, const std::string &search) {
  28. std::string s2 = string::lowercase(s);
  29. std::string search2 = string::lowercase(search);
  30. return (s2.find(search2) != std::string::npos);
  31. }
  32. static bool isModelMatch(plugin::Model *model, const std::string &search) {
  33. if (search.empty())
  34. return true;
  35. std::string s;
  36. s += model->plugin->slug;
  37. s += " ";
  38. s += model->plugin->author;
  39. s += " ";
  40. s += model->plugin->name;
  41. s += " ";
  42. s += model->slug;
  43. s += " ";
  44. s += model->name;
  45. for (const std::string &tag : model->tags) {
  46. // TODO Normalize tag
  47. s += tag;
  48. s += " ";
  49. }
  50. return isMatch(s, search);
  51. }
  52. struct BrowserOverlay : widget::OpaqueWidget {
  53. void step() override {
  54. box = parent->box.zeroPos();
  55. // Only step if visible, since there are potentially thousands of descendants that don't need to be stepped.
  56. if (visible)
  57. widget::OpaqueWidget::step();
  58. }
  59. void onButton(const event::Button &e) override {
  60. widget::OpaqueWidget::onButton(e);
  61. if (e.getConsumed() != this)
  62. return;
  63. if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT) {
  64. hide();
  65. }
  66. }
  67. };
  68. static const float MODEL_BOX_ZOOM = 1.0f;
  69. struct ModelBox : widget::OpaqueWidget {
  70. plugin::Model *model;
  71. widget::Widget *infoWidget;
  72. /** Lazily created */
  73. widget::Widget *previewWidget = NULL;
  74. /** Number of frames since draw() has been called */
  75. int visibleFrames = 0;
  76. bool selected = false;
  77. ModelBox() {
  78. // Approximate size as 10HP before we know the actual size.
  79. // 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.
  80. box.size.x = 10 * RACK_GRID_WIDTH * MODEL_BOX_ZOOM;
  81. box.size.y = RACK_GRID_HEIGHT * MODEL_BOX_ZOOM;
  82. box.size = box.size.ceil();
  83. }
  84. void setModel(plugin::Model *model) {
  85. this->model = model;
  86. infoWidget = new widget::Widget;
  87. infoWidget->hide();
  88. addChild(infoWidget);
  89. math::Vec pos;
  90. // Name label
  91. ui::Label *nameLabel = new ui::Label;
  92. // nameLabel->box.size.x = infoWidget->box.size.x;
  93. nameLabel->box.pos = pos;
  94. nameLabel->text = model->name;
  95. infoWidget->addChild(nameLabel);
  96. pos = nameLabel->box.getBottomLeft();
  97. // Plugin label
  98. ui::Label *pluginLabel = new ui::Label;
  99. // pluginLabel->box.size.x = infoWidget->box.size.x;
  100. pluginLabel->box.pos = pos;
  101. pluginLabel->text = model->plugin->name;
  102. infoWidget->addChild(pluginLabel);
  103. pos = pluginLabel->box.getBottomLeft();
  104. ui::Label *descriptionLabel = new ui::Label;
  105. descriptionLabel->box.size.x = infoWidget->box.size.x;
  106. descriptionLabel->box.pos = pos;
  107. descriptionLabel->text = model->description;
  108. infoWidget->addChild(descriptionLabel);
  109. pos = descriptionLabel->box.getBottomLeft();
  110. // for (const std::string &tag : model->tags) {
  111. // ui::Button *tagButton = new ui::Button;
  112. // tagButton->box.size.x = infoWidget->box.size.x;
  113. // tagButton->box.pos = pos;
  114. // tagButton->text = tag;
  115. // infoWidget->addChild(tagButton);
  116. // pos = tagButton->box.getTopLeft();
  117. // }
  118. // // Favorite button
  119. // ui::Button *favoriteButton = new ui::Button;
  120. // favoriteButton->box.size.x = box.size.x;
  121. // favoriteButton->box.pos = pos;
  122. // favoriteButton->box.pos.y -= favoriteButton->box.size.y;
  123. // favoriteButton->text = "★";
  124. // addChild(favoriteButton);
  125. // pos = favoriteButton->box.getTopLeft();
  126. }
  127. void createPreview() {
  128. assert(!previewWidget);
  129. previewWidget = new widget::TransparentWidget;
  130. previewWidget->box.size.y = std::ceil(RACK_GRID_HEIGHT * MODEL_BOX_ZOOM);
  131. addChild(previewWidget);
  132. widget::FramebufferWidget *fbWidget = 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. fbWidget->oversample = 2.0;
  136. }
  137. previewWidget->addChild(fbWidget);
  138. widget::ZoomWidget *zoomWidget = new widget::ZoomWidget;
  139. zoomWidget->setZoom(MODEL_BOX_ZOOM);
  140. fbWidget->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. infoWidget->box.size = previewWidget->box.size;
  147. box.size.x = previewWidget->box.size.x;
  148. }
  149. void deletePreview() {
  150. assert(previewWidget);
  151. removeChild(previewWidget);
  152. delete previewWidget;
  153. previewWidget = NULL;
  154. }
  155. void step() override {
  156. if (previewWidget && ++visibleFrames >= 60) {
  157. deletePreview();
  158. }
  159. }
  160. void draw(const DrawArgs &args) override {
  161. visibleFrames = 0;
  162. // Lazily create preview when drawn
  163. if (!previewWidget) {
  164. createPreview();
  165. }
  166. // Draw shadow
  167. nvgBeginPath(args.vg);
  168. float r = 10; // Blur radius
  169. float c = 10; // Corner radius
  170. nvgRect(args.vg, -r, -r, box.size.x + 2*r, box.size.y + 2*r);
  171. NVGcolor shadowColor = nvgRGBAf(0, 0, 0, 0.5);
  172. NVGcolor transparentColor = nvgRGBAf(0, 0, 0, 0);
  173. nvgFillPaint(args.vg, nvgBoxGradient(args.vg, 0, 0, box.size.x, box.size.y, c, r, shadowColor, transparentColor));
  174. nvgFill(args.vg);
  175. nvgScissor(args.vg, RECT_ARGS(args.clipBox));
  176. widget::OpaqueWidget::draw(args);
  177. nvgResetScissor(args.vg);
  178. // Translucent overlay when selected
  179. if (selected) {
  180. nvgBeginPath(args.vg);
  181. nvgRect(args.vg, 0.0, 0.0, box.size.x, box.size.y);
  182. nvgFillColor(args.vg, nvgRGBAf(1, 1, 1, 0.25));
  183. nvgFill(args.vg);
  184. }
  185. }
  186. void onButton(const event::Button &e) override;
  187. void onEnter(const event::Enter &e) override {
  188. e.consume(this);
  189. selected = true;
  190. }
  191. void onLeave(const event::Leave &e) override {
  192. selected = false;
  193. }
  194. };
  195. struct BrowserSearchField : ui::TextField {
  196. void step() override {
  197. // Steal focus when step is called
  198. APP->event->setSelected(this);
  199. ui::TextField::step();
  200. }
  201. void onSelectKey(const event::SelectKey &e) override {
  202. if (e.action == GLFW_PRESS) {
  203. if (e.key == GLFW_KEY_ESCAPE) {
  204. BrowserOverlay *overlay = getAncestorOfType<BrowserOverlay>();
  205. overlay->hide();
  206. e.consume(this);
  207. }
  208. }
  209. if (!e.getConsumed())
  210. ui::TextField::onSelectKey(e);
  211. }
  212. void onChange(const event::Change &e) override;
  213. void onHide(const event::Hide &e) override {
  214. setText("");
  215. APP->event->setSelected(NULL);
  216. ui::TextField::onHide(e);
  217. }
  218. };
  219. struct BrowserSidebar : widget::Widget {
  220. BrowserSearchField *searchField;
  221. ui::List *pluginList;
  222. ui::ScrollWidget *pluginScroll;
  223. ui::List *tagList;
  224. ui::ScrollWidget *tagScroll;
  225. BrowserSidebar() {
  226. searchField = new BrowserSearchField;
  227. addChild(searchField);
  228. // Plugin list
  229. pluginScroll = new ui::ScrollWidget;
  230. addChild(pluginScroll);
  231. pluginList = new ui::List;
  232. pluginScroll->container->addChild(pluginList);
  233. std::vector<std::string> pluginNames;
  234. for (plugin::Plugin *plugin : plugin::plugins) {
  235. pluginNames.push_back(plugin->name);
  236. }
  237. std::sort(pluginNames.begin(), pluginNames.end(), string::CaseInsensitiveCompare());
  238. for (const std::string &pluginName : pluginNames) {
  239. ui::MenuItem *item = new ui::MenuItem;
  240. item->text = pluginName;
  241. pluginList->addChild(item);
  242. }
  243. // Tag list
  244. tagScroll = new ui::ScrollWidget;
  245. addChild(tagScroll);
  246. tagList = new ui::List;
  247. tagScroll->container->addChild(tagList);
  248. for (const std::string &tag : plugin::allowedTags) {
  249. ui::MenuItem *item = new ui::MenuItem;
  250. item->text = tag;
  251. tagList->addChild(item);
  252. }
  253. }
  254. void step() override {
  255. searchField->box.size.x = box.size.x;
  256. pluginScroll->box.pos = searchField->box.getBottomLeft();
  257. pluginScroll->box.size.y = (box.size.y - searchField->box.size.y) / 2;
  258. pluginScroll->box.size.x = box.size.x;
  259. pluginList->box.size.x = pluginScroll->box.size.x;
  260. tagScroll->box.pos = pluginScroll->box.getBottomLeft().floor();
  261. tagScroll->box.size.y = (box.size.y - searchField->box.size.y) / 2;
  262. tagScroll->box.size.x = box.size.x;
  263. tagList->box.size.x = tagScroll->box.size.x;
  264. widget::Widget::step();
  265. }
  266. };
  267. struct ModuleBrowser : widget::OpaqueWidget {
  268. BrowserSidebar *sidebar;
  269. ui::ScrollWidget *modelScroll;
  270. ui::MarginLayout *modelMargin;
  271. ui::SequentialLayout *modelContainer;
  272. ModuleBrowser() {
  273. sidebar = new BrowserSidebar;
  274. sidebar->box.size.x = 200;
  275. addChild(sidebar);
  276. modelScroll = new ui::ScrollWidget;
  277. addChild(modelScroll);
  278. modelMargin = new ui::MarginLayout;
  279. modelMargin->margin = math::Vec(10, 10);
  280. modelScroll->container->addChild(modelMargin);
  281. modelContainer = new ui::SequentialLayout;
  282. modelContainer->spacing = math::Vec(10, 10);
  283. modelMargin->addChild(modelContainer);
  284. for (plugin::Plugin *plugin : plugin::plugins) {
  285. for (plugin::Model *model : plugin->models) {
  286. ModelBox *moduleBox = new ModelBox;
  287. moduleBox->setModel(model);
  288. modelContainer->addChild(moduleBox);
  289. }
  290. }
  291. }
  292. void step() override {
  293. box = parent->box.zeroPos().grow(math::Vec(-50, -50));
  294. sidebar->box.size.y = box.size.y;
  295. modelScroll->box.pos.x = sidebar->box.size.x;
  296. modelScroll->box.size.x = box.size.x - sidebar->box.size.x;
  297. modelScroll->box.size.y = box.size.y;
  298. modelMargin->box.size.x = modelScroll->box.size.x;
  299. modelMargin->box.size.y = modelContainer->getChildrenBoundingBox().size.y + 2 * modelMargin->margin.y;
  300. widget::OpaqueWidget::step();
  301. }
  302. void draw(const DrawArgs &args) override {
  303. bndMenuBackground(args.vg, 0.0, 0.0, box.size.x, box.size.y, 0);
  304. widget::Widget::draw(args);
  305. }
  306. void setSearch(const std::string &search) {
  307. for (Widget *w : modelContainer->children) {
  308. ModelBox *modelBox = dynamic_cast<ModelBox*>(w);
  309. assert(modelBox);
  310. bool match = isModelMatch(modelBox->model, search);
  311. modelBox->visible = match;
  312. }
  313. // Reset scroll position
  314. modelScroll->offset = math::Vec();
  315. }
  316. };
  317. // Implementations to resolve dependencies
  318. void ModelBox::onButton(const event::Button &e) {
  319. widget::OpaqueWidget::onButton(e);
  320. if (e.getConsumed() != this)
  321. return;
  322. if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT) {
  323. // Create module
  324. ModuleWidget *moduleWidget = model->createModuleWidget();
  325. assert(moduleWidget);
  326. APP->scene->rackWidget->addModuleAtMouse(moduleWidget);
  327. // Pretend the moduleWidget was clicked so it can be dragged in the RackWidget
  328. e.consume(moduleWidget);
  329. // Hide Module Browser
  330. BrowserOverlay *overlay = getAncestorOfType<BrowserOverlay>();
  331. overlay->hide();
  332. // Push ModuleAdd history action
  333. history::ModuleAdd *h = new history::ModuleAdd;
  334. h->name = "create module";
  335. h->setModule(moduleWidget);
  336. APP->history->push(h);
  337. }
  338. }
  339. void BrowserSearchField::onChange(const event::Change &e) {
  340. ModuleBrowser *browser = getAncestorOfType<ModuleBrowser>();
  341. browser->setSearch(text);
  342. }
  343. // Global functions
  344. widget::Widget *moduleBrowserCreate() {
  345. BrowserOverlay *overlay = new BrowserOverlay;
  346. ModuleBrowser *browser = new ModuleBrowser;
  347. overlay->addChild(browser);
  348. return overlay;
  349. }
  350. json_t *moduleBrowserToJson() {
  351. json_t *rootJ = json_object();
  352. json_t *favoritesJ = json_array();
  353. for (plugin::Model *model : sFavoriteModels) {
  354. json_t *modelJ = json_object();
  355. json_object_set_new(modelJ, "plugin", json_string(model->plugin->slug.c_str()));
  356. json_object_set_new(modelJ, "model", json_string(model->slug.c_str()));
  357. json_array_append_new(favoritesJ, modelJ);
  358. }
  359. json_object_set_new(rootJ, "favorites", favoritesJ);
  360. return rootJ;
  361. }
  362. void moduleBrowserFromJson(json_t *rootJ) {
  363. json_t *favoritesJ = json_object_get(rootJ, "favorites");
  364. if (favoritesJ) {
  365. size_t i;
  366. json_t *favoriteJ;
  367. json_array_foreach(favoritesJ, i, favoriteJ) {
  368. json_t *pluginJ = json_object_get(favoriteJ, "plugin");
  369. json_t *modelJ = json_object_get(favoriteJ, "model");
  370. if (!pluginJ || !modelJ)
  371. continue;
  372. std::string pluginSlug = json_string_value(pluginJ);
  373. std::string modelSlug = json_string_value(modelJ);
  374. plugin::Model *model = plugin::getModel(pluginSlug, modelSlug);
  375. if (!model)
  376. continue;
  377. sFavoriteModels.insert(model);
  378. }
  379. }
  380. }
  381. } // namespace app
  382. } // namespace rack