diff --git a/README.md b/README.md index 6c3808f..0cfeb80 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,6 @@ It also has a collection of helper methods to load Win2D images, dispatch code t - [Quick start](#quick-start) - [Animations](#animations) - [`UI.Composition` effects](#uicomposition-effects) - - [Reveal highlight effect](#reveal-highlight-effect) - [Misc](#misc) - [Requirements](#requirements) @@ -37,108 +36,60 @@ More details available [here](https://www.nuget.org/packages/UICompositionAnimat ## Animations -The package exposes synchronous and asynchronous APIs (from the `CompositionExtensions` and `XAMLTransformExtensions` classes) to quickly setup and start animations. There are different kinds of available animation types to choose from: -- **Fade** (Opacity) -- **Slide** (Translation) -- **Scale** -- **Color** (for a `SolidColorBrush` object) +The available animation APIs use the fluent pattern and support combining multiple animations togetger. The main entry point is the ` UIElementExtensions.Animation` method, that returns an `IAnimationBuilder` object targeting the input `UIElement`. This object exposes all the available animation APIs. -The library also has APIs to automatically combine different kinds of animations, like Fade + Slide or Fade + Scale, and various helper methods to change UI-related parameters of a target object. Here are some animation exaples: - -#### Synchronous fade animation +You can use it like this: ```C# -MyControl.StartCompositionFadeAnimation( - null, // Using null will make the fade animation start from the current value - 1, // End opacity - 200, // Duration in ms - null, // Optional delay in ms - EasingFunctionNames.CircleEaseOut, // Easing function, - () => Foo()); // Optional callback +MyControl.Animation() + .Opacity(0) + .Translation(Axis.X, 60) + .Duration(250) + .Start(); ``` -#### Asynchronous fade and scale animation +It is also possible to set an initial delay, and to wait for the animation to be completed. Also, should you need to do so in a particular situation, it is also possible to choose between the `Windows.UI.Composition` and `Windows.UI.Xaml.Media.Animation` APIs to run the animations. To toggle between the two, just pass a `FrameworkLayer` value to the `Animation` method. Furthermore, each animation API has two overloads: one that just takes the target value, and one that also sets the initial value for the animation. It is also possible to specify an easing function for each individual animation. Here is another, more complex example: ```C# -await MyControl.StartCompositionFadeScaleAnimationAsync( - null, // Initial opacity (use current value) - 1, // Target opacity - 1.1f, // Initial scale - 1, // End scale - 250, // Animation duration - null, // Optional scale animation duration (if null, the base animation duration will be used) - null, // Optional delay - EasingFunctionNames.Linear); // Easing function +await MyControl.Animation(FrameworkLayer.Xaml) + .Opacity(0, 1, Easing.CircleEaseOut) + .Scale(1.2, 1, Easing.QuadratincEaseInOut) + .Duration(500) + .Delay(250) + .StartAsync(); ``` ## `UI.Composition` effects -The library provides several ways to use `UI.Composition` effects. There are ready to use XAML brushes, a `CompositionBrushBuilder` class to create complex composition effect pipelines, an `AttachedCompositionEffectsFactory` class that provides an alternative way to attach commonly used effects to visual elements, and much more. +The library provides several ways to use `UI.Composition` effects: there are both ready to use XAML brushes (like a customizable acrylic brush), a `CompositionBrushBuilder` class to create complex composition effect pipelines, and more. -#### Declare a shared acrylic brush in XAML +#### Declare an acrylic brush in XAML ```XAML - +xmlns:brushes="using:UICompositionAnimations.Brushes"> - - - ... - + + ``` **Note**: the `NoiseTextureUri` parameter must be set to a .png image with a noise texture. It is up to the developer to create his own noise texture and to import it into the app. An easy plugin to create one is [NoiseChoice](https://forums.getpaint.net/topic/22500-red-ochre-plug-in-pack-v9-updated-30th-july-2014/) for [Paint.NET](https://www.getpaint.net/). -#### Create and assign an acrylic brush in C# - -```C# -control.Background = CompositionBrushBuilder.FromHostBackdropAcrylic(Colors.DarkOrange, 0.6f, new Uri("ms-appx:///Assets/noise.png")).AsBrush(); -``` - -#### Build an acrylic effect pipeline from scratch: - -```C# -Brush brush = CompositionBrushBuilder.FromHostBackdropBrush() - .Effect(source => new LuminanceToAlphaEffect { Source = source }) - .Opacity(0.4f) - .Blend(CompositionBrushBuilder.FromHostBackdropBrush(), BlendEffectMode.Multiply) - .Tint(Color.FromArgb(0xFF, 0x14, 0x14, 0x14), 0.8f) - .Blend(CompositionBrushBuilder.FromTiles(new Uri("ms-appx:///Assets/noise.png")), BlendEffectMode.Overlay, EffectPlacement.Background) - .AsBrush(); -``` - -The `CompositionBrushBuilder` class can also be used to quickly implement custom XAML brushes with an arbitrary effects pipeline. To do so, just inherit from `XamlCompositionEffectBrushBase` and setup your own effects pipeline in the `OnBrushRequested` method. - -#### Get a custom effect that can be animated: - -```C# -// Build the effects pipeline -XamlCompositionBrush acrylic = CompositionBrushBuilder.FromHostBackdropAcrylic(Colors.Orange, 0.6f, new Uri("ms-appx:///Assets/noise.png")) - .Saturation(1, out EffectAnimation animation) - .AsBrush(); -acrylic.Bind(animation, out XamlEffectAnimation saturation); // Bind the effect animation to the target brush - -// Later on, when needed -saturation(0.2f, 250); // Animate the opacity to 0.2 in 250ms -``` - #### Create custom effects in XAML: -Using the APIs in `UICompositionAnimations.Behaviours.Xaml` it is also possible to build complex Composition/Win2D pipelines directly from XAML, in a declarative way. This is how to define a custom host backdrop acrylic brush: +Using the APIs in `UICompositionAnimations.Brushes.Effects` it is also possible to build complex Composition/Win2D pipelines directly from XAML, in a declarative way. This is how to define a custom host backdrop acrylic brush: ```xml -xmlns:xaml="using:UICompositionAnimations.Behaviours.Xaml" -xmlns:effects="using:UICompositionAnimations.Behaviours.Xaml.Effects" +xmlns:brushes="using:UICompositionAnimations.Brushes" +xmlns:effects="using:UICompositionAnimations.Brushes.Effects" - - + + @@ -153,62 +104,51 @@ xmlns:effects="using:UICompositionAnimations.Behaviours.Xaml.Effects" - - + + ``` -## Reveal highlight effect - -Part of the Fluent Design System introduced with Windows 10 Fall Creators Update, this effect can actually already be used with Windows 10 Creators Update (build 15063.x). The library exposes APIs to easily use the effect in an application. - -#### Setup the lights +#### Create and assign an acrylic brush in C# ```C# -// In App.xaml.cs, before loading the application UI -LightsSourceHelper.Initialize( - () => new PointerPositionSpotLight { Shade = 0x60 }, // Example light - () => new PointerPositionSpotLight - { - IdAppendage = "[Wide]", // This ID is used to specify the target brushes for the specific light - Z = 30, - Shade = 0x10 - }); // It is possible to add an arbitrary number of lights here -Window.Current.Content = new Frame(); // Sample UI initialization -LightsSourceHelper.SetIsLightsContainer(Window.Current.Content, true); // Assign the lights to the app main UI +control.Background = PipelineBuilder.FromHostBackdropAcrylic(Colors.DarkOrange, 0.6f, new Uri("ms-appx:///Assets/noise.png")) + .AsBrush(); ``` -#### Setup the target brushes for the lights - -````XAML - - - - ... - -```` +#### Build an acrylic effect pipeline from scratch: ```C# -// Since the second light has a special ID, it is necessary to manually set its target brush -LightingBrush brush = Application.Current.Resources["BorderWideLightBrush"] as LightingBrush; -XamlLight.AddTargetBrush($"{PointerPositionSpotLight.GetIdStatic()}[Wide]", brush); +Brush brush = PipelineBuilder.FromHostBackdropBrush() + .Effect(source => new LuminanceToAlphaEffect { Source = source }) + .Opacity(0.4f) + .Blend(CompositionBrushBuilder.FromHostBackdropBrush(), BlendEffectMode.Multiply) + .Tint(Color.FromArgb(0xFF, 0x14, 0x14, 0x14), 0.8f) + .Blend(CompositionBrushBuilder.FromTiles(new Uri("ms-appx:///Assets/noise.png")), BlendEffectMode.Overlay, Placement.Background) + .AsBrush(); ``` -At this point we have a series of lights, each targeting an arbitrary number of brushes. The library takes care of managing the pointer events for the lights as well. It is now possible to use those brushes in any `UIElement` to see the reveal highlight effect in action. +The `PipelineBuilder` class can also be used to quickly implement custom XAML brushes with an arbitrary effects pipeline. To do so, just inherit from `XamlCompositionEffectBrushBase` and setup your own effects pipeline in the `OnBrushRequested` method. -**Note**: the lights only work on `UIElement`s in their visual tree. This means that in order for the `LightingBrush` objects to work correctly in popups or flyouts, the lights must be added to their visual tree too. To do this, just call the `LightsSourceHelper.SetIsLightsContainer` method on the root element of the new object being displayed (for example, the root `Grid` inside a new `Popup`). +#### Get a custom effect that can be animated: + +```C# +// Build the effects pipeline +XamlCompositionBrush acrylic = PipelineBuilder.FromHostBackdropAcrylic(Colors.Orange, 0.6f, new Uri("ms-appx:///Assets/noise.png")) + .Saturation(1, out EffectAnimation animation) + .AsBrush(); +acrylic.Bind(animation, out XamlEffectAnimation saturation); // Bind the effect animation to the target brush + +// Later on, when needed +saturation(0.2f, 250); // Animate the opacity to 0.2 in 250ms +``` # Misc Many utility methods are also available, here are some useful classes: -- `XAMLTransformToolkit`: exposes methods to manually create, start and wait for `DoubleAnimation`(s) and `Storyboard`(s), as well as for quickly assigning a certain `RenderTransform` object to a `UIElement`. - `DispatcherHelper`: exposes methods to easily execute code on the UI thread or on a target `CoreDispatcher` object - `Win2DImageHelper`: exposes APIs to quickly load a Win2D image on a `CompositionSurfaceBrush` object - `PointerHelper`: exposes APIs to quickly setup pointer event handlers for `UIElement`s +- `AsyncMutex`: an async mutex included into `System.Threading.Tasks` that can be used to asynchronously acquire a lock with a `using` block. # Requirements At least Windows 10 April Update (17134.x) diff --git a/UICompositionAnimations/Animations/Abstract/AnimationBuilderBase.cs b/UICompositionAnimations/Animations/Abstract/AnimationBuilderBase.cs new file mode 100644 index 0000000..2c49970 --- /dev/null +++ b/UICompositionAnimations/Animations/Abstract/AnimationBuilderBase.cs @@ -0,0 +1,223 @@ +using System; +using System.Collections.Generic; +using System.Numerics; +using System.Threading.Tasks; +using Windows.UI.Xaml; +using JetBrains.Annotations; +using UICompositionAnimations.Animations.Interfaces; +using UICompositionAnimations.Enums; + +namespace UICompositionAnimations.Animations.Abstract +{ + /// + /// An used as base by all the animation builder classes + /// + internal abstract class AnimationBuilderBase : IAnimationBuilder + { + /// + /// A constructor that initializes the target + /// + /// The target to animate + protected AnimationBuilderBase([NotNull] UIElement target) => TargetElement = target; + + /// + /// The target to animate + /// + [NotNull] + protected UIElement TargetElement { get; } + + /// + /// Gets the list of instances used to create the animations to run + /// + [NotNull, ItemNotNull] + protected IList AnimationFactories { get; } = new List(); + + /// + /// Gets the that defines the duration of the animation + /// + protected TimeSpan DurationInterval { get; private set; } + + /// + /// The that defines the initial delay of the animation + /// + private TimeSpan? _Delay; + + #region Animations + + /// + public IAnimationBuilder Opacity(double to, Easing ease = Easing.Linear) => OnOpacity(null, to, ease); + + /// + public IAnimationBuilder Opacity(double from, double to, Easing ease = Easing.Linear) => OnOpacity(from, to, ease); + + /// + /// Schedules an opacity animation on a single axis + /// + /// The optional starting value + /// The target value + /// The easing function to use for the translation animation + [MustUseReturnValue, NotNull] + protected abstract IAnimationBuilder OnOpacity(double? from, double to, Easing ease); + + /// + public IAnimationBuilder Translation(Axis axis, double to, Easing ease = Easing.Linear) => OnTranslation(axis, null, to, ease); + + /// + public IAnimationBuilder Translation(Axis axis, double from, double to, Easing ease = Easing.Linear) => OnTranslation(axis, from, to, ease); + + /// + /// Schedules a translation animation on a single axis + /// + /// The target axis to animate + /// The optional starting value + /// The target value + /// The easing function to use for the translation animation + [MustUseReturnValue, NotNull] + protected abstract IAnimationBuilder OnTranslation(Axis axis, double? from, double to, Easing ease); + + /// + public IAnimationBuilder Translation(Vector2 to, Easing ease = Easing.Linear) => OnTranslation(null, to, ease); + + /// + public IAnimationBuilder Translation(Vector2 from, Vector2 to, Easing ease = Easing.Linear) => OnTranslation(from, to, ease); + + /// + /// Schedules a 2D translation animation + /// + /// The optional starting position + /// The target position + /// The easing function to use for the translation animation + [MustUseReturnValue, NotNull] + protected abstract IAnimationBuilder OnTranslation(Vector2? from, Vector2 to, Easing ease = Easing.Linear); + + /// + public IAnimationBuilder Offset(Axis axis, double to, Easing ease = Easing.Linear) => OnOffset(axis, null, to, ease); + + /// + public IAnimationBuilder Offset(Axis axis, double from, double to, Easing ease = Easing.Linear) => OnOffset(axis, from, to, ease); + + /// + /// Schedules an offset animation on a single axis + /// + /// The target axis to animate + /// The optional starting value + /// The target value + /// The easing function to use for the offset animation + [MustUseReturnValue, NotNull] + protected abstract IAnimationBuilder OnOffset(Axis axis, double? from, double to, Easing ease); + + /// + public IAnimationBuilder Offset(Vector2 to, Easing ease = Easing.Linear) => OnOffset(null, to, ease); + + /// + public IAnimationBuilder Offset(Vector2 from, Vector2 to, Easing ease = Easing.Linear) => OnOffset(from, to, ease); + + /// + /// Schedules a 2D offset animation + /// + /// The optional starting position + /// The target position + /// The easing function to use for the offset animation + [MustUseReturnValue, NotNull] + protected abstract IAnimationBuilder OnOffset(Vector2? from, Vector2 to, Easing ease = Easing.Linear); + + /// + public IAnimationBuilder Scale(double to, Easing ease = Easing.Linear) => OnScale(null, to, ease); + + /// + public IAnimationBuilder Scale(double from, double to, Easing ease = Easing.Linear) => OnScale(from, to, ease); + + /// + /// Schedules a scale animation + /// + /// The optional starting value + /// The target value + /// The easing function to use for the scale animation + [MustUseReturnValue, NotNull] + protected abstract IAnimationBuilder OnScale(double? from, double to, Easing ease); + + /// + public IAnimationBuilder Rotate(double to, Easing ease = Easing.Linear) => OnRotate(null, to, ease); + + /// + public IAnimationBuilder Rotate(double from, double to, Easing ease = Easing.Linear) => OnRotate(from, to, ease); + + /// + /// Schedules a rotation animation + /// + /// The optional starting value + /// The target value + /// The easing function to use for the rotation animation + [MustUseReturnValue, NotNull] + protected abstract IAnimationBuilder OnRotate(double? from, double to, Easing ease); + + /// + public IAnimationBuilder Clip(Side side, double to, Easing ease = Easing.Linear) => OnClip(side, null, to, ease); + + /// + public IAnimationBuilder Clip(Side side, double from, double to, Easing ease = Easing.Linear) => OnClip(side, from, to, ease); + + /// + /// Schedules a clip animation + /// + /// The clip side to animate + /// The optional starting value + /// The target value + /// The easing function to use for the clip animation + [MustUseReturnValue, NotNull] + protected abstract IAnimationBuilder OnClip(Side side, double? from, double to, Easing ease); + + #endregion + + /// + public IAnimationBuilder Duration(int ms) => Duration(TimeSpan.FromMilliseconds(ms)); + + /// + public IAnimationBuilder Duration(TimeSpan duration) + { + DurationInterval = duration; + return this; + } + + /// + public IAnimationBuilder Delay(int ms) => Delay(TimeSpan.FromMilliseconds(ms)); + + /// + public IAnimationBuilder Delay(TimeSpan interval) + { + _Delay = interval; + return this; + } + + /// + public async void Start() + { + if (_Delay != null) await Task.Delay(_Delay.Value); + OnStart(); + } + + /// + /// Starts the animation represented by the current instance + /// + protected abstract void OnStart(); + + /// + public async void Start(Action callback) + { + await StartAsync(); + callback(); + } + + /// + public async Task StartAsync() + { + if (_Delay != null) await Task.Delay(_Delay.Value); + await OnStartAsync(); + } + + /// + /// Starts the animation represented by the current instance, and returns a to track it + /// + protected abstract Task OnStartAsync(); + } +} diff --git a/UICompositionAnimations/Animations/CompositionAnimationBuilder.cs b/UICompositionAnimations/Animations/CompositionAnimationBuilder.cs new file mode 100644 index 0000000..7abc3a1 --- /dev/null +++ b/UICompositionAnimations/Animations/CompositionAnimationBuilder.cs @@ -0,0 +1,211 @@ +using System; +using System.Numerics; +using System.Threading.Tasks; +using Windows.UI.Composition; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Hosting; +using JetBrains.Annotations; +using UICompositionAnimations.Animations.Abstract; +using UICompositionAnimations.Animations.Interfaces; +using UICompositionAnimations.Enums; + +namespace UICompositionAnimations.Animations +{ + /// + /// A that implements the using composition APIs + /// + internal sealed class CompositionAnimationBuilder : AnimationBuilderBase> + { + /// + /// The target to animate + /// + [NotNull] + private readonly Visual TargetVisual; + + public CompositionAnimationBuilder([NotNull] UIElement target) : base(target) + { + TargetVisual = target.GetVisual(); + ElementCompositionPreview.SetIsTranslationEnabled(TargetElement, true); + } + + /// + protected override IAnimationBuilder OnOpacity(double? from, double to, Easing ease) + { + AnimationFactories.Add(duration => + { + TargetVisual.StopAnimation(nameof(Visual.Opacity)); + CompositionEasingFunction easingFunction = TargetVisual.GetEasingFunction(ease); + ScalarKeyFrameAnimation animation = TargetVisual.Compositor.CreateScalarKeyFrameAnimation((float?)from, (float)to, duration, null, easingFunction); + TargetVisual.StartAnimation(nameof(Visual.Opacity), animation); + }); + + return this; + } + + /// + protected override IAnimationBuilder OnTranslation(Axis axis, double? from, double to, Easing ease) + { + AnimationFactories.Add(duration => + { + // Stop the animation and get the easing function + string property = $"Translation.{axis}"; + TargetVisual.StopAnimation(property); + CompositionEasingFunction easingFunction = TargetVisual.GetEasingFunction(ease); + + // Create and return the animation + ScalarKeyFrameAnimation animation = TargetVisual.Compositor.CreateScalarKeyFrameAnimation((float?)from, (float)to, duration, null, easingFunction); + TargetVisual.StartAnimation(property, animation); + }); + + return this; + } + + /// + protected override IAnimationBuilder OnTranslation(Vector2? from, Vector2 to, Easing ease = Easing.Linear) + { + AnimationFactories.Add(duration => + { + // Stop the animation and get the easing function + TargetVisual.StopAnimation("Translation"); + CompositionEasingFunction easingFunction = TargetVisual.GetEasingFunction(ease); + + // Create and return the animation + Vector3? from3 = from == null ? default : new Vector3(from.Value, 0); + Vector3 to3 = new Vector3(to, 0); + Vector3KeyFrameAnimation animation = TargetVisual.Compositor.CreateVector3KeyFrameAnimation(from3, to3, duration, null, easingFunction); + TargetVisual.StartAnimation("Translation", animation); + }); + + return this; + } + + /// + protected override IAnimationBuilder OnOffset(Axis axis, double? from, double to, Easing ease) + { + AnimationFactories.Add(duration => + { + // Stop the animation and get the easing function + TargetVisual.StopAnimation(nameof(Visual.Offset)); + CompositionEasingFunction easingFunction = TargetVisual.GetEasingFunction(ease); + + // Create and return the animation + ScalarKeyFrameAnimation animation = TargetVisual.Compositor.CreateScalarKeyFrameAnimation((float?)from, (float)to, duration, null, easingFunction); + TargetVisual.StartAnimation(nameof(Visual.Offset), animation); + }); + + return this; + } + + /// + protected override IAnimationBuilder OnOffset(Vector2? from, Vector2 to, Easing ease = Easing.Linear) + { + AnimationFactories.Add(duration => + { + // Stop the animation and get the easing function + TargetVisual.StopAnimation(nameof(Visual.Offset)); + CompositionEasingFunction easingFunction = TargetVisual.GetEasingFunction(ease); + + // Create and return the animation + Vector3? from3 = from == null ? default : new Vector3(from.Value, 0); + Vector3 to3 = new Vector3(to, 0); + Vector3KeyFrameAnimation animation = TargetVisual.Compositor.CreateVector3KeyFrameAnimation(from3, to3, duration, null, easingFunction); + TargetVisual.StartAnimation(nameof(Visual.Offset), animation); + }); + + return this; + } + + /// + protected override IAnimationBuilder OnScale(double? from, double to, Easing ease) + { + // Center the visual center point + if (!(TargetElement is FrameworkElement element)) throw new InvalidOperationException("The scale animation needs a framework element"); + element.GetVisual().CenterPoint = new Vector3((float)(element.ActualWidth / 2), (float)(element.ActualHeight / 2), 0); + + // Add the scale animation + AnimationFactories.Add(duration => + { + // Stop the animation and get the easing function + TargetVisual.StopAnimation(nameof(Visual.Scale)); + CompositionEasingFunction easingFunction = TargetVisual.GetEasingFunction(ease); + + // Create and return the animation + Vector3? from3 = from == null ? default : new Vector3((float)from.Value, (float)from.Value, 0); + Vector3 to3 = new Vector3((float)to, (float)to, 0); + Vector3KeyFrameAnimation animation = TargetVisual.Compositor.CreateVector3KeyFrameAnimation(from3, to3, duration, null, easingFunction); + TargetVisual.StartAnimation(nameof(Visual.Scale), animation); + }); + + return this; + } + + /// + protected override IAnimationBuilder OnRotate(double? from, double to, Easing ease) + { + // Center the visual center point + if (!(TargetElement is FrameworkElement element)) throw new InvalidOperationException("The rotation animation needs a framework element"); + element.GetVisual().CenterPoint = new Vector3((float)(element.ActualWidth / 2), (float)(element.ActualHeight / 2), 0); + + // Add the rotation animation + AnimationFactories.Add(duration => + { + TargetVisual.StopAnimation(nameof(Visual.RotationAngleInDegrees)); + CompositionEasingFunction easingFunction = TargetVisual.GetEasingFunction(ease); + ScalarKeyFrameAnimation animation = TargetVisual.Compositor.CreateScalarKeyFrameAnimation((float?)from, (float)to, duration, null, easingFunction); + TargetVisual.StartAnimation(nameof(Visual.RotationAngleInDegrees), animation); + }); + + return this; + } + + /// + protected override IAnimationBuilder OnClip(Side side, double? from, double to, Easing ease) + { + AnimationFactories.Add(duration => + { + // Stop the animation and get the easing function + string property; + switch (side) + { + case Side.Top: property = nameof(InsetClip.TopInset); break; + case Side.Bottom: property = nameof(InsetClip.BottomInset); break; + case Side.Right: property = nameof(InsetClip.RightInset); break; + case Side.Left: property = nameof(InsetClip.LeftInset); break; + default: throw new ArgumentException("Invalid side", nameof(side)); + } + InsetClip clip = TargetVisual.Clip as InsetClip ?? (TargetVisual.Clip = TargetVisual.Compositor.CreateInsetClip()).To(); + clip.StopAnimation(property); + CompositionEasingFunction easingFunction = clip.GetEasingFunction(ease); + + // Create and return the animation + ScalarKeyFrameAnimation animation = clip.Compositor.CreateScalarKeyFrameAnimation((float?)from, (float)to, duration, null, easingFunction); + clip.StartAnimation(property, animation); + }); + + return this; + } + + /// + /// Creates and starts the pending animations + /// + private void StartAnimations() + { + foreach (var animation in AnimationFactories) + animation(DurationInterval); + } + + /// + protected override void OnStart() => StartAnimations(); + + /// + protected override Task OnStartAsync() + { + CompositionScopedBatch batch = TargetVisual.Compositor.CreateScopedBatch(CompositionBatchTypes.Animation); + TaskCompletionSource tcs = new TaskCompletionSource(); + batch.Completed += (s, e) => tcs.SetResult(); + StartAnimations(); + batch.End(); + return tcs.Task; + } + } +} diff --git a/UICompositionAnimations/Animations/Interfaces/IAnimationBuilder.cs b/UICompositionAnimations/Animations/Interfaces/IAnimationBuilder.cs new file mode 100644 index 0000000..7bf99d4 --- /dev/null +++ b/UICompositionAnimations/Animations/Interfaces/IAnimationBuilder.cs @@ -0,0 +1,201 @@ +using System; +using System.Numerics; +using System.Threading.Tasks; +using JetBrains.Annotations; +using UICompositionAnimations.Enums; + +namespace UICompositionAnimations.Animations.Interfaces +{ + /// + /// An that represents an animation builder that uses a specific set of APIs + /// + [PublicAPI] + public interface IAnimationBuilder + { + /// + /// Schedules an opacity animation + /// + /// The target opacity value to animate to + /// The easing function to use for the opacity animation + [MustUseReturnValue, NotNull] + IAnimationBuilder Opacity(double to, Easing ease = Easing.Linear); + + /// + /// Schedules an opacity animation + /// + /// The opacity value to animate from + /// The target opacity value to animate to + /// The easing function to use for the opacity animation + [MustUseReturnValue, NotNull] + IAnimationBuilder Opacity(double from, double to, Easing ease = Easing.Linear); + + /// + /// Schedules a translation animation + /// + /// The translation axis to animate + /// The target translation value to animate to + /// The easing function to use for the translation animation + [MustUseReturnValue, NotNull] + IAnimationBuilder Translation(Axis axis, double to, Easing ease = Easing.Linear); + + /// + /// Schedules a translation animation + /// + /// The translation axis to animate + /// The initial translation value to animate from + /// The target translation value to animate to + /// The easing function to use for the translation animation + [MustUseReturnValue, NotNull] + IAnimationBuilder Translation(Axis axis, double from, double to, Easing ease = Easing.Linear); + + /// + /// Schedules a translation animation + /// + /// The target translation position to animate to + /// The easing function to use for the translation animation + [MustUseReturnValue, NotNull] + IAnimationBuilder Translation(Vector2 to, Easing ease = Easing.Linear); + + /// + /// Schedules a translation animation + /// + /// The initial translation position to animate from + /// The target translation position to animate to + /// The easing function to use for the translation animation + [MustUseReturnValue, NotNull] + IAnimationBuilder Translation(Vector2 from, Vector2 to, Easing ease = Easing.Linear); + + /// + /// Schedules an offset animation + /// + /// The offset axis to animate + /// The target offset value to animate to + /// The easing function to use for the offset animation + [MustUseReturnValue, NotNull] + IAnimationBuilder Offset(Axis axis, double to, Easing ease = Easing.Linear); + + /// + /// Schedules an offset animation + /// + /// The offset axis to animate + /// The initial offset value to animate from + /// The target offset value to animate to + /// The easing function to use for the offset animation + [MustUseReturnValue, NotNull] + IAnimationBuilder Offset(Axis axis, double from, double to, Easing ease = Easing.Linear); + + /// + /// Schedules an offset animation + /// + /// The target offset position to animate to + /// The easing function to use for the offset animation + [MustUseReturnValue, NotNull] + IAnimationBuilder Offset(Vector2 to, Easing ease = Easing.Linear); + + /// + /// Schedules an offset animation + /// + /// The initial offset position to animate from + /// The target offset position to animate to + /// The easing function to use for the offset animation + [MustUseReturnValue, NotNull] + IAnimationBuilder Offset(Vector2 from, Vector2 to, Easing ease = Easing.Linear); + + /// + /// Schedules a scale animation + /// + /// The target scale value to animate to + /// The easing function to use for the scale animation + [MustUseReturnValue, NotNull] + IAnimationBuilder Scale(double to, Easing ease = Easing.Linear); + + /// + /// Schedules a scale animation + /// + /// The scale value to animate from + /// The target scale value to animate to + /// The easing function to use for the scale animation + [MustUseReturnValue, NotNull] + IAnimationBuilder Scale(double from, double to, Easing ease = Easing.Linear); + + /// + /// Schedules a rotation animation + /// + /// The target rotation value to animate to + /// The easing function to use for the rotation animation + [MustUseReturnValue, NotNull] + IAnimationBuilder Rotate(double to, Easing ease = Easing.Linear); + + /// + /// Schedules a rotation animation + /// + /// The rotation value to animate from + /// The target rotation value to animate to + /// The easing function to use for the rotation animation + [MustUseReturnValue, NotNull] + IAnimationBuilder Rotate(double from, double to, Easing ease = Easing.Linear); + + /// + /// Schedules a clip animation + /// + /// The clip side to animate + /// The target clip value to animate to + /// The easing function to use for the clip animation + [MustUseReturnValue, NotNull] + IAnimationBuilder Clip(Side side, double to, Easing ease = Easing.Linear); + + /// + /// Schedules a clip animation + /// + /// The clip side to animate + /// The clip value to animate from + /// The target clip value to animate to + /// The easing function to use for the clip animation + [MustUseReturnValue, NotNull] + IAnimationBuilder Clip(Side side, double from, double to, Easing ease = Easing.Linear); + + /// + /// Sets the duration of the animation + /// + /// The animation duration, in milliseconds + [MustUseReturnValue, NotNull] + IAnimationBuilder Duration(int ms); + + /// + /// Sets the duration of the animation + /// + /// The value indicating the animation duration + [MustUseReturnValue, NotNull] + IAnimationBuilder Duration(TimeSpan duration); + + /// + /// Sets the optional initial delay for the animation + /// + /// The delay duration, in milliseconds + [MustUseReturnValue, NotNull] + IAnimationBuilder Delay(int ms); + + /// + /// Sets the optional initial delay for the animation + /// + /// The value indicating the animation delay duration + [MustUseReturnValue, NotNull] + IAnimationBuilder Delay(TimeSpan duration); + + /// + /// Starts the animation + /// + void Start(); + + /// + /// Starts the animation and executes a callback when it completes + /// + /// The callback to execute when the animation completes + void Start([NotNull] Action callback); + + /// + /// Starts the animation and returns a to track its completion + /// + Task StartAsync(); + } +} diff --git a/UICompositionAnimations/Animations/XamlAnimationBuilder.cs b/UICompositionAnimations/Animations/XamlAnimationBuilder.cs new file mode 100644 index 0000000..f219ff1 --- /dev/null +++ b/UICompositionAnimations/Animations/XamlAnimationBuilder.cs @@ -0,0 +1,94 @@ +using System; +using System.Linq; +using System.Numerics; +using System.Threading.Tasks; +using Windows.Foundation; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Media.Animation; +using JetBrains.Annotations; +using UICompositionAnimations.Animations.Abstract; +using UICompositionAnimations.Animations.Interfaces; +using UICompositionAnimations.Enums; + +namespace UICompositionAnimations.Animations +{ + /// + /// A that implements the using composition APIs + /// + internal sealed class XamlAnimationBuilder : AnimationBuilderBase> + { + /// + /// The target to animate + /// + [NotNull] + private readonly CompositeTransform TargetTransform; + + public XamlAnimationBuilder([NotNull] UIElement target) : base(target) + { + TargetTransform = target.GetTransform(false); + target.RenderTransformOrigin = new Point(0.5, 0.5); + } + + /// + protected override IAnimationBuilder OnOpacity(double? from, double to, Easing ease) + { + AnimationFactories.Add(duration => TargetElement.CreateDoubleAnimation(nameof(UIElement.Opacity), from, to, duration, ease)); + + return this; + } + + /// + protected override IAnimationBuilder OnTranslation(Axis axis, double? from, double to, Easing ease) + { + AnimationFactories.Add(duration => TargetTransform.CreateDoubleAnimation($"Translate{axis}", from, to, duration, ease)); + + return this; + } + + /// + protected override IAnimationBuilder OnTranslation(Vector2? from, Vector2 to, Easing ease = Easing.Linear) + { + AnimationFactories.Add(duration => TargetTransform.CreateDoubleAnimation(nameof(CompositeTransform.TranslateX), from?.X, to.X, duration, ease)); + AnimationFactories.Add(duration => TargetTransform.CreateDoubleAnimation(nameof(CompositeTransform.TranslateY), from?.Y, to.Y, duration, ease)); + + return this; + } + + protected override IAnimationBuilder OnOffset(Axis axis, double? from, double to, Easing ease) => throw new NotSupportedException("Can't animate the offset property from XAML"); + + protected override IAnimationBuilder OnOffset(Vector2? from, Vector2 to, Easing ease = Easing.Linear) => throw new NotSupportedException("Can't animate the offset property from XAML"); + + /// + protected override IAnimationBuilder OnScale(double? from, double to, Easing ease) + { + AnimationFactories.Add(duration => TargetTransform.CreateDoubleAnimation(nameof(CompositeTransform.ScaleX), from, to, duration, ease)); + AnimationFactories.Add(duration => TargetTransform.CreateDoubleAnimation(nameof(CompositeTransform.ScaleY), from, to, duration, ease)); + + return this; + } + + /// + protected override IAnimationBuilder OnRotate(double? from, double to, Easing ease) + { + AnimationFactories.Add(duration => TargetTransform.CreateDoubleAnimation(nameof(CompositeTransform.Rotation), from, to, duration, ease)); + + return this; + } + + /// + protected override IAnimationBuilder OnClip(Side side, double? @from, double to, Easing ease) => throw new NotSupportedException("Can't animate the clip property from XAML"); + + /// + /// Gets the represented by the current instance + /// + [NotNull] + private Storyboard Storyboard => AnimationFactories.Select(f => f(DurationInterval)).ToStoryboard(); + + /// + protected override void OnStart() => Storyboard.Begin(); + + /// + protected override Task OnStartAsync() => Storyboard.BeginAsync(); + } +} diff --git a/UICompositionAnimations/AttachedProperties/ClipHelper.cs b/UICompositionAnimations/AttachedProperties/ClipHelper.cs new file mode 100644 index 0000000..05294d2 --- /dev/null +++ b/UICompositionAnimations/AttachedProperties/ClipHelper.cs @@ -0,0 +1,43 @@ +using System; +using Windows.UI.Composition; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Hosting; +using JetBrains.Annotations; + +namespace UICompositionAnimations.AttachedProperties +{ + /// + /// A with an attached property to set a composition clip to instances + /// + [PublicAPI] + public sealed class ClipHelper + { + /// + /// Gets the value of + /// + /// The to read the property value from + public static bool GetClipToBounds(UIElement element) => element.GetValue(ClipToBoundsProperty).To(); + + /// + /// Sets the value of + /// + /// The to set the property to + /// The new value of the attached property + public static void SetClipToBounds(UIElement element, bool value) => element.SetValue(ClipToBoundsProperty, value); + + /// + /// A property that indicates whether or not the contents of the target should always be clipped to their parent's bounds + /// + public static readonly DependencyProperty ClipToBoundsProperty = DependencyProperty.RegisterAttached( + "ClipToBounds", + typeof(bool), + typeof(ClipHelper), + new PropertyMetadata(false, OnClipToBoundsPropertyChanged)); + + private static void OnClipToBoundsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + Visual visual = ElementCompositionPreview.GetElementVisual(d.To()); + visual.Clip = e.NewValue.To() ? visual.Compositor.CreateInsetClip() : null; + } + } +} diff --git a/UICompositionAnimations/Behaviours/AcrylicEffectHelper.cs b/UICompositionAnimations/Behaviours/AcrylicEffectHelper.cs deleted file mode 100644 index f0f9ccb..0000000 --- a/UICompositionAnimations/Behaviours/AcrylicEffectHelper.cs +++ /dev/null @@ -1,113 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Windows.Graphics.Effects; -using Windows.UI; -using Windows.UI.Composition; -using JetBrains.Annotations; -using Microsoft.Graphics.Canvas; -using Microsoft.Graphics.Canvas.Effects; -using Microsoft.Graphics.Canvas.UI.Xaml; -using UICompositionAnimations.Enums; -using UICompositionAnimations.Helpers; - -namespace UICompositionAnimations.Behaviours -{ - /// - /// An helper class that manages the tint and noise layers on the custom acrylic brush effect - /// - internal static class AcrylicEffectHelper - { - /// - /// Concatenates the source effect with a tint overlay and a border effect - /// - /// The current object to use - /// The source effect to insert in the pipeline - /// A dictionary to use to keep track of reference parameters to add when creating a - /// The tint color - /// The amount of tint color to apply - /// The optional to use to generate the image for the - /// The path to the source image to use for the - /// The resulting effect through the pipeline - /// The method does side effect on the variable - [MustUseReturnValue, ItemNotNull] - public static async Task ConcatenateEffectWithTintAndBorderAsync( - [NotNull] Compositor compositor, - [NotNull] IGraphicsEffectSource source, [NotNull] IDictionary parameters, - Color color, float colorMix, - [CanBeNull] CanvasControl canvas, [NotNull] Uri uri) - { - // Setup the tint effect - ArithmeticCompositeEffect tint = new ArithmeticCompositeEffect - { - MultiplyAmount = 0, - Source1Amount = 1 - colorMix, - Source2Amount = colorMix, // Mix the background with the desired tint color - Source1 = source, - Source2 = new ColorSourceEffect { Color = color } - }; - - // Get the noise brush using Win2D - CompositionSurfaceBrush noiseBitmap = canvas == null - ? await Win2DImageHelper.LoadImageAsync(compositor, uri, BitmapDPIMode.CopyDisplayDPISettingsWith96AsLowerBound, BitmapCacheMode.Default) - : await Win2DImageHelper.LoadImageAsync(compositor, canvas, uri, BitmapDPIMode.CopyDisplayDPISettingsWith96AsLowerBound, BitmapCacheMode.Default); - - // Make sure the Win2D brush was loaded correctly - if (noiseBitmap != null) - { - // Layer 4 - Noise effect - BorderEffect borderEffect = new BorderEffect - { - ExtendX = CanvasEdgeBehavior.Wrap, - ExtendY = CanvasEdgeBehavior.Wrap, - Source = new CompositionEffectSourceParameter(nameof(noiseBitmap)) - }; - BlendEffect blendEffect = new BlendEffect - { - Background = tint, - Foreground = borderEffect, - Mode = BlendEffectMode.Overlay - }; - parameters.Add(nameof(noiseBitmap), noiseBitmap); - return blendEffect; - } - return tint; - } - - /// - /// Overlays a texture on a tint effect - /// - /// The current object to use - /// A dictionary to use to keep track of reference parameters to add when creating a - /// The tint color - /// The path to the source image to use for the - /// The resulting effect through the pipeline - /// The method does side effect on the variable - [MustUseReturnValue, ItemNotNull] - public static async Task LoadTextureEffectWithTintAsync( - [NotNull] Compositor compositor, [NotNull] IDictionary parameters, - Color color, [NotNull] Uri uri) - { - // Initial setup - ColorSourceEffect colorEffect = new ColorSourceEffect { Color = color }; - CompositionSurfaceBrush noiseBitmap = await Win2DImageHelper.LoadImageAsync(compositor, uri, BitmapDPIMode.CopyDisplayDPISettingsWith96AsLowerBound, BitmapCacheMode.Default); - if (noiseBitmap == null) return colorEffect; - - // Blend the effects - BorderEffect borderEffect = new BorderEffect - { - ExtendX = CanvasEdgeBehavior.Wrap, - ExtendY = CanvasEdgeBehavior.Wrap, - Source = new CompositionEffectSourceParameter(nameof(noiseBitmap)) - }; - BlendEffect blendEffect = new BlendEffect - { - Background = colorEffect, - Foreground = borderEffect, - Mode = BlendEffectMode.Overlay - }; - parameters.Add(nameof(noiseBitmap), noiseBitmap); - return blendEffect; - } - } -} \ No newline at end of file diff --git a/UICompositionAnimations/Behaviours/CompositionSourceInitializer.cs b/UICompositionAnimations/Behaviours/BrushProvider.cs similarity index 74% rename from UICompositionAnimations/Behaviours/CompositionSourceInitializer.cs rename to UICompositionAnimations/Behaviours/BrushProvider.cs index 7ab97a6..06ce3a3 100644 --- a/UICompositionAnimations/Behaviours/CompositionSourceInitializer.cs +++ b/UICompositionAnimations/Behaviours/BrushProvider.cs @@ -9,7 +9,7 @@ namespace UICompositionAnimations.Behaviours /// A simple container used to store info on a custom composition effect to create /// [PublicAPI] - public sealed class CompositionSourceInitializer + public sealed class BrushProvider { /// /// Gets the stored effect initializer @@ -23,7 +23,7 @@ public sealed class CompositionSourceInitializer [NotNull] internal string Name { get; } - private CompositionSourceInitializer([NotNull] string name, [NotNull] Func> initializer) + private BrushProvider([NotNull] string name, [NotNull] Func> initializer) { Name = name; Initializer = initializer; @@ -35,7 +35,7 @@ private CompositionSourceInitializer([NotNull] string name, [NotNull] FuncThe target effect name /// A instance that will produce the to use to initialize the effect [Pure, NotNull] - public static CompositionSourceInitializer New([NotNull] string name, [NotNull] Func initializer) => new CompositionSourceInitializer(name, () => Task.FromResult(initializer())); + public static BrushProvider New([NotNull] string name, [NotNull] Func initializer) => new BrushProvider(name, () => Task.FromResult(initializer())); /// /// Creates a new instance with the info on a given to initialize @@ -43,6 +43,6 @@ private CompositionSourceInitializer([NotNull] string name, [NotNull] FuncThe target effect name /// An asynchronous instance that will produce the to use to initialize the effect [Pure, NotNull] - public static CompositionSourceInitializer New([NotNull] string name, [NotNull] Func> initializer) => new CompositionSourceInitializer(name, initializer); + public static BrushProvider New([NotNull] string name, [NotNull] Func> initializer) => new BrushProvider(name, initializer); } } diff --git a/UICompositionAnimations/Behaviours/CompositionBrushBuilder.cs b/UICompositionAnimations/Behaviours/PipelineBuilder.cs similarity index 73% rename from UICompositionAnimations/Behaviours/CompositionBrushBuilder.cs rename to UICompositionAnimations/Behaviours/PipelineBuilder.cs index 87f0069..2aa121a 100644 --- a/UICompositionAnimations/Behaviours/CompositionBrushBuilder.cs +++ b/UICompositionAnimations/Behaviours/PipelineBuilder.cs @@ -12,9 +12,9 @@ using Microsoft.Graphics.Canvas; using Microsoft.Graphics.Canvas.Effects; using UICompositionAnimations.Brushes; -using UICompositionAnimations.Brushes.Cache; using UICompositionAnimations.Enums; using UICompositionAnimations.Helpers; +using UICompositionAnimations.Helpers.Cache; namespace UICompositionAnimations.Behaviours { @@ -30,7 +30,7 @@ namespace UICompositionAnimations.Behaviours /// A that allows to build custom effects pipelines and create instances from them /// [PublicAPI] - public sealed class CompositionBrushBuilder + public sealed class PipelineBuilder { /// /// The instance used to produce the output for this pipeline @@ -56,7 +56,7 @@ public sealed class CompositionBrushBuilder /// Constructor used to initialize a pipeline from a , for example using the method /// /// A instance that will return the initial - private CompositionBrushBuilder([NotNull] Func> factory) + private PipelineBuilder([NotNull] Func> factory) { string guid = Guid.NewGuid().ToString("N"), @@ -73,7 +73,7 @@ private CompositionBrushBuilder([NotNull] Func> factory) /// A instance that will produce the new to add to the pipeline /// The collection of animation properties for the new effect /// The collection of instances that needs to be initialized for the new effect - private CompositionBrushBuilder([NotNull] Func> factory, [NotNull] [ItemNotNull] IReadOnlyCollection animations, [NotNull] IReadOnlyDictionary>> lazy) + private PipelineBuilder([NotNull] Func> factory, [NotNull] [ItemNotNull] IReadOnlyCollection animations, [NotNull] IReadOnlyDictionary>> lazy) { SourceProducer = factory; AnimationProperties = animations; @@ -84,7 +84,7 @@ private CompositionBrushBuilder([NotNull] Func> fact /// Constructor used to initialize a pipeline from a custom instance /// /// A instance that will return the initial - private CompositionBrushBuilder([NotNull] Func> factory) + private PipelineBuilder([NotNull] Func> factory) : this(factory, new string[0], new Dictionary>>()) { } @@ -95,7 +95,7 @@ private CompositionBrushBuilder([NotNull] Func> fact /// A instance that will produce the new to add to the pipeline /// The collection of animation properties for the new effect /// The collection of instances that needs to be initialized for the new effect - private CompositionBrushBuilder([NotNull] CompositionBrushBuilder source, + private PipelineBuilder([NotNull] PipelineBuilder source, [NotNull] Func> factory, [CanBeNull] [ItemNotNull] IReadOnlyCollection animations = null, [CanBeNull] IReadOnlyDictionary>> lazy = null) : this(factory, animations?.Merge(source.AnimationProperties) ?? source.AnimationProperties, lazy?.Merge(source.LazyParameters) ?? source.LazyParameters) @@ -109,8 +109,8 @@ private CompositionBrushBuilder([NotNull] CompositionBrushBuilder source, /// The second pipeline to merge /// The collection of animation properties for the new effect /// The collection of instances that needs to be initialized for the new effect - private CompositionBrushBuilder([NotNull] Func> factory, - [NotNull] CompositionBrushBuilder a, [NotNull] CompositionBrushBuilder b, + private PipelineBuilder([NotNull] Func> factory, + [NotNull] PipelineBuilder a, [NotNull] PipelineBuilder b, [CanBeNull] [ItemNotNull] IReadOnlyCollection animations = null, [CanBeNull] IReadOnlyDictionary>> lazy = null) : this(factory, animations?.Merge(a.AnimationProperties.Merge(b.AnimationProperties)) ?? a.AnimationProperties.Merge(b.AnimationProperties), lazy?.Merge(a.LazyParameters.Merge(b.LazyParameters)) ?? a.LazyParameters.Merge(b.LazyParameters)) { } @@ -124,78 +124,78 @@ private CompositionBrushBuilder([NotNull] Func> fact private static readonly ThreadSafeCompositionCache BackdropBrushCache = new ThreadSafeCompositionCache(); /// - /// Starts a new pipeline from the returned by + /// Starts a new pipeline from the returned by /// [Pure, NotNull] - public static CompositionBrushBuilder FromBackdropBrush() => new CompositionBrushBuilder(() => BackdropBrushCache.TryGetInstanceAsync(Window.Current.Compositor.CreateBackdropBrush)); + public static PipelineBuilder FromBackdropBrush() => new PipelineBuilder(() => BackdropBrushCache.TryGetInstanceAsync(Window.Current.Compositor.CreateBackdropBrush)); // The cache manager for host backdrop brushes [NotNull] private static readonly ThreadSafeCompositionCache HostBackdropBrushCache = new ThreadSafeCompositionCache(); /// - /// Starts a new pipeline from the returned by + /// Starts a new pipeline from the returned by /// [Pure, NotNull] - public static CompositionBrushBuilder FromHostBackdropBrush() => new CompositionBrushBuilder(() => HostBackdropBrushCache.TryGetInstanceAsync(Window.Current.Compositor.CreateHostBackdropBrush)); + public static PipelineBuilder FromHostBackdropBrush() => new PipelineBuilder(() => HostBackdropBrushCache.TryGetInstanceAsync(Window.Current.Compositor.CreateHostBackdropBrush)); /// - /// Starts a new pipeline from a solid with the specified color + /// Starts a new pipeline from a solid with the specified color /// /// The desired color for the initial [Pure, NotNull] - public static CompositionBrushBuilder FromColor(Color color) => new CompositionBrushBuilder(() => Task.FromResult(new ColorSourceEffect { Color = color }.To())); + public static PipelineBuilder FromColor(Color color) => new PipelineBuilder(() => Task.FromResult(new ColorSourceEffect { Color = color }.To())); /// - /// Starts a new pipeline from the input instance + /// Starts a new pipeline from the input instance /// /// A that synchronously returns a instance to start the pipeline [Pure, NotNull] - public static CompositionBrushBuilder FromBrush(Func factory) => new CompositionBrushBuilder(() => Task.FromResult(factory())); + public static PipelineBuilder FromBrush(Func factory) => new PipelineBuilder(() => Task.FromResult(factory())); /// - /// Starts a new pipeline from the input instance + /// Starts a new pipeline from the input instance /// /// A that asynchronously returns a instance to start the pipeline [Pure, NotNull] - public static CompositionBrushBuilder FromBrush(Func> factory) => new CompositionBrushBuilder(factory); + public static PipelineBuilder FromBrush(Func> factory) => new PipelineBuilder(factory); /// - /// Starts a new pipeline from the input instance + /// Starts a new pipeline from the input instance /// /// A that synchronously returns a instance to start the pipeline [Pure, NotNull] - public static CompositionBrushBuilder FromEffect(Func factory) => new CompositionBrushBuilder(() => Task.FromResult(factory())); + public static PipelineBuilder FromEffect(Func factory) => new PipelineBuilder(() => Task.FromResult(factory())); /// - /// Starts a new pipeline from the input instance + /// Starts a new pipeline from the input instance /// /// A that asynchronously returns a instance to start the pipeline [Pure, NotNull] - public static CompositionBrushBuilder FromEffect(Func> factory) => new CompositionBrushBuilder(factory); + public static PipelineBuilder FromEffect(Func> factory) => new PipelineBuilder(factory); /// - /// Starts a new pipeline from a Win2D image + /// Starts a new pipeline from a Win2D image /// /// The path for the image to load /// Indicates the desired DPI mode to use when loading the image /// The cache mode to use to load the image [Pure, NotNull] - public static CompositionBrushBuilder FromImage([NotNull] Uri uri, BitmapDPIMode dpiMode = BitmapDPIMode.CopyDisplayDPISettingsWith96AsLowerBound, BitmapCacheMode cache = BitmapCacheMode.Default) + public static PipelineBuilder FromImage([NotNull] Uri uri, DpiMode dpiMode = DpiMode.DisplayDpiWith96AsLowerBound, CacheMode cache = CacheMode.Default) { - return new CompositionBrushBuilder(() => Win2DImageHelper.LoadImageAsync(Window.Current.Compositor, uri, dpiMode, cache).ContinueWith(t => t.Result as CompositionBrush)); + return new PipelineBuilder(() => Win2DImageHelper.LoadImageAsync(Window.Current.Compositor, uri, dpiMode, cache).ContinueWith(t => t.Result as CompositionBrush)); } /// - /// Starts a new pipeline from a Win2D image tiled to cover the available space + /// Starts a new pipeline from a Win2D image tiled to cover the available space /// /// The path for the image to load /// Indicates the desired DPI mode to use when loading the image /// The cache mode to use to load the image [Pure, NotNull] - public static CompositionBrushBuilder FromTiles([NotNull] Uri uri, BitmapDPIMode dpiMode = BitmapDPIMode.CopyDisplayDPISettingsWith96AsLowerBound, BitmapCacheMode cache = BitmapCacheMode.Default) + public static PipelineBuilder FromTiles([NotNull] Uri uri, DpiMode dpiMode = DpiMode.DisplayDpiWith96AsLowerBound, CacheMode cache = CacheMode.Default) { - CompositionBrushBuilder image = FromImage(uri, dpiMode, cache); + PipelineBuilder image = FromImage(uri, dpiMode, cache); async Task Factory() => new BorderEffect { @@ -204,17 +204,17 @@ public static CompositionBrushBuilder FromTiles([NotNull] Uri uri, BitmapDPIMode Source = await image.SourceProducer() }; - return new CompositionBrushBuilder(image, Factory); + return new PipelineBuilder(image, Factory); } /// - /// Starts a new pipeline from the returned by on the input + /// Starts a new pipeline from the returned by on the input /// /// The source to use to create the pipeline [Pure, NotNull] - public static CompositionBrushBuilder FromUIElement([NotNull] UIElement element) + public static PipelineBuilder FromUIElement([NotNull] UIElement element) { - return new CompositionBrushBuilder(() => Task.FromResult(element.GetVisual().Compositor.CreateBackdropBrush().To())); + return new PipelineBuilder(() => Task.FromResult(element.GetVisual().Compositor.CreateBackdropBrush().To())); } #endregion @@ -222,25 +222,25 @@ public static CompositionBrushBuilder FromUIElement([NotNull] UIElement element) #region Prebuilt pipelines /// - /// Returns a new instance that implements the host backdrop acrylic effect + /// Returns a new instance that implements the host backdrop acrylic effect /// /// The tint color to use /// The amount of tint to apply over the current effect /// The for the noise texture to load for the acrylic effect /// The cache mode to use to load the image [Pure, NotNull] - public static CompositionBrushBuilder FromHostBackdropAcrylic(Color tint, float mix, [NotNull] Uri noiseUri, BitmapCacheMode cache = BitmapCacheMode.Default) + public static PipelineBuilder FromHostBackdropAcrylic(Color tint, float mix, [NotNull] Uri noiseUri, CacheMode cache = CacheMode.Default) { return FromHostBackdropBrush() .Effect(source => new LuminanceToAlphaEffect { Source = source }) .Opacity(0.4f) .Blend(FromHostBackdropBrush(), BlendEffectMode.Multiply) .Tint(tint, mix) - .Blend(FromTiles(noiseUri, cache: cache), BlendEffectMode.Overlay, EffectPlacement.Background); + .Blend(FromTiles(noiseUri, cache: cache), BlendEffectMode.Overlay, Placement.Background); } /// - /// Returns a new instance that implements the host backdrop acrylic effect + /// Returns a new instance that implements the host backdrop acrylic effect /// /// The tint color to use /// The animation to apply on the tint color of the effect @@ -248,18 +248,18 @@ public static CompositionBrushBuilder FromHostBackdropAcrylic(Color tint, float /// The for the noise texture to load for the acrylic effect /// The cache mode to use to load the image [Pure, NotNull] - public static CompositionBrushBuilder FromHostBackdropAcrylic(Color tint, float mix, out EffectAnimation tintAnimation, [NotNull] Uri noiseUri, BitmapCacheMode cache = BitmapCacheMode.Default) + public static PipelineBuilder FromHostBackdropAcrylic(Color tint, float mix, out EffectAnimation tintAnimation, [NotNull] Uri noiseUri, CacheMode cache = CacheMode.Default) { return FromHostBackdropBrush() .Effect(source => new LuminanceToAlphaEffect { Source = source }) .Opacity(0.4f) .Blend(FromHostBackdropBrush(), BlendEffectMode.Multiply) .Tint(tint, mix, out tintAnimation) - .Blend(FromTiles(noiseUri, cache: cache), BlendEffectMode.Overlay, EffectPlacement.Background); + .Blend(FromTiles(noiseUri, cache: cache), BlendEffectMode.Overlay, Placement.Background); } /// - /// Returns a new instance that implements the in-app backdrop acrylic effect + /// Returns a new instance that implements the in-app backdrop acrylic effect /// /// The tint color to use /// The amount of tint to apply over the current effect @@ -267,16 +267,16 @@ public static CompositionBrushBuilder FromHostBackdropAcrylic(Color tint, float /// The for the noise texture to load for the acrylic effect /// The cache mode to use to load the image [Pure, NotNull] - public static CompositionBrushBuilder FromBackdropAcrylic(Color tint, float mix, float blur, [NotNull] Uri noiseUri, BitmapCacheMode cache = BitmapCacheMode.Default) + public static PipelineBuilder FromBackdropAcrylic(Color tint, float mix, float blur, [NotNull] Uri noiseUri, CacheMode cache = CacheMode.Default) { return FromBackdropBrush() .Tint(tint, mix) .Blur(blur) - .Blend(FromTiles(noiseUri, cache: cache), BlendEffectMode.Overlay, EffectPlacement.Background); + .Blend(FromTiles(noiseUri, cache: cache), BlendEffectMode.Overlay, Placement.Background); } /// - /// Returns a new instance that implements the in-app backdrop acrylic effect + /// Returns a new instance that implements the in-app backdrop acrylic effect /// /// The tint color to use /// The amount of tint to apply over the current effect @@ -285,19 +285,19 @@ public static CompositionBrushBuilder FromBackdropAcrylic(Color tint, float mix, /// The for the noise texture to load for the acrylic effect /// The cache mode to use to load the image [Pure, NotNull] - public static CompositionBrushBuilder FromBackdropAcrylic( + public static PipelineBuilder FromBackdropAcrylic( Color tint, float mix, out EffectAnimation tintAnimation, float blur, - [NotNull] Uri noiseUri, BitmapCacheMode cache = BitmapCacheMode.Default) + [NotNull] Uri noiseUri, CacheMode cache = CacheMode.Default) { return FromBackdropBrush() .Tint(tint, mix, out tintAnimation) .Blur(blur) - .Blend(FromTiles(noiseUri, cache: cache), BlendEffectMode.Overlay, EffectPlacement.Background); + .Blend(FromTiles(noiseUri, cache: cache), BlendEffectMode.Overlay, Placement.Background); } /// - /// Returns a new instance that implements the in-app backdrop acrylic effect + /// Returns a new instance that implements the in-app backdrop acrylic effect /// /// The tint color to use /// The amount of tint to apply over the current effect @@ -306,19 +306,19 @@ public static CompositionBrushBuilder FromBackdropAcrylic( /// The for the noise texture to load for the acrylic effect /// The cache mode to use to load the image [Pure, NotNull] - public static CompositionBrushBuilder FromBackdropAcrylic( + public static PipelineBuilder FromBackdropAcrylic( Color tint, float mix, float blur, out EffectAnimation blurAnimation, - [NotNull] Uri noiseUri, BitmapCacheMode cache = BitmapCacheMode.Default) + [NotNull] Uri noiseUri, CacheMode cache = CacheMode.Default) { return FromBackdropBrush() .Tint(tint, mix) .Blur(blur, out blurAnimation) - .Blend(FromTiles(noiseUri, cache: cache), BlendEffectMode.Overlay, EffectPlacement.Background); + .Blend(FromTiles(noiseUri, cache: cache), BlendEffectMode.Overlay, Placement.Background); } /// - /// Returns a new instance that implements the in-app backdrop acrylic effect + /// Returns a new instance that implements the in-app backdrop acrylic effect /// /// The tint color to use /// The amount of tint to apply over the current effect @@ -328,15 +328,15 @@ public static CompositionBrushBuilder FromBackdropAcrylic( /// The for the noise texture to load for the acrylic effect /// The cache mode to use to load the image [Pure, NotNull] - public static CompositionBrushBuilder FromBackdropAcrylic( + public static PipelineBuilder FromBackdropAcrylic( Color tint, float mix, out EffectAnimation tintAnimation, float blur, out EffectAnimation blurAnimation, - [NotNull] Uri noiseUri, BitmapCacheMode cache = BitmapCacheMode.Default) + [NotNull] Uri noiseUri, CacheMode cache = CacheMode.Default) { return FromBackdropBrush() .Tint(tint, mix, out tintAnimation) .Blur(blur, out blurAnimation) - .Blend(FromTiles(noiseUri, cache: cache), BlendEffectMode.Overlay, EffectPlacement.Background); + .Blend(FromTiles(noiseUri, cache: cache), BlendEffectMode.Overlay, Placement.Background); } #endregion @@ -346,13 +346,13 @@ public static CompositionBrushBuilder FromBackdropAcrylic( /// /// Blends two pipelines using a instance with the specified mode /// - /// The second instance to blend + /// The second instance to blend /// The desired to use to blend the input pipelines /// The sorting mode to use with the two input pipelines [Pure, NotNull] - public CompositionBrushBuilder Blend([NotNull] CompositionBrushBuilder pipeline, BlendEffectMode mode, EffectPlacement sorting = EffectPlacement.Foreground) + public PipelineBuilder Blend([NotNull] PipelineBuilder pipeline, BlendEffectMode mode, Placement sorting = Placement.Foreground) { - (var foreground, var background) = sorting == EffectPlacement.Foreground ? (this, pipeline) : (pipeline, this); + var (foreground, background) = sorting == Placement.Foreground ? (this, pipeline) : (pipeline, this); async Task Factory() => new BlendEffect { @@ -361,20 +361,20 @@ public CompositionBrushBuilder Blend([NotNull] CompositionBrushBuilder pipeline, Mode = mode }; - return new CompositionBrushBuilder(Factory, foreground, background); + return new PipelineBuilder(Factory, foreground, background); } /// /// Blends two pipelines using an instance /// - /// The second instance to blend + /// The second instance to blend /// The cross fade factor to blend the input effects /// The sorting mode to use with the two input pipelines [Pure, NotNull] - public CompositionBrushBuilder Mix([NotNull] CompositionBrushBuilder pipeline, float factor = 0.5f, EffectPlacement sorting = EffectPlacement.Foreground) + public PipelineBuilder Mix([NotNull] PipelineBuilder pipeline, float factor = 0.5f, Placement sorting = Placement.Foreground) { if (factor < 0 || factor > 1) throw new ArgumentOutOfRangeException(nameof(factor), "The factor must be in the [0,1] range"); - (var foreground, var background) = sorting == EffectPlacement.Foreground ? (this, pipeline) : (pipeline, this); + var (foreground, background) = sorting == Placement.Foreground ? (this, pipeline) : (pipeline, this); async Task Factory() => new CrossFadeEffect { @@ -383,22 +383,22 @@ public CompositionBrushBuilder Mix([NotNull] CompositionBrushBuilder pipeline, f Source2 = await background.SourceProducer() }; - return new CompositionBrushBuilder(Factory, foreground, background); + return new PipelineBuilder(Factory, foreground, background); } /// /// Blends two pipelines using an instance /// - /// The second instance to blend + /// The second instance to blend /// The cross fade factor to blend the input effects /// The optional blur animation for the effect /// The sorting mode to use with the two input pipelines /// Note that each pipeline can only contain a single instance of any of the built-in effects with animation support [Pure, NotNull] - public CompositionBrushBuilder Mix([NotNull] CompositionBrushBuilder pipeline, float factor, out EffectAnimation animation, EffectPlacement sorting = EffectPlacement.Foreground) + public PipelineBuilder Mix([NotNull] PipelineBuilder pipeline, float factor, out EffectAnimation animation, Placement sorting = Placement.Foreground) { if (factor < 0 || factor > 1) throw new ArgumentOutOfRangeException(nameof(factor), "The factor must be in the [0,1] range"); - (var foreground, var background) = sorting == EffectPlacement.Foreground ? (this, pipeline) : (pipeline, this); + var (foreground, background) = sorting == Placement.Foreground ? (this, pipeline) : (pipeline, this); async Task Factory() => new CrossFadeEffect { @@ -414,7 +414,7 @@ public CompositionBrushBuilder Mix([NotNull] CompositionBrushBuilder pipeline, f return brush.StartAnimationAsync("Fade.CrossFade", value, TimeSpan.FromMilliseconds(ms)); }; - return new CompositionBrushBuilder(Factory, foreground, background, new[] { "Fade.CrossFade" }); + return new PipelineBuilder(Factory, foreground, background, new[] { "Fade.CrossFade" }); } /// @@ -425,14 +425,14 @@ public CompositionBrushBuilder Mix([NotNull] CompositionBrushBuilder pipeline, f /// The list of optional animatable properties in the returned effect /// The list of source parameters that require deferred initialization (see for more info) [Pure, NotNull] - public CompositionBrushBuilder Merge( + public PipelineBuilder Merge( [NotNull] Func factory, - [NotNull] CompositionBrushBuilder background, - IEnumerable animations = null, IEnumerable initializers = null) + [NotNull] PipelineBuilder background, + IEnumerable animations = null, IEnumerable initializers = null) { async Task Factory() => factory(await SourceProducer(), await background.SourceProducer()); - return new CompositionBrushBuilder(Factory, this, background, animations?.ToArray(), initializers?.ToDictionary(item => item.Name, item => item.Initializer)); + return new PipelineBuilder(Factory, this, background, animations?.ToArray(), initializers?.ToDictionary(item => item.Name, item => item.Initializer)); } /// @@ -443,14 +443,14 @@ public CompositionBrushBuilder Merge( /// The list of optional animatable properties in the returned effect /// The list of source parameters that require deferred initialization (see for more info) [Pure, NotNull] - public CompositionBrushBuilder Merge( + public PipelineBuilder Merge( [NotNull] Func> factory, - [NotNull] CompositionBrushBuilder background, - IEnumerable animations = null, IEnumerable initializers = null) + [NotNull] PipelineBuilder background, + IEnumerable animations = null, IEnumerable initializers = null) { async Task Factory() => await factory(await SourceProducer(), await background.SourceProducer()); - return new CompositionBrushBuilder(Factory, this, background, animations?.ToArray(), initializers?.ToDictionary(item => item.Name, item => item.Initializer)); + return new PipelineBuilder(Factory, this, background, animations?.ToArray(), initializers?.ToDictionary(item => item.Name, item => item.Initializer)); } #endregion @@ -464,7 +464,7 @@ public CompositionBrushBuilder Merge( /// The parameter for the effect, defaults to /// The parameter to use, defaults to [Pure, NotNull] - public CompositionBrushBuilder Blur(float blur, EffectBorderMode mode = EffectBorderMode.Hard, EffectOptimization optimization = EffectOptimization.Balanced) + public PipelineBuilder Blur(float blur, EffectBorderMode mode = EffectBorderMode.Hard, EffectOptimization optimization = EffectOptimization.Balanced) { // Blur effect async Task Factory() => new GaussianBlurEffect @@ -475,7 +475,7 @@ public CompositionBrushBuilder Blur(float blur, EffectBorderMode mode = EffectBo Source = await SourceProducer() }; - return new CompositionBrushBuilder(this, Factory); + return new PipelineBuilder(this, Factory); } /// @@ -487,7 +487,7 @@ public CompositionBrushBuilder Blur(float blur, EffectBorderMode mode = EffectBo /// The parameter to use, defaults to /// Note that each pipeline can only contain a single instance of any of the built-in effects with animation support [Pure, NotNull] - public CompositionBrushBuilder Blur(float blur, out EffectAnimation animation, EffectBorderMode mode = EffectBorderMode.Hard, EffectOptimization optimization = EffectOptimization.Balanced) + public PipelineBuilder Blur(float blur, out EffectAnimation animation, EffectBorderMode mode = EffectBorderMode.Hard, EffectOptimization optimization = EffectOptimization.Balanced) { // Blur effect async Task Factory() => new GaussianBlurEffect @@ -501,7 +501,7 @@ public CompositionBrushBuilder Blur(float blur, out EffectAnimation animation, E animation = (brush, value, ms) => brush.StartAnimationAsync("Blur.BlurAmount", value, TimeSpan.FromMilliseconds(ms)); - return new CompositionBrushBuilder(this, Factory, new[] { "Blur.BlurAmount" }); + return new PipelineBuilder(this, Factory, new[] { "Blur.BlurAmount" }); } /// @@ -509,7 +509,7 @@ public CompositionBrushBuilder Blur(float blur, out EffectAnimation animation, E /// /// The saturation amount for the new effect [Pure, NotNull] - public CompositionBrushBuilder Saturation(float saturation) + public PipelineBuilder Saturation(float saturation) { if (saturation < 0 || saturation > 1) throw new ArgumentOutOfRangeException(nameof(saturation), "The saturation must be in the [0,1] range"); async Task Factory() => new SaturationEffect @@ -518,7 +518,7 @@ public CompositionBrushBuilder Saturation(float saturation) Source = await SourceProducer() }; - return new CompositionBrushBuilder(this, Factory); + return new PipelineBuilder(this, Factory); } /// @@ -528,7 +528,7 @@ public CompositionBrushBuilder Saturation(float saturation) /// The optional saturation animation for the effect /// Note that each pipeline can only contain a single instance of any of the built-in effects with animation support [Pure, NotNull] - public CompositionBrushBuilder Saturation(float saturation, out EffectAnimation animation) + public PipelineBuilder Saturation(float saturation, out EffectAnimation animation) { if (saturation < 0 || saturation > 1) throw new ArgumentOutOfRangeException(nameof(saturation), "The saturation must be in the [0,1] range"); async Task Factory() => new SaturationEffect @@ -544,7 +544,7 @@ public CompositionBrushBuilder Saturation(float saturation, out EffectAnimation return brush.StartAnimationAsync("Saturation.Saturation", value, TimeSpan.FromMilliseconds(ms)); }; - return new CompositionBrushBuilder(this, Factory, new[] { "Saturation.Saturation" }); + return new PipelineBuilder(this, Factory, new[] { "Saturation.Saturation" }); } /// @@ -552,7 +552,7 @@ public CompositionBrushBuilder Saturation(float saturation, out EffectAnimation /// /// The opacity value to apply to the pipeline [Pure, NotNull] - public CompositionBrushBuilder Opacity(float opacity) + public PipelineBuilder Opacity(float opacity) { if (opacity < 0 || opacity > 1) throw new ArgumentOutOfRangeException(nameof(opacity), "The opacity must be in the [0,1] range"); async Task Factory() => new OpacityEffect @@ -561,7 +561,7 @@ public CompositionBrushBuilder Opacity(float opacity) Source = await SourceProducer() }; - return new CompositionBrushBuilder(this, Factory); + return new PipelineBuilder(this, Factory); } /// @@ -571,7 +571,7 @@ public CompositionBrushBuilder Opacity(float opacity) /// The optional opacity animation for the effect /// Note that each pipeline can only contain a single instance of any of the built-in effects with animation support [Pure, NotNull] - public CompositionBrushBuilder Opacity(float opacity, out EffectAnimation animation) + public PipelineBuilder Opacity(float opacity, out EffectAnimation animation) { if (opacity < 0 || opacity > 1) throw new ArgumentOutOfRangeException(nameof(opacity), "The opacity must be in the [0,1] range"); async Task Factory() => new OpacityEffect @@ -587,7 +587,7 @@ public CompositionBrushBuilder Opacity(float opacity, out EffectAnimation animat return brush.StartAnimationAsync("Opacity.Opacity", value, TimeSpan.FromMilliseconds(ms)); }; - return new CompositionBrushBuilder(this, Factory, new[] { "Opacity.Opacity" }); + return new PipelineBuilder(this, Factory, new[] { "Opacity.Opacity" }); } /// @@ -596,7 +596,7 @@ public CompositionBrushBuilder Opacity(float opacity, out EffectAnimation animat /// The tint color to use /// The amount of tint to apply over the current effect [Pure, NotNull] - public CompositionBrushBuilder Tint(Color color, float mix) => FromColor(color).Mix(this, mix); + public PipelineBuilder Tint(Color color, float mix) => FromColor(color).Mix(this, mix); /// /// Applies a tint color on the current pipeline @@ -606,7 +606,7 @@ public CompositionBrushBuilder Opacity(float opacity, out EffectAnimation animat /// The optional tint animation for the effect /// Note that each pipeline can only contain a single instance of any of the built-in effects with animation support [Pure, NotNull] - public CompositionBrushBuilder Tint(Color color, float mix, out EffectAnimation animation) => FromColor(color).Mix(this, mix, out animation); + public PipelineBuilder Tint(Color color, float mix, out EffectAnimation animation) => FromColor(color).Mix(this, mix, out animation); #endregion @@ -619,11 +619,11 @@ public CompositionBrushBuilder Opacity(float opacity, out EffectAnimation animat /// The list of optional animatable properties in the returned effect /// The list of source parameters that require deferred initialization (see for more info) [Pure, NotNull] - public CompositionBrushBuilder Effect([NotNull] Func factory, IEnumerable animations = null, IEnumerable initializers = null) + public PipelineBuilder Effect([NotNull] Func factory, IEnumerable animations = null, IEnumerable initializers = null) { async Task Factory() => factory(await SourceProducer()); - return new CompositionBrushBuilder(this, Factory, animations?.ToArray(), initializers?.ToDictionary(item => item.Name, item => item.Initializer)); + return new PipelineBuilder(this, Factory, animations?.ToArray(), initializers?.ToDictionary(item => item.Name, item => item.Initializer)); } /// @@ -633,11 +633,11 @@ public CompositionBrushBuilder Effect([NotNull] FuncThe list of optional animatable properties in the returned effect /// The list of source parameters that require deferred initialization (see for more info) [Pure, NotNull] - public CompositionBrushBuilder Effect([NotNull] Func> factory, IEnumerable animations = null, IEnumerable initializers = null) + public PipelineBuilder Effect([NotNull] Func> factory, IEnumerable animations = null, IEnumerable initializers = null) { async Task Factory() => await factory(await SourceProducer()); - return new CompositionBrushBuilder(this, Factory, animations?.ToArray(), initializers?.ToDictionary(item => item.Name, item => item.Initializer)); + return new PipelineBuilder(this, Factory, animations?.ToArray(), initializers?.ToDictionary(item => item.Name, item => item.Initializer)); } #endregion diff --git a/UICompositionAnimations/Brushes/AcrylicBrush.cs b/UICompositionAnimations/Brushes/AcrylicBrush.cs new file mode 100644 index 0000000..8e4bdc0 --- /dev/null +++ b/UICompositionAnimations/Brushes/AcrylicBrush.cs @@ -0,0 +1,51 @@ +using System; +using Windows.UI; +using Windows.UI.Xaml.Media; +using UICompositionAnimations.Behaviours; +using UICompositionAnimations.Brushes.Base; + +namespace UICompositionAnimations.Brushes +{ + /// + /// A that implements an acrylic effect with customizable parameters + /// + public sealed class AcrylicBrush : XamlCompositionEffectBrushBase + { + /// + /// Gets or sets the source mode for the effect + /// + public AcrylicBackgroundSource Source { get; set; } + + /// + /// Gets or sets the blur amount for the effect + /// + /// This property is ignored when the active mode is + public double BlurAmount { get; set; } + + /// + /// Gets or sets the tint for the effect + /// + public Color Tint { get; set; } + + /// + /// Gets or sets the color for the tint effect + /// + public double TintMix { get; set; } + + /// + /// Gets or sets the to the texture to use + /// + public Uri TextureUri { get; set; } + + /// + protected override PipelineBuilder OnBrushRequested() + { + switch (Source) + { + case AcrylicBackgroundSource.Backdrop: return PipelineBuilder.FromBackdropAcrylic(Tint, (float)TintMix, (float)BlurAmount, TextureUri); + case AcrylicBackgroundSource.HostBackdrop: return PipelineBuilder.FromHostBackdropAcrylic(Tint, (float)TintMix, TextureUri); + default: throw new ArgumentOutOfRangeException(nameof(Source), $"Invalid acrylic source: {Source}"); + } + } + } +} diff --git a/UICompositionAnimations/Brushes/Base/XamlCompositionEffectBrushBase.cs b/UICompositionAnimations/Brushes/Base/XamlCompositionEffectBrushBase.cs index 685bd9a..a967254 100644 --- a/UICompositionAnimations/Brushes/Base/XamlCompositionEffectBrushBase.cs +++ b/UICompositionAnimations/Brushes/Base/XamlCompositionEffectBrushBase.cs @@ -6,7 +6,7 @@ namespace UICompositionAnimations.Brushes.Base { /// - /// A custom that's ready to be used with a custom pipeline. + /// A custom that's ready to be used with a custom pipeline. /// [PublicAPI] public abstract class XamlCompositionEffectBrushBase : XamlCompositionBrushBase @@ -16,11 +16,11 @@ public abstract class XamlCompositionEffectBrushBase : XamlCompositionBrushBase private readonly AsyncMutex ConnectedMutex = new AsyncMutex(); /// - /// A method that builds and returns the pipeline to use in the current instance. + /// A method that builds and returns the pipeline to use in the current instance. /// This method can also be used to store any needed instances in local fields, for later use (they will need to be called upon ). /// [MustUseReturnValue, NotNull] - protected abstract CompositionBrushBuilder OnBrushRequested(); + protected abstract PipelineBuilder OnBrushRequested(); /// protected override async void OnConnected() diff --git a/UICompositionAnimations/Brushes/CustomAcrylicBrush.cs b/UICompositionAnimations/Brushes/CustomAcrylicBrush.cs deleted file mode 100644 index df67cc5..0000000 --- a/UICompositionAnimations/Brushes/CustomAcrylicBrush.cs +++ /dev/null @@ -1,441 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using Windows.Graphics.Effects; -using Windows.UI; -using Microsoft.Graphics.Canvas.Effects; -using Windows.UI.Composition; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Media; -using UICompositionAnimations.Behaviours; -using UICompositionAnimations.Brushes.Cache; -using UICompositionAnimations.Enums; -using UICompositionAnimations.Helpers; -using Windows.ApplicationModel; -using JetBrains.Annotations; - -namespace UICompositionAnimations.Brushes -{ - /// - /// A custom XAML brush that includes an acrylic effect that blurs the in-app content - /// - public sealed class CustomAcrylicBrush : XamlCompositionBrushBase - { - #region Constants - - // The name of the animatable blur amount property - private const string BlurAmountParameterName = "Blur.BlurAmount"; - - // The name of the animatable source 1 property (the brush) of the tint effect - private const string TintColor1ParameterName = "Tint.Source1Amount"; - - // The name of the animatable source 2 property (the tint color) of the tint effect - private const string TintColor2ParameterName = "Tint.Source2Amount"; - - // The name of the animatable color property of the color effect - private const string ColorSourceParameterName = "ColorSource.Color"; - - // The name of the animatable color property of the fallback color effect - private const string FallbackColorParameterName = "FallbackColor.Color"; - - #endregion - - #region Properties - - /// - /// Gets or sets the source mode for the custom acrylic effect - /// - public AcrylicEffectMode Mode - { - get => GetValue(ModeProperty).To(); - set => SetValue(ModeProperty, value); - } - - /// - /// Gets the for the property - /// - public static readonly DependencyProperty ModeProperty = - DependencyProperty.Register(nameof(Mode), typeof(AcrylicEffectMode), typeof(CustomAcrylicBrush), new PropertyMetadata(AcrylicEffectMode.InAppBlur, OnModePropertyChanged)); - - private static async void OnModePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - CustomAcrylicBrush @this = d.To(); - await @this.ConnectedSemaphore.WaitAsync(); - if (@this.CompositionBrush != null) - { - // Rebuild the effects pipeline when needed - @this.CompositionBrush.Dispose(); - @this.CompositionBrush = null; - @this._State = AcrylicBrushEffectState.Default; - await @this.SetupEffectAsync(); - } - @this.ConnectedSemaphore.Release(); - } - - /// - /// Gets or sets the blur amount for the effect - /// - /// This property is ignored when the active mode is - public double BlurAmount - { - get => GetValue(BlurAmountProperty).To(); - set => SetValue(BlurAmountProperty, value); - } - - /// - /// Gets the for the property - /// - public static readonly DependencyProperty BlurAmountProperty = - DependencyProperty.Register(nameof(BlurAmount), typeof(double), typeof(CustomAcrylicBrush), new PropertyMetadata(8d, OnBlurAmountPropertyChanged)); - - private static async void OnBlurAmountPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - CustomAcrylicBrush @this = d.To(); - await @this.ConnectedSemaphore.WaitAsync(); - if (@this.Mode == AcrylicEffectMode.InAppBlur) - { - // Update the blur value with or without an animation - if (@this.BlurAnimationDuration == 0) - { - @this._EffectBrush?.Properties.InsertScalar(BlurAmountParameterName, (float)e.NewValue.To()); - } - else @this._EffectBrush?.Properties.StartAnimationAsync(BlurAmountParameterName, (float)e.NewValue, TimeSpan.FromMilliseconds(@this.BlurAnimationDuration)); - } - @this.ConnectedSemaphore.Release(); - } - - /// - /// Gets or sets the duration of the optional animation played when changing the property - /// - /// This property is ignored when the active mode is - public int BlurAnimationDuration - { - get => GetValue(BlurAnimationDurationProperty).To(); - set => SetValue(BlurAnimationDurationProperty, value); - } - - /// - /// Gets the for the property - /// - public static readonly DependencyProperty BlurAnimationDurationProperty = - DependencyProperty.Register(nameof(BlurAnimationDuration), typeof(int), typeof(CustomAcrylicBrush), new PropertyMetadata(0)); - - /// - /// Gets or sets the color for the tint effect - /// - public Color Tint - { - get => GetValue(TintProperty).To(); - set => SetValue(TintProperty, value); - } - - /// - /// Gets the for the property - /// - public static readonly DependencyProperty TintProperty = - DependencyProperty.Register(nameof(Tint), typeof(Color), typeof(CustomAcrylicBrush), new PropertyMetadata(Colors.Transparent, OnTintPropertyChanged)); - - private static async void OnTintPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - CustomAcrylicBrush @this = d.To(); - await @this.ConnectedSemaphore.WaitAsync(); - if (@this.CompositionBrush != null && @this._State != AcrylicBrushEffectState.FallbackMode) - { - @this._EffectBrush?.Properties.InsertColor(ColorSourceParameterName, e.NewValue.To()); - } - @this.ConnectedSemaphore.Release(); - } - - /// - /// Gets or sets the color for the tint effect (NOTE: this value must be in the [0..1) range) - /// - public double TintMix - { - get => GetValue(TintMixProperty).To(); - set => SetValue(TintMixProperty, value); - } - - /// - /// Gets the for the property - /// - public static readonly DependencyProperty TintMixProperty = - DependencyProperty.Register(nameof(TintMix), typeof(double), typeof(CustomAcrylicBrush), new PropertyMetadata(0d, OnTintMixPropertyChanged)); - - private static async void OnTintMixPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - double value = e.NewValue.To(); - float fvalue = (float)value; - if (value < 0 || value >= 1) throw new ArgumentOutOfRangeException(nameof(value), "The tint mix must be in the [0..1) range"); - CustomAcrylicBrush @this = d.To(); - await @this.ConnectedSemaphore.WaitAsync(); - if (@this.CompositionBrush != null && @this._State != AcrylicBrushEffectState.FallbackMode) - { - @this._EffectBrush.Properties.InsertScalar(TintColor1ParameterName, 1 - fvalue); - @this._EffectBrush.Properties.InsertScalar(TintColor2ParameterName, fvalue); - } - @this.ConnectedSemaphore.Release(); - } - - /// - /// Gets or sets the optional color to use for the brush, if the effect can't be loaded - /// - public Color UnsupportedEffectFallbackColor - { - get => GetValue(UnsupportedEffectFallbackColorProperty).To(); - set => SetValue(UnsupportedEffectFallbackColorProperty, value); - } - - /// - /// Gets the for the property - /// - public static readonly DependencyProperty UnsupportedEffectFallbackColorProperty = - DependencyProperty.Register(nameof(UnsupportedEffectFallbackColor), typeof(Color), typeof(CustomAcrylicBrush), - new PropertyMetadata(Colors.Transparent, OnUnsupportedEffectFallbackColorPropertyChanged)); - - private static async void OnUnsupportedEffectFallbackColorPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - // Unpack and lock - CustomAcrylicBrush @this = d.To(); - await @this.ConnectedSemaphore.WaitAsync(); - Color value = e.NewValue.To(); - - // The fallback mode is currently enabled - if (@this._State == AcrylicBrushEffectState.FallbackMode) - { - // New value is null, reset the current effect - if (value.Equals(Colors.Transparent)) - { - @this.CompositionBrush.Dispose(); - @this.CompositionBrush = null; - @this._State = AcrylicBrushEffectState.Default; - } - else - { - // Otherwise, update the fallback color effect - @this._EffectBrush?.Properties.InsertColor(FallbackColorParameterName, value); - } - } - @this.ConnectedSemaphore.Release(); - } - - /// - /// Gets or sets the to the noise texture to use - /// - /// This property must be initialized before using the brush - public Uri NoiseTextureUri { get; set; } - - /// - /// Indicates whether or not to enable an additional safety procedure when loading the acrylic brush. - /// This property should be used for brushes being rendered on secondary windows that use a given acrylic mode for the first - /// time in the application, to prevent the internal cache from storing a brush instance that would cause the app to crash if - /// reused in the app primary window, due to the different associated with that window. - /// This property can stay disabled in most cases and should only be turned on when dealing with particular edge cases or issues - /// with secondary app windows. - /// - /// Turning this property on disables the internal cache system for instances. - /// This property, like the property, must be initialized before using the brush - public bool DispatchProtectionEnabled { get; set; } - - #endregion - - // Initialization semaphore (due to the Win2D image loading being asynchronous) - private readonly SemaphoreSlim ConnectedSemaphore = new SemaphoreSlim(1); - - // The composition brush used to render the effect - private CompositionEffectBrush _EffectBrush; - - // Gets the current brush shate - private AcrylicBrushEffectState _State = AcrylicBrushEffectState.Default; - - /// - protected override async void OnConnected() - { - if (CompositionBrush == null) - { - await ConnectedSemaphore.WaitAsync(); - if (CompositionBrush == null) // It could have been initialized while waiting on the semaphore - { - await SetupEffectAsync(); - } - ConnectedSemaphore.Release(); - } - base.OnConnected(); - } - - /// - protected override async void OnDisconnected() - { - if (CompositionBrush != null) - { - await ConnectedSemaphore.WaitAsync(); - if (CompositionBrush != null) - { - CompositionBrush.Dispose(); - CompositionBrush = null; - _State = AcrylicBrushEffectState.Default; - } - ConnectedSemaphore.Release(); - } - base.OnDisconnected(); - } - - #region CompositionBackdropBrush cache - - // The synchronization semaphore for the in-app backdrop brush - private static readonly SemaphoreSlim BackdropSemaphore = new SemaphoreSlim(1); - - // The cached in-app backdrop brush - private static CompositionBackdropBrush _BackdropInstance; - - // The name to use for the in-app backdrop reference parameter - private const string BackdropReferenceParameterName = "BackdropBrush"; - - // The synchronization semaphore for the host backdrop brush - private static readonly SemaphoreSlim HostBackdropSemaphore = new SemaphoreSlim(1); - - // The cached host backdrop effect and partial pipeline to reuse - private static HostBackdropInstanceWrapper _HostBackdropCache; - - // The name to use for the host backdrop reference parameter - private const string HostBackdropReferenceParameterName = "HostBackdropBrush"; - - /// - /// Clears the internal cache of instances - /// - [PublicAPI] - public static async Task ClearCacheAsync(AcrylicEffectMode targets) - { - // In-app backdrop brush - if (targets.HasFlag(AcrylicEffectMode.InAppBlur)) - { - await BackdropSemaphore.WaitAsync(); - _BackdropInstance = null; - BackdropSemaphore.Release(); - } - - // Host backdrop brush - if (targets.HasFlag(AcrylicEffectMode.HostBackdrop)) - { - await HostBackdropSemaphore.WaitAsync(); - _HostBackdropCache = null; - HostBackdropSemaphore.Release(); - } - } - - #endregion - - /// - /// Initializes the appropriate acrylic effect for the current instance - /// - private async Task SetupEffectAsync() - { - // Designer check - if (DesignMode.DesignMode2Enabled) return; - - // Dictionary to track the reference and animatable parameters - IDictionary sourceParameters = new Dictionary(); - List animatableParameters = new List - { - TintColor1ParameterName, - TintColor2ParameterName, - ColorSourceParameterName - }; - - // Setup the base effect - IGraphicsEffectSource baseEffect; - if (Mode == AcrylicEffectMode.InAppBlur) - { - // Manage the cache - await BackdropSemaphore.WaitAsync(); - if (_BackdropInstance == null && !DispatchProtectionEnabled) - { - _BackdropInstance = Window.Current.Compositor.CreateBackdropBrush(); - } - - // Prepare the blur effect for the backdrop brush - baseEffect = new GaussianBlurEffect - { - Name = "Blur", - BlurAmount = 0f, // The blur value is inserted later on as it isn't applied correctly when set from here - BorderMode = EffectBorderMode.Hard, - Optimization = EffectOptimization.Balanced, - Source = new CompositionEffectSourceParameter(nameof(BackdropReferenceParameterName)) - }; - animatableParameters.Add(BlurAmountParameterName); - sourceParameters.Add(nameof(BackdropReferenceParameterName), - _BackdropInstance?.Dispatcher.HasThreadAccess == true - ? _BackdropInstance - : Window.Current.Compositor.CreateBackdropBrush()); // Create a new instance when on a secondary window - BackdropSemaphore.Release(); - } - else - { - // Manage the cache - await HostBackdropSemaphore.WaitAsync(); - if (_HostBackdropCache == null || // Cache not initialized yet - !_HostBackdropCache.Brush.Dispatcher.HasThreadAccess) // Cache initialized on another UI thread (different window) - { - // Prepare a luminosity to alpha effect to adjust the background contrast - CompositionBackdropBrush hostBackdropBrush = Window.Current.Compositor.CreateHostBackdropBrush(); - CompositionEffectSourceParameter backgroundParameter = new CompositionEffectSourceParameter(nameof(hostBackdropBrush)); - LuminanceToAlphaEffect alphaEffect = new LuminanceToAlphaEffect { Source = backgroundParameter }; - OpacityEffect opacityEffect = new OpacityEffect - { - Source = alphaEffect, - Opacity = 0.4f // Reduce the amount of the effect to avoid making bright areas completely black - }; - - // Layer [0,1,3] - Desktop background with blur and opacity mask - baseEffect = new BlendEffect - { - Background = backgroundParameter, - Foreground = opacityEffect, - Mode = BlendEffectMode.Multiply - }; - sourceParameters.Add(HostBackdropReferenceParameterName, hostBackdropBrush); - - // Update the cache when needed - if (_HostBackdropCache == null && !DispatchProtectionEnabled) - { - _HostBackdropCache = new HostBackdropInstanceWrapper(baseEffect, hostBackdropBrush); - } - } - else - { - // Reuse the cached pipeline and effect - baseEffect = _HostBackdropCache.Pipeline; - sourceParameters.Add(HostBackdropReferenceParameterName, _HostBackdropCache.Brush); - } - HostBackdropSemaphore.Release(); - } - - // Get the noise brush using Win2D - IGraphicsEffect source = await AcrylicEffectHelper.ConcatenateEffectWithTintAndBorderAsync(Window.Current.Compositor, - baseEffect, sourceParameters, Tint, (float)TintMix, null, NoiseTextureUri); - - // Extract and setup the tint and color effects - ArithmeticCompositeEffect tint = source as ArithmeticCompositeEffect ?? source.To().Background as ArithmeticCompositeEffect; - if (tint == null) throw new InvalidOperationException("Error while retrieving the tint effect"); - tint.Name = "Tint"; - if (!(tint.Source2 is ColorSourceEffect color)) throw new InvalidOperationException("Error while retrieving the color effect"); - color.Name = "ColorSource"; - - // Make sure the Win2D brush was loaded correctly - CompositionEffectFactory factory = Window.Current.Compositor.CreateEffectFactory(source, animatableParameters); - - // Create the effect factory and apply the final effect - _EffectBrush = factory.CreateBrush(); - foreach (KeyValuePair pair in sourceParameters) - { - _EffectBrush.SetSourceParameter(pair.Key, pair.Value); - } - - // Update the blur amount and store the effect - if (Mode == AcrylicEffectMode.InAppBlur) - _EffectBrush.Properties.InsertScalar(BlurAmountParameterName, (float)BlurAmount); - CompositionBrush = _EffectBrush; - _State = AcrylicBrushEffectState.EffectEnabled; - } - } -} diff --git a/UICompositionAnimations/Behaviours/Xaml/Effects/Abstract/ImageEffectBase.cs b/UICompositionAnimations/Brushes/Effects/Abstract/ImageEffectBase.cs similarity index 60% rename from UICompositionAnimations/Behaviours/Xaml/Effects/Abstract/ImageEffectBase.cs rename to UICompositionAnimations/Brushes/Effects/Abstract/ImageEffectBase.cs index 381ada6..f2cd40a 100644 --- a/UICompositionAnimations/Behaviours/Xaml/Effects/Abstract/ImageEffectBase.cs +++ b/UICompositionAnimations/Brushes/Effects/Abstract/ImageEffectBase.cs @@ -1,7 +1,8 @@ using System; +using UICompositionAnimations.Brushes.Effects.Interfaces; using UICompositionAnimations.Enums; -namespace UICompositionAnimations.Behaviours.Xaml.Effects.Abstract +namespace UICompositionAnimations.Brushes.Effects.Abstract { /// /// An image based effect that loads an image at the specified location @@ -14,13 +15,13 @@ public abstract class ImageEffectBase : IPipelineEffect public Uri Uri { get; set; } /// - /// Gets or sets the DPI mode used to render the image (the default is ) + /// Gets or sets the DPI mode used to render the image (the default is ) /// - public BitmapDPIMode DPIMode { get; set; } = BitmapDPIMode.CopyDisplayDPISettingsWith96AsLowerBound; + public DpiMode DPIMode { get; set; } = DpiMode.DisplayDpiWith96AsLowerBound; /// - /// Gets or sets the cache mode to use when loading the image (the default is ) + /// Gets or sets the cache mode to use when loading the image (the default is ) /// - public BitmapCacheMode CacheMode { get; set; } = BitmapCacheMode.Default; + public CacheMode CacheMode { get; set; } = CacheMode.Default; } } diff --git a/UICompositionAnimations/Behaviours/Xaml/Effects/Abstract/ValueEffectBase.cs b/UICompositionAnimations/Brushes/Effects/Abstract/ValueEffectBase.cs similarity index 75% rename from UICompositionAnimations/Behaviours/Xaml/Effects/Abstract/ValueEffectBase.cs rename to UICompositionAnimations/Brushes/Effects/Abstract/ValueEffectBase.cs index 63bfba1..6bcb00d 100644 --- a/UICompositionAnimations/Behaviours/Xaml/Effects/Abstract/ValueEffectBase.cs +++ b/UICompositionAnimations/Brushes/Effects/Abstract/ValueEffectBase.cs @@ -1,4 +1,6 @@ -namespace UICompositionAnimations.Behaviours.Xaml.Effects.Abstract +using UICompositionAnimations.Brushes.Effects.Interfaces; + +namespace UICompositionAnimations.Brushes.Effects.Abstract { /// /// A base for an effect that exposes a single parameter diff --git a/UICompositionAnimations/Behaviours/Xaml/Effects/BackdropEffect.cs b/UICompositionAnimations/Brushes/Effects/BackdropEffect.cs similarity index 78% rename from UICompositionAnimations/Behaviours/Xaml/Effects/BackdropEffect.cs rename to UICompositionAnimations/Brushes/Effects/BackdropEffect.cs index f614801..adefa5f 100644 --- a/UICompositionAnimations/Behaviours/Xaml/Effects/BackdropEffect.cs +++ b/UICompositionAnimations/Brushes/Effects/BackdropEffect.cs @@ -1,6 +1,7 @@ using Windows.UI.Xaml.Media; +using UICompositionAnimations.Brushes.Effects.Interfaces; -namespace UICompositionAnimations.Behaviours.Xaml.Effects +namespace UICompositionAnimations.Brushes.Effects { /// /// A backdrop effect that can sample from a specified source diff --git a/UICompositionAnimations/Behaviours/Xaml/Effects/BlendEffect.cs b/UICompositionAnimations/Brushes/Effects/BlendEffect.cs similarity index 78% rename from UICompositionAnimations/Behaviours/Xaml/Effects/BlendEffect.cs rename to UICompositionAnimations/Brushes/Effects/BlendEffect.cs index aafaf9c..397a8d3 100644 --- a/UICompositionAnimations/Behaviours/Xaml/Effects/BlendEffect.cs +++ b/UICompositionAnimations/Brushes/Effects/BlendEffect.cs @@ -1,9 +1,10 @@ using System.Collections.Generic; using JetBrains.Annotations; using Microsoft.Graphics.Canvas.Effects; +using UICompositionAnimations.Brushes.Effects.Interfaces; using UICompositionAnimations.Enums; -namespace UICompositionAnimations.Behaviours.Xaml.Effects +namespace UICompositionAnimations.Brushes.Effects { /// /// A blend effect that merges the current pipeline with an input one @@ -22,8 +23,8 @@ public sealed class BlendEffect : IPipelineEffect public BlendEffectMode Mode { get; set; } /// - /// Gets or sets the placement of the input pipeline with respect to the current one (the default is ) + /// Gets or sets the placement of the input pipeline with respect to the current one (the default is ) /// - public EffectPlacement Placement { get; set; } = EffectPlacement.Foreground; + public Placement Placement { get; set; } = Placement.Foreground; } } diff --git a/UICompositionAnimations/Behaviours/Xaml/Effects/BlurEffect.cs b/UICompositionAnimations/Brushes/Effects/BlurEffect.cs similarity index 50% rename from UICompositionAnimations/Behaviours/Xaml/Effects/BlurEffect.cs rename to UICompositionAnimations/Brushes/Effects/BlurEffect.cs index 5d25528..c9323f5 100644 --- a/UICompositionAnimations/Behaviours/Xaml/Effects/BlurEffect.cs +++ b/UICompositionAnimations/Brushes/Effects/BlurEffect.cs @@ -1,6 +1,6 @@ -using UICompositionAnimations.Behaviours.Xaml.Effects.Abstract; +using UICompositionAnimations.Brushes.Effects.Abstract; -namespace UICompositionAnimations.Behaviours.Xaml.Effects +namespace UICompositionAnimations.Brushes.Effects { /// /// A gaussian blur effect diff --git a/UICompositionAnimations/Behaviours/Xaml/Effects/ImageEffect.cs b/UICompositionAnimations/Brushes/Effects/ImageEffect.cs similarity index 58% rename from UICompositionAnimations/Behaviours/Xaml/Effects/ImageEffect.cs rename to UICompositionAnimations/Brushes/Effects/ImageEffect.cs index 5f159f3..8b6325b 100644 --- a/UICompositionAnimations/Behaviours/Xaml/Effects/ImageEffect.cs +++ b/UICompositionAnimations/Brushes/Effects/ImageEffect.cs @@ -1,6 +1,6 @@ -using UICompositionAnimations.Behaviours.Xaml.Effects.Abstract; +using UICompositionAnimations.Brushes.Effects.Abstract; -namespace UICompositionAnimations.Behaviours.Xaml.Effects +namespace UICompositionAnimations.Brushes.Effects { /// /// An image effect, which displays an image loaded as a Win2D surface diff --git a/UICompositionAnimations/Behaviours/Xaml/IPipelineEffect.cs b/UICompositionAnimations/Brushes/Effects/Interfaces/IPipelineEffect.cs similarity index 75% rename from UICompositionAnimations/Behaviours/Xaml/IPipelineEffect.cs rename to UICompositionAnimations/Brushes/Effects/Interfaces/IPipelineEffect.cs index 6f8c954..b1479ec 100644 --- a/UICompositionAnimations/Behaviours/Xaml/IPipelineEffect.cs +++ b/UICompositionAnimations/Brushes/Effects/Interfaces/IPipelineEffect.cs @@ -1,4 +1,4 @@ -namespace UICompositionAnimations.Behaviours.Xaml +namespace UICompositionAnimations.Brushes.Effects.Interfaces { /// /// The base for all the pipeline effects to be used in a diff --git a/UICompositionAnimations/Behaviours/Xaml/Effects/LuminanceEffect.cs b/UICompositionAnimations/Brushes/Effects/LuminanceEffect.cs similarity index 66% rename from UICompositionAnimations/Behaviours/Xaml/Effects/LuminanceEffect.cs rename to UICompositionAnimations/Brushes/Effects/LuminanceEffect.cs index f98cce9..5fb2002 100644 --- a/UICompositionAnimations/Behaviours/Xaml/Effects/LuminanceEffect.cs +++ b/UICompositionAnimations/Brushes/Effects/LuminanceEffect.cs @@ -1,4 +1,6 @@ -namespace UICompositionAnimations.Behaviours.Xaml.Effects +using UICompositionAnimations.Brushes.Effects.Interfaces; + +namespace UICompositionAnimations.Brushes.Effects { /// /// A luminance effect which directly replicates diff --git a/UICompositionAnimations/Behaviours/Xaml/Effects/OpacityEffect.cs b/UICompositionAnimations/Brushes/Effects/OpacityEffect.cs similarity index 51% rename from UICompositionAnimations/Behaviours/Xaml/Effects/OpacityEffect.cs rename to UICompositionAnimations/Brushes/Effects/OpacityEffect.cs index 04f49f9..ca5cc92 100644 --- a/UICompositionAnimations/Behaviours/Xaml/Effects/OpacityEffect.cs +++ b/UICompositionAnimations/Brushes/Effects/OpacityEffect.cs @@ -1,6 +1,6 @@ -using UICompositionAnimations.Behaviours.Xaml.Effects.Abstract; +using UICompositionAnimations.Brushes.Effects.Abstract; -namespace UICompositionAnimations.Behaviours.Xaml.Effects +namespace UICompositionAnimations.Brushes.Effects { /// /// A simple opacity effect diff --git a/UICompositionAnimations/Behaviours/Xaml/Effects/SaturationEffect.cs b/UICompositionAnimations/Brushes/Effects/SaturationEffect.cs similarity index 51% rename from UICompositionAnimations/Behaviours/Xaml/Effects/SaturationEffect.cs rename to UICompositionAnimations/Brushes/Effects/SaturationEffect.cs index 78a7efc..6141712 100644 --- a/UICompositionAnimations/Behaviours/Xaml/Effects/SaturationEffect.cs +++ b/UICompositionAnimations/Brushes/Effects/SaturationEffect.cs @@ -1,6 +1,6 @@ -using UICompositionAnimations.Behaviours.Xaml.Effects.Abstract; +using UICompositionAnimations.Brushes.Effects.Abstract; -namespace UICompositionAnimations.Behaviours.Xaml.Effects +namespace UICompositionAnimations.Brushes.Effects { /// /// A saturation effect diff --git a/UICompositionAnimations/Behaviours/Xaml/Effects/SolidColorEffect.cs b/UICompositionAnimations/Brushes/Effects/SolidColorEffect.cs similarity index 76% rename from UICompositionAnimations/Behaviours/Xaml/Effects/SolidColorEffect.cs rename to UICompositionAnimations/Brushes/Effects/SolidColorEffect.cs index 05a1003..fbb1284 100644 --- a/UICompositionAnimations/Behaviours/Xaml/Effects/SolidColorEffect.cs +++ b/UICompositionAnimations/Brushes/Effects/SolidColorEffect.cs @@ -1,6 +1,7 @@ using Windows.UI; +using UICompositionAnimations.Brushes.Effects.Interfaces; -namespace UICompositionAnimations.Behaviours.Xaml.Effects +namespace UICompositionAnimations.Brushes.Effects { /// /// A simple effect that renders a solid color on the available surface diff --git a/UICompositionAnimations/Behaviours/Xaml/Effects/TileEffect.cs b/UICompositionAnimations/Brushes/Effects/TileEffect.cs similarity index 60% rename from UICompositionAnimations/Behaviours/Xaml/Effects/TileEffect.cs rename to UICompositionAnimations/Brushes/Effects/TileEffect.cs index ca9d942..a4fd228 100644 --- a/UICompositionAnimations/Behaviours/Xaml/Effects/TileEffect.cs +++ b/UICompositionAnimations/Brushes/Effects/TileEffect.cs @@ -1,6 +1,6 @@ -using UICompositionAnimations.Behaviours.Xaml.Effects.Abstract; +using UICompositionAnimations.Brushes.Effects.Abstract; -namespace UICompositionAnimations.Behaviours.Xaml.Effects +namespace UICompositionAnimations.Brushes.Effects { /// /// An effect that loads an image and replicates it to cover all the available surface area diff --git a/UICompositionAnimations/Behaviours/Xaml/Effects/TintEffect.cs b/UICompositionAnimations/Brushes/Effects/TintEffect.cs similarity index 80% rename from UICompositionAnimations/Behaviours/Xaml/Effects/TintEffect.cs rename to UICompositionAnimations/Brushes/Effects/TintEffect.cs index 404c465..811de70 100644 --- a/UICompositionAnimations/Behaviours/Xaml/Effects/TintEffect.cs +++ b/UICompositionAnimations/Brushes/Effects/TintEffect.cs @@ -1,6 +1,7 @@ using Windows.UI; +using UICompositionAnimations.Brushes.Effects.Interfaces; -namespace UICompositionAnimations.Behaviours.Xaml.Effects +namespace UICompositionAnimations.Brushes.Effects { /// /// A tint effect with a customizable opacity diff --git a/UICompositionAnimations/Brushes/LightingBrush.cs b/UICompositionAnimations/Brushes/LightingBrush.cs deleted file mode 100644 index 0ce927b..0000000 --- a/UICompositionAnimations/Brushes/LightingBrush.cs +++ /dev/null @@ -1,136 +0,0 @@ -using Microsoft.Graphics.Canvas.Effects; -using Windows.UI.Composition; -using Windows.UI.Composition.Effects; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Media; -using UICompositionAnimations.Helpers; - -namespace UICompositionAnimations.Brushes -{ - /// - /// A custom XAML brush that includes a lighting effect - /// - public sealed class LightingBrush : XamlCompositionBrushBase - { - /// - /// Gets or sets the diffuse property for the brush - /// - public double DiffuseAmount - { - get => (double)GetValue(DiffuseAmountProperty); - set => SetValue(DiffuseAmountProperty, value); - } - - /// - /// Gets the for the property - /// - public static readonly DependencyProperty DiffuseAmountProperty = - DependencyProperty.Register(nameof(DiffuseAmount), typeof(double), typeof(LightingBrush), new PropertyMetadata(1d, OnDiffuseAmountChanged)); - - private static void OnDiffuseAmountChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - d.To()?.CompositionBrush?.Properties.InsertScalar("Light.DiffuseAmount", (float)(double)e.NewValue); - } - - - /// - /// Gets or sets the specular shine of the light. The default value is 16 and it must be between 1 and 128 - /// - public double SpecularShine - { - get => (double)GetValue(SpecularShineProperty); - set => SetValue(SpecularShineProperty, value); - } - - /// - /// Gets the for the property - /// - public static readonly DependencyProperty SpecularShineProperty = - DependencyProperty.Register(nameof(SpecularShine), typeof(double), typeof(LightingBrush), new PropertyMetadata(16d, OnSpecularShineChanged)); - - private static void OnSpecularShineChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - d.To()?.CompositionBrush?.Properties.InsertScalar("Light.SpecularShine", (float)(double)e.NewValue); - } - - /// - /// Gets or sets the specular amount for the effect - /// - public double SpecularAmount - { - get => (double)GetValue(SpecularAmountProperty); - set => SetValue(SpecularAmountProperty, value); - } - - /// - /// Gets the for the property - /// - public static readonly DependencyProperty SpecularAmountProperty = - DependencyProperty.Register(nameof(SpecularAmount), typeof(double), typeof(LightingBrush), new PropertyMetadata(1d, OnSpecularAmountChanged)); - - private static void OnSpecularAmountChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - d.To()?.CompositionBrush?.Properties.InsertScalar("Light.SpecularAmount", (float)(double)e.NewValue); - } - - // The effect brush to use - CompositionEffectBrush _Brush; - - // The factory to create the brush - CompositionEffectFactory _Factory; - - // The color transformation effect to convert luminance to opacity - ColorMatrixEffect _ColorMatrixEffect; - - /// - protected override void OnConnected() - { - if (CompositionBrush == null) - { - // Effects setup - SceneLightingEffect sceneLightingEffect = new SceneLightingEffect // Base lighting effect - { - Name = "Light", - SpecularShine = (float)SpecularShine, - SpecularAmount = (float)SpecularAmount, - DiffuseAmount = (float)DiffuseAmount, - AmbientAmount = 0 - }; - - // Setup the color matrix effect to map the luminosity - _ColorMatrixEffect = new ColorMatrixEffect - { - Source = sceneLightingEffect, - ColorMatrix = new Matrix5x4 - { - M11 = 0, M21 = 0, M31 = 0, M41 = 0, M51 = 1, - M12 = 0, M22 = 0, M32 = 0, M42 = 0, M52 = 1, - M13 = 0, M23 = 0, M33 = 0, M43 = 0, M53 = 1, - M14 = 0.2125f, M24 = 0.7154f, M34 = 0.0721f, M44 = 0, M54 = 0 - } - }; - - // Initialize the factory - _Factory = Window.Current.Compositor.CreateEffectFactory(_ColorMatrixEffect, new[] { "Light.DiffuseAmount", "Light.SpecularShine", "Light.SpecularAmount" }); - - // Create and store the brush - _Brush = _Factory.CreateBrush(); - CompositionBrush = _Brush; - } - base.OnConnected(); - } - - /// - protected override void OnDisconnected() - { - if (CompositionBrush != null) - { - _Brush?.Dispose(); - _Factory?.Dispose(); - _ColorMatrixEffect?.Dispose(); - CompositionBrush = null; - } - base.OnDisconnected(); - } - } -} diff --git a/UICompositionAnimations/Brushes/NoiseTextureBrush.cs b/UICompositionAnimations/Brushes/NoiseTextureBrush.cs index 254ac34..1b9e4e8 100644 --- a/UICompositionAnimations/Brushes/NoiseTextureBrush.cs +++ b/UICompositionAnimations/Brushes/NoiseTextureBrush.cs @@ -1,130 +1,21 @@ using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using Windows.Graphics.Effects; -using Windows.UI; -using Microsoft.Graphics.Canvas.Effects; -using Windows.UI.Composition; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Media; using UICompositionAnimations.Behaviours; -using UICompositionAnimations.Helpers; -using Windows.ApplicationModel; +using UICompositionAnimations.Brushes.Base; namespace UICompositionAnimations.Brushes { /// - /// A custom XAML brush that overlays a noise texture over a tint color + /// A that displays a tiled noise texture /// - public sealed class NoiseTextureBrush : XamlCompositionBrushBase + public sealed class NoiseTextureBrush : XamlCompositionEffectBrushBase { - // The name of the animatable color property of the color effect - private const string ColorSourceParameterName = "ColorSource.Color"; - - #region Properties - - /// - /// Gets or sets the color for the tint effect - /// - public Color Tint - { - get => GetValue(TintProperty).To(); - set => SetValue(TintProperty, value); - } - - /// - /// Gets the for the property - /// - public static readonly DependencyProperty TintProperty = - DependencyProperty.Register(nameof(Tint), typeof(Color), typeof(NoiseTextureBrush), new PropertyMetadata(Colors.Transparent, OnTintPropertyChanged)); - - private static async void OnTintPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - NoiseTextureBrush @this = d.To(); - await @this.ConnectedSemaphore.WaitAsync(); - if (@this.CompositionBrush != null) - { - @this._EffectBrush?.Properties.InsertColor(ColorSourceParameterName, e.NewValue.To()); - } - @this.ConnectedSemaphore.Release(); - } - /// /// Gets or sets the to the texture to use /// /// This property must be initialized before using the brush public Uri TextureUri { get; set; } - #endregion - - // Initialization semaphore (due to the Win2D image loading being asynchronous) - private readonly SemaphoreSlim ConnectedSemaphore = new SemaphoreSlim(1); - - // The composition brush used to render the effect - private CompositionEffectBrush _EffectBrush; - - /// - protected override async void OnConnected() - { - if (CompositionBrush == null) - { - await ConnectedSemaphore.WaitAsync(); - if (CompositionBrush == null) // It could have been initialized while waiting on the semaphore - { - await SetupEffectAsync(); - } - ConnectedSemaphore.Release(); - } - base.OnConnected(); - } - /// - protected override async void OnDisconnected() - { - if (CompositionBrush != null) - { - await ConnectedSemaphore.WaitAsync(); - if (CompositionBrush != null) - { - CompositionBrush.Dispose(); - CompositionBrush = null; - } - ConnectedSemaphore.Release(); - } - base.OnDisconnected(); - } - - /// - /// Initializes the appropriate effect for the current instance - /// - private async Task SetupEffectAsync() - { - // Designer check - if (DesignMode.DesignMode2Enabled) return; - - // Dictionary to track the reference and animatable parameters - IDictionary sourceParameters = new Dictionary(); - List animatableParameters = new List { ColorSourceParameterName }; - - // Get the noise brush using Win2D - IGraphicsEffect source = await AcrylicEffectHelper.LoadTextureEffectWithTintAsync(Window.Current.Compositor, sourceParameters, Tint, TextureUri); - - // Extract and setup the tint and color effects - ColorSourceEffect color = source as ColorSourceEffect ?? source.To().Background as ColorSourceEffect; - if (color == null) throw new InvalidOperationException("Error while retrieving the color effect"); - color.Name = "ColorSource"; - - // Make sure the Win2D brush was loaded correctly - CompositionEffectFactory factory = Window.Current.Compositor.CreateEffectFactory(source, animatableParameters); - - // Create the effect factory and apply the final effect - _EffectBrush = factory.CreateBrush(); - foreach (KeyValuePair pair in sourceParameters) - { - _EffectBrush.SetSourceParameter(pair.Key, pair.Value); - } - CompositionBrush = _EffectBrush; - } + protected override PipelineBuilder OnBrushRequested() => PipelineBuilder.FromTiles(TextureUri); } } diff --git a/UICompositionAnimations/Behaviours/Xaml/PipelineBrush.cs b/UICompositionAnimations/Brushes/PipelineBrush.cs similarity index 72% rename from UICompositionAnimations/Behaviours/Xaml/PipelineBrush.cs rename to UICompositionAnimations/Brushes/PipelineBrush.cs index 8bed0eb..0f33992 100644 --- a/UICompositionAnimations/Behaviours/Xaml/PipelineBrush.cs +++ b/UICompositionAnimations/Brushes/PipelineBrush.cs @@ -3,11 +3,13 @@ using System.Linq; using Windows.UI.Xaml.Media; using JetBrains.Annotations; -using UICompositionAnimations.Behaviours.Xaml.Effects; +using UICompositionAnimations.Behaviours; using UICompositionAnimations.Brushes.Base; +using UICompositionAnimations.Brushes.Effects; +using UICompositionAnimations.Brushes.Effects.Interfaces; using LuminanceToAlphaEffect = Microsoft.Graphics.Canvas.Effects.LuminanceToAlphaEffect; -namespace UICompositionAnimations.Behaviours.Xaml +namespace UICompositionAnimations.Brushes { /// /// A that renders a customizable Composition/Win2D effects pipeline @@ -15,26 +17,26 @@ namespace UICompositionAnimations.Behaviours.Xaml public sealed class PipelineBrush : XamlCompositionEffectBrushBase { /// - protected override CompositionBrushBuilder OnBrushRequested() + protected override PipelineBuilder OnBrushRequested() { // Starts a new composition pipeline from the given effect - CompositionBrushBuilder Start(IPipelineEffect effect) + PipelineBuilder Start(IPipelineEffect effect) { switch (effect) { case BackdropEffect backdrop when backdrop.Source == AcrylicBackgroundSource.Backdrop: - return CompositionBrushBuilder.FromBackdropBrush(); + return PipelineBuilder.FromBackdropBrush(); case BackdropEffect backdrop when backdrop.Source == AcrylicBackgroundSource.HostBackdrop: - return CompositionBrushBuilder.FromHostBackdropBrush(); - case SolidColorEffect color: return CompositionBrushBuilder.FromColor(color.Color); - case ImageEffect image: return CompositionBrushBuilder.FromImage(image.Uri, image.DPIMode, image.CacheMode); - case TileEffect tile: return CompositionBrushBuilder.FromTiles(tile.Uri, tile.DPIMode, tile.CacheMode); + return PipelineBuilder.FromHostBackdropBrush(); + case SolidColorEffect color: return PipelineBuilder.FromColor(color.Color); + case ImageEffect image: return PipelineBuilder.FromImage(image.Uri, image.DPIMode, image.CacheMode); + case TileEffect tile: return PipelineBuilder.FromTiles(tile.Uri, tile.DPIMode, tile.CacheMode); default: throw new ArgumentException($"Invalid initial pipeline effect: {effect.GetType()}"); } } // Appends an effect to an existing composition pipeline - CompositionBrushBuilder Append(IPipelineEffect effect, CompositionBrushBuilder builder) + PipelineBuilder Append(IPipelineEffect effect, PipelineBuilder builder) { switch (effect) { @@ -49,7 +51,7 @@ CompositionBrushBuilder Append(IPipelineEffect effect, CompositionBrushBuilder b } // Builds a new effects pipeline from the input effects sequence - CompositionBrushBuilder Build(IList effects) + PipelineBuilder Build(IList effects) { if (effects.Count == 0) throw new ArgumentException("An effects pipeline can't be empty"); return effects.Skip(1).Aggregate(Start(effects[0]), (b, e) => Append(e, b)); diff --git a/UICompositionAnimations/Brushes/XamlCompositionBrush.cs b/UICompositionAnimations/Brushes/XamlCompositionBrush.cs index 37a3173..4350496 100644 --- a/UICompositionAnimations/Brushes/XamlCompositionBrush.cs +++ b/UICompositionAnimations/Brushes/XamlCompositionBrush.cs @@ -14,21 +14,21 @@ namespace UICompositionAnimations.Brushes public delegate Task XamlEffectAnimation(float value, int ms); /// - /// A simple that can be used to quickly create XAML brushes from arbitrary pipelines + /// A simple that can be used to quickly create XAML brushes from arbitrary pipelines /// public sealed class XamlCompositionBrush : XamlCompositionEffectBrushBase { /// - /// Gets the pipeline for the current instance + /// Gets the pipeline for the current instance /// [NotNull] - public CompositionBrushBuilder Pipeline { get; } + public PipelineBuilder Pipeline { get; } /// /// Creates a new XAML brush from the input effects pipeline /// - /// The instance to create the effect - public XamlCompositionBrush([NotNull] CompositionBrushBuilder pipeline) => Pipeline = pipeline; + /// The instance to create the effect + public XamlCompositionBrush([NotNull] PipelineBuilder pipeline) => Pipeline = pipeline; /// /// Binds an to the composition brush in the current instance @@ -43,7 +43,7 @@ public XamlCompositionBrush Bind([NotNull] EffectAnimation animation, out XamlEf } /// - protected override CompositionBrushBuilder OnBrushRequested() => Pipeline; + protected override PipelineBuilder OnBrushRequested() => Pipeline; /// /// Clones the current instance by rebuilding the source . Use this method to reuse the same effects pipeline on a different diff --git a/UICompositionAnimations/Composition/CompositionManager.cs b/UICompositionAnimations/Composition/CompositionManager.cs deleted file mode 100644 index 4cba52e..0000000 --- a/UICompositionAnimations/Composition/CompositionManager.cs +++ /dev/null @@ -1,279 +0,0 @@ -using System; -using System.Numerics; -using Windows.UI.Composition; -using Windows.UI.Xaml; -using JetBrains.Annotations; - -namespace UICompositionAnimations.Composition -{ - /// - /// Create composition animations using this class - /// - [PublicAPI] - public static class CompositionManager - { - #region Animations initialization - - /// - /// Creates and starts a scalar animation on the target element - /// - /// The element to animate - /// The path that identifies the property to animate - /// The optional starting value for the animation - /// The final value for the animation - /// The animation duration - /// The optional initial delay for the animation - /// The optional easing function for the animation - public static void BeginScalarAnimation([NotNull] UIElement element, [NotNull] string propertyPath, - float? from, float to, TimeSpan duration, TimeSpan? delay, [CanBeNull] CompositionEasingFunction ease = null) - { - element.GetVisual().BeginScalarAnimation(propertyPath, from, to, duration, delay, ease); - } - - /// - /// Creates and starts a animation on the target element - /// - /// The element to animate - /// The path that identifies the property to animate - /// The optional starting value for the animation - /// The final value for the animation - /// The animation duration - /// The optional initial delay for the animation - /// The optional easing function for the animation - public static void BeginVector2Animation([NotNull] UIElement element, [NotNull] string propertyPath, - Vector2? from, Vector2 to, TimeSpan duration, TimeSpan? delay, [CanBeNull] CompositionEasingFunction ease = null) - { - element.GetVisual().BeginVector2Animation(propertyPath, from, to, duration, delay, ease); - } - - /// - /// Creates and starts a animation on the target element - /// - /// The element to animate - /// The path that identifies the property to animate - /// The optional starting value for the animation - /// The final value for the animation - /// The animation duration - /// The optional initial delay for the animation - /// The optional easing function for the animation - public static void BeginVector3Animation([NotNull] UIElement element, [NotNull] string propertyPath, - Vector3? from, Vector3 to, TimeSpan duration, TimeSpan? delay, [CanBeNull] CompositionEasingFunction ease = null) - { - element.GetVisual().BeginVector3Animation(propertyPath, from, to, duration, delay, ease); - } - - /// - /// Creates and starts a scalar animation on the current - /// - /// The target to animate - /// The path that identifies the property to animate - /// The optional starting value for the animation - /// The final value for the animation - /// The animation duration - /// The optional initial delay for the animation - /// The optional easing function for the animation - public static void BeginScalarAnimation([NotNull] this CompositionObject compObj, [NotNull] string propertyPath, - float? from, float to, TimeSpan duration, TimeSpan? delay, [CanBeNull] CompositionEasingFunction ease = null) - { - compObj.StartAnimation(propertyPath, compObj.Compositor.CreateScalarKeyFrameAnimation(from, to, duration, delay, ease)); - } - - /// - /// Creates and starts a animation on the current - /// - /// The target to animate - /// The path that identifies the property to animate - /// The optional starting value for the animation - /// The final value for the animation - /// The animation duration - /// The optional initial delay for the animation - /// The optional easing function for the animation - public static void BeginVector2Animation([NotNull]this CompositionObject compObj, [NotNull] string propertyPath, - Vector2? from, Vector2 to, TimeSpan duration, TimeSpan? delay, [CanBeNull] CompositionEasingFunction ease = null) - { - compObj.StartAnimation(propertyPath, compObj.Compositor.CreateVector2KeyFrameAnimation(from, to, duration, delay, ease)); - } - - /// - /// Creates and starts a animation on the current - /// - /// The target to animate - /// The path that identifies the property to animate - /// The optional starting value for the animation - /// The final value for the animation - /// The animation duration - /// The optional initial delay for the animation - /// The optional easing function for the animation - public static void BeginVector3Animation([NotNull] this CompositionObject compObj, [NotNull] string propertyPath, - Vector3? from, Vector3 to, TimeSpan duration, TimeSpan? delay, [CanBeNull] CompositionEasingFunction ease = null) - { - compObj.StartAnimation(propertyPath, compObj.Compositor.CreateVector3KeyFrameAnimation(from, to, duration, delay, ease)); - } - - #endregion - - #region KeyFrame animations - - /// - /// Creates a instance with the given parameters to on a target element - /// - /// The current instance used to create the animation - /// The optional starting value for the animation - /// The final value for the animation - /// The animation duration - /// The optional initial delay for the animation - /// The optional easing function for the animation - [Pure, NotNull] - public static ScalarKeyFrameAnimation CreateScalarKeyFrameAnimation([NotNull] this Compositor compositor, - float? from, float to, TimeSpan duration, TimeSpan? delay, [CanBeNull] CompositionEasingFunction ease = null) - { - // Set duration and delay time - ScalarKeyFrameAnimation ani = compositor.CreateScalarKeyFrameAnimation(); - ani.Duration = duration; - if (delay.HasValue) ani.DelayTime = delay.Value; - - // Insert "to" and "from" keyframes - ani.InsertKeyFrame(1, to, ease ?? compositor.CreateLinearEasingFunction()); - if (from.HasValue) ani.InsertKeyFrame(0, from.Value); - return ani; - } - - /// - /// Creates a instance with the given parameters to on a target element, using an expression animation - /// - /// The current instance used to create the animation - /// The optional starting value for the animation - /// A string that indicates the final value for the animation - /// The animation duration - /// The optional initial delay for the animation - /// The optional easing function for the animation - [Pure, NotNull] - public static ScalarKeyFrameAnimation CreateScalarKeyFrameAnimation([NotNull] this Compositor compositor, - float? from, [NotNull] string to, TimeSpan duration, TimeSpan? delay, [CanBeNull] CompositionEasingFunction ease = null) - { - // Set duration and delay time - ScalarKeyFrameAnimation ani = compositor.CreateScalarKeyFrameAnimation(); - ani.Duration = duration; - if (delay.HasValue) ani.DelayTime = delay.Value; - - // Insert "to" and "from" keyframes - ani.InsertExpressionKeyFrame(1, to, ease ?? compositor.CreateLinearEasingFunction()); - if (from.HasValue) ani.InsertKeyFrame(0, from.Value); - return ani; - } - - /// - /// Creates a instance with the given parameters to on a target element - /// - /// The current instance used to create the animation - /// The optional starting value for the animation - /// The final value for the animation - /// The animation duration - /// The optional initial delay for the animation - /// The optional easing function for the animation - [Pure, NotNull] - public static Vector2KeyFrameAnimation CreateVector2KeyFrameAnimation([NotNull] this Compositor compositor, - Vector2? from, Vector2 to, TimeSpan duration, TimeSpan? delay, [CanBeNull] CompositionEasingFunction ease = null) - { - // Set duration and delay time - Vector2KeyFrameAnimation ani = compositor.CreateVector2KeyFrameAnimation(); - ani.Duration = duration; - if (delay.HasValue) ani.DelayTime = delay.Value; - - // Insert "to" and "from" keyframes - ani.InsertKeyFrame(1, to, ease ?? compositor.CreateLinearEasingFunction()); - if (from.HasValue) ani.InsertKeyFrame(0, from.Value); - return ani; - } - - /// - /// Creates a instance with the given parameters to on a target element, using an expression animation - /// - /// The current instance used to create the animation - /// The optional starting value for the animation - /// A string that indicates the final value for the animation - /// The animation duration - /// The optional initial delay for the animation - /// The optional easing function for the animation - [Pure, NotNull] - public static Vector2KeyFrameAnimation CreateVector2KeyFrameAnimation([NotNull] this Compositor compositor, - Vector2? from, [NotNull] string to, TimeSpan duration, TimeSpan? delay, [CanBeNull] CompositionEasingFunction ease = null) - { - // Set duration and delay time - Vector2KeyFrameAnimation ani = compositor.CreateVector2KeyFrameAnimation(); - ani.Duration = duration; - if (delay.HasValue) ani.DelayTime = delay.Value; - - // Insert "to" and "from" keyframes - ani.InsertExpressionKeyFrame(1, to, ease ?? compositor.CreateLinearEasingFunction()); - if (from.HasValue) ani.InsertKeyFrame(0, from.Value); - return ani; - } - - /// - /// Creates a instance with the given parameters to on a target element - /// - /// The current instance used to create the animation - /// The optional starting value for the animation - /// The final value for the animation - /// The animation duration - /// The optional initial delay for the animation - /// The optional easing function for the animation - [Pure, NotNull] - public static Vector3KeyFrameAnimation CreateVector3KeyFrameAnimation([NotNull] this Compositor compositor, - Vector3? from, Vector3 to, TimeSpan duration, TimeSpan? delay, [CanBeNull] CompositionEasingFunction ease = null) - { - // Set duration and delay time - Vector3KeyFrameAnimation ani = compositor.CreateVector3KeyFrameAnimation(); - ani.Duration = duration; - if (delay.HasValue) ani.DelayTime = delay.Value; - - // Insert "to" and "from" keyframes - ani.InsertKeyFrame(1, to, ease ?? compositor.CreateLinearEasingFunction()); - if (from.HasValue) ani.InsertKeyFrame(0, from.Value); - return ani; - } - - /// - /// Creates a instance with the given parameters to on a target element, using an expression animation - /// - /// The current instance used to create the animation - /// The optional starting value for the animation - /// A string that indicates the final value for the animation - /// The animation duration - /// The optional initial delay for the animation - /// The optional easing function for the animation - [Pure, NotNull] - public static Vector3KeyFrameAnimation CreateVector3KeyFrameAnimation([NotNull] this Compositor compositor, - Vector3? from, [NotNull] string to, TimeSpan duration, TimeSpan? delay, [CanBeNull] CompositionEasingFunction ease = null) - { - // Set duration and delay time - Vector3KeyFrameAnimation ani = compositor.CreateVector3KeyFrameAnimation(); - ani.Duration = duration; - if (delay.HasValue) ani.DelayTime = delay.Value; - - // Insert "to" and "from" keyframes - ani.InsertExpressionKeyFrame(1, to, ease ?? compositor.CreateLinearEasingFunction()); - if (from.HasValue) ani.InsertKeyFrame(0, from.Value); - return ani; - } - - /// - /// Creates a instance with the given parameters to on a target element, using an expression animation - /// - /// The current instance used to create the animation - /// The optional starting value for the animation - /// The final value for the animation - /// The animation duration - /// The optional initial delay for the animation - /// The optional easing function for the animation - [Pure, NotNull] - public static CompositionAnimation CreateMatrix4x4KeyFrameAnimation([NotNull] this Compositor compositor, - Matrix4x4? from, Matrix4x4 to, TimeSpan duration, TimeSpan? delay, [CanBeNull] CompositionEasingFunction ease = null) - { - throw new NotImplementedException(); // TODO - } - - #endregion - } -} diff --git a/UICompositionAnimations/Composition/CubicBeizerEasingProvider.cs b/UICompositionAnimations/Composition/CubicBeizerEasingProvider.cs deleted file mode 100644 index c0c1fce..0000000 --- a/UICompositionAnimations/Composition/CubicBeizerEasingProvider.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using System.Numerics; -using Windows.UI.Composition; -using JetBrains.Annotations; -using UICompositionAnimations.Enums; - -namespace UICompositionAnimations.Composition -{ - /// - /// A static class that generates a CubicBeizer curve from an input easing function name - /// - internal static class CubicBeizerEasingProvider - { - /// - /// Creates a from the input control points - /// - /// The source object used to create the easing function - /// The X coordinate of the first control point - /// The Y coordinate of the first control point - /// The X coordinate of the second control point - /// The Y coordinate of the second control point - [Pure, NotNull] - public static CubicBezierEasingFunction GetEasingFunction([NotNull] this CompositionObject compObject, float x1, float y1, float x2, float y2) - { - return compObject.Compositor.CreateCubicBezierEasingFunction(new Vector2 { X = x1, Y = y1 }, new Vector2 { X = x2, Y = y2 }); - } - - /// - /// Creates the appropriate from the given easing function name - /// - /// The source object used to create the easing function - /// The target easing function to create - [Pure, NotNull] - public static CubicBezierEasingFunction GetEasingFunction([NotNull] this CompositionObject compObject, EasingFunctionNames ease) - { - switch (ease) - { - case EasingFunctionNames.Linear: return compObject.GetEasingFunction(0, 0, 1, 1); - case EasingFunctionNames.SineEaseIn: return compObject.GetEasingFunction(0.4f, 0, 1, 1); - case EasingFunctionNames.SineEaseOut: return compObject.GetEasingFunction(0, 0, 0.6f, 1); - case EasingFunctionNames.SineEaseInOut: return compObject.GetEasingFunction(0.4f, 0, 0.6f, 1); - case EasingFunctionNames.QuadraticEaseIn: return compObject.GetEasingFunction(0.8f, 0, 1, 1); - case EasingFunctionNames.QuadraticEaseOut: return compObject.GetEasingFunction(0, 0, 0.2f, 1); - case EasingFunctionNames.QuadraticEaseInOut: return compObject.GetEasingFunction(0.8f, 0, 0.2f, 1); - case EasingFunctionNames.CircleEaseIn: return compObject.GetEasingFunction(1, 0, 1, 0.8f); - case EasingFunctionNames.CircleEaseOut: return compObject.GetEasingFunction(0, 0.3f, 0, 1); - case EasingFunctionNames.CircleEaseInOut: return compObject.GetEasingFunction(0.9f, 0, 0.1f, 1); - default: throw new ArgumentOutOfRangeException(nameof(ease), ease, "This shouldn't happen"); - } - } - } -} \ No newline at end of file diff --git a/UICompositionAnimations/Composition/Misc/CompositeRotationAnimationStartInfo.cs b/UICompositionAnimations/Composition/Misc/CompositeRotationAnimationStartInfo.cs deleted file mode 100644 index 0d79c3a..0000000 --- a/UICompositionAnimations/Composition/Misc/CompositeRotationAnimationStartInfo.cs +++ /dev/null @@ -1,30 +0,0 @@ -namespace UICompositionAnimations.Composition.Misc -{ - /// - /// A simple struct that keeps track of the initial values for a rotation animation - /// - internal struct CompositeRotationAnimationStartInfo - { - /// - /// Gets the initial animation opacity - /// - public float Opacity { get; } - - /// - /// Gets the initial value of the secondary animation parameter (either offset or scale) - /// - public float SecondaryProperty { get; } - - /// - /// Gets the initial animation degrees property - /// - public float Degrees { get; } - - public CompositeRotationAnimationStartInfo(float opacity, float property, float degrees) - { - Opacity = opacity; - SecondaryProperty = property; - Degrees = degrees; - } - } -} \ No newline at end of file diff --git a/UICompositionAnimations/CompositionExtensions.cs b/UICompositionAnimations/CompositionExtensions.cs deleted file mode 100644 index c23ffe5..0000000 --- a/UICompositionAnimations/CompositionExtensions.cs +++ /dev/null @@ -1,2089 +0,0 @@ -using System; -using System.Threading.Tasks; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Hosting; -using System.Numerics; -using Windows.Foundation; -using Windows.UI; -using Windows.UI.Composition; -using Windows.UI.Core; -using Windows.UI.Xaml.Controls; -using JetBrains.Annotations; -using UICompositionAnimations.Composition; -using UICompositionAnimations.Composition.Misc; -using UICompositionAnimations.Enums; -using UICompositionAnimations.Helpers; -using Windows.UI.Xaml.Shapes; - -namespace UICompositionAnimations -{ - /// - /// A static class that wraps the animation methods in the Windows.UI.Composition namespace - /// - [PublicAPI] - public static class CompositionExtensions - { - #region Internal tools - - /// - /// Sets the property of a object to the center of a given - /// - /// The source element - /// The Visual object for the source - private static void SetFixedCenterPoint([NotNull] this FrameworkElement element, [NotNull] Visual visual) - { - if (double.IsNaN(element.Width) || double.IsNaN(element.Height)) - throw new InvalidOperationException("The target element must have a fixed size"); - visual.CenterPoint = new Vector3((float)element.Width / 2, (float)element.Height / 2, 0); - } - - /// - /// Sets the property of a object to the center of a given - /// - /// The source element - /// The Visual object for the source - private static async Task SetCenterPointAsync([NotNull] this FrameworkElement element, [NotNull] Visual visual) - { - // Check if the control hasn't already been loaded - bool CheckLoadingPending() => element.ActualWidth + element.ActualHeight < 0.1; - if (CheckLoadingPending()) - { - // Wait for the loaded event and set the CenterPoint - TaskCompletionSource loadedTcs = new TaskCompletionSource(); - void Handler(object s, RoutedEventArgs e) - { - loadedTcs.SetResult(null); - element.Loaded -= Handler; - } - - // Wait for the loaded event for a given time threshold - element.Loaded += Handler; - await Task.WhenAny(loadedTcs.Task, Task.Delay(500)); - element.Loaded -= Handler; - - // If the control still hasn't been loaded, approximate the center point with its desired size - if (CheckLoadingPending()) - { - element.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); - visual.CenterPoint = new Vector3((float)element.DesiredSize.Width / 2, (float)element.DesiredSize.Height / 2, 0); - return; - } - } - - // Update the center point - visual.CenterPoint = new Vector3((float)element.ActualWidth / 2, (float)element.ActualHeight / 2, 0); - } - - #endregion - - #region Fade - - // Manages the fade animation - private static Task ManageCompositionFadeAnimationAsync([NotNull] Visual visual, - float? startOp, float endOp, - int ms, int? msDelay, [NotNull] CompositionEasingFunction easingFunction) - { - // Get the default values - visual.StopAnimation("Opacity"); - if (!startOp.HasValue) startOp = visual.Opacity; - - // Get the easing function, the duration and delay - TimeSpan duration = TimeSpan.FromMilliseconds(ms); - TimeSpan? delay; - if (msDelay.HasValue) delay = TimeSpan.FromMilliseconds(msDelay.Value); - else delay = null; - - // Get the opacity animation - ScalarKeyFrameAnimation opacityAnimation = visual.Compositor.CreateScalarKeyFrameAnimation(startOp, endOp, duration, delay, easingFunction); - - // Close the batch and manage its event - CompositionScopedBatch batch = visual.Compositor.CreateScopedBatch(CompositionBatchTypes.Animation); - TaskCompletionSource tcs = new TaskCompletionSource(); - batch.Completed += (s, e) => tcs.SetResult(null); - visual.StartAnimation("Opacity", opacityAnimation); - batch.End(); - return tcs.Task; - } - - /// - /// Starts a fade animation on the target and optionally runs a callback when the animation finishes - /// - /// The to animate - /// The initial opacity value. If null, the current opacity will be used - /// The final opacity value - /// The duration of the animation, in milliseconds - /// The delay before the animation starts, in milliseconds. If null, there will be no delay - /// The easing function to use with the new animations - /// An to execute when the new animations end - public static async void StartCompositionFadeAnimation([NotNull] this UIElement element, - float? startOp, float endOp, - int ms, int? msDelay, EasingFunctionNames easingFunction, Action callback = null) - { - await StartCompositionFadeAnimationAsync(element, startOp, endOp, ms, msDelay, easingFunction); - callback?.Invoke(); - } - - /// - /// Starts a fade animation on the target and optionally runs a callback when the animation finishes - /// - /// The to animate - /// The initial opacity value. If null, the current opacity will be used - /// The final opacity value - /// The duration of the animation, in milliseconds - /// The delay before the animation starts, in milliseconds. If null, there will be no delay - /// The X coordinate of the first control point of the cubic beizer easing function - /// The Y coordinate of the first control point of the cubic beizer easing function - /// The X coordinate of the second control point of the cubic beizer easing function - /// The Y coordinate of the second control point of the cubic beizer easing function - /// An to execute when the new animations end - public static async void StartCompositionFadeAnimation([NotNull] this UIElement element, - float? startOp, float endOp, - int ms, int? msDelay, float x1, float y1, float x2, float y2, Action callback = null) - { - await StartCompositionFadeAnimationAsync(element, startOp, endOp, ms, msDelay, x1,y1, x2, y2); - callback?.Invoke(); - } - - /// - /// Starts a fade animation on the target and returns a that completes when the animation ends - /// - /// The to animate - /// The initial opacity value. If null, the current opacity will be used - /// The final opacity value - /// The duration of the animation, in milliseconds - /// The delay before the animation starts, in milliseconds. If null, there will be no delay - /// The easing function to use with the new animations - public static Task StartCompositionFadeAnimationAsync([NotNull] this UIElement element, - float? startOp, float endOp, - int ms, int? msDelay, EasingFunctionNames easingFunction) - { - Visual visual = element.GetVisual(); - CompositionEasingFunction ease = visual.GetEasingFunction(easingFunction); - return ManageCompositionFadeAnimationAsync(visual, startOp, endOp, ms, msDelay, ease); - } - - /// - /// Starts a fade animation on the target and returns a that completes when the animation ends - /// - /// The to animate - /// The initial opacity value. If null, the current opacity will be used - /// The final opacity value - /// The duration of the animation, in milliseconds - /// The delay before the animation starts, in milliseconds. If null, there will be no delay - /// The X coordinate of the first control point of the cubic beizer easing function - /// The Y coordinate of the first control point of the cubic beizer easing function - /// The X coordinate of the second control point of the cubic beizer easing function - /// The Y coordinate of the second control point of the cubic beizer easing function - public static Task StartCompositionFadeAnimationAsync([NotNull] this UIElement element, - float? startOp, float endOp, - int ms, int? msDelay, float x1, float y1, float x2, float y2) - { - Visual visual = element.GetVisual(); - CompositionEasingFunction ease = visual.GetEasingFunction(x1, y1, x2, y2); - return ManageCompositionFadeAnimationAsync(visual, startOp, endOp, ms, msDelay, ease); - } - - // Sets an implicit fade animation on the target element - private static void SetCompositionFadeImplicitAnimation([NotNull] UIElement element, [NotNull] Compositor compositor, ImplicitAnimationType type, - float start, float end, - int ms, int? msDelay, [NotNull] CompositionEasingFunction easingFunction) - { - // Get the easing function, the duration and delay - TimeSpan duration = TimeSpan.FromMilliseconds(ms); - TimeSpan? delay; - if (msDelay.HasValue) delay = TimeSpan.FromMilliseconds(msDelay.Value); - else delay = null; - - // Get the opacity animation - CompositionAnimationGroup group = compositor.CreateAnimationGroup(); - ScalarKeyFrameAnimation opacityAnimation = compositor.CreateScalarKeyFrameAnimation(start, end, duration, delay, easingFunction); - opacityAnimation.Target = "Opacity"; - group.Add(opacityAnimation); - - // Set the implicit animation - if (type == ImplicitAnimationType.Show) ElementCompositionPreview.SetImplicitShowAnimation(element, group); - else ElementCompositionPreview.SetImplicitHideAnimation(element, group); - } - - /// - /// Sets an implicit fade animation on the target - /// - /// The to animate - /// The type of implicit animation to set - /// The initial opacity value. If null, the current opacity will be used - /// The final opacity value - /// The duration of the fade animation, in milliseconds - /// The delay before the animation starts, in milliseconds. If null, there will be no delay - /// The easing function to use with the new animations - public static void SetCompositionFadeImplicitAnimation([NotNull] this UIElement element, ImplicitAnimationType type, - float start, float end, - int ms, int? msDelay, EasingFunctionNames easingFunction) - { - Visual visual = element.GetVisual(); - CompositionEasingFunction ease = visual.GetEasingFunction(easingFunction); - SetCompositionFadeImplicitAnimation(element, visual.Compositor, type, start, end, ms, msDelay, ease); - } - - /// - /// Sets an implicit fade animation on the target - /// - /// The to animate - /// The type of implicit animation to set - /// The initial opacity value. If null, the current opacity will be used - /// The final opacity value - /// The duration of the fade animation, in milliseconds - /// The delay before the animation starts, in milliseconds. If null, there will be no delay - /// The X coordinate of the first control point of the cubic beizer easing function - /// The Y coordinate of the first control point of the cubic beizer easing function - /// The X coordinate of the second control point of the cubic beizer easing function - /// The Y coordinate of the second control point of the cubic beizer easing function - public static void SetCompositionFadeImplicitAnimation([NotNull] this UIElement element, ImplicitAnimationType type, - float start, float end, - int ms, int? msDelay, float x1, float y1, float x2, float y2) - { - Visual visual = element.GetVisual(); - CompositionEasingFunction ease = visual.GetEasingFunction(x1, y1, x2, y2); - SetCompositionFadeImplicitAnimation(element, visual.Compositor, type, start, end, ms, msDelay, ease); - - } - - #endregion - - #region Fade and slide - - // Manages the fade and slide animation - private static Task ManageCompositionFadeSlideAnimationAsync([NotNull] UIElement element, - float? startOp, float endOp, - TranslationAxis axis, float? startXY, float endXY, - int msOp, int? msSlide, int? msDelay, [NotNull] CompositionEasingFunction easingFunction) - { - // Get the default values - Visual visual = element.GetVisual(); - visual.StopAnimation("Opacity"); - visual.StopAnimation("Offset"); - if (!startOp.HasValue) startOp = visual.Opacity; - - // Get the easing function, the duration and delay - TimeSpan durationOp = TimeSpan.FromMilliseconds(msOp); - TimeSpan durationSlide = TimeSpan.FromMilliseconds(msSlide ?? msOp); - TimeSpan? delay; - if (msDelay.HasValue) delay = TimeSpan.FromMilliseconds(msDelay.Value); - else delay = null; - - // Calculate the initial and final offset values - Vector3 initialOffset = visual.Offset; - Vector3 endOffset = visual.Offset; - if (axis == TranslationAxis.X) - { - if (startXY.HasValue) initialOffset.X = startXY.Value; - endOffset.X = endXY; - } - else - { - if (startXY.HasValue) initialOffset.Y = startXY.Value; - endOffset.Y = endXY; - } - - // Get the batch - CompositionScopedBatch batch = visual.Compositor.CreateScopedBatch(CompositionBatchTypes.Animation); - - // Get the opacity the animation - ScalarKeyFrameAnimation opacityAnimation = visual.Compositor.CreateScalarKeyFrameAnimation(startOp, endOp, durationOp, delay, easingFunction); - - // Offset animation - Vector3KeyFrameAnimation offsetAnimation = visual.Compositor.CreateVector3KeyFrameAnimation(initialOffset, endOffset, durationSlide, delay, easingFunction); - - // Close the batch and manage its event - TaskCompletionSource tcs = new TaskCompletionSource(); - batch.Completed += (s, e) => tcs.SetResult(null); - visual.StartAnimation("Opacity", opacityAnimation); - visual.StartAnimation("Offset", offsetAnimation); - batch.End(); - return tcs.Task; - } - - /// - /// Starts a fade and slide animation on the target and optionally runs a callback when the animations finish - /// - /// The to animate - /// The initial opacity value. If null, the current opacity will be used - /// The final opacity value - /// The offset axis to use on the translation animation - /// The initial offset value. If null, the current offset will be used - /// The final offset value - /// The duration of the fade animation, in milliseconds - /// The duration of the slide animation, in milliseconds - /// The delay before the animation starts, in milliseconds. If null, there will be no delay - /// The easing function to use with the new animations - /// An to execute when the new animations end - public static async void StartCompositionFadeSlideAnimation([NotNull] this UIElement element, - float? startOp, float endOp, - TranslationAxis axis, float? startXY, float endXY, - int msOp, int? msSlide, int? msDelay, EasingFunctionNames easingFunction, Action callback = null) - { - await StartCompositionFadeSlideAnimationAsync(element, startOp, endOp, axis, startXY, endXY, msOp, msSlide, msDelay, easingFunction); - callback?.Invoke(); - } - - /// - /// Starts a fade and slide animation on the target and optionally runs a callback when the animations finish - /// - /// The to animate - /// The initial opacity value. If null, the current opacity will be used - /// The final opacity value - /// The offset axis to use on the translation animation - /// The initial offset value. If null, the current offset will be used - /// The final offset value - /// The duration of the fade animation, in milliseconds - /// The duration of the slide animation, in milliseconds - /// The delay before the animation starts, in milliseconds. If null, there will be no delay - /// The X coordinate of the first control point of the cubic beizer easing function - /// The Y coordinate of the first control point of the cubic beizer easing function - /// The X coordinate of the second control point of the cubic beizer easing function - /// The Y coordinate of the second control point of the cubic beizer easing function - /// An to execute when the new animations end - public static async void StartCompositionFadeSlideAnimation([NotNull] this UIElement element, - float? startOp, float endOp, - TranslationAxis axis, float? startXY, float endXY, - int msOp, int? msSlide, int? msDelay, float x1, float y1, float x2, float y2, Action callback = null) - { - await StartCompositionFadeSlideAnimationAsync(element, startOp, endOp, axis, startXY, endXY, msOp, msSlide, msDelay, x1, y1, x2, y2); - callback?.Invoke(); - } - - /// - /// Starts a fade and slide animation on the target and returns a that completes when the animation ends - /// - /// The to animate - /// The initial opacity value. If null, the current opacity will be used - /// The final opacity value - /// The offset axis to use on the translation animation - /// The initial offset value. If null, the current offset will be used - /// The final offset value - /// The duration of the fade animation, in milliseconds - /// The duration of the slide animation, in milliseconds - /// The delay before the animation starts, in milliseconds. If null, there will be no delay - /// The easing function to use with the new animations - public static Task StartCompositionFadeSlideAnimationAsync([NotNull] this UIElement element, - float? startOp, float endOp, - TranslationAxis axis, float? startXY, float endXY, - int msOp, int? msSlide, int? msDelay, EasingFunctionNames easingFunction) - { - Visual visual = element.GetVisual(); - CompositionEasingFunction ease = visual.GetEasingFunction(easingFunction); - return ManageCompositionFadeSlideAnimationAsync(element, startOp, endOp, axis, startXY, endXY, msOp, msSlide, msDelay, ease); - } - - /// - /// Starts a fade and slide animation on the target and returns a that completes when the animation ends - /// - /// The to animate - /// The initial opacity value. If null, the current opacity will be used - /// The final opacity value - /// The offset axis to use on the translation animation - /// The initial offset value. If null, the current offset will be used - /// The final offset value - /// The duration of the fade animation, in milliseconds - /// The duration of the slide animation, in milliseconds - /// The delay before the animation starts, in milliseconds. If null, there will be no delay - /// The X coordinate of the first control point of the cubic beizer easing function - /// The Y coordinate of the first control point of the cubic beizer easing function - /// The X coordinate of the second control point of the cubic beizer easing function - /// The Y coordinate of the second control point of the cubic beizer easing function - public static Task StartCompositionFadeSlideAnimationAsync([NotNull] this UIElement element, - float? startOp, float endOp, - TranslationAxis axis, float? startXY, float endXY, - int msOp, int? msSlide, int? msDelay, float x1, float y1, float x2, float y2) - { - Visual visual = element.GetVisual(); - CompositionEasingFunction ease = visual.GetEasingFunction(x1, y1, x2, y2); - return ManageCompositionFadeSlideAnimationAsync(element, startOp, endOp, axis, startXY, endXY, msOp, msSlide, msDelay, ease); - } - - // Sets an implicit fade and slide animation on the target element - private static void SetCompositionFadeSlideImplicitAnimation([NotNull] UIElement element, [NotNull] Visual visual, ImplicitAnimationType type, - float startOp, float endOp, - TranslationAxis axis, float startXY, float endXY, - int msOp, int? msSlide, int? msDelay, [NotNull] CompositionEasingFunction easingFunction) - { - // Get the easing function, the duration and delay - TimeSpan durationOp = TimeSpan.FromMilliseconds(msOp); - TimeSpan durationSlide = TimeSpan.FromMilliseconds(msSlide ?? msOp); - TimeSpan? delay; - if (msDelay.HasValue) delay = TimeSpan.FromMilliseconds(msDelay.Value); - else delay = null; - - // Calculate the initial and final offset values - Vector3 initialOffset = visual.Offset; - Vector3 endOffset = visual.Offset; - if (axis == TranslationAxis.X) - { - initialOffset.X = startXY; - endOffset.X = endXY; - } - else - { - initialOffset.Y = startXY; - endOffset.Y = endXY; - } - - // Create and return the animations - CompositionAnimationGroup group = visual.Compositor.CreateAnimationGroup(); - ScalarKeyFrameAnimation fade = visual.Compositor.CreateScalarKeyFrameAnimation(startOp, endOp, durationOp, delay, easingFunction); - fade.Target = "Opacity"; - group.Add(fade); - Vector3KeyFrameAnimation slide = visual.Compositor.CreateVector3KeyFrameAnimation(initialOffset, endOffset, durationSlide, delay, easingFunction); - slide.Target = "Offset"; - group.Add(slide); - - // Set the implicit animation - if (type == ImplicitAnimationType.Show) ElementCompositionPreview.SetImplicitShowAnimation(element, group); - else ElementCompositionPreview.SetImplicitHideAnimation(element, group); - } - - /// - /// Sets an implicit fade and slide animation on the target - /// - /// The to animate - /// The type of implicit animation to set - /// The initial opacity value. If null, the current opacity will be used - /// The final opacity value - /// The offset axis to use on the translation animation - /// The initial offset value - /// The final offset value - /// The duration of the fade animation, in milliseconds - /// The duration of the slide animation, in milliseconds - /// The delay before the animation starts, in milliseconds. If null, there will be no delay - /// The easing function to use with the new animations - public static void SetCompositionFadeSlideImplicitAnimation([NotNull] this UIElement element, ImplicitAnimationType type, - float startOp, float endOp, - TranslationAxis axis, float startXY, float endXY, - int msOp, int? msSlide, int? msDelay, EasingFunctionNames easingFunction) - { - Visual visual = element.GetVisual(); - CompositionEasingFunction ease = visual.GetEasingFunction(easingFunction); - SetCompositionFadeSlideImplicitAnimation(element, visual, type, startOp, endOp, axis, startXY, endXY, msOp, msSlide, msDelay, ease); - } - - /// - /// Sets an implicit fade and slide animation on the target - /// - /// The to animate - /// The type of implicit animation to set - /// The initial opacity value. If null, the current opacity will be used - /// The final opacity value - /// The offset axis to use on the translation animation - /// The initial offset value - /// The final offset value - /// The duration of the fade animation, in milliseconds - /// The duration of the slide animation, in milliseconds - /// The delay before the animation starts, in milliseconds. If null, there will be no delay - /// The X coordinate of the first control point of the cubic beizer easing function - /// The Y coordinate of the first control point of the cubic beizer easing function - /// The X coordinate of the second control point of the cubic beizer easing function - /// The Y coordinate of the second control point of the cubic beizer easing function - public static void SetCompositionFadeSlideImplicitAnimation([NotNull] this UIElement element, ImplicitAnimationType type, - float startOp, float endOp, - TranslationAxis axis, float startXY, float endXY, - int msOp, int? msSlide, int? msDelay, float x1, float y1, float x2, float y2) - { - Visual visual = element.GetVisual(); - CompositionEasingFunction ease = visual.GetEasingFunction(x1, y1, x2, y2); - SetCompositionFadeSlideImplicitAnimation(element, visual, type, startOp, endOp, axis, startXY, endXY, msOp, msSlide, msDelay, ease); - } - - #endregion - - #region Fade and scale - - // Manages the fade and scale animation - private static async Task ManageCompositionFadeScaleAnimationAsync([NotNull] FrameworkElement element, Visual visual, - float? startOp, float endOp, - float? startXY, float endXY, - int msOp, int? msScale, int? msDelay, [NotNull] CompositionEasingFunction easingFunction, bool useFixedScale) - { - // Get the default values and set the CenterPoint - visual.StopAnimation("Opacity"); - visual.StopAnimation("Scale"); - if (useFixedScale) element.SetFixedCenterPoint(visual); - else await element.SetCenterPointAsync(visual); - if (!startOp.HasValue) startOp = visual.Opacity; - - // Get the easing function, the duration and delay - TimeSpan durationOp = TimeSpan.FromMilliseconds(msOp); - TimeSpan durationScale = TimeSpan.FromMilliseconds(msScale ?? msOp); - TimeSpan? delay; - if (msDelay.HasValue) delay = TimeSpan.FromMilliseconds(msDelay.Value); - else delay = null; - - // Calculate the initial and final scale values - Vector3 initialScale = visual.Scale; - if (startXY.HasValue) - { - initialScale.X = startXY.Value; - initialScale.Y = startXY.Value; - } - Vector3 endScale = new Vector3 - { - X = endXY, - Y = endXY, - Z = visual.Scale.Z - }; - - // Get the opacity the animation - ScalarKeyFrameAnimation opacityAnimation = visual.Compositor.CreateScalarKeyFrameAnimation(startOp, endOp, durationOp, delay, easingFunction); - - // Scale animation - Vector3KeyFrameAnimation scaleAnimation = visual.Compositor.CreateVector3KeyFrameAnimation(initialScale, endScale, durationScale, delay, easingFunction); - - // Get the batch and start the animations - CompositionScopedBatch batch = visual.Compositor.CreateScopedBatch(CompositionBatchTypes.Animation); - TaskCompletionSource tcs = new TaskCompletionSource(); - batch.Completed += (s, e) => tcs.SetResult(null); - visual.StartAnimation("Opacity", opacityAnimation); - visual.StartAnimation("Scale", scaleAnimation); - batch.End(); - await tcs.Task; - } - - /// - /// Starts a fade and scale animation on the target and optionally runs a callback when the animations finish - /// - /// The to animate - /// The initial opacity value. If null, the current opacity will be used - /// The final opacity value - /// The initial scale X and Y value. If null, the current scale will be used - /// The final scale X and Y value - /// The duration of the fade animation, in milliseconds - /// The duration of the scale animation, in milliseconds - /// The delay before the animation starts, in milliseconds. If null, there will be no delay - /// The easing function to use with the new animations - /// An to execute when the new animations end - /// If true, the fixed and properties - /// will be used, otherwise the center point will be calculated using the and width - public static async void StartCompositionFadeScaleAnimation([NotNull] this FrameworkElement element, - float? startOp, float endOp, - float? startScale, float endScale, - int msOp, int? msScale, int? msDelay, EasingFunctionNames easingFunction, Action callback = null, bool useFixedSize = false) - { - await StartCompositionFadeScaleAnimationAsync(element, startOp, endOp, startScale, endScale, msOp, msScale, msDelay, easingFunction, useFixedSize); - callback?.Invoke(); - } - - /// - /// Starts a fade and scale animation on the target and optionally runs a callback when the animations finish - /// - /// The to animate - /// The initial opacity value. If null, the current opacity will be used - /// The final opacity value - /// The initial scale X and Y value. If null, the current scale will be used - /// The final scale X and Y value - /// The duration of the fade animation, in milliseconds - /// The duration of the scale animation, in milliseconds - /// The delay before the animation starts, in milliseconds. If null, there will be no delay - /// The X coordinate of the first control point of the cubic beizer easing function - /// The Y coordinate of the first control point of the cubic beizer easing function - /// The X coordinate of the second control point of the cubic beizer easing function - /// The Y coordinate of the second control point of the cubic beizer easing function - /// An to execute when the new animations end - /// If true, the fixed and properties - /// will be used, otherwise the center point will be calculated using the and width - public static async void StartCompositionFadeScaleAnimation([NotNull] this FrameworkElement element, - float? startOp, float endOp, - float? startScale, float endScale, - int msOp, int? msScale, int? msDelay, float x1, float y1, float x2, float y2, Action callback = null, bool useFixedSize = false) - { - await StartCompositionFadeScaleAnimationAsync(element, startOp, endOp, startScale, endScale, msOp, msScale, msDelay, x1, y1, x2, y2, useFixedSize); - callback?.Invoke(); - } - - /// - /// Starts a fade and scale animation on the target and returns a that completes when the animation ends - /// - /// The to animate - /// The initial opacity value. If null, the current opacity will be used - /// The final opacity value - /// The initial scale X and Y value. If null, the current scale will be used - /// The final scale X and Y value - /// The duration of the fade animation, in milliseconds - /// The duration of the scale animation, in milliseconds - /// The delay before the animation starts, in milliseconds. If null, there will be no delay - /// The easing function to use with the new animations - /// If true, the fixed and properties - /// will be used, otherwise the center point will be calculated using the and width - public static Task StartCompositionFadeScaleAnimationAsync([NotNull] this FrameworkElement element, - float? startOp, float endOp, - float? startScale, float endScale, - int msOp, int? msScale, int? msDelay, EasingFunctionNames easingFunction, bool useFixedSize = false) - { - Visual visual = element.GetVisual(); - CompositionEasingFunction ease = visual.GetEasingFunction(easingFunction); - return ManageCompositionFadeScaleAnimationAsync(element, visual, startOp, endOp, startScale, endScale, msOp, msScale, msDelay, ease, useFixedSize); - } - - /// - /// Starts a fade and scale animation on the target and returns a that completes when the animation ends - /// - /// The to animate - /// The initial opacity value. If null, the current opacity will be used - /// The final opacity value - /// The initial scale X and Y value. If null, the current scale will be used - /// The final scale X and Y value - /// The duration of the fade animation, in milliseconds - /// The duration of the scale animation, in milliseconds - /// The delay before the animation starts, in milliseconds. If null, there will be no delay - /// The X coordinate of the first control point of the cubic beizer easing function - /// The Y coordinate of the first control point of the cubic beizer easing function - /// The X coordinate of the second control point of the cubic beizer easing function - /// The Y coordinate of the second control point of the cubic beizer easing function - /// If true, the fixed and properties - /// will be used, otherwise the center point will be calculated using the and width - public static Task StartCompositionFadeScaleAnimationAsync([NotNull] this FrameworkElement element, - float? startOp, float endOp, - float? startScale, float endScale, - int msOp, int? msScale, int? msDelay, float x1, float y1, float x2, float y2, bool useFixedSize = false) - { - Visual visual = element.GetVisual(); - CompositionEasingFunction ease = visual.GetEasingFunction(x1, y1, x2, y2); - return ManageCompositionFadeScaleAnimationAsync(element, visual, startOp, endOp, startScale, endScale, msOp, msScale, msDelay, ease, useFixedSize); - } - - // Sets an implicit fade and scale animation on the target element - private static async Task SetCompositionFadeScaleImplicitAnimationAsync([NotNull] FrameworkElement element, [NotNull] Visual visual, ImplicitAnimationType type, - float startOp, float endOp, - float startScale, float endScale, - int msOp, int? msScale, int? msDelay, [NotNull] CompositionEasingFunction easingFunction, bool useFixedSize) - { - // Get the default values and set the CenterPoint - if (useFixedSize) element.SetFixedCenterPoint(visual); - else await element.SetCenterPointAsync(visual); - - // Get the easing function, the duration and delay - TimeSpan durationOp = TimeSpan.FromMilliseconds(msOp); - TimeSpan durationScale = TimeSpan.FromMilliseconds(msScale ?? msOp); - TimeSpan? delay; - if (msDelay.HasValue) delay = TimeSpan.FromMilliseconds(msDelay.Value); - else delay = null; - - // Calculate the initial and final scale values - Vector3 initialScale = new Vector3(startScale, startScale, visual.Scale.Z); - Vector3 endScale3 = new Vector3(endScale, endScale, visual.Scale.Z); - - // Get the animations - CompositionAnimationGroup group = visual.Compositor.CreateAnimationGroup(); - ScalarKeyFrameAnimation opacityAnimation = visual.Compositor.CreateScalarKeyFrameAnimation(startOp, endOp, durationOp, delay, easingFunction); - opacityAnimation.Target = "Opacity"; - group.Add(opacityAnimation); - Vector3KeyFrameAnimation scaleAnimation = visual.Compositor.CreateVector3KeyFrameAnimation(initialScale, endScale3, durationScale, delay, easingFunction); - scaleAnimation.Target = "Scale"; - group.Add(scaleAnimation); - - // Set the implicit animation - if (type == ImplicitAnimationType.Show) ElementCompositionPreview.SetImplicitShowAnimation(element, group); - else ElementCompositionPreview.SetImplicitHideAnimation(element, group); - } - - /// - /// Sets an implicit fade and scale animation on the target - /// - /// The to animate - /// The type of implicit animation to set - /// The initial opacity value. If null, the current opacity will be used - /// The final opacity value - /// The initial scale X and Y value. If null, the current scale will be used - /// The final scale X and Y value - /// The duration of the fade animation, in milliseconds - /// The duration of the scale animation, in milliseconds - /// The delay before the animation starts, in milliseconds. If null, there will be no delay - /// The easing function to use with the new animations - /// If true, the fixed and properties - /// will be used, otherwise the center point will be calculated using the and width - public static Task SetCompositionFadeScaleImplicitAnimationAsync([NotNull] this FrameworkElement element, ImplicitAnimationType type, - float startOp, float endOp, - float startScale, float endScale, - int msOp, int? msScale, int? msDelay, EasingFunctionNames easingFunction, bool useFixedSize = false) - { - Visual visual = element.GetVisual(); - CompositionEasingFunction ease = visual.GetEasingFunction(easingFunction); - return SetCompositionFadeScaleImplicitAnimationAsync(element, visual, type, startOp, endOp, startScale, endScale, msOp, msScale, msDelay, ease, useFixedSize); - } - - /// - /// Sets an implicit fade and scale animation on the target - /// - /// The to animate - /// The type of implicit animation to set - /// The initial opacity value. If null, the current opacity will be used - /// The final opacity value - /// The initial scale X and Y value. If null, the current scale will be used - /// The final scale X and Y value - /// The duration of the fade animation, in milliseconds - /// The duration of the scale animation, in milliseconds - /// The delay before the animation starts, in milliseconds. If null, there will be no delay - /// The X coordinate of the first control point of the cubic beizer easing function - /// The Y coordinate of the first control point of the cubic beizer easing function - /// The X coordinate of the second control point of the cubic beizer easing function - /// The Y coordinate of the second control point of the cubic beizer easing function - /// If true, the fixed and properties - /// will be used, otherwise the center point will be calculated using the and width - public static Task SetCompositionFadeScaleImplicitAnimationAsync([NotNull] this FrameworkElement element, ImplicitAnimationType type, - float startOp, float endOp, - float startScale, float endScale, - int msOp, int? msScale, int? msDelay, float x1, float y1, float x2, float y2, bool useFixedSize = false) - { - Visual visual = element.GetVisual(); - CompositionEasingFunction ease = visual.GetEasingFunction(x1, y1, x2, y2); - return SetCompositionFadeScaleImplicitAnimationAsync(element, visual, type, startOp, endOp, startScale, endScale, msOp, msScale, msDelay, ease, useFixedSize); - } - - #endregion - - #region Scale only - - // Manages the scale animation - private static async Task ManageCompositionScaleAnimationAsync([NotNull] FrameworkElement element, [NotNull] Visual visual, - float? startXY, float endXY, - int ms, int? msDelay, [NotNull] CompositionEasingFunction easingFunction, bool useFixedSize) - { - // Get the default values and set the CenterPoint - visual.StopAnimation("Scale"); - if (useFixedSize) element.SetFixedCenterPoint(visual); - else await element.SetCenterPointAsync(visual); - - // Get the easing function, the duration and delay - TimeSpan duration = TimeSpan.FromMilliseconds(ms); - TimeSpan? delay; - if (msDelay.HasValue) delay = TimeSpan.FromMilliseconds(msDelay.Value); - else delay = null; - - // Calculate the initial and final scale values - Vector3 initialScale = visual.Scale; - if (startXY.HasValue) - { - initialScale.X = startXY.Value; - initialScale.Y = startXY.Value; - } - Vector3 endScale = new Vector3 - { - X = endXY, - Y = endXY, - Z = visual.Scale.Z - }; - - // Scale animation - Vector3KeyFrameAnimation scaleAnimation = visual.Compositor.CreateVector3KeyFrameAnimation(initialScale, endScale, duration, delay, easingFunction); - - // Get the batch and start the animations - CompositionScopedBatch batch = visual.Compositor.CreateScopedBatch(CompositionBatchTypes.Animation); - TaskCompletionSource tcs = new TaskCompletionSource(); - batch.Completed += (s, e) => tcs.SetResult(null); - visual.StartAnimation("Scale", scaleAnimation); - batch.End(); - await tcs.Task; - return initialScale.X; - } - - /// - /// Starts a scale animation on the target and optionally runs a callback when the animation finishes - /// - /// The to animate - /// The initial scale X and Y value. If null, the current scale will be used - /// The final scale X and Y value - /// The duration of the animation, in milliseconds - /// The delay before the animation starts, in milliseconds. If null, there will be no delay - /// The easing function to use with the new animations - /// If true, the animation will be played in reverse mode when it finishes for the first time - /// An to execute when the new animations end - /// If true, the fixed and properties - /// will be used, otherwise the center point will be calculated using the and width - public static async void StartCompositionScaleAnimation([NotNull] this FrameworkElement element, - float? startScale, float endScale, - int ms, int? msDelay, EasingFunctionNames easingFunction, - bool reverse = false, Action callback = null, bool useFixedSize = false) - { - await element.StartCompositionScaleAnimationAsync(startScale, endScale, ms, msDelay, easingFunction, reverse); - callback?.Invoke(); - } - - /// - /// Starts a scale animation on the target and optionally runs a callback when the animation finishes - /// - /// The to animate - /// The initial scale X and Y value. If null, the current scale will be used - /// The final scale X and Y value - /// The duration of the animation, in milliseconds - /// The delay before the animation starts, in milliseconds. If null, there will be no delay - /// The X coordinate of the first control point of the cubic beizer easing function - /// The Y coordinate of the first control point of the cubic beizer easing function - /// The X coordinate of the second control point of the cubic beizer easing function - /// The Y coordinate of the second control point of the cubic beizer easing function - /// If true, the animation will be played in reverse mode when it finishes for the first time - /// An to execute when the new animations end - /// If true, the fixed and properties - /// will be used, otherwise the center point will be calculated using the and width - public static async void StartCompositionScaleAnimation([NotNull] this FrameworkElement element, - float? startScale, float endScale, - int ms, int? msDelay, float x1, float y1, float x2, float y2, bool reverse = false, Action callback = null, bool useFixedSize = false) - { - await element.StartCompositionScaleAnimationAsync(startScale, endScale, ms, msDelay, x1, y1, x2, y2, reverse); - callback?.Invoke(); - } - - /// - /// Starts a scale animation on the target and returns a that completes when the animation ends - /// - /// The to animate - /// The initial scale X and Y value. If null, the current scale will be used - /// The final scale X and Y value - /// The duration of the animation, in milliseconds - /// The delay before the animation starts, in milliseconds. If null, there will be no delay - /// The easing function to use with the new animations - /// If true, the animation will be played in reverse mode when it finishes for the first time - /// If true, the fixed and properties - /// will be used, otherwise the center point will be calculated using the and width - public static async Task StartCompositionScaleAnimationAsync([NotNull] this FrameworkElement element, - float? startScale, float endScale, - int ms, int? msDelay, EasingFunctionNames easingFunction, bool reverse = false, bool useFixedSize = false) - { - Visual visual = element.GetVisual(); - CompositionEasingFunction ease = visual.GetEasingFunction(easingFunction); - startScale = await ManageCompositionScaleAnimationAsync(element, visual, startScale, endScale, ms, msDelay, ease, useFixedSize); - if (reverse) await ManageCompositionScaleAnimationAsync(element, visual, endScale, startScale.Value, ms, null, ease, useFixedSize); - } - - /// - /// Starts a scale animation on the target and returns a that completes when the animation ends - /// - /// The to animate - /// The initial scale X and Y value. If null, the current scale will be used - /// The final scale X and Y value - /// The duration of the animation, in milliseconds - /// The delay before the animation starts, in milliseconds. If null, there will be no delay - /// The X coordinate of the first control point of the cubic beizer easing function - /// The Y coordinate of the first control point of the cubic beizer easing function - /// The X coordinate of the second control point of the cubic beizer easing function - /// The Y coordinate of the second control point of the cubic beizer easing function - /// If true, the animation will be played in reverse mode when it finishes for the first time - /// If true, the fixed and properties - /// will be used, otherwise the center point will be calculated using the and width - public static async Task StartCompositionScaleAnimationAsync([NotNull] this FrameworkElement element, - float? startScale, float endScale, - int ms, int? msDelay, float x1, float y1, float x2, float y2, bool reverse = false, bool useFixedSize = false) - { - Visual visual = element.GetVisual(); - CompositionEasingFunction ease = visual.GetEasingFunction(x1, y1, x2, y2); - startScale = await ManageCompositionScaleAnimationAsync(element, visual, startScale, endScale, ms, msDelay, ease, useFixedSize); - if (reverse) await ManageCompositionScaleAnimationAsync(element, visual, endScale, startScale.Value, ms, null, ease, useFixedSize); - } - - // Sets an implicit scale animation on the target element - private static async Task SetCompositionScaleImplicitAnimationAsync([NotNull] FrameworkElement element, [NotNull] Visual visual, ImplicitAnimationType type, - float start, float end, - int ms, int? msDelay, [NotNull] CompositionEasingFunction easingFunction) - { - // Get the default values and set the CenterPoint - await element.SetCenterPointAsync(visual); - - // Get the easing function, the duration and delay - TimeSpan duration = TimeSpan.FromMilliseconds(ms); - TimeSpan? delay; - if (msDelay.HasValue) delay = TimeSpan.FromMilliseconds(msDelay.Value); - else delay = null; - - // Calculate the initial and final scale values - Vector3 initialScale = new Vector3(start, start, visual.Scale.Z); - Vector3 endScale = new Vector3(end, end, visual.Scale.Z); - - // Get the animations - CompositionAnimationGroup group = visual.Compositor.CreateAnimationGroup(); - Vector3KeyFrameAnimation scaleAnimation = visual.Compositor.CreateVector3KeyFrameAnimation(initialScale, endScale, duration, delay, easingFunction); - scaleAnimation.Target = "Scale"; - group.Add(scaleAnimation); - - // Set the implicit animation - if (type == ImplicitAnimationType.Show) ElementCompositionPreview.SetImplicitShowAnimation(element, group); - else ElementCompositionPreview.SetImplicitHideAnimation(element, group); - } - - /// - /// Sets an implicit scale animation on the target - /// - /// The to animate - /// The type of implicit animation to set - /// The initial scale value - /// The final value - /// The duration of the scale animation, in milliseconds - /// The delay before the animation starts, in milliseconds. If null, there will be no delay - /// The easing function to use with the new animations - public static Task SetCompositionScaleImplicitAnimationAsync([NotNull] this FrameworkElement element, ImplicitAnimationType type, - float start, float end, - int ms, int? msDelay, EasingFunctionNames easingFunction) - { - Visual visual = element.GetVisual(); - CompositionEasingFunction ease = visual.GetEasingFunction(easingFunction); - return SetCompositionScaleImplicitAnimationAsync(element, visual, type, start, end, ms, msDelay, ease); - } - - /// - /// Sets an implicit scale animation on the target - /// - /// The to animate - /// The type of implicit animation to set - /// The initial scale value - /// The final value - /// The duration of the scale animation, in milliseconds - /// The delay before the animation starts, in milliseconds. If null, there will be no delay - /// The X coordinate of the first control point of the cubic beizer easing function - /// The Y coordinate of the first control point of the cubic beizer easing function - /// The X coordinate of the second control point of the cubic beizer easing function - /// The Y coordinate of the second control point of the cubic beizer easing function - public static Task SetCompositionScaleImplicitAnimationAsync([NotNull] this FrameworkElement element, ImplicitAnimationType type, - float start, float end, - int ms, int? msDelay, float x1, float y1, float x2, float y2) - { - Visual visual = element.GetVisual(); - CompositionEasingFunction ease = visual.GetEasingFunction(x1, y1, x2, y2); - return SetCompositionScaleImplicitAnimationAsync(element, visual, type, start, end, ms, msDelay, ease); - } - - #endregion - - #region Slide only - - // Manages the scale animation - private static async Task ManageCompositionSlideAnimationAsync([NotNull] Visual visual, - TranslationAxis axis, float? startXY, float endXY, - int ms, int? msDelay, [NotNull] CompositionEasingFunction easingFunction) - { - // Get the default values - visual.StopAnimation("Offset"); - - // Get the easing function, the duration and delay - TimeSpan duration = TimeSpan.FromMilliseconds(ms); - TimeSpan? delay; - if (msDelay.HasValue) delay = TimeSpan.FromMilliseconds(msDelay.Value); - else delay = null; - - // Setup the animation start and end positions - Vector3 - initialOffset = visual.Offset, - endOffset = visual.Offset; - - // Create the animation - if (axis == TranslationAxis.X) - { - if (startXY.HasValue) initialOffset.X = startXY.Value; - endOffset.X = endXY; - } - else - { - if (startXY.HasValue) initialOffset.Y = startXY.Value; - endOffset.Y = endXY; - } - Vector3KeyFrameAnimation animation = visual.Compositor.CreateVector3KeyFrameAnimation(initialOffset, endOffset, duration, delay, easingFunction); - - // Get the batch and start the animations - CompositionScopedBatch batch = visual.Compositor.CreateScopedBatch(CompositionBatchTypes.Animation); - TaskCompletionSource tcs = new TaskCompletionSource(); - batch.Completed += (s, e) => tcs.SetResult(null); - visual.StartAnimation("Offset", animation); - batch.End(); - await tcs.Task; - return axis == TranslationAxis.X ? initialOffset.X : initialOffset.Y; - } - - /// - /// Starts an offset animation on the target and optionally runs a callback when the animation finishes - /// - /// The to animate - /// The offset axis - /// The initial offset X and Y value. If null, the current offset will be used - /// The final offset X and Y value - /// The duration of the animation, in milliseconds - /// The delay before the animation starts, in milliseconds. If null, there will be no delay - /// The easing function to use with the new animations - /// If true, the animation will be played in reverse mode when it finishes for the first time - /// An to execute when the new animations end - public static async void StartCompositionSlideAnimation([NotNull] this UIElement element, - TranslationAxis axis, float? startOffset, float endOffset, - int ms, int? msDelay, EasingFunctionNames easingFunction, bool reverse = false, Action callback = null) - { - await element.StartCompositionSlideAnimationAsync(axis, startOffset, endOffset, ms, msDelay, easingFunction, reverse); - callback?.Invoke(); - } - - /// - /// Starts an offset animation on the target and optionally runs a callback when the animation finishes - /// - /// The to animate - /// The offset axis - /// The initial offset X and Y value. If null, the current offset will be used - /// The final offset X and Y value - /// The duration of the animation, in milliseconds - /// The delay before the animation starts, in milliseconds. If null, there will be no delay - /// The X coordinate of the first control point of the cubic beizer easing function - /// The Y coordinate of the first control point of the cubic beizer easing function - /// The X coordinate of the second control point of the cubic beizer easing function - /// The Y coordinate of the second control point of the cubic beizer easing function - /// If true, the animation will be played in reverse mode when it finishes for the first time - /// An to execute when the new animations end - public static async void StartCompositionSlideAnimation([NotNull] this UIElement element, - TranslationAxis axis, float? startOffset, float endOffset, - int ms, int? msDelay, float x1, float y1, float x2, float y2, bool reverse = false, Action callback = null) - { - await element.StartCompositionSlideAnimationAsync(axis, startOffset, endOffset, ms, msDelay, x1, y1, x2, y2, reverse); - callback?.Invoke(); - } - - /// - /// Starts an offset animation on the target and returns a that completes when the animation ends - /// - /// The UIElement to animate - /// The offset axis - /// The initial offset X and Y value. If null, the current offset will be used - /// The final offset X and Y value - /// The duration of the animation, in milliseconds - /// The delay before the animation starts, in milliseconds. If null, there will be no delay - /// The easing function to use with the new animations - /// If true, the animation will be played in reverse mode when it finishes for the first time - public static async Task StartCompositionSlideAnimationAsync([NotNull] this UIElement element, - TranslationAxis axis, float? startOffset, float endOffset, - int ms, int? msDelay, EasingFunctionNames easingFunction, bool reverse = false) - { - Visual visual = element.GetVisual(); - CompositionEasingFunction ease = visual.GetEasingFunction(easingFunction); - startOffset = await ManageCompositionSlideAnimationAsync(visual, axis, startOffset, endOffset, ms, msDelay, ease); - if (reverse) await ManageCompositionSlideAnimationAsync(visual, axis, endOffset, startOffset.Value, ms, msDelay, ease); - } - - /// - /// Starts an offset animation on the target and returns a that completes when the animation ends - /// - /// The to animate - /// The offset axis - /// The initial offset X and Y value. If null, the current offset will be used - /// The final offset X and Y value - /// The duration of the animation, in milliseconds - /// The delay before the animation starts, in milliseconds. If null, there will be no delay - /// The X coordinate of the first control point of the cubic beizer easing function - /// The Y coordinate of the first control point of the cubic beizer easing function - /// The X coordinate of the second control point of the cubic beizer easing function - /// The Y coordinate of the second control point of the cubic beizer easing function - /// If true, the animation will be played in reverse mode when it finishes for the first time - public static async Task StartCompositionSlideAnimationAsync([NotNull] this UIElement element, - TranslationAxis axis, float? startOffset, float endOffset, - int ms, int? msDelay, float x1, float y1, float x2, float y2, bool reverse = false) - { - Visual visual = element.GetVisual(); - CompositionEasingFunction ease = visual.GetEasingFunction(x1, y1, x2, y2); - startOffset = await ManageCompositionSlideAnimationAsync(visual, axis, startOffset, endOffset, ms, msDelay, ease); - if (reverse) await ManageCompositionSlideAnimationAsync(visual, axis, endOffset, startOffset.Value, ms, msDelay, ease); - } - - // Sets an implicit slide animation on the target element - private static void SetCompositionSlideImplicitAnimation([NotNull] UIElement element, [NotNull] Visual visual, ImplicitAnimationType type, - TranslationAxis axis, float start, float end, - int ms, int? msDelay, [NotNull] CompositionEasingFunction easingFunction) - { - // Get the easing function, the duration and delay - TimeSpan duration = TimeSpan.FromMilliseconds(ms); - TimeSpan? delay; - if (msDelay.HasValue) delay = TimeSpan.FromMilliseconds(msDelay.Value); - else delay = null; - - // Calculate the initial and final offset values - Vector3 - initialOffset = visual.Offset, - endOffset = visual.Offset; - - // Setup the target values - if (axis == TranslationAxis.X) - { - initialOffset.X = start; - endOffset.X = end; - } - else - { - initialOffset.Y = start; - endOffset.Y = end; - } - - // Get the animations - CompositionAnimationGroup group = visual.Compositor.CreateAnimationGroup(); - Vector3KeyFrameAnimation offsetAnimation = visual.Compositor.CreateVector3KeyFrameAnimation(initialOffset, endOffset, duration, delay, easingFunction); - offsetAnimation.Target = "Offset"; - group.Add(offsetAnimation); - - // Set the implicit animation - if (type == ImplicitAnimationType.Show) ElementCompositionPreview.SetImplicitShowAnimation(element, group); - else ElementCompositionPreview.SetImplicitHideAnimation(element, group); - } - - /// - /// Sets an implicit slide animation on the target - /// - /// The to animate - /// The type of implicit animation to set - /// The offset axis - /// The initial scale value - /// The final value - /// The duration of the scale animation, in milliseconds - /// The delay before the animation starts, in milliseconds. If null, there will be no delay - /// The easing function to use with the new animations - public static void SetCompositionSlideImplicitAnimation([NotNull] this UIElement element, ImplicitAnimationType type, - TranslationAxis axis, float start, float end, - int ms, int? msDelay, EasingFunctionNames easingFunction) - { - Visual visual = element.GetVisual(); - CompositionEasingFunction ease = visual.GetEasingFunction(easingFunction); - SetCompositionSlideImplicitAnimation(element, visual, type, axis, start, end, ms, msDelay, ease); - } - - /// - /// Sets an implicit slide animation on the target - /// - /// The to animate - /// The type of implicit animation to set - /// The offset axis - /// The initial scale value - /// The final value - /// The duration of the scale animation, in milliseconds - /// The delay before the animation starts, in milliseconds. If null, there will be no delay - /// The X coordinate of the first control point of the cubic beizer easing function - /// The Y coordinate of the first control point of the cubic beizer easing function - /// The X coordinate of the second control point of the cubic beizer easing function - /// The Y coordinate of the second control point of the cubic beizer easing function - public static void SetCompositionSlideImplicitAnimation([NotNull] this UIElement element, ImplicitAnimationType type, - TranslationAxis axis, float start, float end, - int ms, int? msDelay, float x1, float y1, float x2, float y2) - { - Visual visual = element.GetVisual(); - CompositionEasingFunction ease = visual.GetEasingFunction(x1, y1, x2, y2); - SetCompositionSlideImplicitAnimation(element, visual, type, axis, start, end, ms, msDelay, ease); - } - - #endregion - - #region Translation only - - // Manages the scale animation - private static Task ManageCompositionTranslationAnimationAsync([NotNull] UIElement element, [NotNull] Visual visual, - float endX, float endY, - int ms, int? msDelay, [NotNull] CompositionEasingFunction easingFunction) - { - // Get the default values - ElementCompositionPreview.SetIsTranslationEnabled(element, true); - visual.StopAnimation("Translation"); - - // Get the easing function, the duration and delay - TimeSpan duration = TimeSpan.FromMilliseconds(ms); - TimeSpan? delay; - if (msDelay.HasValue) delay = TimeSpan.FromMilliseconds(msDelay.Value); - else delay = null; - - // Setup the animation start and end positions - Vector3 vector = new Vector3(endX, endY, 0); - Vector3KeyFrameAnimation animation = visual.Compositor.CreateVector3KeyFrameAnimation(null, vector, duration, delay, easingFunction); - - // Get the batch and start the animations - CompositionScopedBatch batch = visual.Compositor.CreateScopedBatch(CompositionBatchTypes.Animation); - TaskCompletionSource tcs = new TaskCompletionSource(); - batch.Completed += (s, e) => tcs.SetResult(null); - visual.StartAnimation("Translation", animation); - batch.End(); - return tcs.Task; - } - - /// - /// Starts an offset animation on the target and optionally runs a callback when the animation finishes - /// - /// The to animate - /// The final offset X value - /// The final offset Y value - /// The duration of the animation, in milliseconds - /// The delay before the animation starts, in milliseconds. If null, there will be no delay - /// The easing function to use with the new animations - /// An to execute when the new animations end - public static async void StartCompositionTranslationAnimation([NotNull] this UIElement element, - float endX, float endY, - int ms, int? msDelay, EasingFunctionNames easingFunction, Action callback = null) - { - await element.StartCompositionTranslationAnimationAsync(endX, endY, ms, msDelay, easingFunction); - callback?.Invoke(); - } - - /// - /// Starts an offset animation on the target and optionally runs a callback when the animation finishes - /// - /// The to animate - /// The final offset X value - /// The final offset Y value - /// The duration of the animation, in milliseconds - /// The delay before the animation starts, in milliseconds. If null, there will be no delay - /// The X coordinate of the first control point of the cubic beizer easing function - /// The Y coordinate of the first control point of the cubic beizer easing function - /// The X coordinate of the second control point of the cubic beizer easing function - /// The Y coordinate of the second control point of the cubic beizer easing function - /// An to execute when the new animations end - public static async void StartCompositionTranslationAnimation([NotNull] this UIElement element, - float endX, float endY, - int ms, int? msDelay, float x1, float y1, float x2, float y2, Action callback = null) - { - await element.StartCompositionTranslationAnimationAsync(endX, endY, ms, msDelay, x1, y1, x2, y2); - callback?.Invoke(); - } - - /// - /// Starts an offset animation on the target and returns a that completes when the animation ends - /// - /// The UIElement to animate - /// The final offset X value - /// The final offset Y value - /// The duration of the animation, in milliseconds - /// The delay before the animation starts, in milliseconds. If null, there will be no delay - /// The easing function to use with the new animations - public static Task StartCompositionTranslationAnimationAsync([NotNull] this UIElement element, - float endX, float endY, - int ms, int? msDelay, EasingFunctionNames easingFunction) - { - Visual visual = element.GetVisual(); - CompositionEasingFunction ease = visual.GetEasingFunction(easingFunction); - return ManageCompositionTranslationAnimationAsync(element, visual, endX, endY, ms, msDelay, ease); - } - - /// - /// Starts an offset animation on the target and returns a that completes when the animation ends - /// - /// The to animate - /// The final offset X value - /// The final offset Y value - /// The duration of the animation, in milliseconds - /// The delay before the animation starts, in milliseconds. If null, there will be no delay - /// The X coordinate of the first control point of the cubic beizer easing function - /// The Y coordinate of the first control point of the cubic beizer easing function - /// The X coordinate of the second control point of the cubic beizer easing function - /// The Y coordinate of the second control point of the cubic beizer easing function - public static Task StartCompositionTranslationAnimationAsync([NotNull] this UIElement element, - float endX, float endY, - int ms, int? msDelay, float x1, float y1, float x2, float y2) - { - Visual visual = element.GetVisual(); - CompositionEasingFunction ease = visual.GetEasingFunction(x1, y1, x2, y2); - return ManageCompositionTranslationAnimationAsync(element, visual, endX, endY, ms, msDelay, ease); - } - - #endregion - - #region Roll - - // Manages the roll animation - private static async Task ManageCompositionRollAnimationAsync([NotNull] this FrameworkElement element, - float? startOp, float endOp, - TranslationAxis axis, float? startXY, float endXY, - float? startDegrees, float endDegrees, - int ms, int? msDelay, EasingFunctionNames easingFunction, bool useFixedSize) - { - // Get the default values - Visual visual = element.GetVisual(); - visual.StopAnimation("Opacity"); - visual.StopAnimation("Offset"); - visual.StopAnimation("RotationAngle"); - if (useFixedSize) element.SetFixedCenterPoint(visual); - else await element.SetCenterPointAsync(visual); - - // Get the current opacity - if (!startOp.HasValue) startOp = visual.Opacity; - - // Get the easing function, the duration and delay - CompositionEasingFunction ease = visual.GetEasingFunction(easingFunction); - TimeSpan duration = TimeSpan.FromMilliseconds(ms); - TimeSpan? delay; - if (msDelay.HasValue) delay = TimeSpan.FromMilliseconds(msDelay.Value); - else delay = null; - - // Calculate the initial and final offset values - Vector3 initialOffset = visual.Offset; - Vector3 endOffset = visual.Offset; - if (axis == TranslationAxis.X) - { - if (startXY.HasValue) initialOffset.X = startXY.Value; - endOffset.X = endXY; - } - else - { - if (startXY.HasValue) initialOffset.Y = startXY.Value; - endOffset.Y = endXY; - } - - // Calculate the initial and final rotation angle - if (startDegrees == null) startDegrees = visual.RotationAngle.ToDegrees(); - - // Get the opacity the animation - ScalarKeyFrameAnimation opacityAnimation = visual.Compositor.CreateScalarKeyFrameAnimation(startOp, endOp, duration, delay, ease); - - // Scale animation - Vector3KeyFrameAnimation offsetAnimation = visual.Compositor.CreateVector3KeyFrameAnimation(initialOffset, endOffset, duration, delay, ease); - - // Rotate animation - ScalarKeyFrameAnimation rotateAnimation = visual.Compositor.CreateScalarKeyFrameAnimation(startDegrees.Value.ToRadians(), endDegrees.ToRadians(), duration, delay, ease); - - // Get the batch and start the animations - CompositionScopedBatch batch = visual.Compositor.CreateScopedBatch(CompositionBatchTypes.Animation); - TaskCompletionSource tcs = new TaskCompletionSource(); - batch.Completed += (s, e) => tcs.SetResult(null); - visual.StartAnimation("Opacity", opacityAnimation); - visual.StartAnimation("Offset", offsetAnimation); - visual.StartAnimation("RotationAngle", rotateAnimation); - batch.End(); - await tcs.Task; - return new CompositeRotationAnimationStartInfo(startOp.Value, initialOffset.X, startDegrees.Value); - } - - /// - /// Starts a roll animation on the target and optionally runs a callback when the animation finishes - /// - /// The to animate - /// The initial opacity value. If null, the current opacity will be used - /// The final opacity value - /// The offset axis - /// The initial offset X and Y value. If null, the current offset will be used - /// The final offset X and Y value - /// The initial angle in degrees - /// The target angle in degrees - /// The duration of the animation, in milliseconds - /// The delay before the animation starts, in milliseconds. If null, there will be no delay - /// The easing function to use with the new animations - /// If true, the animation will be played in reverse mode when it finishes for the first time - /// An to execute when the new animations end - /// If true, the fixed and properties - /// will be used, otherwise the center point will be calculated using the and width - public static async void StartCompositionRollAnimation([NotNull] this FrameworkElement element, - float? startOp, float endOp, - TranslationAxis axis, float? startXY, float endXY, - float? startDegrees, float endDegrees, - int ms, int? msDelay, EasingFunctionNames easingFunction, bool reverse = false, Action callback = null, bool useFixedSize = false) - { - await element.ManageCompositionRollAnimationAsync(startOp, endOp, axis, startXY, endXY, startDegrees, endDegrees, ms, msDelay, easingFunction, useFixedSize); - callback?.Invoke(); - } - - /// - /// Starts a roll animation on the target and returns a that completes when the animation ends - /// - /// The to animate - /// The initial opacity value. If null, the current opacity will be used - /// The final opacity value - /// The offset axis - /// The initial offset X and Y value. If null, the current offset will be used - /// The final offset X and Y value - /// The initial angle in degrees - /// The target angle in degrees - /// The duration of the animation, in milliseconds - /// The delay before the animation starts, in milliseconds. If null, there will be no delay - /// The easing function to use with the new animations - /// If true, the animation will be played in reverse mode when it finishes for the first time - /// If true, the fixed and properties - /// will be used, otherwise the center point will be calculated using the and width - public static async Task StartCompositionRollAnimationAsync([NotNull] this FrameworkElement element, - float? startOp, float endOp, - TranslationAxis axis, float? startXY, float endXY, - float? startDegrees, float endDegrees, - int ms, int? msDelay, EasingFunctionNames easingFunction, bool reverse = false, bool useFixedSize = false) - { - CompositeRotationAnimationStartInfo startInfo = await element.ManageCompositionRollAnimationAsync(startOp, endOp, axis, startXY, endXY, startDegrees, endDegrees, ms, msDelay, easingFunction, useFixedSize); - if (reverse) await element.ManageCompositionRollAnimationAsync(endOp, startInfo.Opacity, axis, endXY, startInfo.SecondaryProperty, endDegrees, startInfo.Degrees, ms, msDelay, easingFunction, useFixedSize); - } - - #endregion - - #region Rotation + fade + slide - - // Manages the composite animation - private static async Task ManageCompositionRotationFadeSlideAnimationAsync([NotNull] this FrameworkElement element, - float? startOp, float endOp, - float? startXY, float endXY, - float? startDegrees, float endDegrees, - int ms, int? msDelay, EasingFunctionNames easingFunction, bool useFixedSize) - { - // Get the default values - Visual visual = element.GetVisual(); - visual.StopAnimation("Opacity"); - visual.StopAnimation("Scale"); - visual.StopAnimation("RotationAngle"); - if (useFixedSize) element.SetFixedCenterPoint(visual); - else await element.SetCenterPointAsync(visual); - - // Get the current opacity - if (!startOp.HasValue) startOp = visual.Opacity; - - // Get the easing function, the duration and delay - CompositionEasingFunction ease = visual.GetEasingFunction(easingFunction); - TimeSpan duration = TimeSpan.FromMilliseconds(ms); - TimeSpan? delay; - if (msDelay.HasValue) delay = TimeSpan.FromMilliseconds(msDelay.Value); - else delay = null; - - // Calculate the initial and final scale values - Vector3 initialScale = visual.Scale; - if (startXY.HasValue) - { - initialScale.X = startXY.Value; - initialScale.Y = startXY.Value; - } - Vector3 endScale = new Vector3 - { - X = endXY, - Y = endXY, - Z = visual.Scale.Z - }; - - // Scale animation - Vector3KeyFrameAnimation scaleAnimation = visual.Compositor.CreateVector3KeyFrameAnimation(initialScale, endScale, duration, delay, ease); - - // Calculate the initial and final rotation angle - if (startDegrees == null) startDegrees = visual.RotationAngle.ToDegrees(); - - // Get the opacity the animation - ScalarKeyFrameAnimation opacityAnimation = visual.Compositor.CreateScalarKeyFrameAnimation(startOp, endOp, duration, delay, ease); - - // Rotate animation - ScalarKeyFrameAnimation rotateAnimation = visual.Compositor.CreateScalarKeyFrameAnimation(startDegrees.Value.ToRadians(), endDegrees.ToRadians(), duration, delay, ease); - - // Get the batch and start the animations - CompositionScopedBatch batch = visual.Compositor.CreateScopedBatch(CompositionBatchTypes.Animation); - TaskCompletionSource tcs = new TaskCompletionSource(); - batch.Completed += (s, e) => tcs.SetResult(null); - visual.StartAnimation("Opacity", opacityAnimation); - visual.StartAnimation("Scale", scaleAnimation); - visual.StartAnimation("RotationAngle", rotateAnimation); - batch.End(); - await tcs.Task; - return new CompositeRotationAnimationStartInfo(startOp.Value, initialScale.X, startDegrees.Value); - } - - /// - /// Starts a rotation, fade and slide animation on the target and optionally runs a callback when the animation finishes - /// - /// The to animate - /// The initial opacity value. If null, the current opacity will be used - /// The final opacity value - /// The initial scale value. If null, the current scale will be used - /// The final scale value - /// The initial angle in degrees - /// The target angle in degrees - /// The duration of the animation, in milliseconds - /// The delay before the animation starts, in milliseconds. If null, there will be no delay - /// The easing function to use with the new animations - /// If true, the animation will be played in reverse mode when it finishes for the first time - /// An to execute when the new animations end - /// If true, the fixed and properties - /// will be used, otherwise the center point will be calculated using the and width - public static async void StartCompositionRotationFadeSlideAnimation([NotNull] this FrameworkElement element, - float? startOp, float endOp, - float? startXY, float endXY, - float? startDegrees, float endDegrees, - int ms, int? msDelay, EasingFunctionNames easingFunction, bool reverse = false, Action callback = null, bool useFixedSize = false) - { - await element.ManageCompositionRotationFadeSlideAnimationAsync(startOp, endOp, startXY, endXY, startDegrees, endDegrees, ms, msDelay, easingFunction, useFixedSize); - callback?.Invoke(); - } - - /// - /// Starts a rotation, fade and slide animation on the target and returns a that completes when the animation ends - /// - /// The to animate - /// The initial opacity value. If null, the current opacity will be used - /// The final opacity value - /// The initial scale value. If null, the current scale will be used - /// The final scale value - /// The initial angle in degrees - /// The target angle in degrees - /// The duration of the animation, in milliseconds - /// The delay before the animation starts, in milliseconds. If null, there will be no delay - /// The easing function to use with the new animations - /// If true, the animation will be played in reverse mode when it finishes for the first time - /// If true, the fixed and properties - /// will be used, otherwise the center point will be calculated using the and width - public static async Task StartCompositionRotationFadeSlideAnimationAsync([NotNull] this FrameworkElement element, - float? startOp, float endOp, - float? startXY, float endXY, - float? startDegrees, float endDegrees, - int ms, int? msDelay, EasingFunctionNames easingFunction, bool reverse = false, bool useFixedSize = false) - { - CompositeRotationAnimationStartInfo startInfo = await element.ManageCompositionRotationFadeSlideAnimationAsync(startOp, endOp, startXY, endXY, startDegrees, endDegrees, ms, msDelay, easingFunction, useFixedSize); - if (reverse) await element.ManageCompositionRotationFadeSlideAnimationAsync(endOp, startInfo.Opacity, endXY, startInfo.SecondaryProperty, endDegrees, startInfo.Degrees, ms, msDelay, easingFunction, useFixedSize); - } - - #endregion - - #region Clip - - // Manages the clip animation - private static Task ManageCompositionClipAnimationAsync([NotNull] Visual visual, - float? start, float end, MarginSide side, - int ms, int? msDelay, [NotNull] CompositionEasingFunction easingFunction) - { - // Setup - InsetClip clip = visual.Clip as InsetClip ?? (InsetClip)(visual.Clip = visual.Compositor.CreateInsetClip()); - string property; - switch (side) - { - case MarginSide.Top: - property = nameof(InsetClip.TopInset); - if (!start.HasValue) start = clip.TopInset; - break; - case MarginSide.Bottom: - property = nameof(InsetClip.BottomInset); - if (!start.HasValue) start = clip.BottomInset; - break; - case MarginSide.Right: - property = nameof(InsetClip.RightInset); - if (!start.HasValue) start = clip.RightInset; - break; - case MarginSide.Left: - property = nameof(InsetClip.LeftInset); - if (!start.HasValue) start = clip.LeftInset; - break; - default: throw new ArgumentException("Invalid side", nameof(side)); - } - - // Get the default values - clip.StopAnimation(property); - - // Get the easing function, the duration and delay - TimeSpan duration = TimeSpan.FromMilliseconds(ms); - TimeSpan? delay; - if (msDelay.HasValue) delay = TimeSpan.FromMilliseconds(msDelay.Value); - else delay = null; - - // Get the opacity animation - ScalarKeyFrameAnimation animation = clip.Compositor.CreateScalarKeyFrameAnimation(start, end, duration, delay, easingFunction); - - // Close the batch and manage its event - CompositionScopedBatch batch = clip.Compositor.CreateScopedBatch(CompositionBatchTypes.Animation); - TaskCompletionSource tcs = new TaskCompletionSource(); - batch.Completed += (s, e) => tcs.SetResult(null); - clip.StartAnimation(property, animation); - batch.End(); - return tcs.Task; - } - - /// - /// Starts a clip animation on the target and optionally runs a callback when the animation finishes - /// - /// The to animate - /// The initial clip value. If null, the current clip value will be used - /// The final clip value - /// The clip side to animate - /// The duration of the animation, in milliseconds - /// The delay before the animation starts, in milliseconds. If null, there will be no delay - /// The easing function to use with the new animations - /// An to execute when the new animations end - public static async void StartCompositionClipAnimation([NotNull] this UIElement element, - float? start, float end, MarginSide side, - int ms, int? msDelay, EasingFunctionNames easingFunction, Action callback = null) - { - await StartCompositionClipAnimationAsync(element, start, end, side, ms, msDelay, easingFunction); - callback?.Invoke(); - } - - /// - /// Starts a clip animation on the target and returns a that completes when the animation ends - /// - /// The to animate - /// The initial clip value. If null, the current clip value will be used - /// The final clip value - /// The clip side to animate - /// The duration of the animation, in milliseconds - /// The delay before the animation starts, in milliseconds. If null, there will be no delay - /// The easing function to use with the new animations - public static Task StartCompositionClipAnimationAsync([NotNull] this UIElement element, - float? start, float end, MarginSide side, - int ms, int? msDelay, EasingFunctionNames easingFunction) - { - Visual visual = element.GetVisual(); - CompositionEasingFunction ease = visual.GetEasingFunction(easingFunction); - return ManageCompositionClipAnimationAsync(visual, start, end, side, ms, msDelay, ease); - } - - #endregion - - #region Expression animations - - /// - /// Creates and starts an animation on the target element that binds either the X or Y axis of the source - /// - /// The target that will be animated - /// The source control to use - /// The scrolling axis of the source - /// The optional scrolling axis of the target element, if null the source axis will be used - /// Indicates whether or not to invert the animation from the source - [NotNull] - public static ExpressionAnimation StartExpressionAnimation( - [NotNull] this UIElement element, [NotNull] ScrollViewer scroller, - TranslationAxis sourceXY, TranslationAxis? targetXY = null, bool invertSourceAxis = false) - { - CompositionPropertySet scrollSet = ElementCompositionPreview.GetScrollViewerManipulationPropertySet(scroller); - string sign = invertSourceAxis ? "-" : string.Empty; - ExpressionAnimation animation = scrollSet.Compositor.CreateExpressionAnimation($"{sign}scroll.Translation.{sourceXY}"); - animation.SetReferenceParameter("scroll", scrollSet); - element.GetVisual().StartAnimation($"Offset.{targetXY ?? sourceXY}", animation); - return animation; - } - - /// - /// Creates and starts an animation on the target element, with the addition of a scalar parameter in the resulting - /// - /// The target that will be animated - /// The source control to use - /// The scrolling axis of the source - /// An additional parameter that will be included in the expression animation - /// The optional scrolling axis of the target element, if null the source axis will be used - /// Indicates whether or not to invert the animation from the source - [NotNull] - public static ExpressionAnimationWithScalarParameter StartExpressionAnimation( - [NotNull] this UIElement element, [NotNull] ScrollViewer scroller, - TranslationAxis sourceXY, float parameter, - TranslationAxis? targetXY = null, bool invertSourceAxis = false) - { - // Get the property set and setup the scroller offset sign - CompositionPropertySet scrollSet = ElementCompositionPreview.GetScrollViewerManipulationPropertySet(scroller); - string sign = invertSourceAxis ? "-" : "+"; - - // Prepare the second property set to insert the additional parameter - CompositionPropertySet properties = scroller.GetVisual().Compositor.CreatePropertySet(); - properties.InsertScalar(nameof(parameter), parameter); - - // Create and start the animation - ExpressionAnimation animation = scrollSet.Compositor.CreateExpressionAnimation($"{nameof(properties)}.{nameof(parameter)} {sign} scroll.Translation.{sourceXY}"); - animation.SetReferenceParameter("scroll", scrollSet); - animation.SetReferenceParameter(nameof(properties), properties); - element.GetVisual().StartAnimation($"Offset.{targetXY ?? sourceXY}", animation); - return new ExpressionAnimationWithScalarParameter(animation, properties, nameof(parameter)); - } - - #endregion - - #region Shadows - - /// - /// Creates a object from the given - /// - /// The source element for the shadow - /// The optional target element to apply the shadow to (it can be the same as the source element) - /// Indicates whether or not to immediately add the shadow to the visual tree - /// The optional width of the shadow (if null, the element width will be used) - /// The optional height of the shadow (if null, the element height will be used) - /// The shadow color (the default is - /// The opacity of the shadow - /// The optional horizontal offset of the shadow - /// The optional vertical offset of the shadow - /// The optional margin of the clip area of the shadow - /// The optional horizontal offset of the clip area of the shadow - /// The optional vertical offset of the clip area of the shadow - /// The optional explicit shadow blur radius - /// The optional to use to create an alpha mask for the shadow - /// The object that hosts the shadow - public static SpriteVisual AttachVisualShadow( - [NotNull] this FrameworkElement element, [NotNull] UIElement target, bool apply, - float? width, float? height, - Color color, float opacity, - float offsetX = 0, float offsetY = 0, - Thickness? clipMargin = null, float clipOffsetX = 0, float clipOffsetY = 0, - float? blurRadius = null, [CanBeNull] UIElement maskElement = null) - { - // Setup the shadow - Visual elementVisual = ElementCompositionPreview.GetElementVisual(element); - Compositor compositor = elementVisual.Compositor; - SpriteVisual sprite = compositor.CreateSpriteVisual(); - DropShadow shadow = compositor.CreateDropShadow(); - shadow.Color = color; - shadow.Opacity = opacity; - shadow.Offset = new Vector3(offsetX, offsetY, 0); - if (blurRadius != null) shadow.BlurRadius = blurRadius.Value; - sprite.Shadow = shadow; - sprite.Size = new Vector2(width ?? (float)element.Width, height ?? (float)element.Height); - - // Clip it (if needed) and add it to the visual tree - if (clipMargin != null || clipOffsetX > 0 || clipOffsetY > 0) - { - InsetClip clip = compositor.CreateInsetClip( - (float)(clipMargin?.Left ?? 0), (float)(clipMargin?.Top ?? 0), - (float)(clipMargin?.Right ?? 0), (float)(clipMargin?.Bottom ?? 0)); - clip.Offset = new Vector2(clipOffsetX, clipOffsetY); - sprite.Clip = clip; - } - - // Alpha mask - switch (maskElement) - { - case null: break; - case Shape shape: shadow.Mask = shape.GetAlphaMask(); break; - case Image image: shadow.Mask = image.GetAlphaMask(); break; - case TextBlock textBlock: shadow.Mask = textBlock.GetAlphaMask(); break; - } - if (apply) ElementCompositionPreview.SetElementChildVisual(target, sprite); - return sprite; - } - - #endregion - - #region Utility extensions - - /// - /// Sets a target property to the given value - /// - /// The target object - /// The name of the property to animate - /// The final value of the property - public static void SetInstantValue([NotNull] this CompositionObject compObject, string property, float value) - { - // Stop previous animations - compObject.StopAnimation(property); - - // Setup the animation - ScalarKeyFrameAnimation animation = compObject.Compositor.CreateScalarKeyFrameAnimation(); - animation.InsertKeyFrame(1f, value); - animation.Duration = TimeSpan.FromMilliseconds(1); - compObject.StartAnimation(property, animation); - } - - /// - /// Starts an animation on the given property of a composition object - /// - /// The target object - /// The name of the property to animate - /// The final value of the property - /// The animation duration - public static Task StartAnimationAsync([NotNull] this CompositionObject compObject, string property, float value, TimeSpan duration) - { - // Stop previous animations - compObject.StopAnimation(property); - - // Setup the animation - ScalarKeyFrameAnimation animation = compObject.Compositor.CreateScalarKeyFrameAnimation(); - animation.InsertKeyFrame(1f, value); - animation.Duration = duration; - - // Get the batch and start the animations - CompositionScopedBatch batch = compObject.Compositor.CreateScopedBatch(CompositionBatchTypes.Animation); - TaskCompletionSource tcs = new TaskCompletionSource(); - batch.Completed += (s, e) => tcs.SetResult(null); - compObject.StartAnimation(property, animation); - batch.End(); - return tcs.Task; - } - - /// - /// Stops the animations with the target names on the given element - /// - /// The target element - /// The names of the animations to stop - public static void StopAnimations([NotNull] this UIElement element, [NotNull, ItemNotNull] params string[] properties) - { - if (properties.Length == 0) return; - Visual visual = element.GetVisual(); - foreach (string property in properties) visual.StopAnimation(property); - } - - /// - /// Sets the scale property of the visual object for a given - /// - /// The target element - /// The X value of the scale property - /// The Y value of the scale property - /// The Z value of the scale property - public static void SetVisualScale([NotNull] this UIElement element, float? x, float? y, float? z) - { - // Get the default values and set the CenterPoint - Visual visual = element.GetVisual(); - - // Set the scale property - if (x == null && y == null && z == null) return; - Vector3 targetScale = new Vector3 - { - X = x ?? visual.Scale.X, - Y = y ?? visual.Scale.Y, - Z = z ?? visual.Scale.Z - }; - visual.Scale = targetScale; - } - - /// - /// Sets the scale property of the visual object for a given and sets the center point to the center of the element - /// - /// The target element - /// The X value of the scale property - /// The Y value of the scale property - /// The Z value of the scale property - public static async Task SetVisualScaleAsync([NotNull] this FrameworkElement element, float? x, float? y, float? z) - { - // Get the default values and set the CenterPoint - Visual visual = element.GetVisual(); - await element.SetCenterPointAsync(visual); - - // Set the scale property - if (x == null && y == null && z == null) return; - Vector3 targetScale = new Vector3 - { - X = x ?? visual.Scale.X, - Y = y ?? visual.Scale.Y, - Z = z ?? visual.Scale.Z - }; - visual.Scale = targetScale; - } - - /// - /// Sets the offset property of the visual object for a given object - /// - /// The target element - /// The offset axis to edit - /// The final offset value to set for that axis - public static void SetVisualOffset([NotNull] this UIElement element, TranslationAxis axis, float offset) - { - // Get the element visual and stop the animation - Visual visual = element.GetVisual(); - - // Set the desired offset - Vector3 endOffset = visual.Offset; - if (axis == TranslationAxis.X) endOffset.X = offset; - else endOffset.Y = offset; - visual.Offset = endOffset; - } - - /// - /// Sets the offset property of the visual object for a given object - /// - /// The target element - /// The x offset - /// The y offset - public static void SetVisualOffset([NotNull] this UIElement element, float x, float y) - { - // Get the element visual and stop the animation - Visual visual = element.GetVisual(); - - // Set the desired offset - Vector3 offset = new Vector3(x, y, visual.Offset.Z); - visual.Offset = offset; - } - - /// - /// Sets the offset value of a given object - /// - /// The to edit - /// The offset axis to set - /// The new value for the axis to set - public static Task SetVisualOffsetAsync([NotNull] this UIElement element, TranslationAxis axis, float value) - { - Visual visual = element.GetVisual(); - return Task.Run(() => - { - Vector3 offset = visual.Offset; - if (axis == TranslationAxis.X) offset.X = value; - else offset.Y = value; - visual.Offset = offset; - }); - } - - /// - /// Sets the offset value of a given object - /// - /// The to edit - /// The x offset - /// The y offset - public static Task SetVisualOffsetAsync([NotNull] this UIElement element, float x, float y) - { - Visual visual = element.GetVisual(); - return Task.Run(() => - { - Vector3 offset = new Vector3(x, y, visual.Offset.Z); - visual.Offset = offset; - }); - } - - /// - /// Returns the visual offset for a target - /// - /// The input element - public static Vector3 GetVisualOffset([NotNull] this UIElement element) => element.GetVisual().Offset; - - /// - /// Sets the translation property of the visual object for a given object - /// - /// The target element - /// The translation axis to edit - /// The final translation value to set for that axis - public static void SetVisualTranslation([NotNull] this UIElement element, TranslationAxis axis, float translation) - { - // Get the element visual and stop the animation - Visual visual = element.GetVisual(); - - // Set the desired translation - /* [1.0, 0.0, 0.0, 0.0, - 0.0, 1.0, 0.0, 0.0, - 0.0, 0.0, 1.0, 0.0, - translation.X, translation.Y, translation.Z, 1.0] */ - if (axis == TranslationAxis.X) - { - float y = visual.TransformMatrix.M42; - visual.TransformMatrix = Matrix4x4.CreateTranslation(translation, y, 0); - } - else - { - float x = visual.TransformMatrix.M41; - visual.TransformMatrix = Matrix4x4.CreateTranslation(x, translation, 0); - } - } - - /// - /// Sets the translation property of the visual object for a given object - /// - /// The target element - /// The x translation - /// The y translation - public static void SetVisualTranslation([NotNull] this UIElement element, float x, float y) - { - // Get the element visual and stop the animation - Visual visual = element.GetVisual(); - - // Set the desired offset - visual.TransformMatrix = Matrix4x4.CreateTranslation(x, y, 0); - } - - /// - /// Sets the clip property of the visual object for a given object - /// - /// The target element - /// The desired clip margins to set - public static void SetVisualClip([NotNull] this UIElement element, Thickness clip) - { - // Get the element visual - Visual visual = element.GetVisual(); - - // Set the desired clip - InsetClip inset = visual.Clip as InsetClip ?? (InsetClip)(visual.Clip = visual.Compositor.CreateInsetClip()); - inset.TopInset = (float)clip.Top; - inset.BottomInset = (float)clip.Bottom; - inset.LeftInset = (float)clip.Left; - inset.RightInset = (float)clip.Right; - visual.Clip = inset; - } - - /// - /// Sets the clip property of the visual object for a given object - /// - /// The target element - /// The desired clip value to set - /// The target clip side to update - public static void SetVisualClip([NotNull] this UIElement element, float clip, MarginSide side) - { - // Get the element visual - Visual visual = element.GetVisual(); - - // Set the desired clip - InsetClip inset = visual.Clip as InsetClip ?? (InsetClip)(visual.Clip = visual.Compositor.CreateInsetClip()); - switch (side) - { - case MarginSide.Top: inset.TopInset = clip; break; - case MarginSide.Bottom: inset.BottomInset = clip; break; - case MarginSide.Right: inset.RightInset = clip; break; - case MarginSide.Left: inset.LeftInset = clip; break; - default: throw new ArgumentException("Invalid side", nameof(side)); - } - } - - /// - /// Resets the scale, offset and opacity properties for a framework element - /// - /// The element to edit - public static async Task ResetCompositionVisualPropertiesAsync([NotNull] this FrameworkElement element) - { - // Get the default values and set the CenterPoint - Visual visual = element.GetVisual(); - visual.StopAnimation("Scale"); - visual.StopAnimation("Offset"); - visual.StopAnimation("Opacity"); - await element.SetCenterPointAsync(visual); - - // Reset the visual properties - visual.Scale = Vector3.One; - visual.Offset = Vector3.Zero; - visual.Opacity = 1.0f; - } - - /// - /// Gets the opacity for the Visual object behind a given UIElement - /// - /// The source UIElement - public static float GetVisualOpacity([NotNull] this UIElement element) => element.GetVisual().Opacity; - - /// - /// Sets the opacity for the Visual object behind a given UIElement - /// - /// The source UIElement - /// The new opacity value - public static void SetVisualOpacity([NotNull] this UIElement element, float value) => element.GetVisual().Opacity = value; - - /// - /// Returns the Visual object for a given UIElement - /// - /// The source UIElement - public static Visual GetVisual([NotNull] this UIElement element) => ElementCompositionPreview.GetElementVisual(element); - - /// - /// Adds a instance on top of the target and binds the size of the two items with an expression animation - /// - /// The instance to display - /// The target that will host the effect - public static void AttachToElement([NotNull] this CompositionBrush brush, [NotNull] FrameworkElement target) - { - // Add the brush to a sprite and attach it to the target element - SpriteVisual sprite = Window.Current.Compositor.CreateSpriteVisual(); - sprite.Brush = brush; - sprite.Size = new Vector2((float)target.ActualWidth, (float)target.ActualHeight); - ElementCompositionPreview.SetElementChildVisual(target, sprite); - - // Keep the sprite size in sync - sprite.BindSize(target); - } - - /// - /// Starts an expression animation to keep the size of the source in sync with the target - /// - /// The composition object to start the animation on - /// The target to read the size updates from - public static void BindSize([NotNull] this CompositionObject source, [NotNull] UIElement target) - { - Visual visual = target.GetVisual(); - ExpressionAnimation bindSizeAnimation = Window.Current.Compositor.CreateExpressionAnimation($"{nameof(visual)}.Size"); - bindSizeAnimation.SetReferenceParameter(nameof(visual), visual); - - // Start the animation - source.StartAnimation("Size", bindSizeAnimation); - } - - /// - /// Tries to retrieve the instance of the input - /// - /// The source instance - /// The resulting , if existing - [MustUseReturnValue] - public static bool TryGetDispatcher([NotNull] this CompositionObject source, out CoreDispatcher dispatcher) - { - try - { - dispatcher = source.Dispatcher; - return true; - } - catch (ObjectDisposedException) - { - // I'm sorry Jack, I was too late! :'( - dispatcher = null; - return false; - } - } - - #endregion - } -} - \ No newline at end of file diff --git a/UICompositionAnimations/Enums/AcrylicBrushEffectState.cs b/UICompositionAnimations/Enums/AcrylicBrushEffectState.cs deleted file mode 100644 index a2856e0..0000000 --- a/UICompositionAnimations/Enums/AcrylicBrushEffectState.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace UICompositionAnimations.Enums -{ - /// - /// Indicates the internal state of the acrylic brush - /// - internal enum AcrylicBrushEffectState - { - /// - /// The composition effect is enabled with the current settings - /// - EffectEnabled, - - /// - /// The current effect is not supported and a fallback color is being used - /// - FallbackMode, - - /// - /// Default state, no visible effects - /// - Default - } -} \ No newline at end of file diff --git a/UICompositionAnimations/Enums/AcrylicEffectMode.cs b/UICompositionAnimations/Enums/AcrylicEffectMode.cs deleted file mode 100644 index 11b02f1..0000000 --- a/UICompositionAnimations/Enums/AcrylicEffectMode.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; - -namespace UICompositionAnimations.Enums -{ - /// - /// Indicates the UI mode for an acrylic effect brush - /// - [Flags] - public enum AcrylicEffectMode - { - /// - /// The source content is the blurred UI of the application window - /// - InAppBlur = 1, - - /// - /// The source content is the host screen - /// - HostBackdrop = 1 << 1 - } -} \ No newline at end of file diff --git a/UICompositionAnimations/Enums/ImplicitAnimationType.cs b/UICompositionAnimations/Enums/AnimationType.cs similarity index 92% rename from UICompositionAnimations/Enums/ImplicitAnimationType.cs rename to UICompositionAnimations/Enums/AnimationType.cs index 191a086..da6f558 100644 --- a/UICompositionAnimations/Enums/ImplicitAnimationType.cs +++ b/UICompositionAnimations/Enums/AnimationType.cs @@ -6,7 +6,7 @@ namespace UICompositionAnimations.Enums /// Indicates the type of an implicit composition animation /// [PublicAPI] - public enum ImplicitAnimationType + public enum AnimationType { /// /// The animation plays when the item becomes visible diff --git a/UICompositionAnimations/Enums/TranslationAxis.cs b/UICompositionAnimations/Enums/Axis.cs similarity index 92% rename from UICompositionAnimations/Enums/TranslationAxis.cs rename to UICompositionAnimations/Enums/Axis.cs index 8c68a6d..0bdcb42 100644 --- a/UICompositionAnimations/Enums/TranslationAxis.cs +++ b/UICompositionAnimations/Enums/Axis.cs @@ -6,7 +6,7 @@ namespace UICompositionAnimations.Enums /// Indicates the translation axis to use in an animation /// [PublicAPI] - public enum TranslationAxis + public enum Axis { /// /// Horizontal translation diff --git a/UICompositionAnimations/Enums/BitmapCacheMode.cs b/UICompositionAnimations/Enums/CacheMode.cs similarity index 94% rename from UICompositionAnimations/Enums/BitmapCacheMode.cs rename to UICompositionAnimations/Enums/CacheMode.cs index 451a86e..7e7f65b 100644 --- a/UICompositionAnimations/Enums/BitmapCacheMode.cs +++ b/UICompositionAnimations/Enums/CacheMode.cs @@ -6,7 +6,7 @@ namespace UICompositionAnimations.Enums /// Indicates the cache mode to use when loading a Win2D image /// [PublicAPI] - public enum BitmapCacheMode + public enum CacheMode { /// /// The default behavior, the cache is enabled diff --git a/UICompositionAnimations/Enums/BitmapDPIMode.cs b/UICompositionAnimations/Enums/DpiMode.cs similarity index 80% rename from UICompositionAnimations/Enums/BitmapDPIMode.cs rename to UICompositionAnimations/Enums/DpiMode.cs index dac988d..94c44ec 100644 --- a/UICompositionAnimations/Enums/BitmapDPIMode.cs +++ b/UICompositionAnimations/Enums/DpiMode.cs @@ -3,26 +3,26 @@ /// /// Indicates the DPI mode to use to load an image /// - public enum BitmapDPIMode + public enum DpiMode { /// /// Uses the original DPI settings of the loaded image /// - UseSourceDPI, + UseSourceDpi, /// /// Uses the default value of 96 DPI /// - Default96DPI, + Default96Dpi, /// /// Overrides the image DPI settings with the current screen DPI value /// - CopyDisplayDPISetting, + DisplayDpi, /// /// Overrides the image DPI settings with the current screen DPI value and ensures the resulting value is at least 96 /// - CopyDisplayDPISettingsWith96AsLowerBound + DisplayDpiWith96AsLowerBound } } \ No newline at end of file diff --git a/UICompositionAnimations/Enums/EasingFunctionNames.cs b/UICompositionAnimations/Enums/Easing.cs similarity index 79% rename from UICompositionAnimations/Enums/EasingFunctionNames.cs rename to UICompositionAnimations/Enums/Easing.cs index ce93c9e..8556b61 100644 --- a/UICompositionAnimations/Enums/EasingFunctionNames.cs +++ b/UICompositionAnimations/Enums/Easing.cs @@ -1,10 +1,11 @@ -#pragma warning disable 1591 +#pragma warning disable 1591 // Missing XML comments in the enum values + namespace UICompositionAnimations.Enums { /// /// Indicates an easing function for an animation /// - public enum EasingFunctionNames + public enum Easing { Linear, SineEaseIn, diff --git a/UICompositionAnimations/Enums/FrameworkLayer.cs b/UICompositionAnimations/Enums/FrameworkLayer.cs new file mode 100644 index 0000000..52e3952 --- /dev/null +++ b/UICompositionAnimations/Enums/FrameworkLayer.cs @@ -0,0 +1,21 @@ +using JetBrains.Annotations; + +namespace UICompositionAnimations.Enums +{ + /// + /// An that indicates the framework layer to target in a specific animation + /// + [PublicAPI] + public enum FrameworkLayer + { + /// + /// Indicates the APIs + /// + Composition, + + /// + /// Indicates the APIs + /// + Xaml + } +} \ No newline at end of file diff --git a/UICompositionAnimations/Enums/EffectPlacement.cs b/UICompositionAnimations/Enums/Placement.cs similarity index 95% rename from UICompositionAnimations/Enums/EffectPlacement.cs rename to UICompositionAnimations/Enums/Placement.cs index 5c61248..d74cbb3 100644 --- a/UICompositionAnimations/Enums/EffectPlacement.cs +++ b/UICompositionAnimations/Enums/Placement.cs @@ -7,7 +7,7 @@ namespace UICompositionAnimations.Enums /// An used to modify the default placement of the input instance in a blend operation /// [PublicAPI] - public enum EffectPlacement + public enum Placement { /// /// The instance used to call the blend method is placed on top of the other diff --git a/UICompositionAnimations/Enums/MarginSide.cs b/UICompositionAnimations/Enums/Side.cs similarity index 95% rename from UICompositionAnimations/Enums/MarginSide.cs rename to UICompositionAnimations/Enums/Side.cs index 90fc418..d1f4c89 100644 --- a/UICompositionAnimations/Enums/MarginSide.cs +++ b/UICompositionAnimations/Enums/Side.cs @@ -3,7 +3,7 @@ /// /// Indicates a side to animate in the margin of a given element /// - public enum MarginSide + public enum Side { /// /// Maps the top side of the target margin diff --git a/UICompositionAnimations/Extensions/EnumExtensions.cs b/UICompositionAnimations/Extensions/EnumExtensions.cs new file mode 100644 index 0000000..5af47e9 --- /dev/null +++ b/UICompositionAnimations/Extensions/EnumExtensions.cs @@ -0,0 +1,34 @@ +using System; +using Windows.UI.Xaml.Media.Animation; +using UICompositionAnimations.Enums; + +namespace UICompositionAnimations.Extensions +{ + /// + /// An extension for the types in the library + /// + internal static class EnumExtensions + { + /// + /// Converts an easing value to the right easing function + /// + /// The desired easing function + public static EasingFunctionBase ToEasingFunction(this Easing ease) + { + switch (ease) + { + case Easing.Linear: return null; + case Easing.SineEaseIn: return new SineEase { EasingMode = EasingMode.EaseIn }; + case Easing.SineEaseOut: return new SineEase { EasingMode = EasingMode.EaseOut }; + case Easing.SineEaseInOut: return new SineEase { EasingMode = EasingMode.EaseInOut }; + case Easing.QuadraticEaseIn: return new QuadraticEase { EasingMode = EasingMode.EaseIn }; + case Easing.QuadraticEaseOut: return new QuadraticEase { EasingMode = EasingMode.EaseOut }; + case Easing.QuadraticEaseInOut: return new QuadraticEase { EasingMode = EasingMode.EaseInOut }; + case Easing.CircleEaseIn: return new CircleEase { EasingMode = EasingMode.EaseIn }; + case Easing.CircleEaseOut: return new CircleEase { EasingMode = EasingMode.EaseOut }; + case Easing.CircleEaseInOut: return new CircleEase { EasingMode = EasingMode.EaseInOut }; + default: throw new ArgumentOutOfRangeException(nameof(ease), ease, "This shouldn't happen"); + } + } + } +} diff --git a/UICompositionAnimations/Extensions/System/BaseExtensions.cs b/UICompositionAnimations/Extensions/System/BaseExtensions.cs new file mode 100644 index 0000000..15b822d --- /dev/null +++ b/UICompositionAnimations/Extensions/System/BaseExtensions.cs @@ -0,0 +1,52 @@ +using System.Runtime.CompilerServices; +using JetBrains.Annotations; + +namespace System +{ + /// + /// An extension for the + /// + public static class BaseExtensions + { + /// + /// Performs a direct cast on the given + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Pure] + public static T To(this object o) => (T)o; + + /// + /// Converts an angle in radians to degrees + /// + /// The value to convert + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Pure] + public static float ToDegrees(this float radians) => (float)(Math.PI * radians / 180.0); + + /// + /// Converts an angle in degrees to radians + /// + /// The value to convert + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Pure] + public static float ToRadians(this float degrees) => (float)(Math.PI / 180 * degrees); + + /// + /// Returns an that starts with the ms-appx:// prefix + /// + /// The input to process + [Pure, NotNull] + internal static Uri ToAppxUri([NotNull] this Uri uri) + { + if (uri.Scheme.Equals("ms-resource")) + { + string path = uri.AbsolutePath.StartsWith("/Files") + ? uri.AbsolutePath.Replace("/Files", string.Empty) + : uri.AbsolutePath; + return new Uri($"ms-appx://{path}"); + } + + return uri; + } + } +} diff --git a/UICompositionAnimations/Extensions/System/Collections.Generic/GenericExtensions.cs b/UICompositionAnimations/Extensions/System/Collections.Generic/GenericExtensions.cs new file mode 100644 index 0000000..13c2c31 --- /dev/null +++ b/UICompositionAnimations/Extensions/System/Collections.Generic/GenericExtensions.cs @@ -0,0 +1,37 @@ +using System.Linq; +using JetBrains.Annotations; + +namespace System.Collections.Generic +{ + /// + /// An extension for the + /// + internal static class GenericExtensions + { + /// + /// Merges the two input instances and makes sure no duplicate keys are present + /// + /// The first to merge + /// The second to merge + [Pure, NotNull] + public static IReadOnlyDictionary Merge( + [NotNull] this IReadOnlyDictionary a, + [NotNull] IReadOnlyDictionary b) + { + if (a.Keys.FirstOrDefault(b.ContainsKey) is TKey key) throw new InvalidOperationException($"The key {key} already exists in the current pipeline"); + return new Dictionary(a.Concat(b)); + } + + /// + /// Merges the two input instances and makes sure no duplicate items are present + /// + /// The first to merge + /// The second to merge + [Pure, NotNull, ItemNotNull] + public static IReadOnlyCollection Merge([NotNull, ItemNotNull] this IReadOnlyCollection a, [NotNull, ItemNotNull] IReadOnlyCollection b) + { + if (a.Any(b.Contains)) throw new InvalidOperationException("The input collection has at least an item already present in the second collection"); + return a.Concat(b).ToArray(); + } + } +} diff --git a/UICompositionAnimations/Helpers/AsyncMutex.cs b/UICompositionAnimations/Extensions/System/Threading.Tasks/AsyncMutex.cs similarity index 97% rename from UICompositionAnimations/Helpers/AsyncMutex.cs rename to UICompositionAnimations/Extensions/System/Threading.Tasks/AsyncMutex.cs index 663a6c5..dd61c76 100644 --- a/UICompositionAnimations/Helpers/AsyncMutex.cs +++ b/UICompositionAnimations/Extensions/System/Threading.Tasks/AsyncMutex.cs @@ -6,7 +6,7 @@ namespace System.Threading.Tasks /// /// An implementation that can be easily used inside a block /// - internal sealed class AsyncMutex + public sealed class AsyncMutex { // The underlying semaphore used by this instance [NotNull] @@ -15,8 +15,8 @@ internal sealed class AsyncMutex /// /// Acquires a lock for the current instance, that is automatically released outside the block /// - [NotNull, ItemNotNull] [MethodImpl(MethodImplOptions.AggressiveInlining)] + [NotNull, ItemNotNull] public async Task LockAsync() { await Semaphore.WaitAsync().ConfigureAwait(false); diff --git a/UICompositionAnimations/Extensions/System/Threading.Tasks/TaskCompletionSource.cs b/UICompositionAnimations/Extensions/System/Threading.Tasks/TaskCompletionSource.cs new file mode 100644 index 0000000..afda6ac --- /dev/null +++ b/UICompositionAnimations/Extensions/System/Threading.Tasks/TaskCompletionSource.cs @@ -0,0 +1,18 @@ +namespace System.Threading.Tasks +{ + /// + /// A implementation without the generic argument + /// + public sealed class TaskCompletionSource : TaskCompletionSource + { + /// + /// Transitions the underlying into the state + /// + public void SetResult() => SetResult(Unit.Value); + + /// + /// Attempts to transition the underlying into the state + /// + public void TrySetResult() => TrySetResult(Unit.Value); + } +} diff --git a/UICompositionAnimations/Extensions/System/Unit.cs b/UICompositionAnimations/Extensions/System/Unit.cs new file mode 100644 index 0000000..76071f1 --- /dev/null +++ b/UICompositionAnimations/Extensions/System/Unit.cs @@ -0,0 +1,25 @@ +namespace System +{ + /// + /// An empty that represents the F# unit type + /// + public sealed class Unit : IEquatable, IComparable + { + // Private constructor + private Unit() { } + + /// + /// Gets the default value + /// + public static Unit Value { get; } = new Unit(); + + /// + public bool Equals(Unit other) => other != null; + + /// + public override int GetHashCode() => 0; + + /// + public int CompareTo(Unit other) => 0; + } +} diff --git a/UICompositionAnimations/Extensions/Windows.UI/Composition/CompositionExtensions.cs b/UICompositionAnimations/Extensions/Windows.UI/Composition/CompositionExtensions.cs new file mode 100644 index 0000000..34d2f11 --- /dev/null +++ b/UICompositionAnimations/Extensions/Windows.UI/Composition/CompositionExtensions.cs @@ -0,0 +1,204 @@ +using System; +using System.Numerics; +using System.Threading.Tasks; +using Windows.UI.Core; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Hosting; +using JetBrains.Annotations; +using UICompositionAnimations.Enums; + +namespace Windows.UI.Composition +{ + /// + /// An extension for some composition types + /// + [PublicAPI] + public static class CompositionExtensions + { + #region Easing functions + + /// + /// Creates a from the input control points + /// + /// The source used to create the easing function + /// The X coordinate of the first control point + /// The Y coordinate of the first control point + /// The X coordinate of the second control point + /// The Y coordinate of the second control point + [Pure, NotNull] + public static CubicBezierEasingFunction GetEasingFunction([NotNull] this CompositionObject source, float x1, float y1, float x2, float y2) + { + return source.Compositor.CreateCubicBezierEasingFunction(new Vector2 { X = x1, Y = y1 }, new Vector2 { X = x2, Y = y2 }); + } + + /// + /// Creates the appropriate from the given easing function name + /// + /// The source used to create the easing function + /// The target easing function to create + [Pure, NotNull] + public static CubicBezierEasingFunction GetEasingFunction([NotNull] this CompositionObject source, Easing ease) + { + switch (ease) + { + case Easing.Linear: return source.GetEasingFunction(0, 0, 1, 1); + case Easing.SineEaseIn: return source.GetEasingFunction(0.4f, 0, 1, 1); + case Easing.SineEaseOut: return source.GetEasingFunction(0, 0, 0.6f, 1); + case Easing.SineEaseInOut: return source.GetEasingFunction(0.4f, 0, 0.6f, 1); + case Easing.QuadraticEaseIn: return source.GetEasingFunction(0.8f, 0, 1, 1); + case Easing.QuadraticEaseOut: return source.GetEasingFunction(0, 0, 0.2f, 1); + case Easing.QuadraticEaseInOut: return source.GetEasingFunction(0.8f, 0, 0.2f, 1); + case Easing.CircleEaseIn: return source.GetEasingFunction(1, 0, 1, 0.8f); + case Easing.CircleEaseOut: return source.GetEasingFunction(0, 0.3f, 0, 1); + case Easing.CircleEaseInOut: return source.GetEasingFunction(0.9f, 0, 0.1f, 1); + default: throw new ArgumentOutOfRangeException(nameof(ease), ease, "This shouldn't happen"); + } + } + + #endregion + + #region Animations + + /// + /// Creates and starts a scalar animation on the current + /// + /// The target to animate + /// The path that identifies the property to animate + /// The optional starting value for the animation + /// The final value for the animation + /// The animation duration + /// The optional initial delay for the animation + /// The optional easing function for the animation + public static void BeginScalarAnimation( + [NotNull] this CompositionObject target, + [NotNull] string propertyPath, + float? from, float to, + TimeSpan duration, TimeSpan? delay, + [CanBeNull] CompositionEasingFunction ease = null) + { + target.StartAnimation(propertyPath, target.Compositor.CreateScalarKeyFrameAnimation(from, to, duration, delay, ease)); + } + + /// + /// Creates and starts a animation on the current + /// + /// The target to animate + /// The path that identifies the property to animate + /// The optional starting value for the animation + /// The final value for the animation + /// The animation duration + /// The optional initial delay for the animation + /// The optional easing function for the animation + public static void BeginVector2Animation( + [NotNull]this CompositionObject target, + [NotNull] string propertyPath, + Vector2? from, Vector2 to, + TimeSpan duration, TimeSpan? delay, + [CanBeNull] CompositionEasingFunction ease = null) + { + target.StartAnimation(propertyPath, target.Compositor.CreateVector2KeyFrameAnimation(from, to, duration, delay, ease)); + } + + /// + /// Creates and starts a animation on the current + /// + /// The target to animate + /// The path that identifies the property to animate + /// The optional starting value for the animation + /// The final value for the animation + /// The animation duration + /// The optional initial delay for the animation + /// The optional easing function for the animation + public static void BeginVector3Animation( + [NotNull] this CompositionObject target, + [NotNull] string propertyPath, + Vector3? from, Vector3 to, + TimeSpan duration, TimeSpan? delay, + [CanBeNull] CompositionEasingFunction ease = null) + { + target.StartAnimation(propertyPath, target.Compositor.CreateVector3KeyFrameAnimation(from, to, duration, delay, ease)); + } + + /// + /// Starts an animation on the given property of a + /// + /// The target + /// The name of the property to animate + /// The final value of the property + /// The animation duration + public static Task StartAnimationAsync([NotNull] this CompositionObject target, string property, float value, TimeSpan duration) + { + // Stop previous animations + target.StopAnimation(property); + + // Setup the animation + ScalarKeyFrameAnimation animation = target.Compositor.CreateScalarKeyFrameAnimation(); + animation.InsertKeyFrame(1f, value); + animation.Duration = duration; + + // Get the batch and start the animations + CompositionScopedBatch batch = target.Compositor.CreateScopedBatch(CompositionBatchTypes.Animation); + TaskCompletionSource tcs = new TaskCompletionSource(); + batch.Completed += (s, e) => tcs.SetResult(null); + target.StartAnimation(property, animation); + batch.End(); + return tcs.Task; + } + + #endregion + + #region Misc + + /// + /// Adds a instance on top of the target + /// + /// The instance to display + /// The target that will host the effect + public static void AttachToElement([NotNull] this CompositionBrush brush, [NotNull] FrameworkElement target) + { + // Add the brush to a sprite and attach it to the target element + SpriteVisual sprite = Window.Current.Compositor.CreateSpriteVisual(); + sprite.Brush = brush; + sprite.Size = new Vector2((float)target.ActualWidth, (float)target.ActualHeight); + ElementCompositionPreview.SetElementChildVisual(target, sprite); + } + + /// + /// Starts an to keep the size of the source in sync with the target + /// + /// The to start the animation on + /// The target to read the size updates from + public static void BindSize([NotNull] this CompositionObject source, [NotNull] UIElement target) + { + Visual visual = target.GetVisual(); + ExpressionAnimation bindSizeAnimation = Window.Current.Compositor.CreateExpressionAnimation($"{nameof(visual)}.Size"); + bindSizeAnimation.SetReferenceParameter(nameof(visual), visual); + + // Start the animation + source.StartAnimation("Size", bindSizeAnimation); + } + + /// + /// Tries to retrieve the instance of the input + /// + /// The source instance + /// The resulting , if existing + [MustUseReturnValue] + public static bool TryGetDispatcher([NotNull] this CompositionObject source, out CoreDispatcher dispatcher) + { + try + { + dispatcher = source.Dispatcher; + return true; + } + catch (ObjectDisposedException) + { + // I'm sorry Jack, I was too late! :'( + dispatcher = null; + return false; + } + } + + #endregion + } +} diff --git a/UICompositionAnimations/Extensions/Windows.UI/Composition/CompositorExtensions.cs b/UICompositionAnimations/Extensions/Windows.UI/Composition/CompositorExtensions.cs new file mode 100644 index 0000000..625c7d1 --- /dev/null +++ b/UICompositionAnimations/Extensions/Windows.UI/Composition/CompositorExtensions.cs @@ -0,0 +1,175 @@ +using System; +using System.Numerics; +using JetBrains.Annotations; + +namespace Windows.UI.Composition +{ + /// + /// An extension for the + /// + [PublicAPI] + public static class CompositorExtensions + { + /// + /// Creates a instance with the given parameters to on a target element + /// + /// The current instance used to create the animation + /// The optional starting value for the animation + /// The final value for the animation + /// The animation duration + /// The optional initial delay for the animation + /// The optional easing function for the animation + [Pure, NotNull] + public static ScalarKeyFrameAnimation CreateScalarKeyFrameAnimation( + [NotNull] this Compositor compositor, + float? from, float to, + TimeSpan duration, TimeSpan? delay, + [CanBeNull] CompositionEasingFunction ease = null) + { + // Set duration and delay time + ScalarKeyFrameAnimation ani = compositor.CreateScalarKeyFrameAnimation(); + ani.Duration = duration; + if (delay.HasValue) ani.DelayTime = delay.Value; + + // Insert "to" and "from" keyframes + ani.InsertKeyFrame(1, to, ease ?? compositor.CreateLinearEasingFunction()); + if (from.HasValue) ani.InsertKeyFrame(0, from.Value); + return ani; + } + + /// + /// Creates a instance with the given parameters to on a target element, using an expression animation + /// + /// The current instance used to create the animation + /// The optional starting value for the animation + /// A string that indicates the final value for the animation + /// The animation duration + /// The optional initial delay for the animation + /// The optional easing function for the animation + [Pure, NotNull] + public static ScalarKeyFrameAnimation CreateScalarKeyFrameAnimation( + [NotNull] this Compositor compositor, + float? from, [NotNull] string to, + TimeSpan duration, TimeSpan? delay, + [CanBeNull] CompositionEasingFunction ease = null) + { + // Set duration and delay time + ScalarKeyFrameAnimation ani = compositor.CreateScalarKeyFrameAnimation(); + ani.Duration = duration; + if (delay.HasValue) ani.DelayTime = delay.Value; + + // Insert "to" and "from" keyframes + ani.InsertExpressionKeyFrame(1, to, ease ?? compositor.CreateLinearEasingFunction()); + if (from.HasValue) ani.InsertKeyFrame(0, from.Value); + return ani; + } + + /// + /// Creates a instance with the given parameters to on a target element + /// + /// The current instance used to create the animation + /// The optional starting value for the animation + /// The final value for the animation + /// The animation duration + /// The optional initial delay for the animation + /// The optional easing function for the animation + [Pure, NotNull] + public static Vector2KeyFrameAnimation CreateVector2KeyFrameAnimation( + [NotNull] this Compositor compositor, + Vector2? from, Vector2 to, + TimeSpan duration, TimeSpan? delay, + [CanBeNull] CompositionEasingFunction ease = null) + { + // Set duration and delay time + Vector2KeyFrameAnimation ani = compositor.CreateVector2KeyFrameAnimation(); + ani.Duration = duration; + if (delay.HasValue) ani.DelayTime = delay.Value; + + // Insert "to" and "from" keyframes + ani.InsertKeyFrame(1, to, ease ?? compositor.CreateLinearEasingFunction()); + if (from.HasValue) ani.InsertKeyFrame(0, from.Value); + return ani; + } + + /// + /// Creates a instance with the given parameters to on a target element, using an expression animation + /// + /// The current instance used to create the animation + /// The optional starting value for the animation + /// A string that indicates the final value for the animation + /// The animation duration + /// The optional initial delay for the animation + /// The optional easing function for the animation + [Pure, NotNull] + public static Vector2KeyFrameAnimation CreateVector2KeyFrameAnimation( + [NotNull] this Compositor compositor, + Vector2? from, [NotNull] string to, + TimeSpan duration, TimeSpan? delay, + [CanBeNull] CompositionEasingFunction ease = null) + { + // Set duration and delay time + Vector2KeyFrameAnimation ani = compositor.CreateVector2KeyFrameAnimation(); + ani.Duration = duration; + if (delay.HasValue) ani.DelayTime = delay.Value; + + // Insert "to" and "from" keyframes + ani.InsertExpressionKeyFrame(1, to, ease ?? compositor.CreateLinearEasingFunction()); + if (from.HasValue) ani.InsertKeyFrame(0, from.Value); + return ani; + } + + /// + /// Creates a instance with the given parameters to on a target element + /// + /// The current instance used to create the animation + /// The optional starting value for the animation + /// The final value for the animation + /// The animation duration + /// The optional initial delay for the animation + /// The optional easing function for the animation + [Pure, NotNull] + public static Vector3KeyFrameAnimation CreateVector3KeyFrameAnimation( + [NotNull] this Compositor compositor, + Vector3? from, Vector3 to, + TimeSpan duration, TimeSpan? delay, + [CanBeNull] CompositionEasingFunction ease = null) + { + // Set duration and delay time + Vector3KeyFrameAnimation ani = compositor.CreateVector3KeyFrameAnimation(); + ani.Duration = duration; + if (delay.HasValue) ani.DelayTime = delay.Value; + + // Insert "to" and "from" keyframes + ani.InsertKeyFrame(1, to, ease ?? compositor.CreateLinearEasingFunction()); + if (from.HasValue) ani.InsertKeyFrame(0, from.Value); + return ani; + } + + /// + /// Creates a instance with the given parameters to on a target element, using an expression animation + /// + /// The current instance used to create the animation + /// The optional starting value for the animation + /// A string that indicates the final value for the animation + /// The animation duration + /// The optional initial delay for the animation + /// The optional easing function for the animation + [Pure, NotNull] + public static Vector3KeyFrameAnimation CreateVector3KeyFrameAnimation( + [NotNull] this Compositor compositor, + Vector3? from, [NotNull] string to, + TimeSpan duration, TimeSpan? delay, + [CanBeNull] CompositionEasingFunction ease = null) + { + // Set duration and delay time + Vector3KeyFrameAnimation ani = compositor.CreateVector3KeyFrameAnimation(); + ani.Duration = duration; + if (delay.HasValue) ani.DelayTime = delay.Value; + + // Insert "to" and "from" keyframes + ani.InsertExpressionKeyFrame(1, to, ease ?? compositor.CreateLinearEasingFunction()); + if (from.HasValue) ani.InsertKeyFrame(0, from.Value); + return ani; + } + } +} diff --git a/UICompositionAnimations/Composition/ExpressionAnimationWithScalarParameter.cs b/UICompositionAnimations/Extensions/Windows.UI/Composition/ScalarExpressionAnimation.cs similarity index 68% rename from UICompositionAnimations/Composition/ExpressionAnimationWithScalarParameter.cs rename to UICompositionAnimations/Extensions/Windows.UI/Composition/ScalarExpressionAnimation.cs index e0770d2..fc5bccb 100644 --- a/UICompositionAnimations/Composition/ExpressionAnimationWithScalarParameter.cs +++ b/UICompositionAnimations/Extensions/Windows.UI/Composition/ScalarExpressionAnimation.cs @@ -1,15 +1,13 @@ using System; -using Windows.UI.Composition; using Windows.UI.Xaml; using JetBrains.Annotations; -using UICompositionAnimations.Helpers; -namespace UICompositionAnimations.Composition +namespace Windows.UI.Composition { /// - /// A simple wrapper class around an instance with a custom parameter + /// A simple wrapper around an instance with a custom parameter /// - public sealed class ExpressionAnimationWithScalarParameter : DependencyObject + public sealed class ScalarExpressionAnimation : DependencyObject { /// /// Gets the actual instance that's being played @@ -24,13 +22,12 @@ public sealed class ExpressionAnimationWithScalarParameter : DependencyObject private readonly CompositionPropertySet PropertySet; /// - /// Internal constructor for a target animation + /// Internal constructor for a target /// - /// The new animation that will be played - /// The property set with the custom parameter + /// The that will be played + /// The with the custom parameter /// The name of the custom parameter that will be updated when requested - internal ExpressionAnimationWithScalarParameter([NotNull] ExpressionAnimation animation, - [NotNull] CompositionPropertySet propertySet, [NotNull] string parameterName) + internal ScalarExpressionAnimation([NotNull] ExpressionAnimation animation, [NotNull] CompositionPropertySet propertySet, [NotNull] string parameterName) { Animation = animation; PropertySet = propertySet; @@ -50,19 +47,22 @@ public float Parameter /// Gets the for the property /// public static readonly DependencyProperty ParameterProperty = DependencyProperty.Register( - nameof(Parameter), typeof(float), typeof(ExpressionAnimationWithScalarParameter), + nameof(Parameter), + typeof(float), + typeof(ScalarExpressionAnimation), new PropertyMetadata(default(float), OnParameterPropertyChanged)); private static void OnParameterPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { // Unpack - ExpressionAnimationWithScalarParameter @this = d.To(); + ScalarExpressionAnimation @this = d.To(); float value = e.NewValue.To(); // Update the parameter when needed CompositionGetValueStatus status = @this.PropertySet.TryGetScalar(@this.ParameterName, out float old); if (status != CompositionGetValueStatus.Succeeded) throw new InvalidOperationException("The target parameter has not been found"); - float delta = value - old, + float + delta = value - old, abs = delta >= 0 ? delta : -delta; if (abs > 0.1) @this.PropertySet.InsertScalar(@this.ParameterName, value); } diff --git a/UICompositionAnimations/Extensions/Windows.UI/Core/DispatcherExtensions.cs b/UICompositionAnimations/Extensions/Windows.UI/Core/DispatcherExtensions.cs new file mode 100644 index 0000000..668589b --- /dev/null +++ b/UICompositionAnimations/Extensions/Windows.UI/Core/DispatcherExtensions.cs @@ -0,0 +1,99 @@ +using System; +using System.Threading.Tasks; +using JetBrains.Annotations; + +namespace Windows.UI.Core +{ + /// + /// An extension for the + /// + public static class DispatcherExtensions + { + /// + /// Executes a given action on the UI thread without awaiting the operation + /// + /// The target dispatcher to use to schedule the callback execution + /// The action to execute on the UI thread + public static void Run([NotNull] this CoreDispatcher dispatcher, [NotNull] Action callback) + { + if (dispatcher.HasThreadAccess) callback(); + else _ = dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => callback()); + } + + /// + /// Executes a given async action on the UI thread without awaiting the operation + /// + /// The target dispatcher to use to schedule the callback execution + /// The action to execute on the UI thread + public static void Run([NotNull] this CoreDispatcher dispatcher, [NotNull] Func asyncCallback) + { + if (dispatcher.HasThreadAccess) asyncCallback(); + else _ = dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => asyncCallback()); + } + + /// + /// Executes a given action on the UI thread and waits for it to be completed + /// + /// The target dispatcher to use to schedule the callback execution + /// The action to execute on the UI thread + public static async Task RunAsync([NotNull] this CoreDispatcher dispatcher, [NotNull] Action callback) + { + if (dispatcher.HasThreadAccess) callback(); + else + { + TaskCompletionSource tcs = new TaskCompletionSource(); + _ = dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => + { + callback(); + tcs.SetResult(null); + }); + await tcs.Task; + } + } + + /// + /// Executes a given action on the UI thread and waits for it to be completed + /// + /// The target dispatcher to use to schedule the callback execution + /// The action to execute on the UI thread + public static Task RunAsync([NotNull] this CoreDispatcher dispatcher, [NotNull] Func asyncCallback) + { + if (dispatcher.HasThreadAccess) return asyncCallback(); + TaskCompletionSource tcs = new TaskCompletionSource(); + _ = dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => tcs.SetResult(asyncCallback())); + return tcs.Task.Unwrap(); + } + + /// + /// Executes a given function on the UI thread and returns its result + /// + /// The return type + /// The target dispatcher to use to schedule the callback execution + /// The function to execute on the UI thread + public static Task GetAsync([NotNull] this CoreDispatcher dispatcher, [NotNull] Func function) + { + if (dispatcher.HasThreadAccess) return Task.FromResult(function()); + TaskCompletionSource tcs = new TaskCompletionSource(); + _ = dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => + { + T result = function(); + tcs.SetResult(result); + }); + return tcs.Task; + } + + /// + /// Executes a given async function on the UI thread and returns its result + /// + /// The return type + /// The target dispatcher to use to schedule the callback execution + /// The async function to execute on the UI thread + public static Task GetAsync([NotNull] this CoreDispatcher dispatcher, [NotNull] Func> function) + { + if (dispatcher.HasThreadAccess) return function(); + TaskCompletionSource> tcs = new TaskCompletionSource>(); + _ = dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => tcs.SetResult(function())); + return tcs.Task.Unwrap(); + } + } +} diff --git a/UICompositionAnimations/Extensions/Windows.UI/Xaml/DependencyObjectExtensions.cs b/UICompositionAnimations/Extensions/Windows.UI/Xaml/DependencyObjectExtensions.cs new file mode 100644 index 0000000..4ef8761 --- /dev/null +++ b/UICompositionAnimations/Extensions/Windows.UI/Xaml/DependencyObjectExtensions.cs @@ -0,0 +1,46 @@ +using System; +using Windows.UI.Xaml.Media.Animation; +using JetBrains.Annotations; +using UICompositionAnimations.Enums; +using UICompositionAnimations.Extensions; + +namespace Windows.UI.Xaml +{ + /// + /// An extension for the type + /// + [PublicAPI] + public static class DependencyObjectExtensions + { + /// + /// Prepares a with the given info + /// + /// The target to animate + /// The property to animate inside the target + /// The initial property value + /// The final property value + /// The duration of the + /// The easing function to use inside the + /// Indicates whether or not to apply this animation to elements that need the visual tree to be rearranged + [Pure, NotNull] + public static DoubleAnimation CreateDoubleAnimation( + [NotNull] this DependencyObject target, string property, + double? from, double? to, + TimeSpan duration, + Easing easing = Easing.Linear, + bool enableDependecyAnimations = false) + { + DoubleAnimation animation = new DoubleAnimation + { + From = from, + To = to, + Duration = duration, + EnableDependentAnimation = enableDependecyAnimations + }; + if (easing != Easing.Linear) animation.EasingFunction = easing.ToEasingFunction(); + Storyboard.SetTarget(animation, target); + Storyboard.SetTargetProperty(animation, property); + return animation; + } + } +} diff --git a/UICompositionAnimations/Extensions/Windows.UI/Xaml/FrameworkElementExtensions.cs b/UICompositionAnimations/Extensions/Windows.UI/Xaml/FrameworkElementExtensions.cs new file mode 100644 index 0000000..2f4b865 --- /dev/null +++ b/UICompositionAnimations/Extensions/Windows.UI/Xaml/FrameworkElementExtensions.cs @@ -0,0 +1,92 @@ +using System; +using System.Numerics; +using Windows.UI.Composition; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Hosting; +using Windows.UI.Xaml.Shapes; +using JetBrains.Annotations; + +namespace Windows.UI.Xaml +{ + /// + /// An extension for the + /// + [PublicAPI] + public static class FrameworkElementExtensions + { + /// + /// Sets the property of the behind a given + /// + /// The source + /// The center to use on the X axis. If , the center of the will be used + /// The center to use on the Y axis. If , the center of the will be used + public static void SetVisualCenterPoint([NotNull] this FrameworkElement element, double? x, double? y) + { + if (double.IsNaN(element.Width) || double.IsNaN(element.Height)) + throw new InvalidOperationException("The target element must have a fixed size"); + element.GetVisual().CenterPoint = new Vector3((float?)x ?? (float)(element.Width / 2), (float?)y ?? (float)(element.Height / 2), 0); + } + + /// + /// Creates a from the given + /// + /// The source for the + /// The optional target to apply the to (it can be the same as the source ) + /// Indicates whether or not to immediately add the to the visual tree + /// The optional width of the (if null, the element width will be used) + /// The optional height of the (if null, the element height will be used) + /// The color (the default is + /// The opacity of the + /// The optional horizontal offset of the + /// The optional vertical offset of the + /// The optional margin of the clip area of the + /// The optional horizontal offset of the clip area of the + /// The optional vertical offset of the clip area of the + /// The optional explicit blur radius + /// The optional to use to create an alpha mask for the + /// The that hosts the + public static SpriteVisual AttachVisualShadow( + [NotNull] this FrameworkElement element, [NotNull] UIElement target, + bool apply, + float? width, float? height, + Color color, float opacity, + float offsetX = 0, float offsetY = 0, + Thickness? clipMargin = null, float clipOffsetX = 0, float clipOffsetY = 0, + float? blurRadius = null, + [CanBeNull] UIElement maskElement = null) + { + // Setup the shadow + Visual elementVisual = ElementCompositionPreview.GetElementVisual(element); + Compositor compositor = elementVisual.Compositor; + SpriteVisual sprite = compositor.CreateSpriteVisual(); + DropShadow shadow = compositor.CreateDropShadow(); + shadow.Color = color; + shadow.Opacity = opacity; + shadow.Offset = new Vector3(offsetX, offsetY, 0); + if (blurRadius != null) shadow.BlurRadius = blurRadius.Value; + sprite.Shadow = shadow; + sprite.Size = new Vector2(width ?? (float)element.Width, height ?? (float)element.Height); + + // Clip it (if needed) and add it to the visual tree + if (clipMargin != null || clipOffsetX > 0 || clipOffsetY > 0) + { + InsetClip clip = compositor.CreateInsetClip( + (float)(clipMargin?.Left ?? 0), (float)(clipMargin?.Top ?? 0), + (float)(clipMargin?.Right ?? 0), (float)(clipMargin?.Bottom ?? 0)); + clip.Offset = new Vector2(clipOffsetX, clipOffsetY); + sprite.Clip = clip; + } + + // Alpha mask + switch (maskElement) + { + case null: break; + case Shape shape: shadow.Mask = shape.GetAlphaMask(); break; + case Image image: shadow.Mask = image.GetAlphaMask(); break; + case TextBlock textBlock: shadow.Mask = textBlock.GetAlphaMask(); break; + } + if (apply) ElementCompositionPreview.SetElementChildVisual(target, sprite); + return sprite; + } + } +} diff --git a/UICompositionAnimations/Extensions/Windows.UI/Xaml/Media/Animation/StoryboardExtensions.cs b/UICompositionAnimations/Extensions/Windows.UI/Xaml/Media/Animation/StoryboardExtensions.cs new file mode 100644 index 0000000..7a87919 --- /dev/null +++ b/UICompositionAnimations/Extensions/Windows.UI/Xaml/Media/Animation/StoryboardExtensions.cs @@ -0,0 +1,35 @@ +using System; +using System.Threading.Tasks; +using JetBrains.Annotations; + +namespace Windows.UI.Xaml.Media.Animation +{ + /// + /// An extension for the + /// + [PublicAPI] + public static class StoryboardExtensions + { + /// + /// Schedules a callback to run when the animation completes + /// + /// The to start + /// The callback to execute when the animation ends + public static void Then([NotNull] this Storyboard target, Action action) + { + // Set up the token + TaskCompletionSource tcs = new TaskCompletionSource(); + + // Prepare the handler + void Handler(object sender, object e) + { + action(); + target.Completed -= Handler; + tcs.SetResult(); + } + + // Assign the handler + target.Completed += Handler; + } + } +} diff --git a/UICompositionAnimations/Extensions/Windows.UI/Xaml/Media/Animation/TimelineExtensions.cs b/UICompositionAnimations/Extensions/Windows.UI/Xaml/Media/Animation/TimelineExtensions.cs new file mode 100644 index 0000000..669c08a --- /dev/null +++ b/UICompositionAnimations/Extensions/Windows.UI/Xaml/Media/Animation/TimelineExtensions.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using JetBrains.Annotations; + +namespace Windows.UI.Xaml.Media.Animation +{ + /// + /// An extension for the + /// + [PublicAPI] + public static class TimelineExtensions + { + /// + /// Creates a new with the given animation + /// + /// The single animation to insert into the returned + [NotNull] + public static Storyboard ToStoryboard([NotNull] this Timeline animation) + { + Storyboard storyboard = new Storyboard(); + storyboard.Children.Add(animation); + return storyboard; + } + + /// + /// Prepares a with the given animations + /// + /// The animations to run inside the + public static Storyboard ToStoryboard([NotNull, ItemNotNull] this IEnumerable animations) + { + Storyboard storyboard = new Storyboard(); + foreach (Timeline animation in animations) + storyboard.Children.Add(animation); + return storyboard; + } + + /// + /// Starts an animation and waits for it to be completed + /// + /// The target storyboard + public static Task BeginAsync(this Storyboard storyboard) + { + if (storyboard == null) throw new ArgumentNullException(); + TaskCompletionSource tcs = new TaskCompletionSource(); + storyboard.Completed += (s, e) => tcs.SetResult(null); + storyboard.Begin(); + return tcs.Task; + } + } +} diff --git a/UICompositionAnimations/Extensions/Windows.UI/Xaml/Media/SolidColorBrushExtensions.cs b/UICompositionAnimations/Extensions/Windows.UI/Xaml/Media/SolidColorBrushExtensions.cs new file mode 100644 index 0000000..0da50a0 --- /dev/null +++ b/UICompositionAnimations/Extensions/Windows.UI/Xaml/Media/SolidColorBrushExtensions.cs @@ -0,0 +1,37 @@ +using System; +using Windows.UI.Xaml.Media.Animation; +using JetBrains.Annotations; +using UICompositionAnimations.Enums; +using UICompositionAnimations.Extensions; + +namespace Windows.UI.Xaml.Media +{ + /// + /// An extension for the + /// + [PublicAPI] + public static class SolidColorBrushExtensions + { + /// + /// Animates the target to a given color + /// + /// The to animate + /// The target value to set + /// The duration of the animation + /// The easing function to use + [Pure, NotNull] + public static ColorAnimation CreateColorAnimation([NotNull] this SolidColorBrush solidColorBrush, Color to, TimeSpan duration, Easing easing) + { + ColorAnimation animation = new ColorAnimation + { + From = solidColorBrush.Color, + To = to, + Duration = duration, + EasingFunction = easing.ToEasingFunction() + }; + Storyboard.SetTarget(animation, solidColorBrush); + Storyboard.SetTargetProperty(animation, nameof(SolidColorBrush.Color)); + return animation; + } + } +} diff --git a/UICompositionAnimations/Extensions/Windows.UI/Xaml/ScrollViewerExtensions.cs b/UICompositionAnimations/Extensions/Windows.UI/Xaml/ScrollViewerExtensions.cs new file mode 100644 index 0000000..be72cfb --- /dev/null +++ b/UICompositionAnimations/Extensions/Windows.UI/Xaml/ScrollViewerExtensions.cs @@ -0,0 +1,67 @@ +using Windows.UI.Composition; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Hosting; +using JetBrains.Annotations; +using UICompositionAnimations.Enums; + +namespace Windows.UI.Xaml +{ + /// + /// An extension for the control + /// + [PublicAPI] + public static class ScrollViewerExtensions + { + /// + /// Creates and starts an animation on the target element that binds either the X or Y axis of the source + /// + /// The source control to use + /// The target that will be animated + /// The scrolling axis of the source + /// The optional scrolling axis of the target element, if the source axis will be used + /// Indicates whether or not to invert the animation from the source + [NotNull] + public static ExpressionAnimation StartExpressionAnimation( + [NotNull] this ScrollViewer scroller, [NotNull] UIElement target, + Axis sourceXY, Axis? targetXY = null, bool invertSourceAxis = false) + { + CompositionPropertySet scrollSet = ElementCompositionPreview.GetScrollViewerManipulationPropertySet(scroller); + string sign = invertSourceAxis ? "-" : string.Empty; + ExpressionAnimation animation = scrollSet.Compositor.CreateExpressionAnimation($"{sign}scroll.Translation.{sourceXY}"); + animation.SetReferenceParameter("scroll", scrollSet); + target.GetVisual().StartAnimation($"Offset.{targetXY ?? sourceXY}", animation); + return animation; + } + + /// + /// Creates and starts an animation on the target element, with the addition of a scalar parameter in the resulting + /// + /// The source control to use + /// The target that will be animated + /// The scrolling axis of the source + /// An additional parameter that will be included in the expression animation + /// The optional scrolling axis of the target element, if the source axis will be used + /// Indicates whether or not to invert the animation from the source + [NotNull] + public static ScalarExpressionAnimation StartExpressionAnimation( + [NotNull] this ScrollViewer scroller, [NotNull] UIElement target, + Axis sourceXY, float parameter, + Axis? targetXY = null, bool invertSourceAxis = false) + { + // Get the property set and setup the scroller offset sign + CompositionPropertySet scrollSet = ElementCompositionPreview.GetScrollViewerManipulationPropertySet(scroller); + string sign = invertSourceAxis ? "-" : "+"; + + // Prepare the second property set to insert the additional parameter + CompositionPropertySet properties = scroller.GetVisual().Compositor.CreatePropertySet(); + properties.InsertScalar(nameof(parameter), parameter); + + // Create and start the animation + ExpressionAnimation animation = scrollSet.Compositor.CreateExpressionAnimation($"{nameof(properties)}.{nameof(parameter)} {sign} scroll.Translation.{sourceXY}"); + animation.SetReferenceParameter("scroll", scrollSet); + animation.SetReferenceParameter(nameof(properties), properties); + target.GetVisual().StartAnimation($"Offset.{targetXY ?? sourceXY}", animation); + return new ScalarExpressionAnimation(animation, properties, nameof(parameter)); + } + } +} diff --git a/UICompositionAnimations/Extensions/Windows.UI/Xaml/UIElementExtensions.cs b/UICompositionAnimations/Extensions/Windows.UI/Xaml/UIElementExtensions.cs new file mode 100644 index 0000000..18c60e2 --- /dev/null +++ b/UICompositionAnimations/Extensions/Windows.UI/Xaml/UIElementExtensions.cs @@ -0,0 +1,265 @@ +using System; +using System.Numerics; +using Windows.UI.Composition; +using Windows.UI.Xaml.Hosting; +using Windows.UI.Xaml.Media; +using JetBrains.Annotations; +using UICompositionAnimations.Animations; +using UICompositionAnimations.Animations.Interfaces; +using UICompositionAnimations.Enums; + +namespace Windows.UI.Xaml +{ + /// + /// An extension for the + /// + [PublicAPI] + public static class UIElementExtensions + { + /// + /// Returns the Visual object for a given UIElement + /// + /// The source UIElement + public static Visual GetVisual([NotNull] this UIElement element) => ElementCompositionPreview.GetElementVisual(element); + + #region Animations + + /// + /// Initializes an instance that targets the input + /// + /// The target to animate + /// The target layer to animate + [Pure, NotNull] + public static IAnimationBuilder Animation([NotNull] this UIElement target, FrameworkLayer layer = FrameworkLayer.Composition) + { + switch (layer) + { + case FrameworkLayer.Composition: return new CompositionAnimationBuilder(target); + case FrameworkLayer.Xaml: return new XamlAnimationBuilder(target); + default: throw new ArgumentOutOfRangeException(nameof(layer), layer, $"The {layer} value isn't valid"); + } + } + + /// + /// Creates and starts a scalar animation on the target + /// + /// The to animate + /// The path that identifies the property to animate + /// The optional starting value for the animation + /// The final value for the animation + /// The animation duration + /// The optional initial delay for the animation + /// The optional easing function for the animation + public static void BeginScalarAnimation( + [NotNull] this UIElement element, + [NotNull] string propertyPath, + float? from, float to, + TimeSpan duration, TimeSpan? delay, + [CanBeNull] CompositionEasingFunction ease = null) + { + element.GetVisual().BeginScalarAnimation(propertyPath, from, to, duration, delay, ease); + } + + /// + /// Creates and starts a animation on the target + /// + /// The to animate + /// The path that identifies the property to animate + /// The optional starting value for the animation + /// The final value for the animation + /// The animation duration + /// The optional initial delay for the animation + /// The optional easing function for the animation + public static void BeginVector2Animation( + [NotNull] this UIElement element, + [NotNull] string propertyPath, + Vector2? from, Vector2 to, + TimeSpan duration, TimeSpan? delay, + [CanBeNull] CompositionEasingFunction ease = null) + { + element.GetVisual().BeginVector2Animation(propertyPath, from, to, duration, delay, ease); + } + + /// + /// Creates and starts a animation on the target + /// + /// The to animate + /// The path that identifies the property to animate + /// The optional starting value for the animation + /// The final value for the animation + /// The animation duration + /// The optional initial delay for the animation + /// The optional easing function for the animation + public static void BeginVector3Animation( + [NotNull] this UIElement element, + [NotNull] string propertyPath, + Vector3? from, Vector3 to, + TimeSpan duration, TimeSpan? delay, + [CanBeNull] CompositionEasingFunction ease = null) + { + element.GetVisual().BeginVector3Animation(propertyPath, from, to, duration, delay, ease); + } + + #endregion + + #region Property setters + + /// + /// Returns the desired instance after assigning it to the property of the target + /// + /// The desired type + /// The target to modify + /// If , a new instance will always be created and assigned to the + /// + public static T GetTransform(this UIElement element, bool reset = true) where T : Transform, new() + { + // Return the existing transform object, if it exists + if (element.RenderTransform is T && !reset) return element.RenderTransform.To(); + + // Create a new transform + T transform = new T(); + element.RenderTransform = transform; + return transform; + } + + /// + /// Stops the animations with the target names on the given + /// + /// The target + /// The names of the animations to stop + public static void StopVisualAnimations([NotNull] this UIElement element, [NotNull, ItemNotNull] params string[] properties) + { + if (properties.Length == 0) return; + Visual visual = element.GetVisual(); + foreach (string property in properties) + visual.StopAnimation(property); + } + + /// + /// Sets the translation property of the instance for a given object + /// + /// The target + /// The axis to edit + /// The translation value to set for that axis + public static void SetVisualTranslation([NotNull] this UIElement element, Axis axis, float translation) + { + // Get the element visual and stop the animation + Visual visual = element.GetVisual(); + ElementCompositionPreview.SetIsTranslationEnabled(element, true); + + // Set the desired offset + Matrix4x4 transform = visual.TransformMatrix; + Vector3 translation3 = visual.TransformMatrix.Translation; + if (axis == Axis.X) translation3.X = translation; + else translation3.Y = translation; + transform.Translation = translation3; + visual.TransformMatrix = transform; + } + + /// + /// Sets the translation property of the instance for a given object + /// + /// The target + /// The horizontal translation value + /// The vertical translation value + public static void SetVisualTranslation([NotNull] this UIElement element, float x, float y) + { + // Get the element visual and stop the animation + Visual visual = element.GetVisual(); + ElementCompositionPreview.SetIsTranslationEnabled(element, true); + + // Set the desired offset + Matrix4x4 transform = visual.TransformMatrix; + Vector3 translation3 = visual.TransformMatrix.Translation; + translation3.X = x; + translation3.Y = y; + transform.Translation = translation3; + visual.TransformMatrix = transform; + } + + /// + /// Sets the property of the instance for a given object + /// + /// The target + /// The axis to edit + /// The value to set for that axis + public static void SetVisualOffset([NotNull] this UIElement element, Axis axis, float offset) + { + // Get the element visual and stop the animation + Visual visual = element.GetVisual(); + + // Set the desired offset + Vector3 offset3 = visual.Offset; + if (axis == Axis.X) offset3.X = offset; + else offset3.Y = offset; + visual.Offset = offset3; + } + + /// + /// Sets the property of the instance for a given + /// + /// The target + /// The X value of the property + /// The Y value of the property + /// The Z value of the property + public static void SetVisualScale([NotNull] this UIElement element, float? x, float? y, float? z) + { + // Get the default values and set the CenterPoint + Visual visual = element.GetVisual(); + + // Set the scale property + if (x == null && y == null && z == null) return; + Vector3 targetScale = new Vector3 + { + X = x ?? visual.Scale.X, + Y = y ?? visual.Scale.Y, + Z = z ?? visual.Scale.Z + }; + visual.Scale = targetScale; + } + + /// + /// Sets the clip property of the visual instance for a given + /// + /// The target + /// The desired clip margins to set + public static void SetVisualClip([NotNull] this UIElement element, Thickness clip) + { + // Get the element visual + Visual visual = element.GetVisual(); + + // Set the desired clip + InsetClip inset = visual.Clip as InsetClip ?? (visual.Clip = visual.Compositor.CreateInsetClip()).To(); + inset.TopInset = (float)clip.Top; + inset.BottomInset = (float)clip.Bottom; + inset.LeftInset = (float)clip.Left; + inset.RightInset = (float)clip.Right; + visual.Clip = inset; + } + + /// + /// Sets the clip property of the visual instance for a given + /// + /// The target + /// The target clip side to update + /// The desired clip value to set + public static void SetVisualClip([NotNull] this UIElement element, Side side, float clip) + { + // Get the element visual + Visual visual = element.GetVisual(); + + // Set the desired clip + InsetClip inset = visual.Clip as InsetClip ?? (InsetClip)(visual.Clip = visual.Compositor.CreateInsetClip()); + switch (side) + { + case Side.Top: inset.TopInset = clip; break; + case Side.Bottom: inset.BottomInset = clip; break; + case Side.Right: inset.RightInset = clip; break; + case Side.Left: inset.LeftInset = clip; break; + default: throw new ArgumentException("Invalid side", nameof(side)); + } + } + + #endregion + } +} diff --git a/UICompositionAnimations/Brushes/Cache/HostBackdropInstanceWrapper.cs b/UICompositionAnimations/Helpers/Cache/HostBackdropInstanceWrapper.cs similarity index 96% rename from UICompositionAnimations/Brushes/Cache/HostBackdropInstanceWrapper.cs rename to UICompositionAnimations/Helpers/Cache/HostBackdropInstanceWrapper.cs index b776414..aaae8d7 100644 --- a/UICompositionAnimations/Brushes/Cache/HostBackdropInstanceWrapper.cs +++ b/UICompositionAnimations/Helpers/Cache/HostBackdropInstanceWrapper.cs @@ -2,7 +2,7 @@ using Windows.UI.Composition; using JetBrains.Annotations; -namespace UICompositionAnimations.Brushes.Cache +namespace UICompositionAnimations.Helpers.Cache { /// /// A simple class that holds information on a instance and its effects pipeline diff --git a/UICompositionAnimations/Brushes/Cache/ThreadSafeCompositionCache.cs b/UICompositionAnimations/Helpers/Cache/ThreadSafeCompositionCache.cs similarity index 97% rename from UICompositionAnimations/Brushes/Cache/ThreadSafeCompositionCache.cs rename to UICompositionAnimations/Helpers/Cache/ThreadSafeCompositionCache.cs index 9c4f371..71d7a93 100644 --- a/UICompositionAnimations/Brushes/Cache/ThreadSafeCompositionCache.cs +++ b/UICompositionAnimations/Helpers/Cache/ThreadSafeCompositionCache.cs @@ -5,7 +5,7 @@ using Windows.UI.Core; using JetBrains.Annotations; -namespace UICompositionAnimations.Brushes.Cache +namespace UICompositionAnimations.Helpers.Cache { /// /// A used to cache reusable instances diff --git a/UICompositionAnimations/Brushes/Cache/ThreadSafeCompositionMapCache.cs b/UICompositionAnimations/Helpers/Cache/ThreadSafeCompositionMapCache.cs similarity index 98% rename from UICompositionAnimations/Brushes/Cache/ThreadSafeCompositionMapCache.cs rename to UICompositionAnimations/Helpers/Cache/ThreadSafeCompositionMapCache.cs index ec6e7c5..acaf7cb 100644 --- a/UICompositionAnimations/Brushes/Cache/ThreadSafeCompositionMapCache.cs +++ b/UICompositionAnimations/Helpers/Cache/ThreadSafeCompositionMapCache.cs @@ -5,7 +5,7 @@ using Windows.UI.Core; using JetBrains.Annotations; -namespace UICompositionAnimations.Brushes.Cache +namespace UICompositionAnimations.Helpers.Cache { /// /// A used to cache reusable instances with an associated key diff --git a/UICompositionAnimations/Helpers/ColorConverter.cs b/UICompositionAnimations/Helpers/ColorConverter.cs deleted file mode 100644 index e72ede3..0000000 --- a/UICompositionAnimations/Helpers/ColorConverter.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Linq; -using Windows.UI; - -namespace UICompositionAnimations.Helpers -{ - /// - /// A simple class that contains some color managment methods - /// - internal static class ColorConverter - { - /// - /// Returns the Color represented by the hex string - /// - /// If it contains just the RGB values {RRBBGG} the Alpha channel is automatically set to FF - public static Color String2Color(string color) - { - //Cancels the # symbol from the string, if present - if (color.Contains('#')) color = color.Substring(1); - byte alpha; - string RGB; - if (color.Length == 6) - { - alpha = 255; - RGB = color; - } - else - { - alpha = byte.Parse(color.Substring(0, 2), System.Globalization.NumberStyles.HexNumber); - RGB = color.Substring(2); - } - return ColorHelper.FromArgb(alpha, - byte.Parse(RGB.Substring(0, 2), System.Globalization.NumberStyles.HexNumber), - byte.Parse(RGB.Substring(2, 2), System.Globalization.NumberStyles.HexNumber), - byte.Parse(RGB.Substring(4, 2), System.Globalization.NumberStyles.HexNumber)); - } - } -} diff --git a/UICompositionAnimations/Helpers/DispatcherHelper.cs b/UICompositionAnimations/Helpers/DispatcherHelper.cs index 02dc43e..6745ee1 100644 --- a/UICompositionAnimations/Helpers/DispatcherHelper.cs +++ b/UICompositionAnimations/Helpers/DispatcherHelper.cs @@ -7,161 +7,58 @@ namespace UICompositionAnimations.Helpers { /// - /// A static class that manages the UI dispatching from background threads + /// A that manages the UI dispatching from background threads /// [PublicAPI] public static class DispatcherHelper { - #region Target dispatcher helpers - - /// - /// Executes a given action on the UI thread without awaiting the operation - /// - /// The target dispatcher to use to schedule the callback execution - /// The action to execute on the UI thread - public static void Run([NotNull] this CoreDispatcher dispatcher, [NotNull] Action callback) - { - if (dispatcher.HasThreadAccess) callback(); - else dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => callback()).Forget(); - } - - /// - /// Executes a given async action on the UI thread without awaiting the operation - /// - /// The target dispatcher to use to schedule the callback execution - /// The action to execute on the UI thread - public static void Run([NotNull] this CoreDispatcher dispatcher, [NotNull] Func asyncCallback) - { - if (dispatcher.HasThreadAccess) asyncCallback(); - else dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => asyncCallback()).Forget(); - } - - /// - /// Executes a given action on the UI thread and waits for it to be completed - /// - /// The target dispatcher to use to schedule the callback execution - /// The action to execute on the UI thread - public static async Task RunAsync([NotNull] this CoreDispatcher dispatcher, [NotNull] Action callback) - { - if (dispatcher.HasThreadAccess) callback(); - else - { - TaskCompletionSource tcs = new TaskCompletionSource(); - dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => - { - callback(); - tcs.SetResult(null); - }).Forget(); - await tcs.Task; - } - } - - /// - /// Executes a given action on the UI thread and waits for it to be completed - /// - /// The target dispatcher to use to schedule the callback execution - /// The action to execute on the UI thread - public static Task RunAsync([NotNull] this CoreDispatcher dispatcher, [NotNull] Func asyncCallback) - { - if (dispatcher.HasThreadAccess) return asyncCallback(); - TaskCompletionSource tcs = new TaskCompletionSource(); - dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => - { - tcs.SetResult(asyncCallback()); - }).Forget(); - return tcs.Task.Unwrap(); - } - /// - /// Executes a given function on the UI thread and returns its result - /// - /// The return type - /// The target dispatcher to use to schedule the callback execution - /// The function to execute on the UI thread - public static Task GetAsync([NotNull] this CoreDispatcher dispatcher, [NotNull] Func function) - { - if (dispatcher.HasThreadAccess) return Task.FromResult(function()); - TaskCompletionSource tcs = new TaskCompletionSource(); - dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => - { - T result = function(); - tcs.SetResult(result); - }).Forget(); - return tcs.Task; - } - - /// - /// Executes a given async function on the UI thread and returns its result - /// - /// The return type - /// The target dispatcher to use to schedule the callback execution - /// The async function to execute on the UI thread - public static Task GetAsync([NotNull] this CoreDispatcher dispatcher, [NotNull] Func> function) - { - if (dispatcher.HasThreadAccess) return function(); - TaskCompletionSource> tcs = new TaskCompletionSource>(); - dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => - { - tcs.SetResult(function()); - }).Forget(); - return tcs.Task.Unwrap(); - } - - #endregion - - #region Standalone helpers - - private static CoreDispatcher _CoreDispatcher; - - /// - /// Gets the current CoreDispatcher instance + /// Gets the current instance /// [NotNull] - private static CoreDispatcher CoreDispatcher => _CoreDispatcher ?? (_CoreDispatcher = CoreApplication.MainView.CoreWindow.Dispatcher); + private static CoreDispatcher CoreDispatcher { get; } = CoreApplication.MainView.CoreWindow.Dispatcher; /// /// Checks whether or not the current thread has access to the UI /// - public static bool HasUIThreadAccess => CoreDispatcher.HasThreadAccess; + public static bool HasThreadAccess => CoreDispatcher.HasThreadAccess; /// - /// Executes a given action on the UI thread without awaiting the operation + /// Executes a given on the UI thread without awaiting the operation /// - /// The action to execute on the UI thread - public static void RunOnUIThread([NotNull] Action callback) => CoreDispatcher.Run(callback); + /// The to execute on the UI thread + public static void Run([NotNull] Action callback) => CoreDispatcher.Run(callback); /// - /// Executes a given async action on the UI thread without awaiting the operation + /// Executes a given that returns a on the UI thread without awaiting the operation /// - /// The action to execute on the UI thread - public static void RunOnUIThread([NotNull] Func asyncCallback) => CoreDispatcher.Run(asyncCallback); + /// The to execute on the UI thread + public static void Run([NotNull] Func asyncCallback) => CoreDispatcher.Run(asyncCallback); /// - /// Executes a given action on the UI thread and waits for it to be completed + /// Executes a given on the UI thread and waits for it to be completed /// - /// The action to execute on the UI thread - public static Task RunOnUIThreadAsync([NotNull] Action callback) => CoreDispatcher.RunAsync(callback); + /// The to execute on the UI thread + public static Task RunAsync([NotNull] Action callback) => CoreDispatcher.RunAsync(callback); /// - /// Executes a given action on the UI thread and waits for it to be completed + /// Executes a given that returns a on the UI thread and waits for it to be completed /// - /// The action to execute on the UI thread - public static Task RunOnUIThreadAsync([NotNull] Func asyncCallback) => CoreDispatcher.RunAsync(asyncCallback); + /// The to execute on the UI thread + public static Task RunAsync([NotNull] Func asyncCallback) => CoreDispatcher.RunAsync(asyncCallback); /// - /// Executes a given function on the UI thread and returns its result + /// Executes a given on the UI thread and returns its result /// /// The return type - /// The function to execute on the UI thread - public static Task GetFromUIThreadAsync([NotNull] Func function) => CoreDispatcher.GetAsync(function); + /// The to execute on the UI thread + public static Task GetAsync([NotNull] Func function) => CoreDispatcher.GetAsync(function); /// - /// Executes a given async function on the UI thread and returns its result + /// Executes a given that returns a on the UI thread and returns its result /// - /// The return type - /// The async function to execute on the UI thread - public static Task GetFromUIThreadAsync([NotNull] Func> function) => CoreDispatcher.GetAsync(function); - - #endregion + /// The return type for the + /// The to execute on the UI thread + public static Task GetAsync([NotNull] Func> function) => CoreDispatcher.GetAsync(function); } } diff --git a/UICompositionAnimations/Helpers/Extensions.cs b/UICompositionAnimations/Helpers/Extensions.cs deleted file mode 100644 index f20972f..0000000 --- a/UICompositionAnimations/Helpers/Extensions.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.CompilerServices; -using Windows.Foundation; -using JetBrains.Annotations; - -namespace UICompositionAnimations.Helpers -{ - /// - /// A misc extensions class - /// - internal static class Extensions - { - /// - /// Safely calls the Equals method on a given object, returning false if the object is null - /// - /// The Type of the two object - /// The first object to test - /// The comparison value - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool SafeEquals(this T value, T test) => value?.Equals(test) == true; - - /// - /// Performs a direct cast on the given object - /// - public static T To(this object o) => (T)o; - - /// - /// Converts an angle in radians to degrees - /// - /// The value to convert - public static float ToDegrees(this float radians) => (float)(Math.PI * radians / 180.0); - - /// - /// Converts an angle in degrees to radians - /// - /// The value to convert - public static float ToRadians(this float degrees) => (float)(Math.PI / 180 * degrees); - - /// - /// Suppresses the warnings when calling an async method without awaiting it - /// - /// The IAsyncAction returned by the async call - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Forget(this IAsyncAction action) { } - - /// - /// Merges the two input instances and makes sure no duplicate keys are present - /// - /// The first to merge - /// The second to merge - [Pure, NotNull] - public static IReadOnlyDictionary Merge( - [NotNull] this IReadOnlyDictionary a, - [NotNull] IReadOnlyDictionary b) - { - if (a.Keys.FirstOrDefault(b.ContainsKey) is TKey key) - throw new InvalidOperationException($"The key {key} already exists in the current pipeline"); - return new Dictionary(a.Concat(b)); - } - - /// - /// Merges the two input instances and makes sure no duplicate items are present - /// - /// The first to merge - /// The second to merge - [Pure, NotNull, ItemNotNull] - public static IReadOnlyCollection Merge([NotNull, ItemNotNull] this IReadOnlyCollection a, [NotNull, ItemNotNull] IReadOnlyCollection b) - { - if (a.FirstOrDefault(b.Contains) is T animation) - throw new InvalidOperationException($"The animation {animation} already exists in the current pipeline"); - return a.Concat(b).ToArray(); - } - } -} diff --git a/UICompositionAnimations/Helpers/Win2DImageHelper.cs b/UICompositionAnimations/Helpers/Win2DImageHelper.cs index 1686632..b69fcef 100644 --- a/UICompositionAnimations/Helpers/Win2DImageHelper.cs +++ b/UICompositionAnimations/Helpers/Win2DImageHelper.cs @@ -15,14 +15,15 @@ using Microsoft.Graphics.Canvas.UI; using Microsoft.Graphics.Canvas.UI.Composition; using Microsoft.Graphics.Canvas.UI.Xaml; -using UICompositionAnimations.Brushes.Cache; using UICompositionAnimations.Enums; +using UICompositionAnimations.Helpers.Cache; namespace UICompositionAnimations.Helpers { /// - /// A helper classe that loads Win2D images and manages an internal cache of objects with the loaded images + /// A helper that loads Win2D images and manages an internal cache of instances with the loaded images /// + [PublicAPI] public static class Win2DImageHelper { /// @@ -48,9 +49,8 @@ public static class Win2DImageHelper /// The path to the image to load /// Indicates the desired DPI mode to use when loading the image /// Indicates the cache option to use to load the image - [PublicAPI] [Pure, ItemCanBeNull] - public static Task LoadImageAsync([NotNull] Uri uri, BitmapDPIMode dpiMode, BitmapCacheMode cache = BitmapCacheMode.Default) + public static Task LoadImageAsync([NotNull] Uri uri, DpiMode dpiMode, CacheMode cache = CacheMode.Default) { return LoadImageAsync(Window.Current.Compositor, uri, dpiMode, cache); } @@ -62,9 +62,8 @@ public static Task LoadImageAsync([NotNull] Uri uri, Bi /// The path to the image to load /// Indicates the desired DPI mode to use when loading the image /// Indicates the cache option to use to load the image - [PublicAPI] [Pure, ItemCanBeNull] - public static Task LoadImageAsync([NotNull] this CanvasControl canvas, [NotNull] Uri uri, BitmapDPIMode dpiMode, BitmapCacheMode cache = BitmapCacheMode.Default) + public static Task LoadImageAsync([NotNull] this CanvasControl canvas, [NotNull] Uri uri, DpiMode dpiMode, CacheMode cache = CacheMode.Default) { return LoadImageAsync(Window.Current.Compositor, canvas, uri, dpiMode, cache); } @@ -73,8 +72,7 @@ public static Task LoadImageAsync([NotNull] this Canvas /// Clears the internal cache of Win2D images /// /// A sequence of the objects that were present in the cache - /// The returned items should be manually disposed once checked that they are no longer being used in other effect pipelines - [PublicAPI] + /// The returned items should be manually disposed after checking that they are no longer in use in other effect pipelines [MustUseReturnValue, ItemNotNull] public static async Task> ClearCacheAsync() { @@ -95,8 +93,10 @@ public static async Task> ClearCacheAsync() /// The path to the image to load /// Indicates the desired DPI mode to use when loading the image [ItemNotNull] - private static async Task LoadSurfaceBrushAsync([NotNull] ICanvasResourceCreator creator, - [NotNull] Compositor compositor, [NotNull] CanvasDevice canvasDevice, [NotNull] Uri uri, BitmapDPIMode dpiMode) + private static async Task LoadSurfaceBrushAsync( + [NotNull] ICanvasResourceCreator creator, + [NotNull] Compositor compositor, [NotNull] CanvasDevice canvasDevice, + [NotNull] Uri uri, DpiMode dpiMode) { CanvasBitmap bitmap = null; try @@ -106,16 +106,16 @@ private static async Task LoadSurfaceBrushAsync([NotNul float dpi = display.LogicalDpi; switch (dpiMode) { - case BitmapDPIMode.UseSourceDPI: + case DpiMode.UseSourceDpi: bitmap = await CanvasBitmap.LoadAsync(creator, uri); break; - case BitmapDPIMode.Default96DPI: + case DpiMode.Default96Dpi: bitmap = await CanvasBitmap.LoadAsync(creator, uri, 96); break; - case BitmapDPIMode.CopyDisplayDPISetting: + case DpiMode.DisplayDpi: bitmap = await CanvasBitmap.LoadAsync(creator, uri, dpi); break; - case BitmapDPIMode.CopyDisplayDPISettingsWith96AsLowerBound: + case DpiMode.DisplayDpiWith96AsLowerBound: bitmap = await CanvasBitmap.LoadAsync(creator, uri, dpi >= 96 ? dpi : 96); break; default: @@ -169,7 +169,10 @@ private static async Task LoadSurfaceBrushAsync([NotNul /// Indicates the desired DPI mode to use when loading the image /// Indicates the cache option to use to load the image [ItemCanBeNull] - internal static async Task LoadImageAsync([NotNull] Compositor compositor, [NotNull] CanvasControl canvas, [NotNull] Uri uri, BitmapDPIMode dpiMode, BitmapCacheMode cache) + internal static async Task LoadImageAsync( + [NotNull] Compositor compositor, + [NotNull] CanvasControl canvas, + [NotNull] Uri uri, DpiMode dpiMode, CacheMode cache) { TaskCompletionSource tcs = new TaskCompletionSource(); @@ -214,7 +217,7 @@ async void Canvas_CreateResources(CanvasControl sender, CanvasCreateResourcesEve // Lock the semaphore and check the cache first using (await Win2DMutex.LockAsync()) { - if (cache == BitmapCacheMode.Default && Cache.TryGetInstance(uri, out CompositionSurfaceBrush cached)) return cached; + if (cache == CacheMode.Default && Cache.TryGetInstance(uri, out CompositionSurfaceBrush cached)) return cached; // Load the image canvas.CreateResources += Canvas_CreateResources; @@ -239,8 +242,8 @@ async void Canvas_CreateResources(CanvasControl sender, CanvasCreateResourcesEve // Cache when needed and return the result if (brush != null) { - if (cache == BitmapCacheMode.Default) Cache.Add(uri, brush); - else if (cache == BitmapCacheMode.Overwrite) Cache.Overwrite(uri, brush); + if (cache == CacheMode.Default) Cache.Add(uri, brush); + else if (cache == CacheMode.Overwrite) Cache.Overwrite(uri, brush); } return brush; } @@ -254,21 +257,13 @@ async void Canvas_CreateResources(CanvasControl sender, CanvasCreateResourcesEve /// Indicates the desired DPI mode to use when loading the image /// Indicates the cache option to use to load the image [ItemCanBeNull] - internal static async Task LoadImageAsync([NotNull] Compositor compositor, [NotNull] Uri uri, BitmapDPIMode dpiMode, BitmapCacheMode cache) + internal static async Task LoadImageAsync([NotNull] Compositor compositor, [NotNull] Uri uri, DpiMode dpiMode, CacheMode cache) { - // Fix the Uri if it has been generated by the XAML designer - if (uri.Scheme.Equals("ms-resource")) - { - string path = uri.AbsolutePath.StartsWith("/Files") - ? uri.AbsolutePath.Replace("/Files", string.Empty) - : uri.AbsolutePath; - uri = new Uri($"ms-appx://{path}"); - } - // Lock and check the cache first using (await Win2DMutex.LockAsync()) { - if (cache == BitmapCacheMode.Default && Cache.TryGetInstance(uri, out CompositionSurfaceBrush cached)) return cached; + uri = uri.ToAppxUri(); + if (cache == CacheMode.Default && Cache.TryGetInstance(uri, out CompositionSurfaceBrush cached)) return cached; // Load the image CompositionSurfaceBrush brush; @@ -287,8 +282,8 @@ internal static async Task LoadImageAsync([NotNull] Com // Cache when needed and return the result if (brush != null) { - if (cache == BitmapCacheMode.Default) Cache.Add(uri, brush); - else if (cache == BitmapCacheMode.Overwrite) Cache.Overwrite(uri, brush); + if (cache == CacheMode.Default) Cache.Add(uri, brush); + else if (cache == CacheMode.Overwrite) Cache.Overwrite(uri, brush); } return brush; } diff --git a/UICompositionAnimations/Lights/LightsSourceHelper.cs b/UICompositionAnimations/Lights/LightsSourceHelper.cs deleted file mode 100644 index 35af443..0000000 --- a/UICompositionAnimations/Lights/LightsSourceHelper.cs +++ /dev/null @@ -1,108 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Windows.ApplicationModel; -using Windows.Devices.Input; -using Windows.UI.Xaml; -using JetBrains.Annotations; -using UICompositionAnimations.Helpers; -using UICompositionAnimations.Helpers.PointerEvents; - -namespace UICompositionAnimations.Lights -{ - /// - /// A class that contains an attached property to register a target as a lights container - /// - [PublicAPI] - public static class LightsSourceHelper - { - // The list of light generators - private static Func[] LightGenerators; - - /// - /// Initializes the helper with a series of light generators used to create the lights in each target - /// - /// The sequence of light generators to store and use when needed. - /// Each generator can be as simple as () => new PointerPositionSpotLight(), or it can assign custom - /// properties to each light when needed, like () => new PointerPositionSpotLight { IdAppendage = "MyID", Shade = 0x10 } - public static void Initialize([NotNull] params Func[] generators) - { - if (LightGenerators != null) throw new InvalidOperationException("The helper has already been initialized"); - LightGenerators = generators; - } - - /// - /// Gets the value for the target - /// - /// The target element to inspect - public static bool GetIsLightsContainer(UIElement element) - { - return element.GetValue(IsLightsContainerProperty).To(); - } - - /// - /// Sets the value for the target - /// - /// The target element to edit - /// The new value for the property - public static void SetIsLightsContainer(UIElement element, bool value) - { - element?.SetValue(IsLightsContainerProperty, value); - } - - /// - /// Identifies the attached to set a target as a lights container - /// - public static readonly DependencyProperty IsLightsContainerProperty = - DependencyProperty.RegisterAttached("IsLightsContainer", typeof(bool), typeof(LightsSourceHelper), - new PropertyMetadata(default(bool), OnIsLightsContainerPropertyChanged)); - - // Private dictionary to keep track of the added pointer event handlers - private static readonly IDictionary HandlersMap = new Dictionary(); - - private static void OnIsLightsContainerPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - // Designer test - if (DesignMode.DesignMode2Enabled) return; - - // Unpack - if (LightGenerators == null) throw new NullReferenceException($"The {nameof(LightsSourceHelper)} class hasn't been initialized"); - UIElement @this = d.To(); - bool value = e.NewValue.To(); - - // Lights setup - if (value) - { - // Lights setup - PointerPositionSpotLight[] lights = LightGenerators.Select(factory => - { - PointerPositionSpotLight light = factory(); - light.Active = false; - @this.Lights.Add(light); - return light; - }).ToArray(); - - // Animate the lights when the pointer exits and leaves the area - bool lightsEnabled = false; - ControlAttachedHandlersInfo info = @this.ManageHostPointerStates((type, state) => - { - bool lightsVisible = type == PointerDeviceType.Mouse && state; - if (lightsEnabled == lightsVisible) return; - lightsEnabled = lightsVisible; - foreach (PointerPositionSpotLight light in lights) - light.Active = lightsEnabled; - }); - HandlersMap.Add(@this, info); - } - else - { - // Remove the lights and the custom pointer handlers - if (!HandlersMap.TryGetValue(@this, out ControlAttachedHandlersInfo info)) - throw new InvalidOperationException("Error retrieving the pointer handlers info on the current control"); - HandlersMap.Remove(@this); - info.TryRemoveHandlers(); - @this.Lights.Clear(); - } - } - } -} diff --git a/UICompositionAnimations/Lights/PointerPositionSpotLight.cs b/UICompositionAnimations/Lights/PointerPositionSpotLight.cs deleted file mode 100644 index 82f3363..0000000 --- a/UICompositionAnimations/Lights/PointerPositionSpotLight.cs +++ /dev/null @@ -1,220 +0,0 @@ -using System; -using UICompositionAnimations.Helpers; -using Windows.UI; -using Windows.UI.Composition; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Hosting; -using Windows.UI.Xaml.Media; -using JetBrains.Annotations; - -namespace UICompositionAnimations.Lights -{ - /// - /// An attached XAML property to enable the XAML brush - /// - [PublicAPI] - public class PointerPositionSpotLight : XamlLight - { - #region Properties - - /// - /// Gets or sets the intensity of the light. A higher value will result in a brighter light - /// - public byte Shade - { - get => GetValue(ShadeProperty).To(); - set => SetValue(ShadeProperty, value); - } - - /// - /// Gets the for the property - /// - public static readonly DependencyProperty ShadeProperty = - DependencyProperty.Register(nameof(Shade), typeof(byte), typeof(PointerPositionSpotLight), new PropertyMetadata(byte.MaxValue, OnShadePropertyChanged)); - - private static void OnShadePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - if (d is PointerPositionSpotLight @this && @this._Light != null) - { - byte shade = e.NewValue.To(); - Color color = new Color - { - A = byte.MaxValue, - R = shade, - G = shade, - B = shade - }; - @this._Light.InnerConeColor = @this._Light.OuterConeColor = color; - } - } - - float _Z = 20; - - /// - /// Gets or sets the Z axis of the light - /// - public float Z - { - get => _Z; - set - { - _Z = value; - _Properties?.InsertScalar("Z", value); - } - } - - /// - /// Gets the object for the current instance - /// - public CompositionPropertySet Properties => _Properties; - - /// - /// Gets or sets the cone angle of the light - /// - public float OuterConeAngle - { - get => (float)GetValue(OuterConeAngleProperty); - set => SetValue(OuterConeAngleProperty, value); - } - - /// - /// Gets the for the property - /// - public static readonly DependencyProperty OuterConeAngleProperty = - DependencyProperty.Register(nameof(OuterConeAngle), typeof(float), typeof(PointerPositionSpotLight), new PropertyMetadata(90f, OuterConeAngleChanged)); - - private static void OuterConeAngleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - if (d is PointerPositionSpotLight @this && @this._Light != null) - { - @this._Light.OuterConeAngleInDegrees = (float)e.NewValue; - } - } - - // The constant attenuation value to use when the light is inactive - private const int InactiveAttenuationValue = 50; - - /// - /// Gets or sets whether or not the light is active - /// - public bool Active - { - get => GetValue(ActiveProperty).To(); - set => SetValue(ActiveProperty, value); - } - - /// - /// Gets the for the property - /// - public static readonly DependencyProperty ActiveProperty = - DependencyProperty.Register(nameof(Active), typeof(bool), typeof(PointerPositionSpotLight), new PropertyMetadata(true, OnActivePropertyChanged)); - - private static void OnActivePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - if (d is PointerPositionSpotLight @this) - @this._Light?.StartAnimationAsync("ConstantAttenuation", e.NewValue.To() ? 0 : InactiveAttenuationValue, TimeSpan.FromMilliseconds(250)); - } - - #endregion - - /// - /// Gets the value for the target object - /// - /// The target onject to inspect - public static bool GetIsTarget(DependencyObject obj) => (bool)obj.GetValue(IsTargetProperty); - - /// - /// Sets the value for the target object - /// - /// The target object - /// The value of the property to set - public static void SetIsTarget(DependencyObject obj, bool value) => obj.SetValue(IsTargetProperty, value); - - /// - /// Gets or sets the value of the attached property - /// - public static readonly DependencyProperty IsTargetProperty = - DependencyProperty.RegisterAttached("IsTarget", typeof(bool), typeof(PointerPositionSpotLight), new PropertyMetadata(false, OnIsTargetChanged)); - - private static void OnIsTargetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - // Apply the changes - if ((bool)e.NewValue) - { - // Add - if (d is Brush b) - { - AddTargetBrush(GetIdStatic(), b); - } - else if (d is UIElement el) - { - AddTargetElement(GetIdStatic(), el); - } - } - else - { - // Remove - if (d is Brush b) - { - RemoveTargetBrush(GetIdStatic(), b); - } - else if (d is UIElement el) - { - RemoveTargetElement(GetIdStatic(), el); - } - } - } - - // The light to use - private SpotLight _Light; - - // The source compositor - private Compositor _Compositor; - - // The expression animation for the light position - private ExpressionAnimation _Animation; - - // The properties for the animation - private CompositionPropertySet _Properties; - - /// - protected override void OnConnected(UIElement newElement) - { - if (CompositionLight == null) - { - // Initialize the fields - _Compositor = Window.Current.Compositor; - _Properties = _Compositor.CreatePropertySet(); - _Properties.InsertScalar("Z", Z); - _Light = Window.Current.Compositor.CreateSpotLight(); - - // Setup the light - CompositionPropertySet pointer = ElementCompositionPreview.GetPointerPositionPropertySet(newElement); - _Animation = _Compositor.CreateExpressionAnimation("Vector3(pointer.Position.X, pointer.Position.Y, props.Z)"); - _Animation.SetReferenceParameter("pointer", pointer); - _Animation.SetReferenceParameter("props", _Properties); - _Light.StartAnimation("Offset", _Animation); - _Light.InnerConeColor = _Light.OuterConeColor = Color.FromArgb(byte.MaxValue, Shade, Shade, Shade); - _Light.InnerConeAngleInDegrees = 0; - _Light.OuterConeAngleInDegrees = OuterConeAngle; - _Light.ConstantAttenuation = Active ? 0 : InactiveAttenuationValue; - CompositionLight = _Light; - } - base.OnConnected(newElement); - } - - /// - /// Gets or sets a custom appendage for the method - /// - [NotNull] - public string IdAppendage { get; set; } = string.Empty; - - /// - protected override string GetId() => GetIdStatic() + IdAppendage; - - /// - /// Gets a static Id for the class - /// - public static string GetIdStatic() => typeof(PointerPositionSpotLight).FullName; - } -} diff --git a/UICompositionAnimations/Properties/AssemblyInfo.cs b/UICompositionAnimations/Properties/AssemblyInfo.cs index 9e38cf0..dabc2f0 100644 --- a/UICompositionAnimations/Properties/AssemblyInfo.cs +++ b/UICompositionAnimations/Properties/AssemblyInfo.cs @@ -23,8 +23,8 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("3.1.0.0")] -[assembly: AssemblyFileVersion("3.1.0.0")] -[assembly: AssemblyInformationalVersion("3.1.0")] +[assembly: AssemblyVersion("4.0.0.0")] +[assembly: AssemblyFileVersion("4.0.0.0")] +[assembly: AssemblyInformationalVersion("4.0.0")] [assembly: ComVisible(false)] diff --git a/UICompositionAnimations/UICompositionAnimations.csproj b/UICompositionAnimations/UICompositionAnimations.csproj index 7b44c92..e5d4ffd 100644 --- a/UICompositionAnimations/UICompositionAnimations.csproj +++ b/UICompositionAnimations/UICompositionAnimations.csproj @@ -120,68 +120,74 @@ latest - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - - - + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - + - 2018.3.0 + 2019.1.1 - 6.1.9 + 6.2.8 1.23.0 diff --git a/UICompositionAnimations/UICompositionAnimations.nuspec b/UICompositionAnimations/UICompositionAnimations.nuspec index 9c3aed5..cda4dc0 100644 --- a/UICompositionAnimations/UICompositionAnimations.nuspec +++ b/UICompositionAnimations/UICompositionAnimations.nuspec @@ -2,7 +2,7 @@ UICompositionAnimations - 3.1.0 + 4.0.0 UICompositionAnimations A wrapper UWP PCL to work with Windows.UI.Composition and XAML animations, and Win2D effects Sergio Pedri @@ -10,12 +10,12 @@ https://github.com/Sergio0694/UICompositionAnimations GPL-3.0-only false - New PipelineBrush and composition effect APIs - Copyright © 2010 + Major refactoring, API changes + Copyright © 2019 uwp composition animations xaml csharp windows winrt universal app ui win2d graphics - - + + diff --git a/UICompositionAnimations/XAMLTransform/EasingFunctionBaseProvider.cs b/UICompositionAnimations/XAMLTransform/EasingFunctionBaseProvider.cs deleted file mode 100644 index 89e4311..0000000 --- a/UICompositionAnimations/XAMLTransform/EasingFunctionBaseProvider.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; -using Windows.UI.Xaml.Media.Animation; -using UICompositionAnimations.Enums; - -namespace UICompositionAnimations.XAMLTransform -{ - /// - /// A simple class that creates the desired XAML transform easing function - /// - internal static class EasingFunctionBaseProvider - { - /// - /// Converts an easing function name to the right easing function - /// - /// The desired easing function - public static EasingFunctionBase ToEasingFunction(this EasingFunctionNames ease) - { - switch (ease) - { - case EasingFunctionNames.Linear: return null; - case EasingFunctionNames.SineEaseIn: return new SineEase { EasingMode = EasingMode.EaseIn }; - case EasingFunctionNames.SineEaseOut: return new SineEase { EasingMode = EasingMode.EaseOut }; - case EasingFunctionNames.SineEaseInOut: return new SineEase { EasingMode = EasingMode.EaseInOut }; - case EasingFunctionNames.QuadraticEaseIn: return new QuadraticEase { EasingMode = EasingMode.EaseIn }; - case EasingFunctionNames.QuadraticEaseOut: return new QuadraticEase { EasingMode = EasingMode.EaseOut }; - case EasingFunctionNames.QuadraticEaseInOut: return new QuadraticEase { EasingMode = EasingMode.EaseInOut }; - case EasingFunctionNames.CircleEaseIn: return new CircleEase { EasingMode = EasingMode.EaseIn }; - case EasingFunctionNames.CircleEaseOut: return new CircleEase { EasingMode = EasingMode.EaseOut }; - case EasingFunctionNames.CircleEaseInOut: return new CircleEase { EasingMode = EasingMode.EaseInOut }; - default: throw new ArgumentOutOfRangeException(nameof(ease), ease, "This shouldn't happen"); - } - } - } -} diff --git a/UICompositionAnimations/XAMLTransform/XAMLTransformToolkit.cs b/UICompositionAnimations/XAMLTransform/XAMLTransformToolkit.cs deleted file mode 100644 index 3ef0868..0000000 --- a/UICompositionAnimations/XAMLTransform/XAMLTransformToolkit.cs +++ /dev/null @@ -1,151 +0,0 @@ -using System; -using System.Linq; -using System.Threading.Tasks; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Media; -using Windows.UI.Xaml.Media.Animation; -using JetBrains.Annotations; -using UICompositionAnimations.Enums; -using UICompositionAnimations.Helpers; - -namespace UICompositionAnimations.XAMLTransform -{ - /// - /// A toolkit with some extensions for XAML animations and some useful methods - /// - [PublicAPI] - public static class XAMLTransformToolkit - { - #region Extensions - - /// - /// Creates a new with the given animation - /// - /// The single animation to insert into the returned - [NotNull] - public static Storyboard ToStoryboard([NotNull] this Timeline animation) - { - Storyboard storyboard = new Storyboard(); - storyboard.Children.Add(animation); - return storyboard; - } - - /// - /// Returns the desired XAML transform object after assigning it to the render transform property of the target item - /// - /// The desired render transform object - /// The target element to modify - /// If true, a new render transform object will always be created and assigned to the element - /// - public static T GetRenderTransform(this UIElement element, bool forceReset = true) where T : Transform, new() - { - // Return the existing transform object, if it exists - if (element.RenderTransform is T && !forceReset) return element.RenderTransform.To(); - - // Create a new transform - T transform = new T(); - element.RenderTransform = transform; - return transform; - } - - /// - /// Starts an animation and waits for it to be completed - /// - /// The target storyboard - public static Task WaitAsync(this Storyboard storyboard) - { - if (storyboard == null) throw new ArgumentNullException(); - TaskCompletionSource tcs = new TaskCompletionSource(); - storyboard.Completed += (s, e) => tcs.SetResult(null); - storyboard.Begin(); - return tcs.Task; - } - - /// - /// Starts an animation and runs an action when it completes - /// - /// The storyboard to start - /// The callback action to execute when the animation ends - public static Task RunDelegateOnAnimationEndedAsync(this Storyboard target, Action action) - { - // Set up the token - TaskCompletionSource tcs = new TaskCompletionSource(); - - // Prepare the handler - void Handler(object sender, object e) - { - action(); - target.Completed -= Handler; - tcs.SetResult(null); - } - - // Assign the handler, start the animation and return the Task to wait for - target.Completed += Handler; - target.Begin(); - return tcs.Task; - } - - /// - /// Checks whether or not the target value of the two animations is the same - /// - /// The input animation - /// The animation to compare to the first one - public static bool CompareTargetValue(this Storyboard storyboard, Storyboard test) - { - if (storyboard == null || storyboard.Children.Count != 1 || test == null || test.Children.Count != 1) return false; - return storyboard.Children.First().To().To.SafeEquals(test.Children.First().To().To); - } - - #endregion - - #region Tools - - /// - /// Prepares a an animation with the given info - /// - /// The target object to animate - /// The property to animate inside the target object - /// The initial property value - /// The final property value - /// The duration of the animation - /// The easing function to use inside the animation - /// Indicates whether or not to apply this animation to elements that need the visual tree to be rearranged - public static DoubleAnimation CreateDoubleAnimation(DependencyObject target, string property, - double? from, double? to, int ms, EasingFunctionNames easing = EasingFunctionNames.Linear, bool enableDependecyAnimations = false) - { - DoubleAnimation animation = new DoubleAnimation - { - From = from, - To = to, - Duration = new Duration(TimeSpan.FromMilliseconds(ms)), - EnableDependentAnimation = enableDependecyAnimations - }; - if (easing != EasingFunctionNames.Linear) animation.EasingFunction = easing.ToEasingFunction(); - Storyboard.SetTarget(animation, target); - Storyboard.SetTargetProperty(animation, property); - return animation; - } - - /// - /// Prepares a with the given animations - /// - /// The animations to run inside the storyboard - public static Storyboard PrepareStoryboard(params Timeline[] animations) - { - Storyboard storyboard = new Storyboard(); - foreach (Timeline animation in animations) - { - storyboard.Children.Add(animation); - } - return storyboard; - } - - /// - /// Converts the given TranslationAxis enum into its string representation - /// - /// The enum to convert - public static string ToPropertyString(this TranslationAxis axis) => axis == TranslationAxis.X ? "X" : "Y"; - - #endregion - } -} diff --git a/UICompositionAnimations/XAMLTransformExtensions.cs b/UICompositionAnimations/XAMLTransformExtensions.cs deleted file mode 100644 index b2e95ca..0000000 --- a/UICompositionAnimations/XAMLTransformExtensions.cs +++ /dev/null @@ -1,572 +0,0 @@ -using System; -using System.Diagnostics.CodeAnalysis; -using System.Threading.Tasks; -using Windows.UI; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Media; -using Windows.UI.Xaml.Media.Animation; -using JetBrains.Annotations; -using UICompositionAnimations.Enums; -using UICompositionAnimations.Helpers; -using UICompositionAnimations.XAMLTransform; - -namespace UICompositionAnimations -{ - /// - /// A static class that wraps the animation methods in the Windows.UI.Xaml.Media.Animation namespace - /// - [PublicAPI] - public static class XAMLTransformExtensions - { - #region Fade - - // Manages the fade animation - private static async Task ManageXAMLTransformFadeAnimationAsync(this UIElement element, - double? startOp, double? endOp, - int ms, int? msDelay, EasingFunctionNames easingFunction, bool reverse) - { - // Delay if necessary - if (msDelay.HasValue) await Task.Delay(msDelay.Value); - - // Start and wait the animation - DoubleAnimation animation = XAMLTransformToolkit.CreateDoubleAnimation(element, "Opacity", startOp ?? element.Opacity, endOp, ms, easingFunction); - Storyboard storyboard = XAMLTransformToolkit.PrepareStoryboard(animation); - storyboard.AutoReverse = reverse; - await storyboard.WaitAsync(); - } - - /// - /// Starts a fade animation on the target UIElement and optionally runs a callback Action when the animations finish - /// - /// The UIElement to animate - /// The initial opacity value. If null, the current opacity will be used - /// The final opacity value - /// The duration of the animation, in milliseconds - /// The delay before the animation starts, in milliseconds. If null, there will be no delay - /// The easing function to use with the new animations - /// An Action to execute when the new animations end - /// If true, the animation will be played in reverse mode when it finishes for the first time - public static async void StartXAMLTransformFadeAnimation(this UIElement element, - double? startOp, double? endOp, - int ms, int? msDelay, EasingFunctionNames easingFunction, Action callback = null, bool reverse = false) - { - await ManageXAMLTransformFadeAnimationAsync(element, startOp, endOp, ms, msDelay, easingFunction, reverse); - callback?.Invoke(); - } - - /// - /// Starts a fade animation on the target UIElement and returns a Task that completes when the animation ends - /// - /// The UIElement to animate - /// The initial opacity value. If null, the current opacity will be used - /// The final opacity value - /// The duration of the animation, in milliseconds - /// The delay before the animation starts, in milliseconds. If null, there will be no delay - /// The easing function to use with the new animations - /// If true, the animation will be played in reverse mode when it finishes for the first time - public static Task StartXAMLTransformFadeAnimationAsync(this UIElement element, - double? startOp, double? endOp, - int ms, int? msDelay, EasingFunctionNames easingFunction, bool reverse = false) - { - return ManageXAMLTransformFadeAnimationAsync(element, startOp, endOp, ms, msDelay, easingFunction, reverse); - } - - #endregion - - #region Fade and slide - - // Manages the fade and slide animation - private static async Task ManageXAMLTransformFadeSlideAnimationAsync(this UIElement element, - double? startOp, double? endOp, - TranslationAxis axis, double? startXY, double? endXY, - int msOp, int? msSlide, int? msDelay, EasingFunctionNames easingFunction, bool reverse) - { - // Delay if necessary - if (msDelay.HasValue) await Task.Delay(msDelay.Value); - - // Try to get the original starting value if necessary - if (startXY == null && element.RenderTransform is TranslateTransform) - { - startXY = axis == TranslationAxis.X ? element.RenderTransform.To().X : element.RenderTransform.To().Y; - } - - // Start and wait the animation - DoubleAnimation opacity = XAMLTransformToolkit.CreateDoubleAnimation(element, "Opacity", startOp ?? element.Opacity, endOp, msOp, easingFunction); - DoubleAnimation slide = XAMLTransformToolkit.CreateDoubleAnimation(element.GetRenderTransform(), - axis.ToPropertyString(), startXY, endXY, - msSlide ?? msOp, easingFunction); - Storyboard storyboard = XAMLTransformToolkit.PrepareStoryboard(opacity, slide); - storyboard.AutoReverse = reverse; - await storyboard.WaitAsync(); - } - - /// - /// Starts a fade and slide animation on the target UIElement and optionally runs a callback Action when the animations finish - /// - /// The UIElement to animate - /// The initial opacity value. If null, the current opacity will be used - /// The final opacity value - /// The offset axis to use on the translation animation - /// The initial offset value. If null, the current offset will be used - /// The final offset value - /// The duration of the animation, in milliseconds - /// The duration of the animation, in milliseconds - /// The delay before the animation starts, in milliseconds. If null, there will be no delay - /// The easing function to use with the new animations - /// An Action to execute when the new animations end - /// If true, the animation will be played in reverse mode when it finishes for the first time - public static async void StartXAMLTransformFadeSlideAnimation(this UIElement element, - double? startOp, double? endOp, - TranslationAxis axis, double? startXY, double? endXY, - int msOp, int? msSlide, int? msDelay, EasingFunctionNames easingFunction, Action callback = null, bool reverse = false) - { - await element.ManageXAMLTransformFadeSlideAnimationAsync(startOp, endOp, axis, startXY, endXY, msOp, msSlide, msDelay, easingFunction, reverse); - callback?.Invoke(); - } - - /// - /// Starts and wait a fade and slide animation on the target UIElement - /// - /// The UIElement to animate - /// The initial opacity value. If null, the current opacity will be used - /// The final opacity value - /// The offset axis to use on the translation animation - /// The initial offset value. If null, the current offset will be used - /// The final offset value - /// The duration of the fade animation, in milliseconds - /// The duration of the slide animation, in milliseconds - /// The delay before the animation starts, in milliseconds. If null, there will be no delay - /// The easing function to use with the new animations - /// If true, the animation will be played in reverse mode when it finishes for the first time - public static Task StartXAMLTransformFadeSlideAnimationAsync(this UIElement element, - double? startOp, double? endOp, - TranslationAxis axis, double? startXY, double? endXY, - int msOp, int? msSlide, int? msDelay, EasingFunctionNames easingFunction, bool reverse = false) - { - return element.ManageXAMLTransformFadeSlideAnimationAsync(startOp, endOp, axis, startXY, endXY, msOp, msSlide, msDelay, easingFunction, reverse); - } - - /// - /// Fades the opacity of a target element and slides it over a given axis - /// - /// The element to animate - /// The initial opacity - /// The end opacity - /// A string that indicates which axis to use with the TranslateTransform animation - /// The initial axis value - /// The final axis value - /// The duration of the animation in milliseconds - /// The easing function to use in the animation - public static Storyboard GetXAMLTransformFadeSlideStoryboard(this UIElement element, double? startOp, double? endOp, - TranslationAxis axis, double? startXY, double? endXY, int ms, EasingFunctionNames easing) - { - // Try to get the original starting value if necessary - TranslateTransform translate = element.RenderTransform as TranslateTransform; - bool cleanAnimation = startXY != null; - if (startXY == null && translate != null) - { - startXY = axis == TranslationAxis.X ? translate.X : translate.Y; - } - - // Prepare and run the animation - if (translate == null || cleanAnimation) - { - translate = new TranslateTransform(); - element.RenderTransform = translate; - } - return XAMLTransformToolkit.PrepareStoryboard( - XAMLTransformToolkit.CreateDoubleAnimation(element, "Opacity", startOp ?? element.Opacity, endOp, ms, easing), - XAMLTransformToolkit.CreateDoubleAnimation(translate, axis.ToPropertyString(), startXY, endXY, ms, easing)); - } - - #endregion - - #region Fade and scale - - // Manages the scale animation - private static async Task ManageXAMLTransformFadeScaleAnimationAsync(this UIElement element, - double? startOp, double? endOp, - double? startScale, double? endScale, - int msOp, int? msScale, int? msDelay, EasingFunctionNames easingFunction, bool reverse) - { - // Delay if necessary - if (msDelay.HasValue) await Task.Delay(msDelay.Value); - - // Try to get the original starting values if necessary - if (startScale == null && element.RenderTransform is ScaleTransform) - { - ScaleTransform scale = element.RenderTransform.To(); - startScale = (scale.ScaleX + scale.ScaleY) / 2; - } - - // Start and wait the animation - DoubleAnimation opacity = XAMLTransformToolkit.CreateDoubleAnimation(element, "Opacity", startOp ?? element.Opacity, endOp, msOp, easingFunction); - DoubleAnimation scaleX = XAMLTransformToolkit.CreateDoubleAnimation(element.GetRenderTransform(), "ScaleX", - startScale, endScale, msScale ?? msOp, easingFunction); - DoubleAnimation scaleY = XAMLTransformToolkit.CreateDoubleAnimation(element.GetRenderTransform(), "ScaleY", - startScale, endScale, msScale ?? msOp, easingFunction); - Storyboard storyboard = XAMLTransformToolkit.PrepareStoryboard(opacity, scaleX, scaleY); - storyboard.AutoReverse = reverse; - await storyboard.WaitAsync(); - } - - /// - /// Starts a fade and slide animation on the target UIElement and optionally runs a callback Action when the animations finish - /// - /// The UIElement to animate - /// The initial opacity value. If null, the current opacity will be used - /// The final opacity value - /// The initial scale value. If null, the current scale will be used - /// The final scale value - /// The duration of the animation, in milliseconds - /// The duration of the animation, in milliseconds - /// The delay before the animation starts, in milliseconds. If null, there will be no delay - /// The easing function to use with the new animations - /// An Action to execute when the new animations end - /// If true, the animation will be played in reverse mode when it finishes for the first time - public static async void StartXAMLTransformFadeScaleAnimation(this UIElement element, - double? startOp, double? endOp, - double? startScale, double? endScale, - int msOp, int? msScale, int? msDelay, EasingFunctionNames easingFunction, Action callback = null, bool reverse = false) - { - await element.ManageXAMLTransformFadeScaleAnimationAsync(startOp, endOp, startScale, endScale, msOp, msScale, msDelay, easingFunction, reverse); - callback?.Invoke(); - } - - /// - /// Starts and wait a fade and slide animation on the target UIElement - /// - /// The UIElement to animate - /// The initial opacity value. If null, the current opacity will be used - /// The final opacity value - /// The initial offset value. If null, the current offset will be used - /// The final offset value - /// The duration of the animation, in milliseconds - /// The duration of the animation, in milliseconds - /// The delay before the animation starts, in milliseconds. If null, there will be no delay - /// The easing function to use with the new animations - /// If true, the animation will be played in reverse mode when it finishes for the first time - public static Task StartXAMLTransformFadeScaleAnimationAsync(this UIElement element, - double? startOp, double? endOp, - double? startScale, double? endScale, - int msOp, int? msSlide, int? msDelay, EasingFunctionNames easingFunction, bool reverse = false) - { - return element.ManageXAMLTransformFadeScaleAnimationAsync(startOp, endOp, startScale, endScale, msOp, msSlide, msDelay, easingFunction, reverse); - } - - #endregion - - #region Scale only - - // Manages the scale animation - private static async Task ManageXAMLTransformFadeScaleAnimationAsync(this UIElement element, - double? startScale, double? endScale, - int ms, int? msDelay, EasingFunctionNames easingFunction, bool reverse) - { - // Delay if necessary - if (msDelay.HasValue) await Task.Delay(msDelay.Value); - - // Try to get the original starting values if necessary - if (startScale == null && element.RenderTransform is ScaleTransform) - { - ScaleTransform scale = element.RenderTransform.To(); - startScale = (scale.ScaleX + scale.ScaleY) / 2; - } - - // Start and wait the animation - DoubleAnimation scaleX = XAMLTransformToolkit.CreateDoubleAnimation(element.GetRenderTransform(), "ScaleX", - startScale, endScale, ms, easingFunction); - DoubleAnimation scaleY = XAMLTransformToolkit.CreateDoubleAnimation(element.GetRenderTransform(), "ScaleY", - startScale, endScale, ms, easingFunction); - Storyboard storyboard = XAMLTransformToolkit.PrepareStoryboard(scaleX, scaleY); - storyboard.AutoReverse = reverse; - await storyboard.WaitAsync(); - } - - /// - /// Starts a fade and slide animation on the target UIElement and optionally runs a callback Action when the animations finish - /// - /// The UIElement to animate - /// The initial scale value. If null, the current scale will be used - /// The final scale value - /// The duration of the animation, in milliseconds - /// The delay before the animation starts, in milliseconds. If null, there will be no delay - /// The easing function to use with the new animations - /// An Action to execute when the new animations end - /// If true, the animation will be played in reverse mode when it finishes for the first time - public static async void StartXAMLTransformScaleAnimation(this UIElement element, - double? startScale, double? endScale, - int ms, int? msDelay, EasingFunctionNames easingFunction, Action callback = null, bool reverse = false) - { - await element.ManageXAMLTransformFadeScaleAnimationAsync(startScale, endScale, ms, msDelay, easingFunction, reverse); - callback?.Invoke(); - } - - /// - /// Starts and wait a fade and slide animation on the target UIElement - /// - /// The UIElement to animate - /// The initial offset value. If null, the current offset will be used - /// The final offset value - /// The duration of the fade animation, in milliseconds - /// The delay before the animation starts, in milliseconds. If null, there will be no delay - /// The easing function to use with the new animations - /// If true, the animation will be played in reverse mode when it finishes for the first time - public static Task StartXAMLTransformScaleAnimationAsync(this UIElement element, - double? startScale, double? endScale, - int ms, int? msDelay, EasingFunctionNames easingFunction, bool reverse = false) - { - return element.ManageXAMLTransformFadeScaleAnimationAsync(startScale, endScale, ms, msDelay, easingFunction, reverse); - } - - #endregion - - #region Slide only - - // Manages the fade and slide animation - private static async Task ManageXAMLTransformSlideAnimationAsync(this UIElement element, - TranslationAxis axis, double? startXY, double? endXY, - int ms, int? msDelay, EasingFunctionNames easingFunction, bool reverse) - { - // Delay if necessary - if (msDelay.HasValue) await Task.Delay(msDelay.Value); - - // Try to get the original starting value if necessary - if (startXY == null && element.RenderTransform is TranslateTransform) - { - startXY = axis == TranslationAxis.X ? element.RenderTransform.To().X : element.RenderTransform.To().Y; - } - - // Start and wait the animation - DoubleAnimation slide = XAMLTransformToolkit.CreateDoubleAnimation(element.GetRenderTransform(), - axis.ToPropertyString(), startXY, endXY, ms, easingFunction); - Storyboard storyboard = XAMLTransformToolkit.PrepareStoryboard(slide); - storyboard.AutoReverse = reverse; - await storyboard.WaitAsync(); - } - - /// - /// Starts a fade and slide animation on the target UIElement and optionally runs a callback Action when the animations finish - /// - /// The UIElement to animate - /// The offset axis to use on the translation animation - /// The initial offset value. If null, the current offset will be used - /// The final offset value - /// The duration of the animation, in milliseconds - /// The delay before the animation starts, in milliseconds. If null, there will be no delay - /// The easing function to use with the new animations - /// An Action to execute when the new animations end - /// If true, the animation will be played in reverse mode when it finishes for the first time - public static async void StartXAMLTransformSlideAnimation(this UIElement element, - TranslationAxis axis, double? startXY, double? endXY, - int ms, int? msDelay, EasingFunctionNames easingFunction, Action callback = null, bool reverse = false) - { - await element.ManageXAMLTransformSlideAnimationAsync(axis, startXY, endXY, ms, msDelay, easingFunction, reverse); - callback?.Invoke(); - } - - /// - /// Starts and wait a fade and slide animation on the target UIElement - /// - /// The UIElement to animate - /// The offset axis to use on the translation animation - /// The initial offset value. If null, the current offset will be used - /// The final offset value - /// The duration of the fade animation, in milliseconds - /// The delay before the animation starts, in milliseconds. If null, there will be no delay - /// The easing function to use with the new animations - /// If true, the animation will be played in reverse mode when it finishes for the first time - public static Task StartXAMLTransformSlideAnimationAsync(this UIElement element, - TranslationAxis axis, double? startXY, double? endXY, - int ms, int? msDelay, EasingFunctionNames easingFunction, bool reverse = false) - { - return element.ManageXAMLTransformSlideAnimationAsync(axis, startXY, endXY, ms, msDelay, easingFunction, reverse); - } - - /// - /// Slides a target element over a given axis - /// - /// The element to animate - /// A string that indicates which axis to use with the TranslateTransform animation - /// The initial axis value - /// The final axis value - /// The duration of the animation in milliseconds - /// The easing function to use in the animation - public static Storyboard GetXAMLTransformSlideStoryboard(this UIElement element, - TranslationAxis axis, double? startXY, double? endXY, int ms, EasingFunctionNames easing) - { - // Try to get the original starting value if necessary - TranslateTransform translate = element.RenderTransform as TranslateTransform; - bool cleanAnimation = startXY != null; - if (startXY == null && translate != null) - { - startXY = axis == TranslationAxis.X ? element.RenderTransform.To().X : element.RenderTransform.To().Y; - } - - // Prepare and run the animation - if (translate == null || cleanAnimation) - { - translate = new TranslateTransform(); - element.RenderTransform = translate; - } - return XAMLTransformToolkit.PrepareStoryboard(XAMLTransformToolkit.CreateDoubleAnimation(translate, axis.ToPropertyString(), startXY, endXY, ms, easing)); - } - - #endregion - - #region Thickness animations - - /// - /// Animates a side of the margin of the target - /// - /// The element to animate - /// The initial value for the animation - /// The final value for the animation - /// The margin side to animate - /// The duration of the animation to create - /// The optional initial delay for the animation - [SuppressMessage("ReSharper", "AccessToModifiedClosure")] // Margin updates at each animation timestep - public static async Task StartXAMLMarginAnimation([NotNull] this FrameworkElement element, double? start, double end, MarginSide side, int duration, int? delay = null) - { - // Delay if needed, and calculate the start offset - if (delay != null) await Task.Delay(delay.Value); - if (start == null) - { - switch (side) - { - case MarginSide.Top: - start = element.Margin.Top; - break; - case MarginSide.Bottom: - start = element.Margin.Bottom; - break; - case MarginSide.Left: - start = element.Margin.Left; - break; - case MarginSide.Right: - start = element.Margin.Right; - break; - default: throw new ArgumentOutOfRangeException(nameof(side), "Invalid margin side"); - } - } - - // Calculate the animation steps - int - frames = (int)Math.Ceiling(duration * 4d / 100d), - elapsed = 0; - double - delta = end - start.Value, - step = delta / frames; - - // Execute the animation - TaskCompletionSource tcs = new TaskCompletionSource(); - DispatcherTimer timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(25) }; // 40fps - timer.Tick += TickHandler; - Thickness margin = element.Margin; - - void TickHandler(object sender, object e) - { - elapsed++; - switch (side) - { - case MarginSide.Top: - margin.Top += step; - break; - case MarginSide.Bottom: - margin.Bottom += step; - break; - case MarginSide.Left: - margin.Left += step; - break; - case MarginSide.Right: - margin.Right += step; - break; - default: throw new ArgumentOutOfRangeException(nameof(side), "Invalid margin side"); - } - element.Margin = margin; - if (elapsed >= frames) - { - timer.Tick -= TickHandler; - timer.Stop(); - tcs.SetResult(true); - } - } - timer.Start(); - await tcs.Task; - - // Wait for completion and adjust the final margin (just to be sure) - switch (side) - { - case MarginSide.Top: - margin.Top = end; - break; - case MarginSide.Bottom: - margin.Bottom = end; - break; - case MarginSide.Left: - margin.Left = end; - break; - case MarginSide.Right: - margin.Right = end; - break; - default: throw new ArgumentOutOfRangeException(nameof(side), "Invalid margin side"); - } - element.Margin = margin; - } - - #endregion - - #region Misc - - /// - /// Animates the target color brush to a given color - /// - /// The brush to animate - /// The target color to set - /// The duration of the animation - /// The easing function to use - public static void AnimateColor(this SolidColorBrush solidColorBrush, string toColor, int ms, EasingFunctionNames easing) - { - // Get the target color - Color targetColor = ColorConverter.String2Color(toColor); - if (solidColorBrush.Color.Equals(targetColor)) return; - - // Prepare the animation - ColorAnimation animation = new ColorAnimation - { - From = solidColorBrush.Color, - To = targetColor, - Duration = new Duration(TimeSpan.FromMilliseconds(ms)), - EasingFunction = easing.ToEasingFunction() - }; - Storyboard.SetTarget(animation, solidColorBrush); - Storyboard.SetTargetProperty(animation, "Color"); - Storyboard storyboard = new Storyboard(); - storyboard.Children.Add(animation); - storyboard.Begin(); - } - - /// - /// Gets a looped storyboard that makes the opacity of the element go from to 0 to 1 and vice versa - /// - /// The element to animate - /// The loop duration - public static Storyboard GetLoopedFadeStoryboard(this UIElement element, int ms) - { - DoubleAnimation backgroundAnimation = new DoubleAnimation - { - From = 0, - To = 1, - Duration = new Duration(TimeSpan.FromMilliseconds(ms)), - EasingFunction = new CircleEase { EasingMode = EasingMode.EaseInOut }, - RepeatBehavior = RepeatBehavior.Forever, - AutoReverse = true - }; - Storyboard.SetTarget(backgroundAnimation, element); - Storyboard.SetTargetProperty(backgroundAnimation, "Opacity"); - return XAMLTransformToolkit.PrepareStoryboard(backgroundAnimation); - } - - #endregion - } -} \ No newline at end of file