Merge branch 'copilot/add-fences-for-application-models' of tig:gui-cs/Terminal.Gui into copilot/add-fences-for-application-models

This commit is contained in:
Tig
2025-11-22 20:39:04 -07:00
8 changed files with 252 additions and 38 deletions

View File

@@ -13,38 +13,44 @@ public static partial class Application // Driver abstractions
internal set => ApplicationImpl.Instance.Driver = value;
}
private static bool _force16Colors = false; // Resources/config.json overrides
/// <inheritdoc cref="IApplication.Force16Colors"/>
[ConfigurationProperty (Scope = typeof (SettingsScope))]
[Obsolete ("The legacy static Application object is going away.")]
public static bool Force16Colors
{
get => ApplicationImpl.Instance.Force16Colors;
set => ApplicationImpl.Instance.Force16Colors = value;
get => _force16Colors;
set
{
bool oldValue = _force16Colors;
_force16Colors = value;
Force16ColorsChanged?.Invoke (null, new ValueChangedEventArgs<bool> (oldValue, _force16Colors));
}
}
/// <summary>Raised when <see cref="Force16Colors"/> changes.</summary>
public static event EventHandler<ValueChangedEventArgs<bool>>? Force16ColorsChanged;
private static string _forceDriver = string.Empty; // Resources/config.json overrides
/// <inheritdoc cref="IApplication.ForceDriver"/>
[ConfigurationProperty (Scope = typeof (SettingsScope))]
[Obsolete ("The legacy static Application object is going away.")]
public static string ForceDriver
{
get => ApplicationImpl.Instance.ForceDriver;
get => _forceDriver;
set
{
if (!string.IsNullOrEmpty (ApplicationImpl.Instance.ForceDriver) && value != Driver?.GetName ())
{
// ForceDriver cannot be changed if it has a valid value
return;
}
if (ApplicationImpl.Instance.Initialized && value != Driver?.GetName ())
{
throw new InvalidOperationException ($"The {nameof (ForceDriver)} can only be set before initialized.");
}
ApplicationImpl.Instance.ForceDriver = value;
string oldValue = _forceDriver;
_forceDriver = value;
ForceDriverChanged?.Invoke (null, new ValueChangedEventArgs<string> (oldValue, _forceDriver));
}
}
/// <summary>Raised when <see cref="ForceDriver"/> changes.</summary>
public static event EventHandler<ValueChangedEventArgs<string>>? ForceDriverChanged;
/// <inheritdoc cref="IApplication.Sixel"/>
[Obsolete ("The legacy static Application object is going away.")]
public static List<SixelToRender> Sixel => ApplicationImpl.Instance.Sixel;

View File

