using Avalonia; using Avalonia.Controls; namespace Toolkit.UI.Controls.Avalonia { public class ResponsiveGrid : Grid { public static readonly AvaloniaProperty ActualColumnProperty = AvaloniaProperty.RegisterAttached("ActualColumn", 0); public static readonly AvaloniaProperty ActualRowProperty = AvaloniaProperty.RegisterAttached("ActualRow", 0); public static readonly StyledProperty BreakPointsProperty = AvaloniaProperty.Register(nameof(Thresholds)); public static readonly AvaloniaProperty LargeOffsetProperty = AvaloniaProperty.RegisterAttached("LargeOffset", 0); public static readonly AvaloniaProperty LargePullProperty = AvaloniaProperty.RegisterAttached("LargePull", 0); public static readonly AvaloniaProperty LargePushProperty = AvaloniaProperty.RegisterAttached("LargePush", 0); public static readonly AvaloniaProperty LargeProperty = AvaloniaProperty.RegisterAttached("Large", 0); public static readonly StyledProperty MaxDivisionProperty = AvaloniaProperty.Register(nameof(MaxDivision), 12); public static readonly AvaloniaProperty MediumOffsetProperty = AvaloniaProperty.RegisterAttached("MediumOffset", 0); public static readonly AvaloniaProperty MediumPullProperty = AvaloniaProperty.RegisterAttached("MediumPull", 0); public static readonly AvaloniaProperty MediumPushProperty = AvaloniaProperty.RegisterAttached("MediumPush", 0); public static readonly AvaloniaProperty MediumProperty = AvaloniaProperty.RegisterAttached("Medium", 0); public static readonly AvaloniaProperty SmallOffsetProperty = AvaloniaProperty.RegisterAttached("SmallOffset", 0); public static readonly AvaloniaProperty SmallPullProperty = AvaloniaProperty.RegisterAttached("SmallPull", 0); public static readonly AvaloniaProperty SmallPushProperty = AvaloniaProperty.RegisterAttached("SmallPush", 0); public static readonly AvaloniaProperty SmallProperty = AvaloniaProperty.RegisterAttached("Small", 0); public static readonly AvaloniaProperty ExtraSmallOffsetProperty = AvaloniaProperty.RegisterAttached("ExtraSmallOffset", 0); public static readonly AvaloniaProperty ExtraSmallPullProperty = AvaloniaProperty.RegisterAttached("ExtraSmallPull", 0); public static readonly AvaloniaProperty ExtraSmallPushProperty = AvaloniaProperty.RegisterAttached("ExtraSmallPush", 0); public static readonly AvaloniaProperty ExtraSmallProperty = AvaloniaProperty.RegisterAttached("ExtraSmall", 0); static ResponsiveGrid() { AffectsMeasure( MaxDivisionProperty, BreakPointsProperty, LargeProperty, MediumProperty, SmallProperty, ExtraSmallProperty, LargeOffsetProperty, LargePullProperty, LargePushProperty, MediumOffsetProperty, MediumPullProperty, MediumPushProperty, SmallOffsetProperty, SmallPullProperty, SmallPushProperty, ExtraSmallOffsetProperty, ExtraSmallPullProperty, ExtraSmallPushProperty ); } public ResponsiveGrid() { MaxDivision = 12; Thresholds = new SizeThresholds(); } public int MaxDivision { get => GetValue(MaxDivisionProperty); set => SetValue(MaxDivisionProperty, value); } public SizeThresholds Thresholds { get => GetValue(BreakPointsProperty); set => SetValue(BreakPointsProperty, value); } public static int GetActualColumn(AvaloniaObject avaloniaObject) => avaloniaObject.GetValue(ActualColumnProperty); public static int GetActualRow(AvaloniaObject avaloniaObject) => avaloniaObject.GetValue(ActualRowProperty); public static int GetLarge(AvaloniaObject avaloniaObject) => avaloniaObject.GetValue(LargeProperty); public static int GetLargeOffset(AvaloniaObject avaloniaObject) => avaloniaObject.GetValue(LargeOffsetProperty); public static int GetLargePull(AvaloniaObject avaloniaObject) => avaloniaObject.GetValue(LargePullProperty); public static int GetLargePush(AvaloniaObject avaloniaObject) => avaloniaObject.GetValue(LargePushProperty); public static int GetMedium(AvaloniaObject avaloniaObject) => avaloniaObject.GetValue(MediumProperty); public static int GetMediumOffset(AvaloniaObject avaloniaObject) => avaloniaObject.GetValue(MediumOffsetProperty); public static int GetMediumPull(AvaloniaObject avaloniaObject) => avaloniaObject.GetValue(MediumPullProperty); public static int GetMediumPush(AvaloniaObject avaloniaObject) => avaloniaObject.GetValue(MediumPushProperty); public static int GetSmall(AvaloniaObject avaloniaObject) => avaloniaObject.GetValue(SmallProperty); public static int GetSmallOffset(AvaloniaObject avaloniaObject) => avaloniaObject.GetValue(SmallOffsetProperty); public static int GetSmallPull(AvaloniaObject avaloniaObject) => avaloniaObject.GetValue(SmallPullProperty); public static int GetSmallPush(AvaloniaObject avaloniaObject) => avaloniaObject.GetValue(SmallPushProperty); public static int GetExtraSmall(AvaloniaObject avaloniaObject) => avaloniaObject.GetValue(ExtraSmallProperty); public static int GetExtraSmallOffset(AvaloniaObject avaloniaObject) => avaloniaObject.GetValue(ExtraSmallOffsetProperty); public static int GetExtraSmallPull(AvaloniaObject avaloniaObject) => avaloniaObject.GetValue(ExtraSmallPullProperty); public static int GetExtraSmallPush(AvaloniaObject avaloniaObject) => avaloniaObject.GetValue(ExtraSmallPushProperty); public static void SetLarge(AvaloniaObject avaloniaObject, int value) => avaloniaObject.SetValue(LargeProperty, value); public static void SetLargeOffset(AvaloniaObject avaloniaObject, int value) => avaloniaObject.SetValue(LargeOffsetProperty, value); public static void SetLargePull(AvaloniaObject avaloniaObject, int value) => avaloniaObject.SetValue(LargePullProperty, value); public static void SetLargePush(AvaloniaObject avaloniaObject, int value) => avaloniaObject.SetValue(LargePushProperty, value); public static void SetMedium(AvaloniaObject avaloniaObject, int value) => avaloniaObject.SetValue(MediumProperty, value); public static void SetMediumOffset(AvaloniaObject avaloniaObject, int value) => avaloniaObject.SetValue(MediumOffsetProperty, value); public static void SetMediumPull(AvaloniaObject avaloniaObject, int value) => avaloniaObject.SetValue(MediumPullProperty, value); public static void SetMediumPush(AvaloniaObject avaloniaObject, int value) => avaloniaObject.SetValue(MediumPushProperty, value); public static void SetSmall(AvaloniaObject avaloniaObject, int value) => avaloniaObject.SetValue(SmallProperty, value); public static void SetSmallOffset(AvaloniaObject avaloniaObject, int value) => avaloniaObject.SetValue(SmallOffsetProperty, value); public static void SetSmallPull(AvaloniaObject avaloniaObject, int value) => avaloniaObject.SetValue(SmallPullProperty, value); public static void SetSmallPush(AvaloniaObject avaloniaObject, int value) => avaloniaObject.SetValue(SmallPushProperty, value); public static void SetExtraSmall(AvaloniaObject avaloniaObject, int value) => avaloniaObject.SetValue(ExtraSmallProperty, value); public static void SetExtraSmallOffset(AvaloniaObject avaloniaObject, int value) => avaloniaObject.SetValue(ExtraSmallOffsetProperty, value); public static void SetExtraSmallPull(AvaloniaObject avaloniaObject, int value) => avaloniaObject.SetValue(ExtraSmallPullProperty, value); public static void SetExtraSmallPush(AvaloniaObject avaloniaObject, int value) => avaloniaObject.SetValue(ExtraSmallPushProperty, value); protected static void SetActualColumn(AvaloniaObject avaloniaObject, int value) => avaloniaObject.SetValue(ActualColumnProperty, value); protected static void SetActualRow(AvaloniaObject avaloniaObject, int value) => avaloniaObject.SetValue(ActualRowProperty, value); protected override Size ArrangeOverride(Size finalSize) { double columnWidth = finalSize.Width / MaxDivision; IEnumerable> groupedRows = Children.OfType().GroupBy(GetActualRow); double yOffset = 0; foreach (IGrouping row in groupedRows) { double maxRowHeight = row.Max(control => control.DesiredSize.Height); foreach (Control element in row) { int column = GetActualColumn(element); int span = GetSpan(element, finalSize.Width); Rect rect = new(column * columnWidth, yOffset, span * columnWidth, maxRowHeight); element.Arrange(rect); } yOffset += maxRowHeight; } return finalSize; } protected int GetOffset(Control control, double width) { int GetXS(Control control) => GetExtraSmallOffset(control) is 0 ? 0 : GetExtraSmallOffset(control); int GetSM(Control control) => GetSmallOffset(control) is 0 ? GetXS(control) : GetSmallOffset(control); int GetMD(Control control) => GetMediumOffset(control) is 0 ? GetSM(control) : GetMediumOffset(control); int GetLG(Control control) => GetLargeOffset(control) is 0 ? GetMD(control) : GetLargeOffset(control); int span = width < Thresholds.ExtraSmallToSmall ? GetXS(control) : width < Thresholds.SmallToMedium ? GetSM(control) : width < Thresholds.MediumToLarge ? GetMD(control) : GetLG(control); return Math.Min(span, MaxDivision); } protected int GetPull(Control control, double width) { int GetXS(Control control) => GetExtraSmallPull(control) is 0 ? 0 : GetExtraSmallPull(control); int GetSM(Control control) => GetSmallPull(control) is 0 ? GetXS(control) : GetSmallPull(control); int GetMD(Control control) => GetMediumPull(control) is 0 ? GetSM(control) : GetMediumPull(control); int GetLG(Control control) => GetLargePull(control) is 0 ? GetMD(control) : GetLargePull(control); int span = width < Thresholds.ExtraSmallToSmall ? GetXS(control) : width < Thresholds.SmallToMedium ? GetSM(control) : width < Thresholds.MediumToLarge ? GetMD(control) : GetLG(control); return Math.Min(span, MaxDivision); } protected int GetPush(Control control, double width) { int GetXS(Control control) => GetExtraSmallPush(control) is 0 ? 0 : GetExtraSmallPush(control); int GetSM(Control control) => GetSmallPush(control) is 0 ? GetXS(control) : GetSmallPush(control); int GetMD(Control control) => GetMediumPush(control) is 0 ? GetSM(control) : GetMediumPush(control); int GetLG(Control control) => GetLargePush(control) is 0 ? GetMD(control) : GetLargePush(control); int span = width < Thresholds.ExtraSmallToSmall ? GetXS(control) : width < Thresholds.SmallToMedium ? GetSM(control) : width < Thresholds.MediumToLarge ? GetMD(control) : GetLG(control); return Math.Min(span, MaxDivision); } protected int GetSpan(Control control, double width) { int GetXS(Control control) => GetExtraSmall(control) is 0 ? MaxDivision : GetExtraSmall(control); int GetSM(Control control) => GetSmall(control) is 0 ? GetXS(control) : GetSmall(control); int GetMD(Control control) => GetMedium(control) is 0 ? GetSM(control) : GetMedium(control); int GetLG(Control control) => GetLarge(control) is 0 ? GetMD(control) : GetLarge(control); int span = width < Thresholds.ExtraSmallToSmall ? GetXS(control) : width < Thresholds.SmallToMedium ? GetSM(control) : width < Thresholds.MediumToLarge ? GetMD(control) : GetLG(control); return Math.Min(Math.Max(0, span), MaxDivision); ; } protected override Size MeasureOverride(Size availableSize) { int count = 0; int currentRow = 0; double availableWidth = double.IsPositiveInfinity(availableSize.Width) ? double.PositiveInfinity : availableSize.Width / MaxDivision; foreach (Control control in Children.OfType()) { if (control.IsVisible) { int span = GetSpan(control, availableSize.Width); int offset = GetOffset(control, availableSize.Width); int push = GetPush(control, availableSize.Width); int pull = GetPull(control, availableSize.Width); if (count + span + offset > MaxDivision) { currentRow++; count = 0; } SetActualColumn(control, count + offset + push - pull); SetActualRow(control, currentRow); count += span + offset; Size size = new(availableWidth * span, double.PositiveInfinity); control.Measure(size); } } IEnumerable> groupedRows = Children.OfType().GroupBy(GetActualRow); Size totalSize = new(groupedRows.Max(rows => rows.Sum(control => control.DesiredSize.Width)), groupedRows.Sum(rows => rows.Max(control => control.DesiredSize.Height))); return totalSize; } } }