From 6851b42a4928fad1e17565522e648f56f36a9a51 Mon Sep 17 00:00:00 2001 From: Tig Date: Sat, 21 Oct 2023 08:06:04 -0700 Subject: [PATCH] Fixes #2921 - MainLoop refactoring (#2922) * Adds basic MainLoop unit tests * Remove WinChange action from Curses * Remove WinChange action from Curses * Remove ProcessInput action from Windows MainLoop * Simplified MainLoop/ConsoleDriver by making MainLoop internal and moving impt fns to Application * Modernized Terminal resize events * Modernized Terminal resize events * Removed un used property * for _isWindowsTerminal devenv->wininit; not sure what changed * Modernized mouse/keyboard events (Action->EventHandler) * Updated OnMouseEvent API docs * Using WT_SESSION to detect WT * removes hacky GetParentProcess * Updates to fix #2634 (clear last line) * removes hacky GetParentProcess2 * Addressed mac resize issue * Addressed mac resize issue * Removes ConsoleDriver.PrepareToRun, has Init return MainLoop * Removes unneeded Attribute methods * Removed GetProcesssName * Removed GetProcesssName * Refactored KeyEvent and KeyEventEventArgs into a single class * Revert "Refactored KeyEvent and KeyEventEventArgs into a single class" This reverts commit 88a00658dbcb53306d56af1b766594c0eea10b2c. * Fixed key repeat issue; reverted stupidity on 1049/1047 confusion * Updated CSI API Docs * merge --- CONTRIBUTING.md | 6 +- ReactiveExample/TerminalScheduler.cs | 6 +- Terminal.Gui/Application.cs | 453 ++-- .../Configuration/ConfigurationManager.cs | 2 +- Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs | 128 +- .../CursesDriver/CursesDriver.cs | 263 +-- .../CursesDriver/UnixMainLoop.cs | 12 +- .../ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs | 25 +- .../ConsoleDrivers/FakeDriver/FakeDriver.cs | 36 +- Terminal.Gui/ConsoleDrivers/NetDriver.cs | 84 +- Terminal.Gui/ConsoleDrivers/WindowsDriver.cs | 244 +- Terminal.Gui/Drawing/Color.cs | 3 +- Terminal.Gui/Input/Event.cs | 4 +- Terminal.Gui/Input/MouseEventEventArgs.cs | 2 + Terminal.Gui/MainLoop.cs | 83 +- Terminal.Gui/Timeout.cs | 2 +- Terminal.Gui/TimeoutEventArgs.cs | 2 +- Terminal.Gui/View/Layout/ResizedEventArgs.cs | 32 - .../View/Layout/SizeChangedEventArgs.cs | 5 + Terminal.Gui/View/ViewKeyboard.cs | 12 +- Terminal.Gui/View/ViewLayout.cs | 2 +- Terminal.Gui/Views/Button.cs | 8 +- Terminal.Gui/Views/FileDialog.cs | 14 +- Terminal.Gui/Views/Label.cs | 2 +- Terminal.Gui/Views/Menu.cs | 24 +- Terminal.Gui/Views/Slider.cs | 1 + Terminal.Gui/Views/SpinnerView/SpinnerView.cs | 10 +- .../Views/TableView/TreeTableSource.cs | 4 +- Terminal.Gui/Views/Toplevel.cs | 30 +- Terminal.Gui/Views/ToplevelOverlapped.cs | 40 +- Terminal.sln | 1 + UICatalog/KeyBindingsDialog.cs | 4 +- UICatalog/Properties/launchSettings.json | 2 +- UICatalog/Scenarios/ASCIICustomButton.cs | 4 +- UICatalog/Scenarios/Animation.cs | 2 +- .../Scenarios/BackgroundWorkerCollection.cs | 4 +- UICatalog/Scenarios/BasicColors.cs | 8 +- UICatalog/Scenarios/CharacterMap.cs | 2 +- UICatalog/Scenarios/ContextMenus.cs | 10 +- UICatalog/Scenarios/CsvEditor.cs | 2 +- UICatalog/Scenarios/Editor.cs | 2 +- UICatalog/Scenarios/GraphViewExample.cs | 4 +- UICatalog/Scenarios/InteractiveTree.cs | 2 +- UICatalog/Scenarios/Keys.cs | 37 +- UICatalog/Scenarios/LineDrawing.cs | 2 +- UICatalog/Scenarios/ListColumns.cs | 2 +- UICatalog/Scenarios/Mouse.cs | 6 +- UICatalog/Scenarios/ProcessTable.cs | 4 +- UICatalog/Scenarios/Progress.cs | 12 +- UICatalog/Scenarios/ProgressBarStyles.cs | 4 +- UICatalog/Scenarios/Scrolling.cs | 8 +- UICatalog/Scenarios/SendKeys.cs | 4 +- UICatalog/Scenarios/SingleBackgroundWorker.cs | 2 +- UICatalog/Scenarios/Snake.cs | 2 +- UICatalog/Scenarios/TableEditor.cs | 2 +- UICatalog/Scenarios/Threading.cs | 2 +- UICatalog/Scenarios/TreeViewFileSystem.cs | 4 +- UICatalog/Scenarios/TrueColors.cs | 6 +- UICatalog/Scenarios/VkeyPacketSimulator.cs | 10 +- UnitTests/Application/ApplicationTests.cs | 1958 ++++++++--------- UnitTests/Application/MainLoopTests.cs | 1474 ++++++------- .../Application/SynchronizatonContextTests.cs | 4 +- UnitTests/Clipboard/ClipboardTests.cs | 16 +- .../Configuration/ConfigurationMangerTests.cs | 3 - UnitTests/ConsoleDrivers/AddRuneTests.cs | 10 +- .../ConsoleDrivers/ConsoleDriverTests.cs | 26 +- UnitTests/ConsoleDrivers/DriverColorTests.cs | 4 +- UnitTests/ConsoleDrivers/KeyTests.cs | 4 +- .../ConsoleDrivers/MainLoopDriverTests.cs | 270 +++ UnitTests/Dialogs/DialogTests.cs | 26 +- UnitTests/Dialogs/MessageBoxTests.cs | 28 +- UnitTests/Dialogs/WizardTests.cs | 10 +- UnitTests/Drawing/AttributeTests.cs | 6 +- UnitTests/Input/EscSeqUtilsTests.cs | 28 +- UnitTests/ReflectionTools.cs | 35 - UnitTests/UICatalog/ScenarioTests.cs | 16 +- UnitTests/View/FrameTests.cs | 16 +- UnitTests/View/KeyboardTests.cs | 12 +- UnitTests/View/Layout/DimTests.cs | 12 +- UnitTests/View/Layout/LayoutTests.cs | 33 +- UnitTests/View/Layout/PosTests.cs | 14 +- UnitTests/View/NavigationTests.cs | 22 +- UnitTests/View/ViewTests.cs | 8 +- UnitTests/Views/ContextMenuTests.cs | 79 +- UnitTests/Views/GraphViewTests.cs | 4 +- UnitTests/Views/MenuTests.cs | 76 +- UnitTests/Views/OverlappedTests.cs | 20 +- UnitTests/Views/ScrollBarViewTests.cs | 26 +- UnitTests/Views/StatusBarTests.cs | 4 +- UnitTests/Views/TabViewTests.cs | 2 +- UnitTests/Views/TextFieldTests.cs | 30 +- UnitTests/Views/TextViewTests.cs | 18 +- UnitTests/Views/ToplevelTests.cs | 304 +-- UnitTests/Views/TreeViewTests.cs | 2 +- 94 files changed, 3187 insertions(+), 3106 deletions(-) delete mode 100644 Terminal.Gui/View/Layout/ResizedEventArgs.cs create mode 100644 UnitTests/ConsoleDrivers/MainLoopDriverTests.cs delete mode 100644 UnitTests/ReflectionTools.cs diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d837cfc82..8e622eaff 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -148,11 +148,11 @@ The [Microsoft .NET Framework Design Guidelines](https://docs.microsoft.com/en-u > ✔️ DO name event argument classes with the "EventArgs" suffix. 1. We follow the naming guidelines provided in https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/names-of-type-members?redirectedfrom=MSDN -2. We use the `event Action` idiom. +2. We use the `event EventHandler` idiom. 3. For public APIs, the class that can raise the event will implement: - A `virtual` event raising function, named as `OnEventToRaise`. Typical implementations will simply do a `EventToRaise?.Invoke(this, eventArgs)`. - - An `event` as in `public event Action EventToRaise` - - Consumers of the event can do `theobject.EventToRaise += (args) => {};` + - An `event` as in `public event EventHandler EventToRaise` + - Consumers of the event can do `theobject.EventToRaise += (sender, args) => {};` - Sub-classes of the class implementing `EventToRaise` can override `OnEventToRaise` as needed. 4. Where possible, a subclass of `EventArgs` should be provided and the old and new state should be included. By doing this, event handler methods do not have to query the sender for state. diff --git a/ReactiveExample/TerminalScheduler.cs b/ReactiveExample/TerminalScheduler.cs index 49f217059..38631d525 100644 --- a/ReactiveExample/TerminalScheduler.cs +++ b/ReactiveExample/TerminalScheduler.cs @@ -15,7 +15,7 @@ namespace ReactiveExample { IDisposable PostOnMainLoop() { var composite = new CompositeDisposable(2); var cancellation = new CancellationDisposable(); - Application.MainLoop.Invoke (() => { + Application.Invoke (() => { if (!cancellation.Token.IsCancellationRequested) composite.Add(action(this, state)); }); @@ -25,11 +25,11 @@ namespace ReactiveExample { IDisposable PostOnMainLoopAsTimeout () { var composite = new CompositeDisposable (2); - var timeout = Application.MainLoop.AddTimeout (dueTime, args => { + var timeout = Application.AddTimeout (dueTime, () => { composite.Add(action (this, state)); return false; }); - composite.Add (Disposable.Create (() => Application.MainLoop.RemoveTimeout (timeout))); + composite.Add (Disposable.Create (() => Application.RemoveTimeout (timeout))); return composite; } diff --git a/Terminal.Gui/Application.cs b/Terminal.Gui/Application.cs index 5019058bf..b1bcc63fd 100644 --- a/Terminal.Gui/Application.cs +++ b/Terminal.Gui/Application.cs @@ -108,19 +108,15 @@ namespace Terminal.Gui { /// 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 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 ). - /// - /// Specifies the to use. - /// Must not be if is not . - /// - public static void Init (ConsoleDriver driver = null, IMainLoopDriver mainLoopDriver = null) => InternalInit (() => Toplevel.Create (), driver, mainLoopDriver); + public static void Init (ConsoleDriver driver = null) => InternalInit (() => Toplevel.Create (), driver); internal static bool _initialized = false; internal static int _mainThreadId = -1; @@ -134,7 +130,7 @@ namespace Terminal.Gui { // 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, IMainLoopDriver mainLoopDriver = null, bool calledViaRunT = false) + internal static void InternalInit (Func topLevelFactory, ConsoleDriver driver = null, bool calledViaRunT = false) { if (_initialized && driver == null) { return; @@ -179,26 +175,8 @@ namespace Terminal.Gui { } } - if (mainLoopDriver == null) { - // TODO: Move this logic into ConsoleDriver - if (Driver is FakeDriver) { - mainLoopDriver = new FakeMainLoop (Driver); - } else if (Driver is NetDriver) { - mainLoopDriver = new NetMainLoop (Driver); - } else if (Driver is WindowsDriver) { - mainLoopDriver = new WindowsMainLoop (Driver); - } else if (Driver is CursesDriver) { - mainLoopDriver = new UnixMainLoop (Driver); - } - if (mainLoopDriver == null) { - throw new InvalidOperationException ("Init could not determine the MainLoopDriver to use."); - } - } - - MainLoop = new MainLoop (mainLoopDriver); - try { - Driver.Init (OnTerminalResized); + 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 @@ -207,7 +185,13 @@ namespace Terminal.Gui { 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); } - SynchronizationContext.SetSynchronizationContext (new MainLoopSyncContext (MainLoop)); + 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; @@ -218,10 +202,10 @@ namespace Terminal.Gui { /// - /// Shutdown an application initialized with . + /// Shutdown an application initialized with . /// /// - /// Shutdown must be called for every call to or + /// 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 () @@ -239,11 +223,11 @@ namespace Terminal.Gui { // 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) { + foreach (var t in _topLevels) { t.Running = false; t.Dispose (); } - _toplevels.Clear (); + _topLevels.Clear (); Current = null; Top?.Dispose (); Top = null; @@ -255,9 +239,11 @@ namespace Terminal.Gui { Driver?.End (); Driver = null; Iteration = null; - RootMouseEvent = null; - RootKeyEvent = null; - TerminalResized = null; + MouseEvent = null; + KeyDown = null; + KeyUp = null; + KeyPressed = null; + SizeChanging = null; _mainThreadId = -1; NotifyNewRunState = null; NotifyStopRunState = null; @@ -274,14 +260,14 @@ namespace Terminal.Gui { #endregion Initialization (Init/Shutdown) - #region Run (Begin, Run, End) + #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 + /// If is callers to /// must also subscribe to /// and manually dispose of the token when the application is done. /// @@ -291,7 +277,7 @@ namespace Terminal.Gui { /// Notify that a existent is stopping ( was called). /// /// - /// If is callers to + /// If is callers to /// must also subscribe to /// and manually dispose of the token when the application is done. /// @@ -304,8 +290,7 @@ namespace Terminal.Gui { /// 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 + /// 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. @@ -329,34 +314,34 @@ namespace Terminal.Gui { Toplevel.EndInit (); } - lock (_toplevels) { + 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)) { + if (Top != null && Toplevel != Top && !_topLevels.Contains (Top)) { Top.Dispose (); Top = null; - } else if (Top != null && Toplevel != Top && _toplevels.Contains (Top)) { + } 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) { + var id = (_topLevels.Count + count).ToString (); + while (_topLevels.Count > 0 && _topLevels.FirstOrDefault (x => x.Id == id) != null) { count++; - id = (_toplevels.Count + count).ToString (); + id = (_topLevels.Count + count).ToString (); } - Toplevel.Id = (_toplevels.Count + count).ToString (); + Toplevel.Id = (_topLevels.Count + count).ToString (); - _toplevels.Push (Toplevel); + _topLevels.Push (Toplevel); } else { - var dup = _toplevels.FirstOrDefault (x => x.Id == Toplevel.Id); + var dup = _topLevels.FirstOrDefault (x => x.Id == Toplevel.Id); if (dup == null) { - _toplevels.Push (Toplevel); + _topLevels.Push (Toplevel); } } - if (_toplevels.FindDuplicates (new ToplevelEqualityComparer ()).Count > 0) { + if (_topLevels.FindDuplicates (new ToplevelEqualityComparer ()).Count > 0) { throw new ArgumentException ("There are duplicates Toplevels Id's"); } } @@ -374,7 +359,7 @@ namespace Terminal.Gui { } else { refreshDriver = false; } - } else if ((OverlappedTop != null && Toplevel != OverlappedTop && Current?.Modal == true && !_toplevels.Peek ().Modal) + } else if ((OverlappedTop != null && Toplevel != OverlappedTop && Current?.Modal == true && !_topLevels.Peek ().Modal) || (OverlappedTop != null && Toplevel != OverlappedTop && Current?.Running == false)) { refreshDriver = false; MoveCurrent (Toplevel); @@ -383,7 +368,6 @@ namespace Terminal.Gui { MoveCurrent (Current); } - Driver.PrepareToRun (MainLoop, ProcessKeyEvent, ProcessKeyDownEvent, ProcessKeyUpEvent, ProcessMouseEvent); if (Toplevel.LayoutStyle == LayoutStyle.Computed) { Toplevel.SetRelativeLayout (new Rect (0, 0, Driver.Cols, Driver.Rows)); } @@ -415,7 +399,7 @@ namespace Terminal.Gui { /// 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. + /// Calling first is not needed as this function will initialize the application. /// /// /// must be called when the application is closing (typically after Run> has @@ -428,10 +412,9 @@ namespace Terminal.Gui { /// /// The to use. If not specified the default driver for the /// platform will be used (, , or ). - /// Must be if has already been called. + /// 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() + public static void Run (Func errorHandler = null, ConsoleDriver driver = null) where T : Toplevel, new() { if (_initialized) { if (Driver != null) { @@ -451,7 +434,7 @@ namespace Terminal.Gui { } } else { // Init() has NOT been called. - InternalInit (() => new T (), driver, mainLoopDriver, calledViaRunT: true); + InternalInit (() => new T (), driver, calledViaRunT: true); Run (Top, errorHandler); } } @@ -496,12 +479,12 @@ namespace Terminal.Gui { try { #endif resume = false; - var runToken = Begin (view); + var runState = Begin (view); // If ExitRunLoopAfterFirstIteration is true then the user must dispose of the runToken // by using NotifyStopRunState event. - RunLoop (runToken); - if (!ExitRunLoopAfterFirstIteration) { - End (runToken); + RunLoop (runState); + if (!EndAfterFirstIteration) { + End (runState); } #if !DEBUG } @@ -517,6 +500,49 @@ namespace Terminal.Gui { } } + /// + /// 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. /// @@ -525,7 +551,7 @@ namespace Terminal.Gui { // 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 ()) { + foreach (var v in _topLevels.Reverse ()) { if (v.Visible) { v.SetNeedsDisplay (); v.SetSubViewNeedsDisplay (); @@ -543,40 +569,33 @@ namespace Terminal.Gui { /// /// See also /// - public static Action Iteration; + public static event EventHandler Iteration; /// /// The driver for the application /// /// The main loop. - public static MainLoop MainLoop { get; private set; } + internal static MainLoop MainLoop { get; private 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. + /// 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 ExitRunLoopAfterFirstIteration { get; set; } = false; + 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 { - readonly MainLoop _mainLoop; - - public MainLoopSyncContext (MainLoop mainLoop) - { - this._mainLoop = mainLoop; - } - public override SynchronizationContext CreateCopy () { - return new MainLoopSyncContext (MainLoop); + return new MainLoopSyncContext (); } public override void Post (SendOrPostCallback d, object state) { - _mainLoop.AddIdle (() => { + MainLoop.AddIdle (() => { d (state); return false; }); @@ -589,7 +608,7 @@ namespace Terminal.Gui { d (state); } else { var wasExecuted = false; - _mainLoop.Invoke (() => { + Invoke (() => { d (state); wasExecuted = true; }); @@ -615,20 +634,20 @@ namespace Terminal.Gui { var firstIteration = true; for (state.Toplevel.Running = true; state.Toplevel.Running;) { - if (ExitRunLoopAfterFirstIteration && !firstIteration) { + if (EndAfterFirstIteration && !firstIteration) { return; } - RunMainLoopIteration (ref state, ref firstIteration); + RunIteration (ref state, ref firstIteration); } } /// - /// Run one iteration of the . + /// 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 RunMainLoopIteration (ref RunState state, ref bool firstIteration) + public static void RunIteration (ref RunState state, ref bool firstIteration) { if (MainLoop.EventsPending ()) { // Notify Toplevel it's ready @@ -637,7 +656,7 @@ namespace Terminal.Gui { } MainLoop.RunIteration (); - Iteration?.Invoke (); + Iteration?.Invoke (null, new IterationEventArgs()); EnsureModalOrVisibleAlwaysOnTop (state.Toplevel); if (state.Toplevel != Current) { @@ -661,7 +680,7 @@ namespace Terminal.Gui { state.Toplevel.SetNeedsDisplay (state.Toplevel.Frame); Top.Clear (); Top.Draw (); - foreach (var top in _toplevels.Reverse ()) { + foreach (var top in _topLevels.Reverse ()) { if (top != Top && top != state.Toplevel) { top.SetNeedsDisplay (); top.SetSubViewNeedsDisplay (); @@ -670,7 +689,7 @@ namespace Terminal.Gui { } } } - if (_toplevels.Count == 1 && state.Toplevel == Top + 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)) { @@ -738,7 +757,7 @@ namespace Terminal.Gui { } 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)) { + && Current?.Running == false && !top.Running && _topLevels.ToArray () [1].Running)) { MoveCurrent (top); } else if (OverlappedTop != null && Current != top && Current?.Running == true && !top.Running @@ -774,7 +793,7 @@ namespace Terminal.Gui { static void OnNotifyStopRunState (Toplevel top) { - if (ExitRunLoopAfterFirstIteration) { + if (EndAfterFirstIteration) { NotifyStopRunState?.Invoke (top, new ToplevelEventArgs (top)); } } @@ -785,8 +804,9 @@ namespace Terminal.Gui { /// The returned by the method. public static void End (RunState runState) { - if (runState == null) + if (runState == null) { throw new ArgumentNullException (nameof (runState)); + } if (OverlappedTop != null) { OverlappedTop.OnChildUnloaded (runState.Toplevel); @@ -796,13 +816,13 @@ namespace Terminal.Gui { // End the RunState.Toplevel // First, take it off the Toplevel Stack - if (_toplevels.Count > 0) { - if (_toplevels.Peek () != runState.Toplevel) { + 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 (); + _topLevels.Pop (); } // Notify that it is closing @@ -815,11 +835,11 @@ namespace Terminal.Gui { } // Set Current and Top to the next TopLevel on the stack - if (_toplevels.Count == 0) { + if (_topLevels.Count == 0) { Current = null; } else { - Current = _toplevels.Peek (); - if (_toplevels.Count == 1 && Current == OverlappedTop) { + Current = _topLevels.Peek (); + if (_topLevels.Count == 1 && Current == OverlappedTop) { OverlappedTop.OnAllChildClosed (); } else { SetCurrentOverlappedAsTop (); @@ -837,7 +857,7 @@ namespace Terminal.Gui { #endregion Run (Begin, Run, End) #region Toplevel handling - static readonly Stack _toplevels = new Stack (); + static readonly Stack _topLevels = new Stack (); /// /// The object used for the application on startup () @@ -854,11 +874,11 @@ namespace Terminal.Gui { static void EnsureModalOrVisibleAlwaysOnTop (Toplevel Toplevel) { - if (!Toplevel.Running || (Toplevel == Current && Toplevel.Visible) || OverlappedTop == null || _toplevels.Peek ().Modal) { + if (!Toplevel.Running || (Toplevel == Current && Toplevel.Visible) || OverlappedTop == null || _topLevels.Peek ().Modal) { return; } - foreach (var top in _toplevels.Reverse ()) { + foreach (var top in _topLevels.Reverse ()) { if (top.Modal && top != Current) { MoveCurrent (top); return; @@ -879,12 +899,12 @@ namespace Terminal.Gui { return null; } - if (_toplevels != null) { - int count = _toplevels.Count; + 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) { + foreach (var t in _topLevels) { if (t != Current) { if (t != start && t.Visible && t.Frame.Contains (rx, ry)) { start = t; @@ -915,16 +935,16 @@ namespace Terminal.Gui { { // 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 ()); + 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 (); + 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 ()); + lock (_topLevels) { + _topLevels.MoveTo (top, index, new ToplevelEqualityComparer ()); } } index++; @@ -934,26 +954,26 @@ namespace Terminal.Gui { // 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 ()); + lock (_topLevels) { + _topLevels.MoveTo (Current, 0, new ToplevelEqualityComparer ()); } var index = 0; - foreach (var t in _toplevels.ToArray ()) { + foreach (var t in _topLevels.ToArray ()) { if (!t.Running && t != Current && index > 0) { - lock (_toplevels) { - _toplevels.MoveTo (top, index - 1, new ToplevelEqualityComparer ()); + lock (_topLevels) { + _topLevels.MoveTo (top, index - 1, new ToplevelEqualityComparer ()); } } index++; } return false; } - if ((OverlappedTop != null && top?.Modal == true && _toplevels.Peek () != top) + 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 ()); + lock (_topLevels) { + _topLevels.MoveTo (top, 0, new ToplevelEqualityComparer ()); Current = top; } } @@ -961,21 +981,35 @@ namespace Terminal.Gui { } /// - /// Invoked when the terminal was resized. The new size of the terminal is provided. + /// Invoked when the terminal's size changed. The new size of the terminal is provided. /// - public static Action TerminalResized; + /// + /// Event handlers can set to + /// to prevent from changing it's size to match the new terminal size. + /// + public static event EventHandler SizeChanging; - static void OnTerminalResized () + /// + /// 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) { - var full = new Rect (0, 0, Driver.Cols, Driver.Rows); - TerminalResized?.Invoke (new ResizedEventArgs () { Cols = full.Width, Rows = full.Height }); - foreach (var t in _toplevels) { - t.SetRelativeLayout (full); + 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.OnTerminalResized (new SizeChangedEventArgs (full.Size)); + t.OnSizeChanging (new SizeChangedEventArgs (args.Size)); } Refresh (); + return true; } #endregion Toplevel handling @@ -1080,14 +1114,30 @@ namespace Terminal.Gui { UnGrabbedMouse?.Invoke (view, new ViewEventArgs (view)); } - /// - /// Merely a debugging aid to see the raw mouse events - /// - public static Action RootMouseEvent { get; set; } - static View _lastMouseOwnerView; - static void ProcessMouseEvent (MouseEvent me) + /// + /// 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. + /// + /// + public static void OnMouseEvent (MouseEventEventArgs a) { static bool OutsideBounds (Point p, Rect r) => p.X < 0 || p.X > r.Right || p.Y < 0 || p.Y > r.Bottom; @@ -1095,7 +1145,7 @@ namespace Terminal.Gui { return; } - var view = View.FindDeepestView (Current, me.X, me.Y, out int rx, out int ry); + var view = View.FindDeepestView (Current, a.MouseEvent.X, a.MouseEvent.Y, out int rx, out int ry); if (view != null && view.WantContinuousButtonPressed) { WantContinuousButtonPressedView = view; @@ -1103,26 +1153,26 @@ namespace Terminal.Gui { WantContinuousButtonPressedView = null; } if (view != null) { - me.View = view; + a.MouseEvent.View = view; } - RootMouseEvent?.Invoke (me); + MouseEvent?.Invoke (null, new MouseEventEventArgs (a.MouseEvent)); - if (me.Handled) { + if (a.MouseEvent.Handled) { return; } if (_mouseGrabView != null) { - var newxy = _mouseGrabView.ScreenToView (me.X, me.Y); + var newxy = _mouseGrabView.ScreenToView (a.MouseEvent.X, a.MouseEvent.Y); var nme = new MouseEvent () { X = newxy.X, Y = newxy.Y, - Flags = me.Flags, - OfX = me.X - newxy.X, - OfY = me.Y - newxy.Y, + Flags = a.MouseEvent.Flags, + OfX = a.MouseEvent.X - newxy.X, + OfY = a.MouseEvent.Y - newxy.Y, View = view }; if (OutsideBounds (new Point (nme.X, nme.Y), _mouseGrabView.Bounds)) { - _lastMouseOwnerView?.OnMouseLeave (me); + _lastMouseOwnerView?.OnMouseLeave (a.MouseEvent); } //System.Diagnostics.Debug.WriteLine ($"{nme.Flags};{nme.X};{nme.Y};{mouseGrabView}"); if (_mouseGrabView?.OnMouseEvent (nme) == true) { @@ -1131,10 +1181,10 @@ namespace Terminal.Gui { } if ((view == null || view == OverlappedTop) && !Current.Modal && OverlappedTop != null - && me.Flags != MouseFlags.ReportMousePosition && me.Flags != 0) { + && a.MouseEvent.Flags != MouseFlags.ReportMousePosition && a.MouseEvent.Flags != 0) { - var top = FindDeepestTop (Top, me.X, me.Y, out _, out _); - view = View.FindDeepestView (top, me.X, me.Y, out rx, out ry); + var top = FindDeepestTop (Top, a.MouseEvent.X, a.MouseEvent.Y, out _, out _); + view = View.FindDeepestView (top, a.MouseEvent.X, a.MouseEvent.Y, out rx, out ry); if (view != null && view != OverlappedTop && top != Current) { MoveCurrent ((Toplevel)top); @@ -1145,7 +1195,7 @@ namespace Terminal.Gui { var nme = new MouseEvent () { X = rx, Y = ry, - Flags = me.Flags, + Flags = a.MouseEvent.Flags, OfX = 0, OfY = 0, View = view @@ -1160,7 +1210,7 @@ namespace Terminal.Gui { _lastMouseOwnerView = view; } - if (!view.WantMousePositionReports && me.Flags == MouseFlags.ReportMousePosition) + if (!view.WantMousePositionReports && a.MouseEvent.Flags == MouseFlags.ReportMousePosition) return; if (view.WantContinuousButtonPressed) @@ -1178,7 +1228,6 @@ namespace Terminal.Gui { #region Keyboard handling - static Key _alternateForwardKey = Key.PageDown | Key.CtrlMask; /// @@ -1198,7 +1247,7 @@ namespace Terminal.Gui { static void OnAlternateForwardKeyChanged (KeyChangedEventArgs e) { - foreach (var top in _toplevels.ToArray ()) { + foreach (var top in _topLevels.ToArray ()) { top.OnAlternateForwardKeyChanged (e); } } @@ -1222,7 +1271,7 @@ namespace Terminal.Gui { static void OnAlternateBackwardKeyChanged (KeyChangedEventArgs oldKey) { - foreach (var top in _toplevels.ToArray ()) { + foreach (var top in _topLevels.ToArray ()) { top.OnAlternateBackwardKeyChanged (oldKey); } } @@ -1246,57 +1295,84 @@ namespace Terminal.Gui { static void OnQuitKeyChanged (KeyChangedEventArgs e) { // Duplicate the list so if it changes during enumeration we're safe - foreach (var top in _toplevels.ToArray ()) { + foreach (var top in _topLevels.ToArray ()) { top.OnQuitKeyChanged (e); } } - static void ProcessKeyEvent (KeyEvent ke) + /// + /// 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) { - if (RootKeyEvent?.Invoke (ke) ?? false) { - return; + KeyPressed?.Invoke (null, a); + if (a.Handled) { + return true; } - var chain = _toplevels.ToList (); + var chain = _topLevels.ToList (); foreach (var topLevel in chain) { - if (topLevel.ProcessHotKey (ke)) - return; + if (topLevel.ProcessHotKey (a.KeyEvent)) { + return true; + } if (topLevel.Modal) break; } foreach (var topLevel in chain) { - if (topLevel.ProcessKey (ke)) - return; + if (topLevel.ProcessKey (a.KeyEvent)) { + return true; + } if (topLevel.Modal) break; } foreach (var topLevel in chain) { // Process the key normally - if (topLevel.ProcessColdKey (ke)) - return; + if (topLevel.ProcessColdKey (a.KeyEvent)) { + return true; + } if (topLevel.Modal) break; } + return false; } - static void ProcessKeyDownEvent (KeyEvent ke) - { - var chain = _toplevels.ToList (); - foreach (var topLevel in chain) { - if (topLevel.OnKeyDown (ke)) - return; - if (topLevel.Modal) - break; - } - } + /// + /// 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; - static void ProcessKeyUpEvent (KeyEvent ke) + /// + /// Called when a key is pressed (and not yet released). Fires the event. + /// + /// + public static void OnKeyDown (KeyEventEventArgs a) { - var chain = _toplevels.ToList (); + KeyDown?.Invoke (null, a); + var chain = _topLevels.ToList (); foreach (var topLevel in chain) { - if (topLevel.OnKeyUp (ke)) + if (topLevel.OnKeyDown (a.KeyEvent)) return; if (topLevel.Modal) break; @@ -1304,14 +1380,37 @@ namespace Terminal.Gui { } /// - /// - /// 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 + /// Event fired when a key is released. /// - public static Func RootKeyEvent { get; set; } + /// + /// 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 { + } } diff --git a/Terminal.Gui/Configuration/ConfigurationManager.cs b/Terminal.Gui/Configuration/ConfigurationManager.cs index 228b2efb2..5e05ecfb4 100644 --- a/Terminal.Gui/Configuration/ConfigurationManager.cs +++ b/Terminal.Gui/Configuration/ConfigurationManager.cs @@ -251,7 +251,7 @@ public static partial class ConfigurationManager { /// /// Resets the state of . Should be called whenever a new app session - /// (e.g. in starts. Called by + /// (e.g. in starts. Called by /// if the reset parameter is . /// /// diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs index 3f4dcb7e4..042a10bb2 100644 --- a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs @@ -9,7 +9,6 @@ using static Terminal.Gui.ColorScheme; namespace Terminal.Gui; - /// /// Base class for Terminal.Gui ConsoleDriver implementations. /// @@ -32,20 +31,31 @@ public abstract class ConsoleDriver { /// internal static bool RunningUnitTests { get; set; } - /// - /// Prepare the driver and set the key and mouse events handlers. - /// - /// The main loop. - /// The handler for ProcessKey - /// The handler for key down events - /// The handler for key up events - /// The handler for mouse events - public abstract void PrepareToRun (MainLoop mainLoop, Action keyHandler, Action keyDownHandler, Action keyUpHandler, Action mouseHandler); + #region Setup & Teardown /// - /// The handler fired when the terminal is resized. + /// Initializes the driver /// - protected Action TerminalResized; + /// Returns an instance of using the for the driver. + internal abstract MainLoop Init (); + + /// + /// Ends the execution of the console driver. + /// + internal abstract void End (); + + #endregion + + /// + /// The event fired when the terminal is resized. + /// + public event EventHandler SizeChanged; + + /// + /// Called when the terminal size changes. Fires the event. + /// + /// + public void OnSizeChanged (SizeChangedEventArgs args) => SizeChanged?.Invoke (this, args); /// /// The number of columns visible in the terminal. @@ -90,12 +100,6 @@ public abstract class ConsoleDriver { ///// public Cell [,] Contents { get; internal set; } - /// - /// Initializes the driver - /// - /// Method to invoke when the terminal is resized. - public abstract void Init (Action terminalResized); - /// /// Gets the column last set by . and /// are used by and to determine where to add content. @@ -411,23 +415,14 @@ public abstract class ConsoleDriver { return prevAttribute; } - /// - /// Make the attribute for the foreground and background colors. - /// - /// Foreground. - /// Background. - /// - public virtual Attribute MakeAttribute (Color fore, Color back) - { - return MakeColor (fore, back); - } - /// /// Gets the current . /// /// The current attribute. public Attribute GetAttribute () => CurrentAttribute; - + + // TODO: This is only overridden by CursesDriver. Once CursesDriver supports 24-bit color, this virtual method can be + // removed (and Attribute can lose the platformColor property). /// /// Makes an . /// @@ -445,6 +440,64 @@ public abstract class ConsoleDriver { } + #endregion + + #region Mouse and Keyboard + + /// + /// Event fired after a key has been pressed and released. + /// + public event EventHandler KeyPressed; + + /// + /// Called after a key has been pressed and released. Fires the event. + /// + /// + public void OnKeyPressed (KeyEventEventArgs a) => KeyPressed?.Invoke(this, a); + + /// + /// Event fired when a key is released. + /// + public event EventHandler KeyUp; + + /// + /// Called when a key is released. Fires the event. + /// + /// + public void OnKeyUp (KeyEventEventArgs a) => KeyUp?.Invoke (this, a); + + /// + /// Event fired when a key is pressed. + /// + public event EventHandler KeyDown; + + /// + /// Called when a key is pressed. Fires the event. + /// + /// + public void OnKeyDown (KeyEventEventArgs a) => KeyDown?.Invoke (this, a); + + /// + /// Event fired when a mouse event occurs. + /// + public event EventHandler MouseEvent; + + /// + /// Called when a mouse event occurs. Fires the event. + /// + /// + public void OnMouseEvent (MouseEventEventArgs a) => MouseEvent?.Invoke (this, a); + + /// + /// Simulates a key press. + /// + /// The key character. + /// The key. + /// If simulates the Shift key being pressed. + /// If simulates the Alt key being pressed. + /// If simulates the Ctrl key being pressed. + public abstract void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool ctrl); + #endregion /// @@ -479,16 +532,6 @@ public abstract class ConsoleDriver { /// This is only implemented in . public abstract void Suspend (); - /// - /// Simulates a key press. - /// - /// The key character. - /// The key. - /// If simulates the Shift key being pressed. - /// If simulates the Alt key being pressed. - /// If simulates the Ctrl key being pressed. - public abstract void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool ctrl); - // TODO: Move FillRect to ./Drawing /// /// Fills the specified rectangle with the specified rune. @@ -513,11 +556,6 @@ public abstract class ConsoleDriver { /// public void FillRect (Rect rect, char c) => FillRect (rect, new Rune (c)); - /// - /// Ends the execution of the console driver. - /// - public abstract void End (); - /// /// Returns the name of the driver and relevant library version information. /// diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs index f7563327c..036c20178 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs @@ -22,9 +22,92 @@ internal class CursesDriver : ConsoleDriver { CursorVisibility? _currentCursorVisibility = null; public override string GetVersionInfo () => $"{Curses.curses_version ()}"; - + UnixMainLoop _mainLoopDriver = null; public override bool SupportsTrueColor => false; + object _processInputToken; + + internal override MainLoop Init () + { + _mainLoopDriver = new UnixMainLoop (this); + if (!RunningUnitTests) { + + _window = Curses.initscr (); + Curses.set_escdelay (10); + + // Ensures that all procedures are performed at some previous closing. + Curses.doupdate (); + + // + // We are setting Invisible as default so we could ignore XTerm DECSUSR setting + // + switch (Curses.curs_set (0)) { + case 0: + _currentCursorVisibility = _initialCursorVisibility = CursorVisibility.Invisible; + break; + + case 1: + _currentCursorVisibility = _initialCursorVisibility = CursorVisibility.Underline; + Curses.curs_set (1); + break; + + case 2: + _currentCursorVisibility = _initialCursorVisibility = CursorVisibility.Box; + Curses.curs_set (2); + break; + + default: + _currentCursorVisibility = _initialCursorVisibility = null; + break; + } + if (!Curses.HasColors) { + throw new InvalidOperationException ("V2 - This should never happen. File an Issue if it does."); + } + + Curses.raw (); + Curses.noecho (); + + Curses.Window.Standard.keypad (true); + + Curses.StartColor (); + Curses.UseDefaultColors (); + + if (!RunningUnitTests) { + Curses.timeout (0); + } + + _processInputToken = _mainLoopDriver?.AddWatch (0, UnixMainLoop.Condition.PollIn, x => { + ProcessInput (); + return true; + }); + } + + CurrentAttribute = new Attribute (ColorName.White, ColorName.Black); + + if (Environment.OSVersion.Platform == PlatformID.Win32NT) { + Clipboard = new FakeDriver.FakeClipboard (); + } else { + if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) { + Clipboard = new MacOSXClipboard (); + } else { + if (Is_WSL_Platform ()) { + Clipboard = new WSLClipboard (); + } else { + Clipboard = new CursesClipboard (); + } + } + } + + ClearContents (); + StartReportingMouseMoves (); + + if (!RunningUnitTests) { + Curses.CheckWinChange (); + Curses.refresh (); + } + return new MainLoop (_mainLoopDriver); + } + public override void Move (int col, int row) { base.Move (col, row); @@ -54,11 +137,11 @@ internal class CursesDriver : ConsoleDriver { UpdateCursor (); } - private void ProcessWinChange () + internal void ProcessWinChange () { if (!RunningUnitTests && Curses.CheckWinChange ()) { ClearContents (); - TerminalResized?.Invoke (); + OnSizeChanged (new SizeChangedEventArgs (new Size (Cols, Rows))); } } @@ -82,25 +165,6 @@ internal class CursesDriver : ConsoleDriver { background: CursesColorNumberToColorName (background)); } - /// - /// In the CursesDriver, colors are encoded as an int. - /// The foreground color is stored in the most significant 4 bits, - /// and the background color is stored in the least significant 4 bits. - /// The Terminal.GUi Color values are converted to curses color encoding before being encoded. - /// - private Attribute MakeColor (ColorName foregroundName, ColorName backgroundName) - { - if (!RunningUnitTests) { - return MakeColor (ColorNameToCursesColorNumber (foregroundName), ColorNameToCursesColorNumber (backgroundName)); - } else { - return new Attribute ( - platformColor: 0, - foreground: ColorNameToCursesColorNumber (foregroundName), - background: ColorNameToCursesColorNumber (backgroundName)); - } - } - - /// /// In the CursesDriver, colors are encoded as an int. /// The foreground color is stored in the most significant 4 bits, @@ -110,7 +174,7 @@ internal class CursesDriver : ConsoleDriver { public override Attribute MakeColor (Color foreground, Color background) { if (!RunningUnitTests) { - return MakeColor (foreground.ColorName, background.ColorName); + return MakeColor (ColorNameToCursesColorNumber (foreground.ColorName), ColorNameToCursesColorNumber (background.ColorName)); } else { return new Attribute ( platformColor: 0, @@ -208,14 +272,13 @@ internal class CursesDriver : ConsoleDriver { } } - public override void End () + internal override void End () { StopReportingMouseMoves (); SetCursorVisibility (CursorVisibility.Default); - if (_mainLoop != null) { - _mainLoop.RemoveWatch (_processInputToken); - _mainLoop.WinChanged -= ProcessInput; + if (_mainLoopDriver != null) { + _mainLoopDriver.RemoveWatch (_processInputToken); } if (RunningUnitTests) { @@ -371,7 +434,7 @@ internal class CursesDriver : ConsoleDriver { return _keyModifiers; } - void ProcessInput () + internal void ProcessInput () { int wch; var code = Curses.get_wch (out wch); @@ -428,9 +491,9 @@ internal class CursesDriver : ConsoleDriver { wch -= 60; k = Key.ShiftMask | Key.AltMask | MapCursesKey (wch); } - _keyDownHandler (new KeyEvent (k, MapKeyModifiers (k))); - _keyHandler (new KeyEvent (k, MapKeyModifiers (k))); - _keyUpHandler (new KeyEvent (k, MapKeyModifiers (k))); + OnKeyDown (new KeyEventEventArgs (new KeyEvent (k, MapKeyModifiers (k)))); + OnKeyUp (new KeyEventEventArgs (new KeyEvent (k, MapKeyModifiers (k)))); + OnKeyPressed (new KeyEventEventArgs (new KeyEvent (k, MapKeyModifiers (k)))); return; } @@ -481,16 +544,18 @@ internal class CursesDriver : ConsoleDriver { } } key = new KeyEvent (k, MapKeyModifiers (k)); - _keyDownHandler (key); - _keyHandler (key); + OnKeyDown (new KeyEventEventArgs (key)); + OnKeyUp (new KeyEventEventArgs (key)); + OnKeyPressed (new KeyEventEventArgs (key)); } else { k = Key.Esc; - _keyHandler (new KeyEvent (k, MapKeyModifiers (k))); + OnKeyPressed (new KeyEventEventArgs (new KeyEvent (k, MapKeyModifiers (k)))); } } else if (wch == Curses.KeyTab) { k = MapCursesKey (wch); - _keyDownHandler (new KeyEvent (k, MapKeyModifiers (k))); - _keyHandler (new KeyEvent (k, MapKeyModifiers (k))); + OnKeyDown (new KeyEventEventArgs (new KeyEvent (k, MapKeyModifiers (k)))); + OnKeyUp (new KeyEventEventArgs (new KeyEvent (k, MapKeyModifiers (k)))); + OnKeyPressed (new KeyEventEventArgs (new KeyEvent (k, MapKeyModifiers (k)))); } else { // Unfortunately there are no way to differentiate Ctrl+alfa and Ctrl+Shift+alfa. k = (Key)wch; @@ -503,9 +568,9 @@ internal class CursesDriver : ConsoleDriver { } else if (wch >= (uint)Key.A && wch <= (uint)Key.Z) { _keyModifiers.Shift = true; } - _keyDownHandler (new KeyEvent (k, MapKeyModifiers (k))); - _keyHandler (new KeyEvent (k, MapKeyModifiers (k))); - _keyUpHandler (new KeyEvent (k, MapKeyModifiers (k))); + OnKeyDown (new KeyEventEventArgs (new KeyEvent (k, MapKeyModifiers (k)))); + OnKeyUp (new KeyEventEventArgs (new KeyEvent (k, MapKeyModifiers (k)))); + OnKeyPressed (new KeyEventEventArgs (new KeyEvent (k, MapKeyModifiers (k)))); } // Cause OnKeyUp and OnKeyPressed. Note that the special handling for ESC above // will not impact KeyUp. @@ -525,7 +590,7 @@ internal class CursesDriver : ConsoleDriver { code = Curses.get_wch (out wch2); var consoleKeyInfo = new ConsoleKeyInfo ((char)wch2, 0, false, false, false); if (wch2 == 0 || wch2 == 27 || wch2 == Curses.KeyMouse) { - EscSeqUtils.DecodeEscSeq (null, ref consoleKeyInfo, ref ck, cki, ref mod, out _, out _, out _, out _, out bool isKeyMouse, out List mouseFlags, out Point pos, out _, ProcessContinuousButtonPressed); + EscSeqUtils.DecodeEscSeq (null, ref consoleKeyInfo, ref ck, cki, ref mod, out _, out _, out _, out _, out bool isKeyMouse, out List mouseFlags, out Point pos, out _, ProcessMouseEvent); if (isKeyMouse) { foreach (var mf in mouseFlags) { ProcessMouseEvent (mf, pos); @@ -539,8 +604,8 @@ internal class CursesDriver : ConsoleDriver { k = ConsoleKeyMapping.MapConsoleKeyToKey (consoleKeyInfo.Key, out _); k = ConsoleKeyMapping.MapKeyModifiers (consoleKeyInfo, k); key = new KeyEvent (k, MapKeyModifiers (k)); - _keyDownHandler (key); - _keyHandler (key); + OnKeyDown (new KeyEventEventArgs (key)); + OnKeyPressed (new KeyEventEventArgs (key)); } } else { cki = EscSeqUtils.ResizeArray (consoleKeyInfo, cki); @@ -592,116 +657,10 @@ internal class CursesDriver : ConsoleDriver { X = pos.X, Y = pos.Y }; - _mouseHandler (me); + OnMouseEvent (new MouseEventEventArgs (me)); } - void ProcessContinuousButtonPressed (MouseFlags mouseFlag, Point pos) - { - ProcessMouseEvent (mouseFlag, pos); - } - - Action _keyHandler; - Action _keyDownHandler; - Action _keyUpHandler; - Action _mouseHandler; - - UnixMainLoop _mainLoop; - object _processInputToken; - - public override void PrepareToRun (MainLoop mainLoop, Action keyHandler, Action keyDownHandler, Action keyUpHandler, Action mouseHandler) - { - if (!RunningUnitTests) { - // Note: Curses doesn't support keydown/up events and thus any passed keyDown/UpHandlers will never be called - Curses.timeout (0); - } - this._keyHandler = keyHandler; - this._keyDownHandler = keyDownHandler; - this._keyUpHandler = keyUpHandler; - this._mouseHandler = mouseHandler; - - _mainLoop = mainLoop.MainLoopDriver as UnixMainLoop; - - _processInputToken = _mainLoop?.AddWatch (0, UnixMainLoop.Condition.PollIn, x => { - ProcessInput (); - return true; - }); - - _mainLoop.WinChanged = ProcessInput; - } - - public override void Init (Action terminalResized) - { - if (!RunningUnitTests) { - - _window = Curses.initscr (); - Curses.set_escdelay (10); - - // Ensures that all procedures are performed at some previous closing. - Curses.doupdate (); - - // - // We are setting Invisible as default so we could ignore XTerm DECSUSR setting - // - switch (Curses.curs_set (0)) { - case 0: - _currentCursorVisibility = _initialCursorVisibility = CursorVisibility.Invisible; - break; - - case 1: - _currentCursorVisibility = _initialCursorVisibility = CursorVisibility.Underline; - Curses.curs_set (1); - break; - - case 2: - _currentCursorVisibility = _initialCursorVisibility = CursorVisibility.Box; - Curses.curs_set (2); - break; - - default: - _currentCursorVisibility = _initialCursorVisibility = null; - break; - } - if (!Curses.HasColors) { - throw new InvalidOperationException ("V2 - This should never happen. File an Issue if it does."); - } - - Curses.raw (); - Curses.noecho (); - - Curses.Window.Standard.keypad (true); - - Curses.StartColor (); - Curses.UseDefaultColors (); - } - - CurrentAttribute = MakeColor (ColorName.White, ColorName.Black); - - TerminalResized = terminalResized; - - if (Environment.OSVersion.Platform == PlatformID.Win32NT) { - Clipboard = new FakeDriver.FakeClipboard (); - } else { - if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) { - Clipboard = new MacOSXClipboard (); - } else { - if (Is_WSL_Platform ()) { - Clipboard = new WSLClipboard (); - } else { - Clipboard = new CursesClipboard (); - } - } - } - - ClearContents (); - StartReportingMouseMoves (); - - if (!RunningUnitTests) { - Curses.CheckWinChange (); - Curses.refresh (); - } - } - public static bool Is_WSL_Platform () { // xclip does not work on WSL, so we need to use the Windows clipboard vis Powershell @@ -819,9 +778,9 @@ internal class CursesDriver : ConsoleDriver { key |= Key.CtrlMask; km.Ctrl = control; } - _keyDownHandler (new KeyEvent (key, km)); - _keyHandler (new KeyEvent (key, km)); - _keyUpHandler (new KeyEvent (key, km)); + OnKeyDown (new KeyEventEventArgs (new KeyEvent (key, km))); + OnKeyPressed (new KeyEventEventArgs (new KeyEvent (key, km))); + OnKeyUp (new KeyEventEventArgs (new KeyEvent (key, km))); } diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs index bba17ea3c..0044cf414 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs @@ -15,9 +15,11 @@ namespace Terminal.Gui { /// can watch file descriptors using the AddWatch methods. /// internal class UnixMainLoop : IMainLoopDriver { + private CursesDriver _cursesDriver; public UnixMainLoop (ConsoleDriver consoleDriver = null) { // UnixDriver doesn't use the consoleDriver parameter, but the WindowsDriver does. + _cursesDriver = (CursesDriver)Application.Driver; } public const int KEY_RESIZE = unchecked((int)0xffffffffffffffff); @@ -86,8 +88,6 @@ namespace Terminal.Gui { MainLoop _mainLoop; bool _winChanged; - internal Action WinChanged; - void IMainLoopDriver.Wakeup () { if (!ConsoleDriver.RunningUnitTests) { @@ -119,7 +119,7 @@ namespace Terminal.Gui { /// /// The token parameter is the value returned from AddWatch /// - public void RemoveWatch (object token) + internal void RemoveWatch (object token) { if (!ConsoleDriver.RunningUnitTests) { if (token is not Watch watch) { @@ -140,7 +140,7 @@ namespace Terminal.Gui { /// The return value is a token that represents this watch, you can /// use this token to remove the watch by calling RemoveWatch. /// - public object AddWatch (int fileDescriptor, Condition condition, Func callback) + internal object AddWatch (int fileDescriptor, Condition condition, Func callback) { if (callback == null) { throw new ArgumentNullException (nameof (callback)); @@ -187,7 +187,9 @@ namespace Terminal.Gui { { if (_winChanged) { _winChanged = false; - WinChanged?.Invoke (); + _cursesDriver.ProcessInput (); + // This is needed on the mac. See https://github.com/gui-cs/Terminal.Gui/pull/2922#discussion_r1365992426 + _cursesDriver.ProcessWinChange (); } if (_pollMap == null) return; foreach (var p in _pollMap) { diff --git a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs index 5626d002b..08c2d686b 100644 --- a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs +++ b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Management; using System.Runtime.InteropServices; using System.Threading.Tasks; +using static Unix.Terminal.Curses; namespace Terminal.Gui; /// @@ -72,22 +73,42 @@ public static class EscSeqUtils { /// /// ESC [ ? 1047 h - Activate xterm alternative buffer (no backscroll) /// + /// + /// From https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Functions-using-CSI-_-ordered-by-the-final-character_s_ + /// Use Alternate Screen Buffer, xterm. + /// public static readonly string CSI_ActivateAltBufferNoBackscroll = CSI + "?1047h"; /// /// ESC [ ? 1047 l - Restore xterm working buffer (with backscroll) /// + /// + /// From https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Functions-using-CSI-_-ordered-by-the-final-character_s_ + /// Use Normal Screen Buffer, xterm. Clear the screen first if in the Alternate Screen Buffer. + /// public static readonly string CSI_RestoreAltBufferWithBackscroll = CSI + "?1047l"; /// /// ESC [ ? 1049 h - Save cursor position and activate xterm alternative buffer (no backscroll) /// + /// + /// From https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Functions-using-CSI-_-ordered-by-the-final-character_s_ + /// Save cursor as in DECSC, xterm. After saving the cursor, switch to the Alternate Screen Buffer, + /// clearing it first. + /// This control combines the effects of the 1047 and 1048 modes. + /// Use this with terminfo-based applications rather than the 47 mode. + /// public static readonly string CSI_SaveCursorAndActivateAltBufferNoBackscroll = CSI + "?1049h"; /// /// ESC [ ? 1049 l - Restore cursor position and restore xterm working buffer (with backscroll) /// - public static readonly string CSI_RestoreCursorAndActivateAltBufferWithBackscroll = CSI + "?1049l"; + /// + /// From https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Functions-using-CSI-_-ordered-by-the-final-character_s_ + /// Use Normal Screen Buffer and restore cursor as in DECRC, xterm. + /// resource.This combines the effects of the 1047 and 1048 modes. + /// + public static readonly string CSI_RestoreCursorAndRestoreAltBufferWithBackscroll = CSI + "?1049l"; /// /// Options for ANSI ESC "[xJ" - Clears part of the screen. @@ -1060,7 +1081,7 @@ public static class EscSeqUtils { if (view == null) break; if (isButtonPressed && lastMouseButtonPressed != null && (mouseFlag & MouseFlags.ReportMousePosition) == 0) { - Application.MainLoop.Invoke (() => continuousButtonPressedHandler (mouseFlag, point)); + Application.Invoke (() => continuousButtonPressedHandler (mouseFlag, point)); } } } diff --git a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs index b08958bd6..af9df98c1 100644 --- a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs @@ -57,24 +57,28 @@ public class FakeDriver : ConsoleDriver { } } - public override void End () + internal override void End () { FakeConsole.ResetColor (); FakeConsole.Clear (); } - public override void Init (Action terminalResized) + FakeMainLoop _mainLoopDriver = null; + + internal override MainLoop Init () { FakeConsole.MockKeyPresses.Clear (); - TerminalResized = terminalResized; - Cols = FakeConsole.WindowWidth = FakeConsole.BufferWidth = FakeConsole.WIDTH; Rows = FakeConsole.WindowHeight = FakeConsole.BufferHeight = FakeConsole.HEIGHT; FakeConsole.Clear (); ResizeScreen (); CurrentAttribute = new Attribute (Color.White, Color.Black); ClearContents (); + + _mainLoopDriver = new FakeMainLoop (this); + _mainLoopDriver.KeyPressed = ProcessInput; + return new MainLoop (_mainLoopDriver); } @@ -341,20 +345,8 @@ public class FakeDriver : ConsoleDriver { return keyMod != Key.Null ? keyMod | key : key; } - Action _keyDownHandler; - Action _keyHandler; - Action _keyUpHandler; private CursorVisibility _savedCursorVisibility; - public override void PrepareToRun (MainLoop mainLoop, Action keyHandler, Action keyDownHandler, Action keyUpHandler, Action mouseHandler) - { - _keyDownHandler = keyDownHandler; - _keyHandler = keyHandler; - _keyUpHandler = keyUpHandler; - - // Note: Net doesn't support keydown/up events and thus any passed keyDown/UpHandlers will never be called - (mainLoop.MainLoopDriver as FakeMainLoop).KeyPressed = (consoleKey) => ProcessInput (consoleKey); - } void ProcessInput (ConsoleKeyInfo consoleKey) { @@ -374,15 +366,15 @@ public class FakeDriver : ConsoleDriver { var map = MapKey (consoleKey); if (map == (Key)0xffffffff) { if ((consoleKey.Modifiers & (ConsoleModifiers.Shift | ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) { - _keyDownHandler (new KeyEvent (map, keyModifiers)); - _keyUpHandler (new KeyEvent (map, keyModifiers)); + OnKeyDown(new KeyEventEventArgs(new KeyEvent (map, keyModifiers))); + OnKeyUp (new KeyEventEventArgs (new KeyEvent (map, keyModifiers))); } return; } - _keyDownHandler (new KeyEvent (map, keyModifiers)); - _keyHandler (new KeyEvent (map, keyModifiers)); - _keyUpHandler (new KeyEvent (map, keyModifiers)); + OnKeyDown (new KeyEventEventArgs (new KeyEvent (map, keyModifiers))); + OnKeyUp (new KeyEventEventArgs (new KeyEvent (map, keyModifiers))); + OnKeyPressed (new KeyEventEventArgs (new KeyEvent (map, keyModifiers))); } /// @@ -454,7 +446,7 @@ public class FakeDriver : ConsoleDriver { { ResizeScreen (); ClearContents (); - TerminalResized?.Invoke (); + OnSizeChanged (new SizeChangedEventArgs (new Size (Cols, Rows))); } public virtual void ResizeScreen () diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver.cs index 2a9019b27..b442f83fa 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver.cs @@ -628,32 +628,14 @@ internal class NetDriver : ConsoleDriver { const int COLOR_BRIGHT_CYAN = 96; const int COLOR_BRIGHT_WHITE = 97; + NetMainLoop _mainLoopDriver = null; + public override bool SupportsTrueColor => Environment.OSVersion.Platform == PlatformID.Unix || (IsWinPlatform && Environment.OSVersion.Version.Build >= 14931); public NetWinVTConsole NetWinConsole { get; private set; } public bool IsWinPlatform { get; private set; } - public override void End () - { - if (IsWinPlatform) { - NetWinConsole?.Cleanup (); - } - - StopReportingMouseMoves (); - - if (!RunningUnitTests) { - Console.ResetColor (); - - //Disable alternative screen buffer. - Console.Out.Write (EscSeqUtils.CSI_RestoreCursorAndActivateAltBufferWithBackscroll); - - //Set cursor key to cursor. - Console.Out.Write (EscSeqUtils.CSI_ShowCursor); - Console.Out.Close (); - } - } - - public override void Init (Action terminalResized) + internal override MainLoop Init () { var p = Environment.OSVersion.Platform; if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows) { @@ -676,9 +658,6 @@ internal class NetDriver : ConsoleDriver { } } - TerminalResized = terminalResized; - - if (!RunningUnitTests) { Console.TreatControlCAsInput = true; @@ -703,6 +682,30 @@ internal class NetDriver : ConsoleDriver { CurrentAttribute = new Attribute (Color.White, Color.Black); StartReportingMouseMoves (); + + _mainLoopDriver = new NetMainLoop (this); + _mainLoopDriver.ProcessInput = ProcessInput; + return new MainLoop (_mainLoopDriver); + } + + internal override void End () + { + if (IsWinPlatform) { + NetWinConsole?.Cleanup (); + } + + StopReportingMouseMoves (); + + if (!RunningUnitTests) { + Console.ResetColor (); + + //Disable alternative screen buffer. + Console.Out.Write (EscSeqUtils.CSI_RestoreCursorAndRestoreAltBufferWithBackscroll); + + //Set cursor key to cursor. + Console.Out.Write (EscSeqUtils.CSI_ShowCursor); + Console.Out.Close (); + } } public virtual void ResizeScreen () @@ -1106,25 +1109,6 @@ internal class NetDriver : ConsoleDriver { return keyMod != Key.Null ? keyMod | key : key; } - Action _keyHandler; - Action _keyDownHandler; - Action _keyUpHandler; - Action _mouseHandler; - NetMainLoop _mainLoop; - - public override void PrepareToRun (MainLoop mainLoop, Action keyHandler, Action keyDownHandler, Action keyUpHandler, Action mouseHandler) - { - _keyHandler = keyHandler; - _keyDownHandler = keyDownHandler; - _keyUpHandler = keyUpHandler; - _mouseHandler = mouseHandler; - - _mainLoop = mainLoop.MainLoopDriver as NetMainLoop; - - // Note: .Net API doesn't support keydown/up events and thus any passed keyDown/UpHandlers will be simulated to be called. - _mainLoop.ProcessInput = ProcessInput; - } - volatile bool _winSizeChanging; void ProcessInput (NetEvents.InputResult inputEvent) @@ -1141,16 +1125,16 @@ internal class NetDriver : ConsoleDriver { return; } if (map == Key.Null) { - _keyDownHandler (new KeyEvent (map, _keyModifiers)); - _keyUpHandler (new KeyEvent (map, _keyModifiers)); + OnKeyDown (new KeyEventEventArgs (new KeyEvent (map, _keyModifiers))); + OnKeyUp (new KeyEventEventArgs (new KeyEvent (map, _keyModifiers))); } else { - _keyDownHandler (new KeyEvent (map, _keyModifiers)); - _keyHandler (new KeyEvent (map, _keyModifiers)); - _keyUpHandler (new KeyEvent (map, _keyModifiers)); + OnKeyDown (new KeyEventEventArgs (new KeyEvent (map, _keyModifiers))); + OnKeyUp (new KeyEventEventArgs (new KeyEvent (map, _keyModifiers))); + OnKeyPressed (new KeyEventEventArgs (new KeyEvent (map, _keyModifiers))); } break; case NetEvents.EventType.Mouse: - _mouseHandler (ToDriverMouse (inputEvent.MouseEvent)); + OnMouseEvent (new MouseEventEventArgs (ToDriverMouse (inputEvent.MouseEvent))); break; case NetEvents.EventType.WindowSize: _winSizeChanging = true; @@ -1161,7 +1145,7 @@ internal class NetDriver : ConsoleDriver { ResizeScreen (); ClearContents (); _winSizeChanging = false; - TerminalResized?.Invoke (); + OnSizeChanged (new SizeChangedEventArgs (new Size (Cols, Rows))); break; case NetEvents.EventType.RequestResponse: // BUGBUG: What is this for? It does not seem to be used anywhere. diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs index 18e6492b9..b86429954 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs @@ -18,12 +18,10 @@ using System.Text; using System; using System.Collections.Generic; using System.ComponentModel; -using System.Linq; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using System.Diagnostics; -using System.Management; namespace Terminal.Gui; @@ -780,18 +778,13 @@ internal class WindowsConsole { internal class WindowsDriver : ConsoleDriver { WindowsConsole.ExtendedCharInfo [] _outputBuffer; WindowsConsole.SmallRect _damageRegion; - Action _keyHandler; - Action _keyDownHandler; - Action _keyUpHandler; - Action _mouseHandler; public WindowsConsole WinConsole { get; private set; } - public override bool SupportsTrueColor => RunningUnitTests || (Environment.OSVersion.Version.Build >= 14931 - && (_isWindowsTerminal || _parentProcessName == "devenv")); + public override bool SupportsTrueColor => RunningUnitTests || (Environment.OSVersion.Version.Build >= 14931 && _isWindowsTerminal); readonly bool _isWindowsTerminal = false; - readonly string _parentProcessName = "WindowsTerminal"; + WindowsMainLoop _mainLoopDriver = null; public WindowsDriver () { @@ -803,68 +796,59 @@ internal class WindowsDriver : ConsoleDriver { Clipboard = new FakeDriver.FakeClipboard (); } - if (!RunningUnitTests) { - _parentProcessName = GetParentProcessName (); - _isWindowsTerminal = _parentProcessName == "WindowsTerminal"; - if (!_isWindowsTerminal && _parentProcessName != "devenv") { - Force16Colors = true; - } + // TODO: if some other Windows-based terminal supports true color, update this logic to not + // force 16color mode (.e.g ConEmu which really doesn't work well at all). + _isWindowsTerminal = Environment.GetEnvironmentVariable ("WT_SESSION") != null; + if (!_isWindowsTerminal) { + Force16Colors = true; } } - private static string GetParentProcessName () + internal override MainLoop Init () { -#pragma warning disable CA1416 // Validate platform compatibility - var myId = Process.GetCurrentProcess ().Id; - var query = string.Format ($"SELECT ParentProcessId FROM Win32_Process WHERE ProcessId = {myId}"); - var search = new ManagementObjectSearcher ("root\\CIMV2", query); - var queryObj = search.Get ().OfType ().FirstOrDefault (); - if (queryObj == null) { - return null; - } - var parentId = (uint)queryObj ["ParentProcessId"]; - var parent = Process.GetProcessById ((int)parentId); - var prevParent = parent; - - // Check if the parent is from other parent - while (queryObj != null) { - query = string.Format ($"SELECT ParentProcessId FROM Win32_Process WHERE ProcessId = {parentId}"); - search = new ManagementObjectSearcher ("root\\CIMV2", query); - queryObj = search.Get ().OfType ().FirstOrDefault (); - if (queryObj == null) { - return parent.ProcessName; - } - parentId = (uint)queryObj ["ParentProcessId"]; - try { - parent = Process.GetProcessById ((int)parentId); - if (string.Equals (parent.ProcessName, "explorer", StringComparison.InvariantCultureIgnoreCase)) { - return prevParent.ProcessName; - } - prevParent = parent; - } catch (ArgumentException) { - - return prevParent.ProcessName; - } + _mainLoopDriver = new WindowsMainLoop (this); + if (RunningUnitTests) { + return new MainLoop (_mainLoopDriver); } - return parent.ProcessName; -#pragma warning restore CA1416 // Validate platform compatibility - } + try { + if (WinConsole != null) { + // BUGBUG: The results from GetConsoleOutputWindow are incorrect when called from Init. + // Our thread in WindowsMainLoop.CheckWin will get the correct results. See #if HACK_CHECK_WINCHANGED + var winSize = WinConsole.GetConsoleOutputWindow (out Point pos); + Cols = winSize.Width; + Rows = winSize.Height; + } + WindowsConsole.SmallRect.MakeEmpty (ref _damageRegion); - WindowsMainLoop _mainLoop; + if (_isWindowsTerminal) { + Console.Out.Write (EscSeqUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll); + } + } catch (Win32Exception e) { + // We are being run in an environment that does not support a console + // such as a unit test, or a pipe. + Debug.WriteLine ($"Likely running unit tests. Setting WinConsole to null so we can test it elsewhere. Exception: {e}"); + WinConsole = null; + } - public override void PrepareToRun (MainLoop mainLoop, Action keyHandler, Action keyDownHandler, Action keyUpHandler, Action mouseHandler) - { - _keyHandler = keyHandler; - _keyDownHandler = keyDownHandler; - _keyUpHandler = keyUpHandler; - _mouseHandler = mouseHandler; + CurrentAttribute = new Attribute (Color.White, Color.Black); + + _outputBuffer = new WindowsConsole.ExtendedCharInfo [Rows * Cols]; + Clip = new Rect (0, 0, Cols, Rows); + _damageRegion = new WindowsConsole.SmallRect () { + Top = 0, + Left = 0, + Bottom = (short)Rows, + Right = (short)Cols + }; + + ClearContents (); - _mainLoop = mainLoop.MainLoopDriver as WindowsMainLoop; - _mainLoop.ProcessInput = ProcessInput; #if HACK_CHECK_WINCHANGED - _mainLoop.WinChanged = ChangeWin; + _mainLoopDriver.WinChanged = ChangeWin; #endif + return new MainLoop (_mainLoopDriver); + } #if HACK_CHECK_WINCHANGED @@ -889,11 +873,20 @@ internal class WindowsDriver : ConsoleDriver { ResizeScreen (); ClearContents (); - TerminalResized.Invoke (); + OnSizeChanged (new SizeChangedEventArgs (new Size (Cols, Rows))); } #endif - void ProcessInput (WindowsConsole.InputRecord inputEvent) + // This is a bit hacky, but it enables users to hold down a key and + // OnKeyDown, OnKeyPressed, OnKeyPressed, OnKeyUp + // It might be worth making OnKeyDown and OnKeyUp virtual so this can be tracked from those calls in case + // somoene calls them externally?? + // + // It also is broken when modifiers keys are down too + // + //Key _keyDown = (Key)0xffffffff; + + internal void ProcessInput (WindowsConsole.InputRecord inputEvent) { switch (inputEvent.EventType) { case WindowsConsole.EventType.Key: @@ -971,19 +964,26 @@ internal class WindowsDriver : ConsoleDriver { } if (inputEvent.KeyEvent.bKeyDown) { - _keyDownHandler (key); + //_keyDown = key.Key; + OnKeyDown (new KeyEventEventArgs (key)); } else { - _keyUpHandler (key); + //_keyDown = (Key)0xffffffff; + OnKeyUp (new KeyEventEventArgs (key)); } } else { if (inputEvent.KeyEvent.bKeyDown) { // May occurs using SendKeys _keyModifiers ??= new KeyModifiers (); - // Key Down - Fire KeyDown Event and KeyStroke (ProcessKey) Event - _keyDownHandler (new KeyEvent (map, _keyModifiers)); - _keyHandler (new KeyEvent (map, _keyModifiers)); + + //if (_keyDown == (Key)0xffffffff) { + // Avoid sending repeat keydowns + // _keyDown = map; + OnKeyDown (new KeyEventEventArgs (new KeyEvent (map, _keyModifiers))); + //} + OnKeyPressed (new KeyEventEventArgs (new KeyEvent (map, _keyModifiers))); } else { - _keyUpHandler (new KeyEvent (map, _keyModifiers)); + //_keyDown = (Key)0xffffffff; + OnKeyUp (new KeyEventEventArgs (new KeyEvent (map, _keyModifiers))); } } if (!inputEvent.KeyEvent.bKeyDown && inputEvent.KeyEvent.dwControlKeyState == 0) { @@ -993,14 +993,13 @@ internal class WindowsDriver : ConsoleDriver { case WindowsConsole.EventType.Mouse: var me = ToDriverMouse (inputEvent.MouseEvent); - _mouseHandler (me); + OnMouseEvent (new MouseEventEventArgs (me)); if (_processButtonClick) { - _mouseHandler ( - new MouseEvent () { - X = me.X, - Y = me.Y, - Flags = ProcessButtonClick (inputEvent.MouseEvent) - }); + OnMouseEvent (new MouseEventEventArgs (new MouseEvent () { + X = me.X, + Y = me.Y, + Flags = ProcessButtonClick (inputEvent.MouseEvent) + })); } break; @@ -1258,7 +1257,7 @@ internal class WindowsDriver : ConsoleDriver { break; } if (_isButtonPressed && (mouseFlag & MouseFlags.ReportMousePosition) == 0) { - Application.MainLoop.Invoke (() => _mouseHandler (me)); + Application.Invoke (() => OnMouseEvent (new MouseEventEventArgs (me))); } } } @@ -1505,57 +1504,6 @@ internal class WindowsDriver : ConsoleDriver { return base.IsRuneSupported (rune) && rune.IsBmp; } - public override void Init (Action terminalResized) - { - TerminalResized = terminalResized; - - if (RunningUnitTests) { - return; - } - - try { - if (WinConsole != null) { - // BUGBUG: The results from GetConsoleOutputWindow are incorrect when called from Init. - // Our thread in WindowsMainLoop.CheckWin will get the correct results. See #if HACK_CHECK_WINCHANGED - var winSize = WinConsole.GetConsoleOutputWindow (out Point pos); - Cols = winSize.Width; - Rows = winSize.Height; - } - WindowsConsole.SmallRect.MakeEmpty (ref _damageRegion); - - // Needed for Windows Terminal - // ESC [ ? 1047 h Save cursor position and activate xterm alternative buffer (no backscroll) - // ESC [ ? 1047 l Restore cursor position and restore xterm working buffer (with backscroll) - // ESC [ ? 1048 h Save cursor position - // ESC [ ? 1048 l Restore cursor position - // ESC [ ? 1049 h Activate xterm alternative buffer (no backscroll) - // ESC [ ? 1049 l Restore xterm working buffer (with backscroll) - // Per Issue #2264 using the alternative screen buffer is required for Windows Terminal to not - // wipe out the backscroll buffer when the application exits. - if (_isWindowsTerminal) { - Console.Out.Write (EscSeqUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll); - } - } catch (Win32Exception e) { - // We are being run in an environment that does not support a console - // such as a unit test, or a pipe. - Debug.WriteLine ($"Likely running unit tests. Setting WinConsole to null so we can test it elsewhere. Exception: {e}"); - WinConsole = null; - } - - CurrentAttribute = new Attribute (Color.White, Color.Black); - - _outputBuffer = new WindowsConsole.ExtendedCharInfo [Rows * Cols]; - Clip = new Rect (0, 0, Cols, Rows); - _damageRegion = new WindowsConsole.SmallRect () { - Top = 0, - Left = 0, - Bottom = (short)Rows, - Right = (short)Cols - }; - - ClearContents (); - } - void ResizeScreen () { _outputBuffer = new WindowsConsole.ExtendedCharInfo [Rows * Cols]; @@ -1573,7 +1521,7 @@ internal class WindowsDriver : ConsoleDriver { public override void UpdateScreen () { - var windowSize = WinConsole?.GetConsoleBufferWindow (out _) ?? new Size (Cols, Rows); + var windowSize = WinConsole?.GetConsoleBufferWindow (out var _) ?? new Size (Cols, Rows); if (!windowSize.IsEmpty && (windowSize.Width != Cols || windowSize.Height != Rows)) { return; } @@ -1637,25 +1585,6 @@ internal class WindowsDriver : ConsoleDriver { UpdateCursor (); } - #region Color Handling - - /// - /// In the WindowsDriver, colors are encoded as an int. - /// The background color is stored in the least significant 4 bits, - /// and the foreground color is stored in the next 4 bits. - /// - public override Attribute MakeColor (Color foreground, Color background) - { - // Encode the colors into the int value. - return new Attribute ( - platformColor: 0, // Not used anymore! (((int)foreground.ColorName) | ((int)background.ColorName << 4)), - foreground: foreground, - background: background - ); - } - - #endregion - CursorVisibility _cachedCursorVisibility; public override void UpdateCursor () @@ -1751,22 +1680,21 @@ internal class WindowsDriver : ConsoleDriver { } } - public override void End () + internal override void End () { - if (_mainLoop != null) { - _mainLoop.ProcessInput -= ProcessInput; + if (_mainLoopDriver != null) { #if HACK_CHECK_WINCHANGED //_mainLoop.WinChanged -= ChangeWin; #endif } - _mainLoop = null; + _mainLoopDriver = null; WinConsole?.Cleanup (); WinConsole = null; - if (!RunningUnitTests && (_isWindowsTerminal || _parentProcessName == "devenv")) { + if (!RunningUnitTests && _isWindowsTerminal) { // Disable alternative screen buffer. - Console.Out.Write (EscSeqUtils.CSI_RestoreCursorAndActivateAltBufferWithBackscroll); + Console.Out.Write (EscSeqUtils.CSI_RestoreCursorAndRestoreAltBufferWithBackscroll); } } @@ -1797,11 +1725,6 @@ internal class WindowsMainLoop : IMainLoopDriver { // The records that we keep fetching readonly Queue _resultQueue = new Queue (); - /// - /// Invoked when a Key is pressed or released. - /// - public Action ProcessInput; - /// /// Invoked when the window is changed. /// @@ -1879,7 +1802,7 @@ internal class WindowsMainLoop : IMainLoopDriver { bool IMainLoopDriver.EventsPending () { _waitForProbe.Set (); -#if HACK_CHECK_WINCHANGED +#if HACK_CHECK_WINCHANGED _winChange.Set (); #endif if (_mainLoop.CheckTimersAndIdleHandlers (out var waitTimeout)) { @@ -1916,8 +1839,7 @@ internal class WindowsMainLoop : IMainLoopDriver { while (_resultQueue.Count > 0) { var inputRecords = _resultQueue.Dequeue (); if (inputRecords is { Length: > 0 }) { - var inputEvent = inputRecords [0]; - ProcessInput?.Invoke (inputEvent); + ((WindowsDriver)_consoleDriver).ProcessInput (inputRecords [0]); } } #if HACK_CHECK_WINCHANGED diff --git a/Terminal.Gui/Drawing/Color.cs b/Terminal.Gui/Drawing/Color.cs index e2f40d218..a1ce645cb 100644 --- a/Terminal.Gui/Drawing/Color.cs +++ b/Terminal.Gui/Drawing/Color.cs @@ -706,12 +706,13 @@ public readonly struct Attribute : IEquatable { Foreground = foreground; Background = background; + // TODO: Once CursesDriver supports truecolor all the PlatformColor stuff goes away if (Application.Driver == null) { PlatformColor = -1; return; } - var make = Application.Driver.MakeAttribute (foreground, background); + var make = Application.Driver.MakeColor (foreground, background); PlatformColor = make.PlatformColor; } diff --git a/Terminal.Gui/Input/Event.cs b/Terminal.Gui/Input/Event.cs index a46bbf7e5..995e463cf 100644 --- a/Terminal.Gui/Input/Event.cs +++ b/Terminal.Gui/Input/Event.cs @@ -766,12 +766,14 @@ namespace Terminal.Gui { AllEvents = unchecked((int)0x7ffffff), } + // TODO: Merge MouseEvent and MouseEventEventArgs into a single class. + /// /// Low-level construct that conveys the details of mouse events, such /// as coordinates and button state, from ConsoleDrivers up to and /// Views. /// - /// The class includes the + /// The class includes the /// Action which takes a MouseEvent argument. public class MouseEvent { /// diff --git a/Terminal.Gui/Input/MouseEventEventArgs.cs b/Terminal.Gui/Input/MouseEventEventArgs.cs index 4f5827465..5df8b4b00 100644 --- a/Terminal.Gui/Input/MouseEventEventArgs.cs +++ b/Terminal.Gui/Input/MouseEventEventArgs.cs @@ -12,6 +12,8 @@ namespace Terminal.Gui { /// /// The mouse event. public MouseEventEventArgs (MouseEvent me) => MouseEvent = me; + + // TODO: Merge MouseEvent and MouseEventEventArgs into a single class. /// /// The for the event. /// diff --git a/Terminal.Gui/MainLoop.cs b/Terminal.Gui/MainLoop.cs index c210bc507..78275887f 100644 --- a/Terminal.Gui/MainLoop.cs +++ b/Terminal.Gui/MainLoop.cs @@ -12,7 +12,7 @@ namespace Terminal.Gui { /// /// Public interface to create a platform specific driver. /// - public interface IMainLoopDriver { + internal interface IMainLoopDriver { /// /// Initializes the , gets the calling main loop for the initialization. /// @@ -44,6 +44,7 @@ namespace Terminal.Gui { void TearDown (); } + /// /// The MainLoop monitors timers and idle handlers. /// @@ -51,7 +52,7 @@ namespace Terminal.Gui { /// Monitoring of file descriptors is only available on Unix, there /// does not seem to be a way of supporting this on Windows. /// - public class MainLoop : IDisposable { + internal class MainLoop : IDisposable { internal SortedList _timeouts = new SortedList (); readonly object _timeoutsLockToken = new object (); @@ -67,12 +68,12 @@ namespace Terminal.Gui { /// A shorter limit time can be added at the end, but it will be called before an /// earlier addition that has a longer limit time. /// - public SortedList Timeouts => _timeouts; + internal SortedList Timeouts => _timeouts; /// /// Gets a copy of the list of all idle handlers. /// - public ReadOnlyCollection> IdleHandlers { + internal ReadOnlyCollection> IdleHandlers { get { lock (_idleHandlersLock) { return new List> (_idleHandlers).AsReadOnly (); @@ -84,13 +85,13 @@ namespace Terminal.Gui { /// The current in use. /// /// The main loop driver. - public IMainLoopDriver MainLoopDriver { get; private set; } + internal IMainLoopDriver MainLoopDriver { get; private set; } /// /// Invoked when a new timeout is added. To be used in the case - /// when is . + /// when is . /// - public event EventHandler TimeoutAdded; + internal event EventHandler TimeoutAdded; /// /// Creates a new MainLoop. @@ -100,24 +101,12 @@ namespace Terminal.Gui { /// /// The instance /// (one of the implementations FakeMainLoop, UnixMainLoop, NetMainLoop or WindowsMainLoop). - public MainLoop (IMainLoopDriver driver) + internal MainLoop (IMainLoopDriver driver) { MainLoopDriver = driver; driver.Setup (this); } - /// - /// Runs on the thread that is processing events - /// - /// the action to be invoked on the main processing thread. - public void Invoke (Action action) - { - AddIdle (() => { - action (); - return false; - }); - } - /// /// 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. @@ -131,7 +120,7 @@ namespace Terminal.Gui { /// /// /// Token that can be used to remove the idle handler with . - public Func AddIdle (Func idleHandler) + internal Func AddIdle (Func idleHandler) { lock (_idleHandlersLock) { _idleHandlers.Add (idleHandler); @@ -147,7 +136,7 @@ namespace Terminal.Gui { /// A token returned by /// Returns trueif the idle handler is successfully removed; otherwise, false. /// This method also returns false if the idle handler is not found. - public bool RemoveIdle (Func token) + internal bool RemoveIdle (Func token) { lock (_idleHandlersLock) { return _idleHandlers.Remove (token); @@ -174,7 +163,7 @@ namespace Terminal.Gui { /// The returned value is a token that can be used to stop the timeout /// by calling . /// - public object AddTimeout (TimeSpan time, Func callback) + internal object AddTimeout (TimeSpan time, Func callback) { if (callback == null) { throw new ArgumentNullException (nameof (callback)); @@ -195,7 +184,7 @@ namespace Terminal.Gui { /// /// Returns trueif the timeout is successfully removed; otherwise, false. /// This method also returns false if the timeout is not found. - public bool RemoveTimeout (object token) + internal bool RemoveTimeout (object token) { lock (_timeoutsLockToken) { var idx = _timeouts.IndexOfValue (token as Timeout); @@ -223,7 +212,7 @@ namespace Terminal.Gui { foreach ((var k, var timeout) in copy) { if (k < now) { - if (timeout.Callback (this)) { + if (timeout.Callback ()) { AddTimeout (timeout.Span, timeout); } } else { @@ -240,7 +229,7 @@ namespace Terminal.Gui { /// Returns the number of milliseconds remaining in the current timer (if any). Will be -1 if /// there are no active timers. /// if there is a timer or idle handler active. - public bool CheckTimersAndIdleHandlers (out int waitTimeout) + internal bool CheckTimersAndIdleHandlers (out int waitTimeout) { var now = DateTime.UtcNow.Ticks; @@ -275,7 +264,7 @@ namespace Terminal.Gui { /// /// /// - private long NudgeToUniqueKey (long k) + long NudgeToUniqueKey (long k) { lock (_timeoutsLockToken) { while (_timeouts.ContainsKey (k)) { @@ -303,7 +292,10 @@ namespace Terminal.Gui { } } - bool _running; + /// + /// Used for unit tests. + /// + internal bool Running { get; private set; } /// /// Determines whether there are pending events to be processed. @@ -313,7 +305,7 @@ namespace Terminal.Gui { /// Typically used if you need to flush the input queue while still /// running some of your own code in your main thread. /// - public bool EventsPending () + internal bool EventsPending () { return MainLoopDriver.EventsPending (); } @@ -328,14 +320,14 @@ namespace Terminal.Gui { /// while (main.EventsPending ()) RunIteration (); /// /// - public void RunIteration () + internal void RunIteration () { lock (_timeouts) { if (_timeouts.Count > 0) { RunTimers (); } } - + MainLoopDriver.Iteration (); var runIdle = false; @@ -349,34 +341,39 @@ namespace Terminal.Gui { } /// - /// Runs the . + /// Runs the . Used only for unit tests. /// - public void Run () + internal void Run () { - var prev = _running; - _running = true; - while (_running) { + var prev = Running; + Running = true; + while (Running) { EventsPending (); RunIteration (); } - _running = prev; + Running = prev; } /// - /// Stops the main loop driver and calls . + /// Wakes up the that might be waiting on input. /// - public void Stop () + internal void Wakeup () => MainLoopDriver?.Wakeup (); + + /// + /// Stops the main loop driver and calls . Used only for unit tests. + /// + internal void Stop () { - _running = false; - MainLoopDriver.Wakeup (); + Running = false; + Wakeup (); } - + /// public void Dispose () { GC.SuppressFinalize (this); Stop (); - _running = false; + Running = false; MainLoopDriver?.TearDown (); MainLoopDriver = null; } diff --git a/Terminal.Gui/Timeout.cs b/Terminal.Gui/Timeout.cs index 4273e034a..cd26ad973 100644 --- a/Terminal.Gui/Timeout.cs +++ b/Terminal.Gui/Timeout.cs @@ -19,5 +19,5 @@ public sealed class Timeout { /// /// The function that will be invoked. /// - public Func Callback; + public Func Callback; } diff --git a/Terminal.Gui/TimeoutEventArgs.cs b/Terminal.Gui/TimeoutEventArgs.cs index 876dde678..80f9d8357 100644 --- a/Terminal.Gui/TimeoutEventArgs.cs +++ b/Terminal.Gui/TimeoutEventArgs.cs @@ -5,7 +5,7 @@ namespace Terminal.Gui { /// /// for timeout events (e.g. ) /// - public class TimeoutEventArgs : EventArgs { + internal class TimeoutEventArgs : EventArgs { /// /// Gets the timeout callback handler /// diff --git a/Terminal.Gui/View/Layout/ResizedEventArgs.cs b/Terminal.Gui/View/Layout/ResizedEventArgs.cs deleted file mode 100644 index cf3505840..000000000 --- a/Terminal.Gui/View/Layout/ResizedEventArgs.cs +++ /dev/null @@ -1,32 +0,0 @@ -// -// Core.cs: The core engine for gui.cs -// -// Authors: -// Miguel de Icaza (miguel@gnome.org) -// -// Pending: -// - Check for NeedDisplay on the hierarchy and repaint -// - Layout support -// - "Colors" type or "Attributes" type? -// - What to surface as "BackgroundCOlor" when clearing a window, an attribute or colors? -// -// Optimizations -// - Add rendering limitation to the exposed area -using System; - -namespace Terminal.Gui { - - /// - /// Event arguments for the event. - /// - public class ResizedEventArgs : EventArgs { - /// - /// The number of rows in the resized terminal. - /// - public int Rows { get; set; } - /// - /// The number of columns in the resized terminal. - /// - public int Cols { get; set; } - } -} diff --git a/Terminal.Gui/View/Layout/SizeChangedEventArgs.cs b/Terminal.Gui/View/Layout/SizeChangedEventArgs.cs index 66c52bf3a..adde33a15 100644 --- a/Terminal.Gui/View/Layout/SizeChangedEventArgs.cs +++ b/Terminal.Gui/View/Layout/SizeChangedEventArgs.cs @@ -22,5 +22,10 @@ namespace Terminal.Gui { /// resolved. /// public Size Size { get; } + + /// + /// Set to to cause the resize to be cancelled, if appropriate. + /// + public bool Cancel { get; set; } } } diff --git a/Terminal.Gui/View/ViewKeyboard.cs b/Terminal.Gui/View/ViewKeyboard.cs index 66a2ce491..421f94162 100644 --- a/Terminal.Gui/View/ViewKeyboard.cs +++ b/Terminal.Gui/View/ViewKeyboard.cs @@ -163,7 +163,7 @@ namespace Terminal.Gui { /// /// Invoked when a character key is pressed and occurs after the key up event. /// - public event EventHandler KeyPress; + public event EventHandler KeyPressed; /// public override bool ProcessKey (KeyEvent keyEvent) @@ -173,11 +173,11 @@ namespace Terminal.Gui { } var args = new KeyEventEventArgs (keyEvent); - KeyPress?.Invoke (this, args); + KeyPressed?.Invoke (this, args); if (args.Handled) return true; if (Focused?.Enabled == true) { - Focused?.KeyPress?.Invoke (this, args); + Focused?.KeyPressed?.Invoke (this, args); if (args.Handled) return true; } @@ -348,7 +348,7 @@ namespace Terminal.Gui { var args = new KeyEventEventArgs (keyEvent); if (MostFocused?.Enabled == true) { - MostFocused?.KeyPress?.Invoke (this, args); + MostFocused?.KeyPressed?.Invoke (this, args); if (args.Handled) return true; } @@ -371,11 +371,11 @@ namespace Terminal.Gui { } var args = new KeyEventEventArgs (keyEvent); - KeyPress?.Invoke (this, args); + KeyPressed?.Invoke (this, args); if (args.Handled) return true; if (MostFocused?.Enabled == true) { - MostFocused?.KeyPress?.Invoke (this, args); + MostFocused?.KeyPressed?.Invoke (this, args); if (args.Handled) return true; } diff --git a/Terminal.Gui/View/ViewLayout.cs b/Terminal.Gui/View/ViewLayout.cs index 3b720339c..48b5434cd 100644 --- a/Terminal.Gui/View/ViewLayout.cs +++ b/Terminal.Gui/View/ViewLayout.cs @@ -431,7 +431,7 @@ namespace Terminal.Gui { /// /// Called whenever the view needs to be resized. Sets and - /// triggers a call. /// + /// triggers a call. /// /// /// Can be overridden if the view resize behavior is different than the default. diff --git a/Terminal.Gui/Views/Button.cs b/Terminal.Gui/Views/Button.cs index 7af58684f..5cd1605dd 100644 --- a/Terminal.Gui/Views/Button.cs +++ b/Terminal.Gui/Views/Button.cs @@ -10,11 +10,11 @@ using System.Text; namespace Terminal.Gui { /// - /// Button is a that provides an item that invokes an when activated by the user. + /// Button is a that provides an item that invokes raises the event. /// /// /// - /// Provides a button showing text invokes an when clicked on with a mouse + /// Provides a button showing text that raises the event when clicked on with a mouse /// or when the user presses SPACE, ENTER, or hotkey. The hotkey is the first letter or digit following the first underscore ('_') /// in the button text. /// @@ -27,7 +27,7 @@ namespace Terminal.Gui { /// /// When the button is configured as the default () and the user presses /// the ENTER key, if no other processes the , the 's - /// will be invoked. + /// event will will be fired. /// /// public class Button : View { @@ -258,7 +258,7 @@ namespace Terminal.Gui { } /// - /// Clicked , raised when the user clicks the primary mouse button within the Bounds of this + /// The event fired when the user clicks the primary mouse button within the Bounds of this /// or if the user presses the action key while this view is focused. (TODO: IsDefault) /// /// diff --git a/Terminal.Gui/Views/FileDialog.cs b/Terminal.Gui/Views/FileDialog.cs index 11832d4ec..725bbfa86 100644 --- a/Terminal.Gui/Views/FileDialog.cs +++ b/Terminal.Gui/Views/FileDialog.cs @@ -145,7 +145,7 @@ namespace Terminal.Gui { X = Pos.Function (CalculateOkButtonPosX) }; this.btnOk.Clicked += (s, e) => this.Accept (true); - this.btnOk.KeyPress += (s, k) => { + this.btnOk.KeyPressed += (s, k) => { this.NavigateIf (k, Key.CursorLeft, this.btnCancel); this.NavigateIf (k, Key.CursorUp, this.tableView); }; @@ -154,7 +154,7 @@ namespace Terminal.Gui { Y = Pos.AnchorEnd (1), X = Pos.Right (btnOk) + 1 }; - this.btnCancel.KeyPress += (s, k) => { + this.btnCancel.KeyPressed += (s, k) => { this.NavigateIf (k, Key.CursorLeft, this.btnToggleSplitterCollapse); this.NavigateIf (k, Key.CursorUp, this.tableView); this.NavigateIf (k, Key.CursorRight, this.btnOk); @@ -179,7 +179,7 @@ namespace Terminal.Gui { Width = Dim.Fill (0), CaptionColor = new Color (Color.Black) }; - this.tbPath.KeyPress += (s, k) => { + this.tbPath.KeyPressed += (s, k) => { ClearFeedback (); @@ -228,7 +228,7 @@ namespace Terminal.Gui { typeStyle.MinWidth = 6; typeStyle.ColorGetter = this.ColorGetter; - this.tableView.KeyPress += (s, k) => { + this.tableView.KeyPressed += (s, k) => { if (this.tableView.SelectedRow <= 0) { this.NavigateIf (k, Key.CursorUp, this.tbPath); } @@ -285,7 +285,7 @@ namespace Terminal.Gui { }; tbFind.TextChanged += (s, o) => RestartSearch (); - tbFind.KeyPress += (s, o) => { + tbFind.KeyPressed += (s, o) => { if (o.KeyEvent.Key == Key.Enter) { RestartSearch (); o.Handled = true; @@ -1438,7 +1438,7 @@ namespace Terminal.Gui { UpdateChildrenToFound (); } - Application.MainLoop.Invoke (() => { + Application.Invoke (() => { Parent.spinnerView.Visible = false; }); } @@ -1450,7 +1450,7 @@ namespace Terminal.Gui { Children = found.ToArray (); } - Application.MainLoop.Invoke (() => { + Application.Invoke (() => { Parent.tbPath.Autocomplete.GenerateSuggestions ( new AutocompleteFilepathContext (Parent.tbPath.Text, Parent.tbPath.CursorPosition, this) ); diff --git a/Terminal.Gui/Views/Label.cs b/Terminal.Gui/Views/Label.cs index 9ea34be11..29b476fb7 100644 --- a/Terminal.Gui/Views/Label.cs +++ b/Terminal.Gui/Views/Label.cs @@ -65,7 +65,7 @@ namespace Terminal.Gui { } /// - /// Clicked , raised when the user clicks the primary mouse button within the Bounds of this + /// The event fired when the user clicks the primary mouse button within the Bounds of this /// or if the user presses the action key while this view is focused. (TODO: IsDefault) /// /// diff --git a/Terminal.Gui/Views/Menu.cs b/Terminal.Gui/Views/Menu.cs index 8b24bd2aa..7c16edc53 100644 --- a/Terminal.Gui/Views/Menu.cs +++ b/Terminal.Gui/Views/Menu.cs @@ -482,9 +482,9 @@ namespace Terminal.Gui { if (Application.Current != null) { Application.Current.DrawContentComplete += Current_DrawContentComplete; - Application.Current.TerminalResized += Current_TerminalResized; + Application.Current.SizeChanging += Current_TerminalResized; } - Application.RootMouseEvent += Application_RootMouseEvent; + Application.MouseEvent += Application_RootMouseEvent; // Things this view knows how to do AddCommand (Command.LineUp, () => MoveUp ()); @@ -523,15 +523,15 @@ namespace Terminal.Gui { { base.OnVisibleChanged (); if (Visible) { - Application.RootMouseEvent += Application_RootMouseEvent; + Application.MouseEvent += Application_RootMouseEvent; } else { - Application.RootMouseEvent -= Application_RootMouseEvent; + Application.MouseEvent -= Application_RootMouseEvent; } } - private void Application_RootMouseEvent (MouseEvent me) + private void Application_RootMouseEvent (object sender, MouseEventEventArgs a) { - if (me.View is MenuBar) { + if (a.MouseEvent.View is MenuBar) { return; } var locationOffset = host.GetScreenOffsetFromCurrent (); @@ -539,7 +539,7 @@ namespace Terminal.Gui { locationOffset.X += SuperView.Border.Thickness.Left; locationOffset.Y += SuperView.Border.Thickness.Top; } - var view = View.FindDeepestView (this, me.X + locationOffset.X, me.Y + locationOffset.Y, out int rx, out int ry); + var view = View.FindDeepestView (this, a.MouseEvent.X + locationOffset.X, a.MouseEvent.Y + locationOffset.Y, out int rx, out int ry); if (view == this) { if (!Visible) { throw new InvalidOperationException ("This shouldn't running on a invisible menu!"); @@ -548,11 +548,11 @@ namespace Terminal.Gui { var nme = new MouseEvent () { X = rx, Y = ry, - Flags = me.Flags, + Flags = a.MouseEvent.Flags, View = view }; - if (MouseEvent (nme) || me.Flags == MouseFlags.Button1Pressed || me.Flags == MouseFlags.Button1Released) { - me.Handled = true; + if (MouseEvent (nme) || a.MouseEvent.Flags == MouseFlags.Button1Pressed || a.MouseEvent.Flags == MouseFlags.Button1Released) { + a.MouseEvent.Handled = true; } } } @@ -985,9 +985,9 @@ namespace Terminal.Gui { { if (Application.Current != null) { Application.Current.DrawContentComplete -= Current_DrawContentComplete; - Application.Current.TerminalResized -= Current_TerminalResized; + Application.Current.SizeChanging -= Current_TerminalResized; } - Application.RootMouseEvent -= Application_RootMouseEvent; + Application.MouseEvent -= Application_RootMouseEvent; base.Dispose (disposing); } } diff --git a/Terminal.Gui/Views/Slider.cs b/Terminal.Gui/Views/Slider.cs index d6d67fc12..a490516c5 100644 --- a/Terminal.Gui/Views/Slider.cs +++ b/Terminal.Gui/Views/Slider.cs @@ -337,6 +337,7 @@ public class Slider : View { /// /// /// if the focus change was cancelled. + /// public virtual bool OnOptionFocused (int newFocusedOption, SliderEventArgs args) { if (newFocusedOption > _options.Count - 1 || newFocusedOption < 0) { diff --git a/Terminal.Gui/Views/SpinnerView/SpinnerView.cs b/Terminal.Gui/Views/SpinnerView/SpinnerView.cs index cf00352ad..ef16d4eb9 100644 --- a/Terminal.Gui/Views/SpinnerView/SpinnerView.cs +++ b/Terminal.Gui/Views/SpinnerView/SpinnerView.cs @@ -153,7 +153,7 @@ namespace Terminal.Gui { /// ignored based on . /// /// Ensure this method is called on the main UI - /// thread e.g. via + /// thread e.g. via /// public void AdvanceAnimation() { @@ -221,9 +221,9 @@ namespace Terminal.Gui { return; } - _timeout = Application.MainLoop.AddTimeout ( - TimeSpan.FromMilliseconds (SpinDelay), (m) => { - Application.MainLoop.Invoke (this.AdvanceAnimation); + _timeout = Application.AddTimeout ( + TimeSpan.FromMilliseconds (SpinDelay), () => { + Application.Invoke (this.AdvanceAnimation); return true; }); } @@ -232,7 +232,7 @@ namespace Terminal.Gui { private void RemoveAutoSpinTimeout () { if (_timeout != null) { - Application.MainLoop.RemoveTimeout (_timeout); + Application.RemoveTimeout (_timeout); _timeout = null; } } diff --git a/Terminal.Gui/Views/TableView/TreeTableSource.cs b/Terminal.Gui/Views/TableView/TreeTableSource.cs index 9a8b08866..0b06691fd 100644 --- a/Terminal.Gui/Views/TableView/TreeTableSource.cs +++ b/Terminal.Gui/Views/TableView/TreeTableSource.cs @@ -38,7 +38,7 @@ public class TreeTableSource : IEnumerableTableSource, IDisposable where T { _tableView = table; _tree = tree; - _tableView.KeyPress += Table_KeyPress; + _tableView.KeyPressed += Table_KeyPress; _tableView.MouseClick += Table_MouseClick; var colList = subsequentColumns.Keys.ToList (); @@ -68,7 +68,7 @@ public class TreeTableSource : IEnumerableTableSource, IDisposable where T /// public void Dispose () { - _tableView.KeyPress -= Table_KeyPress; + _tableView.KeyPressed -= Table_KeyPress; _tableView.MouseClick -= Table_MouseClick; _tree.Dispose (); } diff --git a/Terminal.Gui/Views/Toplevel.cs b/Terminal.Gui/Views/Toplevel.cs index c8b35f80b..4612d84cf 100644 --- a/Terminal.Gui/Views/Toplevel.cs +++ b/Terminal.Gui/Views/Toplevel.cs @@ -15,7 +15,7 @@ namespace Terminal.Gui { /// been called (which sets the property to false). /// /// - /// A Toplevel is created when an application initializes Terminal.Gui by calling . + /// A Toplevel is created when an application initializes Terminal.Gui by calling . /// The application Toplevel can be accessed via . Additional Toplevels can be created /// and run (e.g. s. To run a Toplevel, create the and /// call . @@ -98,27 +98,16 @@ namespace Terminal.Gui { /// /// Invoked when the terminal has been resized. The new of the terminal is provided. /// - public event EventHandler TerminalResized; + public event EventHandler SizeChanging; - internal virtual void OnTerminalResized (SizeChangedEventArgs size) - { - TerminalResized?.Invoke (this, size); - } + // TODO: Make cancelable? + internal virtual void OnSizeChanging (SizeChangedEventArgs size) => SizeChanging?.Invoke (this, size); - internal virtual void OnChildUnloaded (Toplevel top) - { - ChildUnloaded?.Invoke (this, new ToplevelEventArgs (top)); - } + internal virtual void OnChildUnloaded (Toplevel top) => ChildUnloaded?.Invoke (this, new ToplevelEventArgs (top)); - internal virtual void OnChildLoaded (Toplevel top) - { - ChildLoaded?.Invoke (this, new ToplevelEventArgs (top)); - } + internal virtual void OnChildLoaded (Toplevel top) => ChildLoaded?.Invoke (this, new ToplevelEventArgs (top)); - internal virtual void OnClosed (Toplevel top) - { - Closed?.Invoke (this, new ToplevelEventArgs (top)); - } + internal virtual void OnClosed (Toplevel top) => Closed?.Invoke (this, new ToplevelEventArgs (top)); internal virtual bool OnClosing (ToplevelClosingEventArgs ev) { @@ -126,10 +115,7 @@ namespace Terminal.Gui { return ev.Cancel; } - internal virtual void OnAllChildClosed () - { - AllChildClosed?.Invoke (this, EventArgs.Empty); - } + internal virtual void OnAllChildClosed () => AllChildClosed?.Invoke (this, EventArgs.Empty); internal virtual void OnChildClosed (Toplevel top) { diff --git a/Terminal.Gui/Views/ToplevelOverlapped.cs b/Terminal.Gui/Views/ToplevelOverlapped.cs index 1c95d6647..e1e56e4de 100644 --- a/Terminal.Gui/Views/ToplevelOverlapped.cs +++ b/Terminal.Gui/Views/ToplevelOverlapped.cs @@ -30,7 +30,7 @@ namespace Terminal.Gui { get { if (OverlappedTop != null) { List _overlappedChildren = new List (); - foreach (var top in _toplevels) { + foreach (var top in _topLevels) { if (top != OverlappedTop && !top.Modal) { _overlappedChildren.Add (top); } @@ -71,9 +71,9 @@ namespace Terminal.Gui { return null; } - int count = _toplevels.Count; + int count = _topLevels.Count; for (int i = count - 1; i >= 0; i--) { - foreach (var top in _toplevels) { + foreach (var top in _topLevels) { var rx = x - startFrame.X; var ry = y - startFrame.Y; if (top.Visible && top.Frame.Contains (rx, ry)) { @@ -96,7 +96,7 @@ namespace Terminal.Gui { return false; } - foreach (var top in _toplevels) { + foreach (var top in _topLevels) { if (top != Current && top.Visible && (top.NeedsDisplay || top.SubViewNeedsDisplay || top.LayoutNeeded)) { OverlappedTop.SetSubViewNeedsDisplay (); return true; @@ -124,19 +124,19 @@ namespace Terminal.Gui { public static void OverlappedMoveNext () { if (OverlappedTop != null && !Current.Modal) { - lock (_toplevels) { - _toplevels.MoveNext (); + lock (_topLevels) { + _topLevels.MoveNext (); var isOverlapped = false; - while (_toplevels.Peek () == OverlappedTop || !_toplevels.Peek ().Visible) { - if (!isOverlapped && _toplevels.Peek () == OverlappedTop) { + while (_topLevels.Peek () == OverlappedTop || !_topLevels.Peek ().Visible) { + if (!isOverlapped && _topLevels.Peek () == OverlappedTop) { isOverlapped = true; - } else if (isOverlapped && _toplevels.Peek () == OverlappedTop) { + } else if (isOverlapped && _topLevels.Peek () == OverlappedTop) { MoveCurrent (Top); break; } - _toplevels.MoveNext (); + _topLevels.MoveNext (); } - Current = _toplevels.Peek (); + Current = _topLevels.Peek (); } } } @@ -147,19 +147,19 @@ namespace Terminal.Gui { public static void OverlappedMovePrevious () { if (OverlappedTop != null && !Current.Modal) { - lock (_toplevels) { - _toplevels.MovePrevious (); + lock (_topLevels) { + _topLevels.MovePrevious (); var isOverlapped = false; - while (_toplevels.Peek () == OverlappedTop || !_toplevels.Peek ().Visible) { - if (!isOverlapped && _toplevels.Peek () == OverlappedTop) { + while (_topLevels.Peek () == OverlappedTop || !_topLevels.Peek ().Visible) { + if (!isOverlapped && _topLevels.Peek () == OverlappedTop) { isOverlapped = true; - } else if (isOverlapped && _toplevels.Peek () == OverlappedTop) { + } else if (isOverlapped && _topLevels.Peek () == OverlappedTop) { MoveCurrent (Top); break; } - _toplevels.MovePrevious (); + _topLevels.MovePrevious (); } - Current = _toplevels.Peek (); + Current = _topLevels.Peek (); } } } @@ -172,8 +172,8 @@ namespace Terminal.Gui { public static bool MoveToOverlappedChild (Toplevel top) { if (top.Visible && OverlappedTop != null && Current?.Modal == false) { - lock (_toplevels) { - _toplevels.MoveTo (top, 0, new ToplevelEqualityComparer ()); + lock (_topLevels) { + _topLevels.MoveTo (top, 0, new ToplevelEqualityComparer ()); Current = top; } return true; diff --git a/Terminal.sln b/Terminal.sln index 990626580..58a9ace1a 100644 --- a/Terminal.sln +++ b/Terminal.sln @@ -24,6 +24,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution nuget.config = nuget.config .github\workflows\publish.yml = .github\workflows\publish.yml README.md = README.md + Terminal.Gui\.vscode\settings.json = Terminal.Gui\.vscode\settings.json Terminal.sln.DotSettings = Terminal.sln.DotSettings testenvironments.json = testenvironments.json EndProjectSection diff --git a/UICatalog/KeyBindingsDialog.cs b/UICatalog/KeyBindingsDialog.cs index f133f1b57..538320f38 100644 --- a/UICatalog/KeyBindingsDialog.cs +++ b/UICatalog/KeyBindingsDialog.cs @@ -35,7 +35,7 @@ namespace UICatalog { RecordView (top); // Refresh known windows - Application.MainLoop.AddTimeout (TimeSpan.FromMilliseconds (100), (m) => { + Application.AddTimeout (TimeSpan.FromMilliseconds (100), () => { lock (lockKnownViews) { RecordView (Application.Top); @@ -180,7 +180,7 @@ namespace UICatalog { // prompt user to hit a key var dlg = new Dialog () { Title = "Enter Key" }; - dlg.KeyPress += (s, k) => { + dlg.KeyPressed += (s, k) => { key = k.KeyEvent.Key; Application.RequestStop (); }; diff --git a/UICatalog/Properties/launchSettings.json b/UICatalog/Properties/launchSettings.json index 6bc8e3150..b06e2ce0b 100644 --- a/UICatalog/Properties/launchSettings.json +++ b/UICatalog/Properties/launchSettings.json @@ -3,7 +3,7 @@ "UICatalog": { "commandName": "Project", "environmentVariables": { - "WT_SESSION": "1" + "WT_SESSION": "yes" } }, "WSL : UICatalog": { diff --git a/UICatalog/Scenarios/ASCIICustomButton.cs b/UICatalog/Scenarios/ASCIICustomButton.cs index 066bc65d2..eabde34c5 100644 --- a/UICatalog/Scenarios/ASCIICustomButton.cs +++ b/UICatalog/Scenarios/ASCIICustomButton.cs @@ -205,7 +205,7 @@ namespace UICatalog.Scenarios { button.Clicked += Button_Clicked; button.PointerEnter += Button_PointerEnter; button.MouseClick += Button_MouseClick; - button.KeyPress += Button_KeyPress; + button.KeyPressed += Button_KeyPress; scrollView.Add (button); buttons.Add (button); prevButton = button; @@ -215,7 +215,7 @@ namespace UICatalog.Scenarios { closeButton.Clicked += Button_Clicked; closeButton.PointerEnter += Button_PointerEnter; closeButton.MouseClick += Button_MouseClick; - closeButton.KeyPress += Button_KeyPress; + closeButton.KeyPressed += Button_KeyPress; scrollView.Add (closeButton); buttons.Add (closeButton); diff --git a/UICatalog/Scenarios/Animation.cs b/UICatalog/Scenarios/Animation.cs index 7bb15d2e0..0758dd461 100644 --- a/UICatalog/Scenarios/Animation.cs +++ b/UICatalog/Scenarios/Animation.cs @@ -53,7 +53,7 @@ namespace UICatalog.Scenarios { Task.Run (() => { while (!isDisposed) { // When updating from a Thread/Task always use Invoke - Application.MainLoop.Invoke (() => { + Application.Invoke (() => { imageView.NextFrame (); imageView.SetNeedsDisplay (); }); diff --git a/UICatalog/Scenarios/BackgroundWorkerCollection.cs b/UICatalog/Scenarios/BackgroundWorkerCollection.cs index fb661153b..22434aaed 100644 --- a/UICatalog/Scenarios/BackgroundWorkerCollection.cs +++ b/UICatalog/Scenarios/BackgroundWorkerCollection.cs @@ -56,7 +56,7 @@ namespace UICatalog.Scenarios { Closed += OverlappedMain_Closed; - Application.Iteration += () => { + Application.Iteration += (s, a) => { if (canOpenWorkerApp && !workerApp.Running && Application.OverlappedTop.Running) { Application.Run (workerApp); } @@ -338,7 +338,7 @@ namespace UICatalog.Scenarios { close.Clicked += OnReportClosed; Add (close); - KeyPress += (s, e) => { + KeyPressed += (s, e) => { if (e.KeyEvent.Key == Key.Esc) { OnReportClosed (this, EventArgs.Empty); } diff --git a/UICatalog/Scenarios/BasicColors.cs b/UICatalog/Scenarios/BasicColors.cs index 4ca1594a7..e99c88db3 100644 --- a/UICatalog/Scenarios/BasicColors.cs +++ b/UICatalog/Scenarios/BasicColors.cs @@ -86,10 +86,10 @@ namespace UICatalog.Scenarios { }; Win.Add (viewBackground); - Application.RootMouseEvent = (e) => { - if (e.View != null) { - var fore = e.View.GetNormalColor ().Foreground; - var back = e.View.GetNormalColor ().Background; + Application.MouseEvent += (s, e) => { + if (e.MouseEvent.View != null) { + var fore = e.MouseEvent.View.GetNormalColor ().Foreground; + var back = e.MouseEvent.View.GetNormalColor ().Background; lblForeground.Text = $"#{fore.R:X2}{fore.G:X2}{fore.B:X2} {fore.ColorName} "; viewForeground.ColorScheme.Normal = new Attribute (fore, fore); lblBackground.Text = $"#{back.R:X2}{back.G:X2}{back.B:X2} {back.ColorName} "; diff --git a/UICatalog/Scenarios/CharacterMap.cs b/UICatalog/Scenarios/CharacterMap.cs index 7c220312d..6f2370209 100644 --- a/UICatalog/Scenarios/CharacterMap.cs +++ b/UICatalog/Scenarios/CharacterMap.cs @@ -604,7 +604,7 @@ class CharMap : ScrollView { decResponse = await client.GetCodepointDec ((int)SelectedCodePoint); } catch (HttpRequestException e) { (s as Dialog).Text = e.Message; - Application.MainLoop.Invoke (() => { + Application.Invoke (() => { spinner.Visible = false; errorLabel.Text = e.Message; errorLabel.ColorScheme = Colors.ColorSchemes ["Error"]; diff --git a/UICatalog/Scenarios/ContextMenus.cs b/UICatalog/Scenarios/ContextMenus.cs index b66f482a2..c39cb248c 100644 --- a/UICatalog/Scenarios/ContextMenus.cs +++ b/UICatalog/Scenarios/ContextMenus.cs @@ -58,7 +58,7 @@ namespace UICatalog.Scenarios { Point mousePos = default; - Win.KeyPress += (s, e) => { + Win.KeyPressed += (s, e) => { if (e.KeyEvent.Key == (Key.Space | Key.CtrlMask)) { ShowContextMenu (mousePos.X, mousePos.Y); e.Handled = true; @@ -72,18 +72,18 @@ namespace UICatalog.Scenarios { } }; - Application.RootMouseEvent += Application_RootMouseEvent; + Application.MouseEvent += ApplicationMouseEvent; - void Application_RootMouseEvent (MouseEvent me) + void ApplicationMouseEvent (object sender, MouseEventEventArgs a) { - mousePos = new Point (me.X, me.Y); + mousePos = new Point (a.MouseEvent.X, a.MouseEvent.Y); } Win.WantMousePositionReports = true; Application.Top.Closed += (s,e) => { Thread.CurrentThread.CurrentUICulture = new CultureInfo ("en-US"); - Application.RootMouseEvent -= Application_RootMouseEvent; + Application.MouseEvent -= ApplicationMouseEvent; }; } diff --git a/UICatalog/Scenarios/CsvEditor.cs b/UICatalog/Scenarios/CsvEditor.cs index 4b3825cec..96ff667ff 100644 --- a/UICatalog/Scenarios/CsvEditor.cs +++ b/UICatalog/Scenarios/CsvEditor.cs @@ -88,7 +88,7 @@ namespace UICatalog.Scenarios { tableView.SelectedCellChanged += OnSelectedCellChanged; tableView.CellActivated += EditCurrentCell; - tableView.KeyPress += TableViewKeyPress; + tableView.KeyPressed += TableViewKeyPress; SetupScrollBar (); } diff --git a/UICatalog/Scenarios/Editor.cs b/UICatalog/Scenarios/Editor.cs index 20552e119..45c6f704f 100644 --- a/UICatalog/Scenarios/Editor.cs +++ b/UICatalog/Scenarios/Editor.cs @@ -176,7 +176,7 @@ namespace UICatalog.Scenarios { _scrollBar.Refresh (); }; - Win.KeyPress += (s, e) => { + Win.KeyPressed += (s, e) => { var keys = ShortcutHelper.GetModifiersKey (e.KeyEvent); if (_winDialog != null && (e.KeyEvent.Key == Key.Esc || e.KeyEvent.Key == Application.QuitKey)) { diff --git a/UICatalog/Scenarios/GraphViewExample.cs b/UICatalog/Scenarios/GraphViewExample.cs index be945b7db..9f79be4c2 100644 --- a/UICatalog/Scenarios/GraphViewExample.cs +++ b/UICatalog/Scenarios/GraphViewExample.cs @@ -566,7 +566,7 @@ namespace UICatalog.Scenarios { var series = new DiscoBarSeries (); var bars = new List (); - Func genSample = (l) => { + Func genSample = () => { bars.Clear (); // generate an imaginary sample @@ -582,7 +582,7 @@ namespace UICatalog.Scenarios { return graphView.Series.Contains (series); }; - Application.MainLoop.AddTimeout (TimeSpan.FromMilliseconds (250), genSample); + Application.AddTimeout (TimeSpan.FromMilliseconds (250), genSample); series.Bars = bars; diff --git a/UICatalog/Scenarios/InteractiveTree.cs b/UICatalog/Scenarios/InteractiveTree.cs index 623946437..26b26d1fd 100644 --- a/UICatalog/Scenarios/InteractiveTree.cs +++ b/UICatalog/Scenarios/InteractiveTree.cs @@ -30,7 +30,7 @@ namespace UICatalog.Scenarios { Width = Dim.Fill (), Height = Dim.Fill (1), }; - treeView.KeyPress += TreeView_KeyPress; + treeView.KeyPressed += TreeView_KeyPress; Win.Add (treeView); diff --git a/UICatalog/Scenarios/Keys.cs b/UICatalog/Scenarios/Keys.cs index a1bb15f2b..6594daa07 100644 --- a/UICatalog/Scenarios/Keys.cs +++ b/UICatalog/Scenarios/Keys.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using Terminal.Gui; namespace UICatalog.Scenarios { - [ScenarioMetadata (Name: "Keys", Description: "Shows how to handle keyboard input")] + [ScenarioMetadata (Name: "Keys", Description: "Shows keyboard input handling.")] [ScenarioCategory ("Mouse and Keyboard")] public class Keys : Scenario { @@ -37,7 +37,7 @@ namespace UICatalog.Scenarios { Application.Init (); ConfigurationManager.Themes.Theme = Theme; ConfigurationManager.Apply (); - + Win = new TestWindow () { Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}", X = 0, @@ -65,7 +65,7 @@ namespace UICatalog.Scenarios { Win.Add (edit); // Last KeyPress: ______ - var keyPressedLabel = new Label ("Last KeyPress:") { + var keyPressedLabel = new Label ("Last Application.KeyPress:") { X = Pos.Left (editLabel), Y = Pos.Top (editLabel) + 2, }; @@ -79,10 +79,10 @@ namespace UICatalog.Scenarios { }; Win.Add (labelKeypress); - Win.KeyPress += (s,e) => labelKeypress.Text = e.KeyEvent.ToString (); + Win.KeyPressed += (s, e) => labelKeypress.Text = e.KeyEvent.ToString (); // Key stroke log: - var keyLogLabel = new Label ("Key stroke log:") { + var keyLogLabel = new Label ("Key event log:") { X = Pos.Left (editLabel), Y = Pos.Top (editLabel) + 4, }; @@ -94,19 +94,19 @@ namespace UICatalog.Scenarios { }); var maxLogEntry = $"Key{"",-5}: {fakeKeyPress}".Length; var yOffset = (Application.Top == Application.Top ? 1 : 6); - var keyStrokelist = new List (); - var keyStrokeListView = new ListView (keyStrokelist) { + var keyEventlist = new List (); + var keyEventListView = new ListView (keyEventlist) { X = 0, Y = Pos.Top (keyLogLabel) + yOffset, Width = Dim.Percent (30), Height = Dim.Fill (), }; - keyStrokeListView.ColorScheme = Colors.TopLevel; - Win.Add (keyStrokeListView); + keyEventListView.ColorScheme = Colors.TopLevel; + Win.Add (keyEventListView); // ProcessKey log: var processKeyLogLabel = new Label ("ProcessKey log:") { - X = Pos.Right (keyStrokeListView) + 1, + X = Pos.Right (keyEventListView) + 1, Y = Pos.Top (editLabel) + 4, }; Win.Add (processKeyLogLabel); @@ -116,7 +116,7 @@ namespace UICatalog.Scenarios { var processKeyListView = new ListView (((TestWindow)Win)._processKeyList) { X = Pos.Left (processKeyLogLabel), Y = Pos.Top (processKeyLogLabel) + yOffset, - Width = Dim.Percent(30), + Width = Dim.Percent (30), Height = Dim.Fill (), }; processKeyListView.ColorScheme = Colors.TopLevel; @@ -156,15 +156,16 @@ namespace UICatalog.Scenarios { Height = Dim.Fill (), }; - Win.KeyDown += (s,a) => KeyDownPressUp (a.KeyEvent, "Down"); - Win.KeyPress += (s, a) => KeyDownPressUp (a.KeyEvent, "Press"); - Win.KeyUp += (s, a) => KeyDownPressUp (a.KeyEvent, "Up"); + Application.KeyDown += (s, a) => KeyDownPressUp (a, "Down"); + Application.KeyPressed += (s, a) => KeyDownPressUp (a, "Press"); + Application.KeyUp += (s, a) => KeyDownPressUp (a, "Up"); - void KeyDownPressUp (KeyEvent keyEvent, string updown) + void KeyDownPressUp (KeyEventEventArgs args, string updown) { - var msg = $"Key{updown,-5}: {keyEvent}"; - keyStrokelist.Add (msg); - keyStrokeListView.MoveDown (); + // BUGBUG: KeyEvent.ToString is badly broken + var msg = $"Key{updown,-5}: {args.KeyEvent}"; + keyEventlist.Add (msg); + keyEventListView.MoveDown (); processKeyListView.MoveDown (); processColdKeyListView.MoveDown (); processHotKeyListView.MoveDown (); diff --git a/UICatalog/Scenarios/LineDrawing.cs b/UICatalog/Scenarios/LineDrawing.cs index 295fe67a1..45de71fac 100644 --- a/UICatalog/Scenarios/LineDrawing.cs +++ b/UICatalog/Scenarios/LineDrawing.cs @@ -32,7 +32,7 @@ namespace UICatalog.Scenarios { Win.Add (canvas); Win.Add (tools); - Win.KeyPress += (s,e) => { e.Handled = canvas.ProcessKey (e.KeyEvent); }; + Win.KeyPressed += (s,e) => { e.Handled = canvas.ProcessKey (e.KeyEvent); }; } class ToolsView : Window { diff --git a/UICatalog/Scenarios/ListColumns.cs b/UICatalog/Scenarios/ListColumns.cs index 96ebafd42..c3c38f069 100644 --- a/UICatalog/Scenarios/ListColumns.cs +++ b/UICatalog/Scenarios/ListColumns.cs @@ -101,7 +101,7 @@ namespace UICatalog.Scenarios { Win.Add (selectedCellLabel); listColView.SelectedCellChanged += (s, e) => { selectedCellLabel.Text = $"{listColView.SelectedRow},{listColView.SelectedColumn}"; }; - listColView.KeyPress += TableViewKeyPress; + listColView.KeyPressed += TableViewKeyPress; SetupScrollBar (); diff --git a/UICatalog/Scenarios/Mouse.cs b/UICatalog/Scenarios/Mouse.cs index 52c01ae6d..05f722c5a 100644 --- a/UICatalog/Scenarios/Mouse.cs +++ b/UICatalog/Scenarios/Mouse.cs @@ -27,9 +27,9 @@ namespace UICatalog.Scenarios { }; Win.Add (rmeList); - Application.RootMouseEvent += delegate (MouseEvent me) { - ml.Text = $"Mouse: ({me.X},{me.Y}) - {me.Flags} {count}"; - rme.Add ($"({me.X},{me.Y}) - {me.Flags} {count++}"); + Application.MouseEvent += (sender, a) => { + ml.Text = $"Mouse: ({a.MouseEvent.X},{a.MouseEvent.Y}) - {a.MouseEvent.Flags} {count}"; + rme.Add ($"({a.MouseEvent.X},{a.MouseEvent.Y}) - {a.MouseEvent.Flags} {count++}"); rmeList.MoveDown (); }; diff --git a/UICatalog/Scenarios/ProcessTable.cs b/UICatalog/Scenarios/ProcessTable.cs index d07f16bcb..382603889 100644 --- a/UICatalog/Scenarios/ProcessTable.cs +++ b/UICatalog/Scenarios/ProcessTable.cs @@ -31,8 +31,8 @@ namespace UICatalog.Scenarios { CreateProcessTable (); // Then every second - Application.MainLoop.AddTimeout (TimeSpan.FromSeconds (1), - (s) => { + Application.AddTimeout (TimeSpan.FromSeconds (1), + () => { CreateProcessTable (); return true; }); diff --git a/UICatalog/Scenarios/Progress.cs b/UICatalog/Scenarios/Progress.cs index 2755e625d..1b2faf8a7 100644 --- a/UICatalog/Scenarios/Progress.cs +++ b/UICatalog/Scenarios/Progress.cs @@ -123,7 +123,7 @@ namespace UICatalog.Scenarios { { Started = true; StartBtnClick?.Invoke (); - Application.MainLoop.Invoke(()=>{ + Application.Invoke(()=>{ Spinner.Visible = true; ActivityProgressBar.Width = Dim.Fill () - Spinner.Width; this.LayoutSubviews(); @@ -135,7 +135,7 @@ namespace UICatalog.Scenarios { Started = false; StopBtnClick?.Invoke (); - Application.MainLoop.Invoke(()=>{ + Application.Invoke(()=>{ Spinner.Visible = false; ActivityProgressBar.Width = Dim.Fill () - Spinner.Width; this.LayoutSubviews(); @@ -182,7 +182,7 @@ namespace UICatalog.Scenarios { _systemTimer = new Timer ((o) => { // Note the check for Mainloop being valid. System.Timers can run after they are Disposed. // This code must be defensive for that. - Application.MainLoop?.Invoke (() => systemTimerDemo.Pulse ()); + Application.Invoke (() => systemTimerDemo.Pulse ()); }, null, 0, _systemTimerTick); }; @@ -209,7 +209,7 @@ namespace UICatalog.Scenarios { }; Win.Add (systemTimerDemo); - // Demo #2 - Use Application.MainLoop.AddTimeout (no threads) + // Demo #2 - Use Application.AddTimeout (no threads) var mainLoopTimeoutDemo = new ProgressDemo ("Application.AddTimer (no threads)") { X = 0, Y = Pos.Bottom (systemTimerDemo), @@ -221,7 +221,7 @@ namespace UICatalog.Scenarios { mainLoopTimeoutDemo.ActivityProgressBar.Fraction = 0F; mainLoopTimeoutDemo.PulseProgressBar.Fraction = 0F; - _mainLoopTimeout = Application.MainLoop.AddTimeout (TimeSpan.FromMilliseconds (_mainLooopTimeoutTick), (loop) => { + _mainLoopTimeout = Application.AddTimeout (TimeSpan.FromMilliseconds (_mainLooopTimeoutTick), () => { mainLoopTimeoutDemo.Pulse (); return true; @@ -229,7 +229,7 @@ namespace UICatalog.Scenarios { }; mainLoopTimeoutDemo.StopBtnClick = () => { if (_mainLoopTimeout != null) { - Application.MainLoop.RemoveTimeout (_mainLoopTimeout); + Application.RemoveTimeout (_mainLoopTimeout); _mainLoopTimeout = null; } diff --git a/UICatalog/Scenarios/ProgressBarStyles.cs b/UICatalog/Scenarios/ProgressBarStyles.cs index 022d0adba..a4f0af3ba 100644 --- a/UICatalog/Scenarios/ProgressBarStyles.cs +++ b/UICatalog/Scenarios/ProgressBarStyles.cs @@ -79,7 +79,7 @@ namespace UICatalog.Scenarios { _fractionTimer = null; button.Enabled = true; } - Application.MainLoop.MainLoopDriver.Wakeup (); + Application.Wakeup (); }, null, 0, _timerTick); } }; @@ -128,7 +128,7 @@ namespace UICatalog.Scenarios { marqueesBlocksPB.Text = marqueesContinuousPB.Text = DateTime.Now.TimeOfDay.ToString (); marqueesBlocksPB.Pulse (); marqueesContinuousPB.Pulse (); - Application.MainLoop.MainLoopDriver.Wakeup (); + Application.Wakeup (); }, null, 0, 300); Application.Top.Unloaded += Top_Unloaded; diff --git a/UICatalog/Scenarios/Scrolling.cs b/UICatalog/Scenarios/Scrolling.cs index a685116f6..e021c5676 100644 --- a/UICatalog/Scenarios/Scrolling.cs +++ b/UICatalog/Scenarios/Scrolling.cs @@ -291,8 +291,8 @@ namespace UICatalog.Scenarios { Width = 50, }; Win.Add (mousePos); - Application.RootMouseEvent += delegate (MouseEvent me) { - mousePos.Text = $"Mouse: ({me.X},{me.Y}) - {me.Flags} {count++}"; + Application.MouseEvent += (sender, a) => { + mousePos.Text = $"Mouse: ({a.MouseEvent.X},{a.MouseEvent.Y}) - {a.MouseEvent.Flags} {count++}"; }; var progress = new ProgressBar { @@ -303,12 +303,12 @@ namespace UICatalog.Scenarios { Win.Add (progress); bool pulsing = true; - bool timer (MainLoop caller) + bool timer () { progress.Pulse (); return pulsing; } - Application.MainLoop.AddTimeout (TimeSpan.FromMilliseconds (300), timer); + Application.AddTimeout (TimeSpan.FromMilliseconds (300), timer); void Top_Unloaded (object sender, EventArgs args) { diff --git a/UICatalog/Scenarios/SendKeys.cs b/UICatalog/Scenarios/SendKeys.cs index 9aebec657..dd9eef8dd 100644 --- a/UICatalog/Scenarios/SendKeys.cs +++ b/UICatalog/Scenarios/SendKeys.cs @@ -57,7 +57,7 @@ namespace UICatalog.Scenarios { var IsAlt = false; var IsCtrl = false; - txtResult.KeyPress += (s, e) => { + txtResult.KeyPressed += (s, e) => { rKeys += (char)e.KeyEvent.Key; if (!IsShift && e.KeyEvent.IsShift) { rControlKeys += " Shift "; @@ -116,7 +116,7 @@ namespace UICatalog.Scenarios { button.Clicked += (s,e) => ProcessInput (); - Win.KeyPress += (s, e) => { + Win.KeyPressed += (s, e) => { if (e.KeyEvent.Key == Key.Enter) { ProcessInput (); e.Handled = true; diff --git a/UICatalog/Scenarios/SingleBackgroundWorker.cs b/UICatalog/Scenarios/SingleBackgroundWorker.cs index 3318f2ea6..80b3693e6 100644 --- a/UICatalog/Scenarios/SingleBackgroundWorker.cs +++ b/UICatalog/Scenarios/SingleBackgroundWorker.cs @@ -133,7 +133,7 @@ namespace UICatalog.Scenarios { public StagingUIController (DateTime? start, List list) { top = new Toplevel (Application.Top.Frame); - top.KeyPress += (s,e) => { + top.KeyPressed += (s,e) => { // Prevents Ctrl+Q from closing this. // Only Ctrl+C is allowed. if (e.KeyEvent.Key == Application.QuitKey) { diff --git a/UICatalog/Scenarios/Snake.cs b/UICatalog/Scenarios/Snake.cs index 25978ea57..327480ba8 100644 --- a/UICatalog/Scenarios/Snake.cs +++ b/UICatalog/Scenarios/Snake.cs @@ -38,7 +38,7 @@ namespace UICatalog.Scenarios { if (state.AdvanceState ()) { // When updating from a Thread/Task always use Invoke - Application.MainLoop?.Invoke (() => { + Application.Invoke (() => { snakeView.SetNeedsDisplay (); }); } diff --git a/UICatalog/Scenarios/TableEditor.cs b/UICatalog/Scenarios/TableEditor.cs index f710a7924..bf477e8ea 100644 --- a/UICatalog/Scenarios/TableEditor.cs +++ b/UICatalog/Scenarios/TableEditor.cs @@ -122,7 +122,7 @@ namespace UICatalog.Scenarios { tableView.SelectedCellChanged += (s, e) => { selectedCellLabel.Text = $"{tableView.SelectedRow},{tableView.SelectedColumn}"; }; tableView.CellActivated += EditCurrentCell; - tableView.KeyPress += TableViewKeyPress; + tableView.KeyPressed += TableViewKeyPress; SetupScrollBar (); diff --git a/UICatalog/Scenarios/Threading.cs b/UICatalog/Scenarios/Threading.cs index 6bf004964..4a064ae71 100644 --- a/UICatalog/Scenarios/Threading.cs +++ b/UICatalog/Scenarios/Threading.cs @@ -45,7 +45,7 @@ namespace UICatalog.Scenarios { }; _btnActionCancel = new Button (1, 1, "Cancelable Load Items"); - _btnActionCancel.Clicked += (s, e) => Application.MainLoop.Invoke (CallLoadItemsAsync); + _btnActionCancel.Clicked += (s, e) => Application.Invoke (CallLoadItemsAsync); Win.Add (new Label ("Data Items:") { X = Pos.X (_btnActionCancel), diff --git a/UICatalog/Scenarios/TreeViewFileSystem.cs b/UICatalog/Scenarios/TreeViewFileSystem.cs index 0f5cc497f..7e4c72242 100644 --- a/UICatalog/Scenarios/TreeViewFileSystem.cs +++ b/UICatalog/Scenarios/TreeViewFileSystem.cs @@ -95,7 +95,7 @@ namespace UICatalog.Scenarios { Win.Add (_detailsFrame); treeViewFiles.MouseClick += TreeViewFiles_MouseClick; - treeViewFiles.KeyPress += TreeViewFiles_KeyPress; + treeViewFiles.KeyPressed += TreeViewFiles_KeyPress; treeViewFiles.SelectionChanged += TreeViewFiles_SelectionChanged; SetupFileTree (); @@ -206,7 +206,7 @@ namespace UICatalog.Scenarios { menu.MenuItems = new MenuBarItem (new [] { new MenuItem ("Properties", null, () => ShowPropertiesOf (forObject)) }); - Application.MainLoop.Invoke (menu.Show); + Application.Invoke (menu.Show); } class DetailsFrame : FrameView { diff --git a/UICatalog/Scenarios/TrueColors.cs b/UICatalog/Scenarios/TrueColors.cs index c2f15d5e9..9cd618961 100644 --- a/UICatalog/Scenarios/TrueColors.cs +++ b/UICatalog/Scenarios/TrueColors.cs @@ -82,9 +82,9 @@ namespace UICatalog.Scenarios { }; Win.Add (lblBlue); - Application.RootMouseEvent = (e) => { - if (e.View != null) { - var normal = e.View.GetNormalColor (); + Application.MouseEvent += (s, e) => { + if (e.MouseEvent.View != null) { + var normal = e.MouseEvent.View.GetNormalColor (); lblRed.Text = normal.Foreground.R.ToString (); lblGreen.Text = normal.Foreground.G.ToString (); lblBlue.Text = normal.Foreground.B.ToString (); diff --git a/UICatalog/Scenarios/VkeyPacketSimulator.cs b/UICatalog/Scenarios/VkeyPacketSimulator.cs index af32bd1cb..f1d940f6e 100644 --- a/UICatalog/Scenarios/VkeyPacketSimulator.cs +++ b/UICatalog/Scenarios/VkeyPacketSimulator.cs @@ -96,13 +96,13 @@ namespace UICatalog.Scenarios { } }; - tvOutput.KeyPress += (s, e) => { + tvOutput.KeyPressed += (s, e) => { //System.Diagnostics.Debug.WriteLine ($"Output - KeyPress - _keyboardStrokes: {_keyboardStrokes.Count}"); if (_outputStarted && _keyboardStrokes.Count > 0) { var ev = ShortcutHelper.GetModifiersKey (e.KeyEvent); //System.Diagnostics.Debug.WriteLine ($"Output - KeyPress: {ev}"); if (!tvOutput.ProcessKey (e.KeyEvent)) { - Application.MainLoop.Invoke (() => { + Application.Invoke (() => { MessageBox.Query ("Keys", $"'{ShortcutHelper.GetShortcutTag (ev)}' pressed!", "Ok"); }); } @@ -124,7 +124,7 @@ namespace UICatalog.Scenarios { KeyEventEventArgs unknownChar = null; - tvInput.KeyPress += (s, e) => { + tvInput.KeyPressed += (s, e) => { if (e.KeyEvent.Key == (Key.Q | Key.CtrlMask)) { Application.RequestStop (); return; @@ -186,7 +186,7 @@ namespace UICatalog.Scenarios { } //} } catch (Exception) { - Application.MainLoop.Invoke (() => { + Application.Invoke (() => { MessageBox.ErrorQuery ("Error", "Couldn't send the keystrokes!", "Ok"); Application.RequestStop (); }); @@ -196,7 +196,7 @@ namespace UICatalog.Scenarios { _keyboardStrokes.RemoveAt (0); if (_keyboardStrokes.Count == 0) { _outputStarted = false; - Application.MainLoop.Invoke (() => { + Application.Invoke (() => { tvOutput.ReadOnly = true; tvInput.SetFocus (); }); diff --git a/UnitTests/Application/ApplicationTests.cs b/UnitTests/Application/ApplicationTests.cs index e5eb2d7ce..a5fb9f56e 100644 --- a/UnitTests/Application/ApplicationTests.cs +++ b/UnitTests/Application/ApplicationTests.cs @@ -9,1001 +9,1001 @@ using Xunit; // Alias Console to MockConsole so we don't accidentally use Console using Console = Terminal.Gui.FakeConsole; -namespace Terminal.Gui.ApplicationTests { - public class ApplicationTests { - public ApplicationTests () - { +namespace Terminal.Gui.ApplicationTests; + +public class ApplicationTests { + public ApplicationTests () + { #if DEBUG_IDISPOSABLE - Responder.Instances.Clear (); - RunState.Instances.Clear (); + Responder.Instances.Clear (); + RunState.Instances.Clear (); #endif - } + } - void Pre_Init_State () - { - Assert.Null (Application.Driver); - Assert.Null (Application.Top); - Assert.Null (Application.Current); - Assert.Null (Application.MainLoop); - Assert.Null (Application.Iteration); - Assert.Null (Application.RootMouseEvent); - Assert.Null (Application.TerminalResized); - } + void Pre_Init_State () + { + Assert.Null (Application.Driver); + Assert.Null (Application.Top); + Assert.Null (Application.Current); + Assert.Null (Application.MainLoop); + } - void Post_Init_State () - { - Assert.NotNull (Application.Driver); - Assert.NotNull (Application.Top); - Assert.NotNull (Application.Current); - Assert.NotNull (Application.MainLoop); - Assert.Null (Application.Iteration); - Assert.Null (Application.RootMouseEvent); - Assert.Null (Application.TerminalResized); - // FakeDriver is always 80x25 - Assert.Equal (80, Application.Driver.Cols); - Assert.Equal (25, Application.Driver.Rows); + void Post_Init_State () + { + Assert.NotNull (Application.Driver); + Assert.NotNull (Application.Top); + Assert.NotNull (Application.Current); + Assert.NotNull (Application.MainLoop); + // FakeDriver is always 80x25 + Assert.Equal (80, Application.Driver.Cols); + Assert.Equal (25, Application.Driver.Rows); - } + } - void Init () - { - Application.Init (new FakeDriver ()); - Assert.NotNull (Application.Driver); - Assert.NotNull (Application.MainLoop); - Assert.NotNull (SynchronizationContext.Current); - } + void Init () + { + Application.Init (new FakeDriver ()); + Assert.NotNull (Application.Driver); + Assert.NotNull (Application.MainLoop); + Assert.NotNull (SynchronizationContext.Current); + } - void Shutdown () - { - Application.Shutdown (); - } + void Shutdown () + { + Application.Shutdown (); + } - [Fact] - public void Init_Shutdown_Cleans_Up () - { - // Verify initial state is per spec - //Pre_Init_State (); + [Fact] + public void Init_Shutdown_Cleans_Up () + { + // Verify initial state is per spec + //Pre_Init_State (); - Application.Init (new FakeDriver ()); + Application.Init (new FakeDriver ()); - // Verify post-Init state is correct - //Post_Init_State (); + // Verify post-Init state is correct + //Post_Init_State (); - Application.Shutdown (); + Application.Shutdown (); - // Verify state is back to initial - //Pre_Init_State (); + // Verify state is back to initial + //Pre_Init_State (); #if DEBUG_IDISPOSABLE // Validate there are no outstanding Responder-based instances // after a scenario was selected to run. This proves the main UI Catalog // 'app' closed cleanly. Assert.Empty (Responder.Instances); #endif - } - - [Fact] - public void Init_Unbalanced_Throws () - { - Application.Init (new FakeDriver ()); - - Toplevel topLevel = null; - Assert.Throws (() => Application.InternalInit (() => topLevel = new TestToplevel (), new FakeDriver ())); - Shutdown (); - - Assert.Null (Application.Top); - Assert.Null (Application.MainLoop); - Assert.Null (Application.Driver); - - // Now try the other way - topLevel = null; - Application.InternalInit (() => topLevel = new TestToplevel (), new FakeDriver ()); - - Assert.Throws (() => Application.Init (new FakeDriver ())); - Shutdown (); - - Assert.Null (Application.Top); - Assert.Null (Application.MainLoop); - Assert.Null (Application.Driver); - } - - class TestToplevel : Toplevel { - public TestToplevel () - { - IsOverlappedContainer = false; - } - } - - [Fact] - public void Init_Null_Driver_Should_Pick_A_Driver () - { - Application.Init (null); - - Assert.NotNull (Application.Driver); - - Shutdown (); - } - - [Fact] - public void Init_Begin_End_Cleans_Up () - { - Init (); - - // Begin will cause Run() to be called, which will call Begin(). Thus will block the tests - // if we don't stop - Application.Iteration = () => { - Application.RequestStop (); - }; - - RunState runstate = null; - EventHandler NewRunStateFn = (s, e) => { - Assert.NotNull (e.State); - runstate = e.State; - }; - Application.NotifyNewRunState += NewRunStateFn; - - Toplevel topLevel = new Toplevel (); - var rs = Application.Begin (topLevel); - Assert.NotNull (rs); - Assert.NotNull (runstate); - Assert.Equal (rs, runstate); - - Assert.Equal (topLevel, Application.Top); - Assert.Equal (topLevel, Application.Current); - - Application.NotifyNewRunState -= NewRunStateFn; - Application.End (runstate); - - Assert.Null (Application.Current); - Assert.NotNull (Application.Top); - Assert.NotNull (Application.MainLoop); - Assert.NotNull (Application.Driver); - - Shutdown (); - - Assert.Null (Application.Top); - Assert.Null (Application.MainLoop); - Assert.Null (Application.Driver); - } - - [Fact] - public void InitWithTopLevelFactory_Begin_End_Cleans_Up () - { - // Begin will cause Run() to be called, which will call Begin(). Thus will block the tests - // if we don't stop - Application.Iteration = () => { - Application.RequestStop (); - }; - - // NOTE: Run, when called after Init has been called behaves differently than - // when called if Init has not been called. - Toplevel topLevel = null; - Application.InternalInit (() => topLevel = new TestToplevel (), new FakeDriver ()); - - RunState runstate = null; - EventHandler NewRunStateFn = (s, e) => { - Assert.NotNull (e.State); - runstate = e.State; - }; - Application.NotifyNewRunState += NewRunStateFn; - - var rs = Application.Begin (topLevel); - Assert.NotNull (rs); - Assert.NotNull (runstate); - Assert.Equal (rs, runstate); - - Assert.Equal (topLevel, Application.Top); - Assert.Equal (topLevel, Application.Current); - - Application.NotifyNewRunState -= NewRunStateFn; - Application.End (runstate); - - Assert.Null (Application.Current); - Assert.NotNull (Application.Top); - Assert.NotNull (Application.MainLoop); - Assert.NotNull (Application.Driver); - - Shutdown (); - - Assert.Null (Application.Top); - Assert.Null (Application.MainLoop); - Assert.Null (Application.Driver); - } - - [Fact] - public void Begin_Null_Toplevel_Throws () - { - // Setup Mock driver - Init (); - - // Test null Toplevel - Assert.Throws (() => Application.Begin (null)); - - Shutdown (); - - Assert.Null (Application.Top); - Assert.Null (Application.MainLoop); - Assert.Null (Application.Driver); - } - - #region RunTests - - [Fact] - public void Run_T_After_InitWithDriver_with_TopLevel_Throws () - { - // Setup Mock driver - Init (); - - // Run when already initialized with a Driver will throw (because Toplevel is not dervied from TopLevel) - Assert.Throws (() => Application.Run (errorHandler: null)); - - Shutdown (); - - Assert.Null (Application.Top); - Assert.Null (Application.MainLoop); - Assert.Null (Application.Driver); - } - - [Fact] - public void Run_T_After_InitWithDriver_with_TopLevel_and_Driver_Throws () - { - // Setup Mock driver - Init (); - - // Run when already initialized with a Driver will throw (because Toplevel is not dervied from TopLevel) - Assert.Throws (() => Application.Run (errorHandler: null, new FakeDriver ())); - - Shutdown (); - - Assert.Null (Application.Top); - Assert.Null (Application.MainLoop); - Assert.Null (Application.Driver); - } - - [Fact] - public void Run_T_After_InitWithDriver_with_TestTopLevel_DoesNotThrow () - { - // Setup Mock driver - Init (); - - Application.Iteration = () => { - Application.RequestStop (); - }; - - // Init has been called and we're passing no driver to Run. This is ok. - Application.Run (); - - Shutdown (); - - Assert.Null (Application.Top); - Assert.Null (Application.MainLoop); - Assert.Null (Application.Driver); - } - - [Fact] - public void Run_T_After_InitNullDriver_with_TestTopLevel_Throws () - { - Application._forceFakeConsole = true; - - Application.Init (null, null); - Assert.Equal (typeof (FakeDriver), Application.Driver.GetType ()); - - Application.Iteration = () => { - Application.RequestStop (); - }; - - // Init has been called without selecting a driver and we're passing no driver to Run. Bad - Application.Run (); - - Shutdown (); - - Assert.Null (Application.Top); - Assert.Null (Application.MainLoop); - Assert.Null (Application.Driver); - } - - [Fact] - public void Run_T_Init_Driver_Cleared_with_TestTopLevel_Throws () - { - Init (); - - Application.Driver = null; - - Application.Iteration = () => { - Application.RequestStop (); - }; - - // Init has been called, but Driver has been set to null. Bad. - Assert.Throws (() => Application.Run ()); - - Shutdown (); - - Assert.Null (Application.Top); - Assert.Null (Application.MainLoop); - Assert.Null (Application.Driver); - } - - [Fact] - public void Run_T_NoInit_DoesNotThrow () - { - Application._forceFakeConsole = true; - - Application.Iteration = () => { - Application.RequestStop (); - }; - - Application.Run (); - Assert.Equal (typeof (FakeDriver), Application.Driver.GetType ()); - - Shutdown (); - - Assert.Null (Application.Top); - Assert.Null (Application.MainLoop); - Assert.Null (Application.Driver); - } - - [Fact] - public void Run_T_NoInit_WithDriver_DoesNotThrow () - { - Application.Iteration = () => { - Application.RequestStop (); - }; - - // Init has NOT been called and we're passing a valid driver to Run. This is ok. - Application.Run (errorHandler: null, new FakeDriver ()); - - Shutdown (); - - Assert.Null (Application.Top); - Assert.Null (Application.MainLoop); - Assert.Null (Application.Driver); - } - - [Fact] - public void Run_RequestStop_Stops () - { - // Setup Mock driver - Init (); - - var top = new Toplevel (); - var rs = Application.Begin (top); - Assert.NotNull (rs); - Assert.Equal (top, Application.Current); - - Application.Iteration = () => { - Application.RequestStop (); - }; - - Application.Run (top); - - Application.Shutdown (); - Assert.Null (Application.Current); - Assert.Null (Application.Top); - Assert.Null (Application.MainLoop); - Assert.Null (Application.Driver); - } - - [Fact] - public void Run_RunningFalse_Stops () - { - // Setup Mock driver - Init (); - - var top = new Toplevel (); - var rs = Application.Begin (top); - Assert.NotNull (rs); - Assert.Equal (top, Application.Current); - - Application.Iteration = () => { - top.Running = false; - }; - - Application.Run (top); - - Application.Shutdown (); - Assert.Null (Application.Current); - Assert.Null (Application.Top); - Assert.Null (Application.MainLoop); - Assert.Null (Application.Driver); - } - - [Fact] - public void Run_Loaded_Ready_Unlodaded_Events () - { - Init (); - var top = Application.Top; - var count = 0; - top.Loaded += (s, e) => count++; - top.Ready += (s, e) => count++; - top.Unloaded += (s, e) => count++; - Application.Iteration = () => Application.RequestStop (); - Application.Run (); - Application.Shutdown (); - Assert.Equal (3, count); - } - - // TODO: Add tests for Run that test errorHandler - - #endregion - - #region ShutdownTests - [Fact] - public void Shutdown_Allows_Async () - { - static async Task TaskWithAsyncContinuation () - { - await Task.Yield (); - await Task.Yield (); - } - - Init (); - Application.Shutdown (); - - var task = TaskWithAsyncContinuation (); - Thread.Sleep (20); - Assert.True (task.IsCompletedSuccessfully); - } - - [Fact] - public void Shutdown_Resets_SyncContext () - { - Init (); - Application.Shutdown (); - Assert.Null (SynchronizationContext.Current); - } - #endregion - - [Fact, AutoInitShutdown] - public void Begin_Sets_Application_Top_To_Console_Size () - { - Assert.Equal (new Rect (0, 0, 80, 25), Application.Top.Frame); - - ((FakeDriver)Application.Driver).SetBufferSize (5, 5); - Application.Begin (Application.Top); - Assert.Equal (new Rect (0, 0, 80, 25), Application.Top.Frame); - ((FakeDriver)Application.Driver).SetBufferSize (5, 5); - Assert.Equal (new Rect (0, 0, 5, 5), Application.Top.Frame); - } - - [Fact] - [AutoInitShutdown] - public void SetCurrentAsTop_Run_A_Not_Modal_Toplevel_Make_It_The_Current_Application_Top () - { - var t1 = new Toplevel (); - var t2 = new Toplevel (); - var t3 = new Toplevel (); - var d = new Dialog (); - var t4 = new Toplevel (); - - // t1, t2, t3, d, t4 - var iterations = 5; - - t1.Ready += (s, e) => { - Assert.Equal (t1, Application.Top); - Application.Run (t2); - }; - t2.Ready += (s, e) => { - Assert.Equal (t2, Application.Top); - Application.Run (t3); - }; - t3.Ready += (s, e) => { - Assert.Equal (t3, Application.Top); - Application.Run (d); - }; - d.Ready += (s, e) => { - Assert.Equal (t3, Application.Top); - Application.Run (t4); - }; - t4.Ready += (s, e) => { - Assert.Equal (t4, Application.Top); - t4.RequestStop (); - d.RequestStop (); - t3.RequestStop (); - t2.RequestStop (); - }; - // Now this will close the OverlappedContainer when all OverlappedChildren was closed - t2.Closed += (s, _) => { - t1.RequestStop (); - }; - Application.Iteration += () => { - if (iterations == 5) { - // The Current still is t4 because Current.Running is false. - Assert.Equal (t4, Application.Current); - Assert.False (Application.Current.Running); - Assert.Equal (t4, Application.Top); - } else if (iterations == 4) { - // The Current is d and Current.Running is false. - Assert.Equal (d, Application.Current); - Assert.False (Application.Current.Running); - Assert.Equal (t4, Application.Top); - } else if (iterations == 3) { - // The Current is t3 and Current.Running is false. - Assert.Equal (t3, Application.Current); - Assert.False (Application.Current.Running); - Assert.Equal (t3, Application.Top); - } else if (iterations == 2) { - // The Current is t2 and Current.Running is false. - Assert.Equal (t2, Application.Current); - Assert.False (Application.Current.Running); - Assert.Equal (t2, Application.Top); - } else { - // The Current is t1. - Assert.Equal (t1, Application.Current); - Assert.False (Application.Current.Running); - Assert.Equal (t1, Application.Top); - } - iterations--; - }; - - Application.Run (t1); - - Assert.Equal (t1, Application.Top); - } - - [Fact] - [AutoInitShutdown] - public void Internal_Properties_Correct () - { - Assert.True (Application._initialized); - Assert.NotNull (Application.Top); - var rs = Application.Begin (Application.Top); - Assert.Equal (Application.Top, rs.Toplevel); - Assert.Null (Application.MouseGrabView); // public - Assert.Null (Application.WantContinuousButtonPressedView); // public - Assert.False (Application.MoveToOverlappedChild (Application.Top)); - } - - #region KeyboardTests - [Fact] - public void KeyUp_Event () - { - // Setup Mock driver - Init (); - - // Setup some fake keypresses (This) - var input = "Tests"; - - // Put a control-q in at the end - FakeConsole.MockKeyPresses.Push (new ConsoleKeyInfo ('q', ConsoleKey.Q, shift: false, alt: false, control: true)); - foreach (var c in input.Reverse ()) { - if (char.IsLetter (c)) { - FakeConsole.MockKeyPresses.Push (new ConsoleKeyInfo (c, (ConsoleKey)char.ToUpper (c), shift: char.IsUpper (c), alt: false, control: false)); - } else { - FakeConsole.MockKeyPresses.Push (new ConsoleKeyInfo (c, (ConsoleKey)c, shift: false, alt: false, control: false)); - } - } - - int stackSize = FakeConsole.MockKeyPresses.Count; - - int iterations = 0; - Application.Iteration = () => { - iterations++; - // Stop if we run out of control... - if (iterations > 10) { - Application.RequestStop (); - } - }; - - int keyUps = 0; - var output = string.Empty; - Application.Top.KeyUp += (object sender, KeyEventEventArgs args) => { - if (args.KeyEvent.Key != (Key.CtrlMask | Key.Q)) { - output += (char)args.KeyEvent.KeyValue; - } - keyUps++; - }; - - Application.Run (Application.Top); - - // Input string should match output - Assert.Equal (input, output); - - // # of key up events should match stack size - //Assert.Equal (stackSize, keyUps); - // We can't use numbers variables on the left side of an Assert.Equal/NotEqual, - // it must be literal (Linux only). - Assert.Equal (6, keyUps); - - // # of key up events should match # of iterations - Assert.Equal (stackSize, iterations); - - Application.Shutdown (); - Assert.Null (Application.Current); - Assert.Null (Application.Top); - Assert.Null (Application.MainLoop); - Assert.Null (Application.Driver); - } - - [Fact] - public void AlternateForwardKey_AlternateBackwardKey_Tests () - { - Init (); - - var top = Application.Top; - var w1 = new Window (); - var v1 = new TextField (); - var v2 = new TextView (); - w1.Add (v1, v2); - - var w2 = new Window (); - var v3 = new CheckBox (); - var v4 = new Button (); - w2.Add (v3, v4); - - top.Add (w1, w2); - - Application.Iteration += () => { - Assert.True (v1.HasFocus); - // Using default keys. - top.ProcessKey (new KeyEvent (Key.CtrlMask | Key.Tab, - new KeyModifiers () { Ctrl = true })); - Assert.True (v2.HasFocus); - top.ProcessKey (new KeyEvent (Key.CtrlMask | Key.Tab, - new KeyModifiers () { Ctrl = true })); - Assert.True (v3.HasFocus); - top.ProcessKey (new KeyEvent (Key.CtrlMask | Key.Tab, - new KeyModifiers () { Ctrl = true })); - Assert.True (v4.HasFocus); - top.ProcessKey (new KeyEvent (Key.CtrlMask | Key.Tab, - new KeyModifiers () { Ctrl = true })); - Assert.True (v1.HasFocus); - - top.ProcessKey (new KeyEvent (Key.ShiftMask | Key.CtrlMask | Key.Tab, - new KeyModifiers () { Shift = true, Ctrl = true })); - Assert.True (v4.HasFocus); - top.ProcessKey (new KeyEvent (Key.ShiftMask | Key.CtrlMask | Key.Tab, - new KeyModifiers () { Shift = true, Ctrl = true })); - Assert.True (v3.HasFocus); - top.ProcessKey (new KeyEvent (Key.ShiftMask | Key.CtrlMask | Key.Tab, - new KeyModifiers () { Shift = true, Ctrl = true })); - Assert.True (v2.HasFocus); - top.ProcessKey (new KeyEvent (Key.ShiftMask | Key.CtrlMask | Key.Tab, - new KeyModifiers () { Shift = true, Ctrl = true })); - Assert.True (v1.HasFocus); - - top.ProcessKey (new KeyEvent (Key.CtrlMask | Key.PageDown, - new KeyModifiers () { Ctrl = true })); - Assert.True (v2.HasFocus); - top.ProcessKey (new KeyEvent (Key.CtrlMask | Key.PageDown, - new KeyModifiers () { Ctrl = true })); - Assert.True (v3.HasFocus); - top.ProcessKey (new KeyEvent (Key.CtrlMask | Key.PageDown, - new KeyModifiers () { Ctrl = true })); - Assert.True (v4.HasFocus); - top.ProcessKey (new KeyEvent (Key.CtrlMask | Key.PageDown, - new KeyModifiers () { Ctrl = true })); - Assert.True (v1.HasFocus); - - top.ProcessKey (new KeyEvent (Key.CtrlMask | Key.PageUp, - new KeyModifiers () { Ctrl = true })); - Assert.True (v4.HasFocus); - top.ProcessKey (new KeyEvent (Key.CtrlMask | Key.PageUp, - new KeyModifiers () { Ctrl = true })); - Assert.True (v3.HasFocus); - top.ProcessKey (new KeyEvent (Key.CtrlMask | Key.PageUp, - new KeyModifiers () { Ctrl = true })); - Assert.True (v2.HasFocus); - top.ProcessKey (new KeyEvent (Key.CtrlMask | Key.PageUp, - new KeyModifiers () { Ctrl = true })); - Assert.True (v1.HasFocus); - - // Using another's alternate keys. - Application.AlternateForwardKey = Key.F7; - Application.AlternateBackwardKey = Key.F6; - - top.ProcessKey (new KeyEvent (Key.F7, new KeyModifiers ())); - Assert.True (v2.HasFocus); - top.ProcessKey (new KeyEvent (Key.F7, new KeyModifiers ())); - Assert.True (v3.HasFocus); - top.ProcessKey (new KeyEvent (Key.F7, new KeyModifiers ())); - Assert.True (v4.HasFocus); - top.ProcessKey (new KeyEvent (Key.F7, new KeyModifiers ())); - Assert.True (v1.HasFocus); - - top.ProcessKey (new KeyEvent (Key.F6, new KeyModifiers ())); - Assert.True (v4.HasFocus); - top.ProcessKey (new KeyEvent (Key.F6, new KeyModifiers ())); - Assert.True (v3.HasFocus); - top.ProcessKey (new KeyEvent (Key.F6, new KeyModifiers ())); - Assert.True (v2.HasFocus); - top.ProcessKey (new KeyEvent (Key.F6, new KeyModifiers ())); - Assert.True (v1.HasFocus); - - Application.RequestStop (); - }; - - Application.Run (top); - - // Replacing the defaults keys to avoid errors on others unit tests that are using it. - Application.AlternateForwardKey = Key.PageDown | Key.CtrlMask; - Application.AlternateBackwardKey = Key.PageUp | Key.CtrlMask; - Application.QuitKey = Key.Q | Key.CtrlMask; - - Assert.Equal (Key.PageDown | Key.CtrlMask, Application.AlternateForwardKey); - Assert.Equal (Key.PageUp | Key.CtrlMask, Application.AlternateBackwardKey); - Assert.Equal (Key.Q | Key.CtrlMask, Application.QuitKey); - - // Shutdown must be called to safely clean up Application if Init has been called - Application.Shutdown (); - } - - [Fact] - [AutoInitShutdown] - public void QuitKey_Getter_Setter () - { - var top = Application.Top; - var isQuiting = false; - - top.Closing += (s, e) => { - isQuiting = true; - e.Cancel = true; - }; - - Application.Begin (top); - top.Running = true; - - Assert.Equal (Key.Q | Key.CtrlMask, Application.QuitKey); - Application.Driver.SendKeys ('q', ConsoleKey.Q, false, false, true); - Assert.True (isQuiting); - - isQuiting = false; - Application.QuitKey = Key.C | Key.CtrlMask; - - Application.Driver.SendKeys ('q', ConsoleKey.Q, false, false, true); - Assert.False (isQuiting); - Application.Driver.SendKeys ('c', ConsoleKey.C, false, false, true); - Assert.True (isQuiting); - - // Reset the QuitKey to avoid throws errors on another tests - Application.QuitKey = Key.Q | Key.CtrlMask; - } - - [Fact] - [AutoInitShutdown] - public void EnsuresTopOnFront_CanFocus_True_By_Keyboard_And_Mouse () - { - var top = Application.Top; - var win = new Window () { Title = "win", X = 0, Y = 0, Width = 20, Height = 10 }; - var tf = new TextField () { Width = 10 }; - win.Add (tf); - var win2 = new Window () { Title = "win2", X = 22, Y = 0, Width = 20, Height = 10 }; - var tf2 = new TextField () { Width = 10 }; - win2.Add (tf2); - top.Add (win, win2); - - Application.Begin (top); - - Assert.True (win.CanFocus); - Assert.True (win.HasFocus); - Assert.True (win2.CanFocus); - Assert.False (win2.HasFocus); - Assert.Equal ("win2", ((Window)top.Subviews [top.Subviews.Count - 1]).Title); - - top.ProcessKey (new KeyEvent (Key.CtrlMask | Key.Tab, new KeyModifiers ())); - Assert.True (win.CanFocus); - Assert.False (win.HasFocus); - Assert.True (win2.CanFocus); - Assert.True (win2.HasFocus); - Assert.Equal ("win2", ((Window)top.Subviews [top.Subviews.Count - 1]).Title); - - top.ProcessKey (new KeyEvent (Key.CtrlMask | Key.Tab, new KeyModifiers ())); - Assert.True (win.CanFocus); - Assert.True (win.HasFocus); - Assert.True (win2.CanFocus); - Assert.False (win2.HasFocus); - Assert.Equal ("win", ((Window)top.Subviews [top.Subviews.Count - 1]).Title); - - win2.MouseEvent (new MouseEvent () { Flags = MouseFlags.Button1Pressed }); - Assert.True (win.CanFocus); - Assert.False (win.HasFocus); - Assert.True (win2.CanFocus); - 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); - } - - [Fact] - [AutoInitShutdown] - public void EnsuresTopOnFront_CanFocus_False_By_Keyboard_And_Mouse () - { - var top = Application.Top; - var win = new Window () { Title = "win", X = 0, Y = 0, Width = 20, Height = 10 }; - var tf = new TextField () { Width = 10 }; - win.Add (tf); - var win2 = new Window () { Title = "win2", X = 22, Y = 0, Width = 20, Height = 10 }; - var tf2 = new TextField () { Width = 10 }; - win2.Add (tf2); - top.Add (win, win2); - - Application.Begin (top); - - Assert.True (win.CanFocus); - Assert.True (win.HasFocus); - Assert.True (win2.CanFocus); - Assert.False (win2.HasFocus); - Assert.Equal ("win2", ((Window)top.Subviews [top.Subviews.Count - 1]).Title); - - win.CanFocus = false; - Assert.False (win.CanFocus); - Assert.False (win.HasFocus); - Assert.True (win2.CanFocus); - Assert.True (win2.HasFocus); - Assert.Equal ("win2", ((Window)top.Subviews [top.Subviews.Count - 1]).Title); - - top.ProcessKey (new KeyEvent (Key.CtrlMask | Key.Tab, new KeyModifiers ())); - Assert.True (win2.CanFocus); - Assert.False (win.HasFocus); - Assert.True (win2.CanFocus); - Assert.True (win2.HasFocus); - Assert.Equal ("win2", ((Window)top.Subviews [top.Subviews.Count - 1]).Title); - - top.ProcessKey (new KeyEvent (Key.CtrlMask | Key.Tab, new KeyModifiers ())); - Assert.False (win.CanFocus); - Assert.False (win.HasFocus); - Assert.True (win2.CanFocus); - Assert.True (win2.HasFocus); - Assert.Equal ("win2", ((Window)top.Subviews [top.Subviews.Count - 1]).Title); - - win.MouseEvent (new MouseEvent () { Flags = MouseFlags.Button1Pressed }); - Assert.False (win.CanFocus); - Assert.False (win.HasFocus); - Assert.True (win2.CanFocus); - 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); - } - - #endregion - - - #region mousegrabtests - [Fact, AutoInitShutdown] - public void MouseGrabView_WithNullMouseEventView () - { - var tf = new TextField () { Width = 10 }; - var sv = new ScrollView () { - Width = Dim.Fill (), - Height = Dim.Fill (), - ContentSize = new Size (100, 100) - }; - - sv.Add (tf); - Application.Top.Add (sv); - - var iterations = -1; - - Application.Iteration = () => { - iterations++; - if (iterations == 0) { - Assert.True (tf.HasFocus); - Assert.Null (Application.MouseGrabView); - - ReflectionTools.InvokePrivate ( - typeof (Application), - "ProcessMouseEvent", - new MouseEvent () { - X = 5, - Y = 5, - Flags = MouseFlags.ReportMousePosition - }); - - Assert.Equal (sv, Application.MouseGrabView); - - MessageBox.Query ("Title", "Test", "Ok"); - - Assert.Null (Application.MouseGrabView); - } else if (iterations == 1) { - // Application.MouseGrabView is null because - // another toplevel (Dialog) was opened - Assert.Null (Application.MouseGrabView); - - ReflectionTools.InvokePrivate ( - typeof (Application), - "ProcessMouseEvent", - new MouseEvent () { - X = 5, - Y = 5, - Flags = MouseFlags.ReportMousePosition - }); - - Assert.Null (Application.MouseGrabView); - - ReflectionTools.InvokePrivate ( - typeof (Application), - "ProcessMouseEvent", - new MouseEvent () { - X = 40, - Y = 12, - Flags = MouseFlags.ReportMousePosition - }); - - Assert.Null (Application.MouseGrabView); - - ReflectionTools.InvokePrivate ( - typeof (Application), - "ProcessMouseEvent", - new MouseEvent () { - X = 0, - Y = 0, - Flags = MouseFlags.Button1Pressed - }); - - Assert.Null (Application.MouseGrabView); - - Application.RequestStop (); - } else if (iterations == 2) { - Assert.Null (Application.MouseGrabView); - - Application.RequestStop (); - } - }; - - Application.Run (); - } - - [Fact, AutoInitShutdown] - public void MouseGrabView_GrabbedMouse_UnGrabbedMouse () - { - View grabView = null; - var count = 0; - - var view1 = new View (); - var view2 = new View (); - - Application.GrabbedMouse += Application_GrabbedMouse; - Application.UnGrabbedMouse += Application_UnGrabbedMouse; - - Application.GrabMouse (view1); - Assert.Equal (0, count); - Assert.Equal (grabView, view1); - Assert.Equal (view1, Application.MouseGrabView); - - Application.UngrabMouse (); - Assert.Equal (1, count); - Assert.Equal (grabView, view1); - Assert.Null (Application.MouseGrabView); - - Application.GrabbedMouse += Application_GrabbedMouse; - Application.UnGrabbedMouse += Application_UnGrabbedMouse; - - Application.GrabMouse (view2); - Assert.Equal (1, count); - Assert.Equal (grabView, view2); - Assert.Equal (view2, Application.MouseGrabView); - - Application.UngrabMouse (); - Assert.Equal (2, count); - Assert.Equal (grabView, view2); - Assert.Null (Application.MouseGrabView); - - void Application_GrabbedMouse (object sender, ViewEventArgs e) - { - if (count == 0) { - Assert.Equal (view1, e.View); - grabView = view1; - } else { - Assert.Equal (view2, e.View); - grabView = view2; - } - - Application.GrabbedMouse -= Application_GrabbedMouse; - } - - void Application_UnGrabbedMouse (object sender, ViewEventArgs e) - { - if (count == 0) { - Assert.Equal (view1, e.View); - Assert.Equal (grabView, e.View); - } else { - Assert.Equal (view2, e.View); - Assert.Equal (grabView, e.View); - } - count++; - - Application.UnGrabbedMouse -= Application_UnGrabbedMouse; - } - } - #endregion } -} + + [Fact] + public void Init_Unbalanced_Throws () + { + Application.Init (new FakeDriver ()); + + Toplevel topLevel = null; + Assert.Throws (() => Application.InternalInit (() => topLevel = new TestToplevel (), new FakeDriver ())); + Shutdown (); + + Assert.Null (Application.Top); + Assert.Null (Application.MainLoop); + Assert.Null (Application.Driver); + + // Now try the other way + topLevel = null; + Application.InternalInit (() => topLevel = new TestToplevel (), new FakeDriver ()); + + Assert.Throws (() => Application.Init (new FakeDriver ())); + Shutdown (); + + Assert.Null (Application.Top); + Assert.Null (Application.MainLoop); + Assert.Null (Application.Driver); + } + + class TestToplevel : Toplevel { + public TestToplevel () + { + IsOverlappedContainer = false; + } + } + + [Fact] + public void Init_Null_Driver_Should_Pick_A_Driver () + { + Application.Init (null); + + Assert.NotNull (Application.Driver); + + Shutdown (); + } + + [Fact] + public void Init_Begin_End_Cleans_Up () + { + Init (); + + // Begin will cause Run() to be called, which will call Begin(). Thus will block the tests + // if we don't stop + Application.Iteration += (s, a) => { + Application.RequestStop (); + }; + + RunState runstate = null; + EventHandler NewRunStateFn = (s, e) => { + Assert.NotNull (e.State); + runstate = e.State; + }; + Application.NotifyNewRunState += NewRunStateFn; + + Toplevel topLevel = new Toplevel (); + var rs = Application.Begin (topLevel); + Assert.NotNull (rs); + Assert.NotNull (runstate); + Assert.Equal (rs, runstate); + + Assert.Equal (topLevel, Application.Top); + Assert.Equal (topLevel, Application.Current); + + Application.NotifyNewRunState -= NewRunStateFn; + Application.End (runstate); + + Assert.Null (Application.Current); + Assert.NotNull (Application.Top); + Assert.NotNull (Application.MainLoop); + Assert.NotNull (Application.Driver); + + Shutdown (); + + Assert.Null (Application.Top); + Assert.Null (Application.MainLoop); + Assert.Null (Application.Driver); + } + + [Fact] + public void InitWithTopLevelFactory_Begin_End_Cleans_Up () + { + // Begin will cause Run() to be called, which will call Begin(). Thus will block the tests + // if we don't stop + Application.Iteration += (s, a) => { + Application.RequestStop (); + }; + + // NOTE: Run, when called after Init has been called behaves differently than + // when called if Init has not been called. + Toplevel topLevel = null; + Application.InternalInit (() => topLevel = new TestToplevel (), new FakeDriver ()); + + RunState runstate = null; + EventHandler NewRunStateFn = (s, e) => { + Assert.NotNull (e.State); + runstate = e.State; + }; + Application.NotifyNewRunState += NewRunStateFn; + + var rs = Application.Begin (topLevel); + Assert.NotNull (rs); + Assert.NotNull (runstate); + Assert.Equal (rs, runstate); + + Assert.Equal (topLevel, Application.Top); + Assert.Equal (topLevel, Application.Current); + + Application.NotifyNewRunState -= NewRunStateFn; + Application.End (runstate); + + Assert.Null (Application.Current); + Assert.NotNull (Application.Top); + Assert.NotNull (Application.MainLoop); + Assert.NotNull (Application.Driver); + + Shutdown (); + + Assert.Null (Application.Top); + Assert.Null (Application.MainLoop); + Assert.Null (Application.Driver); + } + + [Fact] + public void Begin_Null_Toplevel_Throws () + { + // Setup Mock driver + Init (); + + // Test null Toplevel + Assert.Throws (() => Application.Begin (null)); + + Shutdown (); + + Assert.Null (Application.Top); + Assert.Null (Application.MainLoop); + Assert.Null (Application.Driver); + } + + #region RunTests + + [Fact] + public void Run_T_After_InitWithDriver_with_TopLevel_Throws () + { + // Setup Mock driver + Init (); + + // Run when already initialized with a Driver will throw (because Toplevel is not dervied from TopLevel) + Assert.Throws (() => Application.Run (errorHandler: null)); + + Shutdown (); + + Assert.Null (Application.Top); + Assert.Null (Application.MainLoop); + Assert.Null (Application.Driver); + } + + [Fact] + public void Run_T_After_InitWithDriver_with_TopLevel_and_Driver_Throws () + { + // Setup Mock driver + Init (); + + // Run when already initialized with a Driver will throw (because Toplevel is not dervied from TopLevel) + Assert.Throws (() => Application.Run (errorHandler: null, new FakeDriver ())); + + Shutdown (); + + Assert.Null (Application.Top); + Assert.Null (Application.MainLoop); + Assert.Null (Application.Driver); + } + + [Fact] + public void Run_T_After_InitWithDriver_with_TestTopLevel_DoesNotThrow () + { + // Setup Mock driver + Init (); + + Application.Iteration += (s, a) => { + Application.RequestStop (); + }; + + // Init has been called and we're passing no driver to Run. This is ok. + Application.Run (); + + Shutdown (); + + Assert.Null (Application.Top); + Assert.Null (Application.MainLoop); + Assert.Null (Application.Driver); + } + + [Fact] + public void Run_T_After_InitNullDriver_with_TestTopLevel_Throws () + { + Application._forceFakeConsole = true; + + Application.Init (null); + Assert.Equal (typeof (FakeDriver), Application.Driver.GetType ()); + + Application.Iteration += (s, a) => { + Application.RequestStop (); + }; + + // Init has been called without selecting a driver and we're passing no driver to Run. Bad + Application.Run (); + + Shutdown (); + + Assert.Null (Application.Top); + Assert.Null (Application.MainLoop); + Assert.Null (Application.Driver); + } + + [Fact] + public void Run_T_Init_Driver_Cleared_with_TestTopLevel_Throws () + { + Init (); + + Application.Driver = null; + + Application.Iteration += (s, a) => { + Application.RequestStop (); + }; + + // Init has been called, but Driver has been set to null. Bad. + Assert.Throws (() => Application.Run ()); + + Shutdown (); + + Assert.Null (Application.Top); + Assert.Null (Application.MainLoop); + Assert.Null (Application.Driver); + } + + [Fact] + public void Run_T_NoInit_DoesNotThrow () + { + Application._forceFakeConsole = true; + + Application.Iteration += (s, a) => { + Application.RequestStop (); + }; + + Application.Run (); + Assert.Equal (typeof (FakeDriver), Application.Driver.GetType ()); + + Shutdown (); + + Assert.Null (Application.Top); + Assert.Null (Application.MainLoop); + Assert.Null (Application.Driver); + } + + [Fact] + public void Run_T_NoInit_WithDriver_DoesNotThrow () + { + Application.Iteration += (s, a) => { + Application.RequestStop (); + }; + + // Init has NOT been called and we're passing a valid driver to Run. This is ok. + Application.Run (errorHandler: null, new FakeDriver ()); + + Shutdown (); + + Assert.Null (Application.Top); + Assert.Null (Application.MainLoop); + Assert.Null (Application.Driver); + } + + [Fact] + public void Run_RequestStop_Stops () + { + // Setup Mock driver + Init (); + + var top = new Toplevel (); + var rs = Application.Begin (top); + Assert.NotNull (rs); + Assert.Equal (top, Application.Current); + + Application.Iteration += (s, a) => { + Application.RequestStop (); + }; + + Application.Run (top); + + Application.Shutdown (); + Assert.Null (Application.Current); + Assert.Null (Application.Top); + Assert.Null (Application.MainLoop); + Assert.Null (Application.Driver); + } + + [Fact] + public void Run_RunningFalse_Stops () + { + // Setup Mock driver + Init (); + + var top = new Toplevel (); + var rs = Application.Begin (top); + Assert.NotNull (rs); + Assert.Equal (top, Application.Current); + + Application.Iteration += (s, a) => { + top.Running = false; + }; + + Application.Run (top); + + Application.Shutdown (); + Assert.Null (Application.Current); + Assert.Null (Application.Top); + Assert.Null (Application.MainLoop); + Assert.Null (Application.Driver); + } + + [Fact] + public void Run_Loaded_Ready_Unlodaded_Events () + { + Init (); + var top = Application.Top; + var count = 0; + top.Loaded += (s, e) => count++; + top.Ready += (s, e) => count++; + top.Unloaded += (s, e) => count++; + Application.Iteration += (s, a) => Application.RequestStop (); + Application.Run (); + Application.Shutdown (); + Assert.Equal (3, count); + } + + // TODO: Add tests for Run that test errorHandler + + #endregion + + #region ShutdownTests + [Fact] + public void Shutdown_Allows_Async () + { + static async Task TaskWithAsyncContinuation () + { + await Task.Yield (); + await Task.Yield (); + } + + Init (); + Application.Shutdown (); + + var task = TaskWithAsyncContinuation (); + Thread.Sleep (20); + Assert.True (task.IsCompletedSuccessfully); + } + + [Fact] + public void Shutdown_Resets_SyncContext () + { + Init (); + Application.Shutdown (); + Assert.Null (SynchronizationContext.Current); + } + #endregion + + [Fact, AutoInitShutdown] + public void Begin_Sets_Application_Top_To_Console_Size () + { + Assert.Equal (new Rect (0, 0, 80, 25), Application.Top.Frame); + + ((FakeDriver)Application.Driver).SetBufferSize (5, 5); + Application.Begin (Application.Top); + Assert.Equal (new Rect (0, 0, 80, 25), Application.Top.Frame); + ((FakeDriver)Application.Driver).SetBufferSize (5, 5); + Assert.Equal (new Rect (0, 0, 5, 5), Application.Top.Frame); + } + + [Fact] + [AutoInitShutdown] + public void SetCurrentAsTop_Run_A_Not_Modal_Toplevel_Make_It_The_Current_Application_Top () + { + var t1 = new Toplevel (); + var t2 = new Toplevel (); + var t3 = new Toplevel (); + var d = new Dialog (); + var t4 = new Toplevel (); + + // t1, t2, t3, d, t4 + var iterations = 5; + + t1.Ready += (s, e) => { + Assert.Equal (t1, Application.Top); + Application.Run (t2); + }; + t2.Ready += (s, e) => { + Assert.Equal (t2, Application.Top); + Application.Run (t3); + }; + t3.Ready += (s, e) => { + Assert.Equal (t3, Application.Top); + Application.Run (d); + }; + d.Ready += (s, e) => { + Assert.Equal (t3, Application.Top); + Application.Run (t4); + }; + t4.Ready += (s, e) => { + Assert.Equal (t4, Application.Top); + t4.RequestStop (); + d.RequestStop (); + t3.RequestStop (); + t2.RequestStop (); + }; + // Now this will close the OverlappedContainer when all OverlappedChildren was closed + t2.Closed += (s, _) => { + t1.RequestStop (); + }; + Application.Iteration += (s, a) => { + if (iterations == 5) { + // The Current still is t4 because Current.Running is false. + Assert.Equal (t4, Application.Current); + Assert.False (Application.Current.Running); + Assert.Equal (t4, Application.Top); + } else if (iterations == 4) { + // The Current is d and Current.Running is false. + Assert.Equal (d, Application.Current); + Assert.False (Application.Current.Running); + Assert.Equal (t4, Application.Top); + } else if (iterations == 3) { + // The Current is t3 and Current.Running is false. + Assert.Equal (t3, Application.Current); + Assert.False (Application.Current.Running); + Assert.Equal (t3, Application.Top); + } else if (iterations == 2) { + // The Current is t2 and Current.Running is false. + Assert.Equal (t2, Application.Current); + Assert.False (Application.Current.Running); + Assert.Equal (t2, Application.Top); + } else { + // The Current is t1. + Assert.Equal (t1, Application.Current); + Assert.False (Application.Current.Running); + Assert.Equal (t1, Application.Top); + } + iterations--; + }; + + Application.Run (t1); + + Assert.Equal (t1, Application.Top); + } + + [Fact] + [AutoInitShutdown] + public void Internal_Properties_Correct () + { + Assert.True (Application._initialized); + Assert.NotNull (Application.Top); + var rs = Application.Begin (Application.Top); + Assert.Equal (Application.Top, rs.Toplevel); + Assert.Null (Application.MouseGrabView); // public + Assert.Null (Application.WantContinuousButtonPressedView); // public + Assert.False (Application.MoveToOverlappedChild (Application.Top)); + } + + #region KeyboardTests + [Fact] + public void KeyUp_Event () + { + // Setup Mock driver + Init (); + + // Setup some fake keypresses (This) + var input = "Tests"; + + // Put a control-q in at the end + FakeConsole.MockKeyPresses.Push (new ConsoleKeyInfo ('q', ConsoleKey.Q, shift: false, alt: false, control: true)); + foreach (var c in input.Reverse ()) { + if (char.IsLetter (c)) { + FakeConsole.MockKeyPresses.Push (new ConsoleKeyInfo (c, (ConsoleKey)char.ToUpper (c), shift: char.IsUpper (c), alt: false, control: false)); + } else { + FakeConsole.MockKeyPresses.Push (new ConsoleKeyInfo (c, (ConsoleKey)c, shift: false, alt: false, control: false)); + } + } + + int stackSize = FakeConsole.MockKeyPresses.Count; + + int iterations = 0; + Application.Iteration += (s, a) => { + iterations++; + // Stop if we run out of control... + if (iterations > 10) { + Application.RequestStop (); + } + }; + + int keyUps = 0; + var output = string.Empty; + Application.Top.KeyUp += (object sender, KeyEventEventArgs args) => { + if (args.KeyEvent.Key != (Key.CtrlMask | Key.Q)) { + output += (char)args.KeyEvent.KeyValue; + } + keyUps++; + }; + + Application.Run (Application.Top); + + // Input string should match output + Assert.Equal (input, output); + + // # of key up events should match stack size + //Assert.Equal (stackSize, keyUps); + // We can't use numbers variables on the left side of an Assert.Equal/NotEqual, + // it must be literal (Linux only). + Assert.Equal (6, keyUps); + + // # of key up events should match # of iterations + Assert.Equal (stackSize, iterations); + + Application.Shutdown (); + Assert.Null (Application.Current); + Assert.Null (Application.Top); + Assert.Null (Application.MainLoop); + Assert.Null (Application.Driver); + } + + [Fact] + public void AlternateForwardKey_AlternateBackwardKey_Tests () + { + Init (); + + var top = Application.Top; + var w1 = new Window (); + var v1 = new TextField (); + var v2 = new TextView (); + w1.Add (v1, v2); + + var w2 = new Window (); + var v3 = new CheckBox (); + var v4 = new Button (); + w2.Add (v3, v4); + + top.Add (w1, w2); + + Application.Iteration += (s, a) => { + Assert.True (v1.HasFocus); + // Using default keys. + top.ProcessKey (new KeyEvent (Key.CtrlMask | Key.Tab, + new KeyModifiers () { Ctrl = true })); + Assert.True (v2.HasFocus); + top.ProcessKey (new KeyEvent (Key.CtrlMask | Key.Tab, + new KeyModifiers () { Ctrl = true })); + Assert.True (v3.HasFocus); + top.ProcessKey (new KeyEvent (Key.CtrlMask | Key.Tab, + new KeyModifiers () { Ctrl = true })); + Assert.True (v4.HasFocus); + top.ProcessKey (new KeyEvent (Key.CtrlMask | Key.Tab, + new KeyModifiers () { Ctrl = true })); + Assert.True (v1.HasFocus); + + top.ProcessKey (new KeyEvent (Key.ShiftMask | Key.CtrlMask | Key.Tab, + new KeyModifiers () { Shift = true, Ctrl = true })); + Assert.True (v4.HasFocus); + top.ProcessKey (new KeyEvent (Key.ShiftMask | Key.CtrlMask | Key.Tab, + new KeyModifiers () { Shift = true, Ctrl = true })); + Assert.True (v3.HasFocus); + top.ProcessKey (new KeyEvent (Key.ShiftMask | Key.CtrlMask | Key.Tab, + new KeyModifiers () { Shift = true, Ctrl = true })); + Assert.True (v2.HasFocus); + top.ProcessKey (new KeyEvent (Key.ShiftMask | Key.CtrlMask | Key.Tab, + new KeyModifiers () { Shift = true, Ctrl = true })); + Assert.True (v1.HasFocus); + + top.ProcessKey (new KeyEvent (Key.CtrlMask | Key.PageDown, + new KeyModifiers () { Ctrl = true })); + Assert.True (v2.HasFocus); + top.ProcessKey (new KeyEvent (Key.CtrlMask | Key.PageDown, + new KeyModifiers () { Ctrl = true })); + Assert.True (v3.HasFocus); + top.ProcessKey (new KeyEvent (Key.CtrlMask | Key.PageDown, + new KeyModifiers () { Ctrl = true })); + Assert.True (v4.HasFocus); + top.ProcessKey (new KeyEvent (Key.CtrlMask | Key.PageDown, + new KeyModifiers () { Ctrl = true })); + Assert.True (v1.HasFocus); + + top.ProcessKey (new KeyEvent (Key.CtrlMask | Key.PageUp, + new KeyModifiers () { Ctrl = true })); + Assert.True (v4.HasFocus); + top.ProcessKey (new KeyEvent (Key.CtrlMask | Key.PageUp, + new KeyModifiers () { Ctrl = true })); + Assert.True (v3.HasFocus); + top.ProcessKey (new KeyEvent (Key.CtrlMask | Key.PageUp, + new KeyModifiers () { Ctrl = true })); + Assert.True (v2.HasFocus); + top.ProcessKey (new KeyEvent (Key.CtrlMask | Key.PageUp, + new KeyModifiers () { Ctrl = true })); + Assert.True (v1.HasFocus); + + // Using another's alternate keys. + Application.AlternateForwardKey = Key.F7; + Application.AlternateBackwardKey = Key.F6; + + top.ProcessKey (new KeyEvent (Key.F7, new KeyModifiers ())); + Assert.True (v2.HasFocus); + top.ProcessKey (new KeyEvent (Key.F7, new KeyModifiers ())); + Assert.True (v3.HasFocus); + top.ProcessKey (new KeyEvent (Key.F7, new KeyModifiers ())); + Assert.True (v4.HasFocus); + top.ProcessKey (new KeyEvent (Key.F7, new KeyModifiers ())); + Assert.True (v1.HasFocus); + + top.ProcessKey (new KeyEvent (Key.F6, new KeyModifiers ())); + Assert.True (v4.HasFocus); + top.ProcessKey (new KeyEvent (Key.F6, new KeyModifiers ())); + Assert.True (v3.HasFocus); + top.ProcessKey (new KeyEvent (Key.F6, new KeyModifiers ())); + Assert.True (v2.HasFocus); + top.ProcessKey (new KeyEvent (Key.F6, new KeyModifiers ())); + Assert.True (v1.HasFocus); + + Application.RequestStop (); + }; + + Application.Run (top); + + // Replacing the defaults keys to avoid errors on others unit tests that are using it. + Application.AlternateForwardKey = Key.PageDown | Key.CtrlMask; + Application.AlternateBackwardKey = Key.PageUp | Key.CtrlMask; + Application.QuitKey = Key.Q | Key.CtrlMask; + + Assert.Equal (Key.PageDown | Key.CtrlMask, Application.AlternateForwardKey); + Assert.Equal (Key.PageUp | Key.CtrlMask, Application.AlternateBackwardKey); + Assert.Equal (Key.Q | Key.CtrlMask, Application.QuitKey); + + // Shutdown must be called to safely clean up Application if Init has been called + Application.Shutdown (); + } + + [Fact] + [AutoInitShutdown] + public void QuitKey_Getter_Setter () + { + var top = Application.Top; + var isQuiting = false; + + top.Closing += (s, e) => { + isQuiting = true; + e.Cancel = true; + }; + + Application.Begin (top); + top.Running = true; + + Assert.Equal (Key.Q | Key.CtrlMask, Application.QuitKey); + Application.Driver.SendKeys ('q', ConsoleKey.Q, false, false, true); + Assert.True (isQuiting); + + isQuiting = false; + Application.QuitKey = Key.C | Key.CtrlMask; + + Application.Driver.SendKeys ('q', ConsoleKey.Q, false, false, true); + Assert.False (isQuiting); + Application.Driver.SendKeys ('c', ConsoleKey.C, false, false, true); + Assert.True (isQuiting); + + // Reset the QuitKey to avoid throws errors on another tests + Application.QuitKey = Key.Q | Key.CtrlMask; + } + + [Fact] + [AutoInitShutdown] + public void EnsuresTopOnFront_CanFocus_True_By_Keyboard_And_Mouse () + { + var top = Application.Top; + var win = new Window () { Title = "win", X = 0, Y = 0, Width = 20, Height = 10 }; + var tf = new TextField () { Width = 10 }; + win.Add (tf); + var win2 = new Window () { Title = "win2", X = 22, Y = 0, Width = 20, Height = 10 }; + var tf2 = new TextField () { Width = 10 }; + win2.Add (tf2); + top.Add (win, win2); + + Application.Begin (top); + + Assert.True (win.CanFocus); + Assert.True (win.HasFocus); + Assert.True (win2.CanFocus); + Assert.False (win2.HasFocus); + Assert.Equal ("win2", ((Window)top.Subviews [top.Subviews.Count - 1]).Title); + + top.ProcessKey (new KeyEvent (Key.CtrlMask | Key.Tab, new KeyModifiers ())); + Assert.True (win.CanFocus); + Assert.False (win.HasFocus); + Assert.True (win2.CanFocus); + Assert.True (win2.HasFocus); + Assert.Equal ("win2", ((Window)top.Subviews [top.Subviews.Count - 1]).Title); + + top.ProcessKey (new KeyEvent (Key.CtrlMask | Key.Tab, new KeyModifiers ())); + Assert.True (win.CanFocus); + Assert.True (win.HasFocus); + Assert.True (win2.CanFocus); + Assert.False (win2.HasFocus); + Assert.Equal ("win", ((Window)top.Subviews [top.Subviews.Count - 1]).Title); + + win2.MouseEvent (new MouseEvent () { Flags = MouseFlags.Button1Pressed }); + Assert.True (win.CanFocus); + Assert.False (win.HasFocus); + Assert.True (win2.CanFocus); + 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); + } + + [Fact] + [AutoInitShutdown] + public void EnsuresTopOnFront_CanFocus_False_By_Keyboard_And_Mouse () + { + var top = Application.Top; + var win = new Window () { Title = "win", X = 0, Y = 0, Width = 20, Height = 10 }; + var tf = new TextField () { Width = 10 }; + win.Add (tf); + var win2 = new Window () { Title = "win2", X = 22, Y = 0, Width = 20, Height = 10 }; + var tf2 = new TextField () { Width = 10 }; + win2.Add (tf2); + top.Add (win, win2); + + Application.Begin (top); + + Assert.True (win.CanFocus); + Assert.True (win.HasFocus); + Assert.True (win2.CanFocus); + Assert.False (win2.HasFocus); + Assert.Equal ("win2", ((Window)top.Subviews [top.Subviews.Count - 1]).Title); + + win.CanFocus = false; + Assert.False (win.CanFocus); + Assert.False (win.HasFocus); + Assert.True (win2.CanFocus); + Assert.True (win2.HasFocus); + Assert.Equal ("win2", ((Window)top.Subviews [top.Subviews.Count - 1]).Title); + + top.ProcessKey (new KeyEvent (Key.CtrlMask | Key.Tab, new KeyModifiers ())); + Assert.True (win2.CanFocus); + Assert.False (win.HasFocus); + Assert.True (win2.CanFocus); + Assert.True (win2.HasFocus); + Assert.Equal ("win2", ((Window)top.Subviews [top.Subviews.Count - 1]).Title); + + top.ProcessKey (new KeyEvent (Key.CtrlMask | Key.Tab, new KeyModifiers ())); + Assert.False (win.CanFocus); + Assert.False (win.HasFocus); + Assert.True (win2.CanFocus); + Assert.True (win2.HasFocus); + Assert.Equal ("win2", ((Window)top.Subviews [top.Subviews.Count - 1]).Title); + + win.MouseEvent (new MouseEvent () { Flags = MouseFlags.Button1Pressed }); + Assert.False (win.CanFocus); + Assert.False (win.HasFocus); + Assert.True (win2.CanFocus); + 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); + } + + #endregion + + + // Invoke Tests + // TODO: Test with threading scenarios + [Fact] + public void Invoke_Adds_Idle () + { + Application.Init (new FakeDriver ()); + var top = new Toplevel (); + var rs = Application.Begin (top); + bool firstIteration = false; + + var actionCalled = 0; + Application.Invoke (() => { actionCalled++; }); + Application.RunIteration (ref rs, ref firstIteration); + Assert.Equal (1, actionCalled); + Application.Shutdown (); + } + + + #region mousegrabtests + [Fact, AutoInitShutdown] + public void MouseGrabView_WithNullMouseEventView () + { + var tf = new TextField () { Width = 10 }; + var sv = new ScrollView () { + Width = Dim.Fill (), + Height = Dim.Fill (), + ContentSize = new Size (100, 100) + }; + + sv.Add (tf); + Application.Top.Add (sv); + + var iterations = -1; + + Application.Iteration += (s, a) => { + iterations++; + if (iterations == 0) { + Assert.True (tf.HasFocus); + Assert.Null (Application.MouseGrabView); + + Application.OnMouseEvent (new MouseEventEventArgs (new MouseEvent () { + X = 5, + Y = 5, + Flags = MouseFlags.ReportMousePosition + })); + + Assert.Equal (sv, Application.MouseGrabView); + + MessageBox.Query ("Title", "Test", "Ok"); + + Assert.Null (Application.MouseGrabView); + } else if (iterations == 1) { + // Application.MouseGrabView is null because + // another toplevel (Dialog) was opened + Assert.Null (Application.MouseGrabView); + + Application.OnMouseEvent (new MouseEventEventArgs (new MouseEvent () { + X = 5, + Y = 5, + Flags = MouseFlags.ReportMousePosition + })); + + Assert.Null (Application.MouseGrabView); + + Application.OnMouseEvent (new MouseEventEventArgs (new MouseEvent () { + X = 40, + Y = 12, + Flags = MouseFlags.ReportMousePosition + })); + + Assert.Null (Application.MouseGrabView); + + Application.OnMouseEvent (new MouseEventEventArgs (new MouseEvent () { + X = 0, + Y = 0, + Flags = MouseFlags.Button1Pressed + })); + + Assert.Null (Application.MouseGrabView); + + Application.RequestStop (); + } else if (iterations == 2) { + Assert.Null (Application.MouseGrabView); + + Application.RequestStop (); + } + }; + + Application.Run (); + } + + [Fact, AutoInitShutdown] + public void MouseGrabView_GrabbedMouse_UnGrabbedMouse () + { + View grabView = null; + var count = 0; + + var view1 = new View (); + var view2 = new View (); + + Application.GrabbedMouse += Application_GrabbedMouse; + Application.UnGrabbedMouse += Application_UnGrabbedMouse; + + Application.GrabMouse (view1); + Assert.Equal (0, count); + Assert.Equal (grabView, view1); + Assert.Equal (view1, Application.MouseGrabView); + + Application.UngrabMouse (); + Assert.Equal (1, count); + Assert.Equal (grabView, view1); + Assert.Null (Application.MouseGrabView); + + Application.GrabbedMouse += Application_GrabbedMouse; + Application.UnGrabbedMouse += Application_UnGrabbedMouse; + + Application.GrabMouse (view2); + Assert.Equal (1, count); + Assert.Equal (grabView, view2); + Assert.Equal (view2, Application.MouseGrabView); + + Application.UngrabMouse (); + Assert.Equal (2, count); + Assert.Equal (grabView, view2); + Assert.Null (Application.MouseGrabView); + + void Application_GrabbedMouse (object sender, ViewEventArgs e) + { + if (count == 0) { + Assert.Equal (view1, e.View); + grabView = view1; + } else { + Assert.Equal (view2, e.View); + grabView = view2; + } + + Application.GrabbedMouse -= Application_GrabbedMouse; + } + + void Application_UnGrabbedMouse (object sender, ViewEventArgs e) + { + if (count == 0) { + Assert.Equal (view1, e.View); + Assert.Equal (grabView, e.View); + } else { + Assert.Equal (view2, e.View); + Assert.Equal (grabView, e.View); + } + count++; + + Application.UnGrabbedMouse -= Application_UnGrabbedMouse; + } + } + #endregion +} \ No newline at end of file diff --git a/UnitTests/Application/MainLoopTests.cs b/UnitTests/Application/MainLoopTests.cs index 7b15faad5..a3cba7a4e 100644 --- a/UnitTests/Application/MainLoopTests.cs +++ b/UnitTests/Application/MainLoopTests.cs @@ -13,828 +13,808 @@ using Xunit.Sdk; // Alias Console to MockConsole so we don't accidentally use Console using Console = Terminal.Gui.FakeConsole; -namespace Terminal.Gui.ApplicationTests { - /// - /// Tests MainLoop using the FakeMainLoop. - /// - public class MainLoopTests { +namespace Terminal.Gui.ApplicationTests; +/// +/// Tests MainLoop using the FakeMainLoop. +/// +public class MainLoopTests { - // TODO: Expand to test all the MainLoop implementations. + // See Also ConsoleDRivers/MainLoopDriverTests.cs for tests of the MainLoopDriver + + + // Idle Handler tests + [Fact] + public void AddIdle_Adds_And_Removes () + { + var ml = new MainLoop (new FakeMainLoop ()); - [Fact] - public void Constructor_Setups_Driver () - { - var ml = new MainLoop (new FakeMainLoop ()); - Assert.NotNull (ml.MainLoopDriver); - } + Func fnTrue = () => true; + Func fnFalse = () => false; - // Idle Handler tests - [Fact] - public void AddIdle_Adds_And_Removes () - { - var ml = new MainLoop (new FakeMainLoop ()); + ml.AddIdle (fnTrue); + ml.AddIdle (fnFalse); - Func fnTrue = () => true; - Func fnFalse = () => false; + Assert.Equal (2, ml.IdleHandlers.Count); + Assert.Equal (fnTrue, ml.IdleHandlers [0]); + Assert.NotEqual (fnFalse, ml.IdleHandlers [0]); - ml.AddIdle (fnTrue); - ml.AddIdle (fnFalse); + Assert.True (ml.RemoveIdle (fnTrue)); + Assert.Single (ml.IdleHandlers); - Assert.Equal (2, ml.IdleHandlers.Count); - Assert.Equal (fnTrue, ml.IdleHandlers [0]); - Assert.NotEqual (fnFalse, ml.IdleHandlers [0]); + // BUGBUG: This doesn't throw or indicate an error. Ideally RemoveIdle would either + // throw an exception in this case, or return an error. + // No. Only need to return a boolean. + Assert.False (ml.RemoveIdle (fnTrue)); - Assert.True (ml.RemoveIdle (fnTrue)); - Assert.Single (ml.IdleHandlers); + Assert.True (ml.RemoveIdle (fnFalse)); - // BUGBUG: This doesn't throw or indicate an error. Ideally RemoveIdle would either - // throw an exception in this case, or return an error. - // No. Only need to return a boolean. - Assert.False (ml.RemoveIdle (fnTrue)); + // BUGBUG: This doesn't throw an exception or indicate an error. Ideally RemoveIdle would either + // throw an exception in this case, or return an error. + // No. Only need to return a boolean. + Assert.False (ml.RemoveIdle (fnFalse)); - Assert.True (ml.RemoveIdle (fnFalse)); + // Add again, but with dupe + ml.AddIdle (fnTrue); + ml.AddIdle (fnTrue); - // BUGBUG: This doesn't throw an exception or indicate an error. Ideally RemoveIdle would either - // throw an exception in this case, or return an error. - // No. Only need to return a boolean. - Assert.False (ml.RemoveIdle (fnFalse)); + Assert.Equal (2, ml.IdleHandlers.Count); + Assert.Equal (fnTrue, ml.IdleHandlers [0]); + Assert.True (ml.IdleHandlers [0] ()); + Assert.Equal (fnTrue, ml.IdleHandlers [1]); + Assert.True (ml.IdleHandlers [1] ()); - // Add again, but with dupe - ml.AddIdle (fnTrue); - ml.AddIdle (fnTrue); + Assert.True (ml.RemoveIdle (fnTrue)); + Assert.Single (ml.IdleHandlers); + Assert.Equal (fnTrue, ml.IdleHandlers [0]); + Assert.NotEqual (fnFalse, ml.IdleHandlers [0]); - Assert.Equal (2, ml.IdleHandlers.Count); - Assert.Equal (fnTrue, ml.IdleHandlers [0]); - Assert.True (ml.IdleHandlers [0] ()); - Assert.Equal (fnTrue, ml.IdleHandlers [1]); - Assert.True (ml.IdleHandlers [1] ()); + Assert.True (ml.RemoveIdle (fnTrue)); + Assert.Empty (ml.IdleHandlers); - Assert.True (ml.RemoveIdle (fnTrue)); - Assert.Single (ml.IdleHandlers); - Assert.Equal (fnTrue, ml.IdleHandlers [0]); - Assert.NotEqual (fnFalse, ml.IdleHandlers [0]); + // BUGBUG: This doesn't throw an exception or indicate an error. Ideally RemoveIdle would either + // throw an exception in this case, or return an error. + // No. Only need to return a boolean. + Assert.False (ml.RemoveIdle (fnTrue)); + } - Assert.True (ml.RemoveIdle (fnTrue)); - Assert.Empty (ml.IdleHandlers); + [Fact] + public void AddIdle_Function_GetsCalled_OnIteration () + { + var ml = new MainLoop (new FakeMainLoop ()); - // BUGBUG: This doesn't throw an exception or indicate an error. Ideally RemoveIdle would either - // throw an exception in this case, or return an error. - // No. Only need to return a boolean. - Assert.False (ml.RemoveIdle (fnTrue)); - } + var functionCalled = 0; + Func fn = () => { + functionCalled++; + return true; + }; - [Fact] - public void AddIdle_Function_GetsCalled_OnIteration () - { - var ml = new MainLoop (new FakeMainLoop ()); + ml.AddIdle (fn); + ml.RunIteration (); + Assert.Equal (1, functionCalled); + } - var functionCalled = 0; - Func fn = () => { - functionCalled++; - return true; - }; + [Fact] + public void RemoveIdle_Function_NotCalled () + { + var ml = new MainLoop (new FakeMainLoop ()); - ml.AddIdle (fn); - ml.RunIteration (); - Assert.Equal (1, functionCalled); - } + var functionCalled = 0; + Func fn = () => { + functionCalled++; + return true; + }; - [Fact] - public void RemoveIdle_Function_NotCalled () - { - var ml = new MainLoop (new FakeMainLoop ()); + Assert.False (ml.RemoveIdle (fn)); + ml.RunIteration (); + Assert.Equal (0, functionCalled); + } - var functionCalled = 0; - Func fn = () => { - functionCalled++; - return true; - }; + [Fact] + public void AddThenRemoveIdle_Function_NotCalled () + { + var ml = new MainLoop (new FakeMainLoop ()); - Assert.False (ml.RemoveIdle (fn)); - ml.RunIteration (); - Assert.Equal (0, functionCalled); - } + var functionCalled = 0; + Func fn = () => { + functionCalled++; + return true; + }; - [Fact] - public void AddThenRemoveIdle_Function_NotCalled () - { - var ml = new MainLoop (new FakeMainLoop ()); + ml.AddIdle (fn); + Assert.True (ml.RemoveIdle (fn)); + ml.RunIteration (); + Assert.Equal (0, functionCalled); + } - var functionCalled = 0; - Func fn = () => { - functionCalled++; - return true; - }; + [Fact] + public void AddIdleTwice_Function_CalledTwice () + { + var ml = new MainLoop (new FakeMainLoop ()); - ml.AddIdle (fn); - Assert.True (ml.RemoveIdle (fn)); - ml.RunIteration (); - Assert.Equal (0, functionCalled); - } + var functionCalled = 0; + Func fn = () => { + functionCalled++; + return true; + }; - [Fact] - public void AddIdleTwice_Function_CalledTwice () - { - var ml = new MainLoop (new FakeMainLoop ()); + ml.AddIdle (fn); + ml.AddIdle (fn); + ml.RunIteration (); + Assert.Equal (2, functionCalled); + Assert.Equal (2, ml.IdleHandlers.Count); - var functionCalled = 0; - Func fn = () => { - functionCalled++; - return true; - }; + functionCalled = 0; + Assert.True (ml.RemoveIdle (fn)); + Assert.Single (ml.IdleHandlers); + ml.RunIteration (); + Assert.Equal (1, functionCalled); - ml.AddIdle (fn); - ml.AddIdle (fn); - ml.RunIteration (); - Assert.Equal (2, functionCalled); - Assert.Equal (2, ml.IdleHandlers.Count); + functionCalled = 0; + Assert.True (ml.RemoveIdle (fn)); + Assert.Empty (ml.IdleHandlers); + ml.RunIteration (); + Assert.Equal (0, functionCalled); + Assert.False (ml.RemoveIdle (fn)); + } - functionCalled = 0; - Assert.True (ml.RemoveIdle (fn)); - Assert.Single (ml.IdleHandlers); - ml.RunIteration (); - Assert.Equal (1, functionCalled); + [Fact] + public void False_Idle_Stops_It_Being_Called_Again () + { + var ml = new MainLoop (new FakeMainLoop ()); - functionCalled = 0; - Assert.True (ml.RemoveIdle (fn)); - Assert.Empty (ml.IdleHandlers); - ml.RunIteration (); - Assert.Equal (0, functionCalled); - Assert.False (ml.RemoveIdle (fn)); - } + var functionCalled = 0; + Func fn1 = () => { + functionCalled++; + if (functionCalled == 10) return false; + return true; + }; - [Fact] - public void False_Idle_Stops_It_Being_Called_Again () - { - var ml = new MainLoop (new FakeMainLoop ()); + // Force stop if 20 iterations + var stopCount = 0; + Func fnStop = () => { + stopCount++; + if (stopCount == 20) ml.Stop (); + return true; + }; - var functionCalled = 0; - Func fn1 = () => { - functionCalled++; - if (functionCalled == 10) return false; - return true; - }; + ml.AddIdle (fnStop); + ml.AddIdle (fn1); + ml.Run (); + Assert.True (ml.RemoveIdle (fnStop)); + Assert.False (ml.RemoveIdle (fn1)); - // Force stop if 20 iterations - var stopCount = 0; - Func fnStop = () => { - stopCount++; - if (stopCount == 20) ml.Stop (); - return true; - }; + Assert.Equal (10, functionCalled); + Assert.Equal (20, stopCount); + } - ml.AddIdle (fnStop); - ml.AddIdle (fn1); - ml.Run (); - Assert.True (ml.RemoveIdle (fnStop)); - Assert.False (ml.RemoveIdle (fn1)); + [Fact] + public void AddIdle_Twice_Returns_False_Called_Twice () + { + var ml = new MainLoop (new FakeMainLoop ()); - Assert.Equal (10, functionCalled); - Assert.Equal (20, stopCount); - } + var functionCalled = 0; + Func fn1 = () => { + functionCalled++; + return false; + }; - [Fact] - public void AddIdle_Twice_Returns_False_Called_Twice () - { - var ml = new MainLoop (new FakeMainLoop ()); + // Force stop if 10 iterations + var stopCount = 0; + Func fnStop = () => { + stopCount++; + if (stopCount == 10) ml.Stop (); + return true; + }; - var functionCalled = 0; - Func fn1 = () => { - functionCalled++; - return false; - }; + ml.AddIdle (fnStop); + ml.AddIdle (fn1); + ml.AddIdle (fn1); + ml.Run (); + Assert.True (ml.RemoveIdle (fnStop)); + Assert.False (ml.RemoveIdle (fn1)); + Assert.False (ml.RemoveIdle (fn1)); - // Force stop if 10 iterations - var stopCount = 0; - Func fnStop = () => { - stopCount++; - if (stopCount == 10) ml.Stop (); - return true; - }; + Assert.Equal (2, functionCalled); + } - ml.AddIdle (fnStop); - ml.AddIdle (fn1); - ml.AddIdle (fn1); - ml.Run (); - Assert.True (ml.RemoveIdle (fnStop)); - Assert.False (ml.RemoveIdle (fn1)); - Assert.False (ml.RemoveIdle (fn1)); + [Fact] + public void Run_Runs_Idle_Stop_Stops_Idle () + { + var ml = new MainLoop (new FakeMainLoop ()); - Assert.Equal (2, functionCalled); - } - - [Fact] - public void Run_Runs_Idle_Stop_Stops_Idle () - { - var ml = new MainLoop (new FakeMainLoop ()); - - var functionCalled = 0; - Func fn = () => { - functionCalled++; - if (functionCalled == 10) { - ml.Stop (); - } - return true; - }; - - ml.AddIdle (fn); - ml.Run (); - Assert.True (ml.RemoveIdle (fn)); - - Assert.Equal (10, functionCalled); - } - - // Timeout Handler Tests - [Fact] - public void AddTimer_Adds_Removes_NoFaults () - { - var ml = new MainLoop (new FakeMainLoop ()); - var ms = 100; - - var callbackCount = 0; - Func callback = (loop) => { - callbackCount++; - return true; - }; - - var token = ml.AddTimeout (TimeSpan.FromMilliseconds (ms), callback); - - Assert.True (ml.RemoveTimeout (token)); - - // BUGBUG: This should probably fault? - // Must return a boolean. - Assert.False (ml.RemoveTimeout (token)); - } - - // Timeout Handler Tests - [Fact] - public void AddTimer_EventFired () - { - var ml = new MainLoop (new FakeMainLoop ()); - var ms = 100; - - var originTicks = DateTime.UtcNow.Ticks; - - var callbackCount = 0; - Func callback = (loop) => { - callbackCount++; - return true; - }; - - object sender = null; - TimeoutEventArgs args = null; - ml.TimeoutAdded += (s, e) => { - sender = s; - args = e; - }; - - var token = ml.AddTimeout (TimeSpan.FromMilliseconds (ms), callback); - - Assert.Same (ml, sender); - Assert.NotNull (args.Timeout); - Assert.True (args.Ticks - originTicks >= 100 * TimeSpan.TicksPerMillisecond); - - } - [Fact] - public void AddTimer_Run_Called () - { - var ml = new MainLoop (new FakeMainLoop ()); - var ms = 100; - - var callbackCount = 0; - Func callback = (loop) => { - callbackCount++; + var functionCalled = 0; + Func fn = () => { + functionCalled++; + if (functionCalled == 10) { ml.Stop (); - return true; - }; + } + return true; + }; - var token = ml.AddTimeout (TimeSpan.FromMilliseconds (ms), callback); - ml.Run (); - Assert.True (ml.RemoveTimeout (token)); + ml.AddIdle (fn); + ml.Run (); + Assert.True (ml.RemoveIdle (fn)); - Assert.Equal (1, callbackCount); - } + Assert.Equal (10, functionCalled); + } - [Fact] - public async Task AddTimer_Duplicate_Keys_Not_Allowed () - { - var ml = new MainLoop (new FakeMainLoop ()); - const int ms = 100; - object token1 = null, token2 = null; + // Timeout Handler Tests + [Fact] + public void AddTimer_Adds_Removes_NoFaults () + { + var ml = new MainLoop (new FakeMainLoop ()); + var ms = 100; - var callbackCount = 0; - Func callback = (loop) => { - callbackCount++; - if (callbackCount == 2) ml.Stop (); - return true; - }; + var callbackCount = 0; + Func callback = () => { + callbackCount++; + return true; + }; - var task1 = new Task (() => token1 = ml.AddTimeout (TimeSpan.FromMilliseconds (ms), callback)); - var task2 = new Task (() => token2 = ml.AddTimeout (TimeSpan.FromMilliseconds (ms), callback)); - Assert.Null (token1); - Assert.Null (token2); - task1.Start (); - task2.Start (); - ml.Run (); - Assert.NotNull (token1); - Assert.NotNull (token2); - await Task.WhenAll (task1, task2); - Assert.True (ml.RemoveTimeout (token1)); - Assert.True (ml.RemoveTimeout (token2)); + var token = ml.AddTimeout (TimeSpan.FromMilliseconds (ms), callback); - Assert.Equal (2, callbackCount); - } + Assert.True (ml.RemoveTimeout (token)); - [Fact] - public void AddTimer_In_Parallel_Wont_Throw () - { - var ml = new MainLoop (new FakeMainLoop ()); - const int ms = 100; - object token1 = null, token2 = null; + // BUGBUG: This should probably fault? + // Must return a boolean. + Assert.False (ml.RemoveTimeout (token)); + } - var callbackCount = 0; - Func callback = (loop) => { - callbackCount++; - if (callbackCount == 2) ml.Stop (); - return true; - }; + // Timeout Handler Tests + [Fact] + public void AddTimer_EventFired () + { + var ml = new MainLoop (new FakeMainLoop ()); + var ms = 100; - Parallel.Invoke ( - () => token1 = ml.AddTimeout (TimeSpan.FromMilliseconds (ms), callback), - () => token2 = ml.AddTimeout (TimeSpan.FromMilliseconds (ms), callback) - ); - ml.Run (); - Assert.NotNull (token1); - Assert.NotNull (token2); - Assert.True (ml.RemoveTimeout (token1)); - Assert.True (ml.RemoveTimeout (token2)); + var originTicks = DateTime.UtcNow.Ticks; - Assert.Equal (2, callbackCount); - } + var callbackCount = 0; + Func callback = () => { + callbackCount++; + return true; + }; - class MillisecondTolerance : IEqualityComparer { - int _tolerance = 0; - public MillisecondTolerance (int tolerance) { _tolerance = tolerance; } - public bool Equals (TimeSpan x, TimeSpan y) => Math.Abs (x.Milliseconds - y.Milliseconds) <= _tolerance; - public int GetHashCode (TimeSpan obj) => obj.GetHashCode (); - } + object sender = null; + TimeoutEventArgs args = null; + ml.TimeoutAdded += (s, e) => { + sender = s; + args = e; + }; - [Fact] - public void AddTimer_Run_CalledAtApproximatelyRightTime () - { - var ml = new MainLoop (new FakeMainLoop ()); - var ms = TimeSpan.FromMilliseconds (50); - var watch = new System.Diagnostics.Stopwatch (); + var token = ml.AddTimeout (TimeSpan.FromMilliseconds (ms), callback); - var callbackCount = 0; - Func callback = (loop) => { + Assert.Same (ml, sender); + Assert.NotNull (args.Timeout); + Assert.True (args.Ticks - originTicks >= 100 * TimeSpan.TicksPerMillisecond); + + } + [Fact] + public void AddTimer_Run_Called () + { + var ml = new MainLoop (new FakeMainLoop ()); + var ms = 100; + + var callbackCount = 0; + Func callback = () => { + callbackCount++; + ml.Stop (); + return true; + }; + + var token = ml.AddTimeout (TimeSpan.FromMilliseconds (ms), callback); + ml.Run (); + Assert.True (ml.RemoveTimeout (token)); + + Assert.Equal (1, callbackCount); + } + + [Fact] + public async Task AddTimer_Duplicate_Keys_Not_Allowed () + { + var ml = new MainLoop (new FakeMainLoop ()); + const int ms = 100; + object token1 = null, token2 = null; + + var callbackCount = 0; + Func callback = () => { + callbackCount++; + if (callbackCount == 2) ml.Stop (); + return true; + }; + + var task1 = new Task (() => token1 = ml.AddTimeout (TimeSpan.FromMilliseconds (ms), callback)); + var task2 = new Task (() => token2 = ml.AddTimeout (TimeSpan.FromMilliseconds (ms), callback)); + Assert.Null (token1); + Assert.Null (token2); + task1.Start (); + task2.Start (); + ml.Run (); + Assert.NotNull (token1); + Assert.NotNull (token2); + await Task.WhenAll (task1, task2); + Assert.True (ml.RemoveTimeout (token1)); + Assert.True (ml.RemoveTimeout (token2)); + + Assert.Equal (2, callbackCount); + } + + [Fact] + public void AddTimer_In_Parallel_Wont_Throw () + { + var ml = new MainLoop (new FakeMainLoop ()); + const int ms = 100; + object token1 = null, token2 = null; + + var callbackCount = 0; + Func callback = () => { + callbackCount++; + if (callbackCount == 2) ml.Stop (); + return true; + }; + + Parallel.Invoke ( + () => token1 = ml.AddTimeout (TimeSpan.FromMilliseconds (ms), callback), + () => token2 = ml.AddTimeout (TimeSpan.FromMilliseconds (ms), callback) + ); + ml.Run (); + Assert.NotNull (token1); + Assert.NotNull (token2); + Assert.True (ml.RemoveTimeout (token1)); + Assert.True (ml.RemoveTimeout (token2)); + + Assert.Equal (2, callbackCount); + } + + class MillisecondTolerance : IEqualityComparer { + int _tolerance = 0; + public MillisecondTolerance (int tolerance) { _tolerance = tolerance; } + public bool Equals (TimeSpan x, TimeSpan y) => Math.Abs (x.Milliseconds - y.Milliseconds) <= _tolerance; + public int GetHashCode (TimeSpan obj) => obj.GetHashCode (); + } + + [Fact] + public void AddTimer_Run_CalledAtApproximatelyRightTime () + { + var ml = new MainLoop (new FakeMainLoop ()); + var ms = TimeSpan.FromMilliseconds (50); + var watch = new System.Diagnostics.Stopwatch (); + + var callbackCount = 0; + Func callback = () => { + watch.Stop (); + callbackCount++; + ml.Stop (); + return true; + }; + + var token = ml.AddTimeout (ms, callback); + watch.Start (); + ml.Run (); + // +/- 100ms should be good enuf + // https://github.com/xunit/assert.xunit/pull/25 + Assert.Equal (ms * callbackCount, watch.Elapsed, new MillisecondTolerance (100)); + + Assert.True (ml.RemoveTimeout (token)); + Assert.Equal (1, callbackCount); + } + + [Fact] + public void AddTimer_Run_CalledTwiceApproximatelyRightTime () + { + var ml = new MainLoop (new FakeMainLoop ()); + var ms = TimeSpan.FromMilliseconds (50); + var watch = new System.Diagnostics.Stopwatch (); + + var callbackCount = 0; + Func callback = () => { + callbackCount++; + if (callbackCount == 2) { watch.Stop (); - callbackCount++; ml.Stop (); - return true; - }; - - var token = ml.AddTimeout (ms, callback); - watch.Start (); - ml.Run (); - // +/- 100ms should be good enuf - // https://github.com/xunit/assert.xunit/pull/25 - Assert.Equal (ms * callbackCount, watch.Elapsed, new MillisecondTolerance (100)); - - Assert.True (ml.RemoveTimeout (token)); - Assert.Equal (1, callbackCount); - } - - [Fact] - public void AddTimer_Run_CalledTwiceApproximatelyRightTime () - { - var ml = new MainLoop (new FakeMainLoop ()); - var ms = TimeSpan.FromMilliseconds (50); - var watch = new System.Diagnostics.Stopwatch (); - - var callbackCount = 0; - Func callback = (loop) => { - callbackCount++; - if (callbackCount == 2) { - watch.Stop (); - ml.Stop (); - } - return true; - }; - - var token = ml.AddTimeout (ms, callback); - watch.Start (); - ml.Run (); - // +/- 100ms should be good enuf - // https://github.com/xunit/assert.xunit/pull/25 - Assert.Equal (ms * callbackCount, watch.Elapsed, new MillisecondTolerance (100)); - - Assert.True (ml.RemoveTimeout (token)); - Assert.Equal (2, callbackCount); - } - - [Fact] - public void AddTimer_Remove_NotCalled () - { - var ml = new MainLoop (new FakeMainLoop ()); - var ms = TimeSpan.FromMilliseconds (50); - - // Force stop if 10 iterations - var stopCount = 0; - Func fnStop = () => { - stopCount++; - if (stopCount == 10) ml.Stop (); - return true; - }; - ml.AddIdle (fnStop); - - var callbackCount = 0; - Func callback = (loop) => { - callbackCount++; - return true; - }; - - var token = ml.AddTimeout (ms, callback); - Assert.True (ml.RemoveTimeout (token)); - ml.Run (); - Assert.Equal (0, callbackCount); - } - - [Fact] - public void AddTimer_ReturnFalse_StopsBeingCalled () - { - var ml = new MainLoop (new FakeMainLoop ()); - var ms = TimeSpan.FromMilliseconds (50); - - // Force stop if 10 iterations - var stopCount = 0; - Func fnStop = () => { - Thread.Sleep (10); // Sleep to enable timer to fire - stopCount++; - if (stopCount == 10) ml.Stop (); - return true; - }; - ml.AddIdle (fnStop); - - var callbackCount = 0; - Func callback = (loop) => { - callbackCount++; - return false; - }; - - var token = ml.AddTimeout (ms, callback); - ml.Run (); - Assert.Equal (1, callbackCount); - Assert.Equal (10, stopCount); - Assert.False (ml.RemoveTimeout (token)); - } - - // Invoke Tests - // TODO: Test with threading scenarios - [Fact] - public void Invoke_Adds_Idle () - { - var ml = new MainLoop (new FakeMainLoop ()); - - var actionCalled = 0; - ml.Invoke (() => { actionCalled++; }); - ml.RunIteration (); - Assert.Equal (1, actionCalled); - } - - [Fact] - public void CheckTimersAndIdleHandlers_NoTimers_Returns_False () - { - var ml = new MainLoop (new FakeMainLoop ()); - var retVal = ml.CheckTimersAndIdleHandlers (out var waitTimeOut); - Assert.False (retVal); - Assert.Equal (-1, waitTimeOut); - } - - [Fact] - public void CheckTimersAndIdleHandlers_NoTimers_WithIdle_Returns_True () - { - var ml = new MainLoop (new FakeMainLoop ()); - Func fnTrue = () => true; - - ml.AddIdle (fnTrue); - var retVal = ml.CheckTimersAndIdleHandlers (out var waitTimeOut); - Assert.True (retVal); - Assert.Equal (-1, waitTimeOut); - } - - [Fact] - public void CheckTimersAndIdleHandlers_With1Timer_Returns_Timer () - { - var ml = new MainLoop (new FakeMainLoop ()); - var ms = TimeSpan.FromMilliseconds (50); - - static bool Callback (MainLoop loop) => false; - - _ = ml.AddTimeout (ms, Callback); - var retVal = ml.CheckTimersAndIdleHandlers (out var waitTimeOut); - - Assert.True (retVal); - // It should take < 10ms to execute to here - Assert.True (ms.TotalMilliseconds <= (waitTimeOut + 10)); - } - - [Fact] - public void CheckTimersAndIdleHandlers_With2Timers_Returns_Timer () - { - var ml = new MainLoop (new FakeMainLoop ()); - var ms = TimeSpan.FromMilliseconds (50); - - static bool Callback (MainLoop loop) => false; - - _ = ml.AddTimeout (ms, Callback); - _ = ml.AddTimeout (ms, Callback); - var retVal = ml.CheckTimersAndIdleHandlers (out var waitTimeOut); - - Assert.True (retVal); - // It should take < 10ms to execute to here - Assert.True (ms.TotalMilliseconds <= (waitTimeOut + 10)); - } - - [Fact] - public void Internal_Tests () - { - var testMainloop = new TestMainloop (); - var mainloop = new MainLoop (testMainloop); - Assert.Empty (mainloop._timeouts); - Assert.Empty (mainloop._idleHandlers); - Assert.NotNull (new Timeout () { - Span = new TimeSpan (), - Callback = (_) => true - }); - } - - private class TestMainloop : IMainLoopDriver { - private MainLoop mainLoop; - - public bool EventsPending () - { - throw new NotImplementedException (); } + return true; + }; - public void Iteration () - { - throw new NotImplementedException (); - } - public void TearDown () - { - throw new NotImplementedException (); - } + var token = ml.AddTimeout (ms, callback); + watch.Start (); + ml.Run (); + // +/- 100ms should be good enuf + // https://github.com/xunit/assert.xunit/pull/25 + Assert.Equal (ms * callbackCount, watch.Elapsed, new MillisecondTolerance (100)); - public void Setup (MainLoop mainLoop) - { - this.mainLoop = mainLoop; - } + Assert.True (ml.RemoveTimeout (token)); + Assert.Equal (2, callbackCount); + } - public void Wakeup () - { - throw new NotImplementedException (); - } - } + [Fact] + public void AddTimer_Remove_NotCalled () + { + var ml = new MainLoop (new FakeMainLoop ()); + var ms = TimeSpan.FromMilliseconds (50); - // TODO: EventsPending tests - // - wait = true - // - wait = false + // Force stop if 10 iterations + var stopCount = 0; + Func fnStop = () => { + stopCount++; + if (stopCount == 10) ml.Stop (); + return true; + }; + ml.AddIdle (fnStop); - // TODO: Add IMainLoop tests + var callbackCount = 0; + Func callback = () => { + callbackCount++; + return true; + }; - volatile static int tbCounter = 0; - static ManualResetEventSlim _wakeUp = new ManualResetEventSlim (false); + var token = ml.AddTimeout (ms, callback); + Assert.True (ml.RemoveTimeout (token)); + ml.Run (); + Assert.Equal (0, callbackCount); + } - private static void Launch (Random r, TextField tf, int target) + [Fact] + public void AddTimer_ReturnFalse_StopsBeingCalled () + { + var ml = new MainLoop (new FakeMainLoop ()); + var ms = TimeSpan.FromMilliseconds (50); + + // Force stop if 10 iterations + var stopCount = 0; + Func fnStop = () => { + Thread.Sleep (10); // Sleep to enable timer to fire + stopCount++; + if (stopCount == 10) ml.Stop (); + return true; + }; + ml.AddIdle (fnStop); + + var callbackCount = 0; + Func callback = () => { + callbackCount++; + return false; + }; + + var token = ml.AddTimeout (ms, callback); + ml.Run (); + Assert.Equal (1, callbackCount); + Assert.Equal (10, stopCount); + Assert.False (ml.RemoveTimeout (token)); + } + + [Fact] + public void CheckTimersAndIdleHandlers_NoTimers_Returns_False () + { + var ml = new MainLoop (new FakeMainLoop ()); + var retVal = ml.CheckTimersAndIdleHandlers (out var waitTimeOut); + Assert.False (retVal); + Assert.Equal (-1, waitTimeOut); + } + + [Fact] + public void CheckTimersAndIdleHandlers_NoTimers_WithIdle_Returns_True () + { + var ml = new MainLoop (new FakeMainLoop ()); + Func fnTrue = () => true; + + ml.AddIdle (fnTrue); + var retVal = ml.CheckTimersAndIdleHandlers (out var waitTimeOut); + Assert.True (retVal); + Assert.Equal (-1, waitTimeOut); + } + + [Fact] + public void CheckTimersAndIdleHandlers_With1Timer_Returns_Timer () + { + var ml = new MainLoop (new FakeMainLoop ()); + var ms = TimeSpan.FromMilliseconds (50); + + static bool Callback () => false; + + _ = ml.AddTimeout (ms, Callback); + var retVal = ml.CheckTimersAndIdleHandlers (out var waitTimeOut); + + Assert.True (retVal); + // It should take < 10ms to execute to here + Assert.True (ms.TotalMilliseconds <= (waitTimeOut + 10)); + } + + [Fact] + public void CheckTimersAndIdleHandlers_With2Timers_Returns_Timer () + { + var ml = new MainLoop (new FakeMainLoop ()); + var ms = TimeSpan.FromMilliseconds (50); + + static bool Callback () => false; + + _ = ml.AddTimeout (ms, Callback); + _ = ml.AddTimeout (ms, Callback); + var retVal = ml.CheckTimersAndIdleHandlers (out var waitTimeOut); + + Assert.True (retVal); + // It should take < 10ms to execute to here + Assert.True (ms.TotalMilliseconds <= (waitTimeOut + 10)); + } + + [Fact] + public void Internal_Tests () + { + var testMainloop = new TestMainloop (); + var mainloop = new MainLoop (testMainloop); + Assert.Empty (mainloop._timeouts); + Assert.Empty (mainloop._idleHandlers); + Assert.NotNull (new Timeout () { + Span = new TimeSpan (), + Callback = () => true + }); + } + + private class TestMainloop : IMainLoopDriver { + private MainLoop mainLoop; + + public bool EventsPending () { - Task.Run (() => { - Thread.Sleep (r.Next (2, 4)); - Application.MainLoop.Invoke (() => { - tf.Text = $"index{r.Next ()}"; - Interlocked.Increment (ref tbCounter); - if (target == tbCounter) { - // On last increment wake up the check - _wakeUp.Set (); - } - }); - }); + throw new NotImplementedException (); } - private static void RunTest (Random r, TextField tf, int numPasses, int numIncrements, int pollMs) + public void Iteration () { - for (int j = 0; j < numPasses; j++) { - - _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; - _wakeUp.Wait (pollMs); - if (tbCounter == tbNow) { - // No change after wait: Idle handlers added via Application.MainLoop.Invoke have gone missing - Application.MainLoop.Invoke (() => Application.RequestStop ()); - throw new TimeoutException ( - $"Timeout: Increment lost. tbCounter ({tbCounter}) didn't " + - $"change after waiting {pollMs} ms. Failed to reach {(j + 1) * numIncrements} on pass {j + 1}"); - } - }; - } - Application.MainLoop.Invoke (() => Application.RequestStop ()); + throw new NotImplementedException (); } - - [Fact] - [AutoInitShutdown] - public async Task InvokeLeakTest () + public void TearDown () { - Random r = new (); - TextField tf = new (); - Application.Top.Add (tf); - - const int numPasses = 5; - const int numIncrements = 5000; - const int pollMs = 10000; - - var task = Task.Run (() => RunTest (r, tf, numPasses, numIncrements, pollMs)); - - // blocks here until the RequestStop is processed at the end of the test - Application.Run (); - - await task; // Propagate exception if any occurred - - Assert.Equal (numIncrements * numPasses, tbCounter); + throw new NotImplementedException (); } - private static int total; - private static Button btn; - private static string clickMe; - private static string cancel; - private static string pewPew; - private static int zero; - private static int one; - private static int two; - private static int three; - private static int four; - private static bool taskCompleted; - - [Theory, AutoInitShutdown] - [MemberData (nameof (TestAddIdle))] - public void Mainloop_Invoke_Or_AddIdle_Can_Be_Used_For_Events_Or_Actions (Action action, string pclickMe, string pcancel, string ppewPew, int pzero, int pone, int ptwo, int pthree, int pfour) + public void Setup (MainLoop mainLoop) { - total = 0; - btn = null; - clickMe = pclickMe; - cancel = pcancel; - pewPew = ppewPew; - zero = pzero; - one = pone; - two = ptwo; - three = pthree; - four = pfour; - taskCompleted = false; - - var btnLaunch = new Button ("Open Window"); - - btnLaunch.Clicked += (s, e) => action (); - - Application.Top.Add (btnLaunch); - - var iterations = -1; - - Application.Iteration += () => { - iterations++; - if (iterations == 0) { - Assert.Null (btn); - Assert.Equal (zero, total); - Assert.True (btnLaunch.ProcessKey (new KeyEvent (Key.Enter, null))); - if (btn == null) { - Assert.Null (btn); - Assert.Equal (zero, total); - } else { - Assert.Equal (clickMe, btn.Text); - Assert.Equal (four, total); - } - } else if (iterations == 1) { - Assert.Equal (clickMe, btn.Text); - Assert.Equal (zero, total); - Assert.True (btn.ProcessKey (new KeyEvent (Key.Enter, null))); - Assert.Equal (cancel, btn.Text); - Assert.Equal (one, total); - } else if (taskCompleted) { - Application.RequestStop (); - } - }; - - Application.Run (); - - Assert.True (taskCompleted); - Assert.Equal (clickMe, btn.Text); - Assert.Equal (four, total); + this.mainLoop = mainLoop; } - public static IEnumerable TestAddIdle { - get { - // Goes fine - Action a1 = StartWindow; - yield return new object [] { a1, "Click Me", "Cancel", "Pew Pew", 0, 1, 2, 3, 4 }; - - // Also goes fine - Action a2 = () => Application.MainLoop.Invoke (StartWindow); - yield return new object [] { a2, "Click Me", "Cancel", "Pew Pew", 0, 1, 2, 3, 4 }; - } - } - - private static void StartWindow () + public void Wakeup () { - var startWindow = new Window { - Modal = true - }; - - btn = new Button { - Text = "Click Me" - }; - - btn.Clicked += RunAsyncTest; - - var totalbtn = new Button () { - X = Pos.Right (btn), - Text = "total" - }; - - totalbtn.Clicked += (s, e) => { - MessageBox.Query ("Count", $"Count is {total}", "Ok"); - }; - - startWindow.Add (btn); - startWindow.Add (totalbtn); - - Application.Run (startWindow); - - Assert.Equal (clickMe, btn.Text); - Assert.Equal (four, total); - - Application.RequestStop (); - } - - private static async void RunAsyncTest (object sender, EventArgs e) - { - Assert.Equal (clickMe, btn.Text); - Assert.Equal (zero, total); - - btn.Text = "Cancel"; - Interlocked.Increment (ref total); - btn.SetNeedsDisplay (); - - await Task.Run (() => { - try { - Assert.Equal (cancel, btn.Text); - Assert.Equal (one, total); - - RunSql (); - } finally { - SetReadyToRun (); - } - }).ContinueWith (async (s, e) => { - - await Task.Delay (1000); - Assert.Equal (clickMe, btn.Text); - Assert.Equal (three, total); - - Interlocked.Increment (ref total); - - Assert.Equal (clickMe, btn.Text); - Assert.Equal (four, total); - - taskCompleted = true; - - }, TaskScheduler.FromCurrentSynchronizationContext ()); - } - - private static void RunSql () - { - Thread.Sleep (100); - Assert.Equal (cancel, btn.Text); - Assert.Equal (one, total); - - Application.MainLoop.Invoke (() => { - btn.Text = "Pew Pew"; - Interlocked.Increment (ref total); - btn.SetNeedsDisplay (); - }); - } - - private static void SetReadyToRun () - { - Thread.Sleep (100); - Assert.Equal (pewPew, btn.Text); - Assert.Equal (two, total); - - Application.MainLoop.Invoke (() => { - btn.Text = "Click Me"; - Interlocked.Increment (ref total); - btn.SetNeedsDisplay (); - }); + throw new NotImplementedException (); } } + + // TODO: EventsPending tests + // - wait = true + // - wait = false + + // TODO: Add IMainLoop tests + + volatile static int tbCounter = 0; + static ManualResetEventSlim _wakeUp = new ManualResetEventSlim (false); + + private static void Launch (Random r, TextField tf, int target) + { + Task.Run (() => { + Thread.Sleep (r.Next (2, 4)); + Application.Invoke (() => { + tf.Text = $"index{r.Next ()}"; + Interlocked.Increment (ref tbCounter); + if (target == tbCounter) { + // On last increment wake up the check + _wakeUp.Set (); + } + }); + }); + } + + private static void RunTest (Random r, TextField tf, int numPasses, int numIncrements, int pollMs) + { + for (int j = 0; j < numPasses; j++) { + + _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; + _wakeUp.Wait (pollMs); + if (tbCounter == tbNow) { + // No change after wait: Idle handlers added via Application.Invoke have gone missing + Application.Invoke (() => Application.RequestStop ()); + throw new TimeoutException ( + $"Timeout: Increment lost. tbCounter ({tbCounter}) didn't " + + $"change after waiting {pollMs} ms. Failed to reach {(j + 1) * numIncrements} on pass {j + 1}"); + } + }; + } + Application.Invoke (() => Application.RequestStop ()); + } + + [Fact] + [AutoInitShutdown] + public async Task InvokeLeakTest () + { + Random r = new (); + TextField tf = new (); + Application.Top.Add (tf); + + const int numPasses = 5; + const int numIncrements = 5000; + const int pollMs = 10000; + + var task = Task.Run (() => RunTest (r, tf, numPasses, numIncrements, pollMs)); + + // blocks here until the RequestStop is processed at the end of the test + Application.Run (); + + await task; // Propagate exception if any occurred + + Assert.Equal (numIncrements * numPasses, tbCounter); + } + + private static int total; + private static Button btn; + private static string clickMe; + private static string cancel; + private static string pewPew; + private static int zero; + private static int one; + private static int two; + private static int three; + private static int four; + private static bool taskCompleted; + + [Theory, AutoInitShutdown] + [MemberData (nameof (TestAddIdle))] + public void Mainloop_Invoke_Or_AddIdle_Can_Be_Used_For_Events_Or_Actions (Action action, string pclickMe, string pcancel, string ppewPew, int pzero, int pone, int ptwo, int pthree, int pfour) + { + total = 0; + btn = null; + clickMe = pclickMe; + cancel = pcancel; + pewPew = ppewPew; + zero = pzero; + one = pone; + two = ptwo; + three = pthree; + four = pfour; + taskCompleted = false; + + var btnLaunch = new Button ("Open Window"); + + btnLaunch.Clicked += (s, e) => action (); + + Application.Top.Add (btnLaunch); + + var iterations = -1; + + Application.Iteration += (s, a) => { + iterations++; + if (iterations == 0) { + Assert.Null (btn); + Assert.Equal (zero, total); + Assert.True (btnLaunch.ProcessKey (new KeyEvent (Key.Enter, null))); + if (btn == null) { + Assert.Null (btn); + Assert.Equal (zero, total); + } else { + Assert.Equal (clickMe, btn.Text); + Assert.Equal (four, total); + } + } else if (iterations == 1) { + Assert.Equal (clickMe, btn.Text); + Assert.Equal (zero, total); + Assert.True (btn.ProcessKey (new KeyEvent (Key.Enter, null))); + Assert.Equal (cancel, btn.Text); + Assert.Equal (one, total); + } else if (taskCompleted) { + Application.RequestStop (); + } + }; + + Application.Run (); + + Assert.True (taskCompleted); + Assert.Equal (clickMe, btn.Text); + Assert.Equal (four, total); + } + + public static IEnumerable TestAddIdle { + get { + // Goes fine + Action a1 = StartWindow; + yield return new object [] { a1, "Click Me", "Cancel", "Pew Pew", 0, 1, 2, 3, 4 }; + + // Also goes fine + Action a2 = () => Application.Invoke (StartWindow); + yield return new object [] { a2, "Click Me", "Cancel", "Pew Pew", 0, 1, 2, 3, 4 }; + } + } + + private static void StartWindow () + { + var startWindow = new Window { + Modal = true + }; + + btn = new Button { + Text = "Click Me" + }; + + btn.Clicked += RunAsyncTest; + + var totalbtn = new Button () { + X = Pos.Right (btn), + Text = "total" + }; + + totalbtn.Clicked += (s, e) => { + MessageBox.Query ("Count", $"Count is {total}", "Ok"); + }; + + startWindow.Add (btn); + startWindow.Add (totalbtn); + + Application.Run (startWindow); + + Assert.Equal (clickMe, btn.Text); + Assert.Equal (four, total); + + Application.RequestStop (); + } + + private static async void RunAsyncTest (object sender, EventArgs e) + { + Assert.Equal (clickMe, btn.Text); + Assert.Equal (zero, total); + + btn.Text = "Cancel"; + Interlocked.Increment (ref total); + btn.SetNeedsDisplay (); + + await Task.Run (() => { + try { + Assert.Equal (cancel, btn.Text); + Assert.Equal (one, total); + + RunSql (); + } finally { + SetReadyToRun (); + } + }).ContinueWith (async (s, e) => { + + await Task.Delay (1000); + Assert.Equal (clickMe, btn.Text); + Assert.Equal (three, total); + + Interlocked.Increment (ref total); + + Assert.Equal (clickMe, btn.Text); + Assert.Equal (four, total); + + taskCompleted = true; + + }, TaskScheduler.FromCurrentSynchronizationContext ()); + } + + private static void RunSql () + { + Thread.Sleep (100); + Assert.Equal (cancel, btn.Text); + Assert.Equal (one, total); + + Application.Invoke (() => { + btn.Text = "Pew Pew"; + Interlocked.Increment (ref total); + btn.SetNeedsDisplay (); + }); + } + + private static void SetReadyToRun () + { + Thread.Sleep (100); + Assert.Equal (pewPew, btn.Text); + Assert.Equal (two, total); + + Application.Invoke (() => { + btn.Text = "Click Me"; + Interlocked.Increment (ref total); + btn.SetNeedsDisplay (); + }); + } } diff --git a/UnitTests/Application/SynchronizatonContextTests.cs b/UnitTests/Application/SynchronizatonContextTests.cs index c40682356..6c6566a88 100644 --- a/UnitTests/Application/SynchronizatonContextTests.cs +++ b/UnitTests/Application/SynchronizatonContextTests.cs @@ -31,7 +31,7 @@ namespace Terminal.Gui.ApplicationTests { success = true; // then tell the application to quit - Application.MainLoop.Invoke (() => Application.RequestStop ()); + Application.Invoke (() => Application.RequestStop ()); }, null); Assert.False (success); }); @@ -56,7 +56,7 @@ namespace Terminal.Gui.ApplicationTests { success = true; // then tell the application to quit - Application.MainLoop.Invoke (() => Application.RequestStop ()); + Application.Invoke (() => Application.RequestStop ()); }, null); Assert.True (success); }); diff --git a/UnitTests/Clipboard/ClipboardTests.cs b/UnitTests/Clipboard/ClipboardTests.cs index 7ea054345..1440780f9 100644 --- a/UnitTests/Clipboard/ClipboardTests.cs +++ b/UnitTests/Clipboard/ClipboardTests.cs @@ -36,7 +36,7 @@ namespace Terminal.Gui.ClipboardTests { var clipText = "The Contents_Gets_Sets unit test pasted this to the OS clipboard."; Clipboard.Contents = clipText; - Application.Iteration += () => Application.RequestStop (); + Application.Iteration += (s, a) => Application.RequestStop (); Application.Run (); Assert.Equal (clipText, Clipboard.Contents); @@ -53,7 +53,7 @@ namespace Terminal.Gui.ClipboardTests { var clipText = "The Contents_Gets_Sets unit test pasted this to the OS clipboard."; Clipboard.Contents = clipText; - Application.Iteration += () => Application.RequestStop (); + Application.Iteration += (s, a) => Application.RequestStop (); Application.Run (); Assert.Equal (clipText, Clipboard.Contents); @@ -71,7 +71,7 @@ namespace Terminal.Gui.ClipboardTests { var clipText = "The Contents_Gets_Sets unit test pasted this to the OS clipboard."; Clipboard.Contents = clipText; - Application.Iteration += () => Application.RequestStop (); + Application.Iteration += (s, a) => Application.RequestStop (); Application.Run (); Assert.Equal (clipText, Clipboard.Contents); @@ -89,7 +89,7 @@ namespace Terminal.Gui.ClipboardTests { var clipText = "The Contents_Gets_Sets unit test pasted this to the OS clipboard."; Clipboard.Contents = clipText; - Application.Iteration += () => Application.RequestStop (); + Application.Iteration += (s, a) => Application.RequestStop (); Application.Run (); Assert.Equal (clipText, Clipboard.Contents); @@ -108,7 +108,7 @@ namespace Terminal.Gui.ClipboardTests { var clipText = "The TryGetClipboardData_Gets_From_OS_Clipboard unit test pasted this to the OS clipboard."; Clipboard.Contents = clipText; - Application.Iteration += () => Application.RequestStop (); + Application.Iteration += (s, a) => Application.RequestStop (); Application.Run (); @@ -128,7 +128,7 @@ namespace Terminal.Gui.ClipboardTests { if (Clipboard.IsSupported) Assert.True (Clipboard.TrySetClipboardData (clipText)); else Assert.False (Clipboard.TrySetClipboardData (clipText)); - Application.Iteration += () => Application.RequestStop (); + Application.Iteration += (s, a) => Application.RequestStop (); Application.Run (); @@ -150,7 +150,7 @@ namespace Terminal.Gui.ClipboardTests { var failed = false; var getClipText = ""; - Application.Iteration += () => { + Application.Iteration += (s, a) => { int exitCode = 0; string result = ""; output.WriteLine ($"Pasting to OS clipboard: {clipText}..."); @@ -221,7 +221,7 @@ namespace Terminal.Gui.ClipboardTests { var clipReadText = ""; var failed = false; - Application.Iteration += () => { + Application.Iteration += (s, a) => { Clipboard.Contents = clipText; int exitCode = 0; diff --git a/UnitTests/Configuration/ConfigurationMangerTests.cs b/UnitTests/Configuration/ConfigurationMangerTests.cs index c7f6d83da..5bd36a38c 100644 --- a/UnitTests/Configuration/ConfigurationMangerTests.cs +++ b/UnitTests/Configuration/ConfigurationMangerTests.cs @@ -3,12 +3,9 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; -using System.Reflection.Metadata; using System.Text.Json; -using Terminal.Gui; using Xunit; using static Terminal.Gui.ConfigurationManager; -using Attribute = Terminal.Gui.Attribute; namespace Terminal.Gui.ConfigurationTests { public class ConfigurationManagerTests { diff --git a/UnitTests/ConsoleDrivers/AddRuneTests.cs b/UnitTests/ConsoleDrivers/AddRuneTests.cs index 5d2e7c5a1..a58f0a83e 100644 --- a/UnitTests/ConsoleDrivers/AddRuneTests.cs +++ b/UnitTests/ConsoleDrivers/AddRuneTests.cs @@ -20,7 +20,7 @@ public class AddRuneTests { var driver = new FakeDriver (); Application.Init (driver); - driver.Init (() => { }); + driver.Init (); driver.AddRune (new Rune ('a')); Assert.Equal ((Rune)'a', driver.Contents [0, 0].Runes [0]); @@ -34,7 +34,7 @@ public class AddRuneTests { { var driver = new FakeDriver (); Application.Init (driver); - driver.Init (() => { }); + driver.Init (); driver.Move (driver.Cols, driver.Rows); driver.AddRune ('a'); @@ -54,7 +54,7 @@ public class AddRuneTests { { var driver = new FakeDriver (); Application.Init (driver); - driver.Init (() => { }); + driver.Init (); driver.AddRune ('a'); Assert.Equal ((Rune)'a', driver.Contents [0, 0].Runes [0]); @@ -95,7 +95,7 @@ public class AddRuneTests { { var driver = new FakeDriver (); Application.Init (driver); - driver.Init (() => { }); + driver.Init (); // 🍕 Slice of Pizza "\U0001F355" var operationStatus = Rune.DecodeFromUtf16 ("\U0001F355", out Rune rune, out int charsConsumed); @@ -143,7 +143,7 @@ public class AddRuneTests { { var driver = new FakeDriver (); Application.Init (driver); - driver.Init (() => { }); + driver.Init (); var expected = new Rune ('ắ'); diff --git a/UnitTests/ConsoleDrivers/ConsoleDriverTests.cs b/UnitTests/ConsoleDrivers/ConsoleDriverTests.cs index 405ffb404..f36a550b1 100644 --- a/UnitTests/ConsoleDrivers/ConsoleDriverTests.cs +++ b/UnitTests/ConsoleDrivers/ConsoleDriverTests.cs @@ -27,7 +27,7 @@ namespace Terminal.Gui.DriverTests { { var driver = (ConsoleDriver)Activator.CreateInstance (driverType); Application.Init (driver); - driver.Init (() => { }); + driver.Init (); Assert.Equal (80, Console.BufferWidth); Assert.Equal (25, Console.BufferHeight); @@ -50,7 +50,7 @@ namespace Terminal.Gui.DriverTests { { var driver = (ConsoleDriver)Activator.CreateInstance (driverType); Application.Init (driver); - driver.Init (() => { }); + driver.Init (); Console.ForegroundColor = ConsoleColor.Red; Assert.Equal (ConsoleColor.Red, Console.ForegroundColor); @@ -81,12 +81,12 @@ namespace Terminal.Gui.DriverTests { var count = 0; var wasKeyPressed = false; - view.KeyPress += (s, e) => { + view.KeyPressed += (s, e) => { wasKeyPressed = true; }; top.Add (view); - Application.Iteration += () => { + Application.Iteration += (s, a) => { count++; if (count == 10) Application.RequestStop (); }; @@ -120,7 +120,7 @@ namespace Terminal.Gui.DriverTests { var rText = ""; var idx = 0; - view.KeyPress += (s, e) => { + view.KeyPressed += (s, e) => { Assert.Equal (text [idx], (char)e.KeyEvent.Key); rText += (char)e.KeyEvent.Key; Assert.Equal (rText, text.Substring (0, idx + 1)); @@ -129,7 +129,7 @@ namespace Terminal.Gui.DriverTests { }; top.Add (view); - Application.Iteration += () => { + Application.Iteration += (s, a) => { if (mKeys.Count == 0) Application.RequestStop (); }; @@ -160,7 +160,7 @@ namespace Terminal.Gui.DriverTests { // return false; // }; // output.WriteLine ($"Add timeout to simulate key presses after {quitTime}ms"); - // _ = Application.MainLoop.AddTimeout (TimeSpan.FromMilliseconds (quitTime), closeCallback); + // _ = Application.AddTimeout (TimeSpan.FromMilliseconds (quitTime), closeCallback); // // If Top doesn't quit within abortTime * 5 (500ms), this will force it // uint abortTime = quitTime * 5; @@ -170,7 +170,7 @@ namespace Terminal.Gui.DriverTests { // return false; // }; // output.WriteLine ($"Add timeout to force quit after {abortTime}ms"); - // _ = Application.MainLoop.AddTimeout (TimeSpan.FromMilliseconds (abortTime), forceCloseCallback); + // _ = Application.AddTimeout (TimeSpan.FromMilliseconds (abortTime), forceCloseCallback); // Key key = Key.Unknown; @@ -182,7 +182,7 @@ namespace Terminal.Gui.DriverTests { // }; // int iterations = 0; - // Application.Iteration += () => { + // Application.Iteration += (s, a) => { // output.WriteLine ($" iteration {++iterations}"); // if (Console.MockKeyPresses.Count == 0) { @@ -204,10 +204,10 @@ namespace Terminal.Gui.DriverTests { var driver = (FakeDriver)Activator.CreateInstance (driverType); Application.Init (driver); var wasTerminalResized = false; - Application.TerminalResized = (e) => { + Application.SizeChanging += (s, e) => { wasTerminalResized = true; - Assert.Equal (120, e.Cols); - Assert.Equal (40, e.Rows); + Assert.Equal (120, e.Size.Width); + Assert.Equal (40, e.Size.Height); }; Assert.Equal (80, Console.BufferWidth); @@ -238,7 +238,7 @@ namespace Terminal.Gui.DriverTests { // System.Threading.Tasks.Task.Run (() => { // System.Threading.Tasks.Task.Delay (500).Wait (); -// Application.MainLoop.Invoke (() => { +// Application.Invoke (() => { // var lbl = new Label ("Hello World") { X = Pos.Center () }; // var dlg = new Dialog (); // dlg.Add (lbl); diff --git a/UnitTests/ConsoleDrivers/DriverColorTests.cs b/UnitTests/ConsoleDrivers/DriverColorTests.cs index d9de6973e..80c5a8caa 100644 --- a/UnitTests/ConsoleDrivers/DriverColorTests.cs +++ b/UnitTests/ConsoleDrivers/DriverColorTests.cs @@ -47,7 +47,7 @@ namespace Terminal.Gui.DriverTests { public void SupportsTrueColor_Defaults (Type driverType, bool expectedSetting) { var driver = (ConsoleDriver)Activator.CreateInstance (driverType); - driver.Init (() => { }); + driver.Init (); Assert.Equal (expectedSetting, driver.SupportsTrueColor); @@ -65,7 +65,7 @@ namespace Terminal.Gui.DriverTests { public void Force16Colors_Sets (Type driverType) { var driver = (ConsoleDriver)Activator.CreateInstance (driverType); - driver.Init (() => { }); + driver.Init (); driver.Force16Colors = true; Assert.True (driver.Force16Colors); diff --git a/UnitTests/ConsoleDrivers/KeyTests.cs b/UnitTests/ConsoleDrivers/KeyTests.cs index 56e6d3c1f..b7ad45b28 100644 --- a/UnitTests/ConsoleDrivers/KeyTests.cs +++ b/UnitTests/ConsoleDrivers/KeyTests.cs @@ -208,7 +208,7 @@ namespace Terminal.Gui.InputTests { var top = Application.Top; - top.KeyPress += (s, e) => { + top.KeyPressed += (s, e) => { var after = ShortcutHelper.GetModifiersKey (e.KeyEvent); Assert.Equal (expectedRemapping, after); e.Handled = true; @@ -217,7 +217,7 @@ namespace Terminal.Gui.InputTests { var iterations = -1; - Application.Iteration += () => { + Application.Iteration += (s, a) => { iterations++; if (iterations == 0) Application.Driver.SendKeys ((char)mappedConsoleKey, ConsoleKey.Packet, shift, alt, control); }; diff --git a/UnitTests/ConsoleDrivers/MainLoopDriverTests.cs b/UnitTests/ConsoleDrivers/MainLoopDriverTests.cs new file mode 100644 index 000000000..01bea55f6 --- /dev/null +++ b/UnitTests/ConsoleDrivers/MainLoopDriverTests.cs @@ -0,0 +1,270 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using Xunit; +using Xunit.Abstractions; + +// Alias Console to MockConsole so we don't accidentally use Console +using Console = Terminal.Gui.FakeConsole; + +namespace Terminal.Gui.DriverTests; + +public class MainLoopDriverTests { + + public MainLoopDriverTests (ITestOutputHelper output) + { + ConsoleDriver.RunningUnitTests = true; + } + + [Theory] + [InlineData (typeof (FakeDriver), typeof (FakeMainLoop))] + [InlineData (typeof (NetDriver), typeof (NetMainLoop))] + [InlineData (typeof (CursesDriver), typeof (UnixMainLoop))] + [InlineData (typeof (WindowsDriver), typeof (WindowsMainLoop))] + public void MainLoop_Constructs_Disposes (Type driverType, Type mainLoopDriverType) + { + var driver = (ConsoleDriver)Activator.CreateInstance (driverType); + var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, new object [] { driver }); + var mainLoop = new MainLoop (mainLoopDriver); + + // Check default values + Assert.NotNull (mainLoop); + Assert.Equal (mainLoopDriver, mainLoop.MainLoopDriver); + Assert.Empty (mainLoop.IdleHandlers); + Assert.Empty (mainLoop.Timeouts); + Assert.False (mainLoop.Running); + + // Clean up + mainLoop.Dispose (); + // TODO: It'd be nice if we could really verify IMainLoopDriver.TearDown was called + // and that it was actually cleaned up. + Assert.Null (mainLoop.MainLoopDriver); + Assert.Empty (mainLoop.IdleHandlers); + Assert.Empty (mainLoop.Timeouts); + Assert.False (mainLoop.Running); + } + + [Theory] + [InlineData (typeof (FakeDriver), typeof (FakeMainLoop))] + [InlineData (typeof (NetDriver), typeof (NetMainLoop))] + [InlineData (typeof (CursesDriver), typeof (UnixMainLoop))] + [InlineData (typeof (WindowsDriver), typeof (WindowsMainLoop))] + public void MainLoop_AddTimeout_ValidParameters_ReturnsToken (Type driverType, Type mainLoopDriverType) + { + var driver = (ConsoleDriver)Activator.CreateInstance (driverType); + var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, new object [] { driver }); + var mainLoop = new MainLoop (mainLoopDriver); + var callbackInvoked = false; + + var token = mainLoop.AddTimeout (TimeSpan.FromMilliseconds (100), () => { + callbackInvoked = true; + return false; + }); + + Assert.NotNull (token); + mainLoop.RunIteration (); // Run an iteration to process the timeout + Assert.False (callbackInvoked); // Callback should not be invoked immediately + Thread.Sleep (200); // Wait for the timeout + mainLoop.RunIteration (); // Run an iteration to process the timeout + Assert.True (callbackInvoked); // Callback should be invoked after the timeout + mainLoop.Dispose (); + } + + [Theory] + [InlineData (typeof (FakeDriver), typeof (FakeMainLoop))] + [InlineData (typeof (NetDriver), typeof (NetMainLoop))] + [InlineData (typeof (CursesDriver), typeof (UnixMainLoop))] + [InlineData (typeof (WindowsDriver), typeof (WindowsMainLoop))] + public void MainLoop_RemoveTimeout_ValidToken_ReturnsTrue (Type driverType, Type mainLoopDriverType) + { + var driver = (ConsoleDriver)Activator.CreateInstance (driverType); + var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, new object [] { driver }); + var mainLoop = new MainLoop (mainLoopDriver); + + var token = mainLoop.AddTimeout (TimeSpan.FromMilliseconds (100), () => false); + var result = mainLoop.RemoveTimeout (token); + + Assert.True (result); + mainLoop.Dispose (); + } + + [Theory] + [InlineData (typeof (FakeDriver), typeof (FakeMainLoop))] + [InlineData (typeof (NetDriver), typeof (NetMainLoop))] + [InlineData (typeof (CursesDriver), typeof (UnixMainLoop))] + [InlineData (typeof (WindowsDriver), typeof (WindowsMainLoop))] + public void MainLoop_RemoveTimeout_InvalidToken_ReturnsFalse (Type driverType, Type mainLoopDriverType) + { + var driver = (ConsoleDriver)Activator.CreateInstance (driverType); + var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, new object [] { driver }); + var mainLoop = new MainLoop (mainLoopDriver); + + var result = mainLoop.RemoveTimeout (new object ()); + + Assert.False (result); + } + + [Theory] + [InlineData (typeof (FakeDriver), typeof (FakeMainLoop))] + [InlineData (typeof (NetDriver), typeof (NetMainLoop))] + [InlineData (typeof (CursesDriver), typeof (UnixMainLoop))] + [InlineData (typeof (WindowsDriver), typeof (WindowsMainLoop))] + public void MainLoop_AddIdle_ValidIdleHandler_ReturnsToken (Type driverType, Type mainLoopDriverType) + { + var driver = (ConsoleDriver)Activator.CreateInstance (driverType); + var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, new object [] { driver }); + var mainLoop = new MainLoop (mainLoopDriver); + var idleHandlerInvoked = false; + + bool IdleHandler () + { + idleHandlerInvoked = true; + return false; + } + + Func token = mainLoop.AddIdle (IdleHandler); + + Assert.NotNull (token); + Assert.False (idleHandlerInvoked); // Idle handler should not be invoked immediately + mainLoop.RunIteration (); // Run an iteration to process the idle handler + Assert.True (idleHandlerInvoked); // Idle handler should be invoked after processing + mainLoop.Dispose (); + } + + [Theory] + [InlineData (typeof (FakeDriver), typeof (FakeMainLoop))] + [InlineData (typeof (NetDriver), typeof (NetMainLoop))] + [InlineData (typeof (CursesDriver), typeof (UnixMainLoop))] + [InlineData (typeof (WindowsDriver), typeof (WindowsMainLoop))] + public void MainLoop_RemoveIdle_ValidToken_ReturnsTrue (Type driverType, Type mainLoopDriverType) + { + var driver = (ConsoleDriver)Activator.CreateInstance (driverType); + var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, new object [] { driver }); + var mainLoop = new MainLoop (mainLoopDriver); + + bool IdleHandler () => false; + Func token = mainLoop.AddIdle (IdleHandler); + var result = mainLoop.RemoveIdle (token); + + Assert.True (result); + mainLoop.Dispose (); + } + + [Theory] + [InlineData (typeof (FakeDriver), typeof (FakeMainLoop))] + [InlineData (typeof (NetDriver), typeof (NetMainLoop))] + [InlineData (typeof (CursesDriver), typeof (UnixMainLoop))] + [InlineData (typeof (WindowsDriver), typeof (WindowsMainLoop))] + public void MainLoop_RemoveIdle_InvalidToken_ReturnsFalse (Type driverType, Type mainLoopDriverType) + { + var driver = (ConsoleDriver)Activator.CreateInstance (driverType); + var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, new object [] { driver }); + var mainLoop = new MainLoop (mainLoopDriver); + + var result = mainLoop.RemoveIdle (() => false); + + Assert.False (result); + mainLoop.Dispose (); + } + + [Theory] + [InlineData (typeof (FakeDriver), typeof (FakeMainLoop))] + [InlineData (typeof (NetDriver), typeof (NetMainLoop))] + [InlineData (typeof (CursesDriver), typeof (UnixMainLoop))] + [InlineData (typeof (WindowsDriver), typeof (WindowsMainLoop))] + public void MainLoop_RunIteration_ValidIdleHandler_CallsIdleHandler (Type driverType, Type mainLoopDriverType) + { + var driver = (ConsoleDriver)Activator.CreateInstance (driverType); + var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, new object [] { driver }); + var mainLoop = new MainLoop (mainLoopDriver); + var idleHandlerInvoked = false; + + Func idleHandler = () => { + idleHandlerInvoked = true; + return false; + }; + + mainLoop.AddIdle (idleHandler); + mainLoop.RunIteration (); // Run an iteration to process the idle handler + + Assert.True (idleHandlerInvoked); + mainLoop.Dispose (); + } + + [Theory] + [InlineData (typeof (FakeDriver), typeof (FakeMainLoop))] + [InlineData (typeof (NetDriver), typeof (NetMainLoop))] + [InlineData (typeof (CursesDriver), typeof (UnixMainLoop))] + [InlineData (typeof (WindowsDriver), typeof (WindowsMainLoop))] + public void MainLoop_CheckTimersAndIdleHandlers_NoTimersOrIdleHandlers_ReturnsFalse (Type driverType, Type mainLoopDriverType) + { + var driver = (ConsoleDriver)Activator.CreateInstance (driverType); + var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, new object [] { driver }); + var mainLoop = new MainLoop (mainLoopDriver); + + var result = mainLoop.CheckTimersAndIdleHandlers (out var waitTimeout); + + Assert.False (result); + Assert.Equal (-1, waitTimeout); + mainLoop.Dispose (); + } + + [Theory] + [InlineData (typeof (FakeDriver), typeof (FakeMainLoop))] + [InlineData (typeof (NetDriver), typeof (NetMainLoop))] + [InlineData (typeof (CursesDriver), typeof (UnixMainLoop))] + [InlineData (typeof (WindowsDriver), typeof (WindowsMainLoop))] + public void MainLoop_CheckTimersAndIdleHandlers_TimersActive_ReturnsTrue (Type driverType, Type mainLoopDriverType) + { + var driver = (ConsoleDriver)Activator.CreateInstance (driverType); + var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, new object [] { driver }); + var mainLoop = new MainLoop (mainLoopDriver); + + mainLoop.AddTimeout (TimeSpan.FromMilliseconds (100), () => false); + var result = mainLoop.CheckTimersAndIdleHandlers (out var waitTimeout); + + Assert.True (result); + Assert.True (waitTimeout >= 0); + mainLoop.Dispose (); + } + + [Theory] + [InlineData (typeof (FakeDriver), typeof (FakeMainLoop))] + [InlineData (typeof (NetDriver), typeof (NetMainLoop))] + [InlineData (typeof (CursesDriver), typeof (UnixMainLoop))] + [InlineData (typeof (WindowsDriver), typeof (WindowsMainLoop))] + public void MainLoop_CheckTimersAndIdleHandlers_IdleHandlersActive_ReturnsTrue (Type driverType, Type mainLoopDriverType) + { + var driver = (ConsoleDriver)Activator.CreateInstance (driverType); + var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, new object [] { driver }); + var mainLoop = new MainLoop (mainLoopDriver); + + mainLoop.AddIdle (() => false); + var result = mainLoop.CheckTimersAndIdleHandlers (out var waitTimeout); + + Assert.True (result); + Assert.Equal (-1, waitTimeout); + mainLoop.Dispose (); + } + + //[Theory] + //[InlineData (typeof (FakeDriver), typeof (FakeMainLoop))] + //[InlineData (typeof (NetDriver), typeof (NetMainLoop))] + //[InlineData (typeof (CursesDriver), typeof (UnixMainLoop))] + //[InlineData (typeof (WindowsDriver), typeof (WindowsMainLoop))] + //public void MainLoop_Invoke_ValidAction_RunsAction (Type driverType, Type mainLoopDriverType) + //{ + // var driver = (ConsoleDriver)Activator.CreateInstance (driverType); + // var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, new object [] { driver }); + // var mainLoop = new MainLoop (mainLoopDriver); + // var actionInvoked = false; + + // mainLoop.Invoke (() => { actionInvoked = true; }); + // mainLoop.RunIteration (); // Run an iteration to process the action. + + // Assert.True (actionInvoked); + // mainLoop.Dispose (); + //} +} diff --git a/UnitTests/Dialogs/DialogTests.cs b/UnitTests/Dialogs/DialogTests.cs index 7550799ea..e01885ef9 100644 --- a/UnitTests/Dialogs/DialogTests.cs +++ b/UnitTests/Dialogs/DialogTests.cs @@ -148,7 +148,7 @@ namespace Terminal.Gui.DialogTests { Application.Top.BorderStyle = LineStyle.Double; var iterations = -1; - Application.Iteration += () => { + Application.Iteration += (s, a) => { iterations++; if (iterations == 0) { @@ -352,7 +352,7 @@ namespace Terminal.Gui.DialogTests { button2 = new Button (btn2Text); (runstate, dlg) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, button1, button2); button1.Visible = false; - Application.RunMainLoopIteration (ref runstate, ref firstIteration); + Application.RunIteration (ref runstate, ref firstIteration); buttonRow = $@"{CM.Glyphs.VLine} {btn2} {CM.Glyphs.VLine}"; TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); Application.End (runstate); @@ -363,7 +363,7 @@ namespace Terminal.Gui.DialogTests { button2 = new Button (btn2Text); (runstate, dlg) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Justify, button1, button2); button1.Visible = false; - Application.RunMainLoopIteration (ref runstate, ref firstIteration); + Application.RunIteration (ref runstate, ref firstIteration); buttonRow = $@"{CM.Glyphs.VLine} {btn2}{CM.Glyphs.VLine}"; TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); Application.End (runstate); @@ -374,7 +374,7 @@ namespace Terminal.Gui.DialogTests { button2 = new Button (btn2Text); (runstate, dlg) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, button1, button2); button1.Visible = false; - Application.RunMainLoopIteration (ref runstate, ref firstIteration); + Application.RunIteration (ref runstate, ref firstIteration); TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); Application.End (runstate); @@ -384,7 +384,7 @@ namespace Terminal.Gui.DialogTests { button2 = new Button (btn2Text); (runstate, dlg) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Left, button1, button2); button1.Visible = false; - Application.RunMainLoopIteration (ref runstate, ref firstIteration); + Application.RunIteration (ref runstate, ref firstIteration); buttonRow = $@"{CM.Glyphs.VLine} {btn2} {CM.Glyphs.VLine}"; TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); Application.End (runstate); @@ -713,7 +713,7 @@ namespace Terminal.Gui.DialogTests { buttonRow = $"{CM.Glyphs.VLine} {btn1} {btn2} {CM.Glyphs.VLine}"; dlg.AddButton (new Button (btn2Text)); bool first = false; - Application.RunMainLoopIteration (ref runstate, ref first); + Application.RunIteration (ref runstate, ref first); TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); Application.End (runstate); @@ -729,7 +729,7 @@ namespace Terminal.Gui.DialogTests { buttonRow = $"{CM.Glyphs.VLine}{btn1} {btn2}{CM.Glyphs.VLine}"; dlg.AddButton (new Button (btn2Text)); first = false; - Application.RunMainLoopIteration (ref runstate, ref first); + Application.RunIteration (ref runstate, ref first); TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); Application.End (runstate); @@ -745,7 +745,7 @@ namespace Terminal.Gui.DialogTests { buttonRow = $"{CM.Glyphs.VLine} {btn1} {btn2}{CM.Glyphs.VLine}"; dlg.AddButton (new Button (btn2Text)); first = false; - Application.RunMainLoopIteration (ref runstate, ref first); + Application.RunIteration (ref runstate, ref first); TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); Application.End (runstate); @@ -761,7 +761,7 @@ namespace Terminal.Gui.DialogTests { buttonRow = $"{CM.Glyphs.VLine}{btn1} {btn2} {CM.Glyphs.VLine}"; dlg.AddButton (new Button (btn2Text)); first = false; - Application.RunMainLoopIteration (ref runstate, ref first); + Application.RunIteration (ref runstate, ref first); TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); Application.End (runstate); } @@ -796,7 +796,7 @@ namespace Terminal.Gui.DialogTests { var btn = $"{CM.Glyphs.LeftBracket}{CM.Glyphs.LeftDefaultIndicator} Ok {CM.Glyphs.RightDefaultIndicator}{CM.Glyphs.RightBracket}"; var iterations = -1; - Application.Iteration += () => { + Application.Iteration += (s, a) => { iterations++; if (iterations == 0) { Assert.True (btn1.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ()))); @@ -876,7 +876,7 @@ namespace Terminal.Gui.DialogTests { var win = new Window (); int iterations = 0; - Application.Iteration += () => { + Application.Iteration += (s, a) => { if (++iterations > 2) { Application.RequestStop (); } @@ -913,7 +913,7 @@ namespace Terminal.Gui.DialogTests { // ((FakeDriver)Application.Driver).SetBufferSize (20, height); // var win = new Window (); - // Application.Iteration += () => { + // Application.Iteration += (s, a) => { // var dlg = new Dialog ("Test", new Button ("Ok")); // dlg.LayoutComplete += (s, a) => { @@ -946,7 +946,7 @@ namespace Terminal.Gui.DialogTests { var win = new Window (); int iterations = 0; - Application.Iteration += () => { + Application.Iteration += (s, a) => { if (++iterations > 2) { Application.RequestStop (); } diff --git a/UnitTests/Dialogs/MessageBoxTests.cs b/UnitTests/Dialogs/MessageBoxTests.cs index 1a7a1afbd..624c58805 100644 --- a/UnitTests/Dialogs/MessageBoxTests.cs +++ b/UnitTests/Dialogs/MessageBoxTests.cs @@ -23,7 +23,7 @@ namespace Terminal.Gui.DialogTests { Application.Begin (Application.Top); ((FakeDriver)Application.Driver).SetBufferSize (100, 100); - Application.Iteration += () => { + Application.Iteration += (s, a) => { iterations++; if (iterations == 0) { @@ -52,7 +52,7 @@ namespace Terminal.Gui.DialogTests { Application.Begin (Application.Top); ((FakeDriver)Application.Driver).SetBufferSize (100, 100); - Application.Iteration += () => { + Application.Iteration += (s, a) => { iterations++; if (iterations == 0) { @@ -87,7 +87,7 @@ namespace Terminal.Gui.DialogTests { Application.Begin (Application.Top); ((FakeDriver)Application.Driver).SetBufferSize (100, 100); - Application.Iteration += () => { + Application.Iteration += (s, a) => { iterations++; if (iterations == 0) { @@ -121,7 +121,7 @@ namespace Terminal.Gui.DialogTests { Application.Begin (Application.Top); ((FakeDriver)Application.Driver).SetBufferSize (100, 100); - Application.Iteration += () => { + Application.Iteration += (s, a) => { iterations++; if (iterations == 0) { @@ -155,7 +155,7 @@ namespace Terminal.Gui.DialogTests { Application.Begin (Application.Top); ((FakeDriver)Application.Driver).SetBufferSize (100, 100); - Application.Iteration += () => { + Application.Iteration += (s, a) => { iterations++; if (iterations == 0) { @@ -179,7 +179,7 @@ namespace Terminal.Gui.DialogTests { var iterations = -1; Application.Begin (Application.Top); - Application.Iteration += () => { + Application.Iteration += (s, a) => { iterations++; if (iterations == 0) { @@ -219,7 +219,7 @@ namespace Terminal.Gui.DialogTests { ((FakeDriver)Application.Driver).SetBufferSize (40 + 4, 8); - Application.Iteration += () => { + Application.Iteration += (s, a) => { iterations++; if (iterations == 0) { @@ -253,7 +253,7 @@ namespace Terminal.Gui.DialogTests { var iterations = -1; Application.Begin (Application.Top); - Application.Iteration += () => { + Application.Iteration += (s, a) => { iterations++; if (iterations == 0) { @@ -287,7 +287,7 @@ namespace Terminal.Gui.DialogTests { Application.Begin (Application.Top); var btn = $"{CM.Glyphs.LeftBracket}{CM.Glyphs.LeftDefaultIndicator} Ok {CM.Glyphs.RightDefaultIndicator}{CM.Glyphs.RightBracket}"; - Application.Iteration += () => { + Application.Iteration += (s, a) => { iterations++; if (iterations == 0) { @@ -320,7 +320,7 @@ namespace Terminal.Gui.DialogTests { ((FakeDriver)Application.Driver).SetBufferSize (20, 10); var btn = $"{CM.Glyphs.LeftBracket}{CM.Glyphs.LeftDefaultIndicator} btn {CM.Glyphs.RightDefaultIndicator}{CM.Glyphs.RightBracket}"; - Application.Iteration += () => { + Application.Iteration += (s, a) => { iterations++; if (iterations == 0) { @@ -377,7 +377,7 @@ namespace Terminal.Gui.DialogTests { var btn = $"{CM.Glyphs.LeftBracket}{CM.Glyphs.LeftDefaultIndicator} btn {CM.Glyphs.RightDefaultIndicator}{CM.Glyphs.RightBracket}"; - Application.Iteration += () => { + Application.Iteration += (s, a) => { iterations++; if (iterations == 0) { @@ -435,7 +435,7 @@ namespace Terminal.Gui.DialogTests { var btn = $"{CM.Glyphs.LeftBracket}{CM.Glyphs.LeftDefaultIndicator} btn {CM.Glyphs.RightDefaultIndicator}{CM.Glyphs.RightBracket}"; - Application.Iteration += () => { + Application.Iteration += (s, a) => { iterations++; if (iterations == 0) { @@ -490,7 +490,7 @@ namespace Terminal.Gui.DialogTests { ((FakeDriver)Application.Driver).SetBufferSize (20, 10); var btn = $"{CM.Glyphs.LeftBracket}{CM.Glyphs.LeftDefaultIndicator} btn {CM.Glyphs.RightDefaultIndicator}{CM.Glyphs.RightBracket}"; - Application.Iteration += () => { + Application.Iteration += (s, a) => { iterations++; if (iterations == 0) { @@ -552,7 +552,7 @@ namespace Terminal.Gui.DialogTests { var iterations = -1; Application.Begin (Application.Top); - Application.Iteration += () => { + Application.Iteration += (s, a) => { iterations++; if (iterations == 0) { diff --git a/UnitTests/Dialogs/WizardTests.cs b/UnitTests/Dialogs/WizardTests.cs index 978d4784b..f5afb7d9f 100644 --- a/UnitTests/Dialogs/WizardTests.cs +++ b/UnitTests/Dialogs/WizardTests.cs @@ -163,7 +163,7 @@ namespace Terminal.Gui.DialogTests { //wizard.LayoutSubviews (); var firstIteration = false; var runstate = Application.Begin (wizard); - Application.RunMainLoopIteration (ref runstate, ref firstIteration); + Application.RunIteration (ref runstate, ref firstIteration); TestHelpers.AssertDriverContentsWithFrameAre ($"{topRow}\n{row2}\n{row3}\n{row4}\n{separatorRow}\n{buttonRow}\n{bottomRow}", output); Application.End (runstate); @@ -530,10 +530,10 @@ namespace Terminal.Gui.DialogTests { var runstate = Application.Begin (wizard); var firstIteration = true; - Application.RunMainLoopIteration (ref runstate, ref firstIteration); + Application.RunIteration (ref runstate, ref firstIteration); wizard.NextFinishButton.OnClicked (); - Application.RunMainLoopIteration (ref runstate, ref firstIteration); + Application.RunIteration (ref runstate, ref firstIteration); Application.End (runstate); Assert.True (finishedFired); Assert.True (closedFired); @@ -559,7 +559,7 @@ namespace Terminal.Gui.DialogTests { }; runstate = Application.Begin (wizard); - Application.RunMainLoopIteration (ref runstate, ref firstIteration); + Application.RunIteration (ref runstate, ref firstIteration); Assert.Equal (step1.Title, wizard.CurrentStep.Title); wizard.NextFinishButton.OnClicked (); @@ -597,7 +597,7 @@ namespace Terminal.Gui.DialogTests { }; runstate = Application.Begin (wizard); - Application.RunMainLoopIteration (ref runstate, ref firstIteration); + Application.RunIteration (ref runstate, ref firstIteration); Assert.Equal (step2.Title, wizard.CurrentStep.Title); Assert.Equal (wizard.GetLastStep ().Title, wizard.CurrentStep.Title); diff --git a/UnitTests/Drawing/AttributeTests.cs b/UnitTests/Drawing/AttributeTests.cs index 33cc15a59..f837e70c8 100644 --- a/UnitTests/Drawing/AttributeTests.cs +++ b/UnitTests/Drawing/AttributeTests.cs @@ -91,7 +91,7 @@ public class AttributeTests { { var driver = new FakeDriver (); Application.Init (driver); - driver.Init (() => { }); + driver.Init (); // Test parameterless constructor var attr = new Attribute (); @@ -197,7 +197,7 @@ public class AttributeTests { { var driver = new FakeDriver (); Application.Init (driver); - driver.Init (() => { }); + driver.Init (); var attr = new Attribute (); @@ -238,7 +238,7 @@ public class AttributeTests { { var driver = new FakeDriver (); Application.Init (driver); - driver.Init (() => { }); + driver.Init (); var fg = new Color (); fg = new Color (Color.Red); diff --git a/UnitTests/Input/EscSeqUtilsTests.cs b/UnitTests/Input/EscSeqUtilsTests.cs index 9afb3d903..a7e5c9580 100644 --- a/UnitTests/Input/EscSeqUtilsTests.cs +++ b/UnitTests/Input/EscSeqUtilsTests.cs @@ -519,14 +519,11 @@ namespace Terminal.Gui.InputTests { Application.Top.Add (view); Application.Begin (Application.Top); - ReflectionTools.InvokePrivate ( - typeof (Application), - "ProcessMouseEvent", - new MouseEvent () { - X = 0, - Y = 0, - Flags = 0 - }); + Application.OnMouseEvent (new MouseEventEventArgs (new MouseEvent () { + X = 0, + Y = 0, + Flags = 0 + })); ClearAll (); cki = new ConsoleKeyInfo [] { @@ -558,18 +555,15 @@ namespace Terminal.Gui.InputTests { Assert.Equal (new Point (1, 2), pos); Assert.False (isReq); - Application.Iteration += () => { + Application.Iteration += (s, a) => { if (actionStarted) { // set Application.WantContinuousButtonPressedView to null view.WantContinuousButtonPressed = false; - ReflectionTools.InvokePrivate ( - typeof (Application), - "ProcessMouseEvent", - new MouseEvent () { - X = 0, - Y = 0, - Flags = 0 - }); + Application.OnMouseEvent (new MouseEventEventArgs (new MouseEvent () { + X = 0, + Y = 0, + Flags = 0 + })); Application.RequestStop (); } diff --git a/UnitTests/ReflectionTools.cs b/UnitTests/ReflectionTools.cs deleted file mode 100644 index 3f661bc5f..000000000 --- a/UnitTests/ReflectionTools.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using System.Reflection; - -public static class ReflectionTools { - // If the class is non-static - public static Object InvokePrivate (Object objectUnderTest, string method, params object [] args) - { - Type t = objectUnderTest.GetType (); - return t.InvokeMember (method, - BindingFlags.InvokeMethod | - BindingFlags.NonPublic | - BindingFlags.Instance | - BindingFlags.Static, - null, - objectUnderTest, - args); - } - // if the class is static - public static Object InvokePrivate (Type typeOfObjectUnderTest, string method, params object [] args) - { - MemberInfo [] members = typeOfObjectUnderTest.GetMembers (BindingFlags.NonPublic | BindingFlags.Static); - foreach (var member in members) { - if (member.Name == method) { - return typeOfObjectUnderTest.InvokeMember (method, - BindingFlags.NonPublic | - BindingFlags.Static | - BindingFlags.InvokeMethod, - null, - typeOfObjectUnderTest, - args); - } - } - return null; - } -} diff --git a/UnitTests/UICatalog/ScenarioTests.cs b/UnitTests/UICatalog/ScenarioTests.cs index 66992ba89..f95107353 100644 --- a/UnitTests/UICatalog/ScenarioTests.cs +++ b/UnitTests/UICatalog/ScenarioTests.cs @@ -69,7 +69,7 @@ namespace UICatalog.Tests { FakeConsole.PushMockKeyPress (Application.QuitKey); // The only key we care about is the QuitKey - Application.Top.KeyPress += (object sender, KeyEventEventArgs args) => { + Application.Top.KeyPressed += (object sender, KeyEventEventArgs args) => { output.WriteLine ($" Keypress: {args.KeyEvent.Key}"); // BUGBUG: (#2474) For some reason ReadKey is not returning the QuitKey for some Scenarios // by adding this Space it seems to work. @@ -79,7 +79,7 @@ namespace UICatalog.Tests { uint abortTime = 500; // If the scenario doesn't close within 500ms, this will force it to quit - Func forceCloseCallback = (MainLoop loop) => { + Func forceCloseCallback = () => { if (Application.Top.Running && FakeConsole.MockKeyPresses.Count == 0) { Application.RequestStop (); // See #2474 for why this is commented out @@ -88,9 +88,9 @@ namespace UICatalog.Tests { return false; }; //output.WriteLine ($" Add timeout to force quit after {abortTime}ms"); - _ = Application.MainLoop.AddTimeout (TimeSpan.FromMilliseconds (abortTime), forceCloseCallback); + _ = Application.AddTimeout (TimeSpan.FromMilliseconds (abortTime), forceCloseCallback); - Application.Iteration += () => { + Application.Iteration += (s, a) => { //output.WriteLine ($" iteration {++iterations}"); if (Application.Top.Running && FakeConsole.MockKeyPresses.Count == 0) { Application.RequestStop (); @@ -130,7 +130,7 @@ namespace UICatalog.Tests { var ms = 100; var abortCount = 0; - Func abortCallback = (MainLoop loop) => { + Func abortCallback = () => { abortCount++; output.WriteLine ($"'Generic' abortCount {abortCount}"); Application.RequestStop (); @@ -139,7 +139,7 @@ namespace UICatalog.Tests { int iterations = 0; object token = null; - Application.Iteration = () => { + Application.Iteration += (s, a) => { if (token == null) { // Timeout only must start at first iteration token = Application.MainLoop.AddTimeout (TimeSpan.FromMilliseconds (ms), abortCallback); @@ -153,7 +153,7 @@ namespace UICatalog.Tests { } }; - Application.Top.KeyPress += (object sender, KeyEventEventArgs args) => { + Application.Top.KeyPressed += (object sender, KeyEventEventArgs args) => { // See #2474 for why this is commented out Assert.Equal (Key.CtrlMask | Key.Q, args.KeyEvent.Key); }; @@ -392,7 +392,7 @@ namespace UICatalog.Tests { int iterations = 0; - Application.Iteration += () => { + Application.Iteration += (s, a) => { iterations++; if (iterations < _viewClasses.Count) { diff --git a/UnitTests/View/FrameTests.cs b/UnitTests/View/FrameTests.cs index 685380b7a..f2497f2d6 100644 --- a/UnitTests/View/FrameTests.cs +++ b/UnitTests/View/FrameTests.cs @@ -61,7 +61,7 @@ namespace Terminal.Gui.ViewTests { bool firstIteration = false; ((FakeDriver)Application.Driver).SetBufferSize (20, height); - Application.RunMainLoopIteration (ref rs, ref firstIteration); + Application.RunIteration (ref rs, ref firstIteration); var expected = string.Empty; switch (height) { @@ -119,7 +119,7 @@ namespace Terminal.Gui.ViewTests { bool firstIteration = false; ((FakeDriver)Application.Driver).SetBufferSize (width, 3); - Application.RunMainLoopIteration (ref rs, ref firstIteration); + Application.RunIteration (ref rs, ref firstIteration); var expected = string.Empty; switch (width) { @@ -210,7 +210,7 @@ namespace Terminal.Gui.ViewTests { bool firstIteration = false; ((FakeDriver)Application.Driver).SetBufferSize (3, 3); - Application.RunMainLoopIteration (ref rs, ref firstIteration); + Application.RunIteration (ref rs, ref firstIteration); var expected = @" ┌─┐ │ │ @@ -234,7 +234,7 @@ namespace Terminal.Gui.ViewTests { bool firstIteration = false; ((FakeDriver)Application.Driver).SetBufferSize (5, 5); - Application.RunMainLoopIteration (ref rs, ref firstIteration); + Application.RunIteration (ref rs, ref firstIteration); var expected = @" ╔═══╗ ║┌─┐║ @@ -262,7 +262,7 @@ namespace Terminal.Gui.ViewTests { bool firstIteration = false; ((FakeDriver)Application.Driver).SetBufferSize (10, 4); - Application.RunMainLoopIteration (ref rs, ref firstIteration); + Application.RunIteration (ref rs, ref firstIteration); var expected = @" ╔════════╗ ║┌┤1234├┐║ @@ -299,7 +299,7 @@ namespace Terminal.Gui.ViewTests { bool firstIteration = false; ((FakeDriver)Application.Driver).SetBufferSize (width, 4); - Application.RunMainLoopIteration (ref rs, ref firstIteration); + Application.RunIteration (ref rs, ref firstIteration); var expected = string.Empty; switch (width) { @@ -411,7 +411,7 @@ namespace Terminal.Gui.ViewTests { bool firstIteration = false; ((FakeDriver)Application.Driver).SetBufferSize (width, 4); - Application.RunMainLoopIteration (ref rs, ref firstIteration); + Application.RunIteration (ref rs, ref firstIteration); var expected = string.Empty; switch (width) { @@ -523,7 +523,7 @@ namespace Terminal.Gui.ViewTests { bool firstIteration = false; ((FakeDriver)Application.Driver).SetBufferSize (width, 5); - Application.RunMainLoopIteration (ref rs, ref firstIteration); + Application.RunIteration (ref rs, ref firstIteration); var expected = string.Empty; switch (width) { diff --git a/UnitTests/View/KeyboardTests.cs b/UnitTests/View/KeyboardTests.cs index 7b35d9c80..a9557294b 100644 --- a/UnitTests/View/KeyboardTests.cs +++ b/UnitTests/View/KeyboardTests.cs @@ -25,14 +25,14 @@ namespace Terminal.Gui.ViewTests { var top = Application.Top; var text = new TextField (""); - text.KeyPress += (s, e) => { + text.KeyPressed += (s, e) => { e.Handled = true; Assert.True (e.Handled); Assert.Equal (Key.N, e.KeyEvent.Key); }; top.Add (text); - Application.Iteration += () => { + Application.Iteration += (s, a) => { Console.MockKeyPresses.Push (new ConsoleKeyInfo ('N', ConsoleKey.N, false, false, false)); Assert.Equal ("", text.Text); @@ -61,7 +61,7 @@ namespace Terminal.Gui.ViewTests { e.Handled = true; keyDown = true; }; - view.KeyPress += (s, e) => { + view.KeyPressed += (s, e) => { Assert.Equal (Key.a, e.KeyEvent.Key); Assert.False (keyPress); Assert.False (view.IsKeyPress); @@ -80,7 +80,7 @@ namespace Terminal.Gui.ViewTests { Console.MockKeyPresses.Push (new ConsoleKeyInfo ('a', ConsoleKey.A, false, false, false)); - Application.Iteration += () => Application.RequestStop (); + Application.Iteration += (s, a) => Application.RequestStop (); Assert.True (view.CanFocus); @@ -145,7 +145,7 @@ namespace Terminal.Gui.ViewTests { Assert.False (view.IsKeyDown); keyDown = true; }; - view.KeyPress += (s, e) => { + view.KeyPressed += (s, e) => { keyPress = true; }; view.KeyUp += (s, e) => { @@ -162,7 +162,7 @@ namespace Terminal.Gui.ViewTests { Console.MockKeyPresses.Push (new ConsoleKeyInfo ('\0', 0, shift, alt, control)); - Application.Iteration += () => Application.RequestStop (); + Application.Iteration += (s, a) => Application.RequestStop (); Assert.True (view.CanFocus); diff --git a/UnitTests/View/Layout/DimTests.cs b/UnitTests/View/Layout/DimTests.cs index 577c56a22..c70b66d45 100644 --- a/UnitTests/View/Layout/DimTests.cs +++ b/UnitTests/View/Layout/DimTests.cs @@ -298,7 +298,7 @@ namespace Terminal.Gui.ViewTests { Assert.Equal (2, v.Height); }; - Application.Iteration += () => Application.RequestStop (); + Application.Iteration += (s, a) => Application.RequestStop (); Application.Run (); } @@ -533,7 +533,7 @@ namespace Terminal.Gui.ViewTests { Assert.Equal (38, v6.Frame.Height); // 198-7*20=18 }; - Application.Iteration += () => Application.RequestStop (); + Application.Iteration += (s, a) => Application.RequestStop (); Application.Run (); } @@ -702,7 +702,7 @@ namespace Terminal.Gui.ViewTests { } }; - Application.Iteration += () => { + Application.Iteration += (s, a) => { while (count < 20) field.OnKeyDown (new KeyEvent (Key.Enter, new KeyModifiers ())); Application.RequestStop (); @@ -1075,7 +1075,7 @@ namespace Terminal.Gui.ViewTests { } }; - Application.Iteration += () => { + Application.Iteration += (s, a) => { while (count < 21) { field.OnKeyDown (new KeyEvent (Key.Enter, new KeyModifiers ())); if (count == 20) { @@ -1137,7 +1137,7 @@ namespace Terminal.Gui.ViewTests { } }; - Application.Iteration += () => { + Application.Iteration += (s, a) => { while (count > 0) field.OnKeyDown (new KeyEvent (Key.Enter, new KeyModifiers ())); Application.RequestStop (); @@ -1214,7 +1214,7 @@ namespace Terminal.Gui.ViewTests { } }; - Application.Iteration += () => { + Application.Iteration += (s, a) => { while (count > -1) { field.OnKeyDown (new KeyEvent (Key.Enter, new KeyModifiers ())); if (count == 0) { diff --git a/UnitTests/View/Layout/LayoutTests.cs b/UnitTests/View/Layout/LayoutTests.cs index 4935bcf98..ea5887a19 100644 --- a/UnitTests/View/Layout/LayoutTests.cs +++ b/UnitTests/View/Layout/LayoutTests.cs @@ -83,7 +83,7 @@ namespace Terminal.Gui.ViewTests { var second = new View () { Id = "second" }; root.Add (second); - + second.X = Pos.Right (first) + 1; root.LayoutSubviews (); @@ -586,7 +586,7 @@ Y { var text = ""; for (int i = 0; i < 4; i++) { - text += Application.Driver.Contents [0, i].Runes[0]; + text += Application.Driver.Contents [0, i].Runes [0]; } return text; } @@ -1396,7 +1396,7 @@ Y view1.Frame = new Rect (0, 0, 25, 4); bool firstIteration = false; - Application.RunMainLoopIteration (ref rs, ref firstIteration); + Application.RunIteration (ref rs, ref firstIteration); Assert.True (view1.AutoSize); Assert.Equal (LayoutStyle.Absolute, view1.LayoutStyle); @@ -1407,7 +1407,7 @@ Y Assert.Equal ("Absolute(1)", view1.Height.ToString ()); view2.Frame = new Rect (0, 0, 1, 25); - Application.RunMainLoopIteration (ref rs, ref firstIteration); + Application.RunIteration (ref rs, ref firstIteration); Assert.True (view2.AutoSize); Assert.Equal (LayoutStyle.Absolute, view2.LayoutStyle); @@ -1454,7 +1454,7 @@ Y Assert.Equal ("Center", view2.Y.ToString ()); Assert.Equal ("Fill(0)", view2.Width.ToString ()); Assert.Equal ("Fill(0)", view2.Height.ToString ()); - + } [Fact, TestRespondersDisposed] @@ -1598,7 +1598,7 @@ Y ((FakeDriver)Application.Driver).SetBufferSize (20, height); - Application.RunMainLoopIteration (ref rs, ref firstIteration); + Application.RunIteration (ref rs, ref firstIteration); var expected = string.Empty; switch (height) { @@ -1736,7 +1736,7 @@ Y ((FakeDriver)Application.Driver).SetBufferSize (width, 7); - Application.RunMainLoopIteration (ref rs, ref firstIteration); + Application.RunIteration (ref rs, ref firstIteration); var expected = string.Empty; switch (width) { @@ -1861,7 +1861,7 @@ Y var clicked = false; var top = Application.Top; var win1 = new Window () { Id = "win1", Width = 20, Height = 10 }; - var label= new Label ("[ ok ]"); + var label = new Label ("[ ok ]"); var win2 = new Window () { Id = "win2", Y = Pos.Bottom (label) + 1, Width = 10, Height = 3 }; var view1 = new View () { Id = "view1", Width = Dim.Fill (), Height = 1, CanFocus = true }; view1.MouseClick += (sender, e) => clicked = true; @@ -1893,14 +1893,11 @@ Y Assert.Equal (new Rect (0, 0, 7, 1), view2.Frame); var foundView = View.FindDeepestView (top, 9, 4, out int rx, out int ry); Assert.Equal (foundView, view1); - ReflectionTools.InvokePrivate ( - typeof (Application), - "ProcessMouseEvent", - new MouseEvent () { - X = 9, - Y = 4, - Flags = MouseFlags.Button1Clicked - }); + Application.OnMouseEvent (new MouseEventEventArgs (new MouseEvent () { + X = 9, + Y = 4, + Flags = MouseFlags.Button1Clicked + })); Assert.True (clicked); Application.End (rs); @@ -1920,7 +1917,7 @@ Y }; top.Add (view); - Application.Iteration += () => { + Application.Iteration += (s, a) => { Assert.Equal (-2, view.Y); Application.RequestStop (); @@ -1947,7 +1944,7 @@ Y var view = new View ("view") { X = -2 }; top.Add (view); - Application.Iteration += () => { + Application.Iteration += (s, a) => { Assert.Equal (-2, view.X); Application.RequestStop (); diff --git a/UnitTests/View/Layout/PosTests.cs b/UnitTests/View/Layout/PosTests.cs index 054785a9a..1fb7febf7 100644 --- a/UnitTests/View/Layout/PosTests.cs +++ b/UnitTests/View/Layout/PosTests.cs @@ -540,7 +540,7 @@ namespace Terminal.Gui.ViewTests { (Window win, Button button) setup () { Application.Init (new FakeDriver ()); - Application.Iteration = () => { + Application.Iteration += (s, a) => { Application.RequestStop (); }; var win = new Window () { @@ -708,7 +708,7 @@ namespace Terminal.Gui.ViewTests { Assert.Throws (() => v.Y = 2); }; - Application.Iteration += () => Application.RequestStop (); + Application.Iteration += (s, a) => Application.RequestStop (); Application.Run (); Application.Shutdown (); @@ -729,7 +729,7 @@ namespace Terminal.Gui.ViewTests { Assert.Equal (2, w.Y = 2); }; - Application.Iteration += () => Application.RequestStop (); + Application.Iteration += (s, a) => Application.RequestStop (); Application.Run (); Application.Shutdown (); @@ -761,7 +761,7 @@ namespace Terminal.Gui.ViewTests { Assert.Equal (2, v.Y = 2); }; - Application.Iteration += () => Application.RequestStop (); + Application.Iteration += (s, a) => Application.RequestStop (); Application.Run (); Application.Shutdown (); @@ -809,7 +809,7 @@ namespace Terminal.Gui.ViewTests { // Assert.Equal (6, v2.Frame.Y); // }; - // Application.Iteration += () => Application.RequestStop (); + // Application.Iteration += (s, a) => Application.RequestStop (); // Application.Run (); // Application.Shutdown (); @@ -875,7 +875,7 @@ namespace Terminal.Gui.ViewTests { } }; - Application.Iteration += () => { + Application.Iteration += (s, a) => { while (count < 20) field.OnKeyDown (new KeyEvent (Key.Enter, new KeyModifiers ())); Application.RequestStop (); @@ -933,7 +933,7 @@ namespace Terminal.Gui.ViewTests { } }; - Application.Iteration += () => { + Application.Iteration += (s, a) => { while (count > 0) field.OnKeyDown (new KeyEvent (Key.Enter, new KeyModifiers ())); Application.RequestStop (); diff --git a/UnitTests/View/NavigationTests.cs b/UnitTests/View/NavigationTests.cs index 6f1715e4c..46a5e7784 100644 --- a/UnitTests/View/NavigationTests.cs +++ b/UnitTests/View/NavigationTests.cs @@ -515,7 +515,7 @@ namespace Terminal.Gui.ViewTests { Assert.False (f.CanFocus); Assert.True (v.CanFocus); - Application.Iteration += () => Application.RequestStop (); + Application.Iteration += (s, a) => Application.RequestStop (); Application.Run (); Application.Shutdown (); @@ -558,7 +558,7 @@ namespace Terminal.Gui.ViewTests { Assert.True (v.CanFocus); }; - Application.Iteration += () => Application.RequestStop (); + Application.Iteration += (s, a) => Application.RequestStop (); Application.Run (); Application.Shutdown (); @@ -593,7 +593,7 @@ namespace Terminal.Gui.ViewTests { Assert.False (v2.CanFocus); }; - Application.Iteration += () => Application.RequestStop (); + Application.Iteration += (s, a) => Application.RequestStop (); Application.Run (); Application.Shutdown (); @@ -634,7 +634,7 @@ namespace Terminal.Gui.ViewTests { Assert.True (v2.CanFocus); }; - Application.Iteration += () => Application.RequestStop (); + Application.Iteration += (s, a) => Application.RequestStop (); Application.Run (); Application.Shutdown (); @@ -654,7 +654,7 @@ namespace Terminal.Gui.ViewTests { // Keyboard navigation with tab Console.MockKeyPresses.Push (new ConsoleKeyInfo ('\t', ConsoleKey.Tab, false, false, false)); - Application.Iteration += () => Application.RequestStop (); + Application.Iteration += (s, a) => Application.RequestStop (); Application.Run (); Application.Shutdown (); @@ -702,7 +702,7 @@ namespace Terminal.Gui.ViewTests { var iterations = 0; - Application.Iteration += () => { + Application.Iteration += (s, a) => { iterations++; button.ProcessKey (new KeyEvent (Key.Enter, null)); @@ -899,7 +899,7 @@ namespace Terminal.Gui.ViewTests { new StatusItem(Key.CtrlMask | Key.Q, "~^Q~ Quit", () => sbQuiting = true ) }); var tf = new TextField (); - tf.KeyPress += Tf_KeyPress; + tf.KeyPressed += Tf_KeyPress; void Tf_KeyPress (object sender, KeyEventEventArgs obj) { @@ -911,7 +911,7 @@ namespace Terminal.Gui.ViewTests { var win = new Window (); win.Add (sb, tf); var top = Application.Top; - top.KeyPress += Top_KeyPress; + top.KeyPressed += Top_KeyPress; void Top_KeyPress (object sender, KeyEventEventArgs obj) { @@ -932,7 +932,7 @@ namespace Terminal.Gui.ViewTests { Assert.True (tfQuiting); Assert.False (topQuiting); - tf.KeyPress -= Tf_KeyPress; + tf.KeyPressed -= Tf_KeyPress; tfQuiting = false; Application.Driver.SendKeys ('q', ConsoleKey.Q, false, false, true); Application.MainLoop.RunIteration (); @@ -959,7 +959,7 @@ namespace Terminal.Gui.ViewTests { new StatusItem(Key.CtrlMask | Key.Q, "~^Q~ Quit", () => sbQuiting = true ) }); var tf = new TextField (); - tf.KeyPress += Tf_KeyPress; + tf.KeyPressed += Tf_KeyPress; void Tf_KeyPress (object sender, KeyEventEventArgs obj) { @@ -981,7 +981,7 @@ namespace Terminal.Gui.ViewTests { Assert.False (sbQuiting); Assert.True (tfQuiting); - tf.KeyPress -= Tf_KeyPress; + tf.KeyPressed -= Tf_KeyPress; tfQuiting = false; Application.Driver.SendKeys ('q', ConsoleKey.Q, false, false, true); Application.MainLoop.RunIteration (); diff --git a/UnitTests/View/ViewTests.cs b/UnitTests/View/ViewTests.cs index aea66a2c3..e2d90d882 100644 --- a/UnitTests/View/ViewTests.cs +++ b/UnitTests/View/ViewTests.cs @@ -332,7 +332,7 @@ namespace Terminal.Gui.ViewTests { w.Add (v1, v2); t.Add (w); - Application.Iteration = () => { + Application.Iteration += (s, a) => { Application.Refresh (); t.Running = false; }; @@ -403,7 +403,7 @@ namespace Terminal.Gui.ViewTests { w.Add (v1, v2); t.Add (w); - Application.Iteration = () => { + Application.Iteration += (s, a) => { var sv1 = new View () { Id = "sv1", Width = Dim.Fill (), Height = Dim.Fill () }; sv1.Initialized += (s, e) => { @@ -564,7 +564,7 @@ namespace Terminal.Gui.ViewTests { var iterations = 0; - Application.Iteration += () => { + Application.Iteration += (s, a) => { iterations++; Assert.True (button.Visible); @@ -846,7 +846,7 @@ namespace Terminal.Gui.ViewTests { label.Visible = false; bool firstIteration = false; - Application.RunMainLoopIteration (ref rs, ref firstIteration); + Application.RunIteration (ref rs, ref firstIteration); TestHelpers.AssertDriverContentsWithFrameAre (@" ┌────────────────────────────┐ │ │ diff --git a/UnitTests/Views/ContextMenuTests.cs b/UnitTests/Views/ContextMenuTests.cs index 0623572a1..96c324760 100644 --- a/UnitTests/Views/ContextMenuTests.cs +++ b/UnitTests/Views/ContextMenuTests.cs @@ -180,7 +180,7 @@ namespace Terminal.Gui.ViewsTests { var cm = new ContextMenu (); - lbl.KeyPress += (s, e) => { + lbl.KeyPressed += (s, e) => { if (e.KeyEvent.Key == cm.Key) { lbl.Text = "Replaced"; e.Handled = true; @@ -957,17 +957,14 @@ namespace Terminal.Gui.ViewsTests { │ │ └──────────────────┘", output); - ReflectionTools.InvokePrivate ( - typeof (Application), - "ProcessMouseEvent", - new MouseEvent () { - X = 9, - Y = 3, - Flags = MouseFlags.Button3Clicked - }); + Application.OnMouseEvent (new MouseEventEventArgs (new MouseEvent () { + X = 9, + Y = 3, + Flags = MouseFlags.Button3Clicked + })); var firstIteration = false; - Application.RunMainLoopIteration (ref rs, ref firstIteration); + Application.RunIteration (ref rs, ref firstIteration); TestHelpers.AssertDriverContentsWithFrameAre (@" ┌──────────────────┐ │ │ @@ -1008,17 +1005,14 @@ namespace Terminal.Gui.ViewsTests { │ │ └─────────────┘", output); - ReflectionTools.InvokePrivate ( - typeof (Application), - "ProcessMouseEvent", - new MouseEvent () { - X = 9, - Y = 3, - Flags = MouseFlags.Button3Clicked - }); + Application.OnMouseEvent (new MouseEventEventArgs (new MouseEvent () { + X = 9, + Y = 3, + Flags = MouseFlags.Button3Clicked + })); var firstIteration = false; - Application.RunMainLoopIteration (ref rs, ref firstIteration); + Application.RunIteration (ref rs, ref firstIteration); TestHelpers.AssertDriverContentsWithFrameAre (@" ┌─────────────┐ │ Test │ @@ -1052,17 +1046,14 @@ namespace Terminal.Gui.ViewsTests { TestHelpers.AssertDriverContentsWithFrameAre (@" Test", output); - ReflectionTools.InvokePrivate ( - typeof (Application), - "ProcessMouseEvent", - new MouseEvent () { - X = 8, - Y = 2, - Flags = MouseFlags.Button3Clicked - }); + Application.OnMouseEvent (new MouseEventEventArgs (new MouseEvent () { + X = 8, + Y = 2, + Flags = MouseFlags.Button3Clicked + })); var firstIteration = false; - Application.RunMainLoopIteration (ref rs, ref firstIteration); + Application.RunIteration (ref rs, ref firstIteration); TestHelpers.AssertDriverContentsWithFrameAre (@" Test ┌─────────────────── @@ -1103,17 +1094,14 @@ namespace Terminal.Gui.ViewsTests { │ Three │ └────────┘", output); - ReflectionTools.InvokePrivate ( - typeof (Application), - "ProcessMouseEvent", - new MouseEvent () { - X = 5, - Y = 13, - Flags = MouseFlags.Button1Clicked - }); + Application.OnMouseEvent (new MouseEventEventArgs (new MouseEvent () { + X = 5, + Y = 13, + Flags = MouseFlags.Button1Clicked + })); var firstIteration = false; - Application.RunMainLoopIteration (ref rs, ref firstIteration); + Application.RunIteration (ref rs, ref firstIteration); Assert.Equal (new Rect (5, 11, 10, 5), Application.Top.Subviews [0].Frame); Assert.Equal (new Rect (5, 11, 15, 6), Application.Top.Subviews [1].Frame); TestHelpers.AssertDriverContentsWithFrameAre (@" @@ -1124,17 +1112,14 @@ namespace Terminal.Gui.ViewsTests { │ Sub-Menu 2 │ └─────────────┘", output); - ReflectionTools.InvokePrivate ( - typeof (Application), - "ProcessMouseEvent", - new MouseEvent () { - X = 5, - Y = 12, - Flags = MouseFlags.Button1Clicked - }); + Application.OnMouseEvent (new MouseEventEventArgs (new MouseEvent () { + X = 5, + Y = 12, + Flags = MouseFlags.Button1Clicked + })); firstIteration = false; - Application.RunMainLoopIteration (ref rs, ref firstIteration); + Application.RunIteration (ref rs, ref firstIteration); Assert.Equal (new Rect (5, 11, 10, 5), Application.Top.Subviews [0].Frame); TestHelpers.AssertDriverContentsWithFrameAre (@" ┌────────┐ @@ -1154,7 +1139,7 @@ namespace Terminal.Gui.ViewsTests { var isMenuAllClosed = false; MenuBarItem mi = null; var iterations = -1; - Application.Iteration += () => { + Application.Iteration += (s, a) => { iterations++; if (iterations == 0) { cm.Show (); diff --git a/UnitTests/Views/GraphViewTests.cs b/UnitTests/Views/GraphViewTests.cs index f660ccad8..5a7d52fd2 100644 --- a/UnitTests/Views/GraphViewTests.cs +++ b/UnitTests/Views/GraphViewTests.cs @@ -69,7 +69,7 @@ namespace Terminal.Gui.ViewsTests { throw new Exception ("A test did not call shutdown correctly. Test stack trace was:" + LastInitFakeDriver); } - driver.Init (() => { }); + driver.Init (); LastInitFakeDriver = Environment.StackTrace; return driver; @@ -1516,7 +1516,7 @@ namespace Terminal.Gui.ViewsTests { { var driver = new FakeDriver (); Application.Init (driver); - driver.Init (() => { }); + driver.Init (); // create a wide window var mount = new View () { diff --git a/UnitTests/Views/MenuTests.cs b/UnitTests/Views/MenuTests.cs index 7ec9209a6..0211fe075 100644 --- a/UnitTests/Views/MenuTests.cs +++ b/UnitTests/Views/MenuTests.cs @@ -1677,7 +1677,7 @@ Edit var top = Application.Top; top.Add (win); - Application.Iteration += () => { + Application.Iteration += (s, a) => { ((FakeDriver)Application.Driver).SetBufferSize (40, 8); TestHelpers.AssertDriverContentsWithFrameAre (@" @@ -1907,7 +1907,7 @@ Edit { ((FakeDriver)Application.Driver).SetBufferSize (40, 8); - Application.Iteration += () => { + Application.Iteration += (s, a) => { var top = Application.Top; TestHelpers.AssertDriverContentsWithFrameAre (@" @@ -2376,7 +2376,7 @@ wo Assert.Equal ("File", menu.Menus [0].Title); menu.OpenMenu (); var firstIteration = false; - Application.RunMainLoopIteration (ref rs, ref firstIteration); + Application.RunIteration (ref rs, ref firstIteration); TestHelpers.AssertDriverContentsWithFrameAre (@" ┌──────────────────────────────────────┐ │ │ @@ -2394,17 +2394,14 @@ wo │ │ └──────────────────────────────────────┘", output); - ReflectionTools.InvokePrivate ( - typeof (Application), - "ProcessMouseEvent", - new MouseEvent () { - X = 20, - Y = 4, - Flags = MouseFlags.Button1Clicked - }); + Application.OnMouseEvent (new MouseEventEventArgs (new MouseEvent () { + X = 20, + Y = 4, + Flags = MouseFlags.Button1Clicked + })); firstIteration = false; - Application.RunMainLoopIteration (ref rs, ref firstIteration); + Application.RunIteration (ref rs, ref firstIteration); Assert.Equal (items [0], menu.Menus [0].Title); TestHelpers.AssertDriverContentsWithFrameAre (@" ┌──────────────────────────────────────┐ @@ -2426,24 +2423,21 @@ wo for (int i = 1; i < items.Count; i++) { menu.OpenMenu (); - ReflectionTools.InvokePrivate ( - typeof (Application), - "ProcessMouseEvent", - new MouseEvent () { - X = 20, - Y = 4 + i, - Flags = MouseFlags.Button1Clicked - }); + Application.OnMouseEvent (new MouseEventEventArgs (new MouseEvent () { + X = 20, + Y = 4 + i, + Flags = MouseFlags.Button1Clicked + })); firstIteration = false; - Application.RunMainLoopIteration (ref rs, ref firstIteration); + Application.RunIteration (ref rs, ref firstIteration); Assert.Equal (items [i], menu.Menus [0].Title); } ((FakeDriver)Application.Driver).SetBufferSize (20, 15); menu.OpenMenu (); firstIteration = false; - Application.RunMainLoopIteration (ref rs, ref firstIteration); + Application.RunIteration (ref rs, ref firstIteration); TestHelpers.AssertDriverContentsWithFrameAre (@" ┌──────────────────┐ │ │ @@ -2505,7 +2499,7 @@ wo Assert.Equal ("File", menu.Menus [0].Title); menu.OpenMenu (); var firstIteration = false; - Application.RunMainLoopIteration (ref rs, ref firstIteration); + Application.RunIteration (ref rs, ref firstIteration); TestHelpers.AssertDriverContentsWithFrameAre (@" ┌─────────────┐ │ File │ @@ -2518,17 +2512,14 @@ wo │ Delete Delete a file Ctrl+A │ └──────────────────────────────────┘", output); - ReflectionTools.InvokePrivate ( - typeof (Application), - "ProcessMouseEvent", - new MouseEvent () { - X = 20, - Y = 5, - Flags = MouseFlags.Button1Clicked - }); + Application.OnMouseEvent (new MouseEventEventArgs (new MouseEvent () { + X = 20, + Y = 5, + Flags = MouseFlags.Button1Clicked + })); firstIteration = false; - Application.RunMainLoopIteration (ref rs, ref firstIteration); + Application.RunIteration (ref rs, ref firstIteration); Assert.Equal (items [0], menu.Menus [0].Title); TestHelpers.AssertDriverContentsWithFrameAre (@" ┌─────────────┐ @@ -2539,24 +2530,21 @@ wo for (int i = 1; i < items.Count; i++) { menu.OpenMenu (); - ReflectionTools.InvokePrivate ( - typeof (Application), - "ProcessMouseEvent", - new MouseEvent () { - X = 20, - Y = 5 + i, - Flags = MouseFlags.Button1Clicked - }); + Application.OnMouseEvent (new MouseEventEventArgs (new MouseEvent () { + X = 20, + Y = 5 + i, + Flags = MouseFlags.Button1Clicked + })); firstIteration = false; - Application.RunMainLoopIteration (ref rs, ref firstIteration); + Application.RunIteration (ref rs, ref firstIteration); Assert.Equal (items [i], menu.Menus [0].Title); } ((FakeDriver)Application.Driver).SetBufferSize (20, 15); menu.OpenMenu (); firstIteration = false; - Application.RunMainLoopIteration (ref rs, ref firstIteration); + Application.RunIteration (ref rs, ref firstIteration); TestHelpers.AssertDriverContentsWithFrameAre (@" ┌─────────────┐ │ Delete │ @@ -2585,7 +2573,7 @@ wo menu.OpenMenu (); var firstIteration = false; - Application.RunMainLoopIteration (ref rs, ref firstIteration); + Application.RunIteration (ref rs, ref firstIteration); TestHelpers.AssertDriverContentsWithFrameAre (@" File ┌────────────────────────────┐ @@ -2594,7 +2582,7 @@ wo ((FakeDriver)Application.Driver).SetBufferSize (20, 15); firstIteration = false; - Application.RunMainLoopIteration (ref rs, ref firstIteration); + Application.RunIteration (ref rs, ref firstIteration); TestHelpers.AssertDriverContentsWithFrameAre (@" File", output); diff --git a/UnitTests/Views/OverlappedTests.cs b/UnitTests/Views/OverlappedTests.cs index 2d43d979f..7a22adc6f 100644 --- a/UnitTests/Views/OverlappedTests.cs +++ b/UnitTests/Views/OverlappedTests.cs @@ -88,7 +88,7 @@ namespace Terminal.Gui.ViewsTests { d.Closed += (s, e) => Application.RequestStop (top1); - Application.Iteration += () => { + Application.Iteration += (s, a) => { Assert.Null (Application.OverlappedChildren); if (iterations == 4) Assert.True (Application.Current == d); else if (iterations == 3) Assert.True (Application.Current == top4); @@ -154,7 +154,7 @@ namespace Terminal.Gui.ViewsTests { overlapped.RequestStop (); }; - Application.Iteration += () => { + Application.Iteration += (s, a) => { if (iterations == 4) { // The Dialog was not closed before and will be closed now. Assert.True (Application.Current == d); @@ -212,7 +212,7 @@ namespace Terminal.Gui.ViewsTests { // Now this will close the OverlappedContainer propagating through the OverlappedChildren. d.Closed += (s, e) => Application.RequestStop (overlapped); - Application.Iteration += () => { + Application.Iteration += (s, a) => { if (iterations == 4) { // The Dialog was not closed before and will be closed now. Assert.True (Application.Current == d); @@ -270,7 +270,7 @@ namespace Terminal.Gui.ViewsTests { // Now this will close the OverlappedContainer propagating through the OverlappedChildren. d.Closed += (s, e) => Application.RequestStop (overlapped); - Application.Iteration += () => { + Application.Iteration += (s, a) => { if (iterations == 4) { // The Dialog still is the current top and we can't request stop to OverlappedContainer // because we are not using parameter calls. @@ -298,7 +298,7 @@ namespace Terminal.Gui.ViewsTests { var c3 = new Window (); var d = new Dialog (); - Application.Iteration += () => { + Application.Iteration += (s, a) => { Assert.False (overlapped.IsOverlapped); Assert.True (c1.IsOverlapped); Assert.True (c2.IsOverlapped); @@ -362,7 +362,7 @@ namespace Terminal.Gui.ViewsTests { overlapped.RequestStop (); }; - Application.Iteration += () => { + Application.Iteration += (s, a) => { if (iterations == 5) { // The Dialog2 still is the current top and we can't request stop to OverlappedContainer // because Dialog2 and Dialog1 must be closed first. @@ -433,7 +433,7 @@ namespace Terminal.Gui.ViewsTests { overlapped.RequestStop (); }; - Application.Iteration += () => { + Application.Iteration += (s, a) => { if (iterations == 5) { // The Dialog2 still is the current top and we can't request stop to OverlappedContainer // because Dialog2 and Dialog1 must be closed first. @@ -487,7 +487,7 @@ namespace Terminal.Gui.ViewsTests { c1.Closed += (s, e) => { overlapped.RequestStop (); }; - Application.Iteration += () => { + Application.Iteration += (s, a) => { if (iterations == 3) { // The Current still is c3 because Current.Running is false. Assert.True (Application.Current == c3); @@ -555,7 +555,7 @@ namespace Terminal.Gui.ViewsTests { logger.Ready += (s, e) => Assert.Single (Application.OverlappedChildren); - Application.Iteration += () => { + Application.Iteration += (s, a) => { if (stageCompleted && running) { stageCompleted = false; var stage = new Window () { Modal = true }; @@ -635,7 +635,7 @@ namespace Terminal.Gui.ViewsTests { overlapped.AllChildClosed += (s, e) => { overlapped.RequestStop (); }; - Application.Iteration += () => { + Application.Iteration += (s, a) => { if (iterations == 3) { // The Current still is c3 because Current.Running is false. Assert.True (Application.Current == c3); diff --git a/UnitTests/Views/ScrollBarViewTests.cs b/UnitTests/Views/ScrollBarViewTests.cs index 08cec5c4f..79dbb9b2c 100644 --- a/UnitTests/Views/ScrollBarViewTests.cs +++ b/UnitTests/Views/ScrollBarViewTests.cs @@ -1111,14 +1111,11 @@ This is a test This is a test This is a test ", output); - ReflectionTools.InvokePrivate ( - typeof (Application), - "ProcessMouseEvent", - new MouseEvent () { - X = 15, - Y = 0, - Flags = MouseFlags.Button1Clicked - }); + Application.OnMouseEvent (new MouseEventEventArgs (new MouseEvent () { + X = 15, + Y = 0, + Flags = MouseFlags.Button1Clicked + })); Assert.Null (Application.MouseGrabView); Assert.True (clicked); @@ -1138,14 +1135,11 @@ This is a test This is a test This is a test ", output); - ReflectionTools.InvokePrivate ( - typeof (Application), - "ProcessMouseEvent", - new MouseEvent () { - X = 15, - Y = 0, - Flags = MouseFlags.Button1Clicked - }); + Application.OnMouseEvent (new MouseEventEventArgs (new MouseEvent () { + X = 15, + Y = 0, + Flags = MouseFlags.Button1Clicked + })); Assert.Null (Application.MouseGrabView); Assert.True (clicked); diff --git a/UnitTests/Views/StatusBarTests.cs b/UnitTests/Views/StatusBarTests.cs index d2e81ead1..76b6aa395 100644 --- a/UnitTests/Views/StatusBarTests.cs +++ b/UnitTests/Views/StatusBarTests.cs @@ -45,7 +45,7 @@ namespace Terminal.Gui.ViewsTests { Assert.Equal (CursorVisibility.Default, cv); Assert.True (FakeConsole.CursorVisible); - Application.Iteration += () => { + Application.Iteration += (s, a) => { Assert.Equal (24, sb.Frame.Y); driver.SetWindowSize (driver.Cols, 15); @@ -77,7 +77,7 @@ namespace Terminal.Gui.ViewsTests { var iteration = 0; - Application.Iteration += () => { + Application.Iteration += (s, a) => { if (iteration == 0) { Assert.Equal ("", msg); sb.ProcessHotKey (new KeyEvent (Key.CtrlMask | Key.Q, null)); diff --git a/UnitTests/Views/TabViewTests.cs b/UnitTests/Views/TabViewTests.cs index 571a90d6a..ea049a299 100644 --- a/UnitTests/Views/TabViewTests.cs +++ b/UnitTests/Views/TabViewTests.cs @@ -850,7 +850,7 @@ namespace Terminal.Gui.ViewsTests { { var driver = new FakeDriver (); Application.Init (driver); - driver.Init (() => { }); + driver.Init (); } } } \ No newline at end of file diff --git a/UnitTests/Views/TextFieldTests.cs b/UnitTests/Views/TextFieldTests.cs index 5a4582dc1..00e10c074 100644 --- a/UnitTests/Views/TextFieldTests.cs +++ b/UnitTests/Views/TextFieldTests.cs @@ -1220,7 +1220,7 @@ namespace Terminal.Gui.ViewsTests { [AutoInitShutdown] public void Test_RootKeyEvent_Cancel () { - Application.RootKeyEvent += SuppressKey; + Application.KeyPressed += SuppressKey; var tf = new TextField (); @@ -1234,7 +1234,7 @@ namespace Terminal.Gui.ViewsTests { Application.Driver.SendKeys ('j', ConsoleKey.A, false, false, false); Assert.Equal ("a", tf.Text); - Application.RootKeyEvent -= SuppressKey; + Application.KeyPressed -= SuppressKey; // Now that the delegate has been removed we can type j again Application.Driver.SendKeys ('j', ConsoleKey.A, false, false, false); @@ -1244,7 +1244,7 @@ namespace Terminal.Gui.ViewsTests { [AutoInitShutdown] public void Test_RootMouseKeyEvent_Cancel () { - Application.RootMouseEvent += SuppressRightClick; + Application.MouseEvent += SuppressRightClick; var tf = new TextField () { Width = 10 }; int clickCounter = 0; @@ -1253,15 +1253,12 @@ namespace Terminal.Gui.ViewsTests { Application.Top.Add (tf); Application.Begin (Application.Top); - var processMouseEventMethod = typeof (Application).GetMethod ("ProcessMouseEvent", BindingFlags.Static | BindingFlags.NonPublic) - ?? throw new Exception ("Expected private method not found 'ProcessMouseEvent', this method was used for testing mouse behaviours"); - var mouseEvent = new MouseEvent { Flags = MouseFlags.Button1Clicked, View = tf }; - processMouseEventMethod.Invoke (null, new object [] { mouseEvent }); + Application.OnMouseEvent(new MouseEventEventArgs(mouseEvent)); Assert.Equal (1, clickCounter); // Get a fresh instance that represents a right click. @@ -1270,10 +1267,10 @@ namespace Terminal.Gui.ViewsTests { Flags = MouseFlags.Button3Clicked, View = tf }; - processMouseEventMethod.Invoke (null, new object [] { mouseEvent }); + Application.OnMouseEvent (new MouseEventEventArgs (mouseEvent)); Assert.Equal (1, clickCounter); - Application.RootMouseEvent -= SuppressRightClick; + Application.MouseEvent -= SuppressRightClick; // Get a fresh instance that represents a right click. // Should no longer be ignored as the callback was removed @@ -1282,21 +1279,20 @@ namespace Terminal.Gui.ViewsTests { View = tf }; - processMouseEventMethod.Invoke (null, new object [] { mouseEvent }); + Application.OnMouseEvent (new MouseEventEventArgs (mouseEvent)); Assert.Equal (2, clickCounter); } - private bool SuppressKey (KeyEvent arg) + private void SuppressKey (object s, KeyEventEventArgs arg) { - if (arg.KeyValue == 'j') - return true; - - return false; + if (arg.KeyEvent.KeyValue == 'j') { + arg.Handled = true; + } } - private void SuppressRightClick (MouseEvent arg) + private void SuppressRightClick (object sender, MouseEventEventArgs arg) { - if (arg.Flags.HasFlag (MouseFlags.Button3Clicked)) + if (arg.MouseEvent.Flags.HasFlag (MouseFlags.Button3Clicked)) arg.Handled = true; } diff --git a/UnitTests/Views/TextViewTests.cs b/UnitTests/Views/TextViewTests.cs index a62c4af74..7485da252 100644 --- a/UnitTests/Views/TextViewTests.cs +++ b/UnitTests/Views/TextViewTests.cs @@ -1604,7 +1604,7 @@ namespace Terminal.Gui.ViewsTests { { Application.Top.Add (_textView); - Application.Iteration += () => { + Application.Iteration += (s, a) => { var width = _textView.Bounds.Width - 1; Assert.Equal (30, width + 1); Assert.Equal (10, _textView.Height); @@ -1640,7 +1640,7 @@ namespace Terminal.Gui.ViewsTests { { Application.Top.Add (_textView); - Application.Iteration += () => { + Application.Iteration += (s, a) => { var width = _textView.Bounds.Width - 1; Assert.Equal (30, width + 1); Assert.Equal (10, _textView.Height); @@ -1683,7 +1683,7 @@ namespace Terminal.Gui.ViewsTests { { Application.Top.Add (_textView); - Application.Iteration += () => { + Application.Iteration += (s, a) => { var width = _textView.Bounds.Width - 1; Assert.Equal (30, width + 1); Assert.Equal (10, _textView.Height); @@ -1726,7 +1726,7 @@ namespace Terminal.Gui.ViewsTests { { Application.Top.Add (_textView); - Application.Iteration += () => { + Application.Iteration += (s, a) => { var width = _textView.Bounds.Width - 1; Assert.Equal (30, width + 1); Assert.Equal (10, _textView.Height); @@ -1762,7 +1762,7 @@ namespace Terminal.Gui.ViewsTests { { Application.Top.Add (_textView); - Application.Iteration += () => { + Application.Iteration += (s, a) => { var width = _textView.Bounds.Width - 1; Assert.Equal (30, width + 1); Assert.Equal (10, _textView.Height); @@ -1820,7 +1820,7 @@ namespace Terminal.Gui.ViewsTests { { Application.Top.Add (_textView); - Application.Iteration += () => { + Application.Iteration += (s, a) => { var width = _textView.Bounds.Width - 1; Assert.Equal (30, width + 1); Assert.Equal (10, _textView.Height); @@ -6618,7 +6618,7 @@ This is the second line. [Fact, AutoInitShutdown] public void ContentsChanged_Event_Fires_On_Init () { - Application.Iteration += () => { + Application.Iteration += (s, a) => { Application.RequestStop (); }; @@ -6644,7 +6644,7 @@ This is the second line. [Fact, AutoInitShutdown] public void ContentsChanged_Event_Fires_On_Set_Text () { - Application.Iteration += () => { + Application.Iteration += (s, a) => { Application.RequestStop (); }; var eventcount = 0; @@ -6679,7 +6679,7 @@ This is the second line. [Fact, AutoInitShutdown] public void ContentsChanged_Event_Fires_On_Typing () { - Application.Iteration += () => { + Application.Iteration += (s, a) => { Application.RequestStop (); }; var eventcount = 0; diff --git a/UnitTests/Views/ToplevelTests.cs b/UnitTests/Views/ToplevelTests.cs index 9ea7e2613..42e6df504 100644 --- a/UnitTests/Views/ToplevelTests.cs +++ b/UnitTests/Views/ToplevelTests.cs @@ -45,7 +45,7 @@ namespace Terminal.Gui.ViewsTests { { var iterations = 0; - Application.Iteration += () => { + Application.Iteration += (s, a) => { if (iterations == 0) { Assert.False (Application.Top.AutoSize); Assert.Equal ("Top1", Application.Top.Text); @@ -674,7 +674,7 @@ namespace Terminal.Gui.ViewsTests { top.Add (win); var iterations = -1; - Application.Iteration = () => { + Application.Iteration += (s, a) => { iterations++; if (iterations == 0) { ((FakeDriver)Application.Driver).SetBufferSize (40, 15); @@ -701,14 +701,11 @@ namespace Terminal.Gui.ViewsTests { } else if (iterations == 2) { Assert.Null (Application.MouseGrabView); // Grab the mouse - ReflectionTools.InvokePrivate ( - typeof (Application), - "ProcessMouseEvent", - new MouseEvent () { - X = 8, - Y = 5, - Flags = MouseFlags.Button1Pressed - }); + Application.OnMouseEvent (new MouseEventEventArgs (new MouseEvent () { + X = 8, + Y = 5, + Flags = MouseFlags.Button1Pressed + })); Assert.Equal (Application.Current, Application.MouseGrabView); Assert.Equal (new Rect (8, 5, 24, 5), Application.MouseGrabView.Frame); @@ -716,14 +713,11 @@ namespace Terminal.Gui.ViewsTests { } else if (iterations == 3) { Assert.Equal (Application.Current, Application.MouseGrabView); // Drag to left - ReflectionTools.InvokePrivate ( - typeof (Application), - "ProcessMouseEvent", - new MouseEvent () { - X = 7, - Y = 5, - Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition - }); + Application.OnMouseEvent (new MouseEventEventArgs (new MouseEvent () { + X = 7, + Y = 5, + Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition + })); Assert.Equal (Application.Current, Application.MouseGrabView); Assert.Equal (new Rect (7, 5, 24, 5), Application.MouseGrabView.Frame); @@ -752,14 +746,11 @@ namespace Terminal.Gui.ViewsTests { } else if (iterations == 5) { Assert.Equal (Application.Current, Application.MouseGrabView); // Drag up - ReflectionTools.InvokePrivate ( - typeof (Application), - "ProcessMouseEvent", - new MouseEvent () { - X = 7, - Y = 4, - Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition - }); + Application.OnMouseEvent (new MouseEventEventArgs (new MouseEvent () { + X = 7, + Y = 4, + Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition + })); Assert.Equal (Application.Current, Application.MouseGrabView); Assert.Equal (new Rect (7, 4, 24, 5), Application.MouseGrabView.Frame); @@ -790,14 +781,11 @@ namespace Terminal.Gui.ViewsTests { } else if (iterations == 7) { Assert.Equal (Application.Current, Application.MouseGrabView); // Ungrab the mouse - ReflectionTools.InvokePrivate ( - typeof (Application), - "ProcessMouseEvent", - new MouseEvent () { - X = 7, - Y = 4, - Flags = MouseFlags.Button1Released - }); + Application.OnMouseEvent (new MouseEventEventArgs (new MouseEvent () { + X = 7, + Y = 4, + Flags = MouseFlags.Button1Released + })); Assert.Null (Application.MouseGrabView); @@ -827,7 +815,7 @@ namespace Terminal.Gui.ViewsTests { var location = new Rect (win.Frame.X, win.Frame.Y, 7, 3); - Application.Iteration = () => { + Application.Iteration += (s, a) => { iterations++; if (iterations == 0) { ((FakeDriver)Application.Driver).SetBufferSize (30, 10); @@ -836,14 +824,11 @@ namespace Terminal.Gui.ViewsTests { Assert.Null (Application.MouseGrabView); // Grab the mouse - ReflectionTools.InvokePrivate ( - typeof (Application), - "ProcessMouseEvent", - new MouseEvent () { - X = win.Frame.X, - Y = win.Frame.Y, - Flags = MouseFlags.Button1Pressed - }); + Application.OnMouseEvent (new MouseEventEventArgs (new MouseEvent () { + X = win.Frame.X, + Y = win.Frame.Y, + Flags = MouseFlags.Button1Pressed + })); Assert.Equal (win, Application.MouseGrabView); Assert.Equal (location, Application.MouseGrabView.Frame); @@ -852,14 +837,11 @@ namespace Terminal.Gui.ViewsTests { // Drag to left movex = 1; movey = 0; - ReflectionTools.InvokePrivate ( - typeof (Application), - "ProcessMouseEvent", - new MouseEvent () { - X = win.Frame.X + movex, - Y = win.Frame.Y + movey, - Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition - }); + Application.OnMouseEvent (new MouseEventEventArgs (new MouseEvent () { + X = win.Frame.X + movex, + Y = win.Frame.Y + movey, + Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition + })); Assert.Equal (win, Application.MouseGrabView); @@ -875,14 +857,11 @@ namespace Terminal.Gui.ViewsTests { // Drag up movex = 0; movey = -1; - ReflectionTools.InvokePrivate ( - typeof (Application), - "ProcessMouseEvent", - new MouseEvent () { - X = win.Frame.X + movex, - Y = win.Frame.Y + movey, - Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition - }); + Application.OnMouseEvent (new MouseEventEventArgs (new MouseEvent () { + X = win.Frame.X + movex, + Y = win.Frame.Y + movey, + Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition + })); Assert.Equal (win, Application.MouseGrabView); @@ -897,14 +876,11 @@ namespace Terminal.Gui.ViewsTests { // Ungrab the mouse movex = 0; movey = 0; - ReflectionTools.InvokePrivate ( - typeof (Application), - "ProcessMouseEvent", - new MouseEvent () { - X = win.Frame.X + movex, - Y = win.Frame.Y + movey, - Flags = MouseFlags.Button1Released - }); + Application.OnMouseEvent (new MouseEventEventArgs (new MouseEvent () { + X = win.Frame.X + movex, + Y = win.Frame.Y + movey, + Flags = MouseFlags.Button1Released + })); Assert.Null (Application.MouseGrabView); } else if (iterations == 7) { @@ -1162,25 +1138,19 @@ namespace Terminal.Gui.ViewsTests { │ ▼ ◄├──────┤░░░░░░░░░░░░░░░░░░░░░░░░░░░░░► ", output); - ReflectionTools.InvokePrivate ( - typeof (Application), - "ProcessMouseEvent", - new MouseEvent () { - X = 6, - Y = 6, - Flags = MouseFlags.Button1Pressed - }); + Application.OnMouseEvent (new MouseEventEventArgs (new MouseEvent () { + X = 6, + Y = 6, + Flags = MouseFlags.Button1Pressed + })); Assert.Equal (win, Application.MouseGrabView); Assert.Equal (new Rect (3, 3, 194, 94), win.Frame); - ReflectionTools.InvokePrivate ( - typeof (Application), - "ProcessMouseEvent", - new MouseEvent () { - X = 9, - Y = 9, - Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition - }); + Application.OnMouseEvent (new MouseEventEventArgs (new MouseEvent () { + X = 9, + Y = 9, + Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition + })); Assert.Equal (win, Application.MouseGrabView); top.SetNeedsLayout (); top.LayoutSubviews (); @@ -1204,14 +1174,11 @@ namespace Terminal.Gui.ViewsTests { │ ▼ ◄├──────┤░░░░░░░░░░░░░░░░░░░░░░░░░░░░░► ", output); - ReflectionTools.InvokePrivate ( - typeof (Application), - "ProcessMouseEvent", - new MouseEvent () { - X = 5, - Y = 5, - Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition - }); + Application.OnMouseEvent (new MouseEventEventArgs (new MouseEvent () { + X = 5, + Y = 5, + Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition + })); Assert.Equal (win, Application.MouseGrabView); top.SetNeedsLayout (); top.LayoutSubviews (); @@ -1235,24 +1202,18 @@ namespace Terminal.Gui.ViewsTests { │ ▼ ◄├──────┤░░░░░░░░░░░░░░░░░░░░░░░░░░░░░► ", output); - ReflectionTools.InvokePrivate ( - typeof (Application), - "ProcessMouseEvent", - new MouseEvent () { - X = 5, - Y = 5, - Flags = MouseFlags.Button1Released - }); + Application.OnMouseEvent (new MouseEventEventArgs (new MouseEvent () { + X = 5, + Y = 5, + Flags = MouseFlags.Button1Released + })); Assert.Null (Application.MouseGrabView); - ReflectionTools.InvokePrivate ( - typeof (Application), - "ProcessMouseEvent", - new MouseEvent () { - X = 4, - Y = 4, - Flags = MouseFlags.ReportMousePosition - }); + Application.OnMouseEvent (new MouseEventEventArgs (new MouseEvent () { + X = 4, + Y = 4, + Flags = MouseFlags.ReportMousePosition + })); Assert.Equal (scrollView, Application.MouseGrabView); } @@ -1275,25 +1236,19 @@ namespace Terminal.Gui.ViewsTests { Assert.Null (Application.MouseGrabView); - ReflectionTools.InvokePrivate ( - typeof (Application), - "ProcessMouseEvent", - new MouseEvent () { - X = 10, - Y = 3, - Flags = MouseFlags.Button1Pressed - }); + Application.OnMouseEvent (new MouseEventEventArgs (new MouseEvent () { + X = 10, + Y = 3, + Flags = MouseFlags.Button1Pressed + })); Assert.Equal (dialog, Application.MouseGrabView); - ReflectionTools.InvokePrivate ( - typeof (Application), - "ProcessMouseEvent", - new MouseEvent () { - X = -11, - Y = -4, - Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition - }); + Application.OnMouseEvent (new MouseEventEventArgs (new MouseEvent () { + X = -11, + Y = -4, + Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition + })); Application.Refresh (); Assert.Equal (new Rect (0, 0, 40, 10), top.Frame); @@ -1306,14 +1261,11 @@ namespace Terminal.Gui.ViewsTests { // Changes Top size to same size as Dialog more menu and scroll bar ((FakeDriver)Application.Driver).SetBufferSize (20, 3); - ReflectionTools.InvokePrivate ( - typeof (Application), - "ProcessMouseEvent", - new MouseEvent () { - X = -1, - Y = -1, - Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition - }); + Application.OnMouseEvent (new MouseEventEventArgs (new MouseEvent () { + X = -1, + Y = -1, + Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition + })); Application.Refresh (); Assert.Equal (new Rect (0, 0, 20, 3), top.Frame); @@ -1326,14 +1278,11 @@ namespace Terminal.Gui.ViewsTests { // Changes Top size smaller than Dialog size ((FakeDriver)Application.Driver).SetBufferSize (19, 2); - ReflectionTools.InvokePrivate ( - typeof (Application), - "ProcessMouseEvent", - new MouseEvent () { - X = -1, - Y = -1, - Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition - }); + Application.OnMouseEvent (new MouseEventEventArgs (new MouseEvent () { + X = -1, + Y = -1, + Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition + })); Application.Refresh (); Assert.Equal (new Rect (0, 0, 19, 2), top.Frame); @@ -1343,14 +1292,11 @@ namespace Terminal.Gui.ViewsTests { {CM.Glyphs.LeftBracket} Ok {CM.Glyphs.RightBracket} │ ", output); - ReflectionTools.InvokePrivate ( - typeof (Application), - "ProcessMouseEvent", - new MouseEvent () { - X = 18, - Y = 1, - Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition - }); + Application.OnMouseEvent (new MouseEventEventArgs (new MouseEvent () { + X = 18, + Y = 1, + Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition + })); Application.Refresh (); Assert.Equal (new Rect (0, 0, 19, 2), top.Frame); @@ -1359,14 +1305,11 @@ namespace Terminal.Gui.ViewsTests { ┌", output); // On a real app we can't go beyond the SuperView bounds - ReflectionTools.InvokePrivate ( - typeof (Application), - "ProcessMouseEvent", - new MouseEvent () { - X = 19, - Y = 2, - Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition - }); + Application.OnMouseEvent (new MouseEventEventArgs (new MouseEvent () { + X = 19, + Y = 2, + Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition + })); Application.Refresh (); Assert.Equal (new Rect (0, 0, 19, 2), top.Frame); @@ -1406,17 +1349,14 @@ namespace Terminal.Gui.ViewsTests { │ │ └────────────────────────────┘", output); - ReflectionTools.InvokePrivate ( - typeof (Application), - "ProcessMouseEvent", - new MouseEvent () { - X = 25, - Y = 7, - Flags = MouseFlags.Button1Pressed - }); + Application.OnMouseEvent (new MouseEventEventArgs (new MouseEvent () { + X = 25, + Y = 7, + Flags = MouseFlags.Button1Pressed + })); var firstIteration = false; - Application.RunMainLoopIteration (ref rs, ref firstIteration); + Application.RunIteration (ref rs, ref firstIteration); Assert.Equal (dialog, Application.MouseGrabView); Assert.Equal (new Rect (25, 7, 30, 10), dialog.Frame); @@ -1432,17 +1372,14 @@ namespace Terminal.Gui.ViewsTests { │ │ └────────────────────────────┘", output); - ReflectionTools.InvokePrivate ( - typeof (Application), - "ProcessMouseEvent", - new MouseEvent () { - X = 20, - Y = 10, - Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition - }); + Application.OnMouseEvent (new MouseEventEventArgs (new MouseEvent () { + X = 20, + Y = 10, + Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition + })); firstIteration = false; - Application.RunMainLoopIteration (ref rs, ref firstIteration); + Application.RunIteration (ref rs, ref firstIteration); Assert.Equal (dialog, Application.MouseGrabView); Assert.Equal (new Rect (20, 10, 30, 10), dialog.Frame); TestHelpers.AssertDriverContentsWithFrameAre (@" @@ -1550,17 +1487,14 @@ namespace Terminal.Gui.ViewsTests { │ │ └──────────────────┘", output); - ReflectionTools.InvokePrivate ( - typeof (Application), - "ProcessMouseEvent", - new MouseEvent () { - X = 9, - Y = 13, - Flags = MouseFlags.Button1Clicked - }); + Application.OnMouseEvent (new MouseEventEventArgs (new MouseEvent () { + X = 9, + Y = 13, + Flags = MouseFlags.Button1Clicked + })); var firstIteration = false; - Application.RunMainLoopIteration (ref rs, ref firstIteration); + Application.RunIteration (ref rs, ref firstIteration); TestHelpers.AssertDriverContentsWithFrameAre (@$" ┌──────────────────┐ │ │ @@ -1619,7 +1553,7 @@ namespace Terminal.Gui.ViewsTests { bool fromTopStillKnowSecondIsRunning = false; bool fromFirstStillKnowSecondIsRunning = false; - Application.MainLoop.AddTimeout (TimeSpan.FromMilliseconds (100), (_) => { + Application.AddTimeout (TimeSpan.FromMilliseconds (100), () => { count++; if (count1 == 5) { log1 = true; @@ -1656,7 +1590,7 @@ namespace Terminal.Gui.ViewsTests { var od = new OpenDialog (); od.Ready += SecondDialogToplevel; - Application.MainLoop.AddTimeout (TimeSpan.FromMilliseconds (100), (_) => { + Application.AddTimeout (TimeSpan.FromMilliseconds (100), () => { count1++; if (count2 == 5) { log2 = true; @@ -1679,7 +1613,7 @@ namespace Terminal.Gui.ViewsTests { { var d = new Dialog (); - Application.MainLoop.AddTimeout (TimeSpan.FromMilliseconds (100), (_) => { + Application.AddTimeout (TimeSpan.FromMilliseconds (100), () => { count2++; if (count < 30) { log = true; diff --git a/UnitTests/Views/TreeViewTests.cs b/UnitTests/Views/TreeViewTests.cs index 991f63fe5..d6fa9ade2 100644 --- a/UnitTests/Views/TreeViewTests.cs +++ b/UnitTests/Views/TreeViewTests.cs @@ -1280,7 +1280,7 @@ FFFFFFFFFF { var driver = new FakeDriver (); Application.Init (driver); - driver.Init (() => { }); + driver.Init (); } } }