fix(grid): keep canvas backing store at observed pixel density under reduced UI scale
This commit is contained in:
1 parent
c398a07573
commit
122b606bd7
3 files changed
+25
-7
No files matched your search
@@ -142,7 +142,7 @@ import { dataGridHeaderContentWidth, scrollbarGutterWidth } from "@/lib/dataGrid
|
|||||||
import { canFetchNextDataGridSegment, canGoNextDataGridPage, dataGridTotalRowCountLabelKey, dataGridTruncationHintKey, hasCompleteLocalDataGridResult, resolveDataGridPaginationTotal, type DataGridInexactTotalRowCountMode } from "@/lib/dataGrid/dataGridPagination";
|
import { canFetchNextDataGridSegment, canGoNextDataGridPage, dataGridTotalRowCountLabelKey, dataGridTruncationHintKey, hasCompleteLocalDataGridResult, resolveDataGridPaginationTotal, type DataGridInexactTotalRowCountMode } from "@/lib/dataGrid/dataGridPagination";
|
||||||
import { dataGridCountQueryOptions } from "@/lib/dataGrid/dataGridQueryOptions";
|
import { dataGridCountQueryOptions } from "@/lib/dataGrid/dataGridQueryOptions";
|
||||||
import { dataGridBottomScrollTop, dataGridScrollPosition, isDataGridAtScrollBottom, isDataGridNearScrollBottom, isDataGridPrefixAppend, shouldCheckInfiniteScrollAfterScroll, type DataGridScrollPosition } from "@/lib/dataGrid/dataGridInfiniteScroll";
|
import { dataGridBottomScrollTop, dataGridScrollPosition, isDataGridAtScrollBottom, isDataGridNearScrollBottom, isDataGridPrefixAppend, shouldCheckInfiniteScrollAfterScroll, type DataGridScrollPosition } from "@/lib/dataGrid/dataGridInfiniteScroll";
|
||||||
import { CANVAS_DATA_GRID_ROW_HEIGHT, canvasDataGridActionOverlayWidth, canvasDataGridActionReservedWidth, dataGridSearchMatchKey, drawCanvasDataGrid, type CanvasDevicePixelSize } from "@/lib/dataGrid/canvasDataGridRenderer";
|
import { CANVAS_DATA_GRID_ROW_HEIGHT, MAX_CANVAS_DATA_GRID_PIXEL_RATIO, 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 { DATA_GRID_DARK_STRIPED_ROW_BG, DATA_GRID_LIGHT_STRIPED_ROW_BG, dataGridActiveRowBackground } from "@/lib/dataGrid/dataGridPaintTheme";
|
||||||
import { createRowLowerTextCache } from "@/lib/dataGrid/dataGridRowLowerText";
|
import { createRowLowerTextCache } from "@/lib/dataGrid/dataGridRowLowerText";
|
||||||
import { dataGridPreviewLabelKey, dataGridSaveActionMode, dataGridSaveToolbarState } from "@/lib/dataGrid/dataGridSaveUi";
|
import { dataGridPreviewLabelKey, dataGridSaveActionMode, dataGridSaveToolbarState } from "@/lib/dataGrid/dataGridSaveUi";
|
||||||
@@ -4775,7 +4775,7 @@ const canvasScrollTop = ref(0);
|
|||||||
const canvasHoverCell = ref<{ rowIndex: number; visibleColIdx: number } | null>(null);
|
const canvasHoverCell = ref<{ rowIndex: number; visibleColIdx: number } | null>(null);
|
||||||
const canvasDevicePixelRatio = ref(typeof window === "undefined" ? 1 : window.devicePixelRatio || 1);
|
const canvasDevicePixelRatio = ref(typeof window === "undefined" ? 1 : window.devicePixelRatio || 1);
|
||||||
const canvasMeasuredDevicePixelSize = ref<CanvasDevicePixelSize | null>(null);
|
const canvasMeasuredDevicePixelSize = ref<CanvasDevicePixelSize | null>(null);
|
||||||
const canvasBackingPixelRatio = computed(() => Math.min(4, Math.max(1, canvasDevicePixelRatio.value * settingsStore.editorSettings.uiScale)));
|
const canvasBackingPixelRatio = computed(() => Math.min(MAX_CANVAS_DATA_GRID_PIXEL_RATIO, Math.max(1, canvasDevicePixelRatio.value * settingsStore.editorSettings.uiScale)));
|
||||||
const useCanvasGridRows = computed(() => dataGridRenderMode.value === "canvas");
|
const useCanvasGridRows = computed(() => dataGridRenderMode.value === "canvas");
|
||||||
const canvasContentHeight = computed(() => Math.max(1, displayRowCount.value * CANVAS_DATA_GRID_ROW_HEIGHT));
|
const canvasContentHeight = computed(() => Math.max(1, displayRowCount.value * CANVAS_DATA_GRID_ROW_HEIGHT));
|
||||||
// Clamp the sticky canvas/overlay to the content width. A viewport-wide sticky surface inflates the
|
// Clamp the sticky canvas/overlay to the content width. A viewport-wide sticky surface inflates the
|
||||||
|
|||||||
+17
-2
@@ -97,6 +97,17 @@ describe("drawCanvasDataGrid with frozen columns", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it.each([1, 1.125, 1.1875, 1.2345])("keeps the exact observed density when the nominal ratio is %s", (pixelRatio) => {
|
||||||
|
expect(
|
||||||
|
resolveCanvasBackingStoreMetrics({
|
||||||
|
width: 800,
|
||||||
|
height: 400,
|
||||||
|
pixelRatio,
|
||||||
|
devicePixelSize: { cssWidth: 800, cssHeight: 400, pixelWidth: 1000, pixelHeight: 500 },
|
||||||
|
}),
|
||||||
|
).toEqual({ pixelWidth: 1000, pixelHeight: 500, scaleX: 1.25, scaleY: 1.25, measured: true });
|
||||||
|
});
|
||||||
|
|
||||||
it("ignores a stale observed size after the CSS viewport changes", () => {
|
it("ignores a stale observed size after the CSS viewport changes", () => {
|
||||||
expect(
|
expect(
|
||||||
resolveCanvasBackingStoreMetrics({
|
resolveCanvasBackingStoreMetrics({
|
||||||
@@ -108,17 +119,21 @@ describe("drawCanvasDataGrid with frozen columns", () => {
|
|||||||
).toEqual({ pixelWidth: 1125, pixelHeight: 500, scaleX: 1.25, scaleY: 1.25, measured: false });
|
).toEqual({ pixelWidth: 1125, pixelHeight: 500, scaleX: 1.25, scaleY: 1.25, measured: false });
|
||||||
});
|
});
|
||||||
|
|
||||||
it("caps the observed backing store at the configured pixel ratio", () => {
|
it("caps the observed backing store only at the absolute safety limit", () => {
|
||||||
expect(
|
expect(
|
||||||
resolveCanvasBackingStoreMetrics({
|
resolveCanvasBackingStoreMetrics({
|
||||||
width: 801,
|
width: 801,
|
||||||
height: 399,
|
height: 399,
|
||||||
pixelRatio: 4,
|
pixelRatio: 1.125,
|
||||||
devicePixelSize: { cssWidth: 801, cssHeight: 399, pixelWidth: 4005, pixelHeight: 1995 },
|
devicePixelSize: { cssWidth: 801, cssHeight: 399, pixelWidth: 4005, pixelHeight: 1995 },
|
||||||
}),
|
}),
|
||||||
).toEqual({ pixelWidth: 3204, pixelHeight: 1596, scaleX: 4, scaleY: 4, measured: true });
|
).toEqual({ pixelWidth: 3204, pixelHeight: 1596, scaleX: 4, scaleY: 4, measured: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("caps a fallback ratio at the absolute safety limit", () => {
|
||||||
|
expect(resolveCanvasBackingStoreMetrics({ width: 801, height: 399, pixelRatio: 5 })).toEqual({ pixelWidth: 3204, pixelHeight: 1596, scaleX: 4, scaleY: 4, measured: false });
|
||||||
|
});
|
||||||
|
|
||||||
it("draws without errors when frozenColumnCount is 0", () => {
|
it("draws without errors when frozenColumnCount is 0", () => {
|
||||||
const canvas = createMockCanvas();
|
const canvas = createMockCanvas();
|
||||||
const options = createBaseOptions({ canvas, frozenColumnCount: 0 });
|
const options = createBaseOptions({ canvas, frozenColumnCount: 0 });
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import type { RowStatus } from "@/lib/dataGrid/gridRowStatus";
|
|||||||
import { DATA_GRID_DARK_SEARCH_COLORS, resolveDataGridPaintTheme, type DataGridPaintTheme } from "@/lib/dataGrid/dataGridPaintTheme";
|
import { DATA_GRID_DARK_SEARCH_COLORS, resolveDataGridPaintTheme, type DataGridPaintTheme } from "@/lib/dataGrid/dataGridPaintTheme";
|
||||||
|
|
||||||
export const CANVAS_DATA_GRID_ROW_HEIGHT = 26;
|
export const CANVAS_DATA_GRID_ROW_HEIGHT = 26;
|
||||||
|
export const MAX_CANVAS_DATA_GRID_PIXEL_RATIO = 4;
|
||||||
|
|
||||||
export interface CanvasDevicePixelSize {
|
export interface CanvasDevicePixelSize {
|
||||||
cssWidth: number;
|
cssWidth: number;
|
||||||
@@ -111,13 +112,15 @@ export interface CanvasBackingStoreMetrics {
|
|||||||
export function resolveCanvasBackingStoreMetrics(options: { width: number; height: number; pixelRatio: number; devicePixelSize?: CanvasDevicePixelSize | null }): CanvasBackingStoreMetrics {
|
export function resolveCanvasBackingStoreMetrics(options: { width: number; height: number; pixelRatio: number; devicePixelSize?: CanvasDevicePixelSize | null }): CanvasBackingStoreMetrics {
|
||||||
const width = Math.max(1, options.width);
|
const width = Math.max(1, options.width);
|
||||||
const height = Math.max(1, options.height);
|
const height = Math.max(1, options.height);
|
||||||
const fallbackRatio = Math.max(1, options.pixelRatio);
|
const fallbackRatio = Math.min(MAX_CANVAS_DATA_GRID_PIXEL_RATIO, Math.max(1, options.pixelRatio));
|
||||||
const measured = options.devicePixelSize;
|
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 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 fallbackPixelWidth = Math.max(1, Math.ceil(width * fallbackRatio));
|
||||||
const fallbackPixelHeight = Math.max(1, Math.ceil(height * fallbackRatio));
|
const fallbackPixelHeight = Math.max(1, Math.ceil(height * fallbackRatio));
|
||||||
const pixelWidth = measurementMatches ? Math.min(measured.pixelWidth, fallbackPixelWidth) : fallbackPixelWidth;
|
const maxPixelWidth = Math.max(1, Math.ceil(width * MAX_CANVAS_DATA_GRID_PIXEL_RATIO));
|
||||||
const pixelHeight = measurementMatches ? Math.min(measured.pixelHeight, fallbackPixelHeight) : fallbackPixelHeight;
|
const maxPixelHeight = Math.max(1, Math.ceil(height * MAX_CANVAS_DATA_GRID_PIXEL_RATIO));
|
||||||
|
const pixelWidth = measurementMatches ? Math.min(measured.pixelWidth, maxPixelWidth) : fallbackPixelWidth;
|
||||||
|
const pixelHeight = measurementMatches ? Math.min(measured.pixelHeight, maxPixelHeight) : fallbackPixelHeight;
|
||||||
return {
|
return {
|
||||||
pixelWidth,
|
pixelWidth,
|
||||||
pixelHeight,
|
pixelHeight,
|
||||||
|
|||||||
Reference in new issue
Block a user