Skip to main content

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}