Add Exit button + controller led fix (#221)

This commit is contained in:
Nicholas Bly
2026-09-13 08:07:54 -04:00
committed by GitHub
parent b555ede2d3
commit 4bdaff01fc
3 changed files with 51 additions and 2 deletions
+1
View File
@@ -58,6 +58,7 @@ inline void Flush(bool force = false) {
// still active. A window close is an intentional successful exit, so end the
// process directly and do not run the crash/atexit paths.
[[noreturn]] inline void ExitForAuroraWindowClose() noexcept {
settings_overlay::ReleaseControllers();
WindowPlacementPersistence::Flush(true);
#if defined(_WIN32)
::ExitProcess(0);
+2
View File
@@ -12,4 +12,6 @@ void Draw() noexcept;
bool StartupScreenVisible() noexcept;
void NotifyStrapInputAccepted() noexcept;
void AdvancePresentedFrame() noexcept;
// Put host controllers back to a neutral state before the process ends.
void ReleaseControllers() noexcept;
} // namespace settings_overlay
+48 -2
View File
@@ -1,5 +1,6 @@
#include "settings_overlay.h"
#include "audio_backend.h"
#include "aurora_events.h"
#include "controller_button_names.h"
#include "controller_mapping_wizard.h"
#include "input_bindings.h"
@@ -15,6 +16,7 @@
#include <SDL3/SDL_keyboard.h>
#include <SDL3/SDL_mouse.h>
#include <SDL3/SDL_scancode.h>
#include <SDL3/SDL_timer.h>
#include <array>
#include <algorithm>
@@ -70,6 +72,7 @@ const char* GraphicsApiDisplayName() {
}
bool g_topBarVisible = false;
bool g_exitPromptOpen = false;
bool g_rumbleEnabled = RuntimeConfigFile::RumbleEnabled(true);
int g_controllerPort = 0;
float g_resolutionScale = RuntimeConfigFile::ResolutionMultiplier(1.0f);
@@ -1175,6 +1178,18 @@ void DrawStartupScreen() {
ImGui::PopStyleColor();
}
void DrawExitPrompt() {
constexpr const char* kTitle = "Exit";
if (g_exitPromptOpen && !ImGui::IsPopupOpen(kTitle)) ImGui::OpenPopup(kTitle);
if (!ImGui::BeginPopupModal(kTitle, &g_exitPromptOpen, ImGuiWindowFlags_AlwaysAutoResize)) return;
ImGui::TextUnformatted("Quit the game?");
if (ImGui::Button("Exit", ImVec2(120.0f, 0.0f))) ExitForAuroraWindowClose();
ImGui::SameLine();
if (ImGui::Button("Cancel", ImVec2(120.0f, 0.0f))) g_exitPromptOpen = false;
if (!g_exitPromptOpen) ImGui::CloseCurrentPopup();
ImGui::EndPopup();
}
void DrawTopBar() {
if (!g_topBarVisible) {
return;
@@ -1243,11 +1258,17 @@ void DrawTopBar() {
ImGui::EndMenu();
}
const float hideWidth = ImGui::CalcTextSize("Hide (F10)").x + ImGui::GetStyle().FramePadding.x * 2.0f;
ImGui::SetCursorPosX(std::max(ImGui::GetCursorPosX(), ImGui::GetWindowWidth() - hideWidth - 8.0f));
const ImGuiStyle& style = ImGui::GetStyle();
const float hideWidth = ImGui::CalcTextSize("Hide (F10)").x + style.FramePadding.x * 2.0f;
const float exitWidth = ImGui::CalcTextSize("X").x + style.FramePadding.x * 2.0f;
ImGui::SetCursorPosX(std::max(ImGui::GetCursorPosX(),
ImGui::GetWindowWidth() - hideWidth - exitWidth - style.ItemSpacing.x - 8.0f));
if (ImGui::MenuItem("Hide (F10)")) {
SetTopBarVisible(false);
}
if (ImGui::MenuItem("X")) {
g_exitPromptOpen = true;
}
ImGui::EndMainMenuBar();
}
@@ -1342,12 +1363,36 @@ void HandleEvents(const AuroraEvent* events) noexcept {
if (!g_rebind.active && IsToggleKey(ev->sdl, SDL_SCANCODE_F10)) {
SetTopBarVisible(!g_topBarVisible);
}
if (!g_rebind.active && !g_topBarVisible && IsToggleKey(ev->sdl, SDL_SCANCODE_ESCAPE)) {
g_exitPromptOpen = true;
}
if (IsMouseActivity(ev->sdl)) {
g_lastMouseActivity = Clock::now();
}
}
}
void ReleaseControllers() noexcept {
// Aurora drives the LED white on first PADRead and never clears it, and the
// exit paths terminate the process outright, so do it here.
bool queued = false;
for (uint32_t port = 0; port < PAD_MAX_CONTROLLERS; ++port) {
const s32 index = PADGetIndexForPort(port);
if (index < 0) continue;
if (SDL_Gamepad* pad = PADGetSDLGamepadForIndex(static_cast<u32>(index))) {
SDL_SetGamepadLED(pad, 0, 0, 0);
queued = true;
}
}
constexpr std::array<uint32_t, PAD_MAX_CONTROLLERS> stopAll{
PAD_MOTOR_STOP_HARD, PAD_MOTOR_STOP_HARD, PAD_MOTOR_STOP_HARD, PAD_MOTOR_STOP_HARD};
PADControlAllMotors(stopAll.data());
// SDL hands LED and rumble reports to its own HIDAPI sender thread rather
// than writing them here, so without this the process dies before the
// controller ever receives them.
if (queued) SDL_Delay(120);
}
void Draw() noexcept {
// Wait for the frame worker's DONE phase: it has replayed the previous frame's ImGui draw lists
// and started the next ImGui frame, so all overlay callers can now safely issue ImGui commands.
@@ -1365,6 +1410,7 @@ void Draw() noexcept {
}
DrawFpsOverlay();
DrawTopBar();
DrawExitPrompt();
controller_mapping_wizard::Draw();
// The wizard captures raw presses; keep them out of the game.
const bool inputBlocked = controller_mapping_wizard::IsActive() || g_rebind.active;