1use std::sync::Arc;
2use std::{ops::Range, str::FromStr as _};
3
4use super::{
5 cursor::{CCursor, LayoutCursor},
6 font::UvRect,
7};
8use crate::{Color32, FontId, Mesh, Stroke, text::FontsView};
9use emath::{Align, GuiRounding as _, NumExt as _, OrderedFloat, Pos2, Rect, Vec2, pos2, vec2};
10pub use font_types::Tag;
11use smallvec::SmallVec;
12
13#[derive(Clone, Debug, PartialEq)]
47#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
48pub struct LayoutJob {
49 pub text: String,
51
52 pub sections: Vec<LayoutSection>,
54
55 pub wrap: TextWrapping,
57
58 pub first_row_min_height: f32,
64
65 pub break_on_newline: bool,
73
74 pub halign: Align,
76
77 pub justify: bool,
79
80 pub round_output_to_gui: bool,
82}
83
84impl Default for LayoutJob {
85 #[inline]
86 fn default() -> Self {
87 Self {
88 text: Default::default(),
89 sections: Default::default(),
90 wrap: Default::default(),
91 first_row_min_height: 0.0,
92 break_on_newline: true,
93 halign: Align::LEFT,
94 justify: false,
95 round_output_to_gui: true,
96 }
97 }
98}
99
100impl LayoutJob {
101 #[inline]
103 pub fn simple(text: String, font_id: FontId, color: Color32, wrap_width: f32) -> Self {
104 Self {
105 sections: vec![LayoutSection {
106 leading_space: 0.0,
107 byte_range: 0..text.len(),
108 format: TextFormat::simple(font_id, color),
109 }],
110 text,
111 wrap: TextWrapping {
112 max_width: wrap_width,
113 ..Default::default()
114 },
115 break_on_newline: true,
116 ..Default::default()
117 }
118 }
119
120 #[inline]
122 pub fn simple_format(text: String, format: TextFormat) -> Self {
123 Self {
124 sections: vec![LayoutSection {
125 leading_space: 0.0,
126 byte_range: 0..text.len(),
127 format,
128 }],
129 text,
130 break_on_newline: true,
131 ..Default::default()
132 }
133 }
134
135 #[inline]
137 pub fn simple_singleline(text: String, font_id: FontId, color: Color32) -> Self {
138 Self {
139 sections: vec![LayoutSection {
140 leading_space: 0.0,
141 byte_range: 0..text.len(),
142 format: TextFormat::simple(font_id, color),
143 }],
144 text,
145 wrap: Default::default(),
146 break_on_newline: false,
147 ..Default::default()
148 }
149 }
150
151 #[inline]
152 pub fn single_section(text: String, format: TextFormat) -> Self {
153 Self {
154 sections: vec![LayoutSection {
155 leading_space: 0.0,
156 byte_range: 0..text.len(),
157 format,
158 }],
159 text,
160 wrap: Default::default(),
161 break_on_newline: true,
162 ..Default::default()
163 }
164 }
165
166 #[inline]
167 pub fn is_empty(&self) -> bool {
168 self.sections.is_empty()
169 }
170
171 pub fn append(&mut self, text: &str, leading_space: f32, format: TextFormat) {
173 let start = self.text.len();
174 self.text += text;
175 let byte_range = start..self.text.len();
176 self.sections.push(LayoutSection {
177 leading_space,
178 byte_range,
179 format,
180 });
181 }
182
183 pub fn font_height(&self, fonts: &mut FontsView<'_>) -> f32 {
187 let mut max_height = 0.0_f32;
188 for section in &self.sections {
189 max_height = max_height.max(fonts.row_height(§ion.format.font_id));
190 }
191 max_height
192 }
193
194 pub fn effective_wrap_width(&self) -> f32 {
196 if self.round_output_to_gui {
197 self.wrap.max_width + 0.5
201 } else {
202 self.wrap.max_width
203 }
204 }
205}
206
207impl std::hash::Hash for LayoutJob {
208 #[inline]
209 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
210 let Self {
211 text,
212 sections,
213 wrap,
214 first_row_min_height,
215 break_on_newline,
216 halign,
217 justify,
218 round_output_to_gui,
219 } = self;
220
221 text.hash(state);
222 sections.hash(state);
223 wrap.hash(state);
224 emath::OrderedFloat(*first_row_min_height).hash(state);
225 break_on_newline.hash(state);
226 halign.hash(state);
227 justify.hash(state);
228 round_output_to_gui.hash(state);
229 }
230}
231
232#[derive(Clone, Debug, PartialEq)]
235#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
236pub struct LayoutSection {
237 pub leading_space: f32,
239
240 pub byte_range: Range<usize>,
242
243 pub format: TextFormat,
244}
245
246impl std::hash::Hash for LayoutSection {
247 #[inline]
248 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
249 let Self {
250 leading_space,
251 byte_range,
252 format,
253 } = self;
254 OrderedFloat(*leading_space).hash(state);
255 byte_range.hash(state);
256 format.hash(state);
257 }
258}
259
260pub trait IntoTag {
264 fn into_tag(self) -> font_types::Tag;
265}
266
267impl IntoTag for font_types::Tag {
268 #[inline(always)]
269 fn into_tag(self) -> font_types::Tag {
270 self
271 }
272}
273
274impl IntoTag for u32 {
275 #[inline(always)]
276 fn into_tag(self) -> font_types::Tag {
277 font_types::Tag::from_u32(self)
278 }
279}
280
281impl IntoTag for [u8; 4] {
282 #[inline(always)]
283 fn into_tag(self) -> font_types::Tag {
284 font_types::Tag::new_checked(&self).expect("Invalid variation axis tag")
285 }
286}
287
288impl IntoTag for &[u8; 4] {
289 #[inline(always)]
290 fn into_tag(self) -> font_types::Tag {
291 font_types::Tag::new_checked(self).expect("Invalid variation axis tag")
292 }
293}
294
295impl IntoTag for &str {
296 #[inline(always)]
297 fn into_tag(self) -> font_types::Tag {
298 font_types::Tag::from_str(self).expect("Invalid variation axis tag")
299 }
300}
301
302#[derive(Clone, Debug, PartialEq, Default)]
305#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
306pub struct VariationCoords(SmallVec<[(font_types::Tag, f32); 2]>);
307
308impl VariationCoords {
309 pub fn new<T: IntoTag>(values: impl IntoIterator<Item = (T, f32)>) -> Self {
321 Self(values.into_iter().map(|(t, c)| (t.into_tag(), c)).collect())
322 }
323
324 #[inline(always)]
326 pub fn push(&mut self, tag: impl IntoTag, coord: f32) {
327 self.0.push((tag.into_tag(), coord));
328 }
329
330 pub fn remove(&mut self, index: usize) {
332 self.0.remove(index);
333 }
334
335 pub fn clear(&mut self) {
336 self.0.clear();
337 }
338}
339
340impl AsRef<[(font_types::Tag, f32)]> for VariationCoords {
341 #[inline(always)]
342 fn as_ref(&self) -> &[(font_types::Tag, f32)] {
343 &self.0
344 }
345}
346
347impl AsMut<[(font_types::Tag, f32)]> for VariationCoords {
348 fn as_mut(&mut self) -> &mut [(font_types::Tag, f32)] {
349 &mut self.0
350 }
351}
352
353impl std::hash::Hash for VariationCoords {
354 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
355 self.0.len().hash(state);
356 for (tag, coord) in &self.0 {
357 tag.hash(state);
358 OrderedFloat(*coord).hash(state);
359 }
360 }
361}
362
363#[derive(Clone, Debug, PartialEq)]
365#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
366pub struct TextFormat {
367 pub font_id: FontId,
368
369 pub extra_letter_spacing: f32,
373
374 pub line_height: Option<f32>,
382
383 pub color: Color32,
385
386 pub background: Color32,
387
388 pub expand_bg: f32,
392
393 pub coords: VariationCoords,
394
395 pub italics: bool,
396
397 pub underline: Stroke,
398
399 pub strikethrough: Stroke,
400
401 pub valign: Align,
411}
412
413impl Default for TextFormat {
414 #[inline]
415 fn default() -> Self {
416 Self {
417 font_id: FontId::default(),
418 extra_letter_spacing: 0.0,
419 line_height: None,
420 color: Color32::GRAY,
421 background: Color32::TRANSPARENT,
422 expand_bg: 1.0,
423 coords: VariationCoords::default(),
424 italics: false,
425 underline: Stroke::NONE,
426 strikethrough: Stroke::NONE,
427 valign: Align::BOTTOM,
428 }
429 }
430}
431
432impl std::hash::Hash for TextFormat {
433 #[inline]
434 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
435 let Self {
436 font_id,
437 extra_letter_spacing,
438 line_height,
439 color,
440 background,
441 expand_bg,
442 coords,
443 italics,
444 underline,
445 strikethrough,
446 valign,
447 } = self;
448 font_id.hash(state);
449 emath::OrderedFloat(*extra_letter_spacing).hash(state);
450 if let Some(line_height) = *line_height {
451 emath::OrderedFloat(line_height).hash(state);
452 }
453 color.hash(state);
454 background.hash(state);
455 emath::OrderedFloat(*expand_bg).hash(state);
456 coords.hash(state);
457 italics.hash(state);
458 underline.hash(state);
459 strikethrough.hash(state);
460 valign.hash(state);
461 }
462}
463
464impl TextFormat {
465 #[inline]
466 pub fn simple(font_id: FontId, color: Color32) -> Self {
467 Self {
468 font_id,
469 color,
470 ..Default::default()
471 }
472 }
473}
474
475#[derive(Clone, Copy, Debug, PartialEq, Eq)]
481#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
482pub enum TextWrapMode {
483 Extend,
485
486 Wrap,
488
489 Truncate,
493}
494
495#[derive(Clone, Debug, PartialEq)]
497#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
498pub struct TextWrapping {
499 pub max_width: f32,
508
509 pub max_rows: usize,
523
524 pub break_anywhere: bool,
533
534 pub overflow_character: Option<char>,
540}
541
542impl std::hash::Hash for TextWrapping {
543 #[inline]
544 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
545 let Self {
546 max_width,
547 max_rows,
548 break_anywhere,
549 overflow_character,
550 } = self;
551 emath::OrderedFloat(*max_width).hash(state);
552 max_rows.hash(state);
553 break_anywhere.hash(state);
554 overflow_character.hash(state);
555 }
556}
557
558impl Default for TextWrapping {
559 fn default() -> Self {
560 Self {
561 max_width: f32::INFINITY,
562 max_rows: usize::MAX,
563 break_anywhere: false,
564 overflow_character: Some('…'),
565 }
566 }
567}
568
569impl TextWrapping {
570 pub fn from_wrap_mode_and_width(mode: TextWrapMode, max_width: f32) -> Self {
572 match mode {
573 TextWrapMode::Extend => Self::no_max_width(),
574 TextWrapMode::Wrap => Self::wrap_at_width(max_width),
575 TextWrapMode::Truncate => Self::truncate_at_width(max_width),
576 }
577 }
578
579 pub fn no_max_width() -> Self {
581 Self {
582 max_width: f32::INFINITY,
583 ..Default::default()
584 }
585 }
586
587 pub fn wrap_at_width(max_width: f32) -> Self {
589 Self {
590 max_width,
591 ..Default::default()
592 }
593 }
594
595 pub fn truncate_at_width(max_width: f32) -> Self {
597 Self {
598 max_width,
599 max_rows: 1,
600 break_anywhere: true,
601 ..Default::default()
602 }
603 }
604}
605
606#[derive(Clone, Debug, PartialEq)]
623#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
624pub struct Galley {
625 pub job: Arc<LayoutJob>,
628
629 pub rows: Vec<PlacedRow>,
637
638 pub elided: bool,
640
641 pub rect: Rect,
650
651 pub mesh_bounds: Rect,
654
655 pub num_vertices: usize,
657
658 pub num_indices: usize,
660
661 pub pixels_per_point: f32,
666
667 pub(crate) intrinsic_size: Vec2,
668}
669
670#[derive(Clone, Debug, PartialEq)]
671#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
672pub struct PlacedRow {
673 pub pos: Pos2,
677
678 pub row: Arc<Row>,
680
681 pub ends_with_newline: bool,
687}
688
689impl PlacedRow {
690 pub fn rect(&self) -> Rect {
694 Rect::from_min_size(self.pos, self.row.size)
695 }
696
697 pub fn rect_without_leading_space(&self) -> Rect {
699 let x = self.glyphs.first().map_or(self.pos.x, |g| g.pos.x);
700 let size_x = self.size.x - x;
701 Rect::from_min_size(Pos2::new(x, self.pos.y), Vec2::new(size_x, self.size.y))
702 }
703}
704
705impl std::ops::Deref for PlacedRow {
706 type Target = Row;
707
708 fn deref(&self) -> &Self::Target {
709 &self.row
710 }
711}
712
713#[derive(Clone, Debug, PartialEq)]
714#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
715pub struct Row {
716 pub(crate) section_index_at_start: u32,
722
723 pub glyphs: Vec<Glyph>,
725
726 pub size: Vec2,
729
730 pub visuals: RowVisuals,
732}
733
734#[derive(Clone, Debug, PartialEq, Eq)]
736#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
737pub struct RowVisuals {
738 pub mesh: Mesh,
741
742 pub mesh_bounds: Rect,
745
746 pub glyph_index_start: usize,
751
752 pub glyph_vertex_range: Range<usize>,
756}
757
758impl Default for RowVisuals {
759 fn default() -> Self {
760 Self {
761 mesh: Default::default(),
762 mesh_bounds: Rect::NOTHING,
763 glyph_index_start: 0,
764 glyph_vertex_range: 0..0,
765 }
766 }
767}
768
769#[derive(Copy, Clone, Debug, PartialEq)]
770#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
771pub struct Glyph {
772 pub chr: char,
774
775 pub pos: Pos2,
778
779 pub advance_width: f32,
781
782 pub line_height: f32,
787
788 pub font_ascent: f32,
790
791 pub font_height: f32,
793
794 pub font_face_ascent: f32,
796
797 pub font_face_height: f32,
799
800 pub uv_rect: UvRect,
802
803 pub(crate) section_index: u32,
809
810 pub first_vertex: u32,
812}
813
814impl Glyph {
815 #[inline]
816 pub fn size(&self) -> Vec2 {
817 Vec2::new(self.advance_width, self.line_height)
818 }
819
820 #[inline]
821 pub fn max_x(&self) -> f32 {
822 self.pos.x + self.advance_width
823 }
824
825 #[inline]
827 pub fn logical_rect(&self) -> Rect {
828 Rect::from_min_size(self.pos - vec2(0.0, self.font_ascent), self.size())
829 }
830}
831
832impl Row {
835 pub fn text(&self) -> String {
837 self.glyphs.iter().map(|g| g.chr).collect()
838 }
839
840 #[inline]
842 pub fn char_count_excluding_newline(&self) -> usize {
843 self.glyphs.len()
844 }
845
846 pub fn char_at(&self, desired_x: f32) -> usize {
849 for (i, glyph) in self.glyphs.iter().enumerate() {
850 if desired_x < glyph.logical_rect().center().x {
851 return i;
852 }
853 }
854 self.char_count_excluding_newline()
855 }
856
857 pub fn x_offset(&self, column: usize) -> f32 {
858 if let Some(glyph) = self.glyphs.get(column) {
859 glyph.pos.x
860 } else {
861 self.size.x
862 }
863 }
864
865 #[inline]
866 pub fn height(&self) -> f32 {
867 self.size.y
868 }
869}
870
871impl PlacedRow {
872 #[inline]
873 pub fn min_y(&self) -> f32 {
874 self.rect().top()
875 }
876
877 #[inline]
878 pub fn max_y(&self) -> f32 {
879 self.rect().bottom()
880 }
881
882 #[inline]
884 pub fn char_count_including_newline(&self) -> usize {
885 self.row.glyphs.len() + (self.ends_with_newline as usize)
886 }
887}
888
889impl Galley {
890 #[inline]
891 pub fn is_empty(&self) -> bool {
892 self.job.is_empty()
893 }
894
895 #[inline]
897 pub fn text(&self) -> &str {
898 &self.job.text
899 }
900
901 #[inline]
902 pub fn size(&self) -> Vec2 {
903 self.rect.size()
904 }
905
906 #[inline]
911 pub fn intrinsic_size(&self) -> Vec2 {
912 if self.job.round_output_to_gui {
915 self.intrinsic_size.round_ui()
916 } else {
917 self.intrinsic_size
918 }
919 }
920
921 pub(crate) fn round_output_to_gui(&mut self) {
922 for placed_row in &mut self.rows {
923 let rounded_size = placed_row.row.size.round_ui();
925 if placed_row.row.size != rounded_size {
926 Arc::make_mut(&mut placed_row.row).size = rounded_size;
927 }
928 }
929
930 let rect = &mut self.rect;
931
932 let did_exceed_wrap_width_by_a_lot = rect.width() > self.job.wrap.max_width + 1.0;
933
934 *rect = rect.round_ui();
935
936 if did_exceed_wrap_width_by_a_lot {
937 } else {
940 rect.max.x = rect
942 .max
943 .x
944 .at_most(rect.min.x + self.job.wrap.max_width)
945 .floor_ui();
946 }
947 }
948
949 pub fn concat(job: Arc<LayoutJob>, galleys: &[Arc<Self>], pixels_per_point: f32) -> Self {
951 profiling::function_scope!();
952
953 let mut merged_galley = Self {
954 job,
955 rows: Vec::new(),
956 elided: false,
957 rect: Rect::ZERO,
958 mesh_bounds: Rect::NOTHING,
959 num_vertices: 0,
960 num_indices: 0,
961 pixels_per_point,
962 intrinsic_size: Vec2::ZERO,
963 };
964
965 for (i, galley) in galleys.iter().enumerate() {
966 let current_y_offset = merged_galley.rect.height();
967 let is_last_galley = i + 1 == galleys.len();
968
969 merged_galley
970 .rows
971 .extend(galley.rows.iter().enumerate().map(|(row_idx, placed_row)| {
972 let new_pos = placed_row.pos + current_y_offset * Vec2::Y;
973 let new_pos = new_pos.round_to_pixels(pixels_per_point);
974 merged_galley.mesh_bounds |=
975 placed_row.visuals.mesh_bounds.translate(new_pos.to_vec2());
976 merged_galley.rect |= Rect::from_min_size(new_pos, placed_row.size);
977
978 let mut ends_with_newline = placed_row.ends_with_newline;
979 let is_last_row_in_galley = row_idx + 1 == galley.rows.len();
980 ends_with_newline |= !is_last_galley && is_last_row_in_galley;
982 super::PlacedRow {
983 pos: new_pos,
984 row: Arc::clone(&placed_row.row),
985 ends_with_newline,
986 }
987 }));
988
989 merged_galley.num_vertices += galley.num_vertices;
990 merged_galley.num_indices += galley.num_indices;
991 merged_galley.elided |= galley.elided;
994 merged_galley.intrinsic_size.x =
995 f32::max(merged_galley.intrinsic_size.x, galley.intrinsic_size.x);
996 merged_galley.intrinsic_size.y += galley.intrinsic_size.y;
997 }
998
999 if merged_galley.job.round_output_to_gui {
1000 merged_galley.round_output_to_gui();
1001 }
1002
1003 merged_galley
1004 }
1005}
1006
1007impl AsRef<str> for Galley {
1008 #[inline]
1009 fn as_ref(&self) -> &str {
1010 self.text()
1011 }
1012}
1013
1014impl std::borrow::Borrow<str> for Galley {
1015 #[inline]
1016 fn borrow(&self) -> &str {
1017 self.text()
1018 }
1019}
1020
1021impl std::ops::Deref for Galley {
1022 type Target = str;
1023 #[inline]
1024 fn deref(&self) -> &str {
1025 self.text()
1026 }
1027}
1028
1029impl Galley {
1033 fn end_pos(&self) -> Rect {
1035 if let Some(row) = self.rows.last() {
1036 let x = row.rect().right();
1037 Rect::from_min_max(pos2(x, row.min_y()), pos2(x, row.max_y()))
1038 } else {
1039 Rect::from_min_max(pos2(0.0, 0.0), pos2(0.0, 0.0))
1041 }
1042 }
1043
1044 pub fn pos_from_layout_cursor(&self, layout_cursor: &LayoutCursor) -> Rect {
1046 let Some(row) = self.rows.get(layout_cursor.row) else {
1047 return self.end_pos();
1048 };
1049
1050 let x = row.x_offset(layout_cursor.column) + row.pos.x - self.rect.left();
1051 Rect::from_min_max(pos2(x, row.min_y()), pos2(x, row.max_y()))
1052 }
1053
1054 pub fn pos_from_cursor(&self, cursor: CCursor) -> Rect {
1056 self.pos_from_layout_cursor(&self.layout_from_cursor(cursor))
1057 }
1058
1059 pub fn cursor_from_pos(&self, pos: Vec2) -> CCursor {
1067 const VMARGIN: f32 = 5.0;
1069
1070 if let Some(first_row) = self.rows.first()
1071 && pos.y < first_row.min_y() - VMARGIN
1072 {
1073 return self.begin();
1074 }
1075 if let Some(last_row) = self.rows.last()
1076 && last_row.max_y() + VMARGIN < pos.y
1077 {
1078 return self.end();
1079 }
1080
1081 let mut best_y_dist = f32::INFINITY;
1082 let mut cursor = CCursor::default();
1083
1084 let mut ccursor_index = 0;
1085
1086 for row in &self.rows {
1087 let min_y = row.min_y();
1088 let max_y = row.max_y();
1089
1090 let is_pos_within_row = min_y <= pos.y && pos.y <= max_y;
1091 let y_dist = (min_y - pos.y).abs().min((max_y - pos.y).abs());
1092 if is_pos_within_row || y_dist < best_y_dist {
1093 best_y_dist = y_dist;
1094 let column = row.char_at(pos.x - row.pos.x + self.rect.left());
1096 let prefer_next_row = column < row.char_count_excluding_newline();
1097 cursor = CCursor {
1098 index: ccursor_index + column,
1099 prefer_next_row,
1100 };
1101
1102 if is_pos_within_row {
1103 return cursor;
1104 }
1105 }
1106 ccursor_index += row.char_count_including_newline();
1107 }
1108
1109 cursor
1110 }
1111}
1112
1113impl Galley {
1115 #[inline]
1119 #[expect(clippy::unused_self)]
1120 pub fn begin(&self) -> CCursor {
1121 CCursor::default()
1122 }
1123
1124 pub fn end(&self) -> CCursor {
1126 if self.rows.is_empty() {
1127 return Default::default();
1128 }
1129 let mut ccursor = CCursor {
1130 index: 0,
1131 prefer_next_row: true,
1132 };
1133 for row in &self.rows {
1134 let row_char_count = row.char_count_including_newline();
1135 ccursor.index += row_char_count;
1136 }
1137 ccursor
1138 }
1139}
1140
1141impl Galley {
1143 pub fn layout_from_cursor(&self, cursor: CCursor) -> LayoutCursor {
1145 let prefer_next_row = cursor.prefer_next_row;
1146 let mut ccursor_it = CCursor {
1147 index: 0,
1148 prefer_next_row,
1149 };
1150
1151 for (row_nr, row) in self.rows.iter().enumerate() {
1152 let row_char_count = row.char_count_excluding_newline();
1153
1154 if ccursor_it.index <= cursor.index && cursor.index <= ccursor_it.index + row_char_count
1155 {
1156 let column = cursor.index - ccursor_it.index;
1157
1158 let select_next_row_instead = prefer_next_row
1159 && !row.ends_with_newline
1160 && column >= row.char_count_excluding_newline();
1161 if !select_next_row_instead {
1162 return LayoutCursor {
1163 row: row_nr,
1164 column,
1165 };
1166 }
1167 }
1168 ccursor_it.index += row.char_count_including_newline();
1169 }
1170 debug_assert!(ccursor_it == self.end(), "Cursor out of bounds");
1171
1172 if let Some(last_row) = self.rows.last() {
1173 LayoutCursor {
1174 row: self.rows.len() - 1,
1175 column: last_row.char_count_including_newline(),
1176 }
1177 } else {
1178 Default::default()
1179 }
1180 }
1181
1182 fn cursor_from_layout(&self, layout_cursor: LayoutCursor) -> CCursor {
1183 if layout_cursor.row >= self.rows.len() {
1184 return self.end();
1185 }
1186
1187 let prefer_next_row =
1188 layout_cursor.column < self.rows[layout_cursor.row].char_count_excluding_newline();
1189 let mut cursor_it = CCursor {
1190 index: 0,
1191 prefer_next_row,
1192 };
1193
1194 for (row_nr, row) in self.rows.iter().enumerate() {
1195 if row_nr == layout_cursor.row {
1196 cursor_it.index += layout_cursor
1197 .column
1198 .at_most(row.char_count_excluding_newline());
1199
1200 return cursor_it;
1201 }
1202 cursor_it.index += row.char_count_including_newline();
1203 }
1204 cursor_it
1205 }
1206}
1207
1208impl Galley {
1210 #[expect(clippy::unused_self)]
1211 pub fn cursor_left_one_character(&self, cursor: &CCursor) -> CCursor {
1212 if cursor.index == 0 {
1213 Default::default()
1214 } else {
1215 CCursor {
1216 index: cursor.index - 1,
1217 prefer_next_row: true, }
1219 }
1220 }
1221
1222 pub fn cursor_right_one_character(&self, cursor: &CCursor) -> CCursor {
1223 CCursor {
1224 index: (cursor.index + 1).min(self.end().index),
1225 prefer_next_row: true, }
1227 }
1228
1229 pub fn clamp_cursor(&self, cursor: &CCursor) -> CCursor {
1230 self.cursor_from_layout(self.layout_from_cursor(*cursor))
1231 }
1232
1233 pub fn cursor_up_one_row(
1234 &self,
1235 cursor: &CCursor,
1236 h_pos: Option<f32>,
1237 ) -> (CCursor, Option<f32>) {
1238 let layout_cursor = self.layout_from_cursor(*cursor);
1239 let h_pos = h_pos.unwrap_or_else(|| self.pos_from_layout_cursor(&layout_cursor).center().x);
1240 if layout_cursor.row == 0 {
1241 (CCursor::default(), None)
1242 } else {
1243 let new_row = layout_cursor.row - 1;
1244
1245 let new_layout_cursor = {
1246 let column = self.rows[new_row].char_at(h_pos);
1248 LayoutCursor {
1249 row: new_row,
1250 column,
1251 }
1252 };
1253 (self.cursor_from_layout(new_layout_cursor), Some(h_pos))
1254 }
1255 }
1256
1257 pub fn cursor_down_one_row(
1258 &self,
1259 cursor: &CCursor,
1260 h_pos: Option<f32>,
1261 ) -> (CCursor, Option<f32>) {
1262 let layout_cursor = self.layout_from_cursor(*cursor);
1263 let h_pos = h_pos.unwrap_or_else(|| self.pos_from_layout_cursor(&layout_cursor).center().x);
1264 if layout_cursor.row + 1 < self.rows.len() {
1265 let new_row = layout_cursor.row + 1;
1266
1267 let new_layout_cursor = {
1268 let column = self.rows[new_row].char_at(h_pos);
1270 LayoutCursor {
1271 row: new_row,
1272 column,
1273 }
1274 };
1275
1276 (self.cursor_from_layout(new_layout_cursor), Some(h_pos))
1277 } else {
1278 (self.end(), None)
1279 }
1280 }
1281
1282 pub fn cursor_begin_of_row(&self, cursor: &CCursor) -> CCursor {
1283 let layout_cursor = self.layout_from_cursor(*cursor);
1284 self.cursor_from_layout(LayoutCursor {
1285 row: layout_cursor.row,
1286 column: 0,
1287 })
1288 }
1289
1290 pub fn cursor_end_of_row(&self, cursor: &CCursor) -> CCursor {
1291 let layout_cursor = self.layout_from_cursor(*cursor);
1292 self.cursor_from_layout(LayoutCursor {
1293 row: layout_cursor.row,
1294 column: self.rows[layout_cursor.row].char_count_excluding_newline(),
1295 })
1296 }
1297
1298 pub fn cursor_begin_of_paragraph(&self, cursor: &CCursor) -> CCursor {
1299 let mut layout_cursor = self.layout_from_cursor(*cursor);
1300 layout_cursor.column = 0;
1301
1302 loop {
1303 let prev_row = layout_cursor
1304 .row
1305 .checked_sub(1)
1306 .and_then(|row| self.rows.get(row));
1307
1308 let Some(prev_row) = prev_row else {
1309 break;
1311 };
1312
1313 if prev_row.ends_with_newline {
1314 break;
1315 }
1316
1317 layout_cursor.row -= 1;
1318 }
1319
1320 self.cursor_from_layout(layout_cursor)
1321 }
1322
1323 pub fn cursor_end_of_paragraph(&self, cursor: &CCursor) -> CCursor {
1324 let mut layout_cursor = self.layout_from_cursor(*cursor);
1325 loop {
1326 let row = &self.rows[layout_cursor.row];
1327 if row.ends_with_newline || layout_cursor.row == self.rows.len() - 1 {
1328 layout_cursor.column = row.char_count_excluding_newline();
1329 break;
1330 }
1331
1332 layout_cursor.row += 1;
1333 }
1334
1335 self.cursor_from_layout(layout_cursor)
1336 }
1337}