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

532 lines
17 KiB
C#

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using WPFDark.Internals;
namespace WPFDark.Controls
{
public class BiaUniformGrid : Panel
{
#region ColumnSpacing
public double ColumnSpacing
{
get => _columnSpacing;
set
{
if (NumberHelper.AreClose(value, _columnSpacing) == false)
SetValue(ColumnSpacingProperty, value);
}
}
private double _columnSpacing;
public static readonly DependencyProperty ColumnSpacingProperty =
DependencyProperty.Register(
nameof(ColumnSpacing),
typeof(double),
typeof(BiaUniformGrid),
new PropertyMetadata(
0.0,
(s, e) =>
{
var self = (BiaUniformGrid) s;
self._columnSpacing = (double) e.NewValue;
}));
#endregion
#region RowSpacing
public double RowSpacing
{
get => _rowSpacing;
set
{
if (NumberHelper.AreClose(value, _rowSpacing) == false)
SetValue(RowSpacingProperty, value);
}
}
private double _rowSpacing;
public static readonly DependencyProperty RowSpacingProperty =
DependencyProperty.Register(
nameof(RowSpacing),
typeof(double),
typeof(BiaUniformGrid),
new PropertyMetadata(
0.0,
(s, e) =>
{
var self = (BiaUniformGrid) s;
self._rowSpacing = (double) e.NewValue;
}));
#endregion
#region Columns
public int Columns
{
get => _Columns;
set
{
if (value != _Columns)
SetValue(ColumnsProperty, value);
}
}
private int _Columns;
public static readonly DependencyProperty ColumnsProperty =
DependencyProperty.Register(
nameof(Columns),
typeof(int),
typeof(BiaUniformGrid),
new FrameworkPropertyMetadata(
0,
FrameworkPropertyMetadataOptions.AffectsMeasure,
(s, e) =>
{
var self = (BiaUniformGrid) s;
self._Columns = (int) e.NewValue;
}));
#endregion
#region Rows
public int Rows
{
get => _Rows;
set
{
if (value != _Rows)
SetValue(RowsProperty, value);
}
}
private int _Rows;
public static readonly DependencyProperty RowsProperty =
DependencyProperty.Register(
nameof(Rows),
typeof(int),
typeof(BiaUniformGrid),
new FrameworkPropertyMetadata(
0,
FrameworkPropertyMetadataOptions.AffectsMeasure,
(s, e) =>
{
var self = (BiaUniformGrid) s;
self._Rows = (int) e.NewValue;
}));
#endregion
#region IsFillLastRow
public bool IsFillLastRow
{
get => _IsFillLastRow;
set
{
if (value != _IsFillLastRow)
SetValue(IsFillLastRowProperty, value);
}
}
private bool _IsFillLastRow = true;
public static readonly DependencyProperty IsFillLastRowProperty =
DependencyProperty.Register(
nameof(IsFillLastRow),
typeof(bool),
typeof(BiaUniformGrid),
new FrameworkPropertyMetadata(
true,
FrameworkPropertyMetadataOptions.AffectsMeasure,
(s, e) =>
{
var self = (BiaUniformGrid) s;
self._IsFillLastRow = (bool) e.NewValue;
}));
#endregion
static BiaUniformGrid()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(BiaUniformGrid),
new FrameworkPropertyMetadata(typeof(BiaUniformGrid)));
}
protected override Size MeasureOverride(Size constraint)
{
UpdateComputedValues();
var rounder = new LayoutRounder(this);
var cs = rounder.RoundLayoutValue(ColumnSpacing);
var rs = rounder.RoundLayoutValue(RowSpacing);
var childConstraint = new Size(
(constraint.Width - cs * (_columns - 1)) / _columns,
(constraint.Height - rs * (_rows - 1)) / _rows);
var maxChildDesiredWidth = 0d;
var maxChildDesiredHeight = 0d;
foreach (UIElement? child in InternalChildren)
{
if (child is null)
continue;
child.Measure(childConstraint);
var childDesiredSize = child.DesiredSize;
maxChildDesiredWidth = Math.Max(maxChildDesiredWidth, childDesiredSize.Width);
maxChildDesiredHeight = Math.Max(maxChildDesiredHeight, childDesiredSize.Height);
}
var w = maxChildDesiredWidth * _columns + cs * (_columns - 1);
var h = maxChildDesiredHeight * _rows + rs * (_rows - 1);
return new Size(w, h);
}
protected override Size ArrangeOverride(Size arrangeSize)
{
var rounder = new LayoutRounder(this);
var cs = rounder.RoundLayoutValue(ColumnSpacing);
var rs = rounder.RoundLayoutValue(RowSpacing);
var childWidth = Math.Floor((arrangeSize.Width - cs * (_columns - 1)) / _columns);
var childHeight = Math.Floor((arrangeSize.Height - rs * (_rows - 1)) / _rows);
var childBounds = default(Rect);
var xStep = childWidth + cs;
var xIndex = 0;
var yIndex = 0;
var isLastRow = yIndex == _rows - 1;
var x = 0d;
var y = 0d;
foreach (UIElement? child in InternalChildren)
{
if (child is null)
continue;
childBounds.Width = xIndex == _columns - 1
? Math.Max(0d, arrangeSize.Width - childBounds.X)
: childWidth;
childBounds.Height = isLastRow
? Math.Max(0d, arrangeSize.Height - childBounds.Y)
: childHeight;
child.Arrange(childBounds);
if (child.Visibility != Visibility.Collapsed)
{
++xIndex;
if (xIndex == _columns)
{
x = 0d;
y += childHeight + rs;
xIndex = 0;
++yIndex;
isLastRow = yIndex == _rows - 1;
childBounds.X = 0d;
childBounds.Y = rounder.RoundLayoutValue(y);
}
else
{
x += xStep;
childBounds.X = rounder.RoundLayoutValue(x);
}
}
}
return arrangeSize;
}
private void UpdateComputedValues()
{
_columns = Columns;
_rows = Rows;
if (_rows == 0 || _columns == 0)
{
var nonCollapsedCount = 0;
for (var i = 0; i != InternalChildren.Count; ++i)
{
var child = InternalChildren[i];
if (child.Visibility != Visibility.Collapsed)
nonCollapsedCount++;
}
if (nonCollapsedCount == 0)
nonCollapsedCount = 1;
if (_rows == 0)
{
if (_columns > 0)
_rows = (nonCollapsedCount + (_columns - 1)) / _columns;
else
{
_columns = nonCollapsedCount;
_rows = 1;
}
}
else if (_columns == 0)
_columns = (nonCollapsedCount + (_rows - 1)) / _rows;
}
}
private int _rows;
private int _columns;
#region
#region CornerRadius
public CornerRadius CornerRadius
{
get => _CornerRadius;
set
{
if (value != _CornerRadius)
SetValue(CornerRadiusProperty, value);
}
}
private CornerRadius _CornerRadius;
public static readonly DependencyProperty CornerRadiusProperty =
DependencyProperty.Register(
nameof(CornerRadius),
typeof(CornerRadius),
typeof(BiaUniformGrid),
new FrameworkPropertyMetadata(
default(CornerRadius),
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault |
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.SubPropertiesDoNotAffectRender,
(s, e) =>
{
var self = (BiaUniformGrid) s;
self._CornerRadius = (CornerRadius) e.NewValue;
}));
#endregion
protected override void OnRender(DrawingContext dc)
{
OnApplyChildClip();
base.OnRender(dc);
}
protected virtual void OnApplyChildClip()
{
// ReSharper disable CompareOfFloatsByEqualityOperator
if (CornerRadius.BottomLeft == 0.0 &&
CornerRadius.BottomRight == 0.0 &&
CornerRadius.TopLeft == 0.0 &&
CornerRadius.TopRight == 0.0)
{
var parentClip = (Parent as UIElement)?.Clip;
if (parentClip != null)
Clip = parentClip;
return;
}
// ReSharper restore CompareOfFloatsByEqualityOperator
var rect = new Rect(RenderSize);
var key = (rect, CornerRadius);
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(RenderSize), CornerRadius)
: MakeRoundRectangleGeometry(new Rect(RenderSize), CornerRadius);
_clipRectCache.Add(key, clipRect);
}
Clip = clipRect;
}
private static readonly Dictionary<(Rect Rect, CornerRadius CornerRadius), Geometry> _clipRectCache =
new Dictionary<(Rect Rect, CornerRadius CornerRadius), Geometry>();
private static Geometry MakeRoundRectangleGeometrySameCorner(Rect baseRect, CornerRadius cornerRadius)
{
var radius = (0.0, cornerRadius.TopLeft).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)
{
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 topLeftRect = new Rect(
baseRect.Location.X,
baseRect.Location.Y,
(0.0, cornerRadius.TopLeft).Max(),
(0.0, cornerRadius.TopLeft).Max());
var topRightRect = new Rect(
baseRect.Location.X + baseRect.Width - cornerRadius.TopRight,
baseRect.Location.Y,
(0.0, cornerRadius.TopRight).Max(),
(0.0, cornerRadius.TopRight).Max());
var bottomRightRect = new Rect(
baseRect.Location.X + baseRect.Width - cornerRadius.BottomRight,
baseRect.Location.Y + baseRect.Height - cornerRadius.BottomRight,
(0.0, cornerRadius.BottomRight).Max(),
(0.0, cornerRadius.BottomRight).Max());
var bottomLeftRect = new Rect(
baseRect.Location.X,
baseRect.Location.Y + baseRect.Height - cornerRadius.BottomLeft,
(0.0, cornerRadius.BottomLeft).Max(),
(0.0, cornerRadius.BottomLeft).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;
}
#endregion
}
}