浏览代码

Rebased onto dim.auto

Tig 1 年之前
父节点
当前提交
44cc57b9f8

+ 323 - 0
Terminal.Gui/Drawing/Justification.cs

@@ -0,0 +1,323 @@
+namespace Terminal.Gui;
+
+/// <summary>
+///     Controls how the <see cref="Justifier"/> justifies items within a container. 
+/// </summary>
+public enum Justification
+{
+    /// <summary>
+    ///     The items will be aligned to the left.
+    ///     Set <see cref="Justifier.PutSpaceBetweenItems"/> to <see langword="true"/> to ensure at least one space between
+    ///     each item.
+    /// </summary>
+    /// <example>
+    ///     <c>
+    ///         111 2222 33333
+    ///     </c>
+    /// </example>
+    Left,
+
+    /// <summary>
+    ///     The items will be aligned to the right.
+    ///     Set <see cref="Justifier.PutSpaceBetweenItems"/> to <see langword="true"/> to ensure at least one space between
+    ///     each item.
+    /// </summary>
+    /// <example>
+    ///     <c>
+    ///         111 2222 33333
+    ///     </c>
+    /// </example>
+    Right,
+
+    /// <summary>
+    ///     The group will be centered in the container.
+    ///     If centering is not possible, the group will be left-justified.
+    ///     Set <see cref="Justifier.PutSpaceBetweenItems"/> to <see langword="true"/> to ensure at least one space between
+    ///     each item.
+    /// </summary>
+    /// <example>
+    ///     <c>
+    ///         111 2222 33333
+    ///     </c>
+    /// </example>
+    Centered,
+
+    /// <summary>
+    ///     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 <see cref="Justifier.PutSpaceBetweenItems"/> to <see langword="true"/> to ensure at least one space between
+    ///     each item.
+    /// </summary>
+    /// <example>
+    ///     <c>
+    ///         111    2222     33333
+    ///     </c>
+    /// </example>
+    Justified,
+
+    /// <summary>
+    ///     The first item will be aligned to the left and the remaining will aligned to the right.
+    ///     Set <see cref="Justifier.PutSpaceBetweenItems"/> to <see langword="true"/> to ensure at least one space between
+    ///     each item.
+    /// </summary>
+    /// <example>
+    ///     <c>
+    ///         111        2222 33333
+    ///     </c>
+    /// </example>
+    FirstLeftRestRight,
+
+    /// <summary>
+    ///     The last item will be aligned to the right and the remaining will aligned to the left.
+    ///     Set <see cref="Justifier.PutSpaceBetweenItems"/> to <see langword="true"/> to ensure at least one space between
+    ///     each item.
+    /// </summary>
+    /// <example>
+    ///     <c>
+    ///         111 2222        33333
+    ///     </c>
+    /// </example>
+    LastRightRestLeft
+}
+
+/// <summary>
+///     Justifies items within a container based on the specified <see cref="Justification"/>.
+/// </summary>
+public class Justifier
+{
+    private int _maxSpaceBetweenItems;
+
+    /// <summary>
+    ///     Gets or sets whether <see cref="Justify"/> puts a space is placed between items. Default is <see langword="false"/>. If <see langword="true"/>, a space will be
+    ///     placed between each item, which is useful for
+    ///     justifying text.
+    /// </summary>
+    public bool PutSpaceBetweenItems
+    {
+        get => _maxSpaceBetweenItems == 1;
+        set => _maxSpaceBetweenItems = value ? 1 : 0;
+    }
+
+    /// <summary>
+    ///     Takes a list of items and returns their positions when justified within a container <paramref name="totalSize"/> wide based on the specified
+    ///     <see cref="Justification"/>.
+    /// </summary>
+    /// <param name="sizes">The sizes of the items to justify.</param>
+    /// <param name="justification">The justification style.</param>
+    /// <param name="totalSize">The width of the container.</param>
+    /// <returns>The locations of the items, from left to right.</returns>
+    public int [] Justify (int [] sizes, Justification justification, int totalSize)
+    {
+        if (sizes.Length == 0)
+        {
+            return new int [] { };
+        }
+
+        int totalItemsSize = sizes.Sum ();
+
+        if (totalItemsSize > totalSize)
+        {
+            throw new ArgumentException ("The sum of the sizes is greater than the total size.");
+        }
+
+        var positions = new int [sizes.Length];
+        totalItemsSize = sizes.Sum (); // total size of items
+        int totalGaps = sizes.Length - 1; // total gaps (MinimumSpaceBetweenItems)
+        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 >= totalSize)
+        {
+            spaces = 0;
+        }
+        else if (totalItemsAndSpaces > totalSize)
+        {
+            spaces = totalSize - totalItemsSize;
+        }
+
+        switch (justification)
+        {
+            case Justification.Left:
+                var currentPosition = 0;
+
+                for (var i = 0; i < sizes.Length; i++)
+                {
+                    if (sizes [i] < 0)
+                    {
+                        throw new ArgumentException ("The size of an item cannot be negative.");
+                    }
+
+                    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 Justification.Right:
+                currentPosition = Math.Max (0, totalSize - totalItemsSize - spaces);
+
+                for (var i = 0; i < sizes.Length; i++)
+                {
+                    if (sizes [i] < 0)
+                    {
+                        throw new ArgumentException ("The size of an item cannot be negative.");
+                    }
+
+                    int spaceBefore = spaces-- > 0 ? _maxSpaceBetweenItems : 0;
+
+                    positions [i] = currentPosition;
+                    currentPosition += sizes [i] + spaceBefore;
+                }
+
+                break;
+
+            case Justification.Centered:
+                if (sizes.Length > 1)
+                {
+                    // remaining space to be distributed before first and after the items
+                    int remainingSpace = Math.Max (0, totalSize - totalItemsSize - spaces);
+
+                    for (var i = 0; i < sizes.Length; i++)
+                    {
+                        if (sizes [i] < 0)
+                        {
+                            throw new ArgumentException ("The size of an item cannot be negative.");
+                        }
+
+                        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)
+                {
+                    if (sizes [0] < 0)
+                    {
+                        throw new ArgumentException ("The size of an item cannot be negative.");
+                    }
+
+                    positions [0] = (totalSize - sizes [0]) / 2; // single item is centered
+                }
+
+                break;
+
+            case Justification.Justified:
+                int spaceBetween = sizes.Length > 1 ? (totalSize - totalItemsSize) / (sizes.Length - 1) : 0;
+                int remainder = sizes.Length > 1 ? (totalSize - totalItemsSize) % (sizes.Length - 1) : 0;
+                currentPosition = 0;
+
+                for (var i = 0; i < sizes.Length; i++)
+                {
+                    if (sizes [i] < 0)
+                    {
+                        throw new ArgumentException ("The size of an item cannot be negative.");
+                    }
+
+                    positions [i] = currentPosition;
+                    int extraSpace = i < remainder ? 1 : 0;
+                    currentPosition += sizes [i] + spaceBetween + extraSpace;
+                }
+
+                break;
+
+            // 111 2222        33333
+            case Justification.LastRightRestLeft:
+                if (sizes.Length > 1)
+                {
+                    currentPosition = 0;
+
+                    for (var i = 0; i < sizes.Length; i++)
+                    {
+                        if (sizes [i] < 0)
+                        {
+                            throw new ArgumentException ("The size of an item cannot be negative.");
+                        }
+
+                        if (i < sizes.Length - 1)
+                        {
+                            int spaceBefore = spaces-- > 0 ? _maxSpaceBetweenItems : 0;
+
+                            positions [i] = currentPosition;
+                            currentPosition += sizes [i] + spaceBefore;
+                        }
+                    }
+
+                    positions [sizes.Length - 1] = totalSize - sizes [sizes.Length - 1];
+                }
+                else if (sizes.Length == 1)
+                {
+                    if (sizes [0] < 0)
+                    {
+                        throw new ArgumentException ("The size of an item cannot be negative.");
+                    }
+
+                    positions [0] = totalSize - sizes [0]; // single item is flush right
+                }
+
+                break;
+
+            // 111        2222 33333
+            case Justification.FirstLeftRestRight:
+                if (sizes.Length > 1)
+                {
+                    currentPosition = 0;
+                    positions [0] = currentPosition; // first item is flush left
+
+                    for (int i = sizes.Length - 1; i >= 0; i--)
+                    {
+                        if (sizes [i] < 0)
+                        {
+                            throw new ArgumentException ("The size of an item cannot be negative.");
+                        }
+
+                        if (i == sizes.Length - 1)
+                        {
+                            // start at right
+                            currentPosition = totalSize - 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)
+                {
+                    if (sizes [0] < 0)
+                    {
+                        throw new ArgumentException ("The size of an item cannot be negative.");
+                    }
+
+                    positions [0] = 0; // single item is flush left
+                }
+
+                break;
+
+            default:
+                throw new ArgumentOutOfRangeException (nameof (justification), justification, null);
+        }
+
+        return positions;
+    }
+}

+ 1 - 1
Terminal.Gui/Resources/config.json

@@ -24,7 +24,7 @@
   "Themes": [
     {
       "Default": {
-        "Dialog.DefaultButtonAlignment": "Center",
+        "Dialog.DefaultButtonAlignment": "Centered",
         "FrameView.DefaultBorderStyle": "Single",
         "Window.DefaultBorderStyle": "Single",
         "ColorSchemes": [

+ 85 - 7
Terminal.Gui/View/Layout/PosDim.cs

@@ -1,7 +1,4 @@
 using System.Diagnostics;
-using static System.Net.Mime.MediaTypeNames;
-using static Terminal.Gui.Dialog;
-using static Terminal.Gui.Dim;
 
 namespace Terminal.Gui;
 
@@ -206,6 +203,14 @@ public class Pos
     /// <returns>The <see cref="Pos"/> returned from the function.</returns>
     public static Pos Function (Func<int> function) { return new PosFunc (function); }
 
+    /// <summary>
+    ///      Creates a <see cref="Pos"/> object that justifies a set of views according to the specified justification.
+    /// </summary>
+    /// <param name="views"></param>
+    /// <param name="justification"></param>
+    /// <returns></returns>
+    public static Pos Justify ( Justification justification) { return new PosJustify (justification); }
+
     /// <summary>Serves as the default hash function. </summary>
     /// <returns>A hash code for the current object.</returns>
     public override int GetHashCode () { return Anchor (0).GetHashCode (); }
@@ -349,7 +354,7 @@ public class Pos
     ///     that
     ///     is used.
     /// </returns>
-    internal virtual int Calculate (int superviewDimension, Dim dim, View us, Dimension dimension)
+    internal virtual int Calculate (int superviewDimension, Dim dim, View us, Dim.Dimension dimension)
     {
         return Anchor (superviewDimension);
     }
@@ -388,7 +393,7 @@ public class Pos
             return width - _offset;
         }
 
-        internal override int Calculate (int superviewDimension, Dim dim, View us, Dimension dimension)
+        internal override int Calculate (int superviewDimension, Dim dim, View us, Dim.Dimension dimension)
         {
             int newLocation = Anchor (superviewDimension);
 
@@ -406,7 +411,7 @@ public class Pos
         public override string ToString () { return "Center"; }
         internal override int Anchor (int width) { return width / 2; }
 
-        internal override int Calculate (int superviewDimension, Dim dim, View us, Dimension dimension)
+        internal override int Calculate (int superviewDimension, Dim dim, View us, Dim.Dimension dimension)
         {
             int newDimension = Math.Max (dim.Calculate (0, superviewDimension, us, dimension), 0);
 
@@ -434,7 +439,7 @@ public class Pos
             return la - ra;
         }
 
-        internal override int Calculate (int superviewDimension, Dim dim, View us, Dimension dimension)
+        internal override int Calculate (int superviewDimension, Dim dim, View us, Dim.Dimension dimension)
         {
             int newDimension = dim.Calculate (0, superviewDimension, us, dimension);
             int left = _left.Calculate (superviewDimension, dim, us, dimension);
@@ -458,6 +463,79 @@ public class Pos
         internal override int Anchor (int width) { return (int)(width * _factor); }
     }
 
+
+    /// <summary>
+    /// Enables justification of a set of views. 
+    /// </summary>
+    public class PosJustify : Pos
+    {
+        private readonly Justification _justification;
+
+        /// <summary>
+        /// Enables justification of a set of views.
+        /// </summary>
+        /// <param name="views">The set of views to justify according to <paramref name="justification"/>.</param>
+        /// <param name="justification"></param>
+        public PosJustify (Justification justification)
+        {
+            _justification = justification;
+        }
+
+        public override bool Equals (object other)
+        {
+            return other is PosJustify justify && justify._justification == _justification;
+        }
+
+        public override int GetHashCode () { return _justification.GetHashCode (); }
+
+
+        public override string ToString ()
+        {
+            return $"Justify(alignment={_justification})";
+        }
+
+        internal override int Anchor (int width)
+        {
+            return width;
+        }
+
+        internal override int Calculate (int superviewDimension, Dim dim, View us, Dim.Dimension dimension)
+        {
+            // Find all the views that are being justified - they have the same justification and opposite position as us
+            // Use linq to filter us.Superview.Subviews that match `dimension` and are at our same location in the opposite dimension (e.g. if dimension is Width, filter by Y)
+            // Then, pass the array of views to the Justify method
+            int [] dimensions;
+            int [] positions;
+
+            int ourIndex = 0;
+            if (dimension == Dim.Dimension.Width)
+            {
+                List<int> dimensionsList = new List<int> ();
+                for (int i = 0; i < us.SuperView.Subviews.Count; i++)
+                {
+                    if (us.SuperView.Subviews [i].Frame.Y == us.Frame.Y)
+                    {
+                        dimensionsList.Add (us.SuperView.Subviews [i].Frame.Width);
+
+                        if (us.SuperView.Subviews [i] == us)
+                        {
+                            ourIndex = dimensionsList.Count - 1;
+                        }
+                    }
+                }
+                dimensions = dimensionsList.ToArray ();
+                positions = new Justifier ().Justify (dimensions, _justification, superviewDimension);
+            }
+            else
+            {
+                dimensions = us.SuperView.Subviews.Where (v => v.Frame.X == us.Frame.X).Select(v => v.Frame.Height ).ToArray ();
+                positions = new Justifier ().Justify (dimensions, _justification, superviewDimension);
+            }
+
+            return positions [ourIndex];
+        }
+
+    }
     // Helper class to provide dynamic value by the execution of a function that returns an integer.
     internal class PosFunc (Func<int> n) : Pos
     {

+ 1 - 0
Terminal.Gui/View/Layout/ViewLayout.cs

@@ -36,6 +36,7 @@ public enum LayoutStyle
     Computed
 }
 
+
 public partial class View
 {
     #region Frame

+ 7 - 22
Terminal.Gui/Views/Dialog.cs

@@ -15,21 +15,6 @@ namespace Terminal.Gui;
 /// </remarks>
 public class Dialog : Window
 {
-    /// <summary>Determines the horizontal alignment of the Dialog buttons.</summary>
-    public enum ButtonAlignments
-    {
-        /// <summary>Center-aligns the buttons (the default).</summary>
-        Center = 0,
-
-        /// <summary>Justifies the buttons</summary>
-        Justify,
-
-        /// <summary>Left-aligns the buttons</summary>
-        Left,
-
-        /// <summary>Right-aligns the buttons</summary>
-        Right
-    }
 
     // TODO: Reenable once border/borderframe design is settled
     /// <summary>
@@ -108,7 +93,7 @@ public class Dialog : Window
     }
 
     /// <summary>Determines how the <see cref="Dialog"/> <see cref="Button"/>s are aligned along the bottom of the dialog.</summary>
-    public ButtonAlignments ButtonAlignment { get; set; }
+    public Justification ButtonAlignment { get; set; }
 
     /// <summary>Optional buttons to lay out at the bottom of the dialog.</summary>
     public Button [] Buttons
@@ -128,11 +113,11 @@ public class Dialog : Window
         }
     }
 
-    /// <summary>The default <see cref="ButtonAlignments"/> for <see cref="Dialog"/>.</summary>
+    /// <summary>The default <see cref="Justification"/> for <see cref="Dialog"/>.</summary>
     /// <remarks>This property can be set in a Theme.</remarks>
     [SerializableConfigurationProperty (Scope = typeof (ThemeScope))]
     [JsonConverter (typeof (JsonStringEnumConverter))]
-    public static ButtonAlignments DefaultButtonAlignment { get; set; } = ButtonAlignments.Center;
+    public static Justification DefaultButtonAlignment { get; set; } = Justification.Centered;
 
     /// <summary>
     ///     Adds a <see cref="Button"/> to the <see cref="Dialog"/>, its layout will be controlled by the
@@ -199,7 +184,7 @@ public class Dialog : Window
 
         switch (ButtonAlignment)
         {
-            case ButtonAlignments.Center:
+            case Justification.Centered:
                 // Center Buttons
                 shiftLeft = (Viewport.Width - buttonsWidth - _buttons.Count - 1) / 2 + 1;
 
@@ -222,7 +207,7 @@ public class Dialog : Window
 
                 break;
 
-            case ButtonAlignments.Justify:
+            case Justification.Justified:
                 // Justify Buttons
                 // leftmost and rightmost buttons are hard against edges. The rest are evenly spaced.
 
@@ -257,7 +242,7 @@ public class Dialog : Window
 
                 break;
 
-            case ButtonAlignments.Left:
+            case Justification.Left:
                 // Left Align Buttons
                 Button prevButton = _buttons [0];
                 prevButton.X = 0;
@@ -273,7 +258,7 @@ public class Dialog : Window
 
                 break;
 
-            case ButtonAlignments.Right:
+            case Justification.Right:
                 // Right align buttons
                 shiftLeft = _buttons [_buttons.Count - 1].Frame.Width;
                 _buttons [_buttons.Count - 1].X = Pos.AnchorEnd (shiftLeft);

+ 4 - 1
Terminal.Gui/Views/TextView.cs

@@ -4180,7 +4180,10 @@ public class TextView : View
         }
         else
         {
-            PositionCursor ();
+            if (IsInitialized)
+            {
+                PositionCursor ();
+            }
         }
 
         OnUnwrappedCursorPosition ();

+ 1 - 1
Terminal.Gui/Views/Wizard/Wizard.cs

@@ -85,7 +85,7 @@ public class Wizard : Dialog
     {
         // Using Justify causes the Back and Next buttons to be hard justified against
         // the left and right edge
-        ButtonAlignment = ButtonAlignments.Justify;
+        ButtonAlignment = Justification.Justified;
         BorderStyle = LineStyle.Double;
 
         //// Add a horiz separator

+ 1 - 0
Terminal.sln.DotSettings

@@ -438,5 +438,6 @@
 	<s:String x:Key="/Default/PatternsAndTemplates/Todo/TodoPatterns/=B0C2F2A1AF61DA42BBF270980E3DCEF7/Name/@EntryValue">Concurrency Issue</s:String>
 	<s:String x:Key="/Default/PatternsAndTemplates/Todo/TodoPatterns/=B0C2F2A1AF61DA42BBF270980E3DCEF7/Pattern/@EntryValue">(?&lt;=\W|^)(?&lt;TAG&gt;CONCURRENCY:)(\W|$)(.*)</s:String>
 	<s:String x:Key="/Default/PatternsAndTemplates/Todo/TodoPatterns/=B0C2F2A1AF61DA42BBF270980E3DCEF7/TodoIconStyle/@EntryValue">Warning</s:String>
+	<s:Boolean x:Key="/Default/UserDictionary/Words/=Justifier/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=unsynchronized/@EntryIndexedValue">True</s:Boolean>
 </wpf:ResourceDictionary>

+ 14 - 11
UICatalog/Scenarios/ComputedLayout.cs

@@ -2,6 +2,7 @@
 using System.Collections.Generic;
 using System.Linq;
 using Terminal.Gui;
+using static Terminal.Gui.Dialog;
 
 namespace UICatalog.Scenarios;
 
@@ -332,13 +333,13 @@ public class ComputedLayout : Scenario
         // This is intentionally convoluted to illustrate potential bugs.
         var anchorEndLabel1 = new Label
         {
-            Text = "This Label should be the 2nd to last line (AnchorEnd (2)).",
+            Text = "This Label should be the 3rd to last line (AnchorEnd (3)).",
             TextAlignment = TextAlignment.Centered,
             ColorScheme = Colors.ColorSchemes ["Menu"],
             AutoSize = false,
             Width = Dim.Fill (5),
             X = 5,
-            Y = Pos.AnchorEnd (2)
+            Y = Pos.AnchorEnd (3)
         };
         app.Add (anchorEndLabel1);
 
@@ -347,20 +348,19 @@ public class ComputedLayout : Scenario
         var anchorEndLabel2 = new TextField
         {
             Text =
-                "This TextField should be the 3rd to last line (AnchorEnd (2) - 1).",
+                "This TextField should be the 4th to last line (AnchorEnd (3) - 1).",
             TextAlignment = TextAlignment.Left,
             ColorScheme = Colors.ColorSchemes ["Menu"],
             AutoSize = false,
             Width = Dim.Fill (5),
             X = 5,
-            Y = Pos.AnchorEnd (2) - 1 // Pos.Combine
+            Y = Pos.AnchorEnd (3) - 1 // Pos.Combine
         };
         app.Add (anchorEndLabel2);
 
-        // Show positioning vertically using Pos.AnchorEnd via Pos.Combine
         var leftButton = new Button
         {
-            Text = "Left", Y = Pos.AnchorEnd (0) - 1 // Pos.Combine
+            Text = "Left", Y = Pos.AnchorEnd () - 1
         };
 
         leftButton.Accept += (s, e) =>
@@ -376,7 +376,7 @@ public class ComputedLayout : Scenario
         // show positioning vertically using Pos.AnchorEnd
         var centerButton = new Button
         {
-            Text = "Center", X = Pos.Center (), Y = Pos.AnchorEnd (1) // Pos.AnchorEnd(1)
+            Text = "Center", Y = Pos.AnchorEnd (2) // Pos.AnchorEnd(1)
         };
 
         centerButton.Accept += (s, e) =>
@@ -402,14 +402,17 @@ public class ComputedLayout : Scenario
                                    app.LayoutSubviews ();
                                };
 
-        // Center three buttons with 5 spaces between them
-        leftButton.X = Pos.Left (centerButton) - (Pos.Right (leftButton) - Pos.Left (leftButton)) - 5;
-        rightButton.X = Pos.Right (centerButton) + 5;
-
+        View [] buttons = { leftButton, centerButton, rightButton };
         app.Add (leftButton);
         app.Add (centerButton);
         app.Add (rightButton);
 
+
+        // Center three buttons with 
+        leftButton.X = Pos.Justify (Justification.Centered);
+        centerButton.X = Pos.Justify (Justification.Centered);
+        rightButton.X = Pos.Justify (Justification.Centered);
+
         Application.Run (app);
         app.Dispose ();
     }

+ 2 - 2
UICatalog/Scenarios/Dialogs.cs

@@ -137,7 +137,7 @@ public class Dialogs : Scenario
         {
             X = Pos.Right (label) + 1,
             Y = Pos.Top (label),
-            RadioLabels = new [] { "_Center", "_Justify", "_Left", "_Right" }
+            RadioLabels = new [] { "_Centered", "_Justified", "_Left", "_Right" }
         };
         frame.Add (styleRadioGroup);
 
@@ -265,7 +265,7 @@ public class Dialogs : Scenario
             dialog = new Dialog
             {
                 Title = titleEdit.Text,
-                ButtonAlignment = (Dialog.ButtonAlignments)styleRadioGroup.SelectedItem,
+                ButtonAlignment = (Justification)styleRadioGroup.SelectedItem,
                 Buttons = buttons.ToArray ()
             };
 

+ 3 - 3
UnitTests/Configuration/ThemeScopeTests.cs

@@ -29,12 +29,12 @@ public class ThemeScopeTests
     {
         Reset ();
         Assert.NotEmpty (Themes);
-        Assert.Equal (Dialog.ButtonAlignments.Center, Dialog.DefaultButtonAlignment);
+        Assert.Equal (Justification.Centered, Dialog.DefaultButtonAlignment);
 
-        Themes ["Default"] ["Dialog.DefaultButtonAlignment"].PropertyValue = Dialog.ButtonAlignments.Right;
+        Themes ["Default"] ["Dialog.DefaultButtonAlignment"].PropertyValue = Justification.Right;
 
         ThemeManager.Themes! [ThemeManager.SelectedTheme]!.Apply ();
-        Assert.Equal (Dialog.ButtonAlignments.Right, Dialog.DefaultButtonAlignment);
+        Assert.Equal (Justification.Right, Dialog.DefaultButtonAlignment);
         Reset ();
     }
 

+ 3 - 3
UnitTests/Configuration/ThemeTests.cs

@@ -77,15 +77,15 @@ public class ThemeTests
     public void TestSerialize_RoundTrip ()
     {
         var theme = new ThemeScope ();
-        theme ["Dialog.DefaultButtonAlignment"].PropertyValue = Dialog.ButtonAlignments.Right;
+        theme ["Dialog.DefaultButtonAlignment"].PropertyValue = Justification.Right;
 
         string json = JsonSerializer.Serialize (theme, _jsonOptions);
 
         var deserialized = JsonSerializer.Deserialize<ThemeScope> (json, _jsonOptions);
 
         Assert.Equal (
-                      Dialog.ButtonAlignments.Right,
-                      (Dialog.ButtonAlignments)deserialized ["Dialog.DefaultButtonAlignment"].PropertyValue
+                      Justification.Right,
+                      (Justification)deserialized ["Dialog.DefaultButtonAlignment"].PropertyValue
                      );
         Reset ();
     }

+ 236 - 236
UnitTests/Dialogs/DialogTests.cs

@@ -32,8 +32,8 @@ public class DialogTests
             Title = title,
             Width = width,
             Height = 1,
-            ButtonAlignment = Dialog.ButtonAlignments.Center,
-            Buttons = [new () { Text = btn1Text }]
+            ButtonAlignment = Justification.Centered,
+            Buttons = [new Button { Text = btn1Text }]
         };
 
         // Create with no top or bottom border to simplify testing button layout (no need to account for title etc..)
@@ -57,8 +57,8 @@ public class DialogTests
             Title = title,
             Width = width,
             Height = 1,
-            ButtonAlignment = Dialog.ButtonAlignments.Justify,
-            Buttons = [new () { Text = btn1Text }]
+            ButtonAlignment = Justification.Justified,
+            Buttons = [new Button { Text = btn1Text }]
         };
 
         // Create with no top or bottom border to simplify testing button layout (no need to account for title etc..)
@@ -82,8 +82,8 @@ public class DialogTests
             Title = title,
             Width = width,
             Height = 1,
-            ButtonAlignment = Dialog.ButtonAlignments.Right,
-            Buttons = [new () { Text = btn1Text }]
+            ButtonAlignment = Justification.Right,
+            Buttons = [new Button { Text = btn1Text }]
         };
 
         // Create with no top or bottom border to simplify testing button layout (no need to account for title etc..)
@@ -107,8 +107,8 @@ public class DialogTests
             Title = title,
             Width = width,
             Height = 1,
-            ButtonAlignment = Dialog.ButtonAlignments.Left,
-            Buttons = [new () { Text = btn1Text }]
+            ButtonAlignment = Justification.Left,
+            Buttons = [new Button { Text = btn1Text }]
         };
 
         // Create with no top or bottom border to simplify testing button layout (no need to account for title etc..)
@@ -153,14 +153,14 @@ public class DialogTests
 
         // Default - Center
         (runstate, Dialog dlg) = RunButtonTestDialog (
-                                                      title,
-                                                      width,
-                                                      Dialog.ButtonAlignments.Center,
-                                                      new Button { Text = btn1Text },
-                                                      new Button { Text = btn2Text },
-                                                      new Button { Text = btn3Text },
-                                                      new Button { Text = btn4Text }
-                                                     );
+                                                    title,
+                                                    width,
+                                                    Justification.Centered,
+                                                    new Button { Text = btn1Text },
+                                                    new Button { Text = btn2Text },
+                                                    new Button { Text = btn3Text },
+                                                    new Button { Text = btn4Text }
+                                                   );
         TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output);
         End (runstate);
         dlg.Dispose ();
