See end for list of issues this design addresses.
More GUI than Command Line. The concept of a cursor on the command line of a terminal is intrinsically tied to enabling the user to know where keybaord import is going to impact text editing. TUI apps have many more modalities than text editing where the keyboard is used (e.g. scrolling through a ColorPicker
). Terminal.Gui's cursor system is biased towards the broader TUI experiences.
Be Consistent With the User's Platform - Users get to choose the platform they run Terminal.Gui apps on and the cursor should behave in a way consistent with the terminal.
ListView
the Cursor and Selection (SelectedItem
) are the same, but the Cursor
is not visible. In a TextView
with text selected, the Cursor
is at either the start or end of the Selection
. A `TableView' supports mutliple things being selected at once.Application.CursorStyle
.this.CursorPosition
.this.CursorPostion
to null
.Enabled == true
Visible == true
CanFocus == true
this == SuperView.MostFocused
ConsoleDriver
supports Cursor Styles other than Default, they should be supported per-application (NOT View).Application
, not View
.Driver.
API. Only Application
and the View
base class should call ConsoleDriver
APIs; before we ship v2, all ConsoleDriver
APIs will be made internal
.View
Focus ChangesIt doesn't make sense the every View instance has it's own notion of MostFocused
. The current implemention is overly complicated and fragile because the concept of "MostFocused" is handled by View
. There can be only ONE "most focused" view in an application. MostFocused
should be a property on Application
.
View.MostFocused
Application.MostFocusedView
(see Application
below)view._hasFocus =
and change them to use SetHasFocus
(today, anyplace that sets _hasFocus
is a BUG!!).SetFocus
/SetHasFocus
etc... such that if the focus is changed to a different view heirarchy, Application.MostFocusedView
gets set appropriately.MORE THOUGHT REQUIRED HERE - There be dragons given how Toplevel
has OnEnter/OnLeave
overrrides. The above needs more study, but is directioally correct.
View
Cursor Changespublic Point? CursorPosition
private Point? _cursorPosition
!HasValue
the cursor is not visibleHasValue
the cursor is visible at the Point.value != _cursorPosition
, call OnCursorPositionChanged()
public event EventHandler<LocaitonChangedEventArgs>? CursorPositionChanged
internal void OnCursorPositionChanged(LocationChangedEventArgs a)
CursorPositionChanged
ConsoleDriver
sRemove Refresh
and have UpdateScreen
and UpdateCursor
be called separately. The fact that Refresh
in all drivers currently calls both is a source of flicker.
Remove the xxxCursorVisibility
APIs and replace with:
internal int CursorStyle {get; internal set; }
private int _cursorStyle
OnCursorStyleChanged()
internal abstract void OnCursorStyleChanged()
Called by base
whenever the cursor style changes, but ONLY if value != _cursorStyle
.
Add internal virtual (int Id, string StyleName) [] GetCursorStyles()
Returns an array of styles supported by the driver, NOT including Invisible.
The first item in array is always "Default".
Base implementation returns { 0, "Default" }
CursesDriver
and WindowsDriver
will need to implement overrides.
Add internal Point? CursorPosition {get; internal set; }
Backed with private Point? _cursorPosition
If !HasValue
the cursor is not visible
If HasValue
the cursor is visible at the Point.
On set, calls OnCursorPositionChanged
ONLY if value != _cursorPosition
.
Add internal abstract void OnCursorPositionChanged()
Called by base
whenever the cursor position changes.
Depending on the value of CursorPosition
:
!HasValue
the cursor is not visible - does whatever is needed to make the cursor invisible.HasValue
the cursor is visible at the CursorPosition
- does whatever is needed to make the cursor visible (using CursorStyle
).Make sure the drivers only make the cursor visible (or leave it visible) when CursorPosition
changes!
Application
Add internal static View FocusedView {get; private set;}
private static _focusedView
value != _focusedView
_focusedView.CursorPositionChanged
value.CursorPositionChanged += CursorPositionChanged
_focusedView = value
UpdateCursor
Add internal bool CursorPositionChanged (object sender, LocationChangedEventArgs a)
Called when:
FocusedView
FocusedView.Visible/Enable
changes)CursorPosition
CursorStyle
has changedDoes:
FocusedView is {}
and FocusedView.CursorPosition
is visible (e.g. w/in FocusedView.SuperView.Viewport
)
Driver.CursorPosition = ToScreen(FocusedView.CursorPosition)
Driver.CursorPosition = null
Add public static int CursorStyle {get; internal set; }
value != _cursorStyle
ConsoleDriver.CursorStyle = _cursorStyle
UpdateCursor
Add public (int Id, string StyleName) [] GetCursorStyles()
ConsoleDriver.GetCursorStyles()
Driver.Row/Pos
, which are changed via Move
serves two purposes that confuse each other:a) Where the next AddRune
will put the next rune
b) The current "Cursor Location"
If most TUI apps acted like a command line where the visible cursor was always visible, this might make sense. But the fact that only a very few View subclasses we've seen actually care to show the cursor illustrates a problem:
Any drawing causes the "Cursor Position" to be changed/lost. This means we have a ton of code that is constantly repositioning the cursor every MainLoop iteration.
Mainloop.Iteration
).Derived from above, the current design means we need to call
View.PositionCursor` every iteration. For some views this is a low-cost operation. For others it involves a lot of math.
This is just stupid.
Related to the above, we need constantly Show/Hide the cursor every iteration. This causes ridiculous cursor flicker.
View.PositionCursor
is poorly spec'd and confusing to implement correctlyShould a view call base.PositionCursor
? If so, before or after doing stuff?
OnEnter
actually makes no senseFirst, leaving it up to views to do this is fragile.
Second, when a View gets focus is but one of many places where cursor visibilty should be updated.