Skip to main content

cranpose_ui/
keyboard_options.rs

1//! Compose's `KeyboardOptions` and keyboard actions: which software keyboard
2//! a text field asks for, and what its action key does.
3
4use std::{cell::Cell, rc::Rc};
5
6/// The keys a software keyboard offers: Compose's `KeyboardType`.
7#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
8pub enum KeyboardType {
9    /// The platform's text keyboard.
10    #[default]
11    Text,
12    /// Letters, digits and symbols from ASCII only.
13    Ascii,
14    /// Digits.
15    Number,
16    /// Digits and the symbols of a phone number.
17    Phone,
18    /// Text keys for a web address.
19    Uri,
20    /// Text keys for an email address.
21    Email,
22    /// Text keys for a password: no suggestions, no learning from it.
23    Password,
24    /// Digits for a PIN: no suggestions, no learning from it.
25    NumberPassword,
26    /// Digits and a decimal separator.
27    Decimal,
28}
29
30impl KeyboardType {
31    /// Whether the keyboard enters a secret, which it neither corrects nor
32    /// learns from.
33    pub fn is_password(self) -> bool {
34        matches!(self, Self::Password | Self::NumberPassword)
35    }
36}
37
38/// Which letters a software keyboard capitalizes on its own: Compose's
39/// `KeyboardCapitalization`. Only text keyboards capitalize.
40#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
41pub enum KeyboardCapitalization {
42    /// Capitalizes nothing.
43    #[default]
44    None,
45    /// Every character.
46    Characters,
47    /// The first letter of every word.
48    Words,
49    /// The first letter of every sentence.
50    Sentences,
51}
52
53/// The action key a software keyboard shows, which Enter also runs in a
54/// single-line field: Compose's `ImeAction`.
55#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
56pub enum ImeAction {
57    /// [`Done`](Self::Done) in a single-line field, [`None`](Self::None) in
58    /// a multi-line one.
59    #[default]
60    Default,
61    /// No action: Enter adds a line in a multi-line field and does nothing
62    /// in a single-line one.
63    None,
64    /// Opens the destination the person typed, such as a web address.
65    Go,
66    /// Runs the search for the text typed.
67    Search,
68    /// Sends the text typed.
69    Send,
70    /// Moves focus to the previous field by default.
71    Previous,
72    /// Moves focus to the next field by default.
73    Next,
74    /// Hides the software keyboard by default.
75    Done,
76}
77
78impl ImeAction {
79    /// The action a field with these lines shows: [`Default`](Self::Default)
80    /// resolved, any other action as it is.
81    pub fn resolve(self, single_line: bool) -> Self {
82        match self {
83            Self::Default if single_line => Self::Done,
84            Self::Default => Self::None,
85            action => action,
86        }
87    }
88}
89
90/// What a text field holds, so autofill and password managers can offer to
91/// fill it: Compose's `ContentType`. Android, iOS and web pass it to the
92/// platform.
93#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
94pub enum ContentType {
95    /// A username or login name.
96    Username,
97    /// A password.
98    Password,
99    /// A username the person is choosing.
100    NewUsername,
101    /// A password the person is choosing, for which a password manager can
102    /// suggest a strong one.
103    NewPassword,
104    /// An email address.
105    EmailAddress,
106    /// A phone number.
107    PhoneNumber,
108    /// A person's full name.
109    PersonFullName,
110    /// A street address.
111    PostalAddress,
112    /// A postal code.
113    PostalCode,
114    /// A credit card number.
115    CreditCardNumber,
116    /// A one-time code from a text message.
117    SmsOtpCode,
118}
119
120/// The software keyboard a text field asks for: Compose's `KeyboardOptions`.
121/// Platforms without a software keyboard ignore everything but the action,
122/// which Enter runs in a single-line field.
123#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
124pub struct KeyboardOptions {
125    /// The keys the keyboard offers.
126    pub keyboard_type: KeyboardType,
127    /// Which letters the keyboard capitalizes on its own.
128    pub capitalization: KeyboardCapitalization,
129    /// Whether the keyboard corrects and suggests words. Password keyboards
130    /// never do.
131    pub auto_correct: bool,
132    /// The action key the keyboard shows. Enter runs the same action in a single-line field.
133    pub ime_action: ImeAction,
134    /// What the field holds, for autofill. Compose sets this through the
135    /// field's semantics.
136    pub content_type: Option<ContentType>,
137}
138
139impl Default for KeyboardOptions {
140    fn default() -> Self {
141        Self {
142            keyboard_type: KeyboardType::Text,
143            capitalization: KeyboardCapitalization::None,
144            auto_correct: true,
145            ime_action: ImeAction::Default,
146            content_type: None,
147        }
148    }
149}
150
151impl KeyboardOptions {
152    /// Whether the keyboard corrects and suggests words: asked for, and not
153    /// a password keyboard.
154    pub fn corrects(&self) -> bool {
155        self.auto_correct && !self.keyboard_type.is_password()
156    }
157}
158
159/// What a keyboard action handler gets: the action the person ran, and a
160/// way to run that action's default behavior too, as Compose's
161/// `performDefaultAction` does.
162pub struct KeyboardActionScope {
163    action: ImeAction,
164    default_requested: Cell<bool>,
165}
166
167impl KeyboardActionScope {
168    /// The resolved action: never [`ImeAction::Default`].
169    pub fn action(&self) -> ImeAction {
170        self.action
171    }
172
173    /// Runs the action's default after the handler returns: Next and
174    /// Previous move focus, Done hides the software keyboard, and the other
175    /// actions do nothing.
176    pub fn perform_default_action(&self) {
177        self.default_requested.set(true);
178    }
179}
180
181/// Runs when the person presses a field's keyboard action key, or Enter in
182/// a single-line field. A field without a handler runs the action's
183/// default; a handler runs it only through
184/// [`KeyboardActionScope::perform_default_action`].
185#[derive(Clone)]
186pub struct KeyboardActionHandler(Rc<dyn Fn(&KeyboardActionScope)>);
187
188impl KeyboardActionHandler {
189    /// Wraps `handler`, which runs on a keyboard action with a [`KeyboardActionScope`].
190    pub fn new(handler: impl Fn(&KeyboardActionScope) + 'static) -> Self {
191        Self(Rc::new(handler))
192    }
193}
194
195impl std::fmt::Debug for KeyboardActionHandler {
196    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
197        f.write_str("KeyboardActionHandler")
198    }
199}
200
201impl PartialEq for KeyboardActionHandler {
202    fn eq(&self, other: &Self) -> bool {
203        Rc::ptr_eq(&self.0, &other.0)
204    }
205}
206
207/// Runs `action`, resolved, through `handler` or else its default.
208pub(crate) fn run_keyboard_action(action: ImeAction, handler: Option<&KeyboardActionHandler>) {
209    let scope = KeyboardActionScope {
210        action,
211        default_requested: Cell::new(handler.is_none()),
212    };
213    if let Some(handler) = handler {
214        (handler.0)(&scope);
215    }
216    if scope.default_requested.get() {
217        perform_default_action(action);
218    }
219}
220
221fn perform_default_action(action: ImeAction) {
222    match action {
223        ImeAction::Next => crate::focus_order::request_focus_move(crate::FocusDirection::Next),
224        ImeAction::Previous => {
225            crate::focus_order::request_focus_move(crate::FocusDirection::Previous);
226        }
227        ImeAction::Done => crate::text_input_session::hide_software_keyboard(),
228        ImeAction::Default
229        | ImeAction::None
230        | ImeAction::Go
231        | ImeAction::Search
232        | ImeAction::Send => {}
233    }
234}