← Назад к новостям

Как я делал компактную библиотеку для создания приложений с графическим интерфейсом на языке C++. Часть 3

Вступление Это третья статья из цикла статей о создании компактной кросс‑платформенной библиотеки для разработки приложений с графическим интерфейсом на языке C++ — Frenchie. Для тех, кто привык изучать исходный код самостоятельно, репозиторий с исходным кодом библиотеки можно найти по ссылке . В данном материале я продолжу рассказывать про ту часть библиотеки, которая управляет состоянием контекстного окна и отслеживает связанные с ним события. Цель всего цикла материалов — дать представление о том, как устроены подобные проекты, начиная от открытия контекстного окна и управления его состоянием, заканчивая рисованием графических примитивов. Надеюсь, что весь цикл материалов окажется полезным как для тех, кто начинает свой путь в разработке приложений с графическим интерфейсом, так и для опытных разработчиков. Что осталось доделать в platform backend-е ? Итак, в предыдущей статье мы разобрались, как моделируются события, связанные с контекстным окном, где они сохраняются, как фиксируются . В коде выше реализовано все кроме функций, работающих в цикле приложения: awake(), frame_start(), frame_update() и.т.д Это те самые функции, реализация которых зависит от конкретной операционной системы. Писать реализацю этих функций мы будем с использованием библиотек GLFW и SDL3. В виду того, что функция инициализации awake() завязана на конкретный графический API, в этом материале я покажу как дописать недостающую логику в привязке к OpenGL. Про то, как реализовать инициализацию для других графических API я покажу в материале, посвященному rendering backend-у. Итак, начнем. Дописываем недостающую логику при помощи библиотеки GLFW Что такое GLFW? GLFW - это кроссплатформенная open-source библиотека, предназначенная для создания контекстных окон и отслеживания их событий. Ровно то, что нужно. Функция awake() Функция awake() выполняет запуск контекстного окна и инициализацию в нем графического API. Помимом этого, нам нужно не забыть создать хранилище состояния platform backend-а, в котором у нас лежит указатель на контекстное окно, а также кеш его событий. Если мы создаем контекстное окно, которое использует OpenGL, то для Unix-подобных систем нужно указать используемую версию OpenGL, включить профилирование и обратную совместимость. Для контекстного окна GLFW отслеживает события и, если какое-то из них происходит, GLFW может вызвать пользовательскую функцию-callback, в которую передадутся параметры события (какая кнопка нажата, отпущена и.т.д). Вот эти пользовательские callback-и на отслеживание событий нам также нужно не забыть установить. Таким образом, на C++, функция awake() будет выглядеть так: bool ApplicationPlatformBackend::awake() { // если инициализация уже выполнена, то выходим if(m_Api != nullptr && m_Api->Window != nullptr) return true; // Запускаем GLFW if(glfwInit() == GLFW_FALSE) return false; // создаем состояние platform backend-а m_Api = std::make_shared (); // для Unix-подобных систем выставляем используемую версию OpenGL, включаем профилирование и обратную совместимость #ifdef FRENCHIE_APPLICATION_PLATFORM_IS_UNIX glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); #endif // выставляем свойства запускаемого контекстного окна // делаем окно видимым glfwWindowHint(GLFW_VISIBLE, GLFW_TRUE); // включаем рамку glfwWindowHint(GLFW_DECORATED, GLFW_TRUE); // включаем возможность изменения размера контекстного окна glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); // включаем возможность сворачивания контекстного окна glfwWindowHint(GLFW_ICONIFIED, GLFW_TRUE); // включаем возможность отслеживания событий в контекстно окне glfwWindowHint(GLFW_FOCUSED, GLFW_TRUE); // создаем контекстное окно и сохраняем ссылку на него m_Api->Window = glfwCreateWindow(512, 256, "Application", nullptr, nullptr); if(m_Api->Window == nullptr) { glfwTerminate(); return false; } // Здесь мы говорим OpenGL API, что рисовать будем в созданном контекстном окне glfwMakeContextCurrent(reinterpret_cast (m_Api->Window)); // устанавливаем пользовательские callback-и на отслеживание событий в контекстном окне glfwSetWindowSizeCallback(reinterpret_cast (m_Api->Window), FrenchieApplicationGLFWInputHandler::glfw_on_window_resize_callback); glfwSetFramebufferSizeCallback(reinterpret_cast (m_Api->Window), FrenchieApplicationGLFWInputHandler::glfw_on_window_resize_callback); glfwSetWindowMaximizeCallback(reinterpret_cast (m_Api->Window), FrenchieApplicationGLFWInputHandler::glfw_on_window_maximized_callback); glfwSetWindowFocusCallback(reinterpret_cast (m_Api->Window), FrenchieApplicationGLFWInputHandler::glfw_on_window_focused_callback); glfwSetCursorEnterCallback(reinterpret_cast (m_Api->Window), FrenchieApplicationGLFWInputHandler::glfw_on_cursor_enter_callback); glfwSetCursorPosCallback(reinterpret_cast (m_Api->Window), FrenchieApplicationGLFWInputHandler::glfw_on_cursor_moved_callback); glfwSetMouseButtonCallback(reinterpret_cast (m_Api->Window), FrenchieApplicationGLFWInputHandler::glfw_on_mouse_button_callback); glfwSetScrollCallback(reinterpret_cast (m_Api->Window), FrenchieApplicationGLFWInputHandler::glfw_on_mouse_sroll_offset_changed_callback); glfwSetKeyCallback(reinterpret_cast (m_Api->Window), FrenchieApplicationGLFWInputHandler::glfw_on_key_callback); glfwSetCharCallback(reinterpret_cast (m_Api->Window), FrenchieApplicationGLFWInputHandler::glfw_on_character_input_callback); glfwSetWindowIconifyCallback(reinterpret_cast (m_Api->Window), FrenchieApplicationGLFWInputHandler::window_iconify_callback); // запускаем инициализацию графического API через rendering backend if(!ApplicationRenderingBackend::awake((void*(*)(const char*))glfwGetProcAddress)) { glfwTerminate(); return false; } // растягиваем контекстное окно на весь экран glfwMaximizeWindow(reinterpret_cast (m_Api->Window)); return true; } Пользовательские callback-и GLFW для фиксации событий, связанных с контекстным окном Как я писал ранее, GLFW передает нам информацию о произошедших в контекстном окне событиях через пользтовательские callback-и. В нашем случае нам нужно ослеживать следующие события: Изменение размера контекстного окна Изменение размера frame buffer-а контекстного окна Растягивание окна на весь экран Передача фокуса нашему контекстному окну Вхождение курсора в область нашего контектного окна Перемещение курсора в контекстном окне Нажатие/отпускание кнопки мыши Прокручивание колесика мыши Текстовый ввод Сворачивание контекстного окна Пользовательские callback для GLFW для отслеживания указанных выше событий будут реализованы на C++ вот так: // Эта функция конвертирует идентификатор кнопки мыши GLFW в идентификатор Frenchie ApplicationPlatformBackendMouseButton::Button FrenchieApplicationGLFWInputHandler::glfw_mouse_button_to_application_mouse_button(int _MouseButton) { switch (_MouseButton) { case GLFW_MOUSE_BUTTON_LEFT: return ApplicationPlatformBackendMouseButton::Button::ApplicationPlatformBackendMouseButtonLeft; case GLFW_MOUSE_BUTTON_RIGHT: return ApplicationPlatformBackendMouseButton::Button::ApplicationPlatformBackendMouseButtonRight; case GLFW_MOUSE_BUTTON_MIDDLE: return ApplicationPlatformBackendMouseButton::Button::ApplicationPlatformBackendMouseButtonMiddle; } return ApplicationPlatformBackendMouseButton::Button::ApplicationPlatformBackendMouseButtonEnd; } // Эта функция конвертирует идентификатор кнопки клавиатуры GLFW в идентификатор Frenchie ApplicationPlatformBackendKey::Key FrenchieApplicationGLFWInputHandler::glfw_key_to_application_key(int _KeyKode, int) { switch (_KeyKode) { case GLFW_KEY_TAB: return ApplicationPlatformBackendKey::ApplicationPlatformBackendKey_Tab; case GLFW_KEY_LEFT: return ApplicationPlatformBackendKey::ApplicationPlatformBackendKey_LeftArrow; case GLFW_KEY_RIGHT: return ApplicationPlatformBackendKey::ApplicationPlatformBackendKey_RightArrow; // И так далее. Ключей очень много... default: return ApplicationPlatformBackendKey::ApplicationPlatformBackendKey_None; } } // Эта функция конвертирует булев TRUE/FALSE от GLFW в обычный булев TRUE/FALSE C++ bool FrenchieApplicationGLFWInputHandler::glfw_boolean_to_application_boolean(int _Boolean) { return _Boolean == GLFW_TRUE; } // Этот callback реагирует на изменение размера контекстного окна void FrenchieApplicationGLFWInputHandler::glfw_on_window_resize_callback(GLFWwindow* _Window, int _Width, int _Height) { // Здесь мы выставляем актуальный размер frame buffer-а контекстного окна (void)_Window; int width = 0; int height = 0; glfwGetFramebufferSize(_Window, &width, &height); ApplicationRenderingBackend::set_viewport(gs_vec2f(0, 0), gs_vec2f(width, height)); } // Этот callback реагирует на растяжение контекстного окна во весь экран void FrenchieApplicationGLFWInputHandler::glfw_on_window_maximized_callback(GLFWwindow* _Window, int _Maximized) { // Здесь мы выставляем актуальный размер frame buffer-а контекстного окна (void)_Window; int width = 0; int height = 0; glfwGetFramebufferSize(_Window, &width, &height); ApplicationRenderingBackend::set_viewport(gs_vec2f(0, 0), gs_vec2f(width, height)); } // Этот callback реагирует на передачу операционной системой фокуса нашему контекстному окну void FrenchieApplicationGLFWInputHandler::glfw_on_window_focused_callback(GLFWwindow* _Window, int _Focused) { ApplicationPlatformBackend::platform_api()->Input.Window.Focused = glfw_boolean_to_application_boolean(_Focused); } // Этот callback реагирует на перемещение курсора внутри контекстного окна void FrenchieApplicationGLFWInputHandler::glfw_on_cursor_moved_callback(GLFWwindow* _Window, double _X, double _Y) { (void)_Window; if(ApplicationPlatformBackend::platform_api()->Input.MouseCursor.Entered) ApplicationPlatformBackend::platform_api()->Input.MouseCursor.Position = gs_vec2f(_X, _Y); } // Этот callback запускается, когда курсор заходит в область контекстного окна void FrenchieApplicationGLFWInputHandler::glfw_on_cursor_enter_callback(GLFWwindow* _Window, int _Entered) { (void)_Window; ApplicationPlatformBackend::platform_api()->Input.MouseCursor.Entered = glfw_boolean_to_application_boolean(_Entered); } // В этом callback-е мы фиксируем нажатие и отпускание кнопок мыши void FrenchieApplicationGLFWInputHandler::glfw_on_mouse_button_callback(GLFWwindow* _Window, int button, int action, int mods) { (void)_Window; ApplicationPlatformBackend::platform_api()->Input.MouseButtons[glfw_mouse_button_to_application_mouse_button(button)].Pressed = action == GLFW_PRESS; ApplicationPlatformBackend::platform_api()->Input.MouseButtons[glfw_mouse_button_to_application_mouse_button(button)].Released = action == GLFW_RELEASE; } // В этом callback-е мы фиксируем нажатие и отпускание кнопок клавиатуры void FrenchieApplicationGLFWInputHandler::glfw_on_key_callback(GLFWwindow* _Window, int _Keycode, int _Scancode, int _Action, int _Mods) { (void)_Window; (void)_Mods; ApplicationPlatformBackend::platform_api()->Input.Keys[glfw_key_to_application_key(_Keycode, _Scancode)].Pressed = _Action == GLFW_PRESS; ApplicationPlatformBackend::platform_api()->Input.Keys[glfw_key_to_application_key(_Keycode, _Scancode)].Released = _Action == GLFW_RELEASE; } // В этом callback-е мы сохраняем последний введенный в контекстном окне символ void FrenchieApplicationGLFWInputHandler::glfw_on_character_input_callback(GLFWwindow* _Window, unsigned int _Character) { (void)_Window; ApplicationPlatformBackend::platform_api()->Input.Character = _Character; } // В этом callback-е мы сохраняем нормированное смещение колесика мыши void FrenchieApplicationGLFWInputHandler::glfw_on_mouse_sroll_offset_changed_callback(GLFWwindow* _Window, double _dX, double _dY) { (void)_Window; ApplicationPlatformBackend::platform_api()->Input.MouseScrollOffset = gs_vector_normalize(gs_vec2f(_dX, _dY)); } // В этом callback-е мы фиксируем событие сворачивания контекстного окна void FrenchieApplicationGLFWInputHandler::window_iconify_callback(GLFWwindow* _Window, int _Iconified) { // фиксируем свернуто ли контекстное окно ApplicationPlatformBackend::platform_api()->Input.Window.Iconified = glfw_boolean_to_application_boolean(_Iconified); // ексли контекстное окно разворачивается, мы должны вернуть ему фокус и указать графическому API, что мы продолжаем рисовать в этом контекстном окне if(!glfw_boolean_to_application_boolean(_Iconified)) { glfwFocusWindow(_Window); glfwMakeContextCurrent(_Window); } } Функция frame_start() В функции frame_start() мы готовим графический API к рисованию в контекстном окне, подхватываем события от контекстного окна (т.е просим GLFW запустить наши callback-и), после чего мы сохраняем события в кеш контекстного окна и запускаем нашу платформо-независимую логику фиксации событий, которую обсуждали в прошлой статье . При работе с GLFW есть один очень интересный момент, связанный со сворачиванием контекстного окна. Когда контекстное окно сворачивается, операционная система забирает у него фокус и устанавливает размер контекстного окна, равный нулю. В таком случае, если не поставить цепочку обработки событий GLFW на паузу, она попросту зависнет вместе с циклом работы нашего приложения. В таком случае мы не сможем развернуть контекстное окно обратно. Таким образом, с учетом описанных нюансов, функция frame_start() будет выглядеть вот так: void ApplicationPlatformBackend::frame_start() { // проверяем, свернуто ли наше контекстное окно. Если оно свернуто, мы ставим на паузу цепочку отслеживания событий GLFW, чтобы она не зависла и выходим if(ApplicationPlatformBackend::is_window_iconified()) { glfwWaitEventsTimeout(0.1); return; } // здесь мы сохраняем текущий размер frame buffer-а контекстного окна int x = 0; int y = 0; glfwGetFramebufferSize(reinterpret_cast (m_Api->Window), &x, &y); m_Api->Input.FrameBufferSize = gs_vec2f(x, y); // здесь мы сохраняем текущий размер контекстного окна glfwGetWindowSize(reinterpret_cast (m_Api->Window), &x, &y); m_Api->Input.WindowSize = gs_vec2f(x, y); // здесь мы сохраняем текущую позицию контекстного окна glfwGetWindowPos(reinterpret_cast (m_Api->Window), &x, &y); m_Api->Input.WindowPosition = gs_vec2f(x, y); // здесь мы запускаем цепочку отслеживания событий GLFW, т.е просим GLFW запустить наши callback-и // и заодно устанавливаем интервал смены текущего frame buffer-а контекстного окна и свеженарисованного glfwPollEvents(); glfwSwapInterval(1); // здесь, используя rendering backend, мы просим готовим графический API к рисованию текущего кадра ApplicationRenderingBackend::frame_start(); // здесь мы запускаем платформо-независимую логику фиксации событий ApplicationPlatformBackend::collect_input(); } Функция frame_update() Это самая изящная и лаконичная функция platform backend-а при использовании библиотеки GLFW: void ApplicationPlatformBackend::frame_update(){} Почему эта функция ничего не делает ? Дело в том, что GLFW предусматривает автоматическое включение/отключение текстового ввода в контекстном окне и нам самим делать ничего не нужно. Функция frame_finish() В функции frame_finish() мы должны попросить rendering backend отдать нам то, что нарисовала видеокарта, отобразить это в контекстном окне, а потом сбросить состояние графического API в исходное вместе с кешем событий контекстного окна: void ApplicationPlatformBackend::frame_finish() { // Если окно свернуто, то не делаем ничего if(ApplicationPlatformBackend::is_window_iconified()) return; // просим rendering backend отдать нам то, что нарисовала видеокарта и сбрасываем состояние графического API в исходное ApplicationRenderingBackend::frame_finish(); // отображаем то, что нарисовала видеокарта, в нашем контекстном окне glfwSwapBuffers(reinterpret_cast (m_Api->Window)); // сбрасываем кеш событий контекстного окна ApplicationPlatformBackend::restore_input(); } Функции quit(), close(), is_closed() Функция quit() деинициализирует графический API и закрывает контекстное окно и делает это именно в таком порядке, т.к графический API привязан к контекстному окну и поэтому уничтожаться он должен первым: void ApplicationPlatformBackend::quit() { // деинициализируем графический API ApplicationRenderingBackend::quit(); // уничтожаем контекстное окно glfwDestroyWindow(reinterpret_cast (m_Api->Window)); glfwTerminate(); m_Api->Window = nullptr; } Функция close() говорит GLFW, что надо закрыть контекстное окно, а функция is_closed() проверяет, не собирается ли контекстное окно закрыться: bool ApplicationPlatformBackend::is_closed() { return glfwWindowShouldClose(reinterpret_cast (m_Api->Window)); } void ApplicationPlatformBackend::close() { glfwSetWindowShouldClose(reinterpret_cast (m_Api->Window), GL_TRUE); } Таким образом, мы дописали недостающие функции platform backend-а с использованием библиотеки GLFW, теперь посмотрим, как сделать тоже самое, имея под рукой библиотеку SDL3. Дописываем недостающую логику с использованием библиотеки SDL3 SDL3 - это библиотека, предоставляющая низкоуровневые функции для работы с аудио, вводом с клавиатуры, мыши, игрового контроллера, а также для работы с различными графическими API типа OpenGL, Vulkan и.т.д. Сам бибилиотека больше чем GLFW и ее функционал намного шире, но, нас интересует только работа с графическими API. Основное отличие SDL3 от GLFW в части работы с событиями контекстного окна - это то, что вместо callback-ов, SDL3 использует переменные состояния. Эти переменные состояние являются дополнительной информацией, которую нужно хранить в состоянии platform backend-а: // Application #include // SDL3 #include "SDL3/SDL.h" namespace Frenchie { namespace Application { // структура-наследник базового состояния контекстного окна struct FrenchieApplicationPlatformSDL3 : public FrenchieApplicationPlatformApi { FrenchieApplicationPlatformSDL3(){} virtual ~FrenchieApplicationPlatformSDL3(){} // в этой переменной хранится информация обо всех событиях, связанных с контекстным окном SDL_Event Event; // в этой переменной хранится контекст OpenGL, она нужна только при работе с OpenGL SDL_GLContext Context; }; } } Функция awake() В функции awake() при использовании SDL3 нам нужно запустить контекстное окно, создать для него контекст OpenGL, привязать этот контекст к контекстному окну и потом инициализировать графический API: bool ApplicationPlatformBackend::awake() { // если инициализация уже выполнена, ничего не делаем if(m_Api != nullptr && m_Api->Window != nullptr) return true; // инициализируем модуль SDL3 для работы с графикой if(!SDL_Init(SDL_INIT_VIDEO)) return false; // создаем состояние platform backend-а m_Api = std::make_shared (); auto SDL3 = platform_api (); // на unix-подобных системах при работе с OpenGL нужно не забыть включить профилирование и режим обратной совместимости. // при работе с SDL3 версию OpenGL API указывать не нужно #ifdef FRENCHIE_APPLICATION_PLATFORM_IS_UNIX SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); #endif // создаем контекстное окно SDL3->Window = SDL_CreateWindow("Application", 512, 256, SDL_WINDOW_OPENGL | SDL_WINDOW_MAXIMIZED | SDL_WINDOW_RESIZABLE); if(m_Api->Window == nullptr) { SDL_Quit(); return false; } // создаем OpenGL контекст и привязываем его к контекстному окну SDL3->Context = SDL_GL_CreateContext(reinterpret_cast (SDL3->Window)); if(SDL3->Context == nullptr) { SDL_DestroyWindow(reinterpret_cast (SDL3->Window)); SDL_Quit(); return false; } if(!SDL_GL_MakeCurrent(reinterpret_cast (SDL3->Window), SDL3->Context)) { SDL_DestroyWindow(reinterpret_cast (SDL3->Window)); SDL_GL_DestroyContext(SDL3->Context); SDL_Quit(); return false; } // инициализируем графический API if(!ApplicationRenderingBackend::awake((void*(*)(const char*))SDL_GL_GetProcAddress)) { SDL_Quit(); return false; } return true; } Функция frame_start() В функции frame_start() нам нужно подготовить графический API к рисованию, отследить события, связанные с контекстным окном, из переменных состояния SDL3, а также запустить текстовый ввод и подхватить размеры котнектсного окна, его frame buffer-а и положение: void ApplicationPlatformBackend::frame_start() { auto SDL3 = platform_api (); if(SDL3 == nullptr) return; // сохраняем размер контекстного окна в пикселях - это и есть размер frame buffer-а int x = 0; int y = 0; SDL_GetWindowSizeInPixels(reinterpret_cast (SDL3->Window), &x, &y); SDL3->Input.FrameBufferSize = gs_vec2f(x, y); // здесь мы сохраняем размер контекстного окна SDL_GetWindowSize(reinterpret_cast (SDL3->Window), &x, &y); SDL3->Input.WindowSize = gs_vec2f(x, y); // сохраняем положение контекстного окна SDL_GetWindowPosition(reinterpret_cast (SDL3->Window), &x, &y); SDL3->Input.WindowPosition = gs_vec2f(x, y); // запускаем отслеживание текстового ввода SDL_StartTextInput(reinterpret_cast (SDL3->Window)); // запускаем сохранение событий, связанных с контекстным окном, в переменные состояния SDL3 SDL_PollEvent(&SDL3->Event); SDL_GL_SetSwapInterval(1); // фиксируем события, связанные с контекстным окном, использую уже подготовленную переменную состояния SDL3, отвечающую за события switch (SDL3->Event.type) { case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED: case SDL_EVENT_WINDOW_RESIZED: ApplicationRenderingBackend::set_viewport(gs_vec2f(0, 0), SDL3->Input.FrameBufferSize); } switch (SDL3->Event.type) { case SDL_EVENT_MOUSE_MOTION: { SDL3->Input.MouseCursor.Position = gs_vec2f((float)SDL3->Event.motion.x, (float)SDL3->Event.motion.y); break; } case SDL_EVENT_MOUSE_WHEEL: { SDL3->Input.MouseScrollOffset = gs_vec2f(SDL3->Event.wheel.x, SDL3->Event.wheel.y); break; } case SDL_EVENT_MOUSE_BUTTON_DOWN: case SDL_EVENT_MOUSE_BUTTON_UP: { SDL3->Input.MouseButtons[SDLApplicationInputHandler::glfw_mouse_button_to_application_mouse_button(SDL3->Event.button.button)].Pressed = SDL3->Event.type == SDL_EVENT_MOUSE_BUTTON_DOWN; SDL3->Input.MouseButtons[SDLApplicationInputHandler::glfw_mouse_button_to_application_mouse_button(SDL3->Event.button.button)].Released = SDL3->Event.type == SDL_EVENT_MOUSE_BUTTON_UP; break; } case SDL_EVENT_TEXT_INPUT: { std::u32string utf32 = Frenchie::Core::String::convert_utf8_to_utf32(SDL3->Event.text.text); if(!utf32.empty()) { SDL3->Input.Character = utf32[0]; break; } } case SDL_EVENT_KEY_DOWN: case SDL_EVENT_KEY_UP: { SDL3->Input.Keys[SDLApplicationInputHandler::glfw_key_to_application_key(SDL3->Event.key.key, SDL3->Event.key.scancode)].Pressed = SDL3->Event.type == SDL_EVENT_KEY_DOWN; SDL3->Input.Keys[SDLApplicationInputHandler::glfw_key_to_application_key(SDL3->Event.key.key, SDL3->Event.key.scancode)].Released = SDL3->Event.type == SDL_EVENT_KEY_UP; break; } case SDL_EVENT_DISPLAY_ORIENTATION: case SDL_EVENT_DISPLAY_ADDED: case SDL_EVENT_DISPLAY_REMOVED: case SDL_EVENT_DISPLAY_MOVED: case SDL_EVENT_DISPLAY_CONTENT_SCALE_CHANGED: { break; } case SDL_EVENT_WINDOW_MOUSE_ENTER: { SDL3->Input.MouseCursor.Entered = true; break; } case SDL_EVENT_WINDOW_MOUSE_LEAVE: { SDL3->Input.MouseCursor.Entered = false; break; } case SDL_WINDOW_MINIMIZED: { SDL3->Input.Window.Iconified = SDL3->Event.type == SDL_WINDOW_MINIMIZED; break; } case SDL_EVENT_WINDOW_FOCUS_GAINED: case SDL_EVENT_WINDOW_FOCUS_LOST: { SDL3->Input.Window.Focused = SDL3->Event.type == SDL_EVENT_WINDOW_FOCUS_GAINED; break; } case SDL_EVENT_WINDOW_CLOSE_REQUESTED: case SDL_EVENT_WINDOW_MOVED: case SDL_EVENT_WINDOW_RESIZED: { if(SDL3->Event.type == SDL_EVENT_WINDOW_CLOSE_REQUESTED) { } else if(SDL3->Event.type == SDL_EVENT_WINDOW_MOVED) { } else if(SDL3->Event.type == SDL_EVENT_WINDOW_RESIZED) { } break; } case SDL_EVENT_GAMEPAD_ADDED: case SDL_EVENT_GAMEPAD_REMOVED: break; default: break; } // готовим графический API к рисованию, используюя rendering_backend() ApplicationRenderingBackend::frame_start(); // запускаем платформо-независимую логику отслеживания событий ApplicationPlatformBackend::collect_input(); } Функция frame_update() В случае с SDL3, функция frame_update() выполняет функцию выключения отслеживания текстового ввода в контекстном окне, SDL3 не делает этого автоматически: void ApplicationPlatformBackend::frame_update() { auto SDL3 = platform_api (); if(SDL3 == nullptr) return; // выключаем отслеживание текстового ввода в контекстном окне if(SDL_TextInputActive(reinterpret_cast (SDL3->Window))) SDL_StopTextInput(reinterpret_cast (SDL3->Window)); } Функция frame_update() В случае с SDL3, функция frame_update() выполняет функцию выключения отслеживания текстового ввода в контекстном окне, SDL3 не делает этого автоматически: void ApplicationPlatformBackend::frame_update() { auto SDL3 = platform_api (); if(SDL3 == nullptr) return; // выключаем отслеживание текстового ввода в контекстном окне if(SDL_TextInputActive(reinterpret_cast (SDL3->Window))) SDL_StopTextInput(reinterpret_cast (SDL3->Window)); } Функция frame_finish() В функции frame_finish() мы должны попросить rendering backend отдать нам то, что нарисовала видеокарта, отобразить это в контекстном окне, а потом сбросить состояние графического API в исходне вместе с кешем событий контекстного окна: void ApplicationPlatformBackend::frame_finish() { auto SDL3 = platform_api (); if(SDL3 == nullptr) return; // execute rendering backend ApplicationRenderingBackend::frame_finish(); // swap frame buffers SDL_GL_SwapWindow(reinterpret_cast (SDL3->Window)); // restore input ApplicationPlatformBackend::restore_input(); } Функции quit(), close(), is_closed() Функция quit() деинициализирует графический API и закрывает контекстное окно и делает это именно в таком порядке, т.к графический API привязан к контекстному окну и поэтому уничтожаться он должен первым: void ApplicationPlatformBackend::quit() { // terminate rendering API ApplicationRenderingBackend::quit(); // terminate self auto SDL3 = platform_api (); if(SDL3 != nullptr) { SDL_GL_DestroyContext(SDL3->Context); SDL_DestroyWindow(reinterpret_cast (SDL3->Window)); } SDL_Quit(); } Функция close() говорит SDL3, что надо закрыть контекстное окно, а функция is_closed() проверяет, не собирается ли контекстное окно закрыться: bool ApplicationPlatformBackend::is_closed() { auto SDL3 = platform_api (); return SDL3 == nullptr || SDL3->Event.type == SDL_EVENT_WINDOW_CLOSE_REQUESTED; } void ApplicationPlatformBackend::close() { auto SDL3 = platform_api (); if(SDL3 == nullptr) return; SDL_Event close_event; SDL_zero(close_event); close_event.type = SDL_EVENT_WINDOW_CLOSE_REQUESTED; close_event.window.windowID = SDL_GetWindowID(reinterpret_cast (SDL3->Window)); SDL_PushEvent(&close_event); } Таким образом, мы дописали недостающую логику platform backend-а, используя библиотеки GLFW и SDL3. В следующем материале мы перейдём к самой сложной и при этом самой интересной части проекта Frenchie - rendering backend-у. А на этом пока-что все, спасибо за внимание, надеюсь, было интересно.
📊 Источник: Habr | Оригинал