@@ -14,6 +14,16 @@ class InputHandler {
1414 this . onRender = onRender ;
1515 this . getPositionFn = getPositionFn ;
1616
17+ // Live mode: sink for classified non-camera events forwarded to the host.
18+ this . _eventSink = null ;
19+ this . onGestureEnd = null ;
20+ this . rotateSensitivity = 0.5 ;
21+
22+ this . _downButton = 0 ;
23+ this . _downX = 0 ;
24+ this . _downY = 0 ;
25+ this . _moved = false ;
26+
1727 this . _isRotating = false ;
1828 this . _isPanning = false ;
1929
@@ -30,6 +40,7 @@ class InputHandler {
3040 this . _onPointerDown = this . _onPointerDown . bind ( this ) ;
3141 this . _onPointerUp = this . _onPointerUp . bind ( this ) ;
3242 this . _onPointerMove = this . _onPointerMove . bind ( this ) ;
43+ this . _onPointerLeave = this . _onPointerLeave . bind ( this ) ;
3344 this . _onWheel = this . _onWheel . bind ( this ) ;
3445 this . _onDblClick = this . _onDblClick . bind ( this ) ;
3546 this . _onContextMenu = ( ev ) => ev . preventDefault ( ) ;
@@ -40,6 +51,7 @@ class InputHandler {
4051 canvas . addEventListener ( 'pointerdown' , this . _onPointerDown ) ;
4152 canvas . addEventListener ( 'pointerup' , this . _onPointerUp ) ;
4253 canvas . addEventListener ( 'pointermove' , this . _onPointerMove ) ;
54+ canvas . addEventListener ( 'pointerleave' , this . _onPointerLeave ) ;
4355 canvas . addEventListener ( 'wheel' , this . _onWheel , { passive : false } ) ;
4456 canvas . addEventListener ( 'dblclick' , this . _onDblClick ) ;
4557 canvas . addEventListener ( 'contextmenu' , this . _onContextMenu ) ;
@@ -49,21 +61,66 @@ class InputHandler {
4961 canvas . addEventListener ( 'touchcancel' , this . _onTouchEnd ) ;
5062 }
5163
64+ setEventSink ( fn ) {
65+ this . _eventSink = fn ;
66+ }
67+
68+ _forward ( type , ev ) {
69+ if ( ! this . _eventSink ) return ;
70+ const rect = this . canvas . getBoundingClientRect ( ) ;
71+ const dpr = window . devicePixelRatio || 1 ;
72+ const payload = {
73+ type,
74+ button : ev . button == null ? 0 : ev . button ,
75+ buttons : ev . buttons == null ? 0 : ev . buttons ,
76+ x : ev . clientX ,
77+ y : ev . clientY ,
78+ canvasX : Math . round ( ( ev . clientX - rect . left ) * dpr ) ,
79+ canvasY : Math . round ( ( ev . clientY - rect . top ) * dpr ) ,
80+ movementX : ev . movementX || 0 ,
81+ movementY : ev . movementY || 0 ,
82+ deltaX : ev . deltaX || 0 ,
83+ deltaY : ev . deltaY || 0 ,
84+ ctrlKey : ! ! ev . ctrlKey ,
85+ shiftKey : ! ! ev . shiftKey ,
86+ altKey : ! ! ev . altKey ,
87+ } ;
88+ try {
89+ this . _eventSink ( payload ) ;
90+ } catch ( e ) {
91+ console . warn ( '[input] event sink failed:' , e && ( e . message || e ) ) ;
92+ }
93+ }
94+
5295 // --- Pointer events (mouse & single touch fallback) ---
5396
5497 _onPointerDown ( ev ) {
5598 ev . preventDefault ( ) ;
56- if ( ev . button === 0 && ! ev . shiftKey && ! ev . ctrlKey && ! ev . altKey ) {
99+ this . _downButton = ev . button ;
100+ this . _downX = ev . clientX ;
101+ this . _downY = ev . clientY ;
102+ this . _moved = false ;
103+ // ctrl/alt are reserved for the host and never start a camera gesture.
104+ const hostModified = ev . ctrlKey || ev . altKey ;
105+ if ( ! hostModified && ev . button === 0 && ! ev . shiftKey ) {
57106 this . _isRotating = true ;
58- } else if ( ev . button === 1 || ( ev . button === 0 && ev . shiftKey ) ) {
107+ } else if ( ! hostModified && ( ev . button === 1 || ( ev . button === 0 && ev . shiftKey ) ) ) {
59108 this . _isPanning = true ;
60109 }
61110 this . canvas . setPointerCapture ( ev . pointerId ) ;
62111 }
63112
64113 _onPointerUp ( ev ) {
114+ const wasGesture = this . _isRotating || this . _isPanning ;
65115 this . _isRotating = false ;
66116 this . _isPanning = false ;
117+ // A press with no movement is a click; camera gestures set _moved.
118+ if ( this . _eventSink && ! this . _moved && ev . button === this . _downButton ) {
119+ this . _forward ( 'click' , ev ) ;
120+ }
121+ if ( wasGesture && this . _moved && this . onGestureEnd ) {
122+ try { this . onGestureEnd ( ) ; } catch ( e ) { /* ignore */ }
123+ }
67124 }
68125
69126 _onPointerMove ( ev ) {
@@ -78,25 +135,48 @@ class InputHandler {
78135 const dpr = window . devicePixelRatio || 1 ;
79136 const t = this . camera . transform ;
80137 if ( this . _isRotating ) {
81- t . rotate ( 0.3 * dpr * ev . movementY , 0.3 * dpr * ev . movementX ) ;
138+ this . _moved = true ;
139+ const s = this . rotateSensitivity * dpr ;
140+ t . rotate ( s * ev . movementY , s * ev . movementX ) ;
82141 this . camera . _notify ( ) ;
83142 this . onRender ( ) ;
84- } else if ( this . _isPanning ) {
143+ return ;
144+ }
145+ if ( this . _isPanning ) {
146+ this . _moved = true ;
85147 t . translate ( 0.01 * dpr * ev . movementX , - 0.01 * dpr * ev . movementY ) ;
86148 this . camera . _notify ( ) ;
87149 this . onRender ( ) ;
150+ return ;
88151 }
152+ // Not a camera gesture — forward to the host (hover, or a modified drag).
153+ if ( ! this . _eventSink ) return ;
154+ if ( ev . buttons !== 0 ) {
155+ this . _moved = true ;
156+ this . _forward ( 'drag' , ev ) ;
157+ } else {
158+ this . _forward ( 'mousemove' , ev ) ;
159+ }
160+ }
161+
162+ _onPointerLeave ( ev ) {
163+ if ( this . _eventSink ) this . _forward ( 'mouseout' , ev ) ;
89164 }
90165
91166 _onWheel ( ev ) {
92167 ev . preventDefault ( ) ;
168+ if ( this . _eventSink && ( ev . ctrlKey || ev . altKey ) ) {
169+ this . _forward ( 'wheel' , ev ) ;
170+ return ;
171+ }
93172 const t = this . camera . transform ;
94173 t . scale ( 1 - ev . deltaY / 1000 , t . _center ) ;
95174 this . camera . _notify ( ) ;
96175 this . onRender ( ) ;
97176 }
98177
99178 _onDblClick ( ev ) {
179+ if ( this . _eventSink ) this . _forward ( 'dblclick' , ev ) ;
100180 if ( ! this . getPositionFn ) return ;
101181 const rect = this . canvas . getBoundingClientRect ( ) ;
102182 const x = ev . clientX - rect . left ;
@@ -219,6 +299,7 @@ class InputHandler {
219299 c . removeEventListener ( 'pointerdown' , this . _onPointerDown ) ;
220300 c . removeEventListener ( 'pointerup' , this . _onPointerUp ) ;
221301 c . removeEventListener ( 'pointermove' , this . _onPointerMove ) ;
302+ c . removeEventListener ( 'pointerleave' , this . _onPointerLeave ) ;
222303 c . removeEventListener ( 'wheel' , this . _onWheel ) ;
223304 c . removeEventListener ( 'dblclick' , this . _onDblClick ) ;
224305 c . removeEventListener ( 'contextmenu' , this . _onContextMenu ) ;
0 commit comments