diff options
| author | Josh Kingsley <josh@joshkingsley.me> | 2025-10-29 01:54:37 +0200 |
|---|---|---|
| committer | Josh Kingsley <josh@joshkingsley.me> | 2025-10-29 01:54:37 +0200 |
| commit | 986e65f9ab7122995ae1d647df23d817cecf6816 (patch) | |
| tree | 39d4f77c6a6565b59522a5a77e2f550334472713 /web/src/components/grid/drawSelection.ts | |
| parent | 95069f13d908bfd3c0f3b33f8fad7d8464fd192e (diff) | |
refactor(web): improve state management
Diffstat (limited to 'web/src/components/grid/drawSelection.ts')
| -rw-r--r-- | web/src/components/grid/drawSelection.ts | 97 |
1 files changed, 97 insertions, 0 deletions
diff --git a/web/src/components/grid/drawSelection.ts b/web/src/components/grid/drawSelection.ts new file mode 100644 index 0000000..e1024a8 --- /dev/null +++ b/web/src/components/grid/drawSelection.ts @@ -0,0 +1,97 @@ +import { RangeSelection, Selection } from "../../selection"; +import { CellRef } from "../../types"; +import excursion from "./excursion"; +import { getRenderedCell, RenderedCell, RenderedGrid } from "./renderGrid"; + +export interface SelectionStyles { + activeCellStroke: string; + selectionRangeFill: string; + selectionRangeStroke: string; +} + +function strokeActiveCell( + ctx: CanvasRenderingContext2D, + styles: SelectionStyles, + grid: RenderedGrid, + cell: RenderedCell, +) { + excursion(ctx, () => { + const isLastCell = cell.rect.bottomRight.x === grid.rect.bottomRight.x; + const isLastRow = cell.rect.bottomRight.y === grid.rect.bottomRight.y; + + ctx.strokeStyle = styles.activeCellStroke; + ctx.lineWidth = 2; + + ctx.strokeRect( + cell.rect.topLeft.x + 1, + cell.rect.topLeft.y + 1, + isLastCell ? cell.rect.width - 2 : cell.rect.width - 1, + isLastRow ? cell.rect.height - 2 : cell.rect.height - 1, + ); + }); +} + +function drawCellRange( + ctx: CanvasRenderingContext2D, + styles: SelectionStyles, + grid: RenderedGrid, + start: CellRef, + end: CellRef, + { stroke }: { stroke: boolean }, +) { + excursion(ctx, () => { + const startCell = getRenderedCell(grid, start); + const endCell = getRenderedCell(grid, end); + + if (!startCell || !endCell) return; + + const rect = startCell.rect.extend(endCell.rect); + + const isRightEdge = rect.bottomRight.x === grid.rect.bottomRight.x; + const isBottomEdge = rect.bottomRight.y === grid.rect.bottomRight.y; + + ctx.fillStyle = styles.selectionRangeFill; + + ctx.fillRect( + rect.topLeft.x + 1, + rect.topLeft.y + 1, + isRightEdge ? rect.width - 2 : rect.width - 1, + isBottomEdge ? rect.height - 2 : rect.height - 1, + ); + + if (!stroke) return; + + ctx.strokeStyle = styles.selectionRangeStroke; + + ctx.strokeRect( + rect.topLeft.x + 0.5, + rect.topLeft.y + 0.5, + isRightEdge ? rect.width - 1 : rect.width, + isBottomEdge ? rect.height - 1 : rect.height, + ); + }); +} + +export default function drawSelection( + ctx: CanvasRenderingContext2D, + styles: SelectionStyles, + grid: RenderedGrid, + selection: Selection | undefined, + { pending }: { pending: boolean }, +) { + ctx.clearRect(0, 0, grid.rect.width, grid.rect.height); + + if (!selection) return; + + const activeCell = getRenderedCell(grid, selection.activeCellRef); + + if (!activeCell) return; + + if (selection instanceof RangeSelection) { + drawCellRange(ctx, styles, grid, selection.range[0], selection.range[1], { + stroke: !pending, + }); + } + + strokeActiveCell(ctx, styles, grid, activeCell); +} |
