This commit is contained in:
GG Z
2025-07-31 20:12:01 +08:00
parent 6d96da6f90
commit 4f6cd2137c
292 changed files with 8276 additions and 10827 deletions

View File

@@ -0,0 +1,81 @@
namespace AntDesign.WPF.Utils
{
using System.Windows;
public static class CornerRadiusUtil
{
/// <summary>
/// 验证角半径是否只包含有效值
/// 有效性检查集作为参数传递。
/// </summary>
/// <param name='corner'>CornerRadius value</param>
/// <param name='allowNegative'>allows negative values</param>
/// <param name='allowNaN'>allows Double.NaN</param>
/// <param name='allowPositiveInfinity'>allows Double.PositiveInfinity</param>
/// <param name='allowNegativeInfinity'>allows Double.NegativeInfinity</param>
/// <returns>Whether or not the CornerRadius complies to the range specified</returns>
public static bool IsValid(CornerRadius corner, bool allowNegative, bool allowNaN, bool allowPositiveInfinity, bool allowNegativeInfinity)
{
if (!allowNegative)
{
if (corner.TopLeft < 0d || corner.TopRight < 0d || corner.BottomLeft < 0d || corner.BottomRight < 0d)
{
return false;
}
}
if (!allowNaN)
{
if (DoubleUtil.IsNaN(corner.TopLeft) || DoubleUtil.IsNaN(corner.TopRight) ||
DoubleUtil.IsNaN(corner.BottomLeft) || DoubleUtil.IsNaN(corner.BottomRight))
{
return false;
}
}
if (!allowPositiveInfinity)
{
if (double.IsPositiveInfinity(corner.TopLeft) || double.IsPositiveInfinity(corner.TopRight) ||
double.IsPositiveInfinity(corner.BottomLeft) || double.IsPositiveInfinity(corner.BottomRight))
{
return false;
}
}
if (!allowNegativeInfinity)
{
if (double.IsNegativeInfinity(corner.TopLeft) || double.IsNegativeInfinity(corner.TopRight) ||
double.IsNegativeInfinity(corner.BottomLeft) || double.IsNegativeInfinity(corner.BottomRight))
{
return false;
}
}
return true;
}
/// <summary>
/// Verifies if the CornerRadius contains only zero values
/// </summary>
/// <param name="corner">CornerRadius</param>
/// <returns>Size</returns>
public static bool IsZero(CornerRadius corner)
{
return DoubleUtil.IsZero(corner.TopLeft) && DoubleUtil.IsZero(corner.TopRight)
&& DoubleUtil.IsZero(corner.BottomRight) && DoubleUtil.IsZero(corner.BottomLeft);
}
/// <summary>
/// Verifies if the CornerRadius contains same values
/// </summary>
/// <param name="corner">CornerRadius</param>
/// <returns>true if yes, otherwise false</returns>
public static bool IsUniform(CornerRadius corner)
{
var topLeft = corner.TopLeft;
return DoubleUtil.AreClose(topLeft, corner.TopRight)
&& DoubleUtil.AreClose(topLeft, corner.BottomRight)
&& DoubleUtil.AreClose(topLeft, corner.BottomLeft);
}
}
}

View File

