Provide onKeyStateChange and onModifierStateChange in Component

Component provides bool keyPressed(const KeyPress& k) and bool keyStateChanged(bool isKeyDown)

This is very 50%/lukewarm support for keyboard input. The former triggers upon repeats, and the latter fails to provide WHICH key changed state. Who wants system-generated repeats? And who wants to know that a key-state changed but not which one?

In short, there’s no simple way to get Key-Up/Down events.

It’s frustrating that keyStateChanged doesn’t provide the key that changed state.

I’d like to see bool keyStateChanged(const KeyPress& key, bool isKeyDown) that fires exactly ONCE for EACH keyDown or keyUp event (current impl. documents that this is not currently guaranteed) .

Adding a duration_ms param sig would be nice too. People are making digital musical instruments and the duration of a keypress could be used. It would be 0 for keyDown events.

This would be non-breaking as it’s a different sig from bool keyStateChanged(bool isKeyDown).

Currently I have to resort to something like this:

struct KeyUpDpwn 
    : public Component
{
    KeyUpDpwn(bool setFocus=false) {
        if(setFocus) {
            setWantsKeyboardFocus(true);
            Timer::callAfterDelay (100, [&] { grabKeyboardFocus(); });
        }
    }

    std::set<juce_wchar> keysDown;

    void keyDown(juce_wchar ch) {
        DBG("KeyDown" << ch);
    }

    void keyUp(juce_wchar ch) {
        DBG("KeyUp" << ch);
    }

    bool keyPressed(const KeyPress& k) final {
        juce_wchar ch = k.getTextCharacter();
        if(keysDown.count(ch) == 0) {
            keysDown.insert(ch);
            keyDown(ch);
        }
        bool consumeEvent = false;
        return consumeEvent;
    }

    bool keyStateChanged(bool isKeyDown) final {
        if(! isKeyDown) {
            std::vector<juce_wchar> keysUp;
            for(auto ch : keysDown)
                if(! KeyPress::isKeyCurrentlyDown(ch)) 
                    keysUp.push_back(ch);
            for(auto ch : keysUp) {
                keysDown.erase(ch);
                keyUp(ch);
            }
        }
        bool consumeEvent = false;
        return consumeEvent;
    }

It’s horrible! But can anyone do better?

An interface might look like this:

struct MyComponent: Component, MyKeyListener {
    MyComponent() {
        addKeyListener(this);
    }

  void keyChange(const KeyPress& key, bool isDown) override {

… or:

struct MyComponent: Component
{
  MyComponent()
  {
    auto onKeyPress = (const KeyPress& key, bool isDown) {
       //do stuff...
    }    

    addKeyListener(&keyListener);
    myKeyListener.onKeyPress = onKeyPress;
  }

  MyKeyListener keyListener;
};

But any dev wanting key up/down events is gona have to reinvent a wheel that should surely already be on the truck. Developers want to maximally focus on the WHAT (they want to do) and be free of the HOW.

This would give good bang for buck, I think.

I did flag this 7 years ago: Keyboard repeat - #9 by _pi

On the same subject, could we also have void onModifierChange(uint16_t oldModifierFlags, uint16_t newModifierFlags)?

Maybe std::bitset<8> instead of uint16_t … that would mean we didn’t have to bit-hack.

My thanks to the moderator (EyalAmir) on TheAudioProgrammer Discord #juce channel for taking the time to scrutinize the proposal.

Here’s my modified key-handler.

#include <chrono>
double now_ms() {
    using namespace std::chrono;
    uint64_t t = duration_cast<milliseconds>(
        system_clock::now().time_since_epoch()
    ).count();
    return (double) t;
}

double now_s() {
    return now_ms() / 1000;
}

struct RootPane 
    : public Component
{
    string keys = "dbrnmvszhlkt 123456789";
    std::map<char, bool> keyStates;
    std::map<char, double> tDown;

    RootPane()
    {
        for(char ch : keys) {
            keyStates[ch] = KeyPress::isKeyCurrentlyDown(ch);
            tDown[ch] = now_s();
        }
    }

    bool keyStateChanged(bool isKeyDown) final
    {
        for (auto const& [key, state] : keyStates) {
            bool newState = KeyPress::isKeyCurrentlyDown(key);
            if(newState != state) {
                keyStates[key] = newState;
                keyStateChanged(key, isKeyDown, now_s() - tLast[key]);
                tLast[key] = now_s();
            }
        }
        return /* consumeEvent = */ false;
    }

    void keyStateChanged(juce_wchar ch, bool isDown, double secondsSinceLastChange) {
        :
    }
}

What’s awkward is that I have to preload all the keys I am expecting.
Is there a way to loop through all the possible keys?

Here’s a handler for modifier keys:

    enum Modifiers { SHIFT, CTRL, OPT, CMD };

    std::bitset<4> modifierFlags = 0;
    void modifierKeysChanged(const ModifierKeys& modifiers) final {
        std::bitset<4> newFlags = modifiers.getRawFlags();
        auto flagsChanged = newFlags ^ modifierFlags;
        for(int i=0; i<4; ++i)
            if(flagsChanged[i])
                modifierChanged(i, newFlags[i]);
        modifierFlags = newFlags;
    }

    void modifierChanged(int index, bool isDown) {
        DBG("Modifier[" << index << "] " << (isDown ? "DOWN" : "UP"));
    }