File: System\Windows\Input\Manipulations\InertiaExpansionBehavior2D.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\System.Windows.Input.Manipulations\System.Windows.Input.Manipulations.csproj (System.Windows.Input.Manipulations)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
namespace System.Windows.Input.Manipulations
{
    /// <summary>
    /// Describes desired expansion behavior of an inertia processor.
    /// </summary>
    public sealed class InertiaExpansionBehavior2D : InertiaParameters2D
    {
        private const string initialRadiusName = "InitialRadius";
        private const string desiredDecelerationName = "DesiredDeceleration";
        private const string desiredExpansionXName = "DesiredExpansionX";
        private const string desiredExpansionYName = "DesiredExpansionY";
        private const string initialVelocityXName = "InitialVelocityX";
        private const string initialVelocityYName = "InitialVelocityY";
 
        private float initialRadius = 1;
        private float desiredDeceleration = float.NaN;
        private float desiredExpansionX = float.NaN;
        private float desiredExpansionY = float.NaN;
        private float initialVelocityX = float.NaN;
        private float initialVelocityY = float.NaN;
 
        /// <summary>
        /// Gets or sets the initial average radius, in coordinate units.
        /// </summary>
        /// <remarks>
        /// <para>
        /// This property is used by the inertia processor to calculate the scale factor for an element 
        /// that is expanding.
        /// </para>
        /// <para>
        /// For instance, if the 
        /// <strong><see cref="InitialVelocityX"/></strong> and  <strong><see cref="InitialVelocityY"/></strong>
        /// properties are set to 3.75, this informs the inertia processor that the starting expansion velocity is 3.75 
        /// coordinate units per millisecond along both axes (expansion must be proportional). 
        /// </para>
        /// <para>
        /// The scale factor associated with this rate of expansion depends upon the size of the element 
        /// that is expanding. For a small element, this expansion velocity represents a larger scale factor 
        /// than for a large element. You inform the inertia processor of the element size by setting the 
        /// <strong>InitialRadius</strong> property.
        /// </para>
        /// <para>The default value for the <strong>InitialRadius</strong> property is 1.0.</para>
        /// <para>Valid values are any finite number greater than or equal to 1.0.</para>
        /// </remarks>
        public float InitialRadius
        {
            get
            {
                return this.initialRadius;
            }
            set
            {
                CheckRadius(value, initialRadiusName);
                ProtectedChangeProperty(
                    () => value == this.initialRadius,
                    () => this.initialRadius = value,
                    initialRadiusName);
            }
        }
 
        /// <summary>
        /// Gets or sets the desired expansion deceleration, in coordinate units
        /// per millisecond squared.
        /// </summary>
        /// <remarks>
        /// <para>
        /// This property value is mutually exclusive with the
        /// <strong><see cref="DesiredExpansionX"/></strong>
        /// and 
        /// <strong><see cref="DesiredExpansionY"/></strong> properties;
        /// setting this property will set 
        /// <strong>DesiredExpansionX</strong>
        /// and 
        /// <strong>DesiredExpansionY</strong> to <strong>NaN</strong>.
        /// </para>
        /// <para>
        /// The default value for this property is <strong>NaN</strong>.
        /// </para>
        /// <para>
        /// This property cannot be set while the inertia processor is running; 
        /// otherwise, an exception is thrown.
        /// </para>
        /// </remarks>
        public float DesiredDeceleration
        {
            get { return this.desiredDeceleration; }
            set
            {
                Validations.CheckFiniteNonNegative(value, desiredDecelerationName);
                ProtectedChangeProperty(
                    () => value == this.desiredDeceleration,
                    () => { this.desiredDeceleration = value; this.desiredExpansionX = this.desiredExpansionY = float.NaN; },
                    desiredDecelerationName);
            }
        }
 