@@ -170,14 +170,14 @@ public class DialogTests
         Assert.Equal (width, buttonRow.Length);
 
         (runstate, dlg) = RunButtonTestDialog (
-                                               title,
-                                               width,
-                                               Dialog.ButtonAlignments.Justify,
-                                               new Button { Text = btn1Text },
-                                               new Button { Text = btn2Text },
-                                               new Button { Text = btn3Text },
-                                               new Button { Text = btn4Text }
-                                              );
+                                                      title,
+                                                      width,
+                                                      Justification.Justified,
+                                                      new Button { Text = btn1Text },
+                                                      new Button { Text = btn2Text },
+                                                      new Button { Text = btn3Text },
+                                                      new Button { Text = btn4Text }
+                                                     );
         TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output);
         End (runstate);
         dlg.Dispose ();
@@ -187,14 +187,14 @@ public class DialogTests
         Assert.Equal (width, buttonRow.Length);
 
         (runstate, dlg) = RunButtonTestDialog (
-                                               title,
-                                               width,
-                                               Dialog.ButtonAlignments.Right,
-                                               new Button { Text = btn1Text },
-                                               new Button { Text = btn2Text },
-                                               new Button { Text = btn3Text },
-                                               new Button { Text = btn4Text }
-                                              );
+                                                      title,
+                                                      width,
+                                                      Justification.Right,
+                                                      new Button { Text = btn1Text },
+                                                      new Button { Text = btn2Text },
+                                                      new Button { Text = btn3Text },
+                                                      new Button { Text = btn4Text }
+                                                     );
         TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output);
         End (runstate);
         dlg.Dispose ();
