/* ============================================================================== This file is part of the JUCE 6 technical preview. Copyright (c) 2020 - Raw Material Software Limited You may use this code under the terms of the GPL v3 (see www.gnu.org/licenses). For this technical preview, this file is not subject to commercial licensing. 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()); }