@@ -4,15 +4,25 @@ namespace Terminal.Gui.App;
public static partial class Application // Mouse handling
{
private static bool _isMouseDisabled = false; // Resources/config.json overrides
/// <summary>Disable or enable the mouse. The mouse is enabled by default.</summary>
[ConfigurationProperty (Scope = typeof (SettingsScope))]
[Obsolete ("The legacy static Application object is going away.")]
public static bool IsMouseDisabled
{
get => Mouse.IsMouseDisabled;
set => Mouse.IsMouseDisabled = value;
get => _isMouseDisabled;
set
{
bool oldValue = _isMouseDisabled;
_isMouseDisabled = value;
IsMouseDisabledChanged?.Invoke (null, new ValueChangedEventArgs<bool> (oldValue, _isMouseDisabled));
}
}
/// <summary>Raised when <see cref="IsMouseDisabled"/> changes.</summary>
public static event EventHandler<ValueChangedEventArgs<bool>>? IsMouseDisabledChanged;
/// <summary>
/// Gets the <see cref="IMouse"/> instance that manages mouse event handling and state.
/// </summary>

View File

@@ -13,22 +13,43 @@ public static partial class Application // Navigation stuff
internal set => ApplicationImpl.Instance.Navigation = value;
}
private static Key _nextTabGroupKey = Key.F6; // Resources/config.json overrides
/// <summary>Alternative key to navigate forwards through views. Ctrl+Tab is the primary key.</summary>
[ConfigurationProperty (Scope = typeof (SettingsScope))]
[Obsolete ("The legacy static Application object is going away.")]public static Key NextTabGroupKey
[Obsolete ("The legacy static Application object is going away.")]
public static Key NextTabGroupKey
{
get => ApplicationImpl.Instance.Keyboard.NextTabGroupKey;
set => ApplicationImpl.Instance.Keyboard.NextTabGroupKey = value;
get => _nextTabGroupKey;
set
{
Key oldValue = _nextTabGroupKey;
_nextTabGroupKey = value;
NextTabGroupKeyChanged?.Invoke (null, new ValueChangedEventArgs<Key> (oldValue, _nextTabGroupKey));
}
}
/// <summary>Raised when <see cref="NextTabGroupKey"/> changes.</summary>
public static event EventHandler<ValueChangedEventArgs<Key>>? NextTabGroupKeyChanged;
private static Key _nextTabKey = Key.Tab; // Resources/config.json overrides
/// <summary>Alternative key to navigate forwards through views. Tab is the primary key.</summary>
[ConfigurationProperty (Scope = typeof (SettingsScope))]
public static Key NextTabKey
{
get => ApplicationImpl.Instance.Keyboard.NextTabKey;
set => ApplicationImpl.Instance.Keyboard.NextTabKey = value;
get => _nextTabKey;
set
{
Key oldValue = _nextTabKey;
_nextTabKey = value;
NextTabKeyChanged?.Invoke (null, new ValueChangedEventArgs<Key> (oldValue, _nextTabKey));
}
}
/// <summary>Raised when <see cref="NextTabKey"/> changes.</summary>
public static event EventHandler<ValueChangedEventArgs<Key>>? NextTabKeyChanged;
/// <summary>
/// Raised when the user releases a key.
/// <para>
@@ -48,19 +69,39 @@ public static partial class Application // Navigation stuff
remove => ApplicationImpl.Instance.Keyboard.KeyUp -= value;
}
private static Key _prevTabGroupKey = Key.F6.WithShift; // Resources/config.json overrides
/// <summary>Alternative key to navigate backwards through views. Shift+Ctrl+Tab is the primary key.</summary>
[ConfigurationProperty (Scope = typeof (SettingsScope))]
public static Key PrevTabGroupKey
{
get => ApplicationImpl.Instance.Keyboard.PrevTabGroupKey;
set => ApplicationImpl.Instance.Keyboard.PrevTabGroupKey = value;
get => _prevTabGroupKey;
set
{
Key oldValue = _prevTabGroupKey;
_prevTabGroupKey = value;
PrevTabGroupKeyChanged?.Invoke (null, new ValueChangedEventArgs<Key> (oldValue, _prevTabGroupKey));
}
}
/// <summary>Raised when <see cref="PrevTabGroupKey"/> changes.</summary>
public static event EventHandler<ValueChangedEventArgs<Key>>? PrevTabGroupKeyChanged;
private static Key _prevTabKey = Key.Tab.WithShift; // Resources/config.json overrides
/// <summary>Alternative key to navigate backwards through views. Shift+Tab is the primary key.</summary>
[ConfigurationProperty (Scope = typeof (SettingsScope))]
public static Key PrevTabKey
{
get => ApplicationImpl.Instance.Keyboard.PrevTabKey;
set => ApplicationImpl.Instance.Keyboard.PrevTabKey = value;
get => _prevTabKey;
set
{
Key oldValue = _prevTabKey;
_prevTabKey = value;
PrevTabKeyChanged?.Invoke (null, new ValueChangedEventArgs<Key> (oldValue, _prevTabKey));
}
}
/// <summary>Raised when <see cref="PrevTabKey"/> changes.</summary>
public static event EventHandler<ValueChangedEventArgs<Key>>? PrevTabKeyChanged;
}

View File

