| 1 | /** |
| 2 | * Drawing a long log a screenful at a time. A step's log past a few |
| 3 | * thousand rows draws only the rows on screen and a few around them, with |
| 4 | * space above and below standing in for the rest, so a 50,000-line log |
| 5 | * opens, grows and scrolls as fast as a short one. Rows are one line high |
| 6 | * until they are drawn and measured (a long line wraps), and the space |
| 7 | * kept for them is corrected then. |
| 8 | */ |
| 9 | |
| 10 | /** Past this many rows, a step's log draws only what is on screen. */ |
| 11 | export const WINDOW_FROM = 2000; |
| 12 | /** A row's height before it is measured: one line of `text-xs leading-5`. */ |
| 13 | export const ROW_HEIGHT = 20; |
| 14 | /** How far past the screen rows are drawn, above and below, in pixels. */ |
| 15 | export const OVERSCAN = 800; |
| 16 | |
| 17 | /** |
| 18 | * Where each row starts, from the top of the log: `starts[i]` for row `i`, |
| 19 | * and `starts[rows]` for the log's whole height. `height(i)` is a row's |
| 20 | * measured height, or undefined for the estimate. |
| 21 | */ |
| 22 | export function rowStarts(rows: number, height: (index: number) => number | undefined): Float64Array { |
| 23 | const starts = new Float64Array(rows + 1); |
| 24 | for (let i = 0; i < rows; i++) starts[i + 1] = starts[i]! + (height(i) ?? ROW_HEIGHT); |
| 25 | return starts; |
| 26 | } |
| 27 | |
| 28 | /** The first row that ends below `y`: the row at `y`, or the last. */ |
| 29 | export function rowAt(starts: Float64Array, y: number): number { |
| 30 | const rows = starts.length - 1; |
| 31 | let low = 0; |
| 32 | let high = rows; |
| 33 | while (low < high) { |
| 34 | const middle = (low + high) >> 1; |
| 35 | if (starts[middle + 1]! <= y) low = middle + 1; |
| 36 | else high = middle; |
| 37 | } |
| 38 | return Math.min(low, Math.max(0, rows - 1)); |
| 39 | } |
| 40 | |
| 41 | /** |
| 42 | * The rows to draw, `[from, to)`, for a view from `top` to `bottom` |
| 43 | * (pixels from the top of the log; either may be past its ends), with |
| 44 | * `overscan` pixels more either side. |
| 45 | */ |
| 46 | export function windowOf(starts: Float64Array, top: number, bottom: number, overscan = OVERSCAN): [number, number] { |
| 47 | const rows = starts.length - 1; |
| 48 | if (rows === 0) return [0, 0]; |
| 49 | const total = starts[rows]!; |
| 50 | const from = top - overscan; |
| 51 | const to = bottom + overscan; |
| 52 | if (to <= 0 || from >= total) { |
| 53 | // Off screen: draw a few rows at the end nearest the view, so the |
| 54 | // log has something to measure and its end can be followed. |
| 55 | return from >= total ? [Math.max(0, rows - 20), rows] : [0, Math.min(rows, 20)]; |
| 56 | } |
| 57 | return [rowAt(starts, Math.max(0, from)), rowAt(starts, to) + 1]; |
| 58 | } |