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.

293 lines
6.7KB

  1. #include "app.hpp"
  2. #include "plugin.hpp"
  3. #include <thread>
  4. #include <set>
  5. #include <algorithm>
  6. namespace rack {
  7. static std::string sManufacturer;
  8. static Model *sModel = NULL;
  9. static std::string sFilter;
  10. struct ListMenu : OpaqueWidget {
  11. void draw(NVGcontext *vg) override {
  12. Widget::draw(vg);
  13. }
  14. void step() override {
  15. Widget::step();
  16. box.size.y = 0;
  17. for (Widget *child : children) {
  18. if (!child->visible)
  19. continue;
  20. // Increase height, set position of child
  21. child->box.pos = Vec(0, box.size.y);
  22. box.size.y += child->box.size.y;
  23. child->box.size.x = box.size.x;
  24. }
  25. }
  26. };
  27. struct UrlItem : MenuItem {
  28. std::string url;
  29. void onAction(EventAction &e) override {
  30. std::thread t(openBrowser, url);
  31. t.detach();
  32. }
  33. };
  34. struct MetadataMenu : ListMenu {
  35. Model *model = NULL;
  36. void step() override {
  37. if (model != sModel) {
  38. model = sModel;
  39. clearChildren();
  40. if (model) {
  41. // Tag list
  42. if (!model->tags.empty()) {
  43. for (ModelTag tag : model->tags) {
  44. addChild(construct<MenuLabel>(&MenuEntry::text, gTagNames[tag]));
  45. }
  46. addChild(construct<MenuEntry>());
  47. }
  48. // Plugin name
  49. std::string pluginName = model->plugin->slug;
  50. if (!model->plugin->version.empty()) {
  51. pluginName += " v";
  52. pluginName += model->plugin->version;
  53. }
  54. addChild(construct<MenuLabel>(&MenuEntry::text, pluginName));
  55. // Plugin metadata
  56. if (!model->plugin->website.empty()) {
  57. addChild(construct<UrlItem>(&MenuEntry::text, "Website", &UrlItem::url, model->plugin->website));
  58. }
  59. if (!model->plugin->manual.empty()) {
  60. addChild(construct<UrlItem>(&MenuEntry::text, "Manual", &UrlItem::url, model->plugin->manual));
  61. }
  62. if (!model->plugin->path.empty()) {
  63. addChild(construct<UrlItem>(&MenuEntry::text, "Browse directory", &UrlItem::url, model->plugin->path));
  64. }
  65. }
  66. }
  67. ListMenu::step();
  68. }
  69. };
  70. static bool isModelMatch(Model *model, std::string search) {
  71. // Build content string
  72. std::string str;
  73. str += model->manufacturer;
  74. str += " ";
  75. str += model->name;
  76. str += " ";
  77. str += model->slug;
  78. for (ModelTag tag : model->tags) {
  79. str += " ";
  80. str += gTagNames[tag];
  81. }
  82. str = lowercase(str);
  83. search = lowercase(search);
  84. return (str.find(search) != std::string::npos);
  85. }
  86. struct ModelItem : MenuItem {
  87. Model *model;
  88. void onAction(EventAction &e) override {
  89. ModuleWidget *moduleWidget = model->createModuleWidget();
  90. gRackWidget->moduleContainer->addChild(moduleWidget);
  91. // Move module nearest to the mouse position
  92. Rect box;
  93. box.size = moduleWidget->box.size;
  94. AddModuleWindow *w = getAncestorOfType<AddModuleWindow>();
  95. box.pos = w->modulePos.minus(box.getCenter());
  96. gRackWidget->requestModuleBoxNearest(moduleWidget, box);
  97. }
  98. void onMouseEnter(EventMouseEnter &e) override {
  99. sModel = model;
  100. MenuItem::onMouseEnter(e);
  101. }
  102. };
  103. struct ModelMenu : ListMenu {
  104. std::string manufacturer;
  105. std::string filter;
  106. void step() override {
  107. if (manufacturer != sManufacturer) {
  108. manufacturer = sManufacturer;
  109. filter = "";
  110. clearChildren();
  111. addChild(construct<MenuLabel>(&MenuLabel::text, manufacturer));
  112. // Add models for the selected manufacturer
  113. for (Plugin *plugin : gPlugins) {
  114. for (Model *model : plugin->models) {
  115. if (model->manufacturer == manufacturer) {
  116. addChild(construct<ModelItem>(&MenuEntry::text, model->name, &ModelItem::model, model));
  117. }
  118. }
  119. }
  120. }
  121. if (filter != sFilter) {
  122. filter = sFilter;
  123. // Make all children invisible
  124. for (Widget *child : children) {
  125. child->visible = false;
  126. }
  127. // Make children with a matching model visible
  128. for (Widget *child : children) {
  129. ModelItem *item = dynamic_cast<ModelItem*>(child);
  130. if (!item)
  131. continue;
  132. if (isModelMatch(item->model, filter)) {
  133. item->visible = true;
  134. }
  135. }
  136. }
  137. ListMenu::step();
  138. }
  139. };
  140. struct ManufacturerItem : MenuItem {
  141. Model *model;
  142. void onAction(EventAction &e) override {
  143. sManufacturer = text;
  144. e.consumed = false;
  145. }
  146. };
  147. struct ManufacturerMenu : ListMenu {
  148. std::string filter;
  149. ManufacturerMenu() {
  150. addChild(construct<MenuLabel>(&MenuLabel::text, "Manufacturers"));
  151. // Collect manufacturer names
  152. std::set<std::string> manufacturers;
  153. for (Plugin *plugin : gPlugins) {
  154. for (Model *model : plugin->models) {
  155. manufacturers.insert(model->manufacturer);
  156. }
  157. }
  158. // Add menu item for each manufacturer name
  159. for (std::string manufacturer : manufacturers) {
  160. addChild(construct<ManufacturerItem>(&MenuEntry::text, manufacturer));
  161. }
  162. }
  163. void step() override {
  164. if (filter != sFilter) {
  165. // Make all children invisible
  166. for (Widget *child : children) {
  167. child->visible = false;
  168. }
  169. // Make children with a matching model visible
  170. for (Widget *child : children) {
  171. MenuItem *item = dynamic_cast<MenuItem*>(child);
  172. if (!item)
  173. continue;
  174. std::string manufacturer = item->text;
  175. for (Plugin *plugin : gPlugins) {
  176. for (Model *model : plugin->models) {
  177. if (model->manufacturer == manufacturer) {
  178. if (isModelMatch(model, sFilter)) {
  179. item->visible = true;
  180. }
  181. }
  182. }
  183. }
  184. }
  185. filter = sFilter;
  186. }
  187. ListMenu::step();
  188. }
  189. };
  190. struct SearchModuleField : TextField {
  191. void onTextChange() override {
  192. sFilter = text;
  193. }
  194. };
  195. AddModuleWindow::AddModuleWindow() {
  196. box.size = Vec(600, 300);
  197. title = "Add module";
  198. float posY = BND_NODE_TITLE_HEIGHT;
  199. // Search
  200. SearchModuleField *searchField = new SearchModuleField();
  201. searchField->box.pos.y = posY;
  202. posY += searchField->box.size.y;
  203. searchField->box.size.x = box.size.x;
  204. searchField->text = sFilter;
  205. gFocusedWidget = searchField;
  206. {
  207. EventFocus eFocus;
  208. searchField->onFocus(eFocus);
  209. searchField->onTextChange();
  210. }
  211. addChild(searchField);
  212. // Manufacturers
  213. ManufacturerMenu *manufacturerMenu = new ManufacturerMenu();
  214. manufacturerMenu->box.size.x = 200;
  215. ScrollWidget *manufacturerScroll = new ScrollWidget();
  216. manufacturerScroll->container->addChild(manufacturerMenu);
  217. manufacturerScroll->box.pos = Vec(0, posY);
  218. manufacturerScroll->box.size = Vec(200, box.size.y - posY);
  219. addChild(manufacturerScroll);
  220. // Models
  221. ModelMenu *modelMenu = new ModelMenu();
  222. modelMenu->box.size.x = 200;
  223. ScrollWidget *modelScroll = new ScrollWidget();
  224. modelScroll->container->addChild(modelMenu);
  225. modelScroll->box.pos = Vec(200, posY);
  226. modelScroll->box.size = Vec(200, box.size.y - posY);
  227. addChild(modelScroll);
  228. // Metadata
  229. MetadataMenu *metadataMenu = new MetadataMenu();
  230. metadataMenu->box.size.x = 200;
  231. ScrollWidget *metadataScroll = new ScrollWidget();
  232. metadataScroll->container->addChild(metadataMenu);
  233. metadataScroll->box.pos = Vec(400, posY);
  234. metadataScroll->box.size = Vec(200, box.size.y - posY);
  235. addChild(metadataScroll);
  236. }
  237. void AddModuleWindow::step() {
  238. Widget::step();
  239. }
  240. } // namespace rack