Terminal.Gui icon indicating copy to clipboard operation
Terminal.Gui copied to clipboard

Proposal: Focus/Navigation Changes - e.g. Remove `Tabindex` and `TabIndexes` in v2

Open tig opened this issue 1 year ago • 8 comments

Remove Tabindex and TabIndexes in v2

Why?

  • I can't find any real uses of it outside of the DynamicMenubar scenario and a simple usage in FileDialog.
  • I've done a code search of terminal.gui/gui-cs projects and can't find any examples of where it's used.
  • TGD doesn't support it https://github.com/gui-cs/TerminalGuiDesigner/issues/79
  • It has a ton of complexity and is a source of bugs. The current implementation is full of bugs. I've added a bunch of unit tests in #3627 and fixed some of the bugs, but ...
  • The same concept has been deprecated from Maui: https://github.com/dotnet/maui/issues/1646

If there are no objections, I am going to remove View.TabIndex and View.TabIndexes from Terminal.Gui v2 in #3627.

Questions

Q: What if I want to set the tab order? A: Simply re-order Subviews.

  • Side note: Dialog currently has a bug where in most typical use cases where Views are added with dlg.Add after Buttons has been set, the tab order is wrong. The buttons should always be the last items in the order, but are first. I plan on fixing this independently of whether TabIndexexists or not by overridingAddto always ensure theButtons` are at the end of the subview List.

Q: Web frameworks have the concept. E.g. React. Are you sure you want to remove it? A: Yes, the same issues that plagued the original concept in Win32, then Winforms, then Xamarin, that are outlined in the Maui issue above apply to jQuery, React, etc...

Q: What about TabStop? A: The need indicates how a View behaves in keyboard navigation is real. I've already changed TabStop to be of type TabBehavior and I think that is still needed.

tig avatar Aug 18 '24 21:08 tig

No objections. Normally when we create an app using var sometimes we need to initialize a view to being referenced by another view, but we want the later view added to be tabbed first. But if we use private fields for views at class scope this isn't an issue.

BDisp avatar Aug 18 '24 21:08 BDisp

Works for me!

tznind avatar Aug 18 '24 22:08 tznind

Rename Leave to Blur

Why?

I wrote this (in the new navigation.md doc) and after doing so was convinced we can do better:

Lexicon & Taxonomy

  • Navigation refers to the user-experience for moving Focus between views in the application view-hierarchy.
  • Focus - Refers to the state where a particular UI element (View), such as a button, input field, or any interactive component, is actively selected and ready to receive user input. When an element has focus, it typically responds to keyboard events and other interactions.
  • Focus Chain - The ordered sequence of UI elements that can receive focus, starting from the currently focused element and extending to its parent (SuperView) elements up to the root of the focus tree (Application.Top). This chain determines the path that focus traversal follows within the application. Only one focus chain in an application can have focus (top.HasFocus == true), and there is one, and only one, View in a focus chain that is the most-focused; the one receiving keyboard input.
  • Navigation - Refers to the user-experience for moving Focus between views in focus chain.
  • Cursor - A visual indicator to the user where keyboard input will have an impact. There is one Cursor per terminal session. See Cursor for a deep-dive.
  • Focus Ordering - The order focusable Views are navigated. Focus Ordering is typically used in UI frameworks to enable screen readers and improve the Accessibility of an application. In v1, TabIndex/TabIndexes enabled Focus Ordering.
  • Tab - Describes the Tab key found on all keyboards, a break in text that is wider than a space, or a UI element that is a stop-point for keyboard navigation. The use of the word "Tab" for this comes from the typewriter, and is re-enforced by the existence of a Tab key on all keyboards.
  • TabStop - A View that is an ultimate stop-point for keyboard navigation. In this usage, ultimate means the View has no focusable subviews. The Application.NextTabStopKey and Application.PrevTabStopKey are Key.Tab and Key.Tab.WithShift respectively. These keys navigate only between peer-views.
  • TabGroup - A View that is a container for other focusable views. The Application.NextTabGroupKey and Application.PrevTabGroupKey are Key.PageDown.WithCtrl and Key.PageUp.WithCtrl respectively. These keys enable the user to use the keyboard to navigate up and down the view-hierarchy.
  • Enter / Gain - Means a View that previously was not focused is now becoming focused. "The View is entering focus" is the same as "The View is gaining focus".
  • Leave / Lose - Means a View that previously was focused is now becoming un-focused. "The View is leaving focus" is the same as "The View is losing focus".

I did a bunch of research and other frameworks either:

  • Avoid verbs for "leave". E.g. SwiftUI just uses a focused(_:) modifieer. Simliarly for Flutter.
  • Use Blur. E.g. jQuery, React, etc...

We don't have a programming model that easily enables the former. So I want to swtich the the later.

Thoughts?

tig avatar Aug 18 '24 22:08 tig

I guess an alternative that would just avoid "leave"/"lost"/"blur" is to

  • Merge OnEnter/Enter/OnLeave/Leave into OnHasFocusChanging/HJasFocusChanging and OnHasFocusChanged/HasFocusChanged

hmmm....

tig avatar Aug 18 '24 22:08 tig

Not a big fan of Blur as a word. To me that just sounds like someone heard Focus and thought of cameras lenses. To me a View gains and looses a thing. Or users input enters and leaves a view.

I don't mind the idea of a single event for focus change. And it's got the word focus in it so it won't be hard for api user to find

tznind avatar Aug 18 '24 22:08 tznind

Not a big fan of Blur as a word. To me that just sounds like someone heard Focus and thought of cameras lenses. To me a View gains and looses a thing. Or users input enters and leaves a view.

I don't mind the idea of a single event for focus change. And it's got the word focus in it so it won't be hard for api user to find

Ok, here's what I'd write in the migration guide:

  • In v1, the View.OnEnter/Enter and View.OnLeave/Leave virtual methods/events could be used to notify that a view had gained or lost focus, but had confusing semantics around what it mean to override (requiring calling base) and bug-ridden behavior on what the return values signified. The "Enter" and "Leave" terminology was confusing. In v2, View.OnHasFocusChanging/HasFocusChanging and View.OnHasFocusChanged/HasFocusChanged replace View.OnEnter/Enter and View.OnLeave/Leave. These virtual methods/events follow standard Terminal.Gui event patterns. The View.OnHasFocusChanging/HasFocusChanging event supports being cancelled.

tig avatar Aug 18 '24 22:08 tig

Just to alert that the Application.PrevTabStopKey which is the Key.Tab.WithShift won't work on Linux and that why it has an alternate key. Don't work on the TextView .

BDisp avatar Aug 18 '24 22:08 BDisp

Just to alert that the Application.PrevTabStopKey which is the Key.Tab.WithShift won't work on Linux and that why it has an alternate key. Don't work on the TextView .

Yep. Current migratingfrom v1.md in #3627 says the following. And this is all implemented in that PR:


In v1, the keys used for navigation were both hard-coded and configurable, but in an inconsistent way. Tab and Shift+Tab worked consistently for navigating between Subviews, but were not configurable. Ctrl+Tab and Ctrl+Shift+Tab navigated across Overlapped views and had configurable "alternate" versions (Ctrl+PageDown and Ctrl+PageUp).

In v2, this is made consistent and configurable:

  • Application.NextTabStopKey (Key.Tab) - Navigates to the next subview that is a TabStop (see below). If there is no next, the first subview that is a TabStop will gain focus.
  • Application.PrevTabStopKey (Key.Tab.WithShift) - Opposite of Application.NextTabStopKey.
  • Key.CursorRight - Operates identically to Application.NextTabStopKey.
  • Key.CursorDown - Operates identically to Application.NextTabStopKey.
  • Key.CursorLeft - Operates identically to Application.PrevTabStopKey.
  • Key.CursorUp - Operates identically to Application.PrevTabStopKey.
  • Application.NextTabGroupKey (Key.F6) - Navigates to the next view in the view-hierarchy that is a TabGroup (see below). If there is no next, the first view that is a TabGroup will gain focus.
  • Application.PrevTabGroupKey (Key.F6.WithShift) - Opposite of Application.NextTabGroupKey.

F6 was chosen to match Windows

These keys are all registered as KeyBindingScope.Application key bindings by Application. Because application-scoped key bindings have the lowest priority, Views can override the behaviors of these keys (e.g. TextView overrides Key.Tab by default, enabling the user to enter \t into text). The AllViews_AtLeastOneNavKey_Leaves unit test ensures all built-in Views have at least one of the above keys that can advance.

tig avatar Aug 18 '24 23:08 tig