Skip to main content

inset_rendering/
editable.rs

1//! Flutter counterpart: `rendering/editable.dart`.
2//!
3//! The custom-paint child boxes, `LeaderLayer` handles, and pointer recognizers
4//! wait.
5//! Caret and selection paint in `paint`, the same work Flutter's child painters
6//! do.
7
8use std::collections::HashMap;
9use std::fmt;
10
11use inset_embedder::{
12    BoxHeightStyle, BoxWidthStyle, Clip, Color, FontCollection, LineMetrics, Matrix4, Offset,
13    Paint, Radius, Rect, Size, TargetPlatform, TextAffinity, TextAlign, TextBaseline, TextBox,
14    TextDirection, TextHeightBehavior, TextPosition, TextRange, TextSelection, clamp_double,
15};
16use inset_foundation::{App, Handle, Listenable, Listener, ValueListenable, ValueNotifier};
17use inset_gestures::TapDownDetails;
18use inset_painting::{
19    ClipContext, InlineSpanRef, PaintingBinding, PlaceholderDimensions, TextPainter, TextScaler,
20    TextWidthBasis,
21};
22use inset_services::{AnyTextSelectionDelegate, SelectionChangedCause, TextLayoutMetrics};
23
24use crate::box_::{AnyRenderBox, BoxConstraints, BoxHitTestResult, RenderBox, RenderBoxData};
25use crate::layer::{ClipRectLayer, ContainerLayer, LayerHandle, LayerLink, LeaderLayer};
26use crate::layout_helper::ChildLayoutHelper;
27use crate::object::{
28    AnyRenderObject, ContainerRenderObjectData, ContainerRenderObjectMixin, RenderHandle,
29    RenderObject, RenderObjectData,
30};
31use crate::painting_context::PaintingContext;
32use crate::paragraph::{RenderInlineChildrenContainerDefaults, TextParentData};
33use crate::pipeline_owner::PipelineOwner;
34use crate::relayout_when_system_fonts_change::{
35    RelayoutWhenSystemFontsChangeData, RelayoutWhenSystemFontsChangeMixin,
36};
37use crate::text_boundary::WordBoundary;
38use crate::viewport_offset::AnyViewportOffset;
39
40const K_CARET_GAP: f64 = 1.0;
41const K_CARET_HEIGHT_OFFSET: f64 = 2.0;
42
43/// Represents the coordinates of the point in a selection, and the text
44/// direction at that point, relative to top left of the [`RenderEditable`] that
45/// holds the selection.
46#[derive(Clone, Copy, Debug, PartialEq)]
47pub struct TextSelectionPoint {
48    /// Coordinates of the lower left or lower right corner of the selection,
49    /// relative to the top left of the [`RenderEditable`] object.
50    pub point: Offset,
51    /// Direction of the text at this edge of the selection.
52    pub direction: Option<TextDirection>,
53}
54
55impl TextSelectionPoint {
56    /// Creates a description of a point in a text selection.
57    pub const fn new(point: Offset, direction: Option<TextDirection>) -> TextSelectionPoint {
58        TextSelectionPoint { point, direction }
59    }
60}
61
62impl fmt::Display for TextSelectionPoint {
63    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64        match self.direction {
65            Some(TextDirection::Ltr) => write!(f, "{:?}-ltr", self.point),
66            Some(TextDirection::Rtl) => write!(f, "{:?}-rtl", self.point),
67            None => write!(f, "{:?}", self.point),
68        }
69    }
70}
71
72/// Starts at a text position and walks vertically through line metrics.
73///
74/// [`is_valid`](Self::is_valid) compares the editable's layout generation, not
75/// Dart's `identical` on the metrics list.
76pub struct VerticalCaretMovementRun {
77    editable: RenderHandle<RenderEditable>,
78    layout_generation: u64,
79    current_offset: Offset,
80    current_line: i32,
81    current_text_position: TextPosition,
82    line_metrics: Vec<LineMetrics>,
83    position_cache: HashMap<i32, (Offset, TextPosition)>,
84    is_valid: bool,
85}
86
87impl VerticalCaretMovementRun {
88    /// Whether this run can still continue.
89    pub fn is_valid(&mut self, app: &App) -> bool {
90        if !self.is_valid {
91            return false;
92        }
93        if self.editable.get(app).layout_generation != self.layout_generation {
94            self.is_valid = false;
95        }
96        self.is_valid
97    }
98
99    /// The current text position.
100    pub fn current(&self) -> TextPosition {
101        debug_assert!(self.is_valid);
102        self.current_text_position
103    }
104
105    fn get_text_position_for_line(
106        &mut self,
107        app: &App,
108        line_number: i32,
109    ) -> (Offset, TextPosition) {
110        debug_assert!(self.is_valid);
111        debug_assert!(line_number >= 0);
112        if let Some(cached) = self.position_cache.get(&line_number) {
113            return *cached;
114        }
115        debug_assert!(line_number != self.current_line);
116        let new_offset = Offset::new(
117            self.current_offset.dx(),
118            self.line_metrics[line_number as usize].baseline,
119        );
120        let closest = self
121            .editable
122            .get(app)
123            .text_painter
124            .get_position_for_offset(new_offset);
125        self.position_cache
126            .insert(line_number, (new_offset, closest));
127        (new_offset, closest)
128    }
129
130    /// Move to the next line.
131    pub fn move_next(&mut self, app: &App) -> bool {
132        debug_assert!(self.is_valid(app));
133        if self.current_line + 1 >= self.line_metrics.len() as i32 {
134            return false;
135        }
136        let (offset, position) = self.get_text_position_for_line(app, self.current_line + 1);
137        self.current_line += 1;
138        self.current_offset = offset;
139        self.current_text_position = position;
140        true
141    }
142
143    /// Move back to the previous element.
144    pub fn move_previous(&mut self, app: &App) -> bool {
145        debug_assert!(self.is_valid(app));
146        if self.current_line <= 0 {
147            return false;
148        }
149        let (offset, position) = self.get_text_position_for_line(app, self.current_line - 1);
150        self.current_line -= 1;
151        self.current_offset = offset;
152        self.current_text_position = position;
153        true
154    }
155
156    /// Move forward or backward by a number of elements determined by pixel `offset`.
157    pub fn move_by_offset(&mut self, app: &App, offset: f64) -> bool {
158        let initial_offset = self.current_offset;
159        if offset >= 0.0 {
160            while self.current_offset.dy() < initial_offset.dy() + offset {
161                if !self.move_next(app) {
162                    break;
163                }
164            }
165        } else {
166            while self.current_offset.dy() > initial_offset.dy() + offset {
167                if !self.move_previous(app) {
168                    break;
169                }
170            }
171        }
172        initial_offset != self.current_offset
173    }
174}
175
176/// Displays some text in a scrollable container with a potentially blinking
177/// cursor and with gesture recognizers.
178///
179/// This is the renderer for an editable text field. It does not directly
180/// provide affordances for editing the text, but it does handle text selection
181/// and manipulation of the text cursor.
182pub struct RenderEditable {
183    render_object: RenderObjectData,
184    render_box: RenderBoxData,
185    container: ContainerRenderObjectData<AnyRenderBox>,
186    text_painter: TextPainter,
187    text_intrinsics: Option<TextPainter>,
188    // Placeholder dimensions representing the sizes of child inline widgets.
189    //
190    // These need to be cached because the text painter's placeholder dimensions
191    // will be overwritten during intrinsic width/height calculations and must be
192    // restored to the original values before final layout and painting.
193    placeholder_dimensions: Option<Vec<PlaceholderDimensions>>,
194    system_fonts: RelayoutWhenSystemFontsChangeData,
195    fonts_override: Option<Handle<FontCollection>>,
196    show_cursor: Handle<ValueNotifier<bool>>,
197    #[allow(dead_code)]
198    dispose_show_cursor: bool,
199    has_focus: bool,
200    start_handle_layer_link: Handle<LayerLink>,
201    end_handle_layer_link: Handle<LayerLink>,
202    max_lines: Option<i32>,
203    min_lines: Option<i32>,
204    expands: bool,
205    selection_color: Option<Color>,
206    selection: Option<TextSelection>,
207    offset: AnyViewportOffset,
208    /// Whether [`handle_event`](RenderObject::handle_event) will propagate pointer
209    /// events to selection handlers.
210    pub ignore_pointer: bool,
211    read_only: bool,
212    force_line: bool,
213    obscuring_character: String,
214    obscure_text: bool,
215    cursor_color: Option<Color>,
216    background_cursor_color: Option<Color>,
217    cursor_width: f64,
218    cursor_height: Option<f64>,
219    cursor_radius: Option<Radius>,
220    paint_cursor_above_text: bool,
221    cursor_offset: Offset,
222    device_pixel_ratio: f64,
223    selection_height_style: BoxHeightStyle,
224    selection_width_style: BoxWidthStyle,
225    enable_interactive_selection: Option<bool>,
226    prompt_rect_range: Option<TextRange>,
227    prompt_rect_color: Option<Color>,
228    clip_behavior: Clip,
229    clip_rect_layer: LayerHandle<Handle<ClipRectLayer>>,
230    leader_layer_handler: LayerHandle<Handle<LeaderLayer>>,
231    text_selection_delegate: AnyTextSelectionDelegate,
232    caret_prototype: Rect,
233    max_scroll_extent: f64,
234    last_tap_down_position: Option<Offset>,
235    last_secondary_tap_down_position: Option<Offset>,
236    layout_generation: u64,
237    selection_start_in_viewport: Handle<ValueNotifier<bool>>,
238    selection_end_in_viewport: Handle<ValueNotifier<bool>>,
239}
240
241impl RenderEditable {
242    /// Creates a render object that implements the visual aspects of a text field.
243    ///
244    /// Add the inline children with [`add`](ContainerRenderObjectMixin::add) or
245    /// [`add_all`](ContainerRenderObjectMixin::add_all); Dart's constructor takes them.
246    pub fn new(
247        app: &mut App,
248        text_direction: TextDirection,
249        start_handle_layer_link: Handle<LayerLink>,
250        end_handle_layer_link: Handle<LayerLink>,
251        offset: AnyViewportOffset,
252        text_selection_delegate: AnyTextSelectionDelegate,
253    ) -> RenderHandle<Self> {
254        let show_cursor = app.create(ValueNotifier::new(false));
255        let selection_start_in_viewport = app.create(ValueNotifier::new(true));
256        let selection_end_in_viewport = app.create(ValueNotifier::new(true));
257        let mut text_painter = TextPainter::new();
258        text_painter.set_text_direction(Some(text_direction));
259        text_painter.set_max_lines(Some(1));
260        RenderHandle::new_box(
261            app,
262            RenderEditable {
263                render_object: RenderObjectData::new(),
264                render_box: RenderBoxData::new(),
265                container: ContainerRenderObjectData::new(),
266                text_painter,
267                text_intrinsics: None,
268                placeholder_dimensions: None,
269                system_fonts: RelayoutWhenSystemFontsChangeData::new(),
270                fonts_override: None,
271                show_cursor,
272                dispose_show_cursor: true,
273                has_focus: false,
274                start_handle_layer_link,
275                end_handle_layer_link,
276                max_lines: Some(1),
277                min_lines: None,
278                expands: false,
279                selection_color: None,
280                selection: None,
281                offset,
282                ignore_pointer: false,
283                read_only: false,
284                force_line: true,
285                obscuring_character: "\u{2022}".into(),
286                obscure_text: false,
287                cursor_color: None,
288                background_cursor_color: None,
289                cursor_width: 1.0,
290                cursor_height: None,
291                cursor_radius: None,
292                paint_cursor_above_text: false,
293                cursor_offset: Offset::ZERO,
294                device_pixel_ratio: 1.0,
295                selection_height_style: BoxHeightStyle::Max,
296                selection_width_style: BoxWidthStyle::Max,
297                enable_interactive_selection: None,
298                prompt_rect_range: None,
299                prompt_rect_color: None,
300                clip_behavior: Clip::HardEdge,
301                clip_rect_layer: LayerHandle::new(),
302                leader_layer_handler: LayerHandle::new(),
303                text_selection_delegate,
304                caret_prototype: Rect::ZERO,
305                max_scroll_extent: 0.0,
306                last_tap_down_position: None,
307                last_secondary_tap_down_position: None,
308                layout_generation: 0,
309                selection_start_in_viewport,
310                selection_end_in_viewport,
311            },
312        )
313    }
314
315    fn fonts(self: RenderHandle<Self>, app: &mut App) -> Handle<FontCollection> {
316        match self.get(app).fonts_override {
317            Some(fonts) => fonts,
318            None => PaintingBinding::instance(app).fonts(app),
319        }
320    }
321
322    fn painter_with_fonts(
323        self: RenderHandle<Self>,
324        app: &mut App,
325    ) -> (&mut TextPainter, &mut FontCollection) {
326        let fonts = self.fonts(app);
327        let (this, fonts) = app.get_disjoint_mut(self.handle(), fonts);
328        (&mut this.text_painter, fonts)
329    }
330
331    fn text_intrinsics_with_fonts(
332        self: RenderHandle<Self>,
333        app: &mut App,
334    ) -> (&mut TextPainter, &mut FontCollection) {
335        let fonts = self.fonts(app);
336        let (this, fonts) = app.get_disjoint_mut(self.handle(), fonts);
337        let painter = &this.text_painter;
338        let intrinsics = this.text_intrinsics.get_or_insert_with(TextPainter::new);
339        intrinsics.set_text(painter.text().cloned());
340        intrinsics.set_text_align(painter.text_align());
341        intrinsics.set_text_direction(painter.text_direction());
342        intrinsics.set_text_scaler(painter.text_scaler().clone());
343        intrinsics.set_max_lines(painter.max_lines());
344        intrinsics.set_text_width_basis(painter.text_width_basis());
345        intrinsics.set_text_height_behavior(painter.text_height_behavior());
346        (intrinsics, fonts)
347    }
348
349    fn mark_needs_paint_listener(self: RenderHandle<Self>) -> Listener {
350        Listener::handle_method(self.handle(), offset_or_cursor_changed)
351    }
352
353    /// The text to display.
354    pub fn text(self: RenderHandle<Self>, app: &App) -> Option<InlineSpanRef> {
355        self.get(app).text_painter.text().cloned()
356    }
357
358    /// Sets [`text`](Self::text).
359    pub fn set_text(self: RenderHandle<Self>, app: &mut App, value: Option<InlineSpanRef>) {
360        let current = self.get(app).text_painter.text().cloned();
361        if current.as_ref().map(|s| s.as_ref() as *const _)
362            == value.as_ref().map(|s| s.as_ref() as *const _)
363        {
364            return;
365        }
366        self.get_mut(app).text_painter.set_text(value);
367        self.get_mut(app).text_intrinsics = None;
368        self.mark_needs_layout(app);
369    }
370
371    /// How the text should be aligned horizontally.
372    pub fn text_align(self: RenderHandle<Self>, app: &App) -> TextAlign {
373        self.get(app).text_painter.text_align()
374    }
375
376    /// Sets [`text_align`](Self::text_align).
377    pub fn set_text_align(self: RenderHandle<Self>, app: &mut App, value: TextAlign) {
378        if self.text_align(app) == value {
379            return;
380        }
381        self.get_mut(app).text_painter.set_text_align(value);
382        self.mark_needs_paint(app);
383    }
384
385    /// The directionality of the text.
386    pub fn text_direction(self: RenderHandle<Self>, app: &App) -> TextDirection {
387        self.get(app)
388            .text_painter
389            .text_direction()
390            .expect("an editable always has a direction")
391    }
392
393    /// Sets [`text_direction`](Self::text_direction).
394    pub fn set_text_direction(self: RenderHandle<Self>, app: &mut App, value: TextDirection) {
395        if self.get(app).text_painter.text_direction() == Some(value) {
396            return;
397        }
398        self.get_mut(app)
399            .text_painter
400            .set_text_direction(Some(value));
401        self.mark_needs_layout(app);
402    }
403
404    /// The color to use when painting the cursor.
405    pub fn cursor_color(self: RenderHandle<Self>, app: &App) -> Option<Color> {
406        self.get(app).cursor_color
407    }
408
409    /// Sets [`cursor_color`](Self::cursor_color).
410    pub fn set_cursor_color(self: RenderHandle<Self>, app: &mut App, value: Option<Color>) {
411        if self.get(app).cursor_color == value {
412            return;
413        }
414        self.get_mut(app).cursor_color = value;
415        self.mark_needs_paint(app);
416    }
417
418    /// The color to use when painting the background cursor aligned with the text
419    /// while rendering the floating cursor.
420    pub fn background_cursor_color(self: RenderHandle<Self>, app: &App) -> Option<Color> {
421        self.get(app).background_cursor_color
422    }
423
424    /// Sets [`background_cursor_color`](Self::background_cursor_color).
425    pub fn set_background_cursor_color(
426        self: RenderHandle<Self>,
427        app: &mut App,
428        value: Option<Color>,
429    ) {
430        if self.get(app).background_cursor_color == value {
431            return;
432        }
433        self.get_mut(app).background_cursor_color = value;
434        self.mark_needs_paint(app);
435    }
436
437    /// Whether to paint the cursor.
438    pub fn show_cursor(self: RenderHandle<Self>, app: &App) -> Handle<ValueNotifier<bool>> {
439        self.get(app).show_cursor
440    }
441
442    /// Sets [`show_cursor`](Self::show_cursor).
443    pub fn set_show_cursor(
444        self: RenderHandle<Self>,
445        app: &mut App,
446        value: Handle<ValueNotifier<bool>>,
447    ) {
448        let old = self.get(app).show_cursor;
449        if old == value {
450            return;
451        }
452        if self.attached(app) {
453            old.remove_listener(app, &self.mark_needs_paint_listener());
454        }
455        self.get_mut(app).show_cursor = value;
456        self.get_mut(app).dispose_show_cursor = false;
457        if self.attached(app) {
458            value.add_listener(app, self.mark_needs_paint_listener());
459        }
460        self.mark_needs_paint(app);
461    }
462
463    /// Whether the editable is currently focused.
464    pub fn has_focus(self: RenderHandle<Self>, app: &App) -> bool {
465        self.get(app).has_focus
466    }
467
468    /// Sets [`has_focus`](Self::has_focus).
469    pub fn set_has_focus(self: RenderHandle<Self>, app: &mut App, value: bool) {
470        if self.get(app).has_focus == value {
471            return;
472        }
473        self.get_mut(app).has_focus = value;
474    }
475
476    /// The [`LayerLink`] of start selection handle.
477    pub fn start_handle_layer_link(self: RenderHandle<Self>, app: &App) -> Handle<LayerLink> {
478        self.get(app).start_handle_layer_link
479    }
480
481    /// Sets [`start_handle_layer_link`](Self::start_handle_layer_link).
482    pub fn set_start_handle_layer_link(
483        self: RenderHandle<Self>,
484        app: &mut App,
485        value: Handle<LayerLink>,
486    ) {
487        if self.get(app).start_handle_layer_link == value {
488            return;
489        }
490        self.get_mut(app).start_handle_layer_link = value;
491        self.mark_needs_paint(app);
492    }
493
494    /// The [`LayerLink`] of end selection handle.
495    pub fn end_handle_layer_link(self: RenderHandle<Self>, app: &App) -> Handle<LayerLink> {
496        self.get(app).end_handle_layer_link
497    }
498
499    /// Sets [`end_handle_layer_link`](Self::end_handle_layer_link).
500    pub fn set_end_handle_layer_link(
501        self: RenderHandle<Self>,
502        app: &mut App,
503        value: Handle<LayerLink>,
504    ) {
505        if self.get(app).end_handle_layer_link == value {
506            return;
507        }
508        self.get_mut(app).end_handle_layer_link = value;
509        self.mark_needs_paint(app);
510    }
511
512    /// The maximum number of lines for the text to span, wrapping if necessary.
513    pub fn max_lines(self: RenderHandle<Self>, app: &App) -> Option<i32> {
514        self.get(app).max_lines
515    }
516
517    /// Sets [`max_lines`](Self::max_lines).
518    pub fn set_max_lines(self: RenderHandle<Self>, app: &mut App, value: Option<i32>) {
519        debug_assert!(value.is_none_or(|n| n > 0));
520        if self.get(app).max_lines == value {
521            return;
522        }
523        self.get_mut(app).max_lines = value;
524        self.get_mut(app)
525            .text_painter
526            .set_max_lines(if value == Some(1) { Some(1) } else { None });
527        self.get_mut(app).text_intrinsics = None;
528        self.mark_needs_layout(app);
529    }
530
531    /// The minimum number of lines to occupy when the content spans fewer lines.
532    pub fn min_lines(self: RenderHandle<Self>, app: &App) -> Option<i32> {
533        self.get(app).min_lines
534    }
535
536    /// Sets [`min_lines`](Self::min_lines).
537    pub fn set_min_lines(self: RenderHandle<Self>, app: &mut App, value: Option<i32>) {
538        debug_assert!(value.is_none_or(|n| n > 0));
539        if self.get(app).min_lines == value {
540            return;
541        }
542        self.get_mut(app).min_lines = value;
543        self.mark_needs_layout(app);
544    }
545
546    /// Whether this widget's height will be sized to fill its parent.
547    pub fn expands(self: RenderHandle<Self>, app: &App) -> bool {
548        self.get(app).expands
549    }
550
551    /// Sets [`expands`](Self::expands).
552    pub fn set_expands(self: RenderHandle<Self>, app: &mut App, value: bool) {
553        if self.get(app).expands == value {
554            return;
555        }
556        self.get_mut(app).expands = value;
557        self.mark_needs_layout(app);
558    }
559
560    /// The color to use when painting the selection.
561    pub fn selection_color(self: RenderHandle<Self>, app: &App) -> Option<Color> {
562        self.get(app).selection_color
563    }
564
565    /// Sets [`selection_color`](Self::selection_color).
566    pub fn set_selection_color(self: RenderHandle<Self>, app: &mut App, value: Option<Color>) {
567        if self.get(app).selection_color == value {
568            return;
569        }
570        self.get_mut(app).selection_color = value;
571        self.mark_needs_paint(app);
572    }
573
574    /// The font scaling strategy to use when laying out and rendering the text.
575    pub fn text_scaler(self: RenderHandle<Self>, app: &App) -> TextScaler {
576        self.get(app).text_painter.text_scaler().clone()
577    }
578
579    /// Sets [`text_scaler`](Self::text_scaler).
580    pub fn set_text_scaler(self: RenderHandle<Self>, app: &mut App, value: TextScaler) {
581        if self.get(app).text_painter.text_scaler() == &value {
582            return;
583        }
584        self.get_mut(app).text_painter.set_text_scaler(value);
585        self.get_mut(app).text_intrinsics = None;
586        self.mark_needs_layout(app);
587    }
588
589    /// The region of text that is selected, if any.
590    pub fn selection(self: RenderHandle<Self>, app: &App) -> Option<TextSelection> {
591        self.get(app).selection
592    }
593
594    /// Sets [`selection`](Self::selection).
595    pub fn set_selection(self: RenderHandle<Self>, app: &mut App, value: Option<TextSelection>) {
596        if self.get(app).selection == value {
597            return;
598        }
599        self.get_mut(app).selection = value;
600        self.mark_needs_paint(app);
601    }
602
603    /// The offset at which the text should be painted.
604    pub fn offset(self: RenderHandle<Self>, app: &App) -> AnyViewportOffset {
605        self.get(app).offset
606    }
607
608    /// Sets [`offset`](Self::offset).
609    pub fn set_offset(self: RenderHandle<Self>, app: &mut App, value: AnyViewportOffset) {
610        let old = self.get(app).offset;
611        if old == value {
612            return;
613        }
614        if self.attached(app) {
615            old.remove_listener(app, &self.mark_needs_paint_listener());
616        }
617        self.get_mut(app).offset = value;
618        if self.attached(app) {
619            value.add_listener(app, self.mark_needs_paint_listener());
620        }
621        self.mark_needs_layout(app);
622    }
623
624    /// Whether the text can be changed.
625    pub fn read_only(self: RenderHandle<Self>, app: &App) -> bool {
626        self.get(app).read_only
627    }
628
629    /// Sets [`read_only`](Self::read_only).
630    pub fn set_read_only(self: RenderHandle<Self>, app: &mut App, value: bool) {
631        if self.get(app).read_only == value {
632            return;
633        }
634        self.get_mut(app).read_only = value;
635    }
636
637    /// Whether this rendering object will take a full line regardless the text width.
638    pub fn force_line(self: RenderHandle<Self>, app: &App) -> bool {
639        self.get(app).force_line
640    }
641
642    /// Sets [`force_line`](Self::force_line).
643    pub fn set_force_line(self: RenderHandle<Self>, app: &mut App, value: bool) {
644        if self.get(app).force_line == value {
645            return;
646        }
647        self.get_mut(app).force_line = value;
648        self.mark_needs_layout(app);
649    }
650
651    /// {@macro dart.ui.textHeightBehavior}
652    pub fn text_height_behavior(self: RenderHandle<Self>, app: &App) -> Option<TextHeightBehavior> {
653        self.get(app).text_painter.text_height_behavior()
654    }
655
656    /// Sets [`text_height_behavior`](Self::text_height_behavior).
657    pub fn set_text_height_behavior(
658        self: RenderHandle<Self>,
659        app: &mut App,
660        value: Option<TextHeightBehavior>,
661    ) {
662        if self.text_height_behavior(app) == value {
663            return;
664        }
665        self.get_mut(app)
666            .text_painter
667            .set_text_height_behavior(value);
668        self.mark_needs_layout(app);
669    }
670
671    /// {@macro flutter.painting.textPainter.textWidthBasis}
672    pub fn text_width_basis(self: RenderHandle<Self>, app: &App) -> TextWidthBasis {
673        self.get(app).text_painter.text_width_basis()
674    }
675
676    /// Sets [`text_width_basis`](Self::text_width_basis).
677    pub fn set_text_width_basis(self: RenderHandle<Self>, app: &mut App, value: TextWidthBasis) {
678        if self.text_width_basis(app) == value {
679            return;
680        }
681        self.get_mut(app).text_painter.set_text_width_basis(value);
682        self.mark_needs_layout(app);
683    }
684
685    /// Character used for obscuring text if [`obscure_text`](Self::obscure_text) is true.
686    pub fn obscuring_character(self: RenderHandle<Self>, app: &App) -> &str {
687        &self.get(app).obscuring_character
688    }
689
690    /// Sets [`obscuring_character`](Self::obscuring_character).
691    pub fn set_obscuring_character(
692        self: RenderHandle<Self>,
693        app: &mut App,
694        value: impl Into<String>,
695    ) {
696        let value = value.into();
697        if self.get(app).obscuring_character == value {
698            return;
699        }
700        self.get_mut(app).obscuring_character = value;
701        self.mark_needs_layout(app);
702    }
703
704    /// Whether to hide the text being edited (e.g., for passwords).
705    pub fn obscure_text(self: RenderHandle<Self>, app: &App) -> bool {
706        self.get(app).obscure_text
707    }
708
709    /// Sets [`obscure_text`](Self::obscure_text).
710    pub fn set_obscure_text(self: RenderHandle<Self>, app: &mut App, value: bool) {
711        if self.get(app).obscure_text == value {
712            return;
713        }
714        self.get_mut(app).obscure_text = value;
715    }
716
717    /// How wide the cursor is.
718    pub fn cursor_width(self: RenderHandle<Self>, app: &App) -> f64 {
719        self.get(app).cursor_width
720    }
721
722    /// Sets [`cursor_width`](Self::cursor_width).
723    pub fn set_cursor_width(self: RenderHandle<Self>, app: &mut App, value: f64) {
724        if self.get(app).cursor_width == value {
725            return;
726        }
727        self.get_mut(app).cursor_width = value;
728        self.mark_needs_layout(app);
729    }
730
731    /// How tall the cursor will be.
732    pub fn cursor_height(self: RenderHandle<Self>, app: &mut App) -> f64 {
733        self.get(app)
734            .cursor_height
735            .unwrap_or_else(|| self.preferred_line_height(app))
736    }
737
738    /// Sets the explicit cursor height; `None` uses [`preferred_line_height`](Self::preferred_line_height).
739    pub fn set_cursor_height(self: RenderHandle<Self>, app: &mut App, value: Option<f64>) {
740        if self.get(app).cursor_height == value {
741            return;
742        }
743        self.get_mut(app).cursor_height = value;
744        self.mark_needs_layout(app);
745    }
746
747    /// The radius of the cursor.
748    pub fn cursor_radius(self: RenderHandle<Self>, app: &App) -> Option<Radius> {
749        self.get(app).cursor_radius
750    }
751
752    /// Sets [`cursor_radius`](Self::cursor_radius).
753    pub fn set_cursor_radius(self: RenderHandle<Self>, app: &mut App, value: Option<Radius>) {
754        if self.get(app).cursor_radius == value {
755            return;
756        }
757        self.get_mut(app).cursor_radius = value;
758        self.mark_needs_paint(app);
759    }
760
761    /// Whether the cursor paints above the text.
762    pub fn paint_cursor_above_text(self: RenderHandle<Self>, app: &App) -> bool {
763        self.get(app).paint_cursor_above_text
764    }
765
766    /// Sets [`paint_cursor_above_text`](Self::paint_cursor_above_text).
767    pub fn set_paint_cursor_above_text(self: RenderHandle<Self>, app: &mut App, value: bool) {
768        if self.get(app).paint_cursor_above_text == value {
769            return;
770        }
771        self.get_mut(app).paint_cursor_above_text = value;
772        self.mark_needs_paint(app);
773    }
774
775    /// The offset that is used, in pixels, when painting the cursor on screen.
776    pub fn cursor_offset(self: RenderHandle<Self>, app: &App) -> Offset {
777        self.get(app).cursor_offset
778    }
779
780    /// Sets [`cursor_offset`](Self::cursor_offset).
781    pub fn set_cursor_offset(self: RenderHandle<Self>, app: &mut App, value: Offset) {
782        if self.get(app).cursor_offset == value {
783            return;
784        }
785        self.get_mut(app).cursor_offset = value;
786        self.mark_needs_paint(app);
787    }
788
789    /// The pixel ratio of the current device.
790    pub fn device_pixel_ratio(self: RenderHandle<Self>, app: &App) -> f64 {
791        self.get(app).device_pixel_ratio
792    }
793
794    /// Sets [`device_pixel_ratio`](Self::device_pixel_ratio).
795    pub fn set_device_pixel_ratio(self: RenderHandle<Self>, app: &mut App, value: f64) {
796        if self.get(app).device_pixel_ratio == value {
797            return;
798        }
799        self.get_mut(app).device_pixel_ratio = value;
800        self.mark_needs_layout(app);
801    }
802
803    /// Controls how tall the selection highlight boxes are computed to be.
804    pub fn selection_height_style(self: RenderHandle<Self>, app: &App) -> BoxHeightStyle {
805        self.get(app).selection_height_style
806    }
807
808    /// Sets [`selection_height_style`](Self::selection_height_style).
809    pub fn set_selection_height_style(
810        self: RenderHandle<Self>,
811        app: &mut App,
812        value: BoxHeightStyle,
813    ) {
814        if self.get(app).selection_height_style == value {
815            return;
816        }
817        self.get_mut(app).selection_height_style = value;
818        self.mark_needs_paint(app);
819    }
820
821    /// Controls how wide the selection highlight boxes are computed to be.
822    pub fn selection_width_style(self: RenderHandle<Self>, app: &App) -> BoxWidthStyle {
823        self.get(app).selection_width_style
824    }
825
826    /// Sets [`selection_width_style`](Self::selection_width_style).
827    pub fn set_selection_width_style(
828        self: RenderHandle<Self>,
829        app: &mut App,
830        value: BoxWidthStyle,
831    ) {
832        if self.get(app).selection_width_style == value {
833            return;
834        }
835        self.get_mut(app).selection_width_style = value;
836        self.mark_needs_paint(app);
837    }
838
839    /// Whether to allow the user to change the selection.
840    pub fn enable_interactive_selection(self: RenderHandle<Self>, app: &App) -> Option<bool> {
841        self.get(app).enable_interactive_selection
842    }
843
844    /// Sets [`enable_interactive_selection`](Self::enable_interactive_selection).
845    pub fn set_enable_interactive_selection(
846        self: RenderHandle<Self>,
847        app: &mut App,
848        value: Option<bool>,
849    ) {
850        if self.get(app).enable_interactive_selection == value {
851            return;
852        }
853        self.get_mut(app).enable_interactive_selection = value;
854        self.mark_needs_paint(app);
855    }
856
857    /// The [`TextSelectionDelegate`](inset_services::TextSelectionDelegate) for
858    /// this [`RenderEditable`].
859    pub fn text_selection_delegate(
860        self: RenderHandle<Self>,
861        app: &App,
862    ) -> AnyTextSelectionDelegate {
863        self.get(app).text_selection_delegate
864    }
865
866    /// Sets [`text_selection_delegate`](Self::text_selection_delegate).
867    pub fn set_text_selection_delegate(
868        self: RenderHandle<Self>,
869        app: &mut App,
870        value: AnyTextSelectionDelegate,
871    ) {
872        self.get_mut(app).text_selection_delegate = value;
873    }
874
875    /// The color of the prompt rectangle for a pending autocorrection.
876    pub fn prompt_rect_color(self: RenderHandle<Self>, app: &App) -> Option<Color> {
877        self.get(app).prompt_rect_color
878    }
879
880    /// Sets [`prompt_rect_color`](Self::prompt_rect_color).
881    pub fn set_prompt_rect_color(self: RenderHandle<Self>, app: &mut App, value: Option<Color>) {
882        if self.get(app).prompt_rect_color == value {
883            return;
884        }
885        self.get_mut(app).prompt_rect_color = value;
886        self.mark_needs_paint(app);
887    }
888
889    /// Sets the range of text that will be changed by a pending autocorrection.
890    pub fn set_prompt_rect_range(
891        self: RenderHandle<Self>,
892        app: &mut App,
893        value: Option<TextRange>,
894    ) {
895        if self.get(app).prompt_rect_range == value {
896            return;
897        }
898        self.get_mut(app).prompt_rect_range = value;
899        self.mark_needs_paint(app);
900    }
901
902    /// The content will be clipped (or not) according to this option.
903    pub fn clip_behavior(self: RenderHandle<Self>, app: &App) -> Clip {
904        self.get(app).clip_behavior
905    }
906
907    /// Sets [`clip_behavior`](Self::clip_behavior).
908    pub fn set_clip_behavior(self: RenderHandle<Self>, app: &mut App, value: Clip) {
909        if self.get(app).clip_behavior == value {
910            return;
911        }
912        self.get_mut(app).clip_behavior = value;
913        self.mark_needs_paint(app);
914    }
915
916    /// Returns a list of rects that bound the given selection, and the text
917    /// direction. The text direction is used by the engine to calculate
918    /// the closest position to a given point.
919    ///
920    /// See [`TextPainter::get_boxes_for_selection`] for more details.
921    pub fn get_boxes_for_selection(
922        self: RenderHandle<Self>,
923        app: &mut App,
924        selection: TextSelection,
925    ) -> Vec<TextBox> {
926        self.compute_text_metrics_if_needed(app);
927        let paint_offset = self.paint_offset(app);
928        let this = self.get(app);
929        this.text_painter
930            .get_boxes_for_selection(
931                selection,
932                this.selection_height_style,
933                this.selection_width_style,
934            )
935            .into_iter()
936            .map(|text_box| {
937                TextBox::from_ltrbd(
938                    text_box.left + paint_offset.dx(),
939                    text_box.top + paint_offset.dy(),
940                    text_box.right + paint_offset.dx(),
941                    text_box.bottom + paint_offset.dy(),
942                    text_box.direction,
943                )
944            })
945            .collect()
946    }
947
948    /// Track whether the start of the selection is currently in the visible
949    /// viewport.
950    pub fn selection_start_in_viewport(
951        self: RenderHandle<Self>,
952        app: &App,
953    ) -> Handle<ValueNotifier<bool>> {
954        self.get(app).selection_start_in_viewport
955    }
956
957    /// Track whether the end of the selection is currently in the visible
958    /// viewport.
959    pub fn selection_end_in_viewport(
960        self: RenderHandle<Self>,
961        app: &App,
962    ) -> Handle<ValueNotifier<bool>> {
963        self.get(app).selection_end_in_viewport
964    }
965
966    fn update_selection_extents_visibility(
967        self: RenderHandle<Self>,
968        app: &mut App,
969        effective_offset: Offset,
970    ) {
971        let Some(selection) = self.selection(app) else {
972            return;
973        };
974        let start_notifier = self.get(app).selection_start_in_viewport;
975        let end_notifier = self.get(app).selection_end_in_viewport;
976        if !selection.is_valid() {
977            start_notifier.set_value(app, false);
978            end_notifier.set_value(app, false);
979            return;
980        }
981        let visible_region = Offset::ZERO & self.size(app);
982        let caret_prototype = self.get(app).caret_prototype;
983        let start = TextPosition::with_affinity(selection.start(), selection.affinity);
984        let end = TextPosition::with_affinity(selection.end(), selection.affinity);
985        let (start_offset, end_offset) = {
986            let (painter, fonts) = self.painter_with_fonts(app);
987            (
988                painter.get_offset_for_caret(fonts, start, caret_prototype),
989                painter.get_offset_for_caret(fonts, end, caret_prototype),
990            )
991        };
992        const VISIBLE_REGION_SLOP: f64 = 0.5;
993        let region = visible_region.inflate(VISIBLE_REGION_SLOP);
994        start_notifier.set_value(app, region.contains(start_offset + effective_offset));
995        end_notifier.set_value(app, region.contains(end_offset + effective_offset));
996    }
997
998    /// If [`obscure_text`](Self::obscure_text) is true, returns the obscured text.
999    pub fn plain_text(self: RenderHandle<Self>, app: &mut App) -> String {
1000        self.get_mut(app).text_painter.plain_text().to_owned()
1001    }
1002
1003    /// An estimate of the height of a line in the text.
1004    pub fn preferred_line_height(self: RenderHandle<Self>, app: &mut App) -> f64 {
1005        let (painter, fonts) = self.painter_with_fonts(app);
1006        painter.preferred_line_height(fonts)
1007    }
1008
1009    fn caret_margin(self: RenderHandle<Self>, app: &App) -> f64 {
1010        K_CARET_GAP + self.get(app).cursor_width
1011    }
1012
1013    fn is_multiline(self: RenderHandle<Self>, app: &App) -> bool {
1014        self.get(app).max_lines != Some(1)
1015    }
1016
1017    fn paint_offset(self: RenderHandle<Self>, app: &App) -> Offset {
1018        let pixels = self.get(app).offset.pixels(app);
1019        if self.is_multiline(app) {
1020            Offset::new(0.0, -pixels)
1021        } else {
1022            Offset::new(-pixels, 0.0)
1023        }
1024    }
1025
1026    fn viewport_extent(self: RenderHandle<Self>, app: &App) -> f64 {
1027        let size = self.size(app);
1028        if self.is_multiline(app) {
1029            size.height()
1030        } else {
1031            size.width()
1032        }
1033    }
1034
1035    fn get_max_scroll_extent(self: RenderHandle<Self>, app: &App, content_size: Size) -> f64 {
1036        let size = self.size(app);
1037        if self.is_multiline(app) {
1038            (content_size.height() - size.height()).max(0.0)
1039        } else {
1040            (content_size.width() - size.width()).max(0.0)
1041        }
1042    }
1043
1044    fn has_visual_overflow(self: RenderHandle<Self>, app: &App) -> bool {
1045        self.get(app).max_scroll_extent > 0.0 || self.paint_offset(app) != Offset::ZERO
1046    }
1047
1048    fn adjust_constraints(
1049        self: RenderHandle<Self>,
1050        app: &App,
1051        min_width: f64,
1052        max_width: f64,
1053    ) -> (f64, f64) {
1054        let available_max_width = (max_width - self.caret_margin(app)).max(0.0);
1055        let available_min_width = min_width.min(available_max_width);
1056        let min = if self.get(app).force_line {
1057            available_max_width
1058        } else {
1059            available_min_width
1060        };
1061        let max = if self.is_multiline(app) {
1062            available_max_width
1063        } else {
1064            f64::INFINITY
1065        };
1066        (min, max)
1067    }
1068
1069    fn compute_text_metrics_if_needed(self: RenderHandle<Self>, app: &mut App) {
1070        let constraints = self.constraints(app);
1071        let (min_width, max_width) =
1072            self.adjust_constraints(app, constraints.min_width, constraints.max_width);
1073        let (painter, fonts) = self.painter_with_fonts(app);
1074        painter.layout(fonts, min_width, max_width);
1075    }
1076
1077    fn compute_caret_prototype(self: RenderHandle<Self>, app: &mut App) {
1078        let cursor_width = self.get(app).cursor_width;
1079        let cursor_height = self.cursor_height(app);
1080        let prototype = match app.platform().target_platform() {
1081            TargetPlatform::IOS | TargetPlatform::MacOS => {
1082                Rect::from_ltwh(0.0, 0.0, cursor_width, cursor_height + 2.0)
1083            }
1084            TargetPlatform::Android
1085            | TargetPlatform::Fuchsia
1086            | TargetPlatform::Linux
1087            | TargetPlatform::Windows => Rect::from_ltwh(
1088                0.0,
1089                K_CARET_HEIGHT_OFFSET,
1090                cursor_width,
1091                cursor_height - 2.0 * K_CARET_HEIGHT_OFFSET,
1092            ),
1093        };
1094        self.get_mut(app).caret_prototype = prototype;
1095    }
1096
1097    fn snap_to_physical_pixel(
1098        self: RenderHandle<Self>,
1099        app: &App,
1100        source_offset: Offset,
1101    ) -> Offset {
1102        let global = self.as_box().local_to_global(app, source_offset, None);
1103        let pixel_multiple = 1.0 / self.get(app).device_pixel_ratio;
1104        Offset::new(
1105            if global.dx().is_finite() {
1106                (global.dx() / pixel_multiple).round() * pixel_multiple - global.dx()
1107            } else {
1108                0.0
1109            },
1110            if global.dy().is_finite() {
1111                (global.dy() / pixel_multiple).round() * pixel_multiple - global.dy()
1112            } else {
1113                0.0
1114            },
1115        )
1116    }
1117
1118    /// Returns the local coordinates of the endpoints of the given selection.
1119    pub fn get_endpoints_for_selection(
1120        self: RenderHandle<Self>,
1121        app: &mut App,
1122        selection: TextSelection,
1123    ) -> Vec<TextSelectionPoint> {
1124        self.compute_text_metrics_if_needed(app);
1125        let paint_offset = self.paint_offset(app);
1126        let height_style = self.get(app).selection_height_style;
1127        let width_style = self.get(app).selection_width_style;
1128        let boxes = if selection.is_collapsed() {
1129            Vec::new()
1130        } else {
1131            self.get(app)
1132                .text_painter
1133                .get_boxes_for_selection(selection, height_style, width_style)
1134        };
1135        if boxes.is_empty() {
1136            let caret_prototype = self.get(app).caret_prototype;
1137            let (painter, fonts) = self.painter_with_fonts(app);
1138            let caret_offset =
1139                painter.get_offset_for_caret(fonts, selection.extent(), caret_prototype);
1140            let line_height = painter.preferred_line_height(fonts);
1141            let start = Offset::new(0.0, line_height) + caret_offset + paint_offset;
1142            return vec![TextSelectionPoint::new(start, None)];
1143        }
1144        let painter_width = self.get(app).text_painter.size().width();
1145        let start = Offset::new(
1146            clamp_double(boxes[0].start(), 0.0, painter_width),
1147            boxes[0].bottom,
1148        ) + paint_offset;
1149        let last = boxes[boxes.len() - 1];
1150        let end =
1151            Offset::new(clamp_double(last.end(), 0.0, painter_width), last.bottom) + paint_offset;
1152        vec![
1153            TextSelectionPoint::new(start, Some(boxes[0].direction)),
1154            TextSelectionPoint::new(end, Some(last.direction)),
1155        ]
1156    }
1157
1158    /// Returns the smallest [`Rect`], in the local coordinate system, that covers
1159    /// the text within the [`TextRange`] specified.
1160    pub fn get_rect_for_composing_range(
1161        self: RenderHandle<Self>,
1162        app: &mut App,
1163        range: TextRange,
1164    ) -> Option<Rect> {
1165        if !range.is_valid() || range.is_collapsed() {
1166            return None;
1167        }
1168        self.compute_text_metrics_if_needed(app);
1169        let boxes = self.get(app).text_painter.get_boxes_for_selection(
1170            TextSelection::new(range.start, range.end),
1171            self.get(app).selection_height_style,
1172            self.get(app).selection_width_style,
1173        );
1174        if boxes.is_empty() {
1175            return None;
1176        }
1177        let paint_offset = self.paint_offset(app);
1178        let mut rect = boxes[0].to_rect();
1179        for box_ in boxes.iter().skip(1) {
1180            rect = rect.expand_to_include(box_.to_rect());
1181        }
1182        Some(rect.shift(paint_offset))
1183    }
1184
1185    /// Returns the [`TextPosition`] closest to the given global point.
1186    pub fn get_position_for_point(
1187        self: RenderHandle<Self>,
1188        app: &mut App,
1189        global_position: Offset,
1190    ) -> TextPosition {
1191        self.compute_text_metrics_if_needed(app);
1192        let local =
1193            self.as_box().global_to_local(app, global_position, None) - self.paint_offset(app);
1194        self.get(app).text_painter.get_position_for_offset(local)
1195    }
1196
1197    /// Returns the [`Rect`] in local coordinates for the caret at the given text
1198    /// position.
1199    pub fn get_local_rect_for_caret(
1200        self: RenderHandle<Self>,
1201        app: &mut App,
1202        caret_position: TextPosition,
1203    ) -> Rect {
1204        self.compute_text_metrics_if_needed(app);
1205        let caret_prototype = self.get(app).caret_prototype;
1206        let cursor_offset = self.get(app).cursor_offset;
1207        let (painter, fonts) = self.painter_with_fonts(app);
1208        let caret_offset = painter.get_offset_for_caret(fonts, caret_position, caret_prototype);
1209        let mut caret_rect = caret_prototype.shift(caret_offset + cursor_offset);
1210        let painter_width = painter.width();
1211        let size = self.size(app);
1212        let caret_margin = self.caret_margin(app);
1213        let scrollable_width = (painter_width + caret_margin).max(size.width());
1214        let caret_x = clamp_double(
1215            caret_rect.left,
1216            0.0,
1217            (scrollable_width - caret_margin).max(0.0),
1218        );
1219        caret_rect = Offset::new(caret_x, caret_rect.top) & caret_rect.size();
1220        let (painter, fonts) = self.painter_with_fonts(app);
1221        let full_height = painter.get_full_height_for_caret(fonts, caret_position, caret_prototype);
1222        caret_rect = match app.platform().target_platform() {
1223            TargetPlatform::IOS | TargetPlatform::MacOS => {
1224                let height_diff = full_height - caret_rect.height();
1225                Rect::from_ltwh(
1226                    caret_rect.left,
1227                    caret_rect.top + height_diff / 2.0,
1228                    caret_rect.width(),
1229                    caret_rect.height(),
1230                )
1231            }
1232            TargetPlatform::Android
1233            | TargetPlatform::Fuchsia
1234            | TargetPlatform::Linux
1235            | TargetPlatform::Windows => {
1236                let caret_height = self.cursor_height(app);
1237                let height_diff = full_height - caret_height;
1238                Rect::from_ltwh(
1239                    caret_rect.left,
1240                    caret_rect.top - K_CARET_HEIGHT_OFFSET + height_diff / 2.0,
1241                    caret_rect.width(),
1242                    caret_height,
1243                )
1244            }
1245        };
1246        caret_rect = caret_rect.shift(self.paint_offset(app));
1247        caret_rect.shift(self.snap_to_physical_pixel(app, caret_rect.top_left()))
1248    }
1249
1250    fn set_text_editing_value(
1251        self: RenderHandle<Self>,
1252        app: &mut App,
1253        new_value: inset_embedder::TextEditingValue,
1254        cause: SelectionChangedCause,
1255    ) {
1256        self.get(app)
1257            .text_selection_delegate
1258            .user_update_text_editing_value(app, new_value, cause);
1259    }
1260
1261    fn apply_selection(
1262        self: RenderHandle<Self>,
1263        app: &mut App,
1264        mut next_selection: TextSelection,
1265        cause: SelectionChangedCause,
1266    ) {
1267        if next_selection.is_valid() {
1268            let text_length = utf16_len(
1269                &self
1270                    .get(app)
1271                    .text_selection_delegate
1272                    .text_editing_value(app)
1273                    .text,
1274            );
1275            next_selection = next_selection
1276                .copy_with()
1277                .base_offset(next_selection.base_offset.min(text_length))
1278                .extent_offset(next_selection.extent_offset.min(text_length));
1279        }
1280        let value = self
1281            .get(app)
1282            .text_selection_delegate
1283            .text_editing_value(app)
1284            .copy_with()
1285            .selection(next_selection);
1286        self.set_text_editing_value(app, value, cause);
1287    }
1288
1289    /// If [`ignore_pointer`](Self::ignore_pointer) is false then this method is called by
1290    /// the internal gesture recognizer. When it is true, an ancestor must call it.
1291    pub fn handle_tap_down(self: RenderHandle<Self>, _app: &mut App, details: &TapDownDetails) {
1292        self.get_mut(_app).last_tap_down_position = Some(details.global_position);
1293    }
1294
1295    /// The position of the most recent secondary tap down event on this text
1296    /// input.
1297    pub fn last_secondary_tap_down_position(self: RenderHandle<Self>, app: &App) -> Option<Offset> {
1298        self.get(app).last_secondary_tap_down_position
1299    }
1300
1301    /// Tracks the position of a secondary tap event.
1302    ///
1303    /// Should be called before attempting to change the selection based on the
1304    /// position of a secondary tap.
1305    pub fn handle_secondary_tap_down(
1306        self: RenderHandle<Self>,
1307        app: &mut App,
1308        details: &TapDownDetails,
1309    ) {
1310        self.get_mut(app).last_tap_down_position = Some(details.global_position);
1311        self.get_mut(app).last_secondary_tap_down_position = Some(details.global_position);
1312    }
1313
1314    /// Select the word at the last tap-down, or the position if the cause is a tap.
1315    pub fn handle_tap(self: RenderHandle<Self>, app: &mut App) {
1316        self.select_position(app, SelectionChangedCause::Tap);
1317    }
1318
1319    /// Select the word around the last tap-down.
1320    pub fn handle_double_tap(self: RenderHandle<Self>, app: &mut App) {
1321        self.select_word(app, SelectionChangedCause::DoubleTap);
1322    }
1323
1324    /// Select the word around the last long-press.
1325    pub fn handle_long_press(self: RenderHandle<Self>, app: &mut App) {
1326        self.select_word(app, SelectionChangedCause::LongPress);
1327    }
1328
1329    /// Move the selection to the location of the last tap down.
1330    pub fn select_position(self: RenderHandle<Self>, app: &mut App, cause: SelectionChangedCause) {
1331        let from = self
1332            .get(app)
1333            .last_tap_down_position
1334            .expect("select_position needs a tap-down");
1335        self.select_position_at(app, from, None, cause);
1336    }
1337
1338    /// Select text between the global positions `from` and `to`.
1339    pub fn select_position_at(
1340        self: RenderHandle<Self>,
1341        app: &mut App,
1342        from: Offset,
1343        to: Option<Offset>,
1344        cause: SelectionChangedCause,
1345    ) {
1346        self.compute_text_metrics_if_needed(app);
1347        let paint_offset = self.paint_offset(app);
1348        let from_position = self
1349            .get(app)
1350            .text_painter
1351            .get_position_for_offset(self.as_box().global_to_local(app, from, None) - paint_offset);
1352        let to_position = to.map(|to| {
1353            self.get(app).text_painter.get_position_for_offset(
1354                self.as_box().global_to_local(app, to, None) - paint_offset,
1355            )
1356        });
1357        let new_selection = TextSelection {
1358            base_offset: from_position.offset,
1359            extent_offset: to_position.unwrap_or(from_position).offset,
1360            affinity: from_position.affinity,
1361            is_directional: false,
1362        };
1363        self.apply_selection(app, new_selection, cause);
1364    }
1365
1366    /// Select a word around the location of the last tap down.
1367    pub fn select_word(self: RenderHandle<Self>, app: &mut App, cause: SelectionChangedCause) {
1368        let from = self
1369            .get(app)
1370            .last_tap_down_position
1371            .expect("select_word needs a tap-down");
1372        self.select_words_in_range(app, from, None, cause);
1373    }
1374
1375    /// Selects the set words of a paragraph that intersect a given range of global positions.
1376    pub fn select_words_in_range(
1377        self: RenderHandle<Self>,
1378        app: &mut App,
1379        from: Offset,
1380        to: Option<Offset>,
1381        cause: SelectionChangedCause,
1382    ) {
1383        self.compute_text_metrics_if_needed(app);
1384        let paint_offset = self.paint_offset(app);
1385        let from_position = self
1386            .get(app)
1387            .text_painter
1388            .get_position_for_offset(self.as_box().global_to_local(app, from, None) - paint_offset);
1389        let from_word = self.get_word_at_offset(app, from_position);
1390        let to_position = match to {
1391            None => from_position,
1392            Some(to) => self.get(app).text_painter.get_position_for_offset(
1393                self.as_box().global_to_local(app, to, None) - paint_offset,
1394            ),
1395        };
1396        let to_word = if to_position == from_position {
1397            from_word
1398        } else {
1399            self.get_word_at_offset(app, to_position)
1400        };
1401        let is_from_word_before_to_word = from_word.start() < to_word.end();
1402        self.apply_selection(
1403            app,
1404            TextSelection {
1405                base_offset: if is_from_word_before_to_word {
1406                    from_word.base().offset
1407                } else {
1408                    from_word.extent().offset
1409                },
1410                extent_offset: if is_from_word_before_to_word {
1411                    to_word.extent().offset
1412                } else {
1413                    to_word.base().offset
1414                },
1415                affinity: from_word.affinity,
1416                is_directional: false,
1417            },
1418            cause,
1419        );
1420    }
1421
1422    /// Move the selection to the beginning or end of a word.
1423    pub fn select_word_edge(self: RenderHandle<Self>, app: &mut App, cause: SelectionChangedCause) {
1424        self.compute_text_metrics_if_needed(app);
1425        let from = self
1426            .get(app)
1427            .last_tap_down_position
1428            .expect("select_word_edge needs a tap-down");
1429        let paint_offset = self.paint_offset(app);
1430        let position = self
1431            .get(app)
1432            .text_painter
1433            .get_position_for_offset(self.as_box().global_to_local(app, from, None) - paint_offset);
1434        let word = self.get(app).text_painter.get_word_boundary(position);
1435        let new_selection = if position.offset <= word.start {
1436            TextSelection::collapsed(word.start, TextAffinity::Downstream)
1437        } else {
1438            TextSelection::collapsed(word.end, TextAffinity::Upstream)
1439        };
1440        self.apply_selection(app, new_selection, cause);
1441    }
1442
1443    /// Returns a [`TextSelection`] that encompasses the word at the given
1444    /// [`TextPosition`].
1445    pub fn get_word_at_offset(
1446        self: RenderHandle<Self>,
1447        app: &mut App,
1448        position: TextPosition,
1449    ) -> TextSelection {
1450        let plain = self.plain_text(app);
1451        let length = utf16_len(&plain);
1452        if position.offset >= length {
1453            return TextSelection::from_position(TextPosition::with_affinity(
1454                length,
1455                TextAffinity::Upstream,
1456            ));
1457        }
1458        if self.get(app).obscure_text {
1459            return TextSelection::new(0, length);
1460        }
1461        let word = self.get(app).text_painter.get_word_boundary(position);
1462        let effective_offset = match position.affinity {
1463            TextAffinity::Upstream => position.offset - 1,
1464            TextAffinity::Downstream => position.offset,
1465        };
1466        debug_assert!(effective_offset >= 0);
1467        if effective_offset > 0
1468            && code_unit_at(&plain, effective_offset).is_some_and(TextLayoutMetrics::is_whitespace)
1469        {
1470            if let Some(previous_word) = self.get_previous_word(app, word.start) {
1471                match app.platform().target_platform() {
1472                    TargetPlatform::IOS => {
1473                        return TextSelection::new(previous_word.start, position.offset);
1474                    }
1475                    TargetPlatform::Android if self.get(app).read_only => {
1476                        return TextSelection::new(previous_word.start, position.offset);
1477                    }
1478                    _ => {}
1479                }
1480            } else {
1481                match app.platform().target_platform() {
1482                    TargetPlatform::IOS => {
1483                        if let Some(next_word) = self.get_next_word(app, word.start) {
1484                            return TextSelection::new(position.offset, next_word.end);
1485                        }
1486                        return TextSelection::collapsed(position.offset, position.affinity);
1487                    }
1488                    TargetPlatform::Android if self.get(app).read_only => {
1489                        return TextSelection::new(position.offset, position.offset + 1);
1490                    }
1491                    _ => {}
1492                }
1493            }
1494        }
1495        TextSelection::new(word.start, word.end)
1496    }
1497
1498    fn get_next_word(
1499        self: RenderHandle<Self>,
1500        app: &mut App,
1501        mut offset: i32,
1502    ) -> Option<TextRange> {
1503        loop {
1504            let range = self
1505                .get(app)
1506                .text_painter
1507                .get_word_boundary(TextPosition::new(offset));
1508            if !range.is_valid() || range.is_collapsed() {
1509                return None;
1510            }
1511            if !self.only_whitespace(app, range) {
1512                return Some(range);
1513            }
1514            offset = range.end;
1515        }
1516    }
1517
1518    fn get_previous_word(
1519        self: RenderHandle<Self>,
1520        app: &mut App,
1521        mut offset: i32,
1522    ) -> Option<TextRange> {
1523        while offset >= 0 {
1524            let range = self
1525                .get(app)
1526                .text_painter
1527                .get_word_boundary(TextPosition::new(offset));
1528            if !range.is_valid() || range.is_collapsed() {
1529                return None;
1530            }
1531            if !self.only_whitespace(app, range) {
1532                return Some(range);
1533            }
1534            offset = range.start - 1;
1535        }
1536        None
1537    }
1538
1539    fn only_whitespace(self: RenderHandle<Self>, app: &mut App, range: TextRange) -> bool {
1540        let plain = self.plain_text(app);
1541        for i in range.start..range.end {
1542            if let Some(unit) = code_unit_at(&plain, i)
1543                && !TextLayoutMetrics::is_whitespace(unit)
1544            {
1545                return false;
1546            }
1547        }
1548        true
1549    }
1550
1551    /// Return a [`TextSelection`] containing the line of the given [`TextPosition`].
1552    pub fn get_line_at_offset(
1553        self: RenderHandle<Self>,
1554        app: &mut App,
1555        position: TextPosition,
1556    ) -> TextSelection {
1557        self.compute_text_metrics_if_needed(app);
1558        let line = self.get(app).text_painter.get_line_boundary(position);
1559        if self.get(app).obscure_text {
1560            return TextSelection::new(0, utf16_len(&self.plain_text(app)));
1561        }
1562        TextSelection::new(line.start, line.end)
1563    }
1564
1565    /// A [`WordBoundary`] over this render object's text and layout.
1566    ///
1567    /// Dart's `RenderEditable.wordBoundaries`, which forwards to the text painter's.
1568    pub fn word_boundaries(self: RenderHandle<Self>, app: &App) -> WordBoundary {
1569        WordBoundary::new(
1570            self.text(app).expect("RenderEditable always has text"),
1571            self,
1572        )
1573    }
1574
1575    /// See `TextPainter.get_word_boundary`.
1576    pub fn get_word_boundary(
1577        self: RenderHandle<Self>,
1578        app: &mut App,
1579        position: TextPosition,
1580    ) -> TextRange {
1581        self.compute_text_metrics_if_needed(app);
1582        self.get(app).text_painter.get_word_boundary(position)
1583    }
1584
1585    /// Returns the TextPosition above the given offset into the text.
1586    pub fn get_text_position_above(
1587        self: RenderHandle<Self>,
1588        app: &mut App,
1589        position: TextPosition,
1590    ) -> TextPosition {
1591        self.compute_text_metrics_if_needed(app);
1592        let line_height = self.preferred_line_height(app);
1593        self.get_text_position_vertical(app, position, -0.5 * line_height)
1594    }
1595
1596    /// Returns the TextPosition below the given offset into the text.
1597    pub fn get_text_position_below(
1598        self: RenderHandle<Self>,
1599        app: &mut App,
1600        position: TextPosition,
1601    ) -> TextPosition {
1602        self.compute_text_metrics_if_needed(app);
1603        let line_height = self.preferred_line_height(app);
1604        self.get_text_position_vertical(app, position, 1.5 * line_height)
1605    }
1606
1607    fn get_text_position_vertical(
1608        self: RenderHandle<Self>,
1609        app: &mut App,
1610        position: TextPosition,
1611        vertical_offset: f64,
1612    ) -> TextPosition {
1613        let caret_prototype = self.get(app).caret_prototype;
1614        let (painter, fonts) = self.painter_with_fonts(app);
1615        let caret_offset = painter.get_offset_for_caret(fonts, position, caret_prototype);
1616        painter.get_position_for_offset(caret_offset.translate(0.0, vertical_offset))
1617    }
1618
1619    /// Starts a [`VerticalCaretMovementRun`] at the given location in the text.
1620    pub fn start_vertical_caret_movement(
1621        self: RenderHandle<Self>,
1622        app: &mut App,
1623        start_position: TextPosition,
1624    ) -> VerticalCaretMovementRun {
1625        self.compute_text_metrics_if_needed(app);
1626        let metrics = self.get_mut(app).text_painter.compute_line_metrics();
1627        let (painter, fonts) = self.painter_with_fonts(app);
1628        let (line, offset) = line_number_for(painter, fonts, start_position, &metrics);
1629        VerticalCaretMovementRun {
1630            editable: self,
1631            layout_generation: self.get(app).layout_generation,
1632            current_offset: offset,
1633            current_line: line,
1634            current_text_position: start_position,
1635            line_metrics: metrics,
1636            position_cache: HashMap::new(),
1637            is_valid: true,
1638        }
1639    }
1640
1641    fn paint_highlights(
1642        self: RenderHandle<Self>,
1643        app: &App,
1644        canvas: &mut inset_embedder::Canvas,
1645        offset: Offset,
1646    ) {
1647        let paint_offset = offset + self.paint_offset(app);
1648        let painter_size = self.get(app).text_painter.size();
1649        let clip = Rect::from_ltwh(0.0, 0.0, painter_size.width(), painter_size.height());
1650        if let (Some(color), Some(range)) = (
1651            self.get(app).prompt_rect_color,
1652            self.get(app).prompt_rect_range,
1653        ) && !range.is_collapsed()
1654        {
1655            paint_range(
1656                canvas,
1657                &self.get(app).text_painter,
1658                range,
1659                color,
1660                paint_offset,
1661                clip,
1662                self.get(app).selection_height_style,
1663                self.get(app).selection_width_style,
1664            );
1665        }
1666        if let (Some(color), Some(selection)) =
1667            (self.get(app).selection_color, self.get(app).selection)
1668            && !selection.is_collapsed()
1669        {
1670            paint_range(
1671                canvas,
1672                &self.get(app).text_painter,
1673                selection.range(),
1674                color,
1675                paint_offset,
1676                clip,
1677                self.get(app).selection_height_style,
1678                self.get(app).selection_width_style,
1679            );
1680        }
1681    }
1682
1683    fn paint_caret(
1684        self: RenderHandle<Self>,
1685        app: &mut App,
1686        canvas: &mut inset_embedder::Canvas,
1687        offset: Offset,
1688    ) {
1689        let Some(selection) = self.get(app).selection else {
1690            return;
1691        };
1692        let show_cursor = self.get(app).show_cursor;
1693        if !selection.is_collapsed() || !*show_cursor.value(app) {
1694            return;
1695        }
1696        let Some(color) = self.get(app).cursor_color else {
1697            return;
1698        };
1699        let caret = self
1700            .get_local_rect_for_caret(app, selection.extent())
1701            .shift(offset);
1702        canvas.draw_rect(
1703            caret,
1704            &Paint {
1705                color: color.into(),
1706                ..Paint::default()
1707            },
1708        );
1709    }
1710
1711    fn paint_contents(
1712        self: RenderHandle<Self>,
1713        app: &mut App,
1714        context: &mut PaintingContext,
1715        offset: Offset,
1716    ) {
1717        let effective = offset + self.paint_offset(app);
1718        if self.selection(app).is_some() {
1719            self.update_selection_extents_visibility(app, effective);
1720        }
1721        let above = self.get(app).paint_cursor_above_text;
1722        if !above {
1723            self.paint_caret(app, context.canvas(), offset);
1724        }
1725        self.paint_highlights(app, context.canvas(), offset);
1726        let (painter, fonts) = self.painter_with_fonts(app);
1727        painter.paint(fonts, context.canvas(), effective);
1728        self.paint_inline_children(app, context, effective);
1729        if above {
1730            self.paint_caret(app, context.canvas(), offset);
1731        }
1732    }
1733
1734    fn preferred_height(self: RenderHandle<Self>, app: &mut App, width: f64) -> f64 {
1735        let max_lines = self.get(app).max_lines;
1736        let min_lines = self.get(app).min_lines.or(max_lines);
1737        let line_height = self.preferred_line_height(app);
1738        let min_height = line_height * f64::from(min_lines.unwrap_or(0));
1739        match max_lines {
1740            None => {
1741                let estimated = if width.is_infinite() {
1742                    line_height * f64::from(count_hard_line_breaks(&self.plain_text(app)) + 1)
1743                } else {
1744                    let (min_width, max_width) = self.adjust_constraints(app, 0.0, width);
1745                    let (intrinsics, fonts) = self.text_intrinsics_with_fonts(app);
1746                    intrinsics.layout(fonts, min_width, max_width);
1747                    intrinsics.height()
1748                };
1749                estimated.max(min_height)
1750            }
1751            Some(1) => {
1752                let (min_width, max_width) = self.adjust_constraints(app, 0.0, width);
1753                let (intrinsics, fonts) = self.text_intrinsics_with_fonts(app);
1754                intrinsics.layout(fonts, min_width, max_width);
1755                intrinsics.height()
1756            }
1757            Some(max) if min_lines == Some(max) => min_height,
1758            Some(max) => {
1759                let max_height = line_height * f64::from(max);
1760                let (min_width, max_width) = self.adjust_constraints(app, 0.0, width);
1761                let (intrinsics, fonts) = self.text_intrinsics_with_fonts(app);
1762                intrinsics.layout(fonts, min_width, max_width);
1763                clamp_double(intrinsics.height(), min_height, max_height)
1764            }
1765        }
1766    }
1767}
1768
1769fn offset_or_cursor_changed(this: Handle<RenderEditable>, app: &mut App) {
1770    RenderHandle::from_handle(this).mark_needs_paint(app);
1771}
1772
1773fn utf16_len(text: &str) -> i32 {
1774    text.encode_utf16().count() as i32
1775}
1776
1777fn code_unit_at(text: &str, offset: i32) -> Option<i32> {
1778    text.encode_utf16().nth(offset as usize).map(i32::from)
1779}
1780
1781fn count_hard_line_breaks(text: &str) -> i32 {
1782    text.encode_utf16()
1783        .filter(|unit| matches!(*unit, 0x000A | 0x0085 | 0x000B | 0x000C | 0x2028 | 0x2029))
1784        .count() as i32
1785}
1786
1787fn line_number_for(
1788    painter: &mut TextPainter,
1789    fonts: &mut FontCollection,
1790    start_position: TextPosition,
1791    metrics: &[LineMetrics],
1792) -> (i32, Offset) {
1793    let offset = painter.get_offset_for_caret(fonts, start_position, Rect::ZERO);
1794    for line in metrics {
1795        if line.baseline > offset.dy() {
1796            return (line.line_number, Offset::new(offset.dx(), line.baseline));
1797        }
1798    }
1799    let last = metrics.last();
1800    (
1801        last.map(|m| m.line_number).unwrap_or(0).max(0),
1802        Offset::new(
1803            offset.dx(),
1804            last.map(|m| m.baseline + m.descent).unwrap_or(0.0),
1805        ),
1806    )
1807}
1808
1809#[allow(clippy::too_many_arguments)]
1810fn paint_range(
1811    canvas: &mut inset_embedder::Canvas,
1812    painter: &TextPainter,
1813    range: TextRange,
1814    color: Color,
1815    paint_offset: Offset,
1816    clip: Rect,
1817    height_style: BoxHeightStyle,
1818    width_style: BoxWidthStyle,
1819) {
1820    if !range.is_valid() || range.is_collapsed() {
1821        return;
1822    }
1823    let boxes = painter.get_boxes_for_selection(
1824        TextSelection::new(range.start, range.end),
1825        height_style,
1826        width_style,
1827    );
1828    let paint = Paint {
1829        color: color.into(),
1830        ..Paint::default()
1831    };
1832    for box_ in boxes {
1833        let rect = box_
1834            .to_rect()
1835            .shift(paint_offset)
1836            .intersect(clip.shift(paint_offset));
1837        if !rect.is_empty() {
1838            canvas.draw_rect(rect, &paint);
1839        }
1840    }
1841}
1842
1843impl RelayoutWhenSystemFontsChangeMixin for RenderEditable {
1844    fn relayout_when_system_fonts_change_data(
1845        self: RenderHandle<Self>,
1846        app: &App,
1847    ) -> &RelayoutWhenSystemFontsChangeData {
1848        &self.get(app).system_fonts
1849    }
1850
1851    fn relayout_when_system_fonts_change_data_mut(
1852        self: RenderHandle<Self>,
1853        app: &mut App,
1854    ) -> &mut RelayoutWhenSystemFontsChangeData {
1855        &mut self.get_mut(app).system_fonts
1856    }
1857
1858    fn system_fonts_did_change(self: RenderHandle<Self>, app: &mut App) {
1859        self.as_render_object(app).mark_needs_layout(app);
1860        self.get_mut(app).text_painter.mark_needs_layout();
1861    }
1862}
1863
1864impl ContainerRenderObjectMixin for RenderEditable {
1865    type ChildType = AnyRenderBox;
1866    type ParentDataType = TextParentData;
1867
1868    fn container_data(
1869        self: RenderHandle<Self>,
1870        app: &App,
1871    ) -> &ContainerRenderObjectData<AnyRenderBox> {
1872        &self.get(app).container
1873    }
1874
1875    fn container_data_mut(
1876        self: RenderHandle<Self>,
1877        app: &mut App,
1878    ) -> &mut ContainerRenderObjectData<AnyRenderBox> {
1879        &mut self.get_mut(app).container
1880    }
1881}
1882
1883impl RenderInlineChildrenContainerDefaults for RenderEditable {}
1884
1885impl RenderObject for RenderEditable {
1886    crate::render_object_accessors!();
1887
1888    fn visit_children(
1889        self: RenderHandle<Self>,
1890        app: &App,
1891        visitor: &mut dyn FnMut(AnyRenderObject),
1892    ) {
1893        // The custom-paint boxes, which Dart visits first, are not ported.
1894        ContainerRenderObjectMixin::visit_children(self, app, visitor)
1895    }
1896
1897    fn redepth_children(self: RenderHandle<Self>, app: &mut App) {
1898        ContainerRenderObjectMixin::redepth_children(self, app)
1899    }
1900
1901    fn did_attach(self: RenderHandle<Self>, app: &mut App, owner: Handle<PipelineOwner>) {
1902        RelayoutWhenSystemFontsChangeMixin::attach_system_fonts(self, app);
1903        ContainerRenderObjectMixin::did_attach(self, app, owner);
1904        let offset = self.get(app).offset;
1905        offset.add_listener(app, self.mark_needs_paint_listener());
1906        let show_cursor = self.get(app).show_cursor;
1907        show_cursor.add_listener(app, self.mark_needs_paint_listener());
1908    }
1909
1910    fn did_detach(self: RenderHandle<Self>, app: &mut App) {
1911        let offset = self.get(app).offset;
1912        offset.remove_listener(app, &self.mark_needs_paint_listener());
1913        let show_cursor = self.get(app).show_cursor;
1914        show_cursor.remove_listener(app, &self.mark_needs_paint_listener());
1915        RelayoutWhenSystemFontsChangeMixin::detach_system_fonts(self, app);
1916        ContainerRenderObjectMixin::did_detach(self, app);
1917    }
1918
1919    fn dispose(self: RenderHandle<Self>, app: &mut App) {
1920        LayerHandle::set_layer(app, |app| &mut self.get_mut(app).leader_layer_handler, None);
1921        LayerHandle::set_layer(app, |app| &mut self.get_mut(app).clip_rect_layer, None);
1922        let start_in_viewport = self.get(app).selection_start_in_viewport;
1923        let end_in_viewport = self.get(app).selection_end_in_viewport;
1924        app.get_mut(start_in_viewport).dispose();
1925        app.get_mut(end_in_viewport).dispose();
1926        crate::object::RenderObjectBase::dispose(self, app);
1927    }
1928
1929    fn perform_layout(self: RenderHandle<Self>, app: &mut App) {
1930        let constraints = self.constraints(app);
1931        let placeholder_dimensions = self.layout_inline_children(
1932            app,
1933            constraints.max_width,
1934            ChildLayoutHelper::layout_child,
1935            ChildLayoutHelper::get_baseline,
1936        );
1937        self.get_mut(app).placeholder_dimensions = Some(placeholder_dimensions.clone());
1938        let (min_width, max_width) =
1939            self.adjust_constraints(app, constraints.min_width, constraints.max_width);
1940        let (painter, fonts) = self.painter_with_fonts(app);
1941        painter.set_placeholder_dimensions(Some(placeholder_dimensions));
1942        painter.layout(fonts, min_width, max_width);
1943        let boxes = self
1944            .get_mut(app)
1945            .text_painter
1946            .inline_placeholder_boxes()
1947            .expect("laid out just now");
1948        self.position_inline_children(app, &boxes);
1949        self.compute_caret_prototype(app);
1950        let caret_margin = self.caret_margin(app);
1951        let painter_width = self.get(app).text_painter.width();
1952        let painter_height = self.get(app).text_painter.height();
1953        let width = if self.get(app).force_line {
1954            constraints.max_width
1955        } else {
1956            constraints.constrain_width(painter_width + caret_margin)
1957        };
1958        let preferred_height = match self.get(app).max_lines {
1959            None => {
1960                let min_lines = self.get(app).min_lines.unwrap_or(0);
1961                painter_height.max(self.preferred_line_height(app) * f64::from(min_lines))
1962            }
1963            Some(1) => painter_height,
1964            Some(max_lines) => {
1965                let line_height = self.preferred_line_height(app);
1966                let min_lines = self.get(app).min_lines.unwrap_or(max_lines);
1967                clamp_double(
1968                    painter_height,
1969                    line_height * f64::from(min_lines),
1970                    line_height * f64::from(max_lines),
1971                )
1972            }
1973        };
1974        let size = Size::new(width, constraints.constrain_height(preferred_height));
1975        self.set_size(app, size);
1976        let content_size = Size::new(painter_width + caret_margin, painter_height);
1977        self.get_mut(app).max_scroll_extent = self.get_max_scroll_extent(app, content_size);
1978        self.get_mut(app).layout_generation += 1;
1979        let viewport_extent = self.viewport_extent(app);
1980        let max_scroll = self.get(app).max_scroll_extent;
1981        let offset = self.get(app).offset;
1982        offset.apply_viewport_dimension(app, viewport_extent);
1983        offset.apply_content_dimensions(app, 0.0, max_scroll);
1984    }
1985
1986    fn paint(
1987        self: RenderHandle<Self>,
1988        app: &mut App,
1989        context: &mut PaintingContext,
1990        offset: Offset,
1991    ) {
1992        self.compute_text_metrics_if_needed(app);
1993        if self.has_visual_overflow(app) && self.get(app).clip_behavior != Clip::None {
1994            let size = self.size(app);
1995            let clip_behavior = self.get(app).clip_behavior;
1996            let old = self.get(app).clip_rect_layer.layer();
1997            let layer = context.push_clip_rect(
1998                app,
1999                self.as_object().needs_compositing(app),
2000                offset,
2001                Offset::ZERO & size,
2002                |app, context, offset| {
2003                    self.paint_contents(app, context, offset);
2004                },
2005                clip_behavior,
2006                old,
2007            );
2008            LayerHandle::set_layer(app, |app| &mut self.get_mut(app).clip_rect_layer, layer);
2009        } else {
2010            LayerHandle::set_layer(app, |app| &mut self.get_mut(app).clip_rect_layer, None);
2011            self.paint_contents(app, context, offset);
2012        }
2013        if let Some(selection) = self.selection(app)
2014            && selection.is_valid()
2015        {
2016            let endpoints = self.get_endpoints_for_selection(app, selection);
2017            self.paint_handle_layers(app, context, &endpoints, offset);
2018        }
2019    }
2020}
2021
2022impl RenderBox for RenderEditable {
2023    crate::render_box_accessors!();
2024
2025    fn setup_parent_data(self: RenderHandle<Self>, app: &mut App, child: AnyRenderObject) {
2026        RenderInlineChildrenContainerDefaults::setup_parent_data(self, app, child);
2027    }
2028
2029    fn compute_min_intrinsic_width(self: RenderHandle<Self>, app: &mut App, _height: f64) -> f64 {
2030        let placeholder_dimensions = self.layout_inline_children(
2031            app,
2032            f64::INFINITY,
2033            |app, child, _| Size::new(child.get_min_intrinsic_width(app, f64::INFINITY), 0.0),
2034            ChildLayoutHelper::get_dry_baseline,
2035        );
2036        let (min_width, max_width) = self.adjust_constraints(app, 0.0, f64::INFINITY);
2037        let (intrinsics, fonts) = self.text_intrinsics_with_fonts(app);
2038        intrinsics.set_placeholder_dimensions(Some(placeholder_dimensions));
2039        intrinsics.layout(fonts, min_width, max_width);
2040        intrinsics.min_intrinsic_width()
2041    }
2042
2043    fn compute_max_intrinsic_width(self: RenderHandle<Self>, app: &mut App, _height: f64) -> f64 {
2044        let placeholder_dimensions = self.layout_inline_children(
2045            app,
2046            f64::INFINITY,
2047            // Height and baseline is irrelevant as all text will be laid
2048            // out in a single line. Therefore, using 0.0 as a dummy for the height.
2049            |app, child, _| Size::new(child.get_max_intrinsic_width(app, f64::INFINITY), 0.0),
2050            ChildLayoutHelper::get_dry_baseline,
2051        );
2052        let (min_width, max_width) = self.adjust_constraints(app, 0.0, f64::INFINITY);
2053        let (intrinsics, fonts) = self.text_intrinsics_with_fonts(app);
2054        intrinsics.set_placeholder_dimensions(Some(placeholder_dimensions));
2055        intrinsics.layout(fonts, min_width, max_width);
2056        intrinsics.max_intrinsic_width() + self.caret_margin(app)
2057    }
2058
2059    fn compute_min_intrinsic_height(self: RenderHandle<Self>, app: &mut App, width: f64) -> f64 {
2060        self.compute_max_intrinsic_height(app, width)
2061    }
2062
2063    fn compute_max_intrinsic_height(self: RenderHandle<Self>, app: &mut App, width: f64) -> f64 {
2064        let placeholder_dimensions = self.layout_inline_children(
2065            app,
2066            width,
2067            ChildLayoutHelper::dry_layout_child,
2068            ChildLayoutHelper::get_dry_baseline,
2069        );
2070        let (intrinsics, _) = self.text_intrinsics_with_fonts(app);
2071        intrinsics.set_placeholder_dimensions(Some(placeholder_dimensions));
2072        self.preferred_height(app, width)
2073    }
2074
2075    fn compute_dry_layout(
2076        self: RenderHandle<Self>,
2077        app: &mut App,
2078        constraints: BoxConstraints,
2079    ) -> Size {
2080        let placeholder_dimensions = self.layout_inline_children(
2081            app,
2082            constraints.max_width,
2083            ChildLayoutHelper::dry_layout_child,
2084            ChildLayoutHelper::get_dry_baseline,
2085        );
2086        let (min_width, max_width) =
2087            self.adjust_constraints(app, constraints.min_width, constraints.max_width);
2088        let force_line = self.get(app).force_line;
2089        let caret_margin = self.caret_margin(app);
2090        let (intrinsics, fonts) = self.text_intrinsics_with_fonts(app);
2091        intrinsics.set_placeholder_dimensions(Some(placeholder_dimensions));
2092        intrinsics.layout(fonts, min_width, max_width);
2093        let painter_width = intrinsics.width();
2094        let width = if force_line {
2095            constraints.max_width
2096        } else {
2097            constraints.constrain_width(painter_width + caret_margin)
2098        };
2099        let height = self.preferred_height(app, constraints.max_width);
2100        Size::new(width, constraints.constrain_height(height))
2101    }
2102
2103    fn compute_distance_to_actual_baseline(
2104        self: RenderHandle<Self>,
2105        app: &mut App,
2106        baseline: TextBaseline,
2107    ) -> Option<f64> {
2108        self.compute_text_metrics_if_needed(app);
2109        Some(
2110            self.get(app)
2111                .text_painter
2112                .compute_distance_to_actual_baseline(baseline),
2113        )
2114    }
2115
2116    fn hit_test_self(self: RenderHandle<Self>, _app: &App, _position: Offset) -> bool {
2117        true
2118    }
2119
2120    fn hit_test_children(
2121        self: RenderHandle<Self>,
2122        app: &mut App,
2123        result: &mut BoxHitTestResult<'_>,
2124        position: Offset,
2125    ) -> bool {
2126        // Spans are not hit-test targets yet, so the hit falls to the inline children.
2127        let effective_position = position - self.paint_offset(app);
2128        self.hit_test_inline_children(app, result, effective_position)
2129    }
2130
2131    fn apply_paint_transform(
2132        self: RenderHandle<Self>,
2133        app: &App,
2134        child: AnyRenderObject,
2135        transform: &mut Matrix4,
2136    ) {
2137        // The custom-paint boxes, which Dart leaves untransformed, are not ported.
2138        self.default_apply_paint_transform(app, child, transform);
2139    }
2140}
2141
2142impl RenderEditable {
2143    /// Flutter's `_paintHandleLayers`: an empty leader at each selection endpoint, clamped into
2144    /// the box, for the selection handles to follow.
2145    fn paint_handle_layers(
2146        self: RenderHandle<Self>,
2147        app: &mut App,
2148        context: &mut PaintingContext,
2149        endpoints: &[TextSelectionPoint],
2150        offset: Offset,
2151    ) {
2152        let size = self.size(app);
2153        let clamp = |point: Offset| {
2154            Offset::new(
2155                point.dx().clamp(0.0, size.width()),
2156                point.dy().clamp(0.0, size.height()),
2157            )
2158        };
2159        let Some(start) = endpoints.first() else {
2160            return;
2161        };
2162        let start_point = clamp(start.point);
2163        let (start_link, end_link) = {
2164            let this = self.get(app);
2165            (this.start_handle_layer_link, this.end_handle_layer_link)
2166        };
2167        let start_layer = LeaderLayer::new(app, start_link);
2168        start_layer.set_offset(app, start_point + offset);
2169        LayerHandle::set_layer(
2170            app,
2171            |app| &mut self.get_mut(app).leader_layer_handler,
2172            Some(start_layer),
2173        );
2174        context.push_layer(
2175            app,
2176            start_layer.as_container_layer(),
2177            |_, _, _| {},
2178            Offset::ZERO,
2179            None,
2180        );
2181        if let Some(end) = endpoints.get(1) {
2182            let end_point = clamp(end.point);
2183            let end_layer = LeaderLayer::new(app, end_link);
2184            end_layer.set_offset(app, end_point + offset);
2185            context.push_layer(
2186                app,
2187                end_layer.as_container_layer(),
2188                |_, _, _| {},
2189                Offset::ZERO,
2190                None,
2191            );
2192        } else if self
2193            .selection(app)
2194            .is_some_and(|selection| selection.is_collapsed())
2195        {
2196            let end_layer = LeaderLayer::new(app, end_link);
2197            end_layer.set_offset(app, start_point + offset);
2198            context.push_layer(
2199                app,
2200                end_layer.as_container_layer(),
2201                |_, _, _| {},
2202                Offset::ZERO,
2203                None,
2204            );
2205        }
2206    }
2207}
2208
2209#[cfg(test)]
2210mod tests {
2211    use std::cell::Cell;
2212    use std::rc::Rc;
2213
2214    use inset_embedder::{FontSource, TextAffinity};
2215    use inset_foundation::{AppCell, Task, ValueListenable};
2216    use inset_painting::{PaintingBinding, TextSpan, TextStyle};
2217    use inset_scheduler::SchedulerBinding;
2218    use inset_services::{TextEditingValue, TextSelectionDelegate};
2219    use valo::{Font, FontAttrs};
2220
2221    use super::*;
2222    use crate::layer::{ContainerLayer, ErasedLayer, OffsetLayer};
2223    use crate::pipeline_owner::PipelineOwner;
2224    use crate::proxy_box::RenderRepaintBoundary;
2225    use crate::viewport_offset::{FixedViewportOffset, ViewportOffset};
2226
2227    #[derive(Default)]
2228    struct TestDelegate {
2229        value: TextEditingValue,
2230    }
2231
2232    impl TextSelectionDelegate for TestDelegate {
2233        fn text_editing_value(self: Handle<Self>, app: &App) -> TextEditingValue {
2234            app.get(self).value.clone()
2235        }
2236
2237        fn user_update_text_editing_value(
2238            self: Handle<Self>,
2239            app: &mut App,
2240            value: TextEditingValue,
2241            _cause: SelectionChangedCause,
2242        ) {
2243            app.get_mut(self).value = value;
2244        }
2245
2246        fn hide_toolbar(self: Handle<Self>, _app: &mut App, _hide_handles: bool) {}
2247
2248        fn bring_into_view(self: Handle<Self>, _app: &mut App, _position: TextPosition) {}
2249
2250        fn cut_selection(self: Handle<Self>, _app: &mut App, _cause: SelectionChangedCause) {}
2251
2252        fn paste_text(
2253            self: Handle<Self>,
2254            _app: &mut App,
2255            _cause: SelectionChangedCause,
2256        ) -> Task<()> {
2257            Task::ready(())
2258        }
2259
2260        fn select_all(self: Handle<Self>, _app: &mut App, _cause: SelectionChangedCause) {}
2261
2262        fn copy_selection(self: Handle<Self>, _app: &mut App, _cause: SelectionChangedCause) {}
2263    }
2264
2265    fn install_fonts(app: &mut App) {
2266        let binding = PaintingBinding::instance(app);
2267        if !binding.has_fonts(app) {
2268            binding.install_fonts(app, |fonts| {
2269                fonts.add_source(valo_system_fonts::SystemFonts::load());
2270            });
2271        }
2272    }
2273
2274    fn span(text: &str) -> InlineSpanRef {
2275        TextSpan::new()
2276            .text(text)
2277            .style(TextStyle::new().font_size(20.0))
2278            .into_span()
2279    }
2280
2281    fn laid_out(app: &mut App, text: &str) -> RenderHandle<RenderEditable> {
2282        install_fonts(app);
2283        let delegate = app.create(TestDelegate {
2284            value: TextEditingValue::new()
2285                .text(text)
2286                .selection(TextSelection::collapsed(0, TextAffinity::Downstream)),
2287        });
2288        let start = LayerLink::new(app);
2289        let end = LayerLink::new(app);
2290        let offset = FixedViewportOffset::zero(app).as_viewport_offset();
2291        let editable = RenderEditable::new(
2292            app,
2293            TextDirection::Ltr,
2294            start,
2295            end,
2296            offset,
2297            delegate.as_text_selection_delegate(),
2298        );
2299        editable.set_text(app, Some(span(text)));
2300        editable.set_cursor_color(app, Some(Color::from_argb(255, 0, 0, 0)));
2301        let root = RenderRepaintBoundary::new(app, Some(editable.as_box()));
2302        let owner = PipelineOwner::new(app, None, None);
2303        owner.set_root_node(app, Some(root.as_object()));
2304        root.schedule_initial_layout(app);
2305        root.layout(
2306            app,
2307            BoxConstraints::tight_for(Some(200.0), Some(40.0)),
2308            false,
2309        );
2310        let paint_root = OffsetLayer::new(app, Offset::ZERO);
2311        paint_root.as_layer().attach(app, root.as_object().id());
2312        root.as_object()
2313            .schedule_initial_paint(app, paint_root.as_container_layer());
2314        editable
2315    }
2316
2317    #[test]
2318    fn move_by_word_boundary_skips_spaces_and_punctuation() {
2319        let cell = AppCell::new();
2320        let mut app = cell.borrow_mut();
2321        let text = concat!(
2322            "ABC   ABC\n",                       // [0, 10)
2323            "A\u{41}\u{301}    \u{41}\u{301}\n", // [10, 20)
2324            "         \n",                       // [20, 30)
2325            "ABC!!!ABC\n",                       // [30, 40)
2326            "  !ABC !!\n",                       // [40, 50)
2327            "A  \u{115CB}\u{115CB} A\n",         // [50, 60)
2328        );
2329        let editable = laid_out(&mut app, text);
2330        let boundary = editable.word_boundaries(&app).move_by_word_boundary();
2331
2332        // 4 points to the 2nd whitespace in the first line.
2333        // Don't break between horizontal spaces and letters/numbers.
2334        assert_eq!(boundary.get_leading_text_boundary_at(&mut app, 4), Some(0));
2335        assert_eq!(boundary.get_trailing_text_boundary_at(&mut app, 4), Some(9));
2336
2337        // Works when words are starting/ending with a combining diacritical mark.
2338        assert_eq!(
2339            boundary.get_leading_text_boundary_at(&mut app, 14),
2340            Some(10)
2341        );
2342        assert_eq!(
2343            boundary.get_trailing_text_boundary_at(&mut app, 14),
2344            Some(19)
2345        );
2346
2347        // Do break before and after newlines.
2348        assert_eq!(
2349            boundary.get_leading_text_boundary_at(&mut app, 24),
2350            Some(20)
2351        );
2352        assert_eq!(
2353            boundary.get_trailing_text_boundary_at(&mut app, 24),
2354            Some(29)
2355        );
2356
2357        // Do not break on punctuations.
2358        assert_eq!(
2359            boundary.get_leading_text_boundary_at(&mut app, 34),
2360            Some(30)
2361        );
2362        assert_eq!(
2363            boundary.get_trailing_text_boundary_at(&mut app, 34),
2364            Some(39)
2365        );
2366
2367        // Ok to break if next to punctuations or separating spaces.
2368        assert_eq!(
2369            boundary.get_leading_text_boundary_at(&mut app, 44),
2370            Some(43)
2371        );
2372        assert_eq!(
2373            boundary.get_trailing_text_boundary_at(&mut app, 44),
2374            Some(46)
2375        );
2376
2377        // 54 points to a low surrogate of a punctuation.
2378        assert_eq!(
2379            boundary.get_leading_text_boundary_at(&mut app, 54),
2380            Some(50)
2381        );
2382        assert_eq!(
2383            boundary.get_trailing_text_boundary_at(&mut app, 54),
2384            Some(59)
2385        );
2386    }
2387
2388    #[test]
2389    fn display_names_the_direction() {
2390        let point = Offset::new(1.0, 2.0);
2391        assert_eq!(
2392            TextSelectionPoint::new(point, Some(TextDirection::Ltr)).to_string(),
2393            format!("{point:?}-ltr")
2394        );
2395        assert_eq!(
2396            TextSelectionPoint::new(point, None).to_string(),
2397            format!("{point:?}")
2398        );
2399    }
2400
2401    #[test]
2402    fn layout_gives_the_text_a_size() {
2403        let cell = AppCell::new();
2404        let mut app = cell.borrow_mut();
2405        let editable = laid_out(&mut app, "hi");
2406        let size = editable.size(&app);
2407        assert!(size.width() > 0.0);
2408        assert!(size.height() > 0.0);
2409    }
2410
2411    #[test]
2412    fn select_position_updates_the_delegate() {
2413        let cell = AppCell::new();
2414        let mut app = cell.borrow_mut();
2415        let editable = laid_out(&mut app, "hello");
2416        let origin = editable.as_box().local_to_global(&app, Offset::ZERO, None);
2417        editable.select_position_at(
2418            &mut app,
2419            origin + Offset::new(2.0, 2.0),
2420            None,
2421            SelectionChangedCause::Tap,
2422        );
2423        let value = editable
2424            .text_selection_delegate(&app)
2425            .text_editing_value(&app);
2426        assert!(value.selection.is_valid());
2427        assert!(value.selection.is_collapsed());
2428    }
2429
2430    #[test]
2431    fn handle_secondary_tap_down_records_the_position() {
2432        let cell = AppCell::new();
2433        let mut app = cell.borrow_mut();
2434        let editable = laid_out(&mut app, "hello");
2435        let at = Offset::new(12.0, 8.0);
2436        editable.handle_secondary_tap_down(&mut app, &TapDownDetails::new(at, None, None));
2437        assert_eq!(editable.last_secondary_tap_down_position(&app), Some(at));
2438    }
2439
2440    #[test]
2441    fn selection_extents_default_in_the_viewport() {
2442        let cell = AppCell::new();
2443        let mut app = cell.borrow_mut();
2444        let editable = laid_out(&mut app, "hello");
2445        assert!(
2446            *editable.selection_start_in_viewport(&app).value(&app),
2447            "Flutter defaults selectionStartInViewport to true"
2448        );
2449        assert!(
2450            *editable.selection_end_in_viewport(&app).value(&app),
2451            "Flutter defaults selectionEndInViewport to true"
2452        );
2453    }
2454
2455    #[test]
2456    fn system_fonts_change_marks_the_editable_dirty() {
2457        let cell = AppCell::new();
2458        let mut app = cell.borrow_mut();
2459        let editable = laid_out(&mut app, "hello");
2460        assert!(!editable.as_object().debug_needs_layout(&app));
2461        PaintingBinding::instance(&mut app).handle_system_fonts_did_change(&mut app);
2462        SchedulerBinding::handle_begin_frame(&mut app, &[], None);
2463        assert!(
2464            editable.as_object().debug_needs_layout(&app),
2465            "RenderEditable listens to PaintingBinding.system_fonts"
2466        );
2467    }
2468
2469    /// A source that can answer ASCII immediately and hold non-ASCII until the
2470    /// test opens the gate — the web host's delayed Noto path.
2471    struct GatedFonts {
2472        inner: valo_system_fonts::SystemFonts,
2473        allow_non_ascii: Rc<Cell<bool>>,
2474    }
2475
2476    impl FontSource for GatedFonts {
2477        fn family(&mut self, _name: &str) -> Vec<Font> {
2478            // Named families on this machine (PingFang, the system UI font) cover
2479            // CJK. The gate is the codepoint path, so family lookup stays empty.
2480            Vec::new()
2481        }
2482
2483        fn face_for_codepoint(&mut self, codepoint: char, attrs: FontAttrs) -> Option<Font> {
2484            if !self.allow_non_ascii.get() && !codepoint.is_ascii() {
2485                return None;
2486            }
2487            self.inner.face_for_codepoint(codepoint, attrs)
2488        }
2489    }
2490
2491    #[test]
2492    fn system_fonts_change_rebuilds_glyphs_once_a_fallback_face_arrives() {
2493        let allow_non_ascii = Rc::new(Cell::new(false));
2494        let cell = AppCell::new();
2495        let mut app = cell.borrow_mut();
2496        PaintingBinding::instance(&mut app).install_fonts(&mut app, {
2497            let allow_non_ascii = Rc::clone(&allow_non_ascii);
2498            move |fonts| {
2499                fonts.add_source(GatedFonts {
2500                    inner: valo_system_fonts::SystemFonts::load(),
2501                    allow_non_ascii,
2502                });
2503            }
2504        });
2505
2506        let text = "a你";
2507        let delegate = app.create(TestDelegate {
2508            value: TextEditingValue::new()
2509                .text(text)
2510                .selection(TextSelection::collapsed(0, TextAffinity::Downstream)),
2511        });
2512        let start = LayerLink::new(&mut app);
2513        let end = LayerLink::new(&mut app);
2514        let offset = FixedViewportOffset::zero(&mut app).as_viewport_offset();
2515        let editable = RenderEditable::new(
2516            &mut app,
2517            TextDirection::Ltr,
2518            start,
2519            end,
2520            offset,
2521            delegate.as_text_selection_delegate(),
2522        );
2523        editable.set_text(&mut app, Some(span(text)));
2524        let root = RenderRepaintBoundary::new(&mut app, Some(editable.as_box()));
2525        let owner = PipelineOwner::new(&mut app, None, None);
2526        owner.set_root_node(&mut app, Some(root.as_object()));
2527        root.schedule_initial_layout(&mut app);
2528        let constraints = BoxConstraints::tight_for(Some(200.0), Some(40.0));
2529        root.layout(&mut app, constraints, false);
2530
2531        let cjk = TextRange::new(1, 2);
2532        let before = editable
2533            .get_rect_for_composing_range(&mut app, cjk)
2534            .expect("an uncovered CJK character still occupies .notdef space");
2535
2536        allow_non_ascii.set(true);
2537        PaintingBinding::instance(&mut app).handle_system_fonts_did_change(&mut app);
2538        SchedulerBinding::handle_begin_frame(&mut app, &[], None);
2539        root.layout(&mut app, constraints, false);
2540
2541        let after = editable
2542            .get_rect_for_composing_range(&mut app, cjk)
2543            .expect("systemFontsDidChange rebuilds the painter against the new face");
2544        assert_ne!(
2545            before.width(),
2546            after.width(),
2547            "the CJK glyph replaces .notdef once its face is available"
2548        );
2549    }
2550}