/* ============================================================================== This file is part of the JUCE framework. Copyright (c) Raw Material Software Limited JUCE is an open source framework subject to commercial or open source licensing. By downloading, installing, or using the JUCE framework, or combining the JUCE framework with any other source code, object code, content or any other copyrightable work, you agree to the terms of the JUCE End User Licence Agreement, and all incorporated terms including the JUCE Privacy Policy and the JUCE Website Terms of Service, as applicable, which will bind you. If you do not agree to the terms of these agreements, we will not license the JUCE framework to you, and you must discontinue the installation or download process and cease use of the JUCE framework. JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/ JUCE Privacy Policy: https://juce.com/juce-privacy-policy JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/ Or: You may also use this code under the terms of the AGPLv3: https://www.gnu.org/licenses/agpl-3.0.en.html THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED. ============================================================================== */ struct BlankCanvas final : public AnimatedContent { String getName() const override { return "Blank Canvas"; } void reset() override {} void handleTouch (Point) override {} void generateCanvas (Graphics&, SharedCanvasDescription&, Rectangle) override {} }; //============================================================================== struct GridLines final : public AnimatedContent { String getName() const override { return "Grid Lines"; } void reset() override {} void handleTouch (Point) override {} void generateCanvas (Graphics& g, SharedCanvasDescription& canvas, Rectangle) override { auto limits = canvas.getLimits(); float lineThickness = 0.1f; g.setColour (Colours::blue); g.drawRect (canvas.getLimits(), lineThickness); for (float y = limits.getY(); y < limits.getBottom(); y += 2.0f) g.drawLine (limits.getX(), y, limits.getRight(), y, lineThickness); for (float x = limits.getX(); x < limits.getRight(); x += 2.0f) g.drawLine (x, limits.getY(), x, limits.getBottom(), lineThickness); g.setColour (Colours::darkred); g.drawLine (limits.getX(), limits.getCentreY(), limits.getRight(), limits.getCentreY(), lineThickness); g.drawLine (limits.getCentreX(), limits.getY(), limits.getCentreX(), limits.getBottom(), lineThickness); g.setColour (Colours::lightgrey); g.drawLine (limits.getX(), limits.getY(), limits.getRight(), limits.getBottom(), lineThickness); g.drawLine (limits.getX(), limits.getBottom(), limits.getRight(), limits.getY(), lineThickness); } }; //============================================================================== struct BackgroundLogo : public AnimatedContent { BackgroundLogo() { static const char logoData[] = R"blahblah( )blahblah"; logo = Drawable::createFromSVG (*parseXML (logoData)); } String getName() const override { return "Background Image"; } void reset() override {} void handleTouch (Point) override {} void generateCanvas (Graphics& g, SharedCanvasDescription& canvas, Rectangle) override { logo->drawWithin (g, canvas.getLimits().reduced (3.0f), RectanglePlacement (RectanglePlacement::centred), 0.6f); } std::unique_ptr logo; }; //============================================================================== struct FlockDemo : public BackgroundLogo { String getName() const override { return "Flock"; } void setNumBirds (int numBirds) { BackgroundLogo::reset(); birds.clear(); for (int i = numBirds; --i >= 0;) birds.add ({}); centreOfGravity = {}; lastGravityMove = {}; fakeMouseTouchLengthToRun = 0; fakeMouseTouchPosition = {}; fakeMouseTouchVelocity = {}; } void reset() override { BackgroundLogo::reset(); setNumBirds (100); } void generateCanvas (Graphics& g, SharedCanvasDescription& canvas, Rectangle activeArea) override { BackgroundLogo::generateCanvas (g, canvas, activeArea); if (Time::getCurrentTime() > lastGravityMove + RelativeTime::seconds (0.5)) { if (fakeMouseTouchLengthToRun > 0) { --fakeMouseTouchLengthToRun; fakeMouseTouchPosition += fakeMouseTouchVelocity; centreOfGravity = fakeMouseTouchPosition; } else { centreOfGravity = {}; if (rng.nextInt (300) == 2 && canvas.clients.size() > 0) { fakeMouseTouchLengthToRun = 50; fakeMouseTouchPosition = canvas.clients.getReference (rng.nextInt (canvas.clients.size())).centre; fakeMouseTouchVelocity = { rng.nextFloat() * 0.3f - 0.15f, rng.nextFloat() * 0.3f - 0.15f }; } } } g.setColour (Colours::white.withAlpha (0.2f)); if (! centreOfGravity.isOrigin()) g.fillEllipse (centreOfGravity.getX() - 1.0f, centreOfGravity.getY() - 1.0f, 2.0f, 2.0f); for (int i = 0; i < birds.size(); ++i) for (int j = i + 1; j < birds.size(); ++j) attractBirds (birds.getReference (i), birds.getReference (j)); for (auto& b : birds) { if (! centreOfGravity.isOrigin()) b.move (centreOfGravity, 0.4f); b.update(); b.draw (g); b.bounceOffEdges (canvas.getLimits().expanded (1.0f)); } for (int i = rings.size(); --i >= 0;) { if (rings.getReference (i).update()) rings.getReference (i).draw (g); else rings.remove (i); } } bool isRingNear (Point p) const { for (auto& r : rings) if (r.centre.getDistanceFrom (p) < 1.0f) return true; return false; } void handleTouch (Point position) override { lastGravityMove = Time::getCurrentTime(); centreOfGravity = position; fakeMouseTouchLengthToRun = 0; if (! isRingNear (position)) rings.add ({ position, 1.0f, 0.5f }); } //============================================================================== struct Bird { Bird() { Random randGen; pos.x = randGen.nextFloat() * 10.0f - 5.0f; pos.y = randGen.nextFloat() * 10.0f - 5.0f; velocity.x = randGen.nextFloat() * 0.001f; velocity.y = randGen.nextFloat() * 0.001f; colour = Colour::fromHSV (randGen.nextFloat(), 0.2f, 0.9f, randGen.nextFloat() * 0.4f + 0.2f); shape.addTriangle (0.0f, 0.0f, -0.3f, 1.0f, 0.3f, 1.0f); shape = shape.createPathWithRoundedCorners (0.2f); shape.applyTransform (AffineTransform::scale (randGen.nextFloat() + 1.0f)); } Point pos, velocity, acc; Colour colour; Path shape; void move (Point target, float strength) { auto r = target - pos; float rSquared = jmax (0.1f, (r.x * r.x) + (r.y * r.y)); if (rSquared > 1.0f) velocity += (r * strength / rSquared); acc = {}; } void accelerate (Point acceleration) { acc += acceleration; } void bounceOffEdges (Rectangle limits) { if (pos.x < limits.getX()) { velocity.x = std::abs (velocity.x); acc = {}; } if (pos.x > limits.getRight()) { velocity.x = -std::abs (velocity.x); acc = {}; } if (pos.y < limits.getY()) { velocity.y = std::abs (velocity.y); acc = {}; } if (pos.y > limits.getBottom()) { velocity.y = -std::abs (velocity.y); acc = {}; } } void update() { velocity += acc; float length = velocity.getDistanceFromOrigin(); const float maxSpeed = 0.5f; if (length > maxSpeed) velocity = getVectorWithLength (velocity, maxSpeed); pos += velocity; } void draw (Graphics& g) { g.setColour (colour); g.fillPath (shape, AffineTransform::rotation (Point().getAngleToPoint (velocity)).translated (pos)); } }; static Point getVectorWithLength (Point v, float newLength) { return v * (newLength / v.getDistanceFromOrigin()); } static void attractBirds (Bird& b1, Bird& b2) { auto delta = b1.pos - b2.pos; const float zoneRadius = 10.0f; const float low = 0.4f; const float high = 0.65f; const float strength = 0.01f; const float distanceSquared = (delta.x * delta.x) * (delta.y * delta.y); if (distanceSquared < zoneRadius * zoneRadius && distanceSquared > 0.01f) { float proportion = distanceSquared / (zoneRadius * zoneRadius); if (proportion < low) { const float F = (low / proportion - 1.0f) * strength * 0.003f; delta = getVectorWithLength (delta, F); b1.accelerate (delta); b2.accelerate (-delta); } else if (proportion < high) { const float regionSize = high - low; const float adjustedProportion = (proportion - low) / regionSize; const float F = (0.5f - std::cos (adjustedProportion * MathConstants::twoPi) * 0.5f + 0.5f) * strength; b1.accelerate (getVectorWithLength (b2.velocity, F)); b2.accelerate (getVectorWithLength (b1.velocity, F)); } else { const float regionSize = 1.0f - high; const float adjustedProportion = (proportion - high) / regionSize; const float F = (0.5f - std::cos (adjustedProportion * MathConstants::twoPi) * 0.5f + 0.5f) * strength; delta = getVectorWithLength (delta, F); b1.accelerate (-delta); b2.accelerate (delta); } } } Random rng; Array birds; Point centreOfGravity; Time lastGravityMove; int fakeMouseTouchLengthToRun = 0; Point fakeMouseTouchPosition, fakeMouseTouchVelocity; //============================================================================== struct Ring { Point centre; float diameter, opacity; bool update() { diameter += 0.7f; opacity -= 0.01f; return opacity > 0; } void draw (Graphics& g) { const float thickness = 0.2f; auto r = Rectangle (diameter, diameter).withCentre (centre); Path p; p.addEllipse (r); p.addEllipse (r.reduced (thickness)); p.setUsingNonZeroWinding (false); g.setColour (Colours::white.withAlpha (opacity)); g.fillPath (p); } }; Array rings; }; //============================================================================== struct FlockWithText final : public FlockDemo { FlockWithText() { messages.add ("JUCE is our cross-platform C++ framework\n\n" "In this demo, the same C++ app is running natively on NUMDEVICES devices,\n" "which are sharing their graphic state via the network"); messages.add ("No other libraries were needed to create this demo.\n" "JUCE provides thousands of classes for cross-platform GUI,\n" "audio, networking, data-structures and many other common tasks"); messages.add ("As well as a code library, JUCE provides tools for managing\n" "cross-platform projects that are built with Xcode,\n" "Visual Studio, Android Studio, GCC and other compilers"); messages.add ("JUCE can be used to build desktop or mobile apps, and also\n" "audio plug-ins in the VST2, VST3, AudioUnit and AAX formats"); } String getName() const override { return "Flock with text"; } void reset() override { FlockDemo::reset(); currentMessage = 0; currentMessageStart = {}; clientIndex = 0; } void generateCanvas (Graphics& g, SharedCanvasDescription& canvas, Rectangle activeArea) override { FlockDemo::generateCanvas (g, canvas, activeArea); const float textSize = 0.5f; // inches const float textBlockWidth = 20.0f; // inches tick(); Graphics::ScopedSaveState ss (g); const float scale = 20.0f; // scaled to allow the fonts to use more reasonable sizes g.addTransform (AffineTransform::scale (1.0f / scale)); String text = String (messages[currentMessage]).replace ("NUMDEVICES", String (canvas.clients.size())); AttributedString as; as.append (text, FontOptions (textSize * scale), Colour (0x80ffffff).withMultipliedAlpha (alpha)); as.setJustification (Justification::centred); auto middle = canvas.clients[clientIndex % canvas.clients.size()].centre * scale; as.draw (g, Rectangle (textBlockWidth * scale, textBlockWidth * scale).withCentre (middle)); } void tick() { const double displayTimeSeconds = 5.0; const double fadeTimeSeconds = 1.0; Time now = Time::getCurrentTime(); const double secondsSinceStart = (now - currentMessageStart).inSeconds(); if (secondsSinceStart > displayTimeSeconds) { currentMessageStart = now; currentMessage = (currentMessage + 1) % messages.size(); ++clientIndex; alpha = 0; } else if (secondsSinceStart > displayTimeSeconds - fadeTimeSeconds) { alpha = (float) jlimit (0.0, 1.0, (displayTimeSeconds - secondsSinceStart) / fadeTimeSeconds); } else if (secondsSinceStart < fadeTimeSeconds) { alpha = (float) jlimit (0.0, 1.0, secondsSinceStart / fadeTimeSeconds); } } StringArray messages; int currentMessage = 0, clientIndex = 0; float alpha = 0; Point centre; Time currentMessageStart; }; //============================================================================== struct SmallFlock final : public FlockDemo { String getName() const override { return "Small Flock"; } void reset() override { setNumBirds (20); } }; //============================================================================== struct BigFlock final : public FlockDemo { String getName() const override { return "Big Flock"; } void reset() override { setNumBirds (200); } }; //============================================================================== template struct MultiLogo final : public BackgroundLogo { String getName() const override { return "Multi-Logo " + String ((int) numHorizontalLogos); } void generateCanvas (Graphics& g, SharedCanvasDescription& canvas, Rectangle) override { float indent = 0.5f; float logoSize = canvas.getLimits().getWidth() / numHorizontalLogos; auto limits = canvas.getLimits(); for (float x = limits.getX(); x < limits.getRight(); x += logoSize) { for (float y = limits.getY(); y < limits.getBottom(); y += logoSize) { logo->drawWithin (g, Rectangle (x, y, logoSize, logoSize).reduced (indent), RectanglePlacement (RectanglePlacement::centred), 0.5f); } } } }; //============================================================================== inline void createAllDemos (OwnedArray& demos) { demos.add (new FlockDemo()); demos.add (new FlockWithText()); demos.add (new SmallFlock()); demos.add (new BigFlock()); demos.add (new BackgroundLogo()); demos.add (new MultiLogo<5>()); demos.add (new MultiLogo<10>()); demos.add (new GridLines()); demos.add (new BlankCanvas()); }