guido 0.12.0

A reactive Rust GUI library using wgpu for Wayland layer shell widgets
Documentation
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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
//! Focus management system for keyboard input routing.
//!
//! This module provides a centralized way to track which widget has keyboard
//! focus. Only one widget can have focus at a time.
//!
//! # Why the path and not just the id
//!
//! A container's `when_focused` applies when *it or anything below it* has
//! focus, so answering "is the focus inside me" used to mean walking the
//! container's descendants with the tree in hand. That is fine at paint time
//! and impossible anywhere else — in particular inside a `create_derived`
//! closure, which is where a container has to resolve the text colour it
//! publishes to its descendants.
//!
//! So the focused widget's ancestors are computed *once*, when focus moves and
//! the tree is right there, and stored alongside it. Asking becomes
//! `path.contains(id)`: no tree, and cheap enough to call from a closure.
//!
//! Being a signal, it also means a container that declares `when_focused`
//! subscribes by asking. That is a wider fan-out than the two `request_job`
//! calls it replaces — every container declaring `when_focused` wakes on any
//! focus change, not just the two on the path — but it is bounded by a rare
//! declaration, and it is what lets a focus change reach a descendant's text.

use smallvec::SmallVec;

use crate::app_state::with_app_state;
use crate::jobs::{JobRequest, request_job};
use crate::reactive::global::GlobalSignal;
use crate::reactive::signal::RwSignal;
use crate::tree::{Tree, WidgetId};
use crate::widget_ref::Attachment;

/// The focused widget and every ancestor of it, innermost first.
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct FocusPath {
    chain: SmallVec<[WidgetId; 8]>,
    by_keyboard: bool,
}

impl FocusPath {
    fn of(tree: &Tree, id: WidgetId, by_keyboard: bool) -> Self {
        Self {
            chain: tree.ancestors(id).collect(),
            by_keyboard,
        }
    }

    /// Whether the focus got here from the keyboard — Tab — rather than from
    /// a press or a call. What a focus ring that shows only for the keyboard
    /// would ask, as the web's `:focus-visible` does.
    pub fn by_keyboard(&self) -> bool {
        self.by_keyboard
    }

    /// The focused widget itself, if any.
    pub fn widget(&self) -> Option<WidgetId> {
        self.chain.first().copied()
    }

    /// Whether `id` is the focused widget or one of its ancestors.
    pub fn contains(&self, id: WidgetId) -> bool {
        self.chain.contains(&id)
    }

    /// The root the focused widget sits under, if anything is focused.
    ///
    /// The chain was walked to a widget with no parent when the focus moved,
    /// and the only widgets without one are surface roots — so the last link
    /// already names the surface the keyboard is on.
    /// [`Tree::surface_root_of`](crate::tree::Tree::surface_root_of) answers
    /// the same question by walking live parents; this one is here because the
    /// answer is already in hand, and the walk is what the path exists to have
    /// done once.
    pub(crate) fn root(&self) -> Option<WidgetId> {
        self.chain.last().copied()
    }

    /// The focused widget and its ancestors, innermost first.
    pub(crate) fn chain(&self) -> &[WidgetId] {
        &self.chain
    }

    pub fn is_empty(&self) -> bool {
        self.chain.is_empty()
    }
}

/// The focused widget and its ancestors. A signal, so that resolving a
/// `when_focused` subscribes to it.
static FOCUS: GlobalSignal<FocusPath> = GlobalSignal::new(FocusPath::default);

fn focus() -> RwSignal<FocusPath> {
    FOCUS.get()
}

/// The focus path as it stands, subscribing nothing: what routes a key, which
/// is not something a key's delivery should be woken by.
pub(crate) fn focus_path_untracked() -> FocusPath {
    focus().get_untracked()
}

/// The current focus path. Reading this subscribes, like any other signal.
pub fn focus_path() -> FocusPath {
    focus().get()
}

/// Request keyboard focus for a widget.
///
/// Takes the tree because the ancestor path is resolved here, once, rather
/// than by every reader later — see the module docs.
pub fn request_focus(tree: &Tree, id: WidgetId) {
    move_focus(tree, id, false);
}

