mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-26 15:57:56 +01:00
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:
243
docfx/docs/cancellable-work-pattern.md
Normal file
243
docfx/docs/cancellable-work-pattern.md
Normal 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.Gui’s `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 view’s 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 view’s 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 pattern’s phases and `Handled` semantics are not fully documented.
|
||||
- **Recommendation**: Document the pattern’s 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 pattern’s 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.
|
||||
@@ -2,58 +2,271 @@
|
||||
|
||||
Terminal.Gui provides persistent configuration settings via the [`ConfigurationManager`](~/api/Terminal.Gui.ConfigurationManager.yml) class.
|
||||
|
||||
1) **Settings**. Settings are applied to the [`Application`](~/api/Terminal.Gui.Application.yml) class. Settings are accessed via the `Settings` property of [`ConfigurationManager`](~/api/Terminal.Gui.ConfigurationManager.yml).
|
||||
2) **Themes**. Themes are a named collection of settings impacting how applications look. The default theme is named "Default". Two other built-in themes are provided: "Dark", and "Light". Additional themes can be defined in the configuration files.
|
||||
3) **AppSettings**. Applications can use the [`ConfigurationManager`](~/api/Terminal.Gui.ConfigurationManager.yml) to store and retrieve application-specific settings.
|
||||
## Configuration Lexicon and Taxonomy
|
||||
|
||||
| Term | Meaning |
|
||||
|:-----|:--------|
|
||||
| **AppSettings** | Application-specific settings stored in the application's resources. |
|
||||
| **Apply** | Apply the configuration to the application; copies settings from configuration properties to corresponding `static` `[ConfigProperty]` properties. |
|
||||
| **Attribute** | Defines concrete visual styling for a visual element (Foreground color, Background color, TextStyle). |
|
||||
| **BackgroundColor** | A property of `Attribute` describing the background text color. |
|
||||
| **Color** | Base terminal color (supports TrueColor and named values like White, Black, Cyan, etc.). |
|
||||
| **ConfigProperty** | A property decorated with `[ConfigProperty]` that can be configured via the configuration system. |
|
||||
| **Configuration** | A collection of settings defining application behavior and appearance. |
|
||||
| **ConfigurationManager** | System that loads and manages application runtime settings from external sources. |
|
||||
| **ForegroundColor** | A property of `Attribute` describing the foreground text color. |
|
||||
| **Load** | Load configuration from given location(s), updating with new values. Loading doesn't apply settings automatically. |
|
||||
| **Location** | Storage location for configuration (e.g., user's home directory, application directory). |
|
||||
| **Reset** | Reset configuration to current values or hard-coded defaults. Does not load configuration. |
|
||||
| **Scope** | Defines the context where configuration applies (Settings, Theme, or AppSettings). |
|
||||
| **Scheme** | Maps `VisualRole` to `Attribute`, defining visual element appearance (color and style) based on semantic purpose. |
|
||||
| **Settings** | Runtime options including both system settings and application-specific settings. |
|
||||
| **Sources** | Set of locations where configuration can be stored (@Terminal.Gui.ConfigLocations enum). |
|
||||
| **Style** | Property of `Attribute` for font-like hints (bold, italic, underline). |
|
||||
| **Theme** | Named instance containing specific appearance settings. |
|
||||
| **ThemeInheritance** | Mechanism where themes can inherit and override settings from other themes. |
|
||||
| **Themes** | Collection of named Theme definitions bundling visual and layout settings. |
|
||||
| **VisualRole** | Semantic role/purpose of a visual element (Normal, Focus, HotFocus, Active, Disabled, ReadOnly). |
|
||||
|
||||
# Fundamentals
|
||||
|
||||
The `ConfigurationManager` class provides a way to store and retrieve configuration settings for an application. The configuration is stored in JSON documents, which can be located in the user's home directory, the current working directory, in memory, or as a resource within the application's main assembly.
|
||||
|
||||
Settings are defined in JSON format, according to this schema: https://gui-cs.github.io/Terminal.GuiV2Docs/schemas/tui-config-schema.json.
|
||||
|
||||
Terminal.Gui library developers can define settings in code and set the default values in the Terminal.Gui assembly's resources (e.g. `Terminal.Gui.Resources.config.json`).
|
||||
|
||||
Terminal.Gui application developers can define settings in their apps' code and set the default values in their apps' resources (e.g. `Resources/config.json`) or by setting @Terminal.Gui.Application.RuntimeConfig to string containing JSON.
|
||||
|
||||
Users can change settings on a global or per-application basis by providing JSON formatted configuration files. The configuration files can be placed in at .tui folder in the user's home directory (e.g. `C:/Users/username/.tui`, or `/usr/username/.tui`) or the folder where the Terminal.Gui application was launched from (e.g. `./.tui`).
|
||||
|
||||
## CM is Disabled by Default
|
||||
|
||||
The `ConfigurationManager` must be enabled explicitly by calling @Terminal.Gui.ConfigurationManager.Enable() in an application's `Main` method.
|
||||
|
||||
```csharp
|
||||
// Enable configuration with all sources
|
||||
ConfigurationManager.Enable(ConfigLocations.All);
|
||||
```
|
||||
|
||||
If `ConfigurationManager.Enable()` is not called (`ConfigurationManager.IsEnabled` is 'false'), all configuration settings are ignored and ConfigurationManager will effectively be a no-op. All `[ConfigurationProperty]` properties will initially be their hard-coded default values. Calling @Terminal.Gui.ConfigurationManager.Reset will reset all configuration properties back to their hard-coded default values.
|
||||
|
||||
Other than that, no other ConfigurationManager APIs will have any effect.
|
||||
|
||||
## Loading and Applying Configuration
|
||||
|
||||
Optionally, developers can more granularly control the loading and applying of configuration by calling the `Load` and `Apply` methods directly.
|
||||
|
||||
When a configuration has been loaded, the @Terminal.Gui.ConfigurationManager.Apply method must be called to apply the settings to the application. This method uses reflection to find all static fields decorated with the `[ConfigurationProperty]` attribute and applies the settings to the corresponding properties.
|
||||
|
||||
```csharp
|
||||
// Load the configuration from just the users home directory.
|
||||
ConfigurationManager.Enable(ConfigLocations.HardCoded);
|
||||
ConfigurationManager.Load(ConfigLocations.GlobalHome);
|
||||
ConfigurationManager.Apply();
|
||||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Configuration Settings Apply at the Process Level.
|
||||
> Configuration settings are applied at the process level, which means that they are applied to all applications that are part of the same process. This is due to the fact that configuration properties are defined as static fields, which are static for the process.
|
||||
|
||||
## Configuration Types and Scopes
|
||||
|
||||
Terminal.Gui supports three main configuration scopes. See the section below titled [What Can Be Configured](#what-can-be-configured) for more details.
|
||||
|
||||
### SettingsScope
|
||||
|
||||
System-level settings that affect Terminal.Gui behavior:
|
||||
```csharp
|
||||
[ConfigurationProperty (Scope = typeof (SettingsScope))]
|
||||
public static int MaxSearchResults { get; set; } = 10000;
|
||||
```
|
||||
|
||||
### ThemeScope
|
||||
Visual appearance settings that can be themed:
|
||||
```csharp
|
||||
[ConfigurationProperty (Scope = typeof (ThemeScope))]
|
||||
public new static LineStyle DefaultBorderStyle { get; set; } = LineStyle.Single;
|
||||
```
|
||||
|
||||
### AppSettingsScope (default)
|
||||
Application-specific settings:
|
||||
```csharp
|
||||
[ConfigurationProperty] // AppSettingsScope is default
|
||||
public static string MyAppSetting { get; set; } = "default";
|
||||
```
|
||||
|
||||
## Configuration Precedence
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[Hard-coded Defaults] --> B[Terminal.Gui Defaults]
|
||||
B --> C[Runtime Config]
|
||||
C --> D[App Resources]
|
||||
D --> E[App Home Directory]
|
||||
E --> F[App Current Directory]
|
||||
F --> G[Global Home Directory]
|
||||
G --> H[Global Current Directory]
|
||||
```
|
||||
|
||||
Settings are applied using the following precedence (higher precedence settings overwrite lower precedence settings):
|
||||
|
||||
1. Hard-coded default values in any static property decorated with the `[ConfigurationProperty]` attribute.
|
||||
|
||||
2. @Terminal.Gui.ConfigLocations.Default - Default settings in the Terminal.Gui assembly -- Lowest precedence.
|
||||
|
||||
3. @Terminal.Gui.ConfigLocations.Runtime - Settings stored in the @Terminal.Gui.ConfigurationManager.RuntimeConfig static property.
|
||||
|
||||
4. @Terminal.Gui.ConfigLocations.AppResources - App settings in app resources (`Resources/config.json`).
|
||||
|
||||
5. @Terminal.Gui.ConfigLocations.AppHome - App-specific settings in the users's home directory (`~/.tui/appname.config.json`).
|
||||
|
||||
6. @Terminal.Gui.ConfigLocations.AppCurrent - App-specific settings in the directory the app was launched from (`./.tui/appname.config.json`).
|
||||
|
||||
7. @Terminal.Gui.ConfigLocations.GlobalHome - Global settings in the the user's home directory (`~/.tui/config.json`).
|
||||
|
||||
8. @Terminal.Gui.ConfigLocations.GlobalCurrent - Global settings in the directory the app was launched from (`./.tui/config.json`) --- Hightest precedence.
|
||||
|
||||
|
||||
The [`ConfigurationManager`](~/api/Terminal.Gui.ConfigurationManager.yml) will look for configuration files in the `.tui` folder in the user's home directory (e.g. `C:/Users/username/.tui` or `/usr/username/.tui`), the folder where the Terminal.Gui application was launched from (e.g. `./.tui`), or as a resource within the Terminal.Gui application's main assembly.
|
||||
|
||||
Settings that will apply to all applications (global settings) reside in files named `config.json`. Settings that will apply to a specific Terminal.Gui application reside in files named `appname.config.json`, where *appname* is the assembly name of the application (e.g. `UICatalog.config.json`).
|
||||
|
||||
Settings are applied using the following precedence (higher precedence settings overwrite lower precedence settings):
|
||||
|
||||
1. @Terminal.Gui.ConfigLocations.Default - Default settings in the Terminal.Gui assembly -- Lowest precedence.
|
||||
|
||||
2. @Terminal.Gui.ConfigLocations.Runtime - Settings stored in the @Terminal.Gui.ConfigurationManager.RuntimeConfig static property.
|
||||
## Configuration Events
|
||||
|
||||
3. @Terminal.Gui.ConfigLocations.AppResources - App settings in app resources (`Resources/config.json`).
|
||||
The ConfigurationManager provides several events to track configuration changes:
|
||||
|
||||
4. @Terminal.Gui.ConfigLocations.AppHome - App-specific settings in the users's home directory (`~/.tui/appname.config.json`).
|
||||
```csharp
|
||||
// Called after configuration is applied
|
||||
ConfigurationManager.Applied += (sender, e) => {
|
||||
// Handle configuration changes
|
||||
};
|
||||
|
||||
5. @Terminal.Gui.ConfigLocations.AppCurrent - App-specific settings in the directory the app was launched from (`./.tui/appname.config.json`).
|
||||
// Called when the active theme changes
|
||||
ConfigurationManager.ThemeChanged += (sender, e) => {
|
||||
// Handle theme changes
|
||||
};
|
||||
```
|
||||
|
||||
6. @Terminal.Gui.ConfigLocations.GlobalHome - Global settings in the the user's home directory (`~/.tui/config.json`).
|
||||
|
||||
7. @Terminal.Gui.ConfigLocations.GlobalCurrent - Global settings in the directory the app was launched from (`./.tui/config.json`) --- Hightest precedence.
|
||||
## How Settings are Defined
|
||||
|
||||
The `UICatalog` application provides an example of how to use the [`ConfigurationManager`](~/api/Terminal.Gui.ConfigurationManager.yml) class to load and save configuration files. The `Configuration Editor` scenario provides an editor that allows users to edit the configuration files. UI Catalog also uses a file system watcher to detect changes to the configuration files to tell [`ConfigurationManager`](~/api/Terminal.Gui.ConfigurationManager.yml) to reload them; allowing users to change settings without having to restart the application.
|
||||
Application developers define settings by decorating static properties with the `[ConfigurationProperty]` attribute.
|
||||
|
||||
```csharp
|
||||
class MyApp
|
||||
{
|
||||
[ConfigurationProperty]
|
||||
public static string MySetting { get; set; } = "Default Value";
|
||||
}
|
||||
```
|
||||
|
||||
Configuration Properties must be `public` or `internal` `static` properties.
|
||||
|
||||
The above example will define a configuration property in the `AppSettings` scope. The name of the property will be `MyApp.MySetting` and will appear in JSON as:
|
||||
|
||||
```json
|
||||
{
|
||||
"AppSettings": {
|
||||
"MyApp.MySetting": "Default Value"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`AppSettings` property names must be globally unique. To ensure this, the name of the AppSettings property is the name of the property prefixed with a period and the full name of the class that holds it. In the example above, the AppSettings property is named `MyApp.MySetting`.
|
||||
|
||||
Terminal.Gui library developers can use the `SettingsScope` and `ThemeScope` attributes to define settings and themes for the terminal.Gui library.
|
||||
|
||||
> [!IMPORTANT] App developers cannot define `SettingScope` or `ThemeScope` properties.
|
||||
|
||||
```csharp
|
||||
/// <summary>
|
||||
/// Gets or sets whether <see cref="Button"/>s are shown with a shadow effect by default.
|
||||
/// </summary>
|
||||
[ConfigurationProperty (Scope = typeof (ThemeScope))]
|
||||
public static ShadowStyle DefaultShadow { get; set; } = ShadowStyle.None;
|
||||
```
|
||||
|
||||
# Sample Code
|
||||
|
||||
The `UICatalog` application provides an example of how to use the [`ConfigurationManager`](~/api/Terminal.Gui.ConfigurationManager.yml) class to load and save configuration files.
|
||||
|
||||
The `Configuration Editor` Scenario provides an editor that allows users to edit the configuration files. UI Catalog also uses a file system watcher to detect changes to the configuration files to tell [`ConfigurationManager`](~/api/Terminal.Gui.ConfigurationManager.yml) to reload them; allowing users to change settings without having to restart the application.
|
||||
|
||||
The `Themes` Scenario in the UI Catalog provides a viewer for the themes defined in the configuration files.
|
||||
|
||||
# What Can Be Configured
|
||||
|
||||
## Settings
|
||||
`ConfigurationManager` provides the following features:
|
||||
|
||||
> [!IMPORTANT]
|
||||
> This list is not complete; search the source code for `SerializableConfigurationProperty` to find all settings that can be configured.
|
||||
1) **Settings**. Settings are applied to the [`Application`](~/api/Terminal.Gui.Application.yml) class. Settings are accessed via the `Settings` property of [`ConfigurationManager`](~/api/Terminal.Gui.ConfigurationManager.yml). E.g. `Settings["Application.QuitKey"]`
|
||||
2) **Themes**. Themes are a named collection of settings impacting how applications look. The default theme is named "Default". Two other built-in themes are provided: "Dark", and "Light". Additional themes can be defined in the configuration files. `Settings ["Themes"]` is a dictionary of theme names to theme settings.
|
||||
3) **AppSettings**. Applications can use the [`ConfigurationManager`](~/api/Terminal.Gui.ConfigurationManager.yml) to store and retrieve application-specific settings.
|
||||
|
||||
* @Terminal.Gui.Application.QuitKey
|
||||
* @Terminal.Gui.Application.NextTabKey
|
||||
* @Terminal.Gui.Application.PrevTabKey
|
||||
* @Terminal.Gui.Application.NextTabGroupKey
|
||||
* @Terminal.Gui.Application.PrevTabGroupKey
|
||||
* @Terminal.Gui.Application.ArrangeKey
|
||||
* @Terminal.Gui.Application.ForceDriver
|
||||
* @Terminal.Gui.Application.Force16Colors
|
||||
* @Terminal.Gui.Application.IsMouseDisabled
|
||||
|
||||
Methods for discovering what can be configured are available in the `ConfigurationManager` class:
|
||||
|
||||
- Call @ConfigurationManager.GetConfigurationProperties()
|
||||
- Search the source code for `[ConfigurationProperty]`
|
||||
- View `./Terminal.Gui/Resources/config.json`
|
||||
|
||||
For complete schema details and examples, refer to:
|
||||
- Schema: https://gui-cs.github.io/Terminal.GuiV2Docs/schemas/tui-config-schema.json
|
||||
- Default configuration: Terminal.Gui/Resources/config.json
|
||||
|
||||
## Themes
|
||||
|
||||
A Theme is a named collection of settings that impact the visual style of Terminal.Gui applications. The default theme is named "Default". The built-in configuration within the Terminal.Gui library defines two more themes: "Dark", and "Light". Additional themes can be defined in the configuration files. The JSON property `Theme` defines the name of the theme that will be used. If the theme is not found, the default theme will be used.
|
||||
|
||||
Themes support defining ColorSchemes as well as various default settings for Views. Both the default color schemes and user-defined color schemes can be configured. See [ColorSchemes](~/api/Terminal.Gui.Colors.yml) for more information.
|
||||
Themes support defining Schemes (a set of colors and styles that define the appearance of views) as well as various default settings for Views. Both the default color schemes and user-defined color schemes can be configured. See [Schemes](~/api/Terminal.Gui.Schemes.yml) for more information.
|
||||
|
||||
### Theme Configuration
|
||||
|
||||
Themes provide a way to bundle visual settings together. When @Terminal.Gui.ConfigurationManager.Apply is called, the theme settings are applied to the application.
|
||||
|
||||
```json
|
||||
// ...
|
||||
"Dark": {
|
||||
"Dialog.DefaultButtonAlignment": "End",
|
||||
"Dialog.DefaultButtonAlignmentModes": "AddSpaceBetweenItems",
|
||||
"Dialog.DefaultBorderStyle": "Heavy",
|
||||
"Dialog.DefaultShadow": "Transparent",
|
||||
"FrameView.DefaultBorderStyle": "Single",
|
||||
"Window.DefaultBorderStyle": "Single",
|
||||
"MessageBox.DefaultButtonAlignment": "Center",
|
||||
"MessageBox.DefaultBorderStyle": "Heavy",
|
||||
"Button.DefaultShadow": "Opaque",
|
||||
"Schemes": [
|
||||
{
|
||||
"TopLevel": {
|
||||
"Normal": {
|
||||
"Foreground": "LightGray",
|
||||
"Background": "Black",
|
||||
"Style": "None"
|
||||
},
|
||||
// etc...
|
||||
```
|
||||
|
||||
Only properties that are defined in the theme will be applied, meaning that themes can be used to override the a previously applied theme.
|
||||
|
||||
To ensure a theme inherits from the default theme, first apply the default theme, then apply the new theme, like this:
|
||||
|
||||
```csharp
|
||||
// Apply the default theme
|
||||
ThemeManager.Theme = "Default";
|
||||
ConfigurationManager.Apply();
|
||||
|
||||
// Apply the new theme
|
||||
ThemeManager.Theme = "MyCustomTheme";
|
||||
ConfigurationManager.Apply();
|
||||
```
|
||||
|
||||
### Glyphs
|
||||
|
||||
Themes support changing the standard set of glyphs used by views (e.g. the default indicator for [Button](~/api/Terminal.Gui.Button.yml)) and line drawing (e.g. [LineCanvas](~/api/Terminal.Gui.LineCanvas.yml)).
|
||||
|
||||
|
||||
The value can be either a decimal number or a string. The string may be:
|
||||
|
||||
- A Unicode char (e.g. "☑")
|
||||
@@ -67,9 +280,98 @@ The value can be either a decimal number or a string. The string may be:
|
||||
"Glyphs.UpArrow": 965010
|
||||
```
|
||||
|
||||
The `UI Catalog` application defines a `UICatlog` Theme. Look at the UI Catalog's `./Resources/config.json` file to see how to define a theme.
|
||||
The `UI Catalog` application defines a `UICatalog` Theme. Look at the UI Catalog's `./Resources/config.json` file to see how to define a theme.
|
||||
|
||||
# Key Bindings
|
||||
### Theme and Scheme Management
|
||||
|
||||
Terminal.Gui provides two key managers for handling visual themes and schemes:
|
||||
|
||||
The ThemeManager provides convenient methods for working with themes:
|
||||
|
||||
```csharp
|
||||
// Get the currently active theme
|
||||
ThemeScope currentTheme = ThemeManager.GetCurrentTheme();
|
||||
|
||||
// Get all available themes
|
||||
Dictionary<string, ThemeScope> themes = ThemeManager.GetThemes();
|
||||
|
||||
// Get list of theme names
|
||||
ImmutableList<string> themeNames = ThemeManager.GetThemeNames();
|
||||
|
||||
// Get/Set current theme name
|
||||
string currentThemeName = ThemeManager.GetCurrentThemeName();
|
||||
ThemeManager.Theme = "Dark"; // Switch themes
|
||||
|
||||
// Listen for theme changes
|
||||
ThemeManager.ThemeChanged += (sender, e) => {
|
||||
// Handle theme changes
|
||||
};
|
||||
```
|
||||
|
||||
### SchemeManager
|
||||
|
||||
The SchemeManager handles schemes within themes. Each theme contains multiple schemes for different UI contexts:
|
||||
|
||||
```csharp
|
||||
// Get current schemes
|
||||
Dictionary<string, Scheme> schemes = SchemeManager.GetCurrentSchemes();
|
||||
|
||||
// Get list of scheme names
|
||||
ImmutableList<string> schemeNames = SchemeManager.GetSchemeNames();
|
||||
|
||||
// Access specific schemes
|
||||
Scheme topLevelScheme = SchemeManager.GetScheme("TopLevel");
|
||||
|
||||
// Listen for scheme changes
|
||||
SchemeManager.CollectionChanged += (sender, e) => {
|
||||
// Handle scheme changes
|
||||
};
|
||||
```
|
||||
|
||||
### Built-in Schemes
|
||||
|
||||
The following Schemes are available by default:
|
||||
|
||||
- **TopLevel**: Used for the application's top-level windows
|
||||
- **Base**: Default scheme for most views
|
||||
- **Dialog**: Used for dialogs and message boxes
|
||||
- **Menu**: Used for menus and status bars
|
||||
- **Error**: Used for error messages and dialogs
|
||||
|
||||
Each Scheme defines the attributes for different VisualRoles.
|
||||
|
||||
## Application Settings
|
||||
|
||||
Terminal.Gui provides several top-level application settings:
|
||||
|
||||
```json
|
||||
{
|
||||
"Key.Separator": "+",
|
||||
"Application.ArrangeKey": "Ctrl+F5",
|
||||
"Application.Force16Colors": false,
|
||||
"Application.IsMouseDisabled": false,
|
||||
"Application.NextTabGroupKey": "F6",
|
||||
"Application.NextTabKey": "Tab",
|
||||
"Application.PrevTabGroupKey": "Shift+F6",
|
||||
"Application.PrevTabKey": "Shift+Tab",
|
||||
"Application.QuitKey": "Esc"
|
||||
}
|
||||
```
|
||||
|
||||
### View-Specific Settings
|
||||
|
||||
Examples of settings that control specific view behaviors:
|
||||
|
||||
```json
|
||||
{
|
||||
"PopoverMenu.DefaultKey": "Shift+F10",
|
||||
"FileDialog.MaxSearchResults": 10000,
|
||||
"FileDialogStyle.DefaultUseColors": false,
|
||||
"FileDialogStyle.DefaultUseUnicodeCharacters": false
|
||||
}
|
||||
```
|
||||
|
||||
### Key Bindings
|
||||
|
||||
> [!WARNING]
|
||||
> Configuration Manager support for key bindings is not yet implemented.
|
||||
@@ -78,18 +380,26 @@ Key bindings are defined in the `KeyBindings` property of the configuration file
|
||||
|
||||
- `Key`: The key to bind to. The format is a string describing the key (e.g. "q", "Q, "Ctrl+Q"). Function keys are specified as "F1", "F2", etc.
|
||||
|
||||
# Error Handling
|
||||
|
||||
```json
|
||||
{
|
||||
"ConfigurationManager.ThrowOnJsonErrors": false
|
||||
}
|
||||
```
|
||||
|
||||
Set to `true` to throw exceptions on JSON parsing errors instead of silent failures.
|
||||
|
||||
# Configuration File Schema
|
||||
|
||||
Settings are defined in JSON format, according to the schema found here:
|
||||
|
||||
https://gui-cs.github.io/Terminal.Gui/schemas/tui-config-schema.json
|
||||
|
||||
## Schema
|
||||
|
||||
[!code-json[tui-config-schema.json](../schemas/tui-config-schema.json)]
|
||||
|
||||
# The Default Config File
|
||||
## The Default Config File
|
||||
|
||||
To illustrate the syntax, the below is the `config.json` file found in `Terminal.Gui.dll`:
|
||||
|
||||
[!code-json[config.json](../../Terminal.Gui/Resources/config.json)]
|
||||
[!code-json[config.json](../../Terminal.Gui/Resources/config.json)]
|
||||
|
||||
|
||||
|
||||
@@ -1,31 +1,50 @@
|
||||
# Drawing (Text, Lines, and Color)
|
||||
|
||||
Terminal.Gui provides a set of APIs for formatting text, line drawing, and character-based graphing. The fundamental concept is a @Terminal.Gui.Cell which occupies a particular row and column in the terminal. A Cell includes the character (glyph) that should be rendred by the terminal, and attributes that indicate how the glyph should be rendered (e.g. the foreground and background color).
|
||||
Terminal.Gui provides a set of APIs for formatting text, line drawing, and character-based graphing.
|
||||
|
||||
Color is supported on all platforms, including Windows, Mac, and Linux. The default colors are 24-bit RGB colors, but the library will gracefully degrade to 16-colors if the terminal does not support 24-bit color, and black and white if the terminal does not support 16-colors.
|
||||
## Drawing Lexicon and Taxonomy
|
||||
|
||||
## View Drawing API
|
||||
| Term | Meaning |
|
||||
|:-----|:--------|
|
||||
| **Attribute** | Defines the concrete visual styling for a visual element, including Foreground color, Background color, and TextStyle. |
|
||||
| **BackgroundColor** | A property of `Attribute` that describes the color of background text. |
|
||||
| **Color** | Base terminal color (part of the color palette; supports TrueColor and named values like White, Black, Cyan, etc.). |
|
||||
| **Cell** | A single character and its attributes which occupies a particular row and column in the terminal. Not exposed directly to the developer, but used internally by drivers. See @Terminal.Gui.Cell |
|
||||
| **ForegroundColor** | A property of `Attribute` that describes the color of foreground text. |
|
||||
| **Scheme** | A Scheme is 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). |
|
||||
| **Style** | A property of `Attribute` that captures additional font-like hints such as bold, italic, underline, beyond color. |
|
||||
| **Theme** | A single named instance containing specific appearance settings (e.g., "Default", "Dark"). |
|
||||
| **Themes** | A collection of named Theme definitions, each of which bundles visual and layout settings. |
|
||||
| **VisualRole** | The semantic role/purpose of a visual element inside a View (e.g., Normal, Focus, HotFocus, Active, Disabled, ReadOnly). |
|
||||
|
||||
# View Drawing API
|
||||
|
||||
Terminal.Gui apps draw using the @Terminal.Gui.View.Move(System.Int32,System.Int32) and @Terminal.Gui.View.AddRune(System.Text.Rune) APIs. Move selects the column and row of the cell and AddRune places the specified glyph in that cell using the @Terminal.Gui.Attribute that was most recently set via @Terminal.Gui.View.SetAttribute(Terminal.Gui.Attribute). The @Terminal.Gui.ConsoleDriver caches all changed Cells and efficiently outputs them to the terminal each iteration of the Application. In other words, Terminal.Gui uses deferred rendering.
|
||||
|
||||
Outputting unformatted text involves:
|
||||
## Coordinate System for Drawing
|
||||
|
||||
The @Terminal.Gui.View draw APIs all take coordinates specified in *Viewport-Relative* coordinates. That is, `0, 0` is the top-left cell visible to the user.
|
||||
|
||||
See [Layout](layout.md) for more details of the Terminal.Gui coordinate system.
|
||||
|
||||
## Outputting unformatted text
|
||||
|
||||
1) Moving the draw cursor using @Terminal.Gui.View.Move(System.Int32,System.Int32).
|
||||
2) Setting the attributes using @Terminal.Gui.View.SetAttribute(Terminal.Gui.Attribute).
|
||||
3) Outputting glyphs by calling @Terminal.Gui.View.AddRune(System.Text.Rune) or @Terminal.Gui.View.AddStr(System.String) .
|
||||
|
||||
Outputting formatted text involves:
|
||||
## Outputting formatted text
|
||||
|
||||
1) Adding the text to a @Terminal.Gui.TextFormatter object.
|
||||
2) Setting formatting options, such as @Terminal.Gui.TextFormatter.Alignment.
|
||||
3) Calling @Terminal.Gui.TextFormatter.Draw(System.Drawing.Rectangle,Terminal.Gui.Attribute,Terminal.Gui.Attribute,System.Drawing.Rectangle,Terminal.Gui.IConsoleDriver).
|
||||
|
||||
Line drawing is accomplished using the @Terminal.Gui.LineCanvas API:
|
||||
## Line drawing
|
||||
|
||||
1) Add the lines via @Terminal.Gui.LineCanvas.AddLine(System.Drawing.Point,System.Int32,Terminal.Gui.Orientation,Terminal.Gui.LineStyle,System.Nullable{Terminal.Gui.Attribute}).
|
||||
2) Either render the line canvas via @Terminal.Gui.LineCanvas.GetMap or let the @Terminal.Gui.View do so automatically (which enables automatic line joining across Views).
|
||||
|
||||
### Drawing occurs each MainLoop Iteration
|
||||
## When Drawing Occurs
|
||||
|
||||
The @Terminal.Gui.Application MainLoop will iterate over all Views in the view hierarchy, starting with @Terminal.Gui.Application.Toplevels. The @Terminal.Gui.View.Draw method will be called which, in turn:
|
||||
|
||||
@@ -48,24 +67,14 @@ Most of the steps above can be overridden by developers using the standard [Term
|
||||
|
||||
Then, after the above steps have completed, the Mainloop will iterate through all views in the view hierarchy again, this time calling Draw on any @Terminal.Gui.View.Margin objects, using the cached Clip region mentioned above. This enables Margin to be transparent.
|
||||
|
||||
|
||||
### Declaring that drawing is needed
|
||||
|
||||
If a View need to redraw because something changed within it's Content Area it can call @Terminal.Gui.View.SetNeedsDraw. If a View needs to be redrawn because something has changed the size of the Viewport, it can call @Terminal.Gui.View.SetNeedsLayout.
|
||||
|
||||
### Clipping
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Clipping is still under development and the API is subject to change.
|
||||
|
||||
## Clipping
|
||||
|
||||
Clipping enables better performance and features like transparent margins by ensuring regions of the terminal that need to be drawn actually get drawn by the @Terminal.Gui.ConsoleDriver. Terminal.Gui supports non-rectangular clip regions with @Terminal.Gui.Region. @Terminal.Gui.ConsoleDriver.Clip is the application managed clip region and is managed by @Terminal.Gui.Application. Developers cannot change this directly, but can use @Terminal.Gui.View.ClipToScreen, @Terminal.Gui.View.SetClip(Region), @Terminal.Gui.View.ClipToFrame, and @Terminal.Gui.ClipToViewPort.
|
||||
|
||||
## Coordinate System for Drawing
|
||||
|
||||
The @Terminal.Gui.View draw APIs all take coordinates specified in *Viewport-Relative* coordinates. That is, `0, 0` is the top-left cell visible to the user.
|
||||
|
||||
See [Layout](layout.md) for more details of the Terminal.Gui coordinate system.
|
||||
|
||||
## Cell
|
||||
|
||||
@@ -83,25 +92,52 @@ See the Character Map sample app in the [UI Catalog](https://gui-cs.github.io/Te
|
||||
|
||||
## Attribute
|
||||
|
||||
The @Terminal.Gui.Attribute class represents the formatting attributes of a `Cell`. It exposes properties for the foreground and background colors. The foreground and background colors are of type @Terminal.Gui.Color. In the future, it will expose properties for bold, underline, and other formatting attributes.
|
||||
The @Terminal.Gui.Attribute class represents the formatting attributes of a `Cell`. It exposes properties for the foreground and background colors as well as the text style. The foreground and background colors are of type @Terminal.Gui.Color. Bold, underline, and other formatting attributes are supported via the @Terminal.Gui.Attribute.Style property.
|
||||
|
||||
Use @Terminal.Gui.View.SetAttribute to indicate which Attribute subsequent @Terminal.Gui.View.AddRune and @Terminal.Gui.View.AddStr calls will use:
|
||||
|
||||
```cs
|
||||
// This is for illustration only. Developers typically use SetAttributeForRole instead.
|
||||
SetAttribute (new Attribute (Color.Red, Color.Black, Style.Underline));
|
||||
AddStr ("Red on Black Underlined.");
|
||||
```
|
||||
|
||||
In the above example a hard-coded Attribute is set. Normally, developers will use @Terminal.Gui.View.SetAttributeForRole(VisualRole) to have the system use the Attributes associated with a `VisualRole` (see below).
|
||||
|
||||
```cs
|
||||
// Modify the View's Scheme such that Focus is Red on Black Underlined
|
||||
SetScheme (new Scheme (Scheme)
|
||||
{
|
||||
Focus = new Attribute (Color.Red, Color.Black, Style.Underline)
|
||||
});
|
||||
|
||||
SetAttributeForRole (VisualRole.Focus);
|
||||
AddStr ("Red on Black Underlined.");
|
||||
```
|
||||
|
||||
## Color
|
||||
|
||||
The `Color` class represents a color. It provides automatic mapping between the legacy 4-bit (16-color) system and 24-bit colors. It contains properties for the red, green, and blue components of the color. The `Color` class also contains a static property for each of the 16 ANSI colors.
|
||||
Color is supported on all platforms, including Windows, Mac, and Linux. The default colors are 24-bit RGB colors, but the library will gracefully degrade to 16-colors if the terminal does not support 24-bit color, and black and white if the terminal does not support 16-colors.
|
||||
|
||||
## Color Schemes
|
||||
The `Color` class represents a color. It provides automatic mapping between the legacy 4-bit (16-color) system and 24-bit colors. It contains properties for the red, green, and blue components of the color. The `StandardColor` enum provides a set of predefined colors.
|
||||
|
||||
Terminal.Gui supports named collections of colors called @Terminal.Gui.ColorScheme. Three built-in color schemes are provided: "Default", "Dark", and "Light". Additional color schemes can be defined via [Configuration Manager](config.md).
|
||||
```cs
|
||||
Attribute attribute = new Attribute(StandardColor.Goldenrod, StandardColor.Wheat Style.None);
|
||||
```
|
||||
|
||||
Color schemes support defining colors for various states of a View. The following states are supported:
|
||||
## VisualRole
|
||||
|
||||
* Normal - The color of normal text.
|
||||
* HotNormal - The color of text indicating a @Terminal.Gui.View.Hotkey.
|
||||
* Focus - The color of text that indicates the view has focus.
|
||||
* HotFocus - The color of text indicating a hot key, when the view has focus.
|
||||
* Disabled - The state of a view when it is disabled.
|
||||
Represents the semantic visual role of a visual element rendered by a View (e.g., Normal text, Focused item, Active selection).
|
||||
|
||||
Change the colors of a view by setting the @Terminal.Gui.View.ColorScheme property.
|
||||
@Terminal.Gui.VisualRole provides a set of predefined VisualRoles:
|
||||
|
||||
[!code-csharp[VisualRole.cs](../../Terminal.Gui/Drawing/VisualRole.cs)]
|
||||
|
||||
## Schemes
|
||||
|
||||
[!code-md[Scheme Overview](scheme.md#Scheme-Overview)]
|
||||
|
||||
See [Scheme Deep Dive](scheme.md) for more details.
|
||||
|
||||
## Text Formatting
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ The simplest application looks like this:
|
||||
|
||||
```csharp
|
||||
using Terminal.Gui;
|
||||
ConfigurationManager.Enable(ConfigLocations.All);
|
||||
Application.Init ();
|
||||
var n = MessageBox.Query (50, 5, "Question", "Do you like TUI apps?", "Yes", "No");
|
||||
Application.Shutdown ();
|
||||
@@ -62,6 +63,7 @@ The [Application](~/api/Terminal.Gui.Application.yml) class additionally creates
|
||||
|
||||
```csharp
|
||||
using Terminal.Gui;
|
||||
ConfigurationManager.Enable(ConfigLocations.All);
|
||||
Application.Init ();
|
||||
|
||||
var label = new Label () {
|
||||
@@ -83,6 +85,7 @@ This example includes a menu bar at the top of the screen and a button that show
|
||||
```csharp
|
||||
using Terminal.Gui;
|
||||
|
||||
ConfigurationManager.Enable(ConfigLocations.All);
|
||||
Application.Init ();
|
||||
var menu = new MenuBar (new MenuBarItem [] {
|
||||
new MenuBarItem ("_File", new MenuItem [] {
|
||||
|
||||
97
docfx/docs/scheme.md
Normal file
97
docfx/docs/scheme.md
Normal file
@@ -0,0 +1,97 @@
|
||||
# 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.
|
||||
|
||||
Reference in New Issue
Block a user