diff --git a/gpu/vk/vk_menu.cpp b/gpu/vk/vk_menu.cpp index 0e3bd44c..bf57fdce 100644 --- a/gpu/vk/vk_menu.cpp +++ b/gpu/vk/vk_menu.cpp @@ -474,7 +474,7 @@ Row rows[] = { "Bounds-checked buffer reads: a read past a buffer's end is dropped instead of stopping the GPU. Applies after a " "restart.", hn_robust, true}, - // Gameplay (3 rows) + // Gameplay {TAB_GAMEPLAY, "FPS counter", &opts.show_fps, 0, 1, 1, tx_onoff, "Frames per second in the top right corner.", hn_fps, false, true}, {TAB_GAMEPLAY, "Game speed", &opts.realtime, 0, 1, 1, tx_speed, "How the game keeps time when it runs below 60 fps.", @@ -517,10 +517,19 @@ Row rows[] = { "Which face button is which. Binding one yourself on the Controls page makes it Custom.", hn_pad_layout}, {TAB_CONTROLS, "Controls", nullptr, 0, 0, 0, nullptr, // (hookshot: opens its page) "Which gamepad button and which keys press each of the Switch's buttons."}, + // (Restart, Quit and Cheats stay the table's last three rows: kRestartRow, kQuitRow and kCheatsRow count back from its end) + {TAB_GAMEPLAY, "Restart", nullptr, 0, 0, 0, nullptr, + "Starts the game again. Save your progress first. Press Enter or the bottom face button twice to restart. Esc or the " + "right face button cancels."}, + {TAB_GAMEPLAY, "Quit", nullptr, 0, 0, 0, nullptr, + "Save your progress first. Press Enter or the bottom face button twice to quit. Esc or the right face button cancels."}, {TAB_GAMEPLAY, "Cheats", nullptr, 0, 0, 0, nullptr, "Open the private gameplay tools."}, }; constexpr int kCheatsRow = sizeof rows / sizeof rows[0] - 1; +constexpr int kQuitRow = kCheatsRow - 1; +constexpr int kRestartRow = kCheatsRow - 2; +const Row* pending = nullptr; // (Restart or Quit: shows Confirm until its second press) bool cheats_enabled = false; std::atomic protect_hearts{-1}; // -1 disables the shortcut, 0 enables it on the ground, and 1 means flying. @@ -1354,7 +1363,8 @@ void paint_menu(HDC dc, uint32_t w, uint32_t h, int s) { SIZE nsz{}; GetTextExtentPoint32A(dc, r.name, (int)strlen(r.name), &nsz); const int room = (int)w - 18 * s - (18 * s + (int)nsz.cx + 12 * s); - std::string label = r.value ? r.text(*r.value) : ">"; // (">": a page) + const char* action = pending == &r ? "Confirm" : ">"; // (">": a page) + std::string label = r.value ? r.text(*r.value) : action; if (r.value && r.restart && *r.value != restart_state.start[restart_slot_of(r)]) label += " (after restart)"; const bool arrows = r.value && sel && !row_fixed(r.value); auto width = [&](const std::string& t) { @@ -2164,7 +2174,8 @@ void menu_tick() { } } } - if (press[5] && restart_ask()) page = 5, ask_sel = 0, changed = true; // (the restart question first) + if (press[5] && pending) pending = nullptr, changed = true; // (a Restart or Quit waiting for its second press: cancelled) + else if (press[5] && restart_ask()) page = 5, ask_sel = 0, changed = true; // (the restart question first) else if (press[5]) open_now = false; // (the tabs: LB and RB, Q and E, or Tab) if (tab_left) cur_tab = (cur_tab + TAB_COUNT - 1) % TAB_COUNT, changed = true; @@ -2174,7 +2185,26 @@ void menu_tick() { if (press[1]) selected = (selected + tab_count - 1) % tab_count, changed = true; if (press[2]) selected = (selected + 1) % tab_count, changed = true; Row& r = rows[tab_row_to_global(cur_tab, selected)]; - if (press[7] && &r == &rows[kCheatsRow]) page = 2, changed = true; + const bool confirm_selected = &r == &rows[kQuitRow] || &r == &rows[kRestartRow]; + if (pending && (pending != &r || !mine)) pending = nullptr, changed = true; + if (press[7] && confirm_selected) { + if (mine && !press[5]) { + if (pending != &r) { + pending = &r, changed = true; + } else { // (as Restart now: saved first, as the window's own close does not force the pipeline cache) + save(); + pipeline_cache_save(true); + pending = nullptr; + if (&r == &rows[kRestartRow]) { // (the restart question's Restart now: the window closes, then the game starts again) + logf("menu: restart"); + open_now = false, changed = true; + if (const HWND window = app_window()) PostMessageA(window, WM_APP + 9, 0, 0); + } else if (const HWND window = app_window()) { + PostMessageA(window, WM_CLOSE, 0, 0); + } + } + } + } else if (press[7] && &r == &rows[kCheatsRow]) page = 2, changed = true; else if (press[7] && !r.value && can_bind()) page = 1, csel = 0, ccol = 0, changed = true; if ((press[3] || press[4] || press[7]) && r.value) { // (Enter, or the bottom face button, as Right) if (press[7]) press[4] = true; @@ -2198,6 +2228,7 @@ void menu_tick() { if (!now_open) { if (drag_row >= 0) save(); // (a slider let go off the window: kept now) hover_tab = -1, drag_row = -1, mouse_read = false; + pending = nullptr; } if (now_open != told && app_window()) { told = now_open; diff --git a/host/posix/display_sdl.c b/host/posix/display_sdl.c index a37eb8ce..29b2e36d 100644 --- a/host/posix/display_sdl.c +++ b/host/posix/display_sdl.c @@ -148,14 +148,16 @@ static volatile int start_asked; static volatile int fullscreen_asked = -1; /* (the Insert menu's row, from the renderer's thread: applied on this one) */ static volatile int menu_asked = -1; /* (the Insert menu opened or closed, likewise) */ static volatile int restart_asked; /* (the Insert menu's Restart now: the window closes as by the player) */ +static volatile int close_asked; /* (the Insert menu's Quit: likewise, without the relaunch) */ /* PostMessageA to the window (host/posix/win32_posix.c): WM_APP + 7, the Insert menu's Fullscreen row, WM_APP + 8 the - menu opened or closed, WM_APP + 9 its Restart now (host/display.c's) */ + menu opened or closed, WM_APP + 9 its Restart now, WM_CLOSE its Quit (host/display.c's) */ __attribute__((visibility("default"))) void hos_posix_post_message(uint32_t msg, uintptr_t wp, intptr_t lp) { (void)lp; if (msg == WM_APP + 7) __atomic_store_n(&fullscreen_asked, wp ? 1 : 0, __ATOMIC_RELEASE); if (msg == WM_APP + 8) __atomic_store_n(&menu_asked, wp ? 1 : 0, __ATOMIC_RELEASE); if (msg == WM_APP + 9) __atomic_store_n(&restart_asked, 1, __ATOMIC_RELEASE); + if (msg == WM_CLOSE) __atomic_store_n(&close_asked, 1, __ATOMIC_RELEASE); } /* the menu opened: the mouse let go (its capture kept for later); closed: the capture back, if the window still has the focus */ static int captured_before_menu; @@ -316,6 +318,10 @@ void hos_display_loop(void) { rx_close_asked(); hos_window_closed_exit(); } + if (__atomic_load_n(&close_asked, __ATOMIC_ACQUIRE)) { + rx_close_asked(); + hos_window_closed_exit(); + } keys_update(); } } diff --git a/host/posix/windows.h b/host/posix/windows.h index 85d262f0..f4c4b00c 100644 --- a/host/posix/windows.h +++ b/host/posix/windows.h @@ -537,6 +537,7 @@ HWND GetForegroundWindow(void); #define WS_EX_NOACTIVATE 0x08000000 static inline LONG_PTR GetWindowLongPtrA(HWND w, int i) { (void)w, (void)i; return 0; } /* (the Insert menu's Fullscreen row, gpu/vk/vk_menu.cpp: WM_APP + 7 to the window sets it, GWL_STYLE without WS_CAPTION reads it) */ +#define WM_CLOSE 0x0010 #define WM_APP 0x8000 #define WS_CAPTION 0x00C00000L BOOL PostMessageA(HWND w, UINT msg, WPARAM wp, LPARAM lp);