1
//! Single-line text input widget with cursor, placeholder, and two-way data binding.
2
//!
3
//! The main entry point is [`TextInput`], which holds the editable state
4
//! ([`TextInputState`]) together with per-platform default styles.  Call
5
//! [`TextInput::dom()`] to obtain a renderable [`Dom`] node.
6
//!
7
//! For higher-level text-input management (IME, clipboard, undo) see
8
//! `layout/src/managers/text_input.rs`.
9

            
10
use alloc::{string::String, vec::Vec};
11

            
12
use azul_core::{
13
    callbacks::{CoreCallback, CoreCallbackData, Update},
14
    dom::Dom,
15
    refany::RefAny,
16
    task::OptionTimerId,
17
    window::VirtualKeyCode,
18
};
19
use azul_css::{
20
    dynamic_selector::{CssPropertyWithConditions, CssPropertyWithConditionsVec},
21
    props::{
22
        basic::*,
23
        layout::*,
24
        property::{CssProperty, *},
25
        style::*,
26
    },
27
    *,
28
};
29
use azul_css::css::BoxOrStatic;
30

            
31
use crate::callbacks::{Callback, CallbackInfo};
32

            
33
const BACKGROUND_COLOR: ColorU = ColorU {
34
    r: 255,
35
    g: 255,
36
    b: 255,
37
    a: 255,
38
}; // white
39
const BLACK: ColorU = ColorU {
40
    r: 0,
41
    g: 0,
42
    b: 0,
43
    a: 255,
44
};
45
const TEXT_COLOR: StyleTextColor = StyleTextColor { inner: BLACK }; // black
46
const COLOR_9B9B9B: ColorU = ColorU {
47
    r: 155,
48
    g: 155,
49
    b: 155,
50
    a: 255,
51
}; // #9b9b9b
52
const COLOR_4286F4: ColorU = ColorU {
53
    r: 66,
54
    g: 134,
55
    b: 244,
56
    a: 255,
57
}; // #4286f4
58
const COLOR_4C4C4C: ColorU = ColorU {
59
    r: 76,
60
    g: 76,
61
    b: 76,
62
    a: 255,
63
}; // #4C4C4C
64

            
65
const CURSOR_COLOR_BLACK: &[StyleBackgroundContent] = &[StyleBackgroundContent::Color(BLACK)];
66
const CURSOR_COLOR: StyleBackgroundContentVec =
67
    StyleBackgroundContentVec::from_const_slice(CURSOR_COLOR_BLACK);
68

            
69
const BACKGROUND_THEME_LIGHT: &[StyleBackgroundContent] =
70
    &[StyleBackgroundContent::Color(BACKGROUND_COLOR)];
71
const BACKGROUND_COLOR_LIGHT: StyleBackgroundContentVec =
72
    StyleBackgroundContentVec::from_const_slice(BACKGROUND_THEME_LIGHT);
73

            
74
const SANS_SERIF_STR: &str = "system:ui";
75
const SANS_SERIF: AzString = AzString::from_const_str(SANS_SERIF_STR);
76
const SANS_SERIF_FAMILIES: &[StyleFontFamily] = &[StyleFontFamily::System(SANS_SERIF)];
77
const SANS_SERIF_FAMILY: StyleFontFamilyVec =
78
    StyleFontFamilyVec::from_const_slice(SANS_SERIF_FAMILIES);
79

            
80
// -- cursor style
81

            
82
const TEXT_CURSOR_TRANSFORM: &[StyleTransform] =
83
    &[StyleTransform::Translate(StyleTransformTranslate2D {
84
        x: PixelValue::const_px(0),
85
        y: PixelValue::const_px(2),
86
    })];
87

            
88
static TEXT_CURSOR_PROPS: &[CssPropertyWithConditions] = &[
89
    CssPropertyWithConditions::simple(CssProperty::const_position(LayoutPosition::Absolute)),
90
    CssPropertyWithConditions::simple(CssProperty::const_width(LayoutWidth::const_px(1))),
91
    CssPropertyWithConditions::simple(CssProperty::const_height(LayoutHeight::const_px(11))),
92
    CssPropertyWithConditions::simple(CssProperty::const_background_content(CURSOR_COLOR)),
93
    CssPropertyWithConditions::simple(CssProperty::const_opacity(StyleOpacity::const_new(0))),
94
    CssPropertyWithConditions::simple(CssProperty::const_transform(
95
        StyleTransformVec::from_const_slice(TEXT_CURSOR_TRANSFORM),
96
    )),
97
];
98

            
99
// -- container style
100

            
101
#[cfg(target_os = "windows")]
102
static TEXT_INPUT_CONTAINER_PROPS: &[CssPropertyWithConditions] = &[
103
    CssPropertyWithConditions::simple(CssProperty::const_position(LayoutPosition::Relative)),
104
    CssPropertyWithConditions::simple(CssProperty::const_cursor(StyleCursor::Text)),
105
    CssPropertyWithConditions::simple(CssProperty::const_box_sizing(LayoutBoxSizing::BorderBox)),
106
    CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(1))),
107
    CssPropertyWithConditions::simple(CssProperty::const_background_content(
108
        BACKGROUND_COLOR_LIGHT,
109
    )),
110
    CssPropertyWithConditions::simple(CssProperty::const_text_color(StyleTextColor {
111
        inner: COLOR_4C4C4C,
112
    })),
113
    CssPropertyWithConditions::simple(CssProperty::const_padding_left(
114
        LayoutPaddingLeft::const_px(2),
115
    )),
116
    CssPropertyWithConditions::simple(CssProperty::const_padding_right(
117
        LayoutPaddingRight::const_px(2),
118
    )),
119
    CssPropertyWithConditions::simple(CssProperty::const_padding_top(LayoutPaddingTop::const_px(
120
        1,
121
    ))),
122
    CssPropertyWithConditions::simple(CssProperty::const_padding_bottom(
123
        LayoutPaddingBottom::const_px(1),
124
    )),
125
    // border: 1px solid #484c52;
126
    CssPropertyWithConditions::simple(CssProperty::const_border_top_width(
127
        LayoutBorderTopWidth::const_px(1),
128
    )),
129
    CssPropertyWithConditions::simple(CssProperty::const_border_bottom_width(
130
        LayoutBorderBottomWidth::const_px(1),
131
    )),
