diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs b/Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs index 566d7e1f3e..f18420c424 100644 --- a/Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs +++ b/Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs @@ -58,6 +58,9 @@ public static class ConsoleKeyMapping { /// the function returns 0. public static uint MapVKtoChar (VK vk) { + if (Environment.OSVersion.Platform != PlatformID.Win32NT) { + return 0; + } var tid = GetWindowThreadProcessId (GetForegroundWindow (), 0); var hkl = GetKeyboardLayout (tid); return MapVirtualKeyEx (vk, 2, hkl); @@ -85,7 +88,18 @@ public static uint MapVKtoChar (VK vk) /// /// [DllImport ("user32.dll")] - public extern static bool GetKeyboardLayoutName ([Out] StringBuilder pwszKLID); + extern static bool GetKeyboardLayoutName ([Out] StringBuilder pwszKLID); + + public static string GetKeyboardLayoutName () + { + if (Environment.OSVersion.Platform != PlatformID.Win32NT) { + return "none"; + } + + StringBuilder klidSB = new StringBuilder (); + GetKeyboardLayoutName (klidSB); + return klidSB.ToString (); + } class ScanCodeMapping : IEquatable { public uint ScanCode; diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver.cs index 591b49447b..58eae6264f 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver.cs @@ -3,14 +3,16 @@ // using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; -using Terminal.Gui.ConsoleDrivers; +using static Terminal.Gui.ConsoleDrivers.ConsoleKeyMapping; using static Terminal.Gui.NetEvents; +using static Terminal.Gui.WindowsConsole; namespace Terminal.Gui; @@ -585,6 +587,35 @@ public struct InputResult { public WindowSizeEvent WindowSizeEvent; public WindowPositionEvent WindowPositionEvent; public RequestResponseEvent RequestResponseEvent; + + public override readonly string ToString () + { + return EventType switch { + EventType.Key => ToString (ConsoleKeyInfo), + EventType.Mouse => MouseEvent.ToString (), + //EventType.WindowSize => WindowSize.ToString (), + //EventType.RequestResponse => RequestResponse.ToString (), + _ => "Unknown event type: " + EventType + }; + } + + /// + /// Prints a ConsoleKeyInfoEx structure + /// + /// + /// + public readonly string ToString (ConsoleKeyInfo cki) + { + var ke = new Key ((KeyCode)cki.KeyChar); + var sb = new StringBuilder (); + sb.Append ($"Key: {(KeyCode)cki.Key} ({cki.Key})"); + sb.Append ((cki.Modifiers & ConsoleModifiers.Shift) != 0 ? " | Shift" : string.Empty); + sb.Append ((cki.Modifiers & ConsoleModifiers.Control) != 0 ? " | Control" : string.Empty); + sb.Append ((cki.Modifiers & ConsoleModifiers.Alt) != 0 ? " | Alt" : string.Empty); + sb.Append ($", KeyChar: {ke.AsRune.MakePrintable ()} ({(uint)cki.KeyChar}) "); + var s = sb.ToString ().TrimEnd (',').TrimEnd (' '); + return $"[ConsoleKeyInfo({s})]"; + } } void HandleKeyboardEvent (ConsoleKeyInfo cki) @@ -877,7 +908,7 @@ void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref int out ); // Dictionary for mapping ConsoleColor values to the values used by System.Net.Console. - static Dictionary colorMap = new() { + static Dictionary colorMap = new () { { ConsoleColor.Black, COLOR_BLACK }, { ConsoleColor.DarkBlue, COLOR_BLUE }, { ConsoleColor.DarkGreen, COLOR_GREEN }, @@ -1100,7 +1131,7 @@ ConsoleKeyInfo FromVKPacketToKConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo) bool alt = (mod & ConsoleModifiers.Alt) != 0; bool control = (mod & ConsoleModifiers.Control) != 0; - var cKeyInfo = ConsoleKeyMapping.DecodeVKPacketToKConsoleKeyInfo (consoleKeyInfo); + var cKeyInfo = DecodeVKPacketToKConsoleKeyInfo (consoleKeyInfo); return new ConsoleKeyInfo (cKeyInfo.KeyChar, cKeyInfo.Key, shift, alt, control); } @@ -1108,37 +1139,11 @@ ConsoleKeyInfo FromVKPacketToKConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo) KeyCode MapKey (ConsoleKeyInfo keyInfo) { switch (keyInfo.Key) { - case ConsoleKey.Escape: - return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.Esc); - case ConsoleKey.Tab: - return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.Tab); - case ConsoleKey.Home: - return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.Home); - case ConsoleKey.End: - return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.End); - case ConsoleKey.LeftArrow: - return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.CursorLeft); - case ConsoleKey.RightArrow: - return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.CursorRight); - case ConsoleKey.UpArrow: - return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.CursorUp); - case ConsoleKey.DownArrow: - return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.CursorDown); - case ConsoleKey.PageUp: - return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.PageUp); - case ConsoleKey.PageDown: - return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.PageDown); - case ConsoleKey.Enter: - return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.Enter); - case ConsoleKey.Spacebar: - return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.Space); - case ConsoleKey.Backspace: - return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.Backspace); - case ConsoleKey.Delete: - return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.Delete); - case ConsoleKey.Insert: - return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.Insert); - + case ConsoleKey.OemPeriod: + case ConsoleKey.OemComma: + case ConsoleKey.OemPlus: + case ConsoleKey.OemMinus: + case ConsoleKey.Packet: case ConsoleKey.Oem1: case ConsoleKey.Oem2: case ConsoleKey.Oem3: @@ -1148,107 +1153,85 @@ KeyCode MapKey (ConsoleKeyInfo keyInfo) case ConsoleKey.Oem7: case ConsoleKey.Oem8: case ConsoleKey.Oem102: - var ret = ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)(uint)keyInfo.KeyChar); - if (ret.HasFlag (KeyCode.ShiftMask)) { - ret &= ~KeyCode.ShiftMask; + if (keyInfo.KeyChar == 0) { + // If the keyChar is 0, keyInfo.Key value is not a printable character. + + return KeyCode.Null;// MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode)keyInfo.Key); + } else { + if (keyInfo.Modifiers != ConsoleModifiers.Shift) { + // If Shift wasn't down we don't need to do anything but return the keyInfo.KeyChar + return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)(keyInfo.KeyChar)); + } + + // Strip off Shift - We got here because they KeyChar from Windows is the shifted char (e.g. "Ç") + // and passing on Shift would be redundant. + return MapToKeyCodeModifiers (keyInfo.Modifiers & ~ConsoleModifiers.Shift, (KeyCode)keyInfo.KeyChar); } - return ret; + break; + - case ConsoleKey.OemPeriod: - case ConsoleKey.OemComma: - case ConsoleKey.OemPlus: - case ConsoleKey.OemMinus: return (KeyCode)(uint)keyInfo.KeyChar; } var key = keyInfo.Key; - if (key is >= ConsoleKey.A and <= ConsoleKey.Z) { - int delta = key - ConsoleKey.A; - if (keyInfo.Modifiers == ConsoleModifiers.Control) { - return (KeyCode)((uint)KeyCode.CtrlMask | (uint)KeyCode.A + delta); - } - if (keyInfo.Modifiers == ConsoleModifiers.Alt) { - return (KeyCode)((uint)KeyCode.AltMask | (uint)KeyCode.A + delta); - } - if (keyInfo.Modifiers == (ConsoleModifiers.Shift | ConsoleModifiers.Alt)) { - return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)((uint)KeyCode.A + delta)); - } - if ((keyInfo.Modifiers & (ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) { - if (keyInfo.KeyChar == 0 || keyInfo.KeyChar != 0 && keyInfo.KeyChar >= 1 && keyInfo.KeyChar <= 26) { - return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)((uint)KeyCode.A + delta)); - } else if (keyInfo.KeyChar != 0) { - return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)keyInfo.KeyChar); - } + // A..Z are special cased: + // - Alone, they represent lowercase a...z + // - With ShiftMask they are A..Z + // - If CapsLock is on the above is reversed. + // - If Alt and/or Ctrl are present, treat as upper case + if (keyInfo.Key is >= ConsoleKey.A and <= ConsoleKey.Z) { + if (keyInfo.Modifiers.HasFlag (ConsoleModifiers.Alt) || keyInfo.Modifiers.HasFlag (ConsoleModifiers.Control)) { + return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)(uint)keyInfo.Key); } - if (keyInfo.Modifiers == ConsoleModifiers.Shift /*^ (keyInfoEx.CapsLock)*/) { - if (keyInfo.KeyChar <= (uint)KeyCode.Z) { - return (KeyCode)((uint)KeyCode.A + delta) | KeyCode.ShiftMask; - } - } - // This is buggy because is converting a lower case to a upper case and mustn't - //if (((KeyCode)((uint)keyInfo.KeyChar) & KeyCode.Space) == KeyCode.Space) { - // return (KeyCode)((uint)keyInfo.KeyChar) & ~KeyCode.Space; - //} - return (KeyCode)(uint)keyInfo.KeyChar; - } - if (key is >= ConsoleKey.D0 and <= ConsoleKey.D9) { - int delta = key - ConsoleKey.D0; - if (keyInfo.Modifiers == ConsoleModifiers.Alt) { - return (KeyCode)((uint)KeyCode.AltMask | (uint)KeyCode.D0 + delta); - } - if (keyInfo.Modifiers == ConsoleModifiers.Control) { - return (KeyCode)((uint)KeyCode.CtrlMask | (uint)KeyCode.D0 + delta); - } - if ((keyInfo.Modifiers & (ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) { - if (keyInfo.KeyChar == 0 || keyInfo.KeyChar == 30 || keyInfo.KeyChar == (uint)KeyCode.D0 + delta) { - return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)((uint)KeyCode.D0 + delta)); + if (keyInfo.Modifiers == ConsoleModifiers.Shift) { + // If ShiftMask is on add the ShiftMask + if (char.IsUpper (keyInfo.KeyChar)) { + return (KeyCode)((uint)keyInfo.Key) | KeyCode.ShiftMask; } } return (KeyCode)(uint)keyInfo.KeyChar; } - if (key is >= ConsoleKey.F1 and <= ConsoleKey.F12) { - int delta = key - ConsoleKey.F1; - if ((keyInfo.Modifiers & (ConsoleModifiers.Shift | ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) { - return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)((uint)KeyCode.F1 + delta)); - } - return (KeyCode)((uint)KeyCode.F1 + delta); + // Handle control keys whose VK codes match the related ASCII value (those below ASCII 33) like ESC + if (keyInfo.Key != ConsoleKey.None && Enum.IsDefined (typeof (KeyCode), (uint)keyInfo.Key)) { + return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)(keyInfo.Key)); } - // Is it a key between a..z? - if ((char)keyInfo.KeyChar is >= 'a' and <= 'z') { - // 'a' should be Key.A - return (KeyCode)(uint)keyInfo.KeyChar & ~KeyCode.Space; + // Handle control keys (e.g. CursorUp) + if (keyInfo.Key != ConsoleKey.None && Enum.IsDefined (typeof (KeyCode), ((uint)keyInfo.Key + (uint)KeyCode.MaxCodePoint))) { + return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)((uint)keyInfo.Key + (uint)KeyCode.MaxCodePoint)); } - // Is it a key between A..Z? - if (((KeyCode)(uint)keyInfo.KeyChar & ~KeyCode.Space) is >= KeyCode.A and <= KeyCode.Z) { - // It's Key.A...Z. Make it Key.A | Key.ShiftMask - return (KeyCode)(uint)keyInfo.KeyChar & ~KeyCode.Space | KeyCode.ShiftMask; - } return (KeyCode)(uint)keyInfo.KeyChar; } #endregion Keyboard Handling - + void ProcessInput (InputResult inputEvent) { switch (inputEvent.EventType) { - case EventType.Key: + case NetEvents.EventType.Key: var consoleKeyInfo = inputEvent.ConsoleKeyInfo; - if (consoleKeyInfo.Key == ConsoleKey.Packet) { - consoleKeyInfo = FromVKPacketToKConsoleKeyInfo (consoleKeyInfo); - } + //if (consoleKeyInfo.Key == ConsoleKey.Packet) { + // consoleKeyInfo = FromVKPacketToKConsoleKeyInfo (consoleKeyInfo); + //} + + //Debug.WriteLine ($"event: {inputEvent}"); + var map = MapKey (consoleKeyInfo); + if (map == KeyCode.Null) { + break; + } + OnKeyDown (new Key (map)); OnKeyUp (new Key (map)); break; - case EventType.Mouse: + case NetEvents.EventType.Mouse: OnMouseEvent (new MouseEventEventArgs (ToDriverMouse (inputEvent.MouseEvent))); break; - case EventType.WindowSize: + case NetEvents.EventType.WindowSize: _winSizeChanging = true; Top = 0; Left = 0; @@ -1260,12 +1243,9 @@ void ProcessInput (InputResult inputEvent) _winSizeChanging = false; OnSizeChanged (new SizeChangedEventArgs (new Size (Cols, Rows))); break; - case EventType.RequestResponse: - // BUGBUG: What is this for? It does not seem to be used anywhere. - // It is also not clear what it does. View.Data is documented as "This property is not used internally" - Application.Top.Data = inputEvent.RequestResponseEvent.ResultTuple; + case NetEvents.EventType.RequestResponse: break; - case EventType.WindowPosition: + case NetEvents.EventType.WindowPosition: break; default: throw new ArgumentOutOfRangeException (); @@ -1275,7 +1255,7 @@ void ProcessInput (InputResult inputEvent) public override void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool control) { var input = new InputResult { - EventType = EventType.Key, + EventType = NetEvents.EventType.Key, ConsoleKeyInfo = new ConsoleKeyInfo (keyChar, key, shift, alt, control) }; diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs index eb1563403b..c6baab686b 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs @@ -1085,9 +1085,7 @@ internal void ProcessInput (WindowsConsole.InputRecord inputEvent) inputEvent.KeyEvent = FromVKPacketToKeyEventRecord (inputEvent.KeyEvent); } var keyInfo = ToConsoleKeyInfoEx (inputEvent.KeyEvent); - StringBuilder klidSB = new StringBuilder (); - GetKeyboardLayoutName (klidSB); - Debug.WriteLine ($"event: KBD: {klidSB} {inputEvent.ToString ()} {keyInfo.ToString (keyInfo)}"); + //Debug.WriteLine ($"event: KBD: {GetKeyboardLayoutName()} {inputEvent.ToString ()} {keyInfo.ToString (keyInfo)}"); var map = MapKey (keyInfo); diff --git a/Terminal.Gui/Input/Responder.cs b/Terminal.Gui/Input/Responder.cs index bdf8ec1ead..e42f6e764e 100644 --- a/Terminal.Gui/Input/Responder.cs +++ b/Terminal.Gui/Input/Responder.cs @@ -47,6 +47,11 @@ public Responder () } #endif + /// + /// Event raised when has been called to signal that this object is being disposed. + /// + public event EventHandler Disposing; + /// /// Gets or sets a value indicating whether this can focus. /// @@ -186,6 +191,7 @@ protected virtual void Dispose (bool disposing) public void Dispose () { // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Disposing?.Invoke (this, EventArgs.Empty); Dispose (disposing: true); GC.SuppressFinalize (this); #if DEBUG_IDISPOSABLE diff --git a/Terminal.Gui/View/Layout/ViewLayout.cs b/Terminal.Gui/View/Layout/ViewLayout.cs index 0ca320c307..c57de26049 100644 --- a/Terminal.Gui/View/Layout/ViewLayout.cs +++ b/Terminal.Gui/View/Layout/ViewLayout.cs @@ -30,22 +30,31 @@ public partial class View { Rect _frame; /// - /// Gets or sets the frame for the view. The frame is relative to the 's . + /// Gets or sets location and size of the view. The frame is relative to the 's . /// - /// The frame. + /// The rectangle describing the location and size of the view, in coordinates relative to the . /// /// - /// Change the Frame when using the layout style to move or resize views. + /// Change the Frame when using the layout style to move or resize views. /// /// - /// Altering the Frame of a view will trigger the redrawing of the - /// view as well as the redrawing of the affected regions of the . + /// 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. /// /// public virtual 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) { LayoutFrames (); TextFormatter.Size = GetTextFormatterSizeNeededForTextAndHotKey (); @@ -161,7 +170,7 @@ public Thickness GetFramesThickness () /// 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 Point (Padding?.Thickness.GetInside (Padding.Frame).X ?? 0, Padding?.Thickness.GetInside (Padding.Frame).Y ?? 0); + 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 @@ -171,7 +180,9 @@ internal virtual void CreateFrames () { void ThicknessChangedHandler (object sender, EventArgs e) { - LayoutFrames (); + if (IsInitialized) { + LayoutFrames (); + } SetNeedsLayout (); SetNeedsDisplay (); } @@ -207,41 +218,74 @@ void ThicknessChangedHandler (object sender, EventArgs e) /// /// 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 + /// , 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 => _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 (); } } /// - /// The view's content area. - /// - /// SubViews are positioned relative to Bounds. - /// + /// 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. + /// /// - /// Drawing is clipped to Bounds ( clips drawing to Bounds.Size). + /// If is the value of Bounds is indeterminate until the + /// view has been initialized ( is true) and has been called. /// /// - /// Mouse events are reported relative to Bounds. + /// Updates to the Bounds updates , and has the same side effects as updating the . /// - /// - /// The view's content area. - /// /// - /// The of Bounds is always (0, 0). To obtain the offset of the Bounds from the Frame use - /// . + /// Altering the Bounds will eventually (when the view is next drawn) cause the + /// and methods to be called. /// /// - /// When using , Bounds is not valid until after the view has been initialized (after has been called and - /// has fired). Accessing this property before the view is initialized is considered an error./> + /// 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 { @@ -249,7 +293,6 @@ public virtual Rect Bounds { #if DEBUG if (LayoutStyle == LayoutStyle.Computed && !IsInitialized) { Debug.WriteLine ($"WARNING: Bounds is being accessed before the View has been initialized. This is likely a bug in {this}"); - Debug.WriteLine ($"The Frame is set before the View has been initialized. So it isn't a bug.Is by design."); } #endif // DEBUG //var frameRelativeBounds = Padding?.Thickness.GetInside (Padding.Frame) ?? new Rect (default, Frame.Size); @@ -280,19 +323,28 @@ Rect FrameGetInsideBounds () Pos _x, _y; /// - /// Gets or sets the X position for the view (the column). Only used if the is . + /// Gets or sets the X position for the view (the column). /// - /// The X Position. + /// The object representing the X position. /// /// - /// If is changing this property has no effect and its value is indeterminate. + /// 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). /// /// public Pos X { - get => VerifyIsInitialized (_x); + get => VerifyIsInitialized (_x, nameof(X)); set { // BUGBUG: null is the sames a Pos.Absolute(0). Should we be explicit and set it? @@ -306,21 +358,29 @@ public Pos X { } } - /// - /// Gets or sets the Y position for the view (the row). Only used if the is . + /// Gets or sets the Y position for the view (the row). /// - /// The X Position. + /// The object representing the Y position. /// /// - /// If is changing this property has no effect and its value is indeterminate. + /// 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). /// /// public Pos Y { - get => VerifyIsInitialized (_y); + get => VerifyIsInitialized (_y, nameof(Y)); set { // BUGBUG: null is the sames a Pos.Absolute(0). Should we be explicit and set it? @@ -333,23 +393,29 @@ public Pos Y { OnResizeNeeded (); } } + Dim _width, _height; /// - /// Gets or sets the width of the view. Only used when is . + /// Gets or sets the width of the view. /// - /// The width. + /// The object representing the width of the view (the number of columns). /// /// - /// If is changing this property - /// has no effect and its value is indeterminate. + /// If is the value is indeterminate until the + /// view has been initialized ( is true) and has been called. /// /// - /// is the same as Dim.Fill (0). + /// 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 . /// /// public Dim Width { - get => VerifyIsInitialized (_width); + get => VerifyIsInitialized (_width, nameof (Width)); set { // BUGBUG: null is the sames a Dim.Fill(0). Should we be explicit and set it? if (ValidatePosDim) { @@ -372,20 +438,25 @@ public Dim Width { } /// - /// Gets or sets the height of the view. Only used when is . + /// Gets or sets the height of the view. /// - /// The width. + /// The object representing the height of the view (the number of rows). /// /// - /// If is changing this property - /// has no effect and its value is indeterminate. + /// If is the value is indeterminate until the + /// view has been initialized ( is true) and has been called. /// /// - /// is the same as Dim.Fill (0). + /// 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 . /// /// public Dim Height { - get => VerifyIsInitialized (_height); + get => VerifyIsInitialized (_height, nameof (Height)); set { // BUGBUG: null is the sames a Dim.Fill(0). Should we be explicit and set it? if (ValidatePosDim) { @@ -408,22 +479,22 @@ public Dim Height { } // Diagnostics to highlight when X or Y is read before the view has been initialized - Pos VerifyIsInitialized (Pos pos) + Pos VerifyIsInitialized (Pos pos, string member) { #if DEBUG if (LayoutStyle == LayoutStyle.Computed && !IsInitialized) { - Debug.WriteLine ($"WARNING: \"{this}\" has not been initialized; position is indeterminate {pos}. This is likely a bug."); + Debug.WriteLine ($"WARNING: \"{this}\" has not been initialized; {member} is indeterminate {pos}. This is potentially a bug."); } #endif // DEBUG return pos; } // Diagnostics to highlight when Width or Height is read before the view has been initialized - Dim VerifyIsInitialized (Dim dim) + Dim VerifyIsInitialized (Dim dim, string member) { #if DEBUG if (LayoutStyle == LayoutStyle.Computed && !IsInitialized) { - Debug.WriteLine ($"WARNING: \"{this}\" has not been initialized; dimension is indeterminate: {dim}. This is likely a bug."); + Debug.WriteLine ($"WARNING: \"{this}\" has not been initialized; {member} is indeterminate: {dim}. This is potentially a bug."); } #endif // DEBUG return dim; @@ -658,7 +729,7 @@ int GetNewDimension (Dim d, int location, int dimension, int autosize) newDimension = d.Anchor (dimension); newDimension = AutoSize && autosize > newDimension ? autosize : newDimension; break; - + case Dim.DimFill: default: newDimension = Math.Max (d.Anchor (dimension - location), 0); @@ -803,7 +874,7 @@ internal void CollectAll (View from, ref HashSet nNodes, ref HashSet<(View // BUGBUG: This should really only work on initialized subviews foreach (var v in from.InternalSubviews /*.Where(v => v.IsInitialized)*/) { nNodes.Add (v); - if (v._layoutStyle != LayoutStyle.Computed) { + if (v.LayoutStyle != LayoutStyle.Computed) { continue; } CollectPos (v.X, v, ref nNodes, ref nEdges); @@ -896,7 +967,6 @@ internal virtual void LayoutFrames () Margin.Width = Frame.Size.Width; Margin.Height = Frame.Size.Height; Margin.SetNeedsLayout (); - Margin.LayoutSubviews (); Margin.SetNeedsDisplay (); } @@ -908,7 +978,6 @@ internal virtual void LayoutFrames () Border.Width = border.Size.Width; Border.Height = border.Size.Height; Border.SetNeedsLayout (); - Border.LayoutSubviews (); Border.SetNeedsDisplay (); } @@ -920,7 +989,6 @@ internal virtual void LayoutFrames () Padding.Width = padding.Size.Width; Padding.Height = padding.Size.Height; Padding.SetNeedsLayout (); - Padding.LayoutSubviews (); Padding.SetNeedsDisplay (); } } @@ -930,7 +998,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. + /// /// public virtual void LayoutSubviews () { @@ -1027,8 +1101,6 @@ bool ResizeView (bool autoSize) Width = newFrameSize.Width; } } - // BUGBUG: This call may be redundant - TextFormatter.Size = GetTextFormatterSizeNeededForTextAndHotKey (); return boundsChanged; } diff --git a/Terminal.Gui/View/View.cs b/Terminal.Gui/View/View.cs index a22e60d948..d6e798c03d 100644 --- a/Terminal.Gui/View/View.cs +++ b/Terminal.Gui/View/View.cs @@ -1,527 +1,514 @@ using System; -using System.Collections.Generic; using System.ComponentModel; -using System.Linq; -using System.Text; -namespace Terminal.Gui { - #region API Docs +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 +/// 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. +/// +/// +/// +/// 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. +/// +/// +/// 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. +/// +/// +/// To switch between Absolute and Computed layout, use the property. +/// +/// +/// 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. +/// +/// +/// 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. +/// +/// +/// 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 . +/// +/// +/// 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. +/// +/// +/// 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 . +/// +/// +/// 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. +/// /// +#endregion API Docs + +public partial class View : Responder, ISupportInitializeNotification { + #region Constructors and Initialization /// - /// 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. + /// 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. /// /// - /// - /// - /// 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. - /// - /// - /// - /// 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. - /// - /// - /// 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. + /// 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. /// /// - /// To switch between Absolute and Computed layout, use the property. + /// If is greater than one, word wrapping is provided. /// /// - /// 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. + /// 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. + /// + /// /// - /// 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. + /// 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. /// /// - /// Subviews (child views) can be added to a View by calling the method. - /// The container of a View can be accessed with the property. + /// 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. + /// + /// /// - /// To flag a region of the View's to be redrawn call . - /// To flag the entire view for redraw call . + /// 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. /// /// - /// 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 . + /// 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. + /// + /// /// - /// 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. + /// 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. /// /// - /// 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. + /// 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 ). + /// /// - /// By using applications will work both - /// in color as well as black and white displays. - /// + /// 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 also opt-in to more sophisticated initialization + /// 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, in which case the + /// 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. /// - /// - /// 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 (text: string.Empty, direction: 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); - } + /// + public virtual void BeginInit () + { + if (!IsInitialized) { + _oldCanFocus = CanFocus; + _oldTabIndex = _tabIndex; - /// - /// 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 (); - - LayoutFrames (); - } + // TODO: Figure out why ScrollView and other tests fail if this call is put here + // instead of the constructor. + //InitializeFrames (); - /// - /// 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; - - // BUGBUG: These should move to EndInit as they access Bounds causing debug spew. - UpdateTextDirection (TextDirection); - UpdateTextFormatterText (); - SetHotKey (); - - // 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."); + } else { + //throw new InvalidOperationException ("The view is already initialized."); - } + } - if (_subviews?.Count > 0) { - foreach (var view in _subviews) { - if (!view.IsInitialized) { - view.BeginInit (); - } + 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; - OnResizeNeeded (); - if (_subviews != null) { - foreach (var view in _subviews) { - if (!view.IsInitialized) { - view.EndInit (); - } + /// + /// 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 - - /// - /// Points to the current driver in use by the view, it is a convenience property - /// for simplifying the development of new views. - /// - public static ConsoleDriver Driver => Application.Driver; - - /// - /// Gets or sets arbitrary data for the view. - /// - /// This property is not used internally. - public object Data { get; set; } - - /// - /// Gets or sets an identifier for the view; - /// - /// The identifier. - /// 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 . - /// is greater than 0. - /// - /// The title. - public string Title { - get => _title; - set { - if (!OnTitleChanging (_title, value)) { - var old = _title; - _title = value; - SetNeedsDisplay (); + Initialized?.Invoke (this, EventArgs.Empty); + } + #endregion Constructors and Initialization + + /// + /// Points to the current driver in use by the view, it is a convenience property + /// for simplifying the development of new views. + /// + public static ConsoleDriver Driver => Application.Driver; + + /// + /// Gets or sets arbitrary data for the view. + /// + /// This property is not used internally. + public object Data { get; set; } + + /// + /// Gets or sets an identifier for the view; + /// + /// The identifier. + /// 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 . + /// is greater than 0. + /// + /// The title. + public string Title { + get => _title; + set { + if (!OnTitleChanging (_title, value)) { + string old = _title; + _title = value; + SetNeedsDisplay (); #if DEBUG - if (_title != null && string.IsNullOrEmpty (Id)) { - Id = _title.ToString (); - } -#endif // DEBUG - OnTitleChanged (old, _title); + if (_title != null && string.IsNullOrEmpty (Id)) { + Id = _title.ToString (); } +#endif // DEBUG + OnTitleChanged (old, _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; - } + /// + /// 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 when the is changing. Set to + /// `true` to cancel the Title change. + /// + public event EventHandler TitleChanging; - /// - /// 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; - set { - if (base.Enabled != value) { - if (value) { - if (SuperView == null || SuperView?.Enabled == true) { - base.Enabled = value; - } - } else { + /// + /// 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; + set { + if (base.Enabled != value) { + if (value) { + if (SuperView == null || SuperView?.Enabled == true) { base.Enabled = value; } - if (!value && HasFocus) { - SetHasFocus (false, this); - } - OnEnabledChanged (); - SetNeedsDisplay (); - - if (_subviews != null) { - foreach (var view in _subviews) { - if (!value) { - view._oldEnabled = view.Enabled; - view.Enabled = false; - } else { - view.Enabled = view._oldEnabled; - view._addingView = false; - } + } else { + base.Enabled = value; + } + if (!value && HasFocus) { + SetHasFocus (false, this); + } + OnEnabledChanged (); + SetNeedsDisplay (); + + if (_subviews != null) { + foreach (var view in _subviews) { + if (!value) { + view._oldEnabled = view.Enabled; + view.Enabled = false; + } else { + view.Enabled = view._oldEnabled; + view._addingView = false; } } } } } + } - /// - /// 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 { - if (base.Visible != value) { - base.Visible = value; - if (!value) { - if (HasFocus) { - SetHasFocus (false, this); - } - if (ClearOnVisibleFalse) { - Clear (); - } + /// + /// 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 { + if (base.Visible != value) { + base.Visible = value; + if (!value) { + if (HasFocus) { + SetHasFocus (false, this); + } + if (ClearOnVisibleFalse) { + Clear (); } - OnVisibleChanged (); - SetNeedsDisplay (); } + OnVisibleChanged (); + SetNeedsDisplay (); } } + } - bool CanBeVisible (View view) - { - if (!view.Visible) { + bool CanBeVisible (View view) + { + if (!view.Visible) { + return false; + } + for (var c = view.SuperView; c != null; c = c.SuperView) { + if (!c.Visible) { return false; } - for (var c = view.SuperView; c != null; c = c.SuperView) { - if (!c.Visible) { - return false; - } - } - - return true; } - /// - /// Pretty prints the View - /// - /// - public override string ToString () - { - return $"{GetType ().Name}({Id}){Frame}"; - } - - /// - protected override void Dispose (bool disposing) - { - LineCanvas.Dispose (); - - Margin?.Dispose (); - Margin = null; - Border?.Dispose (); - Border = null; - Padding?.Dispose (); - Padding = null; - - _height = null; - _width = null; - _x = null; - _y = null; - - for (var i = InternalSubviews.Count - 1; i >= 0; i--) { - var subview = InternalSubviews [i]; - Remove (subview); - subview.Dispose (); - } + return true; + } - base.Dispose (disposing); - System.Diagnostics.Debug.Assert (InternalSubviews.Count == 0); + /// + /// Pretty prints the View + /// + /// + public override string ToString () => $"{GetType ().Name}({Id}){Frame}"; + + /// + protected override void Dispose (bool disposing) + { + LineCanvas.Dispose (); + + Margin?.Dispose (); + Margin = null; + Border?.Dispose (); + Border = null; + Padding?.Dispose (); + Padding = null; + + _height = null; + _width = null; + _x = null; + _y = null; + + for (int 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); } -} +} \ No newline at end of file diff --git a/Terminal.Gui/View/ViewDrawing.cs b/Terminal.Gui/View/ViewDrawing.cs index 4e5ed8206e..f622d80e37 100644 --- a/Terminal.Gui/View/ViewDrawing.cs +++ b/Terminal.Gui/View/ViewDrawing.cs @@ -3,509 +3,517 @@ using System.Linq; using System.Text; -namespace Terminal.Gui { - public partial class View { - - ColorScheme _colorScheme; - - /// - /// The color scheme for this view, if it is not defined, it returns the 's - /// color scheme. - /// - public virtual ColorScheme ColorScheme { - get { - if (_colorScheme == null) { - return SuperView?.ColorScheme; - } - return _colorScheme; - } - set { - if (_colorScheme != value) { - _colorScheme = value; - SetNeedsDisplay (); - } +namespace Terminal.Gui; + +public partial class View { + ColorScheme _colorScheme; + + /// + /// The color scheme for this view, if it is not defined, it returns the 's + /// color scheme. + /// + public virtual ColorScheme ColorScheme { + get { + if (_colorScheme == null) { + return SuperView?.ColorScheme; } + return _colorScheme; } - - /// - /// Determines the current based on the value. - /// - /// if is - /// or if is . - /// If it's overridden can return other values. - public virtual Attribute GetNormalColor () - { - ColorScheme cs = ColorScheme; - if (ColorScheme == null) { - cs = new ColorScheme (); + set { + if (_colorScheme != value) { + _colorScheme = value; + SetNeedsDisplay (); } - return Enabled ? cs.Normal : cs.Disabled; } + } - /// - /// Determines the current based on the value. - /// - /// if is - /// or if is . - /// If it's overridden can return other values. - public virtual Attribute GetFocusColor () - { - return Enabled ? ColorScheme.Focus : ColorScheme.Disabled; + /// + /// Determines the current based on the value. + /// + /// if is + /// or if is . + /// If it's overridden can return other values. + public virtual Attribute GetNormalColor () + { + var cs = ColorScheme; + if (ColorScheme == null) { + cs = new ColorScheme (); } + return Enabled ? cs.Normal : cs.Disabled; + } - /// - /// Determines the current based on the value. - /// - /// if is - /// or if is . - /// If it's overridden can return other values. - public virtual Attribute GetHotNormalColor () - { - return Enabled ? ColorScheme.HotNormal : ColorScheme.Disabled; + /// + /// Determines the current based on the value. + /// + /// if is + /// or if is . + /// 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 + /// or if is . + /// If it's overridden can return other values. + public virtual Attribute GetHotNormalColor () => Enabled ? ColorScheme.HotNormal : ColorScheme.Disabled; + + /// + /// Displays the specified character in the specified column and row of the View. + /// + /// Column (view-relative). + /// Row (view-relative). + /// Ch. + public void AddRune (int col, int row, Rune ch) + { + if (row < 0 || col < 0) { + return; } - - /// - /// Displays the specified character in the specified column and row of the View. - /// - /// Column (view-relative). - /// Row (view-relative). - /// Ch. - public void AddRune (int col, int row, Rune ch) - { - if (row < 0 || col < 0) { - return; - } - if (row > _frame.Height - 1 || col > _frame.Width - 1) { - return; - } - Move (col, row); - Driver.AddRune (ch); + if (row > _frame.Height - 1 || col > _frame.Width - 1) { + return; } + Move (col, row); + Driver.AddRune (ch); + } - /// - /// Clears and . - /// - protected void ClearNeedsDisplay () - { - _needsDisplayRect = Rect.Empty; - _subViewNeedsDisplay = false; - } + /// + /// Clears and . + /// + 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 (); - } + // 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 (); } } + } - /// - /// Sets the area of this view needing to be redrawn to . - /// - public void SetNeedsDisplay () - { - if (!IsInitialized) { - return; - } + /// + /// Sets the area of this view needing to be redrawn to . + /// + /// + /// If the view has not been initialized ( is ), + /// this method does nothing. + /// + public void SetNeedsDisplay () + { + if (IsInitialized) { SetNeedsDisplay (Bounds); } + } - /// - /// Expands the area of this view needing to be redrawn to include . - /// - /// The view-relative region that needs to be redrawn. - public void SetNeedsDisplay (Rect region) - { - if (_needsDisplayRect.IsEmpty) { - _needsDisplayRect = region; - } else { - 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) { - Margin?.SetNeedsDisplay (Margin.Bounds); - Border?.SetNeedsDisplay (Border.Bounds); - Padding?.SetNeedsDisplay (Padding.Bounds); - } + /// + /// Expands the area of this view needing to be redrawn to include . + /// + /// + /// If the view has not been initialized ( is ), + /// the area to be redrawn will be the . + /// + /// The Bounds-relative region that needs to be redrawn. + public void SetNeedsDisplay (Rect region) + { + if (!IsInitialized) { + _needsDisplayRect = region; + return; + } + 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); + _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) { + Margin?.SetNeedsDisplay (Margin.Bounds); + Border?.SetNeedsDisplay (Border.Bounds); + Padding?.SetNeedsDisplay (Padding.Bounds); + } - if (_subviews == null) { - return; - } + if (_subviews == null) { + return; + } - foreach (var subview in _subviews) { - if (subview.Frame.IntersectsWith (region)) { - var subviewRegion = Rect.Intersect (subview.Frame, region); - subviewRegion.X -= subview.Frame.X; - subviewRegion.Y -= subview.Frame.Y; - subview.SetNeedsDisplay (subviewRegion); - } + foreach (var subview in _subviews) { + if (subview.Frame.IntersectsWith (region)) { + var subviewRegion = Rect.Intersect (subview.Frame, region); + subviewRegion.X -= subview.Frame.X; + subviewRegion.Y -= subview.Frame.Y; + subview.SetNeedsDisplay (subviewRegion); } } + } - /// - /// Gets whether any Subviews need to be redrawn. - /// - public bool SubViewNeedsDisplay { - get => _subViewNeedsDisplay; + /// + /// 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) { + _superView.SetSubViewNeedsDisplay (); } + } - bool _subViewNeedsDisplay; + /// + /// Clears the with the normal background color. + /// + /// + /// + /// This clears the Bounds used by this view. + /// + /// + public void Clear () + { + if (IsInitialized) { + Clear (BoundsToScreen (Bounds)); + } - /// - /// Indicates that any Subviews (in the list) need to be repainted. - /// - public void SetSubViewNeedsDisplay () - { - _subViewNeedsDisplay = true; - if (_superView != null && !_superView._subViewNeedsDisplay) { - _superView.SetSubViewNeedsDisplay (); - } + } + + // BUGBUG: This version of the Clear API should be removed. We should have a tenet that says + // "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. + /// + /// + /// + /// The screen-relative rectangle to clear. + public void Clear (Rect regionScreen) + { + if (Driver == null) { + return; } + var prev = Driver.SetAttribute (GetNormalColor ()); + Driver.FillRect (regionScreen); + Driver.SetAttribute (prev); + } + + // 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; + + return new Rect (x, y, w, h); + } + + /// + /// Expands the 's clip region to include . + /// + /// 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 . + /// + /// + public Rect ClipToBounds () + { + var previous = Driver.Clip; + Driver.Clip = Rect.Intersect (previous, BoundsToScreen (Bounds)); + return previous; + } - /// - /// Clears the with the normal background color. - /// - /// - /// - /// This clears the Bounds used by this view. - /// - /// - public void Clear () => Clear (BoundsToScreen(Bounds)); - - // BUGBUG: This version of the Clear API should be removed. We should have a tenet that says - // "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. - /// - /// - /// - /// The screen-relative rectangle to clear. - public void Clear (Rect regionScreen) - { - if (Driver == null) { - return; + /// + /// Utility function to draw strings that contain a hotkey. + /// + /// String to display, the hotkey specifier before a letter flags the next letter as the hotkey. + /// 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 + /// + 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) { + if (rune == hotkeySpec.Value) { + Application.Driver.SetAttribute (hotColor); + continue; } - var prev = Driver.SetAttribute (GetNormalColor ()); - Driver.FillRect (regionScreen); - Driver.SetAttribute (prev); + Application.Driver.AddRune ((Rune)rune); + Application.Driver.SetAttribute (normalColor); } + } - // Clips a rectangle in screen coordinates to the dimensions currently available on the screen - internal Rect ScreenClip (Rect regionScreen) - { - 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); - } - - /// - /// Expands the 's clip region to include . - /// - /// 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 . - /// - /// - public Rect ClipToBounds () - { - var previous = Driver.Clip; - Driver.Clip = Rect.Intersect (previous, BoundsToScreen (Bounds)); - return previous; + /// + /// 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. + /// The color scheme to use. + public void DrawHotString (string text, bool focused, ColorScheme scheme) + { + if (focused) { + DrawHotString (text, scheme.HotFocus, scheme.Focus); + } else { + DrawHotString (text, Enabled ? scheme.HotNormal : scheme.Disabled, Enabled ? scheme.Normal : scheme.Disabled); } + } - /// - /// Utility function to draw strings that contain a hotkey. - /// - /// String to display, the hotkey specifier before a letter flags the next letter as the hotkey. - /// 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 - /// - public void DrawHotString (string text, Attribute hotColor, Attribute normalColor) - { - var hotkeySpec = HotKeySpecifier == (Rune)0xffff ? (Rune)'_' : HotKeySpecifier; - Application.Driver.SetAttribute (normalColor); - foreach (var rune in text) { - if (rune == hotkeySpec.Value) { - Application.Driver.SetAttribute (hotColor); - continue; - } - Application.Driver.AddRune ((Rune)rune); - Application.Driver.SetAttribute (normalColor); - } + /// + /// This moves the cursor to the specified column and row in the view. + /// + /// The move. + /// The column to move to, in view-relative coordinates. + /// the row to move to, in view-relative coordinates. + public void Move (int col, int row) + { + if (Driver.Rows == 0) { + return; } - /// - /// 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. - /// The color scheme to use. - public void DrawHotString (string text, bool focused, ColorScheme scheme) - { - if (focused) - DrawHotString (text, scheme.HotFocus, scheme.Focus); - else - DrawHotString (text, Enabled ? scheme.HotNormal : scheme.Disabled, Enabled ? scheme.Normal : scheme.Disabled); - } + BoundsToScreen (col, row, out int rCol, out int rRow, false); + Driver.Move (rCol, rRow); + } - /// - /// This moves the cursor to the specified column and row in the view. - /// - /// The move. - /// The column to move to, in view-relative coordinates. - /// the row to move to, in view-relative coordinates. - public void Move (int col, int row) - { - if (Driver.Rows == 0) { - return; - } - - 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 + /// method will cause the be prepared to be rendered. + /// + /// + public virtual bool OnDrawFrames () + { + if (!IsInitialized) { + return false; } - /// - /// 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 LineCanvas (); - - /// - /// 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 - /// method will cause the be prepared to be rendered. - /// - /// - public virtual bool OnDrawFrames () - { - if (!IsInitialized) { - return false; - } - // Each of these renders lines to either this View's LineCanvas - // Those lines will be finally rendered in OnRenderLineCanvas - Margin?.OnDrawContent (Margin.Bounds); - Border?.OnDrawContent (Border.Bounds); - Padding?.OnDrawContent (Padding.Bounds); + // Each of these renders lines to either this View's LineCanvas + // Those lines will be finally rendered in OnRenderLineCanvas + Margin?.OnDrawContent (Margin.Bounds); + Border?.OnDrawContent (Border.Bounds); + Padding?.OnDrawContent (Padding.Bounds); - return true; + return true; + } + + /// + /// 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. + /// + /// + public void Draw () + { + if (!CanBeVisible (this)) { + return; } + OnDrawFrames (); - /// - /// 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. - /// - /// - public void Draw () - { - if (!CanBeVisible (this)) { - return; - } - OnDrawFrames (); + var prevClip = ClipToBounds (); - var prevClip = ClipToBounds (); + if (ColorScheme != null) { + //Driver.SetAttribute (HasFocus ? GetFocusColor () : GetNormalColor ()); + Driver.SetAttribute (GetNormalColor ()); + } - if (ColorScheme != null) { - //Driver.SetAttribute (HasFocus ? GetFocusColor () : GetNormalColor ()); - Driver.SetAttribute (GetNormalColor ()); - } + // Invoke DrawContentEvent + var dev = new DrawEventArgs (Bounds); + DrawContent?.Invoke (this, dev); - // Invoke DrawContentEvent - var dev = new DrawEventArgs (Bounds); - DrawContent?.Invoke (this, dev); + if (!dev.Cancel) { + OnDrawContent (Bounds); + } - if (!dev.Cancel) { - OnDrawContent (Bounds); - } + Driver.Clip = prevClip; - Driver.Clip = prevClip; + OnRenderLineCanvas (); + // Invoke DrawContentCompleteEvent + OnDrawContentComplete (Bounds); - OnRenderLineCanvas (); - // Invoke DrawContentCompleteEvent - OnDrawContentComplete (Bounds); + // BUGBUG: v2 - We should be able to use View.SetClip here and not have to resort to knowing Driver details. + ClearLayoutNeeded (); + ClearNeedsDisplay (); + } - // BUGBUG: v2 - We should be able to use View.SetClip here and not have to resort to knowing Driver details. - ClearLayoutNeeded (); - ClearNeedsDisplay (); + // TODO: Make this cancelable + /// + /// 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. + /// + /// + public virtual bool OnRenderLineCanvas () + { + if (!IsInitialized) { + return false; } - // TODO: Make this cancelable - /// - /// 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. - /// - /// - public virtual bool OnRenderLineCanvas () - { - if (!IsInitialized) { - return false; + // If we have a SuperView, it'll render our frames. + if (!SuperViewRendersLineCanvas && LineCanvas.Bounds != Rect.Empty) { + foreach (var p in LineCanvas.GetCellMap ()) { + // Get the entire map + Driver.SetAttribute (p.Value.Attribute ?? ColorScheme.Normal); + Driver.Move (p.Key.X, p.Key.Y); + // TODO: #2616 - Support combining sequences that don't normalize + Driver.AddRune (p.Value.Rune); } + LineCanvas.Clear (); + } - // If we have a SuperView, it'll render our frames. - if (!SuperViewRendersLineCanvas && LineCanvas.Bounds != Rect.Empty) { - foreach (var p in LineCanvas.GetCellMap ()) { // Get the entire map - Driver.SetAttribute (p.Value.Attribute ?? ColorScheme.Normal); - Driver.Move (p.Key.X, p.Key.Y); - // TODO: #2616 - Support combining sequences that don't normalize - Driver.AddRune (p.Value.Rune); - } - LineCanvas.Clear (); + if (Subviews.Any (s => s.SuperViewRendersLineCanvas)) { + foreach (var subview in Subviews.Where (s => s.SuperViewRendersLineCanvas == true)) { + // Combine the LineCanvas' + LineCanvas.Merge (subview.LineCanvas); + subview.LineCanvas.Clear (); } - if (Subviews.Any (s => s.SuperViewRendersLineCanvas)) { - foreach (var subview in Subviews.Where (s => s.SuperViewRendersLineCanvas == true)) { - // Combine the LineCanvas' - LineCanvas.Merge (subview.LineCanvas); - subview.LineCanvas.Clear (); - } - - foreach (var p in LineCanvas.GetCellMap ()) { // Get the entire map - Driver.SetAttribute (p.Value.Attribute ?? ColorScheme.Normal); - Driver.Move (p.Key.X, p.Key.Y); - // TODO: #2616 - Support combining sequences that don't normalize - Driver.AddRune (p.Value.Rune); - } - LineCanvas.Clear (); + foreach (var p in LineCanvas.GetCellMap ()) { + // Get the entire map + Driver.SetAttribute (p.Value.Attribute ?? ColorScheme.Normal); + Driver.Move (p.Key.X, p.Key.Y); + // TODO: #2616 - Support combining sequences that don't normalize + Driver.AddRune (p.Value.Rune); } - - return true; + LineCanvas.Clear (); } - /// - /// 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 . - /// - /// - public event EventHandler DrawContent; - - /// - /// 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 - /// - /// This method will be called before any subviews added with have been drawn. - /// - public virtual void OnDrawContent (Rect contentArea) - { - if (NeedsDisplay) { - if (SuperView != null) { - Clear (BoundsToScreen (Bounds)); - } + return true; + } - if (!string.IsNullOrEmpty (TextFormatter.Text)) { - if (TextFormatter != null) { - TextFormatter.NeedsFormat = true; - } - } - // This should NOT clear - TextFormatter?.Draw (BoundsToScreen (Bounds), HasFocus ? GetFocusColor () : GetNormalColor (), - HasFocus ? ColorScheme.HotFocus : GetHotNormalColor (), - Rect.Empty, false); - SetSubViewNeedsDisplay (); + /// + /// 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 . + /// + /// + public event EventHandler DrawContent; + + /// + /// 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 + /// + /// This method will be called before any subviews added with have been drawn. + /// + public virtual void OnDrawContent (Rect contentArea) + { + if (NeedsDisplay) { + if (SuperView != null) { + Clear (BoundsToScreen (Bounds)); } - // Draw subviews - // TODO: Implement OnDrawSubviews (cancelable); - if (_subviews != null && SubViewNeedsDisplay) { - var subviewsNeedingDraw = _subviews.Where ( - view => view.Visible && - (view.NeedsDisplay || - view.SubViewNeedsDisplay || - view.LayoutNeeded) - ); - - foreach (var view in subviewsNeedingDraw) { - //view.Frame.IntersectsWith (bounds)) { - // && (view.Frame.IntersectsWith (bounds) || bounds.X < 0 || bounds.Y < 0)) { - if (view.LayoutNeeded) { - view.LayoutSubviews (); - } - - // Draw the subview - // Use the view's bounds (view-relative; Location will always be (0,0) - //if (view.Visible && view.Frame.Width > 0 && view.Frame.Height > 0) { - view.Draw (); - //} + if (!string.IsNullOrEmpty (TextFormatter.Text)) { + if (TextFormatter != null) { + TextFormatter.NeedsFormat = true; } } + // This should NOT clear + TextFormatter?.Draw (BoundsToScreen (Bounds), HasFocus ? GetFocusColor () : GetNormalColor (), + HasFocus ? ColorScheme.HotFocus : GetHotNormalColor (), + Rect.Empty, false); + SetSubViewNeedsDisplay (); } - /// - /// 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 . - /// - /// - 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 - /// - /// This method will be called after any subviews removed with have been completed drawing. - /// - public virtual void OnDrawContentComplete (Rect contentArea) - { - DrawContentComplete?.Invoke (this, new DrawEventArgs (contentArea)); - } + // Draw subviews + // TODO: Implement OnDrawSubviews (cancelable); + if (_subviews != null && SubViewNeedsDisplay) { + var subviewsNeedingDraw = _subviews.Where ( + view => view.Visible && + (view.NeedsDisplay || + view.SubViewNeedsDisplay || + view.LayoutNeeded) + ); + + foreach (var view in subviewsNeedingDraw) { + //view.Frame.IntersectsWith (bounds)) { + // && (view.Frame.IntersectsWith (bounds) || bounds.X < 0 || bounds.Y < 0)) { + if (view.LayoutNeeded) { + view.LayoutSubviews (); + } + // Draw the subview + // Use the view's bounds (view-relative; Location will always be (0,0) + //if (view.Visible && view.Frame.Width > 0 && view.Frame.Height > 0) { + view.Draw (); + //} + } + } } + + /// + /// 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 . + /// + /// + 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 + /// + /// This method will be called after any subviews removed with have been completed drawing. + /// + public virtual void OnDrawContentComplete (Rect contentArea) => DrawContentComplete?.Invoke (this, new DrawEventArgs (contentArea)); } \ No newline at end of file diff --git a/Terminal.Gui/View/ViewSubViews.cs b/Terminal.Gui/View/ViewSubViews.cs index a04d60b049..c546200ed4 100644 --- a/Terminal.Gui/View/ViewSubViews.cs +++ b/Terminal.Gui/View/ViewSubViews.cs @@ -82,14 +82,15 @@ public virtual void Add (View view) view._oldEnabled = true; view.Enabled = false; } - SetNeedsLayout (); - SetNeedsDisplay (); OnAdded (new SuperViewChangedEventArgs (this, view)); if (IsInitialized && !view.IsInitialized) { view.BeginInit (); view.EndInit (); } + + SetNeedsLayout (); + SetNeedsDisplay (); } /// diff --git a/Terminal.Gui/View/ViewText.cs b/Terminal.Gui/View/ViewText.cs index 73f191358c..8b3eb0e9d6 100644 --- a/Terminal.Gui/View/ViewText.cs +++ b/Terminal.Gui/View/ViewText.cs @@ -1,260 +1,256 @@ -using System.Text; -using System; +using System; using System.Collections.Generic; -namespace Terminal.Gui { +namespace Terminal.Gui; - public partial class View { - string _text; +public partial class View { + string _text; - /// - /// 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. - /// - /// - public virtual string Text { - get => _text; - set { - _text = value; - SetHotKey (); - UpdateTextFormatterText (); - //TextFormatter.Format (); - OnResizeNeeded (); + /// + /// 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. + /// + /// + public virtual string Text { + get => _text; + set { + _text = value; + SetHotKey (); + UpdateTextFormatterText (); + //TextFormatter.Format (); + OnResizeNeeded (); #if DEBUG - if (_text != null && string.IsNullOrEmpty (Id)) { - Id = _text; - } -#endif + if (_text != null && string.IsNullOrEmpty (Id)) { + Id = _text; } +#endif } + } - /// - /// Gets or sets the used to format . - /// - public TextFormatter TextFormatter { get; set; } + /// + /// Gets or sets the used to format . + /// + 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; - } + /// + /// 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 - /// is formatted for display. The default is . - /// - public virtual bool PreserveTrailingSpaces { - get => TextFormatter.PreserveTrailingSpaces; - set { - if (TextFormatter.PreserveTrailingSpaces != value) { - TextFormatter.PreserveTrailingSpaces = value; - TextFormatter.NeedsFormat = true; - } + /// + /// 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 + /// is formatted for display. The default is . + /// + public virtual bool PreserveTrailingSpaces { + get => TextFormatter.PreserveTrailingSpaces; + set { + if (TextFormatter.PreserveTrailingSpaces != value) { + TextFormatter.PreserveTrailingSpaces = value; + TextFormatter.NeedsFormat = true; } } + } - /// - /// Gets or sets how the View's is aligned horizontally when drawn. Changing this property will redisplay the . - /// - /// The text alignment. - public virtual TextAlignment TextAlignment { - get => TextFormatter.Alignment; - set { - TextFormatter.Alignment = value; - UpdateTextFormatterText (); - OnResizeNeeded (); - } + /// + /// Gets or sets how the View's is aligned horizontally when drawn. Changing this property will redisplay the . + /// + /// The text alignment. + public virtual TextAlignment TextAlignment { + get => TextFormatter.Alignment; + set { + TextFormatter.Alignment = value; + UpdateTextFormatterText (); + OnResizeNeeded (); } + } - /// - /// Gets or sets how the View's is aligned vertically when drawn. Changing this property will redisplay the . - /// - /// The text alignment. - public virtual VerticalTextAlignment VerticalTextAlignment { - get => TextFormatter.VerticalAlignment; - set { - TextFormatter.VerticalAlignment = value; - SetNeedsDisplay (); - } + /// + /// Gets or sets how the View's is aligned vertically when drawn. Changing this property will redisplay the . + /// + /// The text alignment. + public virtual VerticalTextAlignment VerticalTextAlignment { + get => TextFormatter.VerticalAlignment; + set { + TextFormatter.VerticalAlignment = value; + SetNeedsDisplay (); } + } - /// - /// Gets or sets the direction of the View's . Changing this property will redisplay the . - /// - /// The text alignment. - public virtual TextDirection TextDirection { - get => TextFormatter.Direction; - set { - UpdateTextDirection (value); - TextFormatter.Direction = value; - } + /// + /// Gets or sets the direction of the View's . Changing this property will redisplay the . + /// + /// The text alignment. + public virtual TextDirection TextDirection { + get => TextFormatter.Direction; + set { + UpdateTextDirection (value); + TextFormatter.Direction = value; } + } - private void UpdateTextDirection (TextDirection newDirection) - { - var directionChanged = TextFormatter.IsHorizontalDirection (TextFormatter.Direction) - != TextFormatter.IsHorizontalDirection (newDirection); - TextFormatter.Direction = newDirection; + void UpdateTextDirection (TextDirection newDirection) + { + bool directionChanged = TextFormatter.IsHorizontalDirection (TextFormatter.Direction) + != TextFormatter.IsHorizontalDirection (newDirection); + TextFormatter.Direction = newDirection; - var isValidOldAutoSize = AutoSize && IsValidAutoSize (out var _); + bool isValidOldAutoSize = AutoSize && IsValidAutoSize (out var _); - UpdateTextFormatterText (); + UpdateTextFormatterText (); - if ((!ValidatePosDim && directionChanged && AutoSize) - || (ValidatePosDim && directionChanged && AutoSize && isValidOldAutoSize)) { - OnResizeNeeded (); - } else if (directionChanged && IsAdded) { - ResizeBoundsToFit (Bounds.Size); - // BUGBUG: I think this call is redundant. - SetFrameToFitText (); - } else { - SetFrameToFitText (); - } - TextFormatter.Size = GetTextFormatterSizeNeededForTextAndHotKey (); - SetNeedsDisplay (); + if (!ValidatePosDim && directionChanged && AutoSize + || ValidatePosDim && directionChanged && AutoSize && isValidOldAutoSize) { + OnResizeNeeded (); + } else if (directionChanged && IsAdded) { + ResizeBoundsToFit (Bounds.Size); + // BUGBUG: I think this call is redundant. + SetFrameToFitText (); + } else { + SetFrameToFitText (); } + TextFormatter.Size = GetTextFormatterSizeNeededForTextAndHotKey (); + SetNeedsDisplay (); + } - /// - /// 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. - /// - /// Always returns if is or - /// if (Horizontal) or (Vertical) are not not set or zero. - /// Does not take into account word wrapping. - /// - bool SetFrameToFitText () + /// + /// 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. + /// + /// Always returns if is or + /// if (Horizontal) or (Vertical) are not not set or zero. + /// Does not take into account word wrapping. + /// + bool SetFrameToFitText () + { + // BUGBUG: This API is broken - should not assume Frame.Height == Bounds.Height + // + // Gets the minimum dimensions required to fit the View's , factoring in . + // + // The minimum dimensions required. + // if the dimensions fit within the View's , otherwise. + // + // Always returns if is or + // if (Horizontal) or (Vertical) are not not set or zero. + // Does not take into account word wrapping. + // + bool GetMinimumSizeOfText (out Size sizeRequired) { - // BUGBUG: This API is broken - should not assume Frame.Height == Bounds.Height - // - // Gets the minimum dimensions required to fit the View's , factoring in . - // - // The minimum dimensions required. - // if the dimensions fit within the View's , otherwise. - // - // Always returns if is or - // if (Horizontal) or (Vertical) are not not set or zero. - // Does not take into account word wrapping. - // - bool GetMinimumSizeOfText (out Size sizeRequired) - { - if (!IsInitialized) { - sizeRequired = new Size (0, 0); - return false; - } - 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 (!IsInitialized) { + sizeRequired = new Size (0, 0); return false; } + sizeRequired = Bounds.Size; - if (GetMinimumSizeOfText (out var size)) { - _frame = new Rect (_frame.Location, size); - return true; + 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; + } } return false; } - /// - /// Gets the width or height of the characters - /// in the property. - /// - /// - /// 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. - 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 - ? Math.Max (HotKeySpecifier.GetColumns (), 0) : 0; - } + if (GetMinimumSizeOfText (out var size)) { + _frame = new Rect (_frame.Location, size); + return true; } + return false; + } - /// - /// Gets the dimensions required for ignoring a . - /// - /// - public Size GetSizeNeededForTextWithoutHotKey () - { - return new Size (TextFormatter.Size.Width - GetHotKeySpecifierLength (), - TextFormatter.Size.Height - GetHotKeySpecifierLength (false)); + /// + /// Gets the width or height of the characters + /// in the property. + /// + /// + /// 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. + 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 + ? Math.Max (HotKeySpecifier.GetColumns (), 0) : 0; } + } - /// - /// Gets the dimensions required for accounting for a . - /// - /// - public Size GetTextFormatterSizeNeededForTextAndHotKey () - { - if (string.IsNullOrEmpty (TextFormatter.Text)) { - - if (!IsInitialized) return Size.Empty; + /// + /// Gets the dimensions required for ignoring a . + /// + /// + public Size GetSizeNeededForTextWithoutHotKey () => new (TextFormatter.Size.Width - GetHotKeySpecifierLength (), + TextFormatter.Size.Height - GetHotKeySpecifierLength (false)); - return Bounds.Size; - } + /// + /// Gets the dimensions required for accounting for a . + /// + /// + public Size GetTextFormatterSizeNeededForTextAndHotKey () + { + if (!IsInitialized) { + return Size.Empty; + } - // 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 (), - Bounds.Size.Height + GetHotKeySpecifierLength (false)); + if (string.IsNullOrEmpty (TextFormatter.Text)) { + return Bounds.Size; } + + // 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 (), + Bounds.Size.Height + GetHotKeySpecifierLength (false)); } } \ No newline at end of file diff --git a/Terminal.Gui/Views/ColorPicker.cs b/Terminal.Gui/Views/ColorPicker.cs index 0c9b92da49..a19b9f758d 100644 --- a/Terminal.Gui/Views/ColorPicker.cs +++ b/Terminal.Gui/Views/ColorPicker.cs @@ -51,7 +51,9 @@ public int BoxWidth { set { if (_boxWidth != value) { _boxWidth = value; - Bounds = new Rect (Bounds.Location, new Size (_cols * BoxWidth, _rows * BoxHeight)); + if (IsInitialized) { + Bounds = new Rect (Bounds.Location, new Size (_cols * BoxWidth, _rows * BoxHeight)); + } } } } @@ -65,7 +67,9 @@ public int BoxHeight { set { if (_boxHeight != value) { _boxHeight = value; - Bounds = new Rect (Bounds.Location, new Size (_cols * BoxWidth, _rows * BoxHeight)); + if (IsInitialized) { + Bounds = new Rect (Bounds.Location, new Size (_cols * BoxWidth, _rows * BoxHeight)); + } } } } diff --git a/Terminal.Gui/Views/ComboBox.cs b/Terminal.Gui/Views/ComboBox.cs index ddf24086db..851719c243 100644 --- a/Terminal.Gui/Views/ComboBox.cs +++ b/Terminal.Gui/Views/ComboBox.cs @@ -10,878 +10,853 @@ using System.Collections.Generic; using System.Text; -namespace Terminal.Gui { - /// - /// Provides a drop-down list of items the user can select from. - /// - public class ComboBox : View { +namespace Terminal.Gui; - private class ComboListView : ListView { - private int highlighted = -1; - private bool isFocusing; - private ComboBox container; - private bool hideDropdownListOnClick; +/// +/// Provides a drop-down list of items the user can select from. +/// +public class ComboBox : View { + class ComboListView : ListView { + int _highlighted = -1; + bool _isFocusing; + ComboBox _container; + bool _hideDropdownListOnClick; - public ComboListView (ComboBox container, bool hideDropdownListOnClick) - { - Initialize (container, hideDropdownListOnClick); - } + public ComboListView (ComboBox container, bool hideDropdownListOnClick) => SetInitialProperties (container, hideDropdownListOnClick); - public ComboListView (ComboBox container, Rect rect, IList source, bool hideDropdownListOnClick) : base (rect, source) - { - Initialize (container, hideDropdownListOnClick); - } + public ComboListView (ComboBox container, Rect rect, IList source, bool hideDropdownListOnClick) : base (rect, source) => SetInitialProperties (container, hideDropdownListOnClick); - public ComboListView (ComboBox container, IList source, bool hideDropdownListOnClick) : base (source) - { - Initialize (container, hideDropdownListOnClick); - } + public ComboListView (ComboBox container, IList source, bool hideDropdownListOnClick) : base (source) => SetInitialProperties (container, hideDropdownListOnClick); - private void Initialize (ComboBox container, bool hideDropdownListOnClick) - { - this.container = container ?? throw new ArgumentNullException (nameof (container), "ComboBox container cannot be null."); - HideDropdownListOnClick = hideDropdownListOnClick; - } + void SetInitialProperties (ComboBox container, bool hideDropdownListOnClick) + { + _container = container ?? throw new ArgumentNullException (nameof (container), "ComboBox container cannot be null."); + HideDropdownListOnClick = hideDropdownListOnClick; + } - public bool HideDropdownListOnClick { - get => hideDropdownListOnClick; - set => hideDropdownListOnClick = WantContinuousButtonPressed = value; - } + public bool HideDropdownListOnClick { + get => _hideDropdownListOnClick; + set => _hideDropdownListOnClick = WantContinuousButtonPressed = value; + } - public override bool MouseEvent (MouseEvent me) - { - var res = false; - var isMousePositionValid = IsMousePositionValid (me); + public override bool MouseEvent (MouseEvent me) + { + bool res = false; + bool isMousePositionValid = IsMousePositionValid (me); + if (isMousePositionValid) { + res = base.MouseEvent (me); + } + + if (HideDropdownListOnClick && me.Flags == MouseFlags.Button1Clicked) { + if (!isMousePositionValid && !_isFocusing) { + _container._isShow = false; + _container.HideList (); + } else if (isMousePositionValid) { + OnOpenSelectedItem (); + } else { + _isFocusing = false; + } + return true; + } else if (me.Flags == MouseFlags.ReportMousePosition && HideDropdownListOnClick) { if (isMousePositionValid) { - res = base.MouseEvent (me); + _highlighted = Math.Min (TopItem + me.Y, Source.Count); + SetNeedsDisplay (); } + _isFocusing = false; + return true; + } - if (HideDropdownListOnClick && me.Flags == MouseFlags.Button1Clicked) { - if (!isMousePositionValid && !isFocusing) { - container.isShow = false; - container.HideList (); - } else if (isMousePositionValid) { - OnOpenSelectedItem (); - } else { - isFocusing = false; - } - return true; - } else if (me.Flags == MouseFlags.ReportMousePosition && HideDropdownListOnClick) { - if (isMousePositionValid) { - highlighted = Math.Min (TopItem + me.Y, Source.Count); - SetNeedsDisplay (); - } - isFocusing = false; - return true; - } + return res; + } - return res; + bool IsMousePositionValid (MouseEvent me) + { + if (me.X >= 0 && me.X < Frame.Width && me.Y >= 0 && me.Y < Frame.Height) { + return true; } + return false; + } - private bool IsMousePositionValid (MouseEvent me) - { - if (me.X >= 0 && me.X < Frame.Width && me.Y >= 0 && me.Y < Frame.Height) { - return true; + public override void OnDrawContent (Rect contentArea) + { + var current = ColorScheme.Focus; + Driver.SetAttribute (current); + Move (0, 0); + var f = Frame; + int item = TopItem; + bool focused = HasFocus; + int col = AllowsMarking ? 2 : 0; + int start = LeftItem; + + for (int row = 0; row < f.Height; row++, item++) { + bool isSelected = item == _container.SelectedItem; + bool isHighlighted = _hideDropdownListOnClick && item == _highlighted; + + Attribute newcolor; + if (isHighlighted || isSelected && !_hideDropdownListOnClick) { + newcolor = focused ? ColorScheme.Focus : ColorScheme.HotNormal; + } else if (isSelected && _hideDropdownListOnClick) { + newcolor = focused ? ColorScheme.HotFocus : ColorScheme.HotNormal; + } else { + newcolor = focused ? GetNormalColor () : GetNormalColor (); } - return false; - } - public override void OnDrawContent (Rect contentArea) - { - var current = ColorScheme.Focus; - Driver.SetAttribute (current); - Move (0, 0); - var f = Frame; - var item = TopItem; - bool focused = HasFocus; - int col = AllowsMarking ? 2 : 0; - int start = LeftItem; - - for (int row = 0; row < f.Height; row++, item++) { - bool isSelected = item == container.SelectedItem; - bool isHighlighted = hideDropdownListOnClick && item == highlighted; - - Attribute newcolor; - if (isHighlighted || (isSelected && !hideDropdownListOnClick)) { - newcolor = focused ? ColorScheme.Focus : ColorScheme.HotNormal; - } else if (isSelected && hideDropdownListOnClick) { - newcolor = focused ? ColorScheme.HotFocus : ColorScheme.HotNormal; - } else { - newcolor = focused ? GetNormalColor () : GetNormalColor (); - } + if (newcolor != current) { + Driver.SetAttribute (newcolor); + current = newcolor; + } - if (newcolor != current) { - Driver.SetAttribute (newcolor); - current = newcolor; + Move (0, row); + if (Source == null || item >= Source.Count) { + for (int c = 0; c < f.Width; c++) { + Driver.AddRune ((Rune)' '); } - - Move (0, row); - if (Source == null || item >= Source.Count) { - for (int c = 0; c < f.Width; c++) - Driver.AddRune ((Rune)' '); - } else { - var rowEventArgs = new ListViewRowEventArgs (item); - OnRowRender (rowEventArgs); - if (rowEventArgs.RowAttribute != null && current != rowEventArgs.RowAttribute) { - current = (Attribute)rowEventArgs.RowAttribute; - Driver.SetAttribute (current); - } - if (AllowsMarking) { - Driver.AddRune (Source.IsMarked (item) ? (AllowsMultipleSelection ? CM.Glyphs.Checked : CM.Glyphs.Selected) : (AllowsMultipleSelection ? CM.Glyphs.UnChecked : CM.Glyphs.UnSelected)); - Driver.AddRune ((Rune)' '); - } - Source.Render (this, Driver, isSelected, item, col, row, f.Width - col, start); + } else { + var rowEventArgs = new ListViewRowEventArgs (item); + OnRowRender (rowEventArgs); + if (rowEventArgs.RowAttribute != null && current != rowEventArgs.RowAttribute) { + current = (Attribute)rowEventArgs.RowAttribute; + Driver.SetAttribute (current); } + if (AllowsMarking) { + Driver.AddRune (Source.IsMarked (item) ? AllowsMultipleSelection ? Glyphs.Checked : Glyphs.Selected : AllowsMultipleSelection ? Glyphs.UnChecked : Glyphs.UnSelected); + Driver.AddRune ((Rune)' '); + } + Source.Render (this, Driver, isSelected, item, col, row, f.Width - col, start); } } + } - public override bool OnEnter (View view) - { - if (hideDropdownListOnClick) { - isFocusing = true; - highlighted = container.SelectedItem; - Application.GrabMouse (this); - } - - return base.OnEnter (view); + public override bool OnEnter (View view) + { + if (_hideDropdownListOnClick) { + _isFocusing = true; + _highlighted = _container.SelectedItem; + Application.GrabMouse (this); } - public override bool OnLeave (View view) - { - if (hideDropdownListOnClick) { - isFocusing = false; - highlighted = container.SelectedItem; - Application.UngrabMouse (); - } + return base.OnEnter (view); + } - return base.OnLeave (view); + public override bool OnLeave (View view) + { + if (_hideDropdownListOnClick) { + _isFocusing = false; + _highlighted = _container.SelectedItem; + Application.UngrabMouse (); } - public override bool OnSelectedChanged () - { - var res = base.OnSelectedChanged (); + return base.OnLeave (view); + } - highlighted = SelectedItem; + public override bool OnSelectedChanged () + { + bool res = base.OnSelectedChanged (); - return res; - } - } + _highlighted = SelectedItem; - IListDataSource source; - /// - /// Gets or sets the backing this , enabling custom rendering. - /// - /// The source. - /// - /// Use to set a new source. - /// - public IListDataSource Source { - get => source; - set { - source = value; - - // Only need to refresh list if its been added to a container view - if (SuperView != null && SuperView.Subviews.Contains (this)) { - SelectedItem = -1; - search.Text = ""; - Search_Changed (this, new TextChangedEventArgs ("")); - SetNeedsDisplay (); - } - } + return res; } + } - /// - /// Sets the source of the to an . - /// - /// An object implementing the IList interface. - /// - /// Use the property to set a new source and use custome rendering. - /// - public void SetSource (IList source) - { - if (source == null) { - Source = null; - } else { - listview.SetSource (source); - Source = listview.Source; + IListDataSource _source; + + /// + /// Gets or sets the backing this , enabling custom rendering. + /// + /// The source. + /// + /// Use to set a new source. + /// + public IListDataSource Source { + get => _source; + set { + _source = value; + + // Only need to refresh list if its been added to a container view + if (SuperView != null && SuperView.Subviews.Contains (this)) { + SelectedItem = -1; + _search.Text = ""; + Search_Changed (this, new TextChangedEventArgs ("")); + SetNeedsDisplay (); } } + } - /// - /// This event is raised when the selected item in the has changed. - /// - public event EventHandler SelectedItemChanged; - - /// - /// This event is raised when the drop-down list is expanded. - /// - public event EventHandler Expanded; - - /// - /// This event is raised when the drop-down list is collapsed. - /// - public event EventHandler Collapsed; - - /// - /// This event is raised when the user Double Clicks on an item or presses ENTER to open the selected item. - /// - public event EventHandler OpenSelectedItem; - - readonly IList searchset = new List (); - string text = ""; - readonly TextField search; - readonly ComboListView listview; - bool autoHide = true; - readonly int minimumHeight = 2; - - /// - /// Public constructor - /// - public ComboBox () : this (string.Empty) - { + /// + /// Sets the source of the to an . + /// + /// An object implementing the IList interface. + /// + /// Use the property to set a new source and use custome rendering. + /// + public void SetSource (IList source) + { + if (source == null) { + Source = null; + } else { + _listview.SetSource (source); + Source = _listview.Source; } + } - /// - /// Public constructor - /// - /// - public ComboBox (string text) : base () - { - search = new TextField (""); - listview = new ComboListView (this, HideDropdownListOnClick) { LayoutStyle = LayoutStyle.Computed, CanFocus = true, TabStop = false }; + /// + /// This event is raised when the selected item in the has changed. + /// + public event EventHandler SelectedItemChanged; - Initialize (); - Text = text; - } + /// + /// This event is raised when the drop-down list is expanded. + /// + public event EventHandler Expanded; - /// - /// Public constructor - /// - /// - /// - 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 }; + /// + /// This event is raised when the drop-down list is collapsed. + /// + public event EventHandler Collapsed; - Initialize (); - SetSource (source); - } + /// + /// This event is raised when the user Double Clicks on an item or presses ENTER to open the selected item. + /// + public event EventHandler OpenSelectedItem; - /// - /// Initialize with the source. - /// - /// The source. - public ComboBox (IList source) : this (string.Empty) - { - search = new TextField (""); - listview = new ComboListView (this, source, HideDropdownListOnClick) { LayoutStyle = LayoutStyle.Computed, ColorScheme = Colors.Base }; + readonly IList _searchset = new List (); + string _text = ""; + readonly TextField _search; + readonly ComboListView _listview; + bool _autoHide = true; + readonly int _minimumHeight = 2; - Initialize (); - SetSource (source); - } + /// + /// Public constructor + /// + public ComboBox () : this (string.Empty) { } - private void Initialize () - { - if (Bounds.Height < minimumHeight && (Height == null || Height is Dim.DimAbsolute)) { - Height = minimumHeight; - } + /// + /// Public constructor + /// + /// + public ComboBox (string text) : base () + { + _search = new TextField (""); + _listview = new ComboListView (this, HideDropdownListOnClick) { LayoutStyle = LayoutStyle.Computed, CanFocus = true, TabStop = false }; + + SetInitialProperties (); + Text = text; + } - search.TextChanged += Search_Changed; + /// + /// Public constructor + /// + /// + /// + 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 }; + + SetInitialProperties (); + SetSource (source); + } - listview.Y = Pos.Bottom (search); - listview.OpenSelectedItem += (object sender, ListViewItemEventArgs a) => Selected (); + /// + /// Initialize with the source. + /// + /// The source. + public ComboBox (IList source) : this (string.Empty) + { + _search = new TextField (""); + _listview = new ComboListView (this, source, HideDropdownListOnClick) { LayoutStyle = LayoutStyle.Computed, ColorScheme = Colors.Base }; + + SetInitialProperties (); + SetSource (source); + } - Add (search, listview); + void SetInitialProperties () + { + _search.TextChanged += Search_Changed; - // On resize - LayoutComplete += (object sender, LayoutEventArgs a) => { - if ((!autoHide && Bounds.Width > 0 && search.Frame.Width != Bounds.Width) || - (autoHide && Bounds.Width > 0 && search.Frame.Width != Bounds.Width - 1)) { - search.Width = listview.Width = autoHide ? Bounds.Width - 1 : Bounds.Width; - listview.Height = CalculatetHeight (); - search.SetRelativeLayout (Bounds); - listview.SetRelativeLayout (Bounds); - } - }; + _listview.Y = Pos.Bottom (_search); + _listview.OpenSelectedItem += (object sender, ListViewItemEventArgs a) => Selected (); - listview.SelectedItemChanged += (object sender, ListViewItemEventArgs e) => { + Add (_search, _listview); - if (!HideDropdownListOnClick && searchset.Count > 0) { - SetValue (searchset [listview.SelectedItem]); - } - }; + // On resize + LayoutComplete += (object sender, LayoutEventArgs a) => { + if (Bounds.Height < _minimumHeight && (Height == null || Height is Dim.DimAbsolute)) { + Height = _minimumHeight; + } + if (!_autoHide && Bounds.Width > 0 && _search.Frame.Width != Bounds.Width || + _autoHide && Bounds.Width > 0 && _search.Frame.Width != Bounds.Width - 1) { + _search.Width = _listview.Width = _autoHide ? Bounds.Width - 1 : Bounds.Width; + _listview.Height = CalculatetHeight (); + _search.SetRelativeLayout (Bounds); + _listview.SetRelativeLayout (Bounds); + } + }; - Added += (s, e) => { + _listview.SelectedItemChanged += (object sender, ListViewItemEventArgs e) => { - // Determine if this view is hosted inside a dialog and is the only control - for (View view = this.SuperView; view != null; view = view.SuperView) { - if (view is Dialog && SuperView != null && SuperView.Subviews.Count == 1 && SuperView.Subviews [0] == this) { - autoHide = false; - break; - } - } + if (!HideDropdownListOnClick && _searchset.Count > 0) { + SetValue (_searchset [_listview.SelectedItem]); + } + }; - SetNeedsLayout (); - SetNeedsDisplay (); - Search_Changed (this, new TextChangedEventArgs (Text)); - }; - - // Things this view knows how to do - AddCommand (Command.Accept, () => ActivateSelected ()); - AddCommand (Command.ToggleExpandCollapse, () => ExpandCollapse ()); - AddCommand (Command.Expand, () => Expand ()); - AddCommand (Command.Collapse, () => Collapse ()); - AddCommand (Command.LineDown, () => MoveDown ()); - AddCommand (Command.LineUp, () => MoveUp ()); - AddCommand (Command.PageDown, () => PageDown ()); - AddCommand (Command.PageUp, () => PageUp ()); - AddCommand (Command.TopHome, () => MoveHome ()); - AddCommand (Command.BottomEnd, () => MoveEnd ()); - AddCommand (Command.Cancel, () => CancelSelected ()); - AddCommand (Command.UnixEmulation, () => UnixEmulation ()); - - // Default keybindings for this view - KeyBindings.Add (KeyCode.Enter, Command.Accept); - KeyBindings.Add (KeyCode.F4, Command.ToggleExpandCollapse); - KeyBindings.Add (KeyCode.CursorDown, Command.LineDown); - KeyBindings.Add (KeyCode.CursorUp, Command.LineUp); - KeyBindings.Add (KeyCode.PageDown, Command.PageDown); - KeyBindings.Add (KeyCode.PageUp, Command.PageUp); - KeyBindings.Add (KeyCode.Home, Command.TopHome); - KeyBindings.Add (KeyCode.End, Command.BottomEnd); - KeyBindings.Add (KeyCode.Esc, Command.Cancel); - KeyBindings.Add (KeyCode.U | KeyCode.CtrlMask, Command.UnixEmulation); - } - - private bool isShow = false; - private int selectedItem = -1; - private int lastSelectedItem = -1; - private bool hideDropdownListOnClick; - - /// - /// Gets the index of the currently selected item in the - /// - /// The selected item or -1 none selected. - public int SelectedItem { - get => selectedItem; - set { - if (selectedItem != value && (value == -1 - || (source != null && value > -1 && value < source.Count))) { - - selectedItem = lastSelectedItem = value; - if (selectedItem != -1) { - SetValue (source.ToList () [selectedItem].ToString (), true); - } else { - SetValue ("", true); - } - OnSelectedChanged (); + Added += (s, e) => { + + // Determine if this view is hosted inside a dialog and is the only control + for (var view = SuperView; view != null; view = view.SuperView) { + if (view is Dialog && SuperView != null && SuperView.Subviews.Count == 1 && SuperView.Subviews [0] == this) { + _autoHide = false; + break; } } - } - /// - /// Gets the drop down list state, expanded or collapsed. - /// - public bool IsShow => isShow; + SetNeedsLayout (); + SetNeedsDisplay (); + Search_Changed (this, new TextChangedEventArgs (Text)); + }; + + // Things this view knows how to do + AddCommand (Command.Accept, () => ActivateSelected ()); + AddCommand (Command.ToggleExpandCollapse, () => ExpandCollapse ()); + AddCommand (Command.Expand, () => Expand ()); + AddCommand (Command.Collapse, () => Collapse ()); + AddCommand (Command.LineDown, () => MoveDown ()); + AddCommand (Command.LineUp, () => MoveUp ()); + AddCommand (Command.PageDown, () => PageDown ()); + AddCommand (Command.PageUp, () => PageUp ()); + AddCommand (Command.TopHome, () => MoveHome ()); + AddCommand (Command.BottomEnd, () => MoveEnd ()); + AddCommand (Command.Cancel, () => CancelSelected ()); + AddCommand (Command.UnixEmulation, () => UnixEmulation ()); + + // Default keybindings for this view + KeyBindings.Add (KeyCode.Enter, Command.Accept); + KeyBindings.Add (KeyCode.F4, Command.ToggleExpandCollapse); + KeyBindings.Add (KeyCode.CursorDown, Command.LineDown); + KeyBindings.Add (KeyCode.CursorUp, Command.LineUp); + KeyBindings.Add (KeyCode.PageDown, Command.PageDown); + KeyBindings.Add (KeyCode.PageUp, Command.PageUp); + KeyBindings.Add (KeyCode.Home, Command.TopHome); + KeyBindings.Add (KeyCode.End, Command.BottomEnd); + KeyBindings.Add (KeyCode.Esc, Command.Cancel); + KeyBindings.Add (KeyCode.U | KeyCode.CtrlMask, Command.UnixEmulation); + } - /// - public new ColorScheme ColorScheme { - get { - return base.ColorScheme; - } - set { - listview.ColorScheme = value; - base.ColorScheme = value; - SetNeedsDisplay (); - } - } + bool _isShow = false; + int _selectedItem = -1; + int _lastSelectedItem = -1; + bool _hideDropdownListOnClick; - /// - ///If set to true its not allow any changes in the text. - /// - public bool ReadOnly { - get => search.ReadOnly; - set { - search.ReadOnly = value; - if (search.ReadOnly) { - if (search.ColorScheme != null) { - search.ColorScheme.Normal = search.ColorScheme.Focus; - } + /// + /// Gets the index of the currently selected item in the + /// + /// The selected item or -1 none selected. + public int SelectedItem { + get => _selectedItem; + set { + if (_selectedItem != value && (value == -1 + || _source != null && value > -1 && value < _source.Count)) { + + _selectedItem = _lastSelectedItem = value; + if (_selectedItem != -1) { + SetValue (_source.ToList () [_selectedItem].ToString (), true); + } else { + SetValue ("", true); } + OnSelectedChanged (); } } + } - /// - /// Gets or sets if the drop-down list can be hide with a button click event. - /// - public bool HideDropdownListOnClick { - get => hideDropdownListOnClick; - set => hideDropdownListOnClick = listview.HideDropdownListOnClick = value; + /// + /// Gets the drop down list state, expanded or collapsed. + /// + public bool IsShow => _isShow; + + /// + public new ColorScheme ColorScheme { + get => base.ColorScheme; + set { + _listview.ColorScheme = value; + base.ColorScheme = value; + SetNeedsDisplay (); } + } - /// - public override bool MouseEvent (MouseEvent me) - { - if (me.X == Bounds.Right - 1 && me.Y == Bounds.Top && me.Flags == MouseFlags.Button1Pressed - && autoHide) { + /// + ///If set to true its not allow any changes in the text. + /// + public bool ReadOnly { + get => _search.ReadOnly; + set { + _search.ReadOnly = value; + if (_search.ReadOnly) { + if (_search.ColorScheme != null) { + _search.ColorScheme.Normal = _search.ColorScheme.Focus; + } + } + } + } - if (isShow) { - isShow = false; - HideList (); - } else { - SetSearchSet (); + /// + /// Gets or sets if the drop-down list can be hide with a button click event. + /// + public bool HideDropdownListOnClick { + get => _hideDropdownListOnClick; + set => _hideDropdownListOnClick = _listview.HideDropdownListOnClick = value; + } - isShow = true; - ShowList (); - FocusSelectedItem (); - } + /// + public override bool MouseEvent (MouseEvent me) + { + if (me.X == Bounds.Right - 1 && me.Y == Bounds.Top && me.Flags == MouseFlags.Button1Pressed + && _autoHide) { - return true; - } else if (me.Flags == MouseFlags.Button1Pressed) { - if (!search.HasFocus) { - search.SetFocus (); - } + if (_isShow) { + _isShow = false; + HideList (); + } else { + SetSearchSet (); - return true; + _isShow = true; + ShowList (); + FocusSelectedItem (); } - return false; - } + return true; + } else if (me.Flags == MouseFlags.Button1Pressed) { + if (!_search.HasFocus) { + _search.SetFocus (); + } - private void FocusSelectedItem () - { - listview.SelectedItem = SelectedItem > -1 ? SelectedItem : 0; - listview.TabStop = true; - listview.SetFocus (); - OnExpanded (); + return true; } - /// - /// Virtual method which invokes the event. - /// - public virtual void OnExpanded () - { - Expanded?.Invoke (this, EventArgs.Empty); - } + return false; + } - /// - /// Virtual method which invokes the event. - /// - public virtual void OnCollapsed () - { - Collapsed?.Invoke (this, EventArgs.Empty); - } + void FocusSelectedItem () + { + _listview.SelectedItem = SelectedItem > -1 ? SelectedItem : 0; + _listview.TabStop = true; + _listview.SetFocus (); + OnExpanded (); + } - /// - public override bool OnEnter (View view) - { - if (!search.HasFocus && !listview.HasFocus) { - search.SetFocus (); - } + /// + /// Virtual method which invokes the event. + /// + public virtual void OnExpanded () => Expanded?.Invoke (this, EventArgs.Empty); - search.CursorPosition = search.Text.GetRuneCount (); + /// + /// Virtual method which invokes the event. + /// + public virtual void OnCollapsed () => Collapsed?.Invoke (this, EventArgs.Empty); - return base.OnEnter (view); + /// + public override bool OnEnter (View view) + { + if (!_search.HasFocus && !_listview.HasFocus) { + _search.SetFocus (); } - /// - public override bool OnLeave (View view) - { - if (source?.Count > 0 && selectedItem > -1 && selectedItem < source.Count - 1 - && text != source.ToList () [selectedItem].ToString ()) { + _search.CursorPosition = _search.Text.GetRuneCount (); - SetValue (source.ToList () [selectedItem].ToString ()); - } - if (autoHide && isShow && view != this && view != search && view != listview) { - isShow = false; - HideList (); - } else if (listview.TabStop) { - listview.TabStop = false; - } + return base.OnEnter (view); + } - return base.OnLeave (view); + /// + public override bool OnLeave (View view) + { + if (_source?.Count > 0 && _selectedItem > -1 && _selectedItem < _source.Count - 1 + && _text != _source.ToList () [_selectedItem].ToString ()) { + + SetValue (_source.ToList () [_selectedItem].ToString ()); + } + if (_autoHide && _isShow && view != this && view != _search && view != _listview) { + _isShow = false; + HideList (); + } else if (_listview.TabStop) { + _listview.TabStop = false; } - /// - /// Invokes the SelectedChanged event if it is defined. - /// - /// - public virtual bool OnSelectedChanged () - { - // Note: Cannot rely on "listview.SelectedItem != lastSelectedItem" because the list is dynamic. - // So we cannot optimize. Ie: Don't call if not changed - SelectedItemChanged?.Invoke (this, new ListViewItemEventArgs (SelectedItem, search.Text)); + return base.OnLeave (view); + } - return true; - } + /// + /// Invokes the SelectedChanged event if it is defined. + /// + /// + public virtual bool OnSelectedChanged () + { + // Note: Cannot rely on "listview.SelectedItem != lastSelectedItem" because the list is dynamic. + // So we cannot optimize. Ie: Don't call if not changed + SelectedItemChanged?.Invoke (this, new ListViewItemEventArgs (SelectedItem, _search.Text)); + + return true; + } - /// - /// Invokes the OnOpenSelectedItem event if it is defined. - /// - /// - public virtual bool OnOpenSelectedItem () - { - var value = search.Text; - lastSelectedItem = SelectedItem; - OpenSelectedItem?.Invoke (this, new ListViewItemEventArgs (SelectedItem, value)); + /// + /// Invokes the OnOpenSelectedItem event if it is defined. + /// + /// + public virtual bool OnOpenSelectedItem () + { + string value = _search.Text; + _lastSelectedItem = SelectedItem; + OpenSelectedItem?.Invoke (this, new ListViewItemEventArgs (SelectedItem, value)); + + return true; + } - return true; + /// + public override void OnDrawContent (Rect contentArea) + { + base.OnDrawContent (contentArea); + + if (!_autoHide) { + return; } - /// - public override void OnDrawContent (Rect contentArea) - { - base.OnDrawContent (contentArea); + Driver.SetAttribute (ColorScheme.Focus); + Move (Bounds.Right - 1, 0); + Driver.AddRune (Glyphs.DownArrow); + } - if (!autoHide) { - return; + bool UnixEmulation () + { + // Unix emulation + Reset (); + return true; + } + + bool CancelSelected () + { + _search.SetFocus (); + if (ReadOnly || HideDropdownListOnClick) { + SelectedItem = _lastSelectedItem; + if (SelectedItem > -1 && _listview.Source?.Count > 0) { + _search.Text = _text = _listview.Source.ToList () [SelectedItem].ToString (); } + } else if (!ReadOnly) { + _search.Text = _text = ""; + _selectedItem = _lastSelectedItem; + OnSelectedChanged (); + } + return Collapse (); + } - Driver.SetAttribute (ColorScheme.Focus); - Move (Bounds.Right - 1, 0); - Driver.AddRune (CM.Glyphs.DownArrow); + bool? MoveEnd () + { + if (!_isShow && _search.HasFocus) { + return null; + } + if (HasItems ()) { + _listview.MoveEnd (); } + return true; + } - bool UnixEmulation () - { - // Unix emulation - Reset (); - return true; + bool? MoveHome () + { + if (!_isShow && _search.HasFocus) { + return null; + } + if (HasItems ()) { + _listview.MoveHome (); } + return true; + } - bool CancelSelected () - { - search.SetFocus (); - if (ReadOnly || HideDropdownListOnClick) { - SelectedItem = lastSelectedItem; - if (SelectedItem > -1 && listview.Source?.Count > 0) { - search.Text = text = listview.Source.ToList () [SelectedItem].ToString (); - } - } else if (!ReadOnly) { - search.Text = text = ""; - selectedItem = lastSelectedItem; - OnSelectedChanged (); - } - Collapse (); - return true; + bool PageUp () + { + if (HasItems ()) { + _listview.MovePageUp (); } + return true; + } - bool? MoveEnd () - { - if (!isShow && search.HasFocus) { - return null; - } - if (HasItems ()) { - listview.MoveEnd (); - } - return true; + bool PageDown () + { + if (HasItems ()) { + _listview.MovePageDown (); } + return true; + } - bool? MoveHome () - { - if (!isShow && search.HasFocus) { - return null; - } - if (HasItems ()) { - listview.MoveHome (); - } + bool? MoveUp () + { + if (_search.HasFocus) { + // stop odd behavior on KeyUp when search has focus return true; } - bool PageUp () + if (_listview.HasFocus && _listview.SelectedItem == 0 && _searchset?.Count > 0) // jump back to search { - if (HasItems ()) { - listview.MovePageUp (); - } + _search.CursorPosition = _search.Text.GetRuneCount (); + _search.SetFocus (); return true; } + return null; + } - bool PageDown () - { - if (HasItems ()) { - listview.MovePageDown (); + bool? MoveDown () + { + if (_search.HasFocus) { + // jump to list + if (_searchset?.Count > 0) { + _listview.TabStop = true; + _listview.SetFocus (); + if (_listview.SelectedItem > -1) { + SetValue (_searchset [_listview.SelectedItem]); + } + } else { + _listview.TabStop = false; + SuperView?.FocusNext (); } return true; } + return null; + } - bool? MoveUp () - { - if (search.HasFocus) { // stop odd behavior on KeyUp when search has focus - return true; - } - - if (listview.HasFocus && listview.SelectedItem == 0 && searchset?.Count > 0) // jump back to search - { - search.CursorPosition = search.Text.GetRuneCount (); - search.SetFocus (); - return true; + /// + /// Toggles the expand/collapse state of the sublist in the combo box + /// + /// + bool ExpandCollapse () + { + if (_search.HasFocus || _listview.HasFocus) { + if (!_isShow) { + return Expand (); + } else { + return Collapse (); } - return null; } + return false; + } - bool? MoveDown () - { - if (search.HasFocus) { // jump to list - if (searchset?.Count > 0) { - listview.TabStop = true; - listview.SetFocus (); - if (listview.SelectedItem > -1) { - SetValue (searchset [listview.SelectedItem]); - } - } else { - listview.TabStop = false; - SuperView?.FocusNext (); - } - return true; - } - return null; + bool ActivateSelected () + { + if (HasItems ()) { + Selected (); + return true; } + return false; + } - /// - /// Toggles the expand/collapse state of the sublist in the combo box - /// - /// - bool ExpandCollapse () - { - if (search.HasFocus || listview.HasFocus) { - if (!isShow) { - return Expand (); - } else { - return Collapse (); - } - } - return false; - } + bool HasItems () => Source?.Count > 0; - bool ActivateSelected () - { - if (HasItems ()) { - Selected (); - return true; - } + /// + /// Collapses the drop down list. Returns true if the state chagned or false + /// if it was already collapsed and no action was taken + /// + public virtual bool Collapse () + { + if (!_isShow) { return false; } - bool HasItems () - { - return Source?.Count > 0; - } - - /// - /// Collapses the drop down list. Returns true if the state chagned or false - /// if it was already collapsed and no action was taken - /// - public virtual bool Collapse () - { - if (!isShow) { - return false; - } + _isShow = false; + HideList (); + return true; + } - isShow = false; - HideList (); - return true; + /// + /// Expands the drop down list. Returns true if the state chagned or false + /// if it was already expanded and no action was taken + /// + public virtual bool Expand () + { + if (_isShow) { + return false; } - /// - /// Expands the drop down list. Returns true if the state chagned or false - /// if it was already expanded and no action was taken - /// - public virtual bool Expand () - { - if (isShow) { - return false; - } + SetSearchSet (); + _isShow = true; + ShowList (); + FocusSelectedItem (); - SetSearchSet (); - isShow = true; - ShowList (); - FocusSelectedItem (); - - return true; - } + return true; + } - /// - /// The currently selected list item - /// - public new string Text { - get { - return text; - } - set { - SetSearchText (value); - } - } + /// + /// The currently selected list item + /// + public new string Text { + get => _text; + set => SetSearchText (value); + } - /// - /// Current search text - /// - public string SearchText { - get { - return search.Text; - } - set { - SetSearchText (value); - } - } + /// + /// Current search text + /// + public string SearchText { + get => _search.Text; + set => SetSearchText (value); + } - private void SetValue (object text, bool isFromSelectedItem = false) - { - search.TextChanged -= Search_Changed; - this.text = search.Text = text.ToString (); - search.CursorPosition = 0; - search.TextChanged += Search_Changed; - if (!isFromSelectedItem) { - selectedItem = GetSelectedItemFromSource (this.text); - OnSelectedChanged (); - } + void SetValue (object text, bool isFromSelectedItem = false) + { + _search.TextChanged -= Search_Changed; + this._text = _search.Text = text.ToString (); + _search.CursorPosition = 0; + _search.TextChanged += Search_Changed; + if (!isFromSelectedItem) { + _selectedItem = GetSelectedItemFromSource (this._text); + OnSelectedChanged (); } + } - private void Selected () - { - isShow = false; - listview.TabStop = false; - - if (listview.Source.Count == 0 || (searchset?.Count ?? 0) == 0) { - text = ""; - HideList (); - isShow = false; - return; - } + void Selected () + { + _isShow = false; + _listview.TabStop = false; - SetValue (listview.SelectedItem > -1 ? searchset [listview.SelectedItem] : text); - search.CursorPosition = search.Text.GetColumns (); - Search_Changed (this, new TextChangedEventArgs (search.Text)); - OnOpenSelectedItem (); - Reset (keepSearchText: true); + if (_listview.Source.Count == 0 || (_searchset?.Count ?? 0) == 0) { + _text = ""; HideList (); - isShow = false; + _isShow = false; + return; } - private int GetSelectedItemFromSource (string value) + SetValue (_listview.SelectedItem > -1 ? _searchset [_listview.SelectedItem] : _text); + _search.CursorPosition = _search.Text.GetColumns (); + Search_Changed (this, new TextChangedEventArgs (_search.Text)); + OnOpenSelectedItem (); + Reset (keepSearchText: true); + HideList (); + _isShow = false; + } + + private int GetSelectedItemFromSource (string searchText) { - if (source == null) { + if (_source is null) { return -1; } - for (int i = 0; i < source.Count; i++) { - if (source.ToList () [i].ToString () == value) { + for (int i = 0; i < _searchset.Count; i++) { + if (_searchset [i].ToString () == searchText) { return i; } } return -1; } - /// - /// Reset to full original list - /// - private void Reset (bool keepSearchText = false) - { - if (!keepSearchText) { - SetSearchText (string.Empty); - } + /// + /// Reset to full original list + /// + void Reset (bool keepSearchText = false) + { + if (!keepSearchText) { + SetSearchText (string.Empty); + } - ResetSearchSet (); + ResetSearchSet (); - listview.SetSource (searchset); - listview.Height = CalculatetHeight (); + _listview.SetSource (_searchset); + _listview.Height = CalculatetHeight (); - if (Subviews.Count > 0) { - search.SetFocus (); - } - } - private void SetSearchText (string value) - { - search.Text = text = value; + if (Subviews.Count > 0 && HasFocus) { + _search.SetFocus (); } + } - private void ResetSearchSet (bool noCopy = false) - { - searchset.Clear (); + void SetSearchText (string value) => _search.Text = _text = value; - if (autoHide || noCopy) - return; - SetSearchSet (); + void ResetSearchSet (bool noCopy = false) + { + _searchset.Clear (); + + if (_autoHide || noCopy) { + return; } + SetSearchSet (); + } - private void SetSearchSet () - { - if (Source == null) { return; } - // force deep copy - foreach (var item in Source.ToList ()) { - searchset.Add (item); - } + void SetSearchSet () + { + if (Source == null) { return; } + // force deep copy + foreach (object item in Source.ToList ()) { + _searchset.Add (item); } + } private void Search_Changed (object sender, TextChangedEventArgs e) { - if (source == null) { // Object initialization + if (_source is null) { // Object initialization return; } - if (string.IsNullOrEmpty (search.Text) && string.IsNullOrEmpty (e.OldValue)) { + if (string.IsNullOrEmpty (_search.Text) && string.IsNullOrEmpty (e.OldValue)) { ResetSearchSet (); - } else if (search.Text != e.OldValue) { - isShow = true; + } else if (_search.Text != e.OldValue) { + if (_search.Text.Length < e.OldValue.Length) { + _selectedItem = -1; + } + _isShow = true; ResetSearchSet (noCopy: true); - foreach (var item in source.ToList ()) { // Iterate to preserver object type and force deep copy - if (item.ToString ().StartsWith (search.Text, StringComparison.CurrentCultureIgnoreCase)) { - searchset.Add (item); - } + foreach (object item in _source.ToList ()) { + // Iterate to preserver object type and force deep copy + if (item.ToString ().StartsWith (_search.Text, StringComparison.CurrentCultureIgnoreCase)) { + _searchset.Add (item); } } + } - if (HasFocus) { - ShowList (); - } else if (autoHide) { - isShow = false; - HideList (); - } + if (HasFocus) { + ShowList (); + } else if (_autoHide) { + _isShow = false; + HideList (); } + } - /// - /// Show the search list - /// - /// - /// Consider making public - private void ShowList () - { - listview.SetSource (searchset); - listview.Clear (); // Ensure list shrinks in Dialog as you type - listview.Height = CalculatetHeight (); - SuperView?.BringSubviewToFront (this); - } - - /// - /// Hide the search list - /// - /// - /// Consider making public - private void HideList () - { - if (lastSelectedItem != selectedItem) { - OnOpenSelectedItem (); - } - var rect = listview.BoundsToScreen (listview.Bounds); - Reset (keepSearchText: true); - listview.Clear (rect); - listview.TabStop = false; - SuperView?.SendSubviewToBack (this); - SuperView?.SetNeedsDisplay (rect); - OnCollapsed (); - } - - /// - /// Internal height of dynamic search list - /// - /// - private int CalculatetHeight () - { - if (Bounds.Height == 0) - return 0; + /// + /// Show the search list + /// + /// + /// Consider making public + void ShowList () + { + _listview.SetSource (_searchset); + _listview.Clear (); // Ensure list shrinks in Dialog as you type + _listview.Height = CalculatetHeight (); + SuperView?.BringSubviewToFront (this); + } + + /// + /// Hide the search list + /// + /// + /// Consider making public + void HideList () + { + if (_lastSelectedItem != _selectedItem) { + OnOpenSelectedItem (); + } + var rect = _listview.BoundsToScreen (_listview.IsInitialized ? _listview.Bounds : Rect.Empty); + Reset (keepSearchText: true); + _listview.Clear (rect); + _listview.TabStop = false; + SuperView?.SendSubviewToBack (this); + SuperView?.SetNeedsDisplay (rect); + OnCollapsed (); + } - return Math.Min (Math.Max (Bounds.Height - 1, minimumHeight - 1), searchset?.Count > 0 ? searchset.Count : isShow ? Math.Max (Bounds.Height - 1, minimumHeight - 1) : 0); + /// + /// Internal height of dynamic search list + /// + /// + int CalculatetHeight () + { + if (!IsInitialized || Bounds.Height == 0) { + return 0; } + + return Math.Min (Math.Max (Bounds.Height - 1, _minimumHeight - 1), _searchset?.Count > 0 ? _searchset.Count : _isShow ? Math.Max (Bounds.Height - 1, _minimumHeight - 1) : 0); } -} +} \ No newline at end of file diff --git a/Terminal.Gui/Views/FileDialog.cs b/Terminal.Gui/Views/FileDialog.cs index bdc738e3d9..4f8bb4bf1a 100644 --- a/Terminal.Gui/Views/FileDialog.cs +++ b/Terminal.Gui/Views/FileDialog.cs @@ -198,9 +198,12 @@ public FileDialog (IFileSystem fileSystem) Width = Dim.Fill (0), Height = Dim.Fill (1), }; - this.splitContainer.SetSplitterPos (0, 30); + + Initialized += (s, e) => { + this.splitContainer.SetSplitterPos (0, 30); + this.splitContainer.Tiles.ElementAt (0).ContentView.Visible = false; + }; // this.splitContainer.Border.BorderStyle = BorderStyle.None; - this.splitContainer.Tiles.ElementAt (0).ContentView.Visible = false; this.tableView = new TableView { Width = Dim.Fill (), diff --git a/Terminal.Gui/Views/TabView.cs b/Terminal.Gui/Views/TabView.cs index 9b8c80bca3..ca32d8e288 100644 --- a/Terminal.Gui/Views/TabView.cs +++ b/Terminal.Gui/Views/TabView.cs @@ -171,7 +171,9 @@ public void ApplyStyleChanges () // Should be able to just use 0 but switching between top/bottom tabs repeatedly breaks in ValidatePosDim if just using the absolute value 0 tabsBar.Y = Pos.Percent (0); } - LayoutSubviews (); + if (IsInitialized) { + LayoutSubviews (); + } SetNeedsDisplay (); } @@ -279,7 +281,7 @@ public void EnsureValidScrollOffsets () /// public void EnsureSelectedTabIsVisible () { - if (SelectedTab == null) { + if (!IsInitialized || SelectedTab == null) { return; } diff --git a/Terminal.Gui/Views/TextField.cs b/Terminal.Gui/Views/TextField.cs index a416706763..acaf46fdbb 100644 --- a/Terminal.Gui/Views/TextField.cs +++ b/Terminal.Gui/Views/TextField.cs @@ -390,6 +390,9 @@ public virtual int CursorPosition { /// public override void PositionCursor () { + if (!IsInitialized) { + return; + } ProcessAutocomplete (); var col = 0; diff --git a/Terminal.Gui/Views/TextView.cs b/Terminal.Gui/Views/TextView.cs index c0718dd174..0626c6ea73 100644 --- a/Terminal.Gui/Views/TextView.cs +++ b/Terminal.Gui/Views/TextView.cs @@ -1609,175 +1609,58 @@ void SetInitialProperties () Initialized += TextView_Initialized!; - // 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.Accept, () => { - ContextMenu!.Position = new Point (CursorPosition.X - _leftColumn + 2, CursorPosition.Y - _topRow + 2); - ShowContextMenu (); - return true; - }); + // 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); @@ -1881,8 +1764,8 @@ void SetInitialProperties () ContextMenu = new ContextMenu () { MenuItems = BuildContextMenuBarItem () }; ContextMenu.KeyChanged += ContextMenu_KeyChanged!; - KeyBindings.Add ((KeyCode)ContextMenu.Key, KeyBindingScope.HotKey, Command.Accept); - } + 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)), diff --git a/UICatalog/Scenarios/ASCIICustomButton.cs b/UICatalog/Scenarios/ASCIICustomButton.cs index cb2250a1f2..595af572dc 100644 --- a/UICatalog/Scenarios/ASCIICustomButton.cs +++ b/UICatalog/Scenarios/ASCIICustomButton.cs @@ -77,6 +77,7 @@ private void CustomInitialize (string id, string text, Pos x, Pos y, int width, }; AutoSize = false; + 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 d113fe2bac..e5ca76be7f 100644 --- a/UICatalog/Scenarios/AllViewsTester.cs +++ b/UICatalog/Scenarios/AllViewsTester.cs @@ -1,438 +1,407 @@ using System; -using System.Collections; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Reflection; -using System.Text; using Terminal.Gui; -namespace UICatalog.Scenarios { - [ScenarioMetadata (Name: "All Views Tester", Description: "Provides a test UI for all classes derived from View.")] - [ScenarioCategory ("Layout")] - [ScenarioCategory ("Tests")] - [ScenarioCategory ("Top Level Windows")] - public class AllViewsTester : Scenario { - FrameView _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; - - public override void Init () - { - // Don't create a sub-win (Scenario.Win); just use Application.Top - Application.Init (); - ConfigurationManager.Themes.Theme = Theme; - ConfigurationManager.Apply (); - Application.Top.ColorScheme = Colors.ColorSchemes [TopLevelColorScheme]; - } +namespace UICatalog.Scenarios; + +[ScenarioMetadata ("All Views Tester", "Provides a test UI for all classes derived from View.")] +[ScenarioCategory ("Layout")] +[ScenarioCategory ("Tests")] +[ScenarioCategory ("Top Level Windows")] +public class AllViewsTester : Scenario { + FrameView _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; + + public override void Init () + { + // Don't create a sub-win (Scenario.Win); just use Application.Top + Application.Init (); + ConfigurationManager.Themes.Theme = Theme; + ConfigurationManager.Apply (); + Application.Top.ColorScheme = Colors.ColorSchemes [TopLevelColorScheme]; + } - public override void Setup () - { - var statusBar = new StatusBar (new StatusItem [] { - new StatusItem(Application.QuitKey, $"{Application.QuitKey} to Quit", () => Quit()), - new StatusItem(KeyCode.F2, "~F2~ Toggle Frame Ruler", () => { - ConsoleDriver.Diagnostics ^= ConsoleDriver.DiagnosticFlags.FrameRuler; - Application.Top.SetNeedsDisplay (); - }), - new StatusItem(KeyCode.F3, "~F3~ Toggle Frame Padding", () => { - ConsoleDriver.Diagnostics ^= ConsoleDriver.DiagnosticFlags.FramePadding; - Application.Top.SetNeedsDisplay (); - }), - }); - Application.Top.Add (statusBar); - - _viewClasses = GetAllViewClassesCollection () + public override void Setup () + { + var statusBar = new StatusBar (new StatusItem [] { + new (Application.QuitKey, $"{Application.QuitKey} to Quit", () => Quit ()), + new (KeyCode.F2, "~F2~ Toggle Frame Ruler", () => { + ConsoleDriver.Diagnostics ^= ConsoleDriver.DiagnosticFlags.FrameRuler; + Application.Top.SetNeedsDisplay (); + }), + new (KeyCode.F3, "~F3~ Toggle Frame Padding", () => { + ConsoleDriver.Diagnostics ^= ConsoleDriver.DiagnosticFlags.FramePadding; + Application.Top.SetNeedsDisplay (); + }) + }); + Application.Top.Add (statusBar); + + _viewClasses = GetAllViewClassesCollection () .OrderBy (t => t.Name) .Select (t => new KeyValuePair (t.Name, t)) .ToDictionary (t => t.Key, t => t.Value); - _leftPane = new FrameView ("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 (0), - Height = Dim.Fill (0), - AllowsMarking = false, - ColorScheme = Colors.TopLevel, - SelectedItem = 0 - }; - _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]); - }; - _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 }; - _computedCheckBox.Toggled += (s,e) => { - if (_curView != null) { - _curView.LayoutStyle = e.OldValue == true ? LayoutStyle.Absolute : LayoutStyle.Computed; - _hostPane.LayoutSubviews (); - } - }; - _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); - - var label = new Label ("x:") { X = 0, Y = 0 }; - _locationFrame.Add (label); - _xRadioGroup = new RadioGroup (radioItems) { - X = 0, - Y = Pos.Bottom (label), - }; - _xRadioGroup.SelectedItemChanged += (s, selected) => DimPosChanged (_curView); - _xText = new TextField ($"{_xVal}") { X = Pos.Right (label) + 1, Y = 0, Width = 4 }; - _xText.TextChanged += (s, args) => { - try { - _xVal = int.Parse (_xText.Text); - DimPosChanged (_curView); - } catch { - - } - }; - _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 }; - _yText.TextChanged += (s,args) => { - try { - _yVal = int.Parse (_yText.Text); - DimPosChanged (_curView); - } catch { - - } - }; - _locationFrame.Add (_yText); - _yRadioGroup = new RadioGroup (radioItems) { - X = Pos.X (label), - Y = Pos.Bottom (label), - }; - _yRadioGroup.SelectedItemChanged += (s, selected) => DimPosChanged (_curView); - _locationFrame.Add (_yRadioGroup); - - _sizeFrame = new FrameView ("Size (Dim)") { - X = Pos.Right (_locationFrame), - Y = Pos.Y (_locationFrame), - Height = 3 + radioItems.Length, - Width = 40, - }; - - 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), - }; - _wRadioGroup.SelectedItemChanged += (s, selected) => DimPosChanged (_curView); - _wText = new TextField ($"{_wVal}") { X = Pos.Right (label) + 1, Y = 0, Width = 4 }; - _wText.TextChanged += (s,args) => { - try { - switch (_wRadioGroup.SelectedItem) { - case 0: - _wVal = Math.Min (int.Parse (_wText.Text), 100); - break; - case 1: - case 2: - _wVal = int.Parse (_wText.Text); - break; - } - DimPosChanged (_curView); - } catch { - - } - }; - _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 }; - _hText.TextChanged += (s, args) => { - try { - switch (_hRadioGroup.SelectedItem) { - case 0: - _hVal = Math.Min (int.Parse (_hText.Text), 100); - break; - case 1: - case 2: - _hVal = int.Parse (_hText.Text); - break; - } - DimPosChanged (_curView); - } catch { - - } - }; - _sizeFrame.Add (_hText); - - _hRadioGroup = new RadioGroup (radioItems) { - X = Pos.X (label), - Y = Pos.Bottom (label), - }; - _hRadioGroup.SelectedItemChanged += (s, selected) => DimPosChanged (_curView); - _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, - }; - - Application.Top.Add (_leftPane, _settingsPane, _hostPane); - - _curView = CreateClass (_viewClasses.First ().Value); - } - - void DimPosChanged (View view) - { - if (view == null) { - return; + _leftPane = new FrameView ("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 (0), + Height = Dim.Fill (0), + AllowsMarking = false, + ColorScheme = Colors.TopLevel, + SelectedItem = 0 + }; + _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 (); } - - var layout = view.LayoutStyle; - + _curView = CreateClass (_viewClasses.Values.ToArray () [_classListView.SelectedItem]); + }; + _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 }; + _computedCheckBox.Toggled += (s, e) => { + if (_curView != null) { + _curView.LayoutStyle = e.OldValue == true ? LayoutStyle.Absolute : LayoutStyle.Computed; + _hostPane.LayoutSubviews (); + } + }; + _settingsPane.Add (_computedCheckBox); + + string [] 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); + + var label = new Label ("x:") { X = 0, Y = 0 }; + _locationFrame.Add (label); + _xRadioGroup = new RadioGroup (radioItems) { + X = 0, + Y = Pos.Bottom (label) + }; + _xRadioGroup.SelectedItemChanged += (s, selected) => DimPosChanged (_curView); + _xText = new TextField ($"{_xVal}") { X = Pos.Right (label) + 1, Y = 0, Width = 4 }; + _xText.TextChanged += (s, args) => { + try { + _xVal = int.Parse (_xText.Text); + DimPosChanged (_curView); + } catch { } + }; + _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 }; + _yText.TextChanged += (s, args) => { + try { + _yVal = int.Parse (_yText.Text); + DimPosChanged (_curView); + } catch { } + }; + _locationFrame.Add (_yText); + _yRadioGroup = new RadioGroup (radioItems) { + X = Pos.X (label), + Y = Pos.Bottom (label) + }; + _yRadioGroup.SelectedItemChanged += (s, selected) => DimPosChanged (_curView); + _locationFrame.Add (_yRadioGroup); + + _sizeFrame = new FrameView ("Size (Dim)") { + X = Pos.Right (_locationFrame), + Y = Pos.Y (_locationFrame), + Height = 3 + radioItems.Length, + Width = 40 + }; + + 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) + }; + _wRadioGroup.SelectedItemChanged += (s, selected) => DimPosChanged (_curView); + _wText = new TextField ($"{_wVal}") { X = Pos.Right (label) + 1, Y = 0, Width = 4 }; + _wText.TextChanged += (s, args) => { 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); + _wVal = Math.Min (int.Parse (_wText.Text), 100); break; case 1: - view.Width = Dim.Fill (_wVal); - break; case 2: - view.Width = Dim.Sized (_wVal); + _wVal = int.Parse (_wText.Text); break; } - + DimPosChanged (_curView); + } catch { } + }; + _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 }; + _hText.TextChanged += (s, args) => { + try { switch (_hRadioGroup.SelectedItem) { case 0: - view.Height = Dim.Percent (_hVal); + _hVal = Math.Min (int.Parse (_hText.Text), 100); break; case 1: - view.Height = Dim.Fill (_hVal); - break; case 2: - view.Height = Dim.Sized (_hVal); + _hVal = int.Parse (_hText.Text); break; } - } catch (Exception e) { - MessageBox.ErrorQuery ("Exception", e.Message, "Ok"); - } finally { - view.LayoutStyle = layout; - } - UpdateTitle (view); - } + DimPosChanged (_curView); + } catch { } + }; + _sizeFrame.Add (_hText); + + _hRadioGroup = new RadioGroup (radioItems) { + X = Pos.X (label), + Y = Pos.Bottom (label) + }; + _hRadioGroup.SelectedItemChanged += (s, selected) => DimPosChanged (_curView); + _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 + }; + + Application.Top.Add (_leftPane, _settingsPane, _hostPane); + + _curView = CreateClass (_viewClasses.First ().Value); + } - List posNames = new List { "Factor", "AnchorEnd", "Center", "Absolute" }; - List dimNames = new List { "Factor", "Fill", "Absolute" }; - - 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 DimPosChanged (View view) + { + if (view == null) { + return; } - void UpdateTitle (View view) - { - _hostPane.Title = $"{view.GetType ().Name} - {view.X}, {view.Y}, {view.Width}, {view.Height}"; - } + var layout = view.LayoutStyle; - 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); - } - types.Add (typeof (View)); - return types; + try { + view.LayoutStyle = LayoutStyle.Absolute; + + view.X = _xRadioGroup.SelectedItem switch { + 0 => Pos.Percent (_xVal), + 1 => Pos.AnchorEnd (_xVal), + 2 => Pos.Center (), + 3 => Pos.At (_xVal), + _ => view.X + }; + + view.Y = _yRadioGroup.SelectedItem switch { + 0 => Pos.Percent (_yVal), + 1 => Pos.AnchorEnd (_yVal), + 2 => Pos.Center (), + 3 => Pos.At (_yVal), + _ => view.Y + }; + + view.Width = _wRadioGroup.SelectedItem switch { + 0 => Dim.Percent (_wVal), + 1 => Dim.Fill (_wVal), + 2 => Dim.Sized (_wVal), + _ => view.Width + }; + + view.Height = _hRadioGroup.SelectedItem switch { + 0 => Dim.Percent (_hVal), + 1 => Dim.Fill (_hVal), + 2 => Dim.Sized (_hVal), + _ => view.Height + }; + } catch (Exception e) { + MessageBox.ErrorQuery ("Exception", e.Message, "Ok"); + } finally { + view.LayoutStyle = layout; } - + UpdateTitle (view); + } - View CreateClass (Type type) - { - // If we are to create a generic Type - if (type.IsGenericType) { + // TODO: This is missing some + List _posNames = new() { "Factor", "AnchorEnd", "Center", "Absolute" }; + List _dimNames = new() { "Factor", "Fill", "Absolute" }; + + void UpdateSettings (View view) + { + string x = view.X.ToString (); + string 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}"; + + string w = view.Width.ToString (); + string 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}"; + } - // For each of the arguments - List typeArguments = new List (); + void UpdateTitle (View view) => _hostPane.Title = $"{view.GetType ().Name} - {view.X}, {view.Y}, {view.Width}, {view.Height}"; - // use - foreach (var arg in type.GetGenericArguments ()) { - typeArguments.Add (typeof (object)); - } + 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); + } + types.Add (typeof (View)); + return types; + } - // And change what type we are instantiating from MyClass to MyClass - type = type.MakeGenericType (typeArguments.ToArray ()); - } - // Instantiate view - var view = (View)Activator.CreateInstance (type); + // TODO: Add Command.Default handler (pop a message box?) + View CreateClass (Type type) + { + // If we are to create a generic Type + if (type.IsGenericType) { - //_curView.X = Pos.Center (); - //_curView.Y = Pos.Center (); - view.Width = Dim.Percent (75); - view.Height = Dim.Percent (75); + // For each of the arguments + var typeArguments = new List (); - // Set the colorscheme to make it stand out if is null by default - if (view.ColorScheme == null) { - view.ColorScheme = Colors.Base; + // use + foreach (var arg in type.GetGenericArguments ()) { + typeArguments.Add (typeof (object)); } - // 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; - } - } + // And change what type we are instantiating from MyClass to MyClass + type = type.MakeGenericType (typeArguments.ToArray ()); + } + // Instantiate view + var view = (View)Activator.CreateInstance (type); - // 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 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 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); - _hostPane.Clear (); - _hostPane.SetNeedsDisplay (); - UpdateSettings (view); - UpdateTitle (view); + // Set Settings + _computedCheckBox.Checked = view.LayoutStyle == LayoutStyle.Computed; - view.LayoutComplete += LayoutCompleteHandler; + view.Initialized += View_Initialized; - return view; - } + // Add + _hostPane.Add (view); + _hostPane.SetNeedsDisplay (); - void LayoutCompleteHandler (object sender, LayoutEventArgs args) - { - UpdateTitle (_curView); - } + return view; + } + + void View_Initialized (object sender, EventArgs e) + { + var view = sender as View; - private void Quit () - { - Application.RequestStop (); + //view.X = Pos.Center (); + //view.Y = Pos.Center (); + if (view.Width == null || view.Frame.Width == 0) { + view.Width = Dim.Fill(); + } + if (view.Height == null || view.Frame.Height == 0) { + view.Height = Dim.Fill(); } + UpdateSettings (view); + UpdateTitle (view); } + + void LayoutCompleteHandler (object sender, LayoutEventArgs args) + { + UpdateSettings (_curView); + UpdateTitle (_curView); + } + + void Quit () => Application.RequestStop (); } \ No newline at end of file diff --git a/UICatalog/Scenarios/CharacterMap.cs b/UICatalog/Scenarios/CharacterMap.cs index 762017c2b2..7dc7574a34 100644 --- a/UICatalog/Scenarios/CharacterMap.cs +++ b/UICatalog/Scenarios/CharacterMap.cs @@ -114,10 +114,9 @@ public override void Setup () Application.Top.Add (_categoryList); _charMap.SelectedCodePoint = 0; - //jumpList.Refresh (); _charMap.SetFocus (); - - _charMap.Width = Dim.Fill () - _categoryList.Width; + // TODO: Replace this with Dim.Auto when that's ready + _categoryList.Initialized += _categoryList_Initialized; var menu = new MenuBar (new MenuBarItem [] { new ("_File", new MenuItem [] { @@ -128,12 +127,11 @@ public override void Setup () }) }); Application.Top.Add (menu); - - //_charMap.Hover += (s, a) => { - // _errorLabel.Text = $"U+{a.Item:x5} {(Rune)a.Item}"; - //}; + } + private void _categoryList_Initialized (object sender, EventArgs e) => _charMap.Width = Dim.Fill () - _categoryList.Width; + MenuItem CreateMenuShowWidth () { var item = new MenuItem { @@ -255,24 +253,26 @@ public int SelectedCodePoint { get => _selected; set { _selected = value; - int row = SelectedCodePoint / 16 * _rowHeight; - int col = SelectedCodePoint % 16 * COLUMN_WIDTH; - - int height = Bounds.Height - (ShowHorizontalScrollIndicator ? 2 : 1); - if (row + ContentOffset.Y < 0) { - // Moving up. - ContentOffset = new Point (ContentOffset.X, row); - } else if (row + ContentOffset.Y >= height) { - // Moving down. - ContentOffset = new Point (ContentOffset.X, Math.Min (row, row - height + _rowHeight)); - } - int width = Bounds.Width / COLUMN_WIDTH * COLUMN_WIDTH - (ShowVerticalScrollIndicator ? RowLabelWidth + 1 : RowLabelWidth); - if (col + ContentOffset.X < 0) { - // Moving left. - ContentOffset = new Point (col, ContentOffset.Y); - } else if (col + ContentOffset.X >= width) { - // Moving right. - ContentOffset = new Point (Math.Min (col, col - width + COLUMN_WIDTH), ContentOffset.Y); + if (IsInitialized) { + int row = SelectedCodePoint / 16 * _rowHeight; + int col = SelectedCodePoint % 16 * COLUMN_WIDTH; + + int height = Bounds.Height - (ShowHorizontalScrollIndicator ? 2 : 1); + if (row + ContentOffset.Y < 0) { + // Moving up. + ContentOffset = new Point (ContentOffset.X, row); + } else if (row + ContentOffset.Y >= height) { + // Moving down. + ContentOffset = new Point (ContentOffset.X, Math.Min (row, row - height + _rowHeight)); + } + int width = Bounds.Width / COLUMN_WIDTH * COLUMN_WIDTH - (ShowVerticalScrollIndicator ? RowLabelWidth + 1 : RowLabelWidth); + if (col + ContentOffset.X < 0) { + // Moving left. + ContentOffset = new Point (col, ContentOffset.Y); + } else if (col + ContentOffset.X >= width) { + // Moving right. + ContentOffset = new Point (Math.Min (col, col - width + COLUMN_WIDTH), ContentOffset.Y); + } } SetNeedsDisplay (); SelectedCodePointChanged?.Invoke (this, new ListViewItemEventArgs (SelectedCodePoint, null)); diff --git a/UICatalog/Scenarios/ClassExplorer.cs b/UICatalog/Scenarios/ClassExplorer.cs index 79992b3072..60eda61951 100644 --- a/UICatalog/Scenarios/ClassExplorer.cs +++ b/UICatalog/Scenarios/ClassExplorer.cs @@ -59,7 +59,6 @@ public override void Setup () Win.Title = this.GetName (); Win.Y = 1; // menu Win.Height = Dim.Fill (1); // status bar - Application.Top.LayoutSubviews (); var menu = new MenuBar (new MenuBarItem [] { new MenuBarItem ("_File", new MenuItem [] { diff --git a/UICatalog/Scenarios/ColorPicker.cs b/UICatalog/Scenarios/ColorPicker.cs index 9533726687..241c07f1b0 100644 --- a/UICatalog/Scenarios/ColorPicker.cs +++ b/UICatalog/Scenarios/ColorPicker.cs @@ -91,6 +91,7 @@ public override void Setup () // 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 (); } /// diff --git a/UICatalog/Scenarios/ComputedLayout.cs b/UICatalog/Scenarios/ComputedLayout.cs index 19a9fde2c8..c1f24a17c8 100644 --- a/UICatalog/Scenarios/ComputedLayout.cs +++ b/UICatalog/Scenarios/ComputedLayout.cs @@ -79,7 +79,9 @@ public override void Setup () Width = Dim.Fill (margin), Height = 7 }; - subWin.Title = $"{subWin.GetType ().Name} {{X={subWin.X},Y={subWin.Y},Width={subWin.Width},Height={subWin.Height}}}"; + subWin.Initialized += (s, a) => { + subWin.Title = $"{subWin.GetType ().Name} {{X={subWin.X},Y={subWin.Y},Width={subWin.Width},Height={subWin.Height}}}"; + }; Application.Top.Add (subWin); int i = 1; @@ -98,7 +100,10 @@ public override void Setup () Width = 30, Height = 7 }; - frameView.Title = $"{frameView.GetType ().Name} {{X={frameView.X},Y={frameView.Y},Width={frameView.Width},Height={frameView.Height}}}"; + frameView.Initialized += (sender, args) => { + var fv = sender as FrameView; + fv.Title = $"{frameView.GetType ().Name} {{X={fv.X},Y={fv.Y},Width={fv.Width},Height={fv.Height}}}"; + }; i = 1; labelList = new List