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.

473 lines
12KB

  1. #pragma once
  2. #include <plugin/Model.hpp>
  3. #include <ui/MenuOverlay.hpp>
  4. #include <ui/MenuItem.hpp>
  5. #include <ui/MenuLabel.hpp>
  6. #include <ui/Menu.hpp>
  7. #include <app/PortWidget.hpp>
  8. #include <app/ParamWidget.hpp>
  9. #include <app/ModuleLightWidget.hpp>
  10. #include <app/Scene.hpp>
  11. #include <app/SvgPanel.hpp>
  12. #include <engine/Module.hpp>
  13. #include <engine/ParamQuantity.hpp>
  14. #include <context.hpp>
  15. #include <functional>
  16. namespace rack {
  17. /** Returns a Model that constructs a Module and ModuleWidget subclass. */
  18. template <class TModule, class TModuleWidget>
  19. plugin::Model* createModel(std::string slug) {
  20. struct TModel : plugin::Model {
  21. engine::Module* createModule() override {
  22. engine::Module* m = new TModule;
  23. m->model = this;
  24. return m;
  25. }
  26. app::ModuleWidget* createModuleWidget(engine::Module* m) override {
  27. TModule* tm = NULL;
  28. if (m) {
  29. assert(m->model == this);
  30. tm = dynamic_cast<TModule*>(m);
  31. }
  32. app::ModuleWidget* mw = new TModuleWidget(tm);
  33. assert(mw->module == m);
  34. mw->setModel(this);
  35. return mw;
  36. }
  37. };
  38. plugin::Model* o = new TModel;
  39. o->slug = slug;
  40. return o;
  41. }
  42. /** Creates a Widget subclass with its top-left at a position. */
  43. template <class TWidget>
  44. TWidget* createWidget(math::Vec pos) {
  45. TWidget* o = new TWidget;
  46. o->box.pos = pos;
  47. return o;
  48. }
  49. /** Creates a Widget subclass with its center at a position. */
  50. template <class TWidget>
  51. TWidget* createWidgetCentered(math::Vec pos) {
  52. TWidget* o = createWidget<TWidget>(pos);
  53. o->box.pos = o->box.pos.minus(o->box.size.div(2));
  54. return o;
  55. }
  56. /** Creates an SvgPanel and loads the SVG from the given path. */
  57. template <class TPanel = app::SvgPanel>
  58. TPanel* createPanel(std::string svgPath) {
  59. TPanel* panel = new TPanel;
  60. panel->setBackground(window::Svg::load(svgPath));
  61. return panel;
  62. }
  63. /** Creates a ThemedSvgPanel and loads the light/dark SVGs from the given paths. */
  64. template <class TPanel = app::ThemedSvgPanel>
  65. TPanel* createPanel(std::string lightSvgPath, std::string darkSvgPath) {
  66. TPanel* panel = new TPanel;
  67. panel->setBackground(window::Svg::load(lightSvgPath), window::Svg::load(darkSvgPath));
  68. return panel;
  69. }
  70. template <class TParamWidget>
  71. TParamWidget* createParam(math::Vec pos, engine::Module* module, int paramId) {
  72. TParamWidget* o = new TParamWidget;
  73. o->box.pos = pos;
  74. o->app::ParamWidget::module = module;
  75. o->app::ParamWidget::paramId = paramId;
  76. o->initParamQuantity();
  77. return o;
  78. }
  79. template <class TParamWidget>
  80. TParamWidget* createParamCentered(math::Vec pos, engine::Module* module, int paramId) {
  81. TParamWidget* o = createParam<TParamWidget>(pos, module, paramId);
  82. o->box.pos = o->box.pos.minus(o->box.size.div(2));
  83. return o;
  84. }
  85. template <class TPortWidget>
  86. TPortWidget* createInput(math::Vec pos, engine::Module* module, int inputId) {
  87. TPortWidget* o = new TPortWidget;
  88. o->box.pos = pos;
  89. o->app::PortWidget::module = module;
  90. o->app::PortWidget::type = engine::Port::INPUT;
  91. o->app::PortWidget::portId = inputId;
  92. return o;
  93. }
  94. template <class TPortWidget>
  95. TPortWidget* createInputCentered(math::Vec pos, engine::Module* module, int inputId) {
  96. TPortWidget* o = createInput<TPortWidget>(pos, module, inputId);
  97. o->box.pos = o->box.pos.minus(o->box.size.div(2));
  98. return o;
  99. }
  100. template <class TPortWidget>
  101. TPortWidget* createOutput(math::Vec pos, engine::Module* module, int outputId) {
  102. TPortWidget* o = new TPortWidget;
  103. o->box.pos = pos;
  104. o->app::PortWidget::module = module;
  105. o->app::PortWidget::type = engine::Port::OUTPUT;
  106. o->app::PortWidget::portId = outputId;
  107. return o;
  108. }
  109. template <class TPortWidget>
  110. TPortWidget* createOutputCentered(math::Vec pos, engine::Module* module, int outputId) {
  111. TPortWidget* o = createOutput<TPortWidget>(pos, module, outputId);
  112. o->box.pos = o->box.pos.minus(o->box.size.div(2));
  113. return o;
  114. }
  115. template <class TModuleLightWidget>
  116. TModuleLightWidget* createLight(math::Vec pos, engine::Module* module, int firstLightId) {
  117. TModuleLightWidget* o = new TModuleLightWidget;
  118. o->box.pos = pos;
  119. o->app::ModuleLightWidget::module = module;
  120. o->app::ModuleLightWidget::firstLightId = firstLightId;
  121. return o;
  122. }
  123. template <class TModuleLightWidget>
  124. TModuleLightWidget* createLightCentered(math::Vec pos, engine::Module* module, int firstLightId) {
  125. TModuleLightWidget* o = createLight<TModuleLightWidget>(pos, module, firstLightId);
  126. o->box.pos = o->box.pos.minus(o->box.size.div(2));
  127. return o;
  128. }
  129. /** Creates a param with a light and calls setFirstLightId() on it.
  130. Requires ParamWidget to have a `light` member.
  131. */
  132. template <class TParamWidget>
  133. TParamWidget* createLightParam(math::Vec pos, engine::Module* module, int paramId, int firstLightId) {
  134. TParamWidget* o = createParam<TParamWidget>(pos, module, paramId);
  135. o->getLight()->module = module;
  136. o->getLight()->firstLightId = firstLightId;
  137. return o;
  138. }
  139. template <class TParamWidget>
  140. TParamWidget* createLightParamCentered(math::Vec pos, engine::Module* module, int paramId, int firstLightId) {
  141. TParamWidget* o = createLightParam<TParamWidget>(pos, module, paramId, firstLightId);
  142. o->box.pos = o->box.pos.minus(o->box.size.div(2));
  143. return o;
  144. }
  145. template <class TMenu = ui::Menu>
  146. TMenu* createMenu() {
  147. TMenu* menu = new TMenu;
  148. menu->box.pos = APP->scene->mousePos;
  149. ui::MenuOverlay* menuOverlay = new ui::MenuOverlay;
  150. menuOverlay->addChild(menu);
  151. APP->scene->addChild(menuOverlay);
  152. return menu;
  153. }
  154. template <class TMenuLabel = ui::MenuLabel>
  155. TMenuLabel* createMenuLabel(std::string text) {
  156. TMenuLabel* label = new TMenuLabel;
  157. label->text = text;
  158. return label;
  159. }
  160. template <class TMenuItem = ui::MenuItem>
  161. TMenuItem* createMenuItem(std::string text, std::string rightText = "") {
  162. TMenuItem* item = new TMenuItem;
  163. item->text = text;
  164. item->rightText = rightText;
  165. return item;
  166. }
  167. /** Creates a MenuItem with an action that calls a lambda function.
  168. Example:
  169. menu->addChild(createMenuItem("Load sample", "kick.wav",
  170. [=]() {
  171. module->loadSample();
  172. }
  173. ));
  174. */
  175. template <class TMenuItem = ui::MenuItem>
  176. TMenuItem* createMenuItem(std::string text, std::string rightText, std::function<void()> action, bool disabled = false, bool alwaysConsume = false) {
  177. struct Item : TMenuItem {
  178. std::function<void()> action;
  179. bool alwaysConsume;
  180. void onAction(const event::Action& e) override {
  181. action();
  182. if (alwaysConsume)
  183. e.consume(this);
  184. }
  185. };
  186. Item* item = createMenuItem<Item>(text, rightText);
  187. item->action = action;
  188. item->disabled = disabled;
  189. item->alwaysConsume = alwaysConsume;
  190. return item;
  191. }
  192. /** Creates a MenuItem with a check mark set by a lambda function.
  193. Example:
  194. menu->addChild(createCheckMenuItem("Loop", "",
  195. [=]() {
  196. return module->isLoop();
  197. },
  198. [=]() {
  199. module->toggleLoop();
  200. }
  201. ));
  202. */
  203. template <class TMenuItem = ui::MenuItem>
  204. ui::MenuItem* createCheckMenuItem(std::string text, std::string rightText, std::function<bool()> checked, std::function<void()> action, bool disabled = false, bool alwaysConsume = false) {
  205. struct Item : TMenuItem {
  206. std::string rightTextPrefix;
  207. std::function<bool()> checked;
  208. std::function<void()> action;
  209. bool alwaysConsume;
  210. void step() override {
  211. this->rightText = rightTextPrefix;
  212. if (checked()) {
  213. if (!rightTextPrefix.empty())
  214. this->rightText += " ";
  215. this->rightText += CHECKMARK_STRING;
  216. }
  217. TMenuItem::step();
  218. }
  219. void onAction(const event::Action& e) override {
  220. action();
  221. if (alwaysConsume)
  222. e.consume(this);
  223. }
  224. };
  225. Item* item = createMenuItem<Item>(text);
  226. item->rightTextPrefix = rightText;
  227. item->checked = checked;
  228. item->action = action;
  229. item->disabled = disabled;
  230. item->alwaysConsume = alwaysConsume;
  231. return item;
  232. }
  233. /** Creates a MenuItem that controls a boolean value with a check mark.
  234. Example:
  235. menu->addChild(createBoolMenuItem("Loop", "",
  236. [=]() {
  237. return module->isLoop();
  238. },
  239. [=](bool loop) {
  240. module->setLoop(loop);
  241. }
  242. ));
  243. */
  244. template <class TMenuItem = ui::MenuItem>
  245. ui::MenuItem* createBoolMenuItem(std::string text, std::string rightText, std::function<bool()> getter, std::function<void(bool state)> setter, bool disabled = false, bool alwaysConsume = false) {
  246. struct Item : TMenuItem {
  247. std::string rightTextPrefix;
  248. std::function<bool()> getter;
  249. std::function<void(size_t)> setter;
  250. bool alwaysConsume;
  251. void step() override {
  252. this->rightText = rightTextPrefix;
  253. if (getter()) {
  254. if (!rightTextPrefix.empty())
  255. this->rightText += " ";
  256. this->rightText += CHECKMARK_STRING;
  257. }
  258. TMenuItem::step();
  259. }
  260. void onAction(const event::Action& e) override {
  261. setter(!getter());
  262. if (alwaysConsume)
  263. e.consume(this);
  264. }
  265. };
  266. Item* item = createMenuItem<Item>(text);
  267. item->rightTextPrefix = rightText;
  268. item->getter = getter;
  269. item->setter = setter;
  270. item->disabled = disabled;
  271. item->alwaysConsume = alwaysConsume;
  272. return item;
  273. }
  274. /** Easy wrapper for createBoolMenuItem() to modify a bool pointer.
  275. Example:
  276. menu->addChild(createBoolPtrMenuItem("Loop", "", &module->loop));
  277. */
  278. template <typename T>
  279. ui::MenuItem* createBoolPtrMenuItem(std::string text, std::string rightText, T* ptr) {
  280. return createBoolMenuItem(text, rightText,
  281. [=]() {
  282. return ptr ? *ptr : false;
  283. },
  284. [=](T val) {
  285. if (ptr)
  286. *ptr = val;
  287. }
  288. );
  289. }
  290. /** Creates a MenuItem that opens a submenu.
  291. Example:
  292. menu->addChild(createSubmenuItem("Edit", "",
  293. [=](Menu* menu) {
  294. menu->addChild(createMenuItem("Copy", "", [=]() {copy();}));
  295. menu->addChild(createMenuItem("Paste", "", [=]() {paste();}));
  296. }
  297. ));
  298. */
  299. template <class TMenuItem = ui::MenuItem>
  300. ui::MenuItem* createSubmenuItem(std::string text, std::string rightText, std::function<void(ui::Menu* menu)> createMenu, bool disabled = false) {
  301. struct Item : TMenuItem {
  302. std::function<void(ui::Menu* menu)> createMenu;
  303. ui::Menu* createChildMenu() override {
  304. ui::Menu* menu = new ui::Menu;
  305. createMenu(menu);
  306. return menu;
  307. }
  308. };
  309. Item* item = createMenuItem<Item>(text, rightText + (rightText.empty() ? "" : " ") + RIGHT_ARROW);
  310. item->createMenu = createMenu;
  311. item->disabled = disabled;
  312. return item;
  313. }
  314. /** Creates a MenuItem that when hovered, opens a submenu with several MenuItems indexed by an integer.
  315. Example:
  316. menu->addChild(createIndexSubmenuItem("Mode",
  317. {"Hi-fi", "Mid-fi", "Lo-fi"},
  318. [=]() {
  319. return module->getMode();
  320. },
  321. [=](int mode) {
  322. module->setMode(mode);
  323. }
  324. ));
  325. */
  326. template <class TMenuItem = ui::MenuItem>
  327. ui::MenuItem* createIndexSubmenuItem(std::string text, std::vector<std::string> labels, std::function<size_t()> getter, std::function<void(size_t val)> setter, bool disabled = false, bool alwaysConsume = false) {
  328. struct IndexItem : ui::MenuItem {
  329. std::function<size_t()> getter;
  330. std::function<void(size_t)> setter;
  331. size_t index;
  332. bool alwaysConsume;
  333. void step() override {
  334. size_t currIndex = getter();
  335. this->rightText = CHECKMARK(currIndex == index);
  336. MenuItem::step();
  337. }
  338. void onAction(const event::Action& e) override {
  339. setter(index);
  340. if (alwaysConsume)
  341. e.consume(this);
  342. }
  343. };
  344. struct Item : TMenuItem {
  345. std::function<size_t()> getter;
  346. std::function<void(size_t)> setter;
  347. std::vector<std::string> labels;
  348. bool alwaysConsume;
  349. void step() override {
  350. size_t currIndex = getter();
  351. std::string label = (currIndex < labels.size()) ? labels[currIndex] : "";
  352. this->rightText = label + " " + RIGHT_ARROW;
  353. TMenuItem::step();
  354. }
  355. ui::Menu* createChildMenu() override {
  356. ui::Menu* menu = new ui::Menu;
  357. for (size_t i = 0; i < labels.size(); i++) {
  358. IndexItem* item = createMenuItem<IndexItem>(labels[i]);
  359. item->getter = getter;
  360. item->setter = setter;
  361. item->index = i;
  362. item->alwaysConsume = alwaysConsume;
  363. menu->addChild(item);
  364. }
  365. return menu;
  366. }
  367. };
  368. Item* item = createMenuItem<Item>(text);
  369. item->getter = getter;
  370. item->setter = setter;
  371. item->labels = labels;
  372. item->disabled = disabled;
  373. item->alwaysConsume = alwaysConsume;
  374. return item;
  375. }
  376. /** Easy wrapper for createIndexSubmenuItem() that controls an integer index at a pointer address.
  377. Example:
  378. menu->addChild(createIndexPtrSubmenuItem("Mode",
  379. {"Hi-fi", "Mid-fi", "Lo-fi"},
  380. &module->mode
  381. ));
  382. */
  383. template <typename T>
  384. ui::MenuItem* createIndexPtrSubmenuItem(std::string text, std::vector<std::string> labels, T* ptr) {
  385. return createIndexSubmenuItem(text, labels,
  386. [=]() {
  387. return ptr ? *ptr : 0;
  388. },
  389. [=](size_t index) {
  390. if (ptr)
  391. *ptr = T(index);
  392. }
  393. );
  394. }
  395. } // namespace rack