migrating to the latest JUCE version

This commit is contained in:
2022-11-04 23:11:33 +01:00
committed by Nikolai Rodionov
parent 4257a0f8ba
commit faf8f18333
2796 changed files with 888518 additions and 784244 deletions

View File

@ -1,224 +1,224 @@
/*
==============================================================================
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.
==============================================================================
*/
/**
This component runs in a slave process, draws the part of the canvas that this
particular client covers, and updates itself when messages arrive from the master
containing new canvas states.
*/
class SlaveCanvasComponent : public Component,
private OSCSender,
private OSCReceiver,
private OSCReceiver::Listener<OSCReceiver::RealtimeCallback>,
private AsyncUpdater,
private Timer
{
public:
SlaveCanvasComponent (PropertiesFile& p, int windowIndex) : properties (p)
{
{
String uuidPropName ("UUID" + String (windowIndex));
clientName = properties.getValue (uuidPropName);
if (clientName.isEmpty())
{
clientName = "CLIENT_" + String (Random().nextInt (10000)).toUpperCase();
properties.setValue (uuidPropName, clientName);
}
}
setOpaque (true);
setSize (1500, 900);
if (! OSCSender::connect (getBroadcastIPAddress(), clientPortNumber))
error = "Client app OSC sender: network connection error.";
if (! OSCReceiver::connect (masterPortNumber))
error = "Client app OSC receiver: network connection error.";
OSCReceiver::addListener (this);
timerCallback();
startTimer (2000);
}
~SlaveCanvasComponent() override
{
OSCReceiver::removeListener (this);
}
private:
void mouseDrag (const MouseEvent& e) override
{
auto clientArea = getAreaInGlobalSpace();
if (! clientArea.isEmpty())
{
OSCMessage message (userInputOSCAddress);
message.addString (clientName);
message.addFloat32 (e.position.x * clientArea.getWidth() / (float) getWidth() + clientArea.getX());
message.addFloat32 (e.position.y * clientArea.getHeight() / (float) getHeight() + clientArea.getY());
send (message);
}
}
//==============================================================================
void oscMessageReceived (const OSCMessage& message) override
{
auto address = message.getAddressPattern();
if (address.matches (canvasStateOSCAddress))
canvasStateOSCMessageReceived (message);
}
struct NewStateMessage : public Message
{
NewStateMessage (const MemoryBlock& d) : data (d) {}
MemoryBlock data;
};
void canvasStateOSCMessageReceived (const OSCMessage& message)
{
if (message.isEmpty() || ! message[0].isBlob())
return;
if (packetiser.appendIncomingBlock (message[0].getBlob()))
{
const ScopedLock sl (canvasLock);
MemoryBlock newCanvasData;
if (packetiser.reassemble (newCanvasData))
{
MemoryInputStream i (newCanvasData.getData(), newCanvasData.getSize(), false);
canvas2.load (i);
triggerAsyncUpdate();
}
}
}
//==============================================================================
String getMachineInfoToDisplay() const
{
auto* display = Desktop::getInstance().getDisplays().getDisplayForPoint (getScreenBounds().getCentre());
return getOSName() + " " + String (display->dpi) + " " + String (display->scale);
}
static String getOSName()
{
#if JUCE_MAC
return "Mac OSX";
#elif JUCE_ANDROID
return "Android";
#elif JUCE_IOS
return "iOS";
#elif JUCE_WINDOWS
return "Windows";
#elif JUCE_LINUX
return "Linux";
#elif JUCE_BSD
return "BSD";
#endif
}
void paint (Graphics& g) override
{
g.fillAll (canvas.backgroundColour);
auto clientArea = getAreaInGlobalSpace();
if (clientArea.isEmpty())
{
g.setColour (Colours::red.withAlpha (0.5f));
g.setFont (20.0f);
g.drawText ("Not Connected", getLocalBounds(), Justification::centred, false);
return;
}
canvas.draw (g, getLocalBounds().toFloat(), clientArea);
g.setFont (Font (34.0f));
g.setColour (Colours::white.withAlpha (0.6f));
g.drawText (getMachineInfoToDisplay(),
getLocalBounds().reduced (10).removeFromBottom (20),
Justification::centredRight, true);
if (error.isNotEmpty())
{
g.setColour (Colours::red);
g.drawText (error, getLocalBounds().reduced (10).removeFromBottom (80),
Justification::centredRight, true);
}
}
Rectangle<float> getAreaInGlobalSpace() const
{
if (auto client = canvas.findClient (clientName))
{
auto screenBounds = getScreenBounds();
auto* display = Desktop::getInstance().getDisplays().getDisplayForPoint (screenBounds.getCentre());
return ((screenBounds - display->userArea.getCentre()).toFloat() / (client->scaleFactor * display->dpi / display->scale)) + client->centre;
}
return {};
}
Rectangle<float> getScreenAreaInGlobalSpace() const
{
if (auto client = canvas.findClient (clientName))
{
auto* display = Desktop::getInstance().getDisplays().getDisplayForPoint (getScreenBounds().getCentre());
return (display->userArea.toFloat() / (client->scaleFactor * display->dpi / display->scale)).withCentre (client->centre);
}
return {};
}
void timerCallback() override
{
send (newClientOSCAddress, clientName + ":" + IPAddress::getLocalAddress().toString()
+ ":" + getScreenAreaInGlobalSpace().toString());
}
void handleAsyncUpdate() override
{
const ScopedLock sl (canvasLock);
canvas.swapWith (canvas2);
repaint();
}
SharedCanvasDescription canvas, canvas2;
PropertiesFile& properties;
String clientName, error;
CriticalSection canvasLock;
BlockPacketiser packetiser;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SlaveCanvasComponent)
};
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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.
==============================================================================
*/
/**
This component runs in a client process, draws the part of the canvas that this
particular client covers, and updates itself when messages arrive from the master
containing new canvas states.
*/
class ClientCanvasComponent : public Component,
private OSCSender,
private OSCReceiver,
private OSCReceiver::Listener<OSCReceiver::RealtimeCallback>,
private AsyncUpdater,
private Timer
{
public:
ClientCanvasComponent (PropertiesFile& p, int windowIndex) : properties (p)
{
{
String uuidPropName ("UUID" + String (windowIndex));
clientName = properties.getValue (uuidPropName);
if (clientName.isEmpty())
{
clientName = "CLIENT_" + String (Random().nextInt (10000)).toUpperCase();
properties.setValue (uuidPropName, clientName);
}
}
setOpaque (true);
setSize (1500, 900);
if (! OSCSender::connect (getBroadcastIPAddress(), clientPortNumber))
error = "Client app OSC sender: network connection error.";
if (! OSCReceiver::connect (masterPortNumber))
error = "Client app OSC receiver: network connection error.";
OSCReceiver::addListener (this);
timerCallback();
startTimer (2000);
}
~ClientCanvasComponent() override
{
OSCReceiver::removeListener (this);
}
private:
void mouseDrag (const MouseEvent& e) override
{
auto clientArea = getAreaInGlobalSpace();
if (! clientArea.isEmpty())
{
OSCMessage message (userInputOSCAddress);
message.addString (clientName);
message.addFloat32 (e.position.x * clientArea.getWidth() / (float) getWidth() + clientArea.getX());
message.addFloat32 (e.position.y * clientArea.getHeight() / (float) getHeight() + clientArea.getY());
send (message);
}
}
//==============================================================================
void oscMessageReceived (const OSCMessage& message) override
{
auto address = message.getAddressPattern();
if (address.matches (canvasStateOSCAddress))
canvasStateOSCMessageReceived (message);
}
struct NewStateMessage : public Message
{
NewStateMessage (const MemoryBlock& d) : data (d) {}
MemoryBlock data;
};
void canvasStateOSCMessageReceived (const OSCMessage& message)
{
if (message.isEmpty() || ! message[0].isBlob())
return;
if (packetiser.appendIncomingBlock (message[0].getBlob()))
{
const ScopedLock sl (canvasLock);
MemoryBlock newCanvasData;
if (packetiser.reassemble (newCanvasData))
{
MemoryInputStream i (newCanvasData.getData(), newCanvasData.getSize(), false);
canvas2.load (i);
triggerAsyncUpdate();
}
}
}
//==============================================================================
String getMachineInfoToDisplay() const
{
auto* display = Desktop::getInstance().getDisplays().getDisplayForPoint (getScreenBounds().getCentre());
return getOSName() + " " + String (display->dpi) + " " + String (display->scale);
}
static String getOSName()
{
#if JUCE_MAC
return "Mac OSX";
#elif JUCE_ANDROID
return "Android";
#elif JUCE_IOS
return "iOS";
#elif JUCE_WINDOWS
return "Windows";
#elif JUCE_LINUX
return "Linux";
#elif JUCE_BSD
return "BSD";
#endif
}
void paint (Graphics& g) override
{
g.fillAll (canvas.backgroundColour);
auto clientArea = getAreaInGlobalSpace();
if (clientArea.isEmpty())
{
g.setColour (Colours::red.withAlpha (0.5f));
g.setFont (20.0f);
g.drawText ("Not Connected", getLocalBounds(), Justification::centred, false);
return;
}
canvas.draw (g, getLocalBounds().toFloat(), clientArea);
g.setFont (Font (34.0f));
g.setColour (Colours::white.withAlpha (0.6f));
g.drawText (getMachineInfoToDisplay(),
getLocalBounds().reduced (10).removeFromBottom (20),
Justification::centredRight, true);
if (error.isNotEmpty())
{
g.setColour (Colours::red);
g.drawText (error, getLocalBounds().reduced (10).removeFromBottom (80),
Justification::centredRight, true);
}
}
Rectangle<float> getAreaInGlobalSpace() const
{
if (auto client = canvas.findClient (clientName))
{
auto screenBounds = getScreenBounds();
auto* display = Desktop::getInstance().getDisplays().getDisplayForPoint (screenBounds.getCentre());
return ((screenBounds - display->userArea.getCentre()).toFloat() / (client->scaleFactor * display->dpi / display->scale)) + client->centre;
}
return {};
}
Rectangle<float> getScreenAreaInGlobalSpace() const
{
if (auto client = canvas.findClient (clientName))
{
auto* display = Desktop::getInstance().getDisplays().getDisplayForPoint (getScreenBounds().getCentre());
return (display->userArea.toFloat() / (client->scaleFactor * display->dpi / display->scale)).withCentre (client->centre);
}
return {};
}
void timerCallback() override
{
send (newClientOSCAddress, clientName + ":" + IPAddress::getLocalAddress().toString()
+ ":" + getScreenAreaInGlobalSpace().toString());
}
void handleAsyncUpdate() override
{
const ScopedLock sl (canvasLock);
canvas.swapWith (canvas2);
repaint();
}
SharedCanvasDescription canvas, canvas2;
PropertiesFile& properties;
String clientName, error;
CriticalSection canvasLock;
BlockPacketiser packetiser;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ClientCanvasComponent)
};

