Implement UIs using RmlUi

This commit is contained in:
Lexi / Zoe 2026-05-10 23:23:40 +02:00
parent 8039b1276e
commit 7f37be386d
Signed by: binaryDiv
GPG key ID: F8D4956E224DA232
27 changed files with 1457 additions and 42 deletions

View file

@ -3,9 +3,14 @@ module;
export module wrappers.sdl;
// Export submodules
export import wrappers.sdl.clipboard;
export import wrappers.sdl.error;
export import wrappers.sdl.events;
export import wrappers.sdl.init;
export import wrappers.sdl.keyboard;
export import wrappers.sdl.mouse;
export import wrappers.sdl.rect;
export import wrappers.sdl.render;
export import wrappers.sdl.surface;
export import wrappers.sdl.utils;
export import wrappers.sdl.video;

View file

@ -0,0 +1,38 @@
module;
#include <string>
#include <SDL3/SDL_clipboard.h>
export module wrappers.sdl.clipboard;
import wrappers.sdl.error;
import wrappers.sdl.utils;
export namespace sdl
{
/**
* Put UTF-8 text into the clipboard.
*
* Wrapper for SDL_SetClipboardText().
*
* Throws an SDLException on failure.
*/
void set_clipboard_text(const std::string& text)
{
if (!SDL_SetClipboardText(text.c_str())) {
throw SDLException("SDL_SetClipboardText");
}
}
/**
* Get UTF-8 text from the clipboard.
*
* Wrapper for SDL_GetClipboardText().
*
* Returns an empty string on failure.
*/
std::string get_clipboard_text()
{
return wrap_string(SDL_GetClipboardText());
}
}

View file

@ -1,6 +1,6 @@
module;
#include <SDL3/SDL.h>
#include <SDL3/SDL_events.h>
export module wrappers.sdl.events;
@ -10,7 +10,7 @@ export namespace sdl
using Event = SDL_Event;
// Alias for EventType enum
using EventType_t = SDL_EventType;
using EventType = SDL_EventType;
/**
* Wrapper for the SDL_EventType enum.
@ -18,13 +18,30 @@ export namespace sdl
* We're using a namespace here to emulate an enum-like interface, without having to copy the entire enum.
* More constants can be added on demand.
*/
namespace EventType
namespace EventTypes
{
constexpr EventType_t Quit = SDL_EVENT_QUIT;
constexpr EventType_t KeyDown = SDL_EVENT_KEY_DOWN;
constexpr EventType_t KeyUp = SDL_EVENT_KEY_UP;
constexpr EventType_t MouseMotion = SDL_EVENT_MOUSE_MOTION;
constexpr EventType_t MouseButtonUp = SDL_EVENT_MOUSE_BUTTON_UP;
constexpr EventType_t MouseButtonDown = SDL_EVENT_MOUSE_BUTTON_DOWN;
// Application events
constexpr EventType Quit = SDL_EVENT_QUIT;
// Window events
constexpr EventType WindowMouseLeave = SDL_EVENT_WINDOW_MOUSE_LEAVE;
constexpr EventType WindowDisplayScaleChanged = SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED;
constexpr EventType WindowPixelSizeChanged = SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED;
// Keyboard events
constexpr EventType KeyDown = SDL_EVENT_KEY_DOWN;
constexpr EventType KeyUp = SDL_EVENT_KEY_UP;
constexpr EventType TextInput = SDL_EVENT_TEXT_INPUT;
// Mouse events
constexpr EventType MouseMotion = SDL_EVENT_MOUSE_MOTION;
constexpr EventType MouseButtonDown = SDL_EVENT_MOUSE_BUTTON_DOWN;
constexpr EventType MouseButtonUp = SDL_EVENT_MOUSE_BUTTON_UP;
constexpr EventType MouseWheel = SDL_EVENT_MOUSE_WHEEL;
// Touch events
constexpr EventType FingerDown = SDL_EVENT_FINGER_DOWN;
constexpr EventType FingerUp = SDL_EVENT_FINGER_UP;
constexpr EventType FingerMotion = SDL_EVENT_FINGER_MOTION;
}
}

