@@ -126,6 +126,14 @@ export class WarshipMultiSelectionEvent implements GameEvent {
126126 constructor ( public readonly units : UnitView [ ] ) { }
127127}
128128
129+ /** Emitted when a touch long-press is detected (shows crosshair indicator) */
130+ export class TouchLongPressStartEvent implements GameEvent {
131+ constructor (
132+ public readonly x : number ,
133+ public readonly y : number ,
134+ ) { }
135+ }
136+
129137export class ShowBuildMenuEvent implements GameEvent {
130138 constructor (
131139 public readonly x : number ,
@@ -197,6 +205,11 @@ export class InputHandler {
197205 // Warship selection box state
198206 private selectionBoxActive : boolean = false ;
199207
208+ // Touch long-press state
209+ private longPressTimer : ReturnType < typeof setTimeout > | null = null ;
210+ private longPressActive : boolean = false ;
211+ private readonly LONG_PRESS_MS = 800 ;
212+
200213 private moveInterval : NodeJS . Timeout | null = null ;
201214 private activeKeys = new Set < string > ( ) ;
202215 private keybinds : Record < string , string > = { } ;
@@ -256,6 +269,11 @@ export class InputHandler {
256269 }
257270 this . pointerDown = false ;
258271 this . pointers . clear ( ) ;
272+ if ( this . longPressTimer !== null ) {
273+ clearTimeout ( this . longPressTimer ) ;
274+ this . longPressTimer = null ;
275+ }
276+ this . longPressActive = false ;
259277 this . canvas . style . cursor = "" ;
260278 } ) ;
261279 this . pointers . clear ( ) ;
@@ -528,6 +546,22 @@ export class InputHandler {
528546 this . lastPointerDownY = event . clientY ;
529547
530548 this . eventBus . emit ( new MouseDownEvent ( event . clientX , event . clientY ) ) ;
549+
550+ // Start long-press timer for touch devices
551+ if ( event . pointerType === "touch" ) {
552+ this . longPressActive = false ;
553+ this . longPressTimer = setTimeout ( ( ) => {
554+ this . longPressTimer = null ;
555+ this . longPressActive = true ;
556+ this . canvas . style . cursor = "crosshair" ;
557+ this . eventBus . emit (
558+ new TouchLongPressStartEvent (
559+ this . lastPointerDownX ,
560+ this . lastPointerDownY ,
561+ ) ,
562+ ) ;
563+ } , this . LONG_PRESS_MS ) ;
564+ }
531565 } else if ( this . pointers . size === 2 ) {
532566 this . lastPinchDistance = this . getPinchDistance ( ) ;
533567 }
@@ -545,6 +579,17 @@ export class InputHandler {
545579 this . pointerDown = false ;
546580 this . pointers . clear ( ) ;
547581
582+ // Clean up long-press state
583+ if ( this . longPressTimer !== null ) {
584+ clearTimeout ( this . longPressTimer ) ;
585+ this . longPressTimer = null ;
586+ }
587+ const wasLongPress = this . longPressActive ;
588+ this . longPressActive = false ;
589+ if ( wasLongPress ) {
590+ this . canvas . style . cursor = "" ;
591+ }
592+
548593 // Complete selection box if it was active
549594 if ( this . selectionBoxActive ) {
550595 this . selectionBoxActive = false ;
@@ -656,8 +701,19 @@ export class InputHandler {
656701 const deltaX = event . clientX - this . lastPointerX ;
657702 const deltaY = event . clientY - this . lastPointerY ;
658703
659- // If shift is held, draw selection box instead of panning
660- if ( this . activeKeys . has ( this . keybinds . shiftKey ) ) {
704+ // Cancel long-press if finger moved significantly before timer fires
705+ if ( this . longPressTimer !== null ) {
706+ const moveDist =
707+ Math . abs ( event . clientX - this . lastPointerDownX ) +
708+ Math . abs ( event . clientY - this . lastPointerDownY ) ;
709+ if ( moveDist >= 10 ) {
710+ clearTimeout ( this . longPressTimer ) ;
711+ this . longPressTimer = null ;
712+ }
713+ }
714+
715+ // If shift is held OR touch long-press is active, draw selection box
716+ if ( this . activeKeys . has ( this . keybinds . shiftKey ) || this . longPressActive ) {
661717 this . selectionBoxActive = true ;
662718 this . eventBus . emit (
663719 new WarshipSelectionBoxUpdateEvent (
0 commit comments