git subrepo clone --branch=sono6good https://github.com/essej/JUCE.git deps/juce

subrepo:
  subdir:   "deps/juce"
  merged:   "b13f9084e"
upstream:
  origin:   "https://github.com/essej/JUCE.git"
  branch:   "sono6good"
  commit:   "b13f9084e"
git-subrepo:
  version:  "0.4.3"
  origin:   "https://github.com/ingydotnet/git-subrepo.git"
  commit:   "2f68596"
This commit is contained in:
essej
2022-04-18 17:51:22 -04:00
parent 63e175fee6
commit 25bd5d8adb
3210 changed files with 1045392 additions and 0 deletions
deps/juce
.gitattributes
.github
.gitignore.gitlab-ci.yml.gitrepoBREAKING-CHANGES.txtCMakeLists.txtChangeList.txtLICENSE.mdREADME.md
docs
examples
Assets
Audio
CMake
CMakeLists.txt
DSP
DemoRunner
Builds
Android
app
CMakeLists.txtbuild.gradle
src
debug
res
main
AndroidManifest.xml
assets
res
drawable-hdpi
drawable-ldpi
drawable-mdpi
drawable-xhdpi
release
res
build.gradle
gradle
gradlewgradlew.batsettings.gradle
LinuxMakefile
MacOSX
VisualStudio2015
VisualStudio2017
VisualStudio2019
iOS
CMakeLists.txtDemoRunner.jucer
JuceLibraryCode
Source
GUI
Plugins
Utilities
extras
AudioPerformanceTest
AudioPerformanceTest.jucer
Builds
CMakeLists.txt
JuceLibraryCode
Source
AudioPluginHost
AudioPluginHost.jucer
Builds
Android
app
CMakeLists.txtbuild.gradle
src
debug
res
main
AndroidManifest.xml
assets
res
drawable-hdpi
drawable-ldpi
drawable-mdpi
drawable-xhdpi
release
res
build.gradle
gradle
gradlewgradlew.batsettings.gradle
LinuxMakefile
MacOSX
VisualStudio2015
VisualStudio2017
VisualStudio2019
iOS
CMakeLists.txt
JuceLibraryCode
Source
BinaryBuilder
Build
CMakeLists.txt
NetworkGraphicsDemo
Builds
CMakeLists.txt
JuceLibraryCode
NetworkGraphicsDemo.jucerREADME.txt
Source
Projucer
Builds
CMakeLists.txt
JuceLibraryCode
Projucer.jucer
Source
Application
BinaryData
CodeEditor
ComponentEditor
Components
Documents
PaintElements
Properties
UI
jucer_BinaryResources.cppjucer_BinaryResources.hjucer_ComponentLayout.cppjucer_ComponentLayout.hjucer_GeneratedCode.cppjucer_GeneratedCode.hjucer_JucerDocument.cppjucer_JucerDocument.hjucer_ObjectTypes.cppjucer_ObjectTypes.hjucer_PaintRoutine.cppjucer_PaintRoutine.hjucer_UtilityFunctions.h
Licenses
LiveBuildEngine
Project
ProjectSaving
Settings
Utility
UnitTestRunner
WindowsDLL
modules
CMakeLists.txt
juce_analytics
juce_audio_basics
audio_play_head
buffers
juce_audio_basics.cppjuce_audio_basics.hjuce_audio_basics.mm
midi
mpe
native
sources
synthesisers
utilities
juce_audio_devices
audio_io
juce_audio_devices.cppjuce_audio_devices.hjuce_audio_devices.mm
midi_io
native
java
app
juce_android_Audio.cppjuce_android_HighPerformanceAudioHelpers.hjuce_android_Midi.cppjuce_android_Oboe.cppjuce_android_OpenSL.cppjuce_ios_Audio.cppjuce_ios_Audio.hjuce_linux_ALSA.cppjuce_linux_Bela.cppjuce_linux_JackAudio.cppjuce_linux_Midi.cppjuce_mac_CoreAudio.cppjuce_mac_CoreMidi.mmjuce_win32_ASIO.cppjuce_win32_DirectSound.cppjuce_win32_Midi.cppjuce_win32_WASAPI.cpp
oboe
CMakeLists.txtLICENSEREADME.md
include
src
aaudio
common
fifo
flowgraph
opensles
sources
juce_audio_formats
codecs
flac
juce_AiffAudioFormat.cppjuce_AiffAudioFormat.hjuce_CoreAudioFormat.cppjuce_CoreAudioFormat.hjuce_FlacAudioFormat.cppjuce_FlacAudioFormat.hjuce_LAMEEncoderAudioFormat.cppjuce_LAMEEncoderAudioFormat.hjuce_MP3AudioFormat.cppjuce_MP3AudioFormat.hjuce_OggVorbisAudioFormat.cppjuce_OggVorbisAudioFormat.hjuce_WavAudioFormat.cppjuce_WavAudioFormat.hjuce_WindowsMediaAudioFormat.cppjuce_WindowsMediaAudioFormat.h
oggvorbis
format
juce_audio_formats.cppjuce_audio_formats.hjuce_audio_formats.mm
sampler
juce_audio_plugin_client
AAX
AU
AUResources.r
RTAS
Standalone
Unity
VST
VST3
juce_audio_plugin_client.hjuce_audio_plugin_client_AAX.cppjuce_audio_plugin_client_AAX.mmjuce_audio_plugin_client_AU.rjuce_audio_plugin_client_AU_1.mmjuce_audio_plugin_client_AU_2.mmjuce_audio_plugin_client_AUv3.mmjuce_audio_plugin_client_RTAS.rjuce_audio_plugin_client_RTAS_1.cppjuce_audio_plugin_client_RTAS_2.cppjuce_audio_plugin_client_RTAS_3.cppjuce_audio_plugin_client_RTAS_4.cppjuce_audio_plugin_client_RTAS_utils.cppjuce_audio_plugin_client_RTAS_utils.mmjuce_audio_plugin_client_Standalone.cppjuce_audio_plugin_client_Unity.cppjuce_audio_plugin_client_VST2.cppjuce_audio_plugin_client_VST3.cppjuce_audio_plugin_client_VST_utils.mmjuce_audio_plugin_client_utils.cpp
utility
juce_audio_processors
format
format_types
VST3_SDK
JUCE_README.mdLICENSE.txtREADME.mdVST3_License_Agreement.pdfVST3_Usage_Guidelines.pdf
base
pluginterfaces
public.sdk
juce_AU_Shared.hjuce_AudioUnitPluginFormat.hjuce_AudioUnitPluginFormat.mmjuce_LADSPAPluginFormat.cppjuce_LADSPAPluginFormat.hjuce_LegacyAudioParameter.cppjuce_VST3Common.hjuce_VST3Headers.hjuce_VST3PluginFormat.cppjuce_VST3PluginFormat.hjuce_VSTCommon.hjuce_VSTMidiEventList.hjuce_VSTPluginFormat.cppjuce_VSTPluginFormat.h
juce_audio_processors.cppjuce_audio_processors.hjuce_audio_processors.mm
processors
scanning
utilities
juce_audio_utils
juce_box2d
box2d
Box2D.h
Collision
Common
Dynamics
README.txt
Rope
juce_box2d.cppjuce_box2d.h
utils
juce_core
containers
files
javascript
juce_core.cppjuce_core.hjuce_core.mm
logging
maths
memory
misc
native
network
streams
system
text
threads
time
unit_tests
xml
zip
juce_cryptography
juce_data_structures
juce_dsp
containers
filter_design
frequency
juce_dsp.cppjuce_dsp.hjuce_dsp.mm
maths
native
processors
widgets
juce_events
juce_graphics
colour
contexts
effects
fonts
geometry
image_formats
images
juce_graphics.cppjuce_graphics.hjuce_graphics.mm
native
placement
juce_gui_basics
accessibility
application
buttons
commands
components
desktop
drawables
filebrowser
juce_gui_basics.cppjuce_gui_basics.hjuce_gui_basics.mm
keyboard
layout
lookandfeel
menus
misc
mouse
native
accessibility
java
javaopt
juce_MultiTouchMapper.hjuce_ScopedDPIAwarenessDisabler.hjuce_android_ContentSharer.cppjuce_android_FileChooser.cppjuce_android_Windowing.cppjuce_common_MimeTypes.cppjuce_ios_ContentSharer.cppjuce_ios_FileChooser.mmjuce_ios_UIViewComponentPeer.mmjuce_ios_Windowing.mmjuce_linux_FileChooser.cppjuce_linux_Windowing.cppjuce_mac_FileChooser.mmjuce_mac_MainMenu.mmjuce_mac_MouseCursor.mmjuce_mac_NSViewComponentPeer.mmjuce_mac_Windowing.mmjuce_win32_DragAndDrop.cppjuce_win32_FileChooser.cppjuce_win32_ScopedThreadDPIAwarenessSetter.hjuce_win32_Windowing.cpp
x11
positioning
properties
widgets
windows
juce_gui_extra
code_editor
documents
embedding
juce_gui_extra.cppjuce_gui_extra.hjuce_gui_extra.mm
misc
native
juce_opengl
juce_osc
juce_product_unlocking
juce_video

