using System.ComponentModel;
using Microsoft.CodeAnalysis;
using static Terminal.Gui.Pos;
namespace Terminal.Gui;
///
/// Controls how the aligns items within a container.
///
public enum Alignment
{
///
/// The items will be aligned to the left.
/// Set to to ensure at least one space between
/// each item.
///
///
///
/// If the container is smaller than the total size of the items, the right items will be clipped (their locations
/// will be greater than the container size).
///
///
///
///
/// 111 2222 33333
///
///
Left,
///
/// The items will be aligned to the top.
/// Set to to ensure at least one line between
/// each item.
///
Top,
///
/// The items will be aligned to the right.
/// Set to to ensure at least one space between
/// each item.
///
///
///
/// If the container is smaller than the total size of the items, the left items will be clipped (their locations
/// will be negative).
///
///
///
///
/// 111 2222 33333
///
///
Right,
///
/// The items will be aligned to the bottom.
/// Set to to ensure at least one line between
/// each item.
///
Bottom,
///
/// The group will be centered in the container.
/// If centering is not possible, the group will be left-aligned.
/// Set to to ensure at least one space between
/// each item.
///
///
///
/// Extra space will be distributed between the items, biased towards the left.
///
///
///
///
/// 111 2222 33333
///
///
Centered,
///
/// The items will be justified. Space will be added between the items such that the first item
/// is at the start and the right side of the last item against the end.
/// Set to to ensure at least one space between
/// each item.
///
///
///
/// Extra space will be distributed between the items, biased towards the left.
///
///
///
///
/// 111 2222 33333
///
///
Justified,
///
/// The first item will be aligned to the left and the remaining will aligned to the right.
/// Set to to ensure at least one space between
/// each item.
///
///
///
/// If the container is smaller than the total size of the items, the right items will be clipped (their locations
/// will be greater than the container size).
///
///
///
///
/// 111 2222 33333
///
///
FirstLeftRestRight,
///
/// The first item will be aligned to the top and the remaining will aligned to the bottom.
/// Set to to ensure at least one line between
/// each item.
///
FirstTopRestBottom,
///
/// The last item will be aligned to the right and the remaining will aligned to the left.
/// Set to to ensure at least one space between
/// each item.
///
///
///
/// If the container is smaller than the total size of the items, the left items will be clipped (their locations
/// will be negative).
///
///
///
///
/// 111 2222 33333
///
///
LastRightRestLeft,
///
/// The last item will be aligned to the bottom and the remaining will aligned to the left.
/// Set to to ensure at least one line between
/// each item.
///
LastBottomRestTop
}
///
/// Aligns items within a container based on the specified .
///
public class Aligner : INotifyPropertyChanged
{
private Alignment _alignment;
///
/// Gets or sets how the aligns items within a container.
///
public Alignment Alignment
{
get => _alignment;
set
{
_alignment = value;
PropertyChanged?.Invoke (this, new (nameof (Alignment)));
}
}
private int _containerSize;
///
/// The size of the container.
///
public int ContainerSize
{
get => _containerSize;
set
{
_containerSize = value;
PropertyChanged?.Invoke (this, new (nameof (ContainerSize)));
}
}
private bool _putSpaceBetweenItems;
///
/// Gets or sets whether puts a space is placed between items. Default is
/// . If , a space will be
/// placed between each item, which is useful for justifying text.
///
///
///
/// If the total size of the items is greater than the container size, the space between items will be ignored
/// starting
/// from the right.
///
///
public bool PutSpaceBetweenItems
{
get => _putSpaceBetweenItems;
set
{
_putSpaceBetweenItems = value;
PropertyChanged?.Invoke (this, new (nameof (PutSpaceBetweenItems)));
}
}
///
public event PropertyChangedEventHandler PropertyChanged;
///
/// Takes a list of items and returns their positions when aligned within a container
/// wide based on the specified
/// .
///
/// The sizes of the items to align.
/// The locations of the items, from left to right.
public int [] Align (int [] sizes) { return Align (Alignment, PutSpaceBetweenItems, ContainerSize, sizes); }
///
/// Takes a list of items and returns their positions when aligned within a container
/// wide based on the specified
/// .
///
/// The sizes of the items to align.
/// Specifies how the items will be aligned.
/// Puts a space is placed between items.
/// The size of the container.
/// The locations of the items, from left to right.
public static int [] Align (Alignment alignment, bool putSpaceBetweenItems, int containerSize, int [] sizes)
{
if (sizes.Length == 0)
{
return new int [] { };
}
int maxSpaceBetweenItems = putSpaceBetweenItems ? 1 : 0;
var positions = new int [sizes.Length]; // positions of the items. the return value.
int totalItemsSize = sizes.Sum ();
int totalGaps = sizes.Length - 1; // total gaps between items
int totalItemsAndSpaces = totalItemsSize + totalGaps * maxSpaceBetweenItems; // total size of items and spaces if we had enough room
int spaces = totalGaps * maxSpaceBetweenItems; // We'll decrement this below to place one space between each item until we run out
if (totalItemsSize >= containerSize)
{
spaces = 0;
}
else if (totalItemsAndSpaces > containerSize)
{
spaces = containerSize - totalItemsSize;
}
switch (alignment)
{
case Alignment.Left:
case Alignment.Top:
var currentPosition = 0;
for (var i = 0; i < sizes.Length; i++)
{
CheckSizeCannotBeNegative (i, sizes);
if (i == 0)
{
positions [0] = 0; // first item position
continue;
}
int spaceBefore = spaces-- > 0 ? maxSpaceBetweenItems : 0;
// subsequent items are placed one space after the previous item
positions [i] = positions [i - 1] + sizes [i - 1] + spaceBefore;
}
break;
case Alignment.Right:
case Alignment.Bottom:
currentPosition = containerSize - totalItemsSize - spaces;
for (var i = 0; i < sizes.Length; i++)
{
CheckSizeCannotBeNegative (i, sizes);
int spaceBefore = spaces-- > 0 ? maxSpaceBetweenItems : 0;
positions [i] = currentPosition;
currentPosition += sizes [i] + spaceBefore;
}
break;
case Alignment.Centered:
if (sizes.Length > 1)
{
// remaining space to be distributed before first and after the items
int remainingSpace = Math.Max (0, containerSize - totalItemsSize - spaces);
for (var i = 0; i < sizes.Length; i++)
{
CheckSizeCannotBeNegative (i, sizes);
if (i == 0)
{
positions [i] = remainingSpace / 2; // first item position
continue;
}
int spaceBefore = spaces-- > 0 ? maxSpaceBetweenItems : 0;
// subsequent items are placed one space after the previous item
positions [i] = positions [i - 1] + sizes [i - 1] + spaceBefore;
}
}
else if (sizes.Length == 1)
{
CheckSizeCannotBeNegative (0, sizes);
positions [0] = (containerSize - sizes [0]) / 2; // single item is centered
}
break;
case Alignment.Justified:
int spaceBetween = sizes.Length > 1 ? (containerSize - totalItemsSize) / (sizes.Length - 1) : 0;
int remainder = sizes.Length > 1 ? (containerSize - totalItemsSize) % (sizes.Length - 1) : 0;
currentPosition = 0;
for (var i = 0; i < sizes.Length; i++)
{
CheckSizeCannotBeNegative (i, sizes);
positions [i] = currentPosition;
int extraSpace = i < remainder ? 1 : 0;
currentPosition += sizes [i] + spaceBetween + extraSpace;
}
break;
// 111 2222 33333
case Alignment.LastRightRestLeft:
case Alignment.LastBottomRestTop:
if (sizes.Length > 1)
{
if (totalItemsSize > containerSize)
{
currentPosition = containerSize - totalItemsSize - spaces;
}
else
{
currentPosition = 0;
}
for (var i = 0; i < sizes.Length; i++)
{
CheckSizeCannotBeNegative (i, sizes);
if (i < sizes.Length - 1)
{
int spaceBefore = spaces-- > 0 ? maxSpaceBetweenItems : 0;
positions [i] = currentPosition;
currentPosition += sizes [i] + spaceBefore;
}
}
positions [sizes.Length - 1] = containerSize - sizes [^1];
}
else if (sizes.Length == 1)
{
CheckSizeCannotBeNegative (0, sizes);
positions [0] = containerSize - sizes [0]; // single item is flush right
}
break;
// 111 2222 33333
case Alignment.FirstLeftRestRight:
case Alignment.FirstTopRestBottom:
if (sizes.Length > 1)
{
currentPosition = 0;
positions [0] = currentPosition; // first item is flush left
for (int i = sizes.Length - 1; i >= 0; i--)
{
CheckSizeCannotBeNegative (i, sizes);
if (i == sizes.Length - 1)
{
// start at right
currentPosition = Math.Max (totalItemsSize, containerSize) - sizes [i];
positions [i] = currentPosition;
}
if (i < sizes.Length - 1 && i > 0)
{
int spaceBefore = spaces-- > 0 ? maxSpaceBetweenItems : 0;
positions [i] = currentPosition - sizes [i] - spaceBefore;
currentPosition = positions [i];
}
}
}
else if (sizes.Length == 1)
{
CheckSizeCannotBeNegative (0, sizes);
positions [0] = 0; // single item is flush left
}
break;
default:
throw new ArgumentOutOfRangeException (nameof (alignment), alignment, null);
}
return positions;
}
private static void CheckSizeCannotBeNegative (int i, int [] sizes)
{
if (sizes [i] < 0)
{
throw new ArgumentException ("The size of an item cannot be negative.");
}
}
}