Fixes #4331 - Use Application.Screen and ScreenContents to reduce Driver coupling (#4336)

* Initial plan

* Replace direct Driver access with ViewBase helper methods

Co-authored-by: tig <585482+tig@users.noreply.github.com>

* Update documentation to reference View methods instead of Driver

Co-authored-by: tig <585482+tig@users.noreply.github.com>

* Fix documentation wording to avoid driver reference

* Fix missed Driver accesses in Slider.cs

Co-authored-by: tig <585482+tig@users.noreply.github.com>

* Use Application.Screen instead of ScreenRows/ScreenCols, keep MoveToScreen/FillRectScreen internal for ViewBase only

Co-authored-by: tig <585482+tig@users.noreply.github.com>

* Remove MoveToScreen and FillRectScreen helper methods, use Driver directly in ViewBase classes

Co-authored-by: tig <585482+tig@users.noreply.github.com>

* Improve documentation clarity and grammar per CONTRIBUTING.md guidelines

Co-authored-by: tig <585482+tig@users.noreply.github.com>

* Add MoveToScreenPosition helper to eliminate Driver.Move calls in View subclasses

Co-authored-by: tig <585482+tig@users.noreply.github.com>

* Remove MoveToScreenPosition helper method, Menu.cs accesses Driver directly

Co-authored-by: tig <585482+tig@users.noreply.github.com>

* Replace Driver.Move with View.Move in Menu.cs

Co-authored-by: tig <585482+tig@users.noreply.github.com>

* Update documentation to reflect Driver encapsulation changes

Co-authored-by: tig <585482+tig@users.noreply.github.com>

* Add PR warnings policy to CONTRIBUTING.md

Co-authored-by: tig <585482+tig@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Tig <tig@users.noreply.github.com>
Co-authored-by: tig <585482+tig@users.noreply.github.com>
This commit is contained in:
Copilot
2025-10-26 09:48:30 -06:00
committed by GitHub
parent aef88ad4bb
commit f068709d13
25 changed files with 137 additions and 93 deletions

View File

@@ -241,6 +241,11 @@ dotnet run --project Examples/UICatalog/UICatalog.csproj
- **Tests**: Add tests for new functionality (see [Testing Requirements](#testing-requirements))
- **Coverage**: Maintain or increase code coverage
- **Scenarios**: Update UICatalog scenarios when adding features
- **Warnings**: **CRITICAL - PRs must not introduce any new warnings**
- Any file modified in a PR that currently generates warnings **MUST** be fixed to remove those warnings
- Exception: Warnings caused by `[Obsolete]` attributes can remain
- Expected baseline: ~326 warnings (mostly nullable reference warnings, unused variables, xUnit suggestions)
- Action: Before submitting a PR, verify your changes don't add new warnings and fix any warnings in files you modify
---
@@ -396,6 +401,7 @@ Key documentation:
- ❌ Don't decrease code coverage
- ❌ **Don't use `var` for anything but built-in simple types** (use explicit types)
- ❌ **Don't use redundant type names with `new`** (**ALWAYS PREFER** target-typed `new ()`)
- ❌ **Don't introduce new warnings** (fix warnings in files you modify; exception: `[Obsolete]` warnings)
---

View File

@@ -95,12 +95,12 @@ internal class ShadowView : View
{
for (int c = Math.Max (0, screen.X + 1); c < screen.X + screen.Width; c++)
{
Driver?.Move (c, r);
Driver.Move (c, r);
SetAttribute (GetAttributeUnderLocation (new (c, r)));
if (c < Driver?.Contents!.GetLength (1) && r < Driver?.Contents?.GetLength (0))
if (c < ScreenContents?.GetLength (1) && r < ScreenContents?.GetLength (0))
{
Driver.AddRune (Driver.Contents [r, c].Rune);
AddRune (ScreenContents [r, c].Rune);
}
}
}
@@ -129,12 +129,12 @@ internal class ShadowView : View
{
for (int r = Math.Max (0, screen.Y); r < screen.Y + viewport.Height; r++)
{
Driver?.Move (c, r);
Driver.Move (c, r);
SetAttribute (GetAttributeUnderLocation (new (c, r)));
if (Driver?.Contents is { } && screen.X < Driver.Contents.GetLength (1) && r < Driver.Contents.GetLength (0))
if (ScreenContents is { } && screen.X < ScreenContents.GetLength (1) && r < ScreenContents.GetLength (0))
{
Driver.AddRune (Driver.Contents [r, c].Rune);
AddRune (ScreenContents [r, c].Rune);
}
}
}
@@ -151,14 +151,14 @@ internal class ShadowView : View
return Attribute.Default;
}
if (Driver?.Contents == null ||
location.Y < 0 || location.Y >= Driver.Contents.GetLength (0) ||
location.X < 0 || location.X >= Driver.Contents.GetLength (1))
if (ScreenContents == null ||
location.Y < 0 || location.Y >= ScreenContents.GetLength (0) ||
location.X < 0 || location.X >= ScreenContents.GetLength (1))
{
return Attribute.Default;
}
Attribute attr = Driver!.Contents! [location.Y, location.X].Attribute!.Value;
Attribute attr = ScreenContents [location.Y, location.X].Attribute!.Value;
var newAttribute =
new Attribute (

View File

@@ -658,7 +658,7 @@ public partial class View // Drawing APIs
Driver.Move (p.Key.X, p.Key.Y);
// TODO: #2616 - Support combining sequences that don't normalize
Driver.AddRune (p.Value.Value.Rune);
AddRune (p.Value.Value.Rune);
}
}

