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.pos.x + self.glyphs.first().map_or(0.0, |g| g.pos.x);
700 let right = self.pos.x + self.size.x;
701 Rect::from_min_max(
702 Pos2::new(x, self.pos.y),
703 Pos2::new(right, self.pos.y + self.size.y),
704 )
705 }
706}
707
708impl std::ops::Deref for PlacedRow {
709 type Target = Row;
710
711 fn deref(&self) -> &Self::Target {
712 &self.row
713 }
714}
715
716#[derive(Clone, Debug, PartialEq)]
717#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
718pub struct Row {
719 pub(crate) section_index_at_start: u32,
725
726 pub glyphs: Vec<Glyph>,
728
729 pub size: Vec2,
732
733 pub visuals: RowVisuals,
735}
736
737#[derive(Clone, Debug, PartialEq, Eq)]
739#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
740pub struct RowVisuals {
741 pub mesh: Mesh,
744
745 pub mesh_bounds: Rect,
748
749 pub glyph_index_start: usize,
754
755 pub glyph_vertex_range: Range<usize>,
759}
760
761impl Default for RowVisuals {
762 fn default() -> Self {
763 Self {
764 mesh: Default::default(),
765 mesh_bounds: Rect::NOTHING,
766 glyph_index_start: 0,
767 glyph_vertex_range: 0..0,
768 }
769 }
770}
771
772#[derive(Copy, Clone, Debug, PartialEq)]
773#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
774pub struct Glyph {
775 pub chr: char,
777
778 pub pos: Pos2,
781
782 pub advance_width: f32,
784
785 pub line_height: f32,
790
791 pub font_ascent: f32,
793
794 pub font_height: f32,
796
797 pub font_face_ascent: f32,
799
800 pub font_face_height: f32,
802
803 pub uv_rect: UvRect,
805
806 pub(crate) section_index: u32,
812
813 pub first_vertex: u32,
815}
816
817impl Glyph {
818 #[inline]
819 pub fn size(&self) -> Vec2 {
820 Vec2::new(self.advance_width, self.line_height)
821 }
822
823 #[inline]
824 pub fn max_x(&self) -> f32 {
825 self.pos.x + self.advance_width
826 }
827
828 #[inline]
830 pub fn logical_rect(&self) -> Rect {
831 Rect::from_min_size(self.pos - vec2(0.0, self.font_ascent), self.size())
832 }
833}
834
835impl Row {
838 pub fn text(&self) -> String {
840 self.glyphs.iter().map(|g| g.chr).collect()
841 }
842
843 #[inline]
845 pub fn char_count_excluding_newline(&self) -> usize {
846 self.glyphs.len()
847 }
848
849 pub fn char_at(&self, desired_x: f32) -> usize {
852 for (i, glyph) in self.glyphs.iter().enumerate() {
853 if desired_x < glyph.logical_rect().center().x {
854 return i;
855 }
856 }
857 self.char_count_excluding_newline()
858 }
859
860 pub fn x_offset(&self, column: usize) -> f32 {
861 if let Some(glyph) = self.glyphs.get(column) {
862 glyph.pos.x
863 } else {
864 self.size.x
865 }
866 }
867
868 #[inline]
869 pub fn height(&self) -> f32 {
870 self.size.y
871 }
872}
873
874impl PlacedRow {
875 #[inline]
876 pub fn min_y(&self) -> f32 {
877 self.rect().top()
878 }
879
880 #[inline]
881 pub fn max_y(&self) -> f32 {
882 self.rect().bottom()
883 }
884
885 #[inline]
887 pub fn char_count_including_newline(&self) -> usize {
888 self.row.glyphs.len() + (self.ends_with_newline as usize)
889 }
890}
891
892impl Galley {
893 #[inline]
894 pub fn is_empty(&self) -> bool {
895 self.job.is_empty()
896 }
897
898 #[inline]
900 pub fn text(&self) -> &str {
901 &self.job.text
902 }
903
904 #[inline]
905 pub fn size(&self) -> Vec2 {
906 self.rect.size()
907 }
908
909 #[inline]
914 pub fn intrinsic_size(&self) -> Vec2 {
915 if self.job.round_output_to_gui {
918 self.intrinsic_size.round_ui()
919 } else {
920 self.intrinsic_size
921 }
922 }
923
924 pub(crate) fn round_output_to_gui(&mut self) {
925 for placed_row in &mut self.rows {
926 let rounded_size = placed_row.row.size.round_ui();
928 if placed_row.row.size != rounded_size {
929 Arc::make_mut(&mut placed_row.row).size = rounded_size;
930 }
931 }
932
933 let rect = &mut self.rect;
934
935 let did_exceed_wrap_width_by_a_lot = rect.width() > self.job.wrap.max_width + 1.0;
936
937 *rect = rect.round_ui();
938
939 if did_exceed_wrap_width_by_a_lot {
940 } else {
943 rect.max.x = rect
945 .max
946 .x
947 .at_most(rect.min.x + self.job.wrap.max_width)
948 .floor_ui();
949 }
950 }
951
952 pub fn concat(job: Arc<LayoutJob>, galleys: &[Arc<Self>], pixels_per_point: f32) -> Self {
954 profiling::function_scope!();
955
956 let mut merged_galley = Self {
957 job,
958 rows: Vec::new(),
959 elided: false,
960 rect: Rect::ZERO,
961 mesh_bounds: Rect::NOTHING,
962 num_vertices: 0,
963 num_indices: 0,
964 pixels_per_point,
965 intrinsic_size: Vec2::ZERO,
966 };
967
968 for (i, galley) in galleys.iter().enumerate() {
969 let current_y_offset = merged_galley.rect.height();
970 let is_last_galley = i + 1 == galleys.len();
971
972 merged_galley
973 .rows
974 .extend(galley.rows.iter().enumerate().map(|(row_idx, placed_row)| {
975 let new_pos = placed_row.pos + current_y_offset * Vec2::Y;
976 let new_pos = new_pos.round_to_pixels(pixels_per_point);
977 merged_galley.mesh_bounds |=
978 placed_row.visuals.mesh_bounds.translate(new_pos.to_vec2());
979 merged_galley.rect |= Rect::from_min_size(new_pos, placed_row.size);
980
981 let mut ends_with_newline = placed_row.ends_with_newline;
982 let is_last_row_in_galley = row_idx + 1 == galley.rows.len();
983 ends_with_newline |= !is_last_galley && is_last_row_in_galley;
985 super::PlacedRow {
986 pos: new_pos,
987 row: Arc::clone(&placed_row.row),
988 ends_with_newline,
989 }
990 }));
991
992 merged_galley.num_vertices += galley.num_vertices;
993 merged_galley.num_indices += galley.num_indices;
994 merged_galley.elided |= galley.elided;
997 merged_galley.intrinsic_size.x =
998 f32::max(merged_galley.intrinsic_size.x, galley.intrinsic_size.x);
999 merged_galley.intrinsic_size.y += galley.intrinsic_size.y;
1000 }
1001
1002 if merged_galley.job.round_output_to_gui {
1003 merged_galley.round_output_to_gui();
1004 }
1005
1006 merged_galley
1007 }
1008}
1009
1010impl AsRef<str> for Galley {
1011 #[inline]
1012 fn as_ref(&self) -> &str {
1013 self.text()
1014 }
1015}
1016
1017impl std::borrow::Borrow<str> for Galley {
1018 #[inline]
1019 fn borrow(&self) -> &str {
1020 self.text()
1021 }
1022}
1023
1024impl std::ops::Deref for Galley {
1025 type Target = str;
1026 #[inline]
1027 fn deref(&self) -> &str {
1028 self.text()
1029 }
1030}
1031
1032impl Galley {
1036 fn end_pos(&self) -> Rect {
1038 if let Some(row) = self.rows.last() {
1039 let x = row.rect().right();
1040 Rect::from_min_max(pos2(x, row.min_y()), pos2(x, row.max_y()))
1041 } else {
1042 Rect::from_min_max(pos2(0.0, 0.0), pos2(0.0, 0.0))
1044 }
1045 }
1046
1047 pub fn pos_from_layout_cursor(&self, layout_cursor: &LayoutCursor) -> Rect {
1049 let Some(row) = self.rows.get(layout_cursor.row) else {
1050 return self.end_pos();
1051 };
1052
1053 let x = row.x_offset(layout_cursor.column) + row.pos.x;
1054 Rect::from_min_max(pos2(x, row.min_y()), pos2(x, row.max_y()))
1055 }
1056
1057 pub fn pos_from_cursor(&self, cursor: CCursor) -> Rect {
1059 self.pos_from_layout_cursor(&self.layout_from_cursor(cursor))
1060 }
1061
1062 pub fn cursor_from_pos(&self, pos: Vec2) -> CCursor {
1070 const VMARGIN: f32 = 5.0;
1072
1073 if let Some(first_row) = self.rows.first()
1074 && pos.y < first_row.min_y() - VMARGIN
1075 {
1076 return self.begin();
1077 }
1078 if let Some(last_row) = self.rows.last()
1079 && last_row.max_y() + VMARGIN < pos.y
1080 {
1081 return self.end();
1082 }
1083
1084 let mut best_y_dist = f32::INFINITY;
1085 let mut cursor = CCursor::default();
1086
1087 let mut ccursor_index = 0;
1088
1089 for row in &self.rows {
1090 let min_y = row.min_y();
1091 let max_y = row.max_y();
1092
1093 let is_pos_within_row = min_y <= pos.y && pos.y <= max_y;
1094 let y_dist = (min_y - pos.y).abs().min((max_y - pos.y).abs());
1095 if is_pos_within_row || y_dist < best_y_dist {
1096 best_y_dist = y_dist;
1097 let column = row.char_at(pos.x - row.pos.x);
1099 let prefer_next_row = column < row.char_count_excluding_newline();
1100 cursor = CCursor {
1101 index: ccursor_index + column,
1102 prefer_next_row,
1103 };
1104
1105 if is_pos_within_row {
1106 return cursor;
1107 }
1108 }
1109 ccursor_index += row.char_count_including_newline();
1110 }
1111
1112 cursor
1113 }
1114}
1115
1116impl Galley {
1118 #[inline]
1122 #[expect(clippy::unused_self)]
1123 pub fn begin(&self) -> CCursor {
1124 CCursor::default()
1125 }
1126
1127 pub fn end(&self) -> CCursor {
1129 if self.rows.is_empty() {
1130 return Default::default();
1131 }
1132 let mut ccursor = CCursor {
1133 index: 0,
1134 prefer_next_row: true,
1135 };
1136 for row in &self.rows {
1137 let row_char_count = row.char_count_including_newline();
1138 ccursor.index += row_char_count;
1139 }
1140 ccursor
1141 }
1142}
1143
1144impl Galley {
1146 pub fn layout_from_cursor(&self, cursor: CCursor) -> LayoutCursor {
1148 let prefer_next_row = cursor.prefer_next_row;
1149 let mut ccursor_it = CCursor {
1150 index: 0,
1151 prefer_next_row,
1152 };
1153
1154 for (row_nr, row) in self.rows.iter().enumerate() {
1155 let row_char_count = row.char_count_excluding_newline();
1156
1157 if ccursor_it.index <= cursor.index && cursor.index <= ccursor_it.index + row_char_count
1158 {
1159 let column = cursor.index - ccursor_it.index;
1160
1161 let select_next_row_instead = prefer_next_row
1162 && !row.ends_with_newline
1163 && column >= row.char_count_excluding_newline();
1164 if !select_next_row_instead {
1165 return LayoutCursor {
1166 row: row_nr,
1167 column,
1168 };
1169 }
1170 }
1171 ccursor_it.index += row.char_count_including_newline();
1172 }
1173 debug_assert!(ccursor_it == self.end(), "Cursor out of bounds");
1174
1175 if let Some(last_row) = self.rows.last() {
1176 LayoutCursor {
1177 row: self.rows.len() - 1,
1178 column: last_row.char_count_including_newline(),
1179 }
1180 } else {
1181 Default::default()
1182 }
1183 }
1184
1185 fn cursor_from_layout(&self, layout_cursor: LayoutCursor) -> CCursor {
1186 if layout_cursor.row >= self.rows.len() {
1187 return self.end();
1188 }
1189
1190 let prefer_next_row =
1191 layout_cursor.column < self.rows[layout_cursor.row].char_count_excluding_newline();
1192 let mut cursor_it = CCursor {
1193 index: 0,
1194 prefer_next_row,
1195 };
1196
1197 for (row_nr, row) in self.rows.iter().enumerate() {
1198 if row_nr == layout_cursor.row {
1199 cursor_it.index += layout_cursor
1200 .column
1201 .at_most(row.char_count_excluding_newline());
1202
1203 return cursor_it;
1204 }
1205 cursor_it.index += row.char_count_including_newline();
1206 }
1207 cursor_it
1208 }
1209}
1210
1211impl Galley {
1213 #[expect(clippy::unused_self)]
1214 pub fn cursor_left_one_character(&self, cursor: &CCursor) -> CCursor {
1215 if cursor.index == 0 {
1216 Default::default()
1217 } else {
1218 CCursor {
1219 index: cursor.index - 1,
1220 prefer_next_row: true, }
1222 }
1223 }
1224
1225 pub fn cursor_right_one_character(&self, cursor: &CCursor) -> CCursor {
1226 CCursor {
1227 index: (cursor.index + 1).min(self.end().index),
1228 prefer_next_row: true, }
1230 }
1231
1232 pub fn clamp_cursor(&self, cursor: &CCursor) -> CCursor {
1233 self.cursor_from_layout(self.layout_from_cursor(*cursor))
1234 }
1235
1236 pub fn cursor_up_one_row(
1237 &self,
1238 cursor: &CCursor,
1239 h_pos: Option<f32>,
1240 ) -> (CCursor, Option<f32>) {
1241 let layout_cursor = self.layout_from_cursor(*cursor);
1242 let h_pos = h_pos.unwrap_or_else(|| self.pos_from_layout_cursor(&layout_cursor).center().x);
1243 if layout_cursor.row == 0 {
1244 (CCursor::default(), None)
1245 } else {
1246 let new_row = layout_cursor.row - 1;
1247
1248 let new_layout_cursor = {
1249 let column = self.rows[new_row].char_at(h_pos - self.rows[new_row].pos.x);
1252 LayoutCursor {
1253 row: new_row,
1254 column,
1255 }
1256 };
1257 (self.cursor_from_layout(new_layout_cursor), Some(h_pos))
1258 }
1259 }
1260
1261 pub fn cursor_down_one_row(
1262 &self,
1263 cursor: &CCursor,
1264 h_pos: Option<f32>,
1265 ) -> (CCursor, Option<f32>) {
1266 let layout_cursor = self.layout_from_cursor(*cursor);
1267 let h_pos = h_pos.unwrap_or_else(|| self.pos_from_layout_cursor(&layout_cursor).center().x);
1268 if layout_cursor.row + 1 < self.rows.len() {
1269 let new_row = layout_cursor.row + 1;
1270
1271 let new_layout_cursor = {
1272 let column = self.rows[new_row].char_at(h_pos - self.rows[new_row].pos.x);
1275 LayoutCursor {
1276 row: new_row,
1277 column,
1278 }
1279 };
1280
1281 (self.cursor_from_layout(new_layout_cursor), Some(h_pos))
1282 } else {
1283 (self.end(), None)
1284 }
1285 }
1286
1287 pub fn cursor_begin_of_row(&self, cursor: &CCursor) -> CCursor {
1288 let layout_cursor = self.layout_from_cursor(*cursor);
1289 self.cursor_from_layout(LayoutCursor {
1290 row: layout_cursor.row,
1291 column: 0,
1292 })
1293 }
1294
1295 pub fn cursor_end_of_row(&self, cursor: &CCursor) -> CCursor {
1296 let layout_cursor = self.layout_from_cursor(*cursor);
1297 self.cursor_from_layout(LayoutCursor {
1298 row: layout_cursor.row,
1299 column: self.rows[layout_cursor.row].char_count_excluding_newline(),
1300 })
1301 }
1302
1303 pub fn cursor_begin_of_paragraph(&self, cursor: &CCursor) -> CCursor {
1304 let mut layout_cursor = self.layout_from_cursor(*cursor);
1305 layout_cursor.column = 0;
1306
1307 loop {
1308 let prev_row = layout_cursor
1309 .row
1310 .checked_sub(1)
1311 .and_then(|row| self.rows.get(row));
1312
1313 let Some(prev_row) = prev_row else {
1314 break;
1316 };
1317
1318 if prev_row.ends_with_newline {
1319 break;
1320 }
1321
1322 layout_cursor.row -= 1;
1323 }
1324
1325 self.cursor_from_layout(layout_cursor)
1326 }
1327
1328 pub fn cursor_end_of_paragraph(&self, cursor: &CCursor) -> CCursor {
1329 let mut layout_cursor = self.layout_from_cursor(*cursor);
1330 loop {
1331 let row = &self.rows[layout_cursor.row];
1332 if row.ends_with_newline || layout_cursor.row == self.rows.len() - 1 {
1333 layout_cursor.column = row.char_count_excluding_newline();
1334 break;
1335 }
1336
1337 layout_cursor.row += 1;
1338 }
1339
1340 self.cursor_from_layout(layout_cursor)
1341 }
1342}