From df37fa68412d11900e1bea714348bdfb1f9e18c2 Mon Sep 17 00:00:00 2001 From: Tigger Kindel Date: Sun, 3 Dec 2023 08:38:50 -0700 Subject: [PATCH] No longer publish api docs from v2_develop; v2 is published via the Terminal.GuiV2Docs repo --- .github/workflows/api-docs.yml | 3 +- Terminal.Gui/Application.cs | 2675 ++++++++++++++++---------------- docfx/docs/newinv2.md | 14 +- 3 files changed, 1352 insertions(+), 1340 deletions(-) diff --git a/.github/workflows/api-docs.yml b/.github/workflows/api-docs.yml index 0aa1e3116..4e91d239f 100644 --- a/.github/workflows/api-docs.yml +++ b/.github/workflows/api-docs.yml @@ -2,7 +2,8 @@ name: Build and publish API docs on: push: - branches: [main, develop, v2_develop] + # only publish v2 (main or develop); v2 is published via the Terminal.GuiV2Docs repo + branches: [main, develop] permissions: id-token: write diff --git a/Terminal.Gui/Application.cs b/Terminal.Gui/Application.cs index 34df8b3ef..368552192 100644 --- a/Terminal.Gui/Application.cs +++ b/Terminal.Gui/Application.cs @@ -7,477 +7,477 @@ using System.Reflection; using System.IO; using System.Text.Json.Serialization; -namespace Terminal.Gui { +namespace Terminal.Gui; +/// +/// A static, singleton class representing the application. This class is the entry point for the application. +/// +/// +/// +/// // A simple Terminal.Gui app that creates a window with a frame and title with +/// // 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) +/// }; +/// Application.Top.Add(win); +/// Application.Run(); +/// Application.Shutdown(); +/// +/// +/// +/// +/// 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 { + /// - /// A static, singleton class representing the application. This class is the entry point for the application. + /// Gets the that has been selected. See also . + /// + public static ConsoleDriver Driver { get; internal set; } + + /// + /// 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; + + /// + /// Gets or sets whether will be forced to output only the 16 colors defined in . + /// The default is , meaning 24-bit (TrueColor) colors will be output as long as the selected + /// supports TrueColor. + /// + [SerializableConfigurationProperty (Scope = typeof (SettingsScope))] + public static bool Force16Colors { get; set; } = false; + + // For Unit testing - ignores UseSystemConsole + internal static bool _forceFakeConsole; + + private static List _cachedSupportedCultures; + + /// + /// Gets all cultures supported by the application without the invariant language. + /// + public static List SupportedCultures => _cachedSupportedCultures; + + private static List GetSupportedCultures () + { + CultureInfo [] culture = CultureInfo.GetCultures (CultureTypes.AllCultures); + + // Get the assembly + Assembly assembly = Assembly.GetExecutingAssembly (); + + //Find the location of the assembly + string assemblyLocation = AppDomain.CurrentDomain.BaseDirectory; + + // Find the resource file name of the assembly + string resourceFilename = $"{Path.GetFileNameWithoutExtension (assembly.Location)}.resources.dll"; + + // Return all culture for which satellite folder found with culture code. + return culture.Where (cultureInfo => + Directory.Exists (Path.Combine (assemblyLocation, cultureInfo.Name)) && + File.Exists (Path.Combine (assemblyLocation, cultureInfo.Name, resourceFilename)) + ).ToList (); + } + + #region Initialization (Init/Shutdown) + + /// + /// Initializes a new instance of Application. + /// + /// + /// Call this method once per instance (or after has been called). + /// + /// + /// This function loads the right for the platform, + /// Creates a . and assigns it to + /// + /// + /// must be called when the application is closing (typically after has + /// returned) to ensure resources are cleaned up and terminal settings restored. + /// + /// + /// The function + /// combines and + /// into a single call. An application cam use + /// without explicitly calling . + /// + /// + /// The to use. If not specified the default driver for the + /// platform will be used (see , , and ). + public static void Init (ConsoleDriver driver = null) => InternalInit (() => Toplevel.Create (), driver); + + internal static bool _initialized = false; + internal static int _mainThreadId = -1; + + // INTERNAL function for initializing an app with a Toplevel factory object, driver, and mainloop. + // + // Called from: + // + // Init() - When the user wants to use the default Toplevel. calledViaRunT will be false, causing all state to be reset. + // Run() - When the user wants to use a custom Toplevel. calledViaRunT will be true, enabling Run() to be called without calling Init first. + // Unit Tests - To initialize the app with a custom Toplevel, using the FakeDriver. calledViaRunT will be false, causing all state to be reset. + // + // calledViaRunT: If false (default) all state will be reset. If true the state will not be reset. + internal static void InternalInit (Func topLevelFactory, ConsoleDriver driver = null, bool calledViaRunT = false) + { + if (_initialized && driver == null) { + return; + } + + if (_initialized) { + throw new InvalidOperationException ("Init has already been called and must be bracketed by Shutdown."); + } + + if (!calledViaRunT) { + // Reset all class variables (Application is a singleton). + ResetState (); + } + + // For UnitTests + if (driver != null) { + Driver = driver; + } + + // Start the process of configuration management. + // Note that we end up calling LoadConfigurationFromAllSources + // multiple times. We need to do this because some settings are only + // valid after a Driver is loaded. In this cases we need just + // `Settings` so we can determine which driver to use. + ConfigurationManager.Load (true); + ConfigurationManager.Apply (); + + Driver ??= Environment.OSVersion.Platform switch { + _ when _forceFakeConsole => new FakeDriver (), // for unit testing only + _ when UseSystemConsole => new NetDriver (), + PlatformID.Win32NT or PlatformID.Win32S or PlatformID.Win32Windows => new WindowsDriver (), + _ => new CursesDriver (), + }; + + if (Driver == null) throw new InvalidOperationException ("Init could not determine the ConsoleDriver to use."); + + try { + MainLoop = Driver.Init (); + } 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 + // if running in unit tests. + // In this case, we want to throw a more specific exception. + throw new InvalidOperationException ("Unable to initialize the console. This can happen if the console is already in use by another process or in unit tests.", ex); + } + + Driver.SizeChanged += (s, args) => OnSizeChanging (args); + Driver.KeyPressed += (s, args) => OnKeyPressed (args); + Driver.KeyDown += (s, args) => OnKeyDown (args); + Driver.KeyUp += (s, args) => OnKeyUp (args); + Driver.MouseEvent += (s, args) => OnMouseEvent (args); + + SynchronizationContext.SetSynchronizationContext (new MainLoopSyncContext ()); + + Top = topLevelFactory (); + Current = Top; + _cachedSupportedCultures = GetSupportedCultures (); + _mainThreadId = Thread.CurrentThread.ManagedThreadId; + _initialized = true; + } + + + /// + /// Shutdown an application initialized with . + /// + /// + /// 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 (); + } + + // 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?.Dispose (); + MainLoop = null; + Driver?.End (); + Driver = null; + Iteration = null; + MouseEvent = null; + KeyDown = null; + KeyUp = null; + KeyPressed = null; + SizeChanging = null; + _mainThreadId = -1; + NotifyNewRunState = null; + NotifyStopRunState = null; + _initialized = false; + MouseGrabView = null; + _mouseEnteredView = 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, Stop) + + /// + /// 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; + + /// + /// 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, 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) + { + 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."); + } + + // Ensure the mouse is ungrabed. + MouseGrabView = null; + + var rs = new RunState (Toplevel); + + // View implements ISupportInitializeNotification which is derived from ISupportInitialize + if (!Toplevel.IsInitialized) { + Toplevel.BeginInit (); + Toplevel.EndInit (); + } + + 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)) { + var count = 1; + var id = (_topLevels.Count + count).ToString (); + while (_topLevels.Count > 0 && _topLevels.FirstOrDefault (x => x.Id == id) != null) { + count++; + id = (_topLevels.Count + count).ToString (); + } + Toplevel.Id = (_topLevels.Count + count).ToString (); + + _topLevels.Push (Toplevel); + } else { + var dup = _topLevels.FirstOrDefault (x => x.Id == Toplevel.Id); + if (dup == null) { + _topLevels.Push (Toplevel); + } + } + + if (_topLevels.FindDuplicates (new ToplevelEqualityComparer ()).Count > 0) { + throw new ArgumentException ("There are duplicates Toplevels Id's"); + } + } + if (Top == null || Toplevel.IsOverlappedContainer) { + Top = Toplevel; + } + + 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); + } + + 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.Draw (); + Toplevel.PositionCursor (); + Driver.Refresh (); + } + + NotifyNewRunState?.Invoke (Toplevel, new RunStateEventArgs (rs)); + return rs; + } + + /// + /// Runs the application by calling with the value of . + /// + /// + /// See for more details. + /// + public static void Run (Func errorHandler = null) => Run (Top, errorHandler); + + /// + /// 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. + /// + /// + /// + /// 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. + /// + public static void Run (Func errorHandler = null, ConsoleDriver driver = null) where T : Toplevel, new() + { + 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 code path 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, calledViaRunT: true); + Run (Top, errorHandler); + } + } + + /// + /// Runs the main loop on the given container. /// - /// - /// - /// // A simple Terminal.Gui app that creates a window with a frame and title with - /// // 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) - /// }; - /// Application.Top.Add(win); - /// Application.Run(); - /// Application.Shutdown(); - /// - /// /// /// - /// Creates a instance of to process input events, handle timers and - /// other sources of data. It is accessible via the property. + /// 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. /// /// - /// The event is invoked on each iteration of the . + /// To make a stop execution, call . /// /// - /// When invoked it sets the to one that is tied - /// to the , allowing user code to use async/await. + /// 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 exceptions will be rethrown. + /// Otherwise, if will be called. If + /// returns the will resume; otherwise + /// this method will exit. /// /// - public static partial class Application { - - /// - /// Gets the that has been selected. See also . - /// - public static ConsoleDriver Driver { get; internal set; } - - /// - /// 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; - - /// - /// Gets or sets whether will be forced to output only the 16 colors defined in . - /// The default is , meaning 24-bit (TrueColor) colors will be output as long as the selected - /// supports TrueColor. - /// - [SerializableConfigurationProperty (Scope = typeof (SettingsScope))] - public static bool Force16Colors { get; set; } = false; - - // For Unit testing - ignores UseSystemConsole - internal static bool _forceFakeConsole; - - private static List _cachedSupportedCultures; - - /// - /// Gets all cultures supported by the application without the invariant language. - /// - public static List SupportedCultures => _cachedSupportedCultures; - - private static List GetSupportedCultures () - { - CultureInfo [] culture = CultureInfo.GetCultures (CultureTypes.AllCultures); - - // Get the assembly - Assembly assembly = Assembly.GetExecutingAssembly (); - - //Find the location of the assembly - string assemblyLocation = AppDomain.CurrentDomain.BaseDirectory; - - // Find the resource file name of the assembly - string resourceFilename = $"{Path.GetFileNameWithoutExtension (assembly.Location)}.resources.dll"; - - // Return all culture for which satellite folder found with culture code. - return culture.Where (cultureInfo => - Directory.Exists (Path.Combine (assemblyLocation, cultureInfo.Name)) && - File.Exists (Path.Combine (assemblyLocation, cultureInfo.Name, resourceFilename)) - ).ToList (); - } - - #region Initialization (Init/Shutdown) - - /// - /// Initializes a new instance of Application. - /// - /// - /// Call this method once per instance (or after has been called). - /// - /// - /// This function loads the right for the platform, - /// Creates a . and assigns it to - /// - /// - /// must be called when the application is closing (typically after has - /// returned) to ensure resources are cleaned up and terminal settings restored. - /// - /// - /// The function - /// combines and - /// into a single call. An application cam use - /// without explicitly calling . - /// - /// - /// The to use. If not specified the default driver for the - /// platform will be used (see , , and ). - public static void Init (ConsoleDriver driver = null) => InternalInit (() => Toplevel.Create (), driver); - - internal static bool _initialized = false; - internal static int _mainThreadId = -1; - - // INTERNAL function for initializing an app with a Toplevel factory object, driver, and mainloop. - // - // Called from: - // - // Init() - When the user wants to use the default Toplevel. calledViaRunT will be false, causing all state to be reset. - // Run() - When the user wants to use a custom Toplevel. calledViaRunT will be true, enabling Run() to be called without calling Init first. - // Unit Tests - To initialize the app with a custom Toplevel, using the FakeDriver. calledViaRunT will be false, causing all state to be reset. - // - // calledViaRunT: If false (default) all state will be reset. If true the state will not be reset. - internal static void InternalInit (Func topLevelFactory, ConsoleDriver driver = null, bool calledViaRunT = false) - { - if (_initialized && driver == null) { - return; - } - - if (_initialized) { - throw new InvalidOperationException ("Init has already been called and must be bracketed by Shutdown."); - } - - if (!calledViaRunT) { - // Reset all class variables (Application is a singleton). - ResetState (); - } - - // For UnitTests - if (driver != null) { - Driver = driver; - } - - // Start the process of configuration management. - // Note that we end up calling LoadConfigurationFromAllSources - // multiple times. We need to do this because some settings are only - // valid after a Driver is loaded. In this cases we need just - // `Settings` so we can determine which driver to use. - ConfigurationManager.Load (true); - ConfigurationManager.Apply (); - - Driver ??= Environment.OSVersion.Platform switch { - _ when _forceFakeConsole => new FakeDriver (), // for unit testing only - _ when UseSystemConsole => new NetDriver (), - PlatformID.Win32NT or PlatformID.Win32S or PlatformID.Win32Windows => new WindowsDriver (), - _ => new CursesDriver (), - }; - - if (Driver == null) throw new InvalidOperationException ("Init could not determine the ConsoleDriver to use."); - - try { - MainLoop = Driver.Init (); - } 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 - // if running in unit tests. - // In this case, we want to throw a more specific exception. - throw new InvalidOperationException ("Unable to initialize the console. This can happen if the console is already in use by another process or in unit tests.", ex); - } - - Driver.SizeChanged += (s, args) => OnSizeChanging (args); - Driver.KeyPressed += (s, args) => OnKeyPressed (args); - Driver.KeyDown += (s, args) => OnKeyDown (args); - Driver.KeyUp += (s, args) => OnKeyUp (args); - Driver.MouseEvent += (s, args) => OnMouseEvent (args); - - SynchronizationContext.SetSynchronizationContext (new MainLoopSyncContext ()); - - Top = topLevelFactory (); - Current = Top; - _cachedSupportedCultures = GetSupportedCultures (); - _mainThreadId = Thread.CurrentThread.ManagedThreadId; - _initialized = true; - } - - - /// - /// Shutdown an application initialized with . - /// - /// - /// 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 (); - } - - // 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?.Dispose (); - MainLoop = null; - Driver?.End (); - Driver = null; - Iteration = null; - MouseEvent = null; - KeyDown = null; - KeyUp = null; - KeyPressed = null; - SizeChanging = null; - _mainThreadId = -1; - NotifyNewRunState = null; - NotifyStopRunState = null; - _initialized = false; - MouseGrabView = null; - _mouseEnteredView = 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, Stop) - - /// - /// 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; - - /// - /// 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, 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) - { - 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."); - } - - // Ensure the mouse is ungrabed. - MouseGrabView = null; - - var rs = new RunState (Toplevel); - - // View implements ISupportInitializeNotification which is derived from ISupportInitialize - if (!Toplevel.IsInitialized) { - Toplevel.BeginInit (); - Toplevel.EndInit (); - } - - 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)) { - var count = 1; - var id = (_topLevels.Count + count).ToString (); - while (_topLevels.Count > 0 && _topLevels.FirstOrDefault (x => x.Id == id) != null) { - count++; - id = (_topLevels.Count + count).ToString (); - } - Toplevel.Id = (_topLevels.Count + count).ToString (); - - _topLevels.Push (Toplevel); - } else { - var dup = _topLevels.FirstOrDefault (x => x.Id == Toplevel.Id); - if (dup == null) { - _topLevels.Push (Toplevel); - } - } - - if (_topLevels.FindDuplicates (new ToplevelEqualityComparer ()).Count > 0) { - throw new ArgumentException ("There are duplicates Toplevels Id's"); - } - } - if (Top == null || Toplevel.IsOverlappedContainer) { - Top = Toplevel; - } - - 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); - } - - 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.Draw (); - Toplevel.PositionCursor (); - Driver.Refresh (); - } - - NotifyNewRunState?.Invoke (Toplevel, new RunStateEventArgs (rs)); - return rs; - } - - /// - /// Runs the application by calling with the value of . - /// - /// - /// See for more details. - /// - public static void Run (Func errorHandler = null) => Run (Top, errorHandler); - - /// - /// 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. - /// - /// - /// - /// 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. - /// - public static void Run (Func errorHandler = null, ConsoleDriver driver = null) where T : Toplevel, new() - { - 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 code path 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, calledViaRunT: true); - Run (Top, errorHandler); - } - } - - /// - /// Runs the main loop on the given container. - /// - /// - /// - /// 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 exceptions will be rethrown. - /// Otherwise, if will be called. If - /// returns the will resume; otherwise - /// this method will exit. - /// - /// - /// The to run as a modal. - /// RELEASE builds only: Handler for any unhandled exceptions (resumes when returns true, rethrows when null). - public static void Run (Toplevel view, Func errorHandler = null) - { - var resume = true; - while (resume) { + /// The to run as a modal. + /// RELEASE builds only: Handler for any unhandled exceptions (resumes when returns true, rethrows when null). + public static void Run (Toplevel view, Func errorHandler = null) + { + var resume = true; + while (resume) { #if !DEBUG try { #endif - resume = false; - var runState = Begin (view); - // If EndAfterFirstIteration is true then the user must dispose of the runToken - // by using NotifyStopRunState event. - RunLoop (runState); - if (!EndAfterFirstIteration) { - End (runState); - } + resume = false; + var runState = Begin (view); + // If EndAfterFirstIteration is true then the user must dispose of the runToken + // by using NotifyStopRunState event. + RunLoop (runState); + if (!EndAfterFirstIteration) { + End (runState); + } #if !DEBUG } catch (Exception error) @@ -489,781 +489,740 @@ namespace Terminal.Gui { resume = errorHandler(error); } #endif + } + } + + /// + /// Adds a timeout to the application. + /// + /// + /// When time specified passes, the callback will be invoked. + /// If the callback returns true, the timeout will be reset, repeating + /// the invocation. If it returns false, the timeout will stop and be removed. + /// + /// The returned value is a token that can be used to stop the timeout + /// by calling . + /// + public static object AddTimeout (TimeSpan time, Func callback) => MainLoop?.AddTimeout (time, callback); + + /// + /// Removes a previously scheduled timeout + /// + /// + /// The token parameter is the value returned by . + /// + /// Returns trueif the timeout is successfully removed; otherwise, false. + /// This method also returns false if the timeout is not found. + public static bool RemoveTimeout (object token) => MainLoop?.RemoveTimeout (token) ?? false; + + + /// + /// Runs on the thread that is processing events + /// + /// the action to be invoked on the main processing thread. + public static void Invoke (Action action) + { + MainLoop?.AddIdle (() => { + action (); + return false; + }); + } + + // TODO: Determine if this is really needed. The only code that calls WakeUp I can find + // is ProgressBarStyles and it's not clear it needs to. + /// + /// Wakes up the running application that might be waiting on input. + /// + public static void Wakeup () => MainLoop?.Wakeup (); + + /// + /// Triggers a refresh of the entire display. + /// + public static void Refresh () + { + // TODO: Figure out how to remove this call to ClearContents. Refresh should just repaint damaged areas, not clear + Driver.ClearContents (); + View last = null; + foreach (var v in _topLevels.Reverse ()) { + if (v.Visible) { + v.SetNeedsDisplay (); + v.SetSubViewNeedsDisplay (); + v.Draw (); } + last = v; + } + last?.PositionCursor (); + Driver.Refresh (); + } + + /// + /// This event is raised on each iteration of the . + /// + /// + /// See also + /// + public static event EventHandler Iteration; + + /// + /// The driver for the application + /// + /// The main loop. + internal static MainLoop MainLoop { get; private set; } + + /// + /// Set to true to cause to be called after the first iteration. + /// Set to false (the default) to cause the application to continue running until Application.RequestStop () is called. + /// + public static bool EndAfterFirstIteration { get; set; } = false; + + // + // provides the sync context set while executing code in Terminal.Gui, to let + // users use async/await on their code + // + class MainLoopSyncContext : SynchronizationContext { + public override SynchronizationContext CreateCopy () + { + return new MainLoopSyncContext (); } - /// - /// Adds a timeout to the application. - /// - /// - /// When time specified passes, the callback will be invoked. - /// If the callback returns true, the timeout will be reset, repeating - /// the invocation. If it returns false, the timeout will stop and be removed. - /// - /// The returned value is a token that can be used to stop the timeout - /// by calling . - /// - public static object AddTimeout (TimeSpan time, Func callback) => MainLoop?.AddTimeout (time, callback); - - /// - /// Removes a previously scheduled timeout - /// - /// - /// The token parameter is the value returned by . - /// - /// Returns trueif the timeout is successfully removed; otherwise, false. - /// This method also returns false if the timeout is not found. - public static bool RemoveTimeout (object token) => MainLoop?.RemoveTimeout (token) ?? false; - - - /// - /// Runs on the thread that is processing events - /// - /// the action to be invoked on the main processing thread. - public static void Invoke (Action action) + public override void Post (SendOrPostCallback d, object state) { - MainLoop?.AddIdle (() => { - action (); + MainLoop.AddIdle (() => { + d (state); return false; }); + //_mainLoop.Driver.Wakeup (); } - // TODO: Determine if this is really needed. The only code that calls WakeUp I can find - // is ProgressBarStyles and it's not clear it needs to. - /// - /// Wakes up the running application that might be waiting on input. - /// - public static void Wakeup () => MainLoop?.Wakeup (); - - /// - /// Triggers a refresh of the entire display. - /// - public static void Refresh () + public override void Send (SendOrPostCallback d, object state) { - // TODO: Figure out how to remove this call to ClearContents. Refresh should just repaint damaged areas, not clear - Driver.ClearContents (); - View last = null; - foreach (var v in _topLevels.Reverse ()) { - if (v.Visible) { - v.SetNeedsDisplay (); - v.SetSubViewNeedsDisplay (); - v.Draw (); - } - last = v; - } - last?.PositionCursor (); - Driver.Refresh (); - } - - /// - /// This event is raised on each iteration of the . - /// - /// - /// See also - /// - public static event EventHandler Iteration; - - /// - /// The driver for the application - /// - /// The main loop. - internal static MainLoop MainLoop { get; private set; } - - /// - /// Set to true to cause to be called after the first iteration. - /// Set to false (the default) to cause the application to continue running until Application.RequestStop () is called. - /// - public static bool EndAfterFirstIteration { get; set; } = false; - - // - // provides the sync context set while executing code in Terminal.Gui, to let - // users use async/await on their code - // - class MainLoopSyncContext : SynchronizationContext { - public override SynchronizationContext CreateCopy () - { - return new MainLoopSyncContext (); - } - - public override void Post (SendOrPostCallback d, object state) - { - MainLoop.AddIdle (() => { + if (Thread.CurrentThread.ManagedThreadId == _mainThreadId) { + d (state); + } else { + var wasExecuted = false; + Invoke (() => { d (state); - return false; + wasExecuted = true; }); - //_mainLoop.Driver.Wakeup (); - } - - public override void Send (SendOrPostCallback d, object state) - { - if (Thread.CurrentThread.ManagedThreadId == _mainThreadId) { - d (state); - } else { - var wasExecuted = false; - Invoke (() => { - d (state); - wasExecuted = true; - }); - while (!wasExecuted) { - Thread.Sleep (15); - } + while (!wasExecuted) { + Thread.Sleep (15); } } } + } - /// - /// Building block API: Runs the for the created . - /// - /// The state returned by the method. - public static void RunLoop (RunState state) - { - if (state == null) { - throw new ArgumentNullException (nameof (state)); - } - if (state.Toplevel == null) { - throw new ObjectDisposedException ("state"); - } - - var firstIteration = true; - for (state.Toplevel.Running = true; state.Toplevel.Running;) { - if (EndAfterFirstIteration && !firstIteration) { - return; - } - RunIteration (ref state, ref firstIteration); - } + /// + /// Building block API: Runs the for the created . + /// + /// The state returned by the method. + public static void RunLoop (RunState state) + { + if (state == null) { + throw new ArgumentNullException (nameof (state)); + } + if (state.Toplevel == null) { + throw new ObjectDisposedException ("state"); } - /// - /// Run one application iteration. - /// - /// The state returned by . - /// 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 RunIteration (ref RunState state, ref bool firstIteration) - { - if (MainLoop.EventsPending ()) { - // Notify Toplevel it's ready - if (firstIteration) { - state.Toplevel.OnReady (); - } - - MainLoop.RunIteration (); - Iteration?.Invoke (null, new IterationEventArgs ()); - - EnsureModalOrVisibleAlwaysOnTop (state.Toplevel); - if (state.Toplevel != Current) { - OverlappedTop?.OnDeactivate (state.Toplevel); - state.Toplevel = Current; - OverlappedTop?.OnActivate (state.Toplevel); - Top.SetSubViewNeedsDisplay (); - Refresh (); - } - } - - firstIteration = false; - - if (state.Toplevel != Top && - (Top.NeedsDisplay || Top.SubViewNeedsDisplay || Top.LayoutNeeded)) { - state.Toplevel.SetNeedsDisplay (state.Toplevel.Frame); - Top.Draw (); - foreach (var top in _topLevels.Reverse ()) { - if (top != Top && top != state.Toplevel) { - top.SetNeedsDisplay (); - top.SetSubViewNeedsDisplay (); - top.Draw (); - } - } - } - if (_topLevels.Count == 1 && state.Toplevel == Top - && (Driver.Cols != state.Toplevel.Frame.Width || Driver.Rows != state.Toplevel.Frame.Height) - && (state.Toplevel.NeedsDisplay || state.Toplevel.SubViewNeedsDisplay || state.Toplevel.LayoutNeeded)) { - - state.Toplevel.Clear (new Rect (Point.Empty, new Size (Driver.Cols, Driver.Rows))); - } - - if (state.Toplevel.NeedsDisplay || - state.Toplevel.SubViewNeedsDisplay || - state.Toplevel.LayoutNeeded || - OverlappedChildNeedsDisplay ()) { - state.Toplevel.Draw (); - state.Toplevel.PositionCursor (); - Driver.Refresh (); - } else { - Driver.UpdateCursor (); - } - if (state.Toplevel != Top && - !state.Toplevel.Modal && - (Top.NeedsDisplay || Top.SubViewNeedsDisplay || Top.LayoutNeeded)) { - Top.Draw (); + var firstIteration = true; + for (state.Toplevel.Running = true; state.Toplevel.Running;) { + if (EndAfterFirstIteration && !firstIteration) { + return; } + RunIteration (ref state, ref firstIteration); } + } - /// - /// Stops running the most recent or the if provided. - /// - /// The to 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 (OverlappedTop == null || top == null || (OverlappedTop == null && top != null)) { - top = Current; + /// + /// Run one application iteration. + /// + /// The state returned by . + /// 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 RunIteration (ref RunState state, ref bool firstIteration) + { + if (MainLoop.EventsPending ()) { + // Notify Toplevel it's ready + if (firstIteration) { + state.Toplevel.OnReady (); } - if (OverlappedTop != null && top.IsOverlappedContainer && top?.Running == true - && (Current?.Modal == false || (Current?.Modal == true && Current?.Running == false))) { + MainLoop.RunIteration (); + Iteration?.Invoke (null, new IterationEventArgs ()); - OverlappedTop.RequestStop (); - } else if (OverlappedTop != null && top != Current && Current?.Running == true && Current?.Modal == true - && top.Modal && top.Running) { - - var ev = new ToplevelClosingEventArgs (Current); - Current.OnClosing (ev); - if (ev.Cancel) { - return; - } - 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)) { - - 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 { - currentTop = top; - } - if (!currentTop.Running) { - return; - } - var ev = new ToplevelClosingEventArgs (currentTop); - currentTop.OnClosing (ev); - if (ev.Cancel) { - return; - } - currentTop.Running = false; - OnNotifyStopRunState (currentTop); - } - } - - static void OnNotifyStopRunState (Toplevel top) - { - if (EndAfterFirstIteration) { - NotifyStopRunState?.Invoke (top, new ToplevelEventArgs (top)); - } - } - - /// - /// Building block API: completes the execution of a that was started with . - /// - /// The returned by the method. - public static void End (RunState runState) - { - if (runState == null) { - throw new ArgumentNullException (nameof (runState)); - } - - if (OverlappedTop != null) { - OverlappedTop.OnChildUnloaded (runState.Toplevel); - } else { - runState.Toplevel.OnUnloaded (); - } - - // End the RunState.Toplevel - // 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 - // this call to End is not balanced with the call to Begin that started the RunState - throw new ArgumentException ("End must be balanced with calls to Begin"); - } - _topLevels.Pop (); - } - - // Notify that it is closing - runState.Toplevel?.OnClosed (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 - if (_topLevels.Count == 0) { - Current = null; - } else { - Current = _topLevels.Peek (); - if (_topLevels.Count == 1 && Current == OverlappedTop) { - OverlappedTop.OnAllChildClosed (); - } else { - SetCurrentOverlappedAsTop (); - runState.Toplevel.OnLeave (Current); - Current.OnEnter (runState.Toplevel); - } + EnsureModalOrVisibleAlwaysOnTop (state.Toplevel); + if (state.Toplevel != Current) { + OverlappedTop?.OnDeactivate (state.Toplevel); + state.Toplevel = Current; + OverlappedTop?.OnActivate (state.Toplevel); + Top.SetSubViewNeedsDisplay (); Refresh (); } - - runState.Toplevel?.Dispose (); - runState.Toplevel = null; - runState.Dispose (); } - #endregion Run (Begin, Run, End) + firstIteration = false; - #region Toplevel handling - static readonly Stack _topLevels = new Stack (); + if (state.Toplevel != Top && + (Top.NeedsDisplay || Top.SubViewNeedsDisplay || Top.LayoutNeeded)) { + state.Toplevel.SetNeedsDisplay (state.Toplevel.Frame); + Top.Draw (); + foreach (var top in _topLevels.Reverse ()) { + if (top != Top && top != state.Toplevel) { + top.SetNeedsDisplay (); + top.SetSubViewNeedsDisplay (); + top.Draw (); + } + } + } + if (_topLevels.Count == 1 && state.Toplevel == Top + && (Driver.Cols != state.Toplevel.Frame.Width || Driver.Rows != state.Toplevel.Frame.Height) + && (state.Toplevel.NeedsDisplay || state.Toplevel.SubViewNeedsDisplay || state.Toplevel.LayoutNeeded)) { - /// - /// The object used for the application on startup () - /// - /// The top. - public static Toplevel Top { get; private set; } + state.Toplevel.Clear (new Rect (Point.Empty, new Size (Driver.Cols, Driver.Rows))); + } - /// - /// The current object. This is updated when - /// enters and leaves to point to the current . - /// - /// The current. - public static Toplevel Current { get; private set; } + if (state.Toplevel.NeedsDisplay || + state.Toplevel.SubViewNeedsDisplay || + state.Toplevel.LayoutNeeded || + OverlappedChildNeedsDisplay ()) { + state.Toplevel.Draw (); + state.Toplevel.PositionCursor (); + Driver.Refresh (); + } else { + Driver.UpdateCursor (); + } + if (state.Toplevel != Top && + !state.Toplevel.Modal && + (Top.NeedsDisplay || Top.SubViewNeedsDisplay || Top.LayoutNeeded)) { + Top.Draw (); + } + } - static void EnsureModalOrVisibleAlwaysOnTop (Toplevel Toplevel) - { - if (!Toplevel.Running || (Toplevel == Current && Toplevel.Visible) || OverlappedTop == null || _topLevels.Peek ().Modal) { + /// + /// Stops running the most recent or the if provided. + /// + /// The to 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 (OverlappedTop == null || top == null || (OverlappedTop == null && top != null)) { + top = Current; + } + + if (OverlappedTop != null && top.IsOverlappedContainer && top?.Running == true + && (Current?.Modal == false || (Current?.Modal == true && Current?.Running == false))) { + + OverlappedTop.RequestStop (); + } else if (OverlappedTop != null && top != Current && Current?.Running == true && Current?.Modal == true + && top.Modal && top.Running) { + + var ev = new ToplevelClosingEventArgs (Current); + Current.OnClosing (ev); + if (ev.Cancel) { return; } + 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)) { - foreach (var top in _topLevels.Reverse ()) { - if (top.Modal && top != Current) { - MoveCurrent (top); - return; - } + 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 { + currentTop = top; } - if (!Toplevel.Visible && Toplevel == Current) { - OverlappedMoveNext (); + if (!currentTop.Running) { + return; } + var ev = new ToplevelClosingEventArgs (currentTop); + currentTop.OnClosing (ev); + if (ev.Cancel) { + return; + } + currentTop.Running = false; + OnNotifyStopRunState (currentTop); + } + } + + static void OnNotifyStopRunState (Toplevel top) + { + if (EndAfterFirstIteration) { + NotifyStopRunState?.Invoke (top, new ToplevelEventArgs (top)); + } + } + + /// + /// Building block API: completes the execution of a that was started with . + /// + /// The returned by the method. + public static void End (RunState runState) + { + if (runState == null) { + throw new ArgumentNullException (nameof (runState)); } - 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; - } - - 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; + if (OverlappedTop != null) { + OverlappedTop.OnChildUnloaded (runState.Toplevel); + } else { + runState.Toplevel.OnUnloaded (); } - 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; + // End the RunState.Toplevel + // 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 + // this call to End is not balanced with the call to Begin that started the RunState + throw new ArgumentException ("End must be balanced with calls to Begin"); } - return top; + _topLevels.Pop (); } - // 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; + // Notify that it is closing + runState.Toplevel?.OnClosed (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); } - /// - /// Invoked when the terminal's size changed. The new size of the terminal is provided. - /// - /// - /// Event handlers can set to - /// to prevent from changing it's size to match the new terminal size. - /// - public static event EventHandler SizeChanging; - - /// - /// Called when the application's size changes. Sets the size of all s and - /// fires the event. - /// - /// The new size. - /// if the size was changed. - public static bool OnSizeChanging (SizeChangedEventArgs args) - { - SizeChanging?.Invoke (null, args); - if (args.Cancel) { - return false; - } - - foreach (var t in _topLevels) { - t.SetRelativeLayout (new Rect (0, 0, args.Size.Width, args.Size.Height)); - t.LayoutSubviews (); - t.PositionToplevels (); - t.OnSizeChanging (new SizeChangedEventArgs (args.Size)); + // Set Current and Top to the next TopLevel on the stack + if (_topLevels.Count == 0) { + Current = null; + } else { + Current = _topLevels.Peek (); + if (_topLevels.Count == 1 && Current == OverlappedTop) { + OverlappedTop.OnAllChildClosed (); + } else { + SetCurrentOverlappedAsTop (); + runState.Toplevel.OnLeave (Current); + Current.OnEnter (runState.Toplevel); } Refresh (); - return true; } - #endregion Toplevel handling + runState.Toplevel?.Dispose (); + runState.Toplevel = null; + runState.Dispose (); + } - #region Mouse handling - /// - /// Disable or enable the mouse. The mouse is enabled by default. - /// - [SerializableConfigurationProperty (Scope = typeof (SettingsScope))] - public static bool IsMouseDisabled { get; set; } + #endregion Run (Begin, Run, End) - /// - /// The current object that wants continuous mouse button pressed events. - /// - public static View WantContinuousButtonPressedView { get; private set; } + #region Toplevel handling + static readonly Stack _topLevels = new Stack (); - /// - /// Gets the view that grabbed the mouse (e.g. for dragging). When this is set, all mouse events will be - /// routed to this view until the view calls or the mouse is released. - /// - public static View MouseGrabView { get; private set; } + /// + /// The object used for the application on startup () + /// + /// The top. + public static Toplevel Top { get; private set; } - /// - /// Invoked when a view wants to grab the mouse; can be canceled. - /// - public static event EventHandler GrabbingMouse; + /// + /// The current object. This is updated when + /// enters and leaves to point to the current . + /// + /// The current. + public static Toplevel Current { get; private set; } - /// - /// Invoked when a view wants un-grab the mouse; can be canceled. - /// - public static event EventHandler UnGrabbingMouse; + static void EnsureModalOrVisibleAlwaysOnTop (Toplevel Toplevel) + { + if (!Toplevel.Running || (Toplevel == Current && Toplevel.Visible) || OverlappedTop == null || _topLevels.Peek ().Modal) { + return; + } - /// - /// Invoked after a view has grabbed the mouse. - /// - public static event EventHandler GrabbedMouse; - - /// - /// Invoked after a view has un-grabbed the mouse. - /// - public static event EventHandler UnGrabbedMouse; - - /// - /// 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) { + foreach (var top in _topLevels.Reverse ()) { + if (top.Modal && top != Current) { + MoveCurrent (top); return; } - if (!OnGrabbingMouse (view)) { - OnGrabbedMouse (view); - MouseGrabView = view; - } + } + if (!Toplevel.Visible && Toplevel == Current) { + OverlappedMoveNext (); + } + } + + 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; } - /// - /// Releases the mouse grab, so mouse events will be routed to the view on which the mouse is. - /// - public static void UngrabMouse () - { - if (MouseGrabView == null) { - return; - } - if (!OnUnGrabbingMouse (MouseGrabView)) { - OnUnGrabbedMouse (MouseGrabView); - MouseGrabView = null; - } - } - - 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) - { - if (view == null) { - return; - } - GrabbedMouse?.Invoke (view, new ViewEventArgs (view)); - } - - static void OnUnGrabbedMouse (View view) - { - if (view == null) { - return; - } - UnGrabbedMouse?.Invoke (view, new ViewEventArgs (view)); - } - - // Used by OnMouseEvent to track the last view that was clicked on. - static View _mouseEnteredView; - - /// - /// Event fired when a mouse move or click occurs. Coordinates are screen relative. - /// - /// - /// - /// Use this event to receive mouse events in screen coordinates. Use to receive - /// mouse events relative to a 's bounds. - /// - /// - /// The will contain the that contains the mouse coordinates. - /// - /// - public static event EventHandler MouseEvent; - - /// - /// Called when a mouse event occurs. Fires the event. - /// - /// - /// This method can be used to simulate a mouse event, e.g. in unit tests. - /// - /// The mouse event with coordinates relative to the screen. - public static void OnMouseEvent (MouseEventEventArgs a) - { - static bool OutsideRect (Point p, Rect r) => p.X < 0 || p.X > r.Right || p.Y < 0 || p.Y > r.Bottom; - - if (IsMouseDisabled) { - return; - } - - var view = View.FindDeepestView (Current, a.MouseEvent.X, a.MouseEvent.Y, out int screenX, out int screenY); - - if (view != null && view.WantContinuousButtonPressed) { - WantContinuousButtonPressedView = view; - } else { - WantContinuousButtonPressedView = null; - } - if (view != null) { - a.MouseEvent.View = view; - } - MouseEvent?.Invoke (null, new MouseEventEventArgs (a.MouseEvent)); - - if (a.MouseEvent.Handled) { - return; - } - - if (MouseGrabView != null) { - // If the mouse is grabbed, send the event to the view that grabbed it. - // The coordinates are relative to the Bounds of the view that grabbed the mouse. - var newxy = MouseGrabView.ScreenToFrame (a.MouseEvent.X, a.MouseEvent.Y); - var nme = new MouseEvent () { - X = newxy.X, - Y = newxy.Y, - Flags = a.MouseEvent.Flags, - OfX = a.MouseEvent.X - newxy.X, - OfY = a.MouseEvent.Y - newxy.Y, - View = view - }; - if (OutsideRect (new Point (nme.X, nme.Y), MouseGrabView.Bounds)) { - // The mouse has moved outside the bounds of the the view that - // grabbed the mouse, so we tell the view that last got - // OnMouseEnter the mouse is leaving - // BUGBUG: That sentence makes no sense. Either I'm missing something - // or this logic is flawed. - _mouseEnteredView?.OnMouseLeave (a.MouseEvent); - } - //System.Diagnostics.Debug.WriteLine ($"{nme.Flags};{nme.X};{nme.Y};{mouseGrabView}"); - if (MouseGrabView?.OnMouseEvent (nme) == true) { - return; - } - } - - if ((view == null || view == OverlappedTop) && - Current is { Modal: false } && OverlappedTop != null && - a.MouseEvent.Flags != MouseFlags.ReportMousePosition && - a.MouseEvent.Flags != 0) { - - var top = FindDeepestTop (Top, a.MouseEvent.X, a.MouseEvent.Y, out _, out _); - view = View.FindDeepestView (top, a.MouseEvent.X, a.MouseEvent.Y, out screenX, out screenY); - - if (view != null && view != OverlappedTop && top != Current) { - MoveCurrent ((Toplevel)top); - } - } - - bool FrameHandledMouseEvent (Frame frame) - { - if (frame?.Thickness.Contains (frame.FrameToScreen (), a.MouseEvent.X, a.MouseEvent.Y) ?? false) { - var boundsPoint = frame.ScreenToBounds (a.MouseEvent.X, a.MouseEvent.Y); - var me = new MouseEvent () { - X = boundsPoint.X, - Y = boundsPoint.Y, - Flags = a.MouseEvent.Flags, - OfX = boundsPoint.X, - OfY = boundsPoint.Y, - View = frame - }; - frame.OnMouseEvent (me); - return true; - } - return false; - } - - if (view != null) { - // Work inside-out (Padding, Border, Margin) - // TODO: Debate whether inside-out or outside-in is the right strategy - if (FrameHandledMouseEvent (view?.Padding)) { - return; - } - if (FrameHandledMouseEvent (view?.Border)) { - if (view is Toplevel) { - // TODO: This is a temporary hack to work around the fact that - // drag handling is handled in Toplevel (See Issue #2537) - - var me = new MouseEvent () { - X = screenX, - Y = screenY, - Flags = a.MouseEvent.Flags, - OfX = screenX, - OfY = screenY, - View = view - }; - - if (_mouseEnteredView == null) { - _mouseEnteredView = view; - view.OnMouseEnter (me); - } else if (_mouseEnteredView != view) { - _mouseEnteredView.OnMouseLeave (me); - view.OnMouseEnter (me); - _mouseEnteredView = view; + 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; } - - if (!view.WantMousePositionReports && a.MouseEvent.Flags == MouseFlags.ReportMousePosition) { - return; - } - - WantContinuousButtonPressedView = view.WantContinuousButtonPressed ? view : null; - - if (view.OnMouseEvent (me)) { - // Should we bubble up the event, if it is not handled? - //return; - } - - BringOverlappedTopToFront (); } - return; } + } + } + resx = x - startFrame.X; + resy = y - startFrame.Y; + return start; + } - if (FrameHandledMouseEvent (view?.Margin)) { - return; + 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; + } + return top; + } + + // 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; + } + + /// + /// Invoked when the terminal's size changed. The new size of the terminal is provided. + /// + /// + /// Event handlers can set to + /// to prevent from changing it's size to match the new terminal size. + /// + public static event EventHandler SizeChanging; + + /// + /// Called when the application's size changes. Sets the size of all s and + /// fires the event. + /// + /// The new size. + /// if the size was changed. + public static bool OnSizeChanging (SizeChangedEventArgs args) + { + SizeChanging?.Invoke (null, args); + if (args.Cancel) { + return false; + } + + foreach (var t in _topLevels) { + t.SetRelativeLayout (new Rect (0, 0, args.Size.Width, args.Size.Height)); + t.LayoutSubviews (); + t.PositionToplevels (); + t.OnSizeChanging (new SizeChangedEventArgs (args.Size)); + } + Refresh (); + return true; + } + + #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; } + + /// + /// The current object that wants continuous mouse button pressed events. + /// + public static View WantContinuousButtonPressedView { get; private set; } + + /// + /// Gets the view that grabbed the mouse (e.g. for dragging). When this is set, all mouse events will be + /// routed to this view until the view calls or the mouse is released. + /// + public static View MouseGrabView { get; private set; } + + /// + /// Invoked when a view wants to grab the mouse; can be canceled. + /// + public static event EventHandler GrabbingMouse; + + /// + /// Invoked when a view wants un-grab the mouse; can be canceled. + /// + public static event EventHandler UnGrabbingMouse; + + /// + /// Invoked after a view has grabbed the mouse. + /// + public static event EventHandler GrabbedMouse; + + /// + /// Invoked after a view has un-grabbed the mouse. + /// + public static event EventHandler UnGrabbedMouse; + + /// + /// 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; + } + } + + /// + /// Releases the mouse grab, so mouse events will be routed to the view on which the mouse is. + /// + public static void UngrabMouse () + { + if (MouseGrabView == null) { + return; + } + if (!OnUnGrabbingMouse (MouseGrabView)) { + OnUnGrabbedMouse (MouseGrabView); + MouseGrabView = null; + } + } + + 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) + { + if (view == null) { + return; + } + GrabbedMouse?.Invoke (view, new ViewEventArgs (view)); + } + + static void OnUnGrabbedMouse (View view) + { + if (view == null) { + return; + } + UnGrabbedMouse?.Invoke (view, new ViewEventArgs (view)); + } + + // Used by OnMouseEvent to track the last view that was clicked on. + static View _mouseEnteredView; + + /// + /// Event fired when a mouse move or click occurs. Coordinates are screen relative. + /// + /// + /// + /// Use this event to receive mouse events in screen coordinates. Use to receive + /// mouse events relative to a 's bounds. + /// + /// + /// The will contain the that contains the mouse coordinates. + /// + /// + public static event EventHandler MouseEvent; + + /// + /// Called when a mouse event occurs. Fires the event. + /// + /// + /// This method can be used to simulate a mouse event, e.g. in unit tests. + /// + /// The mouse event with coordinates relative to the screen. + public static void OnMouseEvent (MouseEventEventArgs a) + { + static bool OutsideRect (Point p, Rect r) => p.X < 0 || p.X > r.Right || p.Y < 0 || p.Y > r.Bottom; + + if (IsMouseDisabled) { + return; + } + + var view = View.FindDeepestView (Current, a.MouseEvent.X, a.MouseEvent.Y, out int screenX, out int screenY); + + if (view != null && view.WantContinuousButtonPressed) { + WantContinuousButtonPressedView = view; + } else { + WantContinuousButtonPressedView = null; + } + if (view != null) { + a.MouseEvent.View = view; + } + MouseEvent?.Invoke (null, new MouseEventEventArgs (a.MouseEvent)); + + if (a.MouseEvent.Handled) { + return; + } + + if (MouseGrabView != null) { + // If the mouse is grabbed, send the event to the view that grabbed it. + // The coordinates are relative to the Bounds of the view that grabbed the mouse. + var newxy = MouseGrabView.ScreenToFrame (a.MouseEvent.X, a.MouseEvent.Y); + var nme = new MouseEvent () { + X = newxy.X, + Y = newxy.Y, + Flags = a.MouseEvent.Flags, + OfX = a.MouseEvent.X - newxy.X, + OfY = a.MouseEvent.Y - newxy.Y, + View = view + }; + if (OutsideRect (new Point (nme.X, nme.Y), MouseGrabView.Bounds)) { + // The mouse has moved outside the bounds of the the view that + // grabbed the mouse, so we tell the view that last got + // OnMouseEnter the mouse is leaving + // BUGBUG: That sentence makes no sense. Either I'm missing something + // or this logic is flawed. + _mouseEnteredView?.OnMouseLeave (a.MouseEvent); + } + //System.Diagnostics.Debug.WriteLine ($"{nme.Flags};{nme.X};{nme.Y};{mouseGrabView}"); + if (MouseGrabView?.OnMouseEvent (nme) == true) { + return; + } + } + + if ((view == null || view == OverlappedTop) && + Current is { Modal: false } && OverlappedTop != null && + a.MouseEvent.Flags != MouseFlags.ReportMousePosition && + a.MouseEvent.Flags != 0) { + + var top = FindDeepestTop (Top, a.MouseEvent.X, a.MouseEvent.Y, out _, out _); + view = View.FindDeepestView (top, a.MouseEvent.X, a.MouseEvent.Y, out screenX, out screenY); + + if (view != null && view != OverlappedTop && top != Current) { + MoveCurrent ((Toplevel)top); + } + } + + bool FrameHandledMouseEvent (Frame frame) + { + if (frame?.Thickness.Contains (frame.FrameToScreen (), a.MouseEvent.X, a.MouseEvent.Y) ?? false) { + var boundsPoint = frame.ScreenToBounds (a.MouseEvent.X, a.MouseEvent.Y); + var me = new MouseEvent () { + X = boundsPoint.X, + Y = boundsPoint.Y, + Flags = a.MouseEvent.Flags, + OfX = boundsPoint.X, + OfY = boundsPoint.Y, + View = frame + }; + frame.OnMouseEvent (me); + return true; + } + return false; + } + + if (view != null) { + // Work inside-out (Padding, Border, Margin) + // TODO: Debate whether inside-out or outside-in is the right strategy + if (FrameHandledMouseEvent (view?.Padding)) { + return; + } + if (FrameHandledMouseEvent (view?.Border)) { + if (view is Toplevel) { + // TODO: This is a temporary hack to work around the fact that + // drag handling is handled in Toplevel (See Issue #2537) - var bounds = view.BoundsToScreen (view.Bounds); - if (bounds.Contains (a.MouseEvent.X, a.MouseEvent.Y)) { - var boundsPoint = view.ScreenToBounds (a.MouseEvent.X, a.MouseEvent.Y); var me = new MouseEvent () { - X = boundsPoint.X, - Y = boundsPoint.Y, + X = screenX, + Y = screenY, Flags = a.MouseEvent.Flags, - OfX = boundsPoint.X, - OfY = boundsPoint.Y, + OfX = screenX, + OfY = screenY, View = view }; @@ -1289,195 +1248,235 @@ namespace Terminal.Gui { BringOverlappedTopToFront (); } + return; } - } - #endregion Mouse handling - #region Keyboard handling + if (FrameHandledMouseEvent (view?.Margin)) { + return; + } - static Key _alternateForwardKey = Key.PageDown | Key.CtrlMask; + var bounds = view.BoundsToScreen (view.Bounds); + if (bounds.Contains (a.MouseEvent.X, a.MouseEvent.Y)) { + var boundsPoint = view.ScreenToBounds (a.MouseEvent.X, a.MouseEvent.Y); + var me = new MouseEvent () { + X = boundsPoint.X, + Y = boundsPoint.Y, + Flags = a.MouseEvent.Flags, + OfX = boundsPoint.X, + OfY = boundsPoint.Y, + View = view + }; - /// - /// 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)); + if (_mouseEnteredView == null) { + _mouseEnteredView = view; + view.OnMouseEnter (me); + } else if (_mouseEnteredView != view) { + _mouseEnteredView.OnMouseLeave (me); + view.OnMouseEnter (me); + _mouseEnteredView = view; } - } - } - 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)); - } - } - } - 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); - } - } - - /// - /// Event fired after a key has been pressed and released. - /// Set to to suppress the event. - /// - /// - /// All drivers support firing the event. Some drivers (Curses) - /// do not support firing the and events. - /// - public static event EventHandler KeyPressed; - - /// - /// Called after a key has been pressed and released. Fires the event. - /// - /// Called for new KeyPressed events before any processing is performed or - /// views evaluate. Use for global key handling and/or debugging. - /// - /// - /// - /// if the key was handled. - public static bool OnKeyPressed (KeyEventEventArgs a) - { - KeyPressed?.Invoke (null, a); - if (a.Handled) { - return true; - } - - var chain = _topLevels.ToList (); - foreach (var topLevel in chain) { - if (topLevel.ProcessHotKey (a.KeyEvent)) { - return true; - } - if (topLevel.Modal) - break; - } - - foreach (var topLevel in chain) { - if (topLevel.ProcessKey (a.KeyEvent)) { - return true; - } - if (topLevel.Modal) - break; - } - - foreach (var topLevel in chain) { - // Process the key normally - if (topLevel.ProcessColdKey (a.KeyEvent)) { - return true; - } - if (topLevel.Modal) - break; - } - return false; - } - - /// - /// Event fired when a key is pressed (and not yet released). - /// - /// - /// All drivers support firing the event. Some drivers (Curses) - /// do not support firing the and events. - /// - public static event EventHandler KeyDown; - - /// - /// Called when a key is pressed (and not yet released). Fires the event. - /// - /// - public static void OnKeyDown (KeyEventEventArgs a) - { - KeyDown?.Invoke (null, a); - var chain = _topLevels.ToList (); - foreach (var topLevel in chain) { - if (topLevel.OnKeyDown (a.KeyEvent)) + if (!view.WantMousePositionReports && a.MouseEvent.Flags == MouseFlags.ReportMousePosition) { return; - if (topLevel.Modal) - break; + } + + WantContinuousButtonPressedView = view.WantContinuousButtonPressed ? view : null; + + if (view.OnMouseEvent (me)) { + // Should we bubble up the event, if it is not handled? + //return; + } + + BringOverlappedTopToFront (); } } + } + #endregion Mouse handling - /// - /// Event fired when a key is released. - /// - /// - /// All drivers support firing the event. Some drivers (Curses) - /// do not support firing the and events. - /// - public static event EventHandler KeyUp; + #region Keyboard handling - /// - /// Called when a key is released. Fires the event. - /// - /// - public static void OnKeyUp (KeyEventEventArgs a) - { - KeyUp?.Invoke (null, a); - var chain = _topLevels.ToList (); - foreach (var topLevel in chain) { - if (topLevel.OnKeyUp (a.KeyEvent)) - return; - if (topLevel.Modal) - break; + 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)); } - } + } - #endregion Keyboard handling + 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)); + } + } + } + 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); + } } /// - /// Event arguments for the event. + /// Event fired after a key has been pressed and released. + /// Set to to suppress the event. /// - public class IterationEventArgs { + /// + /// All drivers support firing the event. Some drivers (Curses) + /// do not support firing the and events. + /// + public static event EventHandler KeyPressed; + + /// + /// Called after a key has been pressed and released. Fires the event. + /// + /// Called for new KeyPressed events before any processing is performed or + /// views evaluate. Use for global key handling and/or debugging. + /// + /// + /// + /// if the key was handled. + public static bool OnKeyPressed (KeyEventEventArgs a) + { + KeyPressed?.Invoke (null, a); + if (a.Handled) { + return true; + } + + var chain = _topLevels.ToList (); + foreach (var topLevel in chain) { + if (topLevel.ProcessHotKey (a.KeyEvent)) { + return true; + } + if (topLevel.Modal) + break; + } + + foreach (var topLevel in chain) { + if (topLevel.ProcessKey (a.KeyEvent)) { + return true; + } + if (topLevel.Modal) + break; + } + + foreach (var topLevel in chain) { + // Process the key normally + if (topLevel.ProcessColdKey (a.KeyEvent)) { + return true; + } + if (topLevel.Modal) + break; + } + return false; } + + /// + /// Event fired when a key is pressed (and not yet released). + /// + /// + /// All drivers support firing the event. Some drivers (Curses) + /// do not support firing the and events. + /// + public static event EventHandler KeyDown; + + /// + /// Called when a key is pressed (and not yet released). Fires the event. + /// + /// + public static void OnKeyDown (KeyEventEventArgs a) + { + KeyDown?.Invoke (null, a); + var chain = _topLevels.ToList (); + foreach (var topLevel in chain) { + if (topLevel.OnKeyDown (a.KeyEvent)) + return; + if (topLevel.Modal) + break; + } + } + + /// + /// Event fired when a key is released. + /// + /// + /// All drivers support firing the event. Some drivers (Curses) + /// do not support firing the and events. + /// + public static event EventHandler KeyUp; + + /// + /// Called when a key is released. Fires the event. + /// + /// + public static void OnKeyUp (KeyEventEventArgs a) + { + KeyUp?.Invoke (null, a); + var chain = _topLevels.ToList (); + foreach (var topLevel in chain) { + if (topLevel.OnKeyUp (a.KeyEvent)) + return; + if (topLevel.Modal) + break; + } + + } + + #endregion Keyboard handling } + +/// +/// Event arguments for the event. +/// +public class IterationEventArgs { +} \ No newline at end of file diff --git a/docfx/docs/newinv2.md b/docfx/docs/newinv2.md index 72ebe6b12..149f1524b 100644 --- a/docfx/docs/newinv2.md +++ b/docfx/docs/newinv2.md @@ -1,3 +1,15 @@ # Terminal.Gui v2 -Checkout this Discussion: https://github.com/gui-cs/Terminal.Gui/discussions/2448 \ No newline at end of file +Checkout this Discussion: https://github.com/gui-cs/Terminal.Gui/discussions/2448 + +* *Modern Look & Feel* - Apps built with Terminal.Gui now feel modern thanks to these improvements: + * *TrueColor support* - 24-bit color support for Windows, Mac, and Linux. Legacy 16-color systems are still supported, automatically. See [TrueColor](https://gui-cs.github.io/Terminal.Gui/articles/overview.html#truecolor) for details. + * *User Configurable Color Themes* - See [Color Themes](https://gui-cs.github.io/Terminal.Gui/articles/overview.html#color-themes) for details. + * *Enhanced Unicode/Wide Character support *- Terminal.Gui now supports the full range of Unicode/wide characters. See [Unicode](https://gui-cs.github.io/Terminal.Gui/articles/overview.html#unicode) for details. + * *Line Canvas* - Terminal.Gui now supports a line canvas enabling high-performance drawing of lines and shapes using box-drawing glyphs. `LineCanvas` provides *auto join*, a smart TUI drawing system that automatically selects the correct line/box drawing glyphs for intersections making drawing complex shapes easy. See [Line Canvas](https://gui-cs.github.io/Terminal.Gui/articles/overview.html#line-canvas) for details. + * *Enhanced Borders and Padding* - Terminal.Gui now supports a `Border`, `Margin`, and `Padding` property on all views. This simplifies View development and enables a sophisticated look and feel. See [Padding](https://gui-cs.github.io/Terminal.Gui/articles/overview.html#padding) for details. + * *Modern File Dialog* - Terminal.Gui now supports a modern file dialog that includes icons (in TUI!) for files/folders, search, and a `TreeView``. See [FileDialog](https://gui-cs.github.io/Terminal.Gui/articles/overview.html#filedialog) for details. +* *Configuration Manager* - Terminal.Gui now supports a configuration manager enabling library and app settings to be persisted and loaded from the file system. See [Configuration Manager](https://gui-cs.github.io/Terminal.Gui/articles/overview.html#configuration-manager) for details. +* *Simplified API* - The entire library has been reviewed and simplified. As a result, the API is more consistent and uses modern .NET API standards (e.g. for events). This refactoring resulted in the removal of thousands of lines of code, better unit tests, and higher performance than v1. See [Simplified API](https://gui-cs.github.io/Terminal.Gui/articles/overview.html#simplified-api) for details. + +... \ No newline at end of file