Fixes #4057 - MASSIVE! Fully implements ColorScheme->Scheme + VisualRole + Colors.->SchemeManager. (#4062)

* touching publish.yml

* ColorScheme->Scheme

* ColorScheme->Scheme 2

* Prototype of GetAttributeForRole

* Badly broke CM

* Further Badly broke CM

* Refactored CM big-time. View still broken

* All unit test pass again. Tons added. CM is still WIP, but Schemes is not mostly refactored and working.

* Actually:
All unit test pass again.
Tons added.
CM is still WIP, but Schemes is not mostly refactored and working.

* Bug fixes.
DeepMemberWiseClone cleanup

* Further cleanup of Scope<T>, ConfigProperty, etc.

* Made ConfigManager thread safe.

* WIP: Broken

* WIP: new deep clone impl

* WIP: new deep clone impl is done. Now fixing CM

* WIP:
- config.md
- Working on AOT clean up
- Core CM is broken; but known.

* WIP

* Merged.
Removed CM from Application.Init

* WIP

* More WIP; Less broke

* All CM unit tests pass... Not sure if it actually works though

* All unit tests pass... Themes are broken though in UI Cat

* CM Ready for review?

* Fixed failures due to TextStyles PR

* Working on Scheme/Attribute

* Working on Scheme/Attribute 2

* Working on Scheme/Attribute 3

* Working on Scheme/Attribute 4

* Working on Scheme/Attribute 5

* Working on Scheme/Attribute 6

* Added test to show how awful memory usage is

* Improved schema. Updated config.json

* Nade Scope<T> concurrentdictionary and added test to prove

* Made Themes ConcrurrentDictionary. Added bunches of tests

* Code cleanup

* Code cleanup 2

* Code cleanup 3

* Tweaking Scheme

* ClearJsonErrors

* ClearJsonErrors2

* Updated Attribute API

* It all (mostly) works!

* Skip odd unit test

* Messed with Themes

* Theme tweaks

* Code reorg. New .md stuff

* Fixed Enabled. Added mock driver

* Fixed a bunch of View.Enabled related issues

* Scheme -> Get/SetScheme()

* Cleanup

* Cleanup2

* Broke something

* Fixed everything

* Made CM.Enable better

* Text Style Scenario

* Added comments

* Fixed UI Catalog Theme Changing

* Fixed more dynamic CM update stuff

* Warning cleanup

* New Default Theme

* fixed unit test

* Refactoring Scheme and Attribute to fix inheritance

* more unit tests

* ConfigProperty is not updating schemes correctly

* All unit tests pass.
Code cleanup

* All unit tests pass.
Code cleanup2

* Fixed unit tests

* Upgraded TextField and TextView

* Fixed TextView !Enabled bug

* More updates to TextView. More unit tests for SchemeManager

* Upgraded CharMap

* API docs

* Fixe HexView API

* upgrade HexView

* Fixed shortcut KeyView

* Fixed more bugs. Added new themes

* updated themes

* upgraded Border

* Fixed themes memory usage...mostly

* Fixed themes memory usage...mostly2

* Fixed themes memory usage...2

* Fixed themes memory usage...3

* Added new colors

* Fixed GetHardCodedConfig bug

* Added Themes Scenario - WIP

* Added Themes Scenario

* Tweaked Themes Scenario

* Code cleanup

* Fixed json schmea

* updated deepdives

* updated deepdives

* Tweaked Themes Scenario

* Made Schemes a concurrent dict

* Test cleanup

* Thread safe ConfigProperty tests

* trying to make things more thread safe

* more trying to make things more thread safe

* Fixing bugs in shadowview

* Fixing bugs in shadowview 2

* Refactored GetViewsUnderMouse to GetViewsUnderLocation etc...

* Fixed dupe unit tests?

* Added better description of layout and coordiantes to deep dive

* Added better description of layout and coordiantes to deep dive

* Modified tests that call v2.AddTimeout; they were returning true which means restart the timer!
This was causing mac/linux unit test failures.
I think

* Fixed auto scheme.
Broke TextView/TextField selection

* Realized Attribute.IsExplicitlySet is stupid; just use nullable

* Fixed Attribute. Simplified. MOre theme testing

* Updated themes again

* GetViewsUnderMouse to GetViewsUnderLocation broke TransparentMouse.

* Fixing mouseunder bugs

* rewriting...

* All working again.
Shadows are now slick as snot.
GetViewsUnderLocation is rewritten to actually work and be readable.
Tons more low-level unit tests.
Margin is now actually ViewportSettings.Transparent.

* Code cleanup

* Code cleanup

* Code cleanup of color apis

* Fixed Hover/Highlight

