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.

301 lines
6.9KB

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