feat(ui): discoverable context-menu hint — header glyph, highlight, corner dropdown

Hold-Enter context menus were invisible. Add a menu affordance to every screen
that has one:

- A small ≡ glyph in the header's top-right, via new DisplayDriver::
  drawContextMenuHint() and an optional menu_hint arg on drawCenteredHeader() /
  drawInvertedHeader() (reserves the corner so the title never runs under it).
- The glyph highlights (corner cell filled, bars knocked out) while the menu is
  open, tying the hint to the popup it spawned — driven by a menu_open flag the
  screens pass from their live popup state.
- The context menu now drops out of that corner: PopupMenu gains an opt-in
  top-right anchor (begin(..., anchor_top_right)); it right-aligns under the
  header instead of centring, so the menu reads as emerging from the ≡. Default
  stays centred, so non-context popups (Tools/Settings/etc.) are untouched.

Enabled on Nodes (list/scan/detail) and Messages (mode select, contact/room and
channel pickers, DM and channel history).

Both solo envs build green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Jakub
2026-07-07 10:31:01 +02:00
co-authored by Claude Opus 4.8
parent 7f56af1663
commit a1ee67ae94
4 changed files with 71 additions and 25 deletions
+14 -3
View File
@@ -17,14 +17,16 @@ struct PopupMenu {
int _cap; // actual visible cap, updated each render()
bool active;
const char* _title;
bool anchor_tr = false; // opt-in: drop from the top-right ≡ hint instead of centring
enum Result { NONE, SELECTED, CANCELLED };
PopupMenu() : _count(0), _sel(0), _scroll(0), _visible(3), _cap(3), active(false), _title(nullptr) {}
void begin(const char* title, int visible = 3) {
void begin(const char* title, int visible = 3, bool anchor_top_right = false) {
_count = 0; _sel = 0; _scroll = 0;
_visible = visible; _cap = visible; active = true; _title = title;
anchor_tr = anchor_top_right;
}
void addItem(const char* item) {
@@ -86,8 +88,17 @@ struct PopupMenu {
int bh = title_h + vis * item_h + 1; // +1 keeps the last row off the bottom border
// Centre on screen (clamped to the margins on small displays).
int bx = (display.width() - bw) / 2;
int by = (display.height() - bh) / 2;
int bx, by;
if (anchor_tr) {
// Drop out of the top-right ≡ hint: right-aligned under the header, so the
// menu reads as emerging from the glyph that spawned it.
bx = display.width() - bw - margin;
by = display.headerH();
if (by + bh > display.height() - margin) by = display.height() - margin - bh;
} else {
bx = (display.width() - bw) / 2;
by = (display.height() - bh) / 2;
}
if (bx < margin) bx = margin;
if (by < margin) by = margin;