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.

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