        /// <summary>
        /// Gets or sets the desired expansion along the x-axis, in coordinate units.
        /// </summary>
        /// <remarks>
        /// <para>
        /// Expansion must be proportional. The value of this property must equal the value of the
        /// <strong><see cref="DesiredExpansionY"/></strong> property when the inertia processor starts;
        /// otherwise an exception is thrown.
        /// </para>
        /// <para>
        /// This property value is mutually exclusive with the
        /// <strong><see cref="DesiredDeceleration"/></strong> property;
        /// setting this property will set 
        /// <strong>DesiredDeceleration</strong> to <strong>NaN</strong>.
        /// </para>
        /// <para>
        /// The default value for this property is <strong>NaN</strong>.
        /// </para>
        /// <para>
        /// <strong>DesiredExpansionX</strong> must be a finite, non-negative number. 
        /// The rate of expansion is determined by the
        /// <strong><see cref="InitialVelocityX"/></strong> property.
        /// </para>
        /// <para>
        /// This property cannot be set while the inertia processor is running; 
        /// otherwise, an exception is thrown.
        /// </para>
        /// </remarks>
        public float DesiredExpansionX
        {
            get { return this.desiredExpansionX; }
            set
            {
                Validations.CheckFiniteNonNegative(value, desiredExpansionXName);
                ProtectedChangeProperty(
                    () => (value == this.desiredExpansionX),
                    () => { this.desiredExpansionX = value; this.desiredDeceleration = float.NaN; },
                    desiredExpansionXName);
            }
        }
 
        /// <summary>
        /// Gets or sets the desired expansion along the y-axis, in coordinate units.
        /// </summary>
        /// <remarks>
        /// <para>
        /// Expansion must be proportional. The value of this property must equal the value of the
        /// <strong><see cref="DesiredExpansionX"/></strong> property when the inertia processor starts; 
        /// otherwise an exception is thrown.
        /// </para>
        /// <para>
        /// This property value is mutually exclusive with the
        /// <strong><see cref="DesiredDeceleration"/></strong> property;
        /// setting this property will set 
        /// <strong>DesiredDeceleration</strong> to <strong>NaN</strong>.
        /// </para>
        /// <para>
        /// The default value for this property is <strong>NaN</strong>.
        /// </para>
        /// <para>
        /// <strong>DesiredExpansionY</strong> must be a finite, non-negative number. 
        /// The rate of expansion is determined by the
        /// <strong><see cref="InitialVelocityY"/></strong> property.
        /// </para>
        /// <para>
        /// This property cannot be set while the inertia processor is running; 
        /// otherwise, an exception is thrown.
        /// </para>
        /// </remarks>
        public float DesiredExpansionY
        {
            get { return this.desiredExpansionY; }
            set
            {
                Validations.CheckFiniteNonNegative(value, desiredExpansionYName);
                ProtectedChangeProperty(
                    () => (value == this.desiredExpansionY),
                    () => { this.desiredExpansionY = value; this.desiredDeceleration = float.NaN; },
                    desiredExpansionYName);
            }
        }
 
        /// <summary>
        /// Gets or sets the initial expansion velocity along the x-axis, in coordinate
        /// units per millisecond.
        /// </summary>
        /// <remarks>  
        /// <para>
        /// Expansion must be proportional. The value of this property must equal the value of the
        /// <strong><see cref="InitialVelocityY"/></strong> property when the inertia processor starts;
        /// otherwise an exception is thrown.
        /// </para>
        /// <para>
        /// The default value for this property is <strong>NaN</strong>. 
        /// Leaving this property and <strong>InitialVelocityY</strong> unchanged from the default or 
        /// setting this property and <strong>InitialVelocityY</strong> to zero (0) 
        /// will disable expansion inertia.
        /// </para>
        /// <para>
        /// This property cannot be set while the inertia processor is running; 
        /// otherwise, an exception is thrown.
        /// </para>
        /// </remarks>
        public float InitialVelocityX
        {
            get { return this.initialVelocityX; }
            set
            {
                Validations.CheckFinite(value, initialVelocityXName);
                ProtectedChangeProperty(
                    () => value == this.initialVelocityX,
                    () => this.initialVelocityX = value,
                    initialVelocityXName);
            }
        }
 
