Skip to content

Commit

Permalink
Merge pull request #2134 from quadratichq/copy-marching-ants
Browse files Browse the repository at this point in the history
Marching ants on copy target
  • Loading branch information
AyushAgrawal-A2 authored Dec 19, 2024
2 parents ceef298 + cc02a50 commit 94caef6
Show file tree
Hide file tree
Showing 8 changed files with 149 additions and 15 deletions.
2 changes: 2 additions & 0 deletions quadratic-client/src/app/grid/actions/clipboard/clipboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export const copyToClipboardEvent = async (e: ClipboardEvent) => {
debugTimeReset();
const jsClipboard = await quadraticCore.copyToClipboard(sheets.getRustSelection());
await toClipboard(jsClipboard.plainText, jsClipboard.html);
pixiApp.copy.changeCopyRanges();
debugTimeCheck('copy to clipboard');
};

Expand Down Expand Up @@ -111,6 +112,7 @@ export const copyToClipboard = async () => {
debugTimeReset();
const jsClipboard = await quadraticCore.copyToClipboard(sheets.getRustSelection());
await toClipboard(jsClipboard.plainText, jsClipboard.html);
pixiApp.copy.changeCopyRanges();
debugTimeCheck('copy to clipboard');
};

Expand Down
11 changes: 8 additions & 3 deletions quadratic-client/src/app/grid/sheet/Sheet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,9 +149,14 @@ export class Sheet {
}

// @returns screen rectangle for a column/row rectangle
getScreenRectangle(column: number, row: number, width: number, height: number): Rectangle {
const topLeft = this.getCellOffsets(column, row);
const bottomRight = this.getCellOffsets(column + width, row + height);
getScreenRectangle(
column: number | BigInt,
row: number | BigInt,
width: number | BigInt,
height: number | BigInt
): Rectangle {
const topLeft = this.getCellOffsets(Number(column), Number(row));
const bottomRight = this.getCellOffsets(Number(column) + Number(width), Number(row) + Number(height));
return new Rectangle(topLeft.left, topLeft.top, bottomRight.left - topLeft.left, bottomRight.top - topLeft.top);
}

Expand Down
10 changes: 10 additions & 0 deletions quadratic-client/src/app/grid/sheet/SheetCursor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -288,4 +288,14 @@ export class SheetCursor {
return [];
}
}

getRanges(): CellRefRange[] {
const rangesStringified = this.jsSelection.getRanges();
try {
return JSON.parse(rangesStringified);
} catch (e) {
console.error(e);
return [];
}
}
}
95 changes: 95 additions & 0 deletions quadratic-client/src/app/gridGL/UI/UICopy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { events } from '@/app/events/events';
import { sheets } from '@/app/grid/controller/Sheets';
import { DASHED } from '@/app/gridGL/generateTextures';
import { pixiApp } from '@/app/gridGL/pixiApp/PixiApp';
import { drawDashedRectangleMarching } from '@/app/gridGL/UI/cellHighlights/cellHighlightsDraw';
import { getCSSVariableTint } from '@/app/helpers/convertColor';
import { CellRefRange } from '@/app/quadratic-core-types';
import { Graphics } from 'pixi.js';

const MARCH_TIME = 80;
const ALPHA = 0.5;

// walking rectangle offset
const RECT_OFFSET = 1;

