Files
Terminal.Gui/docfx/docs/scheme.md
Tig 7422385457 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>
2025-05-29 13:55:54 -06:00

98 lines
8.6 KiB
Markdown

# Scheme Deep Dive
See [Drawing](drawing.md) for an overview of the drawing system and [Configuration](config.md) for an overview of the configuration system.
## Scheme Overview
A Scheme is named a mapping from `VisualRole`s (e.g. `VisualRole.Focus`) to `Attribute`s, defining how a `View` should look based on its purpose (e.g. Menu or Dialog). @Terminal.Gui.SchemeManager.Schemes is a dictionary of `Scheme`s, indexed by name.
A Scheme defines how Views look based on their semantic purpose. The following schemes are supported:
| Scheme Name | Description |
|:-----|:--------|
| **Base** | The base scheme used for most Views. |
| **Dialog** | The dialog scheme; used for Dialog, MessageBox, and other views dialog-like views. |
| **Error** | The scheme for showing errors, such as in `ErrorQuery`. |
| **Menu** | The menu scheme; used for Terminal.Gui.Menu, MenuBar, and StatusBar. |
| **TopLevel** | The application Toplevel scheme; used for the Toplevel View. |
@Terminal.Gui.SchemeManager manages the set of available schemes and provides a set of convenience methods for getting the current scheme and for overriding the default values for these schemes.
```csharp
Scheme dialogScheme = SchemeManager.GetScheme (Schemes.Dialog);
```
[ConfigurationManager](config.md) can be used to override the default values for these schemes and add additional schemes.
### Scheme Inheritance
A `Scheme` enables consistent, semantic theming of UI elements by associating each visual state with a specific style. Each property (e.g., `Normal` or `Focus`) is an @Terminal.Gui.Attribute.
Only `Normal` is required. If other properties are not explicitly set, its value is derived from other roles (typically `Normal`) using well-defined inheritance rules. See the source code for the `Scheme` class for more details.
### Flexible Scheme Management in `Terminal.Gui.View`
A `View`'s appearance is primarily determined by its `Scheme`, which maps semantic `VisualRole`s (like `Normal`, `Focus`, `Disabled`) to specific `Attribute`s (foreground color, background color, and text style). `Terminal.Gui` provides a flexible system for managing these schemes:
1. **Scheme Inheritance (Default Behavior)**:
* By default, if a `View` does not have a `Scheme` explicitly set, it inherits the `Scheme` from its `SuperView` (its parent in the view hierarchy).
* This cascading behavior allows for consistent styling across related views. If no `SuperView` has a scheme, (e.g., if the view is a top-level view), it ultimately falls back to the "Base" scheme defined in `SchemeManager.GetCurrentSchemes()`.
* The `GetScheme()` method implements this logic:
* It first checks if a scheme has been explicitly set via the `_scheme` field (see point 2).
* If not, and if `SchemeName` is set, it tries to resolve the scheme by name from `SchemeManager`.
* If still no scheme, it recursively calls `SuperView.GetScheme()`.
* As a final fallback, it uses `SchemeManager.GetCurrentSchemes()["Base"]`.
2. **Explicit Scheme Assignment**:
* You can directly assign a `Scheme` object to a `View` using the `View.Scheme` property (which calls `SetScheme(value)`). This overrides any inherited scheme. The `HasScheme` property will then return `true`.
* Alternatively, you can set the `View.SchemeName` property to the name of a scheme registered in `SchemeManager`. If `Scheme` itself hasn't been directly set, `GetScheme()` will use `SchemeName` to look up the scheme. This is useful for declarative configurations (e.g., from a JSON file).
* The `SetScheme(Scheme? scheme)` method updates the internal `_scheme` field. If the new scheme is different from the current one, it marks the view for redraw (`SetNeedsDraw()`) to reflect the visual change. It also handles a special case for `Border` to ensure its scheme is updated if it `HasScheme`.
3. **Event-Driven Customization**:
The scheme resolution and application process includes events that allow for fine-grained control and customization:
* **`GettingScheme` Event (`View.Scheme.cs`)**:
* This event is raised within `GetScheme()` *before* the default logic (inheritance, `SchemeName` lookup, or explicit `_scheme` usage) fully determines the scheme.
* Subscribers (which could be the `SuperView`, a `SubView`, or any other interested component) can handle this event.
* In the event handler, you can:
* **Modify the scheme**: Set `args.NewScheme` to a different `Scheme` object.
* **Cancel default resolution**: Set `args.Cancel = true`. If canceled, the `Scheme` provided in `args.NewScheme` (which might have been modified by the handler) is returned directly by `GetScheme()`.
* The `OnGettingScheme(out Scheme? scheme)` virtual method is called first, allowing derived classes to provide a scheme directly.
* **`SettingScheme` Event (`View.Scheme.cs`)**:
* This event is raised within `SetScheme(Scheme? scheme)` *before* the `_scheme` field is actually updated.
* Subscribers can cancel the scheme change by setting `args.Cancel = true` in the event handler.
* The `OnSettingScheme(in Scheme? scheme)` virtual method is called first, allowing derived classes to prevent the scheme from being set.
4. **Retrieving and Applying Attributes for Visual Roles (`View.Attribute.cs`)**:
Once a `View` has determined its active `Scheme` (via `GetScheme()`), it uses this scheme to get specific `Attribute`s for rendering different parts of itself based on their `VisualRole`.
* **`GetAttributeForRole(VisualRole role)`**:
* This method first retrieves the base `Attribute` for the given `role` from the `View`'s current `Scheme` (`GetScheme()!.GetAttributeForRole(role)`).
* It then raises the `GettingAttributeForRole` event (and calls the `OnGettingAttributeForRole` virtual method).
* Subscribers to `GettingAttributeForRole` can:
* **Modify the attribute**: Change the `args.NewValue` (which is passed by `ref` as `schemeAttribute` to the event).
* **Cancel default behavior**: Set `args.Cancel = true`. The (potentially modified) `args.NewValue` is then returned.
* Crucially, if the `View` is `Enabled == false` and the requested `role` is *not* `VisualRole.Disabled`, this method will recursively call itself to get the `Attribute` for `VisualRole.Disabled`. This ensures disabled views use their designated disabled appearance.
* **`SetAttributeForRole(VisualRole role)`**:
* This method is used to tell the `ConsoleDriver` which `Attribute` to use for subsequent drawing operations (like `AddRune` or `AddStr`).
* It first determines the appropriate `Attribute` for the `role` from the current `Scheme` by calling `GetAttributeForRole`.
* **`SetAttribute(Attribute attribute)`**:
* This is a more direct way to set the driver's current attribute, bypassing the scheme and role system. It's generally preferred to use `SetAttributeForRole` to maintain consistency with the `Scheme`.
### Impact of SuperViews and SubViews via Events
* **SuperView Influence**: A `SuperView` can subscribe to its `SubView`'s `GettingScheme` or `GettingAttributeForRole` events. This would allow a `SuperView` to dynamically alter how its children determine their schemes or specific attributes, perhaps based on the `SuperView`'s state or other application logic. For example, a container view might want all its children to adopt a slightly modified version of its own scheme under certain conditions.
* **SubView Influence (Less Common for Scheme of Parent)**: While a `SubView` *could* subscribe to its `SuperView`'s scheme events, this is less typical for influencing the `SuperView`'s *own* scheme. It's more common for a `SubView` to react to changes in its `SuperView`'s scheme if needed, or to manage its own scheme independently.
* **General Event Usage**: These events are powerful for scenarios where:
* A specific `View` instance needs a unique, dynamically calculated appearance that isn't easily captured by a static `Scheme` object.
* External logic needs to intercept and modify appearance decisions.
* Derived `View` classes want to implement custom scheme or attribute resolution logic by overriding the `On...` methods.
In summary, `Terminal.Gui` offers a layered approach to scheme management: straightforward inheritance and explicit setting for common cases, and a robust event system for advanced customization and dynamic control over how views derive and apply their visual attributes. This allows developers to achieve a wide range of visual styles and behaviors.