denise_keyboard/lib.rs
1//! An on-screen keyboard for panels that have no other one.
2//!
3//! # It is not a special case
4//!
5//! The point of this crate is that nothing downstream of it can tell the
6//! difference between a key tapped here and a key pressed on a keyboard plugged
7//! into the machine. [`denise`] splits keyboard input into two events —
8//! [`InputEvent::Key`], a physical position, and [`InputEvent::Text`], a
9//! character somebody meant to insert — and the hardware path in `denise-evdev`
10//! emits the first followed by whatever the second turns out to be.
11//!
12//! [`Keyboard::press`] emits the same two events, in the same order, produced by
13//! the same [`Composer`] from the same [`Layout`] tables. The application hands
14//! them to [`Ui::handle`], which is the call the hardware path's events arrive
15//! through as well. So a [`TextInput`] inserts them without knowing, a key
16//! binding on Enter fires exactly as it would, and every widget that already
17//! handles keys handles these.
18//!
19//! That is worth stating because the alternative is so tempting: a method on the
20//! text field that inserts a character directly. It would work, and then Enter
21//! would do nothing, Escape would do nothing, and every widget that is not a
22//! text field would be deaf to the keyboard.
23//!
24//! # It is built from widgets
25//!
26//! Every key is a [`Button`] — [`Button::no_focus`], so pressing one does not
27//! move the caret out of the field being typed into. They sit on a
28//! [`Ui::push_shelf`], which slides up from the bottom without pushing a scene,
29//! so the field keeps focus while the keyboard is up. Neither of those is a
30//! keyboard feature; they are toolkit features this is the first user of.
31//!
32//! # Modifiers
33//!
34//! Shift is a one-shot: armed by a tap and spent by the next key. There is no
35//! clock in the press path, so no double-tap window could latch it — and none
36//! is wanted, because Caps Lock has a key of its own where Caps Lock goes.
37//!
38//! Caps is a latch and not a held Shift: it applies to letters and leaves the
39//! digit row alone, which is the difference between a locked keyboard typing
40//! `1` and typing `!`, and caps over shift gives lower case the way a hand
41//! expects. The [`Composer`] models that already, so it is latched with a
42//! `CapsLock` key rather than reimplemented here. Ctrl is a one-shot too, and
43//! reaches the events it modifies.
44//!
45//! Every key that changes what the *next* press means says which state it is
46//! in, and the number and punctuation keys carry what Shift would give in a
47//! small second legend — the `!` over the `1`. Letters do not: a capital `Q`
48//! over a `q` is not news.
49//!
50//! The keys whose meaning is a *name* are drawn rather than lettered — see
51//! [`icons`] — which is why they look the same on a machine with no fonts
52//! installed as on one with DejaVu.
53//!
54//! The third level is the layout's own `AltGr` rather than a page of symbols
55//! chosen here, because there is no such page to choose: `@` is `AltGr`+`2` on a
56//! Norwegian keyboard and `Shift`+`2` on a US one, and a fixed grid would be
57//! wrong on one of them. It latches, since a finger cannot hold one key and
58//! press another.
59//!
60//! # Layouts
61//!
62//! [`Keyboard::from_system`] starts from whatever the machine is configured
63//! for, which is the answer the hardware path starts from too — so a panel with
64//! a keyboard plugged into it and one without agree about what the `;` position
65//! types. It hands back a [`LayoutSource`], and
66//! [`LayoutSource::Unknown`] is the one
67//! worth showing somebody: the system asked for a layout there is no table for
68//! and got US.
69//!
70//! The layout key walks the built-ins. Switching **reletters the keys where
71//! they stand** rather than rebuilding them, because a position does not move
72//! when the layout changes — `KeyCode::Semicolon` is where `ø` lives on
73//! Norwegian and `ö` on German, and it is the same key.
74//!
75//! Switching the keyboard does not switch a physical keyboard attached to the
76//! same machine. An application that wants both in step calls
77//! `InputBackend::set_layout` as well; the toolkit does not couple them,
78//! because it does not know the two are meant to agree.
79//!
80//! # The shape of it
81//!
82//! A compact physical keyboard rather than a phone one: fourteen columns,
83//! Backspace top right, Tab opening the second row, Enter closing the home row,
84//! Shift at both ends of the bottom one. A panel is something somebody stands
85//! in front of and types an address into, so the digits stay on screen instead
86//! of going behind a `123` page, and Tab is how a form gets crossed.
87//!
88//! The width is what makes the layouts complete — see [`ROWS`] for which
89//! positions carry what, and why a narrower grid could not type `å`.
90//!
91//! # Holding a key
92//!
93//! Backspace repeats while it is held and nothing else does, which is what a
94//! phone does and what stops a slow finger typing `aaaaaa`. [`Keyboard::tick`]
95//! collects what a held key has earned, once a frame; it costs nothing when
96//! nobody is touching one, because a repeating key asks the tree to wake it
97//! only between its press and its release.
98//!
99//! Holding a *letter* offers its alternates instead — `é è ê ë` over the `e`,
100//! in a framed strip above the key, chosen by where the finger lifts. The
101//! characters come from the layout, so they change when it does. That gesture
102//! needs the application's help for one call: see [`Keyboard::handle`].
103//!
104//! A field focused under the keyboard is scrolled clear of it where it sits in
105//! something that scrolls; where it does not, [`Keyboard::occluded`] says what
106//! to move it clear of.
107//!
108//! [`InputEvent::Key`]: denise::InputEvent::Key
109//! [`InputEvent::Text`]: denise::InputEvent::Text
110//! [`Ui::handle`]: denise_ui::Ui::handle
111//! [`Ui::push_shelf`]: denise_ui::Ui::push_shelf
112//! [`Button`]: denise_ui::widgets::Button
113//! [`Button::no_focus`]: denise_ui::widgets::Button::no_focus
114//! [`TextInput`]: denise_ui::widgets::TextInput
115
116use denise::{ElementState, InputEvent, KeyCode, Modifiers, Point, Rect, Role};
117use denise_layout::{Composer, Layout, LayoutSource, Output};
118use denise_text::TextStyle;
119use denise_ui::widgets::{Button, Panel, TextInput};
120use denise_ui::{NodeId, Side, Ui};
121
122mod grid;
123pub mod icons;
124
125pub use grid::{Key, ROWS, Row};
126
127/// What the third-level key says.
128const LEVEL3_LEGEND: &str = "alt";
129
130/// The position the layout key borrows.
131///
132/// A key in the grid has to *be* a position, and no real position means "change
133/// layout". `Unidentified` is what the tree already uses for a key it cannot
134/// name, and no layout table letters it — so nothing can be pressed by accident
135/// and nothing else will ever claim it.
136pub(crate) const LAYOUT_KEY: KeyCode = KeyCode::Unidentified(u32::MAX);
137
138/// Height of one key, in logical pixels.
139///
140/// A finger, not a mouse: this is the smallest target that is comfortable on a
141/// panel somebody is standing in front of.
142pub const KEY_HEIGHT: i32 = 48;
143
144/// Space between keys, and around the edge of the shelf.
145pub const KEY_GAP: i32 = 6;
146
147/// Legend size, in logical pixels, when the application names no style.
148const KEY_TEXT: u16 = 16;
149
150/// How long Backspace waits before it starts deleting on its own.
151///
152/// Long enough that no ordinary tap reaches it, short enough that somebody who
153/// meant to hold does not first wonder whether it is broken.
154pub const REPEAT_DELAY_MS: u64 = 450;
155
156/// The margin of frame showing around a strip of alternates, in logical pixels.
157///
158/// Wide enough that the border reads as the edge of something floating rather
159/// than as a seam between keys.
160const STRIP_PAD: i32 = 8;
161
162/// How thick that frame is drawn.
163const STRIP_BORDER: i32 = 2;
164
165/// How long a letter is held before it offers its alternates.
166///
167/// Longer than the pause before Backspace starts repeating, and deliberately:
168/// a repeat that starts a shade early costs one character, and a strip of
169/// letters that opens under a finger that meant to type `e` costs the gesture.
170pub const HOLD_MS: u64 = 500;
171
172/// How often it deletes after that.
173///
174/// Roughly fifteen a second: fast enough to clear a URL bar in a moment, slow
175/// enough to stop where you meant to.
176pub const REPEAT_INTERVAL_MS: u64 = 65;
177
178/// What the Shift key is currently doing.
179///
180/// Three states rather than a shift that latches on a double tap, and the
181/// reason is that [`Keyboard::press`] is not given a clock. A double-tap window
182/// needs one, and threading a timestamp through every key press to serve one key
183/// is a poor trade against a cycle a user can see: the key says which state it
184/// is in, and tapping it moves to the next.
185#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
186pub enum Shift {
187 /// Lower case, and the next character is not shifted.
188 #[default]
189 Off,
190 /// The next character is shifted, and then this releases.
191 Once,
192}
193
194impl Shift {
195 /// What the key says.
196 #[inline]
197 pub const fn legend(self) -> &'static str {
198 match self {
199 Shift::Off => "shift",
200 Shift::Once => "SHIFT",
201 }
202 }
203
204 /// The state after a tap.
205 #[inline]
206 const fn next(self) -> Self {
207 match self {
208 Shift::Off => Shift::Once,
209 Shift::Once => Shift::Off,
210 }
211 }
212
213 /// Whether Shift itself is held for the next press.
214 ///
215 /// Caps Lock is deliberately not part of this. It is not a held Shift —
216 /// treating it as one is the bug that makes a locked keyboard type `!` for
217 /// `1` — and it now has a key of its own, latched in the composer, applying
218 /// to letters only.
219 #[inline]
220 const fn holds_shift(self) -> bool {
221 matches!(self, Shift::Once)
222 }
223}
224
225/// An on-screen keyboard, and the composition state that goes with it.
226///
227/// Holds no widgets of its own: [`Keyboard::open`] builds them into a shelf and
228/// [`Keyboard::close`] takes them away with it. What it keeps between those is
229/// the part that has to survive a key press — the layout and the composer's
230/// half-finished dead keys.
231pub struct Keyboard {
232 layout: &'static Layout,
233 composer: Composer,
234 modifiers: Modifiers,
235 shift: Shift,
236 level3: bool,
237 /// Ctrl armed for the next key. One-shot, like Shift.
238 ctrl: bool,
239 /// The alternates on screen, if a key is being held: the key they came
240 /// from, and one node per character offered.
241 offering: Option<Offering>,
242 shelf: Option<NodeId>,
243 keys: Vec<(KeyCode, NodeId)>,
244 scale: f32,
245 style: TextStyle,
246}
247
248impl Keyboard {
249 /// A keyboard in `layout`.
250 ///
251 /// `denise_layout::from_system()` is the argument that makes it agree with
252 /// whatever the machine is configured for.
253 pub fn new(layout: &'static Layout) -> Self {
254 Self {
255 layout,
256 composer: Composer::new(layout),
257 modifiers: Modifiers::NONE,
258 shift: Shift::Off,
259 level3: false,
260 ctrl: false,
261 offering: None,
262 shelf: None,
263 keys: Vec::new(),
264 scale: 1.0,
265 style: TextStyle::built_in(KEY_TEXT),
266 }
267 }
268
269 /// The same keyboard at a display scale.
270 ///
271 /// The grid is written in logical pixels — [`KEY_HEIGHT`] is a fingertip,
272 /// not a count of device pixels — and this is what turns them into the ones
273 /// the surface has. The same `scale` the application scales its own layout
274 /// by, and the same one [`Theme::scaled`](denise::Theme::scaled) takes.
275 ///
276 /// Set it before opening. A keyboard already on screen is not relaid out,
277 /// because the surface it is sitting on has not changed size either.
278 #[must_use]
279 pub fn with_scale(mut self, scale: f32) -> Self {
280 self.scale = scale;
281 self
282 }
283
284 /// Changes the face the legends are drawn in, keys already up included.
285 ///
286 /// [`with_style`](Self::with_style) is the one to reach for; this is for the
287 /// application that cannot yet know its own font — the table editor builds
288 /// its tree with the built-in face so that it has *a* face whether or not a
289 /// font file turned up, and restyles everything once one has.
290 pub fn set_style<M: Clone + 'static>(&mut self, ui: &mut Ui<M>, style: TextStyle) {
291 self.style = style;
292 for &(_, node) in &self.keys {
293 if let Some(button) = ui.widget_mut::<Button<M>>(node) {
294 button.set_style(style);
295 }
296 }
297 }
298
299 /// The face and size the legends are drawn in.
300 ///
301 /// Worth setting, and the reason is what the default has to be: a widget
302 /// cannot know which fonts the application loaded, so [`Button`] falls back
303 /// to the built-in 8x8 bitmap face and so does this. On a panel that has a
304 /// real font that fallback is visible — the one widget somebody is touching
305 /// is the one drawn in a different typeface — and on a layout with `ß` or a
306 /// composed `ü` on it, the built-in face has no such glyph to draw.
307 ///
308 /// Already scaled, like every other style an application builds: this does
309 /// not multiply `size_px` by [`with_scale`](Self::with_scale).
310 #[must_use]
311 pub fn with_style(mut self, style: TextStyle) -> Self {
312 self.style = style;
313 self
314 }
315
316 /// A keyboard in whatever layout the machine is configured for.
317 ///
318 /// The same answer the hardware path starts from, so a panel with a
319 /// keyboard plugged in and one without agree about what the `;` position
320 /// types. Returns the [`LayoutSource`] alongside, which is worth showing
321 /// somebody: [`LayoutSource::Unknown`] means the system asked for a layout
322 /// there is no table for and got US, and a keyboard silently in the wrong
323 /// language is a bad afternoon.
324 pub fn from_system() -> (Self, LayoutSource) {
325 let (layout, source) = denise_layout::from_system();
326 (Self::new(layout), source)
327 }
328
329 /// Changes layout, relettering the keys where they stand.
330 ///
331 /// A position does not move when the layout changes — `KeyCode::Semicolon`
332 /// is where `ø` lives on Norwegian and `ö` on German — so this replaces
333 /// legends rather than rebuilding the grid.
334 ///
335 /// Any half-typed dead key is dropped: a mark waiting for a base character
336 /// means nothing once the layout that was going to supply it has gone.
337 /// Shift, Caps Lock and the third level survive, because they are facts
338 /// about the keyboard rather than about the layout.
339 pub fn set_layout<M: Clone + 'static>(&mut self, ui: &mut Ui<M>, layout: &'static Layout) {
340 if core::ptr::eq(self.layout, layout) {
341 return;
342 }
343 self.layout = layout;
344 // `Composer::set_layout` drops the pending dead key and keeps Caps Lock
345 // and the third level, which is exactly the split wanted: a half-typed
346 // mark belonged to the old layout, and the user's hands have not moved.
347 self.composer.set_layout(layout);
348 self.relabel(ui);
349 }
350
351 /// Moves to the next built-in layout, wrapping.
352 ///
353 /// What the layout key does. Returns the layout it moved to, whose `name`
354 /// is what the key then says.
355 pub fn cycle_layout<M: Clone + 'static>(&mut self, ui: &mut Ui<M>) -> &'static Layout {
356 let next = denise_layout::BUILT_IN
357 .iter()
358 .position(|l| core::ptr::eq(*l, self.layout))
359 .map_or(0, |i| (i + 1) % denise_layout::BUILT_IN.len());
360 let layout = denise_layout::BUILT_IN[next];
361 self.set_layout(ui, layout);
362 layout
363 }
364
365 /// The layout its keys are lettered from.
366 #[inline]
367 pub const fn layout(&self) -> &'static Layout {
368 self.layout
369 }
370
371 /// The node each key was added as, in grid order.
372 ///
373 /// Kept because a layout switch relabels these rather than rebuilding them:
374 /// a position does not move when the layout changes, only its legend does.
375 #[inline]
376 pub fn keys(&self) -> &[(KeyCode, NodeId)] {
377 &self.keys
378 }
379
380 /// Whether the keyboard is on screen.
381 #[inline]
382 pub const fn is_open(&self) -> bool {
383 self.shelf.is_some()
384 }
385
386 /// The height a shelf needs for the whole keyboard, in logical pixels.
387 ///
388 /// [`Keyboard::height`] is this at the keyboard's scale, and is the one an
389 /// application wants; this is the constant it is derived from.
390 pub const LOGICAL_HEIGHT: i32 = ROWS.len() as i32 * (KEY_HEIGHT + KEY_GAP) + KEY_GAP;
391
392 /// The height the whole keyboard occupies, in the surface's own pixels.
393 ///
394 /// [`Self::LOGICAL_HEIGHT`] through [`with_scale`](Self::with_scale). What
395 /// the shelf is pushed at, and what an application subtracts when it wants
396 /// to know how much screen it has left.
397 #[inline]
398 pub fn height(&self) -> i32 {
399 self.scaled(Self::LOGICAL_HEIGHT)
400 }
401
402 /// One logical length in surface pixels.
403 ///
404 /// Through [`Rect::scaled`] rather than a multiplication written again here,
405 /// so a key's edges and the shelf's height round the same way and the bottom
406 /// row does not end a pixel short of the shelf it sits in.
407 #[inline]
408 fn scaled(&self, logical: i32) -> i32 {
409 Rect::new(0, 0, 0, logical).scaled(self.scale).height
410 }
411
412 /// Slides the keyboard up and letters its keys from the current layout.
413 ///
414 /// `on_key` is how a key press reaches the application, the same shape every
415 /// other widget uses to carry a value into a message. The application answers
416 /// by calling [`Keyboard::press`] and handing the result to
417 /// [`Ui::handle`](denise_ui::Ui::handle).
418 ///
419 /// Returns the shelf, or `None` when one is already up — the tree allows one
420 /// at a time.
421 pub fn open<M: Clone + 'static>(
422 &mut self,
423 ui: &mut Ui<M>,
424 on_key: fn(KeyCode) -> M,
425 ) -> Option<NodeId> {
426 if self.shelf.is_some() {
427 return None;
428 }
429 let shelf = ui.push_shelf(Side::Below, self.height())?;
430 let width = ui.size().width as i32;
431 self.build(ui, shelf, width, on_key);
432 self.shelf = Some(shelf);
433 Some(shelf)
434 }
435
436 /// Slides the keyboard out. The keys go with it.
437 ///
438 /// Any half-typed dead key goes too: a mark waiting for a base character
439 /// means nothing once the keyboard that was going to supply it is gone.
440 pub fn close<M: Clone + 'static>(&mut self, ui: &mut Ui<M>) {
441 if self.shelf.take().is_some() {
442 self.keys.clear();
443 self.composer.set_layout(self.layout);
444 ui.close_shelf();
445 }
446 }
447
448 /// Opens or closes the keyboard to follow the focus, once a frame.
449 ///
450 /// The ordinary policy, and the one a panel wants: focus lands on a
451 /// [`TextInput`] and the keyboard appears;
452 /// focus goes anywhere else, or nowhere, and it leaves. Call it in the
453 /// application's turn, beside [`Ui::drain_messages`](denise_ui::Ui::drain_messages).
454 ///
455 /// It answers *is this a text field* by asking the tree for the node as one,
456 /// so there is no list of fields to keep in step with the tree.
457 ///
458 /// A press on a key moves no focus, so the keyboard does not close itself
459 /// mid-word. Escape is the application's to bind: a shelf pushes no scene,
460 /// so the tree does not claim the key and will not close the keyboard for
461 /// you.
462 ///
463 /// An application wanting a different rule — a search box that already has a
464 /// hardware keyboard, a field that should never summon one — ignores this and
465 /// reads [`Ui::focus_changed`](denise_ui::Ui::focus_changed) itself. That is
466 /// the whole of what this does.
467 pub fn follow_focus<M: Clone + 'static>(&mut self, ui: &mut Ui<M>, on_key: fn(KeyCode) -> M) {
468 let Some(focus) = ui.focus_changed() else {
469 return;
470 };
471 let wants = focus.is_some_and(|id| ui.widget::<TextInput<M>>(id).is_some());
472 if wants {
473 self.open(ui, on_key);
474 } else {
475 self.close(ui);
476 }
477 }
478
479 /// One key from the grid, tapped — modifier keys included.
480 ///
481 /// The call an application makes when a key's message arrives, and the one
482 /// that does the right thing whichever key it was: Shift and the third-level
483 /// key change state and relabel the keyboard, everything else types.
484 ///
485 /// Returns the events to hand to [`Ui::handle`](denise_ui::Ui::handle);
486 /// empty for a modifier key, which changes what the *next* press means and
487 /// sends nothing itself.
488 pub fn press_key<M: Clone + 'static>(
489 &mut self,
490 ui: &mut Ui<M>,
491 code: KeyCode,
492 ) -> Vec<InputEvent> {
493 match code {
494 KeyCode::ShiftLeft => {
495 self.tap_shift();
496 self.relabel(ui);
497 Vec::new()
498 }
499 KeyCode::CapsLock => {
500 self.tap_caps();
501 self.relabel(ui);
502 Vec::new()
503 }
504 KeyCode::ControlLeft => {
505 self.tap_ctrl();
506 self.relabel(ui);
507 Vec::new()
508 }
509 KeyCode::AltRight => {
510 self.tap_level3();
511 self.relabel(ui);
512 Vec::new()
513 }
514 LAYOUT_KEY => {
515 self.cycle_layout(ui);
516 Vec::new()
517 }
518 _ => {
519 let spent = self.shift == Shift::Once || self.ctrl;
520 let events = self.press(code);
521 // A one-shot modifier has just been spent, so the keys have to
522 // stop claiming it is still armed.
523 if spent {
524 self.relabel(ui);
525 }
526 events
527 }
528 }
529 }
530
531 /// Collects whatever a held key has earned, once a frame.
532 ///
533 /// Call it beside [`follow_focus`](Self::follow_focus), and hand the result
534 /// to [`Ui::handle`](denise_ui::Ui::handle) the way a key press is handed
535 /// over. Empty on nearly every frame: only Backspace repeats, and only while
536 /// a finger is actually on it.
537 ///
538 /// The events are the ones a real keyboard sends for an auto-repeat —
539 /// [`InputEvent::Key`] with `repeat: true`, and whatever that types — which
540 /// is what lets a widget tell a repeat from a deliberate second press. A
541 /// `TextInput` inserts both; something that must not act twice on one
542 /// gesture can look.
543 ///
544 /// **Nothing is polled.** A repeating key asks the tree to wake it while it
545 /// is held and stops asking the moment it is released, so a panel with
546 /// nobody touching it schedules nothing — this call simply finds a tally of
547 /// nought and returns.
548 ///
549 /// [`InputEvent::Key`]: denise::InputEvent::Key
550 pub fn tick<M: Clone + 'static>(&mut self, ui: &mut Ui<M>, now_ms: u64) -> Vec<InputEvent> {
551 let _ = now_ms;
552 let mut out = Vec::new();
553 // Collected by position rather than from one remembered key, because
554 // "which key is held" is the tree's fact and not this crate's.
555 //
556 // Read first, take second, and the split is not tidiness: `widget_mut`
557 // damages the node it hands out, because it cannot know whether the
558 // caller changed anything. Asking sixty keys mutably once a frame
559 // therefore repainted the whole keyboard on every frame anything else
560 // woke the tree for — which on a panel is a keyboard that flickers.
561 let owed: Vec<(KeyCode, NodeId)> = self
562 .keys
563 .iter()
564 .copied()
565 .filter(|&(_, node)| {
566 ui.widget::<Button<M>>(node)
567 .is_some_and(|button| button.repeats_pending() > 0)
568 })
569 .collect();
570 let held: Vec<(KeyCode, u32)> = owed
571 .into_iter()
572 .filter_map(|(code, node)| {
573 let repeats = ui.widget_mut::<Button<M>>(node)?.take_repeats();
574 (repeats > 0).then_some((code, repeats))
575 })
576 .collect();
577 for (code, repeats) in held {
578 for _ in 0..repeats {
579 out.extend(self.press_repeat(code));
580 }
581 }
582 self.offer_alternates(ui);
583 out
584 }
585
586 /// Opens a held key's alternates, once it has been held long enough.
587 ///
588 /// A strip of characters above the key, drawn as ordinary nodes at the top
589 /// of the shelf rather than as a popup. That is not a shortcut: a popup
590 /// pushes a scene, a pushed scene cancels whatever press it covers, and the
591 /// press it would cancel here is the one holding the key that opened it.
592 fn offer_alternates<M: Clone + 'static>(&mut self, ui: &mut Ui<M>) {
593 if self.offering.is_some() || self.shelf.is_none() {
594 return;
595 }
596 // The one key held past the threshold, read without writing.
597 let ready = self.keys.iter().copied().find(|&(_, node)| {
598 ui.widget::<Button<M>>(node)
599 .and_then(Button::held_ms)
600 .is_some_and(|held| held >= HOLD_MS)
601 });
602 let Some((code, key)) = ready else {
603 return;
604 };
605 self.open_alternates(ui, code, key);
606 }
607
608 /// Builds the strip for a key, wherever the decision to came from.
609 fn open_alternates<M: Clone + 'static>(&mut self, ui: &mut Ui<M>, code: KeyCode, key: NodeId) {
610 if self.offering.is_some() {
611 return;
612 }
613 let Some(shelf) = self.shelf else {
614 return;
615 };
616 let Some(base) = self.legend(code) else {
617 return;
618 };
619 let choices: Vec<char> = self.layout.alternates_for(base).collect();
620 if choices.is_empty() {
621 return;
622 }
623 let Some(bounds) = ui.layout(key) else {
624 return;
625 };
626
627 // Centred over the key, and kept on screen: a strip that ran off the
628 // edge would put half its choices where no finger can reach them.
629 //
630 // The frame is not decoration. Drawn flush and in the keys' own colours
631 // this landed as *another row of the keyboard* — five accented letters
632 // sitting on the digits, indistinguishable from them, so the one thing
633 // the gesture has to say (these five, right now, and not the sixty
634 // behind them) was the one thing it did not. So: a border in the accent
635 // colour, a margin wide enough to read as an edge rather than a seam,
636 // and a clear gap between the strip and the row it floats over.
637 let cell = bounds.height;
638 let pad = self.scaled(STRIP_PAD);
639 let width = cell * choices.len() as i32 + pad * 2;
640 let height = cell + pad * 2;
641 let x = (bounds.x + bounds.width / 2 - width / 2)
642 .max(0)
643 .min((ui.size().width as i32 - width).max(0));
644 let y = (bounds.y - height - self.scaled(KEY_GAP) * 2).max(0);
645
646 let Some(strip) = ui.add(
647 shelf,
648 Panel::filled(Role::Base300)
649 .backdrop()
650 .with_radius(denise::theme::Radius::Box)
651 .with_border(Role::Primary, self.scaled(STRIP_BORDER).max(1)),
652 Rect::new(x, y, width, height),
653 ) else {
654 return;
655 };
656 let mut nodes = Vec::with_capacity(choices.len());
657 for (i, ch) in choices.iter().enumerate() {
658 // Under the strip rather than beside it, so that taking the strip
659 // away takes the choices with it: `Ui::remove` drops a subtree, and
660 // a choice parented to the shelf would outlive the gesture that
661 // made it and sit there being pressable.
662 let at = Rect::new(pad + cell * i as i32, pad, cell, cell);
663 if let Some(node) = ui.add(
664 strip,
665 // Inert, and that is the design rather than an omission: the
666 // press that opened this strip is still down on the key, so the
667 // tree never presses one of these and they would never emit.
668 // The choice is made by where the finger lifts, which
669 // `Keyboard::handle` answers.
670 Button::<M>::inert(ch.to_string())
671 .no_focus()
672 .with_role(Role::Base100)
673 .with_style(self.style),
674 at,
675 ) {
676 nodes.push((*ch, node));
677 }
678 }
679 self.offering = Some(Offering {
680 strip,
681 choices: nodes,
682 over: None,
683 });
684 }
685
686 /// Input the keyboard answers itself, before the tree sees it.
687 ///
688 /// Only the alternates gesture needs this, and it needs it because the
689 /// gesture has no precedent in the tree: the press that opened the strip is
690 /// still down on the *key*, so the tree quite correctly keeps sending
691 /// everything there, and the choice is made by where the finger lifts
692 /// instead. The keyboard therefore does its own hit test against the strip
693 /// it drew.
694 ///
695 /// Call it beside [`Ui::handle`](denise_ui::Ui::handle), with the same
696 /// events. Returns what to type, which is empty on nearly every call —
697 /// nothing happens here unless a strip is open.
698 ///
699 /// A character chosen this way arrives as [`InputEvent::Text`] alone, with
700 /// no [`InputEvent::Key`] around it. That is the honest shape: `é` is not at
701 /// a position on this keyboard, nothing pressed a key to get it, and a
702 /// binding watching for keys should not think one was pressed.
703 ///
704 /// Lifting anywhere else ends the gesture and types nothing — except on
705 /// the key itself, which types what it always types. That is why the strip
706 /// does not repeat the base character among its choices: the key is still
707 /// there underneath it, still where the finger already is, so a hold opened
708 /// by accident is undone by not moving.
709 ///
710 /// [`InputEvent::Text`]: denise::InputEvent::Text
711 /// [`InputEvent::Key`]: denise::InputEvent::Key
712 pub fn handle<M: Clone + 'static>(
713 &mut self,
714 ui: &mut Ui<M>,
715 events: &[InputEvent],
716 ) -> Vec<InputEvent> {
717 let mut out = Vec::new();
718 for event in events {
719 let Some(offering) = self.offering.as_ref() else {
720 return out;
721 };
722 match event {
723 InputEvent::PointerMoved { position } | InputEvent::TouchMoved { position, .. } => {
724 let at = self.choice_at(ui, *position);
725 if let Some(offering) = self.offering.as_mut()
726 && offering.over != at
727 {
728 offering.over = at;
729 self.highlight(ui);
730 }
731 }
732 InputEvent::PointerButton {
733 state: ElementState::Up,
734 position,
735 ..
736 }
737 | InputEvent::TouchUp {
738 position,
739 cancelled: false,
740 ..
741 } => {
742 let chosen = self
743 .choice_at(ui, *position)
744 .and_then(|i| offering.choices.get(i))
745 .map(|(ch, _)| *ch);
746 self.close_offering(ui);
747 if let Some(ch) = chosen {
748 out.push(InputEvent::Text { ch });
749 }
750 }
751 // A sequence the system took away is not a choice, wherever the
752 // last position happened to land. The strip goes, and nothing
753 // is typed — the same answer as lifting off it, because that is
754 // what a cancelled gesture is.
755 InputEvent::TouchUp {
756 cancelled: true, ..
757 } => self.close_offering(ui),
758 _ => {}
759 }
760 }
761 out
762 }
763
764 /// Which choice a point is over, if any.
765 fn choice_at<M: Clone + 'static>(&self, ui: &Ui<M>, at: Point) -> Option<usize> {
766 let offering = self.offering.as_ref()?;
767 offering
768 .choices
769 .iter()
770 .position(|&(_, node)| ui.bounds(node).is_some_and(|b| b.contains(at)))
771 }
772
773 /// Paints the choice under the finger differently from the rest.
774 fn highlight<M: Clone + 'static>(&mut self, ui: &mut Ui<M>) {
775 let Some(offering) = self.offering.as_ref() else {
776 return;
777 };
778 let over = offering.over;
779 let nodes: Vec<(usize, NodeId)> = offering
780 .choices
781 .iter()
782 .enumerate()
783 .map(|(i, &(_, node))| (i, node))
784 .collect();
785 for (i, node) in nodes {
786 let role = if Some(i) == over {
787 Role::Primary
788 } else {
789 Role::Base100
790 };
791 // Read before writing: `widget_mut` repaints whatever it hands out,
792 // and a strip that repainted every key on every pointer move is the
793 // flicker bug in miniature.
794 let same = ui
795 .widget::<Button<M>>(node)
796 .is_some_and(|button| button.role() == role);
797 if !same && let Some(button) = ui.widget_mut::<Button<M>>(node) {
798 button.set_role(role);
799 }
800 }
801 }
802
803 /// Takes the strip away, leaving the key it came from alone.
804 fn close_offering<M: Clone + 'static>(&mut self, ui: &mut Ui<M>) {
805 if let Some(offering) = self.offering.take() {
806 ui.remove(offering.strip);
807 }
808 }
809
810 /// Opens a key's alternates without waiting for a finger.
811 ///
812 /// For a headless run — a snapshot, a test — which has no finger to hold
813 /// anything with and no clock running while it does. The gesture itself
814 /// goes through [`tick`](Self::tick) and a real press.
815 pub fn offer_for_test<M: Clone + 'static>(&mut self, ui: &mut Ui<M>, code: KeyCode) {
816 let Some(&(_, key)) = self.keys.iter().find(|&&(c, _)| c == code) else {
817 return;
818 };
819 self.open_alternates(ui, code, key);
820 }
821
822 /// The characters currently on offer, and the nodes showing them.
823 ///
824 /// Empty unless a key is being held past [`HOLD_MS`]. Given out so a test
825 /// can aim at one, and so an application that wants to drive the gesture
826 /// some other way can.
827 pub fn choices(&self) -> &[(char, NodeId)] {
828 self.offering.as_ref().map_or(&[], |o| &o.choices)
829 }
830
831 /// Whether a held key is offering its alternates.
832 #[inline]
833 pub const fn offering(&self) -> bool {
834 self.offering.is_some()
835 }
836
837 /// One auto-repeat of a key already down.
838 ///
839 /// [`press`](Self::press) with `repeat: true`, and without the one-shot
840 /// Shift bookkeeping: a repeat is the *same* press arriving again, so it
841 /// cannot spend a shift that the first press already spent.
842 pub fn press_repeat(&mut self, code: KeyCode) -> Vec<InputEvent> {
843 let mut out = Vec::with_capacity(3);
844 let modifiers = self.modifiers();
845 for state in [ElementState::Down, ElementState::Up] {
846 out.push(InputEvent::Key {
847 code,
848 state,
849 repeat: true,
850 modifiers,
851 });
852 let composed = self.composer.feed(code, state, modifiers);
853 for &ch in composed.as_slice() {
854 out.push(InputEvent::Text { ch });
855 }
856 }
857 out
858 }
859
860 /// One key, tapped: the events a real keyboard would have sent.
861 ///
862 /// [`InputEvent::Key`] down, then whatever that typed as
863 /// [`InputEvent::Text`], then [`InputEvent::Key`] up — the order the
864 /// hardware path uses, so that a binding on the key runs before any text
865 /// arrives and a field can insert every character it sees without filtering.
866 ///
867 /// A dead key types nothing and returns just the two `Key` events; the mark
868 /// arrives folded into the next character, or beside it when the two cannot
869 /// combine.
870 ///
871 /// [`InputEvent::Key`]: denise::InputEvent::Key
872 /// [`InputEvent::Text`]: denise::InputEvent::Text
873 pub fn press(&mut self, code: KeyCode) -> Vec<InputEvent> {
874 let mut out = Vec::with_capacity(3);
875 let modifiers = self.modifiers();
876 for state in [ElementState::Down, ElementState::Up] {
877 out.push(InputEvent::Key {
878 code,
879 state,
880 repeat: false,
881 modifiers,
882 });
883 let composed = self.composer.feed(code, state, modifiers);
884 for &ch in composed.as_slice() {
885 out.push(InputEvent::Text { ch });
886 }
887 }
888 // A one-shot modifier is spent on the character it modified. Doing this
889 // after the feed rather than before is what makes it apply to exactly
890 // one key.
891 if self.shift == Shift::Once {
892 self.shift = Shift::Off;
893 }
894 self.ctrl = false;
895 out
896 }
897
898 /// Taps the Shift key: off, then once, then locked, then off again.
899 ///
900 /// Returns the state it moved to. The caller relabels with
901 /// [`Keyboard::relabel`] — or lets [`Keyboard::press_key`] do both.
902 pub fn tap_shift(&mut self) -> Shift {
903 self.shift = self.shift.next();
904 self.shift
905 }
906
907 /// Caps Lock on or off. Returns the state it moved to.
908 ///
909 /// A latch of its own rather than a third state of Shift, which is what a
910 /// real keyboard does and what the composer already modelled: it applies to
911 /// letters and spares the digit row, so a locked keyboard types `1` and not
912 /// `!`. Told through the key stream, exactly as a real Caps Lock reaches it.
913 pub fn tap_caps(&mut self) -> bool {
914 let modifiers = self.modifiers();
915 self.composer
916 .feed(KeyCode::CapsLock, ElementState::Down, modifiers);
917 self.composer
918 .feed(KeyCode::CapsLock, ElementState::Up, modifiers);
919 self.composer.caps_lock()
920 }
921
922 /// Whether Caps Lock is on.
923 #[inline]
924 pub fn caps(&self) -> bool {
925 self.composer.caps_lock()
926 }
927
928 /// Ctrl for the next key, on or off. Returns the state it moved to.
929 ///
930 /// One-shot like Shift, and spent by the next key that types: a modifier
931 /// that stayed on would be a keyboard that could not type a plain letter
932 /// again without somebody noticing why.
933 pub fn tap_ctrl(&mut self) -> bool {
934 self.ctrl = !self.ctrl;
935 self.ctrl
936 }
937
938 /// Whether Ctrl is armed for the next key.
939 #[inline]
940 pub const fn ctrl(&self) -> bool {
941 self.ctrl
942 }
943
944 /// Taps the third-level key, the one a physical keyboard spells `AltGr`.
945 ///
946 /// This is the symbol page, and it is the layout's own third level rather
947 /// than a grid of symbols chosen here: `@` is `AltGr`+`2` on a Norwegian
948 /// keyboard and `Shift`+`2` on a US one, and a fixed grid would be wrong on
949 /// one of them. It latches rather than being held, because a finger cannot
950 /// hold one key and press another.
951 ///
952 /// Returns whether the third level is now on.
953 pub fn tap_level3(&mut self) -> bool {
954 self.level3 = !self.level3;
955 // The composer tracks this from the key stream exactly as it does for a
956 // real AltGr, so it is told the same way.
957 let state = if self.level3 {
958 ElementState::Down
959 } else {
960 ElementState::Up
961 };
962 self.composer
963 .feed(KeyCode::AltRight, state, self.modifiers());
964 self.level3
965 }
966
967 /// The screen rectangle the keyboard is covering, or `None` when it is not
968 /// up.
969 ///
970 /// Focusing a field already scrolls it clear of this, where the field is in
971 /// something that scrolls and has somewhere to scroll to. This is for when
972 /// it is not: a form at fixed rectangles has no scroll to give, and getting
973 /// a field out from under the keyboard means the application moving
974 /// something — shrinking a viewport by this height, or sliding a panel up.
975 ///
976 /// The keyboard's resting place from the moment it is opened, so an
977 /// application acting on it during the slide aims where the keyboard is
978 /// going.
979 #[inline]
980 pub fn occluded<M: Clone + 'static>(&self, ui: &Ui<M>) -> Option<Rect> {
981 self.shelf.and(ui.occluded())
982 }
983
984 /// The state of the Shift key.
985 #[inline]
986 pub const fn shift(&self) -> Shift {
987 self.shift
988 }
989
990 /// Whether the third level — the layout's `AltGr` — is showing.
991 #[inline]
992 pub const fn level3(&self) -> bool {
993 self.level3
994 }
995
996 /// The modifiers a key press reports right now.
997 fn modifiers(&self) -> Modifiers {
998 let mut modifiers = self.modifiers;
999 if self.shift.holds_shift() {
1000 modifiers |= Modifiers::SHIFT;
1001 }
1002 if self.ctrl {
1003 modifiers |= Modifiers::CTRL;
1004 }
1005 modifiers
1006 }
1007
1008 /// What to print on a key, at the level the keyboard is currently showing.
1009 ///
1010 /// A dead key shows its mark, which is what the user is about to be holding.
1011 /// What to print on a key: exactly what pressing it would produce.
1012 ///
1013 /// Asked of the composer rather than worked out here, so Caps Lock sparing
1014 /// the digit row and the third level are right by construction instead of by
1015 /// a rule repeated in two places.
1016 fn legend(&self, code: KeyCode) -> Option<char> {
1017 match self.composer.output_for(code, self.shift.holds_shift()) {
1018 Output::Char(ch) | Output::Dead(ch) => Some(ch),
1019 Output::None => None,
1020 }
1021 }
1022
1023 /// What a key says right now.
1024 ///
1025 /// The three keys whose legends come from the keyboard's own state rather
1026 /// than from the layout, then the layout's answer. One function because
1027 /// [`build`](Self::build) and [`relabel`](Self::relabel) must agree: when
1028 /// they did not, the layout key came up blank and only found its name after
1029 /// something else had caused a relabel.
1030 fn label_for(&self, code: KeyCode) -> String {
1031 match code {
1032 KeyCode::ShiftLeft => self.shift.legend().to_string(),
1033 KeyCode::CapsLock => if self.caps() { "CAPS" } else { "caps" }.to_string(),
1034 KeyCode::ControlLeft => if self.ctrl { "CTRL" } else { "ctrl" }.to_string(),
1035 KeyCode::AltRight => LEVEL3_LEGEND.to_string(),
1036 LAYOUT_KEY => self.layout.name.to_string(),
1037 _ => grid::legend_of(code)
1038 .map(str::to_string)
1039 .or_else(|| self.legend(code).map(|ch| ch.to_string()))
1040 .unwrap_or_default(),
1041 }
1042 }
1043
1044 /// What is printed small in a key's top-right corner, if anything.
1045 ///
1046 /// What the key would type with Shift held — the `!` over the `1`, the `?`
1047 /// over the `+` — which is the whole reason a real keyboard prints it: you
1048 /// cannot discover Shift by pressing Shift, because pressing it is what
1049 /// changes the legend.
1050 ///
1051 /// **Numbers and punctuation only.** A letter's shifted form is its own
1052 /// capital and tells nobody anything, and forty keys each carrying a second
1053 /// glyph is a keyboard that reads as noise. This is the rule the keyboards
1054 /// it is shaped after use, and the reason they use it.
1055 ///
1056 /// Empty while Shift is held, because the main legend has already become
1057 /// the shifted character and printing it twice on one key says nothing.
1058 fn corner_for(&self, code: KeyCode) -> String {
1059 if code == LAYOUT_KEY {
1060 // The one corner that is not a shifted character. The key wears a
1061 // globe, which says what it is for and cannot say which layout is
1062 // live — so the name it used to carry as its legend moves here
1063 // rather than being dropped. On a panel with no other keyboard,
1064 // "am I typing Norwegian?" is a question the keyboard itself has to
1065 // answer.
1066 return self.layout.name.to_string();
1067 }
1068 if grid::legend_of(code).is_some() {
1069 // A key with a fixed word on it — back, enter, tab — types nothing
1070 // and has no other state to advertise.
1071 return String::new();
1072 }
1073 let Some(base) = self.legend(code) else {
1074 return String::new();
1075 };
1076 if base.is_alphabetic() {
1077 return String::new();
1078 }
1079 let shifted = match self.composer.output_for(code, true) {
1080 Output::Char(ch) | Output::Dead(ch) => ch,
1081 Output::None => return String::new(),
1082 };
1083 if shifted == base {
1084 return String::new();
1085 }
1086 shifted.to_string()
1087 }
1088
1089 /// Rewrites every key's legend for the current shift and level.
1090 ///
1091 /// A position does not move when a modifier changes — only what it types —
1092 /// so this replaces labels on the keys that are already there.
1093 pub fn relabel<M: Clone + 'static>(&mut self, ui: &mut Ui<M>) {
1094 for &(code, node) in &self.keys {
1095 let label = self.label_for(code);
1096 let corner = self.corner_for(code);
1097 if let Some(button) = ui.widget_mut::<Button<M>>(node) {
1098 button.set_label(label);
1099 button.set_corner(corner);
1100 }
1101 }
1102 }
1103
1104 /// Lays the rows out and adds a button per key.
1105 ///
1106 /// Explicit rectangles, because the toolkit has no layout engine and a
1107 /// keyboard is the case that wants none: a fixed grid, measured once.
1108 fn build<M: Clone + 'static>(
1109 &mut self,
1110 ui: &mut Ui<M>,
1111 shelf: NodeId,
1112 width: i32,
1113 on_key: fn(KeyCode) -> M,
1114 ) {
1115 // A backdrop first, and it is not decoration: a shelf is a bare
1116 // container, so without one the page underneath shows through the gaps
1117 // between the keys — which on a browser is a paragraph of text running
1118 // between the rows. Added before the keys so it paints behind them.
1119 //
1120 // A *backdrop* and not an ordinary panel, which is what stops a finger
1121 // landing in the gap between two keys from falling through to the page
1122 // and taking the focus with it — a near-miss used to dismiss the whole
1123 // keyboard.
1124 //
1125 // `Base300` rather than `Base200`, which is two steps from the keys
1126 // rather than one: at one step a light theme's keys barely lift off the
1127 // deck and the whole thing reads as a flat grey slab. The dark themes
1128 // were always fine; this is the light one catching up.
1129 ui.add(
1130 shelf,
1131 Panel::filled(Role::Base300).backdrop(),
1132 Rect::new(0, 0, width, self.height()),
1133 );
1134
1135 // The surface's width, back in the units the grid below is written in.
1136 // Laying out logically and scaling each rectangle at the end is what
1137 // keeps a row reaching both edges at 1.5x: `Rect::scaled` scales
1138 // *edges*, so keys that were a gap apart still are.
1139 let width = if self.scale > 0.0 {
1140 Rect::new(0, 0, width, 0).scaled(1.0 / self.scale).width
1141 } else {
1142 width
1143 };
1144 for (r, row) in ROWS.iter().enumerate() {
1145 let y = KEY_GAP + r as i32 * (KEY_HEIGHT + KEY_GAP);
1146 // Every key in a row shares the leftover width, so a row of ten and
1147 // a row of three both reach both edges.
1148 //
1149 // Each key's edges are placed from its running share of the row
1150 // rather than from a rounded width per unit, and the difference is
1151 // the whole point: a width of `leftover / units` throws away the
1152 // remainder once per key, and eleven keys of it leaves the row
1153 // visibly short of the edge it was supposed to reach. Placing edges
1154 // spends the remainder across the row instead, a pixel at a time.
1155 let count = row.keys.len() as i32;
1156 let units: i32 = row.keys.iter().map(|k| k.units).sum::<i32>().max(1);
1157 let leftover = (width - KEY_GAP * (count + 1)).max(count);
1158 let mut done = 0;
1159 for (i, key) in row.keys.iter().enumerate() {
1160 let gaps = KEY_GAP * (i as i32 + 1);
1161 let x = gaps + leftover * done / units;
1162 done += key.units;
1163 let w = gaps + leftover * done / units - x;
1164 let mut button = Button::new(self.label_for(key.code), on_key(key.code))
1165 .no_focus()
1166 .with_role(role_of(key.code))
1167 .with_style(self.style)
1168 .with_corner(self.corner_for(key.code));
1169 // The word stays the button's label even where a picture is
1170 // drawn over it: that is what the key still reports, and what a
1171 // test reads.
1172 if let Some(icon) = grid::icon_of(key.code) {
1173 button = button.with_icon(icon);
1174 }
1175 if key.repeats {
1176 button = button.with_repeat(REPEAT_DELAY_MS, REPEAT_INTERVAL_MS);
1177 } else if grid::legend_of(key.code).is_none() {
1178 // Only the keys that type a character can offer alternates,
1179 // so only they need to be asked how long they have been
1180 // held. A key with a word on it has nothing to offer and
1181 // stays free.
1182 button = button.watching_hold();
1183 }
1184 if let Some(node) = ui.add(
1185 shelf,
1186 button,
1187 Rect::new(x, y, w, KEY_HEIGHT).scaled(self.scale),
1188 ) {
1189 self.keys.push((key.code, node));
1190 }
1191 }
1192 }
1193 }
1194}
1195
1196/// What colour a key wears.
1197///
1198/// Two kinds, and the split is what the key does rather than what it looks
1199/// like: keys that type a character are the neutral field the eye skims over,
1200/// and keys that change what the *next* press means stand out from them,
1201/// because those are the ones a user has to find deliberately. Enter is with
1202/// the second group for the same reason — it is the key with a consequence.
1203///
1204/// [`Role::Primary`] is the toolkit's default for a button and is wrong for
1205/// every key here: forty of them shouting at once is not emphasis.
1206fn role_of(code: KeyCode) -> Role {
1207 match code {
1208 KeyCode::ShiftLeft
1209 | KeyCode::CapsLock
1210 | KeyCode::ControlLeft
1211 | KeyCode::AltRight
1212 | KeyCode::Backspace
1213 | KeyCode::Tab
1214 | KeyCode::Escape
1215 | KeyCode::ArrowLeft
1216 | KeyCode::ArrowRight
1217 | LAYOUT_KEY => Role::Neutral,
1218 KeyCode::Enter => Role::Primary,
1219 _ => Role::Base100,
1220 }
1221}
1222
1223/// Compiles the examples in this crate's README, so they cannot drift from the API
1224/// they claim to demonstrate. Never built except under `cargo test --doc`.
1225#[cfg(doctest)]
1226#[doc = include_str!("../README.md")]
1227struct Readme;
1228
1229/// The alternates a held key is offering, while it offers them.
1230struct Offering {
1231 /// The strip's backdrop, removed with the rest.
1232 strip: NodeId,
1233 /// One node per character, in the order they are shown.
1234 choices: Vec<(char, NodeId)>,
1235 /// Which one the finger is over, if any.
1236 over: Option<usize>,
1237}