132
    CssPropertyWithConditions::simple(CssProperty::const_border_left_width(
133
        LayoutBorderLeftWidth::const_px(1),
134
    )),
135
    CssPropertyWithConditions::simple(CssProperty::const_border_right_width(
136
        LayoutBorderRightWidth::const_px(1),
137
    )),
138
    CssPropertyWithConditions::simple(CssProperty::const_border_top_style(StyleBorderTopStyle {
139
        inner: BorderStyle::Inset,
140
    })),
141
    CssPropertyWithConditions::simple(CssProperty::const_border_bottom_style(
142
        StyleBorderBottomStyle {
143
            inner: BorderStyle::Inset,
144
        },
145
    )),
146
    CssPropertyWithConditions::simple(CssProperty::const_border_left_style(StyleBorderLeftStyle {
147
        inner: BorderStyle::Inset,
148
    })),
149
    CssPropertyWithConditions::simple(CssProperty::const_border_right_style(
150
        StyleBorderRightStyle {
151
            inner: BorderStyle::Inset,
152
        },
153
    )),
154
    CssPropertyWithConditions::simple(CssProperty::const_border_top_color(StyleBorderTopColor {
155
        inner: COLOR_9B9B9B,
156
    })),
157
    CssPropertyWithConditions::simple(CssProperty::const_border_bottom_color(
158
        StyleBorderBottomColor {
159
            inner: COLOR_9B9B9B,
160
        },
161
    )),
162
    CssPropertyWithConditions::simple(CssProperty::const_border_left_color(StyleBorderLeftColor {
163
        inner: COLOR_9B9B9B,
164
    })),
165
    CssPropertyWithConditions::simple(CssProperty::const_border_right_color(
166
        StyleBorderRightColor {
167
            inner: COLOR_9B9B9B,
168
        },
169
    )),
170
    CssPropertyWithConditions::simple(CssProperty::const_overflow_x(LayoutOverflow::Hidden)),
171
    CssPropertyWithConditions::simple(CssProperty::const_overflow_y(LayoutOverflow::Hidden)),
172
    CssPropertyWithConditions::simple(CssProperty::const_justify_content(
173
        LayoutJustifyContent::Center,
174
    )),
175
    // Hover(border-color: #4c4c4c;)
176
    CssPropertyWithConditions::on_hover(CssProperty::const_border_top_color(StyleBorderTopColor {
177
        inner: COLOR_4C4C4C,
178
    })),
179
    CssPropertyWithConditions::on_hover(CssProperty::const_border_bottom_color(
180
        StyleBorderBottomColor {
181
            inner: COLOR_4C4C4C,
182
        },
183
    )),
184
    CssPropertyWithConditions::on_hover(CssProperty::const_border_left_color(
185
        StyleBorderLeftColor {
186
            inner: COLOR_4C4C4C,
187
        },
188
    )),
189
    CssPropertyWithConditions::on_hover(CssProperty::const_border_right_color(
190
        StyleBorderRightColor {
191
            inner: COLOR_4C4C4C,
192
        },
193
    )),
194
    // Focus(border-color: #4286f4;)
195
    CssPropertyWithConditions::on_focus(CssProperty::const_border_top_color(StyleBorderTopColor {
196
        inner: COLOR_4286F4,
197
    })),
198
    CssPropertyWithConditions::on_focus(CssProperty::const_border_bottom_color(
199
        StyleBorderBottomColor {
200
            inner: COLOR_4286F4,
201
        },
202
    )),
203
    CssPropertyWithConditions::on_focus(CssProperty::const_border_left_color(
204
        StyleBorderLeftColor {
205
            inner: COLOR_4286F4,
206
        },
207
    )),
208
    CssPropertyWithConditions::on_focus(CssProperty::const_border_right_color(
209
        StyleBorderRightColor {
210
            inner: COLOR_4286F4,
211
        },
212
    )),
213
];
214

            
215
#[cfg(target_os = "linux")]
216
static TEXT_INPUT_CONTAINER_PROPS: &[CssPropertyWithConditions] = &[
217
    CssPropertyWithConditions::simple(CssProperty::const_position(LayoutPosition::Relative)),
218
    CssPropertyWithConditions::simple(CssProperty::const_cursor(StyleCursor::Text)),
219
    CssPropertyWithConditions::simple(CssProperty::const_box_sizing(LayoutBoxSizing::BorderBox)),
220
    CssPropertyWithConditions::simple(CssProperty::const_font_size(StyleFontSize::const_px(11))),
221
    CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(1))),
222
    CssPropertyWithConditions::simple(CssProperty::const_background_content(
223
        BACKGROUND_COLOR_LIGHT,
224
    )),
225
    CssPropertyWithConditions::simple(CssProperty::const_text_color(StyleTextColor {
226
        inner: COLOR_4C4C4C,
227
    })),
228
    CssPropertyWithConditions::simple(CssProperty::const_padding_left(
229
        LayoutPaddingLeft::const_px(2),
230
    )),
231
    CssPropertyWithConditions::simple(CssProperty::const_padding_right(
232
        LayoutPaddingRight::const_px(2),
233
    )),
234
    CssPropertyWithConditions::simple(CssProperty::const_padding_top(LayoutPaddingTop::const_px(
235
        1,
236
    ))),
237
    CssPropertyWithConditions::simple(CssProperty::const_padding_bottom(
238
        LayoutPaddingBottom::const_px(1),
239
    )),
240
    // border: 1px solid #484c52;
241
    CssPropertyWithConditions::simple(CssProperty::const_border_top_width(
242
        LayoutBorderTopWidth::const_px(1),
243
    )),
244
    CssPropertyWithConditions::simple(CssProperty::const_border_bottom_width(
245
        LayoutBorderBottomWidth::const_px(1),
246
    )),
247
    CssPropertyWithConditions::simple(CssProperty::const_border_left_width(
248
        LayoutBorderLeftWidth::const_px(1),
249
    )),
250
    CssPropertyWithConditions::simple(CssProperty::const_border_right_width(
251
        LayoutBorderRightWidth::const_px(1),
252
    )),
253
    CssPropertyWithConditions::simple(CssProperty::const_border_top_style(StyleBorderTopStyle {
254
        inner: BorderStyle::Inset,
255
    })),
