I hereby want to propose to make keyboard controls a first class citizen in the design of Fyne and especially its widgets.
Some other tickets already talk about accessibility (#1093 and #1285), so I think thinking about inputs is a very important step in that direction. There are multiple disabilities that make it hard to properly navigate a mouse or touchpad, and even with voice control (or joysticks) it is easier to give directional commands (up, down, left, right, next, back) than moving a cursor around.
A widget is already the base for every active component of the application. It can therefore be assumed, that each widget will in some way deal with user input (clicking, scrolling, entering content, etc.).
My proposal would therefore be, to implement the Keyable interface on the base Widget. (Bonus: expose OnKeyDown, OnKeyUp, OnKeyTyped etc. callbacks for custom code outside the widget (for the user of the widget; not for a sub-"class").)
The FocusManager (#1294) can likely deal with the tab order and deal with switching focus.
For example a text control with formatting might want to use tab and shift+tab to actually indent or unindent text. For these cases, it must be possible to differentiate between "edit" and "navigate". Giving such a textbox focus, does not enter edit mode. Pressing enter would enter edit mode. Then tab/shift+tab are no longer used for navigation, but are propagated to the widget for use. Hitting escape will leave edit mode and switch back to navigiation mode. The widget is still highlighted/focused (so pressing enter would go back into edit mode, but tab/shift+tab would now navigate between controls).
Therefore it should be possible to mark a widget as FocusTrappable (or something like that). In this case, the FocusManager allows hitting "enter" to enter said trap. Afterwards tab and shift+tab are propagated to this widget (while it has focus). Esc(ape) however is handled by the FocusManager to take that exclusive/trap mode away again.
Focusing a trappable widget via mouse or finger tap whould immediately enter trapped mode.
The more widgets Fyne has, the more will have to be reworked to get a proper input flow. Given the current recommendation on how to extend widgets (implementing input interfaces yourself) would likely break when implementing those same interfaces on the base widget already. So the sooner this functionality moves to the core, the less trouble will be encountered later.
Also it's easier to design a widget with keyboard control in mind, than reworking existing widgets to be keyboard controlled.
This is also why I propose to implement the Keyable interface on Widget, so every widget implementation knows it has to deal with this. During design.
I agree with the general idea behind this ticket, we should have better keyboard support - and we are working on it.
I have updated your list of items to be check boxes, to show which are already complete (as far as I am aware). Select is almost done as well, but needs some further work on menus to be complete.
implement the Keyable interface on the base Widget
I don't think this is the right approach, not every widget will provide key controls and adding to the list of features that someome will have to implement just to get a widget working will get in the way of the ease of development that we are aiming for.
Bonus: expose OnKeyDown, OnKeyUp, OnKeyTyped etc. callbacks for custom code
I'm not sure why this is desirable? Making it easy for developers to override the standard keyboard controls seems counter to the desired outcome of this ticket.
Giving such a textbox focus, does not enter edit mode. Pressing enter would enter edit mode.
I think this sounds counter-intuitive... Most toolkits I have experienced make an input editable as soon as you tab into it.
Hitting any key combination to make it editable would be cognitive overhead and a behaviour we would have to teach. Slightly better than this would be to have a shortcut to exit the "trap" widget - I think macOS for example uses Cmd-Tab.
Also it's easier to design a widget with keyboard control in mind, than reworking existing widgets to be keyboard controlled.
It seems that this statement is probably true, but I don't think it's sufficient to break every existing widget to force keyboard controls to be added. Is adding the overhead to every new widget the right thing to do when we are trying to make it easy to get started with?
I will answer in a slightly different order since I guess one answer might benefit the next :smile:
Bonus: expose OnKeyDown, OnKeyUp, OnKeyTyped etc. callbacks for custom code
I'm not sure why this is desirable? Making it easy for developers to override the standard keyboard controls seems counter to the desired outcome of this ticket.
I was thinking of something like this (kinda-pseudo-code, since I don't have my IDE at hand):
myEntry := widget.NewEntry()
myEntry.SetOnKeyTyped(func(event *fyne.KeyEvent) bool {
if event.KeyName == fyne.KeyA {
// do something special
return true // but let the widget continue handling it
} else if event.KeyName == fyne.KeyEscape {
// do something else
return false // stop processing the event higher up; we handled it fully
}
return true // by default, let the widget continue
})
This would basically allow intercepting events or handling them without having to "override" anything.
If the event handler is not present at all, that's also fine.
Alternatively it could be designed to work like most middleware http handler for Go's http handlerfuncs are designed, which pass the "next func(...)" as parameter so the handler can decide if it should call the next in the chain or not. That would also open up a nice way to stack / register multiple handlers/listeners.
implement the
Keyableinterface on the baseWidgetI don't think this is the right approach, not every widget will provide key controls and adding to the list of features that someome will have to implement just to get a widget working will get in the way of the ease of development that we are aiming for.
The base widget could always implement some handler. Even if it does nothing. It would probably make sense to propagate the event further then. If the entry doesn't handle it, maybe it's parent widget is interested. If that isn't ... maybe the window is interested. That way an event would always hit the focused component and can be passed down to the lowest level control, but could also be intercepted at any time.
With the aforementioned approach to allow "stop" processing or "continue" processing, this opens many flexible ways to write interactive widgets and reusing existing widgets. IMHO easier than the current approach.
Also: at least in regards to focus, at least every widget will have to be focusable. Even a label, since otherwise a screenreader wouldn't be able to tell where you are and what you want to read. But anyway, it might also be an option to separate between Widget (any complex rendered thing) and Control (or InteractiveWidget) that also implements/offers key/tap/input/etc. handling. A label would be Widget, an entry would be a Control or InteractiveWidget.
Giving such a textbox focus, does not enter edit mode. Pressing enter would enter edit mode.
I think this sounds counter-intuitive... Most toolkits I have experienced make an input editable as soon as you tab into it.
Hitting any key combination to make it editable would be cognitive overhead and a behaviour we would have to teach. Slightly better than this would be to have a shortcut to exit the "trap" widget - I think macOS for example uses Cmd-Tab.
For normal entries (simple text inputs, drop down boxes, etc.) yes. For complex controls (think the richtext area of Word) this differs between applications. But true, I don't think there's a "standard".
This would basically allow intercepting events or handling them without having to "override" anything.
If the event handler is not present at all, that's also fine.
Our design is to keep things simple whilst making it possible to do more advanced things like you illustrate.
The code above is entirely possible by extending the widget and overriding - given that changing keyboard behaviour of widgets is not common on most I don't think we should change the design to accomodate the less frequent case.
The base widget could always implement some handler. Even if it does nothing.
If we take the approach of providing empty handlers as an extension of the API I don't think it makes any difference to the current situation. Ignoring the empty methods is just as easy as ignoring the interfaces available for handling these features.
With the aforementioned approach to allow "stop" processing or "continue" processing, this opens many flexible ways to write interactive widgets and reusing existing widgets. IMHO easier than the current approach.
Eactly this behaviour is possible when overriding - developers either call myEntry.Entry.Tapped() or don't from within their own Tapped() method.
I hope that I have responded to your questions and suggestions.
I think that this ticket is covering many things in one, and I'm not too sure if I have understood it all. In summary:
Thank you very much for your explanations! I am absolutely fine with this, and of course its up to if you want to keep this ticket around or not. I wanted it more as an entrypoint for discussion and maybe to orchestrate the direction of keyboard support (kind of like an Epic). If it doesn't serve this purpose or doesn't fit the desired ticket style, you can just close it :smile: (or rephrase it, or whatever :smiley: )
I think the ticket is great - the discussion is valuble and should be kept visible.
I'll leave this open as the checklist at the top is really useful too (though the details may vary as we progress).
Related:
Consider:
Enter calls the Confirm button.Enter calls the Confirm button (default action)Escape calls the Dismiss button (cancel action)Space "Clicks" the button. (used with global tab navigation)Space "Clicks" the link. (tab navigation)
- [ ] Form -> (Single Line) Entry
Entercalls the Confirm button.
Yes, if this is the last Entry in the Form, otherwise shouldn't it move to the next Entry as if it were a Tab?
Also, not sure if this is covered by;
Select is almost done as well, but needs some further work on menus to be complete.
But I'd add;
Escape dismisses popupEscape dismisses popup
Most helpful comment
I think the ticket is great - the discussion is valuble and should be kept visible.
I'll leave this open as the checklist at the top is really useful too (though the details may vary as we progress).