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