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