Rezonality / zep

Zep - An embeddable editor, with optional support for using vim keystrokes.
Other
922 stars 70 forks source link

Using Zep with "international" keyboard (key code remapping?) #71

Closed totalgee closed 2 years ago

totalgee commented 3 years ago

Describe the bug I'm trying to use Zep with ImGui, inside the Cinder framework (which has its own keymapping but internally uses GLFW). My keyboard is Spanish, which means that some keys (notably {}[]|@#~\) are accessed differently than on an standard US keyboard. On a Spanish keyboard, these require pressing the AltGr key before pressing another key to get the desired character (e.g. bracket/brace). I've managed to handle these keys, by writing a keypress handler (in Cinder) that remaps/translates input keyboard events into simpler events for ImGui. For example, pressing AltGr+´ should give me an open brace {, but in Zep it thinks control was pressed, since AltGr behaves like Ctrl+Alt. So I remapped things in this case:

    auto& io = ImGui::GetIO();
    io.AddInputCharacter('{');
    io.KeyCtrl = io.KeyAlt = false;

And that seems to work...in my program, in the Zep editor, I get a { inserted (if I don't modify KeyCtrl, then I get a <C+{> inserted). But I'm still having problems with other keys (ones that don't require AltGr), such as + or ^. These don't require modifier keys but are different key codes than a US keyboard.

I notice you have special handling for SDL (ref: ZEP_USE_SDK in editor_imgui.h) and I'm wondering if I might need something similar to handle my (Cinder/GLFW) codes. I'm looking through the code but am not very familiar with it yet...can you give me any pointers on how I should be approaching this? i.e. how to fix it in the "correct" way? Thanks!

Desktop (please complete the following information):

totalgee commented 3 years ago

BTW, the problem (e.g. with the + key on my Spanish keyboard) is that it gets into the "Check USB Keys" case (in ZepEditor_ImGui::HandleInput()) with a value of 61 (0x3d which is defined as ZEP_KEY_F4 in editor_imgui.h). It seems to think that Function key is pressed, when in fact it's [code: 61, char: 43 aka '+'].

If I comment out the MapUSBKeys case (or set the map to be empty), it works much better...I can enter characters such as +*^~\|@ without problem. What is that remapping code supposed to do? Are those the codes for the function keys on an en-US keyboard?

meshula commented 3 years ago

Hope this helps -

I wrote a mapping function for the embedding I did into Sokol, basically I filtered the keys zep uses in a straightforward way and otherwise pass them through. If you're using Cinder/glfw, your situation is probably more like mine, and less like the bundled Dear ImGui sample.

static uint32_t sappZepKey(int c)
{
    switch (c)
    {
    case SAPP_KEYCODE_TAB: return Zep::ExtKeys::TAB;
    case SAPP_KEYCODE_ESCAPE: return Zep::ExtKeys::ESCAPE;
    case SAPP_KEYCODE_ENTER: return Zep::ExtKeys::RETURN;
    case SAPP_KEYCODE_DELETE: return Zep::ExtKeys::DEL;
    case SAPP_KEYCODE_HOME: return Zep::ExtKeys::HOME;
    case SAPP_KEYCODE_END: return Zep::ExtKeys::END;
    case SAPP_KEYCODE_BACKSPACE: return Zep::ExtKeys::BACKSPACE;
    case SAPP_KEYCODE_RIGHT: return Zep::ExtKeys::RIGHT;
    case SAPP_KEYCODE_LEFT: return Zep::ExtKeys::LEFT;
    case SAPP_KEYCODE_UP: return Zep::ExtKeys::UP;
    case SAPP_KEYCODE_DOWN: return Zep::ExtKeys::DOWN;
    case SAPP_KEYCODE_PAGE_DOWN: return Zep::ExtKeys::PAGEDOWN;
    case SAPP_KEYCODE_PAGE_UP: return Zep::ExtKeys::PAGEUP;
    case SAPP_KEYCODE_F1: return Zep::ExtKeys::F1;
    case SAPP_KEYCODE_F2: return Zep::ExtKeys::F2;
    case SAPP_KEYCODE_F3: return Zep::ExtKeys::F3;
    case SAPP_KEYCODE_F4: return Zep::ExtKeys::F4;
    case SAPP_KEYCODE_F5: return Zep::ExtKeys::F5;
    case SAPP_KEYCODE_F6: return Zep::ExtKeys::F6;
    case SAPP_KEYCODE_F7: return Zep::ExtKeys::F7;
    case SAPP_KEYCODE_F8: return Zep::ExtKeys::F8;
    case SAPP_KEYCODE_F9: return Zep::ExtKeys::F9;
    case SAPP_KEYCODE_F10: return Zep::ExtKeys::F10;
    case SAPP_KEYCODE_F11: return Zep::ExtKeys::F11;
    case SAPP_KEYCODE_F12: return Zep::ExtKeys::F12;
    default: return c;
    }
}

elsewhere, I construct zep's key codes manually. This routine is called from my key handler in the case that zep has focus.

extern "C" void LabZep_input_sokol_key(LabZep*, int sapp_key, bool shift, bool ctrl)
{
    uint32_t test_key = sappZepKey(sapp_key);
    if (test_key < 32) {
        uint32_t key = shift ? Zep::ModifierKey::Shift : (ctrl ? Zep::ModifierKey::Ctrl : 0);
        zi.keys.push_back(key | test_key);
    }
    else {
        zi.keys.push_back(sapp_key);
    }
}

and then ultimately process the keys in my event processing handler. This handler is called after other systems (such as Dear ImGui) have had a chance to deal with the events in case they have focus. I found it easier to treat mouse and keyboard together this way because it minimized complexity with focus handling elsewhere in my app.

extern "C" void LabZep_process_events(LabZep* zep, 
    float mouse_x, float mouse_y,
    bool lmb_clicked, bool lmb_released, bool rmb_clicked, bool rmb_released)
{
    const Zep::ZepBuffer& buffer = zep->spEditor->GetActiveTabWindow()->GetActiveWindow()->GetBuffer();
    zep->spEditor->HandleMouseInput(buffer,
        mouse_x, mouse_y, lmb_clicked, lmb_released,
        rmb_clicked, rmb_released);

    while (!zi.keys.empty()) {
        uint32_t key = zi.keys.front();
        zi.keys.pop_front();

        zep->spEditor->HandleKeyInput(buffer,
            key & 0xff,
            key & (Zep::ModifierKey::Ctrl << 8),
            key & (Zep::ModifierKey::Shift << 8));
    }
}
cmaughan commented 3 years ago

Hi, thanks for the report. I haven't looked at the key code for a while, but I'll have a look. Key mapping is often a tricky thing, and as @meshula says, you probably have to remap things yourself. Zep's input boils down to passing the right thing into pMode->AddKeyPress. So you can put any layer you need to before making that call. There is also a Qt example, if you want another sample to look at. @meshula What is sokol?

totalgee commented 3 years ago

Thanks @meshula and @cmaughan, that should be enough to get me going... I'll try this in the next few days, sounds like it should be easy enough to handle by building the appropriate events directly.

I'm excited about having vim bindings in my live coding environment, Zep looks like a great project. I may try submitting some more bindings in the future, like % (jump between braces/parens) would be nice, or ci) (change in parentheses)...

meshula commented 3 years ago

Sokol is a very lightweight system wrapper, similar in scope to glfw, but with infinitely less code. It targets dx/metal/gl so covers my needs pretty thoroughly with less fuss. https://github.com/floooh/sokol

Here's a sokol/zep/microui wrapper I made https://github.com/meshula/LabFont ... It might be a little bit of a hack, but I wanted to see what I could come up with as the lightest possible integration of zep into another app, which is this interface: https://github.com/meshula/LabFont/blob/main/LabZep.h

totalgee commented 3 years ago

Hi -- on the way to solving this... Now I'm trying to figure out how to identify when the Zep editor in my ImGui application has "focus". I only want to forward keyboard events to it when it is. When using ImGui (e.g. when using ImGui::InputTextMultiline() as a simple editor instead of Zep, I can just check if the editor widget is "active" by calling active = ImGui::IsItemActive(); immediately after the call to InputTextMultiline(). I want to know if Zep has focus, because right now, if I click on other text fields in my UI (to type values into them), they receive characters as does the Zep editor itself...which is obviously not good.

totalgee commented 3 years ago

[...] Now I'm trying to figure out how to identify when the Zep editor in my ImGui application has "focus". [...]

I was close -- I discovered I can use: ImGui::IsAnyItemActive() (note the word Any in there)-- if it's false (no ImGui widget is focused to receive input), then I can pass keyboard events to Zep. So far, that seems to work pretty well, and solves the "double entry" issue when ImGui widgets are focused...

In my old version where the editor used pure ImGui, I had to click on the editor widget to give it focus for editing, but with the Zep editor it now always has "focus" when no other widget does...which is a change, but actually not bad behaviour.

meshula commented 3 years ago

Another option is to copy the code for one of the text input widgets (I think I started from a simple input field), and using the same logic the original text input code uses for mouse and keyboard, aggregate all the events for zep within that widget. Then you get all the focus logic for free, more or less.

cmaughan commented 3 years ago

IIRC, one mechanism I've used on the ImGui side is to create an invisible button, the size of Zep.

meshula commented 3 years ago

That's the time honored approach, now that you mention it. I started making my own widgets to get finer grained control over events in general, but the invisible button covers Zep's needs.