module; #include export module app.sdl; import app; import core; import core.image; import core.sequence; import core.math; struct bundled_file_store : public app::file_store { void read_file(app::path const & file_path, core::callable const & then) override { // Path is guaranteed to never be greater than 512 characters long (file_path is max 256 and prefix is 2). core::stack path_buffer{&core::null_allocator()}; if (path_buffer.append("./").has_value()) core::unreachable(); // File path is guaranteed to be null-terminated. if (path_buffer.append(file_path.as_slice()).has_value()) core::unreachable(); SDL_RWops * rw_ops = ::SDL_RWFromFile(path_buffer.as_slice().pointer, "r"); if (rw_ops == nullptr) return; then([rw_ops](core::slice const & buffer) -> size_t { return ::SDL_RWread(rw_ops, buffer.pointer, sizeof(uint8_t), buffer.length); }); ::SDL_RWclose(rw_ops); } }; struct sdl_allocator : public core::allocator { core::u8 * reallocate(core::u8 * maybe_allocation, core::usize requested_size) override { return reinterpret_cast(::SDL_malloc(requested_size)); } void deallocate(void * allocation) override { ::SDL_free(allocation); } }; struct sdl_system : public app::system { private: ::SDL_Event sdl_event; sdl_allocator allocator; bundled_file_store bundled_store; public: sdl_system() : sdl_event{0}, allocator{} {} bool poll() override { while (::SDL_PollEvent(&this->sdl_event) != 0) { switch (this->sdl_event.type) { case SDL_QUIT: return false; } } return true; } app::file_store & bundle() override { return this->bundled_store; } void log(app::log_level level, core::slice const & message) override { core::i32 const length = static_cast( core::min(message.length, static_cast(core::i32_max))); ::SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO, "%.*s", length, message.pointer); } core::allocator & thread_safe_allocator() override { return this->allocator; } }; struct sdl_graphics : public app::graphics { static constexpr core::usize title_maximum = 128; core::u32 title_length; char title_buffer[title_maximum]; ::SDL_Window * sdl_window = nullptr; ::SDL_Renderer * sdl_renderer = nullptr; sdl_graphics(core::slice const & title) { this->retitle(title); } void render(canvas & source_canvas) override { if (this->sdl_renderer != nullptr) { SDL_SetRenderDrawColor(this->sdl_renderer, source_canvas.background_color.to_r8(), source_canvas.background_color.to_g8(), source_canvas.background_color.to_b8(), source_canvas.background_color.to_a8()); SDL_RenderClear(this->sdl_renderer); } } void retitle(core::slice const & title) override { this->title_length = core::min(title.length, title_maximum - 1); for (core::usize i = 0; i < this->title_length; i += 1) title_buffer[i] = title[i]; for (core::usize i = this->title_length; i < title_maximum; i += 1) title_buffer[i] = 0; if (this->sdl_window != nullptr) ::SDL_SetWindowTitle(this->sdl_window, title_buffer); } app::graphics::show_error show(core::u16 physical_width, core::u16 physical_height) override { if (this->sdl_window == nullptr) { constexpr int sdl_windowpos = SDL_WINDOWPOS_UNDEFINED; constexpr core::u32 sdl_windowflags = 0; this->sdl_window = ::SDL_CreateWindow(title_buffer, sdl_windowpos, sdl_windowpos, static_cast(physical_width), static_cast(physical_height), sdl_windowflags); if (this->sdl_window == nullptr) return show_error::out_of_memory; } else { ::SDL_ShowWindow(this->sdl_window); } if (this->sdl_renderer == nullptr) { constexpr core::u32 sdl_rendererflags = 0; this->sdl_renderer = ::SDL_CreateRenderer(this->sdl_window, -1, sdl_rendererflags); if (this->sdl_renderer == nullptr) return show_error::out_of_memory; } return show_error::none; } void present() override { if (this->sdl_renderer != nullptr) { ::SDL_RenderPresent(this->sdl_renderer); } } }; export namespace app { int display(core::slice const & title, core::callable const & run) { sdl_system system; sdl_graphics graphics(title); return run(system, graphics); } }