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.

292 lines
8.1KB

  1. /*
  2. * DISTRHO Cardinal Plugin
  3. * Copyright (C) 2021 Filipe Coelho <falktx@falktx.com>
  4. *
  5. * This program is free software; you can redistribute it and/or
  6. * modify it under the terms of the GNU General Public License as
  7. * published by the Free Software Foundation; either version 3 of
  8. * the License, or any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * For a full copy of the GNU General Public License see the LICENSE file.
  16. */
  17. #include "AsyncDialog.hpp"
  18. #include <context.hpp>
  19. #include <app/Scene.hpp>
  20. #include <ui/Button.hpp>
  21. #include <ui/Label.hpp>
  22. #include <ui/MenuOverlay.hpp>
  23. #include <ui/SequentialLayout.hpp>
  24. #include <ui/TextField.hpp>
  25. #include <widget/OpaqueWidget.hpp>
  26. namespace asyncDialog
  27. {
  28. using namespace rack;
  29. using namespace rack::ui;
  30. using namespace rack::widget;
  31. struct AsyncDialog : OpaqueWidget
  32. {
  33. static const constexpr float margin = 10;
  34. static const constexpr float buttonWidth = 100;
  35. SequentialLayout* layout;
  36. SequentialLayout* contentLayout;
  37. SequentialLayout* buttonLayout;
  38. Label* label;
  39. AsyncDialog(const char* const message)
  40. {
  41. setup(message);
  42. struct AsyncDismissButton : Button {
  43. AsyncDialog* dialog;
  44. void onAction(const ActionEvent& e) override {
  45. dialog->getParent()->requestDelete();
  46. }
  47. };
  48. AsyncDismissButton* const dismissButton = new AsyncDismissButton;
  49. dismissButton->box.size.x = buttonWidth;
  50. dismissButton->text = "Dismiss";
  51. dismissButton->dialog = this;
  52. buttonLayout->addChild(dismissButton);
  53. }
  54. AsyncDialog(const char* const message, const std::function<void()> action)
  55. {
  56. setup(message);
  57. struct AsyncCancelButton : Button {
  58. AsyncDialog* dialog;
  59. void onAction(const ActionEvent& e) override {
  60. dialog->getParent()->requestDelete();
  61. }
  62. };
  63. AsyncCancelButton* const cancelButton = new AsyncCancelButton;
  64. cancelButton->box.size.x = buttonWidth;
  65. cancelButton->text = "Cancel";
  66. cancelButton->dialog = this;
  67. buttonLayout->addChild(cancelButton);
  68. struct AsyncOkButton : Button {
  69. AsyncDialog* dialog;
  70. std::function<void()> action;
  71. void onAction(const ActionEvent& e) override {
  72. action();
  73. dialog->getParent()->requestDelete();
  74. }
  75. };
  76. AsyncOkButton* const okButton = new AsyncOkButton;
  77. okButton->box.size.x = buttonWidth;
  78. okButton->text = "Ok";
  79. okButton->dialog = this;
  80. okButton->action = action;
  81. buttonLayout->addChild(okButton);
  82. }
  83. void setup(const char* const message)
  84. {
  85. box.size = math::Vec(400, 120);
  86. layout = new SequentialLayout;
  87. layout->box.pos = math::Vec(0, 0);
  88. layout->box.size = box.size;
  89. layout->orientation = SequentialLayout::VERTICAL_ORIENTATION;
  90. layout->margin = math::Vec(margin, margin);
  91. layout->spacing = math::Vec(margin, margin);
  92. layout->wrap = false;
  93. addChild(layout);
  94. contentLayout = new SequentialLayout;
  95. contentLayout->spacing = math::Vec(margin, margin);
  96. layout->addChild(contentLayout);
  97. buttonLayout = new SequentialLayout;
  98. buttonLayout->alignment = SequentialLayout::CENTER_ALIGNMENT;
  99. buttonLayout->box.size = box.size;
  100. buttonLayout->spacing = math::Vec(margin, margin);
  101. layout->addChild(buttonLayout);
  102. label = new Label;
  103. label->box.size.x = box.size.x - 2*margin;
  104. label->box.size.y = box.size.y - 2*margin - 40;
  105. label->fontSize = 16;
  106. label->text = message;
  107. contentLayout->addChild(label);
  108. }
  109. void step() override
  110. {
  111. OpaqueWidget::step();
  112. box.pos = parent->box.size.minus(box.size).div(2).round();
  113. }
  114. void draw(const DrawArgs& args) override
  115. {
  116. bndMenuBackground(args.vg, 0.0, 0.0, box.size.x, box.size.y, 0);
  117. Widget::draw(args);
  118. }
  119. };
  120. void create(const char* const message)
  121. {
  122. MenuOverlay* const overlay = new MenuOverlay;
  123. overlay->bgColor = nvgRGBAf(0, 0, 0, 0.33);
  124. AsyncDialog* const dialog = new AsyncDialog(message);
  125. overlay->addChild(dialog);
  126. APP->scene->addChild(overlay);
  127. }
  128. void create(const char* const message, const std::function<void()> action)
  129. {
  130. MenuOverlay* const overlay = new MenuOverlay;
  131. overlay->bgColor = nvgRGBAf(0, 0, 0, 0.33);
  132. AsyncDialog* const dialog = new AsyncDialog(message, action);
  133. overlay->addChild(dialog);
  134. APP->scene->addChild(overlay);
  135. }
  136. struct AsyncTextInput : OpaqueWidget
  137. {
  138. static const constexpr float margin = 10;
  139. static const constexpr float buttonWidth = 100;
  140. AsyncTextInput(const char* const message, const char* const text, const std::function<void(char* newText)> action)
  141. {
  142. box.size = math::Vec(400, 80);
  143. SequentialLayout* const layout = new SequentialLayout;
  144. layout->box.pos = math::Vec(0, 0);
  145. layout->box.size = box.size;
  146. layout->orientation = SequentialLayout::VERTICAL_ORIENTATION;
  147. layout->margin = math::Vec(margin, margin);
  148. layout->spacing = math::Vec(margin, margin);
  149. layout->wrap = false;
  150. addChild(layout);
  151. SequentialLayout* const contentLayout = new SequentialLayout;
  152. contentLayout->box.size.x = box.size.x - 2*margin;
  153. contentLayout->box.size.y = box.size.y / 2 - margin;
  154. contentLayout->spacing = math::Vec(margin, margin);
  155. layout->addChild(contentLayout);
  156. SequentialLayout* const buttonLayout = new SequentialLayout;
  157. buttonLayout->alignment = SequentialLayout::CENTER_ALIGNMENT;
  158. buttonLayout->box.size.x = box.size.x - 2*margin;
  159. buttonLayout->box.size.y = box.size.y / 2 - margin;
  160. buttonLayout->spacing = math::Vec(margin, margin);
  161. layout->addChild(buttonLayout);
  162. Label* label;
  163. if (message != nullptr)
  164. {
  165. label = new Label;
  166. nvgFontSize(APP->window->vg, 14);
  167. label->box.size.x = std::min(bndLabelWidth(APP->window->vg, -1, message) + margin,
  168. box.size.x / 2 - margin);
  169. label->box.size.y = contentLayout->box.size.y;
  170. label->fontSize = 14;
  171. label->text = message;
  172. contentLayout->addChild(label);
  173. }
  174. else
  175. {
  176. label = nullptr;
  177. }
  178. struct AsyncTextField : TextField {
  179. AsyncTextInput* dialog;
  180. std::function<void(char*)> action;
  181. void onSelectKey(const SelectKeyEvent& e) override {
  182. if (e.key == GLFW_KEY_ENTER || e.key == GLFW_KEY_KP_ENTER)
  183. {
  184. e.consume(this);
  185. action(strdup(text.c_str()));
  186. dialog->getParent()->requestDelete();
  187. return;
  188. }
  189. TextField::onSelectKey(e);
  190. }
  191. void step() override {
  192. APP->event->setSelectedWidget(this);
  193. TextField::step();
  194. }
  195. };
  196. AsyncTextField* const textField = new AsyncTextField;
  197. textField->box.size.x = contentLayout->box.size.x - (label != nullptr ? label->box.size.x + margin : 0);
  198. textField->box.size.y = 24;
  199. textField->dialog = this;
  200. textField->action = action;
  201. if (text != nullptr)
  202. textField->text = text;
  203. contentLayout->addChild(textField);
  204. struct AsyncCancelButton : Button {
  205. AsyncTextInput* dialog;
  206. void onAction(const ActionEvent& e) override {
  207. dialog->getParent()->requestDelete();
  208. }
  209. };
  210. AsyncCancelButton* const cancelButton = new AsyncCancelButton;
  211. cancelButton->box.size.x = buttonWidth;
  212. cancelButton->text = "Cancel";
  213. cancelButton->dialog = this;
  214. buttonLayout->addChild(cancelButton);
  215. struct AsyncOkButton : Button {
  216. AsyncTextInput* dialog;
  217. TextField* textField;
  218. std::function<void(char*)> action;
  219. void onAction(const ActionEvent& e) override {
  220. action(strdup(textField->text.c_str()));
  221. dialog->getParent()->requestDelete();
  222. }
  223. };
  224. AsyncOkButton* const okButton = new AsyncOkButton;
  225. okButton->box.size.x = buttonWidth;
  226. okButton->text = "Ok";
  227. okButton->dialog = this;
  228. okButton->textField = textField;
  229. okButton->action = action;
  230. buttonLayout->addChild(okButton);
  231. }
  232. void step() override
  233. {
  234. OpaqueWidget::step();
  235. box.pos = parent->box.size.minus(box.size).div(2).round();
  236. }
  237. void draw(const DrawArgs& args) override
  238. {
  239. bndMenuBackground(args.vg, 0.0, 0.0, box.size.x, box.size.y, 0);
  240. Widget::draw(args);
  241. }
  242. };
  243. void textInput(const char* const message, const char* const text, std::function<void(char* newText)> action)
  244. {
  245. MenuOverlay* const overlay = new MenuOverlay;
  246. overlay->bgColor = nvgRGBAf(0, 0, 0, 0.33);
  247. AsyncTextInput* const dialog = new AsyncTextInput(message, text, action);
  248. overlay->addChild(dialog);
  249. APP->scene->addChild(overlay);
  250. }
  251. }