/// Move the focus to `id` because a key said so: what Tab does.
pub(crate) fn request_focus_by_keyboard(tree: &Tree, id: WidgetId) {
    move_focus(tree, id, true);
}

fn move_focus(tree: &Tree, id: WidgetId, by_keyboard: bool) {
    // A child playing its exit is leaving: it gave the focus up at removal
    // and cannot take it back.
    if super::invalidation::is_detached(id) {
        return;
    }
    let old = focus().get_untracked();
    if old.widget() == Some(id) && old.by_keyboard == by_keyboard {
        return;
    }
    // Repaint the previously focused widget so it drops focused styling. The
    // signal write covers everything that *resolves* focus, but a widget that
    // merely draws a caret (a text input) reads `has_focus` directly.
    if let Some(old_id) = old.widget() {
        request_job(old_id, JobRequest::Paint);
    }
    let new = FocusPath::of(tree, id, by_keyboard);
    // Each surface keeps what it had focused while the focus is on another,
    // as a `GtkWindow` keeps its focus widget: the keyboard coming back gives
    // it back, see `give_back_kept_focus`. Kept only while the focus is away,
    // so a surface the focus is on never has an entry to go stale.
    if old.root() != new.root() {
        with_app_state(|app| {
            let mut kept = app.kept_focus.borrow_mut();
            if let (Some(root), Some(widget)) = (old.root(), old.widget()) {
                kept.insert(root, (widget, old.by_keyboard));
            }
            if let Some(root) = new.root() {
                kept.remove(&root);
            }
        });
    }
    focus().set(new);
    request_job(id, JobRequest::Paint);
}

/// The keyboard has come back to the surface rooted at `root`: give the focus
/// back to the widget it had focused when the focus went to another surface,
/// if that widget is still on it.
///
/// What a grabbing popup needs. Opened from a search field, its own field
/// takes the focus; closing, it takes that field with it, and without this
/// nothing would be left to give the focus back to the search field.
pub(crate) fn give_back_kept_focus(tree: &Tree, root: WidgetId) {
    let Some((id, by_keyboard)) = with_app_state(|app| app.kept_focus.borrow_mut().remove(&root))
    else {
        return;
    };
    if tree.surface_root_of(id) == Some(root) {
        move_focus(tree, id, by_keyboard);
    }
}

/// What `autofocus` asks for at a widget's first layout: the focus, unless a
/// widget on its own surface holds it.
///
/// Held on another surface, the focus moves only if the keyboard is already
/// here. Otherwise the widget waits as this surface's kept focus, and the
/// keyboard entering gives it the focus: a popup opened from a focused field
/// is laid out before the keyboard reaches it, and a lock cover for a monitor
/// plugged in mid-lock never has the keyboard come to it unless the user moves
/// there. A widget the surface already keeps comes first.
pub(crate) fn offer_autofocus(tree: &Tree, id: WidgetId) {
    let Some(root) = tree.surface_root_of(id) else {
        return;
    };
    match focus().with_untracked(FocusPath::root) {
        None => request_focus(tree, id),
        Some(held) if held == root => {}
        Some(_) if tree.keyboard_is_on(root) => request_focus(tree, id),
        Some(_) => with_app_state(|app| {
            app.kept_focus
                .borrow_mut()
                .entry(root)
                .or_insert((id, false));
        }),
    }
}

/// Forget what the surface rooted at `root` kept, because it is closing.
pub(crate) fn forget_surface(root: WidgetId) {
    with_app_state(|app| app.kept_focus.borrow_mut().remove(&root));
}

/// Whether `id` has the focus and its surface has the keyboard: what a caret
/// is drawn for. A surface without the keyboard keeps its focus, and a field
/// holding it shows no live caret until the keyboard is back.
pub(crate) fn has_focus_with_keyboard(tree: &Tree, id: WidgetId) -> bool {
    focus().with_untracked(|path| {
        path.widget() == Some(id) && path.root().is_some_and(|root| tree.has_keyboard(root))
    })
}

