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    /// A table cell's box, padding included.
32    cells: Vec<(usize, Part, Bounds<Pixels>)>,
33    /// Kept across frames: [`Self::clear`] leaves it.
34    heights: Heights,
35    /// Kept across frames until the column has shown it.
36    reveal: Option<Reveal>,
37}
38
39/// A range waiting to be scrolled into view.
40#[derive(Clone, Copy)]
41pub(super) struct Reveal {
42    pub(super) range: Selection,
43    /// Frames it has scrolled in.
44    pub(super) tries: u8,
45    /// Where the column item holding it was placed last frame, in window
46    /// coordinates. `None` before it has been built.
47    pub(super) top: Option<Pixels>,
48}
49
50/// Block heights kept across frames, so a block off-screen is placed without
51/// being built.
52#[derive(Default)]
53pub(super) struct Heights {
54    /// A block's height by [`block_key`], as last measured at whatever width
55    /// the column had then.
56    by_key: HashMap<u64, Pixels>,
57    /// The height last measured at each column index, whatever block was
58    /// there.
59    by_index: Vec<Option<Pixels>>,
60}
61
62/// One shaped run and the slice of its part it covers.
63///
64/// A paragraph is one entry over all of its text; a code block is one entry per
65/// line. The range is what lets both resolve a click the same way — the layout
66/// answers in its own coordinates and the base puts the answer back into the
67/// part's.
68pub(super) struct Painted {
69    block: usize,
70    part: Part,
71    range: Range<usize>,
72    layout: TextLayout,
73    shown: Shown,
74}
75
76pub(super) struct PaintedRow {
77    painted: usize,
78    block: usize,
79    part: Part,
80    range: Range<usize>,
81    bounds: Bounds<Pixels>,
82    /// Where its line starts in the painted text's [`Shown`] offsets.
83    line_start: usize,
84    wrapped_row: usize,
85}
86
87impl BlockLayouts {
88    /// The position under `point`.
89    ///
90    /// Falls back to the nearest text vertically, so clicking the margin
91    /// beside a line — or below the last one — still lands somewhere useful
92    /// rather than doing nothing.
93    pub fn hit(&self, point: Point<Pixels>) -> Option<Cursor> {
94        let frames = self.0.borrow();
95        if let Some(row) = frames.rows.iter().find(|row| row.bounds.contains(&point)) {
96            return cursor_in_row(&frames, row, point.x);
97        }
98        frames
99            .rows
100            .iter()
101            .min_by_key(|row| {
102                let bounds = row.bounds;
103                let above = (bounds.origin.y - point.y).abs();
104                let below = (bounds.origin.y + bounds.size.height - point.y).abs();
105                f32::from(above.min(below)) as i64
106            })
107            .and_then(|row| cursor_in_row(&frames, row, point.x))
108    }
109
110    /// Where a position painted last frame, and how tall its line is.
111    ///
112    /// Vertical motion is geometry rather than arithmetic on line numbers, so
113    /// a wrapped row and a hard newline are the same case and neither needs
114    /// counting — the rule `ui::TextField` arrived at.
115    pub fn position(&self, at: Cursor) -> Option<(Point<Pixels>, Pixels)> {
116        let frames = self.0.borrow();
117        let painted = frames.texts.iter().find(|painted| {
118            painted.block == at.block
119                && painted.part == at.part
120                && painted.range.start <= at.offset
121                && at.offset <= painted.range.end
122        })?;
123        let point = painted
124            .layout
125            .position_for_index(painted.shown.at(at.offset - painted.range.start))?;
126        Some((point, painted.layout.line_height()))
127    }
128
129    /// The painted rows of a range, in document order — what a bar centred over
130    /// a selection is placed against, and what a highlight of a caller's own is
131    /// drawn from.
132    ///
133    /// One rect per visual row rather than one box: a selection that wraps or
134    /// crosses blocks has no single box, and a caller given one would paint
135    /// over the gaps.
136    pub fn rects(&self, selection: Selection) -> Vec<Bounds<Pixels>> {
137        let (start, end) = selection.ordered();
138        self.0
139            .borrow()
140            .texts
141            .iter()
142            .filter_map(|painted| {
143                let here = Cursor::new(painted.block, painted.part, 0);
144                let (from, to) = (
145                    Cursor::new(start.block, start.part, 0),
146                    Cursor::new(end.block, end.part, 0),
147                );
148                if here < from || here > to {
149                    return None;
150                }
151                // The offsets only matter at the two ends: in between, the
152                // whole of the text is covered.
153                let len = painted.range.len();
154                let first = if here == from { start.offset } else { 0 };
155                let last = if here == to { end.offset } else { usize::MAX };
156                let range = first.saturating_sub(painted.range.start).min(len)
157                    ..last.saturating_sub(painted.range.start).min(len);
158                let range = painted.shown.range(&range);
159                (range.start < range.end).then(|| range_rects(&painted.layout, &range, 0.0, 0.0))
160            })
161            .flatten()
162            .collect()
163    }
164
165    /// The position one painted row above or below `at`, and the row it landed
166    /// on. Walks the recorded runs in paint order — which is document order.
167    ///
168    /// Two things make this refuse to be a hit test. The gap between blocks
169    /// belongs to no run, so a probe there answers with whichever run is
170    /// nearest — and at a boundary that is the block being *left*, whose bottom
171    /// edge is zero pixels away while the next block's top is a whole gap. And
172    /// `from` is passed in rather than derived from `at`, because an offset at
173    /// a soft wrap belongs to two rows and `position_for_index` always answers
174    /// with the first: derive it and every step down recomputes the same row.
175    pub fn step_row(
176        &self,
177        at: Cursor,
178        from: Point<Pixels>,
179        down: bool,
180    ) -> Option<(Cursor, Pixels)> {
181        let frames = self.0.borrow();
182        let ix = frames
183            .rows
184            .iter()
185            .position(|row| {
186                row.block == at.block
187                    && row.part == at.part
188                    && row_contains(row, at.offset)
189                    && row.bounds.origin.y <= from.y
190                    && from.y < row.bounds.origin.y + row.bounds.size.height
191            })
192            .or_else(|| {
193                frames.rows.iter().position(|row| {
194                    row.block == at.block && row.part == at.part && row_contains(row, at.offset)
195                })
196            })?;
197        let next = match down {
198            true => frames.rows.get(ix + 1)?,
199            false => frames.rows.get(ix.checked_sub(1)?)?,
200        };
201        Some((cursor_in_row(&frames, next, from.x)?, next.bounds.origin.y))
202    }
203
204    /// Whether `point` is inside painted text.
205    ///
206    /// [`Self::hit`] answers with the nearest run wherever it is asked, which
207    /// is what a click wants and what a *pointer* must not have: an I-beam
208    /// belongs where a caret would land, not everywhere an editor's box
209    /// reaches.
210    pub fn over_text(&self, point: Point<Pixels>) -> bool {
211        self.0
212            .borrow()
213            .texts
214            .iter()
215            .any(|painted| painted.layout.bounds().contains(&point))
216    }
217
218    /// The block under `point`, for a gutter handle and a drop target.
219    pub fn block_at(&self, point: Point<Pixels>) -> Option<usize> {
220        let blocks = &self.0.borrow().blocks;
221        blocks
222            .iter()
223            .find(|(_, bounds)| bounds.contains(&point))
224            .or_else(|| {
225                blocks.iter().min_by_key(|(_, bounds)| {
226                    let above = (bounds.origin.y - point.y).abs();
227                    let below = (bounds.origin.y + bounds.size.height - point.y).abs();
228                    f32::from(above.min(below)) as i64
229                })
230            })
231            .map(|(ix, _)| *ix)
232    }
233
234    /// Where a block's first painted row sits, and how tall that row is — what
235    /// a mark in the gutter has to line up with.
236    ///
237    /// [`Self::block_bounds`] is not that: it spans every row the block holds,
238    /// and a heading's single row is taller than a paragraph's, so anything
239    /// placed from the top of the box rides above the text it points at.
240    /// `None` for a block that paints no text at all, a rule being the one
241    /// that does.
242    pub fn first_row(&self, ix: usize) -> Option<(Pixels, Pixels)> {
243        let texts = &self.0.borrow().texts;
244        let painted = texts.iter().find(|painted| painted.block == ix)?;
245        Some((
246            painted.layout.bounds().origin.y,
247            painted.layout.line_height(),
248        ))
249    }
250
251    /// Where a block painted last frame, in window coordinates.
252    pub fn block_bounds(&self, ix: usize) -> Option<Bounds<Pixels>> {
253        self.0
254            .borrow()
255            .blocks
256            .iter()
257            .find(|(block, _)| *block == ix)
258            .map(|(_, bounds)| *bounds)
259    }
260
261    /// Where a fenced block's language label painted — the box a host hangs its
262    /// picker on. Recorded rather than derived: the word's width is the text
263    /// system's answer, and padding arithmetic would be wrong the first time
264    /// any of it changed.
265    pub fn language_bounds(&self, ix: usize) -> Option<Bounds<Pixels>> {
266        self.0
267            .borrow()
268            .languages
269            .iter()
270            .find(|(block, _)| *block == ix)
271            .map(|(_, bounds)| *bounds)
272    }
273
274    /// Where an image block's picture painted, which
275    /// [`BlockLayouts::block_bounds`] does not give: that box spans the column
276    /// and takes in the caption, so a handle placed from it sits off the edge
277    /// of any picture narrower than the page.
278    pub fn picture_bounds(&self, ix: usize) -> Option<Bounds<Pixels>> {
279        self.0
280            .borrow()
281            .pictures
282            .iter()
283            .find(|(block, _)| *block == ix)
284            .map(|(_, bounds)| *bounds)
285    }
286
287    /// Where a task block's checkbox painted, which is the box itself and not
288    /// the marker column it sits in — a press outside it is a press on the
289    /// gutter, and belongs to whatever handles one.
290    pub fn checkbox_bounds(&self, ix: usize) -> Option<Bounds<Pixels>> {
291        self.0
292            .borrow()
293            .checkboxes
294            .iter()
295            .find(|(block, _)| *block == ix)
296            .map(|(_, bounds)| *bounds)
297    }
298
299    /// Where a table cell painted, padding included.
300    pub fn cell_bounds(&self, ix: usize, part: Part) -> Option<Bounds<Pixels>> {
301        self.0
302            .borrow()
303            .cells
304            .iter()
305            .find(|(block, at, _)| *block == ix && *at == part)
306            .map(|(_, _, bounds)| *bounds)
307    }
308
309    /// The table cell under `point`: its block and part.
310    pub fn cell_at(&self, point: Point<Pixels>) -> Option<(usize, Part)> {
311        self.0
312            .borrow()
313            .cells
314            .iter()
315            .find(|(_, _, bounds)| bounds.contains(&point))
316            .map(|(block, part, _)| (*block, *part))
317    }
318
319    /// Scrolls the document until the start of `range` shows, centring it
320    /// when it does not.
321    ///
322    /// Answered over the next few frames by a document rendered with these
323    /// layouts and an [`Editing::scroll`]; without one the request is dropped
324    /// at the next frame. A later call replaces an earlier one.
325    pub fn reveal(&self, range: Selection) {
326        self.0.borrow_mut().reveal = Some(Reveal {
327            range,
328            tries: 0,
329            top: None,
330        });
331    }
332
333    pub(super) fn revealing(&self) -> Option<Reveal> {
334        self.0.borrow().reveal
335    }
336
337    pub(super) fn set_revealing(&self, reveal: Option<Reveal>) {
338        self.0.borrow_mut().reveal = reveal;
339    }
340
341    pub(super) fn record(
342        &self,
343        block: usize,
344        part: Part,
345        range: Range<usize>,
346        layout: TextLayout,
347        shown: Shown,
348    ) {
349        let mut frames = self.0.borrow_mut();
350        let painted = frames.texts.len();
351        record_rows(
352            &mut frames.rows,
353            painted,
354            block,
355            part,
356            &range,
357            &layout,
358            &shown,
359        );
360        frames.texts.push(Painted {
361            block,
362            part,
363            range,
364            layout,
365            shown,
366        });
367    }
368
369    pub(super) fn record_block(&self, ix: usize, bounds: Bounds<Pixels>) {
370        self.0.borrow_mut().blocks.push((ix, bounds));
371    }
372
373    pub(super) fn record_language(&self, ix: usize, bounds: Bounds<Pixels>) {
374        self.0.borrow_mut().languages.push((ix, bounds));
375    }
376
377    pub(super) fn record_picture(&self, ix: usize, bounds: Bounds<Pixels>) {
378        self.0.borrow_mut().pictures.push((ix, bounds));
379    }
380
381    pub(super) fn record_checkbox(&self, ix: usize, bounds: Bounds<Pixels>) {
382        self.0.borrow_mut().checkboxes.push((ix, bounds));
383    }
384
385    pub(super) fn record_cell(&self, ix: usize, part: Part, bounds: Bounds<Pixels>) {
386        self.0.borrow_mut().cells.push((ix, part, bounds));
387    }
388
389    /// The height last measured for `key`, or else for whatever block was
390    /// last at `ix`.
391    pub(super) fn height(&self, ix: usize, key: u64) -> Option<Pixels> {
392        let heights = &self.0.borrow().heights;
393        heights
394            .by_key
395            .get(&key)
396            .copied()
397            .or_else(|| heights.by_index.get(ix).copied().flatten())
398    }
399
400    pub(super) fn record_height(&self, ix: usize, key: u64, height: Pixels) {
401        let heights = &mut self.0.borrow_mut().heights;
402        heights.by_key.insert(key, height);
403        if heights.by_index.len() <= ix {
404            heights.by_index.resize(ix + 1, None);
405        }
406        heights.by_index[ix] = Some(height);
407    }
408
409    /// Drops the heights of blocks no longer in the document once they
410    /// outnumber the ones that are.
411    pub(super) fn prune(&self, keys: &[u64]) {
412        let heights = &mut self.0.borrow_mut().heights;
413        heights.by_index.truncate(keys.len());
414        if heights.by_key.len() > 2 * keys.len() {
415            let keep: std::collections::HashSet<u64> = keys.iter().copied().collect();
416            heights.by_key.retain(|key, _| keep.contains(key));
417        }
418    }
419
420    pub(super) fn clear(&self) {
421        let mut frames = self.0.borrow_mut();
422        frames.texts.clear();
423        frames.rows.clear();
424        frames.blocks.clear();
425        frames.languages.clear();
426        frames.pictures.clear();
427        frames.checkboxes.clear();
428        frames.cells.clear();
429    }
430}
431
432pub(super) fn row_contains(row: &PaintedRow, offset: usize) -> bool {
433    row.range.start <= offset && offset <= row.range.end
434}
435
436pub(super) fn cursor_in_row(frames: &Frames, row: &PaintedRow, x: Pixels) -> Option<Cursor> {
437    let painted = &frames.texts[row.painted];
438    let line = painted.layout.line_layout_for_index(row.line_start)?;
439    let height = row.bounds.size.height;
440    let local = point(
441        x - row.bounds.origin.x,
442        height * (row.wrapped_row as f32 + 0.5),
443    );
444    let (Ok(offset) | Err(offset)) = line.closest_index_for_position(local, height);
445    let offset = painted.range.start + painted.shown.offset(row.line_start + offset);
446    Some(Cursor::new(row.block, row.part, offset.min(row.range.end)))
447}
448
449pub(super) fn record_rows(
450    rows: &mut Vec<PaintedRow>,
451    painted: usize,
452    block: usize,
453    part: Part,
454    range: &Range<usize>,
455    layout: &TextLayout,
456    shown: &Shown,
457) {
458    let line_height = layout.line_height();
459    let bounds = layout.bounds();
460    let mut origin = bounds.origin;
461    let mut line_start = 0;
462    for line in layout.line_layouts() {
463        let shaped = &line.unwrapped_layout;
464        let row_ends = line
465            .wrap_boundaries()
466            .iter()
467            .map(|wrap| shaped.runs[wrap.run_ix].glyphs[wrap.glyph_ix].index)
468            .chain([line.len()]);
469        let mut row_start = 0;
470        for (row, row_end) in row_ends.enumerate() {
471            rows.push(PaintedRow {
472                painted,
473                block,
474                part,
475                range: range.start + shown.offset(line_start + row_start)
476                    ..range.start + shown.offset(line_start + row_end),
477                bounds: Bounds::new(
478                    origin + point(px(0.0), line_height * row as f32),
479                    size(bounds.size.width, line_height),
480                ),
481                line_start,
482                wrapped_row: row,
483            });
484            row_start = row_end;
485        }
486        origin.y += line.size(line_height).height;
487        line_start += line.len() + 1;
488    }
489}