View File

@@ -130,6 +130,9 @@ public partial class View : IDisposable, ISupportInitializeNotification
set => _driver = value;
}
/// <summary>Gets the screen buffer contents. This is a convenience property for Views that need direct access to the screen buffer.</summary>
protected Cell [,]? ScreenContents => Driver?.Contents;
/// <summary>Initializes a new instance of <see cref="View"/>.</summary>
/// <remarks>
/// <para>

View File

@@ -92,7 +92,7 @@ internal abstract class ColorBar : View, IColorBar
{
Move (0, 0);
SetAttribute (HasFocus ? GetAttributeForRole (VisualRole.Focus) : GetAttributeForRole (VisualRole.Normal));
Driver?.AddStr (Text);
AddStr (Text);
// TODO: is there a better method than this? this is what it is in TableView
xOffset = Text.EnumerateRunes ().Sum (c => c.GetColumns ());

View File

@@ -427,9 +427,9 @@ public class FileDialog : Dialog, IDesignable
Move (0, Viewport.Height / 2);
SetAttribute (new (Color.Red, GetAttributeForRole (VisualRole.Normal).Background));
Driver!.AddStr (new (' ', feedbackPadLeft));
Driver.AddStr (_feedback);
Driver.AddStr (new (' ', feedbackPadRight));
AddStr (new (' ', feedbackPadLeft));
AddStr (_feedback);
AddStr (new (' ', feedbackPadRight));
}
return true;
@@ -507,7 +507,7 @@ public class FileDialog : Dialog, IDesignable
_allowedTypeMenuBar.DrawingContent += (s, e) =>
{
_allowedTypeMenuBar.Move (e.NewViewport.Width - 1, 0);
Driver!.AddRune (Glyphs.DownArrow);
AddRune (Glyphs.DownArrow);
};
Add (_allowedTypeMenuBar);

View File

@@ -213,7 +213,7 @@ public class GraphView : View, IDesignable
for (var i = 0; i < Viewport.Height; i++)
{
Move (0, i);
Driver?.AddStr (new (' ', Viewport.Width));
AddStr (new (' ', Viewport.Width));
}
// If there is no data do not display a graph

View File

@@ -18,7 +18,7 @@ public interface IAnnotation
/// <summary>
/// Called once after series have been rendered (or before if <see cref="BeforeSeries"/> is true). Use
/// <see cref="View.Driver"/> to draw and <see cref="View.Viewport"/> to avoid drawing outside of graph
/// methods like <see cref="View.AddStr(string)"/> and <see cref="View.AddRune(Rune)"/> to draw. Use <see cref="View.Viewport"/> to avoid drawing outside of graph.
/// </summary>
/// <param name="graph"></param>
void Render (GraphView graph);

View File

@@ -68,11 +68,11 @@ public class TextAnnotation : IAnnotation
if (Text.Length < availableWidth)
{
graph.Driver?.AddStr (Text);
graph.AddStr (Text);
}
else
{
graph.Driver?.AddStr (Text.Substring (0, availableWidth));
graph.AddStr (Text.Substring (0, availableWidth));
}
}
}

