Skip to main content

ui/input/
overdraw.rs

1//! Text drawn again over the layers painted under it — a selection, a
2//! highlight, a find match — where its own colour does not read on them.
3//!
4//! gpui shapes each colour of a line apart, so a piece is recoloured by
5//! drawing its glyphs again over the line rather than by cutting its runs.
6
7use std::ops::Range;
8
9use gpui::{
10    BorderStyle, Bounds, Hsla, Pixels, Point, TextRun, Window, WrappedLineLayout, point, px, quad,
11    size,
12};
13
14use super::caret;
15
16/// A piece of text that the layers under it leave below its contrast.
17#[derive(Clone, Debug, PartialEq)]
18pub struct Overdraw {
19    pub range: Range<usize>,
20    /// The layers under the piece, flattened over the page.
21    pub plate: Hsla,
22    /// The colour its glyphs are drawn again in.
23    pub ink: Hsla,
24}
25
26/// What is painted under a text, in its offsets: washes (a highlight, a find
27/// match, a comment) and a selection, each in the shape `S` its painter draws
28/// it in. A painter draws [`Layers::painted`] and [`overdraws`] reads the same
29/// order.
30#[derive(Clone, Debug)]
31pub struct Layers<S> {
32    washes: Vec<(Range<usize>, Hsla, S)>,
33    selection: Option<(Range<usize>, Hsla, S)>,
34}
35
36impl<S> Default for Layers<S> {
37    fn default() -> Self {
38        Self {
39            washes: Vec::new(),
40            selection: None,
41        }
42    }
43}
44
45impl<S> Layers<S> {
46    pub fn new() -> Self {
47        Self::default()
48    }
49
50    /// A wash over `range`, painted over the washes before it.
51    pub fn wash(mut self, range: Range<usize>, color: Hsla, shape: S) -> Self {
52        self.washes.push((range, color, shape));
53        self
54    }
55
56    /// The selection, replacing any before it.
57    pub fn select(mut self, range: Range<usize>, color: Hsla, shape: S) -> Self {
58        self.selection = Some((range, color, shape));
59        self
60    }
61
62    /// In the order they are painted: the selection, then the washes in the
63    /// order given.
64    pub fn painted(&self) -> impl Iterator<Item = (&Range<usize>, Hsla, &S)> {
65        self.selection
66            .iter()
67            .chain(&self.washes)
68            .map(|(range, color, shape)| (range, *color, shape))
69    }
70
71    /// What the text over `piece` was coloured for: the washes covering it
72    /// over the opaque `page`.
73    pub fn surface(&self, page: Hsla, piece: &Range<usize>) -> Hsla {
74        theme::stack(
75            page,
76            self.washes
77                .iter()
78                .filter(|(range, ..)| covers(range, piece))
79                .map(|(_, color, _)| *color),
80        )
81    }
82
83    /// Everything [`Self::painted`] covering `piece`, over the opaque `page`,
84    /// or `None` where nothing does.
85    pub fn plate(&self, page: Hsla, piece: &Range<usize>) -> Option<Hsla> {
86        let mut over = self
87            .painted()
88            .filter(|(range, ..)| covers(range, piece))
89            .map(|(_, color, _)| color)
90            .peekable();
91        over.peek()?;
92        Some(theme::stack(page, over))
93    }
94
95    /// These layers cut to `span` of the text, in its offsets.
96    pub fn clipped(&self, span: &Range<usize>) -> Self
97    where
98        S: Clone,
99    {
100        let clip = |(range, color, shape): &(Range<usize>, Hsla, S)| {
101            let (from, to) = (range.start.max(span.start), range.end.min(span.end));
102            (from < to).then(|| (from - span.start..to - span.start, *color, shape.clone()))
103        };
104        Self {
105            washes: self.washes.iter().filter_map(clip).collect(),
106            selection: self.selection.as_ref().and_then(clip),
107        }
108    }
109
110    fn edges(&self) -> impl Iterator<Item = usize> + '_ {
111        self.washes
112            .iter()
113            .map(|(range, ..)| range)
114            .chain(self.selection.as_ref().map(|(range, ..)| range))
115            .flat_map(|range| [range.start, range.end])
116    }
117}
118
119/// The pieces of `runs` to draw again over `layers` on the opaque `page`: each
120/// on its [`Layers::plate`], its ink [`theme::ink_over`] that plate from its
121/// [`Layers::surface`]. Text that reads there already, and text under nothing,
122/// is left out.
123pub fn overdraws<S>(runs: &[TextRun], page: Hsla, layers: &Layers<S>) -> Vec<Overdraw> {
124    let mut out: Vec<Overdraw> = Vec::new();
125    let mut at = 0;
126    for run in runs {
127        let end = at + run.len;
128        let mut cuts: Vec<usize> = layers
129            .edges()
130            .filter(|cut| (at + 1..end).contains(cut))
131            .chain([at, end])
132            .collect();
133        cuts.sort_unstable();
134        cuts.dedup();
135        for piece in cuts.windows(2) {
136            let (from, to) = (piece[0], piece[1]);
137            let Some(plate) = layers.plate(page, &(from..to)) else {
138                continue;
139            };
140            let surface = layers.surface(page, &(from..to));
141            let ink = theme::ink_over(run.color, page, surface, plate);
142            if ink == run.color {
143                continue;
144            }
145            match out.last_mut() {
146                Some(last) if last.range.end == from && last.plate == plate && last.ink == ink => {
147                    last.range.end = to;
148                }
149                _ => out.push(Overdraw {
150                    range: from..to,
151                    plate,
152                    ink,
153                }),
154            }
155        }
156        at = end;
157    }
158    out
159}
160
161fn covers(range: &Range<usize>, piece: &Range<usize>) -> bool {
162    range.start <= piece.start && piece.end <= range.end
163}
164
165/// Draws `pieces` again over what is already painted: each row of a piece
166/// covered in its plate, corners rounded by `radius`, then its glyphs in its
167/// ink. `lines` are a text's lines laid out one under the other from `origin`,
168/// and `pieces` are in the text's offsets.
169pub fn paint<'a>(
170    lines: impl IntoIterator<Item = &'a WrappedLineLayout>,
171    origin: Point<Pixels>,
172    line_height: Pixels,
173    radius: Pixels,
174    pieces: &[Overdraw],
175    window: &mut Window,
176) {
177    if pieces.is_empty() {
178        return;
179    }
180    let (mut start, mut top) = (0, origin);
181    for line in lines {
182        let end = start + line.len();
183        for piece in pieces {
184            let (from, to) = (piece.range.start.max(start), piece.range.end.min(end));
185            if from < to {
186                paint_line(
187                    line,
188                    top,
189                    line_height,
190                    radius,
191                    from - start..to - start,
192                    piece,
193                    window,
194                );
195            }
196        }
197        start = end + 1;
198        top.y += line.size(line_height).height;
199    }
200}
201
202/// `range` of one line of [`paint`], `origin` its top left.
203fn paint_line(
204    line: &WrappedLineLayout,
205    origin: Point<Pixels>,
206    line_height: Pixels,
207    radius: Pixels,
208    range: Range<usize>,
209    piece: &Overdraw,
210    window: &mut Window,
211) {
212    let layout = &line.unwrapped_layout;
213    let starts: Vec<usize> = std::iter::once(0)
214        .chain(
215            line.wrap_boundaries()
216                .iter()
217                .map(|wrap| layout.runs[wrap.run_ix].glyphs[wrap.glyph_ix].index),
218        )
219        .collect();
220    let ends = starts.iter().skip(1).copied().chain([line.len()]);
221    for (row, (start, end)) in starts.iter().copied().zip(ends).enumerate() {
222        let (from, to) = (range.start.max(start), range.end.min(end));
223        if from >= to {
224            continue;
225        }
226        let (left, right) = (layout.x_for_index(from), layout.x_for_index(to));
227        let at = origin + point(left - layout.x_for_index(start), line_height * row as f32);
228        window.paint_quad(quad(
229            Bounds::new(at, size(right - left, line_height)),
230            radius,
231            piece.plate,
232            px(0.),
233            gpui::transparent_black(),
234            BorderStyle::default(),
235        ));
236        caret::paint_covered(layout, &(from..to), at, line_height, piece.ink, window);
237    }
238}