Closed jessegreenberg closed 6 months ago
I started a new class to brainstorm using KeyboardListener with KeyboardDragListener. I don't think this class will be used directly but its helping think thorugh the changes needed in KeyboardDragListener.
This approach uses KeyboardListener + CallbackTimer. There is some complexity around how to move when new keys are pressed while using dragDelta. Im saving this patch here because I want to try out a different method for that part.
A new patch with some testing notes. This now supports most of the options of KeyboardDragListener. But I am trying out a new strategy for hotkeys. I think it would be best for KeyboardDragListener to no longer support hotkeys. If hotkeys are needed, just add a new KeyboardListener. The tricky part of this is to prevent dragging functionality if the hotkey overlaps with one of the keys used for dragging. ('j+w' does this in BASE). This patch gets around this by having the client declare a 'preventDefaultKeys' list that prevents dragging if any of those keys are down. Notes in this patch describe some other things I considered but that did not work.
Some notes from a discussion with @zepumph and @jonathanolson:
Big take away:
Since keyboard events happen infrequently (much less frequently than mouse/touch events), this is not as performance intensive. We can inspect a (pruned) scene graph every event to get information about current keyboard listeners. From this, we should be able to derive the assertions we need as well as prioritizing listeners and letting one handle/abort others.
Edit: Patch with progress on this. THis is actually working quite well (as long as performance is accetpable). I need to switch to an emergent task but will come back to this:
Scratch with a new KeyboardDragListener
Important notes from this change set:
1) areKeysDownWithoutExtraModifierKeys
can be removed now with this "deferred" strategy. That was our old method of preventing behavior when different combinations of modifier keys were down.
2) Should array utility functions like findDuplicates
and isArraySubset
live anywhere else?
3) Look at usages of canNodeReceivePDOMInput. Better way to do that with the trail?
4) I am worried about performance. Looking for a way to remove the scan every keydown event. But a new listener might be added in response to the keypress? Or what if in a key press a Node supports PDOM input? Here is a patch with this idea, but I don't think it would work. Note that this is the SECOND full scene graph traversal now, we already traverse every keydown event to dispatch global events.
4.5) What if scenery only dispatched one keydown event on the first press? Deviate from DOM behavior, but that should work fine for our KeyboardListener and KeyStateTracker.
5) Is there a way to prune the scene graph walk further? LIke a way to determine if any descendants have listeners?
Some notes from reviewing with JO and MK:
"deferred" is confusing, many will think of this as a delay instead of preventing behavior.
Have a specific set of modifiers, and any extras will fire.
One thought: Push back on design: The set of standard system modifier keys should be specified for a hotkey. Where if you press more or fewer, they do not fire. This is backed by a lot of user intuition.
What if we could dynamically define modifier keys. So if any extra modifier keys are pressed, it would not fire the callback.
Maybe we have an option that could 'ignore custom modifier' keys, and then listeners would still fire if we need to.
Please don't put number keys as modifier keys.
We never ignore "Modifier Keys". This way they have a consistent definition and behavior.
Consider a HotkeyListener that extends KeyboardListener. KeyboardListener is more general and covers whatever you might need for keyboard input. HotkeyListener is more specific and could include the complexity described here.
For performance, avoid closures in global scans. Use Set instead of array.
// TODO: Do you need a PDOM instance to receive global keydown events? Is that how it works, what do we want?
// We are worried about KeyboardListeners not being fired because global dispatch uses visual trails.
// You could potentially look at the accessible instance tree? This could simplify things a lot.
// Would require that (global) KeyboardListener are only added to Nodes with a tagName
.
// We would need a way to get the "effective" children with accessible content.
// We have this in 'getEffectiveChildren'.
// Consider adding a pdomAudit assertion that makes sure that all nodes with a KeyboardListener also have a tag name.
// TODO: We want a better understanding of how the system works. For example, when do we use visual vs PDOM trails?
Some terminology:
"Modifier Keys" should only be standard browser modifier keys (alt, shift, ctrl, meta)
"Modifier Keys" change the behavior of the key. If you include a modifier key, it should prevent the firing behavior of a key. For example, "ctrl+t" will NOT fire a listener on "t".
"Hotkey" - A combination of keys that must be held down with a final key that triggers the behavior. It is possible that a hotkey has only one key.
"Custom Modifier Key" - You can opt in to having a custom letter behaving like a modifier key. For example add "J" for "j+w". Adding a new modifier key would be done at a global level for all KeyboardListeners.
Latest takeaways from the above discussion:
First implementation from notes above in https://github.com/phetsims/scenery/commit/a4c22d0c594fb0d624dcd732e67fc7c08fb322cc. I found that I did need a way to opt out of this new "Modifier key" behavior for KeyboardDragListener for SOME keys, but still want it for other keys. For example, I needed the KeyboardDragListener to keep firing when 'shift' was pressed, but it should NOT fire when Custom Modifier Key was added.
This is just an initial commit, more thinking and testing to be done.
I tried to write unit tests to make sure that KeyboardListener overlap problems were caught. But my tests did not work. I ran into a problem where using window.dispatchEvent
fires the event synchronously, and creates a new stack for the try/catch. And so it was impossible to verify assertions from KeyboardListener in response to events. I need to bail on this but here is my patch:
We want to try a totally different approach. This is now on hold for #1621.
A new system for keyboard input was added in https://github.com/phetsims/scenery/issues/1621 and is working well. KeyboardListener was re-implemented to use Hotkey, and KeyboardDragListener was re-implemented to use KeyboardListener.
KeyboardListener also now has a createGlobal
, which is how we will add global hotkeys in PhET sims.
Local testing looks OK and CT has been happy with the changes. Closing.
Reopening because there is a TODO marked for this issue.
Thanks phet-dev, issue link has been removed. Closing.
A next step from https://github.com/phetsims/scenery/issues/1520. Would be great to re-implement KeyboardDragListener with KeyboardListener. Biggest benefit will be that KeyboardDragListener uses the same API as KeyboardListener for hotkeys (they are different at the moment).