/* ============================================================================== This file is part of the JUCE framework examples. Copyright (c) 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" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ============================================================================== */ /******************************************************************************* 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: AnimationEasingDemo version: 1.0.0 vendor: JUCE website: http://juce.com description: Application for comparing animation easings. dependencies: juce_gui_basics, juce_animation exporters: xcode_mac, vs2022, androidstudio, xcode_iphone moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1 type: Component mainClass: AnimationEasingDemo useLocalCopy: 1 END_JUCE_PIP_METADATA *******************************************************************************/ #pragma once #include "../Assets/DemoUtilities.h" struct AnimationEasingDemoConstants { static constexpr auto smallGapSize = 5; static constexpr auto mediumGapSize = smallGapSize * 2; static constexpr auto largeGapSize = mediumGapSize * 2; static constexpr auto defaultComponentHeight = 35; inline static const auto cp1AccentColour = Colour { 0xffff0088 }; inline static const auto cp2AccentColour = Colour { 0xff00aabb }; }; struct AnimationEasingDemoHelpers { static int calculateSectionSize (int originalSize, int numberOfSections, int gapSize) { const auto totalGapSize = (float) gapSize * ((float) numberOfSections - 1.0f); const auto totalSizeOfAllSections = (float) originalSize - totalGapSize; return roundToInt (totalSizeOfAllSections / (float) numberOfSections); } static void layoutComponentsHorizontally (Rectangle bounds, const std::vector& components, int gapSize = AnimationEasingDemoConstants::smallGapSize) { const auto componentWidth = calculateSectionSize (bounds.getWidth(), (int) components.size(), gapSize); for (auto* component : components) { const auto newComponentBounds = bounds.removeFromLeft (componentWidth); if (component != nullptr) component->setBounds (newComponentBounds); bounds.removeFromLeft (gapSize); } } static void layoutComponentsVertically (Rectangle bounds, const std::vector& components, int gapSize = AnimationEasingDemoConstants::smallGapSize) { const auto componentHeight = calculateSectionSize (bounds.getHeight(), (int) components.size(), gapSize); for (auto* component : components) { const auto newComponentBounds = bounds.removeFromTop (componentHeight); if (component != nullptr) component->setBounds (newComponentBounds); bounds.removeFromTop (gapSize); } } static void layoutComponentsVerticallyOrHorizontally (Rectangle bounds, const std::vector& components, int gapSize = AnimationEasingDemoConstants::smallGapSize) { if (bounds.getHeight() > bounds.getWidth()) layoutComponentsVertically (bounds, components, gapSize); else layoutComponentsHorizontally (bounds, components, gapSize); } }; struct AnimationSettings { Value shouldAnimatePosition; Value shouldAnimateSize; Value shouldAnimateAlpha ; Value durationMs; }; class AnimationSettingsComponent final : public Component { public: explicit AnimationSettingsComponent (const AnimationSettings& settingsIn) { playbackControls.button.onClick = [&] { NullCheckedInvocation::invoke (onAnimate); }; durationControls.slider.getValueObject().referTo (settingsIn.durationMs); playbackControls.positionToggle.getToggleStateValue().referTo (settingsIn.shouldAnimatePosition); playbackControls.sizeToggle.getToggleStateValue().referTo (settingsIn.shouldAnimateSize); playbackControls.alphaToggle.getToggleStateValue().referTo (settingsIn.shouldAnimateAlpha); addAndMakeVisible (durationControls); addAndMakeVisible (playbackControls); } void resized() final { AnimationEasingDemoHelpers::layoutComponentsVertically (getLocalBounds(), { &playbackControls, &durationControls }); } std::function onAnimate; private: struct DurationControls final : public Component { DurationControls() { slider.setRange (50.0, 5000.0, 10.0); slider.setValue (1000.0); slider.setTextValueSuffix (" ms"); addAndMakeVisible (label); addAndMakeVisible (slider); } void resized() final { auto bounds = getLocalBounds(); const auto labelWidth = GlyphArrangement::getStringWidthInt (label.getFont(), label.getText()) + AnimationEasingDemoConstants::largeGapSize; label.setBounds (bounds.removeFromLeft (labelWidth)); slider.setBounds (bounds); } Label label { "", "Duration:" }; Slider slider { Slider::SliderStyle::LinearHorizontal, Slider::TextEntryBoxPosition::TextBoxRight }; }; struct PlaybackControls final : public Component { PlaybackControls() { addAndMakeVisible (button); addAndMakeVisible (positionToggle); addAndMakeVisible (sizeToggle); addAndMakeVisible (alphaToggle); } void resized() final { AnimationEasingDemoHelpers::layoutComponentsHorizontally (getLocalBounds(), { &button, nullptr, &positionToggle, &sizeToggle, &alphaToggle }); } TextButton button { "Animate" }; ToggleButton positionToggle { "Position" }; ToggleButton sizeToggle { "Size" }; ToggleButton alphaToggle { "Alpha" }; }; DurationControls durationControls; PlaybackControls playbackControls; }; static Point convertPointInBoundsToBezierPoint (const Point& point, const Rectangle& bounds) { return { jlimit (0.0f, 1.0f, jmap (point.getX(), bounds.getX(), bounds.getRight(), 0.0f, 1.0f)), jmap (point.getY(), bounds.getBottom(), bounds.getY(), 0.0f, 1.0f) }; } static Point convertBezierPointToPointInBounds (const Point& bezierPoint, const Rectangle& bounds) { return bounds.getRelativePoint (bezierPoint.getX(), 1.0f - bezierPoint.getY()); } struct CubicBezier { CubicBezier() = default; CubicBezier (const Point& cp1In, const Point& cp2In) : cp1 (cp1In), cp2 (cp2In) {} Point cp0 { 0.0f, 0.0f }; Point cp1 {}; Point cp2 {}; Point cp3 { 1.0f, 1.0f }; bool operator== (const CubicBezier& other) { const auto tie = [](const CubicBezier& x) { return std::tie (x.cp0, x.cp1, x.cp2, x.cp3); }; return tie (*this) == tie (other); } }; class CubicBezierSettingsComponent final : public Component { public: CubicBezierSettingsComponent() { textEditor.setFont (FontOptions (18.0f)); textEditor.setColour (TextEditor::ColourIds::backgroundColourId, {}); textEditor.setColour (TextEditor::ColourIds::highlightColourId, {}); textEditor.setColour (TextEditor::ColourIds::outlineColourId, {}); textEditor.setColour (TextEditor::ColourIds::focusedOutlineColourId, {}); textEditor.setColour (TextEditor::ColourIds::shadowColourId, {}); textEditor.setJustification (Justification::centred); updateText(); textEditor.onTextChange = [&] { const auto strippedText = textEditor.getText().retainCharacters ("0123456789.-,"); const auto stringValues = StringArray::fromTokens (strippedText, ",", ""); if (stringValues.size() == 4) { setCubicBezierCurve ({{ jlimit (0.0f, 1.0f, stringValues[0].getFloatValue()), stringValues[1].getFloatValue() }, { jlimit (0.0f, 1.0f, stringValues[2].getFloatValue()), stringValues[3].getFloatValue() }}); } updateText(); }; addAndMakeVisible (textEditor); } void setCubicBezierCurve (const CubicBezier& newBezierCurve) { if (std::exchange (bezierCurve, newBezierCurve) == newBezierCurve) return; updateText(); NullCheckedInvocation::invoke (onValueChange); } CubicBezier getCubicBezierCurve() const { return bezierCurve; } void resized() final { textEditor.setBounds (getLocalBounds()); } std::function onValueChange; private: void updateText() { ScopedValueSetter> pauseOnTextChangeCallbacks (textEditor.onTextChange, nullptr); ScopeGuard restoreCaretPosition {[p = textEditor.getCaretPosition(), this] { textEditor.setCaretPosition (p); }}; textEditor.clear(); textEditor.setColour (TextEditor::ColourIds::textColourId, Colours::white); textEditor.insertTextAtCaret ("cubicBezier ("); textEditor.setColour (TextEditor::ColourIds::textColourId, AnimationEasingDemoConstants::cp1AccentColour); textEditor.insertTextAtCaret (String (bezierCurve.cp1.getX(), 2)); textEditor.setColour (TextEditor::ColourIds::textColourId, Colours::white); textEditor.insertTextAtCaret (", "); textEditor.setColour (TextEditor::ColourIds::textColourId, AnimationEasingDemoConstants::cp1AccentColour); textEditor.insertTextAtCaret (String (bezierCurve.cp1.getY(), 2)); textEditor.setColour (TextEditor::ColourIds::textColourId, Colours::white); textEditor.insertTextAtCaret (", "); textEditor.setColour (TextEditor::ColourIds::textColourId, AnimationEasingDemoConstants::cp2AccentColour); textEditor.insertTextAtCaret (String (bezierCurve.cp2.getX(), 2)); textEditor.setColour (TextEditor::ColourIds::textColourId, Colours::white); textEditor.insertTextAtCaret (", "); textEditor.setColour (TextEditor::ColourIds::textColourId, AnimationEasingDemoConstants::cp2AccentColour); textEditor.insertTextAtCaret (String (bezierCurve.cp2.getY(), 2)); textEditor.setColour (TextEditor::ColourIds::textColourId, Colours::white); textEditor.insertTextAtCaret (")"); } CubicBezier bezierCurve; TextEditor textEditor; }; class CubicBezierGraphComponent final : public Component { public: void setCubicBezierCurve (const CubicBezier& newBezierCurve) { if (std::exchange (bezierCurve, newBezierCurve) == newBezierCurve) return; NullCheckedInvocation::invoke (onValueChange); repaint(); } CubicBezier getCubicBezierCurve() const { return bezierCurve; } void paint (Graphics& g) final { const auto bounds = getGraphArea(); const auto lineThickness = 6.0f; const auto cp0 = getControlPointOnGraph (bezierCurve.cp0); const auto cp1 = getControlPointOnGraph (bezierCurve.cp1); const auto cp2 = getControlPointOnGraph (bezierCurve.cp2); const auto cp3 = getControlPointOnGraph (bezierCurve.cp3); const auto outlineColour = getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::outline); const auto highlightColour = getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::highlightedFill); const auto foregroundColour = getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::defaultText); // graph background drawColouredLines (g, bounds, 15, { Colour{}, highlightColour.withAlpha (0.2f) }); // graph outline g.setColour (outlineColour); g.drawRect (bounds); // semi-transparent linear line g.setColour (foregroundColour.withAlpha (0.15f)); g.drawLine ({ cp0, cp3 }, lineThickness); // cubic-bezier curve Path curve; curve.startNewSubPath (cp0); curve.cubicTo (cp1, cp2, cp3); g.setColour (foregroundColour); g.strokePath (curve, PathStrokeType (lineThickness)); // lines between control points g.setColour (foregroundColour); g.drawLine ({ cp0, cp1 }, 2.0f); g.drawLine ({ cp2, cp3 }, 2.0f); // control points drawControlPoint (g, cp0, highlightColour.brighter()); drawControlPoint (g, cp3, highlightColour.brighter()); drawControlPoint (g, cp1, AnimationEasingDemoConstants::cp1AccentColour); drawControlPoint (g, cp2, AnimationEasingDemoConstants::cp2AccentColour); } void mouseDown (const MouseEvent& event) final { const auto pos = event.position; const auto distanceToCp1 = pos.getDistanceFrom (getControlPointOnGraph (bezierCurve.cp1)); const auto distanceToCp2 = pos.getDistanceFrom (getControlPointOnGraph (bezierCurve.cp2)); selectedControlPoint = distanceToCp1 <= distanceToCp2 ? &bezierCurve.cp1 : &bezierCurve.cp2; updateSelectedControlPoint (pos); } void mouseDrag (const MouseEvent& event) final { updateSelectedControlPoint (event.position); } std::function onValueChange; private: Point getControlPointOnGraph (const Point& relativeControlPoint) { return convertBezierPointToPointInBounds (relativeControlPoint, getGraphArea()); } void updateSelectedControlPoint (const Point& newPoint) { jassert (selectedControlPoint != nullptr); const auto newControlPoint = convertPointInBoundsToBezierPoint (newPoint, getGraphArea()); if (*selectedControlPoint == newControlPoint) return; *selectedControlPoint = newControlPoint; NullCheckedInvocation::invoke (onValueChange); repaint(); } Rectangle getGraphArea() const { const auto bounds = getLocalBounds().toFloat(); const auto size = std::min (bounds.getWidth(), bounds.getHeight()); return bounds.withSizeKeepingCentre (size, size).reduced (AnimationEasingDemoConstants::largeGapSize); } void drawControlPoint (Graphics& g, const Point& point, Colour colour) { const auto size = jlimit (10.0f, 35.0f, (float) std::min (getWidth(), getHeight()) / 12.0f); Rectangle bounds; bounds.setSize (size, size); bounds.setCentre (point); g.setColour (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::outline)); g.drawEllipse (bounds, 2.0f); g.setColour (colour); g.fillEllipse (bounds); } void drawColouredLines (Graphics& g, Rectangle bounds, int numLines, std::vector colours) { const auto lineHeight = bounds.getHeight() / (float) numLines; for (size_t line = 0; line < (size_t) numLines; ++line) { g.setColour (colours[line % colours.size()]); g.fillRect (bounds.removeFromTop (lineHeight)); } } CubicBezier bezierCurve; Point* selectedControlPoint{}; }; class AnimationView : public Component { public: AnimationView (const AnimationSettings& animationSettingsIn, std::function easingFunctionFactoryIn) : animationSettings (animationSettingsIn), easingFunctionFactory (std::move (easingFunctionFactoryIn)) { jassert (easingFunctionFactory != nullptr); componentToAnimate.setInterceptsMouseClicks (false, false); addAndMakeVisible (componentToAnimate); } void animate() { showSettingsPage (false); const auto valueChangedCallback = [&](float v) { animateFrame (animationSettings.shouldAnimatePosition.getValue() ? v : 1.0f, animationSettings.shouldAnimateSize.getValue() ? v : 1.0f, animationSettings.shouldAnimateAlpha.getValue() ? v : 1.0f); }; const auto animateIn = ValueAnimatorBuilder{}.withEasing (easingFunctionFactory()) .withDurationMs (animationSettings.durationMs.getValue()) .withValueChangedCallback (valueChangedCallback); const auto animateOut = animateIn.withValueChangedCallback ([=](auto v){ valueChangedCallback (1.0f - v); }); animator = std::make_unique (AnimatorSetBuilder (animateOut.build()) .followedBy (400.0) .followedBy (animateIn.build()) .build()); updater.addAnimator (*animator); animator->start(); } void mouseDown (const MouseEvent&) final { animate(); } void resized() override { auto bounds = getLocalBounds(); componentToAnimate.setBounds (bounds); settingsPageBackground.setBounds (bounds); editViewButton.setBounds (bounds.removeFromTop (AnimationEasingDemoConstants::defaultComponentHeight) .removeFromRight (AnimationEasingDemoConstants::defaultComponentHeight * 2)); if (customSettingsPage != nullptr) customSettingsPage->setBounds (bounds); } protected: void setCustomSettingsPage (Component& settingsPage) { customSettingsPage = &settingsPage; editViewButton.onClick = [&] { toggleSettingsPage(); }; addChildComponent (settingsPageBackground); addChildComponent (customSettingsPage); editViewButton.setButtonText ("Edit"); addAndMakeVisible (editViewButton); } private: void showSettingsPage (bool shouldShowSettingsPage) { if (customSettingsPage == nullptr) return; editViewButton.setButtonText (shouldShowSettingsPage ? "View" : "Edit"); settingsPageBackground.setVisible (shouldShowSettingsPage); customSettingsPage->setVisible (shouldShowSettingsPage); } void toggleSettingsPage() { showSettingsPage (editViewButton.getButtonText() == "Edit"); } struct JUCELogoComponent final : Component { void paint (Graphics& g) final { const auto bounds = getLocalBounds(); g.setColour (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::highlightedFill)); g.fillRect (bounds); g.setColour (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::defaultText)); const auto logo = getJUCELogoPath(); g.addTransform (logo.getTransformToScaleToFit (bounds.toFloat().reduced (AnimationEasingDemoConstants::mediumGapSize), true)); g.fillPath (logo); } }; void animateFrame (float position, float size, float alpha) { // Transforms don't work when scaling to 0 so this work around hides // the component when the size is 0. As the alpha is also animated it's // guaranteed to be correctly set when the size is not 0 if (approximatelyEqual (size, 0.0f)) { componentToAnimate.setAlpha (0.0f); return; } const auto bounds = getLocalBounds().toFloat(); const auto centre = bounds.getCentre(); const auto xLimits = makeAnimationLimits (-bounds.getWidth(), 0.0f); componentToAnimate.setTransform (AffineTransform{}.scaled (size, size, centre.getX(), centre.getY()) .translated (xLimits.lerp (position), 0.0f)); componentToAnimate.setAlpha (alpha); } struct BackgroundFill : Component { void paint (Graphics& g) { g.fillAll (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::windowBackground)); } }; AnimationSettings animationSettings; std::function easingFunctionFactory {}; BackgroundFill settingsPageBackground; Component* customSettingsPage {}; TextButton editViewButton; std::unique_ptr animator; VBlankAnimatorUpdater updater { this }; JUCELogoComponent componentToAnimate; }; class StandardEasingAnimationView final : public AnimationView { public: StandardEasingAnimationView (const AnimationSettings& settings, ValueAnimatorBuilder::EasingFn easingFunction) : AnimationView (settings, [=] { return easingFunction; }) {} }; class CubicBezierEasingAnimationView final : public AnimationView { public: CubicBezierEasingAnimationView (const AnimationSettings& settings) : AnimationView (settings, [&] { return Easings::createCubicBezier (bezierCurve.cp1, bezierCurve.cp2); }) { settingsPage.graph.onValueChange = [&] { setCubicBezierCurve (settingsPage.graph.getCubicBezierCurve()); }; settingsPage.settings.onValueChange = [&] { setCubicBezierCurve (settingsPage.settings.getCubicBezierCurve()); }; settingsPage.graph.setCubicBezierCurve ({ { 0.2f, 0.0f }, { 0.0f, 1.0f } }); setCustomSettingsPage (settingsPage); } private: struct SettingsPage : Component { SettingsPage() { addAndMakeVisible (graph); addAndMakeVisible (settings); } void resized() final { auto bounds = getLocalBounds(); settings.setBounds (bounds.removeFromBottom (AnimationEasingDemoConstants::defaultComponentHeight)); graph.setBounds (bounds); } CubicBezierGraphComponent graph; CubicBezierSettingsComponent settings; }; void setCubicBezierCurve (const CubicBezier& newBezierCurve) { bezierCurve = newBezierCurve; settingsPage.graph.setCubicBezierCurve (newBezierCurve); settingsPage.settings.setCubicBezierCurve (newBezierCurve); } CubicBezier bezierCurve; SettingsPage settingsPage; }; struct SliderAndLabel final : public Component { SliderAndLabel() { addAndMakeVisible (slider); addAndMakeVisible (label); } void resized() final { auto bounds = getLocalBounds(); label.setBounds (bounds.removeFromTop (bounds.getHeight() / 2) .removeFromBottom (AnimationEasingDemoConstants::defaultComponentHeight)); slider.setBounds (bounds.removeFromTop (AnimationEasingDemoConstants::defaultComponentHeight)); } Slider slider; Label label; }; class SpringEasingAnimationView final : public AnimationView { public: SpringEasingAnimationView (const AnimationSettings& settings) : AnimationView (settings, [&] { return Easings::createSpring (SpringEasingOptions{}.withFrequency (getFrequency()) .withAttenuation (getAttenuation()) .withExtraAttenuationRange (getExtraAttenuationRange())); }) { setCustomSettingsPage (settingsPage); } private: float getFrequency() { return (float) settingsPage.frequency.slider.getValue(); } float getAttenuation() { return (float) settingsPage.attenuation.slider.getValue(); } float getExtraAttenuationRange() { return (float) settingsPage.extraAttenuationRange.slider.getValue(); } struct SettingsPage : Component { SettingsPage() { frequency.label.setText ("Frequency", NotificationType::dontSendNotification); frequency.slider.setRange (1.0, 10.0, 1.0); frequency.slider.setValue (3.0); attenuation.label.setText ("Attenuation", NotificationType::dontSendNotification); attenuation.slider.setRange (1.0, 10.0, 1.0); attenuation.slider.setValue (3.0); extraAttenuationRange.label.setText ("Extra attenuation range", NotificationType::dontSendNotification); extraAttenuationRange.slider.setRange (0.05, 0.98, 0.01); extraAttenuationRange.slider.setValue (0.25); addAndMakeVisible (frequency); addAndMakeVisible (attenuation); addAndMakeVisible (extraAttenuationRange); } void resized() final { AnimationEasingDemoHelpers::layoutComponentsVertically (getLocalBounds(), { &frequency, &attenuation, &extraAttenuationRange }); } SliderAndLabel frequency; SliderAndLabel attenuation; SliderAndLabel extraAttenuationRange; }; SettingsPage settingsPage; }; class BounceOutEasingAnimationView final : public AnimationView { public: BounceOutEasingAnimationView (const AnimationSettings& settings) : AnimationView (settings, [&] { return Easings::createBounce (roundToInt (numberOfBounces.slider.getValue())); }) { numberOfBounces.label.setText ("Number of bounces", NotificationType::dontSendNotification); numberOfBounces.slider.setRange (1.0, 10.0, 1.0); numberOfBounces.slider.setValue (3.0); setCustomSettingsPage (numberOfBounces); } private: SliderAndLabel numberOfBounces; }; class AnimationSelectorAndView final : public Component { struct AnimationViewAndName { String name; std::unique_ptr component; }; public: AnimationSelectorAndView (const AnimationSettings& settings) { views.push_back ({ "linear", std::make_unique (settings, Easings::createLinear()) }); views.push_back ({ "ease (default)", std::make_unique (settings, Easings::createEase()) }); views.push_back ({ "easeIn", std::make_unique (settings, Easings::createEaseIn()) }); views.push_back ({ "easeOut", std::make_unique (settings, Easings::createEaseOut()) }); views.push_back ({ "easeInOut", std::make_unique (settings, Easings::createEaseInOut()) }); views.push_back ({ "easeOutBack", std::make_unique (settings, Easings::createEaseOutBack()) }); views.push_back ({ "easeInOutCubic", std::make_unique (settings, Easings::createEaseInOutCubic()) }); views.push_back ({ "cubicBezier", std::make_unique (settings) }); views.push_back ({ "spring", std::make_unique (settings) }); views.push_back ({ "bounce", std::make_unique (settings) }); for (auto [itemId, view] : enumerate (views, int { 1 })) easingSelector.addItem (view.name, itemId); // select "ease" as the default easingSelector.setSelectedItemIndex (1); easingSelector.onChange = [&] { for (auto& view : views) view.component->setVisible (false); views[(size_t) easingSelector.getSelectedItemIndex()].component->setVisible (true); }; for (auto& view : views) addChildComponent (view.component.get()); addAndMakeVisible (easingSelector); } void animate() { for (auto& view : views) view.component->animate(); } void resized() final { auto bounds = getLocalBounds(); easingSelector.setBounds (bounds.removeFromTop (AnimationEasingDemoConstants::defaultComponentHeight)); bounds.removeFromTop (AnimationEasingDemoConstants::smallGapSize); for (auto& view : views) view.component->setBounds (bounds); } private: ComboBox easingSelector; std::vector views; }; class AnimationEasingDemo final : public Component { public: //============================================================================== AnimationEasingDemo() { animationSettings.durationMs = 1000.0; animationSettings.shouldAnimatePosition = true; animationSettings.shouldAnimateSize = false; animationSettings.shouldAnimateAlpha = false; animationSettingsComponent.onAnimate = [&] { animationView1.animate(); animationView2.animate(); }; addAndMakeVisible (animationSettingsComponent); addAndMakeVisible (animationView1); addAndMakeVisible (animationView2); setSize (600, 400); } //============================================================================== void paint (Graphics& g) final { g.fillAll (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::windowBackground)); } void resized() override { auto bounds = getLocalBounds().reduced (AnimationEasingDemoConstants::largeGapSize); animationSettingsComponent.setBounds (bounds.removeFromTop (AnimationEasingDemoConstants::defaultComponentHeight * 2)); bounds.removeFromTop (AnimationEasingDemoConstants::smallGapSize); AnimationEasingDemoHelpers::layoutComponentsVerticallyOrHorizontally (bounds, { &animationView1, &animationView2 }); } private: //============================================================================== AnimationSettings animationSettings; AnimationSettingsComponent animationSettingsComponent { animationSettings }; AnimationSelectorAndView animationView1 { animationSettings }; AnimationSelectorAndView animationView2 { animationSettings }; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AnimationEasingDemo) };