Files
Terminal.Gui/Terminal.Gui/ViewBase/Runnable/Runnable.cs
Tig c9868e9901 Fixes #4434 - InvokeLeakTest (#4435)
* pre-alpha -> alpha

* don't build docs for v2_release

* Pulled from v2_release

* Refactor migration guide for Terminal.Gui v2

Restructured and expanded the migration guide to provide a comprehensive resource for transitioning from Terminal.Gui v1 to v2. Key updates include:

- Added a Table of Contents for easier navigation.
- Summarized major architectural changes in v2, including the instance-based application model, IRunnable architecture, and 24-bit TrueColor support.
- Updated examples to reflect new patterns, such as initializers replacing constructors and explicit disposal using `IDisposable`.
- Documented changes to the layout system, including the removal of `Absolute`/`Computed` styles and the introduction of `Viewport`.
- Standardized event patterns to use `object sender, EventArgs args`.
- Detailed updates to the Keyboard, Mouse, and Navigation APIs, including configurable key bindings and viewport-relative mouse coordinates.
- Replaced legacy components like `ScrollView` and `ContextMenu` with built-in scrolling and `PopoverMenu`.
- Clarified disposal rules and introduced best practices for resource management.
- Provided a complete migration example and a summary of breaking changes.

This update aims to simplify the migration process by addressing breaking changes, introducing new features, and aligning with modern .NET conventions.

* Updated runnable

* Refactor ApplicationStressTests for modularity and robustness

Refactored `ApplicationStressTests` to use `IApplication`
instances instead of static methods, enabling better
testability and alignment with dependency injection.

Enhanced timeout handling in `RunTest` with elapsed time
tracking and debugger-aware polling intervals. Improved
error handling by introducing exceptions for timeouts and
ensuring proper resource cleanup with `application.Dispose`.

Refactored `Launch` and `InvokeLeakTest` methods for
clarity and consistency. Removed redundant code and
improved overall readability and maintainability.
2025-12-03 10:27:42 -07:00

221 lines
7.2 KiB
C#

