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