namespace Terminal.Gui.ViewBase; /// /// 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 }