/* ============================================================================== This file is part of the JUCE library. Copyright (c) 2020 - Raw Material Software Limited JUCE is an open source library subject to commercial or open-source licensing. By using JUCE, you agree to the terms of both the JUCE 6 End-User License Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). End User License Agreement: www.juce.com/juce-6-licence Privacy Policy: www.juce.com/juce-privacy-policy Or: You may also use this code under the terms of the GPL v3 (see www.gnu.org/licenses). JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE DISCLAIMED. ============================================================================== */ struct BlankCanvas : public AnimatedContent { String getName() const override { return "Blank Canvas"; } void reset() override {} void handleTouch (Point) override {} void generateCanvas (Graphics&, SharedCanvasDescription&, Rectangle) override {} }; //============================================================================== struct GridLines : 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 : public FlockDemo { FlockWithText() { messages.add ("JUCE is our cross-platform C++ framework\n\n" "In this demo, the same C++ app is running natively on NUMDEVICES devices,\n" "which are sharing their graphic state via the network"); messages.add ("No other libraries were needed to create this demo.\n" "JUCE provides thousands of classes for cross-platform GUI,\n" "audio, networking, data-structures and many other common tasks"); messages.add ("As well as a code library, JUCE provides tools for managing\n" "cross-platform projects that are built with Xcode,\n" "Visual Studio, Android Studio, GCC and other compilers"); messages.add ("JUCE can be used to build desktop or mobile apps, and also\n" "audio plug-ins in the VST2, VST3, AudioUnit, AAX and RTAS formats"); } String getName() const override { return "Flock with text"; } void reset() override { FlockDemo::reset(); currentMessage = 0; currentMessageStart = {}; clientIndex = 0; } void generateCanvas (Graphics& g, SharedCanvasDescription& canvas, Rectangle activeArea) override { FlockDemo::generateCanvas (g, canvas, activeArea); const float textSize = 0.5f; // inches const float textBlockWidth = 20.0f; // inches tick(); Graphics::ScopedSaveState ss (g); const float scale = 20.0f; // scaled to allow the fonts to use more reasonable sizes g.addTransform (AffineTransform::scale (1.0f / scale)); String text = String (messages[currentMessage]).replace ("NUMDEVICES", String (canvas.clients.size())); AttributedString as; as.append (text, Font (textSize * scale), Colour (0x80ffffff).withMultipliedAlpha (alpha)); as.setJustification (Justification::centred); auto middle = canvas.clients[clientIndex % canvas.clients.size()].centre * scale; as.draw (g, Rectangle (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 : public FlockDemo { String getName() const override { return "Small Flock"; } void reset() override { setNumBirds (20); } }; //============================================================================== struct BigFlock : public FlockDemo { String getName() const override { return "Big Flock"; } void reset() override { setNumBirds (200); } }; //============================================================================== template struct MultiLogo : 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); } } } }; //============================================================================== 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()); }