View File

@@ -750,7 +750,7 @@ public class ListView : View, IDesignable
{
for (var c = 0; c < f.Width; c++)
{
Driver?.AddRune ((Rune)' ');
AddRune ((Rune)' ');
}
}
else
@@ -766,11 +766,11 @@ public class ListView : View, IDesignable
if (_allowsMarking)
{
Driver?.AddRune (
AddRune (
_source.IsMarked (item) ? AllowsMultipleSelection ? Glyphs.CheckStateChecked : Glyphs.Selected :
AllowsMultipleSelection ? Glyphs.CheckStateUnChecked : Glyphs.UnSelected
);
Driver?.AddRune ((Rune)' ');
AddRune ((Rune)' ');
}
Source.Render (this, isSelected, item, col, row, f.Width - col, start);

View File

@@ -847,7 +847,7 @@ internal sealed class Menu : View
continue;
}
if (ViewportToScreen (Viewport).Y + i >= Driver.Rows)
if (ViewportToScreen (Viewport).Y + i >= Application.Screen.Height)
{
break;
}
@@ -863,11 +863,10 @@ internal sealed class Menu : View
if (item is null && BorderStyle != LineStyle.None)
{
Point s = ViewportToScreen (new Point (-1, i));
Driver.Move (s.X, s.Y);
Driver.AddRune (Glyphs.LeftTee);
Move (-1, i);
AddRune (Glyphs.LeftTee);
}
else if (Frame.X < Driver.Cols)
else if (Frame.X < Application.Screen.Width)
{
Move (0, i);
}
@@ -882,28 +881,28 @@ internal sealed class Menu : View
continue;
}
if (ViewportToScreen (Viewport).X + p >= Driver.Cols)
if (ViewportToScreen (Viewport).X + p >= Application.Screen.Width)
{
break;
}
if (item is null)
{
Driver.AddRune (Glyphs.HLine);
AddRune (Glyphs.HLine);
}
else if (i == 0 && p == 0 && _host.UseSubMenusSingleFrame && item.Parent!.Parent is { })
{
Driver.AddRune (Glyphs.LeftArrow);
AddRune (Glyphs.LeftArrow);
}
// This `- 3` is left border + right border + one row in from right
else if (p == Frame.Width - 3 && _barItems?.SubMenu (_barItems.Children [i]!) is { })
{
Driver.AddRune (Glyphs.RightArrow);
AddRune (Glyphs.RightArrow);
}
else
{
Driver.AddRune ((Rune)' ');
AddRune ((Rune)' ');
}
}
@@ -911,9 +910,8 @@ internal sealed class Menu : View
{
if (BorderStyle != LineStyle.None && SuperView?.Frame.Right - Frame.X > Frame.Width)
{
Point s = ViewportToScreen (new Point (Frame.Width - 2, i));
Driver.Move (s.X, s.Y);
Driver.AddRune (Glyphs.RightTee);
Move (Frame.Width - 2, i);
AddRune (Glyphs.RightTee);
}
continue;
@@ -950,9 +948,9 @@ internal sealed class Menu : View
Point screen = ViewportToScreen (new Point (0, i));
if (screen.X < Driver.Cols)
if (screen.X < Application.Screen.Width)
{
Driver.Move (screen.X + 1, screen.Y);
Move (1, i);
if (!item.IsEnabled ())
{
@@ -991,16 +989,16 @@ internal sealed class Menu : View
int col = Frame.Width - l - 3;
screen = ViewportToScreen (new Point (col, i));
if (screen.X < Driver.Cols)
if (screen.X < Application.Screen.Width)
{
Driver.Move (screen.X, screen.Y);
Driver.AddStr (item.Help);
Move (col, i);
AddStr (item.Help);
// The shortcut tag string
if (!string.IsNullOrEmpty (item.ShortcutTag))
{
Driver.Move (screen.X + l - item.ShortcutTag.GetColumns (), screen.Y);
Driver.AddStr (item.ShortcutTag);
Move (col + l - item.ShortcutTag.GetColumns (), i);
AddStr (item.ShortcutTag);
}
}
}

View File

@@ -697,7 +697,7 @@ public class MenuBar : View, IDesignable
internal Point GetScreenOffset ()
{
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
if (Driver is null)
if (Application.Screen.Height == 0)
{
return Point.Empty;
}

View File

@@ -144,11 +144,11 @@ public class ProgressBar : View, IDesignable
{
if (Array.IndexOf (_activityPos!, i) != -1)
{
Driver?.AddRune (SegmentCharacter);
AddRune (SegmentCharacter);
}
else
{
Driver?.AddRune ((Rune)' ');
AddRune ((Rune)' ');
}
}
}
@@ -159,12 +159,12 @@ public class ProgressBar : View, IDesignable
for (i = 0; (i < mid) & (i < Viewport.Width); i++)
{
Driver?.AddRune (SegmentCharacter);
AddRune (SegmentCharacter);
}
for (; i < Viewport.Width; i++)
{
Driver?.AddRune ((Rune)' ');
AddRune ((Rune)' ');
}
}

View File

@@ -375,7 +375,7 @@ public class RadioGroup : View, IDesignable, IOrientation
string rl = _radioLabels [i];
SetAttribute (GetAttributeForRole (VisualRole.Normal));
Driver?.AddStr ($"{(i == _selected ? Glyphs.Selected : Glyphs.UnSelected)} ");
AddStr ($"{(i == _selected ? Glyphs.Selected : Glyphs.UnSelected)} ");
TextFormatter.FindHotKey (rl, HotKeySpecifier, out int hotPos, out Key hotKey);
if (hotPos != -1 && hotKey != Key.Empty)

View File

@@ -441,13 +441,13 @@ public class Slider<T> : View, IOrientation
private void MoveAndAdd (int x, int y, Rune rune)
{
Move (x, y);
Driver?.AddRune (rune);
AddRune (rune);
}
private void MoveAndAdd (int x, int y, string str)
{
Move (x, y);
Driver?.AddStr (str);
AddStr (str);
}
/// <summary>Sets the dimensions of the Slider to the ideal values.</summary>

View File

@@ -187,7 +187,7 @@ public class SpinnerView : View, IDesignable
if (Sequence is { Length: > 0 } && _currentIdx < Sequence.Length)
{
Move (Viewport.X, Viewport.Y);
Driver?.AddStr (Sequence [_currentIdx]);
AddStr (Sequence [_currentIdx]);
}
}

