emulator: RIO panel click-to-press + lamp flash blink

Make the in-fork glass cockpit fully interactive and finish the lamp model.

- Click-to-press: clicking a button's region in a VDB head window presses that
  RIO address into the game (lights white-hot while held, releases on mouse-up).
  vpxlog rt_wndproc hit-tests the click against the bezel geometry (MFD 4 top /
  4 bottom, radar 6 left / 6 right; the radar bottom indicators are display-only,
  not clickable); head windows are tagged via GWLP_USERDATA. Clicks arrive on the
  VPX render thread, so the seam serialrio RIO_HostButton() queues them under a
  mutex and pollInput() drains them on the emu tick through the same incHold/
  decHold path as the keyboard/pad -- so a click also lights the bezel.
- Flash blink: decode the lamp byte's flash bits (RioLampState 1 slow / 2 med /
  3 fast) -- an unpressed flashing lamp now toggles between its brightness level
  and off (half-periods 500/250/125ms); solid lamps unchanged, a press still wins.
- Focus: head windows are WS_EX_NOACTIVATE so a button click never pulls
  foreground off DOSBox (which would demote the emu thread and stop SDL polling
  the pad); a click uses MA_NOACTIVATE too. Do NOT enable SDL background joystick
  events -- that raced the emu-thread controller poll against SDL's main-thread
  pump and crashed the process.

