Files
battle-caster-v1/src/components/Map/MapControl.vue
2024-11-24 17:34:41 +00:00

244 lines
6.9 KiB
Vue

<template>
<Dropzone
v-slot="{isDragOver}"
class="map-control"
:class="{
'map-control--over': isDragOver
}"
@dropFile="onDrop"
@mousewheel="onScroll"
@mousedown="onMouseDown"
>
<button @click="reset()">reset</button>
<select v-model="mode">
<option value="drag">drag</option>
<option value="draw">draw</option>
<option value="measure">measure</option>
</select>
<select
v-if="canvasDisplay?.mapLayer"
v-model="canvasDisplay.mapLayer.grid"
>
<option value="none">no grid</option>
<option value="light">light grid</option>
<option value="dark">dark grid</option>
</select>
<select
v-if="canvasDisplay?.mapLayer"
v-model="canvasDisplay.mapLayer.rotation"
>
<option :value="0">0°</option>
<option :value="1">90°</option>
<option :value="2">180°</option>
<option :value="3">270°</option>
</select>
<input
v-if="canvasDisplay?.mapLayer"
type="number"
v-model="canvasDisplay.mapLayer.mapWidthInInches"
/>
<div ref="rendererWrapper" v-show="canvasDisplay">
<canvas :width="selectedScreen?.imageSink?.resolution.width" :height="selectedScreen?.imageSink?.resolution.height" ref="canvas" style="max-width: 100%;" />
</div>
</Dropzone>
</template>
<script setup lang="ts">
import {computed, inject, onBeforeUnmount, onMounted, reactive, ref} from 'vue';
import Dropzone from '../Dropzone/Dropzone.vue';
import MapRenderer from './MapRenderer.vue';
import {MountedFsSymbol, RemoteDeviceManagerSymbol} from '../../model/injectionSymbols';
import {closerToZero, MapDrawable, MapImage} from './helpers';
import {VirtualFile} from '../../model/FileSystem/types';
import {CanvasDisplay, MapLayerDrawable} from './object';
import {Devices} from '../../model/Remote/RemoteDeviceManager';
const fs = inject(MountedFsSymbol)!;
const remoteManager = inject(RemoteDeviceManagerSymbol)!;
const screens = computed(() => {
return remoteManager.devices
.filter(device => device.hasImageSink)
})
const canvas = ref<HTMLCanvasElement>();
const selectedScreen = ref<Devices>();
const pointerVisible = ref(false);
let canvasDisplay: undefined | CanvasDisplay;
function selectScreen (screen: any) {
if (screen.imageSink?.display) {
selectedScreen.value = screen;
console.log('selectScreen!!!', screen.imageSink?.display);
canvasDisplay?.disconnectTarget(canvas.value!);
canvasDisplay = screen.imageSink?.display;
canvasDisplay?.connectTarget(canvas.value!);
}
}
async function onDrop(file: VirtualFile) {
console.log('onDrop!!', file);
onLoad(file);
}
const mode = ref<'drag' | 'draw' | 'measure'>('drag');
const dragging = ref(false);
function onScroll(event: WheelEvent) {
event.preventDefault();
// console.log('scroll', event.deltaY, event.offsetX, event.offsetY);
const delta = -1 * event.deltaY / 2000;
if (!canvasDisplay) {
return;
}
canvasDisplay.scale = Math.min(Math.max(0.1, canvasDisplay.scale + delta), 10);
if (canvas.value) {
if (delta > 0) {
const displayMousePosition = {
x: ((event.offsetX / canvas.value!.width) - 0.5) * 2 * canvasDisplay.width,
y: ((event.offsetY / canvas.value!.height) - 0.5) * 2 * canvasDisplay.height,
};
canvasDisplay.offsetX += displayMousePosition.x * delta;
canvasDisplay.offsetY += displayMousePosition.y * delta;
} else {
canvasDisplay.offsetX -= closerToZero((canvasDisplay.width * Math.abs(delta)) * Math.sign(canvasDisplay.offsetX), canvasDisplay.offsetX);
canvasDisplay.offsetY -= closerToZero((canvasDisplay.height * Math.abs(delta)) * Math.sign(canvasDisplay.offsetY), canvasDisplay.offsetY);
}
}
}
function showPointer(show: boolean) {
pointerVisible.value = show;
if (canvasDisplay && !show) {
canvasDisplay.distanceMeasurement.pointer = undefined;
}
}
function onMouseDown(event: MouseEvent) {
window.getSelection()?.removeAllRanges();
if (mode.value === 'drag') {
dragging.value = true;
}
if (mode.value === 'measure') {
if (canvasDisplay) {
const mouseScale = canvas.value!.clientWidth / canvasDisplay.width;
canvasDisplay.distanceMeasurement.start = {x: event.offsetX / mouseScale, y: event.offsetY / mouseScale}
}
}
window.addEventListener('mouseup', onMouseUp);
}
function onMouseUp(event: MouseEvent) {
dragging.value = false;
window.removeEventListener('mouseup', onMouseUp);
if (mode.value === 'measure' && canvasDisplay) {
canvasDisplay.distanceMeasurement.start = undefined;
}
}
function onMouseMove(event: MouseEvent) {
if (pointerVisible.value && canvasDisplay) {
const mouseScale = canvas.value!.clientWidth / canvasDisplay.width;
if (canvasDisplay) {
canvasDisplay.distanceMeasurement.pointer = {x: event.offsetX / mouseScale, y: event.offsetY / mouseScale};
}
}
if (event.buttons === 0) {
onMouseUp(event);
return;
}
if (!canvasDisplay) {
return;
}
const mouseScale = canvas.value!.clientWidth / canvasDisplay.width;
if (dragging.value) {
canvasDisplay.offsetX -= event.movementX / mouseScale;
canvasDisplay.offsetY -= event.movementY / mouseScale;
}
if (mode.value === 'measure') {
if (canvasDisplay) {
canvasDisplay.distanceMeasurement.end = {x: event.offsetX / mouseScale, y: event.offsetY / mouseScale};
}
}
}
function reset() {
if (!canvasDisplay) {
return;
}
canvasDisplay.offsetX = 0;
canvasDisplay.offsetY = 0;
canvasDisplay.scale = 1;
}
function onKeydown(event: KeyboardEvent) {
if (event.key === 'Control') {
showPointer(true);
}
}
function onKeyup(event: KeyboardEvent) {
if (event.key === 'Control') {
showPointer(false);
}
}
onMounted(() => {
onLoad();
canvasDisplay?.connectTarget(canvas.value!);
window.addEventListener('reveal-screen', (event: CustomEvent) => {
selectScreen(event.detail);
});
window.addEventListener('keydown', onKeydown);
window.addEventListener('keyup', onKeyup);
window.addEventListener('mousemove', onMouseMove);
});
onBeforeUnmount(() => {
canvasDisplay?.disconnectTarget(canvas.value!);
window.removeEventListener('keydown', onKeydown);
window.removeEventListener('keyup', onKeyup);
window.removeEventListener('mousemove', onMouseMove);
});
async function onLoad(file?: VirtualFile | File) {
const meta = file;
if (!canvasDisplay) {
return;
}
if (!(file instanceof File) && file?.type === 'file') {
file = await fs.download(file);
} else {
return;
}
console.log('onLoad', file, meta?.map?.width);
// @ts-ignore
const layer = new MapLayerDrawable(file, meta?.map?.width);
await layer.preloadFile();
canvasDisplay.mapLayer = layer;
};
</script>
<style lang="css">
.map-control {
display: inline-block;
border: 1px solid white;
background: white;
position: relative;
}
.map-control--over:before {
content: 'Drop here';
position: absolute;
inset: 10px;
border: 3px solid black;
}
</style>