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.

423 lines
13KB

  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. Runs the master node, calls the demo to update the canvas, broadcasts those changes
  20. out to slaves, and shows a view of all the clients to allow them to be dragged around.
  21. */
  22. struct MasterContentComponent : public Component,
  23. private Timer,
  24. private OSCSender,
  25. private OSCReceiver,
  26. private OSCReceiver::Listener<OSCReceiver::MessageLoopCallback>
  27. {
  28. MasterContentComponent (PropertiesFile& props)
  29. : properties (props)
  30. {
  31. setWantsKeyboardFocus (true);
  32. createAllDemos (demos);
  33. setContent (0);
  34. setSize ((int) (15.0f * currentCanvas.getLimits().getWidth()),
  35. (int) (15.0f * currentCanvas.getLimits().getHeight()));
  36. if (! OSCSender::connect (getBroadcastIPAddress(), masterPortNumber))
  37. error = "Master app OSC sender: network connection error.";
  38. if (! OSCReceiver::connect (clientPortNumber))
  39. error = "Master app OSC receiver: network connection error.";
  40. OSCReceiver::addListener (this);
  41. startTimerHz (30);
  42. }
  43. ~MasterContentComponent() override
  44. {
  45. OSCReceiver::removeListener (this);
  46. }
  47. //==============================================================================
  48. struct Client
  49. {
  50. String name, ipAddress;
  51. float widthInches, heightInches;
  52. Point<float> centre; // in inches
  53. float scaleFactor;
  54. };
  55. Array<Client> clients;
  56. void addClient (String name, String ipAddress, String areaDescription)
  57. {
  58. auto area = Rectangle<float>::fromString (areaDescription);
  59. if (auto c = getClient (name))
  60. {
  61. c->ipAddress = ipAddress;
  62. c->widthInches = area.getWidth();
  63. c->heightInches = area.getHeight();
  64. return;
  65. }
  66. DBG (name + " " + ipAddress);
  67. removeClient (name);
  68. clients.add ({ name, ipAddress, area.getWidth(), area.getHeight(), {}, 1.0f });
  69. String lastX = properties.getValue ("lastX_" + name);
  70. String lastY = properties.getValue ("lastY_" + name);
  71. String lastScale = properties.getValue ("scale_" + name);
  72. if (lastX.isEmpty() || lastY.isEmpty())
  73. setClientCentre (name, { Random().nextFloat() * 10.0f,
  74. Random().nextFloat() * 10.0f });
  75. else
  76. setClientCentre (name, Point<float> (lastX.getFloatValue(),
  77. lastY.getFloatValue()));
  78. if (lastScale.isNotEmpty())
  79. setClientScale (name, lastScale.getFloatValue());
  80. else
  81. setClientScale (name, 1.0f);
  82. updateDeviceComponents();
  83. }
  84. void removeClient (String name)
  85. {
  86. for (int i = clients.size(); --i >= 0;)
  87. if (clients.getReference (0).name == name)
  88. clients.remove (i);
  89. updateDeviceComponents();
  90. }
  91. void setClientCentre (const String& name, Point<float> newCentre)
  92. {
  93. if (auto c = getClient (name))
  94. {
  95. newCentre = currentCanvas.getLimits().getConstrainedPoint (newCentre);
  96. c->centre = newCentre;
  97. properties.setValue ("lastX_" + name, String (newCentre.x));
  98. properties.setValue ("lastY_" + name, String (newCentre.y));
  99. startTimer (1);
  100. }
  101. }
  102. float getClientScale (const String& name) const
  103. {
  104. if (auto c = getClient (name))
  105. return c->scaleFactor;
  106. return 1.0f;
  107. }
  108. void setClientScale (const String& name, float newScale)
  109. {
  110. if (auto c = getClient (name))
  111. {
  112. c->scaleFactor = jlimit (0.5f, 2.0f, newScale);
  113. properties.setValue ("scale_" + name, String (newScale));
  114. }
  115. }
  116. Point<float> getClientCentre (const String& name) const
  117. {
  118. if (auto c = getClient (name))
  119. return c->centre;
  120. return {};
  121. }
  122. Rectangle<float> getClientArea (const String& name) const
  123. {
  124. if (auto c = getClient (name))
  125. return Rectangle<float> (c->widthInches, c->heightInches)
  126. .withCentre (c->centre);
  127. return {};
  128. }
  129. Rectangle<float> getActiveCanvasArea() const
  130. {
  131. Rectangle<float> r;
  132. if (clients.size() > 0)
  133. r = Rectangle<float> (1.0f, 1.0f).withCentre (clients.getReference (0).centre);
  134. for (int i = 1; i < clients.size(); ++i)
  135. r = r.getUnion (Rectangle<float> (1.0f, 1.0f).withCentre (clients.getReference (i).centre));
  136. return r.expanded (6.0f);
  137. }
  138. int getContentIndex() const
  139. {
  140. return demos.indexOf (content);
  141. }
  142. void setContent (int demoIndex)
  143. {
  144. content = demos[demoIndex];
  145. if (content != nullptr)
  146. content->reset();
  147. }
  148. bool keyPressed (const KeyPress& key) override
  149. {
  150. if (key == KeyPress::spaceKey || key == KeyPress::rightKey || key == KeyPress::downKey)
  151. {
  152. setContent ((getContentIndex() + 1) % demos.size());
  153. return true;
  154. }
  155. if (key == KeyPress::upKey || key == KeyPress::leftKey)
  156. {
  157. setContent ((getContentIndex() + demos.size() - 1) % demos.size());
  158. return true;
  159. }
  160. return Component::keyPressed (key);
  161. }
  162. private:
  163. //==============================================================================
  164. void paint (Graphics& g) override
  165. {
  166. g.fillAll (Colours::black);
  167. currentCanvas.draw (g, getLocalBounds().toFloat(), currentCanvas.getLimits());
  168. if (error.isNotEmpty())
  169. {
  170. g.setColour (Colours::red);
  171. g.setFont (20.0f);
  172. g.drawText (error, getLocalBounds().reduced (10).removeFromBottom (80),
  173. Justification::centredRight, true);
  174. }
  175. if (content != nullptr)
  176. {
  177. g.setColour (Colours::white);
  178. g.setFont (17.0f);
  179. g.drawText ("Demo: " + content->getName(),
  180. getLocalBounds().reduced (10).removeFromTop (30),
  181. Justification::centredLeft, true);
  182. }
  183. }
  184. void resized() override
  185. {
  186. updateDeviceComponents();
  187. }
  188. void updateDeviceComponents()
  189. {
  190. for (int i = devices.size(); --i >= 0;)
  191. if (getClient (devices.getUnchecked(i)->getName()) == nullptr)
  192. devices.remove (i);
  193. for (const auto& c : clients)
  194. if (getDeviceComponent (c.name) == nullptr)
  195. addAndMakeVisible (devices.add (new DeviceComponent (*this, c.name)));
  196. for (auto d : devices)
  197. d->setBounds (virtualSpaceToLocal (getClientArea (d->getName())).getSmallestIntegerContainer());
  198. }
  199. Point<float> virtualSpaceToLocal (Point<float> p) const
  200. {
  201. auto total = currentCanvas.getLimits();
  202. return { (float) getWidth() * (p.x - total.getX()) / total.getWidth(),
  203. (float) getHeight() * (p.y - total.getY()) / total.getHeight() };
  204. }
  205. Rectangle<float> virtualSpaceToLocal (Rectangle<float> p) const
  206. {
  207. return { virtualSpaceToLocal (p.getTopLeft()),
  208. virtualSpaceToLocal (p.getBottomRight()) };
  209. }
  210. Point<float> localSpaceToVirtual (Point<float> p) const
  211. {
  212. auto total = currentCanvas.getLimits();
  213. return { total.getX() + total.getWidth() * (p.x / (float) getWidth()),
  214. total.getY() + total.getHeight() * (p.y / (float) getHeight()) };
  215. }
  216. //==============================================================================
  217. struct DeviceComponent : public Component
  218. {
  219. DeviceComponent (MasterContentComponent& e, String name)
  220. : Component (name), editor (e)
  221. {
  222. setMouseCursor (MouseCursor::DraggingHandCursor);
  223. }
  224. void paint (Graphics& g) override
  225. {
  226. g.fillAll (Colours::blue.withAlpha (0.4f));
  227. g.setColour (Colours::white);
  228. g.setFont (11.0f);
  229. g.drawFittedText (getName(), getLocalBounds(), Justification::centred, 2);
  230. }
  231. void mouseDown (const MouseEvent&) override
  232. {
  233. dragStartLocation = editor.getClientCentre (getName());
  234. }
  235. void mouseDrag (const MouseEvent& e) override
  236. {
  237. editor.setClientCentre (getName(), dragStartLocation
  238. + editor.localSpaceToVirtual (e.getPosition().toFloat())
  239. - editor.localSpaceToVirtual (e.getMouseDownPosition().toFloat()));
  240. }
  241. void mouseWheelMove (const MouseEvent&, const MouseWheelDetails& e) override
  242. {
  243. editor.setClientScale (getName(), editor.getClientScale (getName()) + 0.1f * e.deltaY);
  244. }
  245. void mouseDoubleClick (const MouseEvent&) override
  246. {
  247. editor.setClientScale (getName(), 1.0f);
  248. }
  249. MasterContentComponent& editor;
  250. Point<float> dragStartLocation;
  251. Rectangle<float> clientArea;
  252. };
  253. DeviceComponent* getDeviceComponent (const String& name) const
  254. {
  255. for (auto d : devices)
  256. if (d->getName() == name)
  257. return d;
  258. return nullptr;
  259. }
  260. //==============================================================================
  261. void broadcastNewCanvasState (const MemoryBlock& canvasData)
  262. {
  263. BlockPacketiser packetiser;
  264. packetiser.createBlocksFromData (canvasData, 1000);
  265. for (const auto& client : clients)
  266. for (auto& b : packetiser.blocks)
  267. sendToIPAddress (client.ipAddress, masterPortNumber, canvasStateOSCAddress, b);
  268. }
  269. void timerCallback() override
  270. {
  271. startTimerHz (30);
  272. currentCanvas.reset();
  273. updateCanvasInfo (currentCanvas);
  274. {
  275. std::unique_ptr<CanvasGeneratingContext> context (new CanvasGeneratingContext (currentCanvas));
  276. Graphics g (*context);
  277. if (content != nullptr)
  278. content->generateCanvas (g, currentCanvas, getActiveCanvasArea());
  279. }
  280. broadcastNewCanvasState (currentCanvas.toMemoryBlock());
  281. updateDeviceComponents();
  282. repaint();
  283. }
  284. void updateCanvasInfo (SharedCanvasDescription& canvas)
  285. {
  286. canvas.backgroundColour = Colours::black;
  287. for (const auto& c : clients)
  288. canvas.clients.add ({ c.name, c.centre, c.scaleFactor });
  289. }
  290. const Client* getClient (const String& name) const
  291. {
  292. for (auto& c : clients)
  293. if (c.name == name)
  294. return &c;
  295. return nullptr;
  296. }
  297. Client* getClient (const String& name)
  298. {
  299. return const_cast<Client*> (static_cast<const MasterContentComponent&> (*this).getClient (name));
  300. }
  301. //==============================================================================
  302. void oscMessageReceived (const OSCMessage& message) override
  303. {
  304. auto address = message.getAddressPattern();
  305. if (address.matches (newClientOSCAddress)) newClientOSCMessageReceived (message);
  306. else if (address.matches (userInputOSCAddress)) userInputOSCMessageReceived (message);
  307. }
  308. void newClientOSCMessageReceived (const OSCMessage& message)
  309. {
  310. if (message.isEmpty() || ! message[0].isString())
  311. return;
  312. StringArray tokens = StringArray::fromTokens (message[0].getString(), ":", "");
  313. addClient (tokens[0], tokens[1], tokens[2]);
  314. }
  315. void userInputOSCMessageReceived (const OSCMessage& message)
  316. {
  317. if (message.size() == 3 && message[0].isString() && message[1].isFloat32() && message[2].isFloat32())
  318. {
  319. content->handleTouch ({ message[1].getFloat32(),
  320. message[2].getFloat32() });
  321. }
  322. }
  323. //==============================================================================
  324. AnimatedContent* content = nullptr;
  325. PropertiesFile& properties;
  326. OwnedArray<DeviceComponent> devices;
  327. SharedCanvasDescription currentCanvas;
  328. String error;
  329. OwnedArray<AnimatedContent> demos;
  330. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MasterContentComponent)
  331. };