256
    CssPropertyWithConditions::simple(CssProperty::const_border_bottom_style(
257
        StyleBorderBottomStyle {
258
            inner: BorderStyle::Inset,
259
        },
260
    )),
261
    CssPropertyWithConditions::simple(CssProperty::const_border_left_style(StyleBorderLeftStyle {
262
        inner: BorderStyle::Inset,
263
    })),
264
    CssPropertyWithConditions::simple(CssProperty::const_border_right_style(
265
        StyleBorderRightStyle {
266
            inner: BorderStyle::Inset,
267
        },
268
    )),
269
    CssPropertyWithConditions::simple(CssProperty::const_border_top_color(StyleBorderTopColor {
270
        inner: COLOR_9B9B9B,
271
    })),
272
    CssPropertyWithConditions::simple(CssProperty::const_border_bottom_color(
273
        StyleBorderBottomColor {
274
            inner: COLOR_9B9B9B,
275
        },
276
    )),
277
    CssPropertyWithConditions::simple(CssProperty::const_border_left_color(StyleBorderLeftColor {
278
        inner: COLOR_9B9B9B,
279
    })),
280
    CssPropertyWithConditions::simple(CssProperty::const_border_right_color(
281
        StyleBorderRightColor {
282
            inner: COLOR_9B9B9B,
283
        },
284
    )),
285
    CssPropertyWithConditions::simple(CssProperty::const_overflow_x(LayoutOverflow::Hidden)),
286
    CssPropertyWithConditions::simple(CssProperty::const_overflow_y(LayoutOverflow::Hidden)),
287
    CssPropertyWithConditions::simple(CssProperty::const_text_align(StyleTextAlign::Left)),
288
    CssPropertyWithConditions::simple(CssProperty::const_justify_content(
289
        LayoutJustifyContent::Center,
290
    )),
291
    CssPropertyWithConditions::simple(CssProperty::const_font_family(SANS_SERIF_FAMILY)),
292
    // Hover(border-color: #4286f4;)
293
    CssPropertyWithConditions::on_hover(CssProperty::const_border_top_color(StyleBorderTopColor {
294
        inner: COLOR_4286F4,
295
    })),
296
    CssPropertyWithConditions::on_hover(CssProperty::const_border_bottom_color(
297
        StyleBorderBottomColor {
298
            inner: COLOR_4286F4,
299
        },
300
    )),
301
    CssPropertyWithConditions::on_hover(CssProperty::const_border_left_color(
302
        StyleBorderLeftColor {
303
            inner: COLOR_4286F4,
304
        },
305
    )),
306
    CssPropertyWithConditions::on_hover(CssProperty::const_border_right_color(
307
        StyleBorderRightColor {
308
            inner: COLOR_4286F4,
309
        },
310
    )),
311
    // Focus(border-color: #4286f4;)
312
    CssPropertyWithConditions::on_focus(CssProperty::const_border_top_color(StyleBorderTopColor {
313
        inner: COLOR_4286F4,
314
    })),
315
    CssPropertyWithConditions::on_focus(CssProperty::const_border_bottom_color(
316
        StyleBorderBottomColor {
317
            inner: COLOR_4286F4,
318
        },
319
    )),
320
    CssPropertyWithConditions::on_focus(CssProperty::const_border_left_color(
321
        StyleBorderLeftColor {
322
            inner: COLOR_4286F4,
323
        },
324
    )),
325
    CssPropertyWithConditions::on_focus(CssProperty::const_border_right_color(
326
        StyleBorderRightColor {
327
            inner: COLOR_4286F4,
328
        },
329
    )),
330
];
331

            
332
// Mobile (Android / iOS) inherit the macOS-style container — same flex
333
// box-sizing and background; touch-target padding is the user's concern.
334
#[cfg(not(any(target_os = "windows", target_os = "linux")))]
335
static TEXT_INPUT_CONTAINER_PROPS: &[CssPropertyWithConditions] = &[
336
    CssPropertyWithConditions::simple(CssProperty::const_position(LayoutPosition::Relative)),
337
    CssPropertyWithConditions::simple(CssProperty::const_cursor(StyleCursor::Text)),
338
    CssPropertyWithConditions::simple(CssProperty::const_box_sizing(LayoutBoxSizing::BorderBox)),
339
    CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(1))),
340
    CssPropertyWithConditions::simple(CssProperty::const_background_content(
341
        BACKGROUND_COLOR_LIGHT,
342
    )),
343
    CssPropertyWithConditions::simple(CssProperty::const_text_color(StyleTextColor {
344
        inner: COLOR_4C4C4C,
345
    })),
346
    CssPropertyWithConditions::simple(CssProperty::const_padding_left(
347
        LayoutPaddingLeft::const_px(2),
348
    )),
349
    CssPropertyWithConditions::simple(CssProperty::const_padding_right(
350
        LayoutPaddingRight::const_px(2),
351
    )),
352
    CssPropertyWithConditions::simple(CssProperty::const_padding_top(LayoutPaddingTop::const_px(
353
        1,
354
    ))),
355
    CssPropertyWithConditions::simple(CssProperty::const_padding_bottom(
356
        LayoutPaddingBottom::const_px(1),
357
    )),
358
    // border: 1px solid #484c52;
359
    CssPropertyWithConditions::simple(CssProperty::const_border_top_width(
360
        LayoutBorderTopWidth::const_px(1),
361
    )),
362
    CssPropertyWithConditions::simple(CssProperty::const_border_bottom_width(
363
        LayoutBorderBottomWidth::const_px(1),
364
    )),
365
    CssPropertyWithConditions::simple(CssProperty::const_border_left_width(
366
        LayoutBorderLeftWidth::const_px(1),
367
    )),
368
    CssPropertyWithConditions::simple(CssProperty::const_border_right_width(
369
        LayoutBorderRightWidth::const_px(1),
370
    )),
371
    CssPropertyWithConditions::simple(CssProperty::const_border_top_style(StyleBorderTopStyle {
372
        inner: BorderStyle::Inset,
373
    })),
374
    CssPropertyWithConditions::simple(CssProperty::const_border_bottom_style(
375
        StyleBorderBottomStyle {
376
            inner: BorderStyle::Inset,
377
        },
378
    )),
379
    CssPropertyWithConditions::simple(CssProperty::const_border_left_style(StyleBorderLeftStyle {
380
        inner: BorderStyle::Inset,
381
    })),
