@@ -80,16 +80,25 @@ function PolygonEditor(props: {
8080 const [ pts , setPts ] = useState < [ number , number ] [ ] > (
8181 existing ?. type === 'polygon' ? existing . points . map ( ( p ) => [ p [ 0 ] , p [ 1 ] ] as [ number , number ] ) : [ ] ,
8282 ) ;
83+ const ptsRef = useRef ( pts ) ;
84+ ptsRef . current = pts ;
85+ const stageRef = useRef < HTMLDivElement > ( null ) ;
86+ // A vertex drag: which point, and whether it actually moved (so a plain click still
87+ // deletes it). Live-updates local state during the drag, commits ONE undo on release.
88+ const vdrag = useRef < { i : number ; moved : boolean } | null > ( null ) ;
8389
8490 const commit = ( next : [ number , number ] [ ] ) => { setPts ( next ) ; TilesetCommands . setTilePolygon ( tileId , next ) ; } ;
8591
86- const addPoint = ( e : ReactMouseEvent ) => {
87- const r = ( e . currentTarget as HTMLElement ) . getBoundingClientRect ( ) ;
88- const px = Math . round ( ( ( e . clientX - r . left ) / r . width ) * tw ) ;
89- const py = Math . round ( ( ( e . clientY - r . top ) / r . height ) * th ) ;
90- commit ( [ ...pts , [ Math . max ( 0 , Math . min ( tw , px ) ) , Math . max ( 0 , Math . min ( th , py ) ) ] ] ) ;
92+ /** Tile-space (0..tw, 0..th) point under the pointer, clamped to the tile. */
93+ const pointAt = ( clientX : number , clientY : number ) : [ number , number ] => {
94+ const r = stageRef . current ! . getBoundingClientRect ( ) ;
95+ const px = Math . round ( ( ( clientX - r . left ) / r . width ) * tw ) ;
96+ const py = Math . round ( ( ( clientY - r . top ) / r . height ) * th ) ;
97+ return [ Math . max ( 0 , Math . min ( tw , px ) ) , Math . max ( 0 , Math . min ( th , py ) ) ] ;
9198 } ;
9299
100+ const addPoint = ( e : ReactMouseEvent ) => { commit ( [ ...pts , pointAt ( e . clientX , e . clientY ) ] ) ; } ;
101+
93102 // Escape closes, like every other transient surface.
94103 useEffect ( ( ) => {
95104 const onKey = ( e : KeyboardEvent ) => {
@@ -109,6 +118,7 @@ function PolygonEditor(props: {
109118 < button type = "button" onClick = { onClose } > { t ( 'tile.done' ) } </ button >
110119 </ div >
111120 < div
121+ ref = { stageRef }
112122 className = "ts-pe-stage"
113123 style = { {
114124 width : SIZE , height : SIZE ,
@@ -125,7 +135,28 @@ function PolygonEditor(props: {
125135 { pts . map ( ( p , i ) => (
126136 < circle
127137 key = { i } cx = { p [ 0 ] } cy = { p [ 1 ] } r = { Math . max ( 1.2 , tw * 0.06 ) } className = "ts-pe-pt"
128- onClick = { ( e ) => { e . stopPropagation ( ) ; commit ( pts . filter ( ( _ , j ) => j !== i ) ) ; } }
138+ // Drag to move the vertex (live), click (no move) to remove it.
139+ onPointerDown = { ( e ) => {
140+ e . stopPropagation ( ) ;
141+ vdrag . current = { i, moved : false } ;
142+ try { ( e . target as Element ) . setPointerCapture ( e . pointerId ) ; } catch { /* capture is optional */ }
143+ } }
144+ onPointerMove = { ( e ) => {
145+ const d = vdrag . current ;
146+ if ( ! d || d . i !== i ) return ;
147+ d . moved = true ;
148+ const np = pointAt ( e . clientX , e . clientY ) ;
149+ setPts ( ( cur ) => cur . map ( ( q , j ) => ( j === i ? np : q ) ) ) ;
150+ } }
151+ onPointerUp = { ( e ) => {
152+ const d = vdrag . current ;
153+ vdrag . current = null ;
154+ try { ( e . target as Element ) . releasePointerCapture ( e . pointerId ) ; } catch { /* not captured */ }
155+ if ( ! d ) return ;
156+ if ( d . moved ) TilesetCommands . setTilePolygon ( tileId , ptsRef . current ) ;
157+ else commit ( ptsRef . current . filter ( ( _ , j ) => j !== i ) ) ;
158+ } }
159+ onClick = { ( e ) => e . stopPropagation ( ) }
129160 />
130161 ) ) }
131162 </ svg >
0 commit comments