namespace Terminal.Gui.Views;
///
/// Base implementation of for views that can be run as blocking sessions without returning a result.
///
///
///
/// Views that don't need to return a result can derive from this class instead of .
///
///
/// This class provides default implementations of the interface
/// following the Terminal.Gui Cancellable Work Pattern (CWP).
///
///
/// For views that need to return a result, use instead.
///
///
public class Runnable : View, IRunnable
{
// Cached state - eliminates race conditions from stack queries
private bool _isRunning;
private bool _isModal;
///
/// Constructs a new instance of the class.
///
public Runnable ()
{
CanFocus = true;
TabStop = TabBehavior.TabGroup;
Arrangement = ViewArrangement.Overlapped;
Width = Dim.Fill ();
Height = Dim.Fill ();
SchemeName = SchemeManager.SchemesToSchemeName (Schemes.Runnable);
}
///
public object? Result { get; set; }
#region IRunnable Implementation - IsRunning (from base interface)
///
public void SetApp (IApplication app)
{
App = app;
}
///
public bool IsRunning => _isRunning;
///
public void SetIsRunning (bool value) { _isRunning = value; }
///
public virtual void RequestStop ()
{
// Use the IRunnable-specific RequestStop if the App supports it
App?.RequestStop (this);
}
///
public bool RaiseIsRunningChanging (bool oldIsRunning, bool newIsRunning)
{
// Clear previous result when starting (for non-generic Runnable)
// Derived Runnable 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 args = new (in oldIsRunning, ref newValue);
IsRunningChanging?.Invoke (this, args);
return args.Cancel;
}
///
public event EventHandler>? IsRunningChanging;
///
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 args = new (newIsRunning);
IsRunningChanged?.Invoke (this, args);
}
///
public event EventHandler>? IsRunningChanged;
///
/// Called before event. Override to cancel state change or perform cleanup.
///
/// 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 perform cleanup or validation before the runnable is removed from the stack.
///
///
///
/// 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);
/// }
///
///
///
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 => _isModal;
///
public void SetIsModal (bool value) { _isModal = value; }
///
public bool StopRequested { get; set; }
///
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 args = new (newIsModal);
IsModalChanged?.Invoke (this, args);
// Layout may need to change when modal state changes
SetNeedsLayout ();
SetNeedsDraw ();
}
///
public event EventHandler>? IsModalChanged;
///
/// 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
}