382
    CssPropertyWithConditions::simple(CssProperty::const_border_right_style(
383
        StyleBorderRightStyle {
384
            inner: BorderStyle::Inset,
385
        },
386
    )),
387
    CssPropertyWithConditions::simple(CssProperty::const_border_top_color(StyleBorderTopColor {
388
        inner: COLOR_9B9B9B,
389
    })),
390
    CssPropertyWithConditions::simple(CssProperty::const_border_bottom_color(
391
        StyleBorderBottomColor {
392
            inner: COLOR_9B9B9B,
393
        },
394
    )),
395
    CssPropertyWithConditions::simple(CssProperty::const_border_left_color(StyleBorderLeftColor {
396
        inner: COLOR_9B9B9B,
397
    })),
398
    CssPropertyWithConditions::simple(CssProperty::const_border_right_color(
399
        StyleBorderRightColor {
400
            inner: COLOR_9B9B9B,
401
        },
402
    )),
403
    CssPropertyWithConditions::simple(CssProperty::const_overflow_x(LayoutOverflow::Hidden)),
404
    CssPropertyWithConditions::simple(CssProperty::const_overflow_y(LayoutOverflow::Hidden)),
405
    CssPropertyWithConditions::simple(CssProperty::const_text_align(StyleTextAlign::Left)),
406
    CssPropertyWithConditions::simple(CssProperty::const_justify_content(
407
        LayoutJustifyContent::Center,
408
    )),
409
    // Hover(border-color: #4286f4;)
410
    CssPropertyWithConditions::on_hover(CssProperty::const_border_top_color(StyleBorderTopColor {
411
        inner: COLOR_4286F4,
412
    })),
413
    CssPropertyWithConditions::on_hover(CssProperty::const_border_bottom_color(
414
        StyleBorderBottomColor {
415
            inner: COLOR_4286F4,
416
        },
417
    )),
418
    CssPropertyWithConditions::on_hover(CssProperty::const_border_left_color(
419
        StyleBorderLeftColor {
420
            inner: COLOR_4286F4,
421
        },
422
    )),
423
    CssPropertyWithConditions::on_hover(CssProperty::const_border_right_color(
424
        StyleBorderRightColor {
425
            inner: COLOR_4286F4,
426
        },
427
    )),
428
    // Focus(border-color: #4286f4;)
429
    CssPropertyWithConditions::on_focus(CssProperty::const_border_top_color(StyleBorderTopColor {
430
        inner: COLOR_4286F4,
431
    })),
432
    CssPropertyWithConditions::on_focus(CssProperty::const_border_bottom_color(
433
        StyleBorderBottomColor {
434
            inner: COLOR_4286F4,
435
        },
436
    )),
437
    CssPropertyWithConditions::on_focus(CssProperty::const_border_left_color(
438
        StyleBorderLeftColor {
439
            inner: COLOR_4286F4,
440
        },
441
    )),
442
    CssPropertyWithConditions::on_focus(CssProperty::const_border_right_color(
443
        StyleBorderRightColor {
444
            inner: COLOR_4286F4,
445
        },
446
    )),
447
];
448

            
449
// -- label style
450

            
451
#[cfg(target_os = "windows")]
452
static TEXT_INPUT_LABEL_PROPS: &[CssPropertyWithConditions] = &[
453
    CssPropertyWithConditions::simple(CssProperty::const_display(LayoutDisplay::InlineBlock)),
454
    CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(0))),
455
    CssPropertyWithConditions::simple(CssProperty::const_position(LayoutPosition::Relative)),
456
    CssPropertyWithConditions::simple(CssProperty::const_font_size(StyleFontSize::const_px(11))),
457
    CssPropertyWithConditions::simple(CssProperty::const_text_color(StyleTextColor {
458
        inner: COLOR_4C4C4C,
459
    })),
460
    CssPropertyWithConditions::simple(CssProperty::const_font_family(SANS_SERIF_FAMILY)),
461
];
462

            
463
#[cfg(target_os = "linux")]
464
static TEXT_INPUT_LABEL_PROPS: &[CssPropertyWithConditions] = &[
465
    CssPropertyWithConditions::simple(CssProperty::const_display(LayoutDisplay::InlineBlock)),
466
    CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(0))),
467
    CssPropertyWithConditions::simple(CssProperty::const_position(LayoutPosition::Relative)),
468
    CssPropertyWithConditions::simple(CssProperty::const_font_size(StyleFontSize::const_px(11))),
469
    CssPropertyWithConditions::simple(CssProperty::const_text_color(StyleTextColor {
470
        inner: COLOR_4C4C4C,
471
    })),
472
    CssPropertyWithConditions::simple(CssProperty::const_font_family(SANS_SERIF_FAMILY)),
473
];
474

            
475
#[cfg(not(any(target_os = "windows", target_os = "linux")))]
476
static TEXT_INPUT_LABEL_PROPS: &[CssPropertyWithConditions] = &[
477
    CssPropertyWithConditions::simple(CssProperty::const_display(LayoutDisplay::InlineBlock)),
478
    CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(0))),
479
    CssPropertyWithConditions::simple(CssProperty::const_position(LayoutPosition::Relative)),
480
    CssPropertyWithConditions::simple(CssProperty::const_font_size(StyleFontSize::const_px(11))),
481
    CssPropertyWithConditions::simple(CssProperty::const_text_color(StyleTextColor {
482
        inner: COLOR_4C4C4C,
483
    })),
484
    CssPropertyWithConditions::simple(CssProperty::const_font_family(SANS_SERIF_FAMILY)),
485
];
486

            
487
// --- placeholder
488

            
489
#[cfg(target_os = "windows")]
490
static TEXT_INPUT_PLACEHOLDER_PROPS: &[CssPropertyWithConditions] = &[
491
    CssPropertyWithConditions::simple(CssProperty::const_display(LayoutDisplay::Block)),
492
    CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(0))),
493
    CssPropertyWithConditions::simple(CssProperty::const_position(LayoutPosition::Absolute)),
494
    CssPropertyWithConditions::simple(CssProperty::const_top(LayoutTop::const_px(2))),
495
    CssPropertyWithConditions::simple(CssProperty::const_left(LayoutLeft::const_px(2))),