@ -0,0 +1,504 @@
/*
==============================================================================
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.
==============================================================================
*/
struct BlankCanvas : public AnimatedContent
{
String getName() const override { return "Blank Canvas"; }
void reset() override {}
void handleTouch (Point<float>) override {}
void generateCanvas (Graphics&, SharedCanvasDescription&, Rectangle<float>) override {}
};
//==============================================================================
struct GridLines : public AnimatedContent
{
String getName() const override { return "Grid Lines"; }
void reset() override {}
void handleTouch (Point<float>) override {}
void generateCanvas (Graphics& g, SharedCanvasDescription& canvas, Rectangle<float>) override
{
auto limits = canvas.getLimits();
float lineThickness = 0.1f;
g.setColour (Colours::blue);
g.drawRect (canvas.getLimits(), lineThickness);
for (float y = limits.getY(); y < limits.getBottom(); y += 2.0f)
g.drawLine (limits.getX(), y, limits.getRight(), y, lineThickness);
for (float x = limits.getX(); x < limits.getRight(); x += 2.0f)
g.drawLine (x, limits.getY(), x, limits.getBottom(), lineThickness);
g.setColour (Colours::darkred);
g.drawLine (limits.getX(), limits.getCentreY(), limits.getRight(), limits.getCentreY(), lineThickness);
g.drawLine (limits.getCentreX(), limits.getY(), limits.getCentreX(), limits.getBottom(), lineThickness);
g.setColour (Colours::lightgrey);
g.drawLine (limits.getX(), limits.getY(), limits.getRight(), limits.getBottom(), lineThickness);
g.drawLine (limits.getX(), limits.getBottom(), limits.getRight(), limits.getY(), lineThickness);
}
};
//==============================================================================
struct BackgroundLogo : public AnimatedContent
{
BackgroundLogo()
{
static const char logoData[] = R"blahblah(
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 239.2 239.2" enable-background="new 0 0 239.2 239.2" xml:space="preserve">
<path fill="#6CC04A" d="M118.8,201.3c-44.6,0-81-36.3-81-81s36.3-81,81-81s81,36.3,81,81S163.4,201.3,118.8,201.3z M118.8,44.8c-41.7,0-75.6,33.9-75.6,75.6s33.9,75.6,75.6,75.6s75.6-33.9,75.6-75.6S160.4,44.8,118.8,44.8z"/>
<path fill="#3B5CAD" d="M182.6,117.6c1.4,0,2.7-0.5,3.7-1.5c1.1-1.1,1.6-2.5,1.4-4c-1.5-12.7-6.5-24.7-14.4-34.8c-1-1.2-2.3-1.9-3.8-1.9c-1.3,0-2.6,0.5-3.6,1.5l-39,39c-0.6,0.6-0.2,1.6,0.7,1.6L182.6,117.6z"/>
<path fill="#E73E51" d="M169.5,165.2L169.5,165.2c1.5,0,2.8-0.7,3.8-1.9c7.9-10.1,12.9-22.1,14.4-34.8c0.2-1.5-0.3-2.9-1.4-4c-1-1-2.3-1.5-3.7-1.5l-55,0c-0.9,0-1.3,1-0.7,1.6l39,39C166.9,164.7,168.2,165.2,169.5,165.2z"/>
<path fill="#E67E3C" d="M122.9,188L122.9,188c1,1,2.5,1.5,4,1.3c12.7-1.5,24.8-6.5,34.8-14.4c1.2-0.9,1.8-2.3,1.9-3.8c0-1.4-0.6-2.7-1.6-3.7l-38.9-38.9c-0.6-0.6-1.6-0.2-1.6,0.7l0,55.2C121.4,185.8,122,187,122.9,188z"/>
<path fill="#F0E049" d="M68,75.4c-1.5,0-2.8,0.7-3.8,1.9c-7.9,10.1-12.9,22.1-14.4,34.8c-0.2,1.5,0.3,2.9,1.4,4c1,1,2.3,1.5,3.7,1.5l55,0c0.9,0,1.3-1,0.7-1.6l-39-39C70.6,76,69.3,75.4,68,75.4z"/>
<path fill="#D5D755" d="M114.6,52.7c-1-1-2.5-1.5-4-1.3c-12.7,1.5-24.8,6.5-34.8,14.4c-1.2,0.9-1.8,2.3-1.9,3.8c0,1.4,0.6,2.7,1.6,3.7l38.9,38.9c0.6,0.6,1.6,0.2,1.6-0.7l0-55.2C116.1,54.9,115.5,53.6,114.6,52.7z"/>
<path fill="#9CB6D3" d="M163.7,69.6c0-1.5-0.7-2.8-1.9-3.8c-10.1-7.9-22.1-12.9-34.8-14.4c-1.5-0.2-2.9,0.3-4,1.4c-1,1-1.5,2.3-1.5,3.7l0,55c0,0.9,1,1.3,1.6,0.7l39-39C163.1,72.1,163.7,70.9,163.7,69.6z"/>
<path fill="#F5BD47" d="M109.9,123l-55,0c-1.4,0-2.7,0.5-3.7,1.5c-1.1,1.1-1.6,2.5-1.4,4c1.5,12.7,6.5,24.7,14.4,34.8c1,1.2,2.3,1.9,3.8,1.9c1.3,0,2.6-0.5,3.5-1.5c0,0,0,0,0,0l39-39C111.2,124,110.8,123,109.9,123z"/>
<path fill="#F19F53" d="M114.4,128.5l-38.9,38.9c-1,1-1.6,2.3-1.6,3.7c0,1.5,0.7,2.9,1.9,3.8c10,7.9,22.1,12.9,34.8,14.4c1.6,0.2,3-0.3,4-1.3c0.9-0.9,1.4-2.2,1.4-3.6c0,0,0,0,0,0l0-55.2C116.1,128.3,115,127.9,114.4,128.5z"/>
</svg>
)blahblah";
logo = Drawable::createFromSVG (*parseXML (logoData));
}
String getName() const override { return "Background Image"; }
void reset() override {}
void handleTouch (Point<float>) override {}
void generateCanvas (Graphics& g, SharedCanvasDescription& canvas, Rectangle<float>) override
{
logo->drawWithin (g, canvas.getLimits().reduced (3.0f), RectanglePlacement (RectanglePlacement::centred), 0.6f);
}
std::unique_ptr<Drawable> logo;
};
//==============================================================================
struct FlockDemo : public BackgroundLogo
{
String getName() const override { return "Flock"; }
void setNumBirds (int numBirds)
{
BackgroundLogo::reset();
birds.clear();
for (int i = numBirds; --i >= 0;)
birds.add ({});
centreOfGravity = {};
lastGravityMove = {};
fakeMouseTouchLengthToRun = 0;
fakeMouseTouchPosition = {};
fakeMouseTouchVelocity = {};
}
void reset() override
{
BackgroundLogo::reset();
setNumBirds (100);
}
void generateCanvas (Graphics& g, SharedCanvasDescription& canvas, Rectangle<float> activeArea) override
{
BackgroundLogo::generateCanvas (g, canvas, activeArea);
if (Time::getCurrentTime() > lastGravityMove + RelativeTime::seconds (0.5))
{
if (fakeMouseTouchLengthToRun > 0)
{
--fakeMouseTouchLengthToRun;
fakeMouseTouchPosition += fakeMouseTouchVelocity;
centreOfGravity = fakeMouseTouchPosition;
}
else
{
centreOfGravity = {};
if (rng.nextInt (300) == 2 && canvas.clients.size() > 0)
{
fakeMouseTouchLengthToRun = 50;
fakeMouseTouchPosition = canvas.clients.getReference (rng.nextInt (canvas.clients.size())).centre;
fakeMouseTouchVelocity = { rng.nextFloat() * 0.3f - 0.15f,
rng.nextFloat() * 0.3f - 0.15f };
}
}
}
g.setColour (Colours::white.withAlpha (0.2f));
if (! centreOfGravity.isOrigin())
g.fillEllipse (centreOfGravity.getX() - 1.0f, centreOfGravity.getY() - 1.0f, 2.0f, 2.0f);
for (int i = 0; i < birds.size(); ++i)
for (int j = i + 1; j < birds.size(); ++j)
attractBirds (birds.getReference(i), birds.getReference(j));
for (auto& b : birds)
{
if (! centreOfGravity.isOrigin())
b.move (centreOfGravity, 0.4f);
b.update();
b.draw (g);
b.bounceOffEdges (canvas.getLimits().expanded (1.0f));
}
for (int i = rings.size(); --i >= 0;)
{
if (rings.getReference(i).update())
rings.getReference(i).draw (g);
else
rings.remove (i);
}
}
bool isRingNear (Point<float> p) const
{
for (auto& r : rings)
if (r.centre.getDistanceFrom (p) < 1.0f)
return true;
return false;
}
void handleTouch (Point<float> position) override
{
lastGravityMove = Time::getCurrentTime();
centreOfGravity = position;
fakeMouseTouchLengthToRun = 0;
if (! isRingNear (position))
rings.add ({ position, 1.0f, 0.5f });
}
//==============================================================================
struct Bird
{
Bird()
{
Random randGen;
pos.x = randGen.nextFloat() * 10.0f - 5.0f;
pos.y = randGen.nextFloat() * 10.0f - 5.0f;
velocity.x = randGen.nextFloat() * 0.001f;
velocity.y = randGen.nextFloat() * 0.001f;
colour = Colour::fromHSV (randGen.nextFloat(), 0.2f, 0.9f, randGen.nextFloat() * 0.4f + 0.2f);
shape.addTriangle (0.0f, 0.0f, -0.3f, 1.0f, 0.3f, 1.0f);
shape = shape.createPathWithRoundedCorners (0.2f);
shape.applyTransform (AffineTransform::scale (randGen.nextFloat() + 1.0f));
}
Point<float> pos, velocity, acc;
Colour colour;
Path shape;
void move (Point<float> target, float strength)
{
auto r = target - pos;
float rSquared = jmax (0.1f, (r.x * r.x) + (r.y * r.y));
if (rSquared > 1.0f)
velocity += (r * strength / rSquared);
acc = {};
}
void accelerate (Point<float> acceleration)
{
acc += acceleration;
}
void bounceOffEdges (Rectangle<float> limits)
{
if (pos.x < limits.getX()) { velocity.x = std::abs (velocity.x); acc = {}; }
if (pos.x > limits.getRight()) { velocity.x = -std::abs (velocity.x); acc = {}; }
if (pos.y < limits.getY()) { velocity.y = std::abs (velocity.y); acc = {}; }
if (pos.y > limits.getBottom()) { velocity.y = -std::abs (velocity.y); acc = {}; }
}
void update()
{
velocity += acc;
float length = velocity.getDistanceFromOrigin();
const float maxSpeed = 0.5f;
if (length > maxSpeed)
velocity = getVectorWithLength (velocity, maxSpeed);
pos += velocity;
}
void draw (Graphics& g)
{
g.setColour (colour);
g.fillPath (shape, AffineTransform::rotation (Point<float>().getAngleToPoint (velocity)).translated (pos));
}
};
static Point<float> getVectorWithLength (Point<float> v, float newLength)
{
return v * (newLength / v.getDistanceFromOrigin());
}
static void attractBirds (Bird& b1, Bird& b2)
{
auto delta = b1.pos - b2.pos;
const float zoneRadius = 10.0f;
const float low = 0.4f;
const float high = 0.65f;
const float strength = 0.01f;
const float distanceSquared = (delta.x * delta.x) * (delta.y * delta.y);
if (distanceSquared < zoneRadius * zoneRadius && distanceSquared > 0.01f)
{
float proportion = distanceSquared / (zoneRadius * zoneRadius);
if (proportion < low)
{
const float F = (low / proportion - 1.0f) * strength * 0.003f;
delta = getVectorWithLength (delta, F);
b1.accelerate (delta);
b2.accelerate (-delta);
}
else if (proportion < high)
{
const float regionSize = high - low;
const float adjustedProportion = (proportion - low) / regionSize;
const float F = (0.5f - std::cos (adjustedProportion * MathConstants<float>::twoPi) * 0.5f + 0.5f) * strength;
b1.accelerate (getVectorWithLength (b2.velocity, F));
b2.accelerate (getVectorWithLength (b1.velocity, F));
}
else
{
const float regionSize = 1.0f - high;
const float adjustedProportion = (proportion - high) / regionSize;
const float F = (0.5f - std::cos (adjustedProportion * MathConstants<float>::twoPi) * 0.5f + 0.5f) * strength;
delta = getVectorWithLength (delta, F);
b1.accelerate (-delta);
b2.accelerate (delta);
}
}
}
Random rng;
Array<Bird> birds;
Point<float> centreOfGravity;
Time lastGravityMove;
int fakeMouseTouchLengthToRun = 0;
Point<float> fakeMouseTouchPosition, fakeMouseTouchVelocity;
//==============================================================================
struct Ring
{
Point<float> centre;
float diameter, opacity;
bool update()
{
diameter += 0.7f;
opacity -= 0.01f;
return opacity > 0;
}
void draw (Graphics& g)
{
const float thickness = 0.2f;
auto r = Rectangle<float> (diameter, diameter).withCentre (centre);
Path p;
p.addEllipse (r);
p.addEllipse (r.reduced (thickness));
p.setUsingNonZeroWinding (false);
g.setColour (Colours::white.withAlpha (opacity));
g.fillPath (p);
}
};
Array<Ring> rings;
};
//==============================================================================
struct FlockWithText : public FlockDemo
{
FlockWithText()
{
messages.add ("JUCE is our cross-platform C++ framework\n\n"
"In this demo, the same C++ app is running natively on NUMDEVICES devices,\n"
"which are sharing their graphic state via the network");
messages.add ("No other libraries were needed to create this demo.\n"
"JUCE provides thousands of classes for cross-platform GUI,\n"
"audio, networking, data-structures and many other common tasks");
messages.add ("As well as a code library, JUCE provides tools for managing\n"
"cross-platform projects that are built with Xcode,\n"
"Visual Studio, Android Studio, GCC and other compilers");
messages.add ("JUCE can be used to build desktop or mobile apps, and also\n"
"audio plug-ins in the VST2, VST3, AudioUnit, AAX and RTAS formats");
}
String getName() const override { return "Flock with text"; }
void reset() override
{
FlockDemo::reset();
currentMessage = 0;
currentMessageStart = {};
clientIndex = 0;
}
void generateCanvas (Graphics& g, SharedCanvasDescription& canvas, Rectangle<float> activeArea) override
{
FlockDemo::generateCanvas (g, canvas, activeArea);
const float textSize = 0.5f; // inches
const float textBlockWidth = 20.0f; // inches
tick();
Graphics::ScopedSaveState ss (g);
const float scale = 20.0f; // scaled to allow the fonts to use more reasonable sizes
g.addTransform (AffineTransform::scale (1.0f / scale));
String text = String (messages[currentMessage]).replace ("NUMDEVICES", String (canvas.clients.size()));
AttributedString as;
as.append (text, Font (textSize * scale), Colour (0x80ffffff).withMultipliedAlpha (alpha));
as.setJustification (Justification::centred);
auto middle = canvas.clients[clientIndex % canvas.clients.size()].centre * scale;
as.draw (g, Rectangle<float> (textBlockWidth * scale, textBlockWidth * scale).withCentre (middle));
}
void tick()
{
const double displayTimeSeconds = 5.0;
const double fadeTimeSeconds = 1.0;
Time now = Time::getCurrentTime();
const double secondsSinceStart = (now - currentMessageStart).inSeconds();
if (secondsSinceStart > displayTimeSeconds)
{
currentMessageStart = now;
currentMessage = (currentMessage + 1) % messages.size();
++clientIndex;
alpha = 0;
}
else if (secondsSinceStart > displayTimeSeconds - fadeTimeSeconds)
{
alpha = (float) jlimit (0.0, 1.0, (displayTimeSeconds - secondsSinceStart) / fadeTimeSeconds);
}
else if (secondsSinceStart < fadeTimeSeconds)
{
alpha = (float) jlimit (0.0, 1.0, secondsSinceStart / fadeTimeSeconds);
}
}
StringArray messages;
int currentMessage = 0, clientIndex = 0;
float alpha = 0;
Point<float> centre;
Time currentMessageStart;
};
//==============================================================================
struct SmallFlock : public FlockDemo
{
String getName() const override { return "Small Flock"; }
void reset() override
{
setNumBirds (20);
}
};
//==============================================================================
struct BigFlock : public FlockDemo
{
String getName() const override { return "Big Flock"; }
void reset() override
{
setNumBirds (200);
}
};
//==============================================================================
template <int numHorizontalLogos>
struct MultiLogo : public BackgroundLogo
{
String getName() const override { return "Multi-Logo " + String ((int) numHorizontalLogos); }
void generateCanvas (Graphics& g, SharedCanvasDescription& canvas, Rectangle<float>) override
{
float indent = 0.5f;
float logoSize = canvas.getLimits().getWidth() / numHorizontalLogos;
auto limits = canvas.getLimits();
for (float x = limits.getX(); x < limits.getRight(); x += logoSize)
{
for (float y = limits.getY(); y < limits.getBottom(); y += logoSize)
{
logo->drawWithin (g, Rectangle<float> (x, y, logoSize, logoSize).reduced (indent),
RectanglePlacement (RectanglePlacement::centred), 0.5f);
}
}
}
};
//==============================================================================
void createAllDemos (OwnedArray<AnimatedContent>& demos)
{
demos.add (new FlockDemo());
demos.add (new FlockWithText());
demos.add (new SmallFlock());
demos.add (new BigFlock());
demos.add (new BackgroundLogo());
demos.add (new MultiLogo<5>());
demos.add (new MultiLogo<10>());
demos.add (new GridLines());
demos.add (new BlankCanvas());
}

