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