@@ -204,14 +204,14 @@ public class DialogTests
         Assert.Equal (width, buttonRow.Length);
 
         (runstate, dlg) = RunButtonTestDialog (
-                                               title,
-                                               width,
-                                               Dialog.ButtonAlignments.Left,
-                                               new Button { Text = btn1Text },
-                                               new Button { Text = btn2Text },
-                                               new Button { Text = btn3Text },
-                                               new Button { Text = btn4Text }
-                                              );
+                                                      title,
+                                                      width,
+                                                      Justification.Left,
+                                                      new Button { Text = btn1Text },
+                                                      new Button { Text = btn2Text },
+                                                      new Button { Text = btn3Text },
+                                                      new Button { Text = btn4Text }
+                                                     );
         TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output);
         End (runstate);
         dlg.Dispose ();
@@ -248,7 +248,7 @@ public class DialogTests
         (runstate, Dialog dlg) = RunButtonTestDialog (
                                                       title,
                                                       width,
-                                                      Dialog.ButtonAlignments.Center,
+                                                      Justification.Centered,
                                                       new Button { Text = btn1Text },
                                                       new Button { Text = btn2Text },
                                                       new Button { Text = btn3Text },
@@ -264,14 +264,14 @@ public class DialogTests
             $"{CM.Glyphs.VLine}{CM.Glyphs.LeftBracket} yes {CM.Glyphs.LeftBracket} no {CM.Glyphs.LeftBracket} maybe {CM.Glyphs.LeftBracket} never {CM.Glyphs.RightBracket}{CM.Glyphs.VLine}";
 
         (runstate, dlg) = RunButtonTestDialog (
-                                               title,
-                                               width,
-                                               Dialog.ButtonAlignments.Justify,
-                                               new Button { Text = btn1Text },
-                                               new Button { Text = btn2Text },
-                                               new Button { Text = btn3Text },
-                                               new Button { Text = btn4Text }
-                                              );
+                                                    title,
+                                                    width,
+                                                    Justification.Justified,
+                                                    new Button { Text = btn1Text },
+                                                    new Button { Text = btn2Text },
+                                                    new Button { Text = btn3Text },
+                                                    new Button { Text = btn4Text }
+                                                   );
         TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output);
         End (runstate);
         dlg.Dispose ();
