1use crate::{
2 black, point, px, size, BorrowWindow, Bounds, Hsla, Pixels, Point, Result, Size,
3 UnderlineStyle, WindowContext, WrapBoundary, WrappedLineLayout,
4};
5use derive_more::{Deref, DerefMut};
6use smallvec::SmallVec;
7use std::sync::Arc;
8
9#[derive(Debug, Clone)]
10pub struct DecorationRun {
11 pub len: u32,
12 pub color: Hsla,
13 pub underline: Option<UnderlineStyle>,
14}
15
16#[derive(Clone, Default, Debug, Deref, DerefMut)]
17pub struct Line {
18 #[deref]
19 #[deref_mut]
20 pub(crate) layout: Arc<WrappedLineLayout>,
21 pub(crate) decorations: SmallVec<[DecorationRun; 32]>,
22}
23
24impl Line {
25 pub fn size(&self, line_height: Pixels) -> Size<Pixels> {
26 size(
27 self.layout.width,
28 line_height * (self.layout.wrap_boundaries.len() + 1),
29 )
30 }
31
32 pub fn width(&self) -> Pixels {
33 self.layout.width
34 }
35
36 pub fn wrap_count(&self) -> usize {
37 self.layout.wrap_boundaries.len()
38 }
39
40 pub fn paint(
41 &self,
42 origin: Point<Pixels>,
43 line_height: Pixels,
44 cx: &mut WindowContext,
45 ) -> Result<()> {
46 let padding_top =
47 (line_height - self.layout.layout.ascent - self.layout.layout.descent) / 2.;
48 let baseline_offset = point(px(0.), padding_top + self.layout.layout.ascent);
49
50 let mut style_runs = self.decorations.iter();
51 let mut wraps = self.layout.wrap_boundaries.iter().peekable();
52 let mut run_end = 0;
53 let mut color = black();
54 let mut current_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
55 let text_system = cx.text_system().clone();
56
57 let mut glyph_origin = origin;
58 let mut prev_glyph_position = Point::default();
59 for (run_ix, run) in self.layout.layout.runs.iter().enumerate() {
60 let max_glyph_size = text_system
61 .bounding_box(run.font_id, self.layout.layout.font_size)?
62 .size;
63
64 for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
65 glyph_origin.x += glyph.position.x - prev_glyph_position.x;
66
67 if wraps.peek() == Some(&&WrapBoundary { run_ix, glyph_ix }) {
68 wraps.next();
69 if let Some((underline_origin, underline_style)) = current_underline.take() {
70 cx.paint_underline(
71 underline_origin,
72 glyph_origin.x - underline_origin.x,
73 &underline_style,
74 )?;
75 }
76
77 glyph_origin.x = origin.x;
78 glyph_origin.y += line_height;
79 }
80 prev_glyph_position = glyph.position;
81 let glyph_origin = glyph_origin + baseline_offset;
82
83 let mut finished_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
84 if glyph.index >= run_end {
85 if let Some(style_run) = style_runs.next() {
86 if let Some((_, underline_style)) = &mut current_underline {
87 if style_run.underline.as_ref() != Some(underline_style) {
88 finished_underline = current_underline.take();
89 }
90 }
91 if let Some(run_underline) = style_run.underline.as_ref() {
92 current_underline.get_or_insert((
93 point(
94 glyph_origin.x,
95 origin.y
96 + baseline_offset.y
97 + (self.layout.layout.descent * 0.618),
98 ),
99 UnderlineStyle {
100 color: Some(run_underline.color.unwrap_or(style_run.color)),
101 thickness: run_underline.thickness,
102 wavy: run_underline.wavy,
103 },
104 ));
105 }
106
107 run_end += style_run.len as usize;
108 color = style_run.color;
109 } else {
110 run_end = self.layout.text.len();
111 finished_underline = current_underline.take();
112 }
113 }
114
115 if let Some((underline_origin, underline_style)) = finished_underline {
116 cx.paint_underline(
117 underline_origin,
118 glyph_origin.x - underline_origin.x,
119 &underline_style,
120 )?;
121 }
122
123 let max_glyph_bounds = Bounds {
124 origin: glyph_origin,
125 size: max_glyph_size,
126 };
127
128 let content_mask = cx.content_mask();
129 if max_glyph_bounds.intersects(&content_mask.bounds) {
130 if glyph.is_emoji {
131 cx.paint_emoji(
132 glyph_origin,
133 run.font_id,
134 glyph.id,
135 self.layout.layout.font_size,
136 )?;
137 } else {
138 cx.paint_glyph(
139 glyph_origin,
140 run.font_id,
141 glyph.id,
142 self.layout.layout.font_size,
143 color,
144 )?;
145 }
146 }
147 }
148 }
149
150 if let Some((underline_start, underline_style)) = current_underline.take() {
151 let line_end_x = origin.x + self.layout.layout.width;
152 cx.paint_underline(
153 underline_start,
154 line_end_x - underline_start.x,
155 &underline_style,
156 )?;
157 }
158
159 Ok(())
160 }
161}