Skip to content

Commit

Permalink
Freeze trailing row support (#848)
Browse files Browse the repository at this point in the history
* WIP for freeze trailing row support

* Fix tests

* WIP

* Restore normal render

* Fix highlight regions

* Add support for recognizing many freeze regions

* Improve clipping regions

* More fixes

* Fix render state provider

* Add story

* Fix test build

* remove unneeded checks

* Fix misnamed variables

* Simplify

* Multiple faster than loop

* Fix dashed line tracking

* Fix column select highlight

* Remove dead comments

* Fix scroll to

* Oops

* Fix aggressive scrolling when selecting on frozen rows

* Fix tests
  • Loading branch information
jassmith committed Jan 6, 2024
1 parent 8a820dd commit 01fa8ab
Show file tree
Hide file tree
Showing 19 changed files with 730 additions and 408 deletions.
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

0 comments on commit 01fa8ab

Please sign in to comment.