From f0155e5c6025238a34f3eefb7363773ee4f7ed9d Mon Sep 17 00:00:00 2001 From: Jonathan Olson Date: Thu, 28 Mar 2024 11:12:50 -0600 Subject: [PATCH] Adding Hotkey.allowOverlap flag (to allow "conflicts"/overlap of hotkeys), see https://github.com/phetsims/scenery/issues/1621 --- js/input/Hotkey.ts | 9 +++++++-- js/input/hotkeyManager.ts | 22 +++++++++++++--------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/js/input/Hotkey.ts b/js/input/Hotkey.ts index 552c92da2..933edba01 100644 --- a/js/input/Hotkey.ts +++ b/js/input/Hotkey.ts @@ -59,7 +59,9 @@ type SelfOptions = { // Fire continuously at this interval (milliseconds) fireOnHoldCustomInterval?: number; - // TODO: consider attach:false https://github.com/phetsims/scenery/issues/1621 + // For each main `key`, the hotkey system will only allow one hotkey with allowOverlap:false to be active at any time. + // This is provided to allow multiple hotkeys with the same keys to fire. Default is false. + allowOverlap?: boolean; }; export type HotkeyOptions = SelfOptions & EnabledComponentOptions; @@ -74,6 +76,7 @@ export default class Hotkey extends EnabledComponent { public readonly fireOnDown: boolean; public readonly fireOnHold: boolean; public readonly fireOnHoldTiming: HotkeyFireOnHoldTiming; + public readonly allowOverlap: boolean; // A Property that tracks whether the hotkey is currently pressed. // Will be true if it meets the following conditions: @@ -106,7 +109,8 @@ export default class Hotkey extends EnabledComponent { fireOnHold: false, fireOnHoldTiming: 'browser', fireOnHoldCustomDelay: 400, - fireOnHoldCustomInterval: 100 + fireOnHoldCustomInterval: 100, + allowOverlap: false }, providedOptions ); super( options ); @@ -119,6 +123,7 @@ export default class Hotkey extends EnabledComponent { this.fireOnDown = options.fireOnDown; this.fireOnHold = options.fireOnHold; this.fireOnHoldTiming = options.fireOnHoldTiming; + this.allowOverlap = options.allowOverlap; // Create a timer to handle the optional fire-on-hold feature. if ( this.fireOnHold && this.fireOnHoldTiming === 'custom' ) { diff --git a/js/input/hotkeyManager.ts b/js/input/hotkeyManager.ts index b025223e1..f5f2f5f72 100644 --- a/js/input/hotkeyManager.ts +++ b/js/input/hotkeyManager.ts @@ -184,12 +184,12 @@ class HotkeyManager { * 2. All modifier keys in the hotkey's modifierKeys pressed * 3. All modifier keys not in the hotkey's modifierKeys (but in the other hotkeys above) not pressed */ - private getHotkeyForMainKey( mainKey: EnglishKey ): Hotkey | null { + private getHotkeysForMainKey( mainKey: EnglishKey ): Hotkey[] { const englishKeysDown = this.englishKeysDownProperty.value; // If the main key isn't down, there's no way it could be active if ( !englishKeysDown.has( mainKey ) ) { - return null; + return []; } const compatibleKeys = [ ...this.enabledHotkeysProperty.value ].filter( hotkey => { @@ -205,9 +205,13 @@ class HotkeyManager { } ); } ); - assert && assert( compatibleKeys.length < 2, `Key conflict detected: ${compatibleKeys.map( hotkey => hotkey.getHotkeyString() )}` ); + if ( assert ) { + const conflictingKeys = compatibleKeys.filter( hotkey => !hotkey.allowOverlap ); - return compatibleKeys[ 0 ] ?? null; + assert && assert( conflictingKeys.length < 2, `Key conflict detected: ${conflictingKeys.map( hotkey => hotkey.getHotkeyString() )}` ); + } + + return compatibleKeys; } /** @@ -216,7 +220,7 @@ class HotkeyManager { */ private updateHotkeyStatus(): void { for ( const hotkey of this.enabledHotkeysProperty.value ) { - const shouldBeActive = this.getHotkeyForMainKey( hotkey.key ) === hotkey; + const shouldBeActive = this.getHotkeysForMainKey( hotkey.key ).includes( hotkey ); const isActive = this.activeHotkeys.has( hotkey ); if ( shouldBeActive && !isActive ) { @@ -271,8 +275,8 @@ class HotkeyManager { this.englishKeysDownProperty.value = new Set( [ ...this.englishKeysDownProperty.value, englishKey ] ); - const hotkey = this.getHotkeyForMainKey( englishKey ); - if ( hotkey ) { + const hotkeys = this.getHotkeysForMainKey( englishKey ); + for ( const hotkey of hotkeys ) { this.addActiveHotkey( hotkey, keyboardEvent, true ); } } @@ -282,8 +286,8 @@ class HotkeyManager { private onKeyUp( englishKey: EnglishKey, keyboardEvent: KeyboardEvent ): void { - const hotkey = this.getHotkeyForMainKey( englishKey ); - if ( hotkey ) { + const hotkeys = this.getHotkeysForMainKey( englishKey ); + for ( const hotkey of hotkeys ) { this.removeActiveHotkey( hotkey, keyboardEvent, true ); }