Skip to main content

azul_layout/widgets/
text_input.rs

1//! Text input (demonstrates two-way data binding)
2
3use alloc::{string::String, vec::Vec};
4
5use azul_core::{
6    callbacks::{CoreCallback, CoreCallbackData, Update},
7    dom::Dom,
8    refany::RefAny,
9    task::OptionTimerId,
10    window::VirtualKeyCode,
11};
12use azul_css::{
13    dynamic_selector::{CssPropertyWithConditions, CssPropertyWithConditionsVec},
14    props::{
15        basic::*,
16        layout::*,
17        property::{CssProperty, *},
18        style::*,
19    },
20    *,
21};
22
23use crate::callbacks::{Callback, CallbackInfo};
24
25const BACKGROUND_COLOR: ColorU = ColorU {
26    r: 255,
27    g: 255,
28    b: 255,
29    a: 255,
30}; // white
31const BLACK: ColorU = ColorU {
32    r: 0,
33    g: 0,
34    b: 0,
35    a: 255,
36};
37const TEXT_COLOR: StyleTextColor = StyleTextColor { inner: BLACK }; // black
38const COLOR_9B9B9B: ColorU = ColorU {
39    r: 155,
40    g: 155,
41    b: 155,
42    a: 255,
43}; // #9b9b9b
44const COLOR_4286F4: ColorU = ColorU {
45    r: 66,
46    g: 134,
47    b: 244,
48    a: 255,
49}; // #4286f4
50const COLOR_4C4C4C: ColorU = ColorU {
51    r: 76,
52    g: 76,
53    b: 76,
54    a: 255,
55}; // #4C4C4C
56
57const CURSOR_COLOR_BLACK: &[StyleBackgroundContent] = &[StyleBackgroundContent::Color(BLACK)];
58const CURSOR_COLOR: StyleBackgroundContentVec =
59    StyleBackgroundContentVec::from_const_slice(CURSOR_COLOR_BLACK);
60
61const BACKGROUND_THEME_LIGHT: &[StyleBackgroundContent] =
62    &[StyleBackgroundContent::Color(BACKGROUND_COLOR)];
63const BACKGROUND_COLOR_LIGHT: StyleBackgroundContentVec =
64    StyleBackgroundContentVec::from_const_slice(BACKGROUND_THEME_LIGHT);
65
66const SANS_SERIF_STR: &str = "sans-serif";
67const SANS_SERIF: AzString = AzString::from_const_str(SANS_SERIF_STR);
68const SANS_SERIF_FAMILIES: &[StyleFontFamily] = &[StyleFontFamily::System(SANS_SERIF)];
69const SANS_SERIF_FAMILY: StyleFontFamilyVec =
70    StyleFontFamilyVec::from_const_slice(SANS_SERIF_FAMILIES);
71
72// -- cursor style
73
74const TEXT_CURSOR_TRANSFORM: &[StyleTransform] =
75    &[StyleTransform::Translate(StyleTransformTranslate2D {
76        x: PixelValue::const_px(0),
77        y: PixelValue::const_px(2),
78    })];
79
80static TEXT_CURSOR_PROPS: &[CssPropertyWithConditions] = &[
81    CssPropertyWithConditions::simple(CssProperty::const_position(LayoutPosition::Absolute)),
82    CssPropertyWithConditions::simple(CssProperty::const_width(LayoutWidth::const_px(1))),
83    CssPropertyWithConditions::simple(CssProperty::const_height(LayoutHeight::const_px(11))),
84    CssPropertyWithConditions::simple(CssProperty::const_background_content(CURSOR_COLOR)),
85    CssPropertyWithConditions::simple(CssProperty::const_opacity(StyleOpacity::const_new(0))),
86    CssPropertyWithConditions::simple(CssProperty::const_transform(
87        StyleTransformVec::from_const_slice(TEXT_CURSOR_TRANSFORM),
88    )),
89];
90
91// -- container style
92
93#[cfg(target_os = "windows")]
94static TEXT_INPUT_CONTAINER_PROPS: &[CssPropertyWithConditions] = &[
95    CssPropertyWithConditions::simple(CssProperty::const_position(LayoutPosition::Relative)),
96    CssPropertyWithConditions::simple(CssProperty::const_cursor(StyleCursor::Text)),
97    CssPropertyWithConditions::simple(CssProperty::const_box_sizing(LayoutBoxSizing::BorderBox)),
98    CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(1))),
99    CssPropertyWithConditions::simple(CssProperty::const_background_content(
100        BACKGROUND_COLOR_LIGHT,
101    )),
102    CssPropertyWithConditions::simple(CssProperty::const_text_color(StyleTextColor {
103        inner: COLOR_4C4C4C,
104    })),
105    CssPropertyWithConditions::simple(CssProperty::const_padding_left(
106        LayoutPaddingLeft::const_px(2),
107    )),
108    CssPropertyWithConditions::simple(CssProperty::const_padding_right(
109        LayoutPaddingRight::const_px(2),
110    )),
111    CssPropertyWithConditions::simple(CssProperty::const_padding_top(LayoutPaddingTop::const_px(
112        1,
113    ))),
114    CssPropertyWithConditions::simple(CssProperty::const_padding_bottom(
115        LayoutPaddingBottom::const_px(1),
116    )),
117    // border: 1px solid #484c52;
118    CssPropertyWithConditions::simple(CssProperty::const_border_top_width(
119        LayoutBorderTopWidth::const_px(1),
120    )),
121    CssPropertyWithConditions::simple(CssProperty::const_border_bottom_width(
122        LayoutBorderBottomWidth::const_px(1),
123    )),
124    CssPropertyWithConditions::simple(CssProperty::const_border_left_width(
125        LayoutBorderLeftWidth::const_px(1),
126    )),
127    CssPropertyWithConditions::simple(CssProperty::const_border_right_width(
128        LayoutBorderRightWidth::const_px(1),
129    )),
130    CssPropertyWithConditions::simple(CssProperty::const_border_top_style(StyleBorderTopStyle {
131        inner: BorderStyle::Inset,
132    })),
133    CssPropertyWithConditions::simple(CssProperty::const_border_bottom_style(
134        StyleBorderBottomStyle {
135            inner: BorderStyle::Inset,
136        },
137    )),
138    CssPropertyWithConditions::simple(CssProperty::const_border_left_style(StyleBorderLeftStyle {
139        inner: BorderStyle::Inset,
140    })),
141    CssPropertyWithConditions::simple(CssProperty::const_border_right_style(
142        StyleBorderRightStyle {
143            inner: BorderStyle::Inset,
144        },
145    )),
146    CssPropertyWithConditions::simple(CssProperty::const_border_top_color(StyleBorderTopColor {
147        inner: COLOR_9B9B9B,
148    })),
149    CssPropertyWithConditions::simple(CssProperty::const_border_bottom_color(
150        StyleBorderBottomColor {
151            inner: COLOR_9B9B9B,
152        },
153    )),
154    CssPropertyWithConditions::simple(CssProperty::const_border_left_color(StyleBorderLeftColor {
155        inner: COLOR_9B9B9B,
156    })),
157    CssPropertyWithConditions::simple(CssProperty::const_border_right_color(
158        StyleBorderRightColor {
159            inner: COLOR_9B9B9B,
160        },
161    )),
162    CssPropertyWithConditions::simple(CssProperty::const_overflow_x(LayoutOverflow::Hidden)),
163    CssPropertyWithConditions::simple(CssProperty::const_overflow_y(LayoutOverflow::Hidden)),
164    CssPropertyWithConditions::simple(CssProperty::const_justify_content(
165        LayoutJustifyContent::Center,
166    )),
167    // Hover(border-color: #4c4c4c;)
168    CssPropertyWithConditions::on_hover(CssProperty::const_border_top_color(StyleBorderTopColor {
169        inner: COLOR_4C4C4C,
170    })),
171    CssPropertyWithConditions::on_hover(CssProperty::const_border_bottom_color(
172        StyleBorderBottomColor {
173            inner: COLOR_4C4C4C,
174        },
175    )),
176    CssPropertyWithConditions::on_hover(CssProperty::const_border_left_color(
177        StyleBorderLeftColor {
178            inner: COLOR_4C4C4C,
179        },
180    )),
181    CssPropertyWithConditions::on_hover(CssProperty::const_border_right_color(
182        StyleBorderRightColor {
183            inner: COLOR_4C4C4C,
184        },
185    )),
186    // Focus(border-color: #4286f4;)
187    CssPropertyWithConditions::on_focus(CssProperty::const_border_top_color(StyleBorderTopColor {
188        inner: COLOR_4286F4,
189    })),
190    CssPropertyWithConditions::on_focus(CssProperty::const_border_bottom_color(
191        StyleBorderBottomColor {
192            inner: COLOR_4286F4,
193        },
194    )),
195    CssPropertyWithConditions::on_focus(CssProperty::const_border_left_color(
196        StyleBorderLeftColor {
197            inner: COLOR_4286F4,
198        },
199    )),
200    CssPropertyWithConditions::on_focus(CssProperty::const_border_right_color(
201        StyleBorderRightColor {
202            inner: COLOR_4286F4,
203        },
204    )),
205];
206
207#[cfg(target_os = "linux")]
208static TEXT_INPUT_CONTAINER_PROPS: &[CssPropertyWithConditions] = &[
209    CssPropertyWithConditions::simple(CssProperty::const_position(LayoutPosition::Relative)),
210    CssPropertyWithConditions::simple(CssProperty::const_cursor(StyleCursor::Text)),
211    CssPropertyWithConditions::simple(CssProperty::const_box_sizing(LayoutBoxSizing::BorderBox)),
212    CssPropertyWithConditions::simple(CssProperty::const_font_size(StyleFontSize::const_px(11))),
213    CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(1))),
214    CssPropertyWithConditions::simple(CssProperty::const_background_content(
215        BACKGROUND_COLOR_LIGHT,
216    )),
217    CssPropertyWithConditions::simple(CssProperty::const_text_color(StyleTextColor {
218        inner: COLOR_4C4C4C,
219    })),
220    CssPropertyWithConditions::simple(CssProperty::const_padding_left(
221        LayoutPaddingLeft::const_px(2),
222    )),
223    CssPropertyWithConditions::simple(CssProperty::const_padding_right(
224        LayoutPaddingRight::const_px(2),
225    )),
226    CssPropertyWithConditions::simple(CssProperty::const_padding_top(LayoutPaddingTop::const_px(
227        1,
228    ))),
229    CssPropertyWithConditions::simple(CssProperty::const_padding_bottom(
230        LayoutPaddingBottom::const_px(1),
231    )),
232    // border: 1px solid #484c52;
233    CssPropertyWithConditions::simple(CssProperty::const_border_top_width(
234        LayoutBorderTopWidth::const_px(1),
235    )),
236    CssPropertyWithConditions::simple(CssProperty::const_border_bottom_width(
237        LayoutBorderBottomWidth::const_px(1),
238    )),
239    CssPropertyWithConditions::simple(CssProperty::const_border_left_width(
240        LayoutBorderLeftWidth::const_px(1),
241    )),
242    CssPropertyWithConditions::simple(CssProperty::const_border_right_width(
243        LayoutBorderRightWidth::const_px(1),
244    )),
245    CssPropertyWithConditions::simple(CssProperty::const_border_top_style(StyleBorderTopStyle {
246        inner: BorderStyle::Inset,
247    })),
248    CssPropertyWithConditions::simple(CssProperty::const_border_bottom_style(
249        StyleBorderBottomStyle {
250            inner: BorderStyle::Inset,
251        },
252    )),
253    CssPropertyWithConditions::simple(CssProperty::const_border_left_style(StyleBorderLeftStyle {
254        inner: BorderStyle::Inset,
255    })),
256    CssPropertyWithConditions::simple(CssProperty::const_border_right_style(
257        StyleBorderRightStyle {
258            inner: BorderStyle::Inset,
259        },
260    )),
261    CssPropertyWithConditions::simple(CssProperty::const_border_top_color(StyleBorderTopColor {
262        inner: COLOR_9B9B9B,
263    })),
264    CssPropertyWithConditions::simple(CssProperty::const_border_bottom_color(
265        StyleBorderBottomColor {
266            inner: COLOR_9B9B9B,
267        },
268    )),
269    CssPropertyWithConditions::simple(CssProperty::const_border_left_color(StyleBorderLeftColor {
270        inner: COLOR_9B9B9B,
271    })),
272    CssPropertyWithConditions::simple(CssProperty::const_border_right_color(
273        StyleBorderRightColor {
274            inner: COLOR_9B9B9B,
275        },
276    )),
277    CssPropertyWithConditions::simple(CssProperty::const_overflow_x(LayoutOverflow::Hidden)),
278    CssPropertyWithConditions::simple(CssProperty::const_overflow_y(LayoutOverflow::Hidden)),
279    CssPropertyWithConditions::simple(CssProperty::const_text_align(StyleTextAlign::Left)),
280    CssPropertyWithConditions::simple(CssProperty::const_font_size(StyleFontSize::const_px(11))),
281    CssPropertyWithConditions::simple(CssProperty::const_justify_content(
282        LayoutJustifyContent::Center,
283    )),
284    CssPropertyWithConditions::simple(CssProperty::const_font_family(SANS_SERIF_FAMILY)),
285    // Hover(border-color: #4286f4;)
286    CssPropertyWithConditions::on_hover(CssProperty::const_border_top_color(StyleBorderTopColor {
287        inner: COLOR_4286F4,
288    })),
289    CssPropertyWithConditions::on_hover(CssProperty::const_border_bottom_color(
290        StyleBorderBottomColor {
291            inner: COLOR_4286F4,
292        },
293    )),
294    CssPropertyWithConditions::on_hover(CssProperty::const_border_left_color(
295        StyleBorderLeftColor {
296            inner: COLOR_4286F4,
297        },
298    )),
299    CssPropertyWithConditions::on_hover(CssProperty::const_border_right_color(
300        StyleBorderRightColor {
301            inner: COLOR_4286F4,
302        },
303    )),
304    // Focus(border-color: #4286f4;)
305    CssPropertyWithConditions::on_focus(CssProperty::const_border_top_color(StyleBorderTopColor {
306        inner: COLOR_4286F4,
307    })),
308    CssPropertyWithConditions::on_focus(CssProperty::const_border_bottom_color(
309        StyleBorderBottomColor {
310            inner: COLOR_4286F4,
311        },
312    )),
313    CssPropertyWithConditions::on_focus(CssProperty::const_border_left_color(
314        StyleBorderLeftColor {
315            inner: COLOR_4286F4,
316        },
317    )),
318    CssPropertyWithConditions::on_focus(CssProperty::const_border_right_color(
319        StyleBorderRightColor {
320            inner: COLOR_4286F4,
321        },
322    )),
323];
324
325#[cfg(target_os = "macos")]
326static TEXT_INPUT_CONTAINER_PROPS: &[CssPropertyWithConditions] = &[
327    CssPropertyWithConditions::simple(CssProperty::const_position(LayoutPosition::Relative)),
328    CssPropertyWithConditions::simple(CssProperty::const_cursor(StyleCursor::Text)),
329    CssPropertyWithConditions::simple(CssProperty::const_box_sizing(LayoutBoxSizing::BorderBox)),
330    CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(1))),
331    CssPropertyWithConditions::simple(CssProperty::const_background_content(
332        BACKGROUND_COLOR_LIGHT,
333    )),
334    CssPropertyWithConditions::simple(CssProperty::const_text_color(StyleTextColor {
335        inner: COLOR_4C4C4C,
336    })),
337    CssPropertyWithConditions::simple(CssProperty::const_padding_left(
338        LayoutPaddingLeft::const_px(2),
339    )),
340    CssPropertyWithConditions::simple(CssProperty::const_padding_right(
341        LayoutPaddingRight::const_px(2),
342    )),
343    CssPropertyWithConditions::simple(CssProperty::const_padding_top(LayoutPaddingTop::const_px(
344        1,
345    ))),
346    CssPropertyWithConditions::simple(CssProperty::const_padding_bottom(
347        LayoutPaddingBottom::const_px(1),
348    )),
349    // border: 1px solid #484c52;
350    CssPropertyWithConditions::simple(CssProperty::const_border_top_width(
351        LayoutBorderTopWidth::const_px(1),
352    )),
353    CssPropertyWithConditions::simple(CssProperty::const_border_bottom_width(
354        LayoutBorderBottomWidth::const_px(1),
355    )),
356    CssPropertyWithConditions::simple(CssProperty::const_border_left_width(
357        LayoutBorderLeftWidth::const_px(1),
358    )),
359    CssPropertyWithConditions::simple(CssProperty::const_border_right_width(
360        LayoutBorderRightWidth::const_px(1),
361    )),
362    CssPropertyWithConditions::simple(CssProperty::const_border_top_style(StyleBorderTopStyle {
363        inner: BorderStyle::Inset,
364    })),
365    CssPropertyWithConditions::simple(CssProperty::const_border_bottom_style(
366        StyleBorderBottomStyle {
367            inner: BorderStyle::Inset,
368        },
369    )),
370    CssPropertyWithConditions::simple(CssProperty::const_border_left_style(StyleBorderLeftStyle {
371        inner: BorderStyle::Inset,
372    })),
373    CssPropertyWithConditions::simple(CssProperty::const_border_right_style(
374        StyleBorderRightStyle {
375            inner: BorderStyle::Inset,
376        },
377    )),
378    CssPropertyWithConditions::simple(CssProperty::const_border_top_color(StyleBorderTopColor {
379        inner: COLOR_9B9B9B,
380    })),
381    CssPropertyWithConditions::simple(CssProperty::const_border_bottom_color(
382        StyleBorderBottomColor {
383            inner: COLOR_9B9B9B,
384        },
385    )),
386    CssPropertyWithConditions::simple(CssProperty::const_border_left_color(StyleBorderLeftColor {
387        inner: COLOR_9B9B9B,
388    })),
389    CssPropertyWithConditions::simple(CssProperty::const_border_right_color(
390        StyleBorderRightColor {
391            inner: COLOR_9B9B9B,
392        },
393    )),
394    CssPropertyWithConditions::simple(CssProperty::const_overflow_x(LayoutOverflow::Hidden)),
395    CssPropertyWithConditions::simple(CssProperty::const_overflow_y(LayoutOverflow::Hidden)),
396    CssPropertyWithConditions::simple(CssProperty::const_text_align(StyleTextAlign::Left)),
397    CssPropertyWithConditions::simple(CssProperty::const_justify_content(
398        LayoutJustifyContent::Center,
399    )),
400    // Hover(border-color: #4286f4;)
401    CssPropertyWithConditions::on_hover(CssProperty::const_border_top_color(StyleBorderTopColor {
402        inner: COLOR_4286F4,
403    })),
404    CssPropertyWithConditions::on_hover(CssProperty::const_border_bottom_color(
405        StyleBorderBottomColor {
406            inner: COLOR_4286F4,
407        },
408    )),
409    CssPropertyWithConditions::on_hover(CssProperty::const_border_left_color(
410        StyleBorderLeftColor {
411            inner: COLOR_4286F4,
412        },
413    )),
414    CssPropertyWithConditions::on_hover(CssProperty::const_border_right_color(
415        StyleBorderRightColor {
416            inner: COLOR_4286F4,
417        },
418    )),
419    // Focus(border-color: #4286f4;)
420    CssPropertyWithConditions::on_focus(CssProperty::const_border_top_color(StyleBorderTopColor {
421        inner: COLOR_4286F4,
422    })),
423    CssPropertyWithConditions::on_focus(CssProperty::const_border_bottom_color(
424        StyleBorderBottomColor {
425            inner: COLOR_4286F4,
426        },
427    )),
428    CssPropertyWithConditions::on_focus(CssProperty::const_border_left_color(
429        StyleBorderLeftColor {
430            inner: COLOR_4286F4,
431        },
432    )),
433    CssPropertyWithConditions::on_focus(CssProperty::const_border_right_color(
434        StyleBorderRightColor {
435            inner: COLOR_4286F4,
436        },
437    )),
438];
439
440// -- label style
441
442#[cfg(target_os = "windows")]
443static TEXT_INPUT_LABEL_PROPS: &[CssPropertyWithConditions] = &[
444    CssPropertyWithConditions::simple(CssProperty::const_display(LayoutDisplay::InlineBlock)),
445    CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(0))),
446    CssPropertyWithConditions::simple(CssProperty::const_position(LayoutPosition::Relative)),
447    CssPropertyWithConditions::simple(CssProperty::const_font_size(StyleFontSize::const_px(11))),
448    CssPropertyWithConditions::simple(CssProperty::const_text_color(StyleTextColor {
449        inner: COLOR_4C4C4C,
450    })),
451    CssPropertyWithConditions::simple(CssProperty::const_font_family(SANS_SERIF_FAMILY)),
452];
453
454#[cfg(target_os = "linux")]
455static TEXT_INPUT_LABEL_PROPS: &[CssPropertyWithConditions] = &[
456    CssPropertyWithConditions::simple(CssProperty::const_display(LayoutDisplay::InlineBlock)),
457    CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(0))),
458    CssPropertyWithConditions::simple(CssProperty::const_position(LayoutPosition::Relative)),
459    CssPropertyWithConditions::simple(CssProperty::const_font_size(StyleFontSize::const_px(11))),
460    CssPropertyWithConditions::simple(CssProperty::const_text_color(StyleTextColor {
461        inner: COLOR_4C4C4C,
462    })),
463    CssPropertyWithConditions::simple(CssProperty::const_font_family(SANS_SERIF_FAMILY)),
464];
465
466#[cfg(target_os = "macos")]
467static TEXT_INPUT_LABEL_PROPS: &[CssPropertyWithConditions] = &[
468    CssPropertyWithConditions::simple(CssProperty::const_display(LayoutDisplay::InlineBlock)),
469    CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(0))),
470    CssPropertyWithConditions::simple(CssProperty::const_position(LayoutPosition::Relative)),
471    CssPropertyWithConditions::simple(CssProperty::const_font_size(StyleFontSize::const_px(11))),
472    CssPropertyWithConditions::simple(CssProperty::const_text_color(StyleTextColor {
473        inner: COLOR_4C4C4C,
474    })),
475    CssPropertyWithConditions::simple(CssProperty::const_font_family(SANS_SERIF_FAMILY)),
476];
477
478// --- placeholder
479
480#[cfg(target_os = "windows")]
481static TEXT_INPUT_PLACEHOLDER_PROPS: &[CssPropertyWithConditions] = &[
482    CssPropertyWithConditions::simple(CssProperty::const_display(LayoutDisplay::Block)),
483    CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(0))),
484    CssPropertyWithConditions::simple(CssProperty::const_position(LayoutPosition::Absolute)),
485    CssPropertyWithConditions::simple(CssProperty::const_top(LayoutTop::const_px(2))),
486    CssPropertyWithConditions::simple(CssProperty::const_left(LayoutLeft::const_px(2))),
487    CssPropertyWithConditions::simple(CssProperty::const_font_size(StyleFontSize::const_px(11))),
488    CssPropertyWithConditions::simple(CssProperty::const_text_color(StyleTextColor {
489        inner: COLOR_4C4C4C,
490    })),
491    CssPropertyWithConditions::simple(CssProperty::const_font_family(SANS_SERIF_FAMILY)),
492    CssPropertyWithConditions::simple(CssProperty::const_opacity(StyleOpacity::const_new(100))),
493];
494
495#[cfg(target_os = "linux")]
496static TEXT_INPUT_PLACEHOLDER_PROPS: &[CssPropertyWithConditions] = &[
497    CssPropertyWithConditions::simple(CssProperty::const_display(LayoutDisplay::Block)),
498    CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(0))),
499    CssPropertyWithConditions::simple(CssProperty::const_position(LayoutPosition::Absolute)),
500    CssPropertyWithConditions::simple(CssProperty::const_top(LayoutTop::const_px(2))),
501    CssPropertyWithConditions::simple(CssProperty::const_left(LayoutLeft::const_px(2))),
502    CssPropertyWithConditions::simple(CssProperty::const_font_size(StyleFontSize::const_px(11))),
503    CssPropertyWithConditions::simple(CssProperty::const_text_color(StyleTextColor {
504        inner: COLOR_4C4C4C,
505    })),
506    CssPropertyWithConditions::simple(CssProperty::const_font_family(SANS_SERIF_FAMILY)),
507    CssPropertyWithConditions::simple(CssProperty::const_opacity(StyleOpacity::const_new(100))),
508];
509
510#[cfg(target_os = "macos")]
511static TEXT_INPUT_PLACEHOLDER_PROPS: &[CssPropertyWithConditions] = &[
512    CssPropertyWithConditions::simple(CssProperty::const_display(LayoutDisplay::Block)),
513    CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(0))),
514    CssPropertyWithConditions::simple(CssProperty::const_position(LayoutPosition::Absolute)),
515    CssPropertyWithConditions::simple(CssProperty::const_top(LayoutTop::const_px(2))),
516    CssPropertyWithConditions::simple(CssProperty::const_left(LayoutLeft::const_px(2))),
517    CssPropertyWithConditions::simple(CssProperty::const_font_size(StyleFontSize::const_px(11))),
518    CssPropertyWithConditions::simple(CssProperty::const_text_color(StyleTextColor {
519        inner: COLOR_4C4C4C,
520    })),
521    CssPropertyWithConditions::simple(CssProperty::const_font_family(SANS_SERIF_FAMILY)),
522    CssPropertyWithConditions::simple(CssProperty::const_opacity(StyleOpacity::const_new(100))),
523];
524
525#[derive(Debug, Clone, PartialEq)]
526#[repr(C)]
527pub struct TextInput {
528    pub text_input_state: TextInputStateWrapper,
529    pub placeholder_style: CssPropertyWithConditionsVec,
530    pub container_style: CssPropertyWithConditionsVec,
531    pub label_style: CssPropertyWithConditionsVec,
532}
533
534#[derive(Debug, Clone, PartialEq)]
535#[repr(C)]
536pub struct TextInputState {
537    pub text: U32Vec, // Vec<char>
538    pub placeholder: OptionString,
539    pub max_len: usize,
540    pub selection: OptionTextInputSelection,
541    pub cursor_pos: usize,
542}
543
544#[derive(Debug, Clone, PartialEq)]
545#[repr(C)]
546pub struct TextInputStateWrapper {
547    pub inner: TextInputState,
548    pub on_text_input: OptionTextInputOnTextInput,
549    pub on_virtual_key_down: OptionTextInputOnVirtualKeyDown,
550    pub on_focus_lost: OptionTextInputOnFocusLost,
551    pub update_text_input_before_calling_focus_lost_fn: bool,
552    pub update_text_input_before_calling_vk_down_fn: bool,
553    pub cursor_animation: OptionTimerId,
554}
555
556#[derive(Debug, Copy, Clone, PartialEq)]
557#[repr(C)]
558pub struct OnTextInputReturn {
559    pub update: Update,
560    pub valid: TextInputValid,
561}
562
563#[derive(Debug, Copy, Clone, PartialEq)]
564#[repr(C)]
565pub enum TextInputValid {
566    Yes,
567    No,
568}
569
570// The text input field has a special return which specifies
571// whether the text input should handle the character
572pub type TextInputOnTextInputCallbackType =
573    extern "C" fn(RefAny, CallbackInfo, TextInputState) -> OnTextInputReturn;
574impl_widget_callback!(
575    TextInputOnTextInput,
576    OptionTextInputOnTextInput,
577    TextInputOnTextInputCallback,
578    TextInputOnTextInputCallbackType
579);
580
581pub type TextInputOnVirtualKeyDownCallbackType =
582    extern "C" fn(RefAny, CallbackInfo, TextInputState) -> OnTextInputReturn;
583impl_widget_callback!(
584    TextInputOnVirtualKeyDown,
585    OptionTextInputOnVirtualKeyDown,
586    TextInputOnVirtualKeyDownCallback,
587    TextInputOnVirtualKeyDownCallbackType
588);
589
590pub type TextInputOnFocusLostCallbackType =
591    extern "C" fn(RefAny, CallbackInfo, TextInputState) -> Update;
592impl_widget_callback!(
593    TextInputOnFocusLost,
594    OptionTextInputOnFocusLost,
595    TextInputOnFocusLostCallback,
596    TextInputOnFocusLostCallbackType
597);
598
599#[derive(Debug, Clone, Hash, PartialEq, Eq)]
600#[repr(C, u8)]
601pub enum TextInputSelection {
602    All,
603    FromTo(TextInputSelectionRange),
604}
605
606azul_css::impl_option!(
607    TextInputSelection,
608    OptionTextInputSelection,
609    copy = false,
610    [Debug, Clone, Hash, PartialEq, Eq]
611);
612
613#[derive(Debug, Clone, Hash, PartialEq, Eq)]
614#[repr(C)]
615pub struct TextInputSelectionRange {
616    pub dir_from: usize,
617    pub dir_to: usize,
618}
619
620impl Default for TextInput {
621    fn default() -> Self {
622        TextInput {
623            text_input_state: TextInputStateWrapper::default(),
624            placeholder_style: CssPropertyWithConditionsVec::from_const_slice(
625                TEXT_INPUT_PLACEHOLDER_PROPS,
626            ),
627            container_style: CssPropertyWithConditionsVec::from_const_slice(
628                TEXT_INPUT_CONTAINER_PROPS,
629            ),
630            label_style: CssPropertyWithConditionsVec::from_const_slice(TEXT_INPUT_LABEL_PROPS),
631        }
632    }
633}
634
635impl Default for TextInputState {
636    fn default() -> Self {
637        TextInputState {
638            text: Vec::new().into(),
639            placeholder: None.into(),
640            max_len: 50,
641            selection: None.into(),
642            cursor_pos: 0,
643        }
644    }
645}
646
647impl TextInputState {
648    pub fn get_text(&self) -> String {
649        self.text
650            .iter()
651            .filter_map(|c| core::char::from_u32(*c))
652            .collect()
653    }
654}
655
656impl Default for TextInputStateWrapper {
657    fn default() -> Self {
658        TextInputStateWrapper {
659            inner: TextInputState::default(),
660            on_text_input: None.into(),
661            on_virtual_key_down: None.into(),
662            on_focus_lost: None.into(),
663            update_text_input_before_calling_focus_lost_fn: true,
664            update_text_input_before_calling_vk_down_fn: true,
665            cursor_animation: None.into(),
666        }
667    }
668}
669
670impl TextInput {
671    pub fn create() -> Self {
672        Self::default()
673    }
674
675    pub fn with_text(mut self, text: AzString) -> Self {
676        self.set_text(text);
677        self
678    }
679
680    pub fn set_text(&mut self, text: AzString) {
681        self.text_input_state.inner.text = text
682            .as_str()
683            .chars()
684            .map(|c| c as u32)
685            .collect::<Vec<_>>()
686            .into();
687    }
688
689    pub fn set_placeholder(&mut self, placeholder: AzString) {
690        self.text_input_state.inner.placeholder = Some(placeholder).into();
691    }
692
693    pub fn with_placeholder(mut self, placeholder: AzString) -> Self {
694        self.set_placeholder(placeholder);
695        self
696    }
697
698    pub fn set_on_text_input<C: Into<TextInputOnTextInputCallback>>(
699        &mut self,
700        refany: RefAny,
701        callback: C,
702    ) {
703        self.text_input_state.on_text_input = Some(TextInputOnTextInput {
704            callback: callback.into(),
705            refany,
706        })
707        .into();
708    }
709
710    pub fn with_on_text_input<C: Into<TextInputOnTextInputCallback>>(
711        mut self,
712        refany: RefAny,
713        callback: C,
714    ) -> Self {
715        self.set_on_text_input(refany, callback);
716        self
717    }
718
719    pub fn set_on_virtual_key_down<C: Into<TextInputOnVirtualKeyDownCallback>>(
720        &mut self,
721        refany: RefAny,
722        callback: C,
723    ) {
724        self.text_input_state.on_virtual_key_down = Some(TextInputOnVirtualKeyDown {
725            callback: callback.into(),
726            refany,
727        })
728        .into();
729    }
730
731    pub fn with_on_virtual_key_down<C: Into<TextInputOnVirtualKeyDownCallback>>(
732        mut self,
733        refany: RefAny,
734        callback: C,
735    ) -> Self {
736        self.set_on_virtual_key_down(refany, callback);
737        self
738    }
739
740    pub fn set_on_focus_lost<C: Into<TextInputOnFocusLostCallback>>(
741        &mut self,
742        refany: RefAny,
743        callback: C,
744    ) {
745        self.text_input_state.on_focus_lost = Some(TextInputOnFocusLost {
746            callback: callback.into(),
747            refany,
748        })
749        .into();
750    }
751
752    pub fn with_on_focus_lost<C: Into<TextInputOnFocusLostCallback>>(
753        mut self,
754        refany: RefAny,
755        callback: C,
756    ) -> Self {
757        self.set_on_focus_lost(refany, callback);
758        self
759    }
760
761    pub fn set_placeholder_style(&mut self, style: CssPropertyWithConditionsVec) {
762        self.placeholder_style = style;
763    }
764
765    pub fn with_placeholder_style(mut self, style: CssPropertyWithConditionsVec) -> Self {
766        self.set_placeholder_style(style);
767        self
768    }
769
770    pub fn set_container_style(&mut self, style: CssPropertyWithConditionsVec) {
771        self.container_style = style;
772    }
773
774    pub fn with_container_style(mut self, style: CssPropertyWithConditionsVec) -> Self {
775        self.set_container_style(style);
776        self
777    }
778
779    pub fn set_label_style(&mut self, style: CssPropertyWithConditionsVec) {
780        self.label_style = style;
781    }
782
783    pub fn with_label_style(mut self, style: CssPropertyWithConditionsVec) -> Self {
784        self.set_label_style(style);
785        self
786    }
787
788    pub fn swap_with_default(&mut self) -> Self {
789        let mut s = Self::default();
790        core::mem::swap(&mut s, self);
791        s
792    }
793
794    pub fn dom(mut self) -> Dom {
795        use azul_core::{
796            callbacks::CoreCallbackData,
797            dom::{EventFilter, FocusEventFilter, HoverEventFilter, IdOrClass::Class, TabIndex},
798        };
799
800        self.text_input_state.inner.cursor_pos = self.text_input_state.inner.text.len();
801
802        let label_text: String = self
803            .text_input_state
804            .inner
805            .text
806            .iter()
807            .filter_map(|s| core::char::from_u32(*s))
808            .collect();
809
810        let placeholder = self
811            .text_input_state
812            .inner
813            .placeholder
814            .as_ref()
815            .map(|s| s.as_str().to_string())
816            .unwrap_or_default();
817
818        let state_ref = RefAny::new(self.text_input_state);
819
820        Dom::create_div()
821            .with_ids_and_classes(vec![Class("__azul-native-text-input-container".into())].into())
822            .with_css_props(self.container_style)
823            .with_tab_index(TabIndex::Auto)
824            .with_dataset(Some(state_ref.clone()).into())
825            .with_callbacks(
826                vec![
827                    CoreCallbackData {
828                        event: EventFilter::Focus(FocusEventFilter::FocusReceived),
829                        refany: state_ref.clone(),
830                        callback: CoreCallback {
831                            cb: default_on_focus_received as usize,
832                            ctx: azul_core::refany::OptionRefAny::None,
833                        },
834                    },
835                    CoreCallbackData {
836                        event: EventFilter::Focus(FocusEventFilter::FocusLost),
837                        refany: state_ref.clone(),
838                        callback: CoreCallback {
839                            cb: default_on_focus_lost as usize,
840                            ctx: azul_core::refany::OptionRefAny::None,
841                        },
842                    },
843                    CoreCallbackData {
844                        event: EventFilter::Focus(FocusEventFilter::TextInput),
845                        refany: state_ref.clone(),
846                        callback: CoreCallback {
847                            cb: default_on_text_input as usize,
848                            ctx: azul_core::refany::OptionRefAny::None,
849                        },
850                    },
851                    CoreCallbackData {
852                        event: EventFilter::Focus(FocusEventFilter::VirtualKeyDown),
853                        refany: state_ref.clone(),
854                        callback: CoreCallback {
855                            cb: default_on_virtual_key_down as usize,
856                            ctx: azul_core::refany::OptionRefAny::None,
857                        },
858                    },
859                    CoreCallbackData {
860                        event: EventFilter::Hover(HoverEventFilter::MouseOver),
861                        refany: state_ref.clone(),
862                        callback: CoreCallback {
863                            cb: default_on_mouse_hover as usize,
864                            ctx: azul_core::refany::OptionRefAny::None,
865                        },
866                    },
867                ]
868                .into(),
869            )
870            .with_children(
871                vec![
872                    Dom::create_text(placeholder)
873                        .with_ids_and_classes(
874                            vec![Class("__azul-native-text-input-placeholder".into())].into(),
875                        )
876                        .with_css_props(self.placeholder_style),
877                    Dom::create_text(label_text)
878                        .with_ids_and_classes(
879                            vec![Class("__azul-native-text-input-label".into())].into(),
880                        )
881                        .with_css_props(self.label_style)
882                        .with_children(
883                            vec![Dom::create_div()
884                                .with_ids_and_classes(
885                                    vec![Class("__azul-native-text-input-cursor".into())].into(),
886                                )
887                                .with_css_props(CssPropertyWithConditionsVec::from_const_slice(
888                                    TEXT_CURSOR_PROPS,
889                                ))]
890                            .into(),
891                        ),
892                ]
893                .into(),
894            )
895    }
896}
897
898extern "C" fn default_on_focus_received(mut text_input: RefAny, mut info: CallbackInfo) -> Update {
899    let mut text_input = match text_input.downcast_mut::<TextInputStateWrapper>() {
900        Some(s) => s,
901        None => return Update::DoNothing,
902    };
903
904    let text_input = &mut *text_input;
905
906    let placeholder_text_node_id = match info.get_first_child(info.get_hit_node()) {
907        Some(s) => s,
908        None => return Update::DoNothing,
909    };
910
911    // hide the placeholder text
912    if text_input.inner.text.is_empty() {
913        info.set_css_property(
914            placeholder_text_node_id,
915            CssProperty::const_opacity(StyleOpacity::const_new(0)),
916        );
917    }
918
919    text_input.inner.cursor_pos = text_input.inner.text.len();
920
921    Update::DoNothing
922}
923
924extern "C" fn default_on_focus_lost(mut text_input: RefAny, mut info: CallbackInfo) -> Update {
925    let mut text_input = match text_input.downcast_mut::<TextInputStateWrapper>() {
926        Some(s) => s,
927        None => return Update::DoNothing,
928    };
929
930    let text_input = &mut *text_input;
931
932    let placeholder_text_node_id = match info.get_first_child(info.get_hit_node()) {
933        Some(s) => s,
934        None => return Update::DoNothing,
935    };
936
937    // show the placeholder text
938    if text_input.inner.text.is_empty() {
939        info.set_css_property(
940            placeholder_text_node_id,
941            CssProperty::const_opacity(StyleOpacity::const_new(100)),
942        );
943    }
944
945    let result = {
946        // rustc doesn't understand the borrowing lifetime here
947        let text_input = &mut *text_input;
948        let onfocuslost = &mut text_input.on_focus_lost;
949        let inner = text_input.inner.clone();
950
951        match onfocuslost.as_mut() {
952            Some(TextInputOnFocusLost { callback, refany }) => {
953                (callback.cb)(refany.clone(), info.clone(), inner)
954            }
955            None => Update::DoNothing,
956        }
957    };
958
959    result
960}
961
962extern "C" fn default_on_text_input(text_input: RefAny, info: CallbackInfo) -> Update {
963    default_on_text_input_inner(text_input, info).unwrap_or(Update::DoNothing)
964}
965
966fn default_on_text_input_inner(mut text_input: RefAny, mut info: CallbackInfo) -> Option<Update> {
967    let mut text_input = text_input.downcast_mut::<TextInputStateWrapper>()?;
968
969    // Get the text changeset (replaces old keyboard_state.current_char API)
970    let changeset = info.get_text_changeset()?;
971    let inserted_text = changeset.inserted_text.as_str().to_string();
972
973    // Early return if no text to insert
974    if inserted_text.is_empty() {
975        return None;
976    }
977
978    let placeholder_node_id = info.get_first_child(info.get_hit_node())?;
979    let label_node_id = info.get_next_sibling(placeholder_node_id)?;
980    let _cursor_node_id = info.get_first_child(label_node_id)?;
981
982    let result = {
983        // rustc doesn't understand the borrowing lifetime here
984        let text_input = &mut *text_input;
985        let ontextinput = &mut text_input.on_text_input;
986
987        // inner_clone has the new text
988        let mut inner_clone = text_input.inner.clone();
989        inner_clone.cursor_pos = inner_clone.cursor_pos.saturating_add(inserted_text.len());
990        inner_clone.text = {
991            let mut internal = inner_clone.text.clone().into_library_owned_vec();
992            internal.extend(inserted_text.chars().map(|c| c as u32));
993            internal.into()
994        };
995
996        match ontextinput.as_mut() {
997            Some(TextInputOnTextInput { callback, refany }) => {
998                (callback.cb)(refany.clone(), info.clone(), inner_clone)
999            }
1000            None => OnTextInputReturn {
1001                update: Update::DoNothing,
1002                valid: TextInputValid::Yes,
1003            },
1004        }
1005    };
1006
1007    if result.valid == TextInputValid::Yes {
1008        // hide the placeholder text
1009        info.set_css_property(
1010            placeholder_node_id,
1011            CssProperty::const_opacity(StyleOpacity::const_new(0)),
1012        );
1013
1014        // append to the text
1015        text_input.inner.text = {
1016            let mut internal = text_input.inner.text.clone().into_library_owned_vec();
1017            internal.extend(inserted_text.chars().map(|c| c as u32));
1018            internal.into()
1019        };
1020        text_input.inner.cursor_pos = text_input
1021            .inner
1022            .cursor_pos
1023            .saturating_add(inserted_text.len());
1024
1025        info.change_node_text(label_node_id, text_input.inner.get_text().into());
1026    }
1027
1028    Some(result.update)
1029}
1030
1031extern "C" fn default_on_virtual_key_down(text_input: RefAny, info: CallbackInfo) -> Update {
1032    default_on_virtual_key_down_inner(text_input, info).unwrap_or(Update::DoNothing)
1033}
1034
1035fn default_on_virtual_key_down_inner(
1036    mut text_input: RefAny,
1037    mut info: CallbackInfo,
1038) -> Option<Update> {
1039    let mut text_input = text_input.downcast_mut::<TextInputStateWrapper>()?;
1040    let keyboard_state = info.get_current_keyboard_state();
1041
1042    let c = keyboard_state.current_virtual_keycode.into_option()?;
1043    let placeholder_node_id = info.get_first_child(info.get_hit_node())?;
1044    let label_node_id = info.get_next_sibling(placeholder_node_id)?;
1045    let _cursor_node_id = info.get_first_child(label_node_id)?;
1046
1047    if c != VirtualKeyCode::Back {
1048        return None;
1049    }
1050
1051    text_input.inner.text = {
1052        let mut internal = text_input.inner.text.clone().into_library_owned_vec();
1053        internal.pop();
1054        internal.into()
1055    };
1056    text_input.inner.cursor_pos = text_input.inner.cursor_pos.saturating_sub(1);
1057
1058    info.change_node_text(label_node_id, text_input.inner.get_text().into());
1059
1060    None
1061}
1062
1063extern "C" fn default_on_mouse_hover(mut text_input: RefAny, _info: CallbackInfo) -> Update {
1064    let _text_input = match text_input.downcast_mut::<TextInputStateWrapper>() {
1065        Some(s) => s,
1066        None => return Update::DoNothing,
1067    };
1068
1069    // println!("default_on_mouse_hover");
1070
1071    Update::DoNothing
1072}