diff --git a/.editorconfig b/.editorconfig index 040e7abd97..f2899c92af 100644 --- a/.editorconfig +++ b/.editorconfig @@ -20,4 +20,4 @@ csharp_style_var_when_type_is_apparent = true csharp_prefer_braces = false csharp_space_before_open_square_brackets = true csharp_space_between_method_call_name_and_opening_parenthesis = true -csharp_space_between_method_declaration_name_and_open_parenthesis = true \ No newline at end of file +csharp_space_between_method_declaration_name_and_open_parenthesis = true diff --git a/Terminal.Gui/Application.cs b/Terminal.Gui/Application.cs index 101b0587de..3f0baed243 100644 --- a/Terminal.Gui/Application.cs +++ b/Terminal.Gui/Application.cs @@ -18,10 +18,10 @@ namespace Terminal.Gui { /// // 5 rows/columns of padding. /// Application.Init(); /// var win = new Window ($"Example App ({Application.QuitKey} to quit)") { - /// X = 5, - /// Y = 5, - /// Width = Dim.Fill (5), - /// Height = Dim.Fill (5) + /// X = 5, + /// Y = 5, + /// Width = Dim.Fill (5), + /// Height = Dim.Fill (5) /// }; /// Application.Top.Add(win); /// Application.Run(); @@ -29,20 +29,19 @@ namespace Terminal.Gui { /// /// /// - /// - /// Creates a instance of to process input events, handle timers and - /// other sources of data. It is accessible via the property. - /// - /// - /// The event is invoked on each iteration of the . - /// - /// - /// When invoked it sets the to one that is tied - /// to the , allowing user code to use async/await. - /// + /// + /// Creates a instance of to process input events, handle timers and + /// other sources of data. It is accessible via the property. + /// + /// + /// The event is invoked on each iteration of the . + /// + /// + /// When invoked it sets the to one that is tied + /// to the , allowing user code to use async/await. + /// /// public static partial class Application { - static readonly Stack _toplevels = new Stack (); /// /// The current in use. @@ -50,54 +49,15 @@ public static partial class Application { public static ConsoleDriver Driver; /// - /// Gets all the Mdi childes which represent all the not modal from the . - /// - public static List MdiChildren { - get { - if (MdiTop != null) { - List _mdiChildren = new List (); - foreach (var top in _toplevels) { - if (top != MdiTop && !top.Modal) { - _mdiChildren.Add (top); - } - } - return _mdiChildren; - } - return null; - } - } - - /// - /// The object used for the application on startup which is true. - /// - public static Toplevel MdiTop { - get { - if (Top.IsMdiContainer) { - return Top; - } - return null; - } - } - - /// - /// The object used for the application on startup () - /// - /// The top. - public static Toplevel Top { get; private set; } - - /// - /// The current object. This is updated when enters and leaves to point to the current . - /// - /// The current. - public static Toplevel Current { get; private set; } - - /// - /// The current object that wants continuous mouse button pressed events. + /// If , forces the use of the System.Console-based (see ) driver. The default is . /// - public static View WantContinuousButtonPressedView { get; private set; } + [SerializableConfigurationProperty (Scope = typeof (SettingsScope))] + public static bool UseSystemConsole { get; set; } = false; + // For Unit testing - ignores UseSystemConsole + internal static bool _forceFakeConsole; + private static bool? _enableConsoleScrolling; - /// /// The current used in the terminal. /// @@ -132,194 +92,35 @@ public static bool EnableConsoleScrolling { } } - static Key _alternateForwardKey = Key.PageDown | Key.CtrlMask; - - /// - /// Alternative key to navigate forwards through views. Ctrl+Tab is the primary key. - /// - [SerializableConfigurationProperty (Scope = typeof (SettingsScope)), JsonConverter (typeof (KeyJsonConverter))] - public static Key AlternateForwardKey { - get => _alternateForwardKey; - set { - if (_alternateForwardKey != value) { - var oldKey = _alternateForwardKey; - _alternateForwardKey = value; - OnAlternateForwardKeyChanged (new KeyChangedEventArgs (oldKey, value)); - } - } - } - - static void OnAlternateForwardKeyChanged (KeyChangedEventArgs e) - { - foreach (var top in _toplevels.ToArray ()) { - top.OnAlternateForwardKeyChanged (e); - } - } - - static Key _alternateBackwardKey = Key.PageUp | Key.CtrlMask; - - /// - /// Alternative key to navigate backwards through views. Shift+Ctrl+Tab is the primary key. - /// - [SerializableConfigurationProperty (Scope = typeof (SettingsScope)), JsonConverter (typeof (KeyJsonConverter))] - public static Key AlternateBackwardKey { - get => _alternateBackwardKey; - set { - if (_alternateBackwardKey != value) { - var oldKey = _alternateBackwardKey; - _alternateBackwardKey = value; - OnAlternateBackwardKeyChanged (new KeyChangedEventArgs (oldKey, value)); - } - } - } - - static void OnAlternateBackwardKeyChanged (KeyChangedEventArgs oldKey) - { - foreach (var top in _toplevels.ToArray ()) { - top.OnAlternateBackwardKeyChanged (oldKey); - } - } - - static Key _quitKey = Key.Q | Key.CtrlMask; - - /// - /// Gets or sets the key to quit the application. - /// - [SerializableConfigurationProperty (Scope = typeof (SettingsScope)), JsonConverter (typeof (KeyJsonConverter))] - public static Key QuitKey { - get => _quitKey; - set { - if (_quitKey != value) { - var oldKey = _quitKey; - _quitKey = value; - OnQuitKeyChanged (new KeyChangedEventArgs (oldKey, value)); - } - } - } - - private static List _supportedCultures; - - /// - /// Gets all supported cultures by the application without the invariant language. - /// - public static List SupportedCultures => _supportedCultures; - - static void OnQuitKeyChanged (KeyChangedEventArgs e) - { - // Duplicate the list so if it changes during enumeration we're safe - foreach (var top in _toplevels.ToArray ()) { - top.OnQuitKeyChanged (e); - } - } - - /// - /// The driver for the application - /// - /// The main loop. - public static MainLoop MainLoop { get; private set; } - - /// - /// Disable or enable the mouse. The mouse is enabled by default. - /// - [SerializableConfigurationProperty (Scope = typeof (SettingsScope))] - public static bool IsMouseDisabled { get; set; } - - /// - /// Set to true to cause the RunLoop method to exit after the first iterations. - /// Set to false (the default) to cause the RunLoop to continue running until Application.RequestStop() is called. - /// - public static bool ExitRunLoopAfterFirstIteration { get; set; } = false; - - /// - /// Notify that a new was created ( was called). The token is created in - /// and this event will be fired before that function exits. - /// - /// - /// If is callers to - /// must also subscribe to - /// and manually dispose of the token when the application is done. - /// - public static event EventHandler NotifyNewRunState; - - /// - /// Notify that a existent is stopping ( was called). - /// - /// - /// If is callers to - /// must also subscribe to - /// and manually dispose of the token when the application is done. - /// - public static event EventHandler NotifyStopRunState; + private static List _cachedSupportedCultures; /// - /// This event is raised on each iteration of the . + /// Gets all cultures supported by the application without the invariant language. /// - /// - /// See also - /// - public static Action Iteration; + public static List SupportedCultures => _cachedSupportedCultures; - /// - /// Returns a rectangle that is centered in the screen for the provided size. - /// - /// The centered rect. - /// Size for the rectangle. - public static Rect MakeCenteredRect (Size size) + private static List GetSupportedCultures () { - return new Rect (new Point ((Driver.Cols - size.Width) / 2, (Driver.Rows - size.Height) / 2), size); - } - - // - // provides the sync context set while executing code in Terminal.Gui, to let - // users use async/await on their code - // - class MainLoopSyncContext : SynchronizationContext { - readonly MainLoop mainLoop; + CultureInfo [] culture = CultureInfo.GetCultures (CultureTypes.AllCultures); - public MainLoopSyncContext (MainLoop mainLoop) - { - this.mainLoop = mainLoop; - } + // Get the assembly + Assembly assembly = Assembly.GetExecutingAssembly (); - public override SynchronizationContext CreateCopy () - { - return new MainLoopSyncContext (MainLoop); - } + //Find the location of the assembly + string assemblyLocation = AppDomain.CurrentDomain.BaseDirectory; - public override void Post (SendOrPostCallback d, object state) - { - mainLoop.AddIdle (() => { - d (state); - return false; - }); - //mainLoop.Driver.Wakeup (); - } + // Find the resource file name of the assembly + string resourceFilename = $"{Path.GetFileNameWithoutExtension (assembly.Location)}.resources.dll"; - public override void Send (SendOrPostCallback d, object state) - { - if (Thread.CurrentThread.ManagedThreadId == _mainThreadId) { - d (state); - } else { - var wasExecuted = false; - mainLoop.Invoke (() => { - d (state); - wasExecuted = true; - }); - while (!wasExecuted) { - Thread.Sleep (15); - } - } - } + // Return all culture for which satellite folder found with culture code. + return culture.Where (cultureInfo => + assemblyLocation != null && + Directory.Exists (Path.Combine (assemblyLocation, cultureInfo.Name)) && + File.Exists (Path.Combine (assemblyLocation, cultureInfo.Name, resourceFilename)) + ).ToList (); } - - /// - /// If , forces the use of the System.Console-based (see ) driver. The default is . - /// - [SerializableConfigurationProperty (Scope = typeof (SettingsScope))] - public static bool UseSystemConsole { get; set; } = false; - - // For Unit testing - ignores UseSystemConsole - internal static bool _forceFakeConsole; + + #region Initialization (Init/Shutdown) /// /// Initializes a new instance of Application. @@ -370,15 +171,6 @@ internal static void InternalInit (Func topLevelFactory, ConsoleDriver throw new InvalidOperationException ("Init has already been called and must be bracketed by Shutdown."); } - // Note in this case, we don't verify the type of the Toplevel created by new T(). - // Used only for start debugging on Unix. - //#if DEBUG - // while (!System.Diagnostics.Debugger.IsAttached) { - // System.Threading.Thread.Sleep (100); - // } - // System.Diagnostics.Debugger.Break (); - //#endif - if (!calledViaRunT) { // Reset all class variables (Application is a singleton). ResetState (); @@ -386,12 +178,6 @@ internal static void InternalInit (Func topLevelFactory, ConsoleDriver // For UnitTests if (driver != null) { - //if (mainLoopDriver == null) { - // throw new ArgumentNullException ("InternalInit mainLoopDriver cannot be null if driver is provided."); - //} - //if (!(driver is FakeDriver)) { - // throw new InvalidOperationException ("InternalInit can only be called with FakeDriver."); - //} Driver = driver; } @@ -440,7 +226,7 @@ internal static void InternalInit (Func topLevelFactory, ConsoleDriver try { Driver.EnableConsoleScrolling = EnableConsoleScrolling; - Driver.Init (TerminalResized); + Driver.Init (OnTerminalResized); } catch (InvalidOperationException ex) { // This is a case where the driver is unable to initialize the console. // This can happen if the console is already in use by another process or @@ -453,47 +239,131 @@ internal static void InternalInit (Func topLevelFactory, ConsoleDriver Top = topLevelFactory (); Current = Top; - _supportedCultures = GetSupportedCultures (); + _cachedSupportedCultures = GetSupportedCultures (); _mainThreadId = Thread.CurrentThread.ManagedThreadId; _initialized = true; } + /// - /// Captures the execution state for the provided view. + /// Shutdown an application initialized with . /// - public class RunState : IDisposable { - /// - /// Initializes a new class. - /// - /// - public RunState (Toplevel view) - { - Toplevel = view; - } - /// - /// The belong to this . - /// - public Toplevel Toplevel { get; internal set; } + /// + /// Shutdown must be called for every call to or + /// to ensure all resources are cleaned up (Disposed) and terminal settings are restored. + /// + public static void Shutdown () + { + ResetState (); + ConfigurationManager.PrintJsonErrors (); + } -#if DEBUG_IDISPOSABLE - /// - /// For debug purposes to verify objects are being disposed properly - /// - public bool WasDisposed = false; - /// - /// For debug purposes to verify objects are being disposed properly - /// - public int DisposedCount = 0; - /// - /// For debug purposes - /// - public static List Instances = new List (); - /// - /// For debug purposes - /// - public RunState () - { - Instances.Add (this); + // Encapsulate all setting of initial state for Application; Having + // this in a function like this ensures we don't make mistakes in + // guaranteeing that the state of this singleton is deterministic when Init + // starts running and after Shutdown returns. + static void ResetState () + { + // Shutdown is the bookend for Init. As such it needs to clean up all resources + // Init created. Apps that do any threading will need to code defensively for this. + // e.g. see Issue #537 + foreach (var t in _toplevels) { + t.Running = false; + t.Dispose (); + } + _toplevels.Clear (); + Current = null; + Top?.Dispose (); + Top = null; + + // BUGBUG: OverlappedTop is not cleared here, but it should be? + + MainLoop = null; + Driver?.End (); + Driver = null; + Iteration = null; + RootMouseEvent = null; + RootKeyEvent = null; + TerminalResized = null; + _mainThreadId = -1; + NotifyNewRunState = null; + NotifyStopRunState = null; + _initialized = false; + _mouseGrabView = null; + _enableConsoleScrolling = false; + _lastMouseOwnerView = null; + + // Reset synchronization context to allow the user to run async/await, + // as the main loop has been ended, the synchronization context from + // gui.cs does no longer process any callbacks. See #1084 for more details: + // (https://github.com/gui-cs/Terminal.Gui/issues/1084). + SynchronizationContext.SetSynchronizationContext (syncContext: null); + } + + #endregion Initialization (Init/Shutdown) + + #region Run (Begin, Run, End) + + /// + /// Notify that a new was created ( was called). The token is created in + /// and this event will be fired before that function exits. + /// + /// + /// If is callers to + /// must also subscribe to + /// and manually dispose of the token when the application is done. + /// + public static event EventHandler NotifyNewRunState; + + /// + /// Notify that a existent is stopping ( was called). + /// + /// + /// If is callers to + /// must also subscribe to + /// and manually dispose of the token when the application is done. + /// + public static event EventHandler NotifyStopRunState; + + /// + /// The execution state for a view. + /// + public class RunState : IDisposable { + /// + /// Initializes a new class. + /// + /// + public RunState (Toplevel view) + { + Toplevel = view; + } + /// + /// The belonging to this . + /// + public Toplevel Toplevel { get; internal set; } + +#if DEBUG_IDISPOSABLE + /// + /// For debug (see DEBUG_IDISPOSABLE define) purposes to verify objects are being disposed properly + /// + public bool WasDisposed = false; + + /// + /// For debug (see DEBUG_IDISPOSABLE define) purposes to verify objects are being disposed properly + /// + public int DisposedCount = 0; + + /// + /// For debug (see DEBUG_IDISPOSABLE define) purposes; the runstate instances that have been created + /// + public static List Instances = new List (); + + /// + /// Creates a new RunState object. + /// + public RunState () + { + Instances.Add (this); } #endif @@ -530,485 +400,499 @@ protected virtual void Dispose (bool disposing) } } - static void ProcessKeyEvent (KeyEvent ke) - { - if (RootKeyEvent?.Invoke (ke) ?? false) { - return; - } - - var chain = _toplevels.ToList (); - foreach (var topLevel in chain) { - if (topLevel.ProcessHotKey (ke)) - return; - if (topLevel.Modal) - break; - } - - foreach (var topLevel in chain) { - if (topLevel.ProcessKey (ke)) - return; - if (topLevel.Modal) - break; - } - - foreach (var topLevel in chain) { - // Process the key normally - if (topLevel.ProcessColdKey (ke)) - return; - if (topLevel.Modal) - break; - } - } - - static void ProcessKeyDownEvent (KeyEvent ke) + /// + /// Building block API: Prepares the provided for execution. + /// + /// The handle that needs to be passed to the method upon completion. + /// The to prepare execution for. + /// + /// This method prepares the provided for running with the focus, + /// it adds this to the list of s, sets up the to process the + /// event, lays out the Subviews, focuses the first element, and draws the + /// in the screen. This is usually followed by executing + /// the method, and then the method upon termination which will + /// undo these changes. + /// + public static RunState Begin (Toplevel Toplevel) { - var chain = _toplevels.ToList (); - foreach (var topLevel in chain) { - if (topLevel.OnKeyDown (ke)) - return; - if (topLevel.Modal) - break; + if (Toplevel == null) { + throw new ArgumentNullException (nameof (Toplevel)); + } else if (Toplevel.IsOverlappedContainer && OverlappedTop != Toplevel && OverlappedTop != null) { + throw new InvalidOperationException ("Only one Overlapped Container is allowed."); } - } + var rs = new RunState (Toplevel); - static void ProcessKeyUpEvent (KeyEvent ke) - { - var chain = _toplevels.ToList (); - foreach (var topLevel in chain) { - if (topLevel.OnKeyUp (ke)) - return; - if (topLevel.Modal) - break; + // View implements ISupportInitializeNotification which is derived from ISupportInitialize + if (!Toplevel.IsInitialized) { + Toplevel.BeginInit (); + Toplevel.EndInit (); } - } - - static View FindDeepestTop (Toplevel start, int x, int y, out int resx, out int resy) - { - var startFrame = start.Frame; - if (!startFrame.Contains (x, y)) { - resx = 0; - resy = 0; - return null; - } + lock (_toplevels) { + // If Top was already initialized with Init, and Begin has never been called + // Top was not added to the Toplevels Stack. It will thus never get disposed. + // Clean it up here: + if (Top != null && Toplevel != Top && !_toplevels.Contains (Top)) { + Top.Dispose (); + Top = null; + } else if (Top != null && Toplevel != Top && _toplevels.Contains (Top)) { + Top.OnLeave (Toplevel); + } + if (string.IsNullOrEmpty (Toplevel.Id.ToString ())) { + var count = 1; + var id = (_toplevels.Count + count).ToString (); + while (_toplevels.Count > 0 && _toplevels.FirstOrDefault (x => x.Id.ToString () == id) != null) { + count++; + id = (_toplevels.Count + count).ToString (); + } + Toplevel.Id = (_toplevels.Count + count).ToString (); - if (_toplevels != null) { - int count = _toplevels.Count; - if (count > 0) { - var rx = x - startFrame.X; - var ry = y - startFrame.Y; - foreach (var t in _toplevels) { - if (t != Current) { - if (t != start && t.Visible && t.Frame.Contains (rx, ry)) { - start = t; - break; - } - } + _toplevels.Push (Toplevel); + } else { + var dup = _toplevels.FirstOrDefault (x => x.Id.ToString () == Toplevel.Id); + if (dup == null) { + _toplevels.Push (Toplevel); } } - } - resx = x - startFrame.X; - resy = y - startFrame.Y; - return start; - } - static View FindDeepestMdiView (View start, int x, int y, out int resx, out int resy) - { - if (start.GetType ().BaseType != typeof (Toplevel) - && !((Toplevel)start).IsMdiContainer) { - resx = 0; - resy = 0; - return null; + if (_toplevels.FindDuplicates (new ToplevelEqualityComparer ()).Count > 0) { + throw new ArgumentException ("There are duplicates Toplevels Id's"); + } } - - var startFrame = start.Frame; - - if (!startFrame.Contains (x, y)) { - resx = 0; - resy = 0; - return null; + if (Top == null || Toplevel.IsOverlappedContainer) { + Top = Toplevel; } - int count = _toplevels.Count; - for (int i = count - 1; i >= 0; i--) { - foreach (var top in _toplevels) { - var rx = x - startFrame.X; - var ry = y - startFrame.Y; - if (top.Visible && top.Frame.Contains (rx, ry)) { - var deep = View.FindDeepestView (top, rx, ry, out resx, out resy); - if (deep == null) - return FindDeepestMdiView (top, rx, ry, out resx, out resy); - if (deep != MdiTop) - return deep; - } + var refreshDriver = true; + if (OverlappedTop == null || Toplevel.IsOverlappedContainer || (Current?.Modal == false && Toplevel.Modal) + || (Current?.Modal == false && !Toplevel.Modal) || (Current?.Modal == true && Toplevel.Modal)) { + + if (Toplevel.Visible) { + Current = Toplevel; + SetCurrentOverlappedAsTop (); + } else { + refreshDriver = false; } + } else if ((OverlappedTop != null && Toplevel != OverlappedTop && Current?.Modal == true && !_toplevels.Peek ().Modal) + || (OverlappedTop != null && Toplevel != OverlappedTop && Current?.Running == false)) { + refreshDriver = false; + MoveCurrent (Toplevel); + } else { + refreshDriver = false; + MoveCurrent (Current); } - resx = x - startFrame.X; - resy = y - startFrame.Y; - return start; - } - - static View FindTopFromView (View view) - { - View top = view?.SuperView != null && view?.SuperView != Top - ? view.SuperView : view; - while (top?.SuperView != null && top?.SuperView != Top) { - top = top.SuperView; + Driver.PrepareToRun (MainLoop, ProcessKeyEvent, ProcessKeyDownEvent, ProcessKeyUpEvent, ProcessMouseEvent); + if (Toplevel.LayoutStyle == LayoutStyle.Computed) { + Toplevel.SetRelativeLayout (new Rect (0, 0, Driver.Cols, Driver.Rows)); + } + Toplevel.LayoutSubviews (); + Toplevel.PositionToplevels (); + Toplevel.FocusFirst (); + if (refreshDriver) { + OverlappedTop?.OnChildLoaded (Toplevel); + Toplevel.OnLoaded (); + Toplevel.SetNeedsDisplay (); + Toplevel.Redraw (Toplevel.Bounds); + Toplevel.PositionCursor (); + Driver.Refresh (); } - return top; - } - - static View _mouseGrabView; - - /// - /// The view that grabbed the mouse, to where will be routed all the mouse events. - /// - public static View MouseGrabView => _mouseGrabView; - /// - /// Event to be invoked when a view want grab the mouse which can be canceled. - /// - public static event EventHandler GrabbingMouse; + NotifyNewRunState?.Invoke (Toplevel, new RunStateEventArgs (rs)); + return rs; + } /// - /// Event to be invoked when a view want ungrab the mouse which can be canceled. + /// Runs the application by calling with the value of . /// - public static event EventHandler UnGrabbingMouse; + /// + /// See for more details. + /// + public static void Run (Func errorHandler = null) + { + Run (Top, errorHandler); + } /// - /// Event to be invoked when a view grab the mouse. + /// Runs the application by calling + /// with a new instance of the specified -derived class. + /// + /// Calling first is not needed as this function will initialize the application. + /// + /// + /// must be called when the application is closing (typically after Run> has + /// returned) to ensure resources are cleaned up and terminal settings restored. + /// /// - public static event EventHandler GrabbedMouse; - - /// - /// Event to be invoked when a view ungrab the mouse. - /// - public static event EventHandler UnGrabbedMouse; - - /// - /// Grabs the mouse, forcing all mouse events to be routed to the specified view until UngrabMouse is called. - /// - /// The grab. - /// View that will receive all mouse events until UngrabMouse is invoked. - public static void GrabMouse (View view) + /// + /// See for more details. + /// + /// + /// The to use. If not specified the default driver for the + /// platform will be used (, , or ). + /// Must be if has already been called. + /// + /// Specifies the to use. + public static void Run (Func errorHandler = null, ConsoleDriver driver = null, IMainLoopDriver mainLoopDriver = null) where T : Toplevel, new() { - if (view == null) - return; - if (!OnGrabbingMouse (view)) { - OnGrabbedMouse (view); - _mouseGrabView = view; - Driver.UncookMouse (); + if (_initialized) { + if (Driver != null) { + // Init() has been called and we have a driver, so just run the app. + var top = new T (); + var type = top.GetType ().BaseType; + while (type != typeof (Toplevel) && type != typeof (object)) { + type = type.BaseType; + } + if (type != typeof (Toplevel)) { + throw new ArgumentException ($"{top.GetType ().Name} must be derived from TopLevel"); + } + Run (top, errorHandler); + } else { + // This codepath should be impossible because Init(null, null) will select the platform default driver + throw new InvalidOperationException ("Init() completed without a driver being set (this should be impossible); Run() cannot be called."); + } + } else { + // Init() has NOT been called. + InternalInit (() => new T (), driver, mainLoopDriver, calledViaRunT: true); + Run (Top, errorHandler); } } /// - /// Releases the mouse grab, so mouse events will be routed to the view on which the mouse is. + /// Runs the main loop on the given container. /// - public static void UngrabMouse () + /// + /// + /// This method is used to start processing events + /// for the main application, but it is also used to + /// run other modal s such as boxes. + /// + /// + /// To make a stop execution, call . + /// + /// + /// Calling is equivalent to calling , followed by , + /// and then calling . + /// + /// + /// Alternatively, to have a program control the main loop and + /// process events manually, call to set things up manually and then + /// repeatedly call with the wait parameter set to false. By doing this + /// the method will only process any pending events, timers, idle handlers and + /// then return control immediately. + /// + /// + /// RELEASE builds only: When is any exeptions will be rethrown. + /// Otherwise, if will be called. If + /// returns the will resume; otherwise + /// this method will exit. + /// + /// + /// The to run modally. + /// RELEASE builds only: Handler for any unhandled exceptions (resumes when returns true, rethrows when null). + public static void Run (Toplevel view, Func errorHandler = null) { - if (_mouseGrabView == null) - return; - if (!OnUnGrabbingMouse (_mouseGrabView)) { - OnUnGrabbedMouse (_mouseGrabView); - _mouseGrabView = null; - Driver.CookMouse (); + var resume = true; + while (resume) { +#if !DEBUG + try { +#endif + resume = false; + var runToken = Begin (view); + // If ExitRunLoopAfterFirstIteration is true then the user must dispose of the runToken + // by using NotifyStopRunState event. + RunLoop (runToken); + if (!ExitRunLoopAfterFirstIteration) { + End (runToken); + } +#if !DEBUG + } + catch (Exception error) + { + if (errorHandler == null) + { + throw; + } + resume = errorHandler(error); + } +#endif } - } - - static bool OnGrabbingMouse (View view) - { - if (view == null) - return false; - var evArgs = new GrabMouseEventArgs (view); - GrabbingMouse?.Invoke (view, evArgs); - return evArgs.Cancel; - } + } - static bool OnUnGrabbingMouse (View view) - { - if (view == null) - return false; - var evArgs = new GrabMouseEventArgs (view); - UnGrabbingMouse?.Invoke (view, evArgs); - return evArgs.Cancel; - } - - static void OnGrabbedMouse (View view) + /// + /// Triggers a refresh of the entire display. + /// + public static void Refresh () { - if (view == null) - return; - GrabbedMouse?.Invoke (view, new ViewEventArgs (view)); + Driver.UpdateOffScreen (); + View last = null; + foreach (var v in _toplevels.Reverse ()) { + if (v.Visible) { + v.SetNeedsDisplay (); + v.Redraw (v.Bounds); + } + last = v; + } + last?.PositionCursor (); + Driver.Refresh (); } - static void OnUnGrabbedMouse (View view) - { - if (view == null) - return; - UnGrabbedMouse?.Invoke (view, new ViewEventArgs (view)); - } + /// + /// This event is raised on each iteration of the . + /// + /// + /// See also + /// + public static Action Iteration; /// - /// Merely a debugging aid to see the raw mouse events + /// The driver for the application /// - public static Action RootMouseEvent; + /// The main loop. + public static MainLoop MainLoop { get; private set; } /// - /// - /// Called for new KeyPress events before any processing is performed or - /// views evaluate. Use for global key handling and/or debugging. - /// - /// Return true to suppress the KeyPress event + /// Set to true to cause the RunLoop method to exit after the first iterations. + /// Set to false (the default) to cause the RunLoop to continue running until Application.RequestStop() is called. /// - public static Func RootKeyEvent; + public static bool ExitRunLoopAfterFirstIteration { get; set; } = false; - static View lastMouseOwnerView; + // + // provides the sync context set while executing code in Terminal.Gui, to let + // users use async/await on their code + // + class MainLoopSyncContext : SynchronizationContext { + readonly MainLoop mainLoop; - static void ProcessMouseEvent (MouseEvent me) - { - if (IsMouseDisabled) { - return; + public MainLoopSyncContext (MainLoop mainLoop) + { + this.mainLoop = mainLoop; } - var view = View.FindDeepestView (Current, me.X, me.Y, out int rx, out int ry); - - if (view != null && view.WantContinuousButtonPressed) { - WantContinuousButtonPressedView = view; - } else { - WantContinuousButtonPressedView = null; - } - if (view != null) { - me.View = view; + public override SynchronizationContext CreateCopy () + { + return new MainLoopSyncContext (MainLoop); } - RootMouseEvent?.Invoke (me); - if (me.Handled) { - return; + public override void Post (SendOrPostCallback d, object state) + { + mainLoop.AddIdle (() => { + d (state); + return false; + }); + //mainLoop.Driver.Wakeup (); } - if (_mouseGrabView != null) { - var newxy = _mouseGrabView.ScreenToView (me.X, me.Y); - var nme = new MouseEvent () { - X = newxy.X, - Y = newxy.Y, - Flags = me.Flags, - OfX = me.X - newxy.X, - OfY = me.Y - newxy.Y, - View = view - }; - if (OutsideFrame (new Point (nme.X, nme.Y), _mouseGrabView.Frame)) { - lastMouseOwnerView?.OnMouseLeave (me); - } - //System.Diagnostics.Debug.WriteLine ($"{nme.Flags};{nme.X};{nme.Y};{mouseGrabView}"); - if (_mouseGrabView?.OnMouseEvent (nme) == true) { - return; + public override void Send (SendOrPostCallback d, object state) + { + if (Thread.CurrentThread.ManagedThreadId == _mainThreadId) { + d (state); + } else { + var wasExecuted = false; + mainLoop.Invoke (() => { + d (state); + wasExecuted = true; + }); + while (!wasExecuted) { + Thread.Sleep (15); + } } } + } - if ((view == null || view == MdiTop) && !Current.Modal && MdiTop != null - && me.Flags != MouseFlags.ReportMousePosition && me.Flags != 0) { - - var top = FindDeepestTop (Top, me.X, me.Y, out _, out _); - view = View.FindDeepestView (top, me.X, me.Y, out rx, out ry); + /// + /// Building block API: Runs the for the created . + /// + /// + /// Use the parameter to control whether this is a blocking or non-blocking call. + /// + /// The state returned by the method. + /// By default this is which will execute the loop waiting for events, + /// if set to , a single iteration will execute. + public static void RunLoop (RunState state, bool wait = true) + { + if (state == null) + throw new ArgumentNullException (nameof (state)); + if (state.Toplevel == null) + throw new ObjectDisposedException ("state"); - if (view != null && view != MdiTop && top != Current) { - MoveCurrent ((Toplevel)top); + bool firstIteration = true; + for (state.Toplevel.Running = true; state.Toplevel.Running;) { + if (ExitRunLoopAfterFirstIteration && !firstIteration) { + return; } + RunMainLoopIteration (ref state, wait, ref firstIteration); } + } - if (view != null) { - var nme = new MouseEvent () { - X = rx, - Y = ry, - Flags = me.Flags, - OfX = 0, - OfY = 0, - View = view - }; - - if (lastMouseOwnerView == null) { - lastMouseOwnerView = view; - view.OnMouseEnter (nme); - } else if (lastMouseOwnerView != view) { - lastMouseOwnerView.OnMouseLeave (nme); - view.OnMouseEnter (nme); - lastMouseOwnerView = view; + /// + /// Run one iteration of the . + /// + /// The state returned by . + /// If will execute the waiting for events. If + /// will return after a single iteration. + /// Set to if this is the first run loop iteration. Upon return, + /// it will be set to if at least one iteration happened. + public static void RunMainLoopIteration (ref RunState state, bool wait, ref bool firstIteration) + { + if (MainLoop.EventsPending (wait)) { + // Notify Toplevel it's ready + if (firstIteration) { + state.Toplevel.OnReady (); } - if (!view.WantMousePositionReports && me.Flags == MouseFlags.ReportMousePosition) - return; - - if (view.WantContinuousButtonPressed) - WantContinuousButtonPressedView = view; - else - WantContinuousButtonPressedView = null; - - // Should we bubbled up the event, if it is not handled? - view.OnMouseEvent (nme); - - EnsuresTopOnFront (); - } - } + MainLoop.RunIteration (); + Iteration?.Invoke (); - // Only return true if the Current has changed. - static bool MoveCurrent (Toplevel top) - { - // The Current is modal and the top is not modal toplevel then - // the Current must be moved above the first not modal toplevel. - if (MdiTop != null && top != MdiTop && top != Current && Current?.Modal == true && !_toplevels.Peek ().Modal) { - lock (_toplevels) { - _toplevels.MoveTo (Current, 0, new ToplevelEqualityComparer ()); + EnsureModalOrVisibleAlwaysOnTop (state.Toplevel); + if ((state.Toplevel != Current && Current?.Modal == true) + || (state.Toplevel != Current && Current?.Modal == false)) { + OverlappedTop?.OnDeactivate (state.Toplevel); + state.Toplevel = Current; + OverlappedTop?.OnActivate (state.Toplevel); + Top.SetSubViewNeedsDisplay (); + Refresh (); } - var index = 0; - var savedToplevels = _toplevels.ToArray (); - foreach (var t in savedToplevels) { - if (!t.Modal && t != Current && t != top && t != savedToplevels [index]) { - lock (_toplevels) { - _toplevels.MoveTo (top, index, new ToplevelEqualityComparer ()); - } - } - index++; + if (Driver.EnsureCursorVisibility ()) { + state.Toplevel.SetNeedsDisplay (); } - return false; + } else if (!wait) { + return; } - // The Current and the top are both not running toplevel then - // the top must be moved above the first not running toplevel. - if (MdiTop != null && top != MdiTop && top != Current && Current?.Running == false && !top.Running) { - lock (_toplevels) { - _toplevels.MoveTo (Current, 0, new ToplevelEqualityComparer ()); - } - var index = 0; - foreach (var t in _toplevels.ToArray ()) { - if (!t.Running && t != Current && index > 0) { - lock (_toplevels) { - _toplevels.MoveTo (top, index - 1, new ToplevelEqualityComparer ()); - } + firstIteration = false; + + if (state.Toplevel != Top + && (!Top._needsDisplay.IsEmpty || Top._childNeedsDisplay || Top.LayoutNeeded)) { + state.Toplevel.SetNeedsDisplay (state.Toplevel.Bounds); + Top.Redraw (Top.Bounds); + foreach (var top in _toplevels.Reverse ()) { + if (top != Top && top != state.Toplevel) { + top.SetNeedsDisplay (); + top.Redraw (top.Bounds); } - index++; } - return false; } - if ((MdiTop != null && top?.Modal == true && _toplevels.Peek () != top) - || (MdiTop != null && Current != MdiTop && Current?.Modal == false && top == MdiTop) - || (MdiTop != null && Current?.Modal == false && top != Current) - || (MdiTop != null && Current?.Modal == true && top == MdiTop)) { - lock (_toplevels) { - _toplevels.MoveTo (top, 0, new ToplevelEqualityComparer ()); - Current = top; - } + if (_toplevels.Count == 1 && state.Toplevel == Top + && (Driver.Cols != state.Toplevel.Frame.Width || Driver.Rows != state.Toplevel.Frame.Height) + && (!state.Toplevel._needsDisplay.IsEmpty || state.Toplevel._childNeedsDisplay || state.Toplevel.LayoutNeeded)) { + + Driver.SetAttribute (Colors.TopLevel.Normal); + state.Toplevel.Clear (new Rect (0, 0, Driver.Cols, Driver.Rows)); + + } + + if (!state.Toplevel._needsDisplay.IsEmpty || state.Toplevel._childNeedsDisplay || state.Toplevel.LayoutNeeded + || OverlappedChildNeedsDisplay ()) { + state.Toplevel.Redraw (state.Toplevel.Bounds); + //if (state.Toplevel.SuperView != null) { + // state.Toplevel.SuperView?.OnRenderLineCanvas (); + //} else { + // state.Toplevel.OnRenderLineCanvas (); + //} + state.Toplevel.PositionCursor (); + Driver.Refresh (); + } else { + Driver.UpdateCursor (); + } + if (state.Toplevel != Top && !state.Toplevel.Modal + && (!Top._needsDisplay.IsEmpty || Top._childNeedsDisplay || Top.LayoutNeeded)) { + Top.Redraw (Top.Bounds); } - return true; } - static bool OutsideFrame (Point p, Rect r) + /// + /// Wakes up the that might be waiting on input; must be thread safe. + /// + public static void DoEvents () { - return p.X < 0 || p.X > r.Width - 1 || p.Y < 0 || p.Y > r.Height - 1; + MainLoop.Driver.Wakeup (); } /// - /// Building block API: Prepares the provided for execution. + /// Stops running the most recent or the if provided. /// - /// The handle that needs to be passed to the method upon completion. - /// The to prepare execution for. + /// The to stop. /// - /// This method prepares the provided toplevel for running with the focus, - /// it adds this to the list of toplevels, sets up the mainloop to process the - /// event, lays out the subviews, focuses the first element, and draws the - /// toplevel in the screen. This is usually followed by executing - /// the method, and then the method upon termination which will - /// undo these changes. + /// + /// This will cause to return. + /// + /// + /// Calling is equivalent to setting the property + /// on the currently running to false. + /// /// - public static RunState Begin (Toplevel toplevel) + public static void RequestStop (Toplevel top = null) { - if (toplevel == null) { - throw new ArgumentNullException (nameof (toplevel)); - } else if (toplevel.IsMdiContainer && MdiTop != toplevel && MdiTop != null) { - throw new InvalidOperationException ("Only one Mdi Container is allowed."); + if (OverlappedTop == null || top == null || (OverlappedTop == null && top != null)) { + top = Current; } - var rs = new RunState (toplevel); + if (OverlappedTop != null && top.IsOverlappedContainer && top?.Running == true + && (Current?.Modal == false || (Current?.Modal == true && Current?.Running == false))) { - // View implements ISupportInitializeNotification which is derived from ISupportInitialize - if (!toplevel.IsInitialized) { - toplevel.BeginInit (); - toplevel.EndInit (); - } + OverlappedTop.RequestStop (); + } else if (OverlappedTop != null && top != Current && Current?.Running == true && Current?.Modal == true + && top.Modal && top.Running) { - lock (_toplevels) { - // If Top was already initialized with Init, and Begin has never been called - // Top was not added to the toplevels Stack. It will thus never get disposed. - // Clean it up here: - if (Top != null && toplevel != Top && !_toplevels.Contains (Top)) { - Top.Dispose (); - Top = null; - } else if (Top != null && toplevel != Top && _toplevels.Contains (Top)) { - Top.OnLeave (toplevel); + var ev = new ToplevelClosingEventArgs (Current); + Current.OnClosing (ev); + if (ev.Cancel) { + return; } - if (string.IsNullOrEmpty (toplevel.Id.ToString ())) { - var count = 1; - var id = (_toplevels.Count + count).ToString (); - while (_toplevels.Count > 0 && _toplevels.FirstOrDefault (x => x.Id.ToString () == id) != null) { - count++; - id = (_toplevels.Count + count).ToString (); - } - toplevel.Id = (_toplevels.Count + count).ToString (); + ev = new ToplevelClosingEventArgs (top); + top.OnClosing (ev); + if (ev.Cancel) { + return; + } + Current.Running = false; + OnNotifyStopRunState (Current); + top.Running = false; + OnNotifyStopRunState (top); + } else if ((OverlappedTop != null && top != OverlappedTop && top != Current && Current?.Modal == false + && Current?.Running == true && !top.Running) + || (OverlappedTop != null && top != OverlappedTop && top != Current && Current?.Modal == false + && Current?.Running == false && !top.Running && _toplevels.ToArray () [1].Running)) { - _toplevels.Push (toplevel); + MoveCurrent (top); + } else if (OverlappedTop != null && Current != top && Current?.Running == true && !top.Running + && Current?.Modal == true && top.Modal) { + // The Current and the top are both modal so needed to set the Current.Running to false too. + Current.Running = false; + OnNotifyStopRunState (Current); + } else if (OverlappedTop != null && Current == top && OverlappedTop?.Running == true && Current?.Running == true && top.Running + && Current?.Modal == true && top.Modal) { + // The OverlappedTop was requested to stop inside a modal Toplevel which is the Current and top, + // both are the same, so needed to set the Current.Running to false too. + Current.Running = false; + OnNotifyStopRunState (Current); + } else { + Toplevel currentTop; + if (top == Current || (Current?.Modal == true && !top.Modal)) { + currentTop = Current; } else { - var dup = _toplevels.FirstOrDefault (x => x.Id.ToString () == toplevel.Id); - if (dup == null) { - _toplevels.Push (toplevel); - } + currentTop = top; } - - if (_toplevels.FindDuplicates (new ToplevelEqualityComparer ()).Count > 0) { - throw new ArgumentException ("There are duplicates toplevels Id's"); + if (!currentTop.Running) { + return; } - } - if (Top == null || toplevel.IsMdiContainer) { - Top = toplevel; - } - - var refreshDriver = true; - if (MdiTop == null || toplevel.IsMdiContainer || (Current?.Modal == false && toplevel.Modal) - || (Current?.Modal == false && !toplevel.Modal) || (Current?.Modal == true && toplevel.Modal)) { - - if (toplevel.Visible) { - Current = toplevel; - SetCurrentAsTop (); - } else { - refreshDriver = false; + var ev = new ToplevelClosingEventArgs (currentTop); + currentTop.OnClosing (ev); + if (ev.Cancel) { + return; } - } else if ((MdiTop != null && toplevel != MdiTop && Current?.Modal == true && !_toplevels.Peek ().Modal) - || (MdiTop != null && toplevel != MdiTop && Current?.Running == false)) { - refreshDriver = false; - MoveCurrent (toplevel); - } else { - refreshDriver = false; - MoveCurrent (Current); + currentTop.Running = false; + OnNotifyStopRunState (currentTop); } + } - Driver.PrepareToRun (MainLoop, ProcessKeyEvent, ProcessKeyDownEvent, ProcessKeyUpEvent, ProcessMouseEvent); - if (toplevel.LayoutStyle == LayoutStyle.Computed) { - toplevel.SetRelativeLayout (new Rect (0, 0, Driver.Cols, Driver.Rows)); - } - toplevel.LayoutSubviews (); - toplevel.PositionToplevels (); - toplevel.WillPresent (); - if (refreshDriver) { - MdiTop?.OnChildLoaded (toplevel); - toplevel.OnLoaded (); - toplevel.SetNeedsDisplay (); - toplevel.Redraw (toplevel.Bounds); - toplevel.PositionCursor (); - Driver.Refresh (); + static void OnNotifyStopRunState (Toplevel top) + { + if (ExitRunLoopAfterFirstIteration) { + NotifyStopRunState?.Invoke (top, new ToplevelEventArgs (top)); } - - NotifyNewRunState?.Invoke (toplevel, new RunStateEventArgs (rs)); - return rs; } - + /// /// Building block API: completes the execution of a that was started with . /// @@ -1018,14 +902,14 @@ public static void End (RunState runState) if (runState == null) throw new ArgumentNullException (nameof (runState)); - if (MdiTop != null) { - MdiTop.OnChildUnloaded (runState.Toplevel); + if (OverlappedTop != null) { + OverlappedTop.OnChildUnloaded (runState.Toplevel); } else { runState.Toplevel.OnUnloaded (); } // End the RunState.Toplevel - // First, take it off the toplevel Stack + // First, take it off the Toplevel Stack if (_toplevels.Count > 0) { if (_toplevels.Peek () != runState.Toplevel) { // If there the top of the stack is not the RunState.Toplevel then @@ -1038,10 +922,10 @@ public static void End (RunState runState) // Notify that it is closing runState.Toplevel?.OnClosed (runState.Toplevel); - // If there is a MdiTop that is not the RunState.Toplevel then runstate.TopLevel - // is a child of MidTop and we should notify the MdiTop that it is closing - if (MdiTop != null && !(runState.Toplevel).Modal && runState.Toplevel != MdiTop) { - MdiTop.OnChildClosed (runState.Toplevel); + // If there is a OverlappedTop that is not the RunState.Toplevel then runstate.TopLevel + // is a child of MidTop and we should notify the OverlappedTop that it is closing + if (OverlappedTop != null && !(runState.Toplevel).Modal && runState.Toplevel != OverlappedTop) { + OverlappedTop.OnChildClosed (runState.Toplevel); } // Set Current and Top to the next TopLevel on the stack @@ -1049,10 +933,10 @@ public static void End (RunState runState) Current = null; } else { Current = _toplevels.Peek (); - if (_toplevels.Count == 1 && Current == MdiTop) { - MdiTop.OnAllChildClosed (); + if (_toplevels.Count == 1 && Current == OverlappedTop) { + OverlappedTop.OnAllChildClosed (); } else { - SetCurrentAsTop (); + SetCurrentOverlappedAsTop (); Current.OnEnter (Current); } Refresh (); @@ -1063,544 +947,488 @@ public static void End (RunState runState) runState.Dispose (); } + #endregion Run (Begin, Run, End) + + #region Toplevel handling + static readonly Stack _toplevels = new Stack (); + /// - /// Shutdown an application initialized with . + /// The object used for the application on startup () /// - /// - /// Shutdown must be called for every call to or - /// to ensure all resources are cleaned up (Disposed) and terminal settings are restored. - /// - public static void Shutdown () + /// The top. + public static Toplevel Top { get; private set; } + + /// + /// The current object. This is updated when + /// enters and leaves to point to the current . + /// + /// The current. + public static Toplevel Current { get; private set; } + + static void EnsureModalOrVisibleAlwaysOnTop (Toplevel Toplevel) { - ResetState (); + if (!Toplevel.Running || (Toplevel == Current && Toplevel.Visible) || OverlappedTop == null || _toplevels.Peek ().Modal) { + return; + } - ConfigurationManager.PrintJsonErrors (); + foreach (var top in _toplevels.Reverse ()) { + if (top.Modal && top != Current) { + MoveCurrent (top); + return; + } + } + if (!Toplevel.Visible && Toplevel == Current) { + OverlappedMoveNext (); + } } - // Encapsulate all setting of initial state for Application; Having - // this in a function like this ensures we don't make mistakes in - // guaranteeing that the state of this singleton is deterministic when Init - // starts running and after Shutdown returns. - static void ResetState () + static View FindDeepestTop (Toplevel start, int x, int y, out int resx, out int resy) { - // Shutdown is the bookend for Init. As such it needs to clean up all resources - // Init created. Apps that do any threading will need to code defensively for this. - // e.g. see Issue #537 - foreach (var t in _toplevels) { - t.Running = false; - t.Dispose (); + var startFrame = start.Frame; + + if (!startFrame.Contains (x, y)) { + resx = 0; + resy = 0; + return null; } - _toplevels.Clear (); - Current = null; - Top?.Dispose (); - Top = null; - // BUGBUG: MdiTop is not cleared here, but it should be? + if (_toplevels != null) { + int count = _toplevels.Count; + if (count > 0) { + var rx = x - startFrame.X; + var ry = y - startFrame.Y; + foreach (var t in _toplevels) { + if (t != Current) { + if (t != start && t.Visible && t.Frame.Contains (rx, ry)) { + start = t; + break; + } + } + } + } + } + resx = x - startFrame.X; + resy = y - startFrame.Y; + return start; + } + + static View FindTopFromView (View view) + { + View top = view?.SuperView != null && view?.SuperView != Top + ? view.SuperView : view; - MainLoop = null; - Driver?.End (); - Driver = null; - Iteration = null; - RootMouseEvent = null; - RootKeyEvent = null; - Resized = null; - _mainThreadId = -1; - NotifyNewRunState = null; - NotifyStopRunState = null; - _initialized = false; - _mouseGrabView = null; - _enableConsoleScrolling = false; - lastMouseOwnerView = null; + while (top?.SuperView != null && top?.SuperView != Top) { + top = top.SuperView; + } + return top; + } - // Reset synchronization context to allow the user to run async/await, - // as the main loop has been ended, the synchronization context from - // gui.cs does no longer process any callbacks. See #1084 for more details: - // (https://github.com/gui-cs/Terminal.Gui/issues/1084). - SynchronizationContext.SetSynchronizationContext (syncContext: null); + // Only return true if the Current has changed. + static bool MoveCurrent (Toplevel top) + { + // The Current is modal and the top is not modal Toplevel then + // the Current must be moved above the first not modal Toplevel. + if (OverlappedTop != null && top != OverlappedTop && top != Current && Current?.Modal == true && !_toplevels.Peek ().Modal) { + lock (_toplevels) { + _toplevels.MoveTo (Current, 0, new ToplevelEqualityComparer ()); + } + var index = 0; + var savedToplevels = _toplevels.ToArray (); + foreach (var t in savedToplevels) { + if (!t.Modal && t != Current && t != top && t != savedToplevels [index]) { + lock (_toplevels) { + _toplevels.MoveTo (top, index, new ToplevelEqualityComparer ()); + } + } + index++; + } + return false; + } + // The Current and the top are both not running Toplevel then + // the top must be moved above the first not running Toplevel. + if (OverlappedTop != null && top != OverlappedTop && top != Current && Current?.Running == false && !top.Running) { + lock (_toplevels) { + _toplevels.MoveTo (Current, 0, new ToplevelEqualityComparer ()); + } + var index = 0; + foreach (var t in _toplevels.ToArray ()) { + if (!t.Running && t != Current && index > 0) { + lock (_toplevels) { + _toplevels.MoveTo (top, index - 1, new ToplevelEqualityComparer ()); + } + } + index++; + } + return false; + } + if ((OverlappedTop != null && top?.Modal == true && _toplevels.Peek () != top) + || (OverlappedTop != null && Current != OverlappedTop && Current?.Modal == false && top == OverlappedTop) + || (OverlappedTop != null && Current?.Modal == false && top != Current) + || (OverlappedTop != null && Current?.Modal == true && top == OverlappedTop)) { + lock (_toplevels) { + _toplevels.MoveTo (top, 0, new ToplevelEqualityComparer ()); + Current = top; + } + } + return true; } /// - /// Triggers a refresh of the entire display. + /// Invoked when the terminal was resized. The new size of the terminal is provided. /// - public static void Refresh () + public static Action TerminalResized; + + static void OnTerminalResized () { - Driver.UpdateOffScreen (); - View last = null; - foreach (var v in _toplevels.Reverse ()) { - if (v.Visible) { - v.SetNeedsDisplay (); - v.Redraw (v.Bounds); - } - last = v; + var full = new Rect (0, 0, Driver.Cols, Driver.Rows); + TerminalResized?.Invoke (new ResizedEventArgs () { Cols = full.Width, Rows = full.Height }); + Driver.Clip = full; + foreach (var t in _toplevels) { + t.SetRelativeLayout (full); + t.LayoutSubviews (); + t.PositionToplevels (); + t.OnTerminalResized (new SizeChangedEventArgs (full.Size)); } - last?.PositionCursor (); - Driver.Refresh (); + Refresh (); } + #endregion Toplevel handling + #region Mouse handling + /// + /// Disable or enable the mouse. The mouse is enabled by default. + /// + [SerializableConfigurationProperty (Scope = typeof (SettingsScope))] + public static bool IsMouseDisabled { get; set; } /// - /// Building block API: Runs the for the created . + /// The current object that wants continuous mouse button pressed events. /// - /// - /// Use the parameter to control whether this is a blocking or non-blocking call. - /// - /// The state returned by the method. - /// By default this is which will execute the runloop waiting for events, - /// if set to , a single iteration will execute. - public static void RunLoop (RunState state, bool wait = true) - { - if (state == null) - throw new ArgumentNullException (nameof (state)); - if (state.Toplevel == null) - throw new ObjectDisposedException ("state"); + public static View WantContinuousButtonPressedView { get; private set; } - bool firstIteration = true; - for (state.Toplevel.Running = true; state.Toplevel.Running;) { - if (ExitRunLoopAfterFirstIteration && !firstIteration) { - return; - } - RunMainLoopIteration (ref state, wait, ref firstIteration); - } - } + static View _mouseGrabView; /// - /// Run one iteration of the . + /// The view that grabbed the mouse, to where mouse events will be routed to. /// - /// The state returned by . - /// If will execute the runloop waiting for events. If - /// will return after a single iteration. - /// Set to if this is the first run loop iteration. Upon return, - /// it will be set to if at least one iteration happened. - public static void RunMainLoopIteration (ref RunState state, bool wait, ref bool firstIteration) - { - if (MainLoop.EventsPending (wait)) { - // Notify Toplevel it's ready - if (firstIteration) { - state.Toplevel.OnReady (); - } - - MainLoop.MainIteration (); - Iteration?.Invoke (); + public static View MouseGrabView => _mouseGrabView; - EnsureModalOrVisibleAlwaysOnTop (state.Toplevel); - if ((state.Toplevel != Current && Current?.Modal == true) - || (state.Toplevel != Current && Current?.Modal == false)) { - MdiTop?.OnDeactivate (state.Toplevel); - state.Toplevel = Current; - MdiTop?.OnActivate (state.Toplevel); - Top.SetSubViewNeedsDisplay (); - Refresh (); - } - if (Driver.EnsureCursorVisibility ()) { - state.Toplevel.SetNeedsDisplay (); - } - } else if (!wait) { - return; - } - firstIteration = false; + /// + /// Invoked when a view wants to grab the mouse; can be canceled. + /// + public static event EventHandler GrabbingMouse; - if (state.Toplevel != Top - && (!Top._needsDisplay.IsEmpty || Top._childNeedsDisplay || Top.LayoutNeeded)) { - state.Toplevel.SetNeedsDisplay (state.Toplevel.Bounds); - Top.Redraw (Top.Bounds); - foreach (var top in _toplevels.Reverse ()) { - if (top != Top && top != state.Toplevel) { - top.SetNeedsDisplay (); - top.Redraw (top.Bounds); - } - } - } - if (_toplevels.Count == 1 && state.Toplevel == Top - && (Driver.Cols != state.Toplevel.Frame.Width || Driver.Rows != state.Toplevel.Frame.Height) - && (!state.Toplevel._needsDisplay.IsEmpty || state.Toplevel._childNeedsDisplay || state.Toplevel.LayoutNeeded)) { + /// + /// Invoked when a view wants ungrab the mouse; can be canceled. + /// + public static event EventHandler UnGrabbingMouse; - Driver.SetAttribute (Colors.TopLevel.Normal); - state.Toplevel.Clear (new Rect (0, 0, Driver.Cols, Driver.Rows)); + /// + /// Invoked after a view has grabbed the mouse. + /// + public static event EventHandler GrabbedMouse; - } + /// + /// Invoked after a view has ungrabbed the mouse. + /// + public static event EventHandler UnGrabbedMouse; - if (!state.Toplevel._needsDisplay.IsEmpty || state.Toplevel._childNeedsDisplay || state.Toplevel.LayoutNeeded - || MdiChildNeedsDisplay ()) { - state.Toplevel.Redraw (state.Toplevel.Bounds); - //if (state.Toplevel.SuperView != null) { - // state.Toplevel.SuperView?.OnRenderLineCanvas (); - //} else { - // state.Toplevel.OnRenderLineCanvas (); - //} - state.Toplevel.PositionCursor (); - Driver.Refresh (); - } else { - Driver.UpdateCursor (); - } - if (state.Toplevel != Top && !state.Toplevel.Modal - && (!Top._needsDisplay.IsEmpty || Top._childNeedsDisplay || Top.LayoutNeeded)) { - Top.Redraw (Top.Bounds); + /// + /// Grabs the mouse, forcing all mouse events to be routed to the specified view until is called. + /// + /// View that will receive all mouse events until is invoked. + public static void GrabMouse (View view) + { + if (view == null) + return; + if (!OnGrabbingMouse (view)) { + OnGrabbedMouse (view); + _mouseGrabView = view; + Driver.UncookMouse (); } } - static void EnsureModalOrVisibleAlwaysOnTop (Toplevel toplevel) + /// + /// Releases the mouse grab, so mouse events will be routed to the view on which the mouse is. + /// + public static void UngrabMouse () { - if (!toplevel.Running || (toplevel == Current && toplevel.Visible) || MdiTop == null || _toplevels.Peek ().Modal) { + if (_mouseGrabView == null) return; - } - - foreach (var top in _toplevels.Reverse ()) { - if (top.Modal && top != Current) { - MoveCurrent (top); - return; - } - } - if (!toplevel.Visible && toplevel == Current) { - MoveNext (); + if (!OnUnGrabbingMouse (_mouseGrabView)) { + OnUnGrabbedMouse (_mouseGrabView); + _mouseGrabView = null; + Driver.CookMouse (); } } - static bool MdiChildNeedsDisplay () + static bool OnGrabbingMouse (View view) { - if (MdiTop == null) { + if (view == null) return false; - } + var evArgs = new GrabMouseEventArgs (view); + GrabbingMouse?.Invoke (view, evArgs); + return evArgs.Cancel; + } - foreach (var top in _toplevels) { - if (top != Current && top.Visible && (!top._needsDisplay.IsEmpty || top._childNeedsDisplay || top.LayoutNeeded)) { - MdiTop.SetSubViewNeedsDisplay (); - return true; - } - } - return false; + static bool OnUnGrabbingMouse (View view) + { + if (view == null) + return false; + var evArgs = new GrabMouseEventArgs (view); + UnGrabbingMouse?.Invoke (view, evArgs); + return evArgs.Cancel; } - /// - /// Runs the application by calling with the value of . - /// - /// - /// See for more details. - /// - public static void Run (Func errorHandler = null) + static void OnGrabbedMouse (View view) { - Run (Top, errorHandler); + if (view == null) + return; + GrabbedMouse?.Invoke (view, new ViewEventArgs (view)); } - /// - /// Runs the application by calling - /// with a new instance of the specified -derived class. - /// - /// Calling first is not needed as this function will initialze the application. - /// - /// - /// must be called when the application is closing (typically after Run> has - /// returned) to ensure resources are cleaned up and terminal settings restored. - /// - /// - /// - /// See for more details. - /// - /// - /// The to use. If not specified the default driver for the - /// platform will be used (, , or ). - /// This parameteter must be if has already been called. - /// - /// Specifies the to use. - public static void Run (Func errorHandler = null, ConsoleDriver driver = null, IMainLoopDriver mainLoopDriver = null) where T : Toplevel, new() + static void OnUnGrabbedMouse (View view) { - if (_initialized) { - if (Driver != null) { - // Init() has been called and we have a driver, so just run the app. - var top = new T (); - var type = top.GetType ().BaseType; - while (type != typeof (Toplevel) && type != typeof (object)) { - type = type.BaseType; - } - if (type != typeof (Toplevel)) { - throw new ArgumentException ($"{top.GetType ().Name} must be derived from TopLevel"); - } - Run (top, errorHandler); - } else { - // This codepath should be impossible because Init(null, null) will select the platform default driver - throw new InvalidOperationException ("Init() completed without a driver being set (this should be impossible); Run() cannot be called."); - } - } else { - // Init() has NOT been called. - InternalInit (() => new T (), driver, mainLoopDriver, calledViaRunT: true); - Run (Top, errorHandler); - } + if (view == null) + return; + UnGrabbedMouse?.Invoke (view, new ViewEventArgs (view)); } /// - /// Runs the main loop on the given container. + /// Merely a debugging aid to see the raw mouse events /// - /// - /// - /// This method is used to start processing events - /// for the main application, but it is also used to - /// run other modal s such as boxes. - /// - /// - /// To make a stop execution, call . - /// - /// - /// Calling is equivalent to calling , followed by , - /// and then calling . - /// - /// - /// Alternatively, to have a program control the main loop and - /// process events manually, call to set things up manually and then - /// repeatedly call with the wait parameter set to false. By doing this - /// the method will only process any pending events, timers, idle handlers and - /// then return control immediately. - /// - /// - /// RELEASE builds only: When is any exeptions will be rethrown. - /// Otheriwse, if will be called. If - /// returns the will resume; otherwise - /// this method will exit. - /// - /// - /// The to run modally. - /// RELEASE builds only: Handler for any unhandled exceptions (resumes when returns true, rethrows when null). - public static void Run (Toplevel view, Func errorHandler = null) + public static Action RootMouseEvent; + + static View _lastMouseOwnerView; + + static void ProcessMouseEvent (MouseEvent me) { - var resume = true; - while (resume) { -#if !DEBUG - try { -#endif - resume = false; - var runToken = Begin (view); - // If ExitRunLoopAfterFirstIteration is true then the user must dispose of the runToken - // by using NotifyStopRunState event. - RunLoop (runToken); - if (!ExitRunLoopAfterFirstIteration) { - End (runToken); - } -#if !DEBUG - } - catch (Exception error) - { - if (errorHandler == null) - { - throw; - } - resume = errorHandler(error); - } -#endif + bool OutsideFrame (Point p, Rect r) + { + return p.X < 0 || p.X > r.Width - 1 || p.Y < 0 || p.Y > r.Height - 1; } - } - /// - /// Stops running the most recent or the if provided. - /// - /// The toplevel to request stop. - /// - /// - /// This will cause to return. - /// - /// - /// Calling is equivalent to setting the property on the currently running to false. - /// - /// - public static void RequestStop (Toplevel top = null) - { - if (MdiTop == null || top == null || (MdiTop == null && top != null)) { - top = Current; + if (IsMouseDisabled) { + return; } - if (MdiTop != null && top.IsMdiContainer && top?.Running == true - && (Current?.Modal == false || (Current?.Modal == true && Current?.Running == false))) { + var view = View.FindDeepestView (Current, me.X, me.Y, out int rx, out int ry); - MdiTop.RequestStop (); - } else if (MdiTop != null && top != Current && Current?.Running == true && Current?.Modal == true - && top.Modal && top.Running) { + if (view != null && view.WantContinuousButtonPressed) { + WantContinuousButtonPressedView = view; + } else { + WantContinuousButtonPressedView = null; + } + if (view != null) { + me.View = view; + } + RootMouseEvent?.Invoke (me); - var ev = new ToplevelClosingEventArgs (Current); - Current.OnClosing (ev); - if (ev.Cancel) { - return; + if (me.Handled) { + return; + } + + if (_mouseGrabView != null) { + var newxy = _mouseGrabView.ScreenToView (me.X, me.Y); + var nme = new MouseEvent () { + X = newxy.X, + Y = newxy.Y, + Flags = me.Flags, + OfX = me.X - newxy.X, + OfY = me.Y - newxy.Y, + View = view + }; + if (OutsideFrame (new Point (nme.X, nme.Y), _mouseGrabView.Frame)) { + _lastMouseOwnerView?.OnMouseLeave (me); } - ev = new ToplevelClosingEventArgs (top); - top.OnClosing (ev); - if (ev.Cancel) { + //System.Diagnostics.Debug.WriteLine ($"{nme.Flags};{nme.X};{nme.Y};{mouseGrabView}"); + if (_mouseGrabView?.OnMouseEvent (nme) == true) { return; } - Current.Running = false; - OnNotifyStopRunState (Current); - top.Running = false; - OnNotifyStopRunState (top); - } else if ((MdiTop != null && top != MdiTop && top != Current && Current?.Modal == false - && Current?.Running == true && !top.Running) - || (MdiTop != null && top != MdiTop && top != Current && Current?.Modal == false - && Current?.Running == false && !top.Running && _toplevels.ToArray () [1].Running)) { + } - MoveCurrent (top); - } else if (MdiTop != null && Current != top && Current?.Running == true && !top.Running - && Current?.Modal == true && top.Modal) { - // The Current and the top are both modal so needed to set the Current.Running to false too. - Current.Running = false; - OnNotifyStopRunState (Current); - } else if (MdiTop != null && Current == top && MdiTop?.Running == true && Current?.Running == true && top.Running - && Current?.Modal == true && top.Modal) { - // The MdiTop was requested to stop inside a modal toplevel which is the Current and top, - // both are the same, so needed to set the Current.Running to false too. - Current.Running = false; - OnNotifyStopRunState (Current); - } else { - Toplevel currentTop; - if (top == Current || (Current?.Modal == true && !top.Modal)) { - currentTop = Current; - } else { - currentTop = top; + if ((view == null || view == OverlappedTop) && !Current.Modal && OverlappedTop != null + && me.Flags != MouseFlags.ReportMousePosition && me.Flags != 0) { + + var top = FindDeepestTop (Top, me.X, me.Y, out _, out _); + view = View.FindDeepestView (top, me.X, me.Y, out rx, out ry); + + if (view != null && view != OverlappedTop && top != Current) { + MoveCurrent ((Toplevel)top); } - if (!currentTop.Running) { - return; + } + + if (view != null) { + var nme = new MouseEvent () { + X = rx, + Y = ry, + Flags = me.Flags, + OfX = 0, + OfY = 0, + View = view + }; + + if (_lastMouseOwnerView == null) { + _lastMouseOwnerView = view; + view.OnMouseEnter (nme); + } else if (_lastMouseOwnerView != view) { + _lastMouseOwnerView.OnMouseLeave (nme); + view.OnMouseEnter (nme); + _lastMouseOwnerView = view; } - var ev = new ToplevelClosingEventArgs (currentTop); - currentTop.OnClosing (ev); - if (ev.Cancel) { + + if (!view.WantMousePositionReports && me.Flags == MouseFlags.ReportMousePosition) return; - } - currentTop.Running = false; - OnNotifyStopRunState (currentTop); - } - } - static void OnNotifyStopRunState (Toplevel top) - { - if (ExitRunLoopAfterFirstIteration) { - NotifyStopRunState?.Invoke (top, new ToplevelEventArgs (top)); - } - } + if (view.WantContinuousButtonPressed) + WantContinuousButtonPressedView = view; + else + WantContinuousButtonPressedView = null; - /// - /// Invoked when the terminal was resized. The new size of the terminal is provided. - /// - public static Action Resized; + // Should we bubbled up the event, if it is not handled? + view.OnMouseEvent (nme); - static void TerminalResized () - { - var full = new Rect (0, 0, Driver.Cols, Driver.Rows); - Resized?.Invoke (new ResizedEventArgs () { Cols = full.Width, Rows = full.Height }); - Driver.Clip = full; - foreach (var t in _toplevels) { - t.SetRelativeLayout (full); - t.LayoutSubviews (); - t.PositionToplevels (); - t.OnResized (new SizeChangedEventArgs (full.Size)); + BringOverlappedTopToFront (); } - Refresh (); } + #endregion Mouse handling - static bool SetCurrentAsTop () - { - if (MdiTop == null && Current != Top && Current?.SuperView == null && Current?.Modal == false) { - if (Current.Frame != new Rect (0, 0, Driver.Cols, Driver.Rows)) { - Current.Frame = new Rect (0, 0, Driver.Cols, Driver.Rows); + #region Keyboard handling + + + static Key _alternateForwardKey = Key.PageDown | Key.CtrlMask; + + /// + /// Alternative key to navigate forwards through views. Ctrl+Tab is the primary key. + /// + [SerializableConfigurationProperty (Scope = typeof (SettingsScope)), JsonConverter (typeof (KeyJsonConverter))] + public static Key AlternateForwardKey { + get => _alternateForwardKey; + set { + if (_alternateForwardKey != value) { + var oldKey = _alternateForwardKey; + _alternateForwardKey = value; + OnAlternateForwardKeyChanged (new KeyChangedEventArgs (oldKey, value)); } - Top = Current; - return true; } - return false; } - /// - /// Move to the next Mdi child from the . - /// - public static void MoveNext () + static void OnAlternateForwardKeyChanged (KeyChangedEventArgs e) { - if (MdiTop != null && !Current.Modal) { - lock (_toplevels) { - _toplevels.MoveNext (); - var isMdi = false; - while (_toplevels.Peek () == MdiTop || !_toplevels.Peek ().Visible) { - if (!isMdi && _toplevels.Peek () == MdiTop) { - isMdi = true; - } else if (isMdi && _toplevels.Peek () == MdiTop) { - MoveCurrent (Top); - break; - } - _toplevels.MoveNext (); - } - Current = _toplevels.Peek (); - } + foreach (var top in _toplevels.ToArray ()) { + top.OnAlternateForwardKeyChanged (e); } } + static Key _alternateBackwardKey = Key.PageUp | Key.CtrlMask; + /// - /// Move to the previous Mdi child from the . + /// Alternative key to navigate backwards through views. Shift+Ctrl+Tab is the primary key. /// - public static void MovePrevious () - { - if (MdiTop != null && !Current.Modal) { - lock (_toplevels) { - _toplevels.MovePrevious (); - var isMdi = false; - while (_toplevels.Peek () == MdiTop || !_toplevels.Peek ().Visible) { - if (!isMdi && _toplevels.Peek () == MdiTop) { - isMdi = true; - } else if (isMdi && _toplevels.Peek () == MdiTop) { - MoveCurrent (Top); - break; - } - _toplevels.MovePrevious (); - } - Current = _toplevels.Peek (); + [SerializableConfigurationProperty (Scope = typeof (SettingsScope)), JsonConverter (typeof (KeyJsonConverter))] + public static Key AlternateBackwardKey { + get => _alternateBackwardKey; + set { + if (_alternateBackwardKey != value) { + var oldKey = _alternateBackwardKey; + _alternateBackwardKey = value; + OnAlternateBackwardKeyChanged (new KeyChangedEventArgs (oldKey, value)); } } } - internal static bool ShowChild (Toplevel top) + static void OnAlternateBackwardKeyChanged (KeyChangedEventArgs oldKey) { - if (top.Visible && MdiTop != null && Current?.Modal == false) { - lock (_toplevels) { - _toplevels.MoveTo (top, 0, new ToplevelEqualityComparer ()); - Current = top; - } - return true; + foreach (var top in _toplevels.ToArray ()) { + top.OnAlternateBackwardKeyChanged (oldKey); } - return false; } + static Key _quitKey = Key.Q | Key.CtrlMask; + /// - /// Wakes up the mainloop that might be waiting on input, must be thread safe. + /// Gets or sets the key to quit the application. /// - public static void DoEvents () + [SerializableConfigurationProperty (Scope = typeof (SettingsScope)), JsonConverter (typeof (KeyJsonConverter))] + public static Key QuitKey { + get => _quitKey; + set { + if (_quitKey != value) { + var oldKey = _quitKey; + _quitKey = value; + OnQuitKeyChanged (new KeyChangedEventArgs (oldKey, value)); + } + } + } + static void OnQuitKeyChanged (KeyChangedEventArgs e) { - MainLoop.Driver.Wakeup (); + // Duplicate the list so if it changes during enumeration we're safe + foreach (var top in _toplevels.ToArray ()) { + top.OnQuitKeyChanged (e); + } } - /// - /// Ensures that the superview of the most focused view is on front. - /// - public static void EnsuresTopOnFront () + static void ProcessKeyEvent (KeyEvent ke) { - if (MdiTop != null) { + if (RootKeyEvent?.Invoke (ke) ?? false) { return; } - var top = FindTopFromView (Top?.MostFocused); - if (top != null && Top.Subviews.Count > 1 && Top.Subviews [Top.Subviews.Count - 1] != top) { - Top.BringSubviewToFront (top); + + var chain = _toplevels.ToList (); + foreach (var topLevel in chain) { + if (topLevel.ProcessHotKey (ke)) + return; + if (topLevel.Modal) + break; + } + + foreach (var topLevel in chain) { + if (topLevel.ProcessKey (ke)) + return; + if (topLevel.Modal) + break; + } + + foreach (var topLevel in chain) { + // Process the key normally + if (topLevel.ProcessColdKey (ke)) + return; + if (topLevel.Modal) + break; } } - internal static List GetSupportedCultures () + static void ProcessKeyDownEvent (KeyEvent ke) { - CultureInfo [] culture = CultureInfo.GetCultures (CultureTypes.AllCultures); - - // Get the assembly - Assembly assembly = Assembly.GetExecutingAssembly (); + var chain = _toplevels.ToList (); + foreach (var topLevel in chain) { + if (topLevel.OnKeyDown (ke)) + return; + if (topLevel.Modal) + break; + } + } - //Find the location of the assembly - string assemblyLocation = AppDomain.CurrentDomain.BaseDirectory; + static void ProcessKeyUpEvent (KeyEvent ke) + { + var chain = _toplevels.ToList (); + foreach (var topLevel in chain) { + if (topLevel.OnKeyUp (ke)) + return; + if (topLevel.Modal) + break; + } + } - // Find the resource file name of the assembly - string resourceFilename = $"{Path.GetFileNameWithoutExtension (assembly.Location)}.resources.dll"; + /// + /// + /// Called for new KeyPress events before any processing is performed or + /// views evaluate. Use for global key handling and/or debugging. + /// + /// Return true to suppress the KeyPress event + /// + public static Func RootKeyEvent; - // Return all culture for which satellite folder found with culture code. - return culture.Where (cultureInfo => - assemblyLocation != null && - Directory.Exists (Path.Combine (assemblyLocation, cultureInfo.Name)) && - File.Exists (Path.Combine (assemblyLocation, cultureInfo.Name, resourceFilename)) - ).ToList (); - } + #endregion Keyboard handling } } diff --git a/Terminal.Gui/Configuration/DictionaryJsonConverter.cs b/Terminal.Gui/Configuration/DictionaryJsonConverter.cs index e8218e5cfa..5027e22cb6 100644 --- a/Terminal.Gui/Configuration/DictionaryJsonConverter.cs +++ b/Terminal.Gui/Configuration/DictionaryJsonConverter.cs @@ -24,7 +24,6 @@ public override Dictionary Read (ref Utf8JsonReader reader, Type type return dictionary; } - public override void Write (Utf8JsonWriter writer, Dictionary value, JsonSerializerOptions options) { writer.WriteStartArray (); diff --git a/Terminal.Gui/Configuration/Scope.cs b/Terminal.Gui/Configuration/Scope.cs index 078ff17e6e..c69aae46e1 100644 --- a/Terminal.Gui/Configuration/Scope.cs +++ b/Terminal.Gui/Configuration/Scope.cs @@ -7,7 +7,6 @@ using System.Text.Json.Serialization; using static Terminal.Gui.ConfigurationManager; - #nullable enable namespace Terminal.Gui { diff --git a/Terminal.Gui/Configuration/SettingsScope.cs b/Terminal.Gui/Configuration/SettingsScope.cs index 49d6f18394..cc91eb0e2a 100644 --- a/Terminal.Gui/Configuration/SettingsScope.cs +++ b/Terminal.Gui/Configuration/SettingsScope.cs @@ -6,7 +6,6 @@ using System.Text.Json; using System.Text.Json.Serialization; - #nullable enable namespace Terminal.Gui { diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs index a8eac2754a..53f04004ca 100644 --- a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs @@ -534,7 +534,7 @@ public static Dictionary Create () } /// - /// The application toplevel color scheme, for the default toplevel views. + /// The application Toplevel color scheme, for the default Toplevel views. /// /// /// @@ -544,7 +544,7 @@ public static Dictionary Create () public static ColorScheme TopLevel { get => GetColorScheme (); set => SetColorScheme (value); } /// - /// The base color scheme, for the default toplevel views. + /// The base color scheme, for the default Toplevel views. /// /// /// diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs index df53595428..d3ab5d1d7c 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs @@ -191,7 +191,7 @@ bool CheckTimers (bool wait, out int pollTimeout) return ic > 0; } - void IMainLoopDriver.MainIteration () + void IMainLoopDriver.Iteration () { if (winChanged) { winChanged = false; diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/binding.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/binding.cs index 1fb3580fba..8a0ed3da78 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/binding.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/binding.cs @@ -73,7 +73,6 @@ public struct MouseEvent { static UnmanagedLibrary curses_library; static NativeMethods methods; - [DllImport ("libc")] public extern static int setlocale (int cate, [MarshalAs (UnmanagedType.LPStr)] string locale); diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/handles.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/handles.cs index de923e9685..8a46149d50 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/handles.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/handles.cs @@ -59,7 +59,6 @@ static public Window Current { } } - public int wtimeout (int delay) { return Curses.wtimeout (Handle, delay); diff --git a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeMainLoop.cs b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeMainLoop.cs index 592b974fcc..06a08a0698 100644 --- a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeMainLoop.cs @@ -87,7 +87,7 @@ bool CheckTimers (bool wait, out int waitTimeout) return ic > 0; } - void IMainLoopDriver.MainIteration () + void IMainLoopDriver.Iteration () { if (keyResult.HasValue) { KeyPressed?.Invoke (keyResult.Value); diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver.cs index 08e8276834..1617cd1889 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver.cs @@ -1115,7 +1115,6 @@ public override void Suspend () { } - public override void SetAttribute (Attribute c) { base.SetAttribute (c); @@ -1172,7 +1171,6 @@ Key MapKey (ConsoleKeyInfo keyInfo) case ConsoleKey.Insert: return MapKeyModifiers (keyInfo, Key.InsertChar); - case ConsoleKey.Oem1: case ConsoleKey.Oem2: case ConsoleKey.Oem3: @@ -1441,7 +1439,6 @@ public override bool GetCursorVisibility (out CursorVisibility visibility) return visibility == CursorVisibility.Default; } - /// public override bool SetCursorVisibility (CursorVisibility visibility) { @@ -1634,7 +1631,7 @@ bool CheckTimers (bool wait, out int waitTimeout) return ic > 0; } - void IMainLoopDriver.MainIteration () + void IMainLoopDriver.Iteration () { while (inputResult.Count > 0) { ProcessInput?.Invoke (inputResult.Dequeue ().Value); diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs index 094b79be65..71b837abff 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs @@ -577,7 +577,6 @@ public struct ConsoleCursorInfo { [DllImport ("kernel32.dll")] static extern bool GetConsoleMode (IntPtr hConsoleHandle, out uint lpMode); - [DllImport ("kernel32.dll")] static extern bool SetConsoleMode (IntPtr hConsoleHandle, uint dwMode); @@ -1980,7 +1979,7 @@ bool CheckTimers (bool wait, out int waitTimeout) return ic > 0; } - void IMainLoopDriver.MainIteration () + void IMainLoopDriver.Iteration () { while (resultQueue.Count > 0) { var inputRecords = resultQueue.Dequeue (); diff --git a/Terminal.Gui/Drawing/LineCanvas.cs b/Terminal.Gui/Drawing/LineCanvas.cs index eeda2b8e57..039bb170a7 100644 --- a/Terminal.Gui/Drawing/LineCanvas.cs +++ b/Terminal.Gui/Drawing/LineCanvas.cs @@ -52,7 +52,6 @@ public class LineCanvas { {IntersectionRuneType.RightTee,new RightTeeIntersectionRuneResolver()}, {IntersectionRuneType.BottomTee,new BottomTeeIntersectionRuneResolver()}, - {IntersectionRuneType.Crosshair,new CrosshairIntersectionRuneResolver()}, // TODO: Add other resolvers }; @@ -132,6 +131,8 @@ public Rect Bounds { } } + // TODO: Unless there's an obvious use case for this API we should delete it in favor of the + // simpler version that doensn't take an area. /// /// Evaluates the lines that have been added to the canvas and returns a map containing /// the glyphs and their locations. The glyphs are the characters that should be rendered @@ -168,8 +169,7 @@ public Dictionary GetMap (Rect inArea) /// the glyphs and their locations. The glyphs are the characters that should be rendered /// so that all lines connect up with the appropriate intersection symbols. /// - /// A rectangle to constrain the search by. - /// A map of the points within the canvas that intersect with . + /// A map of all the points within the canvas. public Dictionary GetCellMap () { var map = new Dictionary (); @@ -243,7 +243,6 @@ public override string ToString () return sb.ToString (); } - private abstract class IntersectionRuneResolver { readonly Rune round; readonly Rune doubleH; @@ -267,7 +266,6 @@ public IntersectionRuneResolver (Rune round, Rune doubleH, Rune doubleV, Rune do bool doubleHorizontal = intersects.Any (l => l.Line.Orientation == Orientation.Horizontal && l.Line.Style == LineStyle.Double); bool doubleVertical = intersects.Any (l => l.Line.Orientation == Orientation.Vertical && l.Line.Style == LineStyle.Double); - if (doubleHorizontal) { return doubleVertical ? doubleBoth : doubleH; } @@ -392,19 +390,24 @@ public CrosshairIntersectionRuneResolver () : } + /// + /// Represents a single row/column within the . Includes the glyph and the foreground/background colors. + /// public class Cell { - public Cell () - { - - } - + /// + /// The glyph to draw. + /// public Rune? Rune { get; set; } + + /// + /// The foreground color to draw the glyph with. + /// public Attribute? Attribute { get; set; } } - private Cell? GetCellForIntersects (ConsoleDriver driver, IntersectionDefinition [] intersects) + private Cell GetCellForIntersects (ConsoleDriver driver, IntersectionDefinition [] intersects) { if (!intersects.Any ()) { return null; @@ -416,7 +419,6 @@ public Cell () return cell; } - private IntersectionRuneType GetRuneTypeForIntersects (IntersectionDefinition [] intersects) { var set = new HashSet (intersects.Select (i => i.Type)); @@ -445,7 +447,6 @@ private IntersectionRuneType GetRuneTypeForIntersects (IntersectionDefinition [] return IntersectionRuneType.Crosshair; } - if (Has (set, IntersectionType.StartLeft, IntersectionType.StartRight, @@ -455,7 +456,6 @@ private IntersectionRuneType GetRuneTypeForIntersects (IntersectionDefinition [] } #endregion - #region Corner Conditions if (Exactly (set, IntersectionType.StartRight, @@ -507,7 +507,6 @@ private IntersectionRuneType GetRuneTypeForIntersects (IntersectionDefinition [] return IntersectionRuneType.BottomTee; } - if (Has (set, IntersectionType.PassOverVertical, IntersectionType.StartRight)) { @@ -520,7 +519,6 @@ private IntersectionRuneType GetRuneTypeForIntersects (IntersectionDefinition [] return IntersectionRuneType.LeftTee; } - if (Has (set, IntersectionType.PassOverVertical, IntersectionType.StartLeft)) { diff --git a/Terminal.Gui/Drawing/Ruler.cs b/Terminal.Gui/Drawing/Ruler.cs index 78bdcb3d20..cb59040c11 100644 --- a/Terminal.Gui/Drawing/Ruler.cs +++ b/Terminal.Gui/Drawing/Ruler.cs @@ -33,7 +33,6 @@ public class Ruler { string _hTemplate { get; } = "|123456789"; string _vTemplate { get; } = "-123456789"; - /// /// Draws the . /// diff --git a/Terminal.Gui/FileServices/AllowedType.cs b/Terminal.Gui/FileServices/AllowedType.cs index 7072c95e9a..bcf44490c6 100644 --- a/Terminal.Gui/FileServices/AllowedType.cs +++ b/Terminal.Gui/FileServices/AllowedType.cs @@ -22,7 +22,6 @@ public interface IAllowedType bool IsAllowed (string path); } - /// /// that allows selection of any types (*.*). /// @@ -74,7 +73,6 @@ public AllowedType (string description, params string [] extensions) /// public string [] Extensions { get; set; } - /// /// Returns plus all separated by semicolons. /// @@ -100,7 +98,6 @@ public bool IsAllowed(string path) return false; } - return this.Extensions.Any (e => e.Equals (extension)); } } diff --git a/Terminal.Gui/FileServices/FileDialogHistory.cs b/Terminal.Gui/FileServices/FileDialogHistory.cs index 02c181313e..d490a73a78 100644 --- a/Terminal.Gui/FileServices/FileDialogHistory.cs +++ b/Terminal.Gui/FileServices/FileDialogHistory.cs @@ -78,7 +78,6 @@ internal bool CanUp () return this.dlg.State?.Directory.Parent != null; } - internal void Push (FileDialogState state, bool clearForward) { if (state == null) { diff --git a/Terminal.Gui/FileServices/FileDialogState.cs b/Terminal.Gui/FileServices/FileDialogState.cs index 6db932e656..ab3ed1033f 100644 --- a/Terminal.Gui/FileServices/FileDialogState.cs +++ b/Terminal.Gui/FileServices/FileDialogState.cs @@ -55,7 +55,6 @@ protected virtual IEnumerable GetChildren (IDirectoryInfo d children = children.Where (MatchesApiFilter).ToList (); } - // allow navigating up as '..' if (dir.Parent != null) { children.Add (new FileSystemInfoStats (dir.Parent) { IsParent = true }); diff --git a/Terminal.Gui/FileServices/FileDialogStyle.cs b/Terminal.Gui/FileServices/FileDialogStyle.cs index 7c4f61d5d9..981733de67 100644 --- a/Terminal.Gui/FileServices/FileDialogStyle.cs +++ b/Terminal.Gui/FileServices/FileDialogStyle.cs @@ -23,7 +23,6 @@ public class FileDialogStyle { [SerializableConfigurationProperty(Scope = typeof (SettingsScope))] public static bool DefaultUseColors { get; set; } - /// /// Gets or sets the default value to use for . /// This can be populated from .tui config files via @@ -158,14 +157,12 @@ public class FileDialogStyle { /// public bool UseUnicodeCharacters { get; set; } = DefaultUseUnicodeCharacters; - /// /// User defined delegate for picking which character(s)/unicode /// symbol(s) to use as an 'icon' for files/folders. /// public Func IconGetter { get; set; } - /// /// Gets or sets the format to use for date/times in the Modified column. /// Defaults to @@ -173,7 +170,6 @@ public class FileDialogStyle { /// public string DateFormat { get; set; } - /// /// Creates a new instance of the class. /// @@ -233,7 +229,6 @@ private static IEnumerable DefaultTreeRootGetter () // Cannot get the system disks thats fine } - try { foreach (var special in Enum.GetValues (typeof (Environment.SpecialFolder)).Cast ()) { try { diff --git a/Terminal.Gui/FileServices/FileDialogTreeBuilder.cs b/Terminal.Gui/FileServices/FileDialogTreeBuilder.cs index 408a0d762a..462a9284d8 100644 --- a/Terminal.Gui/FileServices/FileDialogTreeBuilder.cs +++ b/Terminal.Gui/FileServices/FileDialogTreeBuilder.cs @@ -3,7 +3,6 @@ using System.IO; using System.Linq; - namespace Terminal.Gui { class FileDialogTreeBuilder : ITreeBuilder { diff --git a/Terminal.Gui/FileServices/FileSystemInfoStats.cs b/Terminal.Gui/FileServices/FileSystemInfoStats.cs index 463d0a6eb1..323b94d600 100644 --- a/Terminal.Gui/FileServices/FileSystemInfoStats.cs +++ b/Terminal.Gui/FileServices/FileSystemInfoStats.cs @@ -12,7 +12,6 @@ namespace Terminal.Gui { /// internal class FileSystemInfoStats { - /* ---- Colors used by the ls command line tool ---- * * Blue: Directory @@ -127,7 +126,6 @@ private static string GetHumanReadableFileSize (long value) int mag = (int)Math.Log (value, ByteConversion); double adjustedSize = value / Math.Pow (1000, mag); - return string.Format ("{0:n2} {1}", adjustedSize, SizeSuffixes [mag]); } } diff --git a/Terminal.Gui/FileServices/IFileOperations.cs b/Terminal.Gui/FileServices/IFileOperations.cs index e586b62d46..d308e11a90 100644 --- a/Terminal.Gui/FileServices/IFileOperations.cs +++ b/Terminal.Gui/FileServices/IFileOperations.cs @@ -19,7 +19,6 @@ public interface IFileOperations { /// error handling (e.g. showing a bool Delete (IEnumerable toDelete); - /// /// Specifies how to handle file/directory rename attempts /// in . @@ -31,7 +30,6 @@ public interface IFileOperations { /// error handling (e.g. showing a IFileSystemInfo Rename (IFileSystem fileSystem, IFileSystemInfo toRename); - /// /// Specifies how to handle 'new directory' operation /// in . diff --git a/Terminal.Gui/Input/Command.cs b/Terminal.Gui/Input/Command.cs index 9d106f6643..29de444eea 100644 --- a/Terminal.Gui/Input/Command.cs +++ b/Terminal.Gui/Input/Command.cs @@ -127,7 +127,6 @@ public enum Command { /// ToggleOverwrite, - /// /// Enables overwrite mode such that newly typed text overwrites the text that is /// already there (typically associated with the Insert key). @@ -356,12 +355,12 @@ public enum Command { PreviousView, /// - /// Moves focus to the next view or toplevel (case of MDI). + /// Moves focus to the next view or Toplevel (case of Overlapped). /// NextViewOrTop, /// - /// Moves focus to the next previous or toplevel (case of MDI). + /// Moves focus to the next previous or Toplevel (case of Overlapped). /// PreviousViewOrTop, diff --git a/Terminal.Gui/MainLoop.cs b/Terminal.Gui/MainLoop.cs index b48fe2a77b..777b70e9b8 100644 --- a/Terminal.Gui/MainLoop.cs +++ b/Terminal.Gui/MainLoop.cs @@ -10,17 +10,17 @@ namespace Terminal.Gui { /// - /// Public interface to create your own platform specific main loop driver. + /// Public interface to create a platform specific driver. /// public interface IMainLoopDriver { /// - /// Initializes the main loop driver, gets the calling main loop for the initialization. + /// Initializes the , gets the calling main loop for the initialization. /// /// Main loop. void Setup (MainLoop mainLoop); /// - /// Wakes up the mainloop that might be waiting on input, must be thread safe. + /// Wakes up the that might be waiting on input, must be thread safe. /// void Wakeup (); @@ -34,7 +34,7 @@ public interface IMainLoopDriver { /// /// The iteration function. /// - void MainIteration (); + void Iteration (); } /// @@ -61,12 +61,12 @@ public sealed class Timeout { } internal SortedList timeouts = new SortedList (); - object timeoutsLockToken = new object (); + object _timeoutsLockToken = new object (); /// /// The idle handlers and lock that must be held while manipulating them /// - object idleHandlersLock = new object (); + object _idleHandlersLock = new object (); internal List> idleHandlers = new List> (); /// @@ -81,14 +81,14 @@ public sealed class Timeout { /// public ReadOnlyCollection> IdleHandlers { get { - lock (idleHandlersLock) { + lock (_idleHandlersLock) { return new List> (idleHandlers).AsReadOnly (); } } } /// - /// The current IMainLoopDriver in use. + /// The current in use. /// /// The driver. public IMainLoopDriver Driver { get; } @@ -123,7 +123,8 @@ public void Invoke (Action action) } /// - /// Adds specified idle handler function to mainloop processing. The handler function will be called once per iteration of the main loop after other events have been handled. + /// Adds specified idle handler function to processing. + /// The handler function will be called once per iteration of the main loop after other events have been handled. /// /// /// @@ -136,7 +137,7 @@ public void Invoke (Action action) /// Token that can be used to remove the idle handler with . public Func AddIdle (Func idleHandler) { - lock (idleHandlersLock) { + lock (_idleHandlersLock) { idleHandlers.Add (idleHandler); } @@ -152,13 +153,13 @@ public Func AddIdle (Func idleHandler) /// This method also returns false if the idle handler is not found. public bool RemoveIdle (Func token) { - lock (idleHandlersLock) + lock (_idleHandlersLock) return idleHandlers.Remove (token); } void AddTimeout (TimeSpan time, Timeout timeout) { - lock (timeoutsLockToken) { + lock (_timeoutsLockToken) { var k = (DateTime.UtcNow + time).Ticks; timeouts.Add (NudgeToUniqueKey (k), timeout); TimeoutAdded?.Invoke (this, new TimeoutEventArgs(timeout, k)); @@ -166,7 +167,7 @@ void AddTimeout (TimeSpan time, Timeout timeout) } /// - /// Adds a timeout to the mainloop. + /// Adds a timeout to the . /// /// /// When time specified passes, the callback will be invoked. @@ -198,7 +199,7 @@ public object AddTimeout (TimeSpan time, Func callback) /// This method also returns false if the timeout is not found. public bool RemoveTimeout (object token) { - lock (timeoutsLockToken) { + lock (_timeoutsLockToken) { var idx = timeouts.IndexOfValue (token as Timeout); if (idx == -1) return false; @@ -216,7 +217,7 @@ void RunTimers () // after we have taken the copy but before // we have allocated a new list (which would // result in lost timeouts or errors during enumeration) - lock (timeoutsLockToken) { + lock (_timeoutsLockToken) { copy = timeouts; timeouts = new SortedList (); } @@ -228,7 +229,7 @@ void RunTimers () if (timeout.Callback (this)) AddTimeout (timeout.Span, timeout); } else { - lock (timeoutsLockToken) { + lock (_timeoutsLockToken) { timeouts.Add (NudgeToUniqueKey (k), timeout); } } @@ -243,7 +244,7 @@ void RunTimers () /// private long NudgeToUniqueKey (long k) { - lock (timeoutsLockToken) { + lock (_timeoutsLockToken) { while (timeouts.ContainsKey (k)) { k++; } @@ -255,26 +256,26 @@ private long NudgeToUniqueKey (long k) void RunIdle () { List> iterate; - lock (idleHandlersLock) { + lock (_idleHandlersLock) { iterate = idleHandlers; idleHandlers = new List> (); } foreach (var idle in iterate) { if (idle ()) - lock (idleHandlersLock) + lock (_idleHandlersLock) idleHandlers.Add (idle); } } - bool running; + bool _running; /// /// Stops the mainloop. /// public void Stop () { - running = false; + _running = false; Driver.Wakeup (); } @@ -295,20 +296,21 @@ public bool EventsPending (bool wait = false) /// Runs one iteration of timers and file watches /// /// - /// You use this to process all pending events (timers, idle handlers and file watches). + /// Use this to process all pending events (timers, idle handlers and file watches). /// - /// You can use it like this: - /// while (main.EvensPending ()) MainIteration (); + /// + /// while (main.EvenstPending ()) RunIteration (); + /// /// - public void MainIteration () + public void RunIteration () { if (timeouts.Count > 0) RunTimers (); - Driver.MainIteration (); + Driver.Iteration (); bool runIdle = false; - lock (idleHandlersLock) { + lock (_idleHandlersLock) { runIdle = idleHandlers.Count > 0; } if (runIdle) { @@ -317,17 +319,17 @@ public void MainIteration () } /// - /// Runs the mainloop. + /// Runs the . /// public void Run () { - bool prev = running; - running = true; - while (running) { + bool prev = _running; + _running = true; + while (_running) { EventsPending (true); - MainIteration (); + RunIteration (); } - running = prev; + _running = prev; } } } diff --git a/Terminal.Gui/README.md b/Terminal.Gui/README.md index a8b431e91a..fb380d4c0f 100644 --- a/Terminal.Gui/README.md +++ b/Terminal.Gui/README.md @@ -34,13 +34,12 @@ All files required to build the **Terminal.Gui** library (and NuGet package). - `Views\` - Sub-classes of `View` - `Toplevel` - Derived from `View`, the base class for modal visual elements such as top-level windows and dialogs. Supports the concept of `MenuBar` and `StatusBar`. - - `Window` - Derived from `TopLevel`; implements toplevel views with a visible frame and Title. + - `Window` - Derived from `TopLevel`; implements Toplevel views with a visible frame and Title. - `Dialog` - - etc... - `Types/` - A folder (not namespace) containing implementations of `Point`, `Rect`, and `Size` which are ancient versions of the modern `System.Drawing.Point`, `System.Drawing.Size`, and `System.Drawning.Rectangle`. - ## Version numbers Version info for Terminal.Gui is managed by [gitversion](https://gitversion.net). diff --git a/Terminal.Gui/Text/Autocomplete/AppendAutocomplete.cs b/Terminal.Gui/Text/Autocomplete/AppendAutocomplete.cs index 5f6caaa9ea..4ca620fa73 100644 --- a/Terminal.Gui/Text/Autocomplete/AppendAutocomplete.cs +++ b/Terminal.Gui/Text/Autocomplete/AppendAutocomplete.cs @@ -30,7 +30,6 @@ public AppendAutocomplete (TextField textField) this.textField = textField; SelectionKey = Key.Tab; - ColorScheme = new ColorScheme{ Normal = new Attribute(Color.DarkGray,0), Focus = new Attribute(Color.DarkGray,0), @@ -147,7 +146,6 @@ internal bool AcceptSelectionIfAny () return false; } - internal void SetTextTo (FileSystemInfo fileSystemInfo) { var newText = fileSystemInfo.FullName; @@ -158,7 +156,6 @@ internal void SetTextTo (FileSystemInfo fileSystemInfo) textField.MoveEnd (); } - /// /// Returns true if there is a suggestion that can be made and the control /// is in a state where user would expect to see auto-complete (i.e. focused and diff --git a/Terminal.Gui/Text/Autocomplete/AutocompleteBase.cs b/Terminal.Gui/Text/Autocomplete/AutocompleteBase.cs index 3c27995f4b..a168eb0db6 100644 --- a/Terminal.Gui/Text/Autocomplete/AutocompleteBase.cs +++ b/Terminal.Gui/Text/Autocomplete/AutocompleteBase.cs @@ -26,7 +26,6 @@ public abstract class AutocompleteBase : IAutocomplete { /// - /// public virtual bool Visible { get; set; } @@ -34,11 +33,9 @@ public abstract class AutocompleteBase : IAutocomplete { public virtual ReadOnlyCollection Suggestions { get; set; } = new ReadOnlyCollection (new Suggestion [0]); - /// public virtual int SelectedIdx { get; set; } - /// public abstract ColorScheme ColorScheme { get; set; } @@ -65,7 +62,6 @@ public virtual void ClearSuggestions () Suggestions = Enumerable.Empty ().ToList ().AsReadOnly (); } - /// public virtual void GenerateSuggestions (AutocompleteContext context) { diff --git a/Terminal.Gui/Text/Autocomplete/IAutocomplete.cs b/Terminal.Gui/Text/Autocomplete/IAutocomplete.cs index 8ce2ad3abb..a12c931613 100644 --- a/Terminal.Gui/Text/Autocomplete/IAutocomplete.cs +++ b/Terminal.Gui/Text/Autocomplete/IAutocomplete.cs @@ -75,7 +75,6 @@ public interface IAutocomplete { /// void RenderOverlay (Point renderAt); - /// /// Handle key events before e.g. to make key events like /// up/down apply to the autocomplete control instead of changing the cursor position in @@ -100,14 +99,12 @@ public interface IAutocomplete { /// void ClearSuggestions (); - /// /// Gets or Sets the class responsible for generating /// based on a given of the . /// ISuggestionGenerator SuggestionGenerator { get; set; } - /// /// Populates with all /// proposed by at the given diff --git a/Terminal.Gui/Text/Autocomplete/ISuggestionGenerator.cs b/Terminal.Gui/Text/Autocomplete/ISuggestionGenerator.cs index f4e2d08a52..7ad64569f2 100644 --- a/Terminal.Gui/Text/Autocomplete/ISuggestionGenerator.cs +++ b/Terminal.Gui/Text/Autocomplete/ISuggestionGenerator.cs @@ -12,7 +12,6 @@ public interface ISuggestionGenerator { /// IEnumerable GenerateSuggestions (AutocompleteContext context); - /// /// Returns if is a character that /// would continue autocomplete suggesting. Returns if it diff --git a/Terminal.Gui/Text/Autocomplete/PopupAutocomplete.cs b/Terminal.Gui/Text/Autocomplete/PopupAutocomplete.cs index 5ac399d20b..e97f9611fe 100644 --- a/Terminal.Gui/Text/Autocomplete/PopupAutocomplete.cs +++ b/Terminal.Gui/Text/Autocomplete/PopupAutocomplete.cs @@ -120,7 +120,6 @@ private void ManipulatePopup () } } - /// /// When more suggestions are available than can be rendered the user /// can scroll down the dropdown list. This indicates how far down they @@ -256,7 +255,6 @@ public override void EnsureSelectedIdxIsValid () { base.EnsureSelectedIdxIsValid (); - // if user moved selection up off top of current scroll window if (SelectedIdx < ScrollOffset) { ScrollOffset = SelectedIdx; @@ -410,7 +408,6 @@ protected void RenderSelectedIdxByMouse (MouseEvent me) } - /// /// Completes the autocomplete selection process. Called when user hits the . /// @@ -447,7 +444,6 @@ protected virtual bool InsertSelection (Suggestion accepted) return true; } - /// /// Deletes the text backwards before insert the selected text in the . /// diff --git a/Terminal.Gui/Text/Autocomplete/SingleWordSuggestionGenerator.cs b/Terminal.Gui/Text/Autocomplete/SingleWordSuggestionGenerator.cs index e222b9794c..3af30b8c1e 100644 --- a/Terminal.Gui/Text/Autocomplete/SingleWordSuggestionGenerator.cs +++ b/Terminal.Gui/Text/Autocomplete/SingleWordSuggestionGenerator.cs @@ -53,7 +53,6 @@ public virtual bool IsWordChar (Rune rune) return Char.IsLetterOrDigit ((char)rune); } - /// /// /// Given a of characters, returns the word which ends at diff --git a/Terminal.Gui/Text/Autocomplete/Suggestion.cs b/Terminal.Gui/Text/Autocomplete/Suggestion.cs index 88f8b23750..8e0ee2f077 100644 --- a/Terminal.Gui/Text/Autocomplete/Suggestion.cs +++ b/Terminal.Gui/Text/Autocomplete/Suggestion.cs @@ -21,7 +21,6 @@ public class Suggestion { /// public string Replacement { get; } - /// /// Creates a new instance of the class. /// diff --git a/Terminal.Gui/Text/TextFormatter.cs b/Terminal.Gui/Text/TextFormatter.cs index d3cd9fa30c..97b02b587a 100644 --- a/Terminal.Gui/Text/TextFormatter.cs +++ b/Terminal.Gui/Text/TextFormatter.cs @@ -877,7 +877,6 @@ public static bool FindHotKey (ustring text, Rune hotKeySpecifier, bool firstUpp i++; } - // Legacy support - use first upper case char if the specifier was not found if (hot_pos == -1 && firstUpperCase) { i = 0; @@ -1106,7 +1105,6 @@ public static bool IsTopToBottom (TextDirection textDirection) /// public bool WordWrap { get; set; } = false; - /// /// Gets or sets the size of the area the text will be constrained to when formatted. /// diff --git a/Terminal.Gui/TimeoutEventArgs.cs b/Terminal.Gui/TimeoutEventArgs.cs index c488bdc114..876dde6782 100644 --- a/Terminal.Gui/TimeoutEventArgs.cs +++ b/Terminal.Gui/TimeoutEventArgs.cs @@ -17,7 +17,6 @@ public class TimeoutEventArgs : EventArgs { /// public long Ticks { get; } - /// /// Creates a new instance of the class. /// diff --git a/Terminal.Gui/Types/PointF.cs b/Terminal.Gui/Types/PointF.cs index be874231b5..c1927df125 100644 --- a/Terminal.Gui/Types/PointF.cs +++ b/Terminal.Gui/Types/PointF.cs @@ -104,7 +104,6 @@ public float Y { /// public static PointF Subtract (PointF pt, SizeF sz) => new PointF (pt.X - sz.Width, pt.Y - sz.Height); - /// /// Compares two objects. The result specifies whether the values of the /// and properties of the two @@ -112,7 +111,6 @@ public float Y { /// public override bool Equals (object obj) => obj is PointF && Equals ((PointF)obj); - /// /// Compares two objects. The result specifies whether the values of the /// and properties of the two diff --git a/Terminal.Gui/Types/Rect.cs b/Terminal.Gui/Types/Rect.cs index 8d3ed16f61..9a1e62fc96 100644 --- a/Terminal.Gui/Types/Rect.cs +++ b/Terminal.Gui/Types/Rect.cs @@ -261,7 +261,6 @@ public Rect (int x, int y, int width, int height) } - /// /// Bottom Property /// diff --git a/Terminal.Gui/Types/SizeF.cs b/Terminal.Gui/Types/SizeF.cs index 226dadc0dc..155f6793bc 100644 --- a/Terminal.Gui/Types/SizeF.cs +++ b/Terminal.Gui/Types/SizeF.cs @@ -136,7 +136,6 @@ public float Height { /// public override bool Equals (object obj) => obj is SizeF && Equals ((SizeF)obj); - /// /// Tests whether two objects are identical. /// diff --git a/Terminal.Gui/View/Border.cs b/Terminal.Gui/View/Border.cs deleted file mode 100644 index a68a48e271..0000000000 --- a/Terminal.Gui/View/Border.cs +++ /dev/null @@ -1,10 +0,0 @@ -using NStack; -using System; -using System.Text.Json.Serialization; -using System.Data; -using System.Text; -using System.Collections.Generic; - -namespace Terminal.Gui { - -} \ No newline at end of file diff --git a/Terminal.Gui/View/Frame.cs b/Terminal.Gui/View/Frame.cs index 508160eaf2..c6e7d43cc1 100644 --- a/Terminal.Gui/View/Frame.cs +++ b/Terminal.Gui/View/Frame.cs @@ -12,7 +12,6 @@ namespace Terminal.Gui { // TODO: v2 - If a Frame has focus, navigation keys (e.g Command.NextView) should cycle through SubViews of the Frame // QUESTION: How does a user navigate out of a Frame to another Frame, or back into the Parent's SubViews? - /// /// Frames are a special form of that act as adornments; they appear outside of the /// enabling borders, menus, etc... @@ -184,7 +183,6 @@ public override void Redraw (Rect bounds) sideLineLength++; } - if (Id == "Border" && Thickness.Top > 0 && maxTitleWidth > 0 && !ustring.IsNullOrEmpty (Parent?.Title)) { var prevAttr = Driver.GetAttribute (); Driver.SetAttribute (Parent.HasFocus ? Parent.GetHotNormalColor () : Parent.GetNormalColor ()); @@ -278,7 +276,6 @@ public override void Redraw (Rect bounds) } } - Driver.Clip = prevClip; } diff --git a/Terminal.Gui/View/Layout/ResizedEventArgs.cs b/Terminal.Gui/View/Layout/ResizedEventArgs.cs index a4cc81a0ba..cf3505840f 100644 --- a/Terminal.Gui/View/Layout/ResizedEventArgs.cs +++ b/Terminal.Gui/View/Layout/ResizedEventArgs.cs @@ -17,7 +17,7 @@ namespace Terminal.Gui { /// - /// Event arguments for the event. + /// Event arguments for the event. /// public class ResizedEventArgs : EventArgs { /// diff --git a/Terminal.Gui/View/View.cs b/Terminal.Gui/View/View.cs index 4ee7e0298e..7e662a72fe 100644 --- a/Terminal.Gui/View/View.cs +++ b/Terminal.Gui/View/View.cs @@ -1,32 +1,11 @@ using System; using System.Collections.Generic; using System.ComponentModel; -using System.Diagnostics; using System.Linq; -using System.Reflection; using NStack; - namespace Terminal.Gui { - /// - /// Determines the LayoutStyle for a , if Absolute, during , the - /// value from the will be used, if the value is Computed, then - /// will be updated from the X, Y objects and the Width and Height objects. - /// - public enum LayoutStyle { - /// - /// The position and size of the view are based . - /// - Absolute, - - /// - /// The position and size of the view will be computed based on - /// , , , and . will - /// provide the absolute computed values. - /// - Computed - } - + #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. @@ -37,6 +16,28 @@ public enum LayoutStyle { /// can contain one or more subviews, can respond to user input and render themselves on the screen. /// /// + /// SubView - A View that is contained in another view and will be rendered as part of the containing view's ContentArea. + /// SubViews are added to another view via the ` method. A View may only be a SubView of a single View. + /// + /// + /// SuperView - The View that is a container for SubViews. + /// + /// + /// Focus is a concept that is used to describe which Responder 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 @@ -73,10 +74,16 @@ public enum LayoutStyle { /// 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 + /// 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. /// /// @@ -90,19 +97,6 @@ public enum LayoutStyle { /// in color as well as black and white displays. /// /// - /// 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 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 can also opt-in to more sophisticated initialization /// by implementing overrides to and /// which will be called @@ -117,135 +111,215 @@ public enum LayoutStyle { /// instead of on every run. /// /// + #endregion API Docs public partial class View : Responder, ISupportInitializeNotification { - internal enum Direction { - Forward, - Backward - } - View _superView = null; - View _focused = null; - Direction _focusDirection; - bool _autoSize; - ShortcutHelper _shortcutHelper; + #region Constructors and Initialization /// - /// Event fired when this view is added to another. + /// Initializes a new instance of a class with the absolute + /// dimensions specified in the parameter. /// - public event EventHandler Added; + /// 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) { } /// - /// Event fired when this view is removed from another. + /// Initializes a new instance of using layout. /// - public event EventHandler Removed; + /// + /// + /// 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) { } /// - /// Event fired when the view gets focus. + /// Initializes a new instance of using layout. /// - public event EventHandler Enter; + /// + /// + /// 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, ustring text) : this (TextFormatter.CalcRect (x, y, text), text) { } /// - /// Event fired when the view looses focus. + /// Initializes a new instance of using layout. /// - public event EventHandler Leave; + /// + /// + /// 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, ustring text) + { + SetInitialProperties (text, rect, LayoutStyle.Absolute, TextDirection.LeftRight_TopBottom); + } /// - /// Event fired when the view receives the mouse event for the first time. + /// Initializes a new instance of using layout. /// - public event EventHandler MouseEnter; + /// + /// + /// 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 (ustring text, TextDirection direction = TextDirection.LeftRight_TopBottom) + { + SetInitialProperties (text, Rect.Empty, LayoutStyle.Computed, direction); + } + // TODO: v2 - Remove constructors with parameters /// - /// Event fired when the view receives a mouse event for the last time. + /// Private helper to set the initial properties of the View that were provided via constructors. /// - public event EventHandler MouseLeave; + /// + /// + /// + /// + void SetInitialProperties (ustring text, Rect rect, LayoutStyle layoutStyle = LayoutStyle.Computed, + TextDirection direction = TextDirection.LeftRight_TopBottom) + { + TextFormatter = new TextFormatter (); + TextFormatter.HotKeyChanged += TextFormatter_HotKeyChanged; + TextDirection = direction; - /// - /// Event fired when a mouse event is generated. - /// - public event EventHandler MouseClick; + _shortcutHelper = new ShortcutHelper (); + CanFocus = false; + TabIndex = -1; + TabStop = false; + LayoutStyle = layoutStyle; - /// - /// Event fired when the value is being changed. - /// - public event EventHandler CanFocusChanged; + Text = text == null ? ustring.Empty : text; + LayoutStyle = layoutStyle; + Frame = rect.IsEmpty ? TextFormatter.CalcRect (0, 0, text, direction) : rect; + OnResizeNeeded (); - /// - /// Event fired when the value is being changed. - /// - public event EventHandler EnabledChanged; + CreateFrames (); + + LayoutFrames (); + } /// - /// Event fired when the value is being changed. + /// Get or sets if the has been initialized (via + /// and ). /// - public event EventHandler VisibleChanged; + /// + /// 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; } /// - /// Event invoked when the is changed. + /// Signals the View that initialization is starting. See . /// - public event EventHandler HotKeyChanged; + /// + /// + /// 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; + + UpdateTextDirection (TextDirection); + UpdateTextFormatterText (); + SetHotKey (); - Key _hotKey = Key.Null; + // 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."); - /// - /// Gets or sets the HotKey defined for this view. A user pressing HotKey on the keyboard while this view has focus will cause the Clicked event to fire. - /// - public virtual Key HotKey { - get => _hotKey; - set { - if (_hotKey != value) { - var v = value == Key.Unknown ? Key.Null : value; - if (_hotKey != Key.Null && ContainsKeyBinding (Key.Space | _hotKey)) { - if (v == Key.Null) { - ClearKeybinding (Key.Space | _hotKey); - } else { - ReplaceKeyBinding (Key.Space | _hotKey, Key.Space | v); - } - } else if (v != Key.Null) { - AddKeyBinding (Key.Space | v, Command.Accept); - } - _hotKey = TextFormatter.HotKey = v; - } } - } - /// - /// Gets or sets the specifier character for the hotkey (e.g. '_'). Set to '\xffff' to disable hotkey support for this View instance. The default is '\xffff'. - /// - public virtual Rune HotKeySpecifier { - get { - if (TextFormatter != null) { - return TextFormatter.HotKeySpecifier; - } else { - return new Rune ('\xFFFF'); + if (_subviews?.Count > 0) { + foreach (var view in _subviews) { + if (!view.IsInitialized) { + view.BeginInit (); + } } } - set { - TextFormatter.HotKeySpecifier = value; - SetHotKey (); - } } /// - /// This is the global setting that can be used as a global shortcut to invoke an action if provided. + /// Signals the View that initialization is ending. See . /// - public Key Shortcut { - get => _shortcutHelper.Shortcut; - set { - if (_shortcutHelper.Shortcut != value && (ShortcutHelper.PostShortcutValidation (value) || value == Key.Null)) { - _shortcutHelper.Shortcut = value; + public void EndInit () + { + IsInitialized = true; + OnResizeNeeded (); + if (_subviews != null) { + foreach (var view in _subviews) { + if (!view.IsInitialized) { + view.EndInit (); + } } } + Initialized?.Invoke (this, EventArgs.Empty); } + #endregion Constructors and Initialization /// - /// The keystroke combination used in the as string. - /// - public ustring ShortcutTag => ShortcutHelper.GetShortcutTag (_shortcutHelper.Shortcut); - - /// - /// The action to run if the is defined. + /// Points to the current driver in use by the view, it is a convenience property + /// for simplifying the development of new views. /// - public virtual Action ShortcutAction { get; set; } + public static ConsoleDriver Driver => Application.Driver; /// /// Gets or sets arbitrary data for the view. @@ -253,327 +327,17 @@ public Key Shortcut { /// This property is not used internally. public object Data { get; set; } - internal Direction FocusDirection { - get => SuperView?.FocusDirection ?? _focusDirection; - set { - if (SuperView != null) - SuperView.FocusDirection = value; - else - _focusDirection = value; - } - } - - /// - /// 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; - - static readonly IList _empty = new List (0).AsReadOnly (); - - // This is null, and allocated on demand. - List _subviews; - /// - /// This returns a list of the subviews contained by this view. + /// Gets or sets an identifier for the view; /// - /// The subviews. - public IList Subviews => _subviews?.AsReadOnly () ?? _empty; - - // Internally, we use InternalSubviews rather than subviews, as we do not expect us - // to make the same mistakes our users make when they poke at the Subviews. - internal IList InternalSubviews => _subviews ?? _empty; - - // This is null, and allocated on demand. - List _tabIndexes; + /// The identifier. + /// The id should be unique across all Views that share a SuperView. + public string Id { get; set; } = ""; + ustring _title = ustring.Empty; /// - /// Configurable keybindings supported by the control - /// - private Dictionary KeyBindings { get; set; } = new Dictionary (); - private Dictionary> CommandImplementations { get; set; } = new Dictionary> (); - - /// - /// This returns a tab index list of the subviews contained by this view. - /// - /// The tabIndexes. - public IList TabIndexes => _tabIndexes?.AsReadOnly () ?? _empty; - - int _tabIndex = -1; - - /// - /// Indicates the index of the current from the list. - /// - public int TabIndex { - get { return _tabIndex; } - set { - if (!CanFocus) { - _tabIndex = -1; - return; - } else if (SuperView?._tabIndexes == null || SuperView?._tabIndexes.Count == 1) { - _tabIndex = 0; - return; - } else if (_tabIndex == value) { - return; - } - _tabIndex = value > SuperView._tabIndexes.Count - 1 ? SuperView._tabIndexes.Count - 1 : value < 0 ? 0 : value; - _tabIndex = GetTabIndex (_tabIndex); - if (SuperView._tabIndexes.IndexOf (this) != _tabIndex) { - SuperView._tabIndexes.Remove (this); - SuperView._tabIndexes.Insert (_tabIndex, this); - SetTabIndex (); - } - } - } - - int GetTabIndex (int idx) - { - var i = 0; - foreach (var v in SuperView._tabIndexes) { - if (v._tabIndex == -1 || v == this) { - continue; - } - i++; - } - return Math.Min (i, idx); - } - - void SetTabIndex () - { - var i = 0; - foreach (var v in SuperView._tabIndexes) { - if (v._tabIndex == -1) { - continue; - } - v._tabIndex = i; - i++; - } - } - - bool _tabStop = true; - - /// - /// This only be if the is also - /// and the focus can be avoided by setting this to - /// - public bool TabStop { - get => _tabStop; - set { - if (_tabStop == value) { - return; - } - _tabStop = CanFocus && value; - } - } - - bool _oldCanFocus; - int _oldTabIndex; - - /// - public override bool CanFocus { - get => base.CanFocus; - set { - if (!_addingView && IsInitialized && SuperView?.CanFocus == false && value) { - throw new InvalidOperationException ("Cannot set CanFocus to true if the SuperView CanFocus is false!"); - } - if (base.CanFocus != value) { - base.CanFocus = value; - - switch (value) { - case false when _tabIndex > -1: - TabIndex = -1; - break; - case true when SuperView?.CanFocus == false && _addingView: - SuperView.CanFocus = true; - break; - } - - if (value && _tabIndex == -1) { - TabIndex = SuperView != null ? SuperView._tabIndexes.IndexOf (this) : -1; - } - TabStop = value; - - if (!value && SuperView?.Focused == this) { - SuperView._focused = null; - } - if (!value && HasFocus) { - SetHasFocus (false, this); - SuperView?.EnsureFocus (); - if (SuperView != null && SuperView.Focused == null) { - SuperView.FocusNext (); - if (SuperView.Focused == null) { - Application.Current.FocusNext (); - } - Application.EnsuresTopOnFront (); - } - } - if (_subviews != null && IsInitialized) { - foreach (var view in _subviews) { - if (view.CanFocus != value) { - if (!value) { - view._oldCanFocus = view.CanFocus; - view._oldTabIndex = view._tabIndex; - view.CanFocus = false; - view._tabIndex = -1; - } else { - if (_addingView) { - view._addingView = true; - } - view.CanFocus = view._oldCanFocus; - view._tabIndex = view._oldTabIndex; - view._addingView = false; - } - } - } - } - OnCanFocusChanged (); - SetNeedsDisplay (); - } - } - } - - // The frame for the object. Superview relative. - Rect _frame; - - /// - /// 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; } = ""; - - /// - /// Returns a value indicating if this View is currently on Top (Active) - /// - public bool IsCurrentTop => Application.Current == this; - - /// - /// Gets or sets a value indicating whether this wants mouse position reports. - /// - /// if want mouse position reports; otherwise, . - public virtual bool WantMousePositionReports { get; set; } - - /// - /// Gets or sets a value indicating whether this want continuous button pressed event. - /// - public virtual bool WantContinuousButtonPressed { get; set; } - - /// - /// Gets or sets the frame for the view. The frame is relative to the view's container (). - /// - /// The frame. - /// - /// - /// 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 . - /// - /// - public virtual Rect Frame { - get => _frame; - set { - _frame = new Rect (value.X, value.Y, Math.Max (value.Width, 0), Math.Max (value.Height, 0)); - if (IsInitialized || LayoutStyle == LayoutStyle.Absolute) { - LayoutFrames (); - TextFormatter.Size = GetSizeNeededForTextAndHotKey (); - SetNeedsLayout (); - SetNeedsDisplay (); - } - } - } - - /// - /// The Thickness that separates a View from other SubViews of the same SuperView. - /// The Margin is not part of the View's content and is not clipped by the View's Clip Area. - /// - public Frame Margin { get; private set; } - - /// - /// Thickness where a visual border (drawn using line-drawing glyphs) and the Title are drawn. - /// The Border expands inward; in other words if `Border.Thickness.Top == 2` the border and - /// title will take up the first row and the second row will be filled with spaces. - /// The Border is not part of the View's content and is not clipped by the View's `ClipArea`. - /// - /// - /// provides a simple helper for turning a simple border frame on or off. - /// - public Frame Border { get; private set; } - - /// - /// Means the Thickness inside of an element that offsets the `Content` from the Border. - /// Padding is `{0, 0, 0, 0}` by default. Padding is not part of the View's content and is not clipped by the View's `ClipArea`. - /// - /// - /// (NOTE: in v1 `Padding` is OUTSIDE of the `Border`). - /// - public Frame Padding { get; private set; } - - /// - /// Helper to get the total thickness of the , , and . - /// - /// A thickness that describes the sum of the Frames' thicknesses. - public Thickness GetFramesThickness () - { - var left = Margin.Thickness.Left + Border.Thickness.Left + Padding.Thickness.Left; - var top = Margin.Thickness.Top + Border.Thickness.Top + Padding.Thickness.Top; - var right = Margin.Thickness.Right + Border.Thickness.Right + Padding.Thickness.Right; - var bottom = Margin.Thickness.Bottom + Border.Thickness.Bottom + Padding.Thickness.Bottom; - return new Thickness (left, top, right, bottom); - } - - /// - /// 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); - - /// - /// Creates the view's objects. This internal method is overridden by Frame to do nothing - /// to prevent recursion during View construction. - /// - internal virtual void CreateFrames () - { - void ThicknessChangedHandler (object sender, EventArgs e) - { - LayoutFrames (); - SetNeedsLayout (); - SetNeedsDisplay (); - } - - if (Margin != null) { - Margin.ThicknessChanged -= ThicknessChangedHandler; - Margin.Dispose (); - } - Margin = new Frame () { Id = "Margin", Thickness = new Thickness (0) }; - Margin.ThicknessChanged += ThicknessChangedHandler; - Margin.Parent = this; - - if (Border != null) { - Border.ThicknessChanged -= ThicknessChangedHandler; - Border.Dispose (); - } - Border = new Frame () { Id = "Border", Thickness = new Thickness (0) }; - Border.ThicknessChanged += ThicknessChangedHandler; - Border.Parent = this; - - // TODO: Create View.AddAdornment - - if (Padding != null) { - Padding.ThicknessChanged -= ThicknessChangedHandler; - Padding.Dispose (); - } - Padding = new Frame () { Id = "Padding", Thickness = new Thickness (0) }; - Padding.ThicknessChanged += ThicknessChangedHandler; - Padding.Parent = this; - } - - ustring _title = ustring.Empty; - - /// - /// The title to be displayed for this . The title will be displayed if . - /// is greater than 0. + /// The title to be displayed for this . The title will be displayed if . + /// is greater than 0. /// /// The title. public ustring Title { @@ -628,2710 +392,105 @@ public virtual void OnTitleChanged (ustring oldTitle, ustring newTitle) /// public event EventHandler TitleChanged; - - LayoutStyle _layoutStyle; - - /// - /// Controls how the View's is computed during the LayoutSubviews method, if the style is set to - /// , - /// LayoutSubviews does not change the . If the style is - /// the is updated using - /// the , , , and properties. - /// - /// The layout style. - public LayoutStyle LayoutStyle { - get => _layoutStyle; - set { - _layoutStyle = value; - SetNeedsLayout (); - } - } - /// - /// The View-relative rectangle where View content is displayed. SubViews are positioned relative to - /// Bounds.Location (which is always (0, 0)) and clips drawing to - /// Bounds.Size. + /// Event fired when the value is being changed. /// - /// The bounds. - /// - /// - /// The of Bounds is always (0, 0). To obtain the offset of the Bounds from the Frame use - /// . - /// - /// - public virtual Rect Bounds { - get { -#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. View: {this}"); - } -#endif // DEBUG - var frameRelativeBounds = Padding?.Thickness.GetInside (Padding.Frame) ?? new Rect (default, Frame.Size); - return new Rect (default, frameRelativeBounds.Size); - } - set { - // BUGBUG: Margin etc.. can be null (if typeof(Frame)) - Frame = new Rect (Frame.Location, - new Size ( - value.Size.Width + Margin.Thickness.Horizontal + Border.Thickness.Horizontal + Padding.Thickness.Horizontal, - value.Size.Height + Margin.Thickness.Vertical + Border.Thickness.Vertical + Padding.Thickness.Vertical - ) - ); - } - } - - // Diagnostics to highlight when X or Y is read before the view has been initialized - private Pos VerifyIsIntialized (Pos pos) - { -#if DEBUG - if (LayoutStyle == LayoutStyle.Computed && (!IsInitialized)) { - Debug.WriteLine ($"WARNING: \"{this}\" has not been initialized; position is indeterminate {pos}. This is likely a bug."); - } -#endif // DEBUG - return pos; - } + public event EventHandler EnabledChanged; - // Diagnostics to highlight when Width or Height is read before the view has been initialized - private Dim VerifyIsIntialized (Dim dim) - { -#if DEBUG - if (LayoutStyle == LayoutStyle.Computed && (!IsInitialized)) { - Debug.WriteLine ($"WARNING: \"{this}\" has not been initialized; dimension is indeterminate: {dim}. This is likely a bug."); - } -#endif // DEBUG - return dim; - } + /// + public override void OnEnabledChanged () => EnabledChanged?.Invoke (this, EventArgs.Empty); - Pos _x, _y; + bool _oldEnabled; - /// - /// Gets or sets the X position for the view (the column). Only used if the is . - /// - /// The X Position. - /// - /// If is changing this property has no effect and its value is indeterminate. - /// - public Pos X { - get => VerifyIsIntialized (_x); + /// + public override bool Enabled { + get => base.Enabled; set { - if (ForceValidatePosDim && !ValidatePosDim (_x, value)) { - throw new ArgumentException (); - } - - _x = value; - - OnResizeNeeded (); - } - } + if (base.Enabled != value) { + if (value) { + if (SuperView == null || SuperView?.Enabled == true) { + base.Enabled = value; + } + } else { + base.Enabled = value; + } + if (!value && HasFocus) { + SetHasFocus (false, this); + } + OnEnabledChanged (); + SetNeedsDisplay (); - /// - /// Gets or sets the Y position for the view (the row). Only used if the is . - /// - /// The y position (line). - /// - /// If is changing this property has no effect and its value is indeterminate. - /// - public Pos Y { - get => VerifyIsIntialized (_y); - set { - if (ForceValidatePosDim && !ValidatePosDim (_y, value)) { - throw new ArgumentException (); + 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; + } + } + } } - - _y = value; - - OnResizeNeeded (); } } - Dim _width, _height; - + /// - /// Gets or sets the width of the view. Only used the is . + /// Event fired when the value is being changed. /// - /// The width. - /// - /// If is changing this property has no effect and its value is indeterminate. - /// - public Dim Width { - get => VerifyIsIntialized (_width); - set { - if (ForceValidatePosDim && !ValidatePosDim (_width, value)) { - throw new ArgumentException ("ForceValidatePosDim is enabled", nameof (Width)); - } - - _width = value; - - if (ForceValidatePosDim) { - var isValidNewAutSize = AutoSize && IsValidAutoSizeWidth (_width); + public event EventHandler VisibleChanged; - if (IsAdded && AutoSize && !isValidNewAutSize) { - throw new InvalidOperationException ("Must set AutoSize to false before set the Width."); - } - } - OnResizeNeeded (); - } - } + /// + public override void OnVisibleChanged () => VisibleChanged?.Invoke (this, EventArgs.Empty); /// - /// Gets or sets the height of the view. Only used the is . + /// Gets or sets whether a view is cleared if the property is . /// - /// The height. - /// If is changing this property has no effect and its value is indeterminate. - public Dim Height { - get => VerifyIsIntialized (_height); - set { - if (ForceValidatePosDim && !ValidatePosDim (_height, value)) { - throw new ArgumentException ("ForceValidatePosDim is enabled", nameof (Height)); - } - - _height = value; - - if (ForceValidatePosDim) { - var isValidNewAutSize = AutoSize && IsValidAutoSizeHeight (_height); + public bool ClearOnVisibleFalse { get; set; } = true; - if (IsAdded && AutoSize && !isValidNewAutSize) { - throw new InvalidOperationException ("Must set AutoSize to false before set the Height."); + /// > + 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 (); } - OnResizeNeeded (); } } - /// - /// Forces validation with layout - /// to avoid breaking the and settings. - /// - public bool ForceValidatePosDim { get; set; } - - bool ValidatePosDim (object oldValue, object newValue) + bool CanBeVisible (View view) { - if (!IsInitialized || _layoutStyle == LayoutStyle.Absolute || oldValue == null || oldValue.GetType () == newValue.GetType () || this is Toplevel) { - return true; + if (!view.Visible) { + return false; } - if (_layoutStyle == LayoutStyle.Computed) { - if (oldValue.GetType () != newValue.GetType () && !(newValue is Pos.PosAbsolute || newValue is Dim.DimAbsolute)) { - return true; + for (var c = view.SuperView; c != null; c = c.SuperView) { + if (!c.Visible) { + return false; } } - return false; - } - // BUGBUG: This API is broken - It should be renamed to `GetMinimumBoundsForFrame and - // should not assume Frame.Height == Bounds.Height + return true; + } + /// - /// Gets the minimum dimensions required to fit the View's , factoring in . + /// Pretty prints the View /// - /// 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. - /// - public bool GetMinimumBounds (out Size size) - { - size = Bounds.Size; - - if (!AutoSize && !ustring.IsNullOrEmpty (TextFormatter.Text)) { - switch (TextFormatter.IsVerticalDirection (TextDirection)) { - case true: - var colWidth = TextFormatter.GetSumMaxCharWidth (new List { TextFormatter.Text }, 0, 1); - // TODO: v2 - This uses frame.Width; it should only use Bounds - if (_frame.Width < colWidth && - (Width == null || - (Bounds.Width >= 0 && - Width is Dim.DimAbsolute && - Width.Anchor (0) >= 0 && - Width.Anchor (0) < colWidth))) { - size = new Size (colWidth, Bounds.Height); - return true; - } - break; - default: - if (_frame.Height < 1 && - (Height == null || - (Height is Dim.DimAbsolute && - Height.Anchor (0) == 0))) { - size = new Size (Bounds.Width, 1); - return true; - } - break; - } - } - return false; - } - - // BUGBUG - v2 - Should be renamed "SetBoundsToFitFrame" - /// - /// Sets the size of the View to the minimum width or height required to fit (see . - /// - /// if the size was changed, if - /// will not fit. - public bool SetMinWidthHeight () - { - if (GetMinimumBounds (out Size size)) { - _frame = new Rect (_frame.Location, size); - return true; - } - return false; - } - - /// - /// Gets or sets the used to format . - /// - public TextFormatter TextFormatter { get; set; } - - /// - /// Returns the container for this view, or null if this view has not been added to a container. - /// - /// The super view. - public virtual View SuperView { - get { - return _superView; - } - set { - throw new NotImplementedException (); - } - } - - /// - /// 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, ustring 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, ustring text) - { - SetInitialProperties (text, rect, LayoutStyle.Absolute, TextDirection.LeftRight_TopBottom); - } - - /// - /// Initializes a new instance of using layout. - /// - /// - /// - /// The will be created using - /// coordinates with the given string. The initial size () will be - /// adjusted to fit the contents of , including newlines ('\n') for multiple lines. - /// - /// - /// If is greater than one, word wrapping is provided. - /// - /// - /// text to initialize the property with. - /// The text direction. - public View (ustring 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 (ustring text, Rect rect, LayoutStyle layoutStyle = LayoutStyle.Computed, - TextDirection direction = TextDirection.LeftRight_TopBottom) - { - TextFormatter = new TextFormatter (); - TextFormatter.HotKeyChanged += TextFormatter_HotKeyChanged; - TextDirection = direction; - - _shortcutHelper = new ShortcutHelper (); - CanFocus = false; - TabIndex = -1; - TabStop = false; - LayoutStyle = layoutStyle; - - Text = text == null ? ustring.Empty : text; - LayoutStyle = layoutStyle; - Frame = rect.IsEmpty ? TextFormatter.CalcRect (0, 0, text, direction) : rect; - OnResizeNeeded (); - - CreateFrames (); - - LayoutFrames (); - } - - /// - /// Can be overridden if the has - /// different format than the default. - /// - protected virtual void UpdateTextFormatterText () - { - if (TextFormatter != null) { - TextFormatter.Text = _text; - } - } - - /// - /// Called whenever the view needs to be resized. Sets and - /// triggers a call. /// - /// - /// - /// Can be overridden if the view resize behavior is different than the default. - /// - protected virtual void OnResizeNeeded () - { - var actX = _x is Pos.PosAbsolute ? _x.Anchor (0) : _frame.X; - var actY = _y is Pos.PosAbsolute ? _y.Anchor (0) : _frame.Y; - - if (AutoSize) { - //if (TextAlignment == TextAlignment.Justified) { - // throw new InvalidOperationException ("TextAlignment.Justified cannot be used with AutoSize"); - //} - var s = GetAutoSize (); - var w = _width is Dim.DimAbsolute && _width.Anchor (0) > s.Width ? _width.Anchor (0) : s.Width; - var h = _height is Dim.DimAbsolute && _height.Anchor (0) > s.Height ? _height.Anchor (0) : s.Height; - _frame = new Rect (new Point (actX, actY), new Size (w, h)); // Set frame, not Frame! - } else { - var w = _width is Dim.DimAbsolute ? _width.Anchor (0) : _frame.Width; - var h = _height is Dim.DimAbsolute ? _height.Anchor (0) : _frame.Height; - // BUGBUG: v2 - ? - If layoutstyle is absolute, this overwrites the current frame h/w with 0. Hmmm... - // This is needed for DimAbsolute values by setting the frame before LayoutSubViews. - _frame = new Rect (new Point (actX, actY), new Size (w, h)); // Set frame, not Frame! - } - //// BUGBUG: I think these calls are redundant or should be moved into just the AutoSize case - if (IsInitialized || LayoutStyle == LayoutStyle.Absolute) { - SetMinWidthHeight (); - LayoutFrames (); - TextFormatter.Size = GetSizeNeededForTextAndHotKey (); - SetNeedsLayout (); - SetNeedsDisplay (); - } - } - - void TextFormatter_HotKeyChanged (object sender, KeyChangedEventArgs e) - { - HotKeyChanged?.Invoke (this, e); - } - - internal bool LayoutNeeded { get; private set; } = true; - - internal void SetNeedsLayout () - { - if (LayoutNeeded) - return; - LayoutNeeded = true; - if (SuperView == null) - return; - SuperView.SetNeedsLayout (); - foreach (var view in Subviews) { - view.SetNeedsLayout (); - } - TextFormatter.NeedsFormat = true; - } - - /// - /// Removes the setting on this view. - /// - protected void ClearLayoutNeeded () - { - LayoutNeeded = false; - } - - // The view-relative region that needs to be redrawn - internal Rect _needsDisplay { get; private set; } = Rect.Empty; - - /// - /// Sets a flag indicating this view needs to be redisplayed because its state has changed. - /// - public void SetNeedsDisplay () - { - if (!IsInitialized) return; - SetNeedsDisplay (Bounds); - } - - /// - /// Flags the view-relative region on this View as needing to be redrawn. - /// - /// The view-relative region that needs to be redrawn. - public void SetNeedsDisplay (Rect region) - { - if (_needsDisplay.IsEmpty) - _needsDisplay = region; - else { - var x = Math.Min (_needsDisplay.X, region.X); - var y = Math.Min (_needsDisplay.Y, region.Y); - var w = Math.Max (_needsDisplay.Width, region.Width); - var h = Math.Max (_needsDisplay.Height, region.Height); - _needsDisplay = new Rect (x, y, w, h); - } - _superView?.SetSubViewNeedsDisplay (); - - if (_subviews == null) - return; - - foreach (var view in _subviews) - if (view.Frame.IntersectsWith (region)) { - var childRegion = Rect.Intersect (view.Frame, region); - childRegion.X -= view.Frame.X; - childRegion.Y -= view.Frame.Y; - view.SetNeedsDisplay (childRegion); - } - } - - private Rect GetNeedsDisplayRectScreen (Rect containerBounds) - { - Rect rect = ViewToScreen (_needsDisplay); - if (!containerBounds.IsEmpty) { - rect.Width = Math.Min (_needsDisplay.Width, containerBounds.Width); - rect.Height = Math.Min (_needsDisplay.Height, containerBounds.Height); - } - - return rect; - } - - - internal bool _childNeedsDisplay { get; private set; } - - /// - /// Indicates that any Subviews (in the list) need to be repainted. - /// - public void SetSubViewNeedsDisplay () - { - if (_childNeedsDisplay) { - return; - } - _childNeedsDisplay = true; - if (_superView != null && !_superView._childNeedsDisplay) - _superView.SetSubViewNeedsDisplay (); - } - - internal bool _addingView; - - /// - /// Adds a subview (child) to this view. - /// - /// - /// The Views that have been added to this view can be retrieved via the property. - /// See also - /// - public virtual void Add (View view) - { - if (view == null) { - return; - } - if (_subviews == null) { - _subviews = new List (); - } - if (_tabIndexes == null) { - _tabIndexes = new List (); - } - _subviews.Add (view); - _tabIndexes.Add (view); - view._superView = this; - if (view.CanFocus) { - _addingView = true; - if (SuperView?.CanFocus == false) { - SuperView._addingView = true; - SuperView.CanFocus = true; - SuperView._addingView = false; - } - CanFocus = true; - view._tabIndex = _tabIndexes.IndexOf (view); - _addingView = false; - } - if (view.Enabled && !Enabled) { - view._oldEnabled = true; - view.Enabled = false; - } - SetNeedsLayout (); - SetNeedsDisplay (); - - - OnAdded (new SuperViewChangedEventArgs (this, view)); - if (IsInitialized && !view.IsInitialized) { - view.BeginInit (); - view.EndInit (); - } - } - - /// - /// Adds the specified views (children) to the view. - /// - /// Array of one or more views (can be optional parameter). - /// - /// The Views that have been added to this view can be retrieved via the property. - /// See also - /// - public void Add (params View [] views) - { - if (views == null) { - return; - } - foreach (var view in views) { - Add (view); - } - } - - /// - /// Removes all subviews (children) added via or from this View. - /// - public virtual void RemoveAll () - { - if (_subviews == null) { - return; - } - - while (_subviews.Count > 0) { - Remove (_subviews [0]); - } - } - - /// - /// Removes a subview added via or from this View. - /// - /// - /// - public virtual void Remove (View view) - { - if (view == null || _subviews == null) return; - - var touched = view.Frame; - _subviews.Remove (view); - _tabIndexes.Remove (view); - view._superView = null; - view._tabIndex = -1; - SetNeedsLayout (); - SetNeedsDisplay (); - - foreach (var v in _subviews) { - if (v.Frame.IntersectsWith (touched)) - view.SetNeedsDisplay (); - } - OnRemoved (new SuperViewChangedEventArgs (this, view)); - if (_focused == view) { - _focused = null; - } - } - - void PerformActionForSubview (View subview, Action action) - { - if (_subviews.Contains (subview)) { - action (subview); - } - - SetNeedsDisplay (); - subview.SetNeedsDisplay (); - } - - /// - /// Brings the specified subview to the front so it is drawn on top of any other views. - /// - /// The subview to send to the front - /// - /// . - /// - public void BringSubviewToFront (View subview) - { - PerformActionForSubview (subview, x => { - _subviews.Remove (x); - _subviews.Add (x); - }); - } - - /// - /// Sends the specified subview to the front so it is the first view drawn - /// - /// The subview to send to the front - /// - /// . - /// - public void SendSubviewToBack (View subview) - { - PerformActionForSubview (subview, x => { - _subviews.Remove (x); - _subviews.Insert (0, subview); - }); - } - - /// - /// Moves the subview backwards in the hierarchy, only one step - /// - /// The subview to send backwards - /// - /// If you want to send the view all the way to the back use SendSubviewToBack. - /// - public void SendSubviewBackwards (View subview) - { - PerformActionForSubview (subview, x => { - var idx = _subviews.IndexOf (x); - if (idx > 0) { - _subviews.Remove (x); - _subviews.Insert (idx - 1, x); - } - }); - } - - /// - /// Moves the subview backwards in the hierarchy, only one step - /// - /// The subview to send backwards - /// - /// If you want to send the view all the way to the back use SendSubviewToBack. - /// - public void BringSubviewForward (View subview) - { - PerformActionForSubview (subview, x => { - var idx = _subviews.IndexOf (x); - if (idx + 1 < _subviews.Count) { - _subviews.Remove (x); - _subviews.Insert (idx + 1, x); - } - }); - } - - /// - /// Clears the view region with the current color. - /// - /// - /// - /// This clears the entire region used by this view. - /// - /// - public void Clear () - { - var h = Frame.Height; - var w = Frame.Width; - for (var line = 0; line < h; line++) { - Move (0, line); - for (var col = 0; col < w; col++) - Driver.AddRune (' '); - } - } - - - // BUGBUG: Stupid that this takes screen-relative. We should have a tenet that says - // "View APIs only deal with View-relative coords". - /// - /// Clears the specified region with the current color. - /// - /// - /// - /// The screen-relative region to clear. - public void Clear (Rect regionScreen) - { - var h = regionScreen.Height; - var w = regionScreen.Width; - for (var line = regionScreen.Y; line < regionScreen.Y + h; line++) { - Driver.Move (regionScreen.X, line); - for (var col = 0; col < w; col++) - Driver.AddRune (' '); - } - } - - /// - /// Converts a point from screen-relative coordinates to view-relative coordinates. - /// - /// The mapped point. - /// X screen-coordinate point. - /// Y screen-coordinate point. - public Point ScreenToView (int x, int y) - { - if (SuperView == null) { - return new Point (x - Frame.X, y - _frame.Y); - } else { - var parent = SuperView.ScreenToView (x, y); - return new Point (parent.X - _frame.X, parent.Y - _frame.Y); - } - } - - /// - /// Converts a point from screen-relative coordinates to bounds-relative coordinates. - /// - /// The mapped point. - /// X screen-coordinate point. - /// Y screen-coordinate point. - public Point ScreenToBounds (int x, int y) - { - if (SuperView == null) { - var boundsOffset = GetBoundsOffset (); - return new Point (x - Frame.X + boundsOffset.X, y - Frame.Y + boundsOffset.Y); - } else { - var parent = SuperView.ScreenToView (x, y); - return new Point (parent.X - _frame.X, parent.Y - _frame.Y); - } - } - - /// - /// Converts a view-relative location to a screen-relative location (col,row). The output is optionally clamped to the screen dimensions. - /// - /// View-relative column. - /// View-relative row. - /// Absolute column; screen-relative. - /// Absolute row; screen-relative. - /// If , and will be clamped to the - /// screen dimensions (they never be negative and will always be less than to and - /// , respectively. - public virtual void ViewToScreen (int col, int row, out int rcol, out int rrow, bool clamped = true) - { - var boundsOffset = GetBoundsOffset (); - rcol = col + Frame.X + boundsOffset.X; - rrow = row + Frame.Y + boundsOffset.Y; - - var super = SuperView; - while (super != null) { - boundsOffset = super.GetBoundsOffset (); - rcol += super.Frame.X + boundsOffset.X; - rrow += super.Frame.Y + boundsOffset.Y; - super = super.SuperView; - } - - // The following ensures that the cursor is always in the screen boundaries. - if (clamped) { - rrow = Math.Min (rrow, Driver.Rows - 1); - rcol = Math.Min (rcol, Driver.Cols - 1); - } - } - - /// - /// Converts a region in view-relative coordinates to screen-relative coordinates. - /// - internal Rect ViewToScreen (Rect region) - { - ViewToScreen (region.X, region.Y, out var x, out var y, clamped: false); - return new Rect (x, y, region.Width, region.Height); - } - - // 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); - } - - /// - /// Sets the 's clip region to . - /// - /// The current screen-relative clip region, which can be then re-applied by setting . - /// - /// - /// is View-relative. - /// - /// - /// If and do not intersect, the clip region will be set to . - /// - /// - public Rect ClipToBounds () - { - var clip = Bounds; - - - return SetClip (clip); - } - - // BUGBUG: v2 - SetClip should return VIEW-relative so that it can be used to reset it; using Driver.Clip directly should not be necessary. - /// - /// Sets the clip region to the specified view-relative region. - /// - /// The current screen-relative clip region, which can be then re-applied by setting . - /// View-relative clip region. - /// - /// If and do not intersect, the clip region will be set to . - /// - public Rect SetClip (Rect region) - { - var previous = Driver.Clip; - Driver.Clip = Rect.Intersect (previous, ViewToScreen (region)); - return previous; - } - - /// - /// 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 (ustring text, Attribute hotColor, Attribute normalColor) - { - var hotkeySpec = HotKeySpecifier == (Rune)0xffff ? (Rune)'_' : HotKeySpecifier; - Application.Driver.SetAttribute (normalColor); - foreach (var rune in text) { - if (rune == hotkeySpec) { - Application.Driver.SetAttribute (hotColor); - continue; - } - Application.Driver.AddRune (rune); - Application.Driver.SetAttribute (normalColor); - } - } - - /// - /// 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 (ustring 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); - } - - /// - /// 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. - /// Whether to clip the result of the ViewToScreen method, - /// If , the and values are clamped to the screen (terminal) dimensions (0..TerminalDim-1). - public void Move (int col, int row, bool clipped = true) - { - if (Driver.Rows == 0) { - return; - } - - ViewToScreen (col, row, out var rCol, out var rRow, clipped); - Driver.Move (rCol, rRow); - } - - /// - /// Positions the cursor in the right position based on the currently focused view in the chain. - /// - /// Views that are focusable should override to ensure - /// the cursor is placed in a location that makes sense. Unix terminals do not have - /// a way of hiding the cursor, so it can be distracting to have the cursor left at - /// the last focused view. Views should make sure that they place the cursor - /// in a visually sensible place. - public virtual void PositionCursor () - { - if (!CanBeVisible (this) || !Enabled) { - return; - } - - // BUGBUG: v2 - This needs to support children of Frames too - - if (_focused == null && SuperView != null) { - SuperView.EnsureFocus (); - } else if (_focused?.Visible == true && _focused?.Enabled == true && _focused?.Frame.Width > 0 && _focused.Frame.Height > 0) { - _focused.PositionCursor (); - } else if (_focused?.Visible == true && _focused?.Enabled == false) { - _focused = null; - } else if (CanFocus && HasFocus && Visible && Frame.Width > 0 && Frame.Height > 0) { - Move (TextFormatter.HotKeyPos == -1 ? 0 : TextFormatter.CursorPosition, 0); - } else { - Move (_frame.X, _frame.Y); - } - } - - // BUGBUG: v2 - Seems weird that this is in View and not Responder. - bool _hasFocus; - - /// - public override bool HasFocus => _hasFocus; - - void SetHasFocus (bool value, View view, bool force = false) - { - if (_hasFocus != value || force) { - _hasFocus = value; - if (value) { - OnEnter (view); - } else { - OnLeave (view); - } - SetNeedsDisplay (); - } - - // Remove focus down the chain of subviews if focus is removed - if (!value && _focused != null) { - var f = _focused; - f.OnLeave (view); - f.SetHasFocus (false, view); - _focused = null; - } - } - - /// - /// Method invoked when a subview is being added to this view. - /// - /// Event where is the subview being added. - public virtual void OnAdded (SuperViewChangedEventArgs e) - { - var view = e.Child; - view.IsAdded = true; - view.OnResizeNeeded (); - view._x ??= view._frame.X; - view._y ??= view._frame.Y; - view._width ??= view._frame.Width; - view._height ??= view._frame.Height; - - view.Added?.Invoke (this, e); - } - - /// - /// Method invoked when a subview is being removed from this view. - /// - /// Event args describing the subview being removed. - public virtual void OnRemoved (SuperViewChangedEventArgs e) - { - var view = e.Child; - view.IsAdded = false; - view.Removed?.Invoke (this, e); - } - - /// - public override bool OnEnter (View view) - { - var args = new FocusEventArgs (view); - Enter?.Invoke (this, args); - if (args.Handled) - return true; - if (base.OnEnter (view)) - return true; - - return false; - } - - /// - public override bool OnLeave (View view) - { - var args = new FocusEventArgs (view); - Leave?.Invoke (this, args); - if (args.Handled) - return true; - if (base.OnLeave (view)) - return true; - - return false; - } - - /// - /// Returns the currently focused view inside this view, or null if nothing is focused. - /// - /// The focused. - public View Focused => _focused; - - /// - /// Returns the most focused view in the chain of subviews (the leaf view that has the focus). - /// - /// The most focused View. - public View MostFocused { - get { - if (Focused == null) - return null; - var most = Focused.MostFocused; - if (most != null) - return most; - return Focused; - } - } - - 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 (); - } - } - } - - /// - /// 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); - } - - /// - /// Removes the and the setting on this view. - /// - protected void ClearNeedsDisplay () - { - _needsDisplay = Rect.Empty; - _childNeedsDisplay = 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 virtual LineCanvas LineCanvas { get; set; } = 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 - /// - /// - /// - /// - public virtual bool OnDrawFrames () - { - if (!IsInitialized) { - return false; - } - - var prevClip = Driver.Clip; - Driver.Clip = ViewToScreen (Frame); - - // TODO: Figure out what we should do if we have no superview - //if (SuperView != null) { - // TODO: Clipping is disabled for now to ensure we see errors - Driver.Clip = new Rect (0, 0, Driver.Cols, Driver.Rows);// screenBounds;// SuperView.ClipToBounds (); - //} - - // Each of these renders lines to either this View's LineCanvas - // Those lines will be finally rendered in OnRenderLineCanvas - Margin?.Redraw (Margin.Frame); - Border?.Redraw (Border.Frame); - Padding?.Redraw (Padding.Frame); - - Driver.Clip = prevClip; - - return true; - } - - /// - /// Redraws this view and its subviews; only redraws the views that have been flagged for a re-display. - /// - /// The bounds (view-relative region) to redraw. - /// - /// - /// 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 parameter, as this will cause the driver to clip the entire region. - /// - /// - public virtual void Redraw (Rect bounds) - { - if (!CanBeVisible (this)) { - return; - } - - OnDrawFrames (); - - var prevClip = ClipToBounds (); - - if (ColorScheme != null) { - //Driver.SetAttribute (HasFocus ? GetFocusColor () : GetNormalColor ()); - Driver.SetAttribute (GetNormalColor ()); - } - - if (SuperView != null) { - Clear (ViewToScreen (bounds)); - } - - // Invoke DrawContentEvent - OnDrawContent (bounds); - - // Draw subviews - // TODO: Implement OnDrawSubviews (cancelable); - if (_subviews != null) { - foreach (var view in _subviews) { - if (view.Visible) { //!view._needsDisplay.IsEmpty || view._childNeedsDisplay || view.LayoutNeeded) { - if (true) { //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.Redraw (view.Bounds); - //} - } - view.ClearNeedsDisplay (); - } - } - } - - // Invoke DrawContentCompleteEvent - OnDrawContentComplete (bounds); - Driver.Clip = prevClip; - - OnRenderLineCanvas (); - - // BUGBUG: v2 - We should be able to use View.SetClip here and not have to resort to knowing Driver details. - ClearLayoutNeeded (); - ClearNeedsDisplay (); - } - - internal void OnRenderLineCanvas () - { - //Driver.SetAttribute (new Attribute(Color.White, Color.Black)); - - // 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?.Value ?? ColorScheme.Normal); - Driver.Move (p.Key.X, p.Key.Y); - Driver.AddRune (p.Value.Rune.Value); - } - LineCanvas.Clear (); - } - - if (Subviews.Select (s => s.SuperViewRendersLineCanvas).Count () > 0) { - foreach (var subview in Subviews.Where (s => s.SuperViewRendersLineCanvas)) { - // Combine the LineCavas' - LineCanvas.Merge (subview.LineCanvas); - subview.LineCanvas.Clear (); - } - - foreach (var p in LineCanvas.GetCellMap ()) { // Get the entire map - Driver.SetAttribute (p.Value.Attribute?.Value ?? ColorScheme.Normal); - Driver.Move (p.Key.X, p.Key.Y); - Driver.AddRune (p.Value.Rune.Value); - } - 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) - { - // TODO: Make DrawContent a cancelable event - // if (!DrawContent?.Invoke(this, new DrawEventArgs (viewport)) { - DrawContent?.Invoke (this, new DrawEventArgs (contentArea)); - - if (!ustring.IsNullOrEmpty (TextFormatter.Text)) { - if (TextFormatter != null) { - TextFormatter.NeedsFormat = true; - } - // This should NOT clear - TextFormatter?.Draw (ViewToScreen (contentArea), 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 viewport) - { - DrawContentComplete?.Invoke (this, new DrawEventArgs (viewport)); - } - - /// - /// Causes the specified subview to have focus. - /// - /// View. - void SetFocus (View view) - { - if (view == null) { - return; - } - //Console.WriteLine ($"Request to focus {view}"); - if (!view.CanFocus || !view.Visible || !view.Enabled) { - return; - } - if (_focused?._hasFocus == true && _focused == view) { - return; - } - if ((_focused?._hasFocus == true && _focused?.SuperView == view) || view == this) { - - if (!view._hasFocus) { - view._hasFocus = true; - } - return; - } - // Make sure that this view is a subview - View c; - for (c = view._superView; c != null; c = c._superView) - if (c == this) - break; - if (c == null) - throw new ArgumentException ("the specified view is not part of the hierarchy of this view"); - - if (_focused != null) - _focused.SetHasFocus (false, view); - - var f = _focused; - _focused = view; - _focused.SetHasFocus (true, f); - _focused.EnsureFocus (); - - // Send focus upwards - if (SuperView != null) { - SuperView.SetFocus (this); - } else { - SetFocus (this); - } - } - - /// - /// Causes the specified view and the entire parent hierarchy to have the focused order updated. - /// - public void SetFocus () - { - if (!CanBeVisible (this) || !Enabled) { - if (HasFocus) { - SetHasFocus (false, this); - } - return; - } - - if (SuperView != null) { - SuperView.SetFocus (this); - } else { - SetFocus (this); - } - } - - /// - /// Invoked when a character key is pressed and occurs after the key up event. - /// - public event EventHandler KeyPress; - - /// - public override bool ProcessKey (KeyEvent keyEvent) - { - if (!Enabled) { - return false; - } - - var args = new KeyEventEventArgs (keyEvent); - KeyPress?.Invoke (this, args); - if (args.Handled) - return true; - if (Focused?.Enabled == true) { - Focused?.KeyPress?.Invoke (this, args); - if (args.Handled) - return true; - } - - return Focused?.Enabled == true && Focused?.ProcessKey (keyEvent) == true; - } - - /// - /// Invokes any binding that is registered on this - /// and matches the - /// - /// The key event passed. - protected bool? InvokeKeybindings (KeyEvent keyEvent) - { - bool? toReturn = null; - - if (KeyBindings.ContainsKey (keyEvent.Key)) { - - foreach (var command in KeyBindings [keyEvent.Key]) { - - if (!CommandImplementations.ContainsKey (command)) { - throw new NotSupportedException ($"A KeyBinding was set up for the command {command} ({keyEvent.Key}) but that command is not supported by this View ({GetType ().Name})"); - } - - // each command has its own return value - var thisReturn = CommandImplementations [command] (); - - // if we haven't got anything yet, the current command result should be used - if (toReturn == null) { - toReturn = thisReturn; - } - - // if ever see a true then that's what we will return - if (thisReturn ?? false) { - toReturn = true; - } - } - } - - return toReturn; - } - - - /// - /// Adds a new key combination that will trigger the given - /// (if supported by the View - see ) - /// - /// If the key is already bound to a different it will be - /// rebound to this one - /// Commands are only ever applied to the current (i.e. this feature - /// cannot be used to switch focus to another view and perform multiple commands there) - /// - /// - /// The command(s) to run on the when is pressed. - /// When specifying multiple commands, all commands will be applied in sequence. The bound strike - /// will be consumed if any took effect. - public void AddKeyBinding (Key key, params Command [] command) - { - if (command.Length == 0) { - throw new ArgumentException ("At least one command must be specified", nameof (command)); - } - - if (KeyBindings.ContainsKey (key)) { - KeyBindings [key] = command; - } else { - KeyBindings.Add (key, command); - } - } - - /// - /// Replaces a key combination already bound to . - /// - /// The key to be replaced. - /// The new key to be used. - protected void ReplaceKeyBinding (Key fromKey, Key toKey) - { - if (KeyBindings.ContainsKey (fromKey)) { - var value = KeyBindings [fromKey]; - KeyBindings.Remove (fromKey); - KeyBindings [toKey] = value; - } - } - - /// - /// Checks if the key binding already exists. - /// - /// The key to check. - /// If the key already exist, otherwise. - public bool ContainsKeyBinding (Key key) - { - return KeyBindings.ContainsKey (key); - } - - /// - /// Removes all bound keys from the View and resets the default bindings. - /// - public void ClearKeybindings () - { - KeyBindings.Clear (); - } - - /// - /// Clears the existing keybinding (if any) for the given . - /// - /// - public void ClearKeybinding (Key key) - { - KeyBindings.Remove (key); - } - - /// - /// Removes all key bindings that trigger the given command. Views can have multiple different - /// keys bound to the same command and this method will clear all of them. - /// - /// - public void ClearKeybinding (params Command [] command) - { - foreach (var kvp in KeyBindings.Where (kvp => kvp.Value.SequenceEqual (command)).ToArray ()) { - KeyBindings.Remove (kvp.Key); - } - } - - /// - /// States that the given supports a given - /// and what to perform to make that command happen - /// - /// If the already has an implementation the - /// will replace the old one - /// - /// The command. - /// The function. - protected void AddCommand (Command command, Func f) - { - // if there is already an implementation of this command - if (CommandImplementations.ContainsKey (command)) { - // replace that implementation - CommandImplementations [command] = f; - } else { - // else record how to perform the action (this should be the normal case) - CommandImplementations.Add (command, f); - } - } - - /// - /// Returns all commands that are supported by this . - /// - /// - public IEnumerable GetSupportedCommands () - { - return CommandImplementations.Keys; - } - - /// - /// Gets the key used by a command. - /// - /// The command to search. - /// The used by a - public Key GetKeyFromCommand (params Command [] command) - { - return KeyBindings.First (kb => kb.Value.SequenceEqual (command)).Key; - } - - /// - public override bool ProcessHotKey (KeyEvent keyEvent) - { - if (!Enabled) { - return false; - } - - var args = new KeyEventEventArgs (keyEvent); - if (MostFocused?.Enabled == true) { - MostFocused?.KeyPress?.Invoke (this, args); - if (args.Handled) - return true; - } - if (MostFocused?.Enabled == true && MostFocused?.ProcessKey (keyEvent) == true) - return true; - if (_subviews == null || _subviews.Count == 0) - return false; - - foreach (var view in _subviews) - if (view.Enabled && view.ProcessHotKey (keyEvent)) - return true; - return false; - } - - /// - public override bool ProcessColdKey (KeyEvent keyEvent) - { - if (!Enabled) { - return false; - } - - var args = new KeyEventEventArgs (keyEvent); - KeyPress?.Invoke (this, args); - if (args.Handled) - return true; - if (MostFocused?.Enabled == true) { - MostFocused?.KeyPress?.Invoke (this, args); - if (args.Handled) - return true; - } - if (MostFocused?.Enabled == true && MostFocused?.ProcessKey (keyEvent) == true) - return true; - if (_subviews == null || _subviews.Count == 0) - return false; - - foreach (var view in _subviews) - if (view.Enabled && view.ProcessColdKey (keyEvent)) - return true; - return false; - } - - /// - /// Invoked when a key is pressed. - /// - public event EventHandler KeyDown; - - /// - public override bool OnKeyDown (KeyEvent keyEvent) - { - if (!Enabled) { - return false; - } - - var args = new KeyEventEventArgs (keyEvent); - KeyDown?.Invoke (this, args); - if (args.Handled) { - return true; - } - if (Focused?.Enabled == true) { - Focused.KeyDown?.Invoke (this, args); - if (args.Handled) { - return true; - } - if (Focused?.OnKeyDown (keyEvent) == true) { - return true; - } - } - - return false; - } - - /// - /// Invoked when a key is released. - /// - public event EventHandler KeyUp; - - /// - public override bool OnKeyUp (KeyEvent keyEvent) - { - if (!Enabled) { - return false; - } - - var args = new KeyEventEventArgs (keyEvent); - KeyUp?.Invoke (this, args); - if (args.Handled) { - return true; - } - if (Focused?.Enabled == true) { - Focused.KeyUp?.Invoke (this, args); - if (args.Handled) { - return true; - } - if (Focused?.OnKeyUp (keyEvent) == true) { - return true; - } - } - - return false; - } - - /// - /// Finds the first view in the hierarchy that wants to get the focus if nothing is currently focused, otherwise, does nothing. - /// - public void EnsureFocus () - { - if (_focused == null && _subviews?.Count > 0) { - if (FocusDirection == Direction.Forward) { - FocusFirst (); - } else { - FocusLast (); - } - } - } - - /// - /// Focuses the first focusable subview if one exists. - /// - public void FocusFirst () - { - if (!CanBeVisible (this)) { - return; - } - - if (_tabIndexes == null) { - SuperView?.SetFocus (this); - return; - } - - foreach (var view in _tabIndexes) { - if (view.CanFocus && view._tabStop && view.Visible && view.Enabled) { - SetFocus (view); - return; - } - } - } - - /// - /// Focuses the last focusable subview if one exists. - /// - public void FocusLast () - { - if (!CanBeVisible (this)) { - return; - } - - if (_tabIndexes == null) { - SuperView?.SetFocus (this); - return; - } - - for (var i = _tabIndexes.Count; i > 0;) { - i--; - - var v = _tabIndexes [i]; - if (v.CanFocus && v._tabStop && v.Visible && v.Enabled) { - SetFocus (v); - return; - } - } - } - - /// - /// Focuses the previous view. - /// - /// if previous was focused, otherwise. - public bool FocusPrev () - { - if (!CanBeVisible (this)) { - return false; - } - - FocusDirection = Direction.Backward; - if (_tabIndexes == null || _tabIndexes.Count == 0) - return false; - - if (_focused == null) { - FocusLast (); - return _focused != null; - } - - var focusedIdx = -1; - for (var i = _tabIndexes.Count; i > 0;) { - i--; - var w = _tabIndexes [i]; - - if (w.HasFocus) { - if (w.FocusPrev ()) - return true; - focusedIdx = i; - continue; - } - if (w.CanFocus && focusedIdx != -1 && w._tabStop && w.Visible && w.Enabled) { - _focused.SetHasFocus (false, w); - - if (w.CanFocus && w._tabStop && w.Visible && w.Enabled) - w.FocusLast (); - - SetFocus (w); - return true; - } - } - if (_focused != null) { - _focused.SetHasFocus (false, this); - _focused = null; - } - return false; - } - - /// - /// Focuses the next view. - /// - /// if next was focused, otherwise. - public bool FocusNext () - { - if (!CanBeVisible (this)) { - return false; - } - - FocusDirection = Direction.Forward; - if (_tabIndexes == null || _tabIndexes.Count == 0) - return false; - - if (_focused == null) { - FocusFirst (); - return _focused != null; - } - var focusedIdx = -1; - for (var i = 0; i < _tabIndexes.Count; i++) { - var w = _tabIndexes [i]; - - if (w.HasFocus) { - if (w.FocusNext ()) - return true; - focusedIdx = i; - continue; - } - if (w.CanFocus && focusedIdx != -1 && w._tabStop && w.Visible && w.Enabled) { - _focused.SetHasFocus (false, w); - - if (w.CanFocus && w._tabStop && w.Visible && w.Enabled) - w.FocusFirst (); - - SetFocus (w); - return true; - } - } - if (_focused != null) { - _focused.SetHasFocus (false, this); - _focused = null; - } - return false; - } - - View GetMostFocused (View view) - { - if (view == null) { - return null; - } - - return view._focused != null ? GetMostFocused (view._focused) : view; - } - - /// - /// Sets the View's to the frame-relative coordinates if its container. The - /// container size and location are specified by and are relative to the - /// View's superview. - /// - /// The supserview-relative rectangle describing View's container (nominally the - /// same as this.SuperView.Frame). - internal void SetRelativeLayout (Rect superviewFrame) - { - int newX, newW, newY, newH; - var autosize = Size.Empty; - - if (AutoSize) { - // Note this is global to this function and used as such within the local functions defined - // below. In v2 AutoSize will be re-factored to not need to be dealt with in this function. - autosize = GetAutoSize (); - } - - // Returns the new dimension (width or height) and location (x or y) for the View given - // the superview's Frame.X or Frame.Y - // the superview's width or height - // the current Pos (View.X or View.Y) - // the current Dim (View.Width or View.Height) - (int newLocation, int newDimension) GetNewLocationAndDimension (int superviewLocation, int superviewDimension, Pos pos, Dim dim, int autosizeDimension) - { - int newDimension, newLocation; - - switch (pos) { - case Pos.PosCenter: - if (dim == null) { - newDimension = AutoSize ? autosizeDimension : superviewDimension; - } else { - newDimension = dim.Anchor (superviewDimension); - newDimension = AutoSize && autosizeDimension > newDimension ? autosizeDimension : newDimension; - } - newLocation = pos.Anchor (superviewDimension - newDimension); - break; - - case Pos.PosCombine combine: - int left, right; - (left, newDimension) = GetNewLocationAndDimension (superviewLocation, superviewDimension, combine.left, dim, autosizeDimension); - (right, newDimension) = GetNewLocationAndDimension (superviewLocation, superviewDimension, combine.right, dim, autosizeDimension); - if (combine.add) { - newLocation = left + right; - } else { - newLocation = left - right; - } - newDimension = Math.Max (CalculateNewDimension (dim, newLocation, superviewDimension, autosizeDimension), 0); - break; - - case Pos.PosAbsolute: - case Pos.PosAnchorEnd: - case Pos.PosFactor: - case Pos.PosFunc: - case Pos.PosView: - default: - newLocation = pos?.Anchor (superviewDimension) ?? 0; - newDimension = Math.Max (CalculateNewDimension (dim, newLocation, superviewDimension, autosizeDimension), 0); - break; - } - return (newLocation, newDimension); - } - - // Recursively calculates the new dimension (width or height) of the given Dim given: - // the current location (x or y) - // the current dimension (width or height) - int CalculateNewDimension (Dim d, int location, int dimension, int autosize) - { - int newDimension; - switch (d) { - case null: - newDimension = AutoSize ? autosize : dimension; - break; - case Dim.DimCombine combine: - int leftNewDim = CalculateNewDimension (combine.left, location, dimension, autosize); - int rightNewDim = CalculateNewDimension (combine.right, location, dimension, autosize); - if (combine.add) { - newDimension = leftNewDim + rightNewDim; - } else { - newDimension = leftNewDim - rightNewDim; - } - newDimension = AutoSize && autosize > newDimension ? autosize : newDimension; - break; - - case Dim.DimFactor factor when !factor.IsFromRemaining (): - newDimension = d.Anchor (dimension); - newDimension = AutoSize && autosize > newDimension ? autosize : newDimension; - break; - - case Dim.DimFill: - default: - newDimension = Math.Max (d.Anchor (dimension - location), 0); - newDimension = AutoSize && autosize > newDimension ? autosize : newDimension; - break; - } - - return newDimension; - } - - - // horizontal - (newX, newW) = GetNewLocationAndDimension (superviewFrame.X, superviewFrame.Width, _x, _width, autosize.Width); - - // vertical - (newY, newH) = GetNewLocationAndDimension (superviewFrame.Y, superviewFrame.Height, _y, _height, autosize.Height); - - var r = new Rect (newX, newY, newW, newH); - if (Frame != r) { - Frame = r; - // BUGBUG: Why is this AFTER setting Frame? Seems duplicative. - if (!SetMinWidthHeight ()) { - TextFormatter.Size = GetSizeNeededForTextAndHotKey (); - } - } - } - - /// - /// Fired after the View's method has completed. - /// - /// - /// Subscribe to this event to perform tasks when the has been resized or the layout has otherwise changed. - /// - public event EventHandler LayoutStarted; - - /// - /// Raises the event. Called from before any subviews have been laid out. - /// - internal virtual void OnLayoutStarted (LayoutEventArgs args) - { - LayoutStarted?.Invoke (this, args); - } - - /// - /// Fired after the View's method has completed. - /// - /// - /// Subscribe to this event to perform tasks when the has been resized or the layout has otherwise changed. - /// - public event EventHandler LayoutComplete; - - /// - /// Event called only once when the is being initialized for the first time. - /// Allows configurations and assignments to be performed before the being shown. - /// This derived from to allow notify all the views that are being initialized. - /// - public event EventHandler Initialized; - - /// - /// Raises the event. Called from before all sub-views have been laid out. - /// - internal virtual void OnLayoutComplete (LayoutEventArgs args) - { - LayoutComplete?.Invoke (this, args); - } - - internal void CollectPos (Pos pos, View from, ref HashSet nNodes, ref HashSet<(View, View)> nEdges) - { - switch (pos) { - case Pos.PosView pv: - // See #2461 - //if (!from.InternalSubviews.Contains (pv.Target)) { - // throw new InvalidOperationException ($"View {pv.Target} is not a subview of {from}"); - //} - if (pv.Target != this) { - nEdges.Add ((pv.Target, from)); - } - return; - case Pos.PosCombine pc: - CollectPos (pc.left, from, ref nNodes, ref nEdges); - CollectPos (pc.right, from, ref nNodes, ref nEdges); - break; - } - } - - internal void CollectDim (Dim dim, View from, ref HashSet nNodes, ref HashSet<(View, View)> nEdges) - { - switch (dim) { - case Dim.DimView dv: - // See #2461 - //if (!from.InternalSubviews.Contains (dv.Target)) { - // throw new InvalidOperationException ($"View {dv.Target} is not a subview of {from}"); - //} - if (dv.Target != this) { - nEdges.Add ((dv.Target, from)); - } - return; - case Dim.DimCombine dc: - CollectDim (dc.left, from, ref nNodes, ref nEdges); - CollectDim (dc.right, from, ref nNodes, ref nEdges); - break; - } - } - - internal void CollectAll (View from, ref HashSet nNodes, ref HashSet<(View, View)> nEdges) - { - foreach (var v in from.InternalSubviews) { - nNodes.Add (v); - if (v._layoutStyle != LayoutStyle.Computed) { - continue; - } - CollectPos (v.X, v, ref nNodes, ref nEdges); - CollectPos (v.Y, v, ref nNodes, ref nEdges); - CollectDim (v.Width, v, ref nNodes, ref nEdges); - CollectDim (v.Height, v, ref nNodes, ref nEdges); - } - } - - // https://en.wikipedia.org/wiki/Topological_sorting - internal static List TopologicalSort (View superView, IEnumerable nodes, ICollection<(View From, View To)> edges) - { - var result = new List (); - - // Set of all nodes with no incoming edges - var noEdgeNodes = new HashSet (nodes.Where (n => edges.All (e => !e.To.Equals (n)))); - - - while (noEdgeNodes.Any ()) { - // remove a node n from S - var n = noEdgeNodes.First (); - noEdgeNodes.Remove (n); - - // add n to tail of L - if (n != superView) - result.Add (n); - - // for each node m with an edge e from n to m do - foreach (var e in edges.Where (e => e.From.Equals (n)).ToArray ()) { - var m = e.To; - - // remove edge e from the graph - edges.Remove (e); - - // if m has no other incoming edges then - if (edges.All (me => !me.To.Equals (m)) && m != superView) { - // insert m into S - noEdgeNodes.Add (m); - } - } - } - - if (edges.Any ()) { - foreach ((var from, var to) in edges) { - if (from == to) { - // if not yet added to the result, add it and remove from edge - if (result.Find (v => v == from) == null) { - result.Add (from); - } - edges.Remove ((from, to)); - } else if (from.SuperView == to.SuperView) { - // if 'from' is not yet added to the result, add it - if (result.Find (v => v == from) == null) { - result.Add (from); - } - // if 'to' is not yet added to the result, add it - if (result.Find (v => v == to) == null) { - result.Add (to); - } - // remove from edge - edges.Remove ((from, to)); - } else if (from != superView?.GetTopSuperView (to, from) && !ReferenceEquals (from, to)) { - if (ReferenceEquals (from.SuperView, to)) { - throw new InvalidOperationException ($"ComputedLayout for \"{superView}\": \"{to}\" references a SubView (\"{from}\")."); - } else { - throw new InvalidOperationException ($"ComputedLayout for \"{superView}\": \"{from}\" linked with \"{to}\" was not found. Did you forget to add it to {superView}?"); - } - } - } - } - // return L (a topologically sorted order) - return result; - } // TopologicalSort - - /// - /// Overriden by to do nothing, as the does not have frames. - /// - internal virtual void LayoutFrames () - { - if (Margin == null) return; // CreateFrames() has not been called yet - - if (Margin.Frame.Size != Frame.Size) { - Margin._frame = new Rect (Point.Empty, Frame.Size); - Margin.X = 0; - Margin.Y = 0; - Margin.Width = Frame.Size.Width; - Margin.Height = Frame.Size.Height; - Margin.SetNeedsLayout (); - Margin.LayoutSubviews (); - Margin.SetNeedsDisplay (); - } - - var border = Margin.Thickness.GetInside (Margin.Frame); - if (border != Border.Frame) { - Border._frame = new Rect (new Point (border.Location.X, border.Location.Y), border.Size); - Border.X = border.Location.X; - Border.Y = border.Location.Y; - Border.Width = border.Size.Width; - Border.Height = border.Size.Height; - Border.SetNeedsLayout (); - Border.LayoutSubviews (); - Border.SetNeedsDisplay (); - } - - var padding = Border.Thickness.GetInside (Border.Frame); - if (padding != Padding.Frame) { - Padding._frame = new Rect (new Point (padding.Location.X, padding.Location.Y), padding.Size); - Padding.X = padding.Location.X; - Padding.Y = padding.Location.Y; - Padding.Width = padding.Size.Width; - Padding.Height = padding.Size.Height; - Padding.SetNeedsLayout (); - Padding.LayoutSubviews (); - Padding.SetNeedsDisplay (); - } - } - - /// - /// Invoked when a view starts executing or when the dimensions of the view have changed, for example in - /// response to the container view or terminal resizing. - /// - /// - /// Calls (which raises the event) before it returns. - /// - public virtual void LayoutSubviews () - { - if (!LayoutNeeded) { - return; - } - - LayoutFrames (); - - var oldBounds = Bounds; - OnLayoutStarted (new LayoutEventArgs () { OldBounds = oldBounds }); - - TextFormatter.Size = GetSizeNeededForTextAndHotKey (); - - // Sort out the dependencies of the X, Y, Width, Height properties - var nodes = new HashSet (); - var edges = new HashSet<(View, View)> (); - CollectAll (this, ref nodes, ref edges); - var ordered = View.TopologicalSort (SuperView, nodes, edges); - foreach (var v in ordered) { - LayoutSubview (v, new Rect (GetBoundsOffset (), Bounds.Size)); - } - - // If the 'to' is rooted to 'from' and the layoutstyle is Computed it's a special-case. - // Use LayoutSubview with the Frame of the 'from' - if (SuperView != null && GetTopSuperView () != null && LayoutNeeded && edges.Count > 0) { - foreach ((var from, var to) in edges) { - LayoutSubview (to, from.Frame); - } - } - - LayoutNeeded = false; - - OnLayoutComplete (new LayoutEventArgs () { OldBounds = oldBounds }); - } - - private void LayoutSubview (View v, Rect contentArea) - { - if (v.LayoutStyle == LayoutStyle.Computed) { - v.SetRelativeLayout (contentArea); - } - - v.LayoutSubviews (); - v.LayoutNeeded = false; - } - - ustring _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 ustring Text { - get => _text; - set { - _text = value; - SetHotKey (); - UpdateTextFormatterText (); - //TextFormatter.Format (); - OnResizeNeeded (); - -#if DEBUG - if (_text != null && string.IsNullOrEmpty (Id)) { - Id = _text.ToString (); - } -#endif - } - } - - /// - /// Gets or sets a flag that determines whether the View will be automatically resized to fit the - /// within - /// - /// The default is . Set to to turn on AutoSize. If then - /// and will be used if can fit; - /// if won't fit the view will be resized as needed. - /// - /// - /// In addition, if is the new values of and - /// must be of the same types of the existing one to avoid breaking the settings. - /// - /// - public virtual bool AutoSize { - get => _autoSize; - set { - var v = ResizeView (value); - TextFormatter.AutoSize = v; - if (_autoSize != v) { - _autoSize = v; - TextFormatter.NeedsFormat = true; - UpdateTextFormatterText (); - OnResizeNeeded (); - } - } - } - - /// - /// 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 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; - } - } - - private void UpdateTextDirection (TextDirection newDirection) - { - var directionChanged = TextFormatter.IsHorizontalDirection (TextFormatter.Direction) - != TextFormatter.IsHorizontalDirection (newDirection); - TextFormatter.Direction = newDirection; - - var isValidOldAutoSize = AutoSize && IsValidAutoSize (out var _); - - UpdateTextFormatterText (); - - if ((!ForceValidatePosDim && directionChanged && AutoSize) - || (ForceValidatePosDim && directionChanged && AutoSize && isValidOldAutoSize)) { - OnResizeNeeded (); - } else if (directionChanged && IsAdded) { - SetWidthHeight (Bounds.Size); - SetMinWidthHeight (); - } else { - SetMinWidthHeight (); - } - TextFormatter.Size = GetSizeNeededForTextAndHotKey (); - SetNeedsDisplay (); - } - - /// - /// 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; } - - /// - /// Gets information if the view was already added to the . - /// - public bool IsAdded { get; private set; } - - 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 { - 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; - } - } - } - } - } - } - - /// - /// 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 (); - } - } - } - - /// - /// Gets or sets whether the view has a one row/col thick border. - /// - /// - /// - /// This is a helper for manipulating the view's . Setting this property to any value other than - /// is equivalent to setting 's - /// to `1` and to the value. - /// - /// - /// Setting this property to is equivalent to setting 's - /// to `0` and to . - /// - /// - /// For more advanced customization of the view's border, manipulate see directly. - /// - /// - public LineStyle BorderStyle { - get { - return Border?.BorderStyle ?? LineStyle.None; - } - set { - if (Border == null) { - throw new InvalidOperationException ("Border is null; this is likely a bug."); - } - if (value != LineStyle.None) { - Border.Thickness = new Thickness (1); - } else { - Border.Thickness = new Thickness (0); - } - Border.BorderStyle = value; - LayoutFrames (); - SetNeedsLayout (); - } - } - - /// - /// Pretty prints the View - /// - /// - public override string ToString () + /// + public override string ToString () { return $"{GetType ().Name}({Id})({Frame})"; } - void SetHotKey () - { - if (TextFormatter == null) { - return; // throw new InvalidOperationException ("Can't set HotKey unless a TextFormatter has been created"); - } - TextFormatter.FindHotKey (_text, HotKeySpecifier, true, out _, out var hk); - if (_hotKey != hk) { - HotKey = hk; - } - } - - bool ResizeView (bool autoSize) - { - if (!autoSize) { - return false; - } - - var aSize = true; - var nBoundsSize = GetAutoSize (); - if (nBoundsSize != Bounds.Size) { - if (ForceValidatePosDim) { - aSize = SetWidthHeight (nBoundsSize); - } else { - Height = nBoundsSize.Height; - Width = nBoundsSize.Width; // = new Rect (Bounds.X, Bounds.Y, nBoundsSize.Width, nBoundsSize.Height); - } - } - // BUGBUG: This call may be redundant - TextFormatter.Size = GetSizeNeededForTextAndHotKey (); - return aSize; - } - - /// - /// Resizes the View to fit the specified size. - /// - /// - /// - bool SetWidthHeight (Size nBounds) - { - var aSize = false; - var canSizeW = TrySetWidth (nBounds.Width - GetHotKeySpecifierLength (), out var rW); - var canSizeH = TrySetHeight (nBounds.Height - GetHotKeySpecifierLength (false), out var rH); - if (canSizeW) { - aSize = true; - _width = rW; - } - if (canSizeH) { - aSize = true; - _height = rH; - } - if (aSize) { - Bounds = new Rect (Bounds.X, Bounds.Y, canSizeW ? rW : Bounds.Width, canSizeH ? rH : Bounds.Height); - } - - return aSize; - } - - /// - /// Gets the Frame dimensions required to fit using the text specified by the - /// property and accounting for any characters. - /// - /// The required to fit the text. - public Size GetAutoSize () - { - var rect = TextFormatter.CalcRect (Bounds.X, Bounds.Y, TextFormatter.Text, TextFormatter.Direction); - var newWidth = rect.Size.Width - GetHotKeySpecifierLength () + Margin.Thickness.Horizontal + Border.Thickness.Horizontal + Padding.Thickness.Horizontal; - var newHeight = rect.Size.Height - GetHotKeySpecifierLength (false) + Margin.Thickness.Vertical + Border.Thickness.Vertical + Padding.Thickness.Vertical; - return new Size (newWidth, newHeight); - } - - bool IsValidAutoSize (out Size autoSize) - { - var rect = TextFormatter.CalcRect (_frame.X, _frame.Y, TextFormatter.Text, TextDirection); - autoSize = new Size (rect.Size.Width - GetHotKeySpecifierLength (), - rect.Size.Height - GetHotKeySpecifierLength (false)); - return !(ForceValidatePosDim && (!(Width is Dim.DimAbsolute) || !(Height is Dim.DimAbsolute)) - || _frame.Size.Width != rect.Size.Width - GetHotKeySpecifierLength () - || _frame.Size.Height != rect.Size.Height - GetHotKeySpecifierLength (false)); - } - - bool IsValidAutoSizeWidth (Dim width) - { - var rect = TextFormatter.CalcRect (_frame.X, _frame.Y, TextFormatter.Text, TextDirection); - var dimValue = width.Anchor (0); - return !(ForceValidatePosDim && (!(width is Dim.DimAbsolute)) || dimValue != rect.Size.Width - - GetHotKeySpecifierLength ()); - } - - bool IsValidAutoSizeHeight (Dim height) - { - var rect = TextFormatter.CalcRect (_frame.X, _frame.Y, TextFormatter.Text, TextDirection); - var dimValue = height.Anchor (0); - return !(ForceValidatePosDim && (!(height is Dim.DimAbsolute)) || dimValue != rect.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 (HotKeySpecifier) == true - ? Math.Max (Rune.ColumnWidth (HotKeySpecifier), 0) : 0; - } else { - return TextFormatter.IsVerticalDirection (TextDirection) && - TextFormatter.Text?.Contains (HotKeySpecifier) == true - ? Math.Max (Rune.ColumnWidth (HotKeySpecifier), 0) : 0; - } - } - - /// - /// Gets the dimensions required for ignoring a . - /// - /// - public Size GetSizeNeededForTextWithoutHotKey () - { - return new Size (TextFormatter.Size.Width - GetHotKeySpecifierLength (), - TextFormatter.Size.Height - GetHotKeySpecifierLength (false)); - } - - /// - /// Gets the dimensions required for accounting for a . - /// - /// - public Size GetSizeNeededForTextAndHotKey () - { - if (ustring.IsNullOrEmpty (TextFormatter.Text)) { - - if (!IsInitialized) return Size.Empty; - - 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 (_frame.Size.Width + GetHotKeySpecifierLength (), - _frame.Size.Height + GetHotKeySpecifierLength (false)); - } - - /// - public override bool OnMouseEnter (MouseEvent mouseEvent) - { - if (!Enabled) { - return true; - } - - if (!CanBeVisible (this)) { - return false; - } - - var args = new MouseEventEventArgs (mouseEvent); - MouseEnter?.Invoke (this, args); - - return args.Handled || base.OnMouseEnter (mouseEvent); - } - - /// - public override bool OnMouseLeave (MouseEvent mouseEvent) - { - if (!Enabled) { - return true; - } - - if (!CanBeVisible (this)) { - return false; - } - - var args = new MouseEventEventArgs (mouseEvent); - MouseLeave?.Invoke (this, args); - - return args.Handled || base.OnMouseLeave (mouseEvent); - } - - /// - /// Method invoked when a mouse event is generated - /// - /// - /// , if the event was handled, otherwise. - public virtual bool OnMouseEvent (MouseEvent mouseEvent) - { - if (!Enabled) { - return true; - } - - if (!CanBeVisible (this)) { - return false; - } - - var args = new MouseEventEventArgs (mouseEvent); - if (OnMouseClick (args)) - return true; - if (MouseEvent (mouseEvent)) - return true; - - if (mouseEvent.Flags == MouseFlags.Button1Clicked) { - if (CanFocus && !HasFocus && SuperView != null) { - SuperView.SetFocus (this); - SetNeedsDisplay (); - } - - return true; - } - return false; - } - - /// - /// Invokes the MouseClick event. - /// - protected bool OnMouseClick (MouseEventEventArgs args) - { - if (!Enabled) { - return true; - } - - MouseClick?.Invoke (this, args); - return args.Handled; - } - - /// - public override void OnCanFocusChanged () => CanFocusChanged?.Invoke (this, EventArgs.Empty); - - /// - public override void OnEnabledChanged () => EnabledChanged?.Invoke (this, EventArgs.Empty); - - /// - public override void OnVisibleChanged () => VisibleChanged?.Invoke (this, EventArgs.Empty); - /// protected override void Dispose (bool disposing) { @@ -3349,248 +508,5 @@ protected override void Dispose (bool disposing) } base.Dispose (disposing); } - - /// - /// 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; - - 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."); - - } - - 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 (); - } - } - } - Initialized?.Invoke (this, EventArgs.Empty); - } - - bool CanBeVisible (View view) - { - if (!view.Visible) { - return false; - } - for (var c = view.SuperView; c != null; c = c.SuperView) { - if (!c.Visible) { - return false; - } - } - - return true; - } - - /// - /// Determines if the View's can be set to a new value. - /// - /// - /// Contains the width that would result if were set to "/> - /// if the View's can be changed to the specified value. False otherwise. - internal bool TrySetWidth (int desiredWidth, out int resultWidth) - { - var w = desiredWidth; - bool canSetWidth; - switch (Width) { - case Dim.DimCombine _: - case Dim.DimView _: - case Dim.DimFill _: - // It's a Dim.DimCombine and so can't be assigned. Let it have it's Width anchored. - w = Width.Anchor (w); - canSetWidth = !ForceValidatePosDim; - break; - case Dim.DimFactor factor: - // Tries to get the SuperView Width otherwise the view Width. - var sw = SuperView != null ? SuperView.Frame.Width : w; - if (factor.IsFromRemaining ()) { - sw -= Frame.X; - } - w = Width.Anchor (sw); - canSetWidth = !ForceValidatePosDim; - break; - default: - canSetWidth = true; - break; - } - resultWidth = w; - - return canSetWidth; - } - - /// - /// Determines if the View's can be set to a new value. - /// - /// - /// Contains the width that would result if were set to "/> - /// if the View's can be changed to the specified value. False otherwise. - internal bool TrySetHeight (int desiredHeight, out int resultHeight) - { - var h = desiredHeight; - bool canSetHeight; - switch (Height) { - case Dim.DimCombine _: - case Dim.DimView _: - case Dim.DimFill _: - // It's a Dim.DimCombine and so can't be assigned. Let it have it's height anchored. - h = Height.Anchor (h); - canSetHeight = !ForceValidatePosDim; - break; - case Dim.DimFactor factor: - // Tries to get the SuperView height otherwise the view height. - var sh = SuperView != null ? SuperView.Frame.Height : h; - if (factor.IsFromRemaining ()) { - sh -= Frame.Y; - } - h = Height.Anchor (sh); - canSetHeight = !ForceValidatePosDim; - break; - default: - canSetHeight = true; - break; - } - resultHeight = h; - - return canSetHeight; - } - - /// - /// Determines the current based on the value. - /// - /// if is - /// or if is . - /// If it's overridden can return other values. - public virtual Attribute GetNormalColor () - { - return Enabled ? ColorScheme.Normal : 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 () - { - 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 GetHotNormalColor () - { - return Enabled ? ColorScheme.HotNormal : ColorScheme.Disabled; - } - - /// - /// Get the top superview of a given . - /// - /// The superview view. - public View GetTopSuperView (View view = null, View superview = null) - { - View top = superview ?? Application.Top; - for (var v = view?.SuperView ?? (this?.SuperView); v != null; v = v.SuperView) { - top = v; - if (top == superview) { - break; - } - } - - return top; - } - - /// - /// Finds which view that belong to the superview at the provided location. - /// - /// The superview where to look for. - /// The column location in the superview. - /// The row location in the superview. - /// The found view screen relative column location. - /// The found view screen relative row location. - /// - /// The view that was found at the and coordinates. - /// if no view was found. - /// - public static View FindDeepestView (View start, int x, int y, out int resx, out int resy) - { - var startFrame = start.Frame; - - if (!startFrame.Contains (x, y)) { - resx = 0; - resy = 0; - return null; - } - if (start.InternalSubviews != null) { - int count = start.InternalSubviews.Count; - if (count > 0) { - var boundsOffset = start.GetBoundsOffset (); - var rx = x - (startFrame.X + boundsOffset.X); - var ry = y - (startFrame.Y + boundsOffset.Y); - for (int i = count - 1; i >= 0; i--) { - View v = start.InternalSubviews [i]; - if (v.Visible && v.Frame.Contains (rx, ry)) { - var deep = FindDeepestView (v, rx, ry, out resx, out resy); - if (deep == null) - return v; - return deep; - } - } - } - } - resx = x - startFrame.X; - resy = y - startFrame.Y; - return start; - } } } diff --git a/Terminal.Gui/View/ViewDrawing.cs b/Terminal.Gui/View/ViewDrawing.cs new file mode 100644 index 0000000000..502a6db07b --- /dev/null +++ b/Terminal.Gui/View/ViewDrawing.cs @@ -0,0 +1,492 @@ +using System; +using System.Linq; +using NStack; + +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 (); + } + } + } + + /// + /// Determines the current based on the value. + /// + /// if is + /// or if is . + /// If it's overridden can return other values. + public virtual Attribute GetNormalColor () + { + return Enabled ? ColorScheme.Normal : 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 () + { + 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 GetHotNormalColor () + { + return 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; + if (row > _frame.Height - 1 || col > _frame.Width - 1) + return; + Move (col, row); + Driver.AddRune (ch); + } + + /// + /// Removes the and the setting on this view. + /// + protected void ClearNeedsDisplay () + { + _needsDisplay = Rect.Empty; + _childNeedsDisplay = false; + } + + // The view-relative region that needs to be redrawn + internal Rect _needsDisplay { get; private set; } = Rect.Empty; + + /// + /// Sets a flag indicating this view needs to be redisplayed because its state has changed. + /// + public void SetNeedsDisplay () + { + if (!IsInitialized) return; + SetNeedsDisplay (Bounds); + } + + /// + /// Flags the view-relative region on this View as needing to be redrawn. + /// + /// The view-relative region that needs to be redrawn. + public void SetNeedsDisplay (Rect region) + { + if (_needsDisplay.IsEmpty) + _needsDisplay = region; + else { + var x = Math.Min (_needsDisplay.X, region.X); + var y = Math.Min (_needsDisplay.Y, region.Y); + var w = Math.Max (_needsDisplay.Width, region.Width); + var h = Math.Max (_needsDisplay.Height, region.Height); + _needsDisplay = new Rect (x, y, w, h); + } + _superView?.SetSubViewNeedsDisplay (); + + if (_subviews == null) + return; + + foreach (var view in _subviews) + if (view.Frame.IntersectsWith (region)) { + var childRegion = Rect.Intersect (view.Frame, region); + childRegion.X -= view.Frame.X; + childRegion.Y -= view.Frame.Y; + view.SetNeedsDisplay (childRegion); + } + } + + internal bool _childNeedsDisplay { get; private set; } + + /// + /// Indicates that any Subviews (in the list) need to be repainted. + /// + public void SetSubViewNeedsDisplay () + { + if (_childNeedsDisplay) { + return; + } + _childNeedsDisplay = true; + if (_superView != null && !_superView._childNeedsDisplay) + _superView.SetSubViewNeedsDisplay (); + } + + /// + /// Clears the view region with the current color. + /// + /// + /// + /// This clears the entire region used by this view. + /// + /// + public void Clear () + { + var h = Frame.Height; + var w = Frame.Width; + for (var line = 0; line < h; line++) { + Move (0, line); + for (var col = 0; col < w; col++) + Driver.AddRune (' '); + } + } + + // BUGBUG: Stupid that this takes screen-relative. We should have a tenet that says + // "View APIs only deal with View-relative coords". + /// + /// Clears the specified region with the current color. + /// + /// + /// + /// The screen-relative region to clear. + public void Clear (Rect regionScreen) + { + var h = regionScreen.Height; + var w = regionScreen.Width; + for (var line = regionScreen.Y; line < regionScreen.Y + h; line++) { + Driver.Move (regionScreen.X, line); + for (var col = 0; col < w; col++) + Driver.AddRune (' '); + } + } + + // 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); + } + + /// + /// Sets the 's clip region to . + /// + /// The current screen-relative clip region, which can be then re-applied by setting . + /// + /// + /// is View-relative. + /// + /// + /// If and do not intersect, the clip region will be set to . + /// + /// + public Rect ClipToBounds () + { + var clip = Bounds; + + return SetClip (clip); + } + + // BUGBUG: v2 - SetClip should return VIEW-relative so that it can be used to reset it; using Driver.Clip directly should not be necessary. + /// + /// Sets the clip region to the specified view-relative region. + /// + /// The current screen-relative clip region, which can be then re-applied by setting . + /// View-relative clip region. + /// + /// If and do not intersect, the clip region will be set to . + /// + public Rect SetClip (Rect region) + { + var previous = Driver.Clip; + Driver.Clip = Rect.Intersect (previous, ViewToScreen (region)); + return previous; + } + + /// + /// 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 (ustring text, Attribute hotColor, Attribute normalColor) + { + var hotkeySpec = HotKeySpecifier == (Rune)0xffff ? (Rune)'_' : HotKeySpecifier; + Application.Driver.SetAttribute (normalColor); + foreach (var rune in text) { + if (rune == hotkeySpec) { + Application.Driver.SetAttribute (hotColor); + continue; + } + Application.Driver.AddRune (rune); + Application.Driver.SetAttribute (normalColor); + } + } + + /// + /// 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 (ustring 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); + } + + /// + /// 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. + /// Whether to clip the result of the ViewToScreen method, + /// If , the and values are clamped to the screen (terminal) dimensions (0..TerminalDim-1). + public void Move (int col, int row, bool clipped = true) + { + if (Driver.Rows == 0) { + return; + } + + ViewToScreen (col, row, out var rCol, out var rRow, clipped); + 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 virtual LineCanvas LineCanvas { get; set; } = 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 + /// + /// + /// + /// + public virtual bool OnDrawFrames () + { + if (!IsInitialized) { + return false; + } + + var prevClip = Driver.Clip; + Driver.Clip = ViewToScreen (Frame); + + // TODO: Figure out what we should do if we have no superview + //if (SuperView != null) { + // TODO: Clipping is disabled for now to ensure we see errors + Driver.Clip = new Rect (0, 0, Driver.Cols, Driver.Rows);// screenBounds;// SuperView.ClipToBounds (); + //} + + // Each of these renders lines to either this View's LineCanvas + // Those lines will be finally rendered in OnRenderLineCanvas + Margin?.Redraw (Margin.Frame); + Border?.Redraw (Border.Frame); + Padding?.Redraw (Padding.Frame); + + Driver.Clip = prevClip; + + return true; + } + + /// + /// Redraws this view and its subviews; only redraws the views that have been flagged for a re-display. + /// + /// The bounds (view-relative region) to redraw. + /// + /// + /// 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 parameter, as this will cause the driver to clip the entire region. + /// + /// + public virtual void Redraw (Rect bounds) + { + if (!CanBeVisible (this)) { + return; + } + + OnDrawFrames (); + + var prevClip = ClipToBounds (); + + if (ColorScheme != null) { + //Driver.SetAttribute (HasFocus ? GetFocusColor () : GetNormalColor ()); + Driver.SetAttribute (GetNormalColor ()); + } + + if (SuperView != null) { + Clear (ViewToScreen (bounds)); + } + + // Invoke DrawContentEvent + OnDrawContent (bounds); + + // Draw subviews + // TODO: Implement OnDrawSubviews (cancelable); + if (_subviews != null) { + foreach (var view in _subviews) { + if (view.Visible) { //!view._needsDisplay.IsEmpty || view._childNeedsDisplay || view.LayoutNeeded) { + if (true) { //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.Redraw (view.Bounds); + //} + } + view.ClearNeedsDisplay (); + } + } + } + + // Invoke DrawContentCompleteEvent + OnDrawContentComplete (bounds); + Driver.Clip = prevClip; + + OnRenderLineCanvas (); + + // BUGBUG: v2 - We should be able to use View.SetClip here and not have to resort to knowing Driver details. + ClearLayoutNeeded (); + ClearNeedsDisplay (); + } + + internal void OnRenderLineCanvas () + { + //Driver.SetAttribute (new Attribute(Color.White, Color.Black)); + + // 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?.Value ?? ColorScheme.Normal); + Driver.Move (p.Key.X, p.Key.Y); + Driver.AddRune (p.Value.Rune.Value); + } + LineCanvas.Clear (); + } + + if (Subviews.Select (s => s.SuperViewRendersLineCanvas).Count () > 0) { + foreach (var subview in Subviews.Where (s => s.SuperViewRendersLineCanvas)) { + // Combine the LineCavas' + LineCanvas.Merge (subview.LineCanvas); + subview.LineCanvas.Clear (); + } + + foreach (var p in LineCanvas.GetCellMap ()) { // Get the entire map + Driver.SetAttribute (p.Value.Attribute?.Value ?? ColorScheme.Normal); + Driver.Move (p.Key.X, p.Key.Y); + Driver.AddRune (p.Value.Rune.Value); + } + 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) + { + // TODO: Make DrawContent a cancelable event + // if (!DrawContent?.Invoke(this, new DrawEventArgs (viewport)) { + DrawContent?.Invoke (this, new DrawEventArgs (contentArea)); + + if (!ustring.IsNullOrEmpty (TextFormatter.Text)) { + if (TextFormatter != null) { + TextFormatter.NeedsFormat = true; + } + // This should NOT clear + TextFormatter?.Draw (ViewToScreen (contentArea), 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 viewport) + { + DrawContentComplete?.Invoke (this, new DrawEventArgs (viewport)); + } + + } +} \ No newline at end of file diff --git a/Terminal.Gui/View/ViewKeyboard.cs b/Terminal.Gui/View/ViewKeyboard.cs new file mode 100644 index 0000000000..4f23603d94 --- /dev/null +++ b/Terminal.Gui/View/ViewKeyboard.cs @@ -0,0 +1,464 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using NStack; + +namespace Terminal.Gui { + public partial class View { + ShortcutHelper _shortcutHelper; + + /// + /// Event invoked when the is changed. + /// + public event EventHandler HotKeyChanged; + + Key _hotKey = Key.Null; + + /// + /// Gets or sets the HotKey defined for this view. A user pressing HotKey on the keyboard while this view has focus will cause the Clicked event to fire. + /// + public virtual Key HotKey { + get => _hotKey; + set { + if (_hotKey != value) { + var v = value == Key.Unknown ? Key.Null : value; + if (_hotKey != Key.Null && ContainsKeyBinding (Key.Space | _hotKey)) { + if (v == Key.Null) { + ClearKeybinding (Key.Space | _hotKey); + } else { + ReplaceKeyBinding (Key.Space | _hotKey, Key.Space | v); + } + } else if (v != Key.Null) { + AddKeyBinding (Key.Space | v, Command.Accept); + } + _hotKey = TextFormatter.HotKey = v; + } + } + } + + /// + /// Gets or sets the specifier character for the hotkey (e.g. '_'). Set to '\xffff' to disable hotkey support for this View instance. The default is '\xffff'. + /// + public virtual Rune HotKeySpecifier { + get { + if (TextFormatter != null) { + return TextFormatter.HotKeySpecifier; + } else { + return new Rune ('\xFFFF'); + } + } + set { + TextFormatter.HotKeySpecifier = value; + SetHotKey (); + } + } + + /// + /// This is the global setting that can be used as a global shortcut to invoke an action if provided. + /// + public Key Shortcut { + get => _shortcutHelper.Shortcut; + set { + if (_shortcutHelper.Shortcut != value && (ShortcutHelper.PostShortcutValidation (value) || value == Key.Null)) { + _shortcutHelper.Shortcut = value; + } + } + } + + /// + /// The keystroke combination used in the as string. + /// + public ustring ShortcutTag => ShortcutHelper.GetShortcutTag (_shortcutHelper.Shortcut); + + /// + /// The action to run if the is defined. + /// + public virtual Action ShortcutAction { get; set; } + + // This is null, and allocated on demand. + List _tabIndexes; + + /// + /// Configurable keybindings supported by the control + /// + private Dictionary KeyBindings { get; set; } = new Dictionary (); + private Dictionary> CommandImplementations { get; set; } = new Dictionary> (); + + /// + /// This returns a tab index list of the subviews contained by this view. + /// + /// The tabIndexes. + public IList TabIndexes => _tabIndexes?.AsReadOnly () ?? _empty; + + int _tabIndex = -1; + + /// + /// Indicates the index of the current from the list. + /// + public int TabIndex { + get { return _tabIndex; } + set { + if (!CanFocus) { + _tabIndex = -1; + return; + } else if (SuperView?._tabIndexes == null || SuperView?._tabIndexes.Count == 1) { + _tabIndex = 0; + return; + } else if (_tabIndex == value) { + return; + } + _tabIndex = value > SuperView._tabIndexes.Count - 1 ? SuperView._tabIndexes.Count - 1 : value < 0 ? 0 : value; + _tabIndex = GetTabIndex (_tabIndex); + if (SuperView._tabIndexes.IndexOf (this) != _tabIndex) { + SuperView._tabIndexes.Remove (this); + SuperView._tabIndexes.Insert (_tabIndex, this); + SetTabIndex (); + } + } + } + + int GetTabIndex (int idx) + { + var i = 0; + foreach (var v in SuperView._tabIndexes) { + if (v._tabIndex == -1 || v == this) { + continue; + } + i++; + } + return Math.Min (i, idx); + } + + void SetTabIndex () + { + var i = 0; + foreach (var v in SuperView._tabIndexes) { + if (v._tabIndex == -1) { + continue; + } + v._tabIndex = i; + i++; + } + } + + bool _tabStop = true; + + /// + /// This only be if the is also + /// and the focus can be avoided by setting this to + /// + public bool TabStop { + get => _tabStop; + set { + if (_tabStop == value) { + return; + } + _tabStop = CanFocus && value; + } + } + + int _oldTabIndex; + + /// + /// Invoked when a character key is pressed and occurs after the key up event. + /// + public event EventHandler KeyPress; + + /// + public override bool ProcessKey (KeyEvent keyEvent) + { + if (!Enabled) { + return false; + } + + var args = new KeyEventEventArgs (keyEvent); + KeyPress?.Invoke (this, args); + if (args.Handled) + return true; + if (Focused?.Enabled == true) { + Focused?.KeyPress?.Invoke (this, args); + if (args.Handled) + return true; + } + + return Focused?.Enabled == true && Focused?.ProcessKey (keyEvent) == true; + } + + /// + /// Invokes any binding that is registered on this + /// and matches the + /// + /// The key event passed. + protected bool? InvokeKeybindings (KeyEvent keyEvent) + { + bool? toReturn = null; + + if (KeyBindings.ContainsKey (keyEvent.Key)) { + + foreach (var command in KeyBindings [keyEvent.Key]) { + + if (!CommandImplementations.ContainsKey (command)) { + throw new NotSupportedException ($"A KeyBinding was set up for the command {command} ({keyEvent.Key}) but that command is not supported by this View ({GetType ().Name})"); + } + + // each command has its own return value + var thisReturn = CommandImplementations [command] (); + + // if we haven't got anything yet, the current command result should be used + if (toReturn == null) { + toReturn = thisReturn; + } + + // if ever see a true then that's what we will return + if (thisReturn ?? false) { + toReturn = true; + } + } + } + + return toReturn; + } + + /// + /// Adds a new key combination that will trigger the given + /// (if supported by the View - see ) + /// + /// If the key is already bound to a different it will be + /// rebound to this one + /// Commands are only ever applied to the current (i.e. this feature + /// cannot be used to switch focus to another view and perform multiple commands there) + /// + /// + /// The command(s) to run on the when is pressed. + /// When specifying multiple commands, all commands will be applied in sequence. The bound strike + /// will be consumed if any took effect. + public void AddKeyBinding (Key key, params Command [] command) + { + if (command.Length == 0) { + throw new ArgumentException ("At least one command must be specified", nameof (command)); + } + + if (KeyBindings.ContainsKey (key)) { + KeyBindings [key] = command; + } else { + KeyBindings.Add (key, command); + } + } + + /// + /// Replaces a key combination already bound to . + /// + /// The key to be replaced. + /// The new key to be used. + protected void ReplaceKeyBinding (Key fromKey, Key toKey) + { + if (KeyBindings.ContainsKey (fromKey)) { + var value = KeyBindings [fromKey]; + KeyBindings.Remove (fromKey); + KeyBindings [toKey] = value; + } + } + + /// + /// Checks if the key binding already exists. + /// + /// The key to check. + /// If the key already exist, otherwise. + public bool ContainsKeyBinding (Key key) + { + return KeyBindings.ContainsKey (key); + } + + /// + /// Removes all bound keys from the View and resets the default bindings. + /// + public void ClearKeybindings () + { + KeyBindings.Clear (); + } + + /// + /// Clears the existing keybinding (if any) for the given . + /// + /// + public void ClearKeybinding (Key key) + { + KeyBindings.Remove (key); + } + + /// + /// Removes all key bindings that trigger the given command. Views can have multiple different + /// keys bound to the same command and this method will clear all of them. + /// + /// + public void ClearKeybinding (params Command [] command) + { + foreach (var kvp in KeyBindings.Where (kvp => kvp.Value.SequenceEqual (command)).ToArray ()) { + KeyBindings.Remove (kvp.Key); + } + } + + /// + /// States that the given supports a given + /// and what to perform to make that command happen + /// + /// If the already has an implementation the + /// will replace the old one + /// + /// The command. + /// The function. + protected void AddCommand (Command command, Func f) + { + // if there is already an implementation of this command + if (CommandImplementations.ContainsKey (command)) { + // replace that implementation + CommandImplementations [command] = f; + } else { + // else record how to perform the action (this should be the normal case) + CommandImplementations.Add (command, f); + } + } + + /// + /// Returns all commands that are supported by this . + /// + /// + public IEnumerable GetSupportedCommands () + { + return CommandImplementations.Keys; + } + + /// + /// Gets the key used by a command. + /// + /// The command to search. + /// The used by a + public Key GetKeyFromCommand (params Command [] command) + { + return KeyBindings.First (kb => kb.Value.SequenceEqual (command)).Key; + } + + /// + public override bool ProcessHotKey (KeyEvent keyEvent) + { + if (!Enabled) { + return false; + } + + var args = new KeyEventEventArgs (keyEvent); + if (MostFocused?.Enabled == true) { + MostFocused?.KeyPress?.Invoke (this, args); + if (args.Handled) + return true; + } + if (MostFocused?.Enabled == true && MostFocused?.ProcessKey (keyEvent) == true) + return true; + if (_subviews == null || _subviews.Count == 0) + return false; + + foreach (var view in _subviews) + if (view.Enabled && view.ProcessHotKey (keyEvent)) + return true; + return false; + } + + /// + public override bool ProcessColdKey (KeyEvent keyEvent) + { + if (!Enabled) { + return false; + } + + var args = new KeyEventEventArgs (keyEvent); + KeyPress?.Invoke (this, args); + if (args.Handled) + return true; + if (MostFocused?.Enabled == true) { + MostFocused?.KeyPress?.Invoke (this, args); + if (args.Handled) + return true; + } + if (MostFocused?.Enabled == true && MostFocused?.ProcessKey (keyEvent) == true) + return true; + if (_subviews == null || _subviews.Count == 0) + return false; + + foreach (var view in _subviews) + if (view.Enabled && view.ProcessColdKey (keyEvent)) + return true; + return false; + } + + /// + /// Invoked when a key is pressed. + /// + public event EventHandler KeyDown; + + /// + public override bool OnKeyDown (KeyEvent keyEvent) + { + if (!Enabled) { + return false; + } + + var args = new KeyEventEventArgs (keyEvent); + KeyDown?.Invoke (this, args); + if (args.Handled) { + return true; + } + if (Focused?.Enabled == true) { + Focused.KeyDown?.Invoke (this, args); + if (args.Handled) { + return true; + } + if (Focused?.OnKeyDown (keyEvent) == true) { + return true; + } + } + + return false; + } + + /// + /// Invoked when a key is released. + /// + public event EventHandler KeyUp; + + /// + public override bool OnKeyUp (KeyEvent keyEvent) + { + if (!Enabled) { + return false; + } + + var args = new KeyEventEventArgs (keyEvent); + KeyUp?.Invoke (this, args); + if (args.Handled) { + return true; + } + if (Focused?.Enabled == true) { + Focused.KeyUp?.Invoke (this, args); + if (args.Handled) { + return true; + } + if (Focused?.OnKeyUp (keyEvent) == true) { + return true; + } + } + + return false; + } + + void SetHotKey () + { + if (TextFormatter == null) { + return; // throw new InvalidOperationException ("Can't set HotKey unless a TextFormatter has been created"); + } + TextFormatter.FindHotKey (_text, HotKeySpecifier, true, out _, out var hk); + if (_hotKey != hk) { + HotKey = hk; + } + } + } +} diff --git a/Terminal.Gui/View/ViewLayout.cs b/Terminal.Gui/View/ViewLayout.cs new file mode 100644 index 0000000000..187d3cb6c5 --- /dev/null +++ b/Terminal.Gui/View/ViewLayout.cs @@ -0,0 +1,1157 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using NStack; + +namespace Terminal.Gui { + /// + /// Determines the LayoutStyle for a , if Absolute, during , the + /// value from the will be used, if the value is Computed, then + /// will be updated from the X, Y objects and the Width and Height objects. + /// + public enum LayoutStyle { + /// + /// The position and size of the view are based . + /// + Absolute, + + /// + /// The position and size of the view will be computed based on + /// , , , and . will + /// provide the absolute computed values. + /// + Computed + } + + public partial class View { + + // The frame for the object. Superview relative. + Rect _frame; + + /// + /// Gets or sets the frame for the view. The frame is relative to the view's container (). + /// + /// The frame. + /// + /// + /// 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 . + /// + /// + public virtual Rect Frame { + get => _frame; + set { + _frame = new Rect (value.X, value.Y, Math.Max (value.Width, 0), Math.Max (value.Height, 0)); + if (IsInitialized || LayoutStyle == LayoutStyle.Absolute) { + LayoutFrames (); + TextFormatter.Size = GetSizeNeededForTextAndHotKey (); + SetNeedsLayout (); + SetNeedsDisplay (); + } + } + } + + /// + /// The Thickness that separates a View from other SubViews of the same SuperView. + /// The Margin is not part of the View's content and is not clipped by the View's Clip Area. + /// + public Frame Margin { get; private set; } + + /// + /// Thickness where a visual border (drawn using line-drawing glyphs) and the Title are drawn. + /// The Border expands inward; in other words if `Border.Thickness.Top == 2` the border and + /// title will take up the first row and the second row will be filled with spaces. + /// The Border is not part of the View's content and is not clipped by the View's `ClipArea`. + /// + /// + /// provides a simple helper for turning a simple border frame on or off. + /// + public Frame Border { get; private set; } + + /// + /// Gets or sets whether the view has a one row/col thick border. + /// + /// + /// + /// This is a helper for manipulating the view's . Setting this property to any value other than + /// is equivalent to setting 's + /// to `1` and to the value. + /// + /// + /// Setting this property to is equivalent to setting 's + /// to `0` and to . + /// + /// + /// For more advanced customization of the view's border, manipulate see directly. + /// + /// + public LineStyle BorderStyle { + get { + return Border?.BorderStyle ?? LineStyle.None; + } + set { + if (Border == null) { + throw new InvalidOperationException ("Border is null; this is likely a bug."); + } + if (value != LineStyle.None) { + Border.Thickness = new Thickness (1); + } else { + Border.Thickness = new Thickness (0); + } + Border.BorderStyle = value; + LayoutFrames (); + SetNeedsLayout (); + } + } + + /// + /// Means the Thickness inside of an element that offsets the `Content` from the Border. + /// Padding is `{0, 0, 0, 0}` by default. Padding is not part of the View's content and is not clipped by the View's `ClipArea`. + /// + /// + /// (NOTE: in v1 `Padding` is OUTSIDE of the `Border`). + /// + public Frame Padding { get; private set; } + + /// + /// Helper to get the total thickness of the , , and . + /// + /// A thickness that describes the sum of the Frames' thicknesses. + public Thickness GetFramesThickness () + { + var left = Margin.Thickness.Left + Border.Thickness.Left + Padding.Thickness.Left; + var top = Margin.Thickness.Top + Border.Thickness.Top + Padding.Thickness.Top; + var right = Margin.Thickness.Right + Border.Thickness.Right + Padding.Thickness.Right; + var bottom = Margin.Thickness.Bottom + Border.Thickness.Bottom + Padding.Thickness.Bottom; + return new Thickness (left, top, right, bottom); + } + + /// + /// 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); + + /// + /// Creates the view's objects. This internal method is overridden by Frame to do nothing + /// to prevent recursion during View construction. + /// + internal virtual void CreateFrames () + { + void ThicknessChangedHandler (object sender, EventArgs e) + { + LayoutFrames (); + SetNeedsLayout (); + SetNeedsDisplay (); + } + + if (Margin != null) { + Margin.ThicknessChanged -= ThicknessChangedHandler; + Margin.Dispose (); + } + Margin = new Frame () { Id = "Margin", Thickness = new Thickness (0) }; + Margin.ThicknessChanged += ThicknessChangedHandler; + Margin.Parent = this; + + if (Border != null) { + Border.ThicknessChanged -= ThicknessChangedHandler; + Border.Dispose (); + } + Border = new Frame () { Id = "Border", Thickness = new Thickness (0) }; + Border.ThicknessChanged += ThicknessChangedHandler; + Border.Parent = this; + + // TODO: Create View.AddAdornment + + if (Padding != null) { + Padding.ThicknessChanged -= ThicknessChangedHandler; + Padding.Dispose (); + } + Padding = new Frame () { Id = "Padding", Thickness = new Thickness (0) }; + Padding.ThicknessChanged += ThicknessChangedHandler; + Padding.Parent = this; + } + + LayoutStyle _layoutStyle; + + /// + /// Controls how the View's is computed during the LayoutSubviews method, if the style is set to + /// , + /// LayoutSubviews does not change the . If the style is + /// the is updated using + /// the , , , and properties. + /// + /// The layout style. + public LayoutStyle LayoutStyle { + get => _layoutStyle; + set { + _layoutStyle = value; + SetNeedsLayout (); + } + } + + /// + /// The View-relative rectangle where View content is displayed. SubViews are positioned relative to + /// Bounds.Location (which is always (0, 0)) and clips drawing to + /// Bounds.Size. + /// + /// The bounds. + /// + /// + /// The of Bounds is always (0, 0). To obtain the offset of the Bounds from the Frame use + /// . + /// + /// + public virtual Rect Bounds { + get { +#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. View: {this}"); + } +#endif // DEBUG + var frameRelativeBounds = Padding?.Thickness.GetInside (Padding.Frame) ?? new Rect (default, Frame.Size); + return new Rect (default, frameRelativeBounds.Size); + } + set { + // BUGBUG: Margin etc.. can be null (if typeof(Frame)) + Frame = new Rect (Frame.Location, + new Size ( + value.Size.Width + Margin.Thickness.Horizontal + Border.Thickness.Horizontal + Padding.Thickness.Horizontal, + value.Size.Height + Margin.Thickness.Vertical + Border.Thickness.Vertical + Padding.Thickness.Vertical + ) + ); + } + } + + // Diagnostics to highlight when X or Y is read before the view has been initialized + private Pos VerifyIsIntialized (Pos pos) + { +#if DEBUG + if (LayoutStyle == LayoutStyle.Computed && (!IsInitialized)) { + Debug.WriteLine ($"WARNING: \"{this}\" has not been initialized; position is indeterminate {pos}. This is likely a bug."); + } +#endif // DEBUG + return pos; + } + + // Diagnostics to highlight when Width or Height is read before the view has been initialized + private Dim VerifyIsIntialized (Dim dim) + { +#if DEBUG + if (LayoutStyle == LayoutStyle.Computed && (!IsInitialized)) { + Debug.WriteLine ($"WARNING: \"{this}\" has not been initialized; dimension is indeterminate: {dim}. This is likely a bug."); + } +#endif // DEBUG + return dim; + } + + Pos _x, _y; + + /// + /// Gets or sets the X position for the view (the column). Only used if the is . + /// + /// The X Position. + /// + /// If is changing this property has no effect and its value is indeterminate. + /// + public Pos X { + get => VerifyIsIntialized (_x); + set { + if (ForceValidatePosDim && !ValidatePosDim (_x, value)) { + throw new ArgumentException (); + } + + _x = value; + + OnResizeNeeded (); + } + } + + /// + /// Gets or sets the Y position for the view (the row). Only used if the is . + /// + /// The y position (line). + /// + /// If is changing this property has no effect and its value is indeterminate. + /// + public Pos Y { + get => VerifyIsIntialized (_y); + set { + if (ForceValidatePosDim && !ValidatePosDim (_y, value)) { + throw new ArgumentException (); + } + + _y = value; + + OnResizeNeeded (); + } + } + Dim _width, _height; + + /// + /// Gets or sets the width of the view. Only used the is . + /// + /// The width. + /// + /// If is changing this property has no effect and its value is indeterminate. + /// + public Dim Width { + get => VerifyIsIntialized (_width); + set { + if (ForceValidatePosDim && !ValidatePosDim (_width, value)) { + throw new ArgumentException ("ForceValidatePosDim is enabled", nameof (Width)); + } + + _width = value; + + if (ForceValidatePosDim) { + var isValidNewAutSize = AutoSize && IsValidAutoSizeWidth (_width); + + if (IsAdded && AutoSize && !isValidNewAutSize) { + throw new InvalidOperationException ("Must set AutoSize to false before set the Width."); + } + } + OnResizeNeeded (); + } + } + + /// + /// Gets or sets the height of the view. Only used the is . + /// + /// The height. + /// If is changing this property has no effect and its value is indeterminate. + public Dim Height { + get => VerifyIsIntialized (_height); + set { + if (ForceValidatePosDim && !ValidatePosDim (_height, value)) { + throw new ArgumentException ("ForceValidatePosDim is enabled", nameof (Height)); + } + + _height = value; + + if (ForceValidatePosDim) { + var isValidNewAutSize = AutoSize && IsValidAutoSizeHeight (_height); + + if (IsAdded && AutoSize && !isValidNewAutSize) { + throw new InvalidOperationException ("Must set AutoSize to false before set the Height."); + } + } + OnResizeNeeded (); + } + } + + /// + /// Forces validation with layout + /// to avoid breaking the and settings. + /// + public bool ForceValidatePosDim { get; set; } + + bool ValidatePosDim (object oldValue, object newValue) + { + if (!IsInitialized || _layoutStyle == LayoutStyle.Absolute || oldValue == null || oldValue.GetType () == newValue.GetType () || this is Toplevel) { + return true; + } + if (_layoutStyle == LayoutStyle.Computed) { + if (oldValue.GetType () != newValue.GetType () && !(newValue is Pos.PosAbsolute || newValue is Dim.DimAbsolute)) { + return true; + } + } + return false; + } + + // BUGBUG: This API is broken - It should be renamed to `GetMinimumBoundsForFrame and + // 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. + /// + public bool GetMinimumBounds (out Size size) + { + size = Bounds.Size; + + if (!AutoSize && !ustring.IsNullOrEmpty (TextFormatter.Text)) { + switch (TextFormatter.IsVerticalDirection (TextDirection)) { + case true: + var colWidth = TextFormatter.GetSumMaxCharWidth (new List { TextFormatter.Text }, 0, 1); + // TODO: v2 - This uses frame.Width; it should only use Bounds + if (_frame.Width < colWidth && + (Width == null || + (Bounds.Width >= 0 && + Width is Dim.DimAbsolute && + Width.Anchor (0) >= 0 && + Width.Anchor (0) < colWidth))) { + size = new Size (colWidth, Bounds.Height); + return true; + } + break; + default: + if (_frame.Height < 1 && + (Height == null || + (Height is Dim.DimAbsolute && + Height.Anchor (0) == 0))) { + size = new Size (Bounds.Width, 1); + return true; + } + break; + } + } + return false; + } + + // BUGBUG - v2 - Should be renamed "SetBoundsToFitFrame" + /// + /// Sets the size of the View to the minimum width or height required to fit (see . + /// + /// if the size was changed, if + /// will not fit. + public bool SetMinWidthHeight () + { + if (GetMinimumBounds (out Size size)) { + _frame = new Rect (_frame.Location, size); + return true; + } + return false; + } + + /// + /// Called whenever the view needs to be resized. Sets and + /// triggers a call. /// + /// + /// + /// Can be overridden if the view resize behavior is different than the default. + /// + protected virtual void OnResizeNeeded () + { + var actX = _x is Pos.PosAbsolute ? _x.Anchor (0) : _frame.X; + var actY = _y is Pos.PosAbsolute ? _y.Anchor (0) : _frame.Y; + + if (AutoSize) { + //if (TextAlignment == TextAlignment.Justified) { + // throw new InvalidOperationException ("TextAlignment.Justified cannot be used with AutoSize"); + //} + var s = GetAutoSize (); + var w = _width is Dim.DimAbsolute && _width.Anchor (0) > s.Width ? _width.Anchor (0) : s.Width; + var h = _height is Dim.DimAbsolute && _height.Anchor (0) > s.Height ? _height.Anchor (0) : s.Height; + _frame = new Rect (new Point (actX, actY), new Size (w, h)); // Set frame, not Frame! + } else { + var w = _width is Dim.DimAbsolute ? _width.Anchor (0) : _frame.Width; + var h = _height is Dim.DimAbsolute ? _height.Anchor (0) : _frame.Height; + // BUGBUG: v2 - ? - If layoutstyle is absolute, this overwrites the current frame h/w with 0. Hmmm... + // This is needed for DimAbsolute values by setting the frame before LayoutSubViews. + _frame = new Rect (new Point (actX, actY), new Size (w, h)); // Set frame, not Frame! + } + //// BUGBUG: I think these calls are redundant or should be moved into just the AutoSize case + if (IsInitialized || LayoutStyle == LayoutStyle.Absolute) { + SetMinWidthHeight (); + LayoutFrames (); + TextFormatter.Size = GetSizeNeededForTextAndHotKey (); + SetNeedsLayout (); + SetNeedsDisplay (); + } + } + + internal bool LayoutNeeded { get; private set; } = true; + + internal void SetNeedsLayout () + { + if (LayoutNeeded) + return; + LayoutNeeded = true; + if (SuperView == null) + return; + SuperView.SetNeedsLayout (); + foreach (var view in Subviews) { + view.SetNeedsLayout (); + } + TextFormatter.NeedsFormat = true; + } + + /// + /// Removes the setting on this view. + /// + protected void ClearLayoutNeeded () + { + LayoutNeeded = false; + } + + /// + /// Converts a point from screen-relative coordinates to view-relative coordinates. + /// + /// The mapped point. + /// X screen-coordinate point. + /// Y screen-coordinate point. + public Point ScreenToView (int x, int y) + { + if (SuperView == null) { + return new Point (x - Frame.X, y - _frame.Y); + } else { + var parent = SuperView.ScreenToView (x, y); + return new Point (parent.X - _frame.X, parent.Y - _frame.Y); + } + } + + /// + /// Converts a point from screen-relative coordinates to bounds-relative coordinates. + /// + /// The mapped point. + /// X screen-coordinate point. + /// Y screen-coordinate point. + public Point ScreenToBounds (int x, int y) + { + if (SuperView == null) { + var boundsOffset = GetBoundsOffset (); + return new Point (x - Frame.X + boundsOffset.X, y - Frame.Y + boundsOffset.Y); + } else { + var parent = SuperView.ScreenToView (x, y); + return new Point (parent.X - _frame.X, parent.Y - _frame.Y); + } + } + + /// + /// Converts a view-relative location to a screen-relative location (col,row). The output is optionally clamped to the screen dimensions. + /// + /// View-relative column. + /// View-relative row. + /// Absolute column; screen-relative. + /// Absolute row; screen-relative. + /// If , and will be clamped to the + /// screen dimensions (they never be negative and will always be less than to and + /// , respectively. + public virtual void ViewToScreen (int col, int row, out int rcol, out int rrow, bool clamped = true) + { + var boundsOffset = GetBoundsOffset (); + rcol = col + Frame.X + boundsOffset.X; + rrow = row + Frame.Y + boundsOffset.Y; + + var super = SuperView; + while (super != null) { + boundsOffset = super.GetBoundsOffset (); + rcol += super.Frame.X + boundsOffset.X; + rrow += super.Frame.Y + boundsOffset.Y; + super = super.SuperView; + } + + // The following ensures that the cursor is always in the screen boundaries. + if (clamped) { + rrow = Math.Min (rrow, Driver.Rows - 1); + rcol = Math.Min (rcol, Driver.Cols - 1); + } + } + + /// + /// Converts a region in view-relative coordinates to screen-relative coordinates. + /// + internal Rect ViewToScreen (Rect region) + { + ViewToScreen (region.X, region.Y, out var x, out var y, clamped: false); + return new Rect (x, y, region.Width, region.Height); + } + + + /// + /// Sets the View's to the frame-relative coordinates if its container. The + /// container size and location are specified by and are relative to the + /// View's superview. + /// + /// The supserview-relative rectangle describing View's container (nominally the + /// same as this.SuperView.Frame). + internal void SetRelativeLayout (Rect superviewFrame) + { + int newX, newW, newY, newH; + var autosize = Size.Empty; + + if (AutoSize) { + // Note this is global to this function and used as such within the local functions defined + // below. In v2 AutoSize will be re-factored to not need to be dealt with in this function. + autosize = GetAutoSize (); + } + + // Returns the new dimension (width or height) and location (x or y) for the View given + // the superview's Frame.X or Frame.Y + // the superview's width or height + // the current Pos (View.X or View.Y) + // the current Dim (View.Width or View.Height) + (int newLocation, int newDimension) GetNewLocationAndDimension (int superviewLocation, int superviewDimension, Pos pos, Dim dim, int autosizeDimension) + { + int newDimension, newLocation; + + switch (pos) { + case Pos.PosCenter: + if (dim == null) { + newDimension = AutoSize ? autosizeDimension : superviewDimension; + } else { + newDimension = dim.Anchor (superviewDimension); + newDimension = AutoSize && autosizeDimension > newDimension ? autosizeDimension : newDimension; + } + newLocation = pos.Anchor (superviewDimension - newDimension); + break; + + case Pos.PosCombine combine: + int left, right; + (left, newDimension) = GetNewLocationAndDimension (superviewLocation, superviewDimension, combine.left, dim, autosizeDimension); + (right, newDimension) = GetNewLocationAndDimension (superviewLocation, superviewDimension, combine.right, dim, autosizeDimension); + if (combine.add) { + newLocation = left + right; + } else { + newLocation = left - right; + } + newDimension = Math.Max (CalculateNewDimension (dim, newLocation, superviewDimension, autosizeDimension), 0); + break; + + case Pos.PosAbsolute: + case Pos.PosAnchorEnd: + case Pos.PosFactor: + case Pos.PosFunc: + case Pos.PosView: + default: + newLocation = pos?.Anchor (superviewDimension) ?? 0; + newDimension = Math.Max (CalculateNewDimension (dim, newLocation, superviewDimension, autosizeDimension), 0); + break; + } + return (newLocation, newDimension); + } + + // Recursively calculates the new dimension (width or height) of the given Dim given: + // the current location (x or y) + // the current dimension (width or height) + int CalculateNewDimension (Dim d, int location, int dimension, int autosize) + { + int newDimension; + switch (d) { + case null: + newDimension = AutoSize ? autosize : dimension; + break; + case Dim.DimCombine combine: + int leftNewDim = CalculateNewDimension (combine.left, location, dimension, autosize); + int rightNewDim = CalculateNewDimension (combine.right, location, dimension, autosize); + if (combine.add) { + newDimension = leftNewDim + rightNewDim; + } else { + newDimension = leftNewDim - rightNewDim; + } + newDimension = AutoSize && autosize > newDimension ? autosize : newDimension; + break; + + case Dim.DimFactor factor when !factor.IsFromRemaining (): + newDimension = d.Anchor (dimension); + newDimension = AutoSize && autosize > newDimension ? autosize : newDimension; + break; + + case Dim.DimFill: + default: + newDimension = Math.Max (d.Anchor (dimension - location), 0); + newDimension = AutoSize && autosize > newDimension ? autosize : newDimension; + break; + } + + return newDimension; + } + + // horizontal + (newX, newW) = GetNewLocationAndDimension (superviewFrame.X, superviewFrame.Width, _x, _width, autosize.Width); + + // vertical + (newY, newH) = GetNewLocationAndDimension (superviewFrame.Y, superviewFrame.Height, _y, _height, autosize.Height); + + var r = new Rect (newX, newY, newW, newH); + if (Frame != r) { + Frame = r; + // BUGBUG: Why is this AFTER setting Frame? Seems duplicative. + if (!SetMinWidthHeight ()) { + TextFormatter.Size = GetSizeNeededForTextAndHotKey (); + } + } + } + + /// + /// Fired after the View's method has completed. + /// + /// + /// Subscribe to this event to perform tasks when the has been resized or the layout has otherwise changed. + /// + public event EventHandler LayoutStarted; + + /// + /// Raises the event. Called from before any subviews have been laid out. + /// + internal virtual void OnLayoutStarted (LayoutEventArgs args) + { + LayoutStarted?.Invoke (this, args); + } + + /// + /// Fired after the View's method has completed. + /// + /// + /// Subscribe to this event to perform tasks when the has been resized or the layout has otherwise changed. + /// + public event EventHandler LayoutComplete; + + /// + /// Event called only once when the is being initialized for the first time. + /// Allows configurations and assignments to be performed before the being shown. + /// This derived from to allow notify all the views that are being initialized. + /// + public event EventHandler Initialized; + + /// + /// Raises the event. Called from before all sub-views have been laid out. + /// + internal virtual void OnLayoutComplete (LayoutEventArgs args) + { + LayoutComplete?.Invoke (this, args); + } + + internal void CollectPos (Pos pos, View from, ref HashSet nNodes, ref HashSet<(View, View)> nEdges) + { + switch (pos) { + case Pos.PosView pv: + // See #2461 + //if (!from.InternalSubviews.Contains (pv.Target)) { + // throw new InvalidOperationException ($"View {pv.Target} is not a subview of {from}"); + //} + if (pv.Target != this) { + nEdges.Add ((pv.Target, from)); + } + return; + case Pos.PosCombine pc: + CollectPos (pc.left, from, ref nNodes, ref nEdges); + CollectPos (pc.right, from, ref nNodes, ref nEdges); + break; + } + } + + internal void CollectDim (Dim dim, View from, ref HashSet nNodes, ref HashSet<(View, View)> nEdges) + { + switch (dim) { + case Dim.DimView dv: + // See #2461 + //if (!from.InternalSubviews.Contains (dv.Target)) { + // throw new InvalidOperationException ($"View {dv.Target} is not a subview of {from}"); + //} + if (dv.Target != this) { + nEdges.Add ((dv.Target, from)); + } + return; + case Dim.DimCombine dc: + CollectDim (dc.left, from, ref nNodes, ref nEdges); + CollectDim (dc.right, from, ref nNodes, ref nEdges); + break; + } + } + + internal void CollectAll (View from, ref HashSet nNodes, ref HashSet<(View, View)> nEdges) + { + foreach (var v in from.InternalSubviews) { + nNodes.Add (v); + if (v._layoutStyle != LayoutStyle.Computed) { + continue; + } + CollectPos (v.X, v, ref nNodes, ref nEdges); + CollectPos (v.Y, v, ref nNodes, ref nEdges); + CollectDim (v.Width, v, ref nNodes, ref nEdges); + CollectDim (v.Height, v, ref nNodes, ref nEdges); + } + } + + // https://en.wikipedia.org/wiki/Topological_sorting + internal static List TopologicalSort (View superView, IEnumerable nodes, ICollection<(View From, View To)> edges) + { + var result = new List (); + + // Set of all nodes with no incoming edges + var noEdgeNodes = new HashSet (nodes.Where (n => edges.All (e => !e.To.Equals (n)))); + + while (noEdgeNodes.Any ()) { + // remove a node n from S + var n = noEdgeNodes.First (); + noEdgeNodes.Remove (n); + + // add n to tail of L + if (n != superView) + result.Add (n); + + // for each node m with an edge e from n to m do + foreach (var e in edges.Where (e => e.From.Equals (n)).ToArray ()) { + var m = e.To; + + // remove edge e from the graph + edges.Remove (e); + + // if m has no other incoming edges then + if (edges.All (me => !me.To.Equals (m)) && m != superView) { + // insert m into S + noEdgeNodes.Add (m); + } + } + } + + if (edges.Any ()) { + foreach ((var from, var to) in edges) { + if (from == to) { + // if not yet added to the result, add it and remove from edge + if (result.Find (v => v == from) == null) { + result.Add (from); + } + edges.Remove ((from, to)); + } else if (from.SuperView == to.SuperView) { + // if 'from' is not yet added to the result, add it + if (result.Find (v => v == from) == null) { + result.Add (from); + } + // if 'to' is not yet added to the result, add it + if (result.Find (v => v == to) == null) { + result.Add (to); + } + // remove from edge + edges.Remove ((from, to)); + } else if (from != superView?.GetTopSuperView (to, from) && !ReferenceEquals (from, to)) { + if (ReferenceEquals (from.SuperView, to)) { + throw new InvalidOperationException ($"ComputedLayout for \"{superView}\": \"{to}\" references a SubView (\"{from}\")."); + } else { + throw new InvalidOperationException ($"ComputedLayout for \"{superView}\": \"{from}\" linked with \"{to}\" was not found. Did you forget to add it to {superView}?"); + } + } + } + } + // return L (a topologically sorted order) + return result; + } // TopologicalSort + + /// + /// Overriden by to do nothing, as the does not have frames. + /// + internal virtual void LayoutFrames () + { + if (Margin == null) return; // CreateFrames() has not been called yet + + if (Margin.Frame.Size != Frame.Size) { + Margin._frame = new Rect (Point.Empty, Frame.Size); + Margin.X = 0; + Margin.Y = 0; + Margin.Width = Frame.Size.Width; + Margin.Height = Frame.Size.Height; + Margin.SetNeedsLayout (); + Margin.LayoutSubviews (); + Margin.SetNeedsDisplay (); + } + + var border = Margin.Thickness.GetInside (Margin.Frame); + if (border != Border.Frame) { + Border._frame = new Rect (new Point (border.Location.X, border.Location.Y), border.Size); + Border.X = border.Location.X; + Border.Y = border.Location.Y; + Border.Width = border.Size.Width; + Border.Height = border.Size.Height; + Border.SetNeedsLayout (); + Border.LayoutSubviews (); + Border.SetNeedsDisplay (); + } + + var padding = Border.Thickness.GetInside (Border.Frame); + if (padding != Padding.Frame) { + Padding._frame = new Rect (new Point (padding.Location.X, padding.Location.Y), padding.Size); + Padding.X = padding.Location.X; + Padding.Y = padding.Location.Y; + Padding.Width = padding.Size.Width; + Padding.Height = padding.Size.Height; + Padding.SetNeedsLayout (); + Padding.LayoutSubviews (); + Padding.SetNeedsDisplay (); + } + } + + /// + /// Invoked when a view starts executing or when the dimensions of the view have changed, for example in + /// response to the container view or terminal resizing. + /// + /// + /// Calls (which raises the event) before it returns. + /// + public virtual void LayoutSubviews () + { + if (!LayoutNeeded) { + return; + } + + LayoutFrames (); + + var oldBounds = Bounds; + OnLayoutStarted (new LayoutEventArgs () { OldBounds = oldBounds }); + + TextFormatter.Size = GetSizeNeededForTextAndHotKey (); + + // Sort out the dependencies of the X, Y, Width, Height properties + var nodes = new HashSet (); + var edges = new HashSet<(View, View)> (); + CollectAll (this, ref nodes, ref edges); + var ordered = View.TopologicalSort (SuperView, nodes, edges); + foreach (var v in ordered) { + LayoutSubview (v, new Rect (GetBoundsOffset (), Bounds.Size)); + } + + // If the 'to' is rooted to 'from' and the layoutstyle is Computed it's a special-case. + // Use LayoutSubview with the Frame of the 'from' + if (SuperView != null && GetTopSuperView () != null && LayoutNeeded && edges.Count > 0) { + foreach ((var from, var to) in edges) { + LayoutSubview (to, from.Frame); + } + } + + LayoutNeeded = false; + + OnLayoutComplete (new LayoutEventArgs () { OldBounds = oldBounds }); + } + + private void LayoutSubview (View v, Rect contentArea) + { + if (v.LayoutStyle == LayoutStyle.Computed) { + v.SetRelativeLayout (contentArea); + } + + v.LayoutSubviews (); + v.LayoutNeeded = false; + } + + bool _autoSize; + + /// + /// Gets or sets a flag that determines whether the View will be automatically resized to fit the + /// within + /// + /// The default is . Set to to turn on AutoSize. If then + /// and will be used if can fit; + /// if won't fit the view will be resized as needed. + /// + /// + /// In addition, if is the new values of and + /// must be of the same types of the existing one to avoid breaking the settings. + /// + /// + public virtual bool AutoSize { + get => _autoSize; + set { + var v = ResizeView (value); + TextFormatter.AutoSize = v; + if (_autoSize != v) { + _autoSize = v; + TextFormatter.NeedsFormat = true; + UpdateTextFormatterText (); + OnResizeNeeded (); + } + } + } + + bool ResizeView (bool autoSize) + { + if (!autoSize) { + return false; + } + + var aSize = true; + var nBoundsSize = GetAutoSize (); + if (nBoundsSize != Bounds.Size) { + if (ForceValidatePosDim) { + aSize = SetWidthHeight (nBoundsSize); + } else { + Height = nBoundsSize.Height; + Width = nBoundsSize.Width; // = new Rect (Bounds.X, Bounds.Y, nBoundsSize.Width, nBoundsSize.Height); + } + } + // BUGBUG: This call may be redundant + TextFormatter.Size = GetSizeNeededForTextAndHotKey (); + return aSize; + } + + /// + /// Resizes the View to fit the specified size. + /// + /// + /// + bool SetWidthHeight (Size nBounds) + { + var aSize = false; + var canSizeW = TrySetWidth (nBounds.Width - GetHotKeySpecifierLength (), out var rW); + var canSizeH = TrySetHeight (nBounds.Height - GetHotKeySpecifierLength (false), out var rH); + if (canSizeW) { + aSize = true; + _width = rW; + } + if (canSizeH) { + aSize = true; + _height = rH; + } + if (aSize) { + Bounds = new Rect (Bounds.X, Bounds.Y, canSizeW ? rW : Bounds.Width, canSizeH ? rH : Bounds.Height); + } + + return aSize; + } + + /// + /// Gets the Frame dimensions required to fit using the text specified by the + /// property and accounting for any characters. + /// + /// The required to fit the text. + public Size GetAutoSize () + { + var rect = TextFormatter.CalcRect (Bounds.X, Bounds.Y, TextFormatter.Text, TextFormatter.Direction); + var newWidth = rect.Size.Width - GetHotKeySpecifierLength () + Margin.Thickness.Horizontal + Border.Thickness.Horizontal + Padding.Thickness.Horizontal; + var newHeight = rect.Size.Height - GetHotKeySpecifierLength (false) + Margin.Thickness.Vertical + Border.Thickness.Vertical + Padding.Thickness.Vertical; + return new Size (newWidth, newHeight); + } + + bool IsValidAutoSize (out Size autoSize) + { + var rect = TextFormatter.CalcRect (_frame.X, _frame.Y, TextFormatter.Text, TextDirection); + autoSize = new Size (rect.Size.Width - GetHotKeySpecifierLength (), + rect.Size.Height - GetHotKeySpecifierLength (false)); + return !(ForceValidatePosDim && (!(Width is Dim.DimAbsolute) || !(Height is Dim.DimAbsolute)) + || _frame.Size.Width != rect.Size.Width - GetHotKeySpecifierLength () + || _frame.Size.Height != rect.Size.Height - GetHotKeySpecifierLength (false)); + } + + bool IsValidAutoSizeWidth (Dim width) + { + var rect = TextFormatter.CalcRect (_frame.X, _frame.Y, TextFormatter.Text, TextDirection); + var dimValue = width.Anchor (0); + return !(ForceValidatePosDim && (!(width is Dim.DimAbsolute)) || dimValue != rect.Size.Width + - GetHotKeySpecifierLength ()); + } + + bool IsValidAutoSizeHeight (Dim height) + { + var rect = TextFormatter.CalcRect (_frame.X, _frame.Y, TextFormatter.Text, TextDirection); + var dimValue = height.Anchor (0); + return !(ForceValidatePosDim && (!(height is Dim.DimAbsolute)) || dimValue != rect.Size.Height + - GetHotKeySpecifierLength (false)); + } + + /// + /// Determines if the View's can be set to a new value. + /// + /// + /// Contains the width that would result if were set to "/> + /// if the View's can be changed to the specified value. False otherwise. + internal bool TrySetWidth (int desiredWidth, out int resultWidth) + { + var w = desiredWidth; + bool canSetWidth; + switch (Width) { + case Dim.DimCombine _: + case Dim.DimView _: + case Dim.DimFill _: + // It's a Dim.DimCombine and so can't be assigned. Let it have it's Width anchored. + w = Width.Anchor (w); + canSetWidth = !ForceValidatePosDim; + break; + case Dim.DimFactor factor: + // Tries to get the SuperView Width otherwise the view Width. + var sw = SuperView != null ? SuperView.Frame.Width : w; + if (factor.IsFromRemaining ()) { + sw -= Frame.X; + } + w = Width.Anchor (sw); + canSetWidth = !ForceValidatePosDim; + break; + default: + canSetWidth = true; + break; + } + resultWidth = w; + + return canSetWidth; + } + + /// + /// Determines if the View's can be set to a new value. + /// + /// + /// Contains the width that would result if were set to "/> + /// if the View's can be changed to the specified value. False otherwise. + internal bool TrySetHeight (int desiredHeight, out int resultHeight) + { + var h = desiredHeight; + bool canSetHeight; + switch (Height) { + case Dim.DimCombine _: + case Dim.DimView _: + case Dim.DimFill _: + // It's a Dim.DimCombine and so can't be assigned. Let it have it's height anchored. + h = Height.Anchor (h); + canSetHeight = !ForceValidatePosDim; + break; + case Dim.DimFactor factor: + // Tries to get the SuperView height otherwise the view height. + var sh = SuperView != null ? SuperView.Frame.Height : h; + if (factor.IsFromRemaining ()) { + sh -= Frame.Y; + } + h = Height.Anchor (sh); + canSetHeight = !ForceValidatePosDim; + break; + default: + canSetHeight = true; + break; + } + resultHeight = h; + + return canSetHeight; + } + + /// + /// Finds which view that belong to the superview at the provided location. + /// + /// The superview where to look for. + /// The column location in the superview. + /// The row location in the superview. + /// The found view screen relative column location. + /// The found view screen relative row location. + /// + /// The view that was found at the and coordinates. + /// if no view was found. + /// + public static View FindDeepestView (View start, int x, int y, out int resx, out int resy) + { + var startFrame = start.Frame; + + if (!startFrame.Contains (x, y)) { + resx = 0; + resy = 0; + return null; + } + if (start.InternalSubviews != null) { + int count = start.InternalSubviews.Count; + if (count > 0) { + var boundsOffset = start.GetBoundsOffset (); + var rx = x - (startFrame.X + boundsOffset.X); + var ry = y - (startFrame.Y + boundsOffset.Y); + for (int i = count - 1; i >= 0; i--) { + View v = start.InternalSubviews [i]; + if (v.Visible && v.Frame.Contains (rx, ry)) { + var deep = FindDeepestView (v, rx, ry, out resx, out resy); + if (deep == null) + return v; + return deep; + } + } + } + } + resx = x - startFrame.X; + resy = y - startFrame.Y; + return start; + } + } +} diff --git a/Terminal.Gui/View/ViewMouse.cs b/Terminal.Gui/View/ViewMouse.cs new file mode 100644 index 0000000000..f5e1e8c376 --- /dev/null +++ b/Terminal.Gui/View/ViewMouse.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using NStack; + +namespace Terminal.Gui { + public partial class View { + /// + /// Event fired when the view receives the mouse event for the first time. + /// + public event EventHandler MouseEnter; + + /// + /// Event fired when the view receives a mouse event for the last time. + /// + public event EventHandler MouseLeave; + + /// + /// Event fired when a mouse event is generated. + /// + public event EventHandler MouseClick; + + /// + public override bool OnMouseEnter (MouseEvent mouseEvent) + { + if (!Enabled) { + return true; + } + + if (!CanBeVisible (this)) { + return false; + } + + var args = new MouseEventEventArgs (mouseEvent); + MouseEnter?.Invoke (this, args); + + return args.Handled || base.OnMouseEnter (mouseEvent); + } + + /// + public override bool OnMouseLeave (MouseEvent mouseEvent) + { + if (!Enabled) { + return true; + } + + if (!CanBeVisible (this)) { + return false; + } + + var args = new MouseEventEventArgs (mouseEvent); + MouseLeave?.Invoke (this, args); + + return args.Handled || base.OnMouseLeave (mouseEvent); + } + + /// + /// Method invoked when a mouse event is generated + /// + /// + /// , if the event was handled, otherwise. + public virtual bool OnMouseEvent (MouseEvent mouseEvent) + { + if (!Enabled) { + return true; + } + + if (!CanBeVisible (this)) { + return false; + } + + var args = new MouseEventEventArgs (mouseEvent); + if (OnMouseClick (args)) + return true; + if (MouseEvent (mouseEvent)) + return true; + + if (mouseEvent.Flags == MouseFlags.Button1Clicked) { + if (CanFocus && !HasFocus && SuperView != null) { + SuperView.SetFocus (this); + SetNeedsDisplay (); + } + + return true; + } + return false; + } + + /// + /// Invokes the MouseClick event. + /// + protected bool OnMouseClick (MouseEventEventArgs args) + { + if (!Enabled) { + return true; + } + + MouseClick?.Invoke (this, args); + return args.Handled; + } + + /// + /// Gets or sets a value indicating whether this wants mouse position reports. + /// + /// if want mouse position reports; otherwise, . + public virtual bool WantMousePositionReports { get; set; } + + /// + /// Gets or sets a value indicating whether this want continuous button pressed event. + /// + public virtual bool WantContinuousButtonPressed { get; set; } + } +} diff --git a/Terminal.Gui/View/ViewSubViews.cs b/Terminal.Gui/View/ViewSubViews.cs new file mode 100644 index 0000000000..fd9a0f5f2d --- /dev/null +++ b/Terminal.Gui/View/ViewSubViews.cs @@ -0,0 +1,723 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using NStack; + +namespace Terminal.Gui { + public partial class View { + static readonly IList _empty = new List (0).AsReadOnly (); + + View _superView = null; + + /// + /// Returns the container for this view, or null if this view has not been added to a container. + /// + /// The super view. + public virtual View SuperView { + get { + return _superView; + } + set { + throw new NotImplementedException (); + } + } + + List _subviews; // This is null, and allocated on demand. + /// + /// This returns a list of the subviews contained by this view. + /// + /// The subviews. + public IList Subviews => _subviews?.AsReadOnly () ?? _empty; + + // Internally, we use InternalSubviews rather than subviews, as we do not expect us + // to make the same mistakes our users make when they poke at the Subviews. + internal IList InternalSubviews => _subviews ?? _empty; + + /// + /// Returns a value indicating if this View is currently on Top (Active) + /// + public bool IsCurrentTop => Application.Current == this; + + /// + /// Event fired when this view is added to another. + /// + public event EventHandler Added; + + internal bool _addingView; + + /// + /// Adds a subview (child) to this view. + /// + /// + /// The Views that have been added to this view can be retrieved via the property. + /// See also + /// + public virtual void Add (View view) + { + if (view == null) { + return; + } + if (_subviews == null) { + _subviews = new List (); + } + if (_tabIndexes == null) { + _tabIndexes = new List (); + } + _subviews.Add (view); + _tabIndexes.Add (view); + view._superView = this; + if (view.CanFocus) { + _addingView = true; + if (SuperView?.CanFocus == false) { + SuperView._addingView = true; + SuperView.CanFocus = true; + SuperView._addingView = false; + } + CanFocus = true; + view._tabIndex = _tabIndexes.IndexOf (view); + _addingView = false; + } + if (view.Enabled && !Enabled) { + view._oldEnabled = true; + view.Enabled = false; + } + SetNeedsLayout (); + SetNeedsDisplay (); + + OnAdded (new SuperViewChangedEventArgs (this, view)); + if (IsInitialized && !view.IsInitialized) { + view.BeginInit (); + view.EndInit (); + } + } + + /// + /// Adds the specified views (children) to the view. + /// + /// Array of one or more views (can be optional parameter). + /// + /// The Views that have been added to this view can be retrieved via the property. + /// See also + /// + public void Add (params View [] views) + { + if (views == null) { + return; + } + foreach (var view in views) { + Add (view); + } + } + + /// + /// Method invoked when a subview is being added to this view. + /// + /// Event where is the subview being added. + public virtual void OnAdded (SuperViewChangedEventArgs e) + { + var view = e.Child; + view.IsAdded = true; + view.OnResizeNeeded (); + view._x ??= view._frame.X; + view._y ??= view._frame.Y; + view._width ??= view._frame.Width; + view._height ??= view._frame.Height; + + view.Added?.Invoke (this, e); + } + + /// + /// Gets information if the view was already added to the . + /// + public bool IsAdded { get; private set; } + + /// + /// Event fired when this view is removed from another. + /// + public event EventHandler Removed; + + /// + /// Removes all subviews (children) added via or from this View. + /// + public virtual void RemoveAll () + { + if (_subviews == null) { + return; + } + + while (_subviews.Count > 0) { + Remove (_subviews [0]); + } + } + + /// + /// Removes a subview added via or from this View. + /// + /// + /// + public virtual void Remove (View view) + { + if (view == null || _subviews == null) return; + + var touched = view.Frame; + _subviews.Remove (view); + _tabIndexes.Remove (view); + view._superView = null; + view._tabIndex = -1; + SetNeedsLayout (); + SetNeedsDisplay (); + + foreach (var v in _subviews) { + if (v.Frame.IntersectsWith (touched)) + view.SetNeedsDisplay (); + } + OnRemoved (new SuperViewChangedEventArgs (this, view)); + if (_focused == view) { + _focused = null; + } + } + + /// + /// Method invoked when a subview is being removed from this view. + /// + /// Event args describing the subview being removed. + public virtual void OnRemoved (SuperViewChangedEventArgs e) + { + var view = e.Child; + view.IsAdded = false; + view.Removed?.Invoke (this, e); + } + + + void PerformActionForSubview (View subview, Action action) + { + if (_subviews.Contains (subview)) { + action (subview); + } + + SetNeedsDisplay (); + subview.SetNeedsDisplay (); + } + + /// + /// Brings the specified subview to the front so it is drawn on top of any other views. + /// + /// The subview to send to the front + /// + /// . + /// + public void BringSubviewToFront (View subview) + { + PerformActionForSubview (subview, x => { + _subviews.Remove (x); + _subviews.Add (x); + }); + } + + /// + /// Sends the specified subview to the front so it is the first view drawn + /// + /// The subview to send to the front + /// + /// . + /// + public void SendSubviewToBack (View subview) + { + PerformActionForSubview (subview, x => { + _subviews.Remove (x); + _subviews.Insert (0, subview); + }); + } + + /// + /// Moves the subview backwards in the hierarchy, only one step + /// + /// The subview to send backwards + /// + /// If you want to send the view all the way to the back use SendSubviewToBack. + /// + public void SendSubviewBackwards (View subview) + { + PerformActionForSubview (subview, x => { + var idx = _subviews.IndexOf (x); + if (idx > 0) { + _subviews.Remove (x); + _subviews.Insert (idx - 1, x); + } + }); + } + + /// + /// Moves the subview backwards in the hierarchy, only one step + /// + /// The subview to send backwards + /// + /// If you want to send the view all the way to the back use SendSubviewToBack. + /// + public void BringSubviewForward (View subview) + { + PerformActionForSubview (subview, x => { + var idx = _subviews.IndexOf (x); + if (idx + 1 < _subviews.Count) { + _subviews.Remove (x); + _subviews.Insert (idx + 1, x); + } + }); + } + + /// + /// Get the top superview of a given . + /// + /// The superview view. + public View GetTopSuperView (View view = null, View superview = null) + { + View top = superview ?? Application.Top; + for (var v = view?.SuperView ?? (this?.SuperView); v != null; v = v.SuperView) { + top = v; + if (top == superview) { + break; + } + } + + return top; + } + + + + #region Focus + View _focused = null; + + internal enum Direction { + Forward, + Backward + } + + /// + /// Event fired when the view gets focus. + /// + public event EventHandler Enter; + + /// + /// Event fired when the view looses focus. + /// + public event EventHandler Leave; + + Direction _focusDirection; + internal Direction FocusDirection { + get => SuperView?.FocusDirection ?? _focusDirection; + set { + if (SuperView != null) + SuperView.FocusDirection = value; + else + _focusDirection = value; + } + } + + + // BUGBUG: v2 - Seems weird that this is in View and not Responder. + bool _hasFocus; + + /// + public override bool HasFocus => _hasFocus; + + void SetHasFocus (bool value, View view, bool force = false) + { + if (_hasFocus != value || force) { + _hasFocus = value; + if (value) { + OnEnter (view); + } else { + OnLeave (view); + } + SetNeedsDisplay (); + } + + // Remove focus down the chain of subviews if focus is removed + if (!value && _focused != null) { + var f = _focused; + f.OnLeave (view); + f.SetHasFocus (false, view); + _focused = null; + } + } + + /// + /// Event fired when the value is being changed. + /// + public event EventHandler CanFocusChanged; + + /// + public override void OnCanFocusChanged () => CanFocusChanged?.Invoke (this, EventArgs.Empty); + + bool _oldCanFocus; + /// + public override bool CanFocus { + get => base.CanFocus; + set { + if (!_addingView && IsInitialized && SuperView?.CanFocus == false && value) { + throw new InvalidOperationException ("Cannot set CanFocus to true if the SuperView CanFocus is false!"); + } + if (base.CanFocus != value) { + base.CanFocus = value; + + switch (value) { + case false when _tabIndex > -1: + TabIndex = -1; + break; + case true when SuperView?.CanFocus == false && _addingView: + SuperView.CanFocus = true; + break; + } + + if (value && _tabIndex == -1) { + TabIndex = SuperView != null ? SuperView._tabIndexes.IndexOf (this) : -1; + } + TabStop = value; + + if (!value && SuperView?.Focused == this) { + SuperView._focused = null; + } + if (!value && HasFocus) { + SetHasFocus (false, this); + SuperView?.EnsureFocus (); + if (SuperView != null && SuperView.Focused == null) { + SuperView.FocusNext (); + if (SuperView.Focused == null) { + Application.Current.FocusNext (); + } + Application.BringOverlappedTopToFront (); + } + } + if (_subviews != null && IsInitialized) { + foreach (var view in _subviews) { + if (view.CanFocus != value) { + if (!value) { + view._oldCanFocus = view.CanFocus; + view._oldTabIndex = view._tabIndex; + view.CanFocus = false; + view._tabIndex = -1; + } else { + if (_addingView) { + view._addingView = true; + } + view.CanFocus = view._oldCanFocus; + view._tabIndex = view._oldTabIndex; + view._addingView = false; + } + } + } + } + OnCanFocusChanged (); + SetNeedsDisplay (); + } + } + } + + + /// + public override bool OnEnter (View view) + { + var args = new FocusEventArgs (view); + Enter?.Invoke (this, args); + if (args.Handled) + return true; + if (base.OnEnter (view)) + return true; + + return false; + } + + /// + public override bool OnLeave (View view) + { + var args = new FocusEventArgs (view); + Leave?.Invoke (this, args); + if (args.Handled) + return true; + if (base.OnLeave (view)) + return true; + + return false; + } + + /// + /// Returns the currently focused view inside this view, or null if nothing is focused. + /// + /// The focused. + public View Focused => _focused; + + /// + /// Returns the most focused view in the chain of subviews (the leaf view that has the focus). + /// + /// The most focused View. + public View MostFocused { + get { + if (Focused == null) + return null; + var most = Focused.MostFocused; + if (most != null) + return most; + return Focused; + } + } + + /// + /// Causes the specified subview to have focus. + /// + /// View. + void SetFocus (View view) + { + if (view == null) { + return; + } + //Console.WriteLine ($"Request to focus {view}"); + if (!view.CanFocus || !view.Visible || !view.Enabled) { + return; + } + if (_focused?._hasFocus == true && _focused == view) { + return; + } + if ((_focused?._hasFocus == true && _focused?.SuperView == view) || view == this) { + + if (!view._hasFocus) { + view._hasFocus = true; + } + return; + } + // Make sure that this view is a subview + View c; + for (c = view._superView; c != null; c = c._superView) + if (c == this) + break; + if (c == null) + throw new ArgumentException ("the specified view is not part of the hierarchy of this view"); + + if (_focused != null) + _focused.SetHasFocus (false, view); + + var f = _focused; + _focused = view; + _focused.SetHasFocus (true, f); + _focused.EnsureFocus (); + + // Send focus upwards + if (SuperView != null) { + SuperView.SetFocus (this); + } else { + SetFocus (this); + } + } + + /// + /// Causes the specified view and the entire parent hierarchy to have the focused order updated. + /// + public void SetFocus () + { + if (!CanBeVisible (this) || !Enabled) { + if (HasFocus) { + SetHasFocus (false, this); + } + return; + } + + if (SuperView != null) { + SuperView.SetFocus (this); + } else { + SetFocus (this); + } + } + + /// + /// Finds the first view in the hierarchy that wants to get the focus if nothing is currently focused, otherwise, does nothing. + /// + public void EnsureFocus () + { + if (_focused == null && _subviews?.Count > 0) { + if (FocusDirection == Direction.Forward) { + FocusFirst (); + } else { + FocusLast (); + } + } + } + + /// + /// Focuses the first focusable subview if one exists. + /// + public void FocusFirst () + { + if (!CanBeVisible (this)) { + return; + } + + if (_tabIndexes == null) { + SuperView?.SetFocus (this); + return; + } + + foreach (var view in _tabIndexes) { + if (view.CanFocus && view._tabStop && view.Visible && view.Enabled) { + SetFocus (view); + return; + } + } + } + + /// + /// Focuses the last focusable subview if one exists. + /// + public void FocusLast () + { + if (!CanBeVisible (this)) { + return; + } + + if (_tabIndexes == null) { + SuperView?.SetFocus (this); + return; + } + + for (var i = _tabIndexes.Count; i > 0;) { + i--; + + var v = _tabIndexes [i]; + if (v.CanFocus && v._tabStop && v.Visible && v.Enabled) { + SetFocus (v); + return; + } + } + } + + /// + /// Focuses the previous view. + /// + /// if previous was focused, otherwise. + public bool FocusPrev () + { + if (!CanBeVisible (this)) { + return false; + } + + FocusDirection = Direction.Backward; + if (_tabIndexes == null || _tabIndexes.Count == 0) + return false; + + if (_focused == null) { + FocusLast (); + return _focused != null; + } + + var focusedIdx = -1; + for (var i = _tabIndexes.Count; i > 0;) { + i--; + var w = _tabIndexes [i]; + + if (w.HasFocus) { + if (w.FocusPrev ()) + return true; + focusedIdx = i; + continue; + } + if (w.CanFocus && focusedIdx != -1 && w._tabStop && w.Visible && w.Enabled) { + _focused.SetHasFocus (false, w); + + if (w.CanFocus && w._tabStop && w.Visible && w.Enabled) + w.FocusLast (); + + SetFocus (w); + return true; + } + } + if (_focused != null) { + _focused.SetHasFocus (false, this); + _focused = null; + } + return false; + } + + /// + /// Focuses the next view. + /// + /// if next was focused, otherwise. + public bool FocusNext () + { + if (!CanBeVisible (this)) { + return false; + } + + FocusDirection = Direction.Forward; + if (_tabIndexes == null || _tabIndexes.Count == 0) + return false; + + if (_focused == null) { + FocusFirst (); + return _focused != null; + } + var focusedIdx = -1; + for (var i = 0; i < _tabIndexes.Count; i++) { + var w = _tabIndexes [i]; + + if (w.HasFocus) { + if (w.FocusNext ()) + return true; + focusedIdx = i; + continue; + } + if (w.CanFocus && focusedIdx != -1 && w._tabStop && w.Visible && w.Enabled) { + _focused.SetHasFocus (false, w); + + if (w.CanFocus && w._tabStop && w.Visible && w.Enabled) + w.FocusFirst (); + + SetFocus (w); + return true; + } + } + if (_focused != null) { + _focused.SetHasFocus (false, this); + _focused = null; + } + return false; + } + + View GetMostFocused (View view) + { + if (view == null) { + return null; + } + + return view._focused != null ? GetMostFocused (view._focused) : view; + } + + /// + /// Positions the cursor in the right position based on the currently focused view in the chain. + /// + /// Views that are focusable should override to ensure + /// the cursor is placed in a location that makes sense. Unix terminals do not have + /// a way of hiding the cursor, so it can be distracting to have the cursor left at + /// the last focused view. Views should make sure that they place the cursor + /// in a visually sensible place. + public virtual void PositionCursor () + { + if (!CanBeVisible (this) || !Enabled) { + return; + } + + // BUGBUG: v2 - This needs to support children of Frames too + + if (_focused == null && SuperView != null) { + SuperView.EnsureFocus (); + } else if (_focused?.Visible == true && _focused?.Enabled == true && _focused?.Frame.Width > 0 && _focused.Frame.Height > 0) { + _focused.PositionCursor (); + } else if (_focused?.Visible == true && _focused?.Enabled == false) { + _focused = null; + } else if (CanFocus && HasFocus && Visible && Frame.Width > 0 && Frame.Height > 0) { + Move (TextFormatter.HotKeyPos == -1 ? 0 : TextFormatter.CursorPosition, 0); + } else { + Move (_frame.X, _frame.Y); + } + } + #endregion Focus + } +} diff --git a/Terminal.Gui/View/ViewText.cs b/Terminal.Gui/View/ViewText.cs new file mode 100644 index 0000000000..a8dcd1b7f0 --- /dev/null +++ b/Terminal.Gui/View/ViewText.cs @@ -0,0 +1,196 @@ +using NStack; +using System; + +namespace Terminal.Gui { + + public partial class View { + ustring _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 ustring Text { + get => _text; + set { + _text = value; + SetHotKey (); + UpdateTextFormatterText (); + //TextFormatter.Format (); + OnResizeNeeded (); + +#if DEBUG + if (_text != null && string.IsNullOrEmpty (Id)) { + Id = _text.ToString (); + } +#endif + } + } + + /// + /// Gets or sets the used to format . + /// + public TextFormatter TextFormatter { get; set; } + + void TextFormatter_HotKeyChanged (object sender, KeyChangedEventArgs e) + { + HotKeyChanged?.Invoke (this, e); + } + + /// + /// 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 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 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; + + var isValidOldAutoSize = AutoSize && IsValidAutoSize (out var _); + + UpdateTextFormatterText (); + + if ((!ForceValidatePosDim && directionChanged && AutoSize) + || (ForceValidatePosDim && directionChanged && AutoSize && isValidOldAutoSize)) { + OnResizeNeeded (); + } else if (directionChanged && IsAdded) { + SetWidthHeight (Bounds.Size); + SetMinWidthHeight (); + } else { + SetMinWidthHeight (); + } + TextFormatter.Size = GetSizeNeededForTextAndHotKey (); + SetNeedsDisplay (); + } + + /// + /// 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 (HotKeySpecifier) == true + ? Math.Max (Rune.ColumnWidth (HotKeySpecifier), 0) : 0; + } else { + return TextFormatter.IsVerticalDirection (TextDirection) && + TextFormatter.Text?.Contains (HotKeySpecifier) == true + ? Math.Max (Rune.ColumnWidth (HotKeySpecifier), 0) : 0; + } + } + + /// + /// Gets the dimensions required for ignoring a . + /// + /// + public Size GetSizeNeededForTextWithoutHotKey () + { + return new Size (TextFormatter.Size.Width - GetHotKeySpecifierLength (), + TextFormatter.Size.Height - GetHotKeySpecifierLength (false)); + } + + /// + /// Gets the dimensions required for accounting for a . + /// + /// + public Size GetSizeNeededForTextAndHotKey () + { + if (ustring.IsNullOrEmpty (TextFormatter.Text)) { + + if (!IsInitialized) return Size.Empty; + + 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 (_frame.Size.Width + GetHotKeySpecifierLength (), + _frame.Size.Height + GetHotKeySpecifierLength (false)); + } + + } +} \ No newline at end of file diff --git a/Terminal.Gui/Views/AutocompleteFilepathContext.cs b/Terminal.Gui/Views/AutocompleteFilepathContext.cs index eb8af28890..0a7bb090c5 100644 --- a/Terminal.Gui/Views/AutocompleteFilepathContext.cs +++ b/Terminal.Gui/Views/AutocompleteFilepathContext.cs @@ -17,7 +17,6 @@ public AutocompleteFilepathContext (ustring currentLine, int cursorPosition, Fil } } - internal class FilepathSuggestionGenerator : ISuggestionGenerator { FileDialogState state; @@ -66,7 +65,6 @@ public IEnumerable GenerateSuggestions (AutocompleteContext context) .OrderBy (m => m.Length) .ToArray (); - // nothing to suggest if (validSuggestions.Length == 0 || validSuggestions [0].Length == term.Length) { return Enumerable.Empty (); @@ -82,10 +80,8 @@ public bool IsWordChar (Rune rune) return false; } - return true; } - } } \ No newline at end of file diff --git a/Terminal.Gui/Views/ContextMenu.cs b/Terminal.Gui/Views/ContextMenu.cs index 4a0ec5a5f0..13f0b66f2b 100644 --- a/Terminal.Gui/Views/ContextMenu.cs +++ b/Terminal.Gui/Views/ContextMenu.cs @@ -79,7 +79,7 @@ public void Dispose () } if (container != null) { container.Closing -= Container_Closing; - container.Resized -= Container_Resized; + container.TerminalResized -= Container_Resized; } } @@ -93,7 +93,7 @@ public void Show () } container = Application.Top; container.Closing += Container_Closing; - container.Resized += Container_Resized; + container.TerminalResized += Container_Resized; var frame = container.Frame; var position = Position; if (Host != null) { diff --git a/Terminal.Gui/Views/Dialog.cs b/Terminal.Gui/Views/Dialog.cs index df882f608c..2522fc9ccb 100644 --- a/Terminal.Gui/Views/Dialog.cs +++ b/Terminal.Gui/Views/Dialog.cs @@ -90,7 +90,6 @@ private void SetInitialProperties (Button [] buttons) LayoutButtons (); }; - } /// @@ -146,7 +145,6 @@ public enum ButtonAlignments { Right } - /// /// Determines how the s are aligned along the /// bottom of the dialog. diff --git a/Terminal.Gui/Views/FileDialog.cs b/Terminal.Gui/Views/FileDialog.cs index eb43d1ec09..4bf419a1b2 100644 --- a/Terminal.Gui/Views/FileDialog.cs +++ b/Terminal.Gui/Views/FileDialog.cs @@ -63,7 +63,6 @@ public partial class FileDialog : Dialog { '"','<','>','|','*','?', }; - /// /// The UI selected from combo box. May be null. /// @@ -125,7 +124,6 @@ public partial class FileDialog : Dialog { /// error handling (e.g. showing a public IFileOperations FileOperationsHandler { get; set; } = new DefaultFileOperations (); - /// /// Initializes a new instance of the class. /// @@ -247,7 +245,6 @@ public FileDialog (IFileSystem fileSystem) CycleToNextTableEntryBeginningWith (k); } - }; this.treeView = new TreeView () { @@ -276,7 +273,6 @@ public FileDialog (IFileSystem fileSystem) this.LayoutSubviews(); }; - tbFind = new TextField { X = Pos.Right (this.btnToggleSplitterCollapse) + 1, Caption = Style.SearchCaption, @@ -317,7 +313,6 @@ public FileDialog (IFileSystem fileSystem) this.tableView.Style.ShowHorizontalHeaderUnderline = true; this.tableView.Style.ShowHorizontalScrollIndicators = true; - this.SetupTableColumns (); this.sorter = new FileDialogSorter (this, this.tableView); @@ -338,7 +333,6 @@ public FileDialog (IFileSystem fileSystem) this.treeView.KeyDown += (s, k) => { - var selected = treeView.SelectedObject; if (selected != null) { if (!treeView.CanExpand (selected) || treeView.IsExpanded (selected)) { @@ -454,7 +448,6 @@ private IFileSystemInfo [] GetFocusedFiles () } - /// public override bool ProcessHotKey (KeyEvent keyEvent) { @@ -592,7 +585,6 @@ public bool AllowsMultipleSelection { set => this.tableView.MultiSelect = value; } - /// /// Gets or Sets a collection of file types that the user can/must select. Only applies /// when is or . @@ -615,7 +607,6 @@ public bool AllowsMultipleSelection { public IReadOnlyList MultiSelected { get; private set; } = Enumerable.Empty ().ToList ().AsReadOnly (); - /// public override void Redraw (Rect bounds) { @@ -792,7 +783,6 @@ private void Accept (IEnumerable toMultiAccept) FinishAccept (); } - private void Accept (IFileInfo f) { if (!this.IsCompatibleWithOpenMode (f.FullName, out var reason)) { @@ -922,7 +912,6 @@ private void TableView_SelectedCellChanged (object sender, SelectedCellChangedEv } } - private bool TableView_KeyUp (KeyEvent keyEvent) { if (keyEvent.Key == Key.Backspace) { @@ -952,7 +941,6 @@ private bool TableView_KeyUp (KeyEvent keyEvent) return false; } - private void SetupTableColumns () { this.dtFiles = new DataTable (); @@ -1023,10 +1011,8 @@ private void CellActivate (object sender, CellActivatedEventArgs obj) } } - var stats = this.RowToStats (obj.Row); - if (stats.FileSystemInfo is IDirectoryInfo d) { this.PushState (d, true); return; @@ -1282,7 +1268,6 @@ private FileSystemInfoStats RowToStats (int rowIndex) return null; } - private void PathChanged () { // avoid re-entry @@ -1545,7 +1530,6 @@ private void UpdateChildren () cancel = true; } - if (cancel || finished) { break; } diff --git a/Terminal.Gui/Views/FrameView.cs b/Terminal.Gui/Views/FrameView.cs index cc2d3dc3ef..8d4ec30613 100644 --- a/Terminal.Gui/Views/FrameView.cs +++ b/Terminal.Gui/Views/FrameView.cs @@ -55,7 +55,6 @@ void SetInitialProperties (Rect frame, ustring title, View [] views = null) Border.Data = "Border"; } - /// public override bool OnEnter (View view) { diff --git a/Terminal.Gui/Views/GraphView/Annotations.cs b/Terminal.Gui/Views/GraphView/Annotations.cs index 286c838569..9c92cc40a8 100644 --- a/Terminal.Gui/Views/GraphView/Annotations.cs +++ b/Terminal.Gui/Views/GraphView/Annotations.cs @@ -31,7 +31,6 @@ public interface IAnnotation { void Render (GraphView graph); } - /// /// Displays text at a given position (in screen space or graph space) /// @@ -199,7 +198,6 @@ public void Render (GraphView graph) } } - /// /// Adds an entry into the legend. Duplicate entries are permissable /// @@ -238,7 +236,6 @@ public class PathAnnotation : IAnnotation { /// public bool BeforeSeries { get; set; } - /// /// Draws lines connecting each of the /// diff --git a/Terminal.Gui/Views/GraphView/Axis.cs b/Terminal.Gui/Views/GraphView/Axis.cs index 8e4fbf0ea1..fbfc2080a1 100644 --- a/Terminal.Gui/Views/GraphView/Axis.cs +++ b/Terminal.Gui/Views/GraphView/Axis.cs @@ -123,7 +123,6 @@ public HorizontalAxis () : base (Orientation.Horizontal) { } - /// /// Draws the horizontal axis line /// @@ -158,7 +157,6 @@ public override void DrawAxisLine (GraphView graph) } } - /// /// Draws a horizontal axis line at the given , /// screen coordinates @@ -312,7 +310,6 @@ public int GetAxisYPosition (GraphView graph) /// public class VerticalAxis : Axis { - /// /// Creates a new axis /// @@ -371,7 +368,6 @@ private int GetAxisYEnd (GraphView graph) return graph.Bounds.Height; } - /// /// Draws axis markers and labels /// @@ -501,7 +497,6 @@ public int GetAxisXPosition (GraphView graph) } } - /// /// A location on an axis of a that may /// or may not have a label associated with it diff --git a/Terminal.Gui/Views/GraphView/GraphView.cs b/Terminal.Gui/Views/GraphView/GraphView.cs index 6681758807..78826448a3 100644 --- a/Terminal.Gui/Views/GraphView/GraphView.cs +++ b/Terminal.Gui/Views/GraphView/GraphView.cs @@ -27,7 +27,6 @@ public class GraphView : View { /// public List Series { get; } = new List (); - /// /// Elements drawn into graph after series have been drawn e.g. Legends etc /// @@ -162,7 +161,6 @@ public override void Redraw (Rect bounds) SetDriverColorToGraphColor (); - Rect drawBounds = new Rect((int)MarginLeft,0, graphScreenWidth, graphScreenHeight); RectangleF graphSpace = ScreenToGraphSpace (drawBounds); @@ -208,7 +206,6 @@ public RectangleF ScreenToGraphSpace (int col, int row) CellSize.X, CellSize.Y); } - /// /// Returns the section of the graph that is represented by the screen area /// @@ -289,13 +286,11 @@ public void Scroll (float offsetX, float offsetY) SetNeedsDisplay (); } - #region Bresenham's line algorithm // https://rosettacode.org/wiki/Bitmap/Bresenham%27s_line_algorithm#C.23 int ipart (decimal x) { return (int)x; } - decimal fpart (decimal x) { if (x < 0) return (1 - (x - Math.Floor (x))); diff --git a/Terminal.Gui/Views/GraphView/Series.cs b/Terminal.Gui/Views/GraphView/Series.cs index bf7040a01f..19215d1a4f 100644 --- a/Terminal.Gui/Views/GraphView/Series.cs +++ b/Terminal.Gui/Views/GraphView/Series.cs @@ -19,7 +19,6 @@ public interface ISeries { void DrawSeries (GraphView graph, Rect drawBounds, RectangleF graphBounds); } - /// /// Series composed of any number of discrete data points /// @@ -56,7 +55,6 @@ public void DrawSeries (GraphView graph, Rect drawBounds, RectangleF graphBounds } - /// /// Collection of in which bars are clustered by category /// @@ -92,7 +90,6 @@ public MultiBarSeries (int numberOfBarsPerCategory, float barsEvery, float spaci throw new ArgumentException ("Number of colors must match the number of bars", nameof (numberOfBarsPerCategory)); } - for (int i = 0; i < numberOfBarsPerCategory; i++) { subSeries [i] = new BarSeries (); subSeries [i].BarEvery = barsEvery; diff --git a/Terminal.Gui/Views/Line.cs b/Terminal.Gui/Views/Line.cs index 1f396e7fe9..19f1bf4f68 100644 --- a/Terminal.Gui/Views/Line.cs +++ b/Terminal.Gui/Views/Line.cs @@ -22,6 +22,7 @@ public Line () } + /// public override bool OnDrawFrames() { var screenBounds = ViewToScreen (Bounds); @@ -39,6 +40,7 @@ public override bool OnDrawFrames() //} + /// public override void Redraw (Rect bounds) { OnDrawFrames (); diff --git a/Terminal.Gui/Views/Menu.cs b/Terminal.Gui/Views/Menu.cs index 9027c9e573..e7640f71cd 100644 --- a/Terminal.Gui/Views/Menu.cs +++ b/Terminal.Gui/Views/Menu.cs @@ -1057,7 +1057,7 @@ public bool UseSubMenusSingleFrame { public MenuBar () : this (new MenuBarItem [] { }) { } /// - /// Initializes a new instance of the class with the specified set of toplevel menu items. + /// Initializes a new instance of the class with the specified set of Toplevel menu items. /// /// Individual menu items; a null item will result in a separator being drawn. public MenuBar (MenuBarItem [] menus) : base () diff --git a/Terminal.Gui/Views/MessageBox.cs b/Terminal.Gui/Views/MessageBox.cs index 5df103b179..aa40a9e33f 100644 --- a/Terminal.Gui/Views/MessageBox.cs +++ b/Terminal.Gui/Views/MessageBox.cs @@ -164,7 +164,6 @@ public static int Query (ustring title, ustring message, int defaultButton = 0, return QueryFull (false, 0, 0, title, message, defaultButton, wrapMessage, buttons); } - /// /// Presents an error with the specified title and message and a list of buttons to show to the user. /// diff --git a/Terminal.Gui/Views/SaveDialog.cs b/Terminal.Gui/Views/SaveDialog.cs index 7f7ed1ebad..fe968628f0 100644 --- a/Terminal.Gui/Views/SaveDialog.cs +++ b/Terminal.Gui/Views/SaveDialog.cs @@ -49,7 +49,6 @@ public SaveDialog (ustring title, List allowedTypes = null) } } - /// /// Gets the name of the file the user selected for saving, or null /// if the user canceled the . diff --git a/Terminal.Gui/Views/ScrollView.cs b/Terminal.Gui/Views/ScrollView.cs index bc78a8ef70..c02640a254 100644 --- a/Terminal.Gui/Views/ScrollView.cs +++ b/Terminal.Gui/Views/ScrollView.cs @@ -52,7 +52,6 @@ public ScrollView (Rect frame) : base (frame) SetInitialProperties (frame); } - /// /// Initializes a new instance of the class using positioning. /// diff --git a/Terminal.Gui/Views/TabView.cs b/Terminal.Gui/Views/TabView.cs index ec4375b8ad..65a36dfaf9 100644 --- a/Terminal.Gui/Views/TabView.cs +++ b/Terminal.Gui/Views/TabView.cs @@ -53,14 +53,12 @@ public partial class TabView : View { /// public event EventHandler SelectedTabChanged; - /// /// Event fired when a is clicked. Can be used to cancel navigation, /// show context menu (e.g. on right click) etc. /// public event EventHandler TabClicked; - /// /// The currently selected member of chosen by the user /// @@ -104,7 +102,6 @@ public Tab SelectedTab { /// public TabStyle Style { get; set; } = new TabStyle (); - /// /// Initializes a class using layout. /// @@ -125,7 +122,6 @@ public TabView () : base () AddCommand (Command.LeftHome, () => { SelectedTab = Tabs.FirstOrDefault (); return true; }); AddCommand (Command.RightEnd, () => { SelectedTab = Tabs.LastOrDefault (); return true; }); - // Default keybindings for this view AddKeyBinding (Key.CursorLeft, Command.Left); AddKeyBinding (Key.CursorRight, Command.Right); @@ -176,12 +172,10 @@ public void ApplyStyleChanges () tabsBar.Y = Pos.Percent (0); } - SetNeedsDisplay (); } - /// public override void Redraw (Rect bounds) { @@ -247,7 +241,6 @@ public override bool ProcessKey (KeyEvent keyEvent) return base.ProcessKey (keyEvent); } - /// /// Changes the by the given . /// Positive for right, negative for left. If no tab is currently selected then @@ -284,7 +277,6 @@ public void SwitchTabBy (int amount) EnsureSelectedTabIsVisible (); } - /// /// Updates to be a valid index of /// @@ -332,7 +324,6 @@ private int GetTabHeight (bool top) return Style.ShowTopLine ? 3 : 2; } - /// /// Returns which tabs to render at each x location /// @@ -376,7 +367,6 @@ private IEnumerable CalculateViewport (Rect bounds) } } - /// /// Adds the given to /// @@ -388,7 +378,6 @@ public void AddTab (Tab tab, bool andSelect) return; } - tabs.Add (tab); if (SelectedTab == null || andSelect) { @@ -402,7 +391,6 @@ public void AddTab (Tab tab, bool andSelect) SetNeedsDisplay (); } - /// /// Removes the given from . /// Caller is responsible for disposing the tab's hosted @@ -495,7 +483,6 @@ public override void Redraw (Rect bounds) RenderUnderline (tabLocations, width); Driver.SetAttribute (GetNormalColor ()); - } /// @@ -520,7 +507,6 @@ private void RenderOverline (TabToRender [] tabLocations, int width) return; } - Move (selected.X - 1, y); Driver.AddRune (host.Style.TabsOnBottom ? Driver.LLCorner : Driver.ULCorner); @@ -556,7 +542,6 @@ private void RenderTabLine (TabToRender [] tabLocations, int width) } - // clear any old text Move (0, y); Driver.AddStr (new string (' ', width)); @@ -585,7 +570,6 @@ private void RenderTabLine (TabToRender [] tabLocations, int width) } } - Driver.AddStr (toRender.TextToRender); Driver.SetAttribute (GetNormalColor ()); @@ -613,7 +597,6 @@ private void RenderUnderline (TabToRender [] tabLocations, int width) Driver.AddRune (Driver.HLine); } - } var selected = tabLocations.FirstOrDefault (t => t.IsSelected); @@ -628,12 +611,10 @@ private void RenderUnderline (TabToRender [] tabLocations, int width) Driver.AddStr (new string (' ', selected.Width)); - Driver.AddRune (selected.X + selected.Width == width - 1 ? Driver.VLine : (host.Style.TabsOnBottom ? Driver.ULCorner : Driver.LLCorner)); - // draw scroll indicators // if there are more tabs to the left not visible @@ -686,7 +667,6 @@ public override bool MouseEvent (MouseEvent me) } } - if (!me.Flags.HasFlag (MouseFlags.Button1Clicked) && !me.Flags.HasFlag (MouseFlags.Button1DoubleClicked) && !me.Flags.HasFlag (MouseFlags.Button1TripleClicked)) @@ -696,7 +676,6 @@ public override bool MouseEvent (MouseEvent me) SetFocus (); } - if (me.Flags.HasFlag (MouseFlags.Button1Clicked) || me.Flags.HasFlag (MouseFlags.Button1DoubleClicked) || me.Flags.HasFlag (MouseFlags.Button1TripleClicked)) { @@ -711,7 +690,6 @@ public override bool MouseEvent (MouseEvent me) return true; } - if (hit != null) { host.SelectedTab = hit; SetNeedsDisplay (); @@ -822,7 +800,6 @@ public class TabStyle { /// public bool ShowTopLine { get; set; } = true; - /// /// True to show a solid box around the edge of the control. Defaults to true. /// diff --git a/Terminal.Gui/Views/TableView/CellActivatedEventArgs.cs b/Terminal.Gui/Views/TableView/CellActivatedEventArgs.cs index 2cb558776f..beb5dffe82 100644 --- a/Terminal.Gui/Views/TableView/CellActivatedEventArgs.cs +++ b/Terminal.Gui/Views/TableView/CellActivatedEventArgs.cs @@ -3,7 +3,6 @@ namespace Terminal.Gui { - /// /// Defines the event arguments for event /// @@ -14,7 +13,6 @@ public class CellActivatedEventArgs : EventArgs { /// public DataTable Table { get; } - /// /// The column index of the cell that is being activated /// diff --git a/Terminal.Gui/Views/TableView/SelectedCellChangedEventArgs.cs b/Terminal.Gui/Views/TableView/SelectedCellChangedEventArgs.cs index 13dd3bfb55..67e6c3e3d4 100644 --- a/Terminal.Gui/Views/TableView/SelectedCellChangedEventArgs.cs +++ b/Terminal.Gui/Views/TableView/SelectedCellChangedEventArgs.cs @@ -3,7 +3,6 @@ namespace Terminal.Gui { - /// /// Defines the event arguments for /// @@ -14,28 +13,24 @@ public class SelectedCellChangedEventArgs : EventArgs { /// public DataTable Table { get; } - /// /// The previous selected column index. May be invalid e.g. when the selection has been changed as a result of replacing the existing Table with a smaller one /// /// public int OldCol { get; } - /// /// The newly selected column index. /// /// public int NewCol { get; } - /// /// The previous selected row index. May be invalid e.g. when the selection has been changed as a result of deleting rows from the table /// /// public int OldRow { get; } - /// /// The newly selected row index. /// diff --git a/Terminal.Gui/Views/TableView/TableView.cs b/Terminal.Gui/Views/TableView/TableView.cs index d38d021704..718e0202dc 100644 --- a/Terminal.Gui/Views/TableView/TableView.cs +++ b/Terminal.Gui/Views/TableView/TableView.cs @@ -7,7 +7,6 @@ namespace Terminal.Gui { - /// /// View for tabular data based on a . /// @@ -31,7 +30,6 @@ public partial class TableView : View { /// public const int DefaultMaxCellWidth = 100; - /// /// The default minimum cell width for /// @@ -229,7 +227,6 @@ public TableView () : base () AddKeyBinding (CellActivationKey, Command.Accept); } - /// public override void Redraw (Rect bounds) { @@ -704,7 +701,6 @@ public void SetSelection (int col, int row, bool extendExistingSelection) ClearMultiSelectedRegions (true); } - if (extendExistingSelection) { // If we are extending current selection but there isn't one @@ -776,7 +772,6 @@ private void UnionSelection (int col, int row) } } - /// /// Moves the and by the provided offsets. Optionally starting a box selection (see ) /// @@ -835,7 +830,6 @@ public void ChangeSelectionToEndOfTable (bool extend) Update (); } - /// /// Moves or extends the selection to the last cell in the current row /// @@ -905,7 +899,6 @@ public IEnumerable GetAllSelectedCells () } } - // if there are no region selections then it is just the active cell // if we are selecting the full row @@ -1194,7 +1187,6 @@ private bool HasControlOrAlt (MouseEvent me) return null; } - var rowIdx = RowOffset - headerHeight + clientY; // if click is off bottom of the rows don't give an @@ -1275,7 +1267,6 @@ public void EnsureValidScrollOffsets () RowOffset = Math.Max (Math.Min (RowOffset, Table.Rows.Count - 1), 0); } - /// /// Updates , and where they are outside the bounds of the table (by adjusting them to the nearest existing cell). Has no effect if has not been set. /// @@ -1600,7 +1591,6 @@ private int CalculateMaxCellWidth (DataColumn col, int rowsToRender, ColumnStyle if (RowOffset < 0) return spaceRequired; - for (int i = RowOffset; i < RowOffset + rowsToRender && i < Table.Rows.Count; i++) { //expand required space if cell is bigger than the last biggest cell or header @@ -1625,7 +1615,6 @@ private int CalculateMaxCellWidth (DataColumn col, int rowsToRender, ColumnStyle if (spaceRequired > MaxCellWidth) spaceRequired = MaxCellWidth; - return spaceRequired; } @@ -1726,7 +1715,6 @@ public class ColumnStyle { /// If is 0 then will always return false. public bool Visible { get => MaxWidth >= 0 && visible; set => visible = value; } - /// /// Returns the alignment for the cell based on and / /// @@ -1753,7 +1741,6 @@ public string GetRepresentation (object value) return f.ToString (Format, null); } - if (RepresentationGetter != null) return RepresentationGetter (value); diff --git a/Terminal.Gui/Views/TextField.cs b/Terminal.Gui/Views/TextField.cs index af4537098a..4d49fe685b 100644 --- a/Terminal.Gui/Views/TextField.cs +++ b/Terminal.Gui/Views/TextField.cs @@ -486,7 +486,6 @@ public override void Redraw (Rect bounds) if (SelectedLength > 0) return; - // draw autocomplete GenerateSuggestions (); diff --git a/Terminal.Gui/Views/TextValidateField.cs b/Terminal.Gui/Views/TextValidateField.cs index 6d5ffedcba..e85c8568e8 100644 --- a/Terminal.Gui/Views/TextValidateField.cs +++ b/Terminal.Gui/Views/TextValidateField.cs @@ -270,7 +270,6 @@ public bool IsValid { /// public bool ValidateOnInput { get; set; } = true; - bool Validate (List text) { var match = regex.Match (ustring.Make (text).ToString ()); diff --git a/Terminal.Gui/Views/TextView.cs b/Terminal.Gui/Views/TextView.cs index 65e7f6b634..62b414884f 100644 --- a/Terminal.Gui/Views/TextView.cs +++ b/Terminal.Gui/Views/TextView.cs @@ -1887,7 +1887,7 @@ public int RightOffset { /// /// Gets or sets a value indicating whether pressing ENTER in a - /// creates a new line of text in the view or activates the default button for the toplevel. + /// creates a new line of text in the view or activates the default button for the Toplevel. /// public bool AllowsReturn { get => allowsReturn; @@ -2705,7 +2705,6 @@ void AppendClipboard (ustring text) Clipboard.Contents += text; } - /// /// Inserts the given text at the current cursor position /// exactly as if the user had just typed it @@ -2724,7 +2723,6 @@ public void InsertText (string toAdd) throw new ArgumentException ($"Cannot insert character '{ch}' because it does not map to a Key"); } - InsertText (new KeyEvent () { Key = key }); } @@ -3303,7 +3301,7 @@ void ProcessPageDown () bool MovePreviousView () { - if (Application.MdiTop != null) { + if (Application.OverlappedTop != null) { return SuperView?.FocusPrev () == true; } @@ -3312,7 +3310,7 @@ bool MovePreviousView () bool MoveNextView () { - if (Application.MdiTop != null) { + if (Application.OverlappedTop != null) { return SuperView?.FocusNext () == true; } @@ -4477,7 +4475,6 @@ public void ClearHistoryChanges () } } - /// /// Renders an overlay on another view at a given point that allows selecting /// from a range of 'autocomplete' options. diff --git a/Terminal.Gui/Views/TileView.cs b/Terminal.Gui/Views/TileView.cs index 04196bd716..4c0957e915 100644 --- a/Terminal.Gui/Views/TileView.cs +++ b/Terminal.Gui/Views/TileView.cs @@ -380,7 +380,6 @@ public bool SetSplitterPos (int idx, Pos value) /// /// Overridden so no Frames get drawn (BUGBUG: v2 fix this hack) /// - /// /// public override bool OnDrawFrames () { @@ -612,7 +611,6 @@ private bool IsValidNewSplitterPos (int idx, Pos value, int fullSpace) spaceForLast--; } - // don't shrink if it would take us below min size of left panel if (spaceForLast < tiles [idx].MinSize) { return false; @@ -781,7 +779,6 @@ private void HideSplittersBasedOnTileVisibility () splitterLines [Math.Min (i, splitterLines.Count - 1)].Visible = false; } - } } } @@ -941,7 +938,7 @@ public override bool MouseEvent (MouseEvent mouseEvent) // Start a Drag SetFocus (); - Application.EnsuresTopOnFront (); + Application.BringOverlappedTopToFront (); if (mouseEvent.Flags == MouseFlags.Button1Pressed) { dragPosition = new Point (mouseEvent.X, mouseEvent.Y); diff --git a/Terminal.Gui/Views/Toplevel.cs b/Terminal.Gui/Views/Toplevel.cs index 8e2741cb33..3140df1af6 100644 --- a/Terminal.Gui/Views/Toplevel.cs +++ b/Terminal.Gui/Views/Toplevel.cs @@ -21,7 +21,7 @@ namespace Terminal.Gui { /// call . /// /// - public class Toplevel : View { + public partial class Toplevel : View { /// /// Gets or sets whether the for this is running or not. /// @@ -31,18 +31,18 @@ public class Toplevel : View { public bool Running { get; set; } /// - /// Invoked when the Toplevel has begun to be loaded. + /// Invoked when the has begun to be loaded. /// A Loaded event handler is a good place to finalize initialization before calling /// . /// public event EventHandler Loaded; /// - /// Invoked when the Toplevel has started it's first iteration. + /// Invoked when the has started it's first iteration. /// Subscribe to this event to perform tasks when the has been laid out and focus has been set. /// changes. /// A Ready event handler is a good place to finalize initialization after calling - /// on this Toplevel. + /// on this . /// public event EventHandler Ready; @@ -98,11 +98,11 @@ public class Toplevel : View { /// /// Invoked when the terminal has been resized. The new of the terminal is provided. /// - public event EventHandler Resized; + public event EventHandler TerminalResized; - internal virtual void OnResized (SizeChangedEventArgs size) + internal virtual void OnTerminalResized (SizeChangedEventArgs size) { - Resized?.Invoke (this, size); + TerminalResized?.Invoke (this, size); } internal virtual void OnChildUnloaded (Toplevel top) @@ -133,7 +133,7 @@ internal virtual void OnAllChildClosed () internal virtual void OnChildClosed (Toplevel top) { - if (IsMdiContainer) { + if (IsOverlappedContainer) { SetSubViewNeedsDisplay (); } ChildClosed?.Invoke (this, new ToplevelEventArgs (top)); @@ -187,7 +187,7 @@ internal virtual void OnUnloaded () /// /// Initializes a new instance of the class with the specified layout. /// - /// A superview-relative rectangle specifying the location and size for the new Toplevel + /// A Superview-relative rectangle specifying the location and size for the new Toplevel public Toplevel (Rect frame) : base (frame) { SetInitialProperties (); @@ -252,14 +252,14 @@ void SetInitialProperties () private void Application_UnGrabbingMouse (object sender, GrabMouseEventArgs e) { - if (Application.MouseGrabView == this && dragPosition.HasValue) { + if (Application.MouseGrabView == this && _dragPosition.HasValue) { e.Cancel = true; } } private void Application_GrabbingMouse (object sender, GrabMouseEventArgs e) { - if (Application.MouseGrabView == this && dragPosition.HasValue) { + if (Application.MouseGrabView == this && _dragPosition.HasValue) { e.Cancel = true; } } @@ -362,20 +362,6 @@ public override bool CanFocus { /// public virtual StatusBar StatusBar { get; set; } - /// - /// Gets or sets if this Toplevel is a Mdi container. - /// - public bool IsMdiContainer { get; set; } - - /// - /// Gets or sets if this Toplevel is a Mdi child. - /// - public bool IsMdiChild { - get { - return Application.MdiTop != null && Application.MdiTop != this && !Modal; - } - } - /// /// if was already loaded by the /// , otherwise. @@ -443,31 +429,31 @@ public override bool ProcessKey (KeyEvent keyEvent) private void MovePreviousViewOrTop () { - if (Application.MdiTop == null) { + if (Application.OverlappedTop == null) { var top = Modal ? this : Application.Top; top.FocusPrev (); if (top.Focused == null) { top.FocusPrev (); } top.SetNeedsDisplay (); - Application.EnsuresTopOnFront (); + Application.BringOverlappedTopToFront (); } else { - MovePrevious (); + Application.OverlappedMovePrevious (); } } private void MoveNextViewOrTop () { - if (Application.MdiTop == null) { + if (Application.OverlappedTop == null) { var top = Modal ? this : Application.Top; top.FocusNext (); if (top.Focused == null) { top.FocusNext (); } top.SetNeedsDisplay (); - Application.EnsuresTopOnFront (); + Application.BringOverlappedTopToFront (); } else { - MoveNext (); + Application.OverlappedMoveNext (); } } @@ -499,8 +485,8 @@ private void MoveNextView () private void QuitToplevel () { - if (Application.MdiTop != null) { - Application.MdiTop.RequestStop (); + if (Application.OverlappedTop != null) { + Application.OverlappedTop.RequestStop (); } else { Application.RequestStop (); } @@ -584,7 +570,7 @@ internal void AddMenuStatusBar (View view) /// public override void Remove (View view) { - if (this is Toplevel toplevel && toplevel.MenuBar != null) { + if (this is Toplevel Toplevel && Toplevel.MenuBar != null) { RemoveMenuStatusBar (view); } base.Remove (view); @@ -728,7 +714,7 @@ public virtual void PositionToplevel (Toplevel top) out int nx, out int ny, out _, out StatusBar sb); bool layoutSubviews = false; if ((superView != top || top?.SuperView != null || (top != Application.Top && top.Modal) - || (top?.SuperView == null && top.IsMdiChild)) + || (top?.SuperView == null && top.IsOverlapped)) && (top.Frame.X + top.Frame.Width > Driver.Cols || ny > top.Frame.Y) && top.LayoutStyle == LayoutStyle.Computed) { if ((top.X == null || top.X is Pos.PosAbsolute) && top.Frame.X != nx) { @@ -770,8 +756,8 @@ public override void Redraw (Rect bounds) LayoutSubviews (); PositionToplevels (); - if (this == Application.MdiTop) { - foreach (var top in Application.MdiChildren.AsEnumerable ().Reverse ()) { + if (this == Application.OverlappedTop) { + foreach (var top in Application.OverlappedChildren.AsEnumerable ().Reverse ()) { if (top.Frame.IntersectsWith (bounds)) { if (top != this && !top.IsCurrentTop && !OutsideTopFrame (top) && top.Visible) { top.SetNeedsLayout (); @@ -806,8 +792,8 @@ bool OutsideTopFrame (Toplevel top) return false; } - internal static Point? dragPosition; - Point start; + internal static Point? _dragPosition; + Point _startGrabPoint; /// public override bool MouseEvent (MouseEvent mouseEvent) @@ -819,20 +805,20 @@ public override bool MouseEvent (MouseEvent mouseEvent) //System.Diagnostics.Debug.WriteLine ($"dragPosition before: {dragPosition.HasValue}"); int nx, ny; - if (!dragPosition.HasValue && (mouseEvent.Flags == MouseFlags.Button1Pressed + if (!_dragPosition.HasValue && (mouseEvent.Flags == MouseFlags.Button1Pressed || mouseEvent.Flags == MouseFlags.Button2Pressed || mouseEvent.Flags == MouseFlags.Button3Pressed)) { SetFocus (); - Application.EnsuresTopOnFront (); + Application.BringOverlappedTopToFront (); // Only start grabbing if the user clicks on the title bar. if (mouseEvent.Y == 0 && mouseEvent.Flags == MouseFlags.Button1Pressed) { - start = new Point (mouseEvent.X, mouseEvent.Y); - dragPosition = new Point (); + _startGrabPoint = new Point (mouseEvent.X, mouseEvent.Y); + _dragPosition = new Point (); nx = mouseEvent.X - mouseEvent.OfX; ny = mouseEvent.Y - mouseEvent.OfY; - dragPosition = new Point (nx, ny); + _dragPosition = new Point (nx, ny); Application.GrabMouse (this); } @@ -840,7 +826,7 @@ public override bool MouseEvent (MouseEvent mouseEvent) return true; } else if (mouseEvent.Flags == (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) || mouseEvent.Flags == MouseFlags.Button3Pressed) { - if (dragPosition.HasValue) { + if (_dragPosition.HasValue) { if (SuperView == null) { // Redraw the entire app window using just our Frame. Since we are // Application.Top, and our Frame always == our Bounds (Location is always (0,0)) @@ -851,11 +837,11 @@ public override bool MouseEvent (MouseEvent mouseEvent) } else { SuperView.SetNeedsDisplay (); } - GetLocationThatFits (this, mouseEvent.X + (SuperView == null ? mouseEvent.OfX - start.X : Frame.X - start.X), - mouseEvent.Y + (SuperView == null ? mouseEvent.OfY - start.Y : Frame.Y - start.Y), + GetLocationThatFits (this, mouseEvent.X + (SuperView == null ? mouseEvent.OfX - _startGrabPoint.X : Frame.X - _startGrabPoint.X), + mouseEvent.Y + (SuperView == null ? mouseEvent.OfY - _startGrabPoint.Y : Frame.Y - _startGrabPoint.Y), out nx, out ny, out _, out _); - dragPosition = new Point (nx, ny); + _dragPosition = new Point (nx, ny); X = nx; Y = ny; //System.Diagnostics.Debug.WriteLine ($"Drag: nx:{nx},ny:{ny}"); @@ -865,8 +851,8 @@ public override bool MouseEvent (MouseEvent mouseEvent) } } - if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Released) && dragPosition.HasValue) { - dragPosition = null; + if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Released) && _dragPosition.HasValue) { + _dragPosition = null; Application.UngrabMouse (); } @@ -875,43 +861,18 @@ public override bool MouseEvent (MouseEvent mouseEvent) return false; } - /// - /// Invoked by as part of - /// after the views have been laid out, and before the views are drawn for the first time. - /// - public virtual void WillPresent () - { - FocusFirst (); - } - - /// - /// Move to the next Mdi child from the . - /// - public virtual void MoveNext () - { - Application.MoveNext (); - } - - /// - /// Move to the previous Mdi child from the . - /// - public virtual void MovePrevious () - { - Application.MovePrevious (); - } - /// /// Stops and closes this . If this Toplevel is the top-most Toplevel, /// will be called, causing the application to exit. /// public virtual void RequestStop () { - if (IsMdiContainer && Running + if (IsOverlappedContainer && Running && (Application.Current == this || Application.Current?.Modal == false || Application.Current?.Modal == true && Application.Current?.Running == false)) { - foreach (var child in Application.MdiChildren) { + foreach (var child in Application.OverlappedChildren) { var ev = new ToplevelClosingEventArgs (this); if (child.OnClosing (ev)) { return; @@ -921,13 +882,13 @@ public virtual void RequestStop () } Running = false; Application.RequestStop (this); - } else if (IsMdiContainer && Running && Application.Current?.Modal == true && Application.Current?.Running == true) { + } else if (IsOverlappedContainer && Running && Application.Current?.Modal == true && Application.Current?.Running == true) { var ev = new ToplevelClosingEventArgs (Application.Current); if (OnClosing (ev)) { return; } Application.RequestStop (Application.Current); - } else if (!IsMdiContainer && Running && (!Modal || (Modal && Application.Current != this))) { + } else if (!IsOverlappedContainer && Running && (!Modal || (Modal && Application.Current != this))) { var ev = new ToplevelClosingEventArgs (this); if (OnClosing (ev)) { return; @@ -943,7 +904,7 @@ public virtual void RequestStop () /// Stops and closes the specified by . If is the top-most Toplevel, /// will be called, causing the application to exit. /// - /// The toplevel to request stop. + /// The Toplevel to request stop. public virtual void RequestStop (Toplevel top) { top.RequestStop (); @@ -952,7 +913,7 @@ public virtual void RequestStop (Toplevel top) /// public override void PositionCursor () { - if (!IsMdiContainer) { + if (!IsOverlappedContainer) { base.PositionCursor (); if (Focused == null) { EnsureFocus (); @@ -964,7 +925,7 @@ public override void PositionCursor () } if (Focused == null) { - foreach (var top in Application.MdiChildren) { + foreach (var top in Application.OverlappedChildren) { if (top != this && top.Visible) { top.SetFocus (); return; @@ -977,44 +938,6 @@ public override void PositionCursor () } } - /// - /// Gets the current visible Toplevel Mdi child that matches the arguments pattern. - /// - /// The type. - /// The strings to exclude. - /// The matched view. - public View GetTopMdiChild (Type type = null, string [] exclude = null) - { - if (Application.MdiTop == null) { - return null; - } - - foreach (var top in Application.MdiChildren) { - if (type != null && top.GetType () == type - && exclude?.Contains (top.Data.ToString ()) == false) { - return top; - } else if ((type != null && top.GetType () != type) - || (exclude?.Contains (top.Data.ToString ()) == true)) { - continue; - } - return top; - } - return null; - } - - /// - /// Shows the Mdi child indicated by , setting it as . - /// - /// The Toplevel. - /// true if the toplevel can be shown or false if not. - public virtual bool ShowChild (Toplevel top = null) - { - if (Application.MdiTop != null) { - return Application.ShowChild (top == null ? this : top); - } - return false; - } - /// public override bool OnEnter (View view) { @@ -1030,7 +953,7 @@ public override bool OnLeave (View view) /// protected override void Dispose (bool disposing) { - dragPosition = null; + _dragPosition = null; base.Dispose (disposing); } } @@ -1077,7 +1000,7 @@ public int GetHashCode (Toplevel obj) /// /// Implements the to sort the - /// from the if needed. + /// from the if needed. /// public sealed class ToplevelComparer : IComparer { /// Compares two objects and returns a value indicating whether one is less than, equal to, or greater than the other. diff --git a/Terminal.Gui/Views/ToplevelEventArgs.cs b/Terminal.Gui/Views/ToplevelEventArgs.cs index 5d7fa0168a..727c977a20 100644 --- a/Terminal.Gui/Views/ToplevelEventArgs.cs +++ b/Terminal.Gui/Views/ToplevelEventArgs.cs @@ -9,10 +9,10 @@ public class ToplevelEventArgs : EventArgs { /// /// Creates a new instance of the class. /// - /// - public ToplevelEventArgs (Toplevel toplevel) + /// + public ToplevelEventArgs (Toplevel Toplevel) { - Toplevel = toplevel; + Toplevel = Toplevel; } /// @@ -31,7 +31,7 @@ public ToplevelEventArgs (Toplevel toplevel) /// public class ToplevelClosingEventArgs : EventArgs { /// - /// The toplevel requesting stop. + /// The Toplevel requesting stop. /// public View RequestingTop { get; } /// @@ -40,7 +40,7 @@ public class ToplevelClosingEventArgs : EventArgs { public bool Cancel { get; set; } /// - /// Initializes the event arguments with the requesting toplevel. + /// Initializes the event arguments with the requesting Toplevel. /// /// The . public ToplevelClosingEventArgs (Toplevel requestingTop) diff --git a/Terminal.Gui/Views/ToplevelOverlapped.cs b/Terminal.Gui/Views/ToplevelOverlapped.cs new file mode 100644 index 0000000000..1613faaf1b --- /dev/null +++ b/Terminal.Gui/Views/ToplevelOverlapped.cs @@ -0,0 +1,226 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; + +namespace Terminal.Gui { + public partial class Toplevel { + /// + /// Gets or sets if this Toplevel is a container for overlapped children. + /// + public bool IsOverlappedContainer { get; set; } + + /// + /// Gets or sets if this Toplevel is in overlapped mode within a Toplevel container. + /// + public bool IsOverlapped { + get { + return Application.OverlappedTop != null && Application.OverlappedTop != this && !Modal; + } + } + + } + + public static partial class Application { + + /// + /// Gets the list of the Overlapped children which are not modal from the . + /// + public static List OverlappedChildren { + get { + if (OverlappedTop != null) { + List _overlappedChildren = new List (); + foreach (var top in _toplevels) { + if (top != OverlappedTop && !top.Modal) { + _overlappedChildren.Add (top); + } + } + return _overlappedChildren; + } + return null; + } + } + + /// + /// The object used for the application on startup which is true. + /// + public static Toplevel OverlappedTop { + get { + if (Top.IsOverlappedContainer) { + return Top; + } + return null; + } + } + + + static View FindDeepestOverlappedView (View start, int x, int y, out int resx, out int resy) + { + if (start.GetType ().BaseType != typeof (Toplevel) + && !((Toplevel)start).IsOverlappedContainer) { + resx = 0; + resy = 0; + return null; + } + + var startFrame = start.Frame; + + if (!startFrame.Contains (x, y)) { + resx = 0; + resy = 0; + return null; + } + + int count = _toplevels.Count; + for (int i = count - 1; i >= 0; i--) { + foreach (var top in _toplevels) { + var rx = x - startFrame.X; + var ry = y - startFrame.Y; + if (top.Visible && top.Frame.Contains (rx, ry)) { + var deep = View.FindDeepestView (top, rx, ry, out resx, out resy); + if (deep == null) + return FindDeepestOverlappedView (top, rx, ry, out resx, out resy); + if (deep != OverlappedTop) + return deep; + } + } + } + resx = x - startFrame.X; + resy = y - startFrame.Y; + return start; + } + + static bool OverlappedChildNeedsDisplay () + { + if (OverlappedTop == null) { + return false; + } + + foreach (var top in _toplevels) { + if (top != Current && top.Visible && (!top._needsDisplay.IsEmpty || top._childNeedsDisplay || top.LayoutNeeded)) { + OverlappedTop.SetSubViewNeedsDisplay (); + return true; + } + } + return false; + } + + + static bool SetCurrentOverlappedAsTop () + { + if (OverlappedTop == null && Current != Top && Current?.SuperView == null && Current?.Modal == false) { + if (Current.Frame != new Rect (0, 0, Driver.Cols, Driver.Rows)) { + Current.Frame = new Rect (0, 0, Driver.Cols, Driver.Rows); + } + Top = Current; + return true; + } + return false; + } + + /// + /// Move to the next Overlapped child from the . + /// + public static void OverlappedMoveNext () + { + if (OverlappedTop != null && !Current.Modal) { + lock (_toplevels) { + _toplevels.MoveNext (); + var isOverlapped = false; + while (_toplevels.Peek () == OverlappedTop || !_toplevels.Peek ().Visible) { + if (!isOverlapped && _toplevels.Peek () == OverlappedTop) { + isOverlapped = true; + } else if (isOverlapped && _toplevels.Peek () == OverlappedTop) { + MoveCurrent (Top); + break; + } + _toplevels.MoveNext (); + } + Current = _toplevels.Peek (); + } + } + } + + /// + /// Move to the previous Overlapped child from the . + /// + public static void OverlappedMovePrevious () + { + if (OverlappedTop != null && !Current.Modal) { + lock (_toplevels) { + _toplevels.MovePrevious (); + var isOverlapped = false; + while (_toplevels.Peek () == OverlappedTop || !_toplevels.Peek ().Visible) { + if (!isOverlapped && _toplevels.Peek () == OverlappedTop) { + isOverlapped = true; + } else if (isOverlapped && _toplevels.Peek () == OverlappedTop) { + MoveCurrent (Top); + break; + } + _toplevels.MovePrevious (); + } + Current = _toplevels.Peek (); + } + } + } + + /// + /// Move to the next Overlapped child from the and set it as the if it is not already. + /// + /// + /// + public static bool MoveToOverlappedChild (Toplevel top) + { + if (top.Visible && OverlappedTop != null && Current?.Modal == false) { + lock (_toplevels) { + _toplevels.MoveTo (top, 0, new ToplevelEqualityComparer ()); + Current = top; + } + return true; + } + return false; + } + + + /// + /// Brings the superview of the most focused overlapped view is on front. + /// + public static void BringOverlappedTopToFront () + { + if (OverlappedTop != null) { + return; + } + var top = FindTopFromView (Top?.MostFocused); + if (top != null && Top.Subviews.Count > 1 && Top.Subviews [Top.Subviews.Count - 1] != top) { + Top.BringSubviewToFront (top); + } + } + + + /// + /// Gets the current visible Toplevel overlapped child that matches the arguments pattern. + /// + /// The type. + /// The strings to exclude. + /// The matched view. + public static Toplevel GetTopOverlappedChild (Type type = null, string [] exclude = null) + { + if (Application.OverlappedTop == null) { + return null; + } + + foreach (var top in Application.OverlappedChildren) { + if (type != null && top.GetType () == type + && exclude?.Contains (top.Data.ToString ()) == false) { + return top; + } else if ((type != null && top.GetType () != type) + || (exclude?.Contains (top.Data.ToString ()) == true)) { + continue; + } + return top; + } + return null; + } + + } +} diff --git a/Terminal.Gui/Views/TreeView/Branch.cs b/Terminal.Gui/Views/TreeView/Branch.cs index f09fd14f31..56c2c5df6b 100644 --- a/Terminal.Gui/Views/TreeView/Branch.cs +++ b/Terminal.Gui/Views/TreeView/Branch.cs @@ -51,7 +51,6 @@ public Branch (TreeView tree, Branch parentBranchIfAny, T model) } } - /// /// Fetch the children of this branch. This method populates . /// @@ -164,7 +163,6 @@ public virtual void Draw (ConsoleDriver driver, ColorScheme colorScheme, int y, availableWidth -= lineBody.Length; } - // default behaviour is for model to use the color scheme // of the tree view var modelColor = textColor; diff --git a/Terminal.Gui/Views/TreeView/ObjectActivatedEventArgs.cs b/Terminal.Gui/Views/TreeView/ObjectActivatedEventArgs.cs index 46c5760762..861be6a49f 100644 --- a/Terminal.Gui/Views/TreeView/ObjectActivatedEventArgs.cs +++ b/Terminal.Gui/Views/TreeView/ObjectActivatedEventArgs.cs @@ -17,7 +17,6 @@ public class ObjectActivatedEventArgs where T : class { /// public T ActivatedObject { get; } - /// /// Creates a new instance documenting activation of the object /// diff --git a/Terminal.Gui/Views/TreeView/TreeView.cs b/Terminal.Gui/Views/TreeView/TreeView.cs index 286debb6e0..ec9141f6a7 100644 --- a/Terminal.Gui/Views/TreeView/TreeView.cs +++ b/Terminal.Gui/Views/TreeView/TreeView.cs @@ -614,7 +614,6 @@ public override bool ProcessKey (KeyEvent keyEvent) return base.ProcessKey (keyEvent); } - /// /// Triggers the event with the . /// @@ -1079,7 +1078,6 @@ public void AdjustSelectionToBranchEnd () GoToEnd (); } - /// /// Sets the selection to the next branch that matches the . /// @@ -1131,7 +1129,6 @@ public void EnsureVisible (T model) return; } - /*this -1 allows for possible horizontal scroll bar in the last row of the control*/ int leaveSpace = Style.LeaveLastRow ? 1 : 0; diff --git a/Terminal.Gui/Views/Window.cs b/Terminal.Gui/Views/Window.cs index 0643122c3b..a35badfb74 100644 --- a/Terminal.Gui/Views/Window.cs +++ b/Terminal.Gui/Views/Window.cs @@ -68,7 +68,6 @@ public override void Add (View view) AddMenuStatusBar (view); } - /// public override void Remove (View view) { diff --git a/Terminal.Gui/Views/Wizard/Wizard.cs b/Terminal.Gui/Views/Wizard/Wizard.cs index a87f0b34ae..1281587c00 100644 --- a/Terminal.Gui/Views/Wizard/Wizard.cs +++ b/Terminal.Gui/Views/Wizard/Wizard.cs @@ -127,7 +127,6 @@ public ustring HelpText { /// /// Initializes a new instance of the class using positioning. /// - /// public WizardStep () { BorderStyle = LineStyle.None; @@ -700,7 +699,7 @@ private void SizeStep (WizardStep step) /// /// Determines whether the is displayed as modal pop-up or not. /// - /// The default is true. The Wizard will be shown with a frame with and will behave like + /// The default is . The Wizard will be shown with a frame and title and will behave like /// any window. /// /// If set to false the Wizard will have no frame and will behave like any embedded . diff --git a/Terminal.sln b/Terminal.sln index a7a5f8edea..794a7e416f 100644 --- a/Terminal.sln +++ b/Terminal.sln @@ -14,6 +14,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Example", "Example\Example. EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E143FB1F-0B88-48CB-9086-72CDCECFCD22}" ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig .gitignore = .gitignore CODE_OF_CONDUCT.md = CODE_OF_CONDUCT.md CONTRIBUTING.md = CONTRIBUTING.md diff --git a/UICatalog/KeyBindingsDialog.cs b/UICatalog/KeyBindingsDialog.cs index d299b67eee..735b5277f4 100644 --- a/UICatalog/KeyBindingsDialog.cs +++ b/UICatalog/KeyBindingsDialog.cs @@ -7,10 +7,8 @@ namespace UICatalog { - class KeyBindingsDialog : Dialog { - static Dictionary CurrentBindings = new Dictionary(); private Command[] commands; private ListView commandsListView; diff --git a/UICatalog/Scenarios/AllViewsTester.cs b/UICatalog/Scenarios/AllViewsTester.cs index 7ead40e18f..0239a71044 100644 --- a/UICatalog/Scenarios/AllViewsTester.cs +++ b/UICatalog/Scenarios/AllViewsTester.cs @@ -49,7 +49,6 @@ public override void Init () Application.Top.ColorScheme = Colors.ColorSchemes [TopLevelColorScheme]; } - public override void Setup () { var statusBar = new StatusBar (new StatusItem [] { diff --git a/UICatalog/Scenarios/Animation.cs b/UICatalog/Scenarios/Animation.cs index 6fd9b483b8..541542579c 100644 --- a/UICatalog/Scenarios/Animation.cs +++ b/UICatalog/Scenarios/Animation.cs @@ -23,7 +23,6 @@ public override void Setup () { base.Setup (); - var imageView = new ImageView () { Width = Dim.Fill(), Height = Dim.Fill()-2, @@ -145,7 +144,6 @@ class ImageView : View { Rect oldSize = Rect.Empty; - internal void SetImage (Image image) { frameCount = image.Frames.Count; @@ -201,7 +199,6 @@ public override void Redraw (Rect bounds) brailleCache[currentFrame] = braille = GetBraille(matchSizes[currentFrame]); } - var lines = braille.Split('\n'); for(int y = 0; y < lines.Length;y++) diff --git a/UICatalog/Scenarios/BackgroundWorkerCollection.cs b/UICatalog/Scenarios/BackgroundWorkerCollection.cs index ba9a19044a..222bd97993 100644 --- a/UICatalog/Scenarios/BackgroundWorkerCollection.cs +++ b/UICatalog/Scenarios/BackgroundWorkerCollection.cs @@ -15,19 +15,19 @@ public class BackgroundWorkerCollection : Scenario { public override void Run () { - Application.Run (); + Application.Run (); } - class MdiMain : Toplevel { + class OverlappedMain : Toplevel { private WorkerApp workerApp; private bool canOpenWorkerApp; MenuBar menu; - public MdiMain () + public OverlappedMain () { - Data = "MdiMain"; + Data = "OverlappedMain"; - IsMdiContainer = true; + IsOverlappedContainer = true; workerApp = new WorkerApp () { Visible = false }; @@ -51,19 +51,19 @@ public MdiMain () }); Add (statusBar); - Activate += MdiMain_Activate; - Deactivate += MdiMain_Deactivate; + Activate += OverlappedMain_Activate; + Deactivate += OverlappedMain_Deactivate; - Closed += MdiMain_Closed; + Closed += OverlappedMain_Closed; Application.Iteration += () => { - if (canOpenWorkerApp && !workerApp.Running && Application.MdiTop.Running) { + if (canOpenWorkerApp && !workerApp.Running && Application.OverlappedTop.Running) { Application.Run (workerApp); } }; } - private void MdiMain_Closed (object sender, ToplevelEventArgs e) + private void OverlappedMain_Closed (object sender, ToplevelEventArgs e) { workerApp.Dispose (); Dispose (); @@ -82,12 +82,12 @@ private void Menu_MenuOpening (object sender, MenuOpeningEventArgs menu) } } - private void MdiMain_Deactivate (object sender, ToplevelEventArgs top) + private void OverlappedMain_Deactivate (object sender, ToplevelEventArgs top) { workerApp.WriteLog ($"{top.Toplevel.Data} deactivate."); } - private void MdiMain_Activate (object sender, ToplevelEventArgs top) + private void OverlappedMain_Activate (object sender, ToplevelEventArgs top) { workerApp.WriteLog ($"{top.Toplevel.Data} activate."); } @@ -99,17 +99,17 @@ private MenuBarItem View () Title = "WorkerApp", CheckType = MenuItemCheckStyle.Checked }; - var top = Application.MdiChildren?.Find ((x) => x.Data.ToString () == "WorkerApp"); + var top = Application.OverlappedChildren?.Find ((x) => x.Data.ToString () == "WorkerApp"); if (top != null) { item.Checked = top.Visible; } item.Action += () => { - var top = Application.MdiChildren.Find ((x) => x.Data.ToString () == "WorkerApp"); + var top = Application.OverlappedChildren.Find ((x) => x.Data.ToString () == "WorkerApp"); item.Checked = top.Visible = (bool)!item.Checked; if (top.Visible) { - top.ShowChild (); + Application.MoveToOverlappedChild (null); } else { - Application.MdiTop.SetNeedsDisplay (); + Application.OverlappedTop.SetNeedsDisplay (); } }; menuItems.Add (item); @@ -121,9 +121,9 @@ private MenuBarItem OpenedWindows () { var index = 1; List menuItems = new List (); - var sortedChildes = Application.MdiChildren; - sortedChildes.Sort (new ToplevelComparer ()); - foreach (var top in sortedChildes) { + var sortedChildren = Application.OverlappedChildren; + sortedChildren.Sort (new ToplevelComparer ()); + foreach (var top in sortedChildren) { if (top.Data.ToString () == "WorkerApp" && !top.Visible) { continue; } @@ -133,13 +133,13 @@ private MenuBarItem OpenedWindows () item.CheckType |= MenuItemCheckStyle.Checked; var topTitle = top is Window ? ((Window)top).Title : top.Data.ToString (); var itemTitle = item.Title.Substring (index.ToString ().Length + 1); - if (top == top.GetTopMdiChild () && topTitle == itemTitle) { + if (top == Application.GetTopOverlappedChild () && topTitle == itemTitle) { item.Checked = true; } else { item.Checked = false; } item.Action += () => { - top.ShowChild (); + Application.MoveToOverlappedChild (null); }; menuItems.Add (item); } diff --git a/UICatalog/Scenarios/ClassExplorer.cs b/UICatalog/Scenarios/ClassExplorer.cs index 7f282bbe1c..a7ae9e0972 100644 --- a/UICatalog/Scenarios/ClassExplorer.cs +++ b/UICatalog/Scenarios/ClassExplorer.cs @@ -183,7 +183,6 @@ private void TreeView_SelectionChanged (object sender, SelectionChangedEventArgs } } - if (val is ConstructorInfo ctor) { sb.AppendLine ($"Name:{ctor.Name}"); sb.AppendLine ($"Parameters:{(ctor.GetParameters ().Any () ? "" : "None")}"); @@ -263,7 +262,6 @@ private string GetRepresentation (object model) } - } catch (Exception ex) { return ex.Message; diff --git a/UICatalog/Scenarios/ComputedLayout.cs b/UICatalog/Scenarios/ComputedLayout.cs index 674ba0b31c..7e5468ebfc 100644 --- a/UICatalog/Scenarios/ComputedLayout.cs +++ b/UICatalog/Scenarios/ComputedLayout.cs @@ -254,7 +254,6 @@ public override void Setup () Application.Top.LayoutSubviews (); }; - // show positioning vertically using Pos.AnchorEnd var centerButton = new Button ("Center") { X = Pos.Center (), diff --git a/UICatalog/Scenarios/CsvEditor.cs b/UICatalog/Scenarios/CsvEditor.cs index 2caab1e603..4d61348d93 100644 --- a/UICatalog/Scenarios/CsvEditor.cs +++ b/UICatalog/Scenarios/CsvEditor.cs @@ -156,7 +156,6 @@ private void DeleteColum () return; } - try { tableView.Table.Columns.RemoveAt (tableView.SelectedColumn); tableView.Update (); @@ -238,7 +237,6 @@ private void MoveRow () var newIdx = Math.Min (Math.Max (0, int.Parse (newOrdinal)), tableView.Table.Rows.Count - 1); - if (newIdx == oldIdx) return; @@ -361,7 +359,6 @@ private void AddColumn () } - } private void Save () @@ -409,7 +406,6 @@ private void Open (string filename) int lineNumber = 0; currentFile = null; - try { using var reader = new CsvReader (File.OpenText (filename), CultureInfo.InvariantCulture); @@ -499,7 +495,6 @@ private void ClearColumnStyles () tableView.Update (); } - private void CloseExample () { tableView.Table = null; diff --git a/UICatalog/Scenarios/Dialogs.cs b/UICatalog/Scenarios/Dialogs.cs index 85a86f153e..9e5399a522 100644 --- a/UICatalog/Scenarios/Dialogs.cs +++ b/UICatalog/Scenarios/Dialogs.cs @@ -9,7 +9,6 @@ namespace UICatalog.Scenarios { public class Dialogs : Scenario { static int CODE_POINT = '你'; // We know this is a wide char - public override void Setup () { var frame = new FrameView ("Dialog Options") { @@ -115,7 +114,6 @@ public override void Setup () }; frame.Add (styleRadioGroup); - frame.ForceValidatePosDim = true; void Top_Loaded (object sender, EventArgs args) { diff --git a/UICatalog/Scenarios/DynamicMenuBar.cs b/UICatalog/Scenarios/DynamicMenuBar.cs index cc10009fc8..18ce15d63b 100644 --- a/UICatalog/Scenarios/DynamicMenuBar.cs +++ b/UICatalog/Scenarios/DynamicMenuBar.cs @@ -200,7 +200,6 @@ public DynamicMenuBarSample () : base () Add (_frmMenu); - var _frmMenuDetails = new DynamicMenuBarDetails ("Menu Details:") { X = Pos.Right (_frmMenu), Y = Pos.Top (_frmMenu), @@ -286,7 +285,6 @@ public DynamicMenuBarSample () : base () } }; - var _btnOk = new Button ("Ok") { X = Pos.Right (_frmMenu) + 20, Y = Pos.Bottom (_frmMenuDetails), @@ -493,10 +491,8 @@ public DynamicMenuBarSample () : base () SetFrameDetails (null); }; - SetFrameDetails (); - var ustringConverter = new UStringValueConverter (); var listWrapperConverter = new ListWrapperConverter (); @@ -504,7 +500,6 @@ public DynamicMenuBarSample () : base () var lblParent = new Binding (this, "Parent", _lblParent, "Text", ustringConverter); var lstMenus = new Binding (this, "Menus", _lstMenus, "Source", listWrapperConverter); - void SetFrameDetails (MenuItem menuBarItem = null) { MenuItem menuItem; @@ -626,7 +621,6 @@ void UpdateMenuItem (MenuItem _currentEditMenuBarItem, DynamicMenuItem menuItem, SetFrameDetails (_currentEditMenuBarItem); } - //_frmMenuDetails.Initialized += (s, e) => _frmMenuDetails.Enabled = false; } } @@ -845,7 +839,6 @@ bool CheckShortcut (Key k, bool pre) } - public DynamicMenuItem EnterMenuItem () { var valid = false; diff --git a/UICatalog/Scenarios/DynamicStatusBar.cs b/UICatalog/Scenarios/DynamicStatusBar.cs index b0635966bb..c2aed4e80b 100644 --- a/UICatalog/Scenarios/DynamicStatusBar.cs +++ b/UICatalog/Scenarios/DynamicStatusBar.cs @@ -133,7 +133,6 @@ public DynamicStatusBarSample () : base () Add (_frmStatusBar); - var _frmStatusBarDetails = new DynamicStatusBarDetails ("StatusBar Item Details:") { X = Pos.Right (_frmStatusBar), Y = Pos.Top (_frmStatusBar), @@ -268,16 +267,13 @@ public DynamicStatusBarSample () : base () SetFrameDetails (null); }; - SetFrameDetails (); - var ustringConverter = new UStringValueConverter (); var listWrapperConverter = new ListWrapperConverter (); var lstItems = new Binding (this, "Items", _lstItems, "Source", listWrapperConverter); - void SetFrameDetails (StatusItem statusItem = null) { StatusItem newStatusItem; @@ -331,7 +327,6 @@ void UpdateStatusItem (StatusItem _currentEditStatusItem, DynamicStatusItem stat SetFrameDetails (_currentEditStatusItem); } - //_frmStatusBarDetails.Initialized += (s, e) => _frmStatusBarDetails.Enabled = false; } @@ -463,7 +458,6 @@ bool CheckShortcut (Key k, bool pre) Add (_btnShortcut); } - public DynamicStatusItem EnterStatusItem () { var valid = false; diff --git a/UICatalog/Scenarios/FileDialogExamples.cs b/UICatalog/Scenarios/FileDialogExamples.cs index bc54811cbe..0f44f9a634 100644 --- a/UICatalog/Scenarios/FileDialogExamples.cs +++ b/UICatalog/Scenarios/FileDialogExamples.cs @@ -33,7 +33,6 @@ public override void Setup () cbMustExist = new CheckBox ("Must Exist") { Checked = true, Y = y++, X = x }; Win.Add (cbMustExist); - cbUnicode = new CheckBox ("UseUnicode") { Checked = FileDialogStyle.DefaultUseUnicodeCharacters, Y = y++, X = x }; Win.Add (cbUnicode); diff --git a/UICatalog/Scenarios/GraphViewExample.cs b/UICatalog/Scenarios/GraphViewExample.cs index d822200ae0..13f2ec82d7 100644 --- a/UICatalog/Scenarios/GraphViewExample.cs +++ b/UICatalog/Scenarios/GraphViewExample.cs @@ -35,7 +35,6 @@ public override void Setup () ()=>MultiBarGraph() //7 }; - var menu = new MenuBar (new MenuBarItem [] { new MenuBarItem ("_File", new MenuItem [] { new MenuItem ("Scatter _Plot", "",()=>graphs[currentGraph = 0]()), @@ -67,10 +66,8 @@ public override void Setup () Height = 20, }; - Win.Add (graphView); - var frameRight = new FrameView ("About") { X = Pos.Right (graphView) + 1, Y = 0, @@ -78,7 +75,6 @@ public override void Setup () Height = Dim.Fill (), }; - frameRight.Add (about = new TextView () { Width = Dim.Fill (), Height = Dim.Fill () @@ -86,7 +82,6 @@ public override void Setup () Win.Add (frameRight); - var statusBar = new StatusBar (new StatusItem [] { new StatusItem(Application.QuitKey, $"{Application.QuitKey} to Quit", () => Quit()), new StatusItem(Key.CtrlMask | Key.G, "~^G~ Next", ()=>graphs[currentGraph++%graphs.Length]()), @@ -135,7 +130,6 @@ private void MultiBarGraph () graphView.AxisX.ShowLabelsEvery = 0; graphView.AxisX.Minimum = 0; - graphView.AxisY.Minimum = 0; var legend = new LegendAnnotation (new Rect (graphView.Bounds.Width - 20, 0, 20, 5)); @@ -179,14 +173,12 @@ private void SetupLineGraph () graphView.Series.Add (points); graphView.Annotations.Add (line); - randomPoints = new List (); for (int i = 0; i < 10; i++) { randomPoints.Add (new PointF (r.Next (100), r.Next (100))); } - var points2 = new ScatterSeries () { Points = randomPoints, Fill = new GraphCellToRender ('x', red) @@ -471,7 +463,6 @@ private void SetupPopulationPyramid () }; graphView.Series.Add (malesSeries); - // Females var femalesSeries = new BarSeries () { Orientation = Orientation.Horizontal, @@ -501,7 +492,6 @@ private void SetupPopulationPyramid () } }; - var softStiple = new GraphCellToRender ('\u2591'); var mediumStiple = new GraphCellToRender ('\u2592'); @@ -587,7 +577,6 @@ private void SetupDisco () } graphView.SetNeedsDisplay (); - // while the equaliser is showing return graphView.Series.Contains (series); }; diff --git a/UICatalog/Scenarios/LineCanvasExperiment.cs b/UICatalog/Scenarios/LineCanvasExperiment.cs index fd98ed02fd..5f68eb98e0 100644 --- a/UICatalog/Scenarios/LineCanvasExperiment.cs +++ b/UICatalog/Scenarios/LineCanvasExperiment.cs @@ -7,7 +7,6 @@ namespace UICatalog.Scenarios { [ScenarioCategory ("LineCanvas")] public class LineCanvasExperiment : Scenario { - public override void Init () { Application.Init (); diff --git a/UICatalog/Scenarios/LineDrawing.cs b/UICatalog/Scenarios/LineDrawing.cs index eac71e9223..3bbd1caf74 100644 --- a/UICatalog/Scenarios/LineDrawing.cs +++ b/UICatalog/Scenarios/LineDrawing.cs @@ -26,7 +26,6 @@ public override void Setup () Width = Dim.Fill () }; - tools.ColorChanged += (c) => canvas.SetColor (c); tools.SetStyle += (b) => canvas.BorderStyle = b; @@ -183,7 +182,6 @@ public override bool OnMouseEvent (MouseEvent mouseEvent) length = end.X - start.X; } - canvases [currentColor].AddLine ( start, length, diff --git a/UICatalog/Scenarios/LineViewExample.cs b/UICatalog/Scenarios/LineViewExample.cs index 13fe14397f..cf1dab920d 100644 --- a/UICatalog/Scenarios/LineViewExample.cs +++ b/UICatalog/Scenarios/LineViewExample.cs @@ -26,7 +26,6 @@ public override void Setup () }); Application.Top.Add (menu); - Win.Add (new Label ("Regular Line") { Y = 0 }); // creates a horizontal line @@ -56,7 +55,6 @@ public override void Setup () Win.Add (shortLine); - Win.Add (new Label ("Arrow Line") { Y = 6 }); // creates a horizontal line @@ -69,7 +67,6 @@ public override void Setup () Win.Add (arrowLine); - Win.Add (new Label ("Vertical Line") { Y = 9,X=11 }); // creates a horizontal line @@ -79,7 +76,6 @@ public override void Setup () Win.Add (verticalLine); - Win.Add (new Label ("Vertical Arrow") { Y = 11, X = 28 }); // creates a horizontal line diff --git a/UICatalog/Scenarios/ListsAndCombos.cs b/UICatalog/Scenarios/ListsAndCombos.cs index 848083793f..a71b857a38 100644 --- a/UICatalog/Scenarios/ListsAndCombos.cs +++ b/UICatalog/Scenarios/ListsAndCombos.cs @@ -109,7 +109,6 @@ public override void Setup () scrollBarCbx.Refresh (); }; - var btnMoveUp = new Button ("Move _Up") { X = 1, Y = Pos.Bottom(lbListView), diff --git a/UICatalog/Scenarios/Snake.cs b/UICatalog/Scenarios/Snake.cs index 3f4f4e8c65..7f21a07b87 100644 --- a/UICatalog/Scenarios/Snake.cs +++ b/UICatalog/Scenarios/Snake.cs @@ -25,7 +25,6 @@ public override void Setup () Height = state.Height }; - Win.Add (snakeView); Stopwatch sw = new Stopwatch (); @@ -115,7 +114,6 @@ public override void Redraw (Rect bounds) AddRune (p.Key.X, p.Key.Y, p.Value); } - Driver.SetAttribute (red); AddRune (State.Apple.X, State.Apple.Y, 'A'); Driver.SetAttribute (white); diff --git a/UICatalog/Scenarios/SyntaxHighlighting.cs b/UICatalog/Scenarios/SyntaxHighlighting.cs index fa165f5a91..c59141f263 100644 --- a/UICatalog/Scenarios/SyntaxHighlighting.cs +++ b/UICatalog/Scenarios/SyntaxHighlighting.cs @@ -48,7 +48,6 @@ public override void Setup () new StatusItem(Application.QuitKey, $"{Application.QuitKey} to Quit", () => Quit()), }); - Application.Top.Add (statusBar); } @@ -70,7 +69,6 @@ private class SqlTextView : TextView { private Attribute white; private Attribute magenta; - public void Init () { keywords.Add ("select"); @@ -194,7 +192,6 @@ private string IdxToWord (List line, int idx) new string (line.Select (r => (char)r).ToArray ()), "\\b"); - int count = 0; string current = null; diff --git a/UICatalog/Scenarios/TabViewExample.cs b/UICatalog/Scenarios/TabViewExample.cs index 2a321563a3..875fa59c1d 100644 --- a/UICatalog/Scenarios/TabViewExample.cs +++ b/UICatalog/Scenarios/TabViewExample.cs @@ -59,7 +59,6 @@ public override void Setup () Height = 20, }; - tabView.AddTab (new TabView.Tab ("Tab1", new Label ("hodor!")), false); tabView.AddTab (new TabView.Tab ("Tab2", new Label ("durdur")), false); tabView.AddTab (new TabView.Tab ("Interactive Tab", GetInteractiveTab ()), false); @@ -150,7 +149,6 @@ private View GetInteractiveTab () return interactiveTab; } - private View GetBigTextFileTab () { diff --git a/UICatalog/Scenarios/TableEditor.cs b/UICatalog/Scenarios/TableEditor.cs index e1dd9c7303..741596871a 100644 --- a/UICatalog/Scenarios/TableEditor.cs +++ b/UICatalog/Scenarios/TableEditor.cs @@ -79,7 +79,6 @@ public override void Setup () }), }); - Application.Top.Add (menu); var statusBar = new StatusBar (new StatusItem [] { @@ -365,7 +364,6 @@ private void TableViewKeyPress (object sender, KeyEventEventArgs e) e.Handled = true; } - } private void ClearColumnStyles () diff --git a/UICatalog/Scenarios/Text.cs b/UICatalog/Scenarios/Text.cs index 42702e319d..2575215597 100644 --- a/UICatalog/Scenarios/Text.cs +++ b/UICatalog/Scenarios/Text.cs @@ -8,7 +8,6 @@ using Terminal.Gui.TextValidateProviders; - namespace UICatalog.Scenarios { [ScenarioMetadata (Name: "Text Input Controls", Description: "Tests all text input controls")] [ScenarioCategory ("Controls")] @@ -234,7 +233,6 @@ void TextView_DrawContent (object sender, DrawEventArgs e) } }; - Win.Add (labelAppendAutocomplete); Win.Add (appendAutocompleteTextField); } diff --git a/UICatalog/Scenarios/TextAlignmentsAndDirection.cs b/UICatalog/Scenarios/TextAlignmentsAndDirection.cs index 9d8886b580..48c58e6a6f 100644 --- a/UICatalog/Scenarios/TextAlignmentsAndDirection.cs +++ b/UICatalog/Scenarios/TextAlignmentsAndDirection.cs @@ -97,7 +97,6 @@ public override void Setup () Win.Add (container); - // Edit Text var editText = new TextView () { @@ -131,7 +130,6 @@ public override void Setup () Win.Add (editText); - // JUSTIFY CHECKBOX var justifyCheckbox = new CheckBox ("Justify") { @@ -162,7 +160,6 @@ public override void Setup () Win.Add (justifyCheckbox); - // Direction Options var directionsEnum = Enum.GetValues (typeof (Terminal.Gui.TextDirection)).Cast ().ToList (); diff --git a/UICatalog/Scenarios/TextViewAutocompletePopup.cs b/UICatalog/Scenarios/TextViewAutocompletePopup.cs index aaffed50bf..4f284608a0 100644 --- a/UICatalog/Scenarios/TextViewAutocompletePopup.cs +++ b/UICatalog/Scenarios/TextViewAutocompletePopup.cs @@ -27,7 +27,6 @@ public override void Setup () var text = " jamp jemp jimp jomp jump"; - var menu = new MenuBar (new MenuBarItem [] { new MenuBarItem ("_File", new MenuItem [] { miMultiline = new MenuItem ("_Multiline", "", () => Multiline()){CheckType = MenuItemCheckStyle.Checked}, diff --git a/UICatalog/Scenarios/Threading.cs b/UICatalog/Scenarios/Threading.cs index 40f44cb292..23d7d27e49 100644 --- a/UICatalog/Scenarios/Threading.cs +++ b/UICatalog/Scenarios/Threading.cs @@ -44,7 +44,6 @@ public override void Setup () _itemsList.SetSource (items); }; - _btnActionCancel = new Button (1, 1, "Cancelable Load Items"); _btnActionCancel.Clicked += (s,e) => Application.MainLoop.Invoke (CallLoadItemsAsync); diff --git a/UICatalog/Scenarios/TileViewNesting.cs b/UICatalog/Scenarios/TileViewNesting.cs index 4fd2b4980f..07703f4062 100644 --- a/UICatalog/Scenarios/TileViewNesting.cs +++ b/UICatalog/Scenarios/TileViewNesting.cs @@ -37,7 +37,6 @@ public override void Setup () textField.TextChanged += (s,e) => SetupTileView (); - cbHorizontal = new CheckBox ("Horizontal") { X = Pos.Right (textField) + 1 }; @@ -111,10 +110,8 @@ private void SetupTileView () root.Tiles.ElementAt (1).ContentView.Add (CreateContentControl (2)); root.Tiles.ElementAt (1).Title = (bool)cbTitles.Checked ? $"View 2" : string.Empty; - root.LineStyle = (bool)border ? LineStyle.Rounded : LineStyle.None; - workArea.Add (root); if (numberOfViews == 1) { diff --git a/UICatalog/Scenarios/TreeUseCases.cs b/UICatalog/Scenarios/TreeUseCases.cs index 00f339bbbf..48c120a392 100644 --- a/UICatalog/Scenarios/TreeUseCases.cs +++ b/UICatalog/Scenarios/TreeUseCases.cs @@ -75,7 +75,6 @@ private void LoadRooms () currentTree.Dispose (); } - var tree = new TreeView () { X = 0, Y = 0, @@ -91,7 +90,6 @@ private void LoadRooms () } - private abstract class GameObject { } @@ -99,7 +97,6 @@ private class Army : GameObject { public string Designation { get; set; } public List Units { get; set; } - public override string ToString () { return Designation; @@ -132,7 +129,6 @@ public IEnumerable GetChildren (GameObject model) } } - private void LoadArmies (bool useDelegate) { var army1 = new Army () { @@ -149,7 +145,6 @@ private void LoadArmies (bool useDelegate) currentTree.Dispose (); } - var tree = new TreeView () { X = 0, Y = 0, @@ -182,7 +177,6 @@ private void LoadSimpleNodes () currentTree.Dispose (); } - var tree = new TreeView () { X = 0, Y = 0, diff --git a/UICatalog/Scenarios/TreeViewFileSystem.cs b/UICatalog/Scenarios/TreeViewFileSystem.cs index 10be681d7e..c92c5c763c 100644 --- a/UICatalog/Scenarios/TreeViewFileSystem.cs +++ b/UICatalog/Scenarios/TreeViewFileSystem.cs @@ -307,7 +307,6 @@ private void SetMultiSelect () treeViewFiles.MultiSelect = (bool)miMultiSelect.Checked; } - private void SetCustomColors () { var hidden = new ColorScheme { diff --git a/UICatalog/Scenarios/ViewExperiments.cs b/UICatalog/Scenarios/ViewExperiments.cs index edb57190ee..08e597ac38 100644 --- a/UICatalog/Scenarios/ViewExperiments.cs +++ b/UICatalog/Scenarios/ViewExperiments.cs @@ -190,7 +190,6 @@ public FramesEditor (NStack.ustring title, View viewToEdit) viewToEdit.Y = Pos.Center () + 4; - //rbBorderStyle.SelectedItemChanged += (e) => { // viewToEdit.Border.BorderStyle = (BorderStyle)e.SelectedItem; // viewToEdit.SetNeedsDisplay (); diff --git a/UnitTests/Application/ApplicationTests.cs b/UnitTests/Application/ApplicationTests.cs index 159c887890..698e179e4d 100644 --- a/UnitTests/Application/ApplicationTests.cs +++ b/UnitTests/Application/ApplicationTests.cs @@ -28,7 +28,7 @@ void Pre_Init_State () Assert.Null (Application.MainLoop); Assert.Null (Application.Iteration); Assert.Null (Application.RootMouseEvent); - Assert.Null (Application.Resized); + Assert.Null (Application.TerminalResized); } void Post_Init_State () @@ -40,7 +40,7 @@ void Post_Init_State () Assert.NotNull (Application.MainLoop); Assert.Null (Application.Iteration); Assert.Null (Application.RootMouseEvent); - Assert.Null (Application.Resized); + Assert.Null (Application.TerminalResized); } void Init () @@ -124,11 +124,10 @@ public void Init_Unbalanced_Throwss () Assert.Null (Application.Driver); } - class TestToplevel : Toplevel { public TestToplevel () { - IsMdiContainer = false; + IsOverlappedContainer = false; } } @@ -509,7 +508,7 @@ public void SetCurrentAsTop_Run_A_Not_Modal_Toplevel_Make_It_The_Current_Applica t3.RequestStop (); t2.RequestStop (); }; - // Now this will close the MdiContainer when all MdiChildes was closed + // Now this will close the OverlappedContainer when all OverlappedChildren was closed t2.Closed += (s,_) => { t1.RequestStop (); }; @@ -558,7 +557,7 @@ public void Internal_Properties_Correct () Assert.Equal (Application.Top, rs.Toplevel); Assert.Null (Application.MouseGrabView); // public Assert.Null (Application.WantContinuousButtonPressedView); // public - Assert.False (Application.ShowChild (Application.Top)); + Assert.False (Application.MoveToOverlappedChild (Application.Top)); } #region KeyboardTests @@ -808,7 +807,7 @@ public void EnsuresTopOnFront_CanFocus_True_By_Keyboard_And_Mouse () Assert.True (win2.HasFocus); Assert.Equal ("win2", ((Window)top.Subviews [top.Subviews.Count - 1]).Title); win2.MouseEvent (new MouseEvent () { Flags = MouseFlags.Button1Released }); - Assert.Null (Toplevel.dragPosition); + Assert.Null (Toplevel._dragPosition); } [Fact] @@ -860,17 +859,11 @@ public void EnsuresTopOnFront_CanFocus_False_By_Keyboard_And_Mouse () Assert.True (win2.HasFocus); Assert.Equal ("win2", ((Window)top.Subviews [top.Subviews.Count - 1]).Title); win2.MouseEvent (new MouseEvent () { Flags = MouseFlags.Button1Released }); - Assert.Null (Toplevel.dragPosition); + Assert.Null (Toplevel._dragPosition); } #endregion - [Fact, AutoInitShutdown] - public void GetSupportedCultures_Method () - { - var cultures = Application.GetSupportedCultures (); - Assert.Equal (cultures.Count, Application.SupportedCultures.Count); - } #region mousegrabtests [Fact, AutoInitShutdown] diff --git a/UnitTests/Application/MainLoopTests.cs b/UnitTests/Application/MainLoopTests.cs index 6498d4745b..36915f712d 100644 --- a/UnitTests/Application/MainLoopTests.cs +++ b/UnitTests/Application/MainLoopTests.cs @@ -95,7 +95,7 @@ public void AddIdle_Function_GetsCalled_OnIteration () }; ml.AddIdle (fn); - ml.MainIteration (); + ml.RunIteration (); Assert.Equal (1, functionCalled); } @@ -111,7 +111,7 @@ public void RemoveIdle_Function_NotCalled () }; Assert.False (ml.RemoveIdle (fn)); - ml.MainIteration (); + ml.RunIteration (); Assert.Equal (0, functionCalled); } @@ -128,7 +128,7 @@ public void AddThenRemoveIdle_Function_NotCalled () ml.AddIdle (fn); Assert.True (ml.RemoveIdle (fn)); - ml.MainIteration (); + ml.RunIteration (); Assert.Equal (0, functionCalled); } @@ -145,20 +145,20 @@ public void AddTwice_Function_CalledTwice () ml.AddIdle (fn); ml.AddIdle (fn); - ml.MainIteration (); + ml.RunIteration (); Assert.Equal (2, functionCalled); Assert.Equal (2, ml.IdleHandlers.Count); functionCalled = 0; Assert.True (ml.RemoveIdle (fn)); Assert.Single (ml.IdleHandlers); - ml.MainIteration (); + ml.RunIteration (); Assert.Equal (1, functionCalled); functionCalled = 0; Assert.True (ml.RemoveIdle (fn)); Assert.Empty (ml.IdleHandlers); - ml.MainIteration (); + ml.RunIteration (); Assert.Equal (0, functionCalled); Assert.False (ml.RemoveIdle (fn)); } @@ -370,7 +370,6 @@ public void AddTimer_In_Parallel_Wont_Throw () Assert.Equal (2, callbackCount); } - class MillisecondTolerance : IEqualityComparer { int _tolerance = 0; public MillisecondTolerance (int tolerance) { _tolerance = tolerance; } @@ -497,7 +496,7 @@ public void Invoke_Adds_Idle () var actionCalled = 0; ml.Invoke (() => { actionCalled++; }); - ml.MainIteration (); + ml.RunIteration (); Assert.Equal (1, actionCalled); } @@ -522,7 +521,7 @@ public bool EventsPending (bool wait) throw new NotImplementedException (); } - public void MainIteration () + public void Iteration () { throw new NotImplementedException (); } @@ -567,7 +566,6 @@ private static void RunTest (Random r, TextField tf, int numPasses, int numIncre _wakeUp.Reset (); for (var i = 0; i < numIncrements; i++) Launch (r, tf, (j + 1) * numIncrements); - while (tbCounter != (j + 1) * numIncrements) // Wait for tbCounter to reach expected value { var tbNow = tbCounter; diff --git a/UnitTests/Application/StackExtensionsTests.cs b/UnitTests/Application/StackExtensionsTests.cs index 58b1cc8c46..0eb5c16724 100644 --- a/UnitTests/Application/StackExtensionsTests.cs +++ b/UnitTests/Application/StackExtensionsTests.cs @@ -8,16 +8,16 @@ public class StackExtensionsTests { [Fact] public void Stack_Toplevels_CreateToplevels () { - Stack toplevels = CreateToplevels (); + Stack Toplevels = CreateToplevels (); - int index = toplevels.Count - 1; - foreach (var top in toplevels) { + int index = Toplevels.Count - 1; + foreach (var top in Toplevels) { if (top.GetType () == typeof (Toplevel)) Assert.Equal ("Top", top.Id); else Assert.Equal ($"w{index}", top.Id); index--; } - var tops = toplevels.ToArray (); + var tops = Toplevels.ToArray (); Assert.Equal ("w4", tops [0].Id); Assert.Equal ("w3", tops [1].Id); @@ -29,15 +29,15 @@ public void Stack_Toplevels_CreateToplevels () [Fact] public void Stack_Toplevels_Replace () { - Stack toplevels = CreateToplevels (); + Stack Toplevels = CreateToplevels (); var valueToReplace = new Window () { Id = "w1" }; var valueToReplaceWith = new Window () { Id = "new" }; var comparer = new ToplevelEqualityComparer (); - toplevels.Replace (valueToReplace, valueToReplaceWith, comparer); + Toplevels.Replace (valueToReplace, valueToReplaceWith, comparer); - var tops = toplevels.ToArray (); + var tops = Toplevels.ToArray (); Assert.Equal ("w4", tops [0].Id); Assert.Equal ("w3", tops [1].Id); @@ -49,14 +49,14 @@ public void Stack_Toplevels_Replace () [Fact] public void Stack_Toplevels_Swap () { - Stack toplevels = CreateToplevels (); + Stack Toplevels = CreateToplevels (); var valueToSwapFrom = new Window () { Id = "w3" }; var valueToSwapTo = new Window () { Id = "w1" }; var comparer = new ToplevelEqualityComparer (); - toplevels.Swap (valueToSwapFrom, valueToSwapTo, comparer); + Toplevels.Swap (valueToSwapFrom, valueToSwapTo, comparer); - var tops = toplevels.ToArray (); + var tops = Toplevels.ToArray (); Assert.Equal ("w4", tops [0].Id); Assert.Equal ("w1", tops [1].Id); @@ -68,11 +68,11 @@ public void Stack_Toplevels_Swap () [Fact] public void Stack_Toplevels_MoveNext () { - Stack toplevels = CreateToplevels (); + Stack Toplevels = CreateToplevels (); - toplevels.MoveNext (); + Toplevels.MoveNext (); - var tops = toplevels.ToArray (); + var tops = Toplevels.ToArray (); Assert.Equal ("w3", tops [0].Id); Assert.Equal ("w2", tops [1].Id); @@ -84,11 +84,11 @@ public void Stack_Toplevels_MoveNext () [Fact] public void Stack_Toplevels_MovePrevious () { - Stack toplevels = CreateToplevels (); + Stack Toplevels = CreateToplevels (); - toplevels.MovePrevious (); + Toplevels.MovePrevious (); - var tops = toplevels.ToArray (); + var tops = Toplevels.ToArray (); Assert.Equal ("Top", tops [0].Id); Assert.Equal ("w4", tops [1].Id); @@ -100,24 +100,24 @@ public void Stack_Toplevels_MovePrevious () [Fact] public void ToplevelEqualityComparer_GetHashCode () { - Stack toplevels = CreateToplevels (); + Stack Toplevels = CreateToplevels (); // Only allows unique keys var hCodes = new HashSet (); - foreach (var top in toplevels) Assert.True (hCodes.Add (top.GetHashCode ())); + foreach (var top in Toplevels) Assert.True (hCodes.Add (top.GetHashCode ())); } [Fact] public void Stack_Toplevels_FindDuplicates () { - Stack toplevels = CreateToplevels (); + Stack Toplevels = CreateToplevels (); var comparer = new ToplevelEqualityComparer (); - toplevels.Push (new Toplevel () { Id = "w4" }); - toplevels.Push (new Toplevel () { Id = "w1" }); + Toplevels.Push (new Toplevel () { Id = "w4" }); + Toplevels.Push (new Toplevel () { Id = "w1" }); - var dup = toplevels.FindDuplicates (comparer).ToArray (); + var dup = Toplevels.FindDuplicates (comparer).ToArray (); Assert.Equal ("w4", dup [0].Id); Assert.Equal ("w1", dup [^1].Id); @@ -126,24 +126,24 @@ public void Stack_Toplevels_FindDuplicates () [Fact] public void Stack_Toplevels_Contains () { - Stack toplevels = CreateToplevels (); + Stack Toplevels = CreateToplevels (); var comparer = new ToplevelEqualityComparer (); - Assert.True (toplevels.Contains (new Window () { Id = "w2" }, comparer)); - Assert.False (toplevels.Contains (new Toplevel () { Id = "top2" }, comparer)); + Assert.True (Toplevels.Contains (new Window () { Id = "w2" }, comparer)); + Assert.False (Toplevels.Contains (new Toplevel () { Id = "top2" }, comparer)); } [Fact] public void Stack_Toplevels_MoveTo () { - Stack toplevels = CreateToplevels (); + Stack Toplevels = CreateToplevels (); var valueToMove = new Window () { Id = "w1" }; var comparer = new ToplevelEqualityComparer (); - toplevels.MoveTo (valueToMove, 1, comparer); + Toplevels.MoveTo (valueToMove, 1, comparer); - var tops = toplevels.ToArray (); + var tops = Toplevels.ToArray (); Assert.Equal ("w4", tops [0].Id); Assert.Equal ("w1", tops [1].Id); @@ -155,14 +155,14 @@ public void Stack_Toplevels_MoveTo () [Fact] public void Stack_Toplevels_MoveTo_From_Last_To_Top () { - Stack toplevels = CreateToplevels (); + Stack Toplevels = CreateToplevels (); var valueToMove = new Window () { Id = "Top" }; var comparer = new ToplevelEqualityComparer (); - toplevels.MoveTo (valueToMove, 0, comparer); + Toplevels.MoveTo (valueToMove, 0, comparer); - var tops = toplevels.ToArray (); + var tops = Toplevels.ToArray (); Assert.Equal ("Top", tops [0].Id); Assert.Equal ("w4", tops [1].Id); @@ -171,18 +171,17 @@ public void Stack_Toplevels_MoveTo_From_Last_To_Top () Assert.Equal ("w1", tops [^1].Id); } - private Stack CreateToplevels () { - var toplevels = new Stack (); + var Toplevels = new Stack (); - toplevels.Push (new Toplevel () { Id = "Top" }); - toplevels.Push (new Window () { Id = "w1" }); - toplevels.Push (new Window () { Id = "w2" }); - toplevels.Push (new Window () { Id = "w3" }); - toplevels.Push (new Window () { Id = "w4" }); + Toplevels.Push (new Toplevel () { Id = "Top" }); + Toplevels.Push (new Window () { Id = "w1" }); + Toplevels.Push (new Window () { Id = "w2" }); + Toplevels.Push (new Window () { Id = "w3" }); + Toplevels.Push (new Window () { Id = "w4" }); - return toplevels; + return Toplevels; } } } diff --git a/UnitTests/Configuration/ConfigurationMangerTests.cs b/UnitTests/Configuration/ConfigurationMangerTests.cs index 605f1bceed..7390d6092c 100644 --- a/UnitTests/Configuration/ConfigurationMangerTests.cs +++ b/UnitTests/Configuration/ConfigurationMangerTests.cs @@ -103,7 +103,6 @@ public void DeepMemberwiseCopyTest () Assert.Equal (dictSrc ["Disabled"], dictCopy ["Disabled"]); Assert.Equal (dictDest ["Normal"], dictCopy ["Normal"]); - } //[Fact ()] @@ -178,7 +177,6 @@ public void DeepMemberwiseCopyTest () // Assert.True (false, "This test needs an implementation"); //} - /// /// Save the `config.json` file; this can be used to update the file in `Terminal.Gui.Resources.config.json'. /// @@ -261,7 +259,6 @@ public void Reset_and_ResetLoadWithLibraryResourcesOnly_are_same () ConfigurationManager.Settings ["Application.EnableConsoleScrolling"].PropertyValue = true; ConfigurationManager.Settings.Apply (); - ConfigurationManager.Locations = ConfigLocations.DefaultOnly; // act @@ -280,7 +277,6 @@ public void Reset_and_ResetLoadWithLibraryResourcesOnly_are_same () } - [Fact] public void TestConfigProperties () { @@ -622,7 +618,6 @@ public void TestConfigurationManagerInvalidJsonThrows () jsonException = Assert.Throws (() => ConfigurationManager.Settings.Update (json, "test")); Assert.Equal ("Both Foreground and Background colors must be provided.", jsonException.Message); - // Unknown proeprty json = @" { @@ -761,7 +756,6 @@ public void LoadConfigurationFromAllSources_ShouldLoadSettingsFromAllSources () //Assert.Equal ("AppSpecific", ConfigurationManager.Config.Settings.TestSetting); } - [Fact] public void Load_FiresUpdated () { diff --git a/UnitTests/Configuration/JsonConverterTests.cs b/UnitTests/Configuration/JsonConverterTests.cs index ece53145a9..51bf898cd8 100644 --- a/UnitTests/Configuration/JsonConverterTests.cs +++ b/UnitTests/Configuration/JsonConverterTests.cs @@ -40,7 +40,6 @@ public void TestColorDeserializationFromHumanReadableColorNames (string colorNam Assert.Equal (expectedColor, actualColor); } - [Theory] [InlineData (Color.Black, "Black")] [InlineData (Color.Blue, "Blue")] diff --git a/UnitTests/Configuration/ThemeScopeTests.cs b/UnitTests/Configuration/ThemeScopeTests.cs index 34199a0086..206b715d77 100644 --- a/UnitTests/Configuration/ThemeScopeTests.cs +++ b/UnitTests/Configuration/ThemeScopeTests.cs @@ -17,7 +17,6 @@ public class ThemeScopeTests { } }; - [Fact] public void ThemeManager_ClassMethodsWork () { diff --git a/UnitTests/ConsoleDrivers/ConsoleDriverTests.cs b/UnitTests/ConsoleDrivers/ConsoleDriverTests.cs index 020c568b85..4efde7001b 100644 --- a/UnitTests/ConsoleDrivers/ConsoleDriverTests.cs +++ b/UnitTests/ConsoleDrivers/ConsoleDriverTests.cs @@ -173,7 +173,6 @@ public void FakeDriver_MockKeyPresses (Type driverType) // output.WriteLine ($"Add timeout to force quit after {abortTime}ms"); // _ = Application.MainLoop.AddTimeout (TimeSpan.FromMilliseconds (abortTime), forceCloseCallback); - // Key key = Key.Unknown; // Application.Top.KeyPress += (e) => { @@ -206,7 +205,7 @@ public void TerminalResized_Simulation (Type driverType) var driver = (FakeDriver)Activator.CreateInstance (driverType); Application.Init (driver); var wasTerminalResized = false; - Application.Resized = (e) => { + Application.TerminalResized = (e) => { wasTerminalResized = true; Assert.Equal (120, e.Cols); Assert.Equal (40, e.Rows); diff --git a/UnitTests/ConsoleDrivers/ConsoleScrolllingTests.cs b/UnitTests/ConsoleDrivers/ConsoleScrolllingTests.cs index 28eeae454f..c84b8bd25e 100644 --- a/UnitTests/ConsoleDrivers/ConsoleScrolllingTests.cs +++ b/UnitTests/ConsoleDrivers/ConsoleScrolllingTests.cs @@ -17,7 +17,6 @@ public ConsoleScrollingTests (ITestOutputHelper output) this.output = output; } - [Theory] [InlineData (typeof (FakeDriver))] public void EnableConsoleScrolling_Is_False_Left_And_Top_Is_Always_Zero (Type driverType) diff --git a/UnitTests/ConsoleDrivers/KeyTests.cs b/UnitTests/ConsoleDrivers/KeyTests.cs index f480c2417d..4f40707ae3 100644 --- a/UnitTests/ConsoleDrivers/KeyTests.cs +++ b/UnitTests/ConsoleDrivers/KeyTests.cs @@ -11,7 +11,6 @@ enum SimpleEnum { Zero, One, Two, Three, Four, Five } [Flags] enum FlaggedEnum { Zero, One, Two, Three, Four, Five } - enum SimpleHighValueEnum { Zero, One, Two, Three, Four, Last = 0x40000000 } [Flags] @@ -172,7 +171,6 @@ public void Key_ToString () Assert.Equal ("d, ShiftMask", k.ToString ()); } - private static object packetLock = new object (); /// @@ -220,7 +218,6 @@ public void TestVKPacket (uint unicodeCharacter, bool shift, bool alt, bool cont if (iterations == 0) Application.Driver.SendKeys ((char)mappedConsoleKey, ConsoleKey.Packet, shift, alt, control); }; - lock (packetLock) { Application.Run (); Application.Shutdown (); diff --git a/UnitTests/Dialogs/DialogTests.cs b/UnitTests/Dialogs/DialogTests.cs index 8318e9a879..232968bf27 100644 --- a/UnitTests/Dialogs/DialogTests.cs +++ b/UnitTests/Dialogs/DialogTests.cs @@ -84,7 +84,6 @@ public void Location_Default () Assert.Equal (new Point (expected, expected), d.Frame.Location); } - [Fact] [AutoInitShutdown] public void Size_Not_Default () @@ -117,7 +116,6 @@ public void Location_Not_Default () Assert.Equal (new Point (expected, expected), d.Frame.Location); } - [Fact] [AutoInitShutdown] public void Location_When_Application_Top_Not_Default () @@ -195,7 +193,6 @@ public void Location_When_Not_Application_Top_Not_Default () ║ │ │ ╚══└───────────────┘", output); - } else if (iterations > 0) { Application.RequestStop (); } diff --git a/UnitTests/Dialogs/MessageBoxTests.cs b/UnitTests/Dialogs/MessageBoxTests.cs index d9dbd25ffe..cf9a4018ba 100644 --- a/UnitTests/Dialogs/MessageBoxTests.cs +++ b/UnitTests/Dialogs/MessageBoxTests.cs @@ -223,7 +223,6 @@ public void Size_No_With_Button () if (iterations == 0) { - MessageBox.Query (string.Empty, message, "_Ok"); Application.RequestStop (); diff --git a/UnitTests/Dialogs/WizardTests.cs b/UnitTests/Dialogs/WizardTests.cs index 3ad752b30a..2ccf8b8bba 100644 --- a/UnitTests/Dialogs/WizardTests.cs +++ b/UnitTests/Dialogs/WizardTests.cs @@ -38,7 +38,6 @@ public void WizardStep_ButtonText () // Verify set actually changes buttons for the current step } - [Fact] public void WizardStep_Set_Title_Fires_TitleChanging () { diff --git a/UnitTests/Drawing/LineCanvasTests.cs b/UnitTests/Drawing/LineCanvasTests.cs index 3c2d29b887..0cf5e8dee1 100644 --- a/UnitTests/Drawing/LineCanvasTests.cs +++ b/UnitTests/Drawing/LineCanvasTests.cs @@ -512,7 +512,6 @@ public void View_Draws_Corner_Correct () } - [Fact, SetupFakeDriver] public void Top_With_1Down () { @@ -615,7 +614,6 @@ public void View_Draws_Window_Double () canvas.AddLine (new Point (9, 4), -10, Orientation.Horizontal, LineStyle.Double); canvas.AddLine (new Point (0, 4), -5, Orientation.Vertical, LineStyle.Double); - canvas.AddLine (new Point (5, 0), 5, Orientation.Vertical, LineStyle.Double); canvas.AddLine (new Point (0, 2), 10, Orientation.Horizontal, LineStyle.Double); @@ -631,7 +629,6 @@ public void View_Draws_Window_Double () TestHelpers.AssertDriverContentsAre (looksLike, output); } - [Theory, AutoInitShutdown] [InlineData (LineStyle.Single)] [InlineData (LineStyle.Rounded)] @@ -645,7 +642,6 @@ public void View_Draws_Window_DoubleTop_SingleSides (LineStyle thinStyle) canvas.AddLine (new Point (9, 4), -10, Orientation.Horizontal, LineStyle.Double); canvas.AddLine (new Point (0, 4), -5, Orientation.Vertical, thinStyle); - canvas.AddLine (new Point (5, 0), 5, Orientation.Vertical, thinStyle); canvas.AddLine (new Point (0, 2), 10, Orientation.Horizontal, LineStyle.Double); @@ -675,7 +671,6 @@ public void View_Draws_Window_SingleTop_DoubleSides (LineStyle thinStyle) canvas.AddLine (new Point (9, 4), -10, Orientation.Horizontal, thinStyle); canvas.AddLine (new Point (0, 4), -5, Orientation.Vertical, LineStyle.Double); - canvas.AddLine (new Point (5, 0), 5, Orientation.Vertical, LineStyle.Double); canvas.AddLine (new Point (0, 2), 10, Orientation.Horizontal, thinStyle); @@ -780,7 +775,6 @@ string expected TestHelpers.AssertDriverContentsAre (expected, output); } - [Theory, AutoInitShutdown] // Horizontal lines with a vertical zero-length [InlineData ( diff --git a/UnitTests/Drawing/RulerTests.cs b/UnitTests/Drawing/RulerTests.cs index 9c7f9d5f35..cea34d079d 100644 --- a/UnitTests/Drawing/RulerTests.cs +++ b/UnitTests/Drawing/RulerTests.cs @@ -29,7 +29,6 @@ public void Constructor_Defaults () Assert.Equal (default, r.Attribute); } - [Fact ()] public void Orientation_set () { @@ -182,7 +181,6 @@ public void Draw_Vertical () Height = Dim.Fill (), }; - Application.Top.Add (f); Application.Begin (Application.Top); ((FakeDriver)Application.Driver).SetBufferSize (5, len + 5); @@ -303,7 +301,6 @@ public void Draw_Vertical_Start () Height = Dim.Fill (), }; - Application.Top.Add (f); Application.Begin (Application.Top); ((FakeDriver)Application.Driver).SetBufferSize (5, len + 5); @@ -364,4 +361,3 @@ public void Draw_Vertical_Start () } } - diff --git a/UnitTests/Drawing/ThicknessTests.cs b/UnitTests/Drawing/ThicknessTests.cs index 4595150524..80c931c046 100644 --- a/UnitTests/Drawing/ThicknessTests.cs +++ b/UnitTests/Drawing/ThicknessTests.cs @@ -50,7 +50,6 @@ public void Constructor_Width () Assert.Equal (1, t.Bottom); } - [Fact ()] public void Constructor_params () { @@ -361,7 +360,6 @@ public void GetInsideTests_Negative_Thickness_Non_Empty_Size () Assert.Equal (66, inside.Height); } - [Fact ()] public void GetInsideTests_Mixed_Pos_Neg_Thickness_Non_Empty_Size () { @@ -411,7 +409,6 @@ public void DrawTests () TestHelpers.AssertDriverContentsWithFrameAre (@" Test (Left=0,Top=0,Right=0,Bottom=0)", output); - t = new Thickness (1, 1, 1, 1); r = new Rect (5, 5, 40, 15); ConsoleDriver.Diagnostics |= ConsoleDriver.DiagnosticFlags.FramePadding; @@ -458,7 +455,6 @@ T TTT TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT TTTest (Left=1,Top=2,Right=3,Bottom=4)TT", output); - t = new Thickness (-1, 1, 1, 1); r = new Rect (5, 5, 40, 15); ConsoleDriver.Diagnostics |= ConsoleDriver.DiagnosticFlags.FramePadding; @@ -495,7 +491,6 @@ public void DrawTests_Ruler () Height = Dim.Fill (), }; - Application.Top.Add (f); Application.Begin (Application.Top); @@ -528,7 +523,6 @@ public void DrawTests_Ruler () │ │ └───────────────────────────────────────────┘", output); - t = new Thickness (1, 1, 1, 1); r = new Rect (1, 1, 40, 15); Application.Refresh (); @@ -585,7 +579,6 @@ public void DrawTests_Ruler () │ │ └───────────────────────────────────────────┘", output); - t = new Thickness (-1, 1, 1, 1); r = new Rect (5, 5, 40, 15); Application.Refresh (); @@ -641,4 +634,3 @@ public void GetHashCodeTest () } } - diff --git a/UnitTests/FileServices/FileDialogTests.cs b/UnitTests/FileServices/FileDialogTests.cs index 94a1d331ad..8b9102c4f8 100644 --- a/UnitTests/FileServices/FileDialogTests.cs +++ b/UnitTests/FileServices/FileDialogTests.cs @@ -183,7 +183,6 @@ public void TestDirectoryContents_Linux () TestHelpers.AssertDriverContentsAre (expected, output, true); } - [Fact, AutoInitShutdown] public void TestDirectoryContents_Windows () { @@ -284,7 +283,6 @@ public void Autocomplete_NoSuggestion_WhenTextMatchesExactly () Assert.False (tb.AcceptSelectionIfAny ()); } - [Fact, AutoInitShutdown] public void Autocomplete_AcceptSuggstion () { @@ -299,7 +297,6 @@ public void Autocomplete_AcceptSuggstion () Assert.Equal (@"/bob/fish", tb.Text); }*/ - private FileDialog GetInitializedFileDialog () { var dlg = new FileDialog (); diff --git a/UnitTests/TestHelpers.cs b/UnitTests/TestHelpers.cs index 9352a805a3..a3ccc18dfd 100644 --- a/UnitTests/TestHelpers.cs +++ b/UnitTests/TestHelpers.cs @@ -14,7 +14,6 @@ using NStack; using Xunit.Sdk; - // This class enables test functions annotated with the [AutoInitShutdown] attribute to // automatically call Application.Init at start of the test and Application.Shutdown after the // test exits. @@ -85,7 +84,6 @@ public override void After (MethodInfo methodUnderTest) } } - [AttributeUsage (AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] public class SetupFakeDriverAttribute : Xunit.Sdk.BeforeAfterTestAttribute { /// diff --git a/UnitTests/Text/TextFormatterTests.cs b/UnitTests/Text/TextFormatterTests.cs index 9b9ec9ea81..dd13f278ad 100644 --- a/UnitTests/Text/TextFormatterTests.cs +++ b/UnitTests/Text/TextFormatterTests.cs @@ -699,7 +699,6 @@ public void ClipAndJustify_Valid_Left () Assert.True (expectedClippedWidth <= maxWidth); Assert.Equal (ustring.Make (text.ToRunes () [0..expectedClippedWidth]), justifiedText); - text = "A\tsentence\thas\twords."; maxWidth = int.MaxValue; expectedClippedWidth = Math.Min (text.RuneCount, maxWidth); @@ -845,7 +844,6 @@ public void ClipAndJustify_Valid_Right () Assert.True (expectedClippedWidth <= maxWidth); Assert.Equal (ustring.Make (text.ToRunes () [0..expectedClippedWidth]), justifiedText); - text = "A\tsentence\thas\twords."; maxWidth = int.MaxValue; expectedClippedWidth = Math.Min (text.RuneCount, maxWidth); @@ -1066,7 +1064,6 @@ public void ClipAndJustify_Valid_Centered () Assert.Equal (ustring.Make (text.ToRunes () [0..expectedClippedWidth]), justifiedText); } - [Fact] public void ClipAndJustify_Valid_Justified () { @@ -1131,7 +1128,6 @@ public void ClipAndJustify_Valid_Justified () Assert.True (expectedClippedWidth <= maxWidth); Assert.Equal (ustring.Make (text.ToRunes () [0..expectedClippedWidth]), justifiedText); - text = "A\tsentence\thas\twords."; maxWidth = int.MaxValue; expectedClippedWidth = Math.Min (text.RuneCount, maxWidth); @@ -1258,7 +1254,6 @@ public void Justify_SingleWord () width = text.RuneCount + 11; Assert.Equal (justifiedText.ToString (), TextFormatter.Justify (text, width, fillChar).ToString ()); - // Unicode (even #) text = "пÑивеÑ"; justifiedText = text; @@ -1290,7 +1285,6 @@ public void Justify_SingleWord () Assert.Equal (justifiedText.ToString (), TextFormatter.Justify (text, width, fillChar).ToString ()); } - [Fact] public void Justify_Sentence () { @@ -3649,7 +3643,6 @@ public void Format_Dont_Throw_ArgumentException_With_WordWrap_As_False_And_Keep_ } - [Fact] public void Format_Justified_Always_Returns_Text_Width_Equal_To_Passed_Width_Horizontal () { @@ -3780,7 +3773,6 @@ public void GetLengthThatFits_ustring () Assert.Equal (0, TextFormatter.GetLengthThatFits (text, columns)); } - [Fact] public void GetLengthThatFits_Simple_And_Wide_Runes () { @@ -3814,7 +3806,6 @@ public void Format_Truncate_Simple_And_Wide_Runes () } - [Fact] public void Format_With_PreserveTrailingSpaces_And_Without_PreserveTrailingSpaces () { diff --git a/UnitTests/Types/RectTests.cs b/UnitTests/Types/RectTests.cs index 68871cf1df..06fbfa82c9 100644 --- a/UnitTests/Types/RectTests.cs +++ b/UnitTests/Types/RectTests.cs @@ -149,7 +149,6 @@ public void Negative_X_Y_Positions () Assert.Equal (yxCount, rect.Height * rect.Width); } - [Theory] // Empty [InlineData ( @@ -256,7 +255,6 @@ public void Union_NegativeCoords () Assert.Equal (new Rect (-2, -2, 6, 6), result); } - [Fact] public void Union_EmptyRectangles () { @@ -275,7 +273,6 @@ public void Union_SameRectangle () Assert.Equal (new Rect (0, 0, 2, 2), result); } - [Fact] public void Union_RectanglesOverlap_ReturnsCombinedRectangle () { diff --git a/UnitTests/UICatalog/ScenarioTests.cs b/UnitTests/UICatalog/ScenarioTests.cs index 27708422bb..87c481f861 100644 --- a/UnitTests/UICatalog/ScenarioTests.cs +++ b/UnitTests/UICatalog/ScenarioTests.cs @@ -42,7 +42,6 @@ int CreateInput (string input) return FakeConsole.MockKeyPresses.Count; } - /// /// /// This runs through all Scenarios defined in UI Catalog, calling Init, Setup, and Run. @@ -416,7 +415,6 @@ public void Run_All_Views_Tester_Scenario () Application.Shutdown (); - void DimPosChanged (View view) { if (view == null) { diff --git a/UnitTests/View/DrawTests.cs b/UnitTests/View/DrawTests.cs index 35443b4390..32096a712c 100644 --- a/UnitTests/View/DrawTests.cs +++ b/UnitTests/View/DrawTests.cs @@ -197,7 +197,6 @@ void Top_LayoutComplete (object sender, LayoutEventArgs e) TestHelpers.AssertDriverContentsWithFrameAre ("", output); } - [Fact, AutoInitShutdown] public void Draw_Negative_Bounds_Horizontal_With_New_Lines () { diff --git a/UnitTests/View/FrameTests.cs b/UnitTests/View/FrameTests.cs index 655112eab9..b57e15c29a 100644 --- a/UnitTests/View/FrameTests.cs +++ b/UnitTests/View/FrameTests.cs @@ -245,7 +245,6 @@ public void HasSuperView () _ = TestHelpers.AssertDriverContentsWithFrameAre (expected, output); } - [Fact, AutoInitShutdown] public void HasSuperView_Title () { diff --git a/UnitTests/View/Layout/AbsoluteLayoutTests.cs b/UnitTests/View/Layout/AbsoluteLayoutTests.cs index 6a631197ca..57a4e83885 100644 --- a/UnitTests/View/Layout/AbsoluteLayoutTests.cs +++ b/UnitTests/View/Layout/AbsoluteLayoutTests.cs @@ -51,7 +51,6 @@ public void AbsoluteLayout_Constructor () Assert.Null (v.Width); } - [Fact] public void AbsoluteLayout_Change_Frame () { @@ -98,7 +97,6 @@ public void AbsoluteLayout_Change_Frame () } - [Fact] public void AbsoluteLayout_Change_Height_or_Width_Absolute () { diff --git a/UnitTests/View/Layout/DimTests.cs b/UnitTests/View/Layout/DimTests.cs index 83136e24ce..7a77c0a331 100644 --- a/UnitTests/View/Layout/DimTests.cs +++ b/UnitTests/View/Layout/DimTests.cs @@ -134,7 +134,6 @@ public void Width_Equals () Assert.NotEqual (dim1, dim2); } - [Fact] public void Height_Set_To_Null_Throws () { @@ -172,7 +171,6 @@ public void Fill_SetsValue () Assert.Equal ($"Fill({testMargin})", dim.ToString ()); } - [Fact] public void Fill_Equal () { @@ -661,7 +659,6 @@ public void PosCombine_View_Not_Added_Throws () Assert.Throws (() => t.LayoutSubviews ()); } - [Fact, AutoInitShutdown] public void Dim_Add_Operator () { diff --git a/UnitTests/View/Layout/LayoutTests.cs b/UnitTests/View/Layout/LayoutTests.cs index cc82035c35..598c346477 100644 --- a/UnitTests/View/Layout/LayoutTests.cs +++ b/UnitTests/View/Layout/LayoutTests.cs @@ -81,7 +81,6 @@ public void LayoutSubviews_RootHas_SuperView () Assert.Equal (6, second.Frame.X); } - [Fact] public void LayoutSubviews_ViewThatRefsSubView_Throws () { @@ -1518,7 +1517,6 @@ public void SetRelativeLayout_PosCombine_Center_Plus_Absolute () Assert.Equal (6, testView.Frame.Y); } - [Theory, AutoInitShutdown] [InlineData (1)] [InlineData (2)] @@ -1550,7 +1548,6 @@ public void Dim_CenteredSubView_85_Percent_Height (int height) bool firstIteration = false; - ((FakeDriver)Application.Driver).SetBufferSize (20, height); Application.RunMainLoopIteration (ref rs, true, ref firstIteration); var expected = string.Empty; @@ -1688,7 +1685,6 @@ public void Dim_CenteredSubView_85_Percent_Width (int width) bool firstIteration = false; - ((FakeDriver)Application.Driver).SetBufferSize (width, 7); Application.RunMainLoopIteration (ref rs, true, ref firstIteration); var expected = string.Empty; @@ -1860,7 +1856,6 @@ public void PosCombine_DimCombine_View_With_SubViews () Application.End (rs); } - [Fact] public void Draw_Vertical_Throws_IndexOutOfRangeException_With_Negative_Bounds () { diff --git a/UnitTests/View/Layout/PosTests.cs b/UnitTests/View/Layout/PosTests.cs index 63fc581d92..b7642e0c12 100644 --- a/UnitTests/View/Layout/PosTests.cs +++ b/UnitTests/View/Layout/PosTests.cs @@ -1027,7 +1027,6 @@ public void PosPercentPlusOne (bool testHorizontal) Application.Top.Add (container); Application.Top.LayoutSubviews (); - Assert.Equal (100, container.Frame.Width); Assert.Equal (100, container.Frame.Height); diff --git a/UnitTests/View/NavigationTests.cs b/UnitTests/View/NavigationTests.cs index f47e7c9a94..61c5bb54c9 100644 --- a/UnitTests/View/NavigationTests.cs +++ b/UnitTests/View/NavigationTests.cs @@ -621,7 +621,6 @@ public void CanFocus_Container_Toggling_All_Subviews_To_Old_Value_When_Is_True ( Application.Shutdown (); } - [Fact] public void Navigation_With_Null_Focused_View () { @@ -729,7 +728,6 @@ public void Enabled_Sets_Also_Sets_Subviews () Assert.Equal (1, iterations); } - [Fact] [AutoInitShutdown] public void CanFocus_Sets_To_False_Does_Not_Sets_HasFocus_To_True () @@ -875,7 +873,7 @@ void Top_KeyPress (object sender, KeyEventEventArgs obj) tf.KeyPress -= Tf_KeyPress; tfQuiting = false; Application.Driver.SendKeys ('q', ConsoleKey.Q, false, false, true); - Application.MainLoop.MainIteration (); + Application.MainLoop.RunIteration (); Assert.True (sbQuiting); Assert.False (tfQuiting); Assert.False (topQuiting); @@ -883,7 +881,7 @@ void Top_KeyPress (object sender, KeyEventEventArgs obj) sb.RemoveItem (0); sbQuiting = false; Application.Driver.SendKeys ('q', ConsoleKey.Q, false, false, true); - Application.MainLoop.MainIteration (); + Application.MainLoop.RunIteration (); Assert.False (sbQuiting); Assert.False (tfQuiting); Assert.True (topQuiting); @@ -924,7 +922,7 @@ void Tf_KeyPress (object sender, KeyEventEventArgs obj) tf.KeyPress -= Tf_KeyPress; tfQuiting = false; Application.Driver.SendKeys ('q', ConsoleKey.Q, false, false, true); - Application.MainLoop.MainIteration (); + Application.MainLoop.RunIteration (); Assert.True (sbQuiting); Assert.False (tfQuiting); } diff --git a/UnitTests/View/ViewTests.cs b/UnitTests/View/ViewTests.cs index 47a4905818..9d198740ef 100644 --- a/UnitTests/View/ViewTests.cs +++ b/UnitTests/View/ViewTests.cs @@ -135,7 +135,6 @@ public void New_Methods_Return_False () // TODO: Add more } - [Fact] public void View_With_No_Difference_Between_An_Object_Initializer_And_A_Constructor () { @@ -412,7 +411,6 @@ public void Initialized_Event_Will_Be_Invoked_When_Added_Dynamically () Assert.False (v2.CanFocus); } - [Fact] public void Multi_Thread_Toplevels () { @@ -593,7 +591,6 @@ public void Internal_Tests () Assert.Equal (79, view.Bounds.Width); Assert.Equal (24, view.Bounds.Height); - view.X = 0; view.Y = 0; Assert.Equal ("Absolute(0)", view.X.ToString ()); @@ -1367,7 +1364,6 @@ public void Test_Nested_Views_With_Height_Equal_To_One () v.LayoutSubviews (); v.Redraw (v.Bounds); - string looksLike = @" 111 diff --git a/UnitTests/Views/AppendAutocompleteTests.cs b/UnitTests/Views/AppendAutocompleteTests.cs index 89d8bbea55..1146d05bfe 100644 --- a/UnitTests/Views/AppendAutocompleteTests.cs +++ b/UnitTests/Views/AppendAutocompleteTests.cs @@ -24,7 +24,6 @@ public void TestAutoAppend_ShowThenAccept_MatchCase () var generator = (SingleWordSuggestionGenerator)tf.Autocomplete.SuggestionGenerator; generator.AllSuggestions = new List { "fish" }; - tf.Redraw (tf.Bounds); TestHelpers.AssertDriverContentsAre ("", output); @@ -57,7 +56,6 @@ public void TestAutoAppend_ShowThenAccept_CasesDiffer () var generator = (SingleWordSuggestionGenerator)tf.Autocomplete.SuggestionGenerator; generator.AllSuggestions = new List { "FISH" }; - tf.Redraw (tf.Bounds); TestHelpers.AssertDriverContentsAre ("", output); tf.ProcessKey (new KeyEvent (Key.m, new KeyModifiers ())); @@ -77,7 +75,6 @@ public void TestAutoAppend_ShowThenAccept_CasesDiffer () Assert.Equal ("my FISH", tf.Text.ToString ()); } - [Fact, AutoInitShutdown] public void TestAutoAppend_AfterCloseKey_NoAutocomplete () { @@ -132,7 +129,6 @@ public void TestAutoAppend_AfterCloseKey_ReapearsOnLetter () Assert.Equal ("fi", tf.Text.ToString ()); } - [Theory, AutoInitShutdown] [InlineData ("ffffffffffffffffffffffffff", "ffffffffff")] [InlineData ("f234567890", "f234567890")] @@ -148,7 +144,6 @@ public void TestAutoAppendRendering_ShouldNotOverspill (string overspillUsing, s Assert.Equal ("f", tf.Text.ToString ()); } - [Theory, AutoInitShutdown] [InlineData (ConsoleKey.UpArrow)] [InlineData (ConsoleKey.DownArrow)] @@ -194,7 +189,6 @@ public void TestAutoAppend_NoRender_WhenNoMatch () Assert.Equal ("fx", tf.Text.ToString ()); } - [Fact, AutoInitShutdown] public void TestAutoAppend_NoRender_WhenCursorNotAtEnd () { @@ -216,7 +210,6 @@ public void TestAutoAppend_NoRender_WhenCursorNotAtEnd () } - private TextField GetTextFieldsInViewSuggesting (params string [] suggestions) { var tf = GetTextFieldsInView (); diff --git a/UnitTests/Views/ButtonTests.cs b/UnitTests/Views/ButtonTests.cs index 00a3d06c77..666b9ef846 100644 --- a/UnitTests/Views/ButtonTests.cs +++ b/UnitTests/Views/ButtonTests.cs @@ -138,7 +138,6 @@ public void ChangeHotKey () Assert.True (clicked); } - /// /// This test demonstrates how to change the activation key for Button /// as described in the README.md keyboard handling section diff --git a/UnitTests/Views/CheckBoxTests.cs b/UnitTests/Views/CheckBoxTests.cs index 4c1a90954f..48a99b3e63 100644 --- a/UnitTests/Views/CheckBoxTests.cs +++ b/UnitTests/Views/CheckBoxTests.cs @@ -359,7 +359,6 @@ public void TextAlignment_Justified () Assert.Equal ("╴ Check second out 你", checkBox2.TextFormatter.Text); Assert.False (checkBox2.AutoSize); - var expected = @" ┌┤Test Demo 你├──────────────┐ │ │ diff --git a/UnitTests/Views/ComboBoxTests.cs b/UnitTests/Views/ComboBoxTests.cs index 41f3f84dbb..8b3f389385 100644 --- a/UnitTests/Views/ComboBoxTests.cs +++ b/UnitTests/Views/ComboBoxTests.cs @@ -83,7 +83,6 @@ public void EnsureKeyEventsDoNotCauseExceptions () } } - [Fact] [AutoInitShutdown] public void KeyBindings_Command () diff --git a/UnitTests/Views/ContextMenuTests.cs b/UnitTests/Views/ContextMenuTests.cs index 46654e8dee..abbbdbc0ff 100644 --- a/UnitTests/Views/ContextMenuTests.cs +++ b/UnitTests/Views/ContextMenuTests.cs @@ -153,7 +153,6 @@ public void MenuItens_Changing () new MenuItem ("Third", "", null) }); - cm.Show (); Application.Refresh (); diff --git a/UnitTests/Views/FrameViewTests.cs b/UnitTests/Views/FrameViewTests.cs index 953dca640a..05c5296c85 100644 --- a/UnitTests/Views/FrameViewTests.cs +++ b/UnitTests/Views/FrameViewTests.cs @@ -61,7 +61,6 @@ public void Draw_Defaults () │ │ └───┘", output); - fv.X = 1; fv.Y = 2; Assert.Equal (new Rect (1, 2, 5, 5), fv.Frame); diff --git a/UnitTests/Views/GraphViewTests.cs b/UnitTests/Views/GraphViewTests.cs index 6d38eb075e..8059103e4d 100644 --- a/UnitTests/Views/GraphViewTests.cs +++ b/UnitTests/Views/GraphViewTests.cs @@ -111,7 +111,6 @@ public void ScreenToGraphSpace_DefaultCellSize () Assert.Equal (1, botLeft.Width); Assert.Equal (1, botLeft.Height); - // up 2 rows of the console and along 1 col var up2along1 = gv.ScreenToGraphSpace (1, 7); Assert.Equal (1, up2along1.X); @@ -292,7 +291,6 @@ public void GraphSpaceToScreen_CustomCellSize () Assert.Equal (7, gv.GraphSpaceToScreen (new PointF (2, 11)).Y); } - [Fact] public void GraphSpaceToScreen_CustomCellSize_WithScrollOffset () { @@ -318,7 +316,6 @@ public void GraphSpaceToScreen_CustomCellSize_WithScrollOffset () Assert.Equal (12, along2up1.X); Assert.Equal (7, along2up1.Y); - // More boundary testing for this cell size/offset Assert.Equal (6, gv.GraphSpaceToScreen (new PointF (2, 6)).Y); Assert.Equal (6, gv.GraphSpaceToScreen (new PointF (2, 7)).Y); @@ -330,7 +327,6 @@ public void GraphSpaceToScreen_CustomCellSize_WithScrollOffset () #endregion - /// /// A cell size of 0 would result in mapping all graph space into the /// same cell of the console. Since @@ -357,7 +353,6 @@ public void CellSizeZero () } - /// /// Tests that each point in the screen space maps to a rectangle of /// (float) graph space and that each corner of that rectangle of graph @@ -549,7 +544,6 @@ public void MultiBarSeries_BarSpacing () Assert.Equal (4, series.SubSeries.ElementAt (4).Offset); } - [Fact] public void MultiBarSeriesColors_WrongNumber () { @@ -568,7 +562,6 @@ public void MultiBarSeriesColors_WrongNumber () Application.Shutdown (); } - [Fact] public void MultiBarSeriesColors_RightNumber () { @@ -592,7 +585,6 @@ public void MultiBarSeriesColors_RightNumber () Application.Shutdown (); } - [Fact] public void MultiBarSeriesAddValues_WrongNumber () { @@ -606,7 +598,6 @@ public void MultiBarSeriesAddValues_WrongNumber () } - [Fact] public void TestRendering_MultibarSeries () { @@ -684,7 +675,6 @@ public void TestRendering_MultibarSeries () public class BarSeriesTests { - private GraphView GetGraph (out FakeBarSeries series, out FakeHAxis axisX, out FakeVAxis axisY) { GraphViewTests.InitFakeDriver (); @@ -742,7 +732,6 @@ public void TestZeroHeightBar_WithName () Application.Shutdown (); } - [Fact] public void TestTwoTallBars_WithOffset () { @@ -882,10 +871,8 @@ protected override void DrawBarLine (GraphView graph, Point start, Point end, Ba } } - public class AxisTests { - private GraphView GetGraph (out FakeHAxis axis) { return GetGraph (out axis, out _); @@ -993,7 +980,6 @@ public void TestHAxisLocation_MarginLeft () #region VerticalAxisTests - /// /// Tests that the horizontal axis is computed correctly and does not over spill /// it's bounds @@ -1124,7 +1110,6 @@ public void TestTextAnnotation_ScreenUnits () Application.Shutdown (); } - [Fact] public void TestTextAnnotation_GraphUnits () { @@ -1197,12 +1182,10 @@ public void TestTextAnnotation_LongText () TestHelpers.AssertDriverContentsAre (expected, output); - // Shutdown must be called to safely clean up Application if Init has been called Application.Shutdown (); } - [Fact] public void TestTextAnnotation_Offscreen () { @@ -1262,7 +1245,6 @@ public void TestTextAnnotation_EmptyText (string whitespace) TestHelpers.AssertDriverContentsAre (expected, output); - // Shutdown must be called to safely clean up Application if Init has been called Application.Shutdown (); } @@ -1297,7 +1279,6 @@ public void LegendNormalUsage_WithBorder () TestHelpers.AssertDriverContentsAre (expected, output); - // Shutdown must be called to safely clean up Application if Init has been called Application.Shutdown (); } @@ -1326,7 +1307,6 @@ public void LegendNormalUsage_WithoutBorder () TestHelpers.AssertDriverContentsAre (expected, output); - // Shutdown must be called to safely clean up Application if Init has been called Application.Shutdown (); } @@ -1369,7 +1349,6 @@ public void PathAnnotation_Box () TestHelpers.AssertDriverContentsAre (expected, output); - // Shutdown must be called to safely clean up Application if Init has been called Application.Shutdown (); } @@ -1409,7 +1388,6 @@ 0 5 "; TestHelpers.AssertDriverContentsAre (expected, output); - // Shutdown must be called to safely clean up Application if Init has been called Application.Shutdown (); } @@ -1451,12 +1429,10 @@ 0 5 "; TestHelpers.AssertDriverContentsAre (expected, output); - // Shutdown must be called to safely clean up Application if Init has been called Application.Shutdown (); } - [Fact] public void MarginBottom_BiggerThanHeight_ExpectBlankGraph () { @@ -1478,7 +1454,6 @@ public void MarginBottom_BiggerThanHeight_ExpectBlankGraph () "; TestHelpers.AssertDriverContentsAre (expected, output); - // Shutdown must be called to safely clean up Application if Init has been called Application.Shutdown (); } @@ -1503,7 +1478,6 @@ public void MarginLeft_BiggerThanWidth_ExpectBlankGraph () "; TestHelpers.AssertDriverContentsAre (expected, output); - // Shutdown must be called to safely clean up Application if Init has been called Application.Shutdown (); } @@ -1537,7 +1511,6 @@ public void PathAnnotation_Diamond () TestHelpers.AssertDriverContentsAre (expected, output); - // Shutdown must be called to safely clean up Application if Init has been called Application.Shutdown (); } @@ -1571,7 +1544,7 @@ public void LabelChangeText_RendersCorrectly (bool useFill) //put label into view mount.Add (lbl1); - //putting mount into toplevel since changing size + //putting mount into Toplevel since changing size //also change AutoSize to false Application.Top.Add (mount); Application.Begin (Application.Top); @@ -1591,7 +1564,6 @@ public void LabelChangeText_RendersCorrectly (bool useFill) // should have the new text rendered TestHelpers.AssertDriverContentsAre ("ff1234", null); - } finally { Application.Shutdown (); } diff --git a/UnitTests/Views/LabelTests.cs b/UnitTests/Views/LabelTests.cs index 28c130ae23..8feb25db4a 100644 --- a/UnitTests/Views/LabelTests.cs +++ b/UnitTests/Views/LabelTests.cs @@ -71,7 +71,6 @@ public void TestAssignTextToLabel () Assert.Equal ("heyb", ((Label)b).Text); } - [Fact, AutoInitShutdown] public void Update_Only_On_Or_After_Initialize () { @@ -372,7 +371,6 @@ public void Label_HotKeyChanged_EventFires_WithNone () Assert.Equal (Key.r, args.NewKey); } - [Fact, AutoInitShutdown] public void Label_WordWrap_PreserveTrailingSpaces_Horizontal_With_Simple_Runes () { @@ -525,7 +523,6 @@ public void Label_WordWrap_PreserveTrailingSpaces_Vertical_With_Wide_Runes () Assert.Equal (new Rect (0, 0, width + 2, height + 2), pos); } - [Fact, AutoInitShutdown] public void Label_Draw_Horizontal_Simple_TextAlignments_Justified () { @@ -673,7 +670,6 @@ public void Label_Draw_Vertical_Wide_Runes_With_ForceValidatePosDim () Assert.Equal (new Rect (0, 0, 2, 7), pos); } - [Fact, AutoInitShutdown] public void Label_Draw_Horizontal_Simple_TextAlignments () { diff --git a/UnitTests/Views/ListViewTests.cs b/UnitTests/Views/ListViewTests.cs index 4b54a41e1c..8d0b093f24 100644 --- a/UnitTests/Views/ListViewTests.cs +++ b/UnitTests/Views/ListViewTests.cs @@ -113,7 +113,6 @@ public void SettingEmptyKeybindingThrows () Assert.Throws (() => lv.AddKeyBinding (Key.Space)); } - /// /// Tests that when none of the Commands in a chained keybinding are possible /// the returns the appropriate result diff --git a/UnitTests/Views/MenuTests.cs b/UnitTests/Views/MenuTests.cs index 13fafdbcdb..0f77509b92 100644 --- a/UnitTests/Views/MenuTests.cs +++ b/UnitTests/Views/MenuTests.cs @@ -478,7 +478,7 @@ public void KeyBindings_Command () Assert.False (menu.IsMenuOpen); Assert.Equal ("Closed", GetCurrentMenuBarItemTitle ()); Assert.Equal ("None", GetCurrentMenuTitle ()); - Application.MainLoop.MainIteration (); + Application.MainLoop.RunIteration (); Assert.Equal ("New", miAction); Assert.True (menu.ProcessHotKey (new KeyEvent (Key.F9, new KeyModifiers ()))); @@ -493,7 +493,7 @@ public void KeyBindings_Command () Assert.False (menu.IsMenuOpen); Assert.Equal ("Closed", GetCurrentMenuBarItemTitle ()); Assert.Equal ("None", GetCurrentMenuTitle ()); - Application.MainLoop.MainIteration (); + Application.MainLoop.RunIteration (); Assert.Equal ("About", miAction); Assert.True (menu.ProcessHotKey (new KeyEvent (Key.F9, new KeyModifiers ()))); @@ -565,10 +565,9 @@ public void KeyBindings_Command () Assert.False (menu.IsMenuOpen); Assert.Equal ("Closed", GetCurrentMenuBarItemTitle ()); Assert.Equal ("None", GetCurrentMenuTitle ()); - Application.MainLoop.MainIteration (); + Application.MainLoop.RunIteration (); Assert.Equal ("Copy", miAction); - string GetCurrentMenuBarItemTitle () { return mbiCurrent != null ? mbiCurrent.Title.ToString () : "Closed"; @@ -1084,7 +1083,7 @@ public void UseSubMenusSingleFrame_False_By_Mouse () // Assert.Equal (new Rect (1, 0, 11, 1), pos); // Assert.True (menu.ProcessKey (new (Key.N, null))); -// Application.MainLoop.MainIteration (); +// Application.MainLoop.RunIteration (); // Assert.True (newAction); // Assert.True (menu.ProcessHotKey (new (Key.AltMask, new KeyModifiers () { Alt = true }))); @@ -1099,7 +1098,7 @@ public void UseSubMenusSingleFrame_False_By_Mouse () // Assert.True (menu.ProcessKey (new (Key.CursorRight, null))); // Assert.True (menu.ProcessKey (new (Key.C, null))); -// Application.MainLoop.MainIteration (); +// Application.MainLoop.RunIteration (); // Assert.True (copyAction); // } @@ -1256,7 +1255,7 @@ public void HotKey_MenuBar_ProcessHotKey_Menu_ProcessKey () TestHelpers.AssertDriverContentsAre (expectedMenu.expectedSubMenuOpen (0), output); Assert.True (Application.Top.Subviews [1].ProcessKey (new (Key.N, null))); - Application.MainLoop.MainIteration (); + Application.MainLoop.RunIteration (); Assert.True (newAction); Assert.True (menu.ProcessHotKey (new (Key.AltMask | Key.E, new KeyModifiers () { Alt = true }))); @@ -1265,7 +1264,7 @@ public void HotKey_MenuBar_ProcessHotKey_Menu_ProcessKey () TestHelpers.AssertDriverContentsAre (expectedMenu.expectedSubMenuOpen (1), output); Assert.True (Application.Top.Subviews [1].ProcessKey (new (Key.C, null))); - Application.MainLoop.MainIteration (); + Application.MainLoop.RunIteration (); Assert.True (copyAction); } @@ -1464,7 +1463,6 @@ public void Parent_MenuItem_Stay_Focused_If_Child_MenuItem_Is_Empty_By_Mouse () Application.Top.Redraw (Application.Top.Bounds); TestHelpers.AssertDriverContentsAre (expectedMenu.expectedSubMenuOpen (0), output); - Assert.True (menu.MouseEvent (new MouseEvent () { X = 8, Y = 0, Flags = MouseFlags.Button1Pressed, View = menu })); Assert.False (menu.IsMenuOpen); Assert.True (tf.HasFocus); @@ -1759,7 +1757,7 @@ public void AllowNullChecked_Get_Set () Assert.False (mi.Checked); Assert.True (menu.ProcessHotKey (new KeyEvent (Key.F9, new KeyModifiers ()))); Assert.True (menu.openMenu.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ()))); - Application.MainLoop.MainIteration (); + Application.MainLoop.RunIteration (); Assert.True (mi.Checked); Assert.True (menu.MouseEvent (new MouseEvent () { X = 0, @@ -1773,13 +1771,13 @@ public void AllowNullChecked_Get_Set () Flags = MouseFlags.Button1Clicked, View = menu.openMenu })); - Application.MainLoop.MainIteration (); + Application.MainLoop.RunIteration (); Assert.False (mi.Checked); mi.AllowNullChecked = true; Assert.True (menu.ProcessHotKey (new KeyEvent (Key.F9, new KeyModifiers ()))); Assert.True (menu.openMenu.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ()))); - Application.MainLoop.MainIteration (); + Application.MainLoop.RunIteration (); Assert.Null (mi.Checked); Assert.True (menu.MouseEvent (new MouseEvent () { X = 0, @@ -1799,11 +1797,11 @@ Nullable Checked Flags = MouseFlags.Button1Clicked, View = menu.openMenu })); - Application.MainLoop.MainIteration (); + Application.MainLoop.RunIteration (); Assert.True (mi.Checked); Assert.True (menu.ProcessHotKey (new KeyEvent (Key.F9, new KeyModifiers ()))); Assert.True (menu.openMenu.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ()))); - Application.MainLoop.MainIteration (); + Application.MainLoop.RunIteration (); Assert.False (mi.Checked); Assert.True (menu.MouseEvent (new MouseEvent () { X = 0, @@ -1817,7 +1815,7 @@ Nullable Checked Flags = MouseFlags.Button1Clicked, View = menu.openMenu })); - Application.MainLoop.MainIteration (); + Application.MainLoop.RunIteration (); Assert.Null (mi.Checked); mi.AllowNullChecked = false; @@ -1830,7 +1828,6 @@ Nullable Checked Assert.Throws (mi.ToggleChecked); } - [Fact, AutoInitShutdown] public void Menu_With_Separator () { diff --git a/UnitTests/Views/MdiTests.cs b/UnitTests/Views/OverlappedTests.cs similarity index 53% rename from UnitTests/Views/MdiTests.cs rename to UnitTests/Views/OverlappedTests.cs index 6277438776..c37ec5a45b 100644 --- a/UnitTests/Views/MdiTests.cs +++ b/UnitTests/Views/OverlappedTests.cs @@ -10,8 +10,8 @@ using Console = Terminal.Gui.FakeConsole; namespace Terminal.Gui.ViewsTests { - public class MdiTests { - public MdiTests () + public class OverlappedTests { + public OverlappedTests () { #if DEBUG_IDISPOSABLE Responder.Instances.Clear (); @@ -19,9 +19,8 @@ public MdiTests () #endif } - [Fact] - public void Dispose_Toplevel_IsMdiContainer_False_With_Begin_End () + public void Dispose_Toplevel_IsOverlappedContainer_False_With_Begin_End () { Application.Init (new FakeDriver ()); @@ -39,12 +38,12 @@ public void Dispose_Toplevel_IsMdiContainer_False_With_Begin_End () } [Fact] - public void Dispose_Toplevel_IsMdiContainer_True_With_Begin () + public void Dispose_Toplevel_IsOverlappedContainer_True_With_Begin () { Application.Init (new FakeDriver ()); - var mdi = new Toplevel { IsMdiContainer = true }; - var rs = Application.Begin (mdi); + var overlapped = new Toplevel { IsOverlappedContainer = true }; + var rs = Application.Begin (overlapped); Application.End (rs); Application.Shutdown (); @@ -56,7 +55,7 @@ public void Dispose_Toplevel_IsMdiContainer_True_With_Begin () } [Fact, AutoInitShutdown] - public void Application_RequestStop_With_Params_On_A_Not_MdiContainer_Always_Use_Application_Current () + public void Application_RequestStop_With_Params_On_A_Not_OverlappedContainer_Always_Use_Application_Current () { var top1 = new Toplevel (); var top2 = new Toplevel (); @@ -68,25 +67,25 @@ public void Application_RequestStop_With_Params_On_A_Not_MdiContainer_Always_Use var iterations = 4; top1.Ready += (s, e) => { - Assert.Null (Application.MdiChildren); + Assert.Null (Application.OverlappedChildren); Application.Run (top2); }; top2.Ready += (s, e) => { - Assert.Null (Application.MdiChildren); + Assert.Null (Application.OverlappedChildren); Application.Run (top3); }; top3.Ready += (s, e) => { - Assert.Null (Application.MdiChildren); + Assert.Null (Application.OverlappedChildren); Application.Run (top4); }; top4.Ready += (s, e) => { - Assert.Null (Application.MdiChildren); + Assert.Null (Application.OverlappedChildren); Application.Run (d); }; d.Ready += (s, e) => { - Assert.Null (Application.MdiChildren); - // This will close the d because on a not MdiContainer the Application.Current it always used. + Assert.Null (Application.OverlappedChildren); + // This will close the d because on a not OverlappedContainer the Application.Current it always used. Application.RequestStop (top1); Assert.True (Application.Current == d); }; @@ -94,7 +93,7 @@ public void Application_RequestStop_With_Params_On_A_Not_MdiContainer_Always_Use d.Closed += (s,e) => Application.RequestStop (top1); Application.Iteration += () => { - Assert.Null (Application.MdiChildren); + Assert.Null (Application.OverlappedChildren); if (iterations == 4) Assert.True (Application.Current == d); else if (iterations == 3) Assert.True (Application.Current == top4); else if (iterations == 2) Assert.True (Application.Current == top3); @@ -106,57 +105,57 @@ public void Application_RequestStop_With_Params_On_A_Not_MdiContainer_Always_Use Application.Run (top1); - Assert.Null (Application.MdiChildren); + Assert.Null (Application.OverlappedChildren); } - class Mdi : Toplevel { - public Mdi () + class Overlapped : Toplevel { + public Overlapped () { - IsMdiContainer = true; + IsOverlappedContainer = true; } } [Fact] [AutoInitShutdown] - public void MdiContainer_With_Toplevel_RequestStop_Balanced () + public void OverlappedContainer_With_Toplevel_RequestStop_Balanced () { - var mdi = new Mdi (); + var overlapped = new Overlapped (); var c1 = new Toplevel (); var c2 = new Window (); var c3 = new Window (); var d = new Dialog (); - // MdiChild = c1, c2, c3 + // OverlappedChild = c1, c2, c3 // d1 = 1 var iterations = 4; - mdi.Ready += (s, e) => { - Assert.Empty (Application.MdiChildren); + overlapped.Ready += (s, e) => { + Assert.Empty (Application.OverlappedChildren); Application.Run (c1); }; c1.Ready += (s, e) => { - Assert.Single (Application.MdiChildren); + Assert.Single (Application.OverlappedChildren); Application.Run (c2); }; c2.Ready += (s, e) => { - Assert.Equal (2, Application.MdiChildren.Count); + Assert.Equal (2, Application.OverlappedChildren.Count); Application.Run (c3); }; c3.Ready += (s, e) => { - Assert.Equal (3, Application.MdiChildren.Count); + Assert.Equal (3, Application.OverlappedChildren.Count); Application.Run (d); }; - // More easy because the Mdi Container handles all at once + // More easy because the Overlapped Container handles all at once d.Ready += (s, e) => { - Assert.Equal (3, Application.MdiChildren.Count); - // This will not close the MdiContainer because d is a modal toplevel and will be closed. - mdi.RequestStop (); + Assert.Equal (3, Application.OverlappedChildren.Count); + // This will not close the OverlappedContainer because d is a modal Toplevel and will be closed. + overlapped.RequestStop (); }; - // Now this will close the MdiContainer propagating through the MdiChildes. + // Now this will close the OverlappedContainer propagating through the OverlappedChildren. d.Closed += (s,e) => { - mdi.RequestStop (); + overlapped.RequestStop (); }; Application.Iteration += () => { @@ -165,57 +164,57 @@ public void MdiContainer_With_Toplevel_RequestStop_Balanced () Assert.True (Application.Current == d); Assert.False (d.Running); } else { - Assert.Equal (iterations, Application.MdiChildren.Count); - for (int i = 0; i < iterations; i++) Assert.Equal ((iterations - i + 1).ToString (), Application.MdiChildren [i].Id); + Assert.Equal (iterations, Application.OverlappedChildren.Count); + for (int i = 0; i < iterations; i++) Assert.Equal ((iterations - i + 1).ToString (), Application.OverlappedChildren [i].Id); } iterations--; }; - Application.Run (mdi); + Application.Run (overlapped); - Assert.Empty (Application.MdiChildren); + Assert.Empty (Application.OverlappedChildren); } [Fact] [AutoInitShutdown] - public void MdiContainer_With_Application_RequestStop_MdiTop_With_Params () + public void OverlappedContainer_With_Application_RequestStop_OverlappedTop_With_Params () { - var mdi = new Mdi (); + var overlapped = new Overlapped (); var c1 = new Toplevel (); var c2 = new Window (); var c3 = new Window (); var d = new Dialog (); - // MdiChild = c1, c2, c3 + // OverlappedChild = c1, c2, c3 // d1 = 1 var iterations = 4; - mdi.Ready += (s, e) => { - Assert.Empty (Application.MdiChildren); + overlapped.Ready += (s, e) => { + Assert.Empty (Application.OverlappedChildren); Application.Run (c1); }; c1.Ready += (s, e) => { - Assert.Single (Application.MdiChildren); + Assert.Single (Application.OverlappedChildren); Application.Run (c2); }; c2.Ready += (s, e) => { - Assert.Equal (2, Application.MdiChildren.Count); + Assert.Equal (2, Application.OverlappedChildren.Count); Application.Run (c3); }; c3.Ready += (s, e) => { - Assert.Equal (3, Application.MdiChildren.Count); + Assert.Equal (3, Application.OverlappedChildren.Count); Application.Run (d); }; - // Also easy because the Mdi Container handles all at once + // Also easy because the Overlapped Container handles all at once d.Ready += (s, e) => { - Assert.Equal (3, Application.MdiChildren.Count); - // This will not close the MdiContainer because d is a modal toplevel - Application.RequestStop (mdi); + Assert.Equal (3, Application.OverlappedChildren.Count); + // This will not close the OverlappedContainer because d is a modal Toplevel + Application.RequestStop (overlapped); }; - // Now this will close the MdiContainer propagating through the MdiChildes. - d.Closed += (s,e) => Application.RequestStop (mdi); + // Now this will close the OverlappedContainer propagating through the OverlappedChildren. + d.Closed += (s,e) => Application.RequestStop (overlapped); Application.Iteration += () => { if (iterations == 4) { @@ -223,153 +222,153 @@ public void MdiContainer_With_Application_RequestStop_MdiTop_With_Params () Assert.True (Application.Current == d); Assert.False (d.Running); } else { - Assert.Equal (iterations, Application.MdiChildren.Count); - for (int i = 0; i < iterations; i++) Assert.Equal ((iterations - i + 1).ToString (), Application.MdiChildren [i].Id); + Assert.Equal (iterations, Application.OverlappedChildren.Count); + for (int i = 0; i < iterations; i++) Assert.Equal ((iterations - i + 1).ToString (), Application.OverlappedChildren [i].Id); } iterations--; }; - Application.Run (mdi); + Application.Run (overlapped); - Assert.Empty (Application.MdiChildren); + Assert.Empty (Application.OverlappedChildren); } [Fact] [AutoInitShutdown] - public void MdiContainer_With_Application_RequestStop_MdiTop_Without_Params () + public void OverlappedContainer_With_Application_RequestStop_OverlappedTop_Without_Params () { - var mdi = new Mdi (); + var overlapped = new Overlapped (); var c1 = new Toplevel (); var c2 = new Window (); var c3 = new Window (); var d = new Dialog (); - // MdiChild = c1, c2, c3 = 3 + // OverlappedChild = c1, c2, c3 = 3 // d1 = 1 var iterations = 4; - mdi.Ready += (s, e) => { - Assert.Empty (Application.MdiChildren); + overlapped.Ready += (s, e) => { + Assert.Empty (Application.OverlappedChildren); Application.Run (c1); }; c1.Ready += (s, e) => { - Assert.Single (Application.MdiChildren); + Assert.Single (Application.OverlappedChildren); Application.Run (c2); }; c2.Ready += (s, e) => { - Assert.Equal (2, Application.MdiChildren.Count); + Assert.Equal (2, Application.OverlappedChildren.Count); Application.Run (c3); }; c3.Ready += (s, e) => { - Assert.Equal (3, Application.MdiChildren.Count); + Assert.Equal (3, Application.OverlappedChildren.Count); Application.Run (d); }; //More harder because it's sequential. d.Ready += (s, e) => { - Assert.Equal (3, Application.MdiChildren.Count); + Assert.Equal (3, Application.OverlappedChildren.Count); // Close the Dialog Application.RequestStop (); }; - // Now this will close the MdiContainer propagating through the MdiChildes. - d.Closed += (s, e) => Application.RequestStop (mdi); + // Now this will close the OverlappedContainer propagating through the OverlappedChildren. + d.Closed += (s, e) => Application.RequestStop (overlapped); Application.Iteration += () => { if (iterations == 4) { - // The Dialog still is the current top and we can't request stop to MdiContainer + // The Dialog still is the current top and we can't request stop to OverlappedContainer // because we are not using parameter calls. Assert.True (Application.Current == d); Assert.False (d.Running); } else { - Assert.Equal (iterations, Application.MdiChildren.Count); - for (int i = 0; i < iterations; i++) Assert.Equal ((iterations - i + 1).ToString (), Application.MdiChildren [i].Id); + Assert.Equal (iterations, Application.OverlappedChildren.Count); + for (int i = 0; i < iterations; i++) Assert.Equal ((iterations - i + 1).ToString (), Application.OverlappedChildren [i].Id); } iterations--; }; - Application.Run (mdi); + Application.Run (overlapped); - Assert.Empty (Application.MdiChildren); + Assert.Empty (Application.OverlappedChildren); } [Fact] [AutoInitShutdown] - public void IsMdiChild_Testing () + public void IsOverlappedChild_Testing () { - var mdi = new Mdi (); + var overlapped = new Overlapped (); var c1 = new Toplevel (); var c2 = new Window (); var c3 = new Window (); var d = new Dialog (); Application.Iteration += () => { - Assert.False (mdi.IsMdiChild); - Assert.True (c1.IsMdiChild); - Assert.True (c2.IsMdiChild); - Assert.True (c3.IsMdiChild); - Assert.False (d.IsMdiChild); + Assert.False (overlapped.IsOverlapped); + Assert.True (c1.IsOverlapped); + Assert.True (c2.IsOverlapped); + Assert.True (c3.IsOverlapped); + Assert.False (d.IsOverlapped); - mdi.RequestStop (); + overlapped.RequestStop (); }; - Application.Run (mdi); + Application.Run (overlapped); } [Fact] [AutoInitShutdown] public void Modal_Toplevel_Can_Open_Another_Modal_Toplevel_But_RequestStop_To_The_Caller_Also_Sets_Current_Running_To_False_Too () { - var mdi = new Mdi (); + var overlapped = new Overlapped (); var c1 = new Toplevel (); var c2 = new Window (); var c3 = new Window (); var d1 = new Dialog (); var d2 = new Dialog (); - // MdiChild = c1, c2, c3 = 3 + // OverlappedChild = c1, c2, c3 = 3 // d1, d2 = 2 var iterations = 5; - mdi.Ready += (s, e) => { - Assert.Empty (Application.MdiChildren); + overlapped.Ready += (s, e) => { + Assert.Empty (Application.OverlappedChildren); Application.Run (c1); }; c1.Ready += (s, e) => { - Assert.Single (Application.MdiChildren); + Assert.Single (Application.OverlappedChildren); Application.Run (c2); }; c2.Ready += (s, e) => { - Assert.Equal (2, Application.MdiChildren.Count); + Assert.Equal (2, Application.OverlappedChildren.Count); Application.Run (c3); }; c3.Ready += (s, e) => { - Assert.Equal (3, Application.MdiChildren.Count); + Assert.Equal (3, Application.OverlappedChildren.Count); Application.Run (d1); }; d1.Ready += (s, e) => { - Assert.Equal (3, Application.MdiChildren.Count); + Assert.Equal (3, Application.OverlappedChildren.Count); Application.Run (d2); }; d2.Ready += (s, e) => { - Assert.Equal (3, Application.MdiChildren.Count); + Assert.Equal (3, Application.OverlappedChildren.Count); Assert.True (Application.Current == d2); Assert.True (Application.Current.Running); // Trying to close the Dialog1 d1.RequestStop (); }; - // Now this will close the MdiContainer propagating through the MdiChildes. + // Now this will close the OverlappedContainer propagating through the OverlappedChildren. d1.Closed += (s, e) => { Assert.True (Application.Current == d1); Assert.False (Application.Current.Running); - mdi.RequestStop (); + overlapped.RequestStop (); }; Application.Iteration += () => { if (iterations == 5) { - // The Dialog2 still is the current top and we can't request stop to MdiContainer + // The Dialog2 still is the current top and we can't request stop to OverlappedContainer // because Dialog2 and Dialog1 must be closed first. // Dialog2 will be closed in this iteration. Assert.True (Application.Current == d2); @@ -380,185 +379,185 @@ public void Modal_Toplevel_Can_Open_Another_Modal_Toplevel_But_RequestStop_To_Th Assert.True (Application.Current == d1); Assert.False (Application.Current.Running); } else { - Assert.Equal (iterations, Application.MdiChildren.Count); - for (int i = 0; i < iterations; i++) Assert.Equal ((iterations - i + 1).ToString (), Application.MdiChildren [i].Id); + Assert.Equal (iterations, Application.OverlappedChildren.Count); + for (int i = 0; i < iterations; i++) Assert.Equal ((iterations - i + 1).ToString (), Application.OverlappedChildren [i].Id); } iterations--; }; - Application.Run (mdi); + Application.Run (overlapped); - Assert.Empty (Application.MdiChildren); + Assert.Empty (Application.OverlappedChildren); } [Fact] [AutoInitShutdown] public void Modal_Toplevel_Can_Open_Another_Not_Modal_Toplevel_But_RequestStop_To_The_Caller_Also_Sets_Current_Running_To_False_Too () { - var mdi = new Mdi (); + var overlapped = new Overlapped (); var c1 = new Toplevel (); var c2 = new Window (); var c3 = new Window (); var d1 = new Dialog (); var c4 = new Toplevel (); - // MdiChild = c1, c2, c3, c4 = 4 + // OverlappedChild = c1, c2, c3, c4 = 4 // d1 = 1 var iterations = 5; - mdi.Ready += (s, e) => { - Assert.Empty (Application.MdiChildren); + overlapped.Ready += (s, e) => { + Assert.Empty (Application.OverlappedChildren); Application.Run (c1); }; c1.Ready += (s, e) => { - Assert.Single (Application.MdiChildren); + Assert.Single (Application.OverlappedChildren); Application.Run (c2); }; c2.Ready += (s, e) => { - Assert.Equal (2, Application.MdiChildren.Count); + Assert.Equal (2, Application.OverlappedChildren.Count); Application.Run (c3); }; c3.Ready += (s, e) => { - Assert.Equal (3, Application.MdiChildren.Count); + Assert.Equal (3, Application.OverlappedChildren.Count); Application.Run (d1); }; d1.Ready += (s, e) => { - Assert.Equal (3, Application.MdiChildren.Count); + Assert.Equal (3, Application.OverlappedChildren.Count); Application.Run (c4); }; c4.Ready += (s, e) => { - Assert.Equal (4, Application.MdiChildren.Count); + Assert.Equal (4, Application.OverlappedChildren.Count); // Trying to close the Dialog1 d1.RequestStop (); }; - // Now this will close the MdiContainer propagating through the MdiChildes. + // Now this will close the OverlappedContainer propagating through the OverlappedChildren. d1.Closed += (s, e) => { - mdi.RequestStop (); + overlapped.RequestStop (); }; Application.Iteration += () => { if (iterations == 5) { - // The Dialog2 still is the current top and we can't request stop to MdiContainer + // The Dialog2 still is the current top and we can't request stop to OverlappedContainer // because Dialog2 and Dialog1 must be closed first. // Using request stop here will call the Dialog again without need Assert.True (Application.Current == d1); Assert.False (Application.Current.Running); Assert.True (c4.Running); } else { - Assert.Equal (iterations, Application.MdiChildren.Count); + Assert.Equal (iterations, Application.OverlappedChildren.Count); for (int i = 0; i < iterations; i++) Assert.Equal ((iterations - i + (iterations == 4 && i == 0 ? 2 : 1)).ToString (), - Application.MdiChildren [i].Id); + Application.OverlappedChildren [i].Id); } iterations--; }; - Application.Run (mdi); + Application.Run (overlapped); - Assert.Empty (Application.MdiChildren); + Assert.Empty (Application.OverlappedChildren); } [Fact] [AutoInitShutdown] public void MoveCurrent_Returns_False_If_The_Current_And_Top_Parameter_Are_Both_With_Running_Set_To_False () { - var mdi = new Mdi (); + var overlapped = new Overlapped (); var c1 = new Toplevel (); var c2 = new Window (); var c3 = new Window (); - // MdiChild = c1, c2, c3 + // OverlappedChild = c1, c2, c3 var iterations = 3; - mdi.Ready += (s, e) => { - Assert.Empty (Application.MdiChildren); + overlapped.Ready += (s, e) => { + Assert.Empty (Application.OverlappedChildren); Application.Run (c1); }; c1.Ready += (s, e) => { - Assert.Single (Application.MdiChildren); + Assert.Single (Application.OverlappedChildren); Application.Run (c2); }; c2.Ready += (s, e) => { - Assert.Equal (2, Application.MdiChildren.Count); + Assert.Equal (2, Application.OverlappedChildren.Count); Application.Run (c3); }; c3.Ready += (s, e) => { - Assert.Equal (3, Application.MdiChildren.Count); + Assert.Equal (3, Application.OverlappedChildren.Count); c3.RequestStop (); c1.RequestStop (); }; - // Now this will close the MdiContainer propagating through the MdiChildes. + // Now this will close the OverlappedContainer propagating through the OverlappedChildren. c1.Closed += (s, e) => { - mdi.RequestStop (); + overlapped.RequestStop (); }; Application.Iteration += () => { if (iterations == 3) { // The Current still is c3 because Current.Running is false. Assert.True (Application.Current == c3); Assert.False (Application.Current.Running); - // But the childes order were reorder by Running = false - Assert.True (Application.MdiChildren [0] == c3); - Assert.True (Application.MdiChildren [1] == c1); - Assert.True (Application.MdiChildren [^1] == c2); + // But the Children order were reorder by Running = false + Assert.True (Application.OverlappedChildren [0] == c3); + Assert.True (Application.OverlappedChildren [1] == c1); + Assert.True (Application.OverlappedChildren [^1] == c2); } else if (iterations == 2) { // The Current is c1 and Current.Running is false. Assert.True (Application.Current == c1); Assert.False (Application.Current.Running); - Assert.True (Application.MdiChildren [0] == c1); - Assert.True (Application.MdiChildren [^1] == c2); + Assert.True (Application.OverlappedChildren [0] == c1); + Assert.True (Application.OverlappedChildren [^1] == c2); } else if (iterations == 1) { // The Current is c2 and Current.Running is false. Assert.True (Application.Current == c2); Assert.False (Application.Current.Running); - Assert.True (Application.MdiChildren [^1] == c2); + Assert.True (Application.OverlappedChildren [^1] == c2); } else { - // The Current is mdi. - Assert.True (Application.Current == mdi); - Assert.Empty (Application.MdiChildren); + // The Current is overlapped. + Assert.True (Application.Current == overlapped); + Assert.Empty (Application.OverlappedChildren); } iterations--; }; - Application.Run (mdi); + Application.Run (overlapped); - Assert.Empty (Application.MdiChildren); + Assert.Empty (Application.OverlappedChildren); } [Fact] [AutoInitShutdown] - public void MdiContainer_Throws_If_More_Than_One () + public void OverlappedContainer_Throws_If_More_Than_One () { - var mdi = new Mdi (); - var mdi2 = new Mdi (); + var overlapped = new Overlapped (); + var overlapped2 = new Overlapped (); - mdi.Ready += (s, e) => { - Assert.Throws (() => Application.Run (mdi2)); - mdi.RequestStop (); + overlapped.Ready += (s, e) => { + Assert.Throws (() => Application.Run (overlapped2)); + overlapped.RequestStop (); }; - Application.Run (mdi); + Application.Run (overlapped); } [Fact] [AutoInitShutdown] - public void MdiContainer_Open_And_Close_Modal_And_Open_Not_Modal_Toplevels_Randomly () + public void OverlappedContainer_Open_And_Close_Modal_And_Open_Not_Modal_Toplevels_Randomly () { - var mdi = new Mdi (); + var overlapped = new Overlapped (); var logger = new Toplevel (); var iterations = 1; // The logger var running = true; var stageCompleted = true; var allStageClosed = false; - var mdiRequestStop = false; + var overlappedRequestStop = false; - mdi.Ready += (s, e) => { - Assert.Empty (Application.MdiChildren); + overlapped.Ready += (s, e) => { + Assert.Empty (Application.OverlappedChildren); Application.Run (logger); }; - logger.Ready += (s, e) => Assert.Single (Application.MdiChildren); + logger.Ready += (s, e) => Assert.Single (Application.OverlappedChildren); Application.Iteration += () => { if (stageCompleted && running) { @@ -566,13 +565,13 @@ public void MdiContainer_Open_And_Close_Modal_And_Open_Not_Modal_Toplevels_Rando var stage = new Window () { Modal = true }; stage.Ready += (s, e) => { - Assert.Equal (iterations, Application.MdiChildren.Count); + Assert.Equal (iterations, Application.OverlappedChildren.Count); stage.RequestStop (); }; stage.Closed += (_, _) => { if (iterations == 11) allStageClosed = true; - Assert.Equal (iterations, Application.MdiChildren.Count); + Assert.Equal (iterations, Application.OverlappedChildren.Count); if (running) { stageCompleted = true; @@ -580,7 +579,7 @@ public void MdiContainer_Open_And_Close_Modal_And_Open_Not_Modal_Toplevels_Rando rpt.Ready += (s, e) => { iterations++; - Assert.Equal (iterations, Application.MdiChildren.Count); + Assert.Equal (iterations, Application.OverlappedChildren.Count); }; Application.Run (rpt); @@ -591,87 +590,87 @@ public void MdiContainer_Open_And_Close_Modal_And_Open_Not_Modal_Toplevels_Rando } else if (iterations == 11 && running) { running = false; - Assert.Equal (iterations, Application.MdiChildren.Count); + Assert.Equal (iterations, Application.OverlappedChildren.Count); - } else if (!mdiRequestStop && running && !allStageClosed) Assert.Equal (iterations, Application.MdiChildren.Count); -else if (!mdiRequestStop && !running && allStageClosed) { - Assert.Equal (iterations, Application.MdiChildren.Count); - mdiRequestStop = true; - mdi.RequestStop (); - } else Assert.Empty (Application.MdiChildren); + } else if (!overlappedRequestStop && running && !allStageClosed) Assert.Equal (iterations, Application.OverlappedChildren.Count); +else if (!overlappedRequestStop && !running && allStageClosed) { + Assert.Equal (iterations, Application.OverlappedChildren.Count); + overlappedRequestStop = true; + overlapped.RequestStop (); + } else Assert.Empty (Application.OverlappedChildren); }; - Application.Run (mdi); + Application.Run (overlapped); - Assert.Empty (Application.MdiChildren); + Assert.Empty (Application.OverlappedChildren); } [Fact] [AutoInitShutdown] public void AllChildClosed_Event_Test () { - var mdi = new Mdi (); + var overlapped = new Overlapped (); var c1 = new Toplevel (); var c2 = new Window (); var c3 = new Window (); - // MdiChild = c1, c2, c3 + // OverlappedChild = c1, c2, c3 var iterations = 3; - mdi.Ready += (s, e) => { - Assert.Empty (Application.MdiChildren); + overlapped.Ready += (s, e) => { + Assert.Empty (Application.OverlappedChildren); Application.Run (c1); }; c1.Ready += (s, e) => { - Assert.Single (Application.MdiChildren); + Assert.Single (Application.OverlappedChildren); Application.Run (c2); }; c2.Ready += (s, e) => { - Assert.Equal (2, Application.MdiChildren.Count); + Assert.Equal (2, Application.OverlappedChildren.Count); Application.Run (c3); }; c3.Ready += (s, e) => { - Assert.Equal (3, Application.MdiChildren.Count); + Assert.Equal (3, Application.OverlappedChildren.Count); c3.RequestStop (); c2.RequestStop (); c1.RequestStop (); }; - // Now this will close the MdiContainer when all MdiChildes was closed - mdi.AllChildClosed += (s, e) => { - mdi.RequestStop (); + // Now this will close the OverlappedContainer when all OverlappedChildren was closed + overlapped.AllChildClosed += (s, e) => { + overlapped.RequestStop (); }; Application.Iteration += () => { if (iterations == 3) { // The Current still is c3 because Current.Running is false. Assert.True (Application.Current == c3); Assert.False (Application.Current.Running); - // But the childes order were reorder by Running = false - Assert.True (Application.MdiChildren [0] == c3); - Assert.True (Application.MdiChildren [1] == c2); - Assert.True (Application.MdiChildren [^1] == c1); + // But the Children order were reorder by Running = false + Assert.True (Application.OverlappedChildren [0] == c3); + Assert.True (Application.OverlappedChildren [1] == c2); + Assert.True (Application.OverlappedChildren [^1] == c1); } else if (iterations == 2) { // The Current is c2 and Current.Running is false. Assert.True (Application.Current == c2); Assert.False (Application.Current.Running); - Assert.True (Application.MdiChildren [0] == c2); - Assert.True (Application.MdiChildren [^1] == c1); + Assert.True (Application.OverlappedChildren [0] == c2); + Assert.True (Application.OverlappedChildren [^1] == c1); } else if (iterations == 1) { // The Current is c1 and Current.Running is false. Assert.True (Application.Current == c1); Assert.False (Application.Current.Running); - Assert.True (Application.MdiChildren [^1] == c1); + Assert.True (Application.OverlappedChildren [^1] == c1); } else { - // The Current is mdi. - Assert.True (Application.Current == mdi); + // The Current is overlapped. + Assert.True (Application.Current == overlapped); Assert.False (Application.Current.Running); - Assert.Empty (Application.MdiChildren); + Assert.Empty (Application.OverlappedChildren); } iterations--; }; - Application.Run (mdi); + Application.Run (overlapped); - Assert.Empty (Application.MdiChildren); + Assert.Empty (Application.OverlappedChildren); } } } diff --git a/UnitTests/Views/ScrollBarViewTests.cs b/UnitTests/Views/ScrollBarViewTests.cs index 85b61b05bb..f708181722 100644 --- a/UnitTests/Views/ScrollBarViewTests.cs +++ b/UnitTests/Views/ScrollBarViewTests.cs @@ -978,7 +978,6 @@ public void Hosting_ShowBothScrollIndicator_Invisible () Assert.Equal (new Rect (0, 0, 10, 10), pos); } - [Fact, AutoInitShutdown] public void ContentBottomRightCorner_Not_Redraw_If_Both_Size_Equal_To_Zero () { @@ -1155,7 +1154,6 @@ This is a test Assert.False (sbv.Visible); } - [Fact, AutoInitShutdown] public void ClearOnVisibleFalse_Gets_Sets () { diff --git a/UnitTests/Views/SpinnerViewTests.cs b/UnitTests/Views/SpinnerViewTests.cs index 9c30e753f4..7260d1082f 100644 --- a/UnitTests/Views/SpinnerViewTests.cs +++ b/UnitTests/Views/SpinnerViewTests.cs @@ -73,14 +73,12 @@ public void TestSpinnerView_NoThrottle () view.Redraw (view.Bounds); - var expected = @"─"; TestHelpers.AssertDriverContentsWithFrameAre (expected, output); view.SetNeedsDisplay (); view.Redraw (view.Bounds); - expected = @"\"; TestHelpers.AssertDriverContentsWithFrameAre (expected, output); } diff --git a/UnitTests/Views/TabViewTests.cs b/UnitTests/Views/TabViewTests.cs index 419fb646ef..75e2ec816b 100644 --- a/UnitTests/Views/TabViewTests.cs +++ b/UnitTests/Views/TabViewTests.cs @@ -300,7 +300,6 @@ public void ShowTopLine_True_TabsOnBottom_False_TestThinTabView_WithLongNames () │hi2 │ └────────┘", output); - // now make both tabs too long tab1.Text = "12345678910"; tab2.Text = "abcdefghijklmnopq"; @@ -340,7 +339,6 @@ public void ShowTopLine_False_TabsOnBottom_False_TestThinTabView_WithLongNames ( │ │ └────────┘", output); - tv.SelectedTab = tab2; tv.Redraw (tv.Bounds); @@ -378,7 +376,6 @@ public void ShowTopLine_False_TabsOnBottom_False_TestThinTabView_WithLongNames ( │ │ └────────┘", output); - // now make both tabs too long tab1.Text = "12345678910"; tab2.Text = "abcdefghijklmnopq"; @@ -494,7 +491,6 @@ public void ShowTopLine_True_TabsOnBottom_True_TestThinTabView_WithLongNames () │12│13 └──┘ ", output); - // Test first tab name too long tab1.Text = "12345678910"; tab2.Text = "13"; @@ -519,7 +515,6 @@ public void ShowTopLine_True_TabsOnBottom_True_TestThinTabView_WithLongNames () │13│ └──┘ ", output); - // now make both tabs too long tab1.Text = "12345678910"; tab2.Text = "abcdefghijklmnopq"; @@ -559,7 +554,6 @@ public void ShowTopLine_False_TabsOnBottom_True_TestThinTabView_WithLongNames () │ ┌─────┘ │12│13 ", output); - tv.SelectedTab = tab2; tv.Redraw (tv.Bounds); @@ -597,7 +591,6 @@ public void ShowTopLine_False_TabsOnBottom_True_TestThinTabView_WithLongNames () ◄ ┌─────┘ │13│ ", output); - // now make both tabs too long tab1.Text = "12345678910"; tab2.Text = "abcdefghijklmnopq"; @@ -812,7 +805,6 @@ public void MouseClick_ChangesTab () Assert.Equal(tab1, clicked); Assert.Equal(tab1, tv.SelectedTab); - // Click to tab2 tabRow.MouseEvent(new MouseEvent{ X = 7, @@ -839,7 +831,6 @@ public void MouseClick_ChangesTab () Assert.Equal(tab1, clicked); Assert.Equal(tab2, tv.SelectedTab); - tabRow.MouseEvent (new MouseEvent { X = 10, Y = 1, diff --git a/UnitTests/Views/TableViewTests.cs b/UnitTests/Views/TableViewTests.cs index 8d6acde2b1..8f192c0aa9 100644 --- a/UnitTests/Views/TableViewTests.cs +++ b/UnitTests/Views/TableViewTests.cs @@ -37,7 +37,6 @@ public void EnsureValidScrollOffsets_WithNoCells () } - [Fact] public void EnsureValidScrollOffsets_LoadSmallerTable () { @@ -67,7 +66,6 @@ public void EnsureValidScrollOffsets_LoadSmallerTable () Assert.Equal (0, tableView.RowOffset); Assert.Equal (0, tableView.ColumnOffset); - // Trying to set invalid indexes should not be possible tableView.RowOffset = 20; tableView.ColumnOffset = 10; @@ -92,7 +90,6 @@ public void Redraw_EmptyTable () tableView.Redraw (tableView.Bounds); } - [Fact] public void SelectedCellChanged_NotFiredForSameValue () { @@ -116,7 +113,6 @@ public void SelectedCellChanged_NotFiredForSameValue () } - [Fact] public void SelectedCellChanged_SelectedColumnIndexesCorrect () { @@ -199,7 +195,6 @@ public void IsSelected_MultiSelectionOn_Vertical () Assert.False (tableView.IsSelected (2, 4)); } - [Fact] public void IsSelected_MultiSelectionOn_Horizontal () { @@ -224,7 +219,6 @@ public void IsSelected_MultiSelectionOn_Horizontal () } - [Fact] public void IsSelected_MultiSelectionOn_BoxSelection () { @@ -269,7 +263,6 @@ public void PageDown_ExcludesHeaders () Application.Top.Add (tableView); Application.Begin (Application.Top); - Application.Top.FocusFirst (); Assert.True (tableView.HasFocus); @@ -315,7 +308,6 @@ public void DeleteRow_SelectAll_AdjustsSelectionToPreventOverrun () Assert.Equal (9, tableView.GetAllSelectedCells ().Count ()); } - [Fact] public void DeleteRow_SelectLastRow_AdjustsSelectionToPreventOverrun () { @@ -362,7 +354,6 @@ public void GetAllSelectedCells_SingleCellSelected_ReturnsOne (bool multiSelect) Assert.Equal (new Point (1, 1), tableView.GetAllSelectedCells ().Single ()); } - [Fact] public void GetAllSelectedCells_SquareSelection_ReturnsFour () { @@ -387,7 +378,6 @@ public void GetAllSelectedCells_SquareSelection_ReturnsFour () Assert.Equal (new Point (2, 2), selected [3]); } - [Fact] public void GetAllSelectedCells_SquareSelection_FullRowSelect () { @@ -415,7 +405,6 @@ public void GetAllSelectedCells_SquareSelection_FullRowSelect () Assert.Equal (new Point (2, 2), selected [5]); } - [Fact] public void GetAllSelectedCells_TwoIsolatedSelections_ReturnsSix () { @@ -477,7 +466,6 @@ public void TableView_ExpandLastColumn_True () Application.Shutdown (); } - [Fact, AutoInitShutdown] public void TableView_ExpandLastColumn_False () { @@ -757,7 +745,6 @@ public void TableView_ColorTests_FocusedOrNot (bool focused) "; TestHelpers.AssertDriverContentsAre (expected, output); - string expectedColors = @" 00000 00000 @@ -802,7 +789,6 @@ public void TableView_ColorTests_InvertSelectedCellFirstCharacter (bool focused) "; TestHelpers.AssertDriverContentsAre (expected, output); - string expectedColors = @" 00000 00000 @@ -822,7 +808,6 @@ public void TableView_ColorTests_InvertSelectedCellFirstCharacter (bool focused) Application.Shutdown (); } - [Theory, AutoInitShutdown] [InlineData (false)] [InlineData (true)] @@ -860,7 +845,6 @@ public void TableView_ColorsTest_RowColorGetter (bool focused) "; TestHelpers.AssertDriverContentsAre (expected, output); - string expectedColors = @" 00000 00000 @@ -876,7 +860,6 @@ public void TableView_ColorsTest_RowColorGetter (bool focused) // 2 rowHighlight.Normal}); - // change the value in the table so that // it no longer matches the RowColorGetter // delegate conditional ( which checks for @@ -892,7 +875,6 @@ public void TableView_ColorsTest_RowColorGetter (bool focused) "; TestHelpers.AssertDriverContentsAre (expected, output); - expectedColors = @" 00000 00000 @@ -909,7 +891,6 @@ public void TableView_ColorsTest_RowColorGetter (bool focused) // 1 focused ? tv.ColorScheme.HotFocus : tv.ColorScheme.HotNormal }); - // Shutdown must be called to safely clean up Application if Init has been called Application.Shutdown (); } @@ -954,7 +935,6 @@ public void TableView_ColorsTest_ColorGetter (bool focused) "; TestHelpers.AssertDriverContentsAre (expected, output); - string expectedColors = @" 00000 00000 @@ -970,7 +950,6 @@ public void TableView_ColorsTest_ColorGetter (bool focused) // 2 cellHighlight.Normal}); - // change the value in the table so that // it no longer matches the ColorGetter // delegate conditional ( which checks for @@ -986,7 +965,6 @@ public void TableView_ColorsTest_ColorGetter (bool focused) "; TestHelpers.AssertDriverContentsAre (expected, output); - expectedColors = @" 00000 00000 @@ -1003,7 +981,6 @@ public void TableView_ColorsTest_ColorGetter (bool focused) // 1 focused ? tv.ColorScheme.HotFocus : tv.ColorScheme.HotNormal }); - // Shutdown must be called to safely clean up Application if Init has been called Application.Shutdown (); } @@ -1097,11 +1074,9 @@ public void ScrollRight_SmoothScrolling () TestHelpers.AssertDriverContentsAre (expected, output); - // Scroll right tableView.ProcessKey (new KeyEvent () { Key = Key.CursorRight }); - tableView.Redraw (tableView.Bounds); // Note that with SmoothHorizontalScrolling only a single new column @@ -1118,7 +1093,6 @@ public void ScrollRight_SmoothScrolling () TestHelpers.AssertDriverContentsAre (expected, output); - // Shutdown must be called to safely clean up Application if Init has been called Application.Shutdown (); } @@ -1161,11 +1135,9 @@ public void ScrollRight_WithoutSmoothScrolling () TestHelpers.AssertDriverContentsAre (expected, output); - // Scroll right tableView.ProcessKey (new KeyEvent () { Key = Key.CursorRight }); - tableView.Redraw (tableView.Bounds); // notice that without smooth scrolling we just update the first column @@ -1174,7 +1146,6 @@ public void ScrollRight_WithoutSmoothScrolling () // area as we scroll until the new column (D) is exposed. But it makes // the view 'jump' to expose all new columns - expected = @" │D│E│F│ @@ -1182,7 +1153,6 @@ public void ScrollRight_WithoutSmoothScrolling () TestHelpers.AssertDriverContentsAre (expected, output); - // Shutdown must be called to safely clean up Application if Init has been called Application.Shutdown (); } @@ -1209,7 +1179,6 @@ private TableView GetABCDEFTableView (out DataTable dt) dt.Columns.Add ("E"); dt.Columns.Add ("F"); - dt.Rows.Add (1, 2, 3, 4, 5, 6); tableView.Table = dt; @@ -1254,7 +1223,6 @@ public void TestColumnStyle_FirstColumnVisibleFalse_IsNotRendered () TestHelpers.AssertDriverContentsAre (expected, output); } - [Fact, AutoInitShutdown] public void TestColumnStyle_AllColumnsVisibleFalse_BehavesAsTableNull () { @@ -1268,7 +1236,6 @@ public void TestColumnStyle_AllColumnsVisibleFalse_BehavesAsTableNull () tableView.Style.GetOrCreateColumnStyle (dt.Columns ["F"]).Visible = false; tableView.LayoutSubviews (); - // expect nothing to be rendered when all columns are invisible string expected = @" @@ -1277,7 +1244,6 @@ public void TestColumnStyle_AllColumnsVisibleFalse_BehavesAsTableNull () tableView.Redraw (tableView.Bounds); TestHelpers.AssertDriverContentsAre (expected, output); - // expect behavior to match when Table is null tableView.Table = null; @@ -1407,7 +1373,6 @@ public void TestColumnStyle_FirstColumnVisibleFalse_CursorStaysAt1 (bool useHome Assert.Equal (1, tableView.SelectedColumn); } - [InlineData (true)] [InlineData (false)] [Theory, AutoInitShutdown] @@ -1586,7 +1551,6 @@ public void TestToggleCells_MultiSelectOn () Assert.Equal (0, s2.X); Assert.Equal (1, s2.Y); - // Go back to the toggled cell tableView.ProcessKey (new KeyEvent { Key = Key.CursorRight }); tableView.ProcessKey (new KeyEvent { Key = Key.CursorUp }); @@ -1639,7 +1603,6 @@ public void TestToggleCells_MultiSelectOn_FullRowSelect () } - [Fact, AutoInitShutdown] public void TestToggleCells_MultiSelectOn_SquareSelectToggled () { @@ -1709,7 +1672,6 @@ public void TestToggleCells_MultiSelectOn_Two_SquareSelects_BothToggled () Assert.Equal (8, tableView.GetAllSelectedCells ().Count ()); } - [Theory, AutoInitShutdown] [InlineData (new object [] { true, true })] [InlineData (new object [] { false, true })] @@ -1888,7 +1850,6 @@ public void LongColumnTest () Application.Shutdown (); } - [Fact, AutoInitShutdown] public void ScrollIndicators () { @@ -1931,11 +1892,9 @@ public void ScrollIndicators () TestHelpers.AssertDriverContentsAre (expected, output); - // Scroll right tableView.ProcessKey (new KeyEvent () { Key = Key.CursorRight }); - // since A is now pushed off screen we get indicator showing // that user can scroll left to see first column tableView.Redraw (tableView.Bounds); @@ -1948,7 +1907,6 @@ public void ScrollIndicators () TestHelpers.AssertDriverContentsAre (expected, output); - // Scroll right twice more (to end of columns) tableView.ProcessKey (new KeyEvent () { Key = Key.CursorRight }); tableView.ProcessKey (new KeyEvent () { Key = Key.CursorRight }); @@ -2021,7 +1979,6 @@ public void CellEventsBackgroundFill () } - tv.Redraw (tv.Bounds); expected = @" @@ -2032,7 +1989,6 @@ public void CellEventsBackgroundFill () "; TestHelpers.AssertDriverColorsAre (expected, new Attribute [] { tv.ColorScheme.Normal, color }); - } /// @@ -2102,7 +2058,6 @@ public void Test_ScreenToCell () // after last row Assert.Null (tableView.ScreenToCell (1, 4)); - // ---------------- X=2 ----------------------- // ( even though there is a horizontal dividing line here we treat it as a hit on the cell before) // click in header @@ -2116,7 +2071,6 @@ public void Test_ScreenToCell () // after last row Assert.Null (tableView.ScreenToCell (2, 4)); - // ---------------- X=3 ----------------------- // click in header Assert.Null (tableView.ScreenToCell (3, 0)); @@ -2180,7 +2134,6 @@ public void Test_ScreenToCell_DataColumnOverload () Assert.Null (tableView.ScreenToCell (1, 4, out col)); Assert.Null (col); - // ---------------- X=2 ----------------------- // click in header Assert.Null (tableView.ScreenToCell (2, 0, out col)); @@ -2198,7 +2151,6 @@ public void Test_ScreenToCell_DataColumnOverload () Assert.Null (tableView.ScreenToCell (2, 4, out col)); Assert.Null (col); - // ---------------- X=3 ----------------------- // click in header Assert.Null (tableView.ScreenToCell (3, 0, out col)); diff --git a/UnitTests/Views/TextFieldTests.cs b/UnitTests/Views/TextFieldTests.cs index a5ac8bcfca..0084a3e23b 100644 --- a/UnitTests/Views/TextFieldTests.cs +++ b/UnitTests/Views/TextFieldTests.cs @@ -1286,7 +1286,6 @@ public void Test_RootMouseKeyEvent_Cancel () Assert.Equal (2, clickCounter); } - private bool SuppressKey (KeyEvent arg) { if (arg.KeyValue == 'j') @@ -1361,7 +1360,6 @@ public void TestSetTextAndMoveCursorToEnd_WhenExistingSelection (string newText) Assert.Null (tf.SelectedText); } - [Fact] public void WordBackward_WordForward_SelectedText_With_Accent () { @@ -1532,7 +1530,6 @@ public void CaptionedTextField_DoesNotOverspillBounds (string caption, string ex TestHelpers.AssertDriverContentsAre (expectedRender, output); } - private TextField GetTextFieldsInView () { var tf = new TextField { diff --git a/UnitTests/Views/TextValidateFieldTests.cs b/UnitTests/Views/TextValidateFieldTests.cs index 777474dc72..2b3b5bd3ca 100644 --- a/UnitTests/Views/TextValidateFieldTests.cs +++ b/UnitTests/Views/TextValidateFieldTests.cs @@ -184,7 +184,6 @@ public void Insert_Skips_Non_Editable_Characters () Assert.True (field.IsValid); } - [Fact] [AutoInitShutdown] public void Initial_Value_Exact_Valid () @@ -296,7 +295,6 @@ public void Backspace_Key_Deletes_Previous_Character () Assert.False (field.IsValid); } - [Fact] [AutoInitShutdown] public void Set_Text_After_Initialization () diff --git a/UnitTests/Views/TextViewTests.cs b/UnitTests/Views/TextViewTests.cs index f92a34922f..4ffbcc0710 100644 --- a/UnitTests/Views/TextViewTests.cs +++ b/UnitTests/Views/TextViewTests.cs @@ -5231,7 +5231,6 @@ public void HistoryText_Undo_Redo_KillToStartOfLine () Assert.Equal (1, tv.Lines); Assert.Equal (new Point (0, 0), tv.CursorPosition); - // Undo Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ()))); Assert.Equal ("First line.", tv.Text); @@ -6739,7 +6738,6 @@ public void ContentsChanged_Event_Fires_Using_Kill_Delete_Tests () Assert.Equal (expectedEventCount, eventcount); } - [Fact, TextViewTestsAutoInitShutdown] public void ContentsChanged_Event_Fires_Using_Copy_Or_Cut_Tests () { diff --git a/UnitTests/Views/TileViewTests.cs b/UnitTests/Views/TileViewTests.cs index 2e637f55b7..58cca243ee 100644 --- a/UnitTests/Views/TileViewTests.cs +++ b/UnitTests/Views/TileViewTests.cs @@ -6,7 +6,6 @@ // BUGBUG: v2 - These tests are all broken for now - //namespace Terminal.Gui.ViewsTests { // public class TileViewTests { // readonly ITestOutputHelper output; @@ -16,7 +15,6 @@ // this.output = output; // } - // [Fact, AutoInitShutdown] // public void TestTileView_Vertical () // { @@ -83,7 +81,6 @@ // │ "; // TestHelpers.AssertDriverContentsAre (looksLike, output); - // // and 2 to the left // line.ProcessKey (new KeyEvent (Key.CursorLeft, new KeyModifiers ())); // line.ProcessKey (new KeyEvent (Key.CursorLeft, new KeyModifiers ())); @@ -123,7 +120,6 @@ //└─────┴───┘"; // TestHelpers.AssertDriverContentsAre (looksLike, output); - // // and 2 to the left // line.ProcessKey (new KeyEvent (Key.CursorLeft, new KeyModifiers ())); // line.ProcessKey (new KeyEvent (Key.CursorLeft, new KeyModifiers ())); @@ -137,7 +133,6 @@ // TestHelpers.AssertDriverContentsAre (looksLike, output); // } - // [Fact, AutoInitShutdown] // public void TestTileView_Vertical_Focused_50PercentSplit () // { @@ -169,7 +164,6 @@ // // Even when moving the splitter location it should stay a Percentage based one // Assert.IsType (tileView.SplitterDistances.ElementAt (0)); - // // and 2 to the left // line.ProcessKey (new KeyEvent (Key.CursorLeft, new KeyModifiers ())); // line.ProcessKey (new KeyEvent (Key.CursorLeft, new KeyModifiers ())); @@ -206,7 +200,6 @@ // TestHelpers.AssertDriverContentsAre (looksLike, output); // } - // [Fact, AutoInitShutdown] // public void TestTileView_Vertical_View1MinSize_Absolute () // { @@ -251,7 +244,6 @@ // TestHelpers.AssertDriverContentsAre (looksLike, output); // } - // [Fact, AutoInitShutdown] // public void TestTileView_Vertical_View1MinSize_Absolute_WithBorder () // { @@ -478,7 +470,6 @@ // TestHelpers.AssertDriverContentsAre (looksLike, output); // } - // [Fact, AutoInitShutdown] // public void TestTileView_Horizontal_View1MinSize_Absolute () // { @@ -534,7 +525,6 @@ // var ex = Assert.Throws (() => tileView.SetSplitterPos (0, Pos.Right (tileView))); // Assert.Equal ("Only Percent and Absolute values are supported. Passed value was PosCombine", ex.Message); - // ex = Assert.Throws (() => tileView.SetSplitterPos (0, Pos.Function (() => 1))); // Assert.Equal ("Only Percent and Absolute values are supported. Passed value was PosFunc", ex.Message); @@ -556,7 +546,6 @@ // var left = (TileView)tileView.Tiles.ElementAt (0).ContentView; // Assert.Same (left.SuperView, tileView); - // Assert.Equal (2, left.Tiles.ElementAt (0).ContentView.Subviews.Count); // Assert.IsType