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.

416 lines
13KB

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