GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
src/text_input.rs (3.6K)
1 //! "A text field is being edited": the announcement behind text input.
2 //!
3 //! The compositor needs to know when a field is open for typing, because that
4 //! is what raises the on-screen keyboard after a touch (cce-compositor's
5 //! `osk.rs`) and what activates an input method. A widget says so by calling
6 //! [`claim`] from its paint, every frame it is editing; a widget that stops
7 //! painting has stopped claiming, so there is no enable/disable pair to close
8 //! on every path out of editing (Enter, Escape, a click elsewhere, a focus
9 //! change, its page dropped).
10 //!
11 //! A claim is the caret the input method sees: it is [`crate::ime::report_caret`]
12 //! by its older name, and the shells read it through [`crate::ime::caret`]
13 //! once the frame is built (Wayland's `text-input-v3` in
14 //! `backend::text_input`, the browser's keyboard sink, the AppKit shell's
15 //! `NSTextInputClient`). A widget that knows its caret claims the caret; one
16 //! that does not claims its field. The last claim of a frame wins, so a
17 //! widget may claim its field first and its caret once it has drawn it.
18 //!
19 //! `TextBox`, `Spinbox`, `Slider`'s readout, `ColorSelector`, the params
20 //! pane's code rows and a focused `DocEditor` claim on their own. An app
21 //! drawing its own text (a `LineEdit`, a terminal, an editor) claims from its
22 //! `display_list` while it has a caret.
23 //!
24 //! An app that paints its widgets only on some frames and replays the cached
25 //! result on the rest (a `rebuild_layout` flattening the tree) would claim on
26 //! the rebuilds alone, and every replayed frame would read as the field
27 //! closing. It runs the painting under [`capture`] and claims the captured
28 //! rectangle again on every frame.
29
30 /// A field is editing this frame, its caret (or the field, when the caret is
31 /// not known) at `x, y, w, h` in the window's logical px. The last claim of a
32 /// frame wins.
33 pub fn claim(x: f32, y: f32, w: f32, h: f32) {
34 crate::ime::report_caret(x, y, w, h);
35 }
36
37 /// Run `paint` and return what it claimed, alongside its result. A claim
38 /// made earlier this frame stands if `paint` makes none, so wrapping a
39 /// painting changes nothing about the frame's claim — it only reports it.
40 pub fn capture<R>(paint: impl FnOnce() -> R) -> (R, Option<[f32; 4]>) {
41 let before = crate::ime::swap_reported(None);
42 let out = paint();
43 let made = crate::ime::swap_reported(None);
44 crate::ime::swap_reported(made.or(before));
45 (out, made)
46 }
47
48 #[cfg(test)]
49 mod tests {
50 use super::*;
51
52 #[test]
53 fn a_claim_lasts_one_frame() {
54 crate::ime::begin_frame();
55 claim(1.0, 2.0, 3.0, 4.0);
56 crate::ime::end_frame();
57 assert_eq!(crate::ime::caret(), Some([1.0, 2.0, 3.0, 4.0]));
58 crate::ime::begin_frame();
59 crate::ime::end_frame();
60 assert_eq!(crate::ime::caret(), None, "a frame nobody claimed disables the text input");
61 }
62
63 #[test]
64 fn capture_reports_a_painting_claim_and_keeps_the_frame_whole() {
65 crate::ime::begin_frame();
66 claim(1.0, 1.0, 1.0, 1.0);
67 let ((), made) = capture(|| {});
68 assert_eq!(made, None, "a painting that claims nothing reports nothing");
69 crate::ime::end_frame();
70 assert_eq!(crate::ime::caret(), Some([1.0, 1.0, 1.0, 1.0]), "and the earlier claim stands");
71 crate::ime::begin_frame();
72 let (n, made) = capture(|| {
73 claim(5.0, 6.0, 7.0, 8.0);
74 3
75 });
76 assert_eq!((n, made), (3, Some([5.0, 6.0, 7.0, 8.0])));
77 crate::ime::end_frame();
78 assert_eq!(crate::ime::caret(), Some([5.0, 6.0, 7.0, 8.0]), "the frame keeps what was captured");
79 }
80 }