feat: add support for stretching ImGui output to window and return to selector functionality

This commit is contained in:
moonpower
2026-02-02 00:29:25 +03:00
parent 25add6ada7
commit 7efbae3f5f
7 changed files with 104 additions and 17 deletions

View File

@@ -28,6 +28,7 @@ struct FrontendSettings {
WindowIcon icon = WindowIcon::Rpog; WindowIcon icon = WindowIcon::Rpog;
std::string language = "en"; std::string language = "en";
bool showImGuiDebugPanel = true; bool showImGuiDebugPanel = true;
bool stretchImGuiOutputToWindow = false;
static Theme themeFromString(std::string inString); static Theme themeFromString(std::string inString);
static const char* themeToString(Theme theme); static const char* themeToString(Theme theme);

View File

@@ -35,6 +35,9 @@ class FrontendSDL {
bool loadROM(const std::filesystem::path& path); bool loadROM(const std::filesystem::path& path);
void run(); void run();
std::optional<std::filesystem::path> selectGame(); std::optional<std::filesystem::path> selectGame();
#ifdef IMGUI_FRONTEND
bool consumeReturnToSelector();
#endif
u32 getMapping(InputMappings::Scancode scancode) { return keyboardMappings.getMapping(scancode); } u32 getMapping(InputMappings::Scancode scancode) { return keyboardMappings.getMapping(scancode); }
SDL_Window* window = nullptr; SDL_Window* window = nullptr;
@@ -60,6 +63,7 @@ class FrontendSDL {
bool keyboardAnalogX = false; bool keyboardAnalogX = false;
bool keyboardAnalogY = false; bool keyboardAnalogY = false;
bool emuPaused = false; bool emuPaused = false;
bool returnToSelector = false;
private: private:
void setupControllerSensors(SDL_GameController* controller); void setupControllerSensors(SDL_GameController* controller);

View File

@@ -26,6 +26,7 @@ class ImGuiLayer {
void setPaused(bool paused) { isPaused = paused; } void setPaused(bool paused) { isPaused = paused; }
void setPauseCallback(std::function<void(bool)> callback) { onPauseChange = std::move(callback); } void setPauseCallback(std::function<void(bool)> callback) { onPauseChange = std::move(callback); }
void setVsyncCallback(std::function<void(bool)> callback) { onVsyncChange = std::move(callback); } void setVsyncCallback(std::function<void(bool)> callback) { onVsyncChange = std::move(callback); }
void setExitToSelectorCallback(std::function<void()> callback) { onExitToSelector = std::move(callback); }
private: private:
void drawDebugPanel(); void drawDebugPanel();
@@ -45,6 +46,7 @@ class ImGuiLayer {
std::function<void(bool)> onPauseChange; std::function<void(bool)> onPauseChange;
std::function<void(bool)> onVsyncChange; std::function<void(bool)> onVsyncChange;
std::function<void()> onExitToSelector;
}; };
#endif #endif

View File

@@ -153,6 +153,7 @@ void EmulatorConfig::load() {
frontendSettings.icon = FrontendSettings::iconFromString(toml::find_or<std::string>(ui, "WindowIcon", "rpog")); frontendSettings.icon = FrontendSettings::iconFromString(toml::find_or<std::string>(ui, "WindowIcon", "rpog"));
frontendSettings.language = toml::find_or<std::string>(ui, "Language", "en"); frontendSettings.language = toml::find_or<std::string>(ui, "Language", "en");
frontendSettings.showImGuiDebugPanel = toml::find_or<toml::boolean>(ui, "ShowImGuiDebugPanel", true); frontendSettings.showImGuiDebugPanel = toml::find_or<toml::boolean>(ui, "ShowImGuiDebugPanel", true);
frontendSettings.stretchImGuiOutputToWindow = toml::find_or<toml::boolean>(ui, "StretchImGuiOutputToWindow", false);
} }
} }
} }
@@ -222,6 +223,7 @@ void EmulatorConfig::save() {
data["UI"]["WindowIcon"] = std::string(FrontendSettings::iconToString(frontendSettings.icon)); data["UI"]["WindowIcon"] = std::string(FrontendSettings::iconToString(frontendSettings.icon));
data["UI"]["Language"] = frontendSettings.language; data["UI"]["Language"] = frontendSettings.language;
data["UI"]["ShowImGuiDebugPanel"] = frontendSettings.showImGuiDebugPanel; data["UI"]["ShowImGuiDebugPanel"] = frontendSettings.showImGuiDebugPanel;
data["UI"]["StretchImGuiOutputToWindow"] = frontendSettings.stretchImGuiOutputToWindow;
std::ofstream file(path, std::ios::out); std::ofstream file(path, std::ios::out);
file << data; file << data;

View File

@@ -124,6 +124,24 @@ void FrontendSDL::initialize(SDL_Window* existingWindow, SDL_GLContext existingC
ScreenLayout::calculateCoordinates( ScreenLayout::calculateCoordinates(
screenCoordinates, u32(windowWidth), u32(windowHeight), emu.getConfig().topScreenSize, emu.getConfig().screenLayout screenCoordinates, u32(windowWidth), u32(windowHeight), emu.getConfig().topScreenSize, emu.getConfig().screenLayout
); );
#ifdef IMGUI_FRONTEND
if (emu.getConfig().frontendSettings.stretchImGuiOutputToWindow) {
int drawableW = 0;
int drawableH = 0;
SDL_Window* currentWindow = existingWindow ? existingWindow : SDL_GL_GetCurrentWindow();
if (currentWindow) {
SDL_GL_GetDrawableSize(currentWindow, &drawableW, &drawableH);
if (drawableW > 0 && drawableH > 0) {
windowWidth = u32(drawableW);
windowHeight = u32(drawableH);
emu.setOutputSize(windowWidth, windowHeight);
ScreenLayout::calculateCoordinates(
screenCoordinates, windowWidth, windowHeight, emu.getConfig().topScreenSize, emu.getConfig().screenLayout
);
}
}
}
#endif
if (needOpenGL) { if (needOpenGL) {
#ifdef IMGUI_FRONTEND #ifdef IMGUI_FRONTEND
@@ -266,6 +284,12 @@ void FrontendSDL::initialize(SDL_Window* existingWindow, SDL_GLContext existingC
imgui->init(); imgui->init();
imgui->setPauseCallback([this](bool paused) { setPaused(paused); }); imgui->setPauseCallback([this](bool paused) { setPaused(paused); });
imgui->setVsyncCallback([this](bool enabled) { SDL_GL_SetSwapInterval(enabled ? 1 : 0); }); imgui->setVsyncCallback([this](bool enabled) { SDL_GL_SetSwapInterval(enabled ? 1 : 0); });
imgui->setExitToSelectorCallback([this]() {
returnToSelector = true;
programRunning = false;
emu.reset(Emulator::ReloadOption::NoReload);
emu.romType = ROMType::None;
});
#endif #endif
} }
@@ -287,6 +311,23 @@ void FrontendSDL::run() {
holdingRightClick = false; holdingRightClick = false;
while (programRunning) { while (programRunning) {
#ifdef IMGUI_FRONTEND
const auto& cfg = emu.getConfig();
if (cfg.frontendSettings.stretchImGuiOutputToWindow) {
int drawableW = 0;
int drawableH = 0;
SDL_GL_GetDrawableSize(window, &drawableW, &drawableH);
if (drawableW > 0 && drawableH > 0) {
windowWidth = u32(drawableW);
windowHeight = u32(drawableH);
emu.setOutputSize(windowWidth, windowHeight);
ScreenLayout::calculateCoordinates(
screenCoordinates, windowWidth, windowHeight, cfg.topScreenSize, cfg.screenLayout
);
glViewport(0, 0, drawableW, drawableH);
}
}
#endif
#ifdef PANDA3DS_ENABLE_HTTP_SERVER #ifdef PANDA3DS_ENABLE_HTTP_SERVER
httpServer.processActions(); httpServer.processActions();
#endif #endif
@@ -559,12 +600,19 @@ void FrontendSDL::run() {
case SDL_WINDOWEVENT: { case SDL_WINDOWEVENT: {
auto type = event.window.event; auto type = event.window.event;
if (type == SDL_WINDOWEVENT_RESIZED) { if (type == SDL_WINDOWEVENT_RESIZED) {
windowWidth = event.window.data1; int drawableW = event.window.data1;
windowHeight = event.window.data2; int drawableH = event.window.data2;
#ifdef IMGUI_FRONTEND
if (emu.getConfig().frontendSettings.stretchImGuiOutputToWindow) {
SDL_GL_GetDrawableSize(window, &drawableW, &drawableH);
}
#endif
windowWidth = u32(drawableW);
windowHeight = u32(drawableH);
const auto& config = emu.getConfig(); const auto& config = emu.getConfig();
ScreenLayout::calculateCoordinates( ScreenLayout::calculateCoordinates(
screenCoordinates, u32(windowWidth), u32(windowHeight), emu.getConfig().topScreenSize, emu.getConfig().screenLayout screenCoordinates, windowWidth, windowHeight, config.topScreenSize, config.screenLayout
); );
emu.setOutputSize(windowWidth, windowHeight); emu.setOutputSize(windowWidth, windowHeight);
@@ -640,13 +688,23 @@ void FrontendSDL::run() {
} }
#ifdef IMGUI_FRONTEND #ifdef IMGUI_FRONTEND
if (imgui) { if (imgui && !returnToSelector) {
imgui->shutdown(); imgui->shutdown();
imgui.reset(); imgui.reset();
} }
#endif #endif
} }
#ifdef IMGUI_FRONTEND
bool FrontendSDL::consumeReturnToSelector() {
if (!returnToSelector) {
return false;
}
returnToSelector = false;
return true;
}
#endif
void FrontendSDL::setupControllerSensors(SDL_GameController* controller) { void FrontendSDL::setupControllerSensors(SDL_GameController* controller) {
bool haveGyro = SDL_GameControllerHasSensor(controller, SDL_SENSOR_GYRO) == SDL_TRUE; bool haveGyro = SDL_GameControllerHasSensor(controller, SDL_SENSOR_GYRO) == SDL_TRUE;
bool haveAccelerometer = SDL_GameControllerHasSensor(controller, SDL_SENSOR_ACCEL) == SDL_TRUE; bool haveAccelerometer = SDL_GameControllerHasSensor(controller, SDL_SENSOR_ACCEL) == SDL_TRUE;

View File

@@ -407,9 +407,13 @@ void ImGuiLayer::drawPausePanel() {
showSettings = true; showSettings = true;
} }
if (ImGui::Button("Quit", ImVec2(-1, 0))) { if (ImGui::Button("Quit", ImVec2(-1, 0))) {
SDL_Event quit{}; if (onExitToSelector) {
quit.type = SDL_QUIT; onExitToSelector();
SDL_PushEvent(&quit); } else {
SDL_Event quit{};
quit.type = SDL_QUIT;
SDL_PushEvent(&quit);
}
} }
ImGui::End(); ImGui::End();
@@ -518,6 +522,7 @@ void ImGuiLayer::drawSettingsPanel() {
cfg.frontendSettings.showImGuiDebugPanel = showDebugPanel; cfg.frontendSettings.showImGuiDebugPanel = showDebugPanel;
showDebug = showDebugPanel; showDebug = showDebugPanel;
} }
ImGui::Checkbox("Stretch Output To Window", &cfg.frontendSettings.stretchImGuiOutputToWindow);
} }
if (ImGui::CollapsingHeader("Graphics")) { if (ImGui::CollapsingHeader("Graphics")) {

View File

@@ -35,6 +35,30 @@ int emu_main(int argc, char *argv[]) {
FrontendSDL app; FrontendSDL app;
#endif #endif
#ifdef IMGUI_FRONTEND
std::optional<std::filesystem::path> forcedRom;
if (argc > 1) {
forcedRom = std::filesystem::current_path() / argv[1];
}
while (true) {
std::optional<std::filesystem::path> selected = forcedRom;
if (!selected.has_value()) {
selected = app.selectGame();
if (!selected.has_value()) {
return 0;
}
}
if (!app.loadROM(*selected)) {
Helpers::panic("Failed to load ROM file: %s", selected->string().c_str());
}
forcedRom.reset();
app.run();
if (!app.consumeReturnToSelector()) {
break;
}
}
#else
if (argc > 1) { if (argc > 1) {
auto romPath = std::filesystem::current_path() / argv[1]; auto romPath = std::filesystem::current_path() / argv[1];
if (!app.loadROM(romPath)) { if (!app.loadROM(romPath)) {
@@ -42,20 +66,11 @@ int emu_main(int argc, char *argv[]) {
Helpers::panic("Failed to load ROM file: %s", romPath.string().c_str()); Helpers::panic("Failed to load ROM file: %s", romPath.string().c_str());
} }
} else { } else {
#ifdef IMGUI_FRONTEND
auto selected = app.selectGame();
if (!selected.has_value()) {
return 0;
}
if (!app.loadROM(*selected)) {
Helpers::panic("Failed to load ROM file: %s", selected->string().c_str());
}
#else
printf("No ROM inserted! Load a ROM by dragging and dropping it into the emulator window!\n"); printf("No ROM inserted! Load a ROM by dragging and dropping it into the emulator window!\n");
#endif
} }
app.run(); app.run();
#endif
return 0; return 0;
} }