v0.1.0: software map renderer + SDL3 window viewer

This commit is contained in:
EnderTheCoder
2026-09-12 01:02:49 +08:00
parent 3ad380c81e
commit b35372da52
13 changed files with 659 additions and 31 deletions
+93
View File
@@ -0,0 +1,93 @@
module;
#include <SDL3/SDL.h>
#include <algorithm>
#include <string>
export module ra3.ui;
export import ra3.core;
import ra3.render;
/**
* A minimal SDL3 window for viewing a rendered scene.
*
* Controls: drag with the left mouse button to pan, wheel to zoom, Esc/close
* to quit. When no display is available `run_viewer` returns false so the
* caller can fall back to an offscreen image.
*/
export namespace ra3::ui {
/** Window parameters. */
struct viewer_options {
std::string title = "OpenRA3";
int width = 1024;
int height = 768;
};
/**
* Show `scene` in a window until the user quits.
*
* @return true if the window ran; false if video init or window creation
* failed (typically no display).
*/
[[nodiscard]] inline auto run_viewer(const ra3::render::image &scene, const viewer_options &options) -> bool {
if (scene.empty()) return false;
if (!SDL_Init(SDL_INIT_VIDEO)) return false;
SDL_Window *window = SDL_CreateWindow(options.title.c_str(), options.width, options.height, SDL_WINDOW_RESIZABLE);
if (window == nullptr) {
SDL_Quit();
return false;
}
SDL_Renderer *renderer = SDL_CreateRenderer(window, nullptr);
if (renderer == nullptr) {
SDL_DestroyWindow(window);
SDL_Quit();
return false;
}
SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, static_cast<int>(scene.width()),
static_cast<int>(scene.height()));
if (texture == nullptr) {
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
return false;
}
SDL_UpdateTexture(texture, nullptr, scene.data(), static_cast<int>(scene.width()) * 4);
SDL_SetTextureScaleMode(texture, SDL_SCALEMODE_NEAREST);
float zoom = 1.0F;
float pan_x = 0.0F;
float pan_y = 0.0F;
bool running = true;
SDL_Event event;
while (running) {
while (SDL_PollEvent(&event)) {
if (event.type == SDL_EVENT_QUIT) {
running = false;
} else if (event.type == SDL_EVENT_KEY_DOWN && event.key.key == SDLK_ESCAPE) {
running = false;
} else if (event.type == SDL_EVENT_MOUSE_WHEEL) {
zoom *= event.wheel.y > 0.0F ? 1.1F : (1.0F / 1.1F);
zoom = std::clamp(zoom, 0.1F, 20.0F);
} else if (event.type == SDL_EVENT_MOUSE_MOTION && (event.motion.state & SDL_BUTTON_LMASK) != 0U) {
pan_x += event.motion.xrel;
pan_y += event.motion.yrel;
}
}
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderClear(renderer);
const SDL_FRect dst{pan_x, pan_y, static_cast<float>(scene.width()) * zoom, static_cast<float>(scene.height()) * zoom};
SDL_RenderTexture(renderer, texture, nullptr, &dst);
SDL_RenderPresent(renderer);
}
SDL_DestroyTexture(texture);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
return true;
}
}