        /// <summary>
        /// Gets or sets the initial expansion velocity along the y-axis, in coordinate
        /// units per millisecond.
        /// </summary>
        /// <remarks>
        /// <para>
        /// Expansion must be proportional. The value of this property must equal the value of the
        /// <strong><see cref="InitialVelocityX"/></strong> property when the inertia processor starts;
        /// otherwise an exception is thrown.
        /// </para>
        /// <para>
        /// The default value for this property is <strong>NaN</strong>. 
        /// Leaving this property and <strong>InitialVelocityX</strong> unchanged from the default or 
        /// setting this property and <strong>InitialVelocityX</strong> to zero (0) 
        /// will disable expansion inertia.
        /// </para>
        /// <para>
        /// This property cannot be set while the inertia processor is running; 
        /// otherwise, an exception is thrown.
        /// </para>
        /// </remarks>
        public float InitialVelocityY
        {
            get { return this.initialVelocityY; }
            set
            {
                Validations.CheckFinite(value, initialVelocityYName);
                ProtectedChangeProperty(
                    () => value == this.initialVelocityY,
                    () => this.initialVelocityY = value,
                    initialVelocityYName);
            }
        }
 
        /// <summary>
        /// This is called when the inertia processor is about to start processing.
        /// It checks to make sure that the behavior is in a valid state. There are
        /// two possible valid states:
        /// 
        /// 1. Initial velocity is NaN, in which case expansion inertia will not
        ///    occur. Desired deceleration and expansion values are ignored.
        /// 
        /// 2. Initial velocity is set to something other than NaN, which means
        ///    that rotation inertia will occur. In this case, either the
        ///    desired deceleration or rotation values must be set. Also,
        ///    the desired X and Y expansion components (velocities, and
        ///    desired expansions if set) must equal each other.
        /// </summary>
        internal void CheckValid()
        {
            if (float.IsNaN(this.initialVelocityX) && float.IsNaN(this.initialVelocityY))
            {
                // Velocity is unspecified, so no inertia will occur. This is
                // a valid state.
                return;
            }
 
            // X and Y components must match.
            if (this.initialVelocityX != this.initialVelocityY)
            {
                throw Exceptions.OnlyProportionalExpansionSupported(
                    SubpropertyName(initialVelocityXName),
                    SubpropertyName(initialVelocityYName));
            }
            if (!float.IsNaN(this.desiredExpansionX)
                && !float.IsNaN(this.desiredExpansionY)
                && (this.desiredExpansionX != this.desiredExpansionY))
            {
                throw Exceptions.OnlyProportionalExpansionSupported(
                    SubpropertyName(desiredExpansionXName),
                    SubpropertyName(desiredExpansionYName));
            }
 
            // Must specify either deceleration or desired expansion.
            if (float.IsNaN(this.desiredDeceleration)
                && (float.IsNaN(this.desiredExpansionX) || float.IsNaN(this.desiredExpansionY)))
            {
                throw Exceptions.InertiaParametersUnspecified1and2(
                    SubpropertyName(desiredDecelerationName),
                    SubpropertyName(desiredExpansionXName),
                    SubpropertyName(desiredExpansionYName));
            }
        }
 
        private static string SubpropertyName(string paramName)
        {
            return "ExpansionBehavior." + paramName;
        }
 
        /// <summary>
        /// Checks if the given value is valid radius.
        /// </summary>
        /// <param name="value"></param>
        /// <param name="paramName"></param>
        private static void CheckRadius(float value, string paramName)
        {
            if (value < 1 || double.IsInfinity(value) || double.IsNaN(value))
            {
                throw Exceptions.IllegialInertiaRadius(paramName, value);
            }
        }
 
    }
}