namespace Terminal.Gui.ViewBase;
/// <summary>
/// Base implementation of <see cref="IRunnable"/> for views that can be run as blocking sessions without returning a result.
/// </summary>
/// <remarks>
/// <para>
/// Views that don't need to return a result can derive from this class instead of <see cref="Runnable{TResult}"/>.
/// </para>
/// <para>
/// This class provides default implementations of the <see cref="IRunnable"/> interface
/// following the Terminal.Gui Cancellable Work Pattern (CWP).
/// </para>
/// <para>
/// For views that need to return a result, use <see cref="Runnable{TResult}"/> instead.
/// </para>
/// </remarks>
public class Runnable : View, IRunnable
{
// Cached state - eliminates race conditions from stack queries
private bool _isRunning;
private bool _isModal;
/// <summary>
/// Constructs a new instance of the <see cref="Runnable"/> class.
/// </summary>
public Runnable ()
{
CanFocus = true;
TabStop = TabBehavior.TabGroup;
Arrangement = ViewArrangement.Overlapped;
Width = Dim.Fill ();
Height = Dim.Fill ();
SchemeName = SchemeManager.SchemesToSchemeName (Schemes.Runnable);
}
/// <inheritdoc/>
public object? Result { get; set; }
#region IRunnable Implementation - IsRunning (from base interface)
/// <inheritdoc />
public void SetApp (IApplication app)
{
App = app;
}
/// <inheritdoc/>
public bool IsRunning => _isRunning;
/// <inheritdoc/>
public void SetIsRunning (bool value) { _isRunning = value; }
/// <inheritdoc />
public virtual void RequestStop ()
{
// Use the IRunnable-specific RequestStop if the App supports it
App?.RequestStop (this);
}
/// <inheritdoc/>
public bool RaiseIsRunningChanging (bool oldIsRunning, bool newIsRunning)
{
// Clear previous result when starting (for non-generic Runnable)
// Derived Runnable<TResult> will clear its typed Result in OnIsRunningChanging override
if (newIsRunning)
{
Result = null;
}
// CWP Phase 1: Virtual method (pre-notification)
if (OnIsRunningChanging (oldIsRunning, newIsRunning))
{
return true; // Canceled
}
// CWP Phase 2: Event notification
bool newValue = newIsRunning;
CancelEventArgs<bool> args = new (in oldIsRunning, ref newValue);
IsRunningChanging?.Invoke (this, args);
return args.Cancel;
}
/// <inheritdoc/>
public event EventHandler<CancelEventArgs<bool>>? IsRunningChanging;
/// <inheritdoc/>
public void RaiseIsRunningChangedEvent (bool newIsRunning)
{
// Initialize if needed when starting
if (newIsRunning && !IsInitialized)
{
BeginInit ();
EndInit ();
// Initialized event is raised by View.EndInit()
}
// CWP Phase 3: Post-notification (work already done by Application.Begin/End)
OnIsRunningChanged (newIsRunning);
EventArgs<bool> args = new (newIsRunning);
IsRunningChanged?.Invoke (this, args);
}
/// <inheritdoc/>
public event EventHandler<EventArgs<bool>>? IsRunningChanged;
/// <summary>
/// Called before <see cref="IsRunningChanging"/> event. Override to cancel state change or perform cleanup.
/// </summary>
/// <param name="oldIsRunning">The current value of <see cref="IsRunning"/>.</param>
/// <param name="newIsRunning">The new value of <see cref="IsRunning"/> (true = starting, false = stopping).</param>
/// <returns><see langword="true"/> to cancel; <see langword="false"/> to proceed.</returns>
/// <remarks>
/// <para>
/// Default implementation returns <see langword="false"/> (allow change).
/// </para>
/// <para>
/// <b>IMPORTANT</b>: When <paramref name="newIsRunning"/> is <see langword="false"/> (stopping), this is the ideal
/// place to perform cleanup or validation before the runnable is removed from the stack.
/// </para>
/// <example>
/// <code>
/// protected override bool OnIsRunningChanging (bool oldIsRunning, bool newIsRunning)
/// {
/// if (!newIsRunning) // Stopping
/// {
/// // Check if user wants to save first
/// if (HasUnsavedChanges ())
/// {
/// int result = MessageBox.Query (App, "Save?", "Save changes?", "Yes", "No", "Cancel");
/// if (result == 2) return true; // Cancel stopping
/// if (result == 0) Save ();
/// }
/// }
///
/// return base.OnIsRunningChanging (oldIsRunning, newIsRunning);
/// }
/// </code>
/// </example>
/// </remarks>
protected virtual bool OnIsRunningChanging (bool oldIsRunning, bool newIsRunning) => false;
/// <summary>
/// Called after <see cref="IsRunning"/> has changed. Override for post-state-change logic.
/// </summary>
/// <param name="newIsRunning">The new value of <see cref="IsRunning"/> (true = started, false = stopped).</param>
/// <remarks>
/// Default implementation does nothing. Overrides should call base to ensure extensibility.
/// </remarks>
protected virtual void OnIsRunningChanged (bool newIsRunning)
{
// Default: no-op
}
#endregion
#region IRunnable Implementation - IsModal (from base interface)
/// <inheritdoc/>
public bool IsModal => _isModal;
/// <inheritdoc/>
public void SetIsModal (bool value) { _isModal = value; }
/// <inheritdoc />
public bool StopRequested { get; set; }
/// <inheritdoc/>
public void RaiseIsModalChangedEvent (bool newIsModal)
{
if (newIsModal)
{
// Set focus to self if becoming modal
if (HasFocus is false)
{
SetFocus ();
}
// Position cursor and update driver
if (App?.PositionCursor () == true)
{
App?.Driver?.UpdateCursor ();
}
}
// CWP Phase 3: Post-notification (work already done by Application)
OnIsModalChanged (newIsModal);
EventArgs<bool> args = new (newIsModal);
IsModalChanged?.Invoke (this, args);
// Layout may need to change when modal state changes
SetNeedsLayout ();
SetNeedsDraw ();
}
/// <inheritdoc/>
public event EventHandler<EventArgs<bool>>? IsModalChanged;
/// <summary>
/// Called after <see cref="IsModal"/> has changed. Override for post-activation logic.
/// </summary>
/// <param name="newIsModal">The new value of <see cref="IsModal"/> (true = became modal, false = no longer modal).</param>
/// <remarks>
/// <para>
/// Default implementation does nothing. Overrides should call base to ensure extensibility.
/// </para>
/// <para>
/// Common uses: setting focus when becoming modal, updating UI state.
/// </para>
/// </remarks>
protected virtual void OnIsModalChanged (bool newIsModal)
{
// Default: no-op
}
#endregion
}