Как я делал компактную библиотеку для создания приложений с графическим интерфейсом на языке 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<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-и. В нашем случае нам нужно ослеживать следующие события:

  1. Изменение размера контекстного окна

  2. Изменение размера frame buffer-а контекстного окна

  3. Растягивание окна на весь экран

  4. Передача фокуса нашему контекстному окну

  5. Вхождение курсора в область нашего контектного окна

  6. Перемещение курсора в контекстном окне

  7. Нажатие/отпускание кнопки мыши

  8. Прокручивание колесика мыши

  9. Текстовый ввод

  10. Сворачивание контекстного окна

Пользовательские 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/