1#![allow(clippy::derived_hash_with_manual_eq)] #![allow(clippy::wrong_self_convention)] use std::ops::Range;
5use std::sync::Arc;
6
7use super::{cursor::*, font::UvRect};
8use crate::{Color32, FontId, Mesh, Stroke};
9use emath::*;
10
11#[derive(Clone, Debug, PartialEq)]
45#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
46pub struct LayoutJob {
47 pub text: String,
49
50 pub sections: Vec<LayoutSection>,
52
53 pub wrap: TextWrapping,
55
56 pub first_row_min_height: f32,
62
63 pub break_on_newline: bool,
71
72 pub halign: Align,
74
75 pub justify: bool,
77
78 pub round_output_size_to_nearest_ui_point: bool,
81}
82
83impl Default for LayoutJob {
84 #[inline]
85 fn default() -> Self {
86 Self {
87 text: Default::default(),
88 sections: Default::default(),
89 wrap: Default::default(),
90 first_row_min_height: 0.0,
91 break_on_newline: true,
92 halign: Align::LEFT,
93 justify: false,
94 round_output_size_to_nearest_ui_point: true,
95 }
96 }
97}
98
99impl LayoutJob {
100 #[inline]
102 pub fn simple(text: String, font_id: FontId, color: Color32, wrap_width: f32) -> Self {
103 Self {
104 sections: vec![LayoutSection {
105 leading_space: 0.0,
106 byte_range: 0..text.len(),
107 format: TextFormat::simple(font_id, color),
108 }],
109 text,
110 wrap: TextWrapping {
111 max_width: wrap_width,
112 ..Default::default()
113 },
114 break_on_newline: true,
115 ..Default::default()
116 }
117 }
118
119 #[inline]
121 pub fn simple_singleline(text: String, font_id: FontId, color: Color32) -> Self {
122 Self {
123 sections: vec![LayoutSection {
124 leading_space: 0.0,
125 byte_range: 0..text.len(),
126 format: TextFormat::simple(font_id, color),
127 }],
128 text,
129 wrap: Default::default(),
130 break_on_newline: false,
131 ..Default::default()
132 }
133 }
134
135 #[inline]
136 pub fn single_section(text: String, format: TextFormat) -> Self {
137 Self {
138 sections: vec![LayoutSection {
139 leading_space: 0.0,
140 byte_range: 0..text.len(),
141 format,
142 }],
143 text,
144 wrap: Default::default(),
145 break_on_newline: true,
146 ..Default::default()
147 }
148 }
149
150 #[inline]
151 pub fn is_empty(&self) -> bool {
152 self.sections.is_empty()
153 }
154
155 pub fn append(&mut self, text: &str, leading_space: f32, format: TextFormat) {
157 let start = self.text.len();
158 self.text += text;
159 let byte_range = start..self.text.len();
160 self.sections.push(LayoutSection {
161 leading_space,
162 byte_range,
163 format,
164 });
165 }
166
167 pub fn font_height(&self, fonts: &crate::Fonts) -> f32 {
169 let mut max_height = 0.0_f32;
170 for section in &self.sections {
171 max_height = max_height.max(fonts.row_height(§ion.format.font_id));
172 }
173 max_height
174 }
175}
176
177impl std::hash::Hash for LayoutJob {
178 #[inline]
179 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
180 let Self {
181 text,
182 sections,
183 wrap,
184 first_row_min_height,
185 break_on_newline,
186 halign,
187 justify,
188 round_output_size_to_nearest_ui_point,
189 } = self;
190
191 text.hash(state);
192 sections.hash(state);
193 wrap.hash(state);
194 emath::OrderedFloat(*first_row_min_height).hash(state);
195 break_on_newline.hash(state);
196 halign.hash(state);
197 justify.hash(state);
198 round_output_size_to_nearest_ui_point.hash(state);
199 }
200}
201
202#[derive(Clone, Debug, PartialEq)]
205#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
206pub struct LayoutSection {
207 pub leading_space: f32,
209
210 pub byte_range: Range<usize>,
212
213 pub format: TextFormat,
214}
215
216impl std::hash::Hash for LayoutSection {
217 #[inline]
218 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
219 let Self {
220 leading_space,
221 byte_range,
222 format,
223 } = self;
224 OrderedFloat(*leading_space).hash(state);
225 byte_range.hash(state);
226 format.hash(state);
227 }
228}
229
230#[derive(Clone, Debug, PartialEq)]
234#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
235pub struct TextFormat {
236 pub font_id: FontId,
237
238 pub extra_letter_spacing: f32,
244
245 pub line_height: Option<f32>,
253
254 pub color: Color32,
256
257 pub background: Color32,
258
259 pub italics: bool,
260
261 pub underline: Stroke,
262
263 pub strikethrough: Stroke,
264
265 pub valign: Align,
268 }
270
271impl Default for TextFormat {
272 #[inline]
273 fn default() -> Self {
274 Self {
275 font_id: FontId::default(),
276 extra_letter_spacing: 0.0,
277 line_height: None,
278 color: Color32::GRAY,
279 background: Color32::TRANSPARENT,
280 italics: false,
281 underline: Stroke::NONE,
282 strikethrough: Stroke::NONE,
283 valign: Align::BOTTOM,
284 }
285 }
286}
287
288impl std::hash::Hash for TextFormat {
289 #[inline]
290 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
291 let Self {
292 font_id,
293 extra_letter_spacing,
294 line_height,
295 color,
296 background,
297 italics,
298 underline,
299 strikethrough,
300 valign,
301 } = self;
302 font_id.hash(state);
303 emath::OrderedFloat(*extra_letter_spacing).hash(state);
304 if let Some(line_height) = *line_height {
305 emath::OrderedFloat(line_height).hash(state);
306 }
307 color.hash(state);
308 background.hash(state);
309 italics.hash(state);
310 underline.hash(state);
311 strikethrough.hash(state);
312 valign.hash(state);
313 }
314}
315
316impl TextFormat {
317 #[inline]
318 pub fn simple(font_id: FontId, color: Color32) -> Self {
319 Self {
320 font_id,
321 color,
322 ..Default::default()
323 }
324 }
325}
326
327#[derive(Clone, Copy, Debug, PartialEq, Eq)]
333#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
334pub enum TextWrapMode {
335 Extend,
337
338 Wrap,
340
341 Truncate,
345}
346
347#[derive(Clone, Debug, PartialEq)]
349#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
350pub struct TextWrapping {
351 pub max_width: f32,
360
361 pub max_rows: usize,
375
376 pub break_anywhere: bool,
385
386 pub overflow_character: Option<char>,
392}
393
394impl std::hash::Hash for TextWrapping {
395 #[inline]
396 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
397 let Self {
398 max_width,
399 max_rows,
400 break_anywhere,
401 overflow_character,
402 } = self;
403 emath::OrderedFloat(*max_width).hash(state);
404 max_rows.hash(state);
405 break_anywhere.hash(state);
406 overflow_character.hash(state);
407 }
408}
409
410impl Default for TextWrapping {
411 fn default() -> Self {
412 Self {
413 max_width: f32::INFINITY,
414 max_rows: usize::MAX,
415 break_anywhere: false,
416 overflow_character: Some('…'),
417 }
418 }
419}
420
421impl TextWrapping {
422 pub fn from_wrap_mode_and_width(mode: TextWrapMode, max_width: f32) -> Self {
424 match mode {
425 TextWrapMode::Extend => Self::no_max_width(),
426 TextWrapMode::Wrap => Self::wrap_at_width(max_width),
427 TextWrapMode::Truncate => Self::truncate_at_width(max_width),
428 }
429 }
430
431 pub fn no_max_width() -> Self {
433 Self {
434 max_width: f32::INFINITY,
435 ..Default::default()
436 }
437 }
438
439 pub fn wrap_at_width(max_width: f32) -> Self {
441 Self {
442 max_width,
443 ..Default::default()
444 }
445 }
446
447 pub fn truncate_at_width(max_width: f32) -> Self {
449 Self {
450 max_width,
451 max_rows: 1,
452 break_anywhere: true,
453 ..Default::default()
454 }
455 }
456}
457
458#[derive(Clone, Debug, PartialEq)]
475#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
476pub struct Galley {
477 pub job: Arc<LayoutJob>,
480
481 pub rows: Vec<Row>,
489
490 pub elided: bool,
492
493 pub rect: Rect,
502
503 pub mesh_bounds: Rect,
506
507 pub num_vertices: usize,
509
510 pub num_indices: usize,
512
513 pub pixels_per_point: f32,
518}
519
520#[derive(Clone, Debug, PartialEq)]
521#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
522pub struct Row {
523 pub section_index_at_start: u32,
525
526 pub glyphs: Vec<Glyph>,
528
529 pub rect: Rect,
533
534 pub visuals: RowVisuals,
536
537 pub ends_with_newline: bool,
543}
544
545#[derive(Clone, Debug, PartialEq, Eq)]
547#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
548pub struct RowVisuals {
549 pub mesh: Mesh,
552
553 pub mesh_bounds: Rect,
556
557 pub glyph_vertex_range: Range<usize>,
561}
562
563impl Default for RowVisuals {
564 fn default() -> Self {
565 Self {
566 mesh: Default::default(),
567 mesh_bounds: Rect::NOTHING,
568 glyph_vertex_range: 0..0,
569 }
570 }
571}
572
573#[derive(Copy, Clone, Debug, PartialEq)]
574#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
575pub struct Glyph {
576 pub chr: char,
578
579 pub pos: Pos2,
582
583 pub ascent: f32,
585
586 pub size: Vec2,
590
591 pub uv_rect: UvRect,
593
594 pub section_index: u32,
596}
597
598impl Glyph {
599 pub fn max_x(&self) -> f32 {
600 self.pos.x + self.size.x
601 }
602
603 #[inline]
605 pub fn logical_rect(&self) -> Rect {
606 Rect::from_min_size(self.pos - vec2(0.0, self.ascent), self.size)
607 }
608}
609
610impl Row {
613 pub fn text(&self) -> String {
615 self.glyphs.iter().map(|g| g.chr).collect()
616 }
617
618 #[inline]
620 pub fn char_count_excluding_newline(&self) -> usize {
621 self.glyphs.len()
622 }
623
624 #[inline]
626 pub fn char_count_including_newline(&self) -> usize {
627 self.glyphs.len() + (self.ends_with_newline as usize)
628 }
629
630 #[inline]
631 pub fn min_y(&self) -> f32 {
632 self.rect.top()
633 }
634
635 #[inline]
636 pub fn max_y(&self) -> f32 {
637 self.rect.bottom()
638 }
639
640 #[inline]
641 pub fn height(&self) -> f32 {
642 self.rect.height()
643 }
644
645 pub fn char_at(&self, desired_x: f32) -> usize {
648 for (i, glyph) in self.glyphs.iter().enumerate() {
649 if desired_x < glyph.logical_rect().center().x {
650 return i;
651 }
652 }
653 self.char_count_excluding_newline()
654 }
655
656 pub fn x_offset(&self, column: usize) -> f32 {
657 if let Some(glyph) = self.glyphs.get(column) {
658 glyph.pos.x
659 } else {
660 self.rect.right()
661 }
662 }
663}
664
665impl Galley {
666 #[inline]
667 pub fn is_empty(&self) -> bool {
668 self.job.is_empty()
669 }
670
671 #[inline]
673 pub fn text(&self) -> &str {
674 &self.job.text
675 }
676
677 #[inline]
678 pub fn size(&self) -> Vec2 {
679 self.rect.size()
680 }
681}
682
683impl AsRef<str> for Galley {
684 #[inline]
685 fn as_ref(&self) -> &str {
686 self.text()
687 }
688}
689
690impl std::borrow::Borrow<str> for Galley {
691 #[inline]
692 fn borrow(&self) -> &str {
693 self.text()
694 }
695}
696
697impl std::ops::Deref for Galley {
698 type Target = str;
699 #[inline]
700 fn deref(&self) -> &str {
701 self.text()
702 }
703}
704
705impl Galley {
709 fn end_pos(&self) -> Rect {
711 if let Some(row) = self.rows.last() {
712 let x = row.rect.right();
713 Rect::from_min_max(pos2(x, row.min_y()), pos2(x, row.max_y()))
714 } else {
715 Rect::from_min_max(pos2(0.0, 0.0), pos2(0.0, 0.0))
717 }
718 }
719
720 pub fn pos_from_cursor(&self, cursor: &Cursor) -> Rect {
722 self.pos_from_pcursor(cursor.pcursor) }
724
725 pub fn pos_from_pcursor(&self, pcursor: PCursor) -> Rect {
727 let mut it = PCursor::default();
728
729 for row in &self.rows {
730 if it.paragraph == pcursor.paragraph {
731 if it.offset <= pcursor.offset
734 && (pcursor.offset <= it.offset + row.char_count_excluding_newline()
735 || row.ends_with_newline)
736 {
737 let column = pcursor.offset - it.offset;
738
739 let select_next_row_instead = pcursor.prefer_next_row
740 && !row.ends_with_newline
741 && column >= row.char_count_excluding_newline();
742 if !select_next_row_instead {
743 let x = row.x_offset(column);
744 return Rect::from_min_max(pos2(x, row.min_y()), pos2(x, row.max_y()));
745 }
746 }
747 }
748
749 if row.ends_with_newline {
750 it.paragraph += 1;
751 it.offset = 0;
752 } else {
753 it.offset += row.char_count_including_newline();
754 }
755 }
756
757 self.end_pos()
758 }
759
760 pub fn pos_from_ccursor(&self, ccursor: CCursor) -> Rect {
762 self.pos_from_cursor(&self.from_ccursor(ccursor))
763 }
764
765 pub fn pos_from_rcursor(&self, rcursor: RCursor) -> Rect {
767 self.pos_from_cursor(&self.from_rcursor(rcursor))
768 }
769
770 pub fn cursor_from_pos(&self, pos: Vec2) -> Cursor {
778 if let Some(first_row) = self.rows.first() {
779 if pos.y < first_row.min_y() {
780 return self.begin();
781 }
782 }
783 if let Some(last_row) = self.rows.last() {
784 if last_row.max_y() < pos.y {
785 return self.end();
786 }
787 }
788
789 let mut best_y_dist = f32::INFINITY;
790 let mut cursor = Cursor::default();
791
792 let mut ccursor_index = 0;
793 let mut pcursor_it = PCursor::default();
794
795 for (row_nr, row) in self.rows.iter().enumerate() {
796 let is_pos_within_row = row.min_y() <= pos.y && pos.y <= row.max_y();
797 let y_dist = (row.min_y() - pos.y).abs().min((row.max_y() - pos.y).abs());
798 if is_pos_within_row || y_dist < best_y_dist {
799 best_y_dist = y_dist;
800 let column = row.char_at(pos.x);
801 let prefer_next_row = column < row.char_count_excluding_newline();
802 cursor = Cursor {
803 ccursor: CCursor {
804 index: ccursor_index + column,
805 prefer_next_row,
806 },
807 rcursor: RCursor {
808 row: row_nr,
809 column,
810 },
811 pcursor: PCursor {
812 paragraph: pcursor_it.paragraph,
813 offset: pcursor_it.offset + column,
814 prefer_next_row,
815 },
816 };
817
818 if is_pos_within_row {
819 return cursor;
820 }
821 }
822 ccursor_index += row.char_count_including_newline();
823 if row.ends_with_newline {
824 pcursor_it.paragraph += 1;
825 pcursor_it.offset = 0;
826 } else {
827 pcursor_it.offset += row.char_count_including_newline();
828 }
829 }
830
831 cursor
832 }
833}
834
835impl Galley {
837 #[inline]
841 #[allow(clippy::unused_self)]
842 pub fn begin(&self) -> Cursor {
843 Cursor::default()
844 }
845
846 pub fn end(&self) -> Cursor {
848 if self.rows.is_empty() {
849 return Default::default();
850 }
851 let mut ccursor = CCursor {
852 index: 0,
853 prefer_next_row: true,
854 };
855 let mut pcursor = PCursor {
856 paragraph: 0,
857 offset: 0,
858 prefer_next_row: true,
859 };
860 for row in &self.rows {
861 let row_char_count = row.char_count_including_newline();
862 ccursor.index += row_char_count;
863 if row.ends_with_newline {
864 pcursor.paragraph += 1;
865 pcursor.offset = 0;
866 } else {
867 pcursor.offset += row_char_count;
868 }
869 }
870 Cursor {
871 ccursor,
872 rcursor: self.end_rcursor(),
873 pcursor,
874 }
875 }
876
877 pub fn end_rcursor(&self) -> RCursor {
878 if let Some(last_row) = self.rows.last() {
879 RCursor {
880 row: self.rows.len() - 1,
881 column: last_row.char_count_including_newline(),
882 }
883 } else {
884 Default::default()
885 }
886 }
887}
888
889impl Galley {
891 pub fn from_ccursor(&self, ccursor: CCursor) -> Cursor {
893 let prefer_next_row = ccursor.prefer_next_row;
894 let mut ccursor_it = CCursor {
895 index: 0,
896 prefer_next_row,
897 };
898 let mut pcursor_it = PCursor {
899 paragraph: 0,
900 offset: 0,
901 prefer_next_row,
902 };
903
904 for (row_nr, row) in self.rows.iter().enumerate() {
905 let row_char_count = row.char_count_excluding_newline();
906
907 if ccursor_it.index <= ccursor.index
908 && ccursor.index <= ccursor_it.index + row_char_count
909 {
910 let column = ccursor.index - ccursor_it.index;
911
912 let select_next_row_instead = prefer_next_row
913 && !row.ends_with_newline
914 && column >= row.char_count_excluding_newline();
915 if !select_next_row_instead {
916 pcursor_it.offset += column;
917 return Cursor {
918 ccursor,
919 rcursor: RCursor {
920 row: row_nr,
921 column,
922 },
923 pcursor: pcursor_it,
924 };
925 }
926 }
927 ccursor_it.index += row.char_count_including_newline();
928 if row.ends_with_newline {
929 pcursor_it.paragraph += 1;
930 pcursor_it.offset = 0;
931 } else {
932 pcursor_it.offset += row.char_count_including_newline();
933 }
934 }
935 debug_assert!(ccursor_it == self.end().ccursor);
936 Cursor {
937 ccursor: ccursor_it, rcursor: self.end_rcursor(),
939 pcursor: pcursor_it,
940 }
941 }
942
943 pub fn from_rcursor(&self, rcursor: RCursor) -> Cursor {
944 if rcursor.row >= self.rows.len() {
945 return self.end();
946 }
947
948 let prefer_next_row =
949 rcursor.column < self.rows[rcursor.row].char_count_excluding_newline();
950 let mut ccursor_it = CCursor {
951 index: 0,
952 prefer_next_row,
953 };
954 let mut pcursor_it = PCursor {
955 paragraph: 0,
956 offset: 0,
957 prefer_next_row,
958 };
959
960 for (row_nr, row) in self.rows.iter().enumerate() {
961 if row_nr == rcursor.row {
962 ccursor_it.index += rcursor.column.at_most(row.char_count_excluding_newline());
963
964 if row.ends_with_newline {
965 pcursor_it.offset += rcursor.column;
967 } else {
968 pcursor_it.offset += rcursor.column.at_most(row.char_count_excluding_newline());
969 }
970 return Cursor {
971 ccursor: ccursor_it,
972 rcursor,
973 pcursor: pcursor_it,
974 };
975 }
976 ccursor_it.index += row.char_count_including_newline();
977 if row.ends_with_newline {
978 pcursor_it.paragraph += 1;
979 pcursor_it.offset = 0;
980 } else {
981 pcursor_it.offset += row.char_count_including_newline();
982 }
983 }
984 Cursor {
985 ccursor: ccursor_it,
986 rcursor: self.end_rcursor(),
987 pcursor: pcursor_it,
988 }
989 }
990
991 pub fn from_pcursor(&self, pcursor: PCursor) -> Cursor {
993 let prefer_next_row = pcursor.prefer_next_row;
994 let mut ccursor_it = CCursor {
995 index: 0,
996 prefer_next_row,
997 };
998 let mut pcursor_it = PCursor {
999 paragraph: 0,
1000 offset: 0,
1001 prefer_next_row,
1002 };
1003
1004 for (row_nr, row) in self.rows.iter().enumerate() {
1005 if pcursor_it.paragraph == pcursor.paragraph {
1006 if pcursor_it.offset <= pcursor.offset
1009 && (pcursor.offset <= pcursor_it.offset + row.char_count_excluding_newline()
1010 || row.ends_with_newline)
1011 {
1012 let column = pcursor.offset - pcursor_it.offset;
1013
1014 let select_next_row_instead = pcursor.prefer_next_row
1015 && !row.ends_with_newline
1016 && column >= row.char_count_excluding_newline();
1017
1018 if !select_next_row_instead {
1019 ccursor_it.index += column.at_most(row.char_count_excluding_newline());
1020
1021 return Cursor {
1022 ccursor: ccursor_it,
1023 rcursor: RCursor {
1024 row: row_nr,
1025 column,
1026 },
1027 pcursor,
1028 };
1029 }
1030 }
1031 }
1032
1033 ccursor_it.index += row.char_count_including_newline();
1034 if row.ends_with_newline {
1035 pcursor_it.paragraph += 1;
1036 pcursor_it.offset = 0;
1037 } else {
1038 pcursor_it.offset += row.char_count_including_newline();
1039 }
1040 }
1041 Cursor {
1042 ccursor: ccursor_it,
1043 rcursor: self.end_rcursor(),
1044 pcursor,
1045 }
1046 }
1047}
1048
1049impl Galley {
1051 pub fn cursor_left_one_character(&self, cursor: &Cursor) -> Cursor {
1052 if cursor.ccursor.index == 0 {
1053 Default::default()
1054 } else {
1055 let ccursor = CCursor {
1056 index: cursor.ccursor.index,
1057 prefer_next_row: true, };
1059 self.from_ccursor(ccursor - 1)
1060 }
1061 }
1062
1063 pub fn cursor_right_one_character(&self, cursor: &Cursor) -> Cursor {
1064 let ccursor = CCursor {
1065 index: cursor.ccursor.index,
1066 prefer_next_row: true, };
1068 self.from_ccursor(ccursor + 1)
1069 }
1070
1071 pub fn cursor_up_one_row(&self, cursor: &Cursor) -> Cursor {
1072 if cursor.rcursor.row == 0 {
1073 Cursor::default()
1074 } else {
1075 let new_row = cursor.rcursor.row - 1;
1076
1077 let cursor_is_beyond_end_of_current_row = cursor.rcursor.column
1078 >= self.rows[cursor.rcursor.row].char_count_excluding_newline();
1079
1080 let new_rcursor = if cursor_is_beyond_end_of_current_row {
1081 RCursor {
1083 row: new_row,
1084 column: cursor.rcursor.column,
1085 }
1086 } else {
1087 let x = self.pos_from_cursor(cursor).center().x;
1089 let column = if x > self.rows[new_row].rect.right() {
1090 cursor.rcursor.column
1092 } else {
1093 self.rows[new_row].char_at(x)
1094 };
1095 RCursor {
1096 row: new_row,
1097 column,
1098 }
1099 };
1100 self.from_rcursor(new_rcursor)
1101 }
1102 }
1103
1104 pub fn cursor_down_one_row(&self, cursor: &Cursor) -> Cursor {
1105 if cursor.rcursor.row + 1 < self.rows.len() {
1106 let new_row = cursor.rcursor.row + 1;
1107
1108 let cursor_is_beyond_end_of_current_row = cursor.rcursor.column
1109 >= self.rows[cursor.rcursor.row].char_count_excluding_newline();
1110
1111 let new_rcursor = if cursor_is_beyond_end_of_current_row {
1112 RCursor {
1114 row: new_row,
1115 column: cursor.rcursor.column,
1116 }
1117 } else {
1118 let x = self.pos_from_cursor(cursor).center().x;
1120 let column = if x > self.rows[new_row].rect.right() {
1121 cursor.rcursor.column
1123 } else {
1124 self.rows[new_row].char_at(x)
1125 };
1126 RCursor {
1127 row: new_row,
1128 column,
1129 }
1130 };
1131
1132 self.from_rcursor(new_rcursor)
1133 } else {
1134 self.end()
1135 }
1136 }
1137
1138 pub fn cursor_begin_of_row(&self, cursor: &Cursor) -> Cursor {
1139 self.from_rcursor(RCursor {
1140 row: cursor.rcursor.row,
1141 column: 0,
1142 })
1143 }
1144
1145 pub fn cursor_end_of_row(&self, cursor: &Cursor) -> Cursor {
1146 self.from_rcursor(RCursor {
1147 row: cursor.rcursor.row,
1148 column: self.rows[cursor.rcursor.row].char_count_excluding_newline(),
1149 })
1150 }
1151}