@@ -4,22 +4,42 @@ namespace Terminal.Gui.App;
public static partial class Application // Run (Begin -> Run -> Layout/Draw -> End -> Stop)
{
private static Key _quitKey = Key.Esc; // Resources/config.json overrides
/// <summary>Gets or sets the key to quit the application.</summary>
[ConfigurationProperty (Scope = typeof (SettingsScope))]
public static Key QuitKey
{
get => ApplicationImpl.Instance.Keyboard.QuitKey;
set => ApplicationImpl.Instance.Keyboard.QuitKey = value;
get => _quitKey;
set
{
Key oldValue = _quitKey;
_quitKey = value;
QuitKeyChanged?.Invoke (null, new ValueChangedEventArgs<Key> (oldValue, _quitKey));
}
}
/// <summary>Raised when <see cref="QuitKey"/> changes.</summary>
public static event EventHandler<ValueChangedEventArgs<Key>>? QuitKeyChanged;
private static Key _arrangeKey = Key.F5.WithCtrl; // Resources/config.json overrides
/// <summary>Gets or sets the key to activate arranging views using the keyboard.</summary>
[ConfigurationProperty (Scope = typeof (SettingsScope))]
public static Key ArrangeKey
{
get => ApplicationImpl.Instance.Keyboard.ArrangeKey;
set => ApplicationImpl.Instance.Keyboard.ArrangeKey = value;
get => _arrangeKey;
set
{
Key oldValue = _arrangeKey;
_arrangeKey = value;
ArrangeKeyChanged?.Invoke (null, new ValueChangedEventArgs<Key> (oldValue, _arrangeKey));
}
}
/// <summary>Raised when <see cref="ArrangeKey"/> changes.</summary>
public static event EventHandler<ValueChangedEventArgs<Key>>? ArrangeKeyChanged;
/// <inheritdoc cref="IApplication.Begin(IRunnable)"/>
[Obsolete ("The legacy static Application object is going away.")]
public static SessionToken Begin (Toplevel toplevel) => ApplicationImpl.Instance.Begin (toplevel);

View File

@@ -254,6 +254,17 @@ public partial class ApplicationImpl
ClearScreenNextIteration = false;
// === 6. Reset input systems ===
// Dispose keyboard and mouse to unsubscribe from events
if (_keyboard is IDisposable keyboardDisposable)
{
keyboardDisposable.Dispose ();
}
if (_mouse is IDisposable mouseDisposable)
{
mouseDisposable.Dispose ();
}
// Mouse and Keyboard will be lazy-initialized on next access
_mouse = null;
_keyboard = null;
@@ -286,10 +297,33 @@ public partial class ApplicationImpl
// gui.cs does no longer process any callbacks. See #1084 for more details:
// (https://github.com/gui-cs/Terminal.Gui/issues/1084).
SynchronizationContext.SetSynchronizationContext (null);
// === 12. Unsubscribe from Application static property change events ===
UnsubscribeApplicationEvents ();
}
/// <summary>
/// Raises the <see cref="InitializedChanged"/> event.
/// </summary>
internal void RaiseInitializedChanged (object sender, EventArgs<bool> e) { InitializedChanged?.Invoke (sender, e); }
// Event handlers for Application static property changes
private void OnForce16ColorsChanged (object? sender, ValueChangedEventArgs<bool> e)
{
Force16Colors = e.NewValue;
}
private void OnForceDriverChanged (object? sender, ValueChangedEventArgs<string> e)
{
ForceDriver = e.NewValue;
}
/// <summary>
/// Unsubscribes from Application static property change events.
/// </summary>
private void UnsubscribeApplicationEvents ()
{
Application.Force16ColorsChanged -= OnForce16ColorsChanged;
Application.ForceDriverChanged -= OnForceDriverChanged;
}
}

View File

@@ -9,15 +9,27 @@ namespace Terminal.Gui.App;
public partial class ApplicationImpl : IApplication
{
/// <summary>
/// INTERNAL: Creates a new instance of the Application backend.
/// INTERNAL: Creates a new instance of the Application backend and subscribes to Application configuration property events.
/// </summary>
internal ApplicationImpl () { }
internal ApplicationImpl ()
{
// Initialize from Application static properties (ConfigurationManager may have set these before we were created)
Force16Colors = Application.Force16Colors;
ForceDriver = Application.ForceDriver;
// Subscribe to Application static property change events
Application.Force16ColorsChanged += OnForce16ColorsChanged;
Application.ForceDriverChanged += OnForceDriverChanged;
}
/// <summary>
/// INTERNAL: Creates a new instance of the Application backend.
/// </summary>
/// <param name="componentFactory"></param>
internal ApplicationImpl (IComponentFactory componentFactory) { _componentFactory = componentFactory; }
internal ApplicationImpl (IComponentFactory componentFactory) : this ()
{
_componentFactory = componentFactory;
}
#region Singleton
@@ -108,6 +120,18 @@ public partial class ApplicationImpl : IApplication
// If an instance exists, reset it
_instance?.ResetState (ignoreDisposed);
// Reset Application static properties to their defaults
// This ensures tests start with clean state
Application.ForceDriver = string.Empty;
Application.Force16Colors = false;
Application.IsMouseDisabled = false;
Application.QuitKey = Key.Esc;
Application.ArrangeKey = Key.F5.WithCtrl;
Application.NextTabGroupKey = Key.F6;
Application.NextTabKey = Key.Tab;
Application.PrevTabGroupKey = Key.F6.WithShift;
Application.PrevTabKey = Key.Tab.WithShift;
// Always reset the model tracking to allow tests to use either model after reset
ResetModelUsageTracking ();
}

View File