View File

@@ -943,7 +943,7 @@ public class TableView : View, IDesignable
SetAttribute (GetAttributeForRole (VisualRole.Normal));
//invalidate current row (prevents scrolling around leaving old characters in the frame
Driver?.AddStr (new (' ', Viewport.Width));
AddStr (new (' ', Viewport.Width));
var line = 0;
@@ -1289,12 +1289,11 @@ public class TableView : View, IDesignable
protected virtual void OnSelectedCellChanged (SelectedCellChangedEventArgs args) { SelectedCellChanged?.Invoke (this, args); }
/// <summary>
/// Override to provide custom multi colouring to cells. Use <see cref="View.Driver"/> to with
/// <see cref="IConsoleDriver.AddStr(string)"/>. The driver will already be in the correct place when rendering and
/// you
/// must render the full <paramref name="render"/> or the view will not look right. For simpler provision of color use
/// <see cref="ColumnStyle.ColorGetter"/> For changing the content that is rendered use
/// <see cref="ColumnStyle.RepresentationGetter"/>
/// Override to provide custom multi-coloring to cells. Use methods like <see cref="View.AddStr(string)"/>.
/// The cursor will already be in the correct position when rendering. You must render the full
/// <paramref name="render"/> or the view will not look right. For simpler color provision use
/// <see cref="ColumnStyle.ColorGetter"/>. For changing the content that is rendered use
/// <see cref="ColumnStyle.RepresentationGetter"/>.
/// </summary>
/// <param name="cellAttribute"></param>
/// <param name="render"></param>
@@ -1310,19 +1309,19 @@ public class TableView : View, IDesignable
{
// invert the color of the current cell for the first character
SetAttribute (new (cellAttribute.Foreground, cellAttribute.Background, TextStyle.Reverse));
Driver?.AddRune ((Rune)render [0]);
AddRune ((Rune)render [0]);
if (render.Length > 1)
{
SetAttribute (cellAttribute);
Driver?.AddStr (render.Substring (1));
AddStr (render.Substring (1));
}
}
}
else
{
SetAttribute (cellAttribute);
Driver?.AddStr (render);
AddStr (render);
}
}
@@ -1349,10 +1348,10 @@ public class TableView : View, IDesignable
/// <returns></returns>
internal int GetHeaderHeightIfAny () { return ShouldRenderHeaders () ? GetHeaderHeight () : 0; }
private void AddRuneAt (IConsoleDriver d, int col, int row, Rune ch)
private void AddRuneAt (int col, int row, Rune ch)
{
Move (col, row);
d?.AddRune (ch);
AddRune (ch);
}
/// <summary>
@@ -1534,14 +1533,14 @@ public class TableView : View, IDesignable
/// <param name="width"></param>
private void ClearLine (int row, int width)
{
if (Driver is null)
if (Application.Screen.Height == 0)
{
return;
}
Move (0, row);
SetAttribute (GetAttributeForRole (VisualRole.Normal));
Driver.AddStr (new (' ', width));
AddStr (new (' ', width));
}
private void ClearMultiSelectedRegions (bool keepToggledSelections)
@@ -1734,7 +1733,7 @@ public class TableView : View, IDesignable
}
}
AddRuneAt (Driver, c, row, rune);
AddRuneAt (c, row, rune);
}
}
@@ -1762,7 +1761,7 @@ public class TableView : View, IDesignable
Move (current.X, row);
Driver?.AddStr (TruncateOrPad (colName, colName, current.Width, colStyle));
AddStr (TruncateOrPad (colName, colName, current.Width, colStyle));
if (Style.ExpandLastColumn == false && current.IsVeryLast)
{
@@ -1810,9 +1809,9 @@ public class TableView : View, IDesignable
}
}
if (Driver is { })
if (Application.Screen.Height > 0)
{
AddRuneAt (Driver, c, row, rune);
AddRuneAt (c, row, rune);
}
}
}
@@ -1906,7 +1905,7 @@ public class TableView : View, IDesignable
}
}
AddRuneAt (Driver, c, row, rune);
AddRuneAt (c, row, rune);
}
}
@@ -1934,7 +1933,7 @@ public class TableView : View, IDesignable
}
SetAttribute (attribute.Value);
Driver?.AddStr (new (' ', Viewport.Width));
AddStr (new (' ', Viewport.Width));
// Render cells for each visible header for the current row
for (var i = 0; i < columnsToRender.Length; i++)

