Beispiel #1
0
        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();
        }
Beispiel #2
0
        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();
        }
Beispiel #3
0
        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);
        }
Beispiel #4
0
        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]);
            }
        }
Beispiel #5
0
        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}");
            }
        }
Beispiel #6
0
 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);
 }
Beispiel #7
0
        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);
        }
Beispiel #8
0
        /// <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();
        }
Beispiel #9
0
 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);
 }
Beispiel #10
0
        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;
        }
Beispiel #14
0
        /// <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();
        }
Beispiel #16
0
        /// <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;
        }