using System.ComponentModel; using System.Numerics; using System.Runtime.CompilerServices; namespace Dashboard.Layout { public class ContainerLayoutInfo : INotifyPropertyChanged { public FlowDirection FlowDirection { get; set => SetField(ref field, value); } = FlowDirection.Row; public Extents Padding { get; set => SetField(ref field, value); } public event PropertyChangedEventHandler? PropertyChanged; public virtual void ValidateLayout(ILayoutContainer container) { if (container.Count == 0) return; ItemSolution[] items = [.. container.Select(x => new ItemSolution(x))]; // TODO: is this the actual size budget available at draw time? Vector2 budget = container.Layout.ComputedBox.Size - Padding.ExtentSize; IEnumerable growItems = items.Where(x => x.DisplayMode == DisplayMode.Grow); Vector2 denominator = growItems.Any() ? growItems.Select(x => x.Layout.Size + x.Layout.Margin.ExtentSize) .Aggregate((a, b) => a + b) : Vector2.One; // TODO: The layout engine assumes row layout atm. Implement other layouts. for (int i = 0; i < items.Length; i++) items[i].Measure(budget, budget/denominator); float fixedWidth = items .Where(x => x.DisplayMode != DisplayMode.Grow) .Sum(x => x.DesiredSize.X); float remaining = budget.X - fixedWidth; float star = remaining / denominator.X; Vector2 pen = container.Layout.ComputedBox.Min + new Vector2(Padding.Left, Padding.Top); for (int i = 0; i < items.Length; i++) { items[i].Measure(budget, new Vector2(star, budget.Y)); items[i].Position = pen; pen.X += items[i].DesiredSize.X; items[i].Finish(); } } private struct ItemSolution(ILayoutItem item) { public ILayoutItem Item { get; } = item; public Vector2 InstrinsicSize { get; } = item.CalculateIntrinsicSize(); public Vector2 DesiredSize { get; private set; } public readonly LayoutInfo Layout => Item.Layout; public readonly DisplayMode DisplayMode => Item.Layout.DisplayMode; public Vector2 Position { get; set; } public void Measure(Vector2 budget, Vector2 coefficient) { switch (DisplayMode) { case DisplayMode.None: DesiredSize = Vector2.Zero; break; case DisplayMode.Fit: DesiredSize = InstrinsicSize; break; case DisplayMode.Fixed: DesiredSize = Layout.Size; break; case DisplayMode.Grow: DesiredSize = Layout.Size * coefficient; break; case DisplayMode.Relative: DesiredSize = Layout.Size * budget; break; } DesiredSize = Vector2.Clamp(DesiredSize, Layout.MinimumSize, Layout.MaximumSize); } public void Finish() { Layout.ComputedBox = new Box2d(Position, Position + DesiredSize); } } protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } protected bool SetField(ref T field, T value, [CallerMemberName] string? propertyName = null) { if (EqualityComparer.Default.Equals(field, value)) return false; field = value; OnPropertyChanged(propertyName); return true; } } }