fix(grid): sharpen canvas text at fractional scaling
This commit is contained in:
parent
eed0a26b6f
commit
16369acbda
|
|
@ -142,7 +142,7 @@ import { dataGridHeaderContentWidth, scrollbarGutterWidth } from "@/lib/dataGrid
|
|||
import { canFetchNextDataGridSegment, canGoNextDataGridPage, dataGridTotalRowCountLabelKey, hasCompleteLocalDataGridResult, resolveDataGridPaginationTotal, type DataGridInexactTotalRowCountMode } from "@/lib/dataGrid/dataGridPagination";
|
||||
import { dataGridCountQueryOptions } from "@/lib/dataGrid/dataGridQueryOptions";
|
||||
import { dataGridBottomScrollTop, dataGridScrollPosition, isDataGridAtScrollBottom, isDataGridNearScrollBottom, shouldCheckInfiniteScrollAfterScroll, type DataGridScrollPosition } from "@/lib/dataGrid/dataGridInfiniteScroll";
|
||||
import { CANVAS_DATA_GRID_ROW_HEIGHT, canvasDataGridActionOverlayWidth, canvasDataGridActionReservedWidth, dataGridSearchMatchKey, drawCanvasDataGrid } from "@/lib/dataGrid/canvasDataGridRenderer";
|
||||
import { CANVAS_DATA_GRID_ROW_HEIGHT, canvasDataGridActionOverlayWidth, canvasDataGridActionReservedWidth, dataGridSearchMatchKey, drawCanvasDataGrid, type CanvasDevicePixelSize } from "@/lib/dataGrid/canvasDataGridRenderer";
|
||||
import { DATA_GRID_DARK_STRIPED_ROW_BG, DATA_GRID_LIGHT_STRIPED_ROW_BG, dataGridActiveRowBackground } from "@/lib/dataGrid/dataGridPaintTheme";
|
||||
import { createRowLowerTextCache } from "@/lib/dataGrid/dataGridRowLowerText";
|
||||
import { dataGridPreviewLabelKey, dataGridSaveActionMode, dataGridSaveToolbarState } from "@/lib/dataGrid/dataGridSaveUi";
|
||||
|
|
@ -194,7 +194,7 @@ import { columnNamesForCopy } from "@/lib/dataGrid/dataGridColumnNameCopy";
|
|||
import { DATA_GRID_ROW_NUM_WIDTH, dataGridRowNumberColumnWidth, resolveDataGridMaxRowNumber, useDataGridColumnResize } from "@/composables/useDataGridColumnResize";
|
||||
import { createDataGridColumnStructureSignature } from "@/lib/dataGrid/dataGridColumnWidthState";
|
||||
import { useDataGridColumnLayout, useDataGridColumnLayoutState } from "@/composables/useDataGridColumnLayout";
|
||||
import { useDataGridCanvasRuntime, type DataGridCanvasRuntime } from "@/composables/useDataGridCanvasRuntime";
|
||||
import { dataGridCanvasDevicePixelSize, useDataGridCanvasRuntime, type DataGridCanvasRuntime } from "@/composables/useDataGridCanvasRuntime";
|
||||
import { useDataGridScrollbars, type DataGridScrollbarsRuntime } from "@/composables/useDataGridScrollbars";
|
||||
import { useDataGridSelection } from "@/composables/useDataGridSelection";
|
||||
import { moveDataGridCell } from "@/lib/dataGrid/dataGridNavigation";
|
||||
|
|
@ -4761,6 +4761,7 @@ const canvasViewportHeight = ref(0);
|
|||
const canvasScrollTop = ref(0);
|
||||
const canvasHoverCell = ref<{ rowIndex: number; visibleColIdx: number } | null>(null);
|
||||
const canvasDevicePixelRatio = ref(typeof window === "undefined" ? 1 : window.devicePixelRatio || 1);
|
||||
const canvasMeasuredDevicePixelSize = ref<CanvasDevicePixelSize | null>(null);
|
||||
const canvasBackingPixelRatio = computed(() => Math.min(4, Math.max(1, canvasDevicePixelRatio.value * settingsStore.editorSettings.uiScale)));
|
||||
const useCanvasGridRows = computed(() => dataGridRenderMode.value === "canvas");
|
||||
const canvasContentHeight = computed(() => Math.max(1, displayRowCount.value * CANVAS_DATA_GRID_ROW_HEIGHT));
|
||||
|
|
@ -4850,10 +4851,13 @@ function dataGridRowFromClientPoint(_clientX: number, clientY: number): number |
|
|||
return Number.isInteger(rowIndex) ? rowIndex : null;
|
||||
}
|
||||
|
||||
function syncCanvasViewport() {
|
||||
function syncCanvasViewport(entries?: readonly ResizeObserverEntry[]) {
|
||||
if (!dataGridIsActive) return;
|
||||
const scroller = canvasScrollerElement();
|
||||
if (!scroller) return;
|
||||
const canvas = canvasRef.value;
|
||||
const canvasEntry = canvas ? entries?.find((entry) => entry.target === canvas) : undefined;
|
||||
if (canvasEntry) canvasMeasuredDevicePixelSize.value = dataGridCanvasDevicePixelSize(canvasEntry);
|
||||
canvasViewportWidth.value = scroller.clientWidth;
|
||||
canvasViewportHeight.value = scroller.clientHeight;
|
||||
canvasScrollTop.value = scroller.scrollTop;
|
||||
|
|
@ -4914,6 +4918,7 @@ canvasRuntime = useDataGridCanvasRuntime({
|
|||
draw: drawCanvasGrid,
|
||||
syncViewport: syncCanvasViewport,
|
||||
getViewport: canvasScrollerElement,
|
||||
getSurface: () => canvasRef.value ?? null,
|
||||
refreshPixelRatio: () => {
|
||||
const next = currentCanvasDevicePixelRatio();
|
||||
if (Math.abs(next - canvasDevicePixelRatio.value) > 0.001) {
|
||||
|
|
@ -5343,6 +5348,7 @@ function drawCanvasGrid() {
|
|||
width: Math.max(1, canvasSurfaceWidth.value || scroller.clientWidth),
|
||||
height: Math.max(1, canvasViewportHeight.value || scroller.clientHeight),
|
||||
pixelRatio: canvasBackingPixelRatio.value,
|
||||
devicePixelSize: canvasMeasuredDevicePixelSize.value,
|
||||
isDark: isDark.value,
|
||||
styleKey: canvasRenderStyleKey.value,
|
||||
rowCount: displayRowCount.value,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { effectScope } from "vue";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { useDataGridCanvasRuntime, type DataGridAnimationFrameDriver } from "@/composables/useDataGridCanvasRuntime";
|
||||
import { dataGridCanvasDevicePixelSize, useDataGridCanvasRuntime, type DataGridAnimationFrameDriver } from "@/composables/useDataGridCanvasRuntime";
|
||||
import { useDataGridScrollbars } from "@/composables/useDataGridScrollbars";
|
||||
|
||||
function createFrameDriver() {
|
||||
|
|
@ -26,6 +26,34 @@ function createObserver() {
|
|||
}
|
||||
|
||||
describe("data grid visual runtimes", () => {
|
||||
it("reads the exact device-pixel content box reported for a canvas", () => {
|
||||
const size = dataGridCanvasDevicePixelSize({
|
||||
contentRect: { width: 801, height: 399 },
|
||||
devicePixelContentBoxSize: [{ inlineSize: 1001, blockSize: 499 }],
|
||||
} as unknown as ResizeObserverEntry);
|
||||
|
||||
expect(size).toEqual({ cssWidth: 801, cssHeight: 399, pixelWidth: 1001, pixelHeight: 499 });
|
||||
});
|
||||
|
||||
it("observes the canvas device-pixel box together with its viewport", () => {
|
||||
const resize = createObserver();
|
||||
const viewport = { children: [] } as unknown as Element;
|
||||
const surface = { children: [] } as unknown as Element;
|
||||
const runtime = useDataGridCanvasRuntime({
|
||||
draw: vi.fn(),
|
||||
syncViewport: vi.fn(),
|
||||
getViewport: () => viewport,
|
||||
getSurface: () => surface,
|
||||
createResizeObserver: () => resize.observer,
|
||||
});
|
||||
|
||||
runtime.observeViewport();
|
||||
|
||||
expect(resize.observe).toHaveBeenNthCalledWith(1, viewport);
|
||||
expect(resize.observe).toHaveBeenNthCalledWith(2, surface, { box: "device-pixel-content-box" });
|
||||
runtime.dispose();
|
||||
});
|
||||
|
||||
it("coalesces canvas frames and cancels all resources on dispose", () => {
|
||||
const frames = createFrameDriver();
|
||||
const resize = createObserver();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { getCurrentScope, onScopeDispose } from "vue";
|
||||
import type { CanvasDevicePixelSize } from "@/lib/dataGrid/canvasDataGridRenderer";
|
||||
|
||||
export interface DataGridAnimationFrameDriver {
|
||||
request(callback: FrameRequestCallback): number;
|
||||
|
|
@ -7,8 +8,9 @@ export interface DataGridAnimationFrameDriver {
|
|||
|
||||
export interface DataGridCanvasRuntimeOptions {
|
||||
draw(): void;
|
||||
syncViewport(): void;
|
||||
syncViewport(entries?: readonly ResizeObserverEntry[]): void;
|
||||
getViewport(): Element | null;
|
||||
getSurface?: () => Element | null;
|
||||
refreshPixelRatio?: () => void;
|
||||
frameDriver?: DataGridAnimationFrameDriver;
|
||||
createResizeObserver?: (callback: ResizeObserverCallback) => ResizeObserver | null;
|
||||
|
|
@ -27,6 +29,18 @@ export interface DataGridCanvasRuntime {
|
|||
dispose(): void;
|
||||
}
|
||||
|
||||
export function dataGridCanvasDevicePixelSize(entry: ResizeObserverEntry): CanvasDevicePixelSize | null {
|
||||
const boxes = entry.devicePixelContentBoxSize;
|
||||
const box = Array.isArray(boxes) ? boxes[0] : (boxes as unknown as ResizeObserverSize | undefined);
|
||||
if (!box || !Number.isFinite(box.inlineSize) || !Number.isFinite(box.blockSize) || box.inlineSize <= 0 || box.blockSize <= 0) return null;
|
||||
return {
|
||||
cssWidth: entry.contentRect.width,
|
||||
cssHeight: entry.contentRect.height,
|
||||
pixelWidth: Math.round(box.inlineSize),
|
||||
pixelHeight: Math.round(box.blockSize),
|
||||
};
|
||||
}
|
||||
|
||||
function defaultFrameDriver(): DataGridAnimationFrameDriver {
|
||||
return {
|
||||
request: (callback) => requestAnimationFrame(callback),
|
||||
|
|
@ -88,10 +102,18 @@ export function useDataGridCanvasRuntime(options: DataGridCanvasRuntimeOptions):
|
|||
options.syncViewport();
|
||||
const viewport = options.getViewport();
|
||||
if (!viewport) return;
|
||||
resizeObserver = createResizeObserver(() => {
|
||||
if (active && !disposed) options.syncViewport();
|
||||
resizeObserver = createResizeObserver((entries) => {
|
||||
if (active && !disposed) options.syncViewport(entries);
|
||||
});
|
||||
resizeObserver?.observe(viewport);
|
||||
const surface = options.getSurface?.();
|
||||
if (resizeObserver && surface && surface !== viewport) {
|
||||
try {
|
||||
resizeObserver.observe(surface, { box: "device-pixel-content-box" });
|
||||
} catch {
|
||||
resizeObserver.observe(surface);
|
||||
}
|
||||
}
|
||||
},
|
||||
pause() {
|
||||
active = false;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// @vitest-environment happy-dom
|
||||
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { CANVAS_DATA_GRID_ROW_HEIGHT, drawCanvasDataGrid, type DrawCanvasDataGridOptions } from "@/lib/dataGrid/canvasDataGridRenderer";
|
||||
import { drawCanvasDataGrid, resolveCanvasBackingStoreMetrics, type DrawCanvasDataGridOptions } from "@/lib/dataGrid/canvasDataGridRenderer";
|
||||
|
||||
function createMockCanvas(width = 800, height = 400) {
|
||||
const canvas = document.createElement("canvas");
|
||||
|
|
@ -80,6 +80,45 @@ describe("drawCanvasDataGrid with frozen columns", () => {
|
|||
} as CSSStyleDeclaration);
|
||||
});
|
||||
|
||||
it("uses the exact observed device-pixel size instead of a nominal DPR", () => {
|
||||
expect(
|
||||
resolveCanvasBackingStoreMetrics({
|
||||
width: 801,
|
||||
height: 399,
|
||||
pixelRatio: 1.25,
|
||||
devicePixelSize: { cssWidth: 801, cssHeight: 399, pixelWidth: 1001, pixelHeight: 499 },
|
||||
}),
|
||||
).toEqual({
|
||||
pixelWidth: 1001,
|
||||
pixelHeight: 499,
|
||||
scaleX: 1001 / 801,
|
||||
scaleY: 499 / 399,
|
||||
measured: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("ignores a stale observed size after the CSS viewport changes", () => {
|
||||
expect(
|
||||
resolveCanvasBackingStoreMetrics({
|
||||
width: 900,
|
||||
height: 400,
|
||||
pixelRatio: 1.25,
|
||||
devicePixelSize: { cssWidth: 801, cssHeight: 399, pixelWidth: 1001, pixelHeight: 499 },
|
||||
}),
|
||||
).toEqual({ pixelWidth: 1125, pixelHeight: 500, scaleX: 1.25, scaleY: 1.25, measured: false });
|
||||
});
|
||||
|
||||
it("caps the observed backing store at the configured pixel ratio", () => {
|
||||
expect(
|
||||
resolveCanvasBackingStoreMetrics({
|
||||
width: 801,
|
||||
height: 399,
|
||||
pixelRatio: 4,
|
||||
devicePixelSize: { cssWidth: 801, cssHeight: 399, pixelWidth: 4005, pixelHeight: 1995 },
|
||||
}),
|
||||
).toEqual({ pixelWidth: 3204, pixelHeight: 1596, scaleX: 4, scaleY: 4, measured: true });
|
||||
});
|
||||
|
||||
it("draws without errors when frozenColumnCount is 0", () => {
|
||||
const canvas = createMockCanvas();
|
||||
const options = createBaseOptions({ canvas, frozenColumnCount: 0 });
|
||||
|
|
|
|||
|
|
@ -5,6 +5,13 @@ import { BOOLEAN_CHECKBOX_SIZE, isBooleanCheckboxValue, normalizeBooleanCellValu
|
|||
|
||||
export const CANVAS_DATA_GRID_ROW_HEIGHT = 26;
|
||||
|
||||
export interface CanvasDevicePixelSize {
|
||||
cssWidth: number;
|
||||
cssHeight: number;
|
||||
pixelWidth: number;
|
||||
pixelHeight: number;
|
||||
}
|
||||
|
||||
export interface CanvasDataGridRow {
|
||||
id: number;
|
||||
displayIndex: number;
|
||||
|
|
@ -49,6 +56,7 @@ export interface DrawCanvasDataGridOptions {
|
|||
width: number;
|
||||
height: number;
|
||||
pixelRatio?: number;
|
||||
devicePixelSize?: CanvasDevicePixelSize | null;
|
||||
isDark: boolean;
|
||||
styleKey?: string;
|
||||
rowCount: number;
|
||||
|
|
@ -94,6 +102,33 @@ interface CanvasRenderState {
|
|||
currentSearchBorder: string;
|
||||
}
|
||||
|
||||
export interface CanvasBackingStoreMetrics {
|
||||
pixelWidth: number;
|
||||
pixelHeight: number;
|
||||
scaleX: number;
|
||||
scaleY: number;
|
||||
measured: boolean;
|
||||
}
|
||||
|
||||
export function resolveCanvasBackingStoreMetrics(options: { width: number; height: number; pixelRatio: number; devicePixelSize?: CanvasDevicePixelSize | null }): CanvasBackingStoreMetrics {
|
||||
const width = Math.max(1, options.width);
|
||||
const height = Math.max(1, options.height);
|
||||
const fallbackRatio = Math.max(1, options.pixelRatio);
|
||||
const measured = options.devicePixelSize;
|
||||
const measurementMatches = !!measured && Math.abs(measured.cssWidth - width) <= 0.5 && Math.abs(measured.cssHeight - height) <= 0.5 && measured.pixelWidth > 0 && measured.pixelHeight > 0;
|
||||
const fallbackPixelWidth = Math.max(1, Math.ceil(width * fallbackRatio));
|
||||
const fallbackPixelHeight = Math.max(1, Math.ceil(height * fallbackRatio));
|
||||
const pixelWidth = measurementMatches ? Math.min(measured.pixelWidth, fallbackPixelWidth) : fallbackPixelWidth;
|
||||
const pixelHeight = measurementMatches ? Math.min(measured.pixelHeight, fallbackPixelHeight) : fallbackPixelHeight;
|
||||
return {
|
||||
pixelWidth,
|
||||
pixelHeight,
|
||||
scaleX: pixelWidth / width,
|
||||
scaleY: pixelHeight / height,
|
||||
measured: measurementMatches,
|
||||
};
|
||||
}
|
||||
|
||||
export function resolveCanvasDataGridRowFill(theme: Pick<DataGridPaintTheme, "cellActive" | "cellSelected">, rowBase: string, options: { isActive: boolean; isDeleted: boolean; isSelected: boolean }): string {
|
||||
if (options.isSelected) return theme.cellSelected;
|
||||
if (options.isActive && !options.isDeleted) return theme.cellActive;
|
||||
|
|
@ -193,11 +228,11 @@ function alignCanvasPixel(value: number, dpr: number): number {
|
|||
return Math.round(value * dpr) / dpr;
|
||||
}
|
||||
|
||||
function drawBooleanCheckbox(ctx: CanvasRenderingContext2D, options: { drawX: number; y: number; colWidth: number; dpr: number; theme: DataGridPaintTheme; checked: boolean }): void {
|
||||
const { drawX, y, colWidth, dpr, theme, checked } = options;
|
||||
function drawBooleanCheckbox(ctx: CanvasRenderingContext2D, options: { drawX: number; y: number; colWidth: number; scaleX: number; scaleY: number; theme: DataGridPaintTheme; checked: boolean }): void {
|
||||
const { drawX, y, colWidth, scaleX, scaleY, theme, checked } = options;
|
||||
const size = BOOLEAN_CHECKBOX_SIZE;
|
||||
const boxX = alignCanvasPixel(drawX + (colWidth - size) / 2, dpr);
|
||||
const boxY = alignCanvasPixel(y + (CANVAS_DATA_GRID_ROW_HEIGHT - size) / 2, dpr);
|
||||
const boxX = alignCanvasPixel(drawX + (colWidth - size) / 2, scaleX);
|
||||
const boxY = alignCanvasPixel(y + (CANVAS_DATA_GRID_ROW_HEIGHT - size) / 2, scaleY);
|
||||
ctx.lineWidth = 1;
|
||||
if (checked) {
|
||||
ctx.fillStyle = theme.primary;
|
||||
|
|
@ -302,9 +337,13 @@ export function drawCanvasDataGrid(options: DrawCanvasDataGridOptions) {
|
|||
rightAlignedActionCell,
|
||||
columnIsBoolean,
|
||||
} = options;
|
||||
const dpr = Math.max(1, options.pixelRatio ?? window.devicePixelRatio ?? 1);
|
||||
const pixelWidth = Math.max(1, Math.ceil(width * dpr));
|
||||
const pixelHeight = Math.max(1, Math.ceil(height * dpr));
|
||||
const fallbackRatio = Math.max(1, options.pixelRatio ?? window.devicePixelRatio ?? 1);
|
||||
const { pixelWidth, pixelHeight, scaleX, scaleY } = resolveCanvasBackingStoreMetrics({
|
||||
width,
|
||||
height,
|
||||
pixelRatio: fallbackRatio,
|
||||
devicePixelSize: options.devicePixelSize,
|
||||
});
|
||||
if (canvas.width !== pixelWidth || canvas.height !== pixelHeight) {
|
||||
canvas.width = pixelWidth;
|
||||
canvas.height = pixelHeight;
|
||||
|
|
@ -316,7 +355,7 @@ export function drawCanvasDataGrid(options: DrawCanvasDataGridOptions) {
|
|||
|
||||
const ctx = canvas.getContext("2d");
|
||||
if (!ctx) return;
|
||||
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
||||
ctx.setTransform(scaleX, 0, 0, scaleY, 0, 0);
|
||||
ctx.imageSmoothingEnabled = false;
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
|
||||
|
|
@ -343,9 +382,9 @@ export function drawCanvasDataGrid(options: DrawCanvasDataGridOptions) {
|
|||
const firstCol = firstVisibleColumn(offsets, Math.max(0, contentStart - maxPreviewRightShift));
|
||||
const columnOffset = offsets[firstCol] ?? 0;
|
||||
const paintSearchMatches = !isScrolling && searchMatchKeys.size > 0;
|
||||
const rowNumberBorderX = crispCanvasLine(rowNumberWidth - 1, dpr);
|
||||
const rowNumberTextX = alignCanvasPixel(Math.max(0, rowNumberWidth - 1) / 2, dpr);
|
||||
const rowTextOffsetY = alignCanvasPixel(CANVAS_DATA_GRID_ROW_HEIGHT / 2, dpr);
|
||||
const rowNumberBorderX = crispCanvasLine(rowNumberWidth - 1, scaleX);
|
||||
const rowNumberTextX = alignCanvasPixel(Math.max(0, rowNumberWidth - 1) / 2, scaleX);
|
||||
const rowTextOffsetY = alignCanvasPixel(CANVAS_DATA_GRID_ROW_HEIGHT / 2, scaleY);
|
||||
|
||||
for (let rowIndex = firstRow; rowIndex <= lastRow; rowIndex++) {
|
||||
const item = rowAt(rowIndex);
|
||||
|
|
@ -360,7 +399,7 @@ export function drawCanvasDataGrid(options: DrawCanvasDataGridOptions) {
|
|||
isDeleted: item.isDeleted,
|
||||
isSelected: rowSelectionVisual,
|
||||
});
|
||||
const rowBorderY = crispCanvasLine(y + CANVAS_DATA_GRID_ROW_HEIGHT - 1, dpr);
|
||||
const rowBorderY = crispCanvasLine(y + CANVAS_DATA_GRID_ROW_HEIGHT - 1, scaleY);
|
||||
ctx.globalAlpha = item.isDeleted ? 0.7 : 1;
|
||||
ctx.fillStyle = rowFill;
|
||||
ctx.fillRect(0, y, width, CANVAS_DATA_GRID_ROW_HEIGHT);
|
||||
|
|
@ -390,7 +429,7 @@ export function drawCanvasDataGrid(options: DrawCanvasDataGridOptions) {
|
|||
ctx.fillStyle = rowNumberText;
|
||||
ctx.font = item.status === "new" || item.status === "edited" || item.status === "draft" ? semiboldFont : normalFont;
|
||||
ctx.textAlign = "center";
|
||||
const textY = alignCanvasPixel(y + rowTextOffsetY, dpr);
|
||||
const textY = alignCanvasPixel(y + rowTextOffsetY, scaleY);
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
ctx.rect(0, y, rowNumberWidth, CANVAS_DATA_GRID_ROW_HEIGHT);
|
||||
|
|
@ -472,24 +511,24 @@ export function drawCanvasDataGrid(options: DrawCanvasDataGridOptions) {
|
|||
ctx.font = value === null ? italicFont : tabularFont;
|
||||
setCanvasNumericVariant(ctx, value === null ? "normal" : "tabular-nums");
|
||||
const reservedWidth = rightAlignedActionCell?.rowIndex === item.displayIndex && rightAlignedActionCell.visibleColIdx === visibleColIdx ? rightAlignedActionCell.reservedWidth : 0;
|
||||
const { textAnchorX, maxWidth: cellMaxWidth } = resolveCanvasCellTextLayout({ drawX, colWidth, dpr, isRightAlign, reservedWidth });
|
||||
const { textAnchorX, maxWidth: cellMaxWidth } = resolveCanvasCellTextLayout({ drawX, colWidth, dpr: scaleX, isRightAlign, reservedWidth });
|
||||
if (isBooleanCell && value !== null && !isEditingThisCell) {
|
||||
drawBooleanCheckbox(ctx, { drawX, y, colWidth, dpr, theme, checked: normalizeBooleanCellValue(value) === true });
|
||||
drawBooleanCheckbox(ctx, { drawX, y, colWidth, scaleX, scaleY, theme, checked: normalizeBooleanCellValue(value) === true });
|
||||
if (item.isDeleted) {
|
||||
const boxX = alignCanvasPixel(drawX + (colWidth - BOOLEAN_CHECKBOX_SIZE) / 2, dpr);
|
||||
const strikeY = alignCanvasPixel(y + CANVAS_DATA_GRID_ROW_HEIGHT / 2, dpr);
|
||||
const boxX = alignCanvasPixel(drawX + (colWidth - BOOLEAN_CHECKBOX_SIZE) / 2, scaleX);
|
||||
const strikeY = alignCanvasPixel(y + CANVAS_DATA_GRID_ROW_HEIGHT / 2, scaleY);
|
||||
ctx.strokeStyle = theme.foreground;
|
||||
ctx.lineWidth = 1;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(boxX - 1, strikeY);
|
||||
ctx.lineTo(alignCanvasPixel(boxX + BOOLEAN_CHECKBOX_SIZE + 1, dpr), strikeY);
|
||||
ctx.lineTo(alignCanvasPixel(boxX + BOOLEAN_CHECKBOX_SIZE + 1, scaleX), strikeY);
|
||||
ctx.stroke();
|
||||
}
|
||||
} else {
|
||||
const rawDisplayText = item.isDraft && value === null ? (draftCellPlaceholder ?? "") : formatCell(value, actualColIdx);
|
||||
const displayText = isEditingThisCell ? "" : firstLineCellDisplayValue(rawDisplayText);
|
||||
const text = isEditingThisCell ? displayText : fitCanvasText(ctx, displayText, cellMaxWidth, isBooleanNullCell ? "left" : isRightAlign ? "right" : "left");
|
||||
const anchorX = isBooleanNullCell ? alignCanvasPixel(drawX + colWidth / 2, dpr) : textAnchorX;
|
||||
const anchorX = isBooleanNullCell ? alignCanvasPixel(drawX + colWidth / 2, scaleX) : textAnchorX;
|
||||
ctx.fillText(text, anchorX, textY);
|
||||
if (item.isDeleted && text) {
|
||||
const textWidth = ctx.measureText(text).width;
|
||||
|
|
@ -497,7 +536,7 @@ export function drawCanvasDataGrid(options: DrawCanvasDataGridOptions) {
|
|||
ctx.strokeStyle = theme.foreground;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(lineStartX, textY);
|
||||
ctx.lineTo(alignCanvasPixel(lineStartX + textWidth, dpr), textY);
|
||||
ctx.lineTo(alignCanvasPixel(lineStartX + textWidth, scaleX), textY);
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
|
|
@ -507,7 +546,7 @@ export function drawCanvasDataGrid(options: DrawCanvasDataGridOptions) {
|
|||
|
||||
ctx.strokeStyle = theme.border;
|
||||
ctx.beginPath();
|
||||
const columnBorderX = crispCanvasLine(drawX + colWidth - 1, dpr);
|
||||
const columnBorderX = crispCanvasLine(drawX + colWidth - 1, scaleX);
|
||||
ctx.moveTo(columnBorderX, y);
|
||||
ctx.lineTo(columnBorderX, y + CANVAS_DATA_GRID_ROW_HEIGHT);
|
||||
ctx.stroke();
|
||||
|
|
|
|||
Loading…
Reference in New Issue