Skip to main content

agg_gui/
focus.rs

1//! Thread-local programmatic focus-request channel.
2//!
3//! Widgets built in app code can't reach the [`App`](crate::widget::App)'s
4//! private focus path to focus themselves when they appear — e.g. a search
5//! field that should grab the keyboard the instant its overlay opens. This
6//! channel mirrors [`crate::animation::request_draw`]:
7//!
8//! 1. The widget is built with a stable [`FocusId`] and returns it from
9//!    [`Widget::focus_id`](crate::widget::Widget::focus_id).
10//! 2. App logic calls [`request_focus`] with that id (typically from the
11//!    same handler that makes the widget visible).
12//! 3. The `App` consumes the pending request on its next `layout`, locates
13//!    the focusable widget whose `focus_id` matches, and moves focus to it
14//!    — dispatching `FocusGained` and (for text inputs) raising the
15//!    on-screen keyboard.
16//!
17//! Only one request is held at a time; a later [`request_focus`] before the
18//! `App` services the previous one wins.
19
20use std::cell::Cell;
21
22/// Opaque, app-chosen identifier for a focusable widget. Values only need to
23/// be unique among the widgets that opt into focus-by-request.
24pub type FocusId = u64;
25
26std::thread_local! {
27    static PENDING_FOCUS: Cell<Option<FocusId>> = const { Cell::new(None) };
28}
29
30/// Request that the widget whose [`Widget::focus_id`](crate::widget::Widget::focus_id)
31/// equals `id` receive focus on the next frame. Also wakes the host loop
32/// (via [`crate::animation::request_draw`]) so the request is serviced
33/// promptly.
34pub fn request_focus(id: FocusId) {
35    PENDING_FOCUS.with(|c| c.set(Some(id)));
36    crate::animation::request_draw();
37}
38
39/// Read-and-clear the pending focus request. Called by the `App` once per
40/// `layout`.
41pub fn take_focus_request() -> Option<FocusId> {
42    PENDING_FOCUS.with(|c| c.replace(None))
43}
44
45/// Discard any pending focus request without acting on it.
46pub fn clear_focus_request() {
47    PENDING_FOCUS.with(|c| c.set(None));
48}