Skip to content

xpl: a popup surface receives no keys but Escape, so a menu in one cannot be keyboard-driven #25

Description

@baconpaul

Follow-on from #23, which you closed with NEUI_W_POPUPSURFACE — and which solved the placement half completely. I've moved my menu onto it: one surface per cascade level, submenus anchored RIGHT off the parent's panel, and it now hangs over the desktop the way a plugin menu needs to. Generalizing it to "a frame you fill with widgets" rather than a menu feature was the better call, and get_clamp_size returning a box rather than a capability bit is the part I'd have got wrong. Thank you.

This is the other half of that use case: a popup surface receives no keys except Escape, so a menu inside one cannot be driven from the keyboard.

You already have this as wave 2 in plans/popup-surface.md, so none of this will be news. I'm filing it because I've now hit it with a real menu and can say concretely what breaks, and because your own note there — "a preset browser without type-ahead will read as broken, so this is the wave that decides whether the primitive covers the second half of #23's use case" — turns out to be exactly right.

What happens

Session::popup_gate_key is the whole keyboard story:

bool Session::popup_gate_key(uint32_t keycode)
{
  if (_popup_surfaces.empty()) return false;
  if (keycode != NEUI_KEY_ESCAPE) return false;
  close_all_popup_surfaces(NEUI_POPUP_DISMISS_ESCAPE);
  return true;
}

Escape is consumed; every other key returns false and falls through to normal handling in the owner's window. The comment at the macOS call site says why, and it is the right reason:

Note the key arrives HERE, at the owner: a popup surface never takes activation (inside a DAW that would read as the plugin editor losing focus), so it is not first responder and cannot be sent keys directly.

So with a menu open, an arrow key goes to whatever happens to be focused in the editor behind it. In my demo that is whichever button was last clicked, which means arrowing "through the menu" quietly operates the UI underneath it.

What it costs, concretely

Three things, in the order they hurt:

  1. Arrow / Home / End / Enter navigation. A menu that can only be used with the mouse is a step back from both popup_tree_menu and the JUCE menus I am replacing.
  2. A focusable text field inside a popup. This is the one that blocks a faithful port. sst-jucegui's menus carry a type-in row — in JUCE that is a PopupMenu::CustomComponent wrapping a TextEditor, and it is how you type an exact cutoff or a preset name without leaving the menu. I have the row built and a real NEUI_W_INPUTBOX sitting in it; it renders, it takes a click, and it cannot receive a character, because a widget inside a window that is never the key window can never be the focused widget. Nothing on the client side can reach it — this is not a case of my failing to call set_focus.
  3. Type-ahead in a browser. As you note, a preset list without it reads as broken.

There is also the accessibility angle from your §3 — a surface that never takes focus is close to invisible to a screen reader — but that is its own wave and I don't want to conflate them.

Where it seems to want to live

popup_gate_key already intercepts at the right moment and already has the stack in hand, so routing a key into _popup_surfaces.back() instead of returning false looks like the shape. Your plan says the same thing — "mostly bookkeeping now that the popup is a frame in the same session with a stack" — and I agree the genuinely hard part is the embedded case, where the DAW owns keyboard focus and plugin key handling is host-dependent anyway.

One thing I would gently argue for, if it makes the sequencing easier: the text-field case and the navigation case can be split. Arrows/Enter/type-ahead are the host synthesising navigation over popup content it can see. A focusable INPUTBOX inside a popup is harder — it needs a real focus owner in a non-activating window, which is a different problem per platform. If routing keys to the deepest surface's focused widget is the expensive part, then just delivering unhandled keys to the surface as events would already let a client implement menu navigation itself, and I would take that happily as a first step.

Not urgent, and not a regression

Everything that worked before still works — popup_tree_menu is fully keyboard-driven and is the right answer for menus that do not need a bold header or an embedded widget. This only bites the client-drawn case, which is the one that needed a popup surface in the first place.

Code, if useful: PopupMenu.h is the menu on surfaces, and menudemo.cpp opens it next to a popup_tree_menu doing the same thing, which makes the difference easy to see.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions