line.rs

  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}