diff --git a/Windows.Toolkit.Common.props b/Windows.Toolkit.Common.props
index 51218d281..b8decafc5 100644
--- a/Windows.Toolkit.Common.props
+++ b/Windows.Toolkit.Common.props
@@ -9,7 +9,6 @@
(c) .NET Foundation and Contributors. All rights reserved.
https://github.com/CommunityToolkit/Labs-Windows
https://github.com/CommunityToolkit/Labs-Windows/releases
- Icon.png
https://raw.githubusercontent.com/CommunityToolkit/Labs-Windows/main/nuget.png
$(NoWarn);NU1505;NU1504
diff --git a/common/GlobalUsings_WinUI.cs b/common/GlobalUsings_WinUI.cs
index 547158a0a..8af6f322b 100644
--- a/common/GlobalUsings_WinUI.cs
+++ b/common/GlobalUsings_WinUI.cs
@@ -1,46 +1,56 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-// This file contains directives available to projects that are compiled for multiple frameworks.
-// Learn more global using directives at https://docs.microsoft.com/dotnet/csharp/language-reference/keywords/using-directive#global-modifier
-
-global using System.Runtime.InteropServices.WindowsRuntime;
-
-global using Windows.Foundation;
-global using Windows.Foundation.Collections;
-
-#if !WINAPPSDK
-global using Windows.ApplicationModel;
-global using Windows.ApplicationModel.Activation;
-
-global using Windows.UI.Xaml.Automation;
-global using Windows.UI.Xaml.Automation.Peers;
-
-global using Windows.UI.Xaml;
-global using Windows.UI.Xaml.Controls;
-global using Windows.UI.Xaml.Controls.Primitives;
-global using Windows.UI.Xaml.Data;
-global using Windows.UI.Xaml.Input;
-global using Windows.UI.Xaml.Markup;
-global using Windows.UI.Xaml.Media;
-global using Windows.UI.Xaml.Media.Animation;
-global using Windows.UI.Xaml.Navigation;
-
-#else
-
-global using Microsoft.UI.Xaml.Automation;
-global using Microsoft.UI.Xaml.Automation.Peers;
-
-global using Microsoft.UI.Xaml;
-global using Microsoft.UI.Xaml.Controls;
-global using Microsoft.UI.Xaml.Controls.Primitives;
-global using Microsoft.UI.Xaml.Data;
-global using Microsoft.UI.Xaml.Input;
-global using Microsoft.UI.Xaml.Markup;
-global using Microsoft.UI.Xaml.Media;
-global using Microsoft.UI.Xaml.Media.Animation;
-global using Microsoft.UI.Xaml.Navigation;
-#endif
-
-global using MUXC = Microsoft.UI.Xaml.Controls;
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+// This file contains directives available to projects that are compiled for multiple frameworks.
+// Learn more global using directives at https://docs.microsoft.com/dotnet/csharp/language-reference/keywords/using-directive#global-modifier
+
+global using System.Runtime.InteropServices.WindowsRuntime;
+
+global using Windows.Foundation;
+global using Windows.Foundation.Collections;
+
+#if !WINAPPSDK
+global using Windows.ApplicationModel;
+global using Windows.ApplicationModel.Activation;
+
+global using Windows.UI.Xaml.Automation;
+global using Windows.UI.Xaml.Automation.Peers;
+
+global using Windows.UI.Xaml;
+global using Windows.UI.Xaml.Controls;
+global using Windows.UI.Xaml.Controls.Primitives;
+global using Windows.UI.Xaml.Data;
+global using Windows.UI.Xaml.Input;
+global using Windows.UI.Xaml.Markup;
+global using Windows.UI.Xaml.Media;
+global using Windows.UI.Xaml.Media.Animation;
+global using Windows.UI.Xaml.Navigation;
+
+global using Windows.UI.Composition;
+global using Windows.UI.Composition.Interactions;
+global using Windows.UI.Xaml.Hosting;
+
+#else
+
+global using Microsoft.UI.Xaml.Automation;
+global using Microsoft.UI.Xaml.Automation.Peers;
+
+global using Microsoft.UI.Xaml;
+global using Microsoft.UI.Xaml.Controls;
+global using Microsoft.UI.Xaml.Controls.Primitives;
+global using Microsoft.UI.Xaml.Data;
+global using Microsoft.UI.Xaml.Input;
+global using Microsoft.UI.Xaml.Markup;
+global using Microsoft.UI.Xaml.Media;
+global using Microsoft.UI.Xaml.Media.Animation;
+global using Microsoft.UI.Xaml.Navigation;
+
+global using Microsoft.UI.Composition;
+global using Microsoft.UI.Composition.Interactions;
+global using Microsoft.UI.Xaml.Hosting;
+global using Microsoft.UI;
+
+#endif
+
+global using MUXC = Microsoft.UI.Xaml.Controls;
\ No newline at end of file
diff --git a/common/ProjectHeads/AllComponents/Tests.Uwp/CommunityToolkit.Tests.Uwp.csproj b/common/ProjectHeads/AllComponents/Tests.Uwp/CommunityToolkit.Tests.Uwp.csproj
index 40b8ea00e..7ae118b1e 100644
--- a/common/ProjectHeads/AllComponents/Tests.Uwp/CommunityToolkit.Tests.Uwp.csproj
+++ b/common/ProjectHeads/AllComponents/Tests.Uwp/CommunityToolkit.Tests.Uwp.csproj
@@ -1,7 +1,6 @@
-
true
@@ -24,21 +23,18 @@
-
{FD78002E-C4E6-4BF8-9EC3-C06250DFEF34}
CommunityToolkit.Tests
CommunityToolkit.Tests.Uwp
$(VisualStudioVersion)
-
Designer
-
-
+
\ No newline at end of file
diff --git a/common/ProjectHeads/AllComponents/Uwp/CommunityToolkit.App.Uwp.csproj b/common/ProjectHeads/AllComponents/Uwp/CommunityToolkit.App.Uwp.csproj
index cced3a617..79a071224 100644
--- a/common/ProjectHeads/AllComponents/Uwp/CommunityToolkit.App.Uwp.csproj
+++ b/common/ProjectHeads/AllComponents/Uwp/CommunityToolkit.App.Uwp.csproj
@@ -1,7 +1,6 @@
-
true
@@ -24,7 +23,6 @@
-
CommunityToolkit.App.Uwp
CommunityToolkit.App.Uwp
diff --git a/components/CompositionCollectionView/OpenSolution.bat b/components/CompositionCollectionView/OpenSolution.bat
new file mode 100644
index 000000000..24dc1cb49
--- /dev/null
+++ b/components/CompositionCollectionView/OpenSolution.bat
@@ -0,0 +1,3 @@
+@ECHO OFF
+
+powershell ..\..\common\ProjectHeads\GenerateSingleSampleHeads.ps1 -componentPath %CD% %*
\ No newline at end of file
diff --git a/components/CompositionCollectionView/samples/Assets/ceiling.bmp b/components/CompositionCollectionView/samples/Assets/ceiling.bmp
new file mode 100644
index 000000000..934b0ceb0
Binary files /dev/null and b/components/CompositionCollectionView/samples/Assets/ceiling.bmp differ
diff --git a/components/CompositionCollectionView/samples/Assets/face.png b/components/CompositionCollectionView/samples/Assets/face.png
new file mode 100644
index 000000000..eba7cb2df
Binary files /dev/null and b/components/CompositionCollectionView/samples/Assets/face.png differ
diff --git a/components/CompositionCollectionView/samples/Assets/floor.bmp b/components/CompositionCollectionView/samples/Assets/floor.bmp
new file mode 100644
index 000000000..88181cc41
Binary files /dev/null and b/components/CompositionCollectionView/samples/Assets/floor.bmp differ
diff --git a/components/CompositionCollectionView/samples/Assets/wall.bmp b/components/CompositionCollectionView/samples/Assets/wall.bmp
new file mode 100644
index 000000000..491ddc977
Binary files /dev/null and b/components/CompositionCollectionView/samples/Assets/wall.bmp differ
diff --git a/components/CompositionCollectionView/samples/BasicSample.xaml b/components/CompositionCollectionView/samples/BasicSample.xaml
new file mode 100644
index 000000000..e4714e638
--- /dev/null
+++ b/components/CompositionCollectionView/samples/BasicSample.xaml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
diff --git a/components/CompositionCollectionView/samples/BasicSample.xaml.cs b/components/CompositionCollectionView/samples/BasicSample.xaml.cs
new file mode 100644
index 000000000..eeda87525
--- /dev/null
+++ b/components/CompositionCollectionView/samples/BasicSample.xaml.cs
@@ -0,0 +1,83 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+using CommunityToolkit.Labs.WinUI;
+using Microsoft.Toolkit.Uwp.UI.Animations.ExpressionsFork;
+using System.Xml.Linq;
+
+#if !WINAPPSDK
+using Windows.Foundation;
+using Windows.Foundation.Collections;
+using Windows.UI.Composition;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Controls.Primitives;
+using Windows.UI.Xaml.Data;
+using Windows.UI.Xaml.Input;
+using Windows.UI.Xaml.Media;
+using Windows.UI.Xaml.Navigation;
+using Windows.UI.Xaml.Shapes;
+#else
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Controls.Primitives;
+using Microsoft.UI.Xaml.Data;
+using Microsoft.UI.Xaml.Input;
+using Microsoft.UI.Xaml.Media;
+using Microsoft.UI.Xaml.Navigation;
+#endif
+
+namespace CompositionCollectionView.Sample
+{
+ [ToolkitSample(id: nameof(BasicSample), "Simple layout", description: "Displaying elements in a simple layout.")]
+ public sealed partial class BasicSample : Page
+ {
+ public BasicSample()
+ {
+ this.InitializeComponent();
+
+ var elements = new Dictionary()
+ {
+ { 0, null },
+ { 1, null },
+ { 2, null },
+ { 3, null },
+ { 4, null }
+ };
+ #if !WINAPPSDK
+
+ var layout = new SampleLayout((id) =>
+ new Rectangle()
+ {
+ Width = 100,
+ Height = 100,
+ Fill = new SolidColorBrush(Windows.UI.Colors.CornflowerBlue)
+ });
+ compositionCollectionView.SetLayout(layout);
+ compositionCollectionView.UpdateSource(elements);
+
+ addButton.Click += (object sender, RoutedEventArgs e) =>
+ {
+ elements.Add((uint)elements.Count, null);
+ compositionCollectionView.UpdateSource(elements);
+ };
+ #endif
+ }
+
+#if !WINAPPSDK
+ public class SampleLayout : CompositionCollectionLayout
+ {
+ public SampleLayout(Func elementFactory) : base(elementFactory)
+ {
+ }
+
+ public override Vector3Node GetElementPositionNode(ElementReference element)
+ {
+ return ExpressionFunctions.Vector3(element.Id * 120, 0, 0);
+ }
+
+ public override ScalarNode GetElementScaleNode(ElementReference element) => 1;
+ }
+#endif
+ }
+}
diff --git a/components/CompositionCollectionView/samples/CanvasSample.cs b/components/CompositionCollectionView/samples/CanvasSample.cs
new file mode 100644
index 000000000..4c48a9451
--- /dev/null
+++ b/components/CompositionCollectionView/samples/CanvasSample.cs
@@ -0,0 +1,97 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+using CommunityToolkit.Labs.WinUI;
+using Microsoft.Toolkit.Uwp.UI.Animations.ExpressionsFork;
+using System.Numerics;
+using System.Xml.Linq;
+
+#if !WINAPPSDK
+using Windows.Foundation;
+using Windows.Foundation.Collections;
+using Windows.UI;
+using Windows.UI.Composition;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Controls.Primitives;
+using Windows.UI.Xaml.Data;
+using Windows.UI.Xaml.Input;
+using Windows.UI.Xaml.Media;
+using Windows.UI.Xaml.Navigation;
+using Windows.UI.Xaml.Shapes;
+#else
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Controls.Primitives;
+using Microsoft.UI.Xaml.Data;
+using Microsoft.UI.Xaml.Input;
+using Microsoft.UI.Xaml.Media;
+using Microsoft.UI.Xaml.Navigation;
+#endif
+
+namespace CompositionCollectionView.Sample
+{
+ [ToolkitSample(id: nameof(CanvasSample), "Canvas layout", description: "Displays elements in a 2d canvas, animating between updates.")]
+ public sealed partial class CanvasSample : Page
+ {
+ public CanvasSample()
+ {
+ this.InitializeComponent();
+
+#if !WINAPPSDK
+ var elements = new Dictionary()
+ {
+ { 0, Vector2.Zero },
+ { 1, Vector2.Zero },
+ { 2, Vector2.Zero },
+ { 3, Vector2.Zero },
+ { 4, Vector2.Zero }
+ };
+
+ var layout = new CanvasLayout((id) =>
+ new Rectangle()
+ {
+ Width = 100,
+ Height = 100,
+ Fill = new SolidColorBrush(Windows.UI.Colors.CornflowerBlue),
+ Stroke = new SolidColorBrush(Colors.Gray),
+ StrokeThickness = 1
+ });
+
+ compositionCollectionView.SetLayout(layout);
+ compositionCollectionView.UpdateSource(elements);
+
+ rearrangeButton.Click += (object sender, RoutedEventArgs e) =>
+ {
+ var rnd = new Random();
+
+ for(uint i =0; i< elements.Count; i++)
+ {
+ elements[i] = new Vector2(
+ (float)(rnd.NextDouble() * compositionCollectionView.ActualWidth),
+ (float)(rnd.NextDouble() * compositionCollectionView.ActualHeight));
+ }
+ compositionCollectionView.UpdateSource(elements);
+ };
+#endif
+ }
+
+#if !WINAPPSDK
+ public class CanvasLayout : CompositionCollectionLayout
+ {
+ public CanvasLayout(Func elementFactory) : base(elementFactory)
+ {
+ }
+
+ public override Vector3Node GetElementPositionNode(ElementReference element)
+ {
+ return new Vector3(element.Model.X, element.Model.Y, 0);
+ }
+
+ protected override ElementTransition GetElementTransitionEasingFunction(ElementReference element) =>
+ new(200,
+ Window.Current.Compositor.CreateCubicBezierEasingFunction(new Vector2(0.25f, 0.1f), new Vector2(0.25f, 1f)));
+ }
+#endif
+ }
+}
diff --git a/components/CompositionCollectionView/samples/CanvasSample.xaml b/components/CompositionCollectionView/samples/CanvasSample.xaml
new file mode 100644
index 000000000..e575a31a6
--- /dev/null
+++ b/components/CompositionCollectionView/samples/CanvasSample.xaml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
diff --git a/components/CompositionCollectionView/samples/CompositionCollectionView.Samples.csproj b/components/CompositionCollectionView/samples/CompositionCollectionView.Samples.csproj
new file mode 100644
index 000000000..d0930b55b
--- /dev/null
+++ b/components/CompositionCollectionView/samples/CompositionCollectionView.Samples.csproj
@@ -0,0 +1,15 @@
+
+
+ CompositionCollectionView
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/CompositionCollectionView/samples/CompositionCollectionView.md b/components/CompositionCollectionView/samples/CompositionCollectionView.md
new file mode 100644
index 000000000..a395862b2
--- /dev/null
+++ b/components/CompositionCollectionView/samples/CompositionCollectionView.md
@@ -0,0 +1,53 @@
+---
+title: CompositionCollectionView
+author: arcadiogarcia
+description: A composition-driven collection view with fully customizable look and behavior
+keywords: CompositionCollectionView, Control, Layout
+dev_langs:
+ - csharp
+category: Controls
+subcategory: Layout
+discussion-id: 0
+issue-id: 135
+---
+
+
+
+
+
+
+
+# CompositionCollectionView
+
+For more information about this experiment see:
+- Discussion: https://github.com/CommunityToolkit/WindowsCommunityToolkit/discussions/4565
+- Issue: https://github.com/CommunityToolkit/Labs-Windows/issues/135
+
+CompositionCollectionView is a control that can host and position a collection of elements using developer-defined layouts based on the composition animation APIs
+This facilitates the task of creating complex, performant layouts that are integrated with touch gestures, allowing to reuse logic across them.
+
+A layout can be as simple as this one, which calculates an static position for each element according to its properties:
+
+> [!SAMPLE BasicSample]
+
+The CompositionCollectionView can automatically animate the elements as the source is updated, the layouts only need to provide timing and easing information:
+
+> [!SAMPLE CanvasSample]
+
+While a CompositionCollectionView is only using one layout at a time, it can transition to any other layout on demand, implicitly animating the elements if desired.
+
+This sample shows how easy it is to transition between two unrelated layouts:
+
+> [!SAMPLE SwitchLayoutsSample]
+
+Behaviors can be used to define interactions, gestures or any other custom logic that might be shared across layouts. The built in InteractionTrackerGesture simplifies the task of integrating touch into a layout and defining layout-independent touch gestures.
+
+This sample shows a scrollable list of elements that follow a custom path:
+
+> [!SAMPLE InteractionTrackerSample]
+
+Layouts can be as complicated as you want, your creativity is the limit!
+
+This sample is inspired by the classic Maze 3d screensaver. The user can move around the maze with touch gestures (collisions with the inner walls are ommited) and will transition to an overhead view when they touch the smiley face.
+
+> [!SAMPLE MazeSample]
diff --git a/components/CompositionCollectionView/samples/Dependencies.props b/components/CompositionCollectionView/samples/Dependencies.props
new file mode 100644
index 000000000..e622e1df4
--- /dev/null
+++ b/components/CompositionCollectionView/samples/Dependencies.props
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/CompositionCollectionView/samples/InteractionTrackerSample.xaml b/components/CompositionCollectionView/samples/InteractionTrackerSample.xaml
new file mode 100644
index 000000000..35d63aa33
--- /dev/null
+++ b/components/CompositionCollectionView/samples/InteractionTrackerSample.xaml
@@ -0,0 +1,13 @@
+
+
+
diff --git a/components/CompositionCollectionView/samples/InteractionTrackerSample.xaml.cs b/components/CompositionCollectionView/samples/InteractionTrackerSample.xaml.cs
new file mode 100644
index 000000000..8f4e3ba78
--- /dev/null
+++ b/components/CompositionCollectionView/samples/InteractionTrackerSample.xaml.cs
@@ -0,0 +1,154 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+using CommunityToolkit.Labs.WinUI;
+using Microsoft.Toolkit.Uwp.UI.Animations.ExpressionsFork;
+using System.Numerics;
+
+#if !WINAPPSDK
+using Windows.Foundation;
+using Windows.Foundation.Collections;
+using Windows.UI;
+using Windows.UI.Composition;
+using Windows.UI.Composition.Interactions;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Controls.Primitives;
+using Windows.UI.Xaml.Data;
+using Windows.UI.Xaml.Hosting;
+using Windows.UI.Xaml.Input;
+using Windows.UI.Xaml.Media;
+using Windows.UI.Xaml.Navigation;
+using Windows.UI.Xaml.Shapes;
+#else
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Controls.Primitives;
+using Microsoft.UI.Xaml.Data;
+using Microsoft.UI.Xaml.Input;
+using Microsoft.UI.Xaml.Media;
+using Microsoft.UI.Xaml.Navigation;
+#endif
+
+
+namespace CompositionCollectionView.Sample
+{
+ [ToolkitSample(id: nameof(InteractionTrackerSample), "Interaction tracker layout", description: "Layout driven by an interaction tracker.")]
+ public sealed partial class InteractionTrackerSample : Page
+ {
+ const int ElementWidth = 100;
+
+ public InteractionTrackerSample()
+ {
+ this.InitializeComponent();
+
+#if !WINAPPSDK
+ Dictionary elements = new()
+ {
+ { 0, null },
+ { 1, null },
+ { 2, null },
+ { 3, null },
+ { 4, null }
+ };
+
+ var layout = new LinearLayout((id) =>
+ new Rectangle()
+ {
+ Width = ElementWidth,
+ Height = ElementWidth,
+ Fill = new SolidColorBrush(Colors.CornflowerBlue),
+ Stroke = new SolidColorBrush(Colors.Gray),
+ StrokeThickness = 1
+ });
+
+ compositionCollectionView.SetLayout(layout);
+ compositionCollectionView.UpdateSource(elements);
+#endif
+ }
+#if !WINAPPSDK
+ public class LinearLayout : CompositionCollectionLayout
+ {
+ public LinearLayout(Func elementFactory) : base(elementFactory)
+ {
+ }
+
+ protected override void OnActivated()
+ {
+ if (TryGetBehavior>() is null)
+ {
+ // Tracker can't be created until activation, we don't have access to the root panel until then
+ var trackerBehavior = new InteractionTrackerBehavior(RootPanel);
+ AddBehavior(trackerBehavior);
+
+ var tracker = trackerBehavior.Tracker;
+ var interactionSource = trackerBehavior.InteractionSource;
+
+ UpdateTrackerLimits();
+
+ interactionSource.ScaleSourceMode = InteractionSourceMode.Disabled;
+ interactionSource.PositionXSourceMode = InteractionSourceMode.EnabledWithInertia;
+ interactionSource.PositionYSourceMode = InteractionSourceMode.Disabled;
+ }
+
+ RootPanel.Background = new SolidColorBrush(Colors.Transparent);
+ RootPanel.PointerPressed += RootPointerPressed;
+ }
+
+ protected override void OnDeactivated()
+ {
+ RootPanel.PointerPressed -= RootPointerPressed;
+ }
+
+ void RootPointerPressed(object sender, PointerRoutedEventArgs e)
+ {
+ if (e.Pointer.PointerDeviceType == Windows.Devices.Input.PointerDeviceType.Touch)
+ {
+ var position = e.GetCurrentPoint(RootPanel);
+ GetBehavior>().InteractionSource.TryRedirectForManipulation(position);
+ }
+ }
+
+ protected override void OnElementsUpdated()
+ {
+ UpdateTrackerLimits();
+ }
+
+ private void UpdateTrackerLimits()
+ {
+ var trackerBehavior = GetBehavior>();
+
+ var availableWidth = (float)RootPanel.ActualWidth - ElementWidth;
+ var elementsWidth = Source.Count() * ElementWidth * 1.2f;
+
+ trackerBehavior.Tracker.MaxPosition = new Vector3(elementsWidth - ((float)RootPanel.ActualWidth + ElementWidth) / 2, 0, 0);
+ trackerBehavior.Tracker.MinPosition = new Vector3(-((float)RootPanel.ActualWidth - ElementWidth) / 2, 0, 0);
+ }
+
+ private ScalarNode ScrollProgress(ElementReference element)
+ {
+ var availableWidth = RootPanelVisual.GetReference().Size.X - ElementWidth;
+
+ return ExpressionFunctions.Clamp(
+ (element.Id * ElementWidth * 1.2f
+ - GetBehavior>().Tracker.GetReference().Position.X) / availableWidth,
+ 0,
+ 1);
+ }
+
+ public override Vector3Node GetElementPositionNode(ElementReference element)
+ {
+ var availableWidth = RootPanelVisual.GetReference().Size.X - ElementWidth;
+
+ var xPosition = availableWidth * ScrollProgress(element);
+
+ var yPosition = 50 * ExpressionFunctions.Sin(ScrollProgress(element) * (float)Math.PI);
+
+ return ExpressionFunctions.Vector3(xPosition, yPosition, 0);
+ }
+
+ public override ScalarNode GetElementScaleNode(ElementReference element) => 1.5f - ExpressionFunctions.Abs(0.5f - ScrollProgress(element));
+ }
+#endif
+ }
+}
diff --git a/components/CompositionCollectionView/samples/MazeSample.xaml b/components/CompositionCollectionView/samples/MazeSample.xaml
new file mode 100644
index 000000000..6320b46b0
--- /dev/null
+++ b/components/CompositionCollectionView/samples/MazeSample.xaml
@@ -0,0 +1,13 @@
+
+
+
diff --git a/components/CompositionCollectionView/samples/MazeSample.xaml.cs b/components/CompositionCollectionView/samples/MazeSample.xaml.cs
new file mode 100644
index 000000000..1a9de5217
--- /dev/null
+++ b/components/CompositionCollectionView/samples/MazeSample.xaml.cs
@@ -0,0 +1,458 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+using CommunityToolkit.Labs.WinUI;
+using Microsoft.Toolkit.Uwp.UI.Animations.ExpressionsFork;
+using System.Numerics;
+
+#if !WINAPPSDK
+using Windows.Foundation;
+using Windows.Foundation.Collections;
+using Windows.UI;
+using Windows.UI.Composition;
+using Windows.UI.Composition.Interactions;
+using Windows.UI.StartScreen;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Controls.Primitives;
+using Windows.UI.Xaml.Data;
+using Windows.UI.Xaml.Hosting;
+using Windows.UI.Xaml.Input;
+using Windows.UI.Xaml.Media;
+using Windows.UI.Xaml.Media.Imaging;
+using Windows.UI.Xaml.Navigation;
+using Windows.UI.Xaml.Shapes;
+#else
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Controls.Primitives;
+using Microsoft.UI.Xaml.Data;
+using Microsoft.UI.Xaml.Input;
+using Microsoft.UI.Xaml.Media;
+using Microsoft.UI.Xaml.Navigation;
+#endif
+
+
+namespace CompositionCollectionView.Sample
+{
+ [ToolkitSample(id: nameof(MazeSample), "Maze layout", description: "Layout driven by an interaction tracker.")]
+ public sealed partial class MazeSample : Page
+ {
+ public enum TileType { Floor, Ceiling, HorizontalWall, VerticalWall, Goal }
+
+ const int TileWidth = 100;
+ const int MazeSize = 10;
+
+ static readonly Vector3 GoalPosition = new Vector3(9, 0, 0);
+
+
+#if !WINAPPSDK
+ private Dictionary elements { get; init; }
+#endif
+
+ public MazeSample()
+ {
+ this.InitializeComponent();
+
+#if !WINAPPSDK
+ List tiles = new();
+
+ for (int i = 0; i < MazeSize; i++)
+ {
+ for (int j = 0; j < MazeSize; j++)
+ {
+ tiles.Add(new(TileType.Floor, i, j));
+ tiles.Add(new(TileType.Ceiling, i, j));
+
+ if (j == 0)
+ {
+ tiles.Add(new(TileType.HorizontalWall, i, j));
+ }
+ else if (j == MazeSize - 1)
+ {
+ tiles.Add(new(TileType.HorizontalWall, i, j + 1));
+ }
+
+ if (i == 0)
+ {
+ tiles.Add(new(TileType.VerticalWall, i, j));
+ }
+ else if (i == MazeSize - 1)
+ {
+ tiles.Add(new(TileType.VerticalWall, i + 1, j));
+ }
+ }
+ }
+
+ var maze = new Maze(MazeSize, MazeSize);
+ tiles.AddRange(maze.Walls);
+
+ tiles.Add(new(TileType.Goal, (int)GoalPosition.X, (int)GoalPosition.Y));
+
+ elements = tiles.OrderBy(x => x.Y).Select((x, i) => (index: (uint)i, element: x)).ToDictionary(x => x.index, x => x.element);
+
+ var layout = new SpinningMazeLayout((id) => TileControl.Create());
+ compositionCollectionView.SetLayout(layout);
+ compositionCollectionView.UpdateSource(elements);
+
+ Visual visual = ElementCompositionPreview.GetElementVisual(compositionCollectionView);
+ var viewSize = visual.GetReference().Size;
+
+ visual.StartAnimation(AnimationConstants.TransformMatrix,
+ ExpressionFunctions.CreateTranslation(ExpressionFunctions.Vector3(-viewSize.X / 2, -viewSize.Y / 2, 0))
+ * ExpressionFunctions.Matrix4x4(
+ 1.0f, 0.0f, 0.0f, 0.0f,
+ 0.0f, 1.0f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 1.0f, -1.0f / viewSize.X,
+ 0.0f, 0.0f, 0.0f, 1.0f) *
+ ExpressionFunctions.CreateTranslation(ExpressionFunctions.Vector3(viewSize.X / 2, viewSize.Y / 2, 0)));
+#endif
+ }
+
+ public record Tile(TileType Type, int X, int Y);
+
+#if !WINAPPSDK
+ public abstract class MazeLayout : CompositionCollectionLayout
+ {
+ protected const string PositionNode = nameof(PositionNode);
+ protected const string ScaleNode = nameof(ScaleNode);
+ protected const string RotationNode = nameof(RotationNode);
+ const string CameraTransformNode = nameof(CameraTransformNode);
+
+ public MazeLayout(CompositionCollectionLayout sourceLayout) : base(sourceLayout)
+ {
+ }
+
+ public MazeLayout(Func elementFactory) : base(elementFactory)
+ {
+ }
+
+ protected override void OnActivated()
+ {
+ var translation = ExpressionFunctions.CreateTranslation(AnimatableNodes.GetOrCreateVector3Node(PositionNode, Vector3.Zero).Reference);
+
+ var scaleNode = AnimatableNodes.GetOrCreateScalarNode(ScaleNode, 1).Reference;
+ var scale = ExpressionFunctions.CreateScale(ExpressionFunctions.Vector3(scaleNode, scaleNode, scaleNode));
+
+ var rotationNode = AnimatableNodes.GetOrCreateVector3Node(RotationNode, Vector3.Zero).Reference;
+ var rotation = ExpressionFunctions.CreateMatrix4x4FromAxisAngle(Vector3.UnitX, rotationNode.X) *
+ ExpressionFunctions.CreateMatrix4x4FromAxisAngle(Vector3.UnitY, rotationNode.Y) *
+ ExpressionFunctions.CreateMatrix4x4FromAxisAngle(Vector3.UnitZ, rotationNode.Z);
+
+ AnimatableNodes.GetOrCreateMatrix4x4Node(CameraTransformNode, Matrix4x4.Identity).Animate(translation * rotation * scale);
+
+ TileControl.LoadBrushes();
+ }
+
+
+ public override Vector3Node GetElementPositionNode(ElementReference element)
+ {
+ var xPosition = element.Model.Type switch
+ {
+ _ => element.Model.X * TileWidth
+ };
+
+ var yPosition = element.Model.Type switch
+ {
+ TileType.Goal => (element.Model.Y + 0.5f) * TileWidth,
+ _ => element.Model.Y * TileWidth
+ };
+
+ var height = element.Model.Type switch
+ {
+ TileType.Floor => TileWidth,
+ _ => 0
+ };
+
+ var camera = AnimatableNodes.GetOrCreateMatrix4x4Node(CameraTransformNode, Matrix4x4.Identity).Reference;
+
+ return ExpressionFunctions.Transform(ExpressionFunctions.Vector4(xPosition, height, yPosition, 1), camera).XYZ;
+ }
+
+ public override ScalarNode GetElementScaleNode(ElementReference element)
+ {
+ return AnimatableNodes.GetOrCreateScalarNode(ScaleNode, 1).Reference;
+ }
+
+ public override QuaternionNode GetElementOrientationNode(ElementReference element)
+ {
+ var localOrientation = element.Model.Type switch
+ {
+ TileType.Floor => Quaternion.CreateFromYawPitchRoll(0, (float)Math.PI / 2, 0),
+ TileType.Ceiling => Quaternion.CreateFromYawPitchRoll(0, (float)Math.PI / 2, 0),
+ TileType.VerticalWall => Quaternion.CreateFromYawPitchRoll((float)-Math.PI / 2, 0, 0),
+ TileType.HorizontalWall => Quaternion.Identity,
+ TileType.Goal => Quaternion.Identity,
+ _ => Quaternion.Identity
+ };
+
+ var rotationNode = AnimatableNodes.GetOrCreateVector3Node(RotationNode, Vector3.Zero).Reference;
+ var cameraOrientation = ExpressionFunctions.CreateQuaternionFromAxisAngle(Vector3.UnitX, rotationNode.X) *
+ ExpressionFunctions.CreateQuaternionFromAxisAngle(Vector3.UnitY, rotationNode.Y) *
+ ExpressionFunctions.CreateQuaternionFromAxisAngle(Vector3.UnitZ, rotationNode.Z);
+
+ return cameraOrientation * localOrientation;
+ }
+
+ protected override void ConfigureElement(ElementReference element)
+ {
+ if (element.Container is Rectangle rect)
+ {
+ rect.Fill = TileControl.BrushFor(element.Model.Type);
+ }
+ }
+
+ protected override ElementTransition GetElementTransitionEasingFunction(ElementReference element) =>
+ new(600,
+ Window.Current.Compositor.CreateCubicBezierEasingFunction(new Vector2(0.25f, 0.1f), new Vector2(0.25f, 1f)));
+ }
+
+ public class SpinningMazeLayout : MazeLayout
+ {
+ public SpinningMazeLayout(CompositionCollectionLayout sourceLayout) : base(sourceLayout)
+ {
+ }
+
+ public SpinningMazeLayout(Func elementFactory) : base(elementFactory)
+ {
+ }
+
+ protected override void OnActivated()
+ {
+ base.OnActivated();
+
+ var animation = Compositor.CreateVector3KeyFrameAnimation();
+ animation.Duration = TimeSpan.FromSeconds(5);
+ animation.InsertKeyFrame(0, Vector3.Zero);
+ animation.InsertKeyFrame(1, new Vector3(0, (float)Math.PI * 2, 0));
+ animation.IterationBehavior = AnimationIterationBehavior.Forever;
+
+ var rotationNode = AnimatableNodes.GetOrCreateVector3Node(RotationNode, Vector3.Zero);
+ rotationNode.Animate(animation);
+
+ float mazeSide = TileWidth * MazeSize;
+ var mazeCenter = new Vector3(-mazeSide / 2, 0, -mazeSide / 2);
+
+ AnimatableNodes.GetOrCreateVector3Node(PositionNode, Vector3.Zero).Animate(ExpressionFunctions.Vector3(
+ ExpressionFunctions.Sin(rotationNode.Reference.Y) * mazeSide * 1.5f,
+ mazeSide / 3,
+ -ExpressionFunctions.Cos(rotationNode.Reference.Y) * mazeSide * 1.5f
+ ) + (Vector3Node)mazeCenter);
+
+ AnimatableNodes.GetOrCreateScalarNode(ScaleNode, 1).Animate(RootPanelVisual.GetReference().Size.Y / TileWidth);
+ RootPanel.Tapped += this.OnTapped;
+ }
+
+ protected override void OnDeactivated()
+ {
+ RootPanel.Tapped -= OnTapped;
+ }
+
+ private void OnTapped(object sender, TappedRoutedEventArgs e)
+ {
+ TransitionTo(x => new TraversableMazeLayout(this));
+ }
+
+ public override ScalarNode GetElementOpacityNode(ElementReference element)
+ {
+ return element.Model.Type switch
+ {
+ TileType.Ceiling => 0.3f,
+ _ => 1
+ };
+ }
+ }
+
+
+ public class TraversableMazeLayout : MazeLayout
+ {
+ public TraversableMazeLayout(Func elementFactory) : base(elementFactory)
+ {
+ }
+
+ public TraversableMazeLayout(CompositionCollectionLayout sourceLayout) : base(sourceLayout)
+ {
+ }
+
+ protected override void OnActivated()
+ {
+ base.OnActivated();
+
+ var trackerBehavior = TryGetBehavior>();
+
+ if (trackerBehavior is null)
+ {
+ // Tracker can't be created until activation, we don't have access to the root panel until then
+ trackerBehavior = new InteractionTrackerBehavior(RootPanel);
+ AddBehavior(trackerBehavior);
+ }
+
+ var tracker = trackerBehavior.Tracker;
+ var interactionSource = trackerBehavior.InteractionSource;
+
+ UpdateTrackerLimits();
+
+ interactionSource.ScaleSourceMode = InteractionSourceMode.Disabled;
+ interactionSource.PositionXSourceMode = InteractionSourceMode.EnabledWithInertia;
+ interactionSource.PositionYSourceMode = InteractionSourceMode.EnabledWithInertia;
+
+ AnimatableNodes.GetOrCreateVector3Node(PositionNode, Vector3.Zero).Animate(
+ ExpressionFunctions.Vector3(
+ -tracker.GetReference().Position.X,
+ 0,
+ tracker.GetReference().Position.Y));
+
+ trackerBehavior.TrackerOwner.OnIdleStateEntered += this.OnTrackerIdleStateEntered;
+
+ AnimatableNodes.GetOrCreateScalarNode(ScaleNode, 1).Animate(RootPanelVisual.GetReference().Size.Y / TileWidth);
+ AnimatableNodes.GetOrCreateVector3Node(RotationNode, Vector3.Zero).Value = new Vector3(0, 0, 0);
+
+ RootPanel.Background = new SolidColorBrush(Colors.Black);
+ RootPanel.PointerPressed += RootPointerPressed;
+ }
+
+ protected override void OnDeactivated()
+ {
+ RootPanel.PointerPressed -= RootPointerPressed;
+ GetBehavior>().TrackerOwner.OnIdleStateEntered -= this.OnTrackerIdleStateEntered;
+ }
+
+ private void OnTrackerIdleStateEntered(InteractionTracker sender, InteractionTrackerIdleStateEnteredArgs args, InteractionTrackerState previousState)
+ {
+ if (Vector3.Distance(sender.Position, new Vector3(GoalPosition.X * TileWidth, -GoalPosition.Y * TileWidth, 0)) < TileWidth)
+ {
+ TransitionTo(x => new SpinningMazeLayout(this));
+ }
+ }
+
+ void RootPointerPressed(object sender, PointerRoutedEventArgs e)
+ {
+ if (e.Pointer.PointerDeviceType == Windows.Devices.Input.PointerDeviceType.Touch)
+ {
+ var position = e.GetCurrentPoint(RootPanel);
+ GetBehavior>().InteractionSource.TryRedirectForManipulation(position);
+ }
+ }
+
+ protected override void OnElementsUpdated()
+ {
+ UpdateTrackerLimits();
+ }
+
+ private void UpdateTrackerLimits()
+ {
+ var trackerBehavior = GetBehavior>();
+
+ trackerBehavior.Tracker.MaxPosition = new Vector3(TileWidth * (MazeSize - 1), 0, 0);
+ trackerBehavior.Tracker.MinPosition = new Vector3(0, -TileWidth * (MazeSize - 1) + 20, 0);
+ }
+
+ public override ScalarNode GetElementOpacityNode(ElementReference element)
+ {
+ return element.Model switch
+ {
+ { Type: TileType.Ceiling } => 1,
+ { Type: TileType.Floor } => 1,
+ { Type: TileType.Goal } => 1,
+ { Type: TileType.VerticalWall } and ({ X: 0 } or { X: MazeSize }) => 1,
+ { Type: TileType.HorizontalWall } and ({ Y: 0 } or { Y: MazeSize }) => 1,
+ _ => 0.3f
+ };
+ }
+ }
+
+ private class TileControl
+ {
+ private static ImageBrush? WallBrush, CeilingBrush, FloorBrush, GoalBrush;
+
+ public static Rectangle Create() => new Rectangle()
+ {
+ Width = TileWidth,
+ Height = TileWidth
+ };
+
+ public static void LoadBrushes()
+ {
+ WallBrush = WallBrush ?? new ImageBrush()
+ {
+ ImageSource = new BitmapImage(new Uri("ms-appx:///CompositionCollectionViewExperiment.Samples/Assets/wall.bmp"))
+ };
+
+ CeilingBrush = CeilingBrush ?? new ImageBrush()
+ {
+ ImageSource = new BitmapImage(new Uri("ms-appx:///CompositionCollectionViewExperiment.Samples/Assets/ceiling.bmp"))
+ };
+
+ FloorBrush = FloorBrush ?? new ImageBrush()
+ {
+ ImageSource = new BitmapImage(new Uri("ms-appx:///CompositionCollectionViewExperiment.Samples/Assets/floor.bmp"))
+ };
+
+ GoalBrush = GoalBrush ?? new ImageBrush()
+ {
+ ImageSource = new BitmapImage(new Uri("ms-appx:///CompositionCollectionViewExperiment.Samples/Assets/face.png"))
+ };
+ }
+
+ public static ImageBrush? BrushFor(TileType type) => type switch
+ {
+ TileType.Ceiling => CeilingBrush,
+ TileType.Floor => FloorBrush,
+ TileType.Goal => GoalBrush,
+ _ => WallBrush
+ };
+ }
+
+#endif
+
+ private class Maze
+ {
+ public IReadOnlyList Walls { get; init; }
+
+ public Maze(int width, int height)
+ {
+ Queue _pendingRooms = new();
+ _pendingRooms.Enqueue(new Rect(0, 0, width, height));
+
+ List walls = new();
+ var r = new Random();
+
+ while (_pendingRooms.Count > 0)
+ {
+ var room = _pendingRooms.Dequeue();
+ var attemptHorizontalWall = r.NextDouble() > 0.5f;
+ if (attemptHorizontalWall && room.Height > 1)
+ {
+ var wallY = 1 + Math.Floor(r.NextDouble() * room.Height);
+ var doorX = Math.Floor(r.NextDouble() * (room.Width + 1));
+ for (var x = 0; x < room.Width; x++)
+ {
+ if (x != doorX)
+ {
+ walls.Add(new(TileType.HorizontalWall, (int)(x + room.X), (int)(wallY + room.Y)));
+ }
+ }
+ _pendingRooms.Enqueue(new Rect(room.X, room.Y, room.Width, wallY));
+ _pendingRooms.Enqueue(new Rect(room.X, room.Y + wallY, room.Width, (room.Height - wallY)));
+ }
+ else if (room.Width > 1)
+ {
+ var wallX = 1 + Math.Floor(r.NextDouble() * room.Width);
+ var doorY = Math.Floor(r.NextDouble() * (room.Height + 1));
+ for (var y = 0; y < room.Height; y++)
+ {
+ if (y != doorY)
+ {
+ walls.Add(new(TileType.VerticalWall, (int)(wallX + room.X), (int)(y + room.Y)));
+ }
+ }
+ _pendingRooms.Enqueue(new Rect(room.X, room.Y, wallX, room.Height));
+ _pendingRooms.Enqueue(new Rect(room.X + wallX, room.Y, (room.Width - wallX), room.Height));
+ }
+ }
+
+ Walls = walls;
+ }
+ }
+ }
+}
diff --git a/components/CompositionCollectionView/samples/SwitchLayoutsSample.xaml b/components/CompositionCollectionView/samples/SwitchLayoutsSample.xaml
new file mode 100644
index 000000000..7a4d7081b
--- /dev/null
+++ b/components/CompositionCollectionView/samples/SwitchLayoutsSample.xaml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
diff --git a/components/CompositionCollectionView/samples/SwitchLayoutsSample.xaml.cs b/components/CompositionCollectionView/samples/SwitchLayoutsSample.xaml.cs
new file mode 100644
index 000000000..e4bf63cfa
--- /dev/null
+++ b/components/CompositionCollectionView/samples/SwitchLayoutsSample.xaml.cs
@@ -0,0 +1,135 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+using CommunityToolkit.Labs.WinUI;
+using Microsoft.Toolkit.Uwp.UI.Animations.ExpressionsFork;
+using System.Numerics;
+
+#if !WINAPPSDK
+using Windows.Foundation;
+using Windows.Foundation.Collections;
+using Windows.UI;
+using Windows.UI.Composition;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Controls.Primitives;
+using Windows.UI.Xaml.Data;
+using Windows.UI.Xaml.Hosting;
+using Windows.UI.Xaml.Input;
+using Windows.UI.Xaml.Media;
+using Windows.UI.Xaml.Navigation;
+using Windows.UI.Xaml.Shapes;
+#else
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Controls.Primitives;
+using Microsoft.UI.Xaml.Data;
+using Microsoft.UI.Xaml.Input;
+using Microsoft.UI.Xaml.Media;
+using Microsoft.UI.Xaml.Navigation;
+#endif
+
+
+namespace CompositionCollectionView.Sample
+{
+ [ToolkitSample(id: nameof(SwitchLayoutsSample), "Layout transition", description: "Transition between different layouts.")]
+ public sealed partial class SwitchLayoutsSample : Page
+ {
+ public SwitchLayoutsSample()
+ {
+ this.InitializeComponent();
+
+#if !WINAPPSDK
+ Dictionary elements = new()
+ {
+ { 0, null },
+ { 1, null },
+ { 2, null },
+ { 3, null },
+ { 4, null }
+ };
+
+ var layout = new LinearLayout((id) =>
+ new Rectangle()
+ {
+ Width = 100,
+ Height = 100,
+ Fill = new SolidColorBrush(Windows.UI.Colors.CornflowerBlue),
+ Stroke = new SolidColorBrush(Colors.Gray),
+ StrokeThickness = 1
+ });
+ compositionCollectionView.SetLayout(layout);
+ compositionCollectionView.UpdateSource(elements);
+#endif
+ }
+
+#if !WINAPPSDK
+ public class LinearLayout : CompositionCollectionLayout
+ {
+ public LinearLayout(Func elementFactory) : base(elementFactory)
+ {
+ }
+
+ public LinearLayout(CompositionCollectionLayout sourceLayout) : base(sourceLayout)
+ {
+ }
+
+ public override Vector3Node GetElementPositionNode(ElementReference element)
+ {
+ return ExpressionFunctions.Vector3(element.Id * 120, 0, 0);
+ }
+
+ public override ScalarNode GetElementScaleNode(ElementReference element) => 1;
+
+ protected override ElementTransition GetElementTransitionEasingFunction(ElementReference element) =>
+ new(100,
+ Window.Current.Compositor.CreateCubicBezierEasingFunction(new Vector2(0.25f, 0.1f), new Vector2(0.25f, 1f)));
+ }
+
+ public class StackLayout : CompositionCollectionLayout
+ {
+ public StackLayout(Func elementFactory) : base(elementFactory)
+ {
+ }
+
+ public StackLayout(CompositionCollectionLayout sourceLayout) : base(sourceLayout)
+ {
+ }
+
+ public override Vector3Node GetElementPositionNode(ElementReference element)
+ {
+ return ExpressionFunctions.Vector3(element.Id * 10, element.Id * 10, 0);
+ }
+
+ public override ScalarNode GetElementScaleNode(ElementReference element) => (float)Math.Pow(0.95f, element.Id);
+
+ protected override ElementTransition GetElementTransitionEasingFunction(ElementReference element) =>
+ new(100,
+ Window.Current.Compositor.CreateCubicBezierEasingFunction(new Vector2(0.25f, 0.1f), new Vector2(0.25f, 1f)));
+
+ protected override void ConfigureElement(ElementReference element)
+ {
+ element.Container.SetValue(Canvas.ZIndexProperty, -(int)element.Id);
+ }
+ }
+#endif
+
+ private void ToggleSwitch_Toggled(object sender, RoutedEventArgs e)
+ {
+#if !WINAPPSDK
+ if (sender is ToggleSwitch toggle)
+ {
+ if (toggle.IsOn && compositionCollectionView.Layout() is LinearLayout currentLinearLayout)
+ {
+ currentLinearLayout.TransitionTo(_ => new StackLayout(currentLinearLayout));
+ }
+ else if (!toggle.IsOn && compositionCollectionView.Layout() is StackLayout currentStackLayout)
+ {
+ currentStackLayout.TransitionTo(_ => new LinearLayout(currentStackLayout));
+ }
+ }
+#endif
+
+ }
+ }
+}
diff --git a/components/CompositionCollectionView/src/AdditionalAssemblyInfo.cs b/components/CompositionCollectionView/src/AdditionalAssemblyInfo.cs
new file mode 100644
index 000000000..5ba7ccac3
--- /dev/null
+++ b/components/CompositionCollectionView/src/AdditionalAssemblyInfo.cs
@@ -0,0 +1,13 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.CompilerServices;
+
+// These `InternalsVisibleTo` calls are intended to make it easier for
+// for any internal code to be testable in all the different test projects
+// used with the Labs infrastructure.
+[assembly: InternalsVisibleTo("CompositionCollectionView.Tests.Uwp")]
+[assembly: InternalsVisibleTo("CompositionCollectionView.Tests.WinAppSdk")]
+[assembly: InternalsVisibleTo("CommunityToolkit.Labs.Tests.Uwp")]
+[assembly: InternalsVisibleTo("CommunityToolkit.Labs.Tests.WinAppSdk")]
diff --git a/components/CompositionCollectionView/src/AnimatableNodes/AnimatableCompositionNodeSet.cs b/components/CompositionCollectionView/src/AnimatableNodes/AnimatableCompositionNodeSet.cs
new file mode 100644
index 000000000..a11d7e910
--- /dev/null
+++ b/components/CompositionCollectionView/src/AnimatableNodes/AnimatableCompositionNodeSet.cs
@@ -0,0 +1,144 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+#nullable enable
+using System.Numerics;
+
+namespace CommunityToolkit.Labs.WinUI;
+public class AnimatableCompositionNodeSet : IDisposable
+{
+ private Dictionary _nodes = new();
+ private Compositor _compositor;
+
+ private bool disposedValue;
+
+ public AnimatableCompositionNodeSet(Compositor compositor)
+ {
+ _compositor = compositor;
+ }
+
+ public AnimatableScalarCompositionNode GetOrCreateScalarNode(string id, float defaultValue)
+ {
+ if (_nodes.ContainsKey(id))
+ {
+ if (_nodes[id] is AnimatableScalarCompositionNode node)
+ {
+ return node;
+ }
+ else
+ {
+ throw new InvalidCastException("Node {id} is not a scalar node");
+ }
+ }
+ else
+ {
+ var newNode = new AnimatableScalarCompositionNode(_compositor);
+ newNode.Value = defaultValue;
+ _nodes[id] = newNode;
+ return newNode;
+ }
+ }
+
+ public AnimatableVector3CompositionNode GetOrCreateVector3Node(string id, Vector3 defaultValue)
+ {
+ if (_nodes.ContainsKey(id))
+ {
+ if (_nodes[id] is AnimatableVector3CompositionNode node)
+ {
+ return node;
+ }
+ else
+ {
+ throw new InvalidCastException("Node {id} is not a vector3 node");
+ }
+ }
+ else
+ {
+ var newNode = new AnimatableVector3CompositionNode(_compositor);
+ newNode.Value = defaultValue;
+ _nodes[id] = newNode;
+ return newNode;
+ }
+ }
+
+
+ public AnimatableQuaternionCompositionNode GetOrCreateQuaternionNode(string id, Quaternion defaultValue)
+ {
+ if (_nodes.ContainsKey(id))
+ {
+ if (_nodes[id] is AnimatableQuaternionCompositionNode node)
+ {
+ return node;
+ }
+ else
+ {
+ throw new InvalidCastException("Node {id} is not a vector3 node");
+ }
+ }
+ else
+ {
+ var newNode = new AnimatableQuaternionCompositionNode(_compositor);
+ newNode.Value = defaultValue;
+ _nodes[id] = newNode;
+ return newNode;
+ }
+ }
+
+ public AnimatableMatrix4x4CompositionNode GetOrCreateMatrix4x4Node(string id, Matrix4x4 defaultValue)
+ {
+ if (_nodes.ContainsKey(id))
+ {
+ if (_nodes[id] is AnimatableMatrix4x4CompositionNode node)
+ {
+ return node;
+ }
+ else
+ {
+ throw new InvalidCastException("Node {id} is not a matrix4x4 node");
+ }
+ }
+ else
+ {
+ var newNode = new AnimatableMatrix4x4CompositionNode(_compositor);
+ newNode.Value = defaultValue;
+ _nodes[id] = newNode;
+ return newNode;
+ }
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!disposedValue)
+ {
+ if (disposing)
+ {
+ // TODO: dispose managed state (managed objects)
+ foreach (var node in _nodes)
+ {
+ if (node.Value is IDisposable disposable)
+ {
+ disposable.Dispose();
+ }
+ }
+ }
+
+ // TODO: free unmanaged resources (unmanaged objects) and override finalizer
+ // TODO: set large fields to null
+ disposedValue = true;
+ }
+ }
+
+ // // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
+ // ~AnimatableScalarCompositionNode()
+ // {
+ // // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
+ // Dispose(disposing: false);
+ // }
+
+ public void Dispose()
+ {
+ // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
+ Dispose(disposing: true);
+ GC.SuppressFinalize(this);
+ }
+}
diff --git a/components/CompositionCollectionView/src/AnimatableNodes/AnimatableMatrix4x4CompositionNode.cs b/components/CompositionCollectionView/src/AnimatableNodes/AnimatableMatrix4x4CompositionNode.cs
new file mode 100644
index 000000000..405fbfbdf
--- /dev/null
+++ b/components/CompositionCollectionView/src/AnimatableNodes/AnimatableMatrix4x4CompositionNode.cs
@@ -0,0 +1,89 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#nullable enable
+using System.Numerics;
+using static CommunityToolkit.Labs.WinUI.AnimationConstants;
+
+namespace CommunityToolkit.Labs.WinUI;
+public class AnimatableMatrix4x4CompositionNode : IDisposable
+{
+ private Visual _underlyingVisual;
+ private bool disposedValue;
+ private Matrix4x4Node? _currentAnimationNode = null;
+
+ public Matrix4x4 Value
+ {
+ get
+ {
+ if (_currentAnimationNode is not null)
+ {
+ // When the node value is being driven by a ongoing scalarnode animation, reading the property might return a stale value,
+ // so we instead default to evaluating the original expression to get the most accurate value
+ return _currentAnimationNode.Evaluate();
+ }
+ else
+ {
+ return ComposerValue;
+ }
+ }
+ set
+ {
+ _underlyingVisual.TransformMatrix = value;
+ _currentAnimationNode = null;
+ }
+ }
+
+ public Matrix4x4 ComposerValue => _underlyingVisual.TransformMatrix;
+
+ public AnimatableMatrix4x4CompositionNode(Compositor compositor)
+ {
+ _underlyingVisual = compositor.CreateShapeVisual();
+ }
+
+ public void Animate(CompositionAnimation animation)
+ {
+ _currentAnimationNode = null;
+ _underlyingVisual.StartAnimation(TransformMatrix, animation);
+ }
+
+ public void Animate(Matrix4x4Node animation)
+ {
+ _currentAnimationNode = animation;
+ _underlyingVisual.StartAnimation(TransformMatrix, animation);
+ }
+
+ public Matrix4x4Node Reference { get => _underlyingVisual.GetReference().TransformMatrix; }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!disposedValue)
+ {
+ if (disposing)
+ {
+ // TODO: dispose managed state (managed objects)
+ _underlyingVisual.Dispose();
+ _currentAnimationNode?.Dispose();
+ }
+
+ // TODO: free unmanaged resources (unmanaged objects) and override finalizer
+ // TODO: set large fields to null
+ disposedValue = true;
+ }
+ }
+
+ // // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
+ // ~AnimatableScalarCompositionNode()
+ // {
+ // // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
+ // Dispose(disposing: false);
+ // }
+
+ public void Dispose()
+ {
+ // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
+ Dispose(disposing: true);
+ GC.SuppressFinalize(this);
+ }
+}
diff --git a/components/CompositionCollectionView/src/AnimatableNodes/AnimatableQuaternionCompositionNode.cs b/components/CompositionCollectionView/src/AnimatableNodes/AnimatableQuaternionCompositionNode.cs
new file mode 100644
index 000000000..356531644
--- /dev/null
+++ b/components/CompositionCollectionView/src/AnimatableNodes/AnimatableQuaternionCompositionNode.cs
@@ -0,0 +1,88 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#nullable enable
+using System.Numerics;
+
+namespace CommunityToolkit.Labs.WinUI;
+public class AnimatableQuaternionCompositionNode : IDisposable
+{
+ private Visual _underlyingVisual;
+ private bool disposedValue;
+ private QuaternionNode? _currentAnimationNode = null;
+
+ public Quaternion Value
+ {
+ get
+ {
+ if (_currentAnimationNode is not null)
+ {
+ // When the node value is being driven by a ongoing scalarnode animation, reading the property might return a stale value,
+ // so we instead default to evaluating the original expression to get the most accurate value
+ return _currentAnimationNode.Evaluate();
+ }
+ else
+ {
+ return ComposerValue;
+ }
+ }
+ set
+ {
+ _underlyingVisual.Orientation = value;
+ _currentAnimationNode = null;
+ }
+ }
+
+ public Quaternion ComposerValue => _underlyingVisual.Orientation;
+
+ public AnimatableQuaternionCompositionNode(Compositor compositor)
+ {
+ _underlyingVisual = compositor.CreateShapeVisual();
+ }
+
+ public void Animate(CompositionAnimation animation)
+ {
+ _currentAnimationNode = null;
+ _underlyingVisual.StartAnimation(AnimationConstants.Orientation, animation);
+ }
+
+ public void Animate(QuaternionNode animation)
+ {
+ _currentAnimationNode = animation;
+ _underlyingVisual.StartAnimation(AnimationConstants.Orientation, animation);
+ }
+
+ public QuaternionNode Reference { get => _underlyingVisual.GetReference().Orientation; }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!disposedValue)
+ {
+ if (disposing)
+ {
+ // TODO: dispose managed state (managed objects)
+ _underlyingVisual.Dispose();
+ _currentAnimationNode?.Dispose();
+ }
+
+ // TODO: free unmanaged resources (unmanaged objects) and override finalizer
+ // TODO: set large fields to null
+ disposedValue = true;
+ }
+ }
+
+ // // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
+ // ~AnimatableScalarCompositionNode()
+ // {
+ // // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
+ // Dispose(disposing: false);
+ // }
+
+ public void Dispose()
+ {
+ // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
+ Dispose(disposing: true);
+ GC.SuppressFinalize(this);
+ }
+}
diff --git a/components/CompositionCollectionView/src/AnimatableNodes/AnimatableScalarCompositionNode.cs b/components/CompositionCollectionView/src/AnimatableNodes/AnimatableScalarCompositionNode.cs
new file mode 100644
index 000000000..8a9dbb935
--- /dev/null
+++ b/components/CompositionCollectionView/src/AnimatableNodes/AnimatableScalarCompositionNode.cs
@@ -0,0 +1,91 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#nullable enable
+using System.Numerics;
+using static CommunityToolkit.Labs.WinUI.AnimationConstants;
+
+
+namespace CommunityToolkit.Labs.WinUI;
+
+public class AnimatableScalarCompositionNode : IDisposable
+{
+ private Visual _underlyingVisual;
+ private bool disposedValue;
+ private ScalarNode? _currentAnimationNode = null;
+
+ public float Value
+ {
+ get
+ {
+ if (_currentAnimationNode is not null)
+ {
+ // When the node value is being driven by a ongoing scalarnode animation, reading the property might return a stale value,
+ // so we instead default to evaluating the original expression to get the most accurate value
+ return _currentAnimationNode.Evaluate();
+ }
+ else
+ {
+ return ComposerValue;
+ }
+ }
+ set
+ {
+ _underlyingVisual.Offset = new Vector3(value, 0, 0);
+ _currentAnimationNode = null;
+ }
+ }
+
+ public float ComposerValue => _underlyingVisual.Offset.X;
+
+ public AnimatableScalarCompositionNode(Compositor compositor)
+ {
+ _underlyingVisual = compositor.CreateShapeVisual();
+ }
+
+ public void Animate(CompositionAnimation animation)
+ {
+ _currentAnimationNode = null;
+ _underlyingVisual.StartAnimation(Offset.X, animation);
+ }
+
+ public void Animate(ScalarNode animation)
+ {
+ _currentAnimationNode = animation;
+ _underlyingVisual.StartAnimation(Offset.X, animation);
+ }
+
+ public ScalarNode Reference { get => _underlyingVisual.GetReference().Offset.X; }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!disposedValue)
+ {
+ if (disposing)
+ {
+ // TODO: dispose managed state (managed objects)
+ _underlyingVisual.Dispose();
+ _currentAnimationNode?.Dispose();
+ }
+
+ // TODO: free unmanaged resources (unmanaged objects) and override finalizer
+ // TODO: set large fields to null
+ disposedValue = true;
+ }
+ }
+
+ // // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
+ // ~AnimatableScalarCompositionNode()
+ // {
+ // // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
+ // Dispose(disposing: false);
+ // }
+
+ public void Dispose()
+ {
+ // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
+ Dispose(disposing: true);
+ GC.SuppressFinalize(this);
+ }
+}
diff --git a/components/CompositionCollectionView/src/AnimatableNodes/AnimatableVector3CompositionNode.cs b/components/CompositionCollectionView/src/AnimatableNodes/AnimatableVector3CompositionNode.cs
new file mode 100644
index 000000000..7a0888503
--- /dev/null
+++ b/components/CompositionCollectionView/src/AnimatableNodes/AnimatableVector3CompositionNode.cs
@@ -0,0 +1,89 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#nullable enable
+using System.Numerics;
+using static CommunityToolkit.Labs.WinUI.AnimationConstants;
+
+namespace CommunityToolkit.Labs.WinUI;
+public class AnimatableVector3CompositionNode : IDisposable
+{
+ private Visual _underlyingVisual;
+ private bool disposedValue;
+ private Vector3Node? _currentAnimationNode = null;
+
+ public Vector3 Value
+ {
+ get
+ {
+ if (_currentAnimationNode is not null)
+ {
+ // When the node value is being driven by a ongoing scalarnode animation, reading the property might return a stale value,
+ // so we instead default to evaluating the original expression to get the most accurate value
+ return _currentAnimationNode.Evaluate();
+ }
+ else
+ {
+ return ComposerValue;
+ }
+ }
+ set
+ {
+ _underlyingVisual.Offset = value;
+ _currentAnimationNode = null;
+ }
+ }
+
+ public Vector3 ComposerValue => _underlyingVisual.Offset;
+
+ public AnimatableVector3CompositionNode(Compositor compositor)
+ {
+ _underlyingVisual = compositor.CreateShapeVisual();
+ }
+
+ public void Animate(CompositionAnimation animation)
+ {
+ _currentAnimationNode = null;
+ _underlyingVisual.StartAnimation(Offset, animation);
+ }
+
+ public void Animate(Vector3Node animation)
+ {
+ _currentAnimationNode = animation;
+ _underlyingVisual.StartAnimation(Offset, animation);
+ }
+
+ public Vector3Node Reference { get => _underlyingVisual.GetReference().Offset; }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!disposedValue)
+ {
+ if (disposing)
+ {
+ // TODO: dispose managed state (managed objects)
+ _underlyingVisual.Dispose();
+ _currentAnimationNode?.Dispose();
+ }
+
+ // TODO: free unmanaged resources (unmanaged objects) and override finalizer
+ // TODO: set large fields to null
+ disposedValue = true;
+ }
+ }
+
+ // // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
+ // ~AnimatableScalarCompositionNode()
+ // {
+ // // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
+ // Dispose(disposing: false);
+ // }
+
+ public void Dispose()
+ {
+ // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
+ Dispose(disposing: true);
+ GC.SuppressFinalize(this);
+ }
+}
diff --git a/components/CompositionCollectionView/src/AnimationConstants.cs b/components/CompositionCollectionView/src/AnimationConstants.cs
new file mode 100644
index 000000000..6ce86827e
--- /dev/null
+++ b/components/CompositionCollectionView/src/AnimationConstants.cs
@@ -0,0 +1,58 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#nullable enable
+
+namespace CommunityToolkit.Labs.WinUI;
+public static class AnimationConstants
+{
+ // Strings for all the animatable properties of a composition visual
+ // as listed in https://docs.microsoft.com/en-us/uwp/api/windows.ui.composition.compositionobject.startanimation?view=winrt-19041
+ public static Vector2PropertyName AnchorPoint { get; } = new Vector2PropertyName(nameof(AnchorPoint));
+ public static Vector3PropertyName CenterPoint { get; } = new Vector3PropertyName(nameof(CenterPoint));
+ public static Vector3PropertyName Offset { get; } = new Vector3PropertyName(nameof(Offset));
+ public static Vector3PropertyName Translation { get; } = new Vector3PropertyName(nameof(Translation));
+ public static Vector3PropertyName Scale { get; } = new Vector3PropertyName(nameof(Scale));
+ public static string Opacity { get; } = nameof(Opacity);
+ public static Vector4PropertyName Orientation { get; } = new Vector4PropertyName(nameof(Orientation));
+ public static string RotationAngle { get; } = nameof(RotationAngle);
+ public static Vector3PropertyName RotationAxis { get; } = new Vector3PropertyName(nameof(RotationAxis));
+ public static Vector2PropertyName Size { get; } = new Vector2PropertyName(nameof(Size));
+ public static string TransformMatrix { get; } = nameof(TransformMatrix);
+
+ public class PropertyName
+ {
+ private readonly string _value;
+ public PropertyName(string value)
+ {
+ this._value = value;
+ }
+ public static implicit operator string(PropertyName PropertyName) => PropertyName._value;
+ public override string ToString() => _value;
+ }
+
+ public class Vector2PropertyName : PropertyName
+ {
+ public Vector2PropertyName(string value) : base(value)
+ {
+ }
+ public string X => this + ".X";
+ public string Y => this + ".Y";
+ }
+
+ public class Vector3PropertyName : Vector2PropertyName
+ {
+ public Vector3PropertyName(string value) : base(value)
+ {
+ }
+ public string Z => this + ".Z";
+ }
+ public class Vector4PropertyName : Vector3PropertyName
+ {
+ public Vector4PropertyName(string value) : base(value)
+ {
+ }
+ public string W => this + ".W";
+ }
+}
diff --git a/components/CompositionCollectionView/src/Behaviors/ElementInteractionTrackerBehavior.cs b/components/CompositionCollectionView/src/Behaviors/ElementInteractionTrackerBehavior.cs
new file mode 100644
index 000000000..80948a44c
--- /dev/null
+++ b/components/CompositionCollectionView/src/Behaviors/ElementInteractionTrackerBehavior.cs
@@ -0,0 +1,53 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+#nullable enable
+
+namespace CommunityToolkit.Labs.WinUI;
+public class ElementInteractionTrackerBehavior : CompositionCollectionLayoutBehavior where TId : notnull
+{
+ Dictionary> _elementTrackers = new();
+
+ public override void Configure(CompositionCollectionLayout layout)
+ {
+ base.Configure(layout);
+ foreach (var tracker in _elementTrackers)
+ {
+ tracker.Value.Configure(layout);
+ }
+ }
+
+ public InteractionTrackerBehavior CreateTrackerFor(ElementReference element)
+ {
+ if (TryGetTrackerFor(element.Id, out var tracker) && tracker is not null)
+ {
+ return tracker;
+ }
+
+ tracker = new InteractionTrackerBehavior(element.Container);
+ tracker.Configure(Layout);
+ _elementTrackers[element.Id] = tracker;
+ return tracker;
+ }
+
+ public bool TryGetTrackerFor(TId id, out InteractionTrackerBehavior? tracker)
+ {
+ return _elementTrackers.TryGetValue(id, out tracker);
+ }
+
+ override public void OnActivated()
+ {
+ foreach (var tracker in _elementTrackers)
+ {
+ tracker.Value.OnActivated();
+ }
+ }
+
+ override public void OnDeactivated()
+ {
+ foreach (var tracker in _elementTrackers)
+ {
+ tracker.Value.OnDeactivated();
+ }
+ }
+}
diff --git a/components/CompositionCollectionView/src/Behaviors/InteractionTrackerBehavior.cs b/components/CompositionCollectionView/src/Behaviors/InteractionTrackerBehavior.cs
new file mode 100644
index 000000000..7db71baa6
--- /dev/null
+++ b/components/CompositionCollectionView/src/Behaviors/InteractionTrackerBehavior.cs
@@ -0,0 +1,194 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+#nullable enable
+using System.Numerics;
+
+namespace CommunityToolkit.Labs.WinUI;
+
+//The possible states of the tracker as documented in https://learn.microsoft.com/en-us/windows/windows-app-sdk/api/winrt/microsoft.ui.composition.interactions.interactiontracker?view=windows-app-sdk-1.1#interactiontracker-states-and-transitions
+public enum InteractionTrackerState { Idle, Inertia, Interacting, CustomAnimation };
+
+public class InteractionTrackerBehavior : CompositionCollectionLayoutBehavior where TId : notnull
+{
+ public VisualInteractionSource InteractionSource { get; init; }
+ public InteractionTracker Tracker { get; init; }
+ public InteractionTrackerOwner TrackerOwner { get; init; }
+
+ private List> _gestures = new List>();
+
+ public InteractionTrackerBehavior(FrameworkElement root)
+ {
+ var rootVisual = ElementCompositionPreview.GetElementVisual(root);
+
+ InteractionSource = InitializeInteractionSource();
+ TrackerOwner = new InteractionTrackerOwner();
+ Tracker = InitializeTracker();
+
+ VisualInteractionSource InitializeInteractionSource()
+ {
+ var interactionSource = VisualInteractionSource.Create(rootVisual);
+ interactionSource.ScaleSourceMode = InteractionSourceMode.EnabledWithInertia;
+ interactionSource.PositionXSourceMode = InteractionSourceMode.EnabledWithInertia;
+ interactionSource.PositionYSourceMode = InteractionSourceMode.EnabledWithInertia;
+ interactionSource.IsPositionXRailsEnabled = false;
+ interactionSource.IsPositionYRailsEnabled = false;
+ interactionSource.ManipulationRedirectionMode = VisualInteractionSourceRedirectionMode.CapableTouchpadAndPointerWheel;
+ return interactionSource;
+ }
+
+ InteractionTracker InitializeTracker()
+ {
+ var tracker = InteractionTracker.CreateWithOwner(rootVisual.Compositor, TrackerOwner);
+ tracker.InteractionSources.Add(InteractionSource);
+ return tracker;
+ }
+ }
+
+ public void AddGesture(InteractionTrackerGesture gesture)
+ {
+ if (gesture.PreviewControl is { })
+ {
+ gesture.PreviewControl.HorizontalAlignment = HorizontalAlignment.Left;
+ gesture.PreviewControl.VerticalAlignment = VerticalAlignment.Top;
+ Layout.RootPanel.Children.Add(gesture.PreviewControl);
+ gesture.Restart();
+ }
+
+ if (Layout.IsActive)
+ {
+ RegisterGestureHandlers(gesture);
+ }
+
+ _gestures.Add(gesture);
+ }
+
+ public InteractionTrackerGesture? GetGesture() where T : InteractionTrackerGesture => _gestures.OfType().FirstOrDefault();
+
+ public void RemoveGesture(InteractionTrackerGesture gesture)
+ {
+ gesture.Disable();
+ UnregisterGestureHandlers(gesture);
+
+ if (gesture.PreviewControl is { })
+ {
+ Layout.RootPanel.Children.Remove(gesture.PreviewControl);
+ }
+
+ _gestures.Remove(gesture);
+ if (gesture is IDisposable disposableGesture)
+ {
+ disposableGesture.Dispose();
+ }
+ }
+
+ private void RegisterGestureHandlers(InteractionTrackerGesture gesture)
+ {
+ TrackerOwner.OnInteractingStateEntered += gesture.InteractingStateEntered;
+ TrackerOwner.OnInertiaStateEntered += gesture.InertiaStateEntered;
+ TrackerOwner.OnValuesChanged += gesture.ValuesChanged;
+ }
+
+ private void UnregisterGestureHandlers(InteractionTrackerGesture gesture)
+ {
+ TrackerOwner.OnInteractingStateEntered -= gesture.InteractingStateEntered;
+ TrackerOwner.OnInertiaStateEntered -= gesture.InertiaStateEntered;
+ TrackerOwner.OnValuesChanged -= gesture.ValuesChanged;
+ }
+
+ public virtual void RestartGestures()
+ {
+ foreach (var gesture in _gestures)
+ {
+ gesture.Restart();
+ }
+ }
+
+ public void Disable()
+ {
+ InteractionSource.IsPositionXRailsEnabled = false;
+ InteractionSource.IsPositionYRailsEnabled = false;
+
+ InteractionSource.PositionXSourceMode = InteractionSourceMode.EnabledWithInertia;
+ InteractionSource.PositionYSourceMode = InteractionSourceMode.EnabledWithInertia;
+ Tracker.MaxPosition = new Vector3(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity);
+ Tracker.MinPosition = new Vector3(float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity);
+ }
+
+ override public void OnActivated()
+ {
+ foreach (var gesture in _gestures)
+ {
+ RegisterGestureHandlers(gesture);
+ }
+ }
+
+ override public void OnDeactivated()
+ {
+ for (var i = _gestures.Count - 1; i >= 0; i--)
+ {
+ RemoveGesture(_gestures[i]);
+ }
+ }
+
+ public class InteractionTrackerOwner : IInteractionTrackerOwner
+ {
+ public InteractionTrackerState CurrentState { get; private set; }
+
+ public delegate void CustomAnimationStateEnteredHandler(InteractionTracker sender, InteractionTrackerCustomAnimationStateEnteredArgs args, InteractionTrackerState previousState);
+ public event CustomAnimationStateEnteredHandler? OnCustomAnimationStateEntered;
+
+ public delegate void IdleStateEnteredHandler(InteractionTracker sender, InteractionTrackerIdleStateEnteredArgs args, InteractionTrackerState previousState);
+ public event IdleStateEnteredHandler? OnIdleStateEntered;
+
+ public delegate void InertiaStateEnteredHandler(InteractionTracker sender, InteractionTrackerInertiaStateEnteredArgs args, InteractionTrackerState previousState);
+ public event InertiaStateEnteredHandler? OnInertiaStateEntered;
+
+ public delegate void InteractingStateEnteredHandler(InteractionTracker sender, InteractionTrackerInteractingStateEnteredArgs args, InteractionTrackerState previousState);
+ public event InteractingStateEnteredHandler? OnInteractingStateEntered;
+
+ public delegate void RequestIgnoredHandler(InteractionTracker sender, InteractionTrackerRequestIgnoredArgs args);
+ public event RequestIgnoredHandler? OnRequestIgnored;
+
+ public delegate void ValuesChangedHandler(InteractionTracker sender, InteractionTrackerValuesChangedArgs args);
+ public event ValuesChangedHandler? OnValuesChanged;
+
+ public void CustomAnimationStateEntered(InteractionTracker sender, InteractionTrackerCustomAnimationStateEnteredArgs args)
+ {
+ OnCustomAnimationStateEntered?.Invoke(sender, args, CurrentState);
+ CurrentState = InteractionTrackerState.CustomAnimation;
+ }
+
+ public void IdleStateEntered(InteractionTracker sender, InteractionTrackerIdleStateEnteredArgs args)
+ {
+ OnIdleStateEntered?.Invoke(sender, args, CurrentState);
+ CurrentState = InteractionTrackerState.Idle;
+ }
+
+ public void InertiaStateEntered(InteractionTracker sender, InteractionTrackerInertiaStateEnteredArgs args)
+ {
+ OnInertiaStateEntered?.Invoke(sender, args, CurrentState);
+ CurrentState = InteractionTrackerState.Inertia;
+ }
+
+ public void InteractingStateEntered(InteractionTracker sender, InteractionTrackerInteractingStateEnteredArgs args)
+ {
+ OnInteractingStateEntered?.Invoke(sender, args, CurrentState);
+ CurrentState = InteractionTrackerState.Interacting;
+ }
+
+ public void RequestIgnored(InteractionTracker sender, InteractionTrackerRequestIgnoredArgs args) => OnRequestIgnored?.Invoke(sender, args);
+
+ public void ValuesChanged(InteractionTracker sender, InteractionTrackerValuesChangedArgs args) => OnValuesChanged?.Invoke(sender, args);
+
+ public void Reset()
+ {
+ OnCustomAnimationStateEntered = null;
+ OnIdleStateEntered = null;
+ OnInertiaStateEntered = null;
+ OnInteractingStateEntered = null;
+ OnRequestIgnored = null;
+ OnValuesChanged = null;
+ }
+ }
+}
diff --git a/components/CompositionCollectionView/src/Behaviors/InteractionTrackerGesture/GesturePreviewControl.cs b/components/CompositionCollectionView/src/Behaviors/InteractionTrackerGesture/GesturePreviewControl.cs
new file mode 100644
index 000000000..e1d55487d
--- /dev/null
+++ b/components/CompositionCollectionView/src/Behaviors/InteractionTrackerGesture/GesturePreviewControl.cs
@@ -0,0 +1,47 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+#nullable enable
+using Animation = CommunityToolkit.Labs.WinUI.AnimationConstants;
+
+namespace CommunityToolkit.Labs.WinUI;
+public abstract class GesturePreviewControl : UserControl
+{
+ private ScalarNode? _opacity;
+ private Matrix4x4Node? _transform;
+
+ public ScalarNode? OpacityNode
+ {
+ get => _opacity;
+ set
+ {
+ _opacity = value;
+ ResetVisualState();
+ }
+ }
+ public Matrix4x4Node? TransformNode
+ {
+ get => _transform;
+ set
+ {
+ _transform = value;
+ ResetVisualState();
+ }
+ }
+
+ public virtual void ResetVisualState()
+ {
+ var visual = ElementCompositionPreview.GetElementVisual(this);
+ if (TransformNode is { })
+ {
+ visual.StartAnimation(Animation.TransformMatrix, TransformNode);
+ }
+ if (OpacityNode is { })
+ {
+ visual.StartAnimation(Animation.Opacity, OpacityNode);
+ }
+ }
+
+ public abstract void SetPageSize(int width, int height);
+ public abstract Task StartCommandCompletedAnimation(float offset = 0);
+}
diff --git a/components/CompositionCollectionView/src/Behaviors/InteractionTrackerGesture/InteractionTrackerGesture.cs b/components/CompositionCollectionView/src/Behaviors/InteractionTrackerGesture/InteractionTrackerGesture.cs
new file mode 100644
index 000000000..b627daa19
--- /dev/null
+++ b/components/CompositionCollectionView/src/Behaviors/InteractionTrackerGesture/InteractionTrackerGesture.cs
@@ -0,0 +1,143 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+#nullable enable
+using static CommunityToolkit.Labs.WinUI.AnimationConstants;
+
+namespace CommunityToolkit.Labs.WinUI;
+public abstract class InteractionTrackerGesture
+{
+ public event EventHandler? GestureCompleted;
+
+ protected BindableCompositionPropertySet LayoutProperties { get; init; }
+ protected CompositionPropertySet GestureProperties { get; init; }
+
+ const string InteractionInProgressId = "IIP";
+ protected bool InteractionInProgress
+ {
+ get => GestureProperties.TryGetBoolean(InteractionInProgressId, out var inProgress) == CompositionGetValueStatus.Succeeded && inProgress;
+ set => GestureProperties.InsertBoolean(InteractionInProgressId, value);
+ }
+
+ protected BooleanNode InteractionInProgressReference => GestureProperties.GetReference().GetBooleanProperty(InteractionInProgressId);
+
+ const string CompletionInProgressId = "CIP";
+
+ protected bool CompletionInProgress
+ {
+ get => GestureProperties.TryGetBoolean(CompletionInProgressId, out var inProgress) == CompositionGetValueStatus.Succeeded && inProgress;
+ set => GestureProperties.InsertBoolean(CompletionInProgressId, value);
+ }
+
+ protected BooleanNode CompletionInProgressReference => GestureProperties.GetReference().GetBooleanProperty(CompletionInProgressId);
+
+ const string InertiaInProgressId = "NIP";
+
+ protected bool InertiaInProgress
+ {
+ get => GestureProperties.TryGetBoolean(InertiaInProgressId, out var inProgress) == CompositionGetValueStatus.Succeeded && inProgress;
+ set => GestureProperties.InsertBoolean(InertiaInProgressId, value);
+ }
+
+ protected BooleanNode InertiaInProgressReference => GestureProperties.GetReference().GetBooleanProperty(InertiaInProgressId);
+
+ private InteractionTrackerReferenceNode _tracker;
+
+ protected DateTime InteractionStartedTime { get; private set; }
+
+ protected InteractionTrackerGesture(Compositor compositor, InteractionTrackerReferenceNode tracker, BindableCompositionPropertySet layoutProperties)
+ {
+ LayoutProperties = layoutProperties;
+ GestureProperties = compositor.CreatePropertySet();
+ _tracker = tracker;
+ InteractionInProgress = false;
+ CompletionInProgress = false;
+ InertiaInProgress = false;
+ }
+
+ private bool _isDisabled = false;
+
+ //Disabling a gesture prevents it from processing tracker updates immediately, it can't be undone
+ public void Disable() => _isDisabled = true;
+
+ public void PauseAnimation()
+ {
+ if (PreviewControl is null) { return; }
+ var visual = ElementCompositionPreview.GetElementVisual(PreviewControl);
+ visual.StopAnimation(TransformMatrix);
+ visual.StopAnimation(Opacity);
+ }
+
+ public void Restart()
+ {
+ InteractionInProgress = false;
+ CompletionInProgress = false;
+
+ if (PreviewControl is null) { return; }
+ var visual = ElementCompositionPreview.GetElementVisual(PreviewControl);
+
+ var visibility = GetPreviewVisibility(_tracker);
+ var opacity = GetPreviewOpacity(_tracker);
+ var transform = GetPreviewTransform(_tracker);
+
+ visual.StartAnimation(TransformMatrix, transform);
+ visual.StartAnimation(Opacity, ExpressionFunctions.Conditional(visibility,
+ opacity,
+ 0));
+
+ PreviewControl?.ResetVisualState();
+ }
+
+ protected void InvokeGestureCompleted()
+ {
+ InteractionInProgress = false;
+ InertiaInProgress = false;
+ CompletionInProgress = true;
+ GestureCompleted?.Invoke(this, EventArgs.Empty);
+ }
+ public void ValuesChanged(InteractionTracker tracker, InteractionTrackerValuesChangedArgs args)
+ {
+ if (_isDisabled)
+ {
+ return;
+ }
+ OnValuesChanged(tracker, args);
+ }
+
+ public void InteractingStateEntered(InteractionTracker _, InteractionTrackerInteractingStateEnteredArgs _1, InteractionTrackerState _3)
+ {
+ InteractionInProgress = true;
+ InertiaInProgress = false;
+ InteractionStartedTime = DateTime.Now;
+ }
+
+ public void InertiaStateEntered(InteractionTracker tracker, InteractionTrackerInertiaStateEnteredArgs args, InteractionTrackerState _)
+ {
+ if (_isDisabled)
+ {
+ return;
+ }
+ InteractionInProgress = false;
+ InertiaInProgress = true;
+ OnInertiaStateEntered(tracker, args);
+ }
+
+ protected virtual void OnValuesChanged(InteractionTracker _, InteractionTrackerValuesChangedArgs _1) { }
+
+ protected virtual void OnInertiaStateEntered(InteractionTracker _, InteractionTrackerInertiaStateEnteredArgs _1) { }
+
+ protected abstract ScalarNode GetPreviewOpacity(InteractionTrackerReferenceNode tracker);
+ protected abstract BooleanNode GetPreviewVisibility(InteractionTrackerReferenceNode tracker);
+ protected abstract Matrix4x4Node GetPreviewTransform(InteractionTrackerReferenceNode tracker);
+
+ public GesturePreviewControl? PreviewControl { get; set; }
+
+}
+
+public abstract class InteractionTrackerGesture : InteractionTrackerGesture where TPanningGesturePreview : GesturePreviewControl, new()
+{
+ protected InteractionTrackerGesture(Compositor compositor, InteractionTrackerReferenceNode tracker, BindableCompositionPropertySet layoutProperties) : base(compositor, tracker, layoutProperties)
+ {
+ PreviewControl = new TPanningGesturePreview();
+ }
+}
diff --git a/components/CompositionCollectionView/src/Behaviors/LayoutBehavior.cs b/components/CompositionCollectionView/src/Behaviors/LayoutBehavior.cs
new file mode 100644
index 000000000..d76b7dbbb
--- /dev/null
+++ b/components/CompositionCollectionView/src/Behaviors/LayoutBehavior.cs
@@ -0,0 +1,28 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+#nullable enable
+
+
+namespace CommunityToolkit.Labs.WinUI;
+public abstract class CompositionCollectionLayoutBehavior where TId : notnull
+{
+ protected CompositionCollectionLayout Layout => _layout is null ? throw new InvalidOperationException("Behavior has not been added to any layout yet") : _layout;
+ private CompositionCollectionLayout? _layout = null;
+
+ public virtual void Configure(CompositionCollectionLayout layout)
+ {
+ if (_layout != layout)
+ {
+ _layout = layout;
+ OnConfigure();
+ }
+ }
+
+ virtual public void ConfigureElement(ElementReference element) { }
+ virtual public void CleanupElement(ElementReference element) { }
+
+ virtual public void OnConfigure() { }
+ virtual public void OnActivated() { }
+ virtual public void OnDeactivated() { }
+}
diff --git a/components/CompositionCollectionView/src/BindableCompositionPropertySet.cs b/components/CompositionCollectionView/src/BindableCompositionPropertySet.cs
new file mode 100644
index 000000000..9a38fba79
--- /dev/null
+++ b/components/CompositionCollectionView/src/BindableCompositionPropertySet.cs
@@ -0,0 +1,125 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+#nullable enable
+using System.Numerics;
+using Windows.UI;
+
+namespace CommunityToolkit.Labs.WinUI;
+
+public class BindableCompositionPropertySet : INotifyPropertyChanged, IDisposable
+{
+ private CompositionPropertySet _propertySet;
+ private bool disposedValue;
+
+ public event PropertyChangedEventHandler? PropertyChanged;
+
+ public BindableCompositionPropertySet(CompositionPropertySet propertySet)
+ {
+ _propertySet = propertySet;
+ }
+
+#if !WINAPPSDK
+ public void InsertColor(string propertyName, Color value)
+ {
+ _propertySet.InsertColor(propertyName, value);
+ OnPropertyChanged(propertyName);
+ }
+#endif
+
+ public void InsertMatrix3x2(string propertyName, Matrix3x2 value)
+ {
+ _propertySet.InsertMatrix3x2(propertyName, value);
+ OnPropertyChanged(propertyName);
+ }
+
+ public void InsertMatrix4x4(string propertyName, Matrix4x4 value)
+ {
+ _propertySet.InsertMatrix4x4(propertyName, value);
+ OnPropertyChanged(propertyName);
+ }
+
+ public void InsertQuaternion(string propertyName, Quaternion value)
+ {
+ _propertySet.InsertQuaternion(propertyName, value);
+ OnPropertyChanged(propertyName);
+ }
+
+ public void InsertScalar(string propertyName, float value)
+ {
+ _propertySet.InsertScalar(propertyName, value);
+ OnPropertyChanged(propertyName);
+ }
+
+ public void InsertVector2(string propertyName, Vector2 value)
+ {
+ _propertySet.InsertVector2(propertyName, value);
+ OnPropertyChanged(propertyName);
+ }
+
+ public void InsertVector3(string propertyName, Vector3 value)
+ {
+ _propertySet.InsertVector3(propertyName, value);
+ OnPropertyChanged(propertyName);
+ }
+
+ public void InsertVector4(string propertyName, Vector4 value)
+ {
+ _propertySet.InsertVector4(propertyName, value);
+ OnPropertyChanged(propertyName);
+ }
+
+ public void InsertBoolean(string propertyName, bool value)
+ {
+ _propertySet.InsertBoolean(propertyName, value);
+ OnPropertyChanged(propertyName);
+ }
+
+ private void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+
+#if !WINAPPSDK
+ public CompositionGetValueStatus TryGetColor(string propertyName, out Color value) => _propertySet.TryGetColor(propertyName, out value);
+#endif
+
+ public CompositionGetValueStatus TryGetMatrix3x2(string propertyName, out Matrix3x2 value) => _propertySet.TryGetMatrix3x2(propertyName, out value);
+ public CompositionGetValueStatus TryGetMatrix4x4(string propertyName, out Matrix4x4 value) => _propertySet.TryGetMatrix4x4(propertyName, out value);
+ public CompositionGetValueStatus TryGetQuaternion(string propertyName, out Quaternion value) => _propertySet.TryGetQuaternion(propertyName, out value);
+ public CompositionGetValueStatus TryGetScalar(string propertyName, out float value) => _propertySet.TryGetScalar(propertyName, out value);
+ public CompositionGetValueStatus TryGetVector2(string propertyName, out Vector2 value) => _propertySet.TryGetVector2(propertyName, out value);
+ public CompositionGetValueStatus TryGetVector3(string propertyName, out Vector3 value) => _propertySet.TryGetVector3(propertyName, out value);
+ public CompositionGetValueStatus TryGetVector4(string propertyName, out Vector4 value) => _propertySet.TryGetVector4(propertyName, out value);
+ public CompositionGetValueStatus TryGetBoolean(string propertyName, out bool value) => _propertySet.TryGetBoolean(propertyName, out value);
+
+ public PropertySetReferenceNode GetReference() => _propertySet.GetReference();
+
+ #region IDisposable
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!disposedValue)
+ {
+ if (disposing)
+ {
+ _propertySet.Dispose();
+ }
+
+ // TODO: free unmanaged resources (unmanaged objects) and override finalizer
+ // TODO: set large fields to null
+ disposedValue = true;
+ }
+ }
+
+ // // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
+ // ~BindableCompositionPropertySet()
+ // {
+ // // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
+ // Dispose(disposing: false);
+ // }
+
+ public void Dispose()
+ {
+ // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
+ Dispose(disposing: true);
+ GC.SuppressFinalize(this);
+ }
+ #endregion
+}
diff --git a/components/CompositionCollectionView/src/CommunityToolkit.Labs.WinUI.CompositionCollectionView.csproj b/components/CompositionCollectionView/src/CommunityToolkit.Labs.WinUI.CompositionCollectionView.csproj
new file mode 100644
index 000000000..7fcbcfbda
--- /dev/null
+++ b/components/CompositionCollectionView/src/CommunityToolkit.Labs.WinUI.CompositionCollectionView.csproj
@@ -0,0 +1,45 @@
+
+
+ CompositionCollectionView
+ This package contains CompositionCollectionView.
+ 0.0.29
+ warnings
+ True
+
+
+ CommunityToolkit.Labs.WinUI.CompositionCollectionViewRns
+
+
+
+
+
+
+ WMC1006;CS8034;Uno0001
+
+
+
+ WMC1006;CS8034;Uno0001
+
+
+
+
+
+ MSBuild:Compile
+
+
+ MSBuild:Compile
+
+
+ MSBuild:Compile
+
+
+ MSBuild:Compile
+
+
+ MSBuild:Compile
+
+
+
+
+
+
diff --git a/components/CompositionCollectionView/src/CompositionCollectionLayout.Behaviors.cs b/components/CompositionCollectionView/src/CompositionCollectionLayout.Behaviors.cs
new file mode 100644
index 000000000..f28a0ede7
--- /dev/null
+++ b/components/CompositionCollectionView/src/CompositionCollectionLayout.Behaviors.cs
@@ -0,0 +1,52 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+#nullable enable
+
+namespace CommunityToolkit.Labs.WinUI;
+public abstract partial class CompositionCollectionLayout : ILayout, IDisposable where TId : notnull
+{
+ private List> _behaviors = new();
+
+ public void AddBehavior(CompositionCollectionLayoutBehavior behavior)
+ {
+ if (IsActive)
+ {
+ behavior.Configure(this);
+ }
+
+ if (_behaviors.Contains(behavior))
+ {
+ return;
+ }
+
+ _behaviors.Add(behavior);
+ }
+
+ public T GetBehavior() where T : CompositionCollectionLayoutBehavior =>
+ _behaviors.OfType().First();
+
+ public T? TryGetBehavior() where T : CompositionCollectionLayoutBehavior =>
+ _behaviors.OfType().FirstOrDefault();
+
+ public void RemoveBehavior(CompositionCollectionLayoutBehavior behavior)
+ {
+ _behaviors.Remove(behavior);
+ }
+
+ private void ConfigureElementBehaviors(ElementReference elementReference)
+ {
+ foreach (var behavior in _behaviors)
+ {
+ behavior.ConfigureElement(elementReference);
+ }
+ }
+
+ private void CleanupElementBehaviors(ElementReference elementReference)
+ {
+ foreach (var behavior in _behaviors)
+ {
+ behavior.CleanupElement(elementReference);
+ }
+ }
+}
diff --git a/components/CompositionCollectionView/src/CompositionCollectionLayout.Overridable.cs b/components/CompositionCollectionView/src/CompositionCollectionLayout.Overridable.cs
new file mode 100644
index 000000000..927b76d9e
--- /dev/null
+++ b/components/CompositionCollectionView/src/CompositionCollectionLayout.Overridable.cs
@@ -0,0 +1,62 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+#nullable enable
+using System.Numerics;
+
+namespace CommunityToolkit.Labs.WinUI;
+public abstract partial class CompositionCollectionLayout : ILayout, IDisposable where TId : notnull
+{
+ ///
+ /// Invoked only once, when a CompositionCollectionView transitions to this layout
+ ///
+ protected virtual void OnActivated() { }
+ ///
+ /// Invoked only once, when a CompositionCollectionView transitions away from this layout
+ ///
+ protected virtual void OnDeactivated() { }
+ ///
+ /// Invoked every time the list of instantiated element is updated, when the layout is activated and in any successive source updates
+ ///
+ protected virtual void OnElementsUpdated() { }
+
+ ///
+ /// Invoked only once per element and layout, after the element is registered with the layout (might happen on activation or when created on a source update)
+ ///
+ ///
+ protected virtual void ConfigureElement(ElementReference element) { }
+ ///
+ /// Invoked only once per element and layout, after the element is unregistered from the layout (might happen on deactivation or when destroyed on a source update)
+ ///
+ protected virtual void CleanupElement(ElementReference element) { }
+
+
+
+ ///
+ /// Invoked per-element as part of a source update, before its animation has been updated.
+ /// This is where any composition property set/animation nodes can be updated
+ ///
+ ///
+ public virtual void UpdateElementData(ElementReference element) { }
+ ///
+ /// Invoked per-element as part of a source update, after its animation has been updated
+ ///
+ ///
+ public virtual void UpdateElement(ElementReference element) { }
+
+
+ public virtual Vector3Node GetElementPositionNode(ElementReference element) => Vector3.Zero;
+ public virtual ScalarNode GetElementScaleNode(ElementReference element) => 1;
+ public virtual ScalarNode GetElementOpacityNode(ElementReference element) => 1;
+ public virtual QuaternionNode GetElementOrientationNode(ElementReference element) => Quaternion.Identity;
+ protected virtual ElementTransition? GetElementTransitionEasingFunction(ElementReference element) => null;
+
+ // These methods have a default implementation evaluates the value of the node returned by the layout and should
+ // be good enough for more cases. It should only be overriden when evaluating the nodes is not always enough to determine the latest value,
+ // e.g. if the node depends on a reference to another node which is also animated through composition and returns a stale value when evaluated
+ public virtual Vector3 GetElementPositionValue(ElementReference element) => GetElementPositionNode(element).Evaluate();
+ public virtual float GetElementScaleValue(ElementReference element) => GetElementScaleNode(element).Evaluate();
+ public virtual Quaternion GetElementOrientationValue(ElementReference element) => GetElementOrientationNode(element).Evaluate();
+ public virtual float GetElementOpacityValue(ElementReference element) => GetElementOpacityNode(element).Evaluate();
+
+}
diff --git a/components/CompositionCollectionView/src/CompositionCollectionLayout.Transition.cs b/components/CompositionCollectionView/src/CompositionCollectionLayout.Transition.cs
new file mode 100644
index 000000000..6e7ccd78f
--- /dev/null
+++ b/components/CompositionCollectionView/src/CompositionCollectionLayout.Transition.cs
@@ -0,0 +1,159 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+#nullable enable
+using System.Numerics;
+using static CommunityToolkit.Labs.WinUI.AnimationConstants;
+
+namespace CommunityToolkit.Labs.WinUI;
+public abstract partial class CompositionCollectionLayout : ILayout, IDisposable where TId : notnull
+{
+ public void Activate(Panel panel)
+ {
+ var rootPanelVisual = InitializeRootContainer(panel);
+
+ _uiRoot = new(
+ panel,
+ rootPanelVisual);
+
+ Activate();
+
+ Visual InitializeRootContainer(Panel root)
+ {
+ var rootContainer = ElementCompositionPreview.GetElementVisual(root);
+ rootContainer.Size = new Vector2((float)root.ActualWidth, (float)root.ActualHeight);
+ return rootContainer;
+ }
+ }
+
+ private void Activate()
+ {
+ //The parent layout should only be accessible before we transition to the current layout,
+ //once we activate the current layout we dispose and stop referencing it
+ ParentLayout?.Dispose();
+ ParentLayout = null;
+ IsActive = true;
+
+ foreach (var behavior in _behaviors)
+ {
+ behavior.Configure(this);
+ }
+
+ OnActivated();
+
+ foreach (var behavior in _behaviors)
+ {
+ behavior.OnActivated();
+ }
+ }
+
+ private void Deactivate()
+ {
+ OnDeactivated();
+
+ IsActive = false;
+
+
+ foreach (var behavior in _behaviors)
+ {
+ behavior.OnDeactivated();
+ }
+ }
+
+ public T TransitionTo(Func, T> factory, bool animateTransition = true) where T : CompositionCollectionLayout
+ {
+ if (!IsActive)
+ {
+ throw new InvalidOperationException("TransitionTo can only be used in active layouts. You might have already transitioned away from this layout.");
+ }
+
+ Deactivate();
+
+ var newLayout = factory(this);
+
+ foreach (var behavior in _behaviors)
+ {
+ newLayout.AddBehavior(behavior);
+ }
+
+ newLayout.Activate();
+
+ TransferElements();
+
+ LayoutReplaced?.Invoke(this, newLayout, animateTransition);
+
+ return newLayout;
+
+ void TransferElements()
+ {
+ foreach (var (id, element) in _elements)
+ {
+ element.ReasignTo(newLayout);
+ newLayout._elements.Add(id, element);
+ CleanupElement(element);
+ CleanupElementBehaviors(element);
+ }
+
+ //Configure the animations after all the elements have been added to the new layout,
+ //to allow elements to depend on each other
+ foreach (var (id, element) in _elements)
+ {
+ newLayout.ConfigureElement(element);
+ newLayout.ConfigureElementBehaviors(element);
+
+ var currentPosition = GetElementPositionValue(element);
+ var currentScale = GetElementScaleValue(element);
+ var currentOrientation = GetElementOrientationValue(element);
+ var currentOpacity = GetElementOpacityValue(element);
+
+ if (animateTransition && newLayout.GetElementTransitionEasingFunction(element) is ElementTransition transition)
+ {
+ TaskCompletionSource tsc = new();
+ newLayout.StopElementAnimation(element);
+
+ var progressAnimation = Compositor.CreateScalarKeyFrameAnimation();
+ progressAnimation.Duration = TimeSpan.FromMilliseconds(transition.Length);
+ progressAnimation.StopBehavior = AnimationStopBehavior.SetToFinalValue;
+ progressAnimation.InsertKeyFrame(0, 0f, transition.EasingFunction);
+ progressAnimation.InsertKeyFrame(1, 1f, transition.EasingFunction);
+
+ var animProgressNode = new AnimatableScalarCompositionNode(Compositor);
+
+ element.Visual.StartAnimation(Offset, ExpressionFunctions.Lerp(currentPosition, newLayout.GetElementPositionNode(element), animProgressNode.Reference));
+ var scale = newLayout.GetElementScaleNode(element);
+ element.Visual.StartAnimation(Scale, ExpressionFunctions.Lerp(new Vector3(currentScale), ExpressionFunctions.Vector3(scale, scale, scale), animProgressNode.Reference));
+ element.Visual.StartAnimation(AnimationConstants.Orientation, ExpressionFunctions.Slerp(currentOrientation, newLayout.GetElementOrientationNode(element), animProgressNode.Reference));
+ element.Visual.StartAnimation(Opacity, ExpressionFunctions.Lerp(currentOpacity, newLayout.GetElementOpacityNode(element), animProgressNode.Reference));
+
+ var batch = Compositor.CreateScopedBatch(CompositionBatchTypes.Animation);
+ batch.Completed += (object _, CompositionBatchCompletedEventArgs _1) =>
+ {
+ if (newLayout._elements.ContainsKey(element.Id))
+ {
+ newLayout.ConfigureElementAnimation(element);
+ }
+ tsc.SetResult(true);
+
+ batch.Dispose();
+ animProgressNode.Dispose();
+ progressAnimation.Dispose();
+ };
+
+ animProgressNode.Animate(progressAnimation);
+
+ batch.End();
+ }
+ else
+ {
+ newLayout.ConfigureElementAnimation(element);
+ }
+
+ newLayout.UpdateElement(element);
+ }
+
+ _elements.Clear();
+
+ newLayout.OnElementsUpdated();
+ }
+ }
+}
diff --git a/components/CompositionCollectionView/src/CompositionCollectionLayout.Update.cs b/components/CompositionCollectionView/src/CompositionCollectionLayout.Update.cs
new file mode 100644
index 000000000..6e1392a22
--- /dev/null
+++ b/components/CompositionCollectionView/src/CompositionCollectionLayout.Update.cs
@@ -0,0 +1,169 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+#nullable enable
+using System.Numerics;
+using static CommunityToolkit.Labs.WinUI.AnimationConstants;
+
+
+namespace CommunityToolkit.Labs.WinUI;
+public abstract partial class CompositionCollectionLayout : ILayout, IDisposable where TId : notnull
+{
+
+ bool _isUpdatingSource = false;
+
+ private record SourceUpdate(IDictionary UpdatedElements, TaskCompletionSource TaskCompletion, bool Animated);
+ Queue _pendingSourceUpdates = new();
+
+ public async Task UpdateSource(IDictionary source, bool animate)
+ {
+ var tcs = new TaskCompletionSource();
+
+ if (!_isUpdatingSource)
+ {
+ _isUpdatingSource = true;
+ ProcessSourceUpdate(source, tcs, animate);
+ }
+ else
+ {
+ _pendingSourceUpdates.Enqueue(new SourceUpdate(source, tcs, animate));
+ }
+
+ await tcs.Task;
+ }
+
+ private HashSet _ongoingSourceUpdateAnimation = new();
+
+ public async void ProcessSourceUpdate(IDictionary updatedElements, TaskCompletionSource taskCompletion, bool animate)
+ {
+ List> elementUpdateTask = new();
+
+ HashSet processedElements = new();
+
+ foreach (var (id, element) in _elements.ToArray())
+ {
+ if (!updatedElements.ContainsKey(id))
+ {
+ DestroyElement(element, id);
+ }
+ else
+ {
+ UpdateAndTransitionElement(element, updatedElements[id]);
+ }
+ }
+ foreach (var (id, model) in updatedElements)
+ {
+ if (processedElements.Contains(id))
+ {
+ continue;
+ }
+ InstantiateElement(id, model);
+ }
+
+ OnElementsUpdated();
+
+ taskCompletion.SetResult(true);
+
+ if (elementUpdateTask.Any())
+ {
+ await Task.WhenAll(elementUpdateTask);
+ }
+
+ if (_pendingSourceUpdates.Count > 0)
+ {
+ var update = _pendingSourceUpdates.Dequeue();
+ ProcessSourceUpdate(update.UpdatedElements, update.TaskCompletion, update.Animated);
+ }
+ else
+ {
+ _isUpdatingSource = false;
+ }
+
+ void InstantiateElement(TId id, TItem item)
+ {
+ var element = ElementFactory(id);
+ RootPanel.Children.Add(element);
+
+ var elementReference = new ElementReference(id, item, element,/* source, tracker, trackerOwner,*/ this);
+ UpdateElementData(elementReference);
+ _elements[id] = elementReference;
+
+ ConfigureElement(elementReference);
+ ConfigureElementBehaviors(elementReference);
+
+ ConfigureElementAnimation(elementReference);
+ UpdateElement(elementReference);
+ }
+
+ void DestroyElement(ElementReference element, TId id)
+ {
+ CleanupElement(element);
+ CleanupElementBehaviors(element);
+ RootPanel.Children.Remove(element.Container);
+ _elements.Remove(id);
+ element.Dispose();
+ }
+
+ void UpdateAndTransitionElement(ElementReference element, TItem newData)
+ {
+ if (animate && GetElementTransitionEasingFunction(element) is ElementTransition transition)
+ {
+ var currentPosition = GetElementPositionValue(element);
+ var currentScale = GetElementScaleValue(element);
+ var currentOrientation = GetElementOrientationValue(element);
+ var currentOpacity = GetElementOpacityValue(element);
+
+ TaskCompletionSource tsc = new();
+ StopElementAnimation(element);
+
+ element.Model = newData;
+ UpdateElementData(element);
+
+ var progressAnimation = Compositor.CreateScalarKeyFrameAnimation();
+ progressAnimation.Duration = TimeSpan.FromMilliseconds(transition.Length);
+ progressAnimation.StopBehavior = AnimationStopBehavior.SetToFinalValue;
+ progressAnimation.InsertKeyFrame(0, 0f, transition.EasingFunction);
+ progressAnimation.InsertKeyFrame(1, 1f, transition.EasingFunction);
+
+ var animProgressNode = new AnimatableScalarCompositionNode(Compositor);
+ _ongoingSourceUpdateAnimation.Add(animProgressNode);
+
+ element.Visual.StartAnimation(Offset, ExpressionFunctions.Lerp(currentPosition, GetElementPositionNode(element), animProgressNode.Reference));
+ var scale = GetElementScaleNode(element);
+ element.Visual.StartAnimation(Scale, ExpressionFunctions.Lerp(new Vector3(currentScale), ExpressionFunctions.Vector3(scale, scale, scale), animProgressNode.Reference));
+ element.Visual.StartAnimation(AnimationConstants.Orientation, ExpressionFunctions.Slerp(currentOrientation, GetElementOrientationNode(element), animProgressNode.Reference));
+ element.Visual.StartAnimation(Opacity, ExpressionFunctions.Lerp(currentOpacity, GetElementOpacityNode(element), animProgressNode.Reference));
+
+ var batch = Compositor.CreateScopedBatch(CompositionBatchTypes.Animation);
+ batch.Completed += (object _, CompositionBatchCompletedEventArgs _1) =>
+ {
+ if (IsActive)
+ {
+ ConfigureElementAnimation(element);
+ }
+ tsc.SetResult(true);
+
+ batch.Dispose();
+ animProgressNode.Dispose();
+ progressAnimation.Dispose();
+
+ _ongoingSourceUpdateAnimation.Remove(animProgressNode);
+ };
+
+ animProgressNode.Animate(progressAnimation);
+
+ batch.End();
+
+ elementUpdateTask.Add(tsc.Task);
+ }
+ else
+ {
+ element.Model = newData;
+ UpdateElementData(element);
+ }
+
+ UpdateElement(element);
+ processedElements.Add(element.Id);
+ }
+ }
+}
diff --git a/components/CompositionCollectionView/src/CompositionCollectionLayout.cs b/components/CompositionCollectionView/src/CompositionCollectionLayout.cs
new file mode 100644
index 000000000..e3857c6a0
--- /dev/null
+++ b/components/CompositionCollectionView/src/CompositionCollectionLayout.cs
@@ -0,0 +1,148 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+#nullable enable
+using static CommunityToolkit.Labs.WinUI.AnimationConstants;
+
+namespace CommunityToolkit.Labs.WinUI;
+public interface ILayout
+{
+
+ public delegate void LayoutReplacedHandler(ILayout startLayout, ILayout endLayout, bool isAnimated);
+ public event LayoutReplacedHandler? LayoutReplaced;
+
+ void Activate(Panel panel);
+}
+
+public abstract partial class CompositionCollectionLayout : ILayout, IDisposable where TId : notnull
+{
+ public CompositionCollectionLayout(Func elementFactory)
+ {
+ ElementFactory = elementFactory;
+
+ var compositor = Window.Current.Compositor;
+ Properties = new BindableCompositionPropertySet(compositor.CreatePropertySet());
+ AnimatableNodes = new AnimatableCompositionNodeSet(compositor);
+ }
+
+ public CompositionCollectionLayout(CompositionCollectionLayout sourceLayout)
+ {
+ ParentLayout = sourceLayout;
+
+ ElementFactory = sourceLayout.ElementFactory;
+
+ _uiRoot = sourceLayout._uiRoot;
+ Properties = sourceLayout.Properties;
+ AnimatableNodes = sourceLayout.AnimatableNodes;
+ }
+
+ private Dictionary> _elements { get; } = new();
+ public IEnumerable Source => _elements.Keys;
+ public IEnumerable> Elements => _elements.Values;
+
+ public event ILayout.LayoutReplacedHandler? LayoutReplaced;
+ public Panel RootPanel => GetVisualProperties().RootPanel;
+ public Visual RootPanelVisual => GetVisualProperties().RootPanelVisual;
+ public Compositor Compositor => GetVisualProperties().RootPanelVisual.Compositor;
+ public BindableCompositionPropertySet Properties { private init; get; }
+ public AnimatableCompositionNodeSet AnimatableNodes { private init; get; }
+
+ public CompositionCollectionLayout? ParentLayout { get; private set; }
+
+ public bool IsActive { get; private set; } = false;
+
+ private bool disposedValue;
+
+ public ElementReference? GetElement(TId obId) =>
+ _elements.TryGetValue(obId, out var element) ? element : null;
+
+
+ // Protected properties provided for convenience when implementing layouts
+ protected ScalarNode ViewportWidthNode => RootPanelVisual.GetReference().Size.X;
+ protected ScalarNode ViewportHeightNode => RootPanelVisual.GetReference().Size.Y;
+
+
+ // Fields that we need to preserve across layouts
+ protected Func ElementFactory { get; init; }
+
+ private record UIRoot(Panel RootPanel, Visual RootPanelVisual);
+ private UIRoot? _uiRoot;
+
+ private UIRoot GetVisualProperties()
+ {
+ if (_uiRoot is null)
+ {
+ throw new InvalidOperationException("Tried to use this layout when it hasn't been activated yet.");
+ }
+ return _uiRoot;
+ }
+
+ public void ConfigureElementAnimation(ElementReference element, Visual? proxyVisual = null)
+ {
+ var visual = proxyVisual ?? element.Visual;
+
+ visual.StartAnimation(Offset, GetElementPositionNode(element));
+
+ var scale = GetElementScaleNode(element);
+ visual.StartAnimation(Scale, ExpressionFunctions.Vector3(scale, scale, scale));
+
+ visual.StartAnimation(AnimationConstants.Orientation, GetElementOrientationNode(element));
+
+ visual.StartAnimation(Opacity, GetElementOpacityNode(element));
+ }
+
+ public void StopElementsAnimation()
+ {
+ foreach (var (_, element) in _elements)
+ {
+ StopElementAnimation(element);
+ }
+ }
+
+ public void StopElementAnimation(ElementReference element)
+ {
+ element.Visual.StopAnimation(Offset);
+ element.Visual.StopAnimation(Scale);
+ element.Visual.StopAnimation(Opacity);
+ element.Visual.StopAnimation(AnimationConstants.Orientation);
+ }
+
+ public record ElementTransition(uint Length, CompositionEasingFunction EasingFunction);
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!disposedValue)
+ {
+ if (disposing)
+ {
+ if (IsActive)
+ {
+ //We only want to dipose these fields if the layout is still active
+ //If it isn't, that means other layout owns them now
+ Properties.Dispose();
+ //InteractionSource.Dispose();
+ //Tracker.Dispose();
+ AnimatableNodes.Dispose();
+ }
+ }
+
+ // TODO: free unmanaged resources (unmanaged objects) and override finalizer
+ // TODO: set large fields to null
+ disposedValue = true;
+ }
+ }
+
+ // // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
+ // ~Layout()
+ // {
+ // // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
+ // Dispose(disposing: false);
+ // }
+
+ public void Dispose()
+ {
+ // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
+ Dispose(disposing: true);
+ GC.SuppressFinalize(this);
+ }
+}
diff --git a/components/CompositionCollectionView/src/CompositionCollectionView.cs b/components/CompositionCollectionView/src/CompositionCollectionView.cs
new file mode 100644
index 000000000..52d5e0bae
--- /dev/null
+++ b/components/CompositionCollectionView/src/CompositionCollectionView.cs
@@ -0,0 +1,77 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+#nullable enable
+
+
+// The Templated Control item template is documented at https://go.microsoft.com/fwlink/?LinkId=234235
+namespace CommunityToolkit.Labs.WinUI;
+
+
+public sealed class CompositionCollectionView : Control
+{
+ private Canvas? _contentPanel;
+ private ILayout? _layout;
+ private Action? _pendingSourceUpdate;
+ public CompositionCollectionLayout? Layout() where TId : notnull => _layout as CompositionCollectionLayout;
+
+ public delegate void LayoutChangedHandler(CompositionCollectionView sender, ILayout newLayout, bool isAnimated);
+ public event LayoutChangedHandler? LayoutChanged;
+
+ public CompositionCollectionView()
+ {
+ this.DefaultStyleKey = typeof(CompositionCollectionView);
+ }
+
+ public void SetLayout(ILayout layout)
+ {
+ _layout = layout;
+ _layout.LayoutReplaced += OnLayoutReplaced;
+
+ if (_contentPanel is not null)
+ {
+ _layout.Activate(_contentPanel);
+ }
+ }
+
+ public async Task UpdateSource(IDictionary source, bool animate = true) where TId : notnull
+ {
+ if (_contentPanel is not null && _layout as CompositionCollectionLayout is CompositionCollectionLayout layout)
+ {
+ await layout.UpdateSource(source, animate);
+ }
+ else
+ {
+ _pendingSourceUpdate = () => (_layout as CompositionCollectionLayout)?.UpdateSource(source, animate);
+ }
+ }
+
+ private void OnLayoutReplaced(ILayout sender, ILayout newLayout, bool isAnimated)
+ {
+ if (_layout is not null)
+ {
+ _layout.LayoutReplaced -= OnLayoutReplaced;
+ }
+
+ _layout = newLayout;
+ _layout.LayoutReplaced += OnLayoutReplaced;
+ LayoutChanged?.Invoke(this, _layout, isAnimated);
+ }
+
+ protected override void OnApplyTemplate()
+ {
+ if (GetTemplateChild("contentPanel") is Canvas contentPanel)
+ {
+ _contentPanel = contentPanel;
+ if (_layout is not null)
+ {
+ _layout.Activate(_contentPanel);
+ }
+ if (_pendingSourceUpdate is not null)
+ {
+ _pendingSourceUpdate?.Invoke();
+ _pendingSourceUpdate = null;
+ }
+ }
+ }
+}
diff --git a/components/CompositionCollectionView/src/CompositionCollectionView.projitems b/components/CompositionCollectionView/src/CompositionCollectionView.projitems
new file mode 100644
index 000000000..6f352aebb
--- /dev/null
+++ b/components/CompositionCollectionView/src/CompositionCollectionView.projitems
@@ -0,0 +1,36 @@
+
+
+
+ $(MSBuildAllProjects);$(MSBuildThisFileFullPath)
+ true
+ bdb6d5d0-b5e2-4ade-9896-30cd295c2d7a
+
+
+ CompositionCollectionView
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Designer
+ MSBuild:Compile
+
+
+
+
+
+
\ No newline at end of file
diff --git a/components/CompositionCollectionView/src/DeconstructPolyfillExtensions.cs b/components/CompositionCollectionView/src/DeconstructPolyfillExtensions.cs
new file mode 100644
index 000000000..518883fed
--- /dev/null
+++ b/components/CompositionCollectionView/src/DeconstructPolyfillExtensions.cs
@@ -0,0 +1,20 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace CommunityToolkit.Labs.WinUI;
+internal static class DeconstructPolyfillExtensions
+{
+ public static void Deconstruct(
+ this KeyValuePair pair,
+ out T1 key,
+ out T2 value)
+ {
+ key = pair.Key;
+ value = pair.Value;
+ }
+}
diff --git a/components/CompositionCollectionView/src/Dependencies.WinUI2.props b/components/CompositionCollectionView/src/Dependencies.WinUI2.props
new file mode 100644
index 000000000..3bd3202b1
--- /dev/null
+++ b/components/CompositionCollectionView/src/Dependencies.WinUI2.props
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
diff --git a/components/CompositionCollectionView/src/Dependencies.WinUI3.props b/components/CompositionCollectionView/src/Dependencies.WinUI3.props
new file mode 100644
index 000000000..cb9f67653
--- /dev/null
+++ b/components/CompositionCollectionView/src/Dependencies.WinUI3.props
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/components/CompositionCollectionView/src/Dependencies.props b/components/CompositionCollectionView/src/Dependencies.props
new file mode 100644
index 000000000..e622e1df4
--- /dev/null
+++ b/components/CompositionCollectionView/src/Dependencies.props
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/CompositionCollectionView/src/ElementReference.cs b/components/CompositionCollectionView/src/ElementReference.cs
new file mode 100644
index 000000000..f64b52e44
--- /dev/null
+++ b/components/CompositionCollectionView/src/ElementReference.cs
@@ -0,0 +1,71 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+#nullable enable
+
+namespace CommunityToolkit.Labs.WinUI;
+public record ElementReference : IDisposable where TId : notnull
+{
+ private bool disposedValue;
+
+ public TId Id { get; init; }
+ public FrameworkElement Container { get; init; }
+ public Visual Visual { get; init; }
+ public CompositionPropertySet CompositionProperties { get; init; }
+ public AnimatableCompositionNodeSet AnimatableNodes { get; init; }
+ public CompositionCollectionLayout Layout { get; internal set; }
+ public TItem Model { get; internal set; }
+
+ public ElementReference(TId id, TItem model, FrameworkElement container, CompositionCollectionLayout layout)
+ {
+ Id = id;
+ Container = container;
+ Visual = ElementCompositionPreview.GetElementVisual(Container);
+ CompositionProperties = Visual.Compositor.CreatePropertySet();
+ AnimatableNodes = new AnimatableCompositionNodeSet(Visual.Compositor);
+ Layout = layout;
+ Model = model;
+ }
+
+ internal void ReasignTo(CompositionCollectionLayout newLayout)
+ {
+ Layout = newLayout;
+ }
+
+ public void SetZIndex(int zIndex)
+ {
+ //TODO: allow allow elements to process their own z index changes
+ Canvas.SetZIndex(Container, zIndex);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!disposedValue)
+ {
+ if (disposing)
+ {
+ // TODO: dispose managed state (managed objects)
+ CompositionProperties.Dispose();
+ AnimatableNodes.Dispose();
+ }
+
+ // TODO: free unmanaged resources (unmanaged objects) and override finalizer
+ // TODO: set large fields to null
+ disposedValue = true;
+ }
+ }
+
+ // // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
+ // ~ElementReference()
+ // {
+ // // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
+ // Dispose(disposing: false);
+ // }
+
+ public void Dispose()
+ {
+ // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
+ Dispose(disposing: true);
+ GC.SuppressFinalize(this);
+ }
+}
diff --git a/components/CompositionCollectionView/src/ExpressionsFork/Expressions/CompositionExtensions.cs b/components/CompositionCollectionView/src/ExpressionsFork/Expressions/CompositionExtensions.cs
new file mode 100644
index 000000000..b40b9678a
--- /dev/null
+++ b/components/CompositionCollectionView/src/ExpressionsFork/Expressions/CompositionExtensions.cs
@@ -0,0 +1,305 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace Microsoft.Toolkit.Uwp.UI.Animations.ExpressionsFork
+{
+ ///
+ /// Class CompositionExtensions.
+ ///
+ public static class CompositionExtensions
+ {
+ ///
+ /// Create an ExpressionNode reference to this CompositionObject.
+ ///
+ /// The comp object.
+ /// AmbientLightReferenceNode.
+ public static AmbientLightReferenceNode GetReference(this AmbientLight compObj)
+ {
+ return new AmbientLightReferenceNode(null, compObj);
+ }
+
+ ///
+ /// Create an ExpressionNode reference to this CompositionObject.
+ ///
+ /// The comp object.
+ /// ColorBrushReferenceNode.
+ public static ColorBrushReferenceNode GetReference(this CompositionColorBrush compObj)
+ {
+ return new ColorBrushReferenceNode(null, compObj);
+ }
+
+ ///
+ /// Create an ExpressionNode reference to this CompositionObject.
+ ///
+ /// The comp object.
+ /// DistantLightReferenceNode.
+ public static DistantLightReferenceNode GetReference(this DistantLight compObj)
+ {
+ return new DistantLightReferenceNode(null, compObj);
+ }
+
+ ///
+ /// Create an ExpressionNode reference to this CompositionObject.
+ ///
+ /// The comp object.
+ /// DropShadowReferenceNode.
+ public static DropShadowReferenceNode GetReference(this DropShadow compObj)
+ {
+ return new DropShadowReferenceNode(null, compObj);
+ }
+
+ ///
+ /// Create an ExpressionNode reference to this CompositionObject.
+ ///
+ /// The comp object.
+ /// InsetClipReferenceNode.
+ public static InsetClipReferenceNode GetReference(this InsetClip compObj)
+ {
+ return new InsetClipReferenceNode(null, compObj);
+ }
+
+ ///
+ /// Create an ExpressionNode reference to this CompositionObject.
+ ///
+ /// The comp object.
+ /// InteractionTrackerReferenceNode.
+ public static InteractionTrackerReferenceNode GetReference(this InteractionTracker compObj)
+ {
+ return new InteractionTrackerReferenceNode(null, compObj);
+ }
+
+ ///
+ /// Create an ExpressionNode reference to this CompositionObject.
+ ///
+ /// The comp object.
+ /// NineGridBrushReferenceNode.
+ public static NineGridBrushReferenceNode GetReference(this CompositionNineGridBrush compObj)
+ {
+ return new NineGridBrushReferenceNode(null, compObj);
+ }
+
+ ///
+ /// Create an ExpressionNode reference to this CompositionObject.
+ ///
+ /// The comp object.
+ /// PointLightReferenceNode.
+ public static PointLightReferenceNode GetReference(this PointLight compObj)
+ {
+ return new PointLightReferenceNode(null, compObj);
+ }
+
+ ///
+ /// Create an ExpressionNode reference to this CompositionObject.
+ ///
+ /// The comp object.
+ /// PropertySetReferenceNode.
+ public static PropertySetReferenceNode GetReference(this CompositionPropertySet compObj)
+ {
+ return new PropertySetReferenceNode(null, compObj);
+ }
+
+ ///
+ /// Create an ExpressionNode reference to this CompositionObject.
+ ///
+ /// The comp object.
+ /// SpotLightReferenceNode.
+ public static SpotLightReferenceNode GetReference(this SpotLight compObj)
+ {
+ return new SpotLightReferenceNode(null, compObj);
+ }
+
+ ///
+ /// Create an ExpressionNode reference to this CompositionObject.
+ ///
+ /// The comp object.
+ /// SurfaceBrushReferenceNode.
+ public static SurfaceBrushReferenceNode GetReference(this CompositionSurfaceBrush compObj)
+ {
+ return new SurfaceBrushReferenceNode(null, compObj);
+ }
+
+ ///
+ /// Create an ExpressionNode reference to this CompositionObject.
+ ///
+ /// The comp object.
+ /// VisualReferenceNode.
+ public static VisualReferenceNode GetReference(this Visual compObj)
+ {
+ return new VisualReferenceNode(null, compObj);
+ }
+
+ ///
+ /// Create an ExpressionNode reference to this specialized PropertySet.
+ ///
+ /// A class that derives from PropertySetReferenceNode.
+ /// The ps.
+ /// T.
+ /// Invalid property set specialization
+ public static T GetSpecializedReference(this CompositionPropertySet ps)
+ where T : PropertySetReferenceNode
+ {
+ if (typeof(T) == typeof(ManipulationPropertySetReferenceNode))
+ {
+ return new ManipulationPropertySetReferenceNode(null, ps) as T;
+ }
+ else if (typeof(T) == typeof(PointerPositionPropertySetReferenceNode))
+ {
+ return new PointerPositionPropertySetReferenceNode(null, ps) as T;
+ }
+ else
+ {
+ throw new System.Exception("Invalid property set specialization");
+ }
+ }
+
+ ///
+ /// Connects the specified ExpressionNode with the specified property of the object.
+ ///
+ /// The comp object.
+ /// The name of the property that the expression will target.
+ /// The root ExpressionNode that represents the ExpressionAnimation.
+ public static void StartAnimation(this CompositionObject compObject, string propertyName, ExpressionNode expressionNode)
+ {
+ compObject.StartAnimation(propertyName, CreateExpressionAnimationFromNode(compObject.Compositor, expressionNode));
+ }
+
+ ///
+ /// Inserts a KeyFrame whose value is calculated using the specified ExpressionNode.
+ ///
+ /// The keyframe animation.
+ /// The time the key frame should occur at, expressed as a percentage of the animation Duration. Allowed value is from 0.0 to 1.0.
+ /// The root ExpressionNode that represents the ExpressionAnimation.
+ /// The easing function to use when interpolating between frames.
+ public static void InsertExpressionKeyFrame(this KeyFrameAnimation keyframeAnimation, float normalizedProgressKey, ExpressionNode expressionNode, CompositionEasingFunction easing = null)
+ {
+ expressionNode.ClearReferenceInfo();
+
+ keyframeAnimation.InsertExpressionKeyFrame(normalizedProgressKey, expressionNode.ToExpressionString(), easing);
+
+ expressionNode.SetAllParameters(keyframeAnimation);
+ }
+
+ ///
+ /// Use the value of specified ExpressionNode to determine if this inertia modifier should be chosen.
+ ///
+ /// The modifier.
+ /// The root ExpressionNode that represents the ExpressionAnimation.
+ public static void SetCondition(this InteractionTrackerInertiaRestingValue modifier, ExpressionNode expressionNode)
+ {
+ modifier.Condition = CreateExpressionAnimationFromNode(modifier.Compositor, expressionNode);
+ }
+
+ ///
+ /// Use the value of specified ExpressionNode as the resting value for this inertia modifier.
+ ///
+ /// The modifier.
+ /// The root ExpressionNode that represents the ExpressionAnimation.
+ public static void SetRestingValue(this InteractionTrackerInertiaRestingValue modifier, ExpressionNode expressionNode)
+ {
+ modifier.RestingValue = CreateExpressionAnimationFromNode(modifier.Compositor, expressionNode);
+ }
+
+ ///
+ /// Use the value of specified ExpressionNode to determine if this inertia modifier should be chosen.
+ ///
+ /// The modifier.
+ /// The root ExpressionNode that represents the ExpressionAnimation.
+ public static void SetCondition(this InteractionTrackerInertiaMotion modifier, ExpressionNode expressionNode)
+ {
+ modifier.Condition = CreateExpressionAnimationFromNode(modifier.Compositor, expressionNode);
+ }
+
+ ///
+ /// Use the value of specified ExpressionNode to dictate the motion for this inertia modifier.
+ ///
+ /// The modifier.
+ /// The root ExpressionNode that represents the ExpressionAnimation.
+ public static void SetMotion(this InteractionTrackerInertiaMotion modifier, ExpressionNode expressionNode)
+ {
+ modifier.Motion = CreateExpressionAnimationFromNode(modifier.Compositor, expressionNode);
+ }
+
+ ///
+ /// Use the value of specified ExpressionNode to determine if this composition conditional value modifier should be chosen.
+ ///
+ /// The modifier.
+ /// The root ExpressionNode that represents the ExpressionAnimation.
+ public static void SetCondition(this CompositionConditionalValue modifier, ExpressionNode expressionNode)
+ {
+ modifier.Condition = CreateExpressionAnimationFromNode(modifier.Compositor, expressionNode);
+ }
+
+ ///
+ /// Use the value of specified ExpressionNode as the value for this composition conditional value
+ ///
+ /// The modifier.
+ /// The root ExpressionNode that represents the ExpressionAnimation.
+ public static void SetValue(this CompositionConditionalValue modifier, ExpressionNode expressionNode)
+ {
+ modifier.Value = CreateExpressionAnimationFromNode(modifier.Compositor, expressionNode);
+ }
+
+ ///
+ /// Creates the expression animation from node.
+ ///
+ /// The compositor.
+ /// The expression node.
+ /// ExpressionAnimation.
+ private static ExpressionAnimation CreateExpressionAnimationFromNode(Compositor compositor, ExpressionNode expressionNode)
+ {
+ // Only create a new animation if this node hasn't already generated one before, so we don't have to re-parse the expression string.
+ if (expressionNode.ExpressionAnimation == null)
+ {
+ expressionNode.ClearReferenceInfo();
+ expressionNode.ExpressionAnimation = compositor.CreateExpressionAnimation(expressionNode.ToExpressionString());
+ }
+
+ // We need to make sure all parameters are up to date, even if the animation already existed.
+ expressionNode.SetAllParameters(expressionNode.ExpressionAnimation);
+
+ return expressionNode.ExpressionAnimation;
+ }
+
+ internal static float EvaluateSubchannel(this ExpressionNode node, string subchannel) => (node, subchannel) switch
+ {
+ (Vector2Node n, "X") => n.Evaluate().X,
+ (Vector2Node n, "Y") => n.Evaluate().Y,
+
+ (Vector3Node n, "X") => n.Evaluate().X,
+ (Vector3Node n, "Y") => n.Evaluate().Y,
+ (Vector3Node n, "Z") => n.Evaluate().Z,
+
+ (Vector4Node n, "X") => n.Evaluate().X,
+ (Vector4Node n, "Y") => n.Evaluate().Y,
+ (Vector4Node n, "Z") => n.Evaluate().Z,
+ (Vector4Node n, "W") => n.Evaluate().W,
+
+ (Matrix3x2Node n, "Channel11") => n.Evaluate().M11,
+ (Matrix3x2Node n, "Channel12") => n.Evaluate().M12,
+ (Matrix3x2Node n, "Channel21") => n.Evaluate().M21,
+ (Matrix3x2Node n, "Channel22") => n.Evaluate().M22,
+ (Matrix3x2Node n, "Channel31") => n.Evaluate().M31,
+ (Matrix3x2Node n, "Channel32") => n.Evaluate().M32,
+
+ (Matrix4x4Node n, "Channel11") => n.Evaluate().M11,
+ (Matrix4x4Node n, "Channel12") => n.Evaluate().M12,
+ (Matrix4x4Node n, "Channel13") => n.Evaluate().M13,
+ (Matrix4x4Node n, "Channel14") => n.Evaluate().M14,
+ (Matrix4x4Node n, "Channel21") => n.Evaluate().M21,
+ (Matrix4x4Node n, "Channel22") => n.Evaluate().M22,
+ (Matrix4x4Node n, "Channel23") => n.Evaluate().M23,
+ (Matrix4x4Node n, "Channel24") => n.Evaluate().M24,
+ (Matrix4x4Node n, "Channel31") => n.Evaluate().M31,
+ (Matrix4x4Node n, "Channel32") => n.Evaluate().M32,
+ (Matrix4x4Node n, "Channel33") => n.Evaluate().M33,
+ (Matrix4x4Node n, "Channel34") => n.Evaluate().M34,
+ (Matrix4x4Node n, "Channel41") => n.Evaluate().M41,
+ (Matrix4x4Node n, "Channel42") => n.Evaluate().M42,
+ (Matrix4x4Node n, "Channel43") => n.Evaluate().M43,
+ (Matrix4x4Node n, "Channel44") => n.Evaluate().M44,
+
+ _ => 0
+ };
+ }
+}
diff --git a/components/CompositionCollectionView/src/ExpressionsFork/Expressions/ExpressionFunctions.cs b/components/CompositionCollectionView/src/ExpressionsFork/Expressions/ExpressionFunctions.cs
new file mode 100644
index 000000000..b6663b84b
--- /dev/null
+++ b/components/CompositionCollectionView/src/ExpressionsFork/Expressions/ExpressionFunctions.cs
@@ -0,0 +1,1335 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+
+namespace Microsoft.Toolkit.Uwp.UI.Animations.ExpressionsFork
+{
+ ///
+ /// Class ExpressionFunctions.
+ ///
+ public static class ExpressionFunctions
+ {
+ //TODO add all of these to evaluate()
+
+
+ ///
+ /// Returns the angle (in radians) whose cosine is the specified number.
+ ///
+ /// Value between -1 and 1, for which to calculate the arccosine (the inverse cosine).
+ /// ScalarNode.
+ public static ScalarNode ACos(ScalarNode val)
+ {
+ return Function(ExpressionNodeType.Acos, val);
+ }
+
+ ///
+ /// Returns the angle (in radians) whose sine is the specified number.
+ ///
+ /// Value between -1 and 1, for which to calculate the arcsine (the inverse sine).
+ /// ScalarNode.
+ public static ScalarNode ASin(ScalarNode val)
+ {
+ return Function(ExpressionNodeType.Asin, val);
+ }
+
+ ///
+ /// Returns the angle (in radians) whose tangent is the specified number.
+ ///
+ /// Value for which to calculate the arctan (the inverse tan).
+ /// ScalarNode.
+ public static ScalarNode ATan(ScalarNode val)
+ {
+ return Function(ExpressionNodeType.Atan, val);
+ }
+
+ ///
+ /// Returns the smallest integral value that is greater than or equal to the specified value.
+ ///
+ /// The floating point number to round.
+ /// ScalarNode.
+ public static ScalarNode Ceil(ScalarNode val)
+ {
+ return Function(ExpressionNodeType.Ceil, val);
+ }
+
+ ///
+ /// Returns the cosine of the specified angle (in radians).
+ ///
+ /// An angle, measured in radians.
+ /// ScalarNode.
+ public static ScalarNode Cos(ScalarNode val)
+ {
+ return Function(ExpressionNodeType.Cos, val);
+ }
+
+ ///
+ /// Returns the largest integer less than or equal to the specified value.
+ ///
+ /// The floating point number to round.
+ /// ScalarNode.
+ public static ScalarNode Floor(ScalarNode val)
+ {
+ return Function(ExpressionNodeType.Floor, val);
+ }
+
+ ///
+ /// Returns the natural (base e) logarithm of a specified number.
+ ///
+ /// The number whose natural logarithm is to be returned.
+ /// ScalarNode.
+ public static ScalarNode Ln(ScalarNode val)
+ {
+ return Function(ExpressionNodeType.Ln, val);
+ }
+
+ ///
+ /// Returns the base 10 logarithm of a specified number.
+ ///
+ /// The number whose base 10 logarithm is to be calculated.
+ /// ScalarNode.
+ public static ScalarNode Log10(ScalarNode val)
+ {
+ return Function(ExpressionNodeType.Log10, val);
+ }
+
+ ///
+ /// Returns a specified number raised to the specified power.
+ ///
+ /// A floating-point number to be raised to a power.
+ /// A floating-point number that specifies a power.
+ /// ScalarNode.
+ public static ScalarNode Pow(ScalarNode val1, ScalarNode val2)
+ {
+ return Function(ExpressionNodeType.Pow, val1, val2);
+ }
+
+ ///
+ /// Rounds a floating point value to the nearest integral value.
+ ///
+ /// The floating point number to round.
+ /// ScalarNode.
+ public static ScalarNode Round(ScalarNode val)
+ {
+ return Function(ExpressionNodeType.Round, val);
+ }
+
+ ///
+ /// Returns the sine of the specified angle (in radians).
+ ///
+ /// An angle, measured in radians.
+ /// ScalarNode.
+ public static ScalarNode Sin(ScalarNode val)
+ {
+ return Function(ExpressionNodeType.Sin, val);
+ }
+
+ ///
+ /// Returns the specified number multiplied by itself.
+ ///
+ /// The floating point number to square.
+ /// ScalarNode.
+ public static ScalarNode Square(ScalarNode val)
+ {
+ return Function(ExpressionNodeType.Square, val);
+ }
+
+ ///
+ /// Returns the square root of a specified number.
+ ///
+ /// The number whose square root is to be returned.
+ /// ScalarNode.
+ public static ScalarNode Sqrt(ScalarNode val)
+ {
+ return Function(ExpressionNodeType.Sqrt, val);
+ }
+
+ ///
+ /// Returns the tangent of the specified angle (in radians).
+ ///
+ /// An angle, measured in radians.
+ /// ScalarNode.
+ public static ScalarNode Tan(ScalarNode val)
+ {
+ return Function(ExpressionNodeType.Tan, val);
+ }
+
+ ///
+ /// Converts an angle in radians to degrees as: val*180/PI.
+ ///
+ /// A floating point value that represents an angle in radians.
+ /// ScalarNode.
+ public static ScalarNode ToDegrees(ScalarNode val)
+ {
+ return Function(ExpressionNodeType.ToDegrees, val);
+ }
+
+ ///
+ /// Converts an angle in degrees to radians as: val*PI/180.
+ ///
+ /// A floating point value that represents an angle in degrees.
+ /// ScalarNode.
+ public static ScalarNode ToRadians(ScalarNode val)
+ {
+ return Function(ExpressionNodeType.ToRadians, val);
+ }
+
+ // System.Numerics functions
+
+ ///
+ /// Returns the absolute value of the specified input. For vectors, the absolute value of each subchannel is returned.
+ ///
+ /// The input value.
+ /// ScalarNode.
+ public static ScalarNode Abs(ScalarNode val)
+ {
+ return Function(ExpressionNodeType.Absolute, val);
+ }
+
+ ///
+ /// Returns the absolute value of the specified input. For vectors, the absolute value of each subchannel is returned.
+ ///
+ /// The input value.
+ /// Vector2Node.
+ public static Vector2Node Abs(Vector2Node val)
+ {
+ return Function(ExpressionNodeType.Absolute, val);
+ }
+
+ ///
+ /// Returns the absolute value of the specified input. For vectors, the absolute value of each subchannel is returned.
+ ///
+ /// The input value.
+ /// Vector3Node.
+ public static Vector3Node Abs(Vector3Node val)
+ {
+ return Function(ExpressionNodeType.Absolute, val);
+ }
+
+ ///
+ /// Returns the absolute value of the specified input. For vectors, the absolute value of each subchannel is returned.
+ /// .
+ ///
+ /// The input value.
+ /// Vector4Node.
+ public static Vector4Node Abs(Vector4Node val)
+ {
+ return Function(ExpressionNodeType.Absolute, val);
+ }
+
+ ///
+ /// Restricts a value to be within a specified range. For vectors, each subchannel is clamped.
+ ///
+ /// The value to clamp.
+ /// The specified minimum range.
+ /// The specified maximum range.
+ /// ScalarNode.
+ public static ScalarNode Clamp(ScalarNode val, ScalarNode min, ScalarNode max)
+ {
+ return Function(ExpressionNodeType.Clamp, val, min, max);
+ }
+
+ ///
+ /// Restricts a value to be within a specified range. For vectors, each subchannel is clamped.
+ ///
+ /// The value to clamp.
+ /// The specified minimum range.
+ /// The specified maximum range.
+ /// Vector2Node.
+ public static Vector2Node Clamp(Vector2Node val, Vector2Node min, Vector2Node max)
+ {
+ return Function(ExpressionNodeType.Clamp, val, min, max);
+ }
+
+ ///
+ /// Restricts a value to be within a specified range. For vectors, each subchannel is clamped.
+ ///
+ /// The value to clamp.
+ /// The specified minimum range.
+ /// The specified maximum range.
+ /// Vector3Node.
+ public static Vector3Node Clamp(Vector3Node val, Vector3Node min, Vector3Node max)
+ {
+ return Function(ExpressionNodeType.Clamp, val, min, max);
+ }
+
+ ///
+ /// Restricts a value to be within a specified range. For vectors, each subchannel is clamped.
+ ///
+ /// The value to clamp.
+ /// The specified minimum range.
+ /// The specified maximum range.
+ /// Vector4Node.
+ public static Vector4Node Clamp(Vector4Node val, Vector4Node min, Vector4Node max)
+ {
+ return Function(ExpressionNodeType.Clamp, val, min, max);
+ }
+
+ ///
+ /// Linearly interpolates between two colors in the default color space.
+ ///
+ /// Color source value 1.
+ /// Color source value 2.
+ /// A value between 0 and 1.0 indicating the weight of val2.
+ /// ColorNode.
+ public static ColorNode ColorLerp(ColorNode val1, ColorNode val2, ScalarNode progress)
+ {
+ return Function(ExpressionNodeType.ColorLerp, val1, val2, progress);
+ }
+
+ ///
+ /// Linearly interpolates between two colors in the HSL color space.
+ ///
+ /// Color source value 1.
+ /// Color source value 2.
+ /// A value between 0 and 1.0 indicating the weight of val2.
+ /// ColorNode.
+ public static ColorNode ColorLerpHsl(ColorNode val1, ColorNode val2, ScalarNode progress)
+ {
+ return Function(ExpressionNodeType.ColorLerpHsl, val1, val2, progress);
+ }
+
+ ///
+ /// Linearly interpolates between two colors in the RBG color space.
+ ///
+ /// Color source value 1.
+ /// Color source value 2.
+ /// A value between 0 and 1.0 indicating the weight of val2.
+ /// ColorNode.
+ public static ColorNode ColorLerpRgb(ColorNode val1, ColorNode val2, ScalarNode progress)
+ {
+ return Function(ExpressionNodeType.ColorLerpRgb, val1, val2, progress);
+ }
+
+ ///
+ /// Concatenates two Quaternions; the result represents the first rotation followed by the second rotation.
+ ///
+ /// The first quaternion rotation in the series.
+ /// The second quaternion rotation in the series.
+ /// QuaternionNode.
+ public static QuaternionNode Concatenate(QuaternionNode val1, QuaternionNode val2)
+ {
+ return Function(ExpressionNodeType.Concatenate, val1, val2);
+ }
+
+ ///
+ /// Returns the distance between two vectors as: sqrt((x1-x2)^2 + (y1-y2)^2 + ...).
+ ///
+ /// Source value 1.
+ /// Source value 2.
+ /// ScalarNode.
+ public static ScalarNode Distance(ScalarNode val1, ScalarNode val2)
+ {
+ return Function(ExpressionNodeType.Distance, val1, val2);
+ }
+
+ ///
+ /// Returns the distance between two vectors as: sqrt((x1-x2)^2 + (y1-y2)^2 + ...).
+ ///
+ /// Source value 1.
+ /// Source value 2.
+ /// ScalarNode.
+ public static ScalarNode Distance(Vector2Node val1, Vector2Node val2)
+ {
+ return Function(ExpressionNodeType.Distance, val1, val2);
+ }
+
+ ///
+ /// Returns the distance between two vectors as: sqrt((x1-x2)^2 + (y1-y2)^2 + ...).
+ ///
+ /// Source value 1.
+ /// Source value 2.
+ /// ScalarNode.
+ public static ScalarNode Distance(Vector3Node val1, Vector3Node val2)
+ {
+ return Function(ExpressionNodeType.Distance, val1, val2);
+ }
+
+ ///
+ /// Returns the distance between two vectors as: sqrt((x1-x2)^2 + (y1-y2)^2 + ...).
+ ///
+ /// Source value 1.
+ /// Source value 2.
+ /// ScalarNode.
+ public static ScalarNode Distance(Vector4Node val1, Vector4Node val2)
+ {
+ return Function(ExpressionNodeType.Distance, val1, val2);
+ }
+
+ ///
+ /// Returns the squared distance between two vectors as: ((x1-x2)^2 + (y1-y2)^2 + ...).
+ ///
+ /// Source value 1.
+ /// Source value 2.
+ /// ScalarNode.
+ public static ScalarNode DistanceSquared(ScalarNode val1, ScalarNode val2)
+ {
+ return Function(ExpressionNodeType.DistanceSquared, val1, val2);
+ }
+
+ ///
+ /// Returns the squared distance between two vectors as: ((x1-x2)^2 + (y1-y2)^2 + ...).
+ ///
+ /// Source value 1.
+ /// Source value 2.
+ /// ScalarNode.
+ public static ScalarNode DistanceSquared(Vector2Node val1, Vector2Node val2)
+ {
+ return Function(ExpressionNodeType.DistanceSquared, val1, val2);
+ }
+
+ ///
+ /// Returns the squared distance between two vectors as: ((x1-x2)^2 + (y1-y2)^2 + ...).
+ ///
+ /// Source value 1.
+ /// Source value 2.
+ /// ScalarNode.
+ public static ScalarNode DistanceSquared(Vector3Node val1, Vector3Node val2)
+ {
+ return Function(ExpressionNodeType.DistanceSquared, val1, val2);
+ }
+
+ ///
+ /// Returns the squared distance between two vectors as: ((x1-x2)^2 + (y1-y2)^2 + ...).
+ ///
+ /// Source value 1.
+ /// Source value 2.
+ /// ScalarNode.
+ public static ScalarNode DistanceSquared(Vector4Node val1, Vector4Node val2)
+ {
+ return Function(ExpressionNodeType.DistanceSquared, val1, val2);
+ }
+
+ ///
+ /// Returns the inverse of the specified matrix.
+ ///
+ /// The matrix to invert.
+ /// Matrix3x2Node.
+ public static Matrix3x2Node Inverse(Matrix3x2Node val)
+ {
+ return Function(ExpressionNodeType.Inverse, val);
+ }
+
+ ///
+ /// Returns the inverse of the specified matrix.
+ ///
+ /// The matrix to invert.
+ /// Matrix4x4Node.
+ public static Matrix4x4Node Inverse(Matrix4x4Node val)
+ {
+ return Function(ExpressionNodeType.Inverse, val);
+ }
+
+ ///
+ /// Returns the length of the vector as: sqrt(x^2 + y^2 + ...).
+ ///
+ /// Vector value to return the length of.
+ /// ScalarNode.
+ public static ScalarNode Length(ScalarNode val)
+ {
+ return Function(ExpressionNodeType.Length, val);
+ }
+
+ ///
+ /// Returns the length of the vector as: sqrt(x^2 + y^2 + ...).
+ ///
+ /// Vector value to return the length of.
+ /// ScalarNode.
+ public static ScalarNode Length(Vector2Node val)
+ {
+ return Function(ExpressionNodeType.Length, val);
+ }
+
+ ///
+ /// Returns the length of the vector as: sqrt(x^2 + y^2 + ...).
+ ///
+ /// Vector value to return the length of.
+ /// ScalarNode.
+ public static ScalarNode Length(Vector3Node val)
+ {
+ return Function(ExpressionNodeType.Length, val);
+ }
+
+ ///
+ /// Returns the length of the vector as: sqrt(x^2 + y^2 + ...).
+ ///
+ /// Vector value to return the length of.
+ /// ScalarNode.
+ public static ScalarNode Length(Vector4Node val)
+ {
+ return Function(ExpressionNodeType.Length, val);
+ }
+
+ ///
+ /// Returns the length of the vector as: sqrt(x^2 + y^2 + ...).
+ ///
+ /// Vector value to return the length of.
+ /// ScalarNode.
+ public static ScalarNode Length(QuaternionNode val)
+ {
+ return Function(ExpressionNodeType.Length, val);
+ }
+
+ ///
+ /// Returns the squared length of the vector as: (x^2 + y^2 + ...).
+ ///
+ /// Vector value to return the length squared of.
+ /// ScalarNode.
+ public static ScalarNode LengthSquared(ScalarNode val)
+ {
+ return Function(ExpressionNodeType.LengthSquared, val);
+ }
+
+ ///
+ /// Returns the squared length of the vector as: (x^2 + y^2 + ...).
+ ///
+ /// Vector value to return the length squared of.
+ /// ScalarNode.
+ public static ScalarNode LengthSquared(Vector2Node val)
+ {
+ return Function(ExpressionNodeType.LengthSquared, val);
+ }
+
+ ///
+ /// Returns the squared length of the vector as: (x^2 + y^2 + ...).
+ ///
+ /// Vector value to return the length squared of.
+ /// ScalarNode.
+ public static ScalarNode LengthSquared(Vector3Node val)
+ {
+ return Function(ExpressionNodeType.LengthSquared, val);
+ }
+
+ ///
+ /// Returns the squared length of the vector as: (x^2 + y^2 + ...).
+ ///
+ /// Vector value to return the length squared of.
+ /// ScalarNode.
+ public static ScalarNode LengthSquared(Vector4Node val)
+ {
+ return Function(ExpressionNodeType.LengthSquared, val);
+ }
+
+ ///
+ /// Returns the squared length of the vector as: (x^2 + y^2 + ...).
+ ///
+ /// Vector value to return the length squared of.
+ /// ScalarNode.
+ public static ScalarNode LengthSquared(QuaternionNode val)
+ {
+ return Function(ExpressionNodeType.LengthSquared, val);
+ }
+
+ ///
+ /// Linearly interpolates between two vectors as: Output.x = x1 + (x2-x1)*progress.
+ ///
+ /// Source value 1.
+ /// Source value 2.
+ /// A value between 0 and 1.0 indicating the weight of val2.
+ /// ScalarNode.
+ public static ScalarNode Lerp(ScalarNode val1, ScalarNode val2, ScalarNode progress)
+ {
+ return Function(ExpressionNodeType.Lerp, val1, val2, progress);
+ }
+
+ ///
+ /// Linearly interpolates between two vectors as: Output.x = x1 + (x2-x1)*progress.
+ ///
+ /// Source value 1.
+ /// Source value 2.
+ /// A value between 0 and 1.0 indicating the weight of val2.
+ /// Vector2Node.
+ public static Vector2Node Lerp(Vector2Node val1, Vector2Node val2, ScalarNode progress)
+ {
+ return Function(ExpressionNodeType.Lerp, val1, val2, progress);
+ }
+
+ ///
+ /// Linearly interpolates between two vectors as: Output.x = x1 + (x2-x1)*progress.
+ ///
+ /// Source value 1.
+ /// Source value 2.
+ /// A value between 0 and 1.0 indicating the weight of val2.
+ /// Vector3Node.
+ public static Vector3Node Lerp(Vector3Node val1, Vector3Node val2, ScalarNode progress)
+ {
+ return Function(ExpressionNodeType.Lerp, val1, val2, progress);
+ }
+
+ ///
+ /// Linearly interpolates between two vectors as: Output.x = x1 + (x2-x1)*progress.
+ ///
+ /// Source value 1.
+ /// Source value 2.
+ /// A value between 0 and 1.0 indicating the weight of val2.
+ /// Vector4Node.
+ public static Vector4Node Lerp(Vector4Node val1, Vector4Node val2, ScalarNode progress)
+ {
+ return Function(ExpressionNodeType.Lerp, val1, val2, progress);
+ }
+
+ ///
+ /// Returns the maximum of two values. For vectors, the max of each subchannel is returned.
+ ///
+ /// Source value 1.
+ /// Source value 2.
+ /// ScalarNode.
+ public static ScalarNode Max(ScalarNode val1, ScalarNode val2)
+ {
+ return Function(ExpressionNodeType.Max, val1, val2);
+ }
+
+ ///
+ /// Returns the maximum of two values. For vectors, the max of each subchannel is returned.
+ ///
+ /// Source value 1.
+ /// Source value 2.
+ /// Vector2Node.
+ public static Vector2Node Max(Vector2Node val1, Vector2Node val2)
+ {
+ return Function(ExpressionNodeType.Max, val1, val2);
+ }
+
+ ///
+ /// Returns the maximum of two values. For vectors, the max of each subchannel is returned.
+ ///
+ /// Source value 1.
+ /// Source value 2.
+ /// Vector3Node.
+ public static Vector3Node Max(Vector3Node val1, Vector3Node val2)
+ {
+ return Function(ExpressionNodeType.Max, val1, val2);
+ }
+
+ ///
+ /// Returns the maximum of two values. For vectors, the max of each subchannel is returned.
+ ///
+ /// Source value 1.
+ /// Source value 2.
+ /// Vector4Node.
+ public static Vector4Node Max(Vector4Node val1, Vector4Node val2)
+ {
+ return Function(ExpressionNodeType.Max, val1, val2);
+ }
+
+ ///
+ /// Returns the minimum of two values. For vectors, the min of each subchannel is returned.
+ ///
+ /// Source value 1.
+ /// Source value 2.
+ /// ScalarNode.
+ public static ScalarNode Min(ScalarNode val1, ScalarNode val2)
+ {
+ return Function(ExpressionNodeType.Min, val1, val2);
+ }
+
+ ///
+ /// Returns the minimum of two values. For vectors, the min of each subchannel is returned.
+ ///
+ /// Source value 1.
+ /// Source value 2.
+ /// Vector2Node.
+ public static Vector2Node Min(Vector2Node val1, Vector2Node val2)
+ {
+ return Function(ExpressionNodeType.Min, val1, val2);
+ }
+
+ ///
+ /// Returns the minimum of two values. For vectors, the min of each subchannel is returned.
+ ///
+ /// Source value 1.
+ /// Source value 2.
+ /// Vector3Node.
+ public static Vector3Node Min(Vector3Node val1, Vector3Node val2)
+ {
+ return Function(ExpressionNodeType.Min, val1, val2);
+ }
+
+ ///
+ /// Returns the minimum of two values. For vectors, the min of each subchannel is returned.
+ ///
+ /// Source value 1.
+ /// Source value 2.
+ /// Vector4Node.
+ public static Vector4Node Min(Vector4Node val1, Vector4Node val2)
+ {
+ return Function(ExpressionNodeType.Min, val1, val2);
+ }
+
+ ///
+ /// Returns the remainder resulting from dividing val1/val2. For vectors, the remainder for each subchannel is returned.
+ ///
+ /// The numerator value.
+ /// The denominator value.
+ /// ScalarNode.
+ public static ScalarNode Mod(ScalarNode val1, ScalarNode val2)
+ {
+ return Function(ExpressionNodeType.Modulus, val1, val2);
+ }
+
+ ///
+ /// Returns the remainder resulting from dividing val1/val2. For vectors, the remainder for each subchannel is returned.
+ ///
+ /// The numerator value.
+ /// The denominator value.
+ /// Vector2Node.
+ public static Vector2Node Mod(Vector2Node val1, Vector2Node val2)
+ {
+ return Function(ExpressionNodeType.Modulus, val1, val2);
+ }
+
+ ///
+ /// Returns the remainder resulting from dividing val1/val2. For vectors, the remainder for each subchannel is returned.
+ ///
+ /// The numerator value.
+ /// The denominator value.
+ /// Vector3Node.
+ public static Vector3Node Mod(Vector3Node val1, Vector3Node val2)
+ {
+ return Function(ExpressionNodeType.Modulus, val1, val2);
+ }
+
+ ///
+ /// Returns the remainder resulting from dividing val1/val2. For vectors, the remainder for each subchannel is returned.
+ ///
+ /// The numerator value.
+ /// The denominator value.
+ /// Vector4Node.
+ public static Vector4Node Mod(Vector4Node val1, Vector4Node val2)
+ {
+ return Function(ExpressionNodeType.Modulus, val1, val2);
+ }
+
+ ///
+ /// Returns the normalized version of a vector.
+ ///
+ /// Vector value to normalize.
+ /// Vector2Node.
+ public static Vector2Node Normalize(Vector2Node val)
+ {
+ return Function(ExpressionNodeType.Normalize, val);
+ }
+
+ ///
+ /// Returns the normalized version of a vector.
+ ///
+ /// Vector value to normalize.
+ /// Vector3Node.
+ public static Vector3Node Normalize(Vector3Node val)
+ {
+ return Function(ExpressionNodeType.Normalize, val);
+ }
+
+ ///
+ /// Returns the normalized version of a vector.
+ ///
+ /// Vector value to normalize.
+ /// Vector4Node.
+ public static Vector4Node Normalize(Vector4Node val)
+ {
+ return Function(ExpressionNodeType.Normalize, val);
+ }
+
+ ///
+ /// Returns the normalized version of a vector.
+ ///
+ /// Vector value to normalize.
+ /// QuaternionNode.
+ public static QuaternionNode Normalize(QuaternionNode val)
+ {
+ return Function(ExpressionNodeType.Normalize, val);
+ }
+
+ ///
+ /// Multiply each subchannel of the specified vector/matrix by a float value.
+ ///
+ /// Source value to scale.
+ /// Scaling value.
+ /// ScalarNode.
+ public static ScalarNode Scale(ScalarNode val1, ScalarNode val2)
+ {
+ return Function(ExpressionNodeType.Scale, val1, val2);
+ }
+
+ ///
+ /// Multiply each subchannel of the specified vector/matrix by a float value.
+ ///
+ /// Source value to scale.
+ /// Scaling value.
+ /// Vector2Node.
+ public static Vector2Node Scale(Vector2Node val1, ScalarNode val2)
+ {
+ return Function(ExpressionNodeType.Scale, val1, val2);
+ }
+
+ ///
+ /// Multiply each subchannel of the specified vector/matrix by a float value.
+ ///
+ /// Source value to scale.
+ /// Scaling value.
+ /// Vector3Node.
+ public static Vector3Node Scale(Vector3Node val1, ScalarNode val2)
+ {
+ return Function(ExpressionNodeType.Scale, val1, val2);
+ }
+
+ ///
+ /// Multiply each subchannel of the specified vector/matrix by a float value.
+ ///
+ /// Source value to scale.
+ /// Scaling value.
+ /// Vector4Node.
+ public static Vector4Node Scale(Vector4Node val1, ScalarNode val2)
+ {
+ return Function(ExpressionNodeType.Scale, val1, val2);
+ }
+
+ ///
+ /// Multiply each subchannel of the specified vector/matrix by a float value.
+ ///
+ /// Source value to scale.
+ /// Scaling value.
+ /// Matrix3x2Node.
+ public static Matrix3x2Node Scale(Matrix3x2Node val1, ScalarNode val2)
+ {
+ return Function(ExpressionNodeType.Scale, val1, val2);
+ }
+
+ ///
+ /// Multiply each subchannel of the specified vector/matrix by a float value.
+ ///
+ /// Source value to scale.
+ /// Scaling value.
+ /// Matrix4x4Node.
+ public static Matrix4x4Node Scale(Matrix4x4Node val1, ScalarNode val2)
+ {
+ return Function(ExpressionNodeType.Scale, val1, val2);
+ }
+
+ ///
+ /// Spherically interpolates between two quaternions.
+ ///
+ /// Quaternion source value 1.
+ /// Quaternion source value 2.
+ /// A value between 0 and 1.0 indicating the weight of val2.
+ /// QuaternionNode.
+ public static QuaternionNode Slerp(QuaternionNode val1, QuaternionNode val2, ScalarNode progress)
+ {
+ return Function(ExpressionNodeType.Slerp, val1, val2, progress);
+ }
+
+ ///
+ /// Transforms a vector by the specified matrix.
+ ///
+ /// Vector to be transformed.
+ /// The transformation matrix.
+ /// Vector2Node.
+ public static Vector2Node Transform(Vector2Node val1, Matrix3x2Node val2)
+ {
+ return Function(ExpressionNodeType.Transform, val1, val2);
+ }
+
+ ///
+ /// Transforms a vector by the specified matrix.
+ ///
+ /// Vector to be transformed.
+ /// The transformation matrix.
+ /// Vector4Node.
+ public static Vector4Node Transform(Vector4Node val1, Matrix4x4Node val2)
+ {
+ return Function(ExpressionNodeType.Transform, val1, val2);
+ }
+
+ // System.Numerics Type Constructors
+
+ ///
+ /// Creates a vector whose subchannels have the specified values.
+ ///
+ /// The x.
+ /// The y.
+ /// Vector2Node.
+ public static Vector2Node Vector2(ScalarNode x, ScalarNode y)
+ {
+ return Function(ExpressionNodeType.Vector2, x, y);
+ }
+
+ ///
+ /// Creates a vector whose subchannels have the specified values.
+ ///
+ /// The x.
+ /// The y.
+ /// The z.
+ /// Vector3Node.
+ public static Vector3Node Vector3(ScalarNode x, ScalarNode y, ScalarNode z)
+ {
+ return Function(ExpressionNodeType.Vector3, x, y, z);
+ }
+
+ ///
+ /// Creates a vector whose subchannels have the specified values.
+ ///
+ /// The x.
+ /// The y.
+ /// The z.
+ /// The w.
+ /// Vector4Node.
+ public static Vector4Node Vector4(ScalarNode x, ScalarNode y, ScalarNode z, ScalarNode w)
+ {
+ return Function(ExpressionNodeType.Vector4, x, y, z, w);
+ }
+
+ ///
+ /// Creates a color in the HSL format.
+ ///
+ /// Hue
+ /// Saturation
+ /// Luminosity
+ /// ColorNode.
+ public static ColorNode ColorHsl(ScalarNode h, ScalarNode s, ScalarNode l)
+ {
+ return Function(ExpressionNodeType.ColorHsl, h, s, l);
+ }
+
+ ///
+ /// Creates a Color in the ARGB format.
+ ///
+ /// The alpha.
+ /// The red.
+ /// The green.
+ /// The blue.
+ /// ColorNode.
+ public static ColorNode ColorRgb(ScalarNode alpha, ScalarNode red, ScalarNode green, ScalarNode blue)
+ {
+ return Function(ExpressionNodeType.ColorRgb, alpha, red, green, blue);
+ }
+
+ ///
+ /// Creates a quaternion whose subchannels have the specified values.
+ ///
+ /// The x.
+ /// The y.
+ /// The z.
+ /// The w.
+ /// QuaternionNode.
+ public static QuaternionNode Quaternion(ScalarNode x, ScalarNode y, ScalarNode z, ScalarNode w)
+ {
+ return Function(ExpressionNodeType.Quaternion, x, y, z, w);
+ }
+
+ ///
+ /// Creates a matrix whose subchannels have the specified values.
+ ///
+ /// The channel11.
+ /// The channel12.
+ /// The channel21.
+ /// The channel22.
+ /// The channel31.
+ /// The channel32.
+ /// Matrix3x2Node.
+ public static Matrix3x2Node Matrix3x2(ScalarNode channel11, ScalarNode channel12, ScalarNode channel21, ScalarNode channel22, ScalarNode channel31, ScalarNode channel32)
+ {
+ return Function(ExpressionNodeType.Matrix3x2, channel11, channel12, channel21, channel22, channel31, channel32);
+ }
+
+ ///
+ /// Creates a matrix whose subchannels have the specified values.
+ ///
+ /// The channel11.
+ /// The channel12.
+ /// The channel13.
+ /// The channel14.
+ /// The channel21.
+ /// The channel22.
+ /// The channel23.
+ /// The channel24.
+ /// The channel31.
+ /// The channel32.
+ /// The channel33.
+ /// The channel34.
+ /// The channel41.
+ /// The channel42.
+ /// The channel43.
+ /// The channel44.
+ /// Matrix4x4Node.
+#pragma warning disable SA1117 // Parameters must be on same line or separate lines
+ public static Matrix4x4Node Matrix4x4(ScalarNode channel11, ScalarNode channel12, ScalarNode channel13, ScalarNode channel14,
+ ScalarNode channel21, ScalarNode channel22, ScalarNode channel23, ScalarNode channel24,
+ ScalarNode channel31, ScalarNode channel32, ScalarNode channel33, ScalarNode channel34,
+ ScalarNode channel41, ScalarNode channel42, ScalarNode channel43, ScalarNode channel44)
+#pragma warning restore SA1117 // Parameters must be on same line or separate lines
+ {
+ return Function(ExpressionNodeType.Matrix4x4, channel11, channel12, channel13, channel14, channel21, channel22, channel23, channel24, channel31, channel32, channel33, channel34, channel41, channel42, channel43, channel44);
+ }
+
+ ///
+ /// Creates a 4x4 matrix from a 3x2 matrix.
+ ///
+ /// The value.
+ /// Matrix4x4Node.
+ public static Matrix4x4Node Matrix4x4(Matrix3x2Node val)
+ {
+#pragma warning disable SA1117 // Parameters must be on same line or separate lines
+ return Function(
+ ExpressionNodeType.Matrix4x4,
+ val.Channel11, val.Channel12, (ScalarNode)0, (ScalarNode)0,
+ val.Channel21, val.Channel22, (ScalarNode)0, (ScalarNode)0,
+ (ScalarNode)0, (ScalarNode)0, (ScalarNode)1, (ScalarNode)0,
+ val.Channel31, val.Channel32, (ScalarNode)0, (ScalarNode)1);
+#pragma warning restore SA1117 // Parameters must be on same line or separate lines
+ }
+
+ ///
+ /// Creates a translation matrix from the specified vector.
+ ///
+ /// Source translation vector.
+ /// Matrix3x2Node.
+ public static Matrix3x2Node CreateTranslation(Vector2Node val)
+ {
+ return Function(ExpressionNodeType.Matrix3x2FromTranslation, val);
+ }
+
+ ///
+ /// Creates a translation matrix from the specified vector.
+ ///
+ /// Source translation vector.
+ /// Matrix4x4Node.
+ public static Matrix4x4Node CreateTranslation(Vector3Node val)
+ {
+ return Function(ExpressionNodeType.Matrix4x4FromTranslation, val);
+ }
+
+ ///