diff --git a/Terminal.Gui/Application.cs b/Terminal.Gui/Application.cs index ed140fed6d..15b2dc6663 100644 --- a/Terminal.Gui/Application.cs +++ b/Terminal.Gui/Application.cs @@ -109,7 +109,7 @@ static List GetSupportedCultures () /// /// The to use. If neither or are specified the default driver for the platform will be used. /// The short name (e.g. "net", "windows", "ansi", "fake", or "curses") of the to use. If neither or are specified the default driver for the platform will be used. - public static void Init (ConsoleDriver driver = null, string driverName = null) => InternalInit (Toplevel.Create, driver, driverName); + public static void Init (ConsoleDriver driver = null, string driverName = null) => InternalInit (() => new Toplevel (), driver, driverName); internal static bool _initialized = false; internal static int _mainThreadId = -1; @@ -194,6 +194,10 @@ internal static void InternalInit (Func topLevelFactory, ConsoleDriver Top = topLevelFactory (); Current = Top; + + // Ensure Top's layout is up to date. + Current.SetRelativeLayout (Driver.Bounds); + _cachedSupportedCultures = GetSupportedCultures (); _mainThreadId = Thread.CurrentThread.ManagedThreadId; _initialized = true; @@ -397,9 +401,9 @@ public static RunState Begin (Toplevel Toplevel) MoveCurrent (Current); } - if (Toplevel.LayoutStyle == LayoutStyle.Computed) { - Toplevel.SetRelativeLayout (new Rect (0, 0, Driver.Cols, Driver.Rows)); - } + //if (Toplevel.LayoutStyle == LayoutStyle.Computed) { + Toplevel.SetRelativeLayout (Driver.Bounds); + //} Toplevel.LayoutSubviews (); Toplevel.PositionToplevels (); Toplevel.FocusFirst (); @@ -443,7 +447,7 @@ public static RunState Begin (Toplevel Toplevel) /// platform will be used (, , or ). /// Must be if has already been called. /// - public static void Run (Func errorHandler = null, ConsoleDriver driver = null) where T : Toplevel, new () + public static void Run (Func errorHandler = null, ConsoleDriver driver = null) where T : Toplevel, new() { if (_initialized) { if (Driver != null) { @@ -710,7 +714,7 @@ public static void RunIteration (ref RunState state, ref bool firstIteration) && (Driver.Cols != state.Toplevel.Frame.Width || Driver.Rows != state.Toplevel.Frame.Height) && (state.Toplevel.NeedsDisplay || state.Toplevel.SubViewNeedsDisplay || state.Toplevel.LayoutNeeded)) { - state.Toplevel.Clear (new Rect (Point.Empty, new Size (Driver.Cols, Driver.Rows))); + state.Toplevel.Clear (Driver.Bounds); } if (state.Toplevel.NeedsDisplay || @@ -1334,7 +1338,8 @@ bool FrameHandledMouseEvent (Frame frame) /// /// Alternative key to navigate forwards through views. Ctrl+Tab is the primary key. /// - [SerializableConfigurationProperty (Scope = typeof (SettingsScope))] [JsonConverter (typeof (KeyJsonConverter))] + [SerializableConfigurationProperty (Scope = typeof (SettingsScope))] + [JsonConverter (typeof (KeyJsonConverter))] public static Key AlternateForwardKey { get => _alternateForwardKey; set { @@ -1358,7 +1363,8 @@ static void OnAlternateForwardKeyChanged (KeyChangedEventArgs e) /// /// Alternative key to navigate backwards through views. Shift+Ctrl+Tab is the primary key. /// - [SerializableConfigurationProperty (Scope = typeof (SettingsScope))] [JsonConverter (typeof (KeyJsonConverter))] + [SerializableConfigurationProperty (Scope = typeof (SettingsScope))] + [JsonConverter (typeof (KeyJsonConverter))] public static Key AlternateBackwardKey { get => _alternateBackwardKey; set { @@ -1382,7 +1388,8 @@ static void OnAlternateBackwardKeyChanged (KeyChangedEventArgs oldKey) /// /// Gets or sets the key to quit the application. /// - [SerializableConfigurationProperty (Scope = typeof (SettingsScope))] [JsonConverter (typeof (KeyJsonConverter))] + [SerializableConfigurationProperty (Scope = typeof (SettingsScope))] + [JsonConverter (typeof (KeyJsonConverter))] public static Key QuitKey { get => _quitKey; set { diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs index 4bd0143e16..08cc4a9c78 100644 --- a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs @@ -62,7 +62,7 @@ public virtual int Cols { get => _cols; internal set { _cols = value; - ClearContents(); + ClearContents (); } } @@ -73,7 +73,7 @@ public virtual int Rows { get => _rows; internal set { _rows = value; - ClearContents(); + ClearContents (); } } @@ -552,6 +552,11 @@ public enum DiagnosticFlags : uint { /// public static DiagnosticFlags Diagnostics { get; set; } + /// + /// Gets the dimensions of the terminal. + /// + public Rect Bounds => new Rect (0, 0, Cols, Rows); + /// /// Suspends the application (e.g. on Linux via SIGTSTP) and upon resume, resets the console driver. /// diff --git a/Terminal.Gui/Text/Autocomplete/AppendAutocomplete.cs b/Terminal.Gui/Text/Autocomplete/AppendAutocomplete.cs index 9f8392d676..c20bed711b 100644 --- a/Terminal.Gui/Text/Autocomplete/AppendAutocomplete.cs +++ b/Terminal.Gui/Text/Autocomplete/AppendAutocomplete.cs @@ -83,6 +83,7 @@ public override bool ProcessKey (Key a) public override void GenerateSuggestions (AutocompleteContext context) { if (_suspendSuggestions) { + _suspendSuggestions = false; return; } base.GenerateSuggestions (context); diff --git a/Terminal.Gui/Text/Autocomplete/PopupAutocomplete.cs b/Terminal.Gui/Text/Autocomplete/PopupAutocomplete.cs index d78767a469..a805b6cf3a 100644 --- a/Terminal.Gui/Text/Autocomplete/PopupAutocomplete.cs +++ b/Terminal.Gui/Text/Autocomplete/PopupAutocomplete.cs @@ -23,17 +23,6 @@ public Popup (PopupAutocomplete autocomplete) WantMousePositionReports = true; } - public override Rect Frame { - get => base.Frame; - set { - base.Frame = value; - X = value.X; - Y = value.Y; - Width = value.Width; - Height = value.Height; - } - } - public override void OnDrawContent (Rect contentArea) { if (autocomplete.LastPopupPos == null) { diff --git a/Terminal.Gui/Text/TextFormatter.cs b/Terminal.Gui/Text/TextFormatter.cs index 1045322708..7ada30a324 100644 --- a/Terminal.Gui/Text/TextFormatter.cs +++ b/Terminal.Gui/Text/TextFormatter.cs @@ -50,23 +50,50 @@ public enum VerticalTextAlignment { Justified } - /// TextDirection [H] = Horizontal [V] = Vertical - /// ============= - /// LeftRight_TopBottom [H] Normal - /// TopBottom_LeftRight [V] Normal - /// - /// RightLeft_TopBottom [H] Invert Text - /// TopBottom_RightLeft [V] Invert Lines - /// - /// LeftRight_BottomTop [H] Invert Lines - /// BottomTop_LeftRight [V] Invert Text - /// - /// RightLeft_BottomTop [H] Invert Text + Invert Lines - /// BottomTop_RightLeft [V] Invert Text + Invert Lines - /// /// /// Text direction enumeration, controls how text is displayed. /// + /// + /// TextDirection [H] = Horizontal [V] = Vertical + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + ///
TextDirectionDescription
LeftRight_TopBottom [H]Normal
TopBottom_LeftRight [V]Normal
RightLeft_TopBottom [H]Invert Text
TopBottom_RightLeft [V]Invert Lines
LeftRight_BottomTop [H]Invert Lines
BottomTop_LeftRight [V]Invert Text
RightLeft_BottomTop [H]Invert Text + Invert Lines
BottomTop_RightLeft [V]Invert Text + Invert Lines
+ ///
public enum TextDirection { /// /// Normal horizontal direction. @@ -1075,7 +1102,7 @@ public virtual string Text { _text = EnableNeedsFormat (value); if ((AutoSize && Alignment != TextAlignment.Justified && VerticalAlignment != VerticalTextAlignment.Justified) || (textWasNull && Size.IsEmpty)) { - Size = CalcRect (0, 0, _text, _textDirection, TabWidth).Size; + Size = CalcRect (0, 0, _text, Direction, TabWidth).Size; } //if (_text != null && _text.GetRuneCount () > 0 && (Size.Width == 0 || Size.Height == 0 || Size.Width != _text.GetColumns ())) { @@ -1087,20 +1114,22 @@ public virtual string Text { } /// - /// Used by to resize the view's with the . - /// Setting to true only work if the and are null or - /// values and doesn't work with layout, - /// to avoid breaking the and settings. + /// Gets or sets whether the should be automatically changed to fit the . /// /// - /// Auto size is ignored if the and are used. + /// + /// Used by to resize the view's to fit . + /// + /// + /// AutoSize is ignored if and are used. + /// /// public bool AutoSize { get => _autoSize; set { _autoSize = EnableNeedsFormat (value); if (_autoSize && Alignment != TextAlignment.Justified && VerticalAlignment != VerticalTextAlignment.Justified) { - Size = CalcRect (0, 0, Text, _textDirection, TabWidth).Size; + Size = CalcRect (0, 0, _text, Direction, TabWidth).Size; } } } @@ -1140,7 +1169,12 @@ public VerticalTextAlignment VerticalAlignment { /// The text vertical alignment. public TextDirection Direction { get => _textDirection; - set => _textDirection = EnableNeedsFormat (value); + set { + _textDirection = EnableNeedsFormat (value); + if (AutoSize && Alignment != TextAlignment.Justified && VerticalAlignment != VerticalTextAlignment.Justified) { + Size = CalcRect (0, 0, Text, Direction, TabWidth).Size; + } + } } /// @@ -1204,7 +1238,7 @@ public static bool IsTopToBottom (TextDirection textDirection) } /// - /// Allows word wrap the to fit the available container width. + /// Gets or sets whether word wrap will be used to fit to . /// public bool WordWrap { get => _wordWrap; @@ -1212,16 +1246,16 @@ public bool WordWrap { } /// - /// Gets or sets the size of the area the text will be constrained to when formatted. + /// Gets or sets the size will be constrained to when formatted. /// /// - /// Does not return the size the formatted text; just the value that was set. + /// Does not return the size of the formatted text but the size that will be used to constrain the text when formatted. /// public Size Size { get => _size; set { if (AutoSize && Alignment != TextAlignment.Justified && VerticalAlignment != VerticalTextAlignment.Justified) { - _size = EnableNeedsFormat (CalcRect (0, 0, Text, _textDirection, TabWidth).Size); + _size = EnableNeedsFormat (CalcRect (0, 0, Text, Direction, TabWidth).Size); } else { _size = EnableNeedsFormat (value); } @@ -1291,7 +1325,7 @@ public List Lines { NeedsFormat = false; return _lines; } - + if (NeedsFormat) { var shown_text = _text; if (FindHotKey (_text, HotKeySpecifier, true, out _hotKeyPos, out var newHotKey)) { @@ -1300,7 +1334,7 @@ public List Lines { shown_text = ReplaceHotKeyWithTag (shown_text, _hotKeyPos); } - if (IsVerticalDirection (_textDirection)) { + if (IsVerticalDirection (Direction)) { var colsWidth = GetSumMaxCharWidth (shown_text, 0, 1, TabWidth); _lines = Format (shown_text, Size.Height, VerticalAlignment == VerticalTextAlignment.Justified, Size.Width > colsWidth && WordWrap, PreserveTrailingSpaces, TabWidth, Direction, MultiLine); @@ -1325,11 +1359,16 @@ public List Lines { } /// - /// Gets or sets whether the needs to format the text when is called. - /// If it is false when Draw is called, the Draw call will be faster. + /// Gets or sets whether the needs to format the text. /// /// /// + /// If false when Draw is called, the Draw call will be faster. + /// + /// + /// Used by + /// + /// /// This is set to true when the properties of are set. /// /// @@ -1400,7 +1439,7 @@ public void Draw (Rect bounds, Attribute normalColor, Attribute hotColor, Rect c // Use "Lines" to ensure a Format (don't use "lines")) var linesFormated = Lines; - switch (_textDirection) { + switch (Direction) { case TextDirection.TopBottom_RightLeft: case TextDirection.LeftRight_BottomTop: case TextDirection.RightLeft_BottomTop: @@ -1409,7 +1448,7 @@ public void Draw (Rect bounds, Attribute normalColor, Attribute hotColor, Rect c break; } - var isVertical = IsVerticalDirection (_textDirection); + var isVertical = IsVerticalDirection (Direction); var maxBounds = bounds; if (driver != null) { maxBounds = containerBounds == default @@ -1441,7 +1480,7 @@ public void Draw (Rect bounds, Attribute normalColor, Attribute hotColor, Rect c var runes = _lines [line].ToRunes (); - switch (_textDirection) { + switch (Direction) { case TextDirection.RightLeft_BottomTop: case TextDirection.RightLeft_TopBottom: case TextDirection.BottomTop_LeftRight: @@ -1454,7 +1493,7 @@ public void Draw (Rect bounds, Attribute normalColor, Attribute hotColor, Rect c int x, y; // Horizontal Alignment - if (_textAlignment == TextAlignment.Right || (_textAlignment == TextAlignment.Justified && !IsLeftToRight (_textDirection))) { + if (_textAlignment == TextAlignment.Right || (_textAlignment == TextAlignment.Justified && !IsLeftToRight (Direction))) { if (isVertical) { var runesWidth = GetSumMaxCharWidth (Lines, line, TabWidth); x = bounds.Right - runesWidth; @@ -1487,7 +1526,7 @@ public void Draw (Rect bounds, Attribute normalColor, Attribute hotColor, Rect c } // Vertical Alignment - if (_textVerticalAlignment == VerticalTextAlignment.Bottom || (_textVerticalAlignment == VerticalTextAlignment.Justified && !IsTopToBottom (_textDirection))) { + if (_textVerticalAlignment == VerticalTextAlignment.Bottom || (_textVerticalAlignment == VerticalTextAlignment.Justified && !IsTopToBottom (Direction))) { if (isVertical) { y = bounds.Bottom - runes.Length; } else { diff --git a/Terminal.Gui/View/Frame.cs b/Terminal.Gui/View/Frame.cs index 95220f96e9..484ce3cd10 100644 --- a/Terminal.Gui/View/Frame.cs +++ b/Terminal.Gui/View/Frame.cs @@ -58,7 +58,7 @@ public override void BoundsToScreen (int col, int row, out int rcol, out int rro /// public override Rect FrameToScreen () { - // Frames are *Children* of a View, not SubViews. Thus View.FramToScreen will not work. + // Frames are *Children* of a View, not SubViews. Thus View.FrameToScreen will not work. // To get the screen-relative coordinates of a Frame, we need to know who // the Parent is var ret = Parent?.Frame ?? Frame; diff --git a/Terminal.Gui/View/Layout/PosDim.cs b/Terminal.Gui/View/Layout/PosDim.cs index 19c0859e8a..92a0432230 100644 --- a/Terminal.Gui/View/Layout/PosDim.cs +++ b/Terminal.Gui/View/Layout/PosDim.cs @@ -1,161 +1,165 @@ using System; -using static Terminal.Gui.Dim; namespace Terminal.Gui; /// -/// Describes the position of a which can be an absolute value, a percentage, centered, or +/// Describes the position of a which can be an absolute value, a percentage, centered, or /// relative to the ending dimension. Integer values are implicitly convertible to /// an absolute . These objects are created using the static methods Percent, -/// AnchorEnd, and Center. The objects can be combined with the addition and +/// AnchorEnd, and Center. The objects can be combined with the addition and /// subtraction operators. /// /// -/// -/// Use the objects on the X or Y properties of a view to control the position. -/// -/// -/// These can be used to set the absolute position, when merely assigning an -/// integer value (via the implicit integer to conversion), and they can be combined -/// to produce more useful layouts, like: Pos.Center - 3, which would shift the position -/// of the 3 characters to the left after centering for example. -/// -/// -/// Reference coordinates of another view by using the methods Left(View), Right(View), Bottom(View), Top(View). The X(View) and Y(View) are -/// aliases to Left(View) and Top(View) respectively. -/// -/// -/// -/// -/// Pos Object -/// Description -/// -/// -/// -/// -/// Creates a object that computes the position by executing the provided function. The function will be called every time the position is needed. -/// -/// -/// -/// -/// -/// Creates a object that is a percentage of the width or height of the SuperView. -/// -/// -/// -/// -/// -/// Creates a object that is anchored to the end (right side or bottom) of the dimension, -/// useful to flush the layout from the right or bottom. -/// -/// -/// -/// -/// -/// Creates a object that can be used to center the . -/// -/// -/// -/// -/// -/// Creates a object that is an absolute position based on the specified integer value. -/// -/// -/// -/// -/// -/// Creates a object that tracks the Left (X) position of the specified . -/// -/// -/// -/// -/// -/// Creates a object that tracks the Left (X) position of the specified . -/// -/// -/// -/// -/// -/// Creates a object that tracks the Top (Y) position of the specified . -/// -/// -/// -/// -/// -/// Creates a object that tracks the Top (Y) position of the specified . -/// -/// -/// -/// -/// -/// Creates a object that tracks the Right (X+Width) coordinate of the specified . -/// -/// -/// -/// -/// -/// Creates a object that tracks the Bottom (Y+Height) coordinate of the specified -/// -/// -/// -/// -/// +/// +/// Use the objects on the X or Y properties of a view to control the position. +/// +/// +/// These can be used to set the absolute position, when merely assigning an +/// integer value (via the implicit integer to conversion), and they can be combined +/// to produce more useful layouts, like: Pos.Center - 3, which would shift the position +/// of the 3 characters to the left after centering for example. +/// +/// +/// Reference coordinates of another view by using the methods Left(View), Right(View), Bottom(View), Top(View). +/// The X(View) and Y(View) are +/// aliases to Left(View) and Top(View) respectively. +/// +/// +/// +/// +/// Pos Object +/// Description +/// +/// +/// +/// +/// +/// +/// Creates a object that computes the position by executing the provided +/// function. The function will be called every time the position is needed. +/// +/// +/// +/// +/// +/// +/// +/// Creates a object that is a percentage of the width or height of the +/// SuperView. +/// +/// +/// +/// +/// +/// +/// +/// Creates a object that is anchored to the end (right side or bottom) +/// of the dimension, +/// useful to flush the layout from the right or bottom. +/// +/// +/// +/// +/// +/// +/// +/// Creates a object that can be used to center the . +/// +/// +/// +/// +/// +/// +/// +/// Creates a object that is an absolute position based on the specified +/// integer value. +/// +/// +/// +/// +/// +/// +/// +/// Creates a object that tracks the Left (X) position of the specified +/// . +/// +/// +/// +/// +/// +/// +/// +/// Creates a object that tracks the Left (X) position of the specified +/// . +/// +/// +/// +/// +/// +/// +/// +/// Creates a object that tracks the Top (Y) position of the specified +/// . +/// +/// +/// +/// +/// +/// +/// +/// Creates a object that tracks the Top (Y) position of the specified +/// . +/// +/// +/// +/// +/// +/// +/// +/// Creates a object that tracks the Right (X+Width) coordinate of the +/// specified . +/// +/// +/// +/// +/// +/// +/// +/// Creates a object that tracks the Bottom (Y+Height) coordinate of the +/// specified +/// +/// +/// +/// +/// /// public class Pos { internal virtual int Anchor (int width) => 0; /// - /// Creates a object that computes the position by executing the provided function. The function will be called every time the position is needed. + /// Creates a object that computes the position by executing the provided function. The function will be + /// called every time the position is needed. /// /// The function to be executed. /// The returned from the function. public static Pos Function (Func function) => new PosFunc (function); - internal class PosFactor : Pos { - readonly float _factor; - - public PosFactor (float n) => _factor = n; - - internal override int Anchor (int width) => (int)(width * _factor); - - public override string ToString () => $"Factor({_factor})"; - - public override int GetHashCode () => _factor.GetHashCode (); - - public override bool Equals (object other) => other is PosFactor f && f._factor == _factor; - } - - // Helper class to provide dynamic value by the execution of a function that returns an integer. - internal class PosFunc : Pos { - readonly Func _function; - - public PosFunc (Func n) => _function = n; - - internal override int Anchor (int width) => _function (); - - public override string ToString () => $"PosFunc({_function ()})"; - - public override int GetHashCode () => _function.GetHashCode (); - - public override bool Equals (object other) => other is PosFunc f && f._function () == _function (); - } - /// /// Creates a percentage object /// /// The percent object. /// A value between 0 and 100 representing the percentage. /// - /// This creates a that is centered horizontally, is 50% of the way down, + /// This creates a that is centered horizontally, is 50% of the way down, /// is 30% the height, and is 80% the width of the it added to. /// - /// var textView = new TextView () { - /// X = Pos.Center (), - /// Y = Pos.Percent (50), - /// Width = Dim.Percent (80), - /// Height = Dim.Percent (30), - /// }; - /// + /// var textView = new TextView () { + /// X = Pos.Center (), + /// Y = Pos.Percent (50), + /// Width = Dim.Percent (80), + /// Height = Dim.Percent (30), + /// }; + /// /// public static Pos Percent (float n) { @@ -167,7 +171,7 @@ public static Pos Percent (float n) } /// - /// Creates a object that is anchored to the end (right side or bottom) of the dimension, + /// Creates a object that is anchored to the end (right side or bottom) of the dimension, /// useful to flush the layout from the right or bottom. /// /// The object anchored to the end (the bottom or the right side). @@ -183,63 +187,30 @@ public static Pos Percent (float n) public static Pos AnchorEnd (int offset = 0) { if (offset < 0) { - throw new ArgumentException (@"Must be positive", nameof(offset)); + throw new ArgumentException (@"Must be positive", nameof (offset)); } return new PosAnchorEnd (offset); } - internal class PosAnchorEnd : Pos { - readonly int _offset; - - public PosAnchorEnd (int offset) => _offset = offset; - - internal override int Anchor (int width) => width - _offset; - - public override string ToString () => $"AnchorEnd({_offset})"; - - public override int GetHashCode () => _offset.GetHashCode (); - - public override bool Equals (object other) => other is PosAnchorEnd anchorEnd && anchorEnd._offset == _offset; - } - /// /// Creates a object that can be used to center the . /// /// The center Pos. /// - /// This creates a that is centered horizontally, is 50% of the way down, + /// This creates a that is centered horizontally, is 50% of the way down, /// is 30% the height, and is 80% the width of the it added to. /// - /// var textView = new TextView () { - /// X = Pos.Center (), - /// Y = Pos.Percent (50), - /// Width = Dim.Percent (80), - /// Height = Dim.Percent (30), - /// }; - /// + /// var textView = new TextView () { + /// X = Pos.Center (), + /// Y = Pos.Percent (50), + /// Width = Dim.Percent (80), + /// Height = Dim.Percent (30), + /// }; + /// /// public static Pos Center () => new PosCenter (); - internal class PosAbsolute : Pos { - readonly int _n; - public PosAbsolute (int n) => _n = n; - - public override string ToString () => $"Absolute({_n})"; - - internal override int Anchor (int width) => _n; - - public override int GetHashCode () => _n.GetHashCode (); - - public override bool Equals (object other) => other is PosAbsolute abs && abs._n == _n; - } - - internal class PosCenter : Pos { - internal override int Anchor (int width) => width / 2; - - public override string ToString () => "Center"; - } - /// /// Creates a object that is an absolute position based on the specified integer value. /// @@ -247,31 +218,6 @@ internal class PosCenter : Pos { /// The value to convert to the . public static Pos At (int n) => new PosAbsolute (n); - internal class PosCombine : Pos { - internal Pos _left, _right; - internal bool _add; - - public PosCombine (bool add, Pos left, Pos right) - { - _left = left; - _right = right; - _add = add; - } - - internal override int Anchor (int width) - { - int la = _left.Anchor (width); - int ra = _right.Anchor (width); - if (_add) { - return la + ra; - } else { - return la - ra; - } - } - - public override string ToString () => $"Combine({_left}{(_add ? '+' : '-')}{_right})"; - } - /// /// Creates an Absolute from the specified integer value. /// @@ -319,9 +265,148 @@ static void SetPosCombine (Pos left, PosCombine newPos) } } + /// + /// Creates a object that tracks the Left (X) position of the specified . + /// + /// The that depends on the other view. + /// The that will be tracked. + public static Pos Left (View view) => new PosView (view, 0); + + /// + /// Creates a object that tracks the Left (X) position of the specified . + /// + /// The that depends on the other view. + /// The that will be tracked. + public static Pos X (View view) => new PosView (view, 0); + + /// + /// Creates a object that tracks the Top (Y) position of the specified . + /// + /// The that depends on the other view. + /// The that will be tracked. + public static Pos Top (View view) => new PosView (view, 1); + + /// + /// Creates a object that tracks the Top (Y) position of the specified . + /// + /// The that depends on the other view. + /// The that will be tracked. + public static Pos Y (View view) => new PosView (view, 1); + + /// + /// Creates a object that tracks the Right (X+Width) coordinate of the specified . + /// + /// The that depends on the other view. + /// The that will be tracked. + public static Pos Right (View view) => new PosView (view, 2); + + /// + /// Creates a object that tracks the Bottom (Y+Height) coordinate of the specified + /// + /// The that depends on the other view. + /// The that will be tracked. + public static Pos Bottom (View view) => new PosView (view, 3); + + /// Serves as the default hash function. + /// A hash code for the current object. + public override int GetHashCode () => Anchor (0).GetHashCode (); + + /// Determines whether the specified object is equal to the current object. + /// The object to compare with the current object. + /// + /// if the specified object is equal to the current object; otherwise, . + /// + public override bool Equals (object other) => other is Pos abs && abs == this; + + internal class PosFactor : Pos { + readonly float _factor; + + public PosFactor (float n) => _factor = n; + + internal override int Anchor (int width) => (int)(width * _factor); + + public override string ToString () => $"Factor({_factor})"; + + public override int GetHashCode () => _factor.GetHashCode (); + + public override bool Equals (object other) => other is PosFactor f && f._factor == _factor; + } + + // Helper class to provide dynamic value by the execution of a function that returns an integer. + internal class PosFunc : Pos { + readonly Func _function; + + public PosFunc (Func n) => _function = n; + + internal override int Anchor (int width) => _function (); + + public override string ToString () => $"PosFunc({_function ()})"; + + public override int GetHashCode () => _function.GetHashCode (); + + public override bool Equals (object other) => other is PosFunc f && f._function () == _function (); + } + + internal class PosAnchorEnd : Pos { + readonly int _offset; + + public PosAnchorEnd (int offset) => _offset = offset; + + internal override int Anchor (int width) => width - _offset; + + public override string ToString () => $"AnchorEnd({_offset})"; + + public override int GetHashCode () => _offset.GetHashCode (); + + public override bool Equals (object other) => other is PosAnchorEnd anchorEnd && anchorEnd._offset == _offset; + } + + internal class PosAbsolute : Pos { + readonly int _n; + public PosAbsolute (int n) => _n = n; + + public override string ToString () => $"Absolute({_n})"; + + internal override int Anchor (int width) => _n; + + public override int GetHashCode () => _n.GetHashCode (); + + public override bool Equals (object other) => other is PosAbsolute abs && abs._n == _n; + } + + internal class PosCenter : Pos { + internal override int Anchor (int width) => width / 2; + + public override string ToString () => "Center"; + } + + internal class PosCombine : Pos { + internal bool _add; + internal Pos _left, _right; + + public PosCombine (bool add, Pos left, Pos right) + { + _left = left; + _right = right; + _add = add; + } + + internal override int Anchor (int width) + { + var la = _left.Anchor (width); + var ra = _right.Anchor (width); + if (_add) { + return la + ra; + } + return la - ra; + } + + public override string ToString () => $"Combine({_left}{(_add ? '+' : '-')}{_right})"; + } + internal class PosView : Pos { + readonly int side; public readonly View Target; - int side; public PosView (View view, int side) { @@ -341,175 +426,140 @@ internal override int Anchor (int width) } } - public override string ToString () - { - string tside; - switch (side) { - case 0: tside = "x"; break; - case 1: tside = "y"; break; - case 2: tside = "right"; break; - case 3: tside = "bottom"; break; - default: tside = "unknown"; break; - } - // Note: We do not checkt `Target` for null here to intentionally throw if so - return $"View(side={tside},target={Target.ToString ()})"; + public override string ToString () + { + string tside; + switch (side) { + case 0: + tside = "x"; + break; + case 1: + tside = "y"; + break; + case 2: + tside = "right"; + break; + case 3: + tside = "bottom"; + break; + default: + tside = "unknown"; + break; + } + if (Target == null) { + throw new NullReferenceException (nameof (Target)); } + return $"View(side={tside},target={Target})"; + } public override int GetHashCode () => Target.GetHashCode (); public override bool Equals (object other) => other is PosView abs && abs.Target == Target; } - - /// - /// Creates a object that tracks the Left (X) position of the specified . - /// - /// The that depends on the other view. - /// The that will be tracked. - public static Pos Left (View view) => new PosView (view, 0); - - /// - /// Creates a object that tracks the Left (X) position of the specified . - /// - /// The that depends on the other view. - /// The that will be tracked. - public static Pos X (View view) => new PosView (view, 0); - - /// - /// Creates a object that tracks the Top (Y) position of the specified . - /// - /// The that depends on the other view. - /// The that will be tracked. - public static Pos Top (View view) => new PosView (view, 1); - - /// - /// Creates a object that tracks the Top (Y) position of the specified . - /// - /// The that depends on the other view. - /// The that will be tracked. - public static Pos Y (View view) => new PosView(view, 1); - - /// - /// Creates a object that tracks the Right (X+Width) coordinate of the specified . - /// - /// The that depends on the other view. - /// The that will be tracked. - public static Pos Right (View view) => new PosView (view, 2); - - /// - /// Creates a object that tracks the Bottom (Y+Height) coordinate of the specified - /// - /// The that depends on the other view. - /// The that will be tracked. - public static Pos Bottom (View view) => new PosView (view, 3); - - /// Serves as the default hash function. - /// A hash code for the current object. - public override int GetHashCode () => Anchor (0).GetHashCode (); - - /// Determines whether the specified object is equal to the current object. - /// The object to compare with the current object. - /// - /// if the specified object is equal to the current object; otherwise, . - public override bool Equals (object other) => other is Pos abs && abs == this; } /// -/// -/// A Dim object describes the dimensions of a . Dim is the type of the and -/// properties of . Dim objects enable Computed Layout (see ) -/// to automatically manage the dimensions of a view. -/// -/// -/// Integer values are implicitly convertible to an absolute . These objects are created using the static methods described below. -/// The objects can be combined with the addition and subtraction operators. -/// +/// +/// A Dim object describes the dimensions of a . Dim is the type of the +/// and +/// properties of . Dim objects enable Computed Layout (see +/// ) +/// to automatically manage the dimensions of a view. +/// +/// +/// Integer values are implicitly convertible to an absolute . These objects are created using the +/// static methods described below. +/// The objects can be combined with the addition and subtraction operators. +/// /// /// -/// -/// -/// -/// Dim Object -/// Description -/// -/// -/// -/// -/// Creates a object that computes the dimension by executing the provided function. The function will be called every time the dimension is needed. -/// -/// -/// -/// -/// -/// Creates a object that is a percentage of the width or height of the SuperView. -/// -/// -/// -/// -/// -/// Creates a object that fills the dimension, leaving the specified number of columns for a margin. -/// -/// -/// -/// -/// -/// Creates a object that tracks the Width of the specified . -/// -/// -/// -/// -/// -/// Creates a object that tracks the Height of the specified . -/// -/// -/// -/// -/// -/// +/// +/// +/// +/// Dim Object +/// Description +/// +/// +/// +/// +/// +/// +/// Creates a object that computes the dimension by executing the +/// provided function. The function will be called every time the dimension is needed. +/// +/// +/// +/// +/// +/// +/// +/// Creates a object that is a percentage of the width or height of the +/// SuperView. +/// +/// +/// +/// +/// +/// +/// +/// Creates a object that fills the dimension, leaving the specified +/// number of columns for a margin. +/// +/// +/// +/// +/// +/// +/// +/// Creates a object that tracks the Width of the specified +/// . +/// +/// +/// +/// +/// +/// +/// +/// Creates a object that tracks the Height of the specified +/// . +/// +/// +/// +/// +/// +/// /// public class Dim { internal virtual int Anchor (int width) => 0; /// /// Creates a function object that computes the dimension by executing the provided function. - /// The function will be called every time the dimension is needed. + /// The function will be called every time the dimension is needed. /// /// The function to be executed. /// The returned from the function. public static Dim Function (Func function) => new DimFunc (function); - // Helper class to provide dynamic value by the execution of a function that returns an integer. - internal class DimFunc : Dim { - readonly Func _function; - - public DimFunc (Func n) => _function = n; - - internal override int Anchor (int width) => _function (); - - public override string ToString () => $"DimFunc({_function ()})"; - - public override int GetHashCode () => _function.GetHashCode (); - - public override bool Equals (object other) => other is DimFunc f && f._function () == _function (); - } - /// /// 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 true the Percent is computed based on the remaining space after the X/Y anchor positions. - /// If false is computed based on the whole original space. + /// + /// If true the Percent is computed based on the remaining space after the X/Y anchor positions. + /// If false is computed based on the whole original space. + /// /// - /// This initializes a that is centered horizontally, is 50% of the way down, + /// This initializes a that is centered horizontally, is 50% of the way down, /// is 30% the height, and is 80% the width of the it added to. /// - /// var textView = new TextView () { - /// X = Pos.Center (), - /// Y = Pos.Percent (50), - /// Width = Dim.Percent (80), - /// Height = Dim.Percent (30), - /// }; - /// + /// var textView = new TextView () { + /// X = Pos.Center (), + /// Y = Pos.Percent (50), + /// Width = Dim.Percent (80), + /// Height = Dim.Percent (30), + /// }; + /// /// public static Dim Percent (float n, bool r = false) { @@ -520,6 +570,102 @@ public static Dim Percent (float n, bool r = false) return new DimFactor (n / 100, r); } + /// + /// Creates a object that fills the dimension, leaving the specified number of columns for a margin. + /// + /// The Fill dimension. + /// Margin to use. + public static Dim Fill (int margin = 0) => new DimFill (margin); + + /// + /// Creates an Absolute from the specified integer value. + /// + /// The Absolute . + /// The value to convert to the pos. + public static implicit operator Dim (int n) => new DimAbsolute (n); + + /// + /// Creates an Absolute from the specified integer value. + /// + /// The Absolute . + /// The value to convert to the . + public static Dim Sized (int n) => new DimAbsolute (n); + + /// + /// Adds a to a , yielding a new . + /// + /// The first to add. + /// The second to add. + /// The that is the sum of the values of left and right. + public static Dim operator + (Dim left, Dim right) + { + if (left is DimAbsolute && right is DimAbsolute) { + return new DimAbsolute (left.Anchor (0) + right.Anchor (0)); + } + var newDim = new DimCombine (true, left, right); + SetDimCombine (left, newDim); + return newDim; + } + + /// + /// Subtracts a from a , yielding a new . + /// + /// The to subtract from (the minuend). + /// The to subtract (the subtrahend). + /// The that is the left minus right. + public static Dim operator - (Dim left, Dim right) + { + if (left is DimAbsolute && right is DimAbsolute) { + return new DimAbsolute (left.Anchor (0) - right.Anchor (0)); + } + var newDim = new DimCombine (false, left, right); + SetDimCombine (left, newDim); + return newDim; + } + + // BUGBUG: newPos is never used. + static void SetDimCombine (Dim left, DimCombine newPos) => (left as DimView)?.Target.SetNeedsLayout (); + + /// + /// Creates a object that tracks the Width of the specified . + /// + /// The width of the other . + /// The view that will be tracked. + public static Dim Width (View view) => new DimView (view, 1); + + /// + /// Creates a object that tracks the Height of the specified . + /// + /// The height of the other . + /// The view that will be tracked. + public static Dim Height (View view) => new DimView (view, 0); + + /// Serves as the default hash function. + /// A hash code for the current object. + public override int GetHashCode () => Anchor (0).GetHashCode (); + + /// Determines whether the specified object is equal to the current object. + /// The object to compare with the current object. + /// + /// if the specified object is equal to the current object; otherwise, . + /// + public override bool Equals (object other) => other is Dim abs && abs == this; + + // Helper class to provide dynamic value by the execution of a function that returns an integer. + internal class DimFunc : Dim { + readonly Func _function; + + public DimFunc (Func n) => _function = n; + + internal override int Anchor (int width) => _function (); + + public override string ToString () => $"DimFunc({_function ()})"; + + public override int GetHashCode () => _function.GetHashCode (); + + public override bool Equals (object other) => other is DimFunc f && f._function () == _function (); + } + internal class DimFactor : Dim { readonly float _factor; readonly bool _remaining; @@ -568,30 +714,9 @@ internal class DimFill : Dim { public override bool Equals (object other) => other is DimFill fill && fill._margin == _margin; } - /// - /// Creates a object that fills the dimension, leaving the specified number of columns for a margin. - /// - /// The Fill dimension. - /// Margin to use. - public static Dim Fill (int margin = 0) => new DimFill (margin); - - /// - /// Creates an Absolute from the specified integer value. - /// - /// The Absolute . - /// The value to convert to the pos. - public static implicit operator Dim (int n) => new DimAbsolute (n); - - /// - /// Creates an Absolute from the specified integer value. - /// - /// The Absolute . - /// The value to convert to the . - public static Dim Sized (int n) => new DimAbsolute (n); - internal class DimCombine : Dim { - internal Dim _left, _right; internal bool _add; + internal Dim _left, _right; public DimCombine (bool add, Dim left, Dim right) { @@ -602,55 +727,18 @@ public DimCombine (bool add, Dim left, Dim right) internal override int Anchor (int width) { - int la = _left.Anchor (width); - int ra = _right.Anchor (width); + var la = _left.Anchor (width); + var ra = _right.Anchor (width); if (_add) { return la + ra; - } else { - return la - ra; } + return la - ra; } public override string ToString () => $"Combine({_left}{(_add ? '+' : '-')}{_right})"; } - /// - /// Adds a to a , yielding a new . - /// - /// The first to add. - /// The second to add. - /// The that is the sum of the values of left and right. - public static Dim operator + (Dim left, Dim right) - { - if (left is DimAbsolute && right is DimAbsolute) { - return new DimAbsolute (left.Anchor (0) + right.Anchor (0)); - } - var newDim = new DimCombine (true, left, right); - SetDimCombine (left, newDim); - return newDim; - } - - /// - /// Subtracts a from a , yielding a new . - /// - /// The to subtract from (the minuend). - /// The to subtract (the subtrahend). - /// The that is the left minus right. - public static Dim operator - (Dim left, Dim right) - { - if (left is DimAbsolute && right is DimAbsolute) { - return new DimAbsolute (left.Anchor (0) - right.Anchor (0)); - } - var newDim = new DimCombine (false, left, right); - SetDimCombine (left, newDim); - return newDim; - } - - // BUGBUG: newPos is never used. - static void SetDimCombine (Dim left, DimCombine newPos) => (left as DimView)?.Target.SetNeedsLayout (); - internal class DimView : Dim { - public View Target { get; init; } readonly int _side; public DimView (View view, int side) @@ -659,6 +747,8 @@ public DimView (View view, int side) _side = side; } + public View Target { get; init; } + internal override int Anchor (int width) => _side switch { 0 => Target.Frame.Height, 1 => Target.Frame.Width, @@ -670,7 +760,7 @@ public override string ToString () if (Target == null) { throw new NullReferenceException (); } - string tside = _side switch { + var tside = _side switch { 0 => "Height", 1 => "Width", _ => "unknown" @@ -682,28 +772,4 @@ public override string ToString () public override bool Equals (object other) => other is DimView abs && abs.Target == Target; } - - /// - /// Creates a object that tracks the Width of the specified . - /// - /// The width of the other . - /// The view that will be tracked. - public static Dim Width (View view) => new DimView (view, 1); - - /// - /// Creates a object that tracks the Height of the specified . - /// - /// The height of the other . - /// The view that will be tracked. - public static Dim Height (View view) => new DimView (view, 0); - - /// Serves as the default hash function. - /// A hash code for the current object. - public override int GetHashCode () => Anchor (0).GetHashCode (); - - /// Determines whether the specified object is equal to the current object. - /// The object to compare with the current object. - /// - /// if the specified object is equal to the current object; otherwise, . - public override bool Equals (object other) => other is Dim abs && abs == this; } \ No newline at end of file diff --git a/Terminal.Gui/View/Layout/ViewLayout.cs b/Terminal.Gui/View/Layout/ViewLayout.cs index c57de26049..7bb052ba5a 100644 --- a/Terminal.Gui/View/Layout/ViewLayout.cs +++ b/Terminal.Gui/View/Layout/ViewLayout.cs @@ -7,57 +7,87 @@ namespace Terminal.Gui; /// -/// Determines the LayoutStyle for a , if Absolute, during , the -/// value from the will be used, if the value is Computed, then -/// will be updated from the X, Y objects and the Width and Height objects. +/// +/// Indicates the LayoutStyle for the . +/// +/// +/// If Absolute, the , , , and +/// +/// objects are all absolute values and are not relative. The position and size of the view is described by +/// . +/// +/// +/// If Computed, one or more of the , , , or +/// +/// objects are relative to the and are computed at layout time. +/// /// public enum LayoutStyle { /// - /// The position and size of the view are based . + /// Indicates the , , , and + /// objects are all absolute values and are not relative. The position and size of the view is described by + /// . /// Absolute, /// - /// The position and size of the view will be computed based on - /// , , , and . will - /// provide the absolute computed values. + /// Indicates one or more of the , , , or + /// + /// objects are relative to the and are computed at layout time. The position and size of the + /// view + /// will be computed based on these objects at layout time. will provide the absolute computed + /// values. /// Computed } public partial class View { - // The frame for the object. Relative to the SuperView's Bounds. + bool _autoSize; Rect _frame; + Dim _height = Dim.Sized (0); + Dim _width = Dim.Sized (0); + Pos _x = Pos.At (0); + Pos _y = Pos.At (0); /// - /// Gets or sets location and size of the view. The frame is relative to the 's . + /// Gets or sets the absolute location and dimension of the view. /// - /// The rectangle describing the location and size of the view, in coordinates relative to the . + /// + /// The rectangle describing absolute location and dimension of the view, + /// in coordinates relative to the 's . + /// /// - /// - /// Change the Frame when using the layout style to move or resize views. - /// - /// - /// Altering the Frame will change to . - /// Additionally, , , , and will be set - /// to the values of the Frame (using and ). - /// - /// - /// Altering the Frame will eventually (when the view is next drawn) cause the - /// and methods to be called. - /// + /// + /// Frame is relative to the 's . + /// + /// + /// Setting Frame will set , , , and + /// to the values of the corresponding properties of the parameter. + /// + /// + /// This causes to be . + /// + /// + /// Altering the Frame will eventually (when the view hierarchy is next laid out via see cref="LayoutSubviews"/>) + /// cause and methods to be called. + /// /// - public virtual Rect Frame { + public Rect Frame { get => _frame; set { _frame = new Rect (value.X, value.Y, Math.Max (value.Width, 0), Math.Max (value.Height, 0)); - //X = _frame.X; - //Y = _frame.Y; - //Width = _frame.Width; - //Height = _frame.Height; - if (IsInitialized || LayoutStyle == LayoutStyle.Absolute) { + + // If Frame gets set, by definition, the View is now LayoutStyle.Absolute, so + // set all Pos/Dim to Absolute values. + _x = _frame.X; + _y = _frame.Y; + _width = _frame.Width; + _height = _frame.Height; + + // TODO: Figure out if the below can be optimized. + if (IsInitialized /*|| LayoutStyle == LayoutStyle.Absolute*/) { LayoutFrames (); - TextFormatter.Size = GetTextFormatterSizeNeededForTextAndHotKey (); + SetTextFormatterSize (); SetNeedsLayout (); SetNeedsDisplay (); } @@ -65,41 +95,46 @@ public virtual Rect Frame { } /// - /// The frame (specified as a ) that separates a View from other SubViews of the same SuperView. - /// The margin offsets the from the . + /// The frame (specified as a ) that separates a View from other SubViews of the same SuperView. + /// The margin offsets the from the . /// /// - /// - /// The frames (, , and ) are not part of the View's content - /// and are not clipped by the View's Clip Area. - /// - /// - /// Changing the size of a frame (, , or ) - /// will change the size of the and trigger to update the layout of the - /// and its . - /// + /// + /// The frames (, , and ) are not part of the View's + /// content + /// and are not clipped by the View's Clip Area. + /// + /// + /// Changing the size of a frame (, , or ) + /// will change the size of the and trigger to update the layout + /// of the + /// and its . + /// /// public Frame Margin { get; private set; } /// - /// The frame (specified as a ) inside of the view that offsets the from the . - /// The Border provides the space for a visual border (drawn using line-drawing glyphs) and the Title. - /// The Border expands inward; in other words if `Border.Thickness.Top == 2` the border and - /// title will take up the first row and the second row will be filled with spaces. + /// The frame (specified as a ) inside of the view that offsets the from the + /// . + /// The Border provides the space for a visual border (drawn using line-drawing glyphs) and the Title. + /// The Border expands inward; in other words if `Border.Thickness.Top == 2` the border and + /// title will take up the first row and the second row will be filled with spaces. /// /// - /// - /// provides a simple helper for turning a simple border frame on or off. - /// - /// - /// The frames (, , and ) are not part of the View's content - /// and are not clipped by the View's Clip Area. - /// - /// - /// Changing the size of a frame (, , or ) - /// will change the size of the and trigger to update the layout of the - /// and its . - /// + /// + /// provides a simple helper for turning a simple border frame on or off. + /// + /// + /// The frames (, , and ) are not part of the View's + /// content + /// and are not clipped by the View's Clip Area. + /// + /// + /// Changing the size of a frame (, , or ) + /// will change the size of the and trigger to update the layout + /// of the + /// and its . + /// /// public Frame Border { get; private set; } @@ -107,18 +142,20 @@ public virtual Rect Frame { /// Gets or sets whether the view has a one row/col thick border. /// /// - /// - /// This is a helper for manipulating the view's . Setting this property to any value other than - /// is equivalent to setting 's - /// to `1` and to the value. - /// - /// - /// Setting this property to is equivalent to setting 's - /// to `0` and to . - /// - /// - /// For more advanced customization of the view's border, manipulate see directly. - /// + /// + /// This is a helper for manipulating the view's . Setting this property to any value other + /// than + /// is equivalent to setting 's + /// to `1` and to the value. + /// + /// + /// Setting this property to is equivalent to setting 's + /// + /// to `0` and to . + /// + /// + /// For more advanced customization of the view's border, manipulate see directly. + /// /// public LineStyle BorderStyle { get => Border?.BorderStyle ?? LineStyle.None; @@ -138,155 +175,75 @@ public LineStyle BorderStyle { } /// - /// The frame (specified as a ) inside of the view that offsets the from the . + /// The frame (specified as a ) inside of the view that offsets the from the + /// . /// /// - /// - /// The frames (, , and ) are not part of the View's content - /// and are not clipped by the View's Clip Area. - /// - /// - /// Changing the size of a frame (, , or ) - /// will change the size of the and trigger to update the layout of the - /// and its . - /// + /// + /// The frames (, , and ) are not part of the View's + /// content + /// and are not clipped by the View's Clip Area. + /// + /// + /// Changing the size of a frame (, , or ) + /// will change the size of the and trigger to update the layout + /// of the + /// and its . + /// /// public Frame Padding { get; private set; } /// - /// Helper to get the total thickness of the , , and . - /// - /// A thickness that describes the sum of the Frames' thicknesses. - public Thickness GetFramesThickness () - { - int left = Margin.Thickness.Left + Border.Thickness.Left + Padding.Thickness.Left; - int top = Margin.Thickness.Top + Border.Thickness.Top + Padding.Thickness.Top; - int right = Margin.Thickness.Right + Border.Thickness.Right + Padding.Thickness.Right; - int bottom = Margin.Thickness.Bottom + Border.Thickness.Bottom + Padding.Thickness.Bottom; - return new Thickness (left, top, right, bottom); - } - - /// - /// Helper to get the X and Y offset of the Bounds from the Frame. This is the sum of the Left and Top properties of - /// , and . + /// + /// Gets the LayoutStyle for the . + /// + /// + /// If Absolute, the , , , and + /// + /// objects are all absolute values and are not relative. The position and size of the view is described by + /// . + /// + /// + /// If Computed, one or more of the , , , or + /// + /// objects are relative to the and are computed at layout time. + /// /// - public Point GetBoundsOffset () => new (Padding?.Thickness.GetInside (Padding.Frame).X ?? 0, Padding?.Thickness.GetInside (Padding.Frame).Y ?? 0); - - /// - /// Creates the view's objects. This internal method is overridden by Frame to do nothing - /// to prevent recursion during View construction. - /// - internal virtual void CreateFrames () - { - void ThicknessChangedHandler (object sender, EventArgs e) - { - if (IsInitialized) { - LayoutFrames (); - } - SetNeedsLayout (); - SetNeedsDisplay (); - } - - if (Margin != null) { - Margin.ThicknessChanged -= ThicknessChangedHandler; - Margin.Dispose (); - } - Margin = new Frame () { Id = "Margin", Thickness = new Thickness (0) }; - Margin.ThicknessChanged += ThicknessChangedHandler; - Margin.Parent = this; - - if (Border != null) { - Border.ThicknessChanged -= ThicknessChangedHandler; - Border.Dispose (); - } - Border = new Frame () { Id = "Border", Thickness = new Thickness (0) }; - Border.ThicknessChanged += ThicknessChangedHandler; - Border.Parent = this; - - // TODO: Create View.AddAdornment - - if (Padding != null) { - Padding.ThicknessChanged -= ThicknessChangedHandler; - Padding.Dispose (); - } - Padding = new Frame () { Id = "Padding", Thickness = new Thickness (0) }; - Padding.ThicknessChanged += ThicknessChangedHandler; - Padding.Parent = this; - } - - LayoutStyle _layoutStyle; - - /// - /// Controls how the View's is computed during . If the style is set to - /// , LayoutSubviews does not change the . - /// If the style is the is updated using - /// the , , , and properties. - /// - /// - /// - /// Setting this property to will cause to determine the - /// size and position of the view. and will be set to using . - /// - /// - /// Setting this property to will cause the view to use the method to - /// size and position of the view. If either of the and properties are `null` they will be set to using - /// the current value of . - /// If either of the and properties are `null` they will be set to using . - /// - /// /// The layout style. public LayoutStyle LayoutStyle { get { - return _layoutStyle; - //if ((X == null || X is Pos.PosAbsolute) && (Y == null || Y is Pos.PosAbsolute) && - //(Width == null || Width is Dim.DimAbsolute) && (Height == null || Height is Dim.DimAbsolute)) { - // return LayoutStyle.Absolute; - //} else { - // return LayoutStyle.Computed; - //} - } - set { - _layoutStyle = value; - //switch (_layoutStyle) { - //case LayoutStyle.Absolute: - // X = Frame.X; - // Y = Frame.Y; - // Width = Frame.Width; - // Height = Frame.Height; - // break; - - //case LayoutStyle.Computed: - // X ??= Frame.X; - // Y ??= Frame.Y; - // Width ??= Frame.Width; - // Height ??= Frame.Height; - // break; - //} - SetNeedsLayout (); + if (_x is Pos.PosAbsolute && _y is Pos.PosAbsolute && _width is Dim.DimAbsolute && _height is Dim.DimAbsolute) { + return LayoutStyle.Absolute; + } + return LayoutStyle.Computed; } } /// - /// The bounds represent the View-relative rectangle used for this view; the area inside of the view where subviews and content are presented. + /// The bounds represent the View-relative rectangle used for this view; the area inside of the view where subviews and + /// content are presented. /// /// The rectangle describing the location and size of the area where the views' subviews and content are drawn. /// - /// - /// If is the value of Bounds is indeterminate until the - /// view has been initialized ( is true) and has been called. - /// - /// - /// Updates to the Bounds updates , and has the same side effects as updating the . - /// - /// - /// Altering the Bounds will eventually (when the view is next drawn) cause the - /// and methods to be called. - /// - /// - /// Because coordinates are relative to the upper-left corner of the , - /// the coordinates of the upper-left corner of the rectangle returned by this property are (0,0). - /// Use this property to obtain the size of the area of the view for tasks such as drawing the view's contents. - /// + /// + /// If is the value of Bounds is indeterminate until + /// the view has been initialized ( is true) and has been + /// called. + /// + /// + /// Updates to the Bounds updates , and has the same effect as updating the + /// . + /// + /// + /// Altering the Bounds will eventually (when the view is next laid out) cause the + /// + /// and methods to be called. + /// + /// + /// Because coordinates are relative to the upper-left corner of the , + /// the coordinates of the upper-left corner of the rectangle returned by this property are (0,0). + /// Use this property to obtain the size of the area of the view for tasks such as drawing the view's contents. + /// /// public virtual Rect Bounds { get { @@ -295,12 +252,17 @@ public virtual Rect Bounds { Debug.WriteLine ($"WARNING: Bounds is being accessed before the View has been initialized. This is likely a bug in {this}"); } #endif // DEBUG - //var frameRelativeBounds = Padding?.Thickness.GetInside (Padding.Frame) ?? new Rect (default, Frame.Size); var frameRelativeBounds = FrameGetInsideBounds (); return new Rect (default, frameRelativeBounds.Size); } set { - // BUGBUG: Margin etc.. can be null (if typeof(Frame)) + // TODO: Should we enforce Bounds.X/Y == 0? The code currently ignores value.X/Y which is + // TODO: correct behavior, but is silent. Perhaps an exception? +#if DEBUG + if (value.Location != Point.Empty) { + Debug.WriteLine ($"WARNING: Bounds.Location must always be 0,0. Location ({value.Location}) is ignored. {this}"); + } +#endif // DEBUG Frame = new Rect (Frame.Location, new Size ( value.Size.Width + Margin.Thickness.Horizontal + Border.Thickness.Horizontal + Padding.Thickness.Horizontal, @@ -310,127 +272,144 @@ public virtual Rect Bounds { } } - Rect FrameGetInsideBounds () - { - if (Margin == null || Border == null || Padding == null) { - return new Rect (default, Frame.Size); - } - int width = Math.Max (0, Frame.Size.Width - Margin.Thickness.Horizontal - Border.Thickness.Horizontal - Padding.Thickness.Horizontal); - int height = Math.Max (0, Frame.Size.Height - Margin.Thickness.Vertical - Border.Thickness.Vertical - Padding.Thickness.Vertical); - return new Rect (Point.Empty, new Size (width, height)); - } - - Pos _x, _y; - /// - /// Gets or sets the X position for the view (the column). + /// Gets or sets the X position for the view (the column). /// /// The object representing the X position. /// - /// - /// If is the value is indeterminate until the - /// view has been initialized ( is true) and has been called. - /// - /// - /// Changing this property will eventually (when the view is next drawn) cause the and - /// methods to be called. - /// - /// - /// If is changing this property will cause the to be updated. If - /// the new value is not of type the will change to . - /// - /// - /// is the same as Pos.Absolute(0). - /// + /// + /// If set to a relative value (e.g. ) the value is indeterminate until the + /// view has been initialized ( is true) and has been + /// called. + /// + /// + /// Changing this property will eventually (when the view is next drawn) cause the + /// and + /// methods to be called. + /// + /// + /// Changing this property will cause to be updated. If + /// the new value is not of type the will change to + /// . + /// + /// + /// The default value is Pos.At (0). + /// /// public Pos X { - get => VerifyIsInitialized (_x, nameof(X)); + get => VerifyIsInitialized (_x, nameof (X)); set { - // BUGBUG: null is the sames a Pos.Absolute(0). Should we be explicit and set it? - - if (ValidatePosDim && LayoutStyle == LayoutStyle.Computed) { - CheckAbsolute (nameof (X), _x, value); - } - - _x = value; - + _x = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (X)} cannot be null"); OnResizeNeeded (); } } /// - /// Gets or sets the Y position for the view (the row). + /// Gets or sets the Y position for the view (the row). /// /// The object representing the Y position. /// - /// - /// If is the value is indeterminate until the - /// view has been initialized ( is true) and has been called. - /// - /// - /// Changing this property will eventually (when the view is next drawn) cause the and - /// methods to be called. - /// - /// - /// If is changing this property will cause the to be updated. If - /// the new value is not of type the will change to . - /// - /// - /// is the same as Pos.Absolute(0). - /// + /// + /// If set to a relative value (e.g. ) the value is indeterminate until the + /// view has been initialized ( is true) and has been + /// called. + /// + /// + /// Changing this property will eventually (when the view is next drawn) cause the + /// and + /// methods to be called. + /// + /// + /// Changing this property will cause to be updated. If + /// the new value is not of type the will change to + /// . + /// + /// + /// The default value is Pos.At (0). + /// /// public Pos Y { - get => VerifyIsInitialized (_y, nameof(Y)); + get => VerifyIsInitialized (_y, nameof (Y)); set { - // BUGBUG: null is the sames a Pos.Absolute(0). Should we be explicit and set it? - - if (ValidatePosDim && LayoutStyle == LayoutStyle.Computed) { - CheckAbsolute (nameof (Y), _y, value); - } - - _y = value; - + _y = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (Y)} cannot be null"); OnResizeNeeded (); } } - Dim _width, _height; - /// - /// Gets or sets the width of the view. + /// Gets or sets the width dimension of the view. /// /// The object representing the width of the view (the number of columns). /// - /// - /// If is the value is indeterminate until the - /// view has been initialized ( is true) and has been called. - /// - /// - /// Changing this property will eventually (when the view is next drawn) cause the - /// and methods to be called. - /// - /// - /// If is changing this property will cause the to be updated. If - /// the new value is not of type the will change to . - /// + /// + /// If set to a relative value (e.g. ) the value is indeterminate until the + /// view has been initialized ( is true) and has been + /// called. + /// + /// + /// Changing this property will eventually (when the view is next drawn) cause the + /// + /// and methods to be called. + /// + /// + /// Changing this property will cause to be updated. If + /// the new value is not of type the will change to + /// . + /// + /// + /// The default value is Dim.Sized (0). + /// /// public Dim Width { get => VerifyIsInitialized (_width, nameof (Width)); set { - // BUGBUG: null is the sames a Dim.Fill(0). Should we be explicit and set it? + _width = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (Width)} cannot be null"); + if (ValidatePosDim) { - if (LayoutStyle == LayoutStyle.Computed) { - CheckAbsolute (nameof (Width), _width, value); + var isValidNewAutSize = AutoSize && IsValidAutoSizeWidth (_width); + + if (IsAdded && AutoSize && !isValidNewAutSize) { + throw new InvalidOperationException ("Must set AutoSize to false before set the Width."); } } + OnResizeNeeded (); + } + } - _width = value; + /// + /// Gets or sets the height dimension of the view. + /// + /// The object representing the height of the view (the number of rows). + /// + /// + /// If set to a relative value (e.g. ) the value is indeterminate until the + /// view has been initialized ( is true) and has been + /// called. + /// + /// + /// Changing this property will eventually (when the view is next drawn) cause the + /// + /// and methods to be called. + /// + /// + /// Changing this property will cause to be updated. If + /// the new value is not of type the will change to + /// . + /// + /// + /// The default value is Dim.Sized (0). + /// + /// + public Dim Height { + get => VerifyIsInitialized (_height, nameof (Height)); + set { + _height = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (Height)} cannot be null"); if (ValidatePosDim) { - bool isValidNewAutSize = AutoSize && IsValidAutoSizeWidth (_width); + var isValidNewAutSize = AutoSize && IsValidAutoSizeHeight (_height); if (IsAdded && AutoSize && !isValidNewAutSize) { - throw new InvalidOperationException ("Must set AutoSize to false before set the Width."); + throw new InvalidOperationException ("Must set AutoSize to false before setting the Height."); } } OnResizeNeeded (); @@ -438,44 +417,128 @@ public Dim Width { } /// - /// Gets or sets the height of the view. + /// Gets or sets whether validation of and occurs. /// - /// The object representing the height of the view (the number of rows). /// + /// Setting this to will enable validation of , , , + /// and + /// during set operations and in . If invalid settings are discovered exceptions will be thrown + /// indicating the error. + /// This will impose a performance penalty and thus should only be used for debugging. + /// + public bool ValidatePosDim { get; set; } + + internal bool LayoutNeeded { get; private set; } = true; + + /// + /// Gets or sets a flag that determines whether the View will be automatically resized to fit the + /// within . /// - /// If is the value is indeterminate until the - /// view has been initialized ( is true) and has been called. + /// The default is . Set to to turn on AutoSize. If + /// then + /// and will be used if can fit; + /// if won't fit the view will be resized as needed. /// /// - /// Changing this property will eventually (when the view is next drawn) cause the - /// and methods to be called. + /// If is set to then and + /// will be changed to if they are not already. /// /// - /// If is changing this property will cause the to be updated. If - /// the new value is not of type the will change to . + /// If is set to then and + /// will left unchanged. /// - /// - public Dim Height { - get => VerifyIsInitialized (_height, nameof (Height)); + /// + public virtual bool AutoSize { + get => _autoSize; set { - // BUGBUG: null is the sames a Dim.Fill(0). Should we be explicit and set it? - if (ValidatePosDim) { - if (LayoutStyle == LayoutStyle.Computed) { - CheckAbsolute (nameof (Height), _height, value); - } + var v = ResizeView (value); + TextFormatter.AutoSize = v; + if (_autoSize != v) { + _autoSize = v; + TextFormatter.NeedsFormat = true; + UpdateTextFormatterText (); + OnResizeNeeded (); } + } + } - _height = value; + /// + /// Event called only once when the is being initialized for the first time. + /// Allows configurations and assignments to be performed before the being shown. + /// This derived from to allow notify all the views that are being + /// initialized. + /// + public event EventHandler Initialized; - if (ValidatePosDim) { - bool isValidNewAutSize = AutoSize && IsValidAutoSizeHeight (_height); + /// + /// Helper to get the total thickness of the , , and . + /// + /// A thickness that describes the sum of the Frames' thicknesses. + public Thickness GetFramesThickness () + { + var left = Margin.Thickness.Left + Border.Thickness.Left + Padding.Thickness.Left; + var top = Margin.Thickness.Top + Border.Thickness.Top + Padding.Thickness.Top; + var right = Margin.Thickness.Right + Border.Thickness.Right + Padding.Thickness.Right; + var bottom = Margin.Thickness.Bottom + Border.Thickness.Bottom + Padding.Thickness.Bottom; + return new Thickness (left, top, right, bottom); + } - if (IsAdded && AutoSize && !isValidNewAutSize) { - throw new InvalidOperationException ("Must set AutoSize to false before setting the Height."); - } + /// + /// Helper to get the X and Y offset of the Bounds from the Frame. This is the sum of the Left and Top properties of + /// , and . + /// + public Point GetBoundsOffset () => new (Padding?.Thickness.GetInside (Padding.Frame).X ?? 0, Padding?.Thickness.GetInside (Padding.Frame).Y ?? 0); + + /// + /// Creates the view's objects. This internal method is overridden by Frame to do nothing + /// to prevent recursion during View construction. + /// + internal virtual void CreateFrames () + { + void ThicknessChangedHandler (object sender, EventArgs e) + { + if (IsInitialized) { + LayoutFrames (); } - OnResizeNeeded (); + SetNeedsLayout (); + SetNeedsDisplay (); + } + + if (Margin != null) { + Margin.ThicknessChanged -= ThicknessChangedHandler; + Margin.Dispose (); + } + Margin = new Frame { Id = "Margin", Thickness = new Thickness (0) }; + Margin.ThicknessChanged += ThicknessChangedHandler; + Margin.Parent = this; + + if (Border != null) { + Border.ThicknessChanged -= ThicknessChangedHandler; + Border.Dispose (); } + Border = new Frame { Id = "Border", Thickness = new Thickness (0) }; + Border.ThicknessChanged += ThicknessChangedHandler; + Border.Parent = this; + + // TODO: Create View.AddAdornment + + if (Padding != null) { + Padding.ThicknessChanged -= ThicknessChangedHandler; + Padding.Dispose (); + } + Padding = new Frame { Id = "Padding", Thickness = new Thickness (0) }; + Padding.ThicknessChanged += ThicknessChangedHandler; + Padding.Parent = this; + } + + Rect FrameGetInsideBounds () + { + if (Margin == null || Border == null || Padding == null) { + return new Rect (default, Frame.Size); + } + var width = Math.Max (0, Frame.Size.Width - Margin.Thickness.Horizontal - Border.Thickness.Horizontal - Padding.Thickness.Horizontal); + var height = Math.Max (0, Frame.Size.Height - Margin.Thickness.Vertical - Border.Thickness.Vertical - Padding.Thickness.Vertical); + return new Rect (Point.Empty, new Size (width, height)); } // Diagnostics to highlight when X or Y is read before the view has been initialized @@ -501,75 +564,42 @@ Dim VerifyIsInitialized (Dim dim, string member) } /// - /// Gets or sets whether validation of and occurs. - /// - /// - /// Setting this to will enable validation of , , , and - /// during set operations and in .If invalid settings are discovered exceptions will be thrown indicating the error. - /// This will impose a performance penalty and thus should only be used for debugging. - /// - public bool ValidatePosDim { get; set; } - - /// - /// Throws an if is or . - /// Used when is turned on to verify correct behavior. - /// - /// - /// Does not verify if this view is Toplevel (WHY??!?). - /// - /// The property name. - /// - /// - void CheckAbsolute (string prop, object oldValue, object newValue) - { - if (!IsInitialized || !ValidatePosDim || oldValue == null || oldValue.GetType () == newValue.GetType () || this is Toplevel) { - return; - } - - if (oldValue.GetType () != newValue.GetType () && newValue is (Pos.PosAbsolute or Dim.DimAbsolute)) { - throw new ArgumentException ($@"{prop} must not be Absolute if LayoutStyle is Computed", prop); - } - } - - /// - /// Called whenever the view needs to be resized. Sets and - /// triggers a call. + /// Called whenever the view needs to be resized. This is called whenever , + /// , , , or changes. /// /// - /// Can be overridden if the view resize behavior is different than the default. + /// + /// Determines the relative bounds of the and its s, and then calls + /// to update the view. + /// /// - protected virtual void OnResizeNeeded () + internal void OnResizeNeeded () { - int actX = _x is Pos.PosAbsolute ? _x.Anchor (0) : _frame.X; - int actY = _y is Pos.PosAbsolute ? _y.Anchor (0) : _frame.Y; - - if (AutoSize) { - //if (TextAlignment == TextAlignment.Justified) { - // throw new InvalidOperationException ("TextAlignment.Justified cannot be used with AutoSize"); - //} - var s = GetAutoSize (); - int w = _width is Dim.DimAbsolute && _width.Anchor (0) > s.Width ? _width.Anchor (0) : s.Width; - int h = _height is Dim.DimAbsolute && _height.Anchor (0) > s.Height ? _height.Anchor (0) : s.Height; - _frame = new Rect (new Point (actX, actY), new Size (w, h)); // Set frame, not Frame! - } else { - int w = _width is Dim.DimAbsolute ? _width.Anchor (0) : _frame.Width; - int h = _height is Dim.DimAbsolute ? _height.Anchor (0) : _frame.Height; - // BUGBUG: v2 - ? - If layoutstyle is absolute, this overwrites the current frame h/w with 0. Hmmm... - // This is needed for DimAbsolute values by setting the frame before LayoutSubViews. - _frame = new Rect (new Point (actX, actY), new Size (w, h)); // Set frame, not Frame! - } - //// BUGBUG: I think these calls are redundant or should be moved into just the AutoSize case - if (IsInitialized || LayoutStyle == LayoutStyle.Absolute) { + // TODO: Identify a real-world use-case where this API should be virtual. + // TODO: Until then leave it `internal` and non-virtual + // First try SuperView.Bounds, then Application.Top, then Driver.Bounds. + // Finally, if none of those are valid, use int.MaxValue (for Unit tests). + var relativeBounds = SuperView is { IsInitialized: true } ? SuperView.Bounds : + Application.Top != null && Application.Top.IsInitialized ? Application.Top.Bounds : + Application.Driver?.Bounds ?? + new Rect (0, 0, int.MaxValue, int.MaxValue); + SetRelativeLayout (relativeBounds); + + // TODO: Determine what, if any of the below is actually needed here. + if (IsInitialized) { SetFrameToFitText (); LayoutFrames (); - TextFormatter.Size = GetTextFormatterSizeNeededForTextAndHotKey (); + SetTextFormatterSize (); SetNeedsLayout (); SetNeedsDisplay (); } } - internal bool LayoutNeeded { get; private set; } = true; - + /// + /// Sets the internal flag for this View and all of it's + /// subviews and it's SuperView. The main loop will call SetRelativeLayout and LayoutSubviews + /// for any view with set. + /// internal void SetNeedsLayout () { if (LayoutNeeded) { @@ -607,7 +637,7 @@ public Point ScreenToFrame (int x, int y) } /// - /// Converts a screen-relative coordinate to a bounds-relative coordinate. + /// Converts a screen-relative coordinate to a bounds-relative coordinate. /// /// The coordinate relative to this view's . /// Screen-relative column. @@ -620,15 +650,18 @@ public Point ScreenToBounds (int x, int y) } /// - /// Converts a -relative coordinate to a screen-relative coordinate. The output is optionally clamped to the screen dimensions. + /// Converts a -relative coordinate to a screen-relative coordinate. The output is optionally clamped + /// to the screen dimensions. /// /// -relative column. /// -relative row. /// Absolute column; screen-relative. /// Absolute row; screen-relative. - /// If , and will be clamped to the + /// + /// If , and will be clamped to the /// screen dimensions (will never be negative and will always be less than and - /// , respectively. + /// , respectively. + /// public virtual void BoundsToScreen (int x, int y, out int rx, out int ry, bool clamped = true) { var boundsOffset = GetBoundsOffset (); @@ -651,16 +684,16 @@ public virtual void BoundsToScreen (int x, int y, out int rx, out int ry, bool c } /// - /// Converts a -relative region to a screen-relative region. + /// Converts a -relative region to a screen-relative region. /// public Rect BoundsToScreen (Rect region) { - BoundsToScreen (region.X, region.Y, out int x, out int y, false); + BoundsToScreen (region.X, region.Y, out var x, out var y, false); return new Rect (x, y, region.Width, region.Height); } /// - /// Gets the with a screen-relative location. + /// Gets the with a screen-relative location. /// /// The location and size of the view in screen-relative coordinates. public virtual Rect FrameToScreen () @@ -676,14 +709,23 @@ public virtual Rect FrameToScreen () return ret; } - // TODO: Come up with a better name for this method. "SetRelativeLayout" lacks clarity and confuses. AdjustSizeAndPosition? /// - /// Applies the view's position (, ) and dimension (, and ) to - /// , given a rectangle describing the SuperView's Bounds (nominally the same as this.SuperView.Bounds). + /// Applies the view's position (, ) and dimension (, and + /// ) to + /// , given a rectangle describing the SuperView's Bounds (nominally the same as + /// this.SuperView.Bounds). /// - /// The rectangle describing the SuperView's Bounds (nominally the same as this.SuperView.Bounds). + /// + /// The rectangle describing the SuperView's Bounds (nominally the same as + /// this.SuperView.Bounds). + /// internal void SetRelativeLayout (Rect superviewBounds) { + Debug.Assert (_x != null); + Debug.Assert (_y != null); + Debug.Assert (_width != null); + Debug.Assert (_height != null); + int newX, newW, newY, newH; var autosize = Size.Empty; @@ -693,6 +735,10 @@ internal void SetRelativeLayout (Rect superviewBounds) autosize = GetAutoSize (); } + // TODO: Since GetNewLocationAndDimension does not depend on View, it can be moved into PosDim.cs + // TODO: to make architecture more clean. Do this after DimAuto is implemented and the + // TODO: View.AutoSize stuff is removed. + // Returns the new dimension (width or height) and location (x or y) for the View given // the superview's Bounds // the current Pos (View.X or View.Y) @@ -702,21 +748,18 @@ internal void SetRelativeLayout (Rect superviewBounds) { // Gets the new dimension (width or height, dependent on `width`) of the given Dim given: // location: the current location (x or y) - // dimension: the current dimension (width or height) + // dimension: the new dimension (width or height) (if relevant for Dim type) // autosize: the size to use if autosize = true - // This mehod is recursive if d is Dim.DimCombine + // This method is recursive if d is Dim.DimCombine int GetNewDimension (Dim d, int location, int dimension, int autosize) { int newDimension; switch (d) { - case null: - // dim == null is the same as dim == Dim.FIll (0) - newDimension = AutoSize ? autosize : dimension; - break; case Dim.DimCombine combine: - int leftNewDim = GetNewDimension (combine._left, location, dimension, autosize); - int rightNewDim = GetNewDimension (combine._right, location, dimension, autosize); + // TODO: Move combine logic into DimCombine? + var leftNewDim = GetNewDimension (combine._left, location, dimension, autosize); + var rightNewDim = GetNewDimension (combine._right, location, dimension, autosize); if (combine._add) { newDimension = leftNewDim + rightNewDim; } else { @@ -730,6 +773,12 @@ int GetNewDimension (Dim d, int location, int dimension, int autosize) newDimension = AutoSize && autosize > newDimension ? autosize : newDimension; break; + case Dim.DimAbsolute: + // DimAbsoulte.Anchor (int width) ignores width and returns n + newDimension = Math.Max (d.Anchor (0), 0); + newDimension = AutoSize && autosize > newDimension ? autosize : newDimension; + break; + case Dim.DimFill: default: newDimension = Math.Max (d.Anchor (dimension - location), 0); @@ -741,7 +790,7 @@ int GetNewDimension (Dim d, int location, int dimension, int autosize) } int newDimension, newLocation; - int superviewDimension = width ? superviewBounds.Width : superviewBounds.Height; + var superviewDimension = width ? superviewBounds.Width : superviewBounds.Height; // Determine new location switch (pos) { @@ -754,6 +803,7 @@ int GetNewDimension (Dim d, int location, int dimension, int autosize) break; case Pos.PosCombine combine: + // TODO: Move combine logic into PosCombine? int left, right; (left, newDimension) = GetNewLocationAndDimension (width, superviewBounds, combine._left, dim, autosizeDimension); (right, newDimension) = GetNewLocationAndDimension (width, superviewBounds, combine._right, dim, autosizeDimension); @@ -767,7 +817,6 @@ int GetNewDimension (Dim d, int location, int dimension, int autosize) case Pos.PosAnchorEnd: case Pos.PosAbsolute: - case null: case Pos.PosFactor: case Pos.PosFunc: case Pos.PosView: @@ -781,7 +830,6 @@ int GetNewDimension (Dim d, int location, int dimension, int autosize) return (newLocation, newDimension); } - // horizontal/width (newX, newW) = GetNewLocationAndDimension (true, superviewBounds, _x, _width, autosize.Width); @@ -790,44 +838,64 @@ int GetNewDimension (Dim d, int location, int dimension, int autosize) var r = new Rect (newX, newY, newW, newH); if (Frame != r) { - Frame = r; + // Set the frame. Do NOT use `Frame` as it overwrites X, Y, Width, and Height, making + // the view LayoutStyle.Absolute. + _frame = r; + if (_x is Pos.PosAbsolute) { + _x = Frame.X; + } + if (_y is Pos.PosAbsolute) { + _y = Frame.Y; + } + if (_width is Dim.DimAbsolute) { + _width = Frame.Width; + } + if (_height is Dim.DimAbsolute) { + _height = Frame.Height; + } + + if (IsInitialized) { + // TODO: Figure out what really is needed here. All unit tests (except AutoSize) pass as-is + //LayoutFrames (); + SetTextFormatterSize (); + SetNeedsLayout (); + //SetNeedsDisplay (); + } + // BUGBUG: Why is this AFTER setting Frame? Seems duplicative. if (!SetFrameToFitText ()) { - TextFormatter.Size = GetTextFormatterSizeNeededForTextAndHotKey (); + SetTextFormatterSize (); } } } /// - /// Fired after the View's method has completed. + /// Fired after the View's method has completed. /// /// - /// Subscribe to this event to perform tasks when the has been resized or the layout has otherwise changed. + /// Subscribe to this event to perform tasks when the has been resized or the layout has otherwise + /// changed. /// public event EventHandler LayoutStarted; /// - /// Raises the event. Called from before any subviews have been laid out. + /// Raises the event. Called from before any subviews have been + /// laid out. /// internal virtual void OnLayoutStarted (LayoutEventArgs args) => LayoutStarted?.Invoke (this, args); /// - /// Fired after the View's method has completed. + /// Fired after the View's method has completed. /// /// - /// Subscribe to this event to perform tasks when the has been resized or the layout has otherwise changed. + /// Subscribe to this event to perform tasks when the has been resized or the layout has otherwise + /// changed. /// public event EventHandler LayoutComplete; /// - /// Event called only once when the is being initialized for the first time. - /// Allows configurations and assignments to be performed before the being shown. - /// This derived from to allow notify all the views that are being initialized. - /// - public event EventHandler Initialized; - - /// - /// Raises the event. Called from before all sub-views have been laid out. + /// Raises the event. Called from before all sub-views have been + /// laid out. /// internal virtual void OnLayoutComplete (LayoutEventArgs args) => LayoutComplete?.Invoke (this, args); @@ -942,9 +1010,8 @@ internal static List TopologicalSort (View superView, IEnumerable no } else if (from != superView?.GetTopSuperView (to, from) && !ReferenceEquals (from, to)) { if (ReferenceEquals (from.SuperView, to)) { throw new InvalidOperationException ($"ComputedLayout for \"{superView}\": \"{to}\" references a SubView (\"{from}\")."); - } else { - throw new InvalidOperationException ($"ComputedLayout for \"{superView}\": \"{from}\" linked with \"{to}\" was not found. Did you forget to add it to {superView}?"); } + throw new InvalidOperationException ($"ComputedLayout for \"{superView}\": \"{from}\" linked with \"{to}\" was not found. Did you forget to add it to {superView}?"); } } // return L (a topologically sorted order) @@ -998,13 +1065,13 @@ internal virtual void LayoutFrames () /// response to the container view or terminal resizing. /// /// - /// - /// The position and dimensions of the view are indeterminate until the view has been initialized. Therefore, - /// the behavior of this method is indeterminate if is . - /// - /// - /// Raises the event) before it returns. - /// + /// + /// The position and dimensions of the view are indeterminate until the view has been initialized. Therefore, + /// the behavior of this method is indeterminate if is . + /// + /// + /// Raises the event) before it returns. + /// /// public virtual void LayoutSubviews () { @@ -1019,9 +1086,9 @@ public virtual void LayoutSubviews () LayoutFrames (); var oldBounds = Bounds; - OnLayoutStarted (new LayoutEventArgs () { OldBounds = oldBounds }); + OnLayoutStarted (new LayoutEventArgs { OldBounds = oldBounds }); - TextFormatter.Size = GetTextFormatterSizeNeededForTextAndHotKey (); + SetTextFormatterSize (); // Sort out the dependencies of the X, Y, Width, Height properties var nodes = new HashSet (); @@ -1042,55 +1109,26 @@ public virtual void LayoutSubviews () LayoutNeeded = false; - OnLayoutComplete (new LayoutEventArgs () { OldBounds = oldBounds }); + OnLayoutComplete (new LayoutEventArgs { OldBounds = oldBounds }); } void LayoutSubview (View v, Rect contentArea) { - if (v.LayoutStyle == LayoutStyle.Computed) { - v.SetRelativeLayout (contentArea); - } + //if (v.LayoutStyle == LayoutStyle.Computed) { + v.SetRelativeLayout (contentArea); + //} v.LayoutSubviews (); v.LayoutNeeded = false; } - bool _autoSize; - - /// - /// Gets or sets a flag that determines whether the View will be automatically resized to fit the - /// within - /// - /// The default is . Set to to turn on AutoSize. If then - /// and will be used if can fit; - /// if won't fit the view will be resized as needed. - /// - /// - /// In addition, if is the new values of and - /// must be of the same types of the existing one to avoid breaking the settings. - /// - /// - public virtual bool AutoSize { - get => _autoSize; - set { - bool v = ResizeView (value); - TextFormatter.AutoSize = v; - if (_autoSize != v) { - _autoSize = v; - TextFormatter.NeedsFormat = true; - UpdateTextFormatterText (); - OnResizeNeeded (); - } - } - } - bool ResizeView (bool autoSize) { if (!autoSize) { return false; } - bool boundsChanged = true; + var boundsChanged = true; var newFrameSize = GetAutoSize (); if (IsInitialized && newFrameSize != Frame.Size) { if (ValidatePosDim) { @@ -1111,9 +1149,9 @@ bool ResizeView (bool autoSize) /// whether the Bounds was changed or not bool ResizeBoundsToFit (Size size) { - bool boundsChanged = false; - bool canSizeW = TrySetWidth (size.Width - GetHotKeySpecifierLength (), out int rW); - bool canSizeH = TrySetHeight (size.Height - GetHotKeySpecifierLength (false), out int rH); + var boundsChanged = false; + var canSizeW = TrySetWidth (size.Width - GetHotKeySpecifierLength (), out var rW); + var canSizeH = TrySetHeight (size.Height - GetHotKeySpecifierLength (false), out var rH); if (canSizeW) { boundsChanged = true; _width = rW; @@ -1129,60 +1167,22 @@ bool ResizeBoundsToFit (Size size) return boundsChanged; } - /// - /// Gets the Frame dimensions required to fit within using the text specified by the - /// property and accounting for any characters. - /// - /// The of the view required to fit the text. - public Size GetAutoSize () - { - int x = 0; - int y = 0; - if (IsInitialized) { - x = Bounds.X; - y = Bounds.Y; - } - var rect = TextFormatter.CalcRect (x, y, TextFormatter.Text, TextFormatter.Direction); - int newWidth = rect.Size.Width - GetHotKeySpecifierLength () + Margin.Thickness.Horizontal + Border.Thickness.Horizontal + Padding.Thickness.Horizontal; - int newHeight = rect.Size.Height - GetHotKeySpecifierLength (false) + Margin.Thickness.Vertical + Border.Thickness.Vertical + Padding.Thickness.Vertical; - return new Size (newWidth, newHeight); - } - - bool IsValidAutoSize (out Size autoSize) - { - var rect = TextFormatter.CalcRect (_frame.X, _frame.Y, TextFormatter.Text, TextDirection); - autoSize = new Size (rect.Size.Width - GetHotKeySpecifierLength (), - rect.Size.Height - GetHotKeySpecifierLength (false)); - return !(ValidatePosDim && (!(Width is Dim.DimAbsolute) || !(Height is Dim.DimAbsolute)) - || _frame.Size.Width != rect.Size.Width - GetHotKeySpecifierLength () - || _frame.Size.Height != rect.Size.Height - GetHotKeySpecifierLength (false)); - } - - bool IsValidAutoSizeWidth (Dim width) - { - var rect = TextFormatter.CalcRect (_frame.X, _frame.Y, TextFormatter.Text, TextDirection); - int dimValue = width.Anchor (0); - return !(ValidatePosDim && !(width is Dim.DimAbsolute) || dimValue != rect.Size.Width - - GetHotKeySpecifierLength ()); - } - - bool IsValidAutoSizeHeight (Dim height) - { - var rect = TextFormatter.CalcRect (_frame.X, _frame.Y, TextFormatter.Text, TextDirection); - int dimValue = height.Anchor (0); - return !(ValidatePosDim && !(height is Dim.DimAbsolute) || dimValue != rect.Size.Height - - GetHotKeySpecifierLength (false)); - } /// /// Determines if the View's can be set to a new value. /// /// - /// Contains the width that would result if were set to "/> - /// if the View's can be changed to the specified value. False otherwise. + /// + /// Contains the width that would result if were set to + /// "/> + /// + /// + /// if the View's can be changed to the specified value. False + /// otherwise. + /// internal bool TrySetWidth (int desiredWidth, out int resultWidth) { - int w = desiredWidth; + var w = desiredWidth; bool canSetWidth; switch (Width) { case Dim.DimCombine _: @@ -1194,7 +1194,7 @@ internal bool TrySetWidth (int desiredWidth, out int resultWidth) break; case Dim.DimFactor factor: // Tries to get the SuperView Width otherwise the view Width. - int sw = SuperView != null ? SuperView.Frame.Width : w; + var sw = SuperView != null ? SuperView.Frame.Width : w; if (factor.IsFromRemaining ()) { sw -= Frame.X; } @@ -1214,11 +1214,17 @@ internal bool TrySetWidth (int desiredWidth, out int resultWidth) /// Determines if the View's can be set to a new value. /// /// - /// Contains the width that would result if were set to "/> - /// if the View's can be changed to the specified value. False otherwise. + /// + /// Contains the width that would result if were set to + /// "/> + /// + /// + /// if the View's can be changed to the specified value. False + /// otherwise. + /// internal bool TrySetHeight (int desiredHeight, out int resultHeight) { - int h = desiredHeight; + var h = desiredHeight; bool canSetHeight; switch (Height) { case Dim.DimCombine _: @@ -1230,7 +1236,7 @@ internal bool TrySetHeight (int desiredHeight, out int resultHeight) break; case Dim.DimFactor factor: // Tries to get the SuperView height otherwise the view height. - int sh = SuperView != null ? SuperView.Frame.Height : h; + var sh = SuperView != null ? SuperView.Frame.Height : h; if (factor.IsFromRemaining ()) { sh -= Frame.Y; } @@ -1255,8 +1261,8 @@ internal bool TrySetHeight (int desiredHeight, out int resultHeight) /// The found view screen relative column location. /// The found view screen relative row location. /// - /// The view that was found at the and coordinates. - /// if no view was found. + /// The view that was found at the and coordinates. + /// if no view was found. /// public static View FindDeepestView (View start, int x, int y, out int resx, out int resy) { @@ -1267,12 +1273,12 @@ public static View FindDeepestView (View start, int x, int y, out int resx, out var startFrame = start.Frame; if (start.InternalSubviews != null) { - int count = start.InternalSubviews.Count; + var count = start.InternalSubviews.Count; if (count > 0) { var boundsOffset = start.GetBoundsOffset (); - int rx = x - (startFrame.X + boundsOffset.X); - int ry = y - (startFrame.Y + boundsOffset.Y); - for (int i = count - 1; i >= 0; i--) { + var rx = x - (startFrame.X + boundsOffset.X); + var ry = y - (startFrame.Y + boundsOffset.Y); + for (var i = count - 1; i >= 0; i--) { var v = start.InternalSubviews [i]; if (v.Visible && v.Frame.Contains (rx, ry)) { var deep = FindDeepestView (v, rx, ry, out resx, out resy); diff --git a/Terminal.Gui/View/SuperViewChangedEventArgs.cs b/Terminal.Gui/View/SuperViewChangedEventArgs.cs index fdd4da3cf2..9a7b437286 100644 --- a/Terminal.Gui/View/SuperViewChangedEventArgs.cs +++ b/Terminal.Gui/View/SuperViewChangedEventArgs.cs @@ -1,33 +1,34 @@ using System; -namespace Terminal.Gui { +namespace Terminal.Gui; + +/// +/// Args for events where the of a is changed +/// (e.g. / events). +/// +public class SuperViewChangedEventArgs : EventArgs { /// - /// Args for events where the of a is changed - /// (e.g. / events). + /// Creates a new instance of the class. /// - public class SuperViewChangedEventArgs : EventArgs + /// + /// + public SuperViewChangedEventArgs (View parent, View child) { - /// - /// Creates a new instance of the class. - /// - /// - /// - public SuperViewChangedEventArgs (View parent, View child) - { - Parent = parent; - Child = child; - } + Parent = parent; + Child = child; + } - /// - /// The parent. For this is the old - /// parent (new parent now being null). For - /// it is the new parent to whom view now belongs. - /// - public View Parent { get; } + // TODO: Parent is the wrong name. It should be SuperView. + /// + /// The parent. For this is the old + /// parent (new parent now being null). For + /// it is the new parent to whom view now belongs. + /// + public View Parent { get; } - /// - /// The view that is having it's changed - /// - public View Child { get; } - } -} + // TODO: Child is the wrong name. It should be View. + /// + /// The view that is having it's changed + /// + public View Child { get; } +} \ No newline at end of file diff --git a/Terminal.Gui/View/View.cs b/Terminal.Gui/View/View.cs index d6e798c03d..df3abf0590 100644 --- a/Terminal.Gui/View/View.cs +++ b/Terminal.Gui/View/View.cs @@ -1,315 +1,137 @@ using System; using System.ComponentModel; +using System.Diagnostics; namespace Terminal.Gui; #region API Docs /// -/// View is the base class for all views on the screen and represents a visible element that can render itself and +/// View is the base class for all views on the screen and represents a visible element that can render itself and /// contains zero or more nested views, called SubViews. View provides basic functionality for layout, positioning, /// and drawing. In addition, View provides keyboard and mouse event handling. /// /// /// -/// -/// TermDefinition -/// -/// -/// SubViewA View that is contained in another view and will be rendered as part of the containing view's ContentArea. -/// SubViews are added to another view via the ` method. A View may only be a SubView of a single View. -/// -/// -/// SuperViewThe View that is a container for SubViews. -/// +/// +/// TermDefinition +/// +/// +/// SubView +/// +/// A View that is contained in another view and will be rendered as part of the containing view's +/// ContentArea. +/// SubViews are added to another view via the ` method. A View may only be a +/// SubView of a single View. +/// +/// +/// +/// SuperViewThe View that is a container for SubViews. +/// /// /// /// Focus is a concept that is used to describe which View is currently receiving user input. Only Views that are /// , , and will receive focus. /// /// -/// Views that are focusable should implement the to make sure that -/// the cursor is placed in a location that makes sense. Unix terminals do not have -/// a way of hiding the cursor, so it can be distracting to have the cursor left at -/// the last focused view. So views should make sure that they place the cursor -/// in a visually sensible place. +/// Views that are focusable should implement the to make sure that +/// the cursor is placed in a location that makes sense. Unix terminals do not have +/// a way of hiding the cursor, so it can be distracting to have the cursor left at +/// the last focused view. So views should make sure that they place the cursor +/// in a visually sensible place. /// /// -/// The View defines the base functionality for user interface elements in Terminal.Gui. Views -/// can contain one or more subviews, can respond to user input and render themselves on the screen. +/// The View defines the base functionality for user interface elements in Terminal.Gui. Views +/// can contain one or more subviews, can respond to user input and render themselves on the screen. /// /// -/// Views supports two layout styles: or . -/// The choice as to which layout style is used by the View -/// is determined when the View is initialized. To create a View using Absolute layout, call a constructor that takes a -/// Rect parameter to specify the absolute position and size (the View.). To create a View -/// using Computed layout use a constructor that does not take a Rect parameter and set the X, Y, Width and Height -/// properties on the view. Both approaches use coordinates that are relative to the container they are being added to. +/// View supports two layout styles: or . +/// The style is determined by the values of , , , and +/// . +/// If any of these is set to non-absolute or object, +/// then the layout style is . Otherwise it is . /// /// -/// To switch between Absolute and Computed layout, use the property. +/// To create a View using Absolute layout, call a constructor that takes a +/// Rect parameter to specify the absolute position and size or simply set ). To create a View +/// using Computed layout use a constructor that does not take a Rect parameter and set the X, Y, Width and Height +/// properties on the view to non-absolute values. Both approaches use coordinates that are relative to the +/// of the the View is added to. /// /// -/// Computed layout is more flexible and supports dynamic console apps where controls adjust layout -/// as the terminal resizes or other Views change size or position. The X, Y, Width and Height -/// properties are Dim and Pos objects that dynamically update the position of a view. -/// The X and Y properties are of type -/// and you can use either absolute positions, percentages or anchor -/// points. The Width and Height properties are of type -/// and can use absolute position, -/// percentages and anchors. These are useful as they will take -/// care of repositioning views when view's frames are resized or -/// if the terminal size changes. +/// Computed layout is more flexible and supports dynamic console apps where controls adjust layout +/// as the terminal resizes or other Views change size or position. The +/// , , , and +/// properties are and objects that dynamically update the +/// position of a view. +/// The X and Y properties are of type +/// and you can use either absolute positions, percentages, or anchor +/// points. The Width and Height properties are of type +/// and can use absolute position, +/// percentages, and anchors. These are useful as they will take +/// care of repositioning views when view's frames are resized or +/// if the terminal size changes. /// /// -/// Absolute layout requires specifying coordinates and sizes of Views explicitly, and the -/// View will typically stay in a fixed position and size. To change the position and size use the -/// property. +/// Absolute layout requires specifying coordinates and sizes of Views explicitly, and the +/// View will typically stay in a fixed position and size. To change the position and size use the +/// property. /// /// -/// Subviews (child views) can be added to a View by calling the method. -/// The container of a View can be accessed with the property. +/// Subviews (child views) can be added to a View by calling the method. +/// The container of a View can be accessed with the property. /// /// -/// To flag a region of the View's to be redrawn call . -/// To flag the entire view for redraw call . +/// To flag a region of the View's to be redrawn call . +/// To flag the entire view for redraw call . /// /// -/// The method is invoked when the size or layout of a view has -/// changed. The default processing system will keep the size and dimensions -/// for views that use the , and will recompute the -/// frames for the vies that use . +/// The method is invoked when the size or layout of a view has +/// changed. The default processing system will keep the size and dimensions +/// for views that use the , and will recompute the +/// frames for the vies that use . /// /// -/// Views have a property that defines the default colors that subviews -/// should use for rendering. This ensures that the views fit in the context where -/// they are being used, and allows for themes to be plugged in. For example, the -/// default colors for windows and Toplevels uses a blue background, while it uses -/// a white background for dialog boxes and a red background for errors. +/// Views have a property that defines the default colors that subviews +/// should use for rendering. This ensures that the views fit in the context where +/// they are being used, and allows for themes to be plugged in. For example, the +/// default colors for windows and Toplevels uses a blue background, while it uses +/// a white background for dialog boxes and a red background for errors. /// /// -/// Subclasses should not rely on being -/// set at construction time. If a is not set on a view, the view will inherit the -/// value from its and the value might only be valid once a view has been -/// added to a SuperView. +/// Subclasses should not rely on being +/// set at construction time. If a is not set on a view, the view will inherit the +/// value from its and the value might only be valid once a view has been +/// added to a SuperView. /// /// -/// By using applications will work both -/// in color as well as black and white displays. +/// By using applications will work both +/// in color as well as black and white displays. /// /// -/// Views can also opt-in to more sophisticated initialization -/// by implementing overrides to and -/// which will be called -/// when the view is added to a . +/// Views can also opt-in to more sophisticated initialization +/// by implementing overrides to and +/// which will be called +/// when the view is added to a . /// /// -/// If first-run-only initialization is preferred, overrides to -/// can be implemented, in which case the -/// methods will only be called if -/// is . This allows proper inheritance hierarchies -/// to override base class layout code optimally by doing so only on first run, -/// instead of on every run. -/// +/// If first-run-only initialization is preferred, overrides to +/// can be implemented, in which case the +/// methods will only be called if +/// is . This allows proper inheritance hierarchies +/// to override base class layout code optimally by doing so only on first run, +/// instead of on every run. +/// /// -/// See for an overview of View keyboard handling. -/// /// +/// See for an overview of View keyboard handling. +/// +/// /// +/// #endregion API Docs public partial class View : Responder, ISupportInitializeNotification { - #region Constructors and Initialization - /// - /// Initializes a new instance of a class with the absolute - /// dimensions specified in the parameter. - /// - /// The region covered by this view. - /// - /// This constructor initialize a View with a of . - /// Use to initialize a View with of - /// - public View (Rect frame) : this (frame, null) { } - - /// - /// Initializes a new instance of using layout. - /// - /// - /// - /// Use , , , and properties to dynamically control the size and location of the view. - /// The will be created using - /// coordinates. The initial size () will be - /// adjusted to fit the contents of , including newlines ('\n') for multiple lines. - /// - /// - /// If is greater than one, word wrapping is provided. - /// - /// - /// This constructor initialize a View with a of . - /// Use , , , and properties to dynamically control the size and location of the view. - /// - /// - public View () : this (string.Empty, TextDirection.LeftRight_TopBottom) { } - - /// - /// Initializes a new instance of using layout. - /// - /// - /// - /// The will be created at the given - /// coordinates with the given string. The size () will be - /// adjusted to fit the contents of , including newlines ('\n') for multiple lines. - /// - /// - /// No line wrapping is provided. - /// - /// - /// column to locate the View. - /// row to locate the View. - /// text to initialize the property with. - public View (int x, int y, string text) : this (TextFormatter.CalcRect (x, y, text), text) { } - - /// - /// Initializes a new instance of using layout. - /// - /// - /// - /// The will be created at the given - /// coordinates with the given string. The initial size () will be - /// adjusted to fit the contents of , including newlines ('\n') for multiple lines. - /// - /// - /// If rect.Height is greater than one, word wrapping is provided. - /// - /// - /// Location. - /// text to initialize the property with. - public View (Rect rect, string text) => SetInitialProperties (text, rect, LayoutStyle.Absolute, TextDirection.LeftRight_TopBottom); - - /// - /// Initializes a new instance of using layout. - /// - /// - /// - /// The will be created using - /// coordinates with the given string. The initial size () will be - /// adjusted to fit the contents of , including newlines ('\n') for multiple lines. - /// - /// - /// If is greater than one, word wrapping is provided. - /// - /// - /// text to initialize the property with. - /// The text direction. - public View (string text, TextDirection direction = TextDirection.LeftRight_TopBottom) => SetInitialProperties (text, Rect.Empty, LayoutStyle.Computed, direction); - - // TODO: v2 - Remove constructors with parameters - /// - /// Private helper to set the initial properties of the View that were provided via constructors. - /// - /// - /// - /// - /// - void SetInitialProperties (string text, Rect rect, LayoutStyle layoutStyle = LayoutStyle.Computed, - TextDirection direction = TextDirection.LeftRight_TopBottom) - { - TextFormatter = new TextFormatter (); - TextFormatter.HotKeyChanged += TextFormatter_HotKeyChanged; - TextDirection = direction; - - CanFocus = false; - TabIndex = -1; - TabStop = false; - LayoutStyle = layoutStyle; - - Text = text == null ? string.Empty : text; - LayoutStyle = layoutStyle; - Frame = rect.IsEmpty ? TextFormatter.CalcRect (0, 0, text, direction) : rect; - OnResizeNeeded (); - - AddCommands (); - - CreateFrames (); - } - - /// - /// Get or sets if the has been initialized (via - /// and ). - /// - /// - /// If first-run-only initialization is preferred, overrides to - /// can be implemented, in which case the - /// methods will only be called if - /// is . This allows proper inheritance hierarchies - /// to override base class layout code optimally by doing so only on first run, - /// instead of on every run. - /// - public virtual bool IsInitialized { get; set; } - - /// - /// Signals the View that initialization is starting. See . - /// - /// - /// - /// Views can opt-in to more sophisticated initialization - /// by implementing overrides to and - /// which will be called - /// when the view is added to a . - /// - /// - /// If first-run-only initialization is preferred, overrides to - /// can be implemented too, in which case the - /// methods will only be called if - /// is . This allows proper inheritance hierarchies - /// to override base class layout code optimally by doing so only on first run, - /// instead of on every run. - /// - /// - public virtual void BeginInit () - { - if (!IsInitialized) { - _oldCanFocus = CanFocus; - _oldTabIndex = _tabIndex; - - - // TODO: Figure out why ScrollView and other tests fail if this call is put here - // instead of the constructor. - //InitializeFrames (); - - } else { - //throw new InvalidOperationException ("The view is already initialized."); - - } - - if (_subviews?.Count > 0) { - foreach (var view in _subviews) { - if (!view.IsInitialized) { - view.BeginInit (); - } - } - } - } - - /// - /// Signals the View that initialization is ending. See . - /// - public void EndInit () - { - IsInitialized = true; - // These calls were moved from BeginInit as they access Bounds which is indeterminate until EndInit is called. - UpdateTextDirection (TextDirection); - UpdateTextFormatterText (); - SetHotKey (); + bool _oldEnabled; - OnResizeNeeded (); - if (_subviews != null) { - foreach (var view in _subviews) { - if (!view.IsInitialized) { - view.EndInit (); - } - } - } - Initialized?.Invoke (this, EventArgs.Empty); - } - #endregion Constructors and Initialization + string _title = string.Empty; /// /// Points to the current driver in use by the view, it is a convenience property @@ -330,10 +152,9 @@ public void EndInit () /// The id should be unique across all Views that share a SuperView. public string Id { get; set; } = ""; - string _title = string.Empty; - /// - /// The title to be displayed for this . The title will be displayed if . + /// The title to be displayed for this . The title will be displayed if . + /// /// is greater than 0. /// /// The title. @@ -341,12 +162,12 @@ public string Title { get => _title; set { if (!OnTitleChanging (_title, value)) { - string old = _title; + var old = _title; _title = value; SetNeedsDisplay (); #if DEBUG if (_title != null && string.IsNullOrEmpty (Id)) { - Id = _title.ToString (); + Id = _title; } #endif // DEBUG OnTitleChanged (old, _title); @@ -354,51 +175,6 @@ public string Title { } } - /// - /// Called before the changes. Invokes the event, which can be cancelled. - /// - /// The that is/has been replaced. - /// The new to be replaced. - /// `true` if an event handler canceled the Title change. - public virtual bool OnTitleChanging (string oldTitle, string newTitle) - { - var args = new TitleEventArgs (oldTitle, newTitle); - TitleChanging?.Invoke (this, args); - return args.Cancel; - } - - /// - /// Event fired when the is changing. Set to - /// `true` to cancel the Title change. - /// - public event EventHandler TitleChanging; - - /// - /// Called when the has been changed. Invokes the event. - /// - /// The that is/has been replaced. - /// The new to be replaced. - public virtual void OnTitleChanged (string oldTitle, string newTitle) - { - var args = new TitleEventArgs (oldTitle, newTitle); - TitleChanged?.Invoke (this, args); - } - - /// - /// Event fired after the has been changed. - /// - public event EventHandler TitleChanged; - - /// - /// Event fired when the value is being changed. - /// - public event EventHandler EnabledChanged; - - /// - public override void OnEnabledChanged () => EnabledChanged?.Invoke (this, EventArgs.Empty); - - bool _oldEnabled; - /// public override bool Enabled { get => base.Enabled; @@ -432,20 +208,13 @@ public override bool Enabled { } } - /// - /// Event fired when the value is being changed. - /// - public event EventHandler VisibleChanged; - - /// - public override void OnVisibleChanged () => VisibleChanged?.Invoke (this, EventArgs.Empty); - /// /// Gets or sets whether a view is cleared if the property is . /// public bool ClearOnVisibleFalse { get; set; } = true; - /// > + /// + /// > public override bool Visible { get => base.Visible; set { @@ -465,6 +234,58 @@ public override bool Visible { } } + /// + /// Called before the changes. Invokes the event, which can be + /// cancelled. + /// + /// The that is/has been replaced. + /// The new to be replaced. + /// `true` if an event handler canceled the Title change. + public virtual bool OnTitleChanging (string oldTitle, string newTitle) + { + var args = new TitleEventArgs (oldTitle, newTitle); + TitleChanging?.Invoke (this, args); + return args.Cancel; + } + + /// + /// Event fired when the is changing. Set to + /// `true` to cancel the Title change. + /// + public event EventHandler TitleChanging; + + /// + /// Called when the has been changed. Invokes the event. + /// + /// The that is/has been replaced. + /// The new to be replaced. + public virtual void OnTitleChanged (string oldTitle, string newTitle) + { + var args = new TitleEventArgs (oldTitle, newTitle); + TitleChanged?.Invoke (this, args); + } + + /// + /// Event fired after the has been changed. + /// + public event EventHandler TitleChanged; + + /// + /// Event fired when the value is being changed. + /// + public event EventHandler EnabledChanged; + + /// + public override void OnEnabledChanged () => EnabledChanged?.Invoke (this, EventArgs.Empty); + + /// + /// Event fired when the value is being changed. + /// + public event EventHandler VisibleChanged; + + /// + public override void OnVisibleChanged () => VisibleChanged?.Invoke (this, EventArgs.Empty); + bool CanBeVisible (View view) { if (!view.Visible) { @@ -497,18 +318,260 @@ protected override void Dispose (bool disposing) Padding?.Dispose (); Padding = null; - _height = null; - _width = null; - _x = null; - _y = null; - - for (int i = InternalSubviews.Count - 1; i >= 0; i--) { + for (var i = InternalSubviews.Count - 1; i >= 0; i--) { var subview = InternalSubviews [i]; Remove (subview); subview.Dispose (); } base.Dispose (disposing); - System.Diagnostics.Debug.Assert (InternalSubviews.Count == 0); + Debug.Assert (InternalSubviews.Count == 0); } + + #region Constructors and Initialization + /// + /// Initializes a new instance of a class with the absolute + /// dimensions specified in the parameter. + /// + /// The region covered by this view. + /// + /// + /// Use , , , and properties to dynamically + /// control the size and location of the view. + /// The will be created using + /// coordinates. The initial size () will be + /// adjusted to fit the contents of , including newlines ('\n') for multiple lines. + /// + /// + /// If is greater than one, word wrapping is provided. + /// + /// + /// This constructor initialize a View with a of + /// . + /// Use , , , and properties to dynamically + /// control the size and location of the view, changing it to . + /// + /// + public View (Rect frame) : this (frame, null) { } + + /// + /// Initializes a new instance of . + /// + /// + /// + /// Use , , , and properties to dynamically + /// control the size and location of the view. + /// The will be created using + /// coordinates. The initial size () will be + /// adjusted to fit the contents of , including newlines ('\n') for multiple lines. + /// + /// + /// If is greater than one, word wrapping is provided. + /// + /// + /// This constructor initialize a View with a of + /// . + /// Use , , , and properties to dynamically + /// control the size and location of the view, changing it to . + /// + /// + public View () : this (string.Empty) { } + + /// + /// Initializes a new instance of in at the position specified with the + /// dimensions specified in the and parameters. + /// + /// + /// + /// The will be created at the given + /// coordinates with the given string. The size () will be + /// adjusted to fit the contents of , including newlines ('\n') for multiple lines. + /// + /// + /// Use , , , and properties to dynamically + /// control the size and location of the view. + /// + /// + /// If is greater than one, word wrapping is provided. + /// + /// + /// This constructor initialize a View with a of + /// . + /// Use , , , and properties to dynamically + /// control the size and location of the view, changing it to . + /// + /// + /// column to locate the View. + /// row to locate the View. + /// text to initialize the property with. + public View (int x, int y, string text) : this (TextFormatter.CalcRect (x, y, text), text) { } + + /// + /// Initializes a new instance of a class with the absolute + /// dimensions specified in the parameter. + /// + /// + /// + /// The will be created at the given + /// coordinates with the given string. The size () will be + /// adjusted to fit the contents of , including newlines ('\n') for multiple lines. + /// + /// + /// Use , , , and properties to dynamically + /// control the size and location of the view. + /// + /// + /// If is greater than one, word wrapping is provided. + /// + /// + /// This constructor initialize a View with a of + /// . + /// Use , , , and properties to dynamically + /// control the size and location of the view, changing it to . + /// + /// + /// Location. + /// text to initialize the property with. + public View (Rect rect, string text) => SetInitialProperties (text, rect, LayoutStyle.Absolute); + + + /// + /// Initializes a new instance of a class with using the given text and text styling information. + /// + /// + /// + /// If is greater than one, word wrapping is provided. + /// + /// + /// The will be created at the given + /// coordinates with the given string. The size () will be + /// adjusted to fit the contents of , including newlines ('\n') for multiple lines. + /// + /// + /// Use , , , and properties to dynamically + /// control the size and location of the view. + /// + /// + /// If is greater than one, word wrapping is provided. + /// + /// + /// This constructor initialize a View with a of + /// . + /// Use , , , and properties to dynamically + /// control the size and location of the view, changing it to . + /// + /// + /// text to initialize the property with. + /// The text direction. + public View (string text, TextDirection direction = TextDirection.LeftRight_TopBottom) => SetInitialProperties (text, Rect.Empty, LayoutStyle.Computed, direction); + + // TODO: v2 - Remove constructors with parameters + + + /// + /// Private helper to set the initial properties of the View that were provided via constructors. + /// + /// + /// + /// + /// + void SetInitialProperties (string text, + Rect rect, + LayoutStyle layoutStyle = LayoutStyle.Computed, + TextDirection direction = TextDirection.LeftRight_TopBottom) + { + TextFormatter = new TextFormatter (); + TextFormatter.HotKeyChanged += TextFormatter_HotKeyChanged; + TextDirection = direction; + + CanFocus = false; + TabIndex = -1; + TabStop = false; + + Text = text == null ? string.Empty : text; + Frame = rect.IsEmpty ? TextFormatter.CalcRect (0, 0, text, direction) : rect; + + AddCommands (); + + CreateFrames (); + } + + /// + /// Get or sets if the has been initialized (via + /// and ). + /// + /// + /// If first-run-only initialization is preferred, overrides to + /// can be implemented, in which case the + /// methods will only be called if + /// is . This allows proper inheritance hierarchies + /// to override base class layout code optimally by doing so only on first run, + /// instead of on every run. + /// + public virtual bool IsInitialized { get; set; } + + /// + /// Signals the View that initialization is starting. See . + /// + /// + /// + /// Views can opt-in to more sophisticated initialization + /// by implementing overrides to and + /// which will be called + /// when the view is added to a . + /// + /// + /// If first-run-only initialization is preferred, overrides to + /// can be implemented too, in which case the + /// methods will only be called if + /// is . This allows proper inheritance hierarchies + /// to override base class layout code optimally by doing so only on first run, + /// instead of on every run. + /// + /// + public virtual void BeginInit () + { + if (!IsInitialized) { + _oldCanFocus = CanFocus; + _oldTabIndex = _tabIndex; + + + // TODO: Figure out why ScrollView and other tests fail if this call is put here + // instead of the constructor. + //InitializeFrames (); + + } + + //throw new InvalidOperationException ("The view is already initialized."); + if (_subviews?.Count > 0) { + foreach (var view in _subviews) { + if (!view.IsInitialized) { + view.BeginInit (); + } + } + } + } + + /// + /// Signals the View that initialization is ending. See . + /// + public void EndInit () + { + IsInitialized = true; + // These calls were moved from BeginInit as they access Bounds which is indeterminate until EndInit is called. + UpdateTextDirection (TextDirection); + UpdateTextFormatterText (); + SetHotKey (); + + OnResizeNeeded (); + if (_subviews != null) { + foreach (var view in _subviews) { + if (!view.IsInitialized) { + view.EndInit (); + } + } + } + Initialized?.Invoke (this, EventArgs.Empty); + } + #endregion Constructors and Initialization } \ No newline at end of file diff --git a/Terminal.Gui/View/ViewDrawing.cs b/Terminal.Gui/View/ViewDrawing.cs index 94d8cad846..5c708c0415 100644 --- a/Terminal.Gui/View/ViewDrawing.cs +++ b/Terminal.Gui/View/ViewDrawing.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Linq; using System.Text; @@ -8,9 +7,12 @@ namespace Terminal.Gui; public partial class View { ColorScheme _colorScheme; + // The view-relative region that needs to be redrawn. Marked internal for unit tests. + internal Rect _needsDisplayRect = Rect.Empty; + /// /// The color scheme for this view, if it is not defined, it returns the 's - /// color scheme. + /// color scheme. /// public virtual ColorScheme ColorScheme { get { @@ -27,12 +29,47 @@ public virtual ColorScheme ColorScheme { } } + /// + /// Gets or sets whether the view needs to be redrawn. + /// + public bool NeedsDisplay { + get => _needsDisplayRect != Rect.Empty; + set { + if (value) { + SetNeedsDisplay (); + } else { + ClearNeedsDisplay (); + } + } + } + + /// + /// Gets whether any Subviews need to be redrawn. + /// + public bool SubViewNeedsDisplay { get; private set; } + + /// + /// The canvas that any line drawing that is to be shared by subviews of this view should add lines to. + /// + /// adds border lines to this LineCanvas. + public LineCanvas LineCanvas { get; } = new (); + + /// + /// Gets or sets whether this View will use it's SuperView's for + /// rendering any border lines. If the rendering of any borders drawn + /// by this Frame will be done by it's parent's SuperView. If (the default) + /// this View's method will be called to render the borders. + /// + public virtual bool SuperViewRendersLineCanvas { get; set; } = false; + /// /// Determines the current based on the value. /// - /// if is + /// + /// if is /// or if is . - /// If it's overridden can return other values. + /// If it's overridden can return other values. + /// public virtual Attribute GetNormalColor () { var cs = ColorScheme; @@ -45,17 +82,21 @@ public virtual Attribute GetNormalColor () /// /// Determines the current based on the value. /// - /// if is + /// + /// if is /// or if is . - /// If it's overridden can return other values. + /// If it's overridden can return other values. + /// public virtual Attribute GetFocusColor () => Enabled ? ColorScheme.Focus : ColorScheme.Disabled; /// /// Determines the current based on the value. /// - /// if is + /// + /// if is /// or if is . - /// If it's overridden can return other values. + /// If it's overridden can return other values. + /// public virtual Attribute GetHotNormalColor () => Enabled ? ColorScheme.HotNormal : ColorScheme.Disabled; /// @@ -82,24 +123,7 @@ public void AddRune (int col, int row, Rune ch) protected void ClearNeedsDisplay () { _needsDisplayRect = Rect.Empty; - _subViewNeedsDisplay = false; - } - - // The view-relative region that needs to be redrawn. Marked internal for unit tests. - internal Rect _needsDisplayRect = Rect.Empty; - - /// - /// Gets or sets whether the view needs to be redrawn. - /// - public bool NeedsDisplay { - get => _needsDisplayRect != Rect.Empty; - set { - if (value) { - SetNeedsDisplay (); - } else { - ClearNeedsDisplay (); - } - } + SubViewNeedsDisplay = false; } /// @@ -133,18 +157,18 @@ public void SetNeedsDisplay (Rect region) if (_needsDisplayRect.IsEmpty) { _needsDisplayRect = region; } else { - int x = Math.Min (_needsDisplayRect.X, region.X); - int y = Math.Min (_needsDisplayRect.Y, region.Y); - int w = Math.Max (_needsDisplayRect.Width, region.Width); - int h = Math.Max (_needsDisplayRect.Height, region.Height); + var x = Math.Min (_needsDisplayRect.X, region.X); + var y = Math.Min (_needsDisplayRect.Y, region.Y); + var w = Math.Max (_needsDisplayRect.Width, region.Width); + var h = Math.Max (_needsDisplayRect.Height, region.Height); _needsDisplayRect = new Rect (x, y, w, h); } _superView?.SetSubViewNeedsDisplay (); if (_needsDisplayRect.X < Bounds.X || - _needsDisplayRect.Y < Bounds.Y || - _needsDisplayRect.Width > Bounds.Width || - _needsDisplayRect.Height > Bounds.Height) { + _needsDisplayRect.Y < Bounds.Y || + _needsDisplayRect.Width > Bounds.Width || + _needsDisplayRect.Height > Bounds.Height) { Margin?.SetNeedsDisplay (Margin.Bounds); Border?.SetNeedsDisplay (Border.Bounds); Padding?.SetNeedsDisplay (Padding.Bounds); @@ -164,31 +188,24 @@ public void SetNeedsDisplay (Rect region) } } - /// - /// Gets whether any Subviews need to be redrawn. - /// - public bool SubViewNeedsDisplay => _subViewNeedsDisplay; - - bool _subViewNeedsDisplay; - /// /// Indicates that any Subviews (in the list) need to be repainted. /// public void SetSubViewNeedsDisplay () { - _subViewNeedsDisplay = true; - if (_superView != null && !_superView._subViewNeedsDisplay) { + SubViewNeedsDisplay = true; + if (_superView != null && !_superView.SubViewNeedsDisplay) { _superView.SetSubViewNeedsDisplay (); } } /// - /// Clears the with the normal background color. + /// Clears the with the normal background color. /// /// - /// - /// This clears the Bounds used by this view. - /// + /// + /// This clears the Bounds used by this view. + /// /// public void Clear () { @@ -202,7 +219,7 @@ public void Clear () // "View APIs only deal with View-relative coords". This is only used by ComboBox which can // be refactored to use the View-relative version. /// - /// Clears the specified screen-relative rectangle with the normal background. + /// Clears the specified screen-relative rectangle with the normal background. /// /// /// @@ -220,10 +237,10 @@ public void Clear (Rect regionScreen) // Clips a rectangle in screen coordinates to the dimensions currently available on the screen internal Rect ScreenClip (Rect regionScreen) { - int x = regionScreen.X < 0 ? 0 : regionScreen.X; - int y = regionScreen.Y < 0 ? 0 : regionScreen.Y; - int w = regionScreen.X + regionScreen.Width >= Driver.Cols ? Driver.Cols - regionScreen.X : regionScreen.Width; - int h = regionScreen.Y + regionScreen.Height >= Driver.Rows ? Driver.Rows - regionScreen.Y : regionScreen.Height; + var x = regionScreen.X < 0 ? 0 : regionScreen.X; + var y = regionScreen.Y < 0 ? 0 : regionScreen.Y; + var w = regionScreen.X + regionScreen.Width >= Driver.Cols ? Driver.Cols - regionScreen.X : regionScreen.Width; + var h = regionScreen.Y + regionScreen.Height >= Driver.Rows ? Driver.Rows - regionScreen.Y : regionScreen.Height; return new Rect (x, y, w, h); } @@ -231,11 +248,15 @@ internal Rect ScreenClip (Rect regionScreen) /// /// Expands the 's clip region to include . /// - /// The current screen-relative clip region, which can be then re-applied by setting . + /// + /// The current screen-relative clip region, which can be then re-applied by setting + /// . + /// /// - /// - /// If and do not intersect, the clip region will be set to . - /// + /// + /// If and do not intersect, the clip region will be set to + /// . + /// /// public Rect ClipToBounds () { @@ -251,14 +272,17 @@ public Rect ClipToBounds () /// Hot color. /// Normal color. /// - /// The hotkey is any character following the hotkey specifier, which is the underscore ('_') character by default. - /// The hotkey specifier can be changed via + /// + /// The hotkey is any character following the hotkey specifier, which is the underscore ('_') character by + /// default. + /// + /// The hotkey specifier can be changed via /// public void DrawHotString (string text, Attribute hotColor, Attribute normalColor) { var hotkeySpec = HotKeySpecifier == (Rune)0xffff ? (Rune)'_' : HotKeySpecifier; Application.Driver.SetAttribute (normalColor); - foreach (char rune in text) { + foreach (var rune in text) { if (rune == hotkeySpec.Value) { Application.Driver.SetAttribute (hotColor); continue; @@ -272,7 +296,10 @@ public void DrawHotString (string text, Attribute hotColor, Attribute normalColo /// Utility function to draw strings that contains a hotkey using a and the "focused" state. /// /// String to display, the underscore before a letter flags the next letter as the hotkey. - /// If set to this uses the focused colors from the color scheme, otherwise the regular ones. + /// + /// If set to this uses the focused colors from the color scheme, otherwise + /// the regular ones. + /// /// The color scheme to use. public void DrawHotString (string text, bool focused, ColorScheme scheme) { @@ -295,28 +322,15 @@ public void Move (int col, int row) return; } - BoundsToScreen (col, row, out int rCol, out int rRow, false); + BoundsToScreen (col, row, out var rCol, out var rRow, false); Driver?.Move (rCol, rRow); } - /// - /// The canvas that any line drawing that is to be shared by subviews of this view should add lines to. - /// - /// adds border lines to this LineCanvas. - public LineCanvas LineCanvas { get; } = new (); - - /// - /// Gets or sets whether this View will use it's SuperView's for - /// rendering any border lines. If the rendering of any borders drawn - /// by this Frame will be done by it's parent's SuperView. If (the default) - /// this View's method will be called to render the borders. - /// - public virtual bool SuperViewRendersLineCanvas { get; set; } = false; - // TODO: Make this cancelable /// - /// Prepares . If is true, only the of - /// this view's subviews will be rendered. If is false (the default), this + /// Prepares . If is true, only the + /// of + /// this view's subviews will be rendered. If is false (the default), this /// method will cause the be prepared to be rendered. /// /// @@ -336,21 +350,22 @@ public virtual bool OnDrawFrames () } /// - /// Draws the view. Causes the following virtual methods to be called (along with their related events): + /// Draws the view. Causes the following virtual methods to be called (along with their related events): /// , . /// /// - /// - /// Always use (view-relative) when calling , NOT (superview-relative). - /// - /// - /// Views should set the color that they want to use on entry, as otherwise this will inherit - /// the last color that was set globally on the driver. - /// - /// - /// Overrides of must ensure they do not set Driver.Clip to a clip region - /// larger than the property, as this will cause the driver to clip the entire region. - /// + /// + /// Always use (view-relative) when calling , NOT + /// (superview-relative). + /// + /// + /// Views should set the color that they want to use on entry, as otherwise this will inherit + /// the last color that was set globally on the driver. + /// + /// + /// Overrides of must ensure they do not set Driver.Clip to a clip region + /// larger than the property, as this will cause the driver to clip the entire region. + /// /// public void Draw () { @@ -387,8 +402,9 @@ public void Draw () // TODO: Make this cancelable /// - /// Renders . If is true, only the of - /// this view's subviews will be rendered. If is false (the default), this + /// Renders . If is true, only the + /// of + /// this view's subviews will be rendered. If is false (the default), this /// method will cause the to be rendered. /// /// @@ -411,7 +427,7 @@ public virtual bool OnRenderLineCanvas () } if (Subviews.Any (s => s.SuperViewRendersLineCanvas)) { - foreach (var subview in Subviews.Where (s => s.SuperViewRendersLineCanvas == true)) { + foreach (var subview in Subviews.Where (s => s.SuperViewRendersLineCanvas)) { // Combine the LineCanvas' LineCanvas.Merge (subview.LineCanvas); subview.LineCanvas.Clear (); @@ -434,21 +450,25 @@ public virtual bool OnRenderLineCanvas () /// Event invoked when the content area of the View is to be drawn. /// /// - /// - /// Will be invoked before any subviews added with have been drawn. - /// - /// - /// Rect provides the view-relative rectangle describing the currently visible viewport into the . - /// + /// + /// Will be invoked before any subviews added with have been drawn. + /// + /// + /// Rect provides the view-relative rectangle describing the currently visible viewport into the + /// . + /// /// public event EventHandler DrawContent; /// - /// Enables overrides to draw infinitely scrolled content and/or a background behind added controls. + /// Enables overrides to draw infinitely scrolled content and/or a background behind added controls. /// - /// The view-relative rectangle describing the currently visible viewport into the + /// + /// The view-relative rectangle describing the currently visible viewport into the + /// + /// /// - /// This method will be called before any subviews added with have been drawn. + /// This method will be called before any subviews added with have been drawn. /// public virtual void OnDrawContent (Rect contentArea) { @@ -475,8 +495,8 @@ public virtual void OnDrawContent (Rect contentArea) var subviewsNeedingDraw = _subviews.Where ( view => view.Visible && (view.NeedsDisplay || - view.SubViewNeedsDisplay || - view.LayoutNeeded) + view.SubViewNeedsDisplay || + view.LayoutNeeded) ); foreach (var view in subviewsNeedingDraw) { @@ -499,19 +519,23 @@ public virtual void OnDrawContent (Rect contentArea) /// Event invoked when the content area of the View is completed drawing. /// /// - /// - /// Will be invoked after any subviews removed with have been completed drawing. - /// - /// - /// Rect provides the view-relative rectangle describing the currently visible viewport into the . - /// + /// + /// Will be invoked after any subviews removed with have been completed drawing. + /// + /// + /// Rect provides the view-relative rectangle describing the currently visible viewport into the + /// . + /// /// public event EventHandler DrawContentComplete; /// /// Enables overrides after completed drawing infinitely scrolled content and/or a background behind removed controls. /// - /// The view-relative rectangle describing the currently visible viewport into the + /// + /// The view-relative rectangle describing the currently visible viewport into the + /// + /// /// /// This method will be called after any subviews removed with have been completed drawing. /// diff --git a/Terminal.Gui/View/ViewSubViews.cs b/Terminal.Gui/View/ViewSubViews.cs index c546200ed4..31566f5fb5 100644 --- a/Terminal.Gui/View/ViewSubViews.cs +++ b/Terminal.Gui/View/ViewSubViews.cs @@ -1,729 +1,725 @@ using System; using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Text; - -namespace Terminal.Gui { - public partial class View { - static readonly IList _empty = new List (0).AsReadOnly (); - - View _superView = null; - - /// - /// Returns the container for this view, or null if this view has not been added to a container. - /// - /// The super view. - public virtual View SuperView { - get { - return _superView; - } - set { - throw new NotImplementedException (); - } - } - List _subviews; // This is null, and allocated on demand. - /// - /// This returns a list of the subviews contained by this view. - /// - /// The subviews. - public IList Subviews => _subviews?.AsReadOnly () ?? _empty; - - // Internally, we use InternalSubviews rather than subviews, as we do not expect us - // to make the same mistakes our users make when they poke at the Subviews. - internal IList InternalSubviews => _subviews ?? _empty; - - /// - /// Returns a value indicating if this View is currently on Top (Active) - /// - public bool IsCurrentTop => Application.Current == this; - - /// - /// Event fired when this view is added to another. - /// - public event EventHandler Added; - - internal bool _addingView; - - /// - /// Adds a subview (child) to this view. - /// - /// - /// The Views that have been added to this view can be retrieved via the property. - /// See also - /// - public virtual void Add (View view) - { - if (view == null) { - return; - } - if (_subviews == null) { - _subviews = new List (); - } - if (_tabIndexes == null) { - _tabIndexes = new List (); - } - _subviews.Add (view); - _tabIndexes.Add (view); - view._superView = this; - if (view.CanFocus) { - _addingView = true; - if (SuperView?.CanFocus == false) { - SuperView._addingView = true; - SuperView.CanFocus = true; - SuperView._addingView = false; - } - CanFocus = true; - view._tabIndex = _tabIndexes.IndexOf (view); - _addingView = false; - } - if (view.Enabled && !Enabled) { - view._oldEnabled = true; - view.Enabled = false; - } +namespace Terminal.Gui; - OnAdded (new SuperViewChangedEventArgs (this, view)); - if (IsInitialized && !view.IsInitialized) { - view.BeginInit (); - view.EndInit (); - } +public partial class View { + static readonly IList _empty = new List (0).AsReadOnly (); - SetNeedsLayout (); - SetNeedsDisplay (); + internal bool _addingView; + + List _subviews; // This is null, and allocated on demand. + + View _superView; + + /// + /// Returns the container for this view, or null if this view has not been added to a container. + /// + /// The super view. + public virtual View SuperView { + get => _superView; + set => throw new NotImplementedException (); + } + + /// + /// This returns a list of the subviews contained by this view. + /// + /// The subviews. + public IList Subviews => _subviews?.AsReadOnly () ?? _empty; + + // Internally, we use InternalSubviews rather than subviews, as we do not expect us + // to make the same mistakes our users make when they poke at the Subviews. + internal IList InternalSubviews => _subviews ?? _empty; + + /// + /// Returns a value indicating if this View is currently on Top (Active) + /// + public bool IsCurrentTop => Application.Current == this; + + /// + /// Indicates whether the view was added to . + /// + public bool IsAdded { get; private set; } + + /// + /// Event fired when this view is added to another. + /// + public event EventHandler Added; + + /// + /// Adds a subview (child) to this view. + /// + /// + /// The Views that have been added to this view can be retrieved via the property. + /// See also + /// + public virtual void Add (View view) + { + if (view == null) { + return; + } + if (_subviews == null) { + _subviews = new List (); + } + if (_tabIndexes == null) { + _tabIndexes = new List (); + } + _subviews.Add (view); + _tabIndexes.Add (view); + view._superView = this; + if (view.CanFocus) { + _addingView = true; + if (SuperView?.CanFocus == false) { + SuperView._addingView = true; + SuperView.CanFocus = true; + SuperView._addingView = false; + } + CanFocus = true; + view._tabIndex = _tabIndexes.IndexOf (view); + _addingView = false; + } + if (view.Enabled && !Enabled) { + view._oldEnabled = true; + view.Enabled = false; } - /// - /// Adds the specified views (children) to the view. - /// - /// Array of one or more views (can be optional parameter). - /// - /// The Views that have been added to this view can be retrieved via the property. - /// See also - /// - public void Add (params View [] views) - { - if (views == null) { - return; - } - foreach (var view in views) { - Add (view); - } + OnAdded (new SuperViewChangedEventArgs (this, view)); + if (IsInitialized && !view.IsInitialized) { + view.BeginInit (); + view.EndInit (); } - /// - /// Method invoked when a subview is being added to this view. - /// - /// Event where is the subview being added. - public virtual void OnAdded (SuperViewChangedEventArgs e) - { - var view = e.Child; - view.IsAdded = true; - view.OnResizeNeeded (); - view._x ??= view._frame.X; - view._y ??= view._frame.Y; - view._width ??= view._frame.Width; - view._height ??= view._frame.Height; - - view.Added?.Invoke (this, e); - } - - /// - /// Indicates whether the view was added to . - /// - public bool IsAdded { get; private set; } - - /// - /// Event fired when this view is removed from another. - /// - public event EventHandler Removed; - - /// - /// Removes all subviews (children) added via or from this View. - /// - public virtual void RemoveAll () - { - if (_subviews == null) { - return; - } + SetNeedsLayout (); + SetNeedsDisplay (); + } - while (_subviews.Count > 0) { - Remove (_subviews [0]); - } + /// + /// Adds the specified views (children) to the view. + /// + /// Array of one or more views (can be optional parameter). + /// + /// The Views that have been added to this view can be retrieved via the property. + /// See also + /// + public void Add (params View [] views) + { + if (views == null) { + return; + } + foreach (var view in views) { + Add (view); } + } - /// - /// Removes a subview added via or from this View. - /// - /// - /// - public virtual void Remove (View view) - { - if (view == null || _subviews == null) return; - - var touched = view.Frame; - _subviews.Remove (view); - _tabIndexes.Remove (view); - view._superView = null; - view._tabIndex = -1; - SetNeedsLayout (); - SetNeedsDisplay (); + /// + /// Method invoked when a subview is being added to this view. + /// + /// Event where is the subview being added. + public virtual void OnAdded (SuperViewChangedEventArgs e) + { + var view = e.Child; + view.IsAdded = true; + view.OnResizeNeeded (); + view.Added?.Invoke (this, e); + } - foreach (var v in _subviews) { - if (v.Frame.IntersectsWith (touched)) - view.SetNeedsDisplay (); - } - OnRemoved (new SuperViewChangedEventArgs (this, view)); - if (_focused == view) { - _focused = null; - } + /// + /// Event fired when this view is removed from another. + /// + public event EventHandler Removed; + + /// + /// Removes all subviews (children) added via or from this View. + /// + public virtual void RemoveAll () + { + if (_subviews == null) { + return; } - /// - /// Method invoked when a subview is being removed from this view. - /// - /// Event args describing the subview being removed. - public virtual void OnRemoved (SuperViewChangedEventArgs e) - { - var view = e.Child; - view.IsAdded = false; - view.Removed?.Invoke (this, e); + while (_subviews.Count > 0) { + Remove (_subviews [0]); } + } + /// + /// Removes a subview added via or from this View. + /// + /// + /// + public virtual void Remove (View view) + { + if (view == null || _subviews == null) { + return; + } - void PerformActionForSubview (View subview, Action action) - { - if (_subviews.Contains (subview)) { - action (subview); - } + var touched = view.Frame; + _subviews.Remove (view); + _tabIndexes.Remove (view); + view._superView = null; + view._tabIndex = -1; + SetNeedsLayout (); + SetNeedsDisplay (); - SetNeedsDisplay (); - subview.SetNeedsDisplay (); - } - - /// - /// Brings the specified subview to the front so it is drawn on top of any other views. - /// - /// The subview to send to the front - /// - /// . - /// - public void BringSubviewToFront (View subview) - { - PerformActionForSubview (subview, x => { - _subviews.Remove (x); - _subviews.Add (x); - }); - } - - /// - /// Sends the specified subview to the front so it is the first view drawn - /// - /// The subview to send to the front - /// - /// . - /// - public void SendSubviewToBack (View subview) - { - PerformActionForSubview (subview, x => { - _subviews.Remove (x); - _subviews.Insert (0, subview); - }); - } - - /// - /// Moves the subview backwards in the hierarchy, only one step - /// - /// The subview to send backwards - /// - /// If you want to send the view all the way to the back use SendSubviewToBack. - /// - public void SendSubviewBackwards (View subview) - { - PerformActionForSubview (subview, x => { - var idx = _subviews.IndexOf (x); - if (idx > 0) { - _subviews.Remove (x); - _subviews.Insert (idx - 1, x); - } - }); - } - - /// - /// Moves the subview backwards in the hierarchy, only one step - /// - /// The subview to send backwards - /// - /// If you want to send the view all the way to the back use SendSubviewToBack. - /// - public void BringSubviewForward (View subview) - { - PerformActionForSubview (subview, x => { - var idx = _subviews.IndexOf (x); - if (idx + 1 < _subviews.Count) { - _subviews.Remove (x); - _subviews.Insert (idx + 1, x); - } - }); - } - - /// - /// Get the top superview of a given . - /// - /// The superview view. - public View GetTopSuperView (View view = null, View superview = null) - { - View top = superview ?? Application.Top; - for (var v = view?.SuperView ?? (this?.SuperView); v != null; v = v.SuperView) { - top = v; - if (top == superview) { - break; - } + foreach (var v in _subviews) { + if (v.Frame.IntersectsWith (touched)) { + view.SetNeedsDisplay (); } - - return top; } + OnRemoved (new SuperViewChangedEventArgs (this, view)); + if (Focused == view) { + Focused = null; + } + } + /// + /// Method invoked when a subview is being removed from this view. + /// + /// Event args describing the subview being removed. + public virtual void OnRemoved (SuperViewChangedEventArgs e) + { + var view = e.Child; + view.IsAdded = false; + view.Removed?.Invoke (this, e); + } - #region Focus - View _focused = null; + void PerformActionForSubview (View subview, Action action) + { + if (_subviews.Contains (subview)) { + action (subview); + } - internal enum Direction { - Forward, - Backward + SetNeedsDisplay (); + subview.SetNeedsDisplay (); + } + + /// + /// Brings the specified subview to the front so it is drawn on top of any other views. + /// + /// The subview to send to the front + /// + /// . + /// + public void BringSubviewToFront (View subview) => PerformActionForSubview (subview, x => { + _subviews.Remove (x); + _subviews.Add (x); + }); + + /// + /// Sends the specified subview to the front so it is the first view drawn + /// + /// The subview to send to the front + /// + /// . + /// + public void SendSubviewToBack (View subview) => PerformActionForSubview (subview, x => { + _subviews.Remove (x); + _subviews.Insert (0, subview); + }); + + /// + /// Moves the subview backwards in the hierarchy, only one step + /// + /// The subview to send backwards + /// + /// If you want to send the view all the way to the back use SendSubviewToBack. + /// + public void SendSubviewBackwards (View subview) => PerformActionForSubview (subview, x => { + var idx = _subviews.IndexOf (x); + if (idx > 0) { + _subviews.Remove (x); + _subviews.Insert (idx - 1, x); + } + }); + + /// + /// Moves the subview backwards in the hierarchy, only one step + /// + /// The subview to send backwards + /// + /// If you want to send the view all the way to the back use SendSubviewToBack. + /// + public void BringSubviewForward (View subview) => PerformActionForSubview (subview, x => { + var idx = _subviews.IndexOf (x); + if (idx + 1 < _subviews.Count) { + _subviews.Remove (x); + _subviews.Insert (idx + 1, x); + } + }); + + /// + /// Get the top superview of a given . + /// + /// The superview view. + public View GetTopSuperView (View view = null, View superview = null) + { + var top = superview ?? Application.Top; + for (var v = view?.SuperView ?? this?.SuperView; v != null; v = v.SuperView) { + top = v; + if (top == superview) { + break; + } } - /// - /// Event fired when the view gets focus. - /// - public event EventHandler Enter; + return top; + } + - /// - /// Event fired when the view looses focus. - /// - public event EventHandler Leave; - Direction _focusDirection; - internal Direction FocusDirection { - get => SuperView?.FocusDirection ?? _focusDirection; - set { - if (SuperView != null) - SuperView.FocusDirection = value; - else - _focusDirection = value; + #region Focus + internal enum Direction { + Forward, + Backward + } + + /// + /// Event fired when the view gets focus. + /// + public event EventHandler Enter; + + /// + /// Event fired when the view looses focus. + /// + public event EventHandler Leave; + + Direction _focusDirection; + + internal Direction FocusDirection { + get => SuperView?.FocusDirection ?? _focusDirection; + set { + if (SuperView != null) { + SuperView.FocusDirection = value; + } else { + _focusDirection = value; } } + } - // BUGBUG: v2 - Seems weird that this is in View and not Responder. - bool _hasFocus; + // BUGBUG: v2 - Seems weird that this is in View and not Responder. + bool _hasFocus; - /// - public override bool HasFocus => _hasFocus; + /// + public override bool HasFocus => _hasFocus; - void SetHasFocus (bool value, View view, bool force = false) - { - if (_hasFocus != value || force) { - _hasFocus = value; - if (value) { - OnEnter (view); - } else { - OnLeave (view); - } - SetNeedsDisplay (); + void SetHasFocus (bool value, View view, bool force = false) + { + if (_hasFocus != value || force) { + _hasFocus = value; + if (value) { + OnEnter (view); + } else { + OnLeave (view); } + SetNeedsDisplay (); + } - // Remove focus down the chain of subviews if focus is removed - if (!value && _focused != null) { - var f = _focused; - f.OnLeave (view); - f.SetHasFocus (false, view); - _focused = null; - } + // Remove focus down the chain of subviews if focus is removed + if (!value && Focused != null) { + var f = Focused; + f.OnLeave (view); + f.SetHasFocus (false, view); + Focused = null; } + } - /// - /// Event fired when the value is being changed. - /// - public event EventHandler CanFocusChanged; + /// + /// Event fired when the value is being changed. + /// + public event EventHandler CanFocusChanged; - /// - public override void OnCanFocusChanged () => CanFocusChanged?.Invoke (this, EventArgs.Empty); + /// + public override void OnCanFocusChanged () => CanFocusChanged?.Invoke (this, EventArgs.Empty); + + bool _oldCanFocus; + + /// + public override bool CanFocus { + get => base.CanFocus; + set { + if (!_addingView && IsInitialized && SuperView?.CanFocus == false && value) { + throw new InvalidOperationException ("Cannot set CanFocus to true if the SuperView CanFocus is false!"); + } + if (base.CanFocus != value) { + base.CanFocus = value; - bool _oldCanFocus; - /// - public override bool CanFocus { - get => base.CanFocus; - set { - if (!_addingView && IsInitialized && SuperView?.CanFocus == false && value) { - throw new InvalidOperationException ("Cannot set CanFocus to true if the SuperView CanFocus is false!"); + switch (value) { + case false when _tabIndex > -1: + TabIndex = -1; + break; + case true when SuperView?.CanFocus == false && _addingView: + SuperView.CanFocus = true; + break; } - if (base.CanFocus != value) { - base.CanFocus = value; - - switch (value) { - case false when _tabIndex > -1: - TabIndex = -1; - break; - case true when SuperView?.CanFocus == false && _addingView: - SuperView.CanFocus = true; - break; - } - if (value && _tabIndex == -1) { - TabIndex = SuperView != null ? SuperView._tabIndexes.IndexOf (this) : -1; - } - TabStop = value; + if (value && _tabIndex == -1) { + TabIndex = SuperView != null ? SuperView._tabIndexes.IndexOf (this) : -1; + } + TabStop = value; - if (!value && SuperView?.Focused == this) { - SuperView._focused = null; - } - if (!value && HasFocus) { - SetHasFocus (false, this); - SuperView?.EnsureFocus (); - if (SuperView != null && SuperView.Focused == null) { - SuperView.FocusNext (); - if (SuperView.Focused == null && Application.Current != null) { - Application.Current.FocusNext (); - } - Application.BringOverlappedTopToFront (); + if (!value && SuperView?.Focused == this) { + SuperView.Focused = null; + } + if (!value && HasFocus) { + SetHasFocus (false, this); + SuperView?.EnsureFocus (); + if (SuperView != null && SuperView.Focused == null) { + SuperView.FocusNext (); + if (SuperView.Focused == null && Application.Current != null) { + Application.Current.FocusNext (); } + Application.BringOverlappedTopToFront (); } - if (_subviews != null && IsInitialized) { - foreach (var view in _subviews) { - if (view.CanFocus != value) { - if (!value) { - view._oldCanFocus = view.CanFocus; - view._oldTabIndex = view._tabIndex; - view.CanFocus = false; - view._tabIndex = -1; - } else { - if (_addingView) { - view._addingView = true; - } - view.CanFocus = view._oldCanFocus; - view._tabIndex = view._oldTabIndex; - view._addingView = false; + } + if (_subviews != null && IsInitialized) { + foreach (var view in _subviews) { + if (view.CanFocus != value) { + if (!value) { + view._oldCanFocus = view.CanFocus; + view._oldTabIndex = view._tabIndex; + view.CanFocus = false; + view._tabIndex = -1; + } else { + if (_addingView) { + view._addingView = true; } + view.CanFocus = view._oldCanFocus; + view._tabIndex = view._oldTabIndex; + view._addingView = false; } } } - OnCanFocusChanged (); - SetNeedsDisplay (); } + OnCanFocusChanged (); + SetNeedsDisplay (); } } + } - /// - public override bool OnEnter (View view) - { - var args = new FocusEventArgs (view); - Enter?.Invoke (this, args); - if (args.Handled) { - return true; - } - if (base.OnEnter (view)) { - return true; - } + /// + public override bool OnEnter (View view) + { + var args = new FocusEventArgs (view); + Enter?.Invoke (this, args); + if (args.Handled) { + return true; + } + if (base.OnEnter (view)) { + return true; + } - return false; + return false; + } + + /// + public override bool OnLeave (View view) + { + var args = new FocusEventArgs (view); + Leave?.Invoke (this, args); + if (args.Handled) { + return true; + } + if (base.OnLeave (view)) { + return true; } - /// - public override bool OnLeave (View view) - { - var args = new FocusEventArgs (view); - Leave?.Invoke (this, args); - if (args.Handled) { - return true; + Driver?.SetCursorVisibility (CursorVisibility.Invisible); + return false; + } + + /// + /// Returns the currently focused view inside this view, or null if nothing is focused. + /// + /// The focused. + public View Focused { get; private set; } + + /// + /// Returns the most focused view in the chain of subviews (the leaf view that has the focus). + /// + /// The most focused View. + public View MostFocused { + get { + if (Focused == null) { + return null; } - if (base.OnLeave (view)) { - return true; + var most = Focused.MostFocused; + if (most != null) { + return most; } - - Driver?.SetCursorVisibility (CursorVisibility.Invisible); - return false; + return Focused; } + } - /// - /// Returns the currently focused view inside this view, or null if nothing is focused. - /// - /// The focused. - public View Focused => _focused; - - /// - /// Returns the most focused view in the chain of subviews (the leaf view that has the focus). - /// - /// The most focused View. - public View MostFocused { - get { - if (Focused == null) - return null; - var most = Focused.MostFocused; - if (most != null) - return most; - return Focused; - } + /// + /// Causes the specified subview to have focus. + /// + /// View. + void SetFocus (View view) + { + if (view == null) { + return; + } + //Console.WriteLine ($"Request to focus {view}"); + if (!view.CanFocus || !view.Visible || !view.Enabled) { + return; } + if (Focused?._hasFocus == true && Focused == view) { + return; + } + if (Focused?._hasFocus == true && Focused?.SuperView == view || view == this) { - /// - /// Causes the specified subview to have focus. - /// - /// View. - void SetFocus (View view) - { - if (view == null) { - return; - } - //Console.WriteLine ($"Request to focus {view}"); - if (!view.CanFocus || !view.Visible || !view.Enabled) { - return; + if (!view._hasFocus) { + view._hasFocus = true; } - if (_focused?._hasFocus == true && _focused == view) { - return; + return; + } + // Make sure that this view is a subview + View c; + for (c = view._superView; c != null; c = c._superView) { + if (c == this) { + break; } - if ((_focused?._hasFocus == true && _focused?.SuperView == view) || view == this) { + } + if (c == null) { + throw new ArgumentException ("the specified view is not part of the hierarchy of this view"); + } - if (!view._hasFocus) { - view._hasFocus = true; - } - return; - } - // Make sure that this view is a subview - View c; - for (c = view._superView; c != null; c = c._superView) - if (c == this) - break; - if (c == null) - throw new ArgumentException ("the specified view is not part of the hierarchy of this view"); + if (Focused != null) { + Focused.SetHasFocus (false, view); + } - if (_focused != null) - _focused.SetHasFocus (false, view); + var f = Focused; + Focused = view; + Focused.SetHasFocus (true, f); + Focused.EnsureFocus (); - var f = _focused; - _focused = view; - _focused.SetHasFocus (true, f); - _focused.EnsureFocus (); + // Send focus upwards + if (SuperView != null) { + SuperView.SetFocus (this); + } else { + SetFocus (this); + } + } - // Send focus upwards - if (SuperView != null) { - SuperView.SetFocus (this); - } else { - SetFocus (this); + /// + /// Causes the specified view and the entire parent hierarchy to have the focused order updated. + /// + public void SetFocus () + { + if (!CanBeVisible (this) || !Enabled) { + if (HasFocus) { + SetHasFocus (false, this); } + return; } - /// - /// Causes the specified view and the entire parent hierarchy to have the focused order updated. - /// - public void SetFocus () - { - if (!CanBeVisible (this) || !Enabled) { - if (HasFocus) { - SetHasFocus (false, this); - } - return; - } + if (SuperView != null) { + SuperView.SetFocus (this); + } else { + SetFocus (this); + } + } - if (SuperView != null) { - SuperView.SetFocus (this); + /// + /// Finds the first view in the hierarchy that wants to get the focus if nothing is currently focused, otherwise, does + /// nothing. + /// + public void EnsureFocus () + { + if (Focused == null && _subviews?.Count > 0) { + if (FocusDirection == Direction.Forward) { + FocusFirst (); } else { - SetFocus (this); + FocusLast (); } } + } - /// - /// Finds the first view in the hierarchy that wants to get the focus if nothing is currently focused, otherwise, does nothing. - /// - public void EnsureFocus () - { - if (_focused == null && _subviews?.Count > 0) { - if (FocusDirection == Direction.Forward) { - FocusFirst (); - } else { - FocusLast (); - } - } + /// + /// Focuses the first focusable subview if one exists. + /// + public void FocusFirst () + { + if (!CanBeVisible (this)) { + return; } - /// - /// Focuses the first focusable subview if one exists. - /// - public void FocusFirst () - { - if (!CanBeVisible (this)) { - return; - } + if (_tabIndexes == null) { + SuperView?.SetFocus (this); + return; + } - if (_tabIndexes == null) { - SuperView?.SetFocus (this); + foreach (var view in _tabIndexes) { + if (view.CanFocus && view._tabStop && view.Visible && view.Enabled) { + SetFocus (view); return; } + } + } - foreach (var view in _tabIndexes) { - if (view.CanFocus && view._tabStop && view.Visible && view.Enabled) { - SetFocus (view); - return; - } - } + /// + /// Focuses the last focusable subview if one exists. + /// + public void FocusLast () + { + if (!CanBeVisible (this)) { + return; } - /// - /// Focuses the last focusable subview if one exists. - /// - public void FocusLast () - { - if (!CanBeVisible (this)) { - return; - } + if (_tabIndexes == null) { + SuperView?.SetFocus (this); + return; + } - if (_tabIndexes == null) { - SuperView?.SetFocus (this); + for (var i = _tabIndexes.Count; i > 0;) { + i--; + + var v = _tabIndexes [i]; + if (v.CanFocus && v._tabStop && v.Visible && v.Enabled) { + SetFocus (v); return; } + } + } - for (var i = _tabIndexes.Count; i > 0;) { - i--; + /// + /// Focuses the previous view. + /// + /// if previous was focused, otherwise. + public bool FocusPrev () + { + if (!CanBeVisible (this)) { + return false; + } - var v = _tabIndexes [i]; - if (v.CanFocus && v._tabStop && v.Visible && v.Enabled) { - SetFocus (v); - return; - } - } + FocusDirection = Direction.Backward; + if (_tabIndexes == null || _tabIndexes.Count == 0) { + return false; } - /// - /// Focuses the previous view. - /// - /// if previous was focused, otherwise. - public bool FocusPrev () - { - if (!CanBeVisible (this)) { - return false; - } + if (Focused == null) { + FocusLast (); + return Focused != null; + } - FocusDirection = Direction.Backward; - if (_tabIndexes == null || _tabIndexes.Count == 0) - return false; + var focusedIdx = -1; + for (var i = _tabIndexes.Count; i > 0;) { + i--; + var w = _tabIndexes [i]; - if (_focused == null) { - FocusLast (); - return _focused != null; + if (w.HasFocus) { + if (w.FocusPrev ()) { + return true; + } + focusedIdx = i; + continue; } + if (w.CanFocus && focusedIdx != -1 && w._tabStop && w.Visible && w.Enabled) { + Focused.SetHasFocus (false, w); - var focusedIdx = -1; - for (var i = _tabIndexes.Count; i > 0;) { - i--; - var w = _tabIndexes [i]; - - if (w.HasFocus) { - if (w.FocusPrev ()) - return true; - focusedIdx = i; - continue; + if (w.CanFocus && w._tabStop && w.Visible && w.Enabled) { + w.FocusLast (); } - if (w.CanFocus && focusedIdx != -1 && w._tabStop && w.Visible && w.Enabled) { - _focused.SetHasFocus (false, w); - - if (w.CanFocus && w._tabStop && w.Visible && w.Enabled) - w.FocusLast (); - SetFocus (w); - return true; - } - } - if (_focused != null) { - _focused.SetHasFocus (false, this); - _focused = null; + SetFocus (w); + return true; } - return false; } + if (Focused != null) { + Focused.SetHasFocus (false, this); + Focused = null; + } + return false; + } - /// - /// Focuses the next view. - /// - /// if next was focused, otherwise. - public bool FocusNext () - { - if (!CanBeVisible (this)) { - return false; - } - - FocusDirection = Direction.Forward; - if (_tabIndexes == null || _tabIndexes.Count == 0) - return false; + /// + /// Focuses the next view. + /// + /// if next was focused, otherwise. + public bool FocusNext () + { + if (!CanBeVisible (this)) { + return false; + } - if (_focused == null) { - FocusFirst (); - return _focused != null; - } - var focusedIdx = -1; - for (var i = 0; i < _tabIndexes.Count; i++) { - var w = _tabIndexes [i]; - - if (w.HasFocus) { - if (w.FocusNext ()) - return true; - focusedIdx = i; - continue; - } - if (w.CanFocus && focusedIdx != -1 && w._tabStop && w.Visible && w.Enabled) { - _focused.SetHasFocus (false, w); + FocusDirection = Direction.Forward; + if (_tabIndexes == null || _tabIndexes.Count == 0) { + return false; + } - if (w.CanFocus && w._tabStop && w.Visible && w.Enabled) - w.FocusFirst (); + if (Focused == null) { + FocusFirst (); + return Focused != null; + } + var focusedIdx = -1; + for (var i = 0; i < _tabIndexes.Count; i++) { + var w = _tabIndexes [i]; - SetFocus (w); + if (w.HasFocus) { + if (w.FocusNext ()) { return true; } + focusedIdx = i; + continue; } - if (_focused != null) { - _focused.SetHasFocus (false, this); - _focused = null; - } - return false; - } + if (w.CanFocus && focusedIdx != -1 && w._tabStop && w.Visible && w.Enabled) { + Focused.SetHasFocus (false, w); - View GetMostFocused (View view) - { - if (view == null) { - return null; + if (w.CanFocus && w._tabStop && w.Visible && w.Enabled) { + w.FocusFirst (); + } + + SetFocus (w); + return true; } + } + if (Focused != null) { + Focused.SetHasFocus (false, this); + Focused = null; + } + return false; + } - return view._focused != null ? GetMostFocused (view._focused) : view; + View GetMostFocused (View view) + { + if (view == null) { + return null; } - /// - /// Positions the cursor in the right position based on the currently focused view in the chain. - /// - /// Views that are focusable should override to ensure - /// the cursor is placed in a location that makes sense. Unix terminals do not have - /// a way of hiding the cursor, so it can be distracting to have the cursor left at - /// the last focused view. Views should make sure that they place the cursor - /// in a visually sensible place. - public virtual void PositionCursor () - { - if (!CanBeVisible (this) || !Enabled) { - return; - } + return view.Focused != null ? GetMostFocused (view.Focused) : view; + } - // BUGBUG: v2 - This needs to support children of Frames too + /// + /// Positions the cursor in the right position based on the currently focused view in the chain. + /// + /// Views that are focusable should override + /// + /// to ensure + /// the cursor is placed in a location that makes sense. Unix terminals do not have + /// a way of hiding the cursor, so it can be distracting to have the cursor left at + /// the last focused view. Views should make sure that they place the cursor + /// in a visually sensible place. + public virtual void PositionCursor () + { + if (!CanBeVisible (this) || !Enabled) { + return; + } - if (_focused == null && SuperView != null) { - SuperView.EnsureFocus (); - } else if (_focused?.Visible == true && _focused?.Enabled == true && _focused?.Frame.Width > 0 && _focused.Frame.Height > 0) { - _focused.PositionCursor (); - } else if (_focused?.Visible == true && _focused?.Enabled == false) { - _focused = null; - } else if (CanFocus && HasFocus && Visible && Frame.Width > 0 && Frame.Height > 0) { - Move (TextFormatter.HotKeyPos == -1 ? 0 : TextFormatter.CursorPosition, 0); - } else { - Move (_frame.X, _frame.Y); - } + // BUGBUG: v2 - This needs to support children of Frames too + + if (Focused == null && SuperView != null) { + SuperView.EnsureFocus (); + } else if (Focused?.Visible == true && Focused?.Enabled == true && Focused?.Frame.Width > 0 && Focused.Frame.Height > 0) { + Focused.PositionCursor (); + } else if (Focused?.Visible == true && Focused?.Enabled == false) { + Focused = null; + } else if (CanFocus && HasFocus && Visible && Frame.Width > 0 && Frame.Height > 0) { + Move (TextFormatter.HotKeyPos == -1 ? 0 : TextFormatter.CursorPosition, 0); + } else { + Move (_frame.X, _frame.Y); } - #endregion Focus } -} + #endregion Focus +} \ No newline at end of file diff --git a/Terminal.Gui/View/ViewText.cs b/Terminal.Gui/View/ViewText.cs index 8b3eb0e9d6..6f412bf27b 100644 --- a/Terminal.Gui/View/ViewText.cs +++ b/Terminal.Gui/View/ViewText.cs @@ -7,24 +7,28 @@ public partial class View { string _text; /// - /// The text displayed by the . + /// The text displayed by the . /// /// - /// - /// The text will be drawn before any subviews are drawn. - /// - /// - /// The text will be drawn starting at the view origin (0, 0) and will be formatted according - /// to and . - /// - /// - /// The text will word-wrap to additional lines if it does not fit horizontally. If 's height - /// is 1, the text will be clipped. - /// - /// - /// Set the to enable hotkey support. To disable hotkey support set to - /// (Rune)0xffff. - /// + /// + /// The text will be drawn before any subviews are drawn. + /// + /// + /// The text will be drawn starting at the view origin (0, 0) and will be formatted according + /// to and . + /// + /// + /// The text will word-wrap to additional lines if it does not fit horizontally. If 's height + /// is 1, the text will be clipped. + /// + /// + /// Set the to enable hotkey support. To disable hotkey support set + /// to + /// (Rune)0xffff. + /// + /// + /// If is true, the will be adjusted to fit the text. + /// /// public virtual string Text { get => _text; @@ -32,7 +36,6 @@ public virtual string Text { _text = value; SetHotKey (); UpdateTextFormatterText (); - //TextFormatter.Format (); OnResizeNeeded (); #if DEBUG @@ -48,21 +51,10 @@ public virtual string Text { /// public TextFormatter TextFormatter { get; set; } - /// - /// Can be overridden if the has - /// different format than the default. - /// - protected virtual void UpdateTextFormatterText () - { - if (TextFormatter != null) { - TextFormatter.Text = _text; - } - } - /// /// Gets or sets whether trailing spaces at the end of word-wrapped lines are preserved - /// or not when is enabled. - /// If trailing spaces at the end of wrapped lines will be removed when + /// or not when is enabled. + /// If trailing spaces at the end of wrapped lines will be removed when /// is formatted for display. The default is . /// public virtual bool PreserveTrailingSpaces { @@ -76,8 +68,14 @@ public virtual bool PreserveTrailingSpaces { } /// - /// Gets or sets how the View's is aligned horizontally when drawn. Changing this property will redisplay the . + /// Gets or sets how the View's is aligned horizontally when drawn. Changing this property will + /// redisplay the . /// + /// + /// + /// If is true, the will be adjusted to fit the text. + /// + /// /// The text alignment. public virtual TextAlignment TextAlignment { get => TextFormatter.Alignment; @@ -89,8 +87,14 @@ public virtual TextAlignment TextAlignment { } /// - /// Gets or sets how the View's is aligned vertically when drawn. Changing this property will redisplay the . + /// Gets or sets how the View's is aligned vertically when drawn. Changing this property will redisplay + /// the . /// + /// + /// + /// If is true, the will be adjusted to fit the text. + /// + /// /// The text alignment. public virtual VerticalTextAlignment VerticalTextAlignment { get => TextFormatter.VerticalAlignment; @@ -101,8 +105,14 @@ public virtual VerticalTextAlignment VerticalTextAlignment { } /// - /// Gets or sets the direction of the View's . Changing this property will redisplay the . + /// Gets or sets the direction of the View's . Changing this property will redisplay the + /// . /// + /// + /// + /// If is true, the will be adjusted to fit the text. + /// + /// /// The text alignment. public virtual TextDirection TextDirection { get => TextFormatter.Direction; @@ -112,18 +122,27 @@ public virtual TextDirection TextDirection { } } + /// + /// Can be overridden if the has + /// different format than the default. + /// + protected virtual void UpdateTextFormatterText () + { + if (TextFormatter != null) { + TextFormatter.Text = _text; + } + } + void UpdateTextDirection (TextDirection newDirection) { - bool directionChanged = TextFormatter.IsHorizontalDirection (TextFormatter.Direction) - != TextFormatter.IsHorizontalDirection (newDirection); + var directionChanged = TextFormatter.IsHorizontalDirection (TextFormatter.Direction) != TextFormatter.IsHorizontalDirection (newDirection); TextFormatter.Direction = newDirection; - bool isValidOldAutoSize = AutoSize && IsValidAutoSize (out var _); + var isValidOldAutoSize = AutoSize && IsValidAutoSize (out var _); UpdateTextFormatterText (); - if (!ValidatePosDim && directionChanged && AutoSize - || ValidatePosDim && directionChanged && AutoSize && isValidOldAutoSize) { + if (!ValidatePosDim && directionChanged && AutoSize || ValidatePosDim && directionChanged && AutoSize && isValidOldAutoSize) { OnResizeNeeded (); } else if (directionChanged && IsAdded) { ResizeBoundsToFit (Bounds.Size); @@ -132,16 +151,19 @@ void UpdateTextDirection (TextDirection newDirection) } else { SetFrameToFitText (); } - TextFormatter.Size = GetTextFormatterSizeNeededForTextAndHotKey (); + SetTextFormatterSize (); SetNeedsDisplay (); } /// - /// Sets the size of the View to the minimum width or height required to fit . + /// Sets the size of the View to the minimum width or height required to fit . /// - /// if the size was changed; if == or - /// will not fit. + /// + /// if the size was changed; if == + /// or + /// will not fit. + /// /// /// Always returns if is or /// if (Horizontal) or (Vertical) are not not set or zero. @@ -168,89 +190,150 @@ bool GetMinimumSizeOfText (out Size sizeRequired) } sizeRequired = Bounds.Size; - if (!AutoSize && !string.IsNullOrEmpty (TextFormatter.Text)) { - switch (TextFormatter.IsVerticalDirection (TextDirection)) { - case true: - int colWidth = TextFormatter.GetSumMaxCharWidth (new List { TextFormatter.Text }, 0, 1); - // TODO: v2 - This uses frame.Width; it should only use Bounds - if (_frame.Width < colWidth && - (Width == null || - Bounds.Width >= 0 && - Width is Dim.DimAbsolute && - Width.Anchor (0) >= 0 && - Width.Anchor (0) < colWidth)) { - sizeRequired = new Size (colWidth, Bounds.Height); - return true; - } - break; - default: - if (_frame.Height < 1 && - (Height == null || - Height is Dim.DimAbsolute && - Height.Anchor (0) == 0)) { - sizeRequired = new Size (Bounds.Width, 1); - return true; - } - break; + if (AutoSize || string.IsNullOrEmpty (TextFormatter.Text)) { + return false; + } + + switch (TextFormatter.IsVerticalDirection (TextDirection)) { + case true: + var colWidth = TextFormatter.GetSumMaxCharWidth (new List { TextFormatter.Text }, 0, 1); + // TODO: v2 - This uses frame.Width; it should only use Bounds + if (_frame.Width < colWidth && + (Width == null || + Bounds.Width >= 0 && + Width is Dim.DimAbsolute && + Width.Anchor (0) >= 0 && + Width.Anchor (0) < colWidth)) { + sizeRequired = new Size (colWidth, Bounds.Height); + return true; + } + break; + default: + if (_frame.Height < 1 && + (Height == null || + Height is Dim.DimAbsolute && + Height.Anchor (0) == 0)) { + sizeRequired = new Size (Bounds.Width, 1); + return true; } + break; } return false; } if (GetMinimumSizeOfText (out var size)) { + // TODO: This is a hack. + //_width = size.Width; + //_height = size.Height; _frame = new Rect (_frame.Location, size); + //throw new InvalidOperationException ("This is a hack."); return true; } return false; } /// - /// Gets the width or height of the characters + /// Gets the width or height of the characters /// in the property. /// /// - /// Only the first hotkey specifier found in is supported. + /// 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. - /// The number of characters required for the . If the text direction specified - /// by does not match the parameter, 0 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 + /// direction specified + /// by does not match the parameter, 0 is returned. + /// public int GetHotKeySpecifierLength (bool isWidth = true) { if (isWidth) { return TextFormatter.IsHorizontalDirection (TextDirection) && - TextFormatter.Text?.Contains ((char)HotKeySpecifier.Value) == true - ? Math.Max (HotKeySpecifier.GetColumns (), 0) : 0; - } else { - return TextFormatter.IsVerticalDirection (TextDirection) && - TextFormatter.Text?.Contains ((char)HotKeySpecifier.Value) == true + TextFormatter.Text?.Contains ((char)HotKeySpecifier.Value) == true ? Math.Max (HotKeySpecifier.GetColumns (), 0) : 0; } + return TextFormatter.IsVerticalDirection (TextDirection) && + TextFormatter.Text?.Contains ((char)HotKeySpecifier.Value) == true + ? Math.Max (HotKeySpecifier.GetColumns (), 0) : 0; } /// - /// Gets the dimensions required for ignoring a . + /// Gets the dimensions required for ignoring a . /// /// - public Size GetSizeNeededForTextWithoutHotKey () => new (TextFormatter.Size.Width - GetHotKeySpecifierLength (), + internal Size GetSizeNeededForTextWithoutHotKey () => new (TextFormatter.Size.Width - GetHotKeySpecifierLength (), TextFormatter.Size.Height - GetHotKeySpecifierLength (false)); /// - /// Gets the dimensions required for accounting for a . + /// Sets .Size to the current size, adjusted for + /// . /// + /// + /// Use this API to set when the view has changed such that the + /// size required to fit the text has changed. + /// changes. + /// /// - public Size GetTextFormatterSizeNeededForTextAndHotKey () + internal void SetTextFormatterSize () { if (!IsInitialized) { - return Size.Empty; + TextFormatter.Size = Size.Empty; + return; } if (string.IsNullOrEmpty (TextFormatter.Text)) { - return Bounds.Size; + TextFormatter.Size = Bounds.Size; + return; } - // BUGBUG: This IGNORES what Text is set to, using on only the current View size. This doesn't seem to make sense. - // BUGBUG: This uses Frame; in v2 it should be Bounds - return new Size (Bounds.Size.Width + GetHotKeySpecifierLength (), + TextFormatter.Size = new Size (Bounds.Size.Width + GetHotKeySpecifierLength (), Bounds.Size.Height + GetHotKeySpecifierLength (false)); } + + /// + /// Gets the Frame dimensions required to fit within using the text + /// specified by the + /// property and accounting for any characters. + /// + /// The the needs to be set to fit the text. + public Size GetAutoSize () + { + var x = 0; + var y = 0; + if (IsInitialized) { + x = Bounds.X; + y = Bounds.Y; + } + var rect = TextFormatter.CalcRect (x, y, TextFormatter.Text, TextFormatter.Direction); + int newWidth = rect.Size.Width - GetHotKeySpecifierLength () + (Margin == null ? 0 : Margin.Thickness.Horizontal + Border.Thickness.Horizontal + Padding.Thickness.Horizontal); + int newHeight = rect.Size.Height - GetHotKeySpecifierLength (false) + (Margin == null ? 0 : Margin.Thickness.Vertical + Border.Thickness.Vertical + Padding.Thickness.Vertical); + return new Size (newWidth, newHeight); + } + + bool IsValidAutoSize (out Size autoSize) + { + var rect = TextFormatter.CalcRect (_frame.X, _frame.Y, TextFormatter.Text, TextDirection); + autoSize = new Size (rect.Size.Width - GetHotKeySpecifierLength (), + rect.Size.Height - GetHotKeySpecifierLength (false)); + return !(ValidatePosDim && (!(Width is Dim.DimAbsolute) || !(Height is Dim.DimAbsolute)) || + _frame.Size.Width != rect.Size.Width - GetHotKeySpecifierLength () || + _frame.Size.Height != rect.Size.Height - GetHotKeySpecifierLength (false)); + } + + bool IsValidAutoSizeWidth (Dim width) + { + var rect = TextFormatter.CalcRect (_frame.X, _frame.Y, TextFormatter.Text, TextDirection); + var dimValue = width.Anchor (0); + return !(ValidatePosDim && !(width is Dim.DimAbsolute) || dimValue != rect.Size.Width - GetHotKeySpecifierLength ()); + } + + bool IsValidAutoSizeHeight (Dim height) + { + var rect = TextFormatter.CalcRect (_frame.X, _frame.Y, TextFormatter.Text, TextDirection); + var dimValue = height.Anchor (0); + return !(ValidatePosDim && !(height is Dim.DimAbsolute) || dimValue != rect.Size.Height - GetHotKeySpecifierLength (false)); + } } \ No newline at end of file diff --git a/Terminal.Gui/Views/Button.cs b/Terminal.Gui/Views/Button.cs index dc72a2700c..c22ec013da 100644 --- a/Terminal.Gui/Views/Button.cs +++ b/Terminal.Gui/Views/Button.cs @@ -1,4 +1,4 @@ -// +// // Button.cs: Button control // // Authors: @@ -10,65 +10,64 @@ namespace Terminal.Gui; /// -/// Button is a that provides an item that invokes raises the event. +/// Button is a that provides an item that invokes raises the event. /// /// -/// -/// Provides a button showing text that raises the event when clicked on with a mouse -/// or when the user presses SPACE, ENTER, or the . The hot key is the first letter or digit following the first underscore ('_') -/// in the button text. -/// -/// -/// Use to change the hot key specifier from the default of ('_'). -/// -/// -/// If no hot key specifier is found, the first uppercase letter encountered will be used as the hot key. -/// -/// -/// When the button is configured as the default () and the user presses -/// the ENTER key, if no other processes the key, the 's -/// event will will be fired. -/// +/// +/// Provides a button showing text that raises the event when clicked on with a mouse +/// or when the user presses SPACE, ENTER, or the . The hot key is the first letter or +/// digit following the first underscore ('_') +/// in the button text. +/// +/// +/// Use to change the hot key specifier from the default of ('_'). +/// +/// +/// If no hot key specifier is found, the first uppercase letter encountered will be used as the hot key. +/// +/// +/// When the button is configured as the default () and the user presses +/// the ENTER key, if no other processes the key, the 's +/// event will will be fired. +/// /// public class Button : View { bool _isDefault; Rune _leftBracket; - Rune _rightBracket; Rune _leftDefault; + Rune _rightBracket; Rune _rightDefault; /// - /// Initializes a new instance of using layout. + /// Initializes a new instance of using layout. /// /// - /// The width of the is computed based on the - /// text length. The height will always be 1. + /// The width of the is computed based on the + /// text length. The height will always be 1. /// - public Button () : this (text: string.Empty, is_default: false) { } + public Button () : this (string.Empty, false) { } /// - /// Initializes a new instance of using layout. + /// Initializes a new instance of using layout. /// /// - /// The width of the is computed based on the - /// text length. The height will always be 1. + /// The width of the is computed based on the + /// text length. The height will always be 1. /// /// The button's text /// - /// If true, a special decoration is used, and the user pressing the enter key - /// in a will implicitly activate this button. + /// If true, a special decoration is used, and the user pressing the enter key + /// in a will implicitly activate this button. /// - public Button (string text, bool is_default = false) : base (text) - { - SetInitialProperties (text, is_default); - } + public Button (string text, bool is_default = false) : base (text) => SetInitialProperties (text, is_default); /// - /// Initializes a new instance of using layout, based on the given text + /// Initializes a new instance of using layout, based on the given + /// text /// /// - /// The width of the is computed based on the - /// text length. The height will always be 1. + /// The width of the is computed based on the + /// text length. The height will always be 1. /// /// X position where the button will be shown. /// Y position where the button will be shown. @@ -76,25 +75,46 @@ public Button (string text, bool is_default = false) : base (text) public Button (int x, int y, string text) : this (x, y, text, false) { } /// - /// Initializes a new instance of using layout, based on the given text. + /// Initializes a new instance of using layout, based on the given + /// text. /// /// - /// The width of the is computed based on the - /// text length. The height will always be 1. + /// The width of the is computed based on the + /// text length. The height will always be 1. /// /// X position where the button will be shown. /// Y position where the button will be shown. /// The button's text /// - /// If true, a special decoration is used, and the user pressing the enter key - /// in a will implicitly activate this button. + /// If true, a special decoration is used, and the user pressing the enter key + /// in a will implicitly activate this button. /// public Button (int x, int y, string text, bool is_default) - : base (new Rect (x, y, text.GetRuneCount () + 4 + (is_default ? 2 : 0), 1), text) - { - SetInitialProperties (text, is_default); + : base (new Rect (x, y, text.GetRuneCount () + 4 + (is_default ? 2 : 0), 1), text) => SetInitialProperties (text, is_default); + + /// + /// Gets or sets whether the is the default action to activate in a dialog. + /// + /// true if is default; otherwise, false. + public bool IsDefault { + get => _isDefault; + set { + _isDefault = value; + UpdateTextFormatterText (); + OnResizeNeeded (); + } } + /// + /// + /// + public bool NoDecorations { get; set; } + + /// + /// + /// + public bool NoPadding { get; set; } + // TODO: v2 - Remove constructors with parameters /// /// Private helper to set the initial properties of the View that were provided via constructors. @@ -108,56 +128,34 @@ void SetInitialProperties (string text, bool is_default) HotKeySpecifier = new Rune ('_'); - _leftBracket = CM.Glyphs.LeftBracket; - _rightBracket = CM.Glyphs.RightBracket; - _leftDefault = CM.Glyphs.LeftDefaultIndicator; - _rightDefault = CM.Glyphs.RightDefaultIndicator; + _leftBracket = Glyphs.LeftBracket; + _rightBracket = Glyphs.RightBracket; + _leftDefault = Glyphs.LeftDefaultIndicator; + _rightDefault = Glyphs.RightDefaultIndicator; CanFocus = true; AutoSize = true; _isDefault = is_default; Text = text ?? string.Empty; - OnResizeNeeded (); - - // Override default behavior of View - Command.Default sets focus - AddCommand (Command.Accept, () => { OnClicked (); return true; }); + // Override default behavior of View + // Command.Default sets focus + AddCommand (Command.Accept, () => { + OnClicked (); + return true; + }); KeyBindings.Add (Key.Space, Command.Default, Command.Accept); KeyBindings.Add (Key.Enter, Command.Default, Command.Accept); } - - /// - /// Gets or sets whether the is the default action to activate in a dialog. - /// - /// true if is default; otherwise, false. - public bool IsDefault { - get => _isDefault; - set { - _isDefault = value; - UpdateTextFormatterText (); - OnResizeNeeded (); - } - } - - /// - /// - /// - public bool NoDecorations { get; set; } - - /// - /// - /// - public bool NoPadding { get; set; } /// protected override void UpdateTextFormatterText () { if (NoDecorations) { TextFormatter.Text = Text; - } else - if (IsDefault) + } else if (IsDefault) { TextFormatter.Text = $"{_leftBracket}{_leftDefault} {Text} {_rightDefault}{_rightBracket}"; - else { + } else { if (NoPadding) { TextFormatter.Text = $"{_leftBracket}{Text}{_rightBracket}"; } else { @@ -170,19 +168,16 @@ protected override void UpdateTextFormatterText () /// /// Virtual method to invoke the event. /// - public virtual void OnClicked () - { - Clicked?.Invoke (this, EventArgs.Empty); - } + public virtual void OnClicked () => Clicked?.Invoke (this, EventArgs.Empty); /// - /// The event fired when the user clicks the primary mouse button within the Bounds of this - /// or if the user presses the action key while this view is focused. (TODO: IsDefault) + /// The event fired when the user clicks the primary mouse button within the Bounds of this + /// or if the user presses the action key while this view is focused. (TODO: IsDefault) /// /// - /// Client code can hook up to this event, it is - /// raised when the button is activated either with - /// the mouse or the keyboard. + /// Client code can hook up to this event, it is + /// raised when the button is activated either with + /// the mouse or the keyboard. /// public event EventHandler Clicked; @@ -208,7 +203,7 @@ public override bool MouseEvent (MouseEvent me) public override void PositionCursor () { if (HotKey.IsValid && Text != "") { - for (int i = 0; i < TextFormatter.Text.GetRuneCount (); i++) { + for (var i = 0; i < TextFormatter.Text.GetRuneCount (); i++) { if (TextFormatter.Text [i] == Text [0]) { Move (i, 0); return; @@ -225,4 +220,4 @@ public override bool OnEnter (View view) return base.OnEnter (view); } -} +} \ No newline at end of file diff --git a/Terminal.Gui/Views/CheckBox.cs b/Terminal.Gui/Views/CheckBox.cs index 10f761e714..3a0da84383 100644 --- a/Terminal.Gui/Views/CheckBox.cs +++ b/Terminal.Gui/Views/CheckBox.cs @@ -86,8 +86,6 @@ void SetInitialProperties (string s, bool is_checked) AutoSize = true; Text = s; - OnResizeNeeded (); - // Things this view knows how to do AddCommand (Command.ToggleChecked, () => ToggleChecked ()); AddCommand (Command.Accept, () => { diff --git a/Terminal.Gui/Views/ColorPicker.cs b/Terminal.Gui/Views/ColorPicker.cs index a19b9f758d..3e74e1793e 100644 --- a/Terminal.Gui/Views/ColorPicker.cs +++ b/Terminal.Gui/Views/ColorPicker.cs @@ -1,281 +1,275 @@ using System; using System.Text; -namespace Terminal.Gui { +namespace Terminal.Gui; +/// +/// Event arguments for the events. +/// +public class ColorEventArgs : EventArgs { /// - /// Event arguments for the events. + /// Initializes a new instance of /// - public class ColorEventArgs : EventArgs { + public ColorEventArgs () { } - /// - /// Initializes a new instance of - /// - public ColorEventArgs () - { - } + /// + /// The new Thickness. + /// + public Color Color { get; set; } + + /// + /// The previous Thickness. + /// + public Color PreviousColor { get; set; } +} - /// - /// The new Thickness. - /// - public Color Color { get; set; } +/// +/// The Color picker. +/// +public class ColorPicker : View { + int _boxHeight = 2; + int _boxWidth = 4; - /// - /// The previous Thickness. - /// - public Color PreviousColor { get; set; } - } /// - /// The Color picker. + /// Columns of color boxes /// - public class ColorPicker : View { - private int _selectColorIndex = (int)Color.Black; - - - /// - /// Columns of color boxes - /// - private int _cols = 8; - - /// - /// Rows of color boxes - /// - private int _rows = 2; - - /// - /// Width of a color box - /// - public int BoxWidth { - get => _boxWidth; - set { - if (_boxWidth != value) { - _boxWidth = value; - if (IsInitialized) { - Bounds = new Rect (Bounds.Location, new Size (_cols * BoxWidth, _rows * BoxHeight)); - } - } - } - } - private int _boxWidth = 4; - - /// - /// Height of a color box - /// - public int BoxHeight { - get => _boxHeight; - set { - if (_boxHeight != value) { - _boxHeight = value; - if (IsInitialized) { - Bounds = new Rect (Bounds.Location, new Size (_cols * BoxWidth, _rows * BoxHeight)); - } - } - } - } - int _boxHeight = 2; - - /// - /// Cursor for the selected color. - /// - public Point Cursor { - get { - return new Point (_selectColorIndex % _cols, _selectColorIndex / _cols); - } + readonly int _cols = 8; - set { - var colorIndex = value.Y * _cols + value.X; - SelectedColor = (ColorName)colorIndex; - } - } + /// + /// Rows of color boxes + /// + readonly int _rows = 2; - /// - /// Fired when a color is picked. - /// - public event EventHandler ColorChanged; - - /// - /// Selected color. - /// - public ColorName SelectedColor { - get { - return (ColorName)_selectColorIndex; - } + int _selectColorIndex = (int)Color.Black; + + /// + /// Initializes a new instance of . + /// + public ColorPicker () => SetInitialProperties (); - set { - ColorName prev = (ColorName)_selectColorIndex; - _selectColorIndex = (int)value; - ColorChanged?.Invoke (this, new ColorEventArgs () { - PreviousColor = new Color (prev), - Color = new Color (value), - }); - SetNeedsDisplay (); + /// + /// Width of a color box + /// + public int BoxWidth { + get => _boxWidth; + set { + if (_boxWidth != value) { + _boxWidth = value; + SetNeedsLayout (); } } + } - /// - /// Initializes a new instance of . - /// - public ColorPicker () - { - SetInitialProperties (); + /// + /// Height of a color box + /// + public int BoxHeight { + get => _boxHeight; + set { + if (_boxHeight != value) { + _boxHeight = value; + SetNeedsLayout (); + } } + } - private void SetInitialProperties () - { - CanFocus = true; - AddCommands (); - AddKeyBindings (); - LayoutStarted += (o, a) => { - Bounds = new Rect (Bounds.Location, new Size (_cols * BoxWidth, _rows * BoxHeight)); - }; + /// + /// Cursor for the selected color. + /// + public Point Cursor { + get => new (_selectColorIndex % _cols, _selectColorIndex / _cols); + set { + var colorIndex = value.Y * _cols + value.X; + SelectedColor = (ColorName)colorIndex; } + } - /// - /// Add the commands. - /// - private void AddCommands () - { - AddCommand (Command.Left, () => MoveLeft ()); - AddCommand (Command.Right, () => MoveRight ()); - AddCommand (Command.LineUp, () => MoveUp ()); - AddCommand (Command.LineDown, () => MoveDown ()); + /// + /// Selected color. + /// + public ColorName SelectedColor { + get => (ColorName)_selectColorIndex; + set { + var prev = (ColorName)_selectColorIndex; + _selectColorIndex = (int)value; + ColorChanged?.Invoke (this, new ColorEventArgs { + PreviousColor = new Color (prev), + Color = new Color (value) + }); + SetNeedsDisplay (); } + } - /// - /// Add the KeyBindinds. - /// - private void AddKeyBindings () - { - KeyBindings.Add (KeyCode.CursorLeft, Command.Left); - KeyBindings.Add (KeyCode.CursorRight, Command.Right); - KeyBindings.Add (KeyCode.CursorUp, Command.LineUp); - KeyBindings.Add (KeyCode.CursorDown, Command.LineDown); - } + /// + /// Fired when a color is picked. + /// + public event EventHandler ColorChanged; + + void SetInitialProperties () + { + CanFocus = true; + AddCommands (); + AddKeyBindings (); + LayoutStarted += (o, a) => { + var thickness = GetFramesThickness (); + Width = _cols * BoxWidth + thickness.Vertical; + Height = _rows * BoxHeight + thickness.Horizontal; + }; + } - /// - public override void OnDrawContent (Rect contentArea) - { - base.OnDrawContent (contentArea); - - Driver.SetAttribute (HasFocus ? ColorScheme.Focus : GetNormalColor ()); - var colorIndex = 0; - - for (var y = 0; y < (Bounds.Height / BoxHeight); y++) { - for (var x = 0; x < (Bounds.Width / BoxWidth); x++) { - var foregroundColorIndex = y == 0 ? colorIndex + _cols : colorIndex - _cols; - Driver.SetAttribute (new Attribute ((ColorName)foregroundColorIndex, (ColorName)colorIndex)); - var selected = x == Cursor.X && y == Cursor.Y; - DrawColorBox (x, y, selected); - colorIndex++; - } - } - } + /// + /// Add the commands. + /// + void AddCommands () + { + AddCommand (Command.Left, () => MoveLeft ()); + AddCommand (Command.Right, () => MoveRight ()); + AddCommand (Command.LineUp, () => MoveUp ()); + AddCommand (Command.LineDown, () => MoveDown ()); + } - /// - /// Draw a box for one color. - /// - /// X location. - /// Y location - /// - private void DrawColorBox (int x, int y, bool selected) - { - var index = 0; - - for (var zoomedY = 0; zoomedY < BoxHeight; zoomedY++) { - for (var zoomedX = 0; zoomedX < BoxWidth; zoomedX++) { - Move (x * BoxWidth + zoomedX, y * BoxHeight + zoomedY); - Driver.AddRune ((Rune)' '); - index++; - } - } + /// + /// Add the KeyBindinds. + /// + void AddKeyBindings () + { + KeyBindings.Add (KeyCode.CursorLeft, Command.Left); + KeyBindings.Add (KeyCode.CursorRight, Command.Right); + KeyBindings.Add (KeyCode.CursorUp, Command.LineUp); + KeyBindings.Add (KeyCode.CursorDown, Command.LineDown); + } - if (selected) { - DrawFocusRect (new Rect (x * BoxWidth, y * BoxHeight, BoxWidth, BoxHeight)); + /// + public override void OnDrawContent (Rect contentArea) + { + base.OnDrawContent (contentArea); + + Driver.SetAttribute (HasFocus ? ColorScheme.Focus : GetNormalColor ()); + var colorIndex = 0; + + for (var y = 0; y < Bounds.Height / BoxHeight; y++) { + for (var x = 0; x < Bounds.Width / BoxWidth; x++) { + var foregroundColorIndex = y == 0 ? colorIndex + _cols : colorIndex - _cols; + Driver.SetAttribute (new Attribute ((ColorName)foregroundColorIndex, (ColorName)colorIndex)); + var selected = x == Cursor.X && y == Cursor.Y; + DrawColorBox (x, y, selected); + colorIndex++; } } + } - private void DrawFocusRect (Rect rect) - { - var lc = new LineCanvas (); - if (rect.Width == 1) { - lc.AddLine (rect.Location, rect.Height, Orientation.Vertical, LineStyle.Dotted); - } else if (rect.Height == 1) { - lc.AddLine (rect.Location, rect.Width, Orientation.Horizontal, LineStyle.Dotted); - } else { - lc.AddLine (rect.Location, rect.Width, Orientation.Horizontal, LineStyle.Dotted); - lc.AddLine (new Point (rect.Location.X, rect.Location.Y + rect.Height - 1), rect.Width, Orientation.Horizontal, LineStyle.Dotted); - - lc.AddLine (rect.Location, rect.Height, Orientation.Vertical, LineStyle.Dotted); - lc.AddLine (new Point (rect.Location.X + rect.Width - 1, rect.Location.Y), rect.Height, Orientation.Vertical, LineStyle.Dotted); - } - foreach (var p in lc.GetMap ()) { - AddRune (p.Key.X, p.Key.Y, p.Value); + /// + /// Draw a box for one color. + /// + /// X location. + /// Y location + /// + void DrawColorBox (int x, int y, bool selected) + { + var index = 0; + + for (var zoomedY = 0; zoomedY < BoxHeight; zoomedY++) { + for (var zoomedX = 0; zoomedX < BoxWidth; zoomedX++) { + Move (x * BoxWidth + zoomedX, y * BoxHeight + zoomedY); + Driver.AddRune ((Rune)' '); + index++; } } - /// - /// Moves the selected item index to the previous column. - /// - /// - public virtual bool MoveLeft () - { - if (Cursor.X > 0) SelectedColor--; - return true; + if (selected) { + DrawFocusRect (new Rect (x * BoxWidth, y * BoxHeight, BoxWidth, BoxHeight)); } + } - /// - /// Moves the selected item index to the next column. - /// - /// - public virtual bool MoveRight () - { - if (Cursor.X < _cols - 1) SelectedColor++; - return true; + void DrawFocusRect (Rect rect) + { + var lc = new LineCanvas (); + if (rect.Width == 1) { + lc.AddLine (rect.Location, rect.Height, Orientation.Vertical, LineStyle.Dotted); + } else if (rect.Height == 1) { + lc.AddLine (rect.Location, rect.Width, Orientation.Horizontal, LineStyle.Dotted); + } else { + lc.AddLine (rect.Location, rect.Width, Orientation.Horizontal, LineStyle.Dotted); + lc.AddLine (new Point (rect.Location.X, rect.Location.Y + rect.Height - 1), rect.Width, Orientation.Horizontal, LineStyle.Dotted); + + lc.AddLine (rect.Location, rect.Height, Orientation.Vertical, LineStyle.Dotted); + lc.AddLine (new Point (rect.Location.X + rect.Width - 1, rect.Location.Y), rect.Height, Orientation.Vertical, LineStyle.Dotted); + } + foreach (var p in lc.GetMap ()) { + AddRune (p.Key.X, p.Key.Y, p.Value); } + } - /// - /// Moves the selected item index to the previous row. - /// - /// - public virtual bool MoveUp () - { - if (Cursor.Y > 0) SelectedColor -= _cols; - return true; + /// + /// Moves the selected item index to the previous column. + /// + /// + public virtual bool MoveLeft () + { + if (Cursor.X > 0) { + SelectedColor--; + } + return true; + } + + /// + /// Moves the selected item index to the next column. + /// + /// + public virtual bool MoveRight () + { + if (Cursor.X < _cols - 1) { + SelectedColor++; } + return true; + } - /// - /// Moves the selected item index to the next row. - /// - /// - public virtual bool MoveDown () - { - if (Cursor.Y < _rows - 1) SelectedColor += _cols; - return true; + /// + /// Moves the selected item index to the previous row. + /// + /// + public virtual bool MoveUp () + { + if (Cursor.Y > 0) { + SelectedColor -= _cols; } + return true; + } - /// - public override bool MouseEvent (MouseEvent me) - { - if (!me.Flags.HasFlag (MouseFlags.Button1Clicked) || !CanFocus) { - return false; - } + /// + /// Moves the selected item index to the next row. + /// + /// + public virtual bool MoveDown () + { + if (Cursor.Y < _rows - 1) { + SelectedColor += _cols; + } + return true; + } - SetFocus (); - if (me.X > Bounds.Width || me.Y > Bounds.Height) { - return true; - } - Cursor = new Point ((me.X ) / _boxWidth, (me.Y) / _boxHeight); + /// + public override bool MouseEvent (MouseEvent me) + { + if (!me.Flags.HasFlag (MouseFlags.Button1Clicked) || !CanFocus) { + return false; + } + SetFocus (); + if (me.X > Bounds.Width || me.Y > Bounds.Height) { return true; } + Cursor = new Point (me.X / _boxWidth, me.Y / _boxHeight); - /// - public override bool OnEnter (View view) - { - Application.Driver.SetCursorVisibility (CursorVisibility.Invisible); + return true; + } - return base.OnEnter (view); - } + /// + public override bool OnEnter (View view) + { + Application.Driver.SetCursorVisibility (CursorVisibility.Invisible); + + return base.OnEnter (view); } -} +} \ No newline at end of file diff --git a/Terminal.Gui/Views/ComboBox.cs b/Terminal.Gui/Views/ComboBox.cs index 2be4291999..af7ef4c78a 100644 --- a/Terminal.Gui/Views/ComboBox.cs +++ b/Terminal.Gui/Views/ComboBox.cs @@ -241,7 +241,7 @@ public ComboBox () : this (string.Empty) { } public ComboBox (string text) : base () { _search = new TextField (""); - _listview = new ComboListView (this, HideDropdownListOnClick) { LayoutStyle = LayoutStyle.Computed, CanFocus = true, TabStop = false }; + _listview = new ComboListView (this, HideDropdownListOnClick) { CanFocus = true, TabStop = false }; SetInitialProperties (); Text = text; @@ -255,7 +255,7 @@ public ComboBox (string text) : base () public ComboBox (Rect rect, IList source) : base (rect) { _search = new TextField ("") { Width = rect.Width }; - _listview = new ComboListView (this, rect, source, HideDropdownListOnClick) { LayoutStyle = LayoutStyle.Computed, ColorScheme = Colors.Base }; + _listview = new ComboListView (this, rect, source, HideDropdownListOnClick) { ColorScheme = Colors.Base }; SetInitialProperties (); SetSource (source); @@ -268,7 +268,7 @@ public ComboBox (Rect rect, IList source) : base (rect) public ComboBox (IList source) : this (string.Empty) { _search = new TextField (""); - _listview = new ComboListView (this, source, HideDropdownListOnClick) { LayoutStyle = LayoutStyle.Computed, ColorScheme = Colors.Base }; + _listview = new ComboListView (this, source, HideDropdownListOnClick) { ColorScheme = Colors.Base }; SetInitialProperties (); SetSource (source); diff --git a/Terminal.Gui/Views/FileDialog.cs b/Terminal.Gui/Views/FileDialog.cs index 4f8bb4bf1a..3fbee15484 100644 --- a/Terminal.Gui/Views/FileDialog.cs +++ b/Terminal.Gui/Views/FileDialog.cs @@ -1,1536 +1,1500 @@ using System; using System.Collections.Generic; -using System.Data; using System.IO; using System.IO.Abstractions; using System.Linq; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; -using System.Text; using Terminal.Gui.Resources; -using static Terminal.Gui.ConfigurationManager; -namespace Terminal.Gui { +namespace Terminal.Gui; + +/// +/// Modal dialog for selecting files/directories. Has auto-complete and expandable +/// navigation pane (Recent, Root drives etc). +/// +public class FileDialog : Dialog { /// - /// Modal dialog for selecting files/directories. Has auto-complete and expandable - /// navigation pane (Recent, Root drives etc). + /// Gets the Path separators for the operating system /// - public partial class FileDialog : Dialog { + internal static char [] Separators = { + System.IO.Path.AltDirectorySeparatorChar, + System.IO.Path.DirectorySeparatorChar + }; - /// - /// Gets settings for controlling how visual elements behave. Style changes should - /// be made before the is loaded and shown to the user for the - /// first time. - /// - public FileDialogStyle Style { get; } + /// + /// Characters to prevent entry into . Note that this is not using + /// because we do want to allow directory + /// separators, arrow keys etc. + /// + static readonly char [] badChars = { + '"', '<', '>', '|', '*', '?' + }; - /// - /// The maximum number of results that will be collected - /// when searching before stopping. - /// - /// - /// This prevents performance issues e.g. when searching - /// root of file system for a common letter (e.g. 'e'). - /// - [SerializableConfigurationProperty (Scope = typeof (SettingsScope))] - public static int MaxSearchResults { get; set; } = 10000; + Dictionary _treeRoots = new (); + MenuBarItem allowedTypeMenu; + MenuBar allowedTypeMenuBar; + MenuItem [] allowedTypeMenuItems; + readonly Button btnBack; + readonly Button btnCancel; + readonly Button btnForward; + readonly Button btnOk; + readonly Button btnToggleSplitterCollapse; + readonly Button btnUp; - /// - /// True if the file/folder must exist already to be selected. - /// This prevents user from entering the name of something that - /// doesn't exist. Defaults to false. - /// - public bool MustExist { get; set; } + int currentSortColumn; - /// - /// Gets the Path separators for the operating system - /// - internal static char [] Separators = new [] - { - System.IO.Path.AltDirectorySeparatorChar, - System.IO.Path.DirectorySeparatorChar, - }; + bool currentSortIsAsc = true; - /// - /// Characters to prevent entry into . Note that this is not using - /// because we do want to allow directory - /// separators, arrow keys etc. - /// - private static char [] badChars = new [] - { - '"','<','>','|','*','?', - }; + bool disposed; + string feedback; + readonly IFileSystem fileSystem; - /// - /// The UI selected from combo box. May be null. - /// - public IAllowedType CurrentFilter { get; private set; } + readonly FileDialogHistory history; + bool loaded; - private bool pushingState = false; - private bool loaded = false; + /// + /// Locking object for ensuring only a single executes at once. + /// + internal object onlyOneSearchLock = new (); - /// - /// Gets the currently open directory and known children presented in the dialog. - /// - internal FileDialogState State { get; private set; } + bool pushingState; + readonly SpinnerView spinnerView; + readonly TileView splitContainer; - /// - /// Locking object for ensuring only a single executes at once. - /// - internal object onlyOneSearchLock = new object (); - - private bool disposed = false; - private IFileSystem fileSystem; - private TextField tbPath; - - private FileDialogHistory history; - - private TableView tableView; - private TreeView treeView; - private TileView splitContainer; - private Button btnOk; - private Button btnCancel; - private Button btnToggleSplitterCollapse; - private Button btnForward; - private Button btnBack; - private Button btnUp; - private string feedback; - private TextField tbFind; - private SpinnerView spinnerView; - private MenuBar allowedTypeMenuBar; - private MenuBarItem allowedTypeMenu; - private MenuItem [] allowedTypeMenuItems; - - private int currentSortColumn; - - private bool currentSortIsAsc = true; - private Dictionary _treeRoots = new Dictionary (); + readonly TableView tableView; + readonly TextField tbFind; + readonly TextField tbPath; + readonly TreeView treeView; - /// - /// Event fired when user attempts to confirm a selection (or multi selection). - /// Allows you to cancel the selection or undertake alternative behavior e.g. - /// open a dialog "File already exists, Overwrite? yes/no". - /// - public event EventHandler FilesSelected; + /// + /// Initializes a new instance of the class. + /// + public FileDialog () : this (new FileSystem ()) { } - /// - /// Gets or sets behavior of the when the user attempts - /// to delete a selected file(s). Set to null to prevent deleting. - /// - /// Ensure you use a try/catch block with appropriate - /// error handling (e.g. showing a - public IFileOperations FileOperationsHandler { get; set; } = new DefaultFileOperations (); + /// + /// Initializes a new instance of the class with + /// a custom . + /// + /// This overload is mainly useful for testing. + public FileDialog (IFileSystem fileSystem) + { + this.fileSystem = fileSystem; + Style = new FileDialogStyle (fileSystem); + + btnOk = new Button (Style.OkButtonText) { + Y = Pos.AnchorEnd (1), + X = Pos.Function (CalculateOkButtonPosX), + IsDefault = true + }; + btnOk.Clicked += (s, e) => Accept (true); + btnOk.KeyDown += (s, k) => { + NavigateIf (k, KeyCode.CursorLeft, btnCancel); + NavigateIf (k, KeyCode.CursorUp, tableView); + }; - /// - /// Initializes a new instance of the class. - /// - public FileDialog () : this (new FileSystem ()) - { + btnCancel = new Button (Strings.btnCancel) { + Y = Pos.AnchorEnd (1), + X = Pos.Right (btnOk) + 1 + }; + btnCancel.KeyDown += (s, k) => { + NavigateIf (k, KeyCode.CursorLeft, btnToggleSplitterCollapse); + NavigateIf (k, KeyCode.CursorUp, tableView); + NavigateIf (k, KeyCode.CursorRight, btnOk); + }; + btnCancel.Clicked += (s, e) => { + Application.RequestStop (); + }; - } + btnUp = new Button { X = 0, Y = 1, NoPadding = true }; + btnUp.Text = GetUpButtonText (); + btnUp.Clicked += (s, e) => history.Up (); - /// - /// Initializes a new instance of the class with - /// a custom . - /// - /// This overload is mainly useful for testing. - public FileDialog (IFileSystem fileSystem) - { - this.fileSystem = fileSystem; - Style = new FileDialogStyle (fileSystem); + btnBack = new Button { X = Pos.Right (btnUp) + 1, Y = 1, NoPadding = true }; + btnBack.Text = GetBackButtonText (); + btnBack.Clicked += (s, e) => history.Back (); - this.btnOk = new Button (Style.OkButtonText) { - Y = Pos.AnchorEnd (1), - X = Pos.Function (CalculateOkButtonPosX), - IsDefault = true - }; - this.btnOk.Clicked += (s, e) => this.Accept (true); - this.btnOk.KeyDown += (s, k) => { - this.NavigateIf (k, KeyCode.CursorLeft, this.btnCancel); - this.NavigateIf (k, KeyCode.CursorUp, this.tableView); - }; + btnForward = new Button { X = Pos.Right (btnBack) + 1, Y = 1, NoPadding = true }; + btnForward.Text = GetForwardButtonText (); + btnForward.Clicked += (s, e) => history.Forward (); - this.btnCancel = new Button (Strings.btnCancel) { - Y = Pos.AnchorEnd (1), - X = Pos.Right (btnOk) + 1 - }; - this.btnCancel.KeyDown += (s, k) => { - this.NavigateIf (k, KeyCode.CursorLeft, this.btnToggleSplitterCollapse); - this.NavigateIf (k, KeyCode.CursorUp, this.tableView); - this.NavigateIf (k, KeyCode.CursorRight, this.btnOk); - }; - this.btnCancel.Clicked += (s, e) => { - Application.RequestStop (); - }; + tbPath = new TextField { + Width = Dim.Fill (), + CaptionColor = new Color (Color.Black) + }; + tbPath.KeyDown += (s, k) => { - this.btnUp = new Button () { X = 0, Y = 1, NoPadding = true }; - btnUp.Text = GetUpButtonText (); - this.btnUp.Clicked += (s, e) => this.history.Up (); + ClearFeedback (); - this.btnBack = new Button () { X = Pos.Right (btnUp) + 1, Y = 1, NoPadding = true }; - btnBack.Text = GetBackButtonText (); - this.btnBack.Clicked += (s, e) => this.history.Back (); + AcceptIf (k, KeyCode.Enter); - this.btnForward = new Button () { X = Pos.Right (btnBack) + 1, Y = 1, NoPadding = true }; - btnForward.Text = GetForwardButtonText (); - this.btnForward.Clicked += (s, e) => this.history.Forward (); + SuppressIfBadChar (k); + }; - this.tbPath = new TextField { - Width = Dim.Fill (0), - CaptionColor = new Color (Color.Black) - }; - this.tbPath.KeyDown += (s, k) => { + tbPath.Autocomplete = new AppendAutocomplete (tbPath); + tbPath.Autocomplete.SuggestionGenerator = new FilepathSuggestionGenerator (); - ClearFeedback (); + splitContainer = new TileView { + X = 0, + Y = 2, + Width = Dim.Fill (), + Height = Dim.Fill (1) + }; - this.AcceptIf (k, KeyCode.Enter); + Initialized += (s, e) => { + splitContainer.SetSplitterPos (0, 30); + splitContainer.Tiles.ElementAt (0).ContentView.Visible = false; + }; + // this.splitContainer.Border.BorderStyle = BorderStyle.None; - this.SuppressIfBadChar (k); - }; + tableView = new TableView { + Width = Dim.Fill (), + Height = Dim.Fill (), + FullRowSelect = true, + CollectionNavigator = new FileDialogCollectionNavigator (this) + }; + tableView.KeyBindings.Add (KeyCode.Space, Command.ToggleChecked); + tableView.MouseClick += OnTableViewMouseClick; + tableView.Style.InvertSelectedCellFirstCharacter = true; + Style.TableStyle = tableView.Style; - tbPath.Autocomplete = new AppendAutocomplete (tbPath); - tbPath.Autocomplete.SuggestionGenerator = new FilepathSuggestionGenerator (); + var nameStyle = Style.TableStyle.GetOrCreateColumnStyle (0); + nameStyle.MinWidth = 10; + nameStyle.ColorGetter = ColorGetter; - this.splitContainer = new TileView () { - X = 0, - Y = 2, - Width = Dim.Fill (0), - Height = Dim.Fill (1), - }; - - Initialized += (s, e) => { - this.splitContainer.SetSplitterPos (0, 30); - this.splitContainer.Tiles.ElementAt (0).ContentView.Visible = false; - }; - // this.splitContainer.Border.BorderStyle = BorderStyle.None; + var sizeStyle = Style.TableStyle.GetOrCreateColumnStyle (1); + sizeStyle.MinWidth = 10; + sizeStyle.ColorGetter = ColorGetter; - this.tableView = new TableView { - Width = Dim.Fill (), - Height = Dim.Fill (), - FullRowSelect = true, - CollectionNavigator = new FileDialogCollectionNavigator (this) - }; - this.tableView.KeyBindings.Add (KeyCode.Space, Command.ToggleChecked); - this.tableView.MouseClick += OnTableViewMouseClick; - tableView.Style.InvertSelectedCellFirstCharacter = true; - Style.TableStyle = tableView.Style; - - var nameStyle = Style.TableStyle.GetOrCreateColumnStyle (0); - nameStyle.MinWidth = 10; - nameStyle.ColorGetter = this.ColorGetter; - - var sizeStyle = Style.TableStyle.GetOrCreateColumnStyle (1); - sizeStyle.MinWidth = 10; - sizeStyle.ColorGetter = this.ColorGetter; - - var dateModifiedStyle = Style.TableStyle.GetOrCreateColumnStyle (2); - dateModifiedStyle.MinWidth = 30; - dateModifiedStyle.ColorGetter = this.ColorGetter; - - var typeStyle = Style.TableStyle.GetOrCreateColumnStyle (3); - typeStyle.MinWidth = 6; - typeStyle.ColorGetter = this.ColorGetter; - - this.tableView.KeyDown += (s, k) => { - if (this.tableView.SelectedRow <= 0) { - this.NavigateIf (k, KeyCode.CursorUp, this.tbPath); - } - if (this.tableView.SelectedRow == this.tableView.Table.Rows - 1) { - this.NavigateIf (k, KeyCode.CursorDown, this.btnToggleSplitterCollapse); - } + var dateModifiedStyle = Style.TableStyle.GetOrCreateColumnStyle (2); + dateModifiedStyle.MinWidth = 30; + dateModifiedStyle.ColorGetter = ColorGetter; - if (splitContainer.Tiles.First ().ContentView.Visible && tableView.SelectedColumn == 0) { - this.NavigateIf (k, KeyCode.CursorLeft, this.treeView); - } + var typeStyle = Style.TableStyle.GetOrCreateColumnStyle (3); + typeStyle.MinWidth = 6; + typeStyle.ColorGetter = ColorGetter; - if (k.Handled) { - return; - } - }; + tableView.KeyDown += (s, k) => { + if (tableView.SelectedRow <= 0) { + NavigateIf (k, KeyCode.CursorUp, tbPath); + } + if (tableView.SelectedRow == tableView.Table.Rows - 1) { + NavigateIf (k, KeyCode.CursorDown, btnToggleSplitterCollapse); + } - this.treeView = new TreeView () { - Width = Dim.Fill (), - Height = Dim.Fill (), - }; + if (splitContainer.Tiles.First ().ContentView.Visible && tableView.SelectedColumn == 0) { + NavigateIf (k, KeyCode.CursorLeft, treeView); + } - var fileDialogTreeBuilder = new FileSystemTreeBuilder (); - this.treeView.TreeBuilder = fileDialogTreeBuilder; - this.treeView.AspectGetter = this.AspectGetter; - this.Style.TreeStyle = treeView.Style; + if (k.Handled) { } + }; - this.treeView.SelectionChanged += this.TreeView_SelectionChanged; + treeView = new TreeView { + Width = Dim.Fill (), + Height = Dim.Fill () + }; - this.splitContainer.Tiles.ElementAt (0).ContentView.Add (this.treeView); - this.splitContainer.Tiles.ElementAt (1).ContentView.Add (this.tableView); + var fileDialogTreeBuilder = new FileSystemTreeBuilder (); + treeView.TreeBuilder = fileDialogTreeBuilder; + treeView.AspectGetter = AspectGetter; + Style.TreeStyle = treeView.Style; - this.btnToggleSplitterCollapse = new Button (GetToggleSplitterText (false)) { - Y = Pos.AnchorEnd (1), - }; - this.btnToggleSplitterCollapse.Clicked += (s, e) => { - var tile = this.splitContainer.Tiles.ElementAt (0); + treeView.SelectionChanged += TreeView_SelectionChanged; - var newState = !tile.ContentView.Visible; - tile.ContentView.Visible = newState; - this.btnToggleSplitterCollapse.Text = GetToggleSplitterText (newState); - this.LayoutSubviews (); - }; + splitContainer.Tiles.ElementAt (0).ContentView.Add (treeView); + splitContainer.Tiles.ElementAt (1).ContentView.Add (tableView); - tbFind = new TextField { - X = Pos.Right (this.btnToggleSplitterCollapse) + 1, - CaptionColor = new Color (Color.Black), - Width = 30, - Y = Pos.AnchorEnd (1), - HotKey = KeyCode.F | KeyCode.AltMask - }; - spinnerView = new SpinnerView () { - X = Pos.Right (tbFind) + 1, - Y = Pos.AnchorEnd (1), - Visible = false, - }; + btnToggleSplitterCollapse = new Button (GetToggleSplitterText (false)) { + Y = Pos.AnchorEnd (1) + }; + btnToggleSplitterCollapse.Clicked += (s, e) => { + var tile = splitContainer.Tiles.ElementAt (0); - tbFind.TextChanged += (s, o) => RestartSearch (); - tbFind.KeyDown += (s, o) => { - if (o.KeyCode == KeyCode.Enter) { - RestartSearch (); - o.Handled = true; - } + var newState = !tile.ContentView.Visible; + tile.ContentView.Visible = newState; + btnToggleSplitterCollapse.Text = GetToggleSplitterText (newState); + LayoutSubviews (); + }; - if (o.KeyCode == KeyCode.Esc) { - if (CancelSearch ()) { - o.Handled = true; - } - } - if (tbFind.CursorIsAtEnd ()) { - NavigateIf (o, KeyCode.CursorRight, btnCancel); - } - if (tbFind.CursorIsAtStart ()) { - NavigateIf (o, KeyCode.CursorLeft, btnToggleSplitterCollapse); + tbFind = new TextField { + X = Pos.Right (btnToggleSplitterCollapse) + 1, + CaptionColor = new Color (Color.Black), + Width = 30, + Y = Pos.AnchorEnd (1), + HotKey = KeyCode.F | KeyCode.AltMask + }; + spinnerView = new SpinnerView { + X = Pos.Right (tbFind) + 1, + Y = Pos.AnchorEnd (1), + Visible = false + }; + + tbFind.TextChanged += (s, o) => RestartSearch (); + tbFind.KeyDown += (s, o) => { + if (o.KeyCode == KeyCode.Enter) { + RestartSearch (); + o.Handled = true; + } + + if (o.KeyCode == KeyCode.Esc) { + if (CancelSearch ()) { + o.Handled = true; } - }; + } + if (tbFind.CursorIsAtEnd ()) { + NavigateIf (o, KeyCode.CursorRight, btnCancel); + } + if (tbFind.CursorIsAtStart ()) { + NavigateIf (o, KeyCode.CursorLeft, btnToggleSplitterCollapse); + } + }; - this.tableView.Style.ShowHorizontalHeaderOverline = true; - this.tableView.Style.ShowVerticalCellLines = true; - this.tableView.Style.ShowVerticalHeaderLines = true; - this.tableView.Style.AlwaysShowHeaders = true; - this.tableView.Style.ShowHorizontalHeaderUnderline = true; - this.tableView.Style.ShowHorizontalScrollIndicators = true; + tableView.Style.ShowHorizontalHeaderOverline = true; + tableView.Style.ShowVerticalCellLines = true; + tableView.Style.ShowVerticalHeaderLines = true; + tableView.Style.AlwaysShowHeaders = true; + tableView.Style.ShowHorizontalHeaderUnderline = true; + tableView.Style.ShowHorizontalScrollIndicators = true; - this.history = new FileDialogHistory (this); + history = new FileDialogHistory (this); - this.tbPath.TextChanged += (s, e) => this.PathChanged (); + tbPath.TextChanged += (s, e) => PathChanged (); - this.tableView.CellActivated += this.CellActivate; - this.tableView.KeyUp += (s, k) => k.Handled = this.TableView_KeyUp (k); - this.tableView.SelectedCellChanged += this.TableView_SelectedCellChanged; + tableView.CellActivated += CellActivate; + tableView.KeyUp += (s, k) => k.Handled = TableView_KeyUp (k); + tableView.SelectedCellChanged += TableView_SelectedCellChanged; - this.tableView.KeyBindings.Add (KeyCode.Home, Command.TopHome); - this.tableView.KeyBindings.Add (KeyCode.End, Command.BottomEnd); - this.tableView.KeyBindings.Add (KeyCode.Home | KeyCode.ShiftMask, Command.TopHomeExtend); - this.tableView.KeyBindings.Add (KeyCode.End | KeyCode.ShiftMask, Command.BottomEndExtend); + tableView.KeyBindings.Add (KeyCode.Home, Command.TopHome); + tableView.KeyBindings.Add (KeyCode.End, Command.BottomEnd); + tableView.KeyBindings.Add (KeyCode.Home | KeyCode.ShiftMask, Command.TopHomeExtend); + tableView.KeyBindings.Add (KeyCode.End | KeyCode.ShiftMask, Command.BottomEndExtend); - this.treeView.KeyDown += (s, k) => { + treeView.KeyDown += (s, k) => { - var selected = treeView.SelectedObject; - if (selected != null) { - if (!treeView.CanExpand (selected) || treeView.IsExpanded (selected)) { - this.NavigateIf (k, KeyCode.CursorRight, this.tableView); - } else - if (treeView.GetObjectRow (selected) == 0) { - this.NavigateIf (k, KeyCode.CursorUp, this.tbPath); - } + var selected = treeView.SelectedObject; + if (selected != null) { + if (!treeView.CanExpand (selected) || treeView.IsExpanded (selected)) { + NavigateIf (k, KeyCode.CursorRight, tableView); + } else if (treeView.GetObjectRow (selected) == 0) { + NavigateIf (k, KeyCode.CursorUp, tbPath); } + } - if (k.Handled) { - return; - } + if (k.Handled) { + return; + } - k.Handled = this.TreeView_KeyDown (k); + k.Handled = TreeView_KeyDown (k); - }; + }; - this.AllowsMultipleSelection = false; + AllowsMultipleSelection = false; + + UpdateNavigationVisibility (); + + // Determines tab order + Add (btnToggleSplitterCollapse); + Add (tbFind); + Add (spinnerView); + Add (btnOk); + Add (btnCancel); + Add (btnUp); + Add (btnBack); + Add (btnForward); + Add (tbPath); + Add (splitContainer); + } - this.UpdateNavigationVisibility (); + /// + /// Gets settings for controlling how visual elements behave. Style changes should + /// be made before the is loaded and shown to the user for the + /// first time. + /// + public FileDialogStyle Style { get; } - // Determines tab order - this.Add (this.btnToggleSplitterCollapse); - this.Add (this.tbFind); - this.Add (this.spinnerView); - this.Add (this.btnOk); - this.Add (this.btnCancel); - this.Add (this.btnUp); - this.Add (this.btnBack); - this.Add (this.btnForward); - this.Add (this.tbPath); - this.Add (this.splitContainer); - } + /// + /// The maximum number of results that will be collected + /// when searching before stopping. + /// + /// + /// This prevents performance issues e.g. when searching + /// root of file system for a common letter (e.g. 'e'). + /// + [SerializableConfigurationProperty (Scope = typeof (SettingsScope))] + public static int MaxSearchResults { get; set; } = 10000; - private int CalculateOkButtonPosX () - { - return this.Bounds.Width - - btnOk.Bounds.Width - - btnCancel.Bounds.Width - - 1 - // TODO: Fiddle factor, seems the Bounds are wrong for someone - - 2; - } + /// + /// True if the file/folder must exist already to be selected. + /// This prevents user from entering the name of something that + /// doesn't exist. Defaults to false. + /// + public bool MustExist { get; set; } - private string AspectGetter (object o) - { - var fsi = (IFileSystemInfo)o; + /// + /// The UI selected from combo box. May be null. + /// + public IAllowedType CurrentFilter { get; private set; } - if (o is IDirectoryInfo dir && _treeRoots.ContainsKey (dir)) { + /// + /// Gets the currently open directory and known children presented in the dialog. + /// + internal FileDialogState State { get; private set; } - // Directory has a special name e.g. 'Pictures' - return _treeRoots [dir]; - } + /// + /// Gets or sets behavior of the when the user attempts + /// to delete a selected file(s). Set to null to prevent deleting. + /// + /// + /// Ensure you use a try/catch block with appropriate + /// error handling (e.g. showing a + /// + public IFileOperations FileOperationsHandler { get; set; } = new DefaultFileOperations (); + + /// + /// Gets or Sets which type can be selected. + /// Defaults to (i.e. or + /// ). + /// + public OpenMode OpenMode { get; set; } = OpenMode.Mixed; - return (Style.IconProvider.GetIconWithOptionalSpace (fsi) + fsi.Name).Trim (); + /// + /// Gets or Sets the selected path in the dialog. This is the result that should + /// be used if is off and + /// is true. + /// + public string Path { + get => tbPath.Text; + set { + tbPath.Text = value; + tbPath.MoveEnd (); } + } - private void OnTableViewMouseClick (object sender, MouseEventEventArgs e) - { - var clickedCell = this.tableView.ScreenToCell (e.MouseEvent.X, e.MouseEvent.Y, out int? clickedCol); + /// + /// Defines how the dialog matches files/folders when using the search + /// box. Provide a custom implementation if you want to tailor how matching + /// is performed. + /// + public ISearchMatcher SearchMatcher { get; set; } = new DefaultSearchMatcher (); - if (clickedCol != null) { - if (e.MouseEvent.Flags.HasFlag (MouseFlags.Button1Clicked)) { + /// + /// Gets or Sets a value indicating whether to allow selecting + /// multiple existing files/directories. Defaults to false. + /// + public bool AllowsMultipleSelection { + get => tableView.MultiSelect; + set => tableView.MultiSelect = value; + } - // left click in a header - this.SortColumn (clickedCol.Value); - } else if (e.MouseEvent.Flags.HasFlag (MouseFlags.Button3Clicked)) { + /// + /// Gets or Sets a collection of file types that the user can/must select. Only applies + /// when is or . + /// + /// + /// adds the option to select any type (*.*). If this + /// collection is empty then any type is supported and no Types drop-down is shown. + /// + public List AllowedTypes { get; set; } = new (); - // right click in a header - this.ShowHeaderContextMenu (clickedCol.Value, e); - } - } else { - if (clickedCell != null && e.MouseEvent.Flags.HasFlag (MouseFlags.Button3Clicked)) { + /// + /// Gets a value indicating whether the was closed + /// without confirming a selection. + /// + public bool Canceled { get; private set; } = true; - // right click in rest of table - this.ShowCellContextMenu (clickedCell, e); - } - } - } + /// + /// Gets all files/directories selected or an empty collection + /// is or . + /// + /// If selecting only a single file/directory then you should use instead. + public IReadOnlyList MultiSelected { get; private set; } + = Enumerable.Empty ().ToList ().AsReadOnly (); - private string GetForwardButtonText () - { - return "-" + CM.Glyphs.RightArrow; - } + /// + /// Event fired when user attempts to confirm a selection (or multi selection). + /// Allows you to cancel the selection or undertake alternative behavior e.g. + /// open a dialog "File already exists, Overwrite? yes/no". + /// + public event EventHandler FilesSelected; - private string GetBackButtonText () - { - return CM.Glyphs.LeftArrow + "-"; + int CalculateOkButtonPosX () + { + if (!IsInitialized || !btnOk.IsInitialized || !btnCancel.IsInitialized) { + return 0; } + return Bounds.Width - + btnOk.Bounds.Width - + btnCancel.Bounds.Width - + 1 + // TODO: Fiddle factor, seems the Bounds are wrong for someone + - + 2; + } - private string GetUpButtonText () - { - return Style.UseUnicodeCharacters ? "◭" : "▲"; - } + string AspectGetter (object o) + { + var fsi = (IFileSystemInfo)o; - private string GetToggleSplitterText (bool isExpanded) - { - return isExpanded ? - new string ((char)CM.Glyphs.LeftArrow.Value, 2) : - new string ((char)CM.Glyphs.RightArrow.Value, 2); + if (o is IDirectoryInfo dir && _treeRoots.ContainsKey (dir)) { + + // Directory has a special name e.g. 'Pictures' + return _treeRoots [dir]; } - private void Delete () - { - var toDelete = GetFocusedFiles (); + return (Style.IconProvider.GetIconWithOptionalSpace (fsi) + fsi.Name).Trim (); + } - if (toDelete != null && FileOperationsHandler.Delete (toDelete)) { - RefreshState (); - } - } + void OnTableViewMouseClick (object sender, MouseEventEventArgs e) + { + var clickedCell = tableView.ScreenToCell (e.MouseEvent.X, e.MouseEvent.Y, out var clickedCol); - private void Rename () - { - var toRename = GetFocusedFiles (); + if (clickedCol != null) { + if (e.MouseEvent.Flags.HasFlag (MouseFlags.Button1Clicked)) { - if (toRename?.Length == 1) { - var newNamed = FileOperationsHandler.Rename (this.fileSystem, toRename.Single ()); + // left click in a header + SortColumn (clickedCol.Value); + } else if (e.MouseEvent.Flags.HasFlag (MouseFlags.Button3Clicked)) { - if (newNamed != null) { - RefreshState (); - RestoreSelection (newNamed); - } + // right click in a header + ShowHeaderContextMenu (clickedCol.Value, e); } - } - private void New () - { - if (State != null) { - var created = FileOperationsHandler.New (this.fileSystem, State.Directory); - if (created != null) { - RefreshState (); - RestoreSelection (created); - } + } else { + if (clickedCell != null && e.MouseEvent.Flags.HasFlag (MouseFlags.Button3Clicked)) { + + // right click in rest of table + ShowCellContextMenu (clickedCell, e); } } - private IFileSystemInfo [] GetFocusedFiles () - { + } - if (!tableView.HasFocus || !tableView.CanFocus || FileOperationsHandler == null) { - return null; - } + string GetForwardButtonText () => "-" + Glyphs.RightArrow; - tableView.EnsureValidSelection (); + string GetBackButtonText () => Glyphs.LeftArrow + "-"; - if (tableView.SelectedRow < 0) { - return null; - } + string GetUpButtonText () => Style.UseUnicodeCharacters ? "◭" : "▲"; - return tableView.GetAllSelectedCells () - .Select (c => c.Y) - .Distinct () - .Select (RowToStats) - .Where (s => !s.IsParent) - .Select (d => d.FileSystemInfo) - .ToArray (); - } + string GetToggleSplitterText (bool isExpanded) => isExpanded ? + new string ((char)Glyphs.LeftArrow.Value, 2) : + new string ((char)Glyphs.RightArrow.Value, 2); + void Delete () + { + var toDelete = GetFocusedFiles (); -// /// -// public override bool OnHotKey (KeyEventArgs keyEvent) -// { -//#if BROKE_IN_2927 -// // BUGBUG: Ctrl-F is forward in a TextField. -// if (this.NavigateIf (keyEvent, Key.Alt | Key.F, this.tbFind)) { -// return true; -// } -//#endif + if (toDelete != null && FileOperationsHandler.Delete (toDelete)) { + RefreshState (); + } + } -// ClearFeedback (); + void Rename () + { + var toRename = GetFocusedFiles (); -// if (allowedTypeMenuBar != null && -// keyEvent.ConsoleDriverKey == Key.Tab && -// allowedTypeMenuBar.IsMenuOpen) { -// allowedTypeMenuBar.CloseMenu (false, false, false); -// } + if (toRename?.Length == 1) { + var newNamed = FileOperationsHandler.Rename (fileSystem, toRename.Single ()); -// return base.OnHotKey (keyEvent); -// } - private void RestartSearch () - { - if (disposed || State?.Directory == null) { - return; + if (newNamed != null) { + RefreshState (); + RestoreSelection (newNamed); } + } + } - if (State is SearchState oldSearch) { - oldSearch.Cancel (); + void New () + { + if (State != null) { + var created = FileOperationsHandler.New (fileSystem, State.Directory); + if (created != null) { + RefreshState (); + RestoreSelection (created); } + } + } - // user is clearing search terms - if (tbFind.Text == null || tbFind.Text.Length == 0) { - - // Wait for search cancellation (if any) to finish - // then push the current dir state - lock (onlyOneSearchLock) { - PushState (new FileDialogState (State.Directory, this), false); - } - return; - } + IFileSystemInfo [] GetFocusedFiles () + { - PushState (new SearchState (State?.Directory, this, tbFind.Text), true); + if (!tableView.HasFocus || !tableView.CanFocus || FileOperationsHandler == null) { + return null; } - /// - protected override void Dispose (bool disposing) - { - disposed = true; - base.Dispose (disposing); + tableView.EnsureValidSelection (); - CancelSearch (); + if (tableView.SelectedRow < 0) { + return null; } - private bool CancelSearch () - { - if (State is SearchState search) { - return search.Cancel (); - } + return tableView.GetAllSelectedCells () + .Select (c => c.Y) + .Distinct () + .Select (RowToStats) + .Where (s => !s.IsParent) + .Select (d => d.FileSystemInfo) + .ToArray (); + } - return false; + + // /// + // public override bool OnHotKey (KeyEventArgs keyEvent) + // { + //#if BROKE_IN_2927 + // // BUGBUG: Ctrl-F is forward in a TextField. + // if (this.NavigateIf (keyEvent, Key.Alt | Key.F, this.tbFind)) { + // return true; + // } + //#endif + + // ClearFeedback (); + + // if (allowedTypeMenuBar != null && + // keyEvent.ConsoleDriverKey == Key.Tab && + // allowedTypeMenuBar.IsMenuOpen) { + // allowedTypeMenuBar.CloseMenu (false, false, false); + // } + + // return base.OnHotKey (keyEvent); + // } + void RestartSearch () + { + if (disposed || State?.Directory == null) { + return; } - private void ClearFeedback () - { - feedback = null; + if (State is SearchState oldSearch) { + oldSearch.Cancel (); } - /// - /// Gets or Sets which type can be selected. - /// Defaults to (i.e. or - /// ). - /// - public OpenMode OpenMode { get; set; } = OpenMode.Mixed; + // user is clearing search terms + if (tbFind.Text == null || tbFind.Text.Length == 0) { - /// - /// Gets or Sets the selected path in the dialog. This is the result that should - /// be used if is off and - /// is true. - /// - public string Path { - get => this.tbPath.Text; - set { - this.tbPath.Text = value; - this.tbPath.MoveEnd (); + // Wait for search cancellation (if any) to finish + // then push the current dir state + lock (onlyOneSearchLock) { + PushState (new FileDialogState (State.Directory, this), false); } + return; } - /// - /// Defines how the dialog matches files/folders when using the search - /// box. Provide a custom implementation if you want to tailor how matching - /// is performed. - /// - public ISearchMatcher SearchMatcher { get; set; } = new DefaultSearchMatcher (); + PushState (new SearchState (State?.Directory, this, tbFind.Text), true); + } - /// - /// Gets or Sets a value indicating whether to allow selecting - /// multiple existing files/directories. Defaults to false. - /// - public bool AllowsMultipleSelection { - get => this.tableView.MultiSelect; - set => this.tableView.MultiSelect = value; + /// + protected override void Dispose (bool disposing) + { + disposed = true; + base.Dispose (disposing); + + CancelSearch (); + } + + bool CancelSearch () + { + if (State is SearchState search) { + return search.Cancel (); } - /// - /// Gets or Sets a collection of file types that the user can/must select. Only applies - /// when is or . - /// - /// adds the option to select any type (*.*). If this - /// collection is empty then any type is supported and no Types drop-down is shown. - public List AllowedTypes { get; set; } = new List (); + return false; + } - /// - /// Gets a value indicating whether the was closed - /// without confirming a selection. - /// - public bool Canceled { get; private set; } = true; + void ClearFeedback () => feedback = null; - /// - /// Gets all files/directories selected or an empty collection - /// is or . - /// - /// If selecting only a single file/directory then you should use instead. - public IReadOnlyList MultiSelected { get; private set; } - = Enumerable.Empty ().ToList ().AsReadOnly (); + /// + public override void OnDrawContent (Rect contentArea) + { + base.OnDrawContent (contentArea); - /// - public override void OnDrawContent (Rect contentArea) - { - base.OnDrawContent (contentArea); + if (!string.IsNullOrWhiteSpace (feedback)) { + var feedbackWidth = feedback.EnumerateRunes ().Sum (c => c.GetColumns ()); + var feedbackPadLeft = (Bounds.Width - feedbackWidth) / 2 - 1; - if (!string.IsNullOrWhiteSpace (feedback)) { - var feedbackWidth = feedback.EnumerateRunes ().Sum (c => c.GetColumns ()); - var feedbackPadLeft = ((Bounds.Width - feedbackWidth) / 2) - 1; + feedbackPadLeft = Math.Min (Bounds.Width, feedbackPadLeft); + feedbackPadLeft = Math.Max (0, feedbackPadLeft); - feedbackPadLeft = Math.Min (Bounds.Width, feedbackPadLeft); - feedbackPadLeft = Math.Max (0, feedbackPadLeft); + var feedbackPadRight = Bounds.Width - (feedbackPadLeft + feedbackWidth + 2); + feedbackPadRight = Math.Min (Bounds.Width, feedbackPadRight); + feedbackPadRight = Math.Max (0, feedbackPadRight); - var feedbackPadRight = Bounds.Width - (feedbackPadLeft + feedbackWidth + 2); - feedbackPadRight = Math.Min (Bounds.Width, feedbackPadRight); - feedbackPadRight = Math.Max (0, feedbackPadRight); + Move (0, Bounds.Height / 2); - Move (0, Bounds.Height / 2); + Driver.SetAttribute (new Attribute (Color.Red, ColorScheme.Normal.Background)); + Driver.AddStr (new string (' ', feedbackPadLeft)); + Driver.AddStr (feedback); + Driver.AddStr (new string (' ', feedbackPadRight)); + } + } - Driver.SetAttribute (new Attribute (Color.Red, this.ColorScheme.Normal.Background)); - Driver.AddStr (new string (' ', feedbackPadLeft)); - Driver.AddStr (feedback); - Driver.AddStr (new string (' ', feedbackPadRight)); - } + /// + public override void OnLoaded () + { + base.OnLoaded (); + if (loaded) { + return; } + loaded = true; - /// - public override void OnLoaded () - { - base.OnLoaded (); - if (loaded) { - return; - } - loaded = true; + // May have been updated after instance was constructed + btnOk.Text = Style.OkButtonText; + btnCancel.Text = Style.CancelButtonText; + btnUp.Text = GetUpButtonText (); + btnBack.Text = GetBackButtonText (); + btnForward.Text = GetForwardButtonText (); + btnToggleSplitterCollapse.Text = GetToggleSplitterText (false); - // May have been updated after instance was constructed - this.btnOk.Text = Style.OkButtonText; - this.btnCancel.Text = Style.CancelButtonText; - this.btnUp.Text = this.GetUpButtonText (); - this.btnBack.Text = this.GetBackButtonText (); - this.btnForward.Text = this.GetForwardButtonText (); - this.btnToggleSplitterCollapse.Text = this.GetToggleSplitterText (false); + if (Style.FlipOkCancelButtonLayoutOrder) { + btnCancel.X = Pos.Function (CalculateOkButtonPosX); + btnOk.X = Pos.Right (btnCancel) + 1; - if (Style.FlipOkCancelButtonLayoutOrder) { - btnCancel.X = Pos.Function (this.CalculateOkButtonPosX); - btnOk.X = Pos.Right (btnCancel) + 1; + // Flip tab order too for consistency + var p1 = btnOk.TabIndex; + var p2 = btnCancel.TabIndex; - // Flip tab order too for consistency - var p1 = this.btnOk.TabIndex; - var p2 = this.btnCancel.TabIndex; + btnOk.TabIndex = p2; + btnCancel.TabIndex = p1; + } - this.btnOk.TabIndex = p2; - this.btnCancel.TabIndex = p1; - } + tbPath.Caption = Style.PathCaption; + tbFind.Caption = Style.SearchCaption; - tbPath.Caption = Style.PathCaption; - tbFind.Caption = Style.SearchCaption; + tbPath.Autocomplete.ColorScheme.Normal = new Attribute (Color.Black, tbPath.ColorScheme.Normal.Background); - tbPath.Autocomplete.ColorScheme.Normal = new Attribute (Color.Black, tbPath.ColorScheme.Normal.Background); + _treeRoots = Style.TreeRootGetter (); + Style.IconProvider.IsOpenGetter = treeView.IsExpanded; - _treeRoots = Style.TreeRootGetter (); - Style.IconProvider.IsOpenGetter = treeView.IsExpanded; + treeView.AddObjects (_treeRoots.Keys); - treeView.AddObjects (_treeRoots.Keys); + // if filtering on file type is configured then create the ComboBox and establish + // initial filtering by extension(s) + if (AllowedTypes.Any ()) { - // if filtering on file type is configured then create the ComboBox and establish - // initial filtering by extension(s) - if (this.AllowedTypes.Any ()) { + CurrentFilter = AllowedTypes [0]; - this.CurrentFilter = this.AllowedTypes [0]; + // Fiddle factor + var width = AllowedTypes.Max (a => a.ToString ().Length) + 6; - // Fiddle factor - var width = this.AllowedTypes.Max (a => a.ToString ().Length) + 6; + allowedTypeMenu = new MenuBarItem ("", + allowedTypeMenuItems = AllowedTypes.Select ( + (a, i) => new MenuItem (a.ToString (), null, () => { + AllowedTypeMenuClicked (i); + })) + .ToArray ()); - allowedTypeMenu = new MenuBarItem ("", - allowedTypeMenuItems = AllowedTypes.Select ( - (a, i) => new MenuItem (a.ToString (), null, () => { - AllowedTypeMenuClicked (i); - })) - .ToArray ()); + allowedTypeMenuBar = new MenuBar (new [] { allowedTypeMenu }) { + Width = width, + Y = 1, + X = Pos.AnchorEnd (width), - allowedTypeMenuBar = new MenuBar (new [] { allowedTypeMenu }) { - Width = width, - Y = 1, - X = Pos.AnchorEnd (width), + // TODO: Does not work, if this worked then we could tab to it instead + // of having to hit F9 + CanFocus = true, + TabStop = true + }; + AllowedTypeMenuClicked (0); - // TODO: Does not work, if this worked then we could tab to it instead - // of having to hit F9 - CanFocus = true, - TabStop = true - }; - AllowedTypeMenuClicked (0); + allowedTypeMenuBar.Enter += (s, e) => { + allowedTypeMenuBar.OpenMenu (0); + }; - allowedTypeMenuBar.Enter += (s, e) => { - allowedTypeMenuBar.OpenMenu (0); - }; + allowedTypeMenuBar.DrawContentComplete += (s, e) => { - allowedTypeMenuBar.DrawContentComplete += (s, e) => { + allowedTypeMenuBar.Move (e.Rect.Width - 1, 0); + Driver.AddRune (Glyphs.DownArrow); - allowedTypeMenuBar.Move (e.Rect.Width - 1, 0); - Driver.AddRune (CM.Glyphs.DownArrow); + }; - }; + Add (allowedTypeMenuBar); + } - this.Add (allowedTypeMenuBar); - } + // if no path has been provided + if (tbPath.Text.Length <= 0) { + Path = Environment.CurrentDirectory; + } - // if no path has been provided - if (this.tbPath.Text.Length <= 0) { - this.Path = Environment.CurrentDirectory; - } + // to streamline user experience and allow direct typing of paths + // with zero navigation we start with focus in the text box and any + // default/current path fully selected and ready to be overwritten + tbPath.FocusFirst (); + tbPath.SelectAll (); - // to streamline user experience and allow direct typing of paths - // with zero navigation we start with focus in the text box and any - // default/current path fully selected and ready to be overwritten - this.tbPath.FocusFirst (); - this.tbPath.SelectAll (); + if (string.IsNullOrEmpty (Title)) { + Title = GetDefaultTitle (); + } + LayoutSubviews (); + } - if (string.IsNullOrEmpty (Title)) { - this.Title = GetDefaultTitle (); - } - this.LayoutSubviews (); + /// + /// Gets a default dialog title, when is not set or empty, + /// result of the function will be shown. + /// + protected virtual string GetDefaultTitle () + { + List titleParts = new () { + Strings.fdOpen + }; + if (MustExist) { + titleParts.Add (Strings.fdExisting); } - /// - /// Gets a default dialog title, when is not set or empty, - /// result of the function will be shown. - /// - protected virtual string GetDefaultTitle () - { - List titleParts = new () { - Strings.fdOpen - }; - if (MustExist) { - titleParts.Add (Strings.fdExisting); - } - switch (OpenMode) { - case OpenMode.File: - titleParts.Add (Strings.fdFile); - break; - case OpenMode.Directory: - titleParts.Add (Strings.fdDirectory); - break; - } - return string.Join (' ', titleParts); + switch (OpenMode) { + case OpenMode.File: + titleParts.Add (Strings.fdFile); + break; + case OpenMode.Directory: + titleParts.Add (Strings.fdDirectory); + break; } + return string.Join (' ', titleParts); + } - private void AllowedTypeMenuClicked (int idx) - { + void AllowedTypeMenuClicked (int idx) + { - var allow = AllowedTypes [idx]; - for (int i = 0; i < AllowedTypes.Count; i++) { - allowedTypeMenuItems [i].Checked = i == idx; - } - allowedTypeMenu.Title = allow.ToString (); + var allow = AllowedTypes [idx]; + for (var i = 0; i < AllowedTypes.Count; i++) { + allowedTypeMenuItems [i].Checked = i == idx; + } + allowedTypeMenu.Title = allow.ToString (); - this.CurrentFilter = allow; + CurrentFilter = allow; - this.tbPath.ClearAllSelection (); - this.tbPath.Autocomplete.ClearSuggestions (); + tbPath.ClearAllSelection (); + tbPath.Autocomplete.ClearSuggestions (); - if (this.State != null) { - this.State.RefreshChildren (); - this.WriteStateToTableView (); - } + if (State != null) { + State.RefreshChildren (); + WriteStateToTableView (); } + } - private void SuppressIfBadChar (Key k) - { - // don't let user type bad letters - var ch = (char)k; + void SuppressIfBadChar (Key k) + { + // don't let user type bad letters + var ch = (char)k; - if (badChars.Contains (ch)) { - k.Handled = true; - } + if (badChars.Contains (ch)) { + k.Handled = true; } + } - private bool TreeView_KeyDown (Key keyEvent) - { - if (this.treeView.HasFocus && Separators.Contains ((char)keyEvent)) { - this.tbPath.FocusFirst (); + bool TreeView_KeyDown (Key keyEvent) + { + if (treeView.HasFocus && Separators.Contains ((char)keyEvent)) { + tbPath.FocusFirst (); - // let that keystroke go through on the tbPath instead - return true; - } - - return false; + // let that keystroke go through on the tbPath instead + return true; } - private void AcceptIf (Key keyEvent, KeyCode isKey) - { - if (!keyEvent.Handled && keyEvent.KeyCode == isKey) { - keyEvent.Handled = true; + return false; + } - // User hit Enter in text box so probably wants the - // contents of the text box as their selection not - // whatever lingering selection is in TableView - this.Accept (false); - } + void AcceptIf (Key keyEvent, KeyCode isKey) + { + if (!keyEvent.Handled && keyEvent.KeyCode == isKey) { + keyEvent.Handled = true; + + // User hit Enter in text box so probably wants the + // contents of the text box as their selection not + // whatever lingering selection is in TableView + Accept (false); } + } - private void Accept (IEnumerable toMultiAccept) - { - if (!this.AllowsMultipleSelection) { - return; - } + void Accept (IEnumerable toMultiAccept) + { + if (!AllowsMultipleSelection) { + return; + } - // Don't include ".." (IsParent) in multiselections - this.MultiSelected = toMultiAccept + // Don't include ".." (IsParent) in multiselections + MultiSelected = toMultiAccept .Where (s => !s.IsParent) .Select (s => s.FileSystemInfo.FullName) .ToList ().AsReadOnly (); - this.Path = this.MultiSelected.Count == 1 ? this.MultiSelected [0] : string.Empty; + Path = MultiSelected.Count == 1 ? MultiSelected [0] : string.Empty; + + FinishAccept (); + } - FinishAccept (); + void Accept (IFileInfo f) + { + if (!IsCompatibleWithOpenMode (f.FullName, out var reason)) { + feedback = reason; + SetNeedsDisplay (); + return; } - private void Accept (IFileInfo f) - { - if (!this.IsCompatibleWithOpenMode (f.FullName, out var reason)) { + Path = f.FullName; + + if (AllowsMultipleSelection) { + MultiSelected = new List { f.FullName }.AsReadOnly (); + } + + FinishAccept (); + } + + void Accept (bool allowMulti) + { + if (allowMulti && TryAcceptMulti ()) { + return; + } + + if (!IsCompatibleWithOpenMode (tbPath.Text, out var reason)) { + if (reason != null) { feedback = reason; SetNeedsDisplay (); - return; } + return; + } - this.Path = f.FullName; + FinishAccept (); + } - if (AllowsMultipleSelection) { - this.MultiSelected = new List { f.FullName }.AsReadOnly (); - } + void FinishAccept () + { + var e = new FilesSelectedEventArgs (this); - FinishAccept (); - } + FilesSelected?.Invoke (this, e); - private void Accept (bool allowMulti) - { - if (allowMulti && TryAcceptMulti ()) { - return; - } + if (e.Cancel) { + return; + } - if (!this.IsCompatibleWithOpenMode (this.tbPath.Text, out string reason)) { - if (reason != null) { - feedback = reason; - SetNeedsDisplay (); - } - return; - } + // if user uses Path selection mode (e.g. Enter in text box) + // then also copy to MultiSelected + if (AllowsMultipleSelection && !MultiSelected.Any ()) { - FinishAccept (); + MultiSelected = string.IsNullOrWhiteSpace (Path) ? + Enumerable.Empty ().ToList ().AsReadOnly () : + new List { Path }.AsReadOnly (); } - private void FinishAccept () - { - var e = new FilesSelectedEventArgs (this); + Canceled = false; + Application.RequestStop (); + } - this.FilesSelected?.Invoke (this, e); + bool NavigateIf (Key keyEvent, KeyCode isKey, View to) + { + if (keyEvent.KeyCode == isKey) { - if (e.Cancel) { - return; + to.FocusFirst (); + if (to == tbPath) { + tbPath.MoveEnd (); } + return true; + } - // if user uses Path selection mode (e.g. Enter in text box) - // then also copy to MultiSelected - if (AllowsMultipleSelection && (!MultiSelected.Any ())) { - - MultiSelected = string.IsNullOrWhiteSpace (Path) ? - Enumerable.Empty ().ToList ().AsReadOnly () : - new List () { Path }.AsReadOnly (); - } + return false; + } - this.Canceled = false; - Application.RequestStop (); + void TreeView_SelectionChanged (object sender, SelectionChangedEventArgs e) + { + if (e.NewValue == null) { + return; } - private bool NavigateIf (Key keyEvent, KeyCode isKey, View to) - { - if (keyEvent.KeyCode == isKey) { + Path = e.NewValue.FullName; + } - to.FocusFirst (); - if (to == tbPath) { - tbPath.MoveEnd (); - } - return true; - } + void UpdateNavigationVisibility () + { + btnBack.Visible = history.CanBack (); + btnForward.Visible = history.CanForward (); + btnUp.Visible = history.CanUp (); + } - return false; + void TableView_SelectedCellChanged (object sender, SelectedCellChangedEventArgs obj) + { + if (!tableView.HasFocus || obj.NewRow == -1 || obj.Table.Rows == 0) { + return; } - private void TreeView_SelectionChanged (object sender, SelectionChangedEventArgs e) - { - if (e.NewValue == null) { - return; - } - - this.Path = e.NewValue.FullName; + if (tableView.MultiSelect && tableView.MultiSelectedRegions.Any ()) { + return; } - private void UpdateNavigationVisibility () - { - this.btnBack.Visible = this.history.CanBack (); - this.btnForward.Visible = this.history.CanForward (); - this.btnUp.Visible = this.history.CanUp (); - } + var stats = RowToStats (obj.NewRow); - private void TableView_SelectedCellChanged (object sender, SelectedCellChangedEventArgs obj) - { - if (!this.tableView.HasFocus || obj.NewRow == -1 || obj.Table.Rows == 0) { - return; - } + if (stats == null) { + return; + } + IFileSystemInfo dest; - if (this.tableView.MultiSelect && this.tableView.MultiSelectedRegions.Any ()) { - return; - } + if (stats.IsParent) { + dest = State.Directory; + } else { + dest = stats.FileSystemInfo; + } - var stats = this.RowToStats (obj.NewRow); + try { + pushingState = true; - if (stats == null) { - return; - } - IFileSystemInfo dest; + Path = dest.FullName; + State.Selected = stats; + tbPath.Autocomplete.ClearSuggestions (); - if (stats.IsParent) { - dest = State.Directory; - } else { - dest = stats.FileSystemInfo; - } + } finally { - try { - this.pushingState = true; + pushingState = false; + } + } - this.Path = dest.FullName; - this.State.Selected = stats; - this.tbPath.Autocomplete.ClearSuggestions (); + bool TableView_KeyUp (Key keyEvent) + { + if (keyEvent.KeyCode == KeyCode.Backspace) { + return history.Back (); + } + if (keyEvent.KeyCode == (KeyCode.ShiftMask | KeyCode.Backspace)) { + return history.Forward (); + } - } finally { + if (keyEvent.KeyCode == KeyCode.Delete) { - this.pushingState = false; - } + Delete (); + return true; } - private bool TableView_KeyUp (Key keyEvent) - { - if (keyEvent.KeyCode == KeyCode.Backspace) { - return this.history.Back (); - } - if (keyEvent.KeyCode == (KeyCode.ShiftMask | KeyCode.Backspace)) { - return this.history.Forward (); - } + if (keyEvent.KeyCode == (KeyCode.CtrlMask | KeyCode.R)) { - if (keyEvent.KeyCode == KeyCode.Delete) { + Rename (); + return true; + } - Delete (); - return true; - } + if (keyEvent.KeyCode == (KeyCode.CtrlMask | KeyCode.N)) { + New (); + return true; + } - if (keyEvent.KeyCode == (KeyCode.CtrlMask | KeyCode.R)) { + return false; + } - Rename (); - return true; - } + void CellActivate (object sender, CellActivatedEventArgs obj) + { + if (TryAcceptMulti ()) { + return; + } - if (keyEvent.KeyCode == (KeyCode.CtrlMask | KeyCode.N)) { - New (); - return true; - } + var stats = RowToStats (obj.Row); - return false; + if (stats.FileSystemInfo is IDirectoryInfo d) { + PushState (d, true); + return; } - private void CellActivate (object sender, CellActivatedEventArgs obj) - { - if (TryAcceptMulti ()) { - return; - } - - var stats = this.RowToStats (obj.Row); + if (stats.FileSystemInfo is IFileInfo f) { + Accept (f); + } + } - if (stats.FileSystemInfo is IDirectoryInfo d) { - this.PushState (d, true); - return; - } + bool TryAcceptMulti () + { + var multi = MultiRowToStats (); + string reason = null; - if (stats.FileSystemInfo is IFileInfo f) { - this.Accept (f); - } + if (!multi.Any ()) { + return false; } - private bool TryAcceptMulti () - { - var multi = this.MultiRowToStats (); - string reason = null; - - if (!multi.Any ()) { - return false; - } + if (multi.All (m => IsCompatibleWithOpenMode ( + m.FileSystemInfo.FullName, out reason))) { + Accept (multi); + return true; + } + if (reason != null) { + feedback = reason; + SetNeedsDisplay (); + } - if (multi.All (m => this.IsCompatibleWithOpenMode ( - m.FileSystemInfo.FullName, out reason))) { - this.Accept (multi); - return true; - } else { - if (reason != null) { - feedback = reason; - SetNeedsDisplay (); - } + return false; + } - return false; - } + /// + /// Returns true if there are no or one of them agrees + /// that . + /// + /// + /// + public bool IsCompatibleWithAllowedExtensions (IFileInfo file) + { + // no restrictions + if (!AllowedTypes.Any ()) { + return true; } + return MatchesAllowedTypes (file); + } - /// - /// Returns true if there are no or one of them agrees - /// that . - /// - /// - /// - public bool IsCompatibleWithAllowedExtensions (IFileInfo file) - { - // no restrictions - if (!this.AllowedTypes.Any ()) { - return true; - } - return this.MatchesAllowedTypes (file); + bool IsCompatibleWithAllowedExtensions (string path) + { + // no restrictions + if (!AllowedTypes.Any ()) { + return true; } - private bool IsCompatibleWithAllowedExtensions (string path) - { - // no restrictions - if (!this.AllowedTypes.Any ()) { - return true; - } + return AllowedTypes.Any (t => t.IsAllowed (path)); + } - return this.AllowedTypes.Any (t => t.IsAllowed (path)); + /// + /// Returns true if any matches . + /// + /// + /// + bool MatchesAllowedTypes (IFileInfo file) => AllowedTypes.Any (t => t.IsAllowed (file.FullName)); + + bool IsCompatibleWithOpenMode (string s, out string reason) + { + reason = null; + if (string.IsNullOrWhiteSpace (s)) { + return false; } - /// - /// Returns true if any matches . - /// - /// - /// - private bool MatchesAllowedTypes (IFileInfo file) - { - return this.AllowedTypes.Any (t => t.IsAllowed (file.FullName)); + if (!IsCompatibleWithAllowedExtensions (s)) { + reason = Style.WrongFileTypeFeedback; + return false; } - private bool IsCompatibleWithOpenMode (string s, out string reason) - { - reason = null; - if (string.IsNullOrWhiteSpace (s)) { + + switch (OpenMode) { + case OpenMode.Directory: + if (MustExist && !Directory.Exists (s)) { + reason = Style.DirectoryMustExistFeedback; return false; } - if (!this.IsCompatibleWithAllowedExtensions (s)) { - reason = Style.WrongFileTypeFeedback; + if (File.Exists (s)) { + reason = Style.FileAlreadyExistsFeedback; return false; } + return true; + case OpenMode.File: - switch (this.OpenMode) { - case OpenMode.Directory: - if (MustExist && !Directory.Exists (s)) { - reason = Style.DirectoryMustExistFeedback; - return false; - } - - if (File.Exists (s)) { - reason = Style.FileAlreadyExistsFeedback; - return false; - } - return true; - case OpenMode.File: - - if (MustExist && !File.Exists (s)) { - reason = Style.FileMustExistFeedback; - return false; - } - if (Directory.Exists (s)) { - reason = Style.DirectoryAlreadyExistsFeedback; - return false; - } - return true; - case OpenMode.Mixed: - if (MustExist && !File.Exists (s) && !Directory.Exists (s)) { - reason = Style.FileOrDirectoryMustExistFeedback; - return false; - } - return true; - default: throw new ArgumentOutOfRangeException (nameof (this.OpenMode)); + if (MustExist && !File.Exists (s)) { + reason = Style.FileMustExistFeedback; + return false; } - } - - /// - /// Changes the dialog such that is being explored. - /// - /// - /// - /// - /// - /// Optional alternate string to set path to. - internal void PushState (IDirectoryInfo d, bool addCurrentStateToHistory, bool setPathText = true, bool clearForward = true, string pathText = null) - { - // no change of state - if (d == this.State?.Directory) { - return; + if (Directory.Exists (s)) { + reason = Style.DirectoryAlreadyExistsFeedback; + return false; } - if (d.FullName == this.State?.Directory.FullName) { - return; + return true; + case OpenMode.Mixed: + if (MustExist && !File.Exists (s) && !Directory.Exists (s)) { + reason = Style.FileOrDirectoryMustExistFeedback; + return false; } - - PushState (new FileDialogState (d, this), addCurrentStateToHistory, setPathText, clearForward, pathText); + return true; + default: throw new ArgumentOutOfRangeException (nameof (OpenMode)); } + } - private void RefreshState () - { - State.RefreshChildren (); - PushState (State, false, false, false); + /// + /// Changes the dialog such that is being explored. + /// + /// + /// + /// + /// + /// Optional alternate string to set path to. + internal void PushState (IDirectoryInfo d, bool addCurrentStateToHistory, bool setPathText = true, bool clearForward = true, string pathText = null) + { + // no change of state + if (d == State?.Directory) { + return; + } + if (d.FullName == State?.Directory.FullName) { + return; } - private void PushState (FileDialogState newState, bool addCurrentStateToHistory, bool setPathText = true, bool clearForward = true, string pathText = null) - { - if (State is SearchState search) { - search.Cancel (); - } + PushState (new FileDialogState (d, this), addCurrentStateToHistory, setPathText, clearForward, pathText); + } - try { - this.pushingState = true; + void RefreshState () + { + State.RefreshChildren (); + PushState (State, false, false, false); + } - // push the old state to history - if (addCurrentStateToHistory) { - this.history.Push (this.State, clearForward); - } + void PushState (FileDialogState newState, bool addCurrentStateToHistory, bool setPathText = true, bool clearForward = true, string pathText = null) + { + if (State is SearchState search) { + search.Cancel (); + } - this.tbPath.Autocomplete.ClearSuggestions (); + try { + pushingState = true; - if (pathText != null) { - this.Path = pathText; - } else - if (setPathText) { - this.Path = newState.Directory.FullName; - } + // push the old state to history + if (addCurrentStateToHistory) { + history.Push (State, clearForward); + } - this.State = newState; - this.tbPath.Autocomplete.GenerateSuggestions ( - new AutocompleteFilepathContext (tbPath.Text, tbPath.CursorPosition, this.State)); + tbPath.Autocomplete.ClearSuggestions (); - this.WriteStateToTableView (); + if (pathText != null) { + Path = pathText; + } else if (setPathText) { + Path = newState.Directory.FullName; + } - if (clearForward) { - this.history.ClearForward (); - } + State = newState; + tbPath.Autocomplete.GenerateSuggestions ( + new AutocompleteFilepathContext (tbPath.Text, tbPath.CursorPosition, State)); - this.tableView.RowOffset = 0; - this.tableView.SelectedRow = 0; + WriteStateToTableView (); - this.SetNeedsDisplay (); - this.UpdateNavigationVisibility (); + if (clearForward) { + history.ClearForward (); + } - } finally { + tableView.RowOffset = 0; + tableView.SelectedRow = 0; - this.pushingState = false; - } - ClearFeedback (); - } + SetNeedsDisplay (); + UpdateNavigationVisibility (); - private void WriteStateToTableView () - { - if (this.State == null) { - return; - } - this.tableView.Table = new FileDialogTableSource (this, this.State, this.Style, currentSortColumn, currentSortIsAsc); + } finally { - this.ApplySort (); - this.tableView.Update (); + pushingState = false; } + ClearFeedback (); + } - private ColorScheme ColorGetter (CellColorGetterArgs args) - { - var stats = this.RowToStats (args.RowIndex); - - if (!Style.UseColors) { - return tableView.ColorScheme; - } + void WriteStateToTableView () + { + if (State == null) { + return; + } + tableView.Table = new FileDialogTableSource (this, State, Style, currentSortColumn, currentSortIsAsc); + ApplySort (); + tableView.Update (); + } - var color = Style.ColorProvider.GetColor (stats.FileSystemInfo) ?? new Color (Color.White); - var black = new Color (Color.Black); + ColorScheme ColorGetter (CellColorGetterArgs args) + { + var stats = RowToStats (args.RowIndex); - // TODO: Add some kind of cache for this - return new ColorScheme { - Normal = new Attribute (color, black), - HotNormal = new Attribute (color, black), - Focus = new Attribute (black, color), - HotFocus = new Attribute (black, color), - }; + if (!Style.UseColors) { + return tableView.ColorScheme; } - /// - /// If is this returns a union of all - /// in the selection. - /// - /// - private IEnumerable MultiRowToStats () - { - var toReturn = new HashSet (); - - if (this.AllowsMultipleSelection && this.tableView.MultiSelectedRegions.Any ()) { - foreach (var p in this.tableView.GetAllSelectedCells ()) { + var color = Style.ColorProvider.GetColor (stats.FileSystemInfo) ?? new Color (Color.White); + var black = new Color (Color.Black); - var add = this.State?.Children [p.Y]; - if (add != null) { - toReturn.Add (add); - } - } - } + // TODO: Add some kind of cache for this + return new ColorScheme { + Normal = new Attribute (color, black), + HotNormal = new Attribute (color, black), + Focus = new Attribute (black, color), + HotFocus = new Attribute (black, color) + }; + } - return toReturn; - } - private FileSystemInfoStats RowToStats (int rowIndex) - { - return this.State?.Children [rowIndex]; - } + /// + /// If is this returns a union of all + /// in the selection. + /// + /// + IEnumerable MultiRowToStats () + { + var toReturn = new HashSet (); - private void PathChanged () - { - // avoid re-entry - if (this.pushingState) { - return; - } + if (AllowsMultipleSelection && tableView.MultiSelectedRegions.Any ()) { - var path = this.tbPath.Text; + foreach (var p in tableView.GetAllSelectedCells ()) { - if (string.IsNullOrWhiteSpace (path)) { - return; + var add = State?.Children [p.Y]; + if (add != null) { + toReturn.Add (add); + } } + } - var dir = this.StringToDirectoryInfo (path); + return toReturn; + } - if (dir.Exists) { - this.PushState (dir, true, false); - } else - if (dir.Parent?.Exists ?? false) { - this.PushState (dir.Parent, true, false); - } + FileSystemInfoStats RowToStats (int rowIndex) => State?.Children [rowIndex]; - tbPath.Autocomplete.GenerateSuggestions (new AutocompleteFilepathContext (tbPath.Text, tbPath.CursorPosition, State)); + void PathChanged () + { + // avoid re-entry + if (pushingState) { + return; } - private IDirectoryInfo StringToDirectoryInfo (string path) - { - // if you pass new DirectoryInfo("C:") you get a weird object - // where the FullName is in fact the current working directory. - // really not what most users would expect - if (Regex.IsMatch (path, "^\\w:$")) { - return fileSystem.DirectoryInfo.New (path + System.IO.Path.DirectorySeparatorChar); - } + var path = tbPath.Text; - return fileSystem.DirectoryInfo.New (path); + if (string.IsNullOrWhiteSpace (path)) { + return; } - /// - /// Select in the table view (if present) - /// - /// - internal void RestoreSelection (IFileSystemInfo toRestore) - { - tableView.SelectedRow = State.Children.IndexOf (r => r.FileSystemInfo == toRestore); - tableView.EnsureSelectedCellIsVisible (); + var dir = StringToDirectoryInfo (path); + + if (dir.Exists) { + PushState (dir, true, false); + } else if (dir.Parent?.Exists ?? false) { + PushState (dir.Parent, true, false); } - internal void ApplySort () - { - var stats = State?.Children ?? new FileSystemInfoStats [0]; + tbPath.Autocomplete.GenerateSuggestions (new AutocompleteFilepathContext (tbPath.Text, tbPath.CursorPosition, State)); + } - // This portion is never reordered (aways .. at top then folders) - var forcedOrder = stats - .OrderByDescending (f => f.IsParent) - .ThenBy (f => f.IsDir ? -1 : 100); + IDirectoryInfo StringToDirectoryInfo (string path) + { + // if you pass new DirectoryInfo("C:") you get a weird object + // where the FullName is in fact the current working directory. + // really not what most users would expect + if (Regex.IsMatch (path, "^\\w:$")) { + return fileSystem.DirectoryInfo.New (path + System.IO.Path.DirectorySeparatorChar); + } - // This portion is flexible based on the column clicked (e.g. alphabetical) - var ordered = - this.currentSortIsAsc ? - forcedOrder.ThenBy (f => FileDialogTableSource.GetRawColumnValue (currentSortColumn, f)) : - forcedOrder.ThenByDescending (f => FileDialogTableSource.GetRawColumnValue (currentSortColumn, f)); + return fileSystem.DirectoryInfo.New (path); + } - State.Children = ordered.ToArray (); + /// + /// Select in the table view (if present) + /// + /// + internal void RestoreSelection (IFileSystemInfo toRestore) + { + tableView.SelectedRow = State.Children.IndexOf (r => r.FileSystemInfo == toRestore); + tableView.EnsureSelectedCellIsVisible (); + } - this.tableView.Update (); - } + internal void ApplySort () + { + var stats = State?.Children ?? new FileSystemInfoStats [0]; - private void SortColumn (int clickedCol) - { - this.GetProposedNewSortOrder (clickedCol, out var isAsc); - this.SortColumn (clickedCol, isAsc); - this.tableView.Table = new FileDialogTableSource (this, State, Style, currentSortColumn, currentSortIsAsc); - } + // This portion is never reordered (aways .. at top then folders) + var forcedOrder = stats + .OrderByDescending (f => f.IsParent) + .ThenBy (f => f.IsDir ? -1 : 100); - internal void SortColumn (int col, bool isAsc) - { - // set a sort order - this.currentSortColumn = col; - this.currentSortIsAsc = isAsc; + // This portion is flexible based on the column clicked (e.g. alphabetical) + var ordered = + currentSortIsAsc ? + forcedOrder.ThenBy (f => FileDialogTableSource.GetRawColumnValue (currentSortColumn, f)) : + forcedOrder.ThenByDescending (f => FileDialogTableSource.GetRawColumnValue (currentSortColumn, f)); - this.ApplySort (); - } + State.Children = ordered.ToArray (); - private string GetProposedNewSortOrder (int clickedCol, out bool isAsc) - { - // work out new sort order - if (this.currentSortColumn == clickedCol && this.currentSortIsAsc) { - isAsc = false; - return string.Format (Strings.fdCtxSortDesc, tableView.Table.ColumnNames [clickedCol]); - } else { - isAsc = true; - return string.Format (Strings.fdCtxSortAsc, tableView.Table.ColumnNames [clickedCol]); - } - } + tableView.Update (); + } - private void ShowHeaderContextMenu (int clickedCol, MouseEventEventArgs e) - { - var sort = this.GetProposedNewSortOrder (clickedCol, out var isAsc); + void SortColumn (int clickedCol) + { + GetProposedNewSortOrder (clickedCol, out var isAsc); + SortColumn (clickedCol, isAsc); + tableView.Table = new FileDialogTableSource (this, State, Style, currentSortColumn, currentSortIsAsc); + } - var contextMenu = new ContextMenu ( - e.MouseEvent.X + 1, - e.MouseEvent.Y + 1, - new MenuBarItem (new MenuItem [] - { - new MenuItem(string.Format (Strings.fdCtxHide, StripArrows (tableView.Table.ColumnNames[clickedCol])), string.Empty, () => this.HideColumn (clickedCol)), - new MenuItem(StripArrows (sort), string.Empty, () => this.SortColumn (clickedCol, isAsc)), - }) - ); + internal void SortColumn (int col, bool isAsc) + { + // set a sort order + currentSortColumn = col; + currentSortIsAsc = isAsc; - contextMenu.Show (); - } + ApplySort (); + } - private static string StripArrows (string columnName) - { - return columnName.Replace (" (▼)", string.Empty).Replace (" (▲)", string.Empty); + string GetProposedNewSortOrder (int clickedCol, out bool isAsc) + { + // work out new sort order + if (currentSortColumn == clickedCol && currentSortIsAsc) { + isAsc = false; + return string.Format (Strings.fdCtxSortDesc, tableView.Table.ColumnNames [clickedCol]); } + isAsc = true; + return string.Format (Strings.fdCtxSortAsc, tableView.Table.ColumnNames [clickedCol]); + } - private void ShowCellContextMenu (Point? clickedCell, MouseEventEventArgs e) - { - if (clickedCell == null) { - return; - } + void ShowHeaderContextMenu (int clickedCol, MouseEventEventArgs e) + { + var sort = GetProposedNewSortOrder (clickedCol, out var isAsc); - var contextMenu = new ContextMenu ( - e.MouseEvent.X + 1, - e.MouseEvent.Y + 1, - new MenuBarItem (new MenuItem [] - { - new MenuItem(Strings.fdCtxNew, string.Empty, New), - new MenuItem(Strings.fdCtxRename, string.Empty, Rename), - new MenuItem(Strings.fdCtxDelete,string.Empty, Delete), - }) - ); + var contextMenu = new ContextMenu ( + e.MouseEvent.X + 1, + e.MouseEvent.Y + 1, + new MenuBarItem (new MenuItem [] { + new (string.Format (Strings.fdCtxHide, StripArrows (tableView.Table.ColumnNames [clickedCol])), string.Empty, () => HideColumn (clickedCol)), + new (StripArrows (sort), string.Empty, () => SortColumn (clickedCol, isAsc)) + }) + ); - tableView.SetSelection (clickedCell.Value.X, clickedCell.Value.Y, false); + contextMenu.Show (); + } - contextMenu.Show (); - } + static string StripArrows (string columnName) => columnName.Replace (" (▼)", string.Empty).Replace (" (▲)", string.Empty); - private void HideColumn (int clickedCol) - { - var style = this.tableView.Style.GetOrCreateColumnStyle (clickedCol); - style.Visible = false; - this.tableView.Update (); + void ShowCellContextMenu (Point? clickedCell, MouseEventEventArgs e) + { + if (clickedCell == null) { + return; } - /// - /// State representing a recursive search from - /// downwards. - /// - internal class SearchState : FileDialogState { + var contextMenu = new ContextMenu ( + e.MouseEvent.X + 1, + e.MouseEvent.Y + 1, + new MenuBarItem (new MenuItem [] { + new (Strings.fdCtxNew, string.Empty, New), + new (Strings.fdCtxRename, string.Empty, Rename), + new (Strings.fdCtxDelete, string.Empty, Delete) + }) + ); - bool cancel = false; - bool finished = false; + tableView.SetSelection (clickedCell.Value.X, clickedCell.Value.Y, false); - // TODO: Add thread safe child adding - List found = new List (); - object oLockFound = new object (); - CancellationTokenSource token = new CancellationTokenSource (); + contextMenu.Show (); + } - public SearchState (IDirectoryInfo dir, FileDialog parent, string searchTerms) : base (dir, parent) - { - parent.SearchMatcher.Initialize (searchTerms); - Children = new FileSystemInfoStats [0]; - BeginSearch (); - } + void HideColumn (int clickedCol) + { + var style = tableView.Style.GetOrCreateColumnStyle (clickedCol); + style.Visible = false; + tableView.Update (); + } - private void BeginSearch () - { - Task.Run (() => { - RecursiveFind (Directory); - finished = true; - }); + /// + /// State representing a recursive search from + /// downwards. + /// + internal class SearchState : FileDialogState { + bool cancel; + bool finished; - Task.Run (() => { - UpdateChildren (); - }); - } + // TODO: Add thread safe child adding + readonly List found = new (); + readonly object oLockFound = new (); + readonly CancellationTokenSource token = new (); - private void UpdateChildren () - { - lock (Parent.onlyOneSearchLock) { - while (!cancel && !finished) { + public SearchState (IDirectoryInfo dir, FileDialog parent, string searchTerms) : base (dir, parent) + { + parent.SearchMatcher.Initialize (searchTerms); + Children = new FileSystemInfoStats [0]; + BeginSearch (); + } - try { - Task.Delay (250).Wait (token.Token); - } catch (OperationCanceledException) { - cancel = true; - } + void BeginSearch () + { + Task.Run (() => { + RecursiveFind (Directory); + finished = true; + }); + + Task.Run (() => { + UpdateChildren (); + }); + } - if (cancel || finished) { - break; - } + void UpdateChildren () + { + lock (Parent.onlyOneSearchLock) { + while (!cancel && !finished) { - UpdateChildrenToFound (); + try { + Task.Delay (250).Wait (token.Token); + } catch (OperationCanceledException) { + cancel = true; } - if (finished && !cancel) { - UpdateChildrenToFound (); + if (cancel || finished) { + break; } - Application.Invoke (() => { - Parent.spinnerView.Visible = false; - }); + UpdateChildrenToFound (); } - } - private void UpdateChildrenToFound () - { - lock (oLockFound) { - Children = found.ToArray (); + if (finished && !cancel) { + UpdateChildrenToFound (); } Application.Invoke (() => { - Parent.tbPath.Autocomplete.GenerateSuggestions ( - new AutocompleteFilepathContext (Parent.tbPath.Text, Parent.tbPath.CursorPosition, this) - ); - Parent.WriteStateToTableView (); - - Parent.spinnerView.Visible = true; - Parent.spinnerView.SetNeedsDisplay (); + Parent.spinnerView.Visible = false; }); } + } + + void UpdateChildrenToFound () + { + lock (oLockFound) { + Children = found.ToArray (); + } - private void RecursiveFind (IDirectoryInfo directory) - { - foreach (var f in GetChildren (directory)) { + Application.Invoke (() => { + Parent.tbPath.Autocomplete.GenerateSuggestions ( + new AutocompleteFilepathContext (Parent.tbPath.Text, Parent.tbPath.CursorPosition, this) + ); + Parent.WriteStateToTableView (); - if (cancel) { - return; - } + Parent.spinnerView.Visible = true; + Parent.spinnerView.SetNeedsDisplay (); + }); + } - if (f.IsParent) { - continue; - } + void RecursiveFind (IDirectoryInfo directory) + { + foreach (var f in GetChildren (directory)) { - lock (oLockFound) { - if (found.Count >= FileDialog.MaxSearchResults) { - finished = true; - return; - } - } + if (cancel) { + return; + } + + if (f.IsParent) { + continue; + } - if (Parent.SearchMatcher.IsMatch (f.FileSystemInfo)) { - lock (oLockFound) { - found.Add (f); - } + lock (oLockFound) { + if (found.Count >= MaxSearchResults) { + finished = true; + return; } + } - if (f.FileSystemInfo is IDirectoryInfo sub) { - RecursiveFind (sub); + if (Parent.SearchMatcher.IsMatch (f.FileSystemInfo)) { + lock (oLockFound) { + found.Add (f); } } - } - internal override void RefreshChildren () - { + if (f.FileSystemInfo is IDirectoryInfo sub) { + RecursiveFind (sub); + } } + } - /// - /// Cancels the current search (if any). Returns true if a search - /// was running and cancellation was successfully set. - /// - /// - internal bool Cancel () - { - var alreadyCancelled = token.IsCancellationRequested || cancel; + internal override void RefreshChildren () { } + + /// + /// Cancels the current search (if any). Returns true if a search + /// was running and cancellation was successfully set. + /// + /// + internal bool Cancel () + { + var alreadyCancelled = token.IsCancellationRequested || cancel; - cancel = true; - token.Cancel (); + cancel = true; + token.Cancel (); - return !alreadyCancelled; - } + return !alreadyCancelled; } - internal class FileDialogCollectionNavigator : CollectionNavigatorBase { - private FileDialog fileDialog; + } - public FileDialogCollectionNavigator (FileDialog fileDialog) - { - this.fileDialog = fileDialog; - } + internal class FileDialogCollectionNavigator : CollectionNavigatorBase { + readonly FileDialog fileDialog; - protected override object ElementAt (int idx) - { - var val = FileDialogTableSource.GetRawColumnValue (fileDialog.tableView.SelectedColumn, fileDialog.State?.Children [idx]); - if (val == null) { - return string.Empty; - } + public FileDialogCollectionNavigator (FileDialog fileDialog) => this.fileDialog = fileDialog; - return val.ToString ().Trim ('.'); + protected override object ElementAt (int idx) + { + var val = FileDialogTableSource.GetRawColumnValue (fileDialog.tableView.SelectedColumn, fileDialog.State?.Children [idx]); + if (val == null) { + return string.Empty; } - protected override int GetCollectionLength () - { - return fileDialog.State?.Children.Length ?? 0; - } + return val.ToString ().Trim ('.'); } + + protected override int GetCollectionLength () => fileDialog.State?.Children.Length ?? 0; } } \ No newline at end of file diff --git a/Terminal.Gui/Views/HexView.cs b/Terminal.Gui/Views/HexView.cs index 4e38d6a4b3..ca350eb876 100644 --- a/Terminal.Gui/Views/HexView.cs +++ b/Terminal.Gui/Views/HexView.cs @@ -95,6 +95,17 @@ public HexView (Stream source) : base () KeyBindings.Add (KeyCode.CursorRight | KeyCode.CtrlMask, Command.EndOfLine); KeyBindings.Add (KeyCode.CursorUp | KeyCode.CtrlMask, Command.StartOfPage); KeyBindings.Add (KeyCode.CursorDown | KeyCode.CtrlMask, Command.EndOfPage); + + LayoutComplete += HexView_LayoutComplete; + } + + private void HexView_LayoutComplete (object sender, LayoutEventArgs e) + { + // Small buffers will just show the position, with the bsize field value (4 bytes) + bytesPerLine = bsize; + if (Bounds.Width - displayWidth > 17) { + bytesPerLine = bsize * ((Bounds.Width - displayWidth) / 18); + } } /// @@ -174,20 +185,6 @@ int bytesPerLine { } } - /// - public override Rect Frame { - get => base.Frame; - set { - base.Frame = value; - - // Small buffers will just show the position, with the bsize field value (4 bytes) - bytesPerLine = bsize; - if (value.Width - displayWidth > 17) { - bytesPerLine = bsize * ((value.Width - displayWidth) / 18); - } - } - } - // // This is used to support editing of the buffer on a peer List<>, // the offset corresponds to an offset relative to DisplayStart, and @@ -214,6 +211,7 @@ public override void OnDrawContent (Rect contentArea) Driver.SetAttribute (current); Move (0, 0); + // BUGBUG: Bounds!!!! var frame = Frame; int nblocks = bytesPerLine / bsize; @@ -309,6 +307,7 @@ void RedisplayLine (long pos) int delta = (int)(pos - DisplayStart); int line = delta / bytesPerLine; + // BUGBUG: Bounds! SetNeedsDisplay (new Rect (0, line, Frame.Width, 1)); } @@ -331,6 +330,7 @@ bool MoveStartOfLine () bool MoveEnd () { position = source.Length; + // BUGBUG: Bounds! if (position >= DisplayStart + bytesPerLine * Frame.Height) { SetDisplayStart (position); SetNeedsDisplay (); @@ -396,6 +396,7 @@ bool MoveRight () if (position < source.Length) { position++; } + // BUGBUG: Bounds! if (position >= DisplayStart + bytesPerLine * Frame.Height) { SetDisplayStart (DisplayStart + bytesPerLine); SetNeedsDisplay (); @@ -424,6 +425,7 @@ bool MoveUp (int bytes) bool MoveDown (int bytes) { + // BUGBUG: Bounds! RedisplayLine (position); if (position + bytes < source.Length) { position += bytes; @@ -513,6 +515,8 @@ public virtual void OnPositionChanged () /// public override bool MouseEvent (MouseEvent me) { + // BUGBUG: Test this with a border! Assumes Frame == Bounds! + if (!me.Flags.HasFlag (MouseFlags.Button1Clicked) && !me.Flags.HasFlag (MouseFlags.Button1DoubleClicked) && !me.Flags.HasFlag (MouseFlags.WheeledDown) && !me.Flags.HasFlag (MouseFlags.WheeledUp)) { return false; @@ -595,6 +599,9 @@ public override bool MouseEvent (MouseEvent me) /// public Point CursorPosition { get { + if (!IsInitialized) { + return new Point (0, 0); + } int delta = (int)position; int line = delta / bytesPerLine + 1; int item = delta % bytesPerLine + 1; diff --git a/Terminal.Gui/Views/Label.cs b/Terminal.Gui/Views/Label.cs index 40350e9c57..b69418564d 100644 --- a/Terminal.Gui/Views/Label.cs +++ b/Terminal.Gui/Views/Label.cs @@ -1,141 +1,116 @@ -// -// Label.cs: Label control -// -// Authors: -// Miguel de Icaza (miguel@gnome.org) -// +using System; + +namespace Terminal.Gui; + +/// +/// The Label displays a string at a given position and supports multiple lines separated by newline +/// characters. +/// Multi-line Labels support word wrap. +/// +/// +/// The view is functionality identical to and is included for API backwards +/// compatibility. +/// +public class Label : View { + /// + public Label () => SetInitialProperties (); + + /// + public Label (Rect frame, bool autosize = false) : base (frame) => SetInitialProperties (autosize); + + /// + public Label (string text, bool autosize = true) : base (text) => SetInitialProperties (autosize); + + /// + public Label (Rect rect, string text, bool autosize = false) : base (rect, text) => SetInitialProperties (autosize); + + /// + public Label (int x, int y, string text, bool autosize = true) : base (x, y, text) => SetInitialProperties (autosize); + + /// + public Label (string text, TextDirection direction, bool autosize = true) + : base (text, direction) => SetInitialProperties (autosize); + + void SetInitialProperties (bool autosize = true) + { + Height = 1; + AutoSize = autosize; + // Things this view knows how to do + AddCommand (Command.Default, () => { + // BUGBUG: This is a hack, but it does work. + var can = CanFocus; + CanFocus = true; + SetFocus (); + SuperView.FocusNext (); + CanFocus = can; + return true; + }); + AddCommand (Command.Accept, () => AcceptKey ()); + + // Default key bindings for this view + KeyBindings.Add (KeyCode.Space, Command.Accept); + } -using System; -using System.Text; + bool AcceptKey () + { + if (!HasFocus) { + SetFocus (); + } + OnClicked (); + return true; + } -namespace Terminal.Gui { /// - /// The Label displays a string at a given position and supports multiple lines separated by newline characters. - /// Multi-line Labels support word wrap. + /// The event fired when the user clicks the primary mouse button within the Bounds of this + /// or if the user presses the action key while this view is focused. (TODO: IsDefault) /// /// - /// The view is functionality identical to and is included for API backwards compatibility. + /// Client code can hook up to this event, it is + /// raised when the button is activated either with + /// the mouse or the keyboard. /// - public class Label : View { - /// - public Label () - { - SetInitialProperties (); - } - - /// - public Label (Rect frame, bool autosize = false) : base (frame) - { - SetInitialProperties (autosize); - } + public event EventHandler Clicked; - /// - public Label (string text, bool autosize = true) : base (text) - { - SetInitialProperties (autosize); - } - - /// - public Label (Rect rect, string text, bool autosize = false) : base (rect, text) - { - SetInitialProperties (autosize); - } - - /// - public Label (int x, int y, string text, bool autosize = true) : base (x, y, text) - { - SetInitialProperties (autosize); - } - - /// - public Label (string text, TextDirection direction, bool autosize = true) - : base (text, direction) - { - SetInitialProperties (autosize); + /// + /// Method invoked when a mouse event is generated + /// + /// + /// true, if the event was handled, false otherwise. + public override bool OnMouseEvent (MouseEvent mouseEvent) + { + var args = new MouseEventEventArgs (mouseEvent); + if (OnMouseClick (args)) { + return true; } - - void SetInitialProperties (bool autosize = true) - { - Height = 1; - AutoSize = autosize; - // Things this view knows how to do - AddCommand (Command.Default, () => { - // BUGBUG: This is a hack, but it does work. - var can = CanFocus; - CanFocus = true; - SetFocus (); - SuperView.FocusNext (); - CanFocus = can; - return true; - }); - AddCommand (Command.Accept, () => AcceptKey ()); - - // Default key bindings for this view - KeyBindings.Add (KeyCode.Space, Command.Accept); + if (MouseEvent (mouseEvent)) { + return true; } - bool AcceptKey () - { - if (!HasFocus) { + if (mouseEvent.Flags == MouseFlags.Button1Clicked) { + if (!HasFocus && SuperView != null) { + if (!SuperView.HasFocus) { + SuperView.SetFocus (); + } SetFocus (); + SetNeedsDisplay (); } + OnClicked (); return true; } - - /// - /// The event fired when the user clicks the primary mouse button within the Bounds of this - /// or if the user presses the action key while this view is focused. (TODO: IsDefault) - /// - /// - /// Client code can hook up to this event, it is - /// raised when the button is activated either with - /// the mouse or the keyboard. - /// - public event EventHandler Clicked; - - /// - /// Method invoked when a mouse event is generated - /// - /// - /// true, if the event was handled, false otherwise. - public override bool OnMouseEvent (MouseEvent mouseEvent) - { - MouseEventEventArgs args = new MouseEventEventArgs (mouseEvent); - if (OnMouseClick (args)) - return true; - if (MouseEvent (mouseEvent)) - return true; - - if (mouseEvent.Flags == MouseFlags.Button1Clicked) { - if (!HasFocus && SuperView != null) { - if (!SuperView.HasFocus) { - SuperView.SetFocus (); - } - SetFocus (); - SetNeedsDisplay (); - } - - OnClicked (); - return true; - } - return false; - } - - /// - public override bool OnEnter (View view) - { - Application.Driver.SetCursorVisibility (CursorVisibility.Invisible); + return false; + } - return base.OnEnter (view); - } + /// + public override bool OnEnter (View view) + { + Application.Driver.SetCursorVisibility (CursorVisibility.Invisible); - /// - /// Virtual method to invoke the event. - /// - public virtual void OnClicked () - { - Clicked?.Invoke (this, EventArgs.Empty); - } + return base.OnEnter (view); } -} + + /// + /// Virtual method to invoke the event. + /// + public virtual void OnClicked () => Clicked?.Invoke (this, EventArgs.Empty); +} \ No newline at end of file diff --git a/Terminal.Gui/Views/Menu/ContextMenu.cs b/Terminal.Gui/Views/Menu/ContextMenu.cs index 53983536b8..c1ec2af7f5 100644 --- a/Terminal.Gui/Views/Menu/ContextMenu.cs +++ b/Terminal.Gui/Views/Menu/ContextMenu.cs @@ -99,7 +99,7 @@ public void Show () } _container = Application.Current; _container.Closing += Container_Closing; - var frame = new Rect (0, 0, View.Driver.Cols, View.Driver.Rows); + var frame = Application.Driver.Bounds; var position = Position; if (Host != null) { Host.BoundsToScreen (frame.X, frame.Y, out int x, out int y); diff --git a/Terminal.Gui/Views/Menu/MenuBar.cs b/Terminal.Gui/Views/Menu/MenuBar.cs index 830519c533..321a48a4fd 100644 --- a/Terminal.Gui/Views/Menu/MenuBar.cs +++ b/Terminal.Gui/Views/Menu/MenuBar.cs @@ -1445,7 +1445,7 @@ internal Point GetScreenOffset () if (Driver == null) { return Point.Empty; } - var superViewFrame = SuperView == null ? new Rect (0, 0, Driver.Cols, Driver.Rows) : SuperView.Frame; + var superViewFrame = SuperView == null ? Driver.Bounds : SuperView.Frame; var sv = SuperView == null ? Application.Current : SuperView; var boundsOffset = sv.GetBoundsOffset (); return new Point (superViewFrame.X - sv.Frame.X - boundsOffset.X, @@ -1458,7 +1458,7 @@ internal Point GetScreenOffset () /// The location offset. internal Point GetScreenOffsetFromCurrent () { - var screen = new Rect (0, 0, Driver.Cols, Driver.Rows); + var screen = Driver.Bounds; var currentFrame = Application.Current.Frame; var boundsOffset = Application.Top.GetBoundsOffset (); return new Point (screen.X - currentFrame.X - boundsOffset.X diff --git a/Terminal.Gui/Views/ProgressBar.cs b/Terminal.Gui/Views/ProgressBar.cs index 8ba75c6728..45337b9ce1 100644 --- a/Terminal.Gui/Views/ProgressBar.cs +++ b/Terminal.Gui/Views/ProgressBar.cs @@ -82,7 +82,8 @@ void ProgressBar_Initialized (object sender, EventArgs e) void ProgressBar_LayoutStarted (object sender, EventArgs e) { - Bounds = new Rect (Bounds.Location, new Size (Bounds.Width, 1)); + // TODO: use Dim.Auto + Height = 1 + GetFramesThickness ().Vertical; } float _fraction; diff --git a/Terminal.Gui/Views/RadioGroup.cs b/Terminal.Gui/Views/RadioGroup.cs index ed8ceb24bc..0061f30c3c 100644 --- a/Terminal.Gui/Views/RadioGroup.cs +++ b/Terminal.Gui/Views/RadioGroup.cs @@ -10,7 +10,7 @@ namespace Terminal.Gui; public class RadioGroup : View { int _selected = -1; int _cursor; - DisplayModeLayout _displayMode; + Orientation _orientation = Orientation.Vertical; int _horizontalSpace = 2; List<(int pos, int length)> _horizontal; @@ -26,7 +26,7 @@ public class RadioGroup : View { /// The index of the item to be selected, the value is clamped to the number of items. public RadioGroup (string [] radioLabels, int selected = 0) : base () { - SetInitialProperties (Rect.Empty, radioLabels, selected); + SetInitialProperties (radioLabels, selected); } /// @@ -37,22 +37,10 @@ public RadioGroup (string [] radioLabels, int selected = 0) : base () /// The index of item to be selected, the value is clamped to the number of items. public RadioGroup (Rect rect, string [] radioLabels, int selected = 0) : base (rect) { - SetInitialProperties (rect, radioLabels, selected); + SetInitialProperties (radioLabels, selected); } - /// - /// Initializes a new instance of the class using layout. - /// The frame is computed from the provided radio labels. - /// - /// The x coordinate. - /// The y coordinate. - /// The radio labels; an array of strings that can contain hotkeys using an underscore before the letter. - /// The item to be selected, the value is clamped to the number of items. - public RadioGroup (int x, int y, string [] radioLabels, int selected = 0) : - this (MakeRect (x, y, radioLabels != null ? radioLabels.ToList () : null), radioLabels, selected) - { } - - void SetInitialProperties (Rect rect, string [] radioLabels, int selected) + void SetInitialProperties (string [] radioLabels, int selected) { HotKeySpecifier = new Rune ('_'); @@ -61,7 +49,6 @@ void SetInitialProperties (Rect rect, string [] radioLabels, int selected) } _selected = selected; - Frame = rect; CanFocus = true; // Things this view knows how to do @@ -87,26 +74,42 @@ void RadioGroup_LayoutStarted (object sender, EventArgs e) } /// - /// Gets or sets the for this . + /// Gets or sets the for this . The default is . /// - public DisplayModeLayout DisplayMode { - get { return _displayMode; } - set { - if (_displayMode != value) { - _displayMode = value; - SetWidthHeight (_radioLabels); - SetNeedsDisplay (); - } + public Orientation Orientation { + get => _orientation; + set => OnOrientationChanged (value); + } + + /// + /// Fired when the view orientation has changed. Can be cancelled by setting + /// to true. + /// + public event EventHandler OrientationChanged; + + /// + /// Called when the view orientation has changed. Invokes the event. + /// + /// + /// True of the event was cancelled. + public virtual bool OnOrientationChanged (Orientation newOrientation) + { + var args = new OrientationEventArgs (newOrientation); + OrientationChanged?.Invoke (this, args); + if (!args.Cancel) { + _orientation = newOrientation; + SetNeedsLayout (); } + return args.Cancel; } /// - /// Gets or sets the horizontal space for this if the is + /// Gets or sets the horizontal space for this if the is /// public int HorizontalSpace { get { return _horizontalSpace; } set { - if (_horizontalSpace != value && _displayMode == DisplayModeLayout.Horizontal) { + if (_horizontalSpace != value && _orientation == Orientation.Horizontal) { _horizontalSpace = value; SetWidthHeight (_radioLabels); UpdateTextFormatterText (); @@ -117,24 +120,24 @@ public int HorizontalSpace { void SetWidthHeight (List radioLabels) { - switch (_displayMode) { - case DisplayModeLayout.Vertical: + switch (_orientation) { + case Orientation.Vertical: var r = MakeRect (0, 0, radioLabels); - Bounds = new Rect (Bounds.Location, new Size (r.Width, radioLabels.Count)); + if (IsInitialized) { + Width = r.Width + GetFramesThickness ().Horizontal; + Height = radioLabels.Count + GetFramesThickness ().Vertical; + } break; - case DisplayModeLayout.Horizontal: + case Orientation.Horizontal: CalculateHorizontalPositions (); var length = 0; foreach (var item in _horizontal) { length += item.length; } - var hr = new Rect (0, 0, length, 1); - if (IsAdded && LayoutStyle == LayoutStyle.Computed) { - Width = hr.Width; - Height = 1; - } else { - Bounds = new Rect (Bounds.Location, new Size (hr.Width, radioLabels.Count)); + if (IsInitialized) { + Width = length + GetFramesThickness ().Vertical; + Height = 1 + GetFramesThickness ().Horizontal; } break; } @@ -199,7 +202,7 @@ public string [] RadioLabels { if (KeyBindings.TryGet (key, out _)) { // Search RadioLabels for (int i = 0; i < _radioLabels.Count; i++) { - if (TextFormatter.FindHotKey (_radioLabels [i], HotKeySpecifier, true, out _, out var hotKey) + if (TextFormatter.FindHotKey (_radioLabels [i], HotKeySpecifier, true, out _, out var hotKey) && (key.NoAlt.NoCtrl.NoShift) == hotKey) { SelectedItem = i; keyEvent.Scope = KeyBindingScope.HotKey; @@ -213,7 +216,7 @@ public string [] RadioLabels { void CalculateHorizontalPositions () { - if (_displayMode == DisplayModeLayout.Horizontal) { + if (_orientation == Orientation.Horizontal) { _horizontal = new List<(int pos, int length)> (); int start = 0; int length = 0; @@ -232,11 +235,11 @@ public override void OnDrawContent (Rect contentArea) Driver.SetAttribute (GetNormalColor ()); for (int i = 0; i < _radioLabels.Count; i++) { - switch (DisplayMode) { - case DisplayModeLayout.Vertical: + switch (Orientation) { + case Orientation.Vertical: Move (0, i); break; - case DisplayModeLayout.Horizontal: + case Orientation.Horizontal: Move (_horizontal [i].pos, 0); break; } @@ -276,11 +279,11 @@ public override void OnDrawContent (Rect contentArea) /// public override void PositionCursor () { - switch (DisplayMode) { - case DisplayModeLayout.Vertical: + switch (Orientation) { + case Orientation.Vertical: Move (0, _cursor); break; - case DisplayModeLayout.Horizontal: + case Orientation.Horizontal: Move (_horizontal [_cursor].pos, 0); break; } @@ -374,11 +377,11 @@ public override bool MouseEvent (MouseEvent me) int boundsX = me.X; int boundsY = me.Y; - var pos = _displayMode == DisplayModeLayout.Horizontal ? boundsX : boundsY; - var rCount = _displayMode == DisplayModeLayout.Horizontal ? _horizontal.Last ().pos + _horizontal.Last ().length : _radioLabels.Count; + var pos = _orientation == Orientation.Horizontal ? boundsX : boundsY; + var rCount = _orientation == Orientation.Horizontal ? _horizontal.Last ().pos + _horizontal.Last ().length : _radioLabels.Count; if (pos < rCount) { - var c = _displayMode == DisplayModeLayout.Horizontal ? _horizontal.FindIndex ((x) => x.pos <= boundsX && x.pos + x.length - 2 >= boundsX) : boundsY; + var c = _orientation == Orientation.Horizontal ? _horizontal.FindIndex ((x) => x.pos <= boundsX && x.pos + x.length - 2 >= boundsX) : boundsY; if (c > -1) { _cursor = SelectedItem = c; SetNeedsDisplay (); @@ -396,16 +399,3 @@ public override bool OnEnter (View view) } } -/// -/// Used for choose the display mode of this -/// -public enum DisplayModeLayout { - /// - /// Vertical mode display. It's the default. - /// - Vertical, - /// - /// Horizontal mode display. - /// - Horizontal -} diff --git a/Terminal.Gui/Views/ScrollBarView.cs b/Terminal.Gui/Views/ScrollBarView.cs index 8932bd1987..c6171f8d52 100644 --- a/Terminal.Gui/Views/ScrollBarView.cs +++ b/Terminal.Gui/Views/ScrollBarView.cs @@ -8,816 +8,824 @@ using System; using System.Text; -namespace Terminal.Gui { +namespace Terminal.Gui; + +/// +/// ScrollBarViews are views that display a 1-character scrollbar, either horizontal or vertical +/// +/// +/// +/// The scrollbar is drawn to be a representation of the Size, assuming that the +/// scroll position is set at Position. +/// +/// +/// If the region to display the scrollbar is larger than three characters, +/// arrow indicators are drawn. +/// +/// +public class ScrollBarView : View { + bool _autoHideScrollBars = true; + View _contentBottomRightCorner; + bool _hosted; + bool _keepContentAlwaysInViewport = true; + + int _lastLocation = -1; + ScrollBarView _otherScrollBarView; + int _posBarOffset; + int _posBottomTee; + int _posLeftTee; + int _posRightTee; + + int _posTopTee; + bool _showScrollIndicator; + int _size, _position; + bool _vertical; + /// - /// ScrollBarViews are views that display a 1-character scrollbar, either horizontal or vertical + /// Initializes a new instance of the class using + /// layout. /// - /// - /// - /// The scrollbar is drawn to be a representation of the Size, assuming that the - /// scroll position is set at Position. - /// - /// - /// If the region to display the scrollbar is larger than three characters, - /// arrow indicators are drawn. - /// - /// - public class ScrollBarView : View { - bool _vertical; - int _size, _position; - bool _showScrollIndicator; - bool _keepContentAlwaysInViewport = true; - bool _autoHideScrollBars = true; - bool _hosted; - ScrollBarView _otherScrollBarView; - View _contentBottomRightCorner; - - bool _showBothScrollIndicator => OtherScrollBarView?._showScrollIndicator == true && _showScrollIndicator; - - /// - /// Initializes a new instance of the class using layout. - /// - /// Frame for the scrollbar. - public ScrollBarView (Rect rect) : this (rect, 0, 0, false) { } - - /// - /// Initializes a new instance of the class using layout. - /// - /// Frame for the scrollbar. - /// The size that this scrollbar represents. Sets the property. - /// The position within this scrollbar. Sets the property. - /// If set to true this is a vertical scrollbar, otherwise, the scrollbar is horizontal. Sets the property. - public ScrollBarView (Rect rect, int size, int position, bool isVertical) : base (rect) - { - SetInitialProperties (size, position, isVertical); - } - - /// - /// Initializes a new instance of the class using layout. - /// - public ScrollBarView () : this (0, 0, false) { } - - /// - /// Initializes a new instance of the class using layout. - /// - /// The size that this scrollbar represents. - /// The position within this scrollbar. - /// If set to true this is a vertical scrollbar, otherwise, the scrollbar is horizontal. - public ScrollBarView (int size, int position, bool isVertical) : base () - { - SetInitialProperties (size, position, isVertical); - } - - /// - /// Initializes a new instance of the class using layout. - /// - /// The view that will host this scrollbar. - /// If set to true this is a vertical scrollbar, otherwise, the scrollbar is horizontal. - /// If set to true (default) will have the other scrollbar, otherwise will have only one. - public ScrollBarView (View host, bool isVertical, bool showBothScrollIndicator = true) : this (0, 0, isVertical) - { - if (host == null) { - throw new ArgumentNullException ("The host parameter can't be null."); - } else if (host.SuperView == null) { - throw new ArgumentNullException ("The host SuperView parameter can't be null."); - } - _hosted = true; - ColorScheme = host.ColorScheme; - X = isVertical ? Pos.Right (host) - 1 : Pos.Left (host); - Y = isVertical ? Pos.Top (host) : Pos.Bottom (host) - 1; - Host = host; - CanFocus = false; - Enabled = host.Enabled; - Visible = host.Visible; - //Host.CanFocusChanged += Host_CanFocusChanged; - Host.EnabledChanged += Host_EnabledChanged; - Host.VisibleChanged += Host_VisibleChanged; - Host.SuperView.Add (this); - AutoHideScrollBars = true; - if (showBothScrollIndicator) { - OtherScrollBarView = new ScrollBarView (0, 0, !isVertical) { - Id = "OtherScrollBarView", - ColorScheme = host.ColorScheme, - Host = host, - CanFocus = false, - Enabled = host.Enabled, - Visible = host.Visible, - OtherScrollBarView = this - }; - OtherScrollBarView._hosted = true; - OtherScrollBarView.X = OtherScrollBarView.IsVertical ? Pos.Right (host) - 1 : Pos.Left (host); - OtherScrollBarView.Y = OtherScrollBarView.IsVertical ? Pos.Top (host) : Pos.Bottom (host) - 1; - OtherScrollBarView.Host.SuperView.Add (OtherScrollBarView); - OtherScrollBarView.ShowScrollIndicator = true; - } - ShowScrollIndicator = true; - CreateBottomRightCorner (); - ClearOnVisibleFalse = false; - } - - private void CreateBottomRightCorner () - { - if (Host != null && (_contentBottomRightCorner == null && OtherScrollBarView == null - || (_contentBottomRightCorner == null && OtherScrollBarView != null && OtherScrollBarView._contentBottomRightCorner == null))) { - - _contentBottomRightCorner = new View () { - Id = "contentBottomRightCorner", - Visible = Host.Visible, - ClearOnVisibleFalse = false, - ColorScheme = ColorScheme - }; - if (_hosted) { - Host.SuperView.Add (_contentBottomRightCorner); - } else { - Host.Add (_contentBottomRightCorner); - } - _contentBottomRightCorner.X = Pos.Right (Host) - 1; - _contentBottomRightCorner.Y = Pos.Bottom (Host) - 1; - _contentBottomRightCorner.Width = 1; - _contentBottomRightCorner.Height = 1; - _contentBottomRightCorner.MouseClick += ContentBottomRightCorner_MouseClick; - _contentBottomRightCorner.DrawContent += _contentBottomRightCorner_DrawContent; - } - } - - private void _contentBottomRightCorner_DrawContent (object sender, DrawEventArgs e) - { - Driver.SetAttribute (Host.HasFocus ? ColorScheme.Focus : GetNormalColor ()); - } + /// Frame for the scrollbar. + public ScrollBarView (Rect rect) : this (rect, 0, 0, false) { } - private void Host_VisibleChanged (object sender, EventArgs e) - { - if (!Host.Visible) { - Visible = Host.Visible; - if (_otherScrollBarView != null) { - _otherScrollBarView.Visible = Visible; - } - _contentBottomRightCorner.Visible = Visible; - } else { - ShowHideScrollBars (); - } - } - - private void Host_EnabledChanged (object sender, EventArgs e) - { - Enabled = Host.Enabled; - if (_otherScrollBarView != null) { - _otherScrollBarView.Enabled = Enabled; - } - _contentBottomRightCorner.Enabled = Enabled; - } - - //private void Host_CanFocusChanged () - //{ - // CanFocus = Host.CanFocus; - // if (otherScrollBarView != null) { - // otherScrollBarView.CanFocus = CanFocus; - // } - //} + /// + /// Initializes a new instance of the class using + /// layout. + /// + /// Frame for the scrollbar. + /// The size that this scrollbar represents. Sets the property. + /// The position within this scrollbar. Sets the property. + /// + /// If set to true this is a vertical scrollbar, otherwise, the scrollbar is horizontal. Sets the + /// property. + /// + public ScrollBarView (Rect rect, int size, int position, bool isVertical) : base (rect) => SetInitialProperties (size, position, isVertical); - void ContentBottomRightCorner_MouseClick (object sender, MouseEventEventArgs me) - { - if (me.MouseEvent.Flags == MouseFlags.WheeledDown || me.MouseEvent.Flags == MouseFlags.WheeledUp - || me.MouseEvent.Flags == MouseFlags.WheeledRight || me.MouseEvent.Flags == MouseFlags.WheeledLeft) { + /// + /// Initializes a new instance of the class using + /// layout. + /// + public ScrollBarView () : this (0, 0, false) { } - MouseEvent (me.MouseEvent); - } else if (me.MouseEvent.Flags == MouseFlags.Button1Clicked) { - Host.SetFocus (); - } + /// + /// Initializes a new instance of the class using + /// layout. + /// + /// The size that this scrollbar represents. + /// The position within this scrollbar. + /// If set to true this is a vertical scrollbar, otherwise, the scrollbar is horizontal. + public ScrollBarView (int size, int position, bool isVertical) => SetInitialProperties (size, position, isVertical); - me.Handled = true; + /// + /// Initializes a new instance of the class using + /// layout. + /// + /// The view that will host this scrollbar. + /// If set to true this is a vertical scrollbar, otherwise, the scrollbar is horizontal. + /// + /// If set to true (default) will have the other scrollbar, otherwise will + /// have only one. + /// + public ScrollBarView (View host, bool isVertical, bool showBothScrollIndicator = true) : this (0, 0, isVertical) + { + if (host == null) { + throw new ArgumentNullException ("The host parameter can't be null."); } + if (host.SuperView == null) { + throw new ArgumentNullException ("The host SuperView parameter can't be null."); + } + _hosted = true; + ColorScheme = host.ColorScheme; + X = isVertical ? Pos.Right (host) - 1 : Pos.Left (host); + Y = isVertical ? Pos.Top (host) : Pos.Bottom (host) - 1; + Host = host; + CanFocus = false; + Enabled = host.Enabled; + Visible = host.Visible; + //Host.CanFocusChanged += Host_CanFocusChanged; + Host.EnabledChanged += Host_EnabledChanged; + Host.VisibleChanged += Host_VisibleChanged; + Host.SuperView.Add (this); + AutoHideScrollBars = true; + if (showBothScrollIndicator) { + OtherScrollBarView = new ScrollBarView (0, 0, !isVertical) { + Id = "OtherScrollBarView", + ColorScheme = host.ColorScheme, + Host = host, + CanFocus = false, + Enabled = host.Enabled, + Visible = host.Visible, + OtherScrollBarView = this + }; + OtherScrollBarView._hosted = true; + OtherScrollBarView.X = OtherScrollBarView.IsVertical ? Pos.Right (host) - 1 : Pos.Left (host); + OtherScrollBarView.Y = OtherScrollBarView.IsVertical ? Pos.Top (host) : Pos.Bottom (host) - 1; + OtherScrollBarView.Host.SuperView.Add (OtherScrollBarView); + OtherScrollBarView.ShowScrollIndicator = true; + } + ShowScrollIndicator = true; + CreateBottomRightCorner (); + ClearOnVisibleFalse = false; + } - void SetInitialProperties (int size, int position, bool isVertical) - { - Id = "ScrollBarView"; - _vertical = isVertical; - this._position = position; - this._size = size; - WantContinuousButtonPressed = true; - ClearOnVisibleFalse = false; - - Added += (s, e) => CreateBottomRightCorner (); + bool _showBothScrollIndicator => OtherScrollBarView?._showScrollIndicator == true && _showScrollIndicator; - Initialized += (s, e) => { + /// + /// If set to true this is a vertical scrollbar, otherwise, the scrollbar is horizontal. + /// + public bool IsVertical { + get => _vertical; + set { + _vertical = value; + if (IsInitialized) { SetWidthHeight (); - SetRelativeLayout (SuperView?.Frame ?? Host?.Frame ?? Frame); - if (Id == "OtherScrollBarView" || OtherScrollBarView == null) { - // Only do this once if both scrollbars are enabled - ShowHideScrollBars (); - } - SetPosition (position); - }; + } } + } - /// - /// If set to true this is a vertical scrollbar, otherwise, the scrollbar is horizontal. - /// - public bool IsVertical { - get => _vertical; - set { - _vertical = value; - if (IsInitialized) { - SetWidthHeight (); - } + /// + /// The size of content the scrollbar represents. + /// + /// The size. + /// + /// The is typically the size of the virtual content. E.g. when a Scrollbar is + /// part of a the Size is set to the appropriate dimension of . + /// + public int Size { + get => _size; + set { + _size = value; + if (IsInitialized) { + SetRelativeLayout (SuperView?.Frame ?? Host.Frame); + ShowHideScrollBars (false); + SetNeedsDisplay (); } } + } - /// - /// The size of content the scrollbar represents. - /// - /// The size. - /// The is typically the size of the virtual content. E.g. when a Scrollbar is - /// part of a the Size is set to the appropriate dimension of . - public int Size { - get => _size; - set { - _size = value; - if (IsInitialized) { - SetRelativeLayout (SuperView?.Frame ?? Host.Frame); - ShowHideScrollBars (false); - SetNeedsDisplay (); - } + /// + /// The position, relative to , to set the scrollbar at. + /// + /// The position. + public int Position { + get => _position; + set { + _position = value; + if (IsInitialized) { + // We're not initialized so we can't do anything fancy. Just cache value. + SetPosition (value); } } + } - /// - /// This event is raised when the position on the scrollbar has changed. - /// - public event EventHandler ChangedPosition; + // BUGBUG: v2 - for consistency this should be named "Parent" not "Host" + /// + /// Get or sets the view that host this + /// + public View Host { get; internal set; } - /// - /// The position, relative to , to set the scrollbar at. - /// - /// The position. - public int Position { - get => _position; - set { - _position = value; - if (IsInitialized) { - // We're not initialized so we can't do anything fancy. Just cache value. - SetPosition (value); - } + /// + /// Represent a vertical or horizontal ScrollBarView other than this. + /// + public ScrollBarView OtherScrollBarView { + get => _otherScrollBarView; + set { + if (value != null && (value.IsVertical && _vertical || !value.IsVertical && !_vertical)) { + throw new ArgumentException ($"There is already a {(_vertical ? "vertical" : "horizontal")} ScrollBarView."); } + _otherScrollBarView = value; } + } - // Helper to assist Initialized event handler - private void SetPosition (int newPosition) - { - if (CanScroll (newPosition - _position, out int max, _vertical)) { - if (max == newPosition - _position) { - _position = newPosition; + // BUGBUG: v2 - Why can't we get rid of this and just use Visible? + /// + /// Gets or sets the visibility for the vertical or horizontal scroll indicator. + /// + /// true if show vertical or horizontal scroll indicator; otherwise, false. + public bool ShowScrollIndicator { + get => _showScrollIndicator; + set { + //if (value == showScrollIndicator) { + // return; + //} + + _showScrollIndicator = value; + if (IsInitialized) { + SetNeedsLayout (); + if (value) { + Visible = true; } else { - _position = Math.Max (_position + max, 0); - } - } else if (max < 0) { - _position = Math.Max (_position + max, 0); - } else { - _position = Math.Max (newPosition, 0); - } - OnChangedPosition (); - SetNeedsDisplay (); - } - - // BUGBUG: v2 - for consistency this should be named "Parent" not "Host" - /// - /// Get or sets the view that host this - /// - public View Host { get; internal set; } - - /// - /// Represent a vertical or horizontal ScrollBarView other than this. - /// - public ScrollBarView OtherScrollBarView { - get => _otherScrollBarView; - set { - if (value != null && (value.IsVertical && _vertical || !value.IsVertical && !_vertical)) { - throw new ArgumentException ($"There is already a {(_vertical ? "vertical" : "horizontal")} ScrollBarView."); - } - _otherScrollBarView = value; - } - } - - // BUGBUG: v2 - Why can't we get rid of this and just use Visible? - /// - /// Gets or sets the visibility for the vertical or horizontal scroll indicator. - /// - /// true if show vertical or horizontal scroll indicator; otherwise, false. - public bool ShowScrollIndicator { - get => _showScrollIndicator; - set { - //if (value == showScrollIndicator) { - // return; - //} - - _showScrollIndicator = value; - if (IsInitialized) { - SetNeedsLayout (); - if (value) { - Visible = true; - } else { - Visible = false; - Position = 0; - } - SetWidthHeight (); + Visible = false; + Position = 0; } + SetWidthHeight (); } } + } - /// - /// Get or sets if the view-port is kept always visible in the area of this - /// - public bool KeepContentAlwaysInViewport { - get { return _keepContentAlwaysInViewport; } - set { - if (_keepContentAlwaysInViewport != value) { - _keepContentAlwaysInViewport = value; - int pos = 0; - if (value && !_vertical && _position + Host.Bounds.Width > _size) { - pos = _size - Host.Bounds.Width + (_showBothScrollIndicator ? 1 : 0); - } - if (value && _vertical && _position + Host.Bounds.Height > _size) { - pos = _size - Host.Bounds.Height + (_showBothScrollIndicator ? 1 : 0); - } - if (pos != 0) { - Position = pos; - } - if (OtherScrollBarView != null && OtherScrollBarView._keepContentAlwaysInViewport != value) { - OtherScrollBarView.KeepContentAlwaysInViewport = value; - } - if (pos == 0) { - Refresh (); - } + /// + /// Get or sets if the view-port is kept always visible in the area of this + /// + public bool KeepContentAlwaysInViewport { + get => _keepContentAlwaysInViewport; + set { + if (_keepContentAlwaysInViewport != value) { + _keepContentAlwaysInViewport = value; + var pos = 0; + if (value && !_vertical && _position + Host.Bounds.Width > _size) { + pos = _size - Host.Bounds.Width + (_showBothScrollIndicator ? 1 : 0); + } + if (value && _vertical && _position + Host.Bounds.Height > _size) { + pos = _size - Host.Bounds.Height + (_showBothScrollIndicator ? 1 : 0); + } + if (pos != 0) { + Position = pos; + } + if (OtherScrollBarView != null && OtherScrollBarView._keepContentAlwaysInViewport != value) { + OtherScrollBarView.KeepContentAlwaysInViewport = value; + } + if (pos == 0) { + Refresh (); } } } + } - /// - /// If true the vertical/horizontal scroll bars won't be showed if it's not needed. - /// - public bool AutoHideScrollBars { - get => _autoHideScrollBars; - set { - if (_autoHideScrollBars != value) { - _autoHideScrollBars = value; - SetNeedsDisplay (); - } + /// + /// If true the vertical/horizontal scroll bars won't be showed if it's not needed. + /// + public bool AutoHideScrollBars { + get => _autoHideScrollBars; + set { + if (_autoHideScrollBars != value) { + _autoHideScrollBars = value; + SetNeedsDisplay (); } } + } - /// - /// Virtual method to invoke the action event. - /// - public virtual void OnChangedPosition () - { - ChangedPosition?.Invoke (this, EventArgs.Empty); + void CreateBottomRightCorner () + { + if (Host != null && + (_contentBottomRightCorner == null && OtherScrollBarView == null || + _contentBottomRightCorner == null && OtherScrollBarView != null && OtherScrollBarView._contentBottomRightCorner == null)) { + + _contentBottomRightCorner = new View { + Id = "contentBottomRightCorner", + Visible = Host.Visible, + ClearOnVisibleFalse = false, + ColorScheme = ColorScheme + }; + if (_hosted) { + Host.SuperView.Add (_contentBottomRightCorner); + } else { + Host.Add (_contentBottomRightCorner); + } + _contentBottomRightCorner.X = Pos.Right (Host) - 1; + _contentBottomRightCorner.Y = Pos.Bottom (Host) - 1; + _contentBottomRightCorner.Width = 1; + _contentBottomRightCorner.Height = 1; + _contentBottomRightCorner.MouseClick += ContentBottomRightCorner_MouseClick; + _contentBottomRightCorner.DrawContent += _contentBottomRightCorner_DrawContent; } + } - /// - /// Only used for a hosted view that will update and redraw the scrollbars. - /// - public virtual void Refresh () - { + void _contentBottomRightCorner_DrawContent (object sender, DrawEventArgs e) => Driver.SetAttribute (Host.HasFocus ? ColorScheme.Focus : GetNormalColor ()); + + void Host_VisibleChanged (object sender, EventArgs e) + { + if (!Host.Visible) { + Visible = Host.Visible; + if (_otherScrollBarView != null) { + _otherScrollBarView.Visible = Visible; + } + _contentBottomRightCorner.Visible = Visible; + } else { ShowHideScrollBars (); } + } - void ShowHideScrollBars (bool redraw = true) - { - if (!_hosted || (_hosted && !_autoHideScrollBars)) { - if (_contentBottomRightCorner != null && _contentBottomRightCorner.Visible) { - _contentBottomRightCorner.Visible = false; - } else if (_otherScrollBarView != null && _otherScrollBarView._contentBottomRightCorner != null && _otherScrollBarView._contentBottomRightCorner.Visible) { - _otherScrollBarView._contentBottomRightCorner.Visible = false; - } - return; - } + void Host_EnabledChanged (object sender, EventArgs e) + { + Enabled = Host.Enabled; + if (_otherScrollBarView != null) { + _otherScrollBarView.Enabled = Enabled; + } + _contentBottomRightCorner.Enabled = Enabled; + } - var pending = CheckBothScrollBars (this); - if (_otherScrollBarView != null) { - CheckBothScrollBars (_otherScrollBarView, pending); - } + //private void Host_CanFocusChanged () + //{ + // CanFocus = Host.CanFocus; + // if (otherScrollBarView != null) { + // otherScrollBarView.CanFocus = CanFocus; + // } + //} + + void ContentBottomRightCorner_MouseClick (object sender, MouseEventEventArgs me) + { + if (me.MouseEvent.Flags == MouseFlags.WheeledDown || + me.MouseEvent.Flags == MouseFlags.WheeledUp || + me.MouseEvent.Flags == MouseFlags.WheeledRight || + me.MouseEvent.Flags == MouseFlags.WheeledLeft) { + + MouseEvent (me.MouseEvent); + } else if (me.MouseEvent.Flags == MouseFlags.Button1Clicked) { + Host.SetFocus (); + } + me.Handled = true; + } + + void SetInitialProperties (int size, int position, bool isVertical) + { + Id = "ScrollBarView"; + _vertical = isVertical; + _position = position; + _size = size; + WantContinuousButtonPressed = true; + ClearOnVisibleFalse = false; + + Added += (s, e) => CreateBottomRightCorner (); + + Initialized += (s, e) => { SetWidthHeight (); - SetRelativeLayout (SuperView?.Frame ?? Host.Frame); - if (_otherScrollBarView != null) { - OtherScrollBarView.SetRelativeLayout (SuperView?.Frame ?? Host.Frame); + SetRelativeLayout (SuperView?.Frame ?? Host?.Frame ?? Frame); + // BUGBUG: We're not supposed to use Id internally! + if (Id == "OtherScrollBarView" || OtherScrollBarView == null) { + // Only do this once if both scrollbars are enabled + ShowHideScrollBars (); } + SetPosition (position); + }; + } - if (_showBothScrollIndicator) { - if (_contentBottomRightCorner != null) { - _contentBottomRightCorner.Visible = true; - } else if (_otherScrollBarView != null && _otherScrollBarView._contentBottomRightCorner != null) { - _otherScrollBarView._contentBottomRightCorner.Visible = true; - } - } else if (!_showScrollIndicator) { - if (_contentBottomRightCorner != null) { - _contentBottomRightCorner.Visible = false; - } else if (_otherScrollBarView != null && _otherScrollBarView._contentBottomRightCorner != null) { - _otherScrollBarView._contentBottomRightCorner.Visible = false; - } - if (Application.MouseGrabView != null && Application.MouseGrabView == this) { - Application.UngrabMouse (); - } - } else if (_contentBottomRightCorner != null) { + /// + /// This event is raised when the position on the scrollbar has changed. + /// + public event EventHandler ChangedPosition; + + // Helper to assist Initialized event handler + void SetPosition (int newPosition) + { + if (CanScroll (newPosition - _position, out var max, _vertical)) { + if (max == newPosition - _position) { + _position = newPosition; + } else { + _position = Math.Max (_position + max, 0); + } + } else if (max < 0) { + _position = Math.Max (_position + max, 0); + } else { + _position = Math.Max (newPosition, 0); + } + OnChangedPosition (); + SetNeedsDisplay (); + } + + /// + /// Virtual method to invoke the action event. + /// + public virtual void OnChangedPosition () => ChangedPosition?.Invoke (this, EventArgs.Empty); + + /// + /// Only used for a hosted view that will update and redraw the scrollbars. + /// + public virtual void Refresh () => ShowHideScrollBars (); + + void ShowHideScrollBars (bool redraw = true) + { + if (!_hosted || _hosted && !_autoHideScrollBars) { + if (_contentBottomRightCorner != null && _contentBottomRightCorner.Visible) { _contentBottomRightCorner.Visible = false; - } else if (_otherScrollBarView != null && _otherScrollBarView._contentBottomRightCorner != null) { + } else if (_otherScrollBarView != null && _otherScrollBarView._contentBottomRightCorner != null && _otherScrollBarView._contentBottomRightCorner.Visible) { _otherScrollBarView._contentBottomRightCorner.Visible = false; } - if (Host?.Visible == true && _showScrollIndicator && !Visible) { - Visible = true; - } - if (Host?.Visible == true && _otherScrollBarView?._showScrollIndicator == true && !_otherScrollBarView.Visible) { - _otherScrollBarView.Visible = true; - } + return; + } - if (!redraw) { - return; - } + var pending = CheckBothScrollBars (this); + if (_otherScrollBarView != null) { + CheckBothScrollBars (_otherScrollBarView, pending); + } + + SetWidthHeight (); + SetRelativeLayout (SuperView?.Frame ?? Host.Frame); + if (_otherScrollBarView != null) { + OtherScrollBarView.SetRelativeLayout (SuperView?.Frame ?? Host.Frame); + } - if (_showScrollIndicator) { - Draw (); + if (_showBothScrollIndicator) { + if (_contentBottomRightCorner != null) { + _contentBottomRightCorner.Visible = true; + } else if (_otherScrollBarView != null && _otherScrollBarView._contentBottomRightCorner != null) { + _otherScrollBarView._contentBottomRightCorner.Visible = true; } - if (_otherScrollBarView != null && _otherScrollBarView._showScrollIndicator) { - _otherScrollBarView.Draw (); + } else if (!_showScrollIndicator) { + if (_contentBottomRightCorner != null) { + _contentBottomRightCorner.Visible = false; + } else if (_otherScrollBarView != null && _otherScrollBarView._contentBottomRightCorner != null) { + _otherScrollBarView._contentBottomRightCorner.Visible = false; } - if (_contentBottomRightCorner != null && _contentBottomRightCorner.Visible) { - _contentBottomRightCorner.Draw (); - } else if (_otherScrollBarView != null && _otherScrollBarView._contentBottomRightCorner != null && _otherScrollBarView._contentBottomRightCorner.Visible) { - _otherScrollBarView._contentBottomRightCorner.Draw (); + if (Application.MouseGrabView != null && Application.MouseGrabView == this) { + Application.UngrabMouse (); } + } else if (_contentBottomRightCorner != null) { + _contentBottomRightCorner.Visible = false; + } else if (_otherScrollBarView != null && _otherScrollBarView._contentBottomRightCorner != null) { + _otherScrollBarView._contentBottomRightCorner.Visible = false; + } + if (Host?.Visible == true && _showScrollIndicator && !Visible) { + Visible = true; + } + if (Host?.Visible == true && _otherScrollBarView?._showScrollIndicator == true && !_otherScrollBarView.Visible) { + _otherScrollBarView.Visible = true; } - bool CheckBothScrollBars (ScrollBarView scrollBarView, bool pending = false) - { - int barsize = scrollBarView._vertical ? scrollBarView.Bounds.Height : scrollBarView.Bounds.Width; + if (!redraw) { + return; + } - if (barsize == 0 || barsize >= scrollBarView._size) { - if (scrollBarView._showScrollIndicator) { - scrollBarView.ShowScrollIndicator = false; - } - if (scrollBarView.Visible) { - scrollBarView.Visible = false; - } - } else if (barsize > 0 && barsize == scrollBarView._size && scrollBarView.OtherScrollBarView != null && pending) { - if (scrollBarView._showScrollIndicator) { - scrollBarView.ShowScrollIndicator = false; - } - if (scrollBarView.Visible) { - scrollBarView.Visible = false; - } - if (scrollBarView.OtherScrollBarView != null && scrollBarView._showBothScrollIndicator) { - scrollBarView.OtherScrollBarView.ShowScrollIndicator = false; - } - if (scrollBarView.OtherScrollBarView.Visible) { - scrollBarView.OtherScrollBarView.Visible = false; - } - } else if (barsize > 0 && barsize == _size && scrollBarView.OtherScrollBarView != null && !pending) { - pending = true; - } else { - if (scrollBarView.OtherScrollBarView != null && pending) { - if (!scrollBarView._showBothScrollIndicator) { - scrollBarView.OtherScrollBarView.ShowScrollIndicator = true; - } - if (!scrollBarView.OtherScrollBarView.Visible) { - scrollBarView.OtherScrollBarView.Visible = true; - } - } - if (!scrollBarView._showScrollIndicator) { - scrollBarView.ShowScrollIndicator = true; + if (_showScrollIndicator) { + Draw (); + } + if (_otherScrollBarView != null && _otherScrollBarView._showScrollIndicator) { + _otherScrollBarView.Draw (); + } + if (_contentBottomRightCorner != null && _contentBottomRightCorner.Visible) { + _contentBottomRightCorner.Draw (); + } else if (_otherScrollBarView != null && _otherScrollBarView._contentBottomRightCorner != null && _otherScrollBarView._contentBottomRightCorner.Visible) { + _otherScrollBarView._contentBottomRightCorner.Draw (); + } + } + + bool CheckBothScrollBars (ScrollBarView scrollBarView, bool pending = false) + { + var barsize = scrollBarView._vertical ? scrollBarView.Bounds.Height : scrollBarView.Bounds.Width; + + if (barsize == 0 || barsize >= scrollBarView._size) { + if (scrollBarView._showScrollIndicator) { + scrollBarView.ShowScrollIndicator = false; + } + if (scrollBarView.Visible) { + scrollBarView.Visible = false; + } + } else if (barsize > 0 && barsize == scrollBarView._size && scrollBarView.OtherScrollBarView != null && pending) { + if (scrollBarView._showScrollIndicator) { + scrollBarView.ShowScrollIndicator = false; + } + if (scrollBarView.Visible) { + scrollBarView.Visible = false; + } + if (scrollBarView.OtherScrollBarView != null && scrollBarView._showBothScrollIndicator) { + scrollBarView.OtherScrollBarView.ShowScrollIndicator = false; + } + if (scrollBarView.OtherScrollBarView.Visible) { + scrollBarView.OtherScrollBarView.Visible = false; + } + } else if (barsize > 0 && barsize == _size && scrollBarView.OtherScrollBarView != null && !pending) { + pending = true; + } else { + if (scrollBarView.OtherScrollBarView != null && pending) { + if (!scrollBarView._showBothScrollIndicator) { + scrollBarView.OtherScrollBarView.ShowScrollIndicator = true; } - if (!scrollBarView.Visible) { - scrollBarView.Visible = true; + if (!scrollBarView.OtherScrollBarView.Visible) { + scrollBarView.OtherScrollBarView.Visible = true; } } - - return pending; + if (!scrollBarView._showScrollIndicator) { + scrollBarView.ShowScrollIndicator = true; + } + if (!scrollBarView.Visible) { + scrollBarView.Visible = true; + } } - // BUGBUG: v2 - rationalize this with View.SetMinWidthHeight - void SetWidthHeight () - { - // BUGBUG: v2 - If Host is also the ScrollBarView's superview, this is all bogus because it's not - // supported that a view can reference it's superview's Dims. This code also assumes the host does - // not have a margin/borderframe/padding. - if (!IsInitialized) { - return; - } + return pending; + } - if (_showBothScrollIndicator) { - Width = _vertical ? 1 : Host != SuperView ? Dim.Width (Host) - 1 : Dim.Fill () - 1; - Height = _vertical ? Host != SuperView ? Dim.Height (Host) - 1 : Dim.Fill () - 1 : 1; + // BUGBUG: v2 - rationalize this with View.SetMinWidthHeight + void SetWidthHeight () + { + // BUGBUG: v2 - If Host is also the ScrollBarView's superview, this is all bogus because it's not + // supported that a view can reference it's superview's Dims. This code also assumes the host does + // not have a margin/borderframe/padding. + if (!IsInitialized) { + return; + } + + if (_showBothScrollIndicator) { + Width = _vertical ? 1 : Host != SuperView ? Dim.Width (Host) - 1 : Dim.Fill () - 1; + Height = _vertical ? Host != SuperView ? Dim.Height (Host) - 1 : Dim.Fill () - 1 : 1; + + _otherScrollBarView.Width = _otherScrollBarView._vertical ? 1 : Host != SuperView ? Dim.Width (Host) - 1 : Dim.Fill () - 1; + _otherScrollBarView.Height = _otherScrollBarView._vertical ? Host != SuperView ? Dim.Height (Host) - 1 : Dim.Fill () - 1 : 1; + } else if (_showScrollIndicator) { + Width = _vertical ? 1 : Host != SuperView ? Dim.Width (Host) : Dim.Fill (); + Height = _vertical ? Host != SuperView ? Dim.Height (Host) : Dim.Fill () : 1; + } else if (_otherScrollBarView?._showScrollIndicator == true) { + _otherScrollBarView.Width = _otherScrollBarView._vertical ? 1 : Host != SuperView ? Dim.Width (Host) : Dim.Fill () - 0; + _otherScrollBarView.Height = _otherScrollBarView._vertical ? Host != SuperView ? Dim.Height (Host) : Dim.Fill () - 0 : 1; + } + } - _otherScrollBarView.Width = _otherScrollBarView._vertical ? 1 : Host != SuperView ? Dim.Width (Host) - 1 : Dim.Fill () - 1; - _otherScrollBarView.Height = _otherScrollBarView._vertical ? Host != SuperView ? Dim.Height (Host) - 1 : Dim.Fill () - 1 : 1; - } else if (_showScrollIndicator) { - Width = _vertical ? 1 : Host != SuperView ? Dim.Width (Host) : Dim.Fill (); - Height = _vertical ? Host != SuperView ? Dim.Height (Host) : Dim.Fill () : 1; - } else if (_otherScrollBarView?._showScrollIndicator == true) { - _otherScrollBarView.Width = _otherScrollBarView._vertical ? 1 : Host != SuperView ? Dim.Width (Host) : Dim.Fill () - 0; - _otherScrollBarView.Height = _otherScrollBarView._vertical ? Host != SuperView ? Dim.Height (Host) : Dim.Fill () - 0 : 1; + /// + public override void OnDrawContent (Rect contentArea) + { + if (ColorScheme == null || (!_showScrollIndicator || Size == 0) && AutoHideScrollBars && Visible) { + if ((!_showScrollIndicator || Size == 0) && AutoHideScrollBars && Visible) { + ShowHideScrollBars (false); } + return; } - int _posTopTee; - int _posLeftTee; - int _posBottomTee; - int _posRightTee; + if (Size == 0 || _vertical && Bounds.Height == 0 || !_vertical && Bounds.Width == 0) { + return; + } - /// - public override void OnDrawContent (Rect contentArea) - { - if (ColorScheme == null || ((!_showScrollIndicator || Size == 0) && AutoHideScrollBars && Visible)) { - if ((!_showScrollIndicator || Size == 0) && AutoHideScrollBars && Visible) { - ShowHideScrollBars (false); - } - return; - } + Driver.SetAttribute (Host.HasFocus ? ColorScheme.Focus : GetNormalColor ()); - if (Size == 0 || (_vertical && Bounds.Height == 0) || (!_vertical && Bounds.Width == 0)) { + if (_vertical) { + if (Bounds.Right < Bounds.Width - 1) { return; } - Driver.SetAttribute (Host.HasFocus ? ColorScheme.Focus : GetNormalColor ()); + var col = Bounds.Width - 1; + var bh = Bounds.Height; + Rune special; - if (_vertical) { - if (Bounds.Right < Bounds.Width - 1) { - return; - } - - var col = Bounds.Width - 1; - var bh = Bounds.Height; - Rune special; + if (bh < 4) { + var by1 = _position * bh / Size; + var by2 = (_position + bh) * bh / Size; - if (bh < 4) { - var by1 = _position * bh / Size; - var by2 = (_position + bh) * bh / Size; - - Move (col, 0); - if (Bounds.Height == 1) { - Driver.AddRune (CM.Glyphs.Diamond); - } else { - Driver.AddRune (CM.Glyphs.UpArrow); - } - if (Bounds.Height == 3) { - Move (col, 1); - Driver.AddRune (CM.Glyphs.Diamond); - } - if (Bounds.Height > 1) { - Move (col, Bounds.Height - 1); - Driver.AddRune (CM.Glyphs.DownArrow); - } + Move (col, 0); + if (Bounds.Height == 1) { + Driver.AddRune (Glyphs.Diamond); } else { - bh -= 2; - var by1 = KeepContentAlwaysInViewport ? _position * bh / Size : _position * bh / (Size + bh); - var by2 = KeepContentAlwaysInViewport ? Math.Min (((_position + bh) * bh / Size) + 1, bh - 1) : (_position + bh) * bh / (Size + bh); - if (KeepContentAlwaysInViewport && by1 == by2) { - by1 = Math.Max (by1 - 1, 0); - } - - Move (col, 0); - Driver.AddRune (CM.Glyphs.UpArrow); - - bool hasTopTee = false; - bool hasDiamond = false; - bool hasBottomTee = false; - for (int y = 0; y < bh; y++) { - Move (col, y + 1); - if ((y < by1 || y > by2) && ((_position > 0 && !hasTopTee) || (hasTopTee && hasBottomTee))) { - special = CM.Glyphs.Stipple; + Driver.AddRune (Glyphs.UpArrow); + } + if (Bounds.Height == 3) { + Move (col, 1); + Driver.AddRune (Glyphs.Diamond); + } + if (Bounds.Height > 1) { + Move (col, Bounds.Height - 1); + Driver.AddRune (Glyphs.DownArrow); + } + } else { + bh -= 2; + var by1 = KeepContentAlwaysInViewport ? _position * bh / Size : _position * bh / (Size + bh); + var by2 = KeepContentAlwaysInViewport ? Math.Min ((_position + bh) * bh / Size + 1, bh - 1) : (_position + bh) * bh / (Size + bh); + if (KeepContentAlwaysInViewport && by1 == by2) { + by1 = Math.Max (by1 - 1, 0); + } + + Move (col, 0); + Driver.AddRune (Glyphs.UpArrow); + + var hasTopTee = false; + var hasDiamond = false; + var hasBottomTee = false; + for (var y = 0; y < bh; y++) { + Move (col, y + 1); + if ((y < by1 || y > by2) && (_position > 0 && !hasTopTee || hasTopTee && hasBottomTee)) { + special = Glyphs.Stipple; + } else { + if (y != by2 && y > 1 && by2 - by1 == 0 && by1 < bh - 1 && hasTopTee && !hasDiamond) { + hasDiamond = true; + special = Glyphs.Diamond; } else { - if (y != by2 && y > 1 && by2 - by1 == 0 && by1 < bh - 1 && hasTopTee && !hasDiamond) { - hasDiamond = true; - special = CM.Glyphs.Diamond; + if (y == by1 && !hasTopTee) { + hasTopTee = true; + _posTopTee = y; + special = Glyphs.TopTee; + } else if ((_position == 0 && y == bh - 1 || y >= by2 || by2 == 0) && !hasBottomTee) { + hasBottomTee = true; + _posBottomTee = y; + special = Glyphs.BottomTee; } else { - if (y == by1 && !hasTopTee) { - hasTopTee = true; - _posTopTee = y; - special = CM.Glyphs.TopTee; - } else if ((_position == 0 && y == bh - 1 || y >= by2 || by2 == 0) && !hasBottomTee) { - hasBottomTee = true; - _posBottomTee = y; - special = CM.Glyphs.BottomTee; - } else { - special = CM.Glyphs.VLine; - } + special = Glyphs.VLine; } } - Driver.AddRune (special); } - if (!hasTopTee) { - Move (col, Bounds.Height - 2); - Driver.AddRune (CM.Glyphs.TopTee); - } - Move (col, Bounds.Height - 1); - Driver.AddRune (CM.Glyphs.DownArrow); + Driver.AddRune (special); } - } else { - if (Bounds.Bottom < Bounds.Height - 1) { - return; + if (!hasTopTee) { + Move (col, Bounds.Height - 2); + Driver.AddRune (Glyphs.TopTee); } + Move (col, Bounds.Height - 1); + Driver.AddRune (Glyphs.DownArrow); + } + } else { + if (Bounds.Bottom < Bounds.Height - 1) { + return; + } - var row = Bounds.Height - 1; - var bw = Bounds.Width; - Rune special; - - if (bw < 4) { - var bx1 = _position * bw / Size; - var bx2 = (_position + bw) * bw / Size; - - Move (0, row); - Driver.AddRune (CM.Glyphs.LeftArrow); - Driver.AddRune (CM.Glyphs.RightArrow); - } else { - bw -= 2; - var bx1 = KeepContentAlwaysInViewport ? _position * bw / Size : _position * bw / (Size + bw); - var bx2 = KeepContentAlwaysInViewport ? Math.Min (((_position + bw) * bw / Size) + 1, bw - 1) : (_position + bw) * bw / (Size + bw); - if (KeepContentAlwaysInViewport && bx1 == bx2) { - bx1 = Math.Max (bx1 - 1, 0); - } + var row = Bounds.Height - 1; + var bw = Bounds.Width; + Rune special; - Move (0, row); - Driver.AddRune (CM.Glyphs.LeftArrow); + if (bw < 4) { + var bx1 = _position * bw / Size; + var bx2 = (_position + bw) * bw / Size; - bool hasLeftTee = false; - bool hasDiamond = false; - bool hasRightTee = false; - for (int x = 0; x < bw; x++) { - if ((x < bx1 || x >= bx2 + 1) && ((_position > 0 && !hasLeftTee) || (hasLeftTee && hasRightTee))) { - special = CM.Glyphs.Stipple; + Move (0, row); + Driver.AddRune (Glyphs.LeftArrow); + Driver.AddRune (Glyphs.RightArrow); + } else { + bw -= 2; + var bx1 = KeepContentAlwaysInViewport ? _position * bw / Size : _position * bw / (Size + bw); + var bx2 = KeepContentAlwaysInViewport ? Math.Min ((_position + bw) * bw / Size + 1, bw - 1) : (_position + bw) * bw / (Size + bw); + if (KeepContentAlwaysInViewport && bx1 == bx2) { + bx1 = Math.Max (bx1 - 1, 0); + } + + Move (0, row); + Driver.AddRune (Glyphs.LeftArrow); + + var hasLeftTee = false; + var hasDiamond = false; + var hasRightTee = false; + for (var x = 0; x < bw; x++) { + if ((x < bx1 || x >= bx2 + 1) && (_position > 0 && !hasLeftTee || hasLeftTee && hasRightTee)) { + special = Glyphs.Stipple; + } else { + if (x != bx2 && x > 1 && bx2 - bx1 == 0 && bx1 < bw - 1 && hasLeftTee && !hasDiamond) { + hasDiamond = true; + special = Glyphs.Diamond; } else { - if (x != bx2 && x > 1 && bx2 - bx1 == 0 && bx1 < bw - 1 && hasLeftTee && !hasDiamond) { - hasDiamond = true; - special = CM.Glyphs.Diamond; + if (x == bx1 && !hasLeftTee) { + hasLeftTee = true; + _posLeftTee = x; + special = Glyphs.LeftTee; + } else if ((_position == 0 && x == bw - 1 || x >= bx2 || bx2 == 0) && !hasRightTee) { + hasRightTee = true; + _posRightTee = x; + special = Glyphs.RightTee; } else { - if (x == bx1 && !hasLeftTee) { - hasLeftTee = true; - _posLeftTee = x; - special = CM.Glyphs.LeftTee; - } else if ((_position == 0 && x == bw - 1 || x >= bx2 || bx2 == 0) && !hasRightTee) { - hasRightTee = true; - _posRightTee = x; - special = CM.Glyphs.RightTee; - } else { - special = CM.Glyphs.HLine; - } + special = Glyphs.HLine; } } - Driver.AddRune (special); } - if (!hasLeftTee) { - Move (Bounds.Width - 2, row); - Driver.AddRune (CM.Glyphs.LeftTee); - } - - Driver.AddRune (CM.Glyphs.RightArrow); + Driver.AddRune (special); + } + if (!hasLeftTee) { + Move (Bounds.Width - 2, row); + Driver.AddRune (Glyphs.LeftTee); } - } - } - - int _lastLocation = -1; - int _posBarOffset; - - /// - public override bool MouseEvent (MouseEvent mouseEvent) - { - if (mouseEvent.Flags != MouseFlags.Button1Pressed && mouseEvent.Flags != MouseFlags.Button1DoubleClicked && - !mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) && - mouseEvent.Flags != MouseFlags.Button1Released && mouseEvent.Flags != MouseFlags.WheeledDown && - mouseEvent.Flags != MouseFlags.WheeledUp && mouseEvent.Flags != MouseFlags.WheeledRight && - mouseEvent.Flags != MouseFlags.WheeledLeft && mouseEvent.Flags != MouseFlags.Button1TripleClicked) { - return false; + Driver.AddRune (Glyphs.RightArrow); } + } + } - if (!Host.CanFocus) { - return true; - } - if (Host?.HasFocus == false) { - Host.SetFocus (); - } + /// + public override bool MouseEvent (MouseEvent mouseEvent) + { + if (mouseEvent.Flags != MouseFlags.Button1Pressed && + mouseEvent.Flags != MouseFlags.Button1DoubleClicked && + !mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) && + mouseEvent.Flags != MouseFlags.Button1Released && + mouseEvent.Flags != MouseFlags.WheeledDown && + mouseEvent.Flags != MouseFlags.WheeledUp && + mouseEvent.Flags != MouseFlags.WheeledRight && + mouseEvent.Flags != MouseFlags.WheeledLeft && + mouseEvent.Flags != MouseFlags.Button1TripleClicked) { - int location = _vertical ? mouseEvent.Y : mouseEvent.X; - int barsize = _vertical ? Bounds.Height : Bounds.Width; - int posTopLeftTee = _vertical ? _posTopTee + 1 : _posLeftTee + 1; - int posBottomRightTee = _vertical ? _posBottomTee + 1 : _posRightTee + 1; - barsize -= 2; - var pos = Position; + return false; + } - if (mouseEvent.Flags != MouseFlags.Button1Released - && (Application.MouseGrabView == null || Application.MouseGrabView != this)) { - Application.GrabMouse (this); - } else if (mouseEvent.Flags == MouseFlags.Button1Released && Application.MouseGrabView != null && Application.MouseGrabView == this) { - _lastLocation = -1; - Application.UngrabMouse (); - return true; - } - if (_showScrollIndicator && (mouseEvent.Flags == MouseFlags.WheeledDown || mouseEvent.Flags == MouseFlags.WheeledUp || - mouseEvent.Flags == MouseFlags.WheeledRight || mouseEvent.Flags == MouseFlags.WheeledLeft)) { + if (!Host.CanFocus) { + return true; + } + if (Host?.HasFocus == false) { + Host.SetFocus (); + } - return Host.MouseEvent (mouseEvent); - } + var location = _vertical ? mouseEvent.Y : mouseEvent.X; + var barsize = _vertical ? Bounds.Height : Bounds.Width; + var posTopLeftTee = _vertical ? _posTopTee + 1 : _posLeftTee + 1; + var posBottomRightTee = _vertical ? _posBottomTee + 1 : _posRightTee + 1; + barsize -= 2; + var pos = Position; + + if (mouseEvent.Flags != MouseFlags.Button1Released && (Application.MouseGrabView == null || Application.MouseGrabView != this)) { + Application.GrabMouse (this); + } else if (mouseEvent.Flags == MouseFlags.Button1Released && Application.MouseGrabView != null && Application.MouseGrabView == this) { + _lastLocation = -1; + Application.UngrabMouse (); + return true; + } + if (_showScrollIndicator && + (mouseEvent.Flags == MouseFlags.WheeledDown || + mouseEvent.Flags == MouseFlags.WheeledUp || + mouseEvent.Flags == MouseFlags.WheeledRight || + mouseEvent.Flags == MouseFlags.WheeledLeft)) { - if (mouseEvent.Flags == MouseFlags.Button1Pressed && location == 0) { - if (pos > 0) { - Position = pos - 1; - } - } else if (mouseEvent.Flags == MouseFlags.Button1Pressed && location == barsize + 1) { - if (CanScroll (1, out _, _vertical)) { - Position = pos + 1; - } - } else if (location > 0 && location < barsize + 1) { - //var b1 = pos * (Size > 0 ? barsize / Size : 0); - //var b2 = Size > 0 - // ? (KeepContentAlwaysInViewport ? Math.Min (((pos + barsize) * barsize / Size) + 1, barsize - 1) : (pos + barsize) * barsize / Size) - // : 0; - //if (KeepContentAlwaysInViewport && b1 == b2) { - // b1 = Math.Max (b1 - 1, 0); - //} - - if (_lastLocation > -1 || (location >= posTopLeftTee && location <= posBottomRightTee - && mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition))) { - if (_lastLocation == -1) { - _lastLocation = location; - _posBarOffset = _keepContentAlwaysInViewport ? Math.Max (location - posTopLeftTee, 1) : 0; - return true; - } + return Host.MouseEvent (mouseEvent); + } - if (location > _lastLocation) { - if (location - _posBarOffset < barsize) { - var np = ((location - _posBarOffset) * Size / barsize) + (Size / barsize); - if (CanScroll (np - pos, out int nv, _vertical)) { - Position = pos + nv; - } - } else if (CanScroll (Size - pos, out int nv, _vertical)) { - Position = Math.Min (pos + nv, Size); - } - } else if (location < _lastLocation) { - if (location - _posBarOffset > 0) { - var np = ((location - _posBarOffset) * Size / barsize) - (Size / barsize); - if (CanScroll (np - pos, out int nv, _vertical)) { - Position = pos + nv; - } - } else { - Position = 0; + if (mouseEvent.Flags == MouseFlags.Button1Pressed && location == 0) { + if (pos > 0) { + Position = pos - 1; + } + } else if (mouseEvent.Flags == MouseFlags.Button1Pressed && location == barsize + 1) { + if (CanScroll (1, out _, _vertical)) { + Position = pos + 1; + } + } else if (location > 0 && location < barsize + 1) { + //var b1 = pos * (Size > 0 ? barsize / Size : 0); + //var b2 = Size > 0 + // ? (KeepContentAlwaysInViewport ? Math.Min (((pos + barsize) * barsize / Size) + 1, barsize - 1) : (pos + barsize) * barsize / Size) + // : 0; + //if (KeepContentAlwaysInViewport && b1 == b2) { + // b1 = Math.Max (b1 - 1, 0); + //} + + if (_lastLocation > -1 || location >= posTopLeftTee && location <= posBottomRightTee && mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition)) { + if (_lastLocation == -1) { + _lastLocation = location; + _posBarOffset = _keepContentAlwaysInViewport ? Math.Max (location - posTopLeftTee, 1) : 0; + return true; + } + + if (location > _lastLocation) { + if (location - _posBarOffset < barsize) { + var np = (location - _posBarOffset) * Size / barsize + Size / barsize; + if (CanScroll (np - pos, out var nv, _vertical)) { + Position = pos + nv; } - } else if (location - _posBarOffset >= barsize && posBottomRightTee - posTopLeftTee >= 3 && CanScroll (Size - pos, out int nv, _vertical)) { - Position = Math.Min (pos + nv, Size); - } else if (location - _posBarOffset >= barsize - 1 && posBottomRightTee - posTopLeftTee <= 3 && CanScroll (Size - pos, out nv, _vertical)) { + } else if (CanScroll (Size - pos, out var nv, _vertical)) { Position = Math.Min (pos + nv, Size); - } else if (location - _posBarOffset <= 0 && posBottomRightTee - posTopLeftTee <= 3) { - Position = 0; - } - } else if (location > posBottomRightTee) { - if (CanScroll (barsize, out int nv, _vertical)) { - Position = pos + nv; } - } else if (location < posTopLeftTee) { - if (CanScroll (-barsize, out int nv, _vertical)) { - Position = pos + nv; + } else if (location < _lastLocation) { + if (location - _posBarOffset > 0) { + var np = (location - _posBarOffset) * Size / barsize - Size / barsize; + if (CanScroll (np - pos, out var nv, _vertical)) { + Position = pos + nv; + } + } else { + Position = 0; } - } else if (location == 1 && posTopLeftTee <= 3) { + } else if (location - _posBarOffset >= barsize && posBottomRightTee - posTopLeftTee >= 3 && CanScroll (Size - pos, out var nv, _vertical)) { + Position = Math.Min (pos + nv, Size); + } else if (location - _posBarOffset >= barsize - 1 && posBottomRightTee - posTopLeftTee <= 3 && CanScroll (Size - pos, out nv, _vertical)) { + Position = Math.Min (pos + nv, Size); + } else if (location - _posBarOffset <= 0 && posBottomRightTee - posTopLeftTee <= 3) { Position = 0; - } else if (location == barsize) { - if (CanScroll (Size - pos, out int nv, _vertical)) { - Position = Math.Min (pos + nv, Size); - } + } + } else if (location > posBottomRightTee) { + if (CanScroll (barsize, out var nv, _vertical)) { + Position = pos + nv; + } + } else if (location < posTopLeftTee) { + if (CanScroll (-barsize, out var nv, _vertical)) { + Position = pos + nv; + } + } else if (location == 1 && posTopLeftTee <= 3) { + Position = 0; + } else if (location == barsize) { + if (CanScroll (Size - pos, out var nv, _vertical)) { + Position = Math.Min (pos + nv, Size); } } - - return true; } - internal bool CanScroll (int n, out int max, bool isVertical = false) - { - if (Host?.Bounds.IsEmpty != false) { - max = 0; - return false; - } - int s = GetBarsize (isVertical); - var newSize = Math.Max (Math.Min (_size - s, _position + n), 0); - max = _size > s + newSize ? (newSize == 0 ? -_position : n) : _size - (s + _position) - 1; - if (_size >= s + newSize && max != 0) { - return true; - } + return true; + } + + internal bool CanScroll (int n, out int max, bool isVertical = false) + { + if (Host?.Bounds.IsEmpty != false) { + max = 0; return false; } + var s = GetBarsize (isVertical); + var newSize = Math.Max (Math.Min (_size - s, _position + n), 0); + max = _size > s + newSize ? newSize == 0 ? -_position : n : _size - (s + _position) - 1; + if (_size >= s + newSize && max != 0) { + return true; + } + return false; + } - int GetBarsize (bool isVertical) - { - if (Host?.Bounds.IsEmpty != false) { - return 0; - } - return isVertical ? - (KeepContentAlwaysInViewport ? Host.Bounds.Height + (_showBothScrollIndicator ? -2 : -1) : 0) : - (KeepContentAlwaysInViewport ? Host.Bounds.Width + (_showBothScrollIndicator ? -2 : -1) : 0); + int GetBarsize (bool isVertical) + { + if (Host?.Bounds.IsEmpty != false) { + return 0; } + return isVertical ? + KeepContentAlwaysInViewport ? Host.Bounds.Height + (_showBothScrollIndicator ? -2 : -1) : 0 : + KeepContentAlwaysInViewport ? Host.Bounds.Width + (_showBothScrollIndicator ? -2 : -1) : 0; + } - /// - public override bool OnEnter (View view) - { - Application.Driver.SetCursorVisibility (CursorVisibility.Invisible); + /// + public override bool OnEnter (View view) + { + Application.Driver.SetCursorVisibility (CursorVisibility.Invisible); - return base.OnEnter (view); - } + return base.OnEnter (view); } -} +} \ No newline at end of file diff --git a/Terminal.Gui/Views/Slider.cs b/Terminal.Gui/Views/Slider.cs index 21fb8a2695..c59d282aa4 100644 --- a/Terminal.Gui/Views/Slider.cs +++ b/Terminal.Gui/Views/Slider.cs @@ -804,20 +804,17 @@ public void SetBoundsBestFit () if (!IsInitialized || AutoSize == false) { return; } - // Hack??? Otherwise we can't go back to Dim.Absolute. - LayoutStyle = LayoutStyle.Absolute; Width = 0; Height = 0; if (_config._sliderOrientation == Orientation.Horizontal) { Bounds = new Rect (Bounds.Location, new Size (int.Min (SuperView.Bounds.Width - GetFramesThickness ().Horizontal, CalcBestLength ()), - int.Min (SuperView.Bounds.Height - GetFramesThickness ().Vertical, CalcThickness ()))); + int.Min (SuperView.Bounds.Height - GetFramesThickness ().Vertical, CalcThickness ()))); } else { Bounds = new Rect (Bounds.Location, new Size (int.Min (SuperView.Bounds.Width - GetFramesThickness ().Horizontal, CalcThickness ()), - int.Min (SuperView.Bounds.Height - GetFramesThickness ().Vertical, CalcBestLength ()))); + int.Min (SuperView.Bounds.Height - GetFramesThickness ().Vertical, CalcBestLength ()))); } - LayoutStyle = LayoutStyle.Computed; } /// @@ -1050,7 +1047,7 @@ void DrawSlider () // Attributes var normalAttr = new Attribute (Color.White, Color.Black); - var setAtrr = new Attribute (Color.Black, Color.White); + var setAtrr = new Attribute (Color.Black, Color.White); if (IsInitialized) { normalAttr = ColorScheme?.Normal ?? Application.Current.ColorScheme.Normal; setAtrr = Style.SetChar.Attribute ?? ColorScheme.HotNormal; @@ -1190,7 +1187,7 @@ void DrawLegends () { // Attributes var normalAttr = new Attribute (Color.White, Color.Black); - var setAttr = new Attribute (Color.Black, Color.White); + var setAttr = new Attribute (Color.Black, Color.White); var spaceAttr = normalAttr; if (IsInitialized) { normalAttr = Style.LegendAttributes.NormalAttribute ?? ColorScheme?.Normal ?? ColorScheme.Disabled; @@ -1462,15 +1459,15 @@ Point ClampMovePosition (Point position) void SetCommands () { - AddCommand (Command.Right, () => MovePlus ()); - AddCommand (Command.LineDown, () => MovePlus ()); - AddCommand (Command.Left, () => MoveMinus ()); - AddCommand (Command.LineUp, () => MoveMinus ()); - AddCommand (Command.LeftHome, () => MoveStart ()); - AddCommand (Command.RightEnd, () => MoveEnd ()); + AddCommand (Command.Right, () => MovePlus ()); + AddCommand (Command.LineDown, () => MovePlus ()); + AddCommand (Command.Left, () => MoveMinus ()); + AddCommand (Command.LineUp, () => MoveMinus ()); + AddCommand (Command.LeftHome, () => MoveStart ()); + AddCommand (Command.RightEnd, () => MoveEnd ()); AddCommand (Command.RightExtend, () => ExtendPlus ()); - AddCommand (Command.LeftExtend, () => ExtendMinus ()); - AddCommand (Command.Accept, () => Set ()); + AddCommand (Command.LeftExtend, () => ExtendMinus ()); + AddCommand (Command.Accept, () => Set ()); SetKeyBindings (); } @@ -1500,8 +1497,8 @@ void SetKeyBindings () KeyBindings.Add (KeyCode.CursorUp | KeyCode.CtrlMask, Command.LeftExtend); } - KeyBindings.Add (KeyCode.Home, Command.LeftHome); - KeyBindings.Add (KeyCode.End, Command.RightEnd); + KeyBindings.Add (KeyCode.Home, Command.LeftHome); + KeyBindings.Add (KeyCode.End, Command.RightEnd); KeyBindings.Add (KeyCode.Enter, Command.Accept); KeyBindings.Add (KeyCode.Space, Command.Accept); diff --git a/Terminal.Gui/Views/TextField.cs b/Terminal.Gui/Views/TextField.cs index acaf46fdbb..2c1ef50afa 100644 --- a/Terminal.Gui/Views/TextField.cs +++ b/Terminal.Gui/Views/TextField.cs @@ -1,1378 +1,1454 @@ -// -// TextField.cs: single-line text editor with Emacs keybindings -// -// Authors: -// Miguel de Icaza (miguel@gnome.org) -// - using System; using System.Collections.Generic; using System.Globalization; using System.Linq; -using System.Threading; using System.Text; +using System.Threading; using Terminal.Gui.Resources; -namespace Terminal.Gui { - /// - /// Single-line text entry - /// - /// - /// The provides editing functionality and mouse support. - /// - public class TextField : View { - List _text; - int _first, _cursorPosition; - int _selectedStart = -1; // -1 represents there is no text selection. - string _selectedText; - HistoryText _historyText = new HistoryText (); - CultureInfo _currentCulture; - - /// - /// Gets or sets the text to render in control when no value has - /// been entered yet and the does not yet have - /// input focus. - /// - public string Caption { get; set; } - - /// - /// Gets or sets the foreground to use when - /// rendering . - /// - public Color CaptionColor { get; set; } = new Color (Color.DarkGray); - - /// - /// Tracks whether the text field should be considered "used", that is, that the user has moved in the entry, so new input should be appended at the cursor position, rather than clearing the entry - /// - public bool Used { get; set; } - - /// - /// If set to true its not allow any changes in the text. - /// - public bool ReadOnly { get; set; } = false; - - /// - /// Changing event, raised before the changes and can be canceled or changing the new text. - /// - public event EventHandler TextChanging; - - /// - /// Changed event, raised when the text has changed. - /// - /// This event is raised when the changes. - /// The passed is a containing the old value. - /// - /// - public event EventHandler TextChanged; - - /// - /// Initializes a new instance of the class using positioning. - /// - public TextField () : this (string.Empty) { } - - /// - /// Initializes a new instance of the class using positioning. - /// - /// Initial text contents. - public TextField (string text) : base (text) - { - SetInitialProperties (text, text.GetRuneCount () + 1); - } +namespace Terminal.Gui; - /// - /// Initializes a new instance of the class using positioning. - /// - /// The x coordinate. - /// The y coordinate. - /// The width. - /// Initial text contents. - public TextField (int x, int y, int w, string text) : base (new Rect (x, y, w, 1)) - { - SetInitialProperties (text, w); - } +/// +/// Single-line text entry +/// +/// +/// The provides editing functionality and mouse support. +/// +public class TextField : View { + CultureInfo _currentCulture; - void SetInitialProperties (string text, int w) - { - Height = 1; + CursorVisibility _desiredCursorVisibility = CursorVisibility.Default; + int _cursorPosition; + readonly HistoryText _historyText = new (); + bool _isButtonPressed; + bool _isButtonReleased = true; - if (text == null) - text = ""; - - this._text = text.Split ("\n") [0].EnumerateRunes ().ToList (); - _cursorPosition = text.GetRuneCount (); - _first = _cursorPosition > w + 1 ? _cursorPosition - w + 1 : 0; - CanFocus = true; - Used = true; - WantMousePositionReports = true; - _savedCursorVisibility = _desiredCursorVisibility; - - _historyText.ChangeText += HistoryText_ChangeText; - - Initialized += TextField_Initialized; - - // Things this view knows how to do - AddCommand (Command.DeleteCharRight, () => { DeleteCharRight (); return true; }); - AddCommand (Command.DeleteCharLeft, () => { DeleteCharLeft (false); return true; }); - AddCommand (Command.LeftHomeExtend, () => { MoveHomeExtend (); return true; }); - AddCommand (Command.RightEndExtend, () => { MoveEndExtend (); return true; }); - AddCommand (Command.LeftHome, () => { MoveHome (); return true; }); - AddCommand (Command.LeftExtend, () => { MoveLeftExtend (); return true; }); - AddCommand (Command.RightExtend, () => { MoveRightExtend (); return true; }); - AddCommand (Command.WordLeftExtend, () => { MoveWordLeftExtend (); return true; }); - AddCommand (Command.WordRightExtend, () => { MoveWordRightExtend (); return true; }); - AddCommand (Command.Left, () => { MoveLeft (); return true; }); - AddCommand (Command.RightEnd, () => { MoveEnd (); return true; }); - AddCommand (Command.Right, () => { MoveRight (); return true; }); - AddCommand (Command.CutToEndLine, () => { KillToEnd (); return true; }); - AddCommand (Command.CutToStartLine, () => { KillToStart (); return true; }); - AddCommand (Command.Undo, () => { Undo (); return true; }); - AddCommand (Command.Redo, () => { Redo (); return true; }); - AddCommand (Command.WordLeft, () => { MoveWordLeft (); return true; }); - AddCommand (Command.WordRight, () => { MoveWordRight (); return true; }); - AddCommand (Command.KillWordForwards, () => { KillWordForwards (); return true; }); - AddCommand (Command.KillWordBackwards, () => { KillWordBackwards (); return true; }); - AddCommand (Command.ToggleOverwrite, () => { SetOverwrite (!Used); return true; }); - AddCommand (Command.EnableOverwrite, () => { SetOverwrite (true); return true; }); - AddCommand (Command.DisableOverwrite, () => { SetOverwrite (false); return true; }); - AddCommand (Command.Copy, () => { Copy (); return true; }); - AddCommand (Command.Cut, () => { Cut (); return true; }); - AddCommand (Command.Paste, () => { Paste (); return true; }); - AddCommand (Command.SelectAll, () => { SelectAll (); return true; }); - AddCommand (Command.DeleteAll, () => { DeleteAll (); return true; }); - AddCommand (Command.ShowContextMenu, () => { ShowContextMenu (); return true; }); - - // Default keybindings for this view - // We follow this as closely as possible: https://en.wikipedia.org/wiki/Table_of_keyboard_shortcuts - KeyBindings.Add (KeyCode.Delete, Command.DeleteCharRight); - KeyBindings.Add (KeyCode.D | KeyCode.CtrlMask, Command.DeleteCharRight); - - KeyBindings.Add (KeyCode.Backspace, Command.DeleteCharLeft); - - KeyBindings.Add (KeyCode.Home | KeyCode.ShiftMask, Command.LeftHomeExtend); - KeyBindings.Add (KeyCode.Home | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.LeftHomeExtend); - KeyBindings.Add (KeyCode.A | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.LeftHomeExtend); - - KeyBindings.Add (KeyCode.End | KeyCode.ShiftMask, Command.RightEndExtend); - KeyBindings.Add (KeyCode.End | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.RightEndExtend); - KeyBindings.Add (KeyCode.E | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.RightEndExtend); - - KeyBindings.Add (KeyCode.Home, Command.LeftHome); - KeyBindings.Add (KeyCode.Home | KeyCode.CtrlMask, Command.LeftHome); - KeyBindings.Add (KeyCode.A | KeyCode.CtrlMask, Command.LeftHome); - - KeyBindings.Add (KeyCode.CursorLeft | KeyCode.ShiftMask, Command.LeftExtend); - KeyBindings.Add (KeyCode.CursorUp | KeyCode.ShiftMask, Command.LeftExtend); - - KeyBindings.Add (KeyCode.CursorRight | KeyCode.ShiftMask, Command.RightExtend); - KeyBindings.Add (KeyCode.CursorDown | KeyCode.ShiftMask, Command.RightExtend); - - KeyBindings.Add (KeyCode.CursorLeft | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.WordLeftExtend); - KeyBindings.Add (KeyCode.CursorUp | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.WordLeftExtend); - KeyBindings.Add ((KeyCode)((int)'B' + KeyCode.ShiftMask | KeyCode.AltMask), Command.WordLeftExtend); - - KeyBindings.Add (KeyCode.CursorRight | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.WordRightExtend); - KeyBindings.Add (KeyCode.CursorDown | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.WordRightExtend); - KeyBindings.Add ((KeyCode)((int)'F' + KeyCode.ShiftMask | KeyCode.AltMask), Command.WordRightExtend); - - KeyBindings.Add (KeyCode.CursorLeft, Command.Left); - KeyBindings.Add (KeyCode.B | KeyCode.CtrlMask, Command.Left); - - KeyBindings.Add (KeyCode.End, Command.RightEnd); - KeyBindings.Add (KeyCode.End | KeyCode.CtrlMask, Command.RightEnd); - KeyBindings.Add (KeyCode.E | KeyCode.CtrlMask, Command.RightEnd); - - KeyBindings.Add (KeyCode.CursorRight, Command.Right); - KeyBindings.Add (KeyCode.F | KeyCode.CtrlMask, Command.Right); - - KeyBindings.Add (KeyCode.K | KeyCode.CtrlMask, Command.CutToEndLine); - KeyBindings.Add (KeyCode.K | KeyCode.AltMask, Command.CutToStartLine); - - KeyBindings.Add (KeyCode.Z | KeyCode.CtrlMask, Command.Undo); - KeyBindings.Add (KeyCode.Backspace | KeyCode.AltMask, Command.Undo); - - KeyBindings.Add (KeyCode.Y | KeyCode.CtrlMask, Command.Redo); - - KeyBindings.Add (KeyCode.CursorLeft | KeyCode.CtrlMask, Command.WordLeft); - KeyBindings.Add (KeyCode.CursorUp | KeyCode.CtrlMask, Command.WordLeft); - KeyBindings.Add ((KeyCode)((int)'B' + KeyCode.AltMask), Command.WordLeft); - - KeyBindings.Add (KeyCode.CursorRight | KeyCode.CtrlMask, Command.WordRight); - KeyBindings.Add (KeyCode.CursorDown | KeyCode.CtrlMask, Command.WordRight); - KeyBindings.Add ((KeyCode)((int)'F' + KeyCode.AltMask), Command.WordRight); - - KeyBindings.Add (KeyCode.Delete | KeyCode.CtrlMask, Command.KillWordForwards); - KeyBindings.Add (KeyCode.Backspace | KeyCode.CtrlMask, Command.KillWordBackwards); - KeyBindings.Add (KeyCode.Insert, Command.ToggleOverwrite); - KeyBindings.Add (KeyCode.C | KeyCode.CtrlMask, Command.Copy); - KeyBindings.Add (KeyCode.X | KeyCode.CtrlMask, Command.Cut); - KeyBindings.Add (KeyCode.V | KeyCode.CtrlMask, Command.Paste); - KeyBindings.Add (KeyCode.T | KeyCode.CtrlMask, Command.SelectAll); - - KeyBindings.Add (KeyCode.R | KeyCode.CtrlMask, Command.DeleteAll); - KeyBindings.Add (KeyCode.D | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.DeleteAll); + bool _isDrawing; - _currentCulture = Thread.CurrentThread.CurrentUICulture; + int _preTextChangedCursorPos; - ContextMenu = new ContextMenu (this, BuildContextMenuBarItem ()); - ContextMenu.KeyChanged += ContextMenu_KeyChanged; + CursorVisibility _savedCursorVisibility; + int _selectedStart = -1; // -1 represents there is no text selection. + string _selectedText; - KeyBindings.Add (ContextMenu.Key.KeyCode, KeyBindingScope.HotKey, Command.ShowContextMenu); - } + int _start; + List _text; - private MenuBarItem BuildContextMenuBarItem () - { - return new MenuBarItem (new MenuItem [] { - new MenuItem (Strings.ctxSelectAll, "", () => SelectAll (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.SelectAll)), - new MenuItem (Strings.ctxDeleteAll, "", () => DeleteAll (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.DeleteAll)), - new MenuItem (Strings.ctxCopy, "", () => Copy (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Copy)), - new MenuItem (Strings.ctxCut, "", () => Cut (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Cut)), - new MenuItem (Strings.ctxPaste, "", () => Paste (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Paste)), - new MenuItem (Strings.ctxUndo, "", () => Undo (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Undo)), - new MenuItem (Strings.ctxRedo, "", () => Redo (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Redo)), - }); - } - - private void ContextMenu_KeyChanged (object sender, KeyChangedEventArgs e) - { - KeyBindings.Replace (e.OldKey.KeyCode, e.NewKey.KeyCode); - } + CursorVisibility _visibility; - private void HistoryText_ChangeText (object sender, HistoryText.HistoryTextItem obj) - { - if (obj == null) - return; + /// + /// Initializes a new instance of the class using positioning. + /// + public TextField () : this (string.Empty) { } - Text = TextModel.ToString (obj?.Lines [obj.CursorPosition.Y]); - CursorPosition = obj.CursorPosition.X; - Adjust (); - } + /// + /// Initializes a new instance of the class using positioning. + /// + /// Initial text contents. + public TextField (string text) : base (text) => SetInitialProperties (text, text.GetRuneCount () + 1); - void TextField_Initialized (object sender, EventArgs e) - { - Autocomplete.HostControl = this; - Autocomplete.PopupInsideContainer = false; - } + /// + /// Initializes a new instance of the class using positioning. + /// + /// The x coordinate. + /// The y coordinate. + /// The width. + /// Initial text contents. + public TextField (int x, int y, int w, string text) : base (new Rect (x, y, w, 1)) => SetInitialProperties (text, w); - /// - public override bool OnEnter (View view) - { - if (IsInitialized) { - Application.Driver.SetCursorVisibility (DesiredCursorVisibility); - } + /// + /// Gets or sets the text to render in control when no value has + /// been entered yet and the does not yet have + /// input focus. + /// + public string Caption { get; set; } - return base.OnEnter (view); - } + /// + /// Gets or sets the foreground to use when + /// rendering . + /// + public Color CaptionColor { get; set; } = new (Color.DarkGray); - /// - public override bool OnLeave (View view) - { - if (Application.MouseGrabView != null && Application.MouseGrabView == this) - Application.UngrabMouse (); - //if (SelectedLength != 0 && !(Application.MouseGrabView is MenuBar)) - // ClearAllSelection (); - - return base.OnLeave (view); - } - - /// - /// Provides autocomplete context menu based on suggestions at the current cursor - /// position. Configure to enable this feature. - /// - public IAutocomplete Autocomplete { get; set; } = new TextFieldAutocomplete (); - - /// - public override Rect Frame { - get => base.Frame; - set { - if (value.Height > 1) { - base.Frame = new Rect (value.X, value.Y, value.Width, 1); - Height = 1; - } else { - base.Frame = value; - } - Adjust (); - } - } + /// + /// Tracks whether the text field should be considered "used", that is, that the user has moved in the entry, so new input + /// should be appended at the cursor position, rather than clearing the entry + /// + public bool Used { get; set; } - /// - /// Sets or gets the text held by the view. - /// - public new string Text { - get { - return StringExtensions.ToString (_text); - } + /// + /// If set to true its not allow any changes in the text. + /// + public bool ReadOnly { get; set; } = false; - set { - var oldText = StringExtensions.ToString (_text); + /// + /// Provides autocomplete context menu based on suggestions at the current cursor + /// position. Configure to enable this feature. + /// + public IAutocomplete Autocomplete { get; set; } = new TextFieldAutocomplete (); - if (oldText == value) - return; + /// + /// Sets or gets the text held by the view. + /// + public new string Text { + get => StringExtensions.ToString (_text); + set { + var oldText = StringExtensions.ToString (_text); - var newText = OnTextChanging (value.Replace ("\t", "").Split ("\n") [0]); - if (newText.Cancel) { - if (_cursorPosition > _text.Count) { - _cursorPosition = _text.Count; - } - return; - } - ClearAllSelection (); - _text = newText.NewText.EnumerateRunes ().ToList (); + if (oldText == value) { + return; + } - if (!Secret && !_historyText.IsFromHistory) { - _historyText.Add (new List> () { TextModel.ToRuneCellList (oldText) }, - new Point (_cursorPosition, 0)); - _historyText.Add (new List> () { TextModel.ToRuneCells (_text) }, new Point (_cursorPosition, 0) - , HistoryText.LineStatus.Replaced); + var newText = OnTextChanging (value.Replace ("\t", "").Split ("\n") [0]); + if (newText.Cancel) { + if (_cursorPosition > _text.Count) { + _cursorPosition = _text.Count; } + return; + } + ClearAllSelection (); + _text = newText.NewText.EnumerateRunes ().ToList (); - TextChanged?.Invoke (this, new TextChangedEventArgs (oldText)); + if (!Secret && !_historyText.IsFromHistory) { + _historyText.Add (new List> { TextModel.ToRuneCellList (oldText) }, + new Point (_cursorPosition, 0)); + _historyText.Add (new List> { TextModel.ToRuneCells (_text) }, new Point (_cursorPosition, 0) + , HistoryText.LineStatus.Replaced); + } - ProcessAutocomplete (); + TextChanged?.Invoke (this, new TextChangedEventArgs (oldText)); - if (_cursorPosition > _text.Count) { - _cursorPosition = Math.Max (TextModel.DisplaySize (_text, 0).size - 1, 0); - } + ProcessAutocomplete (); - Adjust (); - SetNeedsDisplay (); + if (_cursorPosition > _text.Count) { + _cursorPosition = Math.Max (TextModel.DisplaySize (_text, 0).size - 1, 0); } + + Adjust (); + SetNeedsDisplay (); } + } - /// - /// Sets the secret property. - /// - /// This makes the text entry suitable for entering passwords. - /// - /// - public bool Secret { get; set; } - - /// - /// Sets or gets the current cursor position. - /// - public virtual int CursorPosition { - get { return _cursorPosition; } - set { - if (value < 0) { - _cursorPosition = 0; - } else if (value > _text.Count) { - _cursorPosition = _text.Count; - } else { - _cursorPosition = value; - } - PrepareSelection (_selectedStart, _cursorPosition - _selectedStart); + /// + /// Sets the secret property. + /// + /// This makes the text entry suitable for entering passwords. + /// + /// + public bool Secret { get; set; } + + /// + /// Sets or gets the current cursor position. + /// + public virtual int CursorPosition { + get => _cursorPosition; + set { + if (value < 0) { + _cursorPosition = 0; + } else if (value > _text.Count) { + _cursorPosition = _text.Count; + } else { + _cursorPosition = value; } + PrepareSelection (_selectedStart, _cursorPosition - _selectedStart); } + } - /// - /// Gets the left offset position. - /// - public int ScrollOffset => _first; - - /// - /// Indicates whatever the text was changed or not. - /// if the text was changed otherwise. - /// - public bool IsDirty => _historyText.IsDirty (Text); - - /// - /// Indicates whatever the text has history changes or not. - /// if the text has history changes otherwise. - /// - public bool HasHistoryChanges => _historyText.HasHistoryChanges; - - /// - /// Get the for this view. - /// - public ContextMenu ContextMenu { get; private set; } - - /// - /// Sets the cursor position. - /// - public override void PositionCursor () - { - if (!IsInitialized) { - return; - } - ProcessAutocomplete (); + /// + /// Gets the left offset position. + /// + public int ScrollOffset { get; private set; } - var col = 0; - for (int idx = _first < 0 ? 0 : _first; idx < _text.Count; idx++) { - if (idx == _cursorPosition) - break; - var cols = _text [idx].GetColumns (); - TextModel.SetCol (ref col, Frame.Width - 1, cols); - } - var pos = _cursorPosition - _first + Math.Min (Frame.X, 0); - var offB = OffSetBackground (); - var containerFrame = SuperView?.BoundsToScreen (SuperView.Bounds) ?? default; - var thisFrame = BoundsToScreen (Bounds); - if (pos > -1 && col >= pos && pos < Frame.Width + offB - && containerFrame.IntersectsWith (thisFrame)) { - RestoreCursorVisibility (); - Move (col, 0); + /// + /// Indicates whatever the text was changed or not. + /// if the text was changed otherwise. + /// + public bool IsDirty => _historyText.IsDirty (Text); + + /// + /// Indicates whatever the text has history changes or not. + /// if the text has history changes otherwise. + /// + public bool HasHistoryChanges => _historyText.HasHistoryChanges; + + /// + /// Get the for this view. + /// + public ContextMenu ContextMenu { get; private set; } + + /// + public override bool CanFocus { + get => base.CanFocus; + set => base.CanFocus = value; + } + + /// + /// Start position of the selected text. + /// + public int SelectedStart { + get => _selectedStart; + set { + if (value < -1) { + _selectedStart = -1; + } else if (value > _text.Count) { + _selectedStart = _text.Count; } else { - HideCursorVisibility (); - if (pos < 0) { - Move (pos, 0); - } else { - Move (pos - offB, 0); - } + _selectedStart = value; } + PrepareSelection (_selectedStart, _cursorPosition - _selectedStart); } + } - CursorVisibility _savedCursorVisibility; + /// + /// Length of the selected text. + /// + public int SelectedLength { get; private set; } - void HideCursorVisibility () - { - if (_desiredCursorVisibility != CursorVisibility.Invisible) { - DesiredCursorVisibility = CursorVisibility.Invisible; + /// + /// The selected text. + /// + public string SelectedText { + get => Secret ? null : _selectedText; + private set => _selectedText = value; + } + + /// + /// Get / Set the wished cursor when the field is focused + /// + public CursorVisibility DesiredCursorVisibility { + get => _desiredCursorVisibility; + set { + if ((_desiredCursorVisibility != value || _visibility != value) && HasFocus) { + Application.Driver.SetCursorVisibility (value); } + + _desiredCursorVisibility = _visibility = value; } + } - CursorVisibility _visibility; + /// + /// Changing event, raised before the changes and can be canceled or changing the new text. + /// + public event EventHandler TextChanging; - void RestoreCursorVisibility () - { - Application.Driver.GetCursorVisibility (out _visibility); - if (_desiredCursorVisibility != _savedCursorVisibility || _visibility != _savedCursorVisibility) { - DesiredCursorVisibility = _savedCursorVisibility; - } + /// + /// Changed event, raised when the text has changed. + /// + /// This event is raised when the changes. + /// The passed is a containing the old value. + /// + /// + public event EventHandler TextChanged; + + void SetInitialProperties (string text, int w) + { + Height = 1; + + if (text == null) { + text = ""; } - bool _isDrawing = false; + _text = text.Split ("\n") [0].EnumerateRunes ().ToList (); + _cursorPosition = text.GetRuneCount (); + ScrollOffset = _cursorPosition > w + 1 ? _cursorPosition - w + 1 : 0; + CanFocus = true; + Used = true; + WantMousePositionReports = true; + _savedCursorVisibility = _desiredCursorVisibility; - /// - public override void OnDrawContent (Rect contentArea) - { - _isDrawing = true; + _historyText.ChangeText += HistoryText_ChangeText; - var selColor = new Attribute (ColorScheme.Focus.Background, ColorScheme.Focus.Foreground); - SetSelectedStartSelectedLength (); + Initialized += TextField_Initialized; - Driver.SetAttribute (GetNormalColor ()); - Move (0, 0); - - int p = _first; - int col = 0; - int width = Frame.Width + OffSetBackground (); - var tcount = _text.Count; - var roc = GetReadOnlyColor (); - for (int idx = p; idx < tcount; idx++) { - var rune = _text [idx]; - var cols = rune.GetColumns (); - if (idx == _cursorPosition && HasFocus && !Used && _length == 0 && !ReadOnly) { - Driver.SetAttribute (selColor); - } else if (ReadOnly) { - Driver.SetAttribute (idx >= _start && _length > 0 && idx < _start + _length ? selColor : roc); - } else if (!HasFocus && Enabled) { - Driver.SetAttribute (ColorScheme.Focus); - } else if (!Enabled) { - Driver.SetAttribute (roc); - } else { - Driver.SetAttribute (idx >= _start && _length > 0 && idx < _start + _length ? selColor : ColorScheme.Focus); - } - if (col + cols <= width) { - Driver.AddRune ((Secret ? CM.Glyphs.Dot : rune)); - } - if (!TextModel.SetCol (ref col, width, cols)) { - break; - } - if (idx + 1 < tcount && col + _text [idx + 1].GetColumns () > width) { - break; - } - } + LayoutComplete += TextField_LayoutComplete; - Driver.SetAttribute (ColorScheme.Focus); - for (int i = col; i < width; i++) { - Driver.AddRune ((Rune)' '); - } + // Things this view knows how to do + AddCommand (Command.DeleteCharRight, () => { + DeleteCharRight (); + return true; + }); + AddCommand (Command.DeleteCharLeft, () => { + DeleteCharLeft (false); + return true; + }); + AddCommand (Command.LeftHomeExtend, () => { + MoveHomeExtend (); + return true; + }); + AddCommand (Command.RightEndExtend, () => { + MoveEndExtend (); + return true; + }); + AddCommand (Command.LeftHome, () => { + MoveHome (); + return true; + }); + AddCommand (Command.LeftExtend, () => { + MoveLeftExtend (); + return true; + }); + AddCommand (Command.RightExtend, () => { + MoveRightExtend (); + return true; + }); + AddCommand (Command.WordLeftExtend, () => { + MoveWordLeftExtend (); + return true; + }); + AddCommand (Command.WordRightExtend, () => { + MoveWordRightExtend (); + return true; + }); + AddCommand (Command.Left, () => { + MoveLeft (); + return true; + }); + AddCommand (Command.RightEnd, () => { + MoveEnd (); + return true; + }); + AddCommand (Command.Right, () => { + MoveRight (); + return true; + }); + AddCommand (Command.CutToEndLine, () => { + KillToEnd (); + return true; + }); + AddCommand (Command.CutToStartLine, () => { + KillToStart (); + return true; + }); + AddCommand (Command.Undo, () => { + Undo (); + return true; + }); + AddCommand (Command.Redo, () => { + Redo (); + return true; + }); + AddCommand (Command.WordLeft, () => { + MoveWordLeft (); + return true; + }); + AddCommand (Command.WordRight, () => { + MoveWordRight (); + return true; + }); + AddCommand (Command.KillWordForwards, () => { + KillWordForwards (); + return true; + }); + AddCommand (Command.KillWordBackwards, () => { + KillWordBackwards (); + return true; + }); + AddCommand (Command.ToggleOverwrite, () => { + SetOverwrite (!Used); + return true; + }); + AddCommand (Command.EnableOverwrite, () => { + SetOverwrite (true); + return true; + }); + AddCommand (Command.DisableOverwrite, () => { + SetOverwrite (false); + return true; + }); + AddCommand (Command.Copy, () => { + Copy (); + return true; + }); + AddCommand (Command.Cut, () => { + Cut (); + return true; + }); + AddCommand (Command.Paste, () => { + Paste (); + return true; + }); + AddCommand (Command.SelectAll, () => { + SelectAll (); + return true; + }); + AddCommand (Command.DeleteAll, () => { + DeleteAll (); + return true; + }); + AddCommand (Command.ShowContextMenu, () => { + ShowContextMenu (); + return true; + }); - PositionCursor (); + // Default keybindings for this view + // We follow this as closely as possible: https://en.wikipedia.org/wiki/Table_of_keyboard_shortcuts + KeyBindings.Add (KeyCode.Delete, Command.DeleteCharRight); + KeyBindings.Add (KeyCode.D | KeyCode.CtrlMask, Command.DeleteCharRight); - RenderCaption (); + KeyBindings.Add (KeyCode.Backspace, Command.DeleteCharLeft); - ProcessAutocomplete (); + KeyBindings.Add (KeyCode.Home | KeyCode.ShiftMask, Command.LeftHomeExtend); + KeyBindings.Add (KeyCode.Home | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.LeftHomeExtend); + KeyBindings.Add (KeyCode.A | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.LeftHomeExtend); - _isDrawing = false; - } + KeyBindings.Add (KeyCode.End | KeyCode.ShiftMask, Command.RightEndExtend); + KeyBindings.Add (KeyCode.End | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.RightEndExtend); + KeyBindings.Add (KeyCode.E | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.RightEndExtend); - private void ProcessAutocomplete () - { - if (_isDrawing) { - return; - } - if (SelectedLength > 0) { - return; - } + KeyBindings.Add (KeyCode.Home, Command.LeftHome); + KeyBindings.Add (KeyCode.Home | KeyCode.CtrlMask, Command.LeftHome); + KeyBindings.Add (KeyCode.A | KeyCode.CtrlMask, Command.LeftHome); - // draw autocomplete - GenerateSuggestions (); + KeyBindings.Add (KeyCode.CursorLeft | KeyCode.ShiftMask, Command.LeftExtend); + KeyBindings.Add (KeyCode.CursorUp | KeyCode.ShiftMask, Command.LeftExtend); - var renderAt = new Point ( - Autocomplete.Context.CursorPosition, 0); + KeyBindings.Add (KeyCode.CursorRight | KeyCode.ShiftMask, Command.RightExtend); + KeyBindings.Add (KeyCode.CursorDown | KeyCode.ShiftMask, Command.RightExtend); - Autocomplete.RenderOverlay (renderAt); - } + KeyBindings.Add (KeyCode.CursorLeft | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.WordLeftExtend); + KeyBindings.Add (KeyCode.CursorUp | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.WordLeftExtend); + KeyBindings.Add ('B' + KeyCode.ShiftMask | KeyCode.AltMask, Command.WordLeftExtend); - private void RenderCaption () - { - if (HasFocus || Caption == null || Caption.Length == 0 - || Text?.Length > 0) { - return; - } + KeyBindings.Add (KeyCode.CursorRight | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.WordRightExtend); + KeyBindings.Add (KeyCode.CursorDown | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.WordRightExtend); + KeyBindings.Add ('F' + KeyCode.ShiftMask | KeyCode.AltMask, Command.WordRightExtend); - var color = new Attribute (CaptionColor, GetNormalColor ().Background); - Driver.SetAttribute (color); + KeyBindings.Add (KeyCode.CursorLeft, Command.Left); + KeyBindings.Add (KeyCode.B | KeyCode.CtrlMask, Command.Left); - Move (0, 0); - var render = Caption; + KeyBindings.Add (KeyCode.End, Command.RightEnd); + KeyBindings.Add (KeyCode.End | KeyCode.CtrlMask, Command.RightEnd); + KeyBindings.Add (KeyCode.E | KeyCode.CtrlMask, Command.RightEnd); - if (render.GetColumns () > Bounds.Width) { - render = render [..Bounds.Width]; - } + KeyBindings.Add (KeyCode.CursorRight, Command.Right); + KeyBindings.Add (KeyCode.F | KeyCode.CtrlMask, Command.Right); + + KeyBindings.Add (KeyCode.K | KeyCode.CtrlMask, Command.CutToEndLine); + KeyBindings.Add (KeyCode.K | KeyCode.AltMask, Command.CutToStartLine); + + KeyBindings.Add (KeyCode.Z | KeyCode.CtrlMask, Command.Undo); + KeyBindings.Add (KeyCode.Backspace | KeyCode.AltMask, Command.Undo); + + KeyBindings.Add (KeyCode.Y | KeyCode.CtrlMask, Command.Redo); + + KeyBindings.Add (KeyCode.CursorLeft | KeyCode.CtrlMask, Command.WordLeft); + KeyBindings.Add (KeyCode.CursorUp | KeyCode.CtrlMask, Command.WordLeft); + KeyBindings.Add ('B' + KeyCode.AltMask, Command.WordLeft); - Driver.AddStr (render); + KeyBindings.Add (KeyCode.CursorRight | KeyCode.CtrlMask, Command.WordRight); + KeyBindings.Add (KeyCode.CursorDown | KeyCode.CtrlMask, Command.WordRight); + KeyBindings.Add ('F' + KeyCode.AltMask, Command.WordRight); + + KeyBindings.Add (KeyCode.Delete | KeyCode.CtrlMask, Command.KillWordForwards); + KeyBindings.Add (KeyCode.Backspace | KeyCode.CtrlMask, Command.KillWordBackwards); + KeyBindings.Add (KeyCode.Insert, Command.ToggleOverwrite); + KeyBindings.Add (KeyCode.C | KeyCode.CtrlMask, Command.Copy); + KeyBindings.Add (KeyCode.X | KeyCode.CtrlMask, Command.Cut); + KeyBindings.Add (KeyCode.V | KeyCode.CtrlMask, Command.Paste); + KeyBindings.Add (KeyCode.T | KeyCode.CtrlMask, Command.SelectAll); + + KeyBindings.Add (KeyCode.R | KeyCode.CtrlMask, Command.DeleteAll); + KeyBindings.Add (KeyCode.D | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.DeleteAll); + + _currentCulture = Thread.CurrentThread.CurrentUICulture; + + ContextMenu = new ContextMenu (this, BuildContextMenuBarItem ()); + ContextMenu.KeyChanged += ContextMenu_KeyChanged; + + KeyBindings.Add (ContextMenu.Key.KeyCode, KeyBindingScope.HotKey, Command.ShowContextMenu); + } + + void TextField_LayoutComplete (object sender, LayoutEventArgs e) + { + // Don't let height > 1 + if (Frame.Height > 1) { + Height = 1; } + } - private void GenerateSuggestions () - { - var currentLine = TextModel.ToRuneCellList (Text); - var cursorPosition = Math.Min (this.CursorPosition, currentLine.Count); - Autocomplete.Context = new AutocompleteContext (currentLine, cursorPosition, - Autocomplete.Context != null ? Autocomplete.Context.Canceled : false); - Autocomplete.GenerateSuggestions ( - Autocomplete.Context); + MenuBarItem BuildContextMenuBarItem () => new (new MenuItem [] { + new (Strings.ctxSelectAll, "", () => SelectAll (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.SelectAll)), + new (Strings.ctxDeleteAll, "", () => DeleteAll (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.DeleteAll)), + new (Strings.ctxCopy, "", () => Copy (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Copy)), + new (Strings.ctxCut, "", () => Cut (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Cut)), + new (Strings.ctxPaste, "", () => Paste (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Paste)), + new (Strings.ctxUndo, "", () => Undo (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Undo)), + new (Strings.ctxRedo, "", () => Redo (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Redo)) + }); + + void ContextMenu_KeyChanged (object sender, KeyChangedEventArgs e) => KeyBindings.Replace (e.OldKey.KeyCode, e.NewKey.KeyCode); + + void HistoryText_ChangeText (object sender, HistoryText.HistoryTextItem obj) + { + if (obj == null) { + return; } - /// - public override Attribute GetNormalColor () - { - return Enabled ? ColorScheme.Focus : ColorScheme.Disabled; + Text = TextModel.ToString (obj?.Lines [obj.CursorPosition.Y]); + CursorPosition = obj.CursorPosition.X; + Adjust (); + } + + void TextField_Initialized (object sender, EventArgs e) + { + Autocomplete.HostControl = this; + Autocomplete.PopupInsideContainer = false; + } + + /// + public override bool OnEnter (View view) + { + if (IsInitialized) { + Application.Driver.SetCursorVisibility (DesiredCursorVisibility); } - Attribute GetReadOnlyColor () - { - if (ColorScheme.Disabled.Foreground == ColorScheme.Focus.Background) { - return new Attribute (ColorScheme.Focus.Foreground, ColorScheme.Focus.Background); - } - return new Attribute (ColorScheme.Disabled.Foreground, ColorScheme.Focus.Background); + return base.OnEnter (view); + } + + /// + public override bool OnLeave (View view) + { + if (Application.MouseGrabView != null && Application.MouseGrabView == this) { + Application.UngrabMouse (); } + //if (SelectedLength != 0 && !(Application.MouseGrabView is MenuBar)) + // ClearAllSelection (); - void Adjust () - { - if (!IsAdded) { - return; - } + return base.OnLeave (view); + } + + /// + /// Sets the cursor position. + /// + public override void PositionCursor () + { + if (!IsInitialized) { + return; + } + ProcessAutocomplete (); - int offB = OffSetBackground (); - bool need = NeedsDisplay || !Used; - if (_cursorPosition < _first) { - _first = _cursorPosition; - need = true; - } else if (Frame.Width > 0 && (_first + _cursorPosition - (Frame.Width + offB) == 0 || - TextModel.DisplaySize (_text, _first, _cursorPosition).size >= Frame.Width + offB)) { - - _first = Math.Max (TextModel.CalculateLeftColumn (_text, _first, - _cursorPosition, Frame.Width + offB), 0); - need = true; + var col = 0; + for (var idx = ScrollOffset < 0 ? 0 : ScrollOffset; idx < _text.Count; idx++) { + if (idx == _cursorPosition) { + break; } - if (need) { - SetNeedsDisplay (); + var cols = _text [idx].GetColumns (); + TextModel.SetCol (ref col, Frame.Width - 1, cols); + } + var pos = _cursorPosition - ScrollOffset + Math.Min (Frame.X, 0); + var offB = OffSetBackground (); + var containerFrame = SuperView?.BoundsToScreen (SuperView.Bounds) ?? default; + var thisFrame = BoundsToScreen (Bounds); + if (pos > -1 && col >= pos && pos < Frame.Width + offB + && containerFrame.IntersectsWith (thisFrame)) { + RestoreCursorVisibility (); + Move (col, 0); + } else { + HideCursorVisibility (); + if (pos < 0) { + Move (pos, 0); } else { - PositionCursor (); + Move (pos - offB, 0); } } + } - int OffSetBackground () - { - int offB = 0; - if (SuperView?.Frame.Right - Frame.Right < 0) { - offB = SuperView.Frame.Right - Frame.Right - 1; - } - - return offB; + void HideCursorVisibility () + { + if (_desiredCursorVisibility != CursorVisibility.Invisible) { + DesiredCursorVisibility = CursorVisibility.Invisible; } + } - void SetText (List newText) - { - Text = StringExtensions.ToString (newText); + void RestoreCursorVisibility () + { + Application.Driver.GetCursorVisibility (out _visibility); + if (_desiredCursorVisibility != _savedCursorVisibility || _visibility != _savedCursorVisibility) { + DesiredCursorVisibility = _savedCursorVisibility; } + } - void SetText (IEnumerable newText) - { - SetText (newText.ToList ()); + /// + public override void OnDrawContent (Rect contentArea) + { + _isDrawing = true; + + var selColor = new Attribute (ColorScheme.Focus.Background, ColorScheme.Focus.Foreground); + SetSelectedStartSelectedLength (); + + Driver.SetAttribute (GetNormalColor ()); + Move (0, 0); + + var p = ScrollOffset; + var col = 0; + var width = Frame.Width + OffSetBackground (); + var tcount = _text.Count; + var roc = GetReadOnlyColor (); + for (var idx = p; idx < tcount; idx++) { + var rune = _text [idx]; + var cols = rune.GetColumns (); + if (idx == _cursorPosition && HasFocus && !Used && SelectedLength == 0 && !ReadOnly) { + Driver.SetAttribute (selColor); + } else if (ReadOnly) { + Driver.SetAttribute (idx >= _start && SelectedLength > 0 && idx < _start + SelectedLength ? selColor : roc); + } else if (!HasFocus && Enabled) { + Driver.SetAttribute (ColorScheme.Focus); + } else if (!Enabled) { + Driver.SetAttribute (roc); + } else { + Driver.SetAttribute (idx >= _start && SelectedLength > 0 && idx < _start + SelectedLength ? selColor : ColorScheme.Focus); + } + if (col + cols <= width) { + Driver.AddRune (Secret ? Glyphs.Dot : rune); + } + if (!TextModel.SetCol (ref col, width, cols)) { + break; + } + if (idx + 1 < tcount && col + _text [idx + 1].GetColumns () > width) { + break; + } } - /// - public override bool CanFocus { - get => base.CanFocus; - set { base.CanFocus = value; } + Driver.SetAttribute (ColorScheme.Focus); + for (var i = col; i < width; i++) { + Driver.AddRune ((Rune)' '); } - void SetClipboard (IEnumerable text) - { - if (!Secret) - Clipboard.Contents = StringExtensions.ToString (text.ToList ()); + PositionCursor (); + + RenderCaption (); + + ProcessAutocomplete (); + + _isDrawing = false; + } + + void ProcessAutocomplete () + { + if (_isDrawing) { + return; + } + if (SelectedLength > 0) { + return; } - int _preTextChangedCursorPos; + // draw autocomplete + GenerateSuggestions (); - /// - public override bool? OnInvokingKeyBindings (Key a) - { - // Give autocomplete first opportunity to respond to key presses - if (SelectedLength == 0 && Autocomplete.Suggestions.Count > 0 && Autocomplete.ProcessKey (a)) { - return true; - } - return base.OnInvokingKeyBindings (a); - } - - /// TODO: Flush out these docs - /// - /// Processes key presses for the . - /// - /// The control responds to the following keys: - /// - /// - /// Keys - /// Function - /// - /// - /// , - /// Deletes the character before cursor. - /// - /// - /// - /// - /// - /// - public override bool OnProcessKeyDown (Key a) - { - // Remember the cursor position because the new calculated cursor position is needed - // to be set BEFORE the TextChanged event is triggered. - // Needed for the Elmish Wrapper issue https://github.com/DieselMeister/Terminal.Gui.Elmish/issues/2 - _preTextChangedCursorPos = _cursorPosition; + var renderAt = new Point ( + Autocomplete.Context.CursorPosition, 0); - // Ignore other control characters. - if (!a.IsKeyCodeAtoZ && (a.KeyCode < KeyCode.Space || a.KeyCode > KeyCode.CharMask)) { - return false; - } + Autocomplete.RenderOverlay (renderAt); + } - if (ReadOnly) { - return true; - } + void RenderCaption () + { + if (HasFocus || Caption == null || Caption.Length == 0 + || Text?.Length > 0) { + return; + } - InsertText (a, true); + var color = new Attribute (CaptionColor, GetNormalColor ().Background); + Driver.SetAttribute (color); - return true; + Move (0, 0); + var render = Caption; + + if (render.GetColumns () > Bounds.Width) { + render = render [..Bounds.Width]; } - void InsertText (Key a, bool usePreTextChangedCursorPos) - { - _historyText.Add (new List> () { TextModel.ToRuneCells (_text) }, new Point (_cursorPosition, 0)); + Driver.AddStr (render); + } - List newText = _text; - if (_length > 0) { - newText = DeleteSelectedText (); - _preTextChangedCursorPos = _cursorPosition; - } - if (!usePreTextChangedCursorPos) { - _preTextChangedCursorPos = _cursorPosition; - } - var kbstr = a.AsRune.ToString ().EnumerateRunes (); - if (Used) { - _cursorPosition++; - if (_cursorPosition == newText.Count + 1) { - SetText (newText.Concat (kbstr).ToList ()); - } else { - if (_preTextChangedCursorPos > newText.Count) { - _preTextChangedCursorPos = newText.Count; - } - SetText (newText.GetRange (0, _preTextChangedCursorPos).Concat (kbstr).Concat (newText.GetRange (_preTextChangedCursorPos, Math.Min (newText.Count - _preTextChangedCursorPos, newText.Count)))); - } - } else { - SetText (newText.GetRange (0, _preTextChangedCursorPos).Concat (kbstr).Concat (newText.GetRange (Math.Min (_preTextChangedCursorPos + 1, newText.Count), Math.Max (newText.Count - _preTextChangedCursorPos - 1, 0)))); - _cursorPosition++; - } - Adjust (); + void GenerateSuggestions () + { + var currentLine = TextModel.ToRuneCellList (Text); + var cursorPosition = Math.Min (CursorPosition, currentLine.Count); + Autocomplete.Context = new AutocompleteContext (currentLine, cursorPosition, + Autocomplete.Context != null ? Autocomplete.Context.Canceled : false); + + Autocomplete.GenerateSuggestions ( + Autocomplete.Context); + } + + /// + public override Attribute GetNormalColor () => Enabled ? ColorScheme.Focus : ColorScheme.Disabled; + + Attribute GetReadOnlyColor () + { + if (ColorScheme.Disabled.Foreground == ColorScheme.Focus.Background) { + return new Attribute (ColorScheme.Focus.Foreground, ColorScheme.Focus.Background); } + return new Attribute (ColorScheme.Disabled.Foreground, ColorScheme.Focus.Background); + } - void SetOverwrite (bool overwrite) - { - Used = overwrite; + void Adjust () + { + if (!IsAdded) { + return; + } + + var offB = OffSetBackground (); + var need = NeedsDisplay || !Used; + if (_cursorPosition < ScrollOffset) { + ScrollOffset = _cursorPosition; + need = true; + } else if (Frame.Width > 0 && (ScrollOffset + _cursorPosition - (Frame.Width + offB) == 0 || + TextModel.DisplaySize (_text, ScrollOffset, _cursorPosition).size >= Frame.Width + offB)) { + + ScrollOffset = Math.Max (TextModel.CalculateLeftColumn (_text, ScrollOffset, + _cursorPosition, Frame.Width + offB), 0); + need = true; + } + if (need) { SetNeedsDisplay (); + } else { + PositionCursor (); } + } - TextModel GetModel () - { - var model = new TextModel (); - model.LoadString (Text); - return model; + int OffSetBackground () + { + var offB = 0; + if (SuperView?.Frame.Right - Frame.Right < 0) { + offB = SuperView.Frame.Right - Frame.Right - 1; } - /// - /// Deletes word backwards. - /// - public virtual void KillWordBackwards () - { - ClearAllSelection (); - var newPos = GetModel ().WordBackward (_cursorPosition, 0); - if (newPos == null) return; - if (newPos.Value.col != -1) { - SetText (_text.GetRange (0, newPos.Value.col).Concat (_text.GetRange (_cursorPosition, _text.Count - _cursorPosition))); - _cursorPosition = newPos.Value.col; - } - Adjust (); + return offB; + } + + void SetText (List newText) => Text = StringExtensions.ToString (newText); + + void SetText (IEnumerable newText) => SetText (newText.ToList ()); + + void SetClipboard (IEnumerable text) + { + if (!Secret) { + Clipboard.Contents = StringExtensions.ToString (text.ToList ()); } + } - /// - /// Deletes word forwards. - /// - public virtual void KillWordForwards () - { - ClearAllSelection (); - var newPos = GetModel ().WordForward (_cursorPosition, 0); - if (newPos == null) return; - if (newPos.Value.col != -1) { - SetText (_text.GetRange (0, _cursorPosition).Concat (_text.GetRange (newPos.Value.col, _text.Count - newPos.Value.col))); - } - Adjust (); + /// + public override bool? OnInvokingKeyBindings (Key a) + { + // Give autocomplete first opportunity to respond to key presses + if (SelectedLength == 0 && Autocomplete.Suggestions.Count > 0 && Autocomplete.ProcessKey (a)) { + return true; } + return base.OnInvokingKeyBindings (a); + } - void MoveWordRight () - { - ClearAllSelection (); - var newPos = GetModel ().WordForward (_cursorPosition, 0); - if (newPos == null) return; - if (newPos.Value.col != -1) - _cursorPosition = newPos.Value.col; - Adjust (); + /// TODO: Flush out these docs + /// + /// Processes key presses for the . + /// + /// The control responds to the following keys: + /// + /// + /// Keys + /// Function + /// + /// + /// , + /// Deletes the character before cursor. + /// + /// + /// + /// + /// + /// + public override bool OnProcessKeyDown (Key a) + { + // Remember the cursor position because the new calculated cursor position is needed + // to be set BEFORE the TextChanged event is triggered. + // Needed for the Elmish Wrapper issue https://github.com/DieselMeister/Terminal.Gui.Elmish/issues/2 + _preTextChangedCursorPos = _cursorPosition; + + // Ignore other control characters. + if (!a.IsKeyCodeAtoZ && (a.KeyCode < KeyCode.Space || a.KeyCode > KeyCode.CharMask)) { + return false; } - void MoveWordLeft () - { - ClearAllSelection (); - var newPos = GetModel ().WordBackward (_cursorPosition, 0); - if (newPos == null) return; - if (newPos.Value.col != -1) - _cursorPosition = newPos.Value.col; - Adjust (); + if (ReadOnly) { + return true; } - /// - /// Redoes the latest changes. - /// - public void Redo () - { - if (ReadOnly) { - return; + InsertText (a, true); + + return true; + } + + void InsertText (Key a, bool usePreTextChangedCursorPos) + { + _historyText.Add (new List> { TextModel.ToRuneCells (_text) }, new Point (_cursorPosition, 0)); + + var newText = _text; + if (SelectedLength > 0) { + newText = DeleteSelectedText (); + _preTextChangedCursorPos = _cursorPosition; + } + if (!usePreTextChangedCursorPos) { + _preTextChangedCursorPos = _cursorPosition; + } + var kbstr = a.AsRune.ToString ().EnumerateRunes (); + if (Used) { + _cursorPosition++; + if (_cursorPosition == newText.Count + 1) { + SetText (newText.Concat (kbstr).ToList ()); + } else { + if (_preTextChangedCursorPos > newText.Count) { + _preTextChangedCursorPos = newText.Count; + } + SetText (newText.GetRange (0, _preTextChangedCursorPos).Concat (kbstr).Concat (newText.GetRange (_preTextChangedCursorPos, Math.Min (newText.Count - _preTextChangedCursorPos, newText.Count)))); } + } else { + SetText (newText.GetRange (0, _preTextChangedCursorPos).Concat (kbstr).Concat (newText.GetRange (Math.Min (_preTextChangedCursorPos + 1, newText.Count), Math.Max (newText.Count - _preTextChangedCursorPos - 1, 0)))); + _cursorPosition++; + } + Adjust (); + } + + void SetOverwrite (bool overwrite) + { + Used = overwrite; + SetNeedsDisplay (); + } + + TextModel GetModel () + { + var model = new TextModel (); + model.LoadString (Text); + return model; + } - _historyText.Redo (); + /// + /// Deletes word backwards. + /// + public virtual void KillWordBackwards () + { + ClearAllSelection (); + var newPos = GetModel ().WordBackward (_cursorPosition, 0); + if (newPos == null) { + return; + } + if (newPos.Value.col != -1) { + SetText (_text.GetRange (0, newPos.Value.col).Concat (_text.GetRange (_cursorPosition, _text.Count - _cursorPosition))); + _cursorPosition = newPos.Value.col; + } + Adjust (); + } - //if (string.IsNullOrEmpty (Clipboard.Contents)) - // return true; - //var clip = TextModel.ToRunes (Clipboard.Contents); - //if (clip == null) - // return true; + /// + /// Deletes word forwards. + /// + public virtual void KillWordForwards () + { + ClearAllSelection (); + var newPos = GetModel ().WordForward (_cursorPosition, 0); + if (newPos == null) { + return; + } + if (newPos.Value.col != -1) { + SetText (_text.GetRange (0, _cursorPosition).Concat (_text.GetRange (newPos.Value.col, _text.Count - newPos.Value.col))); + } + Adjust (); + } - //if (point == text.Count) { - // point = text.Count; - // SetText(text.Concat(clip).ToList()); - //} else { - // point += clip.Count; - // SetText(text.GetRange(0, oldCursorPos).Concat(clip).Concat(text.GetRange(oldCursorPos, text.Count - oldCursorPos))); - //} - //Adjust (); + void MoveWordRight () + { + ClearAllSelection (); + var newPos = GetModel ().WordForward (_cursorPosition, 0); + if (newPos == null) { + return; + } + if (newPos.Value.col != -1) { + _cursorPosition = newPos.Value.col; + } + Adjust (); + } + + void MoveWordLeft () + { + ClearAllSelection (); + var newPos = GetModel ().WordBackward (_cursorPosition, 0); + if (newPos == null) { + return; + } + if (newPos.Value.col != -1) { + _cursorPosition = newPos.Value.col; + } + Adjust (); + } + + /// + /// Redoes the latest changes. + /// + public void Redo () + { + if (ReadOnly) { + return; + } + + _historyText.Redo (); + + //if (string.IsNullOrEmpty (Clipboard.Contents)) + // return true; + //var clip = TextModel.ToRunes (Clipboard.Contents); + //if (clip == null) + // return true; + + //if (point == text.Count) { + // point = text.Count; + // SetText(text.Concat(clip).ToList()); + //} else { + // point += clip.Count; + // SetText(text.GetRange(0, oldCursorPos).Concat(clip).Concat(text.GetRange(oldCursorPos, text.Count - oldCursorPos))); + //} + //Adjust (); + } + + /// + /// Undoes the latest changes. + /// + public void Undo () + { + if (ReadOnly) { + return; } - /// - /// Undoes the latest changes. - /// - public void Undo () - { - if (ReadOnly) { - return; - } + _historyText.Undo (); + } - _historyText.Undo (); + void KillToStart () + { + if (ReadOnly) { + return; } - void KillToStart () - { - if (ReadOnly) - return; - - ClearAllSelection (); - if (_cursorPosition == 0) - return; - SetClipboard (_text.GetRange (0, _cursorPosition)); - SetText (_text.GetRange (_cursorPosition, _text.Count - _cursorPosition)); - _cursorPosition = 0; - Adjust (); + ClearAllSelection (); + if (_cursorPosition == 0) { + return; } + SetClipboard (_text.GetRange (0, _cursorPosition)); + SetText (_text.GetRange (_cursorPosition, _text.Count - _cursorPosition)); + _cursorPosition = 0; + Adjust (); + } - void KillToEnd () - { - if (ReadOnly) - return; + void KillToEnd () + { + if (ReadOnly) { + return; + } - ClearAllSelection (); - if (_cursorPosition >= _text.Count) - return; - SetClipboard (_text.GetRange (_cursorPosition, _text.Count - _cursorPosition)); - SetText (_text.GetRange (0, _cursorPosition)); - Adjust (); + ClearAllSelection (); + if (_cursorPosition >= _text.Count) { + return; } + SetClipboard (_text.GetRange (_cursorPosition, _text.Count - _cursorPosition)); + SetText (_text.GetRange (0, _cursorPosition)); + Adjust (); + } - void MoveRight () - { - ClearAllSelection (); - if (_cursorPosition == _text.Count) - return; - _cursorPosition++; - Adjust (); + void MoveRight () + { + ClearAllSelection (); + if (_cursorPosition == _text.Count) { + return; } + _cursorPosition++; + Adjust (); + } - /// - /// Moves cursor to the end of the typed text. - /// - public void MoveEnd () - { - ClearAllSelection (); - _cursorPosition = _text.Count; + /// + /// Moves cursor to the end of the typed text. + /// + public void MoveEnd () + { + ClearAllSelection (); + _cursorPosition = _text.Count; + Adjust (); + } + + void MoveLeft () + { + ClearAllSelection (); + if (_cursorPosition > 0) { + _cursorPosition--; Adjust (); } + } - void MoveLeft () - { - ClearAllSelection (); - if (_cursorPosition > 0) { - _cursorPosition--; - Adjust (); + void MoveWordRightExtend () + { + if (_cursorPosition < _text.Count) { + var x = _start > -1 && _start > _cursorPosition ? _start : _cursorPosition; + var newPos = GetModel ().WordForward (x, 0); + if (newPos == null) { + return; + } + if (newPos.Value.col != -1) { + _cursorPosition = newPos.Value.col; } + PrepareSelection (x, newPos.Value.col - x); } + } - void MoveWordRightExtend () - { - if (_cursorPosition < _text.Count) { - int x = _start > -1 && _start > _cursorPosition ? _start : _cursorPosition; - var newPos = GetModel ().WordForward (x, 0); - if (newPos == null) return; - if (newPos.Value.col != -1) + void MoveWordLeftExtend () + { + if (_cursorPosition > 0) { + var x = Math.Min (_start > -1 && _start > _cursorPosition ? _start : _cursorPosition, _text.Count); + if (x > 0) { + var newPos = GetModel ().WordBackward (x, 0); + if (newPos == null) { + return; + } + if (newPos.Value.col != -1) { _cursorPosition = newPos.Value.col; + } PrepareSelection (x, newPos.Value.col - x); } } + } - void MoveWordLeftExtend () - { - if (_cursorPosition > 0) { - int x = Math.Min (_start > -1 && _start > _cursorPosition ? _start : _cursorPosition, _text.Count); - if (x > 0) { - var newPos = GetModel ().WordBackward (x, 0); - if (newPos == null) return; - if (newPos.Value.col != -1) - _cursorPosition = newPos.Value.col; - PrepareSelection (x, newPos.Value.col - x); - } - } + void MoveRightExtend () + { + if (_cursorPosition < _text.Count) { + PrepareSelection (_cursorPosition++, 1); } + } - void MoveRightExtend () - { - if (_cursorPosition < _text.Count) { - PrepareSelection (_cursorPosition++, 1); - } + void MoveLeftExtend () + { + if (_cursorPosition > 0) { + PrepareSelection (_cursorPosition--, -1); } + } - void MoveLeftExtend () - { - if (_cursorPosition > 0) { - PrepareSelection (_cursorPosition--, -1); - } + void MoveHome () + { + ClearAllSelection (); + _cursorPosition = 0; + Adjust (); + } + + void MoveEndExtend () + { + if (_cursorPosition <= _text.Count) { + var x = _cursorPosition; + _cursorPosition = _text.Count; + PrepareSelection (x, _cursorPosition - x); } + } - void MoveHome () - { - ClearAllSelection (); + void MoveHomeExtend () + { + if (_cursorPosition > 0) { + var x = _cursorPosition; _cursorPosition = 0; - Adjust (); + PrepareSelection (x, _cursorPosition - x); } + } - void MoveEndExtend () - { - if (_cursorPosition <= _text.Count) { - int x = _cursorPosition; - _cursorPosition = _text.Count; - PrepareSelection (x, _cursorPosition - x); - } + /// + /// Deletes the character to the left. + /// + /// + /// If set to true use the cursor position cached + /// ; otherwise use . + /// use . + /// + public virtual void DeleteCharLeft (bool usePreTextChangedCursorPos) + { + if (ReadOnly) { + return; } - void MoveHomeExtend () - { - if (_cursorPosition > 0) { - int x = _cursorPosition; - _cursorPosition = 0; - PrepareSelection (x, _cursorPosition - x); - } - } + _historyText.Add (new List> { TextModel.ToRuneCells (_text) }, new Point (_cursorPosition, 0)); - /// - /// Deletes the character to the left. - /// - /// If set to true use the cursor position cached - /// ; otherwise use . - /// use . - public virtual void DeleteCharLeft (bool usePreTextChangedCursorPos) - { - if (ReadOnly) + if (SelectedLength == 0) { + if (_cursorPosition == 0) { return; - - _historyText.Add (new List> () { TextModel.ToRuneCells (_text) }, new Point (_cursorPosition, 0)); - - if (_length == 0) { - if (_cursorPosition == 0) - return; - - if (!usePreTextChangedCursorPos) { - _preTextChangedCursorPos = _cursorPosition; - } - _cursorPosition--; - if (_preTextChangedCursorPos < _text.Count) { - SetText (_text.GetRange (0, _preTextChangedCursorPos - 1).Concat (_text.GetRange (_preTextChangedCursorPos, _text.Count - _preTextChangedCursorPos))); - } else { - SetText (_text.GetRange (0, _preTextChangedCursorPos - 1)); - } - Adjust (); - } else { - var newText = DeleteSelectedText (); - Text = StringExtensions.ToString (newText); - Adjust (); } - } - - /// - /// Deletes the character to the right. - /// - public virtual void DeleteCharRight () - { - if (ReadOnly) - return; - _historyText.Add (new List> () { TextModel.ToRuneCells (_text) }, new Point (_cursorPosition, 0)); - - if (_length == 0) { - if (_text.Count == 0 || _text.Count == _cursorPosition) - return; - - SetText (_text.GetRange (0, _cursorPosition).Concat (_text.GetRange (_cursorPosition + 1, _text.Count - (_cursorPosition + 1)))); - Adjust (); + if (!usePreTextChangedCursorPos) { + _preTextChangedCursorPos = _cursorPosition; + } + _cursorPosition--; + if (_preTextChangedCursorPos < _text.Count) { + SetText (_text.GetRange (0, _preTextChangedCursorPos - 1).Concat (_text.GetRange (_preTextChangedCursorPos, _text.Count - _preTextChangedCursorPos))); } else { - var newText = DeleteSelectedText (); - Text = StringExtensions.ToString (newText); - Adjust (); + SetText (_text.GetRange (0, _preTextChangedCursorPos - 1)); } + Adjust (); + } else { + var newText = DeleteSelectedText (); + Text = StringExtensions.ToString (newText); + Adjust (); } + } - void ShowContextMenu () - { - if (_currentCulture != Thread.CurrentThread.CurrentUICulture) { - - _currentCulture = Thread.CurrentThread.CurrentUICulture; - - ContextMenu.MenuItems = BuildContextMenuBarItem (); - } - ContextMenu.Show (); + /// + /// Deletes the character to the right. + /// + public virtual void DeleteCharRight () + { + if (ReadOnly) { + return; } - /// - /// Selects all text. - /// - public void SelectAll () - { - if (_text.Count == 0) { + _historyText.Add (new List> { TextModel.ToRuneCells (_text) }, new Point (_cursorPosition, 0)); + + if (SelectedLength == 0) { + if (_text.Count == 0 || _text.Count == _cursorPosition) { return; } - _selectedStart = 0; - MoveEndExtend (); - SetNeedsDisplay (); + SetText (_text.GetRange (0, _cursorPosition).Concat (_text.GetRange (_cursorPosition + 1, _text.Count - (_cursorPosition + 1)))); + Adjust (); + } else { + var newText = DeleteSelectedText (); + Text = StringExtensions.ToString (newText); + Adjust (); } + } - /// - /// Deletes all text. - /// - public void DeleteAll () - { - if (_text.Count == 0) { - return; - } + void ShowContextMenu () + { + if (_currentCulture != Thread.CurrentThread.CurrentUICulture) { - _selectedStart = 0; - MoveEndExtend (); - DeleteCharLeft (false); - SetNeedsDisplay (); + _currentCulture = Thread.CurrentThread.CurrentUICulture; + + ContextMenu.MenuItems = BuildContextMenuBarItem (); } + ContextMenu.Show (); + } - /// - /// Start position of the selected text. - /// - public int SelectedStart { - get => _selectedStart; - set { - if (value < -1) { - _selectedStart = -1; - } else if (value > _text.Count) { - _selectedStart = _text.Count; - } else { - _selectedStart = value; - } - PrepareSelection (_selectedStart, _cursorPosition - _selectedStart); - } + /// + /// Selects all text. + /// + public void SelectAll () + { + if (_text.Count == 0) { + return; } - /// - /// Length of the selected text. - /// - public int SelectedLength { get => _length; } + _selectedStart = 0; + MoveEndExtend (); + SetNeedsDisplay (); + } - /// - /// The selected text. - /// - public string SelectedText { - get => Secret ? null : _selectedText; - private set => _selectedText = value; + /// + /// Deletes all text. + /// + public void DeleteAll () + { + if (_text.Count == 0) { + return; } - int _start, _length; - bool _isButtonPressed; - bool _isButtonReleased = true; + _selectedStart = 0; + MoveEndExtend (); + DeleteCharLeft (false); + SetNeedsDisplay (); + } - /// - public override bool MouseEvent (MouseEvent ev) - { - if (!ev.Flags.HasFlag (MouseFlags.Button1Pressed) && !ev.Flags.HasFlag (MouseFlags.ReportMousePosition) && - !ev.Flags.HasFlag (MouseFlags.Button1Released) && !ev.Flags.HasFlag (MouseFlags.Button1DoubleClicked) && - !ev.Flags.HasFlag (MouseFlags.Button1TripleClicked) && !ev.Flags.HasFlag (ContextMenu.MouseFlags)) { - return false; - } + /// + public override bool MouseEvent (MouseEvent ev) + { + if (!ev.Flags.HasFlag (MouseFlags.Button1Pressed) && !ev.Flags.HasFlag (MouseFlags.ReportMousePosition) && + !ev.Flags.HasFlag (MouseFlags.Button1Released) && !ev.Flags.HasFlag (MouseFlags.Button1DoubleClicked) && + !ev.Flags.HasFlag (MouseFlags.Button1TripleClicked) && !ev.Flags.HasFlag (ContextMenu.MouseFlags)) { + return false; + } - if (!CanFocus) { - return true; - } + if (!CanFocus) { + return true; + } - if (!HasFocus && ev.Flags != MouseFlags.ReportMousePosition) { - SetFocus (); - } + if (!HasFocus && ev.Flags != MouseFlags.ReportMousePosition) { + SetFocus (); + } - // Give autocomplete first opportunity to respond to mouse clicks - if (SelectedLength == 0 && Autocomplete.MouseEvent (ev, true)) { - return true; - } + // Give autocomplete first opportunity to respond to mouse clicks + if (SelectedLength == 0 && Autocomplete.MouseEvent (ev, true)) { + return true; + } - if (ev.Flags == MouseFlags.Button1Pressed) { - EnsureHasFocus (); - PositionCursor (ev); - if (_isButtonReleased) { - ClearAllSelection (); - } - _isButtonReleased = true; - _isButtonPressed = true; - } else if (ev.Flags == (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) && _isButtonPressed) { - int x = PositionCursor (ev); - _isButtonReleased = false; - PrepareSelection (x); - if (Application.MouseGrabView == null) { - Application.GrabMouse (this); - } - } else if (ev.Flags == MouseFlags.Button1Released) { - _isButtonReleased = true; - _isButtonPressed = false; - Application.UngrabMouse (); - } else if (ev.Flags == MouseFlags.Button1DoubleClicked) { - EnsureHasFocus (); - int x = PositionCursor (ev); - int sbw = x; - if (x == _text.Count || (x > 0 && (char)_text [x - 1].Value != ' ') - || (x > 0 && (char)_text [x].Value == ' ')) { - - var newPosBw = GetModel ().WordBackward (x, 0); - if (newPosBw == null) return true; - sbw = newPosBw.Value.col; - } - if (sbw != -1) { - x = sbw; - PositionCursor (x); - } - var newPosFw = GetModel ().WordForward (x, 0); - if (newPosFw == null) return true; - ClearAllSelection (); - if (newPosFw.Value.col != -1 && sbw != -1) { - _cursorPosition = newPosFw.Value.col; - } - PrepareSelection (sbw, newPosFw.Value.col - sbw); - } else if (ev.Flags == MouseFlags.Button1TripleClicked) { - EnsureHasFocus (); - PositionCursor (0); + if (ev.Flags == MouseFlags.Button1Pressed) { + EnsureHasFocus (); + PositionCursor (ev); + if (_isButtonReleased) { ClearAllSelection (); - PrepareSelection (0, _text.Count); - } else if (ev.Flags == ContextMenu.MouseFlags) { - ShowContextMenu (); } - - SetNeedsDisplay (); - return true; - - void EnsureHasFocus () - { - if (!HasFocus) { - SetFocus (); + _isButtonReleased = true; + _isButtonPressed = true; + } else if (ev.Flags == (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) && _isButtonPressed) { + var x = PositionCursor (ev); + _isButtonReleased = false; + PrepareSelection (x); + if (Application.MouseGrabView == null) { + Application.GrabMouse (this); + } + } else if (ev.Flags == MouseFlags.Button1Released) { + _isButtonReleased = true; + _isButtonPressed = false; + Application.UngrabMouse (); + } else if (ev.Flags == MouseFlags.Button1DoubleClicked) { + EnsureHasFocus (); + var x = PositionCursor (ev); + var sbw = x; + if (x == _text.Count || x > 0 && (char)_text [x - 1].Value != ' ' + || x > 0 && (char)_text [x].Value == ' ') { + + var newPosBw = GetModel ().WordBackward (x, 0); + if (newPosBw == null) { + return true; } + sbw = newPosBw.Value.col; } - } - - int PositionCursor (MouseEvent ev) - { - // We could also set the cursor position. - int x; - var pX = TextModel.GetColFromX (_text, _first, ev.X); - if (_text.Count == 0) { - x = pX - ev.OfX; - } else { - x = pX; + if (sbw != -1) { + x = sbw; + PositionCursor (x); } - return PositionCursor (x, false); - } - - int PositionCursor (int x, bool getX = true) - { - int pX = x; - if (getX) { - pX = TextModel.GetColFromX (_text, _first, x); + var newPosFw = GetModel ().WordForward (x, 0); + if (newPosFw == null) { + return true; } - if (_first + pX > _text.Count) { - _cursorPosition = _text.Count; - } else if (_first + pX < _first) { - _cursorPosition = 0; - } else { - _cursorPosition = _first + pX; + ClearAllSelection (); + if (newPosFw.Value.col != -1 && sbw != -1) { + _cursorPosition = newPosFw.Value.col; } - - return _cursorPosition; + PrepareSelection (sbw, newPosFw.Value.col - sbw); + } else if (ev.Flags == MouseFlags.Button1TripleClicked) { + EnsureHasFocus (); + PositionCursor (0); + ClearAllSelection (); + PrepareSelection (0, _text.Count); + } else if (ev.Flags == ContextMenu.MouseFlags) { + ShowContextMenu (); } - void PrepareSelection (int x, int direction = 0) + SetNeedsDisplay (); + return true; + + void EnsureHasFocus () { - x = x + _first < -1 ? 0 : x; - _selectedStart = _selectedStart == -1 && _text.Count > 0 && x >= 0 && x <= _text.Count ? x : _selectedStart; - if (_selectedStart > -1) { - _length = Math.Abs (x + direction <= _text.Count ? x + direction - _selectedStart : _text.Count - _selectedStart); - SetSelectedStartSelectedLength (); - if (_start > -1 && _length > 0) { - _selectedText = _length > 0 ? StringExtensions.ToString (_text.GetRange ( - _start < 0 ? 0 : _start, _length > _text.Count ? _text.Count : _length)) : ""; - if (_first > _start) { - _first = _start; - } - } else if (_start > -1 && _length == 0) { - _selectedText = null; - } - SetNeedsDisplay (); - } else if (_length > 0 || _selectedText != null) { - ClearAllSelection (); + if (!HasFocus) { + SetFocus (); } - Adjust (); } + } - /// - /// Clear the selected text. - /// - public void ClearAllSelection () - { - if (_selectedStart == -1 && _length == 0 && string.IsNullOrEmpty (_selectedText)) { - return; - } + int PositionCursor (MouseEvent ev) + { + // We could also set the cursor position. + int x; + var pX = TextModel.GetColFromX (_text, ScrollOffset, ev.X); + if (_text.Count == 0) { + x = pX - ev.OfX; + } else { + x = pX; + } + return PositionCursor (x, false); + } - _selectedStart = -1; - _length = 0; - _selectedText = null; - _start = 0; - _length = 0; - SetNeedsDisplay (); + int PositionCursor (int x, bool getX = true) + { + var pX = x; + if (getX) { + pX = TextModel.GetColFromX (_text, ScrollOffset, x); } - - void SetSelectedStartSelectedLength () - { - if (SelectedStart > -1 && _cursorPosition < SelectedStart) { - _start = _cursorPosition; - } else { - _start = SelectedStart; - } + if (ScrollOffset + pX > _text.Count) { + _cursorPosition = _text.Count; + } else if (ScrollOffset + pX < ScrollOffset) { + _cursorPosition = 0; + } else { + _cursorPosition = ScrollOffset + pX; } - /// - /// Copy the selected text to the clipboard. - /// - public virtual void Copy () - { - if (Secret || _length == 0) - return; + return _cursorPosition; + } - Clipboard.Contents = SelectedText; + void PrepareSelection (int x, int direction = 0) + { + x = x + ScrollOffset < -1 ? 0 : x; + _selectedStart = _selectedStart == -1 && _text.Count > 0 && x >= 0 && x <= _text.Count ? x : _selectedStart; + if (_selectedStart > -1) { + SelectedLength = Math.Abs (x + direction <= _text.Count ? x + direction - _selectedStart : _text.Count - _selectedStart); + SetSelectedStartSelectedLength (); + if (_start > -1 && SelectedLength > 0) { + _selectedText = SelectedLength > 0 ? StringExtensions.ToString (_text.GetRange ( + _start < 0 ? 0 : _start, SelectedLength > _text.Count ? _text.Count : SelectedLength)) : ""; + if (ScrollOffset > _start) { + ScrollOffset = _start; + } + } else if (_start > -1 && SelectedLength == 0) { + _selectedText = null; + } + SetNeedsDisplay (); + } else if (SelectedLength > 0 || _selectedText != null) { + ClearAllSelection (); } + Adjust (); + } - /// - /// Cut the selected text to the clipboard. - /// - public virtual void Cut () - { - if (ReadOnly || Secret || _length == 0) - return; + /// + /// Clear the selected text. + /// + public void ClearAllSelection () + { + if (_selectedStart == -1 && SelectedLength == 0 && string.IsNullOrEmpty (_selectedText)) { + return; + } + + _selectedStart = -1; + SelectedLength = 0; + _selectedText = null; + _start = 0; + SelectedLength = 0; + SetNeedsDisplay (); + } - Clipboard.Contents = SelectedText; - var newText = DeleteSelectedText (); - Text = StringExtensions.ToString (newText); - Adjust (); + void SetSelectedStartSelectedLength () + { + if (SelectedStart > -1 && _cursorPosition < SelectedStart) { + _start = _cursorPosition; + } else { + _start = SelectedStart; } + } - List DeleteSelectedText () - { - SetSelectedStartSelectedLength (); - int selStart = SelectedStart > -1 ? _start : _cursorPosition; - var newText = StringExtensions.ToString (_text.GetRange (0, selStart)) + - StringExtensions.ToString (_text.GetRange (selStart + _length, _text.Count - (selStart + _length))); - - ClearAllSelection (); - _cursorPosition = selStart >= newText.GetRuneCount () ? newText.GetRuneCount () : selStart; - return newText.ToRuneList (); + /// + /// Copy the selected text to the clipboard. + /// + public virtual void Copy () + { + if (Secret || SelectedLength == 0) { + return; } - /// - /// Paste the selected text from the clipboard. - /// - public virtual void Paste () - { - if (ReadOnly || string.IsNullOrEmpty (Clipboard.Contents)) { - return; - } - - SetSelectedStartSelectedLength (); - int selStart = _start == -1 ? CursorPosition : _start; - string cbTxt = Clipboard.Contents.Split ("\n") [0] ?? ""; - Text = StringExtensions.ToString (_text.GetRange (0, selStart)) + - cbTxt + - StringExtensions.ToString (_text.GetRange (selStart + _length, _text.Count - (selStart + _length))); + Clipboard.Contents = SelectedText; + } - _cursorPosition = selStart + cbTxt.GetRuneCount (); - ClearAllSelection (); - SetNeedsDisplay (); - Adjust (); + /// + /// Cut the selected text to the clipboard. + /// + public virtual void Cut () + { + if (ReadOnly || Secret || SelectedLength == 0) { + return; } - /// - /// Virtual method that invoke the event if it's defined. - /// - /// The new text to be replaced. - /// Returns the - public virtual TextChangingEventArgs OnTextChanging (string newText) - { - var ev = new TextChangingEventArgs (newText); - TextChanging?.Invoke (this, ev); - return ev; - } + Clipboard.Contents = SelectedText; + var newText = DeleteSelectedText (); + Text = StringExtensions.ToString (newText); + Adjust (); + } - CursorVisibility _desiredCursorVisibility = CursorVisibility.Default; + List DeleteSelectedText () + { + SetSelectedStartSelectedLength (); + var selStart = SelectedStart > -1 ? _start : _cursorPosition; + var newText = StringExtensions.ToString (_text.GetRange (0, selStart)) + + StringExtensions.ToString (_text.GetRange (selStart + SelectedLength, _text.Count - (selStart + SelectedLength))); - /// - /// Get / Set the wished cursor when the field is focused - /// - public CursorVisibility DesiredCursorVisibility { - get => _desiredCursorVisibility; - set { - if ((_desiredCursorVisibility != value || _visibility != value) && HasFocus) { - Application.Driver.SetCursorVisibility (value); - } + ClearAllSelection (); + _cursorPosition = selStart >= newText.GetRuneCount () ? newText.GetRuneCount () : selStart; + return newText.ToRuneList (); + } - _desiredCursorVisibility = _visibility = value; - } - } + /// + /// Paste the selected text from the clipboard. + /// + public virtual void Paste () + { + if (ReadOnly || string.IsNullOrEmpty (Clipboard.Contents)) { + return; + } + + SetSelectedStartSelectedLength (); + var selStart = _start == -1 ? CursorPosition : _start; + var cbTxt = Clipboard.Contents.Split ("\n") [0] ?? ""; + Text = StringExtensions.ToString (_text.GetRange (0, selStart)) + + cbTxt + + StringExtensions.ToString (_text.GetRange (selStart + SelectedLength, _text.Count - (selStart + SelectedLength))); + + _cursorPosition = selStart + cbTxt.GetRuneCount (); + ClearAllSelection (); + SetNeedsDisplay (); + Adjust (); + } - /// - /// Inserts the given text at the current cursor position - /// exactly as if the user had just typed it - /// - /// Text to add - /// Use the previous cursor position. - public void InsertText (string toAdd, bool useOldCursorPos = true) - { - foreach (var ch in toAdd) { + /// + /// Virtual method that invoke the event if it's defined. + /// + /// The new text to be replaced. + /// Returns the + public virtual TextChangingEventArgs OnTextChanging (string newText) + { + var ev = new TextChangingEventArgs (newText); + TextChanging?.Invoke (this, ev); + return ev; + } - KeyCode key; + /// + /// Inserts the given text at the current cursor position + /// exactly as if the user had just typed it + /// + /// Text to add + /// Use the previous cursor position. + public void InsertText (string toAdd, bool useOldCursorPos = true) + { + foreach (var ch in toAdd) { - try { - key = (KeyCode)ch; - } catch (Exception) { + KeyCode key; - throw new ArgumentException ($"Cannot insert character '{ch}' because it does not map to a Key"); - } + try { + key = (KeyCode)ch; + } catch (Exception) { - InsertText (new Key () { KeyCode = key }, useOldCursorPos); + throw new ArgumentException ($"Cannot insert character '{ch}' because it does not map to a Key"); } - } - /// - /// Allows clearing the items updating the original text. - /// - public void ClearHistoryChanges () - { - _historyText.Clear (Text); + InsertText (new Key { KeyCode = key }, useOldCursorPos); } + } - /// - /// Returns if the current cursor position is - /// at the end of the . This includes when it is empty. - /// - /// - internal bool CursorIsAtEnd () - { - return CursorPosition == Text.Length; - } + /// + /// Allows clearing the items updating the original text. + /// + public void ClearHistoryChanges () => _historyText.Clear (Text); - /// - /// Returns if the current cursor position is - /// at the start of the . - /// - /// - internal bool CursorIsAtStart () - { - return CursorPosition <= 0; - } - } /// - /// Renders an overlay on another view at a given point that allows selecting - /// from a range of 'autocomplete' options. - /// An implementation on a TextField. + /// Returns if the current cursor position is + /// at the end of the . This includes when it is empty. /// - public class TextFieldAutocomplete : PopupAutocomplete { + /// + internal bool CursorIsAtEnd () => CursorPosition == Text.Length; - /// - protected override void DeleteTextBackwards () - { - ((TextField)HostControl).DeleteCharLeft (false); - } + /// + /// Returns if the current cursor position is + /// at the start of the . + /// + /// + internal bool CursorIsAtStart () => CursorPosition <= 0; +} - /// - protected override void InsertText (string accepted) - { - ((TextField)HostControl).InsertText (accepted, false); - } +/// +/// Renders an overlay on another view at a given point that allows selecting +/// from a range of 'autocomplete' options. +/// An implementation on a TextField. +/// +public class TextFieldAutocomplete : PopupAutocomplete { - /// - protected override void SetCursorPosition (int column) - { - ((TextField)HostControl).CursorPosition = column; - } - } + /// + protected override void DeleteTextBackwards () => ((TextField)HostControl).DeleteCharLeft (false); + + /// + protected override void InsertText (string accepted) => ((TextField)HostControl).InsertText (accepted, false); + + /// + protected override void SetCursorPosition (int column) => ((TextField)HostControl).CursorPosition = column; } \ No newline at end of file diff --git a/Terminal.Gui/Views/TextView.cs b/Terminal.Gui/Views/TextView.cs index 6ed4521d7f..1b42944756 100644 --- a/Terminal.Gui/Views/TextView.cs +++ b/Terminal.Gui/Views/TextView.cs @@ -13,10 +13,11 @@ using System.Threading; using Terminal.Gui.Resources; -namespace Terminal.Gui; +namespace Terminal.Gui; /// -/// Represents a single row/column within the . Includes the glyph and the foreground/background colors. +/// Represents a single row/column within the . Includes the glyph and the foreground/background +/// colors. /// [DebuggerDisplay ("{DebuggerDisplay}")] public class RuneCell : IEquatable { @@ -32,11 +33,19 @@ public class RuneCell : IEquatable { [JsonConverter (typeof (ColorSchemeJsonConverter))] public ColorScheme? ColorScheme { get; set; } + string DebuggerDisplay { + get { + var colorSchemeStr = ColorSchemeDebuggerDisplay (); + return $"U+{Rune.Value:X4} '{Rune.ToString ()}'; {colorSchemeStr}"; + } + } + /// Indicates whether the current object is equal to another object of the same type. /// An object to compare with this object. /// - /// if the current object is equal to the parameter; - /// otherwise, . + /// if the current object is equal to the parameter; + /// otherwise, . + /// public bool Equals (RuneCell? other) => other != null && Rune.Equals (other.Rune) && ColorScheme == other.ColorScheme; @@ -45,35 +54,37 @@ public bool Equals (RuneCell? other) => other != null && /// A string that represents the current object. public override string ToString () { - string colorSchemeStr = ColorSchemeDebuggerDisplay (); + var colorSchemeStr = ColorSchemeDebuggerDisplay (); return DebuggerDisplay; } string ColorSchemeDebuggerDisplay () { - string colorSchemeStr = "null"; + var colorSchemeStr = "null"; if (ColorScheme != null) { colorSchemeStr = $"Normal: {ColorScheme.Normal.Foreground},{ColorScheme.Normal.Background}; " + - $"Focus: {ColorScheme.Focus.Foreground},{ColorScheme.Focus.Background}; " + - $"HotNormal: {ColorScheme.HotNormal.Foreground},{ColorScheme.HotNormal.Background}; " + - $"HotFocus: {ColorScheme.HotFocus.Foreground},{ColorScheme.HotFocus.Background}; " + - $"Disabled: {ColorScheme.Disabled.Foreground},{ColorScheme.Disabled.Background}"; + $"Focus: {ColorScheme.Focus.Foreground},{ColorScheme.Focus.Background}; " + + $"HotNormal: {ColorScheme.HotNormal.Foreground},{ColorScheme.HotNormal.Background}; " + + $"HotFocus: {ColorScheme.HotFocus.Foreground},{ColorScheme.HotFocus.Background}; " + + $"Disabled: {ColorScheme.Disabled.Foreground},{ColorScheme.Disabled.Background}"; } return colorSchemeStr; } - - string DebuggerDisplay { - get { - string colorSchemeStr = ColorSchemeDebuggerDisplay (); - return $"U+{Rune.Value:X4} '{Rune.ToString ()}'; {colorSchemeStr}"; - } - } } class TextModel { List> _lines = new (); + (Point startPointToFind, Point currentPointToFind, bool found) _toFind; + + public string? FilePath { get; set; } + + /// + /// The number of text lines in the model + /// + public int Count => _lines.Count; + public event EventHandler? LinesLoaded; public bool LoadFile (string file) @@ -122,7 +133,7 @@ internal static List ToRuneCells (IEnumerable runes, ColorScheme // Splits a string into a List that contains a List for each line public static List> StringToLinesOfRuneCells (string content, ColorScheme? colorScheme = null) { - var cells = content.EnumerateRunes ().Select (x => new RuneCell () { Rune = x, ColorScheme = colorScheme }).ToList (); + var cells = content.EnumerateRunes ().Select (x => new RuneCell { Rune = x, ColorScheme = colorScheme }).ToList (); return SplitNewLines (cells); } @@ -131,7 +142,7 @@ static List> SplitNewLines (List cells) { var lines = new List> (); int start = 0, i = 0; - bool hasCR = false; + var hasCR = false; // ASCII code 13 = Carriage Return. // ASCII code 10 = Line Feed. for (; i < cells.Count; i++) { @@ -157,7 +168,7 @@ static List> SplitNewLines (List cells) void Append (List line) { - string str = StringExtensions.ToString (line.ToArray ()); + var str = StringExtensions.ToString (line.ToArray ()); _lines.Add (StringToRuneCells (str)); } @@ -171,7 +182,7 @@ public void LoadStream (Stream input) var buff = new BufferedStream (input); int v; var line = new List (); - bool wasNewLine = false; + var wasNewLine = false; while ((v = buff.ReadByte ()) != -1) { if (v == 13) { continue; @@ -230,7 +241,7 @@ void SetColorSchemes (ColorScheme? colorScheme) public override string ToString () { var sb = new StringBuilder (); - for (int i = 0; i < _lines.Count; i++) { + for (var i = 0; i < _lines.Count; i++) { sb.Append (ToString (_lines [i])); if (i + 1 < _lines.Count) { sb.AppendLine (); @@ -239,13 +250,6 @@ public override string ToString () return sb.ToString (); } - public string? FilePath { get; set; } - - /// - /// The number of text lines in the model - /// - public int Count => _lines.Count; - /// /// Returns the specified line as a List of Rune /// @@ -256,13 +260,11 @@ public List GetLine (int line) if (_lines.Count > 0) { if (line < Count) { return _lines [line]; - } else { - return _lines [Count - 1]; } - } else { - _lines.Add (new List ()); - return _lines [0]; + return _lines [Count - 1]; } + _lines.Add (new List ()); + return _lines [0]; } public List> GetAllLines () => _lines; @@ -305,12 +307,12 @@ public void ReplaceLine (int pos, List runes) /// The tab width. public int GetMaxVisibleLine (int first, int last, int tabWidth) { - int maxLength = 0; + var maxLength = 0; last = last < _lines.Count ? last : _lines.Count; - for (int i = first; i < last; i++) { + for (var i = first; i < last; i++) { var line = GetLine (i); - int tabSum = line.Sum (c => c.Rune.Value == '\t' ? Math.Max (tabWidth - 1, 0) : 0); - int l = line.Count + tabSum; + var tabSum = line.Sum (c => c.Rune.Value == '\t' ? Math.Max (tabWidth - 1, 0) : 0); + var l = line.Count + tabSum; if (l > maxLength) { maxLength = l; } @@ -343,9 +345,9 @@ internal static int GetColFromX (List t, int start, int x, int tabWidth = if (x < 0) { return x; } - int size = start; - int pX = x + start; - for (int i = start; i < t.Count; i++) { + var size = start; + var pX = x + start; + for (var i = start; i < t.Count; i++) { var r = t [i]; size += r.GetColumns (); if (r.Value == '\t') { @@ -358,8 +360,11 @@ internal static int GetColFromX (List t, int start, int x, int tabWidth = return t.Count - start; } - internal static (int size, int length) DisplaySize (List t, int start = -1, int end = -1, - bool checkNextRune = true, int tabWidth = 0) + internal static (int size, int length) DisplaySize (List t, + int start = -1, + int end = -1, + bool checkNextRune = true, + int tabWidth = 0) { var runes = new List (); foreach (var cell in t) { @@ -369,16 +374,19 @@ internal static (int size, int length) DisplaySize (List t, int start } // Returns the size and length in a range of the string. - internal static (int size, int length) DisplaySize (List t, int start = -1, int end = -1, - bool checkNextRune = true, int tabWidth = 0) + internal static (int size, int length) DisplaySize (List t, + int start = -1, + int end = -1, + bool checkNextRune = true, + int tabWidth = 0) { if (t == null || t.Count == 0) { return (0, 0); } - int size = 0; - int len = 0; - int tcount = end == -1 ? t.Count : end > t.Count ? t.Count : end; - int i = start == -1 ? 0 : start; + var size = 0; + var len = 0; + var tcount = end == -1 ? t.Count : end > t.Count ? t.Count : end; + var i = start == -1 ? 0 : start; for (; i < tcount; i++) { var rune = t [i]; size += rune.GetColumns (); @@ -387,8 +395,7 @@ internal static (int size, int length) DisplaySize (List t, int start = -1 size += tabWidth + 1; len += tabWidth - 1; } - if (checkNextRune && i == tcount - 1 && t.Count > tcount - && IsWideRune (t [i + 1], tabWidth, out int s, out int l)) { + if (checkNextRune && i == tcount - 1 && t.Count > tcount && IsWideRune (t [i + 1], tabWidth, out var s, out var l)) { size += s; len += l; } @@ -424,11 +431,11 @@ internal static int CalculateLeftColumn (List t, int start, int end, int w if (t == null || t.Count == 0) { return 0; } - int size = 0; - int tcount = end > t.Count - 1 ? t.Count - 1 : end; - int col = 0; + var size = 0; + var tcount = end > t.Count - 1 ? t.Count - 1 : end; + var col = 0; - for (int i = tcount; i >= 0; i--) { + for (var i = tcount; i >= 0; i--) { var rune = t [i]; size += rune.GetColumns (); if (rune.Value == '\t') { @@ -439,7 +446,8 @@ internal static int CalculateLeftColumn (List t, int start, int end, int w col++; } break; - } else if (end < t.Count && col > 0 && start < end && col == start || end - col == width - 1) { + } + if (end < t.Count && col > 0 && start < end && col == start || end - col == width - 1) { break; } col = i; @@ -448,8 +456,6 @@ internal static int CalculateLeftColumn (List t, int start, int end, int w return col; } - (Point startPointToFind, Point currentPointToFind, bool found) _toFind; - internal (Point current, bool found) FindNextText (string text, out bool gaveFullTurn, bool matchCase = false, bool matchWholeWord = false) { if (text == null || _lines.Count == 0) { @@ -479,7 +485,7 @@ internal static int CalculateLeftColumn (List t, int start, int end, int w if (_toFind.found) { _toFind.currentPointToFind.X++; } - int linesCount = _toFind.currentPointToFind.IsEmpty ? _lines.Count - 1 : _toFind.currentPointToFind.Y; + var linesCount = _toFind.currentPointToFind.IsEmpty ? _lines.Count - 1 : _toFind.currentPointToFind.Y; var foundPos = GetFoundPreviousTextPoint (text, linesCount, matchCase, matchWholeWord, _toFind.currentPointToFind); if (!foundPos.found && _toFind.currentPointToFind != _toFind.startPointToFind) { foundPos = GetFoundPreviousTextPoint (text, _lines.Count - 1, matchCase, matchWholeWord, @@ -492,14 +498,14 @@ internal static int CalculateLeftColumn (List t, int start, int end, int w internal (Point current, bool found) ReplaceAllText (string text, bool matchCase = false, bool matchWholeWord = false, string? textToReplace = null) { - bool found = false; + var found = false; var pos = Point.Empty; - for (int i = 0; i < _lines.Count; i++) { + for (var i = 0; i < _lines.Count; i++) { var x = _lines [i]; - string txt = GetText (x); - string matchText = !matchCase ? text.ToUpper () : text; - int col = txt.IndexOf (matchText); + var txt = GetText (x); + var matchText = !matchCase ? text.ToUpper () : text; + var col = txt.IndexOf (matchText); while (col > -1) { if (matchWholeWord && !MatchWholeWord (txt, matchText, col)) { if (col + 1 > txt.Length) { @@ -527,7 +533,7 @@ internal static int CalculateLeftColumn (List t, int start, int end, int w string GetText (List x) { - string txt = ToString (x); + var txt = ToString (x); if (!matchCase) { txt = txt.ToUpper (); } @@ -539,19 +545,19 @@ string GetText (List x) string ReplaceText (List source, string textToReplace, string matchText, int col) { - string origTxt = ToString (source); - (int _, int len) = DisplaySize (source, 0, col, false); - (int _, int len2) = DisplaySize (source, col, col + matchText.Length, false); - (int _, int len3) = DisplaySize (source, col + matchText.Length, origTxt.GetRuneCount (), false); + var origTxt = ToString (source); + (var _, var len) = DisplaySize (source, 0, col, false); + (var _, var len2) = DisplaySize (source, col, col + matchText.Length, false); + (var _, var len3) = DisplaySize (source, col + matchText.Length, origTxt.GetRuneCount (), false); return origTxt [..len] + - textToReplace + - origTxt.Substring (len + len2, len3); + textToReplace + + origTxt.Substring (len + len2, len3); } bool ApplyToFind ((Point current, bool found) foundPos) { - bool gaveFullTurn = false; + var gaveFullTurn = false; if (foundPos.found) { _toFind.currentPointToFind = foundPos.current; if (_toFind.found && _toFind.currentPointToFind == _toFind.startPointToFind) { @@ -568,22 +574,21 @@ bool ApplyToFind ((Point current, bool found) foundPos) (Point current, bool found) GetFoundNextTextPoint (string text, int linesCount, bool matchCase, bool matchWholeWord, Point start) { - for (int i = start.Y; i < linesCount; i++) { + for (var i = start.Y; i < linesCount; i++) { var x = _lines [i]; - string txt = ToString (x); + var txt = ToString (x); if (!matchCase) { txt = txt.ToUpper (); } - string matchText = !matchCase ? text.ToUpper () : text; - int col = txt.IndexOf (matchText, Math.Min (start.X, txt.Length)); + var matchText = !matchCase ? text.ToUpper () : text; + var col = txt.IndexOf (matchText, Math.Min (start.X, txt.Length)); if (col > -1 && matchWholeWord && !MatchWholeWord (txt, matchText, col)) { continue; } - if (col > -1 && (i == start.Y && col >= start.X - || i > start.Y) - && txt.Contains (matchText)) { + if (col > -1 && (i == start.Y && col >= start.X || i > start.Y) && txt.Contains (matchText)) { return (new Point (col, i), true); - } else if (col == -1 && start.X > 0) { + } + if (col == -1 && start.X > 0) { start.X = 0; } } @@ -593,23 +598,21 @@ bool ApplyToFind ((Point current, bool found) foundPos) (Point current, bool found) GetFoundPreviousTextPoint (string text, int linesCount, bool matchCase, bool matchWholeWord, Point start) { - for (int i = linesCount; i >= 0; i--) { + for (var i = linesCount; i >= 0; i--) { var x = _lines [i]; - string txt = ToString (x); + var txt = ToString (x); if (!matchCase) { txt = txt.ToUpper (); } if (start.Y != i) { start.X = Math.Max (x.Count - 1, 0); } - string matchText = !matchCase ? text.ToUpper () : text; - int col = txt.LastIndexOf (matchText, _toFind.found ? start.X - 1 : start.X); + var matchText = !matchCase ? text.ToUpper () : text; + var col = txt.LastIndexOf (matchText, _toFind.found ? start.X - 1 : start.X); if (col > -1 && matchWholeWord && !MatchWholeWord (txt, matchText, col)) { continue; } - if (col > -1 && (i <= linesCount && col <= start.X - || i < start.Y) - && txt.Contains (matchText)) { + if (col > -1 && (i <= linesCount && col <= start.X || i < start.Y) && txt.Contains (matchText)) { return (new Point (col, i), true); } } @@ -623,12 +626,11 @@ bool MatchWholeWord (string source, string matchText, int index = 0) return false; } - string txt = matchText.Trim (); - int start = index > 0 ? index - 1 : 0; - int end = index + txt.Length; + var txt = matchText.Trim (); + var start = index > 0 ? index - 1 : 0; + var end = index + txt.Length; - if ((start == 0 || Rune.IsWhiteSpace ((Rune)source [start])) - && (end == source.Length || Rune.IsWhiteSpace ((Rune)source [end]))) { + if ((start == 0 || Rune.IsWhiteSpace ((Rune)source [start])) && (end == source.Length || Rune.IsWhiteSpace ((Rune)source [end]))) { return true; } @@ -650,9 +652,8 @@ RuneCell RuneAt (int col, int row) var line = GetLine (row); if (line.Count > 0) { return line [col > line.Count - 1 ? line.Count - 1 : col]; - } else { - return default!; } + return default!; } bool MoveNext (ref int col, ref int row, out Rune rune) @@ -661,12 +662,12 @@ bool MoveNext (ref int col, ref int row, out Rune rune) if (col + 1 < line.Count) { col++; rune = line [col].Rune; - if (col + 1 == line.Count && !Rune.IsLetterOrDigit (rune) - && !Rune.IsWhiteSpace (line [col - 1].Rune)) { + if (col + 1 == line.Count && !Rune.IsLetterOrDigit (rune) && !Rune.IsWhiteSpace (line [col - 1].Rune)) { col++; } return true; - } else if (col + 1 == line.Count) { + } + if (col + 1 == line.Count) { col++; } while (row + 1 < Count) { @@ -708,23 +709,18 @@ bool MovePrev (ref int col, ref int row, out Rune rune) return false; } - enum RuneType { - IsSymbol, - IsWhiteSpace, - IsLetterOrDigit, - IsPunctuation, - IsUnknow - } - RuneType GetRuneType (Rune rune) { if (Rune.IsSymbol (rune)) { return RuneType.IsSymbol; - } else if (Rune.IsWhiteSpace (rune)) { + } + if (Rune.IsWhiteSpace (rune)) { return RuneType.IsWhiteSpace; - } else if (Rune.IsLetterOrDigit (rune)) { + } + if (Rune.IsLetterOrDigit (rune)) { return RuneType.IsLetterOrDigit; - } else if (Rune.IsPunctuation (rune)) { + } + if (Rune.IsPunctuation (rune)) { return RuneType.IsPunctuation; } return RuneType.IsUnknow; @@ -742,12 +738,12 @@ bool IsSameRuneType (Rune newRune, RuneType runeType) return null; } - int col = fromCol; - int row = fromRow; + var col = fromCol; + var row = fromRow; try { var rune = RuneAt (col, row).Rune; var runeType = GetRuneType (rune); - int lastValidCol = IsSameRuneType (rune, runeType) && (Rune.IsLetterOrDigit (rune) || Rune.IsPunctuation (rune) || Rune.IsSymbol (rune)) ? col : -1; + var lastValidCol = IsSameRuneType (rune, runeType) && (Rune.IsLetterOrDigit (rune) || Rune.IsPunctuation (rune) || Rune.IsSymbol (rune)) ? col : -1; void ProcMoveNext (ref int nCol, ref int nRow, Rune nRune) { @@ -771,7 +767,8 @@ void ProcMoveNext (ref int nCol, ref int nRow, Rune nRune) if (nRow != fromRow) { break; } - lastValidCol = IsSameRuneType (nRune, runeType) && Rune.IsLetterOrDigit (nRune) || Rune.IsPunctuation (nRune) || Rune.IsSymbol (nRune) ? nCol : lastValidCol; + lastValidCol = IsSameRuneType (nRune, runeType) && Rune.IsLetterOrDigit (nRune) || Rune.IsPunctuation (nRune) || Rune.IsSymbol (nRune) ? nCol + : lastValidCol; } if (lastValidCol > -1) { nCol = lastValidCol; @@ -814,8 +811,8 @@ void ProcMoveNext (ref int nCol, ref int nRow, Rune nRune) return null; } - int col = Math.Max (fromCol - 1, 0); - int row = fromRow; + var col = Math.Max (fromCol - 1, 0); + var row = fromRow; try { var cell = RuneAt (col, row); Rune rune; @@ -824,16 +821,16 @@ void ProcMoveNext (ref int nCol, ref int nRow, Rune nRune) } else { if (col > 0) { return (col, row); - } else if (col == 0 && row > 0) { + } + if (col == 0 && row > 0) { row--; var line = GetLine (row); return (line.Count, row); - } else { - return null; } + return null; } var runeType = GetRuneType (rune); - int lastValidCol = IsSameRuneType (rune, runeType) && (Rune.IsLetterOrDigit (rune) || Rune.IsPunctuation (rune) || Rune.IsSymbol (rune)) ? col : -1; + var lastValidCol = IsSameRuneType (rune, runeType) && (Rune.IsLetterOrDigit (rune) || Rune.IsPunctuation (rune) || Rune.IsSymbol (rune)) ? col : -1; void ProcMovePrev (ref int nCol, ref int nRow, Rune nRune) { @@ -860,7 +857,8 @@ void ProcMovePrev (ref int nCol, ref int nRow, Rune nRune) if (nRow != fromRow) { break; } - lastValidCol = IsSameRuneType (nRune, runeType) && Rune.IsLetterOrDigit (nRune) || Rune.IsPunctuation (nRune) || Rune.IsSymbol (nRune) ? nCol : lastValidCol; + lastValidCol = IsSameRuneType (nRune, runeType) && Rune.IsLetterOrDigit (nRune) || Rune.IsPunctuation (nRune) || Rune.IsSymbol (nRune) ? nCol + : lastValidCol; } if (lastValidCol > -1) { nCol = lastValidCol; @@ -921,7 +919,7 @@ public static List ToRuneCellList (string str, ColorScheme? colorSchem /// public static string ToString (IEnumerable cells) { - string str = string.Empty; + var str = string.Empty; foreach (var cell in cells) { str += cell.Rune.ToString (); @@ -929,6 +927,14 @@ public static string ToString (IEnumerable cells) return str; } + + enum RuneType { + IsSymbol, + IsWhiteSpace, + IsLetterOrDigit, + IsPunctuation, + IsUnknow + } } partial class HistoryText { @@ -939,7 +945,7 @@ public enum LineStatus { Added } - List _historyTextItems = new (); + readonly List _historyTextItems = new (); int _idxHistoryText = -1; string? _originalText; @@ -951,12 +957,10 @@ public enum LineStatus { public void Add (List> lines, Point curPos, LineStatus lineStatus = LineStatus.Original) { - if (lineStatus == LineStatus.Original && _historyTextItems.Count > 0 - && _historyTextItems.Last ().LineStatus == LineStatus.Original) { + if (lineStatus == LineStatus.Original && _historyTextItems.Count > 0 && _historyTextItems.Last ().LineStatus == LineStatus.Original) { return; } - if (lineStatus == LineStatus.Replaced && _historyTextItems.Count > 0 - && _historyTextItems.Last ().LineStatus == LineStatus.Replaced) { + if (lineStatus == LineStatus.Replaced && _historyTextItems.Count > 0 && _historyTextItems.Last ().LineStatus == LineStatus.Replaced) { return; } @@ -1018,15 +1022,15 @@ public void Redo () void ProcessChanges (ref HistoryTextItem historyTextItem) { if (historyTextItem.IsUndoing) { - if (_idxHistoryText - 1 > -1 && (_historyTextItems [_idxHistoryText - 1].LineStatus == LineStatus.Added - || _historyTextItems [_idxHistoryText - 1].LineStatus == LineStatus.Removed - || historyTextItem.LineStatus == LineStatus.Replaced && - _historyTextItems [_idxHistoryText - 1].LineStatus == LineStatus.Original)) { + if (_idxHistoryText - 1 > -1 && + (_historyTextItems [_idxHistoryText - 1].LineStatus == LineStatus.Added || + _historyTextItems [_idxHistoryText - 1].LineStatus == LineStatus.Removed || + historyTextItem.LineStatus == LineStatus.Replaced && + _historyTextItems [_idxHistoryText - 1].LineStatus == LineStatus.Original)) { _idxHistoryText--; - while (_historyTextItems [_idxHistoryText].LineStatus == LineStatus.Added - && _historyTextItems [_idxHistoryText - 1].LineStatus == LineStatus.Removed) { + while (_historyTextItems [_idxHistoryText].LineStatus == LineStatus.Added && _historyTextItems [_idxHistoryText - 1].LineStatus == LineStatus.Removed) { _idxHistoryText--; } @@ -1039,12 +1043,12 @@ void ProcessChanges (ref HistoryTextItem historyTextItem) historyTextItem.RemovedOnAdded = new HistoryTextItem (_historyTextItems [_idxHistoryText + 1]); } - if (historyTextItem.LineStatus == LineStatus.Added && _historyTextItems [_idxHistoryText - 1].LineStatus == LineStatus.Original - || historyTextItem.LineStatus == LineStatus.Removed && _historyTextItems [_idxHistoryText - 1].LineStatus == LineStatus.Original - || historyTextItem.LineStatus == LineStatus.Added && _historyTextItems [_idxHistoryText - 1].LineStatus == LineStatus.Removed) { + if (historyTextItem.LineStatus == LineStatus.Added && _historyTextItems [_idxHistoryText - 1].LineStatus == LineStatus.Original || + historyTextItem.LineStatus == LineStatus.Removed && _historyTextItems [_idxHistoryText - 1].LineStatus == LineStatus.Original || + historyTextItem.LineStatus == LineStatus.Added && _historyTextItems [_idxHistoryText - 1].LineStatus == LineStatus.Removed) { - if (!historyTextItem.Lines [0].SequenceEqual (_historyTextItems [_idxHistoryText - 1].Lines [0]) - && historyTextItem.CursorPosition == _historyTextItems [_idxHistoryText - 1].CursorPosition) { + if (!historyTextItem.Lines [0].SequenceEqual (_historyTextItems [_idxHistoryText - 1].Lines [0]) && + historyTextItem.CursorPosition == _historyTextItems [_idxHistoryText - 1].CursorPosition) { historyTextItem.Lines [0] = new List (_historyTextItems [_idxHistoryText - 1].Lines [0]); } if (historyTextItem.LineStatus == LineStatus.Added && _historyTextItems [_idxHistoryText - 1].LineStatus == LineStatus.Removed) { @@ -1057,15 +1061,15 @@ void ProcessChanges (ref HistoryTextItem historyTextItem) } OnChangeText (historyTextItem); - while (_historyTextItems [_idxHistoryText].LineStatus == LineStatus.Removed - || _historyTextItems [_idxHistoryText].LineStatus == LineStatus.Added) { + while (_historyTextItems [_idxHistoryText].LineStatus == LineStatus.Removed || _historyTextItems [_idxHistoryText].LineStatus == LineStatus.Added) { _idxHistoryText--; } } else if (!historyTextItem.IsUndoing) { - if (_idxHistoryText + 1 < _historyTextItems.Count && (historyTextItem.LineStatus == LineStatus.Original - || _historyTextItems [_idxHistoryText + 1].LineStatus == LineStatus.Added - || _historyTextItems [_idxHistoryText + 1].LineStatus == LineStatus.Removed)) { + if (_idxHistoryText + 1 < _historyTextItems.Count && + (historyTextItem.LineStatus == LineStatus.Original || + _historyTextItems [_idxHistoryText + 1].LineStatus == LineStatus.Added || + _historyTextItems [_idxHistoryText + 1].LineStatus == LineStatus.Removed)) { _idxHistoryText++; historyTextItem = new HistoryTextItem (_historyTextItems [_idxHistoryText]); @@ -1077,12 +1081,11 @@ void ProcessChanges (ref HistoryTextItem historyTextItem) historyTextItem.RemovedOnAdded = new HistoryTextItem (_historyTextItems [_idxHistoryText - 1]); } - if (historyTextItem.LineStatus == LineStatus.Removed && _historyTextItems [_idxHistoryText + 1].LineStatus == LineStatus.Replaced - || historyTextItem.LineStatus == LineStatus.Removed && _historyTextItems [_idxHistoryText + 1].LineStatus == LineStatus.Original - || historyTextItem.LineStatus == LineStatus.Added && _historyTextItems [_idxHistoryText + 1].LineStatus == LineStatus.Replaced) { + if (historyTextItem.LineStatus == LineStatus.Removed && _historyTextItems [_idxHistoryText + 1].LineStatus == LineStatus.Replaced || + historyTextItem.LineStatus == LineStatus.Removed && _historyTextItems [_idxHistoryText + 1].LineStatus == LineStatus.Original || + historyTextItem.LineStatus == LineStatus.Added && _historyTextItems [_idxHistoryText + 1].LineStatus == LineStatus.Replaced) { - if (historyTextItem.LineStatus == LineStatus.Removed - && !historyTextItem.Lines [0].SequenceEqual (_historyTextItems [_idxHistoryText + 1].Lines [0])) { + if (historyTextItem.LineStatus == LineStatus.Removed && !historyTextItem.Lines [0].SequenceEqual (_historyTextItems [_idxHistoryText + 1].Lines [0])) { historyTextItem.Lines [0] = new List (_historyTextItems [_idxHistoryText + 1].Lines [0]); } historyTextItem.FinalCursorPosition = _historyTextItems [_idxHistoryText + 1].CursorPosition; @@ -1091,8 +1094,7 @@ void ProcessChanges (ref HistoryTextItem historyTextItem) } OnChangeText (historyTextItem); - while (_historyTextItems [_idxHistoryText].LineStatus == LineStatus.Removed - || _historyTextItems [_idxHistoryText].LineStatus == LineStatus.Added) { + while (_historyTextItems [_idxHistoryText].LineStatus == LineStatus.Removed || _historyTextItems [_idxHistoryText].LineStatus == LineStatus.Added) { _idxHistoryText++; } @@ -1113,46 +1115,49 @@ public void Clear (string text) } class WordWrapManager { - class WrappedLine { - public int ModelLine; - public int Row; - public int RowIndex; - public int ColWidth; - } - - List _wrappedModelLines = new (); int _frameWidth; bool _isWrapModelRefreshing; - public TextModel Model { get; private set; } + List _wrappedModelLines = new (); public WordWrapManager (TextModel model) => Model = model; - public TextModel WrapModel (int width, out int nRow, out int nCol, out int nStartRow, out int nStartCol, - int row = 0, int col = 0, int startRow = 0, int startCol = 0, int tabWidth = 0, bool preserveTrailingSpaces = true) + public TextModel Model { get; private set; } + + public TextModel WrapModel (int width, + out int nRow, + out int nCol, + out int nStartRow, + out int nStartCol, + int row = 0, + int col = 0, + int startRow = 0, + int startCol = 0, + int tabWidth = 0, + bool preserveTrailingSpaces = true) { _frameWidth = width; - int modelRow = _isWrapModelRefreshing ? row : GetModelLineFromWrappedLines (row); - int modelCol = _isWrapModelRefreshing ? col : GetModelColFromWrappedLines (row, col); - int modelStartRow = _isWrapModelRefreshing ? startRow : GetModelLineFromWrappedLines (startRow); - int modelStartCol = _isWrapModelRefreshing ? startCol : GetModelColFromWrappedLines (startRow, startCol); + var modelRow = _isWrapModelRefreshing ? row : GetModelLineFromWrappedLines (row); + var modelCol = _isWrapModelRefreshing ? col : GetModelColFromWrappedLines (row, col); + var modelStartRow = _isWrapModelRefreshing ? startRow : GetModelLineFromWrappedLines (startRow); + var modelStartCol = _isWrapModelRefreshing ? startCol : GetModelColFromWrappedLines (startRow, startCol); var wrappedModel = new TextModel (); - int lines = 0; + var lines = 0; nRow = 0; nCol = 0; nStartRow = 0; nStartCol = 0; - bool isRowAndColSetted = row == 0 && col == 0; - bool isStartRowAndColSetted = startRow == 0 && startCol == 0; + var isRowAndColSetted = row == 0 && col == 0; + var isStartRowAndColSetted = startRow == 0 && startCol == 0; var wModelLines = new List (); - for (int i = 0; i < Model.Count; i++) { + for (var i = 0; i < Model.Count; i++) { var line = Model.GetLine (i); var wrappedLines = ToListRune ( TextFormatter.Format (TextModel.ToString (line), width, TextAlignment.Left, true, preserveTrailingSpaces, tabWidth)); - int sumColWidth = 0; - for (int j = 0; j < wrappedLines.Count; j++) { + var sumColWidth = 0; + for (var j = 0; j < wrappedLines.Count; j++) { var wrapLine = wrappedLines [j]; if (!isRowAndColSetted && modelRow == i) { if (nCol + wrapLine.Count <= modelCol) { @@ -1166,7 +1171,7 @@ public TextModel WrapModel (int width, out int nRow, out int nCol, out int nStar isRowAndColSetted = true; } } else { - int offset = nCol + wrapLine.Count - modelCol; + var offset = nCol + wrapLine.Count - modelCol; nCol = wrapLine.Count - offset; nRow = lines; isRowAndColSetted = true; @@ -1184,18 +1189,18 @@ public TextModel WrapModel (int width, out int nRow, out int nCol, out int nStar isStartRowAndColSetted = true; } } else { - int offset = nStartCol + wrapLine.Count - modelStartCol; + var offset = nStartCol + wrapLine.Count - modelStartCol; nStartCol = wrapLine.Count - offset; nStartRow = lines; isStartRowAndColSetted = true; } } - for (int k = j; k < wrapLine.Count; k++) { + for (var k = j; k < wrapLine.Count; k++) { wrapLine [k].ColorScheme = line [k].ColorScheme; } wrappedModel.AddLine (lines, wrapLine); sumColWidth += wrapLine.Count; - var wrappedLine = new WrappedLine () { + var wrappedLine = new WrappedLine { ModelLine = i, Row = lines, RowIndex = j, @@ -1214,7 +1219,7 @@ public List> ToListRune (List textList) { var runesList = new List> (); - foreach (string text in textList) { + foreach (var text in textList) { runesList.Add (TextModel.ToRuneCellList (text)); } @@ -1231,11 +1236,11 @@ public int GetModelColFromWrappedLines (int line, int col) return 0; } - int modelLine = GetModelLineFromWrappedLines (line); - int firstLine = _wrappedModelLines.IndexOf (r => r.ModelLine == modelLine); - int modelCol = 0; + var modelLine = GetModelLineFromWrappedLines (line); + var firstLine = _wrappedModelLines.IndexOf (r => r.ModelLine == modelLine); + var modelCol = 0; - for (int i = firstLine; i <= Math.Min (line, _wrappedModelLines!.Count - 1); i++) { + for (var i = firstLine; i <= Math.Min (line, _wrappedModelLines!.Count - 1); i++) { var wLine = _wrappedModelLines [i]; if (i < line) { @@ -1252,15 +1257,15 @@ public int GetModelColFromWrappedLines (int line, int col) public void AddLine (int row, int col) { - int modelRow = GetModelLineFromWrappedLines (row); - int modelCol = GetModelColFromWrappedLines (row, col); + var modelRow = GetModelLineFromWrappedLines (row); + var modelCol = GetModelColFromWrappedLines (row, col); var line = GetCurrentLine (modelRow); - int restCount = line.Count - modelCol; + var restCount = line.Count - modelCol; var rest = line.GetRange (modelCol, restCount); line.RemoveRange (modelCol, restCount); Model.AddLine (modelRow + 1, rest); _isWrapModelRefreshing = true; - WrapModel (_frameWidth, out _, out _, out _, out _, modelRow + 1, 0); + WrapModel (_frameWidth, out _, out _, out _, out _, modelRow + 1); _isWrapModelRefreshing = false; } @@ -1270,16 +1275,15 @@ public bool Insert (int row, int col, RuneCell cell) line.Insert (GetModelColFromWrappedLines (row, col), cell); if (line.Count > _frameWidth) { return true; - } else { - return false; } + return false; } public bool RemoveAt (int row, int col) { - int modelRow = GetModelLineFromWrappedLines (row); + var modelRow = GetModelLineFromWrappedLines (row); var line = GetCurrentLine (modelRow); - int modelCol = GetModelColFromWrappedLines (row, col); + var modelCol = GetModelColFromWrappedLines (row, col); if (modelCol > line.Count) { Model.RemoveLine (modelRow); @@ -1289,8 +1293,7 @@ public bool RemoveAt (int row, int col) if (modelCol < line.Count) { line.RemoveAt (modelCol); } - if (line.Count > _frameWidth || row + 1 < _wrappedModelLines.Count - && _wrappedModelLines [row + 1].ModelLine == modelRow) { + if (line.Count > _frameWidth || row + 1 < _wrappedModelLines.Count && _wrappedModelLines [row + 1].ModelLine == modelRow) { return true; } @@ -1300,18 +1303,20 @@ public bool RemoveAt (int row, int col) public bool RemoveLine (int row, int col, out bool lineRemoved, bool forward = true) { lineRemoved = false; - int modelRow = GetModelLineFromWrappedLines (row); + var modelRow = GetModelLineFromWrappedLines (row); var line = GetCurrentLine (modelRow); - int modelCol = GetModelColFromWrappedLines (row, col); + var modelCol = GetModelColFromWrappedLines (row, col); if (modelCol == 0 && line.Count == 0) { Model.RemoveLine (modelRow); return false; - } else if (modelCol < line.Count) { + } + if (modelCol < line.Count) { if (forward) { line.RemoveAt (modelCol); return true; - } else if (modelCol - 1 > -1) { + } + if (modelCol - 1 > -1) { line.RemoveAt (modelCol - 1); return true; } @@ -1346,9 +1351,9 @@ public bool RemoveLine (int row, int col, out bool lineRemoved, bool forward = t public bool RemoveRange (int row, int index, int count) { - int modelRow = GetModelLineFromWrappedLines (row); + var modelRow = GetModelLineFromWrappedLines (row); var line = GetCurrentLine (modelRow); - int modelCol = GetModelColFromWrappedLines (row, index); + var modelCol = GetModelColFromWrappedLines (row, index); try { line.RemoveRange (modelCol, count); @@ -1359,8 +1364,16 @@ public bool RemoveRange (int row, int index, int count) return true; } - public void UpdateModel (TextModel model, out int nRow, out int nCol, out int nStartRow, out int nStartCol, - int row, int col, int startRow, int startCol, bool preserveTrailingSpaces) + public void UpdateModel (TextModel model, + out int nRow, + out int nCol, + out int nStartRow, + out int nStartCol, + int row, + int col, + int startRow, + int startCol, + bool preserveTrailingSpaces) { _isWrapModelRefreshing = true; Model = model; @@ -1375,11 +1388,11 @@ public int GetWrappedLineColWidth (int line, int col, WordWrapManager wrapManage } var wModelLines = wrapManager._wrappedModelLines; - int modelLine = GetModelLineFromWrappedLines (line); - int firstLine = _wrappedModelLines.IndexOf (r => r.ModelLine == modelLine); - int modelCol = 0; - int colWidthOffset = 0; - int i = firstLine; + var modelLine = GetModelLineFromWrappedLines (line); + var firstLine = _wrappedModelLines.IndexOf (r => r.ModelLine == modelLine); + var modelCol = 0; + var colWidthOffset = 0; + var i = firstLine; while (modelCol < col) { var wLine = _wrappedModelLines! [i]; @@ -1399,687 +1412,375 @@ public int GetWrappedLineColWidth (int line, int col, WordWrapManager wrapManage return modelCol - colWidthOffset; } + + class WrappedLine { + public int ColWidth; + public int ModelLine; + public int Row; + public int RowIndex; + } } /// -/// Multi-line text editing . +/// Multi-line text editing . /// /// -/// -/// provides a multi-line text editor. Users interact -/// with it with the standard Windows, Mac, and Linux (Emacs) commands. -/// -/// -/// -/// Shortcut -/// Action performed -/// -/// -/// Left cursor, Control-b -/// -/// Moves the editing point left. -/// -/// -/// -/// Right cursor, Control-f -/// -/// Moves the editing point right. -/// -/// -/// -/// Alt-b -/// -/// Moves one word back. -/// -/// -/// -/// Alt-f -/// -/// Moves one word forward. -/// -/// -/// -/// Up cursor, Control-p -/// -/// Moves the editing point one line up. -/// -/// -/// -/// Down cursor, Control-n -/// -/// Moves the editing point one line down -/// -/// -/// -/// Home key, Control-a -/// -/// Moves the cursor to the beginning of the line. -/// -/// -/// -/// End key, Control-e -/// -/// Moves the cursor to the end of the line. -/// -/// -/// -/// Control-Home -/// -/// Scrolls to the first line and moves the cursor there. -/// -/// -/// -/// Control-End -/// -/// Scrolls to the last line and moves the cursor there. -/// -/// -/// -/// Delete, Control-d -/// -/// Deletes the character in front of the cursor. -/// -/// -/// -/// Backspace -/// -/// Deletes the character behind the cursor. -/// -/// -/// -/// Control-k -/// -/// Deletes the text until the end of the line and replaces the kill buffer -/// with the deleted text. You can paste this text in a different place by -/// using Control-y. -/// -/// -/// -/// Control-y -/// -/// Pastes the content of the kill ring into the current position. -/// -/// -/// -/// Alt-d -/// -/// Deletes the word above the cursor and adds it to the kill ring. You -/// can paste the contents of the kill ring with Control-y. -/// -/// -/// -/// Control-q -/// -/// Quotes the next input character, to prevent the normal processing of -/// key handling to take place. -/// -/// -/// +/// +/// provides a multi-line text editor. Users interact +/// with it with the standard Windows, Mac, and Linux (Emacs) commands. +/// +/// +/// +/// Shortcut +/// Action performed +/// +/// +/// Left cursor, Control-b +/// +/// Moves the editing point left. +/// +/// +/// +/// Right cursor, Control-f +/// +/// Moves the editing point right. +/// +/// +/// +/// Alt-b +/// +/// Moves one word back. +/// +/// +/// +/// Alt-f +/// +/// Moves one word forward. +/// +/// +/// +/// Up cursor, Control-p +/// +/// Moves the editing point one line up. +/// +/// +/// +/// Down cursor, Control-n +/// +/// Moves the editing point one line down +/// +/// +/// +/// Home key, Control-a +/// +/// Moves the cursor to the beginning of the line. +/// +/// +/// +/// End key, Control-e +/// +/// Moves the cursor to the end of the line. +/// +/// +/// +/// Control-Home +/// +/// Scrolls to the first line and moves the cursor there. +/// +/// +/// +/// Control-End +/// +/// Scrolls to the last line and moves the cursor there. +/// +/// +/// +/// Delete, Control-d +/// +/// Deletes the character in front of the cursor. +/// +/// +/// +/// Backspace +/// +/// Deletes the character behind the cursor. +/// +/// +/// +/// Control-k +/// +/// Deletes the text until the end of the line and replaces the kill buffer +/// with the deleted text. You can paste this text in a different place by +/// using Control-y. +/// +/// +/// +/// Control-y +/// +/// Pastes the content of the kill ring into the current position. +/// +/// +/// +/// Alt-d +/// +/// Deletes the word above the cursor and adds it to the kill ring. You +/// can paste the contents of the kill ring with Control-y. +/// +/// +/// +/// Control-q +/// +/// Quotes the next input character, to prevent the normal processing of +/// key handling to take place. +/// +/// +/// /// public class TextView : View { - TextModel _model = new (); - int _topRow; + bool _allowsReturn = true; + bool _allowsTab = true; + int _bottomOffset, _rightOffset; + bool _clickWithSelecting; + + // The column we are tracking, or -1 if we are not tracking any column + int _columnTrack = -1; + bool _continuousFind; + + bool _copyWithoutSelection; + + string? _currentCaller; + CultureInfo? _currentCulture; + + CursorVisibility _desiredCursorVisibility = CursorVisibility.Default; + readonly HistoryText _historyText = new (); + + bool _isButtonShift; + bool _isDrawing; + bool _isReadOnly; + + bool _lastWasKill; int _leftColumn; - int _currentRow; - int _currentColumn; + TextModel _model = new (); + bool _multiline = true; + + CursorVisibility _savedCursorVisibility; int _selectionStartColumn, _selectionStartRow; - bool _selecting; + bool _shiftSelecting; + int _tabWidth = 4; + int _topRow; bool _wordWrap; WordWrapManager? _wrapManager; - bool _continuousFind; - int _bottomOffset, _rightOffset; - int _tabWidth = 4; - bool _allowsTab = true; - bool _allowsReturn = true; - bool _multiline = true; - HistoryText _historyText = new (); - CultureInfo? _currentCulture; + bool _wrapNeeded; + + Dim? savedHeight; /// - /// Raised when the property of the changes. + /// Initializes a on the specified area, with absolute position and size. /// /// - /// The property of only changes when it is explicitly - /// set, not as the user types. To be notified as the user changes the contents of the TextView - /// see . /// - public event EventHandler? TextChanged; + public TextView (Rect frame) : base (frame) => SetInitialProperties (); /// - /// Raised when the contents of the are changed. + /// Initializes a on the specified area, + /// with dimensions controlled with the X, Y, Width and Height properties. /// - /// - /// Unlike the event, this event is raised whenever the user types or - /// otherwise changes the contents of the . - /// - public event EventHandler? ContentsChanged; + public TextView () => SetInitialProperties (); /// - /// Invoked with the unwrapped . + /// Provides autocomplete context menu based on suggestions at the current cursor + /// position. Configure to enable this feature /// - public event EventHandler? UnwrappedCursorPosition; + public IAutocomplete Autocomplete { get; protected set; } = new TextViewAutocomplete (); /// - /// Invoked when the normal color is drawn. + /// Tracks whether the text view should be considered "used", that is, that the user has moved in the entry, + /// so new input should be appended at the cursor position, rather than clearing the entry /// - public event EventHandler? DrawNormalColor; + public bool Used { get; set; } /// - /// Invoked when the selection color is drawn. + /// Sets or gets the text in the . /// - public event EventHandler? DrawSelectionColor; + /// + /// The event is fired whenever this property is set. Note, however, + /// that Text is not set by as the user types. + /// + public override string Text { + get { + if (_wordWrap) { + return _wrapManager!.Model.ToString (); + } + return _model.ToString (); + } + + set { + ResetPosition (); + _model.LoadString (value); + if (_wordWrap) { + _wrapManager = new WordWrapManager (_model); + _model = _wrapManager.WrapModel (_frameWidth, out _, out _, out _, out _); + } + TextChanged?.Invoke (this, EventArgs.Empty); + SetNeedsDisplay (); + + _historyText.Clear (Text); + } + } + + int _frameWidth => Math.Max (Frame.Width - (RightOffset != 0 ? 2 : 1), 0); /// - /// Invoked when the ready only color is drawn. + /// Gets or sets the top row. /// - public event EventHandler? DrawReadOnlyColor; + public int TopRow { get => _topRow; set => _topRow = Math.Max (Math.Min (value, Lines - 1), 0); } /// - /// Invoked when the used color is drawn. The Used Color is used to indicate - /// if the was pressed and enabled. + /// Gets or sets the left column. /// - public event EventHandler? DrawUsedColor; + public int LeftColumn { + get => _leftColumn; + set { + if (value > 0 && _wordWrap) { + return; + } + _leftColumn = Math.Max (Math.Min (value, Maxlength - 1), 0); + } + } /// - /// Provides autocomplete context menu based on suggestions at the current cursor - /// position. Configure to enable this feature + /// Gets the maximum visible length line. /// - public IAutocomplete Autocomplete { get; protected set; } = new TextViewAutocomplete (); + public int Maxlength => _model.GetMaxVisibleLine (_topRow, _topRow + Frame.Height, TabWidth); /// - /// Initializes a on the specified area, with absolute position and size. + /// Gets the number of lines. /// - /// - /// - public TextView (Rect frame) : base (frame) => SetInitialProperties (); + public int Lines => _model.Count; /// - /// Initializes a on the specified area, - /// with dimensions controlled with the X, Y, Width and Height properties. + /// Sets or gets the current cursor position. /// - public TextView () : base () => SetInitialProperties (); - - void SetInitialProperties () - { - CanFocus = true; - Used = true; + public Point CursorPosition { + get => new (CurrentColumn, CurrentRow); + set { + var line = _model.GetLine (Math.Max (Math.Min (value.Y, _model.Count - 1), 0)); + CurrentColumn = value.X < 0 ? 0 : value.X > line.Count ? line.Count : value.X; + CurrentRow = value.Y < 0 ? 0 : value.Y > _model.Count - 1 + ? Math.Max (_model.Count - 1, 0) : value.Y; + SetNeedsDisplay (); + Adjust (); + } + } - _model.LinesLoaded += Model_LinesLoaded!; - _historyText.ChangeText += HistoryText_ChangeText!; + /// + /// Start column position of the selected text. + /// + public int SelectionStartColumn { + get => _selectionStartColumn; + set { + var line = _model.GetLine (_selectionStartRow); + _selectionStartColumn = value < 0 ? 0 : value > line.Count ? line.Count : value; + Selecting = true; + SetNeedsDisplay (); + Adjust (); + } + } - Initialized += TextView_Initialized!; + /// + /// Start row position of the selected text. + /// + public int SelectionStartRow { + get => _selectionStartRow; + set { + _selectionStartRow = value < 0 ? 0 : value > _model.Count - 1 + ? Math.Max (_model.Count - 1, 0) : value; + Selecting = true; + SetNeedsDisplay (); + Adjust (); + } + } - // Things this view knows how to do - AddCommand (Command.PageDown, () => { ProcessPageDown (); return true; }); - AddCommand (Command.PageDownExtend, () => { ProcessPageDownExtend (); return true; }); - AddCommand (Command.PageUp, () => { ProcessPageUp (); return true; }); - AddCommand (Command.PageUpExtend, () => { ProcessPageUpExtend (); return true; }); - AddCommand (Command.LineDown, () => { ProcessMoveDown (); return true; }); - AddCommand (Command.LineDownExtend, () => { ProcessMoveDownExtend (); return true; }); - AddCommand (Command.LineUp, () => { ProcessMoveUp (); return true; }); - AddCommand (Command.LineUpExtend, () => { ProcessMoveUpExtend (); return true; }); - AddCommand (Command.Right, () => ProcessMoveRight ()); - AddCommand (Command.RightExtend, () => { ProcessMoveRightExtend (); return true; }); - AddCommand (Command.Left, () => ProcessMoveLeft ()); - AddCommand (Command.LeftExtend, () => { ProcessMoveLeftExtend (); return true; }); - AddCommand (Command.DeleteCharLeft, () => { ProcessDeleteCharLeft (); return true; }); - AddCommand (Command.StartOfLine, () => { ProcessMoveStartOfLine (); return true; }); - AddCommand (Command.StartOfLineExtend, () => { ProcessMoveStartOfLineExtend (); return true; }); - AddCommand (Command.DeleteCharRight, () => { ProcessDeleteCharRight (); return true; }); - AddCommand (Command.EndOfLine, () => { ProcessMoveEndOfLine (); return true; }); - AddCommand (Command.EndOfLineExtend, () => { ProcessMoveEndOfLineExtend (); return true; }); - AddCommand (Command.CutToEndLine, () => { KillToEndOfLine (); return true; }); - AddCommand (Command.CutToStartLine, () => { KillToStartOfLine (); return true; }); - AddCommand (Command.Paste, () => { ProcessPaste (); return true; }); - AddCommand (Command.ToggleExtend, () => { ToggleSelecting (); return true; }); - AddCommand (Command.Copy, () => { ProcessCopy (); return true; }); - AddCommand (Command.Cut, () => { ProcessCut (); return true; }); - AddCommand (Command.WordLeft, () => { ProcessMoveWordBackward (); return true; }); - AddCommand (Command.WordLeftExtend, () => { ProcessMoveWordBackwardExtend (); return true; }); - AddCommand (Command.WordRight, () => { ProcessMoveWordForward (); return true; }); - AddCommand (Command.WordRightExtend, () => { ProcessMoveWordForwardExtend (); return true; }); - AddCommand (Command.KillWordForwards, () => { ProcessKillWordForward (); return true; }); - AddCommand (Command.KillWordBackwards, () => { ProcessKillWordBackward (); return true; }); - AddCommand (Command.NewLine, () => ProcessReturn ()); - AddCommand (Command.BottomEnd, () => { MoveBottomEnd (); return true; }); - AddCommand (Command.BottomEndExtend, () => { MoveBottomEndExtend (); return true; }); - AddCommand (Command.TopHome, () => { MoveTopHome (); return true; }); - AddCommand (Command.TopHomeExtend, () => { MoveTopHomeExtend (); return true; }); - AddCommand (Command.SelectAll, () => { ProcessSelectAll (); return true; }); - AddCommand (Command.ToggleOverwrite, () => { ProcessSetOverwrite (); return true; }); - AddCommand (Command.EnableOverwrite, () => { SetOverwrite (true); return true; }); - AddCommand (Command.DisableOverwrite, () => { SetOverwrite (false); return true; }); - AddCommand (Command.Tab, () => ProcessTab ()); - AddCommand (Command.BackTab, () => ProcessBackTab ()); - AddCommand (Command.NextView, () => ProcessMoveNextView ()); - AddCommand (Command.PreviousView, () => ProcessMovePreviousView ()); - AddCommand (Command.Undo, () => { Undo (); return true; }); - AddCommand (Command.Redo, () => { Redo (); return true; }); - AddCommand (Command.DeleteAll, () => { DeleteAll (); return true; }); - AddCommand (Command.ShowContextMenu, () => { - ContextMenu!.Position = new Point (CursorPosition.X - _leftColumn + 2, CursorPosition.Y - _topRow + 2); - ShowContextMenu (); - return true; - }); + /// + /// The selected text. + /// + public string SelectedText { + get { + if (!Selecting || _model.Count == 1 && _model.GetLine (0).Count == 0) { + return string.Empty; + } - // Default keybindings for this view - KeyBindings.Add (KeyCode.PageDown, Command.PageDown); - KeyBindings.Add (KeyCode.V | KeyCode.CtrlMask, Command.PageDown); + return GetSelectedRegion (); + } + } - KeyBindings.Add (KeyCode.PageDown | KeyCode.ShiftMask, Command.PageDownExtend); + /// + /// Length of the selected text. + /// + public int SelectedLength => GetSelectedLength (); - KeyBindings.Add (KeyCode.PageUp, Command.PageUp); - KeyBindings.Add ((int)'V' + KeyCode.AltMask, Command.PageUp); - - KeyBindings.Add (KeyCode.PageUp | KeyCode.ShiftMask, Command.PageUpExtend); - - KeyBindings.Add (KeyCode.N | KeyCode.CtrlMask, Command.LineDown); - KeyBindings.Add (KeyCode.CursorDown, Command.LineDown); - - KeyBindings.Add (KeyCode.CursorDown | KeyCode.ShiftMask, Command.LineDownExtend); - - KeyBindings.Add (KeyCode.P | KeyCode.CtrlMask, Command.LineUp); - KeyBindings.Add (KeyCode.CursorUp, Command.LineUp); - - KeyBindings.Add (KeyCode.CursorUp | KeyCode.ShiftMask, Command.LineUpExtend); - - KeyBindings.Add (KeyCode.F | KeyCode.CtrlMask, Command.Right); - KeyBindings.Add (KeyCode.CursorRight, Command.Right); - - KeyBindings.Add (KeyCode.CursorRight | KeyCode.ShiftMask, Command.RightExtend); - - KeyBindings.Add (KeyCode.B | KeyCode.CtrlMask, Command.Left); - KeyBindings.Add (KeyCode.CursorLeft, Command.Left); - - KeyBindings.Add (KeyCode.CursorLeft | KeyCode.ShiftMask, Command.LeftExtend); - - KeyBindings.Add (KeyCode.Backspace, Command.DeleteCharLeft); - - KeyBindings.Add (KeyCode.Home, Command.StartOfLine); - KeyBindings.Add (KeyCode.A | KeyCode.CtrlMask, Command.StartOfLine); - - KeyBindings.Add (KeyCode.Home | KeyCode.ShiftMask, Command.StartOfLineExtend); - - KeyBindings.Add (KeyCode.Delete, Command.DeleteCharRight); - KeyBindings.Add (KeyCode.D | KeyCode.CtrlMask, Command.DeleteCharRight); - - KeyBindings.Add (KeyCode.End, Command.EndOfLine); - KeyBindings.Add (KeyCode.E | KeyCode.CtrlMask, Command.EndOfLine); - - KeyBindings.Add (KeyCode.End | KeyCode.ShiftMask, Command.EndOfLineExtend); - - KeyBindings.Add (KeyCode.K | KeyCode.CtrlMask, Command.CutToEndLine); // kill-to-end - KeyBindings.Add (KeyCode.Delete | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.CutToEndLine); // kill-to-end - - KeyBindings.Add (KeyCode.K | KeyCode.AltMask, Command.CutToStartLine); // kill-to-start - KeyBindings.Add (KeyCode.Backspace | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.CutToStartLine); // kill-to-start - - KeyBindings.Add (KeyCode.Y | KeyCode.CtrlMask, Command.Paste); // Control-y, yank - KeyBindings.Add (KeyCode.Space | KeyCode.CtrlMask, Command.ToggleExtend); - - KeyBindings.Add ((int)'C' + KeyCode.AltMask, Command.Copy); - KeyBindings.Add (KeyCode.C | KeyCode.CtrlMask, Command.Copy); - - KeyBindings.Add ((int)'W' + KeyCode.AltMask, Command.Cut); - KeyBindings.Add (KeyCode.W | KeyCode.CtrlMask, Command.Cut); - KeyBindings.Add (KeyCode.X | KeyCode.CtrlMask, Command.Cut); - - KeyBindings.Add (KeyCode.CursorLeft | KeyCode.CtrlMask, Command.WordLeft); - KeyBindings.Add ((KeyCode)((int)'B' + KeyCode.AltMask), Command.WordLeft); - - KeyBindings.Add (KeyCode.CursorLeft | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.WordLeftExtend); - - KeyBindings.Add (KeyCode.CursorRight | KeyCode.CtrlMask, Command.WordRight); - KeyBindings.Add ((KeyCode)((int)'F' + KeyCode.AltMask), Command.WordRight); - - KeyBindings.Add (KeyCode.CursorRight | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.WordRightExtend); - KeyBindings.Add (KeyCode.Delete | KeyCode.CtrlMask, Command.KillWordForwards); // kill-word-forwards - KeyBindings.Add (KeyCode.Backspace | KeyCode.CtrlMask, Command.KillWordBackwards); // kill-word-backwards - - // BUGBUG: If AllowsReturn is false, Key.Enter should not be bound (so that Toplevel can cause Command.Accept). - KeyBindings.Add (KeyCode.Enter, Command.NewLine); - KeyBindings.Add (KeyCode.End | KeyCode.CtrlMask, Command.BottomEnd); - KeyBindings.Add (KeyCode.End | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.BottomEndExtend); - KeyBindings.Add (KeyCode.Home | KeyCode.CtrlMask, Command.TopHome); - KeyBindings.Add (KeyCode.Home | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.TopHomeExtend); - KeyBindings.Add (KeyCode.T | KeyCode.CtrlMask, Command.SelectAll); - KeyBindings.Add (KeyCode.Insert, Command.ToggleOverwrite); - KeyBindings.Add (KeyCode.Tab, Command.Tab); - KeyBindings.Add (KeyCode.Tab | KeyCode.ShiftMask, Command.BackTab); - - KeyBindings.Add (KeyCode.Tab | KeyCode.CtrlMask, Command.NextView); - KeyBindings.Add ((KeyCode)Application.AlternateForwardKey, Command.NextView); - - KeyBindings.Add (KeyCode.Tab | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.PreviousView); - KeyBindings.Add ((KeyCode)Application.AlternateBackwardKey, Command.PreviousView); - - KeyBindings.Add (KeyCode.Z | KeyCode.CtrlMask, Command.Undo); - KeyBindings.Add (KeyCode.R | KeyCode.CtrlMask, Command.Redo); - - KeyBindings.Add (KeyCode.G | KeyCode.CtrlMask, Command.DeleteAll); - KeyBindings.Add (KeyCode.D | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.DeleteAll); - - _currentCulture = Thread.CurrentThread.CurrentUICulture; - - ContextMenu = new ContextMenu () { MenuItems = BuildContextMenuBarItem () }; - ContextMenu.KeyChanged += ContextMenu_KeyChanged!; - - KeyBindings.Add ((KeyCode)ContextMenu.Key, KeyBindingScope.HotKey, Command.ShowContextMenu); - } - - MenuBarItem BuildContextMenuBarItem () => new (new MenuItem [] { - new (Strings.ctxSelectAll, "", () => SelectAll (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.SelectAll)), - new (Strings.ctxDeleteAll, "", () => DeleteAll (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.DeleteAll)), - new (Strings.ctxCopy, "", () => Copy (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Copy)), - new (Strings.ctxCut, "", () => Cut (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Cut)), - new (Strings.ctxPaste, "", () => Paste (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Paste)), - new (Strings.ctxUndo, "", () => Undo (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Undo)), - new (Strings.ctxRedo, "", () => Redo (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Redo)) - }); - - void ContextMenu_KeyChanged (object sender, KeyChangedEventArgs e) => KeyBindings.Replace ((KeyCode)e.OldKey, (KeyCode)e.NewKey); - - void Model_LinesLoaded (object sender, EventArgs e) - { - // This call is not needed. Model_LinesLoaded gets invoked when - // model.LoadString (value) is called. LoadString is called from one place - // (Text.set) and historyText.Clear() is called immediately after. - // If this call happens, HistoryText_ChangeText will get called multiple times - // when Text is set, which is wrong. - //historyText.Clear (Text); - - if (!_multiline && !IsInitialized) { - _currentColumn = Text.GetRuneCount (); - _leftColumn = _currentColumn > Frame.Width + 1 ? _currentColumn - Frame.Width + 1 : 0; - } - } - - void HistoryText_ChangeText (object sender, HistoryText.HistoryTextItem obj) - { - SetWrapModel (); - - if (obj != null) { - int startLine = obj.CursorPosition.Y; - - if (obj.RemovedOnAdded != null) { - int offset; - if (obj.IsUndoing) { - offset = Math.Max (obj.RemovedOnAdded.Lines.Count - obj.Lines.Count, 1); - } else { - offset = obj.RemovedOnAdded.Lines.Count - 1; - } - for (int i = 0; i < offset; i++) { - if (Lines > obj.RemovedOnAdded.CursorPosition.Y) { - _model.RemoveLine (obj.RemovedOnAdded.CursorPosition.Y); - } else { - break; - } - } - } - - for (int i = 0; i < obj.Lines.Count; i++) { - if (i == 0) { - _model.ReplaceLine (startLine, obj.Lines [i]); - } else if (obj.IsUndoing && obj.LineStatus == HistoryText.LineStatus.Removed - || !obj.IsUndoing && obj.LineStatus == HistoryText.LineStatus.Added) { - _model.AddLine (startLine, obj.Lines [i]); - } else if (Lines > obj.CursorPosition.Y + 1) { - _model.RemoveLine (obj.CursorPosition.Y + 1); - } - startLine++; - } - - CursorPosition = obj.FinalCursorPosition; - } - - UpdateWrapModel (); - - Adjust (); - OnContentsChanged (); - } - - void TextView_Initialized (object sender, EventArgs e) - { - Autocomplete.HostControl = this; - if (Application.Top != null) { - Application.Top.AlternateForwardKeyChanged += Top_AlternateForwardKeyChanged!; - Application.Top.AlternateBackwardKeyChanged += Top_AlternateBackwardKeyChanged!; - } - OnContentsChanged (); - } - - void Top_AlternateBackwardKeyChanged (object sender, KeyChangedEventArgs e) => KeyBindings.Replace ((KeyCode)e.OldKey, (KeyCode)e.NewKey); - - void Top_AlternateForwardKeyChanged (object sender, KeyChangedEventArgs e) => KeyBindings.Replace ((KeyCode)e.OldKey, (KeyCode)e.NewKey); - - /// - /// Tracks whether the text view should be considered "used", that is, that the user has moved in the entry, - /// so new input should be appended at the cursor position, rather than clearing the entry - /// - public bool Used { get; set; } - - void ResetPosition () - { - _topRow = _leftColumn = _currentRow = _currentColumn = 0; - StopSelecting (); - ResetCursorVisibility (); - } + /// + /// Get or sets the selecting. + /// + public bool Selecting { get; set; } /// - /// Sets or gets the text in the . + /// Allows word wrap the to fit the available container width. /// - /// - /// The event is fired whenever this property is set. Note, however, - /// that Text is not set by as the user types. - /// - public override string Text { - get { - if (_wordWrap) { - return _wrapManager!.Model.ToString (); - } else { - return _model.ToString (); - } - } - + public bool WordWrap { + get => _wordWrap; set { + if (value == _wordWrap) { + return; + } + if (value && !_multiline) { + return; + } + _wordWrap = value; ResetPosition (); - _model.LoadString (value); if (_wordWrap) { _wrapManager = new WordWrapManager (_model); _model = _wrapManager.WrapModel (_frameWidth, out _, out _, out _, out _); + } else if (!_wordWrap && _wrapManager != null) { + _model = _wrapManager.Model; } - TextChanged?.Invoke (this, EventArgs.Empty); SetNeedsDisplay (); - - _historyText.Clear (Text); } } - /// - public override Rect Frame { - get => base.Frame; + /// + /// The bottom offset needed to use a horizontal scrollbar or for another reason. + /// This is only needed with the keyboard navigation. + /// + public int BottomOffset { + get => _bottomOffset; set { - base.Frame = value; - if (IsInitialized) { - WrapTextModel (); - Adjust (); + if (CurrentRow == Lines - 1 && _bottomOffset > 0 && value == 0) { + _topRow = Math.Max (_topRow - _bottomOffset, 0); } + _bottomOffset = value; + Adjust (); } } - void WrapTextModel () - { - if (_wordWrap && _wrapManager != null) { - _model = _wrapManager.WrapModel (_frameWidth, - out int nRow, out int nCol, - out int nStartRow, out int nStartCol, - _currentRow, _currentColumn, - _selectionStartRow, _selectionStartColumn, - _tabWidth, true); - _currentRow = nRow; - _currentColumn = nCol; - _selectionStartRow = nStartRow; - _selectionStartColumn = nStartCol; - SetNeedsDisplay (); - } - } - - int _frameWidth => Math.Max (Frame.Width - (RightOffset != 0 ? 2 : 1), 0); - /// - /// Gets or sets the top row. - /// - public int TopRow { get => _topRow; set => _topRow = Math.Max (Math.Min (value, Lines - 1), 0); } - - /// - /// Gets or sets the left column. + /// The right offset needed to use a vertical scrollbar or for another reason. + /// This is only needed with the keyboard navigation. /// - public int LeftColumn { - get => _leftColumn; + public int RightOffset { + get => _rightOffset; set { - if (value > 0 && _wordWrap) { - return; + if (!_wordWrap && CurrentColumn == GetCurrentLine ().Count && _rightOffset > 0 && value == 0) { + _leftColumn = Math.Max (_leftColumn - _rightOffset, 0); } - _leftColumn = Math.Max (Math.Min (value, Maxlength - 1), 0); + _rightOffset = value; + Adjust (); } } /// - /// Gets the maximum visible length line. - /// - public int Maxlength => _model.GetMaxVisibleLine (_topRow, _topRow + Frame.Height, TabWidth); - - /// - /// Gets the number of lines. - /// - public int Lines => _model.Count; - - /// - /// Sets or gets the current cursor position. - /// - public Point CursorPosition { - get => new (_currentColumn, _currentRow); - set { - var line = _model.GetLine (Math.Max (Math.Min (value.Y, _model.Count - 1), 0)); - _currentColumn = value.X < 0 ? 0 : value.X > line.Count ? line.Count : value.X; - _currentRow = value.Y < 0 ? 0 : value.Y > _model.Count - 1 - ? Math.Max (_model.Count - 1, 0) : value.Y; - SetNeedsDisplay (); - Adjust (); - } - } - - /// - /// Start column position of the selected text. - /// - public int SelectionStartColumn { - get => _selectionStartColumn; - set { - var line = _model.GetLine (_selectionStartRow); - _selectionStartColumn = value < 0 ? 0 : value > line.Count ? line.Count : value; - _selecting = true; - SetNeedsDisplay (); - Adjust (); - } - } - - /// - /// Start row position of the selected text. - /// - public int SelectionStartRow { - get => _selectionStartRow; - set { - _selectionStartRow = value < 0 ? 0 : value > _model.Count - 1 - ? Math.Max (_model.Count - 1, 0) : value; - _selecting = true; - SetNeedsDisplay (); - Adjust (); - } - } - - /// - /// The selected text. - /// - public string SelectedText { - get { - if (!_selecting || _model.Count == 1 && _model.GetLine (0).Count == 0) { - return string.Empty; - } - - return GetSelectedRegion (); - } - } - - /// - /// Length of the selected text. - /// - public int SelectedLength => GetSelectedLength (); - - /// - /// Get or sets the selecting. - /// - public bool Selecting { - get => _selecting; - set => _selecting = value; - } - - /// - /// Allows word wrap the to fit the available container width. - /// - public bool WordWrap { - get => _wordWrap; - set { - if (value == _wordWrap) { - return; - } - if (value && !_multiline) { - return; - } - _wordWrap = value; - ResetPosition (); - if (_wordWrap) { - _wrapManager = new WordWrapManager (_model); - _model = _wrapManager.WrapModel (_frameWidth, out _, out _, out _, out _); - } else if (!_wordWrap && _wrapManager != null) { - _model = _wrapManager.Model; - } - SetNeedsDisplay (); - } - } - - /// - /// The bottom offset needed to use a horizontal scrollbar or for another reason. - /// This is only needed with the keyboard navigation. - /// - public int BottomOffset { - get => _bottomOffset; - set { - if (_currentRow == Lines - 1 && _bottomOffset > 0 && value == 0) { - _topRow = Math.Max (_topRow - _bottomOffset, 0); - } - _bottomOffset = value; - Adjust (); - } - } - - /// - /// The right offset needed to use a vertical scrollbar or for another reason. - /// This is only needed with the keyboard navigation. - /// - public int RightOffset { - get => _rightOffset; - set { - if (!_wordWrap && _currentColumn == GetCurrentLine ().Count && _rightOffset > 0 && value == 0) { - _leftColumn = Math.Max (_leftColumn - _rightOffset, 0); - } - _rightOffset = value; - Adjust (); - } - } - - /// - /// Gets or sets a value indicating whether pressing ENTER in a - /// creates a new line of text in the view or activates the default button for the Toplevel. + /// Gets or sets a value indicating whether pressing ENTER in a + /// creates a new line of text in the view or activates the default button for the Toplevel. /// public bool AllowsReturn { get => _allowsReturn; @@ -2097,9 +1798,9 @@ public bool AllowsReturn { } /// - /// Gets or sets whether the inserts a tab character into the text or ignores + /// Gets or sets whether the inserts a tab character into the text or ignores /// tab input. If set to `false` and the user presses the tab key (or shift-tab) the focus will move to the - /// next view (or previous with shift-tab). The default is `true`; if the user presses the tab key, a tab + /// next view (or previous with shift-tab). The default is `true`; if the user presses the tab key, a tab /// character will be inserted into the text. /// public bool AllowsTab { @@ -2133,8 +1834,6 @@ public int TabWidth { } } - Dim? savedHeight = null; - /// /// Gets or sets a value indicating whether this is a multiline text view. /// @@ -2153,62 +1852,554 @@ public bool Multiline { AllowsReturn = false; AllowsTab = false; WordWrap = false; - _currentColumn = 0; - _currentRow = 0; + CurrentColumn = 0; + CurrentRow = 0; savedHeight = Height; - var prevLayoutStyle = LayoutStyle; - if (LayoutStyle == LayoutStyle.Computed) { - LayoutStyle = LayoutStyle.Absolute; + //var prevLayoutStyle = LayoutStyle; + //if (LayoutStyle == LayoutStyle.Computed) { + // LayoutStyle = LayoutStyle.Absolute; + //} + Height = 1; + //LayoutStyle = prevLayoutStyle; + if (!IsInitialized) { + _model.LoadString (Text); + } + SetNeedsDisplay (); + } else if (_multiline && savedHeight != null) { + //var lyout = LayoutStyle; + //if (LayoutStyle == LayoutStyle.Computed) { + // LayoutStyle = LayoutStyle.Absolute; + //} + Height = savedHeight; + //LayoutStyle = lyout; + SetNeedsDisplay (); + } + } + } + + /// + /// Indicates whatever the text was changed or not. + /// if the text was changed otherwise. + /// + public bool IsDirty { + get => _historyText.IsDirty (Text); + set => _historyText.Clear (Text); + } + + /// + /// Indicates whatever the text has history changes or not. + /// if the text has history changes otherwise. + /// + public bool HasHistoryChanges => _historyText.HasHistoryChanges; + + /// + /// Get the for this view. + /// + public ContextMenu? ContextMenu { get; private set; } + + /// + /// If and the current is null + /// will inherit from the previous, otherwise if (default) do nothing. + /// If the text is load with this + /// property is automatically sets to . + /// + public bool InheritsPreviousColorScheme { get; set; } + + /// + /// Gets the current cursor row. + /// + public int CurrentRow { get; private set; } + + /// + /// Gets the cursor column. + /// + /// The cursor column. + public int CurrentColumn { get; private set; } + + /// + /// Gets or sets whether the is in read-only mode or not + /// + /// Boolean value(Default false) + public bool ReadOnly { + get => _isReadOnly; + set { + if (value != _isReadOnly) { + _isReadOnly = value; + + SetNeedsDisplay (); + Adjust (); + } + } + } + + /// + /// Get / Set the wished cursor when the field is focused + /// + public CursorVisibility DesiredCursorVisibility { + get => _desiredCursorVisibility; + set { + if (HasFocus) { + Application.Driver.SetCursorVisibility (value); + } + + _desiredCursorVisibility = value; + SetNeedsDisplay (); + } + } + + /// + public override bool CanFocus { + get => base.CanFocus; + set => base.CanFocus = value; + } + + /// + /// Raised when the property of the changes. + /// + /// + /// The property of only changes when it is explicitly + /// set, not as the user types. To be notified as the user changes the contents of the TextView + /// see . + /// + public event EventHandler? TextChanged; + + /// + /// Raised when the contents of the are changed. + /// + /// + /// Unlike the event, this event is raised whenever the user types or + /// otherwise changes the contents of the . + /// + public event EventHandler? ContentsChanged; + + /// + /// Invoked with the unwrapped . + /// + public event EventHandler? UnwrappedCursorPosition; + + /// + /// Invoked when the normal color is drawn. + /// + public event EventHandler? DrawNormalColor; + + /// + /// Invoked when the selection color is drawn. + /// + public event EventHandler? DrawSelectionColor; + + /// + /// Invoked when the ready only color is drawn. + /// + public event EventHandler? DrawReadOnlyColor; + + /// + /// Invoked when the used color is drawn. The Used Color is used to indicate + /// if the was pressed and enabled. + /// + public event EventHandler? DrawUsedColor; + + void SetInitialProperties () + { + CanFocus = true; + Used = true; + + _model.LinesLoaded += Model_LinesLoaded!; + _historyText.ChangeText += HistoryText_ChangeText!; + + Initialized += TextView_Initialized!; + + LayoutComplete += TextView_LayoutComplete; + + // Things this view knows how to do + AddCommand (Command.PageDown, () => { + ProcessPageDown (); + return true; + }); + AddCommand (Command.PageDownExtend, () => { + ProcessPageDownExtend (); + return true; + }); + AddCommand (Command.PageUp, () => { + ProcessPageUp (); + return true; + }); + AddCommand (Command.PageUpExtend, () => { + ProcessPageUpExtend (); + return true; + }); + AddCommand (Command.LineDown, () => { + ProcessMoveDown (); + return true; + }); + AddCommand (Command.LineDownExtend, () => { + ProcessMoveDownExtend (); + return true; + }); + AddCommand (Command.LineUp, () => { + ProcessMoveUp (); + return true; + }); + AddCommand (Command.LineUpExtend, () => { + ProcessMoveUpExtend (); + return true; + }); + AddCommand (Command.Right, () => ProcessMoveRight ()); + AddCommand (Command.RightExtend, () => { + ProcessMoveRightExtend (); + return true; + }); + AddCommand (Command.Left, () => ProcessMoveLeft ()); + AddCommand (Command.LeftExtend, () => { + ProcessMoveLeftExtend (); + return true; + }); + AddCommand (Command.DeleteCharLeft, () => { + ProcessDeleteCharLeft (); + return true; + }); + AddCommand (Command.StartOfLine, () => { + ProcessMoveStartOfLine (); + return true; + }); + AddCommand (Command.StartOfLineExtend, () => { + ProcessMoveStartOfLineExtend (); + return true; + }); + AddCommand (Command.DeleteCharRight, () => { + ProcessDeleteCharRight (); + return true; + }); + AddCommand (Command.EndOfLine, () => { + ProcessMoveEndOfLine (); + return true; + }); + AddCommand (Command.EndOfLineExtend, () => { + ProcessMoveEndOfLineExtend (); + return true; + }); + AddCommand (Command.CutToEndLine, () => { + KillToEndOfLine (); + return true; + }); + AddCommand (Command.CutToStartLine, () => { + KillToStartOfLine (); + return true; + }); + AddCommand (Command.Paste, () => { + ProcessPaste (); + return true; + }); + AddCommand (Command.ToggleExtend, () => { + ToggleSelecting (); + return true; + }); + AddCommand (Command.Copy, () => { + ProcessCopy (); + return true; + }); + AddCommand (Command.Cut, () => { + ProcessCut (); + return true; + }); + AddCommand (Command.WordLeft, () => { + ProcessMoveWordBackward (); + return true; + }); + AddCommand (Command.WordLeftExtend, () => { + ProcessMoveWordBackwardExtend (); + return true; + }); + AddCommand (Command.WordRight, () => { + ProcessMoveWordForward (); + return true; + }); + AddCommand (Command.WordRightExtend, () => { + ProcessMoveWordForwardExtend (); + return true; + }); + AddCommand (Command.KillWordForwards, () => { + ProcessKillWordForward (); + return true; + }); + AddCommand (Command.KillWordBackwards, () => { + ProcessKillWordBackward (); + return true; + }); + AddCommand (Command.NewLine, () => ProcessReturn ()); + AddCommand (Command.BottomEnd, () => { + MoveBottomEnd (); + return true; + }); + AddCommand (Command.BottomEndExtend, () => { + MoveBottomEndExtend (); + return true; + }); + AddCommand (Command.TopHome, () => { + MoveTopHome (); + return true; + }); + AddCommand (Command.TopHomeExtend, () => { + MoveTopHomeExtend (); + return true; + }); + AddCommand (Command.SelectAll, () => { + ProcessSelectAll (); + return true; + }); + AddCommand (Command.ToggleOverwrite, () => { + ProcessSetOverwrite (); + return true; + }); + AddCommand (Command.EnableOverwrite, () => { + SetOverwrite (true); + return true; + }); + AddCommand (Command.DisableOverwrite, () => { + SetOverwrite (false); + return true; + }); + AddCommand (Command.Tab, () => ProcessTab ()); + AddCommand (Command.BackTab, () => ProcessBackTab ()); + AddCommand (Command.NextView, () => ProcessMoveNextView ()); + AddCommand (Command.PreviousView, () => ProcessMovePreviousView ()); + AddCommand (Command.Undo, () => { + Undo (); + return true; + }); + AddCommand (Command.Redo, () => { + Redo (); + return true; + }); + AddCommand (Command.DeleteAll, () => { + DeleteAll (); + return true; + }); + AddCommand (Command.ShowContextMenu, () => { + ContextMenu!.Position = new Point (CursorPosition.X - _leftColumn + 2, CursorPosition.Y - _topRow + 2); + ShowContextMenu (); + return true; + }); + + // Default keybindings for this view + KeyBindings.Add (KeyCode.PageDown, Command.PageDown); + KeyBindings.Add (KeyCode.V | KeyCode.CtrlMask, Command.PageDown); + + KeyBindings.Add (KeyCode.PageDown | KeyCode.ShiftMask, Command.PageDownExtend); + + KeyBindings.Add (KeyCode.PageUp, Command.PageUp); + KeyBindings.Add ('V' + KeyCode.AltMask, Command.PageUp); + + KeyBindings.Add (KeyCode.PageUp | KeyCode.ShiftMask, Command.PageUpExtend); + + KeyBindings.Add (KeyCode.N | KeyCode.CtrlMask, Command.LineDown); + KeyBindings.Add (KeyCode.CursorDown, Command.LineDown); + + KeyBindings.Add (KeyCode.CursorDown | KeyCode.ShiftMask, Command.LineDownExtend); + + KeyBindings.Add (KeyCode.P | KeyCode.CtrlMask, Command.LineUp); + KeyBindings.Add (KeyCode.CursorUp, Command.LineUp); + + KeyBindings.Add (KeyCode.CursorUp | KeyCode.ShiftMask, Command.LineUpExtend); + + KeyBindings.Add (KeyCode.F | KeyCode.CtrlMask, Command.Right); + KeyBindings.Add (KeyCode.CursorRight, Command.Right); + + KeyBindings.Add (KeyCode.CursorRight | KeyCode.ShiftMask, Command.RightExtend); + + KeyBindings.Add (KeyCode.B | KeyCode.CtrlMask, Command.Left); + KeyBindings.Add (KeyCode.CursorLeft, Command.Left); + + KeyBindings.Add (KeyCode.CursorLeft | KeyCode.ShiftMask, Command.LeftExtend); + + KeyBindings.Add (KeyCode.Backspace, Command.DeleteCharLeft); + + KeyBindings.Add (KeyCode.Home, Command.StartOfLine); + KeyBindings.Add (KeyCode.A | KeyCode.CtrlMask, Command.StartOfLine); + + KeyBindings.Add (KeyCode.Home | KeyCode.ShiftMask, Command.StartOfLineExtend); + + KeyBindings.Add (KeyCode.Delete, Command.DeleteCharRight); + KeyBindings.Add (KeyCode.D | KeyCode.CtrlMask, Command.DeleteCharRight); + + KeyBindings.Add (KeyCode.End, Command.EndOfLine); + KeyBindings.Add (KeyCode.E | KeyCode.CtrlMask, Command.EndOfLine); + + KeyBindings.Add (KeyCode.End | KeyCode.ShiftMask, Command.EndOfLineExtend); + + KeyBindings.Add (KeyCode.K | KeyCode.CtrlMask, Command.CutToEndLine); // kill-to-end + KeyBindings.Add (KeyCode.Delete | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.CutToEndLine); // kill-to-end + + KeyBindings.Add (KeyCode.K | KeyCode.AltMask, Command.CutToStartLine); // kill-to-start + KeyBindings.Add (KeyCode.Backspace | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.CutToStartLine); // kill-to-start + + KeyBindings.Add (KeyCode.Y | KeyCode.CtrlMask, Command.Paste); // Control-y, yank + KeyBindings.Add (KeyCode.Space | KeyCode.CtrlMask, Command.ToggleExtend); + + KeyBindings.Add ('C' + KeyCode.AltMask, Command.Copy); + KeyBindings.Add (KeyCode.C | KeyCode.CtrlMask, Command.Copy); + + KeyBindings.Add ('W' + KeyCode.AltMask, Command.Cut); + KeyBindings.Add (KeyCode.W | KeyCode.CtrlMask, Command.Cut); + KeyBindings.Add (KeyCode.X | KeyCode.CtrlMask, Command.Cut); + + KeyBindings.Add (KeyCode.CursorLeft | KeyCode.CtrlMask, Command.WordLeft); + KeyBindings.Add ('B' + KeyCode.AltMask, Command.WordLeft); + + KeyBindings.Add (KeyCode.CursorLeft | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.WordLeftExtend); + + KeyBindings.Add (KeyCode.CursorRight | KeyCode.CtrlMask, Command.WordRight); + KeyBindings.Add ('F' + KeyCode.AltMask, Command.WordRight); + + KeyBindings.Add (KeyCode.CursorRight | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.WordRightExtend); + KeyBindings.Add (KeyCode.Delete | KeyCode.CtrlMask, Command.KillWordForwards); // kill-word-forwards + KeyBindings.Add (KeyCode.Backspace | KeyCode.CtrlMask, Command.KillWordBackwards); // kill-word-backwards + + // BUGBUG: If AllowsReturn is false, Key.Enter should not be bound (so that Toplevel can cause Command.Accept). + KeyBindings.Add (KeyCode.Enter, Command.NewLine); + KeyBindings.Add (KeyCode.End | KeyCode.CtrlMask, Command.BottomEnd); + KeyBindings.Add (KeyCode.End | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.BottomEndExtend); + KeyBindings.Add (KeyCode.Home | KeyCode.CtrlMask, Command.TopHome); + KeyBindings.Add (KeyCode.Home | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.TopHomeExtend); + KeyBindings.Add (KeyCode.T | KeyCode.CtrlMask, Command.SelectAll); + KeyBindings.Add (KeyCode.Insert, Command.ToggleOverwrite); + KeyBindings.Add (KeyCode.Tab, Command.Tab); + KeyBindings.Add (KeyCode.Tab | KeyCode.ShiftMask, Command.BackTab); + + KeyBindings.Add (KeyCode.Tab | KeyCode.CtrlMask, Command.NextView); + KeyBindings.Add ((KeyCode)Application.AlternateForwardKey, Command.NextView); + + KeyBindings.Add (KeyCode.Tab | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.PreviousView); + KeyBindings.Add ((KeyCode)Application.AlternateBackwardKey, Command.PreviousView); + + KeyBindings.Add (KeyCode.Z | KeyCode.CtrlMask, Command.Undo); + KeyBindings.Add (KeyCode.R | KeyCode.CtrlMask, Command.Redo); + + KeyBindings.Add (KeyCode.G | KeyCode.CtrlMask, Command.DeleteAll); + KeyBindings.Add (KeyCode.D | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.DeleteAll); + + _currentCulture = Thread.CurrentThread.CurrentUICulture; + + ContextMenu = new ContextMenu { MenuItems = BuildContextMenuBarItem () }; + ContextMenu.KeyChanged += ContextMenu_KeyChanged!; + + KeyBindings.Add ((KeyCode)ContextMenu.Key, KeyBindingScope.HotKey, Command.ShowContextMenu); + } + + void TextView_LayoutComplete (object? sender, LayoutEventArgs e) + { + WrapTextModel (); + Adjust (); + } + + MenuBarItem BuildContextMenuBarItem () => new (new MenuItem [] { + new (Strings.ctxSelectAll, "", () => SelectAll (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.SelectAll)), + new (Strings.ctxDeleteAll, "", () => DeleteAll (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.DeleteAll)), + new (Strings.ctxCopy, "", () => Copy (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Copy)), + new (Strings.ctxCut, "", () => Cut (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Cut)), + new (Strings.ctxPaste, "", () => Paste (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Paste)), + new (Strings.ctxUndo, "", () => Undo (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Undo)), + new (Strings.ctxRedo, "", () => Redo (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Redo)) + }); + + void ContextMenu_KeyChanged (object sender, KeyChangedEventArgs e) => KeyBindings.Replace ((KeyCode)e.OldKey, (KeyCode)e.NewKey); + + void Model_LinesLoaded (object sender, EventArgs e) + { + // This call is not needed. Model_LinesLoaded gets invoked when + // model.LoadString (value) is called. LoadString is called from one place + // (Text.set) and historyText.Clear() is called immediately after. + // If this call happens, HistoryText_ChangeText will get called multiple times + // when Text is set, which is wrong. + //historyText.Clear (Text); + + if (!_multiline && !IsInitialized) { + CurrentColumn = Text.GetRuneCount (); + _leftColumn = CurrentColumn > Frame.Width + 1 ? CurrentColumn - Frame.Width + 1 : 0; + } + } + + void HistoryText_ChangeText (object sender, HistoryText.HistoryTextItem obj) + { + SetWrapModel (); + + if (obj != null) { + var startLine = obj.CursorPosition.Y; + + if (obj.RemovedOnAdded != null) { + int offset; + if (obj.IsUndoing) { + offset = Math.Max (obj.RemovedOnAdded.Lines.Count - obj.Lines.Count, 1); + } else { + offset = obj.RemovedOnAdded.Lines.Count - 1; } - Height = 1; - LayoutStyle = prevLayoutStyle; - if (!IsInitialized) { - _model.LoadString (Text); + for (var i = 0; i < offset; i++) { + if (Lines > obj.RemovedOnAdded.CursorPosition.Y) { + _model.RemoveLine (obj.RemovedOnAdded.CursorPosition.Y); + } else { + break; + } } - SetNeedsDisplay (); - } else if (_multiline && savedHeight != null) { - var lyout = LayoutStyle; - if (LayoutStyle == LayoutStyle.Computed) { - LayoutStyle = LayoutStyle.Absolute; + } + + for (var i = 0; i < obj.Lines.Count; i++) { + if (i == 0) { + _model.ReplaceLine (startLine, obj.Lines [i]); + } else if (obj.IsUndoing && obj.LineStatus == HistoryText.LineStatus.Removed || !obj.IsUndoing && obj.LineStatus == HistoryText.LineStatus.Added) { + _model.AddLine (startLine, obj.Lines [i]); + } else if (Lines > obj.CursorPosition.Y + 1) { + _model.RemoveLine (obj.CursorPosition.Y + 1); } - Height = savedHeight; - LayoutStyle = lyout; - SetNeedsDisplay (); + startLine++; } + + CursorPosition = obj.FinalCursorPosition; } + + UpdateWrapModel (); + + Adjust (); + OnContentsChanged (); } - /// - /// Indicates whatever the text was changed or not. - /// if the text was changed otherwise. - /// - public bool IsDirty { - get => _historyText.IsDirty (Text); - set => _historyText.Clear (Text); + void TextView_Initialized (object sender, EventArgs e) + { + Autocomplete.HostControl = this; + if (Application.Top != null) { + Application.Top.AlternateForwardKeyChanged += Top_AlternateForwardKeyChanged!; + Application.Top.AlternateBackwardKeyChanged += Top_AlternateBackwardKeyChanged!; + } + OnContentsChanged (); } - /// - /// Indicates whatever the text has history changes or not. - /// if the text has history changes otherwise. - /// - public bool HasHistoryChanges => _historyText.HasHistoryChanges; + void Top_AlternateBackwardKeyChanged (object sender, KeyChangedEventArgs e) => KeyBindings.Replace ((KeyCode)e.OldKey, (KeyCode)e.NewKey); - /// - /// Get the for this view. - /// - public ContextMenu? ContextMenu { get; private set; } + void Top_AlternateForwardKeyChanged (object sender, KeyChangedEventArgs e) => KeyBindings.Replace ((KeyCode)e.OldKey, (KeyCode)e.NewKey); - /// - /// If and the current is null - /// will inherit from the previous, otherwise if (default) do nothing. - /// If the text is load with this - /// property is automatically sets to . - /// - public bool InheritsPreviousColorScheme { get; set; } + void ResetPosition () + { + _topRow = _leftColumn = CurrentRow = CurrentColumn = 0; + StopSelecting (); + ResetCursorVisibility (); + } - int GetSelectedLength () => SelectedText.Length; + void WrapTextModel () + { + if (_wordWrap && _wrapManager != null) { + _model = _wrapManager.WrapModel (_frameWidth, + out var nRow, out var nCol, + out var nStartRow, out var nStartCol, + CurrentRow, CurrentColumn, + _selectionStartRow, _selectionStartColumn, + _tabWidth); + CurrentRow = nRow; + CurrentColumn = nCol; + _selectionStartRow = nStartRow; + _selectionStartColumn = nStartCol; + SetNeedsDisplay (); + } + } - CursorVisibility _savedCursorVisibility; + int GetSelectedLength () => SelectedText.Length; void SaveCursorVisibility () { @@ -2242,8 +2433,6 @@ public bool Load (string path) res = _model.LoadFile (path); _historyText.Clear (Text); ResetPosition (); - } catch (Exception) { - throw; } finally { UpdateWrapModel (); SetNeedsDisplay (); @@ -2305,7 +2494,7 @@ public void Load (List> cellsList) public bool CloseFile () { SetWrapModel (); - bool res = _model.CloseFile (); + var res = _model.CloseFile (); ResetPosition (); SetNeedsDisplay (); UpdateWrapModel (); @@ -2313,18 +2502,7 @@ public bool CloseFile () } /// - /// Gets the current cursor row. - /// - public int CurrentRow => _currentRow; - - /// - /// Gets the cursor column. - /// - /// The cursor column. - public int CurrentColumn => _currentColumn; - - /// - /// Positions the cursor on the current row and column + /// Positions the cursor on the current row and column /// public override void PositionCursor () { @@ -2334,36 +2512,35 @@ public override void PositionCursor () return; } - if (_selecting) { + if (Selecting) { // BUGBUG: customized rect aren't supported now because the Redraw isn't using the Intersect method. //var minRow = Math.Min (Math.Max (Math.Min (selectionStartRow, currentRow) - topRow, 0), Frame.Height); //var maxRow = Math.Min (Math.Max (Math.Max (selectionStartRow, currentRow) - topRow, 0), Frame.Height); //SetNeedsDisplay (new Rect (0, minRow, Frame.Width, maxRow)); SetNeedsDisplay (); } - var line = _model.GetLine (_currentRow); - int col = 0; + var line = _model.GetLine (CurrentRow); + var col = 0; if (line.Count > 0) { - for (int idx = _leftColumn; idx < line.Count; idx++) { - if (idx >= _currentColumn) { + for (var idx = _leftColumn; idx < line.Count; idx++) { + if (idx >= CurrentColumn) { break; } - int cols = line [idx].Rune.GetColumns (); + var cols = line [idx].Rune.GetColumns (); if (line [idx].Rune.Value == '\t') { cols += TabWidth + 1; } if (!TextModel.SetCol (ref col, Frame.Width, cols)) { - col = _currentColumn; + col = CurrentColumn; break; } } } - int posX = _currentColumn - _leftColumn; - int posY = _currentRow - _topRow; - if (posX > -1 && col >= posX && posX < Frame.Width - RightOffset - && _topRow <= _currentRow && posY < Frame.Height - BottomOffset) { + var posX = CurrentColumn - _leftColumn; + var posY = CurrentRow - _topRow; + if (posX > -1 && col >= posX && posX < Frame.Width - RightOffset && _topRow <= CurrentRow && posY < Frame.Height - BottomOffset) { ResetCursorVisibility (); - Move (col, _currentRow - _topRow); + Move (col, CurrentRow - _topRow); } else { SaveCursorVisibility (); } @@ -2371,9 +2548,9 @@ public override void PositionCursor () void ClearRegion (int left, int top, int right, int bottom) { - for (int row = top; row < bottom; row++) { + for (var row = top; row < bottom; row++) { Move (left, row); - for (int col = left; col < right; col++) { + for (var col = left; col < right; col++) { AddRune (col, row, (Rune)' '); } } @@ -2390,13 +2567,15 @@ public override Attribute GetNormalColor () } /// - /// Sets the driver to the default color for the control where no text is being rendered. Defaults to . + /// Sets the driver to the default color for the control where no text is being rendered. Defaults to + /// . /// protected virtual void SetNormalColor () => Driver.SetAttribute (GetNormalColor ()); /// /// Sets the to an appropriate color for rendering the given of the - /// current . Override to provide custom coloring by calling + /// current . Override to provide custom coloring by calling + /// /// Defaults to . /// /// The line. @@ -2418,12 +2597,14 @@ protected virtual void OnDrawNormalColor (List line, int idxCol, int i /// /// Sets the to an appropriate color for rendering the given of the - /// current . Override to provide custom coloring by calling + /// current . Override to provide custom coloring by calling + /// /// Defaults to . /// /// The line. /// The col index. - /// /// The row index. + /// /// + /// The row index. protected virtual void OnDrawSelectionColor (List line, int idxCol, int idxRow) { var unwrappedPos = GetUnwrappedPosition (idxRow, idxCol); @@ -2440,12 +2621,14 @@ protected virtual void OnDrawSelectionColor (List line, int idxCol, in /// /// Sets the to an appropriate color for rendering the given of the - /// current . Override to provide custom coloring by calling + /// current . Override to provide custom coloring by calling + /// /// Defaults to . /// /// The line. /// The col index. - /// /// The row index. + /// /// + /// The row index. protected virtual void OnDrawReadOnlyColor (List line, int idxCol, int idxRow) { var unwrappedPos = GetUnwrappedPosition (idxRow, idxCol); @@ -2464,12 +2647,14 @@ protected virtual void OnDrawReadOnlyColor (List line, int idxCol, int /// /// Sets the to an appropriate color for rendering the given of the - /// current . Override to provide custom coloring by calling + /// current . Override to provide custom coloring by calling + /// /// Defaults to . /// /// The line. /// The col index. - /// /// The row index. + /// /// + /// The row index. protected virtual void OnDrawUsedColor (List line, int idxCol, int idxRow) { var unwrappedPos = GetUnwrappedPosition (idxRow, idxCol); @@ -2489,44 +2674,6 @@ static void SetValidUsedColor (ColorScheme colorScheme) => //if ((colorScheme!.HotNormal.Foreground & colorScheme.Focus.Background) == colorScheme.Focus.Foreground) { Driver.SetAttribute (new Attribute (colorScheme.Focus.Background, colorScheme.Focus.Foreground)); - //} else { - //Driver.SetAttribute (new Attribute (colorScheme!.HotNormal.Foreground & colorScheme.Focus.Background, colorScheme.Focus.Foreground)); - //} - bool _isReadOnly = false; - - /// - /// Gets or sets whether the is in read-only mode or not - /// - /// Boolean value(Default false) - public bool ReadOnly { - get => _isReadOnly; - set { - if (value != _isReadOnly) { - _isReadOnly = value; - - SetNeedsDisplay (); - Adjust (); - } - } - } - - CursorVisibility _desiredCursorVisibility = CursorVisibility.Default; - - /// - /// Get / Set the wished cursor when the field is focused - /// - public CursorVisibility DesiredCursorVisibility { - get => _desiredCursorVisibility; - set { - if (HasFocus) { - Application.Driver.SetCursorVisibility (value); - } - - _desiredCursorVisibility = value; - SetNeedsDisplay (); - } - } - /// public override bool OnEnter (View view) { @@ -2547,14 +2694,18 @@ public override bool OnLeave (View view) } // Returns an encoded region start..end (top 32 bits are the row, low32 the column) - void GetEncodedRegionBounds (out long start, out long end, - int? startRow = null, int? startCol = null, int? cRow = null, int? cCol = null) + void GetEncodedRegionBounds (out long start, + out long end, + int? startRow = null, + int? startCol = null, + int? cRow = null, + int? cCol = null) { long selection; long point; if (startRow == null || startCol == null || cRow == null || cCol == null) { selection = (long)(uint)_selectionStartRow << 32 | (uint)_selectionStartColumn; - point = (long)(uint)_currentRow << 32 | (uint)_currentColumn; + point = (long)(uint)CurrentRow << 32 | (uint)CurrentColumn; } else { selection = (long)(uint)startRow << 32 | (uint)startCol; point = (long)(uint)cRow << 32 | (uint)cCol; @@ -2572,7 +2723,7 @@ bool PointInSelection (int col, int row) { long start, end; GetEncodedRegionBounds (out start, out end); - long q = (long)(uint)row << 32 | (uint)col; + var q = (long)(uint)row << 32 | (uint)col; return q >= start && q <= end - 1; } @@ -2587,21 +2738,23 @@ string GetRegion (int? sRow = null, int? sCol = null, int? cRow = null, int? cCo if (start == end) { return string.Empty; } - int startRow = (int)(start >> 32); - int maxrow = (int)(end >> 32); - int startCol = (int)(start & 0xffffffff); - int endCol = (int)(end & 0xffffffff); + var startRow = (int)(start >> 32); + var maxrow = (int)(end >> 32); + var startCol = (int)(start & 0xffffffff); + var endCol = (int)(end & 0xffffffff); var line = model == null ? _model.GetLine (startRow) : model.GetLine (startRow); if (startRow == maxrow) { return StringFromRunes (line.GetRange (startCol, endCol - startCol)); } - string res = StringFromRunes (line.GetRange (startCol, line.Count - startCol)); + var res = StringFromRunes (line.GetRange (startCol, line.Count - startCol)); - for (int row = startRow + 1; row < maxrow; row++) { - res = res + Environment.NewLine + StringFromRunes (model == null - ? _model.GetLine (row) : model.GetLine (row)); + for (var row = startRow + 1; row < maxrow; row++) { + res = res + + Environment.NewLine + + StringFromRunes (model == null + ? _model.GetLine (row) : model.GetLine (row)); } line = model == null ? _model.GetLine (maxrow) : model.GetLine (maxrow); res = res + Environment.NewLine + StringFromRunes (line.GetRange (0, endCol)); @@ -2616,15 +2769,15 @@ void ClearRegion () SetWrapModel (); long start, end; - long currentEncoded = (long)(uint)_currentRow << 32 | (uint)_currentColumn; + var currentEncoded = (long)(uint)CurrentRow << 32 | (uint)CurrentColumn; GetEncodedRegionBounds (out start, out end); - int startRow = (int)(start >> 32); - int maxrow = (int)(end >> 32); - int startCol = (int)(start & 0xffffffff); - int endCol = (int)(end & 0xffffffff); + var startRow = (int)(start >> 32); + var maxrow = (int)(end >> 32); + var startCol = (int)(start & 0xffffffff); + var endCol = (int)(end & 0xffffffff); var line = _model.GetLine (startRow); - _historyText.Add (new List> () { new (line) }, new Point (startCol, startRow)); + _historyText.Add (new List> { new (line) }, new Point (startCol, startRow)); var removedLines = new List> (); @@ -2632,7 +2785,7 @@ void ClearRegion () removedLines.Add (new List (line)); line.RemoveRange (startCol, endCol - startCol); - _currentColumn = startCol; + CurrentColumn = startCol; if (_wordWrap) { SetNeedsDisplay (); } else { @@ -2653,16 +2806,16 @@ void ClearRegion () line.RemoveRange (startCol, line.Count - startCol); var line2 = _model.GetLine (maxrow); line.AddRange (line2.Skip (endCol)); - for (int row = startRow + 1; row <= maxrow; row++) { + for (var row = startRow + 1; row <= maxrow; row++) { removedLines.Add (new List (_model.GetLine (startRow + 1))); _model.RemoveLine (startRow + 1); } if (currentEncoded == end) { - _currentRow -= maxrow - startRow; + CurrentRow -= maxrow - startRow; } - _currentColumn = startCol; + CurrentColumn = startCol; _historyText.Add (new List> (removedLines), CursorPosition, HistoryText.LineStatus.Removed); @@ -2684,8 +2837,8 @@ public void SelectAll () StartSelecting (); _selectionStartColumn = 0; _selectionStartRow = 0; - _currentColumn = _model.GetLine (_model.Count - 1).Count; - _currentRow = _model.Count - 1; + CurrentColumn = _model.GetLine (_model.Count - 1).Count; + CurrentRow = _model.Count - 1; SetNeedsDisplay (); } @@ -2699,8 +2852,12 @@ public void SelectAll () /// The text to replace. /// trueIf is replacing.falseotherwise. /// trueIf the text was found.falseotherwise. - public bool FindNextText (string textToFind, out bool gaveFullTurn, bool matchCase = false, - bool matchWholeWord = false, string? textToReplace = null, bool replace = false) + public bool FindNextText (string textToFind, + out bool gaveFullTurn, + bool matchCase = false, + bool matchWholeWord = false, + string? textToReplace = null, + bool replace = false) { if (_model.Count == 0) { gaveFullTurn = false; @@ -2724,8 +2881,12 @@ public bool FindNextText (string textToFind, out bool gaveFullTurn, bool matchCa /// The text to replace. /// trueIf the text was found.falseotherwise. /// trueIf the text was found.falseotherwise. - public bool FindPreviousText (string textToFind, out bool gaveFullTurn, bool matchCase = false, - bool matchWholeWord = false, string? textToReplace = null, bool replace = false) + public bool FindPreviousText (string textToFind, + out bool gaveFullTurn, + bool matchCase = false, + bool matchWholeWord = false, + string? textToReplace = null, + bool replace = false) { if (_model.Count == 0) { gaveFullTurn = false; @@ -2752,8 +2913,10 @@ public bool FindPreviousText (string textToFind, out bool gaveFullTurn, bool mat /// The match whole word setting. /// The text to replace. /// trueIf the text was found.falseotherwise. - public bool ReplaceAllText (string textToFind, bool matchCase = false, bool matchWholeWord = false, - string? textToReplace = null) + public bool ReplaceAllText (string textToFind, + bool matchCase = false, + bool matchWholeWord = false, + string? textToReplace = null) { if (_isReadOnly || _model.Count == 0) { return false; @@ -2766,25 +2929,28 @@ public bool ReplaceAllText (string textToFind, bool matchCase = false, bool matc return SetFoundText (textToFind, foundPos, textToReplace, false, true); } - bool SetFoundText (string text, (Point current, bool found) foundPos, - string? textToReplace = null, bool replace = false, bool replaceAll = false) + bool SetFoundText (string text, + (Point current, bool found) foundPos, + string? textToReplace = null, + bool replace = false, + bool replaceAll = false) { if (foundPos.found) { StartSelecting (); _selectionStartColumn = foundPos.current.X; _selectionStartRow = foundPos.current.Y; if (!replaceAll) { - _currentColumn = _selectionStartColumn + text.GetRuneCount (); + CurrentColumn = _selectionStartColumn + text.GetRuneCount (); } else { - _currentColumn = _selectionStartColumn + textToReplace!.GetRuneCount (); + CurrentColumn = _selectionStartColumn + textToReplace!.GetRuneCount (); } - _currentRow = foundPos.current.Y; + CurrentRow = foundPos.current.Y; if (!_isReadOnly && replace) { Adjust (); ClearSelectedRegion (); InsertAllText (textToReplace!); StartSelecting (); - _selectionStartColumn = _currentColumn - textToReplace!.GetRuneCount (); + _selectionStartColumn = CurrentColumn - textToReplace!.GetRuneCount (); } else { UpdateWrapModel (); SetNeedsDisplay (); @@ -2802,14 +2968,12 @@ bool SetFoundText (string text, (Point current, bool found) foundPos, void ResetContinuousFind () { if (!_continuousFind) { - int col = _selecting ? _selectionStartColumn : _currentColumn; - int row = _selecting ? _selectionStartRow : _currentRow; + var col = Selecting ? _selectionStartColumn : CurrentColumn; + var row = Selecting ? _selectionStartRow : CurrentRow; _model.ResetContinuousFind (new Point (col, row)); } } - string? _currentCaller; - /// /// Restore from original model. /// @@ -2822,8 +2986,8 @@ void SetWrapModel ([CallerMemberName] string? caller = null) if (_wordWrap) { _currentCaller = caller; - _currentColumn = _wrapManager!.GetModelColFromWrappedLines (_currentRow, _currentColumn); - _currentRow = _wrapManager.GetModelLineFromWrappedLines (_currentRow); + CurrentColumn = _wrapManager!.GetModelColFromWrappedLines (CurrentRow, CurrentColumn); + CurrentRow = _wrapManager.GetModelLineFromWrappedLines (CurrentRow); _selectionStartColumn = _wrapManager.GetModelColFromWrappedLines (_selectionStartRow, _selectionStartColumn); _selectionStartRow = _wrapManager.GetModelLineFromWrappedLines (_selectionStartRow); _model = _wrapManager.Model; @@ -2842,12 +3006,12 @@ void UpdateWrapModel ([CallerMemberName] string? caller = null) if (_wordWrap) { _currentCaller = null; - _wrapManager!.UpdateModel (_model, out int nRow, out int nCol, - out int nStartRow, out int nStartCol, - _currentRow, _currentColumn, + _wrapManager!.UpdateModel (_model, out var nRow, out var nCol, + out var nStartRow, out var nStartCol, + CurrentRow, CurrentColumn, _selectionStartRow, _selectionStartColumn, true); - _currentRow = nRow; - _currentColumn = nCol; + CurrentRow = nRow; + CurrentColumn = nCol; _selectionStartRow = nStartRow; _selectionStartColumn = nStartCol; _wrapNeeded = true; @@ -2864,25 +3028,25 @@ void UpdateWrapModel ([CallerMemberName] string? caller = null) /// public virtual void OnUnwrappedCursorPosition (int? cRow = null, int? cCol = null) { - int? row = cRow == null ? _currentRow : cRow; - int? col = cCol == null ? _currentColumn : cCol; + var row = cRow == null ? CurrentRow : cRow; + var col = cCol == null ? CurrentColumn : cCol; if (cRow == null && cCol == null && _wordWrap) { - row = _wrapManager!.GetModelLineFromWrappedLines (_currentRow); - col = _wrapManager.GetModelColFromWrappedLines (_currentRow, _currentColumn); + row = _wrapManager!.GetModelLineFromWrappedLines (CurrentRow); + col = _wrapManager.GetModelColFromWrappedLines (CurrentRow, CurrentColumn); } UnwrappedCursorPosition?.Invoke (this, new PointEventArgs (new Point ((int)col, (int)row))); } string GetSelectedRegion () { - int cRow = _currentRow; - int cCol = _currentColumn; - int startRow = _selectionStartRow; - int startCol = _selectionStartColumn; + var cRow = CurrentRow; + var cCol = CurrentColumn; + var startRow = _selectionStartRow; + var startCol = _selectionStartColumn; var model = _model; if (_wordWrap) { - cRow = _wrapManager!.GetModelLineFromWrappedLines (_currentRow); - cCol = _wrapManager.GetModelColFromWrappedLines (_currentRow, _currentColumn); + cRow = _wrapManager!.GetModelLineFromWrappedLines (CurrentRow); + cCol = _wrapManager.GetModelColFromWrappedLines (CurrentRow, CurrentColumn); startRow = _wrapManager.GetModelLineFromWrappedLines (_selectionStartRow); startCol = _wrapManager.GetModelColFromWrappedLines (_selectionStartRow, _selectionStartColumn); model = _wrapManager.Model; @@ -2891,8 +3055,6 @@ string GetSelectedRegion () return GetRegion (startRow, startCol, cRow, cCol, model); } - bool _isDrawing = false; - /// public override void OnDrawContent (Rect contentArea) { @@ -2901,22 +3063,21 @@ public override void OnDrawContent (Rect contentArea) SetNormalColor (); var offB = OffSetBackground (); - int right = Frame.Width + offB.width + RightOffset; - int bottom = Frame.Height + offB.height + BottomOffset; - int row = 0; - for (int idxRow = _topRow; idxRow < _model.Count; idxRow++) { + var right = Frame.Width + offB.width + RightOffset; + var bottom = Frame.Height + offB.height + BottomOffset; + var row = 0; + for (var idxRow = _topRow; idxRow < _model.Count; idxRow++) { var line = _model.GetLine (idxRow); - int lineRuneCount = line.Count; - int col = 0; + var lineRuneCount = line.Count; + var col = 0; Move (0, row); - for (int idxCol = _leftColumn; idxCol < lineRuneCount; idxCol++) { + for (var idxCol = _leftColumn; idxCol < lineRuneCount; idxCol++) { var rune = idxCol >= lineRuneCount ? (Rune)' ' : line [idxCol].Rune; - int cols = rune.GetColumns (); - if (idxCol < line.Count && _selecting && PointInSelection (idxCol, idxRow)) { + var cols = rune.GetColumns (); + if (idxCol < line.Count && Selecting && PointInSelection (idxCol, idxRow)) { OnDrawSelectionColor (line, idxCol, idxRow); - } else if (idxCol == _currentColumn && idxRow == _currentRow && !_selecting && !Used - && HasFocus && idxCol < lineRuneCount) { + } else if (idxCol == CurrentColumn && idxRow == CurrentRow && !Selecting && !Used && HasFocus && idxCol < lineRuneCount) { OnDrawUsedColor (line, idxCol, idxRow); } else if (ReadOnly) { OnDrawReadOnlyColor (line, idxCol, idxRow); @@ -2929,7 +3090,7 @@ public override void OnDrawContent (Rect contentArea) if (col + cols > right) { cols = right - col; } - for (int i = 0; i < cols; i++) { + for (var i = 0; i < cols; i++) { if (col + i < right) { AddRune (col + i, row, (Rune)' '); } @@ -2995,7 +3156,7 @@ void ProcessAutocomplete () void GenerateSuggestions () { - var currentLine = this.GetCurrentLine (); + var currentLine = GetCurrentLine (); var cursorPosition = Math.Min (CurrentColumn, currentLine.Count); Autocomplete.Context = new AutocompleteContext (currentLine, cursorPosition, Autocomplete.Context != null ? Autocomplete.Context.Canceled : false); @@ -3003,12 +3164,6 @@ void GenerateSuggestions () Autocomplete.Context); } - /// - public override bool CanFocus { - get => base.CanFocus; - set => base.CanFocus = value; - } - void SetClipboard (string text) { if (text != null) { @@ -3028,7 +3183,7 @@ public void InsertText (string toAdd) foreach (char ch in toAdd) { Key key; try { - key = new Key(ch); + key = new Key (ch); } catch (Exception) { throw new ArgumentException ($"Cannot insert character '{ch}' because it does not map to a Key"); } @@ -3044,214 +3199,209 @@ public void InsertText (string toAdd) } void Insert (RuneCell cell) - { - var line = GetCurrentLine (); - if (Used) { - line.Insert (Math.Min (_currentColumn, line.Count), cell); - } else { - if (_currentColumn < line.Count) { - line.RemoveAt (_currentColumn); - } - line.Insert (Math.Min (_currentColumn, line.Count), cell); - } - int prow = _currentRow - _topRow; - if (!_wrapNeeded) { - // BUGBUG: customized rect aren't supported now because the Redraw isn't using the Intersect method. - //SetNeedsDisplay (new Rect (0, prow, Math.Max (Frame.Width, 0), Math.Max (prow + 1, 0))); - SetNeedsDisplay (); + { + var line = GetCurrentLine (); + if (Used) { + line.Insert (Math.Min (CurrentColumn, line.Count), cell); + } else { + if (CurrentColumn < line.Count) { + line.RemoveAt (CurrentColumn); } + line.Insert (Math.Min (CurrentColumn, line.Count), cell); } + var prow = CurrentRow - _topRow; + if (!_wrapNeeded) { + // BUGBUG: customized rect aren't supported now because the Redraw isn't using the Intersect method. + //SetNeedsDisplay (new Rect (0, prow, Math.Max (Frame.Width, 0), Math.Max (prow + 1, 0))); + SetNeedsDisplay (); + } + } - string StringFromRunes (List cells) - { - if (cells == null) { - throw new ArgumentNullException (nameof (cells)); - } - int size = 0; - foreach (var cell in cells) { - size += cell.Rune.GetEncodingLength (); - } - byte [] encoded = new byte [size]; - int offset = 0; - foreach (var cell in cells) { - offset += cell.Rune.Encode (encoded, offset); - } - return StringExtensions.ToString (encoded); - } - - /// - /// Returns the characters on the current line (where the cursor is positioned). - /// Use to determine the position of the cursor within - /// that line - /// - /// - public List GetCurrentLine () => _model.GetLine (_currentRow); - - /// - /// Returns the characters on the . - /// - /// The intended line. - /// - public List GetLine (int line) => _model.GetLine (line); - - /// - /// Gets all lines of characters. - /// - /// - public List> GetAllLines () => _model.GetAllLines (); - - void InsertAllText (string text) - { - if (string.IsNullOrEmpty (text)) { - return; - } - - var lines = TextModel.StringToLinesOfRuneCells (text); + string StringFromRunes (List cells) + { + if (cells == null) { + throw new ArgumentNullException (nameof (cells)); + } + var size = 0; + foreach (var cell in cells) { + size += cell.Rune.GetEncodingLength (); + } + var encoded = new byte [size]; + var offset = 0; + foreach (var cell in cells) { + offset += cell.Rune.Encode (encoded, offset); + } + return StringExtensions.ToString (encoded); + } - if (lines.Count == 0) { - return; - } + /// + /// Returns the characters on the current line (where the cursor is positioned). + /// Use to determine the position of the cursor within + /// that line + /// + /// + public List GetCurrentLine () => _model.GetLine (CurrentRow); - SetWrapModel (); + /// + /// Returns the characters on the . + /// + /// The intended line. + /// + public List GetLine (int line) => _model.GetLine (line); - var line = GetCurrentLine (); + /// + /// Gets all lines of characters. + /// + /// + public List> GetAllLines () => _model.GetAllLines (); - _historyText.Add (new List> () { new (line) }, CursorPosition); + void InsertAllText (string text) + { + if (string.IsNullOrEmpty (text)) { + return; + } - // Optimize single line - if (lines.Count == 1) { - line.InsertRange (_currentColumn, lines [0]); - _currentColumn += lines [0].Count; + var lines = TextModel.StringToLinesOfRuneCells (text); - _historyText.Add (new List> () { new (line) }, CursorPosition, - HistoryText.LineStatus.Replaced); + if (lines.Count == 0) { + return; + } - if (!_wordWrap && _currentColumn - _leftColumn > Frame.Width) { - _leftColumn = Math.Max (_currentColumn - Frame.Width + 1, 0); - } - if (_wordWrap) { - SetNeedsDisplay (); - } else { - // BUGBUG: customized rect aren't supported now because the Redraw isn't using the Intersect method. - //SetNeedsDisplay (new Rect (0, currentRow - topRow, Frame.Width, Math.Max (currentRow - topRow + 1, 0))); - SetNeedsDisplay (); - } + SetWrapModel (); - UpdateWrapModel (); + var line = GetCurrentLine (); - OnContentsChanged (); + _historyText.Add (new List> { new (line) }, CursorPosition); - return; - } + // Optimize single line + if (lines.Count == 1) { + line.InsertRange (CurrentColumn, lines [0]); + CurrentColumn += lines [0].Count; - List? rest = null; - int lastp = 0; + _historyText.Add (new List> { new (line) }, CursorPosition, + HistoryText.LineStatus.Replaced); - if (_model.Count > 0 && line.Count > 0 && !_copyWithoutSelection) { - // Keep a copy of the rest of the line - int restCount = line.Count - _currentColumn; - rest = line.GetRange (_currentColumn, restCount); - line.RemoveRange (_currentColumn, restCount); + if (!_wordWrap && CurrentColumn - _leftColumn > Frame.Width) { + _leftColumn = Math.Max (CurrentColumn - Frame.Width + 1, 0); + } + if (_wordWrap) { + SetNeedsDisplay (); + } else { + // BUGBUG: customized rect aren't supported now because the Redraw isn't using the Intersect method. + //SetNeedsDisplay (new Rect (0, currentRow - topRow, Frame.Width, Math.Max (currentRow - topRow + 1, 0))); + SetNeedsDisplay (); } - // First line is inserted at the current location, the rest is appended - line.InsertRange (_currentColumn, lines [0]); - //model.AddLine (currentRow, lines [0]); + UpdateWrapModel (); + + OnContentsChanged (); - var addedLines = new List> () { new (line) }; + return; + } - for (int i = 1; i < lines.Count; i++) { - _model.AddLine (_currentRow + i, lines [i]); + List? rest = null; + var lastp = 0; - addedLines.Add (new List (lines [i])); - } + if (_model.Count > 0 && line.Count > 0 && !_copyWithoutSelection) { + // Keep a copy of the rest of the line + var restCount = line.Count - CurrentColumn; + rest = line.GetRange (CurrentColumn, restCount); + line.RemoveRange (CurrentColumn, restCount); + } - if (rest != null) { - var last = _model.GetLine (_currentRow + lines.Count - 1); - lastp = last.Count; - last.InsertRange (last.Count, rest); + // First line is inserted at the current location, the rest is appended + line.InsertRange (CurrentColumn, lines [0]); + //model.AddLine (currentRow, lines [0]); - addedLines.Last ().InsertRange (addedLines.Last ().Count, rest); - } + var addedLines = new List> { new (line) }; - _historyText.Add (addedLines, CursorPosition, HistoryText.LineStatus.Added); + for (var i = 1; i < lines.Count; i++) { + _model.AddLine (CurrentRow + i, lines [i]); - // Now adjust column and row positions - _currentRow += lines.Count - 1; - _currentColumn = rest != null ? lastp : lines [lines.Count - 1].Count; - Adjust (); + addedLines.Add (new List (lines [i])); + } - _historyText.Add (new List> () { new (line) }, CursorPosition, - HistoryText.LineStatus.Replaced); + if (rest != null) { + var last = _model.GetLine (CurrentRow + lines.Count - 1); + lastp = last.Count; + last.InsertRange (last.Count, rest); - UpdateWrapModel (); - OnContentsChanged (); + addedLines.Last ().InsertRange (addedLines.Last ().Count, rest); } - // The column we are tracking, or -1 if we are not tracking any column - int _columnTrack = -1; + _historyText.Add (addedLines, CursorPosition, HistoryText.LineStatus.Added); + + // Now adjust column and row positions + CurrentRow += lines.Count - 1; + CurrentColumn = rest != null ? lastp : lines [lines.Count - 1].Count; + Adjust (); - // Tries to snap the cursor to the tracking column - void TrackColumn () - { - // Now track the column - var line = GetCurrentLine (); - if (line.Count < _columnTrack) { - _currentColumn = line.Count; - } else if (_columnTrack != -1) { - _currentColumn = _columnTrack; - } else if (_currentColumn > line.Count) { - _currentColumn = line.Count; - } - Adjust (); + _historyText.Add (new List> { new (line) }, CursorPosition, + HistoryText.LineStatus.Replaced); + + UpdateWrapModel (); + OnContentsChanged (); + } + + // Tries to snap the cursor to the tracking column + void TrackColumn () + { + // Now track the column + var line = GetCurrentLine (); + if (line.Count < _columnTrack) { + CurrentColumn = line.Count; + } else if (_columnTrack != -1) { + CurrentColumn = _columnTrack; + } else if (CurrentColumn > line.Count) { + CurrentColumn = line.Count; } + Adjust (); + } - void Adjust () - { - var offB = OffSetBackground (); - var line = GetCurrentLine (); - bool need = NeedsDisplay || _wrapNeeded || !Used; - var tSize = TextModel.DisplaySize (line, -1, -1, false, TabWidth); - var dSize = TextModel.DisplaySize (line, _leftColumn, _currentColumn, true, TabWidth); - if (!_wordWrap && _currentColumn < _leftColumn) { - _leftColumn = _currentColumn; - need = true; - } else if (!_wordWrap && (_currentColumn - _leftColumn + RightOffset > Frame.Width + offB.width - || dSize.size + RightOffset >= Frame.Width + offB.width)) { - _leftColumn = TextModel.CalculateLeftColumn (line, _leftColumn, _currentColumn, - Frame.Width + offB.width - RightOffset, TabWidth); + void Adjust () + { + var offB = OffSetBackground (); + var line = GetCurrentLine (); + var need = NeedsDisplay || _wrapNeeded || !Used; + var tSize = TextModel.DisplaySize (line, -1, -1, false, TabWidth); + var dSize = TextModel.DisplaySize (line, _leftColumn, CurrentColumn, true, TabWidth); + if (!_wordWrap && CurrentColumn < _leftColumn) { + _leftColumn = CurrentColumn; + need = true; + } else if (!_wordWrap && (CurrentColumn - _leftColumn + RightOffset > Frame.Width + offB.width || dSize.size + RightOffset >= Frame.Width + offB.width)) { + _leftColumn = TextModel.CalculateLeftColumn (line, _leftColumn, CurrentColumn, + Frame.Width + offB.width - RightOffset, TabWidth); + need = true; + } else if (_wordWrap && _leftColumn > 0 || dSize.size + RightOffset < Frame.Width + offB.width && tSize.size + RightOffset < Frame.Width + offB.width) { + if (_leftColumn > 0) { + _leftColumn = 0; need = true; - } else if (_wordWrap && _leftColumn > 0 || dSize.size + RightOffset < Frame.Width + offB.width - && tSize.size + RightOffset < Frame.Width + offB.width) { - if (_leftColumn > 0) { - _leftColumn = 0; - need = true; - } } + } - if (_currentRow < _topRow) { - _topRow = _currentRow; - need = true; - } else if (_currentRow - _topRow + BottomOffset >= Frame.Height + offB.height) { - _topRow = Math.Min (Math.Max (_currentRow - Frame.Height + 1 + BottomOffset, 0), _currentRow); - need = true; - } else if (_topRow > 0 && _currentRow < _topRow) { - _topRow = Math.Max (_topRow - 1, 0); - need = true; - } - if (need) { - if (_wrapNeeded) { - WrapTextModel (); - _wrapNeeded = false; - } - SetNeedsDisplay (); - } else { - PositionCursor (); + if (CurrentRow < _topRow) { + _topRow = CurrentRow; + need = true; + } else if (CurrentRow - _topRow + BottomOffset >= Frame.Height + offB.height) { + _topRow = Math.Min (Math.Max (CurrentRow - Frame.Height + 1 + BottomOffset, 0), CurrentRow); + need = true; + } else if (_topRow > 0 && CurrentRow < _topRow) { + _topRow = Math.Max (_topRow - 1, 0); + need = true; + } + if (need) { + if (_wrapNeeded) { + WrapTextModel (); + _wrapNeeded = false; } - - OnUnwrappedCursorPosition (); + SetNeedsDisplay (); + } else { + PositionCursor (); } + OnUnwrappedCursorPosition (); + } + /// /// Called when the contents of the TextView change. E.g. when the user types text or deletes text. Raises /// the event. @@ -3283,14 +3433,14 @@ void ProcessInheritsPreviousColorScheme (int row, int col) line = GetLine (row); lineToSet = line; } - int colWithColor = Math.Max (Math.Min (col - 2, line.Count - 1), 0); + var colWithColor = Math.Max (Math.Min (col - 2, line.Count - 1), 0); var cell = line [colWithColor]; - int colWithoutColor = Math.Max (col - 1, 0); + var colWithoutColor = Math.Max (col - 1, 0); if (cell.ColorScheme != null && colWithColor == 0 && lineToSet [colWithoutColor].ColorScheme != null) { - for (int r = row - 1; r > -1; r--) { + for (var r = row - 1; r > -1; r--) { var l = GetLine (r); - for (int c = l.Count - 1; c > -1; c--) { + for (var c = l.Count - 1; c > -1; c--) { if (l [c].ColorScheme == null) { l [c].ColorScheme = cell.ColorScheme; } else { @@ -3299,8 +3449,9 @@ void ProcessInheritsPreviousColorScheme (int row, int col) } } return; - } else if (cell.ColorScheme == null) { - for (int r = row; r > -1; r--) { + } + if (cell.ColorScheme == null) { + for (var r = row; r > -1; r--) { var l = GetLine (r); colWithColor = l.FindLastIndex (colWithColor > -1 ? colWithColor : l.Count - 1, rc => rc.ColorScheme != null); if (colWithColor > -1 && l [colWithColor].ColorScheme != null) { @@ -3309,7 +3460,7 @@ void ProcessInheritsPreviousColorScheme (int row, int col) } } } else { - int cRow = row; + var cRow = row; while (cell.ColorScheme == null) { if ((colWithColor == 0 || cell.ColorScheme == null) && cRow > 0) { line = GetLine (--cRow); @@ -3334,8 +3485,8 @@ void ProcessInheritsPreviousColorScheme (int row, int col) (int width, int height) OffSetBackground () { - int w = 0; - int h = 0; + var w = 0; + var h = 0; if (SuperView?.Frame.Right - Frame.Right < 0) { w = SuperView!.Frame.Right - Frame.Right - 1; } @@ -3347,10 +3498,13 @@ void ProcessInheritsPreviousColorScheme (int row, int col) /// /// Will scroll the to display the specified row at the top if is true or - /// will scroll the to display the specified column at the left if is false. + /// will scroll the to display the specified column at the left if is + /// false. /// - /// Row that should be displayed at the top or Column that should be displayed at the left, - /// if the value is negative it will be reset to zero + /// + /// Row that should be displayed at the top or Column that should be displayed at the left, + /// if the value is negative it will be reset to zero + /// /// If true (default) the is a row, column otherwise. public void ScrollTo (int idx, bool isRow = true) { @@ -3360,16 +3514,12 @@ public void ScrollTo (int idx, bool isRow = true) if (isRow) { _topRow = Math.Max (idx > _model.Count - 1 ? _model.Count - 1 : idx, 0); } else if (!_wordWrap) { - int maxlength = _model.GetMaxVisibleLine (_topRow, _topRow + Frame.Height + RightOffset, TabWidth); + var maxlength = _model.GetMaxVisibleLine (_topRow, _topRow + Frame.Height + RightOffset, TabWidth); _leftColumn = Math.Max (!_wordWrap && idx > maxlength - 1 ? maxlength - 1 : idx, 0); } SetNeedsDisplay (); } - bool _lastWasKill; - bool _wrapNeeded; - bool _shiftSelecting; - /// public override bool? OnInvokingKeyBindings (Key a) { @@ -3463,7 +3613,7 @@ void MoveTopHomeExtend () void MoveTopHome () { ResetAllTrack (); - if (_shiftSelecting && _selecting) { + if (_shiftSelecting && Selecting) { StopSelecting (); } MoveHome (); @@ -3479,7 +3629,7 @@ void MoveBottomEndExtend () void MoveBottomEnd () { ResetAllTrack (); - if (_shiftSelecting && _selecting) { + if (_shiftSelecting && Selecting) { StopSelecting (); } MoveEnd (); @@ -3507,7 +3657,7 @@ void ProcessMoveWordForwardExtend () void ProcessMoveWordForward () { ResetAllTrack (); - if (_shiftSelecting && _selecting) { + if (_shiftSelecting && Selecting) { StopSelecting (); } MoveWordForward (); @@ -3523,7 +3673,7 @@ void ProcessMoveWordBackwardExtend () void ProcessMoveWordBackward () { ResetAllTrack (); - if (_shiftSelecting && _selecting) { + if (_shiftSelecting && Selecting) { StopSelecting (); } MoveWordBackward (); @@ -3544,9 +3694,9 @@ void ProcessCopy () void ToggleSelecting () { ResetColumnTrack (); - _selecting = !_selecting; - _selectionStartColumn = _currentColumn; - _selectionStartRow = _currentRow; + Selecting = !Selecting; + _selectionStartColumn = CurrentColumn; + _selectionStartRow = CurrentRow; } void ProcessPaste () @@ -3568,7 +3718,7 @@ void ProcessMoveEndOfLineExtend () void ProcessMoveEndOfLine () { ResetAllTrack (); - if (_shiftSelecting && _selecting) { + if (_shiftSelecting && Selecting) { StopSelecting (); } MoveEndOfLine (); @@ -3590,7 +3740,7 @@ void ProcessMoveStartOfLineExtend () void ProcessMoveStartOfLine () { ResetAllTrack (); - if (_shiftSelecting && _selecting) { + if (_shiftSelecting && Selecting) { StopSelecting (); } MoveStartOfLine (); @@ -3612,13 +3762,13 @@ void ProcessMoveLeftExtend () bool ProcessMoveLeft () { // if the user presses Left (without any control keys) and they are at the start of the text - if (_currentColumn == 0 && _currentRow == 0) { + if (CurrentColumn == 0 && CurrentRow == 0) { // do not respond (this lets the key press fall through to navigation system - which usually changes focus backward) return false; } ResetAllTrack (); - if (_shiftSelecting && _selecting) { + if (_shiftSelecting && Selecting) { StopSelecting (); } MoveLeft (); @@ -3636,16 +3786,16 @@ bool ProcessMoveRight () { // if the user presses Right (without any control keys) // determine where the last cursor position in the text is - int lastRow = _model.Count - 1; - int lastCol = _model.GetLine (lastRow).Count; + var lastRow = _model.Count - 1; + var lastCol = _model.GetLine (lastRow).Count; // if they are at the very end of all the text do not respond (this lets the key press fall through to navigation system - which usually changes focus forward) - if (_currentColumn == lastCol && _currentRow == lastRow) { + if (CurrentColumn == lastCol && CurrentRow == lastRow) { return false; } ResetAllTrack (); - if (_shiftSelecting && _selecting) { + if (_shiftSelecting && Selecting) { StopSelecting (); } MoveRight (); @@ -3662,7 +3812,7 @@ void ProcessMoveUpExtend () void ProcessMoveUp () { ResetContinuousFindTrack (); - if (_shiftSelecting && _selecting) { + if (_shiftSelecting && Selecting) { StopSelecting (); } MoveUp (); @@ -3678,7 +3828,7 @@ void ProcessMoveDownExtend () void ProcessMoveDown () { ResetContinuousFindTrack (); - if (_shiftSelecting && _selecting) { + if (_shiftSelecting && Selecting) { StopSelecting (); } MoveDown (); @@ -3694,7 +3844,7 @@ void ProcessPageUpExtend () void ProcessPageUp () { ResetColumnTrack (); - if (_shiftSelecting && _selecting) { + if (_shiftSelecting && Selecting) { StopSelecting (); } MovePageUp (); @@ -3710,7 +3860,7 @@ void ProcessPageDownExtend () void ProcessPageDown () { ResetColumnTrack (); - if (_shiftSelecting && _selecting) { + if (_shiftSelecting && Selecting) { StopSelecting (); } MovePageDown (); @@ -3741,18 +3891,18 @@ bool ProcessBackTab () if (!AllowsTab || _isReadOnly) { return ProcessMovePreviousView (); } - if (_currentColumn > 0) { + if (CurrentColumn > 0) { SetWrapModel (); var currentLine = GetCurrentLine (); - if (currentLine.Count > 0 && currentLine [_currentColumn - 1].Rune.Value == '\t') { + if (currentLine.Count > 0 && currentLine [CurrentColumn - 1].Rune.Value == '\t') { - _historyText.Add (new List> () { new List (currentLine) }, CursorPosition); + _historyText.Add (new List> { new (currentLine) }, CursorPosition); - currentLine.RemoveAt (_currentColumn - 1); - _currentColumn--; + currentLine.RemoveAt (CurrentColumn - 1); + CurrentColumn--; - _historyText.Add (new List> () { new List (GetCurrentLine ()) }, CursorPosition, + _historyText.Add (new List> { new (GetCurrentLine ()) }, CursorPosition, HistoryText.LineStatus.Replaced); } @@ -3795,37 +3945,37 @@ bool ProcessReturn () var currentLine = GetCurrentLine (); - _historyText.Add (new List> () { new List (currentLine) }, CursorPosition); + _historyText.Add (new List> { new (currentLine) }, CursorPosition); - if (_selecting) { + if (Selecting) { ClearSelectedRegion (); currentLine = GetCurrentLine (); } - int restCount = currentLine.Count - _currentColumn; - var rest = currentLine.GetRange (_currentColumn, restCount); - currentLine.RemoveRange (_currentColumn, restCount); + var restCount = currentLine.Count - CurrentColumn; + var rest = currentLine.GetRange (CurrentColumn, restCount); + currentLine.RemoveRange (CurrentColumn, restCount); - var addedLines = new List> () { new List (currentLine) }; + var addedLines = new List> { new (currentLine) }; - _model.AddLine (_currentRow + 1, rest); + _model.AddLine (CurrentRow + 1, rest); - addedLines.Add (new List (_model.GetLine (_currentRow + 1))); + addedLines.Add (new List (_model.GetLine (CurrentRow + 1))); _historyText.Add (addedLines, CursorPosition, HistoryText.LineStatus.Added); - _currentRow++; + CurrentRow++; - bool fullNeedsDisplay = false; - if (_currentRow >= _topRow + Frame.Height) { + var fullNeedsDisplay = false; + if (CurrentRow >= _topRow + Frame.Height) { _topRow++; fullNeedsDisplay = true; } - _currentColumn = 0; + CurrentColumn = 0; - _historyText.Add (new List> () { new List (GetCurrentLine ()) }, CursorPosition, + _historyText.Add (new List> { new (GetCurrentLine ()) }, CursorPosition, HistoryText.LineStatus.Replaced); - if (!_wordWrap && _currentColumn < _leftColumn) { + if (!_wordWrap && CurrentColumn < _leftColumn) { fullNeedsDisplay = true; _leftColumn = 0; } @@ -3855,42 +4005,42 @@ void KillWordBackward () var currentLine = GetCurrentLine (); - _historyText.Add (new List> () { new List (GetCurrentLine ()) }, CursorPosition); + _historyText.Add (new List> { new (GetCurrentLine ()) }, CursorPosition); - if (_currentColumn == 0) { + if (CurrentColumn == 0) { DeleteTextBackwards (); - _historyText.ReplaceLast (new List> () { new List (GetCurrentLine ()) }, CursorPosition, + _historyText.ReplaceLast (new List> { new (GetCurrentLine ()) }, CursorPosition, HistoryText.LineStatus.Replaced); UpdateWrapModel (); return; } - var newPos = _model.WordBackward (_currentColumn, _currentRow); - if (newPos.HasValue && _currentRow == newPos.Value.row) { - int restCount = _currentColumn - newPos.Value.col; + var newPos = _model.WordBackward (CurrentColumn, CurrentRow); + if (newPos.HasValue && CurrentRow == newPos.Value.row) { + var restCount = CurrentColumn - newPos.Value.col; currentLine.RemoveRange (newPos.Value.col, restCount); if (_wordWrap) { _wrapNeeded = true; } - _currentColumn = newPos.Value.col; + CurrentColumn = newPos.Value.col; } else if (newPos.HasValue) { - int restCount = currentLine.Count - _currentColumn; - currentLine.RemoveRange (_currentColumn, restCount); + var restCount = currentLine.Count - CurrentColumn; + currentLine.RemoveRange (CurrentColumn, restCount); if (_wordWrap) { _wrapNeeded = true; } - _currentColumn = newPos.Value.col; - _currentRow = newPos.Value.row; + CurrentColumn = newPos.Value.col; + CurrentRow = newPos.Value.row; } - _historyText.Add (new List> () { new List (GetCurrentLine ()) }, CursorPosition, + _historyText.Add (new List> { new (GetCurrentLine ()) }, CursorPosition, HistoryText.LineStatus.Replaced); UpdateWrapModel (); - DoSetNeedsDisplay (new Rect (0, _currentRow - _topRow, Frame.Width, Frame.Height)); + DoSetNeedsDisplay (new Rect (0, CurrentRow - _topRow, Frame.Width, Frame.Height)); DoNeededAction (); } @@ -3904,46 +4054,46 @@ void KillWordForward () var currentLine = GetCurrentLine (); - _historyText.Add (new List> () { new List (GetCurrentLine ()) }, CursorPosition); + _historyText.Add (new List> { new (GetCurrentLine ()) }, CursorPosition); - if (currentLine.Count == 0 || _currentColumn == currentLine.Count) { + if (currentLine.Count == 0 || CurrentColumn == currentLine.Count) { DeleteTextForwards (); - _historyText.ReplaceLast (new List> () { new List (GetCurrentLine ()) }, CursorPosition, + _historyText.ReplaceLast (new List> { new (GetCurrentLine ()) }, CursorPosition, HistoryText.LineStatus.Replaced); UpdateWrapModel (); return; } - var newPos = _model.WordForward (_currentColumn, _currentRow); - int restCount = 0; - if (newPos.HasValue && _currentRow == newPos.Value.row) { - restCount = newPos.Value.col - _currentColumn; - currentLine.RemoveRange (_currentColumn, restCount); + var newPos = _model.WordForward (CurrentColumn, CurrentRow); + var restCount = 0; + if (newPos.HasValue && CurrentRow == newPos.Value.row) { + restCount = newPos.Value.col - CurrentColumn; + currentLine.RemoveRange (CurrentColumn, restCount); } else if (newPos.HasValue) { - restCount = currentLine.Count - _currentColumn; - currentLine.RemoveRange (_currentColumn, restCount); + restCount = currentLine.Count - CurrentColumn; + currentLine.RemoveRange (CurrentColumn, restCount); } if (_wordWrap) { _wrapNeeded = true; } - _historyText.Add (new List> () { new List (GetCurrentLine ()) }, CursorPosition, + _historyText.Add (new List> { new (GetCurrentLine ()) }, CursorPosition, HistoryText.LineStatus.Replaced); UpdateWrapModel (); - DoSetNeedsDisplay (new Rect (0, _currentRow - _topRow, Frame.Width, Frame.Height)); + DoSetNeedsDisplay (new Rect (0, CurrentRow - _topRow, Frame.Width, Frame.Height)); DoNeededAction (); } void MoveWordForward () { - var newPos = _model.WordForward (_currentColumn, _currentRow); + var newPos = _model.WordForward (CurrentColumn, CurrentRow); if (newPos.HasValue) { - _currentColumn = newPos.Value.col; - _currentRow = newPos.Value.row; + CurrentColumn = newPos.Value.col; + CurrentRow = newPos.Value.row; } Adjust (); DoNeededAction (); @@ -3951,10 +4101,10 @@ void MoveWordForward () void MoveWordBackward () { - var newPos = _model.WordBackward (_currentColumn, _currentRow); + var newPos = _model.WordBackward (CurrentColumn, CurrentRow); if (newPos.HasValue) { - _currentColumn = newPos.Value.col; - _currentRow = newPos.Value.row; + CurrentColumn = newPos.Value.col; + CurrentRow = newPos.Value.row; } Adjust (); DoNeededAction (); @@ -3973,22 +4123,22 @@ void KillToStartOfLine () SetWrapModel (); var currentLine = GetCurrentLine (); - bool setLastWasKill = true; - if (currentLine.Count > 0 && _currentColumn == 0) { + var setLastWasKill = true; + if (currentLine.Count > 0 && CurrentColumn == 0) { UpdateWrapModel (); DeleteTextBackwards (); return; } - _historyText.Add (new List> () { new List (currentLine) }, CursorPosition); + _historyText.Add (new List> { new (currentLine) }, CursorPosition); if (currentLine.Count == 0) { - if (_currentRow > 0) { - _model.RemoveLine (_currentRow); + if (CurrentRow > 0) { + _model.RemoveLine (CurrentRow); if (_model.Count > 0 || _lastWasKill) { - string val = Environment.NewLine; + var val = Environment.NewLine; if (_lastWasKill) { AppendClipboard (val); } else { @@ -4000,21 +4150,21 @@ void KillToStartOfLine () setLastWasKill = false; } - _currentRow--; - currentLine = _model.GetLine (_currentRow); + CurrentRow--; + currentLine = _model.GetLine (CurrentRow); - var removedLine = new List> () { new List (currentLine) }; + var removedLine = new List> { new (currentLine) }; removedLine.Add (new List ()); _historyText.Add (new List> (removedLine), CursorPosition, HistoryText.LineStatus.Removed); - _currentColumn = currentLine.Count; + CurrentColumn = currentLine.Count; } } else { - int restCount = _currentColumn; + var restCount = CurrentColumn; var rest = currentLine.GetRange (0, restCount); - string val = string.Empty; + var val = string.Empty; val += StringFromRunes (rest); if (_lastWasKill) { AppendClipboard (val); @@ -4022,15 +4172,15 @@ void KillToStartOfLine () SetClipboard (val); } currentLine.RemoveRange (0, restCount); - _currentColumn = 0; + CurrentColumn = 0; } - _historyText.Add (new List> () { new List (GetCurrentLine ()) }, CursorPosition, + _historyText.Add (new List> { new (GetCurrentLine ()) }, CursorPosition, HistoryText.LineStatus.Replaced); UpdateWrapModel (); - DoSetNeedsDisplay (new Rect (0, _currentRow - _topRow, Frame.Width, Frame.Height)); + DoSetNeedsDisplay (new Rect (0, CurrentRow - _topRow, Frame.Width, Frame.Height)); _lastWasKill = setLastWasKill; DoNeededAction (); @@ -4049,21 +4199,21 @@ void KillToEndOfLine () SetWrapModel (); var currentLine = GetCurrentLine (); - bool setLastWasKill = true; - if (currentLine.Count > 0 && _currentColumn == currentLine.Count) { + var setLastWasKill = true; + if (currentLine.Count > 0 && CurrentColumn == currentLine.Count) { UpdateWrapModel (); DeleteTextForwards (); return; } - _historyText.Add (new List> () { new List (currentLine) }, CursorPosition); + _historyText.Add (new List> { new (currentLine) }, CursorPosition); if (currentLine.Count == 0) { - if (_currentRow < _model.Count - 1) { - var removedLines = new List> () { new List (currentLine) }; + if (CurrentRow < _model.Count - 1) { + var removedLines = new List> { new (currentLine) }; - _model.RemoveLine (_currentRow); + _model.RemoveLine (CurrentRow); removedLines.Add (new List (GetCurrentLine ())); @@ -4071,7 +4221,7 @@ void KillToEndOfLine () HistoryText.LineStatus.Removed); } if (_model.Count > 0 || _lastWasKill) { - string val = Environment.NewLine; + var val = Environment.NewLine; if (_lastWasKill) { AppendClipboard (val); } else { @@ -4083,24 +4233,24 @@ void KillToEndOfLine () setLastWasKill = false; } } else { - int restCount = currentLine.Count - _currentColumn; - var rest = currentLine.GetRange (_currentColumn, restCount); - string val = string.Empty; + var restCount = currentLine.Count - CurrentColumn; + var rest = currentLine.GetRange (CurrentColumn, restCount); + var val = string.Empty; val += StringFromRunes (rest); if (_lastWasKill) { AppendClipboard (val); } else { SetClipboard (val); } - currentLine.RemoveRange (_currentColumn, restCount); + currentLine.RemoveRange (CurrentColumn, restCount); } - _historyText.Add (new List> () { new List (GetCurrentLine ()) }, CursorPosition, + _historyText.Add (new List> { new (GetCurrentLine ()) }, CursorPosition, HistoryText.LineStatus.Replaced); UpdateWrapModel (); - DoSetNeedsDisplay (new Rect (0, _currentRow - _topRow, Frame.Width, Frame.Height)); + DoSetNeedsDisplay (new Rect (0, CurrentRow - _topRow, Frame.Width, Frame.Height)); _lastWasKill = setLastWasKill; DoNeededAction (); @@ -4109,7 +4259,7 @@ void KillToEndOfLine () void MoveEndOfLine () { var currentLine = GetCurrentLine (); - _currentColumn = currentLine.Count; + CurrentColumn = currentLine.Count; Adjust (); DoNeededAction (); } @@ -4119,7 +4269,7 @@ void MoveStartOfLine () if (_leftColumn > 0) { SetNeedsDisplay (); } - _currentColumn = 0; + CurrentColumn = 0; _leftColumn = 0; Adjust (); DoNeededAction (); @@ -4136,15 +4286,14 @@ public void DeleteCharRight () SetWrapModel (); - if (_selecting) { - _historyText.Add (new List> () { new List (GetCurrentLine ()) }, CursorPosition, - HistoryText.LineStatus.Original); + if (Selecting) { + _historyText.Add (new List> { new (GetCurrentLine ()) }, CursorPosition); ClearSelectedRegion (); var currentLine = GetCurrentLine (); - _historyText.Add (new List> () { new List (currentLine) }, CursorPosition, + _historyText.Add (new List> { new (currentLine) }, CursorPosition, HistoryText.LineStatus.Replaced); UpdateWrapModel (); @@ -4176,15 +4325,14 @@ public void DeleteCharLeft () SetWrapModel (); - if (_selecting) { - _historyText.Add (new List> () { new List (GetCurrentLine ()) }, CursorPosition, - HistoryText.LineStatus.Original); + if (Selecting) { + _historyText.Add (new List> { new (GetCurrentLine ()) }, CursorPosition); ClearSelectedRegion (); var currentLine = GetCurrentLine (); - _historyText.Add (new List> () { new List (currentLine) }, CursorPosition, + _historyText.Add (new List> { new (currentLine) }, CursorPosition, HistoryText.LineStatus.Replaced); UpdateWrapModel (); @@ -4207,17 +4355,17 @@ public void DeleteCharLeft () void MoveLeft () { - if (_currentColumn > 0) { - _currentColumn--; + if (CurrentColumn > 0) { + CurrentColumn--; } else { - if (_currentRow > 0) { - _currentRow--; - if (_currentRow < _topRow) { + if (CurrentRow > 0) { + CurrentRow--; + if (CurrentRow < _topRow) { _topRow--; SetNeedsDisplay (); } var currentLine = GetCurrentLine (); - _currentColumn = currentLine.Count; + CurrentColumn = currentLine.Count; } } Adjust (); @@ -4227,13 +4375,13 @@ void MoveLeft () void MoveRight () { var currentLine = GetCurrentLine (); - if (_currentColumn < currentLine.Count) { - _currentColumn++; + if (CurrentColumn < currentLine.Count) { + CurrentColumn++; } else { - if (_currentRow + 1 < _model.Count) { - _currentRow++; - _currentColumn = 0; - if (_currentRow >= _topRow + Frame.Height) { + if (CurrentRow + 1 < _model.Count) { + CurrentRow++; + CurrentColumn = 0; + if (CurrentRow >= _topRow + Frame.Height) { _topRow++; SetNeedsDisplay (); } @@ -4245,13 +4393,13 @@ void MoveRight () void MovePageUp () { - int nPageUpShift = Frame.Height - 1; - if (_currentRow > 0) { + var nPageUpShift = Frame.Height - 1; + if (CurrentRow > 0) { if (_columnTrack == -1) { - _columnTrack = _currentColumn; + _columnTrack = CurrentColumn; } - _currentRow = _currentRow - nPageUpShift < 0 ? 0 : _currentRow - nPageUpShift; - if (_currentRow < _topRow) { + CurrentRow = CurrentRow - nPageUpShift < 0 ? 0 : CurrentRow - nPageUpShift; + if (CurrentRow < _topRow) { _topRow = _topRow - nPageUpShift < 0 ? 0 : _topRow - nPageUpShift; SetNeedsDisplay (); } @@ -4263,16 +4411,16 @@ void MovePageUp () void MovePageDown () { - int nPageDnShift = Frame.Height - 1; - if (_currentRow >= 0 && _currentRow < _model.Count) { + var nPageDnShift = Frame.Height - 1; + if (CurrentRow >= 0 && CurrentRow < _model.Count) { if (_columnTrack == -1) { - _columnTrack = _currentColumn; + _columnTrack = CurrentColumn; } - _currentRow = _currentRow + nPageDnShift > _model.Count + CurrentRow = CurrentRow + nPageDnShift > _model.Count ? _model.Count > 0 ? _model.Count - 1 : 0 - : _currentRow + nPageDnShift; - if (_topRow < _currentRow - nPageDnShift) { - _topRow = _currentRow >= _model.Count ? _currentRow - nPageDnShift : _topRow + nPageDnShift; + : CurrentRow + nPageDnShift; + if (_topRow < CurrentRow - nPageDnShift) { + _topRow = CurrentRow >= _model.Count ? CurrentRow - nPageDnShift : _topRow + nPageDnShift; SetNeedsDisplay (); } TrackColumn (); @@ -4315,32 +4463,32 @@ bool InsertText (Key a, ColorScheme? colorScheme = null) SetWrapModel (); - _historyText.Add (new List> () { new List (GetCurrentLine ()) }, CursorPosition); + _historyText.Add (new List> { new (GetCurrentLine ()) }, CursorPosition); - if (_selecting) { + if (Selecting) { ClearSelectedRegion (); } if ((uint)a.KeyCode == '\n') { - _model.AddLine (_currentRow + 1, new List ()); - _currentRow++; - _currentColumn = 0; + _model.AddLine (CurrentRow + 1, new List ()); + CurrentRow++; + CurrentColumn = 0; } else if ((uint)a.KeyCode == '\r') { - _currentColumn = 0; + CurrentColumn = 0; } else { if (Used) { Insert (new RuneCell { Rune = a.AsRune, ColorScheme = colorScheme }); - _currentColumn++; - if (_currentColumn >= _leftColumn + Frame.Width) { + CurrentColumn++; + if (CurrentColumn >= _leftColumn + Frame.Width) { _leftColumn++; SetNeedsDisplay (); } } else { Insert (new RuneCell { Rune = a.AsRune, ColorScheme = colorScheme }); - _currentColumn++; + CurrentColumn++; } } - _historyText.Add (new List> () { new List (GetCurrentLine ()) }, CursorPosition, + _historyText.Add (new List> { new (GetCurrentLine ()) }, CursorPosition, HistoryText.LineStatus.Replaced); UpdateWrapModel (); @@ -4401,46 +4549,46 @@ bool DeleteTextForwards () SetWrapModel (); var currentLine = GetCurrentLine (); - if (_currentColumn == currentLine.Count) { - if (_currentRow + 1 == _model.Count) { + if (CurrentColumn == currentLine.Count) { + if (CurrentRow + 1 == _model.Count) { UpdateWrapModel (); return true; } - _historyText.Add (new List> () { new List (currentLine) }, CursorPosition); + _historyText.Add (new List> { new (currentLine) }, CursorPosition); - var removedLines = new List> () { new List (currentLine) }; + var removedLines = new List> { new (currentLine) }; - var nextLine = _model.GetLine (_currentRow + 1); + var nextLine = _model.GetLine (CurrentRow + 1); removedLines.Add (new List (nextLine)); _historyText.Add (removedLines, CursorPosition, HistoryText.LineStatus.Removed); currentLine.AddRange (nextLine); - _model.RemoveLine (_currentRow + 1); + _model.RemoveLine (CurrentRow + 1); - _historyText.Add (new List> () { new List (currentLine) }, CursorPosition, + _historyText.Add (new List> { new (currentLine) }, CursorPosition, HistoryText.LineStatus.Replaced); if (_wordWrap) { _wrapNeeded = true; } - DoSetNeedsDisplay (new Rect (0, _currentRow - _topRow, Frame.Width, _currentRow - _topRow + 1)); + DoSetNeedsDisplay (new Rect (0, CurrentRow - _topRow, Frame.Width, CurrentRow - _topRow + 1)); } else { - _historyText.Add (new List> () { new List (currentLine) }, CursorPosition); + _historyText.Add (new List> { new (currentLine) }, CursorPosition); - currentLine.RemoveAt (_currentColumn); + currentLine.RemoveAt (CurrentColumn); - _historyText.Add (new List> () { new List (currentLine) }, CursorPosition, + _historyText.Add (new List> { new (currentLine) }, CursorPosition, HistoryText.LineStatus.Replaced); if (_wordWrap) { _wrapNeeded = true; } - DoSetNeedsDisplay (new Rect (_currentColumn - _leftColumn, _currentRow - _topRow, Frame.Width, _currentRow - _topRow + 1)); + DoSetNeedsDisplay (new Rect (CurrentColumn - _leftColumn, CurrentRow - _topRow, Frame.Width, CurrentRow - _topRow + 1)); } UpdateWrapModel (); @@ -4463,22 +4611,22 @@ bool DeleteTextBackwards () { SetWrapModel (); - if (_currentColumn > 0) { + if (CurrentColumn > 0) { // Delete backwards var currentLine = GetCurrentLine (); - _historyText.Add (new List> () { new List (currentLine) }, CursorPosition); + _historyText.Add (new List> { new (currentLine) }, CursorPosition); - currentLine.RemoveAt (_currentColumn - 1); + currentLine.RemoveAt (CurrentColumn - 1); if (_wordWrap) { _wrapNeeded = true; } - _currentColumn--; + CurrentColumn--; - _historyText.Add (new List> () { new List (currentLine) }, CursorPosition, + _historyText.Add (new List> { new (currentLine) }, CursorPosition, HistoryText.LineStatus.Replaced); - if (_currentColumn < _leftColumn) { + if (CurrentColumn < _leftColumn) { _leftColumn--; SetNeedsDisplay (); } else { @@ -4488,33 +4636,33 @@ bool DeleteTextBackwards () } } else { // Merges the current line with the previous one. - if (_currentRow == 0) { + if (CurrentRow == 0) { return true; } - int prowIdx = _currentRow - 1; + var prowIdx = CurrentRow - 1; var prevRow = _model.GetLine (prowIdx); - _historyText.Add (new List> () { new (prevRow) }, CursorPosition); + _historyText.Add (new List> { new (prevRow) }, CursorPosition); List> removedLines = new () { new List (prevRow) }; removedLines.Add (new List (GetCurrentLine ())); - _historyText.Add (removedLines, new Point (_currentColumn, prowIdx), + _historyText.Add (removedLines, new Point (CurrentColumn, prowIdx), HistoryText.LineStatus.Removed); - int prevCount = prevRow.Count; + var prevCount = prevRow.Count; _model.GetLine (prowIdx).AddRange (GetCurrentLine ()); - _model.RemoveLine (_currentRow); + _model.RemoveLine (CurrentRow); if (_wordWrap) { _wrapNeeded = true; } - _currentRow--; + CurrentRow--; - _historyText.Add (new List> () { GetCurrentLine () }, new Point (_currentColumn, prowIdx), + _historyText.Add (new List> { GetCurrentLine () }, new Point (CurrentColumn, prowIdx), HistoryText.LineStatus.Replaced); - _currentColumn = prevCount; + CurrentColumn = prevCount; SetNeedsDisplay (); } @@ -4523,15 +4671,13 @@ bool DeleteTextBackwards () return false; } - bool _copyWithoutSelection; - /// /// Copy the selected text to the clipboard contents. /// public void Copy () { SetWrapModel (); - if (_selecting) { + if (Selecting) { SetClipboard (GetRegion ()); _copyWithoutSelection = false; } else { @@ -4553,11 +4699,11 @@ public void Cut () if (!_isReadOnly) { ClearRegion (); - _historyText.Add (new List> () { new List (GetCurrentLine ()) }, CursorPosition, + _historyText.Add (new List> { new (GetCurrentLine ()) }, CursorPosition, HistoryText.LineStatus.Replaced); } UpdateWrapModel (); - _selecting = false; + Selecting = false; DoNeededAction (); OnContentsChanged (); } @@ -4572,62 +4718,62 @@ public void Paste () } SetWrapModel (); - string? contents = Clipboard.Contents; + var contents = Clipboard.Contents; if (_copyWithoutSelection && contents.FirstOrDefault (x => x == '\n' || x == '\r') == 0) { var runeList = contents == null ? new List () : TextModel.ToRuneCellList (contents); var currentLine = GetCurrentLine (); - _historyText.Add (new List> () { new List (currentLine) }, CursorPosition); + _historyText.Add (new List> { new (currentLine) }, CursorPosition); var addedLine = new List> { - new List (currentLine), + new (currentLine), runeList }; _historyText.Add (new List> (addedLine), CursorPosition, HistoryText.LineStatus.Added); - _model.AddLine (_currentRow, runeList); - _currentRow++; + _model.AddLine (CurrentRow, runeList); + CurrentRow++; - _historyText.Add (new List> () { new List (GetCurrentLine ()) }, CursorPosition, + _historyText.Add (new List> { new (GetCurrentLine ()) }, CursorPosition, HistoryText.LineStatus.Replaced); SetNeedsDisplay (); OnContentsChanged (); } else { - if (_selecting) { + if (Selecting) { ClearRegion (); } _copyWithoutSelection = false; InsertAllText (contents); - if (_selecting) { - _historyText.ReplaceLast (new List> () { new List (GetCurrentLine ()) }, CursorPosition, + if (Selecting) { + _historyText.ReplaceLast (new List> { new (GetCurrentLine ()) }, CursorPosition, HistoryText.LineStatus.Original); } SetNeedsDisplay (); } UpdateWrapModel (); - _selecting = false; + Selecting = false; DoNeededAction (); } void StartSelecting () { - if (_shiftSelecting && _selecting) { + if (_shiftSelecting && Selecting) { return; } _shiftSelecting = true; - _selecting = true; - _selectionStartColumn = _currentColumn; - _selectionStartRow = _currentRow; + Selecting = true; + _selectionStartColumn = CurrentColumn; + _selectionStartRow = CurrentRow; } void StopSelecting () { _shiftSelecting = false; - _selecting = false; + Selecting = false; _isButtonShift = false; } @@ -4638,18 +4784,18 @@ void ClearSelectedRegion () ClearRegion (); } UpdateWrapModel (); - _selecting = false; + Selecting = false; DoNeededAction (); } void MoveUp () { - if (_currentRow > 0) { + if (CurrentRow > 0) { if (_columnTrack == -1) { - _columnTrack = _currentColumn; + _columnTrack = CurrentColumn; } - _currentRow--; - if (_currentRow < _topRow) { + CurrentRow--; + if (CurrentRow < _topRow) { _topRow--; SetNeedsDisplay (); } @@ -4661,18 +4807,18 @@ void MoveUp () void MoveDown () { - if (_currentRow + 1 < _model.Count) { + if (CurrentRow + 1 < _model.Count) { if (_columnTrack == -1) { - _columnTrack = _currentColumn; + _columnTrack = CurrentColumn; } - _currentRow++; - if (_currentRow + BottomOffset >= _topRow + Frame.Height) { + CurrentRow++; + if (CurrentRow + BottomOffset >= _topRow + Frame.Height) { _topRow++; SetNeedsDisplay (); } TrackColumn (); PositionCursor (); - } else if (_currentRow > Frame.Height) { + } else if (CurrentRow > Frame.Height) { Adjust (); } DoNeededAction (); @@ -4692,7 +4838,7 @@ void MoveDown () } while (row < _model.Count) { - for (int c = col; c < line.Count; c++) { + for (var c = col; c < line.Count; c++) { yield return (c, row, line [c]); } col = 0; @@ -4706,9 +4852,9 @@ void MoveDown () /// public void MoveEnd () { - _currentRow = _model.Count - 1; + CurrentRow = _model.Count - 1; var line = GetCurrentLine (); - _currentColumn = line.Count; + CurrentColumn = line.Count; TrackColumn (); PositionCursor (); } @@ -4718,30 +4864,29 @@ public void MoveEnd () /// public void MoveHome () { - _currentRow = 0; + CurrentRow = 0; _topRow = 0; - _currentColumn = 0; + CurrentColumn = 0; _leftColumn = 0; TrackColumn (); PositionCursor (); SetNeedsDisplay (); } - bool _isButtonShift; - bool _clickWithSelecting; - /// public override bool MouseEvent (MouseEvent ev) { - if (!ev.Flags.HasFlag (MouseFlags.Button1Clicked) && !ev.Flags.HasFlag (MouseFlags.Button1Pressed) - && !ev.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) - && !ev.Flags.HasFlag (MouseFlags.Button1Released) - && !ev.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ButtonShift) - && !ev.Flags.HasFlag (MouseFlags.WheeledDown) && !ev.Flags.HasFlag (MouseFlags.WheeledUp) - && !ev.Flags.HasFlag (MouseFlags.Button1DoubleClicked) - && !ev.Flags.HasFlag (MouseFlags.Button1DoubleClicked | MouseFlags.ButtonShift) - && !ev.Flags.HasFlag (MouseFlags.Button1TripleClicked) - && !ev.Flags.HasFlag (ContextMenu!.MouseFlags)) { + if (!ev.Flags.HasFlag (MouseFlags.Button1Clicked) && + !ev.Flags.HasFlag (MouseFlags.Button1Pressed) && + !ev.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) && + !ev.Flags.HasFlag (MouseFlags.Button1Released) && + !ev.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ButtonShift) && + !ev.Flags.HasFlag (MouseFlags.WheeledDown) && + !ev.Flags.HasFlag (MouseFlags.WheeledUp) && + !ev.Flags.HasFlag (MouseFlags.Button1DoubleClicked) && + !ev.Flags.HasFlag (MouseFlags.Button1DoubleClicked | MouseFlags.ButtonShift) && + !ev.Flags.HasFlag (MouseFlags.Button1TripleClicked) && + !ev.Flags.HasFlag (ContextMenu!.MouseFlags)) { return false; } @@ -4771,41 +4916,39 @@ public override bool MouseEvent (MouseEvent ev) SetNeedsDisplay (); } _lastWasKill = false; - _columnTrack = _currentColumn; + _columnTrack = CurrentColumn; } else if (ev.Flags == MouseFlags.WheeledDown) { _lastWasKill = false; - _columnTrack = _currentColumn; + _columnTrack = CurrentColumn; ScrollTo (_topRow + 1); } else if (ev.Flags == MouseFlags.WheeledUp) { _lastWasKill = false; - _columnTrack = _currentColumn; + _columnTrack = CurrentColumn; ScrollTo (_topRow - 1); } else if (ev.Flags == MouseFlags.WheeledRight) { _lastWasKill = false; - _columnTrack = _currentColumn; + _columnTrack = CurrentColumn; ScrollTo (_leftColumn + 1, false); } else if (ev.Flags == MouseFlags.WheeledLeft) { _lastWasKill = false; - _columnTrack = _currentColumn; + _columnTrack = CurrentColumn; ScrollTo (_leftColumn - 1, false); } else if (ev.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition)) { ProcessMouseClick (ev, out var line); PositionCursor (); - if (_model.Count > 0 && _shiftSelecting && _selecting) { - if (_currentRow - _topRow + BottomOffset >= Frame.Height - 1 - && _model.Count + BottomOffset > _topRow + _currentRow) { + if (_model.Count > 0 && _shiftSelecting && Selecting) { + if (CurrentRow - _topRow + BottomOffset >= Frame.Height - 1 && _model.Count + BottomOffset > _topRow + CurrentRow) { ScrollTo (_topRow + Frame.Height); - } else if (_topRow > 0 && _currentRow <= _topRow) { + } else if (_topRow > 0 && CurrentRow <= _topRow) { ScrollTo (_topRow - Frame.Height); } else if (ev.Y >= Frame.Height) { ScrollTo (_model.Count + BottomOffset); } else if (ev.Y < 0 && _topRow > 0) { ScrollTo (0); } - if (_currentColumn - _leftColumn + RightOffset >= Frame.Width - 1 - && line.Count + RightOffset > _leftColumn + _currentColumn) { + if (CurrentColumn - _leftColumn + RightOffset >= Frame.Width - 1 && line.Count + RightOffset > _leftColumn + CurrentColumn) { ScrollTo (_leftColumn + Frame.Width, false); - } else if (_leftColumn > 0 && _currentColumn <= _leftColumn) { + } else if (_leftColumn > 0 && CurrentColumn <= _leftColumn) { ScrollTo (_leftColumn - Frame.Width, false); } else if (ev.X >= Frame.Width) { ScrollTo (line.Count + RightOffset, false); @@ -4814,7 +4957,7 @@ public override bool MouseEvent (MouseEvent ev) } } _lastWasKill = false; - _columnTrack = _currentColumn; + _columnTrack = CurrentColumn; } else if (ev.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ButtonShift)) { if (!_shiftSelecting) { _isButtonShift = true; @@ -4823,7 +4966,7 @@ public override bool MouseEvent (MouseEvent ev) ProcessMouseClick (ev, out _); PositionCursor (); _lastWasKill = false; - _columnTrack = _currentColumn; + _columnTrack = CurrentColumn; } else if (ev.Flags.HasFlag (MouseFlags.Button1Pressed)) { if (_shiftSelecting) { _clickWithSelecting = true; @@ -4831,11 +4974,11 @@ public override bool MouseEvent (MouseEvent ev) } ProcessMouseClick (ev, out _); PositionCursor (); - if (!_selecting) { + if (!Selecting) { StartSelecting (); } _lastWasKill = false; - _columnTrack = _currentColumn; + _columnTrack = CurrentColumn; if (Application.MouseGrabView == null) { Application.GrabMouse (this); } @@ -4843,45 +4986,44 @@ public override bool MouseEvent (MouseEvent ev) Application.UngrabMouse (); } else if (ev.Flags.HasFlag (MouseFlags.Button1DoubleClicked)) { if (ev.Flags.HasFlag (MouseFlags.ButtonShift)) { - if (!_selecting) { + if (!Selecting) { StartSelecting (); } - } else if (_selecting) { + } else if (Selecting) { StopSelecting (); } ProcessMouseClick (ev, out var line); (int col, int row)? newPos; - if (_currentColumn == line.Count || _currentColumn > 0 && (line [_currentColumn - 1].Rune.Value != ' ' - || line [_currentColumn].Rune.Value == ' ')) { + if (CurrentColumn == line.Count || CurrentColumn > 0 && (line [CurrentColumn - 1].Rune.Value != ' ' || line [CurrentColumn].Rune.Value == ' ')) { - newPos = _model.WordBackward (_currentColumn, _currentRow); + newPos = _model.WordBackward (CurrentColumn, CurrentRow); if (newPos.HasValue) { - _currentColumn = _currentRow == newPos.Value.row ? newPos.Value.col : 0; + CurrentColumn = CurrentRow == newPos.Value.row ? newPos.Value.col : 0; } } - if (!_selecting) { + if (!Selecting) { StartSelecting (); } - newPos = _model.WordForward (_currentColumn, _currentRow); + newPos = _model.WordForward (CurrentColumn, CurrentRow); if (newPos != null && newPos.HasValue) { - _currentColumn = _currentRow == newPos.Value.row ? newPos.Value.col : line.Count; + CurrentColumn = CurrentRow == newPos.Value.row ? newPos.Value.col : line.Count; } PositionCursor (); _lastWasKill = false; - _columnTrack = _currentColumn; + _columnTrack = CurrentColumn; } else if (ev.Flags.HasFlag (MouseFlags.Button1TripleClicked)) { - if (_selecting) { + if (Selecting) { StopSelecting (); } ProcessMouseClick (ev, out var line); - _currentColumn = 0; - if (!_selecting) { + CurrentColumn = 0; + if (!Selecting) { StartSelecting (); } - _currentColumn = line.Count; + CurrentColumn = line.Count; PositionCursor (); _lastWasKill = false; - _columnTrack = _currentColumn; + _columnTrack = CurrentColumn; } else if (ev.Flags == ContextMenu!.MouseFlags) { ContextMenu.Position = new Point (ev.X + 2, ev.Y + 2); ShowContextMenu (); @@ -4894,18 +5036,18 @@ void ProcessMouseClick (MouseEvent ev, out List line) { List? r = null; if (_model.Count > 0) { - int maxCursorPositionableLine = Math.Max (_model.Count - 1 - _topRow, 0); + var maxCursorPositionableLine = Math.Max (_model.Count - 1 - _topRow, 0); if (Math.Max (ev.Y, 0) > maxCursorPositionableLine) { - _currentRow = maxCursorPositionableLine + _topRow; + CurrentRow = maxCursorPositionableLine + _topRow; } else { - _currentRow = Math.Max (ev.Y + _topRow, 0); + CurrentRow = Math.Max (ev.Y + _topRow, 0); } r = GetCurrentLine (); - int idx = TextModel.GetColFromX (r, _leftColumn, Math.Max (ev.X, 0), TabWidth); + var idx = TextModel.GetColFromX (r, _leftColumn, Math.Max (ev.X, 0), TabWidth); if (idx - _leftColumn >= r.Count + RightOffset) { - _currentColumn = Math.Max (r.Count - _leftColumn + RightOffset, 0); + CurrentColumn = Math.Max (r.Count - _leftColumn + RightOffset, 0); } else { - _currentColumn = idx + _leftColumn; + CurrentColumn = idx + _leftColumn; } } diff --git a/Terminal.Gui/Views/Toplevel.cs b/Terminal.Gui/Views/Toplevel.cs index 27ecdde5e1..469f963de8 100644 --- a/Terminal.Gui/Views/Toplevel.cs +++ b/Terminal.Gui/Views/Toplevel.cs @@ -1,88 +1,171 @@ -using System; +using System; using System.Collections.Generic; -using System.ComponentModel; using System.Linq; -namespace Terminal.Gui; +namespace Terminal.Gui; /// -/// Toplevel views can be modally executed. They are used for both an application's main view (filling the entire screeN and -/// for pop-up views such as , , and . +/// Toplevel views are used for both an application's main view (filling the entire screen and +/// for modal (pop-up) views such as , , and +/// ). /// /// -/// -/// Toplevels can be modally executing views, started by calling . -/// They return control to the caller when has -/// been called (which sets the property to false). -/// -/// -/// A Toplevel is created when an application initializes Terminal.Gui by calling . -/// The application Toplevel can be accessed via . Additional Toplevels can be created -/// and run (e.g. s. To run a Toplevel, create the and -/// call . -/// +/// +/// Toplevels can run as modal (popup) views, started by calling +/// . +/// They return control to the caller when has +/// been called (which sets the property to false). +/// +/// +/// A Toplevel is created when an application initializes Terminal.Gui by calling +/// . +/// The application Toplevel can be accessed via . Additional +/// Toplevels can be created +/// and run (e.g. s. To run a Toplevel, create the and +/// call . +/// /// public partial class Toplevel : View { + + /// + /// Initializes a new instance of the class with the specified + /// layout. + /// + /// + /// A Superview-relative rectangle specifying the location and size for the new + /// Toplevel + /// + public Toplevel (Rect frame) : base (frame) => SetInitialProperties (); + + /// + /// Initializes a new instance of the class with + /// layout, defaulting to full screen. The and + /// properties + /// will be set to the dimensions of the terminal using . + /// + public Toplevel () + { + SetInitialProperties (); + Width = Dim.Fill (); + Height = Dim.Fill (); + } + /// - /// Gets or sets whether the main loop for this is running or not. + /// Gets or sets whether the main loop for this is running or not. /// /// - /// Setting this property directly is discouraged. Use instead. + /// Setting this property directly is discouraged. Use + /// instead. /// public bool Running { get; set; } + /// + /// Gets or sets a value indicating whether this can focus. + /// + /// true if can focus; otherwise, false. + public override bool CanFocus => SuperView == null ? true : base.CanFocus; + + /// + /// Determines whether the is modal or not. + /// If set to false (the default): + /// + /// + /// events will propagate keys upwards. + /// + /// + /// The Toplevel will act as an embedded view (not a modal/pop-up). + /// + /// + /// If set to true: + /// + /// + /// events will NOT propagate keys upwards. + /// + /// + /// + /// The Toplevel will and look like a modal (pop-up) (e.g. see + /// . + /// + /// + /// + /// + public bool Modal { get; set; } + + /// + /// Gets or sets the menu for this Toplevel. + /// + public virtual MenuBar MenuBar { get; set; } + + /// + /// Gets or sets the status bar for this Toplevel. + /// + public virtual StatusBar StatusBar { get; set; } + + /// + /// if was already loaded by the + /// , otherwise. + /// + public bool IsLoaded { get; private set; } + /// /// Invoked when the has begun to be loaded. - /// A Loaded event handler is a good place to finalize initialization before calling + /// A Loaded event handler is a good place to finalize initialization before calling /// . /// public event EventHandler Loaded; /// /// Invoked when the main loop has started it's first iteration. - /// Subscribe to this event to perform tasks when the has been laid out and focus has been set. - /// changes. - /// A Ready event handler is a good place to finalize initialization after calling - /// on this . + /// Subscribe to this event to perform tasks when the has been laid out and + /// focus has been set. + /// changes. + /// + /// A Ready event handler is a good place to finalize initialization after calling + /// on this . + /// /// public event EventHandler Ready; /// /// Invoked when the Toplevel has been unloaded. - /// A Unloaded event handler is a good place to dispose objects after calling . + /// A Unloaded event handler is a good place to dispose objects after calling + /// . /// public event EventHandler Unloaded; /// - /// Invoked when the Toplevel becomes the Toplevel. + /// Invoked when the Toplevel becomes the + /// Toplevel. /// public event EventHandler Activate; /// - /// Invoked when the Toplevel ceases to be the Toplevel. + /// Invoked when the Toplevel ceases to be the + /// Toplevel. /// public event EventHandler Deactivate; /// - /// Invoked when a child of the Toplevel is closed by + /// Invoked when a child of the Toplevel is closed by /// . /// public event EventHandler ChildClosed; /// - /// Invoked when the last child of the Toplevel is closed from + /// Invoked when the last child of the Toplevel is closed from /// by . /// public event EventHandler AllChildClosed; /// - /// Invoked when the Toplevel's is being closed by + /// Invoked when the Toplevel's is being closed by /// . /// public event EventHandler Closing; /// - /// Invoked when the Toplevel's is closed by . + /// Invoked when the Toplevel's is closed by + /// . /// public event EventHandler Closed; @@ -131,7 +214,8 @@ internal virtual void OnChildClosed (Toplevel top) internal virtual void OnActivate (Toplevel deactivated) => Activate?.Invoke (this, new ToplevelEventArgs (deactivated)); /// - /// Called from before the redraws for the first time. + /// Called from before the redraws for + /// the first time. /// public virtual void OnLoaded () { @@ -143,7 +227,7 @@ public virtual void OnLoaded () } /// - /// Called from after the has entered the + /// Called from after the has entered the /// first iteration of the loop. /// internal virtual void OnReady () @@ -165,23 +249,6 @@ internal virtual void OnUnloaded () Unloaded?.Invoke (this, EventArgs.Empty); } - /// - /// Initializes a new instance of the class with the specified layout. - /// - /// A Superview-relative rectangle specifying the location and size for the new Toplevel - public Toplevel (Rect frame) : base (frame) => SetInitialProperties (); - - /// - /// Initializes a new instance of the class with layout, - /// defaulting to full screen. - /// - public Toplevel () : base () - { - SetInitialProperties (); - Width = Dim.Fill (); - Height = Dim.Fill (); - } - void SetInitialProperties () { ColorScheme = Colors.TopLevel; @@ -251,20 +318,6 @@ void SetInitialProperties () #endif } - void Application_UnGrabbingMouse (object sender, GrabMouseEventArgs e) - { - if (Application.MouseGrabView == this && _dragPosition.HasValue) { - e.Cancel = true; - } - } - - void Application_GrabbingMouse (object sender, GrabMouseEventArgs e) - { - if (Application.MouseGrabView == this && _dragPosition.HasValue) { - e.Cancel = true; - } - } - /// /// Invoked when the is changed. /// @@ -310,60 +363,6 @@ public virtual void OnQuitKeyChanged (KeyChangedEventArgs e) QuitKeyChanged?.Invoke (this, e); } - /// - /// Convenience factory method that creates a new Toplevel with the current terminal dimensions. - /// - /// The created Toplevel. - public static Toplevel Create () => new Toplevel (new Rect (0, 0, Driver.Cols, Driver.Rows)); - - /// - /// Gets or sets a value indicating whether this can focus. - /// - /// true if can focus; otherwise, false. - public override bool CanFocus => SuperView == null ? true : base.CanFocus; - - /// - /// Determines whether the is modal or not. - /// If set to false (the default): - /// - /// - /// - /// events will propagate keys upwards. - /// - /// - /// The Toplevel will act as an embedded view (not a modal/pop-up). - /// - /// - /// - /// If set to true: - /// - /// - /// - /// events will NOT propagate keys upwards. - /// - /// - /// The Toplevel will and look like a modal (pop-up) (e.g. see . - /// - /// - /// - public bool Modal { get; set; } - - /// - /// Gets or sets the menu for this Toplevel. - /// - public virtual MenuBar MenuBar { get; set; } - - /// - /// Gets or sets the status bar for this Toplevel. - /// - public virtual StatusBar StatusBar { get; set; } - - /// - /// if was already loaded by the - /// , otherwise. - /// - public bool IsLoaded { get; private set; } - void MovePreviousViewOrTop () { if (Application.OverlappedTop == null) { @@ -647,9 +646,10 @@ public virtual void PositionToplevel (Toplevel top) if (superView.Margin != null && superView == top.SuperView) { maxWidth -= superView.GetFramesThickness ().Left + superView.GetFramesThickness ().Right; } - if ((superView != top || top?.SuperView != null || top != Application.Top && top.Modal - || top?.SuperView == null && top.IsOverlapped) - && (top.Frame.X + top.Frame.Width > maxWidth || ny > top.Frame.Y) && top.LayoutStyle == LayoutStyle.Computed) { + if ((superView != top || top?.SuperView != null || top != Application.Top && top.Modal || top?.SuperView == null && top.IsOverlapped) + // BUGBUG: Prevously PositionToplevel required LayotuStyle.Computed + && + (top.Frame.X + top.Frame.Width > maxWidth || ny > top.Frame.Y) /*&& top.LayoutStyle == LayoutStyle.Computed*/) { if ((top.X == null || top.X is Pos.PosAbsolute) && top.Frame.X != nx) { top.X = nx; @@ -662,8 +662,7 @@ public virtual void PositionToplevel (Toplevel top) } // TODO: v2 - This is a hack to get the StatusBar to be positioned correctly. - if (sb != null && !top.Subviews.Contains (sb) && ny + top.Frame.Height != superView.Frame.Height - (sb.Visible ? 1 : 0) - && top.Height is Dim.DimFill && -top.Height.Anchor (0) < 1) { + if (sb != null && !top.Subviews.Contains (sb) && ny + top.Frame.Height != superView.Frame.Height - (sb.Visible ? 1 : 0) && top.Height is Dim.DimFill && -top.Height.Anchor (0) < 1) { top.Height = Dim.Fill (sb.Visible ? 1 : 0); layoutSubviews = true; @@ -734,6 +733,20 @@ bool OutsideTopFrame (Toplevel top) internal static Point? _dragPosition; Point _startGrabPoint; + void Application_UnGrabbingMouse (object sender, GrabMouseEventArgs e) + { + if (Application.MouseGrabView == this && _dragPosition.HasValue) { + e.Cancel = true; + } + } + + void Application_GrabbingMouse (object sender, GrabMouseEventArgs e) + { + if (Application.MouseGrabView == this && _dragPosition.HasValue) { + e.Cancel = true; + } + } + /// public override bool MouseEvent (MouseEvent mouseEvent) { @@ -910,9 +923,8 @@ public bool Equals (Toplevel x, Toplevel y) return false; } else if (x.Id == y.Id) { return true; - } else { - return false; } + return false; } /// Returns a hash code for the specified object. @@ -949,12 +961,13 @@ public int Compare (Toplevel x, Toplevel y) { if (ReferenceEquals (x, y)) { return 0; - } else if (x == null) { + } + if (x == null) { return -1; - } else if (y == null) { + } + if (y == null) { return 1; - } else { - return string.Compare (x.Id, y.Id); } + return string.Compare (x.Id, y.Id); } } \ No newline at end of file diff --git a/Terminal.Gui/Views/ToplevelOverlapped.cs b/Terminal.Gui/Views/ToplevelOverlapped.cs index e1e56e4de0..4593f7bed0 100644 --- a/Terminal.Gui/Views/ToplevelOverlapped.cs +++ b/Terminal.Gui/Views/ToplevelOverlapped.cs @@ -1,226 +1,180 @@ using System; using System.Collections.Generic; -using System.ComponentModel; using System.Linq; -namespace Terminal.Gui { - public partial class Toplevel { - /// - /// Gets or sets if this Toplevel is a container for overlapped children. - /// - public bool IsOverlappedContainer { get; set; } - - /// - /// Gets or sets if this Toplevel is in overlapped mode within a Toplevel container. - /// - public bool IsOverlapped { - get { - return Application.OverlappedTop != null && Application.OverlappedTop != this && !Modal; - } - } - - } +namespace Terminal.Gui; - public static partial class Application { - - /// - /// Gets the list of the Overlapped children which are not modal from the . - /// - public static List OverlappedChildren { - get { - if (OverlappedTop != null) { - List _overlappedChildren = new List (); - foreach (var top in _topLevels) { - if (top != OverlappedTop && !top.Modal) { - _overlappedChildren.Add (top); - } - } - return _overlappedChildren; - } - return null; - } - } - - /// - /// The object used for the application on startup which is true. - /// - public static Toplevel OverlappedTop { - get { - if (Top.IsOverlappedContainer) { - return Top; - } - return null; - } - } - - - static View FindDeepestOverlappedView (View start, int x, int y, out int resx, out int resy) - { - if (start.GetType ().BaseType != typeof (Toplevel) - && !((Toplevel)start).IsOverlappedContainer) { - resx = 0; - resy = 0; - return null; - } +public partial class Toplevel { + /// + /// Gets or sets if this Toplevel is a container for overlapped children. + /// + public bool IsOverlappedContainer { get; set; } - var startFrame = start.Frame; - - if (!startFrame.Contains (x, y)) { - resx = 0; - resy = 0; - return null; - } + /// + /// Gets or sets if this Toplevel is in overlapped mode within a Toplevel container. + /// + public bool IsOverlapped => Application.OverlappedTop != null && Application.OverlappedTop != this && !Modal; +} - int count = _topLevels.Count; - for (int i = count - 1; i >= 0; i--) { +public static partial class Application { + /// + /// Gets the list of the Overlapped children which are not modal from the + /// . + /// + public static List OverlappedChildren { + get { + if (OverlappedTop != null) { + var _overlappedChildren = new List (); foreach (var top in _topLevels) { - var rx = x - startFrame.X; - var ry = y - startFrame.Y; - if (top.Visible && top.Frame.Contains (rx, ry)) { - var deep = View.FindDeepestView (top, rx, ry, out resx, out resy); - if (deep == null) - return FindDeepestOverlappedView (top, rx, ry, out resx, out resy); - if (deep != OverlappedTop) - return deep; + if (top != OverlappedTop && !top.Modal) { + _overlappedChildren.Add (top); } } + return _overlappedChildren; } - resx = x - startFrame.X; - resy = y - startFrame.Y; - return start; + return null; } + } - static bool OverlappedChildNeedsDisplay () - { - if (OverlappedTop == null) { - return false; + /// + /// The object used for the application on startup which + /// is true. + /// + public static Toplevel OverlappedTop { + get { + if (Top.IsOverlappedContainer) { + return Top; } + return null; + } + } - foreach (var top in _topLevels) { - if (top != Current && top.Visible && (top.NeedsDisplay || top.SubViewNeedsDisplay || top.LayoutNeeded)) { - OverlappedTop.SetSubViewNeedsDisplay (); - return true; - } - } + static bool OverlappedChildNeedsDisplay () + { + if (OverlappedTop == null) { return false; } - - static bool SetCurrentOverlappedAsTop () - { - if (OverlappedTop == null && Current != Top && Current?.SuperView == null && Current?.Modal == false) { - if (Current.Frame != new Rect (0, 0, Driver.Cols, Driver.Rows)) { - Current.Frame = new Rect (0, 0, Driver.Cols, Driver.Rows); - } - Top = Current; + foreach (var top in _topLevels) { + if (top != Current && top.Visible && (top.NeedsDisplay || top.SubViewNeedsDisplay || top.LayoutNeeded)) { + OverlappedTop.SetSubViewNeedsDisplay (); return true; } - return false; } + return false; + } - /// - /// Move to the next Overlapped child from the . - /// - public static void OverlappedMoveNext () - { - if (OverlappedTop != null && !Current.Modal) { - lock (_topLevels) { - _topLevels.MoveNext (); - var isOverlapped = false; - while (_topLevels.Peek () == OverlappedTop || !_topLevels.Peek ().Visible) { - if (!isOverlapped && _topLevels.Peek () == OverlappedTop) { - isOverlapped = true; - } else if (isOverlapped && _topLevels.Peek () == OverlappedTop) { - MoveCurrent (Top); - break; - } - _topLevels.MoveNext (); + + static bool SetCurrentOverlappedAsTop () + { + if (OverlappedTop == null && Current != Top && Current?.SuperView == null && Current?.Modal == false) { + Top = Current; + return true; + } + return false; + } + + /// + /// Move to the next Overlapped child from the . + /// + public static void OverlappedMoveNext () + { + if (OverlappedTop != null && !Current.Modal) { + lock (_topLevels) { + _topLevels.MoveNext (); + var isOverlapped = false; + while (_topLevels.Peek () == OverlappedTop || !_topLevels.Peek ().Visible) { + if (!isOverlapped && _topLevels.Peek () == OverlappedTop) { + isOverlapped = true; + } else if (isOverlapped && _topLevels.Peek () == OverlappedTop) { + MoveCurrent (Top); + break; } - Current = _topLevels.Peek (); + _topLevels.MoveNext (); } + Current = _topLevels.Peek (); } } + } - /// - /// Move to the previous Overlapped child from the . - /// - public static void OverlappedMovePrevious () - { - if (OverlappedTop != null && !Current.Modal) { - lock (_topLevels) { - _topLevels.MovePrevious (); - var isOverlapped = false; - while (_topLevels.Peek () == OverlappedTop || !_topLevels.Peek ().Visible) { - if (!isOverlapped && _topLevels.Peek () == OverlappedTop) { - isOverlapped = true; - } else if (isOverlapped && _topLevels.Peek () == OverlappedTop) { - MoveCurrent (Top); - break; - } - _topLevels.MovePrevious (); + /// + /// Move to the previous Overlapped child from the . + /// + public static void OverlappedMovePrevious () + { + if (OverlappedTop != null && !Current.Modal) { + lock (_topLevels) { + _topLevels.MovePrevious (); + var isOverlapped = false; + while (_topLevels.Peek () == OverlappedTop || !_topLevels.Peek ().Visible) { + if (!isOverlapped && _topLevels.Peek () == OverlappedTop) { + isOverlapped = true; + } else if (isOverlapped && _topLevels.Peek () == OverlappedTop) { + MoveCurrent (Top); + break; } - Current = _topLevels.Peek (); + _topLevels.MovePrevious (); } + Current = _topLevels.Peek (); } } + } - /// - /// Move to the next Overlapped child from the and set it as the if it is not already. - /// - /// - /// - public static bool MoveToOverlappedChild (Toplevel top) - { - if (top.Visible && OverlappedTop != null && Current?.Modal == false) { - lock (_topLevels) { - _topLevels.MoveTo (top, 0, new ToplevelEqualityComparer ()); - Current = top; - } - return true; - } - return false; + /// + /// Move to the next Overlapped child from the and set it as the + /// if it is not already. + /// + /// + /// + public static bool MoveToOverlappedChild (Toplevel top) + { + if (top.Visible && OverlappedTop != null && Current?.Modal == false) { + lock (_topLevels) { + _topLevels.MoveTo (top, 0, new ToplevelEqualityComparer ()); + Current = top; + } + return true; } + return false; + } - /// - /// Brings the superview of the most focused overlapped view is on front. - /// - public static void BringOverlappedTopToFront () - { - if (OverlappedTop != null) { - return; - } - var top = FindTopFromView (Top?.MostFocused); - if (top != null && Top.Subviews.Count > 1 && Top.Subviews [Top.Subviews.Count - 1] != top) { - Top.BringSubviewToFront (top); - } + /// + /// Brings the superview of the most focused overlapped view is on front. + /// + public static void BringOverlappedTopToFront () + { + if (OverlappedTop != null) { + return; } + var top = FindTopFromView (Top?.MostFocused); + if (top != null && Top.Subviews.Count > 1 && Top.Subviews [Top.Subviews.Count - 1] != top) { + Top.BringSubviewToFront (top); + } + } - /// - /// Gets the current visible Toplevel overlapped child that matches the arguments pattern. - /// - /// The type. - /// The strings to exclude. - /// The matched view. - public static Toplevel GetTopOverlappedChild (Type type = null, string [] exclude = null) - { - if (Application.OverlappedTop == null) { - return null; - } + /// + /// Gets the current visible Toplevel overlapped child that matches the arguments pattern. + /// + /// The type. + /// The strings to exclude. + /// The matched view. + public static Toplevel GetTopOverlappedChild (Type type = null, string [] exclude = null) + { + if (OverlappedTop == null) { + return null; + } - foreach (var top in Application.OverlappedChildren) { - if (type != null && top.GetType () == type - && exclude?.Contains (top.Data.ToString ()) == false) { - return top; - } else if ((type != null && top.GetType () != type) - || (exclude?.Contains (top.Data.ToString ()) == true)) { - continue; - } + foreach (var top in OverlappedChildren) { + if (type != null && top.GetType () == type && exclude?.Contains (top.Data.ToString ()) == false) { return top; } - return null; + if (type != null && top.GetType () != type || exclude?.Contains (top.Data.ToString ()) == true) { + continue; + } + return top; } - + return null; } -} +} \ No newline at end of file diff --git a/Terminal.Gui/Views/Window.cs b/Terminal.Gui/Views/Window.cs index 586620ffd9..50eaa8e521 100644 --- a/Terminal.Gui/Views/Window.cs +++ b/Terminal.Gui/Views/Window.cs @@ -44,7 +44,8 @@ public class Window : Toplevel { /// This property can be set in a Theme to change the default for all /// s. /// - [SerializableConfigurationProperty (Scope = typeof (ThemeScope))] [JsonConverter (typeof (JsonStringEnumConverter))] + [SerializableConfigurationProperty (Scope = typeof (ThemeScope))] + [JsonConverter (typeof (JsonStringEnumConverter))] public static LineStyle DefaultBorderStyle { get; set; } = LineStyle.Single; void SetInitialProperties () diff --git a/UICatalog/Scenario.cs b/UICatalog/Scenario.cs index 43b171a58a..16910498f3 100644 --- a/UICatalog/Scenario.cs +++ b/UICatalog/Scenario.cs @@ -1,271 +1,296 @@ -using System.Text; -using System; +using System; using System.Collections.Generic; using System.Linq; using Terminal.Gui; -namespace UICatalog { +namespace UICatalog; + +/// +/// Base class for each demo/scenario. +/// +/// To define a new scenario: +/// +/// +/// +/// Create a new .cs file in the Scenarios directory that derives from +/// . +/// +/// +/// +/// +/// Annotate the derived class with a +/// attribute specifying the scenario's name and +/// description. +/// +/// +/// +/// +/// Add one or more attributes to the class specifying +/// which categories the scenario belongs to. If you don't specify a category the scenario will +/// show up in "_All". +/// +/// +/// +/// +/// Implement the override which will be called when a user +/// selects the scenario to run. +/// +/// +/// +/// +/// Optionally, implement the and/or overrides +/// to provide a custom implementation. +/// +/// +/// +/// +/// +/// The UI Catalog program uses reflection to find all scenarios and adds them to the +/// ListViews. Press ENTER to run the selected scenario. Press the default quit key to quit. / +/// +/// +/// +/// The example below is provided in the `Scenarios` directory as a generic sample that can be copied and re-named: +/// +/// using Terminal.Gui; +/// +/// namespace UICatalog { +/// [ScenarioMetadata (Name: "Generic", Description: "Generic sample - A template for creating new Scenarios")] +/// [ScenarioCategory ("Controls")] +/// class MyScenario : Scenario { +/// public override void Setup () +/// { +/// // Put your scenario code here, e.g. +/// Win.Add (new Button ("Press me!") { +/// X = Pos.Center (), +/// Y = Pos.Center (), +/// Clicked = () => MessageBox.Query (20, 7, "Hi", "Neat?", "Yes", "No") +/// }); +/// } +/// } +/// } +/// +/// +public class Scenario : IDisposable { + + static int _maxScenarioNameLen = 30; + bool _disposedValue; + + public string Theme = "Default"; + public string TopLevelColorScheme = "Base"; + /// - /// Base class for each demo/scenario. - /// - /// To define a new scenario: - /// - /// Create a new .cs file in the Scenarios directory that derives from . - /// Annotate the derived class with a attribute specifying the scenario's name and description. - /// Add one or more attributes to the class specifying which categories the scenario belongs to. If you don't specify a category the scenario will show up in "_All". - /// Implement the override which will be called when a user selects the scenario to run. - /// Optionally, implement the and/or overrides to provide a custom implementation. - /// - /// - /// - /// The UI Catalog program uses reflection to find all scenarios and adds them to the - /// ListViews. Press ENTER to run the selected scenario. Press the default quit key to quit. / - /// + /// The Window for the . This should be set to in most + /// cases. /// - /// - /// The example below is provided in the `Scenarios` directory as a generic sample that can be copied and re-named: - /// - /// using Terminal.Gui; - /// - /// namespace UICatalog { - /// [ScenarioMetadata (Name: "Generic", Description: "Generic sample - A template for creating new Scenarios")] - /// [ScenarioCategory ("Controls")] - /// class MyScenario : Scenario { - /// public override void Setup () - /// { - /// // Put your scenario code here, e.g. - /// Win.Add (new Button ("Press me!") { - /// X = Pos.Center (), - /// Y = Pos.Center (), - /// Clicked = () => MessageBox.Query (20, 7, "Hi", "Neat?", "Yes", "No") - /// }); - /// } - /// } - /// } - /// - /// - public class Scenario : IDisposable { - private bool _disposedValue; - - public string Theme = "Default"; - public string TopLevelColorScheme = "Base"; + public Window Win { get; set; } - /// - /// The Window for the . This should be set to in most cases. - /// - public Window Win { get; set; } + public void Dispose () + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose (disposing: true); + GC.SuppressFinalize (this); + } - /// - /// Helper that provides the default implementation with a frame and - /// label showing the name of the and logic to exit back to - /// the Scenario picker UI. - /// Override to provide any behavior needed. - /// - /// - /// - /// The base implementation calls and creates a for - /// and adds it to . - /// - /// - /// Overrides that do not call the base., must call - /// before creating any views or calling other Terminal.Gui APIs. - /// - /// - public virtual void Init () - { - Application.Init (); - - ConfigurationManager.Themes.Theme = Theme; - ConfigurationManager.Apply (); - - Win = new Window () { - Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}", - X = 0, - Y = 0, - Width = Dim.Fill (), - Height = Dim.Fill (), - ColorScheme = Colors.ColorSchemes [TopLevelColorScheme], - }; - Application.Top.Add (Win); + /// + /// Helper that provides the default implementation with a frame and + /// label showing the name of the and logic to exit back to + /// the Scenario picker UI. + /// Override to provide any behavior needed. + /// + /// + /// + /// The base implementation calls and creates a for + /// + /// and adds it to . + /// + /// + /// Overrides that do not call the base., must call + /// before creating any views or calling other Terminal.Gui APIs. + /// + /// + public virtual void Init () + { + Application.Init (); + + ConfigurationManager.Themes.Theme = Theme; + ConfigurationManager.Apply (); + + Win = new Window { + Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}", + X = 0, + Y = 0, + Width = Dim.Fill (), + Height = Dim.Fill (), + ColorScheme = Colors.ColorSchemes [TopLevelColorScheme] + }; + Application.Top.Add (Win); + } + + /// + /// Helper to get the Name (defined in ) + /// + /// + public string GetName () => ScenarioMetadata.GetName (GetType ()); + + /// + /// Helper to get the Description (defined in ) + /// + /// + public string GetDescription () => ScenarioMetadata.GetDescription (GetType ()); + + /// + /// Helper function to get the list of categories a belongs to (defined in + /// ) + /// + /// list of category names + public List GetCategories () => ScenarioCategory.GetCategories (GetType ()); + + /// + /// Gets the Scenario Name + Description with the Description padded + /// based on the longest known Scenario name. + /// + /// + public override string ToString () => $"{GetName ().PadRight (_maxScenarioNameLen)}{GetDescription ()}"; + + /// + /// Override this to implement the setup logic (create controls, etc...). + /// + /// This is typically the best place to put scenario logic code. + public virtual void Setup () { } + + /// + /// Runs the . Override to start the + /// using a different than `Top`. + /// + /// + /// Overrides that do not call the base., must call before returning. + /// + public virtual void Run () => + // Must explicit call Application.Shutdown method to shutdown. + Application.Run (Application.Top); + + /// + /// Stops the scenario. Override to change shutdown behavior for the . + /// + public virtual void RequestStop () => Application.RequestStop (); + + /// + /// Returns a list of all Categories set by all of the s defined in the project. + /// + internal static List GetAllCategories () + { + var categories = new List (); + foreach (var type in typeof (Scenario).Assembly.GetTypes () + .Where (myType => myType.IsClass && !myType.IsAbstract && myType.IsSubclassOf (typeof (Scenario)))) { + var attrs = System.Attribute.GetCustomAttributes (type).ToList (); + categories = categories.Union (attrs.Where (a => a is ScenarioCategory).Select (a => ((ScenarioCategory)a).Name)).ToList (); } - /// - /// Defines the metadata (Name and Description) for a - /// - [System.AttributeUsage (System.AttributeTargets.Class)] - public class ScenarioMetadata : System.Attribute { - /// - /// Name - /// - public string Name { get; set; } - - /// - /// Description - /// - public string Description { get; set; } - - public ScenarioMetadata (string Name, string Description) - { - this.Name = Name; - this.Description = Description; + // Sort + categories = categories.OrderBy (c => c).ToList (); + + // Put "All" at the top + categories.Insert (0, "All Scenarios"); + return categories; + } + + /// + /// Returns a list of all instanaces defined in the project, sorted by + /// . + /// https://stackoverflow.com/questions/5411694/get-all-inherited-classes-of-an-abstract-class + /// + public static List GetScenarios () + { + var objects = new List (); + foreach (var type in typeof (Scenario).Assembly.ExportedTypes + .Where (myType => myType.IsClass && !myType.IsAbstract && myType.IsSubclassOf (typeof (Scenario)))) { + var scenario = (Scenario)Activator.CreateInstance (type); + objects.Add (scenario); + _maxScenarioNameLen = Math.Max (_maxScenarioNameLen, scenario.GetName ().Length + 1); + } + return objects.OrderBy (s => s.GetName ()).ToList (); + } + + protected virtual void Dispose (bool disposing) + { + if (!_disposedValue) { + if (disposing) { + // TODO: dispose managed state (managed objects) } - /// - /// Static helper function to get the Name given a Type - /// - /// - /// - public static string GetName (Type t) => ((ScenarioMetadata)System.Attribute.GetCustomAttributes (t) [0]).Name; - - /// - /// Static helper function to get the Description given a Type - /// - /// - /// - public static string GetDescription (Type t) => ((ScenarioMetadata)System.Attribute.GetCustomAttributes (t) [0]).Description; + // TODO: free unmanaged resources (unmanaged objects) and override finalizer + // TODO: set large fields to null + _disposedValue = true; } + } - /// - /// Helper to get the Name (defined in ) - /// - /// - public string GetName () => ScenarioMetadata.GetName (this.GetType ()); + /// + /// Defines the metadata (Name and Description) for a + /// + [AttributeUsage (AttributeTargets.Class)] + public class ScenarioMetadata : System.Attribute { - /// - /// Helper to get the Description (defined in ) - /// - /// - public string GetDescription () => ScenarioMetadata.GetDescription (this.GetType ()); + public ScenarioMetadata (string Name, string Description) + { + this.Name = Name; + this.Description = Description; + } /// - /// Defines the category names used to catagorize a + /// Name /// - [System.AttributeUsage (System.AttributeTargets.Class, AllowMultiple = true)] - public class ScenarioCategory : System.Attribute { - /// - /// Category Name - /// - public string Name { get; set; } - - public ScenarioCategory (string Name) => this.Name = Name; - - /// - /// Static helper function to get the Name given a Type - /// - /// - /// Name of the category - public static string GetName (Type t) => ((ScenarioCategory)System.Attribute.GetCustomAttributes (t) [0]).Name; - - /// - /// Static helper function to get the Categories given a Type - /// - /// - /// list of category names - public static List GetCategories (Type t) => System.Attribute.GetCustomAttributes (t) - .ToList () - .Where (a => a is ScenarioCategory) - .Select (a => ((ScenarioCategory)a).Name) - .ToList (); - } + public string Name { get; set; } /// - /// Helper function to get the list of categories a belongs to (defined in ) + /// Description /// - /// list of category names - public List GetCategories () => ScenarioCategory.GetCategories (this.GetType ()); - - private static int _maxScenarioNameLen = 30; + public string Description { get; set; } /// - /// Gets the Scenario Name + Description with the Description padded - /// based on the longest known Scenario name. + /// Static helper function to get the Name given a Type /// + /// /// - public override string ToString () => $"{GetName ().PadRight(_maxScenarioNameLen)}{GetDescription ()}"; + public static string GetName (Type t) => ((ScenarioMetadata)GetCustomAttributes (t) [0]).Name; /// - /// Override this to implement the setup logic (create controls, etc...). + /// Static helper function to get the Description given a Type /// - /// This is typically the best place to put scenario logic code. - public virtual void Setup () - { - } + /// + /// + public static string GetDescription (Type t) => ((ScenarioMetadata)GetCustomAttributes (t) [0]).Description; + } - /// - /// Runs the . Override to start the - /// using a different than `Top`. - /// - /// - /// Overrides that do not call the base., must call before returning. - /// - public virtual void Run () - { - // Must explicit call Application.Shutdown method to shutdown. - Application.Run (Application.Top); - } + /// + /// Defines the category names used to catagorize a + /// + [AttributeUsage (AttributeTargets.Class, AllowMultiple = true)] + public class ScenarioCategory : System.Attribute { + + public ScenarioCategory (string Name) => this.Name = Name; /// - /// Stops the scenario. Override to change shutdown behavior for the . + /// Category Name /// - public virtual void RequestStop () - { - Application.RequestStop (); - } + public string Name { get; set; } /// - /// Returns a list of all Categories set by all of the s defined in the project. + /// Static helper function to get the Name given a Type /// - internal static List GetAllCategories () - { - List categories = new List (); - foreach (Type type in typeof (Scenario).Assembly.GetTypes () - .Where (myType => myType.IsClass && !myType.IsAbstract && myType.IsSubclassOf (typeof (Scenario)))) { - List attrs = System.Attribute.GetCustomAttributes (type).ToList (); - categories = categories.Union (attrs.Where (a => a is ScenarioCategory).Select (a => ((ScenarioCategory)a).Name)).ToList (); - } - - // Sort - categories = categories.OrderBy (c => c).ToList (); - - // Put "All" at the top - categories.Insert (0, "All Scenarios"); - return categories; - } + /// + /// Name of the category + public static string GetName (Type t) => ((ScenarioCategory)GetCustomAttributes (t) [0]).Name; /// - /// Returns a list of all instanaces defined in the project, sorted by . - /// https://stackoverflow.com/questions/5411694/get-all-inherited-classes-of-an-abstract-class + /// Static helper function to get the Categories given a Type /// - public static List GetScenarios () - { - List objects = new List (); - foreach (Type type in typeof (Scenario).Assembly.ExportedTypes - .Where (myType => myType.IsClass && !myType.IsAbstract && myType.IsSubclassOf (typeof (Scenario)))) { - var scenario = (Scenario)Activator.CreateInstance (type); - objects.Add (scenario); - _maxScenarioNameLen = Math.Max (_maxScenarioNameLen, scenario.GetName ().Length + 1); - } - return objects.OrderBy (s => s.GetName ()).ToList (); - } - - protected virtual void Dispose (bool disposing) - { - if (!_disposedValue) { - if (disposing) { - // TODO: dispose managed state (managed objects) - } - - // TODO: free unmanaged resources (unmanaged objects) and override finalizer - // TODO: set large fields to null - _disposedValue = true; - } - } - - public void Dispose () - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose (disposing: true); - GC.SuppressFinalize (this); - } + /// + /// list of category names + public static List GetCategories (Type t) => GetCustomAttributes (t) + .ToList () + .Where (a => a is ScenarioCategory) + .Select (a => ((ScenarioCategory)a).Name) + .ToList (); } -} +} \ No newline at end of file diff --git a/UICatalog/Scenarios/ASCIICustomButton.cs b/UICatalog/Scenarios/ASCIICustomButton.cs index 595af572dc..8be4a169b6 100644 --- a/UICatalog/Scenarios/ASCIICustomButton.cs +++ b/UICatalog/Scenarios/ASCIICustomButton.cs @@ -77,7 +77,7 @@ private void CustomInitialize (string id, string text, Pos x, Pos y, int width, }; AutoSize = false; - LayoutStyle = LayoutStyle.Absolute; + //LayoutStyle = LayoutStyle.Absolute; var fillText = new System.Text.StringBuilder (); for (int i = 0; i < Bounds.Height; i++) { diff --git a/UICatalog/Scenarios/AllViewsTester.cs b/UICatalog/Scenarios/AllViewsTester.cs index e5ca76be7f..b9e9ad3918 100644 --- a/UICatalog/Scenarios/AllViewsTester.cs +++ b/UICatalog/Scenarios/AllViewsTester.cs @@ -111,7 +111,6 @@ public override void Setup () _computedCheckBox = new CheckBox ("Computed Layout", true) { X = 0, Y = 0 }; _computedCheckBox.Toggled += (s, e) => { if (_curView != null) { - _curView.LayoutStyle = e.OldValue == true ? LayoutStyle.Absolute : LayoutStyle.Computed; _hostPane.LayoutSubviews (); } }; @@ -246,7 +245,7 @@ void DimPosChanged (View view) var layout = view.LayoutStyle; try { - view.LayoutStyle = LayoutStyle.Absolute; + //view.LayoutStyle = LayoutStyle.Absolute; view.X = _xRadioGroup.SelectedItem switch { 0 => Pos.Percent (_xVal), @@ -280,14 +279,14 @@ void DimPosChanged (View view) } catch (Exception e) { MessageBox.ErrorQuery ("Exception", e.Message, "Ok"); } finally { - view.LayoutStyle = layout; + //view.LayoutStyle = layout; } UpdateTitle (view); } // TODO: This is missing some - List _posNames = new() { "Factor", "AnchorEnd", "Center", "Absolute" }; - List _dimNames = new() { "Factor", "Fill", "Absolute" }; + List _posNames = new () { "Factor", "AnchorEnd", "Center", "Absolute" }; + List _dimNames = new () { "Factor", "Fill", "Absolute" }; void UpdateSettings (View view) { @@ -388,10 +387,10 @@ void View_Initialized (object sender, EventArgs e) //view.X = Pos.Center (); //view.Y = Pos.Center (); if (view.Width == null || view.Frame.Width == 0) { - view.Width = Dim.Fill(); + view.Width = Dim.Fill (); } if (view.Height == null || view.Frame.Height == 0) { - view.Height = Dim.Fill(); + view.Height = Dim.Fill (); } UpdateSettings (view); UpdateTitle (view); diff --git a/UICatalog/Scenarios/ColorPicker.cs b/UICatalog/Scenarios/ColorPicker.cs index 241c07f1b0..c2a58427ae 100644 --- a/UICatalog/Scenarios/ColorPicker.cs +++ b/UICatalog/Scenarios/ColorPicker.cs @@ -1,132 +1,129 @@ -using Terminal.Gui; -using System; - -namespace UICatalog.Scenarios { - [ScenarioMetadata (Name: "Color Picker", Description: "Color Picker.")] - [ScenarioCategory ("Colors")] - [ScenarioCategory ("Controls")] - public class ColorPickers : Scenario { - /// - /// Foreground ColorPicker. - /// - private ColorPicker foregroundColorPicker; - - /// - /// Background ColorPicker. - /// - private ColorPicker backgroundColorPicker; - - /// - /// Foreground color label. - /// - private Label _foregroundColorLabel; - - /// - /// Background color Label. - /// - private Label _backgroundColorLabel; - - /// - /// Demo label. - /// - private View _demoView; - - /// - /// Setup the scenario. - /// - public override void Setup () - { - // Scenario Window's. - Win.Title = this.GetName (); - - // Foreground ColorPicker. - foregroundColorPicker = new ColorPicker () { - Title = "Foreground Color", - X = 0, - Y = 0, - BoxHeight = 3, - BoxWidth = 6, - BorderStyle = LineStyle.Single - }; - foregroundColorPicker.ColorChanged += ForegroundColor_ColorChanged; - Win.Add (foregroundColorPicker); - - _foregroundColorLabel = new Label { - X = Pos.Left (foregroundColorPicker), - Y = Pos.Bottom (foregroundColorPicker) + 1 - }; - Win.Add (_foregroundColorLabel); - - // Background ColorPicker. - backgroundColorPicker = new ColorPicker () { - Title = "Background Color", - Y = 0, - X = 0, - BoxHeight = 1, - BoxWidth = 4, - BorderStyle = LineStyle.Single - }; - backgroundColorPicker.X = Pos.AnchorEnd () - (Pos.Right (backgroundColorPicker) - Pos.Left (backgroundColorPicker)); - backgroundColorPicker.ColorChanged += BackgroundColor_ColorChanged; - Win.Add (backgroundColorPicker); - _backgroundColorLabel = new Label (); - _backgroundColorLabel.X = Pos.AnchorEnd () - (Pos.Right (_backgroundColorLabel) - Pos.Left (_backgroundColorLabel)); - _backgroundColorLabel.Y = Pos.Bottom (backgroundColorPicker) + 1; - Win.Add (_backgroundColorLabel); - - // Demo Label. - _demoView = new View () { - Title = "Color Sample", - Text = "Lorem Ipsum", - TextAlignment = TextAlignment.Centered, - VerticalTextAlignment = VerticalTextAlignment.Middle, - BorderStyle = LineStyle.Heavy, - X = Pos.Center (), - Y = Pos.Center (), - Height = 5, - Width = 20 - }; - Win.Add (_demoView); - - // Set default colors. - foregroundColorPicker.SelectedColor = _demoView.SuperView.ColorScheme.Normal.Foreground.ColorName; - backgroundColorPicker.SelectedColor = _demoView.SuperView.ColorScheme.Normal.Background.ColorName; - Win.Initialized += (s, e) => Win.LayoutSubviews (); - } - - /// - /// Fired when foreground color is changed. - /// - private void ForegroundColor_ColorChanged (object sender, EventArgs e) - { - UpdateColorLabel (_foregroundColorLabel, foregroundColorPicker); - UpdateDemoLabel (); - } - - /// - /// Fired when background color is changed. - /// - private void BackgroundColor_ColorChanged (object sender, EventArgs e) - { - UpdateColorLabel (_backgroundColorLabel, backgroundColorPicker); - UpdateDemoLabel (); - } - - /// - /// Update a color label from his ColorPicker. - /// - private void UpdateColorLabel (Label label, ColorPicker colorPicker) - { - label.Clear (); - var color = new Color (colorPicker.SelectedColor); - label.Text = $"{colorPicker.SelectedColor} ({(int)colorPicker.SelectedColor}) #{color.R:X2}{color.G:X2}{color.B:X2}"; - } - - /// - /// Update Demo Label. - /// - private void UpdateDemoLabel () => _demoView.ColorScheme = new ColorScheme () { - Normal = new Attribute (foregroundColorPicker.SelectedColor, backgroundColorPicker.SelectedColor) +using System; +using Terminal.Gui; + +namespace UICatalog.Scenarios; + +[ScenarioMetadata ("Color Picker", "Color Picker.")] +[ScenarioCategory ("Colors")] +[ScenarioCategory ("Controls")] +public class ColorPickers : Scenario { + + /// + /// Background color Label. + /// + Label _backgroundColorLabel; + + /// + /// Demo label. + /// + View _demoView; + + /// + /// Foreground color label. + /// + Label _foregroundColorLabel; + + /// + /// Background ColorPicker. + /// + ColorPicker backgroundColorPicker; + + /// + /// Foreground ColorPicker. + /// + ColorPicker foregroundColorPicker; + + /// + /// Setup the scenario. + /// + public override void Setup () + { + // Scenario Window's. + Win.Title = GetName (); + + // Foreground ColorPicker. + foregroundColorPicker = new ColorPicker { + Title = "Foreground Color", + BorderStyle = LineStyle.Single }; + foregroundColorPicker.ColorChanged += ForegroundColor_ColorChanged; + Win.Add (foregroundColorPicker); + + _foregroundColorLabel = new Label { + X = Pos.Left (foregroundColorPicker), + Y = Pos.Bottom (foregroundColorPicker) + 1 + }; + Win.Add (_foregroundColorLabel); + + // Background ColorPicker. + backgroundColorPicker = new ColorPicker { + Title = "Background Color", + Y = Pos.Center (), + X = Pos.Center (), + BoxHeight = 1, + BoxWidth = 4, + BorderStyle = LineStyle.Single + }; + //backgroundColorPicker.X = Pos.AnchorEnd () - (Pos.Right (backgroundColorPicker) - Pos.Left (backgroundColorPicker)); + backgroundColorPicker.ColorChanged += BackgroundColor_ColorChanged; + Win.Add (backgroundColorPicker); + _backgroundColorLabel = new Label (); + _backgroundColorLabel.X = Pos.AnchorEnd () - (Pos.Right (_backgroundColorLabel) - Pos.Left (_backgroundColorLabel)); + _backgroundColorLabel.Y = Pos.Bottom (backgroundColorPicker) + 1; + Win.Add (_backgroundColorLabel); + + // Demo Label. + _demoView = new View { + Title = "Color Sample", + Text = "Lorem Ipsum", + TextAlignment = TextAlignment.Centered, + VerticalTextAlignment = VerticalTextAlignment.Middle, + BorderStyle = LineStyle.Heavy, + X = Pos.Center (), + Y = Pos.Center (), + Height = 5, + Width = 20 + }; + Win.Add (_demoView); + + // Set default colors. + foregroundColorPicker.SelectedColor = _demoView.SuperView.ColorScheme.Normal.Foreground.ColorName; + backgroundColorPicker.SelectedColor = _demoView.SuperView.ColorScheme.Normal.Background.ColorName; + Win.Initialized += (s, e) => Win.LayoutSubviews (); + } + + /// + /// Fired when foreground color is changed. + /// + void ForegroundColor_ColorChanged (object sender, EventArgs e) + { + UpdateColorLabel (_foregroundColorLabel, foregroundColorPicker); + UpdateDemoLabel (); } + + /// + /// Fired when background color is changed. + /// + void BackgroundColor_ColorChanged (object sender, EventArgs e) + { + UpdateColorLabel (_backgroundColorLabel, backgroundColorPicker); + UpdateDemoLabel (); + } + + /// + /// Update a color label from his ColorPicker. + /// + void UpdateColorLabel (Label label, ColorPicker colorPicker) + { + label.Clear (); + var color = new Color (colorPicker.SelectedColor); + label.Text = $"{colorPicker.SelectedColor} ({(int)colorPicker.SelectedColor}) #{color.R:X2}{color.G:X2}{color.B:X2}"; + } + + /// + /// Update Demo Label. + /// + void UpdateDemoLabel () => _demoView.ColorScheme = new ColorScheme { + Normal = new Attribute (foregroundColorPicker.SelectedColor, backgroundColorPicker.SelectedColor) + }; } \ No newline at end of file diff --git a/UICatalog/Scenarios/ListColumns.cs b/UICatalog/Scenarios/ListColumns.cs index 96b27d7f31..5e8983d673 100644 --- a/UICatalog/Scenarios/ListColumns.cs +++ b/UICatalog/Scenarios/ListColumns.cs @@ -3,331 +3,358 @@ using System.Collections.Generic; using System.Data; using Terminal.Gui; -using static Terminal.Gui.TableView; - -namespace UICatalog.Scenarios { - - [ScenarioMetadata (Name: "ListColumns", Description: "Implements a columned list via a data table.")] - [ScenarioCategory ("TableView")] - [ScenarioCategory ("Controls")] - [ScenarioCategory ("Dialogs")] - [ScenarioCategory ("Text and Formatting")] - [ScenarioCategory ("Top Level Windows")] - public class ListColumns : Scenario { - TableView listColView; - DataTable currentTable; - private MenuItem _miCellLines; - private MenuItem _miExpandLastColumn; - private MenuItem _miAlwaysUseNormalColorForVerticalCellLines; - private MenuItem _miSmoothScrolling; - private MenuItem _miAlternatingColors; - private MenuItem _miCursor; - private MenuItem _miTopline; - private MenuItem _miBottomline; - private MenuItem _miOrientVertical; - private MenuItem _miScrollParallel; - - ColorScheme alternatingColorScheme; - - public override void Setup () - { - Win.Title = this.GetName (); - Win.Y = 1; // menu - Win.Height = Dim.Fill (1); // status bar - - this.listColView = new TableView () { - X = 0, - Y = 0, - Width = Dim.Fill (), - Height = Dim.Fill (1), - Style = new TableStyle { - ShowHeaders = false, - ShowHorizontalHeaderOverline = false, - ShowHorizontalHeaderUnderline = false, - ShowHorizontalBottomline = false, - ExpandLastColumn = false, + +namespace UICatalog.Scenarios; + +[ScenarioMetadata ("ListColumns", "Implements a columned list via a data table.")] +[ScenarioCategory ("TableView")] +[ScenarioCategory ("Controls")] +[ScenarioCategory ("Dialogs")] +[ScenarioCategory ("Text and Formatting")] +[ScenarioCategory ("Top Level Windows")] +public class ListColumns : Scenario { + MenuItem _miAlternatingColors; + MenuItem _miAlwaysUseNormalColorForVerticalCellLines; + MenuItem _miBottomline; + MenuItem _miCellLines; + MenuItem _miCursor; + MenuItem _miExpandLastColumn; + MenuItem _miOrientVertical; + MenuItem _miScrollParallel; + MenuItem _miSmoothScrolling; + MenuItem _miTopline; + + ColorScheme _alternatingColorScheme; + DataTable _currentTable; + TableView _listColView; + + public override void Setup () + { + Win.Title = GetName (); + Win.Y = 1; // menu + Win.Height = Dim.Fill (1); // status bar + + _listColView = new TableView { + X = 0, + Y = 0, + Width = Dim.Fill (), + Height = Dim.Fill (1), + Style = new TableStyle { + ShowHeaders = false, + ShowHorizontalHeaderOverline = false, + ShowHorizontalHeaderUnderline = false, + ShowHorizontalBottomline = false, + ExpandLastColumn = false + } + }; + var listColStyle = new ListColumnStyle (); + + var menu = new MenuBar (new MenuBarItem [] { + new ("_File", new MenuItem [] { + new ("Open_BigListExample", "", () => OpenSimpleList (true)), + new ("Open_SmListExample", "", () => OpenSimpleList (false)), + new ("_CloseExample", "", () => CloseExample ()), + new ("_Quit", "", () => Quit ()) + }), + new ("_View", new [] { + _miTopline = new MenuItem ("_TopLine", "", () => ToggleTopline ()) { + Checked = _listColView.Style.ShowHorizontalHeaderOverline, + CheckType = MenuItemCheckStyle.Checked + }, + _miBottomline = new MenuItem ("_BottomLine", "", () => ToggleBottomline ()) { + Checked = _listColView.Style.ShowHorizontalBottomline, + CheckType = MenuItemCheckStyle.Checked + }, + _miCellLines = new MenuItem ("_CellLines", "", () => ToggleCellLines ()) { + Checked = _listColView.Style.ShowVerticalCellLines, + CheckType = MenuItemCheckStyle.Checked + }, + _miExpandLastColumn = new MenuItem ("_ExpandLastColumn", "", () => ToggleExpandLastColumn ()) { + Checked = _listColView.Style.ExpandLastColumn, + CheckType = MenuItemCheckStyle.Checked + }, + _miAlwaysUseNormalColorForVerticalCellLines = + new MenuItem ("_AlwaysUseNormalColorForVerticalCellLines", "", + () => ToggleAlwaysUseNormalColorForVerticalCellLines ()) { + Checked = _listColView.Style.AlwaysUseNormalColorForVerticalCellLines, + CheckType = MenuItemCheckStyle.Checked + }, + _miSmoothScrolling = new MenuItem ("_SmoothHorizontalScrolling", "", () => ToggleSmoothScrolling ()) { + Checked = _listColView.Style.SmoothHorizontalScrolling, + CheckType = MenuItemCheckStyle.Checked + }, + _miAlternatingColors = new MenuItem ("Alternating Colors", "", () => ToggleAlternatingColors ()) + { CheckType = MenuItemCheckStyle.Checked }, + _miCursor = new MenuItem ("Invert Selected Cell First Character", "", + () => ToggleInvertSelectedCellFirstCharacter ()) { + Checked = _listColView.Style.InvertSelectedCellFirstCharacter, + CheckType = MenuItemCheckStyle.Checked } - }; - var listColStyle = new ListColumnStyle (); - - var menu = new MenuBar (new MenuBarItem [] { - new MenuBarItem ("_File", new MenuItem [] { - new MenuItem ("Open_BigListExample", "", () => OpenSimpleList (true)), - new MenuItem ("Open_SmListExample", "", () => OpenSimpleList (false)), - new MenuItem ("_CloseExample", "", () => CloseExample ()), - new MenuItem ("_Quit", "", () => Quit()), - }), - new MenuBarItem ("_View", new MenuItem [] { - _miTopline = new MenuItem ("_TopLine", "", () => ToggleTopline ()) { Checked = listColView.Style.ShowHorizontalHeaderOverline, CheckType = MenuItemCheckStyle.Checked }, - _miBottomline = new MenuItem ("_BottomLine", "", () => ToggleBottomline ()) { Checked = listColView.Style.ShowHorizontalBottomline, CheckType = MenuItemCheckStyle.Checked }, - _miCellLines = new MenuItem ("_CellLines", "", () => ToggleCellLines ()) { Checked = listColView.Style.ShowVerticalCellLines, CheckType = MenuItemCheckStyle.Checked }, - _miExpandLastColumn = new MenuItem ("_ExpandLastColumn", "", () => ToggleExpandLastColumn ()) { Checked = listColView.Style.ExpandLastColumn, CheckType = MenuItemCheckStyle.Checked }, - _miAlwaysUseNormalColorForVerticalCellLines = new MenuItem ("_AlwaysUseNormalColorForVerticalCellLines", "", () => ToggleAlwaysUseNormalColorForVerticalCellLines ()) { Checked = listColView.Style.AlwaysUseNormalColorForVerticalCellLines, CheckType = MenuItemCheckStyle.Checked }, - _miSmoothScrolling = new MenuItem ("_SmoothHorizontalScrolling", "", () => ToggleSmoothScrolling ()) { Checked = listColView.Style.SmoothHorizontalScrolling, CheckType = MenuItemCheckStyle.Checked }, - _miAlternatingColors = new MenuItem ("Alternating Colors", "", () => ToggleAlternatingColors ()) { CheckType = MenuItemCheckStyle.Checked}, - _miCursor = new MenuItem ("Invert Selected Cell First Character", "", () => ToggleInvertSelectedCellFirstCharacter ()) { Checked = listColView.Style.InvertSelectedCellFirstCharacter,CheckType = MenuItemCheckStyle.Checked}, - }), - new MenuBarItem ("_List", new MenuItem [] { - //new MenuItem ("_Hide Headers", "", HideHeaders), - _miOrientVertical = new MenuItem ("_OrientVertical", "", () => ToggleVerticalOrientation ()) { Checked = listColStyle.Orientation == Orientation.Vertical, CheckType = MenuItemCheckStyle.Checked }, - _miScrollParallel = new MenuItem ("_ScrollParallel", "", () => ToggleScrollParallel ()) { Checked = listColStyle.ScrollParallel, CheckType = MenuItemCheckStyle.Checked }, - new MenuItem ("Set _Max Cell Width", "", SetListMaxWidth), - new MenuItem ("Set Mi_n Cell Width", "", SetListMinWidth), - }), - }); - - Application.Top.Add (menu); - - var statusBar = new StatusBar (new StatusItem [] { - new StatusItem(KeyCode.F2, "~F2~ OpenBigListEx", () => OpenSimpleList (true)), - new StatusItem(KeyCode.F3, "~F3~ CloseExample", () => CloseExample ()), - new StatusItem(KeyCode.F4, "~F4~ OpenSmListEx", () => OpenSimpleList (false)), - new StatusItem(Application.QuitKey, $"{Application.QuitKey} to Quit", () => Quit()), - }); - Application.Top.Add (statusBar); - - Win.Add (listColView); - - var selectedCellLabel = new Label () { - X = 0, - Y = Pos.Bottom (listColView), - Text = "0,0", - Width = Dim.Fill (), - TextAlignment = TextAlignment.Right - - }; - - Win.Add (selectedCellLabel); - - listColView.SelectedCellChanged += (s, e) => { selectedCellLabel.Text = $"{listColView.SelectedRow},{listColView.SelectedColumn}"; }; - listColView.KeyDown += TableViewKeyPress; - - SetupScrollBar (); - - alternatingColorScheme = new ColorScheme () { - - Disabled = Win.ColorScheme.Disabled, - HotFocus = Win.ColorScheme.HotFocus, - Focus = Win.ColorScheme.Focus, - Normal = new Attribute (Color.White, Color.BrightBlue) - }; - - // if user clicks the mouse in TableView - listColView.MouseClick += (s, e) => { - - listColView.ScreenToCell (e.MouseEvent.X, e.MouseEvent.Y, out int? clickedCol); - }; - - listColView.KeyBindings.Add (KeyCode.Space, Command.ToggleChecked); - } + }), + new ("_List", new [] { + //new MenuItem ("_Hide Headers", "", HideHeaders), + _miOrientVertical = new MenuItem ("_OrientVertical", "", () => ToggleVerticalOrientation ()) { + Checked = listColStyle.Orientation == Orientation.Vertical, + CheckType = MenuItemCheckStyle.Checked + }, + _miScrollParallel = new MenuItem ("_ScrollParallel", "", () => ToggleScrollParallel ()) + { Checked = listColStyle.ScrollParallel, CheckType = MenuItemCheckStyle.Checked }, + new ("Set _Max Cell Width", "", SetListMaxWidth), + new ("Set Mi_n Cell Width", "", SetListMinWidth) + }) + }); + + Application.Top.Add (menu); + + var statusBar = new StatusBar (new StatusItem [] { + new (KeyCode.F2, "~F2~ OpenBigListEx", () => OpenSimpleList (true)), + new (KeyCode.F3, "~F3~ CloseExample", () => CloseExample ()), + new (KeyCode.F4, "~F4~ OpenSmListEx", () => OpenSimpleList (false)), + new (Application.QuitKey, $"{Application.QuitKey} to Quit", () => Quit ()) + }); + Application.Top.Add (statusBar); + + Win.Add (_listColView); + + var selectedCellLabel = new Label { + X = 0, + Y = Pos.Bottom (_listColView), + Text = "0,0", + Width = Dim.Fill (), + TextAlignment = TextAlignment.Right + + }; + + Win.Add (selectedCellLabel); + + _listColView.SelectedCellChanged += (s, e) => { selectedCellLabel.Text = $"{_listColView.SelectedRow},{_listColView.SelectedColumn}"; }; + _listColView.KeyDown += TableViewKeyPress; + + SetupScrollBar (); + + _alternatingColorScheme = new ColorScheme { + + Disabled = Win.ColorScheme.Disabled, + HotFocus = Win.ColorScheme.HotFocus, + Focus = Win.ColorScheme.Focus, + Normal = new Attribute (Color.White, Color.BrightBlue) + }; + + // if user clicks the mouse in TableView + _listColView.MouseClick += (s, e) => { + + _listColView.ScreenToCell (e.MouseEvent.X, e.MouseEvent.Y, out var clickedCol); + }; + + _listColView.KeyBindings.Add (KeyCode.Space, Command.ToggleChecked); + } - private void SetupScrollBar () - { - var scrollBar = new ScrollBarView (listColView, true); // (listColView, true, true); + void SetupScrollBar () + { + var scrollBar = new ScrollBarView (_listColView, true); // (listColView, true, true); - scrollBar.ChangedPosition += (s, e) => { - listColView.RowOffset = scrollBar.Position; - if (listColView.RowOffset != scrollBar.Position) { - scrollBar.Position = listColView.RowOffset; - } - listColView.SetNeedsDisplay (); - }; - /* - scrollBar.OtherScrollBarView.ChangedPosition += (s,e) => { - listColView.ColumnOffset = scrollBar.OtherScrollBarView.Position; - if (listColView.ColumnOffset != scrollBar.OtherScrollBarView.Position) { - scrollBar.OtherScrollBarView.Position = listColView.ColumnOffset; - } - listColView.SetNeedsDisplay (); - }; - */ - - listColView.DrawContent += (s, e) => { - scrollBar.Size = listColView.Table?.Rows ?? 0; - scrollBar.Position = listColView.RowOffset; - //scrollBar.OtherScrollBarView.Size = listColView.Table?.Columns - 1 ?? 0; - //scrollBar.OtherScrollBarView.Position = listColView.ColumnOffset; - scrollBar.Refresh (); - }; + scrollBar.ChangedPosition += (s, e) => { + _listColView.RowOffset = scrollBar.Position; + if (_listColView.RowOffset != scrollBar.Position) { + scrollBar.Position = _listColView.RowOffset; + } + _listColView.SetNeedsDisplay (); + }; + /* + scrollBar.OtherScrollBarView.ChangedPosition += (s,e) => { + listColView.ColumnOffset = scrollBar.OtherScrollBarView.Position; + if (listColView.ColumnOffset != scrollBar.OtherScrollBarView.Position) { + scrollBar.OtherScrollBarView.Position = listColView.ColumnOffset; + } + listColView.SetNeedsDisplay (); + }; + */ - } + _listColView.DrawContent += (s, e) => { + scrollBar.Size = _listColView.Table?.Rows ?? 0; + scrollBar.Position = _listColView.RowOffset; + //scrollBar.OtherScrollBarView.Size = listColView.Table?.Columns - 1 ?? 0; + //scrollBar.OtherScrollBarView.Position = listColView.ColumnOffset; + scrollBar.Refresh (); + }; - private void TableViewKeyPress (object sender, Key e) - { - if (e.KeyCode == KeyCode.Delete) { + } - // set all selected cells to null - foreach (var pt in listColView.GetAllSelectedCells ()) { - currentTable.Rows [pt.Y] [pt.X] = DBNull.Value; - } + void TableViewKeyPress (object sender, Key e) + { + if (e.KeyCode == KeyCode.Delete) { - listColView.Update (); - e.Handled = true; + // set all selected cells to null + foreach (var pt in _listColView.GetAllSelectedCells ()) { + _currentTable.Rows [pt.Y] [pt.X] = DBNull.Value; } + _listColView.Update (); + e.Handled = true; } - private void ToggleTopline () - { - _miTopline.Checked = !_miTopline.Checked; - listColView.Style.ShowHorizontalHeaderOverline = (bool)_miTopline.Checked; - listColView.Update (); - } - private void ToggleBottomline () - { - _miBottomline.Checked = !_miBottomline.Checked; - listColView.Style.ShowHorizontalBottomline = (bool)_miBottomline.Checked; - listColView.Update (); - } - private void ToggleExpandLastColumn () - { - _miExpandLastColumn.Checked = !_miExpandLastColumn.Checked; - listColView.Style.ExpandLastColumn = (bool)_miExpandLastColumn.Checked; + } - listColView.Update (); + void ToggleTopline () + { + _miTopline.Checked = !_miTopline.Checked; + _listColView.Style.ShowHorizontalHeaderOverline = (bool)_miTopline.Checked; + _listColView.Update (); + } - } + void ToggleBottomline () + { + _miBottomline.Checked = !_miBottomline.Checked; + _listColView.Style.ShowHorizontalBottomline = (bool)_miBottomline.Checked; + _listColView.Update (); + } - private void ToggleAlwaysUseNormalColorForVerticalCellLines () - { - _miAlwaysUseNormalColorForVerticalCellLines.Checked = !_miAlwaysUseNormalColorForVerticalCellLines.Checked; - listColView.Style.AlwaysUseNormalColorForVerticalCellLines = (bool)_miAlwaysUseNormalColorForVerticalCellLines.Checked; + void ToggleExpandLastColumn () + { + _miExpandLastColumn.Checked = !_miExpandLastColumn.Checked; + _listColView.Style.ExpandLastColumn = (bool)_miExpandLastColumn.Checked; - listColView.Update (); - } - private void ToggleSmoothScrolling () - { - _miSmoothScrolling.Checked = !_miSmoothScrolling.Checked; - listColView.Style.SmoothHorizontalScrolling = (bool)_miSmoothScrolling.Checked; + _listColView.Update (); - listColView.Update (); + } - } - private void ToggleCellLines () - { - _miCellLines.Checked = !_miCellLines.Checked; - listColView.Style.ShowVerticalCellLines = (bool)_miCellLines.Checked; - listColView.Update (); - } - private void ToggleAlternatingColors () - { - //toggle menu item - _miAlternatingColors.Checked = !_miAlternatingColors.Checked; - - if (_miAlternatingColors.Checked == true) { - listColView.Style.RowColorGetter = (a) => { return a.RowIndex % 2 == 0 ? alternatingColorScheme : null; }; - } else { - listColView.Style.RowColorGetter = null; - } - listColView.SetNeedsDisplay (); - } + void ToggleAlwaysUseNormalColorForVerticalCellLines () + { + _miAlwaysUseNormalColorForVerticalCellLines.Checked = !_miAlwaysUseNormalColorForVerticalCellLines.Checked; + _listColView.Style.AlwaysUseNormalColorForVerticalCellLines = (bool)_miAlwaysUseNormalColorForVerticalCellLines.Checked; - private void ToggleInvertSelectedCellFirstCharacter () - { - //toggle menu item - _miCursor.Checked = !_miCursor.Checked; - listColView.Style.InvertSelectedCellFirstCharacter = (bool)_miCursor.Checked; - listColView.SetNeedsDisplay (); - } + _listColView.Update (); + } - private void ToggleVerticalOrientation () - { - _miOrientVertical.Checked = !_miOrientVertical.Checked; - if ((ListTableSource)listColView.Table != null) { - ((ListTableSource)listColView.Table).Style.Orientation = (bool)_miOrientVertical.Checked ? Orientation.Vertical : Orientation.Horizontal; - listColView.SetNeedsDisplay (); - } - } + void ToggleSmoothScrolling () + { + _miSmoothScrolling.Checked = !_miSmoothScrolling.Checked; + _listColView.Style.SmoothHorizontalScrolling = (bool)_miSmoothScrolling.Checked; - private void ToggleScrollParallel () - { - _miScrollParallel.Checked = !_miScrollParallel.Checked; - if ((ListTableSource)listColView.Table != null) { - ((ListTableSource)listColView.Table).Style.ScrollParallel = (bool)_miScrollParallel.Checked; - listColView.SetNeedsDisplay (); - } - } + _listColView.Update (); - private void SetListMinWidth () - { - RunListWidthDialog ("MinCellWidth", (s, v) => s.MinCellWidth = v, (s) => s.MinCellWidth); - listColView.SetNeedsDisplay (); - } + } - private void SetListMaxWidth () - { - RunListWidthDialog ("MaxCellWidth", (s, v) => s.MaxCellWidth = v, (s) => s.MaxCellWidth); - listColView.SetNeedsDisplay (); + void ToggleCellLines () + { + _miCellLines.Checked = !_miCellLines.Checked; + _listColView.Style.ShowVerticalCellLines = (bool)_miCellLines.Checked; + _listColView.Update (); + } + + void ToggleAlternatingColors () + { + //toggle menu item + _miAlternatingColors.Checked = !_miAlternatingColors.Checked; + + if (_miAlternatingColors.Checked == true) { + _listColView.Style.RowColorGetter = a => { return a.RowIndex % 2 == 0 ? _alternatingColorScheme : null; }; + } else { + _listColView.Style.RowColorGetter = null; } + _listColView.SetNeedsDisplay (); + } - private void RunListWidthDialog (string prompt, Action setter, Func getter) - { - var accepted = false; - var ok = new Button ("Ok", is_default: true); - ok.Clicked += (s, e) => { accepted = true; Application.RequestStop (); }; - var cancel = new Button ("Cancel"); - cancel.Clicked += (s, e) => { Application.RequestStop (); }; - var d = new Dialog (ok, cancel) { Title = prompt }; - - var tf = new TextField () { - Text = getter (listColView).ToString (), - X = 0, - Y = 1, - Width = Dim.Fill () - }; - - d.Add (tf); - tf.SetFocus (); - - Application.Run (d); - - if (accepted) { - - try { - setter (listColView, int.Parse (tf.Text)); - } catch (Exception ex) { - MessageBox.ErrorQuery (60, 20, "Failed to set", ex.Message, "Ok"); - } - } + void ToggleInvertSelectedCellFirstCharacter () + { + //toggle menu item + _miCursor.Checked = !_miCursor.Checked; + _listColView.Style.InvertSelectedCellFirstCharacter = (bool)_miCursor.Checked; + _listColView.SetNeedsDisplay (); + } + + void ToggleVerticalOrientation () + { + _miOrientVertical.Checked = !_miOrientVertical.Checked; + if ((ListTableSource)_listColView.Table != null) { + ((ListTableSource)_listColView.Table).Style.Orientation = (bool)_miOrientVertical.Checked ? Orientation.Vertical : Orientation.Horizontal; + _listColView.SetNeedsDisplay (); } + } - private void CloseExample () - { - listColView.Table = null; + void ToggleScrollParallel () + { + _miScrollParallel.Checked = !_miScrollParallel.Checked; + if ((ListTableSource)_listColView.Table != null) { + ((ListTableSource)_listColView.Table).Style.ScrollParallel = (bool)_miScrollParallel.Checked; + _listColView.SetNeedsDisplay (); } + } + + void SetListMinWidth () + { + RunListWidthDialog ("MinCellWidth", (s, v) => s.MinCellWidth = v, s => s.MinCellWidth); + _listColView.SetNeedsDisplay (); + } - private void Quit () - { + void SetListMaxWidth () + { + RunListWidthDialog ("MaxCellWidth", (s, v) => s.MaxCellWidth = v, s => s.MaxCellWidth); + _listColView.SetNeedsDisplay (); + } + + void RunListWidthDialog (string prompt, Action setter, Func getter) + { + var accepted = false; + var ok = new Button ("Ok", true); + ok.Clicked += (s, e) => { + accepted = true; Application.RequestStop (); - } + }; + var cancel = new Button ("Cancel"); + cancel.Clicked += (s, e) => { Application.RequestStop (); }; + var d = new Dialog (ok, cancel) { Title = prompt }; - private void OpenSimpleList (bool big) - { - SetTable (BuildSimpleList (big ? 1023 : 31)); - } + var tf = new TextField { + Text = getter (_listColView).ToString (), + X = 0, + Y = 1, + Width = Dim.Fill () + }; - private void SetTable (IList list) - { - listColView.Table = new ListTableSource (list, listColView); - if ((ListTableSource)listColView.Table != null) { - currentTable = ((ListTableSource)listColView.Table).DataTable; + d.Add (tf); + tf.SetFocus (); + + Application.Run (d); + + if (accepted) { + + try { + setter (_listColView, int.Parse (tf.Text)); + } catch (Exception ex) { + MessageBox.ErrorQuery (60, 20, "Failed to set", ex.Message, "Ok"); } } + } - /// - /// Builds a simple list in which values are the index. This helps testing that scrolling etc is working correctly and not skipping out values when paging - /// - /// - /// - public static IList BuildSimpleList (int items) - { - var list = new List (); - - for (int i = 0; i < items; i++) { - list.Add ("Item " + i); - } + void CloseExample () => _listColView.Table = null; + + void Quit () => Application.RequestStop (); - return list; + void OpenSimpleList (bool big) => SetTable (BuildSimpleList (big ? 1023 : 31)); + + void SetTable (IList list) + { + _listColView.Table = new ListTableSource (list, _listColView); + if ((ListTableSource)_listColView.Table != null) { + _currentTable = ((ListTableSource)_listColView.Table).DataTable; } } + + /// + /// Builds a simple list in which values are the index. This helps testing that scrolling etc is working correctly and not + /// skipping out values when paging + /// + /// + /// + public static IList BuildSimpleList (int items) + { + var list = new List (); + + for (var i = 0; i < items; i++) { + list.Add ("Item " + i); + } + + return list; + } } \ No newline at end of file diff --git a/UICatalog/Scenarios/ProgressBarStyles.cs b/UICatalog/Scenarios/ProgressBarStyles.cs index 40e1709d6f..e1db9768f6 100644 --- a/UICatalog/Scenarios/ProgressBarStyles.cs +++ b/UICatalog/Scenarios/ProgressBarStyles.cs @@ -4,169 +4,168 @@ using Terminal.Gui; using static UICatalog.Scenarios.Frames; -namespace UICatalog.Scenarios { - [ScenarioMetadata (Name: "ProgressBar Styles", Description: "Shows the ProgressBar Styles.")] - [ScenarioCategory ("Controls")] - [ScenarioCategory ("Progress")] - [ScenarioCategory ("Threading")] - - // TODO: Add enable/disable to show that that is working - // TODO: Clean up how FramesEditor works - // TODO: Better align rpPBFormat - - public class ProgressBarStyles : Scenario { - private Timer _fractionTimer; - private Timer _pulseTimer; - private const uint _timerTick = 20; - - public override void Init () +namespace UICatalog.Scenarios; + +[ScenarioMetadata ("ProgressBar Styles", "Shows the ProgressBar Styles.")] +[ScenarioCategory ("Controls")] +[ScenarioCategory ("Progress")] +[ScenarioCategory ("Threading")] + +// TODO: Add enable/disable to show that that is working +// TODO: Clean up how FramesEditor works +// TODO: Better align rpPBFormat +public class ProgressBarStyles : Scenario { + const uint _timerTick = 20; + Timer _fractionTimer; + Timer _pulseTimer; + + public override void Init () + { + Application.Init (); + ConfigurationManager.Themes.Theme = Theme; + ConfigurationManager.Apply (); + + var editor = new FramesEditor { + Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}", + BorderStyle = LineStyle.Single + }; + editor.ColorScheme = Colors.ColorSchemes [TopLevelColorScheme]; + + const float fractionStep = 0.01F; + const int pbWidth = 25; + + var pbFormatEnum = Enum.GetValues (typeof (ProgressBarFormat)).Cast ().ToList (); + + var rbPBFormat = new RadioGroup (pbFormatEnum.Select (e => e.ToString ()).ToArray ()) { + X = Pos.Center (), + Y = 10, + Orientation = Orientation.Horizontal, + BorderStyle = LineStyle.Single + }; + editor.Add (rbPBFormat); + + var button = new Button ("Start timer") { + X = Pos.Center (), + Y = Pos.Bottom (rbPBFormat) + 1 + }; + + editor.Add (button); + var blocksPB = new ProgressBar { + Title = "Blocks", + X = Pos.Center (), + Y = Pos.Bottom (button) + 1, + Width = pbWidth, + BorderStyle = LineStyle.Single + }; + editor.Add (blocksPB); + + var continuousPB = new ProgressBar { + Title = "Continuous", + X = Pos.Center (), + Y = Pos.Bottom (blocksPB) + 1, + Width = pbWidth, + ProgressBarStyle = ProgressBarStyle.Continuous, + BorderStyle = LineStyle.Single + }; + editor.Add (continuousPB); + + button.Clicked += (s, e) => { + if (_fractionTimer == null) { + //blocksPB.Enabled = false; + blocksPB.Fraction = 0; + continuousPB.Fraction = 0; + float fractionSum = 0; + _fractionTimer = new Timer (_ => { + fractionSum += fractionStep; + blocksPB.Fraction = fractionSum; + continuousPB.Fraction = fractionSum; + if (fractionSum > 1) { + _fractionTimer.Dispose (); + _fractionTimer = null; + button.Enabled = true; + } + Application.Wakeup (); + }, null, 0, _timerTick); + } + }; + + var ckbBidirectional = new CheckBox ("BidirectionalMarquee", true) { + X = Pos.Center (), + Y = Pos.Bottom (continuousPB) + 1 + }; + editor.Add (ckbBidirectional); + + var marqueesBlocksPB = new ProgressBar { + Title = "Marquee Blocks", + X = Pos.Center (), + Y = Pos.Bottom (ckbBidirectional) + 1, + Width = pbWidth, + ProgressBarStyle = ProgressBarStyle.MarqueeBlocks, + BorderStyle = LineStyle.Single + }; + editor.Add (marqueesBlocksPB); + + var marqueesContinuousPB = new ProgressBar { + Title = "Marquee Continuous", + X = Pos.Center (), + Y = Pos.Bottom (marqueesBlocksPB) + 1, + Width = pbWidth, + ProgressBarStyle = ProgressBarStyle.MarqueeContinuous, + BorderStyle = LineStyle.Single + }; + editor.Add (marqueesContinuousPB); + + rbPBFormat.SelectedItemChanged += (s, e) => { + blocksPB.ProgressBarFormat = (ProgressBarFormat)e.SelectedItem; + continuousPB.ProgressBarFormat = (ProgressBarFormat)e.SelectedItem; + marqueesBlocksPB.ProgressBarFormat = (ProgressBarFormat)e.SelectedItem; + marqueesContinuousPB.ProgressBarFormat = (ProgressBarFormat)e.SelectedItem; + }; + + ckbBidirectional.Toggled += (s, e) => { + ckbBidirectional.Checked = marqueesBlocksPB.BidirectionalMarquee = marqueesContinuousPB.BidirectionalMarquee = (bool)!e.OldValue; + }; + + _pulseTimer = new Timer (_ => { + marqueesBlocksPB.Text = marqueesContinuousPB.Text = DateTime.Now.TimeOfDay.ToString (); + marqueesBlocksPB.Pulse (); + marqueesContinuousPB.Pulse (); + Application.Wakeup (); + }, null, 0, 300); + + Application.Top.Unloaded += Top_Unloaded; + + void Top_Unloaded (object sender, EventArgs args) { - Application.Init (); - ConfigurationManager.Themes.Theme = Theme; - ConfigurationManager.Apply (); - - var editor = new FramesEditor () { - Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}", - BorderStyle = LineStyle.Single - }; - editor.ColorScheme = Colors.ColorSchemes [TopLevelColorScheme]; - - const float fractionStep = 0.01F; - const int pbWidth = 25; - - var pbFormatEnum = Enum.GetValues (typeof (ProgressBarFormat)).Cast ().ToList (); - - var rbPBFormat = new RadioGroup (pbFormatEnum.Select (e => e.ToString ()).ToArray ()) { - X = Pos.Center (), - Y = 10 - }; - editor.Add (rbPBFormat); - - var button = new Button ("Start timer") { - X = Pos.Center (), - Y = Pos.Bottom (rbPBFormat) + 1 - }; - - editor.Add (button); - var blocksPB = new ProgressBar () { - Title = "Blocks", - X = Pos.Center (), - Y = Pos.Bottom (button) + 1, - Width = pbWidth, - BorderStyle = LineStyle.Single - }; - editor.Add (blocksPB); - - var continuousPB = new ProgressBar () { - Title = "Continuous", - X = Pos.Center (), - Y = Pos.Bottom (blocksPB) + 1, - Width = pbWidth, - ProgressBarStyle = ProgressBarStyle.Continuous, - BorderStyle = LineStyle.Single - }; - editor.Add (continuousPB); - - button.Clicked += (s, e) => { - if (_fractionTimer == null) { - //blocksPB.Enabled = false; - blocksPB.Fraction = 0; - continuousPB.Fraction = 0; - float fractionSum = 0; - _fractionTimer = new Timer ((_) => { - fractionSum += fractionStep; - blocksPB.Fraction = fractionSum; - continuousPB.Fraction = fractionSum; - if (fractionSum > 1) { - _fractionTimer.Dispose (); - _fractionTimer = null; - button.Enabled = true; - } - Application.Wakeup (); - }, null, 0, _timerTick); - } - }; - - var ckbBidirectional = new CheckBox ("BidirectionalMarquee", true) { - X = Pos.Center (), - Y = Pos.Bottom (continuousPB) + 1 - }; - editor.Add (ckbBidirectional); - - var marqueesBlocksPB = new ProgressBar () { - Title = "Marquee Blocks", - X = Pos.Center (), - Y = Pos.Bottom (ckbBidirectional) + 1, - Width = pbWidth, - ProgressBarStyle = ProgressBarStyle.MarqueeBlocks, - BorderStyle = LineStyle.Single - }; - editor.Add (marqueesBlocksPB); - - var marqueesContinuousPB = new ProgressBar () { - Title = "Marquee Continuous", - X = Pos.Center (), - Y = Pos.Bottom (marqueesBlocksPB) + 1, - Width = pbWidth, - ProgressBarStyle = ProgressBarStyle.MarqueeContinuous, - BorderStyle = LineStyle.Single - }; - editor.Add (marqueesContinuousPB); - - rbPBFormat.SelectedItemChanged += (s, e) => { - blocksPB.ProgressBarFormat = (ProgressBarFormat)e.SelectedItem; - continuousPB.ProgressBarFormat = (ProgressBarFormat)e.SelectedItem; - marqueesBlocksPB.ProgressBarFormat = (ProgressBarFormat)e.SelectedItem; - marqueesContinuousPB.ProgressBarFormat = (ProgressBarFormat)e.SelectedItem; - }; - - ckbBidirectional.Toggled += (s, e) => { - ckbBidirectional.Checked = marqueesBlocksPB.BidirectionalMarquee = marqueesContinuousPB.BidirectionalMarquee = (bool)!e.OldValue; - }; - - _pulseTimer = new Timer ((_) => { - marqueesBlocksPB.Text = marqueesContinuousPB.Text = DateTime.Now.TimeOfDay.ToString (); - marqueesBlocksPB.Pulse (); - marqueesContinuousPB.Pulse (); - Application.Wakeup (); - }, null, 0, 300); - - Application.Top.Unloaded += Top_Unloaded; - - void Top_Unloaded (object sender, EventArgs args) - { - if (_fractionTimer != null) { - _fractionTimer.Dispose (); - _fractionTimer = null; - } - if (_pulseTimer != null) { - _pulseTimer.Dispose (); - _pulseTimer = null; - } - Application.Top.Unloaded -= Top_Unloaded; + if (_fractionTimer != null) { + _fractionTimer.Dispose (); + _fractionTimer = null; } - - var pbs = editor.Subviews.Where (v => v.GetType () == typeof (ProgressBar)).Select(v => v.Title).ToList (); - var pbList = new ListView (pbs) { - Title = "Focused ProgressBar", - Y = 0, - X = Pos.Center(), - Width = 30, - Height = 7, - BorderStyle = LineStyle.Single - }; - pbList.SelectedItemChanged += (sender, e) => { - editor.ViewToEdit = (View)editor.Subviews.First(v => v.GetType () == typeof (ProgressBar) && v.Title == (string)e.Value); - }; - editor.Add (pbList); - pbList.SelectedItem = 0; - - Application.Run (editor); - Application.Shutdown (); + if (_pulseTimer != null) { + _pulseTimer.Dispose (); + _pulseTimer = null; + } + Application.Top.Unloaded -= Top_Unloaded; } - public override void Run () - { - } + var pbs = editor.Subviews.Where (v => v.GetType () == typeof (ProgressBar)).Select (v => v.Title).ToList (); + var pbList = new ListView (pbs) { + Title = "Focused ProgressBar", + Y = 0, + X = Pos.Center (), + Width = 30, + Height = 7, + BorderStyle = LineStyle.Single + }; + pbList.SelectedItemChanged += (sender, e) => { + editor.ViewToEdit = editor.Subviews.First (v => v.GetType () == typeof (ProgressBar) && v.Title == (string)e.Value); + }; + editor.Add (pbList); + pbList.SelectedItem = 0; + + Application.Run (editor); + Application.Shutdown (); } + + public override void Run () { } } \ No newline at end of file diff --git a/UICatalog/Scenarios/Sliders.cs b/UICatalog/Scenarios/Sliders.cs index 5da9497115..b578cd43a9 100644 --- a/UICatalog/Scenarios/Sliders.cs +++ b/UICatalog/Scenarios/Sliders.cs @@ -35,7 +35,7 @@ public override void Setup () BorderStyle = LineStyle.Single }; - slider.Style.SetChar.Attribute = new Attribute (Color.BrightGreen, Color.Black); + slider.Style.SetChar.Attribute = new Attribute (Color.BrightGreen, Color.Black); slider.Style.LegendAttributes.SetAttribute = new Attribute (Color.Green, Color.Black); slider.Options = new List> { @@ -79,7 +79,7 @@ public override void Setup () }; slider.SetOption (0); // Legends slider.SetOption (1); // RangeAllowSingle - //slider.SetOption (3); // AutoSize + //slider.SetOption (3); // AutoSize #region Slider Orientation Slider var slider_orientation_slider = new Slider (new List { "Horizontal", "Vertical" }) { @@ -104,9 +104,7 @@ public override void Setup () s.Style.SpaceChar = new Cell { Rune = CM.Glyphs.HLine }; if (prev == null) { - s.LayoutStyle = LayoutStyle.Absolute; s.Y = 0; - s.LayoutStyle = LayoutStyle.Computed; } else { s.Y = Pos.Bottom (prev) + 1; } @@ -119,9 +117,7 @@ public override void Setup () s.Style.SpaceChar = new Cell { Rune = CM.Glyphs.VLine }; if (prev == null) { - s.LayoutStyle = LayoutStyle.Absolute; s.X = 0; - s.LayoutStyle = LayoutStyle.Computed; } else { s.X = Pos.Right (prev) + 2; } @@ -199,7 +195,7 @@ public override void Setup () AutoSize = true }; - sliderFGColor.Style.SetChar.Attribute = new Attribute (Color.BrightGreen, Color.Black); + sliderFGColor.Style.SetChar.Attribute = new Attribute (Color.BrightGreen, Color.Black); sliderFGColor.Style.LegendAttributes.SetAttribute = new Attribute (Color.Green, Color.Blue); var colorOptions = new List> (); @@ -222,11 +218,11 @@ public override void Setup () s.ColorScheme = new ColorScheme (s.ColorScheme); s.ColorScheme.Normal = new Attribute (data.Item2, s.ColorScheme.Normal.Background); - s.Style.OptionChar.Attribute = new Attribute (data.Item1, s.ColorScheme.Normal.Background); - s.Style.SetChar.Attribute = new Attribute (data.Item1, s.Style.SetChar.Attribute?.Background ?? s.ColorScheme.Normal.Background); - s.Style.LegendAttributes.SetAttribute = new Attribute (data.Item1, s.ColorScheme.Normal.Background); - s.Style.RangeChar.Attribute = new Attribute (data.Item1, s.ColorScheme.Normal.Background); - s.Style.SpaceChar.Attribute = new Attribute (data.Item1, s.ColorScheme.Normal.Background); + s.Style.OptionChar.Attribute = new Attribute (data.Item1, s.ColorScheme.Normal.Background); + s.Style.SetChar.Attribute = new Attribute (data.Item1, s.Style.SetChar.Attribute?.Background ?? s.ColorScheme.Normal.Background); + s.Style.LegendAttributes.SetAttribute = new Attribute (data.Item1, s.ColorScheme.Normal.Background); + s.Style.RangeChar.Attribute = new Attribute (data.Item1, s.ColorScheme.Normal.Background); + s.Style.SpaceChar.Attribute = new Attribute (data.Item1, s.ColorScheme.Normal.Background); s.Style.LegendAttributes.NormalAttribute = new Attribute (data.Item1, s.ColorScheme.Normal.Background); } } @@ -244,7 +240,7 @@ public override void Setup () AutoSize = true }; - sliderBGColor.Style.SetChar.Attribute = new Attribute (Color.BrightGreen, Color.Black); + sliderBGColor.Style.SetChar.Attribute = new Attribute (Color.BrightGreen, Color.Black); sliderBGColor.Style.LegendAttributes.SetAttribute = new Attribute (Color.Green, Color.Blue); sliderBGColor.Options = colorOptions; diff --git a/UICatalog/Scenarios/TabViewExample.cs b/UICatalog/Scenarios/TabViewExample.cs index 2ef1bb235c..e847afb23d 100644 --- a/UICatalog/Scenarios/TabViewExample.cs +++ b/UICatalog/Scenarios/TabViewExample.cs @@ -59,7 +59,7 @@ public override void Setup () }; tabView.AddTab (new Tab ("Tab1", new Label ("hodor!")), false); - tabView.AddTab (new Tab ("Tab2", new Label ("durdur")), false); + tabView.AddTab (new Tab ("Tab2", new TextField ("durdur")), false); tabView.AddTab (new Tab ("Interactive Tab", GetInteractiveTab ()), false); tabView.AddTab (new Tab ("Big Text", GetBigTextFileTab ()), false); tabView.AddTab (new Tab ( diff --git a/UICatalog/Scenarios/Text.cs b/UICatalog/Scenarios/Text.cs index 48320ee4b9..203e2514b9 100644 --- a/UICatalog/Scenarios/Text.cs +++ b/UICatalog/Scenarios/Text.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; @@ -6,253 +7,251 @@ using Terminal.Gui; using Terminal.Gui.TextValidateProviders; +namespace UICatalog.Scenarios; -namespace UICatalog.Scenarios { - [ScenarioMetadata (Name: "Text Input Controls", Description: "Tests all text input controls")] - [ScenarioCategory ("Controls")] - [ScenarioCategory ("Mouse and Keyboard")] - [ScenarioCategory ("Text and Formatting")] - public class Text : Scenario { - public override void Setup () +[ScenarioMetadata ("Text Input Controls", "Tests all text input controls")] +[ScenarioCategory ("Controls")] +[ScenarioCategory ("Mouse and Keyboard")] +[ScenarioCategory ("Text and Formatting")] +public class Text : Scenario { + Label _labelMirroringTimeField; + + TimeField _timeField; + + public override void Setup () + { + // TextField is a simple, single-line text input control + var textField = new TextField ("TextField with test text. Unicode shouldn't 𝔹Aℝ𝔽!") { + X = 1, + Y = 0, + Width = Dim.Percent (50) - 1, + // Height will be replaced with 1 + Height = 2 + }; + + var singleWordGenerator = new SingleWordSuggestionGenerator (); + textField.Autocomplete.SuggestionGenerator = singleWordGenerator; + + textField.TextChanging += TextField_TextChanging; + + void TextField_TextChanging (object sender, TextChangingEventArgs e) + { + singleWordGenerator.AllSuggestions = Regex.Matches (e.NewText, "\\w+") + .Select (s => s.Value) + .Distinct ().ToList (); + } + Win.Add (textField); + + var labelMirroringTextField = new Label (textField.Text) { + X = Pos.Right (textField) + 1, + Y = Pos.Top (textField), + Width = Dim.Fill (1) - 1 + }; + Win.Add (labelMirroringTextField); + + textField.TextChanged += (s, prev) => { + labelMirroringTextField.Text = textField.Text; + }; + + // TextView is a rich (as in functionality, not formatting) text editing control + var textView = new TextView { + X = 1, + Y = Pos.Bottom (textField) + 1, + Width = Dim.Percent (50) - 1, + Height = Dim.Percent (30) + }; + textView.Text = "TextView with some more test text. Unicode shouldn't 𝔹Aℝ𝔽!"; + textView.DrawContent += TextView_DrawContent; + + // This shows how to enable autocomplete in TextView. + void TextView_DrawContent (object sender, DrawEventArgs e) { - // TextField is a simple, single-line text input control - var textField = new TextField ("TextField with test text. Unicode shouldn't 𝔹Aℝ𝔽!") { - X = 1, - Y = 0, - Width = Dim.Percent (50) - 1, - // Height will be replaced with 1 - Height = 2 - }; - - var singleWordGenerator = new SingleWordSuggestionGenerator (); - textField.Autocomplete.SuggestionGenerator = singleWordGenerator; - - textField.TextChanging += TextField_TextChanging; - - void TextField_TextChanging (object sender, TextChangingEventArgs e) - { - singleWordGenerator.AllSuggestions = Regex.Matches (e.NewText, "\\w+") - .Select (s => s.Value) - .Distinct ().ToList (); + singleWordGenerator.AllSuggestions = Regex.Matches (textView.Text, "\\w+") + .Select (s => s.Value) + .Distinct ().ToList (); + } + Win.Add (textView); + + var labelMirroringTextView = new Label { + X = Pos.Right (textView) + 1, + Y = Pos.Top (textView), + Width = Dim.Fill (1) - 1, + Height = Dim.Height (textView) - 1 + }; + Win.Add (labelMirroringTextView); + + // Use ContentChanged to detect if the user has typed something in a TextView. + // The TextChanged property is only fired if the TextView.Text property is + // explicitly set + textView.ContentsChanged += (s, a) => { + labelMirroringTextView.Enabled = !labelMirroringTextView.Enabled; + labelMirroringTextView.Text = textView.Text; + }; + + // By default TextView is a multi-line control. It can be forced to + // single-line mode. + var chxMultiline = new CheckBox ("Multiline") { + X = Pos.Left (textView), + Y = Pos.Bottom (textView), + Checked = textView.Multiline + }; + Win.Add (chxMultiline); + + var chxWordWrap = new CheckBox ("Word Wrap") { + X = Pos.Right (chxMultiline) + 2, + Y = Pos.Top (chxMultiline), + Checked = textView.WordWrap + }; + chxWordWrap.Toggled += (s, e) => textView.WordWrap = (bool)e.NewValue; + Win.Add (chxWordWrap); + + // TextView captures Tabs (so users can enter /t into text) by default; + // This means using Tab to navigate doesn't work by default. This shows + // how to turn tab capture off. + var chxCaptureTabs = new CheckBox ("Capture Tabs") { + X = Pos.Right (chxWordWrap) + 2, + Y = Pos.Top (chxWordWrap), + Checked = textView.AllowsTab + }; + + chxMultiline.Toggled += (s, e) => { + textView.Multiline = (bool)e.NewValue; + if (!textView.Multiline && (bool)chxWordWrap.Checked) { + chxWordWrap.Checked = false; } - Win.Add (textField); - - var labelMirroringTextField = new Label (textField.Text) { - X = Pos.Right (textField) + 1, - Y = Pos.Top (textField), - Width = Dim.Fill (1) - 1 - }; - Win.Add (labelMirroringTextField); - - textField.TextChanged += (s, prev) => { - labelMirroringTextField.Text = textField.Text; - }; - - // TextView is a rich (as in functionality, not formatting) text editing control - var textView = new TextView () { - X = 1, - Y = Pos.Bottom (textField) + 1, - Width = Dim.Percent (50) - 1, - Height = Dim.Percent (30), - }; - textView.Text = "TextView with some more test text. Unicode shouldn't 𝔹Aℝ𝔽!"; - textView.DrawContent += TextView_DrawContent; - - // This shows how to enable autocomplete in TextView. - void TextView_DrawContent (object sender, DrawEventArgs e) - { - singleWordGenerator.AllSuggestions = Regex.Matches (textView.Text, "\\w+") - .Select (s => s.Value) - .Distinct ().ToList (); + if (!textView.Multiline && (bool)chxCaptureTabs.Checked) { + chxCaptureTabs.Checked = false; } - Win.Add (textView); - - var labelMirroringTextView = new Label () { - X = Pos.Right (textView) + 1, - Y = Pos.Top (textView), - Width = Dim.Fill (1) - 1, - Height = Dim.Height (textView) - 1, - }; - Win.Add (labelMirroringTextView); - - // Use ContentChanged to detect if the user has typed something in a TextView. - // The TextChanged property is only fired if the TextView.Text property is - // explicitly set - textView.ContentsChanged += (s, a) => { - labelMirroringTextView.Enabled = !labelMirroringTextView.Enabled; - labelMirroringTextView.Text = textView.Text; - }; - - // By default TextView is a multi-line control. It can be forced to - // single-line mode. - var chxMultiline = new CheckBox ("Multiline") { - X = Pos.Left (textView), - Y = Pos.Bottom (textView), - Checked = textView.Multiline - }; - Win.Add (chxMultiline); - - var chxWordWrap = new CheckBox ("Word Wrap") { - X = Pos.Right (chxMultiline) + 2, - Y = Pos.Top (chxMultiline), - Checked = textView.WordWrap - }; - chxWordWrap.Toggled += (s, e) => textView.WordWrap = (bool)e.NewValue; - Win.Add (chxWordWrap); - - // TextView captures Tabs (so users can enter /t into text) by default; - // This means using Tab to navigate doesn't work by default. This shows - // how to turn tab capture off. - var chxCaptureTabs = new CheckBox ("Capture Tabs") { - X = Pos.Right (chxWordWrap) + 2, - Y = Pos.Top (chxWordWrap), - Checked = textView.AllowsTab - }; - - chxMultiline.Toggled += (s, e) => { - textView.Multiline = (bool)e.NewValue; - if (!textView.Multiline && (bool)chxWordWrap.Checked) { - chxWordWrap.Checked = false; - } - if (!textView.Multiline && (bool)chxCaptureTabs.Checked) { - chxCaptureTabs.Checked = false; - } - }; - - var keyTab = textView.KeyBindings.GetKeyFromCommands (Command.Tab); - var keyBackTab = textView.KeyBindings.GetKeyFromCommands (Command.BackTab); - chxCaptureTabs.Toggled += (s, e) => { - if (e.NewValue == true) { - textView.KeyBindings.Add (keyTab, Command.Tab); - textView.KeyBindings.Add (keyBackTab, Command.BackTab); - } else { - textView.KeyBindings.Remove (keyTab); - textView.KeyBindings.Remove (keyBackTab); - } - textView.AllowsTab = (bool)e.NewValue; - }; - Win.Add (chxCaptureTabs); - - var hexEditor = new HexView (new MemoryStream (Encoding.UTF8.GetBytes ("HexEditor Unicode that shouldn't 𝔹Aℝ𝔽!"))) { - X = 1, - Y = Pos.Bottom (chxMultiline) + 1, - Width = Dim.Percent (50) - 1, - Height = Dim.Percent (30), - }; - Win.Add (hexEditor); - - var labelMirroringHexEditor = new Label () { - X = Pos.Right (hexEditor) + 1, - Y = Pos.Top (hexEditor), - Width = Dim.Fill (1) - 1, - Height = Dim.Height (hexEditor) - 1, - }; + }; + + var keyTab = textView.KeyBindings.GetKeyFromCommands (Command.Tab); + var keyBackTab = textView.KeyBindings.GetKeyFromCommands (Command.BackTab); + chxCaptureTabs.Toggled += (s, e) => { + if (e.NewValue == true) { + textView.KeyBindings.Add (keyTab, Command.Tab); + textView.KeyBindings.Add (keyBackTab, Command.BackTab); + } else { + textView.KeyBindings.Remove (keyTab); + textView.KeyBindings.Remove (keyBackTab); + } + textView.AllowsTab = (bool)e.NewValue; + }; + Win.Add (chxCaptureTabs); + + var hexEditor = new HexView (new MemoryStream (Encoding.UTF8.GetBytes ("HexEditor Unicode that shouldn't 𝔹Aℝ𝔽!"))) { + X = 1, + Y = Pos.Bottom (chxMultiline) + 1, + Width = Dim.Percent (50) - 1, + Height = Dim.Percent (30) + }; + Win.Add (hexEditor); + + var labelMirroringHexEditor = new Label { + X = Pos.Right (hexEditor) + 1, + Y = Pos.Top (hexEditor), + Width = Dim.Fill (1) - 1, + Height = Dim.Height (hexEditor) - 1 + }; + var array = ((MemoryStream)hexEditor.Source).ToArray (); + labelMirroringHexEditor.Text = Encoding.UTF8.GetString (array, 0, array.Length); + hexEditor.Edited += (s, kv) => { + hexEditor.ApplyEdits (); var array = ((MemoryStream)hexEditor.Source).ToArray (); labelMirroringHexEditor.Text = Encoding.UTF8.GetString (array, 0, array.Length); - hexEditor.Edited += (s, kv) => { - hexEditor.ApplyEdits (); - var array = ((MemoryStream)hexEditor.Source).ToArray (); - labelMirroringHexEditor.Text = Encoding.UTF8.GetString (array, 0, array.Length); - }; - Win.Add (labelMirroringHexEditor); - - var dateField = new DateField (System.DateTime.Now) { - X = 1, - Y = Pos.Bottom (hexEditor) + 1, - Width = 20, - IsShortFormat = false - }; - Win.Add (dateField); - - var labelMirroringDateField = new Label (dateField.Text) { - X = Pos.Right (dateField) + 1, - Y = Pos.Top (dateField), - Width = Dim.Width (dateField), - Height = Dim.Height (dateField), - }; - Win.Add (labelMirroringDateField); - - dateField.TextChanged += (s, prev) => { - labelMirroringDateField.Text = dateField.Text; - }; - - _timeField = new TimeField (DateTime.Now.TimeOfDay) { - X = Pos.Right (labelMirroringDateField) + 5, - Y = Pos.Bottom (hexEditor) + 1, - Width = 20, - IsShortFormat = false - }; - Win.Add (_timeField); - - _labelMirroringTimeField = new Label (_timeField.Text) { - X = Pos.Right (_timeField) + 1, - Y = Pos.Top (_timeField), - Width = Dim.Width (_timeField), - Height = Dim.Height (_timeField), - }; - Win.Add (_labelMirroringTimeField); - - _timeField.TimeChanged += TimeChanged; - - // MaskedTextProvider - uses .NET MaskedTextProvider - var netProviderLabel = new Label ("NetMaskedTextProvider [ 999 000 LLL >LLL| AAA aaa ]") { - X = Pos.Left (dateField), - Y = Pos.Bottom (dateField) + 1 - }; - Win.Add (netProviderLabel); - - var netProvider = new NetMaskedTextProvider ("999 000 LLL > LLL | AAA aaa"); - - var netProviderField = new TextValidateField (netProvider) { - X = Pos.Right (netProviderLabel) + 1, - Y = Pos.Y (netProviderLabel), - }; - - Win.Add (netProviderField); - - // TextRegexProvider - Regex provider implemented by Terminal.Gui - var regexProvider = new Label ("TextRegexProvider [ ^([0-9]?[0-9]?[0-9]|1000)$ ]") { - X = Pos.Left (netProviderLabel), - Y = Pos.Bottom (netProviderLabel) + 1 - }; - Win.Add (regexProvider); - - var provider2 = new TextRegexProvider ("^([0-9]?[0-9]?[0-9]|1000)$") { ValidateOnInput = false }; - var regexProviderField = new TextValidateField (provider2) { - X = Pos.Right (regexProvider) + 1, - Y = Pos.Y (regexProvider), - Width = 30, - TextAlignment = TextAlignment.Centered - }; - - Win.Add (regexProviderField); - - var labelAppendAutocomplete = new Label ("Append Autocomplete:") { - Y = Pos.Y (regexProviderField) + 2, - X = 1 - }; - var appendAutocompleteTextField = new TextField () { - X = Pos.Right (labelAppendAutocomplete), - Y = labelAppendAutocomplete.Y, - Width = Dim.Fill () - }; - appendAutocompleteTextField.Autocomplete = new AppendAutocomplete (appendAutocompleteTextField); - appendAutocompleteTextField.Autocomplete.SuggestionGenerator = new SingleWordSuggestionGenerator { - AllSuggestions = new System.Collections.Generic.List{ - "fish", "flipper", "fin","fun","the","at","there","some","my","of","be","use","her","than","and","this","an","would","first","have","each","make","water","to","from","which","like","been","in","or","she","him","call","is","one","do","into","who","you","had","how","time","oil","that","by","their","has","its","it","word","if","look","now","he","but","will","two","find","was","not","up","more","long","for","what","other","write","down","on","all","about","go","day","are","were","out","see","did","as","we","many","number","get","with","when","then","no","come","his","your","them","way","made","they","can","these","could","may","said","so","people","part" - } - }; - - Win.Add (labelAppendAutocomplete); - Win.Add (appendAutocompleteTextField); - } - - TimeField _timeField; - Label _labelMirroringTimeField; + }; + Win.Add (labelMirroringHexEditor); + + var dateField = new DateField (DateTime.Now) { + X = 1, + Y = Pos.Bottom (hexEditor) + 1, + Width = 20, + IsShortFormat = false + }; + Win.Add (dateField); + + var labelMirroringDateField = new Label (dateField.Text) { + X = Pos.Right (dateField) + 1, + Y = Pos.Top (dateField), + Width = Dim.Width (dateField), + Height = Dim.Height (dateField) + }; + Win.Add (labelMirroringDateField); + + dateField.TextChanged += (s, prev) => { + labelMirroringDateField.Text = dateField.Text; + }; + + _timeField = new TimeField (DateTime.Now.TimeOfDay) { + X = Pos.Right (labelMirroringDateField) + 5, + Y = Pos.Bottom (hexEditor) + 1, + Width = 20, + IsShortFormat = false + }; + Win.Add (_timeField); + + _labelMirroringTimeField = new Label (_timeField.Text) { + X = Pos.Right (_timeField) + 1, + Y = Pos.Top (_timeField), + Width = Dim.Width (_timeField), + Height = Dim.Height (_timeField) + }; + Win.Add (_labelMirroringTimeField); + + _timeField.TimeChanged += TimeChanged; + + // MaskedTextProvider - uses .NET MaskedTextProvider + var netProviderLabel = new Label ("NetMaskedTextProvider [ 999 000 LLL >LLL| AAA aaa ]") { + X = Pos.Left (dateField), + Y = Pos.Bottom (dateField) + 1 + }; + Win.Add (netProviderLabel); + + var netProvider = new NetMaskedTextProvider ("999 000 LLL > LLL | AAA aaa"); + + var netProviderField = new TextValidateField (netProvider) { + X = Pos.Right (netProviderLabel) + 1, + Y = Pos.Y (netProviderLabel) + }; + + Win.Add (netProviderField); + + // TextRegexProvider - Regex provider implemented by Terminal.Gui + var regexProvider = new Label ("TextRegexProvider [ ^([0-9]?[0-9]?[0-9]|1000)$ ]") { + X = Pos.Left (netProviderLabel), + Y = Pos.Bottom (netProviderLabel) + 1 + }; + Win.Add (regexProvider); + + var provider2 = new TextRegexProvider ("^([0-9]?[0-9]?[0-9]|1000)$") { ValidateOnInput = false }; + var regexProviderField = new TextValidateField (provider2) { + X = Pos.Right (regexProvider) + 1, + Y = Pos.Y (regexProvider), + Width = 30, + TextAlignment = TextAlignment.Centered + }; + + Win.Add (regexProviderField); + + var labelAppendAutocomplete = new Label ("Append Autocomplete:") { + Y = Pos.Y (regexProviderField) + 2, + X = 1 + }; + var appendAutocompleteTextField = new TextField { + X = Pos.Right (labelAppendAutocomplete), + Y = Pos.Bottom (labelAppendAutocomplete), + Width = Dim.Fill () + }; + appendAutocompleteTextField.Autocomplete = new AppendAutocomplete (appendAutocompleteTextField); + appendAutocompleteTextField.Autocomplete.SuggestionGenerator = new SingleWordSuggestionGenerator { + AllSuggestions = new List { + "fish", "flipper", "fin", "fun", "the", "at", "there", "some", "my", "of", "be", "use", "her", "than", "and", "this", "an", "would", "first", "have", "each", "make", "water", "to", "from", "which", "like", "been", "in", "or", "she", "him", "call", "is", "one", "do", "into", "who", "you", "had", "how", "time", "oil", "that", "by", "their", "has", "its", "it", "word", "if", "look", "now", "he", "but", "will", "two", "find", "was", "not", "up", "more", "long", "for", "what", "other", "write", + "down", "on", "all", "about", "go", "day", "are", "were", "out", "see", "did", "as", "we", "many", "number", "get", "with", "when", "then", "no", "come", "his", "your", "them", "way", "made", "they", "can", "these", "could", "may", "said", "so", "people", "part" + } + }; - private void TimeChanged (object sender, DateTimeEventArgs e) - { - _labelMirroringTimeField.Text = _timeField.Text; - } + Win.Add (labelAppendAutocomplete); + Win.Add (appendAutocompleteTextField); } -} + + void TimeChanged (object sender, DateTimeEventArgs e) => _labelMirroringTimeField.Text = _timeField.Text; +} \ No newline at end of file diff --git a/UICatalog/UICatalog.cs b/UICatalog/UICatalog.cs index 31f3aec37f..907d09c1bd 100644 --- a/UICatalog/UICatalog.cs +++ b/UICatalog/UICatalog.cs @@ -63,6 +63,8 @@ struct Options { /* etc. */ } + static Options _options; + static int Main (string [] args) { Console.OutputEncoding = Encoding.Default; @@ -101,21 +103,13 @@ static int Main (string [] args) Scenario = context.ParseResult.GetValueForArgument (scenarioArgument), /* etc. */ }; - context.ExitCode = CommandWrapper (options); + // See https://github.com/dotnet/command-line-api/issues/796 for the rationale behind this hackery + _options = options; ; }); - return rootCommand.Invoke (args); - } + rootCommand.Invoke (args); - // See https://github.com/dotnet/command-line-api/issues/796 for the rationale behind this hackery - static int CommandWrapper (Options options) - { - try { - UICatalogMain (options); - } catch (Exception e) { - Console.WriteLine (e.ToString()); - return 1; - } + UICatalogMain (_options); return 0; } diff --git a/UnitTests/Application/ApplicationTests.cs b/UnitTests/Application/ApplicationTests.cs index 0b49293fb9..5dbc3a9c09 100644 --- a/UnitTests/Application/ApplicationTests.cs +++ b/UnitTests/Application/ApplicationTests.cs @@ -570,8 +570,6 @@ [Fact] [AutoInitShutdown] public void Begin_Sets_Application_Top_To_Console_Size () { Assert.Equal (new Rect (0, 0, 80, 25), Application.Top.Frame); - - ((FakeDriver)Application.Driver).SetBufferSize (5, 5); Application.Begin (Application.Top); Assert.Equal (new Rect (0, 0, 80, 25), Application.Top.Frame); ((FakeDriver)Application.Driver).SetBufferSize (5, 5); diff --git a/UnitTests/Application/KeyboardTests.cs b/UnitTests/Application/KeyboardTests.cs index 97facc89c9..94b0e8175c 100644 --- a/UnitTests/Application/KeyboardTests.cs +++ b/UnitTests/Application/KeyboardTests.cs @@ -16,7 +16,7 @@ public KeyboardTests (ITestOutputHelper output) RunState.Instances.Clear (); #endif } - + [Fact] public void KeyUp_Event () { @@ -193,7 +193,7 @@ public void QuitKey_Getter_Setter () Assert.True (isQuiting); isQuiting = false; - Application.OnKeyDown(new Key ( KeyCode.Q | KeyCode.CtrlMask)); + Application.OnKeyDown (new Key (KeyCode.Q | KeyCode.CtrlMask)); Assert.True (isQuiting); isQuiting = false; @@ -332,7 +332,7 @@ public void OnKeyDown_Application_KeyBinding () var view = new ScopedKeyBindingView (); var invoked = false; view.InvokingKeyBindings += (s, e) => invoked = true; - + Application.Top.Add (view); Application.Begin (Application.Top); diff --git a/UnitTests/Dialogs/DialogTests.cs b/UnitTests/Dialogs/DialogTests.cs index 63e530dd6e..390b498f6d 100644 --- a/UnitTests/Dialogs/DialogTests.cs +++ b/UnitTests/Dialogs/DialogTests.cs @@ -1,35 +1,13 @@ -using Xunit; +using Xunit; using Xunit.Abstractions; using static Terminal.Gui.Application; -namespace Terminal.Gui.DialogTests; +namespace Terminal.Gui.DialogTests; public class DialogTests { - readonly ITestOutputHelper output; + readonly ITestOutputHelper _output; - public DialogTests (ITestOutputHelper output) => this.output = output; - - //[Fact] - //[AutoInitShutdown] - //public void Default_Has_Border () - //{ - // var d = (FakeDriver)Application.Driver; - // d.SetBufferSize (20, 5); - // Application.RunState runstate = null; - - // var title = "Title"; - // var btnText = "ok"; - // var buttonRow = $"{CM.Glyphs.VLine}{CM.Glyphs.LeftBracket} {btnText} {CM.Glyphs.RightBracket}{CM.Glyphs.VLine}"; - // var width = buttonRow.Length; - // var topRow = $"┌┤{title} {new string (d.HLine.ToString () [0], width - title.Length - 2)}├┐"; - // var bottomRow = $"└{new string (d.HLine.ToString () [0], width - 2)}┘"; - - // var dlg = new Dialog (title, new Button (btnText)); - // Application.Begin (dlg); - - // TestHelpers.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output); - // Application.End (runstate); - //} + public DialogTests (ITestOutputHelper output) => _output = output; (RunState, Dialog) RunButtonTestDialog (string title, int width, Dialog.ButtonAlignments align, params Button [] btns) { @@ -125,7 +103,7 @@ public void Location_When_Application_Top_Not_Default () │ │ │ │ │ │ - └───┘", output); + └───┘", _output); } [Fact] @@ -158,7 +136,7 @@ public void Location_When_Not_Application_Top_Not_Default () ║ │ │ ║ ║ └───┘ ║ ║ ║ -╚══════════════════╝", output); +╚══════════════════╝", _output); d = new Dialog { X = 5, @@ -168,18 +146,33 @@ public void Location_When_Not_Application_Top_Not_Default () // This is because of PostionTopLevels and EnsureVisibleBounds Assert.Equal (new Point (3, 2), d.Frame.Location); - Assert.Equal (new Size (17, 8), d.Frame.Size); + // #3127: Before + // Assert.Equal (new Size (17, 8), d.Frame.Size); + // TestHelpers.AssertDriverContentsWithFrameAre (@" + //╔══════════════════╗ + //║ ║ + //║ ┌───────────────┐ + //║ │ │ + //║ │ │ + //║ │ │ + //║ │ │ + //║ │ │ + //║ │ │ + //╚══└───────────────┘", _output); + + // #3127: After: Because Toplevel is now Width/Height = Dim.Filll + Assert.Equal (new Size (15, 6), d.Frame.Size); TestHelpers.AssertDriverContentsWithFrameAre (@" ╔══════════════════╗ ║ ║ -║ ┌───────────────┐ -║ │ │ -║ │ │ -║ │ │ -║ │ │ -║ │ │ -║ │ │ -╚══└───────────────┘", output); +║ ┌─────────────┐ ║ +║ │ │ ║ +║ │ │ ║ +║ │ │ ║ +║ │ │ ║ +║ └─────────────┘ ║ +║ ║ +╚══════════════════╝", _output); } else if (iterations > 0) { RequestStop (); @@ -208,28 +201,28 @@ public void ButtonAlignment_One () (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btnText)); // Center - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output); End (runstate); // Justify buttonRow = $"{CM.Glyphs.VLine} {CM.Glyphs.LeftBracket} {btnText} {CM.Glyphs.RightBracket}{CM.Glyphs.VLine}"; Assert.Equal (width, buttonRow.Length); (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Justify, new Button (btnText)); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output); End (runstate); // Right buttonRow = $"{CM.Glyphs.VLine} {CM.Glyphs.LeftBracket} {btnText} {CM.Glyphs.RightBracket}{CM.Glyphs.VLine}"; Assert.Equal (width, buttonRow.Length); (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, new Button (btnText)); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output); End (runstate); // Left buttonRow = $"{CM.Glyphs.VLine}{CM.Glyphs.LeftBracket} {btnText} {CM.Glyphs.RightBracket} {CM.Glyphs.VLine}"; Assert.Equal (width, buttonRow.Length); (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Left, new Button (btnText)); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output); End (runstate); // Wider @@ -239,28 +232,28 @@ public void ButtonAlignment_One () d.SetBufferSize (width, 1); (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btnText)); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output); End (runstate); // Justify buttonRow = $"{CM.Glyphs.VLine} {CM.Glyphs.LeftBracket} {btnText} {CM.Glyphs.RightBracket}{CM.Glyphs.VLine}"; Assert.Equal (width, buttonRow.Length); (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Justify, new Button (btnText)); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output); End (runstate); // Right buttonRow = $"{CM.Glyphs.VLine} {CM.Glyphs.LeftBracket} {btnText} {CM.Glyphs.RightBracket}{CM.Glyphs.VLine}"; Assert.Equal (width, buttonRow.Length); (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, new Button (btnText)); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output); End (runstate); // Left buttonRow = $"{CM.Glyphs.VLine}{CM.Glyphs.LeftBracket} {btnText} {CM.Glyphs.RightBracket} {CM.Glyphs.VLine}"; Assert.Equal (width, buttonRow.Length); (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Left, new Button (btnText)); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output); End (runstate); } @@ -285,28 +278,28 @@ public void ButtonAlignment_Two () d.SetBufferSize (buttonRow.Length, 3); (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btn1Text), new Button (btn2Text)); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output); End (runstate); // Justify buttonRow = $@"{CM.Glyphs.VLine}{btn1} {btn2}{CM.Glyphs.VLine}"; Assert.Equal (width, buttonRow.Length); (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Justify, new Button (btn1Text), new Button (btn2Text)); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output); End (runstate); // Right buttonRow = $@"{CM.Glyphs.VLine} {btn1} {btn2}{CM.Glyphs.VLine}"; Assert.Equal (width, buttonRow.Length); (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, new Button (btn1Text), new Button (btn2Text)); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output); End (runstate); // Left buttonRow = $@"{CM.Glyphs.VLine}{btn1} {btn2} {CM.Glyphs.VLine}"; Assert.Equal (width, buttonRow.Length); (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Left, new Button (btn1Text), new Button (btn2Text)); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output); End (runstate); } @@ -341,7 +334,7 @@ public void ButtonAlignment_Two_Hidden () button1.Visible = false; RunIteration (ref runstate, ref firstIteration); buttonRow = $@"{CM.Glyphs.VLine} {btn2} {CM.Glyphs.VLine}"; - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output); End (runstate); // Justify @@ -352,7 +345,7 @@ public void ButtonAlignment_Two_Hidden () button1.Visible = false; RunIteration (ref runstate, ref firstIteration); buttonRow = $@"{CM.Glyphs.VLine} {btn2}{CM.Glyphs.VLine}"; - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output); End (runstate); // Right @@ -362,7 +355,7 @@ public void ButtonAlignment_Two_Hidden () (runstate, dlg) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, button1, button2); button1.Visible = false; RunIteration (ref runstate, ref firstIteration); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output); End (runstate); // Left @@ -373,7 +366,7 @@ public void ButtonAlignment_Two_Hidden () button1.Visible = false; RunIteration (ref runstate, ref firstIteration); buttonRow = $@"{CM.Glyphs.VLine} {btn2} {CM.Glyphs.VLine}"; - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output); End (runstate); } @@ -400,28 +393,28 @@ public void ButtonAlignment_Three () d.SetBufferSize (buttonRow.Length, 3); (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text)); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output); End (runstate); // Justify buttonRow = $@"{CM.Glyphs.VLine}{btn1} {btn2} {btn3}{CM.Glyphs.VLine}"; Assert.Equal (width, buttonRow.Length); (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Justify, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text)); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output); End (runstate); // Right buttonRow = $@"{CM.Glyphs.VLine} {btn1} {btn2} {btn3}{CM.Glyphs.VLine}"; Assert.Equal (width, buttonRow.Length); (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text)); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output); End (runstate); // Left buttonRow = $@"{CM.Glyphs.VLine}{btn1} {btn2} {btn3} {CM.Glyphs.VLine}"; Assert.Equal (width, buttonRow.Length); (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Left, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text)); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output); End (runstate); } @@ -451,28 +444,28 @@ public void ButtonAlignment_Four () // Default - Center (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output); End (runstate); // Justify buttonRow = $"{CM.Glyphs.VLine}{btn1} {btn2} {btn3} {btn4}{CM.Glyphs.VLine}"; Assert.Equal (width, buttonRow.Length); (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Justify, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output); End (runstate); // Right buttonRow = $"{CM.Glyphs.VLine} {btn1} {btn2} {btn3} {btn4}{CM.Glyphs.VLine}"; Assert.Equal (width, buttonRow.Length); (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output); End (runstate); // Left buttonRow = $"{CM.Glyphs.VLine}{btn1} {btn2} {btn3} {btn4} {CM.Glyphs.VLine}"; Assert.Equal (width, buttonRow.Length); (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Left, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output); End (runstate); } @@ -504,26 +497,26 @@ public void ButtonAlignment_Four_On_Too_Small_Width () buttonRow = $"{CM.Glyphs.VLine}es {CM.Glyphs.RightBracket} {btn2} {btn3} {CM.Glyphs.LeftBracket} neve{CM.Glyphs.VLine}"; (runstate, var dlg) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); Assert.Equal (new Size (width, 1), dlg.Frame.Size); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output); End (runstate); // Justify buttonRow = $"{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, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Justify, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output); End (runstate); // Right buttonRow = $"{CM.Glyphs.VLine}{CM.Glyphs.RightBracket} {btn2} {btn3} {btn4}{CM.Glyphs.VLine}"; (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output); End (runstate); // Left buttonRow = $"{CM.Glyphs.VLine}{btn1} {btn2} {btn3} {CM.Glyphs.LeftBracket} n{CM.Glyphs.VLine}"; (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Left, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output); End (runstate); } @@ -556,28 +549,28 @@ public void ButtonAlignment_Four_Wider () // Default - Center (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output); End (runstate); // Justify buttonRow = $"{CM.Glyphs.VLine}{btn1} {btn2} {btn3} {btn4}{CM.Glyphs.VLine}"; Assert.Equal (width, buttonRow.GetColumns ()); (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Justify, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output); End (runstate); // Right buttonRow = $"{CM.Glyphs.VLine} {btn1} {btn2} {btn3} {btn4}{CM.Glyphs.VLine}"; Assert.Equal (width, buttonRow.GetColumns ()); (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output); End (runstate); // Left buttonRow = $"{CM.Glyphs.VLine}{btn1} {btn2} {btn3} {btn4} {CM.Glyphs.VLine}"; Assert.Equal (width, buttonRow.GetColumns ()); (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Left, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output); End (runstate); } @@ -609,28 +602,28 @@ public void ButtonAlignment_Four_WideOdd () // Default - Center (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output); End (runstate); // Justify buttonRow = $"{CM.Glyphs.VLine}{btn1} {btn2} {btn3} {btn4}{CM.Glyphs.VLine}"; Assert.Equal (width, buttonRow.Length); (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Justify, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output); End (runstate); // Right buttonRow = $"{CM.Glyphs.VLine} {btn1} {btn2} {btn3} {btn4}{CM.Glyphs.VLine}"; Assert.Equal (width, buttonRow.Length); (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output); End (runstate); // Left buttonRow = $"{CM.Glyphs.VLine}{btn1} {btn2} {btn3} {btn4} {CM.Glyphs.VLine}"; Assert.Equal (width, buttonRow.Length); (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Left, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output); End (runstate); } @@ -649,7 +642,7 @@ public void Zero_Buttons_Works () d.SetBufferSize (buttonRow.Length, 3); (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, null); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output); End (runstate); } @@ -670,7 +663,7 @@ public void One_Button_Works () d.SetBufferSize (buttonRow.Length, 10); (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btnText)); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output); End (runstate); } @@ -698,14 +691,14 @@ public void Add_Button_Works () dlg.Border.Thickness = new Thickness (1, 0, 1, 0); runstate = Begin (dlg); var buttonRow = $"{CM.Glyphs.VLine} {btn1} {CM.Glyphs.VLine}"; - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output); // Now add a second button buttonRow = $"{CM.Glyphs.VLine} {btn1} {btn2} {CM.Glyphs.VLine}"; dlg.AddButton (new Button (btn2Text)); var first = false; RunIteration (ref runstate, ref first); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output); End (runstate); // Justify @@ -714,14 +707,14 @@ public void Add_Button_Works () dlg.Border.Thickness = new Thickness (1, 0, 1, 0); runstate = Begin (dlg); buttonRow = $"{CM.Glyphs.VLine} {btn1}{CM.Glyphs.VLine}"; - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output); // Now add a second button buttonRow = $"{CM.Glyphs.VLine}{btn1} {btn2}{CM.Glyphs.VLine}"; dlg.AddButton (new Button (btn2Text)); first = false; RunIteration (ref runstate, ref first); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output); End (runstate); // Right @@ -730,14 +723,14 @@ public void Add_Button_Works () dlg.Border.Thickness = new Thickness (1, 0, 1, 0); runstate = Begin (dlg); buttonRow = $"{CM.Glyphs.VLine}{new string (' ', width - btn1.Length - 2)}{btn1}{CM.Glyphs.VLine}"; - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output); // Now add a second button buttonRow = $"{CM.Glyphs.VLine} {btn1} {btn2}{CM.Glyphs.VLine}"; dlg.AddButton (new Button (btn2Text)); first = false; RunIteration (ref runstate, ref first); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output); End (runstate); // Left @@ -746,14 +739,14 @@ public void Add_Button_Works () dlg.Border.Thickness = new Thickness (1, 0, 1, 0); runstate = Begin (dlg); buttonRow = $"{CM.Glyphs.VLine}{btn1}{new string (' ', width - btn1.Length - 2)}{CM.Glyphs.VLine}"; - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output); // Now add a second button buttonRow = $"{CM.Glyphs.VLine}{btn1} {btn2} {CM.Glyphs.VLine}"; dlg.AddButton (new Button (btn2Text)); first = false; RunIteration (ref runstate, ref first); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", _output); End (runstate); } @@ -768,7 +761,8 @@ public void FileDialog_FileSystemWatcher () } } - [Fact] [AutoInitShutdown] + [Fact] + [AutoInitShutdown] public void Dialog_Opened_From_Another_Dialog () { ((FakeDriver)Driver).SetBufferSize (30, 10); @@ -809,7 +803,7 @@ public void Dialog_Opened_From_Another_Dialog () │ │ │{CM.Glyphs.LeftBracket} Show Sub {CM.Glyphs.RightBracket} {CM.Glyphs.LeftBracket} Close {CM.Glyphs.RightBracket} │ └───────────────────────┘"; - TestHelpers.AssertDriverContentsWithFrameAre (expected, output); + TestHelpers.AssertDriverContentsWithFrameAre (expected, _output); Assert.True (btn2.NewKeyDownEvent (new Key (KeyCode.Space))); } else if (iterations == 2) { @@ -821,15 +815,15 @@ public void Dialog_Opened_From_Another_Dialog () │ │ {btn} │ │ │ └──────────────────┘ │ │{CM.Glyphs.LeftBracket} Show Sub {CM.Glyphs.RightBracket} {CM.Glyphs.LeftBracket} Close {CM.Glyphs.RightBracket} │ - └───────────────────────┘", output); + └───────────────────────┘", _output); Assert.True (Current.NewKeyDownEvent (new Key (KeyCode.Enter))); } else if (iterations == 3) { - TestHelpers.AssertDriverContentsWithFrameAre (expected, output); + TestHelpers.AssertDriverContentsWithFrameAre (expected, _output); Assert.True (btn3.NewKeyDownEvent (new Key (KeyCode.Space))); } else if (iterations == 4) { - TestHelpers.AssertDriverContentsWithFrameAre ("", output); + TestHelpers.AssertDriverContentsWithFrameAre ("", _output); RequestStop (); } @@ -841,7 +835,8 @@ public void Dialog_Opened_From_Another_Dialog () Assert.Equal (4, iterations); } - [Fact] [AutoInitShutdown] + [Fact] + [AutoInitShutdown] public void Dialog_In_Window_With_Size_One_Button_Aligns () { ((FakeDriver)Driver).SetBufferSize (20, 5); @@ -867,7 +862,7 @@ public void Dialog_In_Window_With_Size_One_Button_Aligns () ││ {btn} ││ │└────────────────┘│ └──────────────────┘"; - _ = TestHelpers.AssertDriverContentsWithFrameAre (expected, output); + _ = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output); }; Run (dlg); @@ -875,43 +870,74 @@ public void Dialog_In_Window_With_Size_One_Button_Aligns () Run (win); } - // [Theory, AutoInitShutdown] - // [InlineData (5)] - // //[InlineData (6)] - // //[InlineData (7)] - // //[InlineData (8)] - // //[InlineData (9)] - // public void Dialog_In_Window_Without_Size_One_Button_Aligns (int height) - // { - // ((FakeDriver)Application.Driver).SetBufferSize (20, height); - // var win = new Window (); - - // Application.Iteration += (s, a) => { - // var dlg = new Dialog ("Test", new Button ("Ok")); - - // dlg.LayoutComplete += (s, a) => { - // Application.Refresh (); - // // BUGBUG: This seems wrong; is it a bug in Dim.Percent(85)?? - // var expected = @" - //┌┌┤Test├─────────┐─┐ - //││ │ │ - //││ [ Ok ] │ │ - //│└───────────────┘ │ - //└──────────────────┘"; - // _ = TestHelpers.AssertDriverContentsWithFrameAre (expected, output); - - // dlg.RequestStop (); - // win.RequestStop (); - // }; - - // Application.Run (dlg); - // }; - - // Application.Run (win); - // Application.Shutdown (); - // } - - [Fact] [AutoInitShutdown] + [Theory] + [AutoInitShutdown] + [InlineData (5, @" +┌┌───────────────┐─┐ +││ │ │ +││ ⟦ Ok ⟧ │ │ +│└───────────────┘ │ +└──────────────────┘")] + [InlineData (6, @" +┌┌───────────────┐─┐ +││ │ │ +││ │ │ +││ ⟦ Ok ⟧ │ │ +│└───────────────┘ │ +└──────────────────┘")] + [InlineData (7, @" +┌──────────────────┐ +│┌───────────────┐ │ +││ │ │ +││ │ │ +││ ⟦ Ok ⟧ │ │ +│└───────────────┘ │ +└──────────────────┘")] + [InlineData (8, @" +┌──────────────────┐ +│┌───────────────┐ │ +││ │ │ +││ │ │ +││ │ │ +││ ⟦ Ok ⟧ │ │ +│└───────────────┘ │ +└──────────────────┘")] + [InlineData (9, @" +┌──────────────────┐ +│┌───────────────┐ │ +││ │ │ +││ │ │ +││ │ │ +││ │ │ +││ ⟦ Ok ⟧ │ │ +│└───────────────┘ │ +└──────────────────┘")] + public void Dialog_In_Window_Without_Size_One_Button_Aligns (int height, string expected) + { + ((FakeDriver)Driver).SetBufferSize (20, height); + var win = new Window (); + + var iterations = -1; + Iteration += (s, a) => { + iterations++; + if (iterations == 0) { + var dlg = new Dialog (new Button ("Ok")); + Run (dlg); + } else if (iterations == 1) { + // BUGBUG: This seems wrong; is it a bug in Dim.Percent(85)?? No + _ = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output); + } else { + RequestStop (); + } + }; + + Run (win); + } + + // TODO: This is not really a Dialog test, but a ViewLayout test (Width = Dim.Fill (1) - Dim.Function (Btn_Width)) + // TODO: Move (and simplify) + [Fact] + [AutoInitShutdown] public void Dialog_In_Window_With_TextField_And_Button_AnchorEnd () { ((FakeDriver)Driver).SetBufferSize (20, 5); @@ -928,39 +954,60 @@ public void Dialog_In_Window_With_TextField_And_Button_AnchorEnd () win.Loaded += (s, a) => { var dlg = new Dialog { Width = 18, Height = 3 }; + Assert.Equal (16, dlg.Bounds.Width); + Button btn = null; btn = new Button ("Ok") { X = Pos.AnchorEnd () - Pos.Function (Btn_Width) }; - int Btn_Width () => btn?.Bounds.Width ?? 0; + btn.SetRelativeLayout (dlg.Bounds); + Assert.Equal (6, btn.Bounds.Width); + Assert.Equal (10, btn.Frame.X); // dlg.Bounds.Width (16) - btn.Frame.Width (6) = 10 + Assert.Equal (0, btn.Frame.Y); + Assert.Equal (6, btn.Frame.Width); + Assert.Equal (1, btn.Frame.Height); + int Btn_Width () + { + return btn?.Bounds.Width ?? 0; + } var tf = new TextField ("01234567890123456789") { + // Dim.Fill (1) fills remaining space minus 1 + // Dim.Function (Btn_Width) is 6 Width = Dim.Fill (1) - Dim.Function (Btn_Width) }; + tf.SetRelativeLayout (dlg.Bounds); + Assert.Equal (9, tf.Bounds.Width); // dlg.Bounds.Width (16) - Dim.Fill (1) - Dim.Function (6) = 9 + Assert.Equal (0, tf.Frame.X); + Assert.Equal (0, tf.Frame.Y); + Assert.Equal (9, tf.Frame.Width); + Assert.Equal (1, tf.Frame.Height); dlg.Loaded += (s, a) => { Refresh (); Assert.Equal (new Rect (10, 0, 6, 1), btn.Frame); - Assert.Equal (new Rect (0, 0, 6, 1), btn.Bounds); - var expected = @$" + Assert.Equal (new Rect (0, 0, 6, 1), btn.Bounds); + + var expected = @" ┌──────────────────┐ │┌────────────────┐│ -││23456789 {b}││ +││012345678 ⟦ Ok ⟧││ │└────────────────┘│ └──────────────────┘"; - _ = TestHelpers.AssertDriverContentsWithFrameAre (expected, output); + + _ = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output); dlg.SetNeedsLayout (); dlg.LayoutSubviews (); Refresh (); Assert.Equal (new Rect (10, 0, 6, 1), btn.Frame); - Assert.Equal (new Rect (0, 0, 6, 1), btn.Bounds); - expected = @$" + Assert.Equal (new Rect (0, 0, 6, 1), btn.Bounds); + expected = @" ┌──────────────────┐ │┌────────────────┐│ -││23456789 {b}││ +││012345678 ⟦ Ok ⟧││ │└────────────────┘│ └──────────────────┘"; - _ = TestHelpers.AssertDriverContentsWithFrameAre (expected, output); + _ = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output); }; dlg.Add (btn, tf); @@ -968,178 +1015,4 @@ public void Dialog_In_Window_With_TextField_And_Button_AnchorEnd () }; Run (win); } - - [Fact] - [AutoInitShutdown] - public void KeyBindings_Esc_Closes () - { - // test that Esc is bound to the Cancel command - var dlg = new Dialog (); - var dlgClosed = false; - dlg.Closed += (s, e) => { - dlgClosed = true; - }; - - var iterations = 0; - Iteration += (s, a) => { - dlg.NewKeyDownEvent (Key.Esc); - - if (++iterations > 1) { - Assert.Fail (); - } - }; - Run (dlg); - Assert.True (dlgClosed); - } - - - [Fact] - [AutoInitShutdown] - public void KeyBindings_Enter_No_Default_Button_NoOp () - { - // test that Enter does nothing if there's no default button - var dlg = new Dialog (); - var iterations = 0; - Iteration += (s, a) => { - if (++iterations > 1) { - // If we get here, the test passed - RequestStop (); - } - dlg.NewKeyDownEvent (Key.Enter); - }; - Run (dlg); - - Assert.Equal (2, iterations); - } - - [Fact] - [AutoInitShutdown] - public void KeyBindings_Enter_Causes_Default_Button_Click () - { - var dlg = new Dialog (); - var ok = new Button { - Text = "Ok", - IsDefault = true - }; - - var okClicked = false; - ok.Clicked += (s, e) => { - okClicked = true; - RequestStop (); - }; - - dlg.AddButton (ok); - - var iterations = 0; - void JustButtonIteration (object sender, IterationEventArgs e) - { - if (++iterations > 1) { - Assert.Fail (); - RequestStop (); - } - Assert.True (ok.HasFocus); - dlg.NewKeyDownEvent (Key.Enter); - } - - Iteration += JustButtonIteration; - Run (dlg); - Assert.True (okClicked); - Assert.Equal (1, iterations); - Iteration -= JustButtonIteration; - iterations = 0; - okClicked = false; - - // Now try it without a default button being focused - dlg = new Dialog (); - ok = new Button { - Text = "Ok", - IsDefault = true - }; - - ok.Clicked += (s, e) => { - okClicked = true; - RequestStop (); - }; - - dlg.AddButton (ok); - - var focusableView = new View { - CanFocus = true - }; - - dlg.Add (focusableView); - - void ButtonAndFocusableViewIteration (object sender, IterationEventArgs e) - { - if (++iterations > 1) { - Assert.Fail (); - RequestStop (); - } - focusableView.SetFocus (); - Assert.False (ok.HasFocus); - dlg.NewKeyDownEvent (Key.Enter); - } - - Iteration += ButtonAndFocusableViewIteration; - Run (dlg); - Assert.Equal (1, iterations); - Iteration -= ButtonAndFocusableViewIteration; - iterations = 0; - okClicked = false; - } - - [Fact] - [AutoInitShutdown] - public void KeyBindings_Enter_With_Focused_ViewThatEatsEnter_NoOp () - { - var dlg = new Dialog (); - var ok = new Button { - Text = "Ok", - IsDefault = true - }; - - // Now try it without a default button being focused, with a view that - // handles Enter. - dlg = new Dialog (); - ok = new Button { - Text = "Ok", - IsDefault = true - }; - - var okClicked = false; - ok.Clicked += (s, e) => { - okClicked = true; - RequestStop (); - }; - - dlg.AddButton (ok); - - var focusableView = new ViewThatEatsEnter (); - - dlg.Add (focusableView); - - var iterations = 0; - void Iteration (object sender, IterationEventArgs e) - { - if (++iterations > 1) { - RequestStop (); - } - focusableView.SetFocus (); - dlg.NewKeyDownEvent (Key.Enter); - } - - Application.Iteration += Iteration; - Run (dlg); - Assert.Equal (2, iterations); - Assert.False (okClicked); - } - - class ViewThatEatsEnter : View { - public ViewThatEatsEnter () - { - CanFocus = true; - AddCommand (Command.Select, () => true); - KeyBindings.Add (Key.Enter, Command.Select); - } - } } \ No newline at end of file diff --git a/UnitTests/Dialogs/MessageBoxTests.cs b/UnitTests/Dialogs/MessageBoxTests.cs index b98cc7ce7c..385c400555 100644 --- a/UnitTests/Dialogs/MessageBoxTests.cs +++ b/UnitTests/Dialogs/MessageBoxTests.cs @@ -644,7 +644,7 @@ public void KeyBindings_Enter_Causes_Focused_Button_Click () Application.OnKeyDown (Key.Tab); Application.OnKeyDown (Key.Enter); break; - + default: Assert.Fail (); break; @@ -672,7 +672,7 @@ public void KeyBindings_Space_Causes_Focused_Button_Click () result = MessageBox.Query (title: string.Empty, message: string.Empty, defaultButton: 0, wrapMessage: false, "btn0", "btn1"); Application.RequestStop (); break; - + case 2: // Tab to btn2 Application.OnKeyDown (Key.Tab); diff --git a/UnitTests/Input/KeyBindingTests.cs b/UnitTests/Input/KeyBindingTests.cs index 0dde97087a..218d08af94 100644 --- a/UnitTests/Input/KeyBindingTests.cs +++ b/UnitTests/Input/KeyBindingTests.cs @@ -225,7 +225,7 @@ public void Replace_Key () Assert.Empty (keyBindings.GetCommands (Key.D)); Assert.Contains (Command.Default, keyBindings.GetCommands (Key.E)); } - + // TryGet [Fact] public void TryGet_Unknown_ReturnsFalse () diff --git a/UnitTests/Text/TextFormatterTests.cs b/UnitTests/Text/TextFormatterTests.cs index e310f5d7b6..482c967dce 100644 --- a/UnitTests/Text/TextFormatterTests.cs +++ b/UnitTests/Text/TextFormatterTests.cs @@ -1,100 +1,96 @@ -using System.Text; -using System; +using System; using System.Collections.Generic; using System.Linq; +using System.Text; using Xunit; using Xunit.Abstractions; - // Alias Console to MockConsole so we don't accidentally use Console using Console = Terminal.Gui.FakeConsole; -namespace Terminal.Gui.TextTests { - public class TextFormatterTests { - readonly ITestOutputHelper output; +namespace Terminal.Gui.TextTests; - public TextFormatterTests (ITestOutputHelper output) - { - this.output = output; - } +public class TextFormatterTests { + readonly ITestOutputHelper _output; - [Fact] - public void Basic_Usage () - { - var testText = "test"; - var expectedSize = new Size (); - var testBounds = new Rect (0, 0, 100, 1); - var tf = new TextFormatter (); - - tf.Text = testText; - expectedSize = new Size (testText.Length, 1); - Assert.Equal (testText, tf.Text); - Assert.Equal (TextAlignment.Left, tf.Alignment); - Assert.Equal (expectedSize, tf.Size); - tf.Draw (testBounds, new Attribute (), new Attribute ()); - Assert.Equal (expectedSize, tf.Size); - Assert.NotEmpty (tf.Lines); - - tf.Alignment = TextAlignment.Right; - expectedSize = new Size (testText.Length, 1); - Assert.Equal (testText, tf.Text); - Assert.Equal (TextAlignment.Right, tf.Alignment); - Assert.Equal (expectedSize, tf.Size); - tf.Draw (testBounds, new Attribute (), new Attribute ()); - Assert.Equal (expectedSize, tf.Size); - Assert.NotEmpty (tf.Lines); - - tf.Alignment = TextAlignment.Right; - expectedSize = new Size (testText.Length * 2, 1); - tf.Size = expectedSize; - Assert.Equal (testText, tf.Text); - Assert.Equal (TextAlignment.Right, tf.Alignment); - Assert.Equal (expectedSize, tf.Size); - tf.Draw (testBounds, new Attribute (), new Attribute ()); - Assert.Equal (expectedSize, tf.Size); - Assert.NotEmpty (tf.Lines); - - tf.Alignment = TextAlignment.Centered; - expectedSize = new Size (testText.Length * 2, 1); - tf.Size = expectedSize; - Assert.Equal (testText, tf.Text); - Assert.Equal (TextAlignment.Centered, tf.Alignment); - Assert.Equal (expectedSize, tf.Size); - tf.Draw (testBounds, new Attribute (), new Attribute ()); - Assert.Equal (expectedSize, tf.Size); - Assert.NotEmpty (tf.Lines); - } + public TextFormatterTests (ITestOutputHelper output) => _output = output; - [Theory] - [InlineData (TextDirection.LeftRight_TopBottom, false)] - [InlineData (TextDirection.LeftRight_TopBottom, true)] - [InlineData (TextDirection.TopBottom_LeftRight, false)] - [InlineData (TextDirection.TopBottom_LeftRight, true)] - public void TestSize_TextChange (TextDirection textDirection, bool autoSize) - { - var tf = new TextFormatter () { Direction = textDirection, Text = "你", AutoSize = autoSize }; - Assert.Equal (2, tf.Size.Width); - Assert.Equal (1, tf.Size.Height); - tf.Text = "你你"; - if (autoSize) { - if (textDirection == TextDirection.LeftRight_TopBottom) { - Assert.Equal (4, tf.Size.Width); - Assert.Equal (1, tf.Size.Height); - } else { - Assert.Equal (2, tf.Size.Width); - Assert.Equal (2, tf.Size.Height); - } - } else { - Assert.Equal (2, tf.Size.Width); - Assert.Equal (1, tf.Size.Height); + public static IEnumerable CMGlyphs => + new List { + new object [] { $"{CM.Glyphs.LeftBracket} Say Hello 你 {CM.Glyphs.RightBracket}", 16, 15 } + }; + + public static IEnumerable FormatEnvironmentNewLine => + new List { + new object [] { $"Line1{Environment.NewLine}Line2{Environment.NewLine}Line3{Environment.NewLine}", 60, new [] { "Line1", "Line2", "Line3" } } + }; + + public static IEnumerable SplitEnvironmentNewLine => + new List { + new object [] { $"First Line 界{Environment.NewLine}Second Line 界{Environment.NewLine}Third Line 界", new [] { "First Line 界", "Second Line 界", "Third Line 界" } }, + new object [] { + $"First Line 界{Environment.NewLine}Second Line 界{Environment.NewLine}Third Line 界{Environment.NewLine}", new [] { "First Line 界", "Second Line 界", "Third Line 界", "" } } - } + }; - [Theory] - [InlineData (TextDirection.LeftRight_TopBottom)] - [InlineData (TextDirection.TopBottom_LeftRight)] - public void TestSize_AutoSizeChange (TextDirection textDirection) - { - var tf = new TextFormatter () { Direction = textDirection, Text = "你你" }; + [Fact] + public void Basic_Usage () + { + var testText = "test"; + var expectedSize = new Size (); + var testBounds = new Rect (0, 0, 100, 1); + var tf = new TextFormatter (); + + tf.Text = testText; + expectedSize = new Size (testText.Length, 1); + Assert.Equal (testText, tf.Text); + Assert.Equal (TextAlignment.Left, tf.Alignment); + Assert.Equal (expectedSize, tf.Size); + tf.Draw (testBounds, new Attribute (), new Attribute ()); + Assert.Equal (expectedSize, tf.Size); + Assert.NotEmpty (tf.Lines); + + tf.Alignment = TextAlignment.Right; + expectedSize = new Size (testText.Length, 1); + Assert.Equal (testText, tf.Text); + Assert.Equal (TextAlignment.Right, tf.Alignment); + Assert.Equal (expectedSize, tf.Size); + tf.Draw (testBounds, new Attribute (), new Attribute ()); + Assert.Equal (expectedSize, tf.Size); + Assert.NotEmpty (tf.Lines); + + tf.Alignment = TextAlignment.Right; + expectedSize = new Size (testText.Length * 2, 1); + tf.Size = expectedSize; + Assert.Equal (testText, tf.Text); + Assert.Equal (TextAlignment.Right, tf.Alignment); + Assert.Equal (expectedSize, tf.Size); + tf.Draw (testBounds, new Attribute (), new Attribute ()); + Assert.Equal (expectedSize, tf.Size); + Assert.NotEmpty (tf.Lines); + + tf.Alignment = TextAlignment.Centered; + expectedSize = new Size (testText.Length * 2, 1); + tf.Size = expectedSize; + Assert.Equal (testText, tf.Text); + Assert.Equal (TextAlignment.Centered, tf.Alignment); + Assert.Equal (expectedSize, tf.Size); + tf.Draw (testBounds, new Attribute (), new Attribute ()); + Assert.Equal (expectedSize, tf.Size); + Assert.NotEmpty (tf.Lines); + } + + [Theory] + [InlineData (TextDirection.LeftRight_TopBottom, false)] + [InlineData (TextDirection.LeftRight_TopBottom, true)] + [InlineData (TextDirection.TopBottom_LeftRight, false)] + [InlineData (TextDirection.TopBottom_LeftRight, true)] + public void TestSize_TextChange (TextDirection textDirection, bool autoSize) + { + var tf = new TextFormatter { Direction = textDirection, Text = "你", AutoSize = autoSize }; + Assert.Equal (2, tf.Size.Width); + Assert.Equal (1, tf.Size.Height); + tf.Text = "你你"; + if (autoSize) { if (textDirection == TextDirection.LeftRight_TopBottom) { Assert.Equal (4, tf.Size.Width); Assert.Equal (1, tf.Size.Height); @@ -102,29 +98,58 @@ public void TestSize_AutoSizeChange (TextDirection textDirection) Assert.Equal (2, tf.Size.Width); Assert.Equal (2, tf.Size.Height); } - Assert.False (tf.AutoSize); + } else { + Assert.Equal (2, tf.Size.Width); + Assert.Equal (1, tf.Size.Height); + } + } - tf.Size = new Size (1, 1); - Assert.Equal (1, tf.Size.Width); + [Theory] + [InlineData (TextDirection.LeftRight_TopBottom)] + [InlineData (TextDirection.TopBottom_LeftRight)] + public void TestSize_AutoSizeChange (TextDirection textDirection) + { + var tf = new TextFormatter { Direction = textDirection, Text = "你你" }; + if (textDirection == TextDirection.LeftRight_TopBottom) { + Assert.Equal (4, tf.Size.Width); Assert.Equal (1, tf.Size.Height); - tf.AutoSize = true; - if (textDirection == TextDirection.LeftRight_TopBottom) { - Assert.Equal (4, tf.Size.Width); - Assert.Equal (1, tf.Size.Height); - } else { - Assert.Equal (2, tf.Size.Width); - Assert.Equal (2, tf.Size.Height); - } + } else { + Assert.Equal (2, tf.Size.Width); + Assert.Equal (2, tf.Size.Height); + } + Assert.False (tf.AutoSize); + + tf.Size = new Size (1, 1); + Assert.Equal (1, tf.Size.Width); + Assert.Equal (1, tf.Size.Height); + tf.AutoSize = true; + if (textDirection == TextDirection.LeftRight_TopBottom) { + Assert.Equal (4, tf.Size.Width); + Assert.Equal (1, tf.Size.Height); + } else { + Assert.Equal (2, tf.Size.Width); + Assert.Equal (2, tf.Size.Height); } + } - [Theory] - [InlineData (TextDirection.LeftRight_TopBottom, false)] - [InlineData (TextDirection.LeftRight_TopBottom, true)] - [InlineData (TextDirection.TopBottom_LeftRight, false)] - [InlineData (TextDirection.TopBottom_LeftRight, true)] - public void TestSize_SizeChange_AutoSize_True_Or_False (TextDirection textDirection, bool autoSize) - { - var tf = new TextFormatter () { Direction = textDirection, Text = "你你", AutoSize = autoSize }; + [Theory] + [InlineData (TextDirection.LeftRight_TopBottom, false)] + [InlineData (TextDirection.LeftRight_TopBottom, true)] + [InlineData (TextDirection.TopBottom_LeftRight, false)] + [InlineData (TextDirection.TopBottom_LeftRight, true)] + public void TestSize_SizeChange_AutoSize_True_Or_False (TextDirection textDirection, bool autoSize) + { + var tf = new TextFormatter { Direction = textDirection, Text = "你你", AutoSize = autoSize }; + if (textDirection == TextDirection.LeftRight_TopBottom) { + Assert.Equal (4, tf.Size.Width); + Assert.Equal (1, tf.Size.Height); + } else { + Assert.Equal (2, tf.Size.Width); + Assert.Equal (2, tf.Size.Height); + } + + tf.Size = new Size (1, 1); + if (autoSize) { if (textDirection == TextDirection.LeftRight_TopBottom) { Assert.Equal (4, tf.Size.Width); Assert.Equal (1, tf.Size.Height); @@ -132,1639 +157,1695 @@ public void TestSize_SizeChange_AutoSize_True_Or_False (TextDirection textDirect Assert.Equal (2, tf.Size.Width); Assert.Equal (2, tf.Size.Height); } - - tf.Size = new Size (1, 1); - if (autoSize) { - if (textDirection == TextDirection.LeftRight_TopBottom) { - Assert.Equal (4, tf.Size.Width); - Assert.Equal (1, tf.Size.Height); - } else { - Assert.Equal (2, tf.Size.Width); - Assert.Equal (2, tf.Size.Height); - } - } else { - Assert.Equal (1, tf.Size.Width); - Assert.Equal (1, tf.Size.Height); - } + } else { + Assert.Equal (1, tf.Size.Width); + Assert.Equal (1, tf.Size.Height); } + } - [Theory] - [InlineData (TextAlignment.Left, false)] - [InlineData (TextAlignment.Centered, true)] - [InlineData (TextAlignment.Right, false)] - [InlineData (TextAlignment.Justified, true)] - public void TestSize_SizeChange_AutoSize_True_Or_False_Horizontal (TextAlignment textAlignment, bool autoSize) - { - var tf = new TextFormatter () { Direction = TextDirection.LeftRight_TopBottom, Text = "你你", Alignment = textAlignment, AutoSize = autoSize }; - Assert.Equal (4, tf.Size.Width); - Assert.Equal (1, tf.Size.Height); - - tf.Size = new Size (1, 1); - if (autoSize && textAlignment != TextAlignment.Justified) { - Assert.Equal (4, tf.Size.Width); - Assert.Equal (1, tf.Size.Height); - } else { - Assert.Equal (1, tf.Size.Width); - Assert.Equal (1, tf.Size.Height); - } + [Theory] + [InlineData (TextAlignment.Left, false)] + [InlineData (TextAlignment.Centered, true)] + [InlineData (TextAlignment.Right, false)] + [InlineData (TextAlignment.Justified, true)] + public void TestSize_SizeChange_AutoSize_True_Or_False_Horizontal (TextAlignment textAlignment, bool autoSize) + { + var tf = new TextFormatter { Direction = TextDirection.LeftRight_TopBottom, Text = "你你", Alignment = textAlignment, AutoSize = autoSize }; + Assert.Equal (4, tf.Size.Width); + Assert.Equal (1, tf.Size.Height); + + tf.Size = new Size (1, 1); + if (autoSize && textAlignment != TextAlignment.Justified) { + Assert.Equal (4, tf.Size.Width); + Assert.Equal (1, tf.Size.Height); + } else { + Assert.Equal (1, tf.Size.Width); + Assert.Equal (1, tf.Size.Height); } + } - [Theory] - [InlineData (VerticalTextAlignment.Top, false)] - [InlineData (VerticalTextAlignment.Middle, true)] - [InlineData (VerticalTextAlignment.Bottom, false)] - [InlineData (VerticalTextAlignment.Justified, true)] - public void TestSize_SizeChange_AutoSize_True_Or_False_Vertical (VerticalTextAlignment textAlignment, bool autoSize) - { - var tf = new TextFormatter () { Direction = TextDirection.TopBottom_LeftRight, Text = "你你", VerticalAlignment = textAlignment, AutoSize = autoSize }; + [Theory] + [InlineData (VerticalTextAlignment.Top, false)] + [InlineData (VerticalTextAlignment.Middle, true)] + [InlineData (VerticalTextAlignment.Bottom, false)] + [InlineData (VerticalTextAlignment.Justified, true)] + public void TestSize_SizeChange_AutoSize_True_Or_False_Vertical (VerticalTextAlignment textAlignment, bool autoSize) + { + var tf = new TextFormatter { Direction = TextDirection.TopBottom_LeftRight, Text = "你你", VerticalAlignment = textAlignment, AutoSize = autoSize }; + Assert.Equal (2, tf.Size.Width); + Assert.Equal (2, tf.Size.Height); + + tf.Size = new Size (1, 1); + if (autoSize && textAlignment != VerticalTextAlignment.Justified) { Assert.Equal (2, tf.Size.Width); Assert.Equal (2, tf.Size.Height); - - tf.Size = new Size (1, 1); - if (autoSize && textAlignment != VerticalTextAlignment.Justified) { - Assert.Equal (2, tf.Size.Width); - Assert.Equal (2, tf.Size.Height); - } else { - Assert.Equal (1, tf.Size.Width); - Assert.Equal (1, tf.Size.Height); - } + } else { + Assert.Equal (1, tf.Size.Width); + Assert.Equal (1, tf.Size.Height); } + } - [Fact] - public void NeedsFormat_Sets () - { - var testText = "test"; - var testBounds = new Rect (0, 0, 100, 1); - var tf = new TextFormatter (); - - tf.Text = "test"; - Assert.True (tf.NeedsFormat); // get_Lines causes a Format - Assert.NotEmpty (tf.Lines); - Assert.False (tf.NeedsFormat); // get_Lines causes a Format - Assert.Equal (testText, tf.Text); - tf.Draw (testBounds, new Attribute (), new Attribute ()); - Assert.False (tf.NeedsFormat); - - tf.Size = new Size (1, 1); - Assert.True (tf.NeedsFormat); - Assert.NotEmpty (tf.Lines); - Assert.False (tf.NeedsFormat); // get_Lines causes a Format - - tf.Alignment = TextAlignment.Centered; - Assert.True (tf.NeedsFormat); - Assert.NotEmpty (tf.Lines); - Assert.False (tf.NeedsFormat); // get_Lines causes a Format + [Theory] + [InlineData (TextAlignment.Left, false)] + [InlineData (TextAlignment.Centered, true)] + [InlineData (TextAlignment.Right, false)] + [InlineData (TextAlignment.Justified, true)] + public void TestSize_DirectionChange_AutoSize_True_Or_False_Horizontal (TextAlignment textAlignment, bool autoSize) + { + var tf = new TextFormatter { Direction = TextDirection.LeftRight_TopBottom, Text = "你你", Alignment = textAlignment, AutoSize = autoSize }; + Assert.Equal (4, tf.Size.Width); + Assert.Equal (1, tf.Size.Height); + + tf.Direction = TextDirection.TopBottom_LeftRight; + if (autoSize && textAlignment != TextAlignment.Justified) { + Assert.Equal (2, tf.Size.Width); + Assert.Equal (2, tf.Size.Height); + } else { + Assert.Equal (4, tf.Size.Width); + Assert.Equal (1, tf.Size.Height); } + } - [Theory] - [InlineData (null)] - [InlineData ("")] - [InlineData ("no hotkey")] - [InlineData ("No hotkey, Upper Case")] - [InlineData ("Non-english: Сохранить")] - public void FindHotKey_Invalid_ReturnsFalse (string text) - { - Rune hotKeySpecifier = (Rune)'_'; - bool supportFirstUpperCase = false; - int hotPos = 0; - Key hotKey = KeyCode.Null; - bool result = false; - - result = TextFormatter.FindHotKey (text, hotKeySpecifier, supportFirstUpperCase, out hotPos, out hotKey); - Assert.False (result); - Assert.Equal (-1, hotPos); - Assert.Equal (KeyCode.Null, hotKey); + [Theory] + [InlineData (VerticalTextAlignment.Top, false)] + [InlineData (VerticalTextAlignment.Middle, true)] + [InlineData (VerticalTextAlignment.Bottom, false)] + [InlineData (VerticalTextAlignment.Justified, true)] + public void TestSize_DirectionChange_AutoSize_True_Or_False_Vertical (VerticalTextAlignment textAlignment, bool autoSize) + { + var tf = new TextFormatter { Direction = TextDirection.TopBottom_LeftRight, Text = "你你", VerticalAlignment = textAlignment, AutoSize = autoSize }; + Assert.Equal (2, tf.Size.Width); + Assert.Equal (2, tf.Size.Height); + + tf.Direction = TextDirection.LeftRight_TopBottom; + if (autoSize && textAlignment != VerticalTextAlignment.Justified) { + Assert.Equal (4, tf.Size.Width); + Assert.Equal (1, tf.Size.Height); + } else { + Assert.Equal (2, tf.Size.Width); + Assert.Equal (2, tf.Size.Height); } + } - [Theory] - [InlineData ("_K Before", true, 0, (KeyCode)'K')] - [InlineData ("a_K Second", true, 1, (KeyCode)'K')] - [InlineData ("Last _K", true, 5, (KeyCode)'K')] - [InlineData ("After K_", false, -1, KeyCode.Null)] - [InlineData ("Multiple _K and _R", true, 9, (KeyCode)'K')] - [InlineData ("Non-english: _Кдать", true, 13, (KeyCode)'К')] // Cryllic K (К) - [InlineData ("_K Before", true, 0, (KeyCode)'K', true)] // Turn on FirstUpperCase and verify same results - [InlineData ("a_K Second", true, 1, (KeyCode)'K', true)] - [InlineData ("Last _K", true, 5, (KeyCode)'K', true)] - [InlineData ("After K_", false, -1, KeyCode.Null, true)] - [InlineData ("Multiple _K and _R", true, 9, (KeyCode)'K', true)] - [InlineData ("Non-english: _Кдать", true, 13, (KeyCode)'К', true)] // Cryllic K (К) - public void FindHotKey_AlphaUpperCase_Succeeds (string text, bool expectedResult, int expectedHotPos, KeyCode expectedKey, bool supportFirstUpperCase = false) - { - Rune hotKeySpecifier = (Rune)'_'; - - var result = TextFormatter.FindHotKey (text, hotKeySpecifier, supportFirstUpperCase, out int hotPos, out var hotKey); - if (expectedResult) { - Assert.True (result); - } else { - Assert.False (result); - } - Assert.Equal (expectedResult, result); - Assert.Equal (expectedHotPos, hotPos); - Assert.Equal ((Key)expectedKey, hotKey); - } + [Fact] + public void NeedsFormat_Sets () + { + var testText = "test"; + var testBounds = new Rect (0, 0, 100, 1); + var tf = new TextFormatter (); + + tf.Text = "test"; + Assert.True (tf.NeedsFormat); // get_Lines causes a Format + Assert.NotEmpty (tf.Lines); + Assert.False (tf.NeedsFormat); // get_Lines causes a Format + Assert.Equal (testText, tf.Text); + tf.Draw (testBounds, new Attribute (), new Attribute ()); + Assert.False (tf.NeedsFormat); + + tf.Size = new Size (1, 1); + Assert.True (tf.NeedsFormat); + Assert.NotEmpty (tf.Lines); + Assert.False (tf.NeedsFormat); // get_Lines causes a Format + + tf.Alignment = TextAlignment.Centered; + Assert.True (tf.NeedsFormat); + Assert.NotEmpty (tf.Lines); + Assert.False (tf.NeedsFormat); // get_Lines causes a Format + } - [Theory] - [InlineData ("_k Before", true, 0, (KeyCode)'K')] // lower case should return uppercase Hotkey - [InlineData ("a_k Second", true, 1, (KeyCode)'K')] - [InlineData ("Last _k", true, 5, (KeyCode)'K')] - [InlineData ("After k_", false, -1, KeyCode.Null)] - [InlineData ("Multiple _k and _R", true, 9, (KeyCode)'K')] - [InlineData ("Non-english: _кдать", true, 13, (KeyCode)'к')] // Lower case Cryllic K (к) - [InlineData ("_k Before", true, 0, (KeyCode)'K', true)] // Turn on FirstUpperCase and verify same results - [InlineData ("a_k Second", true, 1, (KeyCode)'K', true)] - [InlineData ("Last _k", true, 5, (KeyCode)'K', true)] - [InlineData ("After k_", false, -1, KeyCode.Null, true)] - [InlineData ("Multiple _k and _r", true, 9, (KeyCode)'K', true)] - [InlineData ("Non-english: _кдать", true, 13, (KeyCode)'к', true)] // Cryllic K (К) - public void FindHotKey_AlphaLowerCase_Succeeds (string text, bool expectedResult, int expectedHotPos, KeyCode expectedKey, bool supportFirstUpperCase = false) - { - Rune hotKeySpecifier = (Rune)'_'; - - var result = TextFormatter.FindHotKey (text, hotKeySpecifier, supportFirstUpperCase, out int hotPos, out var hotKey); - if (expectedResult) { - Assert.True (result); - } else { - Assert.False (result); - } - Assert.Equal (expectedResult, result); - Assert.Equal (expectedHotPos, hotPos); - Assert.Equal ((Key)expectedKey, hotKey); - } + [Theory] + [InlineData (null)] + [InlineData ("")] + [InlineData ("no hotkey")] + [InlineData ("No hotkey, Upper Case")] + [InlineData ("Non-english: Сохранить")] + public void FindHotKey_Invalid_ReturnsFalse (string text) + { + var hotKeySpecifier = (Rune)'_'; + var supportFirstUpperCase = false; + var hotPos = 0; + Key hotKey = KeyCode.Null; + var result = false; + + result = TextFormatter.FindHotKey (text, hotKeySpecifier, supportFirstUpperCase, out hotPos, out hotKey); + Assert.False (result); + Assert.Equal (-1, hotPos); + Assert.Equal (KeyCode.Null, hotKey); + } - [Theory] - [InlineData ("_1 Before", true, 0, (KeyCode)'1')] // Digits - [InlineData ("a_1 Second", true, 1, (KeyCode)'1')] - [InlineData ("Last _1", true, 5, (KeyCode)'1')] - [InlineData ("After 1_", false, -1, KeyCode.Null)] - [InlineData ("Multiple _1 and _2", true, 9, (KeyCode)'1')] - [InlineData ("_1 Before", true, 0, (KeyCode)'1', true)] // Turn on FirstUpperCase and verify same results - [InlineData ("a_1 Second", true, 1, (KeyCode)'1', true)] - [InlineData ("Last _1", true, 5, (KeyCode)'1', true)] - [InlineData ("After 1_", false, -1, KeyCode.Null, true)] - [InlineData ("Multiple _1 and _2", true, 9, (KeyCode)'1', true)] - public void FindHotKey_Numeric_Succeeds (string text, bool expectedResult, int expectedHotPos, KeyCode expectedKey, bool supportFirstUpperCase = false) - { - Rune hotKeySpecifier = (Rune)'_'; - - var result = TextFormatter.FindHotKey (text, hotKeySpecifier, supportFirstUpperCase, out int hotPos, out var hotKey); - if (expectedResult) { - Assert.True (result); - } else { - Assert.False (result); - } - Assert.Equal (expectedResult, result); - Assert.Equal (expectedHotPos, hotPos); - Assert.Equal ((Key)expectedKey, hotKey); + [Theory] + [InlineData ("_K Before", true, 0, (KeyCode)'K')] + [InlineData ("a_K Second", true, 1, (KeyCode)'K')] + [InlineData ("Last _K", true, 5, (KeyCode)'K')] + [InlineData ("After K_", false, -1, KeyCode.Null)] + [InlineData ("Multiple _K and _R", true, 9, (KeyCode)'K')] + [InlineData ("Non-english: _Кдать", true, 13, (KeyCode)'К')] // Cryllic K (К) + [InlineData ("_K Before", true, 0, (KeyCode)'K', true)] // Turn on FirstUpperCase and verify same results + [InlineData ("a_K Second", true, 1, (KeyCode)'K', true)] + [InlineData ("Last _K", true, 5, (KeyCode)'K', true)] + [InlineData ("After K_", false, -1, KeyCode.Null, true)] + [InlineData ("Multiple _K and _R", true, 9, (KeyCode)'K', true)] + [InlineData ("Non-english: _Кдать", true, 13, (KeyCode)'К', true)] // Cryllic K (К) + public void FindHotKey_AlphaUpperCase_Succeeds (string text, bool expectedResult, int expectedHotPos, KeyCode expectedKey, bool supportFirstUpperCase = false) + { + var hotKeySpecifier = (Rune)'_'; + + var result = TextFormatter.FindHotKey (text, hotKeySpecifier, supportFirstUpperCase, out var hotPos, out var hotKey); + if (expectedResult) { + Assert.True (result); + } else { + Assert.False (result); } + Assert.Equal (expectedResult, result); + Assert.Equal (expectedHotPos, hotPos); + Assert.Equal (expectedKey, hotKey); + } - [Theory] - [InlineData ("K Before", true, 0, (KeyCode)'K')] - [InlineData ("aK Second", true, 1, (KeyCode)'K')] - [InlineData ("last K", true, 5, (KeyCode)'K')] - [InlineData ("multiple K and R", true, 9, (KeyCode)'K')] - [InlineData ("non-english: Кдать", true, 13, (KeyCode)'К')] // Cryllic K (К) - public void FindHotKey_Legacy_FirstUpperCase_Succeeds (string text, bool expectedResult, int expectedHotPos, KeyCode expectedKey) - { - var supportFirstUpperCase = true; - - Rune hotKeySpecifier = (Rune)0; - - var result = TextFormatter.FindHotKey (text, hotKeySpecifier, supportFirstUpperCase, out int hotPos, out var hotKey); - if (expectedResult) { - Assert.True (result); - } else { - Assert.False (result); - } - Assert.Equal (expectedResult, result); - Assert.Equal (expectedHotPos, hotPos); - Assert.Equal ((Key)expectedKey, hotKey); - } - - [Theory] - [InlineData ("_\"k before", true, (KeyCode)'"')] // BUGBUG: Not sure why this fails. " is a normal char - [InlineData ("\"_k before", true, KeyCode.K)] - [InlineData ("_`~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?", true, (KeyCode)'`')] - [InlineData ("`_~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?", true, (KeyCode)'~')] - [InlineData ("`~!@#$%^&*()-__=+[{]}\\|;:'\",<.>/?", true, (KeyCode)'=')] // BUGBUG: Not sure why this fails. Ignore the first and consider the second - [InlineData ("_ ~  s  gui.cs   master ↑10", true, (KeyCode)'')] // ~IsLetterOrDigit + Unicode - [InlineData (" ~  s  gui.cs  _ master ↑10", true, (KeyCode)'')] // ~IsLetterOrDigit + Unicode - [InlineData ("non-english: _кдать", true, (KeyCode)'к')] // Lower case Cryllic K (к) - public void FindHotKey_Symbols_Returns_Symbol (string text, bool found, KeyCode expected) - { - var hotKeySpecifier = (Rune)'_'; - - var result = TextFormatter.FindHotKey (text, hotKeySpecifier, false, out int _, out var hotKey); - Assert.Equal (found, result); - Assert.Equal ((Key)expected, hotKey); + [Theory] + [InlineData ("_k Before", true, 0, (KeyCode)'K')] // lower case should return uppercase Hotkey + [InlineData ("a_k Second", true, 1, (KeyCode)'K')] + [InlineData ("Last _k", true, 5, (KeyCode)'K')] + [InlineData ("After k_", false, -1, KeyCode.Null)] + [InlineData ("Multiple _k and _R", true, 9, (KeyCode)'K')] + [InlineData ("Non-english: _кдать", true, 13, (KeyCode)'к')] // Lower case Cryllic K (к) + [InlineData ("_k Before", true, 0, (KeyCode)'K', true)] // Turn on FirstUpperCase and verify same results + [InlineData ("a_k Second", true, 1, (KeyCode)'K', true)] + [InlineData ("Last _k", true, 5, (KeyCode)'K', true)] + [InlineData ("After k_", false, -1, KeyCode.Null, true)] + [InlineData ("Multiple _k and _r", true, 9, (KeyCode)'K', true)] + [InlineData ("Non-english: _кдать", true, 13, (KeyCode)'к', true)] // Cryllic K (К) + public void FindHotKey_AlphaLowerCase_Succeeds (string text, bool expectedResult, int expectedHotPos, KeyCode expectedKey, bool supportFirstUpperCase = false) + { + var hotKeySpecifier = (Rune)'_'; + + var result = TextFormatter.FindHotKey (text, hotKeySpecifier, supportFirstUpperCase, out var hotPos, out var hotKey); + if (expectedResult) { + Assert.True (result); + } else { + Assert.False (result); } + Assert.Equal (expectedResult, result); + Assert.Equal (expectedHotPos, hotPos); + Assert.Equal (expectedKey, hotKey); + } - [Theory] - [InlineData ("\"k before")] - [InlineData ("ak second")] - [InlineData ("last k")] - [InlineData ("multiple k and r")] - [InlineData ("12345")] - [InlineData ("`~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?")] // punctuation - [InlineData (" ~  s  gui.cs   master ↑10")] // ~IsLetterOrDigit + Unicode - [InlineData ("non-english: кдать")] // Lower case Cryllic K (к) - public void FindHotKey_Legacy_FirstUpperCase_NotFound_Returns_False (string text) - { - bool supportFirstUpperCase = true; - - var hotKeySpecifier = (Rune)0; - - var result = TextFormatter.FindHotKey (text, hotKeySpecifier, supportFirstUpperCase, out int hotPos, out var hotKey); + [Theory] + [InlineData ("_1 Before", true, 0, (KeyCode)'1')] // Digits + [InlineData ("a_1 Second", true, 1, (KeyCode)'1')] + [InlineData ("Last _1", true, 5, (KeyCode)'1')] + [InlineData ("After 1_", false, -1, KeyCode.Null)] + [InlineData ("Multiple _1 and _2", true, 9, (KeyCode)'1')] + [InlineData ("_1 Before", true, 0, (KeyCode)'1', true)] // Turn on FirstUpperCase and verify same results + [InlineData ("a_1 Second", true, 1, (KeyCode)'1', true)] + [InlineData ("Last _1", true, 5, (KeyCode)'1', true)] + [InlineData ("After 1_", false, -1, KeyCode.Null, true)] + [InlineData ("Multiple _1 and _2", true, 9, (KeyCode)'1', true)] + public void FindHotKey_Numeric_Succeeds (string text, bool expectedResult, int expectedHotPos, KeyCode expectedKey, bool supportFirstUpperCase = false) + { + var hotKeySpecifier = (Rune)'_'; + + var result = TextFormatter.FindHotKey (text, hotKeySpecifier, supportFirstUpperCase, out var hotPos, out var hotKey); + if (expectedResult) { + Assert.True (result); + } else { Assert.False (result); - Assert.Equal (-1, hotPos); - Assert.Equal (KeyCode.Null, hotKey); } + Assert.Equal (expectedResult, result); + Assert.Equal (expectedHotPos, hotPos); + Assert.Equal (expectedKey, hotKey); + } - [Theory] - [InlineData (null)] - [InlineData ("")] - [InlineData ("a")] - public void RemoveHotKeySpecifier_InValid_ReturnsOriginal (string text) - { - Rune hotKeySpecifier = (Rune)'_'; - - if (text == null) { - Assert.Null (TextFormatter.RemoveHotKeySpecifier (text, 0, hotKeySpecifier)); - Assert.Null (TextFormatter.RemoveHotKeySpecifier (text, -1, hotKeySpecifier)); - Assert.Null (TextFormatter.RemoveHotKeySpecifier (text, 100, hotKeySpecifier)); - } else { - Assert.Equal (text, TextFormatter.RemoveHotKeySpecifier (text, 0, hotKeySpecifier)); - Assert.Equal (text, TextFormatter.RemoveHotKeySpecifier (text, -1, hotKeySpecifier)); - Assert.Equal (text, TextFormatter.RemoveHotKeySpecifier (text, 100, hotKeySpecifier)); - } + [Theory] + [InlineData ("K Before", true, 0, (KeyCode)'K')] + [InlineData ("aK Second", true, 1, (KeyCode)'K')] + [InlineData ("last K", true, 5, (KeyCode)'K')] + [InlineData ("multiple K and R", true, 9, (KeyCode)'K')] + [InlineData ("non-english: Кдать", true, 13, (KeyCode)'К')] // Cryllic K (К) + public void FindHotKey_Legacy_FirstUpperCase_Succeeds (string text, bool expectedResult, int expectedHotPos, KeyCode expectedKey) + { + var supportFirstUpperCase = true; + + var hotKeySpecifier = (Rune)0; + + var result = TextFormatter.FindHotKey (text, hotKeySpecifier, supportFirstUpperCase, out var hotPos, out var hotKey); + if (expectedResult) { + Assert.True (result); + } else { + Assert.False (result); } + Assert.Equal (expectedResult, result); + Assert.Equal (expectedHotPos, hotPos); + Assert.Equal (expectedKey, hotKey); + } - [Theory] - [InlineData ("_K Before", 0, "K Before")] - [InlineData ("a_K Second", 1, "aK Second")] - [InlineData ("Last _K", 5, "Last K")] - [InlineData ("After K_", 7, "After K")] - [InlineData ("Multiple _K and _R", 9, "Multiple K and _R")] - [InlineData ("Non-english: _Кдать", 13, "Non-english: Кдать")] - public void RemoveHotKeySpecifier_Valid_ReturnsStripped (string text, int hotPos, string expectedText) - { - Rune hotKeySpecifier = (Rune)'_'; - - Assert.Equal (expectedText, TextFormatter.RemoveHotKeySpecifier (text, hotPos, hotKeySpecifier)); - } + [Theory] + [InlineData ("_\"k before", true, (KeyCode)'"')] // BUGBUG: Not sure why this fails. " is a normal char + [InlineData ("\"_k before", true, KeyCode.K)] + [InlineData ("_`~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?", true, (KeyCode)'`')] + [InlineData ("`_~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?", true, (KeyCode)'~')] + [InlineData ("`~!@#$%^&*()-__=+[{]}\\|;:'\",<.>/?", true, (KeyCode)'=')] // BUGBUG: Not sure why this fails. Ignore the first and consider the second + [InlineData ("_ ~  s  gui.cs   master ↑10", true, (KeyCode)'')] // ~IsLetterOrDigit + Unicode + [InlineData (" ~  s  gui.cs  _ master ↑10", true, (KeyCode)'')] // ~IsLetterOrDigit + Unicode + [InlineData ("non-english: _кдать", true, (KeyCode)'к')] // Lower case Cryllic K (к) + public void FindHotKey_Symbols_Returns_Symbol (string text, bool found, KeyCode expected) + { + var hotKeySpecifier = (Rune)'_'; + + var result = TextFormatter.FindHotKey (text, hotKeySpecifier, false, out var _, out var hotKey); + Assert.Equal (found, result); + Assert.Equal (expected, hotKey); + } - [Theory] - [InlineData ("all lower case", 0)] - [InlineData ("K Before", 0)] - [InlineData ("aK Second", 1)] - [InlineData ("Last K", 5)] - [InlineData ("fter K", 7)] - [InlineData ("Multiple K and R", 9)] - [InlineData ("Non-english: Кдать", 13)] - public void RemoveHotKeySpecifier_Valid_Legacy_ReturnsOriginal (string text, int hotPos) - { - Rune hotKeySpecifier = (Rune)'_'; - - Assert.Equal (text, TextFormatter.RemoveHotKeySpecifier (text, hotPos, hotKeySpecifier)); - } + [Theory] + [InlineData ("\"k before")] + [InlineData ("ak second")] + [InlineData ("last k")] + [InlineData ("multiple k and r")] + [InlineData ("12345")] + [InlineData ("`~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?")] // punctuation + [InlineData (" ~  s  gui.cs   master ↑10")] // ~IsLetterOrDigit + Unicode + [InlineData ("non-english: кдать")] // Lower case Cryllic K (к) + public void FindHotKey_Legacy_FirstUpperCase_NotFound_Returns_False (string text) + { + var supportFirstUpperCase = true; + + var hotKeySpecifier = (Rune)0; + + var result = TextFormatter.FindHotKey (text, hotKeySpecifier, supportFirstUpperCase, out var hotPos, out var hotKey); + Assert.False (result); + Assert.Equal (-1, hotPos); + Assert.Equal (KeyCode.Null, hotKey); + } - [Theory] - [InlineData (null)] - [InlineData ("")] - public void CalcRect_Invalid_Returns_Empty (string text) - { - Assert.Equal (Rect.Empty, TextFormatter.CalcRect (0, 0, text)); - Assert.Equal (new Rect (new Point (1, 2), Size.Empty), TextFormatter.CalcRect (1, 2, text)); - Assert.Equal (new Rect (new Point (-1, -2), Size.Empty), TextFormatter.CalcRect (-1, -2, text)); + [Theory] + [InlineData (null)] + [InlineData ("")] + [InlineData ("a")] + public void RemoveHotKeySpecifier_InValid_ReturnsOriginal (string text) + { + var hotKeySpecifier = (Rune)'_'; + + if (text == null) { + Assert.Null (TextFormatter.RemoveHotKeySpecifier (text, 0, hotKeySpecifier)); + Assert.Null (TextFormatter.RemoveHotKeySpecifier (text, -1, hotKeySpecifier)); + Assert.Null (TextFormatter.RemoveHotKeySpecifier (text, 100, hotKeySpecifier)); + } else { + Assert.Equal (text, TextFormatter.RemoveHotKeySpecifier (text, 0, hotKeySpecifier)); + Assert.Equal (text, TextFormatter.RemoveHotKeySpecifier (text, -1, hotKeySpecifier)); + Assert.Equal (text, TextFormatter.RemoveHotKeySpecifier (text, 100, hotKeySpecifier)); } + } - [Theory] - [InlineData ("test")] - [InlineData (" ~  s  gui.cs   master ↑10")] - public void CalcRect_SingleLine_Returns_1High (string text) - { - Assert.Equal (new Rect (0, 0, text.GetRuneCount (), 1), TextFormatter.CalcRect (0, 0, text)); - Assert.Equal (new Rect (0, 0, text.GetColumns (), 1), TextFormatter.CalcRect (0, 0, text)); - } + [Theory] + [InlineData ("_K Before", 0, "K Before")] + [InlineData ("a_K Second", 1, "aK Second")] + [InlineData ("Last _K", 5, "Last K")] + [InlineData ("After K_", 7, "After K")] + [InlineData ("Multiple _K and _R", 9, "Multiple K and _R")] + [InlineData ("Non-english: _Кдать", 13, "Non-english: Кдать")] + public void RemoveHotKeySpecifier_Valid_ReturnsStripped (string text, int hotPos, string expectedText) + { + var hotKeySpecifier = (Rune)'_'; + + Assert.Equal (expectedText, TextFormatter.RemoveHotKeySpecifier (text, hotPos, hotKeySpecifier)); + } - [Theory] - [InlineData ("line1\nline2", 5, 2)] - [InlineData ("\nline2", 5, 2)] - [InlineData ("\n\n", 0, 3)] - [InlineData ("\n\n\n", 0, 4)] - [InlineData ("line1\nline2\nline3long!", 10, 3)] - [InlineData ("line1\nline2\n\n", 5, 4)] - [InlineData ("line1\r\nline2", 5, 2)] - [InlineData (" ~  s  gui.cs   master ↑10\n", 31, 2)] - [InlineData ("\n ~  s  gui.cs   master ↑10", 31, 2)] - [InlineData (" ~  s  gui.cs   master\n↑10", 27, 2)] - public void CalcRect_MultiLine_Returns_nHigh (string text, int expectedWidth, int expectedLines) - { - Assert.Equal (new Rect (0, 0, expectedWidth, expectedLines), TextFormatter.CalcRect (0, 0, text)); - var lines = text.Split (text.Contains (Environment.NewLine) ? Environment.NewLine : "\n"); - var maxWidth = lines.Max (s => s.GetColumns ()); - var lineWider = 0; - for (int i = 0; i < lines.Length; i++) { - var w = lines [i].GetColumns (); - if (w == maxWidth) { - lineWider = i; - } - } - Assert.Equal (new Rect (0, 0, maxWidth, expectedLines), TextFormatter.CalcRect (0, 0, text)); - Assert.Equal (new Rect (0, 0, lines [lineWider].ToRuneList ().Sum (r => Math.Max (r.GetColumns (), 0)), expectedLines), TextFormatter.CalcRect (0, 0, text)); - } + [Theory] + [InlineData ("all lower case", 0)] + [InlineData ("K Before", 0)] + [InlineData ("aK Second", 1)] + [InlineData ("Last K", 5)] + [InlineData ("fter K", 7)] + [InlineData ("Multiple K and R", 9)] + [InlineData ("Non-english: Кдать", 13)] + public void RemoveHotKeySpecifier_Valid_Legacy_ReturnsOriginal (string text, int hotPos) + { + var hotKeySpecifier = (Rune)'_'; + + Assert.Equal (text, TextFormatter.RemoveHotKeySpecifier (text, hotPos, hotKeySpecifier)); + } - [Theory] - [InlineData ("")] - [InlineData (null)] - [InlineData ("test")] - public void ClipAndJustify_Invalid_Returns_Original (string text) - { - var expected = string.IsNullOrEmpty (text) ? text : ""; - Assert.Equal (expected, TextFormatter.ClipAndJustify (text, 0, TextAlignment.Left)); - Assert.Equal (expected, TextFormatter.ClipAndJustify (text, 0, TextAlignment.Left)); - Assert.Throws (() => TextFormatter.ClipAndJustify (text, -1, TextAlignment.Left)); - } + [Theory] + [InlineData (null)] + [InlineData ("")] + public void CalcRect_Invalid_Returns_Empty (string text) + { + Assert.Equal (Rect.Empty, TextFormatter.CalcRect (0, 0, text)); + Assert.Equal (new Rect (new Point (1, 2), Size.Empty), TextFormatter.CalcRect (1, 2, text)); + Assert.Equal (new Rect (new Point (-1, -2), Size.Empty), TextFormatter.CalcRect (-1, -2, text)); + } - [Theory] - [InlineData ("test", "", 0)] - [InlineData ("test", "te", 2)] - [InlineData ("test", "test", int.MaxValue)] - [InlineData ("A sentence has words.", "A sentence has words.", 22)] // should fit - [InlineData ("A sentence has words.", "A sentence has words.", 21)] // should fit - [InlineData ("A sentence has words.", "A sentence has words.", int.MaxValue)] // should fit - [InlineData ("A sentence has words.", "A sentence has words", 20)] // Should not fit - [InlineData ("A sentence has words.", "A sentence", 10)] // Should not fit - [InlineData ("A\tsentence\thas\twords.", "A sentence has words.", int.MaxValue)] - [InlineData ("A\tsentence\thas\twords.", "A sentence", 10)] - [InlineData ("line1\nline2\nline3long!", "line1\nline2\nline3long!", int.MaxValue)] - [InlineData ("line1\nline2\nline3long!", "line1\nline", 10)] - [InlineData (" ~  s  gui.cs   master ↑10", " ~  s  ", 10)] // Unicode - [InlineData ("Ð ÑÐ", "Ð ÑÐ", 5)] // should fit - [InlineData ("Ð ÑÐ", "Ð ÑÐ", 4)] // should fit - [InlineData ("Ð ÑÐ", "Ð Ñ", 3)] // Should not fit - public void ClipAndJustify_Valid_Left (string text, string justifiedText, int maxWidth) - { - var align = TextAlignment.Left; - var textDirection = TextDirection.LeftRight_BottomTop; - var tabWidth = 1; - - Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth)); - var expectedClippedWidth = Math.Min (justifiedText.GetRuneCount (), maxWidth); - Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth)); - Assert.True (justifiedText.GetRuneCount () <= maxWidth); - Assert.True (justifiedText.GetColumns () <= maxWidth); - Assert.Equal (expectedClippedWidth, justifiedText.GetRuneCount ()); - Assert.Equal (expectedClippedWidth, justifiedText.ToRuneList ().Sum (r => Math.Max (r.GetColumns (), 1))); - Assert.True (expectedClippedWidth <= maxWidth); - Assert.Equal (StringExtensions.ToString (justifiedText.ToRunes () [0..expectedClippedWidth]), justifiedText); - } + [Theory] + [InlineData ("test")] + [InlineData (" ~  s  gui.cs   master ↑10")] + public void CalcRect_SingleLine_Returns_1High (string text) + { + Assert.Equal (new Rect (0, 0, text.GetRuneCount (), 1), TextFormatter.CalcRect (0, 0, text)); + Assert.Equal (new Rect (0, 0, text.GetColumns (), 1), TextFormatter.CalcRect (0, 0, text)); + } - [Theory] - [InlineData ("test", "", 0)] - [InlineData ("test", "te", 2)] - [InlineData ("test", "test", int.MaxValue)] - [InlineData ("A sentence has words.", "A sentence has words.", 22)] // should fit - [InlineData ("A sentence has words.", "A sentence has words.", 21)] // should fit - [InlineData ("A sentence has words.", "A sentence has words.", int.MaxValue)] // should fit - [InlineData ("A sentence has words.", "A sentence has words", 20)] // Should not fit - [InlineData ("A sentence has words.", "A sentence", 10)] // Should not fit - [InlineData ("A\tsentence\thas\twords.", "A sentence has words.", int.MaxValue)] - [InlineData ("A\tsentence\thas\twords.", "A sentence", 10)] - [InlineData ("line1\nline2\nline3long!", "line1\nline2\nline3long!", int.MaxValue)] - [InlineData ("line1\nline2\nline3long!", "line1\nline", 10)] - [InlineData (" ~  s  gui.cs   master ↑10", " ~  s  ", 10)] // Unicode - [InlineData ("Ð ÑÐ", "Ð ÑÐ", 5)] // should fit - [InlineData ("Ð ÑÐ", "Ð ÑÐ", 4)] // should fit - [InlineData ("Ð ÑÐ", "Ð Ñ", 3)] // Should not fit - public void ClipAndJustify_Valid_Right (string text, string justifiedText, int maxWidth) - { - var align = TextAlignment.Right; - var textDirection = TextDirection.LeftRight_BottomTop; - var tabWidth = 1; - - Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth)); - var expectedClippedWidth = Math.Min (justifiedText.GetRuneCount (), maxWidth); - Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth)); - Assert.True (justifiedText.GetRuneCount () <= maxWidth); - Assert.True (justifiedText.GetColumns () <= maxWidth); - Assert.Equal (expectedClippedWidth, justifiedText.GetRuneCount ()); - Assert.Equal (expectedClippedWidth, justifiedText.ToRuneList ().Sum (r => Math.Max (r.GetColumns (), 1))); - Assert.True (expectedClippedWidth <= maxWidth); - Assert.Equal (StringExtensions.ToString (justifiedText.ToRunes () [0..expectedClippedWidth]), justifiedText); + [Theory] + [InlineData ("line1\nline2", 5, 2)] + [InlineData ("\nline2", 5, 2)] + [InlineData ("\n\n", 0, 3)] + [InlineData ("\n\n\n", 0, 4)] + [InlineData ("line1\nline2\nline3long!", 10, 3)] + [InlineData ("line1\nline2\n\n", 5, 4)] + [InlineData ("line1\r\nline2", 5, 2)] + [InlineData (" ~  s  gui.cs   master ↑10\n", 31, 2)] + [InlineData ("\n ~  s  gui.cs   master ↑10", 31, 2)] + [InlineData (" ~  s  gui.cs   master\n↑10", 27, 2)] + public void CalcRect_MultiLine_Returns_nHigh (string text, int expectedWidth, int expectedLines) + { + Assert.Equal (new Rect (0, 0, expectedWidth, expectedLines), TextFormatter.CalcRect (0, 0, text)); + var lines = text.Split (text.Contains (Environment.NewLine) ? Environment.NewLine : "\n"); + var maxWidth = lines.Max (s => s.GetColumns ()); + var lineWider = 0; + for (var i = 0; i < lines.Length; i++) { + var w = lines [i].GetColumns (); + if (w == maxWidth) { + lineWider = i; + } } + Assert.Equal (new Rect (0, 0, maxWidth, expectedLines), TextFormatter.CalcRect (0, 0, text)); + Assert.Equal (new Rect (0, 0, lines [lineWider].ToRuneList ().Sum (r => Math.Max (r.GetColumns (), 0)), expectedLines), TextFormatter.CalcRect (0, 0, text)); + } - [Theory] - [InlineData ("test", "", 0)] - [InlineData ("test", "te", 2)] - [InlineData ("test", "test", int.MaxValue)] - [InlineData ("A sentence has words.", "A sentence has words.", 22)] // should fit - [InlineData ("A sentence has words.", "A sentence has words.", 21)] // should fit - [InlineData ("A sentence has words.", "A sentence has words.", int.MaxValue)] // should fit - [InlineData ("A sentence has words.", "A sentence has words", 20)] // Should not fit - [InlineData ("A sentence has words.", "A sentence", 10)] // Should not fit - [InlineData ("A\tsentence\thas\twords.", "A sentence has words.", int.MaxValue)] - [InlineData ("A\tsentence\thas\twords.", "A sentence", 10)] - [InlineData ("line1\nline2\nline3long!", "line1\nline2\nline3long!", int.MaxValue)] - [InlineData ("line1\nline2\nline3long!", "line1\nline", 10)] - [InlineData (" ~  s  gui.cs   master ↑10", " ~  s  ", 10)] // Unicode - [InlineData ("Ð ÑÐ", "Ð ÑÐ", 5)] // should fit - [InlineData ("Ð ÑÐ", "Ð ÑÐ", 4)] // should fit - [InlineData ("Ð ÑÐ", "Ð Ñ", 3)] // Should not fit - public void ClipAndJustify_Valid_Centered (string text, string justifiedText, int maxWidth) - { - var align = TextAlignment.Centered; - var textDirection = TextDirection.LeftRight_TopBottom; - var tabWidth = 1; - - Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth)); - var expectedClippedWidth = Math.Min (justifiedText.GetRuneCount (), maxWidth); - Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth)); - Assert.True (justifiedText.GetRuneCount () <= maxWidth); - Assert.True (justifiedText.GetColumns () <= maxWidth); - Assert.Equal (expectedClippedWidth, justifiedText.GetRuneCount ()); - Assert.Equal (expectedClippedWidth, justifiedText.ToRuneList ().Sum (r => Math.Max (r.GetColumns (), 1))); - Assert.True (expectedClippedWidth <= maxWidth); - Assert.Equal (StringExtensions.ToString (justifiedText.ToRunes () [0..expectedClippedWidth]), justifiedText); - } + [Theory] + [InlineData ("")] + [InlineData (null)] + [InlineData ("test")] + public void ClipAndJustify_Invalid_Returns_Original (string text) + { + var expected = string.IsNullOrEmpty (text) ? text : ""; + Assert.Equal (expected, TextFormatter.ClipAndJustify (text, 0, TextAlignment.Left)); + Assert.Equal (expected, TextFormatter.ClipAndJustify (text, 0, TextAlignment.Left)); + Assert.Throws (() => TextFormatter.ClipAndJustify (text, -1, TextAlignment.Left)); + } - [Theory] - [InlineData ("test", "", 0)] - [InlineData ("test", "te", 2)] - [InlineData ("test", "test", int.MaxValue)] // This doesn't throw because it only create a word with length 1 - [InlineData ("A sentence has words.", "A sentence has words.", 22)] // should fit - [InlineData ("A sentence has words.", "A sentence has words.", 21)] // should fit - [InlineData ("A sentence has words.", "A sentence has words.", 500)] // should fit - [InlineData ("A sentence has words.", "A sentence has words", 20)] // Should not fit - [InlineData ("A sentence has words.", "A sentence", 10)] // Should not fit - // Now throw System.OutOfMemoryException. See https://stackoverflow.com/questions/20672920/maxcapacity-of-stringbuilder - //[InlineData ("A\tsentence\thas\twords.", "A sentence has words.", int.MaxValue)] - [InlineData ("A\tsentence\thas\twords.", "A sentence", 10)] - [InlineData ("line1\nline2\nline3long!", "line1\nline2\nline3long!", int.MaxValue)] // This doesn't throw because it only create a line with length 1 - [InlineData ("line1\nline2\nline3long!", "line1\nline", 10)] - [InlineData (" ~  s  gui.cs   master ↑10", " ~  s  ", 10)] // Unicode - [InlineData ("Ð ÑÐ", "Ð ÑÐ", 5)] // should fit - [InlineData ("Ð ÑÐ", "Ð ÑÐ", 4)] // should fit - [InlineData ("Ð ÑÐ", "Ð Ñ", 3)] // Should not fit - public void ClipAndJustify_Valid_Justified (string text, string justifiedText, int maxWidth) - { - var align = TextAlignment.Justified; - var textDirection = TextDirection.LeftRight_TopBottom; - var tabWidth = 1; - - Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth)); - var expectedClippedWidth = Math.Min (justifiedText.GetRuneCount (), maxWidth); - Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth)); - Assert.True (justifiedText.GetRuneCount () <= maxWidth); - Assert.True (justifiedText.GetColumns () <= maxWidth); - Assert.Equal (expectedClippedWidth, justifiedText.GetRuneCount ()); - Assert.Equal (expectedClippedWidth, justifiedText.ToRuneList ().Sum (r => Math.Max (r.GetColumns (), 1))); - Assert.True (expectedClippedWidth <= maxWidth); - Assert.Equal (StringExtensions.ToString (justifiedText.ToRunes () [0..expectedClippedWidth]), justifiedText); - - // see Justify_ tests below - } + [Theory] + [InlineData ("test", "", 0)] + [InlineData ("test", "te", 2)] + [InlineData ("test", "test", int.MaxValue)] + [InlineData ("A sentence has words.", "A sentence has words.", 22)] // should fit + [InlineData ("A sentence has words.", "A sentence has words.", 21)] // should fit + [InlineData ("A sentence has words.", "A sentence has words.", int.MaxValue)] // should fit + [InlineData ("A sentence has words.", "A sentence has words", 20)] // Should not fit + [InlineData ("A sentence has words.", "A sentence", 10)] // Should not fit + [InlineData ("A\tsentence\thas\twords.", "A sentence has words.", int.MaxValue)] + [InlineData ("A\tsentence\thas\twords.", "A sentence", 10)] + [InlineData ("line1\nline2\nline3long!", "line1\nline2\nline3long!", int.MaxValue)] + [InlineData ("line1\nline2\nline3long!", "line1\nline", 10)] + [InlineData (" ~  s  gui.cs   master ↑10", " ~  s  ", 10)] // Unicode + [InlineData ("Ð ÑÐ", "Ð ÑÐ", 5)] // should fit + [InlineData ("Ð ÑÐ", "Ð ÑÐ", 4)] // should fit + [InlineData ("Ð ÑÐ", "Ð Ñ", 3)] // Should not fit + public void ClipAndJustify_Valid_Left (string text, string justifiedText, int maxWidth) + { + var align = TextAlignment.Left; + var textDirection = TextDirection.LeftRight_BottomTop; + var tabWidth = 1; + + Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth)); + var expectedClippedWidth = Math.Min (justifiedText.GetRuneCount (), maxWidth); + Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth)); + Assert.True (justifiedText.GetRuneCount () <= maxWidth); + Assert.True (justifiedText.GetColumns () <= maxWidth); + Assert.Equal (expectedClippedWidth, justifiedText.GetRuneCount ()); + Assert.Equal (expectedClippedWidth, justifiedText.ToRuneList ().Sum (r => Math.Max (r.GetColumns (), 1))); + Assert.True (expectedClippedWidth <= maxWidth); + Assert.Equal (StringExtensions.ToString (justifiedText.ToRunes () [..expectedClippedWidth]), justifiedText); + } - [Theory] - [InlineData ("")] - [InlineData (null)] - [InlineData ("test")] - public void Justify_Invalid (string text) - { - Assert.Equal (text, TextFormatter.Justify (text, 0)); - Assert.Equal (text, TextFormatter.Justify (text, 0)); - Assert.Throws (() => TextFormatter.Justify (text, -1)); - } + [Theory] + [InlineData ("test", "", 0)] + [InlineData ("test", "te", 2)] + [InlineData ("test", "test", int.MaxValue)] + [InlineData ("A sentence has words.", "A sentence has words.", 22)] // should fit + [InlineData ("A sentence has words.", "A sentence has words.", 21)] // should fit + [InlineData ("A sentence has words.", "A sentence has words.", int.MaxValue)] // should fit + [InlineData ("A sentence has words.", "A sentence has words", 20)] // Should not fit + [InlineData ("A sentence has words.", "A sentence", 10)] // Should not fit + [InlineData ("A\tsentence\thas\twords.", "A sentence has words.", int.MaxValue)] + [InlineData ("A\tsentence\thas\twords.", "A sentence", 10)] + [InlineData ("line1\nline2\nline3long!", "line1\nline2\nline3long!", int.MaxValue)] + [InlineData ("line1\nline2\nline3long!", "line1\nline", 10)] + [InlineData (" ~  s  gui.cs   master ↑10", " ~  s  ", 10)] // Unicode + [InlineData ("Ð ÑÐ", "Ð ÑÐ", 5)] // should fit + [InlineData ("Ð ÑÐ", "Ð ÑÐ", 4)] // should fit + [InlineData ("Ð ÑÐ", "Ð Ñ", 3)] // Should not fit + public void ClipAndJustify_Valid_Right (string text, string justifiedText, int maxWidth) + { + var align = TextAlignment.Right; + var textDirection = TextDirection.LeftRight_BottomTop; + var tabWidth = 1; + + Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth)); + var expectedClippedWidth = Math.Min (justifiedText.GetRuneCount (), maxWidth); + Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth)); + Assert.True (justifiedText.GetRuneCount () <= maxWidth); + Assert.True (justifiedText.GetColumns () <= maxWidth); + Assert.Equal (expectedClippedWidth, justifiedText.GetRuneCount ()); + Assert.Equal (expectedClippedWidth, justifiedText.ToRuneList ().Sum (r => Math.Max (r.GetColumns (), 1))); + Assert.True (expectedClippedWidth <= maxWidth); + Assert.Equal (StringExtensions.ToString (justifiedText.ToRunes () [..expectedClippedWidth]), justifiedText); + } - [Theory] - [InlineData ("word")] // Even # of chars - [InlineData ("word.")] // Odd # of chars - [InlineData ("пÑивеÑ")] // Unicode (even #) - [InlineData ("пÑивеÑ.")] // Unicode (odd # of chars) - public void Justify_SingleWord (string text) - { - var justifiedText = text; - char fillChar = '+'; - - int width = text.GetRuneCount (); - Assert.Equal (justifiedText, TextFormatter.Justify (text, width, fillChar)); - width = text.GetRuneCount () + 1; - Assert.Equal (justifiedText, TextFormatter.Justify (text, width, fillChar)); - width = text.GetRuneCount () + 2; - Assert.Equal (justifiedText, TextFormatter.Justify (text, width, fillChar)); - width = text.GetRuneCount () + 10; - Assert.Equal (justifiedText, TextFormatter.Justify (text, width, fillChar)); - width = text.GetRuneCount () + 11; - Assert.Equal (justifiedText, TextFormatter.Justify (text, width, fillChar)); - } + [Theory] + [InlineData ("test", "", 0)] + [InlineData ("test", "te", 2)] + [InlineData ("test", "test", int.MaxValue)] + [InlineData ("A sentence has words.", "A sentence has words.", 22)] // should fit + [InlineData ("A sentence has words.", "A sentence has words.", 21)] // should fit + [InlineData ("A sentence has words.", "A sentence has words.", int.MaxValue)] // should fit + [InlineData ("A sentence has words.", "A sentence has words", 20)] // Should not fit + [InlineData ("A sentence has words.", "A sentence", 10)] // Should not fit + [InlineData ("A\tsentence\thas\twords.", "A sentence has words.", int.MaxValue)] + [InlineData ("A\tsentence\thas\twords.", "A sentence", 10)] + [InlineData ("line1\nline2\nline3long!", "line1\nline2\nline3long!", int.MaxValue)] + [InlineData ("line1\nline2\nline3long!", "line1\nline", 10)] + [InlineData (" ~  s  gui.cs   master ↑10", " ~  s  ", 10)] // Unicode + [InlineData ("Ð ÑÐ", "Ð ÑÐ", 5)] // should fit + [InlineData ("Ð ÑÐ", "Ð ÑÐ", 4)] // should fit + [InlineData ("Ð ÑÐ", "Ð Ñ", 3)] // Should not fit + public void ClipAndJustify_Valid_Centered (string text, string justifiedText, int maxWidth) + { + var align = TextAlignment.Centered; + var textDirection = TextDirection.LeftRight_TopBottom; + var tabWidth = 1; + + Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth)); + var expectedClippedWidth = Math.Min (justifiedText.GetRuneCount (), maxWidth); + Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth)); + Assert.True (justifiedText.GetRuneCount () <= maxWidth); + Assert.True (justifiedText.GetColumns () <= maxWidth); + Assert.Equal (expectedClippedWidth, justifiedText.GetRuneCount ()); + Assert.Equal (expectedClippedWidth, justifiedText.ToRuneList ().Sum (r => Math.Max (r.GetColumns (), 1))); + Assert.True (expectedClippedWidth <= maxWidth); + Assert.Equal (StringExtensions.ToString (justifiedText.ToRunes () [..expectedClippedWidth]), justifiedText); + } - [Theory] - // Even # of spaces - // 0123456789 - [InlineData ("012 456 89", "012 456 89", 10, 0, "+", true)] - [InlineData ("012 456 89", "012++456+89", 11, 1)] - [InlineData ("012 456 89", "012 456 89", 12, 2, "++", true)] - [InlineData ("012 456 89", "012+++456++89", 13, 3)] - [InlineData ("012 456 89", "012 456 89", 14, 4, "+++", true)] - [InlineData ("012 456 89", "012++++456+++89", 15, 5)] - [InlineData ("012 456 89", "012 456 89", 16, 6, "++++", true)] - [InlineData ("012 456 89", "012 456 89", 30, 20, "+++++++++++", true)] - [InlineData ("012 456 89", "012+++++++++++++456++++++++++++89", 33, 23)] - // Odd # of spaces - // 01234567890123 - [InlineData ("012 456 89 end", "012 456 89 end", 14, 0, "+", true)] - [InlineData ("012 456 89 end", "012++456+89+end", 15, 1)] - [InlineData ("012 456 89 end", "012++456++89+end", 16, 2)] - [InlineData ("012 456 89 end", "012 456 89 end", 17, 3, "++", true)] - [InlineData ("012 456 89 end", "012+++456++89++end", 18, 4)] - [InlineData ("012 456 89 end", "012+++456+++89++end", 19, 5)] - [InlineData ("012 456 89 end", "012 456 89 end", 20, 6, "+++", true)] - [InlineData ("012 456 89 end", "012++++++++456++++++++89+++++++end", 34, 20)] - [InlineData ("012 456 89 end", "012+++++++++456+++++++++89++++++++end", 37, 23)] - // Unicode - // Even # of chars - // 0123456789 - [InlineData ("пÑРвРÑ", "пÑРвРÑ", 10, 0, "+", true)] - [InlineData ("пÑРвРÑ", "пÑÐ++вÐ+Ñ", 11, 1)] - [InlineData ("пÑРвРÑ", "пÑРвРÑ", 12, 2, "++", true)] - [InlineData ("пÑРвРÑ", "пÑÐ+++вÐ++Ñ", 13, 3)] - [InlineData ("пÑРвРÑ", "пÑРвРÑ", 14, 4, "+++", true)] - [InlineData ("пÑРвРÑ", "пÑÐ++++вÐ+++Ñ", 15, 5)] - [InlineData ("пÑРвРÑ", "пÑРвРÑ", 16, 6, "++++", true)] - [InlineData ("пÑРвРÑ", "пÑРвРÑ", 30, 20, "+++++++++++", true)] - [InlineData ("пÑРвРÑ", "пÑÐ+++++++++++++вÐ++++++++++++Ñ", 33, 23)] - // Unicode - // Odd # of chars - // 0123456789 - [InlineData ("Ð ÑРвРÑ", "Ð ÑРвРÑ", 10, 0, "+", true)] - [InlineData ("Ð ÑРвРÑ", "Ð++ÑÐ+вÐ+Ñ", 11, 1)] - [InlineData ("Ð ÑРвРÑ", "Ð++ÑÐ++вÐ+Ñ", 12, 2)] - [InlineData ("Ð ÑРвРÑ", "Ð ÑРвРÑ", 13, 3, "++", true)] - [InlineData ("Ð ÑРвРÑ", "Ð+++ÑÐ++вÐ++Ñ", 14, 4)] - [InlineData ("Ð ÑРвРÑ", "Ð+++ÑÐ+++вÐ++Ñ", 15, 5)] - [InlineData ("Ð ÑРвРÑ", "Ð ÑРвРÑ", 16, 6, "+++", true)] - [InlineData ("Ð ÑРвРÑ", "Ð++++++++ÑÐ++++++++вÐ+++++++Ñ", 30, 20)] - [InlineData ("Ð ÑРвРÑ", "Ð+++++++++ÑÐ+++++++++вÐ++++++++Ñ", 33, 23)] - public void Justify_Sentence (string text, string justifiedText, int forceToWidth, int widthOffset, string replaceWith = null, bool replace = false) - { - char fillChar = '+'; - - Assert.Equal (forceToWidth, text.GetRuneCount () + widthOffset); - if (replace) { - justifiedText = text.Replace (" ", replaceWith); - } - Assert.Equal (justifiedText, TextFormatter.Justify (text, forceToWidth, fillChar)); - Assert.True (Math.Abs (forceToWidth - justifiedText.GetRuneCount ()) < text.Count (s => s == ' ')); - Assert.True (Math.Abs (forceToWidth - justifiedText.GetColumns ()) < text.Count (s => s == ' ')); - } + [Theory] + [InlineData ("test", "", 0)] + [InlineData ("test", "te", 2)] + [InlineData ("test", "test", int.MaxValue)] // This doesn't throw because it only create a word with length 1 + [InlineData ("A sentence has words.", "A sentence has words.", 22)] // should fit + [InlineData ("A sentence has words.", "A sentence has words.", 21)] // should fit + [InlineData ("A sentence has words.", + "A sentence has words.", + 500)] // should fit + [InlineData ("A sentence has words.", "A sentence has words", 20)] // Should not fit + [InlineData ("A sentence has words.", "A sentence", 10)] // Should not fit + // Now throw System.OutOfMemoryException. See https://stackoverflow.com/questions/20672920/maxcapacity-of-stringbuilder + //[InlineData ("A\tsentence\thas\twords.", "A sentence has words.", int.MaxValue)] + [InlineData ("A\tsentence\thas\twords.", "A sentence", 10)] + [InlineData ("line1\nline2\nline3long!", "line1\nline2\nline3long!", int.MaxValue)] // This doesn't throw because it only create a line with length 1 + [InlineData ("line1\nline2\nline3long!", "line1\nline", 10)] + [InlineData (" ~  s  gui.cs   master ↑10", " ~  s  ", 10)] // Unicode + [InlineData ("Ð ÑÐ", "Ð ÑÐ", 5)] // should fit + [InlineData ("Ð ÑÐ", "Ð ÑÐ", 4)] // should fit + [InlineData ("Ð ÑÐ", "Ð Ñ", 3)] // Should not fit + public void ClipAndJustify_Valid_Justified (string text, string justifiedText, int maxWidth) + { + var align = TextAlignment.Justified; + var textDirection = TextDirection.LeftRight_TopBottom; + var tabWidth = 1; + + Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth)); + var expectedClippedWidth = Math.Min (justifiedText.GetRuneCount (), maxWidth); + Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth)); + Assert.True (justifiedText.GetRuneCount () <= maxWidth); + Assert.True (justifiedText.GetColumns () <= maxWidth); + Assert.Equal (expectedClippedWidth, justifiedText.GetRuneCount ()); + Assert.Equal (expectedClippedWidth, justifiedText.ToRuneList ().Sum (r => Math.Max (r.GetColumns (), 1))); + Assert.True (expectedClippedWidth <= maxWidth); + Assert.Equal (StringExtensions.ToString (justifiedText.ToRunes () [..expectedClippedWidth]), justifiedText); + + // see Justify_ tests below + } - [Fact] - public void WordWrap_Invalid () - { - var text = string.Empty; - int width = 0; + [Theory] + [InlineData ("")] + [InlineData (null)] + [InlineData ("test")] + public void Justify_Invalid (string text) + { + Assert.Equal (text, TextFormatter.Justify (text, 0)); + Assert.Equal (text, TextFormatter.Justify (text, 0)); + Assert.Throws (() => TextFormatter.Justify (text, -1)); + } - Assert.Empty (TextFormatter.WordWrapText (null, width)); - Assert.Empty (TextFormatter.WordWrapText (text, width)); - Assert.Throws (() => TextFormatter.WordWrapText (text, -1)); - } + [Theory] + [InlineData ("word")] // Even # of chars + [InlineData ("word.")] // Odd # of chars + [InlineData ("пÑивеÑ")] // Unicode (even #) + [InlineData ("пÑивеÑ.")] // Unicode (odd # of chars) + public void Justify_SingleWord (string text) + { + var justifiedText = text; + var fillChar = '+'; + + var width = text.GetRuneCount (); + Assert.Equal (justifiedText, TextFormatter.Justify (text, width, fillChar)); + width = text.GetRuneCount () + 1; + Assert.Equal (justifiedText, TextFormatter.Justify (text, width, fillChar)); + width = text.GetRuneCount () + 2; + Assert.Equal (justifiedText, TextFormatter.Justify (text, width, fillChar)); + width = text.GetRuneCount () + 10; + Assert.Equal (justifiedText, TextFormatter.Justify (text, width, fillChar)); + width = text.GetRuneCount () + 11; + Assert.Equal (justifiedText, TextFormatter.Justify (text, width, fillChar)); + } - [Fact] - public void WordWrap_BigWidth () - { - List wrappedLines; + [Theory] + // Even # of spaces + // 0123456789 + [InlineData ("012 456 89", "012 456 89", 10, 0, "+", true)] + [InlineData ("012 456 89", "012++456+89", 11, 1)] + [InlineData ("012 456 89", "012 456 89", 12, 2, "++", true)] + [InlineData ("012 456 89", "012+++456++89", 13, 3)] + [InlineData ("012 456 89", "012 456 89", 14, 4, "+++", true)] + [InlineData ("012 456 89", "012++++456+++89", 15, 5)] + [InlineData ("012 456 89", "012 456 89", 16, 6, "++++", true)] + [InlineData ("012 456 89", "012 456 89", 30, 20, "+++++++++++", true)] + [InlineData ("012 456 89", "012+++++++++++++456++++++++++++89", 33, 23)] + // Odd # of spaces + // 01234567890123 + [InlineData ("012 456 89 end", "012 456 89 end", 14, 0, "+", true)] + [InlineData ("012 456 89 end", "012++456+89+end", 15, 1)] + [InlineData ("012 456 89 end", "012++456++89+end", 16, 2)] + [InlineData ("012 456 89 end", "012 456 89 end", 17, 3, "++", true)] + [InlineData ("012 456 89 end", "012+++456++89++end", 18, 4)] + [InlineData ("012 456 89 end", "012+++456+++89++end", 19, 5)] + [InlineData ("012 456 89 end", "012 456 89 end", 20, 6, "+++", true)] + [InlineData ("012 456 89 end", "012++++++++456++++++++89+++++++end", 34, 20)] + [InlineData ("012 456 89 end", "012+++++++++456+++++++++89++++++++end", 37, 23)] + // Unicode + // Even # of chars + // 0123456789 + [InlineData ("пÑРвРÑ", "пÑРвРÑ", 10, 0, "+", true)] + [InlineData ("пÑРвРÑ", "пÑÐ++вÐ+Ñ", 11, 1)] + [InlineData ("пÑРвРÑ", "пÑРвРÑ", 12, 2, "++", true)] + [InlineData ("пÑРвРÑ", "пÑÐ+++вÐ++Ñ", 13, 3)] + [InlineData ("пÑРвРÑ", "пÑРвРÑ", 14, 4, "+++", true)] + [InlineData ("пÑРвРÑ", "пÑÐ++++вÐ+++Ñ", 15, 5)] + [InlineData ("пÑРвРÑ", "пÑРвРÑ", 16, 6, "++++", true)] + [InlineData ("пÑРвРÑ", "пÑРвРÑ", 30, 20, "+++++++++++", true)] + [InlineData ("пÑРвРÑ", "пÑÐ+++++++++++++вÐ++++++++++++Ñ", 33, 23)] + // Unicode + // Odd # of chars + // 0123456789 + [InlineData ("Ð ÑРвРÑ", "Ð ÑРвРÑ", 10, 0, "+", true)] + [InlineData ("Ð ÑРвРÑ", "Ð++ÑÐ+вÐ+Ñ", 11, 1)] + [InlineData ("Ð ÑРвРÑ", "Ð++ÑÐ++вÐ+Ñ", 12, 2)] + [InlineData ("Ð ÑРвРÑ", "Ð ÑРвРÑ", 13, 3, "++", true)] + [InlineData ("Ð ÑРвРÑ", "Ð+++ÑÐ++вÐ++Ñ", 14, 4)] + [InlineData ("Ð ÑРвРÑ", "Ð+++ÑÐ+++вÐ++Ñ", 15, 5)] + [InlineData ("Ð ÑРвРÑ", "Ð ÑРвРÑ", 16, 6, "+++", true)] + [InlineData ("Ð ÑРвРÑ", "Ð++++++++ÑÐ++++++++вÐ+++++++Ñ", 30, 20)] + [InlineData ("Ð ÑРвРÑ", "Ð+++++++++ÑÐ+++++++++вÐ++++++++Ñ", 33, 23)] + public void Justify_Sentence (string text, string justifiedText, int forceToWidth, int widthOffset, string replaceWith = null, bool replace = false) + { + var fillChar = '+'; + + Assert.Equal (forceToWidth, text.GetRuneCount () + widthOffset); + if (replace) { + justifiedText = text.Replace (" ", replaceWith); + } + Assert.Equal (justifiedText, TextFormatter.Justify (text, forceToWidth, fillChar)); + Assert.True (Math.Abs (forceToWidth - justifiedText.GetRuneCount ()) < text.Count (s => s == ' ')); + Assert.True (Math.Abs (forceToWidth - justifiedText.GetColumns ()) < text.Count (s => s == ' ')); + } - var text = "Constantinople"; - wrappedLines = TextFormatter.WordWrapText (text, 100); - Assert.True (wrappedLines.Count == 1); - Assert.Equal ("Constantinople", wrappedLines [0]); - } + [Fact] + public void WordWrap_Invalid () + { + var text = string.Empty; + var width = 0; - [Theory] - [InlineData ("Constantinople", 14, 0, new string [] { "Constantinople" })] - [InlineData ("Constantinople", 12, -2, new string [] { "Constantinop", "le" })] - [InlineData ("Constantinople", 9, -5, new string [] { "Constanti", "nople" })] - [InlineData ("Constantinople", 7, -7, new string [] { "Constan", "tinople" })] - [InlineData ("Constantinople", 5, -9, new string [] { "Const", "antin", "ople" })] - [InlineData ("Constantinople", 4, -10, new string [] { "Cons", "tant", "inop", "le" })] - [InlineData ("Constantinople", 1, -13, new string [] { "C", "o", "n", "s", "t", "a", "n", "t", "i", "n", "o", "p", "l", "e" })] - public void WordWrap_SingleWordLine (string text, int maxWidth, int widthOffset, IEnumerable resultLines) - { - List wrappedLines; - - Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); - var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); - wrappedLines = TextFormatter.WordWrapText (text, maxWidth); - Assert.Equal (wrappedLines.Count, resultLines.Count ()); - Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0)); - Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0)); - Assert.Equal (resultLines, wrappedLines); - } + Assert.Empty (TextFormatter.WordWrapText (null, width)); + Assert.Empty (TextFormatter.WordWrapText (text, width)); + Assert.Throws (() => TextFormatter.WordWrapText (text, -1)); + } - [Theory] - [InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 51, 0, new string [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ" })] - [InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 50, -1, new string [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ" })] - [InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 46, -5, new string [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮ", "ฯะัาำ" })] - [InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 26, -25, new string [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบ", "ปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ" })] - [InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 17, -34, new string [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑ", "ฒณดตถทธนบปผฝพฟภมย", "รฤลฦวศษสหฬอฮฯะัาำ" })] - [InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 13, -38, new string [] { "กขฃคฅฆงจฉชซฌญ", "ฎฏฐฑฒณดตถทธนบ", "ปผฝพฟภมยรฤลฦว", "ศษสหฬอฮฯะัาำ" })] - [InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 1, -50, new string [] { "ก", "ข", "ฃ", "ค", "ฅ", "ฆ", "ง", "จ", "ฉ", "ช", "ซ", "ฌ", "ญ", "ฎ", "ฏ", "ฐ", "ฑ", "ฒ", "ณ", "ด", "ต", "ถ", "ท", "ธ", "น", "บ", "ป", "ผ", "ฝ", "พ", "ฟ", "ภ", "ม", "ย", "ร", "ฤ", "ล", "ฦ", "ว", "ศ", "ษ", "ส", "ห", "ฬ", "อ", "ฮ", "ฯ", "ะั", "า", "ำ" })] - public void WordWrap_Unicode_SingleWordLine (string text, int maxWidth, int widthOffset, IEnumerable resultLines) - { - List wrappedLines; - - var zeroWidth = text.EnumerateRunes ().Where (r => r.GetColumns () == 0); - Assert.Single (zeroWidth); - Assert.Equal ('ั', zeroWidth.ElementAt (0).Value); - Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); - var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); - wrappedLines = TextFormatter.WordWrapText (text, maxWidth); - Assert.Equal (wrappedLines.Count, resultLines.Count ()); - Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount () + zeroWidth.Count () - 1 + widthOffset) : 0)); - Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0)); - Assert.Equal (resultLines, wrappedLines); - } + [Fact] + public void WordWrap_BigWidth () + { + List wrappedLines; - [Theory] - [InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 19, 0, new string [] { "This\u00A0is\u00A0a\u00A0sentence." })] - [InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 18, -1, new string [] { "This\u00A0is\u00A0a\u00A0sentence", "." })] - [InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 17, -2, new string [] { "This\u00A0is\u00A0a\u00A0sentenc", "e." })] - [InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 14, -5, new string [] { "This\u00A0is\u00A0a\u00A0sent", "ence." })] - [InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 10, -9, new string [] { "This\u00A0is\u00A0a\u00A0", "sentence." })] - [InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 7, -12, new string [] { "This\u00A0is", "\u00A0a\u00A0sent", "ence." })] - [InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 5, -14, new string [] { "This\u00A0", "is\u00A0a\u00A0", "sente", "nce." })] - [InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 1, -18, new string [] { "T", "h", "i", "s", "\u00A0", "i", "s", "\u00A0", "a", "\u00A0", "s", "e", "n", "t", "e", "n", "c", "e", "." })] - public void WordWrap_Unicode_LineWithNonBreakingSpace (string text, int maxWidth, int widthOffset, IEnumerable resultLines) - { - List wrappedLines; - - Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); - var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); - wrappedLines = TextFormatter.WordWrapText (text, maxWidth); - Assert.Equal (wrappedLines.Count, resultLines.Count ()); - Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0)); - Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0)); - Assert.Equal (resultLines, wrappedLines); - } + var text = "Constantinople"; + wrappedLines = TextFormatter.WordWrapText (text, 100); + Assert.True (wrappedLines.Count == 1); + Assert.Equal ("Constantinople", wrappedLines [0]); + } - [Theory] - [InlineData ("This\u00A0is\n\u00A0a\u00A0sentence.", 20, 0, new string [] { "This\u00A0is\u00A0a\u00A0sentence." })] - [InlineData ("This\u00A0is\n\u00A0a\u00A0sentence.", 19, -1, new string [] { "This\u00A0is\u00A0a\u00A0sentence." })] - [InlineData ("\u00A0\u00A0\u00A0\u00A0\u00A0test\u00A0sentence.", 19, 0, new string [] { "\u00A0\u00A0\u00A0\u00A0\u00A0test\u00A0sentence." })] - public void WordWrap_Unicode_2LinesWithNonBreakingSpace (string text, int maxWidth, int widthOffset, IEnumerable resultLines) - { - List wrappedLines; - - Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); - var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); - wrappedLines = TextFormatter.WordWrapText (text, maxWidth); - Assert.Equal (wrappedLines.Count, resultLines.Count ()); - Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0)); - Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0)); - Assert.Equal (resultLines, wrappedLines); - } + [Theory] + [InlineData ("Constantinople", 14, 0, new [] { "Constantinople" })] + [InlineData ("Constantinople", 12, -2, new [] { "Constantinop", "le" })] + [InlineData ("Constantinople", 9, -5, new [] { "Constanti", "nople" })] + [InlineData ("Constantinople", 7, -7, new [] { "Constan", "tinople" })] + [InlineData ("Constantinople", 5, -9, new [] { "Const", "antin", "ople" })] + [InlineData ("Constantinople", 4, -10, new [] { "Cons", "tant", "inop", "le" })] + [InlineData ("Constantinople", 1, -13, new [] { "C", "o", "n", "s", "t", "a", "n", "t", "i", "n", "o", "p", "l", "e" })] + public void WordWrap_SingleWordLine (string text, int maxWidth, int widthOffset, IEnumerable resultLines) + { + List wrappedLines; + + Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); + var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); + wrappedLines = TextFormatter.WordWrapText (text, maxWidth); + Assert.Equal (wrappedLines.Count, resultLines.Count ()); + Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0)); + Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0)); + Assert.Equal (resultLines, wrappedLines); + } - [Theory] - [InlineData ("A sentence has words.", 21, 0, new string [] { "A sentence has words." })] - [InlineData ("A sentence has words.", 20, -1, new string [] { "A sentence has", "words." })] - [InlineData ("A sentence has words.", 15, -6, new string [] { "A sentence has", "words." })] - [InlineData ("A sentence has words.", 14, -7, new string [] { "A sentence has", "words." })] - [InlineData ("A sentence has words.", 13, -8, new string [] { "A sentence", "has words." })] - // Unicode - [InlineData ("A Unicode sentence (пÑивеÑ) has words.", 42, 0, new string [] { "A Unicode sentence (пÑивеÑ) has words." })] - [InlineData ("A Unicode sentence (пÑивеÑ) has words.", 41, -1, new string [] { "A Unicode sentence (пÑивеÑ) has", "words." })] - [InlineData ("A Unicode sentence (пÑивеÑ) has words.", 36, -6, new string [] { "A Unicode sentence (пÑивеÑ) has", "words." })] - [InlineData ("A Unicode sentence (пÑивеÑ) has words.", 35, -7, new string [] { "A Unicode sentence (пÑивеÑ) has", "words." })] - [InlineData ("A Unicode sentence (пÑивеÑ) has words.", 34, -8, new string [] { "A Unicode sentence (пÑивеÑ)", "has words." })] - [InlineData ("A Unicode sentence (пÑивеÑ) has words.", 25, -17, new string [] { "A Unicode sentence", "(пÑивеÑ) has words." })] - public void WordWrap_NoNewLines_Default (string text, int maxWidth, int widthOffset, IEnumerable resultLines) - { - // Calls WordWrapText (text, width) and thus preserveTrailingSpaces defaults to false - List wrappedLines; - - Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); - var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); - wrappedLines = TextFormatter.WordWrapText (text, maxWidth); - Assert.Equal (wrappedLines.Count, resultLines.Count ()); - Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0)); - Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0)); - Assert.Equal (resultLines, wrappedLines); - } + [Theory] + [InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 51, 0, new [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ" })] + [InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 50, -1, new [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ" })] + [InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 46, -5, new [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮ", "ฯะัาำ" })] + [InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 26, -25, new [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบ", "ปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ" })] + [InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 17, -34, new [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑ", "ฒณดตถทธนบปผฝพฟภมย", "รฤลฦวศษสหฬอฮฯะัาำ" })] + [InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 13, -38, new [] { "กขฃคฅฆงจฉชซฌญ", "ฎฏฐฑฒณดตถทธนบ", "ปผฝพฟภมยรฤลฦว", "ศษสหฬอฮฯะัาำ" })] + [InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 1, -50, + new [] { + "ก", "ข", "ฃ", "ค", "ฅ", "ฆ", "ง", "จ", "ฉ", "ช", "ซ", "ฌ", "ญ", "ฎ", "ฏ", "ฐ", "ฑ", "ฒ", "ณ", "ด", "ต", "ถ", "ท", "ธ", "น", "บ", "ป", "ผ", "ฝ", "พ", "ฟ", "ภ", "ม", "ย", "ร", + "ฤ", "ล", "ฦ", "ว", "ศ", "ษ", "ส", "ห", "ฬ", "อ", "ฮ", "ฯ", "ะั", "า", "ำ" + })] + public void WordWrap_Unicode_SingleWordLine (string text, int maxWidth, int widthOffset, IEnumerable resultLines) + { + List wrappedLines; + + var zeroWidth = text.EnumerateRunes ().Where (r => r.GetColumns () == 0); + Assert.Single (zeroWidth); + Assert.Equal ('ั', zeroWidth.ElementAt (0).Value); + Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); + var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); + wrappedLines = TextFormatter.WordWrapText (text, maxWidth); + Assert.Equal (wrappedLines.Count, resultLines.Count ()); + Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount () + zeroWidth.Count () - 1 + widthOffset) : 0)); + Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0)); + Assert.Equal (resultLines, wrappedLines); + } - /// - /// WordWrap strips CRLF - /// - [Theory] - [InlineData ("A sentence has words.\nA paragraph has lines.", 44, 0, new string [] { "A sentence has words.A paragraph has lines." })] - [InlineData ("A sentence has words.\nA paragraph has lines.", 43, -1, new string [] { "A sentence has words.A paragraph has lines." })] - [InlineData ("A sentence has words.\nA paragraph has lines.", 38, -6, new string [] { "A sentence has words.A paragraph has", "lines." })] - [InlineData ("A sentence has words.\nA paragraph has lines.", 34, -10, new string [] { "A sentence has words.A paragraph", "has lines." })] - [InlineData ("A sentence has words.\nA paragraph has lines.", 27, -17, new string [] { "A sentence has words.A", "paragraph has lines." })] - // Unicode - [InlineData ("A Unicode sentence (пÑивеÑ) has words.\nA Unicode Пункт has Линии.", 69, 0, new string [] { "A Unicode sentence (пÑивеÑ) has words.A Unicode Пункт has Линии." })] - [InlineData ("A Unicode sentence (пÑивеÑ) has words.\nA Unicode Пункт has Линии.", 68, -1, new string [] { "A Unicode sentence (пÑивеÑ) has words.A Unicode Пункт has Линии." })] - [InlineData ("A Unicode sentence (пÑивеÑ) has words.\nA Unicode Пункт has Линии.", 63, -6, new string [] { "A Unicode sentence (пÑивеÑ) has words.A Unicode Пункт has", "Линии." })] - [InlineData ("A Unicode sentence (пÑивеÑ) has words.\nA Unicode Пункт has Линии.", 59, -10, new string [] { "A Unicode sentence (пÑивеÑ) has words.A Unicode Пункт", "has Линии." })] - [InlineData ("A Unicode sentence (пÑивеÑ) has words.\nA Unicode Пункт has Линии.", 52, -17, new string [] { "A Unicode sentence (пÑивеÑ) has words.A Unicode", "Пункт has Линии." })] - public void WordWrap_WithNewLines (string text, int maxWidth, int widthOffset, IEnumerable resultLines) - { - List wrappedLines; - - Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); - var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); - wrappedLines = TextFormatter.WordWrapText (text, maxWidth); - Assert.Equal (wrappedLines.Count, resultLines.Count ()); - Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0)); - Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0)); - Assert.Equal (resultLines, wrappedLines); - } + [Theory] + [InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 19, 0, new [] { "This\u00A0is\u00A0a\u00A0sentence." })] + [InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 18, -1, new [] { "This\u00A0is\u00A0a\u00A0sentence", "." })] + [InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 17, -2, new [] { "This\u00A0is\u00A0a\u00A0sentenc", "e." })] + [InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 14, -5, new [] { "This\u00A0is\u00A0a\u00A0sent", "ence." })] + [InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 10, -9, new [] { "This\u00A0is\u00A0a\u00A0", "sentence." })] + [InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 7, -12, new [] { "This\u00A0is", "\u00A0a\u00A0sent", "ence." })] + [InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 5, -14, new [] { "This\u00A0", "is\u00A0a\u00A0", "sente", "nce." })] + [InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 1, -18, new [] { "T", "h", "i", "s", "\u00A0", "i", "s", "\u00A0", "a", "\u00A0", "s", "e", "n", "t", "e", "n", "c", "e", "." })] + public void WordWrap_Unicode_LineWithNonBreakingSpace (string text, int maxWidth, int widthOffset, IEnumerable resultLines) + { + List wrappedLines; + + Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); + var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); + wrappedLines = TextFormatter.WordWrapText (text, maxWidth); + Assert.Equal (wrappedLines.Count, resultLines.Count ()); + Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0)); + Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0)); + Assert.Equal (resultLines, wrappedLines); + } - [Theory] - [InlineData ("A sentence has words.", 3, -18, new string [] { "A", "sen", "ten", "ce", "has", "wor", "ds." })] - [InlineData ("A sentence has words.", 2, -19, new string [] { "A", "se", "nt", "en", "ce", "ha", "s", "wo", "rd", "s." })] - [InlineData ("A sentence has words.", 1, -20, new string [] { "A", "s", "e", "n", "t", "e", "n", "c", "e", "h", "a", "s", "w", "o", "r", "d", "s", "." })] - public void WordWrap_Narrow_Default (string text, int maxWidth, int widthOffset, IEnumerable resultLines) - { - // Calls WordWrapText (text, width) and thus preserveTrailingSpaces defaults to false - List wrappedLines; - - Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); - var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); - wrappedLines = TextFormatter.WordWrapText (text, maxWidth); - Assert.Equal (wrappedLines.Count, resultLines.Count ()); - Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0)); - Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0)); - Assert.Equal (resultLines, wrappedLines); - } + [Theory] + [InlineData ("This\u00A0is\n\u00A0a\u00A0sentence.", 20, 0, new [] { "This\u00A0is\u00A0a\u00A0sentence." })] + [InlineData ("This\u00A0is\n\u00A0a\u00A0sentence.", 19, -1, new [] { "This\u00A0is\u00A0a\u00A0sentence." })] + [InlineData ("\u00A0\u00A0\u00A0\u00A0\u00A0test\u00A0sentence.", 19, 0, new [] { "\u00A0\u00A0\u00A0\u00A0\u00A0test\u00A0sentence." })] + public void WordWrap_Unicode_2LinesWithNonBreakingSpace (string text, int maxWidth, int widthOffset, IEnumerable resultLines) + { + List wrappedLines; + + Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); + var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); + wrappedLines = TextFormatter.WordWrapText (text, maxWidth); + Assert.Equal (wrappedLines.Count, resultLines.Count ()); + Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0)); + Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0)); + Assert.Equal (resultLines, wrappedLines); + } - [Theory] - [InlineData ("A sentence has words.", 14, -7, new string [] { "A sentence ", "has words." })] - [InlineData ("A sentence has words.", 8, -13, new string [] { "A ", "sentence", " has ", "words." })] - [InlineData ("A sentence has words.", 6, -15, new string [] { "A ", "senten", "ce ", "has ", "words." })] - [InlineData ("A sentence has words.", 3, -18, new string [] { "A ", "sen", "ten", "ce ", "has", " ", "wor", "ds." })] - [InlineData ("A sentence has words.", 2, -19, new string [] { "A ", "se", "nt", "en", "ce", " ", "ha", "s ", "wo", "rd", "s." })] - [InlineData ("A sentence has words.", 1, -20, new string [] { "A", " ", "s", "e", "n", "t", "e", "n", "c", "e", " ", "h", "a", "s", " ", "w", "o", "r", "d", "s", "." })] - public void WordWrap_PreserveTrailingSpaces_True (string text, int maxWidth, int widthOffset, IEnumerable resultLines) - { - List wrappedLines; - - Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); - var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); - wrappedLines = TextFormatter.WordWrapText (text, maxWidth, preserveTrailingSpaces: true); - Assert.Equal (wrappedLines.Count, resultLines.Count ()); - Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0)); - Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0)); - Assert.Equal (resultLines, wrappedLines); - } + [Theory] + [InlineData ("A sentence has words.", 21, 0, new [] { "A sentence has words." })] + [InlineData ("A sentence has words.", 20, -1, new [] { "A sentence has", "words." })] + [InlineData ("A sentence has words.", 15, -6, new [] { "A sentence has", "words." })] + [InlineData ("A sentence has words.", 14, -7, new [] { "A sentence has", "words." })] + [InlineData ("A sentence has words.", 13, -8, new [] { "A sentence", "has words." })] + // Unicode + [InlineData ("A Unicode sentence (пÑивеÑ) has words.", 42, 0, new [] { "A Unicode sentence (пÑивеÑ) has words." })] + [InlineData ("A Unicode sentence (пÑивеÑ) has words.", 41, -1, new [] { "A Unicode sentence (пÑивеÑ) has", "words." })] + [InlineData ("A Unicode sentence (пÑивеÑ) has words.", 36, -6, new [] { "A Unicode sentence (пÑивеÑ) has", "words." })] + [InlineData ("A Unicode sentence (пÑивеÑ) has words.", 35, -7, new [] { "A Unicode sentence (пÑивеÑ) has", "words." })] + [InlineData ("A Unicode sentence (пÑивеÑ) has words.", 34, -8, new [] { "A Unicode sentence (пÑивеÑ)", "has words." })] + [InlineData ("A Unicode sentence (пÑивеÑ) has words.", 25, -17, new [] { "A Unicode sentence", "(пÑивеÑ) has words." })] + public void WordWrap_NoNewLines_Default (string text, int maxWidth, int widthOffset, IEnumerable resultLines) + { + // Calls WordWrapText (text, width) and thus preserveTrailingSpaces defaults to false + List wrappedLines; + + Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); + var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); + wrappedLines = TextFormatter.WordWrapText (text, maxWidth); + Assert.Equal (wrappedLines.Count, resultLines.Count ()); + Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0)); + Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0)); + Assert.Equal (resultLines, wrappedLines); + } - [Theory] - [InlineData ("文に は言葉 があり ます。", 14, 0, new string [] { "文に は言葉 ", "があり ます。" })] - [InlineData ("文に は言葉 があり ます。", 3, -11, new string [] { "文", "に ", "は", "言", "葉 ", "が", "あ", "り ", "ま", "す", "。" })] - [InlineData ("文に は言葉 があり ます。", 2, -12, new string [] { "文", "に", " ", "は", "言", "葉", " ", "が", "あ", "り", " ", "ま", "す", "。" })] - [InlineData ("文に は言葉 があり ます。", 1, -13, new string [] { })] - public void WordWrap_PreserveTrailingSpaces_True_Wide_Runes (string text, int maxWidth, int widthOffset, IEnumerable resultLines) - { - List wrappedLines; - - Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); - var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); - wrappedLines = TextFormatter.WordWrapText (text, maxWidth, preserveTrailingSpaces: true); - Assert.Equal (wrappedLines.Count, resultLines.Count ()); - Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0)); - Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0)); - Assert.Equal (resultLines, wrappedLines); - } + /// + /// WordWrap strips CRLF + /// + [Theory] + [InlineData ("A sentence has words.\nA paragraph has lines.", 44, 0, new [] { "A sentence has words.A paragraph has lines." })] + [InlineData ("A sentence has words.\nA paragraph has lines.", 43, -1, new [] { "A sentence has words.A paragraph has lines." })] + [InlineData ("A sentence has words.\nA paragraph has lines.", 38, -6, new [] { "A sentence has words.A paragraph has", "lines." })] + [InlineData ("A sentence has words.\nA paragraph has lines.", 34, -10, new [] { "A sentence has words.A paragraph", "has lines." })] + [InlineData ("A sentence has words.\nA paragraph has lines.", 27, -17, new [] { "A sentence has words.A", "paragraph has lines." })] + // Unicode + [InlineData ("A Unicode sentence (пÑивеÑ) has words.\nA Unicode Пункт has Линии.", 69, 0, new [] { "A Unicode sentence (пÑивеÑ) has words.A Unicode Пункт has Линии." })] + [InlineData ("A Unicode sentence (пÑивеÑ) has words.\nA Unicode Пункт has Линии.", 68, -1, new [] { "A Unicode sentence (пÑивеÑ) has words.A Unicode Пункт has Линии." })] + [InlineData ("A Unicode sentence (пÑивеÑ) has words.\nA Unicode Пункт has Линии.", 63, -6, new [] { "A Unicode sentence (пÑивеÑ) has words.A Unicode Пункт has", "Линии." })] + [InlineData ("A Unicode sentence (пÑивеÑ) has words.\nA Unicode Пункт has Линии.", 59, -10, new [] { "A Unicode sentence (пÑивеÑ) has words.A Unicode Пункт", "has Линии." })] + [InlineData ("A Unicode sentence (пÑивеÑ) has words.\nA Unicode Пункт has Линии.", 52, -17, new [] { "A Unicode sentence (пÑивеÑ) has words.A Unicode", "Пункт has Линии." })] + public void WordWrap_WithNewLines (string text, int maxWidth, int widthOffset, IEnumerable resultLines) + { + List wrappedLines; + + Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); + var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); + wrappedLines = TextFormatter.WordWrapText (text, maxWidth); + Assert.Equal (wrappedLines.Count, resultLines.Count ()); + Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0)); + Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0)); + Assert.Equal (resultLines, wrappedLines); + } - [Theory] - [InlineData ("文に は言葉 があり ます。", 14, 0, new string [] { "文に は言葉", "があり ます。" })] - [InlineData ("文に は言葉 があり ます。", 3, -11, new string [] { "文", "に", "は", "言", "葉", "が", "あ", "り", "ま", "す", "。" })] - [InlineData ("文に は言葉 があり ます。", 2, -12, new string [] { "文", "に", "は", "言", "葉", "が", "あ", "り", "ま", "す", "。" })] - [InlineData ("文に は言葉 があり ます。", 1, -13, new string [] { " ", " ", " " })] // Just Spaces; should result in a single space for each line - public void WordWrap_PreserveTrailingSpaces_False_Wide_Runes (string text, int maxWidth, int widthOffset, IEnumerable resultLines) - { - List wrappedLines; - - Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); - var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); - wrappedLines = TextFormatter.WordWrapText (text, maxWidth); - Assert.Equal (wrappedLines.Count, resultLines.Count ()); - Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0)); - Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0)); - Assert.Equal (resultLines, wrappedLines); - } + [Theory] + [InlineData ("A sentence has words.", 3, -18, new [] { "A", "sen", "ten", "ce", "has", "wor", "ds." })] + [InlineData ("A sentence has words.", 2, -19, new [] { "A", "se", "nt", "en", "ce", "ha", "s", "wo", "rd", "s." })] + [InlineData ("A sentence has words.", 1, -20, new [] { "A", "s", "e", "n", "t", "e", "n", "c", "e", "h", "a", "s", "w", "o", "r", "d", "s", "." })] + public void WordWrap_Narrow_Default (string text, int maxWidth, int widthOffset, IEnumerable resultLines) + { + // Calls WordWrapText (text, width) and thus preserveTrailingSpaces defaults to false + List wrappedLines; + + Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); + var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); + wrappedLines = TextFormatter.WordWrapText (text, maxWidth); + Assert.Equal (wrappedLines.Count, resultLines.Count ()); + Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0)); + Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0)); + Assert.Equal (resultLines, wrappedLines); + } - [Theory] - [InlineData ("A sentence has words. ", 3, new string [] { "A ", "sen", "ten", "ce ", "has", " ", "wor", "ds.", " " })] - [InlineData ("A sentence has words. ", 3, new string [] { "A ", " ", "sen", "ten", "ce ", " ", " ", " ", "has", " ", "wor", "ds.", " " })] - public void WordWrap_PreserveTrailingSpaces_True_With_Simple_Runes_Width_3 (string text, int width, IEnumerable resultLines) - { - var wrappedLines = TextFormatter.WordWrapText (text, width, preserveTrailingSpaces: true); - Assert.Equal (wrappedLines.Count, resultLines.Count ()); - Assert.Equal (resultLines, wrappedLines); - var breakLines = ""; - foreach (var line in wrappedLines) { - breakLines += $"{line}{Environment.NewLine}"; - } - var expected = string.Empty; - foreach (var line in resultLines) { - expected += $"{line}{Environment.NewLine}"; - } - Assert.Equal (expected, breakLines); - - // Double space Complex example - this is how VS 2022 does it - //text = "A sentence has words. "; - //breakLines = ""; - //wrappedLines = TextFormatter.WordWrapText (text, width, preserveTrailingSpaces: true); - //foreach (var line in wrappedLines) { - // breakLines += $"{line}{Environment.NewLine}"; - //} - //expected = "A " + Environment.NewLine + - // " se" + Environment.NewLine + - // " nt" + Environment.NewLine + - // " en" + Environment.NewLine + - // " ce" + Environment.NewLine + - // " " + Environment.NewLine + - // " " + Environment.NewLine + - // " " + Environment.NewLine + - // " ha" + Environment.NewLine + - // " s " + Environment.NewLine + - // " wo" + Environment.NewLine + - // " rd" + Environment.NewLine + - // " s." + Environment.NewLine; - //Assert.Equal (expected, breakLines); - } + [Theory] + [InlineData ("A sentence has words.", 14, -7, new [] { "A sentence ", "has words." })] + [InlineData ("A sentence has words.", 8, -13, new [] { "A ", "sentence", " has ", "words." })] + [InlineData ("A sentence has words.", 6, -15, new [] { "A ", "senten", "ce ", "has ", "words." })] + [InlineData ("A sentence has words.", 3, -18, new [] { "A ", "sen", "ten", "ce ", "has", " ", "wor", "ds." })] + [InlineData ("A sentence has words.", 2, -19, new [] { "A ", "se", "nt", "en", "ce", " ", "ha", "s ", "wo", "rd", "s." })] + [InlineData ("A sentence has words.", 1, -20, new [] { "A", " ", "s", "e", "n", "t", "e", "n", "c", "e", " ", "h", "a", "s", " ", "w", "o", "r", "d", "s", "." })] + public void WordWrap_PreserveTrailingSpaces_True (string text, int maxWidth, int widthOffset, IEnumerable resultLines) + { + List wrappedLines; + + Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); + var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); + wrappedLines = TextFormatter.WordWrapText (text, maxWidth, true); + Assert.Equal (wrappedLines.Count, resultLines.Count ()); + Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0)); + Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0)); + Assert.Equal (resultLines, wrappedLines); + } - [Theory] - [InlineData (null, 1, new string [] { })] // null input - [InlineData ("", 1, new string [] { })] // Empty input - [InlineData ("1 34", 1, new string [] { "1", "3", "4" })] // Single Spaces - [InlineData ("1", 1, new string [] { "1" })] // Short input - [InlineData ("12", 1, new string [] { "1", "2" })] - [InlineData ("123", 1, new string [] { "1", "2", "3" })] - [InlineData ("123456", 1, new string [] { "1", "2", "3", "4", "5", "6" })] // No spaces - [InlineData (" ", 1, new string [] { " " })] // Just Spaces; should result in a single space - [InlineData (" ", 1, new string [] { " " })] - [InlineData (" ", 1, new string [] { " ", " " })] - [InlineData (" ", 1, new string [] { " ", " " })] - [InlineData ("12 456", 1, new string [] { "1", "2", "4", "5", "6" })] // Single Spaces - [InlineData (" 2 456", 1, new string [] { " ", "2", "4", "5", "6" })] // Leading spaces should be preserved. - [InlineData (" 2 456 8", 1, new string [] { " ", "2", "4", "5", "6", "8" })] - [InlineData ("A sentence has words. ", 1, new string [] { "A", "s", "e", "n", "t", "e", "n", "c", "e", "h", "a", "s", "w", "o", "r", "d", "s", "." })] // Complex example - [InlineData ("12 567", 1, new string [] { "1", "2", " ", "5", "6", "7" })] // Double Spaces - [InlineData (" 3 567", 1, new string [] { " ", "3", "5", "6", "7" })] // Double Leading spaces should be preserved. - [InlineData (" 3 678 1", 1, new string [] { " ", "3", " ", "6", "7", "8", " ", "1" })] - [InlineData ("1 456", 1, new string [] { "1", " ", "4", "5", "6" })] - [InlineData ("A sentence has words. ", 1, new string [] { "A", " ", "s", "e", "n", "t", "e", "n", "c", "e", " ", "h", "a", "s", "w", "o", "r", "d", "s", ".", " " })] // Double space Complex example - public void WordWrap_PreserveTrailingSpaces_False_With_Simple_Runes_Width_1 (string text, int width, IEnumerable resultLines) - { - var wrappedLines = TextFormatter.WordWrapText (text, width, preserveTrailingSpaces: false); - Assert.Equal (wrappedLines.Count, resultLines.Count ()); - Assert.Equal (resultLines, wrappedLines); - var breakLines = ""; - foreach (var line in wrappedLines) { - breakLines += $"{line}{Environment.NewLine}"; - } - var expected = string.Empty; - foreach (var line in resultLines) { - expected += $"{line}{Environment.NewLine}"; - } - Assert.Equal (expected, breakLines); - } + [Theory] + [InlineData ("文に は言葉 があり ます。", 14, 0, new [] { "文に は言葉 ", "があり ます。" })] + [InlineData ("文に は言葉 があり ます。", 3, -11, new [] { "文", "に ", "は", "言", "葉 ", "が", "あ", "り ", "ま", "す", "。" })] + [InlineData ("文に は言葉 があり ます。", 2, -12, new [] { "文", "に", " ", "は", "言", "葉", " ", "が", "あ", "り", " ", "ま", "す", "。" })] + [InlineData ("文に は言葉 があり ます。", 1, -13, new string [] { })] + public void WordWrap_PreserveTrailingSpaces_True_Wide_Runes (string text, int maxWidth, int widthOffset, IEnumerable resultLines) + { + List wrappedLines; + + Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); + var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); + wrappedLines = TextFormatter.WordWrapText (text, maxWidth, true); + Assert.Equal (wrappedLines.Count, resultLines.Count ()); + Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0)); + Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0)); + Assert.Equal (resultLines, wrappedLines); + } - [Theory] - [InlineData (null, 3, new string [] { })] // null input - [InlineData ("", 3, new string [] { })] // Empty input - [InlineData ("1", 3, new string [] { "1" })] // Short input - [InlineData ("12", 3, new string [] { "12" })] - [InlineData ("123", 3, new string [] { "123" })] - [InlineData ("123456", 3, new string [] { "123", "456" })] // No spaces - [InlineData ("1234567", 3, new string [] { "123", "456", "7" })] // No spaces - [InlineData (" ", 3, new string [] { " " })] // Just Spaces; should result in a single space - [InlineData (" ", 3, new string [] { " " })] - [InlineData (" ", 3, new string [] { " " })] - [InlineData (" ", 3, new string [] { " " })] - [InlineData ("12 456", 3, new string [] { "12", "456" })] // Single Spaces - [InlineData (" 2 456", 3, new string [] { " 2", "456" })] // Leading spaces should be preserved. - [InlineData (" 2 456 8", 3, new string [] { " 2", "456", "8" })] - [InlineData ("A sentence has words. ", 3, new string [] { "A", "sen", "ten", "ce", "has", "wor", "ds." })] // Complex example - [InlineData ("12 567", 3, new string [] { "12 ", "567" })] // Double Spaces - [InlineData (" 3 567", 3, new string [] { " 3", "567" })] // Double Leading spaces should be preserved. - [InlineData (" 3 678 1", 3, new string [] { " 3", " 67", "8 ", "1" })] - [InlineData ("1 456", 3, new string [] { "1 ", "456" })] - [InlineData ("A sentence has words. ", 3, new string [] { "A ", "sen", "ten", "ce ", " ", "has", "wor", "ds.", " " })] // Double space Complex example - public void WordWrap_PreserveTrailingSpaces_False_With_Simple_Runes_Width_3 (string text, int width, IEnumerable resultLines) - { - var wrappedLines = TextFormatter.WordWrapText (text, width, preserveTrailingSpaces: false); - Assert.Equal (wrappedLines.Count, resultLines.Count ()); - Assert.Equal (resultLines, wrappedLines); - var breakLines = ""; - foreach (var line in wrappedLines) { - breakLines += $"{line}{Environment.NewLine}"; - } - var expected = string.Empty; - foreach (var line in resultLines) { - expected += $"{line}{Environment.NewLine}"; - } - Assert.Equal (expected, breakLines); - } + [Theory] + [InlineData ("文に は言葉 があり ます。", 14, 0, new [] { "文に は言葉", "があり ます。" })] + [InlineData ("文に は言葉 があり ます。", 3, -11, new [] { "文", "に", "は", "言", "葉", "が", "あ", "り", "ま", "す", "。" })] + [InlineData ("文に は言葉 があり ます。", 2, -12, new [] { "文", "に", "は", "言", "葉", "が", "あ", "り", "ま", "す", "。" })] + [InlineData ("文に は言葉 があり ます。", 1, -13, new [] { " ", " ", " " })] // Just Spaces; should result in a single space for each line + public void WordWrap_PreserveTrailingSpaces_False_Wide_Runes (string text, int maxWidth, int widthOffset, IEnumerable resultLines) + { + List wrappedLines; + + Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); + var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); + wrappedLines = TextFormatter.WordWrapText (text, maxWidth); + Assert.Equal (wrappedLines.Count, resultLines.Count ()); + Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0)); + Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0)); + Assert.Equal (resultLines, wrappedLines); + } - [Theory] - [InlineData (null, 50, new string [] { })] // null input - [InlineData ("", 50, new string [] { })] // Empty input - [InlineData ("1", 50, new string [] { "1" })] // Short input - [InlineData ("12", 50, new string [] { "12" })] - [InlineData ("123", 50, new string [] { "123" })] - [InlineData ("123456", 50, new string [] { "123456" })] // No spaces - [InlineData ("1234567", 50, new string [] { "1234567" })] // No spaces - [InlineData (" ", 50, new string [] { " " })] // Just Spaces; should result in a single space - [InlineData (" ", 50, new string [] { " " })] - [InlineData (" ", 50, new string [] { " " })] - [InlineData ("12 456", 50, new string [] { "12 456" })] // Single Spaces - [InlineData (" 2 456", 50, new string [] { " 2 456" })] // Leading spaces should be preserved. - [InlineData (" 2 456 8", 50, new string [] { " 2 456 8" })] - [InlineData ("A sentence has words. ", 50, new string [] { "A sentence has words. " })] // Complex example - [InlineData ("12 567", 50, new string [] { "12 567" })] // Double Spaces - [InlineData (" 3 567", 50, new string [] { " 3 567" })] // Double Leading spaces should be preserved. - [InlineData (" 3 678 1", 50, new string [] { " 3 678 1" })] - [InlineData ("1 456", 50, new string [] { "1 456" })] - [InlineData ("A sentence has words. ", 50, new string [] { "A sentence has words. " })] // Double space Complex example - public void WordWrap_PreserveTrailingSpaces_False_With_Simple_Runes_Width_50 (string text, int width, IEnumerable resultLines) - { - var wrappedLines = TextFormatter.WordWrapText (text, width, preserveTrailingSpaces: false); - Assert.Equal (wrappedLines.Count, resultLines.Count ()); - Assert.Equal (resultLines, wrappedLines); - var breakLines = ""; - foreach (var line in wrappedLines) { - breakLines += $"{line}{Environment.NewLine}"; - } - var expected = string.Empty; - foreach (var line in resultLines) { - expected += $"{line}{Environment.NewLine}"; - } - Assert.Equal (expected, breakLines); - } + [Theory] + [InlineData ("A sentence has words. ", 3, new [] { "A ", "sen", "ten", "ce ", "has", " ", "wor", "ds.", " " })] + [InlineData ("A sentence has words. ", 3, new [] { "A ", " ", "sen", "ten", "ce ", " ", " ", " ", "has", " ", "wor", "ds.", " " })] + public void WordWrap_PreserveTrailingSpaces_True_With_Simple_Runes_Width_3 (string text, int width, IEnumerable resultLines) + { + var wrappedLines = TextFormatter.WordWrapText (text, width, true); + Assert.Equal (wrappedLines.Count, resultLines.Count ()); + Assert.Equal (resultLines, wrappedLines); + var breakLines = ""; + foreach (var line in wrappedLines) { + breakLines += $"{line}{Environment.NewLine}"; + } + var expected = string.Empty; + foreach (var line in resultLines) { + expected += $"{line}{Environment.NewLine}"; + } + Assert.Equal (expected, breakLines); + + // Double space Complex example - this is how VS 2022 does it + //text = "A sentence has words. "; + //breakLines = ""; + //wrappedLines = TextFormatter.WordWrapText (text, width, preserveTrailingSpaces: true); + //foreach (var line in wrappedLines) { + // breakLines += $"{line}{Environment.NewLine}"; + //} + //expected = "A " + Environment.NewLine + + // " se" + Environment.NewLine + + // " nt" + Environment.NewLine + + // " en" + Environment.NewLine + + // " ce" + Environment.NewLine + + // " " + Environment.NewLine + + // " " + Environment.NewLine + + // " " + Environment.NewLine + + // " ha" + Environment.NewLine + + // " s " + Environment.NewLine + + // " wo" + Environment.NewLine + + // " rd" + Environment.NewLine + + // " s." + Environment.NewLine; + //Assert.Equal (expected, breakLines); + } - [Theory] - [InlineData ("A sentence\t\t\t has words.", 14, -10, new string [] { "A sentence\t", "\t\t has ", "words." })] - [InlineData ("A sentence\t\t\t has words.", 8, -16, new string [] { "A ", "sentence", "\t\t", "\t ", "has ", "words." })] - [InlineData ("A sentence\t\t\t has words.", 3, -21, new string [] { "A ", "sen", "ten", "ce", "\t", "\t", "\t", " ", "has", " ", "wor", "ds." })] - [InlineData ("A sentence\t\t\t has words.", 2, -22, new string [] { "A ", "se", "nt", "en", "ce", "\t", "\t", "\t", " ", "ha", "s ", "wo", "rd", "s." })] - [InlineData ("A sentence\t\t\t has words.", 1, -23, new string [] { "A", " ", "s", "e", "n", "t", "e", "n", "c", "e", "\t", "\t", "\t", " ", "h", "a", "s", " ", "w", "o", "r", "d", "s", "." })] - public void WordWrap_PreserveTrailingSpaces_True_With_Tab (string text, int maxWidth, int widthOffset, IEnumerable resultLines, int tabWidth = 4) - { - List wrappedLines; - - Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); - var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); - wrappedLines = TextFormatter.WordWrapText (text, maxWidth, preserveTrailingSpaces: true, tabWidth: tabWidth); - Assert.Equal (wrappedLines.Count, resultLines.Count ()); - Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0)); - Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0)); - Assert.Equal (resultLines, wrappedLines); - } + [Theory] + [InlineData (null, 1, new string [] { })] // null input + [InlineData ("", 1, new string [] { })] // Empty input + [InlineData ("1 34", 1, new [] { "1", "3", "4" })] // Single Spaces + [InlineData ("1", 1, new [] { "1" })] // Short input + [InlineData ("12", 1, new [] { "1", "2" })] + [InlineData ("123", 1, new [] { "1", "2", "3" })] + [InlineData ("123456", 1, new [] { "1", "2", "3", "4", "5", "6" })] // No spaces + [InlineData (" ", 1, new [] { " " })] // Just Spaces; should result in a single space + [InlineData (" ", 1, new [] { " " })] + [InlineData (" ", 1, new [] { " ", " " })] + [InlineData (" ", 1, new [] { " ", " " })] + [InlineData ("12 456", 1, new [] { "1", "2", "4", "5", "6" })] // Single Spaces + [InlineData (" 2 456", 1, new [] { " ", "2", "4", "5", "6" })] // Leading spaces should be preserved. + [InlineData (" 2 456 8", 1, new [] { " ", "2", "4", "5", "6", "8" })] + [InlineData ("A sentence has words. ", 1, new [] { "A", "s", "e", "n", "t", "e", "n", "c", "e", "h", "a", "s", "w", "o", "r", "d", "s", "." })] // Complex example + [InlineData ("12 567", 1, new [] { "1", "2", " ", "5", "6", "7" })] // Double Spaces + [InlineData (" 3 567", 1, new [] { " ", "3", "5", "6", "7" })] // Double Leading spaces should be preserved. + [InlineData (" 3 678 1", 1, new [] { " ", "3", " ", "6", "7", "8", " ", "1" })] + [InlineData ("1 456", 1, new [] { "1", " ", "4", "5", "6" })] + [InlineData ("A sentence has words. ", 1, + new [] { "A", " ", "s", "e", "n", "t", "e", "n", "c", "e", " ", "h", "a", "s", "w", "o", "r", "d", "s", ".", " " })] // Double space Complex example + public void WordWrap_PreserveTrailingSpaces_False_With_Simple_Runes_Width_1 (string text, int width, IEnumerable resultLines) + { + var wrappedLines = TextFormatter.WordWrapText (text, width); + Assert.Equal (wrappedLines.Count, resultLines.Count ()); + Assert.Equal (resultLines, wrappedLines); + var breakLines = ""; + foreach (var line in wrappedLines) { + breakLines += $"{line}{Environment.NewLine}"; + } + var expected = string.Empty; + foreach (var line in resultLines) { + expected += $"{line}{Environment.NewLine}"; + } + Assert.Equal (expected, breakLines); + } - [Theory] - [InlineData ("これが最初の行です。 こんにちは世界。 これが2行目です。", 29, 0, new string [] { "これが最初の行です。", "こんにちは世界。", "これが2行目です。" })] - public void WordWrap_PreserveTrailingSpaces_False_Unicode_Wide_Runes (string text, int maxWidth, int widthOffset, IEnumerable resultLines) - { - List wrappedLines; - - Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); - var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); - wrappedLines = TextFormatter.WordWrapText (text, maxWidth, preserveTrailingSpaces: false); - Assert.Equal (wrappedLines.Count, resultLines.Count ()); - Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0)); - Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0)); - Assert.Equal (resultLines, wrappedLines); - } + [Theory] + [InlineData (null, 3, new string [] { })] // null input + [InlineData ("", 3, new string [] { })] // Empty input + [InlineData ("1", 3, new [] { "1" })] // Short input + [InlineData ("12", 3, new [] { "12" })] + [InlineData ("123", 3, new [] { "123" })] + [InlineData ("123456", 3, new [] { "123", "456" })] // No spaces + [InlineData ("1234567", 3, new [] { "123", "456", "7" })] // No spaces + [InlineData (" ", 3, new [] { " " })] // Just Spaces; should result in a single space + [InlineData (" ", 3, new [] { " " })] + [InlineData (" ", 3, new [] { " " })] + [InlineData (" ", 3, new [] { " " })] + [InlineData ("12 456", 3, new [] { "12", "456" })] // Single Spaces + [InlineData (" 2 456", 3, new [] { " 2", "456" })] // Leading spaces should be preserved. + [InlineData (" 2 456 8", 3, new [] { " 2", "456", "8" })] + [InlineData ("A sentence has words. ", 3, new [] { "A", "sen", "ten", "ce", "has", "wor", "ds." })] // Complex example + [InlineData ("12 567", 3, new [] { "12 ", "567" })] // Double Spaces + [InlineData (" 3 567", 3, new [] { " 3", "567" })] // Double Leading spaces should be preserved. + [InlineData (" 3 678 1", 3, new [] { " 3", " 67", "8 ", "1" })] + [InlineData ("1 456", 3, new [] { "1 ", "456" })] + [InlineData ("A sentence has words. ", 3, new [] { "A ", "sen", "ten", "ce ", " ", "has", "wor", "ds.", " " })] // Double space Complex example + public void WordWrap_PreserveTrailingSpaces_False_With_Simple_Runes_Width_3 (string text, int width, IEnumerable resultLines) + { + var wrappedLines = TextFormatter.WordWrapText (text, width); + Assert.Equal (wrappedLines.Count, resultLines.Count ()); + Assert.Equal (resultLines, wrappedLines); + var breakLines = ""; + foreach (var line in wrappedLines) { + breakLines += $"{line}{Environment.NewLine}"; + } + var expected = string.Empty; + foreach (var line in resultLines) { + expected += $"{line}{Environment.NewLine}"; + } + Assert.Equal (expected, breakLines); + } - [Theory] - [InlineData ("test", 0, 't', "test")] - [InlineData ("test", 1, 'e', "test")] - [InlineData ("Ok", 0, 'O', "Ok")] - [InlineData ("[◦ Ok ◦]", 3, 'O', "[◦ Ok ◦]")] - [InlineData ("^k", 0, '^', "^k")] - public void ReplaceHotKeyWithTag (string text, int hotPos, uint tag, string expected) - { - var tf = new TextFormatter (); - var runes = text.ToRuneList (); - Rune rune; - if (Rune.TryGetRuneAt (text, hotPos, out rune)) { - Assert.Equal (rune, (Rune)tag); + [Theory] + [InlineData (null, 50, new string [] { })] // null input + [InlineData ("", 50, new string [] { })] // Empty input + [InlineData ("1", 50, new [] { "1" })] // Short input + [InlineData ("12", 50, new [] { "12" })] + [InlineData ("123", 50, new [] { "123" })] + [InlineData ("123456", 50, new [] { "123456" })] // No spaces + [InlineData ("1234567", 50, new [] { "1234567" })] // No spaces + [InlineData (" ", 50, new [] { " " })] // Just Spaces; should result in a single space + [InlineData (" ", 50, new [] { " " })] + [InlineData (" ", 50, new [] { " " })] + [InlineData ("12 456", 50, new [] { "12 456" })] // Single Spaces + [InlineData (" 2 456", 50, new [] { " 2 456" })] // Leading spaces should be preserved. + [InlineData (" 2 456 8", 50, new [] { " 2 456 8" })] + [InlineData ("A sentence has words. ", 50, new [] { "A sentence has words. " })] // Complex example + [InlineData ("12 567", 50, new [] { "12 567" })] // Double Spaces + [InlineData (" 3 567", 50, new [] { " 3 567" })] // Double Leading spaces should be preserved. + [InlineData (" 3 678 1", 50, new [] { " 3 678 1" })] + [InlineData ("1 456", 50, new [] { "1 456" })] + [InlineData ("A sentence has words. ", 50, new [] { "A sentence has words. " })] // Double space Complex example + public void WordWrap_PreserveTrailingSpaces_False_With_Simple_Runes_Width_50 (string text, int width, IEnumerable resultLines) + { + var wrappedLines = TextFormatter.WordWrapText (text, width); + Assert.Equal (wrappedLines.Count, resultLines.Count ()); + Assert.Equal (resultLines, wrappedLines); + var breakLines = ""; + foreach (var line in wrappedLines) { + breakLines += $"{line}{Environment.NewLine}"; + } + var expected = string.Empty; + foreach (var line in resultLines) { + expected += $"{line}{Environment.NewLine}"; + } + Assert.Equal (expected, breakLines); + } - } - var result = tf.ReplaceHotKeyWithTag (text, hotPos); - Assert.Equal (result, expected); - Assert.Equal ((Rune)tag, result.ToRunes () [hotPos]); - Assert.Equal (text.GetRuneCount (), runes.Count); - Assert.Equal (text, StringExtensions.ToString (runes)); - } + [Theory] + [InlineData ("A sentence\t\t\t has words.", 14, -10, new [] { "A sentence\t", "\t\t has ", "words." })] + [InlineData ("A sentence\t\t\t has words.", 8, -16, new [] { "A ", "sentence", "\t\t", "\t ", "has ", "words." })] + [InlineData ("A sentence\t\t\t has words.", 3, -21, new [] { "A ", "sen", "ten", "ce", "\t", "\t", "\t", " ", "has", " ", "wor", "ds." })] + [InlineData ("A sentence\t\t\t has words.", 2, -22, new [] { "A ", "se", "nt", "en", "ce", "\t", "\t", "\t", " ", "ha", "s ", "wo", "rd", "s." })] + [InlineData ("A sentence\t\t\t has words.", 1, -23, new [] { "A", " ", "s", "e", "n", "t", "e", "n", "c", "e", "\t", "\t", "\t", " ", "h", "a", "s", " ", "w", "o", "r", "d", "s", "." })] + public void WordWrap_PreserveTrailingSpaces_True_With_Tab (string text, int maxWidth, int widthOffset, IEnumerable resultLines, int tabWidth = 4) + { + List wrappedLines; + + Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); + var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); + wrappedLines = TextFormatter.WordWrapText (text, maxWidth, true, tabWidth); + Assert.Equal (wrappedLines.Count, resultLines.Count ()); + Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0)); + Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0)); + Assert.Equal (resultLines, wrappedLines); + } - [Theory] - [InlineData ("", -1, TextAlignment.Left, false, 0)] - [InlineData (null, 0, TextAlignment.Left, false, 1)] - [InlineData (null, 0, TextAlignment.Left, true, 1)] - [InlineData ("", 0, TextAlignment.Left, false, 1)] - [InlineData ("", 0, TextAlignment.Left, true, 1)] - public void Reformat_Invalid (string text, int maxWidth, TextAlignment textAlignment, bool wrap, int linesCount) - { - if (maxWidth < 0) { - Assert.Throws (() => TextFormatter.Format (text, maxWidth, textAlignment, wrap)); - } else { - var list = TextFormatter.Format (text, maxWidth, textAlignment, wrap); - Assert.NotEmpty (list); - Assert.True (list.Count == linesCount); - Assert.Equal (string.Empty, list [0]); - } - } + [Theory] + [InlineData ("これが最初の行です。 こんにちは世界。 これが2行目です。", 29, 0, new [] { "これが最初の行です。", "こんにちは世界。", "これが2行目です。" })] + public void WordWrap_PreserveTrailingSpaces_False_Unicode_Wide_Runes (string text, int maxWidth, int widthOffset, IEnumerable resultLines) + { + List wrappedLines; + + Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); + var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); + wrappedLines = TextFormatter.WordWrapText (text, maxWidth); + Assert.Equal (wrappedLines.Count, resultLines.Count ()); + Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0)); + Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0)); + Assert.Equal (resultLines, wrappedLines); + } - [Theory] - [InlineData ("", 0, 0, TextAlignment.Left, false, 1, true)] - [InlineData ("", 1, 1, TextAlignment.Left, false, 1, true)] - [InlineData ("A sentence has words.", 0, -21, TextAlignment.Left, false, 1, true)] - [InlineData ("A sentence has words.", 1, -20, TextAlignment.Left, false, 1, false)] - [InlineData ("A sentence has words.", 5, -16, TextAlignment.Left, false, 1, false)] - [InlineData ("A sentence has words.", 20, -1, TextAlignment.Left, false, 1, false)] - // no clip - [InlineData ("A sentence has words.", 21, 0, TextAlignment.Left, false, 1, false)] - [InlineData ("A sentence has words.", 22, 1, TextAlignment.Left, false, 1, false)] - public void Reformat_NoWordrap_SingleLine (string text, int maxWidth, int widthOffset, TextAlignment textAlignment, bool wrap, int linesCount, bool stringEmpty) - { - Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); - var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); - var list = TextFormatter.Format (text, maxWidth, textAlignment, wrap); - Assert.NotEmpty (list); - Assert.True (list.Count == linesCount); - if (stringEmpty) { - Assert.Equal (string.Empty, list [0]); - } else { - Assert.NotEqual (string.Empty, list [0]); - } - Assert.Equal (StringExtensions.ToString (text.ToRunes () [0..expectedClippedWidth]), list [0]); - } + [Theory] + [InlineData ("test", 0, 't', "test")] + [InlineData ("test", 1, 'e', "test")] + [InlineData ("Ok", 0, 'O', "Ok")] + [InlineData ("[◦ Ok ◦]", 3, 'O', "[◦ Ok ◦]")] + [InlineData ("^k", 0, '^', "^k")] + public void ReplaceHotKeyWithTag (string text, int hotPos, uint tag, string expected) + { + var tf = new TextFormatter (); + var runes = text.ToRuneList (); + Rune rune; + if (Rune.TryGetRuneAt (text, hotPos, out rune)) { + Assert.Equal (rune, (Rune)tag); + + } + var result = tf.ReplaceHotKeyWithTag (text, hotPos); + Assert.Equal (result, expected); + Assert.Equal ((Rune)tag, result.ToRunes () [hotPos]); + Assert.Equal (text.GetRuneCount (), runes.Count); + Assert.Equal (text, StringExtensions.ToString (runes)); + } - [Theory] - [InlineData ("A sentence has words.\nLine 2.", 0, -29, TextAlignment.Left, false, 1, true)] - [InlineData ("A sentence has words.\nLine 2.", 1, -28, TextAlignment.Left, false, 1, false)] - [InlineData ("A sentence has words.\nLine 2.", 5, -24, TextAlignment.Left, false, 1, false)] - [InlineData ("A sentence has words.\nLine 2.", 28, -1, TextAlignment.Left, false, 1, false)] - // no clip - [InlineData ("A sentence has words.\nLine 2.", 29, 0, TextAlignment.Left, false, 1, false)] - [InlineData ("A sentence has words.\nLine 2.", 30, 1, TextAlignment.Left, false, 1, false)] - [InlineData ("A sentence has words.\r\nLine 2.", 0, -30, TextAlignment.Left, false, 1, true)] - [InlineData ("A sentence has words.\r\nLine 2.", 1, -29, TextAlignment.Left, false, 1, false)] - [InlineData ("A sentence has words.\r\nLine 2.", 5, -25, TextAlignment.Left, false, 1, false)] - [InlineData ("A sentence has words.\r\nLine 2.", 29, -1, TextAlignment.Left, false, 1, false, 1)] - [InlineData ("A sentence has words.\r\nLine 2.", 30, 0, TextAlignment.Left, false, 1, false)] - [InlineData ("A sentence has words.\r\nLine 2.", 31, 1, TextAlignment.Left, false, 1, false)] - public void Reformat_NoWordrap_NewLines_MultiLine_False (string text, int maxWidth, int widthOffset, TextAlignment textAlignment, bool wrap, int linesCount, bool stringEmpty, int clipWidthOffset = 0) - { - Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); - var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth) + clipWidthOffset; + [Theory] + [InlineData ("", -1, TextAlignment.Left, false, 0)] + [InlineData (null, 0, TextAlignment.Left, false, 1)] + [InlineData (null, 0, TextAlignment.Left, true, 1)] + [InlineData ("", 0, TextAlignment.Left, false, 1)] + [InlineData ("", 0, TextAlignment.Left, true, 1)] + public void Reformat_Invalid (string text, int maxWidth, TextAlignment textAlignment, bool wrap, int linesCount) + { + if (maxWidth < 0) { + Assert.Throws (() => TextFormatter.Format (text, maxWidth, textAlignment, wrap)); + } else { var list = TextFormatter.Format (text, maxWidth, textAlignment, wrap); Assert.NotEmpty (list); Assert.True (list.Count == linesCount); - if (stringEmpty) { - Assert.Equal (string.Empty, list [0]); - } else { - Assert.NotEqual (string.Empty, list [0]); - } - if (text.Contains ("\r\n") && maxWidth > 0) { - Assert.Equal (StringExtensions.ToString (text.ToRunes () [0..expectedClippedWidth]).Replace ("\r\n", " "), list [0]); - } else if (text.Contains ('\n') && maxWidth > 0) { - Assert.Equal (StringExtensions.ToString (text.ToRunes () [0..expectedClippedWidth]).Replace ("\n", " "), list [0]); - } else { - Assert.Equal (StringExtensions.ToString (text.ToRunes () [0..expectedClippedWidth]), list [0]); - } + Assert.Equal (string.Empty, list [0]); } + } - [Theory] - [InlineData ("A sentence has words.\nLine 2.", 0, -29, TextAlignment.Left, false, 1, true, new string [] { "" })] - [InlineData ("A sentence has words.\nLine 2.", 1, -28, TextAlignment.Left, false, 2, false, new string [] { "A", "L" })] - [InlineData ("A sentence has words.\nLine 2.", 5, -24, TextAlignment.Left, false, 2, false, new string [] { "A sen", "Line " })] - [InlineData ("A sentence has words.\nLine 2.", 28, -1, TextAlignment.Left, false, 2, false, new string [] { "A sentence has words.", "Line 2." })] - //// no clip - [InlineData ("A sentence has words.\nLine 2.", 29, 0, TextAlignment.Left, false, 2, false, new string [] { "A sentence has words.", "Line 2." })] - [InlineData ("A sentence has words.\nLine 2.", 30, 1, TextAlignment.Left, false, 2, false, new string [] { "A sentence has words.", "Line 2." })] - [InlineData ("A sentence has words.\r\nLine 2.", 0, -30, TextAlignment.Left, false, 1, true, new string [] { "" })] - [InlineData ("A sentence has words.\r\nLine 2.", 1, -29, TextAlignment.Left, false, 2, false, new string [] { "A", "L" })] - [InlineData ("A sentence has words.\r\nLine 2.", 5, -25, TextAlignment.Left, false, 2, false, new string [] { "A sen", "Line " })] - [InlineData ("A sentence has words.\r\nLine 2.", 29, -1, TextAlignment.Left, false, 2, false, new string [] { "A sentence has words.", "Line 2." })] - [InlineData ("A sentence has words.\r\nLine 2.", 30, 0, TextAlignment.Left, false, 2, false, new string [] { "A sentence has words.", "Line 2." })] - [InlineData ("A sentence has words.\r\nLine 2.", 31, 1, TextAlignment.Left, false, 2, false, new string [] { "A sentence has words.", "Line 2." })] - public void Reformat_NoWordrap_NewLines_MultiLine_True (string text, int maxWidth, int widthOffset, TextAlignment textAlignment, bool wrap, int linesCount, bool stringEmpty, IEnumerable resultLines) - { - Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); - var list = TextFormatter.Format (text, maxWidth, textAlignment, wrap, false, 0, TextDirection.LeftRight_TopBottom, true); - Assert.NotEmpty (list); - Assert.True (list.Count == linesCount); - if (stringEmpty) { - Assert.Equal (string.Empty, list [0]); - } else { - Assert.NotEqual (string.Empty, list [0]); - } + [Theory] + [InlineData ("", 0, 0, TextAlignment.Left, false, 1, true)] + [InlineData ("", 1, 1, TextAlignment.Left, false, 1, true)] + [InlineData ("A sentence has words.", 0, -21, TextAlignment.Left, false, 1, true)] + [InlineData ("A sentence has words.", 1, -20, TextAlignment.Left, false, 1, false)] + [InlineData ("A sentence has words.", 5, -16, TextAlignment.Left, false, 1, false)] + [InlineData ("A sentence has words.", 20, -1, TextAlignment.Left, false, 1, false)] + // no clip + [InlineData ("A sentence has words.", 21, 0, TextAlignment.Left, false, 1, false)] + [InlineData ("A sentence has words.", 22, 1, TextAlignment.Left, false, 1, false)] + public void Reformat_NoWordrap_SingleLine (string text, int maxWidth, int widthOffset, TextAlignment textAlignment, bool wrap, int linesCount, bool stringEmpty) + { + Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); + var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); + var list = TextFormatter.Format (text, maxWidth, textAlignment, wrap); + Assert.NotEmpty (list); + Assert.True (list.Count == linesCount); + if (stringEmpty) { + Assert.Equal (string.Empty, list [0]); + } else { + Assert.NotEqual (string.Empty, list [0]); + } + Assert.Equal (StringExtensions.ToString (text.ToRunes () [..expectedClippedWidth]), list [0]); + } - Assert.Equal (list, resultLines); + [Theory] + [InlineData ("A sentence has words.\nLine 2.", 0, -29, TextAlignment.Left, false, 1, true)] + [InlineData ("A sentence has words.\nLine 2.", 1, -28, TextAlignment.Left, false, 1, false)] + [InlineData ("A sentence has words.\nLine 2.", 5, -24, TextAlignment.Left, false, 1, false)] + [InlineData ("A sentence has words.\nLine 2.", 28, -1, TextAlignment.Left, false, 1, false)] + // no clip + [InlineData ("A sentence has words.\nLine 2.", 29, 0, TextAlignment.Left, false, 1, false)] + [InlineData ("A sentence has words.\nLine 2.", 30, 1, TextAlignment.Left, false, 1, false)] + [InlineData ("A sentence has words.\r\nLine 2.", 0, -30, TextAlignment.Left, false, 1, true)] + [InlineData ("A sentence has words.\r\nLine 2.", 1, -29, TextAlignment.Left, false, 1, false)] + [InlineData ("A sentence has words.\r\nLine 2.", 5, -25, TextAlignment.Left, false, 1, false)] + [InlineData ("A sentence has words.\r\nLine 2.", 29, -1, TextAlignment.Left, false, 1, false, 1)] + [InlineData ("A sentence has words.\r\nLine 2.", 30, 0, TextAlignment.Left, false, 1, false)] + [InlineData ("A sentence has words.\r\nLine 2.", 31, 1, TextAlignment.Left, false, 1, false)] + public void Reformat_NoWordrap_NewLines_MultiLine_False (string text, + int maxWidth, + int widthOffset, + TextAlignment textAlignment, + bool wrap, + int linesCount, + bool stringEmpty, + int clipWidthOffset = 0) + { + Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); + var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth) + clipWidthOffset; + var list = TextFormatter.Format (text, maxWidth, textAlignment, wrap); + Assert.NotEmpty (list); + Assert.True (list.Count == linesCount); + if (stringEmpty) { + Assert.Equal (string.Empty, list [0]); + } else { + Assert.NotEqual (string.Empty, list [0]); + } + if (text.Contains ("\r\n") && maxWidth > 0) { + Assert.Equal (StringExtensions.ToString (text.ToRunes () [..expectedClippedWidth]).Replace ("\r\n", " "), list [0]); + } else if (text.Contains ('\n') && maxWidth > 0) { + Assert.Equal (StringExtensions.ToString (text.ToRunes () [..expectedClippedWidth]).Replace ("\n", " "), list [0]); + } else { + Assert.Equal (StringExtensions.ToString (text.ToRunes () [..expectedClippedWidth]), list [0]); } + } - [Theory] - [InlineData ("A sentence has words.\nLine 2.", 0, -29, TextAlignment.Left, false, 1, true, new string [] { "" })] - [InlineData ("A sentence has words.\nLine 2.", 1, -28, TextAlignment.Left, false, 2, false, new string [] { "A", "L" })] - [InlineData ("A sentence has words.\nLine 2.", 5, -24, TextAlignment.Left, false, 2, false, new string [] { "A sen", "Line " })] - [InlineData ("A sentence has words.\nLine 2.", 28, -1, TextAlignment.Left, false, 2, false, new string [] { "A sentence has words.", "Line 2." })] - //// no clip - [InlineData ("A sentence has words.\nLine 2.", 29, 0, TextAlignment.Left, false, 2, false, new string [] { "A sentence has words.", "Line 2." })] - [InlineData ("A sentence has words.\nLine 2.", 30, 1, TextAlignment.Left, false, 2, false, new string [] { "A sentence has words.", "Line 2." })] - [InlineData ("A sentence has words.\r\nLine 2.", 0, -30, TextAlignment.Left, false, 1, true, new string [] { "" })] - [InlineData ("A sentence has words.\r\nLine 2.", 1, -29, TextAlignment.Left, false, 2, false, new string [] { "A", "L" })] - [InlineData ("A sentence has words.\r\nLine 2.", 5, -25, TextAlignment.Left, false, 2, false, new string [] { "A sen", "Line " })] - [InlineData ("A sentence has words.\r\nLine 2.", 29, -1, TextAlignment.Left, false, 2, false, new string [] { "A sentence has words.", "Line 2." })] - [InlineData ("A sentence has words.\r\nLine 2.", 30, 0, TextAlignment.Left, false, 2, false, new string [] { "A sentence has words.", "Line 2." })] - [InlineData ("A sentence has words.\r\nLine 2.", 31, 1, TextAlignment.Left, false, 2, false, new string [] { "A sentence has words.", "Line 2." })] - public void Reformat_NoWordrap_NewLines_MultiLine_True_Vertical (string text, int maxWidth, int widthOffset, TextAlignment textAlignment, bool wrap, int linesCount, bool stringEmpty, IEnumerable resultLines) - { - Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); - var list = TextFormatter.Format (text, maxWidth, textAlignment, wrap, false, 0, TextDirection.TopBottom_LeftRight, true); - Assert.NotEmpty (list); - Assert.True (list.Count == linesCount); - if (stringEmpty) { - Assert.Equal (string.Empty, list [0]); - } else { - Assert.NotEqual (string.Empty, list [0]); - } + [Theory] + [InlineData ("A sentence has words.\nLine 2.", 0, -29, TextAlignment.Left, false, 1, true, new [] { "" })] + [InlineData ("A sentence has words.\nLine 2.", 1, -28, TextAlignment.Left, false, 2, false, new [] { "A", "L" })] + [InlineData ("A sentence has words.\nLine 2.", 5, -24, TextAlignment.Left, false, 2, false, new [] { "A sen", "Line " })] + [InlineData ("A sentence has words.\nLine 2.", 28, -1, TextAlignment.Left, false, 2, false, new [] { "A sentence has words.", "Line 2." })] + //// no clip + [InlineData ("A sentence has words.\nLine 2.", 29, 0, TextAlignment.Left, false, 2, false, new [] { "A sentence has words.", "Line 2." })] + [InlineData ("A sentence has words.\nLine 2.", 30, 1, TextAlignment.Left, false, 2, false, new [] { "A sentence has words.", "Line 2." })] + [InlineData ("A sentence has words.\r\nLine 2.", 0, -30, TextAlignment.Left, false, 1, true, new [] { "" })] + [InlineData ("A sentence has words.\r\nLine 2.", 1, -29, TextAlignment.Left, false, 2, false, new [] { "A", "L" })] + [InlineData ("A sentence has words.\r\nLine 2.", 5, -25, TextAlignment.Left, false, 2, false, new [] { "A sen", "Line " })] + [InlineData ("A sentence has words.\r\nLine 2.", 29, -1, TextAlignment.Left, false, 2, false, new [] { "A sentence has words.", "Line 2." })] + [InlineData ("A sentence has words.\r\nLine 2.", 30, 0, TextAlignment.Left, false, 2, false, new [] { "A sentence has words.", "Line 2." })] + [InlineData ("A sentence has words.\r\nLine 2.", 31, 1, TextAlignment.Left, false, 2, false, new [] { "A sentence has words.", "Line 2." })] + public void Reformat_NoWordrap_NewLines_MultiLine_True (string text, + int maxWidth, + int widthOffset, + TextAlignment textAlignment, + bool wrap, + int linesCount, + bool stringEmpty, + IEnumerable resultLines) + { + Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); + var list = TextFormatter.Format (text, maxWidth, textAlignment, wrap, false, 0, TextDirection.LeftRight_TopBottom, true); + Assert.NotEmpty (list); + Assert.True (list.Count == linesCount); + if (stringEmpty) { + Assert.Equal (string.Empty, list [0]); + } else { + Assert.NotEqual (string.Empty, list [0]); + } + + Assert.Equal (list, resultLines); + } - Assert.Equal (list, resultLines); - } + [Theory] + [InlineData ("A sentence has words.\nLine 2.", 0, -29, TextAlignment.Left, false, 1, true, new [] { "" })] + [InlineData ("A sentence has words.\nLine 2.", 1, -28, TextAlignment.Left, false, 2, false, new [] { "A", "L" })] + [InlineData ("A sentence has words.\nLine 2.", 5, -24, TextAlignment.Left, false, 2, false, new [] { "A sen", "Line " })] + [InlineData ("A sentence has words.\nLine 2.", 28, -1, TextAlignment.Left, false, 2, false, new [] { "A sentence has words.", "Line 2." })] + //// no clip + [InlineData ("A sentence has words.\nLine 2.", 29, 0, TextAlignment.Left, false, 2, false, new [] { "A sentence has words.", "Line 2." })] + [InlineData ("A sentence has words.\nLine 2.", 30, 1, TextAlignment.Left, false, 2, false, new [] { "A sentence has words.", "Line 2." })] + [InlineData ("A sentence has words.\r\nLine 2.", 0, -30, TextAlignment.Left, false, 1, true, new [] { "" })] + [InlineData ("A sentence has words.\r\nLine 2.", 1, -29, TextAlignment.Left, false, 2, false, new [] { "A", "L" })] + [InlineData ("A sentence has words.\r\nLine 2.", 5, -25, TextAlignment.Left, false, 2, false, new [] { "A sen", "Line " })] + [InlineData ("A sentence has words.\r\nLine 2.", 29, -1, TextAlignment.Left, false, 2, false, new [] { "A sentence has words.", "Line 2." })] + [InlineData ("A sentence has words.\r\nLine 2.", 30, 0, TextAlignment.Left, false, 2, false, new [] { "A sentence has words.", "Line 2." })] + [InlineData ("A sentence has words.\r\nLine 2.", 31, 1, TextAlignment.Left, false, 2, false, new [] { "A sentence has words.", "Line 2." })] + public void Reformat_NoWordrap_NewLines_MultiLine_True_Vertical (string text, + int maxWidth, + int widthOffset, + TextAlignment textAlignment, + bool wrap, + int linesCount, + bool stringEmpty, + IEnumerable resultLines) + { + Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); + var list = TextFormatter.Format (text, maxWidth, textAlignment, wrap, false, 0, TextDirection.TopBottom_LeftRight, true); + Assert.NotEmpty (list); + Assert.True (list.Count == linesCount); + if (stringEmpty) { + Assert.Equal (string.Empty, list [0]); + } else { + Assert.NotEqual (string.Empty, list [0]); + } + + Assert.Equal (list, resultLines); + } - [Theory] - // Even # of spaces - // 0123456789 - [InlineData ("012 456 89", 0, -10, TextAlignment.Left, true, true, true, new string [] { "" })] - [InlineData ("012 456 89", 1, -9, TextAlignment.Left, true, true, false, new string [] { "0", "1", "2", " ", "4", "5", "6", " ", "8", "9" }, "01245689")] - [InlineData ("012 456 89", 5, -5, TextAlignment.Left, true, true, false, new string [] { "012 ", "456 ", "89" })] - [InlineData ("012 456 89", 9, -1, TextAlignment.Left, true, true, false, new string [] { "012 456 ", "89" })] - // no clip - [InlineData ("012 456 89", 10, 0, TextAlignment.Left, true, true, false, new string [] { "012 456 89" })] - [InlineData ("012 456 89", 11, 1, TextAlignment.Left, true, true, false, new string [] { "012 456 89" })] - // Odd # of spaces - // 01234567890123 - [InlineData ("012 456 89 end", 13, -1, TextAlignment.Left, true, true, false, new string [] { "012 456 89 ", "end" })] - // no clip - [InlineData ("012 456 89 end", 14, 0, TextAlignment.Left, true, true, false, new string [] { "012 456 89 end" })] - [InlineData ("012 456 89 end", 15, 1, TextAlignment.Left, true, true, false, new string [] { "012 456 89 end" })] - public void Reformat_Wrap_Spaces_No_NewLines (string text, int maxWidth, int widthOffset, TextAlignment textAlignment, bool wrap, bool preserveTrailingSpaces, bool stringEmpty, IEnumerable resultLines, string noSpaceText = "") - { - Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); - var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); - var list = TextFormatter.Format (text, maxWidth, textAlignment, wrap, preserveTrailingSpaces); - Assert.NotEmpty (list); - Assert.True (list.Count == resultLines.Count ()); - if (stringEmpty) { - Assert.Equal (string.Empty, list [0]); + [Theory] + // Even # of spaces + // 0123456789 + [InlineData ("012 456 89", 0, -10, TextAlignment.Left, true, true, true, new [] { "" })] + [InlineData ("012 456 89", 1, -9, TextAlignment.Left, true, true, false, new [] { "0", "1", "2", " ", "4", "5", "6", " ", "8", "9" }, "01245689")] + [InlineData ("012 456 89", 5, -5, TextAlignment.Left, true, true, false, new [] { "012 ", "456 ", "89" })] + [InlineData ("012 456 89", 9, -1, TextAlignment.Left, true, true, false, new [] { "012 456 ", "89" })] + // no clip + [InlineData ("012 456 89", 10, 0, TextAlignment.Left, true, true, false, new [] { "012 456 89" })] + [InlineData ("012 456 89", 11, 1, TextAlignment.Left, true, true, false, new [] { "012 456 89" })] + // Odd # of spaces + // 01234567890123 + [InlineData ("012 456 89 end", 13, -1, TextAlignment.Left, true, true, false, new [] { "012 456 89 ", "end" })] + // no clip + [InlineData ("012 456 89 end", 14, 0, TextAlignment.Left, true, true, false, new [] { "012 456 89 end" })] + [InlineData ("012 456 89 end", 15, 1, TextAlignment.Left, true, true, false, new [] { "012 456 89 end" })] + public void Reformat_Wrap_Spaces_No_NewLines (string text, + int maxWidth, + int widthOffset, + TextAlignment textAlignment, + bool wrap, + bool preserveTrailingSpaces, + bool stringEmpty, + IEnumerable resultLines, + string noSpaceText = "") + { + Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); + var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); + var list = TextFormatter.Format (text, maxWidth, textAlignment, wrap, preserveTrailingSpaces); + Assert.NotEmpty (list); + Assert.True (list.Count == resultLines.Count ()); + if (stringEmpty) { + Assert.Equal (string.Empty, list [0]); + } else { + Assert.NotEqual (string.Empty, list [0]); + } + Assert.Equal (resultLines, list); + + if (maxWidth > 0) { + // remove whitespace chars + if (maxWidth < 5) { + expectedClippedWidth = text.GetRuneCount () - text.Sum (r => r == ' ' ? 1 : 0); } else { - Assert.NotEqual (string.Empty, list [0]); + expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth - text.Sum (r => r == ' ' ? 1 : 0)); } - Assert.Equal (resultLines, list); - - if (maxWidth > 0) { - // remove whitespace chars - if (maxWidth < 5) { - expectedClippedWidth = text.GetRuneCount () - text.Sum (r => r == ' ' ? 1 : 0); - } else { - expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth - text.Sum (r => r == ' ' ? 1 : 0)); - } - list = TextFormatter.Format (text, maxWidth, TextAlignment.Left, wrap, preserveTrailingSpaces: false); - if (maxWidth == 1) { - Assert.Equal (expectedClippedWidth, list.Count); - Assert.Equal (noSpaceText, string.Concat (list.ToArray ())); - } - if (maxWidth > 1 && maxWidth < 10) { - Assert.Equal (StringExtensions.ToString (text.ToRunes () [0..expectedClippedWidth]), list [0]); - } + list = TextFormatter.Format (text, maxWidth, TextAlignment.Left, wrap); + if (maxWidth == 1) { + Assert.Equal (expectedClippedWidth, list.Count); + Assert.Equal (noSpaceText, string.Concat (list.ToArray ())); } - } - - [Theory] - // Unicode - // Even # of chars - // 0123456789 - [InlineData ("\u2660пÑРвРÑ", 10, -1, TextAlignment.Left, true, false, new string [] { "\u2660пÑРвÐ", "Ñ" })] - // no clip - [InlineData ("\u2660пÑРвРÑ", 11, 0, TextAlignment.Left, true, false, new string [] { "\u2660пÑРвРÑ" })] - [InlineData ("\u2660пÑРвРÑ", 12, 1, TextAlignment.Left, true, false, new string [] { "\u2660пÑРвРÑ" })] - // Unicode - // Odd # of chars - // 0123456789 - [InlineData ("\u2660 ÑРвРÑ", 9, -1, TextAlignment.Left, true, false, new string [] { "\u2660 ÑРвÐ", "Ñ" })] - // no clip - [InlineData ("\u2660 ÑРвРÑ", 10, 0, TextAlignment.Left, true, false, new string [] { "\u2660 ÑРвРÑ" })] - [InlineData ("\u2660 ÑРвРÑ", 11, 1, TextAlignment.Left, true, false, new string [] { "\u2660 ÑРвРÑ" })] - public void Reformat_Unicode_Wrap_Spaces_No_NewLines (string text, int maxWidth, int widthOffset, TextAlignment textAlignment, bool wrap, bool preserveTrailingSpaces, IEnumerable resultLines) - { - Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); - var list = TextFormatter.Format (text, maxWidth, textAlignment, wrap, preserveTrailingSpaces); - Assert.Equal (list.Count, resultLines.Count ()); - Assert.Equal (resultLines, list); - } - - [Theory] - // Unicode - [InlineData ("\u2460\u2461\u2462\n\u2460\u2461\u2462\u2463\u2464", 8, -1, TextAlignment.Left, true, false, new string [] { "\u2460\u2461\u2462", "\u2460\u2461\u2462\u2463\u2464" })] - // no clip - [InlineData ("\u2460\u2461\u2462\n\u2460\u2461\u2462\u2463\u2464", 9, 0, TextAlignment.Left, true, false, new string [] { "\u2460\u2461\u2462", "\u2460\u2461\u2462\u2463\u2464" })] - [InlineData ("\u2460\u2461\u2462\n\u2460\u2461\u2462\u2463\u2464", 10, 1, TextAlignment.Left, true, false, new string [] { "\u2460\u2461\u2462", "\u2460\u2461\u2462\u2463\u2464" })] - public void Reformat_Unicode_Wrap_Spaces_NewLines (string text, int maxWidth, int widthOffset, TextAlignment textAlignment, bool wrap, bool preserveTrailingSpaces, IEnumerable resultLines) - { - Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); - var list = TextFormatter.Format (text, maxWidth, textAlignment, wrap, preserveTrailingSpaces); - Assert.Equal (list.Count, resultLines.Count ()); - Assert.Equal (resultLines, list); - } - - [Theory] - [InlineData (" A sentence has words. \n This is the second Line - 2. ", 4, -50, TextAlignment.Left, true, false, new string [] { " A", "sent", "ence", "has", "word", "s. ", " Thi", "s is", "the", "seco", "nd", "Line", "- 2." }, " Asentencehaswords. This isthesecondLine- 2.")] - [InlineData (" A sentence has words. \n This is the second Line - 2. ", 4, -50, TextAlignment.Left, true, true, new string [] { " A ", "sent", "ence", " ", "has ", "word", "s. ", " ", "This", " is ", "the ", "seco", "nd ", "Line", " - ", "2. " }, " A sentence has words. This is the second Line - 2. ")] - public void Format_WordWrap_PreserveTrailingSpaces (string text, int maxWidth, int widthOffset, TextAlignment textAlignment, bool wrap, bool preserveTrailingSpaces, IEnumerable resultLines, string expectedWrappedText) - { - Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); - var list = TextFormatter.Format (text, maxWidth, textAlignment, wrap, preserveTrailingSpaces); - Assert.Equal (list.Count, resultLines.Count ()); - Assert.Equal (resultLines, list); - string wrappedText = string.Empty; - foreach (var txt in list) wrappedText += txt; - Assert.Equal (expectedWrappedText, wrappedText); - } - - [Fact] - public void Format_Dont_Throw_ArgumentException_With_WordWrap_As_False_And_Keep_End_Spaces_As_True () - { - var exception = Record.Exception (() => TextFormatter.Format ("Some text", 4, TextAlignment.Left, false, true)); - Assert.Null (exception); - } - - [Theory] - [InlineData ("Hello world, how are you today? Pretty neat!", 44, 80, "Hello world, how are you today? Pretty neat!")] - public void Format_Justified_Always_Returns_Text_Width_Equal_To_Passed_Width_Horizontal (string text, int runeCount, int maxWidth, string justifiedText) - { - Assert.Equal (runeCount, text.GetRuneCount ()); - - var fmtText = string.Empty; - for (int i = text.GetRuneCount (); i < maxWidth; i++) { - fmtText = TextFormatter.Format (text, i, TextAlignment.Justified, false, true) [0]; - Assert.Equal (i, fmtText.GetRuneCount ()); - var c = fmtText [^1]; - Assert.True (text.EndsWith (c)); + if (maxWidth > 1 && maxWidth < 10) { + Assert.Equal (StringExtensions.ToString (text.ToRunes () [..expectedClippedWidth]), list [0]); } - Assert.Equal (justifiedText, fmtText); } + } - [Theory] - [InlineData ("Hello world, how are you today? Pretty neat!", 44, 80, "Hello world, how are you today? Pretty neat!")] - public void Format_Justified_Always_Returns_Text_Width_Equal_To_Passed_Width_Vertical (string text, int runeCount, int maxWidth, string justifiedText) - { - Assert.Equal (runeCount, text.GetRuneCount ()); - - var fmtText = string.Empty; - for (int i = text.GetRuneCount (); i < maxWidth; i++) { - fmtText = TextFormatter.Format (text, i, TextAlignment.Justified, false, true, 0, TextDirection.TopBottom_LeftRight) [0]; - Assert.Equal (i, fmtText.GetRuneCount ()); - var c = fmtText [^1]; - Assert.True (text.EndsWith (c)); - } - Assert.Equal (justifiedText, fmtText); - } + [Theory] + // Unicode + // Even # of chars + // 0123456789 + [InlineData ("\u2660пÑРвРÑ", 10, -1, TextAlignment.Left, true, false, new [] { "\u2660пÑРвÐ", "Ñ" })] + // no clip + [InlineData ("\u2660пÑРвРÑ", 11, 0, TextAlignment.Left, true, false, new [] { "\u2660пÑРвРÑ" })] + [InlineData ("\u2660пÑРвРÑ", 12, 1, TextAlignment.Left, true, false, new [] { "\u2660пÑРвРÑ" })] + // Unicode + // Odd # of chars + // 0123456789 + [InlineData ("\u2660 ÑРвРÑ", 9, -1, TextAlignment.Left, true, false, new [] { "\u2660 ÑРвÐ", "Ñ" })] + // no clip + [InlineData ("\u2660 ÑРвРÑ", 10, 0, TextAlignment.Left, true, false, new [] { "\u2660 ÑРвРÑ" })] + [InlineData ("\u2660 ÑРвРÑ", 11, 1, TextAlignment.Left, true, false, new [] { "\u2660 ÑРвРÑ" })] + public void Reformat_Unicode_Wrap_Spaces_No_NewLines (string text, + int maxWidth, + int widthOffset, + TextAlignment textAlignment, + bool wrap, + bool preserveTrailingSpaces, + IEnumerable resultLines) + { + Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); + var list = TextFormatter.Format (text, maxWidth, textAlignment, wrap, preserveTrailingSpaces); + Assert.Equal (list.Count, resultLines.Count ()); + Assert.Equal (resultLines, list); + } - [Theory] - [InlineData ("fff", 6, "fff ")] - [InlineData ("Hello World", 16, "Hello World ")] - public void TestClipOrPad_ShortWord (string text, int fillPad, string expectedText) - { - // word is short but we want it to fill # so it should be padded - Assert.Equal (expectedText, TextFormatter.ClipOrPad (text, fillPad)); - } + [Theory] + // Unicode + [InlineData ("\u2460\u2461\u2462\n\u2460\u2461\u2462\u2463\u2464", 8, -1, TextAlignment.Left, true, false, new [] { "\u2460\u2461\u2462", "\u2460\u2461\u2462\u2463\u2464" })] + // no clip + [InlineData ("\u2460\u2461\u2462\n\u2460\u2461\u2462\u2463\u2464", 9, 0, TextAlignment.Left, true, false, new [] { "\u2460\u2461\u2462", "\u2460\u2461\u2462\u2463\u2464" })] + [InlineData ("\u2460\u2461\u2462\n\u2460\u2461\u2462\u2463\u2464", 10, 1, TextAlignment.Left, true, false, new [] { "\u2460\u2461\u2462", "\u2460\u2461\u2462\u2463\u2464" })] + public void Reformat_Unicode_Wrap_Spaces_NewLines (string text, + int maxWidth, + int widthOffset, + TextAlignment textAlignment, + bool wrap, + bool preserveTrailingSpaces, + IEnumerable resultLines) + { + Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); + var list = TextFormatter.Format (text, maxWidth, textAlignment, wrap, preserveTrailingSpaces); + Assert.Equal (list.Count, resultLines.Count ()); + Assert.Equal (resultLines, list); + } - [Theory] - [InlineData ("123456789", 3, "123")] - [InlineData ("Hello World", 8, "Hello Wo")] - public void TestClipOrPad_LongWord (string text, int fillPad, string expectedText) - { - // word is long but we want it to fill # space only - Assert.Equal (expectedText, TextFormatter.ClipOrPad (text, fillPad)); - } + [Theory] + [InlineData (" A sentence has words. \n This is the second Line - 2. ", 4, -50, TextAlignment.Left, true, false, + new [] { " A", "sent", "ence", "has", "word", "s. ", " Thi", "s is", "the", "seco", "nd", "Line", "- 2." }, " Asentencehaswords. This isthesecondLine- 2.")] + [InlineData (" A sentence has words. \n This is the second Line - 2. ", 4, -50, TextAlignment.Left, true, true, + new [] { " A ", "sent", "ence", " ", "has ", "word", "s. ", " ", "This", " is ", "the ", "seco", "nd ", "Line", " - ", "2. " }, + " A sentence has words. This is the second Line - 2. ")] + public void Format_WordWrap_PreserveTrailingSpaces (string text, + int maxWidth, + int widthOffset, + TextAlignment textAlignment, + bool wrap, + bool preserveTrailingSpaces, + IEnumerable resultLines, + string expectedWrappedText) + { + Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); + var list = TextFormatter.Format (text, maxWidth, textAlignment, wrap, preserveTrailingSpaces); + Assert.Equal (list.Count, resultLines.Count ()); + Assert.Equal (resultLines, list); + var wrappedText = string.Empty; + foreach (var txt in list) { + wrappedText += txt; + } + Assert.Equal (expectedWrappedText, wrappedText); + } - [Fact] - public void Internal_Tests () - { - var tf = new TextFormatter (); - Assert.Equal (KeyCode.Null, tf.HotKey); - tf.HotKey = KeyCode.CtrlMask | KeyCode.Q; - Assert.Equal (KeyCode.CtrlMask | KeyCode.Q, tf.HotKey); - } + [Fact] + public void Format_Dont_Throw_ArgumentException_With_WordWrap_As_False_And_Keep_End_Spaces_As_True () + { + var exception = Record.Exception (() => TextFormatter.Format ("Some text", 4, TextAlignment.Left, false, true)); + Assert.Null (exception); + } - [Theory] - [InlineData ("Hello World", 11)] - [InlineData ("こんにちは世界", 14)] - public void GetColumns_Simple_And_Wide_Runes (string text, int width) - { - Assert.Equal (width, text.GetColumns ()); - } + [Theory] + [InlineData ("Hello world, how are you today? Pretty neat!", 44, 80, "Hello world, how are you today? Pretty neat!")] + public void Format_Justified_Always_Returns_Text_Width_Equal_To_Passed_Width_Horizontal (string text, int runeCount, int maxWidth, string justifiedText) + { + Assert.Equal (runeCount, text.GetRuneCount ()); - [Theory] - [InlineData ("Hello World", 11, 6, 1, 1)] - [InlineData ("こんにちは 世界", 15, 6, 1, 2)] - public void GetSumMaxCharWidth_Simple_And_Wide_Runes (string text, int width, int index, int length, int indexWidth) - { - Assert.Equal (width, TextFormatter.GetSumMaxCharWidth (text)); - Assert.Equal (indexWidth, TextFormatter.GetSumMaxCharWidth (text, index, length)); - } - - [Theory] - [InlineData (new string [] { "Hello", "World" }, 2, 1, 1, 1)] - [InlineData (new string [] { "こんにちは", "世界" }, 4, 1, 1, 2)] - public void GetSumMaxCharWidth_List_Simple_And_Wide_Runes (IEnumerable text, int width, int index, int length, int indexWidth) - { - Assert.Equal (width, TextFormatter.GetSumMaxCharWidth (text.ToList ())); - Assert.Equal (indexWidth, TextFormatter.GetSumMaxCharWidth (text.ToList (), index, length)); + var fmtText = string.Empty; + for (var i = text.GetRuneCount (); i < maxWidth; i++) { + fmtText = TextFormatter.Format (text, i, TextAlignment.Justified, false, true) [0]; + Assert.Equal (i, fmtText.GetRuneCount ()); + var c = fmtText [^1]; + Assert.True (text.EndsWith (c)); } + Assert.Equal (justifiedText, fmtText); + } - [Theory] - [InlineData ("test", 3, 3)] - [InlineData ("test", 4, 4)] - [InlineData ("test", 10, 4)] - public void GetLengthThatFits_Runelist (string text, int columns, int expectedLength) - { - var runes = text.ToRuneList (); + [Theory] + [InlineData ("Hello world, how are you today? Pretty neat!", 44, 80, "Hello world, how are you today? Pretty neat!")] + public void Format_Justified_Always_Returns_Text_Width_Equal_To_Passed_Width_Vertical (string text, int runeCount, int maxWidth, string justifiedText) + { + Assert.Equal (runeCount, text.GetRuneCount ()); - Assert.Equal (expectedLength, TextFormatter.GetLengthThatFits (runes, columns)); + var fmtText = string.Empty; + for (var i = text.GetRuneCount (); i < maxWidth; i++) { + fmtText = TextFormatter.Format (text, i, TextAlignment.Justified, false, true, 0, TextDirection.TopBottom_LeftRight) [0]; + Assert.Equal (i, fmtText.GetRuneCount ()); + var c = fmtText [^1]; + Assert.True (text.EndsWith (c)); } + Assert.Equal (justifiedText, fmtText); + } - [Theory] - [InlineData ("test", 3, 3)] - [InlineData ("test", 4, 4)] - [InlineData ("test", 10, 4)] - [InlineData ("test", 1, 1)] - [InlineData ("test", 0, 0)] - [InlineData ("test", -1, 0)] - [InlineData (null, -1, 0)] - [InlineData ("", -1, 0)] - public void GetLengthThatFits_String (string text, int columns, int expectedLength) - { - Assert.Equal (expectedLength, TextFormatter.GetLengthThatFits (text, columns)); - } + [Theory] + [InlineData ("fff", 6, "fff ")] + [InlineData ("Hello World", 16, "Hello World ")] + public void TestClipOrPad_ShortWord (string text, int fillPad, string expectedText) => + // word is short but we want it to fill # so it should be padded + Assert.Equal (expectedText, TextFormatter.ClipOrPad (text, fillPad)); + + [Theory] + [InlineData ("123456789", 3, "123")] + [InlineData ("Hello World", 8, "Hello Wo")] + public void TestClipOrPad_LongWord (string text, int fillPad, string expectedText) => + // word is long but we want it to fill # space only + Assert.Equal (expectedText, TextFormatter.ClipOrPad (text, fillPad)); + + [Fact] + public void Internal_Tests () + { + var tf = new TextFormatter (); + Assert.Equal (KeyCode.Null, tf.HotKey); + tf.HotKey = KeyCode.CtrlMask | KeyCode.Q; + Assert.Equal (KeyCode.CtrlMask | KeyCode.Q, tf.HotKey); + } - [Theory] - [InlineData ("Hello World", 6, 6)] - [InlineData ("こんにちは 世界", 6, 3)] - public void GetLengthThatFits_Simple_And_Wide_Runes (string text, int columns, int expectedLength) - { - Assert.Equal (expectedLength, TextFormatter.GetLengthThatFits (text, columns)); - } + [Theory] + [InlineData ("Hello World", 11)] + [InlineData ("こんにちは世界", 14)] + public void GetColumns_Simple_And_Wide_Runes (string text, int width) => Assert.Equal (width, text.GetColumns ()); + + [Theory] + [InlineData ("Hello World", 11, 6, 1, 1)] + [InlineData ("こんにちは 世界", 15, 6, 1, 2)] + public void GetSumMaxCharWidth_Simple_And_Wide_Runes (string text, int width, int index, int length, int indexWidth) + { + Assert.Equal (width, TextFormatter.GetSumMaxCharWidth (text)); + Assert.Equal (indexWidth, TextFormatter.GetSumMaxCharWidth (text, index, length)); + } - [Theory] - [InlineData ("Hello World", 6, 6)] - [InlineData ("こんにちは 世界", 6, 3)] - [MemberData (nameof (CMGlyphs))] - public void GetLengthThatFits_List_Simple_And_Wide_Runes (string text, int columns, int expectedLength) - { - var runes = text.ToRuneList (); - Assert.Equal (expectedLength, TextFormatter.GetLengthThatFits (runes, columns)); - } + [Theory] + [InlineData (new [] { "Hello", "World" }, 2, 1, 1, 1)] + [InlineData (new [] { "こんにちは", "世界" }, 4, 1, 1, 2)] + public void GetSumMaxCharWidth_List_Simple_And_Wide_Runes (IEnumerable text, int width, int index, int length, int indexWidth) + { + Assert.Equal (width, TextFormatter.GetSumMaxCharWidth (text.ToList ())); + Assert.Equal (indexWidth, TextFormatter.GetSumMaxCharWidth (text.ToList (), index, length)); + } - public static IEnumerable CMGlyphs => - new List - { - new object[] { $"{CM.Glyphs.LeftBracket} Say Hello 你 {CM.Glyphs.RightBracket}", 16, 15 } - }; - - [Theory] - [InlineData ("Truncate", 3, "Tru")] - [InlineData ("デモエムポンズ", 3, "デ")] - public void Format_Truncate_Simple_And_Wide_Runes (string text, int width, string expected) - { - var list = TextFormatter.Format (text, width, false, false); - Assert.Equal (expected, list [^1]); - } + [Theory] + [InlineData ("test", 3, 3)] + [InlineData ("test", 4, 4)] + [InlineData ("test", 10, 4)] + public void GetLengthThatFits_Runelist (string text, int columns, int expectedLength) + { + var runes = text.ToRuneList (); - [Theory] - [MemberData (nameof (FormatEnvironmentNewLine))] - public void Format_With_PreserveTrailingSpaces_And_Without_PreserveTrailingSpaces (string text, int width, IEnumerable expected) - { - var preserveTrailingSpaces = false; - var formated = TextFormatter.Format (text, width, false, true, preserveTrailingSpaces); - Assert.Equal (expected, formated); - - preserveTrailingSpaces = true; - formated = TextFormatter.Format (text, width, false, true, preserveTrailingSpaces); - Assert.Equal (expected, formated); - } + Assert.Equal (expectedLength, TextFormatter.GetLengthThatFits (runes, columns)); + } - public static IEnumerable FormatEnvironmentNewLine => - new List - { - new object[] { $"Line1{Environment.NewLine}Line2{Environment.NewLine}Line3{Environment.NewLine}", 60, new string [] { "Line1", "Line2", "Line3" } } - }; - - [Theory] - [MemberData (nameof (SplitEnvironmentNewLine))] - public void SplitNewLine_Ending__With_Or_Without_NewLine_Probably_CRLF (string text, IEnumerable expected) - { - var splited = TextFormatter.SplitNewLine (text); - Assert.Equal (expected, splited); - } + [Theory] + [InlineData ("test", 3, 3)] + [InlineData ("test", 4, 4)] + [InlineData ("test", 10, 4)] + [InlineData ("test", 1, 1)] + [InlineData ("test", 0, 0)] + [InlineData ("test", -1, 0)] + [InlineData (null, -1, 0)] + [InlineData ("", -1, 0)] + public void GetLengthThatFits_String (string text, int columns, int expectedLength) => Assert.Equal (expectedLength, TextFormatter.GetLengthThatFits (text, columns)); + + [Theory] + [InlineData ("Hello World", 6, 6)] + [InlineData ("こんにちは 世界", 6, 3)] + public void GetLengthThatFits_Simple_And_Wide_Runes (string text, int columns, int expectedLength) => Assert.Equal (expectedLength, TextFormatter.GetLengthThatFits (text, columns)); + + [Theory] + [InlineData ("Hello World", 6, 6)] + [InlineData ("こんにちは 世界", 6, 3)] + [MemberData (nameof (CMGlyphs))] + public void GetLengthThatFits_List_Simple_And_Wide_Runes (string text, int columns, int expectedLength) + { + var runes = text.ToRuneList (); + Assert.Equal (expectedLength, TextFormatter.GetLengthThatFits (runes, columns)); + } - public static IEnumerable SplitEnvironmentNewLine => - new List - { - new object[] { $"First Line 界{Environment.NewLine}Second Line 界{Environment.NewLine}Third Line 界", new string [] { "First Line 界", "Second Line 界", "Third Line 界" } }, - new object[] { $"First Line 界{Environment.NewLine}Second Line 界{Environment.NewLine}Third Line 界{Environment.NewLine}", new string [] { "First Line 界", "Second Line 界", "Third Line 界", "" } } - }; + [Theory] + [InlineData ("Truncate", 3, "Tru")] + [InlineData ("デモエムポンズ", 3, "デ")] + public void Format_Truncate_Simple_And_Wide_Runes (string text, int width, string expected) + { + var list = TextFormatter.Format (text, width, false, false); + Assert.Equal (expected, list [^1]); + } - [Theory] - [InlineData ($"First Line 界\nSecond Line 界\nThird Line 界", new string [] { "First Line 界", "Second Line 界", "Third Line 界" })] - public void SplitNewLine_Ending_Without_NewLine_Only_LF (string text, IEnumerable expected) - { - var splited = TextFormatter.SplitNewLine (text); - Assert.Equal (expected, splited); - } + [Theory] + [MemberData (nameof (FormatEnvironmentNewLine))] + public void Format_With_PreserveTrailingSpaces_And_Without_PreserveTrailingSpaces (string text, int width, IEnumerable expected) + { + var preserveTrailingSpaces = false; + var formated = TextFormatter.Format (text, width, false, true, preserveTrailingSpaces); + Assert.Equal (expected, formated); + + preserveTrailingSpaces = true; + formated = TextFormatter.Format (text, width, false, true, preserveTrailingSpaces); + Assert.Equal (expected, formated); + } - [Theory] - [InlineData ($"First Line 界\nSecond Line 界\nThird Line 界\n", new string [] { "First Line 界", "Second Line 界", "Third Line 界", "" })] - public void SplitNewLine_Ending_With_NewLine_Only_LF (string text, IEnumerable expected) - { - var splited = TextFormatter.SplitNewLine (text); - Assert.Equal (expected, splited); - } + [Theory] + [MemberData (nameof (SplitEnvironmentNewLine))] + public void SplitNewLine_Ending__With_Or_Without_NewLine_Probably_CRLF (string text, IEnumerable expected) + { + var splited = TextFormatter.SplitNewLine (text); + Assert.Equal (expected, splited); + } - [Theory] - [InlineData ("Single Line 界", 14)] - [InlineData ($"First Line 界\nSecond Line 界\nThird Line 界\n", 14)] - public void MaxWidthLine_With_And_Without_Newlines (string text, int expected) - { - Assert.Equal (expected, TextFormatter.MaxWidthLine (text)); - } + [Theory] + [InlineData ("First Line 界\nSecond Line 界\nThird Line 界", new [] { "First Line 界", "Second Line 界", "Third Line 界" })] + public void SplitNewLine_Ending_Without_NewLine_Only_LF (string text, IEnumerable expected) + { + var splited = TextFormatter.SplitNewLine (text); + Assert.Equal (expected, splited); + } - [Theory] - [InlineData ("New Test 你", 10, 10, 20320, 20320, 9, "你")] - [InlineData ("New Test \U0001d539", 10, 11, 120121, 55349, 9, "𝔹")] - public void String_Array_Is_Not_Always_Equal_ToRunes_Array (string text, int runesLength, int stringLength, int runeValue, int stringValue, int index, string expected) - { - var usToRunes = text.ToRunes (); - Assert.Equal (runesLength, usToRunes.Length); - Assert.Equal (stringLength, text.Length); - Assert.Equal (runeValue, usToRunes [index].Value); - Assert.Equal (stringValue, text [index]); - Assert.Equal (expected, usToRunes [index].ToString ()); - if (char.IsHighSurrogate (text [index])) { - // Rune array length isn't equal to string array - Assert.Equal (expected, new string (new char [] { text [index], text [index + 1] })); - } else { - // Rune array length is equal to string array - Assert.Equal (expected, text [index].ToString ()); - } - } + [Theory] + [InlineData ("First Line 界\nSecond Line 界\nThird Line 界\n", new [] { "First Line 界", "Second Line 界", "Third Line 界", "" })] + public void SplitNewLine_Ending_With_NewLine_Only_LF (string text, IEnumerable expected) + { + var splited = TextFormatter.SplitNewLine (text); + Assert.Equal (expected, splited); + } - [Fact] - public void GetLengthThatFits_With_Combining_Runes () - { - var text = "Les Mise\u0328\u0301rables"; - Assert.Equal (16, TextFormatter.GetLengthThatFits (text, 14)); + [Theory] + [InlineData ("Single Line 界", 14)] + [InlineData ("First Line 界\nSecond Line 界\nThird Line 界\n", 14)] + public void MaxWidthLine_With_And_Without_Newlines (string text, int expected) => Assert.Equal (expected, TextFormatter.MaxWidthLine (text)); + + [Theory] + [InlineData ("New Test 你", 10, 10, 20320, 20320, 9, "你")] + [InlineData ("New Test \U0001d539", 10, 11, 120121, 55349, 9, "𝔹")] + public void String_Array_Is_Not_Always_Equal_ToRunes_Array (string text, int runesLength, int stringLength, int runeValue, int stringValue, int index, string expected) + { + var usToRunes = text.ToRunes (); + Assert.Equal (runesLength, usToRunes.Length); + Assert.Equal (stringLength, text.Length); + Assert.Equal (runeValue, usToRunes [index].Value); + Assert.Equal (stringValue, text [index]); + Assert.Equal (expected, usToRunes [index].ToString ()); + if (char.IsHighSurrogate (text [index])) { + // Rune array length isn't equal to string array + Assert.Equal (expected, new string (new [] { text [index], text [index + 1] })); + } else { + // Rune array length is equal to string array + Assert.Equal (expected, text [index].ToString ()); } + } - [Fact] - public void GetMaxColsForWidth_With_Combining_Runes () - { - var text = new List () { "Les Mis", "e\u0328\u0301", "rables" }; - Assert.Equal (1, TextFormatter.GetMaxColsForWidth (text, 1)); - } + [Fact] + public void GetLengthThatFits_With_Combining_Runes () + { + var text = "Les Mise\u0328\u0301rables"; + Assert.Equal (16, TextFormatter.GetLengthThatFits (text, 14)); + } - [Fact] - public void GetSumMaxCharWidth_With_Combining_Runes () - { - var text = "Les Mise\u0328\u0301rables"; - Assert.Equal (1, TextFormatter.GetSumMaxCharWidth (text, 1, 1)); - } + [Fact] + public void GetMaxColsForWidth_With_Combining_Runes () + { + var text = new List { "Les Mis", "e\u0328\u0301", "rables" }; + Assert.Equal (1, TextFormatter.GetMaxColsForWidth (text, 1)); + } - [Fact] - public void GetSumMaxCharWidth_List_With_Combining_Runes () - { - var text = new List () { "Les Mis", "e\u0328\u0301", "rables" }; - Assert.Equal (1, TextFormatter.GetSumMaxCharWidth (text, 1, 1)); - } + [Fact] + public void GetSumMaxCharWidth_With_Combining_Runes () + { + var text = "Les Mise\u0328\u0301rables"; + Assert.Equal (1, TextFormatter.GetSumMaxCharWidth (text, 1, 1)); + } - [Theory] - [InlineData (14, 1, TextDirection.LeftRight_TopBottom)] - [InlineData (1, 14, TextDirection.TopBottom_LeftRight)] - public void CalcRect_With_Combining_Runes (int width, int height, TextDirection textDirection) - { - var text = "Les Mise\u0328\u0301rables"; - Assert.Equal (new Rect (0, 0, width, height), TextFormatter.CalcRect (0, 0, text, textDirection)); - } + [Fact] + public void GetSumMaxCharWidth_List_With_Combining_Runes () + { + var text = new List { "Les Mis", "e\u0328\u0301", "rables" }; + Assert.Equal (1, TextFormatter.GetSumMaxCharWidth (text, 1, 1)); + } + + [Theory] + [InlineData (14, 1, TextDirection.LeftRight_TopBottom)] + [InlineData (1, 14, TextDirection.TopBottom_LeftRight)] + public void CalcRect_With_Combining_Runes (int width, int height, TextDirection textDirection) + { + var text = "Les Mise\u0328\u0301rables"; + Assert.Equal (new Rect (0, 0, width, height), TextFormatter.CalcRect (0, 0, text, textDirection)); + } - [Theory] - [InlineData (14, 1, TextDirection.LeftRight_TopBottom, "Les Misęrables")] - [InlineData (1, 14, TextDirection.TopBottom_LeftRight, "L\ne\ns\n \nM\ni\ns\nę\nr\na\nb\nl\ne\ns")] - [InlineData (4, 4, TextDirection.TopBottom_LeftRight, @" + [Theory] + [InlineData (14, 1, TextDirection.LeftRight_TopBottom, "Les Misęrables")] + [InlineData (1, 14, TextDirection.TopBottom_LeftRight, "L\ne\ns\n \nM\ni\ns\nę\nr\na\nb\nl\ne\ns")] + [InlineData (4, 4, TextDirection.TopBottom_LeftRight, @" LMre eias ssb ęl ")] - public void Draw_With_Combining_Runes (int width, int height, TextDirection textDirection, string expected) - { - var driver = new FakeDriver (); - driver.Init (); + public void Draw_With_Combining_Runes (int width, int height, TextDirection textDirection, string expected) + { + var driver = new FakeDriver (); + driver.Init (); - var text = "Les Mise\u0328\u0301rables"; + var text = "Les Mise\u0328\u0301rables"; - var tf = new TextFormatter (); - tf.Direction = textDirection; - tf.Text = text; - - Assert.True (tf.WordWrap); - if (textDirection == TextDirection.LeftRight_TopBottom) { - Assert.Equal (new Size (width, height), tf.Size); - } else { - Assert.Equal (new Size (1, text.GetColumns ()), tf.Size); - tf.Size = new Size (width, height); - } - tf.Draw (new Rect (0, 0, width, height), new Attribute (ColorName.White, ColorName.Black), new Attribute (ColorName.Blue, ColorName.Black), default, true, driver); - TestHelpers.AssertDriverContentsWithFrameAre (expected, output, driver); - - driver.End (); - } + var tf = new TextFormatter (); + tf.Direction = textDirection; + tf.Text = text; - [Theory] - [InlineData (17, 1, TextDirection.LeftRight_TopBottom, 4, "This is a Tab")] - [InlineData (1, 17, TextDirection.TopBottom_LeftRight, 4, "T\nh\ni\ns\n \ni\ns\n \na\n \n \n \n \n \nT\na\nb")] - [InlineData (13, 1, TextDirection.LeftRight_TopBottom, 0, "This is a Tab")] - [InlineData (1, 13, TextDirection.TopBottom_LeftRight, 0, "T\nh\ni\ns\n \ni\ns\n \na\n \nT\na\nb")] - public void TabWith_PreserveTrailingSpaces_False (int width, int height, TextDirection textDirection, int tabWidth, string expected) - { - var driver = new FakeDriver (); - driver.Init (); - - var text = "This is a \tTab"; - var tf = new TextFormatter (); - tf.Direction = textDirection; - tf.TabWidth = tabWidth; - tf.Text = text; - - Assert.True (tf.WordWrap); - Assert.False (tf.PreserveTrailingSpaces); + Assert.True (tf.WordWrap); + if (textDirection == TextDirection.LeftRight_TopBottom) { Assert.Equal (new Size (width, height), tf.Size); - tf.Draw (new Rect (0, 0, width, height), new Attribute (ColorName.White, ColorName.Black), new Attribute (ColorName.Blue, ColorName.Black), default, true, driver); - TestHelpers.AssertDriverContentsWithFrameAre (expected, output, driver); - - driver.End (); + } else { + Assert.Equal (new Size (1, text.GetColumns ()), tf.Size); + tf.Size = new Size (width, height); } + tf.Draw (new Rect (0, 0, width, height), new Attribute (ColorName.White, ColorName.Black), new Attribute (ColorName.Blue, ColorName.Black), default, true, driver); + TestHelpers.AssertDriverContentsWithFrameAre (expected, _output, driver); - [Theory] - [InlineData (17, 1, TextDirection.LeftRight_TopBottom, 4, "This is a Tab")] - [InlineData (1, 17, TextDirection.TopBottom_LeftRight, 4, "T\nh\ni\ns\n \ni\ns\n \na\n \n \n \n \n \nT\na\nb")] - [InlineData (13, 1, TextDirection.LeftRight_TopBottom, 0, "This is a Tab")] - [InlineData (1, 13, TextDirection.TopBottom_LeftRight, 0, "T\nh\ni\ns\n \ni\ns\n \na\n \nT\na\nb")] - public void TabWith_PreserveTrailingSpaces_True (int width, int height, TextDirection textDirection, int tabWidth, string expected) - { - var driver = new FakeDriver (); - driver.Init (); - - var text = "This is a \tTab"; - var tf = new TextFormatter (); - tf.Direction = textDirection; - tf.TabWidth = tabWidth; - tf.PreserveTrailingSpaces = true; - tf.Text = text; - - Assert.True (tf.WordWrap); - Assert.Equal (new Size (width, height), tf.Size); - tf.Draw (new Rect (0, 0, width, height), new Attribute (ColorName.White, ColorName.Black), new Attribute (ColorName.Blue, ColorName.Black), default, true, driver); - TestHelpers.AssertDriverContentsWithFrameAre (expected, output, driver); + driver.End (); + } - driver.End (); - } + [Theory] + [InlineData (17, 1, TextDirection.LeftRight_TopBottom, 4, "This is a Tab")] + [InlineData (1, 17, TextDirection.TopBottom_LeftRight, 4, "T\nh\ni\ns\n \ni\ns\n \na\n \n \n \n \n \nT\na\nb")] + [InlineData (13, 1, TextDirection.LeftRight_TopBottom, 0, "This is a Tab")] + [InlineData (1, 13, TextDirection.TopBottom_LeftRight, 0, "T\nh\ni\ns\n \ni\ns\n \na\n \nT\na\nb")] + public void TabWith_PreserveTrailingSpaces_False (int width, int height, TextDirection textDirection, int tabWidth, string expected) + { + var driver = new FakeDriver (); + driver.Init (); + + var text = "This is a \tTab"; + var tf = new TextFormatter (); + tf.Direction = textDirection; + tf.TabWidth = tabWidth; + tf.Text = text; + + Assert.True (tf.WordWrap); + Assert.False (tf.PreserveTrailingSpaces); + Assert.Equal (new Size (width, height), tf.Size); + tf.Draw (new Rect (0, 0, width, height), new Attribute (ColorName.White, ColorName.Black), new Attribute (ColorName.Blue, ColorName.Black), default, true, driver); + TestHelpers.AssertDriverContentsWithFrameAre (expected, _output, driver); + + driver.End (); + } - [Theory] - [InlineData (17, 1, TextDirection.LeftRight_TopBottom, 4, "This is a Tab")] - [InlineData (1, 17, TextDirection.TopBottom_LeftRight, 4, "T\nh\ni\ns\n \ni\ns\n \na\n \n \n \n \n \nT\na\nb")] - [InlineData (13, 1, TextDirection.LeftRight_TopBottom, 0, "This is a Tab")] - [InlineData (1, 13, TextDirection.TopBottom_LeftRight, 0, "T\nh\ni\ns\n \ni\ns\n \na\n \nT\na\nb")] - public void TabWith_WordWrap_True (int width, int height, TextDirection textDirection, int tabWidth, string expected) - { - var driver = new FakeDriver (); - driver.Init (); - - var text = "This is a \tTab"; - var tf = new TextFormatter (); - tf.Direction = textDirection; - tf.TabWidth = tabWidth; - tf.WordWrap = true; - tf.Text = text; - - Assert.False (tf.PreserveTrailingSpaces); - Assert.Equal (new Size (width, height), tf.Size); - tf.Draw (new Rect (0, 0, width, height), new Attribute (ColorName.White, ColorName.Black), new Attribute (ColorName.Blue, ColorName.Black), default, true, driver); - TestHelpers.AssertDriverContentsWithFrameAre (expected, output, driver); + [Theory] + [InlineData (17, 1, TextDirection.LeftRight_TopBottom, 4, "This is a Tab")] + [InlineData (1, 17, TextDirection.TopBottom_LeftRight, 4, "T\nh\ni\ns\n \ni\ns\n \na\n \n \n \n \n \nT\na\nb")] + [InlineData (13, 1, TextDirection.LeftRight_TopBottom, 0, "This is a Tab")] + [InlineData (1, 13, TextDirection.TopBottom_LeftRight, 0, "T\nh\ni\ns\n \ni\ns\n \na\n \nT\na\nb")] + public void TabWith_PreserveTrailingSpaces_True (int width, int height, TextDirection textDirection, int tabWidth, string expected) + { + var driver = new FakeDriver (); + driver.Init (); + + var text = "This is a \tTab"; + var tf = new TextFormatter (); + tf.Direction = textDirection; + tf.TabWidth = tabWidth; + tf.PreserveTrailingSpaces = true; + tf.Text = text; + + Assert.True (tf.WordWrap); + Assert.Equal (new Size (width, height), tf.Size); + tf.Draw (new Rect (0, 0, width, height), new Attribute (ColorName.White, ColorName.Black), new Attribute (ColorName.Blue, ColorName.Black), default, true, driver); + TestHelpers.AssertDriverContentsWithFrameAre (expected, _output, driver); + + driver.End (); + } - driver.End (); - } + [Theory] + [InlineData (17, 1, TextDirection.LeftRight_TopBottom, 4, "This is a Tab")] + [InlineData (1, 17, TextDirection.TopBottom_LeftRight, 4, "T\nh\ni\ns\n \ni\ns\n \na\n \n \n \n \n \nT\na\nb")] + [InlineData (13, 1, TextDirection.LeftRight_TopBottom, 0, "This is a Tab")] + [InlineData (1, 13, TextDirection.TopBottom_LeftRight, 0, "T\nh\ni\ns\n \ni\ns\n \na\n \nT\na\nb")] + public void TabWith_WordWrap_True (int width, int height, TextDirection textDirection, int tabWidth, string expected) + { + var driver = new FakeDriver (); + driver.Init (); + + var text = "This is a \tTab"; + var tf = new TextFormatter (); + tf.Direction = textDirection; + tf.TabWidth = tabWidth; + tf.WordWrap = true; + tf.Text = text; + + Assert.False (tf.PreserveTrailingSpaces); + Assert.Equal (new Size (width, height), tf.Size); + tf.Draw (new Rect (0, 0, width, height), new Attribute (ColorName.White, ColorName.Black), new Attribute (ColorName.Blue, ColorName.Black), default, true, driver); + TestHelpers.AssertDriverContentsWithFrameAre (expected, _output, driver); + + driver.End (); } } \ No newline at end of file diff --git a/UnitTests/UICatalog/ScenarioTests.cs b/UnitTests/UICatalog/ScenarioTests.cs index b17924acd8..8fd5fc9d08 100644 --- a/UnitTests/UICatalog/ScenarioTests.cs +++ b/UnitTests/UICatalog/ScenarioTests.cs @@ -1,594 +1,577 @@ -using System.Text; using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Reflection; using Terminal.Gui; -using UICatalog; -using UICatalog.Scenarios; using Xunit; using Xunit.Abstractions; -// Alias Console to MockConsole so we don't accidentally use Console -using Console = Terminal.Gui.FakeConsole; +namespace UICatalog.Tests; -namespace UICatalog.Tests { - public class ScenarioTests { - readonly ITestOutputHelper output; +public class ScenarioTests { + readonly ITestOutputHelper _output; - public ScenarioTests (ITestOutputHelper output) - { + public ScenarioTests (ITestOutputHelper output) + { #if DEBUG_IDISPOSABLE - Responder.Instances.Clear (); + Responder.Instances.Clear (); #endif - this.output = output; - } - - int CreateInput (string input) - { - FakeConsole.MockKeyPresses.Clear (); - // Put a QuitKey in at the end - FakeConsole.PushMockKeyPress ((KeyCode)Application.QuitKey); - foreach (var c in input.Reverse ()) { - KeyCode key = KeyCode.Null; - if (char.IsLetter (c)) { - key = (KeyCode)char.ToUpper (c) | (char.IsUpper (c) ? KeyCode.ShiftMask : (KeyCode)0); - } else { - key = (KeyCode)c; - } - FakeConsole.PushMockKeyPress (key); - } - return FakeConsole.MockKeyPresses.Count; - } - - /// - /// - /// This runs through all Scenarios defined in UI Catalog, calling Init, Setup, and Run. - /// - /// - /// Should find any Scenarios which crash on load or do not respond to . - /// - /// - [Fact] - public void Run_All_Scenarios () - { - List scenarios = Scenario.GetScenarios (); - Assert.NotEmpty (scenarios); - - foreach (var scenario in scenarios) { - output.WriteLine ($"Running Scenario '{scenario.GetName ()}'"); - - Application.Init (new FakeDriver ()); + _output = output; + } - // Press QuitKey - Assert.Empty (FakeConsole.MockKeyPresses); - // BUGBUG: (#2474) For some reason ReadKey is not returning the QuitKey for some Scenarios - // by adding this Space it seems to work. - //FakeConsole.PushMockKeyPress (Key.Space); - FakeConsole.PushMockKeyPress ((KeyCode)Application.QuitKey); - - // The only key we care about is the QuitKey - Application.Top.KeyDown += (object sender, Key args) => { - output.WriteLine ($" Keypress: {args.KeyCode}"); - // BUGBUG: (#2474) For some reason ReadKey is not returning the QuitKey for some Scenarios - // by adding this Space it seems to work. - // See #2474 for why this is commented out - Assert.Equal (Application.QuitKey.KeyCode, args.KeyCode); - }; - - uint abortTime = 500; - // If the scenario doesn't close within 500ms, this will force it to quit - Func forceCloseCallback = () => { - if (Application.Top.Running && FakeConsole.MockKeyPresses.Count == 0) { - Application.RequestStop (); - // See #2474 for why this is commented out - Assert.Fail ($"'{scenario.GetName ()}' failed to Quit with {Application.QuitKey} after {abortTime}ms. Force quit."); - } - return false; - }; - //output.WriteLine ($" Add timeout to force quit after {abortTime}ms"); - _ = Application.AddTimeout (TimeSpan.FromMilliseconds (abortTime), forceCloseCallback); - - Application.Iteration += (s, a) => { - //output.WriteLine ($" iteration {++iterations}"); - if (Application.Top.Running && FakeConsole.MockKeyPresses.Count == 0) { - Application.RequestStop (); - Assert.Fail ($"'{scenario.GetName ()}' failed to Quit with {Application.QuitKey}. Force quit."); - } - }; - - scenario.Init (); - scenario.Setup (); - scenario.Run (); - scenario.Dispose (); - - Application.Shutdown (); -#if DEBUG_IDISPOSABLE - Assert.Empty (Responder.Instances); -#endif + int CreateInput (string input) + { + FakeConsole.MockKeyPresses.Clear (); + // Put a QuitKey in at the end + FakeConsole.PushMockKeyPress ((KeyCode)Application.QuitKey); + foreach (var c in input.Reverse ()) { + var key = KeyCode.Null; + if (char.IsLetter (c)) { + key = (KeyCode)char.ToUpper (c) | (char.IsUpper (c) ? KeyCode.ShiftMask : 0); + } else { + key = (KeyCode)c; } -#if DEBUG_IDISPOSABLE - Assert.Empty (Responder.Instances); -#endif + FakeConsole.PushMockKeyPress (key); } + return FakeConsole.MockKeyPresses.Count; + } - [Fact] - public void Run_Generic () - { - List scenarios = Scenario.GetScenarios (); - Assert.NotEmpty (scenarios); - - var item = scenarios.FindIndex (s => s.GetName ().Equals ("Generic", StringComparison.OrdinalIgnoreCase)); - var generic = scenarios [item]; + /// + /// + /// This runs through all Scenarios defined in UI Catalog, calling Init, Setup, and Run. + /// + /// + /// Should find any Scenarios which crash on load or do not respond to . + /// + /// + [Fact] + public void Run_All_Scenarios () + { + var scenarios = Scenario.GetScenarios (); + Assert.NotEmpty (scenarios); + + foreach (var scenario in scenarios) { + _output.WriteLine ($"Running Scenario '{scenario.GetName ()}'"); Application.Init (new FakeDriver ()); + + // Press QuitKey + Assert.Empty (FakeConsole.MockKeyPresses); // BUGBUG: (#2474) For some reason ReadKey is not returning the QuitKey for some Scenarios // by adding this Space it seems to work. - + //FakeConsole.PushMockKeyPress (Key.Space); FakeConsole.PushMockKeyPress ((KeyCode)Application.QuitKey); - var ms = 100; - var abortCount = 0; - Func abortCallback = () => { - abortCount++; - output.WriteLine ($"'Generic' abortCount {abortCount}"); - Application.RequestStop (); - return false; + // The only key we care about is the QuitKey + Application.Top.KeyDown += (sender, args) => { + _output.WriteLine ($" Keypress: {args.KeyCode}"); + // BUGBUG: (#2474) For some reason ReadKey is not returning the QuitKey for some Scenarios + // by adding this Space it seems to work. + // See #2474 for why this is commented out + Assert.Equal (Application.QuitKey.KeyCode, args.KeyCode); }; - int iterations = 0; - object token = null; - Application.Iteration += (s, a) => { - if (token == null) { - // Timeout only must start at first iteration - token = Application.MainLoop.AddTimeout (TimeSpan.FromMilliseconds (ms), abortCallback); - } - iterations++; - output.WriteLine ($"'Generic' iteration {iterations}"); - // Stop if we run out of control... - if (iterations == 10) { - output.WriteLine ($"'Generic' had to be force quit!"); + uint abortTime = 500; + // If the scenario doesn't close within 500ms, this will force it to quit + var forceCloseCallback = () => { + if (Application.Top.Running && FakeConsole.MockKeyPresses.Count == 0) { Application.RequestStop (); + // See #2474 for why this is commented out + Assert.Fail ($"'{scenario.GetName ()}' failed to Quit with {Application.QuitKey} after {abortTime}ms. Force quit."); } + return false; }; + //output.WriteLine ($" Add timeout to force quit after {abortTime}ms"); + _ = Application.AddTimeout (TimeSpan.FromMilliseconds (abortTime), forceCloseCallback); - Application.Top.KeyDown += (object sender, Key args) => { - // See #2474 for why this is commented out - Assert.Equal (KeyCode.CtrlMask | KeyCode.Q, args.KeyCode); + Application.Iteration += (s, a) => { + //output.WriteLine ($" iteration {++iterations}"); + if (Application.Top.Running && FakeConsole.MockKeyPresses.Count == 0) { + Application.RequestStop (); + Assert.Fail ($"'{scenario.GetName ()}' failed to Quit with {Application.QuitKey}. Force quit."); + } }; - generic.Init (); - generic.Setup (); - generic.Run (); - - Assert.Equal (0, abortCount); - // # of key up events should match # of iterations - Assert.Equal (1, iterations); + scenario.Init (); + scenario.Setup (); + scenario.Run (); + scenario.Dispose (); - generic.Dispose (); - - // Shutdown must be called to safely clean up Application if Init has been called Application.Shutdown (); - #if DEBUG_IDISPOSABLE Assert.Empty (Responder.Instances); #endif } +#if DEBUG_IDISPOSABLE + Assert.Empty (Responder.Instances); +#endif + } - [Fact] - public void Run_All_Views_Tester_Scenario () - { - Window _leftPane; - ListView _classListView; - FrameView _hostPane; - - Dictionary _viewClasses; - View _curView = null; - - // Settings - FrameView _settingsPane; - CheckBox _computedCheckBox; - FrameView _locationFrame; - RadioGroup _xRadioGroup; - TextField _xText; - int _xVal = 0; - RadioGroup _yRadioGroup; - TextField _yText; - int _yVal = 0; - - FrameView _sizeFrame; - RadioGroup _wRadioGroup; - TextField _wText; - int _wVal = 0; - RadioGroup _hRadioGroup; - TextField _hText; - int _hVal = 0; - List posNames = new List { "Factor", "AnchorEnd", "Center", "Absolute" }; - List dimNames = new List { "Factor", "Fill", "Absolute" }; - - Application.Init (new FakeDriver ()); + [Fact] + public void Run_Generic () + { + var scenarios = Scenario.GetScenarios (); + Assert.NotEmpty (scenarios); + + var item = scenarios.FindIndex (s => s.GetName ().Equals ("Generic", StringComparison.OrdinalIgnoreCase)); + var generic = scenarios [item]; + + Application.Init (new FakeDriver ()); + // BUGBUG: (#2474) For some reason ReadKey is not returning the QuitKey for some Scenarios + // by adding this Space it seems to work. + + FakeConsole.PushMockKeyPress ((KeyCode)Application.QuitKey); + + var ms = 100; + var abortCount = 0; + var abortCallback = () => { + abortCount++; + _output.WriteLine ($"'Generic' abortCount {abortCount}"); + Application.RequestStop (); + return false; + }; + + var iterations = 0; + object token = null; + Application.Iteration += (s, a) => { + if (token == null) { + // Timeout only must start at first iteration + token = Application.MainLoop.AddTimeout (TimeSpan.FromMilliseconds (ms), abortCallback); + } + iterations++; + _output.WriteLine ($"'Generic' iteration {iterations}"); + // Stop if we run out of control... + if (iterations == 10) { + _output.WriteLine ("'Generic' had to be force quit!"); + Application.RequestStop (); + } + }; - var Top = Application.Top; - - _viewClasses = GetAllViewClassesCollection () - .OrderBy (t => t.Name) - .Select (t => new KeyValuePair (t.Name, t)) - .ToDictionary (t => t.Key, t => t.Value); - - _leftPane = new Window () { - Title = "Classes", - X = 0, - Y = 0, - Width = 15, - Height = Dim.Fill (1), // for status bar - CanFocus = false, - ColorScheme = Colors.TopLevel, - }; + Application.Top.KeyDown += (sender, args) => { + // See #2474 for why this is commented out + Assert.Equal (KeyCode.CtrlMask | KeyCode.Q, args.KeyCode); + }; - _classListView = new ListView (_viewClasses.Keys.ToList ()) { - X = 0, - Y = 0, - Width = Dim.Fill (0), - Height = Dim.Fill (0), - AllowsMarking = false, - ColorScheme = Colors.TopLevel, - }; - _leftPane.Add (_classListView); - - _settingsPane = new FrameView ("Settings") { - X = Pos.Right (_leftPane), - Y = 0, // for menu - Width = Dim.Fill (), - Height = 10, - CanFocus = false, - ColorScheme = Colors.TopLevel, - }; - _computedCheckBox = new CheckBox ("Computed Layout", true) { X = 0, Y = 0 }; - _settingsPane.Add (_computedCheckBox); - - var radioItems = new string [] { "Percent(x)", "AnchorEnd(x)", "Center", "At(x)" }; - _locationFrame = new FrameView ("Location (Pos)") { - X = Pos.Left (_computedCheckBox), - Y = Pos.Bottom (_computedCheckBox), - Height = 3 + radioItems.Length, - Width = 36, - }; - _settingsPane.Add (_locationFrame); + generic.Init (); + generic.Setup (); + generic.Run (); - var label = new Label ("x:") { X = 0, Y = 0 }; - _locationFrame.Add (label); - _xRadioGroup = new RadioGroup (radioItems) { - X = 0, - Y = Pos.Bottom (label), - }; - _xText = new TextField ($"{_xVal}") { X = Pos.Right (label) + 1, Y = 0, Width = 4 }; - _locationFrame.Add (_xText); - - _locationFrame.Add (_xRadioGroup); - - radioItems = new string [] { "Percent(y)", "AnchorEnd(y)", "Center", "At(y)" }; - label = new Label ("y:") { X = Pos.Right (_xRadioGroup) + 1, Y = 0 }; - _locationFrame.Add (label); - _yText = new TextField ($"{_yVal}") { X = Pos.Right (label) + 1, Y = 0, Width = 4 }; - _locationFrame.Add (_yText); - _yRadioGroup = new RadioGroup (radioItems) { - X = Pos.X (label), - Y = Pos.Bottom (label), - }; - _locationFrame.Add (_yRadioGroup); + Assert.Equal (0, abortCount); + // # of key up events should match # of iterations + Assert.Equal (1, iterations); - _sizeFrame = new FrameView ("Size (Dim)") { - X = Pos.Right (_locationFrame), - Y = Pos.Y (_locationFrame), - Height = 3 + radioItems.Length, - Width = 40, - }; + generic.Dispose (); - radioItems = new string [] { "Percent(width)", "Fill(width)", "Sized(width)" }; - label = new Label ("width:") { X = 0, Y = 0 }; - _sizeFrame.Add (label); - _wRadioGroup = new RadioGroup (radioItems) { - X = 0, - Y = Pos.Bottom (label), - }; - _wText = new TextField ($"{_wVal}") { X = Pos.Right (label) + 1, Y = 0, Width = 4 }; - _sizeFrame.Add (_wText); - _sizeFrame.Add (_wRadioGroup); - - radioItems = new string [] { "Percent(height)", "Fill(height)", "Sized(height)" }; - label = new Label ("height:") { X = Pos.Right (_wRadioGroup) + 1, Y = 0 }; - _sizeFrame.Add (label); - _hText = new TextField ($"{_hVal}") { X = Pos.Right (label) + 1, Y = 0, Width = 4 }; - _sizeFrame.Add (_hText); - - _hRadioGroup = new RadioGroup (radioItems) { - X = Pos.X (label), - Y = Pos.Bottom (label), - }; - _sizeFrame.Add (_hRadioGroup); + // Shutdown must be called to safely clean up Application if Init has been called + Application.Shutdown (); - _settingsPane.Add (_sizeFrame); +#if DEBUG_IDISPOSABLE + Assert.Empty (Responder.Instances); +#endif + } - _hostPane = new FrameView ("") { - X = Pos.Right (_leftPane), - Y = Pos.Bottom (_settingsPane), - Width = Dim.Fill (), - Height = Dim.Fill (1), // + 1 for status bar - ColorScheme = Colors.Dialog, - }; + [Fact] + public void Run_All_Views_Tester_Scenario () + { + Window _leftPane; + ListView _classListView; + FrameView _hostPane; + + Dictionary _viewClasses; + View _curView = null; + + // Settings + FrameView _settingsPane; + CheckBox _computedCheckBox; + FrameView _locationFrame; + RadioGroup _xRadioGroup; + TextField _xText; + var _xVal = 0; + RadioGroup _yRadioGroup; + TextField _yText; + var _yVal = 0; + + FrameView _sizeFrame; + RadioGroup _wRadioGroup; + TextField _wText; + var _wVal = 0; + RadioGroup _hRadioGroup; + TextField _hText; + var _hVal = 0; + var posNames = new List { "Factor", "AnchorEnd", "Center", "Absolute" }; + var dimNames = new List { "Factor", "Fill", "Absolute" }; + + Application.Init (new FakeDriver ()); + + var Top = Application.Top; + + _viewClasses = GetAllViewClassesCollection () + .OrderBy (t => t.Name) + .Select (t => new KeyValuePair (t.Name, t)) + .ToDictionary (t => t.Key, t => t.Value); + + _leftPane = new Window { + Title = "Classes", + X = 0, + Y = 0, + Width = 15, + Height = Dim.Fill (1), // for status bar + CanFocus = false, + ColorScheme = Colors.TopLevel + }; + + _classListView = new ListView (_viewClasses.Keys.ToList ()) { + X = 0, + Y = 0, + Width = Dim.Fill (), + Height = Dim.Fill (), + AllowsMarking = false, + ColorScheme = Colors.TopLevel + }; + _leftPane.Add (_classListView); + + _settingsPane = new FrameView ("Settings") { + X = Pos.Right (_leftPane), + Y = 0, // for menu + Width = Dim.Fill (), + Height = 10, + CanFocus = false, + ColorScheme = Colors.TopLevel + }; + _computedCheckBox = new CheckBox ("Computed Layout", true) { X = 0, Y = 0 }; + _settingsPane.Add (_computedCheckBox); + + var radioItems = new [] { "Percent(x)", "AnchorEnd(x)", "Center", "At(x)" }; + _locationFrame = new FrameView ("Location (Pos)") { + X = Pos.Left (_computedCheckBox), + Y = Pos.Bottom (_computedCheckBox), + Height = 3 + radioItems.Length, + Width = 36 + }; + _settingsPane.Add (_locationFrame); + + var label = new Label ("x:") { X = 0, Y = 0 }; + _locationFrame.Add (label); + _xRadioGroup = new RadioGroup (radioItems) { + X = 0, + Y = Pos.Bottom (label) + }; + _xText = new TextField ($"{_xVal}") { X = Pos.Right (label) + 1, Y = 0, Width = 4 }; + _locationFrame.Add (_xText); + + _locationFrame.Add (_xRadioGroup); + + radioItems = new [] { "Percent(y)", "AnchorEnd(y)", "Center", "At(y)" }; + label = new Label ("y:") { X = Pos.Right (_xRadioGroup) + 1, Y = 0 }; + _locationFrame.Add (label); + _yText = new TextField ($"{_yVal}") { X = Pos.Right (label) + 1, Y = 0, Width = 4 }; + _locationFrame.Add (_yText); + _yRadioGroup = new RadioGroup (radioItems) { + X = Pos.X (label), + Y = Pos.Bottom (label) + }; + _locationFrame.Add (_yRadioGroup); + + _sizeFrame = new FrameView ("Size (Dim)") { + X = Pos.Right (_locationFrame), + Y = Pos.Y (_locationFrame), + Height = 3 + radioItems.Length, + Width = 40 + }; + + radioItems = new [] { "Percent(width)", "Fill(width)", "Sized(width)" }; + label = new Label ("width:") { X = 0, Y = 0 }; + _sizeFrame.Add (label); + _wRadioGroup = new RadioGroup (radioItems) { + X = 0, + Y = Pos.Bottom (label) + }; + _wText = new TextField ($"{_wVal}") { X = Pos.Right (label) + 1, Y = 0, Width = 4 }; + _sizeFrame.Add (_wText); + _sizeFrame.Add (_wRadioGroup); + + radioItems = new [] { "Percent(height)", "Fill(height)", "Sized(height)" }; + label = new Label ("height:") { X = Pos.Right (_wRadioGroup) + 1, Y = 0 }; + _sizeFrame.Add (label); + _hText = new TextField ($"{_hVal}") { X = Pos.Right (label) + 1, Y = 0, Width = 4 }; + _sizeFrame.Add (_hText); + + _hRadioGroup = new RadioGroup (radioItems) { + X = Pos.X (label), + Y = Pos.Bottom (label) + }; + _sizeFrame.Add (_hRadioGroup); + + _settingsPane.Add (_sizeFrame); + + _hostPane = new FrameView ("") { + X = Pos.Right (_leftPane), + Y = Pos.Bottom (_settingsPane), + Width = Dim.Fill (), + Height = Dim.Fill (1), // + 1 for status bar + ColorScheme = Colors.Dialog + }; + + _classListView.OpenSelectedItem += (s, a) => { + _settingsPane.SetFocus (); + }; + _classListView.SelectedItemChanged += (s, args) => { + // Remove existing class, if any + if (_curView != null) { + _curView.LayoutComplete -= LayoutCompleteHandler; + _hostPane.Remove (_curView); + _curView.Dispose (); + _curView = null; + _hostPane.Clear (); + } + _curView = CreateClass (_viewClasses.Values.ToArray () [_classListView.SelectedItem]); + }; - _classListView.OpenSelectedItem += (s, a) => { - _settingsPane.SetFocus (); - }; - _classListView.SelectedItemChanged += (s, args) => { - // Remove existing class, if any - if (_curView != null) { - _curView.LayoutComplete -= LayoutCompleteHandler; - _hostPane.Remove (_curView); - _curView.Dispose (); - _curView = null; - _hostPane.Clear (); - } - _curView = CreateClass (_viewClasses.Values.ToArray () [_classListView.SelectedItem]); - }; + _computedCheckBox.Toggled += (s, e) => { + if (_curView != null) { + //_curView.LayoutStyle = e.OldValue == true ? LayoutStyle.Absolute : LayoutStyle.Computed; + _hostPane.LayoutSubviews (); + } + }; - _computedCheckBox.Toggled += (s, e) => { - if (_curView != null) { - _curView.LayoutStyle = e.OldValue == true ? LayoutStyle.Absolute : LayoutStyle.Computed; - _hostPane.LayoutSubviews (); - } - }; + _xRadioGroup.SelectedItemChanged += (s, selected) => DimPosChanged (_curView); - _xRadioGroup.SelectedItemChanged += (s, selected) => DimPosChanged (_curView); + _xText.TextChanged += (s, args) => { + try { + _xVal = int.Parse (_xText.Text); + DimPosChanged (_curView); + } catch { } + }; - _xText.TextChanged += (s, args) => { - try { - _xVal = int.Parse (_xText.Text); - DimPosChanged (_curView); - } catch { + _yText.TextChanged += (s, e) => { + try { + _yVal = int.Parse (_yText.Text); + DimPosChanged (_curView); + } catch { } + }; - } - }; + _yRadioGroup.SelectedItemChanged += (s, selected) => DimPosChanged (_curView); - _yText.TextChanged += (s, e) => { - try { - _yVal = int.Parse (_yText.Text); - DimPosChanged (_curView); - } catch { + _wRadioGroup.SelectedItemChanged += (s, selected) => DimPosChanged (_curView); - } - }; + _wText.TextChanged += (s, args) => { + try { + _wVal = int.Parse (_wText.Text); + DimPosChanged (_curView); + } catch { } + }; - _yRadioGroup.SelectedItemChanged += (s, selected) => DimPosChanged (_curView); + _hText.TextChanged += (s, args) => { + try { + _hVal = int.Parse (_hText.Text); + DimPosChanged (_curView); + } catch { } + }; - _wRadioGroup.SelectedItemChanged += (s, selected) => DimPosChanged (_curView); + _hRadioGroup.SelectedItemChanged += (s, selected) => DimPosChanged (_curView); - _wText.TextChanged += (s, args) => { - try { - _wVal = int.Parse (_wText.Text); - DimPosChanged (_curView); - } catch { + Top.Add (_leftPane, _settingsPane, _hostPane); - } - }; + Top.LayoutSubviews (); - _hText.TextChanged += (s, args) => { - try { - _hVal = int.Parse (_hText.Text); - DimPosChanged (_curView); - } catch { + _curView = CreateClass (_viewClasses.First ().Value); - } - }; + var iterations = 0; - _hRadioGroup.SelectedItemChanged += (s, selected) => DimPosChanged (_curView); + Application.Iteration += (s, a) => { + iterations++; - Top.Add (_leftPane, _settingsPane, _hostPane); + if (iterations < _viewClasses.Count) { + _classListView.MoveDown (); + Assert.Equal (_curView.GetType ().Name, + _viewClasses.Values.ToArray () [_classListView.SelectedItem].Name); + } else { + Application.RequestStop (); + } + }; - Top.LayoutSubviews (); + Application.Run (); - _curView = CreateClass (_viewClasses.First ().Value); + Assert.Equal (_viewClasses.Count, iterations); - int iterations = 0; + Application.Shutdown (); - Application.Iteration += (s, a) => { - iterations++; + void DimPosChanged (View view) + { + if (view == null) { + return; + } - if (iterations < _viewClasses.Count) { - _classListView.MoveDown (); - Assert.Equal (_curView.GetType ().Name, - _viewClasses.Values.ToArray () [_classListView.SelectedItem].Name); - } else { - Application.RequestStop (); + var layout = view.LayoutStyle; + + try { + //view.LayoutStyle = LayoutStyle.Absolute; + + switch (_xRadioGroup.SelectedItem) { + case 0: + view.X = Pos.Percent (_xVal); + break; + case 1: + view.X = Pos.AnchorEnd (_xVal); + break; + case 2: + view.X = Pos.Center (); + break; + case 3: + view.X = Pos.At (_xVal); + break; } - }; - - Application.Run (); - Assert.Equal (_viewClasses.Count, iterations); - - Application.Shutdown (); - - void DimPosChanged (View view) - { - if (view == null) { - return; + switch (_yRadioGroup.SelectedItem) { + case 0: + view.Y = Pos.Percent (_yVal); + break; + case 1: + view.Y = Pos.AnchorEnd (_yVal); + break; + case 2: + view.Y = Pos.Center (); + break; + case 3: + view.Y = Pos.At (_yVal); + break; } - var layout = view.LayoutStyle; + switch (_wRadioGroup.SelectedItem) { + case 0: + view.Width = Dim.Percent (_wVal); + break; + case 1: + view.Width = Dim.Fill (_wVal); + break; + case 2: + view.Width = Dim.Sized (_wVal); + break; + } - try { - view.LayoutStyle = LayoutStyle.Absolute; - - switch (_xRadioGroup.SelectedItem) { - case 0: - view.X = Pos.Percent (_xVal); - break; - case 1: - view.X = Pos.AnchorEnd (_xVal); - break; - case 2: - view.X = Pos.Center (); - break; - case 3: - view.X = Pos.At (_xVal); - break; - } - - switch (_yRadioGroup.SelectedItem) { - case 0: - view.Y = Pos.Percent (_yVal); - break; - case 1: - view.Y = Pos.AnchorEnd (_yVal); - break; - case 2: - view.Y = Pos.Center (); - break; - case 3: - view.Y = Pos.At (_yVal); - break; - } - - switch (_wRadioGroup.SelectedItem) { - case 0: - view.Width = Dim.Percent (_wVal); - break; - case 1: - view.Width = Dim.Fill (_wVal); - break; - case 2: - view.Width = Dim.Sized (_wVal); - break; - } - - switch (_hRadioGroup.SelectedItem) { - case 0: - view.Height = Dim.Percent (_hVal); - break; - case 1: - view.Height = Dim.Fill (_hVal); - break; - case 2: - view.Height = Dim.Sized (_hVal); - break; - } - } catch (Exception e) { - MessageBox.ErrorQuery ("Exception", e.Message, "Ok"); - } finally { - view.LayoutStyle = layout; + switch (_hRadioGroup.SelectedItem) { + case 0: + view.Height = Dim.Percent (_hVal); + break; + case 1: + view.Height = Dim.Fill (_hVal); + break; + case 2: + view.Height = Dim.Sized (_hVal); + break; } - UpdateTitle (view); + } catch (Exception e) { + MessageBox.ErrorQuery ("Exception", e.Message, "Ok"); } + UpdateTitle (view); + } - void UpdateSettings (View view) - { - var x = view.X.ToString (); - var y = view.Y.ToString (); - _xRadioGroup.SelectedItem = posNames.IndexOf (posNames.Where (s => x.Contains (s)).First ()); - _yRadioGroup.SelectedItem = posNames.IndexOf (posNames.Where (s => y.Contains (s)).First ()); - _xText.Text = $"{view.Frame.X}"; - _yText.Text = $"{view.Frame.Y}"; - - var w = view.Width.ToString (); - var h = view.Height.ToString (); - _wRadioGroup.SelectedItem = dimNames.IndexOf (dimNames.Where (s => w.Contains (s)).First ()); - _hRadioGroup.SelectedItem = dimNames.IndexOf (dimNames.Where (s => h.Contains (s)).First ()); - _wText.Text = $"{view.Frame.Width}"; - _hText.Text = $"{view.Frame.Height}"; - } + void UpdateSettings (View view) + { + var x = view.X.ToString (); + var y = view.Y.ToString (); + _xRadioGroup.SelectedItem = posNames.IndexOf (posNames.Where (s => x.Contains (s)).First ()); + _yRadioGroup.SelectedItem = posNames.IndexOf (posNames.Where (s => y.Contains (s)).First ()); + _xText.Text = $"{view.Frame.X}"; + _yText.Text = $"{view.Frame.Y}"; + + var w = view.Width.ToString (); + var h = view.Height.ToString (); + _wRadioGroup.SelectedItem = dimNames.IndexOf (dimNames.Where (s => w.Contains (s)).First ()); + _hRadioGroup.SelectedItem = dimNames.IndexOf (dimNames.Where (s => h.Contains (s)).First ()); + _wText.Text = $"{view.Frame.Width}"; + _hText.Text = $"{view.Frame.Height}"; + } - void UpdateTitle (View view) - { - _hostPane.Title = $"{view.GetType ().Name} - {view.X.ToString ()}, {view.Y.ToString ()}, {view.Width.ToString ()}, {view.Height.ToString ()}"; - } + void UpdateTitle (View view) + { + _hostPane.Title = $"{view.GetType ().Name} - {view.X}, {view.Y}, {view.Width}, {view.Height}"; + } - List GetAllViewClassesCollection () - { - List types = new List (); - foreach (Type type in typeof (View).Assembly.GetTypes () - .Where (myType => myType.IsClass && !myType.IsAbstract && myType.IsPublic && myType.IsSubclassOf (typeof (View)))) { - types.Add (type); - } - return types; + List GetAllViewClassesCollection () + { + var types = new List (); + foreach (var type in typeof (View).Assembly.GetTypes () + .Where (myType => myType.IsClass && !myType.IsAbstract && myType.IsPublic && myType.IsSubclassOf (typeof (View)))) { + types.Add (type); } + return types; + } - View CreateClass (Type type) - { - // If we are to create a generic Type - if (type.IsGenericType) { - - // For each of the arguments - List typeArguments = new List (); + View CreateClass (Type type) + { + // If we are to create a generic Type + if (type.IsGenericType) { - // use - foreach (var arg in type.GetGenericArguments ()) { - typeArguments.Add (typeof (object)); - } + // For each of the arguments + var typeArguments = new List (); - // And change what type we are instantiating from MyClass to MyClass - type = type.MakeGenericType (typeArguments.ToArray ()); + // use + foreach (var arg in type.GetGenericArguments ()) { + typeArguments.Add (typeof (object)); } - // Instantiate view - var view = (View)Activator.CreateInstance (type); - //_curView.X = Pos.Center (); - //_curView.Y = Pos.Center (); - view.Width = Dim.Percent (75); - view.Height = Dim.Percent (75); + // And change what type we are instantiating from MyClass to MyClass + type = type.MakeGenericType (typeArguments.ToArray ()); + } + // Instantiate view + var view = (View)Activator.CreateInstance (type); - // Set the colorscheme to make it stand out if is null by default - if (view.ColorScheme == null) { - view.ColorScheme = Colors.Base; - } + //_curView.X = Pos.Center (); + //_curView.Y = Pos.Center (); + view.Width = Dim.Percent (75); + view.Height = Dim.Percent (75); + + // Set the colorscheme to make it stand out if is null by default + if (view.ColorScheme == null) { + view.ColorScheme = Colors.Base; + } - // If the view supports a Text property, set it so we have something to look at - if (view.GetType ().GetProperty ("Text") != null) { - try { - view.GetType ().GetProperty ("Text")?.GetSetMethod ()?.Invoke (view, new [] { "Test Text" }); - } catch (TargetInvocationException e) { - MessageBox.ErrorQuery ("Exception", e.InnerException.Message, "Ok"); - view = null; - } + // If the view supports a Text property, set it so we have something to look at + if (view.GetType ().GetProperty ("Text") != null) { + try { + view.GetType ().GetProperty ("Text")?.GetSetMethod ()?.Invoke (view, new [] { "Test Text" }); + } catch (TargetInvocationException e) { + MessageBox.ErrorQuery ("Exception", e.InnerException.Message, "Ok"); + view = null; } + } - // If the view supports a Title property, set it so we have something to look at - if (view != null && view.GetType ().GetProperty ("Title") != null) { - if (view.GetType ().GetProperty ("Title").PropertyType == typeof (string)) { - view?.GetType ().GetProperty ("Title")?.GetSetMethod ()?.Invoke (view, new [] { "Test Title" }); - } else { - view?.GetType ().GetProperty ("Title")?.GetSetMethod ()?.Invoke (view, new [] { "Test Title" }); - } - } - - // If the view supports a Source property, set it so we have something to look at - if (view != null && view.GetType ().GetProperty ("Source") != null && view.GetType ().GetProperty ("Source").PropertyType == typeof (Terminal.Gui.IListDataSource)) { - var source = new ListWrapper (new List () { "Test Text #1", "Test Text #2", "Test Text #3" }); - view?.GetType ().GetProperty ("Source")?.GetSetMethod ()?.Invoke (view, new [] { source }); + // If the view supports a Title property, set it so we have something to look at + if (view != null && view.GetType ().GetProperty ("Title") != null) { + if (view.GetType ().GetProperty ("Title").PropertyType == typeof (string)) { + view?.GetType ().GetProperty ("Title")?.GetSetMethod ()?.Invoke (view, new [] { "Test Title" }); + } else { + view?.GetType ().GetProperty ("Title")?.GetSetMethod ()?.Invoke (view, new [] { "Test Title" }); } + } - // Set Settings - _computedCheckBox.Checked = view.LayoutStyle == LayoutStyle.Computed; + // If the view supports a Source property, set it so we have something to look at + if (view != null && view.GetType ().GetProperty ("Source") != null && view.GetType ().GetProperty ("Source").PropertyType == typeof (IListDataSource)) { + var source = new ListWrapper (new List { "Test Text #1", "Test Text #2", "Test Text #3" }); + view?.GetType ().GetProperty ("Source")?.GetSetMethod ()?.Invoke (view, new [] { source }); + } - // Add - _hostPane.Add (view); - //DimPosChanged (); - _hostPane.LayoutSubviews (); - _hostPane.Clear (); - _hostPane.SetNeedsDisplay (); - UpdateSettings (view); - UpdateTitle (view); + // Set Settings + _computedCheckBox.Checked = view.LayoutStyle == LayoutStyle.Computed; - view.LayoutComplete += LayoutCompleteHandler; + // Add + _hostPane.Add (view); + //DimPosChanged (); + _hostPane.LayoutSubviews (); + _hostPane.Clear (); + _hostPane.SetNeedsDisplay (); + UpdateSettings (view); + UpdateTitle (view); - return view; - } + view.LayoutComplete += LayoutCompleteHandler; - void LayoutCompleteHandler (object sender, LayoutEventArgs args) - { - UpdateTitle (_curView); - } + return view; + } + + void LayoutCompleteHandler (object sender, LayoutEventArgs args) + { + UpdateTitle (_curView); } } -} +} \ No newline at end of file diff --git a/UnitTests/View/DrawTests.cs b/UnitTests/View/DrawTests.cs index aaab0f3308..d41803d9d9 100644 --- a/UnitTests/View/DrawTests.cs +++ b/UnitTests/View/DrawTests.cs @@ -4,14 +4,16 @@ using Xunit.Abstractions; using Microsoft.VisualStudio.TestPlatform.Utilities; -namespace Terminal.Gui.ViewsTests; +namespace Terminal.Gui.ViewsTests; public class DrawTests { readonly ITestOutputHelper _output; public DrawTests (ITestOutputHelper output) => _output = output; - [Fact] [AutoInitShutdown] + // TODO: Refactor this test to not depend on TextView etc... Make it as primitive as possible + [Fact] + [AutoInitShutdown] public void Clipping_AddRune_Left_Or_Right_Replace_Previous_Or_Next_Wide_Rune_With_Space () { var tv = new TextView () { @@ -29,7 +31,8 @@ public void Clipping_AddRune_Left_Or_Right_Replace_Previous_Or_Next_Wide_Rune_Wi var win = new Window () { Width = Dim.Fill (), Height = Dim.Fill () }; win.Add (tv); Application.Top.Add (win); - var lbl = new Label ("ワイドルーン。"); + // Don't use Label. It sets AutoSize = true which is not what we're testing here. + var lbl = new View ("ワイドルーン。"); // Don't have unit tests use things that aren't absolutely critical for the test, like Dialog var dg = new Window () { X = 2, Y = 2, Width = 14, Height = 3 }; dg.Add (lbl); @@ -54,7 +57,8 @@ public void Clipping_AddRune_Left_Or_Right_Replace_Previous_Or_Next_Wide_Rune_Wi } // TODO: The tests below that use Label should use View instead. - [Fact] [AutoInitShutdown] + [Fact] + [AutoInitShutdown] public void Non_Bmp_ConsoleWidth_ColumnWidth_Equal_Two () { string us = "\U0001d539"; @@ -102,7 +106,8 @@ public void Non_Bmp_ConsoleWidth_ColumnWidth_Equal_Two () 0000000000", Application.Driver, expectedColors); } - [Fact] [AutoInitShutdown] + [Fact] + [AutoInitShutdown] public void CJK_Compatibility_Ideographs_ConsoleWidth_ColumnWidth_Equal_Two () { string us = "\U0000f900"; @@ -150,7 +155,8 @@ public void CJK_Compatibility_Ideographs_ConsoleWidth_ColumnWidth_Equal_Two () 0000000000", Application.Driver, expectedColors); } - [Fact] [AutoInitShutdown] + [Fact] + [AutoInitShutdown] public void Colors_On_TextAlignment_Right_And_Bottom () { var labelRight = new Label ("Test") { @@ -191,7 +197,8 @@ public void Colors_On_TextAlignment_Right_And_Bottom () 0", Application.Driver, new Attribute [] { Colors.Base.Normal }); } - [Fact] [AutoInitShutdown] + [Fact] + [AutoInitShutdown] public void Draw_Negative_Bounds_Horizontal_Without_New_Lines () { // BUGBUG: This previously assumed the default height of a View was 1. @@ -235,7 +242,8 @@ public void Draw_Negative_Bounds_Horizontal_Without_New_Lines () TestHelpers.AssertDriverContentsWithFrameAre ("", _output); } - [Fact] [AutoInitShutdown] + [Fact] + [AutoInitShutdown] public void Draw_Negative_Bounds_Horizontal_With_New_Lines () { var subView = new View () { Id = "subView", X = 1, Width = 1, Height = 7, Text = "s\nu\nb\nV\ni\ne\nw" }; @@ -304,7 +312,8 @@ public void Draw_Negative_Bounds_Horizontal_With_New_Lines () TestHelpers.AssertDriverContentsWithFrameAre ("", _output); } - [Fact] [AutoInitShutdown] + [Fact] + [AutoInitShutdown] public void Draw_Negative_Bounds_Vertical () { var subView = new View () { Id = "subView", X = 1, Width = 1, Height = 7, Text = "subView", TextDirection = TextDirection.TopBottom_LeftRight }; diff --git a/UnitTests/View/Layout/AbsoluteLayoutTests.cs b/UnitTests/View/Layout/AbsoluteLayoutTests.cs index 3b730266a9..6968ebd349 100644 --- a/UnitTests/View/Layout/AbsoluteLayoutTests.cs +++ b/UnitTests/View/Layout/AbsoluteLayoutTests.cs @@ -1,5 +1,6 @@ using Xunit; using Xunit.Abstractions; + //using GraphViewTests = Terminal.Gui.Views.GraphViewTests; // Alias Console to MockConsole so we don't accidentally use Console @@ -9,95 +10,146 @@ namespace Terminal.Gui.ViewTests; public class AbsoluteLayoutTests { readonly ITestOutputHelper _output; - public AbsoluteLayoutTests (ITestOutputHelper output) => this._output = output; + public AbsoluteLayoutTests (ITestOutputHelper output) => _output = output; - [Fact] [TestRespondersDisposed] + [Fact] + [TestRespondersDisposed] public void AbsoluteLayout_Constructor () { - var frame = new Rect (1, 2, 3, 4); - var v = new View (frame); + var v = new View (); + Assert.True (v.LayoutStyle == LayoutStyle.Absolute); + v.Dispose (); + + var frame = Rect.Empty; + v = new View (frame); + Assert.True (v.LayoutStyle == LayoutStyle.Absolute); + Assert.Equal (frame, v.Frame); + Assert.Equal (new Rect (0, 0, frame.Width, frame.Height), v.Bounds); // With Absolute Bounds *is* deterministic before Layout + Assert.Equal (Pos.At (0), v.X); + Assert.Equal (Pos.At (0), v.Y); + Assert.Equal (Dim.Sized (0), v.Width); + Assert.Equal (Dim.Sized (0), v.Height); + v.Dispose (); + + frame = new Rect (1, 2, 3, 4); + v = new View (frame); Assert.True (v.LayoutStyle == LayoutStyle.Absolute); Assert.Equal (frame, v.Frame); Assert.Equal (new Rect (0, 0, frame.Width, frame.Height), v.Bounds); // With Absolute Bounds *is* deterministic before Layout - Assert.Null (v.X); - Assert.Null (v.Y); - Assert.Null (v.Height); - Assert.Null (v.Width); + Assert.Equal (Pos.At (1), v.X); + Assert.Equal (Pos.At (2), v.Y); + Assert.Equal (Dim.Sized (3), v.Width); + Assert.Equal (Dim.Sized (4), v.Height); v.Dispose (); v = new View (frame, "v"); Assert.True (v.LayoutStyle == LayoutStyle.Absolute); Assert.Equal (frame, v.Frame); Assert.Equal (new Rect (0, 0, frame.Width, frame.Height), v.Bounds); // With Absolute Bounds *is* deterministic before Layout - Assert.Null (v.X); - Assert.Null (v.Y); - Assert.Null (v.Height); - Assert.Null (v.Width); + Assert.Equal (Pos.At (1), v.X); + Assert.Equal (Pos.At (2), v.Y); + Assert.Equal (Dim.Sized (3), v.Width); + Assert.Equal (Dim.Sized (4), v.Height); v.Dispose (); v = new View (frame.X, frame.Y, "v"); Assert.True (v.LayoutStyle == LayoutStyle.Absolute); - // BUGBUG: v2 - I think the default size should be 0,0 + // BUGBUG: v2 - I think the default size should be 0,0 not 1,1 Assert.Equal (new Rect (frame.X, frame.Y, 1, 1), v.Frame); Assert.Equal (new Rect (0, 0, 1, 1), v.Bounds); // With Absolute Bounds *is* deterministic before Layout - Assert.Null (v.X); - Assert.Null (v.Y); - Assert.Null (v.Height); - Assert.Null (v.Width); + Assert.Equal (Pos.At (1), v.X); + Assert.Equal (Pos.At (2), v.Y); + Assert.Equal (Dim.Sized (1), v.Width); + Assert.Equal (Dim.Sized (1), v.Height); + v.Dispose (); + + v = new View (); + Assert.True (v.LayoutStyle == LayoutStyle.Absolute); + Assert.Equal (new Rect (0, 0, 0, 0), v.Frame); + Assert.Equal (new Rect (0, 0, 0, 0), v.Bounds); // With Absolute Bounds *is* deterministic before Layout + Assert.Equal (Pos.At (0), v.X); + Assert.Equal (Pos.At (0), v.Y); + Assert.Equal (Dim.Sized (0), v.Width); + Assert.Equal (Dim.Sized (0), v.Height); v.Dispose (); + v = new View { + X = frame.X, + Y = frame.Y, + Width = frame.Width, + Height = frame.Height + }; + Assert.True (v.LayoutStyle == LayoutStyle.Absolute); + Assert.Equal (new Rect (frame.X, frame.Y, 3, 4), v.Frame); + Assert.Equal (new Rect (0, 0, 3, 4), v.Bounds); // With Absolute Bounds *is* deterministic before Layout + Assert.Equal (Pos.At (1), v.X); + Assert.Equal (Pos.At (2), v.Y); + Assert.Equal (Dim.Sized (3), v.Width); + Assert.Equal (Dim.Sized (4), v.Height); + v.Dispose (); } - [Fact] [TestRespondersDisposed] + [Fact] + [TestRespondersDisposed] public void AbsoluteLayout_Change_Frame () { var frame = new Rect (1, 2, 3, 4); var newFrame = new Rect (1, 2, 30, 40); - var v = new View (frame); + var v = new View (); + Assert.True (v.LayoutStyle == LayoutStyle.Absolute); + v.Dispose (); + + v = new View (frame); + Assert.True (v.LayoutStyle == LayoutStyle.Absolute); + v.Frame = newFrame; Assert.True (v.LayoutStyle == LayoutStyle.Absolute); Assert.Equal (newFrame, v.Frame); Assert.Equal (new Rect (0, 0, newFrame.Width, newFrame.Height), v.Bounds); // With Absolute Bounds *is* deterministic before Layout - Assert.Null (v.X); - Assert.Null (v.Y); - Assert.Null (v.Height); - Assert.Null (v.Width); + Assert.Equal (Pos.At (1), v.X); + Assert.Equal (Pos.At (2), v.Y); + Assert.Equal (Dim.Sized (30), v.Width); + Assert.Equal (Dim.Sized (40), v.Height); v.Dispose (); v = new View (frame.X, frame.Y, "v"); v.Frame = newFrame; + Assert.True (v.LayoutStyle == LayoutStyle.Absolute); Assert.Equal (newFrame, v.Frame); Assert.Equal (new Rect (0, 0, newFrame.Width, newFrame.Height), v.Bounds); // With Absolute Bounds *is* deterministic before Layout - Assert.Null (v.X); - Assert.Null (v.Y); - Assert.Null (v.Height); - Assert.Null (v.Width); + Assert.Equal (Pos.At (1), v.X); + Assert.Equal (Pos.At (2), v.Y); + Assert.Equal (Dim.Sized (30), v.Width); + Assert.Equal (Dim.Sized (40), v.Height); v.Dispose (); newFrame = new Rect (10, 20, 30, 40); v = new View (frame); v.Frame = newFrame; + Assert.True (v.LayoutStyle == LayoutStyle.Absolute); Assert.Equal (newFrame, v.Frame); Assert.Equal (new Rect (0, 0, newFrame.Width, newFrame.Height), v.Bounds); // With Absolute Bounds *is* deterministic before Layout - Assert.Null (v.X); - Assert.Null (v.Y); - Assert.Null (v.Height); - Assert.Null (v.Width); + Assert.Equal (Pos.At (10), v.X); + Assert.Equal (Pos.At (20), v.Y); + Assert.Equal (Dim.Sized (30), v.Width); + Assert.Equal (Dim.Sized (40), v.Height); v.Dispose (); v = new View (frame.X, frame.Y, "v"); v.Frame = newFrame; + Assert.True (v.LayoutStyle == LayoutStyle.Absolute); Assert.Equal (newFrame, v.Frame); Assert.Equal (new Rect (0, 0, newFrame.Width, newFrame.Height), v.Bounds); // With Absolute Bounds *is* deterministic before Layout - Assert.Null (v.X); - Assert.Null (v.Y); - Assert.Null (v.Height); - Assert.Null (v.Width); + Assert.Equal (Pos.At (10), v.X); + Assert.Equal (Pos.At (20), v.Y); + Assert.Equal (Dim.Sized (30), v.Width); + Assert.Equal (Dim.Sized (40), v.Height); v.Dispose (); } - [Fact] [TestRespondersDisposed] + [Fact] + [TestRespondersDisposed] public void AbsoluteLayout_Change_Height_or_Width_Absolute () { var frame = new Rect (1, 2, 3, 4); @@ -109,34 +161,26 @@ public void AbsoluteLayout_Change_Height_or_Width_Absolute () Assert.True (v.LayoutStyle == LayoutStyle.Absolute); Assert.Equal (newFrame, v.Frame); Assert.Equal (new Rect (0, 0, newFrame.Width, newFrame.Height), v.Bounds); // With Absolute Bounds *is* deterministic before Layout - Assert.Null (v.X); - Assert.Null (v.Y); + Assert.Equal (Pos.At (1), v.X); + Assert.Equal (Pos.At (2), v.Y); Assert.Equal ($"Absolute({newFrame.Height})", v.Height.ToString ()); Assert.Equal ($"Absolute({newFrame.Width})", v.Width.ToString ()); v.Dispose (); } - [Fact] [TestRespondersDisposed] - public void AbsoluteLayout_Change_Height_or_Width_NotAbsolute () + [Fact] + [TestRespondersDisposed] + public void AbsoluteLayout_Change_Height_or_Width_MakesComputed () { var v = new View (Rect.Empty); v.Height = Dim.Fill (); v.Width = Dim.Fill (); - Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // BUGBUG: v2 - Changing the Height or Width should change the LayoutStyle + Assert.True (v.LayoutStyle == LayoutStyle.Computed); v.Dispose (); } - [Fact] [TestRespondersDisposed] - public void AbsoluteLayout_Change_Height_or_Width_Null () - { - var v = new View (Rect.Empty); - v.Height = null; - v.Width = null; - Assert.True (v.LayoutStyle == LayoutStyle.Absolute); - v.Dispose (); - } - - [Fact] [TestRespondersDisposed] + [Fact] + [TestRespondersDisposed] public void AbsoluteLayout_Change_X_or_Y_Absolute () { var frame = new Rect (1, 2, 3, 4); @@ -150,52 +194,24 @@ public void AbsoluteLayout_Change_X_or_Y_Absolute () Assert.Equal (new Rect (0, 0, newFrame.Width, newFrame.Height), v.Bounds); // With Absolute Bounds *is* deterministic before Layout Assert.Equal ($"Absolute({newFrame.X})", v.X.ToString ()); Assert.Equal ($"Absolute({newFrame.Y})", v.Y.ToString ()); - Assert.Null (v.Height); - Assert.Null (v.Width); - v.Dispose (); - } - - [Fact] [TestRespondersDisposed] - public void AbsoluteLayout_Change_X_or_Y_NotAbsolute () - { - var v = new View (Rect.Empty); - v.X = Pos.Center (); - v.Y = Pos.Center (); - Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // BUGBUG: v2 - Changing the Height or Width should change the LayoutStyle + Assert.Equal (Dim.Sized (3), v.Width); + Assert.Equal (Dim.Sized (4), v.Height); v.Dispose (); } - [Fact] [TestRespondersDisposed] - public void AbsoluteLayout_Change_X_or_Y_Null () + [Fact] + [TestRespondersDisposed] + public void AbsoluteLayout_Change_X_or_Y_MakesComputed () { var v = new View (Rect.Empty); - v.X = null; - Assert.True (v.LayoutStyle == LayoutStyle.Absolute); - v.Dispose (); - - v = new View (Rect.Empty); v.X = Pos.Center (); - Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // BUGBUG: v2 - Changing the Height or Width should change the LayoutStyle - - v.X = null; - Assert.True (v.LayoutStyle == LayoutStyle.Absolute); - v.Dispose (); - - v = new View (Rect.Empty); - v.Y = null; - Assert.True (v.LayoutStyle == LayoutStyle.Absolute); - v.Dispose (); - - v = new View (Rect.Empty); v.Y = Pos.Center (); - Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // BUGBUG: v2 - Changing the Height or Width should change the LayoutStyle - - v.Y = null; - Assert.True (v.LayoutStyle == LayoutStyle.Absolute); + Assert.True (v.LayoutStyle == LayoutStyle.Computed); v.Dispose (); } - [Fact] [TestRespondersDisposed] + [Fact] + [TestRespondersDisposed] public void AbsoluteLayout_Change_X_Y_Height_Width_Absolute () { var v = new View (Rect.Empty); @@ -211,14 +227,7 @@ public void AbsoluteLayout_Change_X_Y_Height_Width_Absolute () v.Y = Pos.Center (); v.Width = Dim.Fill (); v.Height = Dim.Fill (); - Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // BUGBUG: v2 - Changing the Height or Width should change the LayoutStyle - - // BUGBUG: v2 - If all of X, Y, Width, and Height are null or Absolute(n), isn't that the same as LayoutStyle.Absoulte? - v.X = null; - v.Y = null; - v.Height = null; - v.Width = null; - Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // We never automatically change to Absolute from Computed?? + Assert.True (v.LayoutStyle == LayoutStyle.Computed); v.Dispose (); v = new View (Rect.Empty); @@ -226,14 +235,10 @@ public void AbsoluteLayout_Change_X_Y_Height_Width_Absolute () v.Y = Pos.Center (); v.Width = Dim.Fill (); v.Height = Dim.Fill (); - Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // BUGBUG: v2 - Changing the Height or Width should change the LayoutStyle + Assert.True (v.LayoutStyle == LayoutStyle.Computed); - // BUGBUG: v2 - If all of X, Y, Width, and Height are null or Absolute(n), isn't that the same as LayoutStyle.Absoulte? v.X = 1; - v.Y = null; - v.Height = null; - v.Width = null; - Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // We never automatically change to Absolute from Computed?? + Assert.True (v.LayoutStyle == LayoutStyle.Computed); v.Dispose (); v = new View (Rect.Empty); @@ -241,14 +246,10 @@ public void AbsoluteLayout_Change_X_Y_Height_Width_Absolute () v.Y = Pos.Center (); v.Width = Dim.Fill (); v.Height = Dim.Fill (); - Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // BUGBUG: v2 - Changing the Height or Width should change the LayoutStyle + Assert.True (v.LayoutStyle == LayoutStyle.Computed); - // BUGBUG: v2 - If all of X, Y, Width, and Height are null or Absolute(n), isn't that the same as LayoutStyle.Absoulte? - v.X = null; v.Y = 2; - v.Height = null; - v.Width = null; - Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // We never automatically change to Absolute from Computed?? + Assert.True (v.LayoutStyle == LayoutStyle.Computed); v.Dispose (); v = new View (Rect.Empty); @@ -256,14 +257,10 @@ public void AbsoluteLayout_Change_X_Y_Height_Width_Absolute () v.Y = Pos.Center (); v.Width = Dim.Fill (); v.Height = Dim.Fill (); - Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // BUGBUG: v2 - Changing the Height or Width should change the LayoutStyle + Assert.True (v.LayoutStyle == LayoutStyle.Computed); - // BUGBUG: v2 - If all of X, Y, Width, and Height are null or Absolute(n), isn't that the same as LayoutStyle.Absoulte? - v.X = null; - v.Y = null; - v.Height = 3; - v.Width = null; - Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // We never automatically change to Absolute from Computed?? + v.Width = 3; + Assert.True (v.LayoutStyle == LayoutStyle.Computed); v.Dispose (); v = new View (Rect.Empty); @@ -271,72 +268,122 @@ public void AbsoluteLayout_Change_X_Y_Height_Width_Absolute () v.Y = Pos.Center (); v.Width = Dim.Fill (); v.Height = Dim.Fill (); - Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // BUGBUG: v2 - Changing the Height or Width should change the LayoutStyle + Assert.True (v.LayoutStyle == LayoutStyle.Computed); - // BUGBUG: v2 - If all of X, Y, Width, and Height are null or Absolute(n), isn't that the same as LayoutStyle.Absoulte? - v.X = null; - v.Y = null; - v.Height = null; - v.Width = 4; - Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // We never automatically change to Absolute from Computed?? + v.Height = 3; + Assert.True (v.LayoutStyle == LayoutStyle.Computed); v.Dispose (); - } - - [Fact] [TestRespondersDisposed] - public void AbsoluteLayout_Change_X_Y_Height_Width_Null () - { - var v = new View (Rect.Empty); - v.X = null; - v.Y = null; - v.Height = null; - v.Width = null; - Assert.True (v.LayoutStyle == LayoutStyle.Absolute); - v.Dispose (); v = new View (Rect.Empty); v.X = Pos.Center (); v.Y = Pos.Center (); v.Width = Dim.Fill (); v.Height = Dim.Fill (); - Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // BUGBUG: v2 - Changing the Height or Width should change the LayoutStyle - - // BUGBUG: v2 - If all of X, Y, Width, and Height are null or Absolute(n), isn't that the same as LayoutStyle.Absoulte? - v.X = null; - v.Y = null; - v.Height = null; - v.Width = null; - Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // We never automatically change to Absolute from Computed?? + Assert.True (v.LayoutStyle == LayoutStyle.Computed); + + v.X = 1; + v.Y = 2; + v.Height = 3; + v.Width = 4; + Assert.True (v.LayoutStyle == LayoutStyle.Absolute); v.Dispose (); } - [Fact] [TestRespondersDisposed] - public void AbsoluteLayout_Layout () + [Fact] + [TestRespondersDisposed] + public void AbsoluteLayout_LayoutSubviews () { var superRect = new Rect (0, 0, 100, 100); var super = new View (superRect, "super"); Assert.True (super.LayoutStyle == LayoutStyle.Absolute); - var v1 = new View () { + var v1 = new View { X = 0, Y = 0, Width = 10, Height = 10 }; - // BUGBUG: v2 - This should be LayoutStyle.Absolute - Assert.True (v1.LayoutStyle == LayoutStyle.Computed); + Assert.True (v1.LayoutStyle == LayoutStyle.Absolute); - var v2 = new View () { + var v2 = new View { X = 10, Y = 10, Width = 10, Height = 10 }; - // BUGBUG: v2 - This should be LayoutStyle.Absolute - Assert.True (v1.LayoutStyle == LayoutStyle.Computed); + Assert.True (v2.LayoutStyle == LayoutStyle.Absolute); super.Add (v1, v2); + Assert.True (v1.LayoutStyle == LayoutStyle.Absolute); + Assert.True (v2.LayoutStyle == LayoutStyle.Absolute); + super.LayoutSubviews (); Assert.Equal (new Rect (0, 0, 10, 10), v1.Frame); Assert.Equal (new Rect (10, 10, 10, 10), v2.Frame); super.Dispose (); } + + [Fact] + public void AbsoluteLayout_Setting_Bounds_Location_NotEmpty () + { + // TODO: Should we enforce Bounds.X/Y == 0? The code currently ignores value.X/Y which is + // TODO: correct behavior, but is silent. Perhaps an exception? + var frame = new Rect (1, 2, 3, 4); + var newBounds = new Rect (10, 20, 30, 40); + var view = new View (frame); + view.Bounds = newBounds; + Assert.Equal (new Rect (0, 0, 30, 40), view.Bounds); + Assert.Equal (new Rect (1, 2, 30, 40), view.Frame); + } + + [Fact] + public void AbsoluteLayout_Setting_Bounds_Sets_Frame () + { + var frame = new Rect (1, 2, 3, 4); + var newBounds = new Rect (0, 0, 30, 40); + + var v = new View (frame); + Assert.True (v.LayoutStyle == LayoutStyle.Absolute); + + v.Bounds = newBounds; + Assert.True (v.LayoutStyle == LayoutStyle.Absolute); + Assert.Equal (newBounds, v.Bounds); + Assert.Equal (new Rect (1, 2, newBounds.Width, newBounds.Height), v.Frame); + Assert.Equal (new Rect (0, 0, newBounds.Width, newBounds.Height), v.Bounds); + Assert.Equal (Pos.At (1), v.X); + Assert.Equal (Pos.At (2), v.Y); + Assert.Equal (Dim.Sized (30), v.Width); + Assert.Equal (Dim.Sized (40), v.Height); + + newBounds = new Rect (0, 0, 3, 4); + v.Bounds = newBounds; + Assert.Equal (newBounds, v.Bounds); + Assert.Equal (new Rect (1, 2, newBounds.Width, newBounds.Height), v.Frame); + Assert.Equal (new Rect (0, 0, newBounds.Width, newBounds.Height), v.Bounds); + Assert.Equal (Pos.At (1), v.X); + Assert.Equal (Pos.At (2), v.Y); + Assert.Equal (Dim.Sized (3), v.Width); + Assert.Equal (Dim.Sized (4), v.Height); + + v.BorderStyle = LineStyle.Single; + // Bounds should shrink + Assert.Equal (new Rect (0, 0, 1, 2), v.Bounds); + // Frame should not change + Assert.Equal (new Rect (1, 2, 3, 4), v.Frame); + Assert.Equal (Pos.At (1), v.X); + Assert.Equal (Pos.At (2), v.Y); + Assert.Equal (Dim.Sized (3), v.Width); + Assert.Equal (Dim.Sized (4), v.Height); + + // Now set bounds bigger as before + newBounds = new Rect (0, 0, 3, 4); + v.Bounds = newBounds; + Assert.Equal (newBounds, v.Bounds); + // Frame grows because there's now a border + Assert.Equal (new Rect (1, 2, 5, 6), v.Frame); + Assert.Equal (new Rect (0, 0, newBounds.Width, newBounds.Height), v.Bounds); + Assert.Equal (Pos.At (1), v.X); + Assert.Equal (Pos.At (2), v.Y); + Assert.Equal (Dim.Sized (5), v.Width); + Assert.Equal (Dim.Sized (6), v.Height); + } } \ No newline at end of file diff --git a/UnitTests/View/Layout/AutoSizeTests.cs b/UnitTests/View/Layout/AutoSizeTests.cs deleted file mode 100644 index 9484c4c81d..0000000000 --- a/UnitTests/View/Layout/AutoSizeTests.cs +++ /dev/null @@ -1,838 +0,0 @@ -using System.Text; -using System; -using System.Collections.Generic; -using Xunit; -using Xunit.Abstractions; - -namespace Terminal.Gui.ViewTests { - public class AutoSizeTests { - readonly ITestOutputHelper output; - - public AutoSizeTests (ITestOutputHelper output) - { - this.output = output; - } - - [Fact, AutoInitShutdown] - public void AutoSize_GetAutoSize_Horizontal () - { - var text = "text"; - var view = new View () { - Text = text, - AutoSize = true - }; - var win = new Window () { - Width = Dim.Fill (), - Height = Dim.Fill () - }; - win.Add (view); - Application.Top.Add (win); - Application.Begin (Application.Top); - ((FakeDriver)Application.Driver).SetBufferSize (10, 4); - - var size = view.GetAutoSize (); - Assert.Equal (new Size (text.Length, 1), size); - - view.Text = $"{text}\n{text}"; - size = view.GetAutoSize (); - Assert.Equal (new Size (text.Length, 2), size); - - view.Text = $"{text}\n{text}\n{text}+"; - size = view.GetAutoSize (); - Assert.Equal (new Size (text.Length + 1, 3), size); - - text = string.Empty; - view.Text = text; - size = view.GetAutoSize (); - Assert.Equal (new Size (0, 0), size); - - text = "1"; - view.Text = text; - size = view.GetAutoSize (); - Assert.Equal (new Size (1, 1), size); - - text = "界"; - view.Text = text; - size = view.GetAutoSize (); - Assert.Equal (new Size (2, 1), size); - } - - [Fact, AutoInitShutdown] - public void AutoSize_GetAutoSize_Vertical() - { - var text = "text"; - var view = new View () { - Text = text, - TextDirection = TextDirection.TopBottom_LeftRight, - AutoSize = true - }; - var win = new Window () { - Width = Dim.Fill (), - Height = Dim.Fill () - }; - win.Add (view); - Application.Top.Add (win); - Application.Begin (Application.Top); - ((FakeDriver)Application.Driver).SetBufferSize (10, 4); - - var size = view.GetAutoSize (); - Assert.Equal (new Size (1, text.Length), size); - - view.Text = $"{text}\n{text}"; - size = view.GetAutoSize (); - Assert.Equal (new Size (2, text.Length), size); - - view.Text = $"{text}\n{text}\n{text}+"; - size = view.GetAutoSize (); - Assert.Equal (new Size (3, text.Length + 1), size); - - text = string.Empty; - view.Text = text; - size = view.GetAutoSize (); - Assert.Equal (new Size (0, 0), size); - - text = "1"; - view.Text = text; - size = view.GetAutoSize (); - Assert.Equal (new Size (1, 1), size); - - text = "界"; - view.Text = text; - size = view.GetAutoSize (); - Assert.Equal (new Size (2, 1), size); - } - - [Fact, AutoInitShutdown] - public void AutoSize_GetAutoSize_Left() - { - var text = "This is some text."; - var view = new View () { - Text = text, - TextAlignment = TextAlignment.Left, - AutoSize = true - }; - var win = new Window () { - Width = Dim.Fill (), - Height = Dim.Fill () - }; - win.Add (view); - Application.Top.Add (win); - Application.Begin (Application.Top); - ((FakeDriver)Application.Driver).SetBufferSize (10, 4); - - var size = view.GetAutoSize (); - Assert.Equal (new Size (text.Length, 1), size); - - view.Text = $"{text}\n{text}"; - size = view.GetAutoSize (); - Assert.Equal (new Size (text.Length, 2), size); - - view.Text = $"{text}\n{text}\n{text}+"; - size = view.GetAutoSize (); - Assert.Equal (new Size (text.Length + 1, 3), size); - - text = string.Empty; - view.Text = text; - size = view.GetAutoSize (); - Assert.Equal (new Size (0, 0), size); - - text = "1"; - view.Text = text; - size = view.GetAutoSize (); - Assert.Equal (new Size (1, 1), size); - - text = "界"; - view.Text = text; - size = view.GetAutoSize (); - Assert.Equal (new Size (2, 1), size); - } - - [Fact, AutoInitShutdown] - public void AutoSize_GetAutoSize_Right () - { - var text = "This is some text."; - var view = new View () { - Text = text, - TextAlignment = TextAlignment.Right, - AutoSize = true - }; - var win = new Window () { - Width = Dim.Fill (), - Height = Dim.Fill () - }; - win.Add (view); - Application.Top.Add (win); - Application.Begin (Application.Top); - ((FakeDriver)Application.Driver).SetBufferSize (10, 4); - - var size = view.GetAutoSize (); - Assert.Equal (new Size (text.Length, 1), size); - - view.Text = $"{text}\n{text}"; - size = view.GetAutoSize (); - Assert.Equal (new Size (text.Length, 2), size); - - view.Text = $"{text}\n{text}\n{text}+"; - size = view.GetAutoSize (); - Assert.Equal (new Size (text.Length + 1, 3), size); - - text = string.Empty; - view.Text = text; - size = view.GetAutoSize (); - Assert.Equal (new Size (0, 0), size); - - text = "1"; - view.Text = text; - size = view.GetAutoSize (); - Assert.Equal (new Size (1, 1), size); - - text = "界"; - view.Text = text; - size = view.GetAutoSize (); - Assert.Equal (new Size (2, 1), size); - } - - [Fact, AutoInitShutdown] - public void AutoSize_GetAutoSize_Centered () - { - var text = "This is some text."; - var view = new View () { - Text = text, - TextAlignment = TextAlignment.Centered, - AutoSize = true - }; - var win = new Window () { - Width = Dim.Fill (), - Height = Dim.Fill () - }; - win.Add (view); - Application.Top.Add (win); - Application.Begin (Application.Top); - ((FakeDriver)Application.Driver).SetBufferSize (10, 4); - - var size = view.GetAutoSize (); - Assert.Equal (new Size (text.Length, 1), size); - - view.Text = $"{text}\n{text}"; - size = view.GetAutoSize (); - Assert.Equal (new Size (text.Length, 2), size); - - view.Text = $"{text}\n{text}\n{text}+"; - size = view.GetAutoSize (); - Assert.Equal (new Size (text.Length + 1, 3), size); - - text = string.Empty; - view.Text = text; - size = view.GetAutoSize (); - Assert.Equal (new Size (0, 0), size); - - text = "1"; - view.Text = text; - size = view.GetAutoSize (); - Assert.Equal (new Size (1, 1), size); - - text = "界"; - view.Text = text; - size = view.GetAutoSize (); - Assert.Equal (new Size (2, 1), size); - } - - [Fact, AutoInitShutdown] - public void AutoSize_False_View_IsEmpty_False_Return_Null_Lines () - { - var text = "Views"; - var view = new View () { - Width = Dim.Fill () - text.Length, - Height = 1, - Text = text - }; - var win = new Window () { - Width = Dim.Fill (), - Height = Dim.Fill () - }; - win.Add (view); - Application.Top.Add (win); - Application.Begin (Application.Top); - ((FakeDriver)Application.Driver).SetBufferSize (10, 4); - - Assert.Equal (5, text.Length); - Assert.False (view.AutoSize); - Assert.Equal (new Rect (0, 0, 3, 1), view.Frame); - Assert.Equal (new Size (3, 1), view.TextFormatter.Size); - Assert.Equal (new List () { "Vie" }, view.TextFormatter.Lines); - Assert.Equal (new Rect (0, 0, 10, 4), win.Frame); - Assert.Equal (new Rect (0, 0, 10, 4), Application.Top.Frame); - var expected = @" -┌────────┐ -│Vie │ -│ │ -└────────┘ -"; - - var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (0, 0, 10, 4), pos); - - text = "0123456789"; - Assert.Equal (10, text.Length); - view.Width = Dim.Fill () - text.Length; - Application.Refresh (); - - Assert.Equal (new Rect (0, 0, 0, 1), view.Frame); - Assert.Equal (new Size (0, 1), view.TextFormatter.Size); - Assert.Equal (new List () { string.Empty }, view.TextFormatter.Lines); - expected = @" -┌────────┐ -│ │ -│ │ -└────────┘ -"; - - pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (0, 0, 10, 4), pos); - } - - [Fact, AutoInitShutdown] - public void AutoSize_False_View_IsEmpty_True_Minimum_Height () - { - var text = "Views"; - var view = new View () { - Width = Dim.Fill () - text.Length, - Text = text - }; - var win = new Window () { - Width = Dim.Fill (), - Height = Dim.Fill () - }; - win.Add (view); - Application.Top.Add (win); - Application.Begin (Application.Top); - ((FakeDriver)Application.Driver).SetBufferSize (10, 4); - - Assert.Equal (5, text.Length); - Assert.False (view.AutoSize); - Assert.Equal (new Rect (0, 0, 3, 1), view.Frame); - Assert.Equal (new Size (3, 1), view.TextFormatter.Size); - Assert.Single (view.TextFormatter.Lines); - Assert.Equal (new Rect (0, 0, 10, 4), win.Frame); - Assert.Equal (new Rect (0, 0, 10, 4), Application.Top.Frame); - var expected = @" -┌────────┐ -│Vie │ -│ │ -└────────┘ -"; - - var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (0, 0, 10, 4), pos); - - text = "0123456789"; - Assert.Equal (10, text.Length); - view.Width = Dim.Fill () - text.Length; - Application.Refresh (); - - Assert.Equal (new Rect (0, 0, 0, 1), view.Frame); - Assert.Equal (new Size (0, 1), view.TextFormatter.Size); - var exception = Record.Exception (() => Assert.Equal (new List () { string.Empty }, view.TextFormatter.Lines)); - Assert.Null (exception); - expected = @" -┌────────┐ -│ │ -│ │ -└────────┘ -"; - - pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (0, 0, 10, 4), pos); - } - - [Fact, AutoInitShutdown] - public void AutoSize_True_Label_IsEmpty_False_Never_Return_Null_Lines () - { - var text = "Label"; - var label = new Label () { - Width = Dim.Fill () - text.Length, - Height = 1, - Text = text - }; - var win = new Window () { - Width = Dim.Fill (), - Height = Dim.Fill () - }; - win.Add (label); - Application.Top.Add (win); - Application.Begin (Application.Top); - ((FakeDriver)Application.Driver).SetBufferSize (10, 4); - - Assert.Equal (5, text.Length); - Assert.True (label.AutoSize); - Assert.Equal (new Rect (0, 0, 5, 1), label.Frame); - Assert.Equal (new Size (5, 1), label.TextFormatter.Size); - Assert.Equal (new List () { "Label" }, label.TextFormatter.Lines); - Assert.Equal (new Rect (0, 0, 10, 4), win.Frame); - Assert.Equal (new Rect (0, 0, 10, 4), Application.Top.Frame); - var expected = @" -┌────────┐ -│Label │ -│ │ -└────────┘ -"; - - var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (0, 0, 10, 4), pos); - - text = "0123456789"; - Assert.Equal (10, text.Length); - label.Width = Dim.Fill () - text.Length; - Application.Refresh (); - - Assert.True (label.AutoSize); - Assert.Equal (new Rect (0, 0, 5, 1), label.Frame); - Assert.Equal (new Size (5, 1), label.TextFormatter.Size); - Assert.Single (label.TextFormatter.Lines); - expected = @" -┌────────┐ -│Label │ -│ │ -└────────┘ -"; - - pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (0, 0, 10, 4), pos); - } - - [Fact, AutoInitShutdown] - public void AutoSize_False_Label_IsEmpty_True_Return_Null_Lines () - { - var text = "Label"; - var label = new Label () { - Width = Dim.Fill () - text.Length, - Height = 1, - Text = text, - AutoSize = false - }; - var win = new Window () { - Width = Dim.Fill (), - Height = Dim.Fill () - }; - win.Add (label); - Application.Top.Add (win); - Application.Begin (Application.Top); - ((FakeDriver)Application.Driver).SetBufferSize (10, 4); - - Assert.Equal (5, text.Length); - Assert.False (label.AutoSize); - Assert.Equal (new Rect (0, 0, 3, 1), label.Frame); - Assert.Equal (new Size (3, 1), label.TextFormatter.Size); - Assert.Equal (new List () { "Lab" }, label.TextFormatter.Lines); - Assert.Equal (new Rect (0, 0, 10, 4), win.Frame); - Assert.Equal (new Rect (0, 0, 10, 4), Application.Top.Frame); - var expected = @" -┌────────┐ -│Lab │ -│ │ -└────────┘ -"; - - var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (0, 0, 10, 4), pos); - - text = "0123456789"; - Assert.Equal (10, text.Length); - label.Width = Dim.Fill () - text.Length; - Application.Refresh (); - - Assert.False (label.AutoSize); - Assert.Equal (new Rect (0, 0, 0, 1), label.Frame); - Assert.Equal (new Size (0, 1), label.TextFormatter.Size); - Assert.Equal (new List { string.Empty }, label.TextFormatter.Lines); - expected = @" -┌────────┐ -│ │ -│ │ -└────────┘ -"; - - pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (0, 0, 10, 4), pos); - } - - [Fact, AutoInitShutdown] - public void AutoSize_True_Label_IsEmpty_False_Minimum_Height () - { - var text = "Label"; - var label = new Label () { - Width = Dim.Fill () - text.Length, - Text = text - }; - var win = new Window () { - Width = Dim.Fill (), - Height = Dim.Fill () - }; - win.Add (label); - Application.Top.Add (win); - Application.Begin (Application.Top); - ((FakeDriver)Application.Driver).SetBufferSize (10, 4); - - Assert.Equal (5, text.Length); - Assert.True (label.AutoSize); - Assert.Equal (new Rect (0, 0, 5, 1), label.Frame); - Assert.Equal (new Size (5, 1), label.TextFormatter.Size); - Assert.Equal (new List () { "Label" }, label.TextFormatter.Lines); - Assert.Equal (new Rect (0, 0, 10, 4), win.Frame); - Assert.Equal (new Rect (0, 0, 10, 4), Application.Top.Frame); - var expected = @" -┌────────┐ -│Label │ -│ │ -└────────┘ -"; - - var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (0, 0, 10, 4), pos); - - text = "0123456789"; - Assert.Equal (10, text.Length); - label.Width = Dim.Fill () - text.Length; - Application.Refresh (); - - Assert.Equal (new Rect (0, 0, 5, 1), label.Frame); - Assert.Equal (new Size (5, 1), label.TextFormatter.Size); - var exception = Record.Exception (() => Assert.Single (label.TextFormatter.Lines)); - Assert.Null (exception); - expected = @" -┌────────┐ -│Label │ -│ │ -└────────┘ -"; - - pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (0, 0, 10, 4), pos); - } - - [Fact, AutoInitShutdown] - public void AutoSize_False_Label_Height_Zero_Returns_Minimum_Height () - { - var text = "Label"; - var label = new Label () { - Width = Dim.Fill () - text.Length, - Text = text, - AutoSize = false - }; - var win = new Window () { - Width = Dim.Fill (), - Height = Dim.Fill () - }; - win.Add (label); - Application.Top.Add (win); - Application.Begin (Application.Top); - ((FakeDriver)Application.Driver).SetBufferSize (10, 4); - - Assert.Equal (5, text.Length); - Assert.False (label.AutoSize); - Assert.Equal (new Rect (0, 0, 3, 1), label.Frame); - Assert.Equal (new Size (3, 1), label.TextFormatter.Size); - Assert.Single (label.TextFormatter.Lines); - Assert.Equal (new Rect (0, 0, 10, 4), win.Frame); - Assert.Equal (new Rect (0, 0, 10, 4), Application.Top.Frame); - var expected = @" -┌────────┐ -│Lab │ -│ │ -└────────┘ -"; - - var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (0, 0, 10, 4), pos); - - text = "0123456789"; - Assert.Equal (10, text.Length); - label.Width = Dim.Fill () - text.Length; - Application.Refresh (); - - Assert.Equal (new Rect (0, 0, 0, 1), label.Frame); - Assert.Equal (new Size (0, 1), label.TextFormatter.Size); - var exception = Record.Exception (() => Assert.Equal (new List () { string.Empty }, label.TextFormatter.Lines)); - Assert.Null (exception); - expected = @" -┌────────┐ -│ │ -│ │ -└────────┘ -"; - - pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (0, 0, 10, 4), pos); - } - - [Fact, AutoInitShutdown] - public void AutoSize_True_View_IsEmpty_False_Minimum_Width () - { - var text = "Views"; - var view = new View () { - TextDirection = TextDirection.TopBottom_LeftRight, - Height = Dim.Fill () - text.Length, - Text = text, - AutoSize = true - }; - var win = new Window () { - Width = Dim.Fill (), - Height = Dim.Fill () - }; - win.Add (view); - Application.Top.Add (win); - Application.Begin (Application.Top); - ((FakeDriver)Application.Driver).SetBufferSize (4, 10); - - Assert.Equal (5, text.Length); - Assert.True (view.AutoSize); - Assert.Equal (new Rect (0, 0, 1, 5), view.Frame); - Assert.Equal (new Size (1, 5), view.TextFormatter.Size); - Assert.Equal (new List () { "Views" }, view.TextFormatter.Lines); - Assert.Equal (new Rect (0, 0, 4, 10), win.Frame); - Assert.Equal (new Rect (0, 0, 4, 10), Application.Top.Frame); - var expected = @" -┌──┐ -│V │ -│i │ -│e │ -│w │ -│s │ -│ │ -│ │ -│ │ -└──┘ -"; - - var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (0, 0, 4, 10), pos); - - text = "0123456789"; - Assert.Equal (10, text.Length); - view.Height = Dim.Fill () - text.Length; - Application.Refresh (); - - Assert.Equal (new Rect (0, 0, 1, 5), view.Frame); - Assert.Equal (new Size (1, 5), view.TextFormatter.Size); - var exception = Record.Exception (() => Assert.Single (view.TextFormatter.Lines)); - Assert.Null (exception); - expected = @" -┌──┐ -│V │ -│i │ -│e │ -│w │ -│s │ -│ │ -│ │ -│ │ -└──┘ -"; - - pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (0, 0, 4, 10), pos); - } - - [Fact, AutoInitShutdown] - public void AutoSize_False_View_Width_Null_Returns_Host_Frame_Width () - { - var text = "Views"; - var view = new View () { - TextDirection = TextDirection.TopBottom_LeftRight, - Height = Dim.Fill () - text.Length, - Text = text - }; - var win = new Window () { - Width = Dim.Fill (), - Height = Dim.Fill () - }; - win.Add (view); - Application.Top.Add (win); - Application.Begin (Application.Top); - ((FakeDriver)Application.Driver).SetBufferSize (4, 10); - - Assert.Equal (5, text.Length); - Assert.False (view.AutoSize); - Assert.Equal (new Rect (0, 0, 1, 3), view.Frame); - Assert.Equal (new Size (1, 3), view.TextFormatter.Size); - Assert.Single (view.TextFormatter.Lines); - Assert.Equal (new Rect (0, 0, 4, 10), win.Frame); - Assert.Equal (new Rect (0, 0, 4, 10), Application.Top.Frame); - var expected = @" -┌──┐ -│V │ -│i │ -│e │ -│ │ -│ │ -│ │ -│ │ -│ │ -└──┘ -"; - - var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (0, 0, 4, 10), pos); - - text = "0123456789"; - Assert.Equal (10, text.Length); - view.Height = Dim.Fill () - text.Length; - Application.Refresh (); - - Assert.Equal (new Rect (0, 0, 1, 0), view.Frame); - Assert.Equal (new Size (1, 0), view.TextFormatter.Size); - var exception = Record.Exception (() => Assert.Equal (new List () { string.Empty }, view.TextFormatter.Lines)); - Assert.Null (exception); - expected = @" -┌──┐ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -└──┘ -"; - - pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (0, 0, 4, 10), pos); - } - - [Fact, AutoInitShutdown] - public void AutoSize_True_View_IsEmpty_False_Minimum_Width_Wide_Rune () - { - var text = "界View"; - var view = new View () { - TextDirection = TextDirection.TopBottom_LeftRight, - Height = Dim.Fill () - text.Length, - Text = text, - AutoSize = true - }; - var win = new Window () { - Width = Dim.Fill (), - Height = Dim.Fill () - }; - win.Add (view); - Application.Top.Add (win); - Application.Begin (Application.Top); - ((FakeDriver)Application.Driver).SetBufferSize (4, 10); - - Assert.Equal (5, text.Length); - Assert.True (view.AutoSize); - Assert.Equal (new Rect (0, 0, 2, 5), view.Frame); - Assert.Equal (new Size (2, 5), view.TextFormatter.Size); - Assert.Equal (new List () { "界View" }, view.TextFormatter.Lines); - Assert.Equal (new Rect (0, 0, 4, 10), win.Frame); - Assert.Equal (new Rect (0, 0, 4, 10), Application.Top.Frame); - var expected = @" -┌──┐ -│界│ -│V │ -│i │ -│e │ -│w │ -│ │ -│ │ -│ │ -└──┘ -"; - - var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (0, 0, 4, 10), pos); - - text = "0123456789"; - Assert.Equal (10, text.Length); - view.Height = Dim.Fill () - text.Length; - Application.Refresh (); - - Assert.Equal (new Rect (0, 0, 2, 5), view.Frame); - Assert.Equal (new Size (2, 5), view.TextFormatter.Size); - var exception = Record.Exception (() => Assert.Equal (new List () { "界View" }, view.TextFormatter.Lines)); - Assert.Null (exception); - expected = @" -┌──┐ -│界│ -│V │ -│i │ -│e │ -│w │ -│ │ -│ │ -│ │ -└──┘ -"; - - pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (0, 0, 4, 10), pos); - } - - [Fact, AutoInitShutdown] - public void AutoSize_False_View_Width_Zero_Returns_Minimum_Width_With_Wide_Rune () - { - var text = "界View"; - var view = new View () { - TextDirection = TextDirection.TopBottom_LeftRight, - Height = Dim.Fill () - text.Length, - Text = text - }; - var win = new Window () { - Width = Dim.Fill (), - Height = Dim.Fill () - }; - win.Add (view); - Application.Top.Add (win); - Application.Begin (Application.Top); - ((FakeDriver)Application.Driver).SetBufferSize (4, 10); - - Assert.Equal (5, text.Length); - Assert.False (view.AutoSize); - Assert.Equal (new Rect (0, 0, 2, 3), view.Frame); - Assert.Equal (new Size (2, 3), view.TextFormatter.Size); - Assert.Single (view.TextFormatter.Lines); - Assert.Equal (new Rect (0, 0, 4, 10), win.Frame); - Assert.Equal (new Rect (0, 0, 4, 10), Application.Top.Frame); - var expected = @" -┌──┐ -│界│ -│V │ -│i │ -│ │ -│ │ -│ │ -│ │ -│ │ -└──┘ -"; - - var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (0, 0, 4, 10), pos); - - text = "0123456789"; - Assert.Equal (10, text.Length); - view.Height = Dim.Fill () - text.Length; - Application.Refresh (); - - Assert.Equal (new Rect (0, 0, 2, 0), view.Frame); - Assert.Equal (new Size (2, 0), view.TextFormatter.Size); - var exception = Record.Exception (() => Assert.Equal (new List () { string.Empty }, view.TextFormatter.Lines)); - Assert.Null (exception); - expected = @" -┌──┐ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -└──┘ -"; - - pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (0, 0, 4, 10), pos); - } - } -} diff --git a/UnitTests/View/Layout/CoordinateTests.cs b/UnitTests/View/Layout/CoordinateTests.cs index 3ce881ff54..8df71f7315 100644 --- a/UnitTests/View/Layout/CoordinateTests.cs +++ b/UnitTests/View/Layout/CoordinateTests.cs @@ -1,22 +1,15 @@ -using System; -using System.Text; -using Xunit; +using Xunit; using Xunit.Abstractions; -// Alias Console to MockConsole so we don't accidentally use Console -using Console = Terminal.Gui.FakeConsole; - namespace Terminal.Gui.ViewTests; + /// /// Tests for view coordinate mapping (e.g. etc...). /// public class CoordinateTests { readonly ITestOutputHelper _output; - public CoordinateTests (ITestOutputHelper output) - { - this._output = output; - } + public CoordinateTests (ITestOutputHelper output) => _output = output; /// /// Tests that screen to view mapping works correctly when the view has no superview and there are no Frames on the view. @@ -32,7 +25,7 @@ public CoordinateTests (ITestOutputHelper output) [InlineData (1, 1, 11, 11, 10, 10)] // it's ok for the view to return coordinates outside of its bounds public void ScreenToView_NoSuper_NoFrames (int viewX, int viewY, int x, int y, int expectedX, int expectedY) { - var view = new View () { + var view = new View { X = viewX, Y = viewY, Width = 10, @@ -58,7 +51,7 @@ public void ScreenToView_NoSuper_NoFrames (int viewX, int viewY, int x, int y, i [InlineData (1, 1, 11, 11, 10, 10)] // it's ok for the view to return coordinates outside of its bounds public void ScreenToView_NoSuper_HasFrames (int viewX, int viewY, int x, int y, int expectedX, int expectedY) { - var view = new View () { + var view = new View { X = viewX, Y = viewY, Width = 10, @@ -85,13 +78,13 @@ public void ScreenToView_NoSuper_HasFrames (int viewX, int viewY, int x, int y, [InlineData (1, 1, 11, 11, 10, 10)] // it's ok for the view to return coordinates outside of its bounds public void ScreenToView_SuperHasNoFrames (int viewX, int viewY, int x, int y, int expectedX, int expectedY) { - var super = new View () { + var super = new View { X = 0, Y = 0, Width = 10, Height = 10 }; - var view = new View () { + var view = new View { X = viewX, Y = viewY, Width = 5, @@ -118,14 +111,14 @@ public void ScreenToView_SuperHasNoFrames (int viewX, int viewY, int x, int y, i [InlineData (1, 1, 11, 11, 9, 9)] // it's ok for the view to return coordinates outside of its bounds public void ScreenToView_SuperHasFrames (int viewX, int viewY, int x, int y, int expectedX, int expectedY) { - var super = new View () { + var super = new View { X = 0, Y = 0, Width = 10, Height = 10, BorderStyle = LineStyle.Single }; - var view = new View () { + var view = new View { X = viewX, Y = viewY, Width = 5, @@ -154,7 +147,7 @@ public void ScreenToView_SuperHasFrames (int viewX, int viewY, int x, int y, int [InlineData (1, 1, 11, 11, 10, 10)] // it's ok for the view to return coordinates outside of its bounds public void ScreenToBounds_NoSuper_NoFrames (int viewX, int viewY, int x, int y, int expectedX, int expectedY) { - var view = new View () { + var view = new View { X = viewX, Y = viewY, Width = 10, @@ -173,14 +166,14 @@ public void ScreenToBounds_NoSuper_NoFrames (int viewX, int viewY, int x, int y, [InlineData (0, 0, 0, 0, -1, -1)] [InlineData (0, 0, 1, 1, 0, 0)] [InlineData (0, 0, 9, 9, 8, 8)] - [InlineData (0, 0, 11, 11, 10, 10)] + [InlineData (0, 0, 11, 11, 10, 10)] [InlineData (1, 1, 0, 0, -2, -2)] [InlineData (1, 1, 1, 1, -1, -1)] [InlineData (1, 1, 9, 9, 7, 7)] - [InlineData (1, 1, 11, 11, 9, 9)] + [InlineData (1, 1, 11, 11, 9, 9)] public void ScreenToBounds_NoSuper_HasFrames (int viewX, int viewY, int x, int y, int expectedX, int expectedY) { - var view = new View () { + var view = new View { X = viewX, Y = viewY, Width = 10, @@ -207,13 +200,13 @@ public void ScreenToBounds_NoSuper_HasFrames (int viewX, int viewY, int x, int y [InlineData (1, 1, 11, 11, 10, 10)] // it's ok for the view to return coordinates outside of its bounds public void ScreenToBounds_SuperHasNoFrames (int viewX, int viewY, int x, int y, int expectedX, int expectedY) { - var super = new View () { + var super = new View { X = 0, Y = 0, Width = 10, Height = 10 }; - var view = new View () { + var view = new View { X = viewX, Y = viewY, Width = 5, @@ -240,14 +233,14 @@ public void ScreenToBounds_SuperHasNoFrames (int viewX, int viewY, int x, int y, [InlineData (1, 1, 11, 11, 9, 9)] // it's ok for the view to return coordinates outside of its bounds public void ScreenToBounds_SuperHasFrames (int viewX, int viewY, int x, int y, int expectedX, int expectedY) { - var super = new View () { + var super = new View { X = 0, Y = 0, Width = 10, Height = 10, BorderStyle = LineStyle.Single }; - var view = new View () { + var view = new View { X = viewX, Y = viewY, Width = 5, diff --git a/UnitTests/View/Layout/DimTests.cs b/UnitTests/View/Layout/DimTests.cs index f12452e11f..f29473befb 100644 --- a/UnitTests/View/Layout/DimTests.cs +++ b/UnitTests/View/Layout/DimTests.cs @@ -1,10 +1,10 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.Text; using System.Threading; using Xunit; using Xunit.Abstractions; - // Alias Console to MockConsole so we don't accidentally use Console using Console = Terminal.Gui.FakeConsole; @@ -16,7 +16,7 @@ public class DimTests { public DimTests (ITestOutputHelper output) { _output = output; - Console.OutputEncoding = System.Text.Encoding.Default; + Console.OutputEncoding = Encoding.Default; // Change current culture var culture = CultureInfo.CreateSpecificCulture ("en-US"); Thread.CurrentThread.CurrentCulture = culture; @@ -36,7 +36,7 @@ public void Sized_SetsValue () var dim = Dim.Sized (0); Assert.Equal ("Absolute(0)", dim.ToString ()); - int testVal = 5; + var testVal = 5; dim = Dim.Sized (testVal); Assert.Equal ($"Absolute({testVal})", dim.ToString ()); @@ -48,8 +48,8 @@ public void Sized_SetsValue () [Fact] public void Sized_Equals () { - int n1 = 0; - int n2 = 0; + var n1 = 0; + var n2 = 0; var dim1 = Dim.Sized (n1); var dim2 = Dim.Sized (n2); Assert.Equal (dim1, dim2); @@ -78,23 +78,25 @@ public void Width_Set_To_Null_Throws () Assert.Throws (() => dim.ToString ()); } - [Fact, TestRespondersDisposed] - public void SetsValue () - { - var testVal = Rect.Empty; - var testValView = new View (testVal); - var dim = Dim.Width (testValView); - Assert.Equal ($"View(Width,View(){testVal})", dim.ToString ()); - testValView.Dispose (); - - testVal = new Rect (1, 2, 3, 4); - testValView = new View (testVal); - dim = Dim.Width (testValView); - Assert.Equal ($"View(Width,View(){testVal})", dim.ToString ()); - testValView.Dispose (); - } + [Fact] + [TestRespondersDisposed] + public void SetsValue () + { + var testVal = Rect.Empty; + var testValView = new View (testVal); + var dim = Dim.Width (testValView); + Assert.Equal ($"View(Width,View(){testVal})", dim.ToString ()); + testValView.Dispose (); + + testVal = new Rect (1, 2, 3, 4); + testValView = new View (testVal); + dim = Dim.Width (testValView); + Assert.Equal ($"View(Width,View(){testVal})", dim.ToString ()); + testValView.Dispose (); + } - [Fact] [TestRespondersDisposed] + [Fact] + [TestRespondersDisposed] public void Width_Equals () { var testRect1 = Rect.Empty; @@ -147,28 +149,29 @@ public void Height_Set_To_Null_Throws () Assert.Throws (() => dim.ToString ()); } - [Fact, TestRespondersDisposed] - public void Height_SetsValue () - { - var testVal = Rect.Empty; - var testValview = new View (testVal); - var dim = Dim.Height (testValview); - Assert.Equal ($"View(Height,View(){testVal})", dim.ToString ()); - testValview.Dispose (); - - testVal = new Rect (1, 2, 3, 4); - testValview = new View (testVal); - dim = Dim.Height (testValview); - Assert.Equal ($"View(Height,View(){testVal})", dim.ToString ()); - testValview.Dispose (); - } + [Fact] + [TestRespondersDisposed] + public void Height_SetsValue () + { + var testVal = Rect.Empty; + var testValview = new View (testVal); + var dim = Dim.Height (testValview); + Assert.Equal ($"View(Height,View(){testVal})", dim.ToString ()); + testValview.Dispose (); + + testVal = new Rect (1, 2, 3, 4); + testValview = new View (testVal); + dim = Dim.Height (testValview); + Assert.Equal ($"View(Height,View(){testVal})", dim.ToString ()); + testValview.Dispose (); + } // TODO: Other Dim.Height tests (e.g. Equal?) [Fact] public void Fill_SetsValue () { - int testMargin = 0; + var testMargin = 0; var dim = Dim.Fill (); Assert.Equal ($"Fill({testMargin})", dim.ToString ()); @@ -184,8 +187,8 @@ public void Fill_SetsValue () [Fact] public void Fill_Equal () { - int margin1 = 0; - int margin2 = 0; + var margin1 = 0; + var margin2 = 0; var dim1 = Dim.Fill (margin1); var dim2 = Dim.Fill (margin2); Assert.Equal (dim1, dim2); @@ -262,44 +265,10 @@ public void Percent_Invalid_Throws () Assert.Throws (() => dim = Dim.Percent (1000001)); } - [Fact] [AutoInitShutdown] - public void ForceValidatePosDim_True_Dim_Validation_If_NewValue_Is_DimAbsolute_And_OldValue_Is_Another_Type_Throws () - { - var t = Application.Top; - - var w = new Window () { - Width = Dim.Fill (0), - Height = Dim.Sized (10) - }; - var v = new View ("v") { - Width = Dim.Width (w) - 2, - Height = Dim.Percent (10), - ValidatePosDim = true - }; - - w.Add (v); - t.Add (w); - - t.Ready += (s, e) => { - Assert.Equal (2, w.Width = 2); - Assert.Equal (2, w.Height = 2); - Assert.Throws (() => v.Width = 2); - Assert.Throws (() => v.Height = 2); - v.ValidatePosDim = false; - var exception = Record.Exception (() => v.Width = 2); - Assert.Null (exception); - Assert.Equal (2, v.Width); - exception = Record.Exception (() => v.Height = 2); - Assert.Null (exception); - Assert.Equal (2, v.Height); - }; - - Application.Iteration += (s, a) => Application.RequestStop (); - - Application.Run (); - } - - [Fact] [TestRespondersDisposed] + // TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved + // TODO: A new test that calls SetRelativeLayout directly is needed. + [Fact] + [TestRespondersDisposed] public void Dim_Validation_Do_Not_Throws_If_NewValue_Is_DimAbsolute_And_OldValue_Is_Null () { var t = new View ("top") { Width = 80, Height = 25 }; @@ -313,13 +282,16 @@ public void Dim_Validation_Do_Not_Throws_If_NewValue_Is_DimAbsolute_And_OldValue t.Dispose (); } - [Fact] [TestRespondersDisposed] + // TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved + // TODO: A new test that calls SetRelativeLayout directly is needed. + [Fact] + [TestRespondersDisposed] public void Dim_Validation_Do_Not_Throws_If_NewValue_Is_DimAbsolute_And_OldValue_Is_Another_Type_After_Sets_To_LayoutStyle_Absolute () { var t = new View ("top") { Width = 80, Height = 25 }; - var w = new Window () { - Width = Dim.Fill (0), + var w = new Window { + Width = Dim.Fill (), Height = Dim.Sized (10) }; var v = new View ("v") { @@ -330,11 +302,17 @@ public void Dim_Validation_Do_Not_Throws_If_NewValue_Is_DimAbsolute_And_OldValue w.Add (v); t.Add (w); + Assert.Equal (LayoutStyle.Absolute, t.LayoutStyle); + Assert.Equal (LayoutStyle.Computed, w.LayoutStyle); + Assert.Equal (LayoutStyle.Computed, v.LayoutStyle); + t.LayoutSubviews (); Assert.Equal (2, v.Width = 2); Assert.Equal (2, v.Height = 2); - v.LayoutStyle = LayoutStyle.Absolute; + // Force v to be LayoutStyle.Absolute; + v.Frame = new Rect (0, 1, 3, 4); + Assert.Equal (LayoutStyle.Absolute, v.LayoutStyle); t.LayoutSubviews (); Assert.Equal (2, v.Width = 2); @@ -342,13 +320,16 @@ public void Dim_Validation_Do_Not_Throws_If_NewValue_Is_DimAbsolute_And_OldValue t.Dispose (); } - [Fact] [AutoInitShutdown] + // TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved + // TODO: A new test that calls SetRelativeLayout directly is needed. + [Fact] + [AutoInitShutdown] public void Only_DimAbsolute_And_DimFactor_As_A_Different_Procedure_For_Assigning_Value_To_Width_Or_Height () { // Testing with the Button because it properly handles the Dim class. var t = Application.Top; - var w = new Window () { + var w = new Window { Width = 100, Height = 100 }; @@ -434,19 +415,19 @@ public void Only_DimAbsolute_And_DimFactor_As_A_Different_Procedure_For_Assignin Assert.Equal (49, f2.Frame.Width); // 50-1=49 Assert.Equal (5, f2.Frame.Height); - Assert.Equal ("Combine(View(Width,FrameView(f1)(0,0,49,5))-Absolute(2))", v1.Width.ToString ()); - Assert.Equal ("Combine(Fill(0)-Absolute(2))", v1.Height.ToString ()); - Assert.Equal (47, v1.Frame.Width); // 49-2=47 - Assert.Equal (89, v1.Frame.Height); // 98-5-2-2=89 + Assert.Equal ("Combine(View(Width,FrameView(f1)(0,0,49,5))-Absolute(2))", v1.Width.ToString ()); + Assert.Equal ("Combine(Fill(0)-Absolute(2))", v1.Height.ToString ()); + Assert.Equal (47, v1.Frame.Width); // 49-2=47 + Assert.Equal (89, v1.Frame.Height); // 98-5-2-2=89 - Assert.Equal ("Combine(View(Width,FrameView(f2)(49,0,49,5))-Absolute(2))", v2.Width.ToString ()); - Assert.Equal ("Combine(Fill(0)-Absolute(2))", v2.Height.ToString ()); - Assert.Equal (47, v2.Frame.Width); // 49-2=47 - Assert.Equal (89, v2.Frame.Height); // 98-5-2-2=89 + Assert.Equal ("Combine(View(Width,FrameView(f2)(49,0,49,5))-Absolute(2))", v2.Width.ToString ()); + Assert.Equal ("Combine(Fill(0)-Absolute(2))", v2.Height.ToString ()); + Assert.Equal (47, v2.Frame.Width); // 49-2=47 + Assert.Equal (89, v2.Frame.Height); // 98-5-2-2=89 Assert.Equal ("Factor(0.1,False)", v3.Width.ToString ()); Assert.Equal ("Factor(0.1,False)", v3.Height.ToString ()); - Assert.Equal (9, v3.Frame.Width); // 98*10%=9 + Assert.Equal (9, v3.Frame.Width); // 98*10%=9 Assert.Equal (9, v3.Frame.Height); // 98*10%=9 Assert.Equal ("Absolute(50)", v4.Width.ToString ()); @@ -454,14 +435,14 @@ public void Only_DimAbsolute_And_DimFactor_As_A_Different_Procedure_For_Assignin Assert.Equal (50, v4.Frame.Width); Assert.Equal (50, v4.Frame.Height); - Assert.Equal ("Combine(View(Width,Button(v1)(2,7,47,89))-View(Width,Button(v3)(0,0,9,9)))", v5.Width.ToString ()); - Assert.Equal ("Combine(View(Height,Button(v1)(2,7,47,89))-View(Height,Button(v3)(0,0,9,9)))", v5.Height.ToString ()); - Assert.Equal (38, v5.Frame.Width); // 47-9=38 - Assert.Equal (80, v5.Frame.Height); // 89-9=80 + Assert.Equal ("Combine(View(Width,Button(v1)(2,7,47,89))-View(Width,Button(v3)(0,0,9,9)))", v5.Width.ToString ()); + Assert.Equal ("Combine(View(Height,Button(v1)(2,7,47,89))-View(Height,Button(v3)(0,0,9,9)))", v5.Height.ToString ()); + Assert.Equal (38, v5.Frame.Width); // 47-9=38 + Assert.Equal (80, v5.Frame.Height); // 89-9=80 Assert.Equal ("Factor(0.2,True)", v6.Width.ToString ()); Assert.Equal ("Factor(0.2,True)", v6.Height.ToString ()); - Assert.Equal (9, v6.Frame.Width); // 47*20%=9 + Assert.Equal (9, v6.Frame.Width); // 47*20%=9 Assert.Equal (18, v6.Frame.Height); // 89*20%=18 w.Width = 200; @@ -486,22 +467,22 @@ public void Only_DimAbsolute_And_DimFactor_As_A_Different_Procedure_For_Assignin Assert.Equal (99, f2.Frame.Width); // 100-1=99 Assert.Equal (5, f2.Frame.Height); - v1.Text = "Button1"; - Assert.Equal ("Combine(View(Width,FrameView(f1)(0,0,99,5))-Absolute(2))", v1.Width.ToString ()); - Assert.Equal ("Combine(Fill(0)-Absolute(2))", v1.Height.ToString ()); - Assert.Equal (97, v1.Frame.Width); // 99-2=97 - Assert.Equal (189, v1.Frame.Height); // 198-2-7=189 + v1.Text = "Button1"; + Assert.Equal ("Combine(View(Width,FrameView(f1)(0,0,99,5))-Absolute(2))", v1.Width.ToString ()); + Assert.Equal ("Combine(Fill(0)-Absolute(2))", v1.Height.ToString ()); + Assert.Equal (97, v1.Frame.Width); // 99-2=97 + Assert.Equal (189, v1.Frame.Height); // 198-2-7=189 - v2.Text = "Button2"; - Assert.Equal ("Combine(View(Width,FrameView(f2)(99,0,99,5))-Absolute(2))", v2.Width.ToString ()); - Assert.Equal ("Combine(Fill(0)-Absolute(2))", v2.Height.ToString ()); - Assert.Equal (97, v2.Frame.Width); // 99-2=97 - Assert.Equal (189, v2.Frame.Height); // 198-2-7=189 + v2.Text = "Button2"; + Assert.Equal ("Combine(View(Width,FrameView(f2)(99,0,99,5))-Absolute(2))", v2.Width.ToString ()); + Assert.Equal ("Combine(Fill(0)-Absolute(2))", v2.Height.ToString ()); + Assert.Equal (97, v2.Frame.Width); // 99-2=97 + Assert.Equal (189, v2.Frame.Height); // 198-2-7=189 v3.Text = "Button3"; Assert.Equal ("Factor(0.1,False)", v3.Width.ToString ()); Assert.Equal ("Factor(0.1,False)", v3.Height.ToString ()); - Assert.Equal (19, v3.Frame.Width); // 198*10%=19 * Percent is related to the super-view if it isn't null otherwise the view width + Assert.Equal (19, v3.Frame.Width); // 198*10%=19 * Percent is related to the super-view if it isn't null otherwise the view width Assert.Equal (19, v3.Frame.Height); // 199*10%=19 v4.Text = "Button4"; @@ -513,19 +494,19 @@ public void Only_DimAbsolute_And_DimFactor_As_A_Different_Procedure_For_Assignin v4.AutoSize = true; Assert.Equal ("Absolute(11)", v4.Width.ToString ()); Assert.Equal ("Absolute(1)", v4.Height.ToString ()); - Assert.Equal (11, v4.Frame.Width); // 11 is the text length and because is Dim.DimAbsolute + Assert.Equal (11, v4.Frame.Width); // 11 is the text length and because is Dim.DimAbsolute Assert.Equal (1, v4.Frame.Height); // 1 because is Dim.DimAbsolute - v5.Text = "Button5"; - Assert.Equal ("Combine(View(Width,Button(v1)(2,7,97,189))-View(Width,Button(v3)(0,0,19,19)))", v5.Width.ToString ()); - Assert.Equal ("Combine(View(Height,Button(v1)(2,7,97,189))-View(Height,Button(v3)(0,0,19,19)))", v5.Height.ToString ()); - Assert.Equal (78, v5.Frame.Width); // 97-9=78 - Assert.Equal (170, v5.Frame.Height); // 189-19=170 + v5.Text = "Button5"; + Assert.Equal ("Combine(View(Width,Button(v1)(2,7,97,189))-View(Width,Button(v3)(0,0,19,19)))", v5.Width.ToString ()); + Assert.Equal ("Combine(View(Height,Button(v1)(2,7,97,189))-View(Height,Button(v3)(0,0,19,19)))", v5.Height.ToString ()); + Assert.Equal (78, v5.Frame.Width); // 97-9=78 + Assert.Equal (170, v5.Frame.Height); // 189-19=170 v6.Text = "Button6"; Assert.Equal ("Factor(0.2,True)", v6.Width.ToString ()); Assert.Equal ("Factor(0.2,True)", v6.Height.ToString ()); - Assert.Equal (19, v6.Frame.Width); // 99*20%=19 + Assert.Equal (19, v6.Frame.Width); // 99*20%=19 Assert.Equal (38, v6.Frame.Height); // 198-7*20=18 }; @@ -553,25 +534,29 @@ public void Only_DimAbsolute_And_DimFactor_As_A_Different_Procedure_For_Assignin // Assert.Throws (() => super.LayoutSubviews ()); //} + // TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved + // TODO: A new test that calls SetRelativeLayout directly is needed. + /// /// This is an intentionally obtuse test. See https://github.com/gui-cs/Terminal.Gui/issues/2461 /// - [Fact] [TestRespondersDisposed] + [Fact] + [TestRespondersDisposed] public void DimCombine_ObtuseScenario_Throw_If_SuperView_Refs_SubView () { - var t = new View () { Width = 80, Height = 25 }; + var t = new View { Width = 80, Height = 25 }; - var w = new Window () { - Width = Dim.Width (t) - 2, // 78 + var w = new Window { + Width = Dim.Width (t) - 2, // 78 Height = Dim.Height (t) - 2 // 23 }; var f = new FrameView (); - var v1 = new View () { - Width = Dim.Width (w) - 2, // 76 + var v1 = new View { + Width = Dim.Width (w) - 2, // 76 Height = Dim.Height (w) - 2 // 21 }; - var v2 = new View () { - Width = Dim.Width (v1) - 2, // 74 + var v2 = new View { + Width = Dim.Width (v1) - 2, // 74 Height = Dim.Height (v1) - 2 // 19 }; @@ -581,9 +566,7 @@ public void DimCombine_ObtuseScenario_Throw_If_SuperView_Refs_SubView () t.BeginInit (); t.EndInit (); - // BUGBUG: v2 - f references t here; t is f's super-superview. This is supported! - // BUGBUG: v2 - f references v2 here; v2 is f's subview. This is not supported! - f.Width = Dim.Width (t) - Dim.Width (v2); // 80 - 74 = 6 + f.Width = Dim.Width (t) - Dim.Width (v2); // 80 - 74 = 6 f.Height = Dim.Height (t) - Dim.Height (v2); // 25 - 19 = 6 Assert.Throws (t.LayoutSubviews); @@ -591,32 +574,34 @@ public void DimCombine_ObtuseScenario_Throw_If_SuperView_Refs_SubView () Assert.Equal (25, t.Frame.Height); Assert.Equal (78, w.Frame.Width); Assert.Equal (23, w.Frame.Height); - // BUGBUG: v2 - this no longer works - see above - //Assert.Equal (6, f.Frame.Width); - //Assert.Equal (6, f.Frame.Height); - //Assert.Equal (76, v1.Frame.Width); - //Assert.Equal (21, v1.Frame.Height); - //Assert.Equal (74, v2.Frame.Width); - //Assert.Equal (19, v2.Frame.Height); + Assert.Equal (6, f.Frame.Width); + Assert.Equal (6, f.Frame.Height); + Assert.Equal (76, v1.Frame.Width); + Assert.Equal (21, v1.Frame.Height); + Assert.Equal (74, v2.Frame.Width); + Assert.Equal (19, v2.Frame.Height); t.Dispose (); } - [Fact] [TestRespondersDisposed] + // TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved + // TODO: A new test that calls SetRelativeLayout directly is needed. + [Fact] + [TestRespondersDisposed] public void DimCombine_ObtuseScenario_Does_Not_Throw_If_Two_SubViews_Refs_The_Same_SuperView () { var t = new View ("top") { Width = 80, Height = 25 }; - var w = new Window () { - Width = Dim.Width (t) - 2, // 78 + var w = new Window { + Width = Dim.Width (t) - 2, // 78 Height = Dim.Height (t) - 2 // 23 }; var f = new FrameView (); - var v1 = new View () { - Width = Dim.Width (w) - 2, // 76 + var v1 = new View { + Width = Dim.Width (w) - 2, // 76 Height = Dim.Height (w) - 2 // 21 }; - var v2 = new View () { - Width = Dim.Width (v1) - 2, // 74 + var v2 = new View { + Width = Dim.Width (v1) - 2, // 74 Height = Dim.Height (v1) - 2 // 19 }; @@ -626,7 +611,7 @@ public void DimCombine_ObtuseScenario_Does_Not_Throw_If_Two_SubViews_Refs_The_Sa t.BeginInit (); t.EndInit (); - f.Width = Dim.Width (t) - Dim.Width (w) + 4; // 80 - 74 = 6 + f.Width = Dim.Width (t) - Dim.Width (w) + 4; // 80 - 74 = 6 f.Height = Dim.Height (t) - Dim.Height (w) + 4; // 25 - 19 = 6 // BUGBUG: v2 - f references t and w here; t is f's super-superview and w is f's superview. This is supported! @@ -645,13 +630,15 @@ public void DimCombine_ObtuseScenario_Does_Not_Throw_If_Two_SubViews_Refs_The_Sa t.Dispose (); } - [Fact] [TestRespondersDisposed] + // TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved + // TODO: A new test that calls SetRelativeLayout directly is needed. + [Fact] + [TestRespondersDisposed] public void PosCombine_View_Not_Added_Throws () { - var t = new View () { Width = 80, Height = 50 }; + var t = new View { Width = 80, Height = 50 }; - // BUGBUG: v2 - super should not reference it's superview (t) - var super = new View () { + var super = new View { Width = Dim.Width (t) - 2, Height = Dim.Height (t) - 2 }; @@ -660,11 +647,11 @@ public void PosCombine_View_Not_Added_Throws () var sub = new View (); super.Add (sub); - var v1 = new View () { + var v1 = new View { Width = Dim.Width (super) - 2, Height = Dim.Height (super) - 2 }; - var v2 = new View () { + var v2 = new View { Width = Dim.Width (v1) - 2, Height = Dim.Height (v1) - 2 }; @@ -675,20 +662,23 @@ public void PosCombine_View_Not_Added_Throws () t.BeginInit (); t.EndInit (); - + Assert.Throws (() => t.LayoutSubviews ()); t.Dispose (); v2.Dispose (); } - [Fact] [AutoInitShutdown] + // TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved + // A new test that does not depend on Application is needed. + [Fact] + [AutoInitShutdown] public void Dim_Add_Operator () { var top = Application.Top; - var view = new View () { X = 0, Y = 0, Width = 20, Height = 0 }; - var field = new TextField () { X = 0, Y = Pos.Bottom (view), Width = 20 }; - int count = 0; + var view = new View { X = 0, Y = 0, Width = 20, Height = 0 }; + var field = new TextField { X = 0, Y = Pos.Bottom (view), Width = 20 }; + var count = 0; field.KeyDown += (s, k) => { if (k.KeyCode == KeyCode.Enter) { @@ -724,394 +714,17 @@ public void Dim_Add_Operator () Assert.Equal (20, count); } - string [] expecteds = new string [21] { - @" -┌────────────────────┐ -│View with long text │ -│ │ -└────────────────────┘", - @" -┌────────────────────┐ -│View with long text │ -│Label 0 │ -│Label 0 │ -└────────────────────┘", - @" -┌────────────────────┐ -│View with long text │ -│Label 0 │ -│Label 1 │ -│Label 1 │ -└────────────────────┘", - @" -┌────────────────────┐ -│View with long text │ -│Label 0 │ -│Label 1 │ -│Label 2 │ -│Label 2 │ -└────────────────────┘", - @" -┌────────────────────┐ -│View with long text │ -│Label 0 │ -│Label 1 │ -│Label 2 │ -│Label 3 │ -│Label 3 │ -└────────────────────┘", - @" -┌────────────────────┐ -│View with long text │ -│Label 0 │ -│Label 1 │ -│Label 2 │ -│Label 3 │ -│Label 4 │ -│Label 4 │ -└────────────────────┘", - @" -┌────────────────────┐ -│View with long text │ -│Label 0 │ -│Label 1 │ -│Label 2 │ -│Label 3 │ -│Label 4 │ -│Label 5 │ -│Label 5 │ -└────────────────────┘", - @" -┌────────────────────┐ -│View with long text │ -│Label 0 │ -│Label 1 │ -│Label 2 │ -│Label 3 │ -│Label 4 │ -│Label 5 │ -│Label 6 │ -│Label 6 │ -└────────────────────┘", - @" -┌────────────────────┐ -│View with long text │ -│Label 0 │ -│Label 1 │ -│Label 2 │ -│Label 3 │ -│Label 4 │ -│Label 5 │ -│Label 6 │ -│Label 7 │ -│Label 7 │ -└────────────────────┘", - @" -┌────────────────────┐ -│View with long text │ -│Label 0 │ -│Label 1 │ -│Label 2 │ -│Label 3 │ -│Label 4 │ -│Label 5 │ -│Label 6 │ -│Label 7 │ -│Label 8 │ -│Label 8 │ -└────────────────────┘", - @" -┌────────────────────┐ -│View with long text │ -│Label 0 │ -│Label 1 │ -│Label 2 │ -│Label 3 │ -│Label 4 │ -│Label 5 │ -│Label 6 │ -│Label 7 │ -│Label 8 │ -│Label 9 │ -│Label 9 │ -└────────────────────┘", - @" -┌────────────────────┐ -│View with long text │ -│Label 0 │ -│Label 1 │ -│Label 2 │ -│Label 3 │ -│Label 4 │ -│Label 5 │ -│Label 6 │ -│Label 7 │ -│Label 8 │ -│Label 9 │ -│Label 10 │ -│Label 10 │ -└────────────────────┘", - @" -┌────────────────────┐ -│View with long text │ -│Label 0 │ -│Label 1 │ -│Label 2 │ -│Label 3 │ -│Label 4 │ -│Label 5 │ -│Label 6 │ -│Label 7 │ -│Label 8 │ -│Label 9 │ -│Label 10 │ -│Label 11 │ -│Label 11 │ -└────────────────────┘", - @" -┌────────────────────┐ -│View with long text │ -│Label 0 │ -│Label 1 │ -│Label 2 │ -│Label 3 │ -│Label 4 │ -│Label 5 │ -│Label 6 │ -│Label 7 │ -│Label 8 │ -│Label 9 │ -│Label 10 │ -│Label 11 │ -│Label 12 │ -│Label 12 │ -└────────────────────┘", - @" -┌────────────────────┐ -│View with long text │ -│Label 0 │ -│Label 1 │ -│Label 2 │ -│Label 3 │ -│Label 4 │ -│Label 5 │ -│Label 6 │ -│Label 7 │ -│Label 8 │ -│Label 9 │ -│Label 10 │ -│Label 11 │ -│Label 12 │ -│Label 13 │ -│Label 13 │ -└────────────────────┘", - @" -┌────────────────────┐ -│View with long text │ -│Label 0 │ -│Label 1 │ -│Label 2 │ -│Label 3 │ -│Label 4 │ -│Label 5 │ -│Label 6 │ -│Label 7 │ -│Label 8 │ -│Label 9 │ -│Label 10 │ -│Label 11 │ -│Label 12 │ -│Label 13 │ -│Label 14 │ -│Label 14 │ -└────────────────────┘", - @" -┌────────────────────┐ -│View with long text │ -│Label 0 │ -│Label 1 │ -│Label 2 │ -│Label 3 │ -│Label 4 │ -│Label 5 │ -│Label 6 │ -│Label 7 │ -│Label 8 │ -│Label 9 │ -│Label 10 │ -│Label 11 │ -│Label 12 │ -│Label 13 │ -│Label 14 │ -│Label 15 │ -│Label 15 │ -└────────────────────┘", - @" -┌────────────────────┐ -│View with long text │ -│Label 0 │ -│Label 1 │ -│Label 2 │ -│Label 3 │ -│Label 4 │ -│Label 5 │ -│Label 6 │ -│Label 7 │ -│Label 8 │ -│Label 9 │ -│Label 10 │ -│Label 11 │ -│Label 12 │ -│Label 13 │ -│Label 14 │ -│Label 15 │ -│Label 16 │ -│Label 16 │ -└────────────────────┘", - @" -┌────────────────────┐ -│View with long text │ -│Label 0 │ -│Label 1 │ -│Label 2 │ -│Label 3 │ -│Label 4 │ -│Label 5 │ -│Label 6 │ -│Label 7 │ -│Label 8 │ -│Label 9 │ -│Label 10 │ -│Label 11 │ -│Label 12 │ -│Label 13 │ -│Label 14 │ -│Label 15 │ -│Label 16 │ -│Label 17 │ -│Label 17 │ -└────────────────────┘", - @" -┌────────────────────┐ -│View with long text │ -│Label 0 │ -│Label 1 │ -│Label 2 │ -│Label 3 │ -│Label 4 │ -│Label 5 │ -│Label 6 │ -│Label 7 │ -│Label 8 │ -│Label 9 │ -│Label 10 │ -│Label 11 │ -│Label 12 │ -│Label 13 │ -│Label 14 │ -│Label 15 │ -│Label 16 │ -│Label 17 │ -│Label 18 │ -│Label 18 │ -└────────────────────┘", - @" -┌────────────────────┐ -│View with long text │ -│Label 0 │ -│Label 1 │ -│Label 2 │ -│Label 3 │ -│Label 4 │ -│Label 5 │ -│Label 6 │ -│Label 7 │ -│Label 8 │ -│Label 9 │ -│Label 10 │ -│Label 11 │ -│Label 12 │ -│Label 13 │ -│Label 14 │ -│Label 15 │ -│Label 16 │ -│Label 17 │ -│Label 18 │ -│Label 19 │ -│Label 19 │ -└────────────────────┘" - }; - - [Fact] [AutoInitShutdown] - public void Dim_Add_Operator_With_Text () - { - var top = Application.Top; - - // BUGBUG: v2 - If a View's height is zero, it should not be drawn. - //// Although view height is zero the text it's draw due the SetMinWidthHeight method - var view = new View ("View with long text") { X = 0, Y = 0, Width = 20, Height = 1 }; - var field = new TextField () { X = 0, Y = Pos.Bottom (view), Width = 20 }; - int count = 0; - var listLabels = new List