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.

419 lines
13KB

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