View file

@ -0,0 +1,177 @@
module;
#include <SDL3/SDL_keyboard.h>
#include <SDL3/SDL_keycode.h>
export module wrappers.sdl.keyboard;
export namespace sdl
{
using Keycode = SDL_Keycode;
/**
* SDL keycode constants (SDLK_...).
* This is not a complete collection of all keycodes, only those we need. Add more when needed.
* Namespace is just called "Key" instead of "Keycodes" to keep it short.
*/
namespace Key
{
// clang-format off
constexpr Keycode Unknown = SDLK_UNKNOWN;
constexpr Keycode Return = SDLK_RETURN;
constexpr Keycode Escape = SDLK_ESCAPE;
constexpr Keycode Backspace = SDLK_BACKSPACE;
constexpr Keycode Tab = SDLK_TAB;
constexpr Keycode Space = SDLK_SPACE;
constexpr Keycode DblApostrophe = SDLK_DBLAPOSTROPHE;
constexpr Keycode Plus = SDLK_PLUS;
constexpr Keycode Comma = SDLK_COMMA;
constexpr Keycode Minus = SDLK_MINUS;
constexpr Keycode Period = SDLK_PERIOD;
constexpr Keycode Slash = SDLK_SLASH;
constexpr Keycode Num0 = SDLK_0;
constexpr Keycode Num1 = SDLK_1;
constexpr Keycode Num2 = SDLK_2;
constexpr Keycode Num3 = SDLK_3;
constexpr Keycode Num4 = SDLK_4;
constexpr Keycode Num5 = SDLK_5;
constexpr Keycode Num6 = SDLK_6;
constexpr Keycode Num7 = SDLK_7;
constexpr Keycode Num8 = SDLK_8;
constexpr Keycode Num9 = SDLK_9;
constexpr Keycode Semicolon = SDLK_SEMICOLON;
constexpr Keycode LeftBracket = SDLK_LEFTBRACKET;
constexpr Keycode Backslash = SDLK_BACKSLASH;
constexpr Keycode RightBracket = SDLK_RIGHTBRACKET;
constexpr Keycode Grave = SDLK_GRAVE;
constexpr Keycode A = SDLK_A;
constexpr Keycode B = SDLK_B;
constexpr Keycode C = SDLK_C;
constexpr Keycode D = SDLK_D;
constexpr Keycode E = SDLK_E;
constexpr Keycode F = SDLK_F;
constexpr Keycode G = SDLK_G;
constexpr Keycode H = SDLK_H;
constexpr Keycode I = SDLK_I;
constexpr Keycode J = SDLK_J;
constexpr Keycode K = SDLK_K;
constexpr Keycode L = SDLK_L;
constexpr Keycode M = SDLK_M;
constexpr Keycode N = SDLK_N;
constexpr Keycode O = SDLK_O;
constexpr Keycode P = SDLK_P;
constexpr Keycode Q = SDLK_Q;
constexpr Keycode R = SDLK_R;
constexpr Keycode S = SDLK_S;
constexpr Keycode T = SDLK_T;
constexpr Keycode U = SDLK_U;
constexpr Keycode V = SDLK_V;
constexpr Keycode W = SDLK_W;
constexpr Keycode X = SDLK_X;
constexpr Keycode Y = SDLK_Y;
constexpr Keycode Z = SDLK_Z;
constexpr Keycode Delete = SDLK_DELETE;
constexpr Keycode CapsLock = SDLK_CAPSLOCK;
constexpr Keycode F1 = SDLK_F1;
constexpr Keycode F2 = SDLK_F2;
constexpr Keycode F3 = SDLK_F3;
constexpr Keycode F4 = SDLK_F4;
constexpr Keycode F5 = SDLK_F5;
constexpr Keycode F6 = SDLK_F6;
constexpr Keycode F7 = SDLK_F7;
constexpr Keycode F8 = SDLK_F8;
constexpr Keycode F9 = SDLK_F9;
constexpr Keycode F10 = SDLK_F10;
constexpr Keycode F11 = SDLK_F11;
constexpr Keycode F12 = SDLK_F12;
constexpr Keycode PrintScreen = SDLK_PRINTSCREEN;
constexpr Keycode ScrollLock = SDLK_SCROLLLOCK;
constexpr Keycode Pause = SDLK_PAUSE;
constexpr Keycode Insert = SDLK_INSERT;
constexpr Keycode Home = SDLK_HOME;
constexpr Keycode PageUp = SDLK_PAGEUP;
constexpr Keycode End = SDLK_END;
constexpr Keycode PageDown = SDLK_PAGEDOWN;
constexpr Keycode Right = SDLK_RIGHT;
constexpr Keycode Left = SDLK_LEFT;
constexpr Keycode Down = SDLK_DOWN;
constexpr Keycode Up = SDLK_UP;
constexpr Keycode NumLockClear = SDLK_NUMLOCKCLEAR;
constexpr Keycode NumpadDivide = SDLK_KP_DIVIDE;
constexpr Keycode NumpadMultiply = SDLK_KP_MULTIPLY;
constexpr Keycode NumpadMinus = SDLK_KP_MINUS;
constexpr Keycode NumpadPlus = SDLK_KP_PLUS;
constexpr Keycode NumpadEnter = SDLK_KP_ENTER;
constexpr Keycode Numpad1 = SDLK_KP_1;
constexpr Keycode Numpad2 = SDLK_KP_2;
constexpr Keycode Numpad3 = SDLK_KP_3;
constexpr Keycode Numpad4 = SDLK_KP_4;
constexpr Keycode Numpad5 = SDLK_KP_5;
constexpr Keycode Numpad6 = SDLK_KP_6;
constexpr Keycode Numpad7 = SDLK_KP_7;
constexpr Keycode Numpad8 = SDLK_KP_8;
constexpr Keycode Numpad9 = SDLK_KP_9;
constexpr Keycode Numpad0 = SDLK_KP_0;
constexpr Keycode NumpadPeriod = SDLK_KP_PERIOD;
constexpr Keycode NumpadEquals = SDLK_KP_EQUALS;
constexpr Keycode F13 = SDLK_F13;
constexpr Keycode F14 = SDLK_F14;
constexpr Keycode F15 = SDLK_F15;
constexpr Keycode Help = SDLK_HELP;
constexpr Keycode Clear = SDLK_CLEAR;
constexpr Keycode LCtrl = SDLK_LCTRL;
constexpr Keycode LShift = SDLK_LSHIFT;
constexpr Keycode LAlt = SDLK_LALT;
constexpr Keycode LGui = SDLK_LGUI;
constexpr Keycode RCtrl = SDLK_RCTRL;
constexpr Keycode RShift = SDLK_RSHIFT;
constexpr Keycode RAlt = SDLK_RALT;
constexpr Keycode RGui = SDLK_RGUI;
// clang-format on
}
// Key modifier (e.g. left shift, left ctrl, ...)
using KeyMod = SDL_Keymod;
// Key modifier state: Bitmask of key modifiers (just an alias for KeyMod for slightly better semantics)
using KeyModState = KeyMod;
/**
* SDL key modifier constants (SDL_KMOD_...).
* This is not a complete collection of all key modifiers, only those we need. Add more when needed.
*/
namespace KeyMods
{
// clang-format off
// Single modifiers
constexpr KeyMod None = SDL_KMOD_NONE;
constexpr KeyMod LShift = SDL_KMOD_LSHIFT;
constexpr KeyMod RShift = SDL_KMOD_RSHIFT;
constexpr KeyMod LCtrl = SDL_KMOD_LCTRL;
constexpr KeyMod RCtrl = SDL_KMOD_RCTRL;
constexpr KeyMod LAlt = SDL_KMOD_LALT;
constexpr KeyMod RAlt = SDL_KMOD_RALT;
constexpr KeyMod LGui = SDL_KMOD_LGUI;
constexpr KeyMod RGui = SDL_KMOD_RGUI;
constexpr KeyMod NumLock = SDL_KMOD_NUM;
constexpr KeyMod CapsLock = SDL_KMOD_CAPS;
// Combined modifiers
constexpr KeyModState Ctrl = SDL_KMOD_CTRL; // Any Ctrl key
constexpr KeyModState Shift = SDL_KMOD_SHIFT; // Any Shift key
constexpr KeyModState Alt = SDL_KMOD_ALT; // Any Alt key
constexpr KeyModState Gui = SDL_KMOD_GUI; // Any GUI key
// clang-format on
}
/**
* Get the current key modifier state for the keyboard.
* Wrapper for SDL_GetModState().
*/
constexpr KeyModState get_key_mod_state()
{
return SDL_GetModState();
}
}