@@ -0,0 +1,135 @@
namespace AntDesign.WPF.Utils
{
using System;
using System.Runtime.InteropServices;
internal static class DoubleUtil
{
// Const values
// Smallest double value such that 1.0+DBL_EPSILON != 1.0
internal const double DBL_EPSILON = 2.2204460492503131e-016;
// Number close to zero, where float.MinValue is -float.MaxValue
internal const float FLT_MIN = 1.175494351e-38F;
/// <summary>
/// AreClose - Returns whether or not two doubles are "close". That is, whether or
/// not they are within epsilon of each other. Note that this epsilon is proportional
/// to the numbers themselves to that AreClose survives scalar multiplication.
/// There are plenty of ways for this to return false even for numbers which
/// are theoretically identical, so no code calling this should fail to work if this
/// returns false. This is important enough to repeat:
/// NB: NO CODE CALLING THIS FUNCTION SHOULD DEPEND ON ACCURATE RESULTS - this should be
/// used for optimizations *only*.
/// </summary>
/// <returns>
/// bool - the result of the AreClose comparision.
/// </returns>
/// <param name="value1"> The first double to compare. </param>
/// <param name="value2"> The second double to compare. </param>
public static bool AreClose(double value1, double value2)
{
//in case they are Infinities (then epsilon check does not work)
if (value1 == value2) return true;
// This computes (|value1-value2| / (|value1| + |value2| + 10.0)) < DBL_EPSILON
var eps = (Math.Abs(value1) + Math.Abs(value2) + 10.0) * DBL_EPSILON;
var delta = value1 - value2;
return -eps < delta && eps > delta;
}
/// <summary>
/// LessThan - Returns whether or not the first double is less than the second double.
/// That is, whether or not the first is strictly less than *and* not within epsilon of
/// the other number. Note that this epsilon is proportional to the numbers themselves
/// to that AreClose survives scalar multiplication. Note,
/// There are plenty of ways for this to return false even for numbers which
/// are theoretically identical, so no code calling this should fail to work if this
/// returns false. This is important enough to repeat:
/// NB: NO CODE CALLING THIS FUNCTION SHOULD DEPEND ON ACCURATE RESULTS - this should be
/// used for optimizations *only*.
/// </summary>
/// <returns>
/// bool - the result of the LessThan comparision.
/// </returns>
/// <param name="value1"> The first double to compare. </param>
/// <param name="value2"> The second double to compare. </param>
public static bool LessThan(double value1, double value2)
{
return value1 < value2 && !AreClose(value1, value2);
}
/// <summary>
/// GreaterThan - Returns whether or not the first double is greater than the second double.
/// That is, whether or not the first is strictly greater than *and* not within epsilon of
/// the other number. Note that this epsilon is proportional to the numbers themselves
/// to that AreClose survives scalar multiplication. Note,
/// There are plenty of ways for this to return false even for numbers which
/// are theoretically identical, so no code calling this should fail to work if this
/// returns false. This is important enough to repeat:
/// NB: NO CODE CALLING THIS FUNCTION SHOULD DEPEND ON ACCURATE RESULTS - this should be
/// used for optimizations *only*.
/// </summary>
/// <returns>
/// bool - the result of the GreaterThan comparision.
/// </returns>
/// <param name="value1"> The first double to compare. </param>
/// <param name="value2"> The second double to compare. </param>
public static bool GreaterThan(double value1, double value2)
{
return value1 > value2 && !AreClose(value1, value2);
}
/// <summary>
/// IsOne - Returns whether or not the double is "close" to 1. Same as AreClose(double, 1),
/// but this is faster.
/// </summary>
/// <returns>
/// bool - the result of the AreClose comparision.
/// </returns>
/// <param name="value"> The double to compare to 1. </param>
public static bool IsOne(double value)
{
return Math.Abs(value - 1.0) < 10.0 * DBL_EPSILON;
}
/// <summary>
/// IsZero - Returns whether or not the double is "close" to 0. Same as AreClose(double, 0),
/// but this is faster.
/// </summary>
/// <returns>
/// bool - the result of the AreClose comparision.
/// </returns>
/// <param name="value"> The double to compare to 0. </param>
public static bool IsZero(double value)
{
return Math.Abs(value) < 10.0 * DBL_EPSILON;
}
[StructLayout(LayoutKind.Explicit)]
private struct NanUnion
{
[FieldOffset(0)]
internal double DoubleValue;
[FieldOffset(0)]
internal ulong UintValue;
}
// The standard CLR double.IsNaN() function is approximately 100 times slower than our own wrapper,
// so please make sure to use DoubleUtil.IsNaN() in performance sensitive code.
// PS item that tracks the CLR improvement is DevDiv Schedule : 26916.
// IEEE 754 : If the argument is any value in the range 0x7ff0000000000001L through 0x7fffffffffffffffL
// or in the range 0xfff0000000000001L through 0xffffffffffffffffL, the result will be NaN.
public static bool IsNaN(double value)
{
var t = new NanUnion
{
DoubleValue = value
};
var exp = t.UintValue & 0xfff0000000000000;
var man = t.UintValue & 0x000fffffffffffff;
return (exp == 0x7ff0000000000000 || exp == 0xfff0000000000000) && man != 0;
}
}
}

View File

