Skip to main content

inset_rendering/
editable.rs

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