View File

@@ -178,7 +178,7 @@ public class TreeTableSource<T> : IEnumerableTableSource<T>, IDisposable where T
Branch<T> branch = RowToBranch (hit.Value.Y);
if (branch.IsHitOnExpandableSymbol (Application.Driver, offsetX.Value))
if (branch.IsHitOnExpandableSymbol (offsetX.Value))
{
T m = branch.Model;

View File

@@ -973,7 +973,7 @@ public class TextField : View, IDesignable
if (col + cols <= width)
{
Driver?.AddRune (Secret ? Glyphs.Dot : rune);
AddRune (Secret ? Glyphs.Dot : rune);
}
if (!TextModel.SetCol (ref col, width, cols))
@@ -992,7 +992,7 @@ public class TextField : View, IDesignable
// Fill rest of line with spaces
for (int i = col; i < width; i++)
{
Driver?.AddRune ((Rune)' ');
AddRune ((Rune)' ');
}
PositionCursor ();
@@ -1717,7 +1717,7 @@ public class TextField : View, IDesignable
render = render [..Viewport.Width];
}
Driver?.AddStr (render);
AddStr (render);
}
private void SetClipboard (IEnumerable<Rune> text)

View File

@@ -177,7 +177,7 @@ public class TextValidateField : View, IDesignable
if (_provider is null)
{
Move (0, 0);
Driver?.AddStr ("Error: ITextValidateProvider not set!");
AddStr ("Error: ITextValidateProvider not set!");
return true;
}
@@ -194,7 +194,7 @@ public class TextValidateField : View, IDesignable
for (var i = 0; i < marginLeft; i++)
{
Driver?.AddRune ((Rune)' ');
AddRune ((Rune)' ');
}
// Content
@@ -203,7 +203,7 @@ public class TextValidateField : View, IDesignable
// Content
for (var i = 0; i < _provider.DisplayText.Length; i++)
{
Driver?.AddRune ((Rune)_provider.DisplayText [i]);
AddRune ((Rune)_provider.DisplayText [i]);
}
// Right Margin
@@ -211,7 +211,7 @@ public class TextValidateField : View, IDesignable
for (var i = 0; i < marginRight; i++)
{
Driver?.AddRune ((Rune)' ');
AddRune ((Rune)' ');
}
return true;

