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.

578 lines
14KB

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