@@ -280,14 +280,14 @@ public class DialogTests
         buttonRow = $"{CM.Glyphs.VLine}{CM.Glyphs.RightBracket} {btn2} {btn3} {btn4}{CM.Glyphs.VLine}";
 
         (runstate, dlg) = RunButtonTestDialog (
-                                               title,
-                                               width,
-                                               Dialog.ButtonAlignments.Right,
-                                               new Button { Text = btn1Text },
-                                               new Button { Text = btn2Text },
-                                               new Button { Text = btn3Text },
-                                               new Button { Text = btn4Text }
-                                              );
+                                                    title,
+                                                    width,
+                                                    Justification.Right,
+                                                    new Button { Text = btn1Text },
+                                                    new Button { Text = btn2Text },
+                                                    new Button { Text = btn3Text },
+                                                    new Button { Text = btn4Text }
+                                                   );
         TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output);
         End (runstate);
         dlg.Dispose ();
@@ -296,14 +296,14 @@ public class DialogTests
         buttonRow = $"{CM.Glyphs.VLine}{btn1} {btn2} {btn3} {CM.Glyphs.LeftBracket} n{CM.Glyphs.VLine}";
 
         (runstate, dlg) = RunButtonTestDialog (
-                                               title,
-                                               width,
-                                               Dialog.ButtonAlignments.Left,
-                                               new Button { Text = btn1Text },
-                                               new Button { Text = btn2Text },
-                                               new Button { Text = btn3Text },
-                                               new Button { Text = btn4Text }
-                                              );
+                                                    title,
+                                                    width,
+                                                    Justification.Left,
+                                                    new Button { Text = btn1Text },
+                                                    new Button { Text = btn2Text },
+                                                    new Button { Text = btn3Text },
+                                                    new Button { Text = btn4Text }
+                                                   );
         TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output);
         End (runstate);
         dlg.Dispose ();
@@ -337,14 +337,14 @@ public class DialogTests
 
         // Default - Center
         (runstate, Dialog dlg) = RunButtonTestDialog (
-                                                      title,
-                                                      width,
-                                                      Dialog.ButtonAlignments.Center,
-                                                      new Button { Text = btn1Text },
-                                                      new Button { Text = btn2Text },
-                                                      new Button { Text = btn3Text },
-                                                      new Button { Text = btn4Text }
-                                                     );
+                                                    title,
+                                                    width,
+                                                    Justification.Centered,
+                                                    new Button { Text = btn1Text },
+                                                    new Button { Text = btn2Text },
+                                                    new Button { Text = btn3Text },
+                                                    new Button { Text = btn4Text }
+                                                   );
         TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output);
         End (runstate);
         dlg.Dispose ();
@@ -354,14 +354,14 @@ public class DialogTests
         Assert.Equal (width, buttonRow.Length);
 
         (runstate, dlg) = RunButtonTestDialog (
-                                               title,
-                                               width,
-                                               Dialog.ButtonAlignments.Justify,
-                                               new Button { Text = btn1Text },
-                                               new Button { Text = btn2Text },
-                                               new Button { Text = btn3Text },
-                                               new Button { Text = btn4Text }
-                                              );
+                                                    title,
+                                                    width,
+                                                    Justification.Justified,
+                                                    new Button { Text = btn1Text },
+                                                    new Button { Text = btn2Text },
+                                                    new Button { Text = btn3Text },
+                                                    new Button { Text = btn4Text }
+                                                   );
         TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output);
         End (runstate);
         dlg.Dispose ();
@@ -371,14 +371,14 @@ public class DialogTests
         Assert.Equal (width, buttonRow.Length);
 
         (runstate, dlg) = RunButtonTestDialog (
-                                               title,
-                                               width,
-                                               Dialog.ButtonAlignments.Right,
-                                               new Button { Text = btn1Text },
-                                               new Button { Text = btn2Text },
-                                               new Button { Text = btn3Text },
-                                               new Button { Text = btn4Text }
-                                              );
+                                                    title,
+                                                    width,
+                                                    Justification.Right,
+                                                    new Button { Text = btn1Text },
+                                                    new Button { Text = btn2Text },
+                                                    new Button { Text = btn3Text },
+                                                    new Button { Text = btn4Text }
+                                                   );
         TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output);
         End (runstate);
         dlg.Dispose ();
@@ -388,14 +388,14 @@ public class DialogTests
         Assert.Equal (width, buttonRow.Length);
 
         (runstate, dlg) = RunButtonTestDialog (
-                                               title,
-                                               width,
-                                               Dialog.ButtonAlignments.Left,
-                                               new Button { Text = btn1Text },
-                                               new Button { Text = btn2Text },
-                                               new Button { Text = btn3Text },
-                                               new Button { Text = btn4Text }
-                                              );
+                                                    title,
+                                                    width,
+                                                    Justification.Left,
+                                                    new Button { Text = btn1Text },
+                                                    new Button { Text = btn2Text },
+                                                    new Button { Text = btn3Text },
+                                                    new Button { Text = btn4Text }
+                                                   );
         TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output);
         End (runstate);
         dlg.Dispose ();
@@ -431,14 +431,14 @@ public class DialogTests
 
         // Default - Center
         (runstate, Dialog dlg) = RunButtonTestDialog (
-                                                      title,
-                                                      width,
-                                                      Dialog.ButtonAlignments.Center,
-                                                      new Button { Text = btn1Text },
-                                                      new Button { Text = btn2Text },
-                                                      new Button { Text = btn3Text },
-                                                      new Button { Text = btn4Text }
-                                                     );
+                                                    title,
+                                                    width,
+                                                    Justification.Centered,
+                                                    new Button { Text = btn1Text },
+                                                    new Button { Text = btn2Text },
+                                                    new Button { Text = btn3Text },
+                                                    new Button { Text = btn4Text }
+                                                   );
         TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output);
         End (runstate);
         dlg.Dispose ();
@@ -448,14 +448,14 @@ public class DialogTests
         Assert.Equal (width, buttonRow.GetColumns ());
 
         (runstate, dlg) = RunButtonTestDialog (
-                                               title,
-                                               width,
-                                               Dialog.ButtonAlignments.Justify,
-                                               new Button { Text = btn1Text },
-                                               new Button { Text = btn2Text },
-                                               new Button { Text = btn3Text },
-                                               new Button { Text = btn4Text }
-                                              );
+                                                    title,
+                                                    width,
+                                                    Justification.Justified,
+                                                    new Button { Text = btn1Text },
+                                                    new Button { Text = btn2Text },
+                                                    new Button { Text = btn3Text },
+                                                    new Button { Text = btn4Text }
+                                                   );
         TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output);
         End (runstate);
         dlg.Dispose ();
@@ -465,14 +465,14 @@ public class DialogTests
         Assert.Equal (width, buttonRow.GetColumns ());
 
         (runstate, dlg) = RunButtonTestDialog (
-                                               title,
-                                               width,
-                                               Dialog.ButtonAlignments.Right,
-                                               new Button { Text = btn1Text },
-                                               new Button { Text = btn2Text },
-                                               new Button { Text = btn3Text },
-                                               new Button { Text = btn4Text }
-                                              );
+                                                    title,
+                                                    width,
+                                                    Justification.Right,
+                                                    new Button { Text = btn1Text },
+                                                    new Button { Text = btn2Text },
+                                                    new Button { Text = btn3Text },
+                                                    new Button { Text = btn4Text }
+                                                   );
         TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output);
         End (runstate);
         dlg.Dispose ();
@@ -482,14 +482,14 @@ public class DialogTests
         Assert.Equal (width, buttonRow.GetColumns ());
 
         (runstate, dlg) = RunButtonTestDialog (
-                                               title,
-                                               width,
-                                               Dialog.ButtonAlignments.Left,
-                                               new Button { Text = btn1Text },
-                                               new Button { Text = btn2Text },
-                                               new Button { Text = btn3Text },
-                                               new Button { Text = btn4Text }
-                                              );
+                                                    title,
+                                                    width,
+                                                    Justification.Left,
+                                                    new Button { Text = btn1Text },
+                                                    new Button { Text = btn2Text },
+                                                    new Button { Text = btn3Text },
+                                                    new Button { Text = btn4Text }
+                                                   );
         TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output);
         End (runstate);
         dlg.Dispose ();
@@ -514,11 +514,11 @@ public class DialogTests
         d.SetBufferSize (width, 1);
 
         (runstate, Dialog dlg) = RunButtonTestDialog (
-                                                      title,
-                                                      width,
-                                                      Dialog.ButtonAlignments.Center,
-                                                      new Button { Text = btnText }
-                                                     );
+                                                    title,
+                                                    width,
+                                                    Justification.Centered,
+                                                    new Button { Text = btnText }
+                                                   );
 
         // Center
         TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output);
@@ -531,11 +531,11 @@ public class DialogTests
         Assert.Equal (width, buttonRow.Length);
 
         (runstate, dlg) = RunButtonTestDialog (
-                                               title,
-                                               width,
-                                               Dialog.ButtonAlignments.Justify,
-                                               new Button { Text = btnText }
-                                              );
+                                                    title,
+                                                    width,
+                                                    Justification.Justified,
+                                                    new Button { Text = btnText }
+                                                   );
         TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output);
         End (runstate);
         dlg.Dispose ();
@@ -546,11 +546,11 @@ public class DialogTests
         Assert.Equal (width, buttonRow.Length);
 
         (runstate, dlg) = RunButtonTestDialog (
-                                               title,
-                                               width,
-                                               Dialog.ButtonAlignments.Right,
-                                               new Button { Text = btnText }
-                                              );
+                                                    title,
+                                                    width,
+                                                    Justification.Right,
+                                                    new Button { Text = btnText }
+                                                   );
         TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output);
         End (runstate);
         dlg.Dispose ();
@@ -561,11 +561,11 @@ public class DialogTests
         Assert.Equal (width, buttonRow.Length);
 
         (runstate, dlg) = RunButtonTestDialog (
-                                               title,
-                                               width,
-                                               Dialog.ButtonAlignments.Left,
-                                               new Button { Text = btnText }
-                                              );
+                                                    title,
+                                                    width,
+                                                    Justification.Left,
+                                                    new Button { Text = btnText }
+                                                   );
         TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output);
         End (runstate);
         dlg.Dispose ();
@@ -578,11 +578,11 @@ public class DialogTests
         d.SetBufferSize (width, 1);
 
         (runstate, dlg) = RunButtonTestDialog (
-                                               title,
-                                               width,
-                                               Dialog.ButtonAlignments.Center,
-                                               new Button { Text = btnText }
-                                              );
+                                                    title,
+                                                    width,
+                                                    Justification.Centered,
+                                                    new Button { Text = btnText }
+                                                   );
         TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output);
         End (runstate);
         dlg.Dispose ();
@@ -593,11 +593,11 @@ public class DialogTests
         Assert.Equal (width, buttonRow.Length);
 
         (runstate, dlg) = RunButtonTestDialog (
-                                               title,
-                                               width,
-                                               Dialog.ButtonAlignments.Justify,
-                                               new Button { Text = btnText }
-                                              );
+                                                    title,
+                                                    width,
+                                                    Justification.Justified,
+                                                    new Button { Text = btnText }
+                                                   );
         TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output);
         End (runstate);
         dlg.Dispose ();
