Editor/Commands/SuiDistributeElementsCommand.cs
using System.Collections.Generic;
using System.Linq;
using SboxUiDesigner.Runtime;
namespace SboxUiDesigner.EditorUi.Commands;
/// <summary>
/// Distribute multiple absolute-mode elements with equal spacing along a single
/// axis. With N=3 elements: leftmost and rightmost stay put; the middle ones
/// are repositioned so the gaps between consecutive elements are equal.
///
/// V1 requires N >= 3 (for N=2 there's nothing to distribute). All elements
/// must share a parent (controller filters this).
/// </summary>
public sealed class SuiDistributeElementsCommand : ISuiCommand
{
public enum Axis
{
Horizontal,
Vertical,
}
private readonly List<string> _ids;
private readonly Axis _axis;
private readonly List<(string Id, float OldX, float OldY)> _saved = new();
public string Description => $"Distribute {_axis}";
public SuiDistributeElementsCommand( IEnumerable<string> elementIds, Axis axis )
{
_ids = elementIds.ToList();
_axis = axis;
}
public void Apply( SuiDocument doc )
{
if ( doc == null || _ids.Count < 3 ) return;
_saved.Clear();
var elements = _ids.Select( doc.GetElement )
.Where( e => e?.Layout != null && e.Layout.Mode == SuiLayoutMode.Absolute )
.ToList();
if ( elements.Count < 3 ) return;
// Save originals.
foreach ( var el in elements )
_saved.Add( (el.Id, el.Layout.X, el.Layout.Y) );
if ( _axis == Axis.Horizontal )
{
elements.Sort( ( a, b ) => a.Layout.X.CompareTo( b.Layout.X ) );
float minStart = elements.First().Layout.X;
float maxEnd = elements.Last().Layout.X + elements.Last().Layout.Width;
float totalWidth = elements.Sum( e => e.Layout.Width );
float gap = (maxEnd - minStart - totalWidth) / (elements.Count - 1);
float cursor = minStart;
foreach ( var el in elements )
{
el.Layout.X = cursor;
cursor += el.Layout.Width + gap;
}
}
else
{
elements.Sort( ( a, b ) => a.Layout.Y.CompareTo( b.Layout.Y ) );
float minStart = elements.First().Layout.Y;
float maxEnd = elements.Last().Layout.Y + elements.Last().Layout.Height;
float totalHeight = elements.Sum( e => e.Layout.Height );
float gap = (maxEnd - minStart - totalHeight) / (elements.Count - 1);
float cursor = minStart;
foreach ( var el in elements )
{
el.Layout.Y = cursor;
cursor += el.Layout.Height + gap;
}
}
}
public void Undo( SuiDocument doc )
{
if ( doc == null ) return;
foreach ( var s in _saved )
{
var el = doc.GetElement( s.Id );
if ( el?.Layout == null ) continue;
el.Layout.X = s.OldX;
el.Layout.Y = s.OldY;
}
_saved.Clear();
}
}