<script lang="ts"> import debounce from "debounce"; import { onMount, onDestroy } from "svelte"; import { get } from "svelte/store"; import { spring } from "svelte/motion"; import { getMenuTransform, setMenuTransform } from "./hudVarsManager"; import { showHud } from "../stores"; export let name: string; // This whole file is a mess, but it's a mess that works* let transform = getMenuTransform(name); let x = transform?.x ?? 50; let y = transform?.y ?? 50; let width = transform?.width ?? 200; let height = transform?.height ?? 200; let lastWidth = width; let lastHeight = height; let coordSpring = spring(moveInbounds( x, y ), { stiffness: 0.1, damping: 0.5 }); let coords = get(coordSpring); let returnX: number = coords.x; let returnY: number = coords.y; function onResize() { let coords = get(coordSpring); coordSpring.set(moveInbounds(coords.x, coords.y)); } function moveInbounds(x: number, y: number) { if(x < 0) x = 0; if(y < 0) y = 0; if(x + lastWidth > window.innerWidth) x = window.innerWidth - lastWidth; if(y + lastHeight > window.innerHeight) y = window.innerHeight - lastHeight; return { x, y }; } let transitioning = false; // move out of the way when the hud is hidden showHud.subscribe((v) => { if(v) { coordSpring.set(moveInbounds(returnX, returnY)); transitioning = false; } else { transitioning = true; let coords = get(coordSpring); // move over the nearest edge let cX = coords.x + lastWidth / 2; let cY = coords.y + lastHeight / 2; let endX = cX < window.innerWidth / 2 ? -lastWidth : window.innerWidth; let endY = cY < window.innerHeight / 2 ? -lastHeight : window.innerHeight; if(Math.abs(endX - coords.x) < Math.abs(endY - coords.y)) { coordSpring.set({ x: endX, y: coords.y }); } else { coordSpring.set({ x: coords.x, y: endY }); } } }) let element: HTMLDivElement; let minimized = transform?.minimized ?? false; let dragState = 'waiting'; let startX: number, startY: number; let dragDistance: number; function startDrag(event: MouseEvent) { dragState = 'checking'; const coords = get(coordSpring) startX = event.clientX - coords.x; startY = event.clientY - coords.y; dragDistance = 0; } function saveTransform() { if(transitioning) return; let coords = get(coordSpring); setMenuTransform(name, { x: coords.x, y: coords.y, width: lastWidth, height: lastHeight, minimized }) } const saveTransformDebounce = debounce(saveTransform, 100); coordSpring.subscribe(() => saveTransformDebounce()) function drag(event: MouseEvent) { if(dragState == 'waiting') return; dragDistance += Math.abs(event.movementX) + Math.abs(event.movementY); if(dragState == 'checking' && dragDistance > 5) { dragState = 'dragging'; return; } if(dragState == 'dragging') { let newX = event.clientX - startX; let newY = event.clientY - startY; returnX = newX; returnY = newY; coordSpring.set(moveInbounds(newX, newY)); saveTransformDebounce(); } } function stopDrag() { dragState = 'waiting'; } let observer = new ResizeObserver((entries) => { dragState = 'waiting'; let entry = entries[0]; if(!minimized) { lastHeight = entry.contentRect.height; } lastWidth = entry.contentRect.width; saveTransformDebounce(); }); onMount(() => { observer.observe(element); element.style.height = `${Math.max(height, 21)}px`; element.style.width = `${Math.max(width, 150)}px`; }); onDestroy(() => { observer.disconnect(); }); function toggleMinimized() { minimized = !minimized; saveTransformDebounce(); } </script> <svelte:window on:mouseup={ stopDrag } on:mousemove={ drag } on:resize={onResize} /> <!-- svelte-ignore a11y-no-static-element-interactions--> <div class="menu" class:minimized={ minimized } bind:this={ element } on:mousedown={ startDrag } style="left: { $coordSpring.x }px; top: { $coordSpring.y }px;"> <div class="header"> {name} <button class="minimize" on:click={toggleMinimized}> {minimized ? "+" : "-"} </button> </div> <div class="children"> <div class="groupContent open"> <slot /> </div> </div> </div> <style> .menu { position: absolute; background-color: var(--menuBackgroundColor); resize: both; overflow: hidden; min-width: 150px; border-radius: 5px; user-select: none; pointer-events: all; outline-width: 3px; outline-style: solid; outline-color: var(--menuOutlineColor); } .children { position: relative; height: calc(100% - 21px); overflow-x: hidden; overflow-y: auto; } :global(.groupContent) { position: absolute; top: 0; left: 0; display: flex; flex-direction: column; width: 100%; } .groupContent { transform: translateX(0); opacity: 1; pointer-events: all; } .menu.minimized { overflow: hidden; resize: horizontal; height: 21px !important; } .header { background-color: var(--menuHeaderBackgroundColor); position: relative; color: var(--menuHeaderTextColor); width: 100%; text-align: center; font-size: 14px; height: 21px; } .minimize { background-color: transparent; border: none; align-items: center; position: absolute; right: 5px; top: 0; cursor: pointer; } </style>