File diff suppressed because it is too large Load Diff

View File

@ -1,157 +1,157 @@
/*
==============================================================================
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.
==============================================================================
*/
#include <JuceHeader.h>
namespace
{
String getBroadcastIPAddress()
{
return IPAddress::getLocalAddress().toString().upToLastOccurrenceOf (".", false, false) + ".255";
}
static const int masterPortNumber = 9001; // the UDP port the master sends on / the clients receive.
static const int clientPortNumber = 9002; // the UDP port the clients send on / the master receives.
static const String canvasStateOSCAddress = "/juce/nfd/canvasState";
static const String newClientOSCAddress = "/juce/nfd/newClient";
static const String userInputOSCAddress = "/juce/nfd/userInput";
}
#include "SharedCanvas.h"
#include "SlaveComponent.h"
#include "Demos.h"
#include "MasterComponent.h"
//==============================================================================
class NetworkGraphicsDemoApplication : public JUCEApplication
{
public:
NetworkGraphicsDemoApplication() : properties (getPropertyFileOptions())
{}
const String getApplicationName() override { return ProjectInfo::projectName; }
const String getApplicationVersion() override { return ProjectInfo::versionString; }
bool moreThanOneInstanceAllowed() override { return true; }
void anotherInstanceStarted (const String&) override {}
//==============================================================================
void initialise (const String& commandLine) override
{
#if ! JUCE_IOS && ! JUCE_ANDROID
// Run as the master if we have a command-line flag "master" or if the exe itself
// has been renamed to include the word "master"..
bool isMaster = commandLine.containsIgnoreCase ("master")
|| File::getSpecialLocation (File::currentApplicationFile)
.getFileName().containsIgnoreCase ("master");
if (isMaster)
mainWindows.add (new MainWindow (properties));
#endif
mainWindows.add (new MainWindow (properties, 0));
Desktop::getInstance().setScreenSaverEnabled (false);
}
void shutdown() override
{
mainWindows.clear();
properties.saveIfNeeded();
}
void systemRequestedQuit() override
{
quit();
}
//==============================================================================
struct MainWindow : public DocumentWindow
{
explicit MainWindow (PropertiesFile& props)
: DocumentWindow ("JUCE Networked Graphics Demo - Master", Colours::white, DocumentWindow::allButtons)
{
setUsingNativeTitleBar (true);
setContentOwned (new MasterContentComponent (props), true);
setBounds (100, 50, getWidth(), getHeight());
setResizable (true, false);
setVisible (true);
glContext.attachTo (*this);
}
MainWindow (PropertiesFile& props, int windowIndex)
: DocumentWindow ("JUCE Networked Graphics Demo", Colours::black, DocumentWindow::allButtons)
{
setUsingNativeTitleBar (true);
setContentOwned (new SlaveCanvasComponent (props, windowIndex), true);
setBounds (500, 100, getWidth(), getHeight());
setResizable (true, false);
setVisible (true);
#if ! JUCE_IOS
glContext.attachTo (*this);
#endif
#if JUCE_IOS || JUCE_ANDROID
setFullScreen (true);
#endif
}
~MainWindow() override
{
glContext.detach();
}
void closeButtonPressed() override
{
JUCEApplication::getInstance()->systemRequestedQuit();
}
OpenGLContext glContext;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainWindow)
};
static PropertiesFile::Options getPropertyFileOptions()
{
PropertiesFile::Options o;
o.applicationName = "JUCE Network Graphics Demo";
o.filenameSuffix = ".settings";
o.folderName = "JUCE Network Graphics Demo";
o.osxLibrarySubFolder = "Application Support/JUCE Network Graphics Demo";
o.millisecondsBeforeSaving = 2000;
return o;
}
PropertiesFile properties;
OwnedArray<MainWindow> mainWindows;
};
//==============================================================================
// This macro generates the main() routine that launches the app.
START_JUCE_APPLICATION (NetworkGraphicsDemoApplication)
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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.
==============================================================================
*/
#include <JuceHeader.h>
namespace
{
String getBroadcastIPAddress()
{
return IPAddress::getLocalAddress().toString().upToLastOccurrenceOf (".", false, false) + ".255";
}
static const int masterPortNumber = 9001; // the UDP port the master sends on / the clients receive.
static const int clientPortNumber = 9002; // the UDP port the clients send on / the master receives.
static const String canvasStateOSCAddress = "/juce/nfd/canvasState";
static const String newClientOSCAddress = "/juce/nfd/newClient";
static const String userInputOSCAddress = "/juce/nfd/userInput";
}
#include "SharedCanvas.h"
#include "ClientComponent.h"
#include "Demos.h"
#include "MasterComponent.h"
//==============================================================================
class NetworkGraphicsDemoApplication : public JUCEApplication
{
public:
NetworkGraphicsDemoApplication() : properties (getPropertyFileOptions())
{}
const String getApplicationName() override { return ProjectInfo::projectName; }
const String getApplicationVersion() override { return ProjectInfo::versionString; }
bool moreThanOneInstanceAllowed() override { return true; }
void anotherInstanceStarted (const String&) override {}
//==============================================================================
void initialise (const String& commandLine) override
{
#if ! JUCE_IOS && ! JUCE_ANDROID
// Run as the master if we have a command-line flag "master" or if the exe itself
// has been renamed to include the word "master"..
bool isMaster = commandLine.containsIgnoreCase ("master")
|| File::getSpecialLocation (File::currentApplicationFile)
.getFileName().containsIgnoreCase ("master");
if (isMaster)
mainWindows.add (new MainWindow (properties));
#endif
mainWindows.add (new MainWindow (properties, 0));
Desktop::getInstance().setScreenSaverEnabled (false);
}
void shutdown() override
{
mainWindows.clear();
properties.saveIfNeeded();
}
void systemRequestedQuit() override
{
quit();
}
//==============================================================================
struct MainWindow : public DocumentWindow
{
explicit MainWindow (PropertiesFile& props)
: DocumentWindow ("JUCE Networked Graphics Demo - Master", Colours::white, DocumentWindow::allButtons)
{
setUsingNativeTitleBar (true);
setContentOwned (new MasterContentComponent (props), true);
setBounds (100, 50, getWidth(), getHeight());
setResizable (true, false);
setVisible (true);
glContext.attachTo (*this);
}
MainWindow (PropertiesFile& props, int windowIndex)
: DocumentWindow ("JUCE Networked Graphics Demo", Colours::black, DocumentWindow::allButtons)
{
setUsingNativeTitleBar (true);
setContentOwned (new ClientCanvasComponent (props, windowIndex), true);
setBounds (500, 100, getWidth(), getHeight());
setResizable (true, false);
setVisible (true);
#if ! JUCE_IOS
glContext.attachTo (*this);
#endif
#if JUCE_IOS || JUCE_ANDROID
setFullScreen (true);
#endif
}
~MainWindow() override
{
glContext.detach();
}
void closeButtonPressed() override
{
JUCEApplication::getInstance()->systemRequestedQuit();
}
OpenGLContext glContext;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainWindow)
};
static PropertiesFile::Options getPropertyFileOptions()
{
PropertiesFile::Options o;
o.applicationName = "JUCE Network Graphics Demo";
o.filenameSuffix = ".settings";
o.folderName = "JUCE Network Graphics Demo";
o.osxLibrarySubFolder = "Application Support/JUCE Network Graphics Demo";
o.millisecondsBeforeSaving = 2000;
return o;
}
PropertiesFile properties;
OwnedArray<MainWindow> mainWindows;
};
//==============================================================================
// This macro generates the main() routine that launches the app.
START_JUCE_APPLICATION (NetworkGraphicsDemoApplication)

View File

@ -1,422 +1,422 @@
/*
==============================================================================
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)
};
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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)
};

File diff suppressed because it is too large Load Diff