ailloli_ui_editor/layout/text_runs.rs
1//! Visible paragraph run values and baseline-derived visual bounds.
2
3use std::ops::Range;
4
5use ailloli_ui_text::{PreparedTextLayout, TextLayoutHandle};
6
7use crate::EditorStyle;
8
9/// One visible logical paragraph run in viewport coordinates.
10///
11/// `byte_range` addresses the source buffer while the layout text and helper
12/// offsets are run-local. `baseline_y` is relative to the text viewport before
13/// the caller adds its screen-space origin.
14///
15/// # Examples
16///
17/// ```
18/// use ailloli_ui_core::{Color, FontId, TextStyle};
19/// use ailloli_ui_editor::layout::EditorTextRun;
20/// use ailloli_ui_text::{TextLayoutParams, TextSystem};
21/// let mut system = TextSystem::new();
22/// let layout = system.layout_cached(TextLayoutParams::new(
23/// "line", TextStyle::new(FontId::Mono, 13, Color::WHITE),
24/// ));
25/// let run = EditorTextRun { index: 2, byte_range: 8..12, baseline_y: 18.0, layout };
26/// assert_eq!(run.byte_range, 8..12);
27/// ```
28#[derive(Debug, Clone)]
29pub struct EditorTextRun {
30 /// Zero-based logical paragraph index in the source buffer.
31 pub index: usize,
32 /// Half-open UTF-8 byte range in the source, excluding a trailing newline.
33 pub byte_range: Range<usize>,
34 /// First shaped baseline relative to the visible text viewport.
35 pub baseline_y: f32,
36 /// Shared prepared layout for this paragraph's newline-trimmed text.
37 pub layout: TextLayoutHandle,
38}
39
40/// Returns the first shaped line's baseline in layout-local logical pixels.
41///
42/// Empty layouts use the zero sentinel.
43///
44/// # Examples
45///
46/// ```
47/// use ailloli_ui_core::{Color, FontId, TextStyle};
48/// use ailloli_ui_editor::layout::first_layout_baseline;
49/// use ailloli_ui_text::{TextLayoutParams, TextSystem};
50/// let mut system = TextSystem::new();
51/// let layout = system.layout_cached(TextLayoutParams::new(
52/// "x", TextStyle::new(FontId::Mono, 13, Color::WHITE),
53/// ));
54/// assert!(first_layout_baseline(&layout) >= 0.0);
55/// ```
56pub fn first_layout_baseline(layout: &PreparedTextLayout) -> f32 {
57 layout
58 .lines
59 .first()
60 .map(|line| line.baseline_y)
61 .unwrap_or(0.0)
62}
63
64/// Returns the run height in logical pixels with an editor-line-height floor.
65///
66/// The configured floor is itself clamped to at least `1.0`; non-finite layout
67/// or style values are not repaired beyond normal [`f32::max`] behavior.
68///
69/// # Examples
70///
71/// ```
72/// use ailloli_ui_core::{Color, FontId, TextStyle};
73/// use ailloli_ui_editor::{layout::layout_visual_height, EditorStyle};
74/// use ailloli_ui_text::{TextLayoutParams, TextSystem};
75/// let mut system = TextSystem::new();
76/// let layout = system.layout_cached(TextLayoutParams::new(
77/// "x", TextStyle::new(FontId::Mono, 13, Color::WHITE),
78/// ));
79/// assert!(layout_visual_height(&layout, EditorStyle::default()) >= 18.0);
80/// ```
81pub fn layout_visual_height(layout: &PreparedTextLayout, style: EditorStyle) -> f32 {
82 layout.height().max(style.line_height.max(1.0))
83}
84
85/// Returns a run's top edge relative to the text viewport.
86///
87/// # Examples
88///
89/// ```
90/// use ailloli_ui_core::{Color, FontId, TextStyle};
91/// use ailloli_ui_editor::layout::{first_layout_baseline, run_visual_top, EditorTextRun};
92/// use ailloli_ui_text::{TextLayoutParams, TextSystem};
93/// let mut system = TextSystem::new();
94/// let layout = system.layout_cached(TextLayoutParams::new(
95/// "x", TextStyle::new(FontId::Mono, 13, Color::WHITE),
96/// ));
97/// let baseline = first_layout_baseline(&layout);
98/// let run = EditorTextRun { index: 0, byte_range: 0..1, baseline_y: baseline + 7.0, layout };
99/// assert_eq!(run_visual_top(&run), 7.0);
100/// ```
101pub fn run_visual_top(run: &EditorTextRun) -> f32 {
102 run.baseline_y - first_layout_baseline(&run.layout)
103}
104
105/// Returns a run's bottom edge relative to the text viewport.
106///
107/// # Examples
108///
109/// ```
110/// use ailloli_ui_core::{Color, FontId, TextStyle};
111/// use ailloli_ui_editor::{layout::{run_visual_bottom, run_visual_top, EditorTextRun}, EditorStyle};
112/// use ailloli_ui_text::{TextLayoutParams, TextSystem};
113/// let mut system = TextSystem::new();
114/// let layout = system.layout_cached(TextLayoutParams::new(
115/// "x", TextStyle::new(FontId::Mono, 13, Color::WHITE),
116/// ));
117/// let run = EditorTextRun { index: 0, byte_range: 0..1, baseline_y: 14.0, layout };
118/// assert!(run_visual_bottom(&run, EditorStyle::default()) > run_visual_top(&run));
119/// ```
120pub fn run_visual_bottom(run: &EditorTextRun, style: EditorStyle) -> f32 {
121 run_visual_top(run) + layout_visual_height(&run.layout, style)
122}