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.

985 lines
37KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE examples.
  4. Copyright (c) 2017 - ROLI Ltd.
  5. The code included in this file is provided under the terms of the ISC license
  6. http://www.isc.org/downloads/software-support-policy/isc-license. Permission
  7. To use, copy, modify, and/or distribute this software for any purpose with or
  8. without fee is hereby granted provided that the above copyright notice and
  9. this permission notice appear in all copies.
  10. THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
  11. WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
  12. PURPOSE, ARE DISCLAIMED.
  13. ==============================================================================
  14. */
  15. /*******************************************************************************
  16. The block below describes the properties of this PIP. A PIP is a short snippet
  17. of code that can be read by the Projucer and used to generate a JUCE project.
  18. BEGIN_JUCE_PIP_METADATA
  19. name: BlocksMonitorDemo
  20. version: 1.0.0
  21. vendor: JUCE
  22. website: http://juce.com
  23. description: Application to monitor Blocks devices.
  24. dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats,
  25. juce_audio_processors, juce_audio_utils, juce_blocks_basics,
  26. juce_core, juce_data_structures, juce_events, juce_graphics,
  27. juce_gui_basics, juce_gui_extra
  28. exporters: xcode_mac, vs2017, linux_make, xcode_iphone
  29. type: Component
  30. mainClass: BlocksMonitorDemo
  31. useLocalCopy: 1
  32. END_JUCE_PIP_METADATA
  33. *******************************************************************************/
  34. #pragma once
  35. //==============================================================================
  36. /**
  37. Base class that renders a Block on the screen
  38. */
  39. class BlockComponent : public Component,
  40. public SettableTooltipClient,
  41. private TouchSurface::Listener,
  42. private ControlButton::Listener,
  43. private Timer
  44. {
  45. public:
  46. BlockComponent (Block::Ptr blockToUse)
  47. : block (blockToUse)
  48. {
  49. updateStatsAndTooltip();
  50. // Register BlockComponent as a listener to the touch surface
  51. if (auto touchSurface = block->getTouchSurface())
  52. touchSurface->addListener (this);
  53. // Register BlockComponent as a listener to any buttons
  54. for (auto button : block->getButtons())
  55. button->addListener (this);
  56. // If this is a Lightpad then set the grid program to be blank
  57. if (block->getLEDGrid() != nullptr)
  58. block->setProgram (new BitmapLEDProgram (*block));
  59. // If this is a Lightpad then redraw it at 25Hz
  60. if (block->getType() == Block::lightPadBlock)
  61. startTimerHz (25);
  62. // Make sure the component can't go offscreen if it is draggable
  63. constrainer.setMinimumOnscreenAmounts (50, 50, 50, 50);
  64. }
  65. ~BlockComponent()
  66. {
  67. // Remove any listeners
  68. if (auto touchSurface = block->getTouchSurface())
  69. touchSurface->removeListener (this);
  70. for (auto button : block->getButtons())
  71. button->removeListener (this);
  72. }
  73. /** Called periodically to update the tooltip with inforamtion about the Block */
  74. void updateStatsAndTooltip()
  75. {
  76. // Get the battery level of this Block and inform any subclasses
  77. auto batteryLevel = block->getBatteryLevel();
  78. handleBatteryLevelUpdate (batteryLevel);
  79. // Update the tooltip
  80. setTooltip ("Name = " + block->getDeviceDescription() + "\n"
  81. + "UID = " + String (block->uid) + "\n"
  82. + "Serial number = " + block->serialNumber + "\n"
  83. + "Battery level = " + String ((int) (batteryLevel * 100)) + "%"
  84. + (block->isBatteryCharging() ? "++"
  85. : "--"));
  86. }
  87. /** Subclasses should override this to paint the Block object on the screen */
  88. virtual void paint (Graphics&) override = 0;
  89. /** Subclasses can override this to receive button down events from the Block */
  90. virtual void handleButtonPressed (ControlButton::ButtonFunction, uint32) {}
  91. /** Subclasses can override this to receive button up events from the Block */
  92. virtual void handleButtonReleased (ControlButton::ButtonFunction, uint32) {}
  93. /** Subclasses can override this to receive touch events from the Block */
  94. virtual void handleTouchChange (TouchSurface::Touch) {}
  95. /** Subclasses can override this to battery level updates from the Block */
  96. virtual void handleBatteryLevelUpdate (float) {}
  97. /** The Block object that this class represents */
  98. Block::Ptr block;
  99. //==============================================================================
  100. /** Returns an integer index corresponding to a physical position on the hardware
  101. for each type of Control Block. */
  102. static int controlButtonFunctionToIndex (ControlButton::ButtonFunction f)
  103. {
  104. using CB = ControlButton;
  105. static Array<ControlButton::ButtonFunction> map[] =
  106. {
  107. { CB::mode, CB::button0, CB::velocitySensitivity },
  108. { CB::volume, CB::button1, CB::glideSensitivity },
  109. { CB::scale, CB::button2, CB::slideSensitivity, CB::click },
  110. { CB::chord, CB::button3, CB::pressSensitivity, CB::snap },
  111. { CB::arp, CB::button4, CB::liftSensitivity, CB::back },
  112. { CB::sustain, CB::button5, CB::fixedVelocity, CB::playOrPause },
  113. { CB::octave, CB::button6, CB::glideLock, CB::record },
  114. { CB::love, CB::button7, CB::pianoMode, CB::learn },
  115. { CB::up },
  116. { CB::down }
  117. };
  118. for (auto i = 0; i < numElementsInArray (map); ++i)
  119. if (map[i].contains (f))
  120. return i;
  121. return -1;
  122. }
  123. Point<float> getOffsetForPort (Block::ConnectionPort port)
  124. {
  125. using e = Block::ConnectionPort::DeviceEdge;
  126. switch (rotation)
  127. {
  128. case 0:
  129. {
  130. switch (port.edge)
  131. {
  132. case e::north:
  133. return { static_cast<float> (port.index), 0.0f };
  134. case e::east:
  135. return { static_cast<float> (block->getWidth()), static_cast<float> (port.index) };
  136. case e::south:
  137. return { static_cast<float> (port.index), static_cast<float> (block->getHeight()) };
  138. case e::west:
  139. return { 0.0f, static_cast<float> (port.index) };
  140. }
  141. }
  142. case 90:
  143. {
  144. switch (port.edge)
  145. {
  146. case e::north:
  147. return { 0.0f, static_cast<float> (port.index) };
  148. case e::east:
  149. return { static_cast<float> (-1.0f - port.index), static_cast<float> (block->getWidth()) };
  150. case e::south:
  151. return { static_cast<float> (0.0f - block->getHeight()), static_cast<float> (port.index) };
  152. case e::west:
  153. return { static_cast<float> (-1.0f - port.index), 0.0f };
  154. }
  155. }
  156. case 180:
  157. {
  158. switch (port.edge)
  159. {
  160. case e::north:
  161. return { static_cast<float> (-1.0f - port.index), 0.0f };
  162. case e::east:
  163. return { static_cast<float> (0.0f - block->getWidth()), static_cast<float> (-1.0f - port.index) };
  164. case e::south:
  165. return { static_cast<float> (-1.0f - port.index), static_cast<float> (0.0f - block->getHeight()) };
  166. case e::west:
  167. return { 0.0f, static_cast<float> (-1.0f - port.index) };
  168. }
  169. }
  170. case 270:
  171. {
  172. switch (port.edge)
  173. {
  174. case e::north:
  175. return { 0.0f, static_cast<float> (-1.0f - port.index) };
  176. case e::east:
  177. return { static_cast<float> (port.index), static_cast<float> (0 - block->getWidth()) };
  178. case e::south:
  179. return { static_cast<float> (block->getHeight()), static_cast<float> (-1.0f - port.index) };
  180. case e::west:
  181. return { static_cast<float> (port.index), 0.0f };
  182. }
  183. }
  184. }
  185. return {};
  186. }
  187. int rotation = 0;
  188. Point<float> topLeft = { 0.0f, 0.0f };
  189. private:
  190. /** Used to call repaint() periodically */
  191. void timerCallback() override { repaint(); }
  192. /** Overridden from TouchSurface::Listener */
  193. void touchChanged (TouchSurface&, const TouchSurface::Touch& t) override { handleTouchChange (t); }
  194. /** Overridden from ControlButton::Listener */
  195. void buttonPressed (ControlButton& b, Block::Timestamp t) override { handleButtonPressed (b.getType(), t); }
  196. /** Overridden from ControlButton::Listener */
  197. void buttonReleased (ControlButton& b, Block::Timestamp t) override { handleButtonReleased (b.getType(), t); }
  198. /** Overridden from MouseListener. Prepares the master Block component for dragging. */
  199. void mouseDown (const MouseEvent& e) override
  200. {
  201. if (block->isMasterBlock())
  202. componentDragger.startDraggingComponent (this, e);
  203. }
  204. /** Overridden from MouseListener. Drags the master Block component */
  205. void mouseDrag (const MouseEvent& e) override
  206. {
  207. if (block->isMasterBlock())
  208. {
  209. componentDragger.dragComponent (this, e, &constrainer);
  210. getParentComponent()->resized();
  211. }
  212. }
  213. ComponentDragger componentDragger;
  214. ComponentBoundsConstrainer constrainer;
  215. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BlockComponent)
  216. };
  217. //==============================================================================
  218. /**
  219. Class that renders a Lightpad on the screen
  220. */
  221. class LightpadComponent : public BlockComponent
  222. {
  223. public:
  224. LightpadComponent (Block::Ptr blockToUse)
  225. : BlockComponent (blockToUse)
  226. {}
  227. void paint (Graphics& g) override
  228. {
  229. auto r = getLocalBounds().toFloat();
  230. // clip the drawing area to only draw in the block area
  231. {
  232. Path clipArea;
  233. clipArea.addRoundedRectangle (r, r.getWidth() / 20.0f);
  234. g.reduceClipRegion (clipArea);
  235. }
  236. // Fill a black square for the Lightpad
  237. g.fillAll (Colours::black);
  238. // size ration between physical and on-screen blocks
  239. Point<float> ratio (r.getWidth() / block->getWidth(),
  240. r.getHeight() / block->getHeight());
  241. auto maxCircleSize = block->getWidth() / 3.0f;
  242. // iterate over the list of current touches and draw them on the onscreen Block
  243. for (auto touch : touches)
  244. {
  245. auto circleSize = touch.touch.z * maxCircleSize;
  246. Point<float> touchPosition (touch.touch.x,
  247. touch.touch.y);
  248. auto blob = Rectangle<float> (circleSize, circleSize)
  249. .withCentre (touchPosition) * ratio;
  250. ColourGradient cg (colourArray[touch.touch.index], blob.getCentreX(), blob.getCentreY(),
  251. Colours::transparentBlack, blob.getRight(), blob.getBottom(),
  252. true);
  253. g.setGradientFill (cg);
  254. g.fillEllipse (blob);
  255. }
  256. }
  257. void handleTouchChange (TouchSurface::Touch touch) override { touches.updateTouch (touch); }
  258. private:
  259. /** An Array of colours to use for touches */
  260. Array<Colour> colourArray = { Colours::red,
  261. Colours::blue,
  262. Colours::green,
  263. Colours::yellow,
  264. Colours::white,
  265. Colours::hotpink,
  266. Colours::mediumpurple };
  267. /** A list of current Touch events */
  268. TouchList<TouchSurface::Touch> touches;
  269. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LightpadComponent)
  270. };
  271. //==============================================================================
  272. /**
  273. Class that renders a Control Block on the screen
  274. */
  275. class ControlBlockComponent : public BlockComponent
  276. {
  277. public:
  278. ControlBlockComponent (Block::Ptr blockToUse)
  279. : BlockComponent (blockToUse),
  280. numLeds (block->getLEDRow()->getNumLEDs())
  281. {
  282. addAndMakeVisible (roundedRectangleButton);
  283. // Display the battery level on the LEDRow
  284. auto numLedsToTurnOn = static_cast<int> (numLeds * block->getBatteryLevel());
  285. // add LEDs
  286. for (auto i = 0; i < numLeds; ++i)
  287. {
  288. auto ledComponent = new LEDComponent();
  289. ledComponent->setOnState (i < numLedsToTurnOn);
  290. addAndMakeVisible (leds.add (ledComponent));
  291. }
  292. previousNumLedsOn = numLedsToTurnOn;
  293. // add buttons
  294. for (auto i = 0; i < 8; ++i)
  295. addAndMakeVisible (circleButtons[i]);
  296. }
  297. void resized() override
  298. {
  299. auto r = getLocalBounds().reduced (10);
  300. auto rowHeight = r.getHeight() / 5;
  301. auto ledWidth = (r.getWidth() - 70) / numLeds;
  302. auto buttonWidth = (r.getWidth() - 40) / 5;
  303. auto row = r;
  304. auto ledRow = row.removeFromTop (rowHeight) .withSizeKeepingCentre (r.getWidth(), ledWidth);
  305. auto buttonRow1 = row.removeFromTop (rowHeight * 2).withSizeKeepingCentre (r.getWidth(), buttonWidth);
  306. auto buttonRow2 = row.removeFromTop (rowHeight * 2).withSizeKeepingCentre (r.getWidth(), buttonWidth);
  307. for (auto* led : leds)
  308. {
  309. led->setBounds (ledRow.removeFromLeft (ledWidth).reduced (2));
  310. ledRow.removeFromLeft (5);
  311. }
  312. for (auto i = 0; i < 5; ++i)
  313. {
  314. circleButtons[i].setBounds (buttonRow1.removeFromLeft (buttonWidth).reduced (2));
  315. buttonRow1.removeFromLeft (10);
  316. }
  317. for (auto i = 5; i < 8; ++i)
  318. {
  319. circleButtons[i].setBounds (buttonRow2.removeFromLeft (buttonWidth).reduced (2));
  320. buttonRow2.removeFromLeft (10);
  321. }
  322. roundedRectangleButton.setBounds (buttonRow2);
  323. }
  324. void paint (Graphics& g) override
  325. {
  326. auto r = getLocalBounds().toFloat();
  327. // Fill a black rectangle for the Control Block
  328. g.setColour (Colours::black);
  329. g.fillRoundedRectangle (r, r.getWidth() / 20.0f);
  330. }
  331. void handleButtonPressed (ControlButton::ButtonFunction function, uint32) override
  332. {
  333. displayButtonInteraction (controlButtonFunctionToIndex (function), true);
  334. }
  335. void handleButtonReleased (ControlButton::ButtonFunction function, uint32) override
  336. {
  337. displayButtonInteraction (controlButtonFunctionToIndex (function), false);
  338. }
  339. void handleBatteryLevelUpdate (float batteryLevel) override
  340. {
  341. // Update the number of LEDs that are on to represent the battery level
  342. auto numLedsOn = static_cast<int> (numLeds * batteryLevel);
  343. if (numLedsOn != previousNumLedsOn)
  344. for (auto i = 0; i < numLeds; ++i)
  345. leds.getUnchecked (i)->setOnState (i < numLedsOn);
  346. previousNumLedsOn = numLedsOn;
  347. repaint();
  348. }
  349. private:
  350. //==============================================================================
  351. /**
  352. Base class that renders a Control Block button
  353. */
  354. struct ControlBlockSubComponent : public Component,
  355. public TooltipClient
  356. {
  357. ControlBlockSubComponent (Colour componentColourToUse)
  358. : componentColour (componentColourToUse)
  359. {}
  360. /** Subclasses should override this to paint the button on the screen */
  361. virtual void paint (Graphics&) override = 0;
  362. /** Sets the colour of the button */
  363. void setColour (Colour c) { componentColour = c; }
  364. /** Sets the on state of the button */
  365. void setOnState (bool isOn)
  366. {
  367. onState = isOn;
  368. repaint();
  369. }
  370. /** Returns the Control Block tooltip */
  371. String getTooltip() override
  372. {
  373. for (Component* comp = this; comp != nullptr; comp = comp->getParentComponent())
  374. if (auto* sttc = dynamic_cast<SettableTooltipClient*> (comp))
  375. return sttc->getTooltip();
  376. return {};
  377. }
  378. //==============================================================================
  379. Colour componentColour;
  380. bool onState = false;
  381. //==============================================================================
  382. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ControlBlockSubComponent)
  383. };
  384. /**
  385. Class that renders a Control Block LED on the screen
  386. */
  387. struct LEDComponent : public ControlBlockSubComponent
  388. {
  389. LEDComponent() : ControlBlockSubComponent (Colours::green) {}
  390. void paint (Graphics& g) override
  391. {
  392. g.setColour (componentColour.withAlpha (onState ? 1.0f : 0.2f));
  393. g.fillEllipse (getLocalBounds().toFloat());
  394. }
  395. };
  396. /**
  397. Class that renders a Control Block single circular button on the screen
  398. */
  399. struct CircleButtonComponent : public ControlBlockSubComponent
  400. {
  401. CircleButtonComponent() : ControlBlockSubComponent (Colours::blue) {}
  402. void paint (Graphics& g) override
  403. {
  404. g.setColour (componentColour.withAlpha (onState ? 1.0f : 0.2f));
  405. g.fillEllipse (getLocalBounds().toFloat());
  406. }
  407. };
  408. /**
  409. Class that renders a Control Block rounded rectangular button containing two buttons
  410. on the screen
  411. */
  412. struct RoundedRectangleButtonComponent : public ControlBlockSubComponent
  413. {
  414. RoundedRectangleButtonComponent() : ControlBlockSubComponent (Colours::blue) {}
  415. void paint (Graphics& g) override
  416. {
  417. auto r = getLocalBounds().toFloat();
  418. g.setColour (componentColour.withAlpha (0.2f));
  419. g.fillRoundedRectangle (r.toFloat(), 20.0f);
  420. g.setColour (componentColour.withAlpha (1.0f));
  421. // is a button pressed?
  422. if (doubleButtonOnState[0] || doubleButtonOnState[1])
  423. {
  424. auto semiButtonWidth = r.getWidth() / 2.0f;
  425. auto semiButtonBounds = r.withWidth (semiButtonWidth)
  426. .withX (doubleButtonOnState[1] ? semiButtonWidth : 0)
  427. .reduced (5.0f, 2.0f);
  428. g.fillEllipse (semiButtonBounds);
  429. }
  430. }
  431. void setPressedState (bool isPressed, int button)
  432. {
  433. doubleButtonOnState[button] = isPressed;
  434. repaint();
  435. }
  436. private:
  437. bool doubleButtonOnState[2] = { false, false };
  438. };
  439. /** Displays a button press or release interaction for a button at a given index */
  440. void displayButtonInteraction (int buttonIndex, bool isPressed)
  441. {
  442. if (! isPositiveAndBelow (buttonIndex, 10))
  443. return;
  444. if (buttonIndex >= 8)
  445. roundedRectangleButton.setPressedState (isPressed, buttonIndex == 8);
  446. else
  447. circleButtons[buttonIndex].setOnState (isPressed);
  448. }
  449. //==============================================================================
  450. int numLeds;
  451. OwnedArray<LEDComponent> leds;
  452. CircleButtonComponent circleButtons[8];
  453. RoundedRectangleButtonComponent roundedRectangleButton;
  454. int previousNumLedsOn;
  455. //==============================================================================
  456. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ControlBlockComponent)
  457. };
  458. //==============================================================================
  459. /**
  460. The main component where the Block components will be displayed
  461. */
  462. class BlocksMonitorDemo : public Component,
  463. public TopologySource::Listener,
  464. private Timer
  465. {
  466. public:
  467. BlocksMonitorDemo()
  468. {
  469. noBlocksLabel.setText ("No BLOCKS connected...", dontSendNotification);
  470. noBlocksLabel.setJustificationType (Justification::centred);
  471. zoomOutButton.setButtonText ("+");
  472. zoomOutButton.onClick = [this] { blockUnitInPixels = (int) (blockUnitInPixels * 1.05f); resized(); };
  473. zoomOutButton.setAlwaysOnTop (true);
  474. zoomInButton.setButtonText ("-");
  475. zoomInButton.onClick = [this] { blockUnitInPixels = (int) (blockUnitInPixels * 0.95f); resized(); };
  476. zoomInButton.setAlwaysOnTop (true);
  477. // Register BlocksMonitorDemo as a listener to the PhysicalTopologySource object
  478. topologySource.addListener (this);
  479. startTimer (10000);
  480. addAndMakeVisible (noBlocksLabel);
  481. addAndMakeVisible (zoomOutButton);
  482. addAndMakeVisible (zoomInButton);
  483. #if JUCE_IOS
  484. connectButton.setButtonText ("Connect");
  485. connectButton.onClick = [] { BluetoothMidiDevicePairingDialogue::open(); };
  486. connectButton.setAlwaysOnTop (true);
  487. addAndMakeVisible (connectButton);
  488. #endif
  489. setSize (600, 600);
  490. }
  491. void paint (Graphics&) override {}
  492. void resized() override
  493. {
  494. #if JUCE_IOS
  495. connectButton.setBounds (getRight() - 100, 20, 80, 30);
  496. #endif
  497. noBlocksLabel.setVisible (false);
  498. auto numBlockComponents = blockComponents.size();
  499. // If there are no currently connected Blocks then display some text on the screen
  500. if (numBlockComponents == 0)
  501. {
  502. noBlocksLabel.setVisible (true);
  503. noBlocksLabel.setBounds (0, (getHeight() / 2) - 50, getWidth(), 100);
  504. return;
  505. }
  506. zoomOutButton.setBounds (10, getHeight() - 40, 40, 30);
  507. zoomInButton.setBounds (zoomOutButton.getRight(), zoomOutButton.getY(), 40, 30);
  508. if (isInitialResized)
  509. {
  510. // Work out the area needed in terms of Block units
  511. Rectangle<float> maxArea;
  512. for (auto blockComponent : blockComponents)
  513. {
  514. auto topLeft = blockComponent->topLeft;
  515. auto rotation = blockComponent->rotation;
  516. auto blockSize = 0;
  517. if (rotation == 180)
  518. blockSize = blockComponent->block->getWidth();
  519. else if (rotation == 90)
  520. blockSize = blockComponent->block->getHeight();
  521. if (topLeft.x - blockSize < maxArea.getX())
  522. maxArea.setX (topLeft.x - blockSize);
  523. blockSize = 0;
  524. if (rotation == 0)
  525. blockSize = blockComponent->block->getWidth();
  526. else if (rotation == 270)
  527. blockSize = blockComponent->block->getHeight();
  528. if (topLeft.x + blockSize > maxArea.getRight())
  529. maxArea.setWidth (topLeft.x + blockSize);
  530. blockSize = 0;
  531. if (rotation == 180)
  532. blockSize = blockComponent->block->getHeight();
  533. else if (rotation == 270)
  534. blockSize = blockComponent->block->getWidth();
  535. if (topLeft.y - blockSize < maxArea.getY())
  536. maxArea.setY (topLeft.y - blockSize);
  537. blockSize = 0;
  538. if (rotation == 0)
  539. blockSize = blockComponent->block->getHeight();
  540. else if (rotation == 90)
  541. blockSize = blockComponent->block->getWidth();
  542. if (topLeft.y + blockSize > maxArea.getBottom())
  543. maxArea.setHeight (topLeft.y + blockSize);
  544. }
  545. auto totalWidth = std::abs (maxArea.getX()) + maxArea.getWidth();
  546. auto totalHeight = std::abs (maxArea.getY()) + maxArea.getHeight();
  547. blockUnitInPixels = static_cast<int> (jmin ((getHeight() / totalHeight) - 50, (getWidth() / totalWidth) - 50));
  548. masterBlockComponent->centreWithSize (masterBlockComponent->block->getWidth() * blockUnitInPixels,
  549. masterBlockComponent->block->getHeight() * blockUnitInPixels);
  550. isInitialResized = false;
  551. }
  552. else
  553. {
  554. masterBlockComponent->setSize (masterBlockComponent->block->getWidth() * blockUnitInPixels, masterBlockComponent->block->getHeight() * blockUnitInPixels);
  555. }
  556. for (auto blockComponent : blockComponents)
  557. {
  558. if (blockComponent == masterBlockComponent)
  559. continue;
  560. blockComponent->setBounds (masterBlockComponent->getX() + static_cast<int> (blockComponent->topLeft.x * blockUnitInPixels),
  561. masterBlockComponent->getY() + static_cast<int> (blockComponent->topLeft.y * blockUnitInPixels),
  562. blockComponent->block->getWidth() * blockUnitInPixels,
  563. blockComponent->block->getHeight() * blockUnitInPixels);
  564. if (blockComponent->rotation != 0)
  565. blockComponent->setTransform (AffineTransform::rotation (static_cast<float> (degreesToRadians (blockComponent->rotation)),
  566. static_cast<float> (blockComponent->getX()),
  567. static_cast<float> (blockComponent->getY())));
  568. }
  569. }
  570. /** Overridden from TopologySource::Listener, called when the topology changes */
  571. void topologyChanged() override
  572. {
  573. // Clear the array of Block components
  574. blockComponents.clear();
  575. masterBlockComponent = nullptr;
  576. // Get the current topology
  577. auto topology = topologySource.getCurrentTopology();
  578. // Create a BlockComponent object for each Block object and store a pointer to the master
  579. for (auto& block : topology.blocks)
  580. {
  581. if (auto* blockComponent = createBlockComponent (block))
  582. {
  583. addAndMakeVisible (blockComponents.add (blockComponent));
  584. if (blockComponent->block->isMasterBlock())
  585. masterBlockComponent = blockComponent;
  586. }
  587. }
  588. // Must have a master Block!
  589. if (topology.blocks.size() > 0)
  590. jassert (masterBlockComponent != nullptr);
  591. // Calculate the relative position and rotation for each Block
  592. positionBlocks (topology);
  593. // Update the display
  594. isInitialResized = true;
  595. resized();
  596. }
  597. private:
  598. /** Creates a BlockComponent object for a new Block and adds it to the content component */
  599. BlockComponent* createBlockComponent (Block::Ptr newBlock)
  600. {
  601. auto type = newBlock->getType();
  602. if (type == Block::lightPadBlock)
  603. return new LightpadComponent (newBlock);
  604. if (type == Block::loopBlock || type == Block::liveBlock
  605. || type == Block::touchBlock || type == Block::developerControlBlock)
  606. return new ControlBlockComponent (newBlock);
  607. // Should only be connecting a Lightpad or Control Block!
  608. jassertfalse;
  609. return nullptr;
  610. }
  611. /** Periodically updates the displayed BlockComponent tooltips */
  612. void timerCallback() override
  613. {
  614. for (auto c : blockComponents)
  615. c->updateStatsAndTooltip();
  616. }
  617. /** Calculates the position and rotation of each connected Block relative to the master Block */
  618. void positionBlocks (BlockTopology topology)
  619. {
  620. Array<BlockComponent*> blocksConnectedToMaster;
  621. auto maxDelta = std::numeric_limits<float>::max();
  622. auto maxLoops = 50;
  623. // Store all the connections to the master Block
  624. Array<BlockDeviceConnection> masterBlockConnections;
  625. for (auto connection : topology.connections)
  626. if (connection.device1 == masterBlockComponent->block->uid || connection.device2 == masterBlockComponent->block->uid)
  627. masterBlockConnections.add (connection);
  628. // Position all the Blocks that are connected to the master Block
  629. while (maxDelta > 0.001f && --maxLoops)
  630. {
  631. maxDelta = 0.0f;
  632. // Loop through each connection on the master Block
  633. for (auto connection : masterBlockConnections)
  634. {
  635. // Work out whether the master Block is device 1 or device 2 in the BlockDeviceConnection struct
  636. bool isDevice1 = true;
  637. if (masterBlockComponent->block->uid == connection.device2)
  638. isDevice1 = false;
  639. // Get the connected ports
  640. auto masterPort = isDevice1 ? connection.connectionPortOnDevice1 : connection.connectionPortOnDevice2;
  641. auto otherPort = isDevice1 ? connection.connectionPortOnDevice2 : connection.connectionPortOnDevice1;
  642. for (auto otherBlockComponent : blockComponents)
  643. {
  644. // Get the other block
  645. if (otherBlockComponent->block->uid == (isDevice1 ? connection.device2 : connection.device1))
  646. {
  647. blocksConnectedToMaster.addIfNotAlreadyThere (otherBlockComponent);
  648. // Get the rotation of the other Block relative to the master Block
  649. otherBlockComponent->rotation = getRotation (masterPort.edge, otherPort.edge);
  650. // Get the offsets for the connected ports
  651. auto masterBlockOffset = masterBlockComponent->getOffsetForPort (masterPort);
  652. auto otherBlockOffset = otherBlockComponent->topLeft + otherBlockComponent->getOffsetForPort (otherPort);
  653. // Work out the distance between the two connected ports
  654. auto delta = masterBlockOffset - otherBlockOffset;
  655. // Move the other block half the distance to the connection
  656. otherBlockComponent->topLeft += delta / 2.0f;
  657. // Work out whether we are close enough for the loop to end
  658. maxDelta = jmax (maxDelta, std::abs (delta.x), std::abs (delta.y));
  659. }
  660. }
  661. }
  662. }
  663. // Check if there are any Blocks that have not been positioned yet
  664. Array<BlockComponent*> unpositionedBlocks;
  665. for (auto blockComponent : blockComponents)
  666. if (blockComponent != masterBlockComponent && ! blocksConnectedToMaster.contains (blockComponent))
  667. unpositionedBlocks.add (blockComponent);
  668. if (unpositionedBlocks.size() > 0)
  669. {
  670. // Reset the loop conditions
  671. maxDelta = std::numeric_limits<float>::max();
  672. maxLoops = 50;
  673. // Position all the remaining Blocks
  674. while (maxDelta > 0.001f && --maxLoops)
  675. {
  676. maxDelta = 0.0f;
  677. // Loop through each unpositioned Block
  678. for (auto blockComponent : unpositionedBlocks)
  679. {
  680. // Store all the connections to this Block
  681. Array<BlockDeviceConnection> blockConnections;
  682. for (auto connection : topology.connections)
  683. if (connection.device1 == blockComponent->block->uid || connection.device2 == blockComponent->block->uid)
  684. blockConnections.add (connection);
  685. // Loop through each connection on this Block
  686. for (auto connection : blockConnections)
  687. {
  688. // Work out whether this Block is device 1 or device 2 in the BlockDeviceConnection struct
  689. auto isDevice1 = true;
  690. if (blockComponent->block->uid == connection.device2)
  691. isDevice1 = false;
  692. // Get the connected ports
  693. auto thisPort = isDevice1 ? connection.connectionPortOnDevice1 : connection.connectionPortOnDevice2;
  694. auto otherPort = isDevice1 ? connection.connectionPortOnDevice2 : connection.connectionPortOnDevice1;
  695. // Get the other Block
  696. for (auto otherBlockComponent : blockComponents)
  697. {
  698. if (otherBlockComponent->block->uid == (isDevice1 ? connection.device2 : connection.device1))
  699. {
  700. // Get the rotation
  701. auto rotation = getRotation (otherPort.edge, thisPort.edge) + otherBlockComponent->rotation;
  702. if (rotation > 360)
  703. rotation -= 360;
  704. blockComponent->rotation = rotation;
  705. // Get the offsets for the connected ports
  706. auto otherBlockOffset = (otherBlockComponent->topLeft + otherBlockComponent->getOffsetForPort (otherPort));
  707. auto thisBlockOffset = (blockComponent->topLeft + blockComponent->getOffsetForPort (thisPort));
  708. // Work out the distance between the two connected ports
  709. auto delta = otherBlockOffset - thisBlockOffset;
  710. // Move this block half the distance to the connection
  711. blockComponent->topLeft += delta / 2.0f;
  712. // Work out whether we are close enough for the loop to end
  713. maxDelta = jmax (maxDelta, std::abs (delta.x), std::abs (delta.y));
  714. }
  715. }
  716. }
  717. }
  718. }
  719. }
  720. }
  721. /** Returns a rotation in degrees based on the connected edges of two blocks */
  722. int getRotation (Block::ConnectionPort::DeviceEdge staticEdge, Block::ConnectionPort::DeviceEdge rotatedEdge)
  723. {
  724. using edge = Block::ConnectionPort::DeviceEdge;
  725. switch (staticEdge)
  726. {
  727. case edge::north:
  728. {
  729. switch (rotatedEdge)
  730. {
  731. case edge::north:
  732. return 180;
  733. case edge::south:
  734. return 0;
  735. case edge::east:
  736. return 90;
  737. case edge::west:
  738. return 270;
  739. }
  740. }
  741. case edge::south:
  742. {
  743. switch (rotatedEdge)
  744. {
  745. case edge::north:
  746. return 0;
  747. case edge::south:
  748. return 180;
  749. case edge::east:
  750. return 270;
  751. case edge::west:
  752. return 90;
  753. }
  754. }
  755. case edge::east:
  756. {
  757. switch (rotatedEdge)
  758. {
  759. case edge::north:
  760. return 270;
  761. case edge::south:
  762. return 90;
  763. case edge::east:
  764. return 180;
  765. case edge::west:
  766. return 0;
  767. }
  768. }
  769. case edge::west:
  770. {
  771. switch (rotatedEdge)
  772. {
  773. case edge::north:
  774. return 90;
  775. case edge::south:
  776. return 270;
  777. case edge::east:
  778. return 0;
  779. case edge::west:
  780. return 180;
  781. }
  782. }
  783. }
  784. return 0;
  785. }
  786. //==============================================================================
  787. PhysicalTopologySource topologySource;
  788. OwnedArray<BlockComponent> blockComponents;
  789. BlockComponent* masterBlockComponent = nullptr;
  790. Label noBlocksLabel;
  791. TextButton zoomOutButton;
  792. TextButton zoomInButton;
  793. int blockUnitInPixels;
  794. bool isInitialResized;
  795. #if JUCE_IOS
  796. TextButton connectButton;
  797. #endif
  798. //==============================================================================
  799. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BlocksMonitorDemo)
  800. };