1#![allow(clippy::derived_hash_with_manual_eq)] #![allow(clippy::wrong_self_convention)] use std::ops::Range;
5use std::sync::Arc;
6
7use super::{
8 cursor::{CCursor, Cursor, PCursor, RCursor},
9 font::UvRect,
10};
11use crate::{Color32, FontId, Mesh, Stroke};
12use emath::{pos2, vec2, Align, NumExt, OrderedFloat, Pos2, Rect, Vec2};
13
14#[derive(Clone, Debug, PartialEq)]
48#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
49pub struct LayoutJob {
50 pub text: String,
52
53 pub sections: Vec<LayoutSection>,
55
56 pub wrap: TextWrapping,
58
59 pub first_row_min_height: f32,
65
66 pub break_on_newline: bool,
74
75 pub halign: Align,
77
78 pub justify: bool,
80
81 pub round_output_to_gui: bool,
83}
84
85impl Default for LayoutJob {
86 #[inline]
87 fn default() -> Self {
88 Self {
89 text: Default::default(),
90 sections: Default::default(),
91 wrap: Default::default(),
92 first_row_min_height: 0.0,
93 break_on_newline: true,
94 halign: Align::LEFT,
95 justify: false,
96 round_output_to_gui: true,
97 }
98 }
99}
100
101impl LayoutJob {
102 #[inline]
104 pub fn simple(text: String, font_id: FontId, color: Color32, wrap_width: f32) -> Self {
105 Self {
106 sections: vec![LayoutSection {
107 leading_space: 0.0,
108 byte_range: 0..text.len(),
109 format: TextFormat::simple(font_id, color),
110 }],
111 text,
112 wrap: TextWrapping {
113 max_width: wrap_width,
114 ..Default::default()
115 },
116 break_on_newline: true,
117 ..Default::default()
118 }
119 }
120
121 #[inline]
123 pub fn simple_singleline(text: String, font_id: FontId, color: Color32) -> Self {
124 Self {
125 sections: vec![LayoutSection {
126 leading_space: 0.0,
127 byte_range: 0..text.len(),
128 format: TextFormat::simple(font_id, color),
129 }],
130 text,
131 wrap: Default::default(),
132 break_on_newline: false,
133 ..Default::default()
134 }
135 }
136
137 #[inline]
138 pub fn single_section(text: String, format: TextFormat) -> Self {
139 Self {
140 sections: vec![LayoutSection {
141 leading_space: 0.0,
142 byte_range: 0..text.len(),
143 format,
144 }],
145 text,
146 wrap: Default::default(),
147 break_on_newline: true,
148 ..Default::default()
149 }
150 }
151
152 #[inline]
153 pub fn is_empty(&self) -> bool {
154 self.sections.is_empty()
155 }
156
157 pub fn append(&mut self, text: &str, leading_space: f32, format: TextFormat) {
159 let start = self.text.len();
160 self.text += text;
161 let byte_range = start..self.text.len();
162 self.sections.push(LayoutSection {
163 leading_space,
164 byte_range,
165 format,
166 });
167 }
168
169 pub fn font_height(&self, fonts: &crate::Fonts) -> f32 {
173 let mut max_height = 0.0_f32;
174 for section in &self.sections {
175 max_height = max_height.max(fonts.row_height(§ion.format.font_id));
176 }
177 max_height
178 }
179
180 pub fn effective_wrap_width(&self) -> f32 {
182 if self.round_output_to_gui {
183 self.wrap.max_width + 0.5
187 } else {
188 self.wrap.max_width
189 }
190 }
191}
192
193impl std::hash::Hash for LayoutJob {
194 #[inline]
195 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
196 let Self {
197 text,
198 sections,
199 wrap,
200 first_row_min_height,
201 break_on_newline,
202 halign,
203 justify,
204 round_output_to_gui,
205 } = self;
206
207 text.hash(state);
208 sections.hash(state);
209 wrap.hash(state);
210 emath::OrderedFloat(*first_row_min_height).hash(state);
211 break_on_newline.hash(state);
212 halign.hash(state);
213 justify.hash(state);
214 round_output_to_gui.hash(state);
215 }
216}
217
218#[derive(Clone, Debug, PartialEq)]
221#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
222pub struct LayoutSection {
223 pub leading_space: f32,
225
226 pub byte_range: Range<usize>,
228
229 pub format: TextFormat,
230}
231
232impl std::hash::Hash for LayoutSection {
233 #[inline]
234 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
235 let Self {
236 leading_space,
237 byte_range,
238 format,
239 } = self;
240 OrderedFloat(*leading_space).hash(state);
241 byte_range.hash(state);
242 format.hash(state);
243 }
244}
245
246#[derive(Clone, Debug, PartialEq)]
250#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
251pub struct TextFormat {
252 pub font_id: FontId,
253
254 pub extra_letter_spacing: f32,
260
261 pub line_height: Option<f32>,
269
270 pub color: Color32,
272
273 pub background: Color32,
274
275 pub italics: bool,
276
277 pub underline: Stroke,
278
279 pub strikethrough: Stroke,
280
281 pub valign: Align,
291}
292
293impl Default for TextFormat {
294 #[inline]
295 fn default() -> Self {
296 Self {
297 font_id: FontId::default(),
298 extra_letter_spacing: 0.0,
299 line_height: None,
300 color: Color32::GRAY,
301 background: Color32::TRANSPARENT,
302 italics: false,
303 underline: Stroke::NONE,
304 strikethrough: Stroke::NONE,
305 valign: Align::BOTTOM,
306 }
307 }
308}
309
310impl std::hash::Hash for TextFormat {
311 #[inline]
312 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
313 let Self {
314 font_id,
315 extra_letter_spacing,
316 line_height,
317 color,
318 background,
319 italics,
320 underline,
321 strikethrough,
322 valign,
323 } = self;
324 font_id.hash(state);
325 emath::OrderedFloat(*extra_letter_spacing).hash(state);
326 if let Some(line_height) = *line_height {
327 emath::OrderedFloat(line_height).hash(state);
328 }
329 color.hash(state);
330 background.hash(state);
331 italics.hash(state);
332 underline.hash(state);
333 strikethrough.hash(state);
334 valign.hash(state);
335 }
336}
337
338impl TextFormat {
339 #[inline]
340 pub fn simple(font_id: FontId, color: Color32) -> Self {
341 Self {
342 font_id,
343 color,
344 ..Default::default()
345 }
346 }
347}
348
349#[derive(Clone, Copy, Debug, PartialEq, Eq)]
355#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
356pub enum TextWrapMode {
357 Extend,
359
360 Wrap,
362
363 Truncate,
367}
368
369#[derive(Clone, Debug, PartialEq)]
371#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
372pub struct TextWrapping {
373 pub max_width: f32,
382
383 pub max_rows: usize,
397
398 pub break_anywhere: bool,
407
408 pub overflow_character: Option<char>,
414}
415
416impl std::hash::Hash for TextWrapping {
417 #[inline]
418 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
419 let Self {
420 max_width,
421 max_rows,
422 break_anywhere,
423 overflow_character,
424 } = self;
425 emath::OrderedFloat(*max_width).hash(state);
426 max_rows.hash(state);
427 break_anywhere.hash(state);
428 overflow_character.hash(state);
429 }
430}
431
432impl Default for TextWrapping {
433 fn default() -> Self {
434 Self {
435 max_width: f32::INFINITY,
436 max_rows: usize::MAX,
437 break_anywhere: false,
438 overflow_character: Some('…'),
439 }
440 }
441}
442
443impl TextWrapping {
444 pub fn from_wrap_mode_and_width(mode: TextWrapMode, max_width: f32) -> Self {
446 match mode {
447 TextWrapMode::Extend => Self::no_max_width(),
448 TextWrapMode::Wrap => Self::wrap_at_width(max_width),
449 TextWrapMode::Truncate => Self::truncate_at_width(max_width),
450 }
451 }
452
453 pub fn no_max_width() -> Self {
455 Self {
456 max_width: f32::INFINITY,
457 ..Default::default()
458 }
459 }
460
461 pub fn wrap_at_width(max_width: f32) -> Self {
463 Self {
464 max_width,
465 ..Default::default()
466 }
467 }
468
469 pub fn truncate_at_width(max_width: f32) -> Self {
471 Self {
472 max_width,
473 max_rows: 1,
474 break_anywhere: true,
475 ..Default::default()
476 }
477 }
478}
479
480#[derive(Clone, Debug, PartialEq)]
497#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
498pub struct Galley {
499 pub job: Arc<LayoutJob>,
502
503 pub rows: Vec<Row>,
511
512 pub elided: bool,
514
515 pub rect: Rect,
524
525 pub mesh_bounds: Rect,
528
529 pub num_vertices: usize,
531
532 pub num_indices: usize,
534
535 pub pixels_per_point: f32,
540}
541
542#[derive(Clone, Debug, PartialEq)]
543#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
544pub struct Row {
545 pub section_index_at_start: u32,
547
548 pub glyphs: Vec<Glyph>,
550
551 pub rect: Rect,
555
556 pub visuals: RowVisuals,
558
559 pub ends_with_newline: bool,
565}
566
567#[derive(Clone, Debug, PartialEq, Eq)]
569#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
570pub struct RowVisuals {
571 pub mesh: Mesh,
574
575 pub mesh_bounds: Rect,
578
579 pub glyph_index_start: usize,
584
585 pub glyph_vertex_range: Range<usize>,
589}
590
591impl Default for RowVisuals {
592 fn default() -> Self {
593 Self {
594 mesh: Default::default(),
595 mesh_bounds: Rect::NOTHING,
596 glyph_index_start: 0,
597 glyph_vertex_range: 0..0,
598 }
599 }
600}
601
602#[derive(Copy, Clone, Debug, PartialEq)]
603#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
604pub struct Glyph {
605 pub chr: char,
607
608 pub pos: Pos2,
611
612 pub advance_width: f32,
614
615 pub line_height: f32,
620
621 pub font_ascent: f32,
623
624 pub font_height: f32,
626
627 pub font_impl_ascent: f32,
629
630 pub font_impl_height: f32,
632
633 pub uv_rect: UvRect,
635
636 pub section_index: u32,
638}
639
640impl Glyph {
641 #[inline]
642 pub fn size(&self) -> Vec2 {
643 Vec2::new(self.advance_width, self.line_height)
644 }
645
646 #[inline]
647 pub fn max_x(&self) -> f32 {
648 self.pos.x + self.advance_width
649 }
650
651 #[inline]
653 pub fn logical_rect(&self) -> Rect {
654 Rect::from_min_size(self.pos - vec2(0.0, self.font_ascent), self.size())
655 }
656}
657
658impl Row {
661 pub fn text(&self) -> String {
663 self.glyphs.iter().map(|g| g.chr).collect()
664 }
665
666 #[inline]
668 pub fn char_count_excluding_newline(&self) -> usize {
669 self.glyphs.len()
670 }
671
672 #[inline]
674 pub fn char_count_including_newline(&self) -> usize {
675 self.glyphs.len() + (self.ends_with_newline as usize)
676 }
677
678 #[inline]
679 pub fn min_y(&self) -> f32 {
680 self.rect.top()
681 }
682
683 #[inline]
684 pub fn max_y(&self) -> f32 {
685 self.rect.bottom()
686 }
687
688 #[inline]
689 pub fn height(&self) -> f32 {
690 self.rect.height()
691 }
692
693 pub fn char_at(&self, desired_x: f32) -> usize {
696 for (i, glyph) in self.glyphs.iter().enumerate() {
697 if desired_x < glyph.logical_rect().center().x {
698 return i;
699 }
700 }
701 self.char_count_excluding_newline()
702 }
703
704 pub fn x_offset(&self, column: usize) -> f32 {
705 if let Some(glyph) = self.glyphs.get(column) {
706 glyph.pos.x
707 } else {
708 self.rect.right()
709 }
710 }
711}
712
713impl Galley {
714 #[inline]
715 pub fn is_empty(&self) -> bool {
716 self.job.is_empty()
717 }
718
719 #[inline]
721 pub fn text(&self) -> &str {
722 &self.job.text
723 }
724
725 #[inline]
726 pub fn size(&self) -> Vec2 {
727 self.rect.size()
728 }
729}
730
731impl AsRef<str> for Galley {
732 #[inline]
733 fn as_ref(&self) -> &str {
734 self.text()
735 }
736}
737
738impl std::borrow::Borrow<str> for Galley {
739 #[inline]
740 fn borrow(&self) -> &str {
741 self.text()
742 }
743}
744
745impl std::ops::Deref for Galley {
746 type Target = str;
747 #[inline]
748 fn deref(&self) -> &str {
749 self.text()
750 }
751}
752
753impl Galley {
757 fn end_pos(&self) -> Rect {
759 if let Some(row) = self.rows.last() {
760 let x = row.rect.right();
761 Rect::from_min_max(pos2(x, row.min_y()), pos2(x, row.max_y()))
762 } else {
763 Rect::from_min_max(pos2(0.0, 0.0), pos2(0.0, 0.0))
765 }
766 }
767
768 pub fn pos_from_cursor(&self, cursor: &Cursor) -> Rect {
770 self.pos_from_pcursor(cursor.pcursor) }
772
773 pub fn pos_from_pcursor(&self, pcursor: PCursor) -> Rect {
775 let mut it = PCursor::default();
776
777 for row in &self.rows {
778 if it.paragraph == pcursor.paragraph {
779 if it.offset <= pcursor.offset
782 && (pcursor.offset <= it.offset + row.char_count_excluding_newline()
783 || row.ends_with_newline)
784 {
785 let column = pcursor.offset - it.offset;
786
787 let select_next_row_instead = pcursor.prefer_next_row
788 && !row.ends_with_newline
789 && column >= row.char_count_excluding_newline();
790 if !select_next_row_instead {
791 let x = row.x_offset(column);
792 return Rect::from_min_max(pos2(x, row.min_y()), pos2(x, row.max_y()));
793 }
794 }
795 }
796
797 if row.ends_with_newline {
798 it.paragraph += 1;
799 it.offset = 0;
800 } else {
801 it.offset += row.char_count_including_newline();
802 }
803 }
804
805 self.end_pos()
806 }
807
808 pub fn pos_from_ccursor(&self, ccursor: CCursor) -> Rect {
810 self.pos_from_cursor(&self.from_ccursor(ccursor))
811 }
812
813 pub fn pos_from_rcursor(&self, rcursor: RCursor) -> Rect {
815 self.pos_from_cursor(&self.from_rcursor(rcursor))
816 }
817
818 pub fn cursor_from_pos(&self, pos: Vec2) -> Cursor {
826 if let Some(first_row) = self.rows.first() {
827 if pos.y < first_row.min_y() {
828 return self.begin();
829 }
830 }
831 if let Some(last_row) = self.rows.last() {
832 if last_row.max_y() < pos.y {
833 return self.end();
834 }
835 }
836
837 let mut best_y_dist = f32::INFINITY;
838 let mut cursor = Cursor::default();
839
840 let mut ccursor_index = 0;
841 let mut pcursor_it = PCursor::default();
842
843 for (row_nr, row) in self.rows.iter().enumerate() {
844 let is_pos_within_row = row.min_y() <= pos.y && pos.y <= row.max_y();
845 let y_dist = (row.min_y() - pos.y).abs().min((row.max_y() - pos.y).abs());
846 if is_pos_within_row || y_dist < best_y_dist {
847 best_y_dist = y_dist;
848 let column = row.char_at(pos.x);
849 let prefer_next_row = column < row.char_count_excluding_newline();
850 cursor = Cursor {
851 ccursor: CCursor {
852 index: ccursor_index + column,
853 prefer_next_row,
854 },
855 rcursor: RCursor {
856 row: row_nr,
857 column,
858 },
859 pcursor: PCursor {
860 paragraph: pcursor_it.paragraph,
861 offset: pcursor_it.offset + column,
862 prefer_next_row,
863 },
864 };
865
866 if is_pos_within_row {
867 return cursor;
868 }
869 }
870 ccursor_index += row.char_count_including_newline();
871 if row.ends_with_newline {
872 pcursor_it.paragraph += 1;
873 pcursor_it.offset = 0;
874 } else {
875 pcursor_it.offset += row.char_count_including_newline();
876 }
877 }
878
879 cursor
880 }
881}
882
883impl Galley {
885 #[inline]
889 #[allow(clippy::unused_self)]
890 pub fn begin(&self) -> Cursor {
891 Cursor::default()
892 }
893
894 pub fn end(&self) -> Cursor {
896 if self.rows.is_empty() {
897 return Default::default();
898 }
899 let mut ccursor = CCursor {
900 index: 0,
901 prefer_next_row: true,
902 };
903 let mut pcursor = PCursor {
904 paragraph: 0,
905 offset: 0,
906 prefer_next_row: true,
907 };
908 for row in &self.rows {
909 let row_char_count = row.char_count_including_newline();
910 ccursor.index += row_char_count;
911 if row.ends_with_newline {
912 pcursor.paragraph += 1;
913 pcursor.offset = 0;
914 } else {
915 pcursor.offset += row_char_count;
916 }
917 }
918 Cursor {
919 ccursor,
920 rcursor: self.end_rcursor(),
921 pcursor,
922 }
923 }
924
925 pub fn end_rcursor(&self) -> RCursor {
926 if let Some(last_row) = self.rows.last() {
927 RCursor {
928 row: self.rows.len() - 1,
929 column: last_row.char_count_including_newline(),
930 }
931 } else {
932 Default::default()
933 }
934 }
935}
936
937impl Galley {
939 pub fn from_ccursor(&self, ccursor: CCursor) -> Cursor {
941 let prefer_next_row = ccursor.prefer_next_row;
942 let mut ccursor_it = CCursor {
943 index: 0,
944 prefer_next_row,
945 };
946 let mut pcursor_it = PCursor {
947 paragraph: 0,
948 offset: 0,
949 prefer_next_row,
950 };
951
952 for (row_nr, row) in self.rows.iter().enumerate() {
953 let row_char_count = row.char_count_excluding_newline();
954
955 if ccursor_it.index <= ccursor.index
956 && ccursor.index <= ccursor_it.index + row_char_count
957 {
958 let column = ccursor.index - ccursor_it.index;
959
960 let select_next_row_instead = prefer_next_row
961 && !row.ends_with_newline
962 && column >= row.char_count_excluding_newline();
963 if !select_next_row_instead {
964 pcursor_it.offset += column;
965 return Cursor {
966 ccursor,
967 rcursor: RCursor {
968 row: row_nr,
969 column,
970 },
971 pcursor: pcursor_it,
972 };
973 }
974 }
975 ccursor_it.index += row.char_count_including_newline();
976 if row.ends_with_newline {
977 pcursor_it.paragraph += 1;
978 pcursor_it.offset = 0;
979 } else {
980 pcursor_it.offset += row.char_count_including_newline();
981 }
982 }
983 debug_assert!(ccursor_it == self.end().ccursor);
984 Cursor {
985 ccursor: ccursor_it, rcursor: self.end_rcursor(),
987 pcursor: pcursor_it,
988 }
989 }
990
991 pub fn from_rcursor(&self, rcursor: RCursor) -> Cursor {
992 if rcursor.row >= self.rows.len() {
993 return self.end();
994 }
995
996 let prefer_next_row =
997 rcursor.column < self.rows[rcursor.row].char_count_excluding_newline();
998 let mut ccursor_it = CCursor {
999 index: 0,
1000 prefer_next_row,
1001 };
1002 let mut pcursor_it = PCursor {
1003 paragraph: 0,
1004 offset: 0,
1005 prefer_next_row,
1006 };
1007
1008 for (row_nr, row) in self.rows.iter().enumerate() {
1009 if row_nr == rcursor.row {
1010 ccursor_it.index += rcursor.column.at_most(row.char_count_excluding_newline());
1011
1012 if row.ends_with_newline {
1013 pcursor_it.offset += rcursor.column;
1015 } else {
1016 pcursor_it.offset += rcursor.column.at_most(row.char_count_excluding_newline());
1017 }
1018 return Cursor {
1019 ccursor: ccursor_it,
1020 rcursor,
1021 pcursor: pcursor_it,
1022 };
1023 }
1024 ccursor_it.index += row.char_count_including_newline();
1025 if row.ends_with_newline {
1026 pcursor_it.paragraph += 1;
1027 pcursor_it.offset = 0;
1028 } else {
1029 pcursor_it.offset += row.char_count_including_newline();
1030 }
1031 }
1032 Cursor {
1033 ccursor: ccursor_it,
1034 rcursor: self.end_rcursor(),
1035 pcursor: pcursor_it,
1036 }
1037 }
1038
1039 pub fn from_pcursor(&self, pcursor: PCursor) -> Cursor {
1041 let prefer_next_row = pcursor.prefer_next_row;
1042 let mut ccursor_it = CCursor {
1043 index: 0,
1044 prefer_next_row,
1045 };
1046 let mut pcursor_it = PCursor {
1047 paragraph: 0,
1048 offset: 0,
1049 prefer_next_row,
1050 };
1051
1052 for (row_nr, row) in self.rows.iter().enumerate() {
1053 if pcursor_it.paragraph == pcursor.paragraph {
1054 if pcursor_it.offset <= pcursor.offset
1057 && (pcursor.offset <= pcursor_it.offset + row.char_count_excluding_newline()
1058 || row.ends_with_newline)
1059 {
1060 let column = pcursor.offset - pcursor_it.offset;
1061
1062 let select_next_row_instead = pcursor.prefer_next_row
1063 && !row.ends_with_newline
1064 && column >= row.char_count_excluding_newline();
1065
1066 if !select_next_row_instead {
1067 ccursor_it.index += column.at_most(row.char_count_excluding_newline());
1068
1069 return Cursor {
1070 ccursor: ccursor_it,
1071 rcursor: RCursor {
1072 row: row_nr,
1073 column,
1074 },
1075 pcursor,
1076 };
1077 }
1078 }
1079 }
1080
1081 ccursor_it.index += row.char_count_including_newline();
1082 if row.ends_with_newline {
1083 pcursor_it.paragraph += 1;
1084 pcursor_it.offset = 0;
1085 } else {
1086 pcursor_it.offset += row.char_count_including_newline();
1087 }
1088 }
1089 Cursor {
1090 ccursor: ccursor_it,
1091 rcursor: self.end_rcursor(),
1092 pcursor,
1093 }
1094 }
1095}
1096
1097impl Galley {
1099 pub fn cursor_left_one_character(&self, cursor: &Cursor) -> Cursor {
1100 if cursor.ccursor.index == 0 {
1101 Default::default()
1102 } else {
1103 let ccursor = CCursor {
1104 index: cursor.ccursor.index,
1105 prefer_next_row: true, };
1107 self.from_ccursor(ccursor - 1)
1108 }
1109 }
1110
1111 pub fn cursor_right_one_character(&self, cursor: &Cursor) -> Cursor {
1112 let ccursor = CCursor {
1113 index: cursor.ccursor.index,
1114 prefer_next_row: true, };
1116 self.from_ccursor(ccursor + 1)
1117 }
1118
1119 pub fn cursor_up_one_row(&self, cursor: &Cursor) -> Cursor {
1120 if cursor.rcursor.row == 0 {
1121 Cursor::default()
1122 } else {
1123 let new_row = cursor.rcursor.row - 1;
1124
1125 let cursor_is_beyond_end_of_current_row = cursor.rcursor.column
1126 >= self.rows[cursor.rcursor.row].char_count_excluding_newline();
1127
1128 let new_rcursor = if cursor_is_beyond_end_of_current_row {
1129 RCursor {
1131 row: new_row,
1132 column: cursor.rcursor.column,
1133 }
1134 } else {
1135 let x = self.pos_from_cursor(cursor).center().x;
1137 let column = if x > self.rows[new_row].rect.right() {
1138 cursor.rcursor.column
1140 } else {
1141 self.rows[new_row].char_at(x)
1142 };
1143 RCursor {
1144 row: new_row,
1145 column,
1146 }
1147 };
1148 self.from_rcursor(new_rcursor)
1149 }
1150 }
1151
1152 pub fn cursor_down_one_row(&self, cursor: &Cursor) -> Cursor {
1153 if cursor.rcursor.row + 1 < self.rows.len() {
1154 let new_row = cursor.rcursor.row + 1;
1155
1156 let cursor_is_beyond_end_of_current_row = cursor.rcursor.column
1157 >= self.rows[cursor.rcursor.row].char_count_excluding_newline();
1158
1159 let new_rcursor = if cursor_is_beyond_end_of_current_row {
1160 RCursor {
1162 row: new_row,
1163 column: cursor.rcursor.column,
1164 }
1165 } else {
1166 let x = self.pos_from_cursor(cursor).center().x;
1168 let column = if x > self.rows[new_row].rect.right() {
1169 cursor.rcursor.column
1171 } else {
1172 self.rows[new_row].char_at(x)
1173 };
1174 RCursor {
1175 row: new_row,
1176 column,
1177 }
1178 };
1179
1180 self.from_rcursor(new_rcursor)
1181 } else {
1182 self.end()
1183 }
1184 }
1185
1186 pub fn cursor_begin_of_row(&self, cursor: &Cursor) -> Cursor {
1187 self.from_rcursor(RCursor {
1188 row: cursor.rcursor.row,
1189 column: 0,
1190 })
1191 }
1192
1193 pub fn cursor_end_of_row(&self, cursor: &Cursor) -> Cursor {
1194 self.from_rcursor(RCursor {
1195 row: cursor.rcursor.row,
1196 column: self.rows[cursor.rcursor.row].char_count_excluding_newline(),
1197 })
1198 }
1199}