~eliasnaur/gio#195: 
How widget focus should work

We had an extensive conversation about widget focus in this ML thread, but we didn't come to any conclusions. This issue exists to be a more searchable and permanent home for the discussion around widget focus and the transfer of focus between widgets.

Status
REPORTED
Submitter
~whereswaldon
Assigned to
No-one
Submitted
3 months ago
Updated
6 days ago
Labels
No labels applied.

~eliasnaur 3 months ago

Continuing the discussion from the mail thread:

Another issue is tabbing through focusables. How do we assign the tabbing order? Assigning order according to draw order works for pointer handlers, but not so well for tab order. An idea is to add a key.Area (or rename pointer.Area to input.Area) and assign tabbing order according to a left-to-right order, starting from the top-left corner.

                                                                                                                                                                                                                                                                         

I worry about using a heuristic to define tab order unless we provide a way to override.

To be clear, I think a way to override the default tab order is a good idea. I only intended to determine that default order.

In particular, there's both RTL languages to consider

RTL should be automatic as well, for example by flipping the horizontal axis.

~whereswaldon 2 months ago

To be clear, I think a way to override the default tab order is a good idea. I only intended to determine that default order.

Okay, then I think using the heuristic that you described as a default is fantastic!

Now for the precise mechanism of overriding it... it feels like this circles all the way back to z-indicies (in a sense). I can't think of any approach better than a way to define the tab order "priority" of a given input, and I think priority must be expressed as something like an integer, as the user must be able to define granular ordering for potentially hundreds of visible input widgets. Of course, this will definitely lead to priority 9000 widgets (choosing priorities that are ridiculously high to ensure they're "first").

I tried to think of a more elegant option like what you did for DeferOp, but I don't think that layout order is fundamentally related to tabbing order, so I'm not sure that's viable. Do you see any other mechanism for this?

I briefly tried to envision a constraint system where you express "this input should be after input with tag X", but it got too complicated too fast. Deciding how to resolve inputs where no relation to other inputs was provided is difficult, and likely to be wrong.

To sum up: I am not creative enough today to come up with anything beyond an integer "priority" that we sort inputs by for tabbing order, but I'd be happy for that to be replaced with something better.

~eliasnaur 2 months ago

It sometimes helps my thinking to come up with a number of use-cases and try to infer patterns from them.

If you agree that the right-to-left case can be covered by the area heuristic, you list one other:

for instance, if your confirm button is to the right of the cancel button, so you want to select it first to optimize for the happy path.

I'm not sure this is a strong use-case for explicit tab ordering, for two reasons. First, you can optimize form submission better by binding the enter key to the submit button. Second, you may win some convenience by tabbing to the submit button before cancel, but you lose a bit of predictability; the user's muscle memory may "over-tab" by one tab, thinking that they needed two tabs to reach the submit button, not one.

Can you think of other use-cases?

~whereswaldon 2 months ago*

I'm not sure this is a strong use-case for explicit tab ordering, for two reasons. First, you can optimize form submission better by binding the enter key to the submit button. Second, you may win some convenience by tabbing to the submit button before cancel, but you lose a bit of predictability; the user's muscle memory may "over-tab" by one tab, thinking that they needed two tabs to reach the submit button, not one.

Honestly, I came up with this purely because I recently encountered this behavior in GitHub's UI. It actually tripped me up (I over-tabbed), but it made me wonder whether this approach is more accessible. Something to research, I suppose?

As for other use-cases, the only thing I can think of is if you want to iterate focus across different regions of the screen. Imagine that the screen is split into two vertical panels and you want the tab order to stay in the left panel until you reach the last input on the left. Or, similarly, you can split the screen horizontally and want focus to stay in the top pane before transferring to the bottom.

We could probably address this by allowing the creation of "regions" through which we iterate focus. The focus could be a tree of areas traversed in a depth-first order.

~eliasnaur 2 months ago

On Sun Feb 7, 2021 at 16:44, ~whereswaldon wrote:

As for other use-cases, the only thing I can think of is if you want to iterate focus across different regions of the screen. Imagine that the screen is split into two vertical panels and you want the tab order to stay in the left panel until you reach the last input on the left. Or, similarly, you can split the screen horizontally and want focus to stay in the top pane before transferring to the bottom.

We could probably address this by allowing the creation of "regions" within which we iterate focus though. The focus could be a tree of areas traversed in a depth-first order.

Good use-case. Focus groups sound good to me.

Elias

~paulomelo 2 months ago

Perhaps I need to read a bit more, but if I may add my two cents... Gio is and immediate mode gui. It should work on several OS combinations (as it is doing), so we have native, wasm through canvas and small devices. Some are touch only devices, other have keyboards and others have only a couple of keys (Iot devices). Windows has a tabindex+tabstop properties, HTML has a tabindex.

So resuming what was discussed between ~eliasnaur and ~whereswaldon I believe you are proposing the setting of a focusgroup and a tabindex property. Am I right?

Paulo

P.S. - sorry, but I'm new to go and gio, but I would like to help ;)

~whereswaldon 2 months ago

~paulomelo Thanks for sharing your thoughts!

We had a long and involved discussion here about how to do overlays and z-indexing, and we ultimately tried to avoid using an indexing strategy. It leads to a kind of "guesswork" where you specify index 90000 in order to try to be the highest, but some other dependency is doing the same thing using 9000000000 and wins.

I think we'd similarly like to avoid tabindex itself. Instead, we'd like to divide the window into focus groups (a tree of them, with the default being a single one at the window scope), and have the order of the focus groups depend on their positioning on the screen. For LTR text, we'd work from left to right, top to bottom. For RTL text, we'd work from right to left, top to bottom. When the next thing is a nested focus group, we iterate the widgets inside of that group before continuing to traverse the space outside of it.

This doesn't really provide a mechanism to override the spatial ordering beyond grouping. I think we'd consider something like that if presented with a compelling use-case, but so far we don't have any.

Welcome to the go and gio communities! We're glad to have you.

~whereswaldon referenced this from #222 6 days ago

~stalker_loki 6 days ago*

I thought further about what I wrote in this post previously and decided that it was horribly cumbersome. This is yet another example where the fluent programming 'Domain Specific Language' has massive advantages. By wrapping all of the creation of objects in function calls you gain the ability to generate secondary data structures that automate this process of generating the tree structures.

Simply embedding your tab movement type into all 'tabbable' widgets, and have these widgets embed to a central variable storing the tab movement type's state, you can have all of the necessary data implemented without having to specify a thing, except to disrupt the intrinsic tree structure (and thus grouping).

You can then make short and simple flag-functions that flip values to do such things as specifying groups, and their starting points, observe their visibility properties, and all sorts of other things that I probably haven't thought of.

It's for this reason I have tenaciously adhered to this grammar, and why I have been slowly developing the architecture for an entire development environment that builds upon this capability.

The thing that was most frustrating for me in the beginning with Gio was the opacity of widgets. DSL solved the problem by allowing a state variable to cut across arbitrary branches of trees.

Register here or Log in to comment, or comment via email.