The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
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.

225 lines
7.2KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2022 - Raw Material Software Limited
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 7 End-User License
  8. Agreement and JUCE Privacy Policy.
  9. End User License Agreement: www.juce.com/juce-7-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. /**
  19. This component runs in a client process, draws the part of the canvas that this
  20. particular client covers, and updates itself when messages arrive from the master
  21. containing new canvas states.
  22. */
  23. class ClientCanvasComponent : public Component,
  24. private OSCSender,
  25. private OSCReceiver,
  26. private OSCReceiver::Listener<OSCReceiver::RealtimeCallback>,
  27. private AsyncUpdater,
  28. private Timer
  29. {
  30. public:
  31. ClientCanvasComponent (PropertiesFile& p, int windowIndex) : properties (p)
  32. {
  33. {
  34. String uuidPropName ("UUID" + String (windowIndex));
  35. clientName = properties.getValue (uuidPropName);
  36. if (clientName.isEmpty())
  37. {
  38. clientName = "CLIENT_" + String (Random().nextInt (10000)).toUpperCase();
  39. properties.setValue (uuidPropName, clientName);
  40. }
  41. }
  42. setOpaque (true);
  43. setSize (1500, 900);
  44. if (! OSCSender::connect (getBroadcastIPAddress(), clientPortNumber))
  45. error = "Client app OSC sender: network connection error.";
  46. if (! OSCReceiver::connect (masterPortNumber))
  47. error = "Client app OSC receiver: network connection error.";
  48. OSCReceiver::addListener (this);
  49. timerCallback();
  50. startTimer (2000);
  51. }
  52. ~ClientCanvasComponent() override
  53. {
  54. OSCReceiver::removeListener (this);
  55. }
  56. private:
  57. void mouseDrag (const MouseEvent& e) override
  58. {
  59. auto clientArea = getAreaInGlobalSpace();
  60. if (! clientArea.isEmpty())
  61. {
  62. OSCMessage message (userInputOSCAddress);
  63. message.addString (clientName);
  64. message.addFloat32 (e.position.x * clientArea.getWidth() / (float) getWidth() + clientArea.getX());
  65. message.addFloat32 (e.position.y * clientArea.getHeight() / (float) getHeight() + clientArea.getY());
  66. send (message);
  67. }
  68. }
  69. //==============================================================================
  70. void oscMessageReceived (const OSCMessage& message) override
  71. {
  72. auto address = message.getAddressPattern();
  73. if (address.matches (canvasStateOSCAddress))
  74. canvasStateOSCMessageReceived (message);
  75. }
  76. struct NewStateMessage : public Message
  77. {
  78. NewStateMessage (const MemoryBlock& d) : data (d) {}
  79. MemoryBlock data;
  80. };
  81. void canvasStateOSCMessageReceived (const OSCMessage& message)
  82. {
  83. if (message.isEmpty() || ! message[0].isBlob())
  84. return;
  85. if (packetiser.appendIncomingBlock (message[0].getBlob()))
  86. {
  87. const ScopedLock sl (canvasLock);
  88. MemoryBlock newCanvasData;
  89. if (packetiser.reassemble (newCanvasData))
  90. {
  91. MemoryInputStream i (newCanvasData.getData(), newCanvasData.getSize(), false);
  92. canvas2.load (i);
  93. triggerAsyncUpdate();
  94. }
  95. }
  96. }
  97. //==============================================================================
  98. String getMachineInfoToDisplay() const
  99. {
  100. auto* display = Desktop::getInstance().getDisplays().getDisplayForPoint (getScreenBounds().getCentre());
  101. return getOSName() + " " + String (display->dpi) + " " + String (display->scale);
  102. }
  103. static String getOSName()
  104. {
  105. #if JUCE_MAC
  106. return "Mac OSX";
  107. #elif JUCE_ANDROID
  108. return "Android";
  109. #elif JUCE_IOS
  110. return "iOS";
  111. #elif JUCE_WINDOWS
  112. return "Windows";
  113. #elif JUCE_LINUX
  114. return "Linux";
  115. #elif JUCE_BSD
  116. return "BSD";
  117. #endif
  118. }
  119. void paint (Graphics& g) override
  120. {
  121. g.fillAll (canvas.backgroundColour);
  122. auto clientArea = getAreaInGlobalSpace();
  123. if (clientArea.isEmpty())
  124. {
  125. g.setColour (Colours::red.withAlpha (0.5f));
  126. g.setFont (20.0f);
  127. g.drawText ("Not Connected", getLocalBounds(), Justification::centred, false);
  128. return;
  129. }
  130. canvas.draw (g, getLocalBounds().toFloat(), clientArea);
  131. g.setFont (Font (34.0f));
  132. g.setColour (Colours::white.withAlpha (0.6f));
  133. g.drawText (getMachineInfoToDisplay(),
  134. getLocalBounds().reduced (10).removeFromBottom (20),
  135. Justification::centredRight, true);
  136. if (error.isNotEmpty())
  137. {
  138. g.setColour (Colours::red);
  139. g.drawText (error, getLocalBounds().reduced (10).removeFromBottom (80),
  140. Justification::centredRight, true);
  141. }
  142. }
  143. Rectangle<float> getAreaInGlobalSpace() const
  144. {
  145. if (auto client = canvas.findClient (clientName))
  146. {
  147. auto screenBounds = getScreenBounds();
  148. auto* display = Desktop::getInstance().getDisplays().getDisplayForPoint (screenBounds.getCentre());
  149. return ((screenBounds - display->userArea.getCentre()).toFloat() / (client->scaleFactor * display->dpi / display->scale)) + client->centre;
  150. }
  151. return {};
  152. }
  153. Rectangle<float> getScreenAreaInGlobalSpace() const
  154. {
  155. if (auto client = canvas.findClient (clientName))
  156. {
  157. auto* display = Desktop::getInstance().getDisplays().getDisplayForPoint (getScreenBounds().getCentre());
  158. return (display->userArea.toFloat() / (client->scaleFactor * display->dpi / display->scale)).withCentre (client->centre);
  159. }
  160. return {};
  161. }
  162. void timerCallback() override
  163. {
  164. send (newClientOSCAddress, clientName + ":" + IPAddress::getLocalAddress().toString()
  165. + ":" + getScreenAreaInGlobalSpace().toString());
  166. }
  167. void handleAsyncUpdate() override
  168. {
  169. const ScopedLock sl (canvasLock);
  170. canvas.swapWith (canvas2);
  171. repaint();
  172. }
  173. SharedCanvasDescription canvas, canvas2;
  174. PropertiesFile& properties;
  175. String clientName, error;
  176. CriticalSection canvasLock;
  177. BlockPacketiser packetiser;
  178. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ClientCanvasComponent)
  179. };