Вступление
Это третья статья из цикла статей о создании компактной кросс‑платформенной библиотеки для разработки приложений с графическим интерфейсом на языке 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<FrenchieApplicationPlatformApi>(); // для 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<GLFWwindow*>(m_Api->Window)); // устанавливаем пользовательские callback-и на отслеживание событий в контекстном окне glfwSetWindowSizeCallback(reinterpret_cast<GLFWwindow*>(m_Api->Window), FrenchieApplicationGLFWInputHandler::glfw_on_window_resize_callback); glfwSetFramebufferSizeCallback(reinterpret_cast<GLFWwindow*>(m_Api->Window), FrenchieApplicationGLFWInputHandler::glfw_on_window_resize_callback); glfwSetWindowMaximizeCallback(reinterpret_cast<GLFWwindow*>(m_Api->Window), FrenchieApplicationGLFWInputHandler::glfw_on_window_maximized_callback); glfwSetWindowFocusCallback(reinterpret_cast<GLFWwindow*>(m_Api->Window), FrenchieApplicationGLFWInputHandler::glfw_on_window_focused_callback); glfwSetCursorEnterCallback(reinterpret_cast<GLFWwindow*>(m_Api->Window), FrenchieApplicationGLFWInputHandler::glfw_on_cursor_enter_callback); glfwSetCursorPosCallback(reinterpret_cast<GLFWwindow*>(m_Api->Window), FrenchieApplicationGLFWInputHandler::glfw_on_cursor_moved_callback); glfwSetMouseButtonCallback(reinterpret_cast<GLFWwindow*>(m_Api->Window), FrenchieApplicationGLFWInputHandler::glfw_on_mouse_button_callback); glfwSetScrollCallback(reinterpret_cast<GLFWwindow*>(m_Api->Window), FrenchieApplicationGLFWInputHandler::glfw_on_mouse_sroll_offset_changed_callback); glfwSetKeyCallback(reinterpret_cast<GLFWwindow*>(m_Api->Window), FrenchieApplicationGLFWInputHandler::glfw_on_key_callback); glfwSetCharCallback(reinterpret_cast<GLFWwindow*>(m_Api->Window), FrenchieApplicationGLFWInputHandler::glfw_on_character_input_callback); glfwSetWindowIconifyCallback(reinterpret_cast<GLFWwindow*>(m_Api->Window), FrenchieApplicationGLFWInputHandler::window_iconify_callback); // запускаем инициализацию графического API через rendering backend if(!ApplicationRenderingBackend::awake((void*(*)(const char*))glfwGetProcAddress)) { glfwTerminate(); return false; } // растягиваем контекстное окно на весь экран glfwMaximizeWindow(reinterpret_cast<GLFWwindow*>(m_Api->Window)); return true;}
Пользовательские callback-и GLFW для фиксации событий, связанных с контекстным окном
Как я писал ранее, GLFW передает нам информацию о произошедших в контекстном окне событиях через пользтовательские callback-и. В нашем случае нам нужно ослеживать следующие события:
-
Изменение размера контекстного окна
-
Изменение размера frame buffer-а контекстного окна
-
Растягивание окна на весь экран
-
Передача фокуса нашему контекстному окну
-
Вхождение курсора в область нашего контектного окна
-
Перемещение курсора в контекстном окне
-
Нажатие/отпускание кнопки мыши
-
Прокручивание колесика мыши
-
Текстовый ввод
-
Сворачивание контекстного окна
Пользовательские callback для GLFW для отслеживания указанных выше событий будут реализованы на C++ вот так:
// Эта функция конвертирует идентификатор кнопки мыши GLFW в идентификатор FrenchieApplicationPlatformBackendMouseButton::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 в идентификатор FrenchieApplicationPlatformBackendKey::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<GLFWwindow*>(m_Api->Window), &x, &y); m_Api->Input.FrameBufferSize = gs_vec2f(x, y); // здесь мы сохраняем текущий размер контекстного окна glfwGetWindowSize(reinterpret_cast<GLFWwindow*>(m_Api->Window), &x, &y); m_Api->Input.WindowSize = gs_vec2f(x, y); // здесь мы сохраняем текущую позицию контекстного окна glfwGetWindowPos(reinterpret_cast<GLFWwindow*>(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<GLFWwindow*>(m_Api->Window)); // сбрасываем кеш событий контекстного окна ApplicationPlatformBackend::restore_input();}
Функции quit(), close(), is_closed()
Функция quit() деинициализирует графический API и закрывает контекстное окно и делает это именно в таком порядке, т.к графический API привязан к контекстному окну и поэтому уничтожаться он должен первым:
void ApplicationPlatformBackend::quit(){ // деинициализируем графический API ApplicationRenderingBackend::quit(); // уничтожаем контекстное окно glfwDestroyWindow(reinterpret_cast<GLFWwindow*>(m_Api->Window)); glfwTerminate(); m_Api->Window = nullptr;}
Функция close() говорит GLFW, что надо закрыть контекстное окно, а функция is_closed() проверяет, не собирается ли контекстное окно закрыться:
bool ApplicationPlatformBackend::is_closed(){ return glfwWindowShouldClose(reinterpret_cast<GLFWwindow*>(m_Api->Window));}void ApplicationPlatformBackend::close(){ glfwSetWindowShouldClose(reinterpret_cast<GLFWwindow*>(m_Api->Window), GL_TRUE);}
Таким образом, мы дописали недостающие функции platform backend-а с использованием библиотеки GLFW, теперь посмотрим, как сделать тоже самое, имея под рукой библиотеку SDL3.
Дописываем недостающую логику с использованием библиотеки SDL3
SDL3 — это библиотека, предоставляющая низкоуровневые функции для работы с аудио, вводом с клавиатуры, мыши, игрового контроллера, а также для работы с различными графическими API типа OpenGL, Vulkan и.т.д. Сам бибилиотека больше чем GLFW и ее функционал намного шире, но, нас интересует только работа с графическими API.
Основное отличие SDL3 от GLFW в части работы с событиями контекстного окна — это то, что вместо callback-ов, SDL3 использует переменные состояния. Эти переменные состояние являются дополнительной информацией, которую нужно хранить в состоянии platform backend-а:
// Application#include <FrenchieApplicationPlatformBackend.hpp>// 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<FrenchieApplicationPlatformSDL3>(); auto SDL3 = platform_api<FrenchieApplicationPlatformSDL3>(); // на 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<SDL_Window*>(SDL3->Window)); if(SDL3->Context == nullptr) { SDL_DestroyWindow(reinterpret_cast<SDL_Window*>(SDL3->Window)); SDL_Quit(); return false; } if(!SDL_GL_MakeCurrent(reinterpret_cast<SDL_Window*>(SDL3->Window), SDL3->Context)) { SDL_DestroyWindow(reinterpret_cast<SDL_Window*>(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<FrenchieApplicationPlatformSDL3>(); if(SDL3 == nullptr) return; // сохраняем размер контекстного окна в пикселях - это и есть размер frame buffer-а int x = 0; int y = 0; SDL_GetWindowSizeInPixels(reinterpret_cast<SDL_Window*>(SDL3->Window), &x, &y); SDL3->Input.FrameBufferSize = gs_vec2f(x, y); // здесь мы сохраняем размер контекстного окна SDL_GetWindowSize(reinterpret_cast<SDL_Window*>(SDL3->Window), &x, &y); SDL3->Input.WindowSize = gs_vec2f(x, y); // сохраняем положение контекстного окна SDL_GetWindowPosition(reinterpret_cast<SDL_Window*>(SDL3->Window), &x, &y); SDL3->Input.WindowPosition = gs_vec2f(x, y); // запускаем отслеживание текстового ввода SDL_StartTextInput(reinterpret_cast<SDL_Window*>(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<FrenchieApplicationPlatformSDL3>(); if(SDL3 == nullptr) return; // выключаем отслеживание текстового ввода в контекстном окне if(SDL_TextInputActive(reinterpret_cast<SDL_Window*>(SDL3->Window))) SDL_StopTextInput(reinterpret_cast<SDL_Window*>(SDL3->Window));}
Функция frame_update()
В случае с SDL3, функция frame_update() выполняет функцию выключения отслеживания текстового ввода в контекстном окне, SDL3 не делает этого автоматически:
void ApplicationPlatformBackend::frame_update(){ auto SDL3 = platform_api<FrenchieApplicationPlatformSDL3>(); if(SDL3 == nullptr) return; // выключаем отслеживание текстового ввода в контекстном окне if(SDL_TextInputActive(reinterpret_cast<SDL_Window*>(SDL3->Window))) SDL_StopTextInput(reinterpret_cast<SDL_Window*>(SDL3->Window));}
Функция frame_finish()
В функции frame_finish() мы должны попросить rendering backend отдать нам то, что нарисовала видеокарта, отобразить это в контекстном окне, а потом сбросить состояние графического API в исходне вместе с кешем событий контекстного окна:
void ApplicationPlatformBackend::frame_finish(){ auto SDL3 = platform_api<FrenchieApplicationPlatformSDL3>(); if(SDL3 == nullptr) return; // execute rendering backend ApplicationRenderingBackend::frame_finish(); // swap frame buffers SDL_GL_SwapWindow(reinterpret_cast<SDL_Window*>(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<FrenchieApplicationPlatformSDL3>(); if(SDL3 != nullptr) { SDL_GL_DestroyContext(SDL3->Context); SDL_DestroyWindow(reinterpret_cast<SDL_Window*>(SDL3->Window)); } SDL_Quit();}
Функция close() говорит SDL3, что надо закрыть контекстное окно, а функция is_closed() проверяет, не собирается ли контекстное окно закрыться:
bool ApplicationPlatformBackend::is_closed(){ auto SDL3 = platform_api<FrenchieApplicationPlatformSDL3>(); return SDL3 == nullptr || SDL3->Event.type == SDL_EVENT_WINDOW_CLOSE_REQUESTED;}void ApplicationPlatformBackend::close(){ auto SDL3 = platform_api<FrenchieApplicationPlatformSDL3>(); 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<SDL_Window*>(SDL3->Window)); SDL_PushEvent(&close_event);}
Таким образом, мы дописали недостающую логику platform backend-а, используя библиотеки GLFW и SDL3. В следующем материале мы перейдём к самой сложной и при этом самой интересной части проекта Frenchie — rendering backend-у. А на этом пока-что все, спасибо за внимание, надеюсь, было интересно.
ссылка на оригинал статьи https://habr.com/ru/articles/1055952/