Compare commits
No commits in common. "51934b7cb968f7b088ec24948abc895a09da1604" and "70ad562bdeefce0441a90b468076ce9a4d215a94" have entirely different histories.
51934b7cb9
...
70ad562bde
@ -1,7 +1,7 @@
|
||||
import { useRef, useState, type ReactElement } from "react";
|
||||
import "./style.css";
|
||||
import Canvas from "./Canvas";
|
||||
import { Editor } from "./editor/Editor";
|
||||
import { Editor } from "./Editor";
|
||||
import Toolbar from "./Toolbar";
|
||||
|
||||
function App(): ReactElement {
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { useEffect, type ReactElement, type RefObject } from "react";
|
||||
import { type Editor } from "./editor/Editor";
|
||||
import { V2 } from "./editor/V2";
|
||||
import { useEffect, useRef, type ReactElement, type RefObject } from "react";
|
||||
import { V2, type Editor } from "./Editor";
|
||||
|
||||
type Props = { editor: Editor; canvasRef: RefObject<HTMLCanvasElement | null> };
|
||||
|
||||
@ -31,9 +30,7 @@ function Canvas({ editor, canvasRef }: Props): ReactElement {
|
||||
editor.renderIfNeeded(ev.target as HTMLCanvasElement);
|
||||
}}
|
||||
onMouseMove={(ev) => {
|
||||
const deltaPos = V2(ev.movementX, ev.movementY);
|
||||
const pos = V2(ev.nativeEvent.offsetX, ev.nativeEvent.offsetY);
|
||||
editor.mouseMove(deltaPos, pos);
|
||||
editor.mouseMove(V2(ev.movementX, ev.movementY));
|
||||
editor.renderIfNeeded(ev.target as HTMLCanvasElement);
|
||||
}}
|
||||
onKeyDown={(ev) => {
|
||||
|
||||
303
editor/src/Editor.ts
Normal file
303
editor/src/Editor.ts
Normal file
@ -0,0 +1,303 @@
|
||||
export type V2 = { x: number; y: number };
|
||||
export const V2 = (x: number, y: number): V2 => ({ x, y });
|
||||
|
||||
export class Editor {
|
||||
private cx = new Cx();
|
||||
|
||||
render(canvas: HTMLCanvasElement) {
|
||||
this.cx.render(canvas);
|
||||
}
|
||||
|
||||
renderIfNeeded(canvas: HTMLCanvasElement) {
|
||||
this.cx.renderIfNeeded(canvas);
|
||||
}
|
||||
|
||||
mouseDown(pos: V2) {
|
||||
this.cx.mouseDown(pos);
|
||||
}
|
||||
|
||||
mouseUp(pos: V2) {
|
||||
this.cx.mouseUp(pos);
|
||||
}
|
||||
|
||||
mouseMove(deltaPos: V2) {
|
||||
this.cx.mouseMove(deltaPos);
|
||||
}
|
||||
|
||||
keyDown(key: string) {
|
||||
this.cx.keyDown(key);
|
||||
}
|
||||
|
||||
keyUp(key: string) {
|
||||
this.cx.keyUp(key);
|
||||
}
|
||||
|
||||
selectTool(tool: Tool) {
|
||||
this.cx.selectTool(tool);
|
||||
}
|
||||
|
||||
selectedTool(): Tool | null {
|
||||
return this.cx.selectedTool();
|
||||
}
|
||||
|
||||
tools(): Tool[] {
|
||||
return ["select", "pan", "and"];
|
||||
}
|
||||
|
||||
addUpdateAction(action: () => void): object {
|
||||
return this.cx.addUpdateAction(action);
|
||||
}
|
||||
|
||||
removeUpdateAction(actionId: object) {
|
||||
this.cx.removeUpdateAction(actionId);
|
||||
}
|
||||
}
|
||||
|
||||
class Cx {
|
||||
private offset = V2(0, 0);
|
||||
private renderNeeded = false;
|
||||
private state = new Normal(this) as State;
|
||||
private updateActions: (() => void)[] = [];
|
||||
private selectionRect: SelectionRect | null = null;
|
||||
|
||||
render(canvas: HTMLCanvasElement) {
|
||||
const c = canvas.getContext("2d")!;
|
||||
|
||||
c.imageSmoothingEnabled = false;
|
||||
c.fillStyle = "#666";
|
||||
c.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
const gridSize = { x: 20, y: 20 };
|
||||
const dotSize = { x: 2, y: 2 };
|
||||
|
||||
c.fillStyle = "#111";
|
||||
for (let y = 0; y < canvas.width / gridSize.x + 1; ++y) {
|
||||
for (let x = 0; x < canvas.height / gridSize.y + 1; ++x) {
|
||||
c.fillRect(
|
||||
(this.offset.x % gridSize.x) + x * gridSize.x - dotSize.x / 2,
|
||||
(this.offset.y % gridSize.y) + y * gridSize.y - dotSize.y / 2,
|
||||
dotSize.x,
|
||||
dotSize.y,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.selectionRect) {
|
||||
const {
|
||||
pos: { x, y },
|
||||
size: { x: w, y: h },
|
||||
} = this.selectionRect;
|
||||
|
||||
c.fillStyle = `#ff880088`;
|
||||
c.fillRect(x, y, w, h);
|
||||
c.strokeStyle = `#ff8800`;
|
||||
c.lineWidth = 2;
|
||||
c.strokeRect(x, y, w, h);
|
||||
}
|
||||
}
|
||||
|
||||
renderIfNeeded(canvas: HTMLCanvasElement) {
|
||||
if (this.renderNeeded) {
|
||||
this.render(canvas);
|
||||
this.renderNeeded = false;
|
||||
}
|
||||
}
|
||||
|
||||
mouseDown(pos: V2) {
|
||||
this.state.onMouseDown?.(pos);
|
||||
}
|
||||
mouseUp(pos: V2) {
|
||||
this.state.onMouseUp?.(pos);
|
||||
}
|
||||
mouseMove(deltaPos: V2) {
|
||||
this.state.onMouseMove?.(deltaPos);
|
||||
}
|
||||
keyDown(key: string) {
|
||||
this.state.onKeyDown?.(key);
|
||||
}
|
||||
keyUp(key: string) {
|
||||
this.state.onKeyUp?.(key);
|
||||
}
|
||||
selectTool(tool: Tool) {
|
||||
this.state.selectTool?.(tool);
|
||||
}
|
||||
selectedTool(): Tool | null {
|
||||
return this.state.selectedTool?.() ?? null;
|
||||
}
|
||||
|
||||
addUpdateAction(action: () => void): object {
|
||||
this.updateActions.push(action);
|
||||
return action;
|
||||
}
|
||||
|
||||
removeUpdateAction(actionId: object) {
|
||||
this.updateActions = this.updateActions.filter(
|
||||
(action) => action !== actionId,
|
||||
);
|
||||
}
|
||||
|
||||
transitionTo(newState: State) {
|
||||
this.state = newState;
|
||||
this.notifyListeners();
|
||||
}
|
||||
|
||||
notifyListeners() {
|
||||
for (const action of this.updateActions) {
|
||||
action();
|
||||
}
|
||||
}
|
||||
|
||||
moveOffset(deltaPos: V2) {
|
||||
this.offset.x += deltaPos.x;
|
||||
this.offset.y += deltaPos.y;
|
||||
this.renderNeeded = true;
|
||||
}
|
||||
|
||||
addSelectionRect(pos: V2) {
|
||||
this.selectionRect = { pos, size: V2(0, 0) };
|
||||
this.renderNeeded = true;
|
||||
}
|
||||
|
||||
removeSelectionRect() {
|
||||
this.selectionRect = null;
|
||||
this.renderNeeded = true;
|
||||
}
|
||||
|
||||
moveSelectionRect(deltaPos: V2) {
|
||||
if (this.selectionRect) {
|
||||
this.selectionRect.size.x += deltaPos.x;
|
||||
this.selectionRect.size.y += deltaPos.y;
|
||||
this.renderNeeded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface State {
|
||||
onMouseDown?(pos: V2): void;
|
||||
onMouseUp?(pos: V2): void;
|
||||
onMouseMove?(deltaPos: V2): void;
|
||||
onKeyDown?(key: string): void;
|
||||
onKeyUp?(key: string): void;
|
||||
selectTool?(tool: Tool): void;
|
||||
selectedTool?(): Tool | null;
|
||||
}
|
||||
|
||||
class Normal implements State {
|
||||
constructor(private cx: Cx) {}
|
||||
|
||||
selectTool(tool: Tool): void {
|
||||
switch (tool) {
|
||||
case "pan":
|
||||
this.cx.transitionTo(new Panning(this.cx));
|
||||
break;
|
||||
case "and":
|
||||
this.cx.transitionTo(new Placing(this.cx, "and"));
|
||||
}
|
||||
}
|
||||
|
||||
onMouseDown(pos: V2): void {
|
||||
this.cx.addSelectionRect(pos);
|
||||
this.cx.transitionTo(new Selecting(this.cx));
|
||||
}
|
||||
|
||||
onKeyDown(key: string): void {
|
||||
if (key === "Shift") {
|
||||
this.cx.transitionTo(new Panning(this.cx));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
selectedTool(): Tool | null {
|
||||
return "select";
|
||||
}
|
||||
}
|
||||
|
||||
class Panning implements State {
|
||||
private dragging = false;
|
||||
|
||||
constructor(private cx: Cx) {}
|
||||
|
||||
onMouseDown(_pos: V2): void {
|
||||
this.dragging = true;
|
||||
}
|
||||
|
||||
onMouseUp(_pos: V2): void {
|
||||
this.dragging = false;
|
||||
}
|
||||
|
||||
onMouseMove(deltaPos: V2): void {
|
||||
if (this.dragging) {
|
||||
this.cx.moveOffset(deltaPos);
|
||||
}
|
||||
}
|
||||
|
||||
onKeyDown(key: string): void {
|
||||
if (key === "Escape") {
|
||||
this.cx.transitionTo(new Normal(this.cx));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
onKeyUp(key: string): void {
|
||||
if (key === "Shift") {
|
||||
this.cx.transitionTo(new Normal(this.cx));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
selectTool(tool: Tool): void {
|
||||
this.cx.transitionTo(new Normal(this.cx));
|
||||
this.cx.selectTool(tool);
|
||||
}
|
||||
|
||||
selectedTool(): Tool | null {
|
||||
return "pan";
|
||||
}
|
||||
}
|
||||
|
||||
type SelectionRect = {
|
||||
pos: V2;
|
||||
size: V2;
|
||||
};
|
||||
|
||||
class Selecting implements State {
|
||||
constructor(private cx: Cx) {}
|
||||
|
||||
onMouseUp(_pos: V2): void {
|
||||
this.cx.removeSelectionRect();
|
||||
this.cx.transitionTo(new Normal(this.cx));
|
||||
}
|
||||
|
||||
onMouseMove(deltaPos: V2): void {
|
||||
this.cx.moveSelectionRect(deltaPos);
|
||||
}
|
||||
|
||||
selectedTool(): Tool | null {
|
||||
return "select";
|
||||
}
|
||||
}
|
||||
|
||||
class Placing implements State {
|
||||
constructor(
|
||||
private cx: Cx,
|
||||
private tool: Tool,
|
||||
) {}
|
||||
|
||||
onMouseUp(pos: V2): void {
|
||||
this.cx.transitionTo(new Normal(this.cx));
|
||||
console.log("place");
|
||||
}
|
||||
|
||||
onKeyDown(key: string): void {
|
||||
if (key === "Escape") {
|
||||
this.cx.transitionTo(new Normal(this.cx));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
selectedTool(): Tool | null {
|
||||
return this.tool;
|
||||
}
|
||||
}
|
||||
|
||||
type Tool = "select" | "pan" | "and";
|
||||
@ -1,5 +1,5 @@
|
||||
import { useEffect, useState, type ReactElement, type RefObject } from "react";
|
||||
import type { Editor } from "./editor/Editor";
|
||||
import type { Editor } from "./Editor";
|
||||
|
||||
type Props = { editor: Editor; canvasRef: RefObject<HTMLCanvasElement | null> };
|
||||
|
||||
|
||||
@ -1,59 +0,0 @@
|
||||
import { rectsCollide, type V2 } from "./V2";
|
||||
|
||||
export class Board {
|
||||
private components: Component[] = [];
|
||||
|
||||
canPlaceComponent(pos: V2, size: V2): boolean {
|
||||
return !this.components.some((comp) =>
|
||||
rectsCollide(comp.pos, comp.size, pos, size),
|
||||
);
|
||||
}
|
||||
|
||||
placeComponent(pos: V2, size: V2, label: string) {
|
||||
this.components.push({ pos, size, label });
|
||||
}
|
||||
|
||||
render(
|
||||
canvas: HTMLCanvasElement,
|
||||
c: CanvasRenderingContext2D,
|
||||
offset: V2,
|
||||
gridSize: Readonly<V2>,
|
||||
) {
|
||||
for (const comp of this.components) {
|
||||
const {
|
||||
pos: { x, y },
|
||||
size: { x: w, y: h },
|
||||
} = comp;
|
||||
|
||||
c.fillStyle = `#0088cc`;
|
||||
c.fillRect(
|
||||
x * gridSize.x + offset.x,
|
||||
y * gridSize.y + offset.y,
|
||||
w * gridSize.x,
|
||||
h * gridSize.y,
|
||||
);
|
||||
c.strokeStyle = `#333333`;
|
||||
c.lineWidth = 2;
|
||||
c.strokeRect(
|
||||
x * gridSize.x + offset.x,
|
||||
y * gridSize.y + offset.y,
|
||||
w * gridSize.x,
|
||||
h * gridSize.y,
|
||||
);
|
||||
c.fillStyle = `#333333`;
|
||||
c.font = "bold 16px monospace";
|
||||
const textMetrix = c.measureText(comp.label);
|
||||
c.fillText(
|
||||
comp.label,
|
||||
x * gridSize.x + offset.x + (w * gridSize.x) / 2 - textMetrix.width / 2,
|
||||
y * gridSize.y + offset.y + 13 + (h * gridSize.y) / 2 - 16 / 2,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type Component = {
|
||||
pos: V2;
|
||||
size: V2;
|
||||
label: string;
|
||||
};
|
||||
@ -1,188 +0,0 @@
|
||||
import { Board } from "./Board";
|
||||
import type { State } from "./State";
|
||||
import { Normal } from "./states/Normal";
|
||||
import { V2 } from "./V2";
|
||||
|
||||
export class Cx {
|
||||
private offset = V2(0, 0);
|
||||
private renderNeeded = false;
|
||||
private state = new Normal(this) as State;
|
||||
private updateActions: (() => void)[] = [];
|
||||
|
||||
public gridSize = Object.freeze(V2(20, 20));
|
||||
|
||||
private selectionRect: SelectionRect | null = null;
|
||||
private componentPlacer: ComponentPlacer | null = null;
|
||||
|
||||
public board = new Board();
|
||||
|
||||
render(canvas: HTMLCanvasElement) {
|
||||
const c = canvas.getContext("2d")!;
|
||||
|
||||
c.imageSmoothingEnabled = false;
|
||||
c.fillStyle = "#666";
|
||||
c.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
const gridSize = this.gridSize;
|
||||
const dotSize = { x: 2, y: 2 };
|
||||
|
||||
c.fillStyle = "#111";
|
||||
for (let y = 0; y < canvas.width / gridSize.x + 1; ++y) {
|
||||
for (let x = 0; x < canvas.height / gridSize.y + 1; ++x) {
|
||||
c.fillRect(
|
||||
(this.offset.x % gridSize.x) + x * gridSize.x - dotSize.x / 2,
|
||||
(this.offset.y % gridSize.y) + y * gridSize.y - dotSize.y / 2,
|
||||
dotSize.x,
|
||||
dotSize.y,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this.board.render(canvas, c, this.offset, gridSize);
|
||||
|
||||
if (this.selectionRect) {
|
||||
const {
|
||||
pos: { x, y },
|
||||
size: { x: w, y: h },
|
||||
} = this.selectionRect;
|
||||
|
||||
c.fillStyle = `#ff880088`;
|
||||
c.fillRect(x, y, w, h);
|
||||
c.strokeStyle = `#ff8800`;
|
||||
c.lineWidth = 2;
|
||||
c.strokeRect(x, y, w, h);
|
||||
}
|
||||
|
||||
if (this.componentPlacer) {
|
||||
const {
|
||||
pos: { x, y },
|
||||
size: { x: w, y: h },
|
||||
} = this.componentPlacer;
|
||||
|
||||
c.strokeStyle = `#ffffff`;
|
||||
c.lineWidth = 2;
|
||||
c.strokeRect(x - (x % gridSize.x), y - (y % gridSize.y), w, h);
|
||||
}
|
||||
}
|
||||
|
||||
renderIfNeeded(canvas: HTMLCanvasElement) {
|
||||
if (this.renderNeeded) {
|
||||
this.render(canvas);
|
||||
this.renderNeeded = false;
|
||||
}
|
||||
}
|
||||
|
||||
mouseDown(pos: V2) {
|
||||
this.state.onMouseDown?.(pos);
|
||||
}
|
||||
mouseUp(pos: V2) {
|
||||
this.state.onMouseUp?.(pos);
|
||||
}
|
||||
mouseMove(deltaPos: V2, pos: V2) {
|
||||
this.state.onMouseMove?.(deltaPos, pos);
|
||||
}
|
||||
keyDown(key: string) {
|
||||
this.state.onKeyDown?.(key);
|
||||
}
|
||||
keyUp(key: string) {
|
||||
this.state.onKeyUp?.(key);
|
||||
}
|
||||
selectTool(tool: Tool) {
|
||||
// this is very much a hack so that other tools
|
||||
// can be selected from any tool, without me
|
||||
// having to add that in every state.
|
||||
if (!(this.state instanceof Normal)) {
|
||||
this.transitionTo(new Normal(this));
|
||||
}
|
||||
this.state.selectTool?.(tool);
|
||||
}
|
||||
selectedTool(): Tool | null {
|
||||
return this.state.selectedTool?.() ?? null;
|
||||
}
|
||||
|
||||
addUpdateAction(action: () => void): object {
|
||||
this.updateActions.push(action);
|
||||
return action;
|
||||
}
|
||||
|
||||
removeUpdateAction(actionId: object) {
|
||||
this.updateActions = this.updateActions.filter(
|
||||
(action) => action !== actionId,
|
||||
);
|
||||
}
|
||||
|
||||
transitionTo(newState: State) {
|
||||
this.state.leaveState?.();
|
||||
this.state = newState;
|
||||
this.state.enterState?.();
|
||||
this.notifyListeners();
|
||||
}
|
||||
|
||||
notifyListeners() {
|
||||
for (const action of this.updateActions) {
|
||||
action();
|
||||
}
|
||||
}
|
||||
|
||||
moveOffset(deltaPos: V2) {
|
||||
this.offset.x += deltaPos.x;
|
||||
this.offset.y += deltaPos.y;
|
||||
this.renderNeeded = true;
|
||||
}
|
||||
|
||||
addSelectionRect(pos: V2) {
|
||||
this.selectionRect = { pos, size: V2(0, 0) };
|
||||
this.renderNeeded = true;
|
||||
}
|
||||
|
||||
removeSelectionRect() {
|
||||
this.selectionRect = null;
|
||||
this.renderNeeded = true;
|
||||
}
|
||||
|
||||
moveSelectionRect(deltaPos: V2) {
|
||||
if (this.selectionRect) {
|
||||
this.selectionRect.size.x += deltaPos.x;
|
||||
this.selectionRect.size.y += deltaPos.y;
|
||||
this.renderNeeded = true;
|
||||
}
|
||||
}
|
||||
|
||||
addComponentPlacer(pos: V2, size: V2) {
|
||||
this.componentPlacer = { pos, size };
|
||||
this.renderNeeded = true;
|
||||
}
|
||||
|
||||
removeComponentPlacer() {
|
||||
this.componentPlacer = null;
|
||||
this.renderNeeded = true;
|
||||
}
|
||||
|
||||
setComponentPlacerPos(pos: V2) {
|
||||
if (this.componentPlacer) {
|
||||
this.componentPlacer.pos = pos;
|
||||
this.renderNeeded = true;
|
||||
}
|
||||
}
|
||||
|
||||
canvasPosToBoard(pos: V2): V2 {
|
||||
const absX = pos.x - this.offset.x;
|
||||
const absY = pos.y - this.offset.y;
|
||||
return V2(
|
||||
(absX - (absX % this.gridSize.x)) / this.gridSize.x,
|
||||
(absY - (absY % this.gridSize.y)) / this.gridSize.y,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export type SelectionRect = {
|
||||
pos: V2;
|
||||
size: V2;
|
||||
};
|
||||
|
||||
export type ComponentPlacer = {
|
||||
pos: V2;
|
||||
size: V2;
|
||||
};
|
||||
|
||||
export type Tool = "select" | "pan" | "and";
|
||||
@ -1,54 +0,0 @@
|
||||
import { Cx, type Tool } from "./Cx";
|
||||
import { V2 } from "./V2";
|
||||
|
||||
export class Editor {
|
||||
private cx = new Cx();
|
||||
|
||||
render(canvas: HTMLCanvasElement) {
|
||||
this.cx.render(canvas);
|
||||
}
|
||||
|
||||
renderIfNeeded(canvas: HTMLCanvasElement) {
|
||||
this.cx.renderIfNeeded(canvas);
|
||||
}
|
||||
|
||||
mouseDown(pos: V2) {
|
||||
this.cx.mouseDown(pos);
|
||||
}
|
||||
|
||||
mouseUp(pos: V2) {
|
||||
this.cx.mouseUp(pos);
|
||||
}
|
||||
|
||||
mouseMove(deltaPos: V2, pos: V2) {
|
||||
this.cx.mouseMove(deltaPos, pos);
|
||||
}
|
||||
|
||||
keyDown(key: string) {
|
||||
this.cx.keyDown(key);
|
||||
}
|
||||
|
||||
keyUp(key: string) {
|
||||
this.cx.keyUp(key);
|
||||
}
|
||||
|
||||
selectTool(tool: Tool) {
|
||||
this.cx.selectTool(tool);
|
||||
}
|
||||
|
||||
selectedTool(): Tool | null {
|
||||
return this.cx.selectedTool();
|
||||
}
|
||||
|
||||
tools(): Tool[] {
|
||||
return ["select", "pan", "and"];
|
||||
}
|
||||
|
||||
addUpdateAction(action: () => void): object {
|
||||
return this.cx.addUpdateAction(action);
|
||||
}
|
||||
|
||||
removeUpdateAction(actionId: object) {
|
||||
this.cx.removeUpdateAction(actionId);
|
||||
}
|
||||
}
|
||||
@ -1,14 +0,0 @@
|
||||
import type { Tool } from "./Cx";
|
||||
import type { V2 } from "./V2";
|
||||
|
||||
export interface State {
|
||||
enterState?(): void;
|
||||
leaveState?(): void;
|
||||
onMouseDown?(pos: V2): void;
|
||||
onMouseUp?(pos: V2): void;
|
||||
onMouseMove?(deltaPos: V2, pos: V2): void;
|
||||
onKeyDown?(key: string): void;
|
||||
onKeyUp?(key: string): void;
|
||||
selectTool?(tool: Tool): void;
|
||||
selectedTool?(): Tool | null;
|
||||
}
|
||||
@ -1,11 +0,0 @@
|
||||
export type V2 = { x: number; y: number };
|
||||
export const V2 = (x: number, y: number): V2 => ({ x, y });
|
||||
|
||||
export function rectsCollide(
|
||||
{ x: ax, y: ay }: V2,
|
||||
{ x: aw, y: ah }: V2,
|
||||
{ x: bx, y: by }: V2,
|
||||
{ x: bw, y: bh }: V2,
|
||||
): boolean {
|
||||
return ax < bx + bw && ax + aw > bx && ay < by + bh && ay + ah > by;
|
||||
}
|
||||
@ -1,36 +0,0 @@
|
||||
import type { Cx, Tool } from "../Cx";
|
||||
import type { V2 } from "../V2";
|
||||
import type { State } from "../State";
|
||||
import { Panning } from "./Panning";
|
||||
import { Placing } from "./Placing";
|
||||
import { Selecting } from "./Selecting";
|
||||
|
||||
export class Normal implements State {
|
||||
constructor(private cx: Cx) {}
|
||||
|
||||
selectTool(tool: Tool): void {
|
||||
switch (tool) {
|
||||
case "pan":
|
||||
this.cx.transitionTo(new Panning(this.cx));
|
||||
break;
|
||||
case "and":
|
||||
this.cx.transitionTo(new Placing(this.cx, "and"));
|
||||
}
|
||||
}
|
||||
|
||||
onMouseDown(pos: V2): void {
|
||||
this.cx.addSelectionRect(pos);
|
||||
this.cx.transitionTo(new Selecting(this.cx));
|
||||
}
|
||||
|
||||
onKeyDown(key: string): void {
|
||||
if (key === "Shift") {
|
||||
this.cx.transitionTo(new Panning(this.cx));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
selectedTool(): Tool | null {
|
||||
return "select";
|
||||
}
|
||||
}
|
||||
@ -1,42 +0,0 @@
|
||||
import type { Cx, Tool } from "../Cx";
|
||||
import type { V2 } from "../V2";
|
||||
import type { State } from "../State";
|
||||
import { Normal } from "./Normal";
|
||||
|
||||
export class Panning implements State {
|
||||
private dragging = false;
|
||||
|
||||
constructor(private cx: Cx) {}
|
||||
|
||||
onMouseDown(_pos: V2): void {
|
||||
this.dragging = true;
|
||||
}
|
||||
|
||||
onMouseUp(_pos: V2): void {
|
||||
this.dragging = false;
|
||||
}
|
||||
|
||||
onMouseMove(deltaPos: V2): void {
|
||||
if (this.dragging) {
|
||||
this.cx.moveOffset(deltaPos);
|
||||
}
|
||||
}
|
||||
|
||||
onKeyDown(key: string): void {
|
||||
if (key === "Escape") {
|
||||
this.cx.transitionTo(new Normal(this.cx));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
onKeyUp(key: string): void {
|
||||
if (key === "Shift") {
|
||||
this.cx.transitionTo(new Normal(this.cx));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
selectedTool(): Tool | null {
|
||||
return "pan";
|
||||
}
|
||||
}
|
||||
@ -1,43 +0,0 @@
|
||||
import { type Cx, type Tool } from "../Cx";
|
||||
import { V2 } from "../V2";
|
||||
import type { State } from "../State";
|
||||
import { Normal } from "./Normal";
|
||||
|
||||
export class Placing implements State {
|
||||
constructor(
|
||||
private cx: Cx,
|
||||
private tool: Tool,
|
||||
) {}
|
||||
|
||||
enterState(): void {
|
||||
this.cx.addComponentPlacer(V2(0, 0), V2(20 * 4, 20 * 2));
|
||||
}
|
||||
|
||||
leaveState(): void {
|
||||
this.cx.removeComponentPlacer();
|
||||
}
|
||||
|
||||
onMouseDown(pos: V2): void {
|
||||
const boardPos = this.cx.canvasPosToBoard(pos);
|
||||
if (this.cx.board.canPlaceComponent(boardPos, V2(4, 2))) {
|
||||
console.log("place");
|
||||
this.cx.board.placeComponent(boardPos, V2(4, 2), "AND");
|
||||
this.cx.transitionTo(new Normal(this.cx));
|
||||
}
|
||||
}
|
||||
|
||||
onMouseMove(_deltaPos: V2, pos: V2): void {
|
||||
this.cx.setComponentPlacerPos(pos);
|
||||
}
|
||||
|
||||
onKeyDown(key: string): void {
|
||||
if (key === "Escape") {
|
||||
this.cx.transitionTo(new Normal(this.cx));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
selectedTool(): Tool | null {
|
||||
return this.tool;
|
||||
}
|
||||
}
|
||||
@ -1,21 +0,0 @@
|
||||
import type { Cx, Tool } from "../Cx";
|
||||
import type { V2 } from "../V2";
|
||||
import type { State } from "../State";
|
||||
import { Normal } from "./Normal";
|
||||
|
||||
export class Selecting implements State {
|
||||
constructor(private cx: Cx) {}
|
||||
|
||||
onMouseUp(_pos: V2): void {
|
||||
this.cx.removeSelectionRect();
|
||||
this.cx.transitionTo(new Normal(this.cx));
|
||||
}
|
||||
|
||||
onMouseMove(deltaPos: V2): void {
|
||||
this.cx.moveSelectionRect(deltaPos);
|
||||
}
|
||||
|
||||
selectedTool(): Tool | null {
|
||||
return "select";
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user