@@ -608,11 +608,11 @@ public class DialogTests
         Assert.Equal (width, buttonRow.Length);
 
         (runstate, dlg) = RunButtonTestDialog (
-                                               title,
-                                               width,
-                                               Dialog.ButtonAlignments.Right,
-                                               new Button { Text = btnText }
-                                              );
+                                                    title,
+                                                    width,
+                                                    Justification.Right,
+                                                    new Button { Text = btnText }
+                                                   );
         TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output);
         End (runstate);
         dlg.Dispose ();
@@ -623,11 +623,11 @@ public class DialogTests
         Assert.Equal (width, buttonRow.Length);
 
         (runstate, dlg) = RunButtonTestDialog (
-                                               title,
-                                               width,
-                                               Dialog.ButtonAlignments.Left,
-                                               new Button { Text = btnText }
-                                              );
+                                                    title,
+                                                    width,
+                                                    Justification.Left,
+                                                    new Button { Text = btnText }
+                                                   );
         TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output);
         End (runstate);
         dlg.Dispose ();
@@ -657,13 +657,13 @@ public class DialogTests
         d.SetBufferSize (buttonRow.Length, 3);
 
         (runstate, Dialog dlg) = RunButtonTestDialog (
-                                                      title,
-                                                      width,
-                                                      Dialog.ButtonAlignments.Center,
-                                                      new Button { Text = btn1Text },
-                                                      new Button { Text = btn2Text },
-                                                      new Button { Text = btn3Text }
-                                                     );
+                                                    title,
+                                                    width,
+                                                    Justification.Centered,
+                                                    new Button { Text = btn1Text },
+                                                    new Button { Text = btn2Text },
+                                                    new Button { Text = btn3Text }
+                                                   );
         TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output);
         End (runstate);
         dlg.Dispose ();
@@ -673,13 +673,13 @@ public class DialogTests
         Assert.Equal (width, buttonRow.Length);
 
         (runstate, dlg) = RunButtonTestDialog (
-                                               title,
-                                               width,
-                                               Dialog.ButtonAlignments.Justify,
-                                               new Button { Text = btn1Text },
-                                               new Button { Text = btn2Text },
-                                               new Button { Text = btn3Text }
-                                              );
+                                                    title,
+                                                    width,
+                                                    Justification.Justified,
+                                                    new Button { Text = btn1Text },
+                                                    new Button { Text = btn2Text },
+                                                    new Button { Text = btn3Text }
+                                                   );
         TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output);
         End (runstate);
         dlg.Dispose ();
@@ -689,13 +689,13 @@ public class DialogTests
         Assert.Equal (width, buttonRow.Length);
 
         (runstate, dlg) = RunButtonTestDialog (
-                                               title,
-                                               width,
-                                               Dialog.ButtonAlignments.Right,
-                                               new Button { Text = btn1Text },
-                                               new Button { Text = btn2Text },
-                                               new Button { Text = btn3Text }
-                                              );
+                                                    title,
+                                                    width,
+                                                    Justification.Right,
+                                                    new Button { Text = btn1Text },
+                                                    new Button { Text = btn2Text },
+                                                    new Button { Text = btn3Text }
+                                                   );
         TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output);
         End (runstate);
         dlg.Dispose ();
@@ -705,13 +705,13 @@ public class DialogTests
         Assert.Equal (width, buttonRow.Length);
 
         (runstate, dlg) = RunButtonTestDialog (
-                                               title,
-                                               width,
-                                               Dialog.ButtonAlignments.Left,
-                                               new Button { Text = btn1Text },
-                                               new Button { Text = btn2Text },
-                                               new Button { Text = btn3Text }
-                                              );
+                                                    title,
+                                                    width,
+                                                    Justification.Left,
+                                                    new Button { Text = btn1Text },
+                                                    new Button { Text = btn2Text },
+                                                    new Button { Text = btn3Text }
+                                                   );
         TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output);
         End (runstate);
         dlg.Dispose ();
@@ -739,12 +739,12 @@ public class DialogTests
         d.SetBufferSize (buttonRow.Length, 3);
 
         (runstate, Dialog dlg) = RunButtonTestDialog (
-                                                      title,
-                                                      width,
-                                                      Dialog.ButtonAlignments.Center,
-                                                      new Button { Text = btn1Text },
-                                                      new Button { Text = btn2Text }
-                                                     );
+                                                    title,
+                                                    width,
+                                                    Justification.Centered,
+                                                    new Button { Text = btn1Text },
+                                                    new Button { Text = btn2Text }
+                                                   );
         TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output);
         End (runstate);
         dlg.Dispose ();
@@ -754,12 +754,12 @@ public class DialogTests
         Assert.Equal (width, buttonRow.Length);
 
         (runstate, dlg) = RunButtonTestDialog (
-                                               title,
-                                               width,
-                                               Dialog.ButtonAlignments.Justify,
-                                               new Button { Text = btn1Text },
-                                               new Button { Text = btn2Text }
-                                              );
+                                                    title,
+                                                    width,
+                                                    Justification.Justified,
+                                                    new Button { Text = btn1Text },
+                                                    new Button { Text = btn2Text }
+                                                   );
         TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output);
         End (runstate);
         dlg.Dispose ();
@@ -769,12 +769,12 @@ public class DialogTests
         Assert.Equal (width, buttonRow.Length);
 
         (runstate, dlg) = RunButtonTestDialog (
-                                               title,
-                                               width,
-                                               Dialog.ButtonAlignments.Right,
-                                               new Button { Text = btn1Text },
-                                               new Button { Text = btn2Text }
-                                              );
+                                                    title,
+                                                    width,
+                                                    Justification.Right,
+                                                    new Button { Text = btn1Text },
+                                                    new Button { Text = btn2Text }
+                                                   );
         TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output);
         End (runstate);
         dlg.Dispose ();
@@ -784,12 +784,12 @@ public class DialogTests
         Assert.Equal (width, buttonRow.Length);
 
         (runstate, dlg) = RunButtonTestDialog (
-                                               title,
-                                               width,
-                                               Dialog.ButtonAlignments.Left,
-                                               new Button { Text = btn1Text },
-                                               new Button { Text = btn2Text }
-                                              );
+                                                    title,
+                                                    width,
+                                                    Justification.Left,
+                                                    new Button { Text = btn1Text },
+                                                    new Button { Text = btn2Text }
+                                                   );
         TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output);
         End (runstate);
         dlg.Dispose ();
@@ -821,9 +821,9 @@ public class DialogTests
         Button button1, button2;
 
         // Default (Center)
-        button1 = new () { Text = btn1Text };
-        button2 = new () { Text = btn2Text };
-        (runstate, dlg) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, button1, button2);
+        button1 = new Button { Text = btn1Text };
+        button2 = new Button { Text = btn2Text };
+        (runstate, dlg) = RunButtonTestDialog (title, width, Justification.Centered, button1, button2);
         button1.Visible = false;
         RunIteration (ref runstate, ref firstIteration);
         buttonRow = $@"{CM.Glyphs.VLine}         {btn2} {CM.Glyphs.VLine}";
@@ -833,9 +833,9 @@ public class DialogTests
 
         // Justify
         Assert.Equal (width, buttonRow.Length);
-        button1 = new () { Text = btn1Text };
-        button2 = new () { Text = btn2Text };
-        (runstate, dlg) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Justify, button1, button2);
+        button1 = new Button { Text = btn1Text };
+        button2 = new Button { Text = btn2Text };
+        (runstate, dlg) = RunButtonTestDialog (title, width, Justification.Justified, button1, button2);
         button1.Visible = false;
         RunIteration (ref runstate, ref firstIteration);
         buttonRow = $@"{CM.Glyphs.VLine}          {btn2}{CM.Glyphs.VLine}";
@@ -845,9 +845,9 @@ public class DialogTests
 
         // Right
         Assert.Equal (width, buttonRow.Length);
-        button1 = new () { Text = btn1Text };
-        button2 = new () { Text = btn2Text };
-        (runstate, dlg) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, button1, button2);
+        button1 = new Button { Text = btn1Text };
+        button2 = new Button { Text = btn2Text };
+        (runstate, dlg) = RunButtonTestDialog (title, width, Justification.Right, button1, button2);
         button1.Visible = false;
         RunIteration (ref runstate, ref firstIteration);
         TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output);
@@ -856,9 +856,9 @@ public class DialogTests
 
         // Left
         Assert.Equal (width, buttonRow.Length);
-        button1 = new () { Text = btn1Text };
-        button2 = new () { Text = btn2Text };
-        (runstate, dlg) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Left, button1, button2);
+        button1 = new Button { Text = btn1Text };
+        button2 = new Button { Text = btn2Text };
+        (runstate, dlg) = RunButtonTestDialog (title, width, Justification.Left, button1, button2);
         button1.Visible = false;
         RunIteration (ref runstate, ref firstIteration);
         buttonRow = $@"{CM.Glyphs.VLine}        {btn2}  {CM.Glyphs.VLine}";
@@ -1281,7 +1281,7 @@ public class DialogTests
         (runstate, Dialog _) = RunButtonTestDialog (
                                                     title,
                                                     width,
-                                                    Dialog.ButtonAlignments.Center,
+                                                    Justification.Centered,
                                                     new Button { Text = btnText }
                                                    );
         TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output);
@@ -1334,7 +1334,7 @@ public class DialogTests
         int width = buttonRow.Length;
         d.SetBufferSize (buttonRow.Length, 3);
 
-        (runstate, Dialog dlg) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, null);
+        (runstate, Dialog _) = RunButtonTestDialog (title, width, Justification.Centered, null);
         TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output);
 
         End (runstate);
