1use std::{
2 cell::{Cell, RefCell},
3 hash::{Hash, Hasher},
4 rc::Rc,
5};
6
7use cranpose_core::{MutableState, mutableStateOf};
8use cranpose_foundation::{
9 Constraints, DelegatableNode, DrawModifierNode, FocusState, InvalidationKind,
10 LayoutModifierNode, Measurable, ModifierNode, ModifierNodeContext, ModifierNodeElement,
11 NodeCapabilities, NodeState, PointerEvent, PointerEventKind, PointerInputNode,
12 SemanticsConfiguration, SemanticsNode, Size,
13 text::{TextFieldLineLimits, TextFieldState, TextRange},
14};
15use cranpose_ui_graphics::{Brush, Color, Point, ProjectiveTransform};
16use cranpose_ui_layout::ceil_to_px;
17
18#[derive(Clone, Copy, PartialEq, Debug)]
19pub struct TextFieldHandleMetrics {
20 pub focused: bool,
21 pub direct_manipulation: bool,
22 pub local_to_window: ProjectiveTransform,
24 pub padding_left: f32,
25 pub padding_top: f32,
26 pub scroll_offset: f32,
27 pub line_height: f32,
28 pub glyph_box: (f32, f32),
29 pub wrap_width: Option<f32>,
30}
31
32#[derive(Clone)]
33pub struct TextFieldHandleController {
34 inner: Rc<TextFieldHandleControllerInner>,
35}
36
37impl PartialEq for TextFieldHandleController {
38 fn eq(&self, other: &Self) -> bool {
39 Rc::ptr_eq(&self.inner, &other.inner)
40 }
41}
42
43struct TextFieldHandleControllerInner {
44 metrics: Cell<Option<TextFieldHandleMetrics>>,
45 activation: MutableState<u64>,
46 geometry: MutableState<u64>,
47 gesture_claim: RefCell<Option<Rc<Cell<bool>>>>,
48 press_track: Cell<Option<MutableState<Option<PointerPressTrack>>>>,
49}
50
51impl TextFieldHandleController {
52 pub fn new() -> Self {
53 Self {
54 inner: Rc::new(TextFieldHandleControllerInner {
55 metrics: Cell::new(None),
56 activation: mutableStateOf(0u64),
57 geometry: mutableStateOf(0u64),
58 gesture_claim: RefCell::new(None),
59 press_track: Cell::new(None),
60 }),
61 }
62 }
63
64 pub(crate) fn publish(&self, metrics: TextFieldHandleMetrics) {
65 let previous = self.inner.metrics.replace(Some(metrics));
66 if previous == Some(metrics) {
67 return;
68 }
69 let was_live = previous.map(|metrics| (metrics.focused, metrics.direct_manipulation));
70 if was_live != Some((metrics.focused, metrics.direct_manipulation)) {
71 self.bump(&self.inner.activation);
72 }
73 self.bump(&self.inner.geometry);
74 }
75
76 fn bump(&self, revision: &MutableState<u64>) {
77 revision.update(|value| *value = value.wrapping_add(1));
78 }
79
80 pub fn metrics(&self) -> Option<TextFieldHandleMetrics> {
93 let _ = self.inner.activation.value();
94 self.inner.metrics.get()
95 }
96
97 pub fn live_metrics(&self) -> Option<TextFieldHandleMetrics> {
105 let _ = self.inner.geometry.value();
106 self.inner.metrics.get()
107 }
108
109 pub fn metrics_now(&self) -> Option<TextFieldHandleMetrics> {
115 self.inner.metrics.get()
116 }
117
118 pub(crate) fn adopt_gesture_claim(&self, claim: &Rc<Cell<bool>>) {
119 let mut slot = self.inner.gesture_claim.borrow_mut();
120 let adopted = slot.as_ref().is_some_and(|held| Rc::ptr_eq(held, claim));
121 if !adopted {
122 *slot = Some(Rc::clone(claim));
123 }
124 }
125
126 pub(crate) fn adopt_press_track(&self, press_track: MutableState<Option<PointerPressTrack>>) {
127 if self.inner.press_track.get() != Some(press_track) {
128 self.inner.press_track.set(Some(press_track));
129 self.bump(&self.inner.activation);
130 }
131 }
132
133 pub fn press(&self) -> Option<PointerPressTrack> {
134 self.inner.press_track.get().and_then(|state| state.get())
135 }
136
137 pub fn claim_gesture(&self) {
138 if let Some(claim) = self.inner.gesture_claim.borrow().as_ref() {
139 claim.set(true);
140 }
141 }
142
143 pub fn gesture_claimed(&self) -> bool {
144 self.inner
145 .gesture_claim
146 .borrow()
147 .as_ref()
148 .is_some_and(|claim| claim.get())
149 }
150}
151
152impl Default for TextFieldHandleController {
153 fn default() -> Self {
154 Self::new()
155 }
156}
157
158const DEFAULT_CURSOR_COLOR: Color = Color(1.0, 1.0, 1.0, 1.0);
159
160pub(crate) const DEFAULT_SELECTION_COLOR: Color = Color(0.0, 0.5, 1.0, 0.3);
161
162const DEFAULT_LINE_HEIGHT: f32 = 20.0;
163
164const MIN_SIZE_TEXT: &str = "HHHHHHHHHH";
167
168const CURSOR_WIDTH: f32 = 2.0;
169
170pub(crate) fn compute_horizontal_scroll_offset(
171 current_offset: f32,
172 cursor_x: f32,
173 text_width: f32,
174 viewport_width: f32,
175) -> f32 {
176 if viewport_width <= 0.0 {
177 return 0.0;
178 }
179 let max_offset = (text_width + CURSOR_WIDTH - viewport_width).max(0.0);
180 let mut offset = current_offset.clamp(0.0, max_offset);
181 let visible_end = offset + viewport_width - CURSOR_WIDTH;
182 if cursor_x > visible_end {
183 offset = cursor_x - viewport_width + CURSOR_WIDTH;
184 } else if cursor_x < offset {
185 offset = cursor_x;
186 }
187 offset.clamp(0.0, max_offset)
188}
189
190pub(crate) fn intersect_rect(
191 rect: cranpose_ui_graphics::Rect,
192 bounds: cranpose_ui_graphics::Rect,
193) -> Option<cranpose_ui_graphics::Rect> {
194 let x0 = rect.x.max(bounds.x);
195 let y0 = rect.y.max(bounds.y);
196 let x1 = (rect.x + rect.width).min(bounds.x + bounds.width);
197 let y1 = (rect.y + rect.height).min(bounds.y + bounds.height);
198 (x1 > x0 && y1 > y0).then_some(cranpose_ui_graphics::Rect {
199 x: x0,
200 y: y0,
201 width: x1 - x0,
202 height: y1 - y0,
203 })
204}
205
206pub type TextPanResolver = Rc<dyn Fn(f32) -> f32>;
209
210#[derive(Clone)]
211pub(crate) struct TextFieldLayoutHandle {
212 state: TextFieldState,
213 node_id: Rc<Cell<Option<cranpose_core::NodeId>>>,
214 wrap_width: Rc<Cell<Option<f32>>>,
215}
216
217impl TextFieldLayoutHandle {
218 pub(crate) fn measured_layout(
219 &self,
220 style: &TextStyle,
221 ) -> std::rc::Rc<crate::text::PreparedTextLayout> {
222 crate::text::prepare_text_layout_for_node(
223 self.node_id.get(),
224 &crate::text::AnnotatedString::from(self.state.text()),
225 style,
226 crate::text::TextLayoutOptions::default(),
227 self.wrap_width.get(),
228 )
229 }
230}
231
232pub(crate) fn caret_visual_line_for_offset(
233 text: &str,
234 style: &TextStyle,
235 node_id: Option<cranpose_core::NodeId>,
236 wrap_width: Option<f32>,
237 offset: usize,
238 affinity: crate::text_selection::LineAffinity,
239) -> (usize, usize) {
240 let offset = offset.min(text.len());
241 match wrap_width {
242 Some(width) if width.is_finite() && width > 0.0 => {
243 let annotated = crate::text::AnnotatedString::from(text);
244 let ranges = crate::text::wrapped_line_ranges(
245 node_id,
246 &annotated,
247 style,
248 crate::text::TextLayoutOptions::default(),
249 Some(width),
250 );
251 crate::text_selection::caret_visual_line(&ranges, offset, affinity)
252 }
253 _ => {
254 let before = &text[..offset];
255 let line_index = before.matches('\n').count();
256 let line_start = before.rfind('\n').map_or(0, |i| i + 1);
257 (line_index, line_start)
258 }
259 }
260}
261
262#[expect(clippy::too_many_arguments)]
263pub(crate) fn range_visual_line_rects(
264 text: &str,
265 style: &TextStyle,
266 node_id: Option<cranpose_core::NodeId>,
267 wrap_width: Option<f32>,
268 pan: f32,
269 line_height: f32,
270 start: usize,
271 end: usize,
272) -> Vec<cranpose_ui_graphics::Rect> {
273 if start >= end {
274 return Vec::new();
275 }
276 let annotated = crate::text::AnnotatedString::from(text);
277 let line_ranges = crate::text::wrapped_line_ranges(
278 node_id,
279 &annotated,
280 style,
281 crate::text::TextLayoutOptions::default(),
282 wrap_width,
283 );
284 let mut rects = Vec::new();
285 for (line_idx, line_range) in line_ranges.iter().enumerate() {
286 let line_start = line_range.start;
287 let line_end = line_range.end;
288 if end <= line_start || start >= line_end {
289 continue;
290 }
291 let seg_start = start.max(line_start);
292 let seg_end = end.min(line_end);
293 let x0 = crate::text::measure_text(
294 &crate::text::AnnotatedString::from(&text[line_start..seg_start]),
295 style,
296 )
297 .width
298 - pan;
299 let x1 = crate::text::measure_text(
300 &crate::text::AnnotatedString::from(&text[line_start..seg_end]),
301 style,
302 )
303 .width
304 - pan;
305 let width = x1 - x0;
306 if width > 0.0 {
307 rects.push(cranpose_ui_graphics::Rect {
308 x: x0,
309 y: line_idx as f32 * line_height,
310 width,
311 height: line_height,
312 });
313 }
314 }
315 rects
316}
317
318fn build_focus_handler(
319 state: TextFieldState,
320 refs: &TextFieldRefs,
321 line_limits: TextFieldLineLimits,
322 style: &TextStyle,
323) -> Rc<dyn crate::text_field_focus::FocusedTextFieldHandler> {
324 crate::text_field_handler::TextFieldHandler::new(
325 state,
326 refs.node_id.get(),
327 refs.focus_node.get(),
328 line_limits,
329 refs.focus_options.get().show_keyboard_on_focus,
330 crate::text_field_handler::CaretGeometryRefs {
331 local_to_window: refs.local_to_window.clone(),
332 content_origin: refs.content_origin.clone(),
333 scroll_offset: refs.scroll_offset.clone(),
334 style: style.clone(),
335 },
336 )
337}
338
339fn request_pointer_focus(
340 state: TextFieldState,
341 refs: &TextFieldRefs,
342 line_limits: TextFieldLineLimits,
343 style: &TextStyle,
344 modal_depth: usize,
345) {
346 if modal_depth < crate::modal::current_modal_depth() {
347 return;
348 }
349 let routed = refs
350 .focus_node
351 .get()
352 .is_some_and(crate::focus_dispatch::request_focus_in_context);
353 if !routed {
354 crate::text_field_focus::request_focus(
355 refs.is_focused.clone(),
356 build_focus_handler(state, refs, line_limits, style),
357 modal_depth,
358 );
359 }
360 if !refs.focus_options.get().show_keyboard_on_focus && *refs.is_focused.borrow() {
361 crate::text_input_session::notify_text_input_focus_gained();
362 }
363}
364
365pub(crate) struct TextFieldFocusBridge {
366 state: TextFieldState,
367 refs: TextFieldRefs,
368 style: TextStyle,
369 line_limits: TextFieldLineLimits,
370}
371
372impl TextFieldFocusBridge {
373 pub(crate) fn handle(
375 state: TextFieldState,
376 refs: TextFieldRefs,
377 style: TextStyle,
378 line_limits: TextFieldLineLimits,
379 ) -> Rc<dyn crate::focus_dispatch::FocusTargetHandle> {
380 Rc::new(Self {
381 state,
382 refs,
383 style,
384 line_limits,
385 })
386 }
387}
388
389impl crate::focus_dispatch::FocusTargetHandle for TextFieldFocusBridge {
390 fn set_focus_state(&self, state: FocusState) {
391 if state.is_focused() {
392 crate::text_field_focus::request_focus(
393 self.refs.is_focused.clone(),
394 build_focus_handler(self.state, &self.refs, self.line_limits, &self.style),
395 self.refs.focus_options.get().modal_depth,
396 );
397 } else if crate::text_field_focus::focused_field_node() == self.refs.node_id.get() {
398 crate::text_field_focus::clear_focus();
399 }
400 }
401}
402
403#[derive(Clone, Copy)]
404struct TextFieldFocusOptions {
405 modal_depth: usize,
406 show_keyboard_on_focus: bool,
407}
408
409#[derive(Clone)]
410pub(crate) struct TextFieldRefs {
411 pub is_focused: Rc<RefCell<bool>>,
412 pub content_origin: Rc<RefCell<crate::modifier::CoordinatorRect>>,
415 pub drag_anchor: Rc<Cell<Option<crate::text_selection::SelectionAnchor>>>,
416 pub last_click_time: Rc<Cell<Option<web_time::Instant>>>,
417 pub last_click_pos: Rc<Cell<Option<(f32, f32)>>>,
418 pub click_count: Rc<Cell<u8>>,
419 pub node_id: Rc<Cell<Option<cranpose_core::NodeId>>>,
420 pub focus_node: Rc<Cell<Option<cranpose_core::NodeId>>>,
423 pub scroll_offset: Rc<Cell<f32>>,
424 pub direct_manipulation: Rc<Cell<bool>>,
425 pub local_to_window: Rc<Cell<ProjectiveTransform>>,
426 pub line_height: Rc<Cell<f32>>,
427 pub wrap_width: Rc<Cell<Option<f32>>>,
428 pub press_track: MutableState<Option<PointerPressTrack>>,
429 pub gesture_claimed: Rc<Cell<bool>>,
430 focus_options: Rc<Cell<TextFieldFocusOptions>>,
431}
432
433#[derive(Clone, Copy, Debug, PartialEq)]
434pub struct PointerPressTrack {
435 pub start: Point,
436 pub position: Point,
437}
438
439impl TextFieldRefs {
440 pub fn new() -> Self {
441 Self {
442 is_focused: Rc::new(RefCell::new(false)),
443 content_origin: Rc::default(),
444 drag_anchor: Rc::new(Cell::new(None)),
445 last_click_time: Rc::new(Cell::new(None::<web_time::Instant>)),
446 last_click_pos: Rc::new(Cell::new(None::<(f32, f32)>)),
447 click_count: Rc::new(Cell::new(0_u8)),
448 node_id: Rc::new(Cell::new(None::<cranpose_core::NodeId>)),
449 focus_node: Rc::new(Cell::new(None::<cranpose_core::NodeId>)),
450 scroll_offset: Rc::new(Cell::new(0.0_f32)),
451 direct_manipulation: Rc::new(Cell::new(false)),
452 local_to_window: Rc::new(Cell::new(ProjectiveTransform::identity())),
453 line_height: Rc::new(Cell::new(DEFAULT_LINE_HEIGHT)),
454 wrap_width: Rc::new(Cell::new(None::<f32>)),
455 press_track: mutableStateOf(None::<PointerPressTrack>),
456 gesture_claimed: Rc::new(Cell::new(false)),
457 focus_options: Rc::new(Cell::new(TextFieldFocusOptions {
458 modal_depth: 0,
459 show_keyboard_on_focus: true,
460 })),
461 }
462 }
463
464 pub(crate) fn key(&self) -> u64 {
467 Rc::as_ptr(&self.is_focused) as usize as u64
468 }
469}
470
471impl PartialEq for TextFieldRefs {
472 fn eq(&self, other: &Self) -> bool {
473 Rc::ptr_eq(&self.is_focused, &other.is_focused)
474 }
475}
476
477use crate::text::TextStyle;
478
479pub struct TextFieldModifierNode {
480 state: TextFieldState,
481 refs: TextFieldRefs,
482 style: TextStyle,
483 cursor_brush: Brush,
484 selection_brush: Brush,
485 line_limits: TextFieldLineLimits,
486 cached_text: String,
487 cached_selection: TextRange,
488 node_state: NodeState,
489 measured_size: Rc<Cell<Size>>,
490 min_size: Cell<Option<(f32, Size)>>,
492 measured_line_height: Rc<Cell<f32>>,
493 measured_wrap_width: Rc<Cell<Option<f32>>>,
494 cached_handler: Rc<dyn Fn(PointerEvent)>,
495 cached_pan_resolver: TextPanResolver,
496 handle_controller: Option<TextFieldHandleController>,
497 modal_depth: usize,
498 focus_bridge: Option<Rc<dyn crate::focus_dispatch::FocusTargetHandle>>,
499 decorated: bool,
502}
503
504impl std::fmt::Debug for TextFieldModifierNode {
505 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
506 f.debug_struct("TextFieldModifierNode")
507 .field("text", &self.state.text())
508 .field("style", &self.style)
509 .field("is_focused", &*self.refs.is_focused.borrow())
510 .finish()
511 }
512}
513
514impl TextFieldModifierNode {
515 pub fn new(state: TextFieldState, style: TextStyle) -> Self {
517 Self::with_refs(state, style, TextFieldRefs::new(), false)
518 }
519
520 fn with_refs(
521 state: TextFieldState,
522 style: TextStyle,
523 refs: TextFieldRefs,
524 decorated: bool,
525 ) -> Self {
526 let value = state.value();
527 let refs_line_height = refs.line_height.clone();
528 let refs_wrap_width = refs.wrap_width.clone();
529 let line_limits = TextFieldLineLimits::default();
530 let cached_handler =
531 Self::create_handler(state, refs.clone(), line_limits, style.clone(), 0);
532 let cached_pan_resolver =
533 Self::create_pan_resolver(state, refs.clone(), line_limits, style.clone());
534
535 Self {
536 state,
537 refs,
538 style,
539 cursor_brush: Brush::solid(DEFAULT_CURSOR_COLOR),
540 selection_brush: Brush::solid(DEFAULT_SELECTION_COLOR),
541 line_limits,
542 cached_text: value.text,
543 cached_selection: value.selection,
544 node_state: NodeState::new(),
545 measured_size: Rc::new(Cell::new(Size {
546 width: 0.0,
547 height: 0.0,
548 })),
549 min_size: Cell::new(None),
550 measured_line_height: refs_line_height,
551 measured_wrap_width: refs_wrap_width,
552 cached_handler,
553 cached_pan_resolver,
554 handle_controller: None,
555 modal_depth: 0,
556 focus_bridge: None,
557 decorated,
558 }
559 }
560
561 pub fn with_line_limits(mut self, line_limits: TextFieldLineLimits) -> Self {
563 self.line_limits = line_limits;
564 self.rebuild_cached_closures();
565 self
566 }
567
568 fn rebuild_cached_closures(&mut self) {
569 self.cached_handler = Self::create_handler(
570 self.state,
571 self.refs.clone(),
572 self.line_limits,
573 self.style.clone(),
574 self.modal_depth,
575 );
576 self.cached_pan_resolver = Self::create_pan_resolver(
577 self.state,
578 self.refs.clone(),
579 self.line_limits,
580 self.style.clone(),
581 );
582 }
583
584 pub fn with_handle_controller(mut self, controller: TextFieldHandleController) -> Self {
586 self.handle_controller = Some(controller);
587 self
588 }
589
590 fn create_pan_resolver(
591 state: TextFieldState,
592 refs: TextFieldRefs,
593 line_limits: TextFieldLineLimits,
594 style: TextStyle,
595 ) -> TextPanResolver {
596 Rc::new(move |viewport_width: f32| {
597 if !line_limits.is_single_line() {
598 refs.scroll_offset.set(0.0);
599 return 0.0;
600 }
601 let text = state.text();
602 let pos = state.selection().start.min(text.len());
603 let text_width = crate::text::measure_text(
604 &crate::text::AnnotatedString::from(text.as_str()),
605 &style,
606 )
607 .width;
608 let cursor_x = crate::text::measure_text(
609 &crate::text::AnnotatedString::from(&text[..pos]),
610 &style,
611 )
612 .width;
613 let offset = compute_horizontal_scroll_offset(
614 refs.scroll_offset.get(),
615 cursor_x,
616 text_width,
617 viewport_width,
618 );
619 refs.scroll_offset.set(offset);
620 offset
621 })
622 }
623
624 pub fn text_pan_resolver(&self) -> Option<TextPanResolver> {
629 self.line_limits
630 .is_single_line()
631 .then(|| self.cached_pan_resolver.clone())
632 }
633
634 pub fn scroll_offset(&self) -> f32 {
636 self.refs.scroll_offset.get()
637 }
638
639 pub fn line_limits(&self) -> TextFieldLineLimits {
641 self.line_limits
642 }
643
644 pub(crate) fn create_handler(
645 state: TextFieldState,
646 refs: TextFieldRefs,
647 line_limits: TextFieldLineLimits,
648 style: TextStyle,
649 modal_depth: usize,
650 ) -> Rc<dyn Fn(PointerEvent)> {
651 use crate::text_selection::{
652 MULTI_TAP_SLOP_PX, MULTI_TAP_TIMEOUT_MS, SelectionAnchor, SelectionGranularity,
653 classify_tap_count, resolve_selection_tap_count, tap_selection_granularity,
654 };
655
656 Rc::new(move |event: PointerEvent| {
657 let content = refs.content_origin.borrow().origin();
658 let click_x = (event.position.x - content.x + refs.scroll_offset.get()).max(0.0);
659 let click_y = (event.position.y - content.y).max(0.0);
660
661 match event.kind {
662 PointerEventKind::Down => {
663 refs.direct_manipulation.set(true);
664 refs.press_track.set(Some(PointerPressTrack {
665 start: event.global_position,
666 position: event.global_position,
667 }));
668 refs.gesture_claimed.set(false);
669
670 request_pointer_focus(state, &refs, line_limits, &style, modal_depth);
671
672 let now = web_time::Instant::now();
673 let text = state.text();
674 let pos = crate::text::offset_for_position_wrapped(
675 &text,
676 &style,
677 refs.node_id.get(),
678 refs.wrap_width.get(),
679 refs.line_height.get(),
680 click_x,
681 click_y,
682 );
683
684 let previous = refs.last_click_pos.get().and_then(|(px, py)| {
685 let count = refs.click_count.get();
686 (count > 0).then_some((count, px, py))
687 });
688 let elapsed_ms = refs
689 .last_click_time
690 .get()
691 .map_or(u128::MAX, |last| now.duration_since(last).as_millis());
692 let tap_count = classify_tap_count(
693 previous,
694 elapsed_ms,
695 event.position.x,
696 event.position.y,
697 MULTI_TAP_TIMEOUT_MS,
698 MULTI_TAP_SLOP_PX,
699 );
700
701 let selection = state.selection();
702 let tap_in_selection =
703 !selection.collapsed() && pos >= selection.min() && pos <= selection.max();
704 let repeat_in_place = refs.last_click_pos.get().is_some_and(|(px, py)| {
705 let dx = event.position.x - px;
706 let dy = event.position.y - py;
707 dx * dx + dy * dy <= MULTI_TAP_SLOP_PX * MULTI_TAP_SLOP_PX
708 });
709 let effective_count = resolve_selection_tap_count(
710 tap_count,
711 refs.click_count.get(),
712 tap_in_selection,
713 repeat_in_place,
714 );
715
716 let granularity = tap_selection_granularity(effective_count);
717 let anchor = SelectionAnchor::at(&text, pos, granularity);
718 refs.drag_anchor.set(Some(anchor));
719 state.edit(|buffer| match granularity {
720 SelectionGranularity::Caret => buffer.place_cursor_before_char(pos),
721 _ => buffer.select(TextRange::new(anchor.start, anchor.end)),
722 });
723
724 refs.click_count.set(effective_count);
725 refs.last_click_time.set(Some(now));
726 refs.last_click_pos
727 .set(Some((event.position.x, event.position.y)));
728 event.consume();
729 }
730 PointerEventKind::Move => {
731 if let Some(mut track) = refs.press_track.get() {
732 track.position = event.global_position;
733 refs.press_track.set(Some(track));
734 if let Some(node_id) = refs.node_id.get() {
735 crate::schedule_draw_repass(node_id);
736 }
737 crate::request_render_invalidation();
738 }
739 if refs.gesture_claimed.get() {
740 event.consume();
741 return;
742 }
743 if let Some(anchor) = refs.drag_anchor.get()
744 && *refs.is_focused.borrow()
745 {
746 let text = state.text();
747 let current_pos = crate::text::offset_for_position_wrapped(
748 &text,
749 &style,
750 refs.node_id.get(),
751 refs.wrap_width.get(),
752 refs.line_height.get(),
753 click_x,
754 click_y,
755 );
756
757 state.set_selection(anchor.dragged_to(&text, current_pos));
758
759 crate::request_render_invalidation();
760
761 event.consume();
762 }
763 }
764 PointerEventKind::Up => {
765 refs.drag_anchor.set(None);
766 refs.press_track.set(None);
767 refs.gesture_claimed.set(false);
768 if let Some(node_id) = refs.node_id.get() {
769 crate::schedule_draw_repass(node_id);
770 }
771 crate::request_render_invalidation();
772 }
773 PointerEventKind::Cancel => {
774 refs.press_track.set(None);
775 refs.gesture_claimed.set(false);
776 if let Some(node_id) = refs.node_id.get() {
777 crate::schedule_draw_repass(node_id);
778 }
779 crate::request_render_invalidation();
780 }
781 _ => {}
782 }
783 })
784 }
785
786 pub fn with_cursor_color(mut self, color: Color) -> Self {
791 self.cursor_brush = Brush::solid(color);
792 self.selection_brush = Brush::solid(
793 color.with_alpha(crate::widgets::basic_text_field::SELECTION_HIGHLIGHT_ALPHA),
794 );
795 self
796 }
797
798 pub fn set_focused(&mut self, focused: bool) {
800 let current = *self.refs.is_focused.borrow();
801 if current != focused {
802 *self.refs.is_focused.borrow_mut() = focused;
803 if !focused {
804 self.refs.direct_manipulation.set(false);
805 self.refs.press_track.set(None);
806 self.refs.gesture_claimed.set(false);
807 }
808 }
809 }
810
811 pub fn is_focused(&self) -> bool {
813 *self.refs.is_focused.borrow()
814 }
815
816 pub(crate) fn window_transform_sink(&self) -> Rc<Cell<ProjectiveTransform>> {
817 self.refs.local_to_window.clone()
818 }
819
820 pub(crate) fn layout_handle(&self) -> TextFieldLayoutHandle {
821 TextFieldLayoutHandle {
822 state: self.state,
823 node_id: self.refs.node_id.clone(),
824 wrap_width: self.measured_wrap_width.clone(),
825 }
826 }
827
828 pub fn text(&self) -> String {
830 self.state.text()
831 }
832
833 pub fn style(&self) -> &TextStyle {
834 &self.style
835 }
836
837 pub fn selection(&self) -> TextRange {
839 self.state.selection()
840 }
841
842 pub fn cursor_brush(&self) -> Brush {
844 self.cursor_brush.clone()
845 }
846
847 pub fn selection_brush(&self) -> Brush {
849 self.selection_brush.clone()
850 }
851
852 pub fn insert_text(&mut self, text: &str) {
854 self.state.edit(|buffer| {
855 buffer.insert(text);
856 });
857 }
858
859 pub fn copy_selection(&self) -> Option<String> {
862 self.state.copy_selection()
863 }
864
865 pub fn cut_selection(&mut self) -> Option<String> {
868 let text = self.copy_selection();
869 if text.is_some() {
870 self.state.edit(|buffer| {
871 buffer.delete(buffer.selection());
872 });
873 }
874 text
875 }
876
877 pub(crate) fn set_content_origin(&self, origin: crate::modifier::CoordinatorRect) {
880 *self.refs.content_origin.borrow_mut() = origin;
881 }
882
883 fn wrap_width(&self, available_width: f32) -> Option<f32> {
884 (!self.line_limits.is_single_line() && available_width.is_finite() && available_width > 0.0)
885 .then_some(available_width)
886 }
887
888 fn measure_text_content(&self, wrap_width: Option<f32>) -> crate::text::TextMetrics {
889 let text = self.state.text();
890 let node_id = self.refs.node_id.get();
891 let annotated = crate::text::AnnotatedString::from(text.as_str());
892 let metrics = match wrap_width {
893 Some(max_width) => crate::text::measure_text_with_options_for_node(
894 node_id,
895 &annotated,
896 &self.style,
897 crate::text::TextLayoutOptions::default(),
898 Some(max_width),
899 ),
900 None => crate::text::measure_text_for_node(node_id, &annotated, &self.style),
901 };
902 self.measured_line_height.set(metrics.line_height);
903 metrics
904 }
905
906 fn text_size(&self, wrap_width: Option<f32>, density: f32) -> (Size, crate::text::TextMetrics) {
909 let metrics = self.measure_text_content(wrap_width);
910 (
911 Size {
912 width: ceil_to_px(metrics.width, density),
913 height: ceil_to_px(metrics.height, density),
914 },
915 metrics,
916 )
917 }
918
919 fn min_size(&self, density: f32) -> Size {
923 if let Some((taken_at, size)) = self.min_size.get()
924 && taken_at == density
925 {
926 return size;
927 }
928 let metrics = crate::text::measure_text(
929 &crate::text::AnnotatedString::from(MIN_SIZE_TEXT),
930 &self.style,
931 );
932 let size = Size {
933 width: ceil_to_px(metrics.width, density),
934 height: ceil_to_px(metrics.height, density),
935 };
936 self.min_size.set(Some((density, size)));
937 size
938 }
939
940 fn update_cached_state(&mut self) -> bool {
941 let value = self.state.value();
942 let text_changed = value.text != self.cached_text;
943 let selection_changed = value.selection != self.cached_selection;
944
945 if text_changed {
946 self.cached_text = value.text;
947 }
948 if selection_changed {
949 self.cached_selection = value.selection;
950 }
951
952 text_changed || selection_changed
953 }
954}
955
956impl DelegatableNode for TextFieldModifierNode {
957 fn node_state(&self) -> &NodeState {
958 &self.node_state
959 }
960}
961
962impl ModifierNode for TextFieldModifierNode {
963 fn on_attach(&mut self, context: &mut dyn ModifierNodeContext) {
964 self.refs.node_id.set(context.node_id());
965
966 context.invalidate(InvalidationKind::Layout);
967 context.invalidate(InvalidationKind::Draw);
968 if self.decorated {
969 return;
970 }
971 context.invalidate(InvalidationKind::Semantics);
972 self.refs.focus_node.set(context.node_id());
973
974 if let Some(node_id) = context.node_id() {
975 let bridge = TextFieldFocusBridge::handle(
976 self.state,
977 self.refs.clone(),
978 self.style.clone(),
979 self.line_limits,
980 );
981 self.focus_bridge = Some(Rc::clone(&bridge));
982 crate::focus_dispatch::register_focus_target(node_id, bridge);
983 }
984 }
985
986 fn on_detach(&mut self) {
987 if let (Some(node_id), Some(bridge)) = (self.refs.node_id.get(), self.focus_bridge.take()) {
988 crate::focus_dispatch::unregister_focus_target(node_id, &bridge);
989 }
990 }
991
992 fn as_draw_node(&self) -> Option<&dyn DrawModifierNode> {
993 Some(self)
994 }
995
996 fn as_draw_node_mut(&mut self) -> Option<&mut dyn DrawModifierNode> {
997 Some(self)
998 }
999
1000 fn as_layout_node(&self) -> Option<&dyn LayoutModifierNode> {
1001 Some(self)
1002 }
1003
1004 fn as_layout_node_mut(&mut self) -> Option<&mut dyn LayoutModifierNode> {
1005 Some(self)
1006 }
1007
1008 cranpose_foundation::impl_semantics_node!();
1009
1010 fn as_pointer_input_node(&self) -> Option<&dyn PointerInputNode> {
1011 (!self.decorated).then_some(self)
1012 }
1013
1014 fn as_pointer_input_node_mut(&mut self) -> Option<&mut dyn PointerInputNode> {
1015 if self.decorated { None } else { Some(self) }
1016 }
1017}
1018
1019impl LayoutModifierNode for TextFieldModifierNode {
1020 fn measure(
1021 &self,
1022 context: &mut dyn ModifierNodeContext,
1023 _measurable: &dyn Measurable,
1024 constraints: Constraints,
1025 ) -> cranpose_ui_layout::LayoutModifierMeasureResult {
1026 let density = context.density();
1027 let wrap_width = self.wrap_width(constraints.max_width);
1028 self.measured_wrap_width.set(wrap_width);
1029 let (text, metrics) = self.text_size(wrap_width, density);
1030 let min = self.min_size(density);
1031 let fit = |length: f32, min: f32, low: f32, high: f32| {
1033 length.max(min.max(low).min(high)).min(high)
1034 };
1035 let size = Size {
1036 width: fit(
1037 text.width,
1038 min.width,
1039 constraints.min_width,
1040 constraints.max_width,
1041 ),
1042 height: fit(
1043 text.height,
1044 min.height,
1045 constraints.min_height,
1046 constraints.max_height,
1047 ),
1048 };
1049 self.measured_size.set(size);
1050
1051 let _ = (self.cached_pan_resolver)(size.width);
1052
1053 let first = crate::text::measure::text_line_box(&self.style)
1054 .map(crate::text::LineBox::first_baseline)
1055 .or_else(|| crate::text::measure::first_baseline(&self.style));
1056 let lines = cranpose_ui_layout::AlignmentLines::new(
1057 first,
1058 first.map(|baseline| {
1059 baseline + metrics.line_count.saturating_sub(1) as f32 * metrics.line_height
1060 }),
1061 );
1062 cranpose_ui_layout::LayoutModifierMeasureResult::with_size(size).with_alignment_lines(lines)
1063 }
1064
1065 fn min_intrinsic_width(&self, _measurable: &dyn Measurable, _height: f32, density: f32) -> f32 {
1068 self.text_size(None, density).0.width
1069 }
1070
1071 fn max_intrinsic_width(&self, _measurable: &dyn Measurable, _height: f32, density: f32) -> f32 {
1072 self.text_size(None, density).0.width
1073 }
1074
1075 fn min_intrinsic_height(&self, _measurable: &dyn Measurable, width: f32, density: f32) -> f32 {
1076 self.intrinsic_height(width, density)
1077 }
1078
1079 fn max_intrinsic_height(&self, _measurable: &dyn Measurable, width: f32, density: f32) -> f32 {
1080 self.intrinsic_height(width, density)
1081 }
1082}
1083
1084impl TextFieldModifierNode {
1085 fn intrinsic_height(&self, width: f32, density: f32) -> f32 {
1086 self.text_size(self.wrap_width(width), density)
1087 .0
1088 .height
1089 .max(self.min_size(density).height)
1090 }
1091}
1092
1093fn content_viewport(
1097 measured: cranpose_ui_graphics::Size,
1098 size: cranpose_foundation::Size,
1099) -> (f32, f32) {
1100 let width = if measured.width > 0.0 {
1101 measured.width
1102 } else {
1103 size.width
1104 };
1105 let height = if measured.height > 0.0 {
1106 measured.height
1107 } else {
1108 size.height
1109 };
1110 (width, height)
1111}
1112
1113impl DrawModifierNode for TextFieldModifierNode {
1114 fn create_draw_closure(
1115 &self,
1116 ) -> Option<Rc<dyn Fn(&mut cranpose_ui_graphics::DrawScopeDefault)>> {
1117 use cranpose_ui_graphics::{DrawPrimitive, DrawScope as _};
1118
1119 let is_focused = self.refs.is_focused.clone();
1120 let state = self.state;
1121 let content_origin = self.refs.content_origin.clone();
1122 let cursor_brush = self.cursor_brush.clone();
1123 let style = self.style.clone();
1124 let cached_line_height = self.measured_line_height.clone();
1125 let measured_size = self.measured_size.clone();
1126 let measured_wrap_width = self.measured_wrap_width.clone();
1127 let node_id = self.refs.node_id.clone();
1128 let pan_resolver = self.cached_pan_resolver.clone();
1129 let handle_controller = self.handle_controller.clone();
1130 let local_to_window = self.refs.local_to_window.clone();
1131 let direct_manipulation = self.refs.direct_manipulation.clone();
1132 let press_track = self.refs.press_track;
1133 let gesture_claimed = self.refs.gesture_claimed.clone();
1134
1135 Some(Rc::new(move |scope| {
1136 let size = scope.size();
1137 if !*is_focused.borrow() {
1138 if let Some(controller) = &handle_controller {
1139 controller.publish(TextFieldHandleMetrics {
1140 focused: false,
1141 direct_manipulation: false,
1142 local_to_window: local_to_window.get(),
1143 padding_left: 0.0,
1144 padding_top: 0.0,
1145 scroll_offset: 0.0,
1146 line_height: cached_line_height.get(),
1147 glyph_box: crate::text::glyph_line_box(&style, cached_line_height.get()),
1148 wrap_width: measured_wrap_width.get(),
1149 });
1150 }
1151 return;
1152 }
1153
1154 let mut primitives = Vec::new();
1155
1156 let text = state.text();
1157 let selection = state.selection();
1158 let line_height = cached_line_height.get();
1159
1160 let (viewport_width, viewport_height) = content_viewport(measured_size.get(), size);
1161 let pan = pan_resolver(viewport_width);
1162
1163 if let Some(controller) = &handle_controller {
1164 controller.adopt_gesture_claim(&gesture_claimed);
1165 controller.adopt_press_track(press_track);
1166 controller.publish(TextFieldHandleMetrics {
1167 focused: true,
1168 direct_manipulation: direct_manipulation.get(),
1169 local_to_window: local_to_window.get(),
1170 padding_left: content_origin.borrow().origin().x,
1171 padding_top: content_origin.borrow().origin().y,
1172 scroll_offset: pan,
1173 line_height,
1174 glyph_box: crate::text::glyph_line_box(&style, line_height),
1175 wrap_width: measured_wrap_width.get(),
1176 });
1177 }
1178 let clip_bounds = cranpose_ui_graphics::Rect {
1179 x: 0.0,
1180 y: 0.0,
1181 width: viewport_width,
1182 height: viewport_height,
1183 };
1184
1185 if let Some(comp_range) = state.composition() {
1186 let comp_start = comp_range.min();
1187 let comp_end = comp_range.max();
1188
1189 if comp_start < comp_end && comp_end <= text.len() {
1190 let underline_brush = cranpose_ui_graphics::Brush::solid(
1191 cranpose_ui_graphics::Color(0.8, 0.8, 0.8, 0.8),
1192 );
1193 let underline_height: f32 = 2.0;
1194
1195 for line_rect in range_visual_line_rects(
1196 &text,
1197 &style,
1198 node_id.get(),
1199 measured_wrap_width.get(),
1200 pan,
1201 line_height,
1202 comp_start,
1203 comp_end,
1204 ) {
1205 let underline_rect = cranpose_ui_graphics::Rect {
1206 x: line_rect.x,
1207 y: line_rect.y + line_height - underline_height,
1208 width: line_rect.width,
1209 height: underline_height,
1210 };
1211 if let Some(clipped) = intersect_rect(underline_rect, clip_bounds) {
1212 primitives.push(DrawPrimitive::Rect {
1213 rect: clipped,
1214 brush: underline_brush.clone(),
1215 stroke: None,
1216 });
1217 }
1218 }
1219 }
1220 }
1221
1222 if selection.collapsed() && crate::cursor_animation::is_cursor_visible() {
1223 let pos = selection.start.min(text.len());
1224 let (line_index, line_start) = caret_visual_line_for_offset(
1225 &text,
1226 &style,
1227 node_id.get(),
1228 measured_wrap_width.get(),
1229 pos,
1230 crate::text_selection::LineAffinity::Upstream,
1231 );
1232 let cursor_x = crate::text::measure_text(
1233 &crate::text::AnnotatedString::from(&text[line_start..pos]),
1234 &style,
1235 )
1236 .width
1237 - pan;
1238 let (box_off, box_h) = crate::text::glyph_line_box(&style, line_height);
1239 let cursor_y = line_index as f32 * line_height + box_off;
1240
1241 let cursor_rect = cranpose_ui_graphics::Rect {
1242 x: cursor_x,
1243 y: cursor_y,
1244 width: CURSOR_WIDTH,
1245 height: box_h,
1246 };
1247
1248 if let Some(clipped) = intersect_rect(cursor_rect, clip_bounds) {
1249 primitives.push(DrawPrimitive::Rect {
1250 rect: clipped,
1251 brush: cursor_brush.clone(),
1252 stroke: None,
1253 });
1254 }
1255 }
1256
1257 scope.push_recorded(primitives);
1258 }))
1259 }
1260
1261 fn create_behind_draw_closure(
1262 &self,
1263 ) -> Option<Rc<dyn Fn(&mut cranpose_ui_graphics::DrawScopeDefault)>> {
1264 use cranpose_ui_graphics::{DrawPrimitive, DrawScope as _};
1265
1266 let is_focused = self.refs.is_focused.clone();
1267 let state = self.state;
1268 let selection_brush = self.selection_brush.clone();
1269 let style = self.style.clone();
1270 let cached_line_height = self.measured_line_height.clone();
1271 let measured_size = self.measured_size.clone();
1272 let measured_wrap_width = self.measured_wrap_width.clone();
1273 let node_id = self.refs.node_id.clone();
1274 let pan_resolver = self.cached_pan_resolver.clone();
1275
1276 Some(Rc::new(move |scope| {
1277 let size = scope.size();
1278 if !*is_focused.borrow() {
1279 return;
1280 }
1281 let selection = state.selection();
1282 if selection.collapsed() {
1283 return;
1284 }
1285 let text = state.text();
1286 let line_height = cached_line_height.get();
1287 let (viewport_width, viewport_height) = content_viewport(measured_size.get(), size);
1288 let pan = pan_resolver(viewport_width);
1289 let clip_bounds = cranpose_ui_graphics::Rect {
1290 x: 0.0,
1291 y: 0.0,
1292 width: viewport_width,
1293 height: viewport_height,
1294 };
1295
1296 let mut primitives = Vec::new();
1297 let (box_off, box_h) = crate::text::glyph_line_box(&style, line_height);
1298 for sel_rect in range_visual_line_rects(
1299 &text,
1300 &style,
1301 node_id.get(),
1302 measured_wrap_width.get(),
1303 pan,
1304 line_height,
1305 selection.min(),
1306 selection.max(),
1307 ) {
1308 let sel_rect = cranpose_ui_graphics::Rect {
1309 y: sel_rect.y + box_off,
1310 height: box_h,
1311 ..sel_rect
1312 };
1313 if let Some(clipped) = intersect_rect(sel_rect, clip_bounds) {
1314 primitives.push(DrawPrimitive::Rect {
1315 rect: clipped,
1316 brush: selection_brush.clone(),
1317 stroke: None,
1318 });
1319 }
1320 }
1321 scope.push_recorded(primitives);
1322 }))
1323 }
1324}
1325
1326impl SemanticsNode for TextFieldModifierNode {
1327 fn merge_semantics(&self, config: &mut SemanticsConfiguration) {
1328 if !self.decorated {
1329 merge_text_field_semantics(self.state, self.line_limits, config);
1330 }
1331 }
1332
1333 fn reach(&self) -> cranpose_foundation::SemanticsReach {
1336 cranpose_foundation::SemanticsReach {
1337 merges_live_state: true,
1338 ..cranpose_foundation::SemanticsReach::default()
1339 }
1340 }
1341}
1342
1343pub(crate) fn merge_text_field_semantics(
1346 state: TextFieldState,
1347 line_limits: TextFieldLineLimits,
1348 config: &mut SemanticsConfiguration,
1349) {
1350 let text = state.text();
1351 if config.content_description.is_none() {
1352 config.content_description = Some(text.clone());
1353 }
1354 config.text = Some(text);
1355 config.is_editable_text = true;
1356 config.is_clickable = true;
1357 config.multiline = !matches!(line_limits, TextFieldLineLimits::SingleLine);
1358 config.set_text = Some(cranpose_foundation::SemanticsSetText::new(move |text| {
1359 state.set_text(text)
1360 }));
1361 config.set_selection = Some(cranpose_foundation::SemanticsSetSelection::new(
1362 move |anchor, focus| {
1363 let text = state.text();
1364 let anchor = floor_char_boundary(&text, anchor);
1365 let focus = floor_char_boundary(&text, focus);
1366 state.set_selection(TextRange::new(anchor, focus));
1367 crate::cursor_animation::reset_cursor_blink();
1368 crate::request_render_invalidation();
1369 true
1370 },
1371 ));
1372 config.text_selection = Some(state.selection());
1373}
1374
1375fn floor_char_boundary(text: &str, index: usize) -> usize {
1376 let mut index = index.min(text.len());
1377 while !text.is_char_boundary(index) {
1378 index -= 1;
1379 }
1380 index
1381}
1382
1383impl PointerInputNode for TextFieldModifierNode {
1384 fn on_pointer_event(
1385 &mut self,
1386 _context: &mut dyn ModifierNodeContext,
1387 _event: &PointerEvent,
1388 ) -> bool {
1389 false
1390 }
1391
1392 fn hit_test(&self, x: f32, y: f32) -> bool {
1393 let size = self.measured_size.get();
1394 x >= 0.0 && x <= size.width && y >= 0.0 && y <= size.height
1395 }
1396
1397 fn pointer_input_handler(&self) -> Option<Rc<dyn Fn(PointerEvent)>> {
1398 Some(self.cached_handler.clone())
1399 }
1400}
1401
1402#[derive(Clone)]
1409pub struct TextFieldElement {
1410 state: TextFieldState,
1411 style: TextStyle,
1412 cursor_color: Color,
1413 line_limits: TextFieldLineLimits,
1414 show_keyboard_on_focus: bool,
1415 handle_controller: Option<TextFieldHandleController>,
1416 modal_depth: usize,
1417 decorator: Option<TextFieldRefs>,
1418}
1419
1420impl TextFieldElement {
1421 pub fn new(state: TextFieldState, style: TextStyle) -> Self {
1423 Self {
1424 state,
1425 style,
1426 cursor_color: DEFAULT_CURSOR_COLOR,
1427 line_limits: TextFieldLineLimits::default(),
1428 show_keyboard_on_focus: true,
1429 handle_controller: None,
1430 modal_depth: 0,
1431 decorator: None,
1432 }
1433 }
1434
1435 pub(crate) fn decorated_by(mut self, refs: TextFieldRefs) -> Self {
1438 self.decorator = Some(refs);
1439 self
1440 }
1441
1442 pub fn with_cursor_color(mut self, color: Color) -> Self {
1444 self.cursor_color = color;
1445 self
1446 }
1447
1448 pub fn with_line_limits(mut self, line_limits: TextFieldLineLimits) -> Self {
1450 self.line_limits = line_limits;
1451 self
1452 }
1453
1454 pub fn with_show_keyboard_on_focus(mut self, show: bool) -> Self {
1457 self.show_keyboard_on_focus = show;
1458 self
1459 }
1460
1461 pub fn with_handle_controller(mut self, controller: TextFieldHandleController) -> Self {
1463 self.handle_controller = Some(controller);
1464 self
1465 }
1466
1467 pub fn with_modal_depth(mut self, depth: usize) -> Self {
1470 self.modal_depth = depth;
1471 self
1472 }
1473}
1474
1475impl std::fmt::Debug for TextFieldElement {
1476 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1477 f.debug_struct("TextFieldElement")
1478 .field("text", &self.state.text())
1479 .field("style", &self.style)
1480 .field("cursor_color", &self.cursor_color)
1481 .finish()
1482 }
1483}
1484
1485impl Hash for TextFieldElement {
1486 fn hash<H: Hasher>(&self, state: &mut H) {
1487 self.state.id().hash(state);
1488 self.cursor_color.0.to_bits().hash(state);
1489 self.cursor_color.1.to_bits().hash(state);
1490 self.cursor_color.2.to_bits().hash(state);
1491 self.cursor_color.3.to_bits().hash(state);
1492 self.style.render_hash().hash(state);
1493 self.line_limits.hash(state);
1494 self.show_keyboard_on_focus.hash(state);
1495 self.modal_depth.hash(state);
1496 self.decorator.as_ref().map(TextFieldRefs::key).hash(state);
1497 }
1498}
1499
1500impl PartialEq for TextFieldElement {
1501 fn eq(&self, other: &Self) -> bool {
1502 self.state == other.state
1503 && self.style == other.style
1504 && self.cursor_color == other.cursor_color
1505 && self.line_limits == other.line_limits
1506 && self.show_keyboard_on_focus == other.show_keyboard_on_focus
1507 && self.modal_depth == other.modal_depth
1508 && self.decorator == other.decorator
1509 }
1510}
1511
1512impl Eq for TextFieldElement {}
1513
1514impl ModifierNodeElement for TextFieldElement {
1515 type Node = TextFieldModifierNode;
1516
1517 fn create(&self) -> Self::Node {
1518 let refs = self.decorator.clone().unwrap_or_else(TextFieldRefs::new);
1519 let decorated = self.decorator.is_some();
1520 let mut node =
1521 TextFieldModifierNode::with_refs(self.state, self.style.clone(), refs, decorated)
1522 .with_cursor_color(self.cursor_color)
1523 .with_line_limits(self.line_limits);
1524 node.modal_depth = self.modal_depth;
1525 node.refs.focus_options.set(TextFieldFocusOptions {
1526 modal_depth: self.modal_depth,
1527 show_keyboard_on_focus: self.show_keyboard_on_focus,
1528 });
1529 if let Some(controller) = self.handle_controller.clone() {
1530 node = node.with_handle_controller(controller);
1531 }
1532 node.rebuild_cached_closures();
1533 node
1534 }
1535
1536 fn update(&self, node: &mut Self::Node) {
1537 node.state = self.state;
1538 if node.style != self.style {
1539 node.min_size.set(None);
1540 node.style = self.style.clone();
1541 }
1542 node.cursor_brush = Brush::solid(self.cursor_color);
1543 node.line_limits = self.line_limits;
1544 node.handle_controller.clone_from(&self.handle_controller);
1545 node.modal_depth = self.modal_depth;
1546 let previous_focus_options = node.refs.focus_options.replace(TextFieldFocusOptions {
1547 modal_depth: self.modal_depth,
1548 show_keyboard_on_focus: self.show_keyboard_on_focus,
1549 });
1550 if self.show_keyboard_on_focus
1551 && !previous_focus_options.show_keyboard_on_focus
1552 && *node.refs.is_focused.borrow()
1553 {
1554 crate::text_input_session::notify_text_input_focus_gained();
1555 }
1556 node.rebuild_cached_closures();
1557
1558 if node.update_cached_state() {}
1559 }
1560
1561 fn key(&self) -> Option<u64> {
1564 self.decorator.as_ref().map(TextFieldRefs::key)
1565 }
1566
1567 fn capabilities(&self) -> NodeCapabilities {
1568 let drawn = NodeCapabilities::LAYOUT | NodeCapabilities::DRAW | NodeCapabilities::SEMANTICS;
1569 if self.decorator.is_some() {
1570 drawn
1571 } else {
1572 drawn | NodeCapabilities::POINTER_INPUT
1573 }
1574 }
1575
1576 fn always_update(&self) -> bool {
1577 true
1578 }
1579}
1580
1581#[cfg(test)]
1582#[path = "tests/text_field_modifier_node_tests.rs"]
1583mod tests;