Compare commits

...

2 Commits

Author SHA1 Message Date
Evert Prants cef1b80679
some cleanup 2023-01-18 20:47:22 +02:00
Evert Prants b82f1b03f2
zooming and panning 2023-01-18 20:11:54 +02:00
8 changed files with 341 additions and 219 deletions

View File

@ -1,6 +1,17 @@
<template>
<div class="relative h-full w-full bg-white">
<canvas ref="canvas" class="border-none" />
<div class="relative h-full w-full overflow-hidden bg-gray-100">
<canvas
ref="canvas"
class="border-none bg-white"
:style="{
width: `${canvasDim[0] * canvasZoom}px`,
height: `${canvasDim[1] * canvasZoom}px`,
transformOrigin: 'top left',
transform: `translate(${canvasPos[0]}px, ${canvasPos[1]}px)`,
}"
/>
</div>
<PlannerToolbar>
<PlannerTool
v-for="toolItem of toolbar"
@ -48,19 +59,20 @@ import {
HomeIcon,
ArrowsPointingOutIcon,
ArrowDownOnSquareIcon,
Square3Stack3DIcon,
} from '@heroicons/vue/24/outline';
import { useSessionStorage } from '@vueuse/core';
import type { Component } from 'vue';
import { onMounted, ref, shallowRef } from 'vue';
import { HousePlanner } from '../../modules/house-planner';
import { Layer, ToolEvent } from '../../modules/house-planner/interfaces';
import { LayerObjectType } from '../../modules/house-planner/types';
import {
Layer,
RepositionEvent,
ToolEvent,
Vec2,
} from '../../modules/house-planner/interfaces';
import deepUnref from '../../utils/deep-unref';
import { ToolbarTool } from './interfaces/toolbar.interfaces';
import PlannerLayerPanel from './PlannerLayerPanel.vue';
import PlannerPropertyPanel from './PlannerPropertyPanel.vue';
import PlannerSidebar from './PlannerSidebar.vue';
import PlannerSidebars from './PlannerSidebars.vue';
import PlannerTool from './PlannerTool.vue';
import PlannerToolbar from './PlannerToolbar.vue';
@ -69,6 +81,9 @@ const canvas = ref();
const module = shallowRef(new HousePlanner());
const tool = ref<string | undefined>('move');
const subTool = ref<string | undefined>(undefined);
const canvasDim = ref<Vec2>([1920, 1080]);
const canvasPos = ref<Vec2>([0, 0]);
const canvasZoom = ref<number>(1);
const serializedLayers = useSessionStorage<Layer[]>(
'roomData',
[
@ -130,7 +145,7 @@ const selectTool = (newTool?: string, newSubTool?: string) => {
if (newTool === tool.value && !newSubTool) {
newTool = undefined;
}
module.value.manager?.tools.setTool(newTool, newSubTool);
module.value.manager?.tools?.setTool(newTool, newSubTool);
};
const commitObjectName = (layerId: number, objectId: number, name: string) => {
@ -144,13 +159,13 @@ const commitLayerName = (layerId: number, name: string) => {
const clickedOnObject = (layerId: number, objectId: number, add?: boolean) => {
const object = module.value.manager?.getLayerObjectById(layerId, objectId);
if (!object) return;
module.value.manager?.tools.selectObject(object, add);
module.value.manager?.tools?.selectObject(object, add);
};
const clickedOnLayer = (layerId: number) => {
const layer = module.value.manager?.getLayerById(layerId);
if (!layer) return;
module.value.manager?.tools.selectLayer(layer);
module.value.manager?.tools?.selectLayer(layer);
};
const updateObjectProperty = (
@ -167,7 +182,10 @@ const updateObjectProperty = (
onMounted(() => {
const cleanUp = module.value.initialize(
canvas.value,
deepUnref(serializedLayers.value)
deepUnref(serializedLayers.value),
[1920, 1080],
canvasPos.value,
canvasZoom.value
);
const events: Record<string, (e: CustomEvent) => void> = {
@ -181,6 +199,10 @@ onMounted(() => {
tool.value = e.detail.primary;
subTool.value = e.detail.secondary as string;
},
'hpc:position': (e: CustomEvent<RepositionEvent>) => {
canvasPos.value = e.detail.position;
canvasZoom.value = e.detail.zoom;
},
};
Object.keys(events).forEach((event) =>

View File

@ -3,21 +3,14 @@
<div class="bg-white-50 flex h-full flex-col overflow-auto">
<template v-for="layer of layers">
<button
@dblclick="editingLayerName = layer.id"
@click="!editingLayerName && emit('selectLayer', layer.id)"
@click="emit('selectLayer', layer.id)"
:class="[
layer.active ? 'bg-blue-50' : '',
'flex w-full flex-row items-center justify-start space-x-2 px-2 py-2',
]"
>
<Square3Stack3DIcon class="h-4 w-4" />
<input
v-model="layer.name"
v-if="editingLayerName === layer.id"
@blur="commitLayerName(layer.name)"
@keypress.enter="commitLayerName(layer.name)"
/>
<span v-else>{{ layer.name }}</span>
<span>{{ layer.name }}</span>
</button>
<div class="flex flex-col bg-gray-50" v-if="layer.active">
<div v-for="object of layer.contents">
@ -29,7 +22,10 @@
emit('selectObject', layer.id, object.id, e.shiftKey)
"
:class="[
object.selected ? 'bg-blue-100' : '',
object.selected
? 'bg-blue-100 hover:bg-blue-200'
: 'hover:bg-gray-100',
!object.visible ? 'italic text-gray-500' : '',
'flex w-full flex-row items-center justify-start space-x-2 px-2 py-2 pl-6',
]"
>
@ -81,7 +77,6 @@ const emit = defineEmits<{
(e: 'selectObject', layer: number, object: number, add?: boolean): void;
}>();
const editingLayerName = ref<number | null>(null);
const editingObjectName = ref<number | null>(null);
const commitObjectName = (layerId: number, name: string) => {
@ -89,10 +84,4 @@ const commitObjectName = (layerId: number, name: string) => {
emit('objectName', layerId, editingObjectName.value, name);
editingObjectName.value = null;
};
const commitLayerName = (name: string) => {
if (editingLayerName.value == null) return;
emit('layerName', editingLayerName.value, name);
editingLayerName.value = null;
};
</script>

View File

@ -1,3 +1,5 @@
import { clamp } from '@vueuse/core';
import type { Ref } from 'vue';
import { HousePlannerCanvasGrid } from './grid';
import {
BezierSegment,
@ -5,6 +7,7 @@ import {
LayerObject,
Line,
LineSegment,
RepositionEvent,
Vec2,
} from './interfaces';
import { HousePlannerCanvasTools } from './tools';
@ -12,20 +15,42 @@ import {
rad2deg,
vec2Add,
vec2AngleFromOrigin,
vec2Clamp,
vec2Distance,
vec2DivideScalar,
vec2MultiplyScalar,
vec2PointFromAngle,
vec2Snap,
vec2Sub,
} from './utils';
export class HousePlannerCanvas {
public ctx!: CanvasRenderingContext2D;
public layers: Layer[] = [];
public tools = new HousePlannerCanvasTools(this);
public tools?;
public grid = new HousePlannerCanvasGrid(this, 8);
public mousePosition: Vec2 = [0, 0];
public mouseClickPosition: Vec2 = [0, 0];
public mousePositionSnapped: Vec2 = [0, 0];
private dragging = false;
private pinching = false;
private lastPinchLength = 0;
private moved = false;
private clickedOn: LayerObject | null = null;
constructor(public canvas: HTMLCanvasElement) {
constructor(
public canvas: HTMLCanvasElement,
public canvasDim: Vec2,
public canvasPos: Vec2,
public canvasZoom = 1,
public editable = true
) {
this.ctx = this.canvas.getContext('2d')!;
this.setupEvents();
this.resizeCanvas();
if (editable) {
this.tools = new HousePlannerCanvasTools(this);
}
}
get width() {
@ -41,8 +66,15 @@ export class HousePlannerCanvas {
this.draw();
}
resizeCanvas() {
const [w, h] = this.canvasDim;
this.canvas.width = w;
this.canvas.height = h;
this.draw();
}
cleanUp() {
this.tools.cleanUp();
this.tools?.cleanUp();
window.removeEventListener('keyup', this.boundKeyUpEvent);
window.removeEventListener('keydown', this.boundKeyDownEvent);
}
@ -50,13 +82,13 @@ export class HousePlannerCanvas {
draw() {
this.ctx.clearRect(0, 0, this.width, this.height);
this.grid.draw();
this.tools.drawHighlights();
for (const layer of this.layers) {
this.tools?.drawHighlights();
for (const layer of this.layers.slice().reverse()) {
if (!layer.visible) continue;
if (!layer.contents?.length) continue;
this.drawLayer(layer);
}
this.tools.drawControls();
this.tools?.drawControls();
}
makeBezier(segment: BezierSegment, path: Path2D) {
@ -166,7 +198,7 @@ export class HousePlannerCanvas {
) {
const object = this.getLayerObjectById(layerId, objectId);
if (!object) return;
this.tools.history.appendToHistory(
this.tools?.history.appendToHistory(
Object.keys(properties).map((key) => ({
object,
property: key,
@ -189,7 +221,7 @@ export class HousePlannerCanvas {
) {
const layer = this.getLayerById(layerId);
if (!layer) return;
this.tools.history.appendToHistory(
this.tools?.history.appendToHistory(
Object.keys(properties).map((key) => ({
object: layer,
property: key,
@ -206,16 +238,35 @@ export class HousePlannerCanvas {
return layer;
}
translateCanvas(offset: Vec2) {
const realSize = vec2MultiplyScalar(this.canvasDim, this.canvasZoom);
this.canvasPos = vec2Sub(this.canvasPos, offset);
this.repositionEvent();
}
zoomCanvas(diff: number) {
this.canvasZoom -= diff;
this.repositionEvent();
}
realMousePos(x: number, y: number) {
const rect = this.canvas.getBoundingClientRect();
const scaleX = this.canvas.width / rect.width;
const scaleY = this.canvas.height / rect.height;
this.mousePosition = [(x - rect.left) * scaleX, (y - rect.top) * scaleY];
this.mousePositionSnapped = !!this.grid.gridSnap
? vec2Snap(this.mousePosition, this.grid.gridSnap)
: this.mousePosition;
}
private keyDownEvent(e: KeyboardEvent) {
if (e.target !== document.body && e.target != null) return;
e.preventDefault();
this.tools.onKeyDown(e);
this.tools?.onKeyDown(e);
}
private boundKeyDownEvent = this.keyDownEvent.bind(this);
private keyUpEvent(e: KeyboardEvent) {
if (e.target !== document.body && e.target != null) return;
e.preventDefault();
this.tools.onKeyUp(e);
this.tools?.onKeyUp(e);
}
private boundKeyUpEvent = this.keyUpEvent.bind(this);
@ -223,20 +274,16 @@ export class HousePlannerCanvas {
window.addEventListener('keyup', this.boundKeyUpEvent);
window.addEventListener('keydown', this.boundKeyDownEvent);
this.canvas.addEventListener('mousemove', (e) => this.tools.onMouseMove(e));
this.canvas.addEventListener('mousedown', (e) => this.tools.onMouseDown(e));
this.canvas.addEventListener('mouseup', (e) => this.tools.onMouseUp(e));
this.canvas.addEventListener('mousemove', (e) => this.onMouseMove(e));
this.canvas.addEventListener('mousedown', (e) => this.onMouseDown(e));
this.canvas.addEventListener('mouseup', (e) => this.onMouseUp(e));
this.canvas.addEventListener('touchmove', (e) => this.tools.onTouchMove(e));
this.canvas.addEventListener('touchstart', (e) =>
this.tools.onTouchStart(e)
);
this.canvas.addEventListener('touchend', (e) => this.tools.onTouchEnd(e));
this.canvas.addEventListener('wheel', (e) => this.tools.onMouseWheel(e));
this.canvas.addEventListener('touchmove', (e) => this.onTouchMove(e));
this.canvas.addEventListener('touchstart', (e) => this.onTouchStart(e));
this.canvas.addEventListener('touchend', (e) => this.onTouchEnd(e));
this.canvas.addEventListener('wheel', (e) => this.onMouseWheel(e));
this.canvas.addEventListener('pointerleave', () =>
this.tools.onPointerLeave()
);
this.canvas.addEventListener('pointerleave', () => this.onPointerLeave());
}
private drawRoomText(line: Line) {
@ -287,4 +334,160 @@ export class HousePlannerCanvas {
}
}
}
onMouseMove(e: MouseEvent) {
this.dragEvent(e.clientX, e.clientY);
}
onMouseDown(e: MouseEvent) {
this.mouseClickPosition = [e.clientX, e.clientY];
this.moved = false;
this.pointerDown();
}
onMouseUp(e: MouseEvent) {
this.dragging = false;
this.pointerUp();
}
onTouchMove(ev: TouchEvent) {
ev.preventDefault();
if (ev.touches.length === 2 && this.pinching) {
const pinchLength = Math.hypot(
ev.touches[0].pageX - ev.touches[1].pageX,
ev.touches[0].pageY - ev.touches[1].pageY
);
if (this.lastPinchLength) {
const delta = pinchLength / this.lastPinchLength;
const scaleX =
(ev.touches[0].clientX - this.canvasPos[0]) / this.canvasZoom;
const scaleY =
(ev.touches[0].clientY - this.canvasPos[1]) / this.canvasZoom;
delta > 0 ? (this.canvasZoom *= delta) : (this.canvasZoom /= delta);
this.canvasZoom = clamp(this.canvasZoom, 1, 100);
this.canvasPos = [
ev.touches[0].clientX - scaleX * this.canvasZoom,
ev.touches[0].clientY - scaleY * this.canvasZoom,
];
this.repositionEvent();
}
this.lastPinchLength = pinchLength;
}
this.dragEvent(ev.touches[0].clientX, ev.touches[0].clientY);
}
onTouchStart(e: TouchEvent) {
e.preventDefault();
const touch = e.touches[0] || e.changedTouches[0];
this.dragging = true;
this.moved = false;
if (e.touches.length === 2) {
this.pinching = true;
}
this.pointerDown();
}
onTouchEnd(e: TouchEvent) {
this.pinching = false;
this.lastPinchLength = 0;
if (!e.touches?.length) {
this.dragging = false;
}
this.pointerUp();
}
onPointerLeave() {
this.dragging = false;
}
private onMouseWheel(e: WheelEvent) {
e.preventDefault();
this.realMousePos(e.clientX, e.clientY);
const scaleX = (e.clientX - this.canvasPos[0]) / this.canvasZoom;
const scaleY = (e.clientY - this.canvasPos[1]) / this.canvasZoom;
e.deltaY < 0 ? (this.canvasZoom *= 1.2) : (this.canvasZoom /= 1.2);
this.canvasZoom = clamp(this.canvasZoom, 0.1, 10);
this.canvasPos = [
e.clientX - scaleX * this.canvasZoom,
e.clientY - scaleY * this.canvasZoom,
];
// this.canvasPos = [
// clamp(this.canvasPos[0], -window.innerWidth / 2, window.innerWidth / 2),
// clamp(this.canvasPos[1], -window.innerHeight / 2, window.innerHeight / 2),
// ];
this.repositionEvent();
}
private dragEvent(x: number, y: number) {
this.moved = true;
const currentPosAbsolute: Vec2 = [...this.mousePosition];
const currentPosSnapped: Vec2 = [
...(!!this.grid.gridSnap
? this.mousePositionSnapped
: this.mousePosition),
];
this.realMousePos(x, y);
let offset = vec2Sub(currentPosSnapped, this.mousePositionSnapped);
let offsetAbsolute = vec2Sub(currentPosAbsolute, this.mousePosition);
this.tools?.mouseMoved(
this.mousePositionSnapped,
offset,
this.mousePosition
);
if (this.dragging) {
const divided = vec2MultiplyScalar(offsetAbsolute, this.canvasZoom);
this.translateCanvas(divided);
// Bias the offset
const [nx, ny] = vec2Add([x, y], divided);
this.realMousePos(nx, ny);
}
}
private pointerDown() {
if (!this.tools) return;
this.clickedOn = this.tools.getMousedObject();
this.tools.mouseDown(this.clickedOn || undefined);
if (!this.clickedOn) {
this.dragging = true;
}
}
private pointerUp() {
// FIXME: possibly there's a better approach, but right now some clicks do not register
if (
this.moved &&
vec2Distance(this.mouseClickPosition, this.mousePosition) < 0.25
) {
this.moved = false;
}
this.tools?.mouseUp(this.moved);
this.dragging = false;
this.clickedOn = null;
}
private repositionEvent() {
this.canvas.dispatchEvent(
new CustomEvent<RepositionEvent>('hpc:position', {
detail: {
position: this.canvasPos,
zoom: this.canvasZoom,
},
})
);
}
}

View File

@ -1,39 +1,42 @@
import { Ref } from 'vue';
import { HousePlannerCanvas } from './canvas';
import { Layer } from './interfaces';
import { Layer, Vec2 } from './interfaces';
export class HousePlanner {
public canvas!: HTMLCanvasElement;
public manager?: HousePlannerCanvas;
initialize(canvas: HTMLCanvasElement, initialData: Layer[]) {
initialize(
canvas: HTMLCanvasElement,
initialData: Layer[],
canvasDim: Vec2,
canvasPos: Vec2,
canvasZoom = 1,
editable = true
) {
this.canvas = canvas;
this.resizeCanvas();
this.addResizeEvents();
this.manager = new HousePlannerCanvas(canvas);
this.manager.layers = initialData;
this.manager.tools.selectLayer(
initialData[initialData.findIndex((layer) => layer.active)]
this.manager = new HousePlannerCanvas(
canvas,
canvasDim,
canvasPos,
canvasZoom,
editable
);
this.manager.tools.setInitialSelection();
this.manager.tools.setTool('move');
this.manager.layers = initialData;
if (editable && this.manager.tools) {
this.manager.tools.selectLayer(
initialData[initialData.findIndex((layer) => layer.active)]
);
this.manager.tools.setInitialSelection();
this.manager.tools.setTool('move');
}
this.manager.draw();
return () => this.cleanUp();
}
cleanUp() {
window.removeEventListener('resize', this.boundResizeEvent);
this.manager?.cleanUp();
}
private addResizeEvents() {
window.addEventListener('resize', this.boundResizeEvent);
}
resizeCanvas() {
this.canvas.width = this.canvas.parentElement!.clientWidth;
this.canvas.height = this.canvas.parentElement!.clientHeight;
this.manager?.draw();
}
private boundResizeEvent = this.resizeCanvas.bind(this);
}

View File

@ -55,14 +55,22 @@ export interface ToolEvent {
secondary?: unknown;
}
export interface ICanvasToolBase<U = undefined> {
export interface RepositionEvent {
position: Vec2;
zoom: number;
}
export interface ICanvasToolMouseEvents {
mouseDown(targetObject?: LayerObject): void;
mouseMoved(mouse: Vec2, offset: Vec2, mouseAbsolute: Vec2): void;
mouseUp(moved: boolean): void;
}
export interface ICanvasToolBase<U = undefined> extends ICanvasToolMouseEvents {
name: string;
subTool: U | undefined;
drawHighlights(): void;
drawControls(): void;
mouseDown(targetObject?: LayerObject): void;
mouseMoved(mouse: Vec2, offset: Vec2, mouseAbsolute: Vec2): void;
mouseUp(moved: boolean): void;
enterPress(e: KeyboardEvent): void;
escapePress(e: KeyboardEvent): void;
setSubTool(subTool?: U): void;

View File

@ -3,6 +3,7 @@ import { HousePlannerCanvasHistory } from './history';
import {
History,
ICanvasToolBase,
ICanvasToolMouseEvents,
Layer,
LayerObject,
Line,
@ -11,14 +12,10 @@ import {
} from './interfaces';
import { LineTool } from './tools/line';
import { MoveTool } from './tools/move';
import { vec2Distance, vec2Snap, vec2Sub } from './utils';
export class HousePlannerCanvasTools {
export class HousePlannerCanvasTools implements ICanvasToolMouseEvents {
public selectedLayer?: Layer;
public selectedObjects: LayerObject[] = [];
public mousePosition: Vec2 = [0, 0];
public mouseClickPosition: Vec2 = [0, 0];
public mousePositionSnapped: Vec2 = [0, 0];
public gridSnap = true;
public gridSnapScale = 8;
public tool?: ICanvasToolBase<unknown>;
@ -31,11 +28,6 @@ export class HousePlannerCanvasTools {
public lastColor = '#000000';
public holdShift = false;
public selectError = 16;
private dragging = false;
private pinching = false;
private lastPinchLength = 0;
private moved = false;
private clickedOn: LayerObject | null = null;
constructor(public manager: HousePlannerCanvas) {}
@ -73,6 +65,8 @@ export class HousePlannerCanvasTools {
},
})
);
this.selectedEvent();
this.manager.draw();
}
selectObject(object?: LayerObject | null, add = false) {
@ -133,7 +127,7 @@ export class HousePlannerCanvasTools {
getMousedObject() {
if (!this.selectedLayer?.visible) return null;
const [x, y] = this.mousePosition;
const [x, y] = this.manager.mousePosition;
for (const object of this.selectedLayer.contents) {
if ((object as Line).segments) {
const moused = this.manager.isOnLine(
@ -151,7 +145,7 @@ export class HousePlannerCanvasTools {
getMousedLineSegment(line: Line) {
if (!this.selectedLayer?.visible) return null;
const [x, y] = this.mousePosition;
const [x, y] = this.manager.mousePosition;
let lastSegment = null;
for (const segment of line.segments) {
const lastPoint = lastSegment ? lastSegment.end : (segment.start as Vec2);
@ -233,101 +227,21 @@ export class HousePlannerCanvasTools {
return this.tool?.name;
}
onMouseMove(e: MouseEvent) {
this.dragEvent(e.clientX, e.clientY);
}
onMouseDown(e: MouseEvent) {
this.mousePosition = [e.clientX, e.clientY];
this.mouseClickPosition = [...this.mousePosition];
this.mousePositionSnapped = this.gridSnap
? vec2Snap(this.mousePosition, this.gridSnapScale)
: this.mousePosition;
this.dragging = true;
this.moved = false;
this.pointerDown();
}
onMouseUp(e: MouseEvent) {
this.dragging = false;
this.pointerUp();
mouseDown(targetObject?: LayerObject | undefined): void {
this?.tool?.mouseDown(targetObject);
}
onTouchMove(ev: TouchEvent) {
ev.preventDefault();
if (ev.touches.length === 2 && this.pinching) {
const pinchLength = Math.hypot(
ev.touches[0].pageX - ev.touches[1].pageX,
ev.touches[0].pageY - ev.touches[1].pageY
);
// TODO: zoom
// if (this.lastPinchLength) {
// const delta = pinchLength / this.lastPinchLength;
// const scaleX = (ev.touches[0].clientX - this._posx) / this._zoom;
// const scaleY = (ev.touches[0].clientY - this._posy) / this._zoom;
// delta > 0 ? (this._zoom *= delta) : (this._zoom /= delta);
// this._zoom = clamp(this._zoom, 1, 100);
// this._posx = ev.touches[0].clientX - scaleX * this._zoom;
// this._posy = ev.touches[0].clientY - scaleY * this._zoom;
// }
// this.lastPinchLength = pinchLength;
}
this.dragEvent(ev.touches[0].clientX, ev.touches[0].clientY);
mouseMoved(mouse: Vec2, offset: Vec2, mouseAbsolute: Vec2): void {
this?.tool?.mouseMoved(mouse, offset, mouseAbsolute);
}
onTouchStart(e: TouchEvent) {
e.preventDefault();
const touch = e.touches[0] || e.changedTouches[0];
this.mousePosition = [touch.pageX, touch.pageY];
this.mouseClickPosition = [...this.mousePosition];
this.mousePositionSnapped = this.gridSnap
? vec2Snap(this.mousePosition, this.gridSnapScale)
: this.mousePosition;
this.dragging = true;
this.moved = false;
if (e.touches.length === 2) {
this.pinching = true;
}
this.pointerDown();
}
onTouchEnd(e: TouchEvent) {
this.pinching = false;
this.lastPinchLength = 0;
if (!e.touches?.length) {
this.dragging = false;
}
this.pointerUp();
}
onMouseWheel(e: WheelEvent) {
// TODO: zoom
// ev.preventDefault();
// this._mousex = ev.clientX;
// this._mousey = ev.clientY;
// const scaleX = (ev.clientX - this._posx) / this._zoom;
// const scaleY = (ev.clientY - this._posy) / this._zoom;
// ev.deltaY < 0 ? (this._zoom *= 1.2) : (this._zoom /= 1.2);
// this._zoom = clamp(this._zoom, this._minZoom, this._maxZoom);
// this._posx = ev.clientX - scaleX * this._zoom;
// this._posy = ev.clientY - scaleY * this._zoom;
// const realSize = this._zoom * this._size;
// this._posx = clamp(this._posx, this._cursorx - realSize, this._cursorx);
// this._posy = clamp(this._posy, this._cursory - realSize, this._cursory);
}
onPointerLeave() {
this.dragging = false;
mouseUp(moved: boolean): void {
this?.tool?.mouseUp(moved);
}
onKeyDown(e: KeyboardEvent) {
if (e.key === 'z' && e.ctrlKey) {
e.preventDefault();
this.history.undo();
this.manager.draw();
this.canvas.dispatchEvent(
@ -345,6 +259,7 @@ export class HousePlannerCanvasTools {
}
if (e.key === 'y' && e.ctrlKey) {
e.preventDefault();
this.history.redo();
this.manager.draw();
this.canvas.dispatchEvent(
@ -366,9 +281,12 @@ export class HousePlannerCanvasTools {
onKeyUp(e: KeyboardEvent) {
if (e.key === 'Enter') {
e.preventDefault();
this.tool?.enterPress(e);
}
if (e.key === 'Escape') {
e.preventDefault();
this.tool?.escapePress(e);
if (this.selectedObjects.length === 0 && this.tool?.isToolCancelable()) {
@ -379,14 +297,17 @@ export class HousePlannerCanvasTools {
}
if (e.key === 'Shift') {
e.preventDefault();
this.holdShift = false;
}
if (e.key === 'l') {
e.preventDefault();
this.setTool('line');
}
if (e.key === 'Delete' || e.key === 'x') {
e.preventDefault();
this.deleteSelection();
}
}
@ -416,47 +337,6 @@ export class HousePlannerCanvasTools {
);
}
private pointerDown() {
this.clickedOn = this.getMousedObject();
this.tool?.mouseDown(this.clickedOn || undefined);
}
private pointerUp() {
// FIXME: possibly there's a better approach, but right now some clicks do not register
if (
this.moved &&
vec2Distance(this.mouseClickPosition, this.mousePosition) < 0.25
) {
this.moved = false;
}
this.tool?.mouseUp(this.moved);
this.clickedOn = null;
}
private dragEvent(x: number, y: number) {
this.moved = true;
const currentPos = this.gridSnap
? vec2Snap(this.mousePosition, this.gridSnapScale)
: this.mousePosition;
const rect = this.manager.canvas.getBoundingClientRect();
this.mousePosition = [x - rect.left, y - rect.top];
this.mousePositionSnapped = this.gridSnap
? vec2Snap(this.mousePosition, this.gridSnapScale)
: this.mousePosition;
let offset = vec2Sub(currentPos, this.mousePositionSnapped);
this.tool?.mouseMoved(
this.mousePositionSnapped,
offset,
this.mousePosition
);
}
private selectedEvent() {
this.canvas.dispatchEvent(
new CustomEvent('hpc:selectionchange', {

View File

@ -56,7 +56,12 @@ export class MoveTool extends CanvasToolBase<undefined> {
}
mouseUp(moved?: boolean): void {
if (!this.handlingBezier && !this.handlingLine && !this.movingObject) {
if (
!this.handlingBezier &&
!this.handlingLine &&
!this.movingObject &&
!moved
) {
this.pick();
}

View File

@ -1,3 +1,4 @@
import { clamp } from '@vueuse/shared';
import { Vec2 } from './interfaces';
export const vec2Length = ([x, y]: Vec2) => Math.abs(Math.sqrt(x * x + y * y));
@ -7,11 +8,22 @@ export const vec2Add = ([x1, y1]: Vec2, [x2, y2]: Vec2): Vec2 => [
y1 + y2,
];
export const vec2Multiply = ([x1, y1]: Vec2, [x2, y2]: Vec2): Vec2 => [
x1 * x2,
y1 * y2,
];
export const vec2Sub = ([x1, y1]: Vec2, [x2, y2]: Vec2): Vec2 => [
x1 - x2,
y1 - y2,
];
export const vec2Clamp = (
[x1, y1]: Vec2,
[x2, y2]: Vec2,
[x3, y3]: Vec2
): Vec2 => [clamp(x1, x2, x3), clamp(y1, y2, y3)];
export const vec2MultiplyScalar = ([x, y]: Vec2, scalar: number): Vec2 => [
x * scalar,
y * scalar,