aboutsummaryrefslogtreecommitdiff
path: root/src/crepe/facade
diff options
context:
space:
mode:
Diffstat (limited to 'src/crepe/facade')
-rw-r--r--src/crepe/facade/CMakeLists.txt20
-rw-r--r--src/crepe/facade/DB.cpp61
-rw-r--r--src/crepe/facade/DB.h75
-rw-r--r--src/crepe/facade/EventData.h54
-rw-r--r--src/crepe/facade/Font.cpp21
-rw-r--r--src/crepe/facade/Font.h42
-rw-r--r--src/crepe/facade/FontFacade.cpp43
-rw-r--r--src/crepe/facade/FontFacade.h34
-rw-r--r--src/crepe/facade/SDLContext.cpp419
-rw-r--r--src/crepe/facade/SDLContext.h365
-rw-r--r--src/crepe/facade/Sound.cpp13
-rw-r--r--src/crepe/facade/Sound.h31
-rw-r--r--src/crepe/facade/SoundContext.cpp36
-rw-r--r--src/crepe/facade/SoundContext.h81
-rw-r--r--src/crepe/facade/SoundHandle.h12
-rw-r--r--src/crepe/facade/Texture.cpp29
-rw-r--r--src/crepe/facade/Texture.h69
17 files changed, 1405 insertions, 0 deletions
diff --git a/src/crepe/facade/CMakeLists.txt b/src/crepe/facade/CMakeLists.txt
new file mode 100644
index 0000000..243ae46
--- /dev/null
+++ b/src/crepe/facade/CMakeLists.txt
@@ -0,0 +1,20 @@
+target_sources(crepe PUBLIC
+ Sound.cpp
+ Texture.cpp
+ SoundContext.cpp
+ SDLContext.cpp
+ DB.cpp
+ FontFacade.cpp
+ Font.cpp
+)
+
+target_sources(crepe PUBLIC FILE_SET HEADERS FILES
+ Sound.h
+ Texture.h
+ SoundContext.h
+ SDLContext.h
+ DB.h
+ FontFacade.h
+ Font.h
+)
+
diff --git a/src/crepe/facade/DB.cpp b/src/crepe/facade/DB.cpp
new file mode 100644
index 0000000..7a3e473
--- /dev/null
+++ b/src/crepe/facade/DB.cpp
@@ -0,0 +1,61 @@
+#include <cstring>
+
+#include "util/dbg.h"
+
+#include "DB.h"
+
+using namespace std;
+using namespace crepe;
+
+DB::DB(const string & path) {
+ dbg_trace();
+ int ret;
+
+ // init database struct
+ libdb::DB * db;
+ if ((ret = libdb::db_create(&db, NULL, 0)) != 0)
+ throw runtime_error(format("db_create: {}", libdb::db_strerror(ret)));
+ this->db = {db, [](libdb::DB * db) { db->close(db, 0); }};
+
+ // load or create database file
+ const char * file = path.empty() ? NULL : path.c_str();
+ ret = this->db->open(this->db.get(), NULL, file, NULL, libdb::DB_BTREE, DB_CREATE, 0);
+ if (ret != 0) throw runtime_error(format("db->open: {}", libdb::db_strerror(ret)));
+}
+
+libdb::DBT DB::to_thing(const string & thing) const noexcept {
+ libdb::DBT thang;
+ memset(&thang, 0, sizeof(libdb::DBT));
+ thang.data = (void *) thing.data();
+ thang.size = thing.size();
+ return thang;
+}
+
+string DB::get(const string & key) {
+ libdb::DBT db_key = this->to_thing(key);
+ libdb::DBT db_val;
+ memset(&db_val, 0, sizeof(libdb::DBT));
+
+ int ret = this->db->get(this->db.get(), NULL, &db_key, &db_val, 0);
+ if (ret == 0) return {static_cast<char *>(db_val.data), db_val.size};
+
+ string err = format("db->get: {}", libdb::db_strerror(ret));
+ if (ret == DB_NOTFOUND) throw out_of_range(err);
+ else throw runtime_error(err);
+}
+
+void DB::set(const string & key, const string & value) {
+ libdb::DBT db_key = this->to_thing(key);
+ libdb::DBT db_val = this->to_thing(value);
+ int ret = this->db->put(this->db.get(), NULL, &db_key, &db_val, 0);
+ if (ret != 0) throw runtime_error(format("db->get: {}", libdb::db_strerror(ret)));
+}
+
+bool DB::has(const std::string & key) {
+ try {
+ this->get(key);
+ } catch (std::out_of_range &) {
+ return false;
+ }
+ return true;
+}
diff --git a/src/crepe/facade/DB.h b/src/crepe/facade/DB.h
new file mode 100644
index 0000000..84cdf19
--- /dev/null
+++ b/src/crepe/facade/DB.h
@@ -0,0 +1,75 @@
+#pragma once
+
+#include <functional>
+#include <memory>
+#include <string>
+
+namespace libdb {
+extern "C" {
+#include <db.h>
+}
+} // namespace libdb
+
+namespace crepe {
+
+/**
+ * \brief Berkeley DB facade
+ *
+ * Berkeley DB is a simple key-value database that stores arbitrary data as both key and value.
+ * This facade uses STL strings as keys/values.
+ */
+class DB {
+public:
+ /**
+ * \param path The path of the database (created if nonexistant)
+ *
+ * \note If \p path is empty, the database is entirely in-memory
+ */
+ DB(const std::string & path = "");
+ virtual ~DB() = default;
+
+public:
+ /**
+ * \brief Get a value from the database, or throw an exception
+ *
+ * \param key The value key
+ *
+ * \return The value
+ *
+ * \throws std::out_of_range if value is not found in DB
+ * \throws std::runtime_error if other error occurs
+ */
+ std::string get(const std::string & key);
+ /**
+ * \brief Set (create or overwrite) a value in the database
+ *
+ * \param key The value key
+ * \param value The value to store
+ *
+ * \throws std::runtime_error if an error occurs
+ */
+ void set(const std::string & key, const std::string & value);
+ /**
+ * \brief Check if a key exists in the database
+ *
+ * \param key The value key
+ *
+ * \returns True if the key exists, or false if it does not
+ */
+ bool has(const std::string & key);
+
+private:
+ //! RAII wrapper around \c DB struct
+ std::unique_ptr<libdb::DB, std::function<void(libdb::DB *)>> db;
+
+private:
+ /**
+ * \brief Convert an STL string to DBT (data base thang)
+ *
+ * \param thing Input data
+ * \return \c DBT with the same data as input \c thing
+ */
+ libdb::DBT to_thing(const std::string & thing) const noexcept;
+};
+
+} // namespace crepe
diff --git a/src/crepe/facade/EventData.h b/src/crepe/facade/EventData.h
new file mode 100644
index 0000000..a7526b4
--- /dev/null
+++ b/src/crepe/facade/EventData.h
@@ -0,0 +1,54 @@
+#pragma once
+#include "../api/KeyCodes.h"
+#include "../types.h"
+namespace crepe {
+//! EventType enum for passing eventType
+enum EventType {
+ NONE = 0,
+ MOUSE_DOWN,
+ MOUSE_UP,
+ MOUSE_MOVE,
+ MOUSE_WHEEL,
+ KEY_UP,
+ KEY_DOWN,
+ SHUTDOWN,
+ WINDOW_MINIMIZE,
+ WINDOW_MAXIMIZE,
+ WINDOW_FOCUS_GAIN,
+ WINDOW_FOCUS_LOST,
+ WINDOW_MOVE,
+ WINDOW_RESIZE,
+ WINDOW_EXPOSE,
+};
+
+//! Struct for storing key data.
+struct KeyData {
+ Keycode key = Keycode::NONE;
+ bool key_repeat = false;
+};
+
+//! Struct for storing mouse data.
+struct MouseData {
+ MouseButton mouse_button = MouseButton::NONE;
+ ivec2 mouse_position = {-1, -1};
+ int scroll_direction = -1;
+ float scroll_delta = INFINITY;
+ ivec2 rel_mouse_move = {-1, -1};
+};
+
+//! Struct for storing window data.
+struct WindowData {
+ ivec2 move_delta;
+ ivec2 resize_dimension;
+};
+
+//! EventData struct for passing event data from facade
+struct EventData {
+ EventType event_type = EventType::NONE;
+ union {
+ KeyData key_data;
+ MouseData mouse_data;
+ WindowData window_data;
+ } data;
+};
+} // namespace crepe
diff --git a/src/crepe/facade/Font.cpp b/src/crepe/facade/Font.cpp
new file mode 100644
index 0000000..771002f
--- /dev/null
+++ b/src/crepe/facade/Font.cpp
@@ -0,0 +1,21 @@
+#include <SDL2/SDL_ttf.h>
+
+#include "../api/Asset.h"
+#include "../api/Config.h"
+
+#include "Font.h"
+
+using namespace std;
+using namespace crepe;
+
+Font::Font(const Asset & src, Mediator & mediator) : Resource(src, mediator) {
+ const Config & config = Config::get_instance();
+ const std::string FONT_PATH = src.get_path();
+ TTF_Font * loaded_font = TTF_OpenFont(FONT_PATH.c_str(), config.font.size);
+ if (loaded_font == NULL) {
+ throw runtime_error(format("Font: {} (path: {})", TTF_GetError(), FONT_PATH));
+ }
+ this->font = {loaded_font, [](TTF_Font * close_font) { TTF_CloseFont(close_font); }};
+}
+
+TTF_Font * Font::get_font() const { return this->font.get(); }
diff --git a/src/crepe/facade/Font.h b/src/crepe/facade/Font.h
new file mode 100644
index 0000000..b208d96
--- /dev/null
+++ b/src/crepe/facade/Font.h
@@ -0,0 +1,42 @@
+#pragma once
+
+#include <SDL2/SDL_ttf.h>
+#include <memory>
+
+#include "../Resource.h"
+#include "../api/Config.h"
+
+namespace crepe {
+
+class Asset;
+/**
+ * \brief Resource for managing font creation and destruction
+ *
+ * This class is a wrapper around an SDL_ttf font instance, encapsulating font loading and usage.
+ * It loads a font from an Asset and manages its lifecycle. The font is automatically unloaded
+ * when this object is destroyed.
+ */
+class Font : public Resource {
+
+public:
+ /**
+ * \param src The Asset containing the font file path and metadata to load the font.
+ * \param mediator The Mediator object used for managing the SDL context or related systems.
+ */
+ Font(const Asset & src, Mediator & mediator);
+ /**
+ * \brief Gets the underlying TTF_Font resource.
+ *
+ * This function returns the raw pointer to the SDL_ttf TTF_Font object that represents
+ * the loaded font. This can be used with SDL_ttf functions to render text.
+ *
+ * \return The raw TTF_Font object wrapped in a unique pointer.
+ */
+ TTF_Font * get_font() const;
+
+private:
+ //! The SDL_ttf font object with custom deleter.
+ std::unique_ptr<TTF_Font, std::function<void(TTF_Font *)>> font = nullptr;
+};
+
+} // namespace crepe
diff --git a/src/crepe/facade/FontFacade.cpp b/src/crepe/facade/FontFacade.cpp
new file mode 100644
index 0000000..87f95ab
--- /dev/null
+++ b/src/crepe/facade/FontFacade.cpp
@@ -0,0 +1,43 @@
+#include <fontconfig/fontconfig.h>
+#include <functional>
+#include <memory>
+#include <stdexcept>
+#include <string>
+
+#include "FontFacade.h"
+
+using namespace std;
+using namespace crepe;
+
+FontFacade::FontFacade() {
+ if (!FcInit()) throw runtime_error("Failed to initialize Fontconfig.");
+}
+
+FontFacade::~FontFacade() { FcFini(); }
+
+Asset FontFacade::get_font_asset(const string & font_family) {
+ FcPattern * raw_pattern
+ = FcNameParse(reinterpret_cast<const FcChar8 *>(font_family.c_str()));
+ if (raw_pattern == NULL) throw runtime_error("Failed to create font pattern.");
+
+ unique_ptr<FcPattern, function<void(FcPattern *)>> pattern{
+ raw_pattern, [](FcPattern * p) { FcPatternDestroy(p); }};
+
+ FcConfig * config = FcConfigGetCurrent();
+ if (config == NULL) throw runtime_error("Failed to get current Fontconfig configuration.");
+
+ FcResult result;
+ FcPattern * raw_matched_pattern = FcFontMatch(config, pattern.get(), &result);
+ if (raw_matched_pattern == NULL) throw runtime_error("No matching font found.");
+
+ unique_ptr<FcPattern, function<void(FcPattern *)>> matched_pattern
+ = {raw_matched_pattern, [](FcPattern * p) { FcPatternDestroy(p); }};
+
+ FcChar8 * file_path = nullptr;
+ FcResult res = FcPatternGetString(matched_pattern.get(), FC_FILE, 0, &file_path);
+ if (res != FcResultMatch || file_path == NULL)
+ throw runtime_error("Failed to get font file path.");
+
+ string font_file_path = reinterpret_cast<const char *>(file_path);
+ return Asset(font_file_path);
+}
diff --git a/src/crepe/facade/FontFacade.h b/src/crepe/facade/FontFacade.h
new file mode 100644
index 0000000..9761070
--- /dev/null
+++ b/src/crepe/facade/FontFacade.h
@@ -0,0 +1,34 @@
+#pragma once
+
+#include <memory>
+
+#include "../api/Asset.h"
+
+namespace crepe {
+
+/**
+ *
+ * \brief Font facade class for converting font family names to absolute file paths
+ *
+ */
+class FontFacade {
+public:
+ FontFacade();
+ ~FontFacade();
+ FontFacade(const FontFacade & other) = delete;
+ FontFacade & operator=(const FontFacade & other) = delete;
+ FontFacade(FontFacade && other) noexcept = delete;
+ FontFacade & operator=(FontFacade && other) noexcept = delete;
+ /**
+ *
+ * \brief Facade function to convert a font_family into an asset.
+ *
+ * This function uses the FontConfig library to convert a font family name (Arial, Inter, Helvetica) and converts it to the font source path.
+ * This function returns a default font path if the font_family name doesnt exist or cant be found
+ * \param font_family Name of the font family name.
+ * \return Asset with filepath to the corresponding font.
+ */
+ Asset get_font_asset(const std::string & font_family);
+};
+
+} // namespace crepe
diff --git a/src/crepe/facade/SDLContext.cpp b/src/crepe/facade/SDLContext.cpp
new file mode 100644
index 0000000..9486ccd
--- /dev/null
+++ b/src/crepe/facade/SDLContext.cpp
@@ -0,0 +1,419 @@
+#include <SDL2/SDL.h>
+#include <SDL2/SDL_blendmode.h>
+#include <SDL2/SDL_image.h>
+#include <SDL2/SDL_keycode.h>
+#include <SDL2/SDL_pixels.h>
+#include <SDL2/SDL_rect.h>
+#include <SDL2/SDL_render.h>
+#include <SDL2/SDL_surface.h>
+#include <SDL2/SDL_ttf.h>
+#include <SDL2/SDL_video.h>
+#include <array>
+#include <cmath>
+#include <cstddef>
+#include <cstdint>
+#include <functional>
+#include <memory>
+#include <stdexcept>
+
+#include "../api/Camera.h"
+#include "../api/Color.h"
+#include "../api/Config.h"
+#include "../api/Sprite.h"
+#include "../util/dbg.h"
+#include "manager/Mediator.h"
+
+#include "SDLContext.h"
+#include "Texture.h"
+#include "types.h"
+
+using namespace crepe;
+using namespace std;
+
+SDLContext::SDLContext(Mediator & mediator) {
+ dbg_trace();
+ if (TTF_Init() == -1) {
+ throw runtime_error(format("SDL_ttf initialization failed: {}", TTF_GetError()));
+ }
+ if (SDL_Init(SDL_INIT_VIDEO) != 0) {
+ throw runtime_error(format("SDLContext: SDL_Init error: {}", SDL_GetError()));
+ }
+
+ auto & cfg = Config::get_instance().window_settings;
+ SDL_Window * tmp_window
+ = SDL_CreateWindow(cfg.window_title.c_str(), SDL_WINDOWPOS_CENTERED,
+ SDL_WINDOWPOS_CENTERED, cfg.default_size.x, cfg.default_size.y, 0);
+ if (!tmp_window) {
+ throw runtime_error(format("SDLContext: SDL_Window error: {}", SDL_GetError()));
+ }
+ this->game_window = {tmp_window, [](SDL_Window * window) { SDL_DestroyWindow(window); }};
+
+ SDL_Renderer * tmp_renderer
+ = SDL_CreateRenderer(this->game_window.get(), -1, SDL_RENDERER_ACCELERATED);
+ if (!tmp_renderer) {
+ throw runtime_error(
+ format("SDLContext: SDL_CreateRenderer error: {}", SDL_GetError()));
+ }
+
+ this->game_renderer
+ = {tmp_renderer, [](SDL_Renderer * renderer) { SDL_DestroyRenderer(renderer); }};
+
+ int img_flags = IMG_INIT_PNG;
+ if (!(IMG_Init(img_flags) & img_flags)) {
+ throw runtime_error("SDLContext: SDL_image could not initialize!");
+ }
+
+ mediator.sdl_context = *this;
+}
+
+SDLContext::~SDLContext() {
+ dbg_trace();
+
+ this->game_renderer.reset();
+ this->game_window.reset();
+
+ // TODO: how are we going to ensure that these are called from the same
+ // thread that SDL_Init() was called on? This has caused problems for me
+ // before.
+ IMG_Quit();
+ TTF_Quit();
+ SDL_Quit();
+}
+
+Keycode SDLContext::sdl_to_keycode(SDL_Scancode sdl_key) {
+ if (!lookup_table.contains(sdl_key)) return Keycode::NONE;
+ return lookup_table.at(sdl_key);
+}
+
+const keyboard_state_t & SDLContext::get_keyboard_state() {
+ SDL_PumpEvents();
+ const Uint8 * current_state = SDL_GetKeyboardState(nullptr);
+
+ for (int i = 0; i < SDL_NUM_SCANCODES; ++i) {
+
+ Keycode key = sdl_to_keycode(static_cast<SDL_Scancode>(i));
+ if (key != Keycode::NONE) {
+ this->keyboard_state[key] = current_state[i] != 0;
+ }
+ }
+ return this->keyboard_state;
+}
+
+MouseButton SDLContext::sdl_to_mousebutton(Uint8 sdl_button) {
+ static const std::array<MouseButton, 5> MOUSE_BUTTON_LOOKUP_TABLE = [] {
+ std::array<MouseButton, 5> table{};
+ table.fill(MouseButton::NONE);
+
+ table[SDL_BUTTON_LEFT] = MouseButton::LEFT_MOUSE;
+ table[SDL_BUTTON_RIGHT] = MouseButton::RIGHT_MOUSE;
+ table[SDL_BUTTON_MIDDLE] = MouseButton::MIDDLE_MOUSE;
+ table[SDL_BUTTON_X1] = MouseButton::X1_MOUSE;
+ table[SDL_BUTTON_X2] = MouseButton::X2_MOUSE;
+
+ return table;
+ }();
+
+ if (sdl_button >= MOUSE_BUTTON_LOOKUP_TABLE.size()) {
+ // Return NONE for invalid or unmapped button
+ return MouseButton::NONE;
+ }
+
+ return MOUSE_BUTTON_LOOKUP_TABLE[sdl_button];
+}
+
+void SDLContext::clear_screen() { SDL_RenderClear(this->game_renderer.get()); }
+void SDLContext::present_screen() {
+ SDL_SetRenderDrawColor(this->game_renderer.get(), 0, 0, 0, 255);
+ SDL_RenderFillRectF(this->game_renderer.get(), &black_bars[0]);
+ SDL_RenderFillRectF(this->game_renderer.get(), &black_bars[1]);
+ SDL_RenderPresent(this->game_renderer.get());
+}
+
+SDL_FRect SDLContext::get_dst_rect(const DestinationRectangleData & ctx) const {
+
+ const Sprite::Data & data = ctx.sprite.data;
+
+ float aspect_ratio
+ = (ctx.sprite.aspect_ratio == 0) ? ctx.texture.get_ratio() : ctx.sprite.aspect_ratio;
+
+ vec2 size = data.size;
+ if (data.size.x == 0 && data.size.y != 0) {
+ size.x = data.size.y * aspect_ratio;
+ }
+ if (data.size.y == 0 && data.size.x != 0) {
+ size.y = data.size.x / aspect_ratio;
+ }
+ size *= cam_aux_data.render_scale * ctx.img_scale * data.scale_offset;
+
+ vec2 screen_pos = (ctx.pos + data.position_offset - cam_aux_data.cam_pos
+ + (cam_aux_data.zoomed_viewport) / 2)
+ * cam_aux_data.render_scale
+ - size / 2 + cam_aux_data.bar_size;
+
+ return SDL_FRect{
+ .x = screen_pos.x,
+ .y = screen_pos.y,
+ .w = size.x,
+ .h = size.y,
+ };
+}
+
+void SDLContext::draw(const RenderContext & ctx) {
+ const Sprite::Data & data = ctx.sprite.data;
+ SDL_RendererFlip render_flip
+ = (SDL_RendererFlip) ((SDL_FLIP_HORIZONTAL * data.flip.flip_x)
+ | (SDL_FLIP_VERTICAL * data.flip.flip_y));
+
+ SDL_Rect srcrect;
+ SDL_Rect * srcrect_ptr = NULL;
+ if (ctx.sprite.mask.w != 0 || ctx.sprite.mask.h != 0) {
+ srcrect.w = ctx.sprite.mask.w;
+ srcrect.h = ctx.sprite.mask.h;
+ srcrect.x = ctx.sprite.mask.x;
+ srcrect.y = ctx.sprite.mask.y;
+ srcrect_ptr = &srcrect;
+ }
+
+ SDL_FRect dstrect = this->get_dst_rect(SDLContext::DestinationRectangleData{
+ .sprite = ctx.sprite,
+ .texture = ctx.texture,
+ .pos = ctx.pos,
+ .img_scale = ctx.scale,
+ });
+
+ double angle = ctx.angle + data.angle_offset;
+
+ this->set_color_texture(ctx.texture, ctx.sprite.data.color);
+ SDL_RenderCopyExF(this->game_renderer.get(), ctx.texture.get_img(), srcrect_ptr, &dstrect,
+ angle, NULL, render_flip);
+}
+
+void SDLContext::update_camera_view(const Camera & cam, const vec2 & new_pos) {
+
+ const Camera::Data & cam_data = cam.data;
+ // resize window
+ int w, h;
+ SDL_GetWindowSize(this->game_window.get(), &w, &h);
+ if (w != cam.screen.x || h != cam.screen.y) {
+ SDL_SetWindowSize(this->game_window.get(), cam.screen.x, cam.screen.y);
+ }
+
+ vec2 & zoomed_viewport = this->cam_aux_data.zoomed_viewport;
+ vec2 & bar_size = this->cam_aux_data.bar_size;
+ vec2 & render_scale = this->cam_aux_data.render_scale;
+ this->cam_aux_data.cam_pos = new_pos;
+
+ zoomed_viewport = cam.viewport_size * cam_data.zoom;
+ float screen_aspect = static_cast<float>(cam.screen.x) / cam.screen.y;
+ float viewport_aspect = zoomed_viewport.x / zoomed_viewport.y;
+
+ // calculate black bars
+ if (screen_aspect > viewport_aspect) {
+ // pillarboxing
+ float scale = cam.screen.y / zoomed_viewport.y;
+ float adj_width = zoomed_viewport.x * scale;
+ float bar_width = (cam.screen.x - adj_width) / 2;
+ this->black_bars[0] = {0, 0, bar_width, (float) cam.screen.y};
+ this->black_bars[1] = {(cam.screen.x - bar_width), 0, bar_width, (float) cam.screen.y};
+
+ bar_size = {bar_width, 0};
+ render_scale.x = render_scale.y = scale;
+ } else {
+ // letterboxing
+ float scale = cam.screen.x / (cam.viewport_size.x * cam_data.zoom);
+ float adj_height = cam.viewport_size.y * scale;
+ float bar_height = (cam.screen.y - adj_height) / 2;
+ this->black_bars[0] = {0, 0, (float) cam.screen.x, bar_height};
+ this->black_bars[1]
+ = {0, (cam.screen.y - bar_height), (float) cam.screen.x, bar_height};
+
+ bar_size = {0, bar_height};
+ render_scale.x = render_scale.y = scale;
+ }
+
+ SDL_SetRenderDrawColor(this->game_renderer.get(), cam_data.bg_color.r, cam_data.bg_color.g,
+ cam_data.bg_color.b, cam_data.bg_color.a);
+
+ SDL_Rect bg = {
+ .x = 0,
+ .y = 0,
+ .w = cam.screen.x,
+ .h = cam.screen.y,
+ };
+
+ // fill bg color
+ SDL_RenderFillRect(this->game_renderer.get(), &bg);
+}
+
+std::unique_ptr<SDL_Texture, std::function<void(SDL_Texture *)>>
+SDLContext::texture_from_path(const std::string & path) {
+
+ SDL_Surface * tmp = IMG_Load(path.c_str());
+ if (tmp == nullptr)
+ throw runtime_error(format("SDLContext: IMG_Load error: {}", SDL_GetError()));
+
+ std::unique_ptr<SDL_Surface, std::function<void(SDL_Surface *)>> img_surface;
+ img_surface = {tmp, [](SDL_Surface * surface) { SDL_FreeSurface(surface); }};
+
+ SDL_Texture * tmp_texture
+ = SDL_CreateTextureFromSurface(this->game_renderer.get(), img_surface.get());
+
+ if (tmp_texture == nullptr) {
+ throw runtime_error(format("SDLContext: Texture cannot be load from {}", path));
+ }
+
+ std::unique_ptr<SDL_Texture, std::function<void(SDL_Texture *)>> img_texture;
+ img_texture = {tmp_texture, [](SDL_Texture * texture) { SDL_DestroyTexture(texture); }};
+
+ SDL_SetTextureBlendMode(img_texture.get(), SDL_BLENDMODE_BLEND);
+ return img_texture;
+}
+
+ivec2 SDLContext::get_size(const Texture & ctx) {
+ ivec2 size;
+ SDL_QueryTexture(ctx.get_img(), NULL, NULL, &size.x, &size.y);
+ return size;
+}
+
+std::vector<EventData> SDLContext::get_events() {
+ std::vector<EventData> event_list;
+ SDL_Event event;
+ const CameraAuxiliaryData & cam = this->cam_aux_data;
+ while (SDL_PollEvent(&event)) {
+ ivec2 mouse_pos;
+ mouse_pos.x = (event.button.x - cam.bar_size.x) / cam.render_scale.x;
+ mouse_pos.y = (event.button.y - cam.bar_size.y) / cam.render_scale.y;
+ switch (event.type) {
+ case SDL_QUIT:
+ event_list.push_back({.event_type = EventType::SHUTDOWN});
+ break;
+ case SDL_KEYDOWN:
+ event_list.push_back(EventData{
+ .event_type = EventType::KEY_DOWN,
+ .data = {
+ .key_data = {
+ .key = this->sdl_to_keycode(event.key.keysym.scancode),
+ .key_repeat = event.key.repeat != 0,
+ },
+ },
+ });
+ break;
+
+ case SDL_KEYUP:
+ event_list.push_back(EventData{
+ .event_type = EventType::KEY_UP,
+ .data = {
+ .key_data = {
+ .key = this->sdl_to_keycode(event.key.keysym.scancode),
+ .key_repeat = event.key.repeat != 0,
+ },
+ },
+ });
+ break;
+
+ case SDL_MOUSEBUTTONDOWN:
+ event_list.push_back(EventData{
+ .event_type = EventType::MOUSE_DOWN,
+ .data = {
+ .mouse_data = {
+ .mouse_button = this->sdl_to_mousebutton(event.button.button),
+ .mouse_position = mouse_pos,
+ },
+ },
+ });
+ break;
+ case SDL_MOUSEBUTTONUP:
+ event_list.push_back(EventData{
+ .event_type = EventType::MOUSE_UP,
+ .data = {
+ .mouse_data = {
+ .mouse_button = this->sdl_to_mousebutton(event.button.button),
+ .mouse_position = mouse_pos,
+ },
+ },
+ });
+ break;
+
+ case SDL_MOUSEMOTION:
+ event_list.push_back(EventData{
+ .event_type = EventType::MOUSE_MOVE,
+ .data = {
+ .mouse_data = {
+ .mouse_position = mouse_pos,
+ .rel_mouse_move = {event.motion.xrel, event.motion.yrel},
+ },
+ },
+ });
+ break;
+
+ case SDL_MOUSEWHEEL:
+ event_list.push_back(EventData{
+ .event_type = EventType::MOUSE_WHEEL,
+ .data = {
+ .mouse_data = {
+ .mouse_position = mouse_pos,
+ .scroll_direction = event.wheel.y < 0 ? -1 : 1,
+ .scroll_delta = event.wheel.preciseY,
+ },
+ },
+ });
+ break;
+ case SDL_WINDOWEVENT:
+ this->handle_window_event(event.window, event_list);
+ break;
+ }
+ }
+
+ return event_list;
+}
+
+void SDLContext::handle_window_event(const SDL_WindowEvent & window_event,
+ std::vector<EventData> & event_list) {
+ switch (window_event.event) {
+ case SDL_WINDOWEVENT_EXPOSED:
+ event_list.push_back(EventData{EventType::WINDOW_EXPOSE});
+ break;
+ case SDL_WINDOWEVENT_RESIZED:
+ event_list.push_back(EventData{
+ .event_type = EventType::WINDOW_RESIZE,
+ .data = {
+ .window_data = {
+ .resize_dimension = {window_event.data1, window_event.data2}
+ },
+ },
+ });
+ break;
+ case SDL_WINDOWEVENT_MOVED:
+ event_list.push_back(EventData{
+ .event_type = EventType::WINDOW_MOVE,
+ .data = {
+ .window_data = {
+ .move_delta = {window_event.data1, window_event.data2}
+ },
+ },
+ });
+ break;
+
+ case SDL_WINDOWEVENT_MINIMIZED:
+ event_list.push_back(EventData{EventType::WINDOW_MINIMIZE});
+ break;
+ case SDL_WINDOWEVENT_MAXIMIZED:
+ event_list.push_back(EventData{EventType::WINDOW_MAXIMIZE});
+ break;
+ case SDL_WINDOWEVENT_FOCUS_GAINED:
+ event_list.push_back(EventData{EventType::WINDOW_FOCUS_GAIN});
+ break;
+ case SDL_WINDOWEVENT_FOCUS_LOST:
+ event_list.push_back(EventData{EventType::WINDOW_FOCUS_LOST});
+ break;
+ }
+}
+
+void SDLContext::set_color_texture(const Texture & texture, const Color & color) {
+ SDL_SetTextureColorMod(texture.get_img(), color.r, color.g, color.b);
+ SDL_SetTextureAlphaMod(texture.get_img(), color.a);
+}
+
+Asset SDLContext::get_font_from_name(const std::string & font_family) {
+ return this->font_facade.get_font_asset(font_family);
+}
diff --git a/src/crepe/facade/SDLContext.h b/src/crepe/facade/SDLContext.h
new file mode 100644
index 0000000..b687f87
--- /dev/null
+++ b/src/crepe/facade/SDLContext.h
@@ -0,0 +1,365 @@
+#pragma once
+
+#include <SDL2/SDL.h>
+#include <SDL2/SDL_keycode.h>
+#include <SDL2/SDL_rect.h>
+#include <SDL2/SDL_render.h>
+#include <SDL2/SDL_video.h>
+#include <cmath>
+#include <functional>
+#include <memory>
+#include <string>
+#include <unordered_map>
+
+#include "../types.h"
+#include "api/Camera.h"
+#include "api/Color.h"
+#include "api/KeyCodes.h"
+#include "api/Sprite.h"
+#include "api/Transform.h"
+#include "types.h"
+
+#include "EventData.h"
+#include "FontFacade.h"
+
+namespace crepe {
+class Texture;
+class Mediator;
+
+/**
+ * \brief Facade for the SDL library
+ *
+ * SDLContext is a singleton that handles the SDL window and renderer, provides methods for
+ * event handling, and rendering to the screen. It is never used directly by the user
+ */
+class SDLContext {
+public:
+ //! data that the camera component cannot hold
+ struct CameraAuxiliaryData {
+
+ //! zoomed in viewport in game_units
+ vec2 zoomed_viewport;
+
+ /**
+ * \brief scaling factor
+ *
+ * depending on the black bars type will the scaling be different.
+ * - letterboxing --> scaling on the y-as
+ * - pillarboxing --> scaling on the x-as
+ */
+ vec2 render_scale;
+
+ /**
+ * \brief size of calculated black bars
+ *
+ * depending on the black bars type will the size be different
+ * - lettorboxing --> {0, bar_height}
+ * - pillarboxing --> {bar_width , 0}
+ */
+ vec2 bar_size;
+
+ //! Calculated camera position
+ vec2 cam_pos;
+ };
+
+ //! rendering data needed to render on screen
+ struct RenderContext {
+ const Sprite & sprite;
+ const Texture & texture;
+ const vec2 & pos;
+ const double & angle;
+ const double & scale;
+ };
+
+public:
+ /**
+ * \brief Gets the singleton instance of SDLContext.
+ * \return Reference to the SDLContext instance.
+ */
+ static SDLContext & get_instance();
+
+public:
+ SDLContext(const SDLContext &) = delete;
+ SDLContext(SDLContext &&) = delete;
+ SDLContext & operator=(const SDLContext &) = delete;
+ SDLContext & operator=(SDLContext &&) = delete;
+
+public:
+ /**
+ * \brief Constructs an SDLContext instance.
+ * Initializes SDL, creates a window and renderer.
+ */
+ SDLContext(Mediator & mediator);
+
+ /**
+ * \brief Destroys the SDLContext instance.
+ * Cleans up SDL resources, including the window and renderer.
+ */
+ ~SDLContext();
+
+public:
+ /**
+ * \brief Retrieves a list of all events from the SDL context.
+ *
+ * This method retrieves all the events from the SDL context that are currently
+ * available. It is primarily used by the InputSystem to process various
+ * input events such as mouse clicks, mouse movements, and keyboard presses.
+ *
+ * \return Events that occurred since last call to `get_events()`
+ */
+ std::vector<EventData> get_events();
+ /**
+ * \brief Fills event_list with triggered window events
+ *
+ * This method checks if any window events are triggered and adds them to the event_list.
+ *
+ */
+ void handle_window_event(const SDL_WindowEvent & window_event,
+ std::vector<EventData> & event_list);
+ /**
+ * \brief Converts an SDL scan code to the custom Keycode type.
+ *
+ * This method maps an SDL scan code to the corresponding `Keycode` enum value,
+ * which is used internally by the system to identify the keys.
+ *
+ * \param sdl_key The SDL scan code to convert.
+ * \return The corresponding `Keycode` value or `Keycode::NONE` if the key is unrecognized.
+ */
+ Keycode sdl_to_keycode(SDL_Scancode sdl_key);
+
+ /**
+ * \brief Converts an SDL mouse button code to the custom MouseButton type.
+ *
+ * This method maps an SDL mouse button code to the corresponding `MouseButton`
+ * enum value, which is used internally by the system to identify mouse buttons.
+ *
+ * \param sdl_button The SDL mouse button code to convert.
+ * \return The corresponding `MouseButton` value or `MouseButton::NONE` if the key is unrecognized
+ */
+ MouseButton sdl_to_mousebutton(Uint8 sdl_button);
+ /**
+ * \brief Gets the current state of the keyboard.
+ *
+ * Updates the internal keyboard state by checking the current key states using
+ * SDL's `SDL_GetKeyboardState()`, and returns a reference to the `keyboard_state_t`.
+ *
+ * \return A constant reference to the `keyboard_state_t`, which holds the state
+ * of each key (true = pressed, false = not pressed).
+ */
+ const keyboard_state_t & get_keyboard_state();
+
+public:
+ /**
+ * \brief Gets the current SDL ticks since the program started.
+ * \return Current ticks in milliseconds as a constant uint64_t.
+ */
+ uint64_t get_ticks() const;
+ /**
+ * \brief Pauses the execution for a specified duration.
+ *
+ * This function uses SDL's delay function to halt the program execution for a given number
+ * of milliseconds, allowing for frame rate control or other timing-related functionality.
+ *
+ * \param ms Duration of the delay in milliseconds.
+ */
+ void delay(int ms) const;
+
+public:
+ /**
+ * \brief Loads a texture from a file path.
+ * \param path Path to the image file.
+ * \return Pointer to the created SDL_Texture.
+ */
+ std::unique_ptr<SDL_Texture, std::function<void(SDL_Texture *)>>
+ texture_from_path(const std::string & path);
+ /**
+ * \brief Gets the size of a texture.
+ * \param texture Reference to the Texture object.
+ * \return Width and height of the texture as an integer in pixels.
+ */
+ ivec2 get_size(const Texture & ctx);
+
+public:
+ /**
+ * \brief Draws a sprite to the screen using the specified transform and camera.
+ * \param RenderContext Reference to rendering data to draw
+ */
+ void draw(const RenderContext & ctx);
+
+ //! Clears the screen, preparing for a new frame.
+ void clear_screen();
+
+ //! Presents the rendered frame to the screen.
+ void present_screen();
+
+ /**
+ * \brief calculates camera view settings. such as black_bars, zoomed_viewport, scaling and
+ * adjusting window size.
+ *
+ * \note only supports windowed mode.
+ * \param camera Reference to the current Camera object in the scene.
+ * \param new_pos new camera position from transform and offset
+ */
+ void update_camera_view(const Camera & camera, const vec2 & new_pos);
+
+public:
+ //! the data needed to construct a sdl dst rectangle
+ struct DestinationRectangleData {
+ const Sprite & sprite;
+ const Texture & texture;
+ const vec2 & pos;
+ const double & img_scale;
+ };
+
+ /**
+ * \brief calculates the sqaure size of the image for destination
+ *
+ * \param data needed to calculate a destination rectangle
+ * \return sdl rectangle to draw a dst image to draw on the screen
+ */
+ SDL_FRect get_dst_rect(const DestinationRectangleData & data) const;
+ /**
+ * \brief Set an additional color value multiplied into render copy operations.
+ *
+ * \param texture the given texture to adjust
+ * \param color the color data for the texture
+ */
+ void set_color_texture(const Texture & texture, const Color & color);
+
+private:
+ //! sdl Window
+ std::unique_ptr<SDL_Window, std::function<void(SDL_Window *)>> game_window;
+
+ //! renderer for the crepe engine
+ std::unique_ptr<SDL_Renderer, std::function<void(SDL_Renderer *)>> game_renderer;
+
+ //! black bars rectangle to draw
+ SDL_FRect black_bars[2] = {};
+
+ /**
+ * \cam_aux_data extra data that the component cannot hold.
+ *
+ * - this is defined in this class because get_events() needs this information aswell
+ */
+ CameraAuxiliaryData cam_aux_data;
+
+private:
+ //! instance of the font_facade
+ FontFacade font_facade{};
+
+public:
+ /**
+ * \brief Function to Get asset from font_family
+ *
+ * This function uses the FontFacade function to convert a font_family to an asset.
+ *
+ * \param font_family name of the font style that needs to be used (will return an asset with default font path of the font_family doesnt exist)
+ *
+ * \return asset with the font style absolute path
+ */
+ Asset get_font_from_name(const std::string & font_family);
+ //! variable to store the state of each key (true = pressed, false = not pressed)
+ keyboard_state_t keyboard_state;
+ //! lookup table for converting SDL_SCANCODES to Keycodes
+ const std::unordered_map<SDL_Scancode, Keycode> lookup_table
+ = {{SDL_SCANCODE_SPACE, Keycode::SPACE},
+ {SDL_SCANCODE_APOSTROPHE, Keycode::APOSTROPHE},
+ {SDL_SCANCODE_COMMA, Keycode::COMMA},
+ {SDL_SCANCODE_MINUS, Keycode::MINUS},
+ {SDL_SCANCODE_PERIOD, Keycode::PERIOD},
+ {SDL_SCANCODE_SLASH, Keycode::SLASH},
+ {SDL_SCANCODE_0, Keycode::D0},
+ {SDL_SCANCODE_1, Keycode::D1},
+ {SDL_SCANCODE_2, Keycode::D2},
+ {SDL_SCANCODE_3, Keycode::D3},
+ {SDL_SCANCODE_4, Keycode::D4},
+ {SDL_SCANCODE_5, Keycode::D5},
+ {SDL_SCANCODE_6, Keycode::D6},
+ {SDL_SCANCODE_7, Keycode::D7},
+ {SDL_SCANCODE_8, Keycode::D8},
+ {SDL_SCANCODE_9, Keycode::D9},
+ {SDL_SCANCODE_SEMICOLON, Keycode::SEMICOLON},
+ {SDL_SCANCODE_EQUALS, Keycode::EQUAL},
+ {SDL_SCANCODE_A, Keycode::A},
+ {SDL_SCANCODE_B, Keycode::B},
+ {SDL_SCANCODE_C, Keycode::C},
+ {SDL_SCANCODE_D, Keycode::D},
+ {SDL_SCANCODE_E, Keycode::E},
+ {SDL_SCANCODE_F, Keycode::F},
+ {SDL_SCANCODE_G, Keycode::G},
+ {SDL_SCANCODE_H, Keycode::H},
+ {SDL_SCANCODE_I, Keycode::I},
+ {SDL_SCANCODE_J, Keycode::J},
+ {SDL_SCANCODE_K, Keycode::K},
+ {SDL_SCANCODE_L, Keycode::L},
+ {SDL_SCANCODE_M, Keycode::M},
+ {SDL_SCANCODE_N, Keycode::N},
+ {SDL_SCANCODE_O, Keycode::O},
+ {SDL_SCANCODE_P, Keycode::P},
+ {SDL_SCANCODE_Q, Keycode::Q},
+ {SDL_SCANCODE_R, Keycode::R},
+ {SDL_SCANCODE_S, Keycode::S},
+ {SDL_SCANCODE_T, Keycode::T},
+ {SDL_SCANCODE_U, Keycode::U},
+ {SDL_SCANCODE_V, Keycode::V},
+ {SDL_SCANCODE_W, Keycode::W},
+ {SDL_SCANCODE_X, Keycode::X},
+ {SDL_SCANCODE_Y, Keycode::Y},
+ {SDL_SCANCODE_Z, Keycode::Z},
+ {SDL_SCANCODE_LEFTBRACKET, Keycode::LEFT_BRACKET},
+ {SDL_SCANCODE_BACKSLASH, Keycode::BACKSLASH},
+ {SDL_SCANCODE_RIGHTBRACKET, Keycode::RIGHT_BRACKET},
+ {SDL_SCANCODE_GRAVE, Keycode::GRAVE_ACCENT},
+ {SDL_SCANCODE_ESCAPE, Keycode::ESCAPE},
+ {SDL_SCANCODE_RETURN, Keycode::ENTER},
+ {SDL_SCANCODE_TAB, Keycode::TAB},
+ {SDL_SCANCODE_BACKSPACE, Keycode::BACKSPACE},
+ {SDL_SCANCODE_INSERT, Keycode::INSERT},
+ {SDL_SCANCODE_DELETE, Keycode::DELETE},
+ {SDL_SCANCODE_RIGHT, Keycode::RIGHT},
+ {SDL_SCANCODE_LEFT, Keycode::LEFT},
+ {SDL_SCANCODE_DOWN, Keycode::DOWN},
+ {SDL_SCANCODE_UP, Keycode::UP},
+ {SDL_SCANCODE_PAGEUP, Keycode::PAGE_UP},
+ {SDL_SCANCODE_PAGEDOWN, Keycode::PAGE_DOWN},
+ {SDL_SCANCODE_HOME, Keycode::HOME},
+ {SDL_SCANCODE_END, Keycode::END},
+ {SDL_SCANCODE_CAPSLOCK, Keycode::CAPS_LOCK},
+ {SDL_SCANCODE_SCROLLLOCK, Keycode::SCROLL_LOCK},
+ {SDL_SCANCODE_NUMLOCKCLEAR, Keycode::NUM_LOCK},
+ {SDL_SCANCODE_PRINTSCREEN, Keycode::PRINT_SCREEN},
+ {SDL_SCANCODE_PAUSE, Keycode::PAUSE},
+ {SDL_SCANCODE_F1, Keycode::F1},
+ {SDL_SCANCODE_F2, Keycode::F2},
+ {SDL_SCANCODE_F3, Keycode::F3},
+ {SDL_SCANCODE_F4, Keycode::F4},
+ {SDL_SCANCODE_F5, Keycode::F5},
+ {SDL_SCANCODE_F6, Keycode::F6},
+ {SDL_SCANCODE_F7, Keycode::F7},
+ {SDL_SCANCODE_F8, Keycode::F8},
+ {SDL_SCANCODE_F9, Keycode::F9},
+ {SDL_SCANCODE_F10, Keycode::F10},
+ {SDL_SCANCODE_F11, Keycode::F11},
+ {SDL_SCANCODE_F12, Keycode::F12},
+ {SDL_SCANCODE_KP_0, Keycode::KP0},
+ {SDL_SCANCODE_KP_1, Keycode::KP1},
+ {SDL_SCANCODE_KP_2, Keycode::KP2},
+ {SDL_SCANCODE_KP_3, Keycode::KP3},
+ {SDL_SCANCODE_KP_4, Keycode::KP4},
+ {SDL_SCANCODE_KP_5, Keycode::KP5},
+ {SDL_SCANCODE_KP_6, Keycode::KP6},
+ {SDL_SCANCODE_KP_7, Keycode::KP7},
+ {SDL_SCANCODE_KP_8, Keycode::KP8},
+ {SDL_SCANCODE_KP_9, Keycode::KP9},
+ {SDL_SCANCODE_LSHIFT, Keycode::LEFT_SHIFT},
+ {SDL_SCANCODE_LCTRL, Keycode::LEFT_CONTROL},
+ {SDL_SCANCODE_LALT, Keycode::LEFT_ALT},
+ {SDL_SCANCODE_LGUI, Keycode::LEFT_SUPER},
+ {SDL_SCANCODE_RSHIFT, Keycode::RIGHT_SHIFT},
+ {SDL_SCANCODE_RCTRL, Keycode::RIGHT_CONTROL},
+ {SDL_SCANCODE_RALT, Keycode::RIGHT_ALT},
+ {SDL_SCANCODE_RGUI, Keycode::RIGHT_SUPER},
+ {SDL_SCANCODE_MENU, Keycode::MENU}};
+};
+
+} // namespace crepe
diff --git a/src/crepe/facade/Sound.cpp b/src/crepe/facade/Sound.cpp
new file mode 100644
index 0000000..b1e6463
--- /dev/null
+++ b/src/crepe/facade/Sound.cpp
@@ -0,0 +1,13 @@
+#include "../api/Asset.h"
+#include "../util/dbg.h"
+
+#include "Sound.h"
+
+using namespace crepe;
+using namespace std;
+
+Sound::Sound(const Asset & src, Mediator & mediator) : Resource(src, mediator) {
+ this->sample.load(src.get_path().c_str());
+ dbg_trace();
+}
+Sound::~Sound() { dbg_trace(); }
diff --git a/src/crepe/facade/Sound.h b/src/crepe/facade/Sound.h
new file mode 100644
index 0000000..4a5d692
--- /dev/null
+++ b/src/crepe/facade/Sound.h
@@ -0,0 +1,31 @@
+#pragma once
+
+#include <soloud/soloud.h>
+#include <soloud/soloud_wav.h>
+
+#include "../Resource.h"
+
+namespace crepe {
+
+class SoundContext;
+class Mediator;
+
+/**
+ * \brief Sound resource facade
+ *
+ * This class is a wrapper around a \c SoLoud::Wav instance, which holds a single sample. It is
+ * part of the sound facade.
+ */
+class Sound : public Resource {
+public:
+ Sound(const Asset & src, Mediator & mediator);
+ ~Sound(); // dbg_trace
+
+private:
+ //! Deserialized resource (soloud)
+ SoLoud::Wav sample;
+ //! SoundContext uses \c sample
+ friend class SoundContext;
+};
+
+} // namespace crepe
diff --git a/src/crepe/facade/SoundContext.cpp b/src/crepe/facade/SoundContext.cpp
new file mode 100644
index 0000000..5091e07
--- /dev/null
+++ b/src/crepe/facade/SoundContext.cpp
@@ -0,0 +1,36 @@
+#include "../util/dbg.h"
+
+#include "SoundContext.h"
+
+using namespace crepe;
+
+SoundContext::SoundContext() {
+ dbg_trace();
+ this->engine.init();
+ this->engine.setMaxActiveVoiceCount(this->config.audio.voices);
+}
+
+SoundContext::~SoundContext() {
+ dbg_trace();
+ this->engine.deinit();
+}
+
+SoundHandle SoundContext::play(Sound & resource) {
+ SoLoud::handle real_handle = this->engine.play(resource.sample, 1.0f);
+ SoundHandle handle = this->next_handle;
+ this->registry[handle] = real_handle;
+ this->next_handle++;
+ return handle;
+}
+
+void SoundContext::stop(const SoundHandle & handle) {
+ this->engine.stop(this->registry[handle]);
+}
+
+void SoundContext::set_volume(const SoundHandle & handle, float volume) {
+ this->engine.setVolume(this->registry[handle], volume);
+}
+
+void SoundContext::set_loop(const SoundHandle & handle, bool loop) {
+ this->engine.setLooping(this->registry[handle], loop);
+}
diff --git a/src/crepe/facade/SoundContext.h b/src/crepe/facade/SoundContext.h
new file mode 100644
index 0000000..d986c59
--- /dev/null
+++ b/src/crepe/facade/SoundContext.h
@@ -0,0 +1,81 @@
+#pragma once
+
+#include <soloud/soloud.h>
+
+#include "../api/Config.h"
+
+#include "Sound.h"
+#include "SoundHandle.h"
+
+namespace crepe {
+
+/**
+ * \brief Sound engine facade
+ *
+ * This class is a wrapper around a \c SoLoud::Soloud instance, which provides the methods for
+ * playing \c Sound instances. It is part of the sound facade.
+ */
+class SoundContext {
+public:
+ SoundContext();
+ virtual ~SoundContext();
+
+ SoundContext(const SoundContext &) = delete;
+ SoundContext(SoundContext &&) = delete;
+ SoundContext & operator=(const SoundContext &) = delete;
+ SoundContext & operator=(SoundContext &&) = delete;
+
+ /**
+ * \brief Play a sample
+ *
+ * Plays a Sound from the beginning of the sample and returns a handle to control it later.
+ *
+ * \param resource Sound instance to play
+ *
+ * \returns Handle to control this voice
+ */
+ virtual SoundHandle play(Sound & resource);
+ /**
+ * \brief Stop a voice immediately if it is still playing
+ *
+ * \note This function does nothing if the handle is invalid or if the sound is already
+ * stopped / finished playing.
+ *
+ * \param handle Voice handle returned by SoundContext::play
+ */
+ virtual void stop(const SoundHandle & handle);
+ /**
+ * \brief Change the volume of a voice
+ *
+ * \note This function does nothing if the handle is invalid or if the sound is already
+ * stopped / finished playing.
+ *
+ * \param handle Voice handle returned by SoundContext::play
+ * \param volume New gain value (0=silent, 1=default)
+ */
+ virtual void set_volume(const SoundHandle & handle, float volume);
+ /**
+ * \brief Set the looping behavior of a voice
+ *
+ * \note This function does nothing if the handle is invalid or if the sound is already
+ * stopped / finished playing.
+ *
+ * \param handle Voice handle returned by SoundContext::play
+ * \param loop Looping behavior (false=oneshot, true=loop)
+ */
+ virtual void set_loop(const SoundHandle & handle, bool loop);
+
+private:
+ //! Abstracted class
+ SoLoud::Soloud engine;
+
+ //! Config reference
+ Config & config = Config::get_instance();
+
+ //! Sound handle registry
+ std::unordered_map<SoundHandle, SoLoud::handle> registry;
+ //! Unique handle counter
+ SoundHandle next_handle = 0;
+};
+
+} // namespace crepe
diff --git a/src/crepe/facade/SoundHandle.h b/src/crepe/facade/SoundHandle.h
new file mode 100644
index 0000000..b7925fc
--- /dev/null
+++ b/src/crepe/facade/SoundHandle.h
@@ -0,0 +1,12 @@
+#pragma once
+
+#include <cstddef>
+
+namespace crepe {
+
+/**
+ * \brief Voice handle returned by
+ */
+typedef size_t SoundHandle;
+
+} // namespace crepe
diff --git a/src/crepe/facade/Texture.cpp b/src/crepe/facade/Texture.cpp
new file mode 100644
index 0000000..6fb22e1
--- /dev/null
+++ b/src/crepe/facade/Texture.cpp
@@ -0,0 +1,29 @@
+#include "../Resource.h"
+#include "../facade/SDLContext.h"
+#include "../manager/Mediator.h"
+#include "../types.h"
+#include "../util/dbg.h"
+
+#include "Texture.h"
+
+using namespace crepe;
+using namespace std;
+
+Texture::Texture(const Asset & src, Mediator & mediator) : Resource(src, mediator) {
+ dbg_trace();
+ SDLContext & ctx = mediator.sdl_context;
+ this->texture = ctx.texture_from_path(src.get_path());
+ this->size = ctx.get_size(*this);
+ this->aspect_ratio = static_cast<float>(this->size.x) / this->size.y;
+}
+
+Texture::~Texture() {
+ dbg_trace();
+ this->texture.reset();
+}
+
+const ivec2 & Texture::get_size() const noexcept { return this->size; }
+
+const float & Texture::get_ratio() const noexcept { return this->aspect_ratio; }
+
+SDL_Texture * Texture::get_img() const noexcept { return this->texture.get(); }
diff --git a/src/crepe/facade/Texture.h b/src/crepe/facade/Texture.h
new file mode 100644
index 0000000..cdacac4
--- /dev/null
+++ b/src/crepe/facade/Texture.h
@@ -0,0 +1,69 @@
+#pragma once
+
+#include <SDL2/SDL_render.h>
+#include <memory>
+
+#include "../Resource.h"
+
+#include "types.h"
+
+namespace crepe {
+
+class Mediator;
+class Asset;
+
+/**
+ * \class Texture
+ * \brief Manages texture loading and properties.
+ *
+ * The Texture class is responsible for loading an image from a source and providing access to
+ * its dimensions. Textures can be used for rendering.
+ */
+class Texture : public Resource {
+
+public:
+ /**
+ * \brief Constructs a Texture from an Asset resource.
+ * \param src Asset with texture data to load.
+ * \param mediator use the SDLContext reference to load the image
+ */
+ Texture(const Asset & src, Mediator & mediator);
+
+ /**
+ * \brief Destroys the Texture instance
+ */
+ ~Texture();
+
+ /**
+ * \brief get width and height of image in pixels
+ * \return pixel size width and height
+ *
+ */
+ const ivec2 & get_size() const noexcept;
+
+ /**
+ * \brief aspect_ratio of image
+ * \return ratio
+ *
+ */
+ const float & get_ratio() const noexcept;
+
+ /**
+ * \brief get the image texture
+ * \return SDL_Texture
+ *
+ */
+ SDL_Texture * get_img() const noexcept;
+
+private:
+ //! The texture of the class from the library
+ std::unique_ptr<SDL_Texture, std::function<void(SDL_Texture *)>> texture;
+
+ // texture size in pixel
+ ivec2 size;
+
+ //! ratio of image
+ float aspect_ratio;
+};
+
+} // namespace crepe