Skip to main content

recovery_ui/
keyboard.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::button::ButtonMessages;
6use crate::constants::constants::{
7    ACTIVE_BUTTON_COLOR, BACKGROUND_GREY, BACKGROUND_WHITE, BUTTON_COLOR, KEY_SPECIAL_COLOR,
8    TEXT_COLOR, TEXT_FIELD_FONT_SIZE,
9};
10use crate::keys::{get_accent, Accent, Key, SpecialKey, KEYBOARD};
11#[cfg(not(feature = "ota_ui"))]
12use crate::proxy_view_assistant::ProxyMessages;
13use crate::text_field::{SceneBuilderTextFieldExt, TextField, TextFieldOptions, TextVisibility};
14use anyhow::Error;
15use carnelian::color::Color;
16use carnelian::drawing::{measure_text_width, FontFace};
17use carnelian::input::{self};
18use carnelian::render::Context as RenderContext;
19use carnelian::scene::facets::{
20    FacetId, SetColorMessage, SetTextMessage, TextFacetOptions, TextHorizontalAlignment,
21    TextVerticalAlignment,
22};
23use carnelian::scene::layout::{
24    Alignment, CrossAxisAlignment, Flex, FlexOptions, MainAxisAlignment, MainAxisSize, Stack,
25    StackOptions,
26};
27use carnelian::scene::scene::{Scene, SceneBuilder};
28use carnelian::{
29    make_message, AppSender, Coord, Message, MessageTarget, Point, Size, ViewAssistant,
30    ViewAssistantContext, ViewKey,
31};
32use euclid::{size2, Size2D};
33#[cfg(feature = "ota_ui")]
34use recovery_util::ota::state_machine::Event as StateMachineEvent;
35use std::collections::VecDeque;
36use std::hash::{Hash, Hasher};
37use zx::{Event, MonotonicDuration, MonotonicInstant};
38
39type FieldName = String;
40type InputText = String;
41
42// TODO(b/259497403): Calculate hardcoded values from screen width
43const TEXT_FIELD_WIDTH: f32 = 960.0;
44
45/// enum that defines all messages sent with `App::queue_message` that
46/// the button view assistant will understand and process.
47pub enum KeyMessages {
48    Pressed(&'static Key, MonotonicInstant, String),
49}
50
51#[allow(unused)]
52pub enum KeyboardMessages {
53    NoInput,
54    Result(FieldName, InputText),
55}
56
57struct KeyButton {
58    bg_color: Color,
59    bg_color_active: Color,
60    bg_color_disabled: Color,
61    bg_color_special: Color,
62    fg_color: Color,
63    fg_color_disabled: Color,
64    tracking_pointer: Option<input::pointer::PointerId>,
65    active: bool,
66    focused: bool,
67    // Used for shifted and accent keys
68    special_action: bool,
69    key: &'static Key,
70    label_text: String,
71    background: FacetId,
72    label: FacetId,
73}
74
75impl KeyButton {
76    pub fn new(
77        key: &'static Key,
78        font_face: FontFace,
79        font_size: f32,
80        padding: f32,
81        builder: &mut SceneBuilder,
82    ) -> Result<KeyButton, Error> {
83        let options = StackOptions { alignment: Alignment::center(), ..StackOptions::default() };
84        builder.start_group("key_button", Stack::with_options_ptr(options));
85        let text = match key {
86            Key::Letter(letter_key) => &letter_key.lower,
87            Key::Special(_special_key, text) => text,
88        };
89        let label = builder.text(
90            font_face.clone(),
91            text,
92            font_size,
93            Point::zero(),
94            TextFacetOptions {
95                color: TEXT_COLOR,
96                horizontal_alignment: TextHorizontalAlignment::Left,
97                vertical_alignment: TextVerticalAlignment::Top,
98                ..TextFacetOptions::default()
99            },
100        );
101        let bg_size = match key {
102            Key::Letter(_letter_key) => size2(font_size + padding, font_size + padding),
103            Key::Special(_special_key, text) => {
104                let label_width = measure_text_width(&font_face, font_size, text);
105                size2(label_width + padding * 1.5, font_size + padding)
106            }
107        };
108        let corner: Coord = Coord::from(5.0);
109        let background = builder.rounded_rectangle(bg_size, corner, BUTTON_COLOR);
110        builder.end_group();
111        let button = KeyButton {
112            key,
113            fg_color: TEXT_COLOR,
114            bg_color: BACKGROUND_GREY,
115            bg_color_active: ACTIVE_BUTTON_COLOR,
116            fg_color_disabled: TEXT_COLOR,
117            bg_color_disabled: BACKGROUND_GREY,
118            bg_color_special: KEY_SPECIAL_COLOR,
119            tracking_pointer: None,
120            active: false,
121            focused: false,
122            special_action: false,
123            label_text: text.to_string(),
124            background,
125            label,
126        };
127
128        Ok(button)
129    }
130
131    fn update_button_bg_color(&mut self, scene: &mut Scene) {
132        let (label_color, color) = if self.focused {
133            if self.special_action {
134                (self.fg_color, self.bg_color_special)
135            } else if self.active {
136                (self.fg_color, self.bg_color_active)
137            } else {
138                (self.fg_color, self.bg_color)
139            }
140        } else {
141            (self.fg_color_disabled, self.bg_color_disabled)
142        };
143        scene.send_message(&self.background, Box::new(SetColorMessage { color }));
144        scene.send_message(&self.label, Box::new(SetColorMessage { color: label_color }));
145    }
146
147    fn set_label(&mut self, scene: &mut Scene, shift: bool, alt: bool, _symbol: bool) {
148        if let Key::Letter(key) = self.key {
149            self.label_text = if shift {
150                key.upper
151            } else if alt {
152                key.alt
153            } else {
154                key.lower
155            }
156            .to_string();
157            scene.send_message(
158                &self.label,
159                Box::new(SetTextMessage { text: self.label_text.clone() }),
160            );
161        }
162    }
163
164    pub fn set_accented_char(
165        &mut self,
166        scene: &mut Scene,
167        accent_char: &Accent,
168        state_shift: bool,
169    ) {
170        self.label_text =
171            if state_shift { accent_char.upper } else { accent_char.lower }.to_string();
172        scene.send_message(&self.label, Box::new(SetTextMessage { text: self.label_text.clone() }));
173    }
174
175    pub fn set_special_action(&mut self, scene: &mut Scene, special_action: bool) {
176        if self.special_action != special_action {
177            self.special_action = special_action;
178            self.update_button_bg_color(scene);
179        }
180    }
181
182    fn set_active(&mut self, scene: &mut Scene, active: bool) {
183        if self.active != active {
184            self.active = active;
185            self.update_button_bg_color(scene);
186        }
187    }
188
189    pub fn set_focused(&mut self, scene: &mut Scene, focused: bool) {
190        if focused != self.focused {
191            self.focused = focused;
192            self.active = false;
193            self.update_button_bg_color(scene);
194            if !focused {
195                self.tracking_pointer = None;
196            }
197        }
198    }
199
200    pub fn handle_pointer_event(
201        &mut self,
202        scene: &mut Scene,
203        context: &mut ViewAssistantContext,
204        pointer_event: &input::pointer::Event,
205    ) {
206        if !self.focused {
207            return;
208        }
209        let bounds = scene.get_facet_bounds(&self.background);
210
211        if self.tracking_pointer.is_none() {
212            match pointer_event.phase {
213                input::pointer::Phase::Down(location) => {
214                    self.set_active(scene, bounds.contains(location.to_f32()));
215                    if self.active {
216                        self.tracking_pointer = Some(pointer_event.pointer_id.clone());
217                    }
218                }
219                _ => (),
220            }
221        } else {
222            let tracking_pointer = self.tracking_pointer.as_ref().expect("tracking_pointer");
223            if tracking_pointer == &pointer_event.pointer_id {
224                match pointer_event.phase {
225                    input::pointer::Phase::Moved(location) => {
226                        self.set_active(scene, bounds.contains(location.to_f32()));
227                    }
228                    input::pointer::Phase::Up => {
229                        if self.active {
230                            context.queue_message(make_message(KeyMessages::Pressed(
231                                self.key,
232                                MonotonicInstant::get(),
233                                self.label_text.clone(),
234                            )));
235                        }
236                        self.tracking_pointer = None;
237                        self.set_active(scene, false);
238                    }
239                    input::pointer::Phase::Remove => {
240                        self.set_active(scene, false);
241                        self.tracking_pointer = None;
242                    }
243                    input::pointer::Phase::Cancel => {
244                        self.set_active(scene, false);
245                        self.tracking_pointer = None;
246                    }
247                    _ => (),
248                }
249            }
250        }
251    }
252}
253
254impl PartialEq for KeyButton {
255    fn eq(&self, other: &Self) -> bool {
256        self.label_text == other.label_text
257    }
258}
259
260impl Eq for KeyButton {}
261
262impl Hash for KeyButton {
263    fn hash<H: Hasher>(&self, state: &mut H) {
264        self.label_text.hash(state);
265    }
266}
267
268pub struct SceneDetails {
269    text_field: TextField,
270    buttons: VecDeque<KeyButton>,
271    pub(crate) scene: Scene,
272}
273
274pub struct KeyboardViewAssistant {
275    font_face: FontFace,
276    app_sender: AppSender,
277    view_key: ViewKey,
278    focused: bool,
279    field_name: FieldName,
280    user_text: InputText,
281    privacy: TextVisibility,
282    scene_details: Option<SceneDetails>,
283    state_shift: bool,
284    shift_time: MonotonicInstant,
285    sticky_shift: bool,
286    accent_key: Option<&'static Key>,
287    state_alt: bool,
288    state_symbols: bool,
289}
290
291impl KeyboardViewAssistant {
292    pub fn new(
293        app_sender: AppSender,
294        view_key: ViewKey,
295        font_face: FontFace,
296    ) -> Result<KeyboardViewAssistant, Error> {
297        Ok(KeyboardViewAssistant {
298            font_face,
299            app_sender: app_sender,
300            view_key,
301            focused: false,
302            field_name: String::new(),
303            user_text: String::new(),
304            privacy: TextVisibility::Always,
305            scene_details: None,
306            state_shift: false,
307            shift_time: MonotonicInstant::ZERO,
308            sticky_shift: false,
309            state_alt: false,
310            accent_key: None,
311            state_symbols: false,
312        })
313    }
314
315    pub fn set_field_name(&mut self, field_name: FieldName) {
316        self.field_name = field_name.clone();
317        if let Some(scene_details) = &mut self.scene_details {
318            scene_details.text_field.set_title(field_name);
319        }
320    }
321
322    pub fn set_text_field(&mut self, text: InputText) {
323        self.user_text = text.clone();
324        if let Some(scene_details) = &mut self.scene_details {
325            scene_details.text_field.set_text(text);
326        }
327    }
328
329    pub fn set_privacy(&mut self, privacy: TextVisibility) {
330        self.privacy = privacy;
331        if let Some(scene_details) = &mut self.scene_details {
332            scene_details.text_field.set_privacy(privacy);
333        }
334    }
335
336    fn remove_last_char(&mut self) {
337        let mut chars = self.user_text.chars();
338        chars.next_back();
339        self.user_text = chars.as_str().to_string();
340    }
341
342    fn key_press(&mut self, key: &&'static Key, key_cap: &String, time: &MonotonicInstant) {
343        let mut keyboard_changed = false;
344        match key {
345            Key::Letter(letter_key) => {
346                // The default is to use the normal key cap character
347                // Save it here, we may change it later.
348                let mut text = key_cap.as_str();
349                if let Some(alt_key) = self.accent_key {
350                    // An accent has previously been selected
351                    // Are we selecting the same or another accent?
352                    if self.state_alt && letter_key.is_alt_accent {
353                        if self.accent_key == Some(key) {
354                            // We want the accent as a character,
355                            // Just leave the alt accent state
356                            self.accent_key = None;
357                        } else {
358                            // We want a different accent character
359                            self.accent_key = Some(key);
360                        }
361                        // The accent key will be added (again) later
362                        self.remove_last_char();
363                        self.state_alt = false;
364                        keyboard_changed = true;
365                    } else {
366                        self.accent_key = None;
367                        // Remove the accent from the input line.
368                        self.remove_last_char();
369                        // Now we check for a valid accented character otherwise use the normal key cap letter
370                        if let Some(accent) = get_accent(alt_key, key) {
371                            // add the accented character
372                            if self.state_shift {
373                                text = accent.upper;
374                            } else {
375                                text = accent.lower;
376                            }
377                        }
378                    }
379                } else {
380                    if self.state_alt && letter_key.is_alt_accent {
381                        self.accent_key = Some(*key);
382                        self.state_alt = false;
383                        keyboard_changed = true;
384                    }
385                }
386                if !self.sticky_shift {
387                    self.state_shift = false;
388                    keyboard_changed = true;
389                }
390                self.user_text.push_str(text);
391            }
392            Key::Special(key, _text) => match key {
393                SpecialKey::DEL => {
394                    let mut chars = self.user_text.chars();
395                    chars.next_back();
396                    self.user_text = chars.into_iter().collect();
397                    if self.accent_key.is_some() {
398                        self.accent_key = None;
399                        keyboard_changed = true;
400                    }
401                }
402                SpecialKey::ENTER => {
403                    #[cfg(feature = "ota_ui")]
404                    self.app_sender.queue_message(
405                        MessageTarget::View(self.view_key),
406                        make_message(StateMachineEvent::UserInput(self.user_text.clone())),
407                    );
408                    // Finish this view and return to the calling view
409                    #[cfg(not(feature = "ota_ui"))]
410                    self.app_sender.queue_message(
411                        MessageTarget::View(self.view_key),
412                        make_message(ProxyMessages::PopViewAssistant),
413                    );
414                    // Send the calling view the result
415                    #[cfg(not(feature = "ota_ui"))]
416                    self.app_sender.queue_message(
417                        MessageTarget::View(self.view_key),
418                        make_message(KeyboardMessages::Result(
419                            self.field_name.clone(),
420                            self.user_text.clone(),
421                        )),
422                    );
423                }
424                SpecialKey::ALT => {
425                    self.state_alt = !self.state_alt;
426                    if self.state_alt {
427                        self.state_shift = false;
428                        self.accent_key = None;
429                    }
430                    keyboard_changed = true;
431                }
432                SpecialKey::SHIFT => {
433                    if !self.state_shift {
434                        self.shift_time = time.clone();
435                        self.state_shift = true;
436                        self.state_alt = false;
437                    } else {
438                        // Is this a quick double press?
439                        if *time - self.shift_time < MonotonicDuration::from_seconds(1) {
440                            self.sticky_shift = true;
441                        } else {
442                            self.sticky_shift = false;
443                            self.state_shift = false;
444                        }
445                    }
446                    keyboard_changed = true;
447                }
448                SpecialKey::SPACE => {
449                    self.user_text.push_str(" ");
450                }
451            },
452        };
453        if keyboard_changed {
454            if let Some(scene_details) = &mut self.scene_details {
455                for button in &mut scene_details.buttons {
456                    button.set_label(
457                        &mut scene_details.scene,
458                        self.state_shift,
459                        self.state_alt,
460                        self.state_symbols,
461                    );
462
463                    if let Key::Special(SpecialKey::SHIFT, _) = button.key {
464                        button.set_special_action(&mut scene_details.scene, self.state_shift)
465                    }
466                    if let Key::Special(SpecialKey::ALT, _) = button.key {
467                        button.set_special_action(&mut scene_details.scene, self.state_alt);
468                    }
469
470                    if let Key::Letter(letter_key) = button.key {
471                        // Reset background first
472                        button.set_special_action(&mut scene_details.scene, false);
473                        // Highlight accent keys if necessary
474                        if letter_key.is_alt_accent {
475                            button.set_special_action(&mut scene_details.scene, self.state_alt);
476                        }
477
478                        // Highlight possible keys that will go with the selected accent character
479                        if let Some(accent_key) = self.accent_key {
480                            if let Some(accent_char) = get_accent(accent_key, button.key) {
481                                button.set_accented_char(
482                                    &mut scene_details.scene,
483                                    accent_char,
484                                    self.state_shift,
485                                );
486                                button.set_special_action(&mut scene_details.scene, true);
487                            }
488                        }
489                    }
490                }
491            }
492        }
493
494        if let Some(scene_details) = self.scene_details.as_mut() {
495            scene_details.text_field.update_text(&mut scene_details.scene, self.user_text.clone());
496        }
497    }
498
499    pub fn keyboard_scene(&mut self, context: &ViewAssistantContext) -> SceneDetails {
500        let scene_details = self.scene_details.take().unwrap_or_else(|| {
501            let target_size = context.size;
502            let min_dimension = target_size.width.min(target_size.height);
503            let font_size = (min_dimension / 5.0).ceil().min(40.0);
504            let padding = (min_dimension / 20.0).ceil().max(8.0);
505            let mut builder = SceneBuilder::new().background_color(BACKGROUND_WHITE);
506            let mut text_field: Option<TextField> = None;
507            let mut buttons: VecDeque<KeyButton> = VecDeque::with_capacity(50);
508            builder
509                .group()
510                .column()
511                .max_size()
512                .main_align(MainAxisAlignment::SpaceEvenly)
513                .contents(|builder| {
514                    builder.start_group(
515                        "text_field_row",
516                        Flex::with_options_ptr(FlexOptions::row(
517                            MainAxisSize::Max,
518                            MainAxisAlignment::Start,
519                            CrossAxisAlignment::End,
520                        )),
521                    );
522                    // TODO(b/259497403): Calculate hardcoded values from screen width
523                    builder.space(Size2D { width: 20.0, height: 10.0, _unit: Default::default() });
524                    text_field = Some(builder.text_field(
525                        self.field_name.to_string(),
526                        self.user_text.to_string(),
527                        self.privacy,
528                        size2(TEXT_FIELD_WIDTH, TEXT_FIELD_FONT_SIZE * 3.0),
529                        TextFieldOptions {
530                            text_size: TEXT_FIELD_FONT_SIZE,
531                            ..TextFieldOptions::default()
532                        },
533                    ));
534
535                    builder.end_group(); // text_field_row
536
537                    for row in KEYBOARD {
538                        builder.start_group(
539                            "row",
540                            Flex::with_options_ptr(FlexOptions::row(
541                                MainAxisSize::Max,
542                                MainAxisAlignment::SpaceEvenly,
543                                CrossAxisAlignment::Center,
544                            )),
545                        );
546                        for key in *row {
547                            let button = KeyButton::new(
548                                key,
549                                self.font_face.clone(),
550                                font_size,
551                                padding,
552                                builder,
553                            )
554                            .expect("KeyButton");
555                            buttons.push_back(button);
556                        }
557                        builder.end_group();
558                    }
559                });
560            let mut scene = builder.build();
561            scene.layout(target_size);
562            for button in buttons.iter_mut() {
563                button.set_focused(&mut scene, self.focused);
564            }
565            let mut text_field = text_field.expect("text field should have been set properly");
566            text_field.set_focused(&mut scene, self.focused);
567            SceneDetails { scene, text_field, buttons }
568        });
569        scene_details
570    }
571}
572
573impl ViewAssistant for KeyboardViewAssistant {
574    fn resize(&mut self, _new_size: &Size) -> Result<(), Error> {
575        self.scene_details = None;
576        Ok(())
577    }
578
579    fn render(
580        &mut self,
581        render_context: &mut RenderContext,
582        ready_event: Event,
583        context: &ViewAssistantContext,
584    ) -> Result<(), Error> {
585        let mut scene_details = self.keyboard_scene(context);
586
587        scene_details.scene.render(render_context, ready_event, context)?;
588        self.scene_details = Some(scene_details);
589        context.request_render();
590        Ok(())
591    }
592
593    fn handle_message(&mut self, message: Message) {
594        if let Some(key_message) = message.downcast_ref::<KeyMessages>() {
595            match key_message {
596                KeyMessages::Pressed(key, time, letter) => {
597                    self.key_press(key, letter, time);
598                }
599            }
600        }
601        if let Some(button_message) = message.downcast_ref::<ButtonMessages>() {
602            match button_message {
603                ButtonMessages::Pressed(_time, button_text) if button_text == &self.field_name => {
604                    self.privacy = self.privacy.toggle();
605                    self.scene_details = None;
606                }
607                &_ => {}
608            }
609        }
610    }
611
612    fn handle_pointer_event(
613        &mut self,
614        context: &mut ViewAssistantContext,
615        _event: &input::Event,
616        pointer_event: &input::pointer::Event,
617    ) -> Result<(), Error> {
618        if let Some(scene_details) = self.scene_details.as_mut() {
619            for button in scene_details.buttons.iter_mut() {
620                button.handle_pointer_event(&mut scene_details.scene, context, &pointer_event);
621            }
622            scene_details.text_field.handle_pointer_event(
623                &mut scene_details.scene,
624                context,
625                &pointer_event,
626            );
627            context.request_render();
628        }
629        Ok(())
630    }
631
632    fn handle_focus_event(
633        &mut self,
634        context: &mut ViewAssistantContext,
635        focused: bool,
636    ) -> Result<(), Error> {
637        self.focused = focused;
638        if let Some(scene_details) = self.scene_details.as_mut() {
639            for button in scene_details.buttons.iter_mut() {
640                button.set_focused(&mut scene_details.scene, focused);
641            }
642            scene_details.text_field.set_focused(&mut scene_details.scene, focused);
643        }
644        context.request_render();
645        Ok(())
646    }
647}