diff --git a/Examples/UICatalog/Scenarios/SendKeys.cs b/Examples/UICatalog/Scenarios/SendKeys.cs
index 04a57d4e4e..4e5591559b 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 += e.ToString ();
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 32127a49cf..756290cc2c 100644
--- a/Terminal.Gui/App/Application.Run.cs
+++ b/Terminal.Gui/App/Application.Run.cs
@@ -460,7 +460,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; }
@@ -618,4 +618,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..e1012c85c1 100644
--- a/Terminal.Gui/App/Application.cs
+++ b/Terminal.Gui/App/Application.cs
@@ -51,6 +51,20 @@ 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 > which is a 40ms sleep
+ /// after iteration (factoring in how long iteration took to run).
+ /// Note that not every iteration draws (see ).
+ /// Only affects v2 drivers.
+ ///
+ public static ushort MaximumIterationsPerSecond = DefaultMaximumIterationsPerSecond;
+
+ ///
+ /// Default value for
+ ///
+ public const ushort DefaultMaximumIterationsPerSecond = 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..a3964328f6 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);
@@ -258,4 +270,4 @@ public override void LayoutAndDraw (bool forceDraw)
Application.Top?.SetNeedsDraw();
Application.Top?.SetNeedsLayout ();
}
-}
+}
\ No newline at end of file
diff --git a/Terminal.Gui/Drivers/V2/ComponentFactory.cs b/Terminal.Gui/Drivers/V2/ComponentFactory.cs
new file mode 100644
index 0000000000..3c5adddd2a
--- /dev/null
+++ b/Terminal.Gui/Drivers/V2/ComponentFactory.cs
@@ -0,0 +1,26 @@
+#nullable enable
+using System.Collections.Concurrent;
+
+namespace Terminal.Gui.Drivers;
+
+///
+/// Abstract base class implementation of
+///
+///
+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 ();
+}
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/IComponentFactory.cs b/Terminal.Gui/Drivers/V2/IComponentFactory.cs
new file mode 100644
index 0000000000..f4f8767239
--- /dev/null
+++ b/Terminal.Gui/Drivers/V2/IComponentFactory.cs
@@ -0,0 +1,50 @@
+#nullable enable
+using System.Collections.Concurrent;
+
+namespace Terminal.Gui.Drivers;
+
+///
+/// Base untyped interface for for methods that are not templated on low level
+/// console input type.
+///
+public interface IComponentFactory
+{
+ ///
+ /// Create the class for the current driver implementation i.e. the class responsible for
+ /// rendering into the console.
+ ///
+ ///
+ IConsoleOutput CreateOutput ();
+}
+
+///
+/// Creates driver specific subcomponent classes (, etc) for a
+/// .
+///
+///
+public interface IComponentFactory : IComponentFactory
+{
+ ///
+ /// Create class for the current driver implementation i.e. the class responsible for reading
+ /// user input from the console.
+ ///
+ ///
+ IConsoleInput CreateInput ();
+
+ ///
+ /// Creates the class for the current driver implementation i.e. the class responsible for
+ /// translating raw console input into Terminal.Gui common event and .
+ ///
+ ///
+ ///
+ IInputProcessor CreateInputProcessor (ConcurrentQueue inputBuffer);
+
+ ///
+ /// Creates class for the current driver implementation i.e. the class responsible for
+ /// reporting the current size of the terminal window.
+ ///
+ ///
+ ///
+ ///
+ IWindowSizeMonitor CreateWindowSizeMonitor (IConsoleOutput consoleOutput, IOutputBuffer outputBuffer);
+}
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..17ba0d1774 100644
--- a/Terminal.Gui/Drivers/V2/IWindowsInput.cs
+++ b/Terminal.Gui/Drivers/V2/IWindowsInput.cs
@@ -1,4 +1,7 @@
namespace Terminal.Gui.Drivers;
-internal interface IWindowsInput : IConsoleInput
+///
+/// Interface for windows only input which uses low level win32 apis (v2win)
+///
+public interface IWindowsInput : IConsoleInput
{ }
diff --git a/Terminal.Gui/Drivers/V2/InputProcessor.cs b/Terminal.Gui/Drivers/V2/InputProcessor.cs
index c860ba796e..04a4e3b6c2 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 = result.WithAlt;
+ }
+
+ if (key.IsCtrl)
+ {
+ result = result.WithCtrl;
+ }
+
+ if (key.IsShift)
+ {
+ result = result.WithShift;
+ }
+
_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..d1d5153416 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 ();
@@ -32,34 +31,22 @@ internal class MainLoopCoordinator : IMainLoopCoordinator
/// Creates a new coordinator
///
///
- ///
- /// Function to create a new input. This must call
- /// explicitly and cannot return an existing instance. This requirement arises because Windows
- /// console screen buffer APIs are thread-specific for certain operations.
- ///
///
- ///
- ///
- /// Function to create a new output. This must call
- /// explicitly and cannot return an existing instance. This requirement arises because Windows
- /// console screen buffer APIs are thread-specific for certain operations.
- ///
///
+ /// Factory for creating driver components
+ /// (, etc)
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 +76,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 +89,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 +129,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/NetComponentFactory.cs b/Terminal.Gui/Drivers/V2/NetComponentFactory.cs
new file mode 100644
index 0000000000..3b682d1fc1
--- /dev/null
+++ b/Terminal.Gui/Drivers/V2/NetComponentFactory.cs
@@ -0,0 +1,29 @@
+#nullable enable
+using System.Collections.Concurrent;
+
+namespace Terminal.Gui.Drivers;
+
+///
+/// implementation for native csharp console I/O i.e. v2net.
+/// This factory creates instances of internal classes , etc.
+///
+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);
+ }
+}
diff --git a/Terminal.Gui/Drivers/V2/NetOutput.cs b/Terminal.Gui/Drivers/V2/NetOutput.cs
index 17956a3dfa..eea6b3edf2 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,9 +71,14 @@ 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)
{
if (_lastCursorPosition is { } && _lastCursorPosition.Value.X == col && _lastCursorPosition.Value.Y == row)
@@ -102,9 +111,12 @@ protected override bool SetCursorPositionImpl (int col, int row)
}
///
- public void Dispose () { }
+ public void Dispose ()
+ {
+ }
- ///
+
+ ///
public override void SetCursorVisibility (CursorVisibility visibility)
{
Console.Out.Write (visibility == CursorVisibility.Default ? EscSeqUtils.CSI_ShowCursor : EscSeqUtils.CSI_HideCursor);
diff --git a/Terminal.Gui/Drivers/V2/OutputBase.cs b/Terminal.Gui/Drivers/V2/OutputBase.cs
index b28551e4bd..6be2e2b89e 100644
--- a/Terminal.Gui/Drivers/V2/OutputBase.cs
+++ b/Terminal.Gui/Drivers/V2/OutputBase.cs
@@ -1,5 +1,14 @@
-namespace Terminal.Gui.Drivers;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+namespace Terminal.Gui.Drivers;
+
+///
+/// Abstract base class to assist with implementing .
+///
public abstract class OutputBase
{
private CursorVisibility? _cachedCursorVisibility;
@@ -7,7 +16,7 @@ public abstract class OutputBase
// Last text style used, for updating style with EscSeqUtils.CSI_AppendTextStyleChange().
private TextStyle _redrawTextStyle = TextStyle.None;
- ///
+ ///
public virtual void Write (IOutputBuffer buffer)
{
if (ConsoleDriver.RunningUnitTests)
@@ -144,6 +153,14 @@ public virtual void Write (IOutputBuffer buffer)
_cachedCursorVisibility = savedVisibility;
}
+ ///
+ /// Changes the color and text style of the console to the given and .
+ /// If command can be buffered in line with other output (e.g. CSI sequence) then it should be appended to
+ /// otherwise the relevant output state should be flushed directly (e.g. by calling relevant win 32 API method)
+ ///
+ ///
+ ///
+ ///
protected abstract void AppendOrWriteAttribute (StringBuilder output, Attribute attr, TextStyle redrawTextStyle);
private void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref int outputWidth)
@@ -155,9 +172,24 @@ private void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref
outputWidth = 0;
}
+ ///
+ /// Output the contents of the to the console.
+ ///
+ ///
protected abstract void Write (StringBuilder output);
+ ///
+ /// When overriden in derived class, positions the terminal output cursor to the specified point on the screen.
+ ///
+ /// Column to move cursor to
+ /// Row to move cursor to
+ ///
protected abstract bool SetCursorPositionImpl (int screenPositionX, int screenPositionY);
+ ///
+ /// Changes the visibility of the cursor in the terminal to the specified e.g.
+ /// the flashing indicator, invisible, box indicator etc.
+ ///
+ ///
public abstract void SetCursorVisibility (CursorVisibility visibility);
}
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/V2/WindowsComponentFactory.cs b/Terminal.Gui/Drivers/V2/WindowsComponentFactory.cs
new file mode 100644
index 0000000000..6436ddc834
--- /dev/null
+++ b/Terminal.Gui/Drivers/V2/WindowsComponentFactory.cs
@@ -0,0 +1,29 @@
+#nullable enable
+using System.Collections.Concurrent;
+
+namespace Terminal.Gui.Drivers;
+
+///
+/// implementation for win32 windows only I/O i.e. v2win.
+/// This factory creates instances of internal classes , etc.
+///
+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 ();
+ }
+}
diff --git a/Terminal.Gui/Drivers/V2/WindowsOutput.cs b/Terminal.Gui/Drivers/V2/WindowsOutput.cs
index 5152d3a238..2e42ae3fc4 100644
--- a/Terminal.Gui/Drivers/V2/WindowsOutput.cs
+++ b/Terminal.Gui/Drivers/V2/WindowsOutput.cs
@@ -431,7 +431,7 @@ protected override bool SetCursorPositionImpl (int screenPositionX, int screenPo
return true;
}
- ///
+ ///
public override void SetCursorVisibility (CursorVisibility visibility)
{
if (ConsoleDriver.RunningUnitTests)
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/Views/Dialog.cs b/Terminal.Gui/Views/Dialog.cs
index 88f2af02c7..41f6ac7ab9 100644
--- a/Terminal.Gui/Views/Dialog.cs
+++ b/Terminal.Gui/Views/Dialog.cs
@@ -109,12 +109,6 @@ public bool Canceled
{
get
{
-#if DEBUG_IDISPOSABLE
- if (EnableDebugIDisposableAsserts && WasDisposed)
- {
- throw new ObjectDisposedException (GetType ().FullName);
- }
-#endif
return _canceled;
}
set
diff --git a/Tests/IntegrationTests/FluentTests/BasicFluentAssertionTests.cs b/Tests/IntegrationTests/FluentTests/BasicFluentAssertionTests.cs
index 0796d5f005..24cb509201 100644
--- a/Tests/IntegrationTests/FluentTests/BasicFluentAssertionTests.cs
+++ b/Tests/IntegrationTests/FluentTests/BasicFluentAssertionTests.cs
@@ -1,4 +1,5 @@
using TerminalGuiFluentTesting;
+using TerminalGuiFluentTestingXunit;
using Xunit.Abstractions;
namespace IntegrationTests.FluentTests;
@@ -7,16 +8,13 @@ public class BasicFluentAssertionTests
{
private readonly TextWriter _out;
- public BasicFluentAssertionTests (ITestOutputHelper outputHelper)
- {
- _out = new TestOutputWriter (outputHelper);
- }
+ public BasicFluentAssertionTests (ITestOutputHelper outputHelper) { _out = new TestOutputWriter (outputHelper); }
[Theory]
[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);
@@ -34,9 +32,6 @@ public void GuiTestContext_QuitKey_Stops (V2TestDriver d)
context.RaiseKeyDownEvent (Application.QuitKey);
Assert.False (top!.Running);
- Application.Top?.Dispose ();
- Application.Shutdown ();
-
context.WriteOutLogs (_out);
context.Stop ();
}
@@ -69,9 +64,10 @@ public void TestWindowsResize (V2TestDriver d)
using GuiTestContext c = With.A (40, 10, d)
.Add (lbl)
- .Then (() => Assert.Equal (38, lbl.Frame.Width)) // Window has 2 border
+ .AssertEqual (38, lbl.Frame.Width) // Window has 2 border
.ResizeConsole (20, 20)
- .Then (() => Assert.Equal (18, lbl.Frame.Width))
+ .WaitIteration ()
+ .AssertEqual (18, lbl.Frame.Width)
.WriteOutLogs (_out)
.Stop ();
}
@@ -85,7 +81,7 @@ public void ContextMenu_CrashesOnRight (V2TestDriver d)
MenuItemv2 [] menuItems = [new ("_New File", string.Empty, () => { clicked = true; })];
using GuiTestContext c = With.A (40, 10, d)
- .WithContextMenu (new PopoverMenu (menuItems))
+ .WithContextMenu (new (menuItems))
.ScreenShot ("Before open menu", _out)
// Click in main area inside border
@@ -98,7 +94,6 @@ public void ContextMenu_CrashesOnRight (V2TestDriver d)
Assert.NotNull (popover);
var popoverMenu = popover as PopoverMenu;
popoverMenu!.Root!.BorderStyle = LineStyle.Single;
-
})
.WaitIteration ()
.ScreenShot ("After open menu", _out)
@@ -114,26 +109,30 @@ public void ContextMenu_OpenSubmenu (V2TestDriver d)
{
var clicked = false;
- MenuItemv2 [] menuItems = [
- new ("One", "", null),
- new ("Two", "", null),
- new ("Three", "", null),
- new ("Four", "", new (
- [
- new ("SubMenu1", "", null),
- new ("SubMenu2", "", ()=>clicked=true),
- new ("SubMenu3", "", null),
- new ("SubMenu4", "", null),
- new ("SubMenu5", "", null),
- new ("SubMenu6", "", null),
- new ("SubMenu7", "", null)
- ])),
- new ("Five", "", null),
- new ("Six", "", null)
- ];
+ MenuItemv2 [] menuItems =
+ [
+ new ("One", "", null),
+ new ("Two", "", null),
+ new ("Three", "", null),
+ new (
+ "Four",
+ "",
+ new (
+ [
+ new ("SubMenu1", "", null),
+ new ("SubMenu2", "", () => clicked = true),
+ new ("SubMenu3", "", null),
+ new ("SubMenu4", "", null),
+ new ("SubMenu5", "", null),
+ new ("SubMenu6", "", null),
+ new ("SubMenu7", "", null)
+ ])),
+ new ("Five", "", null),
+ new ("Six", "", null)
+ ];
using GuiTestContext c = With.A (40, 10, d)
- .WithContextMenu (new PopoverMenu (menuItems))
+ .WithContextMenu (new (menuItems))
.ScreenShot ("Before open menu", _out)
// Click in main area inside border
@@ -177,43 +176,43 @@ public void Toplevel_TabGroup_Forward_Backward (V2TestDriver d)
Application.Top!.Add (w1, w2, w3);
})
.WaitIteration ()
- .Then (() => Assert.True (v5.HasFocus))
+ .AssertTrue (v5.HasFocus)
.RaiseKeyDownEvent (Key.F6)
- .Then (() => Assert.True (v1.HasFocus))
+ .AssertTrue (v1.HasFocus)
.RaiseKeyDownEvent (Key.F6)
- .Then (() => Assert.True (v3.HasFocus))
+ .AssertTrue (v3.HasFocus)
.RaiseKeyDownEvent (Key.F6.WithShift)
- .Then (() => Assert.True (v1.HasFocus))
+ .AssertTrue (v1.HasFocus)
.RaiseKeyDownEvent (Key.F6.WithShift)
- .Then (() => Assert.True (v5.HasFocus))
+ .AssertTrue (v5.HasFocus)
.RaiseKeyDownEvent (Key.F6.WithShift)
- .Then (() => Assert.True (v3.HasFocus))
+ .AssertTrue (v3.HasFocus)
.RaiseKeyDownEvent (Key.F6)
- .Then (() => Assert.True (v5.HasFocus))
+ .AssertTrue (v5.HasFocus)
.RaiseKeyDownEvent (Key.F6)
- .Then (() => Assert.True (v1.HasFocus))
+ .AssertTrue (v1.HasFocus)
.RaiseKeyDownEvent (Key.F6)
- .Then (() => Assert.True (v3.HasFocus))
+ .AssertTrue (v3.HasFocus)
.RaiseKeyDownEvent (Key.F6.WithShift)
- .Then (() => Assert.True (v1.HasFocus))
+ .AssertTrue (v1.HasFocus)
.RaiseKeyDownEvent (Key.F6.WithShift)
- .Then (() => Assert.True (v5.HasFocus))
+ .AssertTrue (v5.HasFocus)
.RaiseKeyDownEvent (Key.F6.WithShift)
- .Then (() => Assert.True (v3.HasFocus))
+ .AssertTrue (v3.HasFocus)
.RaiseKeyDownEvent (Key.Tab)
- .Then (() => Assert.True (v4.HasFocus))
+ .AssertTrue (v4.HasFocus)
.RaiseKeyDownEvent (Key.F6)
- .Then (() => Assert.True (v5.HasFocus))
+ .AssertTrue (v5.HasFocus)
.RaiseKeyDownEvent (Key.F6)
- .Then (() => Assert.True (v1.HasFocus))
+ .AssertTrue (v1.HasFocus)
.RaiseKeyDownEvent (Key.F6.WithShift)
- .Then (() => Assert.True (v5.HasFocus))
+ .AssertTrue (v5.HasFocus)
.RaiseKeyDownEvent (Key.Tab)
- .Then (() => Assert.True (v6.HasFocus))
+ .AssertTrue (v6.HasFocus)
.RaiseKeyDownEvent (Key.F6.WithShift)
- .Then (() => Assert.True (v4.HasFocus))
+ .AssertTrue (v4.HasFocus)
.RaiseKeyDownEvent (Key.F6)
- .Then (() => Assert.True (v6.HasFocus))
+ .AssertTrue (v6.HasFocus)
.WriteOutLogs (_out)
.Stop ();
Assert.False (v1.HasFocus);
@@ -221,6 +220,5 @@ public void Toplevel_TabGroup_Forward_Backward (V2TestDriver d)
Assert.False (v3.HasFocus);
Assert.False (v4.HasFocus);
Assert.False (v5.HasFocus);
- Assert.False (v6.HasFocus);
}
}
diff --git a/Tests/IntegrationTests/FluentTests/FileDialogFluentTests.cs b/Tests/IntegrationTests/FluentTests/FileDialogFluentTests.cs
index 47a819fc78..c8fea9d15e 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))
+ .AssertTrue (sd!.Canceled)
.Stop ();
}
@@ -57,11 +70,11 @@ 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