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    #[default]
43    None,
44    /// Every character.
45    Characters,
46    /// The first letter of every word.
47    Words,
48    /// The first letter of every sentence.
49    Sentences,
50}
51
52/// The action key a software keyboard shows, which Enter also runs in a
53/// single-line field: Compose's `ImeAction`.
54#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
55pub enum ImeAction {
56    /// [`Done`](Self::Done) in a single-line field, [`None`](Self::None) in
57    /// a multi-line one.
58    #[default]
59    Default,
60    /// No action: Enter adds a line in a multi-line field and does nothing
61    /// in a single-line one.
62    None,
63    Go,
64    Search,
65    Send,
66    /// Moves focus to the previous field by default.
67    Previous,
68    /// Moves focus to the next field by default.
69    Next,
70    /// Hides the software keyboard by default.
71    Done,
72}
73
74impl ImeAction {
75    /// The action a field with these lines shows: [`Default`](Self::Default)
76    /// resolved, any other action as it is.
77    pub fn resolve(self, single_line: bool) -> Self {
78        match self {
79            Self::Default if single_line => Self::Done,
80            Self::Default => Self::None,
81            action => action,
82        }
83    }
84}
85
86/// What a text field holds, so autofill and password managers can offer to
87/// fill it: Compose's `ContentType`. Web and iOS pass it to the platform;
88/// Android does not fill text fields yet.
89#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
90pub enum ContentType {
91    Username,
92    Password,
93    /// A username the person is choosing.
94    NewUsername,
95    /// A password the person is choosing, for which a password manager can
96    /// suggest a strong one.
97    NewPassword,
98    EmailAddress,
99    PhoneNumber,
100    PersonFullName,
101    PostalAddress,
102    PostalCode,
103    CreditCardNumber,
104    /// A one-time code from a text message.
105    SmsOtpCode,
106}
107
108/// The software keyboard a text field asks for: Compose's `KeyboardOptions`.
109/// Platforms without a software keyboard ignore everything but the action,
110/// which Enter runs in a single-line field.
111#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
112pub struct KeyboardOptions {
113    pub keyboard_type: KeyboardType,
114    pub capitalization: KeyboardCapitalization,
115    /// Whether the keyboard corrects and suggests words. Password keyboards
116    /// never do.
117    pub auto_correct: bool,
118    pub ime_action: ImeAction,
119    /// What the field holds, for autofill. Compose sets this through the
120    /// field's semantics.
121    pub content_type: Option<ContentType>,
122}
123
124impl Default for KeyboardOptions {
125    fn default() -> Self {
126        Self {
127            keyboard_type: KeyboardType::Text,
128            capitalization: KeyboardCapitalization::None,
129            auto_correct: true,
130            ime_action: ImeAction::Default,
131            content_type: None,
132        }
133    }
134}
135
136impl KeyboardOptions {
137    /// Whether the keyboard corrects and suggests words: asked for, and not
138    /// a password keyboard.
139    pub fn corrects(&self) -> bool {
140        self.auto_correct && !self.keyboard_type.is_password()
141    }
142}
143
144/// What a keyboard action handler gets: the action the person ran, and a
145/// way to run that action's default behavior too, as Compose's
146/// `performDefaultAction` does.
147pub struct KeyboardActionScope {
148    action: ImeAction,
149    default_requested: Cell<bool>,
150}
151
152impl KeyboardActionScope {
153    /// The resolved action: never [`ImeAction::Default`].
154    pub fn action(&self) -> ImeAction {
155        self.action
156    }
157
158    /// Runs the action's default after the handler returns: Next and
159    /// Previous move focus, Done hides the software keyboard, and the other
160    /// actions do nothing.
161    pub fn perform_default_action(&self) {
162        self.default_requested.set(true);
163    }
164}
165
166/// Runs when the person presses a field's keyboard action key, or Enter in
167/// a single-line field. A field without a handler runs the action's
168/// default; a handler runs it only through
169/// [`KeyboardActionScope::perform_default_action`].
170#[derive(Clone)]
171pub struct KeyboardActionHandler(Rc<dyn Fn(&KeyboardActionScope)>);
172
173impl KeyboardActionHandler {
174    pub fn new(handler: impl Fn(&KeyboardActionScope) + 'static) -> Self {
175        Self(Rc::new(handler))
176    }
177}
178
179impl std::fmt::Debug for KeyboardActionHandler {
180    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
181        f.write_str("KeyboardActionHandler")
182    }
183}
184
185impl PartialEq for KeyboardActionHandler {
186    fn eq(&self, other: &Self) -> bool {
187        Rc::ptr_eq(&self.0, &other.0)
188    }
189}
190
191/// Runs `action`, resolved, through `handler` or else its default.
192pub(crate) fn run_keyboard_action(action: ImeAction, handler: Option<&KeyboardActionHandler>) {
193    let scope = KeyboardActionScope {
194        action,
195        default_requested: Cell::new(handler.is_none()),
196    };
197    if let Some(handler) = handler {
198        (handler.0)(&scope);
199    }
200    if scope.default_requested.get() {
201        perform_default_action(action);
202    }
203}
204
205fn perform_default_action(action: ImeAction) {
206    match action {
207        ImeAction::Next => crate::focus_order::request_focus_move(crate::FocusDirection::Next),
208        ImeAction::Previous => {
209            crate::focus_order::request_focus_move(crate::FocusDirection::Previous);
210        }
211        ImeAction::Done => crate::text_input_session::hide_software_keyboard(),
212        ImeAction::Default
213        | ImeAction::None
214        | ImeAction::Go
215        | ImeAction::Search
216        | ImeAction::Send => {}
217    }
218}