* 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>
8.6 KiB
Scheme Deep Dive
See Drawing for an overview of the drawing system and Configuration for an overview of the configuration system.
Scheme Overview
A Scheme is named a mapping from VisualRoles (e.g. VisualRole.Focus) to Attributes, defining how a View should look based on its purpose (e.g. Menu or Dialog). @Terminal.Gui.SchemeManager.Schemes is a dictionary of Schemes, 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 @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 VisualRoles (like Normal, Focus, Disabled) to specific Attributes (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
Viewdoes not have aSchemeexplicitly set, it inherits theSchemefrom itsSuperView(its parent in the view hierarchy). - This cascading behavior allows for consistent styling across related views. If no
SuperViewhas 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
_schemefield (see point 2). - If not, and if
SchemeNameis 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
Schemeobject to aViewusing theView.Schemeproperty (which callsSetScheme(value)). This overrides any inherited scheme. TheHasSchemeproperty will then returntrue. - Alternatively, you can set the
View.SchemeNameproperty to the name of a scheme registered inSchemeManager. IfSchemeitself hasn't been directly set,GetScheme()will useSchemeNameto look up the scheme. This is useful for declarative configurations (e.g., from a JSON file). - The
SetScheme(Scheme? scheme)method updates the internal_schemefield. 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 forBorderto 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:
-
GettingSchemeEvent (View.Scheme.cs):- This event is raised within
GetScheme()before the default logic (inheritance,SchemeNamelookup, or explicit_schemeusage) 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.NewSchemeto a differentSchemeobject. - Cancel default resolution: Set
args.Cancel = true. If canceled, theSchemeprovided 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
-
SettingSchemeEvent (View.Scheme.cs):- This event is raised within
SetScheme(Scheme? scheme)before the_schemefield is actually updated. - Subscribers can cancel the scheme change by setting
args.Cancel = truein 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 aViewhas determined its activeScheme(viaGetScheme()), it uses this scheme to get specificAttributes for rendering different parts of itself based on theirVisualRole.-
GetAttributeForRole(VisualRole role):- This method first retrieves the base
Attributefor the givenrolefrom theView's currentScheme(GetScheme()!.GetAttributeForRole(role)). - It then raises the
GettingAttributeForRoleevent (and calls theOnGettingAttributeForRolevirtual method). - Subscribers to
GettingAttributeForRolecan:- Modify the attribute: Change the
args.NewValue(which is passed byrefasschemeAttributeto the event). - Cancel default behavior: Set
args.Cancel = true. The (potentially modified)args.NewValueis then returned.
- Modify the attribute: Change the
- Crucially, if the
ViewisEnabled == falseand the requestedroleis notVisualRole.Disabled, this method will recursively call itself to get theAttributeforVisualRole.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
ConsoleDriverwhichAttributeto use for subsequent drawing operations (likeAddRuneorAddStr). - It first determines the appropriate
Attributefor therolefrom the currentSchemeby 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
SetAttributeForRoleto 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
SuperViewcan subscribe to itsSubView'sGettingSchemeorGettingAttributeForRoleevents. This would allow aSuperViewto 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
SubViewcould subscribe to itsSuperView's scheme events, this is less typical for influencing theSuperView's own scheme. It's more common for aSubViewto 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
Viewinstance needs a unique, dynamically calculated appearance that isn't easily captured by a staticSchemeobject. - External logic needs to intercept and modify appearance decisions.
- Derived
Viewclasses 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.