Files
ShrlAlgoToolkit/WPFDark/Controls/BiaClippingBorder.xaml.cs
ShrlAlgo 4d35cadb56 更新
2025-07-11 09:20:23 +08:00

242 lines
8.8 KiB
C#

using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using WPFDark.Internals;
namespace WPFDark.Controls
{
// https://stackoverflow.com/questions/324641/how-to-make-the-contents-of-a-round-cornered-border-be-also-round-cornered
public class BiaClippingBorder : Border
{
protected override void OnRender(DrawingContext dc)
{
OnApplyChildClip();
base.OnRender(dc);
}
public override UIElement Child
{
get => base.Child;
set
{
if (Child == value)
return;
Child?.SetValue(ClipProperty, _oldClip);
_oldClip = value?.ReadLocalValue(ClipProperty);
base.Child = value;
}
}
static BiaClippingBorder()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(BiaClippingBorder),
new FrameworkPropertyMetadata(typeof(BiaClippingBorder)));
}
protected virtual void OnApplyChildClip()
{
var child = Child;
if (child is null)
return;
var key = HashCodeMaker.Make(
Child.RenderSize.Width, Child.RenderSize.Height,
CornerRadius.BottomLeft, CornerRadius.BottomRight,
CornerRadius.TopLeft, CornerRadius.TopRight);
if (_clipRectCache.TryGetValue(key, out var clipRect) == false)
{
var isSame =
NumberHelper.AreClose(CornerRadius.TopLeft, CornerRadius.TopRight) &&
NumberHelper.AreClose(CornerRadius.TopRight, CornerRadius.BottomRight) &&
NumberHelper.AreClose(CornerRadius.BottomRight, CornerRadius.BottomLeft) &&
NumberHelper.AreClose(CornerRadius.BottomLeft, CornerRadius.TopLeft);
clipRect = isSame
? MakeRoundRectangleGeometrySameCorner(new Rect(Child.RenderSize), CornerRadius, BorderThickness)
: MakeRoundRectangleGeometry(new Rect(Child.RenderSize), CornerRadius, BorderThickness);
_clipRectCache.Add(key, clipRect);
}
child.Clip = clipRect;
}
private object? _oldClip;
private static readonly Dictionary<long, Geometry> _clipRectCache = new Dictionary<long, Geometry>();
private static Geometry MakeRoundRectangleGeometrySameCorner(Rect baseRect, CornerRadius cornerRadius,
Thickness borderThickness)
{
var radius = (0.0, cornerRadius.TopLeft - borderThickness.Left * 0.5).Max();
var clipRect = new RectangleGeometry
{
RadiusX = radius,
RadiusY = radius,
Rect = baseRect
};
clipRect.Freeze();
return clipRect;
}
// https://wpfspark.wordpress.com/2011/06/08/clipborder-a-wpf-border-that-clips/
private static Geometry MakeRoundRectangleGeometry(Rect baseRect, CornerRadius cornerRadius, Thickness borderThickness)
{
if (cornerRadius.TopLeft < double.Epsilon)
cornerRadius.TopLeft = 0.0;
if (cornerRadius.TopRight < double.Epsilon)
cornerRadius.TopRight = 0.0;
if (cornerRadius.BottomLeft < double.Epsilon)
cornerRadius.BottomLeft = 0.0;
if (cornerRadius.BottomRight < double.Epsilon)
cornerRadius.BottomRight = 0.0;
var leftHalf = borderThickness.Left * 0.5;
if (leftHalf < double.Epsilon)
leftHalf = 0.0;
var topHalf = borderThickness.Top * 0.5;
if (topHalf < double.Epsilon)
topHalf = 0.0;
var rightHalf = borderThickness.Right * 0.5;
if (rightHalf < double.Epsilon)
rightHalf = 0.0;
var bottomHalf = borderThickness.Bottom * 0.5;
if (bottomHalf < double.Epsilon)
bottomHalf = 0.0;
var topLeftRect = new Rect(
baseRect.Location.X,
baseRect.Location.Y,
(0.0, cornerRadius.TopLeft - leftHalf).Max(),
(0.0, cornerRadius.TopLeft - rightHalf).Max());
var topRightRect = new Rect(
baseRect.Location.X + baseRect.Width - cornerRadius.TopRight + rightHalf,
baseRect.Location.Y,
(0.0, cornerRadius.TopRight - rightHalf).Max(),
(0.0, cornerRadius.TopRight - topHalf).Max());
var bottomRightRect = new Rect(
baseRect.Location.X + baseRect.Width - cornerRadius.BottomRight + rightHalf,
baseRect.Location.Y + baseRect.Height - cornerRadius.BottomRight + bottomHalf,
(0.0, cornerRadius.BottomRight - rightHalf).Max(),
(0.0, cornerRadius.BottomRight - bottomHalf).Max());
var bottomLeftRect = new Rect(
baseRect.Location.X,
baseRect.Location.Y + baseRect.Height - cornerRadius.BottomLeft + bottomHalf,
(0.0, cornerRadius.BottomLeft - leftHalf).Max(),
(0.0, cornerRadius.BottomLeft - bottomHalf).Max());
if (topLeftRect.Right > topRightRect.Left)
{
var newWidth = topLeftRect.Width / (topLeftRect.Width + topRightRect.Width) * baseRect.Width;
topLeftRect = new Rect(
topLeftRect.Location.X,
topLeftRect.Location.Y,
newWidth,
topLeftRect.Height);
topRightRect = new Rect(
baseRect.Left + newWidth,
topRightRect.Location.Y,
(0.0, baseRect.Width - newWidth).Max(),
topRightRect.Height);
}
if (topRightRect.Bottom > bottomRightRect.Top)
{
var newHeight = topRightRect.Height / (topRightRect.Height + bottomRightRect.Height) * baseRect.Height;
topRightRect = new Rect(
topRightRect.Location.X,
topRightRect.Location.Y,
topRightRect.Width,
newHeight);
bottomRightRect = new Rect(
bottomRightRect.Location.X,
baseRect.Top + newHeight,
bottomRightRect.Width,
(0.0, baseRect.Height - newHeight).Max());
}
if (bottomRightRect.Left < bottomLeftRect.Right)
{
var newWidth = bottomLeftRect.Width / (bottomLeftRect.Width + bottomRightRect.Width) * baseRect.Width;
bottomLeftRect = new Rect(
bottomLeftRect.Location.X,
bottomLeftRect.Location.Y,
newWidth,
bottomLeftRect.Height);
bottomRightRect = new Rect(
baseRect.Left + newWidth,
bottomRightRect.Location.Y,
(0.0, baseRect.Width - newWidth).Max(),
bottomRightRect.Height);
}
if (bottomLeftRect.Top < topLeftRect.Bottom)
{
var newHeight = topLeftRect.Height / (topLeftRect.Height + bottomLeftRect.Height) * baseRect.Height;
topLeftRect = new Rect(
topLeftRect.Location.X,
topLeftRect.Location.Y,
topLeftRect.Width,
newHeight);
bottomLeftRect = new Rect(
bottomLeftRect.Location.X,
baseRect.Top + newHeight,
bottomLeftRect.Width,
(0.0, baseRect.Height - newHeight).Max());
}
var clipRect = new StreamGeometry();
using (var context = clipRect.Open())
{
context.BeginFigure(topLeftRect.BottomLeft, true, true);
context.ArcTo(topLeftRect.TopRight, topLeftRect.Size, 0, false, SweepDirection.Clockwise,
true, true);
context.LineTo(topRightRect.TopLeft, true, true);
context.ArcTo(topRightRect.BottomRight, topRightRect.Size, 0, false, SweepDirection.Clockwise,
true, true);
context.LineTo(bottomRightRect.TopRight, true, true);
context.ArcTo(bottomRightRect.BottomLeft, bottomRightRect.Size, 0, false, SweepDirection.Clockwise,
true, true);
context.LineTo(bottomLeftRect.BottomRight, true, true);
context.ArcTo(bottomLeftRect.TopLeft, bottomLeftRect.Size, 0, false, SweepDirection.Clockwise,
true, true);
}
clipRect.Freeze();
return clipRect;
}
}
}