Skip to main content

markdown/render/
layouts.rs

1//! Hit-testing and caret geometry over the rows a frame painted.
2
3use super::*;
4
5/// Where each block's text landed, recorded as it painted.
6///
7/// A caret has to be placeable by pointer, and only paint knows where a glyph
8/// ended up. An editor hands one of these in, the renderer fills it, and the
9/// next click resolves against it. Read-only callers pass nothing and pay
10/// nothing.
11#[derive(Clone, Default)]
12pub struct BlockLayouts(Rc<RefCell<Frames>>);
13
14#[derive(Default)]
15pub(super) struct Frames {
16    texts: Vec<Painted>,
17    rows: Vec<PaintedRow>,
18    /// Each block's whole box, which a text layout does not give: a rule and
19    /// an image hold no text at all, and a gutter handle still has to find them.
20    blocks: Vec<(usize, Bounds<Pixels>)>,
21    /// A fenced block's language label, which a host may want to hang a
22    /// picker on.
23    languages: Vec<(usize, Bounds<Pixels>)>,
24    /// An image block's picture, which is not its block: the block runs the
25    /// full column and carries the caption, and a resize handle belongs on the
26    /// edge of the picture itself.
27    pictures: Vec<(usize, Bounds<Pixels>)>,
28    /// A task block's checkbox, which is not its marker column: the column is
29    /// gutter either side of the box, and a click there places a caret.
30    checkboxes: Vec<(usize, Bounds<Pixels>)>,
31}
32
33/// One shaped run and the slice of its part it covers.
34///
35/// A paragraph is one entry over all of its text; a code block is one entry per
36/// line. The range is what lets both resolve a click the same way — the layout
37/// answers in its own coordinates and the base puts the answer back into the
38/// part's.
39pub(super) struct Painted {
40    block: usize,
41    part: Part,
42    range: Range<usize>,
43    layout: TextLayout,
44}
45
46pub(super) struct PaintedRow {
47    painted: usize,
48    block: usize,
49    part: Part,
50    range: Range<usize>,
51    bounds: Bounds<Pixels>,
52    line_start: usize,
53    wrapped_row: usize,
54}
55
56impl BlockLayouts {
57    /// The position under `point`.
58    ///
59    /// Falls back to the nearest text vertically, so clicking the margin
60    /// beside a line — or below the last one — still lands somewhere useful
61    /// rather than doing nothing.
62    pub fn hit(&self, point: Point<Pixels>) -> Option<Cursor> {
63        let frames = self.0.borrow();
64        if let Some(row) = frames.rows.iter().find(|row| row.bounds.contains(&point)) {
65            return cursor_in_row(&frames, row, point.x);
66        }
67        frames
68            .rows
69            .iter()
70            .min_by_key(|row| {
71                let bounds = row.bounds;
72                let above = (bounds.origin.y - point.y).abs();
73                let below = (bounds.origin.y + bounds.size.height - point.y).abs();
74                f32::from(above.min(below)) as i64
75            })
76            .and_then(|row| cursor_in_row(&frames, row, point.x))
77    }
78
79    /// Where a position painted last frame, and how tall its line is.
80    ///
81    /// Vertical motion is geometry rather than arithmetic on line numbers, so
82    /// a wrapped row and a hard newline are the same case and neither needs
83    /// counting — the rule `ui::TextField` arrived at.
84    pub fn position(&self, at: Cursor) -> Option<(Point<Pixels>, Pixels)> {
85        let frames = self.0.borrow();
86        let painted = frames.texts.iter().find(|painted| {
87            painted.block == at.block
88                && painted.part == at.part
89                && painted.range.start <= at.offset
90                && at.offset <= painted.range.end
91        })?;
92        let point = painted
93            .layout
94            .position_for_index(at.offset - painted.range.start)?;
95        Some((point, painted.layout.line_height()))
96    }
97
98    /// The painted rows of a range, in document order — what a bar centred over
99    /// a selection is placed against, and what a highlight of a caller's own is
100    /// drawn from.
101    ///
102    /// One rect per visual row rather than one box: a selection that wraps or
103    /// crosses blocks has no single box, and a caller given one would paint
104    /// over the gaps.
105    pub fn rects(&self, selection: Selection) -> Vec<Bounds<Pixels>> {
106        let (start, end) = selection.ordered();
107        self.0
108            .borrow()
109            .texts
110            .iter()
111            .filter_map(|painted| {
112                let here = Cursor::new(painted.block, painted.part, 0);
113                let (from, to) = (
114                    Cursor::new(start.block, start.part, 0),
115                    Cursor::new(end.block, end.part, 0),
116                );
117                if here < from || here > to {
118                    return None;
119                }
120                // The offsets only matter at the two ends: in between, the
121                // whole of the text is covered.
122                let len = painted.range.len();
123                let first = if here == from { start.offset } else { 0 };
124                let last = if here == to { end.offset } else { usize::MAX };
125                let range = first.saturating_sub(painted.range.start).min(len)
126                    ..last.saturating_sub(painted.range.start).min(len);
127                (range.start < range.end).then(|| range_rects(&painted.layout, &range, 0.0, 0.0))
128            })
129            .flatten()
130            .collect()
131    }
132
133    /// The position one painted row above or below `at`, and the row it landed
134    /// on. Walks the recorded runs in paint order — which is document order.
135    ///
136    /// Two things make this refuse to be a hit test. The gap between blocks
137    /// belongs to no run, so a probe there answers with whichever run is
138    /// nearest — and at a boundary that is the block being *left*, whose bottom
139    /// edge is zero pixels away while the next block's top is a whole gap. And
140    /// `from` is passed in rather than derived from `at`, because an offset at
141    /// a soft wrap belongs to two rows and `position_for_index` always answers
142    /// with the first: derive it and every step down recomputes the same row.
143    pub fn step_row(
144        &self,
145        at: Cursor,
146        from: Point<Pixels>,
147        down: bool,
148    ) -> Option<(Cursor, Pixels)> {
149        let frames = self.0.borrow();
150        let ix = frames
151            .rows
152            .iter()
153            .position(|row| {
154                row.block == at.block
155                    && row.part == at.part
156                    && row_contains(row, at.offset)
157                    && row.bounds.origin.y <= from.y
158                    && from.y < row.bounds.origin.y + row.bounds.size.height
159            })
160            .or_else(|| {
161                frames.rows.iter().position(|row| {
162                    row.block == at.block && row.part == at.part && row_contains(row, at.offset)
163                })
164            })?;
165        let next = match down {
166            true => frames.rows.get(ix + 1)?,
167            false => frames.rows.get(ix.checked_sub(1)?)?,
168        };
169        Some((cursor_in_row(&frames, next, from.x)?, next.bounds.origin.y))
170    }
171
172    /// Whether `point` is inside painted text.
173    ///
174    /// [`Self::hit`] answers with the nearest run wherever it is asked, which
175    /// is what a click wants and what a *pointer* must not have: an I-beam
176    /// belongs where a caret would land, not everywhere an editor's box
177    /// reaches.
178    pub fn over_text(&self, point: Point<Pixels>) -> bool {
179        self.0
180            .borrow()
181            .texts
182            .iter()
183            .any(|painted| painted.layout.bounds().contains(&point))
184    }
185
186    /// The block under `point`, for a gutter handle and a drop target.
187    pub fn block_at(&self, point: Point<Pixels>) -> Option<usize> {
188        let blocks = &self.0.borrow().blocks;
189        blocks
190            .iter()
191            .find(|(_, bounds)| bounds.contains(&point))
192            .or_else(|| {
193                blocks.iter().min_by_key(|(_, bounds)| {
194                    let above = (bounds.origin.y - point.y).abs();
195                    let below = (bounds.origin.y + bounds.size.height - point.y).abs();
196                    f32::from(above.min(below)) as i64
197                })
198            })
199            .map(|(ix, _)| *ix)
200    }
201
202    /// Where a block's first painted row sits, and how tall that row is — what
203    /// a mark in the gutter has to line up with.
204    ///
205    /// [`Self::block_bounds`] is not that: it spans every row the block holds,
206    /// and a heading's single row is taller than a paragraph's, so anything
207    /// placed from the top of the box rides above the text it points at.
208    /// `None` for a block that paints no text at all, a rule being the one
209    /// that does.
210    pub fn first_row(&self, ix: usize) -> Option<(Pixels, Pixels)> {
211        let texts = &self.0.borrow().texts;
212        let painted = texts.iter().find(|painted| painted.block == ix)?;
213        Some((
214            painted.layout.bounds().origin.y,
215            painted.layout.line_height(),
216        ))
217    }
218
219    /// Where a block painted last frame, in window coordinates.
220    pub fn block_bounds(&self, ix: usize) -> Option<Bounds<Pixels>> {
221        self.0
222            .borrow()
223            .blocks
224            .iter()
225            .find(|(block, _)| *block == ix)
226            .map(|(_, bounds)| *bounds)
227    }
228
229    /// Where a fenced block's language label painted — the box a host hangs its
230    /// picker on. Recorded rather than derived: the word's width is the text
231    /// system's answer, and padding arithmetic would be wrong the first time
232    /// any of it changed.
233    pub fn language_bounds(&self, ix: usize) -> Option<Bounds<Pixels>> {
234        self.0
235            .borrow()
236            .languages
237            .iter()
238            .find(|(block, _)| *block == ix)
239            .map(|(_, bounds)| *bounds)
240    }
241
242    /// Where an image block's picture painted, which
243    /// [`BlockLayouts::block_bounds`] does not give: that box spans the column
244    /// and takes in the caption, so a handle placed from it sits off the edge
245    /// of any picture narrower than the page.
246    pub fn picture_bounds(&self, ix: usize) -> Option<Bounds<Pixels>> {
247        self.0
248            .borrow()
249            .pictures
250            .iter()
251            .find(|(block, _)| *block == ix)
252            .map(|(_, bounds)| *bounds)
253    }
254
255    /// Where a task block's checkbox painted, which is the box itself and not
256    /// the marker column it sits in — a press outside it is a press on the
257    /// gutter, and belongs to whatever handles one.
258    pub fn checkbox_bounds(&self, ix: usize) -> Option<Bounds<Pixels>> {
259        self.0
260            .borrow()
261            .checkboxes
262            .iter()
263            .find(|(block, _)| *block == ix)
264            .map(|(_, bounds)| *bounds)
265    }
266
267    pub(super) fn record(&self, block: usize, part: Part, range: Range<usize>, layout: TextLayout) {
268        let mut frames = self.0.borrow_mut();
269        let painted = frames.texts.len();
270        record_rows(&mut frames.rows, painted, block, part, &range, &layout);
271        frames.texts.push(Painted {
272            block,
273            part,
274            range,
275            layout,
276        });
277    }
278
279    pub(super) fn record_block(&self, ix: usize, bounds: Bounds<Pixels>) {
280        self.0.borrow_mut().blocks.push((ix, bounds));
281    }
282
283    pub(super) fn record_language(&self, ix: usize, bounds: Bounds<Pixels>) {
284        self.0.borrow_mut().languages.push((ix, bounds));
285    }
286
287    pub(super) fn record_picture(&self, ix: usize, bounds: Bounds<Pixels>) {
288        self.0.borrow_mut().pictures.push((ix, bounds));
289    }
290
291    pub(super) fn record_checkbox(&self, ix: usize, bounds: Bounds<Pixels>) {
292        self.0.borrow_mut().checkboxes.push((ix, bounds));
293    }
294
295    pub(super) fn clear(&self) {
296        let mut frames = self.0.borrow_mut();
297        frames.texts.clear();
298        frames.rows.clear();
299        frames.blocks.clear();
300        frames.languages.clear();
301        frames.pictures.clear();
302        frames.checkboxes.clear();
303    }
304}
305
306pub(super) fn row_contains(row: &PaintedRow, offset: usize) -> bool {
307    row.range.start <= offset && offset <= row.range.end
308}
309
310pub(super) fn cursor_in_row(frames: &Frames, row: &PaintedRow, x: Pixels) -> Option<Cursor> {
311    let painted = &frames.texts[row.painted];
312    let line = painted
313        .layout
314        .line_layout_for_index(row.line_start - painted.range.start)?;
315    let height = row.bounds.size.height;
316    let local = point(
317        x - row.bounds.origin.x,
318        height * (row.wrapped_row as f32 + 0.5),
319    );
320    let (Ok(offset) | Err(offset)) = line.closest_index_for_position(local, height);
321    Some(Cursor::new(
322        row.block,
323        row.part,
324        (row.line_start + offset).min(row.range.end),
325    ))
326}
327
328pub(super) fn record_rows(
329    rows: &mut Vec<PaintedRow>,
330    painted: usize,
331    block: usize,
332    part: Part,
333    range: &Range<usize>,
334    layout: &TextLayout,
335) {
336    let line_height = layout.line_height();
337    let bounds = layout.bounds();
338    let mut origin = bounds.origin;
339    let mut line_start = range.start;
340    for line in layout.line_layouts() {
341        let shaped = &line.unwrapped_layout;
342        let row_ends = line
343            .wrap_boundaries()
344            .iter()
345            .map(|wrap| shaped.runs[wrap.run_ix].glyphs[wrap.glyph_ix].index)
346            .chain([line.len()]);
347        let mut row_start = 0;
348        for (row, row_end) in row_ends.enumerate() {
349            rows.push(PaintedRow {
350                painted,
351                block,
352                part,
353                range: line_start + row_start..line_start + row_end,
354                bounds: Bounds::new(
355                    origin + point(px(0.0), line_height * row as f32),
356                    size(bounds.size.width, line_height),
357                ),
358                line_start,
359                wrapped_row: row,
360            });
361            row_start = row_end;
362        }
363        origin.y += line.size(line_height).height;
364        line_start += line.len() + 1;
365    }
366}