@@ -0,0 +1,78 @@
namespace AntDesign.WPF.Utils
{
using System.Windows;
using System.Windows.Media;
public static class DpiUtil
{
public static DpiScale GetDpi(Visual visual)
{
#if NET40 || NET45
var source = PresentationSource.FromVisual(visual);
if (source?.CompositionTarget == null)
{
return new DpiScale(1.0, 1.0);
}
var device = source.CompositionTarget.TransformToDevice;
return new DpiScale(device.M11, device.M22);
#else
return VisualTreeHelper.GetDpi(visual);
#endif
}
}
#if NET40 || NET45
/// <summary>Stores DPI information from which a <see cref="T:System.Windows.Media.Visual" /> or <see cref="T:System.Windows.UIElement" /> is rendered.</summary>
public struct DpiScale
{
/// <summary>Gets the DPI scale on the X axis.</summary>
/// <returns>The DPI scale for the X axis.</returns>
public double DpiScaleX { get; private set; }
/// <summary>Gets the DPI scale on the Yaxis.</summary>
/// <returns>The DPI scale for the Y axis.</returns>
public double DpiScaleY { get; private set; }
/// <summary>Get or sets the PixelsPerDip at which the text should be rendered.</summary>
/// <returns>The current <see cref="P:System.Windows.DpiScale.PixelsPerDip" /> value.</returns>
public double PixelsPerDip
{
get
{
return DpiScaleY;
}
}
/// <summary>Gets the DPI along X axis.</summary>
/// <returns>The DPI along the X axis.</returns>
public double PixelsPerInchX
{
get
{
return 96.0 * DpiScaleX;
}
}
/// <summary>Gets the DPI along Y axis.</summary>
/// <returns>The DPI along the Y axis.</returns>
public double PixelsPerInchY
{
get
{
return 96.0 * DpiScaleY;
}
}
/// <summary>Initializes a new instance of the <see cref="T:System.Windows.DpiScale" /> structure.</summary>
/// <param name="dpiScaleX">The DPI scale on the X axis.</param>
/// <param name="dpiScaleY">The DPI scale on the Y axis. </param>
public DpiScale(double dpiScaleX, double dpiScaleY)
{
DpiScaleX = dpiScaleX;
DpiScaleY = dpiScaleY;
}
}
#endif
}

View File

@@ -0,0 +1,34 @@
namespace AntDesign.WPF.Utils
{
using System;
using System.Windows;
internal static class RectUtil
{
/// <summary>
/// Deflates rectangle by given thickness
/// </summary>
/// <param name="rect">Rectangle</param>
/// <param name="thick">Thickness</param>
/// <returns>Deflated Rectangle</returns>
public static Rect Deflate(Rect rect, Thickness thick)
{
return new Rect(rect.Left + thick.Left, rect.Top + thick.Top,
Math.Max(0.0, rect.Width - thick.Left - thick.Right),
Math.Max(0.0, rect.Height - thick.Top - thick.Bottom));
}
/// <summary>
/// Inflates rectangle by given thickness
/// </summary>
/// <param name="rect">Rectangle</param>
/// <param name="thick">Thickness</param>
/// <returns>Inflated Rectangle</returns>
public static Rect Inflate(Rect rect, Thickness thick)
{
return new Rect(rect.Left - thick.Left, rect.Top - thick.Top,
Math.Max(0.0, rect.Width + thick.Left + thick.Right),
Math.Max(0.0, rect.Height + thick.Top + thick.Bottom));
}
}
}

View File

@@ -0,0 +1,94 @@
using System;
using System.Linq;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Animation;
using AntDesign.WPF.Contracts;
namespace AntDesign.WPF.Utils
{
/// <summary>
/// Provides the ability to spin for controls.
/// Known defects:
/// Using trigger to change control RenderTransform property will result in animation lose.
/// Try adding the Spin property Setter after the RenderTransform property Setter for notification purposes.
/// Example: Theme/Switch.xaml
/// </summary>
public static class Spinner
{
private const string storyBoardName = "AntDesign.WPF.SpinnerStoryBoard";
/// <summary>
/// Start the spinning animation.
/// </summary>
public static void BeginSpin<T>(this T control, double seconds) where T : FrameworkElement, ISpinable
{
var transform = control.RenderTransform;
control.SetCurrentValue(UIElement.RenderTransformOriginProperty, new Point(0.5, 0.5));
TransformGroup transformGroup;
if (transform is TransformGroup)
{
if (!(((TransformGroup)transform).Children.FirstOrDefault() is RotateTransform))
{
transformGroup = (TransformGroup)transform.Clone();
transformGroup.Children.Insert(0, new RotateTransform(0.0));
control.SetCurrentValue(UIElement.RenderTransformProperty, transformGroup);
}
}
else
{
transformGroup = new TransformGroup();
if (transform is RotateTransform)
{
transformGroup.Children.Add(transform);
}
else
{
transformGroup.Children.Add(new RotateTransform(0.0));
if (transform != null && transform != Transform.Identity)
{
transformGroup.Children.Add(transform);
}
}
control.SetCurrentValue(UIElement.RenderTransformProperty, transformGroup);
}
if (!(control.Resources[storyBoardName] is Storyboard storyboard))
{
storyboard = new Storyboard();
var animation = new DoubleAnimation
{
From = 0,
To = 360,
AutoReverse = false,
Duration = TimeSpan.FromSeconds(seconds),
RepeatBehavior = RepeatBehavior.Forever
};
Storyboard.SetTarget(animation, control);
Storyboard.SetTargetProperty(animation,
new PropertyPath("(0).(1)[0].(2)", UIElement.RenderTransformProperty,
TransformGroup.ChildrenProperty, RotateTransform.AngleProperty));
storyboard.Children.Add(animation);
control.Resources.Add(storyBoardName, storyboard);
}
storyboard.Begin();
}
/// <summary>
/// Stop the spinning animation.
/// </summary>
public static void StopSpin<T>(this T control) where T : FrameworkElement, ISpinable
{
(control.Resources[storyBoardName] as Storyboard)?.Stop();
}
}
}

