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);