/// Ask for the focus to move to whatever `widget_ref` names, on the next frame.
///
/// The caller has no tree — see
/// [`WidgetRef::focus`](crate::widget_ref::WidgetRef::focus) — so the request is
/// parked here and applied by the loop. It is stored as the *handle* rather than
/// an id so that asking before the widget's first layout works: the id does not
/// exist yet, and by the time the loop applies the request, it does.
pub(crate) fn request_focus_deferred(widget_ref: crate::widget_ref::WidgetRef) {
    with_app_state(|app| *app.pending_focus.borrow_mut() = Some(widget_ref));
    // The loop may be blocked with nothing else to do, and a focus change is work.
    crate::jobs::wake_loop();
}

/// Apply a parked focus request. Called by the loop after layout, where the tree
/// is complete and a handle attached during this frame has resolved.
///
/// Public for the same reason [`request_focus`] is: anything driving frames needs
/// it, and it is only meaningful with a laid-out tree in hand.
pub fn apply_pending_focus(tree: &Tree) {
    let Some(widget_ref) = with_app_state(|app| *app.pending_focus.borrow()) else {
        return;
    };
    // One read of the handle, three outcomes. A request whose ref died with
    // the scope that made it can never be honoured — `.focus()` from inside a
    // popup that closed before the frame came round — and parked it would be
    // read again on every frame after. One naming a widget that is not in the
    // tree *yet* stays parked: that is the ordinary shape of `focus()` from a
    // startup effect. One that names a widget applies.
    match widget_ref.attachment() {
        Attachment::Gone => with_app_state(|app| *app.pending_focus.borrow_mut() = None),
        Attachment::Unattached => {}
        Attachment::To(id) => {
            with_app_state(|app| *app.pending_focus.borrow_mut() = None);
            request_focus(tree, id);
        }
    }
}

/// Drop a parked request that names `widget_ref`.
///
/// Called by the widget-ref registry when a ref's widget leaves the tree. A
/// request waits for a widget that has not been laid out *yet* — that is the
/// ordinary shape of `focus()` from a startup effect — but one whose widget
/// has been and is gone is waiting for something that already happened, and
/// left parked it would fire at whatever takes that ref next, stealing the
/// keyboard from wherever the user had put it.
pub(crate) fn drop_pending_focus_for(widget_ref: crate::widget_ref::WidgetRef) {
    with_app_state(|app| {
        let mut pending = app.pending_focus.borrow_mut();
        if *pending == Some(widget_ref) {
            *pending = None;
        }
    });
}

/// Release keyboard focus from a widget.
/// Only releases if the given widget currently has focus, and repaints it.
///
/// A surface keeping it for the keyboard's return forgets it too: a field
/// blurred while a popup held the focus does not take it back when the popup
/// closes.
pub fn release_focus(id: WidgetId) {
    with_app_state(|app| {
        app.kept_focus
            .borrow_mut()
            .retain(|_, (kept, _)| *kept != id)
    });
    if focus().get_untracked().widget() == Some(id) {
        request_job(id, JobRequest::Paint);
        focus().set(FocusPath::default());
    }
}

/// Drop the focus if `id` is anywhere on the path.
///
/// Called when a widget leaves the tree. Before the path existed this was not
/// needed: focus was a generational id, so a dead widget simply stopped
/// matching any live one and `when_focused` resolved to false on its own.
/// A stored path has no such self-correction — the ancestors would keep
/// answering "the focus is inside me" for a widget that no longer exists.
pub(crate) fn release_focus_if_within(id: WidgetId) {
    if focus_within(id) {
        focus().set(FocusPath::default());
    }
}

/// Whether the focus is on `id` or inside it, without subscribing.
///
/// The tracked spelling of the same question is `focus_path().contains(id)`,
/// and it is the right one for anything resolving a style: it wants to be woken
/// when the answer changes. This one is for the event path, which reads to
/// decide and is already running because something happened — subscribing there
/// would open a subscription from inside an event handler.
pub(crate) fn focus_within(id: WidgetId) -> bool {
    focus().with_untracked(|path| path.contains(id))
}

