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.

552 lines
18KB

  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 <app/Scene.hpp>
  18. #include <context.hpp>
  19. #include <helpers.hpp>
  20. #include <patch.hpp>
  21. #include <settings.hpp>
  22. #include <ui/Button.hpp>
  23. #include <ui/MenuItem.hpp>
  24. #include <ui/MenuSeparator.hpp>
  25. #include <window/Window.hpp>
  26. #ifdef NDEBUG
  27. # undef DEBUG
  28. #endif
  29. #include <Application.hpp>
  30. #include "DistrhoUI.hpp"
  31. #include "PluginContext.hpp"
  32. #include "WindowParameters.hpp"
  33. #include "ResizeHandle.hpp"
  34. GLFWAPI const char* glfwGetClipboardString(GLFWwindow* window) { return nullptr; }
  35. GLFWAPI void glfwSetClipboardString(GLFWwindow* window, const char*) {}
  36. GLFWAPI const char* glfwGetKeyName(int key, int scancode) { return nullptr; }
  37. GLFWAPI int glfwGetKeyScancode(int key) { return 0; }
  38. namespace rack {
  39. namespace app {
  40. widget::Widget* createMenuBar(Window& window, bool isStandalone);
  41. }
  42. namespace window {
  43. void WindowSetPluginUI(Window* window, DISTRHO_NAMESPACE::UI* ui);
  44. void WindowSetMods(Window* window, int mods);
  45. }
  46. }
  47. START_NAMESPACE_DISTRHO
  48. // -----------------------------------------------------------------------------------------------------------
  49. CardinalPluginContext* getRackContextFromPlugin(void* ptr);
  50. // -----------------------------------------------------------------------------------------------------------
  51. class CardinalUI : public UI,
  52. public WindowParametersCallback
  53. {
  54. CardinalPluginContext* const fContext;
  55. rack::math::Vec fLastMousePos;
  56. ResizeHandle fResizeHandle;
  57. WindowParameters fWindowParameters;
  58. struct ScopedContext {
  59. CardinalPluginContext* const context;
  60. ScopedContext(CardinalUI* const ui)
  61. : context(ui->fContext)
  62. {
  63. rack::contextSet(context);
  64. WindowParametersRestore(context->window);
  65. }
  66. ScopedContext(CardinalUI* const ui, const int mods)
  67. : context(ui->fContext)
  68. {
  69. rack::contextSet(context);
  70. rack::window::WindowSetMods(context->window, mods);
  71. WindowParametersRestore(context->window);
  72. }
  73. ~ScopedContext()
  74. {
  75. if (context->window != nullptr)
  76. WindowParametersSave(context->window);
  77. rack::contextSet(nullptr);
  78. }
  79. };
  80. public:
  81. CardinalUI()
  82. : UI(1228, 666),
  83. fContext(getRackContextFromPlugin(getPluginInstancePointer())),
  84. fResizeHandle(this)
  85. {
  86. if (isResizable())
  87. fResizeHandle.hide();
  88. const double scaleFactor = getScaleFactor();
  89. if (scaleFactor != 1)
  90. setSize(1228 * scaleFactor, 666 * scaleFactor);
  91. rack::contextSet(fContext);
  92. rack::window::WindowSetPluginUI(fContext->window, this);
  93. if (fContext->scene->menuBar != nullptr)
  94. fContext->scene->removeChild(fContext->scene->menuBar);
  95. fContext->scene->menuBar = rack::app::createMenuBar(getWindow(), getApp().isStandalone());
  96. fContext->scene->addChildBelow(fContext->scene->menuBar, fContext->scene->rackScroll);
  97. fContext->window->step();
  98. rack::contextSet(nullptr);
  99. WindowParametersSetCallback(fContext->window, this);
  100. }
  101. ~CardinalUI() override
  102. {
  103. rack::contextSet(fContext);
  104. rack::widget::Widget* const menuBar = fContext->scene->menuBar;
  105. fContext->scene->menuBar = nullptr;
  106. fContext->scene->removeChild(menuBar);
  107. rack::window::WindowSetPluginUI(fContext->window, nullptr);
  108. rack::contextSet(nullptr);
  109. }
  110. void onNanoDisplay() override
  111. {
  112. const ScopedContext sc(this);
  113. fContext->window->step();
  114. }
  115. void uiIdle() override
  116. {
  117. repaint();
  118. }
  119. void WindowParametersChanged(const WindowParameterList param, float value) override
  120. {
  121. float mult = 1.0f;
  122. switch (param)
  123. {
  124. case kWindowParameterShowTooltips:
  125. fWindowParameters.tooltips = value > 0.5f;
  126. break;
  127. case kWindowParameterCableOpacity:
  128. mult = 100.0f;
  129. fWindowParameters.cableOpacity = value;
  130. break;
  131. case kWindowParameterCableTension:
  132. mult = 100.0f;
  133. fWindowParameters.cableTension = value;
  134. break;
  135. case kWindowParameterRackBrightness:
  136. mult = 100.0f;
  137. fWindowParameters.rackBrightness = value;
  138. break;
  139. case kWindowParameterHaloBrightness:
  140. mult = 100.0f;
  141. fWindowParameters.haloBrightness = value;
  142. break;
  143. case kWindowParameterKnobMode:
  144. switch (static_cast<int>(value + 0.5f))
  145. {
  146. case rack::settings::KNOB_MODE_LINEAR:
  147. value = 0;
  148. fWindowParameters.knobMode = rack::settings::KNOB_MODE_LINEAR;
  149. break;
  150. case rack::settings::KNOB_MODE_ROTARY_ABSOLUTE:
  151. value = 1;
  152. fWindowParameters.knobMode = rack::settings::KNOB_MODE_ROTARY_ABSOLUTE;
  153. break;
  154. case rack::settings::KNOB_MODE_ROTARY_RELATIVE:
  155. value = 2;
  156. fWindowParameters.knobMode = rack::settings::KNOB_MODE_ROTARY_RELATIVE;
  157. break;
  158. }
  159. break;
  160. case kWindowParameterWheelKnobControl:
  161. fWindowParameters.knobScroll = value > 0.5f;
  162. break;
  163. case kWindowParameterWheelSensitivity:
  164. mult = 1000.0f;
  165. fWindowParameters.knobScrollSensitivity = value;
  166. break;
  167. case kWindowParameterLockModulePositions:
  168. fWindowParameters.lockModules = value > 0.5f;
  169. break;
  170. default:
  171. return;
  172. }
  173. setParameterValue(kModuleParameters + param, value * mult);
  174. }
  175. protected:
  176. /* --------------------------------------------------------------------------------------------------------
  177. * DSP/Plugin Callbacks */
  178. /**
  179. A parameter has changed on the plugin side.
  180. This is called by the host to inform the UI about parameter changes.
  181. */
  182. void parameterChanged(const uint32_t index, const float value) override
  183. {
  184. if (index < kModuleParameters)
  185. return;
  186. switch (index - kModuleParameters)
  187. {
  188. case kWindowParameterShowTooltips:
  189. fWindowParameters.tooltips = value > 0.5f;
  190. break;
  191. case kWindowParameterCableOpacity:
  192. fWindowParameters.cableOpacity = value / 100.0f;
  193. break;
  194. case kWindowParameterCableTension:
  195. fWindowParameters.cableTension = value / 100.0f;
  196. break;
  197. case kWindowParameterRackBrightness:
  198. fWindowParameters.rackBrightness = value / 100.0f;
  199. break;
  200. case kWindowParameterHaloBrightness:
  201. fWindowParameters.haloBrightness = value / 100.0f;
  202. break;
  203. case kWindowParameterKnobMode:
  204. switch (static_cast<int>(value + 0.5f))
  205. {
  206. case 0:
  207. fWindowParameters.knobMode = rack::settings::KNOB_MODE_LINEAR;
  208. break;
  209. case 1:
  210. fWindowParameters.knobMode = rack::settings::KNOB_MODE_ROTARY_ABSOLUTE;
  211. break;
  212. case 2:
  213. fWindowParameters.knobMode = rack::settings::KNOB_MODE_ROTARY_RELATIVE;
  214. break;
  215. }
  216. break;
  217. case kWindowParameterWheelKnobControl:
  218. fWindowParameters.knobScroll = value > 0.5f;
  219. break;
  220. case kWindowParameterWheelSensitivity:
  221. fWindowParameters.knobScrollSensitivity = value / 1000.0f;
  222. break;
  223. case kWindowParameterLockModulePositions:
  224. fWindowParameters.lockModules = value > 0.5f;
  225. break;
  226. default:
  227. return;
  228. }
  229. WindowParametersSetValues(fContext->window, fWindowParameters);
  230. }
  231. void stateChanged(const char* key, const char* value) override
  232. {
  233. if (std::strcmp(key, "windowSize") != 0)
  234. return;
  235. int width = 0;
  236. int height = 0;
  237. std::sscanf(value, "%i:%i", &width, &height);
  238. if (width > 0 && height > 0)
  239. {
  240. const double scaleFactor = getScaleFactor();
  241. setSize(width * scaleFactor, height * scaleFactor);
  242. }
  243. }
  244. // -------------------------------------------------------------------------------------------------------
  245. static int glfwMods(const uint mod) noexcept
  246. {
  247. int mods = 0;
  248. if (mod & kModifierControl)
  249. mods |= GLFW_MOD_CONTROL;
  250. if (mod & kModifierShift)
  251. mods |= GLFW_MOD_SHIFT;
  252. if (mod & kModifierAlt)
  253. mods |= GLFW_MOD_ALT;
  254. if (mod & kModifierSuper)
  255. mods |= GLFW_MOD_SUPER;
  256. /*
  257. if (glfwGetKey(win, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS || glfwGetKey(win, GLFW_KEY_RIGHT_SHIFT) == GLFW_PRESS)
  258. mods |= GLFW_MOD_SHIFT;
  259. if (glfwGetKey(win, GLFW_KEY_LEFT_CONTROL) == GLFW_PRESS || glfwGetKey(win, GLFW_KEY_RIGHT_CONTROL) == GLFW_PRESS)
  260. mods |= GLFW_MOD_CONTROL;
  261. if (glfwGetKey(win, GLFW_KEY_LEFT_ALT) == GLFW_PRESS || glfwGetKey(win, GLFW_KEY_RIGHT_ALT) == GLFW_PRESS)
  262. mods |= GLFW_MOD_ALT;
  263. if (glfwGetKey(win, GLFW_KEY_LEFT_SUPER) == GLFW_PRESS || glfwGetKey(win, GLFW_KEY_RIGHT_SUPER) == GLFW_PRESS)
  264. mods |= GLFW_MOD_SUPER;
  265. */
  266. return mods;
  267. }
  268. bool onMouse(const MouseEvent& ev) override
  269. {
  270. const int action = ev.press ? GLFW_PRESS : GLFW_RELEASE;
  271. const int mods = glfwMods(ev.mod);
  272. int button;
  273. #ifdef DISTRHO_OS_MAC
  274. switch (ev.button)
  275. {
  276. case 1:
  277. button = GLFW_MOUSE_BUTTON_LEFT;
  278. break;
  279. case 2:
  280. button = GLFW_MOUSE_BUTTON_RIGHT;
  281. break;
  282. case 3:
  283. button = GLFW_MOUSE_BUTTON_MIDDLE;
  284. break;
  285. default:
  286. button = 0;
  287. break;
  288. }
  289. #else
  290. switch (ev.button)
  291. {
  292. case 1:
  293. button = GLFW_MOUSE_BUTTON_LEFT;
  294. break;
  295. case 2:
  296. button = GLFW_MOUSE_BUTTON_MIDDLE;
  297. break;
  298. case 3:
  299. button = GLFW_MOUSE_BUTTON_RIGHT;
  300. break;
  301. // case 4:
  302. // button = GLFW_MOUSE_WHEELUP;
  303. // break;
  304. // case 5:
  305. // button = GLFW_MOUSE_WHEELDOWN;
  306. // break;
  307. default:
  308. button = 0;
  309. break;
  310. }
  311. #endif
  312. /*
  313. #if defined ARCH_MAC
  314. // Remap Ctrl-left click to right click on Mac
  315. if (button == GLFW_MOUSE_BUTTON_LEFT && (mods & RACK_MOD_MASK) == GLFW_MOD_CONTROL) {
  316. button = GLFW_MOUSE_BUTTON_RIGHT;
  317. mods &= ~GLFW_MOD_CONTROL;
  318. }
  319. // Remap Ctrl-shift-left click to middle click on Mac
  320. if (button == GLFW_MOUSE_BUTTON_LEFT && (mods & RACK_MOD_MASK) == (GLFW_MOD_CONTROL | GLFW_MOD_SHIFT)) {
  321. button = GLFW_MOUSE_BUTTON_MIDDLE;
  322. mods &= ~(GLFW_MOD_CONTROL | GLFW_MOD_SHIFT);
  323. }
  324. #endif
  325. */
  326. const ScopedContext sc(this, mods);
  327. return fContext->event->handleButton(fLastMousePos, button, action, mods);
  328. }
  329. bool onMotion(const MotionEvent& ev) override
  330. {
  331. const rack::math::Vec mousePos = rack::math::Vec(ev.pos.getX(), ev.pos.getY()).div(getScaleFactor()).round();
  332. const rack::math::Vec mouseDelta = mousePos.minus(fLastMousePos);
  333. fLastMousePos = mousePos;
  334. const ScopedContext sc(this, glfwMods(ev.mod));
  335. return fContext->event->handleHover(mousePos, mouseDelta);
  336. }
  337. bool onScroll(const ScrollEvent& ev) override
  338. {
  339. rack::math::Vec scrollDelta = rack::math::Vec(ev.delta.getX(), ev.delta.getY());
  340. #ifdef DISTRHO_OS_MAC
  341. scrollDelta = scrollDelta.mult(10.0);
  342. #else
  343. scrollDelta = scrollDelta.mult(50.0);
  344. #endif
  345. const ScopedContext sc(this, glfwMods(ev.mod));
  346. return fContext->event->handleScroll(fLastMousePos, scrollDelta);
  347. }
  348. bool onCharacterInput(const CharacterInputEvent& ev) override
  349. {
  350. if (ev.character <= ' ' || ev.character >= kKeyDelete)
  351. return false;
  352. const ScopedContext sc(this, glfwMods(ev.mod));
  353. return fContext->event->handleText(fLastMousePos, ev.character);
  354. }
  355. bool onKeyboard(const KeyboardEvent& ev) override
  356. {
  357. const int action = ev.press ? GLFW_PRESS : GLFW_RELEASE;
  358. const int mods = glfwMods(ev.mod);
  359. /* These are unsupported in pugl right now
  360. #define GLFW_KEY_KP_0 320
  361. #define GLFW_KEY_KP_1 321
  362. #define GLFW_KEY_KP_2 322
  363. #define GLFW_KEY_KP_3 323
  364. #define GLFW_KEY_KP_4 324
  365. #define GLFW_KEY_KP_5 325
  366. #define GLFW_KEY_KP_6 326
  367. #define GLFW_KEY_KP_7 327
  368. #define GLFW_KEY_KP_8 328
  369. #define GLFW_KEY_KP_9 329
  370. #define GLFW_KEY_KP_DECIMAL 330
  371. #define GLFW_KEY_KP_DIVIDE 331
  372. #define GLFW_KEY_KP_MULTIPLY 332
  373. #define GLFW_KEY_KP_SUBTRACT 333
  374. #define GLFW_KEY_KP_ADD 334
  375. #define GLFW_KEY_KP_ENTER 335
  376. #define GLFW_KEY_KP_EQUAL 336
  377. */
  378. int key;
  379. switch (ev.key)
  380. {
  381. case '\r': key = GLFW_KEY_ENTER; break;
  382. case '\t': key = GLFW_KEY_TAB; break;
  383. case kKeyBackspace: key = GLFW_KEY_BACKSPACE; break;
  384. case kKeyEscape: key = GLFW_KEY_ESCAPE; break;
  385. case kKeyDelete: key = GLFW_KEY_DELETE; break;
  386. case kKeyF1: key = GLFW_KEY_F1; break;
  387. case kKeyF2: key = GLFW_KEY_F2; break;
  388. case kKeyF3: key = GLFW_KEY_F3; break;
  389. case kKeyF4: key = GLFW_KEY_F4; break;
  390. case kKeyF5: key = GLFW_KEY_F5; break;
  391. case kKeyF6: key = GLFW_KEY_F6; break;
  392. case kKeyF7: key = GLFW_KEY_F7; break;
  393. case kKeyF8: key = GLFW_KEY_F8; break;
  394. case kKeyF9: key = GLFW_KEY_F9; break;
  395. case kKeyF10: key = GLFW_KEY_F10; break;
  396. case kKeyF11: key = GLFW_KEY_F11; break;
  397. case kKeyF12: key = GLFW_KEY_F12; break;
  398. case kKeyLeft: key = GLFW_KEY_LEFT; break;
  399. case kKeyUp: key = GLFW_KEY_UP; break;
  400. case kKeyRight: key = GLFW_KEY_RIGHT; break;
  401. case kKeyDown: key = GLFW_KEY_DOWN; break;
  402. case kKeyPageUp: key = GLFW_KEY_PAGE_UP; break;
  403. case kKeyPageDown: key = GLFW_KEY_PAGE_DOWN; break;
  404. case kKeyHome: key = GLFW_KEY_HOME; break;
  405. case kKeyEnd: key = GLFW_KEY_END; break;
  406. case kKeyInsert: key = GLFW_KEY_INSERT; break;
  407. case kKeyShiftL: key = GLFW_KEY_LEFT_SHIFT; break;
  408. case kKeyShiftR: key = GLFW_KEY_RIGHT_SHIFT; break;
  409. case kKeyControlL: key = GLFW_KEY_LEFT_CONTROL; break;
  410. case kKeyControlR: key = GLFW_KEY_RIGHT_CONTROL; break;
  411. case kKeyAltL: key = GLFW_KEY_LEFT_ALT; break;
  412. case kKeyAltR: key = GLFW_KEY_RIGHT_ALT; break;
  413. case kKeySuperL: key = GLFW_KEY_LEFT_SUPER; break;
  414. case kKeySuperR: key = GLFW_KEY_RIGHT_SUPER; break;
  415. case kKeyMenu: key = GLFW_KEY_MENU; break;
  416. case kKeyCapsLock: key = GLFW_KEY_CAPS_LOCK; break;
  417. case kKeyScrollLock: key = GLFW_KEY_SCROLL_LOCK; break;
  418. case kKeyNumLock: key = GLFW_KEY_NUM_LOCK; break;
  419. case kKeyPrintScreen: key = GLFW_KEY_PRINT_SCREEN; break;
  420. case kKeyPause: key = GLFW_KEY_PAUSE; break;
  421. default: key = ev.key; break;
  422. }
  423. const ScopedContext sc(this, mods);
  424. return fContext->event->handleKey(fLastMousePos, key, ev.keycode, action, mods);
  425. }
  426. void onResize(const ResizeEvent& ev) override
  427. {
  428. UI::onResize(ev);
  429. if (fContext->window != nullptr)
  430. fContext->window->setSize(rack::math::Vec(ev.size.getWidth(), ev.size.getHeight()));
  431. const double scaleFactor = getScaleFactor();
  432. char sizeString[64];
  433. std::snprintf(sizeString, sizeof(sizeString), "%d:%d",
  434. (int)(ev.size.getWidth() / scaleFactor), (int)(ev.size.getHeight() / scaleFactor));
  435. setState("windowSize", sizeString);
  436. }
  437. void uiFocus(const bool focus, CrossingMode) override
  438. {
  439. if (focus)
  440. return;
  441. const ScopedContext sc(this, 0);
  442. fContext->event->handleLeave();
  443. }
  444. void uiFileBrowserSelected(const char* const filename) override
  445. {
  446. if (filename == nullptr)
  447. return;
  448. rack::contextSet(fContext);
  449. WindowParametersRestore(fContext->window);
  450. fContext->patch->loadAction(filename);
  451. }
  452. #if 0
  453. void uiReshape(const uint width, const uint height) override
  454. {
  455. glEnable(GL_BLEND);
  456. glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  457. glMatrixMode(GL_PROJECTION);
  458. glLoadIdentity();
  459. glOrtho(0.0, width, 0.0, height, -1.0, 1.0);
  460. glViewport(0, 0, width, height);
  461. glMatrixMode(GL_MODELVIEW);
  462. glLoadIdentity();
  463. }
  464. #endif
  465. private:
  466. /**
  467. Set our UI class as non-copyable and add a leak detector just in case.
  468. */
  469. DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CardinalUI)
  470. };
  471. /* ------------------------------------------------------------------------------------------------------------
  472. * UI entry point, called by DPF to create a new UI instance. */
  473. UI* createUI()
  474. {
  475. return new CardinalUI();
  476. }
  477. // -----------------------------------------------------------------------------------------------------------
  478. END_NAMESPACE_DISTRHO