# CWP Pattern Analysis - Executive Summary & Recommendations ## Quick Facts - **Total CWP Implementations**: 33+ event pairs - **Virtual Method Overrides**: 100+ across codebase - **Current Order**: Virtual method → Event (inheritance priority) - **Proposed Order**: Event → Virtual method (composition priority) - **Breaking Change Magnitude**: VERY HIGH - **Estimated Effort**: 4-6 weeks for complete refactor - **Risk Level**: HIGH ## The Core Issue (#3714) External code cannot prevent views like `Slider` from handling mouse events because: ```csharp // What users want but CANNOT do today: slider.MouseEvent += (s, e) => { if (someCondition) e.Handled = true; // TOO LATE! Slider.OnMouseEvent already handled it }; ``` The virtual method (`OnMouseEvent`) runs first and can mark the event as handled, preventing the event from firing. ## Four Solution Options ### Option 1: Reverse Order Globally ⚠️ HIGH RISK **Change**: Event first → Virtual method second (for all 33+ CWP patterns) **Pros**: - Solves #3714 completely - Consistent pattern everywhere - Enables external control (composition over inheritance) - Single comprehensive solution **Cons**: - MAJOR breaking change - 100+ view overrides need review/update - Changes fundamental framework philosophy - Breaks existing user code expectations - Requires extensive testing - Updates to helper classes, documentation, tests **Effort**: 4-6 weeks **Risk**: VERY HIGH **Recommendation**: ❌ NOT RECOMMENDED unless major version change ### Option 2: Add "Before" Events 🎯 RECOMMENDED **Change**: Add new pre-phase events (e.g., `BeforeMouseEvent`, `BeforeKeyDown`) ```csharp // New pattern for critical events protected bool RaiseMouseEvent(MouseEventArgs args) { // New: BeforeMouseEvent (external code gets first chance) BeforeMouseEvent?.Invoke(this, args); if (args.Handled) return true; // Existing: OnMouseEvent (virtual method) if (OnMouseEvent(args) || args.Handled) return true; // Existing: MouseEvent (external code gets second chance) MouseEvent?.Invoke(this, args); return args.Handled; } ``` **Pros**: - Non-breaking (additive change only) - Solves #3714 for mouse events - Can apply selectively to problematic events - Gradual migration path - Clear intent (Before/During/After phases) **Cons**: - More API surface - Two patterns coexist (inconsistency) - More complex for new developers - Partial solution only **Implementation Plan**: 1. **Phase 1** (1 week): Add `BeforeMouseEvent`, `BeforeMouseClick`, `BeforeMouseWheel` 2. **Phase 2** (1 week): Add `BeforeKeyDown` if needed 3. **Phase 3** (ongoing): Add others as needed, deprecate old pattern gradually **Effort**: 2-3 weeks initial, ongoing refinement **Risk**: LOW **Recommendation**: ✅ **RECOMMENDED** - Best balance of solving issue vs. risk ### Option 3: Add `MouseInputEnabled` Property 🔧 QUICK FIX **Change**: Add property to View to disable mouse handling ```csharp public class View { public bool MouseInputEnabled { get; set; } = true; public bool? NewMouseEvent(MouseEventArgs mouseEvent) { if (!MouseInputEnabled) return false; // Don't process // ... existing code } } // Usage slider.MouseInputEnabled = false; // Disable slider mouse handling ``` **Pros**: - Minimal change - Solves immediate #3714 issue - No breaking changes - Easy to implement - Easy to understand **Cons**: - Band-aid solution (doesn't address root cause) - Mouse-specific (doesn't help keyboard, etc.) - All-or-nothing (can't selectively disable) - Adds more properties to View **Effort**: 1 week **Risk**: VERY LOW **Recommendation**: ⚠️ Acceptable as short-term fix, not long-term solution ### Option 4: Reverse Order for Mouse Only 🎯 TARGETED FIX **Change**: Event first → Virtual method second, but ONLY for mouse events **Pros**: - Solves #3714 directly - Limited scope reduces risk - Mouse is the primary concern - Clearer than "Before" events **Cons**: - Inconsistent pattern (mouse different from keyboard) - Still breaking for mouse event overrides - Confusing to have different orders - Doesn't help future similar issues **Effort**: 2 weeks **Risk**: MEDIUM **Recommendation**: ⚠️ Better than Option 1, but Option 2 is cleaner ## Detailed Recommendation: Option 2 (Before Events) ### Implementation Specification #### For Mouse Events (High Priority) ```csharp public partial class View // Mouse APIs { /// /// Event raised BEFORE OnMouseEvent is called, allowing external code /// to cancel mouse event processing before the view handles it. /// public event EventHandler? BeforeMouseEvent; /// /// Event raised BEFORE OnMouseClick is called, allowing external code /// to cancel mouse click processing before the view handles it. /// public event EventHandler? BeforeMouseClick; public bool RaiseMouseEvent (MouseEventArgs mouseEvent) { // Phase 1: Before event (external pre-processing) BeforeMouseEvent?.Invoke(this, mouseEvent); if (mouseEvent.Handled) { return true; } // Phase 2: Virtual method (view processing) if (OnMouseEvent (mouseEvent) || mouseEvent.Handled) { return true; } // Phase 3: After event (external post-processing) MouseEvent?.Invoke (this, mouseEvent); return mouseEvent.Handled; } protected bool RaiseMouseClickEvent (MouseEventArgs args) { if (!Enabled) { return args.Handled = false; } // Phase 1: Before event BeforeMouseClick?.Invoke(this, args); if (args.Handled) { return args.Handled; } // Phase 2: Virtual method if (OnMouseClick (args) || args.Handled) { return args.Handled; } // Phase 3: After event MouseClick?.Invoke (this, args); if (args.Handled) { return true; } // Post-conditions args.Handled = InvokeCommandsBoundToMouse (args) == true; return args.Handled; } } ``` #### Usage Example (Solves #3714) ```csharp var slider = new Slider(); // NOW THIS WORKS - external code can prevent slider from handling mouse slider.BeforeMouseEvent += (sender, args) => { if (someCondition) { args.Handled = true; // Prevents Slider.OnMouseEvent from being called } }; // Or use lambda with specific logic slider.BeforeMouseEvent += (sender, args) => { if (args.Flags.HasFlag(MouseFlags.Button1Clicked) && IsModalDialogOpen()) { args.Handled = true; // Block slider interaction when dialog is open } }; ``` #### For Keyboard Events (Medium Priority) ```csharp public partial class View // Keyboard APIs { /// /// Event raised BEFORE OnKeyDown is called, allowing external code /// to cancel key event processing before the view handles it. /// public event EventHandler? BeforeKeyDown; public bool NewKeyDownEvent (Key k) { // Phase 1: Before event BeforeKeyDown?.Invoke(this, k); if (k.Handled) { return true; } // Phase 2: Virtual method if (OnKeyDown (k) || k.Handled) { return true; } // Phase 3: After event (existing KeyDown) KeyDown?.Invoke (this, k); return k.Handled; } } ``` #### For Other Events (Low Priority - As Needed) Add `Before*` events for other patterns only if/when users request them. ### Migration Path #### v2.x (Current) - Keep existing pattern - Add `Before*` events - Document both patterns - Mark as "preferred" for external control #### v3.0 (Future) - Consider deprecating old pattern - Maybe reverse order globally - Or standardize on Before/After pattern ### Documentation Updates Add to `docfx/docs/events.md`: ````markdown #### Three-Phase CWP Pattern For critical events like mouse and keyboard input, a three-phase pattern is available: ```csharp public class MyView : View { public bool ProcessMouseEvent(MouseEventArgs args) { // Phase 1: BeforeMouseEvent - External pre-processing BeforeMouseEvent?.Invoke(this, args); if (args.Handled) return true; // Phase 2: OnMouseEvent - View processing (virtual method) if (OnMouseEvent(args) || args.Handled) return true; // Phase 3: MouseEvent - External post-processing MouseEvent?.Invoke(this, args); return args.Handled; } } ``` **When to use each phase**: - **BeforeXxx**: Use when you need to prevent the view from processing the event - Example: Disabling a slider when a modal dialog is open - Example: Implementing global keyboard shortcuts - **OnXxx**: Override in derived classes to customize view behavior - Example: Custom mouse handling in a custom view - Example: Custom key handling in a specialized control - **Xxx**: Use for external observation and additional processing - Example: Logging mouse activity - Example: Implementing additional behavior without inheritance ```` ### Testing Strategy 1. **Unit Tests**: Add tests for new Before* events 2. **Integration Tests**: Test interaction between phases 3. **Scenario Tests**: Test #3714 scenario specifically 4. **Regression Tests**: Ensure existing code still works 5. **Documentation Tests**: Verify examples work ### Benefits of This Approach 1. **Solves #3714**: Users can now prevent Slider from handling mouse 2. **Non-Breaking**: All existing code continues to work 3. **Clear Intent**: "Before" explicitly means "external pre-processing" 4. **Selective Application**: Add to problematic events, not all 33 5. **Future-Proof**: Creates migration path to v3.0 6. **Minimal Risk**: Additive only, no changes to existing behavior 7. **Gradual Adoption**: Users can adopt at their own pace ### Risks & Mitigations **Risk**: Three phases more complex than two **Mitigation**: Good documentation, clear examples, gradual rollout **Risk**: People still use wrong phase **Mitigation**: Intellisense XML docs explain when to use each **Risk**: Two patterns coexist **Mitigation**: Document clearly, provide migration examples ## Alternative Considerations ### Could We Just Fix Slider? No - this is a systemic issue. Any view that overrides `OnMouseEvent` has the same problem. Fixing just Slider doesn't help: - ListView - TextView - TableView - TreeView - ScrollBar - And 20+ others ### Could We Change Slider To Not Override OnMouseEvent? Yes, but: - Still doesn't solve systemic issue - Other views have same problem - Not a general solution - Slider's implementation is reasonable ### Could We Add Multiple Overload Virtual Methods? E.g., `OnMouseEventBefore` and `OnMouseEventAfter`? No - virtual methods can't solve this because: - External code can't override virtual methods - Virtual methods are for inheritance, not composition - Issue is specifically about external code priority ## Implementation Checklist (Option 2) - [ ] **Week 1: Mouse Events** - [ ] Add `BeforeMouseEvent` to View - [ ] Add `BeforeMouseClick` to View - [ ] Add `BeforeMouseWheel` to View - [ ] Update `RaiseMouseEvent` to invoke BeforeMouseEvent - [ ] Update `RaiseMouseClickEvent` to invoke BeforeMouseClick - [ ] Update `RaiseMouseWheelEvent` to invoke BeforeMouseWheel - [ ] Add unit tests for new events - [ ] Test Slider scenario specifically - [ ] **Week 2: Documentation & Examples** - [ ] Update `events.md` with three-phase pattern - [ ] Update `cancellable-work-pattern.md` with new pattern - [ ] Add examples showing how to use BeforeMouseEvent - [ ] Add example solving #3714 specifically - [ ] Update API docs (XML comments) - [ ] **Week 3: Keyboard Events (If Needed)** - [ ] Assess if keyboard has similar issues - [ ] Add `BeforeKeyDown` if needed - [ ] Add unit tests - [ ] Update documentation - [ ] **Week 4: Other Events (As Needed)** - [ ] Assess other event patterns - [ ] Add Before* events as needed - [ ] Update documentation - [ ] **Week 5-6: Testing & Refinement** - [ ] Integration testing - [ ] Scenario testing - [ ] User testing - [ ] Bug fixes - [ ] Documentation refinement ## Conclusion **Recommended Solution**: **Option 2 - Add Before Events** This solution: ✅ Solves issue #3714 ✅ Non-breaking change ✅ Clear and understandable ✅ Minimal risk ✅ Provides migration path ✅ Can be applied selectively Estimated effort: **2-3 weeks** Risk level: **LOW** The key insight is that we don't need to change the existing pattern - we can ADD to it. This preserves all existing behavior while enabling the new use cases that #3714 requires. ## References - Issue #3714: Mouse event interception problem - Issue #3417: Related mouse event handling issue - `docfx/docs/events.md`: Current CWP documentation - `docfx/docs/cancellable-work-pattern.md`: CWP philosophy - `Terminal.Gui/ViewBase/View.Mouse.cs`: Mouse event implementation - `Terminal.Gui/ViewBase/View.Keyboard.cs`: Keyboard event implementation - `Terminal.Gui/App/CWP/CWPWorkflowHelper.cs`: CWP helper class