diff --git a/src/ruisapp/glue/linux/glue_wayland.cxx b/src/ruisapp/glue/linux/glue_wayland.cxx
index 21a0c08..2f541a4 100644
--- a/src/ruisapp/glue/linux/glue_wayland.cxx
+++ b/src/ruisapp/glue/linux/glue_wayland.cxx
@@ -2390,7 +2390,7 @@ int main(int argc, const char** argv)
// sequence:
// - update updateables
// - render
- // - wait for events/next cycle
+ // - wait for events and handle them/next cycle
auto to_wait_ms = app.gui.update();
render(app);
diff --git a/src/ruisapp/glue/linux/glue_xorg.cxx b/src/ruisapp/glue/linux/glue_xorg.cxx
index 24e5580..fa364aa 100644
--- a/src/ruisapp/glue/linux/glue_xorg.cxx
+++ b/src/ruisapp/glue/linux/glue_xorg.cxx
@@ -1342,7 +1342,7 @@ int main(int argc, const char** argv)
// sequence:
// - update updateables
// - render
- // - wait for events/next cycle
+ // - wait for events and handle them/next cycle
auto to_wait_ms = app->gui.update();
render(*app);
wait_set.wait(to_wait_ms);
@@ -1387,8 +1387,6 @@ int main(int argc, const char** argv)
render(*app);
break;
case ConfigureNotify:
- // TRACE(<<
- //"ConfigureNotify X event got" << std::endl)
// squash all window resize events into one, for that store the new
// window dimensions and update the viewport later only once
new_win_dims.x() = ruis::real(event.xconfigure.width);
diff --git a/src/ruisapp/glue/macosx/glue.mm b/src/ruisapp/glue/macosx/glue.mm
index 784e5a8..80ae1b2 100644
--- a/src/ruisapp/glue/macosx/glue.mm
+++ b/src/ruisapp/glue/macosx/glue.mm
@@ -732,7 +732,7 @@ int main(int argc, const char** argv){
// sequence:
// - update updateables
// - render
- // - wait for events/next cycle
+ // - wait for events and handle them/next cycle
uint32_t millis = ruisapp::inst().gui.update();
render(ruisapp::inst());
NSEvent *event = [ww.applicationObjectId
diff --git a/src/ruisapp/glue/sdl/glue.cxx b/src/ruisapp/glue/sdl/glue.cxx
index 8c6e93f..0a15282 100644
--- a/src/ruisapp/glue/sdl/glue.cxx
+++ b/src/ruisapp/glue/sdl/glue.cxx
@@ -18,3 +18,405 @@ along with this program. If not, see .
*/
/* ================ LICENSE END ================ */
+
+#include
+
+#include
+
+#include "../../application.hpp"
+
+#if CFG_COMPILER == CFG_COMPILER_MSVC
+# include
+#else
+# include
+#endif
+
+#ifdef RUISAPP_RENDER_OPENGL
+# include
+# include
+#elif defined(RUISAPP_RENDER_OPENGLES)
+# include
+# include
+#else
+# error "Unknown graphics API"
+#endif
+
+#include "../friend_accessors.cxx" // NOLINT(bugprone-suspicious-include)
+
+using namespace std::string_view_literals;
+
+using namespace ruisapp;
+
+namespace {
+class window_wrapper : public utki::destructable
+{
+ class sdl_wrapper
+ {
+ public:
+ sdl_wrapper()
+ {
+ if (SDL_Init(SDL_INIT_VIDEO) < 0) {
+ throw std::runtime_error(utki::cat("Could not initialize SDL, SDL_Error: ", SDL_GetError()));
+ }
+ }
+
+ ~sdl_wrapper()
+ {
+ SDL_Quit();
+ }
+ } sdl;
+
+public:
+ class sdl_window_wrapper
+ {
+ public:
+ SDL_Window* const window;
+
+ sdl_window_wrapper(const window_params& wp) :
+ window([&]() {
+#ifdef RUISAPP_RENDER_OPENGL
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
+#elif defined(RUISAPP_RENDER_OPENGLES)
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
+#else
+# error "Unknown graphics API"
+#endif
+ {
+ auto ver = wp.graphics_api_version;
+ if (ver.major == 0 && ver.minor == 0) {
+ // default OpenGL version is 2.0
+ // TODO: set default version for non-OpenGL APIs
+ ver.major = 2;
+ }
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, ver.major);
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, ver.minor);
+ }
+
+ SDL_Window* window = SDL_CreateWindow(
+ "SDL Tutorial",
+ SDL_WINDOWPOS_UNDEFINED,
+ SDL_WINDOWPOS_UNDEFINED,
+ wp.dims.x(),
+ wp.dims.y(),
+ SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE
+ );
+ if (!window) {
+ std::runtime_error(utki::cat("Could not create SDL window, SDL_Error: ", SDL_GetError()));
+ }
+ return window;
+ }())
+ {}
+
+ ~sdl_window_wrapper()
+ {
+ SDL_DestroyWindow(this->window);
+ }
+ } window;
+
+ class gl_context_wrapper
+ {
+ SDL_GLContext context;
+
+ public:
+ gl_context_wrapper(sdl_window_wrapper& sdl_window) :
+ context([&]() {
+ SDL_GLContext c = SDL_GL_CreateContext(sdl_window.window);
+ if (!c) {
+ throw std::runtime_error(utki::cat("Could not create OpenGL context, SDL Error: ", SDL_GetError()));
+ }
+ return c;
+ }())
+ {}
+
+ ~gl_context_wrapper()
+ {
+ SDL_GL_DeleteContext(this->context);
+ }
+ } gl_context;
+
+ Uint32 user_event_type;
+
+ std::atomic_bool quit_flag = false;
+
+ window_wrapper(const window_params& wp) :
+ window(wp),
+ gl_context(this->window),
+ user_event_type([]() {
+ Uint32 t = SDL_RegisterEvents(1);
+ if (t == (Uint32)(-1)) {
+ throw std::runtime_error(
+ utki::cat("Could not create SDL user event type, SDL Error: ", SDL_GetError())
+ );
+ }
+ return t;
+ }())
+ {
+#ifdef RUISAPP_RENDER_OPENGL
+ if (glewInit() != GLEW_OK) {
+ throw std::runtime_error("Could not initialize GLEW");
+ }
+#endif
+ SDL_StartTextInput();
+ }
+
+ ~window_wrapper()
+ {
+ SDL_StopTextInput();
+ }
+};
+} // namespace
+
+namespace {
+ruisapp::application::directories get_application_directories(std::string_view app_name)
+{
+ ruisapp::application::directories dirs;
+
+ // TODO:
+ dirs.cache = utki::cat(".cache/"sv, app_name);
+ dirs.config = utki::cat(".config/"sv, app_name);
+ dirs.state = utki::cat(".local/state/"sv, app_name);
+
+ // std::cout << "cache dir = " << dirs.cache << std::endl;
+ // std::cout << "config dir = " << dirs.config << std::endl;
+ // std::cout << "state dir = " << dirs.state << std::endl;
+
+ return dirs;
+}
+} // namespace
+
+namespace {
+window_wrapper& get_impl(const std::unique_ptr& pimpl)
+{
+ ASSERT(dynamic_cast(pimpl.get()))
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast)
+ return static_cast(*pimpl);
+}
+
+window_wrapper& get_impl(application& app)
+{
+ return get_impl(get_window_pimpl(app));
+}
+} // namespace
+
+namespace {
+ruis::mouse_button button_number_to_enum(Uint8 number)
+{
+ switch (number) {
+ case SDL_BUTTON_LEFT:
+ return ruis::mouse_button::left;
+ case SDL_BUTTON_X1:
+ // TODO:
+ case SDL_BUTTON_X2:
+ // TODO:
+ default:
+ case SDL_BUTTON_MIDDLE:
+ return ruis::mouse_button::middle;
+ case SDL_BUTTON_RIGHT:
+ return ruis::mouse_button::right;
+ }
+}
+} // namespace
+
+application::application(std::string name, const window_params& wp) :
+ name(std::move(name)),
+ window_pimpl(std::make_unique(wp)),
+ gui(utki::make_shared(
+#ifdef RUISAPP_RENDER_OPENGL
+ utki::make_shared(),
+#elif defined(RUISAPP_RENDER_OPENGLES)
+ utki::make_shared(),
+#else
+# error "Unknown graphics API"
+#endif
+ utki::make_shared(),
+ [this](std::function procedure) {
+ auto& ww = get_impl(*this);
+
+ SDL_Event e;
+ SDL_memset(&e, 0, sizeof(e));
+ e.type = ww.user_event_type;
+ e.user.code = 0;
+ e.user.data1 = new std::function(std::move(procedure));
+ e.user.data2 = 0;
+ SDL_PushEvent(&e);
+ },
+ [this](ruis::mouse_cursor c) {
+ // TODO:
+ // auto& ww = get_impl(*this);
+ // ww.set_cursor(c);
+ },
+ // TODO:
+ 96, // get_impl(window_pimpl).get_dots_per_inch(),
+ 1 // get_impl(window_pimpl).get_dots_per_pp()
+ )),
+ directory(get_application_directories(this->name))
+{
+#ifdef RUISAPP_RASPBERRYPI
+ this->set_fullscreen(true);
+#else
+ this->update_window_rect(ruis::rect(0, 0, ruis::real(wp.dims.x()), ruis::real(wp.dims.y())));
+#endif
+}
+
+void application::quit() noexcept
+{
+ auto& ww = get_impl(this->window_pimpl);
+
+ ww.quit_flag.store(true);
+}
+
+void application::swap_frame_buffers()
+{
+ auto& ww = get_impl(this->window_pimpl);
+
+ SDL_GL_SwapWindow(ww.window.window);
+}
+
+void application::set_fullscreen(bool enable)
+{
+ // TODO:
+}
+
+void application::set_mouse_cursor_visible(bool visible)
+{
+ // TODO:
+}
+
+int main(int argc, const char** argv)
+{
+ std::unique_ptr app = ruisapp::application_factory::create_application(argc, argv);
+ if (!app) {
+ return 1;
+ }
+
+ ASSERT(app)
+
+ auto& ww = get_impl(*app);
+
+ while (!ww.quit_flag.load()) {
+ // sequence:
+ // - update updateables
+ // - render
+ // - wait for events and handle them/next cycle
+
+ auto to_wait_ms = app->gui.update();
+
+ render(*app);
+
+ if (SDL_WaitEventTimeout(nullptr, to_wait_ms) == 0) {
+ // No events or error. In case of error not much we can do, just ignore it.
+ continue;
+ }
+
+ ruis::vector2 new_win_dims(-1, -1);
+
+ SDL_Event e;
+ while (SDL_PollEvent(&e) != 0) {
+ switch (e.type) {
+ case SDL_QUIT:
+ ww.quit_flag.store(true);
+ break;
+ case SDL_WINDOWEVENT:
+ switch (e.window.event) {
+ default:
+ break;
+ case SDL_WINDOWEVENT_RESIZED:
+ case SDL_WINDOWEVENT_SIZE_CHANGED:
+ // squash all window resize events into one, for that store the new
+ // window dimensions and update the viewport later only once
+ new_win_dims.x() = ruis::real(e.window.data1);
+ new_win_dims.y() = ruis::real(e.window.data2);
+ break;
+ case SDL_WINDOWEVENT_ENTER:
+ handle_mouse_hover(*app, true, 0);
+ break;
+ case SDL_WINDOWEVENT_LEAVE:
+ handle_mouse_hover(*app, false, 0);
+ break;
+ }
+ break;
+ case SDL_MOUSEMOTION:
+ {
+ int x = 0;
+ int y = 0;
+ SDL_GetMouseState(&x, &y);
+
+ handle_mouse_move(*app, ruis::vector2(x, y), 0);
+ }
+ break;
+ case SDL_MOUSEBUTTONDOWN:
+ [[fallthrough]];
+ case SDL_MOUSEBUTTONUP:
+ {
+ int x = 0;
+ int y = 0;
+ SDL_GetMouseState(&x, &y);
+
+ handle_mouse_button(
+ *app,
+ e.button.type == SDL_MOUSEBUTTONDOWN,
+ ruis::vector2(x, y),
+ button_number_to_enum(e.button.button),
+ 0 // pointer id
+ );
+ }
+ break;
+ case SDL_KEYDOWN:
+ [[fallthrough]];
+ case SDL_KEYUP:
+ // if (e.key.repeat == 0) {
+ // gui.send_key(e.key.type == SDL_KEYDOWN, sdl_scan_code_to_ruis_key(e.key.keysym.scancode));
+ // }
+ // if (e.type == SDL_KEYDOWN) {
+ // struct SDLUnicodeDummyProvider : public ruis::gui::input_string_provider {
+ // std::u32string get() const override
+ // {
+ // return std::u32string();
+ // }
+ // };
+
+ // gui.send_character_input(
+ // SDLUnicodeDummyProvider(),
+ // sdl_scan_code_to_ruis_key(e.key.keysym.scancode)
+ // );
+ // }
+ break;
+ case SDL_TEXTINPUT:
+ // {
+ // struct SDLUnicodeProvider : public ruis::gui::input_string_provider {
+ // const char* text;
+
+ // SDLUnicodeProvider(const char* text) :
+ // text(text)
+ // {}
+
+ // std::u32string get() const override
+ // {
+ // return utki::to_utf32(this->text);
+ // }
+ // } sdlUnicodeProvider(
+ // // save pointer to text, the ownership of text buffer is not taken!
+ // e.text.text
+ // );
+
+ // gui.send_character_input(sdlUnicodeProvider, ruis::key::unknown);
+ // }
+ break;
+ default:
+ if (e.type == ww.user_event_type) {
+ std::unique_ptr> f(
+ reinterpret_cast*>(e.user.data1)
+ );
+ f->operator()();
+ }
+ break;
+ }
+ }
+
+ if (new_win_dims.is_positive_or_zero()) {
+ update_window_rect(*app, ruis::rect(0, new_win_dims));
+ }
+ }
+
+ return 0;
+}
diff --git a/src/ruisapp/glue/windows/glue.cxx b/src/ruisapp/glue/windows/glue.cxx
index f1a6fec..373c43a 100644
--- a/src/ruisapp/glue/windows/glue.cxx
+++ b/src/ruisapp/glue/windows/glue.cxx
@@ -742,7 +742,7 @@ void winmain(int argc, const char** argv)
// sequence:
// - update updateables
// - render
- // - wait for events/next cycle
+ // - wait for events and handle them/next cycle
uint32_t timeout = app->gui.update();
render(*app);
DWORD status = MsgWaitForMultipleObjectsEx(0, nullptr, timeout, QS_ALLINPUT, MWMO_INPUTAVAILABLE);