diff --git a/Examples/UICatalog/Scenarios/SendKeys.cs b/Examples/UICatalog/Scenarios/SendKeys.cs index 04a57d4e4e..9b458b0ec2 100644 --- a/Examples/UICatalog/Scenarios/SendKeys.cs +++ b/Examples/UICatalog/Scenarios/SendKeys.cs @@ -1,4 +1,4 @@ -using System; +using System.Text; namespace UICatalog.Scenarios; @@ -39,7 +39,7 @@ public override void Main () txtResult.KeyDown += (s, e) => { - rKeys += (char)e.KeyCode; + rKeys += new Rune ((uint)(e.KeyCode & ~KeyCode.AltMask & ~KeyCode.CtrlMask & ~KeyCode.ShiftMask)); if (!IsShift && e.IsShift) { @@ -81,17 +81,15 @@ void ProcessInput () foreach (char r in txtInput.Text) { - ConsoleKey ck = char.IsLetter (r) - ? (ConsoleKey)char.ToUpper (r) - : (ConsoleKey)r; + ConsoleKeyInfo consoleKeyInfo = EscSeqUtils.MapConsoleKeyInfo (new (r, ConsoleKey.None, false, false, false)); Application.Driver?.SendKeys ( - r, - ck, - ckbShift.CheckedState == CheckState.Checked, - ckbAlt.CheckedState == CheckState.Checked, - ckbControl.CheckedState == CheckState.Checked - ); + r, + consoleKeyInfo.Key, + ckbShift.CheckedState == CheckState.Checked || (consoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0, + ckbAlt.CheckedState == CheckState.Checked || (consoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0, + ckbControl.CheckedState == CheckState.Checked || (consoleKeyInfo.Modifiers & ConsoleModifiers.Control) != 0 + ); } lblShippedKeys.Text = rKeys; diff --git a/Terminal.Gui/App/Application.Run.cs b/Terminal.Gui/App/Application.Run.cs index 30c1384a3e..7adf559d72 100644 --- a/Terminal.Gui/App/Application.Run.cs +++ b/Terminal.Gui/App/Application.Run.cs @@ -461,7 +461,7 @@ internal static void LayoutAndDrawImpl (bool forceDraw = false) /// This event is raised on each iteration of the main loop. /// See also public static event EventHandler? Iteration; - + /// The driver for the application /// The main loop. internal static MainLoop? MainLoop { get; set; } @@ -619,4 +619,8 @@ public static void End (RunState runState) LayoutAndDraw (true); } + internal static void RaiseIteration () + { + Iteration?.Invoke (null, new ()); + } } diff --git a/Terminal.Gui/App/Application.cs b/Terminal.Gui/App/Application.cs index 7741b12b18..01aac80002 100644 --- a/Terminal.Gui/App/Application.cs +++ b/Terminal.Gui/App/Application.cs @@ -51,6 +51,15 @@ public static partial class Application /// public static ITimedEvents? TimedEvents => ApplicationImpl.Instance?.TimedEvents; + /// + /// Maximum number of iterations of the main loop (and hence draws) + /// to allow to occur per second. Defaults to 25 which is a 40ms sleep + /// after iteration (factoring in how long iteration took to run). + /// Note that not ever iteration draws (see ). + /// Only affects v2 drivers. + /// + public static ushort MaximumIterationsPerSecond = 25; + /// /// Gets a string representation of the Application as rendered by . /// diff --git a/Terminal.Gui/Drivers/V2/ApplicationV2.cs b/Terminal.Gui/Drivers/V2/ApplicationV2.cs index ca94ebe575..7160a63f9f 100644 --- a/Terminal.Gui/Drivers/V2/ApplicationV2.cs +++ b/Terminal.Gui/Drivers/V2/ApplicationV2.cs @@ -1,5 +1,6 @@ #nullable enable using System.Collections.Concurrent; +using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using Microsoft.Extensions.Logging; @@ -12,10 +13,7 @@ namespace Terminal.Gui.Drivers; /// public class ApplicationV2 : ApplicationImpl { - private readonly Func _netInputFactory; - private readonly Func _netOutputFactory; - private readonly Func _winInputFactory; - private readonly Func _winOutputFactory; + private readonly IComponentFactory? _componentFactory; private IMainLoopCoordinator? _coordinator; private string? _driverName; @@ -24,29 +22,20 @@ public class ApplicationV2 : ApplicationImpl /// public override ITimedEvents TimedEvents => _timedEvents; + internal IMainLoopCoordinator? Coordinator => _coordinator; + /// /// Creates anew instance of the Application backend. The provided /// factory methods will be used on Init calls to get things booted. /// - public ApplicationV2 () : this ( - () => new NetInput (), - () => new NetOutput (), - () => new WindowsInput (), - () => new WindowsOutput () - ) - { } - - internal ApplicationV2 ( - Func netInputFactory, - Func netOutputFactory, - Func winInputFactory, - Func winOutputFactory - ) + public ApplicationV2 () + { + IsLegacy = false; + } + + internal ApplicationV2 (IComponentFactory componentFactory) { - _netInputFactory = netInputFactory; - _netOutputFactory = netOutputFactory; - _winInputFactory = winInputFactory; - _winOutputFactory = winOutputFactory; + _componentFactory = componentFactory; IsLegacy = false; } @@ -92,8 +81,8 @@ private void CreateDriver (string? driverName) { PlatformID p = Environment.OSVersion.Platform; - bool definetlyWin = driverName?.Contains ("win") ?? false; - bool definetlyNet = driverName?.Contains ("net") ?? false; + bool definetlyWin = (driverName?.Contains ("win") ?? false )|| _componentFactory is IComponentFactory; + bool definetlyNet = (driverName?.Contains ("net") ?? false ) || _componentFactory is IComponentFactory; if (definetlyWin) { @@ -125,13 +114,21 @@ private IMainLoopCoordinator CreateWindowsSubcomponents () ConcurrentQueue inputBuffer = new (); MainLoop loop = new (); - return new MainLoopCoordinator ( - _timedEvents, - _winInputFactory, + IComponentFactory cf; + + if (_componentFactory != null) + { + cf = (IComponentFactory)_componentFactory; + } + else + { + cf = new WindowsComponentFactory (); + } + + return new MainLoopCoordinator (_timedEvents, inputBuffer, - new WindowsInputProcessor (inputBuffer), - _winOutputFactory, - loop); + loop, + cf); } private IMainLoopCoordinator CreateNetSubcomponents () @@ -139,13 +136,22 @@ private IMainLoopCoordinator CreateNetSubcomponents () ConcurrentQueue inputBuffer = new (); MainLoop loop = new (); + IComponentFactory cf; + + if (_componentFactory != null) + { + cf = (IComponentFactory)_componentFactory; + } + else + { + cf = new NetComponentFactory (); + } + return new MainLoopCoordinator ( _timedEvents, - _netInputFactory, inputBuffer, - new NetInputProcessor (inputBuffer), - _netOutputFactory, - loop); + loop, + cf); } /// @@ -171,6 +177,12 @@ public override void Run (Toplevel view, Func? errorHandler = n throw new NotInitializedException (nameof (Run)); } + if (Application.Driver == null) + { + // See Run_T_Init_Driver_Cleared_with_TestTopLevel_Throws + throw new InvalidOperationException ("Driver was inexplicably null when trying to Run view"); + } + Application.Top = view; RunState rs = Application.Begin (view); @@ -259,3 +271,73 @@ public override void LayoutAndDraw (bool forceDraw) Application.Top?.SetNeedsLayout (); } } + +public class NetComponentFactory : ComponentFactory +{ + public override IConsoleInput CreateInput () + { + return new NetInput (); + } + + /// + public override IConsoleOutput CreateOutput () + { + return new NetOutput (); + } + + /// + public override IInputProcessor CreateInputProcessor (ConcurrentQueue inputBuffer) + { + return new NetInputProcessor (inputBuffer); + } +} + +public class WindowsComponentFactory : ComponentFactory +{ + /// + public override IConsoleInput CreateInput () + { + return new WindowsInput (); + } + + /// + public override IInputProcessor CreateInputProcessor (ConcurrentQueue inputBuffer) + { + return new WindowsInputProcessor (inputBuffer); + } + + /// + public override IConsoleOutput CreateOutput () + { + return new WindowsOutput (); + } +} + +public abstract class ComponentFactory : IComponentFactory +{ + /// + public abstract IConsoleInput CreateInput (); + + /// + public abstract IInputProcessor CreateInputProcessor (ConcurrentQueue inputBuffer); + + /// + public virtual IWindowSizeMonitor CreateWindowSizeMonitor (IConsoleOutput consoleOutput, IOutputBuffer outputBuffer) + { + return new WindowSizeMonitor (consoleOutput, outputBuffer); + } + + /// + public abstract IConsoleOutput CreateOutput (); +} +public interface IComponentFactory : IComponentFactory +{ + IConsoleInput CreateInput (); + IInputProcessor CreateInputProcessor (ConcurrentQueue inputBuffer); + IWindowSizeMonitor CreateWindowSizeMonitor (IConsoleOutput consoleOutput, IOutputBuffer outputBuffer); +} + +public interface IComponentFactory +{ + IConsoleOutput CreateOutput (); +} diff --git a/Terminal.Gui/Drivers/V2/ConsoleDriverFacade.cs b/Terminal.Gui/Drivers/V2/ConsoleDriverFacade.cs index c89c63965c..c57f67841a 100644 --- a/Terminal.Gui/Drivers/V2/ConsoleDriverFacade.cs +++ b/Terminal.Gui/Drivers/V2/ConsoleDriverFacade.cs @@ -14,6 +14,10 @@ internal class ConsoleDriverFacade : IConsoleDriver, IConsoleDriverFacade public event EventHandler SizeChanged; public IInputProcessor InputProcessor { get; } + public IOutputBuffer OutputBuffer => _outputBuffer; + + public IWindowSizeMonitor WindowSizeMonitor { get; } + public ConsoleDriverFacade ( IInputProcessor inputProcessor, @@ -36,7 +40,8 @@ IWindowSizeMonitor windowSizeMonitor MouseEvent?.Invoke (s, e); }; - windowSizeMonitor.SizeChanging += (_, e) => SizeChanged?.Invoke (this, e); + WindowSizeMonitor = windowSizeMonitor; + windowSizeMonitor.SizeChanging += (_,e) => SizeChanged?.Invoke (this, e); CreateClipboard (); } @@ -68,7 +73,7 @@ public Rectangle Screen { get { - if (ConsoleDriver.RunningUnitTests) + if (ConsoleDriver.RunningUnitTests && _output is WindowsOutput or NetOutput) { // In unit tests, we don't have a real output, so we return an empty rectangle. return Rectangle.Empty; @@ -384,7 +389,15 @@ public Attribute MakeColor (in Color foreground, in Color background) /// If simulates the Ctrl key being pressed. public void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool ctrl) { - // TODO: implement + ConsoleKeyInfo consoleKeyInfo = new (keyChar, key, shift, alt, ctrl); + + Key k = EscSeqUtils.MapKey (consoleKeyInfo); + + if (InputProcessor.IsValidInput (k, out k)) + { + InputProcessor.OnKeyDown (k); + InputProcessor.OnKeyUp (k); + } } /// diff --git a/Terminal.Gui/Drivers/V2/IConsoleDriverFacade.cs b/Terminal.Gui/Drivers/V2/IConsoleDriverFacade.cs index 2bebf3c9bd..b670a196d3 100644 --- a/Terminal.Gui/Drivers/V2/IConsoleDriverFacade.cs +++ b/Terminal.Gui/Drivers/V2/IConsoleDriverFacade.cs @@ -10,5 +10,16 @@ public interface IConsoleDriverFacade /// e.g. into events /// and detecting and processing ansi escape sequences. /// - public IInputProcessor InputProcessor { get; } + IInputProcessor InputProcessor { get; } + + /// + /// Describes the desired screen state. Data source for . + /// + IOutputBuffer OutputBuffer { get; } + + /// + /// Interface for classes responsible for reporting the current + /// size of the terminal window. + /// + IWindowSizeMonitor WindowSizeMonitor { get; } } diff --git a/Terminal.Gui/Drivers/V2/IInputProcessor.cs b/Terminal.Gui/Drivers/V2/IInputProcessor.cs index 93d5cd7773..2c990db3fc 100644 --- a/Terminal.Gui/Drivers/V2/IInputProcessor.cs +++ b/Terminal.Gui/Drivers/V2/IInputProcessor.cs @@ -58,4 +58,15 @@ public interface IInputProcessor /// /// public IAnsiResponseParser GetParser (); + + /// + /// Handles surrogate pairs in the input stream. + /// + /// The key from input. + /// Get the surrogate pair or the key. + /// + /// if the result is a valid surrogate pair or a valid key, otherwise + /// . + /// + bool IsValidInput (Key key, out Key result); } diff --git a/Terminal.Gui/Drivers/V2/IMainLoop.cs b/Terminal.Gui/Drivers/V2/IMainLoop.cs index 647776cbe3..aee2e381fa 100644 --- a/Terminal.Gui/Drivers/V2/IMainLoop.cs +++ b/Terminal.Gui/Drivers/V2/IMainLoop.cs @@ -48,7 +48,14 @@ public interface IMainLoop : IDisposable /// /// /// - void Initialize (ITimedEvents timedEvents, ConcurrentQueue inputBuffer, IInputProcessor inputProcessor, IConsoleOutput consoleOutput); + /// + void Initialize ( + ITimedEvents timedEvents, + ConcurrentQueue inputBuffer, + IInputProcessor inputProcessor, + IConsoleOutput consoleOutput, + IComponentFactory componentFactory + ); /// /// Perform a single iteration of the main loop then blocks for a fixed length diff --git a/Terminal.Gui/Drivers/V2/IWindowsInput.cs b/Terminal.Gui/Drivers/V2/IWindowsInput.cs index d8431b22fe..3bc47fe7d2 100644 --- a/Terminal.Gui/Drivers/V2/IWindowsInput.cs +++ b/Terminal.Gui/Drivers/V2/IWindowsInput.cs @@ -1,4 +1,4 @@ namespace Terminal.Gui.Drivers; -internal interface IWindowsInput : IConsoleInput +public interface IWindowsInput : IConsoleInput { } diff --git a/Terminal.Gui/Drivers/V2/InputProcessor.cs b/Terminal.Gui/Drivers/V2/InputProcessor.cs index c860ba796e..54244cfd5a 100644 --- a/Terminal.Gui/Drivers/V2/InputProcessor.cs +++ b/Terminal.Gui/Drivers/V2/InputProcessor.cs @@ -165,7 +165,8 @@ private IEnumerable ReleaseParserHeldKeysIfStale () internal char _highSurrogate = '\0'; - internal bool IsValidInput (Key key, out Key result) + /// + public bool IsValidInput (Key key, out Key result) { result = key; @@ -179,6 +180,22 @@ internal bool IsValidInput (Key key, out Key result) if (_highSurrogate > 0 && char.IsLowSurrogate ((char)key)) { result = (KeyCode)new Rune (_highSurrogate, (char)key).Value; + + if (key.IsAlt) + { + result = new (result.KeyCode | KeyCode.AltMask); + } + + if (key.IsCtrl) + { + result = new (result.KeyCode | KeyCode.CtrlMask); + } + + if (key.IsShift) + { + result = new (result.KeyCode | KeyCode.ShiftMask); + } + _highSurrogate = '\0'; return true; diff --git a/Terminal.Gui/Drivers/V2/MainLoop.cs b/Terminal.Gui/Drivers/V2/MainLoop.cs index 5b6d9fdde7..a429d42310 100644 --- a/Terminal.Gui/Drivers/V2/MainLoop.cs +++ b/Terminal.Gui/Drivers/V2/MainLoop.cs @@ -83,7 +83,14 @@ public IWindowSizeMonitor WindowSizeMonitor /// /// /// - public void Initialize (ITimedEvents timedEvents, ConcurrentQueue inputBuffer, IInputProcessor inputProcessor, IConsoleOutput consoleOutput) + /// + public void Initialize ( + ITimedEvents timedEvents, + ConcurrentQueue inputBuffer, + IInputProcessor inputProcessor, + IConsoleOutput consoleOutput, + IComponentFactory componentFactory + ) { InputBuffer = inputBuffer; Out = consoleOutput; @@ -92,18 +99,22 @@ public void Initialize (ITimedEvents timedEvents, ConcurrentQueue inputBuffer TimedEvents = timedEvents; AnsiRequestScheduler = new (InputProcessor.GetParser ()); - WindowSizeMonitor = new WindowSizeMonitor (Out, OutputBuffer); + WindowSizeMonitor = componentFactory.CreateWindowSizeMonitor (Out, OutputBuffer); } /// public void Iteration () { + + Application.RaiseIteration (); + DateTime dt = Now (); + int timeAllowed = 1000 / Math.Max(1,(int)Application.MaximumIterationsPerSecond); IterationImpl (); TimeSpan took = Now () - dt; - TimeSpan sleepFor = TimeSpan.FromMilliseconds (50) - took; + TimeSpan sleepFor = TimeSpan.FromMilliseconds (timeAllowed) - took; Logging.TotalIterationMetric.Record (took.Milliseconds); @@ -123,7 +134,8 @@ internal void IterationImpl () if (Application.Top != null) { bool needsDrawOrLayout = AnySubViewsNeedDrawn (Application.Popover?.GetActivePopover () as View) - || AnySubViewsNeedDrawn (Application.Top); + || AnySubViewsNeedDrawn (Application.Top) + || (Application.MouseGrabHandler.MouseGrabView != null && AnySubViewsNeedDrawn (Application.MouseGrabHandler.MouseGrabView)); bool sizeChanged = WindowSizeMonitor.Poll (); diff --git a/Terminal.Gui/Drivers/V2/MainLoopCoordinator.cs b/Terminal.Gui/Drivers/V2/MainLoopCoordinator.cs index a70b089567..d441ab32fd 100644 --- a/Terminal.Gui/Drivers/V2/MainLoopCoordinator.cs +++ b/Terminal.Gui/Drivers/V2/MainLoopCoordinator.cs @@ -13,12 +13,11 @@ namespace Terminal.Gui.Drivers; /// internal class MainLoopCoordinator : IMainLoopCoordinator { - private readonly Func> _inputFactory; private readonly ConcurrentQueue _inputBuffer; private readonly IInputProcessor _inputProcessor; private readonly IMainLoop _loop; + private readonly IComponentFactory _componentFactory; private readonly CancellationTokenSource _tokenSource = new (); - private readonly Func _outputFactory; private IConsoleInput _input; private IConsoleOutput _output; private readonly object _oLockInitialization = new (); @@ -47,19 +46,16 @@ internal class MainLoopCoordinator : IMainLoopCoordinator /// public MainLoopCoordinator ( ITimedEvents timedEvents, - Func> inputFactory, ConcurrentQueue inputBuffer, - IInputProcessor inputProcessor, - Func outputFactory, - IMainLoop loop + IMainLoop loop, + IComponentFactory componentFactory ) { _timedEvents = timedEvents; - _inputFactory = inputFactory; _inputBuffer = inputBuffer; - _inputProcessor = inputProcessor; - _outputFactory = outputFactory; + _inputProcessor = componentFactory.CreateInputProcessor (_inputBuffer); _loop = loop; + _componentFactory = componentFactory; } /// @@ -89,7 +85,7 @@ public async Task StartAsync () throw _inputTask.Exception; } - throw new ("Input loop exited during startup instead of entering read loop properly (i.e. and blocking)"); + Logging.Logger.LogCritical("Input loop exited during startup instead of entering read loop properly (i.e. and blocking)"); } Logging.Logger.LogInformation ("Main Loop Coordinator booting complete"); @@ -102,7 +98,7 @@ private void RunInput () lock (_oLockInitialization) { // Instance must be constructed on the thread in which it is used. - _input = _inputFactory.Invoke (); + _input = _componentFactory.CreateInput (); _input.Initialize (_inputBuffer); BuildFacadeIfPossible (); @@ -142,8 +138,8 @@ private void BootMainLoop () lock (_oLockInitialization) { // Instance must be constructed on the thread in which it is used. - _output = _outputFactory.Invoke (); - _loop.Initialize (_timedEvents, _inputBuffer, _inputProcessor, _output); + _output = _componentFactory.CreateOutput (); + _loop.Initialize (_timedEvents, _inputBuffer, _inputProcessor, _output,_componentFactory); BuildFacadeIfPossible (); } diff --git a/Terminal.Gui/Drivers/V2/NetOutput.cs b/Terminal.Gui/Drivers/V2/NetOutput.cs index 17956a3dfa..5da2ddc16f 100644 --- a/Terminal.Gui/Drivers/V2/NetOutput.cs +++ b/Terminal.Gui/Drivers/V2/NetOutput.cs @@ -28,7 +28,11 @@ public NetOutput () } /// - public void Write (ReadOnlySpan text) { Console.Out.Write (text); } + public void Write (ReadOnlySpan text) + { + Console.Out.Write (text); + } + /// public Size GetWindowSize () @@ -67,8 +71,12 @@ protected override void AppendOrWriteAttribute (StringBuilder output, Attribute EscSeqUtils.CSI_AppendTextStyleChange (output, redrawTextStyle, attr.Style); } - /// - protected override void Write (StringBuilder output) { Console.Out.Write (output); } + + /// + protected override void Write (StringBuilder output) + { + Console.Out.Write (output); + } protected override bool SetCursorPositionImpl (int col, int row) { @@ -102,7 +110,10 @@ protected override bool SetCursorPositionImpl (int col, int row) } /// - public void Dispose () { } + public void Dispose () + { + } + /// public override void SetCursorVisibility (CursorVisibility visibility) diff --git a/Terminal.Gui/Drivers/V2/OutputBase.cs b/Terminal.Gui/Drivers/V2/OutputBase.cs index b28551e4bd..312c5658ef 100644 --- a/Terminal.Gui/Drivers/V2/OutputBase.cs +++ b/Terminal.Gui/Drivers/V2/OutputBase.cs @@ -1,4 +1,10 @@ -namespace Terminal.Gui.Drivers; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Terminal.Gui.Drivers; public abstract class OutputBase { diff --git a/Terminal.Gui/Drivers/V2/OutputBuffer.cs b/Terminal.Gui/Drivers/V2/OutputBuffer.cs index fa44d56309..a424bbfd9b 100644 --- a/Terminal.Gui/Drivers/V2/OutputBuffer.cs +++ b/Terminal.Gui/Drivers/V2/OutputBuffer.cs @@ -141,6 +141,8 @@ public void AddRune (Rune rune) return; } + Clip ??= new Region (Screen); + Rectangle clipRect = Clip!.GetBounds (); if (validLocation) diff --git a/Terminal.Gui/Drivers/V2/ToplevelTransitionManager.cs b/Terminal.Gui/Drivers/V2/ToplevelTransitionManager.cs index 6a12f0861c..4e5937ac32 100644 --- a/Terminal.Gui/Drivers/V2/ToplevelTransitionManager.cs +++ b/Terminal.Gui/Drivers/V2/ToplevelTransitionManager.cs @@ -20,6 +20,9 @@ public void RaiseReadyEventIfNeeded () { top.OnReady (); _readiedTopLevels.Add (top); + + // Views can be closed and opened and run again multiple times, see End_Does_Not_Dispose + top.Closed += (s, e) => _readiedTopLevels.Remove (top); } } diff --git a/Terminal.Gui/Drivers/WindowsDriver/WindowsConsole.cs b/Terminal.Gui/Drivers/WindowsDriver/WindowsConsole.cs index 445ba1410e..ba3dee5993 100644 --- a/Terminal.Gui/Drivers/WindowsDriver/WindowsConsole.cs +++ b/Terminal.Gui/Drivers/WindowsDriver/WindowsConsole.cs @@ -5,7 +5,7 @@ namespace Terminal.Gui.Drivers; -internal partial class WindowsConsole +public partial class WindowsConsole { private CancellationTokenSource? _inputReadyCancellationTokenSource; private readonly BlockingCollection _inputQueue = new (new ConcurrentQueue ()); diff --git a/Terminal.Gui/ViewBase/Adornment/ShadowView.cs b/Terminal.Gui/ViewBase/Adornment/ShadowView.cs index 12f2e08d93..a2d2eb5773 100644 --- a/Terminal.Gui/ViewBase/Adornment/ShadowView.cs +++ b/Terminal.Gui/ViewBase/Adornment/ShadowView.cs @@ -151,6 +151,13 @@ private Attribute GetAttributeUnderLocation (Point location) return Attribute.Default; } + if (Driver?.Contents == null || + location.Y < 0 || location.Y >= Driver.Contents.GetLength (0) || + location.X < 0 || location.X >= Driver.Contents.GetLength (1)) + { + return Attribute.Default; + } + Attribute attr = Driver!.Contents! [location.Y, location.X].Attribute!.Value; var newAttribute = diff --git a/Terminal.Gui/ViewBase/View.Drawing.cs b/Terminal.Gui/ViewBase/View.Drawing.cs index a8d2d4bf85..61feeb1c47 100644 --- a/Terminal.Gui/ViewBase/View.Drawing.cs +++ b/Terminal.Gui/ViewBase/View.Drawing.cs @@ -807,7 +807,7 @@ public void SetNeedsDraw (Rectangle viewPortRelativeRegion) } // There was multiple enumeration error here, so calling ToArray - probably a stop gap - foreach (View subview in InternalSubViews) + foreach (View subview in InternalSubViews.ToArray ()) { if (subview.Frame.IntersectsWith (viewPortRelativeRegion)) { diff --git a/Terminal.Gui/Views/Menuv1/Menu.cs b/Terminal.Gui/Views/Menuv1/Menu.cs index 8969595acd..c4579aab35 100644 --- a/Terminal.Gui/Views/Menuv1/Menu.cs +++ b/Terminal.Gui/Views/Menuv1/Menu.cs @@ -527,7 +527,7 @@ private void Application_RootMouseEvent (object? sender, MouseEventArgs a) private void Application_UnGrabbedMouse (object? sender, ViewEventArgs a) { - if (_host.IsMenuOpen) + if (_host is { IsMenuOpen: true }) { _host.CloseAllMenus (); } diff --git a/Tests/IntegrationTests/FluentTests/BasicFluentAssertionTests.cs b/Tests/IntegrationTests/FluentTests/BasicFluentAssertionTests.cs index 0796d5f005..e3557e8776 100644 --- a/Tests/IntegrationTests/FluentTests/BasicFluentAssertionTests.cs +++ b/Tests/IntegrationTests/FluentTests/BasicFluentAssertionTests.cs @@ -16,7 +16,7 @@ public BasicFluentAssertionTests (ITestOutputHelper outputHelper) [ClassData (typeof (V2TestDrivers))] public void GuiTestContext_NewInstance_Runs (V2TestDriver d) { - using GuiTestContext context = With.A (40, 10, d); + using GuiTestContext context = With.A (40, 10, d,_out); Assert.True (Application.Top!.Running); context.WriteOutLogs (_out); diff --git a/Tests/IntegrationTests/FluentTests/FileDialogFluentTests.cs b/Tests/IntegrationTests/FluentTests/FileDialogFluentTests.cs index 47a819fc78..460313a33c 100644 --- a/Tests/IntegrationTests/FluentTests/FileDialogFluentTests.cs +++ b/Tests/IntegrationTests/FluentTests/FileDialogFluentTests.cs @@ -41,15 +41,28 @@ private MockFileSystem CreateExampleFileSystem () return mockFileSystem; } + private Toplevel NewSaveDialog (out SaveDialog sd, bool modal = true) + { + return NewSaveDialog (out sd, out _, modal); + } + + private Toplevel NewSaveDialog (out SaveDialog sd, out MockFileSystem fs,bool modal = true) + { + fs = CreateExampleFileSystem (); + sd = new SaveDialog (fs) { Modal = modal }; + return sd; + } + + [Theory] [ClassData (typeof (V2TestDrivers))] public void CancelFileDialog_UsingEscape (V2TestDriver d) { - var sd = new SaveDialog (CreateExampleFileSystem ()); - using var c = With.A (sd, 100, 20, d) + SaveDialog? sd = null; + using var c = With.A (()=>NewSaveDialog(out sd), 100, 20, d) .ScreenShot ("Save dialog", _out) .Escape () - .Then (() => Assert.True (sd.Canceled)) + .Then (() => Assert.True (sd!.Canceled)) .Stop (); } @@ -57,8 +70,9 @@ public void CancelFileDialog_UsingEscape (V2TestDriver d) [ClassData (typeof (V2TestDrivers))] public void CancelFileDialog_UsingCancelButton_TabThenEnter (V2TestDriver d) { - var sd = new SaveDialog (CreateExampleFileSystem ()) { Modal = false }; - using var c = With.A (sd, 100, 20, d) + + SaveDialog? sd = null; + using var c = With.A (() => NewSaveDialog (out sd,modal:false), 100, 20, d) .ScreenShot ("Save dialog", _out) .Focus