Skip to content
103 changes: 47 additions & 56 deletions Microsoft.Toolkit.Uwp.UI.Controls/TileControl/TileControl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
using System.Numerics;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Toolkit.Uwp.UI.Animations.Expressions;
using Microsoft.Toolkit.Uwp.UI.Extensions;
using Windows.Foundation;
using Windows.UI.Composition;
Expand Down Expand Up @@ -439,11 +438,16 @@ private async Task CreateModuloExpression(ScrollViewer scrollViewer = null)
/// <param name="scrollOrientation">The ScrollOrientation</param>
private void CreateModuloExpression(ScrollViewer scrollViewer, double imageWidth, double imageHeight, ScrollOrientation scrollOrientation)
{
const string offsetXParam = "offsetX";
const string offsetYParam = "offsetY";
const string imageWidthParam = "imageWidth";
const string imageHeightParam = "imageHeight";
const string speedParam = "speed";
const string propSetParam = "p";
const string offsetXParam = nameof(OffsetX);
const string qualifiedOffsetXParam = propSetParam + "." + offsetXParam;
const string offsetYParam = nameof(OffsetY);
const string qualifiedOffsetYParam = propSetParam + "." + offsetYParam;
const string imageWidthParam = nameof(imageWidth);
const string qualifiedImageWidthParam = propSetParam + "." + imageWidthParam;
const string imageHeightParam = nameof(imageHeight);
const string qualifiedImageHeightParam = propSetParam + "." + imageHeightParam;
const string speedParam = nameof(ParallaxSpeedRatio);

if (_containerVisual == null)
{
Expand All @@ -453,10 +457,8 @@ private void CreateModuloExpression(ScrollViewer scrollViewer, double imageWidth
var compositor = _containerVisual.Compositor;

// Setup the expression
ExpressionNode expressionX = null;
ExpressionNode expressionY = null;
ExpressionNode expressionXVal;
ExpressionNode expressionYVal;
var expressionX = compositor.CreateExpressionAnimation();
var expressionY = compositor.CreateExpressionAnimation();

var propertySetModulo = compositor.CreatePropertySet();
propertySetModulo.InsertScalar(imageWidthParam, (float)imageWidth);
Expand All @@ -465,78 +467,67 @@ private void CreateModuloExpression(ScrollViewer scrollViewer, double imageWidth
propertySetModulo.InsertScalar(offsetYParam, (float)OffsetY);
propertySetModulo.InsertScalar(speedParam, (float)ParallaxSpeedRatio);

var propertySetNodeModulo = propertySetModulo.GetReference();

var imageHeightNode = propertySetNodeModulo.GetScalarProperty(imageHeightParam);
var imageWidthNode = propertySetNodeModulo.GetScalarProperty(imageWidthParam);
expressionX.SetReferenceParameter(propSetParam, propertySetModulo);
expressionY.SetReferenceParameter(propSetParam, propertySetModulo);

string GenerateFormula(string common, string dimension)
=> string.Format(
"{0} == 0 " +
"? 0 " +
": {0} < 0 " +
"? -(Abs({0} - (Ceil({0} / {1}) * {1})) % {1}) " +
": -({1} - ({0} % {1}))",
common,
dimension);

string expressionXVal;
string expressionYVal;
if (scrollViewer == null)
{
var offsetXNode = ExpressionFunctions.Ceil(propertySetNodeModulo.GetScalarProperty(offsetXParam));
var offsetYNode = ExpressionFunctions.Ceil(propertySetNodeModulo.GetScalarProperty(offsetYParam));

// expressions are created to simulate a positive and negative modulo with the size of the image and the offset
expressionXVal = ExpressionFunctions.Conditional(
offsetXNode == 0,
0,
ExpressionFunctions.Conditional(
offsetXNode < 0,
-(ExpressionFunctions.Abs(offsetXNode - (ExpressionFunctions.Ceil(offsetXNode / imageWidthNode) * imageWidthNode)) % imageWidthNode),
-(imageWidthNode - (offsetXNode % imageWidthNode))));

expressionYVal = ExpressionFunctions.Conditional(
offsetYNode == 0,
0,
ExpressionFunctions.Conditional(
offsetYNode < 0,
-(ExpressionFunctions.Abs(offsetYNode - (ExpressionFunctions.Ceil(offsetYNode / imageHeightNode) * imageHeightNode)) % imageHeightNode),
-(imageHeightNode - (offsetYNode % imageHeightNode))));
expressionXVal = GenerateFormula("Ceil(" + qualifiedOffsetXParam + ")", qualifiedImageHeightParam);

expressionYVal = GenerateFormula("Ceil(" + qualifiedOffsetYParam + ")", qualifiedImageWidthParam);
}
else
{
// expressions are created to simulate a positive and negative modulo with the size of the image and the offset and the ScrollViewer offset (Translation)
var scrollProperties = ElementCompositionPreview.GetScrollViewerManipulationPropertySet(scrollViewer);
var scrollPropSet = scrollProperties.GetSpecializedReference<ManipulationPropertySetReferenceNode>();

var speed = propertySetNodeModulo.GetScalarProperty(speedParam);
var xCommon = ExpressionFunctions.Ceil((scrollPropSet.Translation.X * speed) + propertySetNodeModulo.GetScalarProperty(offsetXParam));
expressionXVal = ExpressionFunctions.Conditional(
xCommon == 0,
0,
ExpressionFunctions.Conditional(
xCommon < 0,
-(ExpressionFunctions.Abs(xCommon - (ExpressionFunctions.Ceil(xCommon / imageWidthNode) * imageWidthNode)) % imageWidthNode),
-(imageWidthNode - (xCommon % imageWidthNode))));

var yCommon = ExpressionFunctions.Ceil((scrollPropSet.Translation.Y * speed) + propertySetNodeModulo.GetScalarProperty(offsetYParam));
expressionYVal = ExpressionFunctions.Conditional(
yCommon == 0,
0,
ExpressionFunctions.Conditional(
yCommon < 0,
-(ExpressionFunctions.Abs(yCommon - (ExpressionFunctions.Ceil(yCommon / imageHeightNode) * imageHeightNode)) % imageHeightNode),
-(imageHeightNode - (yCommon % imageHeightNode))));
const string scrollParam = "s";
const string translationParam = scrollParam + "." + nameof(scrollViewer.Translation);
const string qualifiedSpeedParam = propSetParam + "." + speedParam;

expressionX.SetReferenceParameter(scrollParam, scrollProperties);
expressionY.SetReferenceParameter(scrollParam, scrollProperties);

string GenerateParallaxFormula(string scrollTranslation, string speed, string offset, string dimension)
=> GenerateFormula(string.Format("Ceil(({0} * {1}) + {2})", scrollTranslation, speed, offset), dimension);

expressionXVal = GenerateParallaxFormula(translationParam + "." + nameof(scrollViewer.Translation.X), qualifiedSpeedParam, qualifiedOffsetXParam, qualifiedImageWidthParam);

expressionYVal = GenerateParallaxFormula(translationParam + "." + nameof(scrollViewer.Translation.Y), qualifiedSpeedParam, qualifiedOffsetYParam, qualifiedImageHeightParam);
}

if (scrollOrientation == ScrollOrientation.Horizontal || scrollOrientation == ScrollOrientation.Both)
{
expressionX = expressionXVal;
expressionX.Expression = expressionXVal;

if (scrollOrientation == ScrollOrientation.Horizontal)
{
// In horizontal mode we never move the offset y
expressionY = (ScalarNode)0.0f;
expressionY.Expression = "0";
_containerVisual.Offset = new Vector3((float)OffsetY, 0, 0);
}
}

if (scrollOrientation == ScrollOrientation.Vertical || scrollOrientation == ScrollOrientation.Both)
{
expressionY = expressionYVal;
expressionY.Expression = expressionYVal;

if (scrollOrientation == ScrollOrientation.Vertical)
{
// In vertical mode we never move the offset x
expressionX = (ScalarNode)0.0f;
expressionX.Expression = "0";
_containerVisual.Offset = new Vector3(0, (float)OffsetX, 0);
}
}
Expand Down