namespace Terminal.Gui.ViewBase;
///
/// Base implementation of for views that can be run as blocking sessions.
///
/// The type of result data returned when the session completes.
///
///
/// Views can derive from this class or implement directly.
///
///
/// This class provides default implementations of the interface
/// following the Terminal.Gui Cancellable Work Pattern (CWP).
///
///
public class Runnable : View, IRunnable
{
///
public TResult? Result { get; set; }
#region IRunnable Implementation - IsRunning (from base interface)
///
public bool IsRunning => App?.RunnableSessionStack?.Any (token => token.Runnable == this) ?? false;
///
public bool RaiseIsRunningChanging (bool oldIsRunning, bool newIsRunning)
{
// Clear previous result when starting
if (newIsRunning)
{
Result = default (TResult);
}
// CWP Phase 1: Virtual method (pre-notification)
if (OnIsRunningChanging (oldIsRunning, newIsRunning))
{
return true; // Canceled
}
// CWP Phase 2: Event notification
bool newValue = newIsRunning;
CancelEventArgs args = new (in oldIsRunning, ref newValue);
IsRunningChanging?.Invoke (this, args);
return args.Cancel;
}
///
public event EventHandler>? IsRunningChanging;
///
public void RaiseIsRunningChangedEvent (bool newIsRunning)
{
// CWP Phase 3: Post-notification (work already done by Application.Begin/End)
OnIsRunningChanged (newIsRunning);
EventArgs args = new (newIsRunning);
IsRunningChanged?.Invoke (this, args);
}
///
public event EventHandler>? IsRunningChanged;
///
/// Called before event. Override to cancel state change or extract
/// .
///
/// The current value of .
/// The new value of (true = starting, false = stopping).
/// to cancel; to proceed.
///
///
/// Default implementation returns (allow change).
///
///
/// IMPORTANT: When is (stopping), this is the ideal
/// place
/// to extract from views before the runnable is removed from the stack.
/// At this point, all views are still alive and accessible, and subscribers can inspect the result
/// and optionally cancel the stop.
///
///
///
/// protected override bool OnIsRunningChanging (bool oldIsRunning, bool newIsRunning)
/// {
/// if (!newIsRunning) // Stopping
/// {
/// // Extract result before removal from stack
/// Result = _textField.Text;
///
/// // Or 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);
/// }
///
///
///
protected virtual bool OnIsRunningChanging (bool oldIsRunning, bool newIsRunning) => false;
///
/// Called after has changed. Override for post-state-change logic.
///
/// The new value of (true = started, false = stopped).
///
/// Default implementation does nothing. Overrides should call base to ensure extensibility.
///
protected virtual void OnIsRunningChanged (bool newIsRunning)
{
// Default: no-op
}
#endregion
#region IRunnable Implementation - IsModal (from base interface)
///
public bool IsModal
{
get
{
if (App is null)
{
return false;
}
// Check if this runnable is at the top of the RunnableSessionStack
// The top of the stack is the modal runnable
if (App.RunnableSessionStack is { } && App.RunnableSessionStack.TryPeek (out RunnableSessionToken? topToken))
{
return topToken?.Runnable == this;
}
// Fallback: Check if this is the TopRunnable (for Toplevel compatibility)
// In Phase 1, TopRunnable is still Toplevel?, so we need to check both cases
if (this is Toplevel tl && App.TopRunnable == tl)
{
return true;
}
return false;
}
}
///
public bool RaiseIsModalChanging (bool oldIsModal, bool newIsModal)
{
// CWP Phase 1: Virtual method (pre-notification)
if (OnIsModalChanging (oldIsModal, newIsModal))
{
return true; // Canceled
}
// CWP Phase 2: Event notification
bool newValue = newIsModal;
CancelEventArgs args = new (in oldIsModal, ref newValue);
IsModalChanging?.Invoke (this, args);
return args.Cancel;
}
///
public event EventHandler>? IsModalChanging;
///
public void RaiseIsModalChangedEvent (bool newIsModal)
{
// CWP Phase 3: Post-notification (work already done by Application)
OnIsModalChanged (newIsModal);
EventArgs args = new (newIsModal);
IsModalChanged?.Invoke (this, args);
}
///
public event EventHandler>? IsModalChanged;
///
/// Called before event. Override to cancel activation/deactivation.
///
/// The current value of .
/// The new value of (true = becoming modal/top, false = no longer modal).
/// to cancel; to proceed.
///
/// Default implementation returns (allow change).
///
protected virtual bool OnIsModalChanging (bool oldIsModal, bool newIsModal) => false;
///
/// Called after has changed. Override for post-activation logic.
///
/// The new value of (true = became modal, false = no longer modal).
///
///
/// Default implementation does nothing. Overrides should call base to ensure extensibility.
///
///
/// Common uses: setting focus when becoming modal, updating UI state.
///
///
protected virtual void OnIsModalChanged (bool newIsModal)
{
// Default: no-op
}
#endregion
///
/// Requests that this runnable session stop.
///
public virtual void RequestStop ()
{
// Use the IRunnable-specific RequestStop if the App supports it
App?.RequestStop (this);
}
}