diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7db04aa2..daa01279 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -67,7 +67,7 @@ jobs: - {os: debian, codename: bookworm, image_owner: , package_type: deb, labels: [arm32,docker]} - {os: debian, codename: bookworm, image_owner: , package_type: deb, labels: [arm64,docker]} # - {os: ubuntu, codename: focal, image_owner: , package_type: deb} - - {os: ubuntu, codename: jammy, image_owner: , package_type: deb} + # - {os: ubuntu, codename: jammy, image_owner: , package_type: deb} - {os: ubuntu, codename: noble, image_owner: , package_type: deb} # - {os: raspbian, codename: buster, image_owner: igagis/, package_type: deb, labels: [arm32,docker]} # - {os: raspbian, codename: bullseye, image_owner: igagis/, package_type: deb, labels: [arm32,docker]} diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 4d090b95..d12bb854 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -49,6 +49,13 @@ "problemMatcher": [], "group": "build" }, + { + "label": "run-app-sdl-dbg", + "type": "shell", + "command": "make run-app config=dbg sdl=true", + "problemMatcher": [], + "group": "build" + }, { "label": "run-app-dbg-ogles2", "type": "shell", diff --git a/debian/control.in b/debian/control.in index 12806242..e4995d0b 100644 --- a/debian/control.in +++ b/debian/control.in @@ -21,7 +21,8 @@ Build-Depends: libgles2-mesa-dev, libgtk-3-dev, libwayland-dev, - libxkbcommon-dev + libxkbcommon-dev, + libsdl2-dev Build-Depends-Indep: doxygen Standards-Version: 3.9.5 diff --git a/debian/libruisapp-dev.install b/debian/libruisapp-dev.install index 4ebf6dcf..b457e462 100644 --- a/debian/libruisapp-dev.install +++ b/debian/libruisapp-dev.install @@ -2,4 +2,3 @@ usr/include usr/lib/pkgconfig usr/lib/lib*.so usr/lib/lib*.a - diff --git a/src/makefile b/src/makefile index 69653165..6a42195f 100644 --- a/src/makefile +++ b/src/makefile @@ -55,7 +55,10 @@ define ruisapp_rules this_name := ruisapp-$1$(if $2,-$2) - ifeq ($(os), linux) + ifeq ($2,sdl) + this_ldlibs += -lSDL2 + this_cxxflags += -D RUISAPP_BACKEND_SDL + else ifeq ($(os), linux) # use -isystem instead of -I to prevent clang-tidy follow the includes from these locations this_cxxflags += $$(patsubst -I%,-isystem%,$$(shell pkg-config --cflags gdk-3.0)) this_ldlibs += $$(shell pkg-config --libs gdk-3.0) @@ -69,20 +72,17 @@ define ruisapp_rules this_ldlibs += $(wayland_shell_protocol_lib) this_ldlibs += -lxkbcommon this_cxxflags += -isystem $(dir $(wayland_shell_protocol_header)) - this_cxxflags += -D RUISAPP_WAYLAND - else + this_cxxflags += -D RUISAPP_BACKEND_WAYLAND + else ifeq ($2,xorg) this_ldlibs += -lX11 + else endif else ifeq ($(os), windows) this_ldlibs += -lgdi32 -lopengl32 -lglew32 else ifeq ($(os), macosx) this_ldlibs += -lGLEW this_ldlibs += -framework Cocoa -framework OpenGL -ldl - endif - this_ldlibs += -lruis -lpapki -ltml -lutki - - ifeq ($(os), macosx) this_mm_obj := $$(d)$$(this_out_dir)obj_$$(this_name)/objc/ruisapp/glue/macosx/glue.mm.o define this_subrules @@ -94,6 +94,8 @@ $(.RECIPEPREFIX)$(a)$(this_cc) -ObjC++ -c -o "$$$$@" $$(this_cxxflags) $$(OBJCXX $$(eval $$(this_subrules)) endif + this_ldlibs += -lruis -lpapki -ltml -lutki + this_cxxflags += -D RUISAPP_RENDER_$(shell echo $1 | tr '[:lower:]' '[:upper:]') this_ldlibs += -lruis-render-$1 @@ -103,7 +105,8 @@ $(.RECIPEPREFIX)$(a)$(this_cc) -ObjC++ -c -o "$$$$@" $$(this_cxxflags) $$(OBJCXX $$(eval $$(prorab-build-lib)) - ifeq ($(os), macosx) + ifeq ($2,sdl) + else ifeq ($(os), macosx) $$(prorab_this_staticlib): $$(this_mm_obj) $$(prorab_this_name): $$(this_mm_obj) @@ -120,6 +123,8 @@ ifeq ($(os), linux) $(eval $(call ruisapp_rules,opengles,xorg)) # $(eval $(call ruisapp_rules,opengl,wayland)) $(eval $(call ruisapp_rules,opengles,wayland)) + $(eval $(call ruisapp_rules,opengl,sdl)) + $(eval $(call ruisapp_rules,opengles,sdl)) else $(eval $(call ruisapp_rules,opengl,)) endif diff --git a/src/ruisapp/glue/glue.cpp b/src/ruisapp/glue/glue.cpp index 3a294bb7..dd23b2e8 100644 --- a/src/ruisapp/glue/glue.cpp +++ b/src/ruisapp/glue/glue.cpp @@ -21,13 +21,16 @@ along with this program. If not, see . #include -#if CFG_OS == CFG_OS_WINDOWS +#ifdef RUISAPP_BACKEND_SDL +// NOLINTNEXTLINE(bugprone-suspicious-include) +# include "sdl/glue.cxx" +#elif CFG_OS == CFG_OS_WINDOWS // NOLINTNEXTLINE(bugprone-suspicious-include) # include "windows/glue.cxx" #elif CFG_OS == CFG_OS_LINUX && CFG_OS_NAME == CFG_OS_NAME_ANDROID # include "android/glue.cxx" #elif CFG_OS == CFG_OS_LINUX -# ifdef RUISAPP_WAYLAND +# ifdef RUISAPP_BACKEND_WAYLAND // NOLINTNEXTLINE(bugprone-suspicious-include) # include "linux/glue_wayland.cxx" # else diff --git a/src/ruisapp/glue/linux/glue_wayland.cxx b/src/ruisapp/glue/linux/glue_wayland.cxx index 21a0c082..2f541a44 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 24e55807..fa364aad 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 784e5a84..80ae1b2a 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 new file mode 100644 index 00000000..b813b616 --- /dev/null +++ b/src/ruisapp/glue/sdl/glue.cxx @@ -0,0 +1,755 @@ +/* +ruisapp - ruis GUI adaptation layer + +Copyright (C) 2016-2024 Ivan Gagis + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +/* ================ LICENSE END ================ */ + +#include + +#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 { +const std::array key_map = { + { + ruis::key::unknown, // 0 + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::a, + ruis::key::b, // x5 + ruis::key::c, + ruis::key::d, + ruis::key::e, + ruis::key::f, + ruis::key::g, // 10 + ruis::key::h, + ruis::key::i, + ruis::key::j, + ruis::key::k, + ruis::key::l, // x5 + ruis::key::m, + ruis::key::n, + ruis::key::o, + ruis::key::p, + ruis::key::q, // 20 + ruis::key::r, + ruis::key::s, + ruis::key::t, + ruis::key::u, + ruis::key::v, // x5 + ruis::key::w, + ruis::key::x, + ruis::key::y, + ruis::key::z, + ruis::key::one, // 30 + ruis::key::two, + ruis::key::three, + ruis::key::four, + ruis::key::five, + ruis::key::six, // x5 + ruis::key::seven, + ruis::key::eight, + ruis::key::nine, + ruis::key::zero, + ruis::key::enter, // 40 + ruis::key::escape, + ruis::key::backspace, + ruis::key::tabulator, + ruis::key::space, + ruis::key::minus, // x5 + ruis::key::equals, + ruis::key::left_square_bracket, + ruis::key::right_square_bracket, + ruis::key::backslash, + ruis::key::backslash, // 50 + ruis::key::semicolon, + ruis::key::apostrophe, + ruis::key::grave, + ruis::key::comma, + ruis::key::period, // x5 + ruis::key::slash, + ruis::key::capslock, + ruis::key::f1, + ruis::key::f2, + ruis::key::f3, // 60 + ruis::key::f4, + ruis::key::f5, + ruis::key::f6, + ruis::key::f7, + ruis::key::f8, // x5 + ruis::key::f9, + ruis::key::f10, + ruis::key::f11, + ruis::key::f12, + ruis::key::print_screen, // 70 + ruis::key::unknown, + ruis::key::pause, + ruis::key::insert, + ruis::key::home, + ruis::key::page_up, // x5 + ruis::key::deletion, + ruis::key::end, + ruis::key::page_down, + ruis::key::arrow_right, + ruis::key::arrow_left, // 80 + ruis::key::arrow_down, + ruis::key::arrow_up, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, // x5 + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, // 90 + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, // x5 + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, // 100 + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::f13, + ruis::key::f14, // x5 + ruis::key::f15, + ruis::key::f16, + ruis::key::f17, + ruis::key::f18, + ruis::key::f19, // 110 + ruis::key::f20, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, // x5 + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, // 120 + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, // x5 + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, // 130 + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, // x5 + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, // 140 + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, // x5 + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, // 150 + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, // x5 + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, // 160 + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, // x5 + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, // 170 + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, // x5 + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, // 180 + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, // x5 + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, // 190 + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, // x5 + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, // 200 + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, // x5 + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, // 210 + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, // x5 + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, // 220 + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::left_control, + ruis::key::left_shift, // x5 + ruis::key::left_alt, + ruis::key::unknown, + ruis::key::right_control, + ruis::key::right_shift, + ruis::key::right_alt, // 230 + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, // x5 + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, // 240 + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, // x5 + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, // 250 + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown, + ruis::key::unknown // 255 + } +}; + +ruis::key sdl_scan_code_to_ruis_key(SDL_Scancode sc) +{ + if (size_t(sc) >= key_map.size()) { + return ruis::key::unknown; + } + + return key_map[sc]; +} +} // namespace + +namespace { +ruis::real get_dpi(int display_index = 0) +{ + float dpi = ruis::context::default_dots_per_inch; + if (SDL_GetDisplayDPI(display_index, &dpi, nullptr, nullptr) != 0) { + throw std::runtime_error(utki::cat("Could not get SDL display DPI, SDL Error: ", SDL_GetError())); + } + return ruis::real(dpi); +} + +ruis::real get_display_scaling_factor(int display_index = 0) +{ + using std::round; + return round(get_dpi(display_index) / ruis::context::default_dots_per_inch); +} +} // namespace + +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_wrapper(const sdl_wrapper&) = delete; + sdl_wrapper& operator=(const sdl_wrapper&) = delete; + sdl_wrapper(sdl_wrapper&&) = delete; + sdl_wrapper& operator=(sdl_wrapper&&) = delete; + } 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); + } + + auto dims = wp.dims.to() * get_display_scaling_factor(); + + SDL_Window* window = SDL_CreateWindow( + "TODO: window title", + SDL_WINDOWPOS_UNDEFINED, + SDL_WINDOWPOS_UNDEFINED, + int(dims.x()), + int(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); + } + + sdl_window_wrapper(const sdl_window_wrapper&) = delete; + sdl_window_wrapper& operator=(const sdl_window_wrapper&) = delete; + sdl_window_wrapper(sdl_window_wrapper&&) = delete; + sdl_window_wrapper& operator=(sdl_window_wrapper&&) = delete; + } 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_wrapper(const gl_context_wrapper&) = delete; + gl_context_wrapper& operator=(const gl_context_wrapper&) = delete; + gl_context_wrapper(gl_context_wrapper&&) = delete; + gl_context_wrapper& operator=(gl_context_wrapper&&) = delete; + } 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() override + { + SDL_StopTextInput(); + } + + window_wrapper(const window_wrapper&) = delete; + window_wrapper& operator=(const window_wrapper&) = delete; + window_wrapper(window_wrapper&&) = delete; + window_wrapper& operator=(window_wrapper&&) = delete; +}; +} // namespace + +namespace { +ruisapp::application::directories get_application_directories(std::string_view app_name) +{ + char* base_dir = SDL_GetPrefPath("", std::string(app_name).c_str()); + utki::scope_exit base_dir_scope_exit([&]() { + SDL_free(base_dir); + }); + + ruisapp::application::directories dirs; + + dirs.cache = utki::cat(base_dir, "cache/"sv); + dirs.config = utki::cat(base_dir, "config/"sv); + dirs.state = utki::cat(base_dir, "state/"sv); + + // 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; + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) + e.user.data1 = new std::function(std::move(procedure)); + e.user.data2 = nullptr; + SDL_PushEvent(&e); + }, + [](ruis::mouse_cursor c) { + // TODO: + // auto& ww = get_impl(*this); + // ww.set_cursor(c); + }, + get_dpi(), + get_display_scaling_factor() + )), + directory(get_application_directories(this->name)) +{ + this->update_window_rect( + ruis::rect( + {0, 0}, // + ruis::vec2(ruis::real(wp.dims.x()), ruis::real(wp.dims.y())) * get_display_scaling_factor() + ) + ); +} + +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: +} + +namespace { +void main_loop_iteration(void* user_data) +{ + ASSERT(user_data) + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + auto app = reinterpret_cast(user_data); + + auto& ww = get_impl(*app); + + // loop iteration sequence: + // - update updateables + // - render + // - wait for events and handle them/next cycle + + auto to_wait_ms = app->gui.update(); + // clamp to_wait_ms to max of int as SDL_WaitEventTimeout() accepts int type + to_wait_ms = std::min(to_wait_ms, uint32_t(std::numeric_limits::max())); + + render(*app); + + if (SDL_WaitEventTimeout(nullptr, int(to_wait_ms)) == 0) { + // No events or error. In case of error not much we can do, just ignore it. + return; + } + + 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: + { + auto key = sdl_scan_code_to_ruis_key(e.key.keysym.scancode); + if (e.key.repeat == 0) { + handle_key_event( + *app, // + e.key.type == SDL_KEYDOWN, + key + ); + } + if (e.type == SDL_KEYDOWN) { + struct sdl_dummy_input_string_provider : public ruis::gui::input_string_provider { + std::u32string get() const override + { + return {}; + } + }; + + handle_character_input(*app, sdl_dummy_input_string_provider(), key); + } + } + break; + case SDL_TEXTINPUT: + { + struct sdl_input_string_provider : public ruis::gui::input_string_provider { + const char* text; + + sdl_input_string_provider(const char* text) : + text(text) + {} + + std::u32string get() const override + { + return utki::to_utf32(this->text); + } + } sdl_input_string_provider( + // save pointer to text, the ownership of text buffer is not taken! + &(e.text.text[0]) + ); + + handle_character_input(*app, sdl_input_string_provider, ruis::key::unknown); + } + break; + default: + if (e.type == ww.user_event_type) { + std::unique_ptr> f( + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + 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)); + } +} +} // namespace + +int main(int argc, const char** argv) +{ + std::unique_ptr app = ruisapp::application_factory::create_application(argc, argv); + if (!app) { + return 1; + } + + while (!get_impl(*app).quit_flag.load()) { + main_loop_iteration(app.get()); + } + + return 0; +} diff --git a/src/ruisapp/glue/windows/glue.cxx b/src/ruisapp/glue/windows/glue.cxx index f1a6fec4..373c43a9 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); diff --git a/tests/app/makefile b/tests/app/makefile index 74ed7ea7..f95a9524 100644 --- a/tests/app/makefile +++ b/tests/app/makefile @@ -10,7 +10,15 @@ this_srcs += $(call prorab-src-dir, src) this_cxxflags += -I ../../src ifeq ($(os),linux) - this__cfg_suffix := $(if $(ogles2),opengles,opengl)-$(if $(wayland),wayland,xorg) + ifeq ($(wayland),true) + this__backend := wayland + else ifeq ($(sdl),true) + this__backend := sdl + else + this__backend := xorg + endif + + this__cfg_suffix := $(if $(ogles2),opengles,opengl)-$(this__backend) this_libruisapp := libruisapp-$(this__cfg_suffix) else this__cfg_suffix := $(if $(ogles2),opengles,opengl)