@ -0,0 +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)

@ -0,0 +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)
};

@ -0,0 +1,527 @@
/*
==============================================================================
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 scene description is broadcast to all the clients, and contains a list of all
the clients involved, as well as the set of shapes to be drawn.
Each client will draw the part of the path that lies within its own area. It can
find its area by looking at the list of clients contained in this structure.
All the path coordinates are roughly in units of inches, and devices will convert
this to pixels based on their screen size and DPI
*/
struct SharedCanvasDescription
{
SharedCanvasDescription() {}
Colour backgroundColour = Colours::black;
struct ColouredPath
{
Path path;
FillType fill;
};
Array<ColouredPath> paths;
struct ClientArea
{
String name;
Point<float> centre; // in inches
float scaleFactor; // extra scaling
};
Array<ClientArea> clients;
//==============================================================================
void reset()
{
paths.clearQuick();
clients.clearQuick();
}
void swapWith (SharedCanvasDescription& other)
{
std::swap (backgroundColour, other.backgroundColour);
paths.swapWith (other.paths);
clients.swapWith (other.clients);
}
// This is a fixed size that represents the overall canvas limits that
// content should lie within
Rectangle<float> getLimits() const
{
float inchesX = 60.0f;
float inchesY = 30.0f;
return { inchesX * -0.5f, inchesY * -0.5f, inchesX, inchesY };
}
//==============================================================================
void draw (Graphics& g, Rectangle<float> targetArea, Rectangle<float> clientArea) const
{
draw (g, clientArea,
AffineTransform::fromTargetPoints (clientArea.getX(), clientArea.getY(),
targetArea.getX(), targetArea.getY(),
clientArea.getRight(), clientArea.getY(),
targetArea.getRight(), targetArea.getY(),
clientArea.getRight(), clientArea.getBottom(),
targetArea.getRight(), targetArea.getBottom()));
}
void draw (Graphics& g, Rectangle<float> clientArea, AffineTransform t) const
{
g.saveState();
g.addTransform (t);
for (const auto& p : paths)
{
if (p.path.getBounds().intersects (clientArea))
{
g.setFillType (p.fill);
g.fillPath (p.path);
}
}
g.restoreState();
}
const ClientArea* findClient (const String& clientName) const
{
for (const auto& c : clients)
if (c.name == clientName)
return &c;
return nullptr;
}
//==============================================================================
// Serialisation...
void save (OutputStream& out) const
{
out.writeInt (magic);
out.writeInt ((int) backgroundColour.getARGB());
out.writeInt (clients.size());
for (const auto& c : clients)
{
out.writeString (c.name);
writePoint (out, c.centre);
out.writeFloat (c.scaleFactor);
}
out.writeInt (paths.size());
for (const auto& p : paths)
{
writeFill (out, p.fill);
p.path.writePathToStream (out);
}
}
void load (InputStream& in)
{
if (in.readInt() != magic)
return;
backgroundColour = Colour ((uint32) in.readInt());
{
const int numClients = in.readInt();
clients.clearQuick();
for (int i = 0; i < numClients; ++i)
{
ClientArea c;
c.name = in.readString();
c.centre = readPoint (in);
c.scaleFactor = in.readFloat();
clients.add (c);
}
}
{
const int numPaths = in.readInt();
paths.clearQuick();
for (int i = 0; i < numPaths; ++i)
{
ColouredPath p;
p.fill = readFill (in);
p.path.loadPathFromStream (in);
paths.add (std::move (p));
}
}
}
MemoryBlock toMemoryBlock() const
{
MemoryOutputStream o;
save (o);
return o.getMemoryBlock();
}
private:
//==============================================================================
static void writePoint (OutputStream& out, Point<float> p)
{
out.writeFloat (p.x);
out.writeFloat (p.y);
}
static void writeRect (OutputStream& out, Rectangle<float> r)
{
writePoint (out, r.getPosition());
out.writeFloat (r.getWidth());
out.writeFloat (r.getHeight());
}
static Point<float> readPoint (InputStream& in)
{
Point<float> p;
p.x = in.readFloat();
p.y = in.readFloat();
return p;
}
static Rectangle<float> readRect (InputStream& in)
{
Rectangle<float> r;
r.setPosition (readPoint (in));
r.setWidth (in.readFloat());
r.setHeight (in.readFloat());
return r;
}
static void writeFill (OutputStream& out, const FillType& f)
{
if (f.isColour())
{
out.writeByte (0);
out.writeInt ((int) f.colour.getARGB());
}
else if (f.isGradient())
{
const ColourGradient& cg = *f.gradient;
jassert (cg.getNumColours() >= 2);
out.writeByte (cg.isRadial ? 2 : 1);
writePoint (out, cg.point1);
writePoint (out, cg.point2);
out.writeCompressedInt (cg.getNumColours());
for (int i = 0; i < cg.getNumColours(); ++i)
{
out.writeDouble (cg.getColourPosition (i));
out.writeInt ((int) cg.getColour(i).getARGB());
}
}
else
{
jassertfalse;
}
}
static FillType readFill (InputStream& in)
{
int type = in.readByte();
if (type == 0)
return FillType (Colour ((uint32) in.readInt()));
if (type > 2)
{
jassertfalse;
return FillType();
}
ColourGradient cg;
cg.point1 = readPoint (in);
cg.point2 = readPoint (in);
cg.clearColours();
int numColours = in.readCompressedInt();
for (int i = 0; i < numColours; ++i)
{
const double pos = in.readDouble();
cg.addColour (pos, Colour ((uint32) in.readInt()));
}
jassert (cg.getNumColours() >= 2);
return FillType (cg);
}
const int magic = 0x2381239a;
JUCE_DECLARE_NON_COPYABLE (SharedCanvasDescription)
};
//==============================================================================
class CanvasGeneratingContext : public LowLevelGraphicsContext
{
public:
CanvasGeneratingContext (SharedCanvasDescription& c) : canvas (c)
{
stateStack.add (new SavedState());
}
//==============================================================================
bool isVectorDevice() const override { return true; }
float getPhysicalPixelScaleFactor() override { return 1.0f; }
void setOrigin (Point<int> o) override { addTransform (AffineTransform::translation ((float) o.x, (float) o.y)); }
void addTransform (const AffineTransform& t) override
{
getState().transform = t.followedBy (getState().transform);
}
bool clipToRectangle (const Rectangle<int>&) override { return true; }
bool clipToRectangleList (const RectangleList<int>&) override { return true; }
void excludeClipRectangle (const Rectangle<int>&) override {}
void clipToPath (const Path&, const AffineTransform&) override {}
void clipToImageAlpha (const Image&, const AffineTransform&) override {}
void saveState() override
{
stateStack.add (new SavedState (getState()));
}
void restoreState() override
{
jassert (stateStack.size() > 0);
if (stateStack.size() > 0)
stateStack.removeLast();
}
void beginTransparencyLayer (float alpha) override
{
saveState();
getState().transparencyLayer = new SharedCanvasHolder();
getState().transparencyOpacity = alpha;
}
void endTransparencyLayer() override
{
const ReferenceCountedObjectPtr<SharedCanvasHolder> finishedTransparencyLayer (getState().transparencyLayer);
float alpha = getState().transparencyOpacity;
restoreState();
if (SharedCanvasHolder* c = finishedTransparencyLayer)
{
for (auto& path : c->canvas.paths)
{
path.fill.setOpacity (path.fill.getOpacity() * alpha);
getTargetCanvas().paths.add (path);
}
}
}
Rectangle<int> getClipBounds() const override
{
return canvas.getLimits().getSmallestIntegerContainer()
.transformedBy (getState().transform.inverted());
}
bool clipRegionIntersects (const Rectangle<int>&) override { return true; }
bool isClipEmpty() const override { return false; }
//==============================================================================
void setFill (const FillType& fillType) override { getState().fillType = fillType; }
void setOpacity (float op) override { getState().fillType.setOpacity (op); }
void setInterpolationQuality (Graphics::ResamplingQuality) override {}
//==============================================================================
void fillRect (const Rectangle<int>& r, bool) override { fillRect (r.toFloat()); }
void fillRectList (const RectangleList<float>& list) override { fillPath (list.toPath(), AffineTransform()); }
void fillRect (const Rectangle<float>& r) override
{
Path p;
p.addRectangle (r.toFloat());
fillPath (p, AffineTransform());
}
void fillPath (const Path& p, const AffineTransform& t) override
{
Path p2 (p);
p2.applyTransform (t.followedBy (getState().transform));
getTargetCanvas().paths.add ({ std::move (p2), getState().fillType });
}
void drawImage (const Image&, const AffineTransform&) override {}
void drawLine (const Line<float>& line) override
{
Path p;
p.addLineSegment (line, 1.0f);
fillPath (p, AffineTransform());
}
//==============================================================================
const Font& getFont() override { return getState().font; }
void setFont (const Font& newFont) override { getState().font = newFont; }
void drawGlyph (int glyphNumber, const AffineTransform& transform) override
{
Path p;
Font& font = getState().font;
font.getTypefacePtr()->getOutlineForGlyph (glyphNumber, p);
fillPath (p, AffineTransform::scale (font.getHeight() * font.getHorizontalScale(), font.getHeight()).followedBy (transform));
}
private:
//==============================================================================
struct SharedCanvasHolder : public ReferenceCountedObject
{
SharedCanvasDescription canvas;
};
struct SavedState
{
FillType fillType;
AffineTransform transform;
Font font;
ReferenceCountedObjectPtr<SharedCanvasHolder> transparencyLayer;
float transparencyOpacity = 1.0f;
};
SharedCanvasDescription& getTargetCanvas() const
{
if (SharedCanvasHolder* c = getState().transparencyLayer)
return c->canvas;
return canvas;
}
SavedState& getState() const noexcept
{
jassert (stateStack.size() > 0);
return *stateStack.getLast();
}
SharedCanvasDescription& canvas;
OwnedArray<SavedState> stateStack;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CanvasGeneratingContext)
};
//==============================================================================
/** Helper for breaking and reassembling a memory block into smaller checksummed
blocks that will fit inside UDP packets
*/
struct BlockPacketiser
{
void createBlocksFromData (const MemoryBlock& data, size_t maxBlockSize)
{
jassert (blocks.size() == 0);
int offset = 0;
size_t remaining = data.getSize();
while (remaining > 0)
{
auto num = (size_t) jmin (maxBlockSize, remaining);
blocks.add (MemoryBlock (addBytesToPointer (data.getData(), offset), num));
offset += (int) num;
remaining -= num;
}
MemoryOutputStream checksumBlock;
checksumBlock << getLastPacketPrefix() << MD5 (data).toHexString() << (char) 0 << (char) 0;
blocks.add (checksumBlock.getMemoryBlock());
for (int i = 0; i < blocks.size(); ++i)
{
auto index = (uint32) ByteOrder::swapIfBigEndian (i);
blocks.getReference(i).append (&index, sizeof (index));
}
}
// returns true if this is an end-of-sequence block
bool appendIncomingBlock (MemoryBlock data)
{
if (data.getSize() > 4)
blocks.addSorted (*this, data);
return String (CharPointer_ASCII ((const char*) data.getData())).startsWith (getLastPacketPrefix());
}
bool reassemble (MemoryBlock& result)
{
result.reset();
if (blocks.size() > 1)
{
for (int i = 0; i < blocks.size() - 1; ++i)
result.append (blocks.getReference(i).getData(), blocks.getReference(i).getSize() - 4);
String storedMD5 (String (CharPointer_ASCII ((const char*) blocks.getLast().getData()))
.fromFirstOccurrenceOf (getLastPacketPrefix(), false, false));
blocks.clearQuick();
if (MD5 (result).toHexString().trim().equalsIgnoreCase (storedMD5.trim()))
return true;
}
result.reset();
return false;
}
static int compareElements (const MemoryBlock& b1, const MemoryBlock& b2)
{
auto i1 = ByteOrder::littleEndianInt (addBytesToPointer (b1.getData(), b1.getSize() - 4));
auto i2 = ByteOrder::littleEndianInt (addBytesToPointer (b2.getData(), b2.getSize() - 4));
return (int) (i1 - i2);
}
static const char* getLastPacketPrefix() { return "**END_OF_PACKET_LIST** "; }
Array<MemoryBlock> blocks;
};
//==============================================================================
struct AnimatedContent
{
virtual ~AnimatedContent() {}
virtual String getName() const = 0;
virtual void reset() = 0;
virtual void generateCanvas (Graphics&, SharedCanvasDescription& canvas, Rectangle<float> activeArea) = 0;
virtual void handleTouch (Point<float> position) = 0;
};

@ -0,0 +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)
};

Binary file not shown.

After

(image error) Size: 45 KiB