Skip to main content

rosace_widgets/tree/
focus_api.rs

1use rosace_a11y::FocusNode;
2use rosace_render::{Color, DrawCommand};
3use super::{Widget, PaintCtx};
4
5// ── WithFocus wrapper ─────────────────────────────────────────────────────────
6
7/// Wraps a widget with a [`FocusNode`], enabling explicit focus graph wiring
8/// and reactive focus-ring rendering.
9///
10/// Created by the [`FocusApi`] builder methods. `focused()` is a reactive
11/// `Atom<bool>` — set it to `true` (via `FocusNode::request()`) to draw the
12/// focus ring around this widget.
13pub struct WithFocus<W: Widget> {
14    inner:      W,
15    node:       FocusNode,
16}
17
18impl<W: Widget + 'static> WithFocus<W> {
19    pub fn new(inner: W, node: FocusNode) -> Self {
20        Self { inner, node }
21    }
22
23    /// Wire an explicit Tab-forward neighbor.
24    pub fn focus_next_node(self, next: FocusNode) -> Self {
25        self.node.set_next(next);
26        self
27    }
28
29    /// Wire an explicit Shift+Tab / reverse neighbor.
30    pub fn focus_prev_node(self, prev: FocusNode) -> Self {
31        self.node.set_prev(prev);
32        self
33    }
34
35    /// The focus node attached to this widget (cloned — cheap Arc clone).
36    pub fn node(&self) -> FocusNode { self.node.clone() }
37}
38
39impl<W: Widget + Send + Sync + 'static> Widget for WithFocus<W> {
40    fn children(&self) -> super::Children<'_> {
41        super::Children::One(&self.inner)
42    }
43
44    fn paint(&self, ctx: &mut PaintCtx) {
45        // Register in DFS order so FocusManager can build the Tab cycle.
46        ctx.register_focus(self.node.clone());
47
48        self.inner.paint(ctx);
49
50        // Draw a 2px focus ring when focused.
51        if self.node.is_focused() {
52            let rect = ctx.rect;
53            ctx.recorder.push(DrawCommand::StrokeRect {
54                rect,
55                color: Color::rgba(100, 160, 255, 220),
56                width: 2.0,
57            });
58        }
59    }
60    // layout, flex_factor: protocol defaults delegate to the child.
61}
62
63// ── FocusApi trait — blanket impl for all widgets ─────────────────────────────
64
65/// Builder methods that attach a [`FocusNode`] to any widget.
66///
67/// ```rust,ignore
68/// let email  = FocusNode::new();
69/// let pass   = FocusNode::new();
70/// let submit = FocusNode::new();
71///
72/// TextInput::new("Email").focus_node(email.clone())
73///     .focus_next_node(pass.clone())
74///
75/// TextInput::new("Password").focus_node(pass.clone())
76///     .focus_next_node(submit.clone())
77///     .focus_prev_node(email.clone())
78///
79/// Button::new("Login").focus_node(submit.clone())
80///     .focus_prev_node(pass.clone())
81/// ```
82pub trait FocusApi: Widget + Sized + Send + Sync + 'static {
83    /// Attach a focus node. This enables focus-ring rendering and explicit
84    /// neighbor wiring.
85    fn focus_node(self, node: FocusNode) -> WithFocus<Self> {
86        WithFocus::new(self, node)
87    }
88}
89
90impl<W: Widget + Send + Sync + 'static> FocusApi for W {}