diff --git a/Terminal.Gui/Drawing/Aligner.cs b/Terminal.Gui/Drawing/Aligner.cs new file mode 100644 index 000000000..75c981455 --- /dev/null +++ b/Terminal.Gui/Drawing/Aligner.cs @@ -0,0 +1,338 @@ +using System.ComponentModel; + +namespace Terminal.Gui; + +/// +/// Aligns items within a container based on the specified . Both horizontal and vertical +/// alignments are supported. +/// +public class Aligner : INotifyPropertyChanged +{ + private Alignment _alignment; + + /// + /// Gets or sets how the aligns items within a container. + /// + /// + /// + /// provides additional options for aligning items in a container. + /// + /// + public Alignment Alignment + { + get => _alignment; + set + { + _alignment = value; + PropertyChanged?.Invoke (this, new (nameof (Alignment))); + } + } + + private AlignmentModes _alignmentMode = AlignmentModes.StartToEnd | AlignmentModes.AddSpaceBetweenItems; + + /// + /// Gets or sets the modes controlling . + /// + public AlignmentModes AlignmentModes + { + get => _alignmentMode; + set + { + _alignmentMode = value; + PropertyChanged?.Invoke (this, new (nameof (AlignmentModes))); + } + } + + private int _containerSize; + + /// + /// The size of the container. + /// + public int ContainerSize + { + get => _containerSize; + set + { + _containerSize = value; + PropertyChanged?.Invoke (this, new (nameof (ContainerSize))); + } + } + + /// + public event PropertyChangedEventHandler PropertyChanged; + + /// + /// Takes a list of item sizes and returns a list of the positions of those items when aligned within + /// + /// using the and settings. + /// + /// The sizes of the items to align. + /// The locations of the items, from left/top to right/bottom. + public int [] Align (int [] sizes) { return Align (Alignment, AlignmentModes, ContainerSize, sizes); } + + /// + /// Takes a list of item sizes and returns a list of the positions of those items when aligned within + /// + /// using specified parameters. + /// + /// Specifies how the items will be aligned. + /// + /// The size of the container. + /// The sizes of the items to align. + /// The positions of the items, from left/top to right/bottom. + public static int [] Align (in Alignment alignment, in AlignmentModes alignmentMode, in int containerSize, in int [] sizes) + { + if (alignmentMode.HasFlag (AlignmentModes.EndToStart)) + { + throw new NotImplementedException ("EndToStart is not implemented."); + } + + if (sizes.Length == 0) + { + return []; + } + + int maxSpaceBetweenItems = alignmentMode.HasFlag (AlignmentModes.AddSpaceBetweenItems) ? 1 : 0; + int totalItemsSize = sizes.Sum (); + int totalGaps = sizes.Length - 1; // total gaps between items + int totalItemsAndSpaces = totalItemsSize + totalGaps * maxSpaceBetweenItems; // total size of items and spacesToGive if we had enough room + int spacesToGive = totalGaps * maxSpaceBetweenItems; // We'll decrement this below to place one space between each item until we run out + + if (totalItemsSize >= containerSize) + { + spacesToGive = 0; + } + else if (totalItemsAndSpaces > containerSize) + { + spacesToGive = containerSize - totalItemsSize; + } + + switch (alignment) + { + case Alignment.Start: + switch (alignmentMode & ~AlignmentModes.AddSpaceBetweenItems) + { + case AlignmentModes.StartToEnd: + return Start (in sizes, maxSpaceBetweenItems, spacesToGive); + + case AlignmentModes.StartToEnd | AlignmentModes.IgnoreFirstOrLast: + return IgnoreLast (in sizes, containerSize, totalItemsSize, maxSpaceBetweenItems, spacesToGive); + } + + break; + + case Alignment.End: + switch (alignmentMode & ~AlignmentModes.AddSpaceBetweenItems) + { + case AlignmentModes.StartToEnd: + return End (in sizes, containerSize, totalItemsSize, maxSpaceBetweenItems, spacesToGive); + + case AlignmentModes.StartToEnd | AlignmentModes.IgnoreFirstOrLast: + return IgnoreFirst (in sizes, containerSize, totalItemsSize, maxSpaceBetweenItems, spacesToGive); + } + + break; + + case Alignment.Center: + return Center (in sizes, containerSize, totalItemsSize, maxSpaceBetweenItems, spacesToGive); + + case Alignment.Fill: + return Fill (in sizes, containerSize, totalItemsSize); + + default: + throw new ArgumentOutOfRangeException (nameof (alignment), alignment, null); + } + + return []; + } + + internal static int [] Start (ref readonly int [] sizes, int maxSpaceBetweenItems, int spacesToGive) + { + var positions = new int [sizes.Length]; // positions of the items. the return value. + + for (var i = 0; i < sizes.Length; i++) + { + CheckSizeCannotBeNegative (i, in sizes); + + if (i == 0) + { + positions [0] = 0; // first item position + + continue; + } + + int spaceBefore = spacesToGive-- > 0 ? maxSpaceBetweenItems : 0; + + // subsequent items are placed one space after the previous item + positions [i] = positions [i - 1] + sizes [i - 1] + spaceBefore; + } + + return positions; + } + + internal static int [] IgnoreFirst ( + ref readonly int [] sizes, + int containerSize, + int totalItemsSize, + int maxSpaceBetweenItems, + int spacesToGive + ) + { + var positions = new int [sizes.Length]; // positions of the items. the return value. + + if (sizes.Length > 1) + { + var currentPosition = 0; + positions [0] = currentPosition; // first item is flush left + + for (int i = sizes.Length - 1; i >= 0; i--) + { + CheckSizeCannotBeNegative (i, in sizes); + + if (i == sizes.Length - 1) + { + // start at right + currentPosition = Math.Max (totalItemsSize, containerSize) - sizes [i]; + positions [i] = currentPosition; + } + + if (i < sizes.Length - 1 && i > 0) + { + int spaceBefore = spacesToGive-- > 0 ? maxSpaceBetweenItems : 0; + + positions [i] = currentPosition - sizes [i] - spaceBefore; + currentPosition = positions [i]; + } + } + } + else if (sizes.Length == 1) + { + CheckSizeCannotBeNegative (0, in sizes); + positions [0] = 0; // single item is flush left + } + + return positions; + } + + internal static int [] IgnoreLast ( + ref readonly int [] sizes, + int containerSize, + int totalItemsSize, + int maxSpaceBetweenItems, + int spacesToGive + ) + { + var positions = new int [sizes.Length]; // positions of the items. the return value. + + if (sizes.Length > 1) + { + var currentPosition = 0; + if (totalItemsSize > containerSize) + { + currentPosition = containerSize - totalItemsSize - spacesToGive; + } + + for (var i = 0; i < sizes.Length; i++) + { + CheckSizeCannotBeNegative (i, in sizes); + + if (i < sizes.Length - 1) + { + int spaceBefore = spacesToGive-- > 0 ? maxSpaceBetweenItems : 0; + + positions [i] = currentPosition; + currentPosition += sizes [i] + spaceBefore; + } + } + + positions [sizes.Length - 1] = containerSize - sizes [^1]; + } + else if (sizes.Length == 1) + { + CheckSizeCannotBeNegative (0, in sizes); + + positions [0] = containerSize - sizes [0]; // single item is flush right + } + + return positions; + } + + internal static int [] Fill (ref readonly int [] sizes, int containerSize, int totalItemsSize) + { + var positions = new int [sizes.Length]; // positions of the items. the return value. + + int spaceBetween = sizes.Length > 1 ? (containerSize - totalItemsSize) / (sizes.Length - 1) : 0; + int remainder = sizes.Length > 1 ? (containerSize - totalItemsSize) % (sizes.Length - 1) : 0; + var currentPosition = 0; + + for (var i = 0; i < sizes.Length; i++) + { + CheckSizeCannotBeNegative (i, in sizes); + positions [i] = currentPosition; + int extraSpace = i < remainder ? 1 : 0; + currentPosition += sizes [i] + spaceBetween + extraSpace; + } + + return positions; + } + + internal static int [] Center (ref readonly int [] sizes, int containerSize, int totalItemsSize, int maxSpaceBetweenItems, int spacesToGive) + { + var positions = new int [sizes.Length]; // positions of the items. the return value. + + if (sizes.Length > 1) + { + // remaining space to be distributed before first and after the items + int remainingSpace = containerSize - totalItemsSize - spacesToGive; + + for (var i = 0; i < sizes.Length; i++) + { + CheckSizeCannotBeNegative (i, in sizes); + + if (i == 0) + { + positions [i] = remainingSpace / 2; // first item position + + continue; + } + + int spaceBefore = spacesToGive-- > 0 ? maxSpaceBetweenItems : 0; + + // subsequent items are placed one space after the previous item + positions [i] = positions [i - 1] + sizes [i - 1] + spaceBefore; + } + } + else if (sizes.Length == 1) + { + CheckSizeCannotBeNegative (0, in sizes); + positions [0] = (containerSize - sizes [0]) / 2; // single item is centered + } + + return positions; + } + + internal static int [] End (ref readonly int [] sizes, int containerSize, int totalItemsSize, int maxSpaceBetweenItems, int spacesToGive) + { + var positions = new int [sizes.Length]; // positions of the items. the return value. + int currentPosition = containerSize - totalItemsSize - spacesToGive; + + for (var i = 0; i < sizes.Length; i++) + { + CheckSizeCannotBeNegative (i, in sizes); + int spaceBefore = spacesToGive-- > 0 ? maxSpaceBetweenItems : 0; + + positions [i] = currentPosition; + currentPosition += sizes [i] + spaceBefore; + } + + return positions; + } + + private static void CheckSizeCannotBeNegative (int i, ref readonly int [] sizes) + { + if (sizes [i] < 0) + { + throw new ArgumentException ("The size of an item cannot be negative."); + } + } +} diff --git a/Terminal.Gui/Drawing/Alignment.cs b/Terminal.Gui/Drawing/Alignment.cs new file mode 100644 index 000000000..5a32cb491 --- /dev/null +++ b/Terminal.Gui/Drawing/Alignment.cs @@ -0,0 +1,78 @@ +namespace Terminal.Gui; + +/// +/// Determines the position of items when arranged in a container. +/// +public enum Alignment +{ + /// + /// The items will be aligned to the start (left or top) of the container. + /// + /// + /// + /// If the container is smaller than the total size of the items, the end items will be clipped (their locations + /// will be greater than the container size). + /// + /// + /// The enumeration provides additional options for aligning items in a container. + /// + /// + /// + /// + /// |111 2222 33333 | + /// + /// + Start = 0, + + /// + /// The items will be aligned to the end (right or bottom) of the container. + /// + /// + /// + /// If the container is smaller than the total size of the items, the start items will be clipped (their locations + /// will be negative). + /// + /// + /// The enumeration provides additional options for aligning items in a container. + /// + /// + /// + /// + /// | 111 2222 33333| + /// + /// + End, + + /// + /// Center in the available space. + /// + /// + /// + /// If centering is not possible, the group will be left-aligned. + /// + /// + /// Extra space will be distributed between the items, biased towards the left. + /// + /// + /// + /// + /// | 111 2222 33333 | + /// + /// + Center, + + /// + /// The items will fill the available space. + /// + /// + /// + /// Extra space will be distributed between the items, biased towards the end. + /// + /// + /// + /// + /// |111 2222 33333| + /// + /// + Fill, +} \ No newline at end of file diff --git a/Terminal.Gui/Drawing/AlignmentModes.cs b/Terminal.Gui/Drawing/AlignmentModes.cs new file mode 100644 index 000000000..abd88a397 --- /dev/null +++ b/Terminal.Gui/Drawing/AlignmentModes.cs @@ -0,0 +1,49 @@ +namespace Terminal.Gui; + +/// +/// Determines alignment modes for . +/// +[Flags] +public enum AlignmentModes +{ + /// + /// The items will be arranged from start (left/top) to end (right/bottom). + /// + StartToEnd = 0, + + /// + /// The items will be arranged from end (right/bottom) to start (left/top). + /// + /// + /// Not implemented. + /// + EndToStart = 1, + + /// + /// At least one space will be added between items. Useful for justifying text where at least one space is needed. + /// + /// + /// + /// If the total size of the items is greater than the container size, the space between items will be ignored + /// starting from the end. + /// + /// + AddSpaceBetweenItems = 2, + + /// + /// When aligning via or , the item opposite to the alignment (the first or last item) will be ignored. + /// + /// + /// + /// If the container is smaller than the total size of the items, the end items will be clipped (their locations + /// will be greater than the container size). + /// + /// + /// + /// + /// Start: |111 2222 33333| + /// End: |111 2222 33333| + /// + /// + IgnoreFirstOrLast = 4, +} \ No newline at end of file diff --git a/Terminal.Gui/Drawing/Justification.cs b/Terminal.Gui/Drawing/Justification.cs deleted file mode 100644 index f1fba56a8..000000000 --- a/Terminal.Gui/Drawing/Justification.cs +++ /dev/null @@ -1,333 +0,0 @@ -namespace Terminal.Gui; - -/// -/// Controls how the justifies items within a container. -/// -public enum Justification -{ - /// - /// The items will be aligned to the left. - /// Set to to ensure at least one space between - /// each item. - /// - /// - /// - /// 111 2222 33333 - /// - /// - Left, - - /// - /// The items will be aligned to the right. - /// Set to to ensure at least one space between - /// each item. - /// - /// - /// - /// 111 2222 33333 - /// - /// - Right, - - /// - /// The group will be centered in the container. - /// If centering is not possible, the group will be left-justified. - /// Set to to ensure at least one space between - /// each item. - /// - /// - /// - /// 111 2222 33333 - /// - /// - Centered, - - /// - /// The items will be justified. Space will be added between the items such that the first item - /// is at the start and the right side of the last item against the end. - /// Set to to ensure at least one space between - /// each item. - /// - /// - /// - /// 111 2222 33333 - /// - /// - Justified, - - /// - /// The first item will be aligned to the left and the remaining will aligned to the right. - /// Set to to ensure at least one space between - /// each item. - /// - /// - /// - /// 111 2222 33333 - /// - /// - FirstLeftRestRight, - - /// - /// The last item will be aligned to the right and the remaining will aligned to the left. - /// Set to to ensure at least one space between - /// each item. - /// - /// - /// - /// 111 2222 33333 - /// - /// - LastRightRestLeft -} - -/// -/// Justifies items within a container based on the specified . -/// -public class Justifier -{ - /// - /// Gets or sets how the justifies items within a container. - /// - public Justification Justification { get; set; } - - /// - /// The size of the container. - /// - public int ContainerSize { get; set; } - - /// - /// Gets or sets whether puts a space is placed between items. Default is . If , a space will be - /// placed between each item, which is useful for justifying text. - /// - public bool PutSpaceBetweenItems { get; set; } - - /// - /// Takes a list of items and returns their positions when justified within a container wide based on the specified - /// . - /// - /// The sizes of the items to justify. - /// The locations of the items, from left to right. - public int [] Justify (int [] sizes) - { - return Justify (Justification, PutSpaceBetweenItems, ContainerSize, sizes); - } - - /// - /// Takes a list of items and returns their positions when justified within a container wide based on the specified - /// . - /// - /// The sizes of the items to justify. - /// The justification style. - /// - /// The size of the container. - /// The locations of the items, from left to right. - public static int [] Justify (Justification justification, bool putSpaceBetweenItems, int containerSize, int [] sizes) - { - if (sizes.Length == 0) - { - return new int [] { }; - } - - int maxSpaceBetweenItems = putSpaceBetweenItems ? 1 : 0; - - var positions = new int [sizes.Length]; // positions of the items. the return value. - int totalItemsSize = sizes.Sum (); - int totalGaps = sizes.Length - 1; // total gaps between items - int totalItemsAndSpaces = totalItemsSize + totalGaps * maxSpaceBetweenItems; // total size of items and spaces if we had enough room - - int spaces = totalGaps * maxSpaceBetweenItems; // We'll decrement this below to place one space between each item until we run out - if (totalItemsSize >= containerSize) - { - spaces = 0; - } - else if (totalItemsAndSpaces > containerSize) - { - spaces = containerSize - totalItemsSize; - } - - switch (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, containerSize - 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, containerSize - 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] = (containerSize - sizes [0]) / 2; // single item is centered - } - - break; - - case Justification.Justified: - int spaceBetween = sizes.Length > 1 ? (containerSize - totalItemsSize) / (sizes.Length - 1) : 0; - int remainder = sizes.Length > 1 ? (containerSize - totalItemsSize) % (sizes.Length - 1) : 0; - currentPosition = 0; - - for (var i = 0; i < sizes.Length; i++) - { - 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] = containerSize - 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] = containerSize - 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 = containerSize - sizes [i]; - positions [i] = currentPosition; - } - - if (i < sizes.Length - 1 && i > 0) - { - int spaceBefore = spaces-- > 0 ? maxSpaceBetweenItems : 0; - - positions [i] = currentPosition - sizes [i] - spaceBefore; - currentPosition = positions [i]; - } - } - } - else if (sizes.Length == 1) - { - 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; - } -} diff --git a/Terminal.Gui/Drawing/Thickness.cs b/Terminal.Gui/Drawing/Thickness.cs index 6070e6cbe..ad684470b 100644 --- a/Terminal.Gui/Drawing/Thickness.cs +++ b/Terminal.Gui/Drawing/Thickness.cs @@ -230,8 +230,8 @@ public class Thickness : IEquatable var tf = new TextFormatter { Text = label is null ? string.Empty : $"{label} {this}", - Alignment = TextAlignment.Centered, - VerticalAlignment = VerticalTextAlignment.Bottom, + Alignment = Alignment.Center, + VerticalAlignment = Alignment.End, AutoSize = true }; tf.Draw (rect, Application.Driver.CurrentAttribute, Application.Driver.CurrentAttribute, rect); diff --git a/Terminal.Gui/Resources/config.json b/Terminal.Gui/Resources/config.json index 368ccd8bf..8380a14f5 100644 --- a/Terminal.Gui/Resources/config.json +++ b/Terminal.Gui/Resources/config.json @@ -24,7 +24,8 @@ "Themes": [ { "Default": { - "Dialog.DefaultButtonAlignment": "Center", + "Dialog.DefaultButtonAlignment": "End", + "Dialog.DefaultButtonAlignmentModes": "AddSpaceBetweenItems", "FrameView.DefaultBorderStyle": "Single", "Window.DefaultBorderStyle": "Single", "ColorSchemes": [ diff --git a/Terminal.Gui/Text/TextAlignment.cs b/Terminal.Gui/Text/TextAlignment.cs deleted file mode 100644 index 44950cfd5..000000000 --- a/Terminal.Gui/Text/TextAlignment.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace Terminal.Gui; - -/// Text alignment enumeration, controls how text is displayed. -public enum TextAlignment -{ - /// The text will be left-aligned. - Left, - - /// The text will be right-aligned. - Right, - - /// The text will be centered horizontally. - Centered, - - /// - /// The text will be justified (spaces will be added to existing spaces such that the text fills the container - /// horizontally). - /// - Justified -} \ No newline at end of file diff --git a/Terminal.Gui/Text/TextFormatter.cs b/Terminal.Gui/Text/TextFormatter.cs index af4a7b97b..bee37de67 100644 --- a/Terminal.Gui/Text/TextFormatter.cs +++ b/Terminal.Gui/Text/TextFormatter.cs @@ -1,3 +1,5 @@ +using System.Diagnostics; + namespace Terminal.Gui; /// @@ -15,14 +17,14 @@ public class TextFormatter private Size _size; private int _tabWidth = 4; private string _text; - private TextAlignment _textAlignment; + private Alignment _textAlignment = Alignment.Start; private TextDirection _textDirection; - private VerticalTextAlignment _textVerticalAlignment; + private Alignment _textVerticalAlignment = Alignment.Start; private bool _wordWrap = true; - /// Controls the horizontal text-alignment property. + /// Get or sets the horizontal text alignment. /// The text alignment. - public TextAlignment Alignment + public Alignment Alignment { get => _textAlignment; set => _textAlignment = EnableNeedsFormat (value); @@ -32,8 +34,7 @@ public class TextFormatter /// /// Used when is using to resize the view's to fit . /// - /// AutoSize is ignored if and - /// are used. + /// AutoSize is ignored if is used. /// /// public bool AutoSize @@ -68,9 +69,8 @@ public class TextFormatter /// Only the first HotKey specifier found in is supported. /// /// - /// If (the default) the width required for the HotKey specifier is returned. Otherwise the - /// height - /// is returned. + /// If (the default) the width required for the HotKey specifier is returned. Otherwise, the + /// height is returned. /// /// /// The number of characters required for the . If the text @@ -97,8 +97,8 @@ public class TextFormatter /// public int CursorPosition { get; internal set; } - /// Controls the text-direction property. - /// The text vertical alignment. + /// Gets or sets the text-direction. + /// The text direction. public TextDirection Direction { get => _textDirection; @@ -112,8 +112,7 @@ public class TextFormatter } } } - - + /// /// Determines if the viewport width will be used or only the text width will be used, /// If all the viewport area will be filled with whitespaces and the same background color @@ -223,9 +222,9 @@ public class TextFormatter } } - /// Controls the vertical text-alignment property. + /// Gets or sets the vertical text-alignment. /// The text vertical alignment. - public VerticalTextAlignment VerticalAlignment + public Alignment VerticalAlignment { get => _textVerticalAlignment; set => _textVerticalAlignment = EnableNeedsFormat (value); @@ -318,10 +317,10 @@ public class TextFormatter // When text is justified, we lost left or right, so we use the direction to align. - int x, y; + int x = 0, y = 0; // Horizontal Alignment - if (Alignment is TextAlignment.Right) + if (Alignment is Alignment.End) { if (isVertical) { @@ -336,7 +335,7 @@ public class TextFormatter CursorPosition = screen.Width - runesWidth + (_hotKeyPos > -1 ? _hotKeyPos : 0); } } - else if (Alignment is TextAlignment.Left) + else if (Alignment is Alignment.Start) { if (isVertical) { @@ -352,7 +351,7 @@ public class TextFormatter CursorPosition = _hotKeyPos > -1 ? _hotKeyPos : 0; } - else if (Alignment is TextAlignment.Justified) + else if (Alignment is Alignment.Fill) { if (isVertical) { @@ -375,7 +374,7 @@ public class TextFormatter CursorPosition = _hotKeyPos > -1 ? _hotKeyPos : 0; } - else if (Alignment is TextAlignment.Centered) + else if (Alignment is Alignment.Center) { if (isVertical) { @@ -395,11 +394,13 @@ public class TextFormatter } else { - throw new ArgumentOutOfRangeException ($"{nameof (Alignment)}"); + Debug.WriteLine ($"Unsupported Alignment: {nameof (VerticalAlignment)}"); + + return; } // Vertical Alignment - if (VerticalAlignment is VerticalTextAlignment.Bottom) + if (VerticalAlignment is Alignment.End) { if (isVertical) { @@ -410,7 +411,7 @@ public class TextFormatter y = screen.Bottom - linesFormatted.Count + line; } } - else if (VerticalAlignment is VerticalTextAlignment.Top) + else if (VerticalAlignment is Alignment.Start) { if (isVertical) { @@ -421,7 +422,7 @@ public class TextFormatter y = screen.Top + line; } } - else if (VerticalAlignment is VerticalTextAlignment.Justified) + else if (VerticalAlignment is Alignment.Fill) { if (isVertical) { @@ -435,7 +436,7 @@ public class TextFormatter line < linesFormatted.Count - 1 ? screen.Height - interval <= 1 ? screen.Top + 1 : screen.Top + line * interval : screen.Bottom - 1; } } - else if (VerticalAlignment is VerticalTextAlignment.Middle) + else if (VerticalAlignment is Alignment.Center) { if (isVertical) { @@ -450,7 +451,9 @@ public class TextFormatter } else { - throw new ArgumentOutOfRangeException ($"{nameof (VerticalAlignment)}"); + Debug.WriteLine ($"Unsupported Alignment: {nameof (VerticalAlignment)}"); + + return; } int colOffset = screen.X < 0 ? Math.Abs (screen.X) : 0; @@ -471,8 +474,8 @@ public class TextFormatter { if (idx < 0 || (isVertical - ? VerticalAlignment != VerticalTextAlignment.Bottom && current < 0 - : Alignment != TextAlignment.Right && x + current + colOffset < 0)) + ? VerticalAlignment != Alignment.End && current < 0 + : Alignment != Alignment.End && x + current + colOffset < 0)) { current++; @@ -561,7 +564,7 @@ public class TextFormatter if (HotKeyPos > -1 && idx == HotKeyPos) { - if ((isVertical && VerticalAlignment == VerticalTextAlignment.Justified) || (!isVertical && Alignment == TextAlignment.Justified)) + if ((isVertical && VerticalAlignment == Alignment.Fill) || (!isVertical && Alignment == Alignment.Fill)) { CursorPosition = idx - start; } @@ -699,7 +702,7 @@ public class TextFormatter _lines = Format ( text, Size.Height, - VerticalAlignment == VerticalTextAlignment.Justified, + VerticalAlignment == Alignment.Fill, Size.Width > colsWidth && WordWrap, PreserveTrailingSpaces, TabWidth, @@ -723,7 +726,7 @@ public class TextFormatter _lines = Format ( text, Size.Width, - Alignment == TextAlignment.Justified, + Alignment == Alignment.Fill, Size.Height > 1 && WordWrap, PreserveTrailingSpaces, TabWidth, @@ -977,7 +980,7 @@ public class TextFormatter // if value is not wide enough if (text.EnumerateRunes ().Sum (c => c.GetColumns ()) < width) { - // pad it out with spaces to the given alignment + // pad it out with spaces to the given Alignment int toPad = width - text.EnumerateRunes ().Sum (c => c.GetColumns ()); return text + new string (' ', toPad); @@ -999,7 +1002,7 @@ public class TextFormatter /// instance to access any of his objects. /// A list of word wrapped lines. /// - /// This method does not do any justification. + /// This method does not do any alignment. /// This method strips Newline ('\n' and '\r\n') sequences before processing. /// /// If is at most one space will be preserved @@ -1031,7 +1034,7 @@ public class TextFormatter List runes = StripCRLF (text).ToRuneList (); int start = Math.Max ( - !runes.Contains ((Rune)' ') && textFormatter is { VerticalAlignment: VerticalTextAlignment.Bottom } && IsVerticalDirection (textDirection) + !runes.Contains ((Rune)' ') && textFormatter is { VerticalAlignment: Alignment.End } && IsVerticalDirection (textDirection) ? runes.Count - width : 0, 0); @@ -1249,7 +1252,7 @@ public class TextFormatter /// The number of columns to clip the text to. Text longer than will be /// clipped. /// - /// Alignment. + /// Alignment. /// The text direction. /// The number of columns used for a tab. /// instance to access any of his objects. @@ -1257,13 +1260,13 @@ public class TextFormatter public static string ClipAndJustify ( string text, int width, - TextAlignment talign, + Alignment textAlignment, TextDirection textDirection = TextDirection.LeftRight_TopBottom, int tabWidth = 0, TextFormatter textFormatter = null ) { - return ClipAndJustify (text, width, talign == TextAlignment.Justified, textDirection, tabWidth, textFormatter); + return ClipAndJustify (text, width, textAlignment == Alignment.Fill, textDirection, tabWidth, textFormatter); } /// Justifies text within a specified width. @@ -1304,12 +1307,12 @@ public class TextFormatter { if (IsHorizontalDirection (textDirection)) { - if (textFormatter is { Alignment: TextAlignment.Right }) + if (textFormatter is { Alignment: Alignment.End }) { return GetRangeThatFits (runes, runes.Count - width, text, width, tabWidth, textDirection); } - if (textFormatter is { Alignment: TextAlignment.Centered }) + if (textFormatter is { Alignment: Alignment.Center }) { return GetRangeThatFits (runes, Math.Max ((runes.Count - width) / 2, 0), text, width, tabWidth, textDirection); } @@ -1319,12 +1322,12 @@ public class TextFormatter if (IsVerticalDirection (textDirection)) { - if (textFormatter is { VerticalAlignment: VerticalTextAlignment.Bottom }) + if (textFormatter is { VerticalAlignment: Alignment.End }) { return GetRangeThatFits (runes, runes.Count - width, text, width, tabWidth, textDirection); } - if (textFormatter is { VerticalAlignment: VerticalTextAlignment.Middle }) + if (textFormatter is { VerticalAlignment: Alignment.Center }) { return GetRangeThatFits (runes, Math.Max ((runes.Count - width) / 2, 0), text, width, tabWidth, textDirection); } @@ -1342,14 +1345,14 @@ public class TextFormatter if (IsHorizontalDirection (textDirection)) { - if (textFormatter is { Alignment: TextAlignment.Right }) + if (textFormatter is { Alignment: Alignment.End }) { if (GetRuneWidth (text, tabWidth, textDirection) > width) { return GetRangeThatFits (runes, runes.Count - width, text, width, tabWidth, textDirection); } } - else if (textFormatter is { Alignment: TextAlignment.Centered }) + else if (textFormatter is { Alignment: Alignment.Center }) { return GetRangeThatFits (runes, Math.Max ((runes.Count - width) / 2, 0), text, width, tabWidth, textDirection); } @@ -1361,14 +1364,14 @@ public class TextFormatter if (IsVerticalDirection (textDirection)) { - if (textFormatter is { VerticalAlignment: VerticalTextAlignment.Bottom }) + if (textFormatter is { VerticalAlignment: Alignment.End }) { if (runes.Count - zeroLength > width) { return GetRangeThatFits (runes, runes.Count - width, text, width, tabWidth, textDirection); } } - else if (textFormatter is { VerticalAlignment: VerticalTextAlignment.Middle }) + else if (textFormatter is { VerticalAlignment: Alignment.Center }) { return GetRangeThatFits (runes, Math.Max ((runes.Count - width) / 2, 0), text, width, tabWidth, textDirection); } @@ -1475,7 +1478,7 @@ public class TextFormatter /// Formats text into lines, applying text alignment and optionally wrapping text to new lines on word boundaries. /// /// The number of columns to constrain the text to for word wrapping and clipping. - /// Specifies how the text will be aligned horizontally. + /// Specifies how the text will be aligned horizontally. /// /// If , the text will be wrapped to new lines no longer than /// . If , forces text to fit a single line. Line breaks are converted @@ -1498,7 +1501,7 @@ public class TextFormatter public static List Format ( string text, int width, - TextAlignment talign, + Alignment textAlignment, bool wordWrap, bool preserveTrailingSpaces = false, int tabWidth = 0, @@ -1510,7 +1513,7 @@ public class TextFormatter return Format ( text, width, - talign == TextAlignment.Justified, + textAlignment == Alignment.Fill, wordWrap, preserveTrailingSpaces, tabWidth, @@ -1884,7 +1887,7 @@ public class TextFormatter return lineIdx; } - /// Calculates the rectangle required to hold text, assuming no word wrapping or justification. + /// Calculates the rectangle required to hold text, assuming no word wrapping or alignment. /// /// This API will return incorrect results if the text includes glyphs who's width is dependent on surrounding /// glyphs (e.g. Arabic). diff --git a/Terminal.Gui/Text/VerticalTextAlignment.cs b/Terminal.Gui/Text/VerticalTextAlignment.cs deleted file mode 100644 index ef7788577..000000000 --- a/Terminal.Gui/Text/VerticalTextAlignment.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace Terminal.Gui; - -/// Vertical text alignment enumeration, controls how text is displayed. -public enum VerticalTextAlignment -{ - /// The text will be top-aligned. - Top, - - /// The text will be bottom-aligned. - Bottom, - - /// The text will centered vertically. - Middle, - - /// - /// The text will be justified (spaces will be added to existing spaces such that the text fills the container - /// vertically). - /// - Justified -} \ No newline at end of file diff --git a/Terminal.Gui/View/Layout/Dim.cs b/Terminal.Gui/View/Layout/Dim.cs index 1fc16c9c2..79c2e6db0 100644 --- a/Terminal.Gui/View/Layout/Dim.cs +++ b/Terminal.Gui/View/Layout/Dim.cs @@ -150,7 +150,11 @@ public abstract class Dim /// Creates a percentage object that is a percentage of the width or height of the SuperView. /// The percent object. /// A value between 0 and 100 representing the percentage. - /// + /// + /// If the dimension is computed using the View's position ( or + /// ). + /// If the dimension is computed using the View's . + /// /// /// This initializes a that will be centered horizontally, is 50% of the way down, is 30% the /// height, diff --git a/Terminal.Gui/View/Layout/Pos.cs b/Terminal.Gui/View/Layout/Pos.cs index e27fabb3c..3a4e27340 100644 --- a/Terminal.Gui/View/Layout/Pos.cs +++ b/Terminal.Gui/View/Layout/Pos.cs @@ -26,6 +26,14 @@ namespace Terminal.Gui; /// /// /// +/// +/// +/// +/// Creates a object that aligns a set of views. +/// +/// +/// +/// /// /// /// @@ -132,6 +140,30 @@ public abstract class Pos /// The value to convert to the . public static Pos Absolute (int position) { return new PosAbsolute (position); } + /// + /// Creates a object that aligns a set of views according to the specified + /// and . + /// + /// The alignment. + /// The optional alignment modes. + /// + /// The optional identifier of a set of views that should be aligned together. When only a single + /// set of views in a SuperView is aligned, this parameter is optional. + /// + /// + public static Pos Align (Alignment alignment, AlignmentModes modes = AlignmentModes.StartToEnd | AlignmentModes.AddSpaceBetweenItems, int groupId = 0) + { + return new PosAlign + { + Aligner = new () + { + Alignment = alignment, + AlignmentModes = modes + }, + GroupId = groupId + }; + } + /// /// Creates a object that is anchored to the end (right side or /// bottom) of the SuperView's Content Area, minus the respective size of the View. This is equivalent to using @@ -359,5 +391,4 @@ public abstract class Pos } #endregion operators - -} \ No newline at end of file +} diff --git a/Terminal.Gui/View/Layout/PosAlign.cs b/Terminal.Gui/View/Layout/PosAlign.cs new file mode 100644 index 000000000..83ba4a1c8 --- /dev/null +++ b/Terminal.Gui/View/Layout/PosAlign.cs @@ -0,0 +1,169 @@ +#nullable enable + +using System.ComponentModel; + +namespace Terminal.Gui; + +/// +/// Enables alignment of a set of views. +/// +/// +/// +/// Updating the properties of is supported, but will not automatically cause re-layout to +/// happen. +/// must be called on the SuperView. +/// +/// +/// Views that should be aligned together must have a distinct . When only a single +/// set of views is aligned within a SuperView, setting is optional because it defaults to 0. +/// +/// +/// The first view added to the Superview with a given is used to determine the alignment of +/// the group. +/// The alignment is applied to all views with the same . +/// +/// +public class PosAlign : Pos +{ + /// + /// The cached location. Used to store the calculated location to minimize recalculating it. + /// + private int? _cachedLocation; + + /// + /// Gets the identifier of a set of views that should be aligned together. When only a single + /// set of views in a SuperView is aligned, setting is not needed because it defaults to 0. + /// + public int GroupId { get; init; } + + private readonly Aligner? _aligner; + + /// + /// Gets the alignment settings. + /// + public required Aligner Aligner + { + get => _aligner!; + init + { + if (_aligner is { }) + { + _aligner.PropertyChanged -= Aligner_PropertyChanged; + } + + _aligner = value; + _aligner.PropertyChanged += Aligner_PropertyChanged; + } + } + + /// + /// Aligns the views in that have the same group ID as . + /// Updates each view's cached _location. + /// + /// + /// + /// + /// + private static void AlignAndUpdateGroup (int groupId, IList views, Dimension dimension, int size) + { + List dimensionsList = new (); + + // PERF: If this proves a perf issue, consider caching a ref to this list in each item + List viewsInGroup = views.Where ( + v => + { + return dimension switch + { + Dimension.Width when v.X is PosAlign alignX => alignX.GroupId == groupId, + Dimension.Height when v.Y is PosAlign alignY => alignY.GroupId == groupId, + _ => false + }; + }) + .ToList (); + + if (viewsInGroup.Count == 0) + { + return; + } + + // PERF: We iterate over viewsInGroup multiple times here. + + Aligner? firstInGroup = null; + + // Update the dimensionList with the sizes of the views + for (var index = 0; index < viewsInGroup.Count; index++) + { + View view = viewsInGroup [index]; + PosAlign? posAlign = dimension == Dimension.Width ? view.X as PosAlign : view.Y as PosAlign; + + if (posAlign is { }) + { + if (index == 0) + { + firstInGroup = posAlign.Aligner; + } + + dimensionsList.Add (dimension == Dimension.Width ? view.Frame.Width : view.Frame.Height); + } + } + + // Update the first item in the group with the new container size. + firstInGroup!.ContainerSize = size; + + // Align + int [] locations = firstInGroup.Align (dimensionsList.ToArray ()); + + // Update the cached location for each item + for (var index = 0; index < viewsInGroup.Count; index++) + { + View view = viewsInGroup [index]; + PosAlign? align = dimension == Dimension.Width ? view.X as PosAlign : view.Y as PosAlign; + + if (align is { }) + { + align._cachedLocation = locations [index]; + } + } + } + + private void Aligner_PropertyChanged (object? sender, PropertyChangedEventArgs e) { _cachedLocation = null; } + + /// + public override bool Equals (object? other) + { + return other is PosAlign align + && GroupId == align.GroupId + && align.Aligner.Alignment == Aligner.Alignment + && align.Aligner.AlignmentModes == Aligner.AlignmentModes; + } + + /// + public override int GetHashCode () { return HashCode.Combine (Aligner, GroupId); } + + /// + public override string ToString () { return $"Align(alignment={Aligner.Alignment},modes={Aligner.AlignmentModes},groupId={GroupId})"; } + + internal override int GetAnchor (int width) { return _cachedLocation ?? 0 - width; } + + internal override int Calculate (int superviewDimension, Dim dim, View us, Dimension dimension) + { + if (_cachedLocation.HasValue && Aligner.ContainerSize == superviewDimension) + { + return _cachedLocation.Value; + } + + if (us?.SuperView is null) + { + return 0; + } + + AlignAndUpdateGroup (GroupId, us.SuperView.Subviews, dimension, superviewDimension); + + if (_cachedLocation.HasValue) + { + return _cachedLocation.Value; + } + + return 0; + } +} diff --git a/Terminal.Gui/View/ViewText.cs b/Terminal.Gui/View/ViewText.cs index 2ee3a51a0..4b203091b 100644 --- a/Terminal.Gui/View/ViewText.cs +++ b/Terminal.Gui/View/ViewText.cs @@ -87,7 +87,7 @@ public partial class View /// or are using , the will be adjusted to fit the text. /// /// The text alignment. - public virtual TextAlignment TextAlignment + public virtual Alignment TextAlignment { get => TextFormatter.Alignment; set @@ -105,7 +105,7 @@ public partial class View /// /// or are using , the will be adjusted to fit the text. /// - /// The text alignment. + /// The text direction. public virtual TextDirection TextDirection { get => TextFormatter.Direction; @@ -129,8 +129,8 @@ public partial class View /// /// or are using , the will be adjusted to fit the text. /// - /// The text alignment. - public virtual VerticalTextAlignment VerticalTextAlignment + /// The vertical text alignment. + public virtual Alignment VerticalTextAlignment { get => TextFormatter.VerticalAlignment; set diff --git a/Terminal.Gui/Views/Button.cs b/Terminal.Gui/Views/Button.cs index 52c72ab5f..5b1cfcf3d 100644 --- a/Terminal.Gui/Views/Button.cs +++ b/Terminal.Gui/Views/Button.cs @@ -37,8 +37,8 @@ public class Button : View /// The width of the is computed based on the text length. The height will always be 1. public Button () { - TextAlignment = TextAlignment.Centered; - VerticalTextAlignment = VerticalTextAlignment.Middle; + TextAlignment = Alignment.Center; + VerticalTextAlignment = Alignment.Center; _leftBracket = Glyphs.LeftBracket; _rightBracket = Glyphs.RightBracket; diff --git a/Terminal.Gui/Views/CheckBox.cs b/Terminal.Gui/Views/CheckBox.cs index 5971c02ed..cf0adeefc 100644 --- a/Terminal.Gui/Views/CheckBox.cs +++ b/Terminal.Gui/Views/CheckBox.cs @@ -155,13 +155,13 @@ public class CheckBox : View { switch (TextAlignment) { - case TextAlignment.Left: - case TextAlignment.Centered: - case TextAlignment.Justified: + case Alignment.Start: + case Alignment.Center: + case Alignment.Fill: TextFormatter.Text = $"{GetCheckedState ()} {Text}"; break; - case TextAlignment.Right: + case Alignment.End: TextFormatter.Text = $"{Text} {GetCheckedState ()}"; break; diff --git a/Terminal.Gui/Views/DatePicker.cs b/Terminal.Gui/Views/DatePicker.cs index 613051fbe..d0cb41330 100644 --- a/Terminal.Gui/Views/DatePicker.cs +++ b/Terminal.Gui/Views/DatePicker.cs @@ -215,7 +215,6 @@ public class DatePicker : View { X = Pos.Center () - 2, Y = Pos.Bottom (_calendar) - 1, - Height = 1, Width = 2, Text = GetBackButtonText (), WantContinuousButtonPressed = true, @@ -234,7 +233,6 @@ public class DatePicker : View { X = Pos.Right (_previousMonthButton) + 2, Y = Pos.Bottom (_calendar) - 1, - Height = 1, Width = 2, Text = GetForwardButtonText (), WantContinuousButtonPressed = true, @@ -273,8 +271,8 @@ public class DatePicker : View Text = _date.ToString (Format); }; - Height = Dim.Auto (); - Width = Dim.Auto (); + Width = Dim.Auto (DimAutoStyle.Content); + Height = Dim.Auto (DimAutoStyle.Content); // BUGBUG: Remove when Dim.Auto(subviews) fully works SetContentSize (new (_calendar.Style.ColumnStyles.Sum (c => c.Value.MinWidth) + 7, _calendar.Frame.Height + 1)); diff --git a/Terminal.Gui/Views/Dialog.cs b/Terminal.Gui/Views/Dialog.cs index 9a964d86c..8d31511b1 100644 --- a/Terminal.Gui/Views/Dialog.cs +++ b/Terminal.Gui/Views/Dialog.cs @@ -15,21 +15,6 @@ namespace Terminal.Gui; /// public class Dialog : Window { - /// Determines the horizontal alignment of the Dialog buttons. - public enum ButtonAlignments - { - /// Center-aligns the buttons (the default). - Center = 0, - - /// Justifies the buttons - Justify, - - /// Left-aligns the buttons - Left, - - /// Right-aligns the buttons - Right - } // TODO: Reenable once border/borderframe design is settled /// @@ -59,27 +44,25 @@ public class Dialog : Window Y = Pos.Center (); //ValidatePosDim = true; - Width = Dim.Percent (85); + Width = Dim.Percent (85); Height = Dim.Percent (85); ColorScheme = Colors.ColorSchemes ["Dialog"]; Modal = true; ButtonAlignment = DefaultButtonAlignment; + ButtonAlignmentModes = DefaultButtonAlignmentModes; - AddCommand (Command.QuitToplevel, () => - { - Canceled = true; - RequestStop (); - return true; - }); + AddCommand ( + Command.QuitToplevel, + () => + { + Canceled = true; + RequestStop (); + + return true; + }); KeyBindings.Add (Key.Esc, Command.QuitToplevel); - Initialized += Dialog_Initialized; ; - } - - private void Dialog_Initialized (object sender, EventArgs e) - { - LayoutButtons (); } private bool _canceled; @@ -107,12 +90,19 @@ public class Dialog : Window } #endif _canceled = value; + return; } } + // TODO: Update button.X = Pos.Justify when alignment changes /// Determines how the s are aligned along the bottom of the dialog. - public ButtonAlignments ButtonAlignment { get; set; } + public Alignment ButtonAlignment { get; set; } + + /// + /// Gets or sets the alignment modes for the dialog's buttons. + /// + public AlignmentModes ButtonAlignmentModes { get; set; } /// Optional buttons to lay out at the bottom of the dialog. public Button [] Buttons @@ -132,11 +122,17 @@ public class Dialog : Window } } - /// The default for . + /// The default for . /// This property can be set in a Theme. [SerializableConfigurationProperty (Scope = typeof (ThemeScope))] [JsonConverter (typeof (JsonStringEnumConverter))] - public static ButtonAlignments DefaultButtonAlignment { get; set; } = ButtonAlignments.Center; + public static Alignment DefaultButtonAlignment { get; set; } = Alignment.End; + + /// The default for . + /// This property can be set in a Theme. + [SerializableConfigurationProperty (Scope = typeof (ThemeScope))] + [JsonConverter (typeof (JsonStringEnumConverter))] + public static AlignmentModes DefaultButtonAlignmentModes { get; set; } = AlignmentModes.StartToEnd | AlignmentModes.AddSpaceBetweenItems; /// /// Adds a to the , its layout will be controlled by the @@ -150,6 +146,10 @@ public class Dialog : Window return; } + // Use a distinct GroupId so users can use Pos.Align for other views in the Dialog + button.X = Pos.Align (ButtonAlignment, ButtonAlignmentModes, groupId: GetHashCode ()); + button.Y = Pos.AnchorEnd (); + _buttons.Add (button); Add (button); @@ -189,109 +189,4 @@ public class Dialog : Window return widths.Sum (); } - - private void LayoutButtons () - { - if (_buttons.Count == 0 || !IsInitialized) - { - return; - } - - var shiftLeft = 0; - - int buttonsWidth = GetButtonsWidth (); - - switch (ButtonAlignment) - { - case ButtonAlignments.Center: - // Center Buttons - shiftLeft = (Viewport.Width - buttonsWidth - _buttons.Count - 1) / 2 + 1; - - for (int i = _buttons.Count - 1; i >= 0; i--) - { - Button button = _buttons [i]; - shiftLeft += button.Frame.Width + (i == _buttons.Count - 1 ? 0 : 1); - - if (shiftLeft > -1) - { - button.X = Pos.AnchorEnd (shiftLeft); - } - else - { - button.X = Viewport.Width - shiftLeft; - } - - button.Y = Pos.AnchorEnd (); - } - - break; - - case ButtonAlignments.Justify: - // Justify Buttons - // leftmost and rightmost buttons are hard against edges. The rest are evenly spaced. - - var spacing = (int)Math.Ceiling ((double)(Viewport.Width - buttonsWidth) / (_buttons.Count - 1)); - - for (int i = _buttons.Count - 1; i >= 0; i--) - { - Button button = _buttons [i]; - - if (i == _buttons.Count - 1) - { - shiftLeft += button.Frame.Width; - button.X = Pos.AnchorEnd (shiftLeft); - } - else - { - if (i == 0) - { - // first (leftmost) button - int left = Viewport.Width; - button.X = Pos.AnchorEnd (left); - } - else - { - shiftLeft += button.Frame.Width + spacing; - button.X = Pos.AnchorEnd (shiftLeft); - } - } - - button.Y = Pos.AnchorEnd (); - } - - break; - - case ButtonAlignments.Left: - // Left Align Buttons - Button prevButton = _buttons [0]; - prevButton.X = 0; - prevButton.Y = Pos.AnchorEnd (1); - - for (var i = 1; i < _buttons.Count; i++) - { - Button button = _buttons [i]; - button.X = Pos.Right (prevButton) + 1; - button.Y = Pos.AnchorEnd (1); - prevButton = button; - } - - break; - - case ButtonAlignments.Right: - // Right align buttons - shiftLeft = _buttons [_buttons.Count - 1].Frame.Width; - _buttons [_buttons.Count - 1].X = Pos.AnchorEnd (shiftLeft); - _buttons [_buttons.Count - 1].Y = Pos.AnchorEnd (1); - - for (int i = _buttons.Count - 2; i >= 0; i--) - { - Button button = _buttons [i]; - shiftLeft += button.Frame.Width + 1; - button.X = Pos.AnchorEnd (shiftLeft); - button.Y = Pos.AnchorEnd (); - } - - break; - } - } } diff --git a/Terminal.Gui/Views/ListView.cs b/Terminal.Gui/Views/ListView.cs index 725fe6bd8..bf2586185 100644 --- a/Terminal.Gui/Views/ListView.cs +++ b/Terminal.Gui/Views/ListView.cs @@ -1002,7 +1002,7 @@ public class ListWrapper : IListDataSource private void RenderUstr (ConsoleDriver driver, string ustr, int col, int line, int width, int start = 0) { string str = start > ustr.GetColumns () ? string.Empty : ustr.Substring (Math.Min (start, ustr.ToRunes ().Length - 1)); - string u = TextFormatter.ClipAndJustify (str, width, TextAlignment.Left); + string u = TextFormatter.ClipAndJustify (str, width, Alignment.Start); driver.AddStr (u); width -= u.GetColumns (); diff --git a/Terminal.Gui/Views/Menu/Menu.cs b/Terminal.Gui/Views/Menu/Menu.cs index 11bc71c7e..17e92cd1d 100644 --- a/Terminal.Gui/Views/Menu/Menu.cs +++ b/Terminal.Gui/Views/Menu/Menu.cs @@ -890,7 +890,7 @@ internal sealed class Menu : View var tf = new TextFormatter { AutoSize = true, - Alignment = TextAlignment.Centered, HotKeySpecifier = MenuBar.HotKeySpecifier, Text = textToDraw + Alignment = Alignment.Center, HotKeySpecifier = MenuBar.HotKeySpecifier, Text = textToDraw }; // The -3 is left/right border + one space (not sure what for) diff --git a/Terminal.Gui/Views/MessageBox.cs b/Terminal.Gui/Views/MessageBox.cs index 74826cc41..10a40ec39 100644 --- a/Terminal.Gui/Views/MessageBox.cs +++ b/Terminal.Gui/Views/MessageBox.cs @@ -325,7 +325,10 @@ public static class MessageBox foreach (string s in buttons) { - var b = new Button { Text = s }; + var b = new Button + { + Text = s, + }; if (count == defaultButton) { @@ -337,10 +340,10 @@ public static class MessageBox } } - Dialog d; - - d = new Dialog + var d = new Dialog { + ButtonAlignment = Alignment.Center, + ButtonAlignmentModes = AlignmentModes.StartToEnd | AlignmentModes.AddSpaceBetweenItems, Buttons = buttonList.ToArray (), Title = title, BorderStyle = DefaultBorderStyle, @@ -370,7 +373,7 @@ public static class MessageBox var messageLabel = new Label { Text = message, - TextAlignment = TextAlignment.Centered, + TextAlignment = Alignment.Center, X = Pos.Center (), Y = 0, // ColorScheme = Colors.ColorSchemes ["Error"] diff --git a/Terminal.Gui/Views/OrientationEventArgs.cs b/Terminal.Gui/Views/OrientationEventArgs.cs new file mode 100644 index 000000000..8a633ca83 --- /dev/null +++ b/Terminal.Gui/Views/OrientationEventArgs.cs @@ -0,0 +1,19 @@ +namespace Terminal.Gui; + +/// for events. +public class OrientationEventArgs : EventArgs +{ + /// Constructs a new instance. + /// the new orientation + public OrientationEventArgs (Orientation orientation) + { + Orientation = orientation; + Cancel = false; + } + + /// If set to true, the orientation change operation will be canceled, if applicable. + public bool Cancel { get; set; } + + /// The new orientation. + public Orientation Orientation { get; set; } +} \ No newline at end of file diff --git a/Terminal.Gui/Views/ProgressBar.cs b/Terminal.Gui/Views/ProgressBar.cs index f599b299f..80b7545b7 100644 --- a/Terminal.Gui/Views/ProgressBar.cs +++ b/Terminal.Gui/Views/ProgressBar.cs @@ -175,7 +175,7 @@ public class ProgressBar : View if (ProgressBarFormat != ProgressBarFormat.Simple && !_isActivity) { - var tf = new TextFormatter { Alignment = TextAlignment.Centered, Text = Text, AutoSize = true }; + var tf = new TextFormatter { Alignment = Alignment.Center, Text = Text, AutoSize = true }; var attr = new Attribute (ColorScheme.HotNormal.Foreground, ColorScheme.HotNormal.Background); if (_fraction > .5) diff --git a/Terminal.Gui/Views/Slider.cs b/Terminal.Gui/Views/Slider.cs index 992bc72fa..c42247299 100644 --- a/Terminal.Gui/Views/Slider.cs +++ b/Terminal.Gui/Views/Slider.cs @@ -1,210 +1,5 @@ namespace Terminal.Gui; -/// for events. -public class SliderOptionEventArgs : EventArgs -{ - /// Initializes a new instance of - /// indicates whether the option is set - public SliderOptionEventArgs (bool isSet) { IsSet = isSet; } - - /// Gets whether the option is set or not. - public bool IsSet { get; } -} - -/// Represents an option in a . -/// Data type of the option. -public class SliderOption -{ - /// Creates a new empty instance of the class. - public SliderOption () { } - - /// Creates a new instance of the class with values for each property. - public SliderOption (string legend, Rune legendAbbr, T data) - { - Legend = legend; - LegendAbbr = legendAbbr; - Data = data; - } - - /// Event fired when the an option has changed. - public event EventHandler Changed; - - /// Custom data of the option. - public T Data { get; set; } - - /// Legend of the option. - public string Legend { get; set; } - - /// - /// Abbreviation of the Legend. When the too small to fit - /// . - /// - public Rune LegendAbbr { get; set; } - - /// Event Raised when this option is set. - public event EventHandler Set; - - /// Creates a human-readable string that represents this . - public override string ToString () { return "{Legend=" + Legend + ", LegendAbbr=" + LegendAbbr + ", Data=" + Data + "}"; } - - /// Event Raised when this option is unset. - public event EventHandler UnSet; - - /// To Raise the event from the Slider. - internal void OnChanged (bool isSet) { Changed?.Invoke (this, new (isSet)); } - - /// To Raise the event from the Slider. - internal void OnSet () { Set?.Invoke (this, new (true)); } - - /// To Raise the event from the Slider. - internal void OnUnSet () { UnSet?.Invoke (this, new (false)); } -} - -/// Types -public enum SliderType -{ - /// - /// - /// ├─┼─┼─┼─┼─█─┼─┼─┼─┼─┼─┼─┤ - /// - /// - Single, - - /// - /// - /// ├─┼─█─┼─┼─█─┼─┼─┼─┼─█─┼─┤ - /// - /// - Multiple, - - /// - /// - /// ├▒▒▒▒▒▒▒▒▒█─┼─┼─┼─┼─┼─┼─┤ - /// - /// - LeftRange, - - /// - /// - /// ├─┼─┼─┼─┼─█▒▒▒▒▒▒▒▒▒▒▒▒▒┤ - /// - /// - RightRange, - - /// - /// - /// ├─┼─┼─┼─┼─█▒▒▒▒▒▒▒█─┼─┼─┤ - /// - /// - Range -} - -/// Legend Style -public class SliderAttributes -{ - /// Attribute for the Legends Container. - public Attribute? EmptyAttribute { get; set; } - - /// Attribute for when the respective Option is NOT Set. - public Attribute? NormalAttribute { get; set; } - - /// Attribute for when the respective Option is Set. - public Attribute? SetAttribute { get; set; } -} - -/// Style -public class SliderStyle -{ - /// Constructs a new instance. - public SliderStyle () { LegendAttributes = new (); } - - /// The glyph and the attribute to indicate mouse dragging. - public Cell DragChar { get; set; } - - /// The glyph and the attribute used for empty spaces on the slider. - public Cell EmptyChar { get; set; } - - /// The glyph and the attribute used for the end of ranges on the slider. - public Cell EndRangeChar { get; set; } - - /// Legend attributes - public SliderAttributes LegendAttributes { get; set; } - - /// The glyph and the attribute used for each option (tick) on the slider. - public Cell OptionChar { get; set; } - - /// The glyph and the attribute used for filling in ranges on the slider. - public Cell RangeChar { get; set; } - - /// The glyph and the attribute used for options (ticks) that are set on the slider. - public Cell SetChar { get; set; } - - /// The glyph and the attribute used for spaces between options (ticks) on the slider. - public Cell SpaceChar { get; set; } - - /// The glyph and the attribute used for the start of ranges on the slider. - public Cell StartRangeChar { get; set; } -} - -/// All configuration are grouped in this class. -internal class SliderConfiguration -{ - internal bool _allowEmpty; - internal int _endSpacing; - internal int _minInnerSpacing = 1; - internal int _cachedInnerSpacing; // Currently calculated - internal Orientation _legendsOrientation = Orientation.Horizontal; - internal bool _rangeAllowSingle; - internal bool _showEndSpacing; - internal bool _showLegends; - internal bool _showLegendsAbbr; - internal Orientation _sliderOrientation = Orientation.Horizontal; - internal int _startSpacing; - internal SliderType _type = SliderType.Single; - internal bool _useMinimumSize; -} - -/// for events. -public class SliderEventArgs : EventArgs -{ - /// Initializes a new instance of - /// The current options. - /// Index of the option that is focused. -1 if no option has the focus. - public SliderEventArgs (Dictionary> options, int focused = -1) - { - Options = options; - Focused = focused; - Cancel = false; - } - - /// If set to true, the focus operation will be canceled, if applicable. - public bool Cancel { get; set; } - - /// Gets or sets the index of the option that is focused. - public int Focused { get; set; } - - /// Gets/sets whether the option is set or not. - public Dictionary> Options { get; set; } -} - -/// for events. -public class OrientationEventArgs : EventArgs -{ - /// Constructs a new instance. - /// the new orientation - public OrientationEventArgs (Orientation orientation) - { - Orientation = orientation; - Cancel = false; - } - - /// If set to true, the orientation change operation will be canceled, if applicable. - public bool Cancel { get; set; } - - /// The new orientation. - public Orientation Orientation { get; set; } -} - /// Slider control. public class Slider : Slider { @@ -1002,7 +797,7 @@ public class Slider : View } } - private string AlignText (string text, int width, TextAlignment textAlignment) + private string AlignText (string text, int width, Alignment alignment) { if (text is null) { @@ -1019,20 +814,20 @@ public class Slider : View string s2 = new (' ', w % 2); // Note: The formatter doesn't handle all of this ??? - switch (textAlignment) + switch (alignment) { - case TextAlignment.Justified: + case Alignment.Fill: return TextFormatter.Justify (text, width); - case TextAlignment.Left: + case Alignment.Start: return text + s1 + s1 + s2; - case TextAlignment.Centered: + case Alignment.Center: if (text.Length % 2 != 0) { return s1 + text + s1 + s2; } return s1 + s2 + text + s1; - case TextAlignment.Right: + case Alignment.End: return s1 + s1 + s2 + text; default: return text; @@ -1139,12 +934,6 @@ public class Slider : View } break; - case SliderType.Single: - break; - case SliderType.Multiple: - break; - default: - throw new ArgumentOutOfRangeException (); } } @@ -1365,7 +1154,7 @@ public class Slider : View switch (_config._legendsOrientation) { case Orientation.Horizontal: - text = AlignText (text, _config._cachedInnerSpacing + 1, TextAlignment.Centered); + text = AlignText (text, _config._cachedInnerSpacing + 1, Alignment.Center); break; case Orientation.Vertical: @@ -1383,7 +1172,7 @@ public class Slider : View break; case Orientation.Vertical: - text = AlignText (text, _config._cachedInnerSpacing + 1, TextAlignment.Centered); + text = AlignText (text, _config._cachedInnerSpacing + 1, Alignment.Center); break; } diff --git a/Terminal.Gui/Views/SliderAttributes.cs b/Terminal.Gui/Views/SliderAttributes.cs new file mode 100644 index 000000000..6f75546dd --- /dev/null +++ b/Terminal.Gui/Views/SliderAttributes.cs @@ -0,0 +1,14 @@ +namespace Terminal.Gui; + +/// Legend Style +public class SliderAttributes +{ + /// Attribute for the Legends Container. + public Attribute? EmptyAttribute { get; set; } + + /// Attribute for when the respective Option is NOT Set. + public Attribute? NormalAttribute { get; set; } + + /// Attribute for when the respective Option is Set. + public Attribute? SetAttribute { get; set; } +} \ No newline at end of file diff --git a/Terminal.Gui/Views/SliderConfiguration.cs b/Terminal.Gui/Views/SliderConfiguration.cs new file mode 100644 index 000000000..3cadafd86 --- /dev/null +++ b/Terminal.Gui/Views/SliderConfiguration.cs @@ -0,0 +1,19 @@ +namespace Terminal.Gui; + +/// All configuration are grouped in this class. +internal class SliderConfiguration +{ + internal bool _allowEmpty; + internal int _endSpacing; + internal int _minInnerSpacing = 1; + internal int _cachedInnerSpacing; // Currently calculated + internal Orientation _legendsOrientation = Orientation.Horizontal; + internal bool _rangeAllowSingle; + internal bool _showEndSpacing; + internal bool _showLegends; + internal bool _showLegendsAbbr; + internal Orientation _sliderOrientation = Orientation.Horizontal; + internal int _startSpacing; + internal SliderType _type = SliderType.Single; + internal bool _useMinimumSize; +} \ No newline at end of file diff --git a/Terminal.Gui/Views/SliderEventArgs.cs b/Terminal.Gui/Views/SliderEventArgs.cs new file mode 100644 index 000000000..76c4eed90 --- /dev/null +++ b/Terminal.Gui/Views/SliderEventArgs.cs @@ -0,0 +1,24 @@ +namespace Terminal.Gui; + +/// for events. +public class SliderEventArgs : EventArgs +{ + /// Initializes a new instance of + /// The current options. + /// Index of the option that is focused. -1 if no option has the focus. + public SliderEventArgs (Dictionary> options, int focused = -1) + { + Options = options; + Focused = focused; + Cancel = false; + } + + /// If set to true, the focus operation will be canceled, if applicable. + public bool Cancel { get; set; } + + /// Gets or sets the index of the option that is focused. + public int Focused { get; set; } + + /// Gets/sets whether the option is set or not. + public Dictionary> Options { get; set; } +} \ No newline at end of file diff --git a/Terminal.Gui/Views/SliderOption.cs b/Terminal.Gui/Views/SliderOption.cs new file mode 100644 index 000000000..1cfcc1f07 --- /dev/null +++ b/Terminal.Gui/Views/SliderOption.cs @@ -0,0 +1,50 @@ +namespace Terminal.Gui; + +/// Represents an option in a . +/// Data type of the option. +public class SliderOption +{ + /// Creates a new empty instance of the class. + public SliderOption () { } + + /// Creates a new instance of the class with values for each property. + public SliderOption (string legend, Rune legendAbbr, T data) + { + Legend = legend; + LegendAbbr = legendAbbr; + Data = data; + } + + /// Event fired when the an option has changed. + public event EventHandler Changed; + + /// Custom data of the option. + public T Data { get; set; } + + /// Legend of the option. + public string Legend { get; set; } + + /// + /// Abbreviation of the Legend. When the too small to fit + /// . + /// + public Rune LegendAbbr { get; set; } + + /// Event Raised when this option is set. + public event EventHandler Set; + + /// Creates a human-readable string that represents this . + public override string ToString () { return "{Legend=" + Legend + ", LegendAbbr=" + LegendAbbr + ", Data=" + Data + "}"; } + + /// Event Raised when this option is unset. + public event EventHandler UnSet; + + /// To Raise the event from the Slider. + internal void OnChanged (bool isSet) { Changed?.Invoke (this, new (isSet)); } + + /// To Raise the event from the Slider. + internal void OnSet () { Set?.Invoke (this, new (true)); } + + /// To Raise the event from the Slider. + internal void OnUnSet () { UnSet?.Invoke (this, new (false)); } +} \ No newline at end of file diff --git a/Terminal.Gui/Views/SliderOptionEventArgs.cs b/Terminal.Gui/Views/SliderOptionEventArgs.cs new file mode 100644 index 000000000..b4b5e6936 --- /dev/null +++ b/Terminal.Gui/Views/SliderOptionEventArgs.cs @@ -0,0 +1,12 @@ +namespace Terminal.Gui; + +/// for events. +public class SliderOptionEventArgs : EventArgs +{ + /// Initializes a new instance of + /// indicates whether the option is set + public SliderOptionEventArgs (bool isSet) { IsSet = isSet; } + + /// Gets whether the option is set or not. + public bool IsSet { get; } +} \ No newline at end of file diff --git a/Terminal.Gui/Views/SliderStyle.cs b/Terminal.Gui/Views/SliderStyle.cs new file mode 100644 index 000000000..e6429d6cb --- /dev/null +++ b/Terminal.Gui/Views/SliderStyle.cs @@ -0,0 +1,35 @@ +namespace Terminal.Gui; + +/// Style +public class SliderStyle +{ + /// Constructs a new instance. + public SliderStyle () { LegendAttributes = new (); } + + /// The glyph and the attribute to indicate mouse dragging. + public Cell DragChar { get; set; } + + /// The glyph and the attribute used for empty spaces on the slider. + public Cell EmptyChar { get; set; } + + /// The glyph and the attribute used for the end of ranges on the slider. + public Cell EndRangeChar { get; set; } + + /// Legend attributes + public SliderAttributes LegendAttributes { get; set; } + + /// The glyph and the attribute used for each option (tick) on the slider. + public Cell OptionChar { get; set; } + + /// The glyph and the attribute used for filling in ranges on the slider. + public Cell RangeChar { get; set; } + + /// The glyph and the attribute used for options (ticks) that are set on the slider. + public Cell SetChar { get; set; } + + /// The glyph and the attribute used for spaces between options (ticks) on the slider. + public Cell SpaceChar { get; set; } + + /// The glyph and the attribute used for the start of ranges on the slider. + public Cell StartRangeChar { get; set; } +} \ No newline at end of file diff --git a/Terminal.Gui/Views/SliderType.cs b/Terminal.Gui/Views/SliderType.cs new file mode 100644 index 000000000..7cbde908a --- /dev/null +++ b/Terminal.Gui/Views/SliderType.cs @@ -0,0 +1,40 @@ +namespace Terminal.Gui; + +/// Types +public enum SliderType +{ + /// + /// + /// ├─┼─┼─┼─┼─█─┼─┼─┼─┼─┼─┼─┤ + /// + /// + Single, + + /// + /// + /// ├─┼─█─┼─┼─█─┼─┼─┼─┼─█─┼─┤ + /// + /// + Multiple, + + /// + /// + /// ├▒▒▒▒▒▒▒▒▒█─┼─┼─┼─┼─┼─┼─┤ + /// + /// + LeftRange, + + /// + /// + /// ├─┼─┼─┼─┼─█▒▒▒▒▒▒▒▒▒▒▒▒▒┤ + /// + /// + RightRange, + + /// + /// + /// ├─┼─┼─┼─┼─█▒▒▒▒▒▒▒█─┼─┼─┤ + /// + /// + Range +} \ No newline at end of file diff --git a/Terminal.Gui/Views/TableView/ColumnStyle.cs b/Terminal.Gui/Views/TableView/ColumnStyle.cs index cbbc2a3ac..2d277abd9 100644 --- a/Terminal.Gui/Views/TableView/ColumnStyle.cs +++ b/Terminal.Gui/Views/TableView/ColumnStyle.cs @@ -8,10 +8,10 @@ public class ColumnStyle { /// - /// Defines a delegate for returning custom alignment per cell based on cell values. When specified this will + /// Defines a delegate for returning custom alignment per cell based on cell values. When specified this will /// override /// - public Func AlignmentGetter; + public Func AlignmentGetter; /// /// Defines a delegate for returning a custom color scheme per cell based on cell values. Return null for the @@ -20,26 +20,26 @@ public class ColumnStyle public CellColorGetterDelegate ColorGetter; /// - /// Defines a delegate for returning custom representations of cell values. If not set then - /// is used. Return values from your delegate may be truncated e.g. based on + /// Defines a delegate for returning custom representations of cell values. If not set then + /// is used. Return values from your delegate may be truncated e.g. based on /// /// public Func RepresentationGetter; - private bool visible = true; + private bool _visible = true; /// - /// Defines the default alignment for all values rendered in this column. For custom alignment based on cell + /// Defines the default alignment for all values rendered in this column. For custom alignment based on cell /// contents use . /// - public TextAlignment Alignment { get; set; } + public Alignment Alignment { get; set; } /// Defines the format for values e.g. "yyyy-MM-dd" for dates public string Format { get; set; } /// - /// Set the maximum width of the column in characters. This value will be ignored if more than the tables - /// . Defaults to + /// Set the maximum width of the column in characters. This value will be ignored if more than the tables + /// . Defaults to /// public int MaxWidth { get; set; } = TableView.DefaultMaxCellWidth; @@ -47,7 +47,7 @@ public class ColumnStyle public int MinAcceptableWidth { get; set; } = TableView.DefaultMinAcceptableWidth; /// - /// Set the minimum width of the column in characters. Setting this will ensure that even when a column has short + /// Set the minimum width of the column in characters. Setting this will ensure that even when a column has short /// content/header it still fills a given width of the control. /// /// This value will be ignored if more than the tables or the @@ -64,8 +64,8 @@ public class ColumnStyle /// If is 0 then will always return false. public bool Visible { - get => MaxWidth >= 0 && visible; - set => visible = value; + get => MaxWidth >= 0 && _visible; + set => _visible = value; } /// @@ -74,7 +74,7 @@ public class ColumnStyle /// /// /// - public TextAlignment GetAlignment (object cellValue) + public Alignment GetAlignment (object cellValue) { if (AlignmentGetter is { }) { diff --git a/Terminal.Gui/Views/TableView/TableStyle.cs b/Terminal.Gui/Views/TableView/TableStyle.cs index 2cf258bee..4dd947734 100644 --- a/Terminal.Gui/Views/TableView/TableStyle.cs +++ b/Terminal.Gui/Views/TableView/TableStyle.cs @@ -15,11 +15,11 @@ public class TableStyle /// public bool AlwaysUseNormalColorForVerticalCellLines { get; set; } = false; - /// Collection of columns for which you want special rendering (e.g. custom column lengths, text alignment etc) + /// Collection of columns for which you want special rendering (e.g. custom column lengths, text justification, etc.) public Dictionary ColumnStyles { get; set; } = new (); /// - /// Determines rendering when the last column in the table is visible but it's content or + /// Determines rendering when the last column in the table is visible, but it's content or /// is less than the remaining space in the control. True (the default) will expand /// the column to fill the remaining bounds of the control. False will draw a column ending line and leave a blank /// column that cannot be selected in the remaining space. diff --git a/Terminal.Gui/Views/TableView/TableView.cs b/Terminal.Gui/Views/TableView/TableView.cs index 0c39895e3..5e8ed2d74 100644 --- a/Terminal.Gui/Views/TableView/TableView.cs +++ b/Terminal.Gui/Views/TableView/TableView.cs @@ -2116,16 +2116,16 @@ public class TableView : View - (representation.EnumerateRunes ().Sum (c => c.GetColumns ()) + 1 /*leave 1 space for cell boundary*/); - switch (colStyle?.GetAlignment (originalCellValue) ?? TextAlignment.Left) + switch (colStyle?.GetAlignment (originalCellValue) ?? Alignment.Start) { - case TextAlignment.Left: + case Alignment.Start: return representation + new string (' ', toPad); - case TextAlignment.Right: + case Alignment.End: return new string (' ', toPad) + representation; // TODO: With single line cells, centered and justified are the same right? - case TextAlignment.Centered: - case TextAlignment.Justified: + case Alignment.Center: + case Alignment.Fill: return new string (' ', (int)Math.Floor (toPad / 2.0)) + // round down diff --git a/Terminal.Gui/Views/TextValidateField.cs b/Terminal.Gui/Views/TextValidateField.cs index 268bf0c73..3aac68f09 100644 --- a/Terminal.Gui/Views/TextValidateField.cs +++ b/Terminal.Gui/Views/TextValidateField.cs @@ -539,7 +539,7 @@ namespace Terminal.Gui { int c = _provider.Cursor (mouseEvent.Position.X - GetMargins (Viewport.Width).left); - if (_provider.Fixed == false && TextAlignment == TextAlignment.Right && Text.Length > 0) + if (_provider.Fixed == false && TextAlignment == Alignment.End && Text.Length > 0) { c++; } @@ -633,7 +633,7 @@ namespace Terminal.Gui // When it's right-aligned and it's a normal input, the cursor behaves differently. int curPos; - if (_provider?.Fixed == false && TextAlignment == TextAlignment.Right) + if (_provider?.Fixed == false && TextAlignment == Alignment.End) { curPos = _cursorPosition + left - 1; } @@ -650,7 +650,7 @@ namespace Terminal.Gui /// private bool BackspaceKeyHandler () { - if (_provider.Fixed == false && TextAlignment == TextAlignment.Right && _cursorPosition <= 1) + if (_provider.Fixed == false && TextAlignment == Alignment.End && _cursorPosition <= 1) { return false; } @@ -688,7 +688,7 @@ namespace Terminal.Gui /// private bool DeleteKeyHandler () { - if (_provider.Fixed == false && TextAlignment == TextAlignment.Right) + if (_provider.Fixed == false && TextAlignment == Alignment.End) { _cursorPosition = _provider.CursorLeft (_cursorPosition); } @@ -719,11 +719,11 @@ namespace Terminal.Gui switch (TextAlignment) { - case TextAlignment.Left: + case Alignment.Start: return (0, total); - case TextAlignment.Centered: + case Alignment.Center: return (total / 2, total / 2 + total % 2); - case TextAlignment.Right: + case Alignment.End: return (total, 0); default: return (0, total); diff --git a/Terminal.Gui/Views/TextView.cs b/Terminal.Gui/Views/TextView.cs index 7a70a94d0..d36fcda57 100644 --- a/Terminal.Gui/Views/TextView.cs +++ b/Terminal.Gui/Views/TextView.cs @@ -1784,7 +1784,7 @@ internal class WordWrapManager TextFormatter.Format ( TextModel.ToString (line), width, - TextAlignment.Left, + Alignment.Start, true, preserveTrailingSpaces, tabWidth @@ -4161,7 +4161,10 @@ public class TextView : View } else { - PositionCursor (); + if (IsInitialized) + { + PositionCursor (); + } } OnUnwrappedCursorPosition (); diff --git a/Terminal.Gui/Views/Wizard/Wizard.cs b/Terminal.Gui/Views/Wizard/Wizard.cs index 205739d44..deeda162d 100644 --- a/Terminal.Gui/Views/Wizard/Wizard.cs +++ b/Terminal.Gui/Views/Wizard/Wizard.cs @@ -54,23 +54,6 @@ public class Wizard : Dialog private readonly LinkedList _steps = new (); private WizardStep _currentStep; private bool _finishedPressed; - - ///// - ///// The title of the Wizard, shown at the top of the Wizard with " - currentStep.Title" appended. - ///// - ///// - ///// The Title is only displayed when the is set to false. - ///// - //public new string Title { - // get { - // // The base (Dialog) Title holds the full title ("Wizard Title - Step Title") - // return base.Title; - // } - // set { - // wizardTitle = value; - // base.Title = $"{wizardTitle}{(steps.Count > 0 && currentStep is { } ? " - " + currentStep.Title : string.Empty)}"; - // } - //} private string _wizardTitle = string.Empty; /// @@ -83,9 +66,9 @@ public class Wizard : Dialog /// public Wizard () { - // Using Justify causes the Back and Next buttons to be hard justified against - // the left and right edge - ButtonAlignment = ButtonAlignments.Justify; + // TODO: LastEndRestStart will enable a "Quit" button to always appear at the far left + ButtonAlignment = Alignment.Start; + ButtonAlignmentModes |= AlignmentModes.IgnoreFirstOrLast; BorderStyle = LineStyle.Double; //// Add a horiz separator diff --git a/Terminal.sln.DotSettings b/Terminal.sln.DotSettings index ca15b5583..8ca3af1c0 100644 --- a/Terminal.sln.DotSettings +++ b/Terminal.sln.DotSettings @@ -391,6 +391,7 @@ <Policy><Descriptor Staticness="Instance" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Instance fields (not private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /></Policy> <Policy><Descriptor Staticness="Static" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Static fields (not private)"><ElementKinds><Kind Name="FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /></Policy> <Policy><Descriptor Staticness="Static" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Static readonly fields (not private)"><ElementKinds><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /></Policy> + PushToShowHints True True True @@ -439,5 +440,6 @@ Concurrency Issue (?<=\W|^)(?<TAG>CONCURRENCY:)(\W|$)(.*) Warning + True True diff --git a/UICatalog/Scenarios/AllViewsTester.cs b/UICatalog/Scenarios/AllViewsTester.cs index faea44377..fd76ed158 100644 --- a/UICatalog/Scenarios/AllViewsTester.cs +++ b/UICatalog/Scenarios/AllViewsTester.cs @@ -42,14 +42,14 @@ public class AllViewsTester : Scenario private string _demoText = "This, that, and the other thing."; private TextView _demoTextView; - public override void Init () + public override void Main () { // Don't create a sub-win (Scenario.Win); just use Application.Top Application.Init (); - ConfigurationManager.Themes.Theme = Theme; ConfigurationManager.Apply (); - Top = new (); - Top.ColorScheme = Colors.ColorSchemes [TopLevelColorScheme]; + + var app = new Window (); + app.ColorScheme = Colors.ColorSchemes [TopLevelColorScheme]; var statusBar = new StatusBar ( new StatusItem [] @@ -66,7 +66,7 @@ public class AllViewsTester : Scenario { View.Diagnostics ^= ViewDiagnosticFlags.Ruler; - Top.SetNeedsDisplay (); + app.SetNeedsDisplay (); } ), new ( @@ -76,12 +76,12 @@ public class AllViewsTester : Scenario { View.Diagnostics ^= ViewDiagnosticFlags.Padding; - Top.SetNeedsDisplay (); + app.SetNeedsDisplay (); } ) } ); - Top.Add (statusBar); + app.Add (statusBar); _viewClasses = GetAllViewClassesCollection () .OrderBy (t => t.Name) @@ -145,7 +145,7 @@ public class AllViewsTester : Scenario { X = 0, Y = 0, - Height = Dim.Auto (), + Height = Dim.Auto (), Width = Dim.Auto (), Title = "Location (Pos)" }; @@ -279,7 +279,7 @@ public class AllViewsTester : Scenario }; _orientation.SelectedItemChanged += (s, selected) => { - if (_curView?.GetType ().GetProperty ("Orientation") is {} prop) + if (_curView?.GetType ().GetProperty ("Orientation") is { } prop) { prop.GetSetMethod ()?.Invoke (_curView, new object [] { _orientation.SelectedItem }); } @@ -312,9 +312,13 @@ public class AllViewsTester : Scenario ColorScheme = Colors.ColorSchemes ["Dialog"] }; - Top.Add (_leftPane, _settingsPane, _hostPane); + app.Add (_leftPane, _settingsPane, _hostPane); _curView = CreateClass (_viewClasses.First ().Value); + + Application.Run (app); + app.Dispose (); + Application.Shutdown (); } // TODO: Add Command.HotKey handler (pop a message box?) @@ -389,9 +393,9 @@ public class AllViewsTester : Scenario } // If the view supports a Title property, set it so we have something to look at - if (view?.GetType ().GetProperty ("Orientation") is {} prop) + if (view?.GetType ().GetProperty ("Orientation") is { } prop) { - _orientation.SelectedItem = (int)prop.GetGetMethod()!.Invoke (view, null)!; + _orientation.SelectedItem = (int)prop.GetGetMethod ()!.Invoke (view, null)!; _orientation.Enabled = true; } else diff --git a/UICatalog/Scenarios/BasicColors.cs b/UICatalog/Scenarios/BasicColors.cs index 1780d7cbb..b1b75c54f 100644 --- a/UICatalog/Scenarios/BasicColors.cs +++ b/UICatalog/Scenarios/BasicColors.cs @@ -32,7 +32,7 @@ public class BasicColors : Scenario Y = 0, Width = 1, Height = 13, - VerticalTextAlignment = VerticalTextAlignment.Bottom, + VerticalTextAlignment = Alignment.End, ColorScheme = new ColorScheme { Normal = attr }, Text = bg.ToString (), TextDirection = TextDirection.TopBottom_LeftRight @@ -45,7 +45,7 @@ public class BasicColors : Scenario Y = y, Width = 13, Height = 1, - TextAlignment = TextAlignment.Right, + TextAlignment = Alignment.End, ColorScheme = new ColorScheme { Normal = attr }, Text = bg.ToString () }; diff --git a/UICatalog/Scenarios/Buttons.cs b/UICatalog/Scenarios/Buttons.cs index 6ba57e02f..d8e612ae5 100644 --- a/UICatalog/Scenarios/Buttons.cs +++ b/UICatalog/Scenarios/Buttons.cs @@ -218,7 +218,7 @@ public class Buttons : Scenario X = 4, Y = Pos.Bottom (label) + 1, SelectedItem = 2, - RadioLabels = new [] { "Left", "Right", "Centered", "Justified" } + RadioLabels = new [] { "Start", "End", "Center", "Fill" } }; main.Add (radioGroup); @@ -287,39 +287,39 @@ public class Buttons : Scenario switch (args.SelectedItem) { case 0: - moveBtn.TextAlignment = TextAlignment.Left; - sizeBtn.TextAlignment = TextAlignment.Left; - moveBtnA.TextAlignment = TextAlignment.Left; - sizeBtnA.TextAlignment = TextAlignment.Left; - moveHotKeyBtn.TextAlignment = TextAlignment.Left; - moveUnicodeHotKeyBtn.TextAlignment = TextAlignment.Left; + moveBtn.TextAlignment = Alignment.Start; + sizeBtn.TextAlignment = Alignment.Start; + moveBtnA.TextAlignment = Alignment.Start; + sizeBtnA.TextAlignment = Alignment.Start; + moveHotKeyBtn.TextAlignment = Alignment.Start; + moveUnicodeHotKeyBtn.TextAlignment = Alignment.Start; break; case 1: - moveBtn.TextAlignment = TextAlignment.Right; - sizeBtn.TextAlignment = TextAlignment.Right; - moveBtnA.TextAlignment = TextAlignment.Right; - sizeBtnA.TextAlignment = TextAlignment.Right; - moveHotKeyBtn.TextAlignment = TextAlignment.Right; - moveUnicodeHotKeyBtn.TextAlignment = TextAlignment.Right; + moveBtn.TextAlignment = Alignment.End; + sizeBtn.TextAlignment = Alignment.End; + moveBtnA.TextAlignment = Alignment.End; + sizeBtnA.TextAlignment = Alignment.End; + moveHotKeyBtn.TextAlignment = Alignment.End; + moveUnicodeHotKeyBtn.TextAlignment = Alignment.End; break; case 2: - moveBtn.TextAlignment = TextAlignment.Centered; - sizeBtn.TextAlignment = TextAlignment.Centered; - moveBtnA.TextAlignment = TextAlignment.Centered; - sizeBtnA.TextAlignment = TextAlignment.Centered; - moveHotKeyBtn.TextAlignment = TextAlignment.Centered; - moveUnicodeHotKeyBtn.TextAlignment = TextAlignment.Centered; + moveBtn.TextAlignment = Alignment.Center; + sizeBtn.TextAlignment = Alignment.Center; + moveBtnA.TextAlignment = Alignment.Center; + sizeBtnA.TextAlignment = Alignment.Center; + moveHotKeyBtn.TextAlignment = Alignment.Center; + moveUnicodeHotKeyBtn.TextAlignment = Alignment.Center; break; case 3: - moveBtn.TextAlignment = TextAlignment.Justified; - sizeBtn.TextAlignment = TextAlignment.Justified; - moveBtnA.TextAlignment = TextAlignment.Justified; - sizeBtnA.TextAlignment = TextAlignment.Justified; - moveHotKeyBtn.TextAlignment = TextAlignment.Justified; - moveUnicodeHotKeyBtn.TextAlignment = TextAlignment.Justified; + moveBtn.TextAlignment = Alignment.Fill; + sizeBtn.TextAlignment = Alignment.Fill; + moveBtnA.TextAlignment = Alignment.Fill; + sizeBtnA.TextAlignment = Alignment.Fill; + moveHotKeyBtn.TextAlignment = Alignment.Fill; + moveUnicodeHotKeyBtn.TextAlignment = Alignment.Fill; break; } @@ -418,9 +418,8 @@ public class Buttons : Scenario throw new InvalidOperationException ("T must be a numeric type that supports addition and subtraction."); } - // TODO: Use Dim.Auto for the Width and Height - Height = 1; - Width = Dim.Func (() => Digits + 2); // button + 3 for number + button + Width = Dim.Auto (DimAutoStyle.Content); //Dim.Function (() => Digits + 2); // button + 3 for number + button + Height = Dim.Auto (DimAutoStyle.Content); _down = new () { @@ -440,7 +439,7 @@ public class Buttons : Scenario Y = Pos.Top (_down), Width = Dim.Func (() => Digits), Height = 1, - TextAlignment = TextAlignment.Centered, + TextAlignment = Alignment.Center, CanFocus = true }; diff --git a/UICatalog/Scenarios/CharacterMap.cs b/UICatalog/Scenarios/CharacterMap.cs index 0a32490de..9616c30d5 100644 --- a/UICatalog/Scenarios/CharacterMap.cs +++ b/UICatalog/Scenarios/CharacterMap.cs @@ -958,7 +958,7 @@ internal class CharMap : View Y = 1, Width = Dim.Fill (), Height = Dim.Fill (1), - TextAlignment = TextAlignment.Centered + TextAlignment = Alignment.Center }; var spinner = new SpinnerView { X = Pos.Center (), Y = Pos.Center (), Style = new Aesthetic () }; spinner.AutoSpin = true; diff --git a/UICatalog/Scenarios/CollectionNavigatorTester.cs b/UICatalog/Scenarios/CollectionNavigatorTester.cs index ef31ac6a7..4797e4c22 100644 --- a/UICatalog/Scenarios/CollectionNavigatorTester.cs +++ b/UICatalog/Scenarios/CollectionNavigatorTester.cs @@ -142,7 +142,7 @@ public class CollectionNavigatorTester : Scenario var label = new Label { Text = "ListView", - TextAlignment = TextAlignment.Centered, + TextAlignment = Alignment.Center, X = 0, Y = 1, // for menu Width = Dim.Percent (50), @@ -171,7 +171,7 @@ public class CollectionNavigatorTester : Scenario var label = new Label { Text = "TreeView", - TextAlignment = TextAlignment.Centered, + TextAlignment = Alignment.Center, X = Pos.Right (_listView) + 2, Y = 1, // for menu Width = Dim.Percent (50), diff --git a/UICatalog/Scenarios/ColorPicker.cs b/UICatalog/Scenarios/ColorPicker.cs index fe2b08fad..5bc84b70d 100644 --- a/UICatalog/Scenarios/ColorPicker.cs +++ b/UICatalog/Scenarios/ColorPicker.cs @@ -69,8 +69,8 @@ public class ColorPickers : Scenario { Title = "Color Sample", Text = "Lorem Ipsum", - TextAlignment = TextAlignment.Centered, - VerticalTextAlignment = VerticalTextAlignment.Middle, + TextAlignment = Alignment.Center, + VerticalTextAlignment = Alignment.Center, BorderStyle = LineStyle.Heavy, X = Pos.Center (), Y = Pos.Center (), diff --git a/UICatalog/Scenarios/ComputedLayout.cs b/UICatalog/Scenarios/ComputedLayout.cs index 5e6eb5cd2..a28e866f8 100644 --- a/UICatalog/Scenarios/ComputedLayout.cs +++ b/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; @@ -85,12 +86,12 @@ public class ComputedLayout : Scenario var i = 1; var txt = "Resize the terminal to see computed layout in action."; List