Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Freeze trailing row support #848

Merged
merged 25 commits into from
Jan 6, 2024
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 3 additions & 35 deletions packages/core/src/common/image-window-loader.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { type Rectangle } from "../internal/data-grid/data-grid-types.js";
import { CellSet } from "../internal/data-grid/cell-set.js";
import throttle from "lodash/throttle.js";
import { unpackCol, unpackRow, packColRowToNumber, unpackNumberToColRow } from "./render-state-provider.js";
import { packColRowToNumber, unpackNumberToColRow, WindowingTrackerBase } from "./render-state-provider.js";
import type { ImageWindowLoader } from "../internal/data-grid/image-window-loader-interface.js";

interface LoadResult {
Expand All @@ -13,27 +12,10 @@ interface LoadResult {

const imgPool: HTMLImageElement[] = [];

class ImageWindowLoaderImpl implements ImageWindowLoader {
class ImageWindowLoaderImpl extends WindowingTrackerBase implements ImageWindowLoader {
private imageLoaded: (locations: CellSet) => void = () => undefined;
private loadedLocations: [number, number][] = [];

public visibleWindow: Rectangle = {
x: 0,
y: 0,
width: 0,
height: 0,
};

public freezeCols: number = 0;

private isInWindow = (packed: number) => {
const col = unpackCol(packed);
const row = unpackRow(packed);
const w = this.visibleWindow;
if (col < this.freezeCols && row >= w.y && row <= w.y + w.height) return true;
return col >= w.x && col <= w.x + w.width && row >= w.y && row <= w.y + w.height;
};

private cache: Record<string, LoadResult> = {};

public setCallback(imageLoaded: (locations: CellSet) => void) {
Expand All @@ -46,7 +28,7 @@ class ImageWindowLoaderImpl implements ImageWindowLoader {
this.loadedLocations = [];
}, 20);

private clearOutOfWindow = () => {
protected clearOutOfWindow = () => {
const keys = Object.keys(this.cache);
for (const key of keys) {
const obj = this.cache[key];
Expand All @@ -69,20 +51,6 @@ class ImageWindowLoaderImpl implements ImageWindowLoader {
}
};

public setWindow(newWindow: Rectangle, freezeCols: number): void {
if (
this.visibleWindow.x === newWindow.x &&
this.visibleWindow.y === newWindow.y &&
this.visibleWindow.width === newWindow.width &&
this.visibleWindow.height === newWindow.height &&
this.freezeCols === freezeCols
)
return;
this.visibleWindow = newWindow;
this.freezeCols = freezeCols;
this.clearOutOfWindow();
}

private loadImage(url: string, col: number, row: number, key: string) {
let loaded = false;
const img = imgPool.pop() ?? new Image();
Expand Down
243 changes: 243 additions & 0 deletions packages/core/src/common/math.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ export function combineRects(a: Rectangle, b: Rectangle): Rectangle {
return { x, y, width, height };
}

export function rectContains(a: Rectangle, b: Rectangle): boolean {
return a.x <= b.x && a.y <= b.y && a.x + a.width >= b.x + b.width && a.y + a.height >= b.y + b.height;
}

/**
* This function is absolutely critical for the performance of the fill handle and highlight regions. If you don't
* hug rectanges when they are dashed and they are huge you will get giant GPU stalls. The reason for the mod is
Expand Down Expand Up @@ -93,3 +97,242 @@ export function hugRectToTarget(rect: Rectangle, width: number, height: number,

return { x: left, y: top, width: right - left, height: bottom - top };
}

interface SplitRect {
rect: Rectangle;
clip: Rectangle;
}

export function splitRectIntoRegions(
rect: Rectangle,
splitIndicies: readonly [number, number, number, number],
width: number,
height: number,
splitLocations: readonly [number, number, number, number]
): SplitRect[] {
const [lSplit, tSplit, rSplit, bSplit] = splitIndicies;
const [lClip, tClip, rClip, bClip] = splitLocations;
const { x: inX, y: inY, width: inW, height: inH } = rect;

const result: SplitRect[] = [];

if (inW <= 0 || inH <= 0) return result;

const inRight = inX + inW;
const inBottom = inY + inH;

// The goal is to split the inbound rect into up to 9 regions based on the provided split indicies which are
// more or less cut lines. The cut lines are whole numbers as is the rect. We are dividing cells on a table.
// In theory there can be up to 9 regions returned, so we need to be careful to make sure we get them all and
// not return any empty regions.

// compute some handy values
const isOverLeft = inX < lSplit;
const isOverTop = inY < tSplit;
const isOverRight = inX + inW > rSplit;
const isOverBottom = inY + inH > bSplit;

const isOverCenterVert =
(inX > lSplit && inX < rSplit) || (inRight > lSplit && inRight < rSplit) || (inX < lSplit && inRight > rSplit);
const isOverCenterHoriz =
(inY > tSplit && inY < bSplit) ||
(inBottom > tSplit && inBottom < bSplit) ||
(inY < tSplit && inBottom > bSplit);

const isOverCenter = isOverCenterVert && isOverCenterHoriz;

// center
if (isOverCenter) {
const x = Math.max(inX, lSplit);
const y = Math.max(inY, tSplit);
const right = Math.min(inRight, rSplit);
const bottom = Math.min(inBottom, bSplit);
result.push({
rect: { x, y, width: right - x, height: bottom - y },
clip: {
x: lClip,
y: tClip,
width: rClip - lClip + 1,
height: bClip - tClip + 1,
},
});
}

// top left
if (isOverLeft && isOverTop) {
const x = inX;
const y = inY;
const right = Math.min(inRight, lSplit);
const bottom = Math.min(inBottom, tSplit);
result.push({
rect: {
x,
y,
width: right - x,
height: bottom - y,
},
clip: {
x: 0,
y: 0,
width: lClip + 1,
height: tClip + 1,
},
});
}

// top center
if (isOverTop && isOverCenterVert) {
const x = Math.max(inX, lSplit);
const y = inY;
const right = Math.min(inRight, rSplit);
const bottom = Math.min(inBottom, tSplit);
result.push({
rect: {
x,
y,
width: right - x,
height: bottom - y,
},
clip: {
x: lClip,
y: 0,
width: rClip - lClip + 1,
height: tClip + 1,
},
});
}

// top right
if (isOverTop && isOverRight) {
const x = Math.max(inX, rSplit);
const y = inY;
const right = inRight;
const bottom = Math.min(inBottom, tSplit);
result.push({
rect: {
x,
y,
width: right - x,
height: bottom - y,
},
clip: {
x: rClip,
y: 0,
width: width - rClip + 1,
height: tClip + 1,
},
});
}

// center left
if (isOverLeft && isOverCenterHoriz) {
const x = inX;
const y = Math.max(inY, tSplit);
const right = Math.min(inRight, lSplit);
const bottom = Math.min(inBottom, bSplit);
result.push({
rect: {
x,
y,
width: right - x,
height: bottom - y,
},
clip: {
x: 0,
y: tClip,
width: lClip + 1,
height: bClip - tClip + 1,
},
});
}

// center right
if (isOverRight && isOverCenterHoriz) {
const x = Math.max(inX, rSplit);
const y = Math.max(inY, tSplit);
const right = inRight;
const bottom = Math.min(inBottom, bSplit);
result.push({
rect: {
x,
y,
width: right - x,
height: bottom - y,
},
clip: {
x: rClip,
y: tClip,
width: width - rClip + 1,
height: bClip - tClip + 1,
},
});
}

// bottom left
if (isOverLeft && isOverBottom) {
const x = inX;
const y = Math.max(inY, bSplit);
const right = Math.min(inRight, lSplit);
const bottom = inBottom;
result.push({
rect: {
x,
y,
width: right - x,
height: bottom - y,
},
clip: {
x: 0,
y: bClip,
width: lClip + 1,
height: height - bClip + 1,
},
});
}

// bottom center
if (isOverBottom && isOverCenterVert) {
const x = Math.max(inX, lSplit);
const y = Math.max(inY, bSplit);
const right = Math.min(inRight, rSplit);
const bottom = inBottom;
result.push({
rect: {
x,
y,
width: right - x,
height: bottom - y,
},
clip: {
x: lClip,
y: bClip,
width: rClip - lClip + 1,
height: height - bClip + 1,
},
});
}

// bottom right
if (isOverRight && isOverBottom) {
const x = Math.max(inX, rSplit);
const y = Math.max(inY, bSplit);
const right = inRight;
const bottom = inBottom;
result.push({
rect: {
x,
y,
width: right - x,
height: bottom - y,
},
clip: {
x: rClip,
y: bClip,
width: width - rClip + 1,
height: height - bClip + 1,
},
});
}

return result;
}
Loading
Loading