export class UICopy extends Graphics {
private sheetId?: string;
private ranges?: CellRefRange[];
private time = 0;
private march = 0;
private dirty = false;

constructor() {
super();
events.on('changeSheet', this.updateNextTick);
events.on('viewportChanged', this.updateNextTick);
events.on('transactionStart', this.clearCopyRanges);
}

destroy() {
events.off('changeSheet', this.updateNextTick);
events.off('viewportChanged', this.updateNextTick);
events.off('transactionStart', this.clearCopyRanges);
super.destroy();
}

isShowing(): boolean {
return !!this.ranges && this.sheetId === sheets.sheet.id;
}

private updateNextTick = () => (this.dirty = true);

clearCopyRanges = () => {
this.clear();
pixiApp.setViewportDirty();
this.ranges = undefined;
this.sheetId = undefined;
};

changeCopyRanges() {
const range = sheets.sheet.cursor.getRanges();
this.ranges = range;
this.time = 0;
this.march = 0;
this.sheetId = sheets.sheet.id;
}

private draw() {
if (!this.ranges) return;
let render = false;
this.ranges.forEach((cellRefRange) => {
const color = getCSSVariableTint('primary');
render ||= drawDashedRectangleMarching({
g: this,
color,
march: this.march,
noFill: true,
alpha: ALPHA,
offset: RECT_OFFSET,
range: cellRefRange,
});
});
if (render) {
pixiApp.setViewportDirty();
}
}

update() {
if (!this.ranges) return;
if (this.sheetId !== sheets.sheet.id) {
this.clear();
return;
}
const drawFrame = Date.now() - this.time > MARCH_TIME;
if (drawFrame) {
this.march = (this.march + 1) % Math.floor(DASHED);
this.time = Date.now();
}
if (drawFrame || this.dirty) {
this.clear();
this.draw();
this.dirty = false;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,35 +61,43 @@ export function drawDashedRectangleMarching(options: {
g: Graphics;
color: number;
march: number;
noFill?: boolean;
alpha?: number;
offset?: number;
range: CellRefRange;
}) {
const { g, color, march, range } = options;
}): boolean {
const { g, color, march, noFill, alpha, offset = 0, range } = options;

const selectionRect = getRangeScreenRectangleFromCellRefRange(range);
const bounds = pixiApp.viewport.getVisibleBounds();
if (!intersects.rectangleRectangle(selectionRect, bounds)) {
return;
return false;
}

const minX = selectionRect.left;
const minY = selectionRect.top;
const maxX = selectionRect.right;
const maxY = selectionRect.bottom;
const minX = selectionRect.left + offset;
const minY = selectionRect.top + offset;
const maxX = selectionRect.right - offset;
const maxY = selectionRect.bottom - offset;

g.clear();
if (!noFill) {
g.clear();
}

g.lineStyle({
alignment: 0,
});
g.moveTo(minX, minY);
g.beginFill(color, FILL_ALPHA);
g.drawRect(minX, minY, maxX - minX, maxY - minY);
g.endFill();
if (!noFill) {
g.beginFill(color, FILL_ALPHA);
g.drawRect(minX, minY, maxX - minX, maxY - minY);
g.endFill();
}

g.moveTo(minX, minY);
g.lineStyle({
width: CURSOR_THICKNESS,
color,
alignment: 0,
alpha,
});

const clamp = (n: number, min: number, max: number): number => {
Expand Down Expand Up @@ -132,4 +140,6 @@ export function drawDashedRectangleMarching(options: {
g.moveTo(minX + DASHED_THICKNESS, clamp(y - DASHED / 2, minY, maxY));
g.lineTo(minX + DASHED_THICKNESS, clamp(y, minY, maxY));
}

return true;
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ export function keyboardViewport(event: React.KeyboardEvent<HTMLElement>): boole

// Close overlay
if (matchShortcut(Action.CloseOverlay, event)) {
// clear copy range if it is showing
if (pixiApp.copy.isShowing()) {
pixiApp.copy.clearCopyRanges();
return true;
}

if (gridSettings.presentationMode) {
setGridSettings({ ...gridSettings, presentationMode: false });
return true;
Expand Down
4 changes: 4 additions & 0 deletions quadratic-client/src/app/gridGL/pixiApp/PixiApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { GridLines } from '@/app/gridGL/UI/GridLines';
import { HtmlPlaceholders } from '@/app/gridGL/UI/HtmlPlaceholders';
import { UICellImages } from '@/app/gridGL/UI/UICellImages';
import { UICellMoving } from '@/app/gridGL/UI/UICellMoving';
import { UICopy } from '@/app/gridGL/UI/UICopy';
import { UIMultiPlayerCursor } from '@/app/gridGL/UI/UIMultiplayerCursor';
import { UIValidations } from '@/app/gridGL/UI/UIValidations';
import { BoxCells } from '@/app/gridGL/UI/boxCells';
Expand Down Expand Up @@ -67,6 +68,7 @@ export class PixiApp {
imagePlaceholders!: Container;
cellImages!: UICellImages;
validations: UIValidations;
copy: UICopy;

renderer!: Renderer;
momentumDetector: MomentumScrollDetector;
Expand All @@ -93,6 +95,7 @@ export class PixiApp {
this.viewport = new Viewport();
this.background = new Background();
this.momentumDetector = new MomentumScrollDetector();
this.copy = new UICopy();
}

init() {
Expand Down Expand Up @@ -157,6 +160,7 @@ export class PixiApp {
this.gridLines = this.viewportContents.addChild(new GridLines());
this.boxCells = this.viewportContents.addChild(new BoxCells());
this.cellImages = this.viewportContents.addChild(this.cellImages);
this.copy = this.viewportContents.addChild(this.copy);
this.multiplayerCursor = this.viewportContents.addChild(new UIMultiPlayerCursor());
this.cursor = this.viewportContents.addChild(new Cursor());
this.htmlPlaceholders = this.viewportContents.addChild(new HtmlPlaceholders());
Expand Down
2 changes: 2 additions & 0 deletions quadratic-client/src/app/gridGL/pixiApp/Update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ export class Update {
pixiApp.validations.update(pixiApp.viewport.dirty);
debugTimeCheck('[Update] backgrounds');
pixiApp.background.update(pixiApp.viewport.dirty);
debugTimeCheck('[Update] copy');
pixiApp.copy.update();

if (pixiApp.viewport.dirty || rendererDirty) {
debugTimeReset();
Expand Down

0 comments on commit 94caef6

Please sign in to comment.