|
- #pragma once
-
- #include "../JuceLibraryCode/JuceHeader.h"
- #include "BlockComponents.h"
-
- /**
- The main component where the Block components will be displayed
- */
- class MainComponent : public Component,
- public TopologySource::Listener,
- private Timer,
- private Button::Listener
- {
- public:
- MainComponent()
- {
- setSize (600, 600);
-
- noBlocksLabel.setText ("No BLOCKS connected...", dontSendNotification);
- noBlocksLabel.setJustificationType (Justification::centred);
-
- zoomOutButton.setButtonText ("+");
- zoomOutButton.addListener (this);
- zoomOutButton.setAlwaysOnTop (true);
-
- zoomInButton.setButtonText ("-");
- zoomInButton.addListener (this);
- zoomInButton.setAlwaysOnTop (true);
-
- // Register MainComponent as a listener to the PhysicalTopologySource object
- topologySource.addListener (this);
-
- startTimer (10000);
-
- addAndMakeVisible (noBlocksLabel);
- addAndMakeVisible (zoomOutButton);
- addAndMakeVisible (zoomInButton);
-
- #if JUCE_IOS
- connectButton.setButtonText ("Connect");
- connectButton.addListener (this);
- connectButton.setAlwaysOnTop (true);
- addAndMakeVisible (connectButton);
- #endif
- }
-
- void paint (Graphics& g) override
- {
- g.fillAll (Colours::lightgrey);
- }
-
- void resized() override
- {
- #if JUCE_IOS
- connectButton.setBounds (getRight() - 100, 20, 80, 30);
- #endif
-
- noBlocksLabel.setVisible (false);
- const int numBlockComponents = blockComponents.size();
-
- // If there are no currently connected Blocks then display some text on the screen
- if (numBlockComponents == 0)
- {
- noBlocksLabel.setVisible (true);
- noBlocksLabel.setBounds (0, (getHeight() / 2) - 50, getWidth(), 100);
- return;
- }
-
- zoomOutButton.setBounds (10, getHeight() - 40, 40, 30);
- zoomInButton.setBounds (zoomOutButton.getRight(), zoomOutButton.getY(), 40, 30);
-
- if (isInitialResized)
- {
- // Work out the area needed in terms of Block units
- Rectangle<float> maxArea;
- for (auto blockComponent : blockComponents)
- {
- auto topLeft = blockComponent->topLeft;
- int rotation = blockComponent->rotation;
- int blockSize = 0;
-
- if (rotation == 180)
- blockSize = blockComponent->block->getWidth();
- else if (rotation == 90)
- blockSize = blockComponent->block->getHeight();
-
- if (topLeft.x - blockSize < maxArea.getX())
- maxArea.setX (topLeft.x - blockSize);
-
- blockSize = 0;
- if (rotation == 0)
- blockSize = blockComponent->block->getWidth();
- else if (rotation == 270)
- blockSize = blockComponent->block->getHeight();
-
- if (topLeft.x + blockSize > maxArea.getRight())
- maxArea.setWidth (topLeft.x + blockSize);
-
- blockSize = 0;
- if (rotation == 180)
- blockSize = blockComponent->block->getHeight();
- else if (rotation == 270)
- blockSize = blockComponent->block->getWidth();
-
- if (topLeft.y - blockSize < maxArea.getY())
- maxArea.setY (topLeft.y - blockSize);
-
- blockSize = 0;
- if (rotation == 0)
- blockSize = blockComponent->block->getHeight();
- else if (rotation == 90)
- blockSize = blockComponent->block->getWidth();
-
- if (topLeft.y + blockSize > maxArea.getBottom())
- maxArea.setHeight (topLeft.y + blockSize);
- }
-
- float totalWidth = std::abs (maxArea.getX()) + maxArea.getWidth();
- float totalHeight = std::abs (maxArea.getY()) + maxArea.getHeight();
-
- blockUnitInPixels = static_cast<int> (jmin ((getHeight() / totalHeight) - 50, (getWidth() / totalWidth) - 50));
-
- masterBlockComponent->centreWithSize (masterBlockComponent->block->getWidth() * blockUnitInPixels,
- masterBlockComponent->block->getHeight() * blockUnitInPixels);
-
- isInitialResized = false;
- }
- else
- {
- masterBlockComponent->setSize (masterBlockComponent->block->getWidth() * blockUnitInPixels, masterBlockComponent->block->getHeight() * blockUnitInPixels);
- }
-
- for (auto blockComponent : blockComponents)
- {
- if (blockComponent == masterBlockComponent)
- continue;
-
- blockComponent->setBounds (masterBlockComponent->getX() + static_cast<int> (blockComponent->topLeft.x * blockUnitInPixels),
- masterBlockComponent->getY() + static_cast<int> (blockComponent->topLeft.y * blockUnitInPixels),
- blockComponent->block->getWidth() * blockUnitInPixels,
- blockComponent->block->getHeight() * blockUnitInPixels);
-
- if (blockComponent->rotation != 0)
- blockComponent->setTransform (AffineTransform::rotation (blockComponent->rotation * (static_cast<float> (double_Pi) / 180.0f),
- static_cast<float> (blockComponent->getX()),
- static_cast<float> (blockComponent->getY())));
- }
- }
-
- /** Overridden from TopologySource::Listener, called when the topology changes */
- void topologyChanged() override
- {
- // Clear the array of Block components
- blockComponents.clear();
- masterBlockComponent = nullptr;
-
- // Get the current topology
- auto topology = topologySource.getCurrentTopology();
-
- // Create a BlockComponent object for each Block object and store a pointer to the master
- for (auto& block : topology.blocks)
- {
- if (auto* blockComponent = createBlockComponent (block))
- {
- addAndMakeVisible (blockComponents.add (blockComponent));
-
- if (blockComponent->block->isMasterBlock())
- masterBlockComponent = blockComponent;
- }
- }
-
- // Must have a master Block!
- if (topology.blocks.size() > 0)
- jassert (masterBlockComponent != nullptr);
-
- // Calculate the relative position and rotation for each Block
- positionBlocks (topology);
-
- // Update the display
- isInitialResized = true;
- resized();
- }
-
- private:
- /** Creates a BlockComponent object for a new Block and adds it to the content component */
- BlockComponent* createBlockComponent (Block::Ptr newBlock)
- {
- auto type = newBlock->getType();
-
- if (type == Block::lightPadBlock)
- return new LightpadComponent (newBlock);
-
- if (type == Block::loopBlock || type == Block::liveBlock)
- return new ControlBlockComponent (newBlock);
-
- // Should only be connecting a Lightpad or Control Block!
- jassertfalse;
- return nullptr;
- }
-
- /** Periodically updates the displayed BlockComponent tooltips */
- void timerCallback() override
- {
- for (auto c : blockComponents)
- c->updateStatsAndTooltip();
- }
-
- /** Zooms the display in or out */
- void buttonClicked (Button* button) override
- {
- #if JUCE_IOS
- if (button == &connectButton)
- {
- BluetoothMidiDevicePairingDialogue::open();
- return;
- }
- #endif
-
- if (button == &zoomOutButton || button == &zoomInButton)
- {
- blockUnitInPixels *= (button == &zoomOutButton ? 1.05f : 0.95f);
- resized();
- }
- }
-
- /** Calculates the position and rotation of each connected Block relative to the master Block */
- void positionBlocks (BlockTopology topology)
- {
- Array<BlockComponent*> blocksConnectedToMaster;
-
- float maxDelta = std::numeric_limits<float>::max();
- int maxLoops = 50;
-
- // Store all the connections to the master Block
- Array<BlockDeviceConnection> masterBlockConnections;
- for (auto connection : topology.connections)
- if (connection.device1 == masterBlockComponent->block->uid || connection.device2 == masterBlockComponent->block->uid)
- masterBlockConnections.add (connection);
-
- // Position all the Blocks that are connected to the master Block
- while (maxDelta > 0.001f && --maxLoops)
- {
- maxDelta = 0.0f;
-
- // Loop through each connection on the master Block
- for (auto connection : masterBlockConnections)
- {
- // Work out whether the master Block is device 1 or device 2 in the BlockDeviceConnection struct
- bool isDevice1 = true;
- if (masterBlockComponent->block->uid == connection.device2)
- isDevice1 = false;
-
- // Get the connected ports
- auto masterPort = isDevice1 ? connection.connectionPortOnDevice1 : connection.connectionPortOnDevice2;
- auto otherPort = isDevice1 ? connection.connectionPortOnDevice2 : connection.connectionPortOnDevice1;
-
- for (auto otherBlockComponent : blockComponents)
- {
- // Get the other block
- if (otherBlockComponent->block->uid == (isDevice1 ? connection.device2 : connection.device1))
- {
- blocksConnectedToMaster.addIfNotAlreadyThere (otherBlockComponent);
-
- // Get the rotation of the other Block relative to the master Block
- otherBlockComponent->rotation = getRotation (masterPort.edge, otherPort.edge);
-
- // Get the offsets for the connected ports
- auto masterBlockOffset = masterBlockComponent->getOffsetForPort (masterPort);
- auto otherBlockOffset = otherBlockComponent->topLeft + otherBlockComponent->getOffsetForPort (otherPort);
-
- // Work out the distance between the two connected ports
- auto delta = masterBlockOffset - otherBlockOffset;
-
- // Move the other block half the distance to the connection
- otherBlockComponent->topLeft += delta / 2.0f;
-
- // Work out whether we are close enough for the loop to end
- maxDelta = jmax (maxDelta, std::abs (delta.x), std::abs (delta.y));
- }
- }
- }
- }
-
- // Check if there are any Blocks that have not been positioned yet
- Array<BlockComponent*> unpositionedBlocks;
-
- for (auto blockComponent : blockComponents)
- if (blockComponent != masterBlockComponent && ! blocksConnectedToMaster.contains (blockComponent))
- unpositionedBlocks.add (blockComponent);
-
- if (unpositionedBlocks.size() > 0)
- {
- // Reset the loop conditions
- maxDelta = std::numeric_limits<float>::max();
- maxLoops = 50;
-
- // Position all the remaining Blocks
- while (maxDelta > 0.001f && --maxLoops)
- {
- maxDelta = 0.0f;
-
- // Loop through each unpositioned Block
- for (auto blockComponent : unpositionedBlocks)
- {
- // Store all the connections to this Block
- Array<BlockDeviceConnection> blockConnections;
- for (auto connection : topology.connections)
- if (connection.device1 == blockComponent->block->uid || connection.device2 == blockComponent->block->uid)
- blockConnections.add (connection);
-
- // Loop through each connection on this Block
- for (auto connection : blockConnections)
- {
- // Work out whether this Block is device 1 or device 2 in the BlockDeviceConnection struct
- bool isDevice1 = true;
- if (blockComponent->block->uid == connection.device2)
- isDevice1 = false;
-
- // Get the connected ports
- auto thisPort = isDevice1 ? connection.connectionPortOnDevice1 : connection.connectionPortOnDevice2;
- auto otherPort = isDevice1 ? connection.connectionPortOnDevice2 : connection.connectionPortOnDevice1;
-
- // Get the other Block
- for (auto otherBlockComponent : blockComponents)
- {
- if (otherBlockComponent->block->uid == (isDevice1 ? connection.device2 : connection.device1))
- {
- // Get the rotation
- int rotation = getRotation (otherPort.edge, thisPort.edge) + otherBlockComponent->rotation;
- if (rotation > 360)
- rotation -= 360;
-
- blockComponent->rotation = rotation;
-
- // Get the offsets for the connected ports
- auto otherBlockOffset = (otherBlockComponent->topLeft + otherBlockComponent->getOffsetForPort (otherPort));
- auto thisBlockOffset = (blockComponent->topLeft + blockComponent->getOffsetForPort (thisPort));
-
- // Work out the distance between the two connected ports
- auto delta = otherBlockOffset - thisBlockOffset;
-
- // Move this block half the distance to the connection
- blockComponent->topLeft += delta / 2.0f;
-
- // Work out whether we are close enough for the loop to end
- maxDelta = jmax (maxDelta, std::abs (delta.x), std::abs (delta.y));
- }
- }
- }
- }
- }
- }
- }
-
- /** Returns a rotation in degrees based on the connected edges of two blocks */
- int getRotation (Block::ConnectionPort::DeviceEdge staticEdge, Block::ConnectionPort::DeviceEdge rotatedEdge)
- {
- using edge = Block::ConnectionPort::DeviceEdge;
-
- switch (staticEdge)
- {
- case edge::north:
- {
- switch (rotatedEdge)
- {
- case edge::north:
- return 180;
- case edge::south:
- return 0;
- case edge::east:
- return 90;
- case edge::west:
- return 270;
- }
- }
- case edge::south:
- {
- switch (rotatedEdge)
- {
- case edge::north:
- return 0;
- case edge::south:
- return 180;
- case edge::east:
- return 270;
- case edge::west:
- return 90;
- }
- }
- case edge::east:
- {
- switch (rotatedEdge)
- {
- case edge::north:
- return 270;
- case edge::south:
- return 90;
- case edge::east:
- return 180;
- case edge::west:
- return 0;
- }
- }
-
- case edge::west:
- {
- switch (rotatedEdge)
- {
- case edge::north:
- return 90;
- case edge::south:
- return 270;
- case edge::east:
- return 0;
- case edge::west:
- return 180;
- }
- }
- }
-
- return 0;
- }
-
- //==============================================================================
- PhysicalTopologySource topologySource;
- OwnedArray<BlockComponent> blockComponents;
- BlockComponent* masterBlockComponent = nullptr;
-
- Label noBlocksLabel;
-
- TextButton zoomOutButton;
- TextButton zoomInButton;
-
- int blockUnitInPixels;
- bool isInitialResized;
-
- #if JUCE_IOS
- TextButton connectButton;
- #endif
-
- //==============================================================================
- JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainComponent)
- };
|