1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
//! Keyboard focus traversal — `tab` and `shift-tab` between controls.
//!
//! gpui has all the machinery and none of it is on by default: a focus handle
//! carries a `tab_index` and a `tab_stop` flag, `.track_focus` registers the
//! handle for the frame, and [`Window::focus_next`] walks the order — but
//! `tab_stop` starts `false` and gpui binds no keys. This module turns it on.
//!
//! # Order is paint order
//!
//! gpui sorts tab stops by their `tab_index` path and then by insertion, so
//! leaving every index at 0 yields the order the controls are painted in.
//! Nothing has to be numbered by hand, and inserting a control in the middle of
//! a form does not renumber the rest — which is the failure mode that makes
//! HTML `tabindex` a liability.
//!
//! # Where the handle lives
//!
//! Most of this crate is `fn(&Theme, ..) -> Div`: stateless, with the app
//! owning whether a checkbox is checked. Focus is more of that state, so the
//! app owns the handle too and [`focusable`] wires it up. Giving every widget a
//! handle of its own would mean giving every widget an identity and a lifetime,
//! which is the entity machinery [`crate::input::TextField`] needs and a
//! checkbox does not.
//!
//! ```ignore
//! ui::focus::init(cx); // once, at startup
//!
//! // ..and on the root view, so `tab` works wherever focus currently is:
//! focus::traversal(div().track_focus(&self.focus_handle))
//! .child(focus::focusable(&theme, &self.ok_focus, popover::button(&theme, "OK", "ok")))
//! ```
use ;
use Theme;
actions!;
/// Claimed by every [`focusable`] control, so `enter` and `space` mean "press
/// this" only where something is actually focused.
///
/// Scoping matters here: [`crate::palette`] and [`crate::combobox`] both bind
/// `enter` for their own lists, and a multi-line field binds it to insert a
/// newline. A focused control sits deeper in the focus path than any of them,
/// so it wins `enter` while focused and gives it straight back afterwards.
pub const CONTROL_KEY_CONTEXT: &str = "Control";
/// Bind `tab` and `shift-tab`. Call once at startup.
///
/// Optional, like [`crate::input::init`] — the actions are public, so an app
/// that wants different keys binds those instead. The bindings are global
/// rather than scoped to a context: traversal is a property of the window, not
/// of whatever happens to be focused.
///
/// Nothing in this crate claims `tab` for itself, deliberately. A multi-line
/// field could reasonably insert one, but trapping `tab` inside a text box is
/// the classic way to make a form impossible to leave by keyboard.
///
/// [`Decrement`]/[`Increment`] on `left`/`right` are for a control that holds a
/// *value* rather than a press — [`slider`](crate::widgets::Controls::slider)
/// is the one. They
/// carry no step: only the caller knows the range, and a library that picked
/// one would be picking it for a percentage and a font size alike.
/// Attach the traversal handlers, normally to the app's root element.
///
/// It has to live on an element rather than on the app because moving focus
/// needs a [`Window`], and an app-level action handler only gets an [`App`].
/// Put a stateless control into the tab order, show when it holds focus, and
/// let `enter`/`space` press it.
///
/// The ring is the same one [`crate::input::TextField`] paints — the border in
/// [`Theme::caret`] — so a focused button and a focused field read alike.
///
/// It lands on the control's *own* border, which is why every control in
/// [`crate::widgets`] carries one even where it paints nothing: gpui sizes
/// border-box, so a border that only appeared on focus would move the content
/// under it by a pixel. A ring wrapped *around* the control instead would cost
/// every one of them a radius parameter, and would prise a focused tab off the
/// hairline its underline has to overlap.
///
/// Pressing dispatches [`Activate`], which the caller handles beside its
/// `on_click`. Deliberately not folded into one callback: a control that is
/// pressed by mouse and by key is doing the same thing, but only the caller
/// knows what that is, and a keyboard-only affordance that silently diverges
/// from the click is worse than none.