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.

417 lines
13KB

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