496
    CssPropertyWithConditions::simple(CssProperty::const_font_size(StyleFontSize::const_px(11))),
497
    CssPropertyWithConditions::simple(CssProperty::const_text_color(StyleTextColor {
498
        inner: COLOR_4C4C4C,
499
    })),
500
    CssPropertyWithConditions::simple(CssProperty::const_font_family(SANS_SERIF_FAMILY)),
501
    CssPropertyWithConditions::simple(CssProperty::const_opacity(StyleOpacity::const_new(100))),
502
];
503

            
504
#[cfg(target_os = "linux")]
505
static TEXT_INPUT_PLACEHOLDER_PROPS: &[CssPropertyWithConditions] = &[
506
    CssPropertyWithConditions::simple(CssProperty::const_display(LayoutDisplay::Block)),
507
    CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(0))),
508
    CssPropertyWithConditions::simple(CssProperty::const_position(LayoutPosition::Absolute)),
509
    CssPropertyWithConditions::simple(CssProperty::const_top(LayoutTop::const_px(2))),
510
    CssPropertyWithConditions::simple(CssProperty::const_left(LayoutLeft::const_px(2))),
511
    CssPropertyWithConditions::simple(CssProperty::const_font_size(StyleFontSize::const_px(11))),
512
    CssPropertyWithConditions::simple(CssProperty::const_text_color(StyleTextColor {
513
        inner: COLOR_4C4C4C,
514
    })),
515
    CssPropertyWithConditions::simple(CssProperty::const_font_family(SANS_SERIF_FAMILY)),
516
    CssPropertyWithConditions::simple(CssProperty::const_opacity(StyleOpacity::const_new(100))),
517
];
518

            
519
#[cfg(not(any(target_os = "windows", target_os = "linux")))]
520
static TEXT_INPUT_PLACEHOLDER_PROPS: &[CssPropertyWithConditions] = &[
521
    CssPropertyWithConditions::simple(CssProperty::const_display(LayoutDisplay::Block)),
522
    CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(0))),
523
    CssPropertyWithConditions::simple(CssProperty::const_position(LayoutPosition::Absolute)),
524
    CssPropertyWithConditions::simple(CssProperty::const_top(LayoutTop::const_px(2))),
525
    CssPropertyWithConditions::simple(CssProperty::const_left(LayoutLeft::const_px(2))),
526
    CssPropertyWithConditions::simple(CssProperty::const_font_size(StyleFontSize::const_px(11))),
527
    CssPropertyWithConditions::simple(CssProperty::const_text_color(StyleTextColor {
528
        inner: COLOR_4C4C4C,
529
    })),
530
    CssPropertyWithConditions::simple(CssProperty::const_font_family(SANS_SERIF_FAMILY)),
531
    CssPropertyWithConditions::simple(CssProperty::const_opacity(StyleOpacity::const_new(100))),
532
];
533

            
534
/// Single-line text input widget with platform-native styling.
535
///
536
/// Use [`TextInput::create()`] to build an instance, configure it with the
537
/// `with_*` / `set_*` builder methods, and call [`TextInput::dom()`] to
538
/// obtain a renderable DOM tree.
539
#[derive(Debug, Clone, PartialEq)]
540
#[repr(C)]
541
pub struct TextInput {
542
    pub text_input_state: TextInputStateWrapper,
543
    pub placeholder_style: CssPropertyWithConditionsVec,
544
    pub container_style: CssPropertyWithConditionsVec,
545
    pub label_style: CssPropertyWithConditionsVec,
546
}
547

            
548
/// Editable state of a text input (text buffer, cursor position, selection).
549
#[derive(Debug, Clone, PartialEq)]
550
#[repr(C)]
551
pub struct TextInputState {
552
    pub text: U32Vec, // Vec<char>
553
    pub placeholder: OptionString,
554
    pub max_len: usize,
555
    pub selection: OptionTextInputSelection,
556
    pub cursor_pos: usize,
557
}
558

            
559
/// [`TextInputState`] together with optional user callbacks and cursor animation state.
560
#[derive(Debug, Clone, PartialEq)]
561
#[repr(C)]
562
pub struct TextInputStateWrapper {
563
    pub inner: TextInputState,
564
    pub on_text_input: OptionTextInputOnTextInput,
565
    pub on_virtual_key_down: OptionTextInputOnVirtualKeyDown,
566
    pub on_focus_lost: OptionTextInputOnFocusLost,
567
    pub update_text_input_before_calling_focus_lost_fn: bool,
568
    pub update_text_input_before_calling_vk_down_fn: bool,
569
    pub cursor_animation: OptionTimerId,
570
}
571

            
572
/// Return value from a text-input callback indicating whether the framework
573
/// should update and whether the input was valid.
574
#[derive(Debug, Copy, Clone, PartialEq)]
575
#[repr(C)]
576
pub struct OnTextInputReturn {
577
    pub update: Update,
578
    pub valid: TextInputValid,
579
}
580

            
581
/// Whether the text input accepted or rejected the most recent edit.
582
#[derive(Debug, Copy, Clone, PartialEq)]
583
#[repr(C)]
584
pub enum TextInputValid {
585
    Yes,
586
    No,
587
}
588

            
589
// The text input field has a special return which specifies
590
// whether the text input should handle the character
591
pub type TextInputOnTextInputCallbackType =
592
    extern "C" fn(RefAny, CallbackInfo, TextInputState) -> OnTextInputReturn;
593
impl_widget_callback!(
594
    TextInputOnTextInput,
595
    OptionTextInputOnTextInput,
596
    TextInputOnTextInputCallback,
597
    TextInputOnTextInputCallbackType
598
);
599

            
600
azul_core::impl_managed_callback! {
601
    wrapper:        TextInputOnTextInputCallback,
602
    info_ty:        CallbackInfo,
603
    return_ty:      OnTextInputReturn,
604
    default_ret:    OnTextInputReturn { update: Update::DoNothing, valid: TextInputValid::Yes },
605
    invoker_static: TEXT_INPUT_ON_TEXT_INPUT_INVOKER,
606
    invoker_ty:     AzTextInputOnTextInputCallbackInvoker,
607
    thunk_fn:       az_text_input_on_text_input_callback_thunk,
608
    setter_fn:      AzApp_setTextInputOnTextInputCallbackInvoker,
609
    from_handle_fn: AzTextInputOnTextInputCallback_createFromHostHandle,
610
    extra_args:     [ state: TextInputState ],
611
}
612

            
613
pub type TextInputOnVirtualKeyDownCallbackType =
614
    extern "C" fn(RefAny, CallbackInfo, TextInputState) -> OnTextInputReturn;
