From bf18a166beecb4bcef5cd33feee1c7621fb70133 Mon Sep 17 00:00:00 2001 From: James Dansereau <106631954+jkdansereau@users.noreply.github.com> Date: Thu, 3 Sep 2026 09:54:21 -0400 Subject: [PATCH] =?UTF-8?q?port:=20D184=20F10=20overlay=20v2=20=E2=80=94?= =?UTF-8?q?=20mouse=20control=20+=20resolution/fullscreen=20rows?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix invisible text: drawText passed the measured text w/h into textRender's width/height args, which are the on-screen CLIP rect (not the text size), so every glyph failed the clip test. Pass the full 2D viewport like bondview2.c's debug-text path. - Mouse UI: hover-to-highlight; left-click in the value column toggles/cycles; drag sliders; red X close box (clicking a row's label only focuses it, so selecting can't flip a toggle). inputSuspendForOverlay() forces the OS cursor free + visible while the overlay owns the mouse. - New rows: Fullscreen (live) and Resolution (windowed presets filtered to the desktop, live resize + recenter). SDL window ops are posted from the scheduler thread and drained host-side in videoPumpEvents. - Layout recomputed for the wide BankGothic font: right-aligned values, wider row pitch, all 13 rows fit the ~240-unit 2D space; nothing clips. Port-layer only. Links clean; runtime-verified on a real display (front end + -level_09): layout, hover/click/drag, live VSync/filter/fullscreen/resolution. --- port/include/input.h | 4 + port/include/video.h | 10 ++ port/src/input.c | 16 ++ port/src/optionsoverlay.c | 324 ++++++++++++++++++++++++++++++-------- port/src/video.c | 84 ++++++++++ 5 files changed, 371 insertions(+), 67 deletions(-) diff --git a/port/include/input.h b/port/include/input.h index ea57e382..693b0ccc 100644 --- a/port/include/input.h +++ b/port/include/input.h @@ -54,6 +54,10 @@ void inputNotifyClick(void); int inputReleaseCapture(void); int inputMouseCaptureActive(void); +/* F10 options overlay: force the OS cursor free + visible while the overlay + * owns the mouse. Safe to call every poll. */ +void inputSuspendForOverlay(void); + /* Queue a mouse-wheel weapon-cycle input (one short A-button press). Sign is * ignored -- GE only cycles forward on a bare A edge. */ void inputPostWheel(int notches); diff --git a/port/include/video.h b/port/include/video.h index a8c37fd3..d9bbe152 100644 --- a/port/include/video.h +++ b/port/include/video.h @@ -52,6 +52,16 @@ float videoGetFPS(void); * the next frame start. Called by the F10 options overlay after an edit. */ void videoRequestLiveConfig(void); +/* F10 options overlay -> window/fullscreen changes. The overlay input handler + * runs on the scheduler thread; SDL window ops must run on the thread that + * created the window, so these only post a request that videoPumpEvents() + * (host thread) applies. The Get* helpers are read-only and thread-safe. */ +void videoRequestWindowSize(int w, int h); +void videoRequestFullscreen(int on); +void videoGetWindowSize(int *w, int *h); +void videoGetDesktopSize(int *w, int *h); +int videoIsFullscreen(void); + /* Snapshot live window geometry into the config vars (call before configSave * on a clean exit). No-op if the window isn't up. */ void videoSaveWindowState(void); diff --git a/port/src/input.c b/port/src/input.c index 42acbfd4..122b0161 100644 --- a/port/src/input.c +++ b/port/src/input.c @@ -878,6 +878,22 @@ int inputReleaseCapture(void) int inputMouseCaptureActive(void) { return mouseCaptureMode && !mouseGrabbed; } +/* F10 options overlay: while it owns controller 0 the inputComputePad poll + * early-returns before reconcileGrab()/applyCursorVisibility(), so whatever + * grab state was live when F10 was pressed (relative mode + hidden cursor in a + * stage) would persist and the mouse UI would be unusable. Force the cursor + * free + visible every poll; the normal reconcile resumes once the overlay + * closes and the early-return no longer fires. */ +void inputSuspendForOverlay(void) +{ + if (mouseGrabbed) { + mouseGrabbed = 0; + SDL_SetRelativeMouseMode(SDL_FALSE); + mouseDX = mouseDY = 0.0; + } + SDL_ShowCursor(SDL_ENABLE); +} + void inputPostWheel(int notches) { if (notches < 0) notches = -notches; /* both directions cycle forward */ diff --git a/port/src/optionsoverlay.c b/port/src/optionsoverlay.c index 6f987c8a..a195f1a6 100644 --- a/port/src/optionsoverlay.c +++ b/port/src/optionsoverlay.c @@ -28,6 +28,7 @@ #include "system.h" #include "config.h" #include "video.h" +#include "input.h" #include "optionsoverlay.h" /* ---- game symbols (rendering/UI only; see input.c for the same pattern) ---- */ @@ -46,13 +47,27 @@ extern s16 viGetY(void); /* ------------------------------------------------------------------------ */ -enum { ROW_TOGGLE, ROW_SLIDER, ROW_ENUM, ROW_MSAA }; +enum { ROW_TOGGLE, ROW_SLIDER, ROW_ENUM, ROW_MSAA, ROW_RES }; static const char *const kOnOff[] = { "OFF", "ON", NULL }; static const char *const kTexFilter[] = { "NEAREST", "BILINEAR", "3-POINT", NULL }; static const char *const kCapture[] = { "ALWAYS GRAB", "CLICK-TO-LOCK", NULL }; static const int kMsaaSeq[] = { 1, 2, 4, 8 }; +/* Windowed-mode resolution presets. Filtered at init to those that fit the + * desktop; the Resolution row cycles the surviving list. */ +static const int kResList[][2] = { + { 640, 480 }, { 800, 600 }, { 960, 720 }, { 1024, 768 }, + { 1152, 864 }, { 1280, 720 }, { 1280, 800 }, { 1280, 960 }, + { 1366, 768 }, { 1440, 900 }, { 1600, 900 }, { 1600, 1200 }, + { 1680, 1050 }, { 1920, 1080 }, { 1920, 1200 }, { 2560, 1440 }, + { 3200, 1800 }, { 3840, 2160 }, +}; +#define NUM_RES ((int)(sizeof(kResList) / sizeof(kResList[0]))) +static int s_resFit[NUM_RES]; /* indices into kResList that fit the desktop */ +static int s_resFitN = 0; +static int s_resSel = 0; /* index into s_resFit */ + struct Row { const char *key; const char *label; @@ -70,6 +85,8 @@ struct Row { }; static struct Row rows[] = { + { "Video.Fullscreen", "Fullscreen", ROW_TOGGLE, 1, kOnOff, 0, 0, 0, 0,0,0,0,0 }, + { "__Resolution", "Resolution", ROW_RES, 0, NULL, 0, 0, 0, 0,0,0,0,0 }, { "Video.VSync", "VSync", ROW_TOGGLE, 1, kOnOff, 0, 0, 0, 0,0,0,0,0 }, { "Video.FpsCap", "Frame cap", ROW_SLIDER, 10, NULL, 0, 0, 360, 0,0,0,0,0 }, { "Video.MSAA", "MSAA", ROW_MSAA, 0, NULL, 1, 0, 0, 0,0,0,0,0 }, @@ -86,6 +103,53 @@ static int s_inited = 0; static volatile int s_open = 0; static int s_sel = 0; +/* Layout (game 2D pixel space = viGetX() x viGetY(), ~320x240). Shared by the + * emit path and the mouse hit-testing in optionsOverlayHandleInput(). + * BankGothic caps are ~9 units tall here, so rows need ~16 units of pitch and + * values are right-aligned to the panel edge to survive the wide font. */ +#define OV_X0 20 +#define OV_LABEL_X 28 +#define OV_TOP 12 +#define OV_LINE 15 /* row pitch (13 rows must fit ~240) */ +#define OV_HDR 2 /* header rows above row 0 (title+hint) */ +#define OV_ROW_Y(i) (OV_TOP + ((i) + OV_HDR) * OV_LINE) +#define OV_RIGHT (viGetX() - OV_X0) /* right edge for right-aligned text */ +#define OV_NUM_W 36 /* reserved width for a slider's number */ +#define OV_BAR_X 150 + +/* Slider fill bar span in overlay space (shared by emit + hit-testing). */ +static void sliderBarSpan(s32 *x0, s32 *x1) +{ + *x0 = OV_BAR_X; + *x1 = OV_RIGHT - OV_NUM_W; + if (*x1 < *x0 + 16) { + *x1 = *x0 + 16; + } +} + +/* Row index the given overlay-space y falls in, or -1. */ +static int overlayRowAtY(double oy) +{ + for (int i = 0; i < NUM_ROWS; i++) { + double top = OV_ROW_Y(i) - 3; + if (oy >= top && oy < top + OV_LINE) { + return i; + } + } + return -1; +} + +/* The close box brackets the title row at the panel's right edge. */ +#define OV_CB_X0 (OV_RIGHT - 14) +#define OV_CB_X1 (OV_RIGHT + 7) +#define OV_CB_Y0 (OV_TOP - 3) +#define OV_CB_Y1 (OV_TOP + 12) +static int overlayInCloseBox(double ox, double oy) +{ + return ox >= OV_CB_X0 && ox <= OV_CB_X1 && + oy >= OV_CB_Y0 && oy <= OV_CB_Y1; +} + /* ------------------------------------------------------------------------ */ static void resolveCb(const char *key, int type, void *ptr, double min, double max, @@ -120,12 +184,41 @@ static void overlayInit(void) configForEachOption(resolveCb, NULL); for (int i = 0; i < NUM_ROWS; i++) { + if (rows[i].kind == ROW_RES) { + rows[i].found = 1; /* not config-backed; driven via video.c */ + continue; + } if (!rows[i].found) { sysLogPrintf(LOG_WARNING, "optionsoverlay: option '%s' not registered", rows[i].key); } } + /* Build the windowed-resolution preset list: presets that fit the desktop, + * plus the current window size snapped to the nearest surviving entry. */ + { + int dw = 1920, dh = 1080; + videoGetDesktopSize(&dw, &dh); + s_resFitN = 0; + for (int i = 0; i < NUM_RES; i++) { + if (kResList[i][0] <= dw && kResList[i][1] <= dh) { + s_resFit[s_resFitN++] = i; + } + } + if (s_resFitN == 0) { + s_resFit[s_resFitN++] = 0; + } + int cw = 0, ch = 0; + videoGetWindowSize(&cw, &ch); + long best = -1; + for (int k = 0; k < s_resFitN; k++) { + int i = s_resFit[k]; + long d = labs((long)kResList[i][0] - cw) + + labs((long)kResList[i][1] - ch); + if (best < 0 || d < best) { best = d; s_resSel = k; } + } + } + const char *e = getenv("GE_OPTIONSOVERLAY"); if (e && atoi(e) != 0) { s_open = 1; @@ -171,7 +264,9 @@ static void rowSet(struct Row *r, double v) /* Live-apply the video knobs that need a fast3d/SDL call. Everything else * is read straight off the pointer by its owner every frame/poll. */ - if (strncmp(r->key, "Video.", 6) == 0 && !r->restart) { + if (strcmp(r->key, "Video.Fullscreen") == 0) { + videoRequestFullscreen((int)lround(v)); + } else if (strncmp(r->key, "Video.", 6) == 0 && !r->restart) { videoRequestLiveConfig(); } } @@ -205,6 +300,17 @@ static void rowAdjust(struct Row *r, int dir) rowSet(r, v); break; } + case ROW_RES: { + if (s_resFitN <= 0 || videoIsFullscreen()) { + break; /* resolution is windowed-only */ + } + s_resSel += (dir >= 0) ? 1 : -1; + if (s_resSel < 0) s_resSel = s_resFitN - 1; + if (s_resSel >= s_resFitN) s_resSel = 0; + int i = s_resFit[s_resSel]; + videoRequestWindowSize(kResList[i][0], kResList[i][1]); + break; + } default: /* ROW_SLIDER */ rowSet(r, v + dir * r->step); break; @@ -241,46 +347,106 @@ void optionsOverlayScroll(int dir) if (s_sel >= NUM_ROWS) s_sel = 0; } +/* Set a slider row from an overlay-space x inside its value bar, snapped to + * the row's step. */ +static void sliderSetFromX(struct Row *r, double ox) +{ + double lo = rowLo(r), hi = rowHi(r); + if (hi <= lo) { + return; + } + s32 bx0, bx1; + sliderBarSpan(&bx0, &bx1); + double f = (ox - bx0) / (double)(bx1 - bx0); + if (f < 0) f = 0; + if (f > 1) f = 1; + double v = lo + f * (hi - lo); + double step = (r->step > 0.0) ? r->step : 1.0; + v = lround(v / step) * step; + rowSet(r, v); +} + void optionsOverlayHandleInput(void) { - static int prev[6]; /* UP DOWN LEFT RIGHT RETURN MOUSE */ + static int prevUp, prevDn, prevLf, prevRt, prevLmb, prevRmb; + static int dragRow = -1; + if (!s_open) { - memset(prev, 0, sizeof(prev)); + prevUp = prevDn = prevLf = prevRt = prevLmb = prevRmb = 0; + dragRow = -1; return; } - const Uint8 *ks = SDL_GetKeyboardState(NULL); - Uint32 mb = SDL_GetMouseState(NULL, NULL); + /* The overlay owns the mouse while it is open: force the OS cursor free + + * visible (a stage poll would otherwise leave it locked/hidden). */ + inputSuspendForOverlay(); - int cur[6]; - cur[0] = ks[SDL_SCANCODE_UP] || ks[SDL_SCANCODE_KP_8]; - cur[1] = ks[SDL_SCANCODE_DOWN] || ks[SDL_SCANCODE_KP_2]; - cur[2] = ks[SDL_SCANCODE_LEFT] || ks[SDL_SCANCODE_KP_4]; - cur[3] = ks[SDL_SCANCODE_RIGHT] || ks[SDL_SCANCODE_KP_6] - || ks[SDL_SCANCODE_RETURN] || ks[SDL_SCANCODE_KP_ENTER]; - cur[4] = 0; /* reserved */ - cur[5] = (mb & SDL_BUTTON(SDL_BUTTON_LEFT)) ? 1 : 0; + const Uint8 *ks = SDL_GetKeyboardState(NULL); + int mx = 0, my = 0; + Uint32 mb = SDL_GetMouseState(&mx, &my); + int lmb = (mb & SDL_BUTTON(SDL_BUTTON_LEFT)) ? 1 : 0; int rmb = (mb & SDL_BUTTON(SDL_BUTTON_RIGHT)) ? 1 : 0; - if (cur[0] && !prev[0]) { - s_sel = (s_sel + NUM_ROWS - 1) % NUM_ROWS; - } - if (cur[1] && !prev[1]) { - s_sel = (s_sel + 1) % NUM_ROWS; - } - if (cur[2] && !prev[2]) { - rowAdjust(&rows[s_sel], -1); - } - if ((cur[3] && !prev[3]) || (cur[5] && !prev[5])) { - rowAdjust(&rows[s_sel], +1); - } - static int prevRmb = 0; - if (rmb && !prevRmb) { - rowAdjust(&rows[s_sel], -1); - } - prevRmb = rmb; + int up = ks[SDL_SCANCODE_UP] || ks[SDL_SCANCODE_KP_8]; + int dn = ks[SDL_SCANCODE_DOWN] || ks[SDL_SCANCODE_KP_2]; + int lf = ks[SDL_SCANCODE_LEFT] || ks[SDL_SCANCODE_KP_4]; + int rt = ks[SDL_SCANCODE_RIGHT] || ks[SDL_SCANCODE_KP_6] + || ks[SDL_SCANCODE_RETURN] || ks[SDL_SCANCODE_KP_ENTER]; - memcpy(prev, cur, sizeof(prev)); + /* ---- keyboard / D-pad nav ---- */ + if (up && !prevUp) s_sel = (s_sel + NUM_ROWS - 1) % NUM_ROWS; + if (dn && !prevDn) s_sel = (s_sel + 1) % NUM_ROWS; + if (lf && !prevLf) rowAdjust(&rows[s_sel], -1); + if (rt && !prevRt) rowAdjust(&rows[s_sel], +1); + + /* ---- mouse ---- */ + int ww = 0, wh = 0; + videoGetWindowSize(&ww, &wh); + if (ww > 0 && wh > 0) { + double ox = (double)mx * (double)viGetX() / ww; + double oy = (double)my * (double)viGetY() / wh; + int hoverRow = overlayRowAtY(oy); + int onClose = overlayInCloseBox(ox, oy); + + if (hoverRow >= 0 && !onClose) { + s_sel = hoverRow; /* hover-to-highlight */ + } + + /* left press. A click in the label column only focuses the row; a + * click in the value/control column (>= the bar-span start) changes + * it -- so clicking to select a toggle doesn't also flip it. */ + s32 bx0, bx1; + sliderBarSpan(&bx0, &bx1); + if (lmb && !prevLmb) { + if (onClose) { + optionsOverlayToggle(); /* close + configSave */ + return; + } + if (hoverRow >= 0 && ox >= bx0) { + struct Row *r = &rows[hoverRow]; + if (r->kind == ROW_SLIDER && r->found) { + sliderSetFromX(r, ox); + dragRow = hoverRow; + } else { + rowAdjust(r, +1); /* toggle / cycle forward */ + } + } + } + /* drag a slider */ + if (lmb && dragRow >= 0 && rows[dragRow].kind == ROW_SLIDER) { + sliderSetFromX(&rows[dragRow], ox); + } + if (!lmb) { + dragRow = -1; + } + /* right press in the value column: cycle back / decrement */ + if (rmb && !prevRmb && hoverRow >= 0 && !onClose && ox >= bx0) { + rowAdjust(&rows[hoverRow], -1); + } + } + + prevUp = up; prevDn = dn; prevLf = lf; prevRt = rt; + prevLmb = lmb; prevRmb = rmb; } /* ------------------------------------------------------------------------ */ @@ -288,16 +454,6 @@ void optionsOverlayHandleInput(void) #define OV_BUF_CMDS 8192 static Gfx s_buf[OV_BUF_CMDS]; -/* Layout (game 2D pixel space = viGetX() x viGetY(), ~320x240). */ -#define OV_X0 26 -#define OV_LABEL_X 34 -#define OV_VALUE_X 176 -#define OV_BAR_X 176 -#define OV_BAR_W 84 -#define OV_NUM_X 266 -#define OV_TOP 24 -#define OV_LINE 14 - static Gfx *fillRect(Gfx *gdl, s32 x0, s32 y0, s32 x1, s32 y1, u8 r, u8 g, u8 b, u8 a) { @@ -311,6 +467,17 @@ static Gfx *fillRect(Gfx *gdl, s32 x0, s32 y0, s32 x1, s32 y1, static void valueText(const struct Row *r, char *out, int n) { double v = rowGet(r); + if (r->kind == ROW_RES) { + if (videoIsFullscreen()) { + snprintf(out, n, "(fullscreen)"); + } else if (s_resFitN <= 0) { + snprintf(out, n, "n/a"); + } else { + int i = s_resFit[s_resSel]; + snprintf(out, n, "%d x %d", kResList[i][0], kResList[i][1]); + } + return; + } if ((r->kind == ROW_TOGGLE || r->kind == ROW_ENUM) && r->names) { int idx = (int)lround(v); int cnt = 0; @@ -331,7 +498,7 @@ static void valueText(const struct Row *r, char *out, int n) } if (r->kind == ROW_SLIDER && (int)lround(v) == 0 && strcmp(r->key, "Video.FpsCap") == 0) { - snprintf(out, n, "UNCAPPED"); + snprintf(out, n, "OFF"); return; } snprintf(out, n, "%d", (int)lround(v)); @@ -339,11 +506,27 @@ static void valueText(const struct Row *r, char *out, int n) static Gfx *drawText(Gfx *gdl, s32 x, s32 y, const char *str, u32 colour) { - s32 w = 0, h = 0; s32 px = x, py = y; - textMeasure(&h, &w, (char *)str, ptrFontBankGothicChars, ptrFontBankGothic, 10); + /* width/height are the on-screen CLIP rect textRenderGlyph tests against + * (clipX=start x, clipY=start y, +clipWidth/+clipHeight), NOT the text's + * own measured size -- passing the measured w/h clipped every glyph out + * (baseline+height > measured h => nothing drawn). Match the game: pass + * the full 2D viewport, like bondview2.c's debug-text path. */ return textRender(gdl, &px, &py, (char *)str, ptrFontBankGothicChars, - ptrFontBankGothic, colour, w, h, 0, 10); + ptrFontBankGothic, colour, viGetX(), viGetY(), 0, 0); +} + +static s32 measureText(const char *str) +{ + s32 h = 0, w = 0; + textMeasure(&h, &w, (char *)str, ptrFontBankGothicChars, ptrFontBankGothic, 0); + return w; +} + +/* Right-aligned: the string ends at xr. */ +static Gfx *drawTextR(Gfx *gdl, s32 xr, s32 y, const char *str, u32 colour) +{ + return drawText(gdl, xr - measureText(str), y, str, colour); } Gfx *optionsOverlayEmit(void) @@ -357,7 +540,11 @@ Gfx *optionsOverlayEmit(void) const s32 W = viGetX(); const s32 H = viGetY(); - const s32 panelBottom = OV_TOP + (NUM_ROWS + 3) * OV_LINE; + const s32 right = OV_RIGHT; + const s32 panelTop = OV_TOP - 9; + const s32 panelBottom = OV_ROW_Y(NUM_ROWS - 1) + OV_LINE / 2 + 3; + s32 bx0, bx1; + sliderBarSpan(&bx0, &bx1); Gfx *gdl = s_buf; gDPPipeSync(gdl++); @@ -367,23 +554,24 @@ Gfx *optionsOverlayEmit(void) /* ---- pass 1: all fills (G_CC_PRIMITIVE) ---- */ gdl = fillRect(gdl, 0, 0, W, H, 0, 0, 0, 150); /* dim */ - gdl = fillRect(gdl, OV_X0 - 6, OV_TOP - 8, W - (OV_X0 - 6), panelBottom, - 8, 10, 24, 205); /* panel */ + gdl = fillRect(gdl, OV_X0 - 8, panelTop, W - (OV_X0 - 8), panelBottom, + 8, 10, 24, 210); /* panel */ + gdl = fillRect(gdl, OV_CB_X0, OV_CB_Y0, OV_CB_X1, OV_CB_Y1, + 150, 40, 40, 235); /* close */ for (int i = 0; i < NUM_ROWS; i++) { - s32 rowY = OV_TOP + (i + 2) * OV_LINE; + s32 rowY = OV_ROW_Y(i); if (i == s_sel) { - gdl = fillRect(gdl, OV_X0 - 3, rowY - 2, W - (OV_X0 - 3), rowY + OV_LINE - 3, - 40, 46, 96, 220); + gdl = fillRect(gdl, OV_X0 - 4, rowY - 3, W - (OV_X0 - 4), + rowY + OV_LINE - 4, 40, 46, 96, 220); } if (rows[i].kind == ROW_SLIDER && rows[i].found) { double lo = rowLo(&rows[i]), hi = rowHi(&rows[i]); double f = (hi > lo) ? (rowGet(&rows[i]) - lo) / (hi - lo) : 0.0; if (f < 0) f = 0; if (f > 1) f = 1; - s32 by = rowY + 2; - gdl = fillRect(gdl, OV_BAR_X, by, OV_BAR_X + OV_BAR_W, by + 6, - 60, 60, 70, 220); - gdl = fillRect(gdl, OV_BAR_X, by, OV_BAR_X + (s32)(OV_BAR_W * f), by + 6, + s32 by = rowY + 3; + gdl = fillRect(gdl, bx0, by, bx1, by + 5, 60, 60, 70, 220); + gdl = fillRect(gdl, bx0, by, bx0 + (s32)((bx1 - bx0) * f), by + 5, 210, 200, 90, 255); } } @@ -392,28 +580,30 @@ Gfx *optionsOverlayEmit(void) gdl = microcode_constructor(gdl); gdl = drawText(gdl, OV_X0, OV_TOP, "PC OPTIONS", 0xffe040ff); - gdl = drawText(gdl, OV_X0 + 96, OV_TOP, "F10 close arrows adjust", 0x9090a0ff); + gdl = drawText(gdl, OV_X0, OV_TOP + OV_LINE, "click value / drag / arrows", + 0x8890a0ff); /* hint line */ + gdl = drawText(gdl, (OV_CB_X0 + OV_CB_X1) / 2 - measureText("X") / 2, + OV_TOP, "X", 0xffffffff); /* close glyph */ for (int i = 0; i < NUM_ROWS; i++) { - s32 rowY = OV_TOP + (i + 2) * OV_LINE; + s32 rowY = OV_ROW_Y(i); u32 col = (i == s_sel) ? 0xffffffff : 0xc0c0c8ff; char val[48]; + gdl = drawText(gdl, OV_LABEL_X, rowY, (char *)rows[i].label, + rows[i].found ? col : 0x808080ff); if (!rows[i].found) { - gdl = drawText(gdl, OV_LABEL_X, rowY, (char *)rows[i].label, 0x808080ff); - gdl = drawText(gdl, OV_VALUE_X, rowY, "(n/a)", 0x808080ff); + gdl = drawTextR(gdl, right, rowY, "(n/a)", 0x808080ff); continue; } - gdl = drawText(gdl, OV_LABEL_X, rowY, (char *)rows[i].label, col); valueText(&rows[i], val, sizeof(val)); - if (rows[i].kind == ROW_SLIDER) { - gdl = drawText(gdl, OV_NUM_X, rowY, val, col); - } else { - gdl = drawText(gdl, OV_VALUE_X, rowY, val, col); - } if (rows[i].restart) { - gdl = drawText(gdl, OV_VALUE_X + 46, rowY, "(restart)", 0x909090ff); + /* value left of the bar span, "(restart)" pinned to the edge */ + gdl = drawText(gdl, bx0, rowY, val, col); + gdl = drawTextR(gdl, right, rowY, "(restart)", 0x909090ff); + } else { + gdl = drawTextR(gdl, right, rowY, val, col); } } diff --git a/port/src/video.c b/port/src/video.c index 48afc02b..d4716fb3 100644 --- a/port/src/video.c +++ b/port/src/video.c @@ -122,6 +122,85 @@ void videoRequestLiveConfig(void) liveCfgDirty = 1; } +/* --- F10 overlay: window / fullscreen changes, deferred to the host thread --- + * optionsOverlayHandleInput() runs on the scheduler thread; SDL_SetWindowSize / + * SDL_SetWindowFullscreen pump the Win32 message loop and must run on the + * window's creating thread. The overlay posts a request here; the host-thread + * event pump drains it in videoDrainWindowRequests(). */ +static volatile int winReqKind = 0; /* 0 none, 1 resize, 2 fullscreen */ +static volatile int winReqA = 0, winReqB = 0; + +void videoRequestWindowSize(int w, int h) +{ + winReqA = w; winReqB = h; winReqKind = 1; +} + +void videoRequestFullscreen(int on) +{ + winReqA = on ? 1 : 0; winReqKind = 2; +} + +void videoGetWindowSize(int *w, int *h) +{ + uint32_t ww = 0, hh = 0; int32_t x = 0, y = 0; + if (initDone && wmAPI && wmAPI->get_dimensions) { + wmAPI->get_dimensions(&ww, &hh, &x, &y); + } + if (w) *w = (int)ww; + if (h) *h = (int)hh; +} + +void videoGetDesktopSize(int *w, int *h) +{ + SDL_DisplayMode m; + memset(&m, 0, sizeof(m)); + if (SDL_GetDesktopDisplayMode(0, &m) != 0 || m.w <= 0 || m.h <= 0) { + m.w = 1920; m.h = 1080; + } + if (w) *w = m.w; + if (h) *h = m.h; +} + +int videoIsFullscreen(void) +{ + return (initDone && wmAPI && wmAPI->get_fullscreen_state) + ? (wmAPI->get_fullscreen_state() ? 1 : 0) : 0; +} + +static void videoDrainWindowRequests(void) +{ + int kind = winReqKind; + if (!kind || !wmAPI) { + winReqKind = 0; + return; + } + winReqKind = 0; + + if (kind == 1) { + int w = winReqA, h = winReqB; + int32_t px = 100, py = 100; + if (wmAPI->get_fullscreen_state && wmAPI->get_fullscreen_state()) { + if (wmAPI->set_fullscreen) wmAPI->set_fullscreen(false); + cfgFullscreen = 0; + } + if (wmAPI->get_centered_positions) { + wmAPI->get_centered_positions(w, h, &px, &py); + } + if (wmAPI->set_dimensions) { + wmAPI->set_dimensions((uint32_t)w, (uint32_t)h, px, py); + } + gfx_sdl_update_cached_size(); + cfgWinW = w; cfgWinH = h; + sysLogPrintf(LOG_INFO, "video: window -> %dx%d", w, h); + } else if (kind == 2) { + int on = winReqA; + if (wmAPI->set_fullscreen) wmAPI->set_fullscreen(on != 0); + gfx_sdl_update_cached_size(); + cfgFullscreen = on ? 1 : 0; + sysLogPrintf(LOG_INFO, "video: fullscreen %s", on ? "on" : "off"); + } +} + static u32 frames = 0; /* Set by the host event pump (F12), consumed on the render thread in * videoEndFrame where a GL context is current. */ @@ -247,6 +326,11 @@ void videoPumpEvents(void) if (!initDone) { return; } + + /* Apply any window/fullscreen change the F10 overlay posted from the + * scheduler thread (must run here, on the window's creating thread). */ + videoDrainWindowRequests(); + SDL_Event ev; while (SDL_PollEvent(&ev)) { switch (ev.type) {