436 lines
14 KiB
C#
436 lines
14 KiB
C#
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<T>(ref T field, T value, bool updateRequired = true, [CallerMemberName] string? propertyName = null)
|
|
{
|
|
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
|
|
field = value;
|
|
UpdateRequired |= updateRequired;
|
|
OnPropertyChanged(propertyName);
|
|
return true;
|
|
}
|
|
}
|
|
}
|