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.

1037 lines
39KB

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