/// Drop the focus if it belongs to the surface rooted at `root`.
///
/// Focus is one signal for the whole application while one `Tree` holds every
/// surface's root, so "nothing here claimed that press" speaks only for the
/// surface the press landed on. The focus path ends at the root of whichever
/// surface owns the keyboard, and that is the whole test.
pub(crate) fn release_focus_under(root: WidgetId) {
    if focus().with_untracked(|path| path.root() == Some(root)) {
        drop_focus();
    }
}

/// Check if a specific widget has keyboard focus.
pub fn has_focus(id: WidgetId) -> bool {
    focus().get_untracked().widget() == Some(id)
}

/// Get the ID of the currently focused widget, if any.
pub fn focused_widget() -> Option<WidgetId> {
    focus().get_untracked().widget()
}

/// Clear all focus (no widget will have focus).
/// Repaints the previously focused widget if any.
///
/// What every surface kept for the keyboard's return goes too, or one of them
/// would have the focus again when the keyboard next came back.
pub fn clear_focus() {
    with_app_state(|app| app.kept_focus.borrow_mut().clear());
    drop_focus();
}

/// Take the focus off the widget holding it, leaving what other surfaces kept.
fn drop_focus() {
    let old = focus().get_untracked();
    if let Some(old_id) = old.widget() {
        request_job(old_id, JobRequest::Paint);
        focus().set(FocusPath::default());
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::reactive::owner::{create_root_owner, dispose_owner_now, with_owner};

    /// There is one keyboard: its entering another surface is its leaving
    /// this one, whether or not this one's leave has come yet — a leave can
    /// arrive after the enter it made way for. A field kept focused here
    /// draws no caret until the keyboard is back.
    #[test]
    fn the_keyboard_entering_another_surface_leaves_this_one_without_it() {
        use crate::layout::Constraints;
        use crate::widgets::{Event, container, text_input};

        create_root_owner();
        clear_focus();
        let mut tree = Tree::new();
        let field = tree.register(Box::new(text_input(crate::reactive::create_signal(
            String::new(),
        ))));
        let other = tree.register(Box::new(container()));
        for root in [field, other] {
            tree.layout_widget(root, Constraints::new(0.0, 0.0, 200.0, 40.0));
        }
        request_focus(&tree, field);
        assert!(
            has_focus_with_keyboard(&tree, field),
            "nothing said otherwise yet"
        );

        tree.dispatch(other, &Event::FocusIn);
        assert!(has_focus(field), "the focus stays");
        assert!(!has_focus_with_keyboard(&tree, field), "the keyboard went");

        tree.dispatch(field, &Event::FocusOut);
        tree.dispatch(field, &Event::FocusIn);
        assert!(has_focus_with_keyboard(&tree, field), "and came back");
        tree.dispatch(other, &Event::FocusOut);
        assert!(
            has_focus_with_keyboard(&tree, field),
            "a late leave from the other surface says nothing about this one"
        );
    }

    /// A blur reaches a focus kept for the keyboard's return: the field a
    /// popup's handler blurred does not have the focus back when the keyboard
    /// comes back to its surface.
    #[test]
    fn a_kept_focus_that_was_released_is_not_given_back() {
        use crate::layout::Constraints;
        use crate::widgets::{Event, text_input};

        create_root_owner();
        clear_focus();
        let mut tree = Tree::new();
        let field = |tree: &mut Tree| {
            let id = tree.register(Box::new(text_input(crate::reactive::create_signal(
                String::new(),
            ))));
            tree.layout_widget(id, Constraints::new(0.0, 0.0, 200.0, 40.0));
            id
        };
        let search = field(&mut tree);
        let in_popup = field(&mut tree);
        request_focus(&tree, search);
        request_focus(&tree, in_popup);

        release_focus(search);
        tree.dispatch(search, &Event::FocusIn);

        assert!(!has_focus(search), "blurred, and it stays blurred");
        assert!(has_focus(in_popup), "the focus is where it was");
    }

    /// A surface with two fields, laid out, and the field ids.
    fn two_fields(tree: &mut Tree) -> (WidgetId, WidgetId, WidgetId) {
        use crate::layout::Constraints;
        use crate::widgets::{container, text_input};

        let field = || text_input(crate::reactive::create_signal(String::new()));
        let root = tree.register(Box::new(container().child(field()).child(field())));
        tree.with_widget_mut(root, |w, id, t| w.register_children(t, id));
        tree.layout_widget(root, Constraints::new(0.0, 0.0, 200.0, 80.0));
        let children = tree.get_children(root).to_vec();
        (root, children[0], children[1])
    }

    /// Autofocus never takes the focus from a widget on its own surface, even
    /// with the keyboard there — the case in which it would take it from
    /// another surface's.
    #[test]
    fn autofocus_leaves_the_focus_on_its_own_surface_alone() {
        use crate::widgets::Event;

        create_root_owner();
        clear_focus();
        let mut tree = Tree::new();
        let (root, typed_into, autofocusing) = two_fields(&mut tree);
        tree.dispatch(root, &Event::FocusIn);
        request_focus(&tree, typed_into);

        offer_autofocus(&tree, autofocusing);

        assert!(has_focus(typed_into));
    }

    /// A surface waiting for the keyboard gives back the widget it already
    /// kept, not an autofocusing one that appeared on it since.
    #[test]
    fn autofocus_waits_behind_a_widget_its_surface_already_keeps() {
        use crate::widgets::Event;

        create_root_owner();
        clear_focus();
        let mut tree = Tree::new();
        let (popup, kept, autofocusing) = two_fields(&mut tree);
        let (bar, on_bar, _) = two_fields(&mut tree);
        tree.dispatch(bar, &Event::FocusIn);
        request_focus(&tree, kept);
        request_focus(&tree, on_bar);

        offer_autofocus(&tree, autofocusing);
        assert!(has_focus(on_bar), "the keyboard is on the bar");
        tree.dispatch(popup, &Event::FocusIn);

        assert!(has_focus(kept));
    }

    /// A request outlives a widget that comes and goes, and must not fire at
    /// whatever takes its ref next: the field it named left the tree, so the
    /// request is for something that already happened.
    #[test]
    fn a_request_dies_with_the_widget_it_named() {
        use crate::layout::Constraints;
        use crate::widgets::{container, text_input};

        create_root_owner();
        let field = crate::widget_ref::create_widget_ref();
        let mut tree = Tree::new();
        let root = tree.register(Box::new(container().child(
            text_input(crate::reactive::create_signal(String::new())).widget_ref(field),
        )));
        tree.with_widget_mut(root, |w, id, t| w.register_children(t, id));
        tree.layout_widget(root, Constraints::new(0.0, 0.0, 200.0, 40.0));
        crate::widget_ref::update_widget_refs(&tree);
        assert!(field.widget().is_some(), "laid out, so it names a widget");

        request_focus_deferred(field);

        // The field is taken out of the tree: the registry notices on its next
        // pass, and the request goes with the widget it was waiting for.
        crate::jobs::teardown_widget_subtree(&mut tree, root);
        crate::widget_ref::update_widget_refs(&tree);

        assert!(
            with_app_state(|app| app.pending_focus.borrow().is_none()),
            "a request for a widget that has left does not wait for its ref to be reused"
        );
    }

    /// `.focus()` from inside a popup, on a ref composed in that popup, when the
    /// popup closes before the loop comes round: the request is parked by
    /// design — a widget not yet in the tree is the ordinary case — so a dead
    /// one would be read again on every frame from then on.
    #[test]
    fn a_parked_request_whose_ref_died_is_dropped_rather_than_read_forever() {
        create_root_owner();
        let tree = Tree::new();
        let ((), popup_scope) = with_owner(|| {
            request_focus_deferred(crate::widget_ref::create_widget_ref());
        });
        dispose_owner_now(popup_scope);

        apply_pending_focus(&tree);
        apply_pending_focus(&tree);

        assert!(
            with_app_state(|app| app.pending_focus.borrow().is_none()),
            "a request that can never be honoured does not stay parked"
        );
    }
}