139 lines
3.8 KiB
TypeScript
139 lines
3.8 KiB
TypeScript
import Spreadsheet, { CSS_PREFIX, RenderBox } from "../main";
|
|
import { Position } from "../modules/cell";
|
|
|
|
/**
|
|
* Display (CANVAS) element where cells render
|
|
*/
|
|
export class Sheet {
|
|
element: HTMLCanvasElement;
|
|
ctx: CanvasRenderingContext2D;
|
|
root: Spreadsheet;
|
|
constructor(root: Spreadsheet) {
|
|
this.root = root;
|
|
const canvas = document.createElement("canvas");
|
|
canvas.classList.add(CSS_PREFIX + "sheet");
|
|
|
|
//* Set up canvas sizes based on provided root config
|
|
canvas.height = this.root.config.view.height;
|
|
canvas.width = this.root.config.view.width;
|
|
canvas.style.width = this.root.config.view.width + "px";
|
|
canvas.style.height = this.root.config.view.height + "px";
|
|
canvas.style.left = "0px";
|
|
|
|
this.element = canvas;
|
|
|
|
const ctx = this.element.getContext("2d");
|
|
if (!ctx) throw new Error("Enable hardware acceleration");
|
|
this.ctx = ctx;
|
|
}
|
|
|
|
getCellByCoords(x: number, y: number): Position {
|
|
let row = 0;
|
|
let height = 0;
|
|
while (height <= y) {
|
|
height += this.root.config.rows[row].height;
|
|
if (height >= y) break;
|
|
row++;
|
|
}
|
|
|
|
let col = 0;
|
|
let width = 0;
|
|
while (width <= x) {
|
|
width += this.root.config.columns[col].width;
|
|
if (width >= x) break;
|
|
col++;
|
|
}
|
|
|
|
return new Position(row, col);
|
|
}
|
|
|
|
renderCell(position: Position) {
|
|
const { column, row } = position;
|
|
this.root.data[row][column].render(this.root);
|
|
}
|
|
|
|
private getSelectionRange() {
|
|
const { selectedCell, selectedRange } = this.root.selection;
|
|
|
|
if (!selectedCell && !selectedRange) return;
|
|
if (selectedRange) {
|
|
const startRow = Math.min(selectedRange.from.row, selectedRange.to.row);
|
|
const startCol = Math.min(
|
|
selectedRange.from.column,
|
|
selectedRange.to.column,
|
|
);
|
|
const lastRow = Math.max(selectedRange.from.row, selectedRange.to.row);
|
|
const lastCol = Math.max(
|
|
selectedRange.from.column,
|
|
selectedRange.to.column,
|
|
);
|
|
|
|
const startCellBox = new RenderBox(this.root.config, {
|
|
row: startRow,
|
|
column: startCol,
|
|
});
|
|
|
|
let width = 0;
|
|
for (let col = startCol; col <= lastCol; col++) {
|
|
width += this.root.config.columns[col].width;
|
|
}
|
|
|
|
let height = 0;
|
|
for (let row = startRow; row <= lastRow; row++) {
|
|
height += this.root.config.rows[row].height;
|
|
}
|
|
|
|
const x = startCellBox.x - this.root.viewport.left;
|
|
const y = startCellBox.y - this.root.viewport.top;
|
|
|
|
return { x, y, height, width };
|
|
}
|
|
if (!selectedRange && selectedCell) {
|
|
const box = new RenderBox(this.root.config, selectedCell);
|
|
box.x -= this.root.viewport.left;
|
|
box.y -= this.root.viewport.top;
|
|
return box;
|
|
}
|
|
}
|
|
|
|
private renderSelectionRange(
|
|
x: number,
|
|
y: number,
|
|
width: number,
|
|
height: number,
|
|
) {
|
|
this.ctx.save();
|
|
this.ctx.strokeStyle = "#7da8ff";
|
|
this.ctx.lineWidth = 3;
|
|
this.ctx.strokeRect(x, y, width, height);
|
|
this.ctx.fillStyle = "#7da8ff35";
|
|
this.ctx.fillRect(x, y, width, height);
|
|
this.ctx.restore();
|
|
}
|
|
|
|
renderSelection() {
|
|
const box = this.getSelectionRange();
|
|
if (!box) return;
|
|
const { height, width, x, y } = box;
|
|
this.renderSelectionRange(x, y, width, height);
|
|
}
|
|
|
|
renderSheet() {
|
|
const firstRowIdx = this.root.viewport.firstRow;
|
|
const lastColIdx = this.root.viewport.lastCol + 3;
|
|
const lastRowIdx = this.root.viewport.lastRow + 3;
|
|
const firstColIdx = this.root.viewport.firstCol;
|
|
|
|
|
|
for (let row = firstRowIdx; row <= lastRowIdx; row++) {
|
|
for (let col = firstColIdx; col <= lastColIdx; col++) {
|
|
if (!this.root.config.columns[col] || !this.root.config.rows[row])
|
|
break; //* Prevent read undefined
|
|
|
|
this.renderCell({ column: col, row });
|
|
}
|
|
}
|
|
this.renderSelection();
|
|
}
|
|
}
|