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.

512 lines
13KB

  1. #include "app/ModuleWidget.hpp"
  2. #include "engine/Engine.hpp"
  3. #include "logger.hpp"
  4. #include "system.hpp"
  5. #include "asset.hpp"
  6. #include "app/Scene.hpp"
  7. #include "helpers.hpp"
  8. #include "context.hpp"
  9. #include "osdialog.h"
  10. namespace rack {
  11. ModuleWidget::ModuleWidget(Module *module) {
  12. if (module) {
  13. context()->engine->addModule(module);
  14. }
  15. this->module = module;
  16. }
  17. ModuleWidget::~ModuleWidget() {
  18. // Make sure WireWidget destructors are called *before* removing `module` from the rack.
  19. disconnect();
  20. // Remove and delete the Module instance
  21. if (module) {
  22. context()->engine->removeModule(module);
  23. delete module;
  24. module = NULL;
  25. }
  26. }
  27. void ModuleWidget::addInput(Port *input) {
  28. assert(input->type == Port::INPUT);
  29. inputs.push_back(input);
  30. addChild(input);
  31. }
  32. void ModuleWidget::addOutput(Port *output) {
  33. assert(output->type == Port::OUTPUT);
  34. outputs.push_back(output);
  35. addChild(output);
  36. }
  37. void ModuleWidget::addParam(ParamWidget *param) {
  38. params.push_back(param);
  39. addChild(param);
  40. }
  41. void ModuleWidget::setPanel(std::shared_ptr<SVG> svg) {
  42. // Remove old panel
  43. if (panel) {
  44. removeChild(panel);
  45. delete panel;
  46. panel = NULL;
  47. }
  48. panel = new SVGPanel;
  49. panel->setBackground(svg);
  50. addChild(panel);
  51. box.size = panel->box.size;
  52. }
  53. json_t *ModuleWidget::toJson() {
  54. json_t *rootJ = json_object();
  55. // plugin
  56. json_object_set_new(rootJ, "plugin", json_string(model->plugin->slug.c_str()));
  57. // version of plugin
  58. if (!model->plugin->version.empty())
  59. json_object_set_new(rootJ, "version", json_string(model->plugin->version.c_str()));
  60. // model
  61. json_object_set_new(rootJ, "model", json_string(model->slug.c_str()));
  62. // Other properties
  63. if (module) {
  64. json_t *moduleJ = module->toJson();
  65. // Merge with rootJ
  66. json_object_update(rootJ, moduleJ);
  67. json_decref(moduleJ);
  68. }
  69. return rootJ;
  70. }
  71. void ModuleWidget::fromJson(json_t *rootJ) {
  72. // Check if plugin and model are incorrect
  73. json_t *pluginJ = json_object_get(rootJ, "plugin");
  74. std::string pluginSlug;
  75. if (pluginJ) {
  76. pluginSlug = json_string_value(pluginJ);
  77. if (pluginSlug != model->plugin->slug) {
  78. WARN("Plugin %s does not match ModuleWidget's plugin %s.", pluginSlug.c_str(), model->plugin->slug.c_str());
  79. return;
  80. }
  81. }
  82. json_t *modelJ = json_object_get(rootJ, "model");
  83. std::string modelSlug;
  84. if (modelJ) {
  85. modelSlug = json_string_value(modelJ);
  86. if (modelSlug != model->slug) {
  87. WARN("Model %s does not match ModuleWidget's model %s.", modelSlug.c_str(), model->slug.c_str());
  88. return;
  89. }
  90. }
  91. // Check plugin version
  92. json_t *versionJ = json_object_get(rootJ, "version");
  93. if (versionJ) {
  94. std::string version = json_string_value(versionJ);
  95. if (version != model->plugin->version) {
  96. INFO("Patch created with %s version %s, using version %s.", pluginSlug.c_str(), version.c_str(), model->plugin->version.c_str());
  97. }
  98. }
  99. if (module) {
  100. module->fromJson(rootJ);
  101. }
  102. }
  103. void ModuleWidget::copyClipboard() {
  104. json_t *moduleJ = toJson();
  105. char *moduleJson = json_dumps(moduleJ, JSON_INDENT(2) | JSON_REAL_PRECISION(9));
  106. glfwSetClipboardString(context()->window->win, moduleJson);
  107. free(moduleJson);
  108. json_decref(moduleJ);
  109. }
  110. void ModuleWidget::pasteClipboard() {
  111. const char *moduleJson = glfwGetClipboardString(context()->window->win);
  112. if (!moduleJson) {
  113. WARN("Could not get text from clipboard.");
  114. return;
  115. }
  116. json_error_t error;
  117. json_t *moduleJ = json_loads(moduleJson, 0, &error);
  118. if (moduleJ) {
  119. fromJson(moduleJ);
  120. json_decref(moduleJ);
  121. }
  122. else {
  123. WARN("JSON parsing error at %s %d:%d %s", error.source, error.line, error.column, error.text);
  124. }
  125. }
  126. void ModuleWidget::load(std::string filename) {
  127. INFO("Loading preset %s", filename.c_str());
  128. FILE *file = fopen(filename.c_str(), "r");
  129. if (!file) {
  130. // Exit silently
  131. return;
  132. }
  133. json_error_t error;
  134. json_t *moduleJ = json_loadf(file, 0, &error);
  135. if (moduleJ) {
  136. fromJson(moduleJ);
  137. json_decref(moduleJ);
  138. }
  139. else {
  140. std::string message = string::f("JSON parsing error at %s %d:%d %s", error.source, error.line, error.column, error.text);
  141. osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, message.c_str());
  142. }
  143. fclose(file);
  144. }
  145. void ModuleWidget::save(std::string filename) {
  146. INFO("Saving preset %s", filename.c_str());
  147. json_t *moduleJ = toJson();
  148. if (!moduleJ)
  149. return;
  150. FILE *file = fopen(filename.c_str(), "w");
  151. if (file) {
  152. json_dumpf(moduleJ, file, JSON_INDENT(2) | JSON_REAL_PRECISION(9));
  153. fclose(file);
  154. }
  155. json_decref(moduleJ);
  156. }
  157. void ModuleWidget::loadDialog() {
  158. std::string dir = asset::user("presets");
  159. system::createDirectory(dir);
  160. osdialog_filters *filters = osdialog_filters_parse(PRESET_FILTERS.c_str());
  161. char *path = osdialog_file(OSDIALOG_OPEN, dir.c_str(), NULL, filters);
  162. if (path) {
  163. load(path);
  164. free(path);
  165. }
  166. osdialog_filters_free(filters);
  167. }
  168. void ModuleWidget::saveDialog() {
  169. std::string dir = asset::user("presets");
  170. system::createDirectory(dir);
  171. osdialog_filters *filters = osdialog_filters_parse(PRESET_FILTERS.c_str());
  172. char *path = osdialog_file(OSDIALOG_SAVE, dir.c_str(), "Untitled.vcvm", filters);
  173. if (path) {
  174. std::string pathStr = path;
  175. free(path);
  176. std::string extension = string::extension(pathStr);
  177. if (extension.empty()) {
  178. pathStr += ".vcvm";
  179. }
  180. save(pathStr);
  181. }
  182. osdialog_filters_free(filters);
  183. }
  184. void ModuleWidget::disconnect() {
  185. for (Port *input : inputs) {
  186. context()->scene->rackWidget->wireContainer->removeAllWires(input);
  187. }
  188. for (Port *output : outputs) {
  189. context()->scene->rackWidget->wireContainer->removeAllWires(output);
  190. }
  191. }
  192. void ModuleWidget::create() {
  193. }
  194. void ModuleWidget::_delete() {
  195. }
  196. void ModuleWidget::reset() {
  197. for (ParamWidget *param : params) {
  198. param->reset();
  199. }
  200. if (module) {
  201. context()->engine->resetModule(module);
  202. }
  203. }
  204. void ModuleWidget::randomize() {
  205. for (ParamWidget *param : params) {
  206. param->randomize();
  207. }
  208. if (module) {
  209. context()->engine->randomizeModule(module);
  210. }
  211. }
  212. void ModuleWidget::draw(NVGcontext *vg) {
  213. nvgScissor(vg, 0, 0, box.size.x, box.size.y);
  214. Widget::draw(vg);
  215. // Power meter
  216. if (module && context()->engine->powerMeter) {
  217. nvgBeginPath(vg);
  218. nvgRect(vg,
  219. 0, box.size.y - 20,
  220. 55, 20);
  221. nvgFillColor(vg, nvgRGBAf(0, 0, 0, 0.5));
  222. nvgFill(vg);
  223. std::string cpuText = string::f("%.0f mS", module->cpuTime * 1000.f);
  224. // TODO Use blendish text function
  225. nvgFontFaceId(vg, context()->window->uiFont->handle);
  226. nvgFontSize(vg, 12);
  227. nvgFillColor(vg, nvgRGBf(1, 1, 1));
  228. nvgText(vg, 10.0, box.size.y - 6.0, cpuText.c_str(), NULL);
  229. float p = math::clamp(module->cpuTime, 0.f, 1.f);
  230. nvgBeginPath(vg);
  231. nvgRect(vg,
  232. 0, (1.f - p) * box.size.y,
  233. 5, p * box.size.y);
  234. nvgFillColor(vg, nvgRGBAf(1, 0, 0, 1.0));
  235. nvgFill(vg);
  236. }
  237. nvgResetScissor(vg);
  238. }
  239. void ModuleWidget::drawShadow(NVGcontext *vg) {
  240. nvgBeginPath(vg);
  241. float r = 20; // Blur radius
  242. float c = 20; // Corner radius
  243. math::Vec b = math::Vec(-10, 30); // Offset from each corner
  244. nvgRect(vg, b.x - r, b.y - r, box.size.x - 2*b.x + 2*r, box.size.y - 2*b.y + 2*r);
  245. NVGcolor shadowColor = nvgRGBAf(0, 0, 0, 0.2);
  246. NVGcolor transparentColor = nvgRGBAf(0, 0, 0, 0);
  247. nvgFillPaint(vg, nvgBoxGradient(vg, b.x, b.y, box.size.x - 2*b.x, box.size.y - 2*b.y, c, r, shadowColor, transparentColor));
  248. nvgFill(vg);
  249. }
  250. void ModuleWidget::onHover(event::Hover &e) {
  251. OpaqueWidget::onHover(e);
  252. // Instead of checking key-down events, delete the module even if key-repeat hasn't fired yet and the cursor is hovering over the widget.
  253. if (glfwGetKey(context()->window->win, GLFW_KEY_DELETE) == GLFW_PRESS || glfwGetKey(context()->window->win, GLFW_KEY_BACKSPACE) == GLFW_PRESS) {
  254. if (!context()->window->isModPressed() && !context()->window->isShiftPressed()) {
  255. context()->scene->rackWidget->deleteModule(this);
  256. delete this;
  257. // e.target = this;
  258. return;
  259. }
  260. }
  261. }
  262. void ModuleWidget::onButton(event::Button &e) {
  263. OpaqueWidget::onButton(e);
  264. if (e.target == this) {
  265. if (e.button == 1) {
  266. createContextMenu();
  267. }
  268. }
  269. }
  270. void ModuleWidget::onHoverKey(event::HoverKey &e) {
  271. if (e.action == GLFW_PRESS) {
  272. switch (e.key) {
  273. case GLFW_KEY_I: {
  274. if (context()->window->isModPressed() && !context()->window->isShiftPressed()) {
  275. reset();
  276. e.target = this;
  277. }
  278. } break;
  279. case GLFW_KEY_R: {
  280. if (context()->window->isModPressed() && !context()->window->isShiftPressed()) {
  281. randomize();
  282. e.target = this;
  283. }
  284. } break;
  285. case GLFW_KEY_C: {
  286. if (context()->window->isModPressed() && !context()->window->isShiftPressed()) {
  287. copyClipboard();
  288. e.target = this;
  289. }
  290. } break;
  291. case GLFW_KEY_V: {
  292. if (context()->window->isModPressed() && !context()->window->isShiftPressed()) {
  293. pasteClipboard();
  294. e.target = this;
  295. }
  296. } break;
  297. case GLFW_KEY_D: {
  298. if (context()->window->isModPressed() && !context()->window->isShiftPressed()) {
  299. context()->scene->rackWidget->cloneModule(this);
  300. e.target = this;
  301. }
  302. } break;
  303. case GLFW_KEY_U: {
  304. if (context()->window->isModPressed() && !context()->window->isShiftPressed()) {
  305. disconnect();
  306. e.target = this;
  307. }
  308. } break;
  309. }
  310. }
  311. if (!e.target)
  312. OpaqueWidget::onHoverKey(e);
  313. }
  314. void ModuleWidget::onDragStart(event::DragStart &e) {
  315. dragPos = context()->scene->rackWidget->lastMousePos.minus(box.pos);
  316. e.target = this;
  317. }
  318. void ModuleWidget::onDragEnd(event::DragEnd &e) {
  319. }
  320. void ModuleWidget::onDragMove(event::DragMove &e) {
  321. if (!context()->scene->rackWidget->lockModules) {
  322. math::Rect newBox = box;
  323. newBox.pos = context()->scene->rackWidget->lastMousePos.minus(dragPos);
  324. context()->scene->rackWidget->requestModuleBoxNearest(this, newBox);
  325. }
  326. }
  327. struct ModuleDisconnectItem : MenuItem {
  328. ModuleWidget *moduleWidget;
  329. void onAction(event::Action &e) override {
  330. moduleWidget->disconnect();
  331. }
  332. };
  333. struct ModuleResetItem : MenuItem {
  334. ModuleWidget *moduleWidget;
  335. void onAction(event::Action &e) override {
  336. moduleWidget->reset();
  337. }
  338. };
  339. struct ModuleRandomizeItem : MenuItem {
  340. ModuleWidget *moduleWidget;
  341. void onAction(event::Action &e) override {
  342. moduleWidget->randomize();
  343. }
  344. };
  345. struct ModuleCopyItem : MenuItem {
  346. ModuleWidget *moduleWidget;
  347. void onAction(event::Action &e) override {
  348. moduleWidget->copyClipboard();
  349. }
  350. };
  351. struct ModulePasteItem : MenuItem {
  352. ModuleWidget *moduleWidget;
  353. void onAction(event::Action &e) override {
  354. moduleWidget->pasteClipboard();
  355. }
  356. };
  357. struct ModuleSaveItem : MenuItem {
  358. ModuleWidget *moduleWidget;
  359. void onAction(event::Action &e) override {
  360. moduleWidget->saveDialog();
  361. }
  362. };
  363. struct ModuleLoadItem : MenuItem {
  364. ModuleWidget *moduleWidget;
  365. void onAction(event::Action &e) override {
  366. moduleWidget->loadDialog();
  367. }
  368. };
  369. struct ModuleCloneItem : MenuItem {
  370. ModuleWidget *moduleWidget;
  371. void onAction(event::Action &e) override {
  372. context()->scene->rackWidget->cloneModule(moduleWidget);
  373. }
  374. };
  375. struct ModuleDeleteItem : MenuItem {
  376. ModuleWidget *moduleWidget;
  377. void onAction(event::Action &e) override {
  378. context()->scene->rackWidget->deleteModule(moduleWidget);
  379. delete moduleWidget;
  380. }
  381. };
  382. Menu *ModuleWidget::createContextMenu() {
  383. Menu *menu = createMenu();
  384. MenuLabel *menuLabel = new MenuLabel;
  385. menuLabel->text = model->plugin->author + " " + model->name + " " + model->plugin->version;
  386. menu->addChild(menuLabel);
  387. ModuleResetItem *resetItem = new ModuleResetItem;
  388. resetItem->text = "Initialize";
  389. resetItem->rightText = WINDOW_MOD_KEY_NAME "+I";
  390. resetItem->moduleWidget = this;
  391. menu->addChild(resetItem);
  392. ModuleRandomizeItem *randomizeItem = new ModuleRandomizeItem;
  393. randomizeItem->text = "Randomize";
  394. randomizeItem->rightText = WINDOW_MOD_KEY_NAME "+R";
  395. randomizeItem->moduleWidget = this;
  396. menu->addChild(randomizeItem);
  397. ModuleDisconnectItem *disconnectItem = new ModuleDisconnectItem;
  398. disconnectItem->text = "Disconnect cables";
  399. disconnectItem->rightText = WINDOW_MOD_KEY_NAME "+U";
  400. disconnectItem->moduleWidget = this;
  401. menu->addChild(disconnectItem);
  402. ModuleCloneItem *cloneItem = new ModuleCloneItem;
  403. cloneItem->text = "Duplicate";
  404. cloneItem->rightText = WINDOW_MOD_KEY_NAME "+D";
  405. cloneItem->moduleWidget = this;
  406. menu->addChild(cloneItem);
  407. ModuleCopyItem *copyItem = new ModuleCopyItem;
  408. copyItem->text = "Copy preset";
  409. copyItem->rightText = WINDOW_MOD_KEY_NAME "+C";
  410. copyItem->moduleWidget = this;
  411. menu->addChild(copyItem);
  412. ModulePasteItem *pasteItem = new ModulePasteItem;
  413. pasteItem->text = "Paste preset";
  414. pasteItem->rightText = WINDOW_MOD_KEY_NAME "+V";
  415. pasteItem->moduleWidget = this;
  416. menu->addChild(pasteItem);
  417. ModuleLoadItem *loadItem = new ModuleLoadItem;
  418. loadItem->text = "Load preset";
  419. loadItem->moduleWidget = this;
  420. menu->addChild(loadItem);
  421. ModuleSaveItem *saveItem = new ModuleSaveItem;
  422. saveItem->text = "Save preset";
  423. saveItem->moduleWidget = this;
  424. menu->addChild(saveItem);
  425. ModuleDeleteItem *deleteItem = new ModuleDeleteItem;
  426. deleteItem->text = "Delete";
  427. deleteItem->rightText = "Backspace/Delete";
  428. deleteItem->moduleWidget = this;
  429. menu->addChild(deleteItem);
  430. appendContextMenu(menu);
  431. return menu;
  432. }
  433. } // namespace rack