Scheme Deep Dive
See Drawing for an overview of the drawing system and Configuration for an overview of the configuration system.
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.
Scheme dialogScheme = SchemeManager.GetScheme (Schemes.Dialog);
ConfigurationManager 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 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:
Scheme Inheritance (Default Behavior):
- By default, if a
View
does not have aScheme
explicitly set, it inherits theScheme
from itsSuperView
(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 inSchemeManager.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 fromSchemeManager
. - If still no scheme, it recursively calls
SuperView.GetScheme()
. - As a final fallback, it uses
SchemeManager.GetCurrentSchemes()["Base"]
.
- It first checks if a scheme has been explicitly set via the
- By default, if a
Explicit Scheme Assignment:
- You can directly assign a
Scheme
object to aView
using theView.Scheme
property (which callsSetScheme(value)
). This overrides any inherited scheme. TheHasScheme
property will then returntrue
. - Alternatively, you can set the
View.SchemeName
property to the name of a scheme registered inSchemeManager
. IfScheme
itself hasn't been directly set,GetScheme()
will useSchemeName
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 forBorder
to ensure its scheme is updated if itHasScheme
.
- You can directly assign a
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
, aSubView
, or any other interested component) can handle this event. - In the event handler, you can:
- Modify the scheme: Set
args.NewScheme
to a differentScheme
object. - Cancel default resolution: Set
args.Cancel = true
. If canceled, theScheme
provided inargs.NewScheme
(which might have been modified by the handler) is returned directly byGetScheme()
.
- Modify the scheme: Set
- The
OnGettingScheme(out Scheme? scheme)
virtual method is called first, allowing derived classes to provide a scheme directly.
- This event is raised within
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.
- This event is raised within
Retrieving and Applying Attributes for Visual Roles (
View.Attribute.cs
): Once aView
has determined its activeScheme
(viaGetScheme()
), it uses this scheme to get specificAttribute
s for rendering different parts of itself based on theirVisualRole
.GetAttributeForRole(VisualRole role)
:- This method first retrieves the base
Attribute
for the givenrole
from theView
's currentScheme
(GetScheme()!.GetAttributeForRole(role)
). - It then raises the
GettingAttributeForRole
event (and calls theOnGettingAttributeForRole
virtual method). - Subscribers to
GettingAttributeForRole
can:- Modify the attribute: Change the
args.NewValue
(which is passed byref
asschemeAttribute
to the event). - Cancel default behavior: Set
args.Cancel = true
. The (potentially modified)args.NewValue
is then returned.
- Modify the attribute: Change the
- Crucially, if the
View
isEnabled == false
and the requestedrole
is notVisualRole.Disabled
, this method will recursively call itself to get theAttribute
forVisualRole.Disabled
. This ensures disabled views use their designated disabled appearance.
- This method first retrieves the base
SetAttributeForRole(VisualRole role)
:- This method is used to tell the
ConsoleDriver
whichAttribute
to use for subsequent drawing operations (likeAddRune
orAddStr
). - It first determines the appropriate
Attribute
for therole
from the currentScheme
by callingGetAttributeForRole
.
- This method is used to tell the
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 theScheme
.
- 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
Impact of SuperViews and SubViews via Events
SuperView Influence: A
SuperView
can subscribe to itsSubView
'sGettingScheme
orGettingAttributeForRole
events. This would allow aSuperView
to dynamically alter how its children determine their schemes or specific attributes, perhaps based on theSuperView
'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 itsSuperView
's scheme events, this is less typical for influencing theSuperView
's own scheme. It's more common for aSubView
to react to changes in itsSuperView
'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 staticScheme
object. - External logic needs to intercept and modify appearance decisions.
- Derived
View
classes want to implement custom scheme or attribute resolution logic by overriding theOn...
methods.
- A specific
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.