Explode layout only, and only when a serial=rio port is present.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-07-24 11:50:53 -05:00
co-authored by Claude Opus 4.8
parent f2424e9fdc
commit 9dd0aa8702
4 changed files with 125 additions and 5 deletions
+83 -3
View File
@@ -1343,14 +1343,25 @@ static int mfd_hi_for_g(int g) {
return -1;
}
/* Flash phase for a flashing lamp (RioLampState bits 0-1: 1 slow / 2 med / 3
* fast; 0 = solid = always on). Toggles on/off at half-periods 500/250/125ms
* off the wall clock, so a flashing lamp blinks between its level and off. */
static bool rio_flash_on(int flash) {
if (flash == 0) return true;
unsigned int hp = flash == 1 ? 500u : flash == 2 ? 250u : 125u;
return ((GetTickCount() / hp) & 1u) == 0u;
}
/* Panel button color at the host lamp level (vRIO RioLampState: brighter of the
* two brightness fields, bits 2-3 = 0x0C bright/0x04 dim, bits 4-5 = 0x30 bright/
* 0x10 dim). MFD buttons are red, radar Secondary/Screen buttons are amber. A
* press shows white-hot so it's visible over any level. */
* 0x10 dim; bits 0-1 = flash mode). MFD buttons are red, radar Secondary/Screen
* buttons are amber. A press shows white-hot so it's visible over any level. */
static void rio_button_color(uint8_t ls, bool pr, bool amber, float *r, float *g, float *b) {
int f1 = ls & 0x0C, f2 = ls & 0x30;
int level = (f1 == 0x0C || f2 == 0x30) ? 2 : (f1 || f2) ? 1 : 0;
if (pr) { *r = 1.00f; *g = 0.90f; *b = 0.62f; return; } /* pressed white-hot */
int flash = ls & 0x03;
if (flash != 0 && !rio_flash_on(flash)) level = 0; /* flashing, off phase */
if (pr) { *r = 1.00f; *g = 0.90f; *b = 0.62f; return; } /* pressed white-hot (press wins) */
if (amber) {
if (level == 2) { *r = 1.00f; *g = 0.72f; *b = 0.05f; } /* bright amber */
else if (level == 1) { *r = 0.50f; *g = 0.36f; *b = 0.02f; } /* dim amber */
@@ -1717,8 +1728,69 @@ static void pal_draw(HDC dc, int g, int cw, int ch, bool dump) {
SwapBuffers(dc);
}
/* ---- click-to-press: map a click in a head window to a RIO button ----------
* Mirrors the bezel geometry (draw_mfd_bezel / draw_radar_bezel): returns the
* RIO address under (mx,my) in client coords, or -1 for a non-button click.
* The radar bottom indicators (spares) are display-only, not clickable. */
extern void RIO_HostButton(int addr, bool down);
static int rio_hit_test(int g, int cw, int ch, int mx, int my) {
int hi = mfd_hi_for_g(g);
if (hi >= 0) { /* MFD head: 4 top / 4 bottom */
int col = (cw > 0) ? mx * 4 / cw : 0;
if (col < 0) col = 0;
else if (col > 3) col = 3;
if (my >= 0 && my < RIO_BEZEL_BTN) return hi - col; /* top row (window top) */
if (my >= ch - RIO_BEZEL_BTN && my < ch) return hi - 4 - col; /* bottom row */
return -1;
}
if (g == 0) { /* radar: 6 left / 6 right */
const int BTN_H = 104, GAP = 3, N = 6, BTN_W = 100;
int side = -1;
if (mx >= 0 && mx < BTN_W) side = 0; /* left = Secondary 0x10.. */
else if (mx >= cw - BTN_W && mx < cw) side = 1; /* right = Screen 0x18.. */
if (side < 0) return -1;
int stack = N * BTN_H + (N - 1) * GAP;
int margin = ((ch - RIO_RADAR_BOT) - stack) / 2;
int rel = my - margin;
if (rel < 0) return -1;
int i = rel / (BTN_H + GAP);
if (i < 0 || i >= N) return -1;
if (rel - i * (BTN_H + GAP) >= BTN_H) return -1; /* landed in the 3px gap */
return (side == 0 ? 0x10 : 0x18) + i;
}
return -1;
}
static int g_click_addr[10] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; /* held addr per head */
static LRESULT CALLBACK rt_wndproc(HWND w, UINT msg, WPARAM wp, LPARAM lp) {
if (msg == WM_CLOSE) { ShowWindow(w, SW_MINIMIZE); return 0; }
if (msg == WM_MOUSEACTIVATE) return MA_NOACTIVATE; /* click a button, don't steal DOSBox focus */
if (msg == WM_LBUTTONDOWN || msg == WM_LBUTTONUP) {
int tag = (int)(LONG_PTR)GetWindowLongPtrA(w, GWLP_USERDATA); /* head windows store g+1 */
int g = tag - 1;
if (g >= 0 && g < 10 && pal_radar_cw) { /* bezel exists only in explode layout */
if (msg == WM_LBUTTONDOWN) {
RECT cr; GetClientRect(w, &cr);
int addr = rio_hit_test(g, cr.right, cr.bottom,
(short)LOWORD(lp), (short)HIWORD(lp));
if (addr >= 0) {
if (g_click_addr[g] >= 0) RIO_HostButton(g_click_addr[g], false);
g_click_addr[g] = addr;
RIO_HostButton(addr, true);
SetCapture(w);
}
} else { /* WM_LBUTTONUP */
if (g_click_addr[g] >= 0) {
RIO_HostButton(g_click_addr[g], false);
g_click_addr[g] = -1;
ReleaseCapture();
}
}
return 0;
}
}
return DefWindowProcA(w, msg, wp, lp);
}
@@ -1860,6 +1932,14 @@ static DWORD WINAPI rt_main(LPVOID) {
env_rect(en, &x, &y, &w, &h);
phave[g] = make_gl_window(pal_titles[g], w, h, x, y, cockpit,
&pwnd[g], &pdc[g], &pgl[g]);
if (phave[g]) {
/* tag with g+1 so rt_wndproc maps a click to this head (click-to-press) */
SetWindowLongPtrA(pwnd[g], GWLP_USERDATA, (LONG_PTR)(g + 1));
/* WS_EX_NOACTIVATE: clicking a button never pulls foreground off
* DOSBox (else the emu thread demotes and SDL stops polling the pad). */
SetWindowLongPtrA(pwnd[g], GWL_EXSTYLE,
GetWindowLongPtrA(pwnd[g], GWL_EXSTYLE) | WS_EX_NOACTIVATE);
}
}
VFrame cur;