Compare commits
18 commits
main
...
engine-pro
| Author | SHA1 | Date | |
|---|---|---|---|
| eb98beac43 | |||
| 393c6529e2 | |||
| acd53e17d1 | |||
| d5d480d854 | |||
| 5a53457817 | |||
| ae1df9bc8b | |||
| d256de19c5 | |||
| 7b8aac69fb | |||
| 7f37be386d | |||
| 8039b1276e | |||
| bb43d308c0 | |||
| bb9d108102 | |||
| d4c91450d9 | |||
| b8bd56392c | |||
| e37eb0b03f | |||
| d180b8a5b4 | |||
| 4277f4c818 | |||
| 5c3e0e3a86 |
44 changed files with 3502 additions and 241 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -14,3 +14,7 @@ __pycache__/
|
||||||
/build
|
/build
|
||||||
/build.ninja
|
/build.ninja
|
||||||
/shuriken.override.yaml
|
/shuriken.override.yaml
|
||||||
|
|
||||||
|
# Dependencies
|
||||||
|
/vendor
|
||||||
|
!/vendor/README.md
|
||||||
|
|
|
||||||
BIN
assets/fonts/LatoLatin-Regular.ttf
Normal file
BIN
assets/fonts/LatoLatin-Regular.ttf
Normal file
Binary file not shown.
BIN
assets/neofox.png
Normal file
BIN
assets/neofox.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
BIN
assets/sprites/neocat_64.png
Normal file
BIN
assets/sprites/neocat_64.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.5 KiB |
BIN
assets/sprites/neofox_64.png
Normal file
BIN
assets/sprites/neofox_64.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.4 KiB |
BIN
assets/tilesets/terrain.png
Normal file
BIN
assets/tilesets/terrain.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 622 B |
88
assets/ui/base.rcss
Normal file
88
assets/ui/base.rcss
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
/* Base style sheet based on: https://mikke89.github.io/RmlUiDoc/pages/rml/html4_style_sheet.html */
|
||||||
|
|
||||||
|
body, div, h1, h2, h3, h4, h5, h6, p, hr, pre {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 2em;
|
||||||
|
margin: .67em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 1.5em;
|
||||||
|
margin: .75em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 1.17em;
|
||||||
|
margin: .83em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
margin: 1.12em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h5 {
|
||||||
|
font-size: .83em;
|
||||||
|
margin: 1.5em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h6 {
|
||||||
|
font-size: .75em;
|
||||||
|
margin: 1.67em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, h2, h3, h4, h5, h6, strong {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
em {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
white-space: pre;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
border-width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: table;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr {
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: table-row;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: table-cell;
|
||||||
|
}
|
||||||
|
|
||||||
|
col {
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: table-column;
|
||||||
|
}
|
||||||
|
|
||||||
|
colgroup {
|
||||||
|
display: table-column-group;
|
||||||
|
}
|
||||||
|
|
||||||
|
thead, tbody, tfoot {
|
||||||
|
display: table-row-group;
|
||||||
|
}
|
||||||
|
|
||||||
|
tabset tabs {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
background-color: white;
|
||||||
|
color: black;
|
||||||
|
cursor: text;
|
||||||
|
}
|
||||||
16
assets/ui/main_ui.rml
Normal file
16
assets/ui/main_ui.rml
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
<rml>
|
||||||
|
<head>
|
||||||
|
<link type="text/rcss" href="base.rcss"/>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: LatoLatin;
|
||||||
|
font-size: 20dp;
|
||||||
|
color: white;
|
||||||
|
background-color: #330033;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>Meow!</p>
|
||||||
|
</body>
|
||||||
|
</rml>
|
||||||
|
|
@ -1,10 +1,22 @@
|
||||||
defaults:
|
defaults:
|
||||||
cpp_standard: c++23
|
cpp_standard: c++23
|
||||||
cpp_flags: -Wall -Wextra -Werror -pedantic
|
cpp_flags: >-
|
||||||
linker_args: -lSDL3 -lSDL3_image
|
-Wall
|
||||||
|
-Wextra
|
||||||
|
-pedantic
|
||||||
|
-Ivendor/RmlUi/Include
|
||||||
|
linker_args: >-
|
||||||
|
-lSDL3
|
||||||
|
-lSDL3_image
|
||||||
|
-lfreetype
|
||||||
|
vendor/RmlUi/Build/librmlui_debugger.a
|
||||||
|
vendor/RmlUi/Build/librmlui.a
|
||||||
|
|
||||||
|
enable_import_std: true
|
||||||
|
|
||||||
default_target: linux
|
default_target: linux
|
||||||
|
|
||||||
targets:
|
targets:
|
||||||
linux:
|
linux:
|
||||||
output_file: rutile_game
|
output_file: rutile_game
|
||||||
|
# TODO: In a release build, set -DNDEBUG
|
||||||
|
|
|
||||||
31
src/main.cpp
31
src/main.cpp
|
|
@ -1,27 +1,30 @@
|
||||||
import sdl_app;
|
import rutile.app;
|
||||||
|
|
||||||
#define SDL_MAIN_USE_CALLBACKS
|
#define SDL_MAIN_USE_CALLBACKS
|
||||||
#include <SDL3/SDL_main.h>
|
#include <SDL3/SDL_main.h>
|
||||||
|
|
||||||
SDL_AppResult SDL_AppInit(void** appstate, int /*argc*/, char** /*argv*/)
|
SDL_AppResult SDL_AppInit(void** appstate, const int /*argc*/, char** /*argv*/)
|
||||||
{
|
{
|
||||||
*appstate = new AppState;
|
auto* app = new rutile::App;
|
||||||
return sdl_app_init(static_cast<AppState*>(*appstate));
|
*appstate = app;
|
||||||
}
|
return static_cast<SDL_AppResult>(app->initialize());
|
||||||
|
|
||||||
SDL_AppResult SDL_AppIterate(void* appstate)
|
|
||||||
{
|
|
||||||
return sdl_app_iterate(static_cast<AppState*>(appstate));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event)
|
SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event)
|
||||||
{
|
{
|
||||||
return sdl_app_event(static_cast<AppState*>(appstate), event);
|
auto* app = static_cast<rutile::App*>(appstate);
|
||||||
|
return static_cast<SDL_AppResult>(app->handle_event(*event));
|
||||||
}
|
}
|
||||||
|
|
||||||
void SDL_AppQuit(void* appstate, SDL_AppResult /*result*/)
|
SDL_AppResult SDL_AppIterate(void* appstate)
|
||||||
{
|
{
|
||||||
const auto* game_app_state = static_cast<AppState*>(appstate);
|
auto* app = static_cast<rutile::App*>(appstate);
|
||||||
sdl_app_shutdown(game_app_state);
|
return static_cast<SDL_AppResult>(app->iterate());
|
||||||
delete game_app_state;
|
}
|
||||||
|
|
||||||
|
void SDL_AppQuit(void* appstate, const SDL_AppResult /*result*/)
|
||||||
|
{
|
||||||
|
const auto* app = static_cast<rutile::App*>(appstate);
|
||||||
|
app->shutdown();
|
||||||
|
delete app;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
86
src/rutile/app.cppm
Normal file
86
src/rutile/app.cppm
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
module;
|
||||||
|
|
||||||
|
#include <SDL3/SDL_init.h>
|
||||||
|
|
||||||
|
export module rutile.app;
|
||||||
|
|
||||||
|
import std;
|
||||||
|
import rutile.config;
|
||||||
|
import rutile.core.engine;
|
||||||
|
import rutile.game.game;
|
||||||
|
import wrappers.sdl;
|
||||||
|
|
||||||
|
export namespace rutile
|
||||||
|
{
|
||||||
|
class App
|
||||||
|
{
|
||||||
|
std::unique_ptr<Engine> engine_{nullptr};
|
||||||
|
std::unique_ptr<Game> game_{nullptr};
|
||||||
|
|
||||||
|
public:
|
||||||
|
App() = default;
|
||||||
|
|
||||||
|
sdl::AppResult initialize()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// Set SDL application metadata
|
||||||
|
sdl::set_app_metadata_property(SDL_PROP_APP_METADATA_NAME_STRING, config::app_name);
|
||||||
|
sdl::set_app_metadata_property(SDL_PROP_APP_METADATA_VERSION_STRING, config::app_version);
|
||||||
|
sdl::set_app_metadata_property(SDL_PROP_APP_METADATA_IDENTIFIER_STRING, config::app_identifier);
|
||||||
|
sdl::set_app_metadata_property(SDL_PROP_APP_METADATA_CREATOR_STRING, config::app_creator);
|
||||||
|
sdl::set_app_metadata_property(SDL_PROP_APP_METADATA_COPYRIGHT_STRING, config::app_copyright);
|
||||||
|
sdl::set_app_metadata_property(SDL_PROP_APP_METADATA_URL_STRING, config::app_url);
|
||||||
|
sdl::set_app_metadata_property(SDL_PROP_APP_METADATA_TYPE_STRING, "game");
|
||||||
|
|
||||||
|
// Initialize SDL subsystems
|
||||||
|
sdl::initialize_sdl(sdl::InitFlags::Video | sdl::InitFlags::Events);
|
||||||
|
|
||||||
|
// Create engine (includes window and renderer) and game state
|
||||||
|
engine_ = Engine::create();
|
||||||
|
game_ = Game::create(*engine_);
|
||||||
|
}
|
||||||
|
catch (const std::runtime_error& e) {
|
||||||
|
std::cerr << "Unhandled exception during initialization: " << e.what() << '\n';
|
||||||
|
return sdl::AppResult::Failure;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sdl::AppResult::Continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
sdl::AppResult handle_event(const sdl::Event& event)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if (!engine_->handle_event(event)) {
|
||||||
|
game_->handle_event(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (const std::runtime_error& e) {
|
||||||
|
std::cerr << "Unhandled exception during event handling: " << e.what() << '\n';
|
||||||
|
return sdl::AppResult::Failure;
|
||||||
|
}
|
||||||
|
|
||||||
|
return engine_->keep_running() ? sdl::AppResult::Continue : sdl::AppResult::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
sdl::AppResult iterate()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
engine_->update();
|
||||||
|
game_->update();
|
||||||
|
game_->render();
|
||||||
|
}
|
||||||
|
catch (const std::runtime_error& e) {
|
||||||
|
std::cerr << "Unhandled exception during updating: " << e.what() << '\n';
|
||||||
|
return sdl::AppResult::Failure;
|
||||||
|
}
|
||||||
|
|
||||||
|
return engine_->keep_running() ? sdl::AppResult::Continue : sdl::AppResult::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
void shutdown() const
|
||||||
|
{
|
||||||
|
engine_->shutdown();
|
||||||
|
game_->shutdown();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
36
src/rutile/config.cppm
Normal file
36
src/rutile/config.cppm
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
module;
|
||||||
|
|
||||||
|
#ifdef NDEBUG
|
||||||
|
#define DEBUG_BOOL false
|
||||||
|
#else
|
||||||
|
#define DEBUG_BOOL true
|
||||||
|
#endif
|
||||||
|
|
||||||
|
export module rutile.config;
|
||||||
|
|
||||||
|
import std;
|
||||||
|
|
||||||
|
export namespace rutile::config
|
||||||
|
{
|
||||||
|
constexpr auto debug = DEBUG_BOOL;
|
||||||
|
|
||||||
|
constexpr auto app_name = "Rutile Game Prototype";
|
||||||
|
constexpr auto app_version = "0.0.1-dev";
|
||||||
|
constexpr auto app_identifier = "dev.binarydiv.rutile_game";
|
||||||
|
constexpr auto app_creator = "binaryDiv";
|
||||||
|
constexpr auto app_copyright = "Copyright (c) 2025 binaryDiv";
|
||||||
|
constexpr auto app_url = "https://git.0xbd.space/binaryDiv/rutile-game";
|
||||||
|
|
||||||
|
constexpr auto window_width = 640;
|
||||||
|
constexpr auto window_height = 480;
|
||||||
|
|
||||||
|
constexpr auto get_window_title()
|
||||||
|
{
|
||||||
|
if constexpr (debug) {
|
||||||
|
return std::format("{} ({}) [DEBUG]", app_name, app_version);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return std::format("{} ({})", app_name, app_version);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
82
src/rutile/core/drawing/sprite.cppm
Normal file
82
src/rutile/core/drawing/sprite.cppm
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
module;
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
export module rutile.core.drawing.sprite;
|
||||||
|
|
||||||
|
import std;
|
||||||
|
import rutile.core.render_server;
|
||||||
|
import wrappers.sdl;
|
||||||
|
|
||||||
|
export namespace rutile
|
||||||
|
{
|
||||||
|
class Sprite
|
||||||
|
{
|
||||||
|
// Texture ID (managed by TextureManager in RenderServer)
|
||||||
|
TextureID texture_id_;
|
||||||
|
|
||||||
|
// Texture source boundaries: which part of the texture to render
|
||||||
|
sdl::FRect texture_boundaries_;
|
||||||
|
|
||||||
|
// Texture offset: static offset that is added to the render position (BEFORE scale is applied)
|
||||||
|
sdl::FPoint texture_offset_{0, 0};
|
||||||
|
|
||||||
|
// Texture scale: factors applied to width/height (AFTER offset is applied)
|
||||||
|
// TODO: Proper vector class?
|
||||||
|
sdl::FPoint texture_scale_{1, 1};
|
||||||
|
|
||||||
|
// Sprite position: where to render the sprite (world coordinates)
|
||||||
|
sdl::FPoint position_{0, 0};
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit Sprite(
|
||||||
|
const TextureID texture_id,
|
||||||
|
const sdl::FRect texture_boundaries,
|
||||||
|
const sdl::FPoint position,
|
||||||
|
const sdl::FPoint texture_offset = {0, 0},
|
||||||
|
const sdl::FPoint texture_scale = {1, 1}
|
||||||
|
)
|
||||||
|
: texture_id_{texture_id},
|
||||||
|
texture_boundaries_{texture_boundaries},
|
||||||
|
texture_offset_{texture_offset},
|
||||||
|
texture_scale_{texture_scale},
|
||||||
|
position_{position}
|
||||||
|
{
|
||||||
|
assert(texture_boundaries_.w > 0 && texture_boundaries_.h > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Sprite create_from_texture(
|
||||||
|
RenderServer& render_server,
|
||||||
|
const std::string& filename,
|
||||||
|
std::optional<sdl::FRect> texture_boundaries,
|
||||||
|
auto... args
|
||||||
|
)
|
||||||
|
{
|
||||||
|
const TextureID texture_id = render_server.load_texture(filename);
|
||||||
|
|
||||||
|
if (!texture_boundaries.has_value()) {
|
||||||
|
const sdl::Texture& texture = render_server.get_texture(texture_id);
|
||||||
|
texture_boundaries = texture.get_boundaries();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Sprite(texture_id, texture_boundaries.value(), args...);
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_position(const sdl::FPoint new_position)
|
||||||
|
{
|
||||||
|
position_ = new_position;
|
||||||
|
}
|
||||||
|
|
||||||
|
void draw(const RenderServer& render_server) const
|
||||||
|
{
|
||||||
|
const sdl::FRect dest_rect{
|
||||||
|
position_.x + (texture_scale_.x * texture_offset_.x),
|
||||||
|
position_.y + (texture_scale_.y * texture_offset_.y),
|
||||||
|
texture_scale_.x * texture_boundaries_.w,
|
||||||
|
texture_scale_.y * texture_boundaries_.h,
|
||||||
|
};
|
||||||
|
|
||||||
|
render_server.render_texture(texture_id_, &texture_boundaries_, &dest_rect);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
108
src/rutile/core/drawing/tile_map.cppm
Normal file
108
src/rutile/core/drawing/tile_map.cppm
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
module;
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
export module rutile.core.drawing.tile_map;
|
||||||
|
|
||||||
|
import std;
|
||||||
|
import rutile.core.drawing.tile_set;
|
||||||
|
import rutile.core.render_server;
|
||||||
|
import wrappers.sdl;
|
||||||
|
|
||||||
|
export namespace rutile
|
||||||
|
{
|
||||||
|
// Restrict map size to 16 bit per dimension, so that width*height still fits within size_t
|
||||||
|
using MapCoord = std::uint16_t;
|
||||||
|
|
||||||
|
class TileMap
|
||||||
|
{
|
||||||
|
// Tile set ID
|
||||||
|
// TODO: Needed later when we centralize tile set management using a ResourceManager
|
||||||
|
[[maybe_unused]]
|
||||||
|
TileSetID tile_set_id_;
|
||||||
|
|
||||||
|
// Tile size
|
||||||
|
int tile_size_;
|
||||||
|
|
||||||
|
// Map boundaries
|
||||||
|
MapCoord width_;
|
||||||
|
MapCoord height_;
|
||||||
|
|
||||||
|
// Tile map origin in world coordinates
|
||||||
|
sdl::FPoint position_{0, 0};
|
||||||
|
|
||||||
|
// Tile map data: Tiles in a continuous array
|
||||||
|
std::unique_ptr<TileID[]> tiles_{nullptr};
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit TileMap(
|
||||||
|
const TileSetID tile_set_id,
|
||||||
|
const int tile_size,
|
||||||
|
const MapCoord width,
|
||||||
|
const MapCoord height,
|
||||||
|
const sdl::FPoint position = {0, 0}
|
||||||
|
)
|
||||||
|
: tile_set_id_(tile_set_id),
|
||||||
|
tile_size_(tile_size),
|
||||||
|
width_(width),
|
||||||
|
height_(height),
|
||||||
|
position_{position}
|
||||||
|
{
|
||||||
|
// TODO: This maybe shouldn't be an assert...
|
||||||
|
assert(width_ > 0 && height_ > 0);
|
||||||
|
tiles_ = std::make_unique<TileID[]>(
|
||||||
|
static_cast<std::size_t>(width * height)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr std::size_t map_coords_to_index(const MapCoord x, const MapCoord y) const
|
||||||
|
{
|
||||||
|
// TODO: Properly handle x<0, y<0, x>=width, y>=height
|
||||||
|
assert(x < width_ && y < height_);
|
||||||
|
return y * width_ + x;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr TileID get_tile(const MapCoord x, const MapCoord y) const
|
||||||
|
{
|
||||||
|
return tiles_[map_coords_to_index(x, y)];
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr void set_tile(const MapCoord x, const MapCoord y, const TileID tile_id)
|
||||||
|
{
|
||||||
|
tiles_[map_coords_to_index(x, y)] = tile_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Should we get the TileSet from the RenderServer, similar to how we get textures?
|
||||||
|
void draw(const RenderServer& render_server, const TileSet& tile_set) const
|
||||||
|
{
|
||||||
|
// Get tile set texture
|
||||||
|
const sdl::Texture& tile_set_texture = render_server.get_texture(tile_set.get_texture_id());
|
||||||
|
|
||||||
|
// TODO: Implement more efficiently (we could just iterate over tiles_ directly)
|
||||||
|
for (MapCoord y = 0; y < height_; y++) {
|
||||||
|
for (MapCoord x = 0; x < width_; x++) {
|
||||||
|
const int tile_index = get_tile(x, y);
|
||||||
|
|
||||||
|
// Ignore tiles with ID 0
|
||||||
|
if (tile_index == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto [tile_atlas_src_rect] = tile_set.get_tile_definition(tile_index);
|
||||||
|
const sdl::FRect tile_dest_rect{
|
||||||
|
position_.x + x * tile_size_,
|
||||||
|
position_.y + y * tile_size_,
|
||||||
|
static_cast<float>(tile_size_),
|
||||||
|
static_cast<float>(tile_size_),
|
||||||
|
};
|
||||||
|
|
||||||
|
render_server.render_texture(
|
||||||
|
tile_set_texture,
|
||||||
|
&tile_atlas_src_rect,
|
||||||
|
&tile_dest_rect
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
92
src/rutile/core/drawing/tile_set.cppm
Normal file
92
src/rutile/core/drawing/tile_set.cppm
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
module;
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
export module rutile.core.drawing.tile_set;
|
||||||
|
|
||||||
|
import std;
|
||||||
|
import rutile.core.render_server;
|
||||||
|
import wrappers.sdl;
|
||||||
|
|
||||||
|
export namespace rutile
|
||||||
|
{
|
||||||
|
using TileSetID = unsigned int;
|
||||||
|
using TileID = unsigned int;
|
||||||
|
|
||||||
|
struct TileDefinition
|
||||||
|
{
|
||||||
|
sdl::FRect atlas_src_rect;
|
||||||
|
};
|
||||||
|
|
||||||
|
class TileSet
|
||||||
|
{
|
||||||
|
// Texture ID of tileset atlas texture
|
||||||
|
TextureID texture_id_;
|
||||||
|
|
||||||
|
// Size of each tile in pixels
|
||||||
|
// TODO: Do we need this?
|
||||||
|
// int tile_size_;
|
||||||
|
|
||||||
|
// Tile definitions: Maps tile ID to a struct describing the tile, e.g. the texture source boundaries,
|
||||||
|
// collision data, etc.
|
||||||
|
std::vector<TileDefinition> tile_definitions_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
TileSet() = delete;
|
||||||
|
|
||||||
|
explicit TileSet(
|
||||||
|
const TextureID texture_id,
|
||||||
|
std::vector<TileDefinition> tile_definitions
|
||||||
|
)
|
||||||
|
: texture_id_{texture_id},
|
||||||
|
tile_definitions_{std::move(tile_definitions)}
|
||||||
|
{}
|
||||||
|
|
||||||
|
static TileSet create_from_texture(
|
||||||
|
RenderServer& render_server,
|
||||||
|
const std::string& filename,
|
||||||
|
const int tile_size,
|
||||||
|
const std::vector<sdl::Point>& tile_atlas_coords
|
||||||
|
)
|
||||||
|
{
|
||||||
|
const TextureID texture_id = render_server.load_texture(filename);
|
||||||
|
const sdl::Texture& texture = render_server.get_texture(texture_id);
|
||||||
|
|
||||||
|
assert(tile_size > 0);
|
||||||
|
// TODO: These probably shouldn't be asserts...
|
||||||
|
assert(texture.get_width() % tile_size == 0);
|
||||||
|
assert(texture.get_height() % tile_size == 0);
|
||||||
|
|
||||||
|
std::vector<TileDefinition> tile_definitions;
|
||||||
|
|
||||||
|
// TODO: Should we automatically create tile 0 (no tile) instead of listing it in tile_atlas_coords?
|
||||||
|
for (const auto& [x, y] : tile_atlas_coords) {
|
||||||
|
const TileDefinition tile{
|
||||||
|
{
|
||||||
|
static_cast<float>(x * tile_size),
|
||||||
|
static_cast<float>(y * tile_size),
|
||||||
|
static_cast<float>(tile_size),
|
||||||
|
static_cast<float>(tile_size),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: These probably shouldn't be asserts...
|
||||||
|
assert(tile.atlas_src_rect.x + tile.atlas_src_rect.w <= texture.get_width());
|
||||||
|
assert(tile.atlas_src_rect.y + tile.atlas_src_rect.h <= texture.get_height());
|
||||||
|
tile_definitions.push_back(tile);
|
||||||
|
}
|
||||||
|
|
||||||
|
return TileSet(texture_id, tile_definitions);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr TextureID get_texture_id() const
|
||||||
|
{
|
||||||
|
return texture_id_;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr const TileDefinition& get_tile_definition(const TileID tile_id) const
|
||||||
|
{
|
||||||
|
return tile_definitions_.at(tile_id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
118
src/rutile/core/engine.cppm
Normal file
118
src/rutile/core/engine.cppm
Normal file
|
|
@ -0,0 +1,118 @@
|
||||||
|
module;
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
export module rutile.core.engine;
|
||||||
|
|
||||||
|
import std;
|
||||||
|
import rutile.config;
|
||||||
|
import rutile.core.render_server;
|
||||||
|
import rutile.core.ui.ui_server;
|
||||||
|
import wrappers.sdl;
|
||||||
|
|
||||||
|
export namespace rutile
|
||||||
|
{
|
||||||
|
class Engine
|
||||||
|
{
|
||||||
|
// Whether this class is currently instantiated (to prevent multiple instances)
|
||||||
|
static bool instantiated_;
|
||||||
|
|
||||||
|
// If this is set to false, the application will exit
|
||||||
|
bool keep_running_ = true;
|
||||||
|
|
||||||
|
sdl::Window window_;
|
||||||
|
RenderServer render_server_;
|
||||||
|
UIServer ui_server_;
|
||||||
|
|
||||||
|
// Private constructor
|
||||||
|
Engine(sdl::Window&& window, sdl::Renderer&& renderer)
|
||||||
|
: window_{std::move(window)},
|
||||||
|
render_server_{std::move(renderer)},
|
||||||
|
ui_server_{render_server_.get_renderer(), window_}
|
||||||
|
{
|
||||||
|
instantiated_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
Engine() = delete;
|
||||||
|
|
||||||
|
// No copy or move operations
|
||||||
|
Engine(const Engine&) = delete;
|
||||||
|
Engine& operator=(const Engine&) = delete;
|
||||||
|
Engine(Engine&&) = delete;
|
||||||
|
Engine& operator=(Engine&&) = delete;
|
||||||
|
|
||||||
|
~Engine()
|
||||||
|
{
|
||||||
|
instantiated_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::unique_ptr<Engine> create()
|
||||||
|
{
|
||||||
|
// Prevent the class from being instantiated multiple times
|
||||||
|
assert(!instantiated_);
|
||||||
|
|
||||||
|
auto [sdl_window, sdl_renderer] = sdl::create_window_and_renderer(
|
||||||
|
config::get_window_title(),
|
||||||
|
config::window_width,
|
||||||
|
config::window_height,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
|
||||||
|
// NOLINTBEGIN: "Allocated memory is leaked"
|
||||||
|
return std::unique_ptr<Engine>{
|
||||||
|
new Engine{
|
||||||
|
std::move(sdl_window),
|
||||||
|
std::move(sdl_renderer)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// NOLINTEND
|
||||||
|
}
|
||||||
|
|
||||||
|
bool keep_running() const
|
||||||
|
{
|
||||||
|
return keep_running_;
|
||||||
|
}
|
||||||
|
|
||||||
|
sdl::Window& get_window()
|
||||||
|
{
|
||||||
|
return window_;
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderServer& get_render_server()
|
||||||
|
{
|
||||||
|
return render_server_;
|
||||||
|
}
|
||||||
|
|
||||||
|
UIServer& get_ui_server()
|
||||||
|
{
|
||||||
|
return ui_server_;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles an SDL event. Returns true if the event has been handled.
|
||||||
|
bool handle_event(const sdl::Event& event)
|
||||||
|
{
|
||||||
|
if (event.type == sdl::EventTypes::Quit) {
|
||||||
|
// Exit the application
|
||||||
|
keep_running_ = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ui_server_.handle_event(event)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void update()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void shutdown()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
bool Engine::instantiated_ = false;
|
||||||
|
}
|
||||||
89
src/rutile/core/render_server.cppm
Normal file
89
src/rutile/core/render_server.cppm
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
module;
|
||||||
|
|
||||||
|
export module rutile.core.render_server;
|
||||||
|
|
||||||
|
import std;
|
||||||
|
import rutile.core.resource_manager;
|
||||||
|
import rutile.core.texture_loader;
|
||||||
|
import wrappers.sdl;
|
||||||
|
|
||||||
|
export namespace rutile
|
||||||
|
{
|
||||||
|
using TextureID = unsigned int;
|
||||||
|
using TextureManager = ResourceManager<TextureID, sdl::Texture, TextureLoader>;
|
||||||
|
|
||||||
|
class RenderServer
|
||||||
|
{
|
||||||
|
sdl::Renderer renderer_;
|
||||||
|
TextureLoader texture_loader_;
|
||||||
|
TextureManager texture_manager_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
RenderServer() = delete;
|
||||||
|
|
||||||
|
explicit RenderServer(sdl::Renderer&& renderer)
|
||||||
|
: renderer_{std::move(renderer)},
|
||||||
|
texture_loader_{renderer_},
|
||||||
|
texture_manager_{texture_loader_}
|
||||||
|
{
|
||||||
|
renderer_.set_vsync(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// No copy or move operations
|
||||||
|
RenderServer(const RenderServer&) = delete;
|
||||||
|
RenderServer& operator=(const RenderServer&) = delete;
|
||||||
|
RenderServer(RenderServer&&) = delete;
|
||||||
|
RenderServer& operator=(RenderServer&&) = delete;
|
||||||
|
|
||||||
|
~RenderServer() = default;
|
||||||
|
|
||||||
|
constexpr sdl::Renderer& get_renderer()
|
||||||
|
{
|
||||||
|
return renderer_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load a texture from a file (if not loaded yet) and return its texture ID.
|
||||||
|
*/
|
||||||
|
constexpr TextureID load_texture(const std::string& filename)
|
||||||
|
{
|
||||||
|
return texture_manager_.load_resource_by_name(filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a reference to a texture by its texture ID.
|
||||||
|
*/
|
||||||
|
constexpr const sdl::Texture& get_texture(const TextureID texture_id) const
|
||||||
|
{
|
||||||
|
return texture_manager_.get_resource(texture_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void start_frame() const
|
||||||
|
{
|
||||||
|
renderer_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void finish_frame() const
|
||||||
|
{
|
||||||
|
renderer_.present();
|
||||||
|
}
|
||||||
|
|
||||||
|
void render_texture(
|
||||||
|
const TextureID texture_id,
|
||||||
|
const sdl::FRect* src_rect,
|
||||||
|
const sdl::FRect* dest_rect
|
||||||
|
) const
|
||||||
|
{
|
||||||
|
render_texture(get_texture(texture_id), src_rect, dest_rect);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr void render_texture(
|
||||||
|
const sdl::Texture& texture,
|
||||||
|
const sdl::FRect* src_rect,
|
||||||
|
const sdl::FRect* dest_rect
|
||||||
|
) const
|
||||||
|
{
|
||||||
|
renderer_.render_texture(texture, src_rect, dest_rect);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
120
src/rutile/core/resource_manager.cppm
Normal file
120
src/rutile/core/resource_manager.cppm
Normal file
|
|
@ -0,0 +1,120 @@
|
||||||
|
module;
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
export module rutile.core.resource_manager;
|
||||||
|
|
||||||
|
import std;
|
||||||
|
|
||||||
|
export namespace rutile
|
||||||
|
{
|
||||||
|
template <typename ResourceLoaderType, typename ResourceType>
|
||||||
|
concept IsResourceLoader = requires(ResourceLoaderType loader, const std::string& name)
|
||||||
|
{
|
||||||
|
{ loader.load_resource(name) } -> std::same_as<ResourceType>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <
|
||||||
|
typename ResourceIDType,
|
||||||
|
typename ResourceType,
|
||||||
|
IsResourceLoader<ResourceType> ResourceLoaderType
|
||||||
|
>
|
||||||
|
class ResourceManager
|
||||||
|
{
|
||||||
|
// Resource loader
|
||||||
|
ResourceLoaderType& resource_loader_;
|
||||||
|
|
||||||
|
// Registry of loaded resources, array index is resource ID
|
||||||
|
std::vector<ResourceType> resource_registry_;
|
||||||
|
|
||||||
|
// Mapping of all loaded resources from (file) name to resource ID
|
||||||
|
std::map<std::string, ResourceIDType> resource_name_map_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ResourceManager() = delete;
|
||||||
|
|
||||||
|
explicit ResourceManager(ResourceLoaderType& resource_loader)
|
||||||
|
: resource_loader_{resource_loader}
|
||||||
|
{}
|
||||||
|
|
||||||
|
// No copy or move operations
|
||||||
|
ResourceManager(const ResourceManager&) = delete;
|
||||||
|
ResourceManager& operator=(const ResourceManager&) = delete;
|
||||||
|
ResourceManager(ResourceManager&&) = delete;
|
||||||
|
ResourceManager& operator=(ResourceManager&&) = delete;
|
||||||
|
|
||||||
|
~ResourceManager() = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new resource to the registry and returns its ID.
|
||||||
|
*/
|
||||||
|
ResourceIDType add_resource(ResourceType&& resource)
|
||||||
|
{
|
||||||
|
// Add resource to end of vector
|
||||||
|
resource_registry_.push_back(std::move(resource));
|
||||||
|
|
||||||
|
// Return the index of the newly added resource
|
||||||
|
return static_cast<ResourceIDType>(resource_registry_.size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new resource to the registry, associates the name with it and returns its ID.
|
||||||
|
*/
|
||||||
|
ResourceIDType add_resource(ResourceType&& resource, const std::string& name)
|
||||||
|
{
|
||||||
|
// Add resource
|
||||||
|
ResourceIDType resource_id = add_resource(std::move(resource));
|
||||||
|
|
||||||
|
// Associate name with resource ID for future access
|
||||||
|
resource_name_map_.emplace(name, resource_id);
|
||||||
|
|
||||||
|
return resource_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the resource ID for a given resource name, or `std::nullopt` if the resource was not found.
|
||||||
|
*/
|
||||||
|
std::optional<ResourceIDType> get_resource_id_by_name(const std::string& name) const
|
||||||
|
{
|
||||||
|
// Check if resource is already loaded
|
||||||
|
if (
|
||||||
|
const auto search = resource_name_map_.find(name);
|
||||||
|
search != resource_name_map_.end()
|
||||||
|
) {
|
||||||
|
// Return resource ID
|
||||||
|
return search->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads a resource (e.g. from a file) and adds it to the registry if it isn't loaded yet.
|
||||||
|
* Returns the resource ID.
|
||||||
|
*/
|
||||||
|
ResourceIDType load_resource_by_name(const std::string& name)
|
||||||
|
{
|
||||||
|
// Check if resource is already loaded
|
||||||
|
if (
|
||||||
|
auto resource_id = get_resource_id_by_name(name);
|
||||||
|
resource_id.has_value()
|
||||||
|
) {
|
||||||
|
return resource_id.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load resource and add it to the registry
|
||||||
|
return add_resource(resource_loader_.load_resource(name), name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a reference to the resource with the given ID.
|
||||||
|
* The reference is not guaranteed to be valid after adding new resources to the registry.
|
||||||
|
* Assumes that the resource ID is valid.
|
||||||
|
*/
|
||||||
|
const ResourceType& get_resource(const ResourceIDType resource_id) const
|
||||||
|
{
|
||||||
|
assert(resource_id < resource_registry_.size());
|
||||||
|
return resource_registry_[resource_id];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
34
src/rutile/core/texture_loader.cppm
Normal file
34
src/rutile/core/texture_loader.cppm
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
module;
|
||||||
|
|
||||||
|
export module rutile.core.texture_loader;
|
||||||
|
|
||||||
|
import std;
|
||||||
|
import rutile.core.resource_manager;
|
||||||
|
import wrappers.sdl;
|
||||||
|
import wrappers.sdl_image;
|
||||||
|
|
||||||
|
export namespace rutile
|
||||||
|
{
|
||||||
|
class TextureLoader
|
||||||
|
{
|
||||||
|
sdl::Renderer& renderer_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit TextureLoader(sdl::Renderer& renderer)
|
||||||
|
: renderer_{renderer}
|
||||||
|
{}
|
||||||
|
|
||||||
|
// No copy or move operations (reference)
|
||||||
|
TextureLoader(const TextureLoader&) = delete;
|
||||||
|
TextureLoader& operator=(const TextureLoader&) = delete;
|
||||||
|
TextureLoader(TextureLoader&&) = delete;
|
||||||
|
TextureLoader& operator=(TextureLoader&&) = delete;
|
||||||
|
|
||||||
|
~TextureLoader() = default;
|
||||||
|
|
||||||
|
sdl::Texture load_resource(const std::string& filename) const
|
||||||
|
{
|
||||||
|
return sdl_image::load_texture(renderer_, filename);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
275
src/rutile/core/ui/backend/rmlui_input_handler.cppm
Normal file
275
src/rutile/core/ui/backend/rmlui_input_handler.cppm
Normal file
|
|
@ -0,0 +1,275 @@
|
||||||
|
module;
|
||||||
|
|
||||||
|
export module rutile.core.ui.backend.rmlui_input_handler;
|
||||||
|
|
||||||
|
import wrappers.rmlui.core;
|
||||||
|
import wrappers.sdl;
|
||||||
|
|
||||||
|
export namespace rutile
|
||||||
|
{
|
||||||
|
class RmlUiInputHandler
|
||||||
|
{
|
||||||
|
sdl::Window& window_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit RmlUiInputHandler(sdl::Window& window)
|
||||||
|
: window_{window}
|
||||||
|
{}
|
||||||
|
|
||||||
|
static Rml::Input::KeyIdentifier convert_key(const int sdl_key)
|
||||||
|
{
|
||||||
|
namespace Key = sdl::Key;
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
switch (sdl_key) {
|
||||||
|
case Key::Unknown: return Rml::Input::KI_UNKNOWN;
|
||||||
|
case Key::Escape: return Rml::Input::KI_ESCAPE;
|
||||||
|
case Key::Space: return Rml::Input::KI_SPACE;
|
||||||
|
case Key::Num0: return Rml::Input::KI_0;
|
||||||
|
case Key::Num1: return Rml::Input::KI_1;
|
||||||
|
case Key::Num2: return Rml::Input::KI_2;
|
||||||
|
case Key::Num3: return Rml::Input::KI_3;
|
||||||
|
case Key::Num4: return Rml::Input::KI_4;
|
||||||
|
case Key::Num5: return Rml::Input::KI_5;
|
||||||
|
case Key::Num6: return Rml::Input::KI_6;
|
||||||
|
case Key::Num7: return Rml::Input::KI_7;
|
||||||
|
case Key::Num8: return Rml::Input::KI_8;
|
||||||
|
case Key::Num9: return Rml::Input::KI_9;
|
||||||
|
case Key::A: return Rml::Input::KI_A;
|
||||||
|
case Key::B: return Rml::Input::KI_B;
|
||||||
|
case Key::C: return Rml::Input::KI_C;
|
||||||
|
case Key::D: return Rml::Input::KI_D;
|
||||||
|
case Key::E: return Rml::Input::KI_E;
|
||||||
|
case Key::F: return Rml::Input::KI_F;
|
||||||
|
case Key::G: return Rml::Input::KI_G;
|
||||||
|
case Key::H: return Rml::Input::KI_H;
|
||||||
|
case Key::I: return Rml::Input::KI_I;
|
||||||
|
case Key::J: return Rml::Input::KI_J;
|
||||||
|
case Key::K: return Rml::Input::KI_K;
|
||||||
|
case Key::L: return Rml::Input::KI_L;
|
||||||
|
case Key::M: return Rml::Input::KI_M;
|
||||||
|
case Key::N: return Rml::Input::KI_N;
|
||||||
|
case Key::O: return Rml::Input::KI_O;
|
||||||
|
case Key::P: return Rml::Input::KI_P;
|
||||||
|
case Key::Q: return Rml::Input::KI_Q;
|
||||||
|
case Key::R: return Rml::Input::KI_R;
|
||||||
|
case Key::S: return Rml::Input::KI_S;
|
||||||
|
case Key::T: return Rml::Input::KI_T;
|
||||||
|
case Key::U: return Rml::Input::KI_U;
|
||||||
|
case Key::V: return Rml::Input::KI_V;
|
||||||
|
case Key::W: return Rml::Input::KI_W;
|
||||||
|
case Key::X: return Rml::Input::KI_X;
|
||||||
|
case Key::Y: return Rml::Input::KI_Y;
|
||||||
|
case Key::Z: return Rml::Input::KI_Z;
|
||||||
|
case Key::Semicolon: return Rml::Input::KI_OEM_1;
|
||||||
|
case Key::Plus: return Rml::Input::KI_OEM_PLUS;
|
||||||
|
case Key::Comma: return Rml::Input::KI_OEM_COMMA;
|
||||||
|
case Key::Minus: return Rml::Input::KI_OEM_MINUS;
|
||||||
|
case Key::Period: return Rml::Input::KI_OEM_PERIOD;
|
||||||
|
case Key::Slash: return Rml::Input::KI_OEM_2;
|
||||||
|
case Key::Grave: return Rml::Input::KI_OEM_3;
|
||||||
|
case Key::LeftBracket: return Rml::Input::KI_OEM_4;
|
||||||
|
case Key::Backslash: return Rml::Input::KI_OEM_5;
|
||||||
|
case Key::RightBracket: return Rml::Input::KI_OEM_6;
|
||||||
|
case Key::DblApostrophe: return Rml::Input::KI_OEM_7;
|
||||||
|
case Key::Numpad0: return Rml::Input::KI_NUMPAD0;
|
||||||
|
case Key::Numpad1: return Rml::Input::KI_NUMPAD1;
|
||||||
|
case Key::Numpad2: return Rml::Input::KI_NUMPAD2;
|
||||||
|
case Key::Numpad3: return Rml::Input::KI_NUMPAD3;
|
||||||
|
case Key::Numpad4: return Rml::Input::KI_NUMPAD4;
|
||||||
|
case Key::Numpad5: return Rml::Input::KI_NUMPAD5;
|
||||||
|
case Key::Numpad6: return Rml::Input::KI_NUMPAD6;
|
||||||
|
case Key::Numpad7: return Rml::Input::KI_NUMPAD7;
|
||||||
|
case Key::Numpad8: return Rml::Input::KI_NUMPAD8;
|
||||||
|
case Key::Numpad9: return Rml::Input::KI_NUMPAD9;
|
||||||
|
case Key::NumpadEnter: return Rml::Input::KI_NUMPADENTER;
|
||||||
|
case Key::NumpadMultiply: return Rml::Input::KI_MULTIPLY;
|
||||||
|
case Key::NumpadPlus: return Rml::Input::KI_ADD;
|
||||||
|
case Key::NumpadMinus: return Rml::Input::KI_SUBTRACT;
|
||||||
|
case Key::NumpadPeriod: return Rml::Input::KI_DECIMAL;
|
||||||
|
case Key::NumpadDivide: return Rml::Input::KI_DIVIDE;
|
||||||
|
case Key::NumpadEquals: return Rml::Input::KI_OEM_NEC_EQUAL;
|
||||||
|
case Key::Backspace: return Rml::Input::KI_BACK;
|
||||||
|
case Key::Tab: return Rml::Input::KI_TAB;
|
||||||
|
case Key::Clear: return Rml::Input::KI_CLEAR;
|
||||||
|
case Key::Return: return Rml::Input::KI_RETURN;
|
||||||
|
case Key::Pause: return Rml::Input::KI_PAUSE;
|
||||||
|
case Key::CapsLock: return Rml::Input::KI_CAPITAL;
|
||||||
|
case Key::PageUp: return Rml::Input::KI_PRIOR;
|
||||||
|
case Key::PageDown: return Rml::Input::KI_NEXT;
|
||||||
|
case Key::End: return Rml::Input::KI_END;
|
||||||
|
case Key::Home: return Rml::Input::KI_HOME;
|
||||||
|
case Key::Left: return Rml::Input::KI_LEFT;
|
||||||
|
case Key::Up: return Rml::Input::KI_UP;
|
||||||
|
case Key::Right: return Rml::Input::KI_RIGHT;
|
||||||
|
case Key::Down: return Rml::Input::KI_DOWN;
|
||||||
|
case Key::Insert: return Rml::Input::KI_INSERT;
|
||||||
|
case Key::Delete: return Rml::Input::KI_DELETE;
|
||||||
|
case Key::Help: return Rml::Input::KI_HELP;
|
||||||
|
case Key::F1: return Rml::Input::KI_F1;
|
||||||
|
case Key::F2: return Rml::Input::KI_F2;
|
||||||
|
case Key::F3: return Rml::Input::KI_F3;
|
||||||
|
case Key::F4: return Rml::Input::KI_F4;
|
||||||
|
case Key::F5: return Rml::Input::KI_F5;
|
||||||
|
case Key::F6: return Rml::Input::KI_F6;
|
||||||
|
case Key::F7: return Rml::Input::KI_F7;
|
||||||
|
case Key::F8: return Rml::Input::KI_F8;
|
||||||
|
case Key::F9: return Rml::Input::KI_F9;
|
||||||
|
case Key::F10: return Rml::Input::KI_F10;
|
||||||
|
case Key::F11: return Rml::Input::KI_F11;
|
||||||
|
case Key::F12: return Rml::Input::KI_F12;
|
||||||
|
case Key::F13: return Rml::Input::KI_F13;
|
||||||
|
case Key::F14: return Rml::Input::KI_F14;
|
||||||
|
case Key::F15: return Rml::Input::KI_F15;
|
||||||
|
case Key::NumLockClear: return Rml::Input::KI_NUMLOCK;
|
||||||
|
case Key::ScrollLock: return Rml::Input::KI_SCROLL;
|
||||||
|
case Key::LShift: return Rml::Input::KI_LSHIFT;
|
||||||
|
case Key::RShift: return Rml::Input::KI_RSHIFT;
|
||||||
|
case Key::LCtrl: return Rml::Input::KI_LCONTROL;
|
||||||
|
case Key::RCtrl: return Rml::Input::KI_RCONTROL;
|
||||||
|
case Key::LAlt: return Rml::Input::KI_LMENU;
|
||||||
|
case Key::RAlt: return Rml::Input::KI_RMENU;
|
||||||
|
case Key::LGui: return Rml::Input::KI_LMETA;
|
||||||
|
case Key::RGui: return Rml::Input::KI_RMETA;
|
||||||
|
default: return Rml::Input::KI_UNKNOWN;
|
||||||
|
}
|
||||||
|
// clang-format on
|
||||||
|
}
|
||||||
|
|
||||||
|
static int convert_mouse_button(const sdl::MouseButtonIndex sdl_mouse_button)
|
||||||
|
{
|
||||||
|
switch (sdl_mouse_button) {
|
||||||
|
case sdl::MouseButtons::Left: return 0;
|
||||||
|
case sdl::MouseButtons::Right: return 1;
|
||||||
|
case sdl::MouseButtons::Middle: return 2;
|
||||||
|
default: return 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int get_key_modifiers()
|
||||||
|
{
|
||||||
|
const sdl::KeyModState sdl_mods = sdl::get_key_mod_state();
|
||||||
|
int retval = 0;
|
||||||
|
|
||||||
|
if (sdl_mods & sdl::KeyMods::Ctrl) {
|
||||||
|
retval |= Rml::Input::KM_CTRL;
|
||||||
|
}
|
||||||
|
if (sdl_mods & sdl::KeyMods::Shift) {
|
||||||
|
retval |= Rml::Input::KM_SHIFT;
|
||||||
|
}
|
||||||
|
if (sdl_mods & sdl::KeyMods::Alt) {
|
||||||
|
retval |= Rml::Input::KM_ALT;
|
||||||
|
}
|
||||||
|
if (sdl_mods & sdl::KeyMods::NumLock) {
|
||||||
|
retval |= Rml::Input::KM_NUMLOCK;
|
||||||
|
}
|
||||||
|
if (sdl_mods & sdl::KeyMods::CapsLock) {
|
||||||
|
retval |= Rml::Input::KM_CAPSLOCK;
|
||||||
|
}
|
||||||
|
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Rml::TouchList convert_touch_event(const Rml::Context& context, const sdl::Event& ev)
|
||||||
|
{
|
||||||
|
const Rml::Vector2f position = Rml::Vector2f{ev.tfinger.x, ev.tfinger.y}
|
||||||
|
* Rml::Vector2f{context.GetDimensions()};
|
||||||
|
return {Rml::Touch{static_cast<Rml::TouchId>(ev.tfinger.fingerID), position}};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle input events and forward them to RmlUi if relevant.
|
||||||
|
* Returns true if the event was consumed (inverting the return values of RmlUi, where true means the event
|
||||||
|
* was NOT consumed).
|
||||||
|
*/
|
||||||
|
bool handle_input_event(Rml::Context& context, const sdl::Event& ev) const
|
||||||
|
{
|
||||||
|
// Return value of RmlUi event handlers, true if the event was NOT consumed.
|
||||||
|
// Result will be inverted at the end of this method to return true if the event WAS consumed.
|
||||||
|
bool result = true;
|
||||||
|
|
||||||
|
switch (ev.type) {
|
||||||
|
case sdl::EventTypes::MouseMotion: {
|
||||||
|
const float pixel_density = window_.get_pixel_density();
|
||||||
|
result = context.ProcessMouseMove(
|
||||||
|
static_cast<int>(ev.motion.x * pixel_density),
|
||||||
|
static_cast<int>(ev.motion.y * pixel_density),
|
||||||
|
get_key_modifiers()
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case sdl::EventTypes::MouseButtonDown:
|
||||||
|
result = context.ProcessMouseButtonDown(
|
||||||
|
convert_mouse_button(ev.button.button),
|
||||||
|
get_key_modifiers()
|
||||||
|
);
|
||||||
|
sdl::capture_mouse(true);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case sdl::EventTypes::MouseButtonUp:
|
||||||
|
sdl::capture_mouse(false);
|
||||||
|
result = context.ProcessMouseButtonUp(
|
||||||
|
convert_mouse_button(ev.button.button), get_key_modifiers()
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case sdl::EventTypes::MouseWheel:
|
||||||
|
result = context.ProcessMouseWheel(
|
||||||
|
Rml::Vector2f{ev.wheel.x, -ev.wheel.y},
|
||||||
|
get_key_modifiers()
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case sdl::EventTypes::KeyDown:
|
||||||
|
result = context.ProcessKeyDown(convert_key(ev.key.key), get_key_modifiers());
|
||||||
|
|
||||||
|
if (ev.key.key == sdl::Key::Return || ev.key.key == sdl::Key::NumpadEnter) {
|
||||||
|
result &= context.ProcessTextInput('\n');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case sdl::EventTypes::KeyUp:
|
||||||
|
result = context.ProcessKeyUp(convert_key(ev.key.key), get_key_modifiers());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case sdl::EventTypes::TextInput:
|
||||||
|
result = context.ProcessTextInput(Rml::String(&ev.text.text[0]));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case sdl::EventTypes::FingerDown:
|
||||||
|
result = context.ProcessTouchStart(convert_touch_event(context, ev), get_key_modifiers());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case sdl::EventTypes::FingerMotion:
|
||||||
|
result = context.ProcessTouchMove(convert_touch_event(context, ev), get_key_modifiers());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case sdl::EventTypes::FingerUp:
|
||||||
|
result = context.ProcessTouchEnd(convert_touch_event(context, ev), get_key_modifiers());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case sdl::EventTypes::WindowMouseLeave:
|
||||||
|
context.ProcessMouseLeave();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case sdl::EventTypes::WindowPixelSizeChanged:
|
||||||
|
context.SetDimensions(
|
||||||
|
Rml::Vector2i{ev.window.data1, ev.window.data2}
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case sdl::EventTypes::WindowDisplayScaleChanged:
|
||||||
|
context.SetDensityIndependentPixelRatio(window_.get_display_scale());
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invert result to be consistent with the event handling of this app. Return true if the event was
|
||||||
|
// consumed, i.e. no further event handling is necessary.
|
||||||
|
return !result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
198
src/rutile/core/ui/backend/rmlui_render_interface.cppm
Normal file
198
src/rutile/core/ui/backend/rmlui_render_interface.cppm
Normal file
|
|
@ -0,0 +1,198 @@
|
||||||
|
// RmlUi RenderInterface implementation for SDL using SDLrenderer.
|
||||||
|
// Based on RmlUi example code: vendor/RmlUi/Backends/RmlUi_Renderer_SDL.{cpp,h}
|
||||||
|
|
||||||
|
module;
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
export module rutile.core.ui.backend.rmlui_render_interface;
|
||||||
|
|
||||||
|
import std;
|
||||||
|
import wrappers.rmlui.core;
|
||||||
|
import wrappers.sdl;
|
||||||
|
import wrappers.sdl_image;
|
||||||
|
|
||||||
|
export namespace rutile
|
||||||
|
{
|
||||||
|
class RmlUiRenderInterface final : public Rml::RenderInterface
|
||||||
|
{
|
||||||
|
struct GeometryView
|
||||||
|
{
|
||||||
|
Rml::Span<const Rml::Vertex> vertices;
|
||||||
|
Rml::Span<const int> indices;
|
||||||
|
};
|
||||||
|
|
||||||
|
sdl::Renderer& renderer_;
|
||||||
|
SDL_BlendMode blend_mode_ = {};
|
||||||
|
sdl::Rect rect_scissor_ = {};
|
||||||
|
bool scissor_region_enabled_ = false;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit RmlUiRenderInterface(sdl::Renderer& renderer)
|
||||||
|
: renderer_{renderer}
|
||||||
|
{
|
||||||
|
// RmlUi serves vertex colors and textures with premultiplied alpha, set the blend mode accordingly.
|
||||||
|
// Equivalent to glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA).
|
||||||
|
blend_mode_ = SDL_ComposeCustomBlendMode(
|
||||||
|
SDL_BLENDFACTOR_ONE,
|
||||||
|
SDL_BLENDFACTOR_ONE_MINUS_SRC_ALPHA,
|
||||||
|
SDL_BLENDOPERATION_ADD,
|
||||||
|
SDL_BLENDFACTOR_ONE,
|
||||||
|
SDL_BLENDFACTOR_ONE_MINUS_SRC_ALPHA,
|
||||||
|
SDL_BLENDOPERATION_ADD
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up OpenGL states for taking rendering commands from RmlUi.
|
||||||
|
*/
|
||||||
|
void prepare_render() const
|
||||||
|
{
|
||||||
|
renderer_.set_draw_blend_mode(blend_mode_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- Inherited from Rml::RenderInterface
|
||||||
|
|
||||||
|
Rml::CompiledGeometryHandle CompileGeometry(
|
||||||
|
const Rml::Span<const Rml::Vertex> vertices,
|
||||||
|
const Rml::Span<const int> indices
|
||||||
|
) override
|
||||||
|
{
|
||||||
|
// RmlUi handles lifetime and calls ReleaseGeometry to delete the object
|
||||||
|
GeometryView* data = new GeometryView{vertices, indices}; // NOLINT: "Allocated memory is leaked"
|
||||||
|
return reinterpret_cast<Rml::CompiledGeometryHandle>(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReleaseGeometry(const Rml::CompiledGeometryHandle geometry) override
|
||||||
|
{
|
||||||
|
delete reinterpret_cast<GeometryView*>(geometry);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderGeometry(
|
||||||
|
const Rml::CompiledGeometryHandle handle,
|
||||||
|
const Rml::Vector2f translation,
|
||||||
|
const Rml::TextureHandle texture
|
||||||
|
) override
|
||||||
|
{
|
||||||
|
const GeometryView* geometry = reinterpret_cast<GeometryView*>(handle);
|
||||||
|
const Rml::Vertex* vertices = geometry->vertices.data();
|
||||||
|
const std::size_t num_vertices = geometry->vertices.size();
|
||||||
|
const int* indices = geometry->indices.data();
|
||||||
|
const std::size_t num_indices = geometry->indices.size();
|
||||||
|
|
||||||
|
// Convert RmlUi vertices to SDL vertices
|
||||||
|
auto sdl_vertices = std::make_unique<sdl::Vertex[]>(num_vertices);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < num_vertices; i++) {
|
||||||
|
const auto& [position, color, tex_coord] = vertices[i];
|
||||||
|
|
||||||
|
sdl_vertices[i].position = {
|
||||||
|
position.x + translation.x,
|
||||||
|
position.y + translation.y,
|
||||||
|
};
|
||||||
|
sdl_vertices[i].tex_coord = {
|
||||||
|
tex_coord.x,
|
||||||
|
tex_coord.y,
|
||||||
|
};
|
||||||
|
sdl_vertices[i].color = {
|
||||||
|
color.red / 255.f,
|
||||||
|
color.green / 255.f,
|
||||||
|
color.blue / 255.f,
|
||||||
|
color.alpha / 255.f,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
renderer_.render_geometry(
|
||||||
|
reinterpret_cast<SDL_Texture*>(texture),
|
||||||
|
sdl_vertices.get(),
|
||||||
|
static_cast<int>(num_vertices),
|
||||||
|
indices,
|
||||||
|
static_cast<int>(num_indices)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Rml::TextureHandle LoadTexture(
|
||||||
|
Rml::Vector2i& texture_dimensions,
|
||||||
|
const Rml::String& source
|
||||||
|
) override
|
||||||
|
{
|
||||||
|
sdl::Surface surface = sdl_image::load(source.c_str());
|
||||||
|
|
||||||
|
if (surface.get_format() != SDL_PIXELFORMAT_RGBA32 && surface.get_format() != SDL_PIXELFORMAT_BGRA32) {
|
||||||
|
surface = surface.convert_format(SDL_PIXELFORMAT_RGBA32);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert colors to premultiplied alpha, which is necessary for correct alpha compositing.
|
||||||
|
const std::size_t pixels_byte_size = surface.get_width() * surface.get_height() * 4;
|
||||||
|
Rml::byte* pixels = static_cast<Rml::byte*>(surface.get_raw_pixels());
|
||||||
|
|
||||||
|
for (size_t i = 0; i < pixels_byte_size; i += 4) {
|
||||||
|
const Rml::byte alpha = pixels[i + 3];
|
||||||
|
for (size_t j = 0; j < 3; ++j) {
|
||||||
|
pixels[i + j] = static_cast<Rml::byte>(
|
||||||
|
static_cast<int>(pixels[i + j]) * static_cast<int>(alpha) / 255
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer_.get_raw(), surface.get_raw());
|
||||||
|
if (texture) {
|
||||||
|
SDL_SetTextureBlendMode(texture, blend_mode_);
|
||||||
|
}
|
||||||
|
|
||||||
|
texture_dimensions = {surface.get_width(), surface.get_height()};
|
||||||
|
return reinterpret_cast<Rml::TextureHandle>(texture);
|
||||||
|
}
|
||||||
|
|
||||||
|
Rml::TextureHandle GenerateTexture(
|
||||||
|
const Rml::Span<const Rml::byte> source,
|
||||||
|
const Rml::Vector2i source_dimensions
|
||||||
|
) override
|
||||||
|
{
|
||||||
|
assert(
|
||||||
|
source.data() && source.size() == static_cast<size_t>(source_dimensions.x * source_dimensions.y * 4)
|
||||||
|
);
|
||||||
|
|
||||||
|
const sdl::Surface surface = sdl::Surface::create_from(
|
||||||
|
source_dimensions.x,
|
||||||
|
source_dimensions.y,
|
||||||
|
SDL_PIXELFORMAT_RGBA32,
|
||||||
|
// Cast away the const because SDL_CreateSurfaceFrom() expects a void*.
|
||||||
|
// This is fine as long as we don't modify surface.get_raw_pixels().
|
||||||
|
const_cast<Rml::byte*>(source.data()),
|
||||||
|
source_dimensions.x * 4
|
||||||
|
);
|
||||||
|
|
||||||
|
SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer_.get_raw(), surface.get_raw());
|
||||||
|
if (texture) {
|
||||||
|
SDL_SetTextureBlendMode(texture, blend_mode_);
|
||||||
|
}
|
||||||
|
|
||||||
|
return reinterpret_cast<Rml::TextureHandle>(texture);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReleaseTexture(const Rml::TextureHandle texture_handle) override
|
||||||
|
{
|
||||||
|
SDL_DestroyTexture(reinterpret_cast<SDL_Texture*>(texture_handle));
|
||||||
|
}
|
||||||
|
|
||||||
|
void EnableScissorRegion(const bool enable) override
|
||||||
|
{
|
||||||
|
renderer_.set_clip_rect(enable ? &rect_scissor_ : nullptr);
|
||||||
|
scissor_region_enabled_ = enable;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetScissorRegion(const Rml::Rectanglei region) override
|
||||||
|
{
|
||||||
|
rect_scissor_.x = region.Left();
|
||||||
|
rect_scissor_.y = region.Top();
|
||||||
|
rect_scissor_.w = region.Width();
|
||||||
|
rect_scissor_.h = region.Height();
|
||||||
|
|
||||||
|
if (scissor_region_enabled_) {
|
||||||
|
renderer_.set_clip_rect(&rect_scissor_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
117
src/rutile/core/ui/backend/rmlui_system_interface.cppm
Normal file
117
src/rutile/core/ui/backend/rmlui_system_interface.cppm
Normal file
|
|
@ -0,0 +1,117 @@
|
||||||
|
// RmlUi SystemInterface implementation for SDL.
|
||||||
|
// Based on RmlUi example code: vendor/RmlUi/Backends/RmlUi_Platform_SDL.{cpp,h}
|
||||||
|
|
||||||
|
module;
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
export module rutile.core.ui.backend.rmlui_system_interface;
|
||||||
|
|
||||||
|
import std;
|
||||||
|
import utils.memory;
|
||||||
|
import wrappers.rmlui.core;
|
||||||
|
import wrappers.sdl;
|
||||||
|
|
||||||
|
// Shorthand for a unique pointer to an SDL_Cursor
|
||||||
|
// TODO: Build actual wrapper sdl::Cursor at some point?
|
||||||
|
using SDL_CursorPtr = std::unique_ptr<SDL_Cursor, utils::FuncDeleter<SDL_DestroyCursor>>;
|
||||||
|
|
||||||
|
export namespace rutile
|
||||||
|
{
|
||||||
|
class RmlUiSystemInterface final : public Rml::SystemInterface
|
||||||
|
{
|
||||||
|
sdl::Window& window_;
|
||||||
|
|
||||||
|
SDL_CursorPtr cursor_default;
|
||||||
|
SDL_CursorPtr cursor_move;
|
||||||
|
SDL_CursorPtr cursor_pointer;
|
||||||
|
SDL_CursorPtr cursor_resize;
|
||||||
|
SDL_CursorPtr cursor_cross;
|
||||||
|
SDL_CursorPtr cursor_text;
|
||||||
|
SDL_CursorPtr cursor_unavailable;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit RmlUiSystemInterface(sdl::Window& window)
|
||||||
|
: window_{window}
|
||||||
|
{
|
||||||
|
cursor_default = SDL_CursorPtr{SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_DEFAULT)};
|
||||||
|
cursor_move = SDL_CursorPtr{SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_MOVE)};
|
||||||
|
cursor_pointer = SDL_CursorPtr{SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_POINTER)};
|
||||||
|
cursor_resize = SDL_CursorPtr{SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_NWSE_RESIZE)};
|
||||||
|
cursor_cross = SDL_CursorPtr{SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_CROSSHAIR)};
|
||||||
|
cursor_text = SDL_CursorPtr{SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_TEXT)};
|
||||||
|
cursor_unavailable = SDL_CursorPtr{SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_NOT_ALLOWED)};
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- Inherited from Rml::SystemInterface
|
||||||
|
|
||||||
|
double GetElapsedTime() override
|
||||||
|
{
|
||||||
|
// TODO: Add a shared timer to the engine and use that here.
|
||||||
|
static const Uint64 start = SDL_GetPerformanceCounter();
|
||||||
|
static const double frequency = static_cast<double>(SDL_GetPerformanceFrequency());
|
||||||
|
return static_cast<double>(SDL_GetPerformanceCounter() - start) / frequency;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetMouseCursor(const Rml::String& cursor_name) override
|
||||||
|
{
|
||||||
|
SDL_Cursor* cursor = nullptr;
|
||||||
|
|
||||||
|
if (cursor_name.empty() || cursor_name == "arrow") {
|
||||||
|
cursor = cursor_default.get();
|
||||||
|
}
|
||||||
|
else if (cursor_name == "move") {
|
||||||
|
cursor = cursor_move.get();
|
||||||
|
}
|
||||||
|
else if (cursor_name == "pointer") {
|
||||||
|
cursor = cursor_pointer.get();
|
||||||
|
}
|
||||||
|
else if (cursor_name == "resize") {
|
||||||
|
cursor = cursor_resize.get();
|
||||||
|
}
|
||||||
|
else if (cursor_name == "cross") {
|
||||||
|
cursor = cursor_cross.get();
|
||||||
|
}
|
||||||
|
else if (cursor_name == "text") {
|
||||||
|
cursor = cursor_text.get();
|
||||||
|
}
|
||||||
|
else if (cursor_name == "unavailable") {
|
||||||
|
cursor = cursor_unavailable.get();
|
||||||
|
}
|
||||||
|
else if (Rml::StringUtilities::StartsWith(cursor_name, "rmlui-scroll")) {
|
||||||
|
cursor = cursor_move.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cursor) {
|
||||||
|
SDL_SetCursor(cursor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetClipboardText(const Rml::String& text) override
|
||||||
|
{
|
||||||
|
sdl::set_clipboard_text(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GetClipboardText(Rml::String& text) override
|
||||||
|
{
|
||||||
|
text = sdl::get_clipboard_text();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivateKeyboard(const Rml::Vector2f caret_position, const float line_height) override
|
||||||
|
{
|
||||||
|
const sdl::Rect rect{
|
||||||
|
static_cast<int>(caret_position.x),
|
||||||
|
static_cast<int>(caret_position.y),
|
||||||
|
1,
|
||||||
|
static_cast<int>(line_height),
|
||||||
|
};
|
||||||
|
window_.set_text_input_area(&rect, 0);
|
||||||
|
window_.start_text_input();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeactivateKeyboard() override
|
||||||
|
{
|
||||||
|
window_.stop_text_input();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
124
src/rutile/core/ui/ui_server.cppm
Normal file
124
src/rutile/core/ui/ui_server.cppm
Normal file
|
|
@ -0,0 +1,124 @@
|
||||||
|
module;
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
export module rutile.core.ui.ui_server;
|
||||||
|
|
||||||
|
import std;
|
||||||
|
import rutile.core.ui.backend.rmlui_render_interface;
|
||||||
|
import rutile.core.ui.backend.rmlui_system_interface;
|
||||||
|
import rutile.core.ui.backend.rmlui_input_handler;
|
||||||
|
import wrappers.rmlui.core;
|
||||||
|
import wrappers.rmlui.debugger;
|
||||||
|
import wrappers.sdl;
|
||||||
|
|
||||||
|
export namespace rutile
|
||||||
|
{
|
||||||
|
class UIServer
|
||||||
|
{
|
||||||
|
// Whether this class is currently instantiated (to prevent multiple instances because we need to do
|
||||||
|
// Rml::Initialise() and Rml::Shutdown()).
|
||||||
|
static bool instantiated_;
|
||||||
|
|
||||||
|
RmlUiSystemInterface system_interface_;
|
||||||
|
RmlUiRenderInterface render_interface_;
|
||||||
|
RmlUiInputHandler input_handler_;
|
||||||
|
|
||||||
|
// RmlUi context (non-owning pointer, lifetime managed by library)
|
||||||
|
Rml::Context* context_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit UIServer(sdl::Renderer& renderer, sdl::Window& window)
|
||||||
|
: system_interface_{window},
|
||||||
|
render_interface_{renderer},
|
||||||
|
input_handler_{window}
|
||||||
|
{
|
||||||
|
// Prevent the class from being instantiated multiple times
|
||||||
|
assert(!instantiated_);
|
||||||
|
|
||||||
|
Rml::SetSystemInterface(&system_interface_);
|
||||||
|
Rml::SetRenderInterface(&render_interface_);
|
||||||
|
Rml::Initialise();
|
||||||
|
|
||||||
|
// Create the main RmlUi context
|
||||||
|
auto [window_width, window_height] = window.get_size();
|
||||||
|
context_ = Rml::CreateContext("main", Rml::Vector2i(window_width, window_height));
|
||||||
|
if (!context_) {
|
||||||
|
throw std::runtime_error("RmlUi: Couldn't create context");
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Implement some kind of switch to disable debugger (just use config::debug?)
|
||||||
|
Rml::Debugger::Initialise(context_);
|
||||||
|
|
||||||
|
instantiated_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No copy or move operations
|
||||||
|
UIServer(const UIServer&) = delete;
|
||||||
|
UIServer& operator=(const UIServer&) = delete;
|
||||||
|
UIServer(UIServer&&) = delete;
|
||||||
|
UIServer& operator=(UIServer&&) = delete;
|
||||||
|
|
||||||
|
~UIServer()
|
||||||
|
{
|
||||||
|
Rml::Shutdown();
|
||||||
|
instantiated_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr Rml::Context& get_context() const
|
||||||
|
{
|
||||||
|
// TODO: Can we assume context_ is always non-nullptr?
|
||||||
|
assert(context_);
|
||||||
|
return *context_;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles an SDL event. Returns true if the event has been handled.
|
||||||
|
bool handle_event(const sdl::Event& event) const
|
||||||
|
{
|
||||||
|
// Toggle RmlUi debugger with F12
|
||||||
|
if (event.type == sdl::EventTypes::KeyDown && event.key.key == sdl::Key::F12) {
|
||||||
|
Rml::Debugger::SetVisible(!Rml::Debugger::IsVisible());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let RmlUi handle relevant events
|
||||||
|
if (input_handler_.handle_input_event(get_context(), event)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void update() const
|
||||||
|
{
|
||||||
|
get_context().Update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void render() const
|
||||||
|
{
|
||||||
|
render_interface_.prepare_render();
|
||||||
|
get_context().Render();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void load_font_face(const std::string& file_path)
|
||||||
|
{
|
||||||
|
if (!Rml::LoadFontFace(file_path)) {
|
||||||
|
throw std::runtime_error(std::format("RmlUi: Couldn't load font face \"{}\"", file_path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Can/should we avoid the (non-owning) raw pointers here?
|
||||||
|
Rml::ElementDocument* load_document(const std::string& file_path) const
|
||||||
|
{
|
||||||
|
Rml::ElementDocument* document = get_context().LoadDocument(file_path);
|
||||||
|
|
||||||
|
if (!document) {
|
||||||
|
throw std::runtime_error(std::format("RmlUi: Couldn't load document \"{}\"", file_path));
|
||||||
|
}
|
||||||
|
|
||||||
|
return document;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
bool UIServer::instantiated_ = false;
|
||||||
|
}
|
||||||
161
src/rutile/game/game.cppm
Normal file
161
src/rutile/game/game.cppm
Normal file
|
|
@ -0,0 +1,161 @@
|
||||||
|
module;
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
export module rutile.game.game;
|
||||||
|
|
||||||
|
import std;
|
||||||
|
import rutile.core.drawing.sprite;
|
||||||
|
import rutile.core.drawing.tile_map;
|
||||||
|
import rutile.core.drawing.tile_set;
|
||||||
|
import rutile.core.engine;
|
||||||
|
import rutile.core.render_server;
|
||||||
|
import wrappers.rmlui.core;
|
||||||
|
import wrappers.sdl;
|
||||||
|
import wrappers.sdl_image;
|
||||||
|
|
||||||
|
export namespace rutile
|
||||||
|
{
|
||||||
|
class Game
|
||||||
|
{
|
||||||
|
// Whether this class is currently instantiated (to prevent multiple instances)
|
||||||
|
static bool instantiated_;
|
||||||
|
|
||||||
|
// Reference to the engine
|
||||||
|
Engine& engine_;
|
||||||
|
|
||||||
|
// Sprites for testing
|
||||||
|
Sprite player_sprite_;
|
||||||
|
std::vector<Sprite> sprites_;
|
||||||
|
|
||||||
|
// Tile set and tile map (TODO: tile set should be moved to resource manager in engine)
|
||||||
|
TileSet tile_set_;
|
||||||
|
TileMap tile_map_;
|
||||||
|
|
||||||
|
// Private constructor
|
||||||
|
explicit Game(Engine& engine)
|
||||||
|
: engine_(engine),
|
||||||
|
player_sprite_{
|
||||||
|
Sprite::create_from_texture(
|
||||||
|
engine_.get_render_server(),
|
||||||
|
"assets/sprites/neocat_64.png",
|
||||||
|
std::nullopt,
|
||||||
|
sdl::FPoint{0, 0},
|
||||||
|
sdl::FPoint{-32, -32}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
tile_set_{
|
||||||
|
TileSet::create_from_texture(
|
||||||
|
engine_.get_render_server(),
|
||||||
|
"assets/tilesets/terrain.png",
|
||||||
|
32,
|
||||||
|
{
|
||||||
|
// Tile 0: No tile
|
||||||
|
{0, 0},
|
||||||
|
// Tile 1: Actual first tile in the texture
|
||||||
|
{0, 0},
|
||||||
|
{1, 0},
|
||||||
|
{2, 0},
|
||||||
|
{3, 0},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
tile_map_(0, 32, 16, 12, {50, 50})
|
||||||
|
{
|
||||||
|
// Initialize UI
|
||||||
|
const auto& ui_server = engine_.get_ui_server();
|
||||||
|
ui_server.load_font_face("assets/fonts/LatoLatin-Regular.ttf");
|
||||||
|
Rml::ElementDocument* document = ui_server.load_document("assets/ui/main_ui.rml");
|
||||||
|
document->Show();
|
||||||
|
|
||||||
|
// Initialize tile map with example data
|
||||||
|
for (auto x = 0; x < 10; x++) {
|
||||||
|
for (auto y = 0; y < 10; y++) {
|
||||||
|
const auto tile_index = 2 + (x + y) % 2;
|
||||||
|
tile_map_.set_tile(x, y, tile_index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
instantiated_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
Game() = delete;
|
||||||
|
|
||||||
|
// No copy or move operations because we have a reference to the engine
|
||||||
|
Game(const Game&) = delete;
|
||||||
|
Game& operator=(const Game&) = delete;
|
||||||
|
Game(Game&&) = delete;
|
||||||
|
Game& operator=(Game&&) = delete;
|
||||||
|
|
||||||
|
~Game()
|
||||||
|
{
|
||||||
|
instantiated_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::unique_ptr<Game> create(Engine& engine)
|
||||||
|
{
|
||||||
|
// Prevent the class from being instantiated multiple times
|
||||||
|
assert(!instantiated_);
|
||||||
|
return std::unique_ptr<Game>{
|
||||||
|
new Game(engine)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles an SDL event. Returns true if the event has been handled.
|
||||||
|
bool handle_event(const sdl::Event& event)
|
||||||
|
{
|
||||||
|
if (event.type == sdl::EventTypes::MouseMotion) {
|
||||||
|
player_sprite_.set_position({event.motion.x, event.motion.y});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.type == sdl::EventTypes::MouseButtonUp) {
|
||||||
|
sprites_.push_back(
|
||||||
|
Sprite::create_from_texture(
|
||||||
|
engine_.get_render_server(),
|
||||||
|
"assets/sprites/neofox_64.png",
|
||||||
|
std::nullopt,
|
||||||
|
sdl::FPoint{event.motion.x, event.motion.y},
|
||||||
|
sdl::FPoint{-32, -32}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void update()
|
||||||
|
{
|
||||||
|
// TODO: Move this to Engine
|
||||||
|
engine_.get_ui_server().update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void render() const
|
||||||
|
{
|
||||||
|
const auto& render_server = engine_.get_render_server();
|
||||||
|
render_server.start_frame();
|
||||||
|
|
||||||
|
// Render tilemap
|
||||||
|
tile_map_.draw(render_server, tile_set_);
|
||||||
|
|
||||||
|
// Render sprites
|
||||||
|
for (const Sprite& sprite : sprites_) {
|
||||||
|
sprite.draw(render_server);
|
||||||
|
}
|
||||||
|
player_sprite_.draw(render_server);
|
||||||
|
|
||||||
|
// TODO: Move this to Engine
|
||||||
|
engine_.get_ui_server().render();
|
||||||
|
|
||||||
|
render_server.finish_frame();
|
||||||
|
}
|
||||||
|
|
||||||
|
void shutdown()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
bool Game::instantiated_ = false;
|
||||||
|
}
|
||||||
|
|
@ -1,95 +0,0 @@
|
||||||
module;
|
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
#include <string>
|
|
||||||
#include <SDL3/SDL.h>
|
|
||||||
|
|
||||||
export module sdl_app;
|
|
||||||
|
|
||||||
import sprite;
|
|
||||||
|
|
||||||
export {
|
|
||||||
struct AppState
|
|
||||||
{
|
|
||||||
SDL_Window* window = nullptr;
|
|
||||||
SDL_Renderer* renderer = nullptr;
|
|
||||||
Sprite* sprite = nullptr;
|
|
||||||
};
|
|
||||||
|
|
||||||
SDL_AppResult sdl_panic(
|
|
||||||
const std::string& error_prefix,
|
|
||||||
SDL_Window* window = nullptr,
|
|
||||||
SDL_Renderer* renderer = nullptr
|
|
||||||
)
|
|
||||||
{
|
|
||||||
std::cerr << error_prefix << ": " << SDL_GetError() << '\n';
|
|
||||||
|
|
||||||
if (renderer != nullptr) {
|
|
||||||
SDL_DestroyRenderer(renderer);
|
|
||||||
}
|
|
||||||
if (window != nullptr) {
|
|
||||||
SDL_DestroyWindow(window);
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_Quit();
|
|
||||||
return SDL_APP_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_AppResult sdl_app_init(AppState* app_state)
|
|
||||||
{
|
|
||||||
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS)) {
|
|
||||||
return sdl_panic("SDL_Init error");
|
|
||||||
}
|
|
||||||
|
|
||||||
app_state->window = SDL_CreateWindow("BuildSystemTest", 640, 480, 0);
|
|
||||||
if (app_state->window == nullptr) {
|
|
||||||
return sdl_panic("SDL_CreateWindow error");
|
|
||||||
}
|
|
||||||
|
|
||||||
app_state->renderer = SDL_CreateRenderer(app_state->window, nullptr);
|
|
||||||
if (app_state->renderer == nullptr) {
|
|
||||||
return sdl_panic("SDL_CreateRenderer error", app_state->window);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
app_state->sprite = new Sprite(app_state->renderer, "assets/neocat.png", 100, 100);
|
|
||||||
}
|
|
||||||
catch (const std::runtime_error& e) {
|
|
||||||
return sdl_panic(e.what(), app_state->window, app_state->renderer);
|
|
||||||
}
|
|
||||||
|
|
||||||
return SDL_APP_CONTINUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_AppResult sdl_app_event(const AppState* app_state, const SDL_Event* event)
|
|
||||||
{
|
|
||||||
if (event->type == SDL_EVENT_QUIT) {
|
|
||||||
return SDL_APP_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event->type == SDL_EVENT_MOUSE_MOTION) {
|
|
||||||
app_state->sprite->move(
|
|
||||||
event->motion.x - 50,
|
|
||||||
event->motion.y - 50
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return SDL_APP_CONTINUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_AppResult sdl_app_iterate(const AppState* app_state)
|
|
||||||
{
|
|
||||||
SDL_RenderClear(app_state->renderer);
|
|
||||||
app_state->sprite->render(app_state->renderer);
|
|
||||||
SDL_RenderPresent(app_state->renderer);
|
|
||||||
|
|
||||||
return SDL_APP_CONTINUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
void sdl_app_shutdown(const AppState* app_state)
|
|
||||||
{
|
|
||||||
delete app_state->sprite;
|
|
||||||
SDL_DestroyRenderer(app_state->renderer);
|
|
||||||
SDL_DestroyWindow(app_state->window);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,81 +0,0 @@
|
||||||
module;
|
|
||||||
|
|
||||||
#include <stdexcept>
|
|
||||||
#include <string>
|
|
||||||
#include <SDL3/SDL.h>
|
|
||||||
#include <SDL3_image/SDL_image.h>
|
|
||||||
|
|
||||||
export module sprite;
|
|
||||||
|
|
||||||
export class Sprite
|
|
||||||
{
|
|
||||||
SDL_Texture* sdl_texture;
|
|
||||||
SDL_FRect dest_rect{0, 0, 0, 0};
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit Sprite(
|
|
||||||
SDL_Renderer* renderer,
|
|
||||||
const std::string& filename,
|
|
||||||
const int width,
|
|
||||||
const int height
|
|
||||||
)
|
|
||||||
{
|
|
||||||
SDL_Surface* texture_surface = IMG_Load(filename.c_str());
|
|
||||||
if (texture_surface == nullptr) {
|
|
||||||
throw std::runtime_error("IMG_Load error");
|
|
||||||
}
|
|
||||||
|
|
||||||
sdl_texture = SDL_CreateTextureFromSurface(renderer, texture_surface);
|
|
||||||
SDL_DestroySurface(texture_surface);
|
|
||||||
|
|
||||||
if (sdl_texture == nullptr) {
|
|
||||||
throw std::runtime_error("SDL_CreateTextureFromSurface error");
|
|
||||||
}
|
|
||||||
|
|
||||||
dest_rect.w = static_cast<float>(width);
|
|
||||||
dest_rect.h = static_cast<float>(height);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't allow copy operations
|
|
||||||
Sprite(const Sprite&) = delete;
|
|
||||||
Sprite& operator=(const Sprite&) = delete;
|
|
||||||
|
|
||||||
// Move constructor
|
|
||||||
Sprite(Sprite&& other) noexcept
|
|
||||||
: sdl_texture(other.sdl_texture),
|
|
||||||
dest_rect(other.dest_rect)
|
|
||||||
{
|
|
||||||
other.sdl_texture = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move assignment
|
|
||||||
Sprite& operator=(Sprite&& other) noexcept
|
|
||||||
{
|
|
||||||
// Move inner resources from other
|
|
||||||
sdl_texture = other.sdl_texture;
|
|
||||||
dest_rect = other.dest_rect;
|
|
||||||
|
|
||||||
// Reset other to make it safe for deletion
|
|
||||||
other.sdl_texture = nullptr;
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
~Sprite()
|
|
||||||
{
|
|
||||||
if (sdl_texture != nullptr) {
|
|
||||||
SDL_DestroyTexture(sdl_texture);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void move(const float x, const float y)
|
|
||||||
{
|
|
||||||
dest_rect.x = x;
|
|
||||||
dest_rect.y = y;
|
|
||||||
}
|
|
||||||
|
|
||||||
void render(SDL_Renderer* renderer) const
|
|
||||||
{
|
|
||||||
SDL_RenderTexture(renderer, sdl_texture, nullptr, &dest_rect);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
21
src/utils/memory.cppm
Normal file
21
src/utils/memory.cppm
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
module;
|
||||||
|
|
||||||
|
export module utils.memory;
|
||||||
|
|
||||||
|
export namespace utils
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Template to generate deleters for `std::unique_ptr` from functions, e.g. to free SDL resources.
|
||||||
|
*
|
||||||
|
* @tparam delete_func Function that takes a pointer to a resource and deletes the resource.
|
||||||
|
*/
|
||||||
|
template <auto delete_func>
|
||||||
|
struct FuncDeleter
|
||||||
|
{
|
||||||
|
template <typename T>
|
||||||
|
constexpr void operator()(T* ptr) const noexcept
|
||||||
|
{
|
||||||
|
delete_func(ptr);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
530
src/wrappers/rmlui/core.cppm
Normal file
530
src/wrappers/rmlui/core.cppm
Normal file
|
|
@ -0,0 +1,530 @@
|
||||||
|
module;
|
||||||
|
|
||||||
|
#include <RmlUi/Core.h>
|
||||||
|
|
||||||
|
export module wrappers.rmlui.core;
|
||||||
|
|
||||||
|
export
|
||||||
|
{
|
||||||
|
namespace Rml
|
||||||
|
{
|
||||||
|
using Rml::Animation;
|
||||||
|
using Rml::AnimationList;
|
||||||
|
using Rml::Any;
|
||||||
|
using Rml::Array;
|
||||||
|
using Rml::ArrayDefinition;
|
||||||
|
using Rml::Assert;
|
||||||
|
using Rml::AttributeNameList;
|
||||||
|
using Rml::BasePointerDefinition;
|
||||||
|
using Rml::BaseXMLParser;
|
||||||
|
using Rml::BlendMode;
|
||||||
|
using Rml::Box;
|
||||||
|
using Rml::BoxArea;
|
||||||
|
using Rml::BoxDirection;
|
||||||
|
using Rml::BoxEdge;
|
||||||
|
using Rml::BoxShadow;
|
||||||
|
using Rml::BoxShadowList;
|
||||||
|
using Rml::CallbackTexture;
|
||||||
|
using Rml::CallbackTextureFunction;
|
||||||
|
using Rml::CallbackTextureInterface;
|
||||||
|
using Rml::CallbackTextureSource;
|
||||||
|
using Rml::Character;
|
||||||
|
using Rml::ClipMaskGeometry;
|
||||||
|
using Rml::ClipMaskGeometryList;
|
||||||
|
using Rml::ClipMaskOperation;
|
||||||
|
using Rml::ColorFormat;
|
||||||
|
using Rml::ColorStop;
|
||||||
|
using Rml::ColorStopList;
|
||||||
|
using Rml::Colour;
|
||||||
|
using Rml::Colourb;
|
||||||
|
using Rml::ColourbPremultiplied;
|
||||||
|
using Rml::Colourf;
|
||||||
|
using Rml::ColumnMajorMatrix4f;
|
||||||
|
using Rml::ColumnMajorStorage;
|
||||||
|
using Rml::CompiledFilter;
|
||||||
|
using Rml::CompiledFilterHandle;
|
||||||
|
using Rml::CompiledGeometryHandle;
|
||||||
|
using Rml::CompiledShader;
|
||||||
|
using Rml::CompiledShaderHandle;
|
||||||
|
using Rml::ComputedValues;
|
||||||
|
using Rml::ContainerBox;
|
||||||
|
using Rml::Context;
|
||||||
|
using Rml::ContextInstancer;
|
||||||
|
using Rml::ContextPtr;
|
||||||
|
using Rml::ConvolutionFilter;
|
||||||
|
using Rml::CornerSizes;
|
||||||
|
using Rml::CreateContext;
|
||||||
|
using Rml::CreateString;
|
||||||
|
using Rml::DataAddress;
|
||||||
|
using Rml::DataAddressEntry;
|
||||||
|
using Rml::DataController;
|
||||||
|
using Rml::DataControllerInstancer;
|
||||||
|
using Rml::DataControllerPtr;
|
||||||
|
using Rml::DataEventFunc;
|
||||||
|
using Rml::DataGetFunc;
|
||||||
|
using Rml::DataModel;
|
||||||
|
using Rml::DataModelConstructor;
|
||||||
|
using Rml::DataModelHandle;
|
||||||
|
using Rml::DataSetFunc;
|
||||||
|
using Rml::DataTransformFunc;
|
||||||
|
using Rml::DataTypeGetFunc;
|
||||||
|
using Rml::DataTypeRegister;
|
||||||
|
using Rml::DataTypeSetFunc;
|
||||||
|
using Rml::DataVariable;
|
||||||
|
using Rml::DataVariableType;
|
||||||
|
using Rml::DataView;
|
||||||
|
using Rml::DataViewInstancer;
|
||||||
|
using Rml::DataViewPtr;
|
||||||
|
using Rml::Decorator;
|
||||||
|
using Rml::DecoratorDataHandle;
|
||||||
|
using Rml::DecoratorDeclaration;
|
||||||
|
using Rml::DecoratorDeclarationList;
|
||||||
|
using Rml::DecoratorInstancer;
|
||||||
|
using Rml::DecoratorInstancerInterface;
|
||||||
|
using Rml::DecoratorPtrList;
|
||||||
|
using Rml::DecoratorsPtr;
|
||||||
|
using Rml::DefaultActionPhase;
|
||||||
|
using Rml::DefaultStyleSheetParsers;
|
||||||
|
using Rml::Dictionary;
|
||||||
|
using Rml::DirtyVariables;
|
||||||
|
using Rml::DocumentHeader;
|
||||||
|
using Rml::EdgeSizes;
|
||||||
|
using Rml::EffectSpecification;
|
||||||
|
using Rml::Element;
|
||||||
|
using Rml::ElementAnimation;
|
||||||
|
using Rml::ElementAnimationList;
|
||||||
|
using Rml::ElementAttributes;
|
||||||
|
using Rml::ElementBackgroundBorder;
|
||||||
|
using Rml::ElementDefinition;
|
||||||
|
using Rml::ElementDocument;
|
||||||
|
using Rml::ElementForm;
|
||||||
|
using Rml::ElementFormControl;
|
||||||
|
using Rml::ElementFormControlInput;
|
||||||
|
using Rml::ElementFormControlSelect;
|
||||||
|
using Rml::ElementFormControlTextArea;
|
||||||
|
using Rml::ElementInstancer;
|
||||||
|
using Rml::ElementInstancerElement;
|
||||||
|
using Rml::ElementInstancerGeneric;
|
||||||
|
using Rml::ElementInstancerText;
|
||||||
|
using Rml::ElementList;
|
||||||
|
using Rml::ElementMeta;
|
||||||
|
using Rml::ElementProgress;
|
||||||
|
using Rml::ElementPtr;
|
||||||
|
using Rml::ElementScroll;
|
||||||
|
using Rml::ElementStyle;
|
||||||
|
using Rml::ElementTabSet;
|
||||||
|
using Rml::ElementText;
|
||||||
|
using Rml::ElementUtilities;
|
||||||
|
using Rml::EnableObserverPtr;
|
||||||
|
using Rml::Event;
|
||||||
|
using Rml::EventDispatcher;
|
||||||
|
using Rml::EventId;
|
||||||
|
using Rml::EventInstancer;
|
||||||
|
using Rml::EventListener;
|
||||||
|
using Rml::EventListenerInstancer;
|
||||||
|
using Rml::EventPhase;
|
||||||
|
using Rml::EventPtr;
|
||||||
|
using Rml::EventSpecification;
|
||||||
|
using Rml::Factory;
|
||||||
|
using Rml::Family;
|
||||||
|
using Rml::FamilyBase;
|
||||||
|
using Rml::FamilyId;
|
||||||
|
using Rml::FileHandle;
|
||||||
|
using Rml::FileInterface;
|
||||||
|
using Rml::Filter;
|
||||||
|
using Rml::FilterDeclaration;
|
||||||
|
using Rml::FilterDeclarationList;
|
||||||
|
using Rml::FilterHandleList;
|
||||||
|
using Rml::FilterInstancer;
|
||||||
|
using Rml::FilterOperation;
|
||||||
|
using Rml::FiltersPtr;
|
||||||
|
using Rml::FocusFlag;
|
||||||
|
using Rml::FontEffect;
|
||||||
|
using Rml::FontEffectInstancer;
|
||||||
|
using Rml::FontEffectList;
|
||||||
|
using Rml::FontEffects;
|
||||||
|
using Rml::FontEffectsHandle;
|
||||||
|
using Rml::FontEffectsPtr;
|
||||||
|
using Rml::FontEngineInterface;
|
||||||
|
using Rml::FontFaceHandle;
|
||||||
|
using Rml::FontGlyph;
|
||||||
|
using Rml::FontGlyphMap;
|
||||||
|
using Rml::FontMetrics;
|
||||||
|
using Rml::FormatString;
|
||||||
|
using Rml::FromString;
|
||||||
|
using Rml::FuncDefinition;
|
||||||
|
using Rml::Function;
|
||||||
|
using Rml::Geometry;
|
||||||
|
using Rml::Get;
|
||||||
|
using Rml::GetContext;
|
||||||
|
using Rml::GetFileInterface;
|
||||||
|
using Rml::GetFontEngineInterface;
|
||||||
|
using Rml::GetIf;
|
||||||
|
using Rml::GetNumContexts;
|
||||||
|
using Rml::GetRenderInterface;
|
||||||
|
using Rml::GetSystemInterface;
|
||||||
|
using Rml::GetTextInputHandler;
|
||||||
|
using Rml::GetTextureSourceList;
|
||||||
|
using Rml::GetVersion;
|
||||||
|
using Rml::Hash;
|
||||||
|
using Rml::Initialise;
|
||||||
|
using Rml::InlineLevelBox;
|
||||||
|
using Rml::InputType;
|
||||||
|
using Rml::IsVoidMemberFunc;
|
||||||
|
using Rml::KeyframeBlock;
|
||||||
|
using Rml::Keyframes;
|
||||||
|
using Rml::KeyframesMap;
|
||||||
|
using Rml::LayerHandle;
|
||||||
|
using Rml::LayoutEngine;
|
||||||
|
using Rml::List;
|
||||||
|
using Rml::LoadFontFace;
|
||||||
|
using Rml::Log;
|
||||||
|
using Rml::MakeLiteralIntVariable;
|
||||||
|
using Rml::MakeMoveIterator;
|
||||||
|
using Rml::MakeShared;
|
||||||
|
using Rml::MakeUnique;
|
||||||
|
using Rml::Matrix4;
|
||||||
|
using Rml::Matrix4f;
|
||||||
|
using Rml::MatrixStorageBase;
|
||||||
|
using Rml::MediaBlock;
|
||||||
|
using Rml::MediaBlockList;
|
||||||
|
using Rml::MediaQueryId;
|
||||||
|
using Rml::MediaQueryModifier;
|
||||||
|
using Rml::MemberGetFunc;
|
||||||
|
using Rml::MemberGetFuncDefinition;
|
||||||
|
using Rml::MemberGetterFunc;
|
||||||
|
using Rml::MemberObjectDefinition;
|
||||||
|
using Rml::MemberScalarGetSetFuncDefinition;
|
||||||
|
using Rml::MemberSetFunc;
|
||||||
|
using Rml::MemberSetterFunc;
|
||||||
|
using Rml::Mesh;
|
||||||
|
using Rml::MeshUtilities;
|
||||||
|
using Rml::ModalFlag;
|
||||||
|
using Rml::NamedDecorator;
|
||||||
|
using Rml::NamedDecoratorMap;
|
||||||
|
using Rml::NavigationSearchDirection;
|
||||||
|
using Rml::NonCopyMoveable;
|
||||||
|
using Rml::NumericValue;
|
||||||
|
using Rml::ObserverPtr;
|
||||||
|
using Rml::OwnedElementList;
|
||||||
|
using Rml::Pair;
|
||||||
|
using Rml::ParameterMap;
|
||||||
|
using Rml::Plugin;
|
||||||
|
using Rml::PointerDefinition;
|
||||||
|
using Rml::PointerTraits;
|
||||||
|
using Rml::PropertiesIterator;
|
||||||
|
using Rml::PropertiesIteratorView;
|
||||||
|
using Rml::Property;
|
||||||
|
using Rml::PropertyDefinition;
|
||||||
|
using Rml::PropertyDictionary;
|
||||||
|
using Rml::PropertyId;
|
||||||
|
using Rml::PropertyIdNameMap;
|
||||||
|
using Rml::PropertyIdSet;
|
||||||
|
using Rml::PropertyIdSetIterator;
|
||||||
|
using Rml::PropertyMap;
|
||||||
|
using Rml::PropertyParser;
|
||||||
|
using Rml::PropertySource;
|
||||||
|
using Rml::PropertySpecification;
|
||||||
|
using Rml::Queue;
|
||||||
|
using Rml::Rectangle;
|
||||||
|
using Rml::Rectanglef;
|
||||||
|
using Rml::Rectanglei;
|
||||||
|
using Rml::RegisterEventType;
|
||||||
|
using Rml::RegisterPlugin;
|
||||||
|
using Rml::RelativeTarget;
|
||||||
|
using Rml::Releasable;
|
||||||
|
using Rml::ReleaseCompiledGeometry;
|
||||||
|
using Rml::ReleaseFontResources;
|
||||||
|
using Rml::ReleaseRenderManagers;
|
||||||
|
using Rml::ReleaseTexture;
|
||||||
|
using Rml::ReleaseTextures;
|
||||||
|
using Rml::Releaser;
|
||||||
|
using Rml::ReleaserBase;
|
||||||
|
using Rml::RemoveContext;
|
||||||
|
using Rml::RenderBox;
|
||||||
|
using Rml::RenderInterface;
|
||||||
|
using Rml::RenderManager;
|
||||||
|
using Rml::RenderManagerAccess;
|
||||||
|
using Rml::RenderState;
|
||||||
|
using Rml::ReplacedBox;
|
||||||
|
using Rml::ResolveValue;
|
||||||
|
using Rml::ResolveValueOr;
|
||||||
|
using Rml::RowMajorMatrix4f;
|
||||||
|
using Rml::RowMajorStorage;
|
||||||
|
using Rml::ScalarDefinition;
|
||||||
|
using Rml::ScalarFuncDefinition;
|
||||||
|
using Rml::ScriptInterface;
|
||||||
|
using Rml::ScriptInterfacePtr;
|
||||||
|
using Rml::ScriptObject;
|
||||||
|
using Rml::ScrollAlignment;
|
||||||
|
using Rml::ScrollBehavior;
|
||||||
|
using Rml::ScrollController;
|
||||||
|
using Rml::ScrollIntoViewOptions;
|
||||||
|
using Rml::ScrollParentage;
|
||||||
|
using Rml::SetFileInterface;
|
||||||
|
using Rml::SetFontEngineInterface;
|
||||||
|
using Rml::SetRenderInterface;
|
||||||
|
using Rml::SetSystemInterface;
|
||||||
|
using Rml::SetTextInputHandler;
|
||||||
|
using Rml::SharedPtr;
|
||||||
|
using Rml::ShorthandDefinition;
|
||||||
|
using Rml::ShorthandId;
|
||||||
|
using Rml::ShorthandIdNameMap;
|
||||||
|
using Rml::ShorthandType;
|
||||||
|
using Rml::Shutdown;
|
||||||
|
using Rml::SmallOrderedMap;
|
||||||
|
using Rml::SmallOrderedSet;
|
||||||
|
using Rml::SmallUnorderedMap;
|
||||||
|
using Rml::SmallUnorderedSet;
|
||||||
|
using Rml::Span;
|
||||||
|
using Rml::Sprite;
|
||||||
|
using Rml::SpriteDefinitionList;
|
||||||
|
using Rml::SpriteMap;
|
||||||
|
using Rml::Spritesheet;
|
||||||
|
using Rml::SpritesheetList;
|
||||||
|
using Rml::StableMap;
|
||||||
|
using Rml::StableUnorderedMap;
|
||||||
|
using Rml::StableVector;
|
||||||
|
using Rml::StableVectorIndex;
|
||||||
|
using Rml::Stack;
|
||||||
|
using Rml::StackingContextChild;
|
||||||
|
using Rml::Stream;
|
||||||
|
using Rml::String;
|
||||||
|
using Rml::StringIteratorU8;
|
||||||
|
using Rml::StringList;
|
||||||
|
using Rml::StringView;
|
||||||
|
using Rml::StructDefinition;
|
||||||
|
using Rml::StructHandle;
|
||||||
|
using Rml::StyleSheet;
|
||||||
|
using Rml::StyleSheetContainer;
|
||||||
|
using Rml::StyleSheetIndex;
|
||||||
|
using Rml::StyleSheetNode;
|
||||||
|
using Rml::StyleSheetParser;
|
||||||
|
using Rml::StyleSheetSpecification;
|
||||||
|
using Rml::SystemInterface;
|
||||||
|
using Rml::TextInputHandler;
|
||||||
|
using Rml::TextShapingContext;
|
||||||
|
using Rml::Texture;
|
||||||
|
using Rml::TextureDatabase;
|
||||||
|
using Rml::TextureFileIndex;
|
||||||
|
using Rml::TextureHandle;
|
||||||
|
using Rml::TextureSource;
|
||||||
|
using Rml::TexturedMesh;
|
||||||
|
using Rml::TexturedMeshList;
|
||||||
|
using Rml::ToString;
|
||||||
|
using Rml::Touch;
|
||||||
|
using Rml::TouchId;
|
||||||
|
using Rml::TouchList;
|
||||||
|
using Rml::Transform;
|
||||||
|
using Rml::TransformFuncRegister;
|
||||||
|
using Rml::TransformPrimitive;
|
||||||
|
using Rml::TransformPtr;
|
||||||
|
using Rml::TransformState;
|
||||||
|
using Rml::Transition;
|
||||||
|
using Rml::TransitionList;
|
||||||
|
using Rml::Tween;
|
||||||
|
using Rml::TypeConverter;
|
||||||
|
using Rml::URL;
|
||||||
|
using Rml::UniquePtr;
|
||||||
|
using Rml::UniqueReleaserPtr;
|
||||||
|
using Rml::UniqueRenderResource;
|
||||||
|
using Rml::Unit;
|
||||||
|
using Rml::Units;
|
||||||
|
using Rml::UnorderedMap;
|
||||||
|
using Rml::UnorderedMultimap;
|
||||||
|
using Rml::UnorderedSet;
|
||||||
|
using Rml::UnregisterPlugin;
|
||||||
|
using Rml::VariableDefinition;
|
||||||
|
using Rml::Variant;
|
||||||
|
using Rml::VariantList;
|
||||||
|
using Rml::Vector2;
|
||||||
|
using Rml::Vector2f;
|
||||||
|
using Rml::Vector2i;
|
||||||
|
using Rml::Vector3;
|
||||||
|
using Rml::Vector3f;
|
||||||
|
using Rml::Vector3i;
|
||||||
|
using Rml::Vector4;
|
||||||
|
using Rml::Vector4f;
|
||||||
|
using Rml::Vector4i;
|
||||||
|
using Rml::Vector;
|
||||||
|
using Rml::Vertex;
|
||||||
|
using Rml::VoidMemberFunc;
|
||||||
|
using Rml::WeakPtr;
|
||||||
|
using Rml::WidgetDropDown;
|
||||||
|
using Rml::WidgetScroll;
|
||||||
|
using Rml::WidgetTextInputMultiLine;
|
||||||
|
using Rml::XMLAttributes;
|
||||||
|
using Rml::XMLDataType;
|
||||||
|
using Rml::XMLNodeHandler;
|
||||||
|
using Rml::XMLParser;
|
||||||
|
using Rml::byte;
|
||||||
|
using Rml::is_builtin_data_scalar;
|
||||||
|
using Rml::voidPtr;
|
||||||
|
|
||||||
|
namespace Input
|
||||||
|
{
|
||||||
|
using Rml::Input::KeyIdentifier;
|
||||||
|
using Rml::Input::KeyModifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Math
|
||||||
|
{
|
||||||
|
using Rml::Math::ACos;
|
||||||
|
using Rml::Math::ASin;
|
||||||
|
using Rml::Math::ATan2;
|
||||||
|
using Rml::Math::Absolute;
|
||||||
|
using Rml::Math::Clamp;
|
||||||
|
using Rml::Math::Cos;
|
||||||
|
using Rml::Math::DecomposeFractionalIntegral;
|
||||||
|
using Rml::Math::DegreesToRadians;
|
||||||
|
using Rml::Math::Exp;
|
||||||
|
using Rml::Math::ExpandToPixelGrid;
|
||||||
|
using Rml::Math::HexToDecimal;
|
||||||
|
using Rml::Math::IsCloseToZero;
|
||||||
|
using Rml::Math::Lerp;
|
||||||
|
using Rml::Math::Log2;
|
||||||
|
using Rml::Math::Max;
|
||||||
|
using Rml::Math::Min;
|
||||||
|
using Rml::Math::NormaliseAngle;
|
||||||
|
using Rml::Math::RadiansToDegrees;
|
||||||
|
using Rml::Math::RandomBool;
|
||||||
|
using Rml::Math::RandomInteger;
|
||||||
|
using Rml::Math::RandomReal;
|
||||||
|
using Rml::Math::Round;
|
||||||
|
using Rml::Math::RoundDown;
|
||||||
|
using Rml::Math::RoundDownToInteger;
|
||||||
|
using Rml::Math::RoundToInteger;
|
||||||
|
using Rml::Math::RoundUp;
|
||||||
|
using Rml::Math::RoundUpToInteger;
|
||||||
|
using Rml::Math::RoundedLerp;
|
||||||
|
using Rml::Math::Sin;
|
||||||
|
using Rml::Math::SnapToPixelGrid;
|
||||||
|
using Rml::Math::SquareRoot;
|
||||||
|
using Rml::Math::Tan;
|
||||||
|
using Rml::Math::ToPowerOfTwo;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace StringUtilities
|
||||||
|
{
|
||||||
|
using Rml::StringUtilities::BytesUTF8;
|
||||||
|
using Rml::StringUtilities::ConvertByteOffsetToCharacterOffset;
|
||||||
|
using Rml::StringUtilities::ConvertCharacterOffsetToByteOffset;
|
||||||
|
using Rml::StringUtilities::DecodeRml;
|
||||||
|
using Rml::StringUtilities::EncodeRml;
|
||||||
|
using Rml::StringUtilities::EndsWith;
|
||||||
|
using Rml::StringUtilities::ExpandString;
|
||||||
|
using Rml::StringUtilities::IsWhitespace;
|
||||||
|
using Rml::StringUtilities::JoinString;
|
||||||
|
using Rml::StringUtilities::LengthUTF8;
|
||||||
|
using Rml::StringUtilities::Replace;
|
||||||
|
using Rml::StringUtilities::SeekBackwardUTF8;
|
||||||
|
using Rml::StringUtilities::SeekForwardUTF8;
|
||||||
|
using Rml::StringUtilities::StartsWith;
|
||||||
|
using Rml::StringUtilities::StringCompareCaseInsensitive;
|
||||||
|
using Rml::StringUtilities::StripWhitespace;
|
||||||
|
using Rml::StringUtilities::ToCharacter;
|
||||||
|
using Rml::StringUtilities::ToLower;
|
||||||
|
using Rml::StringUtilities::ToUTF8;
|
||||||
|
using Rml::StringUtilities::ToUpper;
|
||||||
|
using Rml::StringUtilities::TrimTrailingDotZeros;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Style
|
||||||
|
{
|
||||||
|
using Rml::Style::AlignContent;
|
||||||
|
using Rml::Style::AlignItems;
|
||||||
|
using Rml::Style::AlignSelf;
|
||||||
|
using Rml::Style::Bottom;
|
||||||
|
using Rml::Style::BoxSizing;
|
||||||
|
using Rml::Style::Clear;
|
||||||
|
using Rml::Style::Clip;
|
||||||
|
using Rml::Style::CommonValues;
|
||||||
|
using Rml::Style::ComputedValues;
|
||||||
|
using Rml::Style::Direction;
|
||||||
|
using Rml::Style::Display;
|
||||||
|
using Rml::Style::Drag;
|
||||||
|
using Rml::Style::FlexBasis;
|
||||||
|
using Rml::Style::FlexDirection;
|
||||||
|
using Rml::Style::FlexWrap;
|
||||||
|
using Rml::Style::Float;
|
||||||
|
using Rml::Style::Focus;
|
||||||
|
using Rml::Style::FontKerning;
|
||||||
|
using Rml::Style::FontStyle;
|
||||||
|
using Rml::Style::FontWeight;
|
||||||
|
using Rml::Style::Height;
|
||||||
|
using Rml::Style::InheritedValues;
|
||||||
|
using Rml::Style::JustifyContent;
|
||||||
|
using Rml::Style::Left;
|
||||||
|
using Rml::Style::LengthPercentage;
|
||||||
|
using Rml::Style::LengthPercentageAuto;
|
||||||
|
using Rml::Style::LineHeight;
|
||||||
|
using Rml::Style::Margin;
|
||||||
|
using Rml::Style::MaxHeight;
|
||||||
|
using Rml::Style::MaxWidth;
|
||||||
|
using Rml::Style::MinHeight;
|
||||||
|
using Rml::Style::MinWidth;
|
||||||
|
using Rml::Style::Nav;
|
||||||
|
using Rml::Style::NumberAuto;
|
||||||
|
using Rml::Style::OriginX;
|
||||||
|
using Rml::Style::OriginY;
|
||||||
|
using Rml::Style::Overflow;
|
||||||
|
using Rml::Style::OverscrollBehavior;
|
||||||
|
using Rml::Style::Padding;
|
||||||
|
using Rml::Style::PerspectiveOrigin;
|
||||||
|
using Rml::Style::PointerEvents;
|
||||||
|
using Rml::Style::Position;
|
||||||
|
using Rml::Style::RareValues;
|
||||||
|
using Rml::Style::Right;
|
||||||
|
using Rml::Style::TabIndex;
|
||||||
|
using Rml::Style::TextAlign;
|
||||||
|
using Rml::Style::TextDecoration;
|
||||||
|
using Rml::Style::TextOverflow;
|
||||||
|
using Rml::Style::TextTransform;
|
||||||
|
using Rml::Style::Top;
|
||||||
|
using Rml::Style::TransformOrigin;
|
||||||
|
using Rml::Style::VerticalAlign;
|
||||||
|
using Rml::Style::Visibility;
|
||||||
|
using Rml::Style::WhiteSpace;
|
||||||
|
using Rml::Style::Width;
|
||||||
|
using Rml::Style::WordBreak;
|
||||||
|
using Rml::Style::ZIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Transforms
|
||||||
|
{
|
||||||
|
using Rml::Transforms::DecomposedMatrix4;
|
||||||
|
using Rml::Transforms::Matrix2D;
|
||||||
|
using Rml::Transforms::Matrix3D;
|
||||||
|
using Rml::Transforms::Perspective;
|
||||||
|
using Rml::Transforms::ResolvedPrimitive;
|
||||||
|
using Rml::Transforms::Rotate2D;
|
||||||
|
using Rml::Transforms::Rotate3D;
|
||||||
|
using Rml::Transforms::RotateX;
|
||||||
|
using Rml::Transforms::RotateY;
|
||||||
|
using Rml::Transforms::RotateZ;
|
||||||
|
using Rml::Transforms::Scale2D;
|
||||||
|
using Rml::Transforms::Scale3D;
|
||||||
|
using Rml::Transforms::ScaleX;
|
||||||
|
using Rml::Transforms::ScaleY;
|
||||||
|
using Rml::Transforms::ScaleZ;
|
||||||
|
using Rml::Transforms::Skew2D;
|
||||||
|
using Rml::Transforms::SkewX;
|
||||||
|
using Rml::Transforms::SkewY;
|
||||||
|
using Rml::Transforms::Translate2D;
|
||||||
|
using Rml::Transforms::Translate3D;
|
||||||
|
using Rml::Transforms::TranslateX;
|
||||||
|
using Rml::Transforms::TranslateY;
|
||||||
|
using Rml::Transforms::TranslateZ;
|
||||||
|
using Rml::Transforms::UnresolvedPrimitive;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Utilities
|
||||||
|
{
|
||||||
|
using Rml::Utilities::HashCombine;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
using ::rmlui_dynamic_cast;
|
||||||
|
using ::rmlui_static_cast;
|
||||||
|
using ::rmlui_type_name;
|
||||||
|
}
|
||||||
14
src/wrappers/rmlui/debugger.cppm
Normal file
14
src/wrappers/rmlui/debugger.cppm
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
module;
|
||||||
|
|
||||||
|
#include <RmlUi/Debugger.h>
|
||||||
|
|
||||||
|
export module wrappers.rmlui.debugger;
|
||||||
|
|
||||||
|
export namespace Rml::Debugger
|
||||||
|
{
|
||||||
|
using Rml::Debugger::Initialise;
|
||||||
|
using Rml::Debugger::IsVisible;
|
||||||
|
using Rml::Debugger::SetContext;
|
||||||
|
using Rml::Debugger::SetVisible;
|
||||||
|
using Rml::Debugger::Shutdown;
|
||||||
|
}
|
||||||
16
src/wrappers/sdl.cppm
Normal file
16
src/wrappers/sdl.cppm
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
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;
|
||||||
38
src/wrappers/sdl/clipboard.cppm
Normal file
38
src/wrappers/sdl/clipboard.cppm
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
module;
|
||||||
|
|
||||||
|
#include <SDL3/SDL_clipboard.h>
|
||||||
|
|
||||||
|
export module wrappers.sdl.clipboard;
|
||||||
|
|
||||||
|
import std;
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
24
src/wrappers/sdl/error.cppm
Normal file
24
src/wrappers/sdl/error.cppm
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
module;
|
||||||
|
|
||||||
|
#include <SDL3/SDL_error.h>
|
||||||
|
|
||||||
|
export module wrappers.sdl.error;
|
||||||
|
|
||||||
|
import std;
|
||||||
|
|
||||||
|
export namespace sdl
|
||||||
|
{
|
||||||
|
// Exception that wraps an SDL error (which SDL function caused the error, what's the error).
|
||||||
|
// SDL_GetError() is used in the constructor to get the error message unless specified explicitly.
|
||||||
|
class SDLException final : public std::runtime_error
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit SDLException(const std::string& sdl_func, const std::string& sdl_error)
|
||||||
|
: runtime_error(std::format("Error in SDL function {}: {}", sdl_func, sdl_error))
|
||||||
|
{}
|
||||||
|
|
||||||
|
explicit SDLException(const std::string& sdl_func)
|
||||||
|
: SDLException(sdl_func, SDL_GetError())
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
}
|
||||||
47
src/wrappers/sdl/events.cppm
Normal file
47
src/wrappers/sdl/events.cppm
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
module;
|
||||||
|
|
||||||
|
#include <SDL3/SDL_events.h>
|
||||||
|
|
||||||
|
export module wrappers.sdl.events;
|
||||||
|
|
||||||
|
export namespace sdl
|
||||||
|
{
|
||||||
|
// Simple alias for SDL_Event union
|
||||||
|
using Event = SDL_Event;
|
||||||
|
|
||||||
|
// Alias for EventType enum
|
||||||
|
using EventType = SDL_EventType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper for the SDL_EventType enum.
|
||||||
|
*
|
||||||
|
* 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 EventTypes
|
||||||
|
{
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
62
src/wrappers/sdl/init.cppm
Normal file
62
src/wrappers/sdl/init.cppm
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
module;
|
||||||
|
|
||||||
|
#include <SDL3/SDL_init.h>
|
||||||
|
|
||||||
|
export module wrappers.sdl.init;
|
||||||
|
|
||||||
|
import std;
|
||||||
|
import wrappers.sdl.error;
|
||||||
|
|
||||||
|
export namespace sdl
|
||||||
|
{
|
||||||
|
using InitFlags_t = SDL_InitFlags;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper for the SDL_InitFlags enum/constants.
|
||||||
|
*
|
||||||
|
* We're using a namespace here to emulate an enum-like interface. If we used an actual enum, we would have to
|
||||||
|
* explicitly define bit-wise operations for it (as well as for every other kind of bit flag enum.
|
||||||
|
* (Maybe in the future this can be replaced with a more elegant template-based solution or something.)
|
||||||
|
*/
|
||||||
|
namespace InitFlags
|
||||||
|
{
|
||||||
|
constexpr InitFlags_t Audio = SDL_INIT_AUDIO;
|
||||||
|
constexpr InitFlags_t Video = SDL_INIT_VIDEO;
|
||||||
|
constexpr InitFlags_t Joystick = SDL_INIT_JOYSTICK;
|
||||||
|
constexpr InitFlags_t Haptic = SDL_INIT_HAPTIC;
|
||||||
|
constexpr InitFlags_t Gamepad = SDL_INIT_GAMEPAD;
|
||||||
|
constexpr InitFlags_t Events = SDL_INIT_EVENTS;
|
||||||
|
constexpr InitFlags_t Sensor = SDL_INIT_SENSOR;
|
||||||
|
constexpr InitFlags_t Camera = SDL_INIT_CAMERA;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper for the SDL_AppResult enum.
|
||||||
|
*/
|
||||||
|
enum class AppResult : std::underlying_type_t<SDL_AppResult>
|
||||||
|
{
|
||||||
|
Continue = SDL_APP_CONTINUE,
|
||||||
|
Success = SDL_APP_SUCCESS,
|
||||||
|
Failure = SDL_APP_FAILURE,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper for SDL_Init().
|
||||||
|
*/
|
||||||
|
constexpr void initialize_sdl(const InitFlags_t flags)
|
||||||
|
{
|
||||||
|
if (!SDL_Init(flags)) {
|
||||||
|
throw SDLException("SDL_Init");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper for SDL_SetAppMetadataProperty().
|
||||||
|
*/
|
||||||
|
constexpr void set_app_metadata_property(const char* property_name, const char* value)
|
||||||
|
{
|
||||||
|
if (!SDL_SetAppMetadataProperty(property_name, value)) {
|
||||||
|
throw SDLException("SDL_SetAppMetadataProperty");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
177
src/wrappers/sdl/keyboard.cppm
Normal file
177
src/wrappers/sdl/keyboard.cppm
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
29
src/wrappers/sdl/mouse.cppm
Normal file
29
src/wrappers/sdl/mouse.cppm
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
module;
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
export module wrappers.sdl.mouse;
|
||||||
|
|
||||||
|
import std;
|
||||||
|
|
||||||
|
export namespace sdl
|
||||||
|
{
|
||||||
|
using MouseButtonIndex = std::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);
|
||||||
|
}
|
||||||
|
}
|
||||||
42
src/wrappers/sdl/rect.cppm
Normal file
42
src/wrappers/sdl/rect.cppm
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
module;
|
||||||
|
|
||||||
|
#include <SDL3/SDL_rect.h>
|
||||||
|
|
||||||
|
export module wrappers.sdl.rect;
|
||||||
|
|
||||||
|
export namespace sdl
|
||||||
|
{
|
||||||
|
// Wrappers around SDL_Point and SDL_FPoint (change to wrapper classes when needed)
|
||||||
|
using FPoint = SDL_FPoint;
|
||||||
|
using Point = SDL_Point;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper for SDL_FRect using class inheritance.
|
||||||
|
*/
|
||||||
|
class FRect : public SDL_FRect
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Wrapper for SDL_RectEmptyFloat().
|
||||||
|
* @return True if the floating point rectangle takes no space.
|
||||||
|
*/
|
||||||
|
bool is_empty() const
|
||||||
|
{
|
||||||
|
return SDL_RectEmptyFloat(this);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper for SDL_Rect using class inheritance.
|
||||||
|
*/
|
||||||
|
class Rect : public SDL_Rect
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Wrapper for SDL_RectEmpty().
|
||||||
|
* @return True if the rectangle takes no space.
|
||||||
|
*/
|
||||||
|
bool is_empty() const
|
||||||
|
{
|
||||||
|
return SDL_RectEmpty(this);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
194
src/wrappers/sdl/render.cppm
Normal file
194
src/wrappers/sdl/render.cppm
Normal file
|
|
@ -0,0 +1,194 @@
|
||||||
|
module;
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
export module wrappers.sdl.render;
|
||||||
|
|
||||||
|
import std;
|
||||||
|
import utils.memory;
|
||||||
|
import wrappers.sdl.error;
|
||||||
|
import wrappers.sdl.rect;
|
||||||
|
import wrappers.sdl.video;
|
||||||
|
|
||||||
|
export namespace sdl
|
||||||
|
{
|
||||||
|
using Vertex = SDL_Vertex;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper for SDL_Texture that manages its lifecycle with a unique_ptr.
|
||||||
|
*/
|
||||||
|
class Texture
|
||||||
|
{
|
||||||
|
std::unique_ptr<
|
||||||
|
SDL_Texture,
|
||||||
|
utils::FuncDeleter<SDL_DestroyTexture>
|
||||||
|
> raw_texture_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Texture() = delete;
|
||||||
|
|
||||||
|
explicit Texture(SDL_Texture* raw_texture)
|
||||||
|
: raw_texture_{raw_texture}
|
||||||
|
{}
|
||||||
|
|
||||||
|
constexpr SDL_Texture* get_raw() const
|
||||||
|
{
|
||||||
|
assert(raw_texture_);
|
||||||
|
return raw_texture_.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr int get_width() const
|
||||||
|
{
|
||||||
|
return raw_texture_->w;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr int get_height() const
|
||||||
|
{
|
||||||
|
return raw_texture_->h;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr FRect get_boundaries() const
|
||||||
|
{
|
||||||
|
return {0, 0, static_cast<float>(raw_texture_->w), static_cast<float>(raw_texture_->h)};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper for SDL_Renderer that manages its lifecycle with a unique_ptr.
|
||||||
|
*/
|
||||||
|
class Renderer
|
||||||
|
{
|
||||||
|
std::unique_ptr<
|
||||||
|
SDL_Renderer,
|
||||||
|
utils::FuncDeleter<SDL_DestroyRenderer>
|
||||||
|
> raw_renderer_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Renderer() = delete;
|
||||||
|
|
||||||
|
explicit Renderer(SDL_Renderer* raw_renderer)
|
||||||
|
: raw_renderer_{raw_renderer}
|
||||||
|
{}
|
||||||
|
|
||||||
|
constexpr SDL_Renderer* get_raw() const
|
||||||
|
{
|
||||||
|
assert(raw_renderer_);
|
||||||
|
return raw_renderer_.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
{
|
||||||
|
// TODO: These functions probably should never fail? Change this to a runtime exception if necessary.
|
||||||
|
if (!SDL_RenderClear(get_raw())) {
|
||||||
|
assert(!"SDL_RenderClear returned false");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper for SDL_RenderPresent().
|
||||||
|
* Uses assert to verify the function returned true.
|
||||||
|
*/
|
||||||
|
constexpr void present() const
|
||||||
|
{
|
||||||
|
if (!SDL_RenderPresent(get_raw())) {
|
||||||
|
assert(!"SDL_RenderPresent returned false");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper for SDL_RenderTexture().
|
||||||
|
* Uses assert to verify the function returned true.
|
||||||
|
*/
|
||||||
|
constexpr void render_texture(
|
||||||
|
const Texture& texture,
|
||||||
|
const FRect* src_rect,
|
||||||
|
const FRect* dest_rect
|
||||||
|
) const
|
||||||
|
{
|
||||||
|
if (!SDL_RenderTexture(get_raw(), texture.get_raw(), src_rect, dest_rect)) {
|
||||||
|
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 for SDL_CreateWindowAndRenderer().
|
||||||
|
* Returns a pair of a Window and a Renderer object.
|
||||||
|
* Throws an SDLException on failure.
|
||||||
|
*/
|
||||||
|
std::pair<Window, Renderer> create_window_and_renderer(
|
||||||
|
const std::string& title,
|
||||||
|
const int width,
|
||||||
|
const int height,
|
||||||
|
const SDL_WindowFlags window_flags
|
||||||
|
)
|
||||||
|
{
|
||||||
|
SDL_Window* raw_window = nullptr;
|
||||||
|
SDL_Renderer* raw_renderer = nullptr;
|
||||||
|
|
||||||
|
if (!SDL_CreateWindowAndRenderer(
|
||||||
|
title.c_str(), width, height, window_flags, &raw_window, &raw_renderer
|
||||||
|
)) {
|
||||||
|
throw SDLException("SDL_CreateWindowAndRenderer");
|
||||||
|
}
|
||||||
|
|
||||||
|
return {Window{raw_window}, Renderer{raw_renderer}};
|
||||||
|
}
|
||||||
|
}
|
||||||
98
src/wrappers/sdl/surface.cppm
Normal file
98
src/wrappers/sdl/surface.cppm
Normal file
|
|
@ -0,0 +1,98 @@
|
||||||
|
module;
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
export module wrappers.sdl.surface;
|
||||||
|
|
||||||
|
import std;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
23
src/wrappers/sdl/utils.cppm
Normal file
23
src/wrappers/sdl/utils.cppm
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
module;
|
||||||
|
|
||||||
|
#include <SDL3/SDL_stdinc.h>
|
||||||
|
|
||||||
|
export module wrappers.sdl.utils;
|
||||||
|
|
||||||
|
import std;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
101
src/wrappers/sdl/video.cppm
Normal file
101
src/wrappers/sdl/video.cppm
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
module;
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
export module wrappers.sdl.video;
|
||||||
|
|
||||||
|
import std;
|
||||||
|
import utils.memory;
|
||||||
|
import wrappers.sdl.error;
|
||||||
|
import wrappers.sdl.rect;
|
||||||
|
|
||||||
|
export namespace sdl
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Wrapper for SDL_Window that manages its lifecycle with a unique_ptr.
|
||||||
|
*/
|
||||||
|
class Window
|
||||||
|
{
|
||||||
|
std::unique_ptr<
|
||||||
|
SDL_Window,
|
||||||
|
utils::FuncDeleter<SDL_DestroyWindow>
|
||||||
|
> raw_window_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Window() = delete;
|
||||||
|
|
||||||
|
explicit Window(SDL_Window* raw_window)
|
||||||
|
: raw_window_{raw_window}
|
||||||
|
{}
|
||||||
|
|
||||||
|
constexpr SDL_Window* get_raw() const
|
||||||
|
{
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
43
src/wrappers/sdl_image.cppm
Normal file
43
src/wrappers/sdl_image.cppm
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
module;
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
#include <SDL3_image/SDL_image.h>
|
||||||
|
|
||||||
|
export module wrappers.sdl_image;
|
||||||
|
|
||||||
|
import std;
|
||||||
|
import utils.memory;
|
||||||
|
import wrappers.sdl.error;
|
||||||
|
import wrappers.sdl.render;
|
||||||
|
import wrappers.sdl.surface;
|
||||||
|
|
||||||
|
export namespace sdl_image
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Wrapper for IMG_Load().
|
||||||
|
*/
|
||||||
|
sdl::Surface load(const std::string& filename)
|
||||||
|
{
|
||||||
|
SDL_Surface* raw_surface = IMG_Load(filename.c_str());
|
||||||
|
|
||||||
|
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{raw_texture};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,14 +3,13 @@
|
||||||
############################################
|
############################################
|
||||||
# shuriken - A ninja-based C++ build tool
|
# shuriken - A ninja-based C++ build tool
|
||||||
# ------------------------------------------
|
# ------------------------------------------
|
||||||
# Version: 0.1.0
|
# Version: 0.1.0 (modified)
|
||||||
# Author: binaryDiv
|
# Author: binaryDiv
|
||||||
# License: MIT License
|
# License: MIT License
|
||||||
# https://git.0xbd.space/binaryDiv/shuriken
|
# https://git.0xbd.space/binaryDiv/shuriken
|
||||||
############################################
|
############################################
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import copy
|
|
||||||
import json
|
import json
|
||||||
import shlex
|
import shlex
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
@ -18,7 +17,7 @@ import sys
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
from dataclasses import asdict, dataclass, field
|
from dataclasses import asdict, dataclass, field
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any, Self
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
|
|
@ -60,10 +59,43 @@ class TargetConfig:
|
||||||
return f"{self.linker_args or ''} {self.linker_args_extra or ''}".strip()
|
return f"{self.linker_args or ''} {self.linker_args_extra or ''}".strip()
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(kw_only=True)
|
||||||
|
class MergedTargetConfig(TargetConfig):
|
||||||
|
cpp_compiler: str = ''
|
||||||
|
cpp_standard: str = ''
|
||||||
|
cpp_flags: str = ''
|
||||||
|
cpp_flags_extra: str = ''
|
||||||
|
|
||||||
|
linker_flags: str = ''
|
||||||
|
linker_flags_extra: str = ''
|
||||||
|
linker_args: str = ''
|
||||||
|
linker_args_extra: str = ''
|
||||||
|
|
||||||
|
output_file: str = ''
|
||||||
|
run_command: str = ''
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create_from(cls, target_defaults: TargetConfig, target_config: TargetConfig) -> Self:
|
||||||
|
merged_config = cls()
|
||||||
|
merged_config.update(asdict(target_defaults))
|
||||||
|
merged_config.update(asdict(target_config))
|
||||||
|
return merged_config
|
||||||
|
|
||||||
|
def validate(self) -> None:
|
||||||
|
# Validate that all required fields are set
|
||||||
|
if not self.cpp_compiler:
|
||||||
|
raise ConfigValidationException('cpp_compiler must be set')
|
||||||
|
if not self.cpp_standard:
|
||||||
|
raise ConfigValidationException('cpp_standard must be set')
|
||||||
|
if not self.output_file:
|
||||||
|
raise ConfigValidationException('output_file must be set')
|
||||||
|
|
||||||
|
|
||||||
@dataclass(kw_only=True)
|
@dataclass(kw_only=True)
|
||||||
class ShurikenConfig:
|
class ShurikenConfig:
|
||||||
__path_fields = ['source_dir', 'build_dir']
|
__path_fields = ['source_dir', 'build_dir']
|
||||||
__list_fields = []
|
__list_fields = []
|
||||||
|
__bool_fields = ['enable_import_std']
|
||||||
|
|
||||||
source_dir: Path = Path('src')
|
source_dir: Path = Path('src')
|
||||||
build_dir: Path = Path('build')
|
build_dir: Path = Path('build')
|
||||||
|
|
@ -71,14 +103,16 @@ class ShurikenConfig:
|
||||||
defaults: TargetConfig = field(default_factory=lambda: TargetConfig(
|
defaults: TargetConfig = field(default_factory=lambda: TargetConfig(
|
||||||
cpp_compiler='clang++',
|
cpp_compiler='clang++',
|
||||||
cpp_standard='c++20',
|
cpp_standard='c++20',
|
||||||
cpp_flags='',
|
|
||||||
linker_flags='',
|
|
||||||
linker_args='',
|
|
||||||
))
|
))
|
||||||
|
|
||||||
|
enable_import_std: bool = False
|
||||||
|
|
||||||
default_target: str = ''
|
default_target: str = ''
|
||||||
targets: dict[str, TargetConfig] = field(default_factory=dict)
|
targets: dict[str, TargetConfig] = field(default_factory=dict)
|
||||||
|
|
||||||
|
# Merged target configs, generated from defaults and targets
|
||||||
|
_merged_target_configs = {}
|
||||||
|
|
||||||
def update(self, data: dict) -> None:
|
def update(self, data: dict) -> None:
|
||||||
for key, value in data.items():
|
for key, value in data.items():
|
||||||
# Ignore unknown config keys
|
# Ignore unknown config keys
|
||||||
|
|
@ -86,13 +120,13 @@ class ShurikenConfig:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if key == 'defaults':
|
if key == 'defaults':
|
||||||
assert (type(value) is dict)
|
assert type(value) is dict
|
||||||
self.defaults.update(value)
|
self.defaults.update(value)
|
||||||
elif key == 'targets':
|
elif key == 'targets':
|
||||||
assert (type(value) is dict)
|
assert type(value) is dict
|
||||||
for target_name, target_config in value.items():
|
for target_name, target_config in value.items():
|
||||||
assert (type(target_name) is str and len(target_name) > 0)
|
assert type(target_name) is str and len(target_name) > 0
|
||||||
assert (type(target_config) is dict or target_config is None)
|
assert type(target_config) is dict or target_config is None
|
||||||
|
|
||||||
# Ignore "hidden" targets starting with a dot (can be used for YAML anchors)
|
# Ignore "hidden" targets starting with a dot (can be used for YAML anchors)
|
||||||
if target_name[0] == '.':
|
if target_name[0] == '.':
|
||||||
|
|
@ -105,11 +139,18 @@ class ShurikenConfig:
|
||||||
elif key in self.__path_fields:
|
elif key in self.__path_fields:
|
||||||
setattr(self, key, Path(value))
|
setattr(self, key, Path(value))
|
||||||
elif key in self.__list_fields:
|
elif key in self.__list_fields:
|
||||||
assert (type(value) is list)
|
assert type(value) is list
|
||||||
setattr(self, key, list(str(item) for item in value))
|
setattr(self, key, list(str(item) for item in value))
|
||||||
|
elif key in self.__bool_fields:
|
||||||
|
assert type(value) is bool
|
||||||
|
setattr(self, key, value)
|
||||||
else:
|
else:
|
||||||
setattr(self, key, str(value))
|
setattr(self, key, str(value))
|
||||||
|
|
||||||
|
# Generate merged target configs (we generate all of them so that we can validate the full config)
|
||||||
|
for target_name, target_config in self.targets.items():
|
||||||
|
self._merged_target_configs[target_name] = MergedTargetConfig.create_from(self.defaults, target_config)
|
||||||
|
|
||||||
def update_from_yaml(self, file_path: Path) -> None:
|
def update_from_yaml(self, file_path: Path) -> None:
|
||||||
with file_path.open('r') as file:
|
with file_path.open('r') as file:
|
||||||
self.update(yaml.safe_load(file))
|
self.update(yaml.safe_load(file))
|
||||||
|
|
@ -131,23 +172,18 @@ class ShurikenConfig:
|
||||||
raise ConfigValidationException('default_target is not defined in targets')
|
raise ConfigValidationException('default_target is not defined in targets')
|
||||||
|
|
||||||
# Validate individual build targets
|
# Validate individual build targets
|
||||||
for target_name, target_config in self.targets.items():
|
for target_name, merged_target_config in self._merged_target_configs.items():
|
||||||
if not target_config.cpp_compiler and not self.defaults.cpp_compiler:
|
try:
|
||||||
raise ConfigValidationException(f'Target "{target_name}": cpp_compiler must be set')
|
merged_target_config.validate()
|
||||||
if not target_config.cpp_standard and not self.defaults.cpp_standard:
|
except ConfigValidationException as exc:
|
||||||
raise ConfigValidationException(f'Target "{target_name}": cpp_standard must be set')
|
raise ConfigValidationException(f'Target "{target_name}": {str(exc)}')
|
||||||
if not target_config.output_file and not self.defaults.output_file:
|
|
||||||
raise ConfigValidationException(f'Target "{target_name}": output_file must be set')
|
|
||||||
|
|
||||||
def get_ninja_file_path(self, target: str) -> Path:
|
def get_ninja_file_path(self, target: str) -> Path:
|
||||||
return self.build_dir / f'build.{target}.ninja'
|
return self.build_dir / f'build.{target}.ninja'
|
||||||
|
|
||||||
def get_merged_target_config(self, target: str) -> TargetConfig:
|
def get_merged_target_config(self, target: str) -> MergedTargetConfig:
|
||||||
assert target in self.targets, f'Target "{target}" not defined!'
|
assert target in self.targets, f'Target "{target}" not defined!'
|
||||||
|
return self._merged_target_configs[target]
|
||||||
merged_config = copy.deepcopy(self.defaults)
|
|
||||||
merged_config.update(asdict(self.targets[target]))
|
|
||||||
return merged_config
|
|
||||||
|
|
||||||
|
|
||||||
class ShurikenArgumentParser:
|
class ShurikenArgumentParser:
|
||||||
|
|
@ -186,8 +222,8 @@ class ShurikenArgumentParser:
|
||||||
help='Print generated Ninja file to stdout instead of writing to a file',
|
help='Print generated Ninja file to stdout instead of writing to a file',
|
||||||
)
|
)
|
||||||
|
|
||||||
subparser_build = subparsers.add_parser('build', help='Build project (default command)')
|
_subparser_build = subparsers.add_parser('build', help='Build project (default command)')
|
||||||
subparser_compdb = subparsers.add_parser('compdb', help='Generate compilation database')
|
_subparser_compdb = subparsers.add_parser('compdb', help='Generate compilation database')
|
||||||
|
|
||||||
subparser_run = subparsers.add_parser('run', help='Build and run project')
|
subparser_run = subparsers.add_parser('run', help='Build and run project')
|
||||||
subparser_run.add_argument(
|
subparser_run.add_argument(
|
||||||
|
|
@ -196,6 +232,13 @@ class ShurikenArgumentParser:
|
||||||
help='Arguments that are passed to the application',
|
help='Arguments that are passed to the application',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
subparser_lint = subparsers.add_parser('lint', help='Lint the project')
|
||||||
|
subparser_lint.add_argument(
|
||||||
|
'lint_files',
|
||||||
|
nargs='*',
|
||||||
|
help='Files to lint (defaults to all source files)',
|
||||||
|
)
|
||||||
|
|
||||||
subparser_clean = subparsers.add_parser('clean', help='Remove all build files of the current target')
|
subparser_clean = subparsers.add_parser('clean', help='Remove all build files of the current target')
|
||||||
subparser_clean.add_argument(
|
subparser_clean.add_argument(
|
||||||
'--all',
|
'--all',
|
||||||
|
|
@ -204,11 +247,11 @@ class ShurikenArgumentParser:
|
||||||
help='Remove all generated files from all targets',
|
help='Remove all generated files from all targets',
|
||||||
)
|
)
|
||||||
|
|
||||||
subparser_dump_config = subparsers.add_parser(
|
_subparser_dump_config = subparsers.add_parser(
|
||||||
'dump-config',
|
'dump-config',
|
||||||
help='Dumps the parsed config as well as the effective build target config (for debugging)',
|
help='Dumps the parsed config as well as the effective build target config (for debugging)',
|
||||||
)
|
)
|
||||||
subparser_dyndep = subparsers.add_parser(
|
_subparser_dyndep = subparsers.add_parser(
|
||||||
'p1689-to-dyndeps',
|
'p1689-to-dyndeps',
|
||||||
help='(Internal command) Parse p1689 file and generate Ninja dyndeps',
|
help='(Internal command) Parse p1689 file and generate Ninja dyndeps',
|
||||||
)
|
)
|
||||||
|
|
@ -298,6 +341,9 @@ class ShurikenCli:
|
||||||
case 'run':
|
case 'run':
|
||||||
self.run_project(selected_target)
|
self.run_project(selected_target)
|
||||||
|
|
||||||
|
case 'lint':
|
||||||
|
self.lint_project(selected_target)
|
||||||
|
|
||||||
case 'clean':
|
case 'clean':
|
||||||
if self.args.clean_all:
|
if self.args.clean_all:
|
||||||
self.log_info('Cleaning up *all* build targets')
|
self.log_info('Cleaning up *all* build targets')
|
||||||
|
|
@ -413,6 +459,26 @@ class ShurikenCli:
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
self.log_info('Keyboard interrupt')
|
self.log_info('Keyboard interrupt')
|
||||||
|
|
||||||
|
def lint_project(self, target: str) -> None:
|
||||||
|
# Build project using Ninja (necessary before linting)
|
||||||
|
self.build_project(target)
|
||||||
|
|
||||||
|
# Collect files to lint
|
||||||
|
if self.args.lint_files:
|
||||||
|
files_args = shlex.join(self.args.lint_files)
|
||||||
|
else:
|
||||||
|
files_args = f"$(find {self.config.source_dir} -name '*.cpp' -or -name '*.cppm')"
|
||||||
|
|
||||||
|
self.log_info('Running clangd-tidy')
|
||||||
|
run_result = subprocess.run(
|
||||||
|
f'clangd-tidy -p {self.config.build_dir} --allow-extensions cpp,cppm {files_args}',
|
||||||
|
shell=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
if run_result.returncode > 0:
|
||||||
|
self.log_error(f'clangd-tidy exited with return code {run_result.returncode}')
|
||||||
|
exit(1)
|
||||||
|
|
||||||
def clean_project(self, target: str) -> None:
|
def clean_project(self, target: str) -> None:
|
||||||
ninja_file_path = self.config.get_ninja_file_path(target)
|
ninja_file_path = self.config.get_ninja_file_path(target)
|
||||||
|
|
||||||
|
|
@ -454,13 +520,14 @@ class ShurikenCli:
|
||||||
print('ninja_dyndep_version = 1\n')
|
print('ninja_dyndep_version = 1\n')
|
||||||
for out, deps in build_dependencies.items():
|
for out, deps in build_dependencies.items():
|
||||||
if deps:
|
if deps:
|
||||||
print(f'build {out}: dyndep | {" ".join(deps)}')
|
print(f'build {out}: dyndep | {" ".join(str(dep) for dep in deps)}')
|
||||||
else:
|
else:
|
||||||
print(f'build {out}: dyndep')
|
print(f'build {out}: dyndep')
|
||||||
|
|
||||||
|
|
||||||
CompileTargetCpp = namedtuple('CompileTargetCpp', ['src', 'obj'])
|
CompileTargetCpp = namedtuple('CompileTargetCpp', ['src', 'obj'])
|
||||||
CompileTargetCppm = namedtuple('CompileTargetCppm', ['src', 'obj', 'pcm'])
|
CompileTargetCppm = namedtuple('CompileTargetCppm', ['src', 'obj', 'pcm'])
|
||||||
|
CompileTargetStdModule = namedtuple('CompileTargetStdModule', ['original_src', 'generated_src', 'pcm'])
|
||||||
|
|
||||||
|
|
||||||
class NinjaFileGenerator:
|
class NinjaFileGenerator:
|
||||||
|
|
@ -470,6 +537,7 @@ class NinjaFileGenerator:
|
||||||
|
|
||||||
compile_targets_cpp: list[CompileTargetCpp]
|
compile_targets_cpp: list[CompileTargetCpp]
|
||||||
compile_targets_cppm: list[CompileTargetCppm]
|
compile_targets_cppm: list[CompileTargetCppm]
|
||||||
|
compile_targets_std_module: list[CompileTargetStdModule]
|
||||||
link_target_obj_files: list[str]
|
link_target_obj_files: list[str]
|
||||||
|
|
||||||
def __init__(self, *, config: ShurikenConfig, target: str):
|
def __init__(self, *, config: ShurikenConfig, target: str):
|
||||||
|
|
@ -487,6 +555,7 @@ class NinjaFileGenerator:
|
||||||
def collect_compile_targets(self) -> None:
|
def collect_compile_targets(self) -> None:
|
||||||
self.compile_targets_cpp = []
|
self.compile_targets_cpp = []
|
||||||
self.compile_targets_cppm = []
|
self.compile_targets_cppm = []
|
||||||
|
self.compile_targets_std_module = []
|
||||||
|
|
||||||
# Compile targets for .cpp files
|
# Compile targets for .cpp files
|
||||||
for src_file in self.find_source_files('**/*.cpp'):
|
for src_file in self.find_source_files('**/*.cpp'):
|
||||||
|
|
@ -499,10 +568,23 @@ class NinjaFileGenerator:
|
||||||
# Compile targets for .cppm files
|
# Compile targets for .cppm files
|
||||||
for src_file in self.find_source_files('**/*.cppm'):
|
for src_file in self.find_source_files('**/*.cppm'):
|
||||||
relative_name = src_file.relative_to(self.config.source_dir)
|
relative_name = src_file.relative_to(self.config.source_dir)
|
||||||
|
module_name = '.'.join(relative_name.with_suffix('.pcm').parts)
|
||||||
self.compile_targets_cppm.append(CompileTargetCppm(
|
self.compile_targets_cppm.append(CompileTargetCppm(
|
||||||
src=str(src_file),
|
src=str(src_file),
|
||||||
obj=f"$builddir/obj/{relative_name.with_suffix('.o')}",
|
obj=f"$builddir/obj/{relative_name.with_suffix('.o')}",
|
||||||
pcm=f"$builddir/pcm/{'.'.join(relative_name.with_suffix('.pcm').parts)}",
|
pcm=f"$builddir/pcm/{module_name}",
|
||||||
|
))
|
||||||
|
|
||||||
|
# Compile targets for standard library modules ("import std")
|
||||||
|
if self.config.enable_import_std:
|
||||||
|
# NOTE: Add std.compat to the list (here and a few other places) if we ever need it. For now, std is enough.
|
||||||
|
std_module_source_finder = StdModuleSourceFinder(config=self.config, target=self.target)
|
||||||
|
std_module_sources = std_module_source_finder.find_module_sources(['std'])
|
||||||
|
for module_name, module_src_path in std_module_sources.items():
|
||||||
|
self.compile_targets_std_module.append(CompileTargetStdModule(
|
||||||
|
original_src=str(module_src_path),
|
||||||
|
generated_src=f"$builddir/generated/stdlib/{module_name}.cppm",
|
||||||
|
pcm=f"$builddir/pcm/{module_name}.pcm",
|
||||||
))
|
))
|
||||||
|
|
||||||
# Link target for output file (executable): gather all object files
|
# Link target for output file (executable): gather all object files
|
||||||
|
|
@ -518,7 +600,25 @@ class NinjaFileGenerator:
|
||||||
config = self.config
|
config = self.config
|
||||||
target = self.target
|
target = self.target
|
||||||
target_config = self.target_config
|
target_config = self.target_config
|
||||||
build_dir = self.config.build_dir
|
build_dir = config.build_dir
|
||||||
|
ninja_file_path = config.get_ninja_file_path(config.default_target)
|
||||||
|
cpp_compiler = target_config.cpp_compiler
|
||||||
|
|
||||||
|
# Generate list of all source input files to be used as implicit dependencies for the dyndep file
|
||||||
|
dyndep_file_implicit_inputs = ' '.join(sorted(
|
||||||
|
t.src for t in self.compile_targets_cpp + self.compile_targets_cppm
|
||||||
|
))
|
||||||
|
|
||||||
|
# Generate list of all .dep files that are generated implicitly during dyndep file generation.
|
||||||
|
# This is mostly necessary so that Ninja can create the directories for these files beforehand.
|
||||||
|
dyndep_file_implicit_outputs = ' '.join(sorted(
|
||||||
|
f'{t.obj}.dep' for t in self.compile_targets_cpp + self.compile_targets_cppm
|
||||||
|
))
|
||||||
|
|
||||||
|
# Generate list of generated source files (currently just the copied std module file)
|
||||||
|
generated_source_files = ' '.join(sorted(
|
||||||
|
t.generated_src for t in self.compile_targets_std_module
|
||||||
|
))
|
||||||
|
|
||||||
# Generate Ninja file
|
# Generate Ninja file
|
||||||
return f"""
|
return f"""
|
||||||
|
|
@ -530,27 +630,39 @@ compdb_file = {build_dir}/compile_commands.json
|
||||||
dyndep_file = $builddir/.ninja_dyndep
|
dyndep_file = $builddir/.ninja_dyndep
|
||||||
|
|
||||||
# Compiler flags
|
# Compiler flags
|
||||||
cpp_flags = -std={target_config.cpp_standard} -fprebuilt-module-path=$builddir/pcm/ {target_config.merged_cpp_flags}
|
cpp_base_flags = -std={target_config.cpp_standard} -fprebuilt-module-path=$builddir/pcm/
|
||||||
|
cpp_flags = $cpp_base_flags {target_config.merged_cpp_flags}
|
||||||
|
cpp_std_module_flags = $cpp_base_flags -Wno-reserved-module-identifier -x c++-module
|
||||||
linker_flags = {target_config.merged_linker_flags}
|
linker_flags = {target_config.merged_linker_flags}
|
||||||
linker_args = {target_config.merged_linker_args}
|
linker_args = {target_config.merged_linker_args}
|
||||||
|
|
||||||
# -- RULES
|
# -- RULES
|
||||||
|
|
||||||
|
# Rule to copy an arbitrary file (or multiple) from in to out
|
||||||
|
rule copy
|
||||||
|
command = cp $in $out
|
||||||
|
|
||||||
# Rule to compile a .cpp file to a .o file
|
# Rule to compile a .cpp file to a .o file
|
||||||
rule cpp
|
rule cpp
|
||||||
command = {target_config.cpp_compiler} $cpp_flags -c $in -o $out
|
depfile = $out.dep
|
||||||
|
command = {cpp_compiler} $cpp_flags -MMD -MF $out.dep -c $in -o $out
|
||||||
|
|
||||||
# Rule to compile a .cppm file (C++20 module) to a .o file ($out) and a .pcm file ($pcm_out)
|
# Rule to compile a .cppm file (C++20 module) to a .o file ($out) and a .pcm file ($pcm_out)
|
||||||
rule cppm
|
rule cppm
|
||||||
command = {target_config.cpp_compiler} $cpp_flags -c $in -o $out -fmodule-output=$pcm_out
|
depfile = $out.dep
|
||||||
|
command = {cpp_compiler} $cpp_flags -MMD -MF $out.dep -c $in -o $out -fmodule-output=$pcm_out
|
||||||
|
|
||||||
|
# Rule to compile a standard library module to a .pcm file (e.g. "import std")
|
||||||
|
rule std_module
|
||||||
|
command = {cpp_compiler} $cpp_std_module_flags --precompile $in -o $out
|
||||||
|
|
||||||
# Rule to link several .o files to an executable
|
# Rule to link several .o files to an executable
|
||||||
rule link
|
rule link
|
||||||
command = {target_config.cpp_compiler} $linker_flags -o $out $in $linker_args
|
command = {cpp_compiler} $linker_flags -o $out $in $linker_args
|
||||||
|
|
||||||
# Rule to generate a compilation database (JSON file) from the build targets defined in a Ninja file
|
# Rule to generate a compilation database (JSON file) from the build targets defined in a Ninja file
|
||||||
rule generate_compdb
|
rule generate_compdb
|
||||||
command = ninja -f $in -t compdb cpp cppm > $out
|
command = ninja -f $in -t compdb cpp cppm std_module > $out
|
||||||
|
|
||||||
# Rule to generate a Ninja dyndep file for C++ module dependencies based on a compilation database
|
# Rule to generate a Ninja dyndep file for C++ module dependencies based on a compilation database
|
||||||
rule generate_dyndeps
|
rule generate_dyndeps
|
||||||
|
|
@ -560,13 +672,13 @@ rule generate_dyndeps
|
||||||
# -- STATIC BUILD TARGETS
|
# -- STATIC BUILD TARGETS
|
||||||
|
|
||||||
# Generate compilation database from default target Ninja file
|
# Generate compilation database from default target Ninja file
|
||||||
build $compdb_file: generate_compdb {self.config.get_ninja_file_path(config.default_target)}
|
build $compdb_file: generate_compdb {ninja_file_path} | generate_source_files
|
||||||
|
|
||||||
# Shortcut alias to generate compilation database
|
# Shortcut alias to generate compilation database
|
||||||
build compdb: phony $compdb_file
|
build compdb: phony $compdb_file
|
||||||
|
|
||||||
# Generate Ninja dyndep file from compilation database
|
# Generate Ninja dyndep file from compilation database
|
||||||
build $dyndep_file: generate_dyndeps $compdb_file
|
build $dyndep_file | {dyndep_file_implicit_outputs}: generate_dyndeps $compdb_file | {dyndep_file_implicit_inputs}
|
||||||
|
|
||||||
|
|
||||||
# -- GENERATED BUILD TARGETS
|
# -- GENERATED BUILD TARGETS
|
||||||
|
|
@ -575,15 +687,19 @@ build $dyndep_file: generate_dyndeps $compdb_file
|
||||||
build $builddir/{target_config.output_file}: link {' '.join(self.link_target_obj_files)}
|
build $builddir/{target_config.output_file}: link {' '.join(self.link_target_obj_files)}
|
||||||
default $builddir/{target_config.output_file}
|
default $builddir/{target_config.output_file}
|
||||||
|
|
||||||
|
# Phony target that depends on all generated source files
|
||||||
|
build generate_source_files: phony {generated_source_files}
|
||||||
|
|
||||||
{self.render_compile_targets()}
|
{self.render_compile_targets()}
|
||||||
|
|
||||||
# End of build.{target}.ninja
|
# End of build.{target}.ninja
|
||||||
""".strip()
|
""".strip()
|
||||||
|
|
||||||
def render_compile_targets(self) -> str:
|
def render_compile_targets(self) -> str:
|
||||||
rendered_targets_cpp = [self.render_compile_target_cpp(target) for target in self.compile_targets_cpp]
|
rendered_targets = list(map(self.render_compile_target_std_module, self.compile_targets_std_module))
|
||||||
rendered_targets_cppm = [self.render_compile_target_cppm(target) for target in self.compile_targets_cppm]
|
rendered_targets += list(map(self.render_compile_target_cpp, self.compile_targets_cpp))
|
||||||
return '\n\n'.join(rendered_targets_cpp + rendered_targets_cppm)
|
rendered_targets += list(map(self.render_compile_target_cppm, self.compile_targets_cppm))
|
||||||
|
return '\n\n'.join(rendered_targets)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def render_compile_target_cpp(target: CompileTargetCpp) -> str:
|
def render_compile_target_cpp(target: CompileTargetCpp) -> str:
|
||||||
|
|
@ -600,23 +716,34 @@ build {target.obj} | {target.pcm}: cppm {target.src} || $dyndep_file
|
||||||
pcm_out = {target.pcm}
|
pcm_out = {target.pcm}
|
||||||
""".strip()
|
""".strip()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def render_compile_target_std_module(target: CompileTargetStdModule) -> str:
|
||||||
|
return f"""
|
||||||
|
build {target.generated_src}: copy {target.original_src}
|
||||||
|
|
||||||
|
build {target.pcm}: std_module {target.generated_src} || $dyndep_file
|
||||||
|
dyndep = $dyndep_file
|
||||||
|
""".strip()
|
||||||
|
|
||||||
|
|
||||||
class BuildDependencyParser:
|
class BuildDependencyParser:
|
||||||
config: ShurikenConfig
|
config: ShurikenConfig
|
||||||
target: str
|
target: str
|
||||||
|
|
||||||
def __init__(self, config: ShurikenConfig, target: str):
|
def __init__(self, *, config: ShurikenConfig, target: str):
|
||||||
self.config = config
|
self.config = config
|
||||||
self.target = target
|
self.target = target
|
||||||
|
|
||||||
def parse_build_dependencies(self, parsed_p1689: dict[str, Any]) -> dict[str, list[str]]:
|
def parse_build_dependencies(self, parsed_p1689: dict[str, Any]) -> dict[Path, list[Path]]:
|
||||||
# Shortcut variables
|
# Shortcut variables
|
||||||
source_dir = self.config.source_dir
|
source_dir = self.config.source_dir
|
||||||
target_build_dir = self.config.build_dir / self.target
|
target_build_dir = self.config.build_dir / self.target
|
||||||
default_target_build_dir = self.config.build_dir / self.config.default_target
|
default_target_build_dir = self.config.build_dir / self.config.default_target
|
||||||
|
|
||||||
|
# Map of module names to PCM file paths
|
||||||
|
module_map: dict[str, Path] = {}
|
||||||
|
|
||||||
# Validate module name and path convention, construct map of module names to PCM file paths
|
# Validate module name and path convention, construct map of module names to PCM file paths
|
||||||
module_map = {}
|
|
||||||
for rule in parsed_p1689['rules']:
|
for rule in parsed_p1689['rules']:
|
||||||
provides = rule.get('provides', [])
|
provides = rule.get('provides', [])
|
||||||
if provides:
|
if provides:
|
||||||
|
|
@ -624,15 +751,19 @@ class BuildDependencyParser:
|
||||||
assert provides[0]['is-interface'] is True, f"Rule provides non-interface module: {rule}"
|
assert provides[0]['is-interface'] is True, f"Rule provides non-interface module: {rule}"
|
||||||
|
|
||||||
module_name: str = provides[0]['logical-name']
|
module_name: str = provides[0]['logical-name']
|
||||||
assert module_name not in module_map, f"Module {module_name} is provided more than once: {rule}"
|
assert module_name not in module_map, f'Module "{module_name}" is provided more than once: {rule}'
|
||||||
|
|
||||||
|
if module_name != 'std':
|
||||||
expected_source_path = str(Path(source_dir, module_name.replace('.', '/') + '.cppm'))
|
expected_source_path = str(Path(source_dir, module_name.replace('.', '/') + '.cppm'))
|
||||||
assert provides[0]['source-path'] == expected_source_path, f"Module name does not match source path: {rule}"
|
assert provides[0]['source-path'] == expected_source_path, \
|
||||||
|
f"Module name does not match source path: {rule}"
|
||||||
|
|
||||||
module_map[module_name] = str(Path(target_build_dir, 'pcm', module_name + '.pcm'))
|
module_map[module_name] = Path(target_build_dir, 'pcm', module_name + '.pcm')
|
||||||
|
|
||||||
|
# Collect all module dependencies to generate dyndeps
|
||||||
|
build_dependencies: dict[Path, list[Path]] = {}
|
||||||
|
|
||||||
# Parse rules and get module dependencies
|
# Parse rules and get module dependencies
|
||||||
build_dependencies = {}
|
|
||||||
for rule in parsed_p1689['rules']:
|
for rule in parsed_p1689['rules']:
|
||||||
# Compilation database contains "build/{default_target}" paths, we need to map them to "build/{target}"
|
# Compilation database contains "build/{default_target}" paths, we need to map them to "build/{target}"
|
||||||
out = target_build_dir / Path(rule['primary-output']).relative_to(default_target_build_dir)
|
out = target_build_dir / Path(rule['primary-output']).relative_to(default_target_build_dir)
|
||||||
|
|
@ -640,11 +771,65 @@ class BuildDependencyParser:
|
||||||
build_dependencies[out] = []
|
build_dependencies[out] = []
|
||||||
for require in rule.get('requires', []):
|
for require in rule.get('requires', []):
|
||||||
module_name = require['logical-name']
|
module_name = require['logical-name']
|
||||||
assert module_name in module_map, f'Module {module_name} not found in module map'
|
assert module_name in module_map, f'Module "{module_name}" not found in module map'
|
||||||
build_dependencies[out].append(module_map[module_name])
|
build_dependencies[out].append(module_map[module_name])
|
||||||
|
|
||||||
return build_dependencies
|
return build_dependencies
|
||||||
|
|
||||||
|
|
||||||
|
class StdModuleSourceFinder:
|
||||||
|
cpp_compiler: str
|
||||||
|
|
||||||
|
def __init__(self, *, config: ShurikenConfig, target: str):
|
||||||
|
self.cpp_compiler = config.get_merged_target_config(target).cpp_compiler
|
||||||
|
|
||||||
|
def find_module_sources(self, module_names: list[str]) -> dict[str, Path]:
|
||||||
|
# Get normalized path of standard library "module manifest" (JSON file containing source paths for std modules)
|
||||||
|
module_manifest_path = self.get_module_manifest_path()
|
||||||
|
module_manifest_base_dir = module_manifest_path.parent
|
||||||
|
|
||||||
|
# Parse module manifest JSON file
|
||||||
|
with module_manifest_path.open('r') as f:
|
||||||
|
module_manifest_json = json.load(f)
|
||||||
|
|
||||||
|
# Collect the module source paths
|
||||||
|
std_module_sources: dict[str, Path] = {}
|
||||||
|
for modules_item in module_manifest_json['modules']:
|
||||||
|
# Ignore modules we don't need (probably just std.compat)
|
||||||
|
module_name = modules_item['logical-name']
|
||||||
|
if module_name not in module_names:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# The source path is usually relative to the module manifest file, convert to absolute path
|
||||||
|
module_src_path = Path(modules_item['source-path'])
|
||||||
|
if not module_src_path.is_absolute():
|
||||||
|
module_src_path = module_manifest_base_dir / module_src_path
|
||||||
|
if not module_src_path.is_file():
|
||||||
|
raise Exception(f'Source path for module "{module_name}" is not a file: {module_src_path}')
|
||||||
|
|
||||||
|
# Resolve symlinks and normalize path
|
||||||
|
std_module_sources[module_name] = module_src_path.resolve()
|
||||||
|
|
||||||
|
return std_module_sources
|
||||||
|
|
||||||
|
def get_module_manifest_path(self) -> Path:
|
||||||
|
# Use compiler to get the path to the module manifest JSON file
|
||||||
|
clang_cmd = [self.cpp_compiler, '-print-library-module-manifest-path']
|
||||||
|
clang_cmd_result = subprocess.run(clang_cmd, capture_output=True)
|
||||||
|
|
||||||
|
if clang_cmd_result.returncode != 0:
|
||||||
|
cmd_stderr = clang_cmd_result.stderr.decode().strip()
|
||||||
|
raise Exception(f'Subprocess "{" ".join(clang_cmd)}" failed. stderr:\n{cmd_stderr}')
|
||||||
|
|
||||||
|
# Read path from subprocess stdout
|
||||||
|
module_manifest_path = Path(clang_cmd_result.stdout.decode().strip())
|
||||||
|
|
||||||
|
if not module_manifest_path.is_file():
|
||||||
|
raise Exception(f'Could not locate stdlib module manifest: {module_manifest_path}')
|
||||||
|
|
||||||
|
# Resolve symlinks and normalize path
|
||||||
|
return module_manifest_path.resolve()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
ShurikenCli().run()
|
ShurikenCli().run()
|
||||||
|
|
|
||||||
30
vendor/README.md
vendored
Normal file
30
vendor/README.md
vendored
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
# Vendor dependencies
|
||||||
|
|
||||||
|
TODO: This should be automated with shuriken at some point. For now, manual instructions.
|
||||||
|
|
||||||
|
|
||||||
|
## System dependencies
|
||||||
|
|
||||||
|
For now, SDL3 and SDL3_image need to be installed on the system. They should be vendored at some point.
|
||||||
|
|
||||||
|
Same for freetype, which is required by RmlUi.
|
||||||
|
|
||||||
|
|
||||||
|
## RmlUi
|
||||||
|
|
||||||
|
[RmlUi](https://github.com/mikke89/RmlUi) is a UI library.
|
||||||
|
|
||||||
|
Within the `vendor` directory, run:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# Either clone git repository or download and unpack archive (version 6.2)
|
||||||
|
git clone git@github.com:mikke89/RmlUi.git RmlUi --branch 6.2
|
||||||
|
cd RmlUi
|
||||||
|
|
||||||
|
# Configure and build with cmake (use samples or standalone preset)
|
||||||
|
cmake -G Ninja -B Build --preset samples -DBUILD_SHARED_LIBS=false -DRMLUI_BACKEND=SDL_SDLrenderer
|
||||||
|
cmake --build Build
|
||||||
|
|
||||||
|
# Optionally test by running a sample
|
||||||
|
./Build/rmlui_sample_demo
|
||||||
|
```
|
||||||
Loading…
Add table
Add a link
Reference in a new issue