using System.ComponentModel; using System.Numerics; using System.Runtime.CompilerServices; namespace Dashboard.Layout { public readonly record struct ComputedBox(Vector4 Margin, Vector4 Padding, Vector4 Border, Vector2 Size) { public float MarginLeft => Margin.X; public float MarginTop => Margin.Y; public float MarginRight => Margin.Z; public float MarginBottom => Margin.W; public float PaddingLeft => Padding.X; public float PaddingTop => Padding.Y; public float PaddingRight => Padding.Z; public float PaddingBottom => Padding.W; public float BorderLeft => Border.X; public float BorderTop => Border.Y; public float BorderRight => Border.Z; public float BorderBottom => Border.W; public float Width => Size.X; public float Height => Size.Y; public Vector2 BoundingSize => new Vector2( MarginLeft + BorderLeft + Width + BorderRight + MarginRight, MarginTop + BorderTop + Height + BorderBottom + MarginBottom); public Vector2 ContentSize => new Vector2( Width - PaddingLeft - PaddingRight, Height - PaddingTop - PaddingBottom); public Vector4 ContentExtents => new Vector4( PaddingLeft, PaddingTop, PaddingLeft + PaddingRight + Width, PaddingTop + PaddingBottom + Height); public Vector4 CornerRadii { get; init; } = Vector4.Zero; } public class LayoutBox : INotifyPropertyChanged { private Vector4 _margin = Vector4.Zero; private Vector4 _padding = Vector4.Zero; private Vector4 _border = Vector4.Zero; private Vector2 _size = Vector2.Zero; private Vector2 _minimumSize = -Vector2.One; private Vector2 _maximumSize = -Vector2.One; private Vector4 _cornerRadii = Vector4.Zero; private LayoutUnit _marginLeftUnit = LayoutUnit.Pixel; private LayoutUnit _marginTopUnit = LayoutUnit.Pixel; private LayoutUnit _marginRightUnit = LayoutUnit.Pixel; private LayoutUnit _marginBottomUnit = LayoutUnit.Pixel; private LayoutUnit _paddingLeftUnit = LayoutUnit.Pixel; private LayoutUnit _paddingTopUnit = LayoutUnit.Pixel; private LayoutUnit _paddingRightUnit = LayoutUnit.Pixel; private LayoutUnit _paddingBottomUnit = LayoutUnit.Pixel; private LayoutUnit _borderLeftUnit = LayoutUnit.Pixel; private LayoutUnit _borderTopUnit = LayoutUnit.Pixel; private LayoutUnit _borderRightUnit = LayoutUnit.Pixel; private LayoutUnit _borderBottomUnit = LayoutUnit.Pixel; private LayoutUnit _widthUnit = LayoutUnit.Pixel; private LayoutUnit _heightUnit = LayoutUnit.Pixel; private LayoutUnit _minimumWidthUnit = LayoutUnit.Pixel; private LayoutUnit _minimumHeightUnit = LayoutUnit.Pixel; private LayoutUnit _maximumWidthUnit = LayoutUnit.Pixel; private LayoutUnit _maximumHeightUnit = LayoutUnit.Pixel; private LayoutUnit _cornerRadiusTopLeftUnit = LayoutUnit.Pixel; private LayoutUnit _cornerRadiusBottomLeftUnit = LayoutUnit.Pixel; private LayoutUnit _cornerRadiusBottomRightUnit = LayoutUnit.Pixel; private LayoutUnit _cornerRadiusTopRightUnit = LayoutUnit.Pixel; private ComputedBox _computedBox = new ComputedBox(); public bool UpdateRequired { get; private set; } = true; public Vector4 Margin { get => _margin; set => SetField(ref _margin, value); } public Vector4 Padding { get => _padding; set => SetField(ref _padding, value); } public Vector4 Border { get => _border; set => SetField(ref _border, value); } public Vector2 Size { get => _size; set => SetField(ref _size, value); } public Vector2 MinimumSize { get => _minimumSize; set => SetField(ref _minimumSize, value); } public Vector2 MaximumSize { get => _maximumSize; set => SetField(ref _maximumSize, value); } public Vector4 CornerRadii { get => _cornerRadii; set => SetField(ref _cornerRadii, value); } public float MarginLeft { get => Margin.X; set => Margin = Margin with { X = value }; } public float MarginTop { get => Margin.Y; set => Margin = Margin with { Y = value }; } public float MarginRight { get => Margin.Z; set => Margin = Margin with { Z = value }; } public float MarginBottom { get => Margin.W; set => Margin = Margin with { W = value }; } public float PaddingLeft { get => Padding.X; set => Padding = Padding with { X = value }; } public float PaddingTop { get => Padding.Y; set => Padding = Padding with { Y = value }; } public float PaddingRight { get => Padding.Z; set => Padding = Padding with { Z = value }; } public float PaddingBottom { get => Padding.W; set => Padding = Padding with { W = value }; } public float BorderLeft { get => Border.X; set => Border = Border with { X = value }; } public float BorderTop { get => Border.Y; set => Border = Border with { Y = value }; } public float BorderRight { get => Border.Z; set => Border = Border with { Z = value }; } public float BorderBottom { get => Border.W; set => Border = Border with { W = value }; } public float CornerRadiusTopLeft { get => CornerRadii.X; set => CornerRadii = CornerRadii with { X = value }; } public float CornerRadiusBottomLeft { get => CornerRadii.Y; set => CornerRadii = CornerRadii with { Y = value }; } public float CornerRadiusBottomRight { get => CornerRadii.Z; set => CornerRadii = CornerRadii with { Z = value }; } public float CornerRadiusTopRight { get => CornerRadii.W; set => CornerRadii = CornerRadii with { W = value }; } public LayoutUnit MarginLeftUnit { get => _marginLeftUnit; set => SetField(ref _marginLeftUnit, value); } public LayoutUnit MarginTopUnit { get => _marginTopUnit; set => SetField(ref _marginTopUnit, value); } public LayoutUnit MarginRightUnit { get => _marginRightUnit; set => SetField(ref _marginRightUnit, value); } public LayoutUnit MarginBottomUnit { get => _marginBottomUnit; set => SetField(ref _marginBottomUnit, value); } public LayoutUnit PaddingLeftUnit { get => _paddingLeftUnit; set => SetField(ref _paddingLeftUnit, value); } public LayoutUnit PaddingTopUnit { get => _paddingTopUnit; set => SetField(ref _paddingTopUnit, value); } public LayoutUnit PaddingRightUnit { get => _paddingRightUnit; set => SetField(ref _paddingRightUnit, value); } public LayoutUnit PaddingBottomUnit { get => _paddingBottomUnit; set => SetField(ref _paddingBottomUnit, value); } public LayoutUnit BorderLeftUnit { get => _borderLeftUnit; set => SetField(ref _borderLeftUnit, value); } public LayoutUnit BorderTopUnit { get => _borderTopUnit; set => SetField(ref _borderTopUnit, value); } public LayoutUnit BorderRightUnit { get => _borderRightUnit; set => SetField(ref _borderRightUnit, value); } public LayoutUnit BorderBottomUnit { get => _borderBottomUnit; set => SetField(ref _borderBottomUnit, value); } public LayoutUnit WidthUnit { get => _widthUnit; set => SetField(ref _widthUnit, value); } public LayoutUnit HeightUnit { get => _heightUnit; set => SetField(ref _heightUnit, value); } public LayoutUnit MinimumWidthUnit { get => _minimumWidthUnit; set => SetField(ref _minimumWidthUnit, value); } public LayoutUnit MinimumHeightUnit { get => _minimumHeightUnit; set => SetField(ref _minimumHeightUnit, value); } public LayoutUnit MaximumWidthUnit { get => _maximumWidthUnit; set => SetField(ref _maximumWidthUnit, value); } public LayoutUnit MaximumHeightUnit { get => _maximumHeightUnit; set => SetField(ref _maximumHeightUnit, value); } public LayoutUnit CornerRadiusTopLeftUnit { get => _cornerRadiusTopLeftUnit; set => SetField(ref _cornerRadiusTopLeftUnit, value); } public LayoutUnit CornerRadiusBottomLeftUnit { get => _cornerRadiusBottomLeftUnit; set => SetField(ref _cornerRadiusBottomLeftUnit, value); } public LayoutUnit CornerRadiusBottomRightUnit { get => _cornerRadiusBottomRightUnit; set => SetField(ref _cornerRadiusBottomRightUnit, value); } public LayoutUnit CornerRadiusTopRightUnit { get => _cornerRadiusTopRightUnit; set => SetField(ref _cornerRadiusTopRightUnit, value); } public ComputedBox ComputedBox { get => _computedBox; private set => SetField(ref _computedBox, value, false); } public event PropertyChangedEventHandler? PropertyChanged; public ComputedBox ComputeLayout(Vector2 intrinsic, Vector2 dpi, Vector2 area, Vector2 star) { // TODO: take intrinsic into account. Vector4 margin = Compute(_margin, dpi, area, star, _marginLeftUnit, _marginTopUnit, _marginRightUnit, _marginBottomUnit); Vector4 padding = Compute(_padding, dpi, area, star, _paddingLeftUnit, _paddingTopUnit, _paddingRightUnit, _paddingBottomUnit); Vector4 border = Compute(_border, dpi, area, star, _borderLeftUnit, _borderTopUnit, _borderRightUnit, _borderBottomUnit); Vector2 size = Compute(_size, dpi, area, star, _widthUnit, _heightUnit); Vector2 minimumSize = Compute(_minimumSize, dpi, area, star, _minimumWidthUnit, _minimumHeightUnit); Vector2 maximumSize = Compute(_maximumSize, dpi, area, star, _maximumWidthUnit, _maximumHeightUnit); Vector4 cornerRadii = Compute(_cornerRadii, dpi, area, star, _cornerRadiusTopLeftUnit, _cornerRadiusBottomLeftUnit, _cornerRadiusBottomRightUnit, _cornerRadiusTopRightUnit); size = Vector2.Clamp(size, minimumSize, maximumSize); ComputedBox = new ComputedBox(margin, padding, border, size) { CornerRadii = cornerRadii, }; UpdateRequired = false; return ComputedBox; } private static float Compute(float value, float dpi, float length, float star, LayoutUnit unit) { const float dpiToMm = 0f; const float dpiToPt = 0f; return unit switch { LayoutUnit.Pixel => value, LayoutUnit.Millimeter => value * dpi * dpiToMm, LayoutUnit.Percent => value * length, LayoutUnit.Point => value * dpi * dpiToPt, LayoutUnit.Star => value * star, _ => throw new ArgumentException(nameof(unit)), }; } private static Vector2 Compute(Vector2 value, Vector2 dpi, Vector2 size, Vector2 star, LayoutUnit xUnit, LayoutUnit yUnit) { return new Vector2( Compute(value.X, dpi.X, size.X, star.X, xUnit), Compute(value.Y, dpi.Y, size.Y, star.Y, yUnit)); } private static Vector4 Compute(Vector4 value, Vector2 dpi, Vector2 size, Vector2 star, LayoutUnit xUnit, LayoutUnit yUnit, LayoutUnit zUnit, LayoutUnit wUnit) { return new Vector4( Compute(value.X, dpi.X, size.X, star.X, xUnit), Compute(value.Y, dpi.Y, size.Y, star.Y, yUnit), Compute(value.Z, dpi.X, size.X, star.X, zUnit), Compute(value.W, dpi.Y, size.Y, star.Y, wUnit)); } private void OnPropertyChanged([CallerMemberName] string? propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } private bool SetField(ref T field, T value, bool updateRequired = true, [CallerMemberName] string? propertyName = null) { if (EqualityComparer.Default.Equals(field, value)) return false; field = value; UpdateRequired |= updateRequired; OnPropertyChanged(propertyName); return true; } } }