View file

@ -0,0 +1,27 @@
module;
#include <cassert>
#include <SDL3/SDL.h>
export module wrappers.sdl.mouse;
export namespace sdl
{
using MouseButtonIndex = uint8_t;
namespace MouseButtons
{
constexpr MouseButtonIndex Left = SDL_BUTTON_LEFT;
constexpr MouseButtonIndex Middle = SDL_BUTTON_MIDDLE;
constexpr MouseButtonIndex Right = SDL_BUTTON_RIGHT;
}
/**
* Wrapper for SDL_CaptureMouse().
*/
constexpr void capture_mouse(const bool enabled)
{
// Ignore errors (probably means it's not supported on this platform)
SDL_CaptureMouse(enabled);
}
}

View file

@ -1,6 +1,6 @@
module;
#include <SDL3/SDL.h>
#include <SDL3/SDL_rect.h>
export module wrappers.sdl.rect;
@ -11,12 +11,12 @@ export namespace sdl
using Point = SDL_Point;
/**
* Wrapper around SDL_FRect using class inheritance.
* Wrapper for SDL_FRect using class inheritance.
*/
class FRect : public SDL_FRect
{
/**
* Wrapper around SDL_RectEmptyFloat().
* Wrapper for SDL_RectEmptyFloat().
* @return True if the floating point rectangle takes no space.
*/
bool is_empty() const
@ -26,12 +26,12 @@ export namespace sdl
};
/**
* Wrapper around SDL_Rect using class inheritance.
* Wrapper for SDL_Rect using class inheritance.
*/
class Rect : public SDL_Rect
{
/**
* Wrapper around SDL_RectEmpty().
* Wrapper for SDL_RectEmpty().
* @return True if the rectangle takes no space.
*/
bool is_empty() const

View file

@ -13,8 +13,10 @@ import wrappers.sdl.video;
export namespace sdl
{
using Vertex = SDL_Vertex;
/**
* Wrapper around SDL_Texture that manages its lifecycle with a unique_ptr.
* Wrapper for SDL_Texture that manages its lifecycle with a unique_ptr.
*/
class Texture
{
@ -53,7 +55,7 @@ export namespace sdl
};
/**
* Wrapper around SDL_Renderer that manages its lifecycle with a unique_ptr.
* Wrapper for SDL_Renderer that manages its lifecycle with a unique_ptr.
*/
class Renderer
{
@ -76,7 +78,41 @@ export namespace sdl
}
/**
* Wrapper around SDL_RenderClear().
* Wrapper for SDL_SetRenderClipRect().
* Uses assert to verify the function returned true.
*/
constexpr void set_clip_rect(const Rect* rect) const
{
if (!SDL_SetRenderClipRect(get_raw(), rect)) {
assert(!"SDL_SetRenderClipRect returned false");
}
}
/**
* Wrapper for SDL_SetRenderDrawBlendMode().
* Uses assert to verify the function returned true.
*/
constexpr void set_draw_blend_mode(SDL_BlendMode blend_mode) const
{
if (!SDL_SetRenderDrawBlendMode(get_raw(), blend_mode)) {
assert(!"SDL_SetRenderDrawBlendMode returned false");
}
}
/**
* Wrapper for SDL_SetRenderVSync().
* Uses assert to verify the function returned true.
*/
constexpr void set_vsync(const int vsync) const
{
// TODO: These functions probably should never fail? Change this to a runtime exception if necessary.
if (!SDL_SetRenderVSync(get_raw(), vsync)) {
assert(!"SDL_SetRenderVSync returned false");
}
}
/**
* Wrapper for SDL_RenderClear().
* Uses assert to verify the function returned true.
*/
constexpr void clear() const
@ -88,7 +124,7 @@ export namespace sdl
}
/**
* Wrapper around SDL_RenderPresent().
* Wrapper for SDL_RenderPresent().
* Uses assert to verify the function returned true.
*/
constexpr void present() const
@ -99,7 +135,7 @@ export namespace sdl
}
/**
* Wrapper around SDL_RenderTexture().
* Wrapper for SDL_RenderTexture().
* Uses assert to verify the function returned true.
*/
constexpr void render_texture(
@ -112,10 +148,28 @@ export namespace sdl
assert(!"SDL_RenderTexture returned false");
}
}
/**
* Wrapper for SDL_RenderGeometry().
* Uses assert to verify the function returned true.
*/
// TODO: Function uses raw SDL_Texture pointer. Add overload for sdl::Texture& when needed.
constexpr void render_geometry(
SDL_Texture* texture,
const Vertex* vertices,
const int num_vertices,
const int* indices,
const int num_indices
) const
{
if (!SDL_RenderGeometry(get_raw(), texture, vertices, num_vertices, indices, num_indices)) {
assert(!"SDL_RenderGeometry returned false");
}
}
};
/**
* Wrapper around SDL_CreateWindowAndRenderer().
* Wrapper for SDL_CreateWindowAndRenderer().
* Returns a pair of a Window and a Renderer object.
* Throws an SDLException on failure.
*/