@@ -1344,7 +1344,7 @@ public class DialogTests
     private (RunState, Dialog) RunButtonTestDialog (
         string title,
         int width,
-        Dialog.ButtonAlignments align,
+        Justification align,
         params Button [] btns
     )
     {

+ 414 - 0
UnitTests/Drawing/JustifierTests.cs

@@ -0,0 +1,414 @@
+using System.Text;
+using Xunit.Abstractions;
+
+namespace Terminal.Gui.DrawingTests;
+
+public class JustifierTests (ITestOutputHelper output)
+{
+    private readonly ITestOutputHelper _output = output;
+
+    public static IEnumerable<object []> JustificationEnumValues ()
+    {
+        foreach (object number in Enum.GetValues (typeof (Justification)))
+        {
+            yield return new [] { number };
+        }
+    }
+
+    [Theory]
+    [MemberData (nameof (JustificationEnumValues))]
+    public void NoItems_Works (Justification justification)
+    {
+        int [] sizes = { };
+        int [] positions = new Justifier ().Justify (sizes, justification, 100);
+        Assert.Equal (new int [] { }, positions);
+    }
+
+    [Theory]
+    [MemberData (nameof (JustificationEnumValues))]
+    public void Items_Width_Cannot_Exceed_TotalSize (Justification justification)
+    {
+        int [] sizes = { 1000, 2000, 3000 };
+        Assert.Throws<ArgumentException> (() => new Justifier ().Justify (sizes, justification, 100));
+    }
+
+    [Theory]
+    [MemberData (nameof (JustificationEnumValues))]
+    public void Negative_Widths_Not_Allowed (Justification justification)
+    {
+        Assert.Throws<ArgumentException> (() => new Justifier ().Justify (new [] { -10, 20, 30 }, justification, 100));
+        Assert.Throws<ArgumentException> (() => new Justifier ().Justify (new [] { 10, -20, 30 }, justification, 100));
+        Assert.Throws<ArgumentException> (() => new Justifier ().Justify (new [] { 10, 20, -30 }, justification, 100));
+    }
+
+    [Theory]
+    [InlineData (Justification.Left, new [] { 0 }, 1, new [] { 0 })]
+    [InlineData (Justification.Left, new [] { 0, 0 }, 1, new [] { 0, 1 })]
+    [InlineData (Justification.Left, new [] { 0, 0, 0 }, 1, new [] { 0, 1, 1 })]
+    [InlineData (Justification.Left, new [] { 1 }, 1, new [] { 0 })]
+    [InlineData (Justification.Left, new [] { 1 }, 2, new [] { 0 })]
+    [InlineData (Justification.Left, new [] { 1 }, 3, new [] { 0 })]
+    [InlineData (Justification.Left, new [] { 1, 1 }, 2, new [] { 0, 1 })]
+    [InlineData (Justification.Left, new [] { 1, 1 }, 3, new [] { 0, 2 })]
+    [InlineData (Justification.Left, new [] { 1, 1 }, 4, new [] { 0, 2 })]
+    [InlineData (Justification.Left, new [] { 1, 1, 1 }, 3, new [] { 0, 1, 2 })]
+    [InlineData (Justification.Left, new [] { 1, 2, 3 }, 6, new [] { 0, 1, 3 })]
+    [InlineData (Justification.Left, new [] { 1, 2, 3 }, 7, new [] { 0, 2, 4 })]
+    [InlineData (Justification.Left, new [] { 1, 2, 3 }, 10, new [] { 0, 2, 5 })]
+    [InlineData (Justification.Left, new [] { 1, 2, 3 }, 11, new [] { 0, 2, 5 })]
+    [InlineData (Justification.Left, new [] { 1, 2, 3 }, 12, new [] { 0, 2, 5 })]
+    [InlineData (Justification.Left, new [] { 1, 2, 3 }, 13, new [] { 0, 2, 5 })]
+    [InlineData (Justification.Left, new [] { 1, 2, 3, 4 }, 10, new [] { 0, 1, 3, 6 })]
+    [InlineData (Justification.Left, new [] { 1, 2, 3, 4 }, 11, new [] { 0, 2, 4, 7 })]
+    [InlineData (Justification.Left, new [] { 33, 33, 33 }, 100, new [] { 0, 34, 67 })]
+    [InlineData (Justification.Left, new [] { 10 }, 101, new [] { 0 })]
+    [InlineData (Justification.Left, new [] { 10, 20 }, 101, new [] { 0, 11 })]
+    [InlineData (Justification.Left, new [] { 10, 20, 30 }, 100, new [] { 0, 11, 32 })]
+    [InlineData (Justification.Left, new [] { 10, 20, 30 }, 101, new [] { 0, 11, 32 })]
+    [InlineData (Justification.Left, new [] { 10, 20, 30, 40 }, 101, new [] { 0, 11, 31, 61 })]
+    [InlineData (Justification.Left, new [] { 10, 20, 30, 40, 50 }, 151, new [] { 0, 11, 31, 61, 101 })]
+    [InlineData (Justification.Right, new [] { 0 }, 1, new [] { 1 })]
+    [InlineData (Justification.Right, new [] { 0, 0 }, 1, new [] { 0, 1 })]
+    [InlineData (Justification.Right, new [] { 0, 0, 0 }, 1, new [] { 0, 1, 1 })]
+    [InlineData (Justification.Right, new [] { 1, 2, 3 }, 6, new [] { 0, 1, 3 })]
+    [InlineData (Justification.Right, new [] { 1, 2, 3 }, 7, new [] { 0, 2, 4 })]
+    [InlineData (Justification.Right, new [] { 1, 2, 3 }, 10, new [] { 2, 4, 7 })]
+    [InlineData (Justification.Right, new [] { 1, 2, 3 }, 11, new [] { 3, 5, 8 })]
+    [InlineData (Justification.Right, new [] { 1, 2, 3 }, 12, new [] { 4, 6, 9 })]
+    [InlineData (Justification.Right, new [] { 1, 2, 3 }, 13, new [] { 5, 7, 10 })]
+    [InlineData (Justification.Right, new [] { 1, 2, 3, 4 }, 10, new [] { 0, 1, 3, 6 })]
+    [InlineData (Justification.Right, new [] { 1, 2, 3, 4 }, 11, new [] { 0, 2, 4, 7 })]
+    [InlineData (Justification.Right, new [] { 10, 20, 30 }, 100, new [] { 38, 49, 70 })]
+    [InlineData (Justification.Right, new [] { 33, 33, 33 }, 100, new [] { 0, 34, 67 })]
+    [InlineData (Justification.Right, new [] { 10 }, 101, new [] { 91 })]
+    [InlineData (Justification.Right, new [] { 10, 20 }, 101, new [] { 70, 81 })]
+    [InlineData (Justification.Right, new [] { 10, 20, 30 }, 101, new [] { 39, 50, 71 })]
+    [InlineData (Justification.Right, new [] { 10, 20, 30, 40 }, 101, new [] { 0, 11, 31, 61 })]
+    [InlineData (Justification.Right, new [] { 10, 20, 30, 40, 50 }, 151, new [] { 0, 11, 31, 61, 101 })]
+    [InlineData (Justification.Centered, new [] { 0 }, 1, new [] { 0 })]
+    [InlineData (Justification.Centered, new [] { 0, 0 }, 1, new [] { 0, 1 })]
+    [InlineData (Justification.Centered, new [] { 0, 0, 0 }, 1, new [] { 0, 1, 1 })]
+    [InlineData (Justification.Centered, new [] { 1 }, 1, new [] { 0 })]
+    [InlineData (Justification.Centered, new [] { 1 }, 2, new [] { 0 })]
+    [InlineData (Justification.Centered, new [] { 1 }, 3, new [] { 1 })]
+    [InlineData (Justification.Centered, new [] { 1, 1 }, 2, new [] { 0, 1 })]
+    [InlineData (Justification.Centered, new [] { 1, 1 }, 3, new [] { 0, 2 })]
+    [InlineData (Justification.Centered, new [] { 1, 1 }, 4, new [] { 0, 2 })]
+    [InlineData (Justification.Centered, new [] { 1, 1, 1 }, 3, new [] { 0, 1, 2 })]
+    [InlineData (Justification.Centered, new [] { 1, 2, 3 }, 6, new [] { 0, 1, 3 })]
+    [InlineData (Justification.Centered, new [] { 1, 2, 3 }, 7, new [] { 0, 2, 4 })]
+    [InlineData (Justification.Centered, new [] { 1, 2, 3 }, 10, new [] { 1, 3, 6 })]
+    [InlineData (Justification.Centered, new [] { 1, 2, 3 }, 11, new [] { 1, 3, 6 })]
+    [InlineData (Justification.Centered, new [] { 1, 2, 3, 4 }, 10, new [] { 0, 1, 3, 6 })]
+    [InlineData (Justification.Centered, new [] { 1, 2, 3, 4 }, 11, new [] { 0, 2, 4, 7 })]
+    [InlineData (Justification.Centered, new [] { 3, 3, 3 }, 9, new [] { 0, 3, 6 })]
+    [InlineData (Justification.Centered, new [] { 3, 3, 3 }, 10, new [] { 0, 4, 7 })]
+    [InlineData (Justification.Centered, new [] { 3, 3, 3 }, 11, new [] { 0, 4, 8 })]
+    [InlineData (Justification.Centered, new [] { 3, 3, 3 }, 12, new [] { 0, 4, 8 })]
+    [InlineData (Justification.Centered, new [] { 3, 3, 3 }, 13, new [] { 1, 5, 9 })]
+    [InlineData (Justification.Centered, new [] { 33, 33, 33 }, 100, new [] { 0, 34, 67 })]
+    [InlineData (Justification.Centered, new [] { 33, 33, 33 }, 101, new [] { 0, 34, 68 })]
+    [InlineData (Justification.Centered, new [] { 33, 33, 33 }, 102, new [] { 0, 34, 68 })]
+    [InlineData (Justification.Centered, new [] { 33, 33, 33 }, 103, new [] { 1, 35, 69 })]
+    [InlineData (Justification.Centered, new [] { 33, 33, 33 }, 104, new [] { 1, 35, 69 })]
+    [InlineData (Justification.Centered, new [] { 10 }, 101, new [] { 45 })]
+    [InlineData (Justification.Centered, new [] { 10, 20 }, 101, new [] { 35, 46 })]
+    [InlineData (Justification.Centered, new [] { 10, 20, 30 }, 100, new [] { 19, 30, 51 })]
+    [InlineData (Justification.Centered, new [] { 10, 20, 30 }, 101, new [] { 19, 30, 51 })]
+    [InlineData (Justification.Centered, new [] { 10, 20, 30, 40 }, 100, new [] { 0, 10, 30, 60 })]
+    [InlineData (Justification.Centered, new [] { 10, 20, 30, 40 }, 101, new [] { 0, 11, 31, 61 })]
+    [InlineData (Justification.Centered, new [] { 10, 20, 30, 40, 50 }, 151, new [] { 0, 11, 31, 61, 101 })]
+    [InlineData (Justification.Centered, new [] { 3, 4, 5, 6 }, 25, new [] { 2, 6, 11, 17 })]
+    [InlineData (Justification.Justified, new [] { 10, 20, 30, 40, 50 }, 151, new [] { 0, 11, 31, 61, 101 })]
+    [InlineData (Justification.Justified, new [] { 10, 20, 30, 40 }, 101, new [] { 0, 11, 31, 61 })]
+    [InlineData (Justification.Justified, new [] { 10, 20, 30 }, 100, new [] { 0, 30, 70 })]
+    [InlineData (Justification.Justified, new [] { 10, 20, 30 }, 101, new [] { 0, 31, 71 })]
+    [InlineData (Justification.Justified, new [] { 33, 33, 33 }, 100, new [] { 0, 34, 67 })]
+    [InlineData (Justification.Justified, new [] { 11, 17, 23 }, 100, new [] { 0, 36, 77 })]
+    [InlineData (Justification.Justified, new [] { 1, 2, 3 }, 11, new [] { 0, 4, 8 })]
+    [InlineData (Justification.Justified, new [] { 10, 20 }, 101, new [] { 0, 81 })]
+    [InlineData (Justification.Justified, new [] { 10 }, 101, new [] { 0 })]
+    [InlineData (Justification.Justified, new [] { 3, 3, 3 }, 21, new [] { 0, 9, 18 })]
+    [InlineData (Justification.Justified, new [] { 3, 4, 5 }, 21, new [] { 0, 8, 16 })]
+    [InlineData (Justification.Justified, new [] { 3, 4, 5, 6 }, 18, new [] { 0, 3, 7, 12 })]
+    [InlineData (Justification.Justified, new [] { 3, 4, 5, 6 }, 19, new [] { 0, 4, 8, 13 })]
+    [InlineData (Justification.Justified, new [] { 3, 4, 5, 6 }, 20, new [] { 0, 4, 9, 14 })]
+    [InlineData (Justification.Justified, new [] { 3, 4, 5, 6 }, 21, new [] { 0, 4, 9, 15 })]
+    [InlineData (Justification.Justified, new [] { 6, 5, 4, 3 }, 22, new [] { 0, 8, 14, 19 })]
+    [InlineData (Justification.Justified, new [] { 6, 5, 4, 3 }, 23, new [] { 0, 8, 15, 20 })]
+    [InlineData (Justification.Justified, new [] { 6, 5, 4, 3 }, 24, new [] { 0, 8, 15, 21 })]
+    [InlineData (Justification.Justified, new [] { 6, 5, 4, 3 }, 25, new [] { 0, 9, 16, 22 })]
+    [InlineData (Justification.Justified, new [] { 6, 5, 4, 3 }, 26, new [] { 0, 9, 17, 23 })]
+    [InlineData (Justification.Justified, new [] { 6, 5, 4, 3 }, 31, new [] { 0, 11, 20, 28 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 0 }, 1, new [] { 1 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 0, 0 }, 1, new [] { 0, 1 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 0, 0, 0 }, 1, new [] { 0, 1, 1 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 1 }, 1, new [] { 0 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 1 }, 2, new [] { 1 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 1 }, 3, new [] { 2 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 1, 1 }, 2, new [] { 0, 1 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 1, 1 }, 3, new [] { 0, 2 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 1, 1 }, 4, new [] { 0, 3 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 1, 1, 1 }, 3, new [] { 0, 1, 2 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 1, 2, 3 }, 6, new [] { 0, 1, 3 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 1, 2, 3 }, 7, new [] { 0, 2, 4 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 1, 2, 3 }, 8, new [] { 0, 2, 5 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 1, 2, 3 }, 9, new [] { 0, 2, 6 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 1, 2, 3 }, 10, new [] { 0, 2, 7 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 1, 2, 3 }, 11, new [] { 0, 2, 8 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 1, 2, 3, 4 }, 10, new [] { 0, 1, 3, 6 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 1, 2, 3, 4 }, 11, new [] { 0, 2, 4, 7 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 3, 3, 3 }, 21, new [] { 0, 4, 18 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 3, 4, 5 }, 21, new [] { 0, 4, 16 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 33, 33, 33 }, 100, new [] { 0, 34, 67 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 10 }, 101, new [] { 91 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 10, 20 }, 101, new [] { 0, 81 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 10, 20, 30 }, 100, new [] { 0, 11, 70 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 10, 20, 30 }, 101, new [] { 0, 11, 71 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 10, 20, 30, 40 }, 101, new [] { 0, 11, 31, 61 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 10, 20, 30, 40, 50 }, 151, new [] { 0, 11, 31, 61, 101 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 0 }, 1, new [] { 0 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 0, 0 }, 1, new [] { 0, 1 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 0, 0, 0 }, 1, new [] { 0, 0, 1 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1 }, 1, new [] { 0 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1 }, 2, new [] { 0 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1 }, 3, new [] { 0 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1, 1 }, 2, new [] { 0, 1 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1, 1 }, 3, new [] { 0, 2 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1, 1 }, 4, new [] { 0, 3 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1, 1, 1 }, 3, new [] { 0, 1, 2 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1, 2, 3 }, 6, new [] { 0, 1, 3 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1, 2, 3 }, 7, new [] { 0, 1, 4 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1, 2, 3 }, 8, new [] { 0, 2, 5 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1, 2, 3 }, 9, new [] { 0, 3, 6 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1, 2, 3 }, 10, new [] { 0, 4, 7 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1, 2, 3 }, 11, new [] { 0, 5, 8 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1, 2, 3, 4 }, 10, new [] { 0, 1, 3, 6 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1, 2, 3, 4 }, 11, new [] { 0, 1, 3, 7 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1, 2, 3, 4 }, 12, new [] { 0, 1, 4, 8 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 3, 3, 3 }, 21, new [] { 0, 14, 18 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 3, 4, 5 }, 21, new [] { 0, 11, 16 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 33, 33, 33 }, 100, new [] { 0, 33, 67 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 10 }, 101, new [] { 0 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 10, 20 }, 101, new [] { 0, 81 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 10, 20, 30 }, 100, new [] { 0, 49, 70 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 10, 20, 30 }, 101, new [] { 0, 50, 71 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 10, 20, 30, 40 }, 101, new [] { 0, 10, 30, 61 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 10, 20, 30, 40, 50 }, 151, new [] { 0, 10, 30, 60, 101 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 3, 3, 3 }, 21, new [] { 0, 14, 18 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 3, 4, 5 }, 21, new [] { 0, 11, 16 })]
+    public void TestJustifications_PutSpaceBetweenItems (Justification justification, int [] sizes, int totalSize, int [] expected)
+    {
+        int [] positions = new Justifier { PutSpaceBetweenItems = true }.Justify (sizes, justification, totalSize);
+        AssertJustification (justification, sizes, totalSize, positions, expected);
+    }
+
+    [Theory]
+    [InlineData (Justification.Left, new [] { 0 }, 1, new [] { 0 })]
+    [InlineData (Justification.Left, new [] { 0, 0 }, 1, new [] { 0, 0 })]
+    [InlineData (Justification.Left, new [] { 0, 0, 0 }, 1, new [] { 0, 0, 0 })]
+    [InlineData (Justification.Left, new [] { 1, 2, 3 }, 6, new [] { 0, 1, 3 })]
+    [InlineData (Justification.Left, new [] { 1, 2, 3 }, 7, new [] { 0, 1, 3 })]
+    [InlineData (Justification.Left, new [] { 1, 2, 3 }, 10, new [] { 0, 1, 3 })]
+    [InlineData (Justification.Left, new [] { 1, 2, 3 }, 11, new [] { 0, 1, 3 })]
+    [InlineData (Justification.Left, new [] { 1, 2, 3 }, 12, new [] { 0, 1, 3 })]
+    [InlineData (Justification.Left, new [] { 1, 2, 3 }, 13, new [] { 0, 1, 3 })]
+    [InlineData (Justification.Left, new [] { 1, 2, 3, 4 }, 10, new [] { 0, 1, 3, 6 })]
+    [InlineData (Justification.Left, new [] { 1, 2, 3, 4 }, 11, new [] { 0, 1, 3, 6 })]
+    [InlineData (Justification.Left, new [] { 10, 20, 30 }, 100, new [] { 0, 10, 30 })]
+    [InlineData (Justification.Left, new [] { 33, 33, 33 }, 100, new [] { 0, 33, 66 })]
+    [InlineData (Justification.Left, new [] { 10 }, 101, new [] { 0 })]
+    [InlineData (Justification.Left, new [] { 10, 20 }, 101, new [] { 0, 10 })]
+    [InlineData (Justification.Left, new [] { 10, 20, 30 }, 101, new [] { 0, 10, 30 })]
+    [InlineData (Justification.Left, new [] { 10, 20, 30, 40 }, 101, new [] { 0, 10, 30, 60 })]
+    [InlineData (Justification.Left, new [] { 10, 20, 30, 40, 50 }, 151, new [] { 0, 10, 30, 60, 100 })]
+    [InlineData (Justification.Right, new [] { 0 }, 1, new [] { 1 })]
+    [InlineData (Justification.Right, new [] { 0, 0 }, 1, new [] { 1, 1 })]
+    [InlineData (Justification.Right, new [] { 0, 0, 0 }, 1, new [] { 1, 1, 1 })]
+    [InlineData (Justification.Right, new [] { 1, 2, 3 }, 6, new [] { 0, 1, 3 })]
+    [InlineData (Justification.Right, new [] { 1, 2, 3 }, 7, new [] { 1, 2, 4 })]
+    [InlineData (Justification.Right, new [] { 1, 2, 3 }, 10, new [] { 4, 5, 7 })]
+    [InlineData (Justification.Right, new [] { 1, 2, 3 }, 11, new [] { 5, 6, 8 })]
+    [InlineData (Justification.Right, new [] { 1, 2, 3 }, 12, new [] { 6, 7, 9 })]
+    [InlineData (Justification.Right, new [] { 1, 2, 3 }, 13, new [] { 7, 8, 10 })]
+    [InlineData (Justification.Right, new [] { 1, 2, 3, 4 }, 10, new [] { 0, 1, 3, 6 })]
+    [InlineData (Justification.Right, new [] { 1, 2, 3, 4 }, 11, new [] { 1, 2, 4, 7 })]
+    [InlineData (Justification.Right, new [] { 10, 20, 30 }, 100, new [] { 40, 50, 70 })]
+    [InlineData (Justification.Right, new [] { 33, 33, 33 }, 100, new [] { 1, 34, 67 })]
+    [InlineData (Justification.Right, new [] { 10 }, 101, new [] { 91 })]
+    [InlineData (Justification.Right, new [] { 10, 20 }, 101, new [] { 71, 81 })]
+    [InlineData (Justification.Right, new [] { 10, 20, 30 }, 101, new [] { 41, 51, 71 })]
+    [InlineData (Justification.Right, new [] { 10, 20, 30, 40 }, 101, new [] { 1, 11, 31, 61 })]
+    [InlineData (Justification.Right, new [] { 10, 20, 30, 40, 50 }, 151, new [] { 1, 11, 31, 61, 101 })]
+    [InlineData (Justification.Centered, new [] { 1 }, 1, new [] { 0 })]
+    [InlineData (Justification.Centered, new [] { 1 }, 2, new [] { 0 })]
+    [InlineData (Justification.Centered, new [] { 1 }, 3, new [] { 1 })]
+    [InlineData (Justification.Centered, new [] { 1, 1 }, 2, new [] { 0, 1 })]
+    [InlineData (Justification.Centered, new [] { 1, 1 }, 3, new [] { 0, 1 })]
+    [InlineData (Justification.Centered, new [] { 1, 1 }, 4, new [] { 1, 2 })]
+    [InlineData (Justification.Centered, new [] { 1, 1, 1 }, 3, new [] { 0, 1, 2 })]
+    [InlineData (Justification.Centered, new [] { 1, 2, 3 }, 6, new [] { 0, 1, 3 })]
+    [InlineData (Justification.Centered, new [] { 1, 2, 3 }, 7, new [] { 0, 1, 3 })]
+    [InlineData (Justification.Centered, new [] { 1, 2, 3 }, 10, new [] { 2, 3, 5 })]
+    [InlineData (Justification.Centered, new [] { 1, 2, 3 }, 11, new [] { 2, 3, 5 })]
+    [InlineData (Justification.Centered, new [] { 1, 2, 3, 4 }, 10, new [] { 0, 1, 3, 6 })]
+    [InlineData (Justification.Centered, new [] { 1, 2, 3, 4 }, 11, new [] { 0, 1, 3, 6 })]
+    [InlineData (Justification.Centered, new [] { 3, 3, 3 }, 9, new [] { 0, 3, 6 })]
+    [InlineData (Justification.Centered, new [] { 3, 3, 3 }, 10, new [] { 0, 3, 6 })]
+    [InlineData (Justification.Centered, new [] { 3, 3, 3 }, 11, new [] { 1, 4, 7 })]
+    [InlineData (Justification.Centered, new [] { 3, 3, 3 }, 12, new [] { 1, 4, 7 })]
+    [InlineData (Justification.Centered, new [] { 3, 3, 3 }, 13, new [] { 2, 5, 8 })]
+    [InlineData (Justification.Centered, new [] { 33, 33, 33 }, 100, new [] { 0, 33, 66 })]
+    [InlineData (Justification.Centered, new [] { 33, 33, 33 }, 101, new [] { 1, 34, 67 })]
+    [InlineData (Justification.Centered, new [] { 33, 33, 33 }, 102, new [] { 1, 34, 67 })]
+    [InlineData (Justification.Centered, new [] { 33, 33, 33 }, 103, new [] { 2, 35, 68 })]
+    [InlineData (Justification.Centered, new [] { 33, 33, 33 }, 104, new [] { 2, 35, 68 })]
+    [InlineData (Justification.Centered, new [] { 3, 4, 5, 6 }, 25, new [] { 3, 6, 10, 15 })]
+    [InlineData (Justification.Justified, new [] { 10, 20, 30, 40, 50 }, 151, new [] { 0, 11, 31, 61, 101 })]
+    [InlineData (Justification.Justified, new [] { 10, 20, 30, 40 }, 101, new [] { 0, 11, 31, 61 })]
+    [InlineData (Justification.Justified, new [] { 10, 20, 30 }, 100, new [] { 0, 30, 70 })]
+    [InlineData (Justification.Justified, new [] { 10, 20, 30 }, 101, new [] { 0, 31, 71 })]
+    [InlineData (Justification.Justified, new [] { 33, 33, 33 }, 100, new [] { 0, 34, 67 })]
+    [InlineData (Justification.Justified, new [] { 11, 17, 23 }, 100, new [] { 0, 36, 77 })]
+    [InlineData (Justification.Justified, new [] { 1, 2, 3 }, 11, new [] { 0, 4, 8 })]
+    [InlineData (Justification.Justified, new [] { 10, 20 }, 101, new [] { 0, 81 })]
+    [InlineData (Justification.Justified, new [] { 10 }, 101, new [] { 0 })]
+    [InlineData (Justification.Justified, new [] { 3, 3, 3 }, 21, new [] { 0, 9, 18 })]
+    [InlineData (Justification.Justified, new [] { 3, 4, 5 }, 21, new [] { 0, 8, 16 })]
+    [InlineData (Justification.Justified, new [] { 3, 4, 5, 6 }, 18, new [] { 0, 3, 7, 12 })]
+    [InlineData (Justification.Justified, new [] { 3, 4, 5, 6 }, 19, new [] { 0, 4, 8, 13 })]
+    [InlineData (Justification.Justified, new [] { 3, 4, 5, 6 }, 20, new [] { 0, 4, 9, 14 })]
+    [InlineData (Justification.Justified, new [] { 3, 4, 5, 6 }, 21, new [] { 0, 4, 9, 15 })]
+    [InlineData (Justification.Justified, new [] { 6, 5, 4, 3 }, 22, new [] { 0, 8, 14, 19 })]
+    [InlineData (Justification.Justified, new [] { 6, 5, 4, 3 }, 23, new [] { 0, 8, 15, 20 })]
+    [InlineData (Justification.Justified, new [] { 6, 5, 4, 3 }, 24, new [] { 0, 8, 15, 21 })]
+    [InlineData (Justification.Justified, new [] { 6, 5, 4, 3 }, 25, new [] { 0, 9, 16, 22 })]
+    [InlineData (Justification.Justified, new [] { 6, 5, 4, 3 }, 26, new [] { 0, 9, 17, 23 })]
+    [InlineData (Justification.Justified, new [] { 6, 5, 4, 3 }, 31, new [] { 0, 11, 20, 28 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 0 }, 1, new [] { 1 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 0, 0 }, 1, new [] { 0, 1 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 0, 0, 0 }, 1, new [] { 0, 0, 1 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 1 }, 1, new [] { 0 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 1 }, 2, new [] { 1 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 1 }, 3, new [] { 2 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 1, 1 }, 2, new [] { 0, 1 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 1, 1 }, 3, new [] { 0, 2 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 1, 1 }, 4, new [] { 0, 3 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 1, 1, 1 }, 3, new [] { 0, 1, 2 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 1, 2, 3 }, 6, new [] { 0, 1, 3 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 1, 2, 3 }, 7, new [] { 0, 1, 4 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 1, 2, 3 }, 8, new [] { 0, 1, 5 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 1, 2, 3 }, 9, new [] { 0, 1, 6 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 1, 2, 3 }, 10, new [] { 0, 1, 7 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 1, 2, 3 }, 11, new [] { 0, 1, 8 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 1, 2, 3, 4 }, 10, new [] { 0, 1, 3, 6 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 1, 2, 3, 4 }, 11, new [] { 0, 1, 3, 7 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 1, 2, 3, 4 }, 12, new [] { 0, 1, 3, 8 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 3, 3, 3 }, 21, new [] { 0, 3, 18 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 3, 4, 5 }, 21, new [] { 0, 3, 16 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 33, 33, 33 }, 100, new [] { 0, 33, 67 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 10 }, 101, new [] { 91 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 10, 20 }, 101, new [] { 0, 81 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 10, 20, 30 }, 100, new [] { 0, 10, 70 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 10, 20, 30 }, 101, new [] { 0, 10, 71 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 10, 20, 30, 40 }, 101, new [] { 0, 10, 30, 61 })]
+    [InlineData (Justification.LastRightRestLeft, new [] { 10, 20, 30, 40, 50 }, 151, new [] { 0, 10, 30, 60, 101 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 0 }, 1, new [] { 0 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 0, 0 }, 1, new [] { 0, 1 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 0, 0, 0 }, 1, new [] { 0, 1, 1 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1 }, 1, new [] { 0 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1 }, 2, new [] { 0 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1 }, 3, new [] { 0 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1, 1 }, 2, new [] { 0, 1 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1, 1 }, 3, new [] { 0, 2 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1, 1 }, 4, new [] { 0, 3 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1, 1, 1 }, 3, new [] { 0, 1, 2 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1, 2, 3 }, 6, new [] { 0, 1, 3 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1, 2, 3 }, 7, new [] { 0, 2, 4 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1, 2, 3 }, 8, new [] { 0, 3, 5 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1, 2, 3 }, 9, new [] { 0, 4, 6 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1, 2, 3 }, 10, new [] { 0, 5, 7 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1, 2, 3 }, 11, new [] { 0, 6, 8 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1, 2, 3, 4 }, 10, new [] { 0, 1, 3, 6 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1, 2, 3, 4 }, 11, new [] { 0, 2, 4, 7 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 1, 2, 3, 4 }, 12, new [] { 0, 3, 5, 8 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 3, 3, 3 }, 21, new [] { 0, 15, 18 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 3, 4, 5 }, 21, new [] { 0, 12, 16 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 33, 33, 33 }, 100, new [] { 0, 34, 67 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 10 }, 101, new [] { 0 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 10, 20 }, 101, new [] { 0, 81 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 10, 20, 30 }, 100, new [] { 0, 50, 70 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 10, 20, 30 }, 101, new [] { 0, 51, 71 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 10, 20, 30, 40 }, 101, new [] { 0, 11, 31, 61 })]
+    [InlineData (Justification.FirstLeftRestRight, new [] { 10, 20, 30, 40, 50 }, 151, new [] { 0, 11, 31, 61, 101 })]
+    public void TestJustifications_NoSpaceBetweenItems (Justification justification, int [] sizes, int totalSize, int [] expected)
+    {
+        int [] positions = new Justifier { PutSpaceBetweenItems = false }.Justify (sizes, justification, totalSize);
+        AssertJustification (justification, sizes, totalSize, positions, expected);
+    }
+
+    public void AssertJustification (Justification justification, int [] sizes, int totalSize, int [] positions, int [] expected)
+    {
+        try
+        {
+            _output.WriteLine ($"Testing: {RenderJustification (justification, sizes, totalSize, expected)}");
+        }
+        catch (Exception e)
+        {
+            _output.WriteLine ($"Exception rendering expected: {e.Message}");
+            _output.WriteLine ($"Actual: {RenderJustification (justification, sizes, totalSize, positions)}");
+        }
+
+        if (!expected.SequenceEqual (positions))
+        {
+            _output.WriteLine ($"Expected: {RenderJustification (justification, sizes, totalSize, expected)}");
+            _output.WriteLine ($"Actual: {RenderJustification (justification, sizes, totalSize, positions)}");
+            Assert.Fail (" Expected and actual do not match");
+        }
+    }
+
+    public string RenderJustification (Justification justification, int [] sizes, int totalSize, int [] positions)
+    {
+        var output = new StringBuilder ();
+        output.AppendLine ($"Justification: {justification}, Positions: {string.Join (", ", positions)}, TotalSize: {totalSize}");
+
+        for (var i = 0; i <= totalSize / 10; i++)
+        {
+            output.Append (i.ToString ().PadRight (9) + " ");
+        }
+
+        output.AppendLine ();
+
+        for (var i = 0; i < totalSize; i++)
+        {
+            output.Append (i % 10);
+        }
+
+        output.AppendLine ();
+
+        var items = new char [totalSize];
+
+        for (var position = 0; position < positions.Length; position++)
+        {
+            // try
+            {
+                for (var j = 0; j < sizes [position] && positions [position] + j < totalSize; j++)
+                {
+                    items [positions [position] + j] = (position + 1).ToString () [0];
+                }
+            }
+
+            //catch (Exception e)
+            //{
+            //    output.AppendLine ($"{e.Message} - position = {position}, positions[{position}]: {positions [position]}, sizes[{position}]: {sizes [position]}, totalSize: {totalSize}");
+            //    output.Append (new string (items).Replace ('\0', ' '));
+
+            //    Assert.Fail (e.Message + output.ToString ());
+            //}
+        }
+
+        output.Append (new string (items).Replace ('\0', ' '));
+
+        return output.ToString ();
+    }
+}