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