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:
369
deps/juce/examples/Utilities/AnalyticsCollectionDemo.h
vendored
Normal file
369
deps/juce/examples/Utilities/AnalyticsCollectionDemo.h
vendored
Normal file
@ -0,0 +1,369 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE examples.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
|
||||
WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
|
||||
PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
/*******************************************************************************
|
||||
The block below describes the properties of this PIP. A PIP is a short snippet
|
||||
of code that can be read by the Projucer and used to generate a JUCE project.
|
||||
|
||||
BEGIN_JUCE_PIP_METADATA
|
||||
|
||||
name: AnalyticsCollectionDemo
|
||||
version: 1.0.0
|
||||
vendor: JUCE
|
||||
website: http://juce.com
|
||||
description: Collects analytics data.
|
||||
|
||||
dependencies: juce_analytics, juce_core, juce_data_structures, juce_events,
|
||||
juce_graphics, juce_gui_basics
|
||||
exporters: xcode_mac, vs2019, linux_make, xcode_iphone, androidstudio
|
||||
|
||||
moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
|
||||
|
||||
type: Component
|
||||
mainClass: AnalyticsCollectionDemo
|
||||
|
||||
useLocalCopy: 1
|
||||
|
||||
END_JUCE_PIP_METADATA
|
||||
|
||||
*******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
//==============================================================================
|
||||
enum DemoAnalyticsEventTypes
|
||||
{
|
||||
event,
|
||||
sessionStart,
|
||||
sessionEnd,
|
||||
screenView,
|
||||
exception
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class GoogleAnalyticsDestination : public ThreadedAnalyticsDestination
|
||||
{
|
||||
public:
|
||||
GoogleAnalyticsDestination()
|
||||
: ThreadedAnalyticsDestination ("GoogleAnalyticsThread")
|
||||
{
|
||||
{
|
||||
// Choose where to save any unsent events.
|
||||
|
||||
auto appDataDir = File::getSpecialLocation (File::userApplicationDataDirectory)
|
||||
.getChildFile (JUCEApplication::getInstance()->getApplicationName());
|
||||
|
||||
if (! appDataDir.exists())
|
||||
appDataDir.createDirectory();
|
||||
|
||||
savedEventsFile = appDataDir.getChildFile ("analytics_events.xml");
|
||||
}
|
||||
|
||||
{
|
||||
// It's often a good idea to construct any analytics service API keys
|
||||
// at runtime, so they're not searchable in the binary distribution of
|
||||
// your application (but we've not done this here). You should replace
|
||||
// the following key with your own to get this example application
|
||||
// fully working.
|
||||
|
||||
apiKey = "UA-XXXXXXXXX-1";
|
||||
}
|
||||
|
||||
startAnalyticsThread (initialPeriodMs);
|
||||
}
|
||||
|
||||
~GoogleAnalyticsDestination() override
|
||||
{
|
||||
// Here we sleep so that our background thread has a chance to send the
|
||||
// last lot of batched events. Be careful - if your app takes too long to
|
||||
// shut down then some operating systems will kill it forcibly!
|
||||
Thread::sleep (initialPeriodMs);
|
||||
|
||||
stopAnalyticsThread (1000);
|
||||
}
|
||||
|
||||
int getMaximumBatchSize() override { return 20; }
|
||||
|
||||
bool logBatchedEvents (const Array<AnalyticsEvent>& events) override
|
||||
{
|
||||
// Send events to Google Analytics.
|
||||
|
||||
String appData ("v=1&aip=1&tid=" + apiKey);
|
||||
|
||||
StringArray postData;
|
||||
|
||||
for (auto& event : events)
|
||||
{
|
||||
StringPairArray data;
|
||||
|
||||
switch (event.eventType)
|
||||
{
|
||||
case (DemoAnalyticsEventTypes::event):
|
||||
{
|
||||
data.set ("t", "event");
|
||||
|
||||
if (event.name == "startup")
|
||||
{
|
||||
data.set ("ec", "info");
|
||||
data.set ("ea", "appStarted");
|
||||
}
|
||||
else if (event.name == "shutdown")
|
||||
{
|
||||
data.set ("ec", "info");
|
||||
data.set ("ea", "appStopped");
|
||||
}
|
||||
else if (event.name == "button_press")
|
||||
{
|
||||
data.set ("ec", "button_press");
|
||||
data.set ("ea", event.parameters["id"]);
|
||||
}
|
||||
else if (event.name == "crash")
|
||||
{
|
||||
data.set ("ec", "crash");
|
||||
data.set ("ea", "crash");
|
||||
}
|
||||
else
|
||||
{
|
||||
jassertfalse;
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
// Unknown event type! In this demo app we're just using a
|
||||
// single event type, but in a real app you probably want to
|
||||
// handle multiple ones.
|
||||
jassertfalse;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
data.set ("cid", event.userID);
|
||||
|
||||
StringArray eventData;
|
||||
|
||||
for (auto& key : data.getAllKeys())
|
||||
eventData.add (key + "=" + URL::addEscapeChars (data[key], true));
|
||||
|
||||
postData.add (appData + "&" + eventData.joinIntoString ("&"));
|
||||
}
|
||||
|
||||
auto url = URL ("https://www.google-analytics.com/batch")
|
||||
.withPOSTData (postData.joinIntoString ("\n"));
|
||||
|
||||
{
|
||||
const ScopedLock lock (webStreamCreation);
|
||||
|
||||
if (shouldExit)
|
||||
return false;
|
||||
|
||||
webStream.reset (new WebInputStream (url, true));
|
||||
}
|
||||
|
||||
auto success = webStream->connect (nullptr);
|
||||
|
||||
// Do an exponential backoff if we failed to connect.
|
||||
if (success)
|
||||
periodMs = initialPeriodMs;
|
||||
else
|
||||
periodMs *= 2;
|
||||
|
||||
setBatchPeriod (periodMs);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
void stopLoggingEvents() override
|
||||
{
|
||||
const ScopedLock lock (webStreamCreation);
|
||||
|
||||
shouldExit = true;
|
||||
|
||||
if (webStream.get() != nullptr)
|
||||
webStream->cancel();
|
||||
}
|
||||
|
||||
private:
|
||||
void saveUnloggedEvents (const std::deque<AnalyticsEvent>& eventsToSave) override
|
||||
{
|
||||
// Save unsent events to disk. Here we use XML as a serialisation format, but
|
||||
// you can use anything else as long as the restoreUnloggedEvents method can
|
||||
// restore events from disk. If you're saving very large numbers of events then
|
||||
// a binary format may be more suitable if it is faster - remember that this
|
||||
// method is called on app shutdown so it needs to complete quickly!
|
||||
|
||||
auto xml = parseXMLIfTagMatches (savedEventsFile, "events");
|
||||
|
||||
if (xml == nullptr)
|
||||
xml = std::make_unique<XmlElement> ("events");
|
||||
|
||||
for (auto& event : eventsToSave)
|
||||
{
|
||||
auto* xmlEvent = new XmlElement ("google_analytics_event");
|
||||
xmlEvent->setAttribute ("name", event.name);
|
||||
xmlEvent->setAttribute ("type", event.eventType);
|
||||
xmlEvent->setAttribute ("timestamp", (int) event.timestamp);
|
||||
xmlEvent->setAttribute ("user_id", event.userID);
|
||||
|
||||
auto* parameters = new XmlElement ("parameters");
|
||||
|
||||
for (auto& key : event.parameters.getAllKeys())
|
||||
parameters->setAttribute (key, event.parameters[key]);
|
||||
|
||||
xmlEvent->addChildElement (parameters);
|
||||
|
||||
auto* userProperties = new XmlElement ("user_properties");
|
||||
|
||||
for (auto& key : event.userProperties.getAllKeys())
|
||||
userProperties->setAttribute (key, event.userProperties[key]);
|
||||
|
||||
xmlEvent->addChildElement (userProperties);
|
||||
|
||||
xml->addChildElement (xmlEvent);
|
||||
}
|
||||
|
||||
xml->writeTo (savedEventsFile, {});
|
||||
}
|
||||
|
||||
void restoreUnloggedEvents (std::deque<AnalyticsEvent>& restoredEventQueue) override
|
||||
{
|
||||
if (auto xml = parseXMLIfTagMatches (savedEventsFile, "events"))
|
||||
{
|
||||
auto numEvents = xml->getNumChildElements();
|
||||
|
||||
for (auto iEvent = 0; iEvent < numEvents; ++iEvent)
|
||||
{
|
||||
auto* xmlEvent = xml->getChildElement (iEvent);
|
||||
|
||||
StringPairArray parameters;
|
||||
auto* xmlParameters = xmlEvent->getChildByName ("parameters");
|
||||
auto numParameters = xmlParameters->getNumAttributes();
|
||||
|
||||
for (auto iParam = 0; iParam < numParameters; ++iParam)
|
||||
parameters.set (xmlParameters->getAttributeName (iParam),
|
||||
xmlParameters->getAttributeValue (iParam));
|
||||
|
||||
StringPairArray userProperties;
|
||||
auto* xmlUserProperties = xmlEvent->getChildByName ("user_properties");
|
||||
auto numUserProperties = xmlUserProperties->getNumAttributes();
|
||||
|
||||
for (auto iProp = 0; iProp < numUserProperties; ++iProp)
|
||||
userProperties.set (xmlUserProperties->getAttributeName (iProp),
|
||||
xmlUserProperties->getAttributeValue (iProp));
|
||||
|
||||
restoredEventQueue.push_back ({
|
||||
xmlEvent->getStringAttribute ("name"),
|
||||
xmlEvent->getIntAttribute ("type"),
|
||||
static_cast<uint32> (xmlEvent->getIntAttribute ("timestamp")),
|
||||
parameters,
|
||||
xmlEvent->getStringAttribute ("user_id"),
|
||||
userProperties
|
||||
});
|
||||
}
|
||||
|
||||
savedEventsFile.deleteFile();
|
||||
}
|
||||
}
|
||||
|
||||
const int initialPeriodMs = 1000;
|
||||
int periodMs = initialPeriodMs;
|
||||
|
||||
CriticalSection webStreamCreation;
|
||||
bool shouldExit = false;
|
||||
std::unique_ptr<WebInputStream> webStream;
|
||||
|
||||
String apiKey;
|
||||
|
||||
File savedEventsFile;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class AnalyticsCollectionDemo : public Component
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
AnalyticsCollectionDemo()
|
||||
{
|
||||
// Add an analytics identifier for the user. Make sure you don't accidentally
|
||||
// collect identifiable information if you haven't asked for permission!
|
||||
Analytics::getInstance()->setUserId ("AnonUser1234");
|
||||
|
||||
// Add any other constant user information.
|
||||
StringPairArray userData;
|
||||
userData.set ("group", "beta");
|
||||
Analytics::getInstance()->setUserProperties (userData);
|
||||
|
||||
// Add any analytics destinations we want to use to the Analytics singleton.
|
||||
Analytics::getInstance()->addDestination (new GoogleAnalyticsDestination());
|
||||
|
||||
// The event type here should probably be DemoAnalyticsEventTypes::sessionStart
|
||||
// in a more advanced app.
|
||||
Analytics::getInstance()->logEvent ("startup", {}, DemoAnalyticsEventTypes::event);
|
||||
|
||||
crashButton.onClick = [this] { sendCrash(); };
|
||||
|
||||
addAndMakeVisible (eventButton);
|
||||
addAndMakeVisible (crashButton);
|
||||
|
||||
setSize (300, 200);
|
||||
|
||||
StringPairArray logButtonPressParameters;
|
||||
logButtonPressParameters.set ("id", "a");
|
||||
logEventButtonPress.reset (new ButtonTracker (eventButton, "button_press", logButtonPressParameters));
|
||||
}
|
||||
|
||||
~AnalyticsCollectionDemo() override
|
||||
{
|
||||
// The event type here should probably be DemoAnalyticsEventTypes::sessionEnd
|
||||
// in a more advanced app.
|
||||
Analytics::getInstance()->logEvent ("shutdown", {}, DemoAnalyticsEventTypes::event);
|
||||
}
|
||||
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId));
|
||||
}
|
||||
|
||||
void resized() override
|
||||
{
|
||||
eventButton.centreWithSize (100, 40);
|
||||
eventButton.setBounds (eventButton.getBounds().translated (0, 25));
|
||||
crashButton.setBounds (eventButton.getBounds().translated (0, -50));
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
void sendCrash()
|
||||
{
|
||||
// In a more advanced application you would probably use a different event
|
||||
// type here.
|
||||
Analytics::getInstance()->logEvent ("crash", {}, DemoAnalyticsEventTypes::event);
|
||||
Analytics::getInstance()->getDestinations().clear();
|
||||
JUCEApplication::getInstance()->shutdown();
|
||||
}
|
||||
|
||||
TextButton eventButton { "Press me!" }, crashButton { "Simulate crash!" };
|
||||
std::unique_ptr<ButtonTracker> logEventButtonPress;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AnalyticsCollectionDemo)
|
||||
};
|
333
deps/juce/examples/Utilities/Box2DDemo.h
vendored
Normal file
333
deps/juce/examples/Utilities/Box2DDemo.h
vendored
Normal file
@ -0,0 +1,333 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE examples.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
|
||||
WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
|
||||
PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
/*******************************************************************************
|
||||
The block below describes the properties of this PIP. A PIP is a short snippet
|
||||
of code that can be read by the Projucer and used to generate a JUCE project.
|
||||
|
||||
BEGIN_JUCE_PIP_METADATA
|
||||
|
||||
name: Box2DDemo
|
||||
version: 1.0.0
|
||||
vendor: JUCE
|
||||
website: http://juce.com
|
||||
description: Showcases 2D graphics features.
|
||||
|
||||
dependencies: juce_box2d, juce_core, juce_data_structures, juce_events,
|
||||
juce_graphics, juce_gui_basics
|
||||
exporters: xcode_mac, vs2019, linux_make, androidstudio, xcode_iphone
|
||||
|
||||
moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
|
||||
|
||||
type: Component
|
||||
mainClass: Box2DDemo
|
||||
|
||||
useLocalCopy: 1
|
||||
|
||||
END_JUCE_PIP_METADATA
|
||||
|
||||
*******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../Assets/DemoUtilities.h"
|
||||
|
||||
//==============================================================================
|
||||
// (These classes and random functions are used inside the 3rd-party Box2D demo code)
|
||||
inline float32 RandomFloat() { return Random::getSystemRandom().nextFloat() * 2.0f - 1.0f; }
|
||||
inline float32 RandomFloat (float32 lo, float32 hi) { return Random::getSystemRandom().nextFloat() * (hi - lo) + lo; }
|
||||
|
||||
struct Settings
|
||||
{
|
||||
b2Vec2 viewCenter { 0.0f, 20.0f };
|
||||
float32 hz = 60.0f;
|
||||
int velocityIterations = 8;
|
||||
int positionIterations = 3;
|
||||
int drawShapes = 1;
|
||||
int drawJoints = 1;
|
||||
int drawAABBs = 0;
|
||||
int drawPairs = 0;
|
||||
int drawContactPoints = 0;
|
||||
int drawContactNormals = 0;
|
||||
int drawContactForces = 0;
|
||||
int drawFrictionForces = 0;
|
||||
int drawCOMs = 0;
|
||||
int drawStats = 0;
|
||||
int drawProfile = 0;
|
||||
int enableWarmStarting = 1;
|
||||
int enableContinuous = 1;
|
||||
int enableSubStepping = 0;
|
||||
int pause = 0;
|
||||
int singleStep = 0;
|
||||
};
|
||||
|
||||
struct Test
|
||||
{
|
||||
Test() {}
|
||||
virtual ~Test() {}
|
||||
|
||||
virtual void Keyboard (unsigned char /*key*/) {}
|
||||
virtual void KeyboardUp (unsigned char /*key*/) {}
|
||||
|
||||
std::unique_ptr<b2World> m_world { new b2World (b2Vec2 (0.0f, -10.0f)) };
|
||||
};
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wimplicit-int-float-conversion")
|
||||
|
||||
#include "../Assets/Box2DTests/AddPair.h"
|
||||
#include "../Assets/Box2DTests/ApplyForce.h"
|
||||
#include "../Assets/Box2DTests/Dominos.h"
|
||||
#include "../Assets/Box2DTests/Chain.h"
|
||||
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
|
||||
//==============================================================================
|
||||
/** This list box just displays a StringArray and broadcasts a change message when the
|
||||
selected row changes.
|
||||
*/
|
||||
class Box2DTestList : public ListBoxModel,
|
||||
public ChangeBroadcaster
|
||||
{
|
||||
public:
|
||||
Box2DTestList (const StringArray& testList)
|
||||
: tests (testList)
|
||||
{}
|
||||
|
||||
int getNumRows() override { return tests.size(); }
|
||||
|
||||
void selectedRowsChanged (int /*lastRowSelected*/) override { sendChangeMessage(); }
|
||||
|
||||
void paintListBoxItem (int row, Graphics& g, int w, int h, bool rowIsSelected) override
|
||||
{
|
||||
auto& lf = LookAndFeel::getDefaultLookAndFeel();
|
||||
|
||||
if (rowIsSelected)
|
||||
g.fillAll (Colour::contrasting (lf.findColour (ListBox::textColourId),
|
||||
lf.findColour (ListBox::backgroundColourId)));
|
||||
|
||||
g.setColour (lf.findColour (ListBox::textColourId));
|
||||
g.setFont ((float) h * 0.7f);
|
||||
g.drawText (tests[row], Rectangle<int> (0, 0, w, h).reduced (2),
|
||||
Justification::centredLeft, true);
|
||||
}
|
||||
|
||||
private:
|
||||
StringArray tests;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Box2DTestList)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
struct Box2DRenderComponent : public Component
|
||||
{
|
||||
Box2DRenderComponent()
|
||||
{
|
||||
setOpaque (true);
|
||||
}
|
||||
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
g.fillAll (Colours::white);
|
||||
|
||||
if (currentTest.get() != nullptr)
|
||||
{
|
||||
Box2DRenderer renderer;
|
||||
|
||||
renderer.render (g, *currentTest->m_world,
|
||||
-16.0f, 30.0f, 16.0f, -1.0f,
|
||||
getLocalBounds().toFloat().reduced (8.0f));
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<Test> currentTest;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class Box2DDemo : public Component,
|
||||
private Timer,
|
||||
private ChangeListener
|
||||
{
|
||||
public:
|
||||
enum Demos
|
||||
{
|
||||
addPair = 0,
|
||||
applyForce,
|
||||
dominoes,
|
||||
chain,
|
||||
numTests
|
||||
};
|
||||
|
||||
Box2DDemo()
|
||||
: testsList (getTestsList())
|
||||
{
|
||||
setOpaque (true);
|
||||
setWantsKeyboardFocus (true);
|
||||
|
||||
testsListModel.addChangeListener (this);
|
||||
|
||||
addAndMakeVisible (renderComponent);
|
||||
|
||||
addAndMakeVisible (testsListBox);
|
||||
testsListBox.setModel (&testsListModel);
|
||||
testsListBox.selectRow (dominoes);
|
||||
|
||||
addAndMakeVisible (instructions);
|
||||
instructions.setMultiLine (true);
|
||||
instructions.setReadOnly (true);
|
||||
|
||||
startTimerHz (60);
|
||||
|
||||
setSize (500, 500);
|
||||
}
|
||||
|
||||
~Box2DDemo() override
|
||||
{
|
||||
testsListModel.removeChangeListener (this);
|
||||
}
|
||||
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
g.fillAll (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::windowBackground));
|
||||
}
|
||||
|
||||
void resized() override
|
||||
{
|
||||
auto r = getLocalBounds().reduced (4);
|
||||
|
||||
auto area = r.removeFromBottom (150);
|
||||
testsListBox.setBounds (area.removeFromLeft (150));
|
||||
|
||||
area.removeFromLeft (4);
|
||||
instructions.setBounds (area);
|
||||
|
||||
r.removeFromBottom (6);
|
||||
renderComponent.setBounds (r);
|
||||
}
|
||||
|
||||
bool keyPressed (const KeyPress& key) override
|
||||
{
|
||||
if (renderComponent.currentTest.get() != nullptr)
|
||||
{
|
||||
// We override this to avoid the system beeping for an unused keypress
|
||||
switch (key.getTextCharacter())
|
||||
{
|
||||
case 'a':
|
||||
case 'w':
|
||||
case 'd':
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
StringArray testsList;
|
||||
Box2DTestList testsListModel { testsList };
|
||||
|
||||
Box2DRenderComponent renderComponent;
|
||||
ListBox testsListBox;
|
||||
TextEditor instructions;
|
||||
|
||||
static Test* createTest (int index)
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case addPair: return new AddPair();
|
||||
case applyForce: return new ApplyForce();
|
||||
case dominoes: return new Dominos();
|
||||
case chain: return new Chain();
|
||||
default: break;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static String getInstructions (int index)
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case applyForce:
|
||||
return String ("Keys:") + newLine + "Left: \'a\'" + newLine
|
||||
+ "Right: \'d\'" + newLine + "Forward: \'w\'";
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void checkKeys()
|
||||
{
|
||||
if (renderComponent.currentTest.get() == nullptr)
|
||||
return;
|
||||
|
||||
checkKeyCode ('a');
|
||||
checkKeyCode ('w');
|
||||
checkKeyCode ('d');
|
||||
}
|
||||
|
||||
void checkKeyCode (const int keyCode)
|
||||
{
|
||||
if (KeyPress::isKeyCurrentlyDown (keyCode))
|
||||
renderComponent.currentTest->Keyboard ((unsigned char) keyCode);
|
||||
}
|
||||
|
||||
void timerCallback() override
|
||||
{
|
||||
if (renderComponent.currentTest.get() == nullptr)
|
||||
return;
|
||||
|
||||
if (isShowing())
|
||||
grabKeyboardFocus();
|
||||
|
||||
checkKeys();
|
||||
renderComponent.currentTest->m_world->Step (1.0f / 60.0f, 6, 2);
|
||||
repaint();
|
||||
}
|
||||
|
||||
void changeListenerCallback (ChangeBroadcaster* source) override
|
||||
{
|
||||
if (source == &testsListModel)
|
||||
{
|
||||
auto index = testsListBox.getSelectedRow();
|
||||
|
||||
renderComponent.currentTest.reset (createTest (index));
|
||||
instructions.setText (getInstructions (index));
|
||||
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
void lookAndFeelChanged() override
|
||||
{
|
||||
instructions.applyFontToAllText (instructions.getFont());
|
||||
}
|
||||
|
||||
static StringArray getTestsList()
|
||||
{
|
||||
return { "Add Pair Stress Test", "Apply Force", "Dominoes", "Chain" };
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Box2DDemo)
|
||||
};
|
24
deps/juce/examples/Utilities/CMakeLists.txt
vendored
Normal file
24
deps/juce/examples/Utilities/CMakeLists.txt
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
# ==============================================================================
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# ==============================================================================
|
||||
|
||||
_juce_add_pips()
|
355
deps/juce/examples/Utilities/ChildProcessDemo.h
vendored
Normal file
355
deps/juce/examples/Utilities/ChildProcessDemo.h
vendored
Normal file
@ -0,0 +1,355 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE examples.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
|
||||
WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
|
||||
PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
/*******************************************************************************
|
||||
The block below describes the properties of this PIP. A PIP is a short snippet
|
||||
of code that can be read by the Projucer and used to generate a JUCE project.
|
||||
|
||||
BEGIN_JUCE_PIP_METADATA
|
||||
|
||||
name: ChildProcessDemo
|
||||
version: 1.0.0
|
||||
vendor: JUCE
|
||||
website: http://juce.com
|
||||
description: Launches applications as child processes.
|
||||
|
||||
dependencies: juce_core, juce_data_structures, juce_events, juce_graphics,
|
||||
juce_gui_basics
|
||||
exporters: xcode_mac, vs2019, linux_make
|
||||
|
||||
moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
|
||||
|
||||
type: Console
|
||||
mainClass: ChildProcessDemo
|
||||
|
||||
useLocalCopy: 1
|
||||
|
||||
END_JUCE_PIP_METADATA
|
||||
|
||||
*******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../Assets/DemoUtilities.h"
|
||||
|
||||
//==============================================================================
|
||||
// This is a token that's used at both ends of our parent-child processes, to
|
||||
// act as a unique token in the command line arguments.
|
||||
static const char* demoCommandLineUID = "demoUID";
|
||||
|
||||
// A few quick utility functions to convert between raw data and ValueTrees
|
||||
static ValueTree memoryBlockToValueTree (const MemoryBlock& mb)
|
||||
{
|
||||
return ValueTree::readFromData (mb.getData(), mb.getSize());
|
||||
}
|
||||
|
||||
static MemoryBlock valueTreeToMemoryBlock (const ValueTree& v)
|
||||
{
|
||||
MemoryOutputStream mo;
|
||||
v.writeToStream (mo);
|
||||
|
||||
return mo.getMemoryBlock();
|
||||
}
|
||||
|
||||
static String valueTreeToString (const ValueTree& v)
|
||||
{
|
||||
if (auto xml = v.createXml())
|
||||
return xml->toString (XmlElement::TextFormat().singleLine().withoutHeader());
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
class ChildProcessDemo : public Component,
|
||||
private MessageListener
|
||||
{
|
||||
public:
|
||||
ChildProcessDemo()
|
||||
{
|
||||
setOpaque (true);
|
||||
|
||||
addAndMakeVisible (launchButton);
|
||||
launchButton.onClick = [this] { launchChildProcess(); };
|
||||
|
||||
addAndMakeVisible (pingButton);
|
||||
pingButton.onClick = [this] { pingChildProcess(); };
|
||||
|
||||
addAndMakeVisible (killButton);
|
||||
killButton.onClick = [this] { killChildProcess(); };
|
||||
|
||||
addAndMakeVisible (testResultsBox);
|
||||
testResultsBox.setMultiLine (true);
|
||||
testResultsBox.setFont ({ Font::getDefaultMonospacedFontName(), 12.0f, Font::plain });
|
||||
|
||||
logMessage (String ("This demo uses the ChildProcessMaster and ChildProcessSlave classes to launch and communicate "
|
||||
"with a child process, sending messages in the form of serialised ValueTree objects.") + newLine);
|
||||
|
||||
setSize (500, 500);
|
||||
}
|
||||
|
||||
~ChildProcessDemo() override
|
||||
{
|
||||
masterProcess.reset();
|
||||
}
|
||||
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
g.fillAll (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::windowBackground));
|
||||
}
|
||||
|
||||
void resized() override
|
||||
{
|
||||
auto area = getLocalBounds();
|
||||
|
||||
auto top = area.removeFromTop (40);
|
||||
launchButton.setBounds (top.removeFromLeft (180).reduced (8));
|
||||
pingButton .setBounds (top.removeFromLeft (180).reduced (8));
|
||||
killButton .setBounds (top.removeFromLeft (180).reduced (8));
|
||||
|
||||
testResultsBox.setBounds (area.reduced (8));
|
||||
}
|
||||
|
||||
// Appends a message to the textbox that's shown in the demo as the console
|
||||
void logMessage (const String& message)
|
||||
{
|
||||
postMessage (new LogMessage (message));
|
||||
}
|
||||
|
||||
// invoked by the 'launch' button.
|
||||
void launchChildProcess()
|
||||
{
|
||||
if (masterProcess.get() == nullptr)
|
||||
{
|
||||
masterProcess.reset (new DemoMasterProcess (*this));
|
||||
|
||||
if (masterProcess->launchSlaveProcess (File::getSpecialLocation (File::currentExecutableFile), demoCommandLineUID))
|
||||
logMessage ("Child process started");
|
||||
}
|
||||
}
|
||||
|
||||
// invoked by the 'ping' button.
|
||||
void pingChildProcess()
|
||||
{
|
||||
if (masterProcess.get() != nullptr)
|
||||
masterProcess->sendPingMessageToSlave();
|
||||
else
|
||||
logMessage ("Child process is not running!");
|
||||
}
|
||||
|
||||
// invoked by the 'kill' button.
|
||||
void killChildProcess()
|
||||
{
|
||||
if (masterProcess.get() != nullptr)
|
||||
{
|
||||
masterProcess.reset();
|
||||
logMessage ("Child process killed");
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
// This class is used by the main process, acting as the master and receiving messages
|
||||
// from the slave process.
|
||||
class DemoMasterProcess : public ChildProcessMaster,
|
||||
private DeletedAtShutdown
|
||||
{
|
||||
public:
|
||||
DemoMasterProcess (ChildProcessDemo& d) : demo (d) {}
|
||||
|
||||
// This gets called when a message arrives from the slave process..
|
||||
void handleMessageFromSlave (const MemoryBlock& mb) override
|
||||
{
|
||||
auto incomingMessage = memoryBlockToValueTree (mb);
|
||||
|
||||
demo.logMessage ("Received: " + valueTreeToString (incomingMessage));
|
||||
}
|
||||
|
||||
// This gets called if the slave process dies.
|
||||
void handleConnectionLost() override
|
||||
{
|
||||
demo.logMessage ("Connection lost to child process!");
|
||||
demo.killChildProcess();
|
||||
}
|
||||
|
||||
void sendPingMessageToSlave()
|
||||
{
|
||||
ValueTree message ("MESSAGE");
|
||||
message.setProperty ("count", count++, nullptr);
|
||||
|
||||
demo.logMessage ("Sending: " + valueTreeToString (message));
|
||||
|
||||
sendMessageToSlave (valueTreeToMemoryBlock (message));
|
||||
}
|
||||
|
||||
ChildProcessDemo& demo;
|
||||
int count = 0;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
std::unique_ptr<DemoMasterProcess> masterProcess;
|
||||
|
||||
private:
|
||||
TextButton launchButton { "Launch Child Process" };
|
||||
TextButton pingButton { "Send Ping" };
|
||||
TextButton killButton { "Kill Child Process" };
|
||||
|
||||
TextEditor testResultsBox;
|
||||
|
||||
struct LogMessage : public Message
|
||||
{
|
||||
LogMessage (const String& m) : message (m) {}
|
||||
|
||||
String message;
|
||||
};
|
||||
|
||||
void handleMessage (const Message& message) override
|
||||
{
|
||||
testResultsBox.moveCaretToEnd();
|
||||
testResultsBox.insertTextAtCaret (static_cast<const LogMessage&> (message).message + newLine);
|
||||
testResultsBox.moveCaretToEnd();
|
||||
}
|
||||
|
||||
void lookAndFeelChanged() override
|
||||
{
|
||||
testResultsBox.applyFontToAllText (testResultsBox.getFont());
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChildProcessDemo)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/* This class gets instantiated in the child process, and receives messages from
|
||||
the master process.
|
||||
*/
|
||||
class DemoSlaveProcess : public ChildProcessSlave,
|
||||
private DeletedAtShutdown
|
||||
{
|
||||
public:
|
||||
DemoSlaveProcess() {}
|
||||
|
||||
void handleMessageFromMaster (const MemoryBlock& mb) override
|
||||
{
|
||||
ValueTree incomingMessage (memoryBlockToValueTree (mb));
|
||||
|
||||
/* In this demo we're only expecting one type of message, which will contain a 'count' parameter -
|
||||
we'll just increment that number and send back a new message containing the new number.
|
||||
|
||||
Obviously in a real app you'll probably want to look at the type of the message, and do
|
||||
some more interesting behaviour.
|
||||
*/
|
||||
|
||||
ValueTree reply ("REPLY");
|
||||
reply.setProperty ("countPlusOne", static_cast<int> (incomingMessage["count"]) + 1, nullptr);
|
||||
|
||||
sendMessageToMaster (valueTreeToMemoryBlock (reply));
|
||||
}
|
||||
|
||||
void handleConnectionMade() override
|
||||
{
|
||||
// This method is called when the connection is established, and in response, we'll just
|
||||
// send off a message to say hello.
|
||||
ValueTree reply ("HelloWorld");
|
||||
sendMessageToMaster (valueTreeToMemoryBlock (reply));
|
||||
}
|
||||
|
||||
/* If no pings are received from the master process for a number of seconds, then this will get invoked.
|
||||
Typically you'll want to use this as a signal to kill the process as quickly as possible, as you
|
||||
don't want to leave it hanging around as a zombie..
|
||||
*/
|
||||
void handleConnectionLost() override
|
||||
{
|
||||
JUCEApplication::quit();
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/* The JUCEApplication::initialise method calls this function to allow the
|
||||
child process to launch when the command line parameters indicate that we're
|
||||
being asked to run as a child process..
|
||||
*/
|
||||
bool invokeChildProcessDemo (const String& commandLine)
|
||||
{
|
||||
std::unique_ptr<DemoSlaveProcess> slave (new DemoSlaveProcess());
|
||||
|
||||
if (slave->initialiseFromCommandLine (commandLine, demoCommandLineUID))
|
||||
{
|
||||
slave.release(); // allow the slave object to stay alive - it'll handle its own deletion.
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifndef JUCE_DEMO_RUNNER
|
||||
//==============================================================================
|
||||
// As we need to modify the JUCEApplication::initialise method to launch the child process
|
||||
// based on the command line parameters, we can't just use the normal auto-generated Main.cpp.
|
||||
// Instead, we don't do anything in Main.cpp and create a JUCEApplication subclass here with
|
||||
// the necessary modifications.
|
||||
class Application : public JUCEApplication
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
Application() {}
|
||||
|
||||
const String getApplicationName() override { return "ChildProcessDemo"; }
|
||||
const String getApplicationVersion() override { return "1.0.0"; }
|
||||
|
||||
void initialise (const String& commandLine) override
|
||||
{
|
||||
// launches the child process if the command line parameters contain the demo UID
|
||||
if (invokeChildProcessDemo (commandLine))
|
||||
return;
|
||||
|
||||
mainWindow.reset (new MainWindow ("ChildProcessDemo", new ChildProcessDemo()));
|
||||
}
|
||||
|
||||
void shutdown() override { mainWindow = nullptr; }
|
||||
|
||||
private:
|
||||
class MainWindow : public DocumentWindow
|
||||
{
|
||||
public:
|
||||
MainWindow (const String& name, Component* c) : DocumentWindow (name,
|
||||
Desktop::getInstance().getDefaultLookAndFeel()
|
||||
.findColour (ResizableWindow::backgroundColourId),
|
||||
DocumentWindow::allButtons)
|
||||
{
|
||||
setUsingNativeTitleBar (true);
|
||||
setContentOwned (c, true);
|
||||
|
||||
centreWithSize (getWidth(), getHeight());
|
||||
|
||||
setVisible (true);
|
||||
}
|
||||
|
||||
void closeButtonPressed() override
|
||||
{
|
||||
JUCEApplication::getInstance()->systemRequestedQuit();
|
||||
}
|
||||
|
||||
private:
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainWindow)
|
||||
};
|
||||
|
||||
std::unique_ptr<MainWindow> mainWindow;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
START_JUCE_APPLICATION (Application)
|
||||
#endif
|
271
deps/juce/examples/Utilities/CryptographyDemo.h
vendored
Normal file
271
deps/juce/examples/Utilities/CryptographyDemo.h
vendored
Normal file
@ -0,0 +1,271 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE examples.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
|
||||
WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
|
||||
PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
/*******************************************************************************
|
||||
The block below describes the properties of this PIP. A PIP is a short snippet
|
||||
of code that can be read by the Projucer and used to generate a JUCE project.
|
||||
|
||||
BEGIN_JUCE_PIP_METADATA
|
||||
|
||||
name: CryptographyDemo
|
||||
version: 1.0.0
|
||||
vendor: JUCE
|
||||
website: http://juce.com
|
||||
description: Encrypts and decrypts data.
|
||||
|
||||
dependencies: juce_core, juce_cryptography, juce_data_structures, juce_events,
|
||||
juce_graphics, juce_gui_basics
|
||||
exporters: xcode_mac, vs2019, linux_make, androidstudio, xcode_iphone
|
||||
|
||||
moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
|
||||
|
||||
type: Component
|
||||
mainClass: CryptographyDemo
|
||||
|
||||
useLocalCopy: 1
|
||||
|
||||
END_JUCE_PIP_METADATA
|
||||
|
||||
*******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../Assets/DemoUtilities.h"
|
||||
|
||||
//==============================================================================
|
||||
class RSAComponent : public Component
|
||||
{
|
||||
public:
|
||||
RSAComponent()
|
||||
{
|
||||
addAndMakeVisible (rsaGroup);
|
||||
|
||||
addAndMakeVisible (bitSize);
|
||||
bitSize.setText (String (256));
|
||||
bitSizeLabel.attachToComponent (&bitSize, true);
|
||||
|
||||
addAndMakeVisible (generateRSAButton);
|
||||
generateRSAButton.onClick = [this] { createRSAKey(); };
|
||||
|
||||
addAndMakeVisible (rsaResultBox);
|
||||
rsaResultBox.setReadOnly (true);
|
||||
rsaResultBox.setMultiLine (true);
|
||||
}
|
||||
|
||||
void resized() override
|
||||
{
|
||||
auto area = getLocalBounds();
|
||||
rsaGroup.setBounds (area);
|
||||
area.removeFromTop (10);
|
||||
area.reduce (5, 5);
|
||||
|
||||
auto topArea = area.removeFromTop (34);
|
||||
topArea.removeFromLeft (110);
|
||||
bitSize.setBounds (topArea.removeFromLeft (topArea.getWidth() / 2).reduced (5));
|
||||
generateRSAButton.setBounds (topArea.reduced (5));
|
||||
|
||||
rsaResultBox.setBounds (area.reduced (5));
|
||||
}
|
||||
|
||||
private:
|
||||
void createRSAKey()
|
||||
{
|
||||
auto bits = jlimit (32, 1024, bitSize.getText().getIntValue());
|
||||
bitSize.setText (String (bits), dontSendNotification);
|
||||
|
||||
// Create a key-pair...
|
||||
RSAKey publicKey, privateKey;
|
||||
RSAKey::createKeyPair (publicKey, privateKey, bits);
|
||||
|
||||
// Test the new key on a piece of data...
|
||||
BigInteger testValue;
|
||||
testValue.parseString ("1234567890abcdef", 16);
|
||||
|
||||
auto encodedValue = testValue;
|
||||
publicKey.applyToValue (encodedValue);
|
||||
|
||||
auto decodedValue = encodedValue;
|
||||
privateKey.applyToValue (decodedValue);
|
||||
|
||||
// ..and show the results..
|
||||
String message;
|
||||
message << "Number of bits: " << bits << newLine
|
||||
<< "Public Key: " << publicKey .toString() << newLine
|
||||
<< "Private Key: " << privateKey.toString() << newLine
|
||||
<< newLine
|
||||
<< "Test input: " << testValue.toString (16) << newLine
|
||||
<< "Encoded: " << encodedValue.toString (16) << newLine
|
||||
<< "Decoded: " << decodedValue.toString (16) << newLine;
|
||||
|
||||
rsaResultBox.setText (message, false);
|
||||
}
|
||||
|
||||
GroupComponent rsaGroup { {}, "RSA Encryption" };
|
||||
TextButton generateRSAButton { "Generate RSA" };
|
||||
Label bitSizeLabel { {}, "Num Bits to Use:" };
|
||||
TextEditor bitSize, rsaResultBox;
|
||||
|
||||
void lookAndFeelChanged() override
|
||||
{
|
||||
rsaGroup.setColour (GroupComponent::outlineColourId,
|
||||
getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::outline,
|
||||
Colours::grey));
|
||||
rsaGroup.setColour (GroupComponent::textColourId,
|
||||
getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::defaultText,
|
||||
Colours::white));
|
||||
rsaResultBox.setColour (TextEditor::backgroundColourId,
|
||||
getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::widgetBackground,
|
||||
Colours::white.withAlpha (0.5f)));
|
||||
|
||||
bitSize.applyFontToAllText (bitSize.getFont());
|
||||
rsaResultBox.applyFontToAllText (rsaResultBox.getFont());
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (RSAComponent)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class HashesComponent : public Component
|
||||
{
|
||||
public:
|
||||
HashesComponent()
|
||||
{
|
||||
addAndMakeVisible (hashGroup);
|
||||
|
||||
addAndMakeVisible (hashEntryBox);
|
||||
hashEntryBox.setMultiLine (true);
|
||||
|
||||
hashEntryBox.setReturnKeyStartsNewLine (true);
|
||||
hashEntryBox.setText ("Type some text in this box and the resulting MD5, SHA and Whirlpool hashes will update below");
|
||||
|
||||
auto updateHashes = [this]
|
||||
{
|
||||
auto text = hashEntryBox.getText();
|
||||
|
||||
updateMD5Result (text.toUTF8());
|
||||
updateSHA256Result (text.toUTF8());
|
||||
updateWhirlpoolResult (text.toUTF8());
|
||||
};
|
||||
|
||||
hashEntryBox.onTextChange = updateHashes;
|
||||
hashEntryBox.onReturnKey = updateHashes;
|
||||
|
||||
hashLabel1.attachToComponent (&hashEntryBox, true);
|
||||
hashLabel2.attachToComponent (&md5Result, true);
|
||||
hashLabel3.attachToComponent (&shaResult, true);
|
||||
hashLabel4.attachToComponent (&whirlpoolResult, true);
|
||||
|
||||
addAndMakeVisible (md5Result);
|
||||
addAndMakeVisible (shaResult);
|
||||
addAndMakeVisible (whirlpoolResult);
|
||||
|
||||
updateHashes();
|
||||
}
|
||||
|
||||
void updateMD5Result (CharPointer_UTF8 text)
|
||||
{
|
||||
md5Result.setText (MD5 (text).toHexString(), dontSendNotification);
|
||||
}
|
||||
|
||||
void updateSHA256Result (CharPointer_UTF8 text)
|
||||
{
|
||||
shaResult.setText (SHA256 (text).toHexString(), dontSendNotification);
|
||||
}
|
||||
|
||||
void updateWhirlpoolResult (CharPointer_UTF8 text)
|
||||
{
|
||||
whirlpoolResult.setText (Whirlpool (text).toHexString(), dontSendNotification);
|
||||
}
|
||||
|
||||
void resized() override
|
||||
{
|
||||
auto area = getLocalBounds();
|
||||
|
||||
hashGroup.setBounds (area);
|
||||
|
||||
area.removeFromLeft (120);
|
||||
area.removeFromTop (10);
|
||||
area.reduce (5, 5);
|
||||
|
||||
whirlpoolResult.setBounds (area.removeFromBottom (34));
|
||||
shaResult .setBounds (area.removeFromBottom (34));
|
||||
md5Result .setBounds (area.removeFromBottom (34));
|
||||
hashEntryBox .setBounds (area.reduced (5));
|
||||
}
|
||||
|
||||
private:
|
||||
GroupComponent hashGroup { {}, "Hashes" };
|
||||
TextEditor hashEntryBox;
|
||||
Label md5Result, shaResult, whirlpoolResult;
|
||||
|
||||
Label hashLabel1 { {}, "Text to Hash:" };
|
||||
Label hashLabel2 { {}, "MD5 Result:" };
|
||||
Label hashLabel3 { {}, "SHA Result:" };
|
||||
Label hashLabel4 { {}, "Whirlpool Result:" };
|
||||
|
||||
void lookAndFeelChanged() override
|
||||
{
|
||||
hashGroup.setColour (GroupComponent::outlineColourId,
|
||||
getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::outline,
|
||||
Colours::grey));
|
||||
hashGroup.setColour (GroupComponent::textColourId,
|
||||
getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::defaultText,
|
||||
Colours::white));
|
||||
hashEntryBox.setColour (TextEditor::backgroundColourId,
|
||||
getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::widgetBackground,
|
||||
Colours::white.withAlpha (0.5f)));
|
||||
|
||||
hashEntryBox.applyFontToAllText (hashEntryBox.getFont());
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (HashesComponent)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class CryptographyDemo : public Component
|
||||
{
|
||||
public:
|
||||
CryptographyDemo()
|
||||
{
|
||||
addAndMakeVisible (rsaDemo);
|
||||
addAndMakeVisible (hashDemo);
|
||||
|
||||
setSize (750, 750);
|
||||
}
|
||||
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
g.fillAll (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::windowBackground,
|
||||
Colour::greyLevel (0.4f)));
|
||||
}
|
||||
|
||||
void resized() override
|
||||
{
|
||||
auto area = getLocalBounds();
|
||||
|
||||
rsaDemo .setBounds (area.removeFromTop (getHeight() / 2).reduced (5));
|
||||
hashDemo.setBounds (area.reduced (5));
|
||||
}
|
||||
|
||||
private:
|
||||
RSAComponent rsaDemo;
|
||||
HashesComponent hashDemo;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CryptographyDemo)
|
||||
};
|
599
deps/juce/examples/Utilities/InAppPurchasesDemo.h
vendored
Normal file
599
deps/juce/examples/Utilities/InAppPurchasesDemo.h
vendored
Normal file
@ -0,0 +1,599 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE examples.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
|
||||
WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
|
||||
PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
/*******************************************************************************
|
||||
The block below describes the properties of this PIP. A PIP is a short snippet
|
||||
of code that can be read by the Projucer and used to generate a JUCE project.
|
||||
|
||||
BEGIN_JUCE_PIP_METADATA
|
||||
|
||||
name: InAppPurchasesDemo
|
||||
version: 1.0.0
|
||||
vendor: JUCE
|
||||
website: http://juce.com
|
||||
description: Showcases in-app purchases features. To run this demo you must enable the
|
||||
"In-App Purchases Capability" option in the Projucer exporter.
|
||||
|
||||
dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats,
|
||||
juce_audio_processors, juce_audio_utils, juce_core,
|
||||
juce_cryptography, juce_data_structures, juce_events,
|
||||
juce_graphics, juce_gui_basics, juce_gui_extra,
|
||||
juce_product_unlocking
|
||||
exporters: xcode_mac, xcode_iphone, androidstudio
|
||||
|
||||
moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
|
||||
JUCE_IN_APP_PURCHASES=1
|
||||
|
||||
type: Component
|
||||
mainClass: InAppPurchasesDemo
|
||||
|
||||
useLocalCopy: 1
|
||||
|
||||
END_JUCE_PIP_METADATA
|
||||
|
||||
*******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../Assets/DemoUtilities.h"
|
||||
|
||||
/*
|
||||
To finish the setup of this demo, do the following in the Projucer project:
|
||||
|
||||
1. In the project settings, set the "Bundle Identifier" to com.rmsl.juceInAppPurchaseSample
|
||||
2. In the Android exporter settings, change the following settings:
|
||||
- "In-App Billing" - Enabled
|
||||
- "Key Signing: key.store" - path to InAppPurchase.keystore file in examples/Assets/Signing
|
||||
- "Key Signing: key.store.password" - amazingvoices
|
||||
- "Key Signing: key-alias" - InAppPurchase
|
||||
- "Key Signing: key.alias.password" - amazingvoices
|
||||
3. Re-save the project
|
||||
*/
|
||||
|
||||
//==============================================================================
|
||||
class VoicePurchases : private InAppPurchases::Listener
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
struct VoiceProduct
|
||||
{
|
||||
const char* identifier;
|
||||
const char* humanReadable;
|
||||
bool isPurchased, priceIsKnown, purchaseInProgress;
|
||||
String purchasePrice;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
VoicePurchases (AsyncUpdater& asyncUpdater)
|
||||
: guiUpdater (asyncUpdater)
|
||||
{
|
||||
voiceProducts = Array<VoiceProduct>(
|
||||
{ VoiceProduct {"robot", "Robot", true, true, false, "Free" },
|
||||
VoiceProduct {"jules", "Jules", false, false, false, "Retrieving price..." },
|
||||
VoiceProduct {"fabian", "Fabian", false, false, false, "Retrieving price..." },
|
||||
VoiceProduct {"ed", "Ed", false, false, false, "Retrieving price..." },
|
||||
VoiceProduct {"lukasz", "Lukasz", false, false, false, "Retrieving price..." },
|
||||
VoiceProduct {"jb", "JB", false, false, false, "Retrieving price..." } });
|
||||
}
|
||||
|
||||
~VoicePurchases() override
|
||||
{
|
||||
InAppPurchases::getInstance()->removeListener (this);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
VoiceProduct getPurchase (int voiceIndex)
|
||||
{
|
||||
if (! havePurchasesBeenRestored)
|
||||
{
|
||||
havePurchasesBeenRestored = true;
|
||||
InAppPurchases::getInstance()->addListener (this);
|
||||
|
||||
InAppPurchases::getInstance()->restoreProductsBoughtList (true);
|
||||
}
|
||||
|
||||
return voiceProducts[voiceIndex];
|
||||
}
|
||||
|
||||
void purchaseVoice (int voiceIndex)
|
||||
{
|
||||
if (havePricesBeenFetched && isPositiveAndBelow (voiceIndex, voiceProducts.size()))
|
||||
{
|
||||
auto& product = voiceProducts.getReference (voiceIndex);
|
||||
|
||||
if (! product.isPurchased)
|
||||
{
|
||||
purchaseInProgress = true;
|
||||
|
||||
product.purchaseInProgress = true;
|
||||
InAppPurchases::getInstance()->purchaseProduct (product.identifier);
|
||||
|
||||
guiUpdater.triggerAsyncUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StringArray getVoiceNames() const
|
||||
{
|
||||
StringArray names;
|
||||
|
||||
for (auto& voiceProduct : voiceProducts)
|
||||
names.add (voiceProduct.humanReadable);
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
bool isPurchaseInProgress() const noexcept { return purchaseInProgress; }
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
void productsInfoReturned (const Array<InAppPurchases::Product>& products) override
|
||||
{
|
||||
if (! InAppPurchases::getInstance()->isInAppPurchasesSupported())
|
||||
{
|
||||
for (auto idx = 1; idx < voiceProducts.size(); ++idx)
|
||||
{
|
||||
auto& voiceProduct = voiceProducts.getReference (idx);
|
||||
|
||||
voiceProduct.isPurchased = false;
|
||||
voiceProduct.priceIsKnown = false;
|
||||
voiceProduct.purchasePrice = "In-App purchases unavailable";
|
||||
}
|
||||
|
||||
AlertWindow::showMessageBoxAsync (MessageBoxIconType::WarningIcon,
|
||||
"In-app purchase is unavailable!",
|
||||
"In-App purchases are not available. This either means you are trying "
|
||||
"to use IAP on a platform that does not support IAP or you haven't setup "
|
||||
"your app correctly to work with IAP.",
|
||||
"OK");
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto product : products)
|
||||
{
|
||||
auto idx = findVoiceIndexFromIdentifier (product.identifier);
|
||||
|
||||
if (isPositiveAndBelow (idx, voiceProducts.size()))
|
||||
{
|
||||
auto& voiceProduct = voiceProducts.getReference (idx);
|
||||
|
||||
voiceProduct.priceIsKnown = true;
|
||||
voiceProduct.purchasePrice = product.price;
|
||||
}
|
||||
}
|
||||
|
||||
AlertWindow::showMessageBoxAsync (MessageBoxIconType::WarningIcon,
|
||||
"Your credit card will be charged!",
|
||||
"You are running the sample code for JUCE In-App purchases. "
|
||||
"Although this is only sample code, it will still CHARGE YOUR CREDIT CARD!",
|
||||
"Understood!");
|
||||
}
|
||||
|
||||
guiUpdater.triggerAsyncUpdate();
|
||||
}
|
||||
|
||||
void productPurchaseFinished (const PurchaseInfo& info, bool success, const String&) override
|
||||
{
|
||||
purchaseInProgress = false;
|
||||
|
||||
auto idx = findVoiceIndexFromIdentifier (info.purchase.productId);
|
||||
|
||||
if (isPositiveAndBelow (idx, voiceProducts.size()))
|
||||
{
|
||||
auto& voiceProduct = voiceProducts.getReference (idx);
|
||||
|
||||
voiceProduct.isPurchased = success;
|
||||
voiceProduct.purchaseInProgress = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// On failure Play Store will not tell us which purchase failed
|
||||
for (auto& voiceProduct : voiceProducts)
|
||||
voiceProduct.purchaseInProgress = false;
|
||||
}
|
||||
|
||||
guiUpdater.triggerAsyncUpdate();
|
||||
}
|
||||
|
||||
void purchasesListRestored (const Array<PurchaseInfo>& infos, bool success, const String&) override
|
||||
{
|
||||
if (success)
|
||||
{
|
||||
for (auto& info : infos)
|
||||
{
|
||||
auto idx = findVoiceIndexFromIdentifier (info.purchase.productId);
|
||||
|
||||
if (isPositiveAndBelow (idx, voiceProducts.size()))
|
||||
{
|
||||
auto& voiceProduct = voiceProducts.getReference (idx);
|
||||
|
||||
voiceProduct.isPurchased = true;
|
||||
}
|
||||
}
|
||||
|
||||
guiUpdater.triggerAsyncUpdate();
|
||||
}
|
||||
|
||||
if (! havePricesBeenFetched)
|
||||
{
|
||||
havePricesBeenFetched = true;
|
||||
StringArray identifiers;
|
||||
|
||||
for (auto& voiceProduct : voiceProducts)
|
||||
identifiers.add (voiceProduct.identifier);
|
||||
|
||||
InAppPurchases::getInstance()->getProductsInformation (identifiers);
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
int findVoiceIndexFromIdentifier (String identifier) const
|
||||
{
|
||||
identifier = identifier.toLowerCase();
|
||||
|
||||
for (auto i = 0; i < voiceProducts.size(); ++i)
|
||||
if (String (voiceProducts.getReference (i).identifier) == identifier)
|
||||
return i;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
AsyncUpdater& guiUpdater;
|
||||
bool havePurchasesBeenRestored = false, havePricesBeenFetched = false, purchaseInProgress = false;
|
||||
Array<VoiceProduct> voiceProducts;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VoicePurchases)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class PhraseModel : public ListBoxModel
|
||||
{
|
||||
public:
|
||||
PhraseModel() {}
|
||||
|
||||
int getNumRows() override { return phrases.size(); }
|
||||
|
||||
void paintListBoxItem (int row, Graphics& g, int w, int h, bool isSelected) override
|
||||
{
|
||||
Rectangle<int> r (0, 0, w, h);
|
||||
|
||||
auto& lf = Desktop::getInstance().getDefaultLookAndFeel();
|
||||
g.setColour (lf.findColour (isSelected ? (int) TextEditor::highlightColourId : (int) ListBox::backgroundColourId));
|
||||
g.fillRect (r);
|
||||
|
||||
g.setColour (lf.findColour (ListBox::textColourId));
|
||||
|
||||
g.setFont (18);
|
||||
|
||||
String phrase = (isPositiveAndBelow (row, phrases.size()) ? phrases[row] : String{});
|
||||
g.drawText (phrase, 10, 0, w, h, Justification::centredLeft);
|
||||
}
|
||||
|
||||
private:
|
||||
StringArray phrases {"I love JUCE!", "The five dimensions of touch", "Make it fast!"};
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PhraseModel)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class VoiceModel : public ListBoxModel
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
class VoiceRow : public Component,
|
||||
private Timer
|
||||
{
|
||||
public:
|
||||
VoiceRow (VoicePurchases& voicePurchases) : purchases (voicePurchases)
|
||||
{
|
||||
addAndMakeVisible (nameLabel);
|
||||
addAndMakeVisible (purchaseButton);
|
||||
addAndMakeVisible (priceLabel);
|
||||
|
||||
purchaseButton.onClick = [this] { clickPurchase(); };
|
||||
|
||||
voices = purchases.getVoiceNames();
|
||||
|
||||
setSize (600, 33);
|
||||
}
|
||||
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
auto r = getLocalBounds().reduced (4);
|
||||
{
|
||||
auto voiceIconBounds = r.removeFromLeft (r.getHeight());
|
||||
g.setColour (Colours::black);
|
||||
g.drawRect (voiceIconBounds);
|
||||
|
||||
voiceIconBounds.reduce (1, 1);
|
||||
g.setColour (hasBeenPurchased ? Colours::white : Colours::grey);
|
||||
g.fillRect (voiceIconBounds);
|
||||
|
||||
g.drawImage (avatar, voiceIconBounds.toFloat());
|
||||
|
||||
if (! hasBeenPurchased)
|
||||
{
|
||||
g.setColour (Colours::white.withAlpha (0.8f));
|
||||
g.fillRect (voiceIconBounds);
|
||||
|
||||
if (purchaseInProgress)
|
||||
getLookAndFeel().drawSpinningWaitAnimation (g, Colours::darkgrey,
|
||||
voiceIconBounds.getX(),
|
||||
voiceIconBounds.getY(),
|
||||
voiceIconBounds.getWidth(),
|
||||
voiceIconBounds.getHeight());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void resized() override
|
||||
{
|
||||
auto r = getLocalBounds().reduced (4 + 8, 4);
|
||||
auto h = r.getHeight();
|
||||
auto w = static_cast<int> (h * 1.5);
|
||||
|
||||
r.removeFromLeft (h);
|
||||
purchaseButton.setBounds (r.removeFromRight (w).withSizeKeepingCentre (w, h / 2));
|
||||
|
||||
nameLabel.setBounds (r.removeFromTop (18));
|
||||
priceLabel.setBounds (r.removeFromTop (18));
|
||||
}
|
||||
|
||||
void update (int rowNumber, bool rowIsSelected)
|
||||
{
|
||||
isSelected = rowIsSelected;
|
||||
rowSelected = rowNumber;
|
||||
|
||||
if (isPositiveAndBelow (rowNumber, voices.size()))
|
||||
{
|
||||
auto imageResourceName = voices[rowNumber] + ".png";
|
||||
|
||||
nameLabel.setText (voices[rowNumber], NotificationType::dontSendNotification);
|
||||
|
||||
auto purchase = purchases.getPurchase (rowNumber);
|
||||
hasBeenPurchased = purchase.isPurchased;
|
||||
purchaseInProgress = purchase.purchaseInProgress;
|
||||
|
||||
if (purchaseInProgress)
|
||||
startTimer (1000 / 50);
|
||||
else
|
||||
stopTimer();
|
||||
|
||||
nameLabel.setFont (Font (16).withStyle (Font::bold | (hasBeenPurchased ? 0 : Font::italic)));
|
||||
nameLabel.setColour (Label::textColourId, hasBeenPurchased ? Colours::white : Colours::grey);
|
||||
|
||||
priceLabel.setFont (Font (10).withStyle (purchase.priceIsKnown ? 0 : Font::italic));
|
||||
priceLabel.setColour (Label::textColourId, hasBeenPurchased ? Colours::white : Colours::grey);
|
||||
priceLabel.setText (purchase.purchasePrice, NotificationType::dontSendNotification);
|
||||
|
||||
if (rowNumber == 0)
|
||||
{
|
||||
purchaseButton.setButtonText ("Internal");
|
||||
purchaseButton.setEnabled (false);
|
||||
}
|
||||
else
|
||||
{
|
||||
purchaseButton.setButtonText (hasBeenPurchased ? "Purchased" : "Purchase");
|
||||
purchaseButton.setEnabled (! hasBeenPurchased && purchase.priceIsKnown);
|
||||
}
|
||||
|
||||
setInterceptsMouseClicks (! hasBeenPurchased, ! hasBeenPurchased);
|
||||
|
||||
if (auto fileStream = createAssetInputStream (String ("Purchases/" + String (imageResourceName)).toRawUTF8()))
|
||||
avatar = PNGImageFormat().decodeImage (*fileStream);
|
||||
}
|
||||
}
|
||||
private:
|
||||
//==============================================================================
|
||||
void clickPurchase()
|
||||
{
|
||||
if (rowSelected >= 0)
|
||||
{
|
||||
if (! hasBeenPurchased)
|
||||
{
|
||||
purchases.purchaseVoice (rowSelected);
|
||||
purchaseInProgress = true;
|
||||
startTimer (1000 / 50);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void timerCallback() override { repaint(); }
|
||||
|
||||
//==============================================================================
|
||||
bool isSelected = false, hasBeenPurchased = false, purchaseInProgress = false;
|
||||
int rowSelected = -1;
|
||||
Image avatar;
|
||||
|
||||
StringArray voices;
|
||||
|
||||
VoicePurchases& purchases;
|
||||
|
||||
Label nameLabel, priceLabel;
|
||||
TextButton purchaseButton {"Purchase"};
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VoiceRow)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
VoiceModel (VoicePurchases& voicePurchases) : purchases (voicePurchases)
|
||||
{
|
||||
voiceProducts = purchases.getVoiceNames();
|
||||
}
|
||||
|
||||
int getNumRows() override { return voiceProducts.size(); }
|
||||
|
||||
Component* refreshComponentForRow (int row, bool selected, Component* existing) override
|
||||
{
|
||||
if (isPositiveAndBelow (row, voiceProducts.size()))
|
||||
{
|
||||
if (existing == nullptr)
|
||||
existing = new VoiceRow (purchases);
|
||||
|
||||
if (auto* voiceRow = dynamic_cast<VoiceRow*> (existing))
|
||||
voiceRow->update (row, selected);
|
||||
|
||||
return existing;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void paintListBoxItem (int, Graphics& g, int w, int h, bool isSelected) override
|
||||
{
|
||||
auto r = Rectangle<int> (0, 0, w, h).reduced (4);
|
||||
|
||||
auto& lf = Desktop::getInstance().getDefaultLookAndFeel();
|
||||
g.setColour (lf.findColour (isSelected ? (int) TextEditor::highlightColourId : (int) ListBox::backgroundColourId));
|
||||
g.fillRect (r);
|
||||
}
|
||||
|
||||
private:
|
||||
StringArray voiceProducts;
|
||||
|
||||
VoicePurchases& purchases;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VoiceModel)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class InAppPurchasesDemo : public Component,
|
||||
private AsyncUpdater
|
||||
{
|
||||
public:
|
||||
InAppPurchasesDemo()
|
||||
{
|
||||
Desktop::getInstance().getDefaultLookAndFeel().setUsingNativeAlertWindows (true);
|
||||
|
||||
dm.addAudioCallback (&player);
|
||||
dm.initialiseWithDefaultDevices (0, 2);
|
||||
|
||||
setOpaque (true);
|
||||
|
||||
phraseListBox.setModel (phraseModel.get());
|
||||
voiceListBox .setModel (voiceModel.get());
|
||||
|
||||
phraseListBox.setRowHeight (33);
|
||||
phraseListBox.selectRow (0);
|
||||
phraseListBox.updateContent();
|
||||
|
||||
voiceListBox.setRowHeight (66);
|
||||
voiceListBox.selectRow (0);
|
||||
voiceListBox.updateContent();
|
||||
voiceListBox.getViewport()->setScrollOnDragEnabled (true);
|
||||
|
||||
addAndMakeVisible (phraseLabel);
|
||||
addAndMakeVisible (phraseListBox);
|
||||
addAndMakeVisible (playStopButton);
|
||||
addAndMakeVisible (voiceLabel);
|
||||
addAndMakeVisible (voiceListBox);
|
||||
|
||||
playStopButton.onClick = [this] { playStopPhrase(); };
|
||||
|
||||
soundNames = purchases.getVoiceNames();
|
||||
|
||||
#if JUCE_ANDROID || JUCE_IOS
|
||||
auto screenBounds = Desktop::getInstance().getDisplays().getPrimaryDisplay()->userArea;
|
||||
setSize (screenBounds.getWidth(), screenBounds.getHeight());
|
||||
#else
|
||||
setSize (800, 600);
|
||||
#endif
|
||||
}
|
||||
|
||||
~InAppPurchasesDemo() override
|
||||
{
|
||||
dm.closeAudioDevice();
|
||||
dm.removeAudioCallback (&player);
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
void handleAsyncUpdate() override
|
||||
{
|
||||
voiceListBox.updateContent();
|
||||
voiceListBox.setEnabled (! purchases.isPurchaseInProgress());
|
||||
voiceListBox.repaint();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void resized() override
|
||||
{
|
||||
auto r = getLocalBounds().reduced (20);
|
||||
|
||||
{
|
||||
auto phraseArea = r.removeFromTop (r.getHeight() / 2);
|
||||
|
||||
phraseLabel .setBounds (phraseArea.removeFromTop (36).reduced (0, 10));
|
||||
playStopButton.setBounds (phraseArea.removeFromBottom (50).reduced (0, 10));
|
||||
phraseListBox .setBounds (phraseArea);
|
||||
}
|
||||
|
||||
{
|
||||
auto voiceArea = r;
|
||||
|
||||
voiceLabel .setBounds (voiceArea.removeFromTop (36).reduced (0, 10));
|
||||
voiceListBox.setBounds (voiceArea);
|
||||
}
|
||||
}
|
||||
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
g.fillAll (Desktop::getInstance().getDefaultLookAndFeel()
|
||||
.findColour (ResizableWindow::backgroundColourId));
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void playStopPhrase()
|
||||
{
|
||||
auto idx = voiceListBox.getSelectedRow();
|
||||
if (isPositiveAndBelow (idx, soundNames.size()))
|
||||
{
|
||||
auto assetName = "Purchases/" + soundNames[idx] + String (phraseListBox.getSelectedRow()) + ".ogg";
|
||||
|
||||
if (auto fileStream = createAssetInputStream (assetName.toRawUTF8()))
|
||||
{
|
||||
currentPhraseData.reset();
|
||||
fileStream->readIntoMemoryBlock (currentPhraseData);
|
||||
|
||||
player.play (currentPhraseData.getData(), currentPhraseData.getSize());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
StringArray soundNames;
|
||||
|
||||
Label phraseLabel { "phraseLabel", NEEDS_TRANS ("Phrases:") };
|
||||
ListBox phraseListBox { "phraseListBox" };
|
||||
std::unique_ptr<ListBoxModel> phraseModel { new PhraseModel() };
|
||||
TextButton playStopButton { "Play" };
|
||||
|
||||
SoundPlayer player;
|
||||
VoicePurchases purchases { *this };
|
||||
AudioDeviceManager dm;
|
||||
|
||||
Label voiceLabel { "voiceLabel", NEEDS_TRANS ("Voices:") };
|
||||
ListBox voiceListBox { "voiceListBox" };
|
||||
std::unique_ptr<VoiceModel> voiceModel { new VoiceModel (purchases) };
|
||||
|
||||
MemoryBlock currentPhraseData;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (InAppPurchasesDemo)
|
||||
};
|
185
deps/juce/examples/Utilities/JavaScriptDemo.h
vendored
Normal file
185
deps/juce/examples/Utilities/JavaScriptDemo.h
vendored
Normal file
@ -0,0 +1,185 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE examples.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
|
||||
WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
|
||||
PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
/*******************************************************************************
|
||||
The block below describes the properties of this PIP. A PIP is a short snippet
|
||||
of code that can be read by the Projucer and used to generate a JUCE project.
|
||||
|
||||
BEGIN_JUCE_PIP_METADATA
|
||||
|
||||
name: JavaScriptDemo
|
||||
version: 1.0.0
|
||||
vendor: JUCE
|
||||
website: http://juce.com
|
||||
description: Showcases JavaScript features.
|
||||
|
||||
dependencies: juce_core, juce_data_structures, juce_events, juce_graphics,
|
||||
juce_gui_basics, juce_gui_extra
|
||||
exporters: xcode_mac, vs2019, linux_make, androidstudio, xcode_iphone
|
||||
|
||||
moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
|
||||
|
||||
type: Component
|
||||
mainClass: JavaScriptDemo
|
||||
|
||||
useLocalCopy: 1
|
||||
|
||||
END_JUCE_PIP_METADATA
|
||||
|
||||
*******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../Assets/DemoUtilities.h"
|
||||
|
||||
//==============================================================================
|
||||
class JavaScriptDemo : public Component,
|
||||
private CodeDocument::Listener,
|
||||
private Timer
|
||||
{
|
||||
public:
|
||||
JavaScriptDemo()
|
||||
{
|
||||
setOpaque (true);
|
||||
|
||||
editor.reset (new CodeEditorComponent (codeDocument, nullptr));
|
||||
addAndMakeVisible (editor.get());
|
||||
editor->setFont ({ Font::getDefaultMonospacedFontName(), 14.0f, Font::plain });
|
||||
editor->setTabSize (4, true);
|
||||
|
||||
outputDisplay.setMultiLine (true);
|
||||
outputDisplay.setReadOnly (true);
|
||||
outputDisplay.setCaretVisible (false);
|
||||
outputDisplay.setFont ({ Font::getDefaultMonospacedFontName(), 14.0f, Font::plain });
|
||||
addAndMakeVisible (outputDisplay);
|
||||
|
||||
codeDocument.addListener (this);
|
||||
|
||||
editor->loadContent (
|
||||
"/*\n"
|
||||
" Javascript! In this simple demo, the native\n"
|
||||
" code provides an object called \'Demo\' which\n"
|
||||
" has a method \'print\' that writes to the\n"
|
||||
" console below...\n"
|
||||
"*/\n"
|
||||
"\n"
|
||||
"Demo.print (\"Hello World in JUCE + Javascript!\");\n"
|
||||
"Demo.print (\"\");\n"
|
||||
"\n"
|
||||
"function factorial (n)\n"
|
||||
"{\n"
|
||||
" var total = 1;\n"
|
||||
" while (n > 0)\n"
|
||||
" total = total * n--;\n"
|
||||
" return total;\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"for (var i = 1; i < 10; ++i)\n"
|
||||
" Demo.print (\"Factorial of \" + i \n"
|
||||
" + \" = \" + factorial (i));\n");
|
||||
|
||||
setSize (600, 750);
|
||||
}
|
||||
|
||||
void runScript()
|
||||
{
|
||||
outputDisplay.clear();
|
||||
|
||||
JavascriptEngine engine;
|
||||
engine.maximumExecutionTime = RelativeTime::seconds (5);
|
||||
engine.registerNativeObject ("Demo", new DemoClass (*this));
|
||||
|
||||
auto startTime = Time::getMillisecondCounterHiRes();
|
||||
|
||||
auto result = engine.execute (codeDocument.getAllContent());
|
||||
|
||||
auto elapsedMs = Time::getMillisecondCounterHiRes() - startTime;
|
||||
|
||||
if (result.failed())
|
||||
outputDisplay.setText (result.getErrorMessage());
|
||||
else
|
||||
outputDisplay.insertTextAtCaret ("\n(Execution time: " + String (elapsedMs, 2) + " milliseconds)");
|
||||
}
|
||||
|
||||
void consoleOutput (const String& message)
|
||||
{
|
||||
outputDisplay.moveCaretToEnd();
|
||||
outputDisplay.insertTextAtCaret (message + newLine);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
// This class is used by the script, and provides methods that the JS can call.
|
||||
struct DemoClass : public DynamicObject
|
||||
{
|
||||
DemoClass (JavaScriptDemo& demo) : owner (demo)
|
||||
{
|
||||
setMethod ("print", print);
|
||||
}
|
||||
|
||||
static Identifier getClassName() { return "Demo"; }
|
||||
|
||||
static var print (const var::NativeFunctionArgs& args)
|
||||
{
|
||||
if (args.numArguments > 0)
|
||||
if (auto* thisObject = dynamic_cast<DemoClass*> (args.thisObject.getObject()))
|
||||
thisObject->owner.consoleOutput (args.arguments[0].toString());
|
||||
|
||||
return var::undefined();
|
||||
}
|
||||
|
||||
JavaScriptDemo& owner;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DemoClass)
|
||||
};
|
||||
|
||||
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
g.fillAll (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::windowBackground));
|
||||
}
|
||||
|
||||
private:
|
||||
CodeDocument codeDocument;
|
||||
std::unique_ptr<CodeEditorComponent> editor;
|
||||
TextEditor outputDisplay;
|
||||
|
||||
void codeDocumentTextInserted (const String&, int) override { startTimer (300); }
|
||||
void codeDocumentTextDeleted (int, int) override { startTimer (300); }
|
||||
|
||||
void timerCallback() override
|
||||
{
|
||||
stopTimer();
|
||||
runScript();
|
||||
}
|
||||
|
||||
void resized() override
|
||||
{
|
||||
auto r = getLocalBounds().reduced (8);
|
||||
|
||||
editor->setBounds (r.removeFromTop (proportionOfHeight (0.6f)));
|
||||
outputDisplay.setBounds (r.withTrimmedTop (8));
|
||||
}
|
||||
|
||||
void lookAndFeelChanged() override
|
||||
{
|
||||
outputDisplay.applyFontToAllText (outputDisplay.getFont());
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JavaScriptDemo)
|
||||
};
|
132
deps/juce/examples/Utilities/LiveConstantDemo.h
vendored
Normal file
132
deps/juce/examples/Utilities/LiveConstantDemo.h
vendored
Normal file
@ -0,0 +1,132 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE examples.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
|
||||
WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
|
||||
PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
/*******************************************************************************
|
||||
The block below describes the properties of this PIP. A PIP is a short snippet
|
||||
of code that can be read by the Projucer and used to generate a JUCE project.
|
||||
|
||||
BEGIN_JUCE_PIP_METADATA
|
||||
|
||||
name: LiveConstantDemo
|
||||
version: 1.0.0
|
||||
vendor: JUCE
|
||||
website: http://juce.com
|
||||
description: Demonstrates the live constant macro.
|
||||
|
||||
dependencies: juce_core, juce_data_structures, juce_events, juce_graphics,
|
||||
juce_gui_basics, juce_gui_extra
|
||||
exporters: xcode_mac, vs2019, linux_make, androidstudio, xcode_iphone
|
||||
|
||||
moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
|
||||
|
||||
type: Component
|
||||
mainClass: LiveConstantDemo
|
||||
|
||||
useLocalCopy: 1
|
||||
|
||||
END_JUCE_PIP_METADATA
|
||||
|
||||
*******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../Assets/DemoUtilities.h"
|
||||
|
||||
//==============================================================================
|
||||
struct LiveConstantDemoComponent : public Component
|
||||
{
|
||||
LiveConstantDemoComponent() {}
|
||||
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
g.fillAll (JUCE_LIVE_CONSTANT (Colour (0xffe5e7a7)));
|
||||
|
||||
g.setColour (JUCE_LIVE_CONSTANT (Colours::red.withAlpha (0.2f)));
|
||||
auto blockWidth = JUCE_LIVE_CONSTANT (0x120);
|
||||
auto blockHeight = JUCE_LIVE_CONSTANT (200);
|
||||
g.fillRect ((getWidth() - blockWidth) / 2, (getHeight() - blockHeight) / 2, blockWidth, blockHeight);
|
||||
|
||||
auto fontColour = JUCE_LIVE_CONSTANT (Colour (0xff000a55));
|
||||
auto fontSize = JUCE_LIVE_CONSTANT (30.0f);
|
||||
|
||||
g.setColour (fontColour);
|
||||
g.setFont (fontSize);
|
||||
|
||||
g.drawFittedText (getDemoText(), getLocalBounds(), Justification::centred, 2);
|
||||
}
|
||||
|
||||
static String getDemoText()
|
||||
{
|
||||
return JUCE_LIVE_CONSTANT ("Hello world!");
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class LiveConstantDemo : public Component
|
||||
{
|
||||
public:
|
||||
LiveConstantDemo()
|
||||
{
|
||||
descriptionLabel.setMinimumHorizontalScale (1.0f);
|
||||
descriptionLabel.setText ("This demonstrates the JUCE_LIVE_CONSTANT macro, which allows you to quickly "
|
||||
"adjust primitive values at runtime by just wrapping them in a macro.\n\n"
|
||||
"To understand what's going on in this demo, you should have a look at the "
|
||||
"LiveConstantDemoComponent class, where you can see the code that's invoking the demo below...",
|
||||
dontSendNotification);
|
||||
|
||||
addAndMakeVisible (descriptionLabel);
|
||||
addAndMakeVisible (startButton);
|
||||
addChildComponent (demoComp);
|
||||
startButton.onClick = [this] { start(); };
|
||||
|
||||
setSize (500, 500);
|
||||
}
|
||||
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
g.fillAll (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::windowBackground));
|
||||
}
|
||||
|
||||
void resized() override
|
||||
{
|
||||
auto r = getLocalBounds().reduced (10);
|
||||
|
||||
descriptionLabel.setBounds (r.removeFromTop (200));
|
||||
startButton .setBounds (r.removeFromTop (22).removeFromLeft (250));
|
||||
demoComp .setBounds (r.withTrimmedTop (10));
|
||||
}
|
||||
|
||||
void start()
|
||||
{
|
||||
startButton.setVisible (false);
|
||||
demoComp .setVisible (true);
|
||||
|
||||
descriptionLabel.setText ("Tweak some of the colours and values in the pop-up window to see what "
|
||||
"the effect of your changes would be on the component below...",
|
||||
dontSendNotification);
|
||||
}
|
||||
|
||||
private:
|
||||
Label descriptionLabel;
|
||||
TextButton startButton { "Begin Demo" };
|
||||
|
||||
LiveConstantDemoComponent demoComp;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LiveConstantDemo)
|
||||
};
|
329
deps/juce/examples/Utilities/MultithreadingDemo.h
vendored
Normal file
329
deps/juce/examples/Utilities/MultithreadingDemo.h
vendored
Normal file
@ -0,0 +1,329 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE examples.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
|
||||
WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
|
||||
PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
/*******************************************************************************
|
||||
The block below describes the properties of this PIP. A PIP is a short snippet
|
||||
of code that can be read by the Projucer and used to generate a JUCE project.
|
||||
|
||||
BEGIN_JUCE_PIP_METADATA
|
||||
|
||||
name: MultithreadingDemo
|
||||
version: 1.0.0
|
||||
vendor: JUCE
|
||||
website: http://juce.com
|
||||
description: Demonstrates multi-threading.
|
||||
|
||||
dependencies: juce_core, juce_data_structures, juce_events, juce_graphics,
|
||||
juce_gui_basics
|
||||
exporters: xcode_mac, vs2019, linux_make, androidstudio, xcode_iphone
|
||||
|
||||
moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
|
||||
|
||||
type: Component
|
||||
mainClass: MultithreadingDemo
|
||||
|
||||
useLocalCopy: 1
|
||||
|
||||
END_JUCE_PIP_METADATA
|
||||
|
||||
*******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../Assets/DemoUtilities.h"
|
||||
|
||||
//==============================================================================
|
||||
class BouncingBall : private ComponentListener
|
||||
{
|
||||
public:
|
||||
BouncingBall (Component& comp)
|
||||
: containerComponent (comp)
|
||||
{
|
||||
containerComponent.addComponentListener (this);
|
||||
|
||||
auto speed = 5.0f; // give each ball a fixed speed so we can
|
||||
// see the effects of thread priority on how fast
|
||||
// they actually go.
|
||||
|
||||
auto angle = Random::getSystemRandom().nextFloat() * MathConstants<float>::twoPi;
|
||||
|
||||
dx = std::sin (angle) * speed;
|
||||
dy = std::cos (angle) * speed;
|
||||
|
||||
colour = Colour ((juce::uint32) Random::getSystemRandom().nextInt())
|
||||
.withAlpha (0.5f)
|
||||
.withBrightness (0.7f);
|
||||
|
||||
componentMovedOrResized (containerComponent, true, true);
|
||||
|
||||
x = Random::getSystemRandom().nextFloat() * parentWidth;
|
||||
y = Random::getSystemRandom().nextFloat() * parentHeight;
|
||||
}
|
||||
|
||||
~BouncingBall() override
|
||||
{
|
||||
containerComponent.removeComponentListener (this);
|
||||
}
|
||||
|
||||
// This will be called from the message thread
|
||||
void draw (Graphics& g)
|
||||
{
|
||||
const ScopedLock lock (drawing);
|
||||
|
||||
g.setColour (colour);
|
||||
g.fillEllipse (x, y, size, size);
|
||||
|
||||
g.setColour (Colours::black);
|
||||
g.setFont (10.0f);
|
||||
g.drawText (String::toHexString ((int64) threadId), Rectangle<float> (x, y, size, size), Justification::centred, false);
|
||||
}
|
||||
|
||||
void moveBall()
|
||||
{
|
||||
const ScopedLock lock (drawing);
|
||||
|
||||
threadId = Thread::getCurrentThreadId(); // this is so the component can print the thread ID inside the ball
|
||||
|
||||
x += dx;
|
||||
y += dy;
|
||||
|
||||
if (x < 0)
|
||||
dx = std::abs (dx);
|
||||
|
||||
if (x > parentWidth)
|
||||
dx = -std::abs (dx);
|
||||
|
||||
if (y < 0)
|
||||
dy = std::abs (dy);
|
||||
|
||||
if (y > parentHeight)
|
||||
dy = -std::abs (dy);
|
||||
}
|
||||
|
||||
private:
|
||||
void componentMovedOrResized (Component& comp, bool, bool) override
|
||||
{
|
||||
const ScopedLock lock (drawing);
|
||||
|
||||
parentWidth = (float) comp.getWidth() - size;
|
||||
parentHeight = (float) comp.getHeight() - size;
|
||||
}
|
||||
|
||||
float x = 0.0f, y = 0.0f,
|
||||
size = Random::getSystemRandom().nextFloat() * 30.0f + 30.0f,
|
||||
dx = 0.0f, dy = 0.0f,
|
||||
parentWidth = 50.0f, parentHeight = 50.0f;
|
||||
|
||||
Colour colour;
|
||||
Thread::ThreadID threadId = {};
|
||||
CriticalSection drawing;
|
||||
|
||||
Component& containerComponent;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BouncingBall)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class DemoThread : public BouncingBall,
|
||||
public Thread
|
||||
{
|
||||
public:
|
||||
DemoThread (Component& containerComp)
|
||||
: BouncingBall (containerComp),
|
||||
Thread ("JUCE Demo Thread")
|
||||
{
|
||||
// give the threads a random priority, so some will move more
|
||||
// smoothly than others..
|
||||
startThread (Random::getSystemRandom().nextInt (3) + 3);
|
||||
}
|
||||
|
||||
~DemoThread() override
|
||||
{
|
||||
// allow the thread 2 seconds to stop cleanly - should be plenty of time.
|
||||
stopThread (2000);
|
||||
}
|
||||
|
||||
void run() override
|
||||
{
|
||||
// this is the code that runs this thread - we'll loop continuously,
|
||||
// updating the coordinates of our blob.
|
||||
|
||||
// threadShouldExit() returns true when the stopThread() method has been
|
||||
// called, so we should check it often, and exit as soon as it gets flagged.
|
||||
while (! threadShouldExit())
|
||||
{
|
||||
// sleep a bit so the threads don't all grind the CPU to a halt..
|
||||
wait (interval);
|
||||
|
||||
// because this is a background thread, we mustn't do any UI work without
|
||||
// first grabbing a MessageManagerLock..
|
||||
const MessageManagerLock mml (Thread::getCurrentThread());
|
||||
|
||||
if (! mml.lockWasGained()) // if something is trying to kill this job, the lock
|
||||
return; // will fail, in which case we'd better return..
|
||||
|
||||
// now we've got the UI thread locked, we can mess about with the components
|
||||
moveBall();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
int interval = Random::getSystemRandom().nextInt (50) + 6;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DemoThread)
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
class DemoThreadPoolJob : public BouncingBall,
|
||||
public ThreadPoolJob
|
||||
{
|
||||
public:
|
||||
DemoThreadPoolJob (Component& containerComp)
|
||||
: BouncingBall (containerComp),
|
||||
ThreadPoolJob ("Demo Threadpool Job")
|
||||
{}
|
||||
|
||||
JobStatus runJob() override
|
||||
{
|
||||
// this is the code that runs this job. It'll be repeatedly called until we return
|
||||
// jobHasFinished instead of jobNeedsRunningAgain.
|
||||
Thread::sleep (30);
|
||||
|
||||
// because this is a background thread, we mustn't do any UI work without
|
||||
// first grabbing a MessageManagerLock..
|
||||
const MessageManagerLock mml (this);
|
||||
|
||||
// before moving the ball, we need to check whether the lock was actually gained, because
|
||||
// if something is trying to stop this job, it will have failed..
|
||||
if (mml.lockWasGained())
|
||||
moveBall();
|
||||
|
||||
return jobNeedsRunningAgain;
|
||||
}
|
||||
|
||||
void removedFromQueue()
|
||||
{
|
||||
// This is called to tell us that our job has been removed from the pool.
|
||||
// In this case there's no need to do anything here.
|
||||
}
|
||||
|
||||
private:
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DemoThreadPoolJob)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class MultithreadingDemo : public Component,
|
||||
private Timer
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
MultithreadingDemo()
|
||||
{
|
||||
setOpaque (true);
|
||||
|
||||
addAndMakeVisible (controlButton);
|
||||
controlButton.changeWidthToFitText (24);
|
||||
controlButton.setTopLeftPosition (20, 20);
|
||||
controlButton.setTriggeredOnMouseDown (true);
|
||||
controlButton.setAlwaysOnTop (true);
|
||||
controlButton.onClick = [this] { showMenu(); };
|
||||
|
||||
setSize (500, 500);
|
||||
|
||||
resetAllBalls();
|
||||
|
||||
startTimerHz (60);
|
||||
}
|
||||
|
||||
~MultithreadingDemo() override
|
||||
{
|
||||
pool.removeAllJobs (true, 2000);
|
||||
}
|
||||
|
||||
void resetAllBalls()
|
||||
{
|
||||
pool.removeAllJobs (true, 4000);
|
||||
balls.clear();
|
||||
|
||||
for (int i = 0; i < 5; ++i)
|
||||
addABall();
|
||||
}
|
||||
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
g.fillAll (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::windowBackground));
|
||||
|
||||
for (auto* ball : balls)
|
||||
ball->draw (g);
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
void setUsingPool (bool usePool)
|
||||
{
|
||||
isUsingPool = usePool;
|
||||
resetAllBalls();
|
||||
}
|
||||
|
||||
void addABall()
|
||||
{
|
||||
if (isUsingPool)
|
||||
{
|
||||
auto newBall = std::make_unique<DemoThreadPoolJob> (*this);
|
||||
pool.addJob (newBall.get(), false);
|
||||
balls.add (newBall.release());
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
balls.add (new DemoThread (*this));
|
||||
}
|
||||
}
|
||||
|
||||
void timerCallback() override
|
||||
{
|
||||
repaint();
|
||||
}
|
||||
|
||||
void showMenu()
|
||||
{
|
||||
PopupMenu m;
|
||||
m.addItem (1, "Use one thread per ball", true, ! isUsingPool);
|
||||
m.addItem (2, "Use a thread pool", true, isUsingPool);
|
||||
|
||||
m.showMenuAsync (PopupMenu::Options().withTargetComponent (controlButton),
|
||||
ModalCallbackFunction::forComponent (menuItemChosenCallback, this));
|
||||
}
|
||||
|
||||
static void menuItemChosenCallback (int result, MultithreadingDemo* demoComponent)
|
||||
{
|
||||
if (result != 0 && demoComponent != nullptr)
|
||||
demoComponent->setUsingPool (result == 2);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
ThreadPool pool { 3 };
|
||||
TextButton controlButton { "Thread type" };
|
||||
bool isUsingPool = false;
|
||||
|
||||
OwnedArray<BouncingBall> balls;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MultithreadingDemo)
|
||||
};
|
137
deps/juce/examples/Utilities/NetworkingDemo.h
vendored
Normal file
137
deps/juce/examples/Utilities/NetworkingDemo.h
vendored
Normal file
@ -0,0 +1,137 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE examples.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
|
||||
WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
|
||||
PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
/*******************************************************************************
|
||||
The block below describes the properties of this PIP. A PIP is a short snippet
|
||||
of code that can be read by the Projucer and used to generate a JUCE project.
|
||||
|
||||
BEGIN_JUCE_PIP_METADATA
|
||||
|
||||
name: NetworkingDemo
|
||||
version: 1.0.0
|
||||
vendor: JUCE
|
||||
website: http://juce.com
|
||||
description: Showcases networking features.
|
||||
|
||||
dependencies: juce_core, juce_data_structures, juce_events, juce_graphics,
|
||||
juce_gui_basics, juce_gui_extra
|
||||
exporters: xcode_mac, vs2019, linux_make, androidstudio, xcode_iphone
|
||||
|
||||
moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
|
||||
|
||||
type: Component
|
||||
mainClass: NetworkingDemo
|
||||
|
||||
useLocalCopy: 1
|
||||
|
||||
END_JUCE_PIP_METADATA
|
||||
|
||||
*******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../Assets/DemoUtilities.h"
|
||||
|
||||
//==============================================================================
|
||||
class NetworkingDemo : public Component,
|
||||
private Thread
|
||||
{
|
||||
public:
|
||||
NetworkingDemo()
|
||||
: Thread ("Network Demo")
|
||||
{
|
||||
setOpaque (true);
|
||||
|
||||
addAndMakeVisible (urlBox);
|
||||
urlBox.setText ("https://www.google.com");
|
||||
urlBox.onReturnKey = [this] { fetchButton.triggerClick(); };
|
||||
|
||||
addAndMakeVisible (fetchButton);
|
||||
fetchButton.onClick = [this] { startThread(); };
|
||||
|
||||
addAndMakeVisible (resultsBox);
|
||||
|
||||
setSize (500, 500);
|
||||
}
|
||||
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
g.fillAll (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::windowBackground));
|
||||
}
|
||||
|
||||
void resized() override
|
||||
{
|
||||
auto area = getLocalBounds();
|
||||
|
||||
{
|
||||
auto topArea = area.removeFromTop (40);
|
||||
fetchButton.setBounds (topArea.removeFromRight (180).reduced (8));
|
||||
urlBox .setBounds (topArea.reduced (8));
|
||||
}
|
||||
|
||||
resultsBox.setBounds (area.reduced (8));
|
||||
}
|
||||
|
||||
void run() override
|
||||
{
|
||||
auto result = getResultText (urlBox.getText());
|
||||
|
||||
MessageManagerLock mml (this);
|
||||
|
||||
if (mml.lockWasGained())
|
||||
resultsBox.loadContent (result);
|
||||
}
|
||||
|
||||
String getResultText (const URL& url)
|
||||
{
|
||||
StringPairArray responseHeaders;
|
||||
int statusCode = 0;
|
||||
|
||||
if (auto stream = url.createInputStream (URL::InputStreamOptions (URL::ParameterHandling::inAddress)
|
||||
.withConnectionTimeoutMs(10000)
|
||||
.withResponseHeaders (&responseHeaders)
|
||||
.withStatusCode (&statusCode)))
|
||||
{
|
||||
return (statusCode != 0 ? "Status code: " + String (statusCode) + newLine : String())
|
||||
+ "Response headers: " + newLine
|
||||
+ responseHeaders.getDescription() + newLine
|
||||
+ "----------------------------------------------------" + newLine
|
||||
+ stream->readEntireStreamAsString();
|
||||
}
|
||||
|
||||
if (statusCode != 0)
|
||||
return "Failed to connect, status code = " + String (statusCode);
|
||||
|
||||
return "Failed to connect!";
|
||||
}
|
||||
|
||||
private:
|
||||
TextEditor urlBox;
|
||||
TextButton fetchButton { "Download URL Contents" };
|
||||
|
||||
CodeDocument resultsDocument;
|
||||
CodeEditorComponent resultsBox { resultsDocument, nullptr };
|
||||
|
||||
void lookAndFeelChanged() override
|
||||
{
|
||||
urlBox.applyFontToAllText (urlBox.getFont());
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NetworkingDemo)
|
||||
};
|
494
deps/juce/examples/Utilities/OSCDemo.h
vendored
Normal file
494
deps/juce/examples/Utilities/OSCDemo.h
vendored
Normal file
@ -0,0 +1,494 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE examples.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
|
||||
WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
|
||||
PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
/*******************************************************************************
|
||||
The block below describes the properties of this PIP. A PIP is a short snippet
|
||||
of code that can be read by the Projucer and used to generate a JUCE project.
|
||||
|
||||
BEGIN_JUCE_PIP_METADATA
|
||||
|
||||
name: OSCDemo
|
||||
version: 1.0.0
|
||||
vendor: JUCE
|
||||
website: http://juce.com
|
||||
description: Application using the OSC protocol.
|
||||
|
||||
dependencies: juce_core, juce_data_structures, juce_events, juce_graphics,
|
||||
juce_gui_basics, juce_osc
|
||||
exporters: xcode_mac, vs2019, linux_make
|
||||
|
||||
moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
|
||||
|
||||
type: Component
|
||||
mainClass: OSCDemo
|
||||
|
||||
useLocalCopy: 1
|
||||
|
||||
END_JUCE_PIP_METADATA
|
||||
|
||||
*******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
//==============================================================================
|
||||
class OSCLogListBox : public ListBox,
|
||||
private ListBoxModel,
|
||||
private AsyncUpdater
|
||||
{
|
||||
public:
|
||||
OSCLogListBox()
|
||||
{
|
||||
setModel (this);
|
||||
}
|
||||
|
||||
~OSCLogListBox() override = default;
|
||||
|
||||
//==============================================================================
|
||||
int getNumRows() override
|
||||
{
|
||||
return oscLogList.size();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void paintListBoxItem (int row, Graphics& g, int width, int height, bool rowIsSelected) override
|
||||
{
|
||||
ignoreUnused (rowIsSelected);
|
||||
|
||||
if (isPositiveAndBelow (row, oscLogList.size()))
|
||||
{
|
||||
g.setColour (Colours::white);
|
||||
|
||||
g.drawText (oscLogList[row],
|
||||
Rectangle<int> (width, height).reduced (4, 0),
|
||||
Justification::centredLeft, true);
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void addOSCMessage (const OSCMessage& message, int level = 0)
|
||||
{
|
||||
oscLogList.add (getIndentationString (level)
|
||||
+ "- osc message, address = '"
|
||||
+ message.getAddressPattern().toString()
|
||||
+ "', "
|
||||
+ String (message.size())
|
||||
+ " argument(s)");
|
||||
|
||||
if (! message.isEmpty())
|
||||
{
|
||||
for (auto& arg : message)
|
||||
addOSCMessageArgument (arg, level + 1);
|
||||
}
|
||||
|
||||
triggerAsyncUpdate();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void addOSCBundle (const OSCBundle& bundle, int level = 0)
|
||||
{
|
||||
OSCTimeTag timeTag = bundle.getTimeTag();
|
||||
|
||||
oscLogList.add (getIndentationString (level)
|
||||
+ "- osc bundle, time tag = "
|
||||
+ timeTag.toTime().toString (true, true, true, true));
|
||||
|
||||
for (auto& element : bundle)
|
||||
{
|
||||
if (element.isMessage())
|
||||
addOSCMessage (element.getMessage(), level + 1);
|
||||
else if (element.isBundle())
|
||||
addOSCBundle (element.getBundle(), level + 1);
|
||||
}
|
||||
|
||||
triggerAsyncUpdate();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void addOSCMessageArgument (const OSCArgument& arg, int level)
|
||||
{
|
||||
String typeAsString;
|
||||
String valueAsString;
|
||||
|
||||
if (arg.isFloat32())
|
||||
{
|
||||
typeAsString = "float32";
|
||||
valueAsString = String (arg.getFloat32());
|
||||
}
|
||||
else if (arg.isInt32())
|
||||
{
|
||||
typeAsString = "int32";
|
||||
valueAsString = String (arg.getInt32());
|
||||
}
|
||||
else if (arg.isString())
|
||||
{
|
||||
typeAsString = "string";
|
||||
valueAsString = arg.getString();
|
||||
}
|
||||
else if (arg.isBlob())
|
||||
{
|
||||
typeAsString = "blob";
|
||||
auto& blob = arg.getBlob();
|
||||
valueAsString = String::fromUTF8 ((const char*) blob.getData(), (int) blob.getSize());
|
||||
}
|
||||
else
|
||||
{
|
||||
typeAsString = "(unknown)";
|
||||
}
|
||||
|
||||
oscLogList.add (getIndentationString (level + 1) + "- " + typeAsString.paddedRight(' ', 12) + valueAsString);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void addInvalidOSCPacket (const char* /* data */, int dataSize)
|
||||
{
|
||||
oscLogList.add ("- (" + String(dataSize) + "bytes with invalid format)");
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void clear()
|
||||
{
|
||||
oscLogList.clear();
|
||||
triggerAsyncUpdate();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void handleAsyncUpdate() override
|
||||
{
|
||||
updateContent();
|
||||
scrollToEnsureRowIsOnscreen (oscLogList.size() - 1);
|
||||
repaint();
|
||||
}
|
||||
|
||||
private:
|
||||
static String getIndentationString (int level)
|
||||
{
|
||||
return String().paddedRight (' ', 2 * level);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
StringArray oscLogList;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OSCLogListBox)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class OSCSenderDemo : public Component
|
||||
{
|
||||
public:
|
||||
OSCSenderDemo()
|
||||
{
|
||||
addAndMakeVisible (senderLabel);
|
||||
senderLabel.attachToComponent (&rotaryKnob, false);
|
||||
|
||||
rotaryKnob.setRange (0.0, 1.0);
|
||||
rotaryKnob.setSliderStyle (Slider::RotaryVerticalDrag);
|
||||
rotaryKnob.setTextBoxStyle (Slider::TextBoxBelow, true, 150, 25);
|
||||
rotaryKnob.setBounds (50, 50, 180, 180);
|
||||
addAndMakeVisible (rotaryKnob);
|
||||
rotaryKnob.onValueChange = [this]
|
||||
{
|
||||
// create and send an OSC message with an address and a float value:
|
||||
if (! sender1.send ("/juce/rotaryknob", (float) rotaryKnob.getValue()))
|
||||
showConnectionErrorMessage ("Error: could not send OSC message.");
|
||||
if (! sender2.send ("/juce/rotaryknob", (float) rotaryKnob.getValue()))
|
||||
showConnectionErrorMessage ("Error: could not send OSC message.");
|
||||
};
|
||||
|
||||
// specify here where to send OSC messages to: host URL and UDP port number
|
||||
if (! sender1.connect ("127.0.0.1", 9001))
|
||||
showConnectionErrorMessage ("Error: could not connect to UDP port 9001.");
|
||||
if (! sender2.connect ("127.0.0.1", 9002))
|
||||
showConnectionErrorMessage ("Error: could not connect to UDP port 9002.");
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
void showConnectionErrorMessage (const String& messageText)
|
||||
{
|
||||
AlertWindow::showMessageBoxAsync (MessageBoxIconType::WarningIcon,
|
||||
"Connection error",
|
||||
messageText,
|
||||
"OK");
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
Slider rotaryKnob;
|
||||
OSCSender sender1, sender2;
|
||||
Label senderLabel { {}, "Sender" };
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OSCSenderDemo)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class OSCReceiverDemo : public Component,
|
||||
private OSCReceiver,
|
||||
private OSCReceiver::ListenerWithOSCAddress<OSCReceiver::MessageLoopCallback>
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
OSCReceiverDemo()
|
||||
{
|
||||
addAndMakeVisible (receiverLabel);
|
||||
receiverLabel.attachToComponent (&rotaryKnob, false);
|
||||
|
||||
rotaryKnob.setRange (0.0, 1.0);
|
||||
rotaryKnob.setSliderStyle (Slider::RotaryVerticalDrag);
|
||||
rotaryKnob.setTextBoxStyle (Slider::TextBoxBelow, true, 150, 25);
|
||||
rotaryKnob.setBounds (50, 50, 180, 180);
|
||||
rotaryKnob.setInterceptsMouseClicks (false, false);
|
||||
addAndMakeVisible (rotaryKnob);
|
||||
|
||||
// specify here on which UDP port number to receive incoming OSC messages
|
||||
if (! connect (9001))
|
||||
showConnectionErrorMessage ("Error: could not connect to UDP port 9001.");
|
||||
|
||||
// tell the component to listen for OSC messages matching this address:
|
||||
addListener (this, "/juce/rotaryknob");
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
void oscMessageReceived (const OSCMessage& message) override
|
||||
{
|
||||
if (message.size() == 1 && message[0].isFloat32())
|
||||
rotaryKnob.setValue (jlimit (0.0f, 10.0f, message[0].getFloat32()));
|
||||
}
|
||||
|
||||
void showConnectionErrorMessage (const String& messageText)
|
||||
{
|
||||
AlertWindow::showMessageBoxAsync (MessageBoxIconType::WarningIcon,
|
||||
"Connection error",
|
||||
messageText,
|
||||
"OK");
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
Slider rotaryKnob;
|
||||
Label receiverLabel { {}, "Receiver" };
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OSCReceiverDemo)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class OSCMonitorDemo : public Component,
|
||||
private OSCReceiver::Listener<OSCReceiver::MessageLoopCallback>
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
OSCMonitorDemo()
|
||||
{
|
||||
portNumberLabel.setBounds (10, 18, 130, 25);
|
||||
addAndMakeVisible (portNumberLabel);
|
||||
|
||||
portNumberField.setEditable (true, true, true);
|
||||
portNumberField.setBounds (140, 18, 50, 25);
|
||||
addAndMakeVisible (portNumberField);
|
||||
|
||||
connectButton.setBounds (210, 18, 100, 25);
|
||||
addAndMakeVisible (connectButton);
|
||||
connectButton.onClick = [this] { connectButtonClicked(); };
|
||||
|
||||
clearButton.setBounds (320, 18, 60, 25);
|
||||
addAndMakeVisible (clearButton);
|
||||
clearButton.onClick = [this] { clearButtonClicked(); };
|
||||
|
||||
connectionStatusLabel.setBounds (450, 18, 240, 25);
|
||||
updateConnectionStatusLabel();
|
||||
addAndMakeVisible (connectionStatusLabel);
|
||||
|
||||
oscLogListBox.setBounds (0, 60, 700, 340);
|
||||
addAndMakeVisible (oscLogListBox);
|
||||
|
||||
oscReceiver.addListener (this);
|
||||
oscReceiver.registerFormatErrorHandler ([this] (const char* data, int dataSize)
|
||||
{
|
||||
oscLogListBox.addInvalidOSCPacket (data, dataSize);
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
Label portNumberLabel { {}, "UDP Port Number: " };
|
||||
Label portNumberField { {}, "9002" };
|
||||
TextButton connectButton { "Connect" };
|
||||
TextButton clearButton { "Clear" };
|
||||
Label connectionStatusLabel;
|
||||
|
||||
OSCLogListBox oscLogListBox;
|
||||
OSCReceiver oscReceiver;
|
||||
|
||||
int currentPortNumber = -1;
|
||||
|
||||
//==============================================================================
|
||||
void connectButtonClicked()
|
||||
{
|
||||
if (! isConnected())
|
||||
connect();
|
||||
else
|
||||
disconnect();
|
||||
|
||||
updateConnectionStatusLabel();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void clearButtonClicked()
|
||||
{
|
||||
oscLogListBox.clear();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void oscMessageReceived (const OSCMessage& message) override
|
||||
{
|
||||
oscLogListBox.addOSCMessage (message);
|
||||
}
|
||||
|
||||
void oscBundleReceived (const OSCBundle& bundle) override
|
||||
{
|
||||
oscLogListBox.addOSCBundle (bundle);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void connect()
|
||||
{
|
||||
auto portToConnect = portNumberField.getText().getIntValue();
|
||||
|
||||
if (! isValidOscPort (portToConnect))
|
||||
{
|
||||
handleInvalidPortNumberEntered();
|
||||
return;
|
||||
}
|
||||
|
||||
if (oscReceiver.connect (portToConnect))
|
||||
{
|
||||
currentPortNumber = portToConnect;
|
||||
connectButton.setButtonText ("Disconnect");
|
||||
}
|
||||
else
|
||||
{
|
||||
handleConnectError (portToConnect);
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void disconnect()
|
||||
{
|
||||
if (oscReceiver.disconnect())
|
||||
{
|
||||
currentPortNumber = -1;
|
||||
connectButton.setButtonText ("Connect");
|
||||
}
|
||||
else
|
||||
{
|
||||
handleDisconnectError();
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void handleConnectError (int failedPort)
|
||||
{
|
||||
AlertWindow::showMessageBoxAsync (MessageBoxIconType::WarningIcon,
|
||||
"OSC Connection error",
|
||||
"Error: could not connect to port " + String (failedPort),
|
||||
"OK");
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void handleDisconnectError()
|
||||
{
|
||||
AlertWindow::showMessageBoxAsync (MessageBoxIconType::WarningIcon,
|
||||
"Unknown error",
|
||||
"An unknown error occurred while trying to disconnect from UDP port.",
|
||||
"OK");
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void handleInvalidPortNumberEntered()
|
||||
{
|
||||
AlertWindow::showMessageBoxAsync (MessageBoxIconType::WarningIcon,
|
||||
"Invalid port number",
|
||||
"Error: you have entered an invalid UDP port number.",
|
||||
"OK");
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool isConnected() const
|
||||
{
|
||||
return currentPortNumber != -1;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool isValidOscPort (int port) const
|
||||
{
|
||||
return port > 0 && port < 65536;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void updateConnectionStatusLabel()
|
||||
{
|
||||
String text = "Status: ";
|
||||
|
||||
if (isConnected())
|
||||
text += "Connected to UDP port " + String (currentPortNumber);
|
||||
else
|
||||
text += "Disconnected";
|
||||
|
||||
auto textColour = isConnected() ? Colours::green : Colours::red;
|
||||
|
||||
connectionStatusLabel.setText (text, dontSendNotification);
|
||||
connectionStatusLabel.setFont (Font (15.00f, Font::bold));
|
||||
connectionStatusLabel.setColour (Label::textColourId, textColour);
|
||||
connectionStatusLabel.setJustificationType (Justification::centredRight);
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OSCMonitorDemo)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class OSCDemo : public Component
|
||||
{
|
||||
public:
|
||||
OSCDemo()
|
||||
{
|
||||
addAndMakeVisible (monitor);
|
||||
addAndMakeVisible (receiver);
|
||||
addAndMakeVisible (sender);
|
||||
|
||||
setSize (700, 400);
|
||||
}
|
||||
|
||||
void resized() override
|
||||
{
|
||||
auto bounds = getLocalBounds();
|
||||
|
||||
auto lowerBounds = bounds.removeFromBottom (getHeight() / 2);
|
||||
auto halfBounds = bounds.removeFromRight (getWidth() / 2);
|
||||
|
||||
sender .setBounds (bounds);
|
||||
receiver.setBounds (halfBounds);
|
||||
monitor .setBounds (lowerBounds.removeFromTop (getHeight() / 2));
|
||||
}
|
||||
|
||||
private:
|
||||
OSCMonitorDemo monitor;
|
||||
OSCReceiverDemo receiver;
|
||||
OSCSenderDemo sender;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OSCDemo)
|
||||
};
|
1244
deps/juce/examples/Utilities/PushNotificationsDemo.h
vendored
Normal file
1244
deps/juce/examples/Utilities/PushNotificationsDemo.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
245
deps/juce/examples/Utilities/SystemInfoDemo.h
vendored
Normal file
245
deps/juce/examples/Utilities/SystemInfoDemo.h
vendored
Normal file
@ -0,0 +1,245 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE examples.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
|
||||
WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
|
||||
PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
/*******************************************************************************
|
||||
The block below describes the properties of this PIP. A PIP is a short snippet
|
||||
of code that can be read by the Projucer and used to generate a JUCE project.
|
||||
|
||||
BEGIN_JUCE_PIP_METADATA
|
||||
|
||||
name: SystemInfoDemo
|
||||
version: 1.0.0
|
||||
vendor: JUCE
|
||||
website: http://juce.com
|
||||
description: Displays system information.
|
||||
|
||||
dependencies: juce_core, juce_data_structures, juce_events, juce_graphics,
|
||||
juce_gui_basics
|
||||
exporters: xcode_mac, vs2019, linux_make, androidstudio, xcode_iphone
|
||||
|
||||
moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
|
||||
|
||||
type: Component
|
||||
mainClass: SystemInfoDemo
|
||||
|
||||
useLocalCopy: 1
|
||||
|
||||
END_JUCE_PIP_METADATA
|
||||
|
||||
*******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../Assets/DemoUtilities.h"
|
||||
|
||||
//==============================================================================
|
||||
static String getMacAddressList()
|
||||
{
|
||||
String addressList;
|
||||
|
||||
for (auto& addr : MACAddress::getAllAddresses())
|
||||
addressList << addr.toString() << newLine;
|
||||
|
||||
return addressList;
|
||||
}
|
||||
|
||||
static String getFileSystemRoots()
|
||||
{
|
||||
Array<File> roots;
|
||||
File::findFileSystemRoots (roots);
|
||||
|
||||
StringArray rootList;
|
||||
for (auto& r : roots)
|
||||
rootList.add (r.getFullPathName());
|
||||
|
||||
return rootList.joinIntoString (", ");
|
||||
}
|
||||
|
||||
static String getIPAddressList()
|
||||
{
|
||||
String addressList;
|
||||
|
||||
for (auto& addr : IPAddress::getAllAddresses())
|
||||
addressList << " " << addr.toString() << newLine;
|
||||
|
||||
return addressList;
|
||||
}
|
||||
|
||||
static const char* getDisplayOrientation()
|
||||
{
|
||||
switch (Desktop::getInstance().getCurrentOrientation())
|
||||
{
|
||||
case Desktop::upright: return "Upright";
|
||||
case Desktop::upsideDown: return "Upside-down";
|
||||
case Desktop::rotatedClockwise: return "Rotated Clockwise";
|
||||
case Desktop::rotatedAntiClockwise: return "Rotated Anti-clockwise";
|
||||
case Desktop::allOrientations: return "All";
|
||||
default: jassertfalse; break;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static String getDisplayInfo()
|
||||
{
|
||||
auto& displays = Desktop::getInstance().getDisplays();
|
||||
|
||||
String displayDesc;
|
||||
|
||||
for (int i = 0; i < displays.displays.size(); ++i)
|
||||
{
|
||||
auto display = displays.displays.getReference (i);
|
||||
|
||||
displayDesc << "Display " << (i + 1) << (display.isMain ? " (main)" : "") << ":" << newLine
|
||||
<< " Total area: " << display.totalArea.toString() << newLine
|
||||
<< " User area: " << display.userArea .toString() << newLine
|
||||
<< " DPI: " << display.dpi << newLine
|
||||
<< " Scale: " << display.scale << newLine
|
||||
<< newLine;
|
||||
}
|
||||
|
||||
|
||||
displayDesc << "Orientation: " << getDisplayOrientation() << newLine;
|
||||
|
||||
return displayDesc;
|
||||
}
|
||||
|
||||
static String getAllSystemInfo()
|
||||
{
|
||||
String systemInfo;
|
||||
|
||||
systemInfo
|
||||
<< "Here are a few system statistics..." << newLine
|
||||
<< newLine
|
||||
<< "Time and date: " << Time::getCurrentTime().toString (true, true) << newLine
|
||||
<< "System up-time: " << RelativeTime::milliseconds ((int64) Time::getMillisecondCounterHiRes()).getDescription() << newLine
|
||||
<< "Compilation date: " << Time::getCompilationDate().toString (true, false) << newLine
|
||||
<< newLine
|
||||
<< "Operating system: " << SystemStats::getOperatingSystemName() << newLine
|
||||
<< "Host name: " << SystemStats::getComputerName() << newLine
|
||||
<< "Device type: " << SystemStats::getDeviceDescription() << newLine
|
||||
<< "Manufacturer: " << SystemStats::getDeviceManufacturer() << newLine
|
||||
<< "User logon name: " << SystemStats::getLogonName() << newLine
|
||||
<< "Full user name: " << SystemStats::getFullUserName() << newLine
|
||||
<< "User region: " << SystemStats::getUserRegion() << newLine
|
||||
<< "User language: " << SystemStats::getUserLanguage() << newLine
|
||||
<< "Display language: " << SystemStats::getDisplayLanguage() << newLine
|
||||
<< newLine;
|
||||
|
||||
systemInfo
|
||||
<< "Number of logical CPUs: " << SystemStats::getNumCpus() << newLine
|
||||
<< "Number of physical CPUs: " << SystemStats::getNumPhysicalCpus() << newLine
|
||||
<< "Memory size: " << SystemStats::getMemorySizeInMegabytes() << " MB" << newLine
|
||||
<< "CPU vendor: " << SystemStats::getCpuVendor() << newLine
|
||||
<< "CPU model: " << SystemStats::getCpuModel() << newLine
|
||||
<< "CPU speed: " << SystemStats::getCpuSpeedInMegahertz() << " MHz" << newLine
|
||||
<< "CPU has MMX: " << (SystemStats::hasMMX() ? "yes" : "no") << newLine
|
||||
<< "CPU has FMA3: " << (SystemStats::hasFMA3() ? "yes" : "no") << newLine
|
||||
<< "CPU has FMA4: " << (SystemStats::hasFMA4() ? "yes" : "no") << newLine
|
||||
<< "CPU has SSE: " << (SystemStats::hasSSE() ? "yes" : "no") << newLine
|
||||
<< "CPU has SSE2: " << (SystemStats::hasSSE2() ? "yes" : "no") << newLine
|
||||
<< "CPU has SSE3: " << (SystemStats::hasSSE3() ? "yes" : "no") << newLine
|
||||
<< "CPU has SSSE3: " << (SystemStats::hasSSSE3() ? "yes" : "no") << newLine
|
||||
<< "CPU has SSE4.1: " << (SystemStats::hasSSE41() ? "yes" : "no") << newLine
|
||||
<< "CPU has SSE4.2: " << (SystemStats::hasSSE42() ? "yes" : "no") << newLine
|
||||
<< "CPU has 3DNOW: " << (SystemStats::has3DNow() ? "yes" : "no") << newLine
|
||||
<< "CPU has AVX: " << (SystemStats::hasAVX() ? "yes" : "no") << newLine
|
||||
<< "CPU has AVX2: " << (SystemStats::hasAVX2() ? "yes" : "no") << newLine
|
||||
<< "CPU has AVX512F: " << (SystemStats::hasAVX512F() ? "yes" : "no") << newLine
|
||||
<< "CPU has AVX512BW: " << (SystemStats::hasAVX512BW() ? "yes" : "no") << newLine
|
||||
<< "CPU has AVX512CD: " << (SystemStats::hasAVX512CD() ? "yes" : "no") << newLine
|
||||
<< "CPU has AVX512DQ: " << (SystemStats::hasAVX512DQ() ? "yes" : "no") << newLine
|
||||
<< "CPU has AVX512ER: " << (SystemStats::hasAVX512ER() ? "yes" : "no") << newLine
|
||||
<< "CPU has AVX512IFMA: " << (SystemStats::hasAVX512IFMA() ? "yes" : "no") << newLine
|
||||
<< "CPU has AVX512PF: " << (SystemStats::hasAVX512PF() ? "yes" : "no") << newLine
|
||||
<< "CPU has AVX512VBMI: " << (SystemStats::hasAVX512VBMI() ? "yes" : "no") << newLine
|
||||
<< "CPU has AVX512VL: " << (SystemStats::hasAVX512VL() ? "yes" : "no") << newLine
|
||||
<< "CPU has AVX512VPOPCNTDQ: " << (SystemStats::hasAVX512VPOPCNTDQ() ? "yes" : "no") << newLine
|
||||
<< "CPU has Neon: " << (SystemStats::hasNeon() ? "yes" : "no") << newLine
|
||||
<< newLine;
|
||||
|
||||
systemInfo
|
||||
<< "Current working directory: " << File::getCurrentWorkingDirectory().getFullPathName() << newLine
|
||||
<< "Current application file: " << File::getSpecialLocation (File::currentApplicationFile).getFullPathName() << newLine
|
||||
<< "Current executable file: " << File::getSpecialLocation (File::currentExecutableFile) .getFullPathName() << newLine
|
||||
<< "Invoked executable file: " << File::getSpecialLocation (File::invokedExecutableFile) .getFullPathName() << newLine
|
||||
<< newLine;
|
||||
|
||||
systemInfo
|
||||
<< "User home folder: " << File::getSpecialLocation (File::userHomeDirectory) .getFullPathName() << newLine
|
||||
<< "User desktop folder: " << File::getSpecialLocation (File::userDesktopDirectory) .getFullPathName() << newLine
|
||||
<< "User documents folder: " << File::getSpecialLocation (File::userDocumentsDirectory) .getFullPathName() << newLine
|
||||
<< "User application data folder: " << File::getSpecialLocation (File::userApplicationDataDirectory) .getFullPathName() << newLine
|
||||
<< "User music folder: " << File::getSpecialLocation (File::userMusicDirectory) .getFullPathName() << newLine
|
||||
<< "User movies folder: " << File::getSpecialLocation (File::userMoviesDirectory) .getFullPathName() << newLine
|
||||
<< "User pictures folder: " << File::getSpecialLocation (File::userPicturesDirectory) .getFullPathName() << newLine
|
||||
<< "Common application data folder: " << File::getSpecialLocation (File::commonApplicationDataDirectory).getFullPathName() << newLine
|
||||
<< "Common documents folder: " << File::getSpecialLocation (File::commonDocumentsDirectory) .getFullPathName() << newLine
|
||||
<< "Local temp folder: " << File::getSpecialLocation (File::tempDirectory) .getFullPathName() << newLine
|
||||
<< newLine;
|
||||
|
||||
systemInfo
|
||||
<< "File System roots: " << getFileSystemRoots() << newLine
|
||||
<< "Free space in home folder: " << File::descriptionOfSizeInBytes (File::getSpecialLocation (File::userHomeDirectory)
|
||||
.getBytesFreeOnVolume()) << newLine
|
||||
<< newLine
|
||||
<< getDisplayInfo() << newLine
|
||||
<< "Network IP addresses: " << newLine << getIPAddressList() << newLine
|
||||
<< "Network card MAC addresses: " << newLine << getMacAddressList() << newLine;
|
||||
|
||||
DBG (systemInfo);
|
||||
return systemInfo;
|
||||
}
|
||||
|
||||
class SystemInfoDemo : public Component
|
||||
{
|
||||
public:
|
||||
SystemInfoDemo()
|
||||
{
|
||||
addAndMakeVisible (resultsBox);
|
||||
resultsBox.setReadOnly (true);
|
||||
resultsBox.setMultiLine (true);
|
||||
resultsBox.setColour (TextEditor::backgroundColourId, Colours::transparentBlack);
|
||||
resultsBox.setFont ({ Font::getDefaultMonospacedFontName(), 12.0f, Font::plain });
|
||||
resultsBox.setText (getAllSystemInfo());
|
||||
|
||||
setSize (500, 500);
|
||||
}
|
||||
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
g.fillAll (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::windowBackground,
|
||||
Colour::greyLevel (0.93f)));
|
||||
}
|
||||
|
||||
void resized() override
|
||||
{
|
||||
resultsBox.setBounds (getLocalBounds().reduced (8));
|
||||
}
|
||||
|
||||
private:
|
||||
TextEditor resultsBox;
|
||||
|
||||
void lookAndFeelChanged() override
|
||||
{
|
||||
resultsBox.applyFontToAllText (resultsBox.getFont());
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SystemInfoDemo)
|
||||
};
|
261
deps/juce/examples/Utilities/TimersAndEventsDemo.h
vendored
Normal file
261
deps/juce/examples/Utilities/TimersAndEventsDemo.h
vendored
Normal file
@ -0,0 +1,261 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE examples.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
|
||||
WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
|
||||
PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
/*******************************************************************************
|
||||
The block below describes the properties of this PIP. A PIP is a short snippet
|
||||
of code that can be read by the Projucer and used to generate a JUCE project.
|
||||
|
||||
BEGIN_JUCE_PIP_METADATA
|
||||
|
||||
name: TimersAndEventsDemo
|
||||
version: 1.0.0
|
||||
vendor: JUCE
|
||||
website: http://juce.com
|
||||
description: Application using timers and events.
|
||||
|
||||
dependencies: juce_core, juce_data_structures, juce_events, juce_graphics,
|
||||
juce_gui_basics
|
||||
exporters: xcode_mac, vs2019, linux_make, androidstudio, xcode_iphone
|
||||
|
||||
moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
|
||||
|
||||
type: Component
|
||||
mainClass: TimersAndEventsDemo
|
||||
|
||||
useLocalCopy: 1
|
||||
|
||||
END_JUCE_PIP_METADATA
|
||||
|
||||
*******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../Assets/DemoUtilities.h"
|
||||
|
||||
//==============================================================================
|
||||
/** Simple message that holds a Colour. */
|
||||
struct ColourMessage : public Message
|
||||
{
|
||||
ColourMessage (Colour col) : colour (col) {}
|
||||
|
||||
/** Returns the colour of a ColourMessage of white if the message is not a ColourMessage. */
|
||||
static Colour getColour (const Message& message)
|
||||
{
|
||||
if (auto* cm = dynamic_cast<const ColourMessage*> (&message))
|
||||
return cm->colour;
|
||||
|
||||
return Colours::white;
|
||||
}
|
||||
|
||||
Colour colour;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ColourMessage)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Simple component that can be triggered to flash.
|
||||
The flash will then fade using a Timer to repaint itself and will send a change
|
||||
message once it is finished.
|
||||
*/
|
||||
class FlashingComponent : public Component,
|
||||
public MessageListener,
|
||||
public ChangeBroadcaster,
|
||||
private Timer
|
||||
{
|
||||
public:
|
||||
FlashingComponent() {}
|
||||
|
||||
void startFlashing()
|
||||
{
|
||||
flashAlpha = 1.0f;
|
||||
startTimerHz (25);
|
||||
}
|
||||
|
||||
/** Stops this component flashing without sending a change message. */
|
||||
void stopFlashing()
|
||||
{
|
||||
flashAlpha = 0.0f;
|
||||
stopTimer();
|
||||
repaint();
|
||||
}
|
||||
|
||||
/** Sets the colour of the component. */
|
||||
void setFlashColour (const Colour newColour)
|
||||
{
|
||||
colour = newColour;
|
||||
repaint();
|
||||
}
|
||||
|
||||
/** Draws our component. */
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
g.setColour (colour.overlaidWith (Colours::white.withAlpha (flashAlpha)));
|
||||
g.fillEllipse (getLocalBounds().toFloat());
|
||||
}
|
||||
|
||||
/** Custom mouse handler to trigger a flash. */
|
||||
void mouseDown (const MouseEvent&) override
|
||||
{
|
||||
startFlashing();
|
||||
}
|
||||
|
||||
/** Message listener callback used to change our colour */
|
||||
void handleMessage (const Message& message) override
|
||||
{
|
||||
setFlashColour (ColourMessage::getColour (message));
|
||||
}
|
||||
|
||||
private:
|
||||
float flashAlpha = 0.0f;
|
||||
Colour colour { Colours::red };
|
||||
|
||||
void timerCallback() override
|
||||
{
|
||||
// Reduce the alpha level of the flash slightly so it fades out
|
||||
flashAlpha -= 0.075f;
|
||||
|
||||
if (flashAlpha < 0.05f)
|
||||
{
|
||||
stopFlashing();
|
||||
sendChangeMessage();
|
||||
// Once we've finished flashing send a change message to trigger the next component to flash
|
||||
}
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FlashingComponent)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class TimersAndEventsDemo : public Component,
|
||||
private ChangeListener
|
||||
{
|
||||
public:
|
||||
TimersAndEventsDemo()
|
||||
{
|
||||
setOpaque (true);
|
||||
|
||||
// Create and add our FlashingComponents with some random colours and sizes
|
||||
for (int i = 0; i < numFlashingComponents; ++i)
|
||||
{
|
||||
auto* newFlasher = new FlashingComponent();
|
||||
flashingComponents.add (newFlasher);
|
||||
|
||||
newFlasher->setFlashColour (getRandomBrightColour());
|
||||
newFlasher->addChangeListener (this);
|
||||
|
||||
auto diameter = 25 + random.nextInt (75);
|
||||
newFlasher->setSize (diameter, diameter);
|
||||
|
||||
addAndMakeVisible (newFlasher);
|
||||
}
|
||||
|
||||
addAndMakeVisible (stopButton);
|
||||
stopButton.onClick = [this] { stopButtonClicked(); };
|
||||
|
||||
addAndMakeVisible (randomColourButton);
|
||||
randomColourButton.onClick = [this] { randomColourButtonClicked(); };
|
||||
|
||||
// lay out our components in a pseudo random grid
|
||||
Rectangle<int> area (0, 100, 150, 150);
|
||||
|
||||
for (auto* comp : flashingComponents)
|
||||
{
|
||||
auto buttonArea = area.withSize (comp->getWidth(), comp->getHeight());
|
||||
buttonArea.translate (random.nextInt (area.getWidth() - comp->getWidth()),
|
||||
random.nextInt (area.getHeight() - comp->getHeight()));
|
||||
comp->setBounds (buttonArea);
|
||||
|
||||
area.translate (area.getWidth(), 0);
|
||||
|
||||
// if we go off the right start a new row
|
||||
if (area.getRight() > (800 - area.getWidth()))
|
||||
{
|
||||
area.translate (0, area.getWidth());
|
||||
area.setX (0);
|
||||
}
|
||||
}
|
||||
|
||||
setSize (600, 600);
|
||||
}
|
||||
|
||||
~TimersAndEventsDemo() override
|
||||
{
|
||||
for (auto* fc : flashingComponents)
|
||||
fc->removeChangeListener (this);
|
||||
}
|
||||
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
g.fillAll (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::windowBackground,
|
||||
Colours::darkgrey));
|
||||
}
|
||||
|
||||
void paintOverChildren (Graphics& g) override
|
||||
{
|
||||
auto explanationArea = getLocalBounds().removeFromTop (100);
|
||||
|
||||
AttributedString s;
|
||||
s.append ("Click on a circle to make it flash. When it has finished flashing it will send a message which causes the next circle to flash");
|
||||
s.append (newLine);
|
||||
s.append ("Click the \"Set Random Colour\" button to change the colour of one of the circles.");
|
||||
s.append (newLine);
|
||||
s.setFont (16.0f);
|
||||
s.setColour (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::defaultText, Colours::lightgrey));
|
||||
s.draw (g, explanationArea.reduced (10).toFloat());
|
||||
}
|
||||
|
||||
void resized() override
|
||||
{
|
||||
auto area = getLocalBounds().removeFromBottom (40);
|
||||
randomColourButton.setBounds (area.removeFromLeft (166) .reduced (8));
|
||||
stopButton .setBounds (area.removeFromRight (166).reduced (8));
|
||||
}
|
||||
|
||||
private:
|
||||
enum { numFlashingComponents = 9 };
|
||||
|
||||
OwnedArray<FlashingComponent> flashingComponents;
|
||||
TextButton randomColourButton { "Set Random Colour" },
|
||||
stopButton { "Stop" };
|
||||
Random random;
|
||||
|
||||
void changeListenerCallback (ChangeBroadcaster* source) override
|
||||
{
|
||||
for (int i = 0; i < flashingComponents.size(); ++i)
|
||||
if (source == flashingComponents.getUnchecked (i))
|
||||
flashingComponents.getUnchecked ((i + 1) % flashingComponents.size())->startFlashing();
|
||||
}
|
||||
|
||||
void randomColourButtonClicked()
|
||||
{
|
||||
// Here we post a new ColourMessage with a random colour to a random flashing component.
|
||||
// This will send a message to the component asynchronously and trigger its handleMessage callback
|
||||
flashingComponents.getUnchecked (random.nextInt (flashingComponents.size()))->postMessage (new ColourMessage (getRandomBrightColour()));
|
||||
}
|
||||
|
||||
void stopButtonClicked()
|
||||
{
|
||||
for (auto* fc : flashingComponents)
|
||||
fc->stopFlashing();
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TimersAndEventsDemo)
|
||||
};
|
232
deps/juce/examples/Utilities/UnitTestsDemo.h
vendored
Normal file
232
deps/juce/examples/Utilities/UnitTestsDemo.h
vendored
Normal file
@ -0,0 +1,232 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE examples.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
|
||||
WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
|
||||
PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
/*******************************************************************************
|
||||
The block below describes the properties of this PIP. A PIP is a short snippet
|
||||
of code that can be read by the Projucer and used to generate a JUCE project.
|
||||
|
||||
BEGIN_JUCE_PIP_METADATA
|
||||
|
||||
name: UnitTestsDemo
|
||||
version: 1.0.0
|
||||
vendor: JUCE
|
||||
website: http://juce.com
|
||||
description: Performs unit tests.
|
||||
|
||||
dependencies: juce_analytics, juce_audio_basics, juce_audio_devices,
|
||||
juce_audio_formats, juce_audio_processors, juce_audio_utils,
|
||||
juce_core, juce_cryptography, juce_data_structures, juce_dsp,
|
||||
juce_events, juce_graphics, juce_gui_basics, juce_gui_extra,
|
||||
juce_opengl, juce_osc, juce_product_unlocking, juce_video
|
||||
exporters: xcode_mac, vs2019, linux_make, androidstudio, xcode_iphone
|
||||
|
||||
moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
|
||||
defines: JUCE_UNIT_TESTS=1
|
||||
|
||||
type: Component
|
||||
mainClass: UnitTestsDemo
|
||||
|
||||
useLocalCopy: 1
|
||||
|
||||
END_JUCE_PIP_METADATA
|
||||
|
||||
*******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../Assets/DemoUtilities.h"
|
||||
|
||||
//==============================================================================
|
||||
class UnitTestsDemo : public Component
|
||||
{
|
||||
public:
|
||||
UnitTestsDemo()
|
||||
{
|
||||
setOpaque (true);
|
||||
|
||||
addAndMakeVisible (startTestButton);
|
||||
startTestButton.onClick = [this] { start(); };
|
||||
|
||||
addAndMakeVisible (testResultsBox);
|
||||
testResultsBox.setMultiLine (true);
|
||||
testResultsBox.setFont (Font (Font::getDefaultMonospacedFontName(), 12.0f, Font::plain));
|
||||
|
||||
addAndMakeVisible (categoriesBox);
|
||||
categoriesBox.addItem ("All Tests", 1);
|
||||
|
||||
auto categories = UnitTest::getAllCategories();
|
||||
categories.sort (true);
|
||||
|
||||
categoriesBox.addItemList (categories, 2);
|
||||
categoriesBox.setSelectedId (1);
|
||||
|
||||
logMessage ("This panel runs the built-in JUCE unit-tests from the selected category.\n");
|
||||
logMessage ("To add your own unit-tests, see the JUCE_UNIT_TESTS macro.");
|
||||
|
||||
setSize (500, 500);
|
||||
}
|
||||
|
||||
~UnitTestsDemo() override
|
||||
{
|
||||
stopTest();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
g.fillAll (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::windowBackground,
|
||||
Colours::grey));
|
||||
}
|
||||
|
||||
void resized() override
|
||||
{
|
||||
auto bounds = getLocalBounds().reduced (6);
|
||||
|
||||
auto topSlice = bounds.removeFromTop (25);
|
||||
startTestButton.setBounds (topSlice.removeFromLeft (200));
|
||||
topSlice.removeFromLeft (10);
|
||||
categoriesBox .setBounds (topSlice.removeFromLeft (250));
|
||||
|
||||
bounds.removeFromTop (5);
|
||||
testResultsBox.setBounds (bounds);
|
||||
}
|
||||
|
||||
void start()
|
||||
{
|
||||
startTest (categoriesBox.getText());
|
||||
}
|
||||
|
||||
void startTest (const String& category)
|
||||
{
|
||||
testResultsBox.clear();
|
||||
startTestButton.setEnabled (false);
|
||||
|
||||
currentTestThread.reset (new TestRunnerThread (*this, category));
|
||||
currentTestThread->startThread();
|
||||
}
|
||||
|
||||
void stopTest()
|
||||
{
|
||||
if (currentTestThread.get() != nullptr)
|
||||
{
|
||||
currentTestThread->stopThread (15000);
|
||||
currentTestThread.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void logMessage (const String& message)
|
||||
{
|
||||
testResultsBox.moveCaretToEnd();
|
||||
testResultsBox.insertTextAtCaret (message + newLine);
|
||||
testResultsBox.moveCaretToEnd();
|
||||
}
|
||||
|
||||
void testFinished()
|
||||
{
|
||||
stopTest();
|
||||
startTestButton.setEnabled (true);
|
||||
logMessage (newLine + "*** Tests finished ***");
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
class TestRunnerThread : public Thread,
|
||||
private Timer
|
||||
{
|
||||
public:
|
||||
TestRunnerThread (UnitTestsDemo& utd, const String& ctg)
|
||||
: Thread ("Unit Tests"),
|
||||
owner (utd),
|
||||
category (ctg)
|
||||
{}
|
||||
|
||||
void run() override
|
||||
{
|
||||
CustomTestRunner runner (*this);
|
||||
|
||||
if (category == "All Tests")
|
||||
runner.runAllTests();
|
||||
else
|
||||
runner.runTestsInCategory (category);
|
||||
|
||||
startTimer (50); // when finished, start the timer which will
|
||||
// wait for the thread to end, then tell our component.
|
||||
}
|
||||
|
||||
void logMessage (const String& message)
|
||||
{
|
||||
WeakReference<UnitTestsDemo> safeOwner (&owner);
|
||||
|
||||
MessageManager::callAsync ([=]
|
||||
{
|
||||
if (auto* o = safeOwner.get())
|
||||
o->logMessage (message);
|
||||
});
|
||||
}
|
||||
|
||||
void timerCallback() override
|
||||
{
|
||||
if (! isThreadRunning())
|
||||
owner.testFinished(); // inform the demo page when done, so it can delete this thread.
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
// This subclass of UnitTestRunner is used to redirect the test output to our
|
||||
// TextBox, and to interrupt the running tests when our thread is asked to stop..
|
||||
class CustomTestRunner : public UnitTestRunner
|
||||
{
|
||||
public:
|
||||
CustomTestRunner (TestRunnerThread& trt) : owner (trt) {}
|
||||
|
||||
void logMessage (const String& message) override
|
||||
{
|
||||
owner.logMessage (message);
|
||||
}
|
||||
|
||||
bool shouldAbortTests() override
|
||||
{
|
||||
return owner.threadShouldExit();
|
||||
}
|
||||
|
||||
private:
|
||||
TestRunnerThread& owner;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CustomTestRunner)
|
||||
};
|
||||
|
||||
UnitTestsDemo& owner;
|
||||
const String category;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TestRunnerThread)
|
||||
};
|
||||
std::unique_ptr<TestRunnerThread> currentTestThread;
|
||||
|
||||
TextButton startTestButton { "Run Unit Tests..." };
|
||||
ComboBox categoriesBox;
|
||||
TextEditor testResultsBox;
|
||||
|
||||
void lookAndFeelChanged() override
|
||||
{
|
||||
testResultsBox.applyFontToAllText (testResultsBox.getFont());
|
||||
}
|
||||
|
||||
JUCE_DECLARE_WEAK_REFERENCEABLE (UnitTestsDemo)
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UnitTestsDemo)
|
||||
};
|
310
deps/juce/examples/Utilities/ValueTreesDemo.h
vendored
Normal file
310
deps/juce/examples/Utilities/ValueTreesDemo.h
vendored
Normal file
@ -0,0 +1,310 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE examples.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
|
||||
WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
|
||||
PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
/*******************************************************************************
|
||||
The block below describes the properties of this PIP. A PIP is a short snippet
|
||||
of code that can be read by the Projucer and used to generate a JUCE project.
|
||||
|
||||
BEGIN_JUCE_PIP_METADATA
|
||||
|
||||
name: ValueTreesDemo
|
||||
version: 1.0.0
|
||||
vendor: JUCE
|
||||
website: http://juce.com
|
||||
description: Showcases value tree features.
|
||||
|
||||
dependencies: juce_core, juce_data_structures, juce_events, juce_graphics,
|
||||
juce_gui_basics
|
||||
exporters: xcode_mac, vs2019, linux_make, androidstudio, xcode_iphone
|
||||
|
||||
moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
|
||||
|
||||
type: Component
|
||||
mainClass: ValueTreesDemo
|
||||
|
||||
useLocalCopy: 1
|
||||
|
||||
END_JUCE_PIP_METADATA
|
||||
|
||||
*******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../Assets/DemoUtilities.h"
|
||||
|
||||
//==============================================================================
|
||||
class ValueTreeItem : public TreeViewItem,
|
||||
private ValueTree::Listener
|
||||
{
|
||||
public:
|
||||
ValueTreeItem (const ValueTree& v, UndoManager& um)
|
||||
: tree (v), undoManager (um)
|
||||
{
|
||||
tree.addListener (this);
|
||||
}
|
||||
|
||||
String getUniqueName() const override
|
||||
{
|
||||
return tree["name"].toString();
|
||||
}
|
||||
|
||||
bool mightContainSubItems() override
|
||||
{
|
||||
return tree.getNumChildren() > 0;
|
||||
}
|
||||
|
||||
void paintItem (Graphics& g, int width, int height) override
|
||||
{
|
||||
if (isSelected())
|
||||
g.fillAll (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::highlightedFill,
|
||||
Colours::teal));
|
||||
|
||||
g.setColour (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::defaultText,
|
||||
Colours::black));
|
||||
g.setFont (15.0f);
|
||||
|
||||
g.drawText (tree["name"].toString(),
|
||||
4, 0, width - 4, height,
|
||||
Justification::centredLeft, true);
|
||||
}
|
||||
|
||||
void itemOpennessChanged (bool isNowOpen) override
|
||||
{
|
||||
if (isNowOpen && getNumSubItems() == 0)
|
||||
refreshSubItems();
|
||||
else
|
||||
clearSubItems();
|
||||
}
|
||||
|
||||
var getDragSourceDescription() override
|
||||
{
|
||||
return "Drag Demo";
|
||||
}
|
||||
|
||||
bool isInterestedInDragSource (const DragAndDropTarget::SourceDetails& dragSourceDetails) override
|
||||
{
|
||||
return dragSourceDetails.description == "Drag Demo";
|
||||
}
|
||||
|
||||
void itemDropped (const DragAndDropTarget::SourceDetails&, int insertIndex) override
|
||||
{
|
||||
OwnedArray<ValueTree> selectedTrees;
|
||||
getSelectedTreeViewItems (*getOwnerView(), selectedTrees);
|
||||
|
||||
moveItems (*getOwnerView(), selectedTrees, tree, insertIndex, undoManager);
|
||||
}
|
||||
|
||||
static void moveItems (TreeView& treeView, const OwnedArray<ValueTree>& items,
|
||||
ValueTree newParent, int insertIndex, UndoManager& undoManager)
|
||||
{
|
||||
if (items.size() > 0)
|
||||
{
|
||||
std::unique_ptr<XmlElement> oldOpenness (treeView.getOpennessState (false));
|
||||
|
||||
for (auto* v : items)
|
||||
{
|
||||
if (v->getParent().isValid() && newParent != *v && ! newParent.isAChildOf (*v))
|
||||
{
|
||||
if (v->getParent() == newParent && newParent.indexOf (*v) < insertIndex)
|
||||
--insertIndex;
|
||||
|
||||
v->getParent().removeChild (*v, &undoManager);
|
||||
newParent.addChild (*v, insertIndex, &undoManager);
|
||||
}
|
||||
}
|
||||
|
||||
if (oldOpenness.get() != nullptr)
|
||||
treeView.restoreOpennessState (*oldOpenness, false);
|
||||
}
|
||||
}
|
||||
|
||||
static void getSelectedTreeViewItems (TreeView& treeView, OwnedArray<ValueTree>& items)
|
||||
{
|
||||
auto numSelected = treeView.getNumSelectedItems();
|
||||
|
||||
for (int i = 0; i < numSelected; ++i)
|
||||
if (auto* vti = dynamic_cast<ValueTreeItem*> (treeView.getSelectedItem (i)))
|
||||
items.add (new ValueTree (vti->tree));
|
||||
}
|
||||
|
||||
private:
|
||||
ValueTree tree;
|
||||
UndoManager& undoManager;
|
||||
|
||||
void refreshSubItems()
|
||||
{
|
||||
clearSubItems();
|
||||
|
||||
for (int i = 0; i < tree.getNumChildren(); ++i)
|
||||
addSubItem (new ValueTreeItem (tree.getChild (i), undoManager));
|
||||
}
|
||||
|
||||
void valueTreePropertyChanged (ValueTree&, const Identifier&) override
|
||||
{
|
||||
repaintItem();
|
||||
}
|
||||
|
||||
void valueTreeChildAdded (ValueTree& parentTree, ValueTree&) override { treeChildrenChanged (parentTree); }
|
||||
void valueTreeChildRemoved (ValueTree& parentTree, ValueTree&, int) override { treeChildrenChanged (parentTree); }
|
||||
void valueTreeChildOrderChanged (ValueTree& parentTree, int, int) override { treeChildrenChanged (parentTree); }
|
||||
void valueTreeParentChanged (ValueTree&) override {}
|
||||
|
||||
void treeChildrenChanged (const ValueTree& parentTree)
|
||||
{
|
||||
if (parentTree == tree)
|
||||
{
|
||||
refreshSubItems();
|
||||
treeHasChanged();
|
||||
setOpen (true);
|
||||
}
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ValueTreeItem)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class ValueTreesDemo : public Component,
|
||||
public DragAndDropContainer,
|
||||
private Timer
|
||||
{
|
||||
public:
|
||||
ValueTreesDemo()
|
||||
{
|
||||
addAndMakeVisible (tree);
|
||||
|
||||
tree.setTitle ("ValueTree");
|
||||
tree.setDefaultOpenness (true);
|
||||
tree.setMultiSelectEnabled (true);
|
||||
rootItem.reset (new ValueTreeItem (createRootValueTree(), undoManager));
|
||||
tree.setRootItem (rootItem.get());
|
||||
|
||||
addAndMakeVisible (undoButton);
|
||||
addAndMakeVisible (redoButton);
|
||||
undoButton.onClick = [this] { undoManager.undo(); };
|
||||
redoButton.onClick = [this] { undoManager.redo(); };
|
||||
|
||||
startTimer (500);
|
||||
|
||||
setSize (500, 500);
|
||||
}
|
||||
|
||||
~ValueTreesDemo() override
|
||||
{
|
||||
tree.setRootItem (nullptr);
|
||||
}
|
||||
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
g.fillAll (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::windowBackground));
|
||||
}
|
||||
|
||||
void resized() override
|
||||
{
|
||||
auto r = getLocalBounds().reduced (8);
|
||||
|
||||
auto buttons = r.removeFromBottom (22);
|
||||
undoButton.setBounds (buttons.removeFromLeft (100));
|
||||
buttons.removeFromLeft (6);
|
||||
redoButton.setBounds (buttons.removeFromLeft (100));
|
||||
|
||||
r.removeFromBottom (4);
|
||||
tree.setBounds (r);
|
||||
}
|
||||
|
||||
static ValueTree createTree (const String& desc)
|
||||
{
|
||||
ValueTree t ("Item");
|
||||
t.setProperty ("name", desc, nullptr);
|
||||
return t;
|
||||
}
|
||||
|
||||
static ValueTree createRootValueTree()
|
||||
{
|
||||
auto vt = createTree ("This demo displays a ValueTree as a treeview.");
|
||||
vt.appendChild (createTree ("You can drag around the nodes to rearrange them"), nullptr);
|
||||
vt.appendChild (createTree ("..and press 'delete' or 'backspace' to delete them"), nullptr);
|
||||
vt.appendChild (createTree ("Then, you can use the undo/redo buttons to undo these changes"), nullptr);
|
||||
|
||||
int n = 1;
|
||||
vt.appendChild (createRandomTree (n, 0), nullptr);
|
||||
|
||||
return vt;
|
||||
}
|
||||
|
||||
static ValueTree createRandomTree (int& counter, int depth)
|
||||
{
|
||||
auto t = createTree ("Item " + String (counter++));
|
||||
|
||||
if (depth < 3)
|
||||
for (int i = 1 + Random::getSystemRandom().nextInt (7); --i >= 0;)
|
||||
t.appendChild (createRandomTree (counter, depth + 1), nullptr);
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
void deleteSelectedItems()
|
||||
{
|
||||
OwnedArray<ValueTree> selectedItems;
|
||||
ValueTreeItem::getSelectedTreeViewItems (tree, selectedItems);
|
||||
|
||||
for (auto* v : selectedItems)
|
||||
{
|
||||
if (v->getParent().isValid())
|
||||
v->getParent().removeChild (*v, &undoManager);
|
||||
}
|
||||
}
|
||||
|
||||
bool keyPressed (const KeyPress& key) override
|
||||
{
|
||||
if (key == KeyPress::deleteKey || key == KeyPress::backspaceKey)
|
||||
{
|
||||
deleteSelectedItems();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (key == KeyPress ('z', ModifierKeys::commandModifier, 0))
|
||||
{
|
||||
undoManager.undo();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (key == KeyPress ('z', ModifierKeys::commandModifier | ModifierKeys::shiftModifier, 0))
|
||||
{
|
||||
undoManager.redo();
|
||||
return true;
|
||||
}
|
||||
|
||||
return Component::keyPressed (key);
|
||||
}
|
||||
|
||||
private:
|
||||
TreeView tree;
|
||||
TextButton undoButton { "Undo" },
|
||||
redoButton { "Redo" };
|
||||
|
||||
std::unique_ptr<ValueTreeItem> rootItem;
|
||||
UndoManager undoManager;
|
||||
|
||||
void timerCallback() override
|
||||
{
|
||||
undoManager.beginNewTransaction();
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ValueTreesDemo)
|
||||
};
|
400
deps/juce/examples/Utilities/XMLandJSONDemo.h
vendored
Normal file
400
deps/juce/examples/Utilities/XMLandJSONDemo.h
vendored
Normal file
@ -0,0 +1,400 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE examples.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
|
||||
WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
|
||||
PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
/*******************************************************************************
|
||||
The block below describes the properties of this PIP. A PIP is a short snippet
|
||||
of code that can be read by the Projucer and used to generate a JUCE project.
|
||||
|
||||
BEGIN_JUCE_PIP_METADATA
|
||||
|
||||
name: XMLandJSONDemo
|
||||
version: 1.0.0
|
||||
vendor: JUCE
|
||||
website: http://juce.com
|
||||
description: Reads XML and JSON files.
|
||||
|
||||
dependencies: juce_core, juce_data_structures, juce_events, juce_graphics,
|
||||
juce_gui_basics, juce_gui_extra
|
||||
exporters: xcode_mac, vs2019, linux_make, androidstudio, xcode_iphone
|
||||
|
||||
moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
|
||||
|
||||
type: Component
|
||||
mainClass: XMLandJSONDemo
|
||||
|
||||
useLocalCopy: 1
|
||||
|
||||
END_JUCE_PIP_METADATA
|
||||
|
||||
*******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../Assets/DemoUtilities.h"
|
||||
|
||||
//==============================================================================
|
||||
class XmlTreeItem : public TreeViewItem
|
||||
{
|
||||
public:
|
||||
XmlTreeItem (XmlElement& x) : xml (x) {}
|
||||
|
||||
String getUniqueName() const override
|
||||
{
|
||||
if (xml.getTagName().isEmpty())
|
||||
return "unknown";
|
||||
|
||||
return xml.getTagName();
|
||||
}
|
||||
|
||||
bool mightContainSubItems() override
|
||||
{
|
||||
return xml.getFirstChildElement() != nullptr;
|
||||
}
|
||||
|
||||
void paintItem (Graphics& g, int width, int height) override
|
||||
{
|
||||
// if this item is selected, fill it with a background colour..
|
||||
if (isSelected())
|
||||
g.fillAll (Colours::blue.withAlpha (0.3f));
|
||||
|
||||
// use a "colour" attribute in the xml tag for this node to set the text colour..
|
||||
g.setColour (Colour::fromString (xml.getStringAttribute ("colour", "ff000000")));
|
||||
g.setFont ((float) height * 0.7f);
|
||||
|
||||
// draw the xml element's tag name..
|
||||
g.drawText (xml.getTagName(),
|
||||
4, 0, width - 4, height,
|
||||
Justification::centredLeft, true);
|
||||
}
|
||||
|
||||
void itemOpennessChanged (bool isNowOpen) override
|
||||
{
|
||||
if (isNowOpen)
|
||||
{
|
||||
// if we've not already done so, we'll now add the tree's sub-items. You could
|
||||
// also choose to delete the existing ones and refresh them if that's more suitable
|
||||
// in your app.
|
||||
if (getNumSubItems() == 0)
|
||||
{
|
||||
// create and add sub-items to this node of the tree, corresponding to
|
||||
// each sub-element in the XML..
|
||||
|
||||
for (auto* child : xml.getChildIterator())
|
||||
if (child != nullptr)
|
||||
addSubItem (new XmlTreeItem (*child));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// in this case, we'll leave any sub-items in the tree when the node gets closed,
|
||||
// though you could choose to delete them if that's more appropriate for
|
||||
// your application.
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
XmlElement& xml;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (XmlTreeItem)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class JsonTreeItem : public TreeViewItem
|
||||
{
|
||||
public:
|
||||
JsonTreeItem (Identifier i, var value)
|
||||
: identifier (i),
|
||||
json (value)
|
||||
{}
|
||||
|
||||
String getUniqueName() const override
|
||||
{
|
||||
return identifier.toString() + "_id";
|
||||
}
|
||||
|
||||
bool mightContainSubItems() override
|
||||
{
|
||||
if (auto* obj = json.getDynamicObject())
|
||||
return obj->getProperties().size() > 0;
|
||||
|
||||
return json.isArray();
|
||||
}
|
||||
|
||||
void paintItem (Graphics& g, int width, int height) override
|
||||
{
|
||||
// if this item is selected, fill it with a background colour..
|
||||
if (isSelected())
|
||||
g.fillAll (Colours::blue.withAlpha (0.3f));
|
||||
|
||||
g.setColour (Colours::black);
|
||||
g.setFont ((float) height * 0.7f);
|
||||
|
||||
// draw the element's tag name..
|
||||
g.drawText (getText(),
|
||||
4, 0, width - 4, height,
|
||||
Justification::centredLeft, true);
|
||||
}
|
||||
|
||||
void itemOpennessChanged (bool isNowOpen) override
|
||||
{
|
||||
if (isNowOpen)
|
||||
{
|
||||
// if we've not already done so, we'll now add the tree's sub-items. You could
|
||||
// also choose to delete the existing ones and refresh them if that's more suitable
|
||||
// in your app.
|
||||
if (getNumSubItems() == 0)
|
||||
{
|
||||
// create and add sub-items to this node of the tree, corresponding to
|
||||
// the type of object this var represents
|
||||
|
||||
if (json.isArray())
|
||||
{
|
||||
for (int i = 0; i < json.size(); ++i)
|
||||
{
|
||||
auto& child = json[i];
|
||||
jassert (! child.isVoid());
|
||||
addSubItem (new JsonTreeItem ({}, child));
|
||||
}
|
||||
}
|
||||
else if (auto* obj = json.getDynamicObject())
|
||||
{
|
||||
auto& props = obj->getProperties();
|
||||
|
||||
for (int i = 0; i < props.size(); ++i)
|
||||
{
|
||||
auto id = props.getName (i);
|
||||
|
||||
auto child = props[id];
|
||||
jassert (! child.isVoid());
|
||||
|
||||
addSubItem (new JsonTreeItem (id, child));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// in this case, we'll leave any sub-items in the tree when the node gets closed,
|
||||
// though you could choose to delete them if that's more appropriate for
|
||||
// your application.
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
Identifier identifier;
|
||||
var json;
|
||||
|
||||
/** Returns the text to display in the tree.
|
||||
This is a little more complex for JSON than XML as nodes can be strings, objects or arrays.
|
||||
*/
|
||||
String getText() const
|
||||
{
|
||||
String text;
|
||||
|
||||
if (identifier.isValid())
|
||||
text << identifier.toString();
|
||||
|
||||
if (! json.isVoid())
|
||||
{
|
||||
if (text.isNotEmpty() && (! json.isArray()))
|
||||
text << ": ";
|
||||
|
||||
if (json.isObject() && (! identifier.isValid()))
|
||||
text << "[Array]";
|
||||
else if (! json.isArray())
|
||||
text << json.toString();
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JsonTreeItem)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class XMLandJSONDemo : public Component,
|
||||
private CodeDocument::Listener
|
||||
{
|
||||
public:
|
||||
/** The type of database to parse. */
|
||||
enum Type
|
||||
{
|
||||
xml,
|
||||
json
|
||||
};
|
||||
|
||||
XMLandJSONDemo()
|
||||
{
|
||||
setOpaque (true);
|
||||
|
||||
addAndMakeVisible (typeBox);
|
||||
typeBox.addItem ("XML", 1);
|
||||
typeBox.addItem ("JSON", 2);
|
||||
|
||||
typeBox.onChange = [this]
|
||||
{
|
||||
if (typeBox.getSelectedId() == 1)
|
||||
reset (xml);
|
||||
else
|
||||
reset (json);
|
||||
};
|
||||
|
||||
comboBoxLabel.attachToComponent (&typeBox, true);
|
||||
|
||||
addAndMakeVisible (codeDocumentComponent);
|
||||
codeDocument.addListener (this);
|
||||
|
||||
resultsTree.setTitle ("Results");
|
||||
addAndMakeVisible (resultsTree);
|
||||
resultsTree.setColour (TreeView::backgroundColourId, Colours::white);
|
||||
resultsTree.setDefaultOpenness (true);
|
||||
|
||||
addAndMakeVisible (errorMessage);
|
||||
errorMessage.setReadOnly (true);
|
||||
errorMessage.setMultiLine (true);
|
||||
errorMessage.setCaretVisible (false);
|
||||
errorMessage.setColour (TextEditor::outlineColourId, Colours::transparentWhite);
|
||||
errorMessage.setColour (TextEditor::shadowColourId, Colours::transparentWhite);
|
||||
|
||||
typeBox.setSelectedId (1);
|
||||
|
||||
setSize (500, 500);
|
||||
}
|
||||
|
||||
~XMLandJSONDemo() override
|
||||
{
|
||||
resultsTree.setRootItem (nullptr);
|
||||
}
|
||||
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
g.fillAll (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::windowBackground));
|
||||
}
|
||||
|
||||
void resized() override
|
||||
{
|
||||
auto area = getLocalBounds();
|
||||
|
||||
typeBox.setBounds (area.removeFromTop (36).removeFromRight (150).reduced (8));
|
||||
codeDocumentComponent.setBounds (area.removeFromTop(area.getHeight() / 2).reduced (8));
|
||||
resultsTree .setBounds (area.reduced (8));
|
||||
errorMessage .setBounds (resultsTree.getBounds());
|
||||
}
|
||||
|
||||
private:
|
||||
ComboBox typeBox;
|
||||
Label comboBoxLabel { {}, "Database Type:" };
|
||||
CodeDocument codeDocument;
|
||||
CodeEditorComponent codeDocumentComponent { codeDocument, nullptr };
|
||||
TreeView resultsTree;
|
||||
|
||||
std::unique_ptr<TreeViewItem> rootItem;
|
||||
std::unique_ptr<XmlElement> parsedXml;
|
||||
TextEditor errorMessage;
|
||||
|
||||
void rebuildTree()
|
||||
{
|
||||
std::unique_ptr<XmlElement> openness;
|
||||
|
||||
if (rootItem.get() != nullptr)
|
||||
openness = rootItem->getOpennessState();
|
||||
|
||||
createNewRootNode();
|
||||
|
||||
if (openness.get() != nullptr && rootItem.get() != nullptr)
|
||||
rootItem->restoreOpennessState (*openness);
|
||||
}
|
||||
|
||||
void createNewRootNode()
|
||||
{
|
||||
// clear the current tree
|
||||
resultsTree.setRootItem (nullptr);
|
||||
rootItem.reset();
|
||||
|
||||
// try and parse the editor's contents
|
||||
switch (typeBox.getSelectedItemIndex())
|
||||
{
|
||||
case xml: rootItem.reset (rebuildXml()); break;
|
||||
case json: rootItem.reset (rebuildJson()); break;
|
||||
default: rootItem.reset(); break;
|
||||
}
|
||||
|
||||
// if we have a valid TreeViewItem hide any old error messages and set our TreeView to use it
|
||||
if (rootItem.get() != nullptr)
|
||||
errorMessage.clear();
|
||||
|
||||
errorMessage.setVisible (! errorMessage.isEmpty());
|
||||
|
||||
resultsTree.setRootItem (rootItem.get());
|
||||
}
|
||||
|
||||
/** Parses the editor's contents as XML. */
|
||||
TreeViewItem* rebuildXml()
|
||||
{
|
||||
parsedXml.reset();
|
||||
|
||||
XmlDocument doc (codeDocument.getAllContent());
|
||||
parsedXml = doc.getDocumentElement();
|
||||
|
||||
if (parsedXml.get() == nullptr)
|
||||
{
|
||||
auto error = doc.getLastParseError();
|
||||
|
||||
if (error.isEmpty())
|
||||
error = "Unknown error";
|
||||
|
||||
errorMessage.setText ("Error parsing XML: " + error, dontSendNotification);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return new XmlTreeItem (*parsedXml);
|
||||
}
|
||||
|
||||
/** Parses the editor's contents as JSON. */
|
||||
TreeViewItem* rebuildJson()
|
||||
{
|
||||
var parsedJson;
|
||||
auto result = JSON::parse (codeDocument.getAllContent(), parsedJson);
|
||||
|
||||
if (! result.wasOk())
|
||||
{
|
||||
errorMessage.setText ("Error parsing JSON: " + result.getErrorMessage());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return new JsonTreeItem (Identifier(), parsedJson);
|
||||
}
|
||||
|
||||
/** Clears the editor and loads some default text. */
|
||||
void reset (Type type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case xml: codeDocument.replaceAllContent (loadEntireAssetIntoString ("treedemo.xml")); break;
|
||||
case json: codeDocument.replaceAllContent (loadEntireAssetIntoString ("juce_module_info")); break;
|
||||
default: codeDocument.replaceAllContent ({}); break;
|
||||
}
|
||||
}
|
||||
|
||||
void codeDocumentTextInserted (const String&, int) override { rebuildTree(); }
|
||||
void codeDocumentTextDeleted (int, int) override { rebuildTree(); }
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (XMLandJSONDemo)
|
||||
};
|
Reference in New Issue
Block a user