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}