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.

457 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 = 0.5f;
  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. box.size.x = 0.f;
  79. box.size.y = std::ceil(RACK_GRID_HEIGHT * MODEL_BOX_ZOOM);
  80. }
  81. void setModel(plugin::Model *model) {
  82. this->model = model;
  83. infoWidget = new widget::Widget;
  84. infoWidget->box.size.x = 140;
  85. infoWidget->box.size.y = box.size.y;
  86. addChild(infoWidget);
  87. math::Vec pos;
  88. ui::Label *nameLabel = new ui::Label;
  89. // nameLabel->box.size.x = infoWidget->box.size.x;
  90. nameLabel->box.pos = pos;
  91. nameLabel->text = model->name;
  92. infoWidget->addChild(nameLabel);
  93. pos = nameLabel->box.getBottomLeft();
  94. ui::Label *pluginLabel = new ui::Label;
  95. // pluginLabel->box.size.x = infoWidget->box.size.x;
  96. pluginLabel->box.pos = pos;
  97. pluginLabel->text = model->plugin->name;
  98. infoWidget->addChild(pluginLabel);
  99. pos = pluginLabel->box.getBottomLeft();
  100. ui::Label *descriptionLabel = new ui::Label;
  101. descriptionLabel->box.size.x = infoWidget->box.size.x;
  102. descriptionLabel->box.pos = pos;
  103. descriptionLabel->text = model->description;
  104. infoWidget->addChild(descriptionLabel);
  105. pos = descriptionLabel->box.getBottomLeft();
  106. pos.y = infoWidget->box.size.y;
  107. for (const std::string &tag : model->tags) {
  108. ui::Button *tagButton = new ui::Button;
  109. tagButton->box.size.x = infoWidget->box.size.x;
  110. tagButton->box.pos = pos;
  111. tagButton->box.pos.y -= tagButton->box.size.y;
  112. tagButton->text = tag;
  113. infoWidget->addChild(tagButton);
  114. pos = tagButton->box.getTopLeft();
  115. }
  116. ui::Button *favoriteButton = new ui::Button;
  117. favoriteButton->box.size.x = infoWidget->box.size.x;
  118. favoriteButton->box.pos = pos;
  119. favoriteButton->box.pos.y -= favoriteButton->box.size.y;
  120. favoriteButton->text = "★";
  121. infoWidget->addChild(favoriteButton);
  122. pos = favoriteButton->box.getTopLeft();
  123. }
  124. void createPreview() {
  125. assert(!previewWidget);
  126. previewWidget = new widget::TransparentWidget;
  127. previewWidget->box.size.y = std::ceil(RACK_GRID_HEIGHT * MODEL_BOX_ZOOM);
  128. addChild(previewWidget);
  129. widget::FramebufferWidget *fbWidget = new widget::FramebufferWidget;
  130. if (math::isNear(APP->window->pixelRatio, 1.0)) {
  131. // Small details draw poorly at low DPI, so oversample when drawing to the framebuffer
  132. fbWidget->oversample = 2.0;
  133. }
  134. previewWidget->addChild(fbWidget);
  135. widget::ZoomWidget *zoomWidget = new widget::ZoomWidget;
  136. zoomWidget->setZoom(MODEL_BOX_ZOOM);
  137. fbWidget->addChild(zoomWidget);
  138. ModuleWidget *moduleWidget = model->createModuleWidgetNull();
  139. zoomWidget->addChild(moduleWidget);
  140. zoomWidget->box.size.x = moduleWidget->box.size.x * MODEL_BOX_ZOOM;
  141. zoomWidget->box.size.y = RACK_GRID_HEIGHT * MODEL_BOX_ZOOM;
  142. previewWidget->box.size.x = std::ceil(zoomWidget->box.size.x);
  143. // Reposition infoWidget
  144. infoWidget->box.pos.x = previewWidget->box.size.x;
  145. box.size.x = previewWidget->box.size.x + infoWidget->box.size.x;
  146. }
  147. void deletePreview() {
  148. assert(previewWidget);
  149. removeChild(previewWidget);
  150. delete previewWidget;
  151. previewWidget = NULL;
  152. }
  153. void step() override {
  154. if (previewWidget && ++visibleFrames >= 60) {
  155. deletePreview();
  156. }
  157. }
  158. void draw(const widget::DrawContext &ctx) override {
  159. visibleFrames = 0;
  160. // Lazily create preview when drawn
  161. if (!previewWidget) {
  162. createPreview();
  163. }
  164. nvgScissor(ctx.vg, RECT_ARGS(ctx.clipBox));
  165. widget::OpaqueWidget::draw(ctx);
  166. nvgResetScissor(ctx.vg);
  167. // Translucent overlay when selected
  168. if (selected) {
  169. nvgBeginPath(ctx.vg);
  170. nvgRect(ctx.vg, 0.0, 0.0, box.size.x, box.size.y);
  171. nvgFillColor(ctx.vg, nvgRGBAf(1, 1, 1, 0.25));
  172. nvgFill(ctx.vg);
  173. }
  174. }
  175. void onButton(const event::Button &e) override;
  176. void onEnter(const event::Enter &e) override {
  177. e.consume(this);
  178. selected = true;
  179. }
  180. void onLeave(const event::Leave &e) override {
  181. selected = false;
  182. }
  183. };
  184. struct BrowserSearchField : ui::TextField {
  185. void step() override {
  186. // Steal focus when step is called
  187. APP->event->setSelected(this);
  188. ui::TextField::step();
  189. }
  190. void onSelectKey(const event::SelectKey &e) override {
  191. if (e.action == GLFW_PRESS) {
  192. if (e.key == GLFW_KEY_ESCAPE) {
  193. BrowserOverlay *overlay = getAncestorOfType<BrowserOverlay>();
  194. overlay->hide();
  195. e.consume(this);
  196. }
  197. }
  198. if (!e.getConsumed())
  199. ui::TextField::onSelectKey(e);
  200. }
  201. void onChange(const event::Change &e) override;
  202. void onHide(const event::Hide &e) override {
  203. setText("");
  204. APP->event->setSelected(NULL);
  205. ui::TextField::onHide(e);
  206. }
  207. };
  208. struct BrowserSidebar : widget::Widget {
  209. BrowserSearchField *searchField;
  210. ui::List *pluginList;
  211. ui::ScrollWidget *pluginScroll;
  212. ui::List *tagList;
  213. ui::ScrollWidget *tagScroll;
  214. BrowserSidebar() {
  215. searchField = new BrowserSearchField;
  216. addChild(searchField);
  217. pluginScroll = new ui::ScrollWidget;
  218. pluginScroll->box.pos = searchField->box.getBottomLeft();
  219. addChild(pluginScroll);
  220. pluginList = new ui::List;
  221. pluginScroll->container->addChild(pluginList);
  222. std::set<std::string> pluginNames;
  223. for (plugin::Plugin *plugin : plugin::plugins) {
  224. pluginNames.insert(plugin->name);
  225. }
  226. for (const std::string &pluginName : pluginNames) {
  227. ui::MenuItem *item = new ui::MenuItem;
  228. item->text = pluginName;
  229. pluginList->addChild(item);
  230. }
  231. tagScroll = new ui::ScrollWidget;
  232. tagScroll->box.pos = searchField->box.getBottomLeft();
  233. addChild(tagScroll);
  234. tagList = new ui::List;
  235. tagScroll->container->addChild(tagList);
  236. for (const std::string &tag : plugin::allowedTags) {
  237. ui::MenuItem *item = new ui::MenuItem;
  238. item->text = tag;
  239. tagList->addChild(item);
  240. }
  241. }
  242. void step() override {
  243. searchField->box.size.x = box.size.x;
  244. pluginScroll->box.size.y = box.size.y - searchField->box.size.y;
  245. pluginList->box.size.x = pluginScroll->box.size.x = box.size.x / 2;
  246. tagScroll->box.pos.x = box.size.x / 2;
  247. tagScroll->box.size.y = box.size.y - searchField->box.size.y;
  248. tagList->box.size.x = tagScroll->box.size.x = box.size.x / 2;
  249. widget::Widget::step();
  250. }
  251. };
  252. struct ModuleBrowser : widget::OpaqueWidget {
  253. BrowserSidebar *sidebar;
  254. ui::ScrollWidget *modelScroll;
  255. ui::MarginLayout *modelMargin;
  256. ui::SequentialLayout *modelContainer;
  257. ModuleBrowser() {
  258. sidebar = new BrowserSidebar;
  259. sidebar->box.size.x = 300;
  260. addChild(sidebar);
  261. modelScroll = new ui::ScrollWidget;
  262. addChild(modelScroll);
  263. modelMargin = new ui::MarginLayout;
  264. modelMargin->margin = math::Vec(20, 20);
  265. modelScroll->container->addChild(modelMargin);
  266. modelContainer = new ui::SequentialLayout;
  267. modelContainer->spacing = math::Vec(20, 20);
  268. modelMargin->addChild(modelContainer);
  269. for (plugin::Plugin *plugin : plugin::plugins) {
  270. for (plugin::Model *model : plugin->models) {
  271. ModelBox *moduleBox = new ModelBox;
  272. moduleBox->setModel(model);
  273. modelContainer->addChild(moduleBox);
  274. }
  275. }
  276. }
  277. void step() override {
  278. box = parent->box.zeroPos().grow(math::Vec(-50, -50));
  279. sidebar->box.size.y = box.size.y;
  280. modelScroll->box.pos.x = sidebar->box.size.x;
  281. modelScroll->box.size.x = box.size.x - sidebar->box.size.x;
  282. modelScroll->box.size.y = box.size.y;
  283. modelMargin->box.size.x = modelScroll->box.size.x;
  284. modelMargin->box.size.y = modelContainer->getChildrenBoundingBox().size.y + 2 * modelMargin->margin.y;
  285. widget::OpaqueWidget::step();
  286. }
  287. void draw(const widget::DrawContext &ctx) override {
  288. bndMenuBackground(ctx.vg, 0.0, 0.0, box.size.x, box.size.y, 0);
  289. widget::Widget::draw(ctx);
  290. }
  291. void setSearch(const std::string &search) {
  292. for (Widget *w : modelContainer->children) {
  293. ModelBox *modelBox = dynamic_cast<ModelBox*>(w);
  294. assert(modelBox);
  295. bool match = isModelMatch(modelBox->model, search);
  296. modelBox->visible = match;
  297. }
  298. // Reset scroll position
  299. modelScroll->offset = math::Vec();
  300. }
  301. };
  302. // Implementations to resolve dependencies
  303. void ModelBox::onButton(const event::Button &e) {
  304. widget::OpaqueWidget::onButton(e);
  305. if (e.getConsumed() != this)
  306. return;
  307. if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT) {
  308. // Create module
  309. ModuleWidget *moduleWidget = model->createModuleWidget();
  310. assert(moduleWidget);
  311. APP->scene->rackWidget->addModuleAtMouse(moduleWidget);
  312. // Pretend the moduleWidget was clicked so it can be dragged in the RackWidget
  313. e.consume(moduleWidget);
  314. // Hide Module Browser
  315. BrowserOverlay *overlay = getAncestorOfType<BrowserOverlay>();
  316. overlay->hide();
  317. // Push ModuleAdd history action
  318. history::ModuleAdd *h = new history::ModuleAdd;
  319. h->name = "create module";
  320. h->setModule(moduleWidget);
  321. APP->history->push(h);
  322. }
  323. }
  324. void BrowserSearchField::onChange(const event::Change &e) {
  325. ModuleBrowser *browser = getAncestorOfType<ModuleBrowser>();
  326. browser->setSearch(text);
  327. }
  328. // Global functions
  329. widget::Widget *moduleBrowserCreate() {
  330. BrowserOverlay *overlay = new BrowserOverlay;
  331. ModuleBrowser *browser = new ModuleBrowser;
  332. overlay->addChild(browser);
  333. return overlay;
  334. }
  335. json_t *moduleBrowserToJson() {
  336. json_t *rootJ = json_object();
  337. json_t *favoritesJ = json_array();
  338. for (plugin::Model *model : sFavoriteModels) {
  339. json_t *modelJ = json_object();
  340. json_object_set_new(modelJ, "plugin", json_string(model->plugin->slug.c_str()));
  341. json_object_set_new(modelJ, "model", json_string(model->slug.c_str()));
  342. json_array_append_new(favoritesJ, modelJ);
  343. }
  344. json_object_set_new(rootJ, "favorites", favoritesJ);
  345. return rootJ;
  346. }
  347. void moduleBrowserFromJson(json_t *rootJ) {
  348. json_t *favoritesJ = json_object_get(rootJ, "favorites");
  349. if (favoritesJ) {
  350. size_t i;
  351. json_t *favoriteJ;
  352. json_array_foreach(favoritesJ, i, favoriteJ) {
  353. json_t *pluginJ = json_object_get(favoriteJ, "plugin");
  354. json_t *modelJ = json_object_get(favoriteJ, "model");
  355. if (!pluginJ || !modelJ)
  356. continue;
  357. std::string pluginSlug = json_string_value(pluginJ);
  358. std::string modelSlug = json_string_value(modelJ);
  359. plugin::Model *model = plugin::getModel(pluginSlug, modelSlug);
  360. if (!model)
  361. continue;
  362. sFavoriteModels.insert(model);
  363. }
  364. }
  365. }
  366. } // namespace app
  367. } // namespace rack