Spaces:
Running
Running
| <script lang="ts"> | |
| import { T } from "@threlte/core"; | |
| import { TransformControls, interactivity } from "@threlte/extras"; | |
| import type { Snippet } from "svelte"; | |
| import { Spring } from "svelte/motion"; | |
| import { useCursor } from "@threlte/extras"; | |
| import { setContext } from "svelte"; | |
| interface Props { | |
| content: Snippet<[{ isHighlighted: boolean }]>; // renderable | |
| enableEdit?: boolean; | |
| hoverColor?: string; | |
| defaultColor?: string; | |
| hoverOpacity?: number; | |
| } | |
| let { | |
| content, | |
| enableEdit = $bindable(true), | |
| hoverColor = "#ffa348", | |
| hoverOpacity = 0.5 | |
| }: Props = $props(); | |
| interactivity(); | |
| const scale = new Spring(1); | |
| // Position state to persist transforms | |
| let position = $state<[number, number, number]>([0, 0, 0]); | |
| let rotation = $state<[number, number, number]>([0, 0, 0]); | |
| // Hover state | |
| let isHovered = $state(false); | |
| let isSelected = $state(false); | |
| let isHighlighted = $derived(enableEdit && (isSelected || isHovered)); | |
| $effect(() => { | |
| // If isHighlighted is true, set the color to hoverColor and opacity to hoverOpacity | |
| if (isHighlighted) { | |
| scale.target = 1.05; | |
| } else { | |
| scale.target = 1; | |
| } | |
| }); | |
| const { onPointerEnter, onPointerLeave } = useCursor(); | |
| // Handle keyboard events for deselection | |
| $effect(() => { | |
| const handleKeyDown = (event: KeyboardEvent) => { | |
| if (event.key === "Escape" && isSelected) { | |
| isSelected = false; | |
| } | |
| }; | |
| if (isSelected) { | |
| document.addEventListener("keydown", handleKeyDown); | |
| return () => { | |
| document.removeEventListener("keydown", handleKeyDown); | |
| }; | |
| } | |
| }); | |
| // Handle transform changes | |
| const handleTransform = (ref: any) => { | |
| ref.updateMatrix(); | |
| // Update our position state from the object's current position | |
| position = ref.position.toArray(); | |
| rotation = ref.rotation.toArray(); | |
| }; | |
| </script> | |
| <T.Group | |
| {position} | |
| {rotation} | |
| onclick={() => { | |
| isSelected = true; | |
| }} | |
| onpointerenter={() => { | |
| onPointerEnter(); | |
| isHovered = true; | |
| }} | |
| onpointerleave={() => { | |
| onPointerLeave(); | |
| isHovered = false; | |
| }} | |
| scale={scale.current} | |
| > | |
| {#snippet children({ ref })} | |
| {@render content({ isHighlighted })} | |
| {#if isSelected && enableEdit} | |
| <TransformControls | |
| object={ref} | |
| mode="translate" | |
| showY={false} | |
| axis="XZ" | |
| space="world" | |
| on:objectChange={() => handleTransform(ref)} | |
| on:mouseUp={() => handleTransform(ref)} | |
| /> | |
| {/if} | |
| {/snippet} | |
| </T.Group> | |
| <!-- From https://github.com/brean/urdf-viewer --> | |