Files
MRCC/launcher/PCL-CE/Plain Craft Launcher 2/Controls/MyDropShadow.cs
T

373 lines
16 KiB
C#
Raw Normal View History

using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace PCL;
public class MyDropShadow : Decorator
{
public static readonly DependencyProperty ColorProperty = DependencyProperty.Register("Color", typeof(Color),
typeof(MyDropShadow),
new FrameworkPropertyMetadata(Color.FromArgb(0x71, 0x0, 0x0, 0x0),
FrameworkPropertyMetadataOptions.AffectsRender, ClearBrushes));
public static readonly DependencyProperty ShadowRadiusProperty = DependencyProperty.Register("ShadowRadius",
typeof(double), typeof(MyDropShadow),
new FrameworkPropertyMetadata(5d, FrameworkPropertyMetadataOptions.AffectsRender, ClearBrushes));
public static readonly DependencyProperty CornerRadiusProperty = DependencyProperty.Register("CornerRadius",
typeof(CornerRadius), typeof(MyDropShadow),
new FrameworkPropertyMetadata(new CornerRadius(), FrameworkPropertyMetadataOptions.AffectsRender, ClearBrushes),
IsCornerRadiusValid);
private static Brush[] _commonBrushes;
private static CornerRadius _commonCornerRadius;
private static readonly object _resourceAccess = new();
private Brush[] _brushes;
/// <summary>
/// 阴影颜色。
/// </summary>
public Color Color
{
get => (Color)GetValue(ColorProperty);
set => SetValue(ColorProperty, value);
}
/// <summary>
/// 阴影模糊半径。
/// </summary>
public double ShadowRadius
{
get => (double)GetValue(ShadowRadiusProperty);
set => SetValue(ShadowRadiusProperty, value);
}
/// <summary>
/// 圆角大小。
/// </summary>
public CornerRadius CornerRadius
{
get => (CornerRadius)GetValue(CornerRadiusProperty);
set => SetValue(CornerRadiusProperty, value);
}
private static bool IsCornerRadiusValid(object value)
{
var cr = (CornerRadius)value;
return !(cr.TopLeft < 0.0d || cr.TopRight < 0.0d || cr.BottomLeft < 0.0d || cr.BottomRight < 0.0d ||
double.IsNaN(cr.TopLeft) || double.IsNaN(cr.TopRight) || double.IsNaN(cr.BottomLeft) ||
double.IsNaN(cr.BottomRight) || double.IsInfinity(cr.TopLeft) || double.IsInfinity(cr.TopRight) ||
double.IsInfinity(cr.BottomLeft) || double.IsInfinity(cr.BottomRight));
}
// =======================================
// 渲染
// =======================================
protected override void OnRender(DrawingContext drawingContext)
{
var cornerRadius = CornerRadius;
var shadowBounds = new Rect(0d, 0d, RenderSize.Width, RenderSize.Height);
var color = Color;
if (shadowBounds.Width > 0d && shadowBounds.Height > 0d && color.A > 0)
{
var centerWidth = shadowBounds.Right - shadowBounds.Left - 2d * ShadowRadius;
var centerHeight = shadowBounds.Bottom - shadowBounds.Top - 2d * ShadowRadius;
var maxRadius = Math.Min(centerWidth * 0.5d, centerHeight * 0.5d);
cornerRadius.TopLeft = Math.Min(cornerRadius.TopLeft, maxRadius);
cornerRadius.TopRight = Math.Min(cornerRadius.TopRight, maxRadius);
cornerRadius.BottomLeft = Math.Min(cornerRadius.BottomLeft, maxRadius);
cornerRadius.BottomRight = Math.Min(cornerRadius.BottomRight, maxRadius);
var brushes = GetBrushes(color, cornerRadius);
var centerTop = shadowBounds.Top + ShadowRadius;
var centerLeft = shadowBounds.Left + ShadowRadius;
var centerRight = shadowBounds.Right - ShadowRadius;
var centerBottom = shadowBounds.Bottom - ShadowRadius;
var guidelineSetX = new[]
{
centerLeft, centerLeft + cornerRadius.TopLeft, centerRight - cornerRadius.TopRight,
centerLeft + cornerRadius.BottomLeft, centerRight - cornerRadius.BottomRight, centerRight
};
var guidelineSetY = new[]
{
centerTop, centerTop + cornerRadius.TopLeft, centerTop + cornerRadius.TopRight,
centerBottom - cornerRadius.BottomLeft, centerBottom - cornerRadius.BottomRight, centerBottom
};
drawingContext.PushGuidelineSet(new GuidelineSet(guidelineSetX, guidelineSetY));
cornerRadius.TopLeft += ShadowRadius;
cornerRadius.TopRight += ShadowRadius;
cornerRadius.BottomLeft += ShadowRadius;
cornerRadius.BottomRight += ShadowRadius;
var topLeft = new Rect(shadowBounds.Left, shadowBounds.Top, cornerRadius.TopLeft, cornerRadius.TopLeft);
drawingContext.DrawRectangle(brushes[(int)Placement.TopLeft], null, topLeft);
var topWidth = guidelineSetX[2] - guidelineSetX[1];
if (topWidth > 0d)
{
var top = new Rect(guidelineSetX[1], shadowBounds.Top, topWidth, ShadowRadius);
drawingContext.DrawRectangle(brushes[(int)Placement.Top], null, top);
}
var topRight = new Rect(guidelineSetX[2], shadowBounds.Top, cornerRadius.TopRight, cornerRadius.TopRight);
drawingContext.DrawRectangle(brushes[(int)Placement.TopRight], null, topRight);
var leftHeight = guidelineSetY[3] - guidelineSetY[1];
if (leftHeight > 0d)
{
var left = new Rect(shadowBounds.Left, guidelineSetY[1], ShadowRadius, leftHeight);
drawingContext.DrawRectangle(brushes[(int)Placement.Left], null, left);
}
var rightHeight = guidelineSetY[4] - guidelineSetY[2];
if (rightHeight > 0d)
{
var right = new Rect(guidelineSetX[5], guidelineSetY[2], ShadowRadius, rightHeight);
drawingContext.DrawRectangle(brushes[(int)Placement.Right], null, right);
}
var bottomLeft = new Rect(shadowBounds.Left, guidelineSetY[3], cornerRadius.BottomLeft,
cornerRadius.BottomLeft);
drawingContext.DrawRectangle(brushes[(int)Placement.BottomLeft], null, bottomLeft);
var bottomWidth = guidelineSetX[4] - guidelineSetX[3];
if (bottomWidth > 0d)
{
var bottom = new Rect(guidelineSetX[3], guidelineSetY[5], bottomWidth, ShadowRadius);
drawingContext.DrawRectangle(brushes[(int)Placement.Bottom], null, bottom);
}
var bottomRight = new Rect(guidelineSetX[4], guidelineSetY[4], cornerRadius.BottomRight,
cornerRadius.BottomRight);
drawingContext.DrawRectangle(brushes[(int)Placement.BottomRight], null, bottomRight);
if (cornerRadius.TopLeft == ShadowRadius && cornerRadius.TopLeft == cornerRadius.TopRight &&
cornerRadius.TopLeft == cornerRadius.BottomLeft && cornerRadius.TopLeft == cornerRadius.BottomRight)
{
var center = new Rect(guidelineSetX[0], guidelineSetY[0], centerWidth, centerHeight);
drawingContext.DrawRectangle(brushes[(int)Placement.Center], null, center);
}
else
{
var figure = new PathFigure();
if (cornerRadius.TopLeft > ShadowRadius)
{
figure.StartPoint = new Point(guidelineSetX[1], guidelineSetY[0]);
figure.Segments.Add(new LineSegment(new Point(guidelineSetX[1], guidelineSetY[1]), true));
figure.Segments.Add(new LineSegment(new Point(guidelineSetX[0], guidelineSetY[1]), true));
}
else
{
figure.StartPoint = new Point(guidelineSetX[0], guidelineSetY[0]);
}
if (cornerRadius.BottomLeft > ShadowRadius)
{
figure.Segments.Add(new LineSegment(new Point(guidelineSetX[0], guidelineSetY[3]), true));
figure.Segments.Add(new LineSegment(new Point(guidelineSetX[3], guidelineSetY[3]), true));
figure.Segments.Add(new LineSegment(new Point(guidelineSetX[3], guidelineSetY[5]), true));
}
else
{
figure.Segments.Add(new LineSegment(new Point(guidelineSetX[0], guidelineSetY[5]), true));
}
if (cornerRadius.BottomRight > ShadowRadius)
{
figure.Segments.Add(new LineSegment(new Point(guidelineSetX[4], guidelineSetY[5]), true));
figure.Segments.Add(new LineSegment(new Point(guidelineSetX[4], guidelineSetY[4]), true));
figure.Segments.Add(new LineSegment(new Point(guidelineSetX[5], guidelineSetY[4]), true));
}
else
{
figure.Segments.Add(new LineSegment(new Point(guidelineSetX[5], guidelineSetY[5]), true));
}
if (cornerRadius.TopRight > ShadowRadius)
{
figure.Segments.Add(new LineSegment(new Point(guidelineSetX[5], guidelineSetY[2]), true));
figure.Segments.Add(new LineSegment(new Point(guidelineSetX[2], guidelineSetY[2]), true));
figure.Segments.Add(new LineSegment(new Point(guidelineSetX[2], guidelineSetY[0]), true));
}
else
{
figure.Segments.Add(new LineSegment(new Point(guidelineSetX[5], guidelineSetY[0]), true));
}
figure.IsClosed = true;
figure.Freeze();
var geometry = new PathGeometry();
geometry.Figures.Add(figure);
geometry.Freeze();
drawingContext.DrawGeometry(brushes[(int)Placement.Center], null, geometry);
}
drawingContext.Pop();
}
}
private static void ClearBrushes(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
((MyDropShadow)o)._brushes = null;
}
private GradientStopCollection CreateStops(Color c, double cornerRadius)
{
var gradientScale = 1d / (ShadowRadius + cornerRadius);
var gsc = new GradientStopCollection();
var stopColor = c;
gsc.Add(new GradientStop(stopColor, (ShadowRadius * 0.1d + cornerRadius) * gradientScale));
stopColor.A = (byte)Math.Round(0.74336d * c.A);
gsc.Add(new GradientStop(stopColor, (ShadowRadius * 0.3d + cornerRadius) * gradientScale));
stopColor.A = (byte)Math.Round(0.38053d * c.A);
gsc.Add(new GradientStop(stopColor, (ShadowRadius * 0.5d + cornerRadius) * gradientScale));
stopColor.A = (byte)Math.Round(0.12389d * c.A);
gsc.Add(new GradientStop(stopColor, (ShadowRadius * 0.7d + cornerRadius) * gradientScale));
stopColor.A = (byte)Math.Round(0.02654d * c.A);
gsc.Add(new GradientStop(stopColor, (ShadowRadius * 0.9d + cornerRadius) * gradientScale));
stopColor.A = 0;
gsc.Add(new GradientStop(stopColor, (ShadowRadius + cornerRadius) * gradientScale));
gsc.Freeze();
return gsc;
}
private Brush[] CreateBrushes(Color c, CornerRadius cornerRadius)
{
var brushes = new Brush[9];
brushes[(int)Placement.Center] = new SolidColorBrush(c);
brushes[(int)Placement.Center].Freeze();
var sideStops = CreateStops(c, 0d);
var top = new LinearGradientBrush(sideStops, new Point(0d, 1d), new Point(0d, 0d));
top.Freeze();
brushes[(int)Placement.Top] = top;
var left = new LinearGradientBrush(sideStops, new Point(1d, 0d), new Point(0d, 0d));
left.Freeze();
brushes[(int)Placement.Left] = left;
var right = new LinearGradientBrush(sideStops, new Point(0d, 0d), new Point(1d, 0d));
right.Freeze();
brushes[(int)Placement.Right] = right;
var bottom = new LinearGradientBrush(sideStops, new Point(0d, 0d), new Point(0d, 1d));
bottom.Freeze();
brushes[(int)Placement.Bottom] = bottom;
GradientStopCollection topLeftStops;
if (cornerRadius.TopLeft == 0d)
topLeftStops = sideStops;
else
topLeftStops = CreateStops(c, cornerRadius.TopLeft);
var topLeft = new RadialGradientBrush(topLeftStops)
{
RadiusX = 1d,
RadiusY = 1d,
Center = new Point(1d, 1d),
GradientOrigin = new Point(1d, 1d)
};
topLeft.Freeze();
brushes[(int)Placement.TopLeft] = topLeft;
GradientStopCollection topRightStops;
if (cornerRadius.TopRight == 0d)
topRightStops = sideStops;
else if (cornerRadius.TopRight == cornerRadius.TopLeft)
topRightStops = topLeftStops;
else
topRightStops = CreateStops(c, cornerRadius.TopRight);
var topRight = new RadialGradientBrush(topRightStops)
{
RadiusX = 1d,
RadiusY = 1d,
Center = new Point(0d, 1d),
GradientOrigin = new Point(0d, 1d)
};
topRight.Freeze();
brushes[(int)Placement.TopRight] = topRight;
GradientStopCollection bottomLeftStops;
if (cornerRadius.BottomLeft == 0d)
bottomLeftStops = sideStops;
else if (cornerRadius.BottomLeft == cornerRadius.TopLeft)
bottomLeftStops = topLeftStops;
else if (cornerRadius.BottomLeft == cornerRadius.TopRight)
bottomLeftStops = topRightStops;
else
bottomLeftStops = CreateStops(c, cornerRadius.BottomLeft);
var bottomLeft = new RadialGradientBrush(bottomLeftStops)
{
RadiusX = 1d,
RadiusY = 1d,
Center = new Point(1d, 0d),
GradientOrigin = new Point(1d, 0d)
};
bottomLeft.Freeze();
brushes[(int)Placement.BottomLeft] = bottomLeft;
GradientStopCollection bottomRightStops;
if (cornerRadius.BottomRight == 0d)
bottomRightStops = sideStops;
else if (cornerRadius.BottomRight == cornerRadius.TopLeft)
bottomRightStops = topLeftStops;
else if (cornerRadius.BottomRight == cornerRadius.TopRight)
bottomRightStops = topRightStops;
else if (cornerRadius.BottomRight == cornerRadius.BottomLeft)
bottomRightStops = bottomLeftStops;
else
bottomRightStops = CreateStops(c, cornerRadius.BottomRight);
var bottomRight = new RadialGradientBrush(bottomRightStops)
{
RadiusX = 1d,
RadiusY = 1d,
Center = new Point(0d, 0d),
GradientOrigin = new Point(0d, 0d)
};
bottomRight.Freeze();
brushes[(int)Placement.BottomRight] = bottomRight;
return brushes;
}
private Brush[] GetBrushes(Color c, CornerRadius cornerRadius)
{
if (_commonBrushes is null)
lock (_resourceAccess)
{
if (_commonBrushes is null)
{
_commonBrushes = CreateBrushes(c, cornerRadius);
_commonCornerRadius = cornerRadius;
}
}
if (c == ((SolidColorBrush)_commonBrushes[(int)Placement.Center]).Color && cornerRadius == _commonCornerRadius)
{
_brushes = null;
return _commonBrushes;
}
if (_brushes is null) _brushes = CreateBrushes(c, cornerRadius);
return _brushes;
}
private enum Placement
{
TopLeft = 0,
Top = 1,
TopRight = 2,
Left = 3,
Center = 4,
Right = 5,
BottomLeft = 6,
Bottom = 7,
BottomRight = 8
}
}
// 参考自:https://referencesource.microsoft.com/#PresentationFramework.Aero/parent/Shared/Microsoft/Windows/Themes/SystemDropShadowChrome.cs,6d9c27d92a8128c1