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,
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    /// Track whether the start of the selection is currently in the visible
917    /// viewport.
918    pub fn selection_start_in_viewport(
919        self: RenderHandle<Self>,
920        app: &App,
921    ) -> Handle<ValueNotifier<bool>> {
922        self.get(app).selection_start_in_viewport
923    }
924
925    /// Track whether the end of the selection is currently in the visible
926    /// viewport.
927    pub fn selection_end_in_viewport(
928        self: RenderHandle<Self>,
929        app: &App,
930    ) -> Handle<ValueNotifier<bool>> {
931        self.get(app).selection_end_in_viewport
932    }
933
934    fn update_selection_extents_visibility(
935        self: RenderHandle<Self>,
936        app: &mut App,
937        effective_offset: Offset,
938    ) {
939        let Some(selection) = self.selection(app) else {
940            return;
941        };
942        let start_notifier = self.get(app).selection_start_in_viewport;
943        let end_notifier = self.get(app).selection_end_in_viewport;
944        if !selection.is_valid() {
945            start_notifier.set_value(app, false);
946            end_notifier.set_value(app, false);
947            return;
948        }
949        let visible_region = Offset::ZERO & self.size(app);
950        let caret_prototype = self.get(app).caret_prototype;
951        let start = TextPosition::with_affinity(selection.start(), selection.affinity);
952        let end = TextPosition::with_affinity(selection.end(), selection.affinity);
953        let (start_offset, end_offset) = {
954            let (painter, fonts) = self.painter_with_fonts(app);
955            (
956                painter.get_offset_for_caret(fonts, start, caret_prototype),
957                painter.get_offset_for_caret(fonts, end, caret_prototype),
958            )
959        };
960        const VISIBLE_REGION_SLOP: f64 = 0.5;
961        let region = visible_region.inflate(VISIBLE_REGION_SLOP);
962        start_notifier.set_value(app, region.contains(start_offset + effective_offset));
963        end_notifier.set_value(app, region.contains(end_offset + effective_offset));
964    }
965
966    /// If [`obscure_text`](Self::obscure_text) is true, returns the obscured text.
967    pub fn plain_text(self: RenderHandle<Self>, app: &mut App) -> String {
968        self.get_mut(app).text_painter.plain_text().to_owned()
969    }
970
971    /// An estimate of the height of a line in the text.
972    pub fn preferred_line_height(self: RenderHandle<Self>, app: &mut App) -> f64 {
973        let (painter, fonts) = self.painter_with_fonts(app);
974        painter.preferred_line_height(fonts)
975    }
976
977    fn caret_margin(self: RenderHandle<Self>, app: &App) -> f64 {
978        K_CARET_GAP + self.get(app).cursor_width
979    }
980
981    fn is_multiline(self: RenderHandle<Self>, app: &App) -> bool {
982        self.get(app).max_lines != Some(1)
983    }
984
985    fn paint_offset(self: RenderHandle<Self>, app: &App) -> Offset {
986        let pixels = self.get(app).offset.pixels(app);
987        if self.is_multiline(app) {
988            Offset::new(0.0, -pixels)
989        } else {
990            Offset::new(-pixels, 0.0)
991        }
992    }
993
994    fn viewport_extent(self: RenderHandle<Self>, app: &App) -> f64 {
995        let size = self.size(app);
996        if self.is_multiline(app) {
997            size.height()
998        } else {
999            size.width()
1000        }
1001    }
1002
1003    fn get_max_scroll_extent(self: RenderHandle<Self>, app: &App, content_size: Size) -> f64 {
1004        let size = self.size(app);
1005        if self.is_multiline(app) {
1006            (content_size.height() - size.height()).max(0.0)
1007        } else {
1008            (content_size.width() - size.width()).max(0.0)
1009        }
1010    }
1011
1012    fn has_visual_overflow(self: RenderHandle<Self>, app: &App) -> bool {
1013        self.get(app).max_scroll_extent > 0.0 || self.paint_offset(app) != Offset::ZERO
1014    }
1015
1016    fn adjust_constraints(
1017        self: RenderHandle<Self>,
1018        app: &App,
1019        min_width: f64,
1020        max_width: f64,
1021    ) -> (f64, f64) {
1022        let available_max_width = (max_width - self.caret_margin(app)).max(0.0);
1023        let available_min_width = min_width.min(available_max_width);
1024        let min = if self.get(app).force_line {
1025            available_max_width
1026        } else {
1027            available_min_width
1028        };
1029        let max = if self.is_multiline(app) {
1030            available_max_width
1031        } else {
1032            f64::INFINITY
1033        };
1034        (min, max)
1035    }
1036
1037    fn compute_text_metrics_if_needed(self: RenderHandle<Self>, app: &mut App) {
1038        let constraints = self.constraints(app);
1039        let (min_width, max_width) =
1040            self.adjust_constraints(app, constraints.min_width, constraints.max_width);
1041        let (painter, fonts) = self.painter_with_fonts(app);
1042        painter.layout(fonts, min_width, max_width);
1043    }
1044
1045    fn compute_caret_prototype(self: RenderHandle<Self>, app: &mut App) {
1046        let cursor_width = self.get(app).cursor_width;
1047        let cursor_height = self.cursor_height(app);
1048        let prototype = match app.platform().target_platform() {
1049            TargetPlatform::IOS | TargetPlatform::MacOS => {
1050                Rect::from_ltwh(0.0, 0.0, cursor_width, cursor_height + 2.0)
1051            }
1052            TargetPlatform::Android
1053            | TargetPlatform::Fuchsia
1054            | TargetPlatform::Linux
1055            | TargetPlatform::Windows => Rect::from_ltwh(
1056                0.0,
1057                K_CARET_HEIGHT_OFFSET,
1058                cursor_width,
1059                cursor_height - 2.0 * K_CARET_HEIGHT_OFFSET,
1060            ),
1061        };
1062        self.get_mut(app).caret_prototype = prototype;
1063    }
1064
1065    fn snap_to_physical_pixel(
1066        self: RenderHandle<Self>,
1067        app: &App,
1068        source_offset: Offset,
1069    ) -> Offset {
1070        let global = self.as_box().local_to_global(app, source_offset, None);
1071        let pixel_multiple = 1.0 / self.get(app).device_pixel_ratio;
1072        Offset::new(
1073            if global.dx().is_finite() {
1074                (global.dx() / pixel_multiple).round() * pixel_multiple - global.dx()
1075            } else {
1076                0.0
1077            },
1078            if global.dy().is_finite() {
1079                (global.dy() / pixel_multiple).round() * pixel_multiple - global.dy()
1080            } else {
1081                0.0
1082            },
1083        )
1084    }
1085
1086    /// Returns the local coordinates of the endpoints of the given selection.
1087    pub fn get_endpoints_for_selection(
1088        self: RenderHandle<Self>,
1089        app: &mut App,
1090        selection: TextSelection,
1091    ) -> Vec<TextSelectionPoint> {
1092        self.compute_text_metrics_if_needed(app);
1093        let paint_offset = self.paint_offset(app);
1094        let height_style = self.get(app).selection_height_style;
1095        let width_style = self.get(app).selection_width_style;
1096        let boxes = if selection.is_collapsed() {
1097            Vec::new()
1098        } else {
1099            self.get(app)
1100                .text_painter
1101                .get_boxes_for_selection(selection, height_style, width_style)
1102        };
1103        if boxes.is_empty() {
1104            let caret_prototype = self.get(app).caret_prototype;
1105            let (painter, fonts) = self.painter_with_fonts(app);
1106            let caret_offset =
1107                painter.get_offset_for_caret(fonts, selection.extent(), caret_prototype);
1108            let line_height = painter.preferred_line_height(fonts);
1109            let start = Offset::new(0.0, line_height) + caret_offset + paint_offset;
1110            return vec![TextSelectionPoint::new(start, None)];
1111        }
1112        let painter_width = self.get(app).text_painter.size().width();
1113        let start = Offset::new(
1114            clamp_double(boxes[0].start(), 0.0, painter_width),
1115            boxes[0].bottom,
1116        ) + paint_offset;
1117        let last = boxes[boxes.len() - 1];
1118        let end =
1119            Offset::new(clamp_double(last.end(), 0.0, painter_width), last.bottom) + paint_offset;
1120        vec![
1121            TextSelectionPoint::new(start, Some(boxes[0].direction)),
1122            TextSelectionPoint::new(end, Some(last.direction)),
1123        ]
1124    }
1125
1126    /// Returns the smallest [`Rect`], in the local coordinate system, that covers
1127    /// the text within the [`TextRange`] specified.
1128    pub fn get_rect_for_composing_range(
1129        self: RenderHandle<Self>,
1130        app: &mut App,
1131        range: TextRange,
1132    ) -> Option<Rect> {
1133        if !range.is_valid() || range.is_collapsed() {
1134            return None;
1135        }
1136        self.compute_text_metrics_if_needed(app);
1137        let boxes = self.get(app).text_painter.get_boxes_for_selection(
1138            TextSelection::new(range.start, range.end),
1139            self.get(app).selection_height_style,
1140            self.get(app).selection_width_style,
1141        );
1142        if boxes.is_empty() {
1143            return None;
1144        }
1145        let paint_offset = self.paint_offset(app);
1146        let mut rect = boxes[0].to_rect();
1147        for box_ in boxes.iter().skip(1) {
1148            rect = rect.expand_to_include(box_.to_rect());
1149        }
1150        Some(rect.shift(paint_offset))
1151    }
1152
1153    /// Returns the [`TextPosition`] closest to the given global point.
1154    pub fn get_position_for_point(
1155        self: RenderHandle<Self>,
1156        app: &mut App,
1157        global_position: Offset,
1158    ) -> TextPosition {
1159        self.compute_text_metrics_if_needed(app);
1160        let local =
1161            self.as_box().global_to_local(app, global_position, None) - self.paint_offset(app);
1162        self.get(app).text_painter.get_position_for_offset(local)
1163    }
1164
1165    /// Returns the [`Rect`] in local coordinates for the caret at the given text
1166    /// position.
1167    pub fn get_local_rect_for_caret(
1168        self: RenderHandle<Self>,
1169        app: &mut App,
1170        caret_position: TextPosition,
1171    ) -> Rect {
1172        self.compute_text_metrics_if_needed(app);
1173        let caret_prototype = self.get(app).caret_prototype;
1174        let cursor_offset = self.get(app).cursor_offset;
1175        let (painter, fonts) = self.painter_with_fonts(app);
1176        let caret_offset = painter.get_offset_for_caret(fonts, caret_position, caret_prototype);
1177        let mut caret_rect = caret_prototype.shift(caret_offset + cursor_offset);
1178        let painter_width = painter.width();
1179        let size = self.size(app);
1180        let caret_margin = self.caret_margin(app);
1181        let scrollable_width = (painter_width + caret_margin).max(size.width());
1182        let caret_x = clamp_double(
1183            caret_rect.left,
1184            0.0,
1185            (scrollable_width - caret_margin).max(0.0),
1186        );
1187        caret_rect = Offset::new(caret_x, caret_rect.top) & caret_rect.size();
1188        let (painter, fonts) = self.painter_with_fonts(app);
1189        let full_height = painter.get_full_height_for_caret(fonts, caret_position, caret_prototype);
1190        caret_rect = match app.platform().target_platform() {
1191            TargetPlatform::IOS | TargetPlatform::MacOS => {
1192                let height_diff = full_height - caret_rect.height();
1193                Rect::from_ltwh(
1194                    caret_rect.left,
1195                    caret_rect.top + height_diff / 2.0,
1196                    caret_rect.width(),
1197                    caret_rect.height(),
1198                )
1199            }
1200            TargetPlatform::Android
1201            | TargetPlatform::Fuchsia
1202            | TargetPlatform::Linux
1203            | TargetPlatform::Windows => {
1204                let caret_height = self.cursor_height(app);
1205                let height_diff = full_height - caret_height;
1206                Rect::from_ltwh(
1207                    caret_rect.left,
1208                    caret_rect.top - K_CARET_HEIGHT_OFFSET + height_diff / 2.0,
1209                    caret_rect.width(),
1210                    caret_height,
1211                )
1212            }
1213        };
1214        caret_rect = caret_rect.shift(self.paint_offset(app));
1215        caret_rect.shift(self.snap_to_physical_pixel(app, caret_rect.top_left()))
1216    }
1217
1218    fn set_text_editing_value(
1219        self: RenderHandle<Self>,
1220        app: &mut App,
1221        new_value: inset_embedder::TextEditingValue,
1222        cause: SelectionChangedCause,
1223    ) {
1224        self.get(app)
1225            .text_selection_delegate
1226            .user_update_text_editing_value(app, new_value, cause);
1227    }
1228
1229    fn apply_selection(
1230        self: RenderHandle<Self>,
1231        app: &mut App,
1232        mut next_selection: TextSelection,
1233        cause: SelectionChangedCause,
1234    ) {
1235        if next_selection.is_valid() {
1236            let text_length = utf16_len(
1237                &self
1238                    .get(app)
1239                    .text_selection_delegate
1240                    .text_editing_value(app)
1241                    .text,
1242            );
1243            next_selection = next_selection
1244                .copy_with()
1245                .base_offset(next_selection.base_offset.min(text_length))
1246                .extent_offset(next_selection.extent_offset.min(text_length));
1247        }
1248        let value = self
1249            .get(app)
1250            .text_selection_delegate
1251            .text_editing_value(app)
1252            .copy_with()
1253            .selection(next_selection);
1254        self.set_text_editing_value(app, value, cause);
1255    }
1256
1257    /// If [`ignore_pointer`](Self::ignore_pointer) is false then this method is called by
1258    /// the internal gesture recognizer. When it is true, an ancestor must call it.
1259    pub fn handle_tap_down(self: RenderHandle<Self>, _app: &mut App, details: &TapDownDetails) {
1260        self.get_mut(_app).last_tap_down_position = Some(details.global_position);
1261    }
1262
1263    /// The position of the most recent secondary tap down event on this text
1264    /// input.
1265    pub fn last_secondary_tap_down_position(self: RenderHandle<Self>, app: &App) -> Option<Offset> {
1266        self.get(app).last_secondary_tap_down_position
1267    }
1268
1269    /// Tracks the position of a secondary tap event.
1270    ///
1271    /// Should be called before attempting to change the selection based on the
1272    /// position of a secondary tap.
1273    pub fn handle_secondary_tap_down(
1274        self: RenderHandle<Self>,
1275        app: &mut App,
1276        details: &TapDownDetails,
1277    ) {
1278        self.get_mut(app).last_tap_down_position = Some(details.global_position);
1279        self.get_mut(app).last_secondary_tap_down_position = Some(details.global_position);
1280    }
1281
1282    /// Select the word at the last tap-down, or the position if the cause is a tap.
1283    pub fn handle_tap(self: RenderHandle<Self>, app: &mut App) {
1284        self.select_position(app, SelectionChangedCause::Tap);
1285    }
1286
1287    /// Select the word around the last tap-down.
1288    pub fn handle_double_tap(self: RenderHandle<Self>, app: &mut App) {
1289        self.select_word(app, SelectionChangedCause::DoubleTap);
1290    }
1291
1292    /// Select the word around the last long-press.
1293    pub fn handle_long_press(self: RenderHandle<Self>, app: &mut App) {
1294        self.select_word(app, SelectionChangedCause::LongPress);
1295    }
1296
1297    /// Move the selection to the location of the last tap down.
1298    pub fn select_position(self: RenderHandle<Self>, app: &mut App, cause: SelectionChangedCause) {
1299        let from = self
1300            .get(app)
1301            .last_tap_down_position
1302            .expect("select_position needs a tap-down");
1303        self.select_position_at(app, from, None, cause);
1304    }
1305
1306    /// Select text between the global positions `from` and `to`.
1307    pub fn select_position_at(
1308        self: RenderHandle<Self>,
1309        app: &mut App,
1310        from: Offset,
1311        to: Option<Offset>,
1312        cause: SelectionChangedCause,
1313    ) {
1314        self.compute_text_metrics_if_needed(app);
1315        let paint_offset = self.paint_offset(app);
1316        let from_position = self
1317            .get(app)
1318            .text_painter
1319            .get_position_for_offset(self.as_box().global_to_local(app, from, None) - paint_offset);
1320        let to_position = to.map(|to| {
1321            self.get(app).text_painter.get_position_for_offset(
1322                self.as_box().global_to_local(app, to, None) - paint_offset,
1323            )
1324        });
1325        let new_selection = TextSelection {
1326            base_offset: from_position.offset,
1327            extent_offset: to_position.unwrap_or(from_position).offset,
1328            affinity: from_position.affinity,
1329            is_directional: false,
1330        };
1331        self.apply_selection(app, new_selection, cause);
1332    }
1333
1334    /// Select a word around the location of the last tap down.
1335    pub fn select_word(self: RenderHandle<Self>, app: &mut App, cause: SelectionChangedCause) {
1336        let from = self
1337            .get(app)
1338            .last_tap_down_position
1339            .expect("select_word needs a tap-down");
1340        self.select_words_in_range(app, from, None, cause);
1341    }
1342
1343    /// Selects the set words of a paragraph that intersect a given range of global positions.
1344    pub fn select_words_in_range(
1345        self: RenderHandle<Self>,
1346        app: &mut App,
1347        from: Offset,
1348        to: Option<Offset>,
1349        cause: SelectionChangedCause,
1350    ) {
1351        self.compute_text_metrics_if_needed(app);
1352        let paint_offset = self.paint_offset(app);
1353        let from_position = self
1354            .get(app)
1355            .text_painter
1356            .get_position_for_offset(self.as_box().global_to_local(app, from, None) - paint_offset);
1357        let from_word = self.get_word_at_offset(app, from_position);
1358        let to_position = match to {
1359            None => from_position,
1360            Some(to) => self.get(app).text_painter.get_position_for_offset(
1361                self.as_box().global_to_local(app, to, None) - paint_offset,
1362            ),
1363        };
1364        let to_word = if to_position == from_position {
1365            from_word
1366        } else {
1367            self.get_word_at_offset(app, to_position)
1368        };
1369        let is_from_word_before_to_word = from_word.start() < to_word.end();
1370        self.apply_selection(
1371            app,
1372            TextSelection {
1373                base_offset: if is_from_word_before_to_word {
1374                    from_word.base().offset
1375                } else {
1376                    from_word.extent().offset
1377                },
1378                extent_offset: if is_from_word_before_to_word {
1379                    to_word.extent().offset
1380                } else {
1381                    to_word.base().offset
1382                },
1383                affinity: from_word.affinity,
1384                is_directional: false,
1385            },
1386            cause,
1387        );
1388    }
1389
1390    /// Move the selection to the beginning or end of a word.
1391    pub fn select_word_edge(self: RenderHandle<Self>, app: &mut App, cause: SelectionChangedCause) {
1392        self.compute_text_metrics_if_needed(app);
1393        let from = self
1394            .get(app)
1395            .last_tap_down_position
1396            .expect("select_word_edge needs a tap-down");
1397        let paint_offset = self.paint_offset(app);
1398        let position = self
1399            .get(app)
1400            .text_painter
1401            .get_position_for_offset(self.as_box().global_to_local(app, from, None) - paint_offset);
1402        let word = self.get(app).text_painter.get_word_boundary(position);
1403        let new_selection = if position.offset <= word.start {
1404            TextSelection::collapsed(word.start, TextAffinity::Downstream)
1405        } else {
1406            TextSelection::collapsed(word.end, TextAffinity::Upstream)
1407        };
1408        self.apply_selection(app, new_selection, cause);
1409    }
1410
1411    /// Returns a [`TextSelection`] that encompasses the word at the given
1412    /// [`TextPosition`].
1413    pub fn get_word_at_offset(
1414        self: RenderHandle<Self>,
1415        app: &mut App,
1416        position: TextPosition,
1417    ) -> TextSelection {
1418        let plain = self.plain_text(app);
1419        let length = utf16_len(&plain);
1420        if position.offset >= length {
1421            return TextSelection::from_position(TextPosition::with_affinity(
1422                length,
1423                TextAffinity::Upstream,
1424            ));
1425        }
1426        if self.get(app).obscure_text {
1427            return TextSelection::new(0, length);
1428        }
1429        let word = self.get(app).text_painter.get_word_boundary(position);
1430        let effective_offset = match position.affinity {
1431            TextAffinity::Upstream => position.offset - 1,
1432            TextAffinity::Downstream => position.offset,
1433        };
1434        debug_assert!(effective_offset >= 0);
1435        if effective_offset > 0
1436            && code_unit_at(&plain, effective_offset).is_some_and(TextLayoutMetrics::is_whitespace)
1437        {
1438            if let Some(previous_word) = self.get_previous_word(app, word.start) {
1439                match app.platform().target_platform() {
1440                    TargetPlatform::IOS => {
1441                        return TextSelection::new(previous_word.start, position.offset);
1442                    }
1443                    TargetPlatform::Android if self.get(app).read_only => {
1444                        return TextSelection::new(previous_word.start, position.offset);
1445                    }
1446                    _ => {}
1447                }
1448            } else {
1449                match app.platform().target_platform() {
1450                    TargetPlatform::IOS => {
1451                        if let Some(next_word) = self.get_next_word(app, word.start) {
1452                            return TextSelection::new(position.offset, next_word.end);
1453                        }
1454                        return TextSelection::collapsed(position.offset, position.affinity);
1455                    }
1456                    TargetPlatform::Android if self.get(app).read_only => {
1457                        return TextSelection::new(position.offset, position.offset + 1);
1458                    }
1459                    _ => {}
1460                }
1461            }
1462        }
1463        TextSelection::new(word.start, word.end)
1464    }
1465
1466    fn get_next_word(
1467        self: RenderHandle<Self>,
1468        app: &mut App,
1469        mut offset: i32,
1470    ) -> Option<TextRange> {
1471        loop {
1472            let range = self
1473                .get(app)
1474                .text_painter
1475                .get_word_boundary(TextPosition::new(offset));
1476            if !range.is_valid() || range.is_collapsed() {
1477                return None;
1478            }
1479            if !self.only_whitespace(app, range) {
1480                return Some(range);
1481            }
1482            offset = range.end;
1483        }
1484    }
1485
1486    fn get_previous_word(
1487        self: RenderHandle<Self>,
1488        app: &mut App,
1489        mut offset: i32,
1490    ) -> Option<TextRange> {
1491        while offset >= 0 {
1492            let range = self
1493                .get(app)
1494                .text_painter
1495                .get_word_boundary(TextPosition::new(offset));
1496            if !range.is_valid() || range.is_collapsed() {
1497                return None;
1498            }
1499            if !self.only_whitespace(app, range) {
1500                return Some(range);
1501            }
1502            offset = range.start - 1;
1503        }
1504        None
1505    }
1506
1507    fn only_whitespace(self: RenderHandle<Self>, app: &mut App, range: TextRange) -> bool {
1508        let plain = self.plain_text(app);
1509        for i in range.start..range.end {
1510            if let Some(unit) = code_unit_at(&plain, i)
1511                && !TextLayoutMetrics::is_whitespace(unit)
1512            {
1513                return false;
1514            }
1515        }
1516        true
1517    }
1518
1519    /// Return a [`TextSelection`] containing the line of the given [`TextPosition`].
1520    pub fn get_line_at_offset(
1521        self: RenderHandle<Self>,
1522        app: &mut App,
1523        position: TextPosition,
1524    ) -> TextSelection {
1525        self.compute_text_metrics_if_needed(app);
1526        let line = self.get(app).text_painter.get_line_boundary(position);
1527        if self.get(app).obscure_text {
1528            return TextSelection::new(0, utf16_len(&self.plain_text(app)));
1529        }
1530        TextSelection::new(line.start, line.end)
1531    }
1532
1533    /// A [`WordBoundary`] over this render object's text and layout.
1534    ///
1535    /// Dart's `RenderEditable.wordBoundaries`, which forwards to the text painter's.
1536    pub fn word_boundaries(self: RenderHandle<Self>, app: &App) -> WordBoundary {
1537        WordBoundary::new(
1538            self.text(app).expect("RenderEditable always has text"),
1539            self,
1540        )
1541    }
1542
1543    /// See `TextPainter.get_word_boundary`.
1544    pub fn get_word_boundary(
1545        self: RenderHandle<Self>,
1546        app: &mut App,
1547        position: TextPosition,
1548    ) -> TextRange {
1549        self.compute_text_metrics_if_needed(app);
1550        self.get(app).text_painter.get_word_boundary(position)
1551    }
1552
1553    /// Returns the TextPosition above the given offset into the text.
1554    pub fn get_text_position_above(
1555        self: RenderHandle<Self>,
1556        app: &mut App,
1557        position: TextPosition,
1558    ) -> TextPosition {
1559        self.compute_text_metrics_if_needed(app);
1560        let line_height = self.preferred_line_height(app);
1561        self.get_text_position_vertical(app, position, -0.5 * line_height)
1562    }
1563
1564    /// Returns the TextPosition below the given offset into the text.
1565    pub fn get_text_position_below(
1566        self: RenderHandle<Self>,
1567        app: &mut App,
1568        position: TextPosition,
1569    ) -> TextPosition {
1570        self.compute_text_metrics_if_needed(app);
1571        let line_height = self.preferred_line_height(app);
1572        self.get_text_position_vertical(app, position, 1.5 * line_height)
1573    }
1574
1575    fn get_text_position_vertical(
1576        self: RenderHandle<Self>,
1577        app: &mut App,
1578        position: TextPosition,
1579        vertical_offset: f64,
1580    ) -> TextPosition {
1581        let caret_prototype = self.get(app).caret_prototype;
1582        let (painter, fonts) = self.painter_with_fonts(app);
1583        let caret_offset = painter.get_offset_for_caret(fonts, position, caret_prototype);
1584        painter.get_position_for_offset(caret_offset.translate(0.0, vertical_offset))
1585    }
1586
1587    /// Starts a [`VerticalCaretMovementRun`] at the given location in the text.
1588    pub fn start_vertical_caret_movement(
1589        self: RenderHandle<Self>,
1590        app: &mut App,
1591        start_position: TextPosition,
1592    ) -> VerticalCaretMovementRun {
1593        self.compute_text_metrics_if_needed(app);
1594        let metrics = self.get_mut(app).text_painter.compute_line_metrics();
1595        let (painter, fonts) = self.painter_with_fonts(app);
1596        let (line, offset) = line_number_for(painter, fonts, start_position, &metrics);
1597        VerticalCaretMovementRun {
1598            editable: self,
1599            layout_generation: self.get(app).layout_generation,
1600            current_offset: offset,
1601            current_line: line,
1602            current_text_position: start_position,
1603            line_metrics: metrics,
1604            position_cache: HashMap::new(),
1605            is_valid: true,
1606        }
1607    }
1608
1609    fn paint_highlights(
1610        self: RenderHandle<Self>,
1611        app: &App,
1612        canvas: &mut inset_embedder::Canvas,
1613        offset: Offset,
1614    ) {
1615        let paint_offset = offset + self.paint_offset(app);
1616        let painter_size = self.get(app).text_painter.size();
1617        let clip = Rect::from_ltwh(0.0, 0.0, painter_size.width(), painter_size.height());
1618        if let (Some(color), Some(range)) = (
1619            self.get(app).prompt_rect_color,
1620            self.get(app).prompt_rect_range,
1621        ) && !range.is_collapsed()
1622        {
1623            paint_range(
1624                canvas,
1625                &self.get(app).text_painter,
1626                range,
1627                color,
1628                paint_offset,
1629                clip,
1630                self.get(app).selection_height_style,
1631                self.get(app).selection_width_style,
1632            );
1633        }
1634        if let (Some(color), Some(selection)) =
1635            (self.get(app).selection_color, self.get(app).selection)
1636            && !selection.is_collapsed()
1637        {
1638            paint_range(
1639                canvas,
1640                &self.get(app).text_painter,
1641                selection.range(),
1642                color,
1643                paint_offset,
1644                clip,
1645                self.get(app).selection_height_style,
1646                self.get(app).selection_width_style,
1647            );
1648        }
1649    }
1650
1651    fn paint_caret(
1652        self: RenderHandle<Self>,
1653        app: &mut App,
1654        canvas: &mut inset_embedder::Canvas,
1655        offset: Offset,
1656    ) {
1657        let Some(selection) = self.get(app).selection else {
1658            return;
1659        };
1660        let show_cursor = self.get(app).show_cursor;
1661        if !selection.is_collapsed() || !*show_cursor.value(app) {
1662            return;
1663        }
1664        let Some(color) = self.get(app).cursor_color else {
1665            return;
1666        };
1667        let caret = self
1668            .get_local_rect_for_caret(app, selection.extent())
1669            .shift(offset);
1670        canvas.draw_rect(
1671            caret,
1672            &Paint {
1673                color: color.into(),
1674                ..Paint::default()
1675            },
1676        );
1677    }
1678
1679    fn paint_contents(
1680        self: RenderHandle<Self>,
1681        app: &mut App,
1682        context: &mut PaintingContext,
1683        offset: Offset,
1684    ) {
1685        let effective = offset + self.paint_offset(app);
1686        if self.selection(app).is_some() {
1687            self.update_selection_extents_visibility(app, effective);
1688        }
1689        let above = self.get(app).paint_cursor_above_text;
1690        if !above {
1691            self.paint_caret(app, context.canvas(), offset);
1692        }
1693        self.paint_highlights(app, context.canvas(), offset);
1694        let (painter, fonts) = self.painter_with_fonts(app);
1695        painter.paint(fonts, context.canvas(), effective);
1696        self.paint_inline_children(app, context, effective);
1697        if above {
1698            self.paint_caret(app, context.canvas(), offset);
1699        }
1700    }
1701
1702    fn preferred_height(self: RenderHandle<Self>, app: &mut App, width: f64) -> f64 {
1703        let max_lines = self.get(app).max_lines;
1704        let min_lines = self.get(app).min_lines.or(max_lines);
1705        let line_height = self.preferred_line_height(app);
1706        let min_height = line_height * f64::from(min_lines.unwrap_or(0));
1707        match max_lines {
1708            None => {
1709                let estimated = if width.is_infinite() {
1710                    line_height * f64::from(count_hard_line_breaks(&self.plain_text(app)) + 1)
1711                } else {
1712                    let (min_width, max_width) = self.adjust_constraints(app, 0.0, width);
1713                    let (intrinsics, fonts) = self.text_intrinsics_with_fonts(app);
1714                    intrinsics.layout(fonts, min_width, max_width);
1715                    intrinsics.height()
1716                };
1717                estimated.max(min_height)
1718            }
1719            Some(1) => {
1720                let (min_width, max_width) = self.adjust_constraints(app, 0.0, width);
1721                let (intrinsics, fonts) = self.text_intrinsics_with_fonts(app);
1722                intrinsics.layout(fonts, min_width, max_width);
1723                intrinsics.height()
1724            }
1725            Some(max) if min_lines == Some(max) => min_height,
1726            Some(max) => {
1727                let max_height = line_height * f64::from(max);
1728                let (min_width, max_width) = self.adjust_constraints(app, 0.0, width);
1729                let (intrinsics, fonts) = self.text_intrinsics_with_fonts(app);
1730                intrinsics.layout(fonts, min_width, max_width);
1731                clamp_double(intrinsics.height(), min_height, max_height)
1732            }
1733        }
1734    }
1735}
1736
1737fn offset_or_cursor_changed(this: Handle<RenderEditable>, app: &mut App) {
1738    RenderHandle::from_handle(this).mark_needs_paint(app);
1739}
1740
1741fn utf16_len(text: &str) -> i32 {
1742    text.encode_utf16().count() as i32
1743}
1744
1745fn code_unit_at(text: &str, offset: i32) -> Option<i32> {
1746    text.encode_utf16().nth(offset as usize).map(i32::from)
1747}
1748
1749fn count_hard_line_breaks(text: &str) -> i32 {
1750    text.encode_utf16()
1751        .filter(|unit| matches!(*unit, 0x000A | 0x0085 | 0x000B | 0x000C | 0x2028 | 0x2029))
1752        .count() as i32
1753}
1754
1755fn line_number_for(
1756    painter: &mut TextPainter,
1757    fonts: &mut FontCollection,
1758    start_position: TextPosition,
1759    metrics: &[LineMetrics],
1760) -> (i32, Offset) {
1761    let offset = painter.get_offset_for_caret(fonts, start_position, Rect::ZERO);
1762    for line in metrics {
1763        if line.baseline > offset.dy() {
1764            return (line.line_number, Offset::new(offset.dx(), line.baseline));
1765        }
1766    }
1767    let last = metrics.last();
1768    (
1769        last.map(|m| m.line_number).unwrap_or(0).max(0),
1770        Offset::new(
1771            offset.dx(),
1772            last.map(|m| m.baseline + m.descent).unwrap_or(0.0),
1773        ),
1774    )
1775}
1776
1777#[allow(clippy::too_many_arguments)]
1778fn paint_range(
1779    canvas: &mut inset_embedder::Canvas,
1780    painter: &TextPainter,
1781    range: TextRange,
1782    color: Color,
1783    paint_offset: Offset,
1784    clip: Rect,
1785    height_style: BoxHeightStyle,
1786    width_style: BoxWidthStyle,
1787) {
1788    if !range.is_valid() || range.is_collapsed() {
1789        return;
1790    }
1791    let boxes = painter.get_boxes_for_selection(
1792        TextSelection::new(range.start, range.end),
1793        height_style,
1794        width_style,
1795    );
1796    let paint = Paint {
1797        color: color.into(),
1798        ..Paint::default()
1799    };
1800    for box_ in boxes {
1801        let rect = box_
1802            .to_rect()
1803            .shift(paint_offset)
1804            .intersect(clip.shift(paint_offset));
1805        if !rect.is_empty() {
1806            canvas.draw_rect(rect, &paint);
1807        }
1808    }
1809}
1810
1811impl RelayoutWhenSystemFontsChangeMixin for RenderEditable {
1812    fn relayout_when_system_fonts_change_data(
1813        self: RenderHandle<Self>,
1814        app: &App,
1815    ) -> &RelayoutWhenSystemFontsChangeData {
1816        &self.get(app).system_fonts
1817    }
1818
1819    fn relayout_when_system_fonts_change_data_mut(
1820        self: RenderHandle<Self>,
1821        app: &mut App,
1822    ) -> &mut RelayoutWhenSystemFontsChangeData {
1823        &mut self.get_mut(app).system_fonts
1824    }
1825
1826    fn system_fonts_did_change(self: RenderHandle<Self>, app: &mut App) {
1827        self.as_render_object(app).mark_needs_layout(app);
1828        self.get_mut(app).text_painter.mark_needs_layout();
1829    }
1830}
1831
1832impl ContainerRenderObjectMixin for RenderEditable {
1833    type ChildType = AnyRenderBox;
1834    type ParentDataType = TextParentData;
1835
1836    fn container_data(
1837        self: RenderHandle<Self>,
1838        app: &App,
1839    ) -> &ContainerRenderObjectData<AnyRenderBox> {
1840        &self.get(app).container
1841    }
1842
1843    fn container_data_mut(
1844        self: RenderHandle<Self>,
1845        app: &mut App,
1846    ) -> &mut ContainerRenderObjectData<AnyRenderBox> {
1847        &mut self.get_mut(app).container
1848    }
1849}
1850
1851impl RenderInlineChildrenContainerDefaults for RenderEditable {}
1852
1853impl RenderObject for RenderEditable {
1854    crate::render_object_accessors!();
1855
1856    fn visit_children(
1857        self: RenderHandle<Self>,
1858        app: &App,
1859        visitor: &mut dyn FnMut(AnyRenderObject),
1860    ) {
1861        // The custom-paint boxes, which Dart visits first, are not ported.
1862        ContainerRenderObjectMixin::visit_children(self, app, visitor)
1863    }
1864
1865    fn redepth_children(self: RenderHandle<Self>, app: &mut App) {
1866        ContainerRenderObjectMixin::redepth_children(self, app)
1867    }
1868
1869    fn did_attach(self: RenderHandle<Self>, app: &mut App, owner: Handle<PipelineOwner>) {
1870        RelayoutWhenSystemFontsChangeMixin::attach_system_fonts(self, app);
1871        ContainerRenderObjectMixin::did_attach(self, app, owner);
1872        let offset = self.get(app).offset;
1873        offset.add_listener(app, self.mark_needs_paint_listener());
1874        let show_cursor = self.get(app).show_cursor;
1875        show_cursor.add_listener(app, self.mark_needs_paint_listener());
1876    }
1877
1878    fn did_detach(self: RenderHandle<Self>, app: &mut App) {
1879        let offset = self.get(app).offset;
1880        offset.remove_listener(app, &self.mark_needs_paint_listener());
1881        let show_cursor = self.get(app).show_cursor;
1882        show_cursor.remove_listener(app, &self.mark_needs_paint_listener());
1883        RelayoutWhenSystemFontsChangeMixin::detach_system_fonts(self, app);
1884        ContainerRenderObjectMixin::did_detach(self, app);
1885    }
1886
1887    fn dispose(self: RenderHandle<Self>, app: &mut App) {
1888        LayerHandle::set_layer(app, |app| &mut self.get_mut(app).leader_layer_handler, None);
1889        LayerHandle::set_layer(app, |app| &mut self.get_mut(app).clip_rect_layer, None);
1890        let start_in_viewport = self.get(app).selection_start_in_viewport;
1891        let end_in_viewport = self.get(app).selection_end_in_viewport;
1892        app.get_mut(start_in_viewport).dispose();
1893        app.get_mut(end_in_viewport).dispose();
1894        crate::object::RenderObjectBase::dispose(self, app);
1895    }
1896
1897    fn perform_layout(self: RenderHandle<Self>, app: &mut App) {
1898        let constraints = self.constraints(app);
1899        let placeholder_dimensions = self.layout_inline_children(
1900            app,
1901            constraints.max_width,
1902            ChildLayoutHelper::layout_child,
1903            ChildLayoutHelper::get_baseline,
1904        );
1905        self.get_mut(app).placeholder_dimensions = Some(placeholder_dimensions.clone());
1906        let (min_width, max_width) =
1907            self.adjust_constraints(app, constraints.min_width, constraints.max_width);
1908        let (painter, fonts) = self.painter_with_fonts(app);
1909        painter.set_placeholder_dimensions(Some(placeholder_dimensions));
1910        painter.layout(fonts, min_width, max_width);
1911        let boxes = self
1912            .get_mut(app)
1913            .text_painter
1914            .inline_placeholder_boxes()
1915            .expect("laid out just now");
1916        self.position_inline_children(app, &boxes);
1917        self.compute_caret_prototype(app);
1918        let caret_margin = self.caret_margin(app);
1919        let painter_width = self.get(app).text_painter.width();
1920        let painter_height = self.get(app).text_painter.height();
1921        let width = if self.get(app).force_line {
1922            constraints.max_width
1923        } else {
1924            constraints.constrain_width(painter_width + caret_margin)
1925        };
1926        let preferred_height = match self.get(app).max_lines {
1927            None => {
1928                let min_lines = self.get(app).min_lines.unwrap_or(0);
1929                painter_height.max(self.preferred_line_height(app) * f64::from(min_lines))
1930            }
1931            Some(1) => painter_height,
1932            Some(max_lines) => {
1933                let line_height = self.preferred_line_height(app);
1934                let min_lines = self.get(app).min_lines.unwrap_or(max_lines);
1935                clamp_double(
1936                    painter_height,
1937                    line_height * f64::from(min_lines),
1938                    line_height * f64::from(max_lines),
1939                )
1940            }
1941        };
1942        let size = Size::new(width, constraints.constrain_height(preferred_height));
1943        self.set_size(app, size);
1944        let content_size = Size::new(painter_width + caret_margin, painter_height);
1945        self.get_mut(app).max_scroll_extent = self.get_max_scroll_extent(app, content_size);
1946        self.get_mut(app).layout_generation += 1;
1947        let viewport_extent = self.viewport_extent(app);
1948        let max_scroll = self.get(app).max_scroll_extent;
1949        let offset = self.get(app).offset;
1950        offset.apply_viewport_dimension(app, viewport_extent);
1951        offset.apply_content_dimensions(app, 0.0, max_scroll);
1952    }
1953
1954    fn paint(
1955        self: RenderHandle<Self>,
1956        app: &mut App,
1957        context: &mut PaintingContext,
1958        offset: Offset,
1959    ) {
1960        self.compute_text_metrics_if_needed(app);
1961        if self.has_visual_overflow(app) && self.get(app).clip_behavior != Clip::None {
1962            let size = self.size(app);
1963            let clip_behavior = self.get(app).clip_behavior;
1964            let old = self.get(app).clip_rect_layer.layer();
1965            let layer = context.push_clip_rect(
1966                app,
1967                self.as_object().needs_compositing(app),
1968                offset,
1969                Offset::ZERO & size,
1970                |app, context, offset| {
1971                    self.paint_contents(app, context, offset);
1972                },
1973                clip_behavior,
1974                old,
1975            );
1976            LayerHandle::set_layer(app, |app| &mut self.get_mut(app).clip_rect_layer, layer);
1977        } else {
1978            LayerHandle::set_layer(app, |app| &mut self.get_mut(app).clip_rect_layer, None);
1979            self.paint_contents(app, context, offset);
1980        }
1981        if let Some(selection) = self.selection(app)
1982            && selection.is_valid()
1983        {
1984            let endpoints = self.get_endpoints_for_selection(app, selection);
1985            self.paint_handle_layers(app, context, &endpoints, offset);
1986        }
1987    }
1988}
1989
1990impl RenderBox for RenderEditable {
1991    crate::render_box_accessors!();
1992
1993    fn setup_parent_data(self: RenderHandle<Self>, app: &mut App, child: AnyRenderObject) {
1994        RenderInlineChildrenContainerDefaults::setup_parent_data(self, app, child);
1995    }
1996
1997    fn compute_min_intrinsic_width(self: RenderHandle<Self>, app: &mut App, _height: f64) -> f64 {
1998        let placeholder_dimensions = self.layout_inline_children(
1999            app,
2000            f64::INFINITY,
2001            |app, child, _| Size::new(child.get_min_intrinsic_width(app, f64::INFINITY), 0.0),
2002            ChildLayoutHelper::get_dry_baseline,
2003        );
2004        let (min_width, max_width) = self.adjust_constraints(app, 0.0, f64::INFINITY);
2005        let (intrinsics, fonts) = self.text_intrinsics_with_fonts(app);
2006        intrinsics.set_placeholder_dimensions(Some(placeholder_dimensions));
2007        intrinsics.layout(fonts, min_width, max_width);
2008        intrinsics.min_intrinsic_width()
2009    }
2010
2011    fn compute_max_intrinsic_width(self: RenderHandle<Self>, app: &mut App, _height: f64) -> f64 {
2012        let placeholder_dimensions = self.layout_inline_children(
2013            app,
2014            f64::INFINITY,
2015            // Height and baseline is irrelevant as all text will be laid
2016            // out in a single line. Therefore, using 0.0 as a dummy for the height.
2017            |app, child, _| Size::new(child.get_max_intrinsic_width(app, f64::INFINITY), 0.0),
2018            ChildLayoutHelper::get_dry_baseline,
2019        );
2020        let (min_width, max_width) = self.adjust_constraints(app, 0.0, f64::INFINITY);
2021        let (intrinsics, fonts) = self.text_intrinsics_with_fonts(app);
2022        intrinsics.set_placeholder_dimensions(Some(placeholder_dimensions));
2023        intrinsics.layout(fonts, min_width, max_width);
2024        intrinsics.max_intrinsic_width() + self.caret_margin(app)
2025    }
2026
2027    fn compute_min_intrinsic_height(self: RenderHandle<Self>, app: &mut App, width: f64) -> f64 {
2028        self.compute_max_intrinsic_height(app, width)
2029    }
2030
2031    fn compute_max_intrinsic_height(self: RenderHandle<Self>, app: &mut App, width: f64) -> f64 {
2032        let placeholder_dimensions = self.layout_inline_children(
2033            app,
2034            width,
2035            ChildLayoutHelper::dry_layout_child,
2036            ChildLayoutHelper::get_dry_baseline,
2037        );
2038        let (intrinsics, _) = self.text_intrinsics_with_fonts(app);
2039        intrinsics.set_placeholder_dimensions(Some(placeholder_dimensions));
2040        self.preferred_height(app, width)
2041    }
2042
2043    fn compute_dry_layout(
2044        self: RenderHandle<Self>,
2045        app: &mut App,
2046        constraints: BoxConstraints,
2047    ) -> Size {
2048        let placeholder_dimensions = self.layout_inline_children(
2049            app,
2050            constraints.max_width,
2051            ChildLayoutHelper::dry_layout_child,
2052            ChildLayoutHelper::get_dry_baseline,
2053        );
2054        let (min_width, max_width) =
2055            self.adjust_constraints(app, constraints.min_width, constraints.max_width);
2056        let force_line = self.get(app).force_line;
2057        let caret_margin = self.caret_margin(app);
2058        let (intrinsics, fonts) = self.text_intrinsics_with_fonts(app);
2059        intrinsics.set_placeholder_dimensions(Some(placeholder_dimensions));
2060        intrinsics.layout(fonts, min_width, max_width);
2061        let painter_width = intrinsics.width();
2062        let width = if force_line {
2063            constraints.max_width
2064        } else {
2065            constraints.constrain_width(painter_width + caret_margin)
2066        };
2067        let height = self.preferred_height(app, constraints.max_width);
2068        Size::new(width, constraints.constrain_height(height))
2069    }
2070
2071    fn compute_distance_to_actual_baseline(
2072        self: RenderHandle<Self>,
2073        app: &mut App,
2074        baseline: TextBaseline,
2075    ) -> Option<f64> {
2076        self.compute_text_metrics_if_needed(app);
2077        Some(
2078            self.get(app)
2079                .text_painter
2080                .compute_distance_to_actual_baseline(baseline),
2081        )
2082    }
2083
2084    fn hit_test_self(self: RenderHandle<Self>, _app: &App, _position: Offset) -> bool {
2085        true
2086    }
2087
2088    fn hit_test_children(
2089        self: RenderHandle<Self>,
2090        app: &mut App,
2091        result: &mut BoxHitTestResult<'_>,
2092        position: Offset,
2093    ) -> bool {
2094        // Spans are not hit-test targets yet, so the hit falls to the inline children.
2095        let effective_position = position - self.paint_offset(app);
2096        self.hit_test_inline_children(app, result, effective_position)
2097    }
2098
2099    fn apply_paint_transform(
2100        self: RenderHandle<Self>,
2101        app: &App,
2102        child: AnyRenderObject,
2103        transform: &mut Matrix4,
2104    ) {
2105        // The custom-paint boxes, which Dart leaves untransformed, are not ported.
2106        self.default_apply_paint_transform(app, child, transform);
2107    }
2108}
2109
2110impl RenderEditable {
2111    /// Flutter's `_paintHandleLayers`: an empty leader at each selection endpoint, clamped into
2112    /// the box, for the selection handles to follow.
2113    fn paint_handle_layers(
2114        self: RenderHandle<Self>,
2115        app: &mut App,
2116        context: &mut PaintingContext,
2117        endpoints: &[TextSelectionPoint],
2118        offset: Offset,
2119    ) {
2120        let size = self.size(app);
2121        let clamp = |point: Offset| {
2122            Offset::new(
2123                point.dx().clamp(0.0, size.width()),
2124                point.dy().clamp(0.0, size.height()),
2125            )
2126        };
2127        let Some(start) = endpoints.first() else {
2128            return;
2129        };
2130        let start_point = clamp(start.point);
2131        let (start_link, end_link) = {
2132            let this = self.get(app);
2133            (this.start_handle_layer_link, this.end_handle_layer_link)
2134        };
2135        let start_layer = LeaderLayer::new(app, start_link);
2136        start_layer.set_offset(app, start_point + offset);
2137        LayerHandle::set_layer(
2138            app,
2139            |app| &mut self.get_mut(app).leader_layer_handler,
2140            Some(start_layer),
2141        );
2142        context.push_layer(
2143            app,
2144            start_layer.as_container_layer(),
2145            |_, _, _| {},
2146            Offset::ZERO,
2147            None,
2148        );
2149        if let Some(end) = endpoints.get(1) {
2150            let end_point = clamp(end.point);
2151            let end_layer = LeaderLayer::new(app, end_link);
2152            end_layer.set_offset(app, end_point + offset);
2153            context.push_layer(
2154                app,
2155                end_layer.as_container_layer(),
2156                |_, _, _| {},
2157                Offset::ZERO,
2158                None,
2159            );
2160        } else if self
2161            .selection(app)
2162            .is_some_and(|selection| selection.is_collapsed())
2163        {
2164            let end_layer = LeaderLayer::new(app, end_link);
2165            end_layer.set_offset(app, start_point + offset);
2166            context.push_layer(
2167                app,
2168                end_layer.as_container_layer(),
2169                |_, _, _| {},
2170                Offset::ZERO,
2171                None,
2172            );
2173        }
2174    }
2175}
2176
2177#[cfg(test)]
2178mod tests {
2179    use std::cell::Cell;
2180    use std::rc::Rc;
2181
2182    use inset_embedder::{FontSource, TextAffinity};
2183    use inset_foundation::{AppCell, Task, ValueListenable};
2184    use inset_painting::{PaintingBinding, TextSpan, TextStyle};
2185    use inset_scheduler::SchedulerBinding;
2186    use inset_services::{TextEditingValue, TextSelectionDelegate};
2187    use valo::{Font, FontAttrs};
2188
2189    use super::*;
2190    use crate::layer::{ContainerLayer, ErasedLayer, OffsetLayer};
2191    use crate::pipeline_owner::PipelineOwner;
2192    use crate::proxy_box::RenderRepaintBoundary;
2193    use crate::viewport_offset::{FixedViewportOffset, ViewportOffset};
2194
2195    #[derive(Default)]
2196    struct TestDelegate {
2197        value: TextEditingValue,
2198    }
2199
2200    impl TextSelectionDelegate for TestDelegate {
2201        fn text_editing_value(self: Handle<Self>, app: &App) -> TextEditingValue {
2202            app.get(self).value.clone()
2203        }
2204
2205        fn user_update_text_editing_value(
2206            self: Handle<Self>,
2207            app: &mut App,
2208            value: TextEditingValue,
2209            _cause: SelectionChangedCause,
2210        ) {
2211            app.get_mut(self).value = value;
2212        }
2213
2214        fn hide_toolbar(self: Handle<Self>, _app: &mut App, _hide_handles: bool) {}
2215
2216        fn bring_into_view(self: Handle<Self>, _app: &mut App, _position: TextPosition) {}
2217
2218        fn cut_selection(self: Handle<Self>, _app: &mut App, _cause: SelectionChangedCause) {}
2219
2220        fn paste_text(
2221            self: Handle<Self>,
2222            _app: &mut App,
2223            _cause: SelectionChangedCause,
2224        ) -> Task<()> {
2225            Task::ready(())
2226        }
2227
2228        fn select_all(self: Handle<Self>, _app: &mut App, _cause: SelectionChangedCause) {}
2229
2230        fn copy_selection(self: Handle<Self>, _app: &mut App, _cause: SelectionChangedCause) {}
2231    }
2232
2233    fn install_fonts(app: &mut App) {
2234        let binding = PaintingBinding::instance(app);
2235        if !binding.has_fonts(app) {
2236            binding.install_fonts(app, |fonts| {
2237                fonts.add_source(valo_system_fonts::SystemFonts::load());
2238            });
2239        }
2240    }
2241
2242    fn span(text: &str) -> InlineSpanRef {
2243        TextSpan::new()
2244            .text(text)
2245            .style(TextStyle::new().font_size(20.0))
2246            .into_span()
2247    }
2248
2249    fn laid_out(app: &mut App, text: &str) -> RenderHandle<RenderEditable> {
2250        install_fonts(app);
2251        let delegate = app.create(TestDelegate {
2252            value: TextEditingValue::new()
2253                .text(text)
2254                .selection(TextSelection::collapsed(0, TextAffinity::Downstream)),
2255        });
2256        let start = LayerLink::new(app);
2257        let end = LayerLink::new(app);
2258        let offset = FixedViewportOffset::zero(app).as_viewport_offset();
2259        let editable = RenderEditable::new(
2260            app,
2261            TextDirection::Ltr,
2262            start,
2263            end,
2264            offset,
2265            delegate.as_text_selection_delegate(),
2266        );
2267        editable.set_text(app, Some(span(text)));
2268        editable.set_cursor_color(app, Some(Color::from_argb(255, 0, 0, 0)));
2269        let root = RenderRepaintBoundary::new(app, Some(editable.as_box()));
2270        let owner = PipelineOwner::new(app, None, None);
2271        owner.set_root_node(app, Some(root.as_object()));
2272        root.schedule_initial_layout(app);
2273        root.layout(
2274            app,
2275            BoxConstraints::tight_for(Some(200.0), Some(40.0)),
2276            false,
2277        );
2278        let paint_root = OffsetLayer::new(app, Offset::ZERO);
2279        paint_root.as_layer().attach(app, root.as_object().id());
2280        root.as_object()
2281            .schedule_initial_paint(app, paint_root.as_container_layer());
2282        editable
2283    }
2284
2285    #[test]
2286    fn move_by_word_boundary_skips_spaces_and_punctuation() {
2287        let cell = AppCell::new();
2288        let mut app = cell.borrow_mut();
2289        let text = concat!(
2290            "ABC   ABC\n",                       // [0, 10)
2291            "A\u{41}\u{301}    \u{41}\u{301}\n", // [10, 20)
2292            "         \n",                       // [20, 30)
2293            "ABC!!!ABC\n",                       // [30, 40)
2294            "  !ABC !!\n",                       // [40, 50)
2295            "A  \u{115CB}\u{115CB} A\n",         // [50, 60)
2296        );
2297        let editable = laid_out(&mut app, text);
2298        let boundary = editable.word_boundaries(&app).move_by_word_boundary();
2299
2300        // 4 points to the 2nd whitespace in the first line.
2301        // Don't break between horizontal spaces and letters/numbers.
2302        assert_eq!(boundary.get_leading_text_boundary_at(&mut app, 4), Some(0));
2303        assert_eq!(boundary.get_trailing_text_boundary_at(&mut app, 4), Some(9));
2304
2305        // Works when words are starting/ending with a combining diacritical mark.
2306        assert_eq!(
2307            boundary.get_leading_text_boundary_at(&mut app, 14),
2308            Some(10)
2309        );
2310        assert_eq!(
2311            boundary.get_trailing_text_boundary_at(&mut app, 14),
2312            Some(19)
2313        );
2314
2315        // Do break before and after newlines.
2316        assert_eq!(
2317            boundary.get_leading_text_boundary_at(&mut app, 24),
2318            Some(20)
2319        );
2320        assert_eq!(
2321            boundary.get_trailing_text_boundary_at(&mut app, 24),
2322            Some(29)
2323        );
2324
2325        // Do not break on punctuations.
2326        assert_eq!(
2327            boundary.get_leading_text_boundary_at(&mut app, 34),
2328            Some(30)
2329        );
2330        assert_eq!(
2331            boundary.get_trailing_text_boundary_at(&mut app, 34),
2332            Some(39)
2333        );
2334
2335        // Ok to break if next to punctuations or separating spaces.
2336        assert_eq!(
2337            boundary.get_leading_text_boundary_at(&mut app, 44),
2338            Some(43)
2339        );
2340        assert_eq!(
2341            boundary.get_trailing_text_boundary_at(&mut app, 44),
2342            Some(46)
2343        );
2344
2345        // 54 points to a low surrogate of a punctuation.
2346        assert_eq!(
2347            boundary.get_leading_text_boundary_at(&mut app, 54),
2348            Some(50)
2349        );
2350        assert_eq!(
2351            boundary.get_trailing_text_boundary_at(&mut app, 54),
2352            Some(59)
2353        );
2354    }
2355
2356    #[test]
2357    fn display_names_the_direction() {
2358        let point = Offset::new(1.0, 2.0);
2359        assert_eq!(
2360            TextSelectionPoint::new(point, Some(TextDirection::Ltr)).to_string(),
2361            format!("{point:?}-ltr")
2362        );
2363        assert_eq!(
2364            TextSelectionPoint::new(point, None).to_string(),
2365            format!("{point:?}")
2366        );
2367    }
2368
2369    #[test]
2370    fn layout_gives_the_text_a_size() {
2371        let cell = AppCell::new();
2372        let mut app = cell.borrow_mut();
2373        let editable = laid_out(&mut app, "hi");
2374        let size = editable.size(&app);
2375        assert!(size.width() > 0.0);
2376        assert!(size.height() > 0.0);
2377    }
2378
2379    #[test]
2380    fn select_position_updates_the_delegate() {
2381        let cell = AppCell::new();
2382        let mut app = cell.borrow_mut();
2383        let editable = laid_out(&mut app, "hello");
2384        let origin = editable.as_box().local_to_global(&app, Offset::ZERO, None);
2385        editable.select_position_at(
2386            &mut app,
2387            origin + Offset::new(2.0, 2.0),
2388            None,
2389            SelectionChangedCause::Tap,
2390        );
2391        let value = editable
2392            .text_selection_delegate(&app)
2393            .text_editing_value(&app);
2394        assert!(value.selection.is_valid());
2395        assert!(value.selection.is_collapsed());
2396    }
2397
2398    #[test]
2399    fn handle_secondary_tap_down_records_the_position() {
2400        let cell = AppCell::new();
2401        let mut app = cell.borrow_mut();
2402        let editable = laid_out(&mut app, "hello");
2403        let at = Offset::new(12.0, 8.0);
2404        editable.handle_secondary_tap_down(&mut app, &TapDownDetails::new(at, None, None));
2405        assert_eq!(editable.last_secondary_tap_down_position(&app), Some(at));
2406    }
2407
2408    #[test]
2409    fn selection_extents_default_in_the_viewport() {
2410        let cell = AppCell::new();
2411        let mut app = cell.borrow_mut();
2412        let editable = laid_out(&mut app, "hello");
2413        assert!(
2414            *editable.selection_start_in_viewport(&app).value(&app),
2415            "Flutter defaults selectionStartInViewport to true"
2416        );
2417        assert!(
2418            *editable.selection_end_in_viewport(&app).value(&app),
2419            "Flutter defaults selectionEndInViewport to true"
2420        );
2421    }
2422
2423    #[test]
2424    fn system_fonts_change_marks_the_editable_dirty() {
2425        let cell = AppCell::new();
2426        let mut app = cell.borrow_mut();
2427        let editable = laid_out(&mut app, "hello");
2428        assert!(!editable.as_object().debug_needs_layout(&app));
2429        PaintingBinding::instance(&mut app).handle_system_fonts_did_change(&mut app);
2430        SchedulerBinding::handle_begin_frame(&mut app, &[], None);
2431        assert!(
2432            editable.as_object().debug_needs_layout(&app),
2433            "RenderEditable listens to PaintingBinding.system_fonts"
2434        );
2435    }
2436
2437    /// A source that can answer ASCII immediately and hold non-ASCII until the
2438    /// test opens the gate — the web host's delayed Noto path.
2439    struct GatedFonts {
2440        inner: valo_system_fonts::SystemFonts,
2441        allow_non_ascii: Rc<Cell<bool>>,
2442    }
2443
2444    impl FontSource for GatedFonts {
2445        fn family(&mut self, _name: &str) -> Vec<Font> {
2446            // Named families on this machine (PingFang, the system UI font) cover
2447            // CJK. The gate is the codepoint path, so family lookup stays empty.
2448            Vec::new()
2449        }
2450
2451        fn face_for_codepoint(&mut self, codepoint: char, attrs: FontAttrs) -> Option<Font> {
2452            if !self.allow_non_ascii.get() && !codepoint.is_ascii() {
2453                return None;
2454            }
2455            self.inner.face_for_codepoint(codepoint, attrs)
2456        }
2457    }
2458
2459    #[test]
2460    fn system_fonts_change_rebuilds_glyphs_once_a_fallback_face_arrives() {
2461        let allow_non_ascii = Rc::new(Cell::new(false));
2462        let cell = AppCell::new();
2463        let mut app = cell.borrow_mut();
2464        PaintingBinding::instance(&mut app).install_fonts(&mut app, {
2465            let allow_non_ascii = Rc::clone(&allow_non_ascii);
2466            move |fonts| {
2467                fonts.add_source(GatedFonts {
2468                    inner: valo_system_fonts::SystemFonts::load(),
2469                    allow_non_ascii,
2470                });
2471            }
2472        });
2473
2474        let text = "a你";
2475        let delegate = app.create(TestDelegate {
2476            value: TextEditingValue::new()
2477                .text(text)
2478                .selection(TextSelection::collapsed(0, TextAffinity::Downstream)),
2479        });
2480        let start = LayerLink::new(&mut app);
2481        let end = LayerLink::new(&mut app);
2482        let offset = FixedViewportOffset::zero(&mut app).as_viewport_offset();
2483        let editable = RenderEditable::new(
2484            &mut app,
2485            TextDirection::Ltr,
2486            start,
2487            end,
2488            offset,
2489            delegate.as_text_selection_delegate(),
2490        );
2491        editable.set_text(&mut app, Some(span(text)));
2492        let root = RenderRepaintBoundary::new(&mut app, Some(editable.as_box()));
2493        let owner = PipelineOwner::new(&mut app, None, None);
2494        owner.set_root_node(&mut app, Some(root.as_object()));
2495        root.schedule_initial_layout(&mut app);
2496        let constraints = BoxConstraints::tight_for(Some(200.0), Some(40.0));
2497        root.layout(&mut app, constraints, false);
2498
2499        let cjk = TextRange::new(1, 2);
2500        let before = editable
2501            .get_rect_for_composing_range(&mut app, cjk)
2502            .expect("an uncovered CJK character still occupies .notdef space");
2503
2504        allow_non_ascii.set(true);
2505        PaintingBinding::instance(&mut app).handle_system_fonts_did_change(&mut app);
2506        SchedulerBinding::handle_begin_frame(&mut app, &[], None);
2507        root.layout(&mut app, constraints, false);
2508
2509        let after = editable
2510            .get_rect_for_composing_range(&mut app, cjk)
2511            .expect("systemFontsDidChange rebuilds the painter against the new face");
2512        assert_ne!(
2513            before.width(),
2514            after.width(),
2515            "the CJK glyph replaces .notdef once its face is available"
2516        );
2517    }
2518}