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.

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