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.

559 lines
13KB

  1. #include "app.hpp"
  2. #include "plugin.hpp"
  3. #include "window.hpp"
  4. #include <set>
  5. #include <algorithm>
  6. #define BND_LABEL_FONT_SIZE 13
  7. namespace rack {
  8. static std::set<Model*> sFavoriteModels;
  9. bool isMatch(std::string s, std::string search) {
  10. s = lowercase(s);
  11. search = lowercase(search);
  12. return (s.find(search) != std::string::npos);
  13. }
  14. static bool isModelMatch(Model *model, std::string search) {
  15. if (search.empty())
  16. return true;
  17. std::string s;
  18. s += model->plugin->slug;
  19. s += " ";
  20. s += model->manufacturer;
  21. s += " ";
  22. s += model->name;
  23. s += " ";
  24. s += model->slug;
  25. for (ModelTag tag : model->tags) {
  26. s += " ";
  27. s += gTagNames[tag];
  28. }
  29. return isMatch(s, search);
  30. }
  31. struct FavoriteRadioButton : RadioButton {
  32. Model *model = NULL;
  33. void onAction(EventAction &e) override;
  34. };
  35. struct SeparatorItem : OpaqueWidget {
  36. SeparatorItem() {
  37. box.size.y = BND_WIDGET_HEIGHT;
  38. }
  39. void setText(std::string text) {
  40. clearChildren();
  41. Label *label = Widget::create<Label>(Vec(0, 0));
  42. label->text = text;
  43. addChild(label);
  44. }
  45. };
  46. struct BrowserListItem : OpaqueWidget {
  47. bool selected = false;
  48. BrowserListItem() {
  49. box.size.y = 2 * BND_WIDGET_HEIGHT + 7;
  50. }
  51. void draw(NVGcontext *vg) override {
  52. BNDwidgetState state = selected ? BND_HOVER : BND_DEFAULT;
  53. bndMenuItem(vg, 0.0, 0.0, box.size.x, box.size.y, state, -1, "");
  54. Widget::draw(vg);
  55. }
  56. void onDragDrop(EventDragDrop &e) override {
  57. if (e.origin != this)
  58. return;
  59. doAction();
  60. }
  61. void doAction() {
  62. EventAction eAction;
  63. eAction.consumed = true;
  64. onAction(eAction);
  65. if (eAction.consumed) {
  66. // deletes `this`
  67. gScene->setOverlay(NULL);
  68. }
  69. }
  70. void onMouseEnter(EventMouseEnter &e) override;
  71. };
  72. struct ModelItem : BrowserListItem {
  73. Model *model;
  74. Label *manufacturerLabel;
  75. void setModel(Model *model) {
  76. clearChildren();
  77. assert(model);
  78. this->model = model;
  79. Label *nameLabel = Widget::create<Label>(Vec(0, 0));
  80. nameLabel->text = model->name;
  81. addChild(nameLabel);
  82. manufacturerLabel = Widget::create<Label>(Vec(0, 0));
  83. manufacturerLabel->alignment = Label::RIGHT_ALIGNMENT;
  84. manufacturerLabel->text = model->manufacturer;
  85. addChild(manufacturerLabel);
  86. SequentialLayout *layout2 = Widget::create<SequentialLayout>(Vec(7, BND_WIDGET_HEIGHT));
  87. layout2->spacing = 0;
  88. addChild(layout2);
  89. FavoriteRadioButton *favoriteButton = new FavoriteRadioButton();
  90. favoriteButton->box.size.x = 20;
  91. favoriteButton->label = "★";
  92. layout2->addChild(favoriteButton);
  93. auto it = sFavoriteModels.find(model);
  94. if (it != sFavoriteModels.end())
  95. favoriteButton->setValue(1);
  96. favoriteButton->model = model;
  97. // for (ModelTag tag : model->tags) {
  98. // Button *tagButton = new Button();
  99. // tagButton->box.size.x = 120;
  100. // tagButton->text = gTagNames[tag];
  101. // layout2->addChild(tagButton);
  102. // }
  103. Label *tagsLabel = new Label();
  104. int i = 0;
  105. for (ModelTag tag : model->tags) {
  106. if (i++ > 0)
  107. tagsLabel->text += ", ";
  108. tagsLabel->text += gTagNames[tag];
  109. }
  110. layout2->addChild(tagsLabel);
  111. }
  112. void step() override {
  113. BrowserListItem::step();
  114. manufacturerLabel->box.size.x = box.size.x - BND_SCROLLBAR_WIDTH;
  115. }
  116. void onAction(EventAction &e) override {
  117. ModuleWidget *moduleWidget = model->createModuleWidget();
  118. gRackWidget->moduleContainer->addChild(moduleWidget);
  119. // Move module nearest to the mouse position
  120. moduleWidget->box.pos = gRackWidget->lastMousePos.minus(moduleWidget->box.size.div(2));
  121. gRackWidget->requestModuleBoxNearest(moduleWidget, moduleWidget->box);
  122. }
  123. };
  124. struct ManufacturerItem : BrowserListItem {
  125. std::string manufacturer;
  126. void setManufacturer(std::string manufacturer) {
  127. clearChildren();
  128. this->manufacturer = manufacturer;
  129. Label *manufacturerLabel = Widget::create<Label>(Vec(0, 0));
  130. if (manufacturer.empty())
  131. manufacturerLabel->text = "Show all modules";
  132. else
  133. manufacturerLabel->text = manufacturer;
  134. addChild(manufacturerLabel);
  135. }
  136. void onAction(EventAction &e) override;
  137. };
  138. struct TagItem : BrowserListItem {
  139. ModelTag tag;
  140. void setTag(ModelTag tag) {
  141. clearChildren();
  142. this->tag = tag;
  143. Label *tagLabel = Widget::create<Label>(Vec(0, 0));
  144. if (tag == NO_TAG)
  145. tagLabel->text = "Show all tags";
  146. else
  147. tagLabel->text = gTagNames[tag];
  148. addChild(tagLabel);
  149. }
  150. void onAction(EventAction &e) override;
  151. };
  152. struct ClearFilterItem : BrowserListItem {
  153. ClearFilterItem() {
  154. Label *label = Widget::create<Label>(Vec(0, 0));
  155. label->text = "Clear filter";
  156. addChild(label);
  157. }
  158. void onAction(EventAction &e) override;
  159. };
  160. struct BrowserList : List {
  161. int selected = 0;
  162. void step() override {
  163. // Count items
  164. int n = 0;
  165. for (Widget *child : children) {
  166. BrowserListItem *item = dynamic_cast<BrowserListItem*>(child);
  167. if (item) {
  168. n++;
  169. }
  170. }
  171. // If we have zero children, this result doesn't matter anyway.
  172. selected = clamp(selected, 0, n - 1);
  173. // Find and select item
  174. int i = 0;
  175. for (Widget *child : children) {
  176. BrowserListItem *item = dynamic_cast<BrowserListItem*>(child);
  177. if (item) {
  178. item->selected = (i == selected);
  179. i++;
  180. }
  181. }
  182. List::step();
  183. }
  184. void selectItem(Widget *w) {
  185. int i = 0;
  186. for (Widget *child : children) {
  187. BrowserListItem *item = dynamic_cast<BrowserListItem*>(child);
  188. if (item) {
  189. if (child == w) {
  190. selected = i;
  191. break;
  192. }
  193. i++;
  194. }
  195. }
  196. }
  197. BrowserListItem *getSelectedItem() {
  198. int i = 0;
  199. for (Widget *child : children) {
  200. BrowserListItem *item = dynamic_cast<BrowserListItem*>(child);
  201. if (item) {
  202. if (i == selected) {
  203. return item;
  204. }
  205. i++;
  206. }
  207. }
  208. return NULL;
  209. }
  210. void scrollSelected() {
  211. BrowserListItem *item = getSelectedItem();
  212. if (item) {
  213. ScrollWidget *parentScroll = dynamic_cast<ScrollWidget*>(parent->parent);
  214. if (parentScroll)
  215. parentScroll->scrollTo(item->box);
  216. }
  217. }
  218. };
  219. struct ModuleBrowser;
  220. struct SearchModuleField : TextField {
  221. ModuleBrowser *moduleBrowser;
  222. void onTextChange() override;
  223. void onKey(EventKey &e) override;
  224. };
  225. struct ModuleBrowser : OpaqueWidget {
  226. SearchModuleField *searchField;
  227. ScrollWidget *moduleScroll;
  228. BrowserList *moduleList;
  229. std::string manufacturerFilter;
  230. ModelTag tagFilter = NO_TAG;
  231. std::set<std::string> availableManufacturers;
  232. std::set<ModelTag> availableTags;
  233. ModuleBrowser() {
  234. box.size.x = 400;
  235. // Search
  236. searchField = new SearchModuleField();
  237. searchField->box.size.x = box.size.x;
  238. searchField->moduleBrowser = this;
  239. addChild(searchField);
  240. moduleList = new BrowserList();
  241. moduleList->box.size = Vec(box.size.x, 0.0);
  242. // Module Scroll
  243. moduleScroll = new ScrollWidget();
  244. moduleScroll->box.pos.y = searchField->box.size.y;
  245. moduleScroll->box.size.x = box.size.x;
  246. moduleScroll->container->addChild(moduleList);
  247. addChild(moduleScroll);
  248. // Collect manufacturers
  249. for (Plugin *plugin : gPlugins) {
  250. for (Model *model : plugin->models) {
  251. // Insert manufacturer
  252. if (!model->manufacturer.empty())
  253. availableManufacturers.insert(model->manufacturer);
  254. // Insert tag
  255. for (ModelTag tag : model->tags) {
  256. if (tag != NO_TAG)
  257. availableTags.insert(tag);
  258. }
  259. }
  260. }
  261. // Trigger search update
  262. clearSearch();
  263. }
  264. void clearSearch() {
  265. searchField->setText("");
  266. }
  267. bool isModelFiltered(Model *model) {
  268. if (!manufacturerFilter.empty() && model->manufacturer != manufacturerFilter)
  269. return false;
  270. if (tagFilter != NO_TAG) {
  271. auto it = std::find(model->tags.begin(), model->tags.end(), tagFilter);
  272. if (it == model->tags.end())
  273. return false;
  274. }
  275. return true;
  276. }
  277. void refreshSearch() {
  278. std::string search = searchField->text;
  279. moduleList->clearChildren();
  280. moduleList->selected = 0;
  281. // Favorites
  282. {
  283. SeparatorItem *item = new SeparatorItem();
  284. item->setText("Favorites");
  285. moduleList->addChild(item);
  286. }
  287. for (Model *model : sFavoriteModels) {
  288. if (isModelFiltered(model) && isModelMatch(model, search)) {
  289. ModelItem *item = new ModelItem();
  290. item->setModel(model);
  291. moduleList->addChild(item);
  292. }
  293. }
  294. // Manufacturers
  295. if (manufacturerFilter.empty() && tagFilter == NO_TAG) {
  296. // Manufacturer items
  297. {
  298. SeparatorItem *item = new SeparatorItem();
  299. item->setText("Manufacturers");
  300. moduleList->addChild(item);
  301. }
  302. for (std::string manufacturer : availableManufacturers) {
  303. if (isMatch(manufacturer, search)) {
  304. ManufacturerItem *item = new ManufacturerItem();
  305. item->setManufacturer(manufacturer);
  306. moduleList->addChild(item);
  307. }
  308. }
  309. // Tag items
  310. {
  311. SeparatorItem *item = new SeparatorItem();
  312. item->setText("Tags");
  313. moduleList->addChild(item);
  314. }
  315. for (ModelTag tag : availableTags) {
  316. if (isMatch(gTagNames[tag], search)) {
  317. TagItem *item = new TagItem();
  318. item->setTag(tag);
  319. moduleList->addChild(item);
  320. }
  321. }
  322. }
  323. else {
  324. ClearFilterItem *item = new ClearFilterItem();
  325. moduleList->addChild(item);
  326. }
  327. // Models
  328. if (!manufacturerFilter.empty() || tagFilter != NO_TAG || !search.empty()) {
  329. {
  330. SeparatorItem *item = new SeparatorItem();
  331. item->setText("Modules");
  332. moduleList->addChild(item);
  333. }
  334. for (Plugin *plugin : gPlugins) {
  335. for (Model *model : plugin->models) {
  336. if (isModelFiltered(model) && isModelMatch(model, search)) {
  337. ModelItem *item = new ModelItem();
  338. item->setModel(model);
  339. moduleList->addChild(item);
  340. }
  341. }
  342. }
  343. }
  344. }
  345. void step() override {
  346. box.pos = parent->box.size.minus(box.size).div(2).round();
  347. box.pos.y = 60;
  348. box.size.y = parent->box.size.y - 2 * box.pos.y;
  349. moduleScroll->box.size.y = box.size.y - moduleScroll->box.pos.y;
  350. gFocusedWidget = searchField;
  351. Widget::step();
  352. }
  353. };
  354. // Implementations of inline methods above
  355. void ManufacturerItem::onAction(EventAction &e) {
  356. ModuleBrowser *moduleBrowser = getAncestorOfType<ModuleBrowser>();
  357. moduleBrowser->manufacturerFilter = manufacturer;
  358. moduleBrowser->clearSearch();
  359. moduleBrowser->refreshSearch();
  360. e.consumed = false;
  361. }
  362. void TagItem::onAction(EventAction &e) {
  363. ModuleBrowser *moduleBrowser = getAncestorOfType<ModuleBrowser>();
  364. moduleBrowser->tagFilter = tag;
  365. moduleBrowser->clearSearch();
  366. moduleBrowser->refreshSearch();
  367. e.consumed = false;
  368. }
  369. void ClearFilterItem::onAction(EventAction &e) {
  370. ModuleBrowser *moduleBrowser = getAncestorOfType<ModuleBrowser>();
  371. moduleBrowser->manufacturerFilter = "";
  372. moduleBrowser->tagFilter = NO_TAG;
  373. moduleBrowser->clearSearch();
  374. moduleBrowser->refreshSearch();
  375. e.consumed = false;
  376. }
  377. void FavoriteRadioButton::onAction(EventAction &e) {
  378. if (!model)
  379. return;
  380. if (value) {
  381. sFavoriteModels.insert(model);
  382. }
  383. else {
  384. auto it = sFavoriteModels.find(model);
  385. if (it != sFavoriteModels.end())
  386. sFavoriteModels.erase(it);
  387. }
  388. ModuleBrowser *moduleBrowser = getAncestorOfType<ModuleBrowser>();
  389. if (moduleBrowser)
  390. moduleBrowser->refreshSearch();
  391. }
  392. void BrowserListItem::onMouseEnter(EventMouseEnter &e) {
  393. BrowserList *list = getAncestorOfType<BrowserList>();
  394. list->selectItem(this);
  395. }
  396. void SearchModuleField::onTextChange() {
  397. moduleBrowser->refreshSearch();
  398. }
  399. void SearchModuleField::onKey(EventKey &e) {
  400. switch (e.key) {
  401. case GLFW_KEY_ESCAPE: {
  402. gScene->setOverlay(NULL);
  403. e.consumed = true;
  404. return;
  405. } break;
  406. case GLFW_KEY_UP: {
  407. moduleBrowser->moduleList->selected--;
  408. moduleBrowser->moduleList->scrollSelected();
  409. e.consumed = true;
  410. } break;
  411. case GLFW_KEY_DOWN: {
  412. moduleBrowser->moduleList->selected++;
  413. moduleBrowser->moduleList->scrollSelected();
  414. e.consumed = true;
  415. } break;
  416. case GLFW_KEY_ENTER: {
  417. BrowserListItem *item = moduleBrowser->moduleList->getSelectedItem();
  418. if (item) {
  419. item->doAction();
  420. e.consumed = true;
  421. return;
  422. }
  423. } break;
  424. }
  425. if (!e.consumed) {
  426. TextField::onKey(e);
  427. }
  428. }
  429. // Global functions
  430. void appModuleBrowserCreate() {
  431. MenuOverlay *overlay = new MenuOverlay();
  432. ModuleBrowser *moduleBrowser = new ModuleBrowser();
  433. overlay->addChild(moduleBrowser);
  434. gScene->setOverlay(overlay);
  435. }
  436. json_t *appModuleBrowserToJson() {
  437. json_t *rootJ = json_object();
  438. json_t *favoritesJ = json_array();
  439. for (Model *model : sFavoriteModels) {
  440. json_t *modelJ = json_object();
  441. json_object_set_new(modelJ, "plugin", json_string(model->plugin->slug.c_str()));
  442. json_object_set_new(modelJ, "model", json_string(model->slug.c_str()));
  443. json_array_append_new(favoritesJ, modelJ);
  444. }
  445. json_object_set_new(rootJ, "favorites", favoritesJ);
  446. return rootJ;
  447. }
  448. void appModuleBrowserFromJson(json_t *rootJ) {
  449. json_t *favoritesJ = json_object_get(rootJ, "favorites");
  450. if (favoritesJ) {
  451. size_t i;
  452. json_t *favoriteJ;
  453. json_array_foreach(favoritesJ, i, favoriteJ) {
  454. json_t *pluginJ = json_object_get(favoriteJ, "plugin");
  455. json_t *modelJ = json_object_get(favoriteJ, "model");
  456. if (!pluginJ || !modelJ)
  457. continue;
  458. std::string pluginSlug = json_string_value(pluginJ);
  459. std::string modelSlug = json_string_value(modelJ);
  460. Model *model = pluginGetModel(pluginSlug, modelSlug);
  461. if (!model)
  462. continue;
  463. sFavoriteModels.insert(model);
  464. }
  465. }
  466. }
  467. } // namespace rack