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.

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