View file

@ -0,0 +1,98 @@
module;
#include <cassert>
#include <memory>
#include <SDL3/SDL.h>
export module wrappers.sdl.surface;
import utils.memory;
import wrappers.sdl.error;
export namespace sdl
{
/**
* Wrapper for SDL_Texture that manages its lifecycle with a unique_ptr.
*/
class Surface
{
std::unique_ptr<
SDL_Surface,
utils::FuncDeleter<SDL_DestroySurface>
> raw_surface_;
public:
Surface() = delete;
explicit Surface(SDL_Surface* raw_surface)
: raw_surface_{raw_surface}
{}
/**
* Allocate a new surface with a specific pixel format and existing pixel data.
*
* NOTE: No copy is made of the pixel data. Pixel data is not managed automatically; you must free the
* surface before you free the pixel data.
*
* Wrapper for SDL_CreateSurfaceFrom().
*/
static Surface create_from(
const int width,
const int height,
const SDL_PixelFormat format,
void* pixels,
const int pitch
)
{
SDL_Surface* raw_surface = SDL_CreateSurfaceFrom(width, height, format, pixels, pitch);
if (raw_surface == nullptr) {
throw SDLException("SDL_CreateSurfaceFrom");
}
return Surface(raw_surface);
}
constexpr SDL_Surface* get_raw() const
{
assert(raw_surface_);
return raw_surface_.get();
}
// TODO: Wrap SDL_PixelFormat?
constexpr SDL_PixelFormat get_format() const
{
return raw_surface_->format;
}
constexpr int get_width() const
{
return raw_surface_->w;
}
constexpr int get_height() const
{
return raw_surface_->h;
}
constexpr void* get_raw_pixels() const
{
return raw_surface_->pixels;
}
/**
* Wrapper for SDL_ConvertSurface().
*/
Surface convert_format(const SDL_PixelFormat new_format) const
{
SDL_Surface* new_raw_surface = SDL_ConvertSurface(get_raw(), new_format);
// TODO: When does this function fail?
if (new_raw_surface == nullptr) {
throw SDLException("SDL_ConvertSurface");
}
return Surface(new_raw_surface);
}
};
}

