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.

583 lines
15KB

  1. /*
  2. * DISTRHO Cardinal Plugin
  3. * Copyright (C) 2021-2022 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. /**
  18. * This file is an edited version of VCVRack's app/Scene.cpp
  19. * Copyright (C) 2016-2021 VCV.
  20. *
  21. * This program is free software: you can redistribute it and/or
  22. * modify it under the terms of the GNU General Public License as
  23. * published by the Free Software Foundation; either version 3 of
  24. * the License, or (at your option) any later version.
  25. */
  26. #include <thread>
  27. #include <osdialog.h>
  28. #include <app/Scene.hpp>
  29. #include <app/Browser.hpp>
  30. #include <app/TipWindow.hpp>
  31. #include <app/MenuBar.hpp>
  32. #include <context.hpp>
  33. #include <engine/Engine.hpp>
  34. #include <system.hpp>
  35. #include <network.hpp>
  36. #include <history.hpp>
  37. #include <settings.hpp>
  38. #include <patch.hpp>
  39. #include <asset.hpp>
  40. #ifdef NDEBUG
  41. # undef DEBUG
  42. #endif
  43. #ifdef STATIC_BUILD
  44. # undef HAVE_LIBLO
  45. #endif
  46. #ifdef HAVE_LIBLO
  47. # include <lo/lo.h>
  48. #endif
  49. #include "../CardinalCommon.hpp"
  50. #include "extra/Base64.hpp"
  51. #include "DistrhoUtils.hpp"
  52. namespace rack {
  53. namespace app {
  54. struct ResizeHandle : widget::OpaqueWidget {
  55. math::Vec size;
  56. void draw(const DrawArgs& args) override {
  57. nvgStrokeColor(args.vg, nvgRGBf(1, 1, 1));
  58. nvgStrokeWidth(args.vg, 1);
  59. nvgBeginPath(args.vg);
  60. nvgMoveTo(args.vg, box.size.x, 0);
  61. nvgLineTo(args.vg, 0, box.size.y);
  62. nvgStroke(args.vg);
  63. nvgBeginPath(args.vg);
  64. nvgMoveTo(args.vg, box.size.x + 5, 0);
  65. nvgLineTo(args.vg, 0, box.size.y + 5);
  66. nvgStroke(args.vg);
  67. nvgBeginPath(args.vg);
  68. nvgMoveTo(args.vg, box.size.x + 10, 0);
  69. nvgLineTo(args.vg, 0, box.size.y + 10);
  70. nvgStroke(args.vg);
  71. nvgStrokeColor(args.vg, nvgRGBf(0, 0, 0));
  72. nvgBeginPath(args.vg);
  73. nvgMoveTo(args.vg, box.size.x+1, 0);
  74. nvgLineTo(args.vg, 0, box.size.y+1);
  75. nvgStroke(args.vg);
  76. nvgBeginPath(args.vg);
  77. nvgMoveTo(args.vg, box.size.x + 6, 0);
  78. nvgLineTo(args.vg, 0, box.size.y + 6);
  79. nvgStroke(args.vg);
  80. nvgBeginPath(args.vg);
  81. nvgMoveTo(args.vg, box.size.x + 11, 0);
  82. nvgLineTo(args.vg, 0, box.size.y + 11);
  83. nvgStroke(args.vg);
  84. }
  85. void onHover(const HoverEvent& e) override {
  86. e.consume(this);
  87. }
  88. void onEnter(const EnterEvent& e) override {
  89. glfwSetCursor(APP->window->win, glfwCreateStandardCursor(GLFW_RESIZE_NWSE_CURSOR));
  90. }
  91. void onLeave(const LeaveEvent& e) override {
  92. glfwSetCursor(APP->window->win, nullptr);
  93. }
  94. void onDragStart(const DragStartEvent&) override {
  95. size = APP->window->getSize();
  96. }
  97. void onDragMove(const DragMoveEvent& e) override {
  98. size = size.plus(e.mouseDelta.mult(APP->window->pixelRatio));
  99. APP->window->setSize(size.round());
  100. }
  101. };
  102. struct Scene::Internal {
  103. ResizeHandle* resizeHandle = nullptr;
  104. bool heldArrowKeys[4] = {};
  105. #ifdef HAVE_LIBLO
  106. double lastSceneChangeTime = 0.0;
  107. int historyActionIndex = -1;
  108. bool oscAutoDeploy = false;
  109. bool oscConnected = false;
  110. lo_server oscServer = nullptr;
  111. static int osc_handler(const char* const path, const char* const types, lo_arg** argv, const int argc, lo_message, void* const self)
  112. {
  113. d_stdout("osc_handler(\"%s\", \"%s\", %p, %i)", path, types, argv, argc);
  114. if (std::strcmp(path, "/resp") == 0 && argc == 2 && types[0] == 's' && types[1] == 's') {
  115. d_stdout("osc_handler(\"%s\", ...) - got resp | '%s' '%s'", path, &argv[0]->s, &argv[1]->s);
  116. if (std::strcmp(&argv[0]->s, "hello") == 0 && std::strcmp(&argv[1]->s, "ok") == 0)
  117. static_cast<Internal*>(self)->oscConnected = true;
  118. }
  119. return 0;
  120. }
  121. ~Internal() {
  122. lo_server_free(oscServer);
  123. }
  124. #endif
  125. };
  126. Scene::Scene() {
  127. internal = new Internal;
  128. rackScroll = new RackScrollWidget;
  129. addChild(rackScroll);
  130. rack = rackScroll->rackWidget;
  131. menuBar = createMenuBar();
  132. addChild(menuBar);
  133. browser = browserCreate();
  134. browser->hide();
  135. addChild(browser);
  136. if (isStandalone())
  137. return;
  138. internal->resizeHandle = new ResizeHandle;
  139. internal->resizeHandle->box.size = math::Vec(16, 16);
  140. addChild(internal->resizeHandle);
  141. }
  142. Scene::~Scene() {
  143. delete internal;
  144. }
  145. math::Vec Scene::getMousePos() {
  146. return mousePos;
  147. }
  148. void Scene::step() {
  149. if (APP->window->isFullScreen()) {
  150. // Expand RackScrollWidget to cover entire screen if fullscreen
  151. rackScroll->box.pos.y = 0;
  152. }
  153. else {
  154. // Always show MenuBar if not fullscreen
  155. menuBar->show();
  156. rackScroll->box.pos.y = menuBar->box.size.y;
  157. }
  158. if (internal->resizeHandle != nullptr)
  159. internal->resizeHandle->box.pos = box.size.minus(internal->resizeHandle->box.size);
  160. // Resize owned descendants
  161. menuBar->box.size.x = box.size.x;
  162. rackScroll->box.size = box.size.minus(rackScroll->box.pos);
  163. // Scroll RackScrollWidget with arrow keys
  164. math::Vec arrowDelta;
  165. if (internal->heldArrowKeys[0]) {
  166. arrowDelta.x -= 1;
  167. }
  168. if (internal->heldArrowKeys[1]) {
  169. arrowDelta.x += 1;
  170. }
  171. if (internal->heldArrowKeys[2]) {
  172. arrowDelta.y -= 1;
  173. }
  174. if (internal->heldArrowKeys[3]) {
  175. arrowDelta.y += 1;
  176. }
  177. if (!arrowDelta.isZero()) {
  178. int mods = APP->window->getMods();
  179. float arrowSpeed = 32.f;
  180. if ((mods & RACK_MOD_MASK) == RACK_MOD_CTRL)
  181. arrowSpeed /= 4.f;
  182. if ((mods & RACK_MOD_MASK) == GLFW_MOD_SHIFT)
  183. arrowSpeed *= 4.f;
  184. if ((mods & RACK_MOD_MASK) == (RACK_MOD_CTRL | GLFW_MOD_SHIFT))
  185. arrowSpeed /= 16.f;
  186. rackScroll->offset += arrowDelta * arrowSpeed;
  187. }
  188. #ifdef HAVE_LIBLO
  189. if (internal->oscServer != nullptr) {
  190. while (lo_server_recv_noblock(internal->oscServer, 0) != 0) {}
  191. if (internal->oscAutoDeploy) {
  192. const int actionIndex = APP->history->actionIndex;
  193. const double time = system::getTime();
  194. if (internal->historyActionIndex != actionIndex && time - internal->lastSceneChangeTime >= 5.0) {
  195. internal->historyActionIndex = actionIndex;
  196. internal->lastSceneChangeTime = time;
  197. patchUtils::deployToRemote();
  198. window::generateScreenshot();
  199. }
  200. }
  201. }
  202. #endif
  203. Widget::step();
  204. }
  205. void Scene::draw(const DrawArgs& args) {
  206. Widget::draw(args);
  207. }
  208. void Scene::onHover(const HoverEvent& e) {
  209. mousePos = e.pos;
  210. if (mousePos.y < menuBar->box.size.y) {
  211. menuBar->show();
  212. }
  213. OpaqueWidget::onHover(e);
  214. }
  215. void Scene::onDragHover(const DragHoverEvent& e) {
  216. mousePos = e.pos;
  217. OpaqueWidget::onDragHover(e);
  218. }
  219. void Scene::onHoverKey(const HoverKeyEvent& e) {
  220. // Key commands that override children
  221. if (e.action == GLFW_PRESS || e.action == GLFW_REPEAT) {
  222. // DEBUG("key '%d '%c' scancode %d '%c' keyName '%s'", e.key, e.key, e.scancode, e.scancode, e.keyName.c_str());
  223. if (e.keyName == "n" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  224. patchUtils::loadTemplateDialog();
  225. e.consume(this);
  226. }
  227. if (e.keyName == "q" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  228. APP->window->close();
  229. e.consume(this);
  230. }
  231. if (e.keyName == "o" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  232. patchUtils::loadDialog();
  233. e.consume(this);
  234. }
  235. if (e.keyName == "o" && (e.mods & RACK_MOD_MASK) == (RACK_MOD_CTRL | GLFW_MOD_SHIFT)) {
  236. patchUtils::revertDialog();
  237. e.consume(this);
  238. }
  239. #ifndef DISTRHO_OS_WASM
  240. if (e.keyName == "s" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  241. // NOTE: will do nothing if path is empty, intentionally
  242. patchUtils::saveDialog(APP->patch->path);
  243. e.consume(this);
  244. }
  245. #endif
  246. if (e.keyName == "s" && (e.mods & RACK_MOD_MASK) == (RACK_MOD_CTRL | GLFW_MOD_SHIFT)) {
  247. patchUtils::saveAsDialog();
  248. e.consume(this);
  249. }
  250. if (e.keyName == "z" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  251. APP->history->undo();
  252. e.consume(this);
  253. }
  254. if (e.keyName == "z" && (e.mods & RACK_MOD_MASK) == (RACK_MOD_CTRL | GLFW_MOD_SHIFT)) {
  255. APP->history->redo();
  256. e.consume(this);
  257. }
  258. if (e.keyName == "-" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  259. float zoom = std::log2(APP->scene->rackScroll->getZoom());
  260. zoom *= 2;
  261. zoom = std::ceil(zoom - 0.01f) - 1;
  262. zoom /= 2;
  263. APP->scene->rackScroll->setZoom(std::pow(2.f, zoom));
  264. e.consume(this);
  265. }
  266. // Numpad has a "+" key, but the main keyboard section hides it under "="
  267. if ((e.keyName == "=" || e.keyName == "+") && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  268. float zoom = std::log2(APP->scene->rackScroll->getZoom());
  269. zoom *= 2;
  270. zoom = std::floor(zoom + 0.01f) + 1;
  271. zoom /= 2;
  272. APP->scene->rackScroll->setZoom(std::pow(2.f, zoom));
  273. e.consume(this);
  274. }
  275. if ((e.keyName == "0" || e.keyName == "1") && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  276. APP->scene->rackScroll->setZoom(1.f);
  277. e.consume(this);
  278. }
  279. if (e.keyName == "2" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  280. APP->scene->rackScroll->setZoom(2.f);
  281. e.consume(this);
  282. }
  283. if (e.key == GLFW_KEY_F1 && (e.mods & RACK_MOD_MASK) == 0) {
  284. patchUtils::openBrowser("https://vcvrack.com/manual/");
  285. e.consume(this);
  286. }
  287. if (e.key == GLFW_KEY_F3 && (e.mods & RACK_MOD_MASK) == 0) {
  288. settings::cpuMeter ^= true;
  289. e.consume(this);
  290. }
  291. if (e.key == GLFW_KEY_F7 && (e.mods & RACK_MOD_MASK) == 0) {
  292. patchUtils::deployToRemote();
  293. window::generateScreenshot();
  294. e.consume(this);
  295. }
  296. if (e.key == GLFW_KEY_F9 && (e.mods & RACK_MOD_MASK) == 0) {
  297. window::generateScreenshot();
  298. e.consume(this);
  299. }
  300. #ifdef DISTRHO_OS_WASM
  301. if (e.key == GLFW_KEY_F11 && (e.mods & RACK_MOD_MASK) == 0) {
  302. APP->window->setFullScreen(!APP->window->isFullScreen());
  303. e.consume(this);
  304. }
  305. #endif
  306. // Module selections
  307. if (e.keyName == "a" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  308. rack->selectAll();
  309. e.consume(this);
  310. }
  311. if (e.keyName == "a" && (e.mods & RACK_MOD_MASK) == (RACK_MOD_CTRL | GLFW_MOD_SHIFT)) {
  312. rack->deselectAll();
  313. e.consume(this);
  314. }
  315. if (e.keyName == "c" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  316. if (rack->hasSelection()) {
  317. rack->copyClipboardSelection();
  318. e.consume(this);
  319. }
  320. }
  321. if (e.keyName == "i" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  322. if (rack->hasSelection()) {
  323. rack->resetSelectionAction();
  324. e.consume(this);
  325. }
  326. }
  327. if (e.keyName == "r" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  328. if (rack->hasSelection()) {
  329. rack->randomizeSelectionAction();
  330. e.consume(this);
  331. }
  332. }
  333. if (e.keyName == "u" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  334. if (rack->hasSelection()) {
  335. rack->disconnectSelectionAction();
  336. e.consume(this);
  337. }
  338. }
  339. if (e.keyName == "e" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  340. if (rack->hasSelection()) {
  341. rack->bypassSelectionAction(!rack->isSelectionBypassed());
  342. e.consume(this);
  343. }
  344. }
  345. if (e.keyName == "d" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  346. if (rack->hasSelection()) {
  347. rack->cloneSelectionAction(false);
  348. e.consume(this);
  349. }
  350. }
  351. if (e.keyName == "d" && (e.mods & RACK_MOD_MASK) == (RACK_MOD_CTRL | GLFW_MOD_SHIFT)) {
  352. if (rack->hasSelection()) {
  353. rack->cloneSelectionAction(true);
  354. e.consume(this);
  355. }
  356. }
  357. if ((e.key == GLFW_KEY_DELETE || e.key == GLFW_KEY_BACKSPACE) && (e.mods & RACK_MOD_MASK) == 0) {
  358. if (rack->hasSelection()) {
  359. rack->deleteSelectionAction();
  360. e.consume(this);
  361. }
  362. }
  363. }
  364. // Scroll RackScrollWidget with arrow keys
  365. if (e.action == GLFW_PRESS || e.action == GLFW_RELEASE) {
  366. if (e.key == GLFW_KEY_LEFT) {
  367. internal->heldArrowKeys[0] = (e.action == GLFW_PRESS);
  368. e.consume(this);
  369. }
  370. if (e.key == GLFW_KEY_RIGHT) {
  371. internal->heldArrowKeys[1] = (e.action == GLFW_PRESS);
  372. e.consume(this);
  373. }
  374. if (e.key == GLFW_KEY_UP) {
  375. internal->heldArrowKeys[2] = (e.action == GLFW_PRESS);
  376. e.consume(this);
  377. }
  378. if (e.key == GLFW_KEY_DOWN) {
  379. internal->heldArrowKeys[3] = (e.action == GLFW_PRESS);
  380. e.consume(this);
  381. }
  382. }
  383. if (e.isConsumed())
  384. return;
  385. OpaqueWidget::onHoverKey(e);
  386. if (e.isConsumed())
  387. return;
  388. // Key commands that can be overridden by children
  389. if (e.action == GLFW_PRESS || e.action == GLFW_REPEAT) {
  390. if (e.keyName == "v" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  391. rack->pasteClipboardAction();
  392. e.consume(this);
  393. }
  394. if ((e.key == GLFW_KEY_ENTER || e.key == GLFW_KEY_KP_ENTER) && (e.mods & RACK_MOD_MASK) == 0) {
  395. browser->show();
  396. e.consume(this);
  397. }
  398. }
  399. }
  400. void Scene::onPathDrop(const PathDropEvent& e) {
  401. if (e.paths.size() >= 1) {
  402. const std::string& path = e.paths[0];
  403. std::string extension = system::getExtension(path);
  404. if (extension == ".vcv") {
  405. patchUtils::loadPathDialog(path);
  406. e.consume(this);
  407. return;
  408. }
  409. if (extension == ".vcvs") {
  410. APP->scene->rack->loadSelection(path);
  411. e.consume(this);
  412. return;
  413. }
  414. }
  415. OpaqueWidget::onPathDrop(e);
  416. }
  417. } // namespace app
  418. } // namespace rack
  419. namespace patchUtils {
  420. bool connectToRemote() {
  421. #ifdef HAVE_LIBLO
  422. rack::app::Scene::Internal* const internal = APP->scene->internal;
  423. if (internal->oscServer == nullptr) {
  424. const lo_server oscServer = lo_server_new_with_proto(nullptr, LO_UDP, nullptr);
  425. DISTRHO_SAFE_ASSERT_RETURN(oscServer != nullptr, false);
  426. lo_server_add_method(oscServer, "/resp", nullptr, rack::app::Scene::Internal::osc_handler, internal);
  427. internal->oscServer = oscServer;
  428. }
  429. const lo_address addr = lo_address_new_with_proto(LO_UDP, REMOTE_HOST, REMOTE_HOST_PORT);
  430. DISTRHO_SAFE_ASSERT_RETURN(addr != nullptr, false);
  431. lo_send(addr, "/hello", "");
  432. lo_address_free(addr);
  433. return true;
  434. #else
  435. return false;
  436. #endif
  437. }
  438. bool isRemoteConnected() {
  439. #ifdef HAVE_LIBLO
  440. return APP->scene->internal->oscConnected;
  441. #else
  442. return false;
  443. #endif
  444. }
  445. bool isRemoteAutoDeployed() {
  446. #ifdef HAVE_LIBLO
  447. return APP->scene->internal->oscAutoDeploy;
  448. #else
  449. return false;
  450. #endif
  451. }
  452. void setRemoteAutoDeploy(bool autoDeploy) {
  453. #ifdef HAVE_LIBLO
  454. APP->scene->internal->oscAutoDeploy = autoDeploy;
  455. #endif
  456. }
  457. void deployToRemote() {
  458. #ifdef HAVE_LIBLO
  459. const lo_address addr = lo_address_new_with_proto(LO_UDP, REMOTE_HOST, REMOTE_HOST_PORT);
  460. DISTRHO_SAFE_ASSERT_RETURN(addr != nullptr,);
  461. APP->engine->prepareSave();
  462. APP->patch->saveAutosave();
  463. APP->patch->cleanAutosave();
  464. std::vector<uint8_t> data(rack::system::archiveDirectory(APP->patch->autosavePath, 1));
  465. if (const lo_blob blob = lo_blob_new(data.size(), data.data())) {
  466. lo_send(addr, "/load", "b", blob);
  467. lo_blob_free(blob);
  468. }
  469. lo_address_free(addr);
  470. #endif
  471. }
  472. void sendScreenshotToRemote(const char* const screenshot) {
  473. #ifdef HAVE_LIBLO
  474. const lo_address addr = lo_address_new_with_proto(LO_UDP, REMOTE_HOST, REMOTE_HOST_PORT);
  475. DISTRHO_SAFE_ASSERT_RETURN(addr != nullptr,);
  476. std::vector<uint8_t> data(d_getChunkFromBase64String(screenshot));
  477. if (const lo_blob blob = lo_blob_new(data.size(), data.data())) {
  478. lo_send(addr, "/screenshot", "b", blob);
  479. lo_blob_free(blob);
  480. }
  481. lo_address_free(addr);
  482. #endif
  483. }
  484. } // namespace patchUtils