Input, focus and Back
Input goes to one app at a time: the focused one. Inside that app, keys and a gamepad move focus from widget to widget, and Back goes to the innermost part of the UI that can take it.
Which app hears input
Windows stack with the most recently focused in front. Focusing an app
brings its window forward; datumhue.app.is_focused says whether your app
has focus, and the app_focused and app_unfocused events tell you when
that changes. A new app takes focus when the app that started it had
focus, or manages the app that had it.
Your app sees real input only while it, or an app it manages, is focused. Every other app reads input as if nothing were pressed, so an app in the background never sees what someone types:
| What an app in the background reads | Value |
|---|---|
| a key or a button held | false |
| a pointer or touch position | nil |
| a stick | { x = 0, y = 0 } |
| a trigger | 0 |
The pointer's look (shown, hidden, locked, its icon) belongs to each app, and changes with focus.
Stacking. The root app is the workstation's base layer: focusing it gives it input without covering the windows it manages, and its own popovers and menus stack above those windows. An app that manages visible windows stacks its popovers and menus above the highest of them. Any other app's popovers and menus stack just above its own window, under a window focused in front of it.
Reserved shortcuts. A window manager can keep key chords, such as
Alt+Tab, and the gamepad's View, Menu and Guide buttons for itself with
datumhue.wm.grab_input. A press it grabs goes to its on_press
handler, and reaches no app as input. The face buttons, the D-pad, the
bumpers and the triggers can never be grabbed: your app always gets
those.
Focus inside your app
One widget of your app holds focus, datumhue.ui.focused, and keys and
gamepad A go to it. Every widget works from the keyboard and a gamepad
with no extra code:
| Input | Moves focus |
|---|---|
| Tab | to the next widget, in order |
| the arrow keys, the D-pad, the left stick | to the nearest widget in that direction |
| a press or a tap | to the widget pressed; a button made with keep_input_focus leaves your app's focused text field focused |
A widget keeps the arrow keys it uses itself: a slider its left and right, a text field its caret keys. Each app keeps its focused widget while another app is in front, and has it again when focused.
A game that reads the arrow keys or the D-pad itself has them as long as
it shows no widget that takes focus. A game with widgets on screen turns
this off with datumhue.ui.focus_navigation.
Keyboard and gamepad has every rule.
Where Back goes
Escape, gamepad B, the mouse's back button, a browser's Back and a phone's back gesture all make one cancel request. Escape, B and the mouse's back button make one only while focus navigation is on, so a game that turned it off keeps them for itself. Escape in a focused text field always makes one.
In a browser the mouse's back button is the browser's Back, and it always makes one, as a phone's back gesture does.
The request goes from the inside out, and the first of these that is there takes it:
- a dialog of the workstation's own, which presses its Cancel;
- in a browser, a Continue chip DatumHue shows for your app or for the workstation, which goes away without running what it held;
- an open menu, or a popover made with
light_dismiss, which closes; - a focused text field, which runs its
on_canceland lets go of focus; - from the focused widget outward, the first element with a cancel
handler (
UiElement:on_cancel), or a router made withpop_on_cancelthat has a page to go back to; - the frame your window manager put around your app.
A request no part of your app takes goes on to the window manager, which usually takes the person out of your app. Handle Back shows how to decide what Back does in your app.
See also
Reference used on this page: datumhue.app.is_focused datumhue.wm.grab_input datumhue.ui.focused datumhue.ui.focus_navigation UiElement:on_cancel