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.

448 lines
17KB

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