View File

@@ -454,7 +454,7 @@ internal class Branch<T> where T : class
/// <param name="driver"></param>
/// <param name="x"></param>
/// <returns></returns>
internal bool IsHitOnExpandableSymbol (IConsoleDriver driver, int x)
internal bool IsHitOnExpandableSymbol (int x)
{
// if leaf node then we cannot expand
if (!CanExpand ())

View File

@@ -1065,7 +1065,7 @@ public class TreeView<T> : View, ITreeView where T : class
return false;
}
bool isExpandToggleAttempt = clickedBranch.IsHitOnExpandableSymbol (Driver, me.Position.X);
bool isExpandToggleAttempt = clickedBranch.IsHitOnExpandableSymbol (me.Position.X);
// If we are already selected (double click)
if (Equals (SelectedObject, clickedBranch.Model))
@@ -1157,7 +1157,7 @@ public class TreeView<T> : View, ITreeView where T : class
if (TreeBuilder is null)
{
Move (0, 0);
Driver?.AddStr (NoBuilderError);
AddStr (NoBuilderError);
return true;
}
@@ -1179,7 +1179,7 @@ public class TreeView<T> : View, ITreeView where T : class
// Else clear the line to prevent stale symbols due to scrolling etc
Move (0, line);
SetAttribute (GetAttributeForRole (VisualRole.Normal));
Driver?.AddStr (new (' ', Viewport.Width));
AddStr (new (' ', Viewport.Width));
}
}

View File

