diff --git a/README.md b/README.md
index 45e199e..5cdd86c 100644
--- a/README.md
+++ b/README.md
@@ -69,17 +69,9 @@ await MyControl.StartCompositionFadeScaleAnimationAsync(
EasingFunctionNames.Linear); // Easing function
```
-#### ColorBrush animation
-```C#
-MyBrush.AnimateColor(
- #FFFF2B1C, // Target color
- 250, // Duration in ms
- EasingFunctionNames.Linear); // Easing function
-```
-
## `UI.Composition` effects
-The library provides several ways to use `UI.Composition` effects. There's a custom acrylic brush that can be used when running Windows 10 build 15063.x or greater, and other "attached" effects. An attached effect (created using the `AttachedCompositionEffectsFactory` class) is an effect that is loaded and then applied to the underlying `Visual` object behing a target `UIElement`. The main advantage of brushes is that they can be initialized and used in XAML and don't need any code-behind. Here are some examples:
+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.
#### Declare a shared acrylic brush in XAML
@@ -91,7 +83,7 @@ The library provides several ways to use `UI.Composition` effects. There's a cus
```
-**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 a custom noise texture 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/).
+**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();
+```
-#### Get a custom acrylic brush effect:
+#### Build an acrylic effect pipeline from scratch:
```C#
-AttachedStaticCompositionEffect attached = await BlurBorder.AttachCompositionInAppCustomAcrylicEffectAsync(
- BlurBorder, // The target host control for the effect visual (can be the same as the source)
- 8, // The amount of blur to apply
- 800, // The milliseconds to initially apply the blur effect with an automatic animation
- Color.FromArgb(byte.MaxValue, 0x1B, 0x1B, 0x1B), // The tint overlay color
- 0.8f, // The ratio of tint overlay over the source effect (the strength of the tint effect)
- null, // Use the default saturation value for the effect (1)
- Win2DCanvas, // A CanvasControl in the current visual tree, used to render parts of the acrylic brush
- new Uri("ms-appx:///Assets/Misc/noise.png"), // A Uri to a custom noise texture to use to create the effect
- BitmapCacheMode.EnableCaching, // The cache mode for the Win2D image to load
- false, // Indicates whether to fade the effect it or to display it as soon as possible
- true); // Indicates whether or not to automatically dispose the effect when the target `UIElement` is unloaded
+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();
```
-**Note**: in order to remove the effect from the target `UIElement`, it is possible to call the `Dispose` method on the returned `AttachedStaticCompositionEffect` object - calling that method will remove the effect from the object `Visual`.
+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 an attached blur effect that can be animated (using composition and Win2D effects):
+#### Get a custom effect that can be animated:
```C#
-AttachedAnimatableCompositionEffect attached = await MyBorder.AttachCompositionAnimatableBlurEffectAsync(
- 14f, // The amount of blur to apply when the effect is enabled
- 0f, // The default amount of blur
- false); // Indicates whether or not to immediately apply the effect to the target amount
+// 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
-await attached.AnimateAsync(
- FixedAnimationType.In, // Indicates whether to fade the blur effect in or out
- TimeSpan.FromMilliseconds(500)); // The animation duration
+saturation(0.2f, 250); // Animate the opacity to 0.2 in 250ms
```
## Reveal highlight effect
@@ -182,8 +173,7 @@ 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
-- `ApiInformationHelper`: provides useful methods to check the capabilities of the current device
- `PointerHelper`: exposes APIs to quickly setup pointer event handlers for `UIElement`s
# Requirements
-At least Windows 10 November Update (10586.x)
+At least Windows 10 April Update (17134.x)
diff --git a/UICompositionAnimations/Behaviours/AcrylicEffectHelper.cs b/UICompositionAnimations/Behaviours/AcrylicEffectHelper.cs
index b007dce..f0f9ccb 100644
--- a/UICompositionAnimations/Behaviours/AcrylicEffectHelper.cs
+++ b/UICompositionAnimations/Behaviours/AcrylicEffectHelper.cs
@@ -28,15 +28,14 @@ internal static class AcrylicEffectHelper
/// 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
- /// Indicates whether or not to force the reload of the Win2D image
/// 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,
+ [NotNull] IGraphicsEffectSource source, [NotNull] IDictionary parameters,
Color color, float colorMix,
- [CanBeNull] CanvasControl canvas, [NotNull] Uri uri, BitmapCacheMode options)
+ [CanBeNull] CanvasControl canvas, [NotNull] Uri uri)
{
// Setup the tint effect
ArithmeticCompositeEffect tint = new ArithmeticCompositeEffect
@@ -50,8 +49,8 @@ internal static class AcrylicEffectHelper
// Get the noise brush using Win2D
CompositionSurfaceBrush noiseBitmap = canvas == null
- ? await Win2DImageHelper.LoadImageAsync(compositor, uri, options, BitmapDPIMode.CopyDisplayDPISettingsWith96AsLowerBound)
- : await Win2DImageHelper.LoadImageAsync(compositor, canvas, uri, options, BitmapDPIMode.CopyDisplayDPISettingsWith96AsLowerBound);
+ ? 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)
@@ -82,17 +81,16 @@ internal static class AcrylicEffectHelper
/// 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
- /// Indicates whether or not to force the reload of the Win2D image
/// 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, BitmapCacheMode options)
+ [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, options, BitmapDPIMode.CopyDisplayDPISettingsWith96AsLowerBound);
+ CompositionSurfaceBrush noiseBitmap = await Win2DImageHelper.LoadImageAsync(compositor, uri, BitmapDPIMode.CopyDisplayDPISettingsWith96AsLowerBound, BitmapCacheMode.Default);
if (noiseBitmap == null) return colorEffect;
// Blend the effects
@@ -112,4 +110,4 @@ internal static class AcrylicEffectHelper
return blendEffect;
}
}
-}
+}
\ No newline at end of file
diff --git a/UICompositionAnimations/Behaviours/AttachedCompositionEffectFactory.cs b/UICompositionAnimations/Behaviours/AttachedCompositionEffectFactory.cs
deleted file mode 100644
index 548734c..0000000
--- a/UICompositionAnimations/Behaviours/AttachedCompositionEffectFactory.cs
+++ /dev/null
@@ -1,740 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Numerics;
-using System.Threading.Tasks;
-using Windows.Graphics.Effects;
-using Windows.UI;
-using Windows.UI.Composition;
-using Windows.UI.Xaml;
-using Windows.UI.Xaml.Hosting;
-using JetBrains.Annotations;
-using Microsoft.Graphics.Canvas.Effects;
-using Microsoft.Graphics.Canvas.UI.Xaml;
-using UICompositionAnimations.Behaviours.Effects;
-using UICompositionAnimations.Behaviours.Misc;
-using UICompositionAnimations.Composition;
-using UICompositionAnimations.Enums;
-using UICompositionAnimations.Helpers;
-
-namespace UICompositionAnimations.Behaviours
-{
- ///
- /// A static class that manages the creation of attached composition effects
- ///
-
- public static class AttachedCompositionEffectsFactory
- {
- #region Static effects
-
- ///
- /// Creates a new instance for the target element
- ///
- /// The type of element to blur
- /// The target element
- /// The amount of blur to apply to the element
- /// The duration of the initial blur animation, in milliseconds
- /// Indicates whether or not to automatically dispose and remove the effect when the target element is unloaded
- /// This method returns a instance and runs synchronously if called on the UI thread
- [ItemNotNull]
- public static async ValueTask> AttachCompositionBlurEffect(
- [NotNull] this T element, float blur, int ms, bool disposeOnUnload = false) where T : FrameworkElement
- {
- // Get the visual and the compositor
- Visual visual = element.GetVisual();
- Compositor compositor = visual.Compositor;
-
- // Create the blur effect and the effect factory
- GaussianBlurEffect blurEffect = new GaussianBlurEffect
- {
- Name = "Blur",
- BlurAmount = 0f,
- BorderMode = EffectBorderMode.Hard,
- Optimization = EffectOptimization.Balanced,
- Source = new CompositionEffectSourceParameter("source")
- };
- CompositionEffectFactory effectFactory = compositor.CreateEffectFactory(blurEffect, new[] { "Blur.BlurAmount" });
-
- // Setup the rest of the effect
- CompositionEffectBrush effectBrush = effectFactory.CreateBrush();
- effectBrush.SetSourceParameter("source", compositor.CreateBackdropBrush());
-
- // Assign the effect to a brush and display it
- SpriteVisual sprite = compositor.CreateSpriteVisual();
- sprite.Brush = effectBrush;
- sprite.Size = new Vector2((float)element.ActualWidth, (float)element.ActualHeight);
- await AddToTreeAndBindSizeAsync(visual, element, sprite);
-
- // Animate the blur amount
- effectBrush.StartAnimationAsync("Blur.BlurAmount", blur, TimeSpan.FromMilliseconds(ms)).Forget();
-
- // Prepare and return the manager
- return new AttachedStaticCompositionEffect(element, sprite, disposeOnUnload);
- }
-
- ///
- /// Creates an effect brush that's similar to the official Acrylic brush in the Fall Creator's Update.
- /// The pipeline uses the following effects: BackdropBrush > >
- /// > with customizable blend factors for each couple of layers
- ///
- /// The type of the element that will be the source for the composition effect
- /// The type of the target element that will host the resulting
- /// The that will be the source of the effect
- /// The target host for the resulting effect
- /// The amount of blur to apply to the element
- /// The duration of the initial blur animation, in milliseconds
- /// The tint color for the effect
- /// The opacity of the color over the blurred background
- /// An optional parameter to set the overall saturation of the effect (if null, it will default to 1)
- /// The optional source to generate the noise image using Win2D
- /// The path of the noise image to use
- /// Indicates whether or not to force the reload of the Win2D image
- /// Indicates whether or not to fade the effect in
- /// Indicates whether or not to automatically dispose and remove the effect when the target element is unloaded
- [ItemNotNull]
- public static async Task> AttachCompositionInAppCustomAcrylicEffectAsync(
- [NotNull] this TSource element, [NotNull] T target, float blur, int ms, Color color, float colorMix, float? saturation,
- [CanBeNull] CanvasControl canvas, [NotNull] Uri uri,
- BitmapCacheMode options = BitmapCacheMode.EnableCaching, bool fadeIn = false, bool disposeOnUnload = false)
- where TSource : FrameworkElement
- where T : FrameworkElement
- {
- // Percentage check
- if (saturation < 0 || saturation > 1) throw new ArgumentOutOfRangeException("The input saturation value must be in the [0,1] range");
- if (colorMix <= 0 || colorMix >= 1) throw new ArgumentOutOfRangeException("The mix factors must be in the [0,1] range");
-
- // Setup the compositor
- Visual visual = ElementCompositionPreview.GetElementVisual(element);
- Compositor compositor = visual.Compositor;
-
- // Prepare a luminosity to alpha effect to adjust the background contrast
- CompositionBackdropBrush backdropBrush = compositor.CreateBackdropBrush();
- const String
- blurName = "Blur",
- blurParameterName = "Blur.BlurAmount";
- GaussianBlurEffect blurEffect = new GaussianBlurEffect
- {
- Name = blurName,
- BlurAmount = 0f,
- BorderMode = EffectBorderMode.Hard,
- Optimization = EffectOptimization.Balanced,
- Source = new CompositionEffectSourceParameter(nameof(backdropBrush))
- };
-
- // Background with blur and tint overlay
- IDictionary sourceParameters = new Dictionary
- {
- { nameof(backdropBrush), backdropBrush }
- };
-
- // Get the noise brush using Win2D
- IGraphicsEffect source = await AcrylicEffectHelper.ConcatenateEffectWithTintAndBorderAsync(compositor,
- blurEffect, sourceParameters, color, colorMix, canvas, uri, options);
-
- // Add the final saturation effect if needed
- if (saturation != null)
- {
- SaturationEffect saturationEffect = new SaturationEffect
- {
- Saturation = saturation.Value,
- Source = source
- };
- source = saturationEffect;
- }
-
- // Make sure the Win2D brush was loaded correctly
- CompositionEffectFactory factory = compositor.CreateEffectFactory(source, new[] { blurParameterName });
-
- // Create the effect factory and apply the final effect
- CompositionEffectBrush effectBrush = factory.CreateBrush();
- foreach (KeyValuePair pair in sourceParameters)
- {
- effectBrush.SetSourceParameter(pair.Key, pair.Value);
- }
-
- // Create the sprite to display and add it to the visual tree
- SpriteVisual sprite = compositor.CreateSpriteVisual();
- sprite.Brush = effectBrush;
-
- // Assign the visual
- if (fadeIn)
- {
- sprite.StopAnimation("Opacity");
- sprite.Opacity = 0;
- }
- await AddToTreeAndBindSizeAsync(target.GetVisual(), target, sprite);
- if (fadeIn)
- {
- // Fade the effect in
- ScalarKeyFrameAnimation opacityAnimation = sprite.Compositor.CreateScalarKeyFrameAnimation(0,
- 1, TimeSpan.FromMilliseconds(ms), null, sprite.GetEasingFunction(EasingFunctionNames.Linear));
- sprite.StartAnimation("Opacity", opacityAnimation);
- }
-
- // Animate the blur and return the result
- effectBrush.StartAnimationAsync(blurParameterName, blur, TimeSpan.FromMilliseconds(ms)).Forget();
- return new AttachedStaticCompositionEffect(target, sprite, disposeOnUnload);
- }
-
- ///
- /// Creates a new instance for the target element
- ///
- /// The type of element to use to host the effect
- /// The target element
- /// Indicates whether or not to automatically dispose and remove the effect when the target element is unloaded
- /// This method returns a instance and runs synchronously if called on the UI thread
- [ItemNotNull]
- public static async ValueTask> AttachCompositionHostBackdropBlurEffect(
- [NotNull] this T element, bool disposeOnUnload = false) where T : FrameworkElement
- {
- // Setup the host backdrop effect
- Visual visual = ElementCompositionPreview.GetElementVisual(element);
- Compositor compositor = visual.Compositor;
- CompositionBackdropBrush brush = compositor.CreateHostBackdropBrush();
- SpriteVisual sprite = compositor.CreateSpriteVisual();
- sprite.Brush = brush;
- await AddToTreeAndBindSizeAsync(visual, element, sprite);
- return new AttachedStaticCompositionEffect(element, sprite, disposeOnUnload);
- }
-
- ///
- /// Creates an effect brush that's similar to the official Acrylic brush in the Fall Creator's Update.
- /// The pipeline uses the following effects: HostBackdropBrush > >
- /// > > >
- /// > with customizable blend factors for each couple of layers
- ///
- /// The type of the target element that will host the resulting
- /// The target element that will host the effect
- /// The tint color for the effect
- /// The opacity of the color over the blurred background
- /// The optional source to generate the noise image using Win2D
- /// The path of the noise image to use
- /// Indicates whether or not to force the reload of the Win2D image
- /// Indicates whether or not to automatically dispose and remove the effect when the target element is unloaded
- [ItemNotNull]
- public static async Task> AttachCompositionCustomAcrylicEffectAsync(
- [NotNull] this T element, Color color, float colorMix,
- [CanBeNull] CanvasControl canvas, [NotNull] Uri uri, BitmapCacheMode options = BitmapCacheMode.EnableCaching, bool disposeOnUnload = false)
- where T : FrameworkElement
- {
- // Percentage check
- if (colorMix <= 0 || colorMix >= 1) throw new ArgumentOutOfRangeException("The mix factors must be in the [0,1] range");
-
- // Setup the compositor
- Visual visual = ElementCompositionPreview.GetElementVisual(element);
- Compositor compositor = visual.Compositor;
-
- // Prepare a luminosity to alpha effect to adjust the background contrast
- CompositionBackdropBrush hostBackdropBrush = 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 tint overlay
- BlendEffect alphaBlend = new BlendEffect
- {
- Background = backgroundParameter,
- Foreground = opacityEffect,
- Mode = BlendEffectMode.Overlay
- };
- IDictionary sourceParameters = new Dictionary
- {
- { nameof(hostBackdropBrush), hostBackdropBrush }
- };
-
- // Get the noise brush using Win2D
- IGraphicsEffect source = await AcrylicEffectHelper.ConcatenateEffectWithTintAndBorderAsync(compositor,
- alphaBlend, sourceParameters, color, colorMix, canvas, uri, options);
-
- // Make sure the Win2D brush was loaded correctly
- CompositionEffectFactory factory = compositor.CreateEffectFactory(source);
-
- // Create the effect factory and apply the final effect
- CompositionEffectBrush effectBrush = factory.CreateBrush();
- foreach (KeyValuePair pair in sourceParameters)
- {
- effectBrush.SetSourceParameter(pair.Key, pair.Value);
- }
-
- // Create the sprite to display and add it to the visual tree
- SpriteVisual sprite = compositor.CreateSpriteVisual();
- sprite.Brush = effectBrush;
- await AddToTreeAndBindSizeAsync(visual, element, sprite);
- return new AttachedStaticCompositionEffect(element, sprite, disposeOnUnload);
- }
-
- ///
- /// Creates an effect brush that's similar to the official Acrylic brush in the Fall Creator's Update and can be toggled
- /// between the host backdrop blur effect and the in-app acrylic brush effect.
- /// The pipeline uses the following effects: HostBackdropBrush > >
- /// > > >
- /// > with customizable blend factors for each couple of layers
- ///
- /// The type of the target element that will host the resulting
- /// The target element that will host the effect
- /// The tint color for the effect
- /// The opacity of the color over the in-app blurred contents
- /// The opacity of the color over the blurred background
- /// Indicates the initial mode for the custom effect
- /// The amount of blur to apply to the element
- /// The duration of the initial blur animation, in milliseconds
- /// The optional source to generate the noise image using Win2D
- /// The path of the noise image to use
- /// Indicates whether or not to force the reload of the Win2D image
- /// Indicates whether or not to automatically dispose and remove the effect when the target element is unloaded
- [ItemNotNull]
- public static async Task> AttachCompositionCustomAcrylicToggleEffectAsync(
- [NotNull] this T element, Color color, float inAppColorMix, float hostColorMix,
- AcrylicEffectMode mode, float blur, int ms,
- [CanBeNull] CanvasControl canvas, [NotNull] Uri uri, BitmapCacheMode options = BitmapCacheMode.EnableCaching, bool disposeOnUnload = false)
- where T : FrameworkElement
- {
- // Percentage check
- if (hostColorMix <= 0 || hostColorMix >= 1 ||
- inAppColorMix <= 0 || inAppColorMix >= 1) throw new ArgumentOutOfRangeException("The mix factors must be in the [0,1] range");
-
- // Setup the compositor
- Visual visual = ElementCompositionPreview.GetElementVisual(element);
- Compositor compositor = visual.Compositor;
-
- // Prepare a luminosity to alpha effect to adjust the background contrast
- CompositionBackdropBrush hostBackdropBrush = 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 tint overlay
- BlendEffect alphaBlend = new BlendEffect
- {
- Background = backgroundParameter,
- Foreground = opacityEffect,
- Mode = BlendEffectMode.Overlay
- };
-
- // In-app backdrop effect
- CompositionBackdropBrush backdropBrush = compositor.CreateBackdropBrush();
- const String
- blurName = "Blur",
- blurParameterName = "Blur.BlurAmount";
- GaussianBlurEffect blurEffect = new GaussianBlurEffect
- {
- Name = blurName,
- BlurAmount = 0f,
- BorderMode = EffectBorderMode.Hard,
- Optimization = EffectOptimization.Balanced,
- Source = new CompositionEffectSourceParameter(nameof(backdropBrush))
- };
-
- // Setup the switch effect
- ArithmeticCompositeEffect switchEffect = new ArithmeticCompositeEffect
- {
- Name = "Switch",
- MultiplyAmount = 0,
- Source1Amount = mode == AcrylicEffectMode.InAppBlur ? 1 : 0,
- Source2Amount = mode == AcrylicEffectMode.InAppBlur ? 0 : 1,
- Source1 = blurEffect,
- Source2 = alphaBlend
- };
-
- // Get the tint and noise brushes using Win2D
- IDictionary sourceParameters = new Dictionary
- {
- { nameof(hostBackdropBrush), hostBackdropBrush },
- { nameof(backdropBrush), backdropBrush }
- };
- IGraphicsEffect source = await AcrylicEffectHelper.ConcatenateEffectWithTintAndBorderAsync(compositor,
- switchEffect, sourceParameters, color, mode == AcrylicEffectMode.InAppBlur ? inAppColorMix : hostColorMix, canvas, uri, options);
-
- // Setup the tint effect
- 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";
- const String
- tint1Name = "Tint.Source1Amount",
- tint2Name = "Tint.Source2Amount";
-
- // Make sure the Win2D brush was loaded correctly
- const String
- source1Name = "Switch.Source1Amount",
- source2Name = "Switch.Source2Amount";
- CompositionEffectFactory factory = compositor.CreateEffectFactory(source, new[]
- {
- blurParameterName,
- tint1Name,
- tint2Name,
- source1Name,
- source2Name
- });
-
- // Create the effect factory and apply the final effect
- CompositionEffectBrush effectBrush = factory.CreateBrush();
- foreach (KeyValuePair pair in sourceParameters)
- {
- effectBrush.SetSourceParameter(pair.Key, pair.Value);
- }
-
- // Setup the toggle function
- void Toggle(AcrylicEffectMode m)
- {
- effectBrush.SetInstantValue(source1Name, m == AcrylicEffectMode.InAppBlur ? 1 : 0);
- effectBrush.SetInstantValue(source2Name, m == AcrylicEffectMode.InAppBlur ? 0 :1);
- float
- mix = m == AcrylicEffectMode.InAppBlur ? inAppColorMix : hostColorMix,
- source1 = 1 - mix,
- source2 = mix;
- effectBrush.SetInstantValue(tint1Name, source1);
- effectBrush.SetInstantValue(tint2Name, source2);
- }
-
- // Create the sprite to display and add it to the visual tree
- SpriteVisual sprite = compositor.CreateSpriteVisual();
- sprite.Brush = effectBrush;
- await AddToTreeAndBindSizeAsync(visual, element, sprite);
- if (mode == AcrylicEffectMode.InAppBlur) effectBrush.StartAnimationAsync(blurParameterName, blur, TimeSpan.FromMilliseconds(ms)).Forget();
- return new AttachedToggleAcrylicEffect(element, mode,
- mode == AcrylicEffectMode.InAppBlur
- ? (Action)null
- : () => effectBrush.StartAnimationAsync(blurParameterName, blur, TimeSpan.FromMilliseconds(ms)).Forget(),
- Toggle, sprite, disposeOnUnload);
- }
-
- #endregion
-
- #region Animated effects
-
- ///
- /// Creates a new instance for the target element
- ///
- /// The type of element to blur
- /// The target element
- /// The amount of saturation effect to apply
- /// The default amount of saturation effect to apply
- /// Indicates whether or not to apply the effect right away
- /// Indicates whether or not to automatically dispose and remove the effect when the target element is unloaded
- [ItemNotNull]
- public static async Task> AttachCompositionAnimatableSaturationEffectAsync(
- [NotNull] this T element, float on, float off, bool initiallyVisible, bool disposeOnUnload = false)
- where T : FrameworkElement
- {
- // Get the compositor
- Visual visual = await element.Dispatcher.GetAsync(element.GetVisual);
- Compositor compositor = visual.Compositor;
-
- // Create the saturation effect and the effect factory
- SaturationEffect saturationEffect = new SaturationEffect
- {
- Name = "SEffect",
- Saturation = initiallyVisible ? off : on,
- Source = new CompositionEffectSourceParameter("source")
- };
- const String animationPropertyName = "SEffect.Saturation";
- CompositionEffectFactory effectFactory = compositor.CreateEffectFactory(saturationEffect, new[] { animationPropertyName });
-
- // Setup the rest of the effect
- CompositionEffectBrush effectBrush = effectFactory.CreateBrush();
- effectBrush.SetSourceParameter("source", compositor.CreateBackdropBrush());
-
- // Assign the effect to a brush and display it
- SpriteVisual sprite = compositor.CreateSpriteVisual();
- sprite.Brush = effectBrush;
- await AddToTreeAndBindSizeAsync(visual, element, sprite);
- if (initiallyVisible) await element.Dispatcher.RunAsync(() => element.Opacity = 1);
- return new AttachedAnimatableCompositionEffect(element, sprite, new CompositionAnimationParameters(animationPropertyName, on, off), disposeOnUnload);
- }
-
- ///
- /// Creates a new instance for the target element
- ///
- /// The type of element to blur
- /// The target element
- /// The amount of blur effect to apply
- /// The default amount of blur effect to apply
- /// Indicates whether or not to apply the effect right away
- /// Indicates whether or not to automatically dispose and remove the effect when the target element is unloaded
- [ItemNotNull]
- public static async Task> AttachCompositionAnimatableBlurEffectAsync(
- [NotNull] this T element, float on, float off, bool initiallyVisible, bool disposeOnUnload = false) where T : FrameworkElement
- {
- // Get the compositor
- Visual visual = await element.Dispatcher.GetAsync(element.GetVisual);
- Compositor compositor = visual.Compositor;
-
- // Create the blur effect and the effect factory
- GaussianBlurEffect blurEffect = new GaussianBlurEffect
- {
- Name = "Blur",
- BlurAmount = 0f,
- BorderMode = EffectBorderMode.Hard,
- Optimization = EffectOptimization.Balanced,
- Source = new CompositionEffectSourceParameter("source")
- };
- const String animationPropertyName = "Blur.BlurAmount";
- CompositionEffectFactory effectFactory = compositor.CreateEffectFactory(blurEffect, new[] { animationPropertyName });
-
- // Setup the rest of the effect
- CompositionEffectBrush effectBrush = effectFactory.CreateBrush();
- effectBrush.SetSourceParameter("source", compositor.CreateBackdropBrush());
-
- // Assign the effect to a brush and display it
- SpriteVisual sprite = compositor.CreateSpriteVisual();
- sprite.Brush = effectBrush;
- await AddToTreeAndBindSizeAsync(visual, element, sprite);
- if (initiallyVisible) await element.Dispatcher.RunAsync(() => element.Opacity = 1);
- return new AttachedAnimatableCompositionEffect(element, sprite, new CompositionAnimationParameters(animationPropertyName, on, off), disposeOnUnload);
- }
-
- ///
- /// Creates a new instance with blur, tint and noise effects
- ///
- /// The type of the element that will be the source for the composition effect
- /// The type of the target element that will host the resulting
- /// The that will be the source of the effect
- /// The target host for the resulting effect
- /// The amount of blur effect to apply
- /// The default amount of blur effect to apply
- /// Indicates whether or not to apply the effect right away
- /// The tint color for the effect
- /// The opacity of the color over the blurred background
- /// The optional source to generate the noise image using Win2D
- /// The path of the noise image to use
- /// Indicates whether or not to force the reload of the Win2D image
- /// Indicates whether or not to automatically dispose and remove the effect when the target element is unloaded
- [ItemNotNull]
- public static async Task> AttachCompositionAnimatableInAppCustomAcrylicEffectAsync(
- [NotNull] this TSource element, [NotNull] T target,
- float on, float off, bool initiallyVisible,
- Color color, float colorMix, [CanBeNull] CanvasControl canvas, [NotNull] Uri uri,
- BitmapCacheMode options = BitmapCacheMode.EnableCaching, bool disposeOnUnload = false)
- where TSource : FrameworkElement
- where T : FrameworkElement
- {
- // Get the compositor
- Visual visual = await element.Dispatcher.GetAsync(element.GetVisual);
- Compositor compositor = visual.Compositor;
-
- // Create the blur effect and the effect factory
- CompositionBackdropBrush backdropBrush = compositor.CreateBackdropBrush();
- GaussianBlurEffect blurEffect = new GaussianBlurEffect
- {
- Name = "Blur",
- BlurAmount = 0f,
- BorderMode = EffectBorderMode.Hard,
- Optimization = EffectOptimization.Balanced,
- Source = new CompositionEffectSourceParameter(nameof(backdropBrush))
- };
- const String animationPropertyName = "Blur.BlurAmount";
-
- // Prepare the dictionary with the parameters to add
- IDictionary sourceParameters = new Dictionary
- {
- { nameof(backdropBrush), backdropBrush }
- };
-
- // Get the noise brush using Win2D
- IGraphicsEffect source = await AcrylicEffectHelper.ConcatenateEffectWithTintAndBorderAsync(compositor,
- blurEffect, sourceParameters, color, colorMix, canvas, uri, options);
-
- // Make sure the Win2D brush was loaded correctly
- CompositionEffectFactory effectFactory = compositor.CreateEffectFactory(source, new[] { animationPropertyName });
-
- // Create the effect factory and apply the final effect
- CompositionEffectBrush effectBrush = effectFactory.CreateBrush();
- foreach (KeyValuePair pair in sourceParameters)
- {
- effectBrush.SetSourceParameter(pair.Key, pair.Value);
- }
-
- // Assign the effect to a brush and display it
- SpriteVisual sprite = compositor.CreateSpriteVisual();
- sprite.Brush = effectBrush;
- await AddToTreeAndBindSizeAsync(target.GetVisual(), target, sprite);
- if (initiallyVisible) await element.Dispatcher.RunAsync(() => element.Opacity = 1);
- return new AttachedAnimatableCompositionEffect(target, sprite, new CompositionAnimationParameters(animationPropertyName, on, off), disposeOnUnload);
- }
-
- ///
- /// Creates a new instance with blur, tint and noise effects
- ///
- /// The type of the element that will be the source for the composition effect
- /// The type of the target element that will host the resulting
- /// The that will be the source of the effect
- /// The target host for the resulting effect
- /// The amount of blur effect to apply
- /// The default amount of blur effect to apply
- /// The amount of saturation effect to apply
- /// The default amount of saturation effect to apply
- /// Indicates whether or not to apply the effect right away
- /// The tint color for the effect
- /// The opacity of the color over the blurred background
- /// The optional source to generate the noise image using Win2D
- /// The path of the noise image to use
- /// Indicates whether or not to force the reload of the Win2D image
- /// Indicates whether or not to automatically dispose and remove the effect when the target element is unloaded
- [ItemNotNull]
- public static async Task> AttachCompositionAnimatableInAppCustomAcrylicAndSaturationEffectAsync(
- [NotNull] this TSource element, [NotNull] T target,
- float onBlur, float offBlur,
- float onSaturation, float offSaturation,
- bool initiallyVisible,
- Color color, float colorMix, [CanBeNull] CanvasControl canvas, [NotNull] Uri uri,
- BitmapCacheMode options = BitmapCacheMode.EnableCaching, bool disposeOnUnload = false)
- where TSource : FrameworkElement
- where T : FrameworkElement
- {
- // Get the compositor
- Visual visual = await element.Dispatcher.GetAsync(element.GetVisual);
- Compositor compositor = visual.Compositor;
-
- // Create the blur effect and the effect factory
- CompositionBackdropBrush backdropBrush = compositor.CreateBackdropBrush();
- GaussianBlurEffect blurEffect = new GaussianBlurEffect
- {
- Name = "Blur",
- BlurAmount = 0f,
- BorderMode = EffectBorderMode.Hard,
- Optimization = EffectOptimization.Balanced,
- Source = new CompositionEffectSourceParameter(nameof(backdropBrush))
- };
- const String animationPropertyName = "Blur.BlurAmount";
-
- // Prepare the dictionary with the parameters to add
- IDictionary sourceParameters = new Dictionary
- {
- { nameof(backdropBrush), backdropBrush }
- };
-
- // Get the noise brush using Win2D
- IGraphicsEffect source = await AcrylicEffectHelper.ConcatenateEffectWithTintAndBorderAsync(compositor,
- blurEffect, sourceParameters, color, colorMix, canvas, uri, options);
-
- // Add the final saturation effect
- SaturationEffect saturationEffect = new SaturationEffect
- {
- Name = "SEffect",
- Saturation = initiallyVisible ? offSaturation : onSaturation,
- Source = source
- };
- const String saturationParameter = "SEffect.Saturation";
-
- // Make sure the Win2D brush was loaded correctly
- CompositionEffectFactory effectFactory = compositor.CreateEffectFactory(saturationEffect, new[]
- {
- animationPropertyName,
- saturationParameter
- });
-
- // Create the effect factory and apply the final effect
- CompositionEffectBrush effectBrush = effectFactory.CreateBrush();
- foreach (KeyValuePair pair in sourceParameters)
- {
- effectBrush.SetSourceParameter(pair.Key, pair.Value);
- }
-
- // Assign the effect to a brush and display it
- SpriteVisual sprite = compositor.CreateSpriteVisual();
- sprite.Brush = effectBrush;
- await AddToTreeAndBindSizeAsync(target.GetVisual(), target, sprite);
- if (initiallyVisible) await element.Dispatcher.RunAsync(() => element.Opacity = 1);
- return new AttachedCompositeAnimatableCompositionEffect(target, sprite,
- new Dictionary
- {
- { animationPropertyName, new CompositionAnimationValueParameters(onBlur, offBlur) },
- { saturationParameter, new CompositionAnimationValueParameters(onSaturation, offSaturation) }
- }, disposeOnUnload);
- }
-
- ///
- /// Creates a new instance for the target element that
- /// applies both a blur and a saturation effect to the visual item
- ///
- /// The type of element to blur
- /// The target element
- /// The amount of blur effect to apply
- /// The default amount of blur effect to apply
- /// The amount of saturation effect to apply
- /// The default amount of saturation effect to apply
- /// Indicates whether or not to apply the effect right away
- /// Indicates whether or not to automatically dispose and remove the effect when the target element is unloaded
- [ItemNotNull]
- public static async Task> AttachCompositionAnimatableBlurAndSaturationEffectAsync(
- [NotNull] this T element, float onBlur, float offBlur, float onSaturation, float offSaturation, bool initiallyVisible, bool disposeOnUnload = false)
- where T : FrameworkElement
- {
- // Get the compositor
- Visual visual = await element.Dispatcher.GetAsync(element.GetVisual);
- Compositor compositor = visual.Compositor;
-
- // Create the blur effect, the saturation effect and the effect factory
- GaussianBlurEffect blurEffect = new GaussianBlurEffect
- {
- Name = "Blur",
- BlurAmount = 0f,
- BorderMode = EffectBorderMode.Hard,
- Optimization = EffectOptimization.Balanced,
- Source = new CompositionEffectSourceParameter("source")
- };
- SaturationEffect saturationEffect = new SaturationEffect
- {
- Name = "SEffect",
- Saturation = initiallyVisible ? offSaturation : onSaturation,
- Source = blurEffect
- };
- const String blurParameter = "Blur.BlurAmount", saturationParameter = "SEffect.Saturation";
- CompositionEffectFactory effectFactory = compositor.CreateEffectFactory(saturationEffect, new[]
- {
- blurParameter,
- saturationParameter
- });
-
- // Setup the rest of the effect
- CompositionEffectBrush effectBrush = effectFactory.CreateBrush();
- effectBrush.SetSourceParameter("source", compositor.CreateBackdropBrush());
-
- // Assign the effect to a brush and display it
- SpriteVisual sprite = compositor.CreateSpriteVisual();
- sprite.Brush = effectBrush;
- await AddToTreeAndBindSizeAsync(visual, element, sprite);
- if (initiallyVisible) await element.Dispatcher.RunAsync(() => element.Opacity = 1);
-
- // Prepare and return the wrapped effect
- return new AttachedCompositeAnimatableCompositionEffect(element, sprite,
- new Dictionary
- {
- { blurParameter, new CompositionAnimationValueParameters(onBlur, offBlur) },
- { saturationParameter, new CompositionAnimationValueParameters(onSaturation, offSaturation) }
- }, disposeOnUnload);
- }
-
- #endregion
-
- #region Tools
-
- ///
- /// Adds a object on top of the target and binds the size of the two items with an expression animation
- ///
- /// The object that will host the effect
- /// The target (bound to the given visual) that will host the effect
- /// The source object to display
- private static async Task AddToTreeAndBindSizeAsync([NotNull] Visual host, [NotNull] UIElement element, [NotNull] Visual visual)
- {
- // Add the shadow as a child of the host in the visual tree
- await element.Dispatcher.RunAsync(() => ElementCompositionPreview.SetElementChildVisual(element, visual));
-
- // Make sure size of shadow host and shadow visual always stay in sync
- ExpressionAnimation bindSizeAnimation = host.Compositor.CreateExpressionAnimation($"{nameof(host)}.Size");
- bindSizeAnimation.SetReferenceParameter(nameof(host), host);
-
- // Start the animation
- visual.StartAnimation("Size", bindSizeAnimation);
- }
-
- #endregion
- }
-}
\ No newline at end of file
diff --git a/UICompositionAnimations/Behaviours/CompositionBrushBuilder.cs b/UICompositionAnimations/Behaviours/CompositionBrushBuilder.cs
new file mode 100644
index 0000000..87f0069
--- /dev/null
+++ b/UICompositionAnimations/Behaviours/CompositionBrushBuilder.cs
@@ -0,0 +1,693 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+using Windows.Graphics.Effects;
+using Windows.UI;
+using Windows.UI.Composition;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Hosting;
+using JetBrains.Annotations;
+using Microsoft.Graphics.Canvas;
+using Microsoft.Graphics.Canvas.Effects;
+using UICompositionAnimations.Brushes;
+using UICompositionAnimations.Brushes.Cache;
+using UICompositionAnimations.Enums;
+using UICompositionAnimations.Helpers;
+
+namespace UICompositionAnimations.Behaviours
+{
+ ///
+ /// A that represents a custom effect animation that can be applied to a
+ ///
+ /// The target instance to use to start the animation
+ /// The animation target value
+ /// The animation duration, in milliseconds
+ public delegate Task EffectAnimation([NotNull] CompositionBrush brush, float value, int ms);
+
+ ///
+ /// A that allows to build custom effects pipelines and create instances from them
+ ///
+ [PublicAPI]
+ public sealed class CompositionBrushBuilder
+ {
+ ///
+ /// The instance used to produce the output for this pipeline
+ ///
+ [NotNull]
+ private readonly Func> SourceProducer;
+
+ ///
+ /// The collection of animation properties present in the current pipeline
+ ///
+ [NotNull, ItemNotNull]
+ private readonly IReadOnlyCollection AnimationProperties;
+
+ ///
+ /// The collection of info on the parameters that need to be initialized after creating the final
+ ///
+ [NotNull]
+ private readonly IReadOnlyDictionary>> LazyParameters;
+
+ #region Constructors
+
+ ///
+ /// 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)
+ {
+ string
+ guid = Guid.NewGuid().ToString("N"),
+ replaced = Regex.Replace(guid, "[0-9]", "_"),
+ id = new string(replaced.ToCharArray().Select((c, i) => c == '_' ? char.ToUpper((char)('a' + i % 26)) : c).ToArray());
+ SourceProducer = () => Task.FromResult(new CompositionEffectSourceParameter(id).To());
+ LazyParameters = new Dictionary>> { { id, factory } };
+ AnimationProperties = new string[0];
+ }
+
+ ///
+ /// Base constructor used to create a new instance from scratch
+ ///
+ /// 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)
+ {
+ SourceProducer = factory;
+ AnimationProperties = animations;
+ LazyParameters = lazy;
+ }
+
+ ///
+ /// Constructor used to initialize a pipeline from a custom instance
+ ///
+ /// A instance that will return the initial
+ private CompositionBrushBuilder([NotNull] Func> factory)
+ : this(factory, new string[0], new Dictionary>>())
+ { }
+
+ ///
+ /// Constructor used to create a new instance obtained by concatenation between the current pipeline and the input effect info
+ ///
+ /// The source pipeline to attach the new effect to
+ /// 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,
+ [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)
+ { }
+
+ ///
+ /// Constructor used to create a new instance obtained by merging two separate pipelines
+ ///
+ /// A instance that will produce the new to add to the pipeline
+ /// The first pipeline to merge
+ /// 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,
+ [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))
+ { }
+
+ #endregion
+
+ #region Initialization
+
+ // The cache manager for backdrop brushes
+ [NotNull]
+ private static readonly ThreadSafeCompositionCache BackdropBrushCache = new ThreadSafeCompositionCache();
+
+ ///
+ /// Starts a new pipeline from the returned by
+ ///
+ [Pure, NotNull]
+ public static CompositionBrushBuilder FromBackdropBrush() => new CompositionBrushBuilder(() => 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
+ ///
+ [Pure, NotNull]
+ public static CompositionBrushBuilder FromHostBackdropBrush() => new CompositionBrushBuilder(() => HostBackdropBrushCache.TryGetInstanceAsync(Window.Current.Compositor.CreateHostBackdropBrush));
+
+ ///
+ /// 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()));
+
+ ///
+ /// 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()));
+
+ ///
+ /// 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);
+
+ ///
+ /// 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()));
+
+ ///
+ /// 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);
+
+ ///
+ /// 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)
+ {
+ return new CompositionBrushBuilder(() => 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
+ ///
+ /// 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)
+ {
+ CompositionBrushBuilder image = FromImage(uri, dpiMode, cache);
+
+ async Task Factory() => new BorderEffect
+ {
+ ExtendX = CanvasEdgeBehavior.Wrap,
+ ExtendY = CanvasEdgeBehavior.Wrap,
+ Source = await image.SourceProducer()
+ };
+
+ return new CompositionBrushBuilder(image, Factory);
+ }
+
+ ///
+ /// 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)
+ {
+ return new CompositionBrushBuilder(() => Task.FromResult(element.GetVisual().Compositor.CreateBackdropBrush().To()));
+ }
+
+ #endregion
+
+ #region Prebuilt pipelines
+
+ ///
+ /// 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)
+ {
+ 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);
+ }
+
+ ///
+ /// 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
+ /// 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, out EffectAnimation tintAnimation, [NotNull] Uri noiseUri, BitmapCacheMode cache = BitmapCacheMode.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);
+ }
+
+ ///
+ /// 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
+ /// The amount of blur to apply to the acrylic brush
+ /// 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)
+ {
+ return FromBackdropBrush()
+ .Tint(tint, mix)
+ .Blur(blur)
+ .Blend(FromTiles(noiseUri, cache: cache), BlendEffectMode.Overlay, EffectPlacement.Background);
+ }
+
+ ///
+ /// 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
+ /// The animation to apply on the tint color of the effect
+ /// The amount of blur to apply to the acrylic brush
+ /// 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, out EffectAnimation tintAnimation,
+ float blur,
+ [NotNull] Uri noiseUri, BitmapCacheMode cache = BitmapCacheMode.Default)
+ {
+ return FromBackdropBrush()
+ .Tint(tint, mix, out tintAnimation)
+ .Blur(blur)
+ .Blend(FromTiles(noiseUri, cache: cache), BlendEffectMode.Overlay, EffectPlacement.Background);
+ }
+
+ ///
+ /// 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
+ /// The amount of blur to apply to the acrylic brush
+ /// The animation to apply on the blur effect in the pipeline
+ /// 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, out EffectAnimation blurAnimation,
+ [NotNull] Uri noiseUri, BitmapCacheMode cache = BitmapCacheMode.Default)
+ {
+ return FromBackdropBrush()
+ .Tint(tint, mix)
+ .Blur(blur, out blurAnimation)
+ .Blend(FromTiles(noiseUri, cache: cache), BlendEffectMode.Overlay, EffectPlacement.Background);
+ }
+
+ ///
+ /// 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
+ /// The animation to apply on the tint color of the effect
+ /// The amount of blur to apply to the acrylic brush
+ /// The animation to apply on the blur effect in the pipeline
+ /// 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, out EffectAnimation tintAnimation,
+ float blur, out EffectAnimation blurAnimation,
+ [NotNull] Uri noiseUri, BitmapCacheMode cache = BitmapCacheMode.Default)
+ {
+ return FromBackdropBrush()
+ .Tint(tint, mix, out tintAnimation)
+ .Blur(blur, out blurAnimation)
+ .Blend(FromTiles(noiseUri, cache: cache), BlendEffectMode.Overlay, EffectPlacement.Background);
+ }
+
+ #endregion
+
+ #region Blends
+
+ ///
+ /// Blends two pipelines using a instance with the specified mode
+ ///
+ /// 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)
+ {
+ (var foreground, var background) = sorting == EffectPlacement.Foreground ? (this, pipeline) : (pipeline, this);
+
+ async Task Factory() => new BlendEffect
+ {
+ Foreground = await foreground.SourceProducer(),
+ Background = await background.SourceProducer(),
+ Mode = mode
+ };
+
+ return new CompositionBrushBuilder(Factory, foreground, background);
+ }
+
+ ///
+ /// Blends two pipelines using an instance
+ ///
+ /// 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)
+ {
+ 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);
+
+ async Task Factory() => new CrossFadeEffect
+ {
+ CrossFade = factor,
+ Source1 = await foreground.SourceProducer(),
+ Source2 = await background.SourceProducer()
+ };
+
+ return new CompositionBrushBuilder(Factory, foreground, background);
+ }
+
+ ///
+ /// Blends two pipelines using an instance
+ ///
+ /// 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)
+ {
+ 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);
+
+ async Task Factory() => new CrossFadeEffect
+ {
+ CrossFade = factor,
+ Source1 = await foreground.SourceProducer(),
+ Source2 = await background.SourceProducer(),
+ Name = "Fade"
+ };
+
+ animation = (brush, value, ms) =>
+ {
+ if (value < 0 || value > 1) throw new ArgumentOutOfRangeException(nameof(value), "The factor must be in the [0,1] range");
+ return brush.StartAnimationAsync("Fade.CrossFade", value, TimeSpan.FromMilliseconds(ms));
+ };
+
+ return new CompositionBrushBuilder(Factory, foreground, background, new[] { "Fade.CrossFade" });
+ }
+
+ ///
+ /// Blends two pipelines using the provided to do so
+ ///
+ /// The blend function to use
+ /// The background pipeline to blend with the current instance
+ /// 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(
+ [NotNull] Func factory,
+ [NotNull] CompositionBrushBuilder 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));
+ }
+
+ ///
+ /// Blends two pipelines using the provided asynchronous to do so
+ ///
+ /// The asynchronous blend function to use
+ /// The background pipeline to blend with the current instance
+ /// 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(
+ [NotNull] Func> factory,
+ [NotNull] CompositionBrushBuilder 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));
+ }
+
+ #endregion
+
+ #region Built-in effects
+
+ ///
+ /// Adds a new to the current pipeline
+ ///
+ /// The blur amount to apply
+ /// 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)
+ {
+ // Blur effect
+ async Task Factory() => new GaussianBlurEffect
+ {
+ BlurAmount = blur,
+ BorderMode = mode,
+ Optimization = optimization,
+ Source = await SourceProducer()
+ };
+
+ return new CompositionBrushBuilder(this, Factory);
+ }
+
+ ///
+ /// Adds a new to the current pipeline
+ ///
+ /// The initial blur amount
+ /// The optional blur animation for the effect
+ /// The parameter for the effect, defaults to
+ /// 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)
+ {
+ // Blur effect
+ async Task Factory() => new GaussianBlurEffect
+ {
+ BlurAmount = blur,
+ BorderMode = mode,
+ Optimization = optimization,
+ Source = await SourceProducer(),
+ Name = "Blur"
+ };
+
+ animation = (brush, value, ms) => brush.StartAnimationAsync("Blur.BlurAmount", value, TimeSpan.FromMilliseconds(ms));
+
+ return new CompositionBrushBuilder(this, Factory, new[] { "Blur.BlurAmount" });
+ }
+
+ ///
+ /// Adds a new to the current pipeline
+ ///
+ /// The saturation amount for the new effect
+ [Pure, NotNull]
+ public CompositionBrushBuilder 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
+ {
+ Saturation = saturation,
+ Source = await SourceProducer()
+ };
+
+ return new CompositionBrushBuilder(this, Factory);
+ }
+
+ ///
+ /// Adds a new to the current pipeline
+ ///
+ /// The initial saturation amount for the new effect
+ /// 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)
+ {
+ if (saturation < 0 || saturation > 1) throw new ArgumentOutOfRangeException(nameof(saturation), "The saturation must be in the [0,1] range");
+ async Task Factory() => new SaturationEffect
+ {
+ Saturation = saturation,
+ Source = await SourceProducer(),
+ Name = "Saturation"
+ };
+
+ animation = (brush, value, ms) =>
+ {
+ if (value < 0 || value > 1) throw new ArgumentOutOfRangeException(nameof(value), "The saturation must be in the [0,1] range");
+ return brush.StartAnimationAsync("Saturation.Saturation", value, TimeSpan.FromMilliseconds(ms));
+ };
+
+ return new CompositionBrushBuilder(this, Factory, new[] { "Saturation.Saturation" });
+ }
+
+ ///
+ /// Adds a new to the current pipeline
+ ///
+ /// The opacity value to apply to the pipeline
+ [Pure, NotNull]
+ public CompositionBrushBuilder 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
+ {
+ Opacity = opacity,
+ Source = await SourceProducer()
+ };
+
+ return new CompositionBrushBuilder(this, Factory);
+ }
+
+ ///
+ /// Adds a new to the current pipeline
+ ///
+ /// The opacity value to apply to the pipeline
+ /// 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)
+ {
+ if (opacity < 0 || opacity > 1) throw new ArgumentOutOfRangeException(nameof(opacity), "The opacity must be in the [0,1] range");
+ async Task Factory() => new OpacityEffect
+ {
+ Opacity = opacity,
+ Source = await SourceProducer(),
+ Name = "Opacity"
+ };
+
+ animation = (brush, value, ms) =>
+ {
+ if (value < 0 || value > 1) throw new ArgumentOutOfRangeException(nameof(value), "The opacity must be in the [0,1] range");
+ return brush.StartAnimationAsync("Opacity.Opacity", value, TimeSpan.FromMilliseconds(ms));
+ };
+
+ return new CompositionBrushBuilder(this, Factory, new[] { "Opacity.Opacity" });
+ }
+
+ ///
+ /// Applies a tint color on the current pipeline
+ ///
+ /// 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);
+
+ ///
+ /// Applies a tint color on the current pipeline
+ ///
+ /// The tint color to use
+ /// The initial amount of tint to apply over the current effect
+ /// 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);
+
+ #endregion
+
+ #region Custom effects
+
+ ///
+ /// Applies a custom effect to the current pipeline
+ ///
+ /// A that takes the current instance and produces a new effect to display
+ /// 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)
+ {
+ async Task Factory() => factory(await SourceProducer());
+
+ return new CompositionBrushBuilder(this, Factory, animations?.ToArray(), initializers?.ToDictionary(item => item.Name, item => item.Initializer));
+ }
+
+ ///
+ /// Applies a custom effect to the current pipeline
+ ///
+ /// An asynchronous that takes the current instance and produces a new effect to display
+ /// 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)
+ {
+ async Task Factory() => await factory(await SourceProducer());
+
+ return new CompositionBrushBuilder(this, Factory, animations?.ToArray(), initializers?.ToDictionary(item => item.Name, item => item.Initializer));
+ }
+
+ #endregion
+
+ #region Results
+
+ ///
+ /// Builds a instance from the current effects pipeline
+ ///
+ [Pure, NotNull, ItemNotNull]
+ public async Task BuildAsync()
+ {
+ // Validate the pipeline and build the effects factory
+ if (!(await SourceProducer() is IGraphicsEffect effect)) throw new InvalidOperationException("The pipeline doesn't contain a valid effects sequence");
+ CompositionEffectFactory factory = AnimationProperties.Count > 0
+ ? Window.Current.Compositor.CreateEffectFactory(effect, AnimationProperties)
+ : Window.Current.Compositor.CreateEffectFactory(effect);
+
+ // Create the effect factory and apply the final effect
+ CompositionEffectBrush effectBrush = factory.CreateBrush();
+ foreach (KeyValuePair>> pair in LazyParameters)
+ effectBrush.SetSourceParameter(pair.Key, await pair.Value());
+
+ // Cleanup
+ BackdropBrushCache.Cleanup();
+ HostBackdropBrushCache.Cleanup();
+ return effectBrush;
+ }
+
+ ///
+ /// Builds the current pipeline and creates a that is applied to the input
+ ///
+ /// The target to apply the brush to
+ /// An optional to use to bind the size of the created brush
+ [ItemNotNull]
+ public async Task AttachAsync([NotNull] UIElement target, [CanBeNull] UIElement reference = null)
+ {
+ SpriteVisual visual = Window.Current.Compositor.CreateSpriteVisual();
+ visual.Brush = await BuildAsync();
+ ElementCompositionPreview.SetElementChildVisual(target, visual);
+ if (reference != null) visual.BindSize(reference);
+ return visual;
+ }
+
+ ///
+ /// Creates a new from the current effects pipeline
+ ///
+ [Pure, NotNull]
+ public XamlCompositionBrush AsBrush() => new XamlCompositionBrush(this);
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/UICompositionAnimations/Behaviours/CompositionSourceInitializer.cs b/UICompositionAnimations/Behaviours/CompositionSourceInitializer.cs
new file mode 100644
index 0000000..7ab97a6
--- /dev/null
+++ b/UICompositionAnimations/Behaviours/CompositionSourceInitializer.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Threading.Tasks;
+using Windows.UI.Composition;
+using JetBrains.Annotations;
+
+namespace UICompositionAnimations.Behaviours
+{
+ ///
+ /// A simple container used to store info on a custom composition effect to create
+ ///
+ [PublicAPI]
+ public sealed class CompositionSourceInitializer
+ {
+ ///
+ /// Gets the stored effect initializer
+ ///
+ [NotNull]
+ internal Func> Initializer { get; }
+
+ ///
+ /// Gets the name of the target
+ ///
+ [NotNull]
+ internal string Name { get; }
+
+ private CompositionSourceInitializer([NotNull] string name, [NotNull] Func> initializer)
+ {
+ Name = name;
+ Initializer = initializer;
+ }
+
+ ///
+ /// Creates a new instance with the info on a given to initialize
+ ///
+ /// The 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()));
+
+ ///
+ /// Creates a new instance with the info on a given to initialize
+ ///
+ /// The 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);
+ }
+}
diff --git a/UICompositionAnimations/Behaviours/Effects/AttachedAnimatableCompositionEffect.cs b/UICompositionAnimations/Behaviours/Effects/AttachedAnimatableCompositionEffect.cs
deleted file mode 100644
index 1c9bac6..0000000
--- a/UICompositionAnimations/Behaviours/Effects/AttachedAnimatableCompositionEffect.cs
+++ /dev/null
@@ -1,42 +0,0 @@
-using System;
-using System.Threading.Tasks;
-using Windows.UI.Composition;
-using Windows.UI.Xaml;
-using JetBrains.Annotations;
-using UICompositionAnimations.Behaviours.Effects.Base;
-using UICompositionAnimations.Behaviours.Misc;
-
-namespace UICompositionAnimations.Behaviours.Effects
-{
- ///
- /// An attached composition effect that supports a single in/out animation
- ///
- /// The type of the visual element the effect will be applied to
- public sealed class AttachedAnimatableCompositionEffect : AttachedAnimatableCompositionEffectBase where T : FrameworkElement
- {
- // The animation parameters
- private readonly CompositionAnimationParameters Parameters;
-
- // Internal constructor
- internal AttachedAnimatableCompositionEffect(
- [NotNull] T element, [NotNull] SpriteVisual sprite,
- [NotNull] CompositionAnimationParameters parameters, bool disposeOnUnload) : base(element, sprite, disposeOnUnload)
- {
- Parameters = parameters;
- }
-
- ///
- protected override void DisposeCore()
- {
- EffectBrush.StopAnimation(Parameters.Property);
- base.DisposeCore();
- }
-
- ///
- public override Task AnimateAsync(FixedAnimationType animationType, TimeSpan duration)
- {
- return EffectBrush.StartAnimationAsync(Parameters.Property,
- animationType == FixedAnimationType.In ? Parameters.On : Parameters.Off, duration);
- }
- }
-}
diff --git a/UICompositionAnimations/Behaviours/Effects/AttachedCompositeAnimatableCompositionEffect.cs b/UICompositionAnimations/Behaviours/Effects/AttachedCompositeAnimatableCompositionEffect.cs
deleted file mode 100644
index 052f7f5..0000000
--- a/UICompositionAnimations/Behaviours/Effects/AttachedCompositeAnimatableCompositionEffect.cs
+++ /dev/null
@@ -1,48 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading.Tasks;
-using Windows.UI.Composition;
-using Windows.UI.Xaml;
-using JetBrains.Annotations;
-using UICompositionAnimations.Behaviours.Effects.Base;
-using UICompositionAnimations.Behaviours.Misc;
-
-namespace UICompositionAnimations.Behaviours.Effects
-{
- ///
- /// An attached composition effect that supports multiple in/out animations
- ///
- /// The type of the visual element the effects will be applied to
- public sealed class AttachedCompositeAnimatableCompositionEffect : AttachedAnimatableCompositionEffectBase where T : FrameworkElement
- {
- // Private animations parameters
- [NotNull]
- private readonly IDictionary PropertiesAnimationValues;
-
- // Internal constructor
- internal AttachedCompositeAnimatableCompositionEffect(
- [NotNull] T element, [NotNull] SpriteVisual sprite,
- [NotNull] IDictionary propertyValues, bool disposeOnUnload) : base(element, sprite, disposeOnUnload)
- {
- PropertiesAnimationValues = propertyValues;
- }
-
- ///
- protected override void DisposeCore()
- {
- foreach (String key in PropertiesAnimationValues.Keys) EffectBrush.StopAnimation(key);
- base.DisposeCore();
- }
-
- ///
- public override Task AnimateAsync(FixedAnimationType animationType, TimeSpan duration)
- {
- // Apply all the animations in parallel and wait for their completion
- return Task.WhenAll(
- from pair in PropertiesAnimationValues
- let target = animationType == FixedAnimationType.In ? pair.Value.On : pair.Value.Off
- select EffectBrush.StartAnimationAsync(pair.Key, target, duration));
- }
- }
-}
\ No newline at end of file
diff --git a/UICompositionAnimations/Behaviours/Effects/AttachedStaticCompositionEffect.cs b/UICompositionAnimations/Behaviours/Effects/AttachedStaticCompositionEffect.cs
deleted file mode 100644
index 49a2f48..0000000
--- a/UICompositionAnimations/Behaviours/Effects/AttachedStaticCompositionEffect.cs
+++ /dev/null
@@ -1,74 +0,0 @@
-using System;
-using Windows.UI.Composition;
-using Windows.UI.Xaml;
-using Windows.UI.Xaml.Hosting;
-using JetBrains.Annotations;
-using UICompositionAnimations.Helpers;
-
-namespace UICompositionAnimations.Behaviours.Effects
-{
- ///
- /// An base class for an attached composition effect
- ///
- /// Tye type of the target element
- public class AttachedStaticCompositionEffect : IDisposable where T : FrameworkElement
- {
- ///
- /// Gets the element used to apply the blur effect
- ///
- [NotNull]
- public T Element { get; }
-
- ///
- /// Gets the actual blur sprite shown above the visual element
- ///
- [NotNull]
- public SpriteVisual Sprite { get; }
-
- ///
- /// Gets the composition effect brush applied to the visual element
- ///
- [NotNull]
- public CompositionEffectBrush EffectBrush => Sprite.Brush.To();
-
- // Internal constructor
- internal AttachedStaticCompositionEffect([NotNull] T element, [NotNull] SpriteVisual sprite, bool disposeOnUnload)
- {
- // Store the parameters
- Element = element;
- Sprite = sprite;
- if (disposeOnUnload) element.Unloaded += (s, e) => Dispose();
- }
-
- ///
- /// Disposes the resources in the current instance
- ///
- protected virtual void DisposeCore()
- {
- Sprite.StopAnimation("Size");
- ElementCompositionPreview.SetElementChildVisual(Element, null);
- EffectBrush.Dispose();
- Sprite.Dispose();
- }
-
- // Indicates whether or not the wrapped effect has already been disposed
- private bool _Disposed;
-
- ///
- /// Stops the size animation, removes the effect from the visual tree and disposes it
- ///
- public void Dispose()
- {
- if (_Disposed) return;
- _Disposed = true;
- try
- {
- DisposeCore();
- }
- catch
- {
- // Never trust the framework
- }
- }
- }
-}
diff --git a/UICompositionAnimations/Behaviours/Effects/AttachedToggleAcrylicEffect.cs b/UICompositionAnimations/Behaviours/Effects/AttachedToggleAcrylicEffect.cs
deleted file mode 100644
index b5b8523..0000000
--- a/UICompositionAnimations/Behaviours/Effects/AttachedToggleAcrylicEffect.cs
+++ /dev/null
@@ -1,61 +0,0 @@
-using System;
-using Windows.UI.Composition;
-using Windows.UI.Xaml;
-using JetBrains.Annotations;
-
-namespace UICompositionAnimations.Behaviours.Effects
-{
- ///
- /// A custom acrylic brush effect that can quickly toggle between different sources (in-app blur or host backdrop effect)
- ///
- /// The the host that will display the effect visual
- public sealed class AttachedToggleAcrylicEffect : AttachedStaticCompositionEffect where T : FrameworkElement
- {
- ///
- /// Gets the action that edits the effects pipeline to apply the requested change
- ///
- [NotNull]
- private readonly Action Toggle;
-
- // An optional fade in animation for the blurred in-app acrylic effect
- [CanBeNull]
- private readonly Action InitialInAppEffectFadeIn;
-
- // Internal constructor
- internal AttachedToggleAcrylicEffect([NotNull] T element, AcrylicEffectMode mode, [CanBeNull] Action fadeIn,
- [NotNull] Action toggle, [NotNull] SpriteVisual sprite, bool disposeOnUnload)
- : base(element, sprite, disposeOnUnload)
- {
- _AcrylicMode = mode;
- _FadeInPending = mode == AcrylicEffectMode.HostBackdrop;
- InitialInAppEffectFadeIn = fadeIn;
- Toggle = toggle;
- }
-
- // Indicates whether or not the fade in animation should be run
- private bool _FadeInPending;
-
- private AcrylicEffectMode _AcrylicMode;
-
- ///
- /// Gets or sets the current effect mode in use
- ///
- public AcrylicEffectMode AcrylicMode
- {
- get => _AcrylicMode;
- set
- {
- if (_AcrylicMode != value)
- {
- Toggle(value);
- if (_FadeInPending && InitialInAppEffectFadeIn != null)
- {
- _FadeInPending = false;
- InitialInAppEffectFadeIn();
- }
- _AcrylicMode = value;
- }
- }
- }
- }
-}
diff --git a/UICompositionAnimations/Behaviours/Effects/Base/AttachedAnimatableCompositionEffectBase.cs b/UICompositionAnimations/Behaviours/Effects/Base/AttachedAnimatableCompositionEffectBase.cs
deleted file mode 100644
index 6ebafe4..0000000
--- a/UICompositionAnimations/Behaviours/Effects/Base/AttachedAnimatableCompositionEffectBase.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-using System;
-using System.Threading.Tasks;
-using Windows.UI.Composition;
-using Windows.UI.Xaml;
-using JetBrains.Annotations;
-using UICompositionAnimations.Behaviours.Misc;
-
-namespace UICompositionAnimations.Behaviours.Effects.Base
-{
- ///
- /// An base class for an attached composition effect that supports a ready to use animation with fixed states
- ///
- /// Tye type of the target element the animation will be applied to
- public abstract class AttachedAnimatableCompositionEffectBase : AttachedStaticCompositionEffect where T : FrameworkElement
- {
- // Protected constructor for the implementations
- internal AttachedAnimatableCompositionEffectBase([NotNull] T element, [NotNull] SpriteVisual sprite, bool disposeOnUnload)
- : base(element, sprite, disposeOnUnload) { }
-
- ///
- /// Executes the animation to the desired destination status and returns a task that completes when the animation ends
- ///
- /// The target animation status
- /// The animation duration
- public abstract Task AnimateAsync(FixedAnimationType animationType, TimeSpan duration);
- }
-}
\ No newline at end of file
diff --git a/UICompositionAnimations/Behaviours/Misc/CompositionAnimationParameters.cs b/UICompositionAnimations/Behaviours/Misc/CompositionAnimationParameters.cs
deleted file mode 100644
index dc1f7bc..0000000
--- a/UICompositionAnimations/Behaviours/Misc/CompositionAnimationParameters.cs
+++ /dev/null
@@ -1,55 +0,0 @@
-using System;
-using JetBrains.Annotations;
-
-namespace UICompositionAnimations.Behaviours.Misc
-{
- ///
- /// A class that stores the parameters for two different states for a pre-defined composition animation
- ///
- internal class CompositionAnimationValueParameters
- {
- ///
- /// Gets the target value when the effect is enabled
- ///
- public float On { get; }
-
- ///
- /// Gets the target value when the effect is disabled
- ///
- public float Off { get; }
-
- ///
- /// Creates a new instance for the given states
- ///
- /// The on parameter
- /// The off parameter
- public CompositionAnimationValueParameters(float on, float off)
- {
- On = on;
- Off = off;
- }
- }
-
- ///
- /// A class that stores the parameters for two different states for a pre-defined composition animation, along with the target property to animate
- ///
- internal sealed class CompositionAnimationParameters : CompositionAnimationValueParameters
- {
- ///
- /// Gets the property to animate
- ///
- [NotNull]
- public String Property { get; }
-
- ///
- /// Creates a new instance for the target property and states
- ///
- /// The target property to animate
- /// The on parameter
- /// The off parameter
- public CompositionAnimationParameters([NotNull] String property, float on, float off) : base(on, off)
- {
- Property = property;
- }
- }
-}
diff --git a/UICompositionAnimations/Behaviours/Misc/FixedAnimationType.cs b/UICompositionAnimations/Behaviours/Misc/FixedAnimationType.cs
deleted file mode 100644
index 5d585eb..0000000
--- a/UICompositionAnimations/Behaviours/Misc/FixedAnimationType.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-namespace UICompositionAnimations.Behaviours.Misc
-{
- ///
- /// Indicates the direction of a ready to use composition animation
- ///
- public enum FixedAnimationType
- {
- ///
- /// Applies the effect
- ///
- In,
-
- ///
- /// Reverts the applied effect
- ///
- Out
- }
-}
\ No newline at end of file
diff --git a/UICompositionAnimations/Brushes/Base/XamlCompositionEffectBrushBase.cs b/UICompositionAnimations/Brushes/Base/XamlCompositionEffectBrushBase.cs
new file mode 100644
index 0000000..685bd9a
--- /dev/null
+++ b/UICompositionAnimations/Brushes/Base/XamlCompositionEffectBrushBase.cs
@@ -0,0 +1,48 @@
+using System.Threading.Tasks;
+using Windows.UI.Xaml.Media;
+using JetBrains.Annotations;
+using UICompositionAnimations.Behaviours;
+
+namespace UICompositionAnimations.Brushes.Base
+{
+ ///
+ /// A custom that's ready to be used with a custom pipeline.
+ ///
+ [PublicAPI]
+ public abstract class XamlCompositionEffectBrushBase : XamlCompositionBrushBase
+ {
+ // Initialization mutex
+ [NotNull]
+ private readonly AsyncMutex ConnectedMutex = new AsyncMutex();
+
+ ///
+ /// 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 override async void OnConnected()
+ {
+ using (await ConnectedMutex.LockAsync())
+ if (CompositionBrush == null)
+ CompositionBrush = await OnBrushRequested().BuildAsync();
+ base.OnConnected();
+ }
+
+ ///
+ protected override async void OnDisconnected()
+ {
+ using (await ConnectedMutex.LockAsync())
+ {
+ if (CompositionBrush != null)
+ {
+ CompositionBrush.Dispose();
+ CompositionBrush = null;
+ }
+ }
+ base.OnDisconnected();
+ }
+ }
+}
diff --git a/UICompositionAnimations/Brushes/Cache/ThreadSafeCompositionCache.cs b/UICompositionAnimations/Brushes/Cache/ThreadSafeCompositionCache.cs
new file mode 100644
index 0000000..9c4f371
--- /dev/null
+++ b/UICompositionAnimations/Brushes/Cache/ThreadSafeCompositionCache.cs
@@ -0,0 +1,58 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Windows.UI.Composition;
+using Windows.UI.Core;
+using JetBrains.Annotations;
+
+namespace UICompositionAnimations.Brushes.Cache
+{
+ ///
+ /// A used to cache reusable instances
+ ///
+ /// The type of instances to cache
+ internal sealed class ThreadSafeCompositionCache where T : CompositionObject
+ {
+ ///
+ /// The cache of weak references, to avoid memory leaks
+ ///
+ [NotNull, ItemNotNull]
+ private readonly List> Cache = new List>();
+
+ ///
+ /// The instance used to synchronize concurrent operations on the cache
+ ///
+ [NotNull]
+ private readonly AsyncMutex Mutex = new AsyncMutex();
+
+ ///
+ /// Tries to retrieve a valid instance from the cache, and uses the provided factory if an existing item is not found
+ ///
+ /// A used when the requested value is not present in the cache
+ [MustUseReturnValue, NotNull, ItemNotNull]
+ public async Task TryGetInstanceAsync([NotNull] Func factory)
+ {
+ using (await Mutex.LockAsync())
+ {
+ // Try to retrieve an valid instance from the cache
+ foreach (WeakReference value in Cache)
+ if (value.TryGetTarget(out T instance) && instance.TryGetDispatcher(out CoreDispatcher dispatcher) && dispatcher.HasThreadAccess)
+ return instance;
+
+ // Create a new instance when needed
+ T fallback = factory();
+ Cache.Add(new WeakReference(fallback));
+ return fallback;
+ }
+ }
+
+ ///
+ /// Performs a cleanup of the cache by removing invalid references
+ ///
+ public async void Cleanup()
+ {
+ using (await Mutex.LockAsync())
+ Cache.RemoveAll(reference => !reference.TryGetTarget(out T target) || !target.TryGetDispatcher(out _));
+ }
+ }
+}
diff --git a/UICompositionAnimations/Brushes/Cache/ThreadSafeCompositionMapCache.cs b/UICompositionAnimations/Brushes/Cache/ThreadSafeCompositionMapCache.cs
new file mode 100644
index 0000000..ec6e7c5
--- /dev/null
+++ b/UICompositionAnimations/Brushes/Cache/ThreadSafeCompositionMapCache.cs
@@ -0,0 +1,92 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Windows.UI.Composition;
+using Windows.UI.Core;
+using JetBrains.Annotations;
+
+namespace UICompositionAnimations.Brushes.Cache
+{
+ ///
+ /// A used to cache reusable instances with an associated key
+ ///
+ /// The type of key to classify the items in the cache
+ /// The type of items stored in the cache
+ internal sealed class ThreadSafeCompositionMapCache where TValue : CompositionObject
+ {
+ ///
+ /// The cache of weak references, to avoid memory leaks
+ ///
+ [NotNull]
+ private readonly Dictionary>> Cache = new Dictionary>>();
+
+ ///
+ /// Tries to retrieve a valid instance from the cache, and uses the provided factory if an existing item is not found
+ ///
+ /// The key to look for
+ /// The resulting value, if existing
+ [MustUseReturnValue]
+ public bool TryGetInstance(TKey key, out TValue result)
+ {
+ // Try to retrieve an valid instance from the cache
+ if (Cache.TryGetValue(key, out List> values))
+ foreach (WeakReference value in values)
+ if (value.TryGetTarget(out TValue instance) && instance.TryGetDispatcher(out CoreDispatcher dispatcher) && dispatcher.HasThreadAccess)
+ {
+ result = instance;
+ return true;
+ }
+
+ // Not found
+ result = null;
+ return false;
+ }
+
+ ///
+ /// Adds a new value with the specified key to the cache
+ ///
+ /// The key of the item to add
+ /// The value to add
+ public void Add(TKey key, TValue value)
+ {
+ if (Cache.TryGetValue(key, out List> list)) list.Add(new WeakReference(value));
+ else Cache.Add(key, new List> { new WeakReference(value) });
+ }
+
+ ///
+ /// Adds a new value and removes previous values with the same key, if any
+ ///
+ /// The key of the item to add
+ /// The value to add
+ public void Overwrite(TKey key, TValue value)
+ {
+ Cache.Remove(key);
+ Cache.Add(key, new List> { new WeakReference(value) });
+ }
+
+ ///
+ /// Performs a cleanup of the cache by removing invalid references
+ ///
+ public void Cleanup()
+ {
+ foreach (List> list in Cache.Values)
+ list.RemoveAll(reference => !reference.TryGetTarget(out TValue value) || !value.TryGetDispatcher(out _));
+ foreach (TKey key in Cache.Keys.ToArray())
+ if (Cache[key].Count == 0)
+ Cache.Remove(key);
+ }
+
+ ///
+ /// Clears the cache by removing all the stored items
+ ///
+ public IReadOnlyList Clear()
+ {
+ List values = new List();
+ foreach (WeakReference reference in Cache.Values.SelectMany(list => list))
+ if (reference.TryGetTarget(out TValue value))
+ values.Add(value);
+ Cache.Clear();
+ return values;
+ }
+ }
+}
\ No newline at end of file
diff --git a/UICompositionAnimations/Brushes/CustomAcrylicBrush.cs b/UICompositionAnimations/Brushes/CustomAcrylicBrush.cs
index 9dd9cbc..4589b85 100644
--- a/UICompositionAnimations/Brushes/CustomAcrylicBrush.cs
+++ b/UICompositionAnimations/Brushes/CustomAcrylicBrush.cs
@@ -10,7 +10,6 @@
using Windows.UI.Xaml.Media;
using JetBrains.Annotations;
using UICompositionAnimations.Behaviours;
-using UICompositionAnimations.Behaviours.Effects;
using UICompositionAnimations.Brushes.Cache;
using UICompositionAnimations.Enums;
using UICompositionAnimations.Helpers;
@@ -21,24 +20,25 @@ namespace UICompositionAnimations.Brushes
///
/// A custom XAML brush that includes an acrylic effect that blurs the in-app content
///
+ [PublicAPI]
public sealed class CustomAcrylicBrush : XamlCompositionBrushBase
{
#region Constants
// The name of the animatable blur amount property
- private const String BlurAmountParameterName = "Blur.BlurAmount";
+ 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";
+ 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";
+ private const string TintColor2ParameterName = "Tint.Source2Amount";
// The name of the animatable color property of the color effect
- private const String ColorSourceParameterName = "ColorSource.Color";
+ private const string ColorSourceParameterName = "ColorSource.Color";
// The name of the animatable color property of the fallback color effect
- private const String FallbackColorParameterName = "FallbackColor.Color";
+ private const string FallbackColorParameterName = "FallbackColor.Color";
#endregion
@@ -49,8 +49,8 @@ public sealed class CustomAcrylicBrush : XamlCompositionBrushBase
///
public AcrylicEffectMode Mode
{
- get { return GetValue(ModeProperty).To(); }
- set { SetValue(ModeProperty, value); }
+ get => GetValue(ModeProperty).To();
+ set => SetValue(ModeProperty, value);
}
///
@@ -80,8 +80,8 @@ private static async void OnModePropertyChanged(DependencyObject d, DependencyPr
/// This property is ignored when the active mode is
public double BlurAmount
{
- get { return GetValue(BlurAmountProperty).To(); }
- set { SetValue(BlurAmountProperty, value); }
+ get => GetValue(BlurAmountProperty).To();
+ set => SetValue(BlurAmountProperty, value);
}
///
@@ -112,8 +112,8 @@ private static async void OnBlurAmountPropertyChanged(DependencyObject d, Depend
/// This property is ignored when the active mode is
public int BlurAnimationDuration
{
- get { return GetValue(BlurAnimationDurationProperty).To(); }
- set { SetValue(BlurAnimationDurationProperty, value); }
+ get => GetValue(BlurAnimationDurationProperty).To();
+ set => SetValue(BlurAnimationDurationProperty, value);
}
///
@@ -127,8 +127,8 @@ public int BlurAnimationDuration
///
public Color Tint
{
- get { return GetValue(TintProperty).To(); }
- set { SetValue(TintProperty, value); }
+ get => GetValue(TintProperty).To();
+ set => SetValue(TintProperty, value);
}
///
@@ -153,8 +153,8 @@ private static async void OnTintPropertyChanged(DependencyObject d, DependencyPr
///
public double TintMix
{
- get { return GetValue(TintMixProperty).To(); }
- set { SetValue(TintMixProperty, value); }
+ get => GetValue(TintMixProperty).To();
+ set => SetValue(TintMixProperty, value);
}
///
@@ -167,7 +167,7 @@ private static async void OnTintMixPropertyChanged(DependencyObject d, Dependenc
{
double value = e.NewValue.To();
float fvalue = (float)value;
- if (value < 0 || value >= 1) throw new ArgumentOutOfRangeException("The tint mix must be in the [0..1) range");
+ 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)
@@ -183,8 +183,8 @@ private static async void OnTintMixPropertyChanged(DependencyObject d, Dependenc
///
public Color UnsupportedEffectFallbackColor
{
- get { return GetValue(UnsupportedEffectFallbackColorProperty).To(); }
- set { SetValue(UnsupportedEffectFallbackColorProperty, value); }
+ get => GetValue(UnsupportedEffectFallbackColorProperty).To();
+ set => SetValue(UnsupportedEffectFallbackColorProperty, value);
}
///
@@ -217,13 +217,6 @@ private static async void OnUnsupportedEffectFallbackColorPropertyChanged(Depend
@this._EffectBrush?.Properties.InsertColor(FallbackColorParameterName, value);
}
}
- else if (@this._State == AcrylicBrushEffectState.Default && // The effect is currently disabled
- @this.Mode == AcrylicEffectMode.HostBackdrop && // The current settings are not valid
- ApiInformationHelper.IsMobileDevice &&
- !value.Equals(Colors.Transparent)) // The new value allows the fallback mode to be enabled
- {
- await @this.SetupEffectAsync();
- }
@this.ConnectedSemaphore.Release();
}
@@ -233,11 +226,6 @@ private static async void OnUnsupportedEffectFallbackColorPropertyChanged(Depend
/// This property must be initialized before using the brush
public Uri NoiseTextureUri { get; set; }
- ///
- /// Gets or sets the caching setting for the acrylic brush
- ///
- public BitmapCacheMode CacheMode { get; set; } = BitmapCacheMode.EnableCaching;
-
///
/// 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
@@ -302,7 +290,7 @@ protected override async void OnDisconnected()
private static CompositionBackdropBrush _BackdropInstance;
// The name to use for the in-app backdrop reference parameter
- private const String BackdropReferenceParameterName = "BackdropBrush";
+ private const string BackdropReferenceParameterName = "BackdropBrush";
// The synchronization semaphore for the host backdrop brush
private static readonly SemaphoreSlim HostBackdropSemaphore = new SemaphoreSlim(1);
@@ -311,12 +299,11 @@ protected override async void OnDisconnected()
private static HostBackdropInstanceWrapper _HostBackdropCache;
// The name to use for the host backdrop reference parameter
- private const String HostBackdropReferenceParameterName = "HostBackdropBrush";
+ private const string HostBackdropReferenceParameterName = "HostBackdropBrush";
///
/// Clears the internal cache of instances
///
- [PublicAPI]
public static async Task ClearCacheAsync(AcrylicEffectMode targets)
{
// In-app backdrop brush
@@ -344,30 +331,11 @@ public static async Task ClearCacheAsync(AcrylicEffectMode targets)
private async Task SetupEffectAsync()
{
// Designer check
- if (DesignMode.DesignModeEnabled) return;
-
- // Platform check
- if (ApiInformationHelper.IsMobileDevice && Mode == AcrylicEffectMode.HostBackdrop)
- {
- // Create the fallback brush effect when needed
- if (!UnsupportedEffectFallbackColor.Equals(Colors.Transparent))
- {
- ColorSourceEffect fallback = new ColorSourceEffect
- {
- Name = "FallbackColor",
- Color = UnsupportedEffectFallbackColor
- };
- CompositionEffectFactory fallbackFactory = Window.Current.Compositor.CreateEffectFactory(fallback);
- _EffectBrush = fallbackFactory.CreateBrush();
- CompositionBrush = _EffectBrush;
- _State = AcrylicBrushEffectState.FallbackMode;
- }
- return;
- }
+ if (DesignMode.DesignMode2Enabled) return;
// Dictionary to track the reference and animatable parameters
- IDictionary sourceParameters = new Dictionary();
- List animatableParameters = new List
+ IDictionary sourceParameters = new Dictionary();
+ List animatableParameters = new List
{
TintColor1ParameterName,
TintColor2ParameterName,
@@ -444,14 +412,13 @@ private async Task SetupEffectAsync()
// Get the noise brush using Win2D
IGraphicsEffect source = await AcrylicEffectHelper.ConcatenateEffectWithTintAndBorderAsync(Window.Current.Compositor,
- baseEffect, sourceParameters, Tint, (float)TintMix, null, NoiseTextureUri, CacheMode);
+ 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";
- ColorSourceEffect color = tint.Source2 as ColorSourceEffect;
- if (color == null) throw new InvalidOperationException("Error while retrieving the color effect");
+ 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
@@ -459,7 +426,7 @@ private async Task SetupEffectAsync()
// Create the effect factory and apply the final effect
_EffectBrush = factory.CreateBrush();
- foreach (KeyValuePair pair in sourceParameters)
+ foreach (KeyValuePair pair in sourceParameters)
{
_EffectBrush.SetSourceParameter(pair.Key, pair.Value);
}
diff --git a/UICompositionAnimations/Brushes/LightingBrush.cs b/UICompositionAnimations/Brushes/LightingBrush.cs
index 3a7d273..0ce927b 100644
--- a/UICompositionAnimations/Brushes/LightingBrush.cs
+++ b/UICompositionAnimations/Brushes/LightingBrush.cs
@@ -17,8 +17,8 @@ public sealed class LightingBrush : XamlCompositionBrushBase
///
public double DiffuseAmount
{
- get { return (double)GetValue(DiffuseAmountProperty); }
- set { SetValue(DiffuseAmountProperty, value); }
+ get => (double)GetValue(DiffuseAmountProperty);
+ set => SetValue(DiffuseAmountProperty, value);
}
///
@@ -38,8 +38,8 @@ private static void OnDiffuseAmountChanged(DependencyObject d, DependencyPropert
///
public double SpecularShine
{
- get { return (double)GetValue(SpecularShineProperty); }
- set { SetValue(SpecularShineProperty, value); }
+ get => (double)GetValue(SpecularShineProperty);
+ set => SetValue(SpecularShineProperty, value);
}
///
@@ -58,8 +58,8 @@ private static void OnSpecularShineChanged(DependencyObject d, DependencyPropert
///
public double SpecularAmount
{
- get { return (double)GetValue(SpecularAmountProperty); }
- set { SetValue(SpecularAmountProperty, value); }
+ get => (double)GetValue(SpecularAmountProperty);
+ set => SetValue(SpecularAmountProperty, value);
}
///
diff --git a/UICompositionAnimations/Brushes/NoiseTextureBrush.cs b/UICompositionAnimations/Brushes/NoiseTextureBrush.cs
index 965edf1..254ac34 100644
--- a/UICompositionAnimations/Brushes/NoiseTextureBrush.cs
+++ b/UICompositionAnimations/Brushes/NoiseTextureBrush.cs
@@ -9,7 +9,6 @@
using Windows.UI.Xaml;
using Windows.UI.Xaml.Media;
using UICompositionAnimations.Behaviours;
-using UICompositionAnimations.Enums;
using UICompositionAnimations.Helpers;
using Windows.ApplicationModel;
@@ -21,7 +20,7 @@ namespace UICompositionAnimations.Brushes
public sealed class NoiseTextureBrush : XamlCompositionBrushBase
{
// The name of the animatable color property of the color effect
- private const String ColorSourceParameterName = "ColorSource.Color";
+ private const string ColorSourceParameterName = "ColorSource.Color";
#region Properties
@@ -30,8 +29,8 @@ public sealed class NoiseTextureBrush : XamlCompositionBrushBase
///
public Color Tint
{
- get { return GetValue(TintProperty).To(); }
- set { SetValue(TintProperty, value); }
+ get => GetValue(TintProperty).To();
+ set => SetValue(TintProperty, value);
}
///
@@ -57,11 +56,6 @@ private static async void OnTintPropertyChanged(DependencyObject d, DependencyPr
/// This property must be initialized before using the brush
public Uri TextureUri { get; set; }
- ///
- /// Gets or sets the caching setting for the acrylic brush
- ///
- public BitmapCacheMode CacheMode { get; set; } = BitmapCacheMode.EnableCaching;
-
#endregion
// Initialization semaphore (due to the Win2D image loading being asynchronous)
@@ -107,14 +101,14 @@ protected override async void OnDisconnected()
private async Task SetupEffectAsync()
{
// Designer check
- if (DesignMode.DesignModeEnabled) return;
+ if (DesignMode.DesignMode2Enabled) return;
// Dictionary to track the reference and animatable parameters
- IDictionary sourceParameters = new Dictionary();
- List animatableParameters = new List { ColorSourceParameterName };
+ 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, CacheMode);
+ 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;
@@ -126,7 +120,7 @@ private async Task SetupEffectAsync()
// Create the effect factory and apply the final effect
_EffectBrush = factory.CreateBrush();
- foreach (KeyValuePair pair in sourceParameters)
+ foreach (KeyValuePair pair in sourceParameters)
{
_EffectBrush.SetSourceParameter(pair.Key, pair.Value);
}
diff --git a/UICompositionAnimations/Brushes/XamlCompositionBrush.cs b/UICompositionAnimations/Brushes/XamlCompositionBrush.cs
new file mode 100644
index 0000000..37a3173
--- /dev/null
+++ b/UICompositionAnimations/Brushes/XamlCompositionBrush.cs
@@ -0,0 +1,64 @@
+using System;
+using System.Threading.Tasks;
+using JetBrains.Annotations;
+using UICompositionAnimations.Behaviours;
+using UICompositionAnimations.Brushes.Base;
+
+namespace UICompositionAnimations.Brushes
+{
+ ///
+ /// A that represents a custom effect animation that can be applied to a instance
+ ///
+ /// The animation target value
+ /// The animation duration, in milliseconds
+ public delegate Task XamlEffectAnimation(float value, int ms);
+
+ ///
+ /// 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
+ ///
+ [NotNull]
+ public CompositionBrushBuilder 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;
+
+ ///
+ /// Binds an to the composition brush in the current instance
+ ///
+ /// The input animation
+ /// The resulting animation
+ [Pure, NotNull]
+ public XamlCompositionBrush Bind([NotNull] EffectAnimation animation, out XamlEffectAnimation bound)
+ {
+ bound = (value, ms) => animation(CompositionBrush, value, ms);
+ return this;
+ }
+
+ ///
+ protected override CompositionBrushBuilder OnBrushRequested() => Pipeline;
+
+ ///
+ /// Clones the current instance by rebuilding the source . Use this method to reuse the same effects pipeline on a different
+ ///
+ [PublicAPI, Pure, NotNull]
+ public XamlCompositionBrush Clone()
+ {
+ if (Dispatcher.HasThreadAccess)
+ {
+ throw new InvalidOperationException("The current thread already has access to the brush dispatcher, so a clone operation is not necessary. " +
+ "You can just assign this brush to an arbitrary number of controls and it will still work correctly. " +
+ "This method is only meant to be used to create a new instance of this brush using the same pipeline, " +
+ "on threads that can't access the current instance, for example in secondary app windows.");
+ }
+ return new XamlCompositionBrush(Pipeline);
+ }
+ }
+}
\ No newline at end of file
diff --git a/UICompositionAnimations/Composition/CompositionManager.cs b/UICompositionAnimations/Composition/CompositionManager.cs
index 06c8fcc..4cba52e 100644
--- a/UICompositionAnimations/Composition/CompositionManager.cs
+++ b/UICompositionAnimations/Composition/CompositionManager.cs
@@ -9,6 +9,7 @@ namespace UICompositionAnimations.Composition
///
/// Create composition animations using this class
///
+ [PublicAPI]
public static class CompositionManager
{
#region Animations initialization
@@ -23,8 +24,7 @@ public static class CompositionManager
/// The animation duration
/// The optional initial delay for the animation
/// The optional easing function for the animation
- [PublicAPI]
- public static void BeginScalarAnimation([NotNull] UIElement element, [NotNull] String propertyPath,
+ 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);
@@ -40,8 +40,7 @@ public static class CompositionManager
/// The animation duration
/// The optional initial delay for the animation
/// The optional easing function for the animation
- [PublicAPI]
- public static void BeginVector2Animation([NotNull] UIElement element, [NotNull] String propertyPath,
+ 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);
@@ -57,7 +56,7 @@ public static class CompositionManager
/// 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,
+ 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);
@@ -73,8 +72,7 @@ public static class CompositionManager
/// The animation duration
/// The optional initial delay for the animation
/// The optional easing function for the animation
- [PublicAPI]
- public static void BeginScalarAnimation([NotNull] this CompositionObject compObj, [NotNull] String propertyPath,
+ 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));
@@ -90,8 +88,7 @@ public static class CompositionManager
/// The animation duration
/// The optional initial delay for the animation
/// The optional easing function for the animation
- [PublicAPI]
- public static void BeginVector2Animation([NotNull]this CompositionObject compObj, [NotNull] String propertyPath,
+ 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));
@@ -107,8 +104,7 @@ public static class CompositionManager
/// The animation duration
/// The optional initial delay for the animation
/// The optional easing function for the animation
- [PublicAPI]
- public static void BeginVector3Animation([NotNull] this CompositionObject compObj, [NotNull] String propertyPath,
+ 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));
@@ -127,7 +123,6 @@ public static class CompositionManager
/// The animation duration
/// The optional initial delay for the animation
/// The optional easing function for the animation
- [PublicAPI]
[Pure, NotNull]
public static ScalarKeyFrameAnimation CreateScalarKeyFrameAnimation([NotNull] this Compositor compositor,
float? from, float to, TimeSpan duration, TimeSpan? delay, [CanBeNull] CompositionEasingFunction ease = null)
@@ -152,10 +147,9 @@ public static class CompositionManager
/// The animation duration
/// The optional initial delay for the animation
/// The optional easing function for the animation
- [PublicAPI]
[Pure, NotNull]
public static ScalarKeyFrameAnimation CreateScalarKeyFrameAnimation([NotNull] this Compositor compositor,
- float? from, [NotNull] String to, TimeSpan duration, TimeSpan? delay, [CanBeNull] CompositionEasingFunction ease = null)
+ float? from, [NotNull] string to, TimeSpan duration, TimeSpan? delay, [CanBeNull] CompositionEasingFunction ease = null)
{
// Set duration and delay time
ScalarKeyFrameAnimation ani = compositor.CreateScalarKeyFrameAnimation();
@@ -177,7 +171,6 @@ public static class CompositionManager
/// The animation duration
/// The optional initial delay for the animation
/// The optional easing function for the animation
- [PublicAPI]
[Pure, NotNull]
public static Vector2KeyFrameAnimation CreateVector2KeyFrameAnimation([NotNull] this Compositor compositor,
Vector2? from, Vector2 to, TimeSpan duration, TimeSpan? delay, [CanBeNull] CompositionEasingFunction ease = null)
@@ -202,10 +195,9 @@ public static class CompositionManager
/// The animation duration
/// The optional initial delay for the animation
/// The optional easing function for the animation
- [PublicAPI]
[Pure, NotNull]
public static Vector2KeyFrameAnimation CreateVector2KeyFrameAnimation([NotNull] this Compositor compositor,
- Vector2? from, [NotNull] String to, TimeSpan duration, TimeSpan? delay, [CanBeNull] CompositionEasingFunction ease = null)
+ Vector2? from, [NotNull] string to, TimeSpan duration, TimeSpan? delay, [CanBeNull] CompositionEasingFunction ease = null)
{
// Set duration and delay time
Vector2KeyFrameAnimation ani = compositor.CreateVector2KeyFrameAnimation();
@@ -227,7 +219,6 @@ public static class CompositionManager
/// The animation duration
/// The optional initial delay for the animation
/// The optional easing function for the animation
- [PublicAPI]
[Pure, NotNull]
public static Vector3KeyFrameAnimation CreateVector3KeyFrameAnimation([NotNull] this Compositor compositor,
Vector3? from, Vector3 to, TimeSpan duration, TimeSpan? delay, [CanBeNull] CompositionEasingFunction ease = null)
@@ -252,10 +243,9 @@ public static class CompositionManager
/// The animation duration
/// The optional initial delay for the animation
/// The optional easing function for the animation
- [PublicAPI]
[Pure, NotNull]
public static Vector3KeyFrameAnimation CreateVector3KeyFrameAnimation([NotNull] this Compositor compositor,
- Vector3? from, [NotNull] String to, TimeSpan duration, TimeSpan? delay, [CanBeNull] CompositionEasingFunction ease = null)
+ Vector3? from, [NotNull] string to, TimeSpan duration, TimeSpan? delay, [CanBeNull] CompositionEasingFunction ease = null)
{
// Set duration and delay time
Vector3KeyFrameAnimation ani = compositor.CreateVector3KeyFrameAnimation();
@@ -277,7 +267,6 @@ public static class CompositionManager
/// The animation duration
/// The optional initial delay for the animation
/// The optional easing function for the animation
- [PublicAPI]
[Pure, NotNull]
public static CompositionAnimation CreateMatrix4x4KeyFrameAnimation([NotNull] this Compositor compositor,
Matrix4x4? from, Matrix4x4 to, TimeSpan duration, TimeSpan? delay, [CanBeNull] CompositionEasingFunction ease = null)
diff --git a/UICompositionAnimations/Composition/ExpressionAnimationWithScalarParameter.cs b/UICompositionAnimations/Composition/ExpressionAnimationWithScalarParameter.cs
index bd77d37..e0770d2 100644
--- a/UICompositionAnimations/Composition/ExpressionAnimationWithScalarParameter.cs
+++ b/UICompositionAnimations/Composition/ExpressionAnimationWithScalarParameter.cs
@@ -18,7 +18,7 @@ public sealed class ExpressionAnimationWithScalarParameter : DependencyObject
public ExpressionAnimation Animation { get; }
// Variable parameter name
- private readonly String ParameterName;
+ private readonly string ParameterName;
// Target property set with the variable parameter
private readonly CompositionPropertySet PropertySet;
@@ -30,7 +30,7 @@ public sealed class ExpressionAnimationWithScalarParameter : DependencyObject
/// The property set 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)
+ [NotNull] CompositionPropertySet propertySet, [NotNull] string parameterName)
{
Animation = animation;
PropertySet = propertySet;
diff --git a/UICompositionAnimations/CompositionExtensions.cs b/UICompositionAnimations/CompositionExtensions.cs
index 0ae33ed..c23ffe5 100644
--- a/UICompositionAnimations/CompositionExtensions.cs
+++ b/UICompositionAnimations/CompositionExtensions.cs
@@ -6,6 +6,7 @@
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;
@@ -19,6 +20,7 @@ namespace UICompositionAnimations
///
/// A static class that wraps the animation methods in the Windows.UI.Composition namespace
///
+ [PublicAPI]
public static class CompositionExtensions
{
#region Internal tools
@@ -1522,8 +1524,8 @@ void Handler(object s, RoutedEventArgs e)
int ms, int? msDelay, [NotNull] CompositionEasingFunction easingFunction)
{
// Setup
- InsetClip clip = visual.Clip as InsetClip ?? ((InsetClip)(visual.Clip = visual.Compositor.CreateInsetClip()));
- String property;
+ InsetClip clip = visual.Clip as InsetClip ?? (InsetClip)(visual.Clip = visual.Compositor.CreateInsetClip());
+ string property;
switch (side)
{
case MarginSide.Top:
@@ -1622,7 +1624,7 @@ void Handler(object s, RoutedEventArgs e)
TranslationAxis sourceXY, TranslationAxis? targetXY = null, bool invertSourceAxis = false)
{
CompositionPropertySet scrollSet = ElementCompositionPreview.GetScrollViewerManipulationPropertySet(scroller);
- String sign = invertSourceAxis ? "-" : String.Empty;
+ 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);
@@ -1646,7 +1648,7 @@ void Handler(object s, RoutedEventArgs e)
{
// Get the property set and setup the scroller offset sign
CompositionPropertySet scrollSet = ElementCompositionPreview.GetScrollViewerManipulationPropertySet(scroller);
- String sign = invertSourceAxis ? "-" : "+";
+ string sign = invertSourceAxis ? "-" : "+";
// Prepare the second property set to insert the additional parameter
CompositionPropertySet properties = scroller.GetVisual().Compositor.CreatePropertySet();
@@ -1719,7 +1721,6 @@ void Handler(object s, RoutedEventArgs e)
case Shape shape: shadow.Mask = shape.GetAlphaMask(); break;
case Image image: shadow.Mask = image.GetAlphaMask(); break;
case TextBlock textBlock: shadow.Mask = textBlock.GetAlphaMask(); break;
- default: break;
}
if (apply) ElementCompositionPreview.SetElementChildVisual(target, sprite);
return sprite;
@@ -1735,7 +1736,7 @@ void Handler(object s, RoutedEventArgs e)
/// The target object
/// The name of the property to animate
/// The final value of the property
- public static void SetInstantValue(this CompositionObject compObject, String property, float value)
+ public static void SetInstantValue([NotNull] this CompositionObject compObject, string property, float value)
{
// Stop previous animations
compObject.StopAnimation(property);
@@ -1754,7 +1755,7 @@ public static void SetInstantValue(this CompositionObject compObject, String pro
/// The name of the property to animate
/// The final value of the property
/// The animation duration
- public static Task StartAnimationAsync(this CompositionObject compObject, String property, float value, TimeSpan duration)
+ public static Task StartAnimationAsync([NotNull] this CompositionObject compObject, string property, float value, TimeSpan duration)
{
// Stop previous animations
compObject.StopAnimation(property);
@@ -1778,11 +1779,11 @@ public static Task StartAnimationAsync(this CompositionObject compObject, String
///
/// The target element
/// The names of the animations to stop
- public static void StopAnimations(this UIElement element, params String[] properties)
+ public static void StopAnimations([NotNull] this UIElement element, [NotNull, ItemNotNull] params string[] properties)
{
- if (properties == null || properties.Length == 0) return;
+ if (properties.Length == 0) return;
Visual visual = element.GetVisual();
- foreach (String property in properties) visual.StopAnimation(property);
+ foreach (string property in properties) visual.StopAnimation(property);
}
///
@@ -1792,7 +1793,7 @@ public static void StopAnimations(this UIElement element, params String[] proper
/// 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(this UIElement element, float? x, float? y, float? z)
+ 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();
@@ -1815,7 +1816,7 @@ public static void SetVisualScale(this UIElement element, float? x, float? y, fl
/// 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(this FrameworkElement element, float? x, float? y, float? z)
+ 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();
@@ -1838,7 +1839,7 @@ public static async Task SetVisualScaleAsync(this FrameworkElement element, floa
/// The target element
/// The offset axis to edit
/// The final offset value to set for that axis
- public static void SetVisualOffset(this UIElement element, TranslationAxis axis, float offset)
+ public static void SetVisualOffset([NotNull] this UIElement element, TranslationAxis axis, float offset)
{
// Get the element visual and stop the animation
Visual visual = element.GetVisual();
@@ -1856,7 +1857,7 @@ public static void SetVisualOffset(this UIElement element, TranslationAxis axis,
/// The target element
/// The x offset
/// The y offset
- public static void SetVisualOffset(this UIElement element, float x, float y)
+ public static void SetVisualOffset([NotNull] this UIElement element, float x, float y)
{
// Get the element visual and stop the animation
Visual visual = element.GetVisual();
@@ -1872,7 +1873,7 @@ public static void SetVisualOffset(this UIElement element, float x, float y)
/// The to edit
/// The offset axis to set
/// The new value for the axis to set
- public static Task SetVisualOffsetAsync(this UIElement element, TranslationAxis axis, float value)
+ public static Task SetVisualOffsetAsync([NotNull] this UIElement element, TranslationAxis axis, float value)
{
Visual visual = element.GetVisual();
return Task.Run(() =>
@@ -1890,7 +1891,7 @@ public static Task SetVisualOffsetAsync(this UIElement element, TranslationAxis
/// The to edit
/// The x offset
/// The y offset
- public static Task SetVisualOffsetAsync(this UIElement element, float x, float y)
+ public static Task SetVisualOffsetAsync([NotNull] this UIElement element, float x, float y)
{
Visual visual = element.GetVisual();
return Task.Run(() =>
@@ -1904,7 +1905,7 @@ public static Task SetVisualOffsetAsync(this UIElement element, float x, float y
/// Returns the visual offset for a target
///
/// The input element
- public static Vector3 GetVisualOffset(this UIElement element) => element.GetVisual().Offset;
+ public static Vector3 GetVisualOffset([NotNull] this UIElement element) => element.GetVisual().Offset;
///
/// Sets the translation property of the visual object for a given object
@@ -1912,7 +1913,7 @@ public static Task SetVisualOffsetAsync(this UIElement element, float x, float y
/// The target element
/// The translation axis to edit
/// The final translation value to set for that axis
- public static void SetVisualTranslation(this UIElement element, TranslationAxis axis, float translation)
+ public static void SetVisualTranslation([NotNull] this UIElement element, TranslationAxis axis, float translation)
{
// Get the element visual and stop the animation
Visual visual = element.GetVisual();
@@ -1940,7 +1941,7 @@ public static void SetVisualTranslation(this UIElement element, TranslationAxis
/// The target element
/// The x translation
/// The y translation
- public static void SetVisualTranslation(this UIElement element, float x, float y)
+ public static void SetVisualTranslation([NotNull] this UIElement element, float x, float y)
{
// Get the element visual and stop the animation
Visual visual = element.GetVisual();
@@ -1954,7 +1955,7 @@ public static void SetVisualTranslation(this UIElement element, float x, float y
///
/// The target element
/// The desired clip margins to set
- public static void SetVisualClip(this UIElement element, Thickness clip)
+ public static void SetVisualClip([NotNull] this UIElement element, Thickness clip)
{
// Get the element visual
Visual visual = element.GetVisual();
@@ -1974,7 +1975,7 @@ public static void SetVisualClip(this UIElement element, Thickness clip)
/// The target element
/// The desired clip value to set
/// The target clip side to update
- public static void SetVisualClip(this UIElement element, float clip, MarginSide side)
+ public static void SetVisualClip([NotNull] this UIElement element, float clip, MarginSide side)
{
// Get the element visual
Visual visual = element.GetVisual();
@@ -1995,7 +1996,7 @@ public static void SetVisualClip(this UIElement element, float clip, MarginSide
/// Resets the scale, offset and opacity properties for a framework element
///
/// The element to edit
- public static async Task ResetCompositionVisualPropertiesAsync(this FrameworkElement element)
+ public static async Task ResetCompositionVisualPropertiesAsync([NotNull] this FrameworkElement element)
{
// Get the default values and set the CenterPoint
Visual visual = element.GetVisual();
@@ -2014,20 +2015,73 @@ public static async Task ResetCompositionVisualPropertiesAsync(this FrameworkEle
/// Gets the opacity for the Visual object behind a given UIElement
///
/// The source UIElement
- public static float GetVisualOpacity(this UIElement element) => element.GetVisual().Opacity;
+ 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(this UIElement element, float value) => element.GetVisual().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(this UIElement element) => ElementCompositionPreview.GetElementVisual(element);
+ 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
}
diff --git a/UICompositionAnimations/Behaviours/Effects/AcrylicEffectMode.cs b/UICompositionAnimations/Enums/AcrylicEffectMode.cs
similarity index 89%
rename from UICompositionAnimations/Behaviours/Effects/AcrylicEffectMode.cs
rename to UICompositionAnimations/Enums/AcrylicEffectMode.cs
index d7f3b7d..11b02f1 100644
--- a/UICompositionAnimations/Behaviours/Effects/AcrylicEffectMode.cs
+++ b/UICompositionAnimations/Enums/AcrylicEffectMode.cs
@@ -1,6 +1,6 @@
using System;
-namespace UICompositionAnimations.Behaviours.Effects
+namespace UICompositionAnimations.Enums
{
///
/// Indicates the UI mode for an acrylic effect brush
diff --git a/UICompositionAnimations/Enums/BitmapCacheMode.cs b/UICompositionAnimations/Enums/BitmapCacheMode.cs
index a50b52b..451a86e 100644
--- a/UICompositionAnimations/Enums/BitmapCacheMode.cs
+++ b/UICompositionAnimations/Enums/BitmapCacheMode.cs
@@ -1,23 +1,26 @@
-namespace UICompositionAnimations.Enums
+using JetBrains.Annotations;
+
+namespace UICompositionAnimations.Enums
{
///
- /// Indicates the cache mode to use when loading an item
+ /// Indicates the cache mode to use when loading a Win2D image
///
+ [PublicAPI]
public enum BitmapCacheMode
{
///
- /// The new item will be either loaded from the cache when possible, or saved in the cache for future use
+ /// The default behavior, the cache is enabled
///
- EnableCaching,
+ Default,
///
- /// The item will not be loaded from the cache, but it will be stored in the cache for future use if possible
+ /// Reload the target image and overwrite the cached entry, if it exists
///
- DisableCachingOnRead,
+ Overwrite,
///
- /// The new item will not be loaded from the cache and it will not be saved into the cache either
+ /// The cache is disabled and new images are always reloaded
///
- DisableCaching
+ Disabled
}
}
\ No newline at end of file
diff --git a/UICompositionAnimations/Enums/EffectPlacement.cs b/UICompositionAnimations/Enums/EffectPlacement.cs
new file mode 100644
index 0000000..5c61248
--- /dev/null
+++ b/UICompositionAnimations/Enums/EffectPlacement.cs
@@ -0,0 +1,22 @@
+using Windows.Graphics.Effects;
+using JetBrains.Annotations;
+
+namespace UICompositionAnimations.Enums
+{
+ ///
+ /// An used to modify the default placement of the input instance in a blend operation
+ ///
+ [PublicAPI]
+ public enum EffectPlacement
+ {
+ ///
+ /// The instance used to call the blend method is placed on top of the other
+ ///
+ Foreground,
+
+ ///
+ /// The instance used to call the blend method is placed behind the other
+ ///
+ Background
+ }
+}
\ No newline at end of file
diff --git a/UICompositionAnimations/Enums/ImplicitAnimationType.cs b/UICompositionAnimations/Enums/ImplicitAnimationType.cs
index 932493a..191a086 100644
--- a/UICompositionAnimations/Enums/ImplicitAnimationType.cs
+++ b/UICompositionAnimations/Enums/ImplicitAnimationType.cs
@@ -1,12 +1,21 @@
-#pragma warning disable 1591
+using JetBrains.Annotations;
+
namespace UICompositionAnimations.Enums
{
///
/// Indicates the type of an implicit composition animation
///
+ [PublicAPI]
public enum ImplicitAnimationType
{
+ ///
+ /// The animation plays when the item becomes visible
+ ///
Show,
+
+ ///
+ /// The animation plays when the item is collapsed
+ ///
Hide
}
}
\ No newline at end of file
diff --git a/UICompositionAnimations/Enums/TranslationAxis.cs b/UICompositionAnimations/Enums/TranslationAxis.cs
index 71d4e94..8c68a6d 100644
--- a/UICompositionAnimations/Enums/TranslationAxis.cs
+++ b/UICompositionAnimations/Enums/TranslationAxis.cs
@@ -1,12 +1,21 @@
-#pragma warning disable 1591
+using JetBrains.Annotations;
+
namespace UICompositionAnimations.Enums
{
///
/// Indicates the translation axis to use in an animation
///
+ [PublicAPI]
public enum TranslationAxis
{
+ ///
+ /// Horizontal translation
+ ///
X,
+
+ ///
+ /// Vertical translation
+ ///
Y
}
}
\ No newline at end of file
diff --git a/UICompositionAnimations/Helpers/ApiInformationHelper.cs b/UICompositionAnimations/Helpers/ApiInformationHelper.cs
deleted file mode 100644
index 4a3790a..0000000
--- a/UICompositionAnimations/Helpers/ApiInformationHelper.cs
+++ /dev/null
@@ -1,205 +0,0 @@
-using System;
-using Windows.ApplicationModel;
-using Windows.ApplicationModel.Resources.Core;
-using Windows.Foundation.Collections;
-using Windows.Foundation.Metadata;
-using Windows.System.Profile;
-using Windows.UI.Composition;
-using Windows.UI.Xaml.Controls;
-using Windows.UI.Xaml.Hosting;
-using Windows.UI.Xaml.Input;
-
-namespace UICompositionAnimations.Helpers
-{
- ///
- /// A static class that provides useful information on the available APIs
- ///
- public static class ApiInformationHelper
- {
- #region OS build
-
- static Version _OSVersion;
-
- ///
- /// Gets the current OS version for the device in use
- ///
- /// It is better to check the available APIs directly with the other properties exposed by the class
- /// instead of just checking the OS version and use it to make further decisions.
- /// In particular, users on a Windows Insider ring could already have a given set of APIs even though their OS
- /// build doesn't exactly match the official Windows 10 version that supports a requested API contract.
- public static Version OSVersion
- {
- get
- {
- if (_OSVersion == null)
- {
- string deviceFamilyVersion = AnalyticsInfo.VersionInfo.DeviceFamilyVersion;
- ulong version = ulong.Parse(deviceFamilyVersion);
- ulong major = (version & 0xFFFF000000000000L) >> 48;
- ulong minor = (version & 0x0000FFFF00000000L) >> 32;
- ulong build = (version & 0x00000000FFFF0000L) >> 16;
- ulong revision = version & 0x000000000000FFFFL;
- _OSVersion = new Version((int)major, (int)minor, (int)build, (int)revision);
- }
- return _OSVersion;
- }
- }
-
- ///
- /// Gets whether or not the current OS version is at least the Anniversary Update (14393)
- ///
- ///
- public static bool IsAnniversaryUpdateOrLater => OSVersion.Build > 14393;
-
- ///
- /// Gets whether or not the current OS version is at least the Creator's Update (15063)
- ///
- ///
- public static bool IsCreatorsUpdateOrLater => OSVersion.Build > 15063;
-
- ///
- /// Gets whether or not the current OS version is at least the Fall Creator's Update (16xxx)
- ///
- ///
- public static bool IsFallCreatorsUpdateOrLater => OSVersion.Build > 16299;
-
- #endregion
-
- #region Available APIs
-
- ///
- /// Gets whether or not the connected animations APIs are available
- ///
- public static bool AreConnectedAnimationsAvailable => ApiInformation.IsTypePresent("Windows.UI.Xaml.Media.Animation.ConnectedAnimationService");
-
- ///
- /// Gets whether or not the APIs to find the focusable elements are available
- ///
- public static bool IsFindFirstFocusableElementAvailable => ApiInformation.IsMethodPresent("Windows.UI.Xaml.Input.FocusManager", nameof(FocusManager.FindFirstFocusableElement));
-
- ///
- /// Gets or sets whether the Target property of the composition animation APIs is available
- ///
- public static bool SupportsCompositionAnimationTarget => ApiInformation.IsPropertyPresent("Windows.UI.Composition.CompositionAnimation", nameof(CompositionAnimation.Target));
-
- ///
- /// Gets whether or not the composition animation group type is present
- ///
- public static bool IsCompositionAnimationGroupAvailable => ApiInformation.IsTypePresent("Windows.UI.Composition.CompositionAnimationGroup");
-
- ///
- /// Gets whether or not the implicit animations APIs are available
- ///
- ///
- public static bool AreImplicitAnimationsAvailable => ApiInformation.IsMethodPresent("Windows.UI.Composition.Compositor", nameof(Compositor.CreateImplicitAnimationCollection));
-
- ///
- /// Gets whether or not the implicit show/hide animation APIs are available
- ///
- public static bool AreImplicitShowHideAnimationsAvailable => ApiInformation.IsMethodPresent("Windows.UI.Xaml.Hosting.ElementCompositionPreview", nameof(ElementCompositionPreview.SetImplicitShowAnimation));
-
- ///
- /// Gets whether or not the XAML lights APIs are available
- ///
- public static bool AreXamlLightsSupported => ApiInformation.IsTypePresent("Windows.UI.Xaml.Media.XamlLight");
-
- ///
- /// Gets whether or not the custom XAML brush APIs are available
- ///
- public static bool AreCustomBrushesSupported => ApiInformation.IsTypePresent("Windows.UI.Xaml.Media.XamlCompositionBrushBase");
-
- ///
- /// Gets whether or not the XYFocusUp property is available/>
- ///
- ///
- public static bool IsXYFocusAvailable => ApiInformation.IsPropertyPresent("Windows.UI.Xaml.Controls.Control", nameof(Control.XYFocusUp));
-
- ///
- /// Gets whether or not the host backdrop effects are available
- ///
- ///
- public static bool AreHostBackdropEffectsAvailable => ApiInformation.IsTypePresent("Microsoft.Graphics.Canvas.Effects.GaussianBlurEffect") &&
- ApiInformation.IsMethodPresent("Windows.UI.Composition.Compositor", nameof(Compositor.CreateHostBackdropBrush));
-
- ///
- /// Gets whether or not the backdrop effects APIs are available
- ///
- ///
- public static bool AreBackdropEffectsAvailable => ApiInformation.IsTypePresent("Microsoft.Graphics.Canvas.Effects.GaussianBlurEffect") &&
- ApiInformation.IsMethodPresent("Windows.UI.Composition.Compositor", nameof(Compositor.CreateBackdropBrush));
-
- ///
- /// Gets whether or not the drop shadows APIs are available
- ///
- public static bool AreDropShadowsAvailable => ApiInformation.IsMethodPresent("Windows.UI.Composition.Compositor", nameof(Compositor.CreateDropShadow));
-
- ///
- /// Gets whether or not the staggering method is available
- ///
- ///
- public static bool IsRepositionStaggerringAvailable => ApiInformation.IsMethodPresent("Windows.UI.Xaml.Media.Animation.RepositionThemeTransition", "IsStaggeringEnabled");
-
- ///
- /// Gets whether or not the RequiresPointer property is available
- ///
- public static bool IsRequiresPointerAvailable => ApiInformation.IsPropertyPresent("Windows.UI.Xaml.Controls.Control", nameof(Control.RequiresPointer));
-
- #endregion
-
- #region Device family
-
- private static bool? _IsMobileDevice;
-
- ///
- /// Gets whether or not the device is a mobile phone
- ///
- public static bool IsMobileDevice
- {
- get
- {
- if (_IsMobileDevice == null)
- {
- try
- {
- IObservableMap qualifiers = ResourceContext.GetForCurrentView().QualifierValues;
- _IsMobileDevice = qualifiers.ContainsKey("DeviceFamily") && qualifiers["DeviceFamily"] == "Mobile";
- }
- catch (UnauthorizedAccessException)
- {
- // No idea why this should happen
- return ApiInformation.IsTypePresent("Windows.Phone.UI.Input.HardwareButtons");
- }
- }
- return _IsMobileDevice.Value;
- }
- }
-
- private static bool? _IsDesktop;
-
- ///
- /// Gets whether or not the device is running Windows 10 Desktop
- ///
- public static bool IsDesktop
- {
- get
- {
- if (_IsDesktop == null)
- {
- try
- {
- IObservableMap qualifiers = ResourceContext.GetForCurrentView().QualifierValues;
- _IsDesktop = qualifiers.ContainsKey("DeviceFamily") && qualifiers["DeviceFamily"] == "Desktop";
- }
- catch (UnauthorizedAccessException)
- {
- // Weird crash, but still...
- return false;
- }
- }
- return _IsDesktop.Value;
- }
- }
-
- #endregion
- }
-}
diff --git a/UICompositionAnimations/Helpers/AsyncMutex.cs b/UICompositionAnimations/Helpers/AsyncMutex.cs
new file mode 100644
index 0000000..663a6c5
--- /dev/null
+++ b/UICompositionAnimations/Helpers/AsyncMutex.cs
@@ -0,0 +1,42 @@
+using System.Runtime.CompilerServices;
+using JetBrains.Annotations;
+
+namespace System.Threading.Tasks
+{
+ ///
+ /// An implementation that can be easily used inside a block
+ ///
+ internal sealed class AsyncMutex
+ {
+ // The underlying semaphore used by this instance
+ [NotNull]
+ private readonly SemaphoreSlim Semaphore = new SemaphoreSlim(1);
+
+ ///
+ /// Acquires a lock for the current instance, that is automatically released outside the block
+ ///
+ [NotNull, ItemNotNull]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public async Task LockAsync()
+ {
+ await Semaphore.WaitAsync().ConfigureAwait(false);
+ return new _Lock(Semaphore);
+ }
+
+ ///
+ /// Private class that implements the automatic release of the semaphore
+ ///
+ private sealed class _Lock : IDisposable
+ {
+ // Reference to the semaphore instance of the parent class
+ [NotNull]
+ private readonly SemaphoreSlim Semaphore;
+
+ public _Lock([NotNull] SemaphoreSlim semaphore) => Semaphore = semaphore;
+
+ // Releases the lock when the instance is disposed
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ void IDisposable.Dispose() => Semaphore.Release();
+ }
+ }
+}
\ No newline at end of file
diff --git a/UICompositionAnimations/Helpers/ColorConverter.cs b/UICompositionAnimations/Helpers/ColorConverter.cs
index 221c8b8..e72ede3 100644
--- a/UICompositionAnimations/Helpers/ColorConverter.cs
+++ b/UICompositionAnimations/Helpers/ColorConverter.cs
@@ -1,5 +1,4 @@
-using System;
-using System.Linq;
+using System.Linq;
using Windows.UI;
namespace UICompositionAnimations.Helpers
@@ -10,15 +9,15 @@ namespace UICompositionAnimations.Helpers
internal static class ColorConverter
{
///
- /// Returns the Color represented by the hex String
+ /// 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)
+ public static Color String2Color(string color)
{
- //Cancels the # symbol from the String, if present
+ //Cancels the # symbol from the string, if present
if (color.Contains('#')) color = color.Substring(1);
byte alpha;
- String RGB;
+ string RGB;
if (color.Length == 6)
{
alpha = 255;
diff --git a/UICompositionAnimations/Helpers/DispatcherHelper.cs b/UICompositionAnimations/Helpers/DispatcherHelper.cs
index d3fbe94..02dc43e 100644
--- a/UICompositionAnimations/Helpers/DispatcherHelper.cs
+++ b/UICompositionAnimations/Helpers/DispatcherHelper.cs
@@ -9,6 +9,7 @@ namespace UICompositionAnimations.Helpers
///
/// A static class that manages the UI dispatching from background threads
///
+ [PublicAPI]
public static class DispatcherHelper
{
#region Target dispatcher helpers
@@ -18,17 +19,10 @@ public static class DispatcherHelper
///
/// The target dispatcher to use to schedule the callback execution
/// The action to execute on the UI thread
- [PublicAPI]
public static void Run([NotNull] this CoreDispatcher dispatcher, [NotNull] Action callback)
{
if (dispatcher.HasThreadAccess) callback();
- else
- {
- dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
- {
- callback();
- }).Forget();
- }
+ else dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => callback()).Forget();
}
///
@@ -36,17 +30,10 @@ public static void Run([NotNull] this CoreDispatcher dispatcher, [NotNull] Actio
///
/// The target dispatcher to use to schedule the callback execution
/// The action to execute on the UI thread
- [PublicAPI]
public static void Run([NotNull] this CoreDispatcher dispatcher, [NotNull] Func asyncCallback)
{
- // Check the current thread
if (dispatcher.HasThreadAccess) asyncCallback();
-
- // Schedule on the UI thread if necessary
- dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
- {
- asyncCallback();
- }).Forget();
+ else dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => asyncCallback()).Forget();
}
///
@@ -54,7 +41,6 @@ public static void Run([NotNull] this CoreDispatcher dispatcher, [NotNull] Func<
///
/// The target dispatcher to use to schedule the callback execution
/// The action to execute on the UI thread
- [PublicAPI]
public static async Task RunAsync([NotNull] this CoreDispatcher dispatcher, [NotNull] Action callback)
{
if (dispatcher.HasThreadAccess) callback();
@@ -75,13 +61,9 @@ public static async Task RunAsync([NotNull] this CoreDispatcher dispatcher, [Not
///
/// The target dispatcher to use to schedule the callback execution
/// The action to execute on the UI thread
- [PublicAPI]
public static Task RunAsync([NotNull] this CoreDispatcher dispatcher, [NotNull] Func asyncCallback)
{
- // Check the current thread
if (dispatcher.HasThreadAccess) return asyncCallback();
-
- // Schedule on the UI thread if necessary
TaskCompletionSource tcs = new TaskCompletionSource();
dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
@@ -96,17 +78,16 @@ public static Task RunAsync([NotNull] this CoreDispatcher dispatcher, [NotNull]
/// The return type
/// The target dispatcher to use to schedule the callback execution
/// The function to execute on the UI thread
- [PublicAPI]
- public static async ValueTask GetAsync([NotNull] this CoreDispatcher dispatcher, [NotNull] Func function)
+ public static Task GetAsync([NotNull] this CoreDispatcher dispatcher, [NotNull] Func function)
{
- if (dispatcher.HasThreadAccess) return function();
+ if (dispatcher.HasThreadAccess) return Task.FromResult(function());
TaskCompletionSource tcs = new TaskCompletionSource();
dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
T result = function();
tcs.SetResult(result);
}).Forget();
- return await tcs.Task;
+ return tcs.Task;
}
///
@@ -115,13 +96,9 @@ public static async ValueTask GetAsync([NotNull] this CoreDispatcher dispa
/// The return type
/// The target dispatcher to use to schedule the callback execution
/// The async function to execute on the UI thread
- [PublicAPI]
public static Task GetAsync([NotNull] this CoreDispatcher dispatcher, [NotNull] Func> function)
{
- // Check the current thread
if (dispatcher.HasThreadAccess) return function();
-
- // Schedule on the UI thread if necessary
TaskCompletionSource> tcs = new TaskCompletionSource>();
dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
@@ -151,28 +128,24 @@ public static Task GetAsync([NotNull] this CoreDispatcher dispatcher, [Not
/// Executes a given action on the UI thread without awaiting the operation
///
/// The action to execute on the UI thread
- [PublicAPI]
public static void RunOnUIThread([NotNull] Action callback) => CoreDispatcher.Run(callback);
///
/// Executes a given async action on the UI thread without awaiting the operation
///
/// The action to execute on the UI thread
- [PublicAPI]
public static void RunOnUIThread([NotNull] Func asyncCallback) => CoreDispatcher.Run(asyncCallback);
///
/// Executes a given action on the UI thread and waits for it to be completed
///
/// The action to execute on the UI thread
- [PublicAPI]
public static Task RunOnUIThreadAsync([NotNull] Action callback) => CoreDispatcher.RunAsync(callback);
///
/// Executes a given action on the UI thread and waits for it to be completed
///
/// The action to execute on the UI thread
- [PublicAPI]
public static Task RunOnUIThreadAsync([NotNull] Func asyncCallback) => CoreDispatcher.RunAsync(asyncCallback);
///
@@ -180,15 +153,13 @@ public static Task GetAsync([NotNull] this CoreDispatcher dispatcher, [Not
///
/// The return type
/// The function to execute on the UI thread
- [PublicAPI]
- public static ValueTask GetFromUIThreadAsync([NotNull] Func function) => CoreDispatcher.GetAsync(function);
+ public static Task GetFromUIThreadAsync([NotNull] Func function) => CoreDispatcher.GetAsync(function);
///
/// Executes a given async function on the UI thread and returns its result
///
/// The return type
/// The async function to execute on the UI thread
- [PublicAPI]
public static Task GetFromUIThreadAsync([NotNull] Func> function) => CoreDispatcher.GetAsync(function);
#endregion
diff --git a/UICompositionAnimations/Helpers/Extensions.cs b/UICompositionAnimations/Helpers/Extensions.cs
index e98734c..f20972f 100644
--- a/UICompositionAnimations/Helpers/Extensions.cs
+++ b/UICompositionAnimations/Helpers/Extensions.cs
@@ -1,7 +1,9 @@
using System;
+using System.Collections.Generic;
+using System.Linq;
using System.Runtime.CompilerServices;
-using System.Threading.Tasks;
using Windows.Foundation;
+using JetBrains.Annotations;
namespace UICompositionAnimations.Helpers
{
@@ -44,10 +46,31 @@ internal static class Extensions
public static void Forget(this IAsyncAction action) { }
///
- /// Suppresses the warnings when calling an async method without awaiting it
+ /// Merges the two input instances and makes sure no duplicate keys are present
///
- /// The task returned by the async call
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void Forget(this Task task) { }
+ /// 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/PointerEvents/PointerHelper.cs b/UICompositionAnimations/Helpers/PointerEvents/PointerHelper.cs
index a8caeab..57fbea2 100644
--- a/UICompositionAnimations/Helpers/PointerEvents/PointerHelper.cs
+++ b/UICompositionAnimations/Helpers/PointerEvents/PointerHelper.cs
@@ -9,89 +9,74 @@ namespace UICompositionAnimations.Helpers.PointerEvents
///
/// A static class with some extension methods to manage different pointer states
///
+ [PublicAPI]
public static class PointerHelper
{
///
- /// Adds an event handler to all the pointer events of the target control
+ /// Adds an event handler to the following targets of the input :
+ /// UIElement.PointerExitedEvent, UIElement.PointerCaptureLostEvent, UIElement.PointerCanceledEvent, UIElement.PointerEnteredEvent, UIElement.PointerReleasedEvent.
+ /// Some additional handling of the input device is also performed automatically to filter out some useful input combinations (eg. disable touch input for the case).
///
- /// The control to monitor
- /// An action to call every time a pointer event is raised. The bool parameter
- /// indicates whether the pointer is moving to or from the control
- [PublicAPI]
+ /// The to monitor
+ /// An to call every time a pointer event is raised. The parameter indicates whether the pointer is moving to or from the control
[NotNull]
- public static ControlAttachedHandlersInfo ManageControlPointerStates(this UIElement control, Action action)
+ public static ControlAttachedHandlersInfo ManageControlPointerStates([NotNull] this UIElement control, [NotNull] Action action)
{
- // Nested functions that adds the actual handlers
- PointerHandlerInfo AddHandler(RoutedEvent @event, bool state, Func predicate)
- {
- PointerEventHandler handler = (_, e) =>
- {
- if (predicate == null || predicate(e.Pointer.PointerDeviceType))
- {
- action(e.Pointer.PointerDeviceType, state);
- }
- };
- control.AddHandler(@event, handler, true);
- return new PointerHandlerInfo(@event, handler);
- }
-
- // Add handlers
return new ControlAttachedHandlersInfo(control,
- AddHandler(UIElement.PointerExitedEvent, false, null),
- AddHandler(UIElement.PointerCaptureLostEvent, false, null),
- AddHandler(UIElement.PointerCanceledEvent, false, null),
- AddHandler(UIElement.PointerEnteredEvent, true, p => p != PointerDeviceType.Touch),
- AddHandler(UIElement.PointerReleasedEvent, false, p => p == PointerDeviceType.Touch));
+ AddHandler(control, UIElement.PointerExitedEvent, false, null, action),
+ AddHandler(control, UIElement.PointerCaptureLostEvent, false, null, action),
+ AddHandler(control, UIElement.PointerCanceledEvent, false, null, action),
+ AddHandler(control, UIElement.PointerEnteredEvent, true, p => p != PointerDeviceType.Touch, action),
+ AddHandler(control, UIElement.PointerReleasedEvent, false, p => p == PointerDeviceType.Touch, action));
}
///
- /// Adds an event handler to all the pointer events of the target element
+ /// Adds an event handler to the following targets of the input : UIElement.PointerExitedEvent, UIElement.PointerMovedEvent.
+ /// This is especially useful to check whether or not the user is currently moving the input cursor above a host control, as this method will correctly handle scenarios
+ /// where the cursor would be considered as exited from the target control, while still being inside its area.
+ /// For example, this could happen if the user clicked on a nested control inside the target host, without actually moving the cursor away.
///
- /// The element to monitor
- /// An action to call every time a pointer event is raised. The bool parameter
- /// indicates whether the pointer is moving to or from the control
- [PublicAPI]
+ /// The to monitor
+ /// An to call every time a pointer event is raised. The parameter indicates whether the pointer is moving to or from the control
[NotNull]
- public static ControlAttachedHandlersInfo ManageHostPointerStates(this UIElement host, Action action)
+ public static ControlAttachedHandlersInfo ManageHostPointerStates([NotNull] this UIElement host, [NotNull] Action action)
{
- // Nested functions that adds the actual handlers
- PointerHandlerInfo AddHandler(RoutedEvent @event, bool state, Func predicate)
- {
- PointerEventHandler handler = (_, e) =>
- {
- if (predicate == null || predicate(e.Pointer.PointerDeviceType))
- {
- action(e.Pointer.PointerDeviceType, state);
- }
- };
- host.AddHandler(@event, handler, true);
- return new PointerHandlerInfo(@event, handler);
- }
-
- // Add handlers
return new ControlAttachedHandlersInfo(host,
- AddHandler(UIElement.PointerExitedEvent, false, null),
- AddHandler(UIElement.PointerMovedEvent, true, p => p != PointerDeviceType.Touch));
+ AddHandler(host, UIElement.PointerExitedEvent, false, null, action),
+ AddHandler(host, UIElement.PointerMovedEvent, true, p => p != PointerDeviceType.Touch, action));
}
///
- /// Adds the appropriate handlers to a control to help setup the light effects (skipped when on a mobile phone)
+ /// Adds the appropriate handlers to a control to help setup the light effects (skipped when using a touch screen)
///
- /// The element to monitor
- /// An action to call every time the light effects state should be changed
- [PublicAPI]
- [CanBeNull]
- public static ControlAttachedHandlersInfo ManageLightsPointerStates(this UIElement element, Action action)
+ /// The to monitor
+ /// An to call every time the light effects state should be changed
+ [NotNull]
+ public static ControlAttachedHandlersInfo ManageLightsPointerStates([NotNull] this UIElement element, [NotNull] Action action)
{
- // Platform check
- if (ApiInformationHelper.IsMobileDevice) return null;
-
- // Nested functions that adds the actual handlers
+ bool state = false;
return element.ManageHostPointerStates((pointer, value) =>
{
- if (pointer != PointerDeviceType.Mouse) return;
+ if (pointer != PointerDeviceType.Mouse || state == value) return;
+ state = value;
action(value);
});
}
+
+ // Private method that adds a custom handler to the target event and control
+ [NotNull]
+ private static PointerHandlerInfo AddHandler([NotNull] UIElement element, [NotNull] RoutedEvent @event, bool state, [CanBeNull] Func predicate, [NotNull] Action action)
+ {
+ // Callback with additional pointer type check
+ void Handler(object _, PointerRoutedEventArgs e)
+ {
+ if (predicate == null || predicate(e.Pointer.PointerDeviceType))
+ action(e.Pointer.PointerDeviceType, state);
+ }
+
+ // Handler setup
+ element.AddHandler(@event, (PointerEventHandler)Handler, true);
+ return new PointerHandlerInfo(@event, Handler);
+ }
}
}
diff --git a/UICompositionAnimations/Helpers/Win2DImageHelper.cs b/UICompositionAnimations/Helpers/Win2DImageHelper.cs
index 7ba2568..1686632 100644
--- a/UICompositionAnimations/Helpers/Win2DImageHelper.cs
+++ b/UICompositionAnimations/Helpers/Win2DImageHelper.cs
@@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Numerics;
using System.Runtime.InteropServices;
-using System.Threading;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.Graphics.DirectX;
@@ -16,6 +15,7 @@
using Microsoft.Graphics.Canvas.UI;
using Microsoft.Graphics.Canvas.UI.Composition;
using Microsoft.Graphics.Canvas.UI.Xaml;
+using UICompositionAnimations.Brushes.Cache;
using UICompositionAnimations.Enums;
namespace UICompositionAnimations.Helpers
@@ -30,15 +30,15 @@ public static class Win2DImageHelper
///
private const int DeviceLostRecoveryThreshold = 1000;
- ///
- /// Gets a shared semaphore to avoid loading multiple Win2D resources at the same time
- ///
- private static readonly SemaphoreSlim Win2DSemaphore = new SemaphoreSlim(1);
+ // Synchronization mutex to access the cache and load Win2D images concurrently
+ [NotNull]
+ private static readonly AsyncMutex Win2DMutex = new AsyncMutex();
///
/// Gets the local cache mapping for previously loaded Win2D images
///
- private static readonly IDictionary SurfacesCache = new Dictionary();
+ [NotNull]
+ private static readonly ThreadSafeCompositionMapCache Cache = new ThreadSafeCompositionMapCache();
#region Public APIs
@@ -47,11 +47,12 @@ 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)
+ public static Task LoadImageAsync([NotNull] Uri uri, BitmapDPIMode dpiMode, BitmapCacheMode cache = BitmapCacheMode.Default)
{
- return LoadImageAsync(Window.Current.Compositor, uri, BitmapCacheMode.DisableCaching, dpiMode);
+ return LoadImageAsync(Window.Current.Compositor, uri, dpiMode, cache);
}
///
@@ -60,11 +61,12 @@ public static Task LoadImageAsync([NotNull] Uri uri, Bi
/// The to use to load the target image
/// 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)
+ public static Task LoadImageAsync([NotNull] this CanvasControl canvas, [NotNull] Uri uri, BitmapDPIMode dpiMode, BitmapCacheMode cache = BitmapCacheMode.Default)
{
- return LoadImageAsync(Window.Current.Compositor, canvas, uri, BitmapCacheMode.DisableCaching, dpiMode);
+ return LoadImageAsync(Window.Current.Compositor, canvas, uri, dpiMode, cache);
}
///
@@ -74,13 +76,10 @@ public static Task LoadImageAsync([NotNull] this Canvas
/// The returned items should be manually disposed once checked that they are no longer being used in other effect pipelines
[PublicAPI]
[MustUseReturnValue, ItemNotNull]
- public static async Task> ClearCacheAsync()
+ public static async Task> ClearCacheAsync()
{
- await Win2DSemaphore.WaitAsync();
- IEnumerable surfaces = SurfacesCache.Values;
- SurfacesCache.Clear();
- Win2DSemaphore.Release();
- return surfaces;
+ using (await Win2DMutex.LockAsync())
+ return Cache.Clear();
}
#endregion
@@ -120,13 +119,12 @@ public static async Task> ClearCacheAsync()
bitmap = await CanvasBitmap.LoadAsync(creator, uri, dpi >= 96 ? dpi : 96);
break;
default:
- throw new ArgumentOutOfRangeException("Unsupported DPI mode");
+ throw new ArgumentOutOfRangeException(nameof(dpiMode), "Unsupported DPI mode");
}
// Get the device and the target surface
CompositionGraphicsDevice device = CanvasComposition.CreateCompositionGraphicsDevice(compositor, canvasDevice);
- CompositionDrawingSurface surface = device.CreateDrawingSurface(default(Size),
- DirectXPixelFormat.B8G8R8A8UIntNormalized, DirectXAlphaMode.Premultiplied);
+ CompositionDrawingSurface surface = device.CreateDrawingSurface(default, DirectXPixelFormat.B8G8R8A8UIntNormalized, DirectXAlphaMode.Premultiplied);
// Calculate the surface size
Size
@@ -158,6 +156,7 @@ public static async Task> ClearCacheAsync()
{
// Manual using block to allow the initial switch statement
bitmap?.Dispose();
+ Cache.Cleanup();
}
}
@@ -167,13 +166,14 @@ public static async Task> ClearCacheAsync()
/// The compositor to use to render the Win2D image
/// The to use to load the target image
/// The path to the image to load
- /// Indicates whether or not to force the reload of the Win2D image
/// 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, BitmapCacheMode options, BitmapDPIMode dpiMode)
+ internal static async Task LoadImageAsync([NotNull] Compositor compositor, [NotNull] CanvasControl canvas, [NotNull] Uri uri, BitmapDPIMode dpiMode, BitmapCacheMode cache)
{
TaskCompletionSource tcs = new TaskCompletionSource();
+
+ // Loads an image using the input CanvasDevice instance
async Task LoadImageAsync(bool shouldThrow)
{
// Load the image - this will only succeed when there's an available Win2D device
@@ -187,6 +187,8 @@ async Task LoadImageAsync(bool shouldThrow)
return null;
}
}
+
+ // Handler to create the Win2D image from the input CanvasControl
async void Canvas_CreateResources(CanvasControl sender, CanvasCreateResourcesEventArgs args)
{
// Cancel previous actions
@@ -210,39 +212,38 @@ async void Canvas_CreateResources(CanvasControl sender, CanvasCreateResourcesEve
}
// Lock the semaphore and check the cache first
- await Win2DSemaphore.WaitAsync();
- if (options == BitmapCacheMode.EnableCaching && SurfacesCache.TryGetValue(uri, out CompositionSurfaceBrush cached))
+ using (await Win2DMutex.LockAsync())
{
- Win2DSemaphore.Release();
- return cached;
- }
+ if (cache == BitmapCacheMode.Default && Cache.TryGetInstance(uri, out CompositionSurfaceBrush cached)) return cached;
- // Load the image
- canvas.CreateResources += Canvas_CreateResources;
- try
- {
- // This will throw and the canvas will re-initialize the Win2D device if needed
- await LoadImageAsync(true);
- }
- catch (ArgumentException)
- {
- // Just ignore here
- }
- catch
- {
- // Win2D messed up big time
- tcs.TrySetResult(null);
- }
- await Task.WhenAny(tcs.Task, Task.Delay(DeviceLostRecoveryThreshold).ContinueWith(t => tcs.TrySetResult(null)));
- canvas.CreateResources -= Canvas_CreateResources;
- CompositionSurfaceBrush instance = tcs.Task.Result;
+ // Load the image
+ canvas.CreateResources += Canvas_CreateResources;
+ try
+ {
+ // This will throw and the canvas will re-initialize the Win2D device if needed
+ await LoadImageAsync(true);
+ }
+ catch (ArgumentException)
+ {
+ // Just ignore here
+ }
+ catch
+ {
+ // Win2D messed up big time
+ tcs.TrySetResult(null);
+ }
+ await Task.WhenAny(tcs.Task, Task.Delay(DeviceLostRecoveryThreshold).ContinueWith(t => tcs.TrySetResult(null)));
+ canvas.CreateResources -= Canvas_CreateResources;
+ CompositionSurfaceBrush brush = tcs.Task.Result;
- // Cache when needed and return the result
- if (instance != null &&
- options != BitmapCacheMode.DisableCaching &&
- !SurfacesCache.ContainsKey(uri)) SurfacesCache.Add(uri, instance);
- Win2DSemaphore.Release();
- return instance;
+ // 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);
+ }
+ return brush;
+ }
}
///
@@ -250,51 +251,49 @@ async void Canvas_CreateResources(CanvasControl sender, CanvasCreateResourcesEve
///
/// The compositor to use to render the Win2D image
/// The path to the image to load
- /// Indicates whether or not to force the reload of the Win2D image
/// 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, BitmapCacheMode options, BitmapDPIMode dpiMode)
+ internal static async Task LoadImageAsync([NotNull] Compositor compositor, [NotNull] Uri uri, BitmapDPIMode dpiMode, BitmapCacheMode 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)
+ string path = uri.AbsolutePath.StartsWith("/Files")
+ ? uri.AbsolutePath.Replace("/Files", string.Empty)
: uri.AbsolutePath;
uri = new Uri($"ms-appx://{path}");
}
- // Lock the semaphore and check the cache first
- await Win2DSemaphore.WaitAsync();
- if (options == BitmapCacheMode.EnableCaching && SurfacesCache.TryGetValue(uri, out CompositionSurfaceBrush cached))
+ // Lock and check the cache first
+ using (await Win2DMutex.LockAsync())
{
- Win2DSemaphore.Release();
- return cached;
- }
+ if (cache == BitmapCacheMode.Default && Cache.TryGetInstance(uri, out CompositionSurfaceBrush cached)) return cached;
- // Load the image
- CompositionSurfaceBrush brush;
- try
- {
- // This will throw and the canvas will re-initialize the Win2D device if needed
- CanvasDevice sharedDevice = CanvasDevice.GetSharedDevice();
- brush = await LoadSurfaceBrushAsync(sharedDevice, compositor, sharedDevice, uri, dpiMode);
- }
- catch
- {
- // Device error
- brush = null;
- }
+ // Load the image
+ CompositionSurfaceBrush brush;
+ try
+ {
+ // This will throw and the canvas will re-initialize the Win2D device if needed
+ CanvasDevice sharedDevice = CanvasDevice.GetSharedDevice();
+ brush = await LoadSurfaceBrushAsync(sharedDevice, compositor, sharedDevice, uri, dpiMode);
+ }
+ catch
+ {
+ // Device error
+ brush = null;
+ }
- // Cache when needed and return the result
- if (brush != null &&
- options != BitmapCacheMode.DisableCaching &&
- !SurfacesCache.ContainsKey(uri)) SurfacesCache.Add(uri, brush);
- Win2DSemaphore.Release();
- return brush;
+ // 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);
+ }
+ return brush;
+ }
}
#endregion
}
-}
+}
\ No newline at end of file
diff --git a/UICompositionAnimations/Lights/LightsSourceHelper.cs b/UICompositionAnimations/Lights/LightsSourceHelper.cs
index 212abe1..35af443 100644
--- a/UICompositionAnimations/Lights/LightsSourceHelper.cs
+++ b/UICompositionAnimations/Lights/LightsSourceHelper.cs
@@ -13,6 +13,7 @@ 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
@@ -62,10 +63,7 @@ public static void SetIsLightsContainer(UIElement element, bool value)
private static void OnIsLightsContainerPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// Designer test
- if (DesignMode.DesignModeEnabled) return;
-
- // Platform test
- if (ApiInformationHelper.IsMobileDevice) return;
+ if (DesignMode.DesignMode2Enabled) return;
// Unpack
if (LightGenerators == null) throw new NullReferenceException($"The {nameof(LightsSourceHelper)} class hasn't been initialized");
diff --git a/UICompositionAnimations/Lights/PointerPositionSpotLight.cs b/UICompositionAnimations/Lights/PointerPositionSpotLight.cs
index 426d9fe..82f3363 100644
--- a/UICompositionAnimations/Lights/PointerPositionSpotLight.cs
+++ b/UICompositionAnimations/Lights/PointerPositionSpotLight.cs
@@ -12,6 +12,7 @@ namespace UICompositionAnimations.Lights
///
/// An attached XAML property to enable the XAML brush
///
+ [PublicAPI]
public class PointerPositionSpotLight : XamlLight
{
#region Properties
@@ -21,8 +22,8 @@ public class PointerPositionSpotLight : XamlLight
///
public byte Shade
{
- get { return GetValue(ShadeProperty).To(); }
- set { SetValue(ShadeProperty, value); }
+ get => GetValue(ShadeProperty).To();
+ set => SetValue(ShadeProperty, value);
}
///
@@ -33,8 +34,7 @@ public byte Shade
private static void OnShadePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
- PointerPositionSpotLight l = d as PointerPositionSpotLight;
- if (l?._Light != null)
+ if (d is PointerPositionSpotLight @this && @this._Light != null)
{
byte shade = e.NewValue.To();
Color color = new Color
@@ -44,7 +44,7 @@ private static void OnShadePropertyChanged(DependencyObject d, DependencyPropert
G = shade,
B = shade
};
- l._Light.InnerConeColor = l._Light.OuterConeColor = color;
+ @this._Light.InnerConeColor = @this._Light.OuterConeColor = color;
}
}
@@ -73,8 +73,8 @@ public float Z
///
public float OuterConeAngle
{
- get { return (float)GetValue(OuterConeAngleProperty); }
- set { SetValue(OuterConeAngleProperty, value); }
+ get => (float)GetValue(OuterConeAngleProperty);
+ set => SetValue(OuterConeAngleProperty, value);
}
///
@@ -85,10 +85,9 @@ public float OuterConeAngle
private static void OuterConeAngleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
- PointerPositionSpotLight l = d as PointerPositionSpotLight;
- if (l?._Light != null)
+ if (d is PointerPositionSpotLight @this && @this._Light != null)
{
- l._Light.OuterConeAngleInDegrees = (float)e.NewValue;
+ @this._Light.OuterConeAngleInDegrees = (float)e.NewValue;
}
}
@@ -100,8 +99,8 @@ private static void OuterConeAngleChanged(DependencyObject d, DependencyProperty
///
public bool Active
{
- get { return GetValue(ActiveProperty).To(); }
- set { SetValue(ActiveProperty, value); }
+ get => GetValue(ActiveProperty).To();
+ set => SetValue(ActiveProperty, value);
}
///
@@ -112,8 +111,8 @@ public bool Active
private static void OnActivePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
- PointerPositionSpotLight l = d as PointerPositionSpotLight;
- l?._Light?.StartAnimationAsync("ConstantAttenuation", e.NewValue.To() ? 0 : InactiveAttenuationValue, TimeSpan.FromMilliseconds(250));
+ if (d is PointerPositionSpotLight @this)
+ @this._Light?.StartAnimationAsync("ConstantAttenuation", e.NewValue.To() ? 0 : InactiveAttenuationValue, TimeSpan.FromMilliseconds(250));
}
#endregion
@@ -139,9 +138,6 @@ private static void OnActivePropertyChanged(DependencyObject d, DependencyProper
private static void OnIsTargetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
- // API test
- if (!ApiInformationHelper.AreXamlLightsSupported) return;
-
// Apply the changes
if ((bool)e.NewValue)
{
@@ -170,16 +166,16 @@ private static void OnIsTargetChanged(DependencyObject d, DependencyPropertyChan
}
// The light to use
- SpotLight _Light;
+ private SpotLight _Light;
// The source compositor
- Compositor _Compositor;
+ private Compositor _Compositor;
// The expression animation for the light position
- ExpressionAnimation _Animation;
+ private ExpressionAnimation _Animation;
// The properties for the animation
- CompositionPropertySet _Properties;
+ private CompositionPropertySet _Properties;
///
protected override void OnConnected(UIElement newElement)
@@ -211,14 +207,14 @@ protected override void OnConnected(UIElement newElement)
/// Gets or sets a custom appendage for the method
///
[NotNull]
- public String IdAppendage { get; set; } = String.Empty;
+ public string IdAppendage { get; set; } = string.Empty;
///
- protected override String GetId() => GetIdStatic() + IdAppendage;
+ protected override string GetId() => GetIdStatic() + IdAppendage;
///
/// Gets a static Id for the class
///
- public static String GetIdStatic() => typeof(PointerPositionSpotLight).FullName;
+ public static string GetIdStatic() => typeof(PointerPositionSpotLight).FullName;
}
}
diff --git a/UICompositionAnimations/Properties/AssemblyInfo.cs b/UICompositionAnimations/Properties/AssemblyInfo.cs
index 2bf9f88..3eabee3 100644
--- a/UICompositionAnimations/Properties/AssemblyInfo.cs
+++ b/UICompositionAnimations/Properties/AssemblyInfo.cs
@@ -9,7 +9,7 @@
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Sergio Pedri")]
[assembly: AssemblyProduct("UICompositionAnimations")]
-[assembly: AssemblyCopyright("Copyright © 2017")]
+[assembly: AssemblyCopyright("Copyright © 2018")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
@@ -23,7 +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("2.15.0.0")]
-[assembly: AssemblyInformationalVersion("2.15.0.0")]
-[assembly: AssemblyFileVersion("1.0.0.0")]
-[assembly: ComVisible(false)]
\ No newline at end of file
+[assembly: AssemblyVersion("3.0.0.0")]
+[assembly: AssemblyFileVersion("3.0.0.0")]
+[assembly: AssemblyInformationalVersion("3.0.0")]
+[assembly: ComVisible(false)]
+
diff --git a/UICompositionAnimations/UICompositionAnimations.csproj b/UICompositionAnimations/UICompositionAnimations.csproj
index 671a449..ae2433a 100644
--- a/UICompositionAnimations/UICompositionAnimations.csproj
+++ b/UICompositionAnimations/UICompositionAnimations.csproj
@@ -11,11 +11,12 @@
UICompositionAnimations
it-IT
UAP
- 10.0.16299.0
- 10.0.10586.0
+ 10.0.17134.0
+ 10.0.17134.0
14
512
{A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ win10-arm;win10-arm-aot;win10-x86;win10-x86-aot;win10-x64;win10-x64-aot
AnyCPU
@@ -27,6 +28,7 @@
prompt
4
bin\Debug\UICompositionAnimations.XML
+ latest
AnyCPU
@@ -37,6 +39,7 @@
prompt
4
bin\Release\UICompositionAnimations.XML
+ latest
x86
@@ -48,6 +51,8 @@
x86
false
prompt
+ latest
+ bin\x86\Debug\UICompositionAnimations.XML
x86
@@ -60,6 +65,7 @@
false
prompt
bin\x86\Release\UICompositionAnimations.XML
+ latest
ARM
@@ -71,6 +77,8 @@
ARM
false
prompt
+ latest
+ bin\ARM\Debug\UICompositionAnimations.XML
ARM
@@ -83,6 +91,7 @@
false
prompt
bin\ARM\Release\UICompositionAnimations.XML
+ latest
x64
@@ -94,6 +103,8 @@
x64
false
prompt
+ latest
+ bin\x64\Debug\UICompositionAnimations.XML
x64
@@ -106,26 +117,21 @@
false
prompt
bin\x64\Release\UICompositionAnimations.XML
+ latest
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
@@ -135,9 +141,10 @@
+
-
+
@@ -155,6 +162,18 @@
+
+
+ 2018.2.1
+
+
+ 6.1.9
+
+
+ 1.23.0
+
+
+
14.0
diff --git a/UICompositionAnimations/UICompositionAnimations.nuspec b/UICompositionAnimations/UICompositionAnimations.nuspec
index 1c9004c..d9f3389 100644
--- a/UICompositionAnimations/UICompositionAnimations.nuspec
+++ b/UICompositionAnimations/UICompositionAnimations.nuspec
@@ -2,15 +2,21 @@
UICompositionAnimations
- $version$
+ 3.0.0
UICompositionAnimations
A wrapper UWP PCL to work with Windows.UI.Composition and XAML animations, and Win2D effects
Sergio Pedri
Sergio Pedri
https://github.com/Sergio0694/UICompositionAnimations
+ https://github.com/Sergio0694/UICompositionAnimations/blob/master/LICENSE.md
false
- Added new Composition Translation animations APIs, bug fixes
- Copyright 2017
+ Retargeted to support SDK 17134, new APIs and code refactoring
+ Copyright © 2018
uwp composition animations xaml csharp windows winrt universal app ui win2d graphics
+
+
+
+
+
\ No newline at end of file
diff --git a/UICompositionAnimations/XAMLTransform/XAMLTransformToolkit.cs b/UICompositionAnimations/XAMLTransform/XAMLTransformToolkit.cs
index e90241f..3ef0868 100644
--- a/UICompositionAnimations/XAMLTransform/XAMLTransformToolkit.cs
+++ b/UICompositionAnimations/XAMLTransform/XAMLTransformToolkit.cs
@@ -13,6 +13,7 @@ namespace UICompositionAnimations.XAMLTransform
///
/// A toolkit with some extensions for XAML animations and some useful methods
///
+ [PublicAPI]
public static class XAMLTransformToolkit
{
#region Extensions
@@ -109,7 +110,7 @@ public static bool CompareTargetValue(this Storyboard storyboard, Storyboard tes
/// 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,
+ 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
@@ -140,10 +141,10 @@ public static Storyboard PrepareStoryboard(params Timeline[] animations)
}
///
- /// Converts the given TranslationAxis enum into its String representation
+ /// 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";
+ public static string ToPropertyString(this TranslationAxis axis) => axis == TranslationAxis.X ? "X" : "Y";
#endregion
}
diff --git a/UICompositionAnimations/XAMLTransformExtensions.cs b/UICompositionAnimations/XAMLTransformExtensions.cs
index c34d4f2..b2e95ca 100644
--- a/UICompositionAnimations/XAMLTransformExtensions.cs
+++ b/UICompositionAnimations/XAMLTransformExtensions.cs
@@ -1,4 +1,5 @@
using System;
+using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
using Windows.UI;
using Windows.UI.Xaml;
@@ -14,6 +15,7 @@ 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
@@ -150,7 +152,7 @@ public static class XAMLTransformExtensions
/// The element to animate
/// The initial opacity
/// The end opacity
- /// A String that indicates which axis to use with the TranslateTransform animation
+ /// 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
@@ -385,7 +387,7 @@ public static class XAMLTransformExtensions
/// Slides a target element over a given axis
///
/// The element to animate
- /// A String that indicates which axis to use with the TranslateTransform animation
+ /// 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
@@ -423,6 +425,7 @@ public static class XAMLTransformExtensions
/// 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
@@ -460,6 +463,7 @@ public static async Task StartXAMLMarginAnimation([NotNull] this FrameworkElemen
DispatcherTimer timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(25) }; // 40fps
timer.Tick += TickHandler;
Thickness margin = element.Margin;
+
void TickHandler(object sender, object e)
{
elapsed++;
@@ -521,7 +525,7 @@ void TickHandler(object sender, object e)
/// 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)
+ public static void AnimateColor(this SolidColorBrush solidColorBrush, string toColor, int ms, EasingFunctionNames easing)
{
// Get the target color
Color targetColor = ColorConverter.String2Color(toColor);
diff --git a/UICompositionAnimations/project.json b/UICompositionAnimations/project.json
deleted file mode 100644
index 991a94b..0000000
--- a/UICompositionAnimations/project.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
- "dependencies": {
- "JetBrains.Annotations": "11.1.0",
- "Microsoft.NETCore.UniversalWindowsPlatform": "6.0.7",
- "System.Threading.Tasks.Extensions": "4.4.0",
- "Win2D.uwp": "1.21.0"
- },
- "frameworks": {
- "uap10.0.10586": {}
- },
- "runtimes": {
- "win10-arm": {},
- "win10-arm-aot": {},
- "win10-x86": {},
- "win10-x86-aot": {},
- "win10-x64": {},
- "win10-x64-aot": {}
- }
-}
\ No newline at end of file