We finally got tab navigation implemented! You might think it should have been an easy feature to add, but achieving a consistent and controllable behavior across our different native GUI backends is not that straightforward. So we opted for a mixed implementation with a general high-level navigation layer in Red and left spatial navigation handling to each backend, in order to preserve the native behavior as much as possible.
Automatic navigation
By default, pressing TAB key will allow you to navigate to all the GUI widgets in a window, capable of acquiring the focus. Once the last widget is reached, the next TAB press will circle back to the first focusable widget. Conversely, back-navigation can be achieved using Shift-TAB key combination, circling from first face to last one. Here is a simple example:
view [ text "Name" field focus return text "Surname" field return below check "Single" check "Employed" button "Send" ]
Note: check-boxes selection/unselection is done using the Space key (default on Windows).
It is possible to make a face "TAB-transparent", so that TAB navigation will skip it in both directions. This is achieved by removing the focusable flag from a navigable face. For example, in the following code, clicking on the "Click me!" button will toggle the button's focusable flag on and off (using set-flag/toggle):
view [ text "Name" field focus return text "Surname" field return below check "Single" check "Employed" button "Send" button "Click me!" 100 [ face/text: pick ["TAB ignore" "TAB stop"] to-logic face/flags set-flag/toggle face 'focusable ] ]
In case of area face, the default behavior for TAB navigation means that tab characters cannot be input in the area. In such cases, the alternative Ctrl-TAB key combination can be used to input tab characters. In case the focusable flag is removed from an area face, then TAB key will directly produce tab characters. Here is an example:
view [ text "Name" field focus return text "Surname" field return below text "Comments" com: area button "Send" button "Toggle Area" [set-flag/toggle com 'focusable] ]
Note: when the focusable flag is on, Ctrl-TAB is used to input tab characters, when it's off, it's just using TAB key.
Manual override
In some cases, the user can decide to set a different path for keyboard navigation. For each navigable face (the ones with a focusable flag), it is possible to manually define the next and/or previous one when tabbing forth and/or back. In order to do so, next and prev options can be set to define how tabbing will navigate to the next or previous face.
Here is a simple example where the default navigation is changed to jump into fields marked as invalid or empty (using pink background) after a typical form submission:
view [ group-box 2 [ style error: field pink text "Name" field "John" text "Surname" field "Smith" text "Age" error "abc" focus text "Address" error "-" text "Zip code" field "12345" text "City" error text "Country" error ] return btn-send: button "Send" do [ list: collect [foreach-face self [if face/color = pink [keep face]]] forall list [list/1/options/next: list/2] btn-send/options/next: list/1 ] ]
Notes:
- For the sake of simplicity in this example, only forward navigation is restricted, backward navigation will visit all focusable faces.
- In the do block, self refers to the window face, as do denotes a global section, not widget-related.
- When list/1 refers to the last element, list/2 returns none, so it does not point to any specific face. In such case, the default tab navigation will automatically (and conveniently) select the next face, which is the "Send" button.
- The last line is there to connect that last face (the button) to the first face in our restricted list.
Other notable changes
An important change concerns the insert-event-func function specification, it now requires a name as argument:
>> ? insert-event-func USAGE: INSERT-EVENT-FUNC name fun DESCRIPTION: Adds a function to monitor global events. Returns the function. INSERT-EVENT-FUNC is a function! value. ARGUMENTS: name [word!] fun [block! function!] "A function or a function body block."
The name is an arbitrary word that only needs to be unique, so it becomes
easier to check if a given global handler has been installed or not. It also
makes it easier to remove it, as it can be referred by name in
remove-event-func. Existing handler names can be checked using:
>> extract system/view/handlers 2 [tab field-sync reactors radio enter debug dragging]
Please update your code if you have been using those functions.
Enjoy!