"preview" rectangle

This commit is contained in:
Jörn-Michael Miehe 2023-01-16 23:40:25 +00:00
parent f0db5e57ec
commit e1bd10980c

View file

@ -1,9 +1,185 @@
<template> <template>
<img src="@/assets/adventskalender.png" /> <div id="container" ref="container">
<img id="background" ref="background" src="@/assets/adventskalender.png" />
<svg
id="drawpad"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 1000 1000"
preserveAspectRatio="none"
@mousedown="on_mousedown"
@mousemove="on_mousemove"
@mouseup="on_mouseup"
>
<rect
v-if="preview_state.visible"
id="preview"
:x="preview_rectangle.origin.x"
:y="preview_rectangle.origin.y"
:width="preview_rectangle.size.x"
:height="preview_rectangle.size.y"
/>
</svg>
</div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { Vue } from "vue-class-component"; import { Vue } from "vue-class-component";
export default class CalendarImage extends Vue {} class Vector2D {
</script> public x: number;
public y: number;
constructor(x = 0, y = 0) {
this.x = x;
this.y = y;
}
public copy(): Vector2D {
return new Vector2D(this.x, this.y);
}
public minus(other: Vector2D): Vector2D {
return new Vector2D(this.x - other.x, this.y - other.y);
}
}
class Rectangle {
private origin: Vector2D;
private size: Vector2D;
constructor(corner1: Vector2D, corner2: Vector2D) {
this.origin = corner1.copy();
this.size = corner2.minus(corner1);
}
public normalize() {
if (this.size.x < 0) {
this.size.x *= -1;
this.origin.x -= this.size.x;
}
if (this.size.y < 0) {
this.size.y *= -1;
this.origin.y -= this.size.y;
}
}
}
class PreviewState {
public visible = false;
private down_location = new Vector2D();
private move_location = new Vector2D();
public mouse_down(location: Vector2D) {
this.down_location = location;
this.move_location = location;
}
public mouse_move(location: Vector2D) {
this.move_location = location;
}
public get_rect(): Rectangle {
return new Rectangle(this.down_location, this.move_location);
}
}
function get_event_thous(event: MouseEvent): Vector2D {
if (event.currentTarget === null) {
return new Vector2D();
}
let target = event.currentTarget as Element;
return new Vector2D(
Math.round((event.offsetX / target.clientWidth) * 1000),
Math.round((event.offsetY / target.clientHeight) * 1000)
);
}
export default class CalendarImage extends Vue {
// "preview" rectangle on click-drag
private preview_state = new PreviewState();
private on_mousedown(event: MouseEvent) {
this.preview_state.visible = true;
this.preview_state.mouse_down(get_event_thous(event));
}
private on_mousemove(event: MouseEvent) {
this.preview_state.mouse_move(get_event_thous(event));
}
private on_mouseup() {
this.preview_state.visible = false;
}
private get preview_rectangle(): Rectangle {
let rect = this.preview_state.get_rect();
rect.normalize();
return rect;
}
// Hook "resize" events
private resize_observer?: ResizeObserver;
declare $refs: {
container: HTMLDivElement;
background: HTMLImageElement;
};
private on_resize() {
this.$refs.container.style.setProperty(
"height",
this.$refs.background.offsetHeight + "px"
);
}
public mounted() {
this.resize_observer = new ResizeObserver(this.on_resize);
this.resize_observer.observe(this.$refs.background);
}
public unmounted() {
if (this.resize_observer instanceof ResizeObserver) {
this.resize_observer.disconnect();
delete this.resize_observer;
}
}
}
</script>
<style lang="scss" scoped>
div#container {
position: relative;
max-height: 100%;
user-select: none;
img#background {
position: absolute;
z-index: 1;
width: 100%;
}
svg#drawpad {
position: absolute;
z-index: 2;
height: 100%;
width: 100%;
rect {
stroke-width: 1;
stroke-opacity: 0.9;
fill-opacity: 0.2;
&#preview {
fill: white;
stroke: red;
}
}
}
}
</style>