1mod rich;
20
21use iced_widget::graphics::text::{Paragraph, cosmic_text};
22pub use rich::Rich;
23use text::{Ellipsis, LineHeight, Shaping, Wrapping};
24pub use text::{Fragment, Highlighter, IntoFragment, Span};
25
26use crate::click;
27use crate::core::alignment;
28use crate::core::keyboard::{self, key};
29use crate::core::layout;
30use crate::core::mouse;
31use crate::core::renderer;
32use crate::core::text;
33use crate::core::text::Paragraph as _;
34use crate::core::time::Duration;
35use crate::core::touch;
36use crate::core::widget;
37use crate::core::widget::text::Format;
38use crate::core::widget::tree::{self, Tree};
39use crate::core::{
40 self, Color, Event, Font, Layout, Length, Pixels, Point, Size, Theme,
41 Widget,
42};
43use crate::selection::{Selection, SelectionEnd};
44
45#[must_use]
62pub struct Text<'a, Theme = iced_widget::Theme>
63where
64 Theme: Catalog,
65{
66 fragment: Fragment<'a>,
67 format: Format,
68 click_interval: Option<Duration>,
69 class: Theme::Class<'a>,
70}
71
72impl<'a, Theme> Text<'a, Theme>
73where
74 Theme: Catalog,
75{
76 pub fn new(fragment: impl IntoFragment<'a>) -> Self {
78 Self {
79 fragment: fragment.into_fragment(),
80 format: Format::default(),
81 click_interval: None,
82 class: Theme::default(),
83 }
84 }
85
86 pub fn size(mut self, size: impl Into<Pixels>) -> Self {
88 self.format.size = Some(size.into());
89 self
90 }
91
92 pub fn line_height(mut self, line_height: impl Into<LineHeight>) -> Self {
94 self.format.line_height = Some(line_height.into());
95 self
96 }
97
98 pub fn font(mut self, font: impl Into<Font>) -> Self {
100 self.format.font = Some(font.into());
101 self
102 }
103
104 pub fn font_maybe(mut self, font: impl Into<Option<Font>>) -> Self {
106 self.format.font = font.into();
107 self
108 }
109
110 pub fn width(mut self, width: impl Into<Length>) -> Self {
112 self.format.width = width.into();
113 self
114 }
115
116 pub fn height(mut self, height: impl Into<Length>) -> Self {
118 self.format.height = height.into();
119 self
120 }
121
122 pub fn center(self) -> Self {
124 self.align_x(alignment::Horizontal::Center)
125 .align_y(alignment::Vertical::Center)
126 }
127
128 pub fn align_x(mut self, alignment: impl Into<text::Alignment>) -> Self {
130 self.format.align_x = alignment.into();
131 self
132 }
133
134 pub fn align_y(
136 mut self,
137 alignment: impl Into<alignment::Vertical>,
138 ) -> Self {
139 self.format.align_y = alignment.into();
140 self
141 }
142
143 pub fn shaping(mut self, shaping: Shaping) -> Self {
145 self.format.shaping = shaping;
146 self
147 }
148
149 pub fn wrapping(mut self, wrapping: Wrapping) -> Self {
151 self.format.wrapping = wrapping;
152 self
153 }
154
155 pub fn ellipsis(mut self, ellipsis: Ellipsis) -> Self {
157 self.format.ellipsis = ellipsis;
158 self
159 }
160
161 pub fn click_interval(mut self, click_interval: Duration) -> Self {
166 self.click_interval = Some(click_interval);
167 self
168 }
169
170 pub fn style(mut self, style: impl Fn(&Theme) -> Style + 'a) -> Self
172 where
173 Theme::Class<'a>: From<StyleFn<'a, Theme>>,
174 {
175 self.class = (Box::new(style) as StyleFn<'a, Theme>).into();
176 self
177 }
178
179 pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
181 self.class = class.into();
182 self
183 }
184}
185
186#[derive(Debug, Default, Clone)]
188pub struct State {
189 paragraph: Paragraph,
190 content: String,
191 is_hovered: bool,
192 selection: Selection,
193 dragging: Option<Dragging>,
194 last_click: Option<click::Click>,
195 keyboard_modifiers: keyboard::Modifiers,
196 visual_lines_bounds: Vec<core::Rectangle>,
197}
198
199#[derive(Debug, Clone, Copy, PartialEq, Eq)]
201#[allow(missing_docs)]
202pub enum Dragging {
203 Grapheme,
204 Word,
205 Line,
206}
207
208impl State {
209 fn grapheme_line_and_index(
210 &self,
211 point: Point,
212 bounds: core::Rectangle,
213 ) -> Option<(usize, usize)> {
214 let bounded_x = if point.y < bounds.y {
215 bounds.x
216 } else if point.y > bounds.y + bounds.height {
217 bounds.x + bounds.width
218 } else {
219 point.x.max(bounds.x).min(bounds.x + bounds.width)
220 };
221 let bounded_y = point.y.max(bounds.y).min(bounds.y + bounds.height);
222 let bounded_point = Point::new(bounded_x, bounded_y);
223 let mut relative_point =
224 bounded_point - core::Vector::new(bounds.x, bounds.y);
225
226 let buffer = self.paragraph.buffer();
227 let line_height = buffer.metrics().line_height;
228 let visual_line = (relative_point.y / line_height).floor() as usize;
229 let visual_line_start_offset = self
230 .visual_lines_bounds
231 .get(visual_line)
232 .map(|r| r.x)
233 .unwrap_or_default();
234 let visual_line_end = self
235 .visual_lines_bounds
236 .get(visual_line)
237 .map(|r| r.x + r.width)
238 .unwrap_or_default();
239
240 if relative_point.x < visual_line_start_offset {
241 relative_point.x = visual_line_start_offset;
242 }
243
244 if relative_point.x > visual_line_end {
245 relative_point.x = visual_line_end;
246 }
247
248 let cursor = buffer.hit(relative_point.x, relative_point.y)?;
249 let value = buffer.lines[cursor.line].text();
250
251 Some((
252 cursor.line,
253 unicode_segmentation::UnicodeSegmentation::graphemes(
254 &value[..cursor.index.min(value.len())],
255 true,
256 )
257 .count(),
258 ))
259 }
260
261 fn selection(&self) -> Vec<core::Rectangle> {
262 let Selection { start, end, .. } = self.selection;
263
264 let buffer = self.paragraph.buffer();
265 let line_height = self.paragraph.buffer().metrics().line_height;
266 let selected_lines = end.line - start.line + 1;
267
268 let visual_lines_offset = visual_lines_offset(start.line, buffer);
269
270 buffer
271 .lines
272 .iter()
273 .skip(start.line)
274 .take(selected_lines)
275 .enumerate()
276 .flat_map(|(i, line)| {
277 highlight_line(
278 line,
279 if i == 0 { start.index } else { 0 },
280 if i == selected_lines - 1 {
281 end.index
282 } else {
283 line.text().len()
284 },
285 )
286 })
287 .enumerate()
288 .filter_map(|(visual_line, (x, width))| {
289 if width > 0.0 {
290 Some(core::Rectangle {
291 x,
292 width,
293 y: (visual_line as i32 + visual_lines_offset) as f32
294 * line_height
295 - buffer.scroll().vertical,
296 height: line_height,
297 })
298 } else {
299 None
300 }
301 })
302 .collect()
303 }
304
305 fn update(&mut self, text: text::Text<&str>) {
306 if self.content != text.content {
307 text.content.clone_into(&mut self.content);
308 self.paragraph = Paragraph::with_text(text);
309 self.update_visual_bounds();
310 return;
311 }
312
313 match self.paragraph.compare(text.with_content(())) {
314 text::Difference::None => {}
315 text::Difference::Bounds => {
316 self.paragraph.resize(text.bounds);
317 self.update_visual_bounds();
318 }
319 text::Difference::Shape => {
320 self.paragraph = Paragraph::with_text(text);
321 self.update_visual_bounds();
322 }
323 }
324 }
325
326 fn update_visual_bounds(&mut self) {
327 let buffer = self.paragraph.buffer();
328 let line_height = buffer.metrics().line_height;
329 self.visual_lines_bounds = buffer
330 .lines
331 .iter()
332 .flat_map(|line| highlight_line(line, 0, line.text().len()))
333 .enumerate()
334 .map(|(visual_line, (x, width))| core::Rectangle {
335 x,
336 width,
337 y: visual_line as f32 * line_height - buffer.scroll().vertical,
338 height: line_height,
339 })
340 .collect();
341 }
342}
343
344impl widget::operation::Text for State {
345 fn text(&self) -> Fragment<'_> {
346 self.paragraph
347 .buffer()
348 .lines
349 .iter()
350 .fold(String::new(), |mut text, next| {
351 text.push_str(next.text());
352 text
353 })
354 .into()
355 }
356
357 fn select(&mut self, start: Point, end: Point, target: text::Target) {
358 let bounds = core::Rectangle::INFINITE;
359
360 let start = {
361 let (line, index) = self
362 .grapheme_line_and_index(start, bounds)
363 .unwrap_or((0, 0));
364
365 SelectionEnd { line, index }
366 };
367
368 let end = {
369 let (line, index) =
370 self.grapheme_line_and_index(end, bounds).unwrap_or((0, 0));
371
372 SelectionEnd { line, index }
373 };
374
375 match target {
376 text::Target::Character => self.selection.select_range(start, end),
377 text::Target::Word => {
378 self.selection.select_word(
379 start.line,
380 start.index,
381 &self.paragraph,
382 );
383
384 self.selection
385 .change_selection_by_word(end, &self.paragraph);
386 }
387 text::Target::Line => {
388 self.selection.select_line(start.line, &self.paragraph);
389
390 self.selection
391 .change_selection_by_line(end.line, &self.paragraph);
392 }
393 };
394 }
395
396 fn select_all(&mut self) {
397 self.selection.select_all(&self.paragraph);
398 }
399
400 fn deselect(&mut self) {
401 self.selection.clear();
402 }
403
404 fn copy(&mut self) -> Option<String> {
405 (!self.selection.is_empty())
406 .then(|| self.selection.text(&self.paragraph))
407 }
408}
409
410impl<Theme> widget::Meta for Text<'_, Theme> where Theme: Catalog {}
411
412impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
413 for Text<'_, Theme>
414where
415 Theme: Catalog,
416 Renderer: text::Renderer<Paragraph = Paragraph>,
417{
418 fn tag(&self) -> tree::Tag {
419 tree::Tag::of::<State>()
420 }
421
422 fn state(&self) -> tree::State {
423 tree::State::new(State::default())
424 }
425
426 fn size(&self) -> Size<Length> {
427 Size {
428 width: self.format.width,
429 height: self.format.height,
430 }
431 }
432
433 fn layout(
434 &mut self,
435 tree: &mut Tree,
436 renderer: &Renderer,
437 limits: &layout::Limits,
438 ) {
439 tree.size = layout(
440 tree.state.downcast_mut::<State>(),
441 renderer,
442 limits,
443 &self.fragment,
444 self.format,
445 );
446 }
447
448 fn update(
449 &mut self,
450 tree: &mut Tree,
451 event: &Event,
452 layout: Layout,
453 cursor: mouse::Cursor,
454 _renderer: &Renderer,
455 shell: &mut core::Shell<'_, Message>,
456 viewport: &core::Rectangle,
457 ) {
458 let state = tree.state.downcast_mut::<State>();
459
460 let bounds = layout.bounds();
461 let click_position = cursor.position_in(bounds);
462
463 if viewport.intersection(&bounds).is_none()
464 && state.selection.is_empty()
465 && state.dragging.is_none()
466 {
467 return;
468 }
469
470 let was_hovered = state.is_hovered;
471 let selection_before = state.selection;
472 state.is_hovered = click_position.is_some();
473
474 match event {
475 Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
476 | Event::Touch(touch::Event::FingerPressed { .. }) => {
477 if let Some(position) = cursor.position_over(bounds) {
478 let click = click::Click::new(
479 position,
480 mouse::Button::Left,
481 state.last_click,
482 self.click_interval,
483 );
484
485 let (line, index) = state
486 .grapheme_line_and_index(position, bounds)
487 .unwrap_or((0, 0));
488
489 match click.kind() {
490 click::Kind::Single => {
491 let new_end = SelectionEnd { line, index };
492
493 if state.keyboard_modifiers.shift() {
494 state.selection.change_selection(new_end);
495 } else {
496 state.selection.select_range(new_end, new_end);
497 }
498
499 state.dragging = Some(Dragging::Grapheme);
500 }
501 click::Kind::Double => {
502 state.selection.select_word(
503 line,
504 index,
505 &state.paragraph,
506 );
507
508 state.dragging = Some(Dragging::Word);
509 }
510 click::Kind::Triple => {
511 state.selection.select_line(line, &state.paragraph);
512 state.dragging = Some(Dragging::Line);
513 }
514 }
515
516 state.last_click = Some(click);
517
518 shell.capture_event();
519 } else {
520 state.selection.clear();
521 }
522 }
523 Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
524 | Event::Touch(touch::Event::FingerLifted { .. })
525 | Event::Touch(touch::Event::FingerLost { .. }) => {
526 state.dragging = None;
527 }
528 Event::Mouse(mouse::Event::CursorMoved { .. })
529 | Event::Touch(touch::Event::FingerMoved { .. }) => {
530 if let Some(position) = cursor.land().position()
531 && let Some(dragging) = state.dragging
532 {
533 let (line, index) = state
534 .grapheme_line_and_index(position, bounds)
535 .unwrap_or((0, 0));
536
537 match dragging {
538 Dragging::Grapheme => {
539 let new_end = SelectionEnd { line, index };
540
541 state.selection.change_selection(new_end);
542 }
543 Dragging::Word => {
544 let new_end = SelectionEnd { line, index };
545
546 state.selection.change_selection_by_word(
547 new_end,
548 &state.paragraph,
549 );
550 }
551 Dragging::Line => {
552 state.selection.change_selection_by_line(
553 line,
554 &state.paragraph,
555 );
556 }
557 };
558 }
559 }
560 Event::Keyboard(keyboard::Event::KeyPressed { key, .. }) => {
561 match key.as_ref() {
562 keyboard::Key::Character("c")
563 if state.keyboard_modifiers.command()
564 && !state.selection.is_empty() =>
565 {
566 shell.write_clipboard(
567 state.selection.text(&state.paragraph),
568 );
569
570 shell.capture_event();
571 }
572 keyboard::Key::Character("a")
573 if state.keyboard_modifiers.command()
574 && !state.selection.is_empty() =>
575 {
576 state.selection.select_all(&state.paragraph);
577
578 shell.capture_event();
579 }
580 keyboard::Key::Named(key::Named::Home)
581 if state.keyboard_modifiers.shift()
582 && !state.selection.is_empty() =>
583 {
584 if state.keyboard_modifiers.jump() {
585 state.selection.select_beginning();
586 } else {
587 state.selection.select_line_beginning();
588 }
589
590 shell.capture_event();
591 }
592 keyboard::Key::Named(key::Named::End)
593 if state.keyboard_modifiers.shift()
594 && !state.selection.is_empty() =>
595 {
596 if state.keyboard_modifiers.jump() {
597 state.selection.select_end(&state.paragraph);
598 } else {
599 state.selection.select_line_end(&state.paragraph);
600 }
601
602 shell.capture_event();
603 }
604 keyboard::Key::Named(key::Named::ArrowLeft)
605 if state.keyboard_modifiers.shift()
606 && !state.selection.is_empty() =>
607 {
608 if state.keyboard_modifiers.macos_command() {
609 state.selection.select_line_beginning();
610 } else if state.keyboard_modifiers.jump() {
611 state
612 .selection
613 .select_left_by_words(&state.paragraph);
614 } else {
615 state.selection.select_left(&state.paragraph);
616 }
617
618 shell.capture_event();
619 }
620 keyboard::Key::Named(key::Named::ArrowRight)
621 if state.keyboard_modifiers.shift()
622 && !state.selection.is_empty() =>
623 {
624 if state.keyboard_modifiers.macos_command() {
625 state.selection.select_line_end(&state.paragraph);
626 } else if state.keyboard_modifiers.jump() {
627 state
628 .selection
629 .select_right_by_words(&state.paragraph);
630 } else {
631 state.selection.select_right(&state.paragraph);
632 }
633
634 shell.capture_event();
635 }
636 keyboard::Key::Named(key::Named::ArrowUp)
637 if state.keyboard_modifiers.shift()
638 && !state.selection.is_empty() =>
639 {
640 if state.keyboard_modifiers.macos_command() {
641 state.selection.select_beginning();
642 } else if state.keyboard_modifiers.jump() {
643 state.selection.select_line_beginning();
644 } else {
645 state.selection.select_up(&state.paragraph);
646 }
647
648 shell.capture_event();
649 }
650 keyboard::Key::Named(key::Named::ArrowDown)
651 if state.keyboard_modifiers.shift()
652 && !state.selection.is_empty() =>
653 {
654 if state.keyboard_modifiers.macos_command() {
655 state.selection.select_end(&state.paragraph);
656 } else if state.keyboard_modifiers.jump() {
657 state.selection.select_line_end(&state.paragraph);
658 } else {
659 state.selection.select_down(&state.paragraph);
660 }
661
662 shell.capture_event();
663 }
664 keyboard::Key::Named(key::Named::Escape) => {
665 state.dragging = None;
666 state.selection.clear();
667
668 state.keyboard_modifiers =
669 keyboard::Modifiers::default();
670
671 if state.selection != selection_before {
672 shell.capture_event();
673 }
674 }
675 _ => {}
676 }
677 }
678 Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => {
679 state.keyboard_modifiers = *modifiers;
680 }
681 _ => {}
682 }
683
684 if state.is_hovered != was_hovered
685 || state.selection != selection_before
686 {
687 shell.request_redraw();
688 }
689 }
690
691 fn draw(
692 &self,
693 tree: &Tree,
694 renderer: &mut Renderer,
695 theme: &Theme,
696 defaults: &renderer::Style,
697 layout: Layout,
698 _cursor_position: mouse::Cursor,
699 viewport: &core::Rectangle,
700 ) {
701 if !layout.bounds().intersects(viewport) {
702 return;
703 }
704
705 let state = tree.state.downcast_ref::<State>();
706 let style = theme.style(&self.class);
707
708 if !state.selection.is_empty() {
709 let bounds = layout.bounds();
710 let translation = bounds.position() - Point::ORIGIN;
711 let ranges = state.selection();
712
713 for range in ranges
714 .into_iter()
715 .filter_map(|range| bounds.intersection(&(range + translation)))
716 {
717 renderer.fill_quad(
718 renderer::Quad {
719 bounds: range,
720 ..renderer::Quad::default()
721 },
722 style.selection,
723 );
724 }
725 }
726
727 draw(
728 renderer,
729 defaults,
730 layout.bounds(),
731 &state.paragraph,
732 style,
733 viewport,
734 );
735 }
736
737 fn operate(
738 &mut self,
739 tree: &mut Tree,
740 layout: Layout,
741 _viewport: &core::Rectangle,
742 _renderer: &Renderer,
743 operation: &mut dyn widget::Operation,
744 ) {
745 let state = tree.state.downcast_mut::<State>();
746 operation.text(None, layout.bounds(), state);
747 }
748
749 fn mouse_interaction(
750 &self,
751 tree: &Tree,
752 _layout: Layout,
753 _cursor: mouse::Cursor,
754 _viewport: &core::Rectangle,
755 _renderer: &Renderer,
756 ) -> mouse::Interaction {
757 let state = tree.state.downcast_ref::<State>();
758
759 if state.is_hovered || state.dragging.is_some() {
760 mouse::Interaction::Text
761 } else {
762 mouse::Interaction::default()
763 }
764 }
765}
766
767pub fn layout<Renderer>(
771 state: &mut State,
772 renderer: &Renderer,
773 limits: &layout::Limits,
774 content: &str,
775 format: Format,
776) -> Size
777where
778 Renderer: text::Renderer<Paragraph = Paragraph>,
779{
780 layout::sized(limits, format.width, format.height, |limits| {
781 let bounds = limits.bounds();
782
783 let size = format.size.unwrap_or_else(|| renderer.text_size());
784 let font = format.font.unwrap_or_else(|| renderer.font());
785 let line_height =
786 format.line_height.unwrap_or_else(|| renderer.line_height());
787
788 state.update(text::Text {
789 content,
790 bounds,
791 size,
792 line_height,
793 font,
794 align_x: format.align_x,
795 align_y: format.align_y,
796 shaping: format.shaping,
797 wrapping: format.wrapping,
798 ellipsis: format.ellipsis,
799 hint_factor: renderer.hint_factor(),
800 });
801
802 state.paragraph.min_bounds()
803 })
804}
805
806pub fn draw<Renderer>(
808 renderer: &mut Renderer,
809 style: &renderer::Style,
810 bounds: core::Rectangle,
811 paragraph: &Paragraph,
812 appearance: Style,
813 viewport: &core::Rectangle,
814) where
815 Renderer: text::Renderer<Paragraph = Paragraph>,
816{
817 let anchor = bounds.anchor(
818 paragraph.min_bounds(),
819 paragraph.align_x(),
820 paragraph.align_y(),
821 );
822
823 renderer.fill_paragraph(
824 paragraph,
825 anchor,
826 appearance.color.unwrap_or(style.text_color),
827 *viewport,
828 );
829}
830
831impl<'a, Theme> From<&'a str> for Text<'a, Theme>
832where
833 Theme: Catalog + 'a,
834{
835 fn from(content: &'a str) -> Self {
836 Self::new(content)
837 }
838}
839
840#[derive(Debug, Clone, Copy, PartialEq, Default)]
842pub struct Style {
843 pub color: Option<Color>,
847 pub selection: Color,
849}
850
851pub trait Catalog: Sized {
853 type Class<'a>;
855
856 fn default<'a>() -> Self::Class<'a>;
858
859 fn style(&self, item: &Self::Class<'_>) -> Style;
861}
862
863pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
867
868impl Catalog for Theme {
869 type Class<'a> = StyleFn<'a, Self>;
870
871 fn default<'a>() -> Self::Class<'a> {
872 Box::new(default)
873 }
874
875 fn style(&self, class: &Self::Class<'_>) -> Style {
876 class(self)
877 }
878}
879
880pub fn default(theme: &Theme) -> Style {
882 Style {
883 color: None,
884 selection: theme.palette().primary.weak.color,
885 }
886}
887
888fn highlight_line(
889 line: &cosmic_text::BufferLine,
890 from: usize,
891 to: usize,
892) -> impl Iterator<Item = (f32, f32)> + '_ {
893 let layout = line.layout_opt().map(Vec::as_slice).unwrap_or_default();
894
895 let mut previous_diff = 0;
897 let previous_lines_diff = line
898 .layout_opt()
899 .map(Vec::as_slice)
900 .unwrap_or_default()
901 .iter()
902 .enumerate()
903 .map(move |(line_nr, visual_line)| {
904 if line_nr == 0 {
905 let current_diff = previous_diff
906 + visual_line.glyphs.iter().fold(0, |d, g| {
907 (d + g.start.abs_diff(g.end)).saturating_sub(1)
908 });
909 previous_diff = current_diff;
910 0
911 } else {
912 let current_diff = previous_diff
913 + visual_line
914 .glyphs
915 .iter()
916 .fold(0, |d, g| d + g.start.abs_diff(g.end) - 1);
917 let previous_diff_temp = previous_diff;
918 previous_diff = current_diff;
919 previous_diff_temp
920 }
921 });
922
923 layout.iter().zip(previous_lines_diff).map(
924 move |(visual_line, previous_lines_diff)| {
925 let start = visual_line
926 .glyphs
927 .first()
928 .map(|glyph| glyph.start)
929 .unwrap_or(0);
930 let end = visual_line
931 .glyphs
932 .last()
933 .map(|glyph| glyph.end)
934 .unwrap_or(0);
935
936 let to = to + previous_lines_diff;
937 let mut range = start.max(from)..end.min(to);
938
939 let x_offset = visual_line
940 .glyphs
941 .first()
942 .map(|glyph| glyph.x)
943 .unwrap_or_default();
944
945 if range.is_empty() {
946 (x_offset, 0.0)
947 } else if range.start == start && range.end == end {
948 (x_offset, visual_line.w)
949 } else {
950 let mut x = 0.0;
951 let mut width = 0.0;
952 for glyph in &visual_line.glyphs {
953 let glyph_count = glyph.start.abs_diff(glyph.end);
954
955 if glyph_count > 1 {
957 if range.start > glyph.start {
958 range.start += glyph_count - 1;
959 range.end += glyph_count - 1;
960 } else if range.end > glyph.start {
961 range.end += glyph_count - 1;
962 }
963 }
964
965 if range.start > glyph.start {
966 x += glyph.w;
967 }
968
969 if range.start <= glyph.start && range.end > glyph.start {
970 width += glyph.w;
971 } else if range.end <= glyph.start {
972 break;
973 }
974 }
975
976 (x_offset + x, width)
977 }
978 },
979 )
980}
981
982fn visual_lines_offset(line: usize, buffer: &cosmic_text::Buffer) -> i32 {
983 let scroll = buffer.scroll();
984
985 let start = scroll.line.min(line);
986 let end = scroll.line.max(line);
987
988 let visual_lines_offset: usize = buffer.lines[start..]
989 .iter()
990 .take(end - start)
991 .map(|line| line.layout_opt().map(Vec::len).unwrap_or_default())
992 .sum();
993
994 visual_lines_offset as i32 * if scroll.line < line { 1 } else { -1 }
995}