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