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