feat: grid overlay

This commit is contained in:
aarthificial
2022-04-08 00:22:48 +02:00
parent 681146a8e9
commit f7aca1854c
3 changed files with 115 additions and 18 deletions

View File

@@ -20,6 +20,7 @@ const Sizes: Record<ProjectSize, [number, number]> = {
export class Project extends Stage {
public readonly background: Rect;
public readonly foreground: Layer;
public readonly center: Vector2d;
public threadsCallback: ThreadsCallback;
public framesPerSeconds = 60;
@@ -63,6 +64,9 @@ export class Project extends Stage {
backgroundLayer.add(this.background);
this.add(backgroundLayer);
this.foreground = new Layer({name: 'foreground'});
this.add(this.foreground);
for (const scene of scenes) {
const handle = new Scene(this, scene);
this.sceneLookup[scene.name] = handle;
@@ -74,6 +78,13 @@ export class Project extends Stage {
}
}
draw(): this {
this.previousScene?.drawScene();
this.currentScene?.drawScene();
return this;
}
public reload(runners: SceneRunner[]) {
for (const runner of runners) {
this.sceneLookup[runner.name]?.reload(runner);
@@ -99,6 +110,7 @@ export class Project extends Stage {
await this.currentScene.reset(this.previousScene);
this.currentScene.firstFrame = this.frame;
this.add(this.currentScene);
this.foreground.moveToTop();
}
}
}
@@ -124,6 +136,7 @@ export class Project extends Stage {
this.frame = this.currentScene.firstFrame ?? 0;
this.add(this.currentScene);
this.foreground.moveToTop();
await this.currentScene.reset();
} else if (this.frame >= frame) {
this.previousScene?.remove();

View File

@@ -5,6 +5,7 @@ import {KonvaNode, getset} from '../decorators';
export interface GridConfig extends LayoutShapeConfig {
gridSize?: number;
subdivision?: boolean;
}
@KonvaNode()
@@ -12,6 +13,9 @@ export class Grid extends LayoutShape {
@getset(16, Grid.prototype.recalculate)
public gridSize: GetSet<number, this>;
@getset(false)
public subdivision: GetSet<GridConfig['subdivision'], this>;
private path: Path2D;
public constructor(config?: GridConfig) {
@@ -19,16 +23,38 @@ export class Grid extends LayoutShape {
this._strokeFunc = context => {
if (!this.path) this.recalculate();
context.stroke(this.path);
if (this.subdivision()) {
const offset = this.gridSize() / 2;
const dash = offset / 8;
context.setLineDash([
0,
dash / 2,
dash,
dash,
dash,
dash,
dash,
dash,
dash / 2,
]);
context.translate(offset, offset);
context.stroke(this.path);
}
};
}
private recalculate() {
this.path = new Path2D();
const gridSize = this.gridSize();
let gridSize = this.gridSize();
if (gridSize < 1) {
console.warn('Too small grid size: ', gridSize);
gridSize = 1;
}
const size = this.getSize();
size.width /= 2;
size.height /= 2;
size.width = size.width / 2 + gridSize;
size.height = size.width / 2 + gridSize;
for (let x = -size.width; x <= size.width; x += gridSize) {
this.path.moveTo(x, -size.height);

View File

@@ -1,35 +1,76 @@
import {Grid} from '../components/Grid';
import {Player} from './Player';
import {Vector2d} from 'konva/lib/types';
const ZOOM_SPEED = 0.05;
const STORAGE_KEY = 'navigator-state';
interface NavigatorState {
position: Vector2d;
scale: number;
gridVisible: boolean;
}
export class Navigator {
private scale = 1;
private readonly state: NavigatorState = {
position: {x: 0, y: 0},
scale: 1,
gridVisible: false,
};
private isPanning = false;
private startPosition = {x: 0, y: 0};
private panStartPosition = {x: 0, y: 0};
private position = {x: 0, y: 0};
public constructor(private readonly root: HTMLElement) {
private readonly grid: Grid;
private gridZoom: number = null;
public constructor(
private readonly player: Player,
private readonly root: HTMLElement,
) {
const savedState = localStorage.getItem(STORAGE_KEY);
if (savedState) {
this.state = JSON.parse(savedState);
}
this.grid = new Grid({
...player.project.center,
...player.project.size(),
strokeWidth: 2,
stroke: 'rgba(255, 255, 255, 0.32',
subdivision: true,
});
player.project.foreground.add(this.grid);
player.project.foreground.drawScene();
document.addEventListener('wheel', this.handleWheel);
document.addEventListener('mousedown', this.handleMouseDown);
document.addEventListener('mouseup', this.handleMouseUp);
document.addEventListener('mousemove', this.handleMouseMove);
document.addEventListener('keydown', event => {
switch (event.key) {
case '0':
this.scale = 1;
this.position = {x: 0, y: 0};
this.state.scale = 1;
this.state.position = {x: 0, y: 0};
this.update();
break;
case '=':
this.scale *= 1 + ZOOM_SPEED;
this.state.scale *= 1 + ZOOM_SPEED;
this.update();
break;
case '-':
this.scale *= 1 - ZOOM_SPEED;
this.state.scale *= 1 - ZOOM_SPEED;
this.update();
break;
case "'":
this.state.gridVisible = !this.state.gridVisible;
this.update();
break;
}
});
this.update();
}
private handleWheel = (event: WheelEvent) => {
@@ -41,11 +82,11 @@ export class Navigator {
};
const ratio = 1 - Math.sign(event.deltaY) * ZOOM_SPEED;
this.scale *= ratio;
this.state.scale *= ratio;
this.position = {
x: pointer.x + (this.position.x - pointer.x) * ratio,
y: pointer.y + (this.position.y - pointer.y) * ratio,
this.state.position = {
x: pointer.x + (this.state.position.x - pointer.x) * ratio,
y: pointer.y + (this.state.position.y - pointer.y) * ratio,
};
this.update();
@@ -55,7 +96,7 @@ export class Navigator {
if (event.button === 1) {
event.preventDefault();
this.isPanning = true;
this.startPosition = {...this.position};
this.startPosition = {...this.state.position};
this.panStartPosition = {x: event.x, y: event.y};
}
};
@@ -69,7 +110,7 @@ export class Navigator {
private handleMouseMove = (event: MouseEvent) => {
if (this.isPanning) {
this.position = {
this.state.position = {
x: this.startPosition.x - this.panStartPosition.x + event.x,
y: this.startPosition.y - this.panStartPosition.y + event.y,
};
@@ -78,6 +119,23 @@ export class Navigator {
};
private update() {
this.root.style.transform = `translate(${this.position.x}px, ${this.position.y}px) scale(${this.scale})`;
localStorage.setItem(STORAGE_KEY, JSON.stringify(this.state));
this.root.style.transform = `translate(${this.state.position.x}px, ${this.state.position.y}px) scale(${this.state.scale})`;
if (this.state.gridVisible) {
const newZoom = Math.min(
Math.pow(2, Math.round(Math.log2(this.state.scale))),
2,
);
if (newZoom !== this.gridZoom) {
this.gridZoom = newZoom;
this.grid.gridSize(80 / newZoom);
this.player.project.foreground.drawScene();
}
}
if (this.grid.visible() !== this.state.gridVisible) {
this.grid.visible(this.state.gridVisible);
this.player.project.foreground.drawScene();
}
}
}