@@ -36,6 +36,8 @@ See the [Layout Deep Dive](layout.md) and the [Arrangement Deep Dive](arrangemen
See the [Drawing Deep Dive](drawing.md).
Views should use viewport-relative coordinates for all drawing operations. The `View.Move(col, row)` method positions the cursor using viewport-relative coordinates. For screen dimensions, use @Terminal.Gui.App.Application.Screen instead of accessing the driver directly.
### Navigation
See the [Navigation Deep Dive](navigation.md).

View File

@@ -151,7 +151,7 @@ When `Application.Shutdown()` is called:
### IConsoleDriver
The main driver interface that applications interact with. Provides:
The main driver interface that the framework uses internally. Provides:
- **Screen Management**: `Screen`, `Cols`, `Rows`, `Contents`
- **Drawing Operations**: `AddRune()`, `AddStr()`, `Move()`, `FillRect()`
@@ -161,6 +161,12 @@ The main driver interface that applications interact with. Provides:
- **Events**: `KeyDown`, `KeyUp`, `MouseEvent`, `SizeChanged`
- **Platform Features**: `SupportsTrueColor`, `Force16Colors`, `Clipboard`
**Note:** The driver is internal to Terminal.Gui. View classes should not access `Driver` directly. Instead:
- Use @Terminal.Gui.App.Application.Screen to get screen dimensions
- Use @Terminal.Gui.ViewBase.View.Move for positioning (with viewport-relative coordinates)
- Use @Terminal.Gui.ViewBase.View.AddRune and @Terminal.Gui.ViewBase.View.AddStr for drawing
- ViewBase infrastructure classes (in `Terminal.Gui/ViewBase/`) can access Driver when needed for framework implementation
### IConsoleDriverFacade
Extended interface for v2 drivers that exposes the internal components:
@@ -215,18 +221,23 @@ This ensures Terminal.Gui applications can be debugged directly in Visual Studio
- Captures output for verification
- Always used when `Application._forceFakeConsole` is true
## Example: Accessing Driver Components
## Example: Checking Driver Capabilities
```csharp
Application.Init();
// Access the driver
IConsoleDriver driver = Application.Driver;
// The driver is internal - access through Application properties
// Check screen dimensions
var screenWidth = Application.Screen.Width;
var screenHeight = Application.Screen.Height;
// Check if it's a v2 driver with facade
if (driver is IConsoleDriverFacade facade)
// Check if 24-bit color is supported
bool supportsTrueColor = Application.Driver?.SupportsTrueColor ?? false;
// Access advanced components (for framework/infrastructure code only)
if (Application.Driver is IConsoleDriverFacade facade)
{
// Access individual components
// Access individual components for advanced scenarios
IInputProcessor inputProcessor = facade.InputProcessor;
IOutputBuffer outputBuffer = facade.OutputBuffer;
IWindowSizeMonitor sizeMonitor = facade.WindowSizeMonitor;
@@ -239,6 +250,11 @@ if (driver is IConsoleDriverFacade facade)
}
```
**Important:** View subclasses should not access `Application.Driver`. Use the View APIs instead:
- `View.Move(col, row)` for positioning
- `View.AddRune()` and `View.AddStr()` for drawing
- `Application.Screen` for screen dimensions
## Custom Drivers
To create a custom driver, implement `IComponentFactory<T>`:

View File

@@ -225,7 +225,7 @@ The cursor and focus system has been redesigned in v2 to be more consistent and
### Cursor
In v1, whether the cursor (the flashing caret) was visible or not was controlled by `View.CursorVisibility` which was an enum extracted from Ncruses/Terminfo. It only works in some cases on Linux, and only partially with `WindowsDriver`. The position of the cursor was the same as `ConsoleDriver.Row`/`Col` and determined by the last call to `ConsoleDriver.Move`. `View.PositionCursor()` could be overridden by views to cause `Application` to call `ConsoleDriver.Move` on behalf of the app and to manage setting `CursorVisibility`. This API was confusing and bug-prone.
In v1, whether the cursor (the flashing caret) was visible or not was controlled by `View.CursorVisibility` which was an enum extracted from Ncruses/Terminfo. It only works in some cases on Linux, and only partially with `WindowsDriver`. The position of the cursor was determined by the last call to the driver's Move method. `View.PositionCursor()` could be overridden by views to cause `Application` to call the driver's positioning method on behalf of the app and to manage setting `CursorVisibility`. This API was confusing and bug-prone.
In v2, the API is (NOT YET IMPLEMENTED) simplified. A view simply reports the style of cursor it wants and the Viewport-relative location:
@@ -237,7 +237,7 @@ In v2, the API is (NOT YET IMPLEMENTED) simplified. A view simply reports the st
- If `null` the default cursor style is used.
- If `{}` specifies the style of cursor. See [cursor.md](cursor.md) for more.
* `Application` now has APIs for querying available cursor styles.
* The details in `ConsoleDriver` are no longer available to applications.
* The driver details are no longer directly accessible to View subclasses.
#### How to Fix (Cursor API)
@@ -245,6 +245,26 @@ In v2, the API is (NOT YET IMPLEMENTED) simplified. A view simply reports the st
* Set @Terminal.Gui.ViewBase.View.CursorVisibility to the cursor style you want to use.
* Remove any overrides of `OnEnter` and `OnLeave` that explicitly change the cursor.
### Driver Access
In v1, Views could access `Driver` directly (e.g., `Driver.Move()`, `Driver.Rows`, `Driver.Cols`). In v2, `Driver` is internal and View subclasses should not access it directly. ViewBase provides all necessary abstractions for Views to function without needing direct driver access.
#### How to Fix (Driver Access)
* Replace `Driver.Rows` and `Driver.Cols` with @Terminal.Gui.App.Application.Screen.Height and @Terminal.Gui.App.Application.Screen.Width
* Replace direct `Driver.Move(screenX, screenY)` calls with @Terminal.Gui.ViewBase.View.Move using viewport-relative coordinates
* Use @Terminal.Gui.ViewBase.View.AddRune and @Terminal.Gui.ViewBase.View.AddStr for drawing
* ViewBase infrastructure classes (in `Terminal.Gui/ViewBase/`) can still access Driver for framework implementation needs
```diff
- if (x >= Driver.Cols) return;
+ if (x >= Application.Screen.Width) return;
- Point screenPos = ViewportToScreen(new Point(col, row));
- Driver.Move(screenPos.X, screenPos.Y);
+ Move(col, row); // Move handles viewport-to-screen conversion
```
### Focus
See [navigation.md](navigation.md) for more details.