paulxstretch/deps/juce/extras/NetworkGraphicsDemo/Source/MasterComponent.h

423 lines
13 KiB
C
Raw Normal View History

/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
/**
Runs the master node, calls the demo to update the canvas, broadcasts those changes
out to slaves, and shows a view of all the clients to allow them to be dragged around.
*/
struct MasterContentComponent : public Component,
private Timer,
private OSCSender,
private OSCReceiver,
private OSCReceiver::Listener<OSCReceiver::MessageLoopCallback>
{
MasterContentComponent (PropertiesFile& props)
: properties (props)
{
setWantsKeyboardFocus (true);
createAllDemos (demos);
setContent (0);
setSize ((int) (15.0f * currentCanvas.getLimits().getWidth()),
(int) (15.0f * currentCanvas.getLimits().getHeight()));
if (! OSCSender::connect (getBroadcastIPAddress(), masterPortNumber))
error = "Master app OSC sender: network connection error.";
if (! OSCReceiver::connect (clientPortNumber))
error = "Master app OSC receiver: network connection error.";
OSCReceiver::addListener (this);
startTimerHz (30);
}
~MasterContentComponent() override
{
OSCReceiver::removeListener (this);
}
//==============================================================================
struct Client
{
String name, ipAddress;
float widthInches, heightInches;
Point<float> centre; // in inches
float scaleFactor;
};
Array<Client> clients;
void addClient (String name, String ipAddress, String areaDescription)
{
auto area = Rectangle<float>::fromString (areaDescription);
if (auto c = getClient (name))
{
c->ipAddress = ipAddress;
c->widthInches = area.getWidth();
c->heightInches = area.getHeight();
return;
}
DBG (name + " " + ipAddress);
removeClient (name);
clients.add ({ name, ipAddress, area.getWidth(), area.getHeight(), {}, 1.0f });
String lastX = properties.getValue ("lastX_" + name);
String lastY = properties.getValue ("lastY_" + name);
String lastScale = properties.getValue ("scale_" + name);
if (lastX.isEmpty() || lastY.isEmpty())
setClientCentre (name, { Random().nextFloat() * 10.0f,
Random().nextFloat() * 10.0f });
else
setClientCentre (name, Point<float> (lastX.getFloatValue(),
lastY.getFloatValue()));
if (lastScale.isNotEmpty())
setClientScale (name, lastScale.getFloatValue());
else
setClientScale (name, 1.0f);
updateDeviceComponents();
}
void removeClient (String name)
{
for (int i = clients.size(); --i >= 0;)
if (clients.getReference (0).name == name)
clients.remove (i);
updateDeviceComponents();
}
void setClientCentre (const String& name, Point<float> newCentre)
{
if (auto c = getClient (name))
{
newCentre = currentCanvas.getLimits().getConstrainedPoint (newCentre);
c->centre = newCentre;
properties.setValue ("lastX_" + name, String (newCentre.x));
properties.setValue ("lastY_" + name, String (newCentre.y));
startTimer (1);
}
}
float getClientScale (const String& name) const
{
if (auto c = getClient (name))
return c->scaleFactor;
return 1.0f;
}
void setClientScale (const String& name, float newScale)
{
if (auto c = getClient (name))
{
c->scaleFactor = jlimit (0.5f, 2.0f, newScale);
properties.setValue ("scale_" + name, String (newScale));
}
}
Point<float> getClientCentre (const String& name) const
{
if (auto c = getClient (name))
return c->centre;
return {};
}
Rectangle<float> getClientArea (const String& name) const
{
if (auto c = getClient (name))
return Rectangle<float> (c->widthInches, c->heightInches)
.withCentre (c->centre);
return {};
}
Rectangle<float> getActiveCanvasArea() const
{
Rectangle<float> r;
if (clients.size() > 0)
r = Rectangle<float> (1.0f, 1.0f).withCentre (clients.getReference (0).centre);
for (int i = 1; i < clients.size(); ++i)
r = r.getUnion (Rectangle<float> (1.0f, 1.0f).withCentre (clients.getReference (i).centre));
return r.expanded (6.0f);
}
int getContentIndex() const
{
return demos.indexOf (content);
}
void setContent (int demoIndex)
{
content = demos[demoIndex];
if (content != nullptr)
content->reset();
}
bool keyPressed (const KeyPress& key) override
{
if (key == KeyPress::spaceKey || key == KeyPress::rightKey || key == KeyPress::downKey)
{
setContent ((getContentIndex() + 1) % demos.size());
return true;
}
if (key == KeyPress::upKey || key == KeyPress::leftKey)
{
setContent ((getContentIndex() + demos.size() - 1) % demos.size());
return true;
}
return Component::keyPressed (key);
}
private:
//==============================================================================
void paint (Graphics& g) override
{
g.fillAll (Colours::black);
currentCanvas.draw (g, getLocalBounds().toFloat(), currentCanvas.getLimits());
if (error.isNotEmpty())
{
g.setColour (Colours::red);
g.setFont (20.0f);
g.drawText (error, getLocalBounds().reduced (10).removeFromBottom (80),
Justification::centredRight, true);
}
if (content != nullptr)
{
g.setColour (Colours::white);
g.setFont (17.0f);
g.drawText ("Demo: " + content->getName(),
getLocalBounds().reduced (10).removeFromTop (30),
Justification::centredLeft, true);
}
}
void resized() override
{
updateDeviceComponents();
}
void updateDeviceComponents()
{
for (int i = devices.size(); --i >= 0;)
if (getClient (devices.getUnchecked(i)->getName()) == nullptr)
devices.remove (i);
for (const auto& c : clients)
if (getDeviceComponent (c.name) == nullptr)
addAndMakeVisible (devices.add (new DeviceComponent (*this, c.name)));
for (auto d : devices)
d->setBounds (virtualSpaceToLocal (getClientArea (d->getName())).getSmallestIntegerContainer());
}
Point<float> virtualSpaceToLocal (Point<float> p) const
{
auto total = currentCanvas.getLimits();
return { (float) getWidth() * (p.x - total.getX()) / total.getWidth(),
(float) getHeight() * (p.y - total.getY()) / total.getHeight() };
}
Rectangle<float> virtualSpaceToLocal (Rectangle<float> p) const
{
return { virtualSpaceToLocal (p.getTopLeft()),
virtualSpaceToLocal (p.getBottomRight()) };
}
Point<float> localSpaceToVirtual (Point<float> p) const
{
auto total = currentCanvas.getLimits();
return { total.getX() + total.getWidth() * (p.x / (float) getWidth()),
total.getY() + total.getHeight() * (p.y / (float) getHeight()) };
}
//==============================================================================
struct DeviceComponent : public Component
{
DeviceComponent (MasterContentComponent& e, String name)
: Component (name), editor (e)
{
setMouseCursor (MouseCursor::DraggingHandCursor);
}
void paint (Graphics& g) override
{
g.fillAll (Colours::blue.withAlpha (0.4f));
g.setColour (Colours::white);
g.setFont (11.0f);
g.drawFittedText (getName(), getLocalBounds(), Justification::centred, 2);
}
void mouseDown (const MouseEvent&) override
{
dragStartLocation = editor.getClientCentre (getName());
}
void mouseDrag (const MouseEvent& e) override
{
editor.setClientCentre (getName(), dragStartLocation
+ editor.localSpaceToVirtual (e.getPosition().toFloat())
- editor.localSpaceToVirtual (e.getMouseDownPosition().toFloat()));
}
void mouseWheelMove (const MouseEvent&, const MouseWheelDetails& e) override
{
editor.setClientScale (getName(), editor.getClientScale (getName()) + 0.1f * e.deltaY);
}
void mouseDoubleClick (const MouseEvent&) override
{
editor.setClientScale (getName(), 1.0f);
}
MasterContentComponent& editor;
Point<float> dragStartLocation;
Rectangle<float> clientArea;
};
DeviceComponent* getDeviceComponent (const String& name) const
{
for (auto d : devices)
if (d->getName() == name)
return d;
return nullptr;
}
//==============================================================================
void broadcastNewCanvasState (const MemoryBlock& canvasData)
{
BlockPacketiser packetiser;
packetiser.createBlocksFromData (canvasData, 1000);
for (const auto& client : clients)
for (auto& b : packetiser.blocks)
sendToIPAddress (client.ipAddress, masterPortNumber, canvasStateOSCAddress, b);
}
void timerCallback() override
{
startTimerHz (30);
currentCanvas.reset();
updateCanvasInfo (currentCanvas);
{
std::unique_ptr<CanvasGeneratingContext> context (new CanvasGeneratingContext (currentCanvas));
Graphics g (*context);
if (content != nullptr)
content->generateCanvas (g, currentCanvas, getActiveCanvasArea());
}
broadcastNewCanvasState (currentCanvas.toMemoryBlock());
updateDeviceComponents();
repaint();
}
void updateCanvasInfo (SharedCanvasDescription& canvas)
{
canvas.backgroundColour = Colours::black;
for (const auto& c : clients)
canvas.clients.add ({ c.name, c.centre, c.scaleFactor });
}
const Client* getClient (const String& name) const
{
for (auto& c : clients)
if (c.name == name)
return &c;
return nullptr;
}
Client* getClient (const String& name)
{
return const_cast<Client*> (static_cast<const MasterContentComponent&> (*this).getClient (name));
}
//==============================================================================
void oscMessageReceived (const OSCMessage& message) override
{
auto address = message.getAddressPattern();
if (address.matches (newClientOSCAddress)) newClientOSCMessageReceived (message);
else if (address.matches (userInputOSCAddress)) userInputOSCMessageReceived (message);
}
void newClientOSCMessageReceived (const OSCMessage& message)
{
if (message.isEmpty() || ! message[0].isString())
return;
StringArray tokens = StringArray::fromTokens (message[0].getString(), ":", "");
addClient (tokens[0], tokens[1], tokens[2]);
}
void userInputOSCMessageReceived (const OSCMessage& message)
{
if (message.size() == 3 && message[0].isString() && message[1].isFloat32() && message[2].isFloat32())
{
content->handleTouch ({ message[1].getFloat32(),
message[2].getFloat32() });
}
}
//==============================================================================
AnimatedContent* content = nullptr;
PropertiesFile& properties;
OwnedArray<DeviceComponent> devices;
SharedCanvasDescription currentCanvas;
String error;
OwnedArray<AnimatedContent> demos;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MasterContentComponent)
};