View file

@ -0,0 +1,22 @@
module;
#include <string>
#include <SDL3/SDL_stdinc.h>
export module wrappers.sdl.utils;
export namespace sdl
{
/**
* Convert a string returned by an SDL function to std::string and free the original memory using SDL_free().
*
* Usage:
* std::string foo = wrap_string(SDL_FunctionThatReturnsAString());
*/
constexpr std::string wrap_string(char* raw_str)
{
std::string str{raw_str};
SDL_free(raw_str);
return str;
}
}

View file

@ -7,11 +7,13 @@ module;
export module wrappers.sdl.video;
import utils.memory;
import wrappers.sdl.error;
import wrappers.sdl.rect;
export namespace sdl
{
/**
* Wrapper around SDL_Window that manages its lifecycle with a unique_ptr.
* Wrapper for SDL_Window that manages its lifecycle with a unique_ptr.
*/
class Window
{
@ -32,5 +34,68 @@ export namespace sdl
assert(raw_window_);
return raw_window_.get();
}
/**
* Wrapper for SDL_GetWindowPixelDensity().
*/
constexpr float get_pixel_density() const
{
return SDL_GetWindowPixelDensity(get_raw());
}
/**
* Wrapper for SDL_GetWindowDisplayScale().
*/
constexpr float get_display_scale() const
{
return SDL_GetWindowDisplayScale(get_raw());
}
/**
* Wrapper for SDL_GetWindowSize().
*/
std::pair<int, int> get_size() const
{
int width, height;
if (!SDL_GetWindowSize(get_raw(), &width, &height)) {
throw SDLException("SDL_GetWindowSize");
}
return {width, height};
}
/**
* Wrapper for SDL_SetTextInputArea().
* Uses assert to verify the function returned true.
*/
constexpr void set_text_input_area(const Rect* rect, const int cursor) const
{
if (!SDL_SetTextInputArea(get_raw(), rect, cursor)) {
assert(!"SDL_SetTextInputArea returned false");
}
}
/**
* Wrapper for SDL_StartTextInput().
* Uses assert to verify the function returned true.
*/
constexpr void start_text_input() const
{
if (!SDL_StartTextInput(get_raw())) {
assert(!"SDL_StartTextInput returned false");
}
}
/**
* Wrapper for SDL_StopTextInput().
* Uses assert to verify the function returned true.
*/
constexpr void stop_text_input() const
{
if (!SDL_StopTextInput(get_raw())) {
assert(!"SDL_StopTextInput returned false");
}
}
};
}

