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.

562 lines
14KB

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