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