|
<script lang="ts">
|
|
import { createEventDispatcher } from "svelte";
|
|
import Handle from "./Handle.svelte";
|
|
import { type EditorContext } from "../ImageEditor.svelte";
|
|
import { clamp } from "../utils/pixi";
|
|
import { resize_and_reposition } from "./crop";
|
|
|
|
export let editor_box: EditorContext["editor_box"];
|
|
export let crop_constraint: number | null;
|
|
|
|
const dispatch = createEventDispatcher<{
|
|
crop_start: {
|
|
x: number;
|
|
y: number;
|
|
width: number;
|
|
height: number;
|
|
};
|
|
crop_continue: {
|
|
x: number;
|
|
y: number;
|
|
width: number;
|
|
height: number;
|
|
};
|
|
crop_end: {
|
|
x: number;
|
|
y: number;
|
|
width: number;
|
|
height: number;
|
|
};
|
|
}>();
|
|
|
|
let _height = $editor_box.child_height;
|
|
let _width = $editor_box.child_width;
|
|
let _top = $editor_box.child_top - $editor_box.parent_top;
|
|
let _left = $editor_box.child_left - $editor_box.parent_left;
|
|
let _right = $editor_box.child_right - $editor_box.parent_left;
|
|
let _bottom = 0;
|
|
|
|
let dragging = false;
|
|
|
|
export let w_p: number;
|
|
export let h_p: number;
|
|
export let l_p: number;
|
|
export let t_p: number;
|
|
|
|
const positions = ["tl", "br", "tr", "bl", "t", "b", "l", "r"] as const;
|
|
|
|
let finished: boolean;
|
|
|
|
let timer: NodeJS.Timeout;
|
|
let triggered = false;
|
|
|
|
$: {
|
|
if (dragging || position_drag) {
|
|
clearTimeout(timer);
|
|
|
|
finished = false;
|
|
} else {
|
|
set_finished_with_timeout_and_delay();
|
|
}
|
|
}
|
|
|
|
function set_finished_with_timeout_and_delay(): void {
|
|
if (timer) {
|
|
clearTimeout(timer);
|
|
}
|
|
timer = setTimeout(() => {
|
|
if (triggered) {
|
|
dispatch("crop_end", {
|
|
x: l_p,
|
|
y: t_p,
|
|
width: w_p,
|
|
height: h_p
|
|
});
|
|
triggered = false;
|
|
}
|
|
|
|
finished = true;
|
|
position_drag = false;
|
|
}, 1000);
|
|
}
|
|
|
|
function handle_change(
|
|
{
|
|
top,
|
|
bottom,
|
|
left,
|
|
right
|
|
}: {
|
|
top: number | undefined;
|
|
bottom: number | undefined;
|
|
left: number | undefined;
|
|
right: number | undefined;
|
|
},
|
|
position: (typeof positions)[number]
|
|
): void {
|
|
_top = clamp(
|
|
top ? top - $editor_box.parent_top : _top,
|
|
$editor_box.child_top - $editor_box.parent_top,
|
|
$editor_box.child_bottom - $editor_box.parent_top
|
|
);
|
|
|
|
_left = clamp(
|
|
left ? left - $editor_box.parent_left : _left,
|
|
$editor_box.child_left - $editor_box.parent_left,
|
|
$editor_box.child_right - $editor_box.parent_left
|
|
);
|
|
_right = clamp(
|
|
right ? right - $editor_box.parent_left : _right,
|
|
$editor_box.child_left - $editor_box.parent_left,
|
|
$editor_box.child_right - $editor_box.parent_left
|
|
);
|
|
_bottom = clamp(
|
|
bottom ? bottom - $editor_box.parent_top : _bottom,
|
|
$editor_box.child_top - $editor_box.parent_top,
|
|
$editor_box.child_bottom - $editor_box.parent_top
|
|
);
|
|
_width = clamp(
|
|
right ? right - _left - $editor_box.parent_left : _width,
|
|
0,
|
|
_right - _left
|
|
);
|
|
_width = clamp(
|
|
left ? _right - left + $editor_box.parent_left : _width,
|
|
0,
|
|
_right - _left
|
|
);
|
|
_height = clamp(
|
|
bottom ? bottom - _top - $editor_box.parent_top : _height,
|
|
0,
|
|
_bottom - _top
|
|
);
|
|
_height = clamp(
|
|
top ? _bottom - top + $editor_box.parent_top : _height,
|
|
0,
|
|
_bottom - _top
|
|
);
|
|
|
|
const anchors_for_position = {
|
|
tl: "br",
|
|
tr: "bl",
|
|
bl: "tr",
|
|
br: "tl",
|
|
t: "b",
|
|
b: "t",
|
|
l: "r",
|
|
r: "l",
|
|
c: "c"
|
|
} as const;
|
|
|
|
if (crop_constraint) {
|
|
const max_w = ["t", "b"].includes(position)
|
|
? $editor_box.child_width
|
|
: _right - _left;
|
|
const max_h = ["l", "r"].includes(position)
|
|
? $editor_box.child_height
|
|
: _bottom - _top;
|
|
let result = resize_and_reposition(
|
|
_width,
|
|
_height,
|
|
anchors_for_position[position],
|
|
crop_constraint,
|
|
max_w,
|
|
max_h
|
|
);
|
|
|
|
_width = result.new_width;
|
|
_height = result.new_height;
|
|
_left = _left + result.x_offset;
|
|
_top = _top + result.y_offset;
|
|
_right = _left + _width;
|
|
_bottom = _top + _height;
|
|
}
|
|
|
|
w_p = _width / $editor_box.child_width;
|
|
h_p = _height / $editor_box.child_height;
|
|
l_p =
|
|
(_left - $editor_box.child_left + $editor_box.parent_left) /
|
|
$editor_box.child_width;
|
|
t_p =
|
|
(_top - $editor_box.child_top + $editor_box.parent_top) /
|
|
$editor_box.child_height;
|
|
|
|
dispatch(triggered ? "crop_continue" : "crop_start", {
|
|
x: l_p,
|
|
y: t_p,
|
|
width: w_p,
|
|
height: h_p
|
|
});
|
|
|
|
triggered = true;
|
|
}
|
|
|
|
function resize(): void {
|
|
_width = w_p * $editor_box.child_width;
|
|
_height = h_p * $editor_box.child_height;
|
|
_left =
|
|
l_p * $editor_box.child_width +
|
|
($editor_box.child_left - $editor_box.parent_left);
|
|
_top =
|
|
t_p * $editor_box.child_height +
|
|
($editor_box.child_top - $editor_box.parent_top);
|
|
_right = _left + _width;
|
|
_bottom = _top + _height;
|
|
}
|
|
|
|
$: $editor_box && resize();
|
|
|
|
let start_x = 0;
|
|
let start_y = 0;
|
|
let position_drag = false;
|
|
function handle_drag_start(e: MouseEvent): void {
|
|
position_drag = true;
|
|
|
|
start_x = e.clientX;
|
|
start_y = e.clientY;
|
|
|
|
if (!finished) return;
|
|
finished = false;
|
|
dispatch("crop_start", {
|
|
x: l_p,
|
|
y: t_p,
|
|
width: w_p,
|
|
height: h_p
|
|
});
|
|
}
|
|
|
|
function handle_drag_end(e: MouseEvent): void {
|
|
if (!position_drag) return;
|
|
|
|
position_drag = false;
|
|
}
|
|
|
|
function handle_dragging(e: MouseEvent): void {
|
|
if (!position_drag) return;
|
|
|
|
const x_delta = e.clientX - start_x;
|
|
const y_delta = e.clientY - start_y;
|
|
|
|
_left = clamp(
|
|
_left + x_delta,
|
|
$editor_box.child_left - $editor_box.parent_left,
|
|
$editor_box.child_right - $editor_box.parent_left - _width
|
|
);
|
|
|
|
_top = clamp(
|
|
_top + y_delta,
|
|
$editor_box.child_top - $editor_box.parent_top,
|
|
$editor_box.child_bottom - $editor_box.parent_top - _height
|
|
);
|
|
|
|
_right = _left + _width;
|
|
_bottom = _top + _height;
|
|
|
|
l_p =
|
|
(_left - $editor_box.child_left + $editor_box.parent_left) /
|
|
$editor_box.child_width;
|
|
t_p =
|
|
(_top - $editor_box.child_top + $editor_box.parent_top) /
|
|
$editor_box.child_height;
|
|
|
|
start_x = e.clientX;
|
|
start_y = e.clientY;
|
|
dispatch("crop_continue", {
|
|
x: l_p,
|
|
y: t_p,
|
|
width: w_p,
|
|
height: h_p
|
|
});
|
|
|
|
triggered = true;
|
|
}
|
|
</script>
|
|
|
|
<svelte:window on:mousemove={handle_dragging} on:mouseup={handle_drag_end} />
|
|
|
|
<div class="wrap">
|
|
<div
|
|
class="box"
|
|
style:width="{_width}px"
|
|
style:height="{_height}px"
|
|
style:top="{_top}px"
|
|
style:left="{_left}px"
|
|
>
|
|
{#each positions as position}
|
|
<Handle
|
|
on:change={({ detail }) => handle_change(detail, position)}
|
|
bind:dragging
|
|
location={position}
|
|
/>
|
|
{/each}
|
|
|
|
|
|
<div class="grid" class:finished on:mousedown={handle_drag_start}>
|
|
{#each { length: 25 } as _}
|
|
<div></div>
|
|
{/each}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<style>
|
|
.grid {
|
|
height: 100%;
|
|
width: 100%;
|
|
display: grid;
|
|
grid-template-rows: 1fr 1px 1fr 1px 1fr;
|
|
grid-template-columns: 1fr 1px 1fr 1px 1fr;
|
|
overflow: hidden;
|
|
transition: 0.2s;
|
|
opacity: 1;
|
|
/* pointer-events: none; */
|
|
}
|
|
|
|
.grid.finished {
|
|
opacity: 0;
|
|
}
|
|
|
|
.grid > div {
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
|
|
.grid > div:nth-of-type(even) {
|
|
background: black;
|
|
opacity: 0.5;
|
|
}
|
|
|
|
.wrap {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
|
|
/* transform: translateY(-20px); */
|
|
}
|
|
|
|
.box {
|
|
position: absolute;
|
|
width: 100%;
|
|
height: 100%;
|
|
border: 1px solid black;
|
|
z-index: var(--layer-top);
|
|
}
|
|
</style>
|
|
|