frus_widgets/ime.rs
1//! What the **software keyboard** should be, for the field that has focus.
2//!
3//! A phone-number field that opens a full QWERTY keyboard is a defect you feel on
4//! every use and never see in a screenshot. So is an email field with no `@` key, a
5//! form whose every action key says *Done* when the next thing to do is move to the
6//! next field, and — the one that matters most — a password field the keyboard
7//! believes is an ordinary sentence, learns into its personal dictionary, and offers
8//! back as a suggestion later, on somebody else's screen.
9//!
10//! [`KeyboardType`] says which keys; [`TextInputAction`] says what the action key does.
11//! Together they are [`Ime`], which [`Widget::ime`] hands to the
12//! platform when a field takes focus.
13//!
14//! ```ignore
15//! TextField::new(&app.phone).keyboard_type(KeyboardType::Phone)
16//! TextField::new(&app.query).action(TextInputAction::Search)
17//! ```
18//!
19//! # The numbers are Android's, and the tests are not
20//!
21//! The mapping to Android's `InputType` and `EditorInfo` bit fields lives here rather
22//! than in the platform layer, and it is not behind `cfg(android)`. Two reasons, and
23//! they are the same reason: **a mapping only exercised on a device is a mapping
24//! nobody checks**, and the input bridge's dex is *checked in* — rebuilding it needs
25//! the Android SDK, so the Java side must never have to change again to learn a new
26//! keyboard type. It receives two integers and sets them on the `EditorInfo`. Every
27//! type this file will ever grow is a change to this file alone.
28
29/// Which keys the software keyboard should offer.
30///
31/// The platform decides what that means: a phone keypad on Android and iOS, and
32/// nothing at all on a desktop, which has one keyboard and it is already open.
33#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
34pub enum KeyboardType {
35 /// Ordinary text, with suggestions and composition. The default.
36 #[default]
37 Text,
38 /// Text over several lines, where the action key is a newline.
39 Multiline,
40 /// Whole numbers.
41 Number,
42 /// Numbers with a decimal separator and a sign.
43 Decimal,
44 /// A telephone keypad: digits, `+`, `*`, `#`.
45 Phone,
46 /// An email address: an `@` and a `.` within reach, and no auto-capitalisation.
47 Email,
48 /// A URL: a `/` and a `.com` within reach.
49 Url,
50 /// A **secret**. The keyboard neither learns it nor suggests it, which is the
51 /// whole point and is not something the masking dots can do on their own.
52 Password,
53 /// A secret the field shows in the clear — a one-time code, a generated
54 /// passphrase being checked. Still never learned.
55 VisiblePassword,
56 /// A person's name: each word capitalised, no sentence casing.
57 Name,
58 /// A postal address.
59 StreetAddress,
60 /// A date or a time.
61 DateTime,
62 /// **No** software keyboard: the field is edited some other way — a picker, a
63 /// scanner, a hardware keyboard only.
64 None,
65}
66
67/// What the keyboard's action key does, and what it says.
68#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
69pub enum TextInputAction {
70 /// *Done*: finish editing and close the keyboard. The default.
71 #[default]
72 Done,
73 /// *Go*: follow what was typed, usually an address.
74 Go,
75 /// *Search*.
76 Search,
77 /// *Send*.
78 Send,
79 /// *Next*: move to the following field, keeping the keyboard open.
80 Next,
81 /// *Previous*: move back to the field before.
82 Previous,
83 /// A **newline**, for a field that takes more than one line. Not an action at
84 /// all — the key inserts a line break instead of doing anything.
85 Newline,
86 /// No action key behaviour at all; the platform picks.
87 Unspecified,
88}
89
90/// Which letters the software keyboard capitalises on its own.
91///
92/// A hint, not a rule: it is what the **keyboard** does to what it sends, and a reader
93/// who turns it off still types what they meant to. A field that must hold capitals
94/// wants [`TextField::input_filter`](crate::TextField::input_filter) as well, which
95/// works whatever the keyboard does and works on a desktop, where there is no keyboard
96/// to hint to.
97#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
98pub enum Capitalization {
99 /// Whatever the keyboard type already implies — sentences for ordinary text, words
100 /// for a name or an address, nothing for an email address. The default, and right
101 /// nearly always, since the type is the better description of the two.
102 #[default]
103 Auto,
104 /// Nothing. The reader capitalises what they mean to capitalise.
105 None,
106 /// The first letter of each sentence.
107 Sentences,
108 /// The first letter of each word.
109 Words,
110 /// Every letter.
111 Characters,
112}
113
114impl Capitalization {
115 /// The Android `InputType` bit for this, or zero for none.
116 const fn flag(self) -> i32 {
117 use input_type as t;
118 match self {
119 // `Auto` never reaches here; see [`Ime::android_input_type`].
120 Self::Auto | Self::None => 0,
121 Self::Sentences => t::TEXT_FLAG_CAP_SENTENCES,
122 Self::Words => t::TEXT_FLAG_CAP_WORDS,
123 Self::Characters => t::TEXT_FLAG_CAP_CHARACTERS,
124 }
125 }
126}
127
128/// The pair the platform is told when a field takes focus.
129#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
130pub struct Ime {
131 /// Which keys.
132 pub keyboard: KeyboardType,
133 /// What the action key does.
134 pub action: TextInputAction,
135 /// Which letters the keyboard capitalises by itself.
136 pub capitalization: Capitalization,
137}
138
139impl Ime {
140 /// A keyboard of `keyboard` whose action key is *Done*.
141 pub const fn new(keyboard: KeyboardType) -> Self {
142 Self {
143 keyboard,
144 action: TextInputAction::Done,
145 capitalization: Capitalization::Auto,
146 }
147 }
148
149 /// The same, capitalising differently from what the type implies.
150 pub const fn capitalization(mut self, capitalization: Capitalization) -> Self {
151 self.capitalization = capitalization;
152 self
153 }
154
155 /// The Android `InputType` bit field for this field: the type's, with the
156 /// capitalisation overruled where one was asked for.
157 ///
158 /// Only a **text** class capitalises. A number pad has no letters to capitalise and
159 /// the same bits mean other things there (`0x1000` is *signed* on a number class),
160 /// so asking a phone field for capitals would quietly turn on a minus key.
161 ///
162 /// [`Capitalization::Auto`] leaves the type's own bits alone, which is why the
163 /// existing types keep answering exactly what they always did.
164 pub const fn android_input_type(self) -> i32 {
165 use input_type as t;
166 let base = self.keyboard.android_input_type();
167 if base & t::CLASS_MASK != t::CLASS_TEXT {
168 return base;
169 }
170 match self.capitalization {
171 Capitalization::Auto => base,
172 other => (base & !t::CAP_MASK) | other.flag(),
173 }
174 }
175
176 /// The same, with a different action key.
177 pub const fn action(mut self, action: TextInputAction) -> Self {
178 self.action = action;
179 self
180 }
181}
182
183// ---------------------------------------------------------------------------
184// Android's numbers
185// ---------------------------------------------------------------------------
186
187/// `android.text.InputType`, the constants this file needs.
188mod input_type {
189 pub const CLASS_TEXT: i32 = 0x0000_0001;
190 pub const CLASS_NUMBER: i32 = 0x0000_0002;
191 pub const CLASS_PHONE: i32 = 0x0000_0003;
192 pub const CLASS_DATETIME: i32 = 0x0000_0004;
193
194 pub const CLASS_MASK: i32 = 0x0000_000f;
195
196 pub const TEXT_FLAG_CAP_CHARACTERS: i32 = 0x0000_1000;
197 pub const TEXT_FLAG_CAP_SENTENCES: i32 = 0x0000_4000;
198 pub const TEXT_FLAG_CAP_WORDS: i32 = 0x0000_2000;
199 /// Every capitalisation bit, so that asking for one can clear the others.
200 pub const CAP_MASK: i32 =
201 TEXT_FLAG_CAP_CHARACTERS | TEXT_FLAG_CAP_SENTENCES | TEXT_FLAG_CAP_WORDS;
202 pub const TEXT_FLAG_MULTI_LINE: i32 = 0x0002_0000;
203
204 pub const TEXT_VARIATION_URI: i32 = 0x0000_0010;
205 pub const TEXT_VARIATION_EMAIL_ADDRESS: i32 = 0x0000_0020;
206 pub const TEXT_VARIATION_PASSWORD: i32 = 0x0000_0080;
207 pub const TEXT_VARIATION_PERSON_NAME: i32 = 0x0000_0060;
208 pub const TEXT_VARIATION_POSTAL_ADDRESS: i32 = 0x0000_0050;
209 pub const TEXT_VARIATION_VISIBLE_PASSWORD: i32 = 0x0000_0090;
210
211 pub const NUMBER_FLAG_SIGNED: i32 = 0x0000_1000;
212 pub const NUMBER_FLAG_DECIMAL: i32 = 0x0000_2000;
213}
214
215/// `android.view.inputmethod.EditorInfo`, likewise.
216mod editor_info {
217 pub const IME_ACTION_UNSPECIFIED: i32 = 0;
218 pub const IME_ACTION_GO: i32 = 2;
219 pub const IME_ACTION_SEARCH: i32 = 3;
220 pub const IME_ACTION_SEND: i32 = 4;
221 pub const IME_ACTION_NEXT: i32 = 5;
222 pub const IME_ACTION_DONE: i32 = 6;
223 pub const IME_ACTION_PREVIOUS: i32 = 7;
224 pub const IME_FLAG_NO_FULLSCREEN: i32 = 0x0200_0000;
225}
226
227impl KeyboardType {
228 /// The Android `InputType` bit field for this keyboard.
229 ///
230 /// [`None`](Self::None) is `TYPE_NULL`, which is zero — the value that tells
231 /// Android there is nothing to type into. It is also what the bridge produced for
232 /// *every* field before it had an `InputType` at all, which is why a keyboard used
233 /// to arrive with no composition, no swipe and no CJK.
234 pub const fn android_input_type(self) -> i32 {
235 use input_type as t;
236 match self {
237 Self::Text => t::CLASS_TEXT | t::TEXT_FLAG_CAP_SENTENCES,
238 Self::Multiline => t::CLASS_TEXT | t::TEXT_FLAG_CAP_SENTENCES | t::TEXT_FLAG_MULTI_LINE,
239 Self::Number => t::CLASS_NUMBER,
240 Self::Decimal => t::CLASS_NUMBER | t::NUMBER_FLAG_DECIMAL | t::NUMBER_FLAG_SIGNED,
241 Self::Phone => t::CLASS_PHONE,
242 // No capitalisation flag: an address that arrives as `Someone@…` is an
243 // address that does not work, and the keyboard would supply the capital.
244 Self::Email => t::CLASS_TEXT | t::TEXT_VARIATION_EMAIL_ADDRESS,
245 Self::Url => t::CLASS_TEXT | t::TEXT_VARIATION_URI,
246 Self::Password => t::CLASS_TEXT | t::TEXT_VARIATION_PASSWORD,
247 Self::VisiblePassword => t::CLASS_TEXT | t::TEXT_VARIATION_VISIBLE_PASSWORD,
248 Self::Name => t::CLASS_TEXT | t::TEXT_VARIATION_PERSON_NAME | t::TEXT_FLAG_CAP_WORDS,
249 Self::StreetAddress => {
250 t::CLASS_TEXT | t::TEXT_VARIATION_POSTAL_ADDRESS | t::TEXT_FLAG_CAP_WORDS
251 }
252 Self::DateTime => t::CLASS_DATETIME,
253 Self::None => 0,
254 }
255 }
256
257 /// Whether the keyboard must never learn what is typed into it.
258 ///
259 /// Android infers this from the variation bits, so nothing else has to be sent —
260 /// but the question is worth asking in this vocabulary too, because it is the
261 /// reason [`Password`](Self::Password) exists as a *type* rather than as a way of
262 /// drawing dots.
263 pub const fn is_secret(self) -> bool {
264 matches!(self, Self::Password | Self::VisiblePassword)
265 }
266}
267
268impl TextInputAction {
269 /// The Android `imeOptions` bit field for this action key.
270 ///
271 /// `IME_FLAG_NO_FULLSCREEN` is on every one of them: the fullscreen editor Android
272 /// offers in landscape replaces the application's own field with a system one, and
273 /// a framework that draws its own text has nothing to gain and a screen to lose.
274 pub const fn android_ime_options(self) -> i32 {
275 use editor_info as e;
276 let action = match self {
277 Self::Done => e::IME_ACTION_DONE,
278 Self::Go => e::IME_ACTION_GO,
279 Self::Search => e::IME_ACTION_SEARCH,
280 Self::Send => e::IME_ACTION_SEND,
281 Self::Next => e::IME_ACTION_NEXT,
282 Self::Previous => e::IME_ACTION_PREVIOUS,
283 // A newline is not an action: the key has to fall through to the editor,
284 // and `UNSPECIFIED` with the multi-line input flag is how Android says so.
285 Self::Newline | Self::Unspecified => e::IME_ACTION_UNSPECIFIED,
286 };
287 action | e::IME_FLAG_NO_FULLSCREEN
288 }
289}
290
291// ---------------------------------------------------------------------------
292// The browser's words
293// ---------------------------------------------------------------------------
294
295/// What a browser is told about the field being edited (milestone 711): the attributes of
296/// the `<input>` or `<textarea>` the web shell edits it through.
297///
298/// Here rather than in the shell for the reason Android's numbers are: the shell's half
299/// runs only in a browser, and this half is checked on every push.
300#[derive(Clone, Copy, Debug, PartialEq, Eq)]
301pub struct WebInput {
302 /// A `<textarea>` rather than an `<input>`: the field takes more than one line.
303 pub multiline: bool,
304 /// The input's `type`: `password` for a secret the field masks, which is what keeps
305 /// it out of the browser's suggestions and offers it to a password manager; `text`
306 /// otherwise. A `<textarea>` has none.
307 pub input_type: &'static str,
308 /// `inputmode`: which keys a software keyboard offers.
309 pub inputmode: &'static str,
310 /// `enterkeyhint`: what the action key says, or `None` for the browser's own.
311 pub enterkeyhint: Option<&'static str>,
312 /// `autocapitalize`: which letters a software keyboard capitalises by itself.
313 pub autocapitalize: &'static str,
314 /// `autocorrect` and `spellcheck`: off for what is not prose — an address, a number,
315 /// a secret — where a correction is a mistake.
316 pub corrects: bool,
317}
318
319impl Ime {
320 /// The attributes a browser is given for this field.
321 pub const fn web(self) -> WebInput {
322 use KeyboardType as K;
323 let k = self.keyboard;
324 let multiline =
325 matches!(k, K::Multiline) || matches!(self.action, TextInputAction::Newline);
326 let auto = match k {
327 K::Text | K::Multiline => "sentences",
328 K::Name | K::StreetAddress => "words",
329 _ => "off",
330 };
331 WebInput {
332 multiline,
333 input_type: if matches!(k, K::Password) && !multiline {
334 "password"
335 } else {
336 "text"
337 },
338 inputmode: match k {
339 K::Number => "numeric",
340 K::Decimal => "decimal",
341 K::Phone => "tel",
342 K::Email => "email",
343 K::Url => "url",
344 K::None => "none",
345 _ => "text",
346 },
347 enterkeyhint: match self.action {
348 TextInputAction::Done => Some("done"),
349 TextInputAction::Go => Some("go"),
350 TextInputAction::Search => Some("search"),
351 TextInputAction::Send => Some("send"),
352 TextInputAction::Next => Some("next"),
353 TextInputAction::Previous => Some("previous"),
354 TextInputAction::Newline => Some("enter"),
355 TextInputAction::Unspecified => None,
356 },
357 autocapitalize: match self.capitalization {
358 Capitalization::Auto => auto,
359 Capitalization::None => "off",
360 Capitalization::Sentences => "sentences",
361 Capitalization::Words => "words",
362 Capitalization::Characters => "characters",
363 },
364 corrects: matches!(k, K::Text | K::Multiline),
365 }
366 }
367}
368
369#[cfg(test)]
370mod tests {
371 use super::*;
372
373 /// A field that says nothing is a single line of prose with a *Done* key, as on Android.
374 #[test]
375 fn the_web_default_is_a_line_of_prose() {
376 let web = Ime::default().web();
377 assert!(!web.multiline);
378 assert_eq!(web.input_type, "text");
379 assert_eq!(web.inputmode, "text");
380 assert_eq!(web.enterkeyhint, Some("done"));
381 assert_eq!(web.autocapitalize, "sentences");
382 assert!(web.corrects);
383 }
384
385 /// A secret is a password input, never corrected nor capitalised; one shown in the clear
386 /// is not a password input — the browser would mask it — but is still not corrected.
387 #[test]
388 fn a_secret_is_a_password_input_on_the_web() {
389 let web = Ime::new(KeyboardType::Password).web();
390 assert_eq!(web.input_type, "password");
391 assert_eq!(web.autocapitalize, "off");
392 assert!(!web.corrects);
393 let shown = Ime::new(KeyboardType::VisiblePassword).web();
394 assert_eq!(shown.input_type, "text");
395 assert!(!shown.corrects);
396 }
397
398 /// The keypads: each keyboard type is its `inputmode`.
399 #[test]
400 fn each_keyboard_is_its_inputmode() {
401 for (keyboard, mode) in [
402 (KeyboardType::Number, "numeric"),
403 (KeyboardType::Decimal, "decimal"),
404 (KeyboardType::Phone, "tel"),
405 (KeyboardType::Email, "email"),
406 (KeyboardType::Url, "url"),
407 (KeyboardType::None, "none"),
408 (KeyboardType::Name, "text"),
409 ] {
410 assert_eq!(Ime::new(keyboard).web().inputmode, mode, "{keyboard:?}");
411 }
412 }
413
414 /// Several lines are a textarea whose key is a newline, and a capitalisation asked for
415 /// wins over the type's.
416 #[test]
417 fn several_lines_are_a_textarea() {
418 let web = Ime::new(KeyboardType::Multiline)
419 .action(TextInputAction::Newline)
420 .web();
421 assert!(web.multiline);
422 assert_eq!(web.enterkeyhint, Some("enter"));
423 let named = Ime::new(KeyboardType::Name).web();
424 assert_eq!(named.autocapitalize, "words");
425 let shouting = Ime::new(KeyboardType::Text)
426 .capitalization(Capitalization::Characters)
427 .web();
428 assert_eq!(shouting.autocapitalize, "characters");
429 // And none at all, on a field whose type would capitalise sentences.
430 let quiet = Ime::default().capitalization(Capitalization::None).web();
431 assert_eq!(quiet.autocapitalize, "off");
432 assert_eq!(
433 Ime::default()
434 .action(TextInputAction::Unspecified)
435 .web()
436 .enterkeyhint,
437 None
438 );
439 }
440
441 /// The pair a field with nothing said is what the bridge used to hardcode for
442 /// every field: sentence-cased text, a *Done* key, no fullscreen editor. Nothing
443 /// that says nothing changes.
444 #[test]
445 fn the_default_is_what_was_hardcoded_before() {
446 let ime = Ime::default();
447 assert_eq!(ime.keyboard, KeyboardType::Text);
448 assert_eq!(ime.action, TextInputAction::Done);
449 // TYPE_CLASS_TEXT | TYPE_TEXT_FLAG_CAP_SENTENCES
450 assert_eq!(ime.keyboard.android_input_type(), 1 | 0x4000);
451 // IME_ACTION_DONE | IME_FLAG_NO_FULLSCREEN
452 assert_eq!(ime.action.android_ime_options(), 6 | 0x0200_0000);
453 }
454
455 /// Asking for a capitalisation **replaces** the type's own rather than adding to it:
456 /// two capitalisation bits at once is a keyboard being told two things.
457 #[test]
458 fn a_capitalisation_replaces_the_types_own() {
459 const CAP_MASK: i32 = 0x0000_7000;
460 let bits = |c: Capitalization| Ime::new(KeyboardType::Text).capitalization(c);
461 // Ordinary text capitalises sentences by itself.
462 assert_eq!(
463 Ime::new(KeyboardType::Text).android_input_type() & CAP_MASK,
464 0x0000_4000
465 );
466 assert_eq!(
467 bits(Capitalization::Words).android_input_type() & CAP_MASK,
468 0x0000_2000
469 );
470 assert_eq!(
471 bits(Capitalization::Characters).android_input_type() & CAP_MASK,
472 0x0000_1000
473 );
474 assert_eq!(
475 bits(Capitalization::None).android_input_type() & CAP_MASK,
476 0
477 );
478 // And the rest of the field is untouched: still a text class.
479 assert_eq!(bits(Capitalization::None).android_input_type() & 0xf, 1);
480 }
481
482 /// `Auto` is the absence of an opinion, so every type answers exactly what it always
483 /// did — a name still capitalises words, an email address still capitalises nothing.
484 #[test]
485 fn auto_leaves_every_type_as_it_was() {
486 for keyboard in [
487 KeyboardType::Text,
488 KeyboardType::Multiline,
489 KeyboardType::Name,
490 KeyboardType::Email,
491 KeyboardType::Password,
492 KeyboardType::Number,
493 KeyboardType::Phone,
494 ] {
495 assert_eq!(
496 Ime::new(keyboard).android_input_type(),
497 keyboard.android_input_type(),
498 "{keyboard:?}"
499 );
500 }
501 }
502
503 /// Only a text class capitalises. `0x1000` is *signed* on a number class, so asking
504 /// a keypad for capitals would quietly turn on a minus key.
505 #[test]
506 fn a_keypad_is_never_told_to_capitalise() {
507 for keyboard in [
508 KeyboardType::Number,
509 KeyboardType::Decimal,
510 KeyboardType::Phone,
511 KeyboardType::DateTime,
512 ] {
513 let asked = Ime::new(keyboard).capitalization(Capitalization::Characters);
514 assert_eq!(
515 asked.android_input_type(),
516 keyboard.android_input_type(),
517 "{keyboard:?}"
518 );
519 }
520 }
521
522 /// Each type carries its own class. A phone field on a text class is a QWERTY
523 /// keyboard with a wrong label.
524 #[test]
525 fn each_type_names_its_own_class() {
526 const CLASS_MASK: i32 = 0x0000_000f;
527 let class = |k: KeyboardType| k.android_input_type() & CLASS_MASK;
528 assert_eq!(class(KeyboardType::Text), 1);
529 assert_eq!(class(KeyboardType::Multiline), 1);
530 assert_eq!(class(KeyboardType::Number), 2);
531 assert_eq!(class(KeyboardType::Decimal), 2);
532 assert_eq!(class(KeyboardType::Phone), 3);
533 assert_eq!(class(KeyboardType::DateTime), 4);
534 assert_eq!(class(KeyboardType::Email), 1);
535 assert_eq!(class(KeyboardType::Url), 1);
536 }
537
538 /// `None` is `TYPE_NULL`, and it is the only zero. A type that fell through to
539 /// zero by accident would silently be *no keyboard at all*, which is the one
540 /// failure a user cannot work around.
541 #[test]
542 fn only_none_is_null() {
543 let every = [
544 KeyboardType::Text,
545 KeyboardType::Multiline,
546 KeyboardType::Number,
547 KeyboardType::Decimal,
548 KeyboardType::Phone,
549 KeyboardType::Email,
550 KeyboardType::Url,
551 KeyboardType::Password,
552 KeyboardType::VisiblePassword,
553 KeyboardType::Name,
554 KeyboardType::StreetAddress,
555 KeyboardType::DateTime,
556 ];
557 for keyboard in every {
558 assert_ne!(
559 keyboard.android_input_type(),
560 0,
561 "{keyboard:?} must not be TYPE_NULL"
562 );
563 }
564 assert_eq!(KeyboardType::None.android_input_type(), 0);
565 }
566
567 /// A secret carries a password **variation**, which is what stops the keyboard
568 /// learning it. Masking dots are drawn by us and tell the keyboard nothing.
569 #[test]
570 fn a_secret_carries_a_password_variation() {
571 const VARIATION_MASK: i32 = 0x0000_0ff0;
572 assert_eq!(
573 KeyboardType::Password.android_input_type() & VARIATION_MASK,
574 0x80
575 );
576 assert_eq!(
577 KeyboardType::VisiblePassword.android_input_type() & VARIATION_MASK,
578 0x90
579 );
580 assert!(KeyboardType::Password.is_secret());
581 assert!(KeyboardType::VisiblePassword.is_secret());
582 assert!(!KeyboardType::Text.is_secret());
583 }
584
585 /// An email address is never auto-capitalised: `Someone@…` is an address that
586 /// does not work, and the keyboard would supply the capital unasked.
587 #[test]
588 fn an_email_is_not_capitalised() {
589 const CAP_MASK: i32 = 0x0000_7000;
590 assert_eq!(KeyboardType::Email.android_input_type() & CAP_MASK, 0);
591 assert_eq!(KeyboardType::Url.android_input_type() & CAP_MASK, 0);
592 assert_eq!(KeyboardType::Password.android_input_type() & CAP_MASK, 0);
593 // Ordinary prose is, and a name is capitalised per word rather than per
594 // sentence.
595 assert_ne!(KeyboardType::Text.android_input_type() & CAP_MASK, 0);
596 assert_eq!(
597 KeyboardType::Name.android_input_type() & CAP_MASK,
598 0x0000_2000
599 );
600 }
601
602 /// Multiline carries the multi-line flag, which is what lets the action key be a
603 /// newline at all.
604 #[test]
605 fn multiline_carries_the_multi_line_flag() {
606 const MULTI_LINE: i32 = 0x0002_0000;
607 assert_ne!(KeyboardType::Multiline.android_input_type() & MULTI_LINE, 0);
608 assert_eq!(KeyboardType::Text.android_input_type() & MULTI_LINE, 0);
609 }
610
611 /// A newline is `UNSPECIFIED`, not an action: the key has to reach the editor.
612 #[test]
613 fn a_newline_is_not_an_action() {
614 const ACTION_MASK: i32 = 0x0000_00ff;
615 assert_eq!(
616 TextInputAction::Newline.android_ime_options() & ACTION_MASK,
617 0
618 );
619 assert_eq!(
620 TextInputAction::Search.android_ime_options() & ACTION_MASK,
621 3
622 );
623 assert_eq!(TextInputAction::Next.android_ime_options() & ACTION_MASK, 5);
624 }
625
626 /// Every action refuses the fullscreen editor: it replaces the application's own
627 /// field with a system one, and a framework that draws its own text loses a screen
628 /// and gains nothing.
629 #[test]
630 fn no_action_accepts_the_fullscreen_editor() {
631 const NO_FULLSCREEN: i32 = 0x0200_0000;
632 for action in [
633 TextInputAction::Done,
634 TextInputAction::Go,
635 TextInputAction::Search,
636 TextInputAction::Send,
637 TextInputAction::Next,
638 TextInputAction::Previous,
639 TextInputAction::Newline,
640 TextInputAction::Unspecified,
641 ] {
642 assert_ne!(
643 action.android_ime_options() & NO_FULLSCREEN,
644 0,
645 "{action:?} must refuse the fullscreen editor"
646 );
647 }
648 }
649}