View file

@ -9,20 +9,35 @@ export module wrappers.sdl_image;
import utils.memory;
import wrappers.sdl.error;
import wrappers.sdl.render;
import wrappers.sdl.surface;
export namespace sdl_image
{
/**
* Wrapper around IMG_LoadTexture().
* Wrapper for IMG_Load().
*/
sdl::Texture LoadTexture(const sdl::Renderer& renderer, const std::string& filename)
sdl::Surface load(const std::string& filename)
{
SDL_Texture* sdl_texture = IMG_LoadTexture(renderer.get_raw(), filename.c_str());
SDL_Surface* raw_surface = IMG_Load(filename.c_str());
if (sdl_texture == nullptr) {
if (raw_surface == nullptr) {
throw sdl::SDLException("IMG_Load");
}
return sdl::Surface{raw_surface};
}
/**
* Wrapper for IMG_LoadTexture().
*/
sdl::Texture load_texture(const sdl::Renderer& renderer, const std::string& filename)
{
SDL_Texture* raw_texture = IMG_LoadTexture(renderer.get_raw(), filename.c_str());
if (raw_texture == nullptr) {
throw sdl::SDLException("IMG_LoadTexture");
}
return sdl::Texture{sdl_texture};
return sdl::Texture{raw_texture};
}
}