some cleanup
This commit is contained in:
parent
b82f1b03f2
commit
cef1b80679
@ -59,10 +59,8 @@ import {
|
|||||||
HomeIcon,
|
HomeIcon,
|
||||||
ArrowsPointingOutIcon,
|
ArrowsPointingOutIcon,
|
||||||
ArrowDownOnSquareIcon,
|
ArrowDownOnSquareIcon,
|
||||||
Square3Stack3DIcon,
|
|
||||||
} from '@heroicons/vue/24/outline';
|
} from '@heroicons/vue/24/outline';
|
||||||
import { useSessionStorage } from '@vueuse/core';
|
import { useSessionStorage } from '@vueuse/core';
|
||||||
import type { Component } from 'vue';
|
|
||||||
import { onMounted, ref, shallowRef } from 'vue';
|
import { onMounted, ref, shallowRef } from 'vue';
|
||||||
import { HousePlanner } from '../../modules/house-planner';
|
import { HousePlanner } from '../../modules/house-planner';
|
||||||
import {
|
import {
|
||||||
@ -71,12 +69,10 @@ import {
|
|||||||
ToolEvent,
|
ToolEvent,
|
||||||
Vec2,
|
Vec2,
|
||||||
} from '../../modules/house-planner/interfaces';
|
} from '../../modules/house-planner/interfaces';
|
||||||
import { LayerObjectType } from '../../modules/house-planner/types';
|
|
||||||
import deepUnref from '../../utils/deep-unref';
|
import deepUnref from '../../utils/deep-unref';
|
||||||
import { ToolbarTool } from './interfaces/toolbar.interfaces';
|
import { ToolbarTool } from './interfaces/toolbar.interfaces';
|
||||||
import PlannerLayerPanel from './PlannerLayerPanel.vue';
|
import PlannerLayerPanel from './PlannerLayerPanel.vue';
|
||||||
import PlannerPropertyPanel from './PlannerPropertyPanel.vue';
|
import PlannerPropertyPanel from './PlannerPropertyPanel.vue';
|
||||||
import PlannerSidebar from './PlannerSidebar.vue';
|
|
||||||
import PlannerSidebars from './PlannerSidebars.vue';
|
import PlannerSidebars from './PlannerSidebars.vue';
|
||||||
import PlannerTool from './PlannerTool.vue';
|
import PlannerTool from './PlannerTool.vue';
|
||||||
import PlannerToolbar from './PlannerToolbar.vue';
|
import PlannerToolbar from './PlannerToolbar.vue';
|
||||||
@ -149,7 +145,7 @@ const selectTool = (newTool?: string, newSubTool?: string) => {
|
|||||||
if (newTool === tool.value && !newSubTool) {
|
if (newTool === tool.value && !newSubTool) {
|
||||||
newTool = undefined;
|
newTool = undefined;
|
||||||
}
|
}
|
||||||
module.value.manager?.tools.setTool(newTool, newSubTool);
|
module.value.manager?.tools?.setTool(newTool, newSubTool);
|
||||||
};
|
};
|
||||||
|
|
||||||
const commitObjectName = (layerId: number, objectId: number, name: string) => {
|
const commitObjectName = (layerId: number, objectId: number, name: string) => {
|
||||||
@ -163,13 +159,13 @@ const commitLayerName = (layerId: number, name: string) => {
|
|||||||
const clickedOnObject = (layerId: number, objectId: number, add?: boolean) => {
|
const clickedOnObject = (layerId: number, objectId: number, add?: boolean) => {
|
||||||
const object = module.value.manager?.getLayerObjectById(layerId, objectId);
|
const object = module.value.manager?.getLayerObjectById(layerId, objectId);
|
||||||
if (!object) return;
|
if (!object) return;
|
||||||
module.value.manager?.tools.selectObject(object, add);
|
module.value.manager?.tools?.selectObject(object, add);
|
||||||
};
|
};
|
||||||
|
|
||||||
const clickedOnLayer = (layerId: number) => {
|
const clickedOnLayer = (layerId: number) => {
|
||||||
const layer = module.value.manager?.getLayerById(layerId);
|
const layer = module.value.manager?.getLayerById(layerId);
|
||||||
if (!layer) return;
|
if (!layer) return;
|
||||||
module.value.manager?.tools.selectLayer(layer);
|
module.value.manager?.tools?.selectLayer(layer);
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateObjectProperty = (
|
const updateObjectProperty = (
|
||||||
@ -188,7 +184,7 @@ onMounted(() => {
|
|||||||
canvas.value,
|
canvas.value,
|
||||||
deepUnref(serializedLayers.value),
|
deepUnref(serializedLayers.value),
|
||||||
[1920, 1080],
|
[1920, 1080],
|
||||||
canvasPos,
|
canvasPos.value,
|
||||||
canvasZoom.value
|
canvasZoom.value
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -204,7 +200,7 @@ onMounted(() => {
|
|||||||
subTool.value = e.detail.secondary as string;
|
subTool.value = e.detail.secondary as string;
|
||||||
},
|
},
|
||||||
'hpc:position': (e: CustomEvent<RepositionEvent>) => {
|
'hpc:position': (e: CustomEvent<RepositionEvent>) => {
|
||||||
//canvasPos.value = e.detail.position;
|
canvasPos.value = e.detail.position;
|
||||||
canvasZoom.value = e.detail.zoom;
|
canvasZoom.value = e.detail.zoom;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -3,21 +3,14 @@
|
|||||||
<div class="bg-white-50 flex h-full flex-col overflow-auto">
|
<div class="bg-white-50 flex h-full flex-col overflow-auto">
|
||||||
<template v-for="layer of layers">
|
<template v-for="layer of layers">
|
||||||
<button
|
<button
|
||||||
@dblclick="editingLayerName = layer.id"
|
@click="emit('selectLayer', layer.id)"
|
||||||
@click="!editingLayerName && emit('selectLayer', layer.id)"
|
|
||||||
:class="[
|
:class="[
|
||||||
layer.active ? 'bg-blue-50' : '',
|
layer.active ? 'bg-blue-50' : '',
|
||||||
'flex w-full flex-row items-center justify-start space-x-2 px-2 py-2',
|
'flex w-full flex-row items-center justify-start space-x-2 px-2 py-2',
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
<Square3Stack3DIcon class="h-4 w-4" />
|
<Square3Stack3DIcon class="h-4 w-4" />
|
||||||
<input
|
<span>{{ layer.name }}</span>
|
||||||
v-model="layer.name"
|
|
||||||
v-if="editingLayerName === layer.id"
|
|
||||||
@blur="commitLayerName(layer.name)"
|
|
||||||
@keypress.enter="commitLayerName(layer.name)"
|
|
||||||
/>
|
|
||||||
<span v-else>{{ layer.name }}</span>
|
|
||||||
</button>
|
</button>
|
||||||
<div class="flex flex-col bg-gray-50" v-if="layer.active">
|
<div class="flex flex-col bg-gray-50" v-if="layer.active">
|
||||||
<div v-for="object of layer.contents">
|
<div v-for="object of layer.contents">
|
||||||
@ -29,7 +22,10 @@
|
|||||||
emit('selectObject', layer.id, object.id, e.shiftKey)
|
emit('selectObject', layer.id, object.id, e.shiftKey)
|
||||||
"
|
"
|
||||||
:class="[
|
: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',
|
'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;
|
(e: 'selectObject', layer: number, object: number, add?: boolean): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const editingLayerName = ref<number | null>(null);
|
|
||||||
const editingObjectName = ref<number | null>(null);
|
const editingObjectName = ref<number | null>(null);
|
||||||
|
|
||||||
const commitObjectName = (layerId: number, name: string) => {
|
const commitObjectName = (layerId: number, name: string) => {
|
||||||
@ -89,10 +84,4 @@ const commitObjectName = (layerId: number, name: string) => {
|
|||||||
emit('objectName', layerId, editingObjectName.value, name);
|
emit('objectName', layerId, editingObjectName.value, name);
|
||||||
editingObjectName.value = null;
|
editingObjectName.value = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const commitLayerName = (name: string) => {
|
|
||||||
if (editingLayerName.value == null) return;
|
|
||||||
emit('layerName', editingLayerName.value, name);
|
|
||||||
editingLayerName.value = null;
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { clamp } from '@vueuse/core';
|
||||||
import type { Ref } from 'vue';
|
import type { Ref } from 'vue';
|
||||||
import { HousePlannerCanvasGrid } from './grid';
|
import { HousePlannerCanvasGrid } from './grid';
|
||||||
import {
|
import {
|
||||||
@ -19,24 +20,37 @@ import {
|
|||||||
vec2DivideScalar,
|
vec2DivideScalar,
|
||||||
vec2MultiplyScalar,
|
vec2MultiplyScalar,
|
||||||
vec2PointFromAngle,
|
vec2PointFromAngle,
|
||||||
|
vec2Snap,
|
||||||
vec2Sub,
|
vec2Sub,
|
||||||
} from './utils';
|
} from './utils';
|
||||||
|
|
||||||
export class HousePlannerCanvas {
|
export class HousePlannerCanvas {
|
||||||
public ctx!: CanvasRenderingContext2D;
|
public ctx!: CanvasRenderingContext2D;
|
||||||
public layers: Layer[] = [];
|
public layers: Layer[] = [];
|
||||||
public tools = new HousePlannerCanvasTools(this);
|
public tools?;
|
||||||
public grid = new HousePlannerCanvasGrid(this, 8);
|
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(
|
constructor(
|
||||||
public canvas: HTMLCanvasElement,
|
public canvas: HTMLCanvasElement,
|
||||||
public canvasDim: Vec2,
|
public canvasDim: Vec2,
|
||||||
public canvasPos: Ref<Vec2>,
|
public canvasPos: Vec2,
|
||||||
public canvasZoom = 1
|
public canvasZoom = 1,
|
||||||
|
public editable = true
|
||||||
) {
|
) {
|
||||||
this.ctx = this.canvas.getContext('2d')!;
|
this.ctx = this.canvas.getContext('2d')!;
|
||||||
this.setupEvents();
|
this.setupEvents();
|
||||||
this.resizeCanvas();
|
this.resizeCanvas();
|
||||||
|
if (editable) {
|
||||||
|
this.tools = new HousePlannerCanvasTools(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get width() {
|
get width() {
|
||||||
@ -60,7 +74,7 @@ export class HousePlannerCanvas {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cleanUp() {
|
cleanUp() {
|
||||||
this.tools.cleanUp();
|
this.tools?.cleanUp();
|
||||||
window.removeEventListener('keyup', this.boundKeyUpEvent);
|
window.removeEventListener('keyup', this.boundKeyUpEvent);
|
||||||
window.removeEventListener('keydown', this.boundKeyDownEvent);
|
window.removeEventListener('keydown', this.boundKeyDownEvent);
|
||||||
}
|
}
|
||||||
@ -68,13 +82,13 @@ export class HousePlannerCanvas {
|
|||||||
draw() {
|
draw() {
|
||||||
this.ctx.clearRect(0, 0, this.width, this.height);
|
this.ctx.clearRect(0, 0, this.width, this.height);
|
||||||
this.grid.draw();
|
this.grid.draw();
|
||||||
this.tools.drawHighlights();
|
this.tools?.drawHighlights();
|
||||||
for (const layer of this.layers) {
|
for (const layer of this.layers.slice().reverse()) {
|
||||||
if (!layer.visible) continue;
|
if (!layer.visible) continue;
|
||||||
if (!layer.contents?.length) continue;
|
if (!layer.contents?.length) continue;
|
||||||
this.drawLayer(layer);
|
this.drawLayer(layer);
|
||||||
}
|
}
|
||||||
this.tools.drawControls();
|
this.tools?.drawControls();
|
||||||
}
|
}
|
||||||
|
|
||||||
makeBezier(segment: BezierSegment, path: Path2D) {
|
makeBezier(segment: BezierSegment, path: Path2D) {
|
||||||
@ -184,7 +198,7 @@ export class HousePlannerCanvas {
|
|||||||
) {
|
) {
|
||||||
const object = this.getLayerObjectById(layerId, objectId);
|
const object = this.getLayerObjectById(layerId, objectId);
|
||||||
if (!object) return;
|
if (!object) return;
|
||||||
this.tools.history.appendToHistory(
|
this.tools?.history.appendToHistory(
|
||||||
Object.keys(properties).map((key) => ({
|
Object.keys(properties).map((key) => ({
|
||||||
object,
|
object,
|
||||||
property: key,
|
property: key,
|
||||||
@ -207,7 +221,7 @@ export class HousePlannerCanvas {
|
|||||||
) {
|
) {
|
||||||
const layer = this.getLayerById(layerId);
|
const layer = this.getLayerById(layerId);
|
||||||
if (!layer) return;
|
if (!layer) return;
|
||||||
this.tools.history.appendToHistory(
|
this.tools?.history.appendToHistory(
|
||||||
Object.keys(properties).map((key) => ({
|
Object.keys(properties).map((key) => ({
|
||||||
object: layer,
|
object: layer,
|
||||||
property: key,
|
property: key,
|
||||||
@ -226,7 +240,7 @@ export class HousePlannerCanvas {
|
|||||||
|
|
||||||
translateCanvas(offset: Vec2) {
|
translateCanvas(offset: Vec2) {
|
||||||
const realSize = vec2MultiplyScalar(this.canvasDim, this.canvasZoom);
|
const realSize = vec2MultiplyScalar(this.canvasDim, this.canvasZoom);
|
||||||
this.canvasPos.value = vec2Sub(this.canvasPos.value, offset);
|
this.canvasPos = vec2Sub(this.canvasPos, offset);
|
||||||
this.repositionEvent();
|
this.repositionEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -235,16 +249,24 @@ export class HousePlannerCanvas {
|
|||||||
this.repositionEvent();
|
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) {
|
private keyDownEvent(e: KeyboardEvent) {
|
||||||
if (e.target !== document.body && e.target != null) return;
|
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 boundKeyDownEvent = this.keyDownEvent.bind(this);
|
||||||
private keyUpEvent(e: KeyboardEvent) {
|
private keyUpEvent(e: KeyboardEvent) {
|
||||||
if (e.target !== document.body && e.target != null) return;
|
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);
|
private boundKeyUpEvent = this.keyUpEvent.bind(this);
|
||||||
|
|
||||||
@ -252,20 +274,16 @@ export class HousePlannerCanvas {
|
|||||||
window.addEventListener('keyup', this.boundKeyUpEvent);
|
window.addEventListener('keyup', this.boundKeyUpEvent);
|
||||||
window.addEventListener('keydown', this.boundKeyDownEvent);
|
window.addEventListener('keydown', this.boundKeyDownEvent);
|
||||||
|
|
||||||
this.canvas.addEventListener('mousemove', (e) => this.tools.onMouseMove(e));
|
this.canvas.addEventListener('mousemove', (e) => this.onMouseMove(e));
|
||||||
this.canvas.addEventListener('mousedown', (e) => this.tools.onMouseDown(e));
|
this.canvas.addEventListener('mousedown', (e) => this.onMouseDown(e));
|
||||||
this.canvas.addEventListener('mouseup', (e) => this.tools.onMouseUp(e));
|
this.canvas.addEventListener('mouseup', (e) => this.onMouseUp(e));
|
||||||
|
|
||||||
this.canvas.addEventListener('touchmove', (e) => this.tools.onTouchMove(e));
|
this.canvas.addEventListener('touchmove', (e) => this.onTouchMove(e));
|
||||||
this.canvas.addEventListener('touchstart', (e) =>
|
this.canvas.addEventListener('touchstart', (e) => this.onTouchStart(e));
|
||||||
this.tools.onTouchStart(e)
|
this.canvas.addEventListener('touchend', (e) => this.onTouchEnd(e));
|
||||||
);
|
this.canvas.addEventListener('wheel', (e) => this.onMouseWheel(e));
|
||||||
this.canvas.addEventListener('touchend', (e) => this.tools.onTouchEnd(e));
|
|
||||||
this.canvas.addEventListener('wheel', (e) => this.tools.onMouseWheel(e));
|
|
||||||
|
|
||||||
this.canvas.addEventListener('pointerleave', () =>
|
this.canvas.addEventListener('pointerleave', () => this.onPointerLeave());
|
||||||
this.tools.onPointerLeave()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private drawRoomText(line: Line) {
|
private drawRoomText(line: Line) {
|
||||||
@ -317,11 +335,156 @@ 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() {
|
private repositionEvent() {
|
||||||
this.canvas.dispatchEvent(
|
this.canvas.dispatchEvent(
|
||||||
new CustomEvent<RepositionEvent>('hpc:position', {
|
new CustomEvent<RepositionEvent>('hpc:position', {
|
||||||
detail: {
|
detail: {
|
||||||
position: this.canvasPos.value,
|
position: this.canvasPos,
|
||||||
zoom: this.canvasZoom,
|
zoom: this.canvasZoom,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -10,7 +10,7 @@ export class HousePlanner {
|
|||||||
canvas: HTMLCanvasElement,
|
canvas: HTMLCanvasElement,
|
||||||
initialData: Layer[],
|
initialData: Layer[],
|
||||||
canvasDim: Vec2,
|
canvasDim: Vec2,
|
||||||
canvasPos: Ref<Vec2>,
|
canvasPos: Vec2,
|
||||||
canvasZoom = 1,
|
canvasZoom = 1,
|
||||||
editable = true
|
editable = true
|
||||||
) {
|
) {
|
||||||
@ -19,14 +19,19 @@ export class HousePlanner {
|
|||||||
canvas,
|
canvas,
|
||||||
canvasDim,
|
canvasDim,
|
||||||
canvasPos,
|
canvasPos,
|
||||||
canvasZoom
|
canvasZoom,
|
||||||
|
editable
|
||||||
);
|
);
|
||||||
this.manager.layers = initialData;
|
this.manager.layers = initialData;
|
||||||
|
|
||||||
|
if (editable && this.manager.tools) {
|
||||||
this.manager.tools.selectLayer(
|
this.manager.tools.selectLayer(
|
||||||
initialData[initialData.findIndex((layer) => layer.active)]
|
initialData[initialData.findIndex((layer) => layer.active)]
|
||||||
);
|
);
|
||||||
this.manager.tools.setInitialSelection();
|
this.manager.tools.setInitialSelection();
|
||||||
this.manager.tools.setTool('move');
|
this.manager.tools.setTool('move');
|
||||||
|
}
|
||||||
|
|
||||||
this.manager.draw();
|
this.manager.draw();
|
||||||
return () => this.cleanUp();
|
return () => this.cleanUp();
|
||||||
}
|
}
|
||||||
|
@ -60,14 +60,17 @@ export interface RepositionEvent {
|
|||||||
zoom: number;
|
zoom: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ICanvasToolBase<U = undefined> {
|
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;
|
name: string;
|
||||||
subTool: U | undefined;
|
subTool: U | undefined;
|
||||||
drawHighlights(): void;
|
drawHighlights(): void;
|
||||||
drawControls(): void;
|
drawControls(): void;
|
||||||
mouseDown(targetObject?: LayerObject): void;
|
|
||||||
mouseMoved(mouse: Vec2, offset: Vec2, mouseAbsolute: Vec2): void;
|
|
||||||
mouseUp(moved: boolean): void;
|
|
||||||
enterPress(e: KeyboardEvent): void;
|
enterPress(e: KeyboardEvent): void;
|
||||||
escapePress(e: KeyboardEvent): void;
|
escapePress(e: KeyboardEvent): void;
|
||||||
setSubTool(subTool?: U): void;
|
setSubTool(subTool?: U): void;
|
||||||
|
@ -1,33 +1,21 @@
|
|||||||
import { clamp } from '@vueuse/core';
|
|
||||||
import type { HousePlannerCanvas } from './canvas';
|
import type { HousePlannerCanvas } from './canvas';
|
||||||
import { HousePlannerCanvasHistory } from './history';
|
import { HousePlannerCanvasHistory } from './history';
|
||||||
import {
|
import {
|
||||||
History,
|
History,
|
||||||
ICanvasToolBase,
|
ICanvasToolBase,
|
||||||
|
ICanvasToolMouseEvents,
|
||||||
Layer,
|
Layer,
|
||||||
LayerObject,
|
LayerObject,
|
||||||
Line,
|
Line,
|
||||||
RepositionEvent,
|
|
||||||
ToolEvent,
|
ToolEvent,
|
||||||
Vec2,
|
Vec2,
|
||||||
} from './interfaces';
|
} from './interfaces';
|
||||||
import { LineTool } from './tools/line';
|
import { LineTool } from './tools/line';
|
||||||
import { MoveTool } from './tools/move';
|
import { MoveTool } from './tools/move';
|
||||||
import {
|
|
||||||
vec2Add,
|
|
||||||
vec2Distance,
|
|
||||||
vec2DivideScalar,
|
|
||||||
vec2MultiplyScalar,
|
|
||||||
vec2Snap,
|
|
||||||
vec2Sub,
|
|
||||||
} from './utils';
|
|
||||||
|
|
||||||
export class HousePlannerCanvasTools {
|
export class HousePlannerCanvasTools implements ICanvasToolMouseEvents {
|
||||||
public selectedLayer?: Layer;
|
public selectedLayer?: Layer;
|
||||||
public selectedObjects: LayerObject[] = [];
|
public selectedObjects: LayerObject[] = [];
|
||||||
public mousePosition: Vec2 = [0, 0];
|
|
||||||
public mouseClickPosition: Vec2 = [0, 0];
|
|
||||||
public mousePositionSnapped: Vec2 = [0, 0];
|
|
||||||
public gridSnap = true;
|
public gridSnap = true;
|
||||||
public gridSnapScale = 8;
|
public gridSnapScale = 8;
|
||||||
public tool?: ICanvasToolBase<unknown>;
|
public tool?: ICanvasToolBase<unknown>;
|
||||||
@ -40,11 +28,6 @@ export class HousePlannerCanvasTools {
|
|||||||
public lastColor = '#000000';
|
public lastColor = '#000000';
|
||||||
public holdShift = false;
|
public holdShift = false;
|
||||||
public selectError = 16;
|
public selectError = 16;
|
||||||
private dragging = false;
|
|
||||||
private pinching = false;
|
|
||||||
private lastPinchLength = 0;
|
|
||||||
private moved = false;
|
|
||||||
private clickedOn: LayerObject | null = null;
|
|
||||||
|
|
||||||
constructor(public manager: HousePlannerCanvas) {}
|
constructor(public manager: HousePlannerCanvas) {}
|
||||||
|
|
||||||
@ -82,6 +65,8 @@ export class HousePlannerCanvasTools {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
this.selectedEvent();
|
||||||
|
this.manager.draw();
|
||||||
}
|
}
|
||||||
|
|
||||||
selectObject(object?: LayerObject | null, add = false) {
|
selectObject(object?: LayerObject | null, add = false) {
|
||||||
@ -142,7 +127,7 @@ export class HousePlannerCanvasTools {
|
|||||||
|
|
||||||
getMousedObject() {
|
getMousedObject() {
|
||||||
if (!this.selectedLayer?.visible) return null;
|
if (!this.selectedLayer?.visible) return null;
|
||||||
const [x, y] = this.mousePosition;
|
const [x, y] = this.manager.mousePosition;
|
||||||
for (const object of this.selectedLayer.contents) {
|
for (const object of this.selectedLayer.contents) {
|
||||||
if ((object as Line).segments) {
|
if ((object as Line).segments) {
|
||||||
const moused = this.manager.isOnLine(
|
const moused = this.manager.isOnLine(
|
||||||
@ -160,7 +145,7 @@ export class HousePlannerCanvasTools {
|
|||||||
|
|
||||||
getMousedLineSegment(line: Line) {
|
getMousedLineSegment(line: Line) {
|
||||||
if (!this.selectedLayer?.visible) return null;
|
if (!this.selectedLayer?.visible) return null;
|
||||||
const [x, y] = this.mousePosition;
|
const [x, y] = this.manager.mousePosition;
|
||||||
let lastSegment = null;
|
let lastSegment = null;
|
||||||
for (const segment of line.segments) {
|
for (const segment of line.segments) {
|
||||||
const lastPoint = lastSegment ? lastSegment.end : (segment.start as Vec2);
|
const lastPoint = lastSegment ? lastSegment.end : (segment.start as Vec2);
|
||||||
@ -242,116 +227,21 @@ export class HousePlannerCanvasTools {
|
|||||||
return this.tool?.name;
|
return this.tool?.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
onMouseMove(e: MouseEvent) {
|
mouseDown(targetObject?: LayerObject | undefined): void {
|
||||||
this.dragEvent(e.clientX, e.clientY);
|
this?.tool?.mouseDown(targetObject);
|
||||||
}
|
|
||||||
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) {
|
mouseMoved(mouse: Vec2, offset: Vec2, mouseAbsolute: Vec2): void {
|
||||||
ev.preventDefault();
|
this?.tool?.mouseMoved(mouse, offset, mouseAbsolute);
|
||||||
|
|
||||||
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);
|
mouseUp(moved: boolean): void {
|
||||||
}
|
this?.tool?.mouseUp(moved);
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
onMouseWheel(e: WheelEvent) {
|
|
||||||
// TODO: zoom
|
|
||||||
e.preventDefault();
|
|
||||||
this.realMousePos(e.clientX, e.clientY);
|
|
||||||
|
|
||||||
const scaleX =
|
|
||||||
(e.clientX - this.manager.canvasPos.value[0]) / this.manager.canvasZoom;
|
|
||||||
const scaleY =
|
|
||||||
(e.clientY - this.manager.canvasPos.value[1]) / this.manager.canvasZoom;
|
|
||||||
e.deltaY < 0
|
|
||||||
? (this.manager.canvasZoom *= 1.2)
|
|
||||||
: (this.manager.canvasZoom /= 1.2);
|
|
||||||
this.manager.canvasZoom = clamp(this.manager.canvasZoom, 0.1, 10);
|
|
||||||
this.manager.canvasPos.value = [
|
|
||||||
e.clientX - scaleX * this.manager.canvasZoom,
|
|
||||||
e.clientY - scaleY * this.manager.canvasZoom,
|
|
||||||
];
|
|
||||||
|
|
||||||
this.manager.canvasPos.value = [
|
|
||||||
clamp(
|
|
||||||
this.manager.canvasPos.value[0],
|
|
||||||
this.mousePosition[0] - this.manager.canvasDim[0],
|
|
||||||
this.mousePosition[0]
|
|
||||||
),
|
|
||||||
clamp(
|
|
||||||
this.manager.canvasPos.value[1],
|
|
||||||
this.mousePosition[1] - this.manager.canvasDim[1],
|
|
||||||
this.mousePosition[1]
|
|
||||||
),
|
|
||||||
];
|
|
||||||
|
|
||||||
this.canvas.dispatchEvent(
|
|
||||||
new CustomEvent<RepositionEvent>('hpc:position', {
|
|
||||||
detail: {
|
|
||||||
position: this.manager.canvasPos.value,
|
|
||||||
zoom: this.manager.canvasZoom,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
onPointerLeave() {
|
|
||||||
this.dragging = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onKeyDown(e: KeyboardEvent) {
|
onKeyDown(e: KeyboardEvent) {
|
||||||
if (e.key === 'z' && e.ctrlKey) {
|
if (e.key === 'z' && e.ctrlKey) {
|
||||||
|
e.preventDefault();
|
||||||
this.history.undo();
|
this.history.undo();
|
||||||
this.manager.draw();
|
this.manager.draw();
|
||||||
this.canvas.dispatchEvent(
|
this.canvas.dispatchEvent(
|
||||||
@ -369,6 +259,7 @@ export class HousePlannerCanvasTools {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (e.key === 'y' && e.ctrlKey) {
|
if (e.key === 'y' && e.ctrlKey) {
|
||||||
|
e.preventDefault();
|
||||||
this.history.redo();
|
this.history.redo();
|
||||||
this.manager.draw();
|
this.manager.draw();
|
||||||
this.canvas.dispatchEvent(
|
this.canvas.dispatchEvent(
|
||||||
@ -390,9 +281,12 @@ export class HousePlannerCanvasTools {
|
|||||||
|
|
||||||
onKeyUp(e: KeyboardEvent) {
|
onKeyUp(e: KeyboardEvent) {
|
||||||
if (e.key === 'Enter') {
|
if (e.key === 'Enter') {
|
||||||
|
e.preventDefault();
|
||||||
|
this.tool?.enterPress(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.key === 'Escape') {
|
if (e.key === 'Escape') {
|
||||||
|
e.preventDefault();
|
||||||
this.tool?.escapePress(e);
|
this.tool?.escapePress(e);
|
||||||
|
|
||||||
if (this.selectedObjects.length === 0 && this.tool?.isToolCancelable()) {
|
if (this.selectedObjects.length === 0 && this.tool?.isToolCancelable()) {
|
||||||
@ -403,14 +297,17 @@ export class HousePlannerCanvasTools {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (e.key === 'Shift') {
|
if (e.key === 'Shift') {
|
||||||
|
e.preventDefault();
|
||||||
this.holdShift = false;
|
this.holdShift = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.key === 'l') {
|
if (e.key === 'l') {
|
||||||
|
e.preventDefault();
|
||||||
this.setTool('line');
|
this.setTool('line');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.key === 'Delete' || e.key === 'x') {
|
if (e.key === 'Delete' || e.key === 'x') {
|
||||||
|
e.preventDefault();
|
||||||
this.deleteSelection();
|
this.deleteSelection();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -440,59 +337,6 @@ export class HousePlannerCanvasTools {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private pointerDown() {
|
|
||||||
this.clickedOn = this.getMousedObject();
|
|
||||||
this.tool?.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.tool?.mouseUp(this.moved);
|
|
||||||
|
|
||||||
this.dragging = false;
|
|
||||||
this.clickedOn = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private dragEvent(x: number, y: number) {
|
|
||||||
this.moved = true;
|
|
||||||
|
|
||||||
const currentPosAbsolute: Vec2 = [...this.mousePosition];
|
|
||||||
const currentPosSnapped: Vec2 = [
|
|
||||||
...(this.gridSnap ? this.mousePositionSnapped : this.mousePosition),
|
|
||||||
];
|
|
||||||
|
|
||||||
this.realMousePos(x, y);
|
|
||||||
let offset = vec2Sub(currentPosSnapped, this.mousePositionSnapped);
|
|
||||||
let offsetAbsolute = vec2Sub(currentPosAbsolute, this.mousePosition);
|
|
||||||
|
|
||||||
this.tool?.mouseMoved(
|
|
||||||
this.mousePositionSnapped,
|
|
||||||
offset,
|
|
||||||
this.mousePosition
|
|
||||||
);
|
|
||||||
|
|
||||||
if (this.dragging) {
|
|
||||||
const divided = vec2MultiplyScalar(
|
|
||||||
offsetAbsolute,
|
|
||||||
this.manager.canvasZoom
|
|
||||||
);
|
|
||||||
this.manager.translateCanvas(divided);
|
|
||||||
// Bias the offset
|
|
||||||
const [nx, ny] = vec2Add([x, y], divided);
|
|
||||||
this.realMousePos(nx, ny);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private selectedEvent() {
|
private selectedEvent() {
|
||||||
this.canvas.dispatchEvent(
|
this.canvas.dispatchEvent(
|
||||||
new CustomEvent('hpc:selectionchange', {
|
new CustomEvent('hpc:selectionchange', {
|
||||||
@ -500,14 +344,4 @@ export class HousePlannerCanvasTools {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private realMousePos(x: number, y: number) {
|
|
||||||
const rect = this.manager.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.gridSnap
|
|
||||||
? vec2Snap(this.mousePosition, this.gridSnapScale)
|
|
||||||
: this.mousePosition;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -56,7 +56,12 @@ export class MoveTool extends CanvasToolBase<undefined> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mouseUp(moved?: boolean): void {
|
mouseUp(moved?: boolean): void {
|
||||||
if (!this.handlingBezier && !this.handlingLine && !this.movingObject) {
|
if (
|
||||||
|
!this.handlingBezier &&
|
||||||
|
!this.handlingLine &&
|
||||||
|
!this.movingObject &&
|
||||||
|
!moved
|
||||||
|
) {
|
||||||
this.pick();
|
this.pick();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user