Skip to main content

crabshell_ui/
keyboard.rs

1//! Keyboard input: the focused button, Tab between buttons, and key
2//! handlers.
3//!
4//! The platform calls [`Window::keyboard_enter`], [`Window::key`] and
5//! [`Window::keyboard_leave`] for the window that has the keyboard. A key
6//! goes to the focused button's `on_key`, then to the window's, and if
7//! neither handled it, Tab moves the focus and Enter or Space clicks the
8//! focused button. Handlers run with the tree released, inside
9//! `catch_unwind`.
10
11use std::rc::Rc;
12
13use crabshell_reactive::{Signal, untrack};
14
15use crate::cx::Cx;
16use crate::edit::Keyed;
17use crate::input::{Handler, run_isolated};
18use crate::text_input::AcceptHandler;
19use crate::tree::{Change, NodeId, NodeKind, Tree, borrow};
20use crate::window::Window;
21
22/// A key press, or a repeat of a key held down.
23#[derive(Clone, Debug, PartialEq, Eq)]
24pub struct KeyEvent {
25  /// Which key, as the keyboard layout names it.
26  pub key: Key,
27  /// The text it types, if any, with Shift and the layout applied:
28  /// `"A"` for Shift+a. Keys like Enter and Ctrl combinations type none.
29  pub text: Option<String>,
30  /// The modifiers held.
31  pub modifiers: Modifiers,
32  /// Whether this is a repeat of a key held down.
33  pub repeat: bool,
34}
35
36/// A key, as the keyboard layout names it.
37#[derive(Clone, Debug, PartialEq, Eq)]
38pub enum Key {
39  /// Enter or Return, on the main keys or the keypad.
40  Enter,
41  /// Escape.
42  Escape,
43  /// Tab, with or without Shift.
44  Tab,
45  /// Backspace.
46  Backspace,
47  /// Delete.
48  Delete,
49  /// The left arrow.
50  Left,
51  /// The right arrow.
52  Right,
53  /// The up arrow.
54  Up,
55  /// The down arrow.
56  Down,
57  /// Home.
58  Home,
59  /// End.
60  End,
61  /// Page Up.
62  PageUp,
63  /// Page Down.
64  PageDown,
65  /// A function key, from F1.
66  Function(u8),
67  /// A key that stands for a character, such as `'a'`, `'A'` with Shift,
68  /// or `' '` for the space bar.
69  Character(char),
70  /// Any other key, by its XKB keysym, such as `0xffe1` for the left
71  /// Shift.
72  Other(u32),
73}
74
75/// What a key asks of the clipboard.
76#[derive(Clone, Debug, PartialEq, Eq)]
77pub enum Clipboard {
78  /// Offer this text as the clipboard's.
79  Copy(String),
80  /// Read the clipboard and [`paste`](Window::paste) it into the focused
81  /// field.
82  Paste,
83}
84
85/// The modifier keys held.
86#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
87pub struct Modifiers {
88  /// Control.
89  pub ctrl: bool,
90  /// Alt.
91  pub alt: bool,
92  /// Shift.
93  pub shift: bool,
94  /// The logo key, also called Super or the Windows key.
95  pub logo: bool,
96}
97
98pub(crate) type KeyHandler = Rc<Handler<dyn Fn(&Cx, &KeyEvent) -> bool>>;
99
100/// Who has the keyboard in a window.
101#[derive(Default)]
102pub(crate) struct Keyboard {
103  /// The window has the keyboard.
104  pub(crate) entered: bool,
105  /// The focused button or field, kept while the keyboard is elsewhere.
106  pub(crate) focused: Option<NodeId>,
107  /// The window's own `on_key`.
108  pub(crate) handler: Option<KeyHandler>,
109}
110
111impl Keyboard {
112  /// Forgets nodes that were removed from the tree.
113  pub(crate) fn forget(&mut self, removed: &[NodeId]) {
114    if self.focused.is_some_and(|id| removed.contains(&id)) {
115      self.focused = None;
116    }
117  }
118}
119
120impl Window {
121  /// The keyboard entered the window: its focused button shows its focus
122  /// style again.
123  pub fn keyboard_enter(&self) {
124    let mut tree = borrow(&self.tree);
125    tree.keyboard.entered = true;
126    let focused = tree.keyboard.focused;
127    tree.show_focus(focused, true);
128  }
129
130  /// The keyboard left the window. The focused button stays focused for
131  /// when it comes back.
132  pub fn keyboard_leave(&self) {
133    let mut tree = borrow(&self.tree);
134    tree.keyboard.entered = false;
135    let focused = tree.keyboard.focused;
136    tree.show_focus(focused, false);
137  }
138
139  /// A key was pressed or repeated. A focused text field edits with it
140  /// first. Then the focused button's `on_key` gets it, then the
141  /// window's; a handler returns `true` when it used the key. Otherwise
142  /// Tab and Shift+Tab move the focus between buttons and fields, and
143  /// Enter or Space clicks the focused button.
144  ///
145  /// Returns what the platform should do with the clipboard: copy a
146  /// field's selection on Ctrl+C or Ctrl+X, or paste into the focused
147  /// field with [`paste`](Self::paste) on Ctrl+V.
148  pub fn key(&self, event: &KeyEvent) -> Option<Clipboard> {
149    let typed = borrow(&self.tree).input_key(event);
150    match typed {
151      Some(Typed::Edited(signal, text, clipboard)) => {
152        // A signal whose owner is gone just isn't written.
153        signal.try_set(text);
154        return clipboard;
155      }
156      Some(Typed::Clipboard(clipboard)) => return Some(clipboard),
157      Some(Typed::Moved) => return None,
158      Some(Typed::Accept(call)) => {
159        call.run();
160        return None;
161      }
162      None => {}
163    }
164    let (button, window) = {
165      let tree = self.tree.borrow();
166      (tree.focused_key_call(), tree.window_key_call())
167    };
168    for call in [button, window].into_iter().flatten() {
169      if call.run(event) {
170        return None;
171      }
172    }
173    let click = borrow(&self.tree).default_key(event);
174    if let Some(click) = click {
175      click.run();
176    }
177    None
178  }
179
180  /// The path of the focused button, for tests and logs.
181  pub fn focused(&self) -> Option<String> {
182    let tree = self.tree.borrow();
183    let id = tree.keyboard.focused?;
184    tree.node(id).map(|node| node.path.to_string())
185  }
186}
187
188impl Tree {
189  /// Marks `id`, a button or field, as showing focus or not. Unlike a
190  /// hover change, a layout this causes checks the hover again: buttons
191  /// may have moved under the pointer.
192  fn show_focus(&mut self, id: Option<NodeId>, shown: bool) {
193    let Some(id) = id else {
194      return;
195    };
196    if matches!(
197      self.node(id).map(|node| &node.kind),
198      Some(NodeKind::Input(_))
199    ) {
200      self.update(id, |node| match &mut node.kind {
201        NodeKind::Input(input) if input.focused != shown => {
202          input.focused = shown;
203          Change::Paint
204        }
205        _ => Change::None,
206      });
207    } else {
208      self.update_button_style(id, |button| button.focused = shown);
209    }
210  }
211
212  /// Moves the focus to `id`. It shows once the window has the keyboard.
213  pub(crate) fn focus(&mut self, id: Option<NodeId>) {
214    let old = std::mem::replace(&mut self.keyboard.focused, id);
215    if old != id {
216      // What an input method was composing there is dropped with it.
217      if let Some(old) = old
218        && matches!(self.node(old).map(|node| &node.kind), Some(NodeKind::Input(input)) if input.preedit.is_some())
219      {
220        self.update_input(old, |input| {
221          input.preedit = None;
222          input.refresh();
223        });
224      }
225      self.show_focus(old, false);
226      let entered = self.keyboard.entered;
227      self.show_focus(id, entered);
228    }
229  }
230
231  /// What `event` does to the focused text field, if one is focused and
232  /// uses the key.
233  fn input_key(&mut self, event: &KeyEvent) -> Option<Typed> {
234    let id = self.keyboard.focused?;
235    let NodeKind::Input(input) = &mut self.node_mut(id)?.kind else {
236      return None;
237    };
238    let keyed = input.edit.key(event, !input.password);
239    let accept = keyed == Keyed::Accept;
240    let typed = match keyed {
241      Keyed::Unused => return None,
242      Keyed::Moved => Typed::Moved,
243      Keyed::Edited => Typed::Edited(input.signal, input.edit.text.clone(), None),
244      Keyed::Copy(text) => Typed::Clipboard(Clipboard::Copy(text)),
245      Keyed::Cut(text) => Typed::Edited(
246        input.signal,
247        input.edit.text.clone(),
248        Some(Clipboard::Copy(text)),
249      ),
250      Keyed::Paste => Typed::Clipboard(Clipboard::Paste),
251      Keyed::Accept => {
252        let handler = untrack(|| input.on_accept.try_with(Option::clone)).flatten();
253        let text = input.edit.text.clone();
254        let node = self.node(id)?;
255        // Without its own `on_accept`, Enter goes on to the window.
256        Typed::Accept(AcceptCall {
257          cx: node.cx.clone(),
258          path: Rc::clone(&node.path),
259          handler: handler?,
260          text,
261        })
262      }
263    };
264    if !accept {
265      self.update_input(id, |input| {
266        input.refresh();
267      });
268    }
269    Some(typed)
270  }
271
272  /// The buttons and fields in the order they are laid out: depth first,
273  /// in child order.
274  fn focusable(&self) -> Vec<NodeId> {
275    let mut found = Vec::new();
276    let mut pending = vec![self.root];
277    while let Some(id) = pending.pop() {
278      if let Some(node) = self.node(id)
279        && matches!(node.kind, NodeKind::Button(_) | NodeKind::Input(_))
280      {
281        found.push(id);
282      }
283      let mut children = self.taffy.children(id).unwrap_or_default();
284      children.reverse();
285      pending.extend(children);
286    }
287    found
288  }
289
290  fn focused_key_call(&self) -> Option<KeyCall> {
291    let id = self.keyboard.focused?;
292    let node = self.node(id)?;
293    let NodeKind::Button(button) = &node.kind else {
294      return None;
295    };
296    // Reading the handlers doesn't make anything depend on them.
297    let handler = untrack(|| button.handlers.try_with(|handlers| handlers.key.clone()))??;
298    Some(KeyCall {
299      cx: node.cx.clone(),
300      path: Rc::clone(&node.path),
301      handler,
302    })
303  }
304
305  fn window_key_call(&self) -> Option<KeyCall> {
306    let node = self.node(self.root)?;
307    Some(KeyCall {
308      cx: node.cx.clone(),
309      path: Rc::clone(&node.path),
310      handler: self.keyboard.handler.clone()?,
311    })
312  }
313
314  /// What a key nobody handled does: Tab moves the focus, Enter and Space
315  /// click the focused button.
316  fn default_key(&mut self, event: &KeyEvent) -> Option<crate::input::Call> {
317    match event.key {
318      Key::Tab => {
319        let buttons = self.focusable();
320        let at = self
321          .keyboard
322          .focused
323          .and_then(|id| buttons.iter().position(|button| *button == id));
324        let count = buttons.len();
325        let next = match (at, event.modifiers.shift) {
326          _ if count == 0 => None,
327          (None, false) => Some(0),
328          (None, true) => Some(count - 1),
329          (Some(at), false) => Some((at + 1) % count),
330          (Some(at), true) => Some((at + count - 1) % count),
331        };
332        self.focus(next.map(|next| buttons[next]));
333        None
334      }
335      Key::Enter | Key::Character(' ') => self.click(self.keyboard.focused?),
336      _ => None,
337    }
338  }
339}
340
341/// What a key did in a text field, once the tree is released.
342enum Typed {
343  /// The text changed; the signal gets it. A cut also copies.
344  Edited(Signal<String>, String, Option<Clipboard>),
345  /// The platform should copy or paste.
346  Clipboard(Clipboard),
347  /// Only the caret or selection moved, already in the tree.
348  Moved,
349  /// Enter: the field's `on_accept` runs.
350  Accept(AcceptCall),
351}
352
353/// A field's `on_accept`, to run once the tree is released.
354struct AcceptCall {
355  cx: Cx,
356  path: Rc<str>,
357  handler: AcceptHandler,
358  text: String,
359}
360
361impl AcceptCall {
362  fn run(self) {
363    run_isolated(&self.path, "on_accept", self.handler, |f| {
364      f(&self.cx, &self.text)
365    });
366  }
367}
368
369/// A key handler to run once the tree is released.
370struct KeyCall {
371  cx: Cx,
372  path: Rc<str>,
373  handler: KeyHandler,
374}
375
376impl KeyCall {
377  /// Whether the handler used the key. One that panicked didn't.
378  fn run(self, event: &KeyEvent) -> bool {
379    run_isolated(&self.path, "on_key", self.handler, |f| f(&self.cx, event)).unwrap_or(false)
380  }
381}