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/// The pieces of `runs` to draw again over `layers`, which are painted in the
27/// order given over the opaque `page`. A piece sits under one set of layers,
28/// its ink [`theme::legible`] on their plate; text that reads there already,
29/// and text under no layer, is left out.
30pub fn overdraws(runs: &[TextRun], page: Hsla, layers: &[(Range<usize>, Hsla)]) -> Vec<Overdraw> {
31    let mut out: Vec<Overdraw> = Vec::new();
32    let mut at = 0;
33    for run in runs {
34        let end = at + run.len;
35        let mut cuts: Vec<usize> = layers
36            .iter()
37            .flat_map(|(range, _)| [range.start, range.end])
38            .filter(|cut| (at + 1..end).contains(cut))
39            .chain([at, end])
40            .collect();
41        cuts.sort_unstable();
42        cuts.dedup();
43        for piece in cuts.windows(2) {
44            let (from, to) = (piece[0], piece[1]);
45            let mut over = layers
46                .iter()
47                .filter(|(range, _)| range.start <= from && to <= range.end)
48                .map(|(_, color)| *color)
49                .peekable();
50            if over.peek().is_none() {
51                continue;
52            }
53            let plate = theme::stack(page, over);
54            let ink = theme::legible(run.color, plate, theme::kept(run.color, page));
55            if ink == run.color {
56                continue;
57            }
58            match out.last_mut() {
59                Some(last) if last.range.end == from && last.plate == plate && last.ink == ink => {
60                    last.range.end = to;
61                }
62                _ => out.push(Overdraw {
63                    range: from..to,
64                    plate,
65                    ink,
66                }),
67            }
68        }
69        at = end;
70    }
71    out
72}
73
74/// Draws `pieces` again over what is already painted: each row of a piece
75/// covered in its plate, corners rounded by `radius`, then its glyphs in its
76/// ink. `lines` are a text's lines laid out one under the other from `origin`,
77/// and `pieces` are in the text's offsets.
78pub fn paint<'a>(
79    lines: impl IntoIterator<Item = &'a WrappedLineLayout>,
80    origin: Point<Pixels>,
81    line_height: Pixels,
82    radius: Pixels,
83    pieces: &[Overdraw],
84    window: &mut Window,
85) {
86    if pieces.is_empty() {
87        return;
88    }
89    let (mut start, mut top) = (0, origin);
90    for line in lines {
91        let end = start + line.len();
92        for piece in pieces {
93            let (from, to) = (piece.range.start.max(start), piece.range.end.min(end));
94            if from < to {
95                paint_line(
96                    line,
97                    top,
98                    line_height,
99                    radius,
100                    from - start..to - start,
101                    piece,
102                    window,
103                );
104            }
105        }
106        start = end + 1;
107        top.y += line.size(line_height).height;
108    }
109}
110
111/// `range` of one line of [`paint`], `origin` its top left.
112fn paint_line(
113    line: &WrappedLineLayout,
114    origin: Point<Pixels>,
115    line_height: Pixels,
116    radius: Pixels,
117    range: Range<usize>,
118    piece: &Overdraw,
119    window: &mut Window,
120) {
121    let layout = &line.unwrapped_layout;
122    let starts: Vec<usize> = std::iter::once(0)
123        .chain(
124            line.wrap_boundaries()
125                .iter()
126                .map(|wrap| layout.runs[wrap.run_ix].glyphs[wrap.glyph_ix].index),
127        )
128        .collect();
129    let ends = starts.iter().skip(1).copied().chain([line.len()]);
130    for (row, (start, end)) in starts.iter().copied().zip(ends).enumerate() {
131        let (from, to) = (range.start.max(start), range.end.min(end));
132        if from >= to {
133            continue;
134        }
135        let (left, right) = (layout.x_for_index(from), layout.x_for_index(to));
136        let at = origin + point(left - layout.x_for_index(start), line_height * row as f32);
137        window.paint_quad(quad(
138            Bounds::new(at, size(right - left, line_height)),
139            radius,
140            piece.plate,
141            px(0.),
142            gpui::transparent_black(),
143            BorderStyle::default(),
144        ));
145        caret::paint_covered(layout, &(from..to), at, line_height, piece.ink, window);
146    }
147}