浏览代码

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

Tig 3 周之前
父节点
当前提交
4b3cdf71d3

+ 21 - 15
Terminal.Gui/App/Application.Driver.cs

@@ -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;

+ 12 - 2
Terminal.Gui/App/Application.Mouse.cs

@@ -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>

+ 50 - 9
Terminal.Gui/App/Application.Navigation.cs

@@ -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;
 }

+ 24 - 4
Terminal.Gui/App/Application.Run.cs

@@ -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);

+ 34 - 0
Terminal.Gui/App/ApplicationImpl.Lifecycle.cs

@@ -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;
+    }
 }

+ 27 - 3
Terminal.Gui/App/ApplicationImpl.cs

@@ -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 ();
     }

+ 61 - 2
Terminal.Gui/App/Keyboard/KeyboardImpl.cs

@@ -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;
+    }
 }

+ 23 - 3
Terminal.Gui/App/Mouse/MouseImpl.cs

@@ -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;
+    }
 }