<!--
    Represent a point on the screen that can optionally be moved by the user.
-->

<script>
    import { onMount, onDestroy, createEventDispatcher } from 'svelte';

    /** Horizontal position of the point. */
    export let x = 0;

    /** Vertical position of the point. */
    export let y = 0;

    /** Radius of the circle that represents the point. */
    export let size = 15;

    /** Whether the user is allowed to move the point. */
    export let movable = true;

    // Reference to the <div> element that represents the point
    let element = null;

    // Bounds of the parent node, computed only once when the user
    // starts dragging
    let bounds;

    // Whether the user is currently dragging the point
    let dragging = false;

    let dispatch = createEventDispatcher();

    const start = evt =>
    {
        if (!movable) return;

        bounds = element.parentNode.getBoundingClientRect();
        dragging = true;

        window.addEventListener('mousemove', move);
        window.addEventListener('touchmove', move);
        window.addEventListener('mouseup', end);
        window.addEventListener('touchend', end);
    };

    const clamp = (value, min, max) => Math.max(min, Math.min(max, value));

    const move = evt =>
    {
        const [userX, userY] = evt.changedTouches
            ? [evt.changedTouches[0].clientX, evt.changedTouches[0].clientY]
            : [evt.clientX, evt.clientY];

        dispatch('move', {
            x: clamp(userX - bounds.left, 0, bounds.width),
            y: clamp(userY - bounds.top, 0, bounds.height),
            shift: evt.shiftKey
        });
    };

    const end = evt =>
    {
        dragging = false;
        window.removeEventListener('mousemove', move);
        window.removeEventListener('touchmove', move);
        window.removeEventListener('mouseup', end);
        window.removeEventListener('touchend', end);
    };

    onMount(() =>
    {
        element.addEventListener('mousedown', start);
        element.addEventListener('touchstart', start);
    });

    onDestroy(() =>
    {
        element.removeEventListener('mousedown', start);
        element.removeEventListener('touchstart', start);
        window.removeEventListener('mousemove', move);
        window.removeEventListener('touchmove', move);
        window.removeEventListener('mouseup', end);
        window.removeEventListener('touchend', end);
    });
</script>

<style>
    div
    {
        display: block;
        position: absolute;
        border-radius: 100%;
        background: #333;

        box-shadow: 0 2px 2px rgba(0, 0, 0, 0.1);
        transition:
            box-shadow .15s var(--easing),
            transform .15s var(--easing),
            opacity .15s var(--easing);
    }

    div.movable
    {
        cursor: pointer;
    }

    div.movable.dragging
    {
        box-shadow: 0 5px 5px rgba(0, 0, 0, 0.2);
        transform: scale(1.5);
    }
</style>

<div
    style="width: {size}px; height: {size}px; left: {x - size / 2}px; top: {y - size / 2}px;"
    class:dragging
    class:movable
    draggable="false"
    bind:this={element}
/>
