launcher: Fullscreen on launch + Skip Launcher on Boot (parity with SMW)

Adds two launcher controls requested for Tomba (issue #6):

- Fullscreen on launch: new [video] fullscreen setting, cloned through the full
  turbo_loads pipeline (UserSettings field + parse/save, launcher model/bind/
  toggle/commit, main.cpp seed/read), applied as SDL_WINDOW_FULLSCREEN_DESKTOP at
  game-window creation. The in-game F11 / Alt+Enter hotkey is unchanged.

- Skip Launcher on Boot: a footer switch (bottom-left) that boots straight into
  the game on subsequent launches, with a (first-in-this-launcher) confirmation
  modal explaining the --launcher escape hatch. The runtime skip_launcher round-
  trip + --launcher / PSX_NO_LAUNCHER already existed; this wires the UI to it.

Also drops the redundant footer Settings/Back links (the top bar already has
them). Mirrors SuperMarioWorldRecomp's feature.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Matthew Stanley
2026-06-21 15:23:41 -07:00
parent ababd91ddc
commit 1965cae59d
5 changed files with 82 additions and 2 deletions
+5
View File
@@ -805,6 +805,9 @@ UserSettings load_user_settings(const fs::path& path) {
if (v.contains("turbo_loads")) try_get([&]{
s.turbo_loads = toml::find<bool>(v, "turbo_loads"); s.has_turbo_loads = true;
});
if (v.contains("fullscreen")) try_get([&]{
s.fullscreen = toml::find<bool>(v, "fullscreen"); s.has_fullscreen = true;
});
if (v.contains("low_latency_input")) try_get([&]{
s.low_latency_input = toml::find<bool>(v, "low_latency_input");
s.has_low_latency_input = true;
@@ -950,6 +953,8 @@ bool save_user_settings(const fs::path& path, const UserSettings& s) {
f << "auto_skip_fmv = " << (s.auto_skip_fmv ? "true" : "false") << "\n";
if (s.has_turbo_loads)
f << "turbo_loads = " << (s.turbo_loads ? "true" : "false") << "\n";
if (s.has_fullscreen)
f << "fullscreen = " << (s.fullscreen ? "true" : "false") << "\n";
if (s.has_low_latency_input)
f << "low_latency_input = " << (s.low_latency_input ? "true" : "false") << "\n";
if (s.has_vsync)
+4
View File
@@ -441,6 +441,10 @@ struct UserSettings {
// (the per-game game.toml value seeds the launcher toggle; user choice in
// settings.toml overrides it).
bool has_turbo_loads = false; bool turbo_loads = true;
// [video] fullscreen: launch the game window in desktop fullscreen (the
// launcher's "Fullscreen on launch" toggle; the in-game F11 / Alt+Enter
// hotkey still toggles it live). false => windowed (default).
bool has_fullscreen = false; bool fullscreen = false;
// Low-latency present knobs. low_latency_input re-samples the pad after the
// wall-clock pacer (just before present) so the next CPU frame reads fresh
// input instead of input ~one frame stale (the dominant input->photon cost
+31 -2
View File
@@ -176,6 +176,7 @@ button:hover { background-color: #232d3c; }
border-top: 1dp #1e2733; padding-top: 16dp; }
.foot-link { color: #8b949e; margin-right: 22dp; }
.foot-link:hover { color: #e6e9ef; }
.skip-lbl { color: #adbac7; font-size: 13dp; margin-left: 9dp; margin-right: 22dp; }
.foot-status { display: flex; flex-direction: row; align-items: center; flex: 1 1 auto; }
#launch {
background-color: #2563eb; border-color: #3b82f6; color: #ffffff; font-weight: bold;
@@ -183,6 +184,19 @@ button:hover { background-color: #232d3c; }
}
#launch:hover { background-color: #3b82f6; }
.back-btn { margin-right: 14dp; }
/* ---- confirmation modal ---- */
.modal-backdrop { position: fixed; left: 0dp; top: 0dp; width: 100%; height: 100%;
background-color: #000000cc; display: flex; flex-direction: row; align-items: center; z-index: 100; }
.modal-box { display: block; width: 460dp; margin: 0dp auto;
background-color: #11161f; border: 1dp #2b3543; border-radius: 12dp; padding: 22dp 24dp; }
.modal-title { display: block; font-size: 19dp; font-weight: bold; color: #ffffff; margin-bottom: 10dp; }
.modal-text { display: block; color: #adbac7; font-size: 14dp; line-height: 20dp; margin-bottom: 20dp; }
.modal-btns { display: flex; flex-direction: row; align-items: center; }
.modal-spacer { flex: 1 1 auto; }
.modal-btns button { margin-left: 10dp; }
.modal-confirm { background-color: #2563eb; border-color: #3b82f6; color: #ffffff; font-weight: bold; }
.modal-confirm:hover { background-color: #3b82f6; }
</style>
</head>
@@ -343,6 +357,8 @@ button:hover { background-color: #232d3c; }
<div class="set-row"><span class="set-lbl">Turbo loads</span><span class="set-ctl">
<button class="toggle" data-event-click="toggle_turbo_loads"><span class="value" data-if="turbo_loads">On</span><span class="value" data-if="turbo_loads == false">Off</span></button>
</span></div>
<div class="set-row"><span class="set-lbl">Fullscreen on launch</span><span class="set-ctl">
<button class="toggle" data-event-click="toggle_fullscreen"><span class="value" data-if="fullscreen">On</span><span class="value" data-if="fullscreen == false">Off</span></button></span></div>
<div class="set-row" data-if="ws_eligible"><span class="set-lbl">Widescreen <span class="exp-tag">EXPERIMENTAL</span></span><span class="set-ctl">
<button class="toggle" data-event-click="toggle_widescreen"><span class="value" data-if="widescreen">On</span><span class="value" data-if="widescreen == false">Off</span></button>
<span class="ws-hint" data-if="widescreen">16:9 native field-of-view</span></span></div>
@@ -372,12 +388,25 @@ button:hover { background-color: #232d3c; }
<!-- ================= FOOTER ================= -->
<div class="footer">
<span class="foot-link" data-if="view == 'dashboard'" data-event-click="show_settings">Settings</span>
<span class="foot-link back-btn" data-if="view == 'settings'" data-event-click="show_dashboard">‹ Back</span>
<span class="switch" data-class-on="skip_launcher" data-event-click="toggle_skip_launcher"><span class="knob"></span></span>
<span class="skip-lbl">Skip Launcher on Boot</span>
<span class="foot-status"><span class="dot"></span><span class="status-txt">Recompiler ready</span></span>
<button id="launch" data-event-click="launch">LAUNCH GAME</button>
</div>
<!-- ================= SKIP-LAUNCHER CONFIRM MODAL ================= -->
<div class="modal-backdrop" data-if="show_skip_modal">
<div class="modal-box">
<span class="modal-title">Skip the launcher on boot?</span>
<span class="modal-text">The launcher will no longer appear &#8212; the game boots straight in every time. To get the launcher back, run the game with the --launcher argument, or set skip_launcher = false in settings.toml.</span>
<div class="modal-btns">
<span class="modal-spacer"></span>
<button data-event-click="skip_modal_cancel">Cancel</button>
<button class="modal-confirm" data-event-click="skip_modal_confirm">Skip on Boot</button>
</div>
</div>
</div>
</div>
</body>
</rml>
+34
View File
@@ -105,6 +105,12 @@ struct LauncherModel {
int window_width = 1280; // window size (height = width*den/num per aspect)
bool widescreen = false; // EXPERIMENTAL 16:9 native-wide (aspect_index==1)
bool ws_eligible = true; // toggle shown only when renderer==software (native-wide is SW-only)
bool fullscreen = false; // launch the game window in desktop fullscreen
// Skip-launcher: boot straight into the game on subsequent launches. Turning
// it ON shows a confirmation modal (show_skip_modal) so the user is told how
// to get the launcher back (run with --launcher). Mirrors SMW's feature.
bool skip_launcher = false;
bool show_skip_modal = false;
Rml::String bios_path;
Rml::String disc_path;
@@ -650,6 +656,8 @@ Result run(SDL_Window* window, void* gl_context,
m.crt = io.screen_kind;
m.auto_skip_fmv = io.auto_skip_fmv;
m.turbo_loads = io.turbo_loads;
m.fullscreen = io.fullscreen;
m.skip_launcher = io.skip_launcher;
m.spu_hq = io.spu_hq;
m.aspect_index = io.has_aspect_ratio ? aspect_index_for(io.aspect_num, io.aspect_den) : 0;
m.window_width = kWinWidths[winsize_index(io.has_window_width ? io.window_width : 1280)];
@@ -702,6 +710,9 @@ Result run(SDL_Window* window, void* gl_context,
c.Bind("antialiasing", &m.antialiasing);
c.Bind("auto_skip_fmv", &m.auto_skip_fmv);
c.Bind("turbo_loads", &m.turbo_loads);
c.Bind("fullscreen", &m.fullscreen);
c.Bind("skip_launcher", &m.skip_launcher);
c.Bind("show_skip_modal",&m.show_skip_modal);
c.Bind("spu_hq", &m.spu_hq);
c.Bind("renderer_label", &m.renderer_label);
c.Bind("crt_label", &m.crt_label);
@@ -821,6 +832,27 @@ Result run(SDL_Window* window, void* gl_context,
m.turbo_loads = !m.turbo_loads;
handle.DirtyVariable("turbo_loads");
});
c.BindEventCallback("toggle_fullscreen",
[&m, handle](Rml::DataModelHandle, Rml::Event&, const Rml::VariantList&) mutable {
m.fullscreen = !m.fullscreen;
handle.DirtyVariable("fullscreen");
});
// Skip launcher: turning OFF is immediate; turning ON opens a confirm modal
// first, so the user learns the --launcher escape hatch before committing.
c.BindEventCallback("toggle_skip_launcher",
[&m, handle](Rml::DataModelHandle, Rml::Event&, const Rml::VariantList&) mutable {
if (m.skip_launcher) { m.skip_launcher = false; handle.DirtyVariable("skip_launcher"); }
else { m.show_skip_modal = true; handle.DirtyVariable("show_skip_modal"); }
});
c.BindEventCallback("skip_modal_confirm",
[&m, handle](Rml::DataModelHandle, Rml::Event&, const Rml::VariantList&) mutable {
m.skip_launcher = true; m.show_skip_modal = false;
handle.DirtyVariable("skip_launcher"); handle.DirtyVariable("show_skip_modal");
});
c.BindEventCallback("skip_modal_cancel",
[&m, handle](Rml::DataModelHandle, Rml::Event&, const Rml::VariantList&) mutable {
m.show_skip_modal = false; handle.DirtyVariable("show_skip_modal");
});
c.BindEventCallback("browse_bios",
[&m, window, handle](Rml::DataModelHandle, Rml::Event&, const Rml::VariantList&) mutable {
std::string p = win_pick_file(window, "Select PlayStation BIOS",
@@ -1008,6 +1040,8 @@ Result run(SDL_Window* window, void* gl_context,
io.screen_kind = m.crt; io.has_screen_kind = true;
io.auto_skip_fmv = m.auto_skip_fmv; io.has_auto_skip_fmv = true;
io.turbo_loads = m.turbo_loads; io.has_turbo_loads = true;
io.fullscreen = m.fullscreen; io.has_fullscreen = true;
io.skip_launcher = m.skip_launcher; io.has_skip_launcher = true;
io.spu_hq = m.spu_hq; io.has_spu_hq = true;
io.aspect_num = kAspects[m.aspect_index][0];
io.aspect_den = kAspects[m.aspect_index][1];
+8
View File
@@ -134,6 +134,7 @@ static int g_video_scale = 1; /* internal-resolution SSAA factor *
static bool g_video_aa = true; /* linear present filtering */
static int g_video_texfilter = 0; /* 0=nearest, 1=bilinear */
static int g_video_renderer = 0; /* 0=software, 1=opengl (requested) */
static int g_fullscreen = 0; /* launch the game window in desktop fullscreen */
static int g_video_screen = 0; /* 0=raw,1=crt,2=composite,3=trinitron */
static int g_video_win_w = 1280; /* window width (height follows aspect) */
static bool g_audio_spu_hq = false; /* SPU float-shadow (env overrides) */
@@ -2032,6 +2033,7 @@ int main(int argc, char** argv) {
if (us.has_screen_kind) g_video_screen = us.screen_kind;
if (us.has_auto_skip_fmv) g_auto_skip_fmv = us.auto_skip_fmv ? 1 : 0;
if (us.has_turbo_loads) g_turbo_loads_enabled = us.turbo_loads ? 1 : 0;
if (us.has_fullscreen) g_fullscreen = us.fullscreen ? 1 : 0;
if (us.has_aspect_ratio) {
g_video_aspect_num = us.aspect_num;
g_video_aspect_den = us.aspect_den;
@@ -2129,6 +2131,7 @@ int main(int argc, char** argv) {
seed.screen_kind = g_video_screen; seed.has_screen_kind = true;
seed.auto_skip_fmv = (g_auto_skip_fmv != 0); seed.has_auto_skip_fmv = true;
seed.turbo_loads = (g_turbo_loads_enabled != 0); seed.has_turbo_loads = true;
seed.fullscreen = (g_fullscreen != 0); seed.has_fullscreen = true;
seed.aspect_num = g_video_aspect_num;
seed.aspect_den = g_video_aspect_den; seed.has_aspect_ratio = true;
seed.spu_hq = g_audio_spu_hq; seed.has_spu_hq = true;
@@ -2198,6 +2201,7 @@ int main(int argc, char** argv) {
g_video_screen = seed.screen_kind;
g_auto_skip_fmv = seed.auto_skip_fmv ? 1 : 0;
g_turbo_loads_enabled = seed.turbo_loads ? 1 : 0;
g_fullscreen = seed.fullscreen ? 1 : 0;
g_video_aspect_num = seed.aspect_num;
g_video_aspect_den = seed.aspect_den;
g_audio_spu_hq = seed.spu_hq;
@@ -2402,6 +2406,10 @@ int main(int argc, char** argv) {
Uint32 win_flags = SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE;
if (g_video_renderer == 1) win_flags |= SDL_WINDOW_OPENGL;
if (g_video_renderer == 2) win_flags |= SDL_WINDOW_VULKAN;
/* Fullscreen on launch (launcher "Fullscreen on launch" toggle). DESKTOP
* fullscreen keeps the desktop resolution and letterboxes the image, matching
* the in-game F11 / Alt+Enter hotkey behaviour. */
if (g_fullscreen) win_flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
/* Open at the user-chosen window size (default 1280 wide) instead of the
* old hardcoded 640x480, so the game doesn't boot into a tiny window. The
* height follows the configured display aspect (4:3 native, wider for the