615
impl_widget_callback!(
616
    TextInputOnVirtualKeyDown,
617
    OptionTextInputOnVirtualKeyDown,
618
    TextInputOnVirtualKeyDownCallback,
619
    TextInputOnVirtualKeyDownCallbackType
620
);
621

            
622
azul_core::impl_managed_callback! {
623
    wrapper:        TextInputOnVirtualKeyDownCallback,
624
    info_ty:        CallbackInfo,
625
    return_ty:      OnTextInputReturn,
626
    default_ret:    OnTextInputReturn { update: Update::DoNothing, valid: TextInputValid::Yes },
627
    invoker_static: TEXT_INPUT_ON_VIRTUAL_KEY_DOWN_INVOKER,
628
    invoker_ty:     AzTextInputOnVirtualKeyDownCallbackInvoker,
629
    thunk_fn:       az_text_input_on_virtual_key_down_callback_thunk,
630
    setter_fn:      AzApp_setTextInputOnVirtualKeyDownCallbackInvoker,
631
    from_handle_fn: AzTextInputOnVirtualKeyDownCallback_createFromHostHandle,
632
    extra_args:     [ state: TextInputState ],
633
}
634

            
635
pub type TextInputOnFocusLostCallbackType =
636
    extern "C" fn(RefAny, CallbackInfo, TextInputState) -> Update;