@@ -10,7 +10,7 @@ namespace Terminal.Gui.App;
/// See <see cref="IKeyboard"/> for usage details.
/// </para>
/// </summary>
internal class KeyboardImpl : IKeyboard
internal class KeyboardImpl : IKeyboard, IDisposable
{
private Key _quitKey = Key.Esc; // Resources/config.json overrides
private Key _arrangeKey = Key.F5.WithCtrl; // Resources/config.json overrides
@@ -103,10 +103,26 @@ internal class KeyboardImpl : IKeyboard
public event EventHandler<Key>? KeyUp;
/// <summary>
/// Initializes keyboard bindings.
/// Initializes keyboard bindings and subscribes to Application configuration property events.
/// </summary>
public KeyboardImpl ()
{
// Initialize from Application static properties (ConfigurationManager may have set these before we were created)
_quitKey = Application.QuitKey;
_arrangeKey = Application.ArrangeKey;
_nextTabGroupKey = Application.NextTabGroupKey;
_nextTabKey = Application.NextTabKey;
_prevTabGroupKey = Application.PrevTabGroupKey;
_prevTabKey = Application.PrevTabKey;
// Subscribe to Application static property change events
Application.QuitKeyChanged += OnQuitKeyChanged;
Application.ArrangeKeyChanged += OnArrangeKeyChanged;
Application.NextTabGroupKeyChanged += OnNextTabGroupKeyChanged;
Application.NextTabKeyChanged += OnNextTabKeyChanged;
Application.PrevTabGroupKeyChanged += OnPrevTabGroupKeyChanged;
Application.PrevTabKeyChanged += OnPrevTabKeyChanged;
AddKeyBindings ();
}
@@ -378,4 +394,47 @@ internal class KeyboardImpl : IKeyboard
KeyBindings.Add (Key.Z.WithCtrl, Command.Suspend);
}
}
// Event handlers for Application static property changes
private void OnQuitKeyChanged (object? sender, ValueChangedEventArgs<Key> e)
{
QuitKey = e.NewValue;
}
private void OnArrangeKeyChanged (object? sender, ValueChangedEventArgs<Key> e)
{
ArrangeKey = e.NewValue;
}
private void OnNextTabGroupKeyChanged (object? sender, ValueChangedEventArgs<Key> e)
{
NextTabGroupKey = e.NewValue;
}
private void OnNextTabKeyChanged (object? sender, ValueChangedEventArgs<Key> e)
{
NextTabKey = e.NewValue;
}
private void OnPrevTabGroupKeyChanged (object? sender, ValueChangedEventArgs<Key> e)
{
PrevTabGroupKey = e.NewValue;
}
private void OnPrevTabKeyChanged (object? sender, ValueChangedEventArgs<Key> e)
{
PrevTabKey = e.NewValue;
}
/// <inheritdoc/>
public void Dispose ()
{
// Unsubscribe from Application static property change events
Application.QuitKeyChanged -= OnQuitKeyChanged;
Application.ArrangeKeyChanged -= OnArrangeKeyChanged;
Application.NextTabGroupKeyChanged -= OnNextTabGroupKeyChanged;
Application.NextTabKeyChanged -= OnNextTabKeyChanged;
Application.PrevTabGroupKeyChanged -= OnPrevTabGroupKeyChanged;
Application.PrevTabKeyChanged -= OnPrevTabKeyChanged;
}
}

View File

@@ -9,12 +9,19 @@ namespace Terminal.Gui.App;
/// enabling better testability and parallel test execution.
/// </para>
/// </summary>
internal class MouseImpl : IMouse
internal class MouseImpl : IMouse, IDisposable
{
/// <summary>
/// Initializes a new instance of the <see cref="MouseImpl"/> class.
/// Initializes a new instance of the <see cref="MouseImpl"/> class and subscribes to Application configuration property events.
/// </summary>
public MouseImpl () { }
public MouseImpl ()
{
// Initialize from Application static property (ConfigurationManager may have set this before we were created)
IsMouseDisabled = Application.IsMouseDisabled;
// Subscribe to Application static property change events
Application.IsMouseDisabledChanged += OnIsMouseDisabledChanged;
}
/// <inheritdoc/>
public IApplication? App { get; set; }
@@ -391,4 +398,17 @@ internal class MouseImpl : IMouse
return false;
}
// Event handler for Application static property changes
private void OnIsMouseDisabledChanged (object? sender, ValueChangedEventArgs<bool> e)
{
IsMouseDisabled = e.NewValue;
}
/// <inheritdoc/>
public void Dispose ()
{
// Unsubscribe from Application static property change events
Application.IsMouseDisabledChanged -= OnIsMouseDisabledChanged;
}
}