17cupsofcoffee / tetra

🎮 A simple 2D game framework written in Rust
MIT License
907 stars 63 forks source link

Add iterators to enumerate all keys and key labels #293

Closed sumibi-yakitori closed 2 years ago

sumibi-yakitori commented 2 years ago

I am remapping the CapsLock key to the Control key. If I are remapping by OS, before #277, I could detect all logical control key presses by using tetra::input::is_key_modifier_down(ctx, KeyModifier::Ctrl)

277 Afterwards, I can use tetra::input::get_key_with_label to check the physical key after remapping, but this method can only return a single key due to SDL2 specification.

Therefore, there is no way to detect when multiple physical keys are mapped to a single logical key.

This PR adds iterators to Tetra that enumerates all items, and the following code solves this problem.

use tetra::input;
let is_ctrl_key_down = input::Key::all()
  .filter_map(|key| {
    input::get_key_label(ctx, key)
      .filter(|label| {
        *label == input::KeyLabel::LeftCtrl || *label == input::KeyLabel::RightCtrl
      })
      .map(|_| key)
  })
  .any(|key| input::is_key_down(ctx, key));
17cupsofcoffee commented 2 years ago

I hadn't considered this use case, good catch! I think there's several questions here:

Should KeyModifier work like it used to?

I'm not sure if KeyModifier being position-based instead of layout-based makes sense, now that I think about it!

If you are asking 'is Ctrl down?', you probably are interested in whether the player has any keys labelled with Ctrl held down, not whether they have one of two particular physical keys down. When using position-based controls, you're much more likely to want to treat the left and right Ctrl as completely seperate bindings.

I think I may change how KeyModifier works under the hood to properly handle this use case (either by making it map to the layout as it used to, or querying the SDL key modifier state), which would fix your immediate issue 🙂

How do we handle multiple physical keys mapping to a single logical key?

It's a shame that SDL doesn't support this better natively. Mapping every possible key at runtime would work, but I do wonder if there might be a more elegant solution... will give that some thought.

Do we want a way of iterating over all keys?

This seems like it could be handy even if we do go with a different approach for the above stuff. Not sure how I feel about bringing in strum for this - I'll need to familiarize myself with that crate a bit more.

sumibi-yakitori commented 2 years ago

Should KeyModifier work like it used to?

Yes. If it's only about is_key_modifier_down, I think the same behavior as before is preferable. Or maybe add a flag to switch back to the pre-#277 behavior for keyboard input.

How do we handle multiple physical keys mapping to a single logical key?

A more elegant implementation would be desirable, but given that Tetra is a game engine, I don't think there are that many requests for elegant handling of this issue 🤣

So I'd be happy to just have a temporary option for developers like me who are trying to make tools that use Modifier keys a lot using Tetra.

For reference, there are cases like Dual-role keys in Modifier key - Wikipedia

Do we want a way of iterating over all keys?

I also develop a software KVM switch using other crates that handle keyboard input, and every time I think about it, It is very inconvenient that there are often no published iterators that can enumerate all the items in an enum that handle keys. But it doesn't have to be an implementation using strum 😄

17cupsofcoffee commented 2 years ago

:+1: I will try to have a look at fixing the modifier keys this week - I don't think the current behaviour is intuitive, given that the main use case for KeyModifier is to implement keyboard shortcuts.

EDIT: Now that I think about it, it could be a good idea to copy what SDL2 and GLFW do and make key modifiers into a bitmask, so you can easily check stuff like 'is Ctrl+Alt` down?' Would be a breaking change though, so will save that for 0.7 :)