637
impl_widget_callback!(
638
    TextInputOnFocusLost,
639
    OptionTextInputOnFocusLost,
640
    TextInputOnFocusLostCallback,
641
    TextInputOnFocusLostCallbackType
642
);
643

            
644
azul_core::impl_managed_callback! {
645
    wrapper:        TextInputOnFocusLostCallback,
646
    info_ty:        CallbackInfo,
647
    return_ty:      Update,
648
    default_ret:    Update::DoNothing,
649
    invoker_static: TEXT_INPUT_ON_FOCUS_LOST_INVOKER,
650
    invoker_ty:     AzTextInputOnFocusLostCallbackInvoker,
651
    thunk_fn:       az_text_input_on_focus_lost_callback_thunk,
652
    setter_fn:      AzApp_setTextInputOnFocusLostCallbackInvoker,
653
    from_handle_fn: AzTextInputOnFocusLostCallback_createFromHostHandle,
654
    extra_args:     [ state: TextInputState ],
655
}
656

            
657
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
658
#[repr(C, u8)]
659
pub enum TextInputSelection {
660
    All,
661
    FromTo(TextInputSelectionRange),
662
}
663

            
664
azul_css::impl_option!(
665
    TextInputSelection,
666
    OptionTextInputSelection,
667
    copy = false,
668
    [Debug, Clone, Hash, PartialEq, Eq]
669
);
670

            
671
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
672
#[repr(C)]
673
pub struct TextInputSelectionRange {
674
    pub dir_from: usize,
675
    pub dir_to: usize,
676
}
677

            
678
impl Default for TextInput {
679
    fn default() -> Self {
680
        TextInput {
681
            text_input_state: TextInputStateWrapper::default(),
682
            placeholder_style: CssPropertyWithConditionsVec::from_const_slice(
683
                TEXT_INPUT_PLACEHOLDER_PROPS,
684
            ),
685
            container_style: CssPropertyWithConditionsVec::from_const_slice(
686
                TEXT_INPUT_CONTAINER_PROPS,
687
            ),
688
            label_style: CssPropertyWithConditionsVec::from_const_slice(TEXT_INPUT_LABEL_PROPS),
689
        }
690
    }
691
}
692

            
693
impl Default for TextInputState {
694
    fn default() -> Self {
695
        TextInputState {
696
            text: Vec::new().into(),
697
            placeholder: None.into(),
698
            max_len: 50,
699
            selection: None.into(),
700
            cursor_pos: 0,
701
        }
702
    }
703
}
704

            
705
impl TextInputState {
706
    pub fn get_text(&self) -> String {
707
        self.text
708
            .iter()
709
            .filter_map(|c| core::char::from_u32(*c))
710
            .collect()
711
    }
712
}
713

            
714
impl Default for TextInputStateWrapper {
715
    fn default() -> Self {
716
        TextInputStateWrapper {
717
            inner: TextInputState::default(),
718
            on_text_input: None.into(),
719
            on_virtual_key_down: None.into(),
720
            on_focus_lost: None.into(),
721
            update_text_input_before_calling_focus_lost_fn: true,
722
            update_text_input_before_calling_vk_down_fn: true,
723
            cursor_animation: None.into(),
724
        }
725
    }
726
}
727

            
728
impl TextInput {
729
    pub fn create() -> Self {
730
        Self::default()
731
    }
732

            
733
    pub fn with_text(mut self, text: AzString) -> Self {
734
        self.set_text(text);
735
        self
736
    }
737

            
738
    pub fn set_text(&mut self, text: AzString) {
739
        self.text_input_state.inner.text = text
740
            .as_str()
741
            .chars()
742
            .map(|c| c as u32)
743
            .collect::<Vec<_>>()
744
            .into();
745
    }
746

            
747
    pub fn set_placeholder(&mut self, placeholder: AzString) {
748
        self.text_input_state.inner.placeholder = Some(placeholder).into();
749
    }
750

            
751
    pub fn with_placeholder(mut self, placeholder: AzString) -> Self {
752
        self.set_placeholder(placeholder);
753
        self
754
    }
755

            
756
    pub fn set_on_text_input<C: Into<TextInputOnTextInputCallback>>(
757
        &mut self,
758
        refany: RefAny,
759
        callback: C,
760
    ) {
761
        self.text_input_state.on_text_input = Some(TextInputOnTextInput {
762
            callback: callback.into(),
763
            refany,
764
        })
765
        .into();
766
    }
767

            
768
    pub fn with_on_text_input<C: Into<TextInputOnTextInputCallback>>(
769
        mut self,
770
        refany: RefAny,
771
        callback: C,
772
    ) -> Self {
773
        self.set_on_text_input(refany, callback);
774
        self
775
    }
776

            
777
    pub fn set_on_virtual_key_down<C: Into<TextInputOnVirtualKeyDownCallback>>(
778
        &mut self,
779
        refany: RefAny,
780
        callback: C,
781
    ) {
782
        self.text_input_state.on_virtual_key_down = Some(TextInputOnVirtualKeyDown {
783
            callback: callback.into(),
784
            refany,
785
        })
786
        .into();
787
    }
788

            
789
    pub fn with_on_virtual_key_down<C: Into<TextInputOnVirtualKeyDownCallback>>(
790
        mut self,
791
        refany: RefAny,
792
        callback: C,
793
    ) -> Self {
794
        self.set_on_virtual_key_down(refany, callback);
795
        self
796
    }
797

            
798
    pub fn set_on_focus_lost<C: Into<TextInputOnFocusLostCallback>>(
799
        &mut self,
800
        refany: RefAny,
801
        callback: C,
802
    ) {
803
        self.text_input_state.on_focus_lost = Some(TextInputOnFocusLost {
804
            callback: callback.into(),
805
            refany,
806
        })
807
        .into();
808
    }
809

            
810
    pub fn with_on_focus_lost<C: Into<TextInputOnFocusLostCallback>>(
811
        mut self,
812
        refany: RefAny,
813
        callback: C,
814
    ) -> Self {
815
        self.set_on_focus_lost(refany, callback);
816
        self
817
    }
818

            
819
    pub fn set_placeholder_style(&mut self, style: CssPropertyWithConditionsVec) {
820
        self.placeholder_style = style;
821
    }
822

            
823
    pub fn with_placeholder_style(mut self, style: CssPropertyWithConditionsVec) -> Self {
824
        self.set_placeholder_style(style);
825
        self
826
    }
827

            
828
    pub fn set_container_style(&mut self, style: CssPropertyWithConditionsVec) {
829
        self.container_style = style;
830
    }
831

            
832
    pub fn with_container_style(mut self, style: CssPropertyWithConditionsVec) -> Self {
833
        self.set_container_style(style);
834
        self
835
    }
836

            
837
    pub fn set_label_style(&mut self, style: CssPropertyWithConditionsVec) {
838
        self.label_style = style;
839
    }
840

            
841
    pub fn with_label_style(mut self, style: CssPropertyWithConditionsVec) -> Self {
842
        self.set_label_style(style);
843
        self
844
    }
845

            
846
    pub fn swap_with_default(&mut self) -> Self {
847
        let mut s = Self::default();
848
        core::mem::swap(&mut s, self);
849
        s
850
    }
851

            
852
    pub fn dom(mut self) -> Dom {
853
        use azul_core::{
854
            callbacks::CoreCallbackData,
855
            dom::{EventFilter, FocusEventFilter, HoverEventFilter, IdOrClass::Class, TabIndex},
856
        };
857

            
858
        self.text_input_state.inner.cursor_pos = self.text_input_state.inner.text.len();
859

            
860
        let label_text: String = self
861
            .text_input_state
862
            .inner
863
            .text
864
            .iter()
865
            .filter_map(|s| core::char::from_u32(*s))
866
            .collect();
867

            
868
        let placeholder = self
869
            .text_input_state
870
            .inner
871
            .placeholder
872
            .as_ref()
873
            .map(|s| s.as_str().to_string())
874
            .unwrap_or_default();
875

            
876
        let state_ref = RefAny::new(self.text_input_state);
877

            
878
        Dom::create_div()
879
            .with_ids_and_classes(vec![Class("__azul-native-text-input-container".into())].into())
880
            .with_css_props(self.container_style)
881
            .with_tab_index(TabIndex::Auto)
882
            .with_dataset(Some(state_ref.clone()).into())
883
            .with_callbacks(
884
                vec![
885
                    CoreCallbackData {
886
                        event: EventFilter::Focus(FocusEventFilter::FocusReceived),
887
                        refany: state_ref.clone(),
888
                        callback: CoreCallback {
889
                            cb: default_on_focus_received as usize,
890
                            ctx: azul_core::refany::OptionRefAny::None,
891
                        },
892
                    },
893
                    CoreCallbackData {
894
                        event: EventFilter::Focus(FocusEventFilter::FocusLost),
895
                        refany: state_ref.clone(),
896
                        callback: CoreCallback {
897
                            cb: default_on_focus_lost as usize,
898
                            ctx: azul_core::refany::OptionRefAny::None,
899
                        },
900
                    },
901
                    CoreCallbackData {
902
                        event: EventFilter::Focus(FocusEventFilter::TextInput),
903
                        refany: state_ref.clone(),
904
                        callback: CoreCallback {
905
                            cb: default_on_text_input as usize,
906
                            ctx: azul_core::refany::OptionRefAny::None,
907
                        },
908
                    },
909
                    CoreCallbackData {
910
                        event: EventFilter::Focus(FocusEventFilter::VirtualKeyDown),
911
                        refany: state_ref.clone(),
912
                        callback: CoreCallback {
913
                            cb: default_on_virtual_key_down as usize,
914
                            ctx: azul_core::refany::OptionRefAny::None,
915
                        },
916
                    },
917
                    CoreCallbackData {
918
                        event: EventFilter::Hover(HoverEventFilter::MouseOver),
919
                        refany: state_ref.clone(),
920
                        callback: CoreCallback {
921
                            cb: default_on_mouse_hover as usize,
922
                            ctx: azul_core::refany::OptionRefAny::None,
923
                        },
924
                    },
925
                ]
926
                .into(),
927
            )
928
            .with_children(
929
                vec![
930
                    Dom::create_text(placeholder)
931
                        .with_ids_and_classes(
932
                            vec![Class("__azul-native-text-input-placeholder".into())].into(),
933
                        )
934
                        .with_css_props(self.placeholder_style),
935
                    Dom::create_text(label_text)
936
                        .with_ids_and_classes(
937
                            vec![Class("__azul-native-text-input-label".into())].into(),
938
                        )
939
                        .with_css_props(self.label_style)
940
                        .with_children(
941
                            vec![Dom::create_div()
942
                                .with_ids_and_classes(
943
                                    vec![Class("__azul-native-text-input-cursor".into())].into(),
944
                                )
945
                                .with_css_props(CssPropertyWithConditionsVec::from_const_slice(
946
                                    TEXT_CURSOR_PROPS,
947
                                ))]
948
                            .into(),
949
                        ),
950
                ]
951
                .into(),
952
            )
953
    }
954
}
955

            
956
extern "C" fn default_on_focus_received(mut text_input: RefAny, mut info: CallbackInfo) -> Update {
957
    let mut text_input = match text_input.downcast_mut::<TextInputStateWrapper>() {
958
        Some(s) => s,
959
        None => return Update::DoNothing,
960
    };
961

            
962
    let text_input = &mut *text_input;
963

            
964
    let placeholder_text_node_id = match info.get_first_child(info.get_hit_node()) {
965
        Some(s) => s,
966
        None => return Update::DoNothing,
967
    };
968

            
969
    // hide the placeholder text
970
    if text_input.inner.text.is_empty() {
971
        info.set_css_property(
972
            placeholder_text_node_id,
973
            CssProperty::const_opacity(StyleOpacity::const_new(0)),
974
        );
975
    }
976

            
977
    text_input.inner.cursor_pos = text_input.inner.text.len();
978

            
979
    Update::DoNothing
980
}
981

            
982
extern "C" fn default_on_focus_lost(mut text_input: RefAny, mut info: CallbackInfo) -> Update {
983
    let mut text_input = match text_input.downcast_mut::<TextInputStateWrapper>() {
984
        Some(s) => s,
985
        None => return Update::DoNothing,
986
    };
987

            
988
    let text_input = &mut *text_input;
989

            
990
    let placeholder_text_node_id = match info.get_first_child(info.get_hit_node()) {
991
        Some(s) => s,
992
        None => return Update::DoNothing,
993
    };
994

            
995
    // show the placeholder text
996
    if text_input.inner.text.is_empty() {
997
        info.set_css_property(
998
            placeholder_text_node_id,
999
            CssProperty::const_opacity(StyleOpacity::const_new(100)),
        );
    }
    // rustc doesn't understand the borrowing lifetime here
    let text_input = &mut *text_input;
    let onfocuslost = &mut text_input.on_focus_lost;
    let inner = text_input.inner.clone();
    match onfocuslost.as_mut() {
        Some(TextInputOnFocusLost { callback, refany }) => {
            (callback.cb)(refany.clone(), info.clone(), inner)
        }
        None => Update::DoNothing,
    }
}
extern "C" fn default_on_text_input(text_input: RefAny, info: CallbackInfo) -> Update {
    default_on_text_input_inner(text_input, info).unwrap_or(Update::DoNothing)
}
fn default_on_text_input_inner(mut text_input: RefAny, mut info: CallbackInfo) -> Option<Update> {
    let mut text_input = text_input.downcast_mut::<TextInputStateWrapper>()?;
    // Get the text changeset (replaces old keyboard_state.current_char API)
    let changeset = info.get_text_changeset()?;
    let inserted_text = changeset.inserted_text.as_str().to_string();
    // Early return if no text to insert
    if inserted_text.is_empty() {
        return None;
    }
    let placeholder_node_id = info.get_first_child(info.get_hit_node())?;
    let label_node_id = info.get_next_sibling(placeholder_node_id)?;
    let _cursor_node_id = info.get_first_child(label_node_id)?;
    let result = {
        // rustc doesn't understand the borrowing lifetime here
        let text_input = &mut *text_input;
        let ontextinput = &mut text_input.on_text_input;
        // inner_clone has the new text
        let mut inner_clone = text_input.inner.clone();
        inner_clone.cursor_pos = inner_clone.cursor_pos.saturating_add(inserted_text.len());
        inner_clone.text = {
            let mut internal = inner_clone.text.clone().into_library_owned_vec();
            internal.extend(inserted_text.chars().map(|c| c as u32));
            internal.into()
        };
        match ontextinput.as_mut() {
            Some(TextInputOnTextInput { callback, refany }) => {
                (callback.cb)(refany.clone(), info.clone(), inner_clone)
            }
            None => OnTextInputReturn {
                update: Update::DoNothing,
                valid: TextInputValid::Yes,
            },
        }
    };
    if result.valid == TextInputValid::Yes {
        // hide the placeholder text
        info.set_css_property(
            placeholder_node_id,
            CssProperty::const_opacity(StyleOpacity::const_new(0)),
        );
        // append to the text
        text_input.inner.text = {
            let mut internal = text_input.inner.text.clone().into_library_owned_vec();
            internal.extend(inserted_text.chars().map(|c| c as u32));
            internal.into()
        };
        text_input.inner.cursor_pos = text_input
            .inner
            .cursor_pos
            .saturating_add(inserted_text.len());
        info.change_node_text(label_node_id, text_input.inner.get_text().into());
    }
    Some(result.update)
}
extern "C" fn default_on_virtual_key_down(text_input: RefAny, info: CallbackInfo) -> Update {
    default_on_virtual_key_down_inner(text_input, info).unwrap_or(Update::DoNothing)
}
fn default_on_virtual_key_down_inner(
    mut text_input: RefAny,
    mut info: CallbackInfo,
) -> Option<Update> {
    let mut text_input = text_input.downcast_mut::<TextInputStateWrapper>()?;
    let keyboard_state = info.get_current_keyboard_state();
    let c = keyboard_state.current_virtual_keycode.into_option()?;
    let placeholder_node_id = info.get_first_child(info.get_hit_node())?;
    let label_node_id = info.get_next_sibling(placeholder_node_id)?;
    let _cursor_node_id = info.get_first_child(label_node_id)?;
    if c != VirtualKeyCode::Back {
        return None;
    }
    text_input.inner.text = {
        let mut internal = text_input.inner.text.clone().into_library_owned_vec();
        internal.pop();
        internal.into()
    };
    text_input.inner.cursor_pos = text_input.inner.cursor_pos.saturating_sub(1);
    info.change_node_text(label_node_id, text_input.inner.get_text().into());
    None
}
extern "C" fn default_on_mouse_hover(mut text_input: RefAny, _info: CallbackInfo) -> Update {
    let _text_input = match text_input.downcast_mut::<TextInputStateWrapper>() {
        Some(s) => s,
        None => return Update::DoNothing,
    };
    Update::DoNothing
}