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.

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