1use alloc::{string::String, vec::Vec};
28
29use azul_core::{
30 callbacks::{CoreCallback, CoreCallbackData, Update},
31 dom::Dom,
32 refany::RefAny,
33 window::VirtualKeyCode,
34};
35use azul_css::{
36 dynamic_selector::{CssPropertyWithConditions, CssPropertyWithConditionsVec},
37 props::{basic::{ColorU, StyleFontFamily, StyleFontFamilyVec, StyleFontSize}, layout::{LayoutPosition, LayoutWidth, LayoutHeight, LayoutBoxSizing, LayoutFlexGrow, LayoutMinHeight, LayoutPaddingLeft, LayoutPaddingRight, LayoutPaddingTop, LayoutPaddingBottom, LayoutOverflow, LayoutDisplay, LayoutTop, LayoutLeft}, property::{CssProperty, StyleWhiteSpaceValue}, style::{StyleBackgroundContent, StyleBackgroundContentVec, StyleOpacity, StyleCursor, StyleTextColor, LayoutBorderTopWidth, LayoutBorderBottomWidth, LayoutBorderLeftWidth, LayoutBorderRightWidth, StyleBorderTopStyle, BorderStyle, StyleBorderBottomStyle, StyleBorderLeftStyle, StyleBorderRightStyle, StyleBorderTopColor, StyleBorderBottomColor, StyleBorderLeftColor, StyleBorderRightColor, StyleTextAlign, StyleWhiteSpace}},
38 impl_option_inner, AzString, U32Vec, OptionString,
39};
40
41use crate::callbacks::{Callback, CallbackInfo};
42use crate::widgets::text_input::{OnTextInputReturn, TextInputValid};
43
44const BACKGROUND_COLOR: ColorU = ColorU {
46 r: 255,
47 g: 255,
48 b: 255,
49 a: 255,
50}; const BLACK: ColorU = ColorU { r: 0, g: 0, b: 0, a: 255 };
52const COLOR_9B9B9B: ColorU = ColorU {
53 r: 155,
54 g: 155,
55 b: 155,
56 a: 255,
57}; const COLOR_4286F4: ColorU = ColorU {
59 r: 66,
60 g: 134,
61 b: 244,
62 a: 255,
63}; const COLOR_4C4C4C: ColorU = ColorU {
65 r: 76,
66 g: 76,
67 b: 76,
68 a: 255,
69}; const CURSOR_COLOR_BLACK: &[StyleBackgroundContent] = &[StyleBackgroundContent::Color(BLACK)];
72const CURSOR_COLOR: StyleBackgroundContentVec =
73 StyleBackgroundContentVec::from_const_slice(CURSOR_COLOR_BLACK);
74
75const BACKGROUND_THEME_LIGHT: &[StyleBackgroundContent] =
76 &[StyleBackgroundContent::Color(BACKGROUND_COLOR)];
77const BACKGROUND_COLOR_LIGHT: StyleBackgroundContentVec =
78 StyleBackgroundContentVec::from_const_slice(BACKGROUND_THEME_LIGHT);
79
80const SANS_SERIF_STR: &str = "system:ui";
81const SANS_SERIF: AzString = AzString::from_const_str(SANS_SERIF_STR);
82const SANS_SERIF_FAMILIES: &[StyleFontFamily] = &[StyleFontFamily::System(SANS_SERIF)];
83const SANS_SERIF_FAMILY: StyleFontFamilyVec =
84 StyleFontFamilyVec::from_const_slice(SANS_SERIF_FAMILIES);
85
86const MIN_HEIGHT_PX: isize = 64;
88
89static TEXT_CURSOR_PROPS: &[CssPropertyWithConditions] = &[
91 CssPropertyWithConditions::simple(CssProperty::const_position(LayoutPosition::Absolute)),
92 CssPropertyWithConditions::simple(CssProperty::const_width(LayoutWidth::const_px(1))),
93 CssPropertyWithConditions::simple(CssProperty::const_height(LayoutHeight::const_px(11))),
94 CssPropertyWithConditions::simple(CssProperty::const_background_content(CURSOR_COLOR)),
95 CssPropertyWithConditions::simple(CssProperty::const_opacity(StyleOpacity::const_new(0))),
96];
97
98static TEXT_AREA_CONTAINER_PROPS: &[CssPropertyWithConditions] = &[
100 CssPropertyWithConditions::simple(CssProperty::const_position(LayoutPosition::Relative)),
101 CssPropertyWithConditions::simple(CssProperty::const_cursor(StyleCursor::Text)),
102 CssPropertyWithConditions::simple(CssProperty::const_box_sizing(LayoutBoxSizing::BorderBox)),
103 CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(1))),
104 CssPropertyWithConditions::simple(CssProperty::const_min_height(LayoutMinHeight::const_px(
105 MIN_HEIGHT_PX,
106 ))),
107 CssPropertyWithConditions::simple(CssProperty::const_font_size(StyleFontSize::const_px(13))),
108 CssPropertyWithConditions::simple(CssProperty::const_background_content(BACKGROUND_COLOR_LIGHT)),
109 CssPropertyWithConditions::simple(CssProperty::const_text_color(StyleTextColor {
110 inner: COLOR_4C4C4C,
111 })),
112 CssPropertyWithConditions::simple(CssProperty::const_padding_left(LayoutPaddingLeft::const_px(
113 4,
114 ))),
115 CssPropertyWithConditions::simple(CssProperty::const_padding_right(
116 LayoutPaddingRight::const_px(4),
117 )),
118 CssPropertyWithConditions::simple(CssProperty::const_padding_top(LayoutPaddingTop::const_px(4))),
119 CssPropertyWithConditions::simple(CssProperty::const_padding_bottom(
120 LayoutPaddingBottom::const_px(4),
121 )),
122 CssPropertyWithConditions::simple(CssProperty::const_border_top_width(
124 LayoutBorderTopWidth::const_px(1),
125 )),
126 CssPropertyWithConditions::simple(CssProperty::const_border_bottom_width(
127 LayoutBorderBottomWidth::const_px(1),
128 )),
129 CssPropertyWithConditions::simple(CssProperty::const_border_left_width(
130 LayoutBorderLeftWidth::const_px(1),
131 )),
132 CssPropertyWithConditions::simple(CssProperty::const_border_right_width(
133 LayoutBorderRightWidth::const_px(1),
134 )),
135 CssPropertyWithConditions::simple(CssProperty::const_border_top_style(StyleBorderTopStyle {
136 inner: BorderStyle::Inset,
137 })),
138 CssPropertyWithConditions::simple(CssProperty::const_border_bottom_style(
139 StyleBorderBottomStyle {
140 inner: BorderStyle::Inset,
141 },
142 )),
143 CssPropertyWithConditions::simple(CssProperty::const_border_left_style(StyleBorderLeftStyle {
144 inner: BorderStyle::Inset,
145 })),
146 CssPropertyWithConditions::simple(CssProperty::const_border_right_style(
147 StyleBorderRightStyle {
148 inner: BorderStyle::Inset,
149 },
150 )),
151 CssPropertyWithConditions::simple(CssProperty::const_border_top_color(StyleBorderTopColor {
152 inner: COLOR_9B9B9B,
153 })),
154 CssPropertyWithConditions::simple(CssProperty::const_border_bottom_color(
155 StyleBorderBottomColor {
156 inner: COLOR_9B9B9B,
157 },
158 )),
159 CssPropertyWithConditions::simple(CssProperty::const_border_left_color(StyleBorderLeftColor {
160 inner: COLOR_9B9B9B,
161 })),
162 CssPropertyWithConditions::simple(CssProperty::const_border_right_color(
163 StyleBorderRightColor {
164 inner: COLOR_9B9B9B,
165 },
166 )),
167 CssPropertyWithConditions::simple(CssProperty::const_overflow_x(LayoutOverflow::Hidden)),
168 CssPropertyWithConditions::simple(CssProperty::const_overflow_y(LayoutOverflow::Scroll)),
169 CssPropertyWithConditions::simple(CssProperty::const_text_align(StyleTextAlign::Left)),
170 CssPropertyWithConditions::simple(CssProperty::const_font_family(SANS_SERIF_FAMILY)),
171 CssPropertyWithConditions::simple(CssProperty::WhiteSpace(StyleWhiteSpaceValue::Exact(
173 StyleWhiteSpace::PreWrap,
174 ))),
175 CssPropertyWithConditions::on_hover(CssProperty::const_border_top_color(StyleBorderTopColor {
177 inner: COLOR_4286F4,
178 })),
179 CssPropertyWithConditions::on_hover(CssProperty::const_border_bottom_color(
180 StyleBorderBottomColor {
181 inner: COLOR_4286F4,
182 },
183 )),
184 CssPropertyWithConditions::on_hover(CssProperty::const_border_left_color(StyleBorderLeftColor {
185 inner: COLOR_4286F4,
186 })),
187 CssPropertyWithConditions::on_hover(CssProperty::const_border_right_color(
188 StyleBorderRightColor {
189 inner: COLOR_4286F4,
190 },
191 )),
192 CssPropertyWithConditions::on_focus(CssProperty::const_border_top_color(StyleBorderTopColor {
193 inner: COLOR_4286F4,
194 })),
195 CssPropertyWithConditions::on_focus(CssProperty::const_border_bottom_color(
196 StyleBorderBottomColor {
197 inner: COLOR_4286F4,
198 },
199 )),
200 CssPropertyWithConditions::on_focus(CssProperty::const_border_left_color(StyleBorderLeftColor {
201 inner: COLOR_4286F4,
202 })),
203 CssPropertyWithConditions::on_focus(CssProperty::const_border_right_color(
204 StyleBorderRightColor {
205 inner: COLOR_4286F4,
206 },
207 )),
208];
209
210static TEXT_AREA_LABEL_PROPS: &[CssPropertyWithConditions] = &[
212 CssPropertyWithConditions::simple(CssProperty::const_display(LayoutDisplay::Block)),
213 CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(0))),
214 CssPropertyWithConditions::simple(CssProperty::const_position(LayoutPosition::Relative)),
215 CssPropertyWithConditions::simple(CssProperty::const_font_size(StyleFontSize::const_px(13))),
216 CssPropertyWithConditions::simple(CssProperty::const_text_color(StyleTextColor {
217 inner: COLOR_4C4C4C,
218 })),
219 CssPropertyWithConditions::simple(CssProperty::const_font_family(SANS_SERIF_FAMILY)),
220 CssPropertyWithConditions::simple(CssProperty::WhiteSpace(StyleWhiteSpaceValue::Exact(
221 StyleWhiteSpace::PreWrap,
222 ))),
223];
224
225static TEXT_AREA_PLACEHOLDER_PROPS: &[CssPropertyWithConditions] = &[
227 CssPropertyWithConditions::simple(CssProperty::const_display(LayoutDisplay::Block)),
228 CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(0))),
229 CssPropertyWithConditions::simple(CssProperty::const_position(LayoutPosition::Absolute)),
230 CssPropertyWithConditions::simple(CssProperty::const_top(LayoutTop::const_px(4))),
231 CssPropertyWithConditions::simple(CssProperty::const_left(LayoutLeft::const_px(4))),
232 CssPropertyWithConditions::simple(CssProperty::const_font_size(StyleFontSize::const_px(13))),
233 CssPropertyWithConditions::simple(CssProperty::const_text_color(StyleTextColor {
234 inner: COLOR_9B9B9B,
235 })),
236 CssPropertyWithConditions::simple(CssProperty::const_font_family(SANS_SERIF_FAMILY)),
237 CssPropertyWithConditions::simple(CssProperty::const_opacity(StyleOpacity::const_new(100))),
238];
239
240#[derive(Debug, Clone, PartialEq, Eq)]
242#[repr(C)]
243pub struct TextArea {
244 pub text_area_state: TextAreaStateWrapper,
245 pub placeholder_style: CssPropertyWithConditionsVec,
246 pub container_style: CssPropertyWithConditionsVec,
247 pub label_style: CssPropertyWithConditionsVec,
248}
249
250#[derive(Debug, Clone, PartialEq, Eq)]
252#[repr(C)]
253pub struct TextAreaState {
254 pub text: U32Vec,
256 pub placeholder: OptionString,
257 pub max_len: usize,
258 pub cursor_pos: usize,
259}
260
261#[derive(Debug, Clone, PartialEq, Eq)]
263#[repr(C)]
264pub struct TextAreaStateWrapper {
265 pub inner: TextAreaState,
266 pub on_text_input: OptionTextAreaOnTextInput,
267 pub on_focus_lost: OptionTextAreaOnFocusLost,
268 pub update_text_area_before_calling_focus_lost_fn: bool,
269 pub on_virtual_key_down: OptionTextAreaOnVirtualKeyDown,
271}
272
273pub type TextAreaOnTextInputCallbackType =
278 extern "C" fn(RefAny, CallbackInfo, TextAreaState) -> OnTextInputReturn;
279impl_widget_callback!(
280 TextAreaOnTextInput,
281 OptionTextAreaOnTextInput,
282 TextAreaOnTextInputCallback,
283 TextAreaOnTextInputCallbackType
284);
285
286azul_core::impl_managed_callback! {
287 wrapper: TextAreaOnTextInputCallback,
288 info_ty: CallbackInfo,
289 return_ty: OnTextInputReturn,
290 default_ret: OnTextInputReturn { update: Update::DoNothing, valid: TextInputValid::Yes },
291 invoker_static: TEXT_AREA_ON_TEXT_INPUT_INVOKER,
292 invoker_ty: AzTextAreaOnTextInputCallbackInvoker,
293 thunk_fn: az_text_area_on_text_input_callback_thunk,
294 setter_fn: AzApp_setTextAreaOnTextInputCallbackInvoker,
295 from_handle_fn: AzTextAreaOnTextInputCallback_createFromHostHandle,
296 extra_args: [ state: TextAreaState ],
297}
298
299pub type TextAreaOnVirtualKeyDownCallbackType =
302 extern "C" fn(RefAny, CallbackInfo, TextAreaState) -> OnTextInputReturn;
303impl_widget_callback!(
304 TextAreaOnVirtualKeyDown,
305 OptionTextAreaOnVirtualKeyDown,
306 TextAreaOnVirtualKeyDownCallback,
307 TextAreaOnVirtualKeyDownCallbackType
308);
309
310azul_core::impl_managed_callback! {
311 wrapper: TextAreaOnVirtualKeyDownCallback,
312 info_ty: CallbackInfo,
313 return_ty: OnTextInputReturn,
314 default_ret: OnTextInputReturn { update: Update::DoNothing, valid: TextInputValid::Yes },
315 invoker_static: TEXT_AREA_ON_VIRTUAL_KEY_DOWN_INVOKER,
316 invoker_ty: AzTextAreaOnVirtualKeyDownCallbackInvoker,
317 thunk_fn: az_text_area_on_virtual_key_down_callback_thunk,
318 setter_fn: AzApp_setTextAreaOnVirtualKeyDownCallbackInvoker,
319 from_handle_fn: AzTextAreaOnVirtualKeyDownCallback_createFromHostHandle,
320 extra_args: [ state: TextAreaState ],
321}
322
323pub type TextAreaOnFocusLostCallbackType =
325 extern "C" fn(RefAny, CallbackInfo, TextAreaState) -> Update;
326impl_widget_callback!(
327 TextAreaOnFocusLost,
328 OptionTextAreaOnFocusLost,
329 TextAreaOnFocusLostCallback,
330 TextAreaOnFocusLostCallbackType
331);
332
333azul_core::impl_managed_callback! {
334 wrapper: TextAreaOnFocusLostCallback,
335 info_ty: CallbackInfo,
336 return_ty: Update,
337 default_ret: Update::DoNothing,
338 invoker_static: TEXT_AREA_ON_FOCUS_LOST_INVOKER,
339 invoker_ty: AzTextAreaOnFocusLostCallbackInvoker,
340 thunk_fn: az_text_area_on_focus_lost_callback_thunk,
341 setter_fn: AzApp_setTextAreaOnFocusLostCallbackInvoker,
342 from_handle_fn: AzTextAreaOnFocusLostCallback_createFromHostHandle,
343 extra_args: [ state: TextAreaState ],
344}
345
346impl Default for TextAreaState {
347 fn default() -> Self {
348 Self {
349 text: Vec::new().into(),
350 placeholder: None.into(),
351 max_len: 1000,
352 cursor_pos: 0,
353 }
354 }
355}
356
357impl TextAreaState {
358 #[must_use] pub fn get_text(&self) -> String {
360 self.text
361 .iter()
362 .filter_map(|c| core::char::from_u32(*c))
363 .collect()
364 }
365}
366
367impl Default for TextAreaStateWrapper {
368 fn default() -> Self {
369 Self {
370 inner: TextAreaState::default(),
371 on_text_input: None.into(),
372 on_focus_lost: None.into(),
373 update_text_area_before_calling_focus_lost_fn: true,
374 on_virtual_key_down: None.into(),
375 }
376 }
377}
378
379impl Default for TextArea {
380 fn default() -> Self {
381 Self {
382 text_area_state: TextAreaStateWrapper::default(),
383 placeholder_style: CssPropertyWithConditionsVec::from_const_slice(
384 TEXT_AREA_PLACEHOLDER_PROPS,
385 ),
386 container_style: CssPropertyWithConditionsVec::from_const_slice(
387 TEXT_AREA_CONTAINER_PROPS,
388 ),
389 label_style: CssPropertyWithConditionsVec::from_const_slice(TEXT_AREA_LABEL_PROPS),
390 }
391 }
392}
393
394impl TextArea {
395 #[must_use] pub fn create() -> Self {
396 Self::default()
397 }
398
399 #[allow(clippy::needless_pass_by_value)] pub fn set_text(&mut self, text: AzString) {
402 self.text_area_state.inner.text = text
403 .as_str()
404 .chars()
405 .map(|c| c as u32)
406 .collect::<Vec<_>>()
407 .into();
408 }
409
410 #[must_use] pub fn with_text(mut self, text: AzString) -> Self {
411 self.set_text(text);
412 self
413 }
414
415 pub fn set_placeholder(&mut self, placeholder: AzString) {
416 self.text_area_state.inner.placeholder = Some(placeholder).into();
417 }
418
419 #[must_use] pub fn with_placeholder(mut self, placeholder: AzString) -> Self {
420 self.set_placeholder(placeholder);
421 self
422 }
423
424 pub fn set_on_text_input<C: Into<TextAreaOnTextInputCallback>>(
425 &mut self,
426 refany: RefAny,
427 callback: C,
428 ) {
429 self.text_area_state.on_text_input = Some(TextAreaOnTextInput {
430 callback: callback.into(),
431 refany,
432 })
433 .into();
434 }
435
436 #[must_use] pub fn with_on_text_input<C: Into<TextAreaOnTextInputCallback>>(
437 mut self,
438 refany: RefAny,
439 callback: C,
440 ) -> Self {
441 self.set_on_text_input(refany, callback);
442 self
443 }
444
445 pub fn set_on_virtual_key_down<C: Into<TextAreaOnVirtualKeyDownCallback>>(
446 &mut self,
447 refany: RefAny,
448 callback: C,
449 ) {
450 self.text_area_state.on_virtual_key_down = Some(TextAreaOnVirtualKeyDown {
451 callback: callback.into(),
452 refany,
453 })
454 .into();
455 }
456
457 #[must_use] pub fn with_on_virtual_key_down<C: Into<TextAreaOnVirtualKeyDownCallback>>(
458 mut self,
459 refany: RefAny,
460 callback: C,
461 ) -> Self {
462 self.set_on_virtual_key_down(refany, callback);
463 self
464 }
465
466 pub fn set_on_focus_lost<C: Into<TextAreaOnFocusLostCallback>>(
467 &mut self,
468 refany: RefAny,
469 callback: C,
470 ) {
471 self.text_area_state.on_focus_lost = Some(TextAreaOnFocusLost {
472 callback: callback.into(),
473 refany,
474 })
475 .into();
476 }
477
478 #[must_use] pub fn with_on_focus_lost<C: Into<TextAreaOnFocusLostCallback>>(
479 mut self,
480 refany: RefAny,
481 callback: C,
482 ) -> Self {
483 self.set_on_focus_lost(refany, callback);
484 self
485 }
486
487 pub fn set_container_style(&mut self, style: CssPropertyWithConditionsVec) {
488 self.container_style = style;
489 }
490
491 #[must_use] pub fn with_container_style(mut self, style: CssPropertyWithConditionsVec) -> Self {
492 self.set_container_style(style);
493 self
494 }
495
496 #[must_use] pub fn swap_with_default(&mut self) -> Self {
497 let mut s = Self::default();
498 core::mem::swap(&mut s, self);
499 s
500 }
501
502 #[must_use] pub fn dom(mut self) -> Dom {
503 use azul_core::dom::{EventFilter, FocusEventFilter, HoverEventFilter, IdOrClass::Class, TabIndex};
504
505 self.text_area_state.inner.cursor_pos = self.text_area_state.inner.text.len();
506
507 let label_text: String = self
508 .text_area_state
509 .inner
510 .text
511 .iter()
512 .filter_map(|s| core::char::from_u32(*s))
513 .collect();
514
515 let placeholder = self
516 .text_area_state
517 .inner
518 .placeholder
519 .as_ref()
520 .map(|s| s.as_str().to_string())
521 .unwrap_or_default();
522
523 let state_ref = RefAny::new(self.text_area_state);
524
525 Dom::create_div()
526 .with_ids_and_classes(vec![Class("__azul-native-text-area-container".into())].into())
527 .with_css_props(self.container_style)
528 .with_tab_index(TabIndex::Auto)
529 .with_dataset(Some(state_ref.clone()).into())
530 .with_callbacks(
531 vec![
532 CoreCallbackData {
533 event: EventFilter::Focus(FocusEventFilter::FocusReceived),
534 refany: state_ref.clone(),
535 callback: CoreCallback {
536 cb: default_on_focus_received as usize,
537 ctx: azul_core::refany::OptionRefAny::None,
538 },
539 },
540 CoreCallbackData {
541 event: EventFilter::Focus(FocusEventFilter::FocusLost),
542 refany: state_ref.clone(),
543 callback: CoreCallback {
544 cb: default_on_focus_lost as usize,
545 ctx: azul_core::refany::OptionRefAny::None,
546 },
547 },
548 CoreCallbackData {
549 event: EventFilter::Focus(FocusEventFilter::TextInput),
550 refany: state_ref.clone(),
551 callback: CoreCallback {
552 cb: default_on_text_input as usize,
553 ctx: azul_core::refany::OptionRefAny::None,
554 },
555 },
556 CoreCallbackData {
557 event: EventFilter::Focus(FocusEventFilter::VirtualKeyDown),
558 refany: state_ref,
559 callback: CoreCallback {
560 cb: default_on_virtual_key_down as usize,
561 ctx: azul_core::refany::OptionRefAny::None,
562 },
563 },
564 ]
565 .into(),
566 )
567 .with_children(
568 vec![
569 Dom::create_text(placeholder)
570 .with_ids_and_classes(
571 vec![Class("__azul-native-text-area-placeholder".into())].into(),
572 )
573 .with_css_props(self.placeholder_style),
574 Dom::create_text(label_text)
575 .with_ids_and_classes(
576 vec![Class("__azul-native-text-area-label".into())].into(),
577 )
578 .with_css_props(self.label_style)
579 .with_children(
580 vec![Dom::create_div()
581 .with_ids_and_classes(
582 vec![Class("__azul-native-text-area-cursor".into())].into(),
583 )
584 .with_css_props(CssPropertyWithConditionsVec::from_const_slice(
585 TEXT_CURSOR_PROPS,
586 ))]
587 .into(),
588 ),
589 ]
590 .into(),
591 )
592 }
593}
594
595extern "C" fn default_on_focus_received(mut text_area: RefAny, mut info: CallbackInfo) -> Update {
596 let Some(mut text_area) = text_area.downcast_mut::<TextAreaStateWrapper>() else {
597 return Update::DoNothing;
598 };
599
600 let text_area = &mut *text_area;
601
602 let Some(placeholder_text_node_id) = info.get_first_child(info.get_hit_node()) else {
603 return Update::DoNothing;
604 };
605
606 if text_area.inner.text.is_empty() {
608 info.set_css_property(
609 placeholder_text_node_id,
610 CssProperty::const_opacity(StyleOpacity::const_new(0)),
611 );
612 }
613
614 text_area.inner.cursor_pos = text_area.inner.text.len();
615
616 Update::DoNothing
617}
618
619extern "C" fn default_on_focus_lost(mut text_area: RefAny, mut info: CallbackInfo) -> Update {
620 let Some(mut text_area) = text_area.downcast_mut::<TextAreaStateWrapper>() else {
621 return Update::DoNothing;
622 };
623
624 let text_area = &mut *text_area;
625
626 let Some(placeholder_text_node_id) = info.get_first_child(info.get_hit_node()) else {
627 return Update::DoNothing;
628 };
629
630 if text_area.inner.text.is_empty() {
632 info.set_css_property(
633 placeholder_text_node_id,
634 CssProperty::const_opacity(StyleOpacity::const_new(100)),
635 );
636 }
637
638 let text_area = &mut *text_area;
639 let onfocuslost = &mut text_area.on_focus_lost;
640 let inner = text_area.inner.clone();
641
642 match onfocuslost.as_mut() {
643 Some(TextAreaOnFocusLost { callback, refany }) => (callback.cb)(refany.clone(), info, inner),
644 None => Update::DoNothing,
645 }
646}
647
648extern "C" fn default_on_text_input(text_area: RefAny, info: CallbackInfo) -> Update {
649 default_on_text_input_inner(text_area, info).unwrap_or(Update::DoNothing)
650}
651
652fn default_on_text_input_inner(mut text_area: RefAny, mut info: CallbackInfo) -> Option<Update> {
653 let mut text_area = text_area.downcast_mut::<TextAreaStateWrapper>()?;
654
655 let changeset = info.get_text_changeset()?;
656 let inserted_text = changeset.inserted_text.as_str().to_string();
657
658 if inserted_text.is_empty() {
659 return None;
660 }
661
662 let placeholder_node_id = info.get_first_child(info.get_hit_node())?;
663 let label_node_id = info.get_next_sibling(placeholder_node_id)?;
664 let _cursor_node_id = info.get_first_child(label_node_id)?;
665
666 let result = {
667 let text_area = &mut *text_area;
668 let ontextinput = &mut text_area.on_text_input;
669
670 let mut inner_clone = text_area.inner.clone();
672 inner_clone.cursor_pos = inner_clone.cursor_pos.saturating_add(inserted_text.len());
673 inner_clone.text = {
674 let mut internal = inner_clone.text.clone().into_library_owned_vec();
675 internal.extend(inserted_text.chars().map(|c| c as u32));
676 internal.into()
677 };
678
679 match ontextinput.as_mut() {
680 Some(TextAreaOnTextInput { callback, refany }) => {
681 (callback.cb)(refany.clone(), info, inner_clone)
682 }
683 None => OnTextInputReturn {
684 update: Update::DoNothing,
685 valid: TextInputValid::Yes,
686 },
687 }
688 };
689
690 if result.valid == TextInputValid::Yes {
691 info.set_css_property(
693 placeholder_node_id,
694 CssProperty::const_opacity(StyleOpacity::const_new(0)),
695 );
696
697 text_area.inner.text = {
699 let mut internal = text_area.inner.text.clone().into_library_owned_vec();
700 internal.extend(inserted_text.chars().map(|c| c as u32));
701 internal.into()
702 };
703 text_area.inner.cursor_pos = text_area
704 .inner
705 .cursor_pos
706 .saturating_add(inserted_text.len());
707
708 info.change_node_text(label_node_id, text_area.inner.get_text().into());
709 }
710
711 Some(result.update)
712}
713
714extern "C" fn default_on_virtual_key_down(text_area: RefAny, info: CallbackInfo) -> Update {
715 default_on_virtual_key_down_inner(text_area, info).unwrap_or(Update::DoNothing)
716}
717
718fn default_on_virtual_key_down_inner(
719 mut text_area: RefAny,
720 mut info: CallbackInfo,
721) -> Option<Update> {
722 let mut text_area = text_area.downcast_mut::<TextAreaStateWrapper>()?;
723 let keyboard_state = info.get_current_keyboard_state();
724
725 let c = keyboard_state.current_virtual_keycode.into_option()?;
726 let placeholder_node_id = info.get_first_child(info.get_hit_node())?;
727 let label_node_id = info.get_next_sibling(placeholder_node_id)?;
728 let _cursor_node_id = info.get_first_child(label_node_id)?;
729
730 let result = {
733 let text_area = &mut *text_area;
735 let inner_clone = text_area.inner.clone();
736 match text_area.on_virtual_key_down.as_mut() {
737 Some(TextAreaOnVirtualKeyDown { callback, refany }) => {
738 (callback.cb)(refany.clone(), info, inner_clone)
739 }
740 None => OnTextInputReturn {
741 update: Update::DoNothing,
742 valid: TextInputValid::Yes,
743 },
744 }
745 };
746
747 if result.valid == TextInputValid::No {
748 return Some(result.update);
749 }
750
751 match c {
752 VirtualKeyCode::Back => {
753 text_area.inner.text = {
754 let mut internal = text_area.inner.text.clone().into_library_owned_vec();
755 internal.pop();
756 internal.into()
757 };
758 text_area.inner.cursor_pos = text_area.inner.cursor_pos.saturating_sub(1);
759 info.change_node_text(label_node_id, text_area.inner.get_text().into());
760
761 if text_area.inner.text.is_empty() {
763 info.set_css_property(
764 placeholder_node_id,
765 CssProperty::const_opacity(StyleOpacity::const_new(100)),
766 );
767 }
768 }
769 VirtualKeyCode::Return | VirtualKeyCode::NumpadEnter => {
770 text_area.inner.text = {
772 let mut internal = text_area.inner.text.clone().into_library_owned_vec();
773 internal.push('\n' as u32);
774 internal.into()
775 };
776 text_area.inner.cursor_pos = text_area.inner.cursor_pos.saturating_add(1);
777 info.change_node_text(label_node_id, text_area.inner.get_text().into());
778 info.set_css_property(
780 placeholder_node_id,
781 CssProperty::const_opacity(StyleOpacity::const_new(0)),
782 );
783 }
784 _ => return Some(result.update),
785 }
786
787 Some(result.update)
788}
789
790impl From<TextArea> for Dom {
791 fn from(t: TextArea) -> Self {
792 t.dom()
793 }
794}