View File

@@ -0,0 +1,87 @@
namespace AntDesign.WPF.Utils
{
using System.Windows;
internal static class ThicknessUtil
{
/// <summary>
/// Verifies if this Thickness contains only valid values
/// The set of validity checks is passed as parameters.
/// </summary>
/// <param name='thick'>Thickness value</param>
/// <param name='allowNegative'>allows negative values</param>
/// <param name='allowNaN'>allows Double.NaN</param>
/// <param name='allowPositiveInfinity'>allows Double.PositiveInfinity</param>
/// <param name='allowNegativeInfinity'>allows Double.NegativeInfinity</param>
/// <returns>Whether or not the thickness complies to the range specified</returns>
public static bool IsValid(Thickness thick, bool allowNegative, bool allowNaN, bool allowPositiveInfinity, bool allowNegativeInfinity)
{
if (!allowNegative)
{
if (thick.Left < 0d || thick.Right < 0d || thick.Top < 0d || thick.Bottom < 0d)
return false;
}
if (!allowNaN)
{
if (DoubleUtil.IsNaN(thick.Left) || DoubleUtil.IsNaN(thick.Right)
|| DoubleUtil.IsNaN(thick.Top) || DoubleUtil.IsNaN(thick.Bottom))
return false;
}
if (!allowPositiveInfinity)
{
if (double.IsPositiveInfinity(thick.Left) || double.IsPositiveInfinity(thick.Right)
|| double.IsPositiveInfinity(thick.Top) || double.IsPositiveInfinity(thick.Bottom))
{
return false;
}
}
if (!allowNegativeInfinity)
{
if (double.IsNegativeInfinity(thick.Left) || double.IsNegativeInfinity(thick.Right)
|| double.IsNegativeInfinity(thick.Top) || double.IsNegativeInfinity(thick.Bottom))
{
return false;
}
}
return true;
}
/// <summary>
/// Method to add up the left and right size as width, as well as the top and bottom size as height
/// </summary>
/// <param name="thick">Thickness</param>
/// <returns>Size</returns>
public static Size CollapseThickness(Thickness thick)
{
return new Size(thick.Left + thick.Right, thick.Top + thick.Bottom);
}
/// <summary>
/// Verifies if the Thickness contains only zero values
/// </summary>
/// <param name="thick">Thickness</param>
/// <returns>Size</returns>
public static bool IsZero(Thickness thick)
{
return DoubleUtil.IsZero(thick.Left) && DoubleUtil.IsZero(thick.Top)
&& DoubleUtil.IsZero(thick.Right) && DoubleUtil.IsZero(thick.Bottom);
}
/// <summary>
/// Verifies if all the values in Thickness are same
/// </summary>
/// <param name="thick">Thickness</param>
/// <returns>true if yes, otherwise false</returns>
public static bool IsUniform(Thickness thick)
{
var left = thick.Left;
return DoubleUtil.AreClose(left, thick.Top)
&& DoubleUtil.AreClose(left, thick.Right)
&& DoubleUtil.AreClose(left, thick.Bottom);
}
}
}

View File

@@ -0,0 +1,31 @@
namespace AntDesign.WPF.Utils
{
using System;
public static class UIElementUtil
{
public static double RoundLayoutValue(double value, double dpiScale)
{
double newValue;
// If DPI == 1, don't use DPI-aware rounding.
if (!DoubleUtil.AreClose(dpiScale, 1.0))
{
newValue = Math.Round(value * dpiScale) / dpiScale;
// If rounding produces a value unacceptable to layout (NaN, Infinity or MaxValue), use the original value.
if (double.IsNaN(newValue) ||
double.IsInfinity(newValue) ||
DoubleUtil.AreClose(newValue, double.MaxValue))
{
newValue = value;
}
}
else
{
newValue = Math.Round(value);
}
return newValue;
}
}
}