* Update Examples/UICatalog/Scenarios/AllViewsTester.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update Examples/UICatalog/Scenarios/CharacterMap/CharacterMap.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update Examples/UICatalog/Scenarios/Clipping.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Fixed race condition?

* reverted

* Simplified Attribute API by removing events from SetAttributeForRole

* Removed recursion from GetViewsAtLocation

* Removed unneeded code

* Code clean up.
Fixed Scheme bug.

* reverted temporary disable

* Adjusted scheme algo

* Upgraded TextValidateField

* Fixed TextValidate bugs

* Tweaks

* Frameview rounded border by default

* API doc cleanup

* Readme fix

* Addressed tznind feeback

* Fixed more unit test issues by protecting Application statics from being set if Application.Initialized is not true

* Fixed more unit test issues by protecting Application statics from being set if Application.Initialized is not true 2

* cleanup

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Tig
2025-05-29 13:55:54 -06:00
committed by GitHub
parent 9c54ee41e5
commit 7422385457
386 changed files with 23600 additions and 13662 deletions

View File

@@ -0,0 +1,243 @@
# Cancellable Work Pattern in Terminal.Gui
The *Cancellable Work Pattern* is a core design pattern in Terminal.Gui, used to structure workflows that can be executed in a default manner, modified by external code or subclasses, or cancelled entirely. This pattern is prevalent across various components of Terminal.Gui, including the `View` class for rendering, keyboard input, and command execution, as well as application-level input handling and property changes. Unlike traditional inheritance-based approaches that rely on overriding virtual methods (which often require subclasses to understand base class implementation details), the *Cancellable Work Pattern* prioritizes events for loose coupling, supplemented by optional virtual methods for flexibility.
This deep dive defines the *Cancellable Work Pattern*, outlines its components and goals, and illustrates its implementation through examples in `View.Draw`, `View.Keyboard`, `View.Command`, `Application.Keyboard`, and `OrientationHelper`.
## Definition
The *Cancellable Work Pattern* is a design pattern for executing a structured workflow with one or more phases, where each phase can:
- Proceed in a default manner.
- Be modified by external code or subclasses.
- Be cancelled to halt further processing.
The pattern uses events as the primary mechanism for notification and customization, supplemented by virtual methods for subclassing when needed. It is a specialization of the **Observer Pattern**, extended with structured workflows, explicit cancellation mechanisms, and context-aware notifications. It also incorporates elements of the **Template Method Pattern** (via virtual methods) and **Pipeline Pattern** (via sequential phases).
## Goals
The *Cancellable Work Pattern* is designed to achieve the following:
1. **Default Execution**: Provide a standard process that executes unless interrupted, ensuring predictable behavior out of the box.
2. **Modification**: Allow external code or subclasses to customize specific phases without requiring deep knowledge of the implementation.
3. **Cancellation**: Enable halting of a phase or the entire workflow, giving consumers control over the process.
4. **Decoupling**: Use events to reduce reliance on inheritance, minimizing the need for subclasses to understand base class details.
## Components
The *Cancellable Work Pattern* consists of the following components:
- **Workflow**: A sequence of phases, which may be multi-phase (e.g., rendering in `View.Draw`), linear (e.g., key processing in `View.Keyboard`), per-unit (e.g., command execution in `View.Command`), or event-driven (e.g., key handling in `Application.Keyboard`, property changes in `OrientationHelper`).
- **Notifications**: Events (e.g., `DrawingText`, `KeyDown`, `Activating`, `OrientationChanging`) and virtual methods (e.g., `OnDrawingText`, `OnKeyDown`, `OnActivating`, `OnOrientationChanging`) raised at each phase to notify observers.
- **Cancellation**: Mechanisms to halt a phase or workflow, such as setting `Cancel`/`Handled` properties in event arguments or returning `bool` from virtual methods.
- **Context**: Data passed to observers for informed decision-making, such as `DrawContext` (drawing), `Key` (keyboard), `ICommandContext` (commands), or `CancelEventArgs<Orientation>` (orientation).
- **Default Behavior**: A standard implementation for each phase, such as `DrawText` (drawing), `InvokeCommands` (keyboard and application-level), `RaiseActivating` (commands), or updating a property (`OrientationHelper`).
## Implementation in Terminal.Gui
The *Cancellable Work Pattern* is implemented consistently across several key areas of Terminal.Guis `v2_develop` branch. Below are five primary examples, each illustrating the pattern in a different domain: rendering, keyboard input at the view level, command execution, application-level keyboard input, and property changes.
### 1. View.Draw: Rendering Workflow
The `View.Draw` method orchestrates the rendering of a view, including its adornments (margin, border, padding), viewport, text, content, subviews, and line canvas. It is a multi-phase workflow where each phase can be customized or cancelled.
#### Example: `DoDrawText`
The `DoDrawText` method, responsible for drawing a views text, exemplifies the pattern:
```csharp
private void DoDrawText(DrawContext? context = null)
{
if (OnDrawingText(context)) // Virtual method for subclasses
{
return; // Cancel if true
}
if (OnDrawingText()) // Legacy virtual method
{
return; // Cancel if true
}
var dev = new DrawEventArgs(Viewport, Rectangle.Empty, context);
DrawingText?.Invoke(this, dev); // Notify observers
if (dev.Cancel) // Check for cancellation
{
return;
}
DrawText(context); // Default behavior
}
```
- **Workflow**: Single phase for text drawing within the broader `Draw` workflow.
- **Notifications**: `OnDrawingText` (virtual), `DrawingText` (event).
- **Cancellation**: `OnDrawingText` returning `true` or `dev.Cancel = true`.
- **Context**: `DrawContext` and `DrawEventArgs` provide rendering details.
- **Default Behavior**: `DrawText` renders the views text.
- **Use Case**: Allows customization of text rendering (e.g., custom formatting) or cancellation (e.g., skipping text for performance).
### 2. View.Keyboard: View-Level Keyboard Input
The `View.ProcessKeyDown` method processes keyboard input for a view, mapping keys to commands or handling them directly. It is a linear workflow with a single phase per key event.
#### Example: `ProcessKeyDown`
```csharp
public virtual bool ProcessKeyDown(Key key)
{
if (OnKeyDown(key)) // Virtual method
{
return true; // Cancel if true
}
KeyDown?.Invoke(this, key); // Notify observers
if (key.Handled) // Check for cancellation
{
return true;
}
bool handled = InvokeCommands(key, KeyBindingScope.HotKey | KeyBindingScope.Focused); // Default behavior
return handled;
}
```
- **Workflow**: Linear, processing one key event.
- **Notifications**: `OnKeyDown` (virtual), `KeyDown` (event).
- **Cancellation**: `OnKeyDown` returning `true` or `key.Handled = true`.
- **Context**: `Key` provides key details and bindings.
- **Default Behavior**: `InvokeCommands` maps keys to commands (e.g., `Command.Accept`).
- **Use Case**: Allows views to customize key handling (e.g., `TextField` capturing input) or cancel default command execution.
### 3. View.Command: Command Execution
The `View.Command` APIs execute commands like `Command.Activate` and `Command.Accept`, used for state changes (e.g., `CheckBox` toggling) and action confirmation (e.g., dialog submission). It is a per-unit workflow, with one phase per command.
#### Example: `RaiseActivating`
The `RaiseActivating` method handles `Command.Activate`:
```csharp
protected bool? RaiseActivating(ICommandContext? ctx)
{
CommandEventArgs args = new() { Context = ctx };
if (OnActivating(args) || args.Handled)
{
return true;
}
Activating?.Invoke(this, args);
return Activating is null ? null : args.Handled;
}
```
- **Workflow**: Single phase for `Command.Activate`.
- **Notifications**: `OnActivating` (virtual), `Activating` (event).
- **Cancellation**: `OnActivating` returning `true` or `args.Handled = true`.
- **Context**: `ICommandContext` provides `Command`, `Source`, and `Binding`.
- **Default Behavior**: `SetFocus` for `Command.Activate` (in `SetupCommands`).
- **Use Case**: Allows customization of state changes (e.g., `CheckBox` toggling) or cancellation (e.g., preventing focus in `MenuItemv2`).
#### Propagation Challenge
- `Command.Activate` is local, limiting hierarchical coordination (e.g., `MenuBarv2` popovers). A proposed `PropagatedCommands` property addresses this, as detailed in the appendix.
### 4. Application.Keyboard: Application-Level Keyboard Input
The `Application.OnKeyDown` method processes application-wide keyboard input, raising events for global key handling. It is an event-driven workflow, with a single phase per key event.
#### Example: `OnKeyDown`
```csharp
public static bool OnKeyDown(Key key)
{
if (KeyDown is null)
{
return false;
}
KeyDown?.Invoke(null, key); // Notify observers
return key.Handled; // Check for cancellation
}
```
- **Workflow**: Event-driven, processing one key event.
- **Notifications**: `KeyDown` (event, no virtual method).
- **Cancellation**: `key.Handled = true`.
- **Context**: `Key` provides key details.
- **Default Behavior**: None; relies on subscribers (e.g., `Top` view processing).
- **Use Case**: Allows global key bindings (e.g., `Ctrl+Q` to quit) or cancellation of default view handling.
### 5. OrientationHelper: Property Changes
The `OrientationHelper` class manages orientation changes (e.g., in `StackPanel`), raising events for property updates. It is an event-driven workflow, with a single phase per change.
#### Example: `Orientation` Setter
```csharp
public Orientation Orientation
{
get => _orientation;
set
{
if (_orientation == value)
{
return;
}
var oldOrientation = _orientation;
var args = new CancelEventArgs<Orientation>(_orientation, ref value);
if (OnOrientationChanging(args))
{
return; // Cancel if true
}
OrientationChanging?.Invoke(this, args);
if (args.Cancel)
{
return;
}
_orientation = value;
var changedArgs = new EventArgs<Orientation>(oldOrientation, _orientation);
OnOrientationChanged(changedArgs);
OrientationChanged?.Invoke(this, changedArgs);
}
}
```
- **Workflow**: Event-driven, processing one property change.
- **Notifications**: `OnOrientationChanging` (virtual), `OrientationChanging` (event), `OnOrientationChanged`, `OrientationChanged` (post-event).
- **Cancellation**: `OnOrientationChanging` returning `true` or `args.Cancel = true`.
- **Context**: `CancelEventArgs<Orientation>` provides old and new values.
- **Default Behavior**: Updates `_orientation` and notifies via `OrientationChanged`.
- **Use Case**: Allows customization of orientation changes (e.g., adjusting layout) or cancellation (e.g., rejecting invalid orientations).
## Proposed Enhancement: Command Propagation
The *Cancellable Work Pattern* in `View.Command` currently supports local `Command.Activate` and propagating `Command.Accept`. To address hierarchical coordination needs (e.g., `MenuBarv2` popovers, `Dialog` closing), a `PropagatedCommands` property is proposed (Issue #4050):
- **Change**: Add `IReadOnlyList<Command> PropagatedCommands` to `View`, defaulting to `[Command.Accept]`. `Raise*` methods propagate if the command is in `SuperView?.PropagatedCommands` and `args.Handled` is `false`.
- **Example**:
```csharp
public IReadOnlyList<Command> PropagatedCommands { get; set; } = new List<Command> { Command.Accept };
protected bool? RaiseActivating(ICommandContext? ctx)
{
CommandEventArgs args = new() { Context = ctx };
if (OnActivating(args) || args.Handled)
{
return true;
}
Activating?.Invoke(this, args);
if (!args.Handled && SuperView?.PropagatedCommands.Contains(Command.Activate) == true)
{
return SuperView.InvokeCommand(Command.Activate, ctx);
}
return Activating is null ? null : args.Handled;
}
```
- **Impact**: Enables `Command.Activate` propagation for `MenuBarv2` while preserving `Command.Accept` propagation, maintaining decoupling and avoiding noise from irrelevant commands.
## Challenges and Recommendations
1. **Conflation in FlagSelector**:
- **Issue**: `CheckBox.Activating` triggers `Accepting`, conflating state change and confirmation.
- **Recommendation**: Refactor to separate `Activating` and `Accepting`:
```csharp
checkbox.Activating += (sender, args) =>
{
if (RaiseActivating(args.Context) is true)
{
args.Handled = true;
}
};
```
2. **Propagation Limitations**:
- **Issue**: Local `Command.Activate` restricts `MenuBarv2` coordination; `Command.Accept` uses hacks (#3925).
- **Recommendation**: Adopt `PropagatedCommands` to enable targeted propagation, as proposed.
3. **Documentation Gaps**:
- **Issue**: The patterns phases and `Handled` semantics are not fully documented.
- **Recommendation**: Document the patterns structure, phases, and examples across `View.Draw`, `View.Keyboard`, `View.Command`, `Application.Keyboard`, and `OrientationHelper`.
4. **Complexity in Multi-Phase Workflows**:
- **Issue**: `View.Draw`s multi-phase workflow can be complex for developers to customize.
- **Recommendation**: Provide clearer phase-specific documentation and examples.
## Conclusion
The *Cancellable Work Pattern* is a foundational design in Terminal.Gui, enabling extensible, cancellable, and decoupled workflows across rendering, input handling, command execution, and property changes. Its implementation in `View.Draw`, `View.Keyboard`, `View.Command`, `Application.Keyboard`, and `OrientationHelper` supports diverse use cases, from `Menuv2`s hierarchical menus to `CheckBox`s state toggling. The proposed `PropagatedCommands` property enhances the patterns applicability in `View.Command`, addressing propagation needs while maintaining its core principles. By refining implementation flaws (e.g., `FlagSelector`) and improving documentation, Terminal.Gui can further leverage this pattern for robust, flexible UI interactions.