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