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.

444 lines
17KB

  1. #pragma once
  2. #include "../JuceLibraryCode/JuceHeader.h"
  3. #include "BlockComponents.h"
  4. /**
  5. The main component where the Block components will be displayed
  6. */
  7. class MainComponent : public Component,
  8. public TopologySource::Listener,
  9. private Timer,
  10. private Button::Listener
  11. {
  12. public:
  13. MainComponent()
  14. {
  15. setSize (600, 600);
  16. noBlocksLabel.setText ("No BLOCKS connected...", dontSendNotification);
  17. noBlocksLabel.setJustificationType (Justification::centred);
  18. zoomOutButton.setButtonText ("+");
  19. zoomOutButton.addListener (this);
  20. zoomOutButton.setAlwaysOnTop (true);
  21. zoomInButton.setButtonText ("-");
  22. zoomInButton.addListener (this);
  23. zoomInButton.setAlwaysOnTop (true);
  24. // Register MainComponent as a listener to the PhysicalTopologySource object
  25. topologySource.addListener (this);
  26. startTimer (10000);
  27. addAndMakeVisible (noBlocksLabel);
  28. addAndMakeVisible (zoomOutButton);
  29. addAndMakeVisible (zoomInButton);
  30. #if JUCE_IOS
  31. connectButton.setButtonText ("Connect");
  32. connectButton.addListener (this);
  33. connectButton.setAlwaysOnTop (true);
  34. addAndMakeVisible (connectButton);
  35. #endif
  36. }
  37. void paint (Graphics& g) override
  38. {
  39. g.fillAll (Colours::lightgrey);
  40. }
  41. void resized() override
  42. {
  43. #if JUCE_IOS
  44. connectButton.setBounds (getRight() - 100, 20, 80, 30);
  45. #endif
  46. noBlocksLabel.setVisible (false);
  47. const int numBlockComponents = blockComponents.size();
  48. // If there are no currently connected Blocks then display some text on the screen
  49. if (numBlockComponents == 0)
  50. {
  51. noBlocksLabel.setVisible (true);
  52. noBlocksLabel.setBounds (0, (getHeight() / 2) - 50, getWidth(), 100);
  53. return;
  54. }
  55. zoomOutButton.setBounds (10, getHeight() - 40, 40, 30);
  56. zoomInButton.setBounds (zoomOutButton.getRight(), zoomOutButton.getY(), 40, 30);
  57. if (isInitialResized)
  58. {
  59. // Work out the area needed in terms of Block units
  60. Rectangle<float> maxArea;
  61. for (auto blockComponent : blockComponents)
  62. {
  63. auto topLeft = blockComponent->topLeft;
  64. int rotation = blockComponent->rotation;
  65. int blockSize = 0;
  66. if (rotation == 180)
  67. blockSize = blockComponent->block->getWidth();
  68. else if (rotation == 90)
  69. blockSize = blockComponent->block->getHeight();
  70. if (topLeft.x - blockSize < maxArea.getX())
  71. maxArea.setX (topLeft.x - blockSize);
  72. blockSize = 0;
  73. if (rotation == 0)
  74. blockSize = blockComponent->block->getWidth();
  75. else if (rotation == 270)
  76. blockSize = blockComponent->block->getHeight();
  77. if (topLeft.x + blockSize > maxArea.getRight())
  78. maxArea.setWidth (topLeft.x + blockSize);
  79. blockSize = 0;
  80. if (rotation == 180)
  81. blockSize = blockComponent->block->getHeight();
  82. else if (rotation == 270)
  83. blockSize = blockComponent->block->getWidth();
  84. if (topLeft.y - blockSize < maxArea.getY())
  85. maxArea.setY (topLeft.y - blockSize);
  86. blockSize = 0;
  87. if (rotation == 0)
  88. blockSize = blockComponent->block->getHeight();
  89. else if (rotation == 90)
  90. blockSize = blockComponent->block->getWidth();
  91. if (topLeft.y + blockSize > maxArea.getBottom())
  92. maxArea.setHeight (topLeft.y + blockSize);
  93. }
  94. float totalWidth = std::abs (maxArea.getX()) + maxArea.getWidth();
  95. float totalHeight = std::abs (maxArea.getY()) + maxArea.getHeight();
  96. blockUnitInPixels = static_cast<int> (jmin ((getHeight() / totalHeight) - 50, (getWidth() / totalWidth) - 50));
  97. masterBlockComponent->centreWithSize (masterBlockComponent->block->getWidth() * blockUnitInPixels,
  98. masterBlockComponent->block->getHeight() * blockUnitInPixels);
  99. isInitialResized = false;
  100. }
  101. else
  102. {
  103. masterBlockComponent->setSize (masterBlockComponent->block->getWidth() * blockUnitInPixels, masterBlockComponent->block->getHeight() * blockUnitInPixels);
  104. }
  105. for (auto blockComponent : blockComponents)
  106. {
  107. if (blockComponent == masterBlockComponent)
  108. continue;
  109. blockComponent->setBounds (masterBlockComponent->getX() + static_cast<int> (blockComponent->topLeft.x * blockUnitInPixels),
  110. masterBlockComponent->getY() + static_cast<int> (blockComponent->topLeft.y * blockUnitInPixels),
  111. blockComponent->block->getWidth() * blockUnitInPixels,
  112. blockComponent->block->getHeight() * blockUnitInPixels);
  113. if (blockComponent->rotation != 0)
  114. blockComponent->setTransform (AffineTransform::rotation (blockComponent->rotation * (static_cast<float> (double_Pi) / 180.0f),
  115. static_cast<float> (blockComponent->getX()),
  116. static_cast<float> (blockComponent->getY())));
  117. }
  118. }
  119. /** Overridden from TopologySource::Listener, called when the topology changes */
  120. void topologyChanged() override
  121. {
  122. // Clear the array of Block components
  123. blockComponents.clear();
  124. masterBlockComponent = nullptr;
  125. // Get the current topology
  126. auto topology = topologySource.getCurrentTopology();
  127. // Create a BlockComponent object for each Block object and store a pointer to the master
  128. for (auto& block : topology.blocks)
  129. {
  130. if (auto* blockComponent = createBlockComponent (block))
  131. {
  132. addAndMakeVisible (blockComponents.add (blockComponent));
  133. if (blockComponent->block->isMasterBlock())
  134. masterBlockComponent = blockComponent;
  135. }
  136. }
  137. // Must have a master Block!
  138. if (topology.blocks.size() > 0)
  139. jassert (masterBlockComponent != nullptr);
  140. // Calculate the relative position and rotation for each Block
  141. positionBlocks (topology);
  142. // Update the display
  143. isInitialResized = true;
  144. resized();
  145. }
  146. private:
  147. /** Creates a BlockComponent object for a new Block and adds it to the content component */
  148. BlockComponent* createBlockComponent (Block::Ptr newBlock)
  149. {
  150. auto type = newBlock->getType();
  151. if (type == Block::lightPadBlock)
  152. return new LightpadComponent (newBlock);
  153. if (type == Block::loopBlock || type == Block::liveBlock)
  154. return new ControlBlockComponent (newBlock);
  155. // Should only be connecting a Lightpad or Control Block!
  156. jassertfalse;
  157. return nullptr;
  158. }
  159. /** Periodically updates the displayed BlockComponent tooltips */
  160. void timerCallback() override
  161. {
  162. for (auto c : blockComponents)
  163. c->updateStatsAndTooltip();
  164. }
  165. /** Zooms the display in or out */
  166. void buttonClicked (Button* button) override
  167. {
  168. #if JUCE_IOS
  169. if (button == &connectButton)
  170. {
  171. BluetoothMidiDevicePairingDialogue::open();
  172. return;
  173. }
  174. #endif
  175. if (button == &zoomOutButton || button == &zoomInButton)
  176. {
  177. blockUnitInPixels *= (button == &zoomOutButton ? 1.05f : 0.95f);
  178. resized();
  179. }
  180. }
  181. /** Calculates the position and rotation of each connected Block relative to the master Block */
  182. void positionBlocks (BlockTopology topology)
  183. {
  184. Array<BlockComponent*> blocksConnectedToMaster;
  185. float maxDelta = std::numeric_limits<float>::max();
  186. int maxLoops = 50;
  187. // Store all the connections to the master Block
  188. Array<BlockDeviceConnection> masterBlockConnections;
  189. for (auto connection : topology.connections)
  190. if (connection.device1 == masterBlockComponent->block->uid || connection.device2 == masterBlockComponent->block->uid)
  191. masterBlockConnections.add (connection);
  192. // Position all the Blocks that are connected to the master Block
  193. while (maxDelta > 0.001f && --maxLoops)
  194. {
  195. maxDelta = 0.0f;
  196. // Loop through each connection on the master Block
  197. for (auto connection : masterBlockConnections)
  198. {
  199. // Work out whether the master Block is device 1 or device 2 in the BlockDeviceConnection struct
  200. bool isDevice1 = true;
  201. if (masterBlockComponent->block->uid == connection.device2)
  202. isDevice1 = false;
  203. // Get the connected ports
  204. auto masterPort = isDevice1 ? connection.connectionPortOnDevice1 : connection.connectionPortOnDevice2;
  205. auto otherPort = isDevice1 ? connection.connectionPortOnDevice2 : connection.connectionPortOnDevice1;
  206. for (auto otherBlockComponent : blockComponents)
  207. {
  208. // Get the other block
  209. if (otherBlockComponent->block->uid == (isDevice1 ? connection.device2 : connection.device1))
  210. {
  211. blocksConnectedToMaster.addIfNotAlreadyThere (otherBlockComponent);
  212. // Get the rotation of the other Block relative to the master Block
  213. otherBlockComponent->rotation = getRotation (masterPort.edge, otherPort.edge);
  214. // Get the offsets for the connected ports
  215. auto masterBlockOffset = masterBlockComponent->getOffsetForPort (masterPort);
  216. auto otherBlockOffset = otherBlockComponent->topLeft + otherBlockComponent->getOffsetForPort (otherPort);
  217. // Work out the distance between the two connected ports
  218. auto delta = masterBlockOffset - otherBlockOffset;
  219. // Move the other block half the distance to the connection
  220. otherBlockComponent->topLeft += delta / 2.0f;
  221. // Work out whether we are close enough for the loop to end
  222. maxDelta = jmax (maxDelta, std::abs (delta.x), std::abs (delta.y));
  223. }
  224. }
  225. }
  226. }
  227. // Check if there are any Blocks that have not been positioned yet
  228. Array<BlockComponent*> unpositionedBlocks;
  229. for (auto blockComponent : blockComponents)
  230. if (blockComponent != masterBlockComponent && ! blocksConnectedToMaster.contains (blockComponent))
  231. unpositionedBlocks.add (blockComponent);
  232. if (unpositionedBlocks.size() > 0)
  233. {
  234. // Reset the loop conditions
  235. maxDelta = std::numeric_limits<float>::max();
  236. maxLoops = 50;
  237. // Position all the remaining Blocks
  238. while (maxDelta > 0.001f && --maxLoops)
  239. {
  240. maxDelta = 0.0f;
  241. // Loop through each unpositioned Block
  242. for (auto blockComponent : unpositionedBlocks)
  243. {
  244. // Store all the connections to this Block
  245. Array<BlockDeviceConnection> blockConnections;
  246. for (auto connection : topology.connections)
  247. if (connection.device1 == blockComponent->block->uid || connection.device2 == blockComponent->block->uid)
  248. blockConnections.add (connection);
  249. // Loop through each connection on this Block
  250. for (auto connection : blockConnections)
  251. {
  252. // Work out whether this Block is device 1 or device 2 in the BlockDeviceConnection struct
  253. bool isDevice1 = true;
  254. if (blockComponent->block->uid == connection.device2)
  255. isDevice1 = false;
  256. // Get the connected ports
  257. auto thisPort = isDevice1 ? connection.connectionPortOnDevice1 : connection.connectionPortOnDevice2;
  258. auto otherPort = isDevice1 ? connection.connectionPortOnDevice2 : connection.connectionPortOnDevice1;
  259. // Get the other Block
  260. for (auto otherBlockComponent : blockComponents)
  261. {
  262. if (otherBlockComponent->block->uid == (isDevice1 ? connection.device2 : connection.device1))
  263. {
  264. // Get the rotation
  265. int rotation = getRotation (otherPort.edge, thisPort.edge) + otherBlockComponent->rotation;
  266. if (rotation > 360)
  267. rotation -= 360;
  268. blockComponent->rotation = rotation;
  269. // Get the offsets for the connected ports
  270. auto otherBlockOffset = (otherBlockComponent->topLeft + otherBlockComponent->getOffsetForPort (otherPort));
  271. auto thisBlockOffset = (blockComponent->topLeft + blockComponent->getOffsetForPort (thisPort));
  272. // Work out the distance between the two connected ports
  273. auto delta = otherBlockOffset - thisBlockOffset;
  274. // Move this block half the distance to the connection
  275. blockComponent->topLeft += delta / 2.0f;
  276. // Work out whether we are close enough for the loop to end
  277. maxDelta = jmax (maxDelta, std::abs (delta.x), std::abs (delta.y));
  278. }
  279. }
  280. }
  281. }
  282. }
  283. }
  284. }
  285. /** Returns a rotation in degrees based on the connected edges of two blocks */
  286. int getRotation (Block::ConnectionPort::DeviceEdge staticEdge, Block::ConnectionPort::DeviceEdge rotatedEdge)
  287. {
  288. using edge = Block::ConnectionPort::DeviceEdge;
  289. switch (staticEdge)
  290. {
  291. case edge::north:
  292. {
  293. switch (rotatedEdge)
  294. {
  295. case edge::north:
  296. return 180;
  297. case edge::south:
  298. return 0;
  299. case edge::east:
  300. return 90;
  301. case edge::west:
  302. return 270;
  303. }
  304. }
  305. case edge::south:
  306. {
  307. switch (rotatedEdge)
  308. {
  309. case edge::north:
  310. return 0;
  311. case edge::south:
  312. return 180;
  313. case edge::east:
  314. return 270;
  315. case edge::west:
  316. return 90;
  317. }
  318. }
  319. case edge::east:
  320. {
  321. switch (rotatedEdge)
  322. {
  323. case edge::north:
  324. return 270;
  325. case edge::south:
  326. return 90;
  327. case edge::east:
  328. return 180;
  329. case edge::west:
  330. return 0;
  331. }
  332. }
  333. case edge::west:
  334. {
  335. switch (rotatedEdge)
  336. {
  337. case edge::north:
  338. return 90;
  339. case edge::south:
  340. return 270;
  341. case edge::east:
  342. return 0;
  343. case edge::west:
  344. return 180;
  345. }
  346. }
  347. }
  348. return 0;
  349. }
  350. //==============================================================================
  351. PhysicalTopologySource topologySource;
  352. OwnedArray<BlockComponent> blockComponents;
  353. BlockComponent* masterBlockComponent = nullptr;
  354. Label noBlocksLabel;
  355. TextButton zoomOutButton;
  356. TextButton zoomInButton;
  357. int blockUnitInPixels;
  358. bool isInitialResized;
  359. #if JUCE_IOS
  360. TextButton connectButton;
  361. #endif
  362. //==============================================================================
  363. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainComponent)
  364. };