public void StartEntranceEffect() { ContainerVisual container = (ContainerVisual)ElementCompositionPreview.GetElementChildVisual(BasePage); Compositor compositor = container.Compositor; // 设置缩放和动画 const float ScaleFactor = 20f; TimeSpan duration = TimeSpan.FromMilliseconds(1200); LinearEasingFunction linearEase = compositor.CreateLinearEasingFunction(); CubicBezierEasingFunction easeInOut = compositor.CreateCubicBezierEasingFunction(new Vector2(.38f, 0f), new Vector2(.45f, 1f)); // 创建淡出动画 ScalarKeyFrameAnimation fadeOutAnimation = compositor.CreateScalarKeyFrameAnimation(); fadeOutAnimation.InsertKeyFrame(1, 0); fadeOutAnimation.Duration = duration; // Grid的动画 Vector2KeyFrameAnimation scaleUpGridAnimation = compositor.CreateVector2KeyFrameAnimation(); scaleUpGridAnimation.InsertKeyFrame(0.1f, new Vector2(1 / ScaleFactor, 1 / ScaleFactor)); scaleUpGridAnimation.InsertKeyFrame(1, new Vector2(1, 1)); scaleUpGridAnimation.Duration = duration; // 初始屏动画 Vector2KeyFrameAnimation scaleUpSplashAnimation = compositor.CreateVector2KeyFrameAnimation(); scaleUpSplashAnimation.InsertKeyFrame(0, new Vector2(1, 1)); scaleUpSplashAnimation.InsertKeyFrame(1, new Vector2(ScaleFactor, ScaleFactor)); scaleUpSplashAnimation.Duration = duration; // 设置Grid的中心缩放视觉 Visual gridVisual = ElementCompositionPreview.GetElementVisual(UIToShow); gridVisual.Size = UIToShow.ActualSize; gridVisual.CenterPoint = new Vector3(gridVisual.Size.X, gridVisual.Size.Y, 0) * .5f; // 创建一个视觉组,当改组所有视觉执行完后不再显示 CompositionScopedBatch batch = compositor.CreateScopedBatch(CompositionBatchTypes.Animation); container.StartAnimation("Opacity", fadeOutAnimation); container.StartAnimation("Scale.XY", scaleUpSplashAnimation); gridVisual.StartAnimation("Scale.XY", scaleUpGridAnimation); batch.Completed += (s, a) => { ElementCompositionPreview.SetElementChildVisual(BasePage, null); SurfaceLoader.Uninitialize(); AnimationCompleted?.Invoke(this, null); }; batch.End(); }
private void HideCustomSplashScreen() { ContainerVisual container = (ContainerVisual)ElementCompositionPreview.GetElementChildVisual(this); Compositor compositor = container.Compositor; // Setup some constants for scaling and animating const float ScaleFactor = 20f; TimeSpan duration = TimeSpan.FromMilliseconds(1200); LinearEasingFunction linearEase = compositor.CreateLinearEasingFunction(); CubicBezierEasingFunction easeInOut = compositor.CreateCubicBezierEasingFunction(new Vector2(.38f, 0f), new Vector2(.45f, 1f)); // Create the fade animation which will target the opacity of the outgoing splash screen ScalarKeyFrameAnimation fadeOutAnimation = compositor.CreateScalarKeyFrameAnimation(); fadeOutAnimation.InsertKeyFrame(1, 0); fadeOutAnimation.Duration = duration; // Create the scale up animation for the grid Vector2KeyFrameAnimation scaleUpGridAnimation = compositor.CreateVector2KeyFrameAnimation(); scaleUpGridAnimation.InsertKeyFrame(0.1f, new Vector2(1 / ScaleFactor, 1 / ScaleFactor)); scaleUpGridAnimation.InsertKeyFrame(1, new Vector2(1, 1)); scaleUpGridAnimation.Duration = duration; // Create the scale up animation for the Splash screen visuals Vector2KeyFrameAnimation scaleUpSplashAnimation = compositor.CreateVector2KeyFrameAnimation(); scaleUpSplashAnimation.InsertKeyFrame(0, new Vector2(1, 1)); scaleUpSplashAnimation.InsertKeyFrame(1, new Vector2(ScaleFactor, ScaleFactor)); scaleUpSplashAnimation.Duration = duration; // Configure the grid visual to scale from the center Visual gridVisual = ElementCompositionPreview.GetElementVisual(MainFrame); gridVisual.Size = new Vector2((float)MainFrame.ActualWidth, (float)MainFrame.ActualHeight); gridVisual.CenterPoint = new Vector3(gridVisual.Size.X, gridVisual.Size.Y, 0) * .5f; // // Create a scoped batch for the animations. When the batch completes, we can dispose of the // splash screen visuals which will no longer be visible. // CompositionScopedBatch batch = compositor.CreateScopedBatch(CompositionBatchTypes.Animation); container.StartAnimation("Opacity", fadeOutAnimation); container.StartAnimation("Scale.XY", scaleUpSplashAnimation); gridVisual.StartAnimation("Scale.XY", scaleUpGridAnimation); batch.Completed += Batch_Completed; batch.End(); }
private ContainerVisual TriggerSlideAnimation(ContainerVisual colorVisual, int delay, int duration = 500) { var size = colorVisual.Size; Compositor compositor = colorVisual.Compositor; ScalarKeyFrameAnimation slideAnimation = compositor.CreateScalarKeyFrameAnimation(); colorVisual.Offset = new Vector3(colorVisual.Offset.X, colorVisual.Offset.Y + colorVisual.Size.Y, 0); colorVisual.Orientation = new Quaternion(1, 0, 0, 0); colorVisual.Size = new Vector2(size.X, 0); if (this.previousSpriteVisual != null && this.previousSpriteVisual.Offset.X == colorVisual.Offset.X) { slideDelay += (duration - delay); } else { slideDelay += delay; } slideAnimation.DelayTime = TimeSpan.FromMilliseconds(slideDelay); slideAnimation.InsertKeyFrame(1f, size.Y); slideAnimation.Duration = TimeSpan.FromMilliseconds(duration); colorVisual.StartAnimation("Size.Y", slideAnimation); this.previousSpriteVisual = colorVisual; return(colorVisual); }
public void ConfigureInteractionTracker() { var backgroundVisual = ElementCompositionPreview.GetElementVisual(MainGrid); backgroundVisual.Size = new Vector2((float)MainGrid.ActualWidth, (float)MainGrid.ActualHeight); // Configure interaction tracker _tracker = InteractionTracker.CreateWithOwner(_compositor, this); _tracker.MaxPosition = new Vector3((float)backgroundVisual.Size.X * _scenarios.Count, backgroundVisual.Size.Y, 0); _tracker.MinPosition = new Vector3(); // Configure interaction source _interactionSource = VisualInteractionSource.Create(backgroundVisual); _interactionSource.PositionXSourceMode = InteractionSourceMode.EnabledWithInertia; _tracker.InteractionSources.Add(_interactionSource); // Bind interaction tracker output to animation for now var positionExpression = -_tracker.GetReference().Position.X; _mainContainer.StartAnimation("Offset.X", positionExpression); ConfigureRestingPoints(); var nestedVisuals = _nestedScenario.GetVisuals(); var exp = _compositor.CreateExpressionAnimation(); for (int i = 0; i < nestedVisuals.Count; i++) { ConfigureParallax(i, nestedVisuals[i]); } }
public void StartEntranceEffect() { try { TimeSpan AnimationDuration = TimeSpan.FromMilliseconds(1000); ContainerVisual Container = (ContainerVisual)ElementCompositionPreview.GetElementChildVisual(BasePage); ScalarKeyFrameAnimation FadeOutAnimation = Container.Compositor.CreateScalarKeyFrameAnimation(); FadeOutAnimation.InsertKeyFrame(1, 0); FadeOutAnimation.Duration = AnimationDuration; Vector2KeyFrameAnimation ScaleUIAnimation = Container.Compositor.CreateVector2KeyFrameAnimation(); ScaleUIAnimation.InsertKeyFrame(0.1f, new Vector2(1 / ScaleFactor, 1 / ScaleFactor)); ScaleUIAnimation.InsertKeyFrame(1, new Vector2(1, 1)); ScaleUIAnimation.Duration = AnimationDuration; Vector2KeyFrameAnimation ScaleSplashAnimation = Container.Compositor.CreateVector2KeyFrameAnimation(); ScaleSplashAnimation.InsertKeyFrame(0, new Vector2(1, 1)); ScaleSplashAnimation.InsertKeyFrame(1, new Vector2(ScaleFactor, ScaleFactor)); ScaleSplashAnimation.Duration = AnimationDuration; Visual UIVisual = ElementCompositionPreview.GetElementVisual(UIToShow); UIVisual.Size = new Vector2((float)UIToShow.ActualWidth, (float)UIToShow.ActualHeight); UIVisual.CenterPoint = new Vector3(UIVisual.Size.X, UIVisual.Size.Y, 0) * 0.5f; CompositionScopedBatch BatchAnimation = Container.Compositor.CreateScopedBatch(CompositionBatchTypes.Animation); Container.StartAnimation("Opacity", FadeOutAnimation); Container.StartAnimation("Scale.XY", ScaleSplashAnimation); UIVisual.StartAnimation("Scale.XY", ScaleUIAnimation); BatchAnimation.Completed += (s, a) => { ElementCompositionPreview.SetElementChildVisual(BasePage, null); SurfaceLoader.Uninitialize(); AnimationCompleted?.Invoke(this, null); }; BatchAnimation.End(); } catch (Exception ex) { Debug.WriteLine($"Error in StartEntranceEffect, message:{ex.Message}"); } }
void TouchArea_ManipulationCompleted(object sender, ManipulationCompletedRoutedEventArgs e) { // once the finger lifts up, add another key frame and // kick off the finish animation to roll back the visuals' offset _animation.InsertKeyFrame(1.0f, 0.0f); _visual.StartAnimation("Offset.X", _animation); _touchAreaVisual.StartAnimation("Offset.X", _animation); }
public void Rotate(float value, float duration = 0) { ScalarKeyFrameAnimation rotateAnimation = _compositor.CreateScalarKeyFrameAnimation(); rotateAnimation.InsertKeyFrame(1, value, _compositor.CreateLinearEasingFunction()); rotateAnimation.Duration = TimeSpan.FromMilliseconds(duration); rotateAnimation.IterationBehavior = AnimationIterationBehavior.Forever; _skyboxContainer.StartAnimation("RotationAngleInDegrees", rotateAnimation); }
/// <summary> /// Start animations and re-parent to destination UI Elmenent to finish the operations /// </summary> /// <param name="destinationElement">Destination UIElement where Visual should show up after page has loaded</param> /// <param name="containerVisual">ContainerVisual that contains Visual which needs to show in UIElement</param> /// <param name="newContainerVisual">ContainerVisual after visual is parented to UIElement</param> public static void InitiateContinuity(FrameworkElement destinationElement, ContainerVisual containerVisual, out ContainerVisual newContainerVisual) { if (null == containerVisual || null == destinationElement) { newContainerVisual = null; return; } //Get the frame of Window var rootFrame = AppShell.Current; Visual rootVisual = ElementCompositionPreview.GetElementVisual(AppShell.Current); Compositor compositor = rootVisual.Compositor; //Create Temporary Container. this will be added to final UIElement ContainerVisual tempContainer = compositor.CreateContainerVisual(); // Get Sprite Visual from incoming container var spriteHeroImage = containerVisual.Children.FirstOrDefault(); //Create animation scoped batch to track animation completion and to complete re-parenting CompositionScopedBatch scopeBatch = compositor.CreateScopedBatch(CompositionBatchTypes.Animation); //Get coordinates of UIElement in reference to root so that it can be used for animations final value var coordinate = destinationElement.TransformToVisual(rootFrame); var position = coordinate.TransformPoint(new Point(0, 0)); //Create offset animation to make visual move on screen Vector3KeyFrameAnimation offsetAnimation = compositor.CreateVector3KeyFrameAnimation(); offsetAnimation.InsertKeyFrame(1f, new System.Numerics.Vector3((float)position.X, (float)position.Y, 0)); offsetAnimation.Duration = TimeSpan.FromMilliseconds(600); //Create size animation to change size of the visuals Vector2KeyFrameAnimation sizeAnimation = compositor.CreateVector2KeyFrameAnimation(); sizeAnimation.InsertKeyFrame(1f, new System.Numerics.Vector2((float)destinationElement.ActualWidth, (float)destinationElement.ActualHeight)); sizeAnimation.Duration = TimeSpan.FromMilliseconds(600); //Start Animations spriteHeroImage.StartAnimation("size", sizeAnimation); containerVisual.StartAnimation("offset", offsetAnimation); //Scoped batch completed event. scopeBatch.Completed += (o, e) => { //Re-parent SpriteVisual to temp container and add temp container to UIElement as animations are finished. spriteHeroImage.Offset = new System.Numerics.Vector3(0, 0, 2000); containerVisual.Children.Remove(spriteHeroImage); tempContainer.Children.InsertAtTop(spriteHeroImage); ElementCompositionPreview.SetElementChildVisual(destinationElement, tempContainer); containerVisual = null; }; newContainerVisual = tempContainer; scopeBatch.End(); }
private void SetupSkyVisualOffsetExpressionAnimation() { // Kick off an expression animation that links the roll & pitch degress to the -offset of the sky canvas visual // TODO: Need to constraint the offset (total offset < dimension * SkyVisualAreaRatio) with // CreateConditionalExpressionAnimation once the next mobile build is available. _skyViusalOffsetExpressionAnimation = _compositor.CreateExpressionAnimation( "Vector3(SkyVisual.Offset.X - Reading.Offset.X * Sensitivity, SkyVisual.Offset.Y - Reading.Offset.Y * Sensitivity, 0.0f)"); _skyViusalOffsetExpressionAnimation.SetReferenceParameter("SkyVisual", _skyVisual); _skyViusalOffsetExpressionAnimation.SetReferenceParameter("Reading", _reading); _skyViusalOffsetExpressionAnimation.SetScalarParameter("Sensitivity", 0.2f); //_skyViusalOffsetExpressionAnimation.SetScalarParameter("MaxDimension", SkyVisualRadius * 2 * SkyVisualAreaRatio); _skyVisual.StartAnimation("Offset", _skyViusalOffsetExpressionAnimation); }
private ContainerVisual TriggerMoveAnimation(ContainerVisual colorVisual, int delay, int duration = 500) { Compositor compositor = colorVisual.Compositor; var animation = compositor.CreateVector3KeyFrameAnimation(); var oldOffset = colorVisual.Offset; colorVisual.Offset = new Vector3(oldOffset.X, oldOffset.Y + moveOffsetChange, 0); animation.DelayTime = TimeSpan.FromMilliseconds(slideDelay); animation.InsertKeyFrame(1f, new Vector3(oldOffset.X, oldOffset.Y, 0)); animation.Duration = TimeSpan.FromMilliseconds(duration); colorVisual.StartAnimation("Offset", animation); slideDelay += delay; return(colorVisual); }
public async Task LoadImageAsync() { try { await RenderImageAsync(); if (!_firstTimeLoad) { ImageLoaded?.Invoke(this, EventArgs.Empty); return; } var batch = _compositor.CreateScopedBatch(CompositionBatchTypes.Animation); EnsureOpacityAnimation(); ContainerVisual.StartAnimation(nameof(Visual.Opacity), _opacityAnimation); batch.Completed += (sender, args) => ImageLoaded?.Invoke(this, EventArgs.Empty); batch.End(); } catch { // do nowt } }
/// <summary> /// Handles the Arrange layout phase /// </summary> /// <param name="finalSize">Final Size of the control</param> /// <returns>Size</returns> protected override Size ArrangeOverride(Size finalSize) { if ((_compositor == null) || (_generator == null)) { return(base.ArrangeOverride(finalSize)); } if (Double.IsInfinity(finalSize.Width) || Double.IsInfinity(finalSize.Height) || Double.IsNaN(finalSize.Width) || Double.IsNaN(finalSize.Height)) { return(base.ArrangeOverride(finalSize)); } // Stop the animations and dispose the previous nodes // and their animations if (_isAnimationStarted) { for (var i = 0; i < _nodes.Count; i++) { _nodes[i].StopAnimation(AnimatedProperty); _animations[i].Dispose(); _animations[i] = null; _nodes[i].Dispose(); _nodes[i] = null; } _container.StopAnimation(AnimatedProperty); _containerAnimation.Dispose(); _containerAnimation = null; _container.Dispose(); _container = null; _animations.Clear(); _nodes.Clear(); _isAnimationStarted = false; } // Validate MaxNodes and ActiveNodes if ((MaxNodes <= 0) || (ActiveNodes <= 0)) { return(finalSize); } // Coerce ActiveNodes to MaxNodes if ActiveNodes > MaxNodes if (ActiveNodes > MaxNodes) { ActiveNodes = MaxNodes; } // Take the smallest of the width or height var sideLength = (float)Math.Min(finalSize.Width, finalSize.Height); // Size of the progress ring displayed _ringSize = new Vector2(sideLength, sideLength); var sideHalf = sideLength / 2f; // Radius of each node _nodeRadius = (float)NodeSizeFactor * sideHalf; // Size of each node _nodeSize = new Vector2(_nodeRadius * 2f, _nodeRadius * 2f); // Radius of the node _ringRadius = sideHalf - _nodeRadius; // Each animation will consist of '_maxFrameBlocks' number of // FrameBlocks. Each FrameBlock will consist of keyframes which allow // the element being animated to move to the next slot and wait // at that slot until all other elements have moved to their next slot. // Each FrameBlock (except the last one) will have '_maxFramesPerBlock' // number of keyframes. The last keyframe in the last FrameBlock // will always be (1f, "this.StartingValue + 360") // Total number of FrameBlocks in each animation _maxFrameBlocks = ActiveNodes; // Total keyframes in each FrameBlock _maxFramesPerBlock = ActiveNodes + 1; // Total keyframes in each animation _maxKeyFrames = _maxFrameBlocks * _maxFramesPerBlock; // Normalized Progress Key unit value for each keyframe _keyFrameSlice = 1f / _maxKeyFrames; // ======================================================================== // NOTE: // gamma * maxNodes = 360 // gamma = alpha + beta // beta = 2 * Asin(nodeRadius / ringRadius) * (180 / Math.PI) // invisibleNodes = MaxNodes - ActiveNodes // phi = (invisibleNodes * gamma) // theta = phi - beta // ======================================================================== // gamma is the angle between adjacent nodes when maxNodes number of nodes are arranged in a circle _gamma = 360f / MaxNodes; // beta is the angle a node must travel after hitting the adjacent node _beta = 2f * (float)(Math.Asin(_nodeRadius / _ringRadius) * (180f / Math.PI)); // alpha is the smallest angle a node must travel before hitting the adjacent node _alpha = _gamma - _beta; // phi is the angle occupied by (MaxNodes - ActiveNodes) number of nodes _phi = (MaxNodes - ActiveNodes + 1) * _gamma; // theta is the largest angle a node must travel before hitting the adjacent node _theta = _phi - _beta; // Create the Animations _animations = CreateAnimations(); // Create the Container _container = _compositor.CreateContainerVisual(); _container.Size = _ringSize; _container.Offset = new Vector3(((float)finalSize.Width - sideLength) / 2f, ((float)finalSize.Height - sideLength) / 2f, 0f); _container.CenterPoint = new Vector3(_ringSize.X / 2f, _ringSize.Y / 2f, 0f); // Create the Nodes var offset = new Vector3(_nodeRadius, _ringSize.Y / 2f, 0); var centerPoint = new Vector3(_ringSize.X / 2f - _nodeRadius, 0, 0); _nodes = new List <SpriteVisual>(); var geometry = CanvasGeometry.CreateCircle(_generator.Device, new Vector2(_nodeRadius, _nodeRadius), _nodeRadius); // Create/Update the nodeMask var color = NodeColor; if (_nodeMask == null) { //Task.Run(async () => // { _nodeMask = _generator.CreateGeometrySurface(_nodeSize.ToSize(), geometry, color); // }) //.Wait(); } else { //Task.Run(async () => // { _nodeMask.Redraw(_nodeSize.ToSize(), geometry, color); // }) //.Wait(); } // Create the SurfaceBrush for the nodes var brush = _compositor.CreateSurfaceBrush(_nodeMask.Surface); var baseAngle = 0f; // Create the visuals for the nodes for (var i = 0; i < _maxFramesPerBlock; i++) { var node = _compositor.CreateSpriteVisual(); node.Size = _nodeSize; node.AnchorPoint = new Vector2(0.5f); node.Offset = offset; node.CenterPoint = centerPoint; node.Brush = brush; node.RotationAngleInDegrees = baseAngle; if (i == 0) { baseAngle += _phi; } else if (i == _maxFramesPerBlock - 2) { baseAngle = -_beta; } else { baseAngle += _gamma; } _nodes.Add(node); // Add the visual to the container _container.Children.InsertAtTop(node); } // Add the container to the Visual Tree ElementCompositionPreview.SetElementChildVisual(this, _container); // Start Node animations for (var i = 0; i < _maxFramesPerBlock; i++) { _nodes[i].StartAnimation(AnimatedProperty, _animations[i]); } // Start container animation _containerAnimation = _compositor.CreateScalarKeyFrameAnimation(); _containerAnimation.InsertExpressionKeyFrame(1f, "this.StartingValue + 360f", _compositor.CreateLinearEasingFunction()); _containerAnimation.Duration = RingDuration; _containerAnimation.IterationBehavior = AnimationIterationBehavior.Forever; _container.StartAnimation(AnimatedProperty, _containerAnimation); _isAnimationStarted = true; return(finalSize); }
/// <summary> /// Creation of an expression to manage modulo (positive and negative value) /// </summary> /// <param name="scrollViewer">The ScrollViewer to synchronized. A null value is valid</param> /// <param name="imageWidth">Width of the image</param> /// <param name="imageHeight">Height of the image</param> /// <param name="scrollOrientation">The ScrollOrientation</param> private void CreateModuloExpression(ScrollViewer scrollViewer, double imageWidth, double imageHeight, ScrollOrientation scrollOrientation) { 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) { return; } var compositor = _containerVisual.Compositor; // Setup the expression var expressionX = compositor.CreateExpressionAnimation(); var expressionY = compositor.CreateExpressionAnimation(); var propertySetModulo = compositor.CreatePropertySet(); propertySetModulo.InsertScalar(imageWidthParam, (float)imageWidth); propertySetModulo.InsertScalar(offsetXParam, (float)OffsetX); propertySetModulo.InsertScalar(imageHeightParam, (float)imageHeight); propertySetModulo.InsertScalar(offsetYParam, (float)OffsetY); propertySetModulo.InsertScalar(speedParam, (float)ParallaxSpeedRatio); 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) { // expressions are created to simulate a positive and negative modulo with the size of the image and the offset 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); 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.Expression = expressionXVal; if (scrollOrientation == ScrollOrientation.Horizontal) { // In horizontal mode we never move the offset y expressionY.Expression = "0"; _containerVisual.Offset = new Vector3((float)OffsetY, 0, 0); } } if (scrollOrientation == ScrollOrientation.Vertical || scrollOrientation == ScrollOrientation.Both) { expressionY.Expression = expressionYVal; if (scrollOrientation == ScrollOrientation.Vertical) { // In vertical mode we never move the offset x expressionX.Expression = "0"; _containerVisual.Offset = new Vector3(0, (float)OffsetX, 0); } } _containerVisual.StopAnimation("Offset.X"); _containerVisual.StopAnimation("Offset.Y"); _containerVisual.StartAnimation("Offset.X", expressionX); _containerVisual.StartAnimation("Offset.Y", expressionY); _propertySetModulo = propertySetModulo; }
/// <summary> /// Creation of an expression to manage modulo (positive and negative value) /// </summary> /// <param name="scrollviewer">The ScrollViewer to synchonized. A null value is valid</param> /// <param name="imageWidth">Width of the image</param> /// <param name="imageHeight">Height of the image</param> /// <param name="scrollOrientation">The ScrollOrientation</param> private void CreateModuloExpression(ScrollViewer scrollviewer, double imageWidth, double imageHeight, ScrollOrientation scrollOrientation) { if (Strategy == UIStrategy.PureXaml) { return; } if (_containerVisual == null) { return; } var compositor = _containerVisual.Compositor; // Setup the expression var expressionX = compositor.CreateExpressionAnimation(); var expressionY = compositor.CreateExpressionAnimation(); var propertyOffsetModulo = compositor.CreatePropertySet(); propertyOffsetModulo.InsertScalar("imageWidth", (float)imageWidth); propertyOffsetModulo.InsertScalar("offsetX", (float)OffsetX); propertyOffsetModulo.InsertScalar("imageHeight", (float)imageHeight); propertyOffsetModulo.InsertScalar("offsetY", (float)OffsetY); propertyOffsetModulo.InsertScalar("speed", (float)ParallaxSpeedRatio); expressionX.SetReferenceParameter("p", propertyOffsetModulo); expressionY.SetReferenceParameter("p", propertyOffsetModulo); string expressionXString = null; string expressionYString = null; if (scrollviewer == null) { // expressions are create to simulate a positive and negative modulo with the size of the image and the offset expressionXString = "Ceil(p.offsetX) == 0 ? 0 : (Ceil(p.offsetX) < 0 ? -(Abs(Ceil(p.offsetX) - Ceil(Ceil(p.offsetX) / p.imageWidth) * p.imageWidth) % p.imageWidth) : -(p.imageWidth - (Ceil(p.offsetX) % p.imageWidth)))"; expressionYString = "Ceil(p.offsetY) == 0 ? 0 : (Ceil(p.offsetY) < 0 ? -(Abs(Ceil(p.offsetY) - Ceil(Ceil(p.offsetY) / p.imageHeight) * p.imageHeight) % p.imageHeight) : -(p.imageHeight - (Ceil(p.offsetY) % p.imageHeight)))"; } else { // expressions are create 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); expressionX.SetReferenceParameter("s", scrollProperties); expressionY.SetReferenceParameter("s", scrollProperties); expressionXString = "Ceil((s.Translation.X * p.speed) + p.offsetX) == 0 ? 0 : (Ceil((s.Translation.X * p.speed) + p.offsetX) < 0 ? -(Abs(Ceil((s.Translation.X * p.speed) + p.offsetX) - Ceil(Ceil((s.Translation.X * p.speed) + p.offsetX) / p.imageWidth) * p.imageWidth) % p.imageWidth) : -(p.imageWidth - (Ceil((s.Translation.X * p.speed) + p.offsetX) % p.imageWidth)))"; expressionYString = "Ceil((s.Translation.Y * p.speed) + p.offsetY) == 0 ? 0 : (Ceil((s.Translation.Y * p.speed) + p.offsetY) < 0 ? -(Abs(Ceil((s.Translation.Y * p.speed) + p.offsetY) - Ceil(Ceil((s.Translation.Y * p.speed) + p.offsetY) / p.imageHeight) * p.imageHeight) % p.imageHeight) : -(p.imageHeight - (Ceil((s.Translation.Y * p.speed) + p.offsetY) % p.imageHeight)))"; } if (scrollOrientation == ScrollOrientation.Horizontal || scrollOrientation == ScrollOrientation.Both) { expressionX.Expression = expressionXString; if (scrollOrientation == ScrollOrientation.Horizontal) { // In horizontal mode we never move the offset y expressionY.Expression = "0"; _containerVisual.Offset = new Vector3((float)OffsetY, 0, 0); } } if (scrollOrientation == ScrollOrientation.Vertical || scrollOrientation == ScrollOrientation.Both) { expressionY.Expression = expressionYString; if (scrollOrientation == ScrollOrientation.Vertical) { // In vertical mode we never move the offset x expressionX.Expression = "0"; _containerVisual.Offset = new Vector3(0, (float)OffsetX, 0); } } _containerVisual.StopAnimation("Offset.X"); _containerVisual.StopAnimation("Offset.Y"); _containerVisual.StartAnimation("Offset.X", expressionX); _containerVisual.StartAnimation("Offset.Y", expressionY); _propertyOffsetModulo = propertyOffsetModulo; }
/// <summary> /// Start animations and re-parent to destination UI Elmenent to finish the operations /// </summary> /// <param name="destinationElement">Destination UIElement where Visual should show up after page has loaded</param> /// <param name="containerVisual">ContainerVisual that contains Visual which needs to show in UIElement</param> /// <param name="newContainerVisual">ContainerVisual after visual is parented to UIElement</param> public static void InitiateContinuity(FrameworkElement destinationElement, ContainerVisual containerVisual, out ContainerVisual newContainerVisual) { if (null == containerVisual || null == destinationElement) { newContainerVisual = null; return; } //Get the frame of Window Frame rootFrame = Window.Current.Content as Frame; Visual rootVisual = ElementCompositionPreview.GetElementVisual(rootFrame); Compositor compositor = rootVisual.Compositor; //Create Temporary Container. this will be added to final UIElement ContainerVisual _TempContainer = compositor.CreateContainerVisual(); // Get Sprite Visual from incoming container var spriteHeroImage = containerVisual.Children.FirstOrDefault(); //Create animation scoped batch to track animation completion and to complete re-parenting CompositionScopedBatch scopeBatch = compositor.CreateScopedBatch(CompositionBatchTypes.Animation); //Get coordinates of UIElement in reference to root so that it can be used for animations final value var coordinate = destinationElement.TransformToVisual(rootFrame); var position = coordinate.TransformPoint(new Point(0, 0)); //Create offset animation to make visual move on screen Vector3KeyFrameAnimation offsetAnimation = compositor.CreateVector3KeyFrameAnimation(); offsetAnimation.InsertKeyFrame(1f, new System.Numerics.Vector3((float)position.X, (float)position.Y, 0)); offsetAnimation.Duration = TimeSpan.FromMilliseconds(600); //Create size animation to change size of the visuals Vector2KeyFrameAnimation sizeAnimation = compositor.CreateVector2KeyFrameAnimation(); sizeAnimation.InsertKeyFrame(1f, new System.Numerics.Vector2((float)destinationElement.ActualWidth, (float)destinationElement.ActualHeight)); sizeAnimation.Duration = TimeSpan.FromMilliseconds(600); //Start Animations spriteHeroImage.StartAnimation("size", sizeAnimation); containerVisual.StartAnimation("offset", offsetAnimation); //Scoped batch completed event. scopeBatch.Completed += (o, e) => { //Re-parent SpriteVisual to temp container and add temp container to UIElement as animations are finished. spriteHeroImage.Offset = new System.Numerics.Vector3(0, 0, 0); containerVisual.Children.Remove(spriteHeroImage); _TempContainer.Children.InsertAtTop(spriteHeroImage); ElementCompositionPreview.SetElementChildVisual(destinationElement, _TempContainer); containerVisual = null; }; newContainerVisual = _TempContainer; scopeBatch.End(); }
/// <summary> /// Creation of an expression to manage modulo (positive and negative value) /// </summary> /// <param name="scrollViewer">The ScrollViewer to synchronized. A null value is valid</param> /// <param name="imageWidth">Width of the image</param> /// <param name="imageHeight">Height of the image</param> /// <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"; if (_containerVisual == null) { return; } var compositor = _containerVisual.Compositor; // Setup the expression ExpressionNode expressionX = null; ExpressionNode expressionY = null; ExpressionNode expressionXVal; ExpressionNode expressionYVal; var propertySetModulo = compositor.CreatePropertySet(); propertySetModulo.InsertScalar(imageWidthParam, (float)imageWidth); propertySetModulo.InsertScalar(offsetXParam, (float)OffsetX); propertySetModulo.InsertScalar(imageHeightParam, (float)imageHeight); propertySetModulo.InsertScalar(offsetYParam, (float)OffsetY); propertySetModulo.InsertScalar(speedParam, (float)ParallaxSpeedRatio); var propertySetNodeModulo = propertySetModulo.GetReference(); var imageHeightNode = propertySetNodeModulo.GetScalarProperty(imageHeightParam); var imageWidthNode = propertySetNodeModulo.GetScalarProperty(imageWidthParam); 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)))); } 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)))); } if (scrollOrientation == ScrollOrientation.Horizontal || scrollOrientation == ScrollOrientation.Both) { expressionX = expressionXVal; if (scrollOrientation == ScrollOrientation.Horizontal) { // In horizontal mode we never move the offset y expressionY = (ScalarNode)0.0f; _containerVisual.Offset = new Vector3((float)OffsetY, 0, 0); } } if (scrollOrientation == ScrollOrientation.Vertical || scrollOrientation == ScrollOrientation.Both) { expressionY = expressionYVal; if (scrollOrientation == ScrollOrientation.Vertical) { // In vertical mode we never move the offset x expressionX = (ScalarNode)0.0f; _containerVisual.Offset = new Vector3(0, (float)OffsetX, 0); } } _containerVisual.StopAnimation("Offset.X"); _containerVisual.StopAnimation("Offset.Y"); _containerVisual.StartAnimation("Offset.X", expressionX); _containerVisual.StartAnimation("Offset.Y", expressionY); _propertySetModulo = propertySetModulo; }
private void SamplePage_Loaded(object sender, Windows.UI.Xaml.RoutedEventArgs e) { // // Example 1 - Animate a tree of XAML content. ElementCompositionPreview.GetElementVisual() // returns the Visual which contains all Visual children under the target // UIElement including that UIELement's Visuals. The returned Visual // can be used to manipulate the XAML tree, but you cannot add to or modify // the Visual tree. // Visual visual = ElementCompositionPreview.GetElementVisual(TextBlock1); Compositor compositor = visual.Compositor; // Apply a simple animation to the XAML content var animation = compositor.CreateVector3KeyFrameAnimation(); animation.InsertKeyFrame(0.5f, new Vector3(100, 0, 0)); animation.InsertKeyFrame(1.0f, new Vector3(0, 0, 0)); animation.Duration = TimeSpan.FromMilliseconds(4000); animation.IterationBehavior = AnimationIterationBehavior.Forever; visual.StartAnimation("Offset", animation); // // Example 2 - Add some Windows.UI.Composition content to the XAML tree. // ElementCompositionPreview.SetElementChildVisual() sets a Windows.UI.Composition // Visual as the child of the target UIElement. You can use this Visual as the // basis for creating a tree of Windows.UI.Composition content under the target // UIElement in the tree. // ContainerVisual container = compositor.CreateContainerVisual(); ElementCompositionPreview.SetElementChildVisual(TextBlock2, container); // Add some solid color sprites under the container. SpriteVisual redSprite = compositor.CreateSpriteVisual(); redSprite.Brush = compositor.CreateColorBrush(Colors.Red); redSprite.Size = new Vector2(100f, 100f); redSprite.Offset = new Vector3(0f, (float)TextBlock2.RenderSize.Height, 0f); container.Children.InsertAtTop(redSprite); SpriteVisual blueSprite = compositor.CreateSpriteVisual(); blueSprite.Brush = compositor.CreateColorBrush(Colors.Blue); blueSprite.Size = new Vector2(100f, 100f); blueSprite.Offset = new Vector3(100f, (float)TextBlock2.RenderSize.Height, 0f); container.Children.InsertAtTop(blueSprite); // Start the same animation container.StartAnimation("Offset", animation); // // Example 3 - Add some Windows.UI.Composition content to the XAML tree and modify their tree order. // _container2 = compositor.CreateContainerVisual(); ElementCompositionPreview.SetElementChildVisual(TextBlock3, _container2); _container2.Offset = new Vector3(0, 50f, 0f); // Add some solid color sprites under the container. SpriteVisual orangeSprite = compositor.CreateSpriteVisual(); orangeSprite.Brush = compositor.CreateColorBrush(Colors.Orange); orangeSprite.Size = new Vector2(100f, 100f); orangeSprite.Offset = new Vector3(0f, 0f, 0f); _container2.Children.InsertAtTop(orangeSprite); SpriteVisual greenSprite = compositor.CreateSpriteVisual(); greenSprite.Brush = compositor.CreateColorBrush(Colors.Green); greenSprite.Size = new Vector2(100f, 100f); greenSprite.Offset = new Vector3(50f, 0f, 0f); _container2.Children.InsertAtTop(greenSprite); SpriteVisual purpleSprite = compositor.CreateSpriteVisual(); purpleSprite.Brush = compositor.CreateColorBrush(Colors.Purple); purpleSprite.Size = new Vector2(100f, 100f); purpleSprite.Offset = new Vector3(100f, 0f, 0f); _container2.Children.InsertAtTop(purpleSprite); // Start a timer, when it fires rearrange the sprites in the tree. DispatcherTimer timer = new DispatcherTimer(); timer.Tick += Timer_Tick; timer.Interval = new TimeSpan(0, 0, 1); timer.Start(); }
/// <summary> /// Handles the Arrange layout phase /// </summary> /// <param name="finalSize">Final Size of the control</param> /// <returns>Size</returns> protected override Size ArrangeOverride(Size finalSize) { if ((_compositor == null) || (_generator == null)) return base.ArrangeOverride(finalSize); if (Double.IsInfinity(finalSize.Width) || Double.IsInfinity(finalSize.Height) || Double.IsNaN(finalSize.Width) || Double.IsNaN(finalSize.Height)) return base.ArrangeOverride(finalSize); // Stop the animations and dispose the previous nodes // and their animations if (_isAnimationStarted) { for (var i = 0; i < _nodes.Count; i++) { _nodes[i].StopAnimation(AnimatedProperty); _animations[i].Dispose(); _animations[i] = null; _nodes[i].Dispose(); _nodes[i] = null; } _container.StopAnimation(AnimatedProperty); _containerAnimation.Dispose(); _containerAnimation = null; _container.Dispose(); _container = null; _animations.Clear(); _nodes.Clear(); _isAnimationStarted = false; } // Validate MaxNodes and ActiveNodes if ((MaxNodes <= 0) || (ActiveNodes <= 0)) return finalSize; // Coerce ActiveNodes to MaxNodes if ActiveNodes > MaxNodes if (ActiveNodes > MaxNodes) ActiveNodes = MaxNodes; // Take the smallest of the width or height var sideLength = (float)Math.Min(finalSize.Width, finalSize.Height); // Size of the progress ring displayed _ringSize = new Vector2(sideLength, sideLength); var sideHalf = sideLength / 2f; // Radius of each node _nodeRadius = (float)NodeSizeFactor * sideHalf; // Size of each node _nodeSize = new Vector2(_nodeRadius * 2f, _nodeRadius * 2f); // Radius of the node _ringRadius = sideHalf - _nodeRadius; // Each animation will consist of '_maxFrameBlocks' number of // FrameBlocks. Each FrameBlock will consist of keyframes which allow // the element being animated to move to the next slot and wait // at that slot until all other elements have moved to their next slot. // Each FrameBlock (except the last one) will have '_maxFramesPerBlock' // number of keyframes. The last keyframe in the last FrameBlock // will always be (1f, "this.StartingValue + 360") // Total number of FrameBlocks in each animation _maxFrameBlocks = ActiveNodes; // Total keyframes in each FrameBlock _maxFramesPerBlock = ActiveNodes + 1; // Total keyframes in each animation _maxKeyFrames = _maxFrameBlocks * _maxFramesPerBlock; // Normalized Progress Key unit value for each keyframe _keyFrameSlice = 1f / _maxKeyFrames; // ======================================================================== // NOTE: // gamma * maxNodes = 360 // gamma = alpha + beta // beta = 2 * Asin(nodeRadius / ringRadius) * (180 / Math.PI) // invisibleNodes = MaxNodes - ActiveNodes // phi = (invisibleNodes * gamma) // theta = phi - beta // ======================================================================== // gamma is the angle between adjacent nodes when maxNodes number of nodes are arranged in a circle _gamma = 360f / MaxNodes; // beta is the angle a node must travel after hitting the adjacent node _beta = 2f * (float)(Math.Asin(_nodeRadius / _ringRadius) * (180f / Math.PI)); // alpha is the smallest angle a node must travel before hitting the adjacent node _alpha = _gamma - _beta; // phi is the angle occupied by (MaxNodes - ActiveNodes) number of nodes _phi = (MaxNodes - ActiveNodes + 1) * _gamma; // theta is the largest angle a node must travel before hitting the adjacent node _theta = _phi - _beta; // Create the Animations _animations = CreateAnimations(); // Create the Container _container = _compositor.CreateContainerVisual(); _container.Size = _ringSize; _container.Offset = new Vector3(((float)finalSize.Width - sideLength) / 2f, ((float)finalSize.Height - sideLength) / 2f, 0f); _container.CenterPoint = new Vector3(_ringSize.X / 2f, _ringSize.Y / 2f, 0f); // Create the Nodes var offset = new Vector3(_nodeRadius, _ringSize.Y / 2f, 0); var centerPoint = new Vector3(_ringSize.X / 2f - _nodeRadius, 0, 0); _nodes = new List<SpriteVisual>(); var geometry = CanvasGeometry.CreateCircle(_generator.Device, new Vector2(_nodeRadius, _nodeRadius), _nodeRadius); // Create/Update the nodeMask var color = NodeColor; if (_nodeMask == null) { //Task.Run(async () => // { _nodeMask = _generator.CreateGeometrySurface(_nodeSize.ToSize(), geometry, color); // }) //.Wait(); } else { //Task.Run(async () => // { _nodeMask.Redraw(_nodeSize.ToSize(), geometry, color); // }) //.Wait(); } // Create the SurfaceBrush for the nodes var brush = _compositor.CreateSurfaceBrush(_nodeMask.Surface); var baseAngle = 0f; // Create the visuals for the nodes for (var i = 0; i < _maxFramesPerBlock; i++) { var node = _compositor.CreateSpriteVisual(); node.Size = _nodeSize; node.AnchorPoint = new Vector2(0.5f); node.Offset = offset; node.CenterPoint = centerPoint; node.Brush = brush; node.RotationAngleInDegrees = baseAngle; if (i == 0) { baseAngle += _phi; } else if (i == _maxFramesPerBlock - 2) { baseAngle = -_beta; } else { baseAngle += _gamma; } _nodes.Add(node); // Add the visual to the container _container.Children.InsertAtTop(node); } // Add the container to the Visual Tree ElementCompositionPreview.SetElementChildVisual(this, _container); // Start Node animations for (var i = 0; i < _maxFramesPerBlock; i++) { _nodes[i].StartAnimation(AnimatedProperty, _animations[i]); } // Start container animation _containerAnimation = _compositor.CreateScalarKeyFrameAnimation(); _containerAnimation.InsertExpressionKeyFrame(1f, "this.StartingValue + 360f", _compositor.CreateLinearEasingFunction()); _containerAnimation.Duration = RingDuration; _containerAnimation.IterationBehavior = AnimationIterationBehavior.Forever; _container.StartAnimation(AnimatedProperty, _containerAnimation); _isAnimationStarted = true; return finalSize; }