Skip to content

SDL3#

Example's page

This page describes an example listed in custom_windowing_toolkits category.

This code demonstrates how to integrate the AUI Framework with SDL3 to create a window with OpenGL rendering.

Overview#

This application creates a simple GUI window using SDL3 for window management and input handling, while AUI Framework handles the UI components and OpenGL rendering. The result is a window displaying "Hello, World!" text and a clickable button.


Core Components#

EmbedRenderingContext#

struct EmbedRenderingContext : IRenderingContext {
    std::shared_ptr<OpenGLRenderer> m_renderer;

    ~EmbedRenderingContext() override {
    }

    void destroyNativeWindow(ASurface& window) override {}

    AImage makeScreenshot() override {
        return {};
    }

    void beginPaint(ASurface& window) override {
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
        glViewport(0, 0, window.getSize().x, window.getSize().y);
        m_renderer->beginPaint(window.getSize());
        glClearColor(1, 1, 1, 1);
        glClear(GL_COLOR_BUFFER_BIT);
    }
    void endPaint(ASurface& window) override {
        m_renderer->endPaint();
    }

    void beginResize(ASurface& window) override {
    }
    void endResize(ASurface& window) override {
    }

    IRenderer& renderer() override {
        return *m_renderer;
    }
};

Purpose: This structure acts as a bridge between AUI's rendering system and the SDL3/OpenGL environment.

Key responsibilities:

  • m_renderer: Holds the OpenGL renderer instance
  • beginPaint(): Prepares the framebuffer for drawing by binding it and clearing it with white color
  • endPaint(): Finalizes the drawing operations
  • renderer(): Provides access to the OpenGL renderer

EmbedWindow#

struct EmbedWindow : AGLEmbedContext, AObject {
    SDL_Window* sdl_window = nullptr;
    SDL_GLContext gl_context = nullptr;
    bool close = false;

    ~EmbedWindow() override {
        SDL_GL_DestroyContext(gl_context);
        SDL_DestroyWindow(sdl_window);
    }

    void init(std::unique_ptr<EmbedRenderingContext>&& context) {
        windowInit(std::move(context));

        int width = 0;
        int height = 0;
        SDL_GetWindowSizeInPixels(sdl_window, &width, &height);
        setViewportSize(width, height);

        setCustomDpiRatio(SDL_GetWindowDisplayScale(sdl_window));

        connect(getWindow()->touchscreenKeyboardShown, this, [this] {
            glm::ivec2 pos = getWindow()->getFocusedView()->getPositionInWindow();
            glm::ivec2 size = getWindow()->getFocusedView()->getSize();
            SDL_Rect rect;
            rect.x = pos.x;
            rect.y = pos.y;
            rect.w = size.x;
            rect.h = size.y;
            SDL_SetTextInputArea(sdl_window, &rect, 0);
            SDL_StartTextInput(sdl_window);
        });
        connect(getWindow()->touchscreenKeyboardHidden, this, [this] {
            SDL_StopTextInput(sdl_window);
        });
    }

    void onNotifyProcessMessages() override {}
};

Purpose: Represents the main application window and manages the connection between SDL3 and AUI Framework.

Key members:

  • sdl_window: The SDL3 window handle
  • gl_context: The OpenGL context for rendering
  • close: Flag to signal when the application should exit

Methods:

  • init(): Initializes the AUI window system with the rendering context, sets viewport size based on actual pixel dimensions, and configures DPI scaling for high-resolution displays
  • ~EmbedWindow(): Destructor that properly cleans up SDL resources (GL context and window)

Input Handling#

sdlToAInput()#

static auto sdlToAInput(Uint32 key) -> AInput::Key {
    static const std::unordered_map<SDL_Keycode, AInput::Key> mapping = {
        {SDLK_A, AInput::Key::A},
        {SDLK_B, AInput::Key::B},
        {SDLK_C, AInput::Key::C},
        {SDLK_D, AInput::Key::D},
        {SDLK_E, AInput::Key::E},
        {SDLK_F, AInput::Key::F},
        {SDLK_G, AInput::Key::G},
        {SDLK_H, AInput::Key::H},
        {SDLK_I, AInput::Key::I},
        {SDLK_J, AInput::Key::J},
        {SDLK_K, AInput::Key::K},
        {SDLK_L, AInput::Key::L},
        {SDLK_M, AInput::Key::M},
        {SDLK_N, AInput::Key::N},
        {SDLK_O, AInput::Key::O},
        {SDLK_P, AInput::Key::P},
        {SDLK_Q, AInput::Key::Q},
        {SDLK_R, AInput::Key::R},
        {SDLK_S, AInput::Key::S},
        {SDLK_T, AInput::Key::T},
        {SDLK_U, AInput::Key::U},
        {SDLK_V, AInput::Key::V},
        {SDLK_W, AInput::Key::W},
        {SDLK_X, AInput::Key::X},
        {SDLK_Y, AInput::Key::Y},
        {SDLK_Z, AInput::Key::Z},

        {SDLK_0, AInput::Key::NUM0},
        {SDLK_1, AInput::Key::NUM1},
        {SDLK_2, AInput::Key::NUM2},
        {SDLK_3, AInput::Key::NUM3},
        {SDLK_4, AInput::Key::NUM4},
        {SDLK_5, AInput::Key::NUM5},
        {SDLK_6, AInput::Key::NUM6},
        {SDLK_7, AInput::Key::NUM7},
        {SDLK_8, AInput::Key::NUM8},
        {SDLK_9, AInput::Key::NUM9},

        {SDLK_F1, AInput::Key::F1},
        {SDLK_F2, AInput::Key::F2},
        {SDLK_F3, AInput::Key::F3},
        {SDLK_F4, AInput::Key::F4},
        {SDLK_F5, AInput::Key::F5},
        {SDLK_F6, AInput::Key::F6},
        {SDLK_F7, AInput::Key::F7},
        {SDLK_F8, AInput::Key::F8},
        {SDLK_F9, AInput::Key::F9},
        {SDLK_F10, AInput::Key::F10},
        {SDLK_F11, AInput::Key::F11},
        {SDLK_F12, AInput::Key::F12},
        {SDLK_F13, AInput::Key::F13},
        {SDLK_F14, AInput::Key::F14},
        {SDLK_F15, AInput::Key::F15},
        {SDLK_F16, AInput::Key::F16},
        {SDLK_F17, AInput::Key::F17},
        {SDLK_F18, AInput::Key::F18},
        {SDLK_F19, AInput::Key::F19},

        {SDLK_EQUALS, AInput::Key::EQUAL},
        {SDLK_MINUS, AInput::Key::DASH},
        {SDLK_RIGHTBRACKET, AInput::Key::RBRACKET},
        {SDLK_LEFTBRACKET, AInput::Key::LBRACKET},
        {SDLK_APOSTROPHE, AInput::Key::QUOTE},
        {SDLK_SEMICOLON, AInput::Key::SEMICOLON},
        {SDLK_BACKSLASH, AInput::Key::BACKSLASH},
        {SDLK_COMMA, AInput::Key::COMMA},
        {SDLK_SLASH, AInput::Key::SLASH},
        {SDLK_PERIOD, AInput::Key::PERIOD},
        {SDLK_GRAVE, AInput::Key::TILDE},

        {SDLK_KP_DECIMAL, AInput::Key::NUMPAD_PERIOD},
        {SDLK_KP_MULTIPLY, AInput::Key::NUMPAD_MULTIPLY},
        {SDLK_KP_PLUS, AInput::Key::NUMPAD_ADD},
        {SDLK_CLEAR, AInput::Key::NUMLOCKCLEAR},
        {SDLK_KP_DIVIDE, AInput::Key::NUMPAD_DIVIDE},
        {SDLK_KP_ENTER, AInput::Key::NUMPAD_RETURN},
        {SDLK_KP_MINUS, AInput::Key::NUMPAD_SUBTRACT},
        {SDLK_KP_EQUALS, AInput::Key::NUMPAD_EQUAL},
        {SDLK_KP_0, AInput::Key::NUMPAD_0},
        {SDLK_KP_1, AInput::Key::NUMPAD_1},
        {SDLK_KP_2, AInput::Key::NUMPAD_2},
        {SDLK_KP_3, AInput::Key::NUMPAD_3},
        {SDLK_KP_4, AInput::Key::NUMPAD_4},
        {SDLK_KP_5, AInput::Key::NUMPAD_5},
        {SDLK_KP_6, AInput::Key::NUMPAD_6},
        {SDLK_KP_7, AInput::Key::NUMPAD_7},
        {SDLK_KP_8, AInput::Key::NUMPAD_8},
        {SDLK_KP_9, AInput::Key::NUMPAD_9},

        {SDLK_RETURN, AInput::Key::RETURN},
        {SDLK_TAB, AInput::Key::TAB},
        {SDLK_SPACE, AInput::Key::SPACE},
        {SDLK_DELETE, AInput::Key::DEL},
        {SDLK_ESCAPE, AInput::Key::ESCAPE},
        {SDLK_LGUI, AInput::Key::LSYSTEM},
        {SDLK_LSHIFT, AInput::Key::LSHIFT},
        {SDLK_CAPSLOCK, AInput::Key::CAPSLOCK},
        {SDLK_LALT, AInput::Key::LALT},
        {SDLK_LCTRL, AInput::Key::LCONTROL},
        {SDLK_RGUI, AInput::Key::RSYSTEM},
        {SDLK_RSHIFT, AInput::Key::RSHIFT},
        {SDLK_RALT, AInput::Key::RALT},
        {SDLK_RCTRL, AInput::Key::RCONTROL},
        {SDLK_VOLUMEUP, AInput::Key::VOLUMEUP},
        {SDLK_VOLUMEDOWN, AInput::Key::VOLUMEDOWN},
        {SDLK_MUTE, AInput::Key::MUTE},
        {SDLK_MENU, AInput::Key::MENU},
        {SDLK_HELP, AInput::Key::INSERT},
        {SDLK_HOME, AInput::Key::HOME},
        {SDLK_PAGEUP, AInput::Key::PAGEUP},
        {SDLK_END, AInput::Key::END},
        {SDLK_PAGEDOWN, AInput::Key::PAGEDOWN},
        {SDLK_LEFT, AInput::Key::LEFT},
        {SDLK_RIGHT, AInput::Key::RIGHT},
        {SDLK_DOWN, AInput::Key::DOWN},
        {SDLK_UP, AInput::Key::UP},
        {SDLK_BACKSPACE, AInput::Key::BACKSPACE},
    };
    auto it = mapping.find(static_cast<SDL_Keycode>(key));
    if (it != mapping.end()) {
        return it->second;
    }
    return AInput::UNKNOWN;
}

Purpose: Converts SDL3 key code constants to AUI's AInput::Key format.

sdlToAPointer()#

static auto sdlToAPointer(Uint8 button) -> APointerIndex {
    switch (button) {
        case SDL_BUTTON_LEFT:
            return APointerIndex::button(AInput::LBUTTON);
        case SDL_BUTTON_MIDDLE:
            return APointerIndex::button(AInput::CBUTTON);
        case SDL_BUTTON_RIGHT:
            return APointerIndex::button(AInput::RBUTTON);
        default:
            return {};
    }
}

Purpose: Converts SDL3 mouse button constants to AUI's pointer index format.

Mappings:

  • SDL_BUTTON_LEFT → Left mouse button
  • SDL_BUTTON_MIDDLE → Middle mouse button
  • SDL_BUTTON_RIGHT → Right mouse button

handleSDLEvent()#

void handleSDLEvent(SDL_Event* event, EmbedWindow& window) {
    switch (event->type) {
        case SDL_EVENT_QUIT:
            window.close = true;
            break;
        case SDL_EVENT_WINDOW_RESIZED: {
            int width = 0;
            int height = 0;
            SDL_GetWindowSizeInPixels(window.sdl_window, &width, &height);
            window.setViewportSize(width, height);
        } break;
        case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED: {
            int width = 0;
            int height = 0;
            SDL_GetWindowSizeInPixels(window.sdl_window, &width, &height);
            window.setViewportSize(width, height);
        } break;
        case SDL_EVENT_WINDOW_DISPLAY_CHANGED:
            break;
        case SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED: {
            window.setCustomDpiRatio(SDL_GetWindowDisplayScale(window.sdl_window));
        } break;
        case SDL_EVENT_KEY_DOWN:
            window.onKeyPressed(sdlToAInput(event->key.key));
            break;
        case SDL_EVENT_KEY_UP:
            window.onKeyReleased(sdlToAInput(event->key.key));
            break;
        case SDL_EVENT_TEXT_INPUT: {
            std::string_view text(event->text.text);
            AUtf8ConstIterator it(text);
            AUtf8ConstIterator it_end(text, text.size());
            for (; it != it_end; ++it) {
                const AChar ch = *it;
                window.onCharEntered(ch);
            }
        } break;
        case SDL_EVENT_MOUSE_MOTION: {
#if AUI_PLATFORM_LINUX
            float display_scale = SDL_GetWindowDisplayScale(window.sdl_window);
            window.onPointerMove(event->motion.x * display_scale, event->motion.y * display_scale);
#else
            window.onPointerMove(event->motion.x, event->motion.y);
#endif
        } break;
        case SDL_EVENT_MOUSE_BUTTON_DOWN: {
#if AUI_PLATFORM_LINUX
            float display_scale = SDL_GetWindowDisplayScale(window.sdl_window);
            window.onPointerPressed(event->button.x * display_scale, event->button.y * display_scale, sdlToAPointer(event->button.button));
#else
            window.onPointerPressed(event->button.x, event->button.y, sdlToAPointer(event->button.button));
#endif
        } break;
        case SDL_EVENT_MOUSE_BUTTON_UP: {
#if AUI_PLATFORM_LINUX
            float display_scale = SDL_GetWindowDisplayScale(window.sdl_window);
            window.onPointerReleased(event->button.x * display_scale, event->button.y * display_scale, sdlToAPointer(event->button.button));
#else
            window.onPointerPressed(event->button.x, event->button.y, sdlToAPointer(event->button.button));
#endif
        } break;
        case SDL_EVENT_MOUSE_WHEEL:
            window.onScroll(event->wheel.mouse_x, event->wheel.mouse_y, event->wheel.x, event->wheel.y);
            break;
        default: break;
    }
}

Purpose: Central event dispatcher that processes all SDL events and forwards them to the AUI window.

Handled events:

  • SDL_EVENT_QUIT: Sets the close flag to terminate the application
  • SDL_EVENT_WINDOW_RESIZED / SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED: Updates the viewport size when window dimensions change
  • SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED: Updates DPI ratio when the window moves between displays with different scaling
  • SDL_EVENT_MOUSE_MOTION: Forwards mouse movement to AUI's pointer tracking
  • SDL_EVENT_MOUSE_BUTTON_DOWN/UP: Forwards mouse clicks with proper button mapping
  • SDL_EVENT_MOUSE_WHEEL: Forwards scroll wheel events with position and scroll delta
  • SDL_EVENT_KEY_DOWN/UP/TEXT_INPUT: Forwards keyboard input to ASurface

Main Application (AUI_ENTRY)#

SDL3 Initialization#

1
2
3
4
if (!SDL_Init(SDL_INIT_VIDEO)) {
    ALogger::err("SDL3") << SDL_GetError();
    return 1;
}

Purpose: Initializes SDL3's video subsystem. This must be called before any other SDL functions. If initialization fails, the error is logged and the application exits.


Window Creation#

1
2
3
4
5
6
EmbedWindow window;
window.sdl_window = SDL_CreateWindow("AUI + SDL3", 600, 400, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY);
if (!window.sdl_window) {
    ALogger::err("SDL3") << SDL_GetError();
    return 1;
}

Purpose: Creates the main window with the following properties:

  • Title: "AUI + SDL3"
  • Initial size: 600×400 pixels
  • SDL_WINDOW_OPENGL: Enables OpenGL rendering
  • SDL_WINDOW_RESIZABLE: Allows the user to resize the window
  • SDL_WINDOW_HIGH_PIXEL_DENSITY: Supports high-DPI displays (Retina, 4K, etc.)

OpenGL Context Setup#

1
2
3
4
5
6
window.gl_context = SDL_GL_CreateContext(window.sdl_window);
if (!window.gl_context) {
    ALogger::err("SDL3") << SDL_GetError();
    return 1;
}
SDL_GL_MakeCurrent(window.sdl_window, window.gl_context);

Purpose: Creates an OpenGL context and makes it current for rendering operations. This is necessary before any OpenGL calls can be made.


Renderer Setup#

if (!OpenGLRenderer::loadGL((OpenGLRenderer::GLLoadProc)SDL_GL_GetProcAddress)) {
    ALogger::err("OpenGLRenderer") << "Failed to load GL";
    return 1;
}

auto renderer = std::make_shared<OpenGLRenderer>();
{
    auto rendering_context = std::make_unique<EmbedRenderingContext>();
    rendering_context->m_renderer = renderer;
    window.init(std::move(rendering_context));
}

Purpose: Creates the rendering pipeline by: 1. Loads OpenGL function pointers using SDL's procedure address function. 2. Instantiating the OpenGL renderer 3. Creating the rendering context wrapper 4. Linking them together 5. Initializing the AUI window with this rendering setup


UI Content Declaration#

window.setContainer(Centered {
    Vertical {
        Label { "Hello, World!" },
        _new<ATextField>(),
        Button {
            .content = Label { "Click me" },
            .onClick = [] {
                ALogger::info("Test") << "Hello world!";
            }
        }
    }
});

Purpose: Defines the UI layout using AUI's declarative syntax:

  • Centered: Centers the content in the window
  • Vertical: Arranges children vertically
  • Label: Displays "Hello, World!" text
  • Button: Creates a clickable button with:
    • Label saying "Click me"
    • onClick handler that logs "Hello world!" when clicked

This is similar to modern declarative UI frameworks like SwiftUI or React.


Main Event Loop#

while (!window.close) {
    /// [EventProcessing]
    SDL_Event event;
    while (SDL_PollEvent(&event)) {
        handleSDLEvent(&event, window);
    }
    AThread::processMessages();
    /// [EventProcessing]

    /// [Rendering]
    if (window.requiresRedraw()) {
        ARenderContext render_context {
            .clippingRects = {},
            .render = *renderer,
        };
        window.render(render_context);
        SDL_GL_SwapWindow(window.sdl_window);
    }
    /// [Rendering]

    /// [FrameRateLimit]
    const SDL_DisplayMode* dm = SDL_GetCurrentDisplayMode(SDL_GetDisplayForWindow(window.sdl_window));
    Sint32 refresh_ms = static_cast<Sint32>(1000.0f / dm->refresh_rate);
    SDL_Delay(refresh_ms);
    /// [FrameRateLimit]
}

Event Processing#

1
2
3
4
5
SDL_Event event;
while (SDL_PollEvent(&event)) {
    handleSDLEvent(&event, window);
}
AThread::processMessages();

Purpose: Polls and processes all pending SDL events (mouse, keyboard, window events) in the queue. Non-blocking - returns immediately if no events are available.


Rendering#

1
2
3
4
5
6
7
8
if (window.requiresRedraw()) {
    ARenderContext render_context {
        .clippingRects = {},
        .render = *renderer,
    };
    window.render(render_context);
    SDL_GL_SwapWindow(window.sdl_window);
}

Purpose: Conditionally renders the UI only when needed (dirty flag system): 1. requiresRedraw(): Checks if the UI needs updating (e.g., after user interaction or animation) 2. ARenderContext: Creates a rendering context with no clipping and the OpenGL renderer 3. window.render(): Executes the actual drawing operations 4. SDL_GL_SwapWindow(): Swaps the front and back buffers to display the newly rendered frame (double buffering)


Frame Rate Limiting#

1
2
3
const SDL_DisplayMode* dm = SDL_GetCurrentDisplayMode(SDL_GetDisplayForWindow(window.sdl_window));
Sint32 refresh_ms = static_cast<Sint32>(1000.0f / dm->refresh_rate);
SDL_Delay(refresh_ms);

Purpose: Limits the frame rate to match the display's refresh rate to:

  • Prevent unnecessary CPU/GPU usage
  • Synchronize with the monitor's refresh cycle
  • Save battery on laptops
  • Reduce heat generation

How it works: 1. Gets the display mode for the monitor showing the window 2. Calculates delay time based on refresh rate (e.g., 60Hz → ~16ms delay) 3. Sleeps the thread for that duration

Example: For a 60Hz display: 1000ms / 60 = ~16.67ms delay per frame

Source Code#

Repository

CMakeLists.txt#

set(AUIB_SDL3_VALIDATE OFF)
auib_import(SDL3 https://github.com/libsdl-org/SDL VERSION release-3.2.26)

aui_executable(aui.example.embedded_sdl)
aui_link(aui.example.embedded_sdl PRIVATE aui::core aui::views)

if (TARGET SDL3::SDL3-static)
    aui_link(aui.example.embedded_sdl PRIVATE SDL3::SDL3-static)
elseif (TARGET SDL3::SDL3-shared)
    aui_link(aui.example.embedded_sdl PRIVATE SDL3::SDL3-shared)
endif ()

src/main.cpp#

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
#include <AUI/Common/AUtf8.hpp>
#include <AUI/Logging/ALogger.h>
#include <AUI/Platform/Entry.h>
#include <AUI/Platform/APlatform.h>
#include <AUI/Platform/AGLEmbedContext.h>
#include <AUI/GL/OpenGLRenderer.h>
#include <AUI/Util/Declarative/Containers.h>
#include <AUI/View/AButton.h>
#include <AUI/View/ATextField.h>
#include <AUI/Thread/AEventLoop.h>
#include <SDL3/SDL.h>
#include <unordered_map>

/// [sdlToAInput]
static auto sdlToAInput(Uint32 key) -> AInput::Key {
    static const std::unordered_map<SDL_Keycode, AInput::Key> mapping = {
        {SDLK_A, AInput::Key::A},
        {SDLK_B, AInput::Key::B},
        {SDLK_C, AInput::Key::C},
        {SDLK_D, AInput::Key::D},
        {SDLK_E, AInput::Key::E},
        {SDLK_F, AInput::Key::F},
        {SDLK_G, AInput::Key::G},
        {SDLK_H, AInput::Key::H},
        {SDLK_I, AInput::Key::I},
        {SDLK_J, AInput::Key::J},
        {SDLK_K, AInput::Key::K},
        {SDLK_L, AInput::Key::L},
        {SDLK_M, AInput::Key::M},
        {SDLK_N, AInput::Key::N},
        {SDLK_O, AInput::Key::O},
        {SDLK_P, AInput::Key::P},
        {SDLK_Q, AInput::Key::Q},
        {SDLK_R, AInput::Key::R},
        {SDLK_S, AInput::Key::S},
        {SDLK_T, AInput::Key::T},
        {SDLK_U, AInput::Key::U},
        {SDLK_V, AInput::Key::V},
        {SDLK_W, AInput::Key::W},
        {SDLK_X, AInput::Key::X},
        {SDLK_Y, AInput::Key::Y},
        {SDLK_Z, AInput::Key::Z},

        {SDLK_0, AInput::Key::NUM0},
        {SDLK_1, AInput::Key::NUM1},
        {SDLK_2, AInput::Key::NUM2},
        {SDLK_3, AInput::Key::NUM3},
        {SDLK_4, AInput::Key::NUM4},
        {SDLK_5, AInput::Key::NUM5},
        {SDLK_6, AInput::Key::NUM6},
        {SDLK_7, AInput::Key::NUM7},
        {SDLK_8, AInput::Key::NUM8},
        {SDLK_9, AInput::Key::NUM9},

        {SDLK_F1, AInput::Key::F1},
        {SDLK_F2, AInput::Key::F2},
        {SDLK_F3, AInput::Key::F3},
        {SDLK_F4, AInput::Key::F4},
        {SDLK_F5, AInput::Key::F5},
        {SDLK_F6, AInput::Key::F6},
        {SDLK_F7, AInput::Key::F7},
        {SDLK_F8, AInput::Key::F8},
        {SDLK_F9, AInput::Key::F9},
        {SDLK_F10, AInput::Key::F10},
        {SDLK_F11, AInput::Key::F11},
        {SDLK_F12, AInput::Key::F12},
        {SDLK_F13, AInput::Key::F13},
        {SDLK_F14, AInput::Key::F14},
        {SDLK_F15, AInput::Key::F15},
        {SDLK_F16, AInput::Key::F16},
        {SDLK_F17, AInput::Key::F17},
        {SDLK_F18, AInput::Key::F18},
        {SDLK_F19, AInput::Key::F19},

        {SDLK_EQUALS, AInput::Key::EQUAL},
        {SDLK_MINUS, AInput::Key::DASH},
        {SDLK_RIGHTBRACKET, AInput::Key::RBRACKET},
        {SDLK_LEFTBRACKET, AInput::Key::LBRACKET},
        {SDLK_APOSTROPHE, AInput::Key::QUOTE},
        {SDLK_SEMICOLON, AInput::Key::SEMICOLON},
        {SDLK_BACKSLASH, AInput::Key::BACKSLASH},
        {SDLK_COMMA, AInput::Key::COMMA},
        {SDLK_SLASH, AInput::Key::SLASH},
        {SDLK_PERIOD, AInput::Key::PERIOD},
        {SDLK_GRAVE, AInput::Key::TILDE},

        {SDLK_KP_DECIMAL, AInput::Key::NUMPAD_PERIOD},
        {SDLK_KP_MULTIPLY, AInput::Key::NUMPAD_MULTIPLY},
        {SDLK_KP_PLUS, AInput::Key::NUMPAD_ADD},
        {SDLK_CLEAR, AInput::Key::NUMLOCKCLEAR},
        {SDLK_KP_DIVIDE, AInput::Key::NUMPAD_DIVIDE},
        {SDLK_KP_ENTER, AInput::Key::NUMPAD_RETURN},
        {SDLK_KP_MINUS, AInput::Key::NUMPAD_SUBTRACT},
        {SDLK_KP_EQUALS, AInput::Key::NUMPAD_EQUAL},
        {SDLK_KP_0, AInput::Key::NUMPAD_0},
        {SDLK_KP_1, AInput::Key::NUMPAD_1},
        {SDLK_KP_2, AInput::Key::NUMPAD_2},
        {SDLK_KP_3, AInput::Key::NUMPAD_3},
        {SDLK_KP_4, AInput::Key::NUMPAD_4},
        {SDLK_KP_5, AInput::Key::NUMPAD_5},
        {SDLK_KP_6, AInput::Key::NUMPAD_6},
        {SDLK_KP_7, AInput::Key::NUMPAD_7},
        {SDLK_KP_8, AInput::Key::NUMPAD_8},
        {SDLK_KP_9, AInput::Key::NUMPAD_9},

        {SDLK_RETURN, AInput::Key::RETURN},
        {SDLK_TAB, AInput::Key::TAB},
        {SDLK_SPACE, AInput::Key::SPACE},
        {SDLK_DELETE, AInput::Key::DEL},
        {SDLK_ESCAPE, AInput::Key::ESCAPE},
        {SDLK_LGUI, AInput::Key::LSYSTEM},
        {SDLK_LSHIFT, AInput::Key::LSHIFT},
        {SDLK_CAPSLOCK, AInput::Key::CAPSLOCK},
        {SDLK_LALT, AInput::Key::LALT},
        {SDLK_LCTRL, AInput::Key::LCONTROL},
        {SDLK_RGUI, AInput::Key::RSYSTEM},
        {SDLK_RSHIFT, AInput::Key::RSHIFT},
        {SDLK_RALT, AInput::Key::RALT},
        {SDLK_RCTRL, AInput::Key::RCONTROL},
        {SDLK_VOLUMEUP, AInput::Key::VOLUMEUP},
        {SDLK_VOLUMEDOWN, AInput::Key::VOLUMEDOWN},
        {SDLK_MUTE, AInput::Key::MUTE},
        {SDLK_MENU, AInput::Key::MENU},
        {SDLK_HELP, AInput::Key::INSERT},
        {SDLK_HOME, AInput::Key::HOME},
        {SDLK_PAGEUP, AInput::Key::PAGEUP},
        {SDLK_END, AInput::Key::END},
        {SDLK_PAGEDOWN, AInput::Key::PAGEDOWN},
        {SDLK_LEFT, AInput::Key::LEFT},
        {SDLK_RIGHT, AInput::Key::RIGHT},
        {SDLK_DOWN, AInput::Key::DOWN},
        {SDLK_UP, AInput::Key::UP},
        {SDLK_BACKSPACE, AInput::Key::BACKSPACE},
    };
    auto it = mapping.find(static_cast<SDL_Keycode>(key));
    if (it != mapping.end()) {
        return it->second;
    }
    return AInput::UNKNOWN;
}
/// [sdlToAInput]

/// [sdlToAPointer]
static auto sdlToAPointer(Uint8 button) -> APointerIndex {
    switch (button) {
        case SDL_BUTTON_LEFT:
            return APointerIndex::button(AInput::LBUTTON);
        case SDL_BUTTON_MIDDLE:
            return APointerIndex::button(AInput::CBUTTON);
        case SDL_BUTTON_RIGHT:
            return APointerIndex::button(AInput::RBUTTON);
        default:
            return {};
    }
}
/// [sdlToAPointer]

class APlatformSDL : public APlatform {
public:
    ~APlatformSDL() override = default;

    void setClipboardText(const AString& text) override {
        SDL_SetClipboardText(text.c_str());
    }
    AString getClipboardText() override {
        return SDL_GetClipboardText();
    }

    AMessageBox::ResultButton messageBoxShow(
        AWindow* parent, const AString& title, const AString& message, AMessageBox::Icon icon,
        AMessageBox::Button b) override {}
};

/// [EmbedRenderingContext]
struct EmbedRenderingContext : IRenderingContext {
    std::shared_ptr<OpenGLRenderer> m_renderer;

    ~EmbedRenderingContext() override {
    }

    void destroyNativeWindow(ASurface& window) override {}

    AImage makeScreenshot() override {
        return {};
    }

    void beginPaint(ASurface& window) override {
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
        glViewport(0, 0, window.getSize().x, window.getSize().y);
        m_renderer->beginPaint(window.getSize());
        glClearColor(1, 1, 1, 1);
        glClear(GL_COLOR_BUFFER_BIT);
    }
    void endPaint(ASurface& window) override {
        m_renderer->endPaint();
    }

    void beginResize(ASurface& window) override {
    }
    void endResize(ASurface& window) override {
    }

    IRenderer& renderer() override {
        return *m_renderer;
    }
};
/// [EmbedRenderingContext]

/// [EmbedWindow]
struct EmbedWindow : AGLEmbedContext, AObject {
    SDL_Window* sdl_window = nullptr;
    SDL_GLContext gl_context = nullptr;
    bool close = false;

    ~EmbedWindow() override {
        SDL_GL_DestroyContext(gl_context);
        SDL_DestroyWindow(sdl_window);
    }

    void init(std::unique_ptr<EmbedRenderingContext>&& context) {
        windowInit(std::move(context));

        int width = 0;
        int height = 0;
        SDL_GetWindowSizeInPixels(sdl_window, &width, &height);
        setViewportSize(width, height);

        setCustomDpiRatio(SDL_GetWindowDisplayScale(sdl_window));

        connect(getWindow()->touchscreenKeyboardShown, this, [this] {
            glm::ivec2 pos = getWindow()->getFocusedView()->getPositionInWindow();
            glm::ivec2 size = getWindow()->getFocusedView()->getSize();
            SDL_Rect rect;
            rect.x = pos.x;
            rect.y = pos.y;
            rect.w = size.x;
            rect.h = size.y;
            SDL_SetTextInputArea(sdl_window, &rect, 0);
            SDL_StartTextInput(sdl_window);
        });
        connect(getWindow()->touchscreenKeyboardHidden, this, [this] {
            SDL_StopTextInput(sdl_window);
        });
    }

    void onNotifyProcessMessages() override {}
};
/// [EmbedWindow]

/// [handleSDLEvent]
void handleSDLEvent(SDL_Event* event, EmbedWindow& window) {
    switch (event->type) {
        case SDL_EVENT_QUIT:
            window.close = true;
            break;
        case SDL_EVENT_WINDOW_RESIZED: {
            int width = 0;
            int height = 0;
            SDL_GetWindowSizeInPixels(window.sdl_window, &width, &height);
            window.setViewportSize(width, height);
        } break;
        case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED: {
            int width = 0;
            int height = 0;
            SDL_GetWindowSizeInPixels(window.sdl_window, &width, &height);
            window.setViewportSize(width, height);
        } break;
        case SDL_EVENT_WINDOW_DISPLAY_CHANGED:
            break;
        case SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED: {
            window.setCustomDpiRatio(SDL_GetWindowDisplayScale(window.sdl_window));
        } break;
        case SDL_EVENT_KEY_DOWN:
            window.onKeyPressed(sdlToAInput(event->key.key));
            break;
        case SDL_EVENT_KEY_UP:
            window.onKeyReleased(sdlToAInput(event->key.key));
            break;
        case SDL_EVENT_TEXT_INPUT: {
            std::string_view text(event->text.text);
            AUtf8ConstIterator it(text);
            AUtf8ConstIterator it_end(text, text.size());
            for (; it != it_end; ++it) {
                const AChar ch = *it;
                window.onCharEntered(ch);
            }
        } break;
        case SDL_EVENT_MOUSE_MOTION: {
#if AUI_PLATFORM_LINUX
            float display_scale = SDL_GetWindowDisplayScale(window.sdl_window);
            window.onPointerMove(event->motion.x * display_scale, event->motion.y * display_scale);
#else
            window.onPointerMove(event->motion.x, event->motion.y);
#endif
        } break;
        case SDL_EVENT_MOUSE_BUTTON_DOWN: {
#if AUI_PLATFORM_LINUX
            float display_scale = SDL_GetWindowDisplayScale(window.sdl_window);
            window.onPointerPressed(event->button.x * display_scale, event->button.y * display_scale, sdlToAPointer(event->button.button));
#else
            window.onPointerPressed(event->button.x, event->button.y, sdlToAPointer(event->button.button));
#endif
        } break;
        case SDL_EVENT_MOUSE_BUTTON_UP: {
#if AUI_PLATFORM_LINUX
            float display_scale = SDL_GetWindowDisplayScale(window.sdl_window);
            window.onPointerReleased(event->button.x * display_scale, event->button.y * display_scale, sdlToAPointer(event->button.button));
#else
            window.onPointerPressed(event->button.x, event->button.y, sdlToAPointer(event->button.button));
#endif
        } break;
        case SDL_EVENT_MOUSE_WHEEL:
            window.onScroll(event->wheel.mouse_x, event->wheel.mouse_y, event->wheel.x, event->wheel.y);
            break;
        default: break;
    }
}
/// [handleSDLEvent]

using namespace declarative;

AUI_ENTRY {
    /// [SDL_Init]
    if (!SDL_Init(SDL_INIT_VIDEO)) {
        ALogger::err("SDL3") << SDL_GetError();
        return 1;
    }
    /// [SDL_Init]

    /// [CreateWindow]
    EmbedWindow window;
    window.sdl_window = SDL_CreateWindow("AUI + SDL3", 600, 400, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY);
    if (!window.sdl_window) {
        ALogger::err("SDL3") << SDL_GetError();
        return 1;
    }
    /// [CreateWindow]

    /// [GLContext]
    window.gl_context = SDL_GL_CreateContext(window.sdl_window);
    if (!window.gl_context) {
        ALogger::err("SDL3") << SDL_GetError();
        return 1;
    }
    SDL_GL_MakeCurrent(window.sdl_window, window.gl_context);
    /// [GLContext]

    /// [APlatformInit]
    APlatform::init(std::make_unique<APlatformSDL>());
    /// [APlatformInit]

    /// [RendererSetup]
    if (!OpenGLRenderer::loadGL((OpenGLRenderer::GLLoadProc)SDL_GL_GetProcAddress)) {
        ALogger::err("OpenGLRenderer") << "Failed to load GL";
        return 1;
    }

    auto renderer = std::make_shared<OpenGLRenderer>();
    {
        auto rendering_context = std::make_unique<EmbedRenderingContext>();
        rendering_context->m_renderer = renderer;
        window.init(std::move(rendering_context));
    }
    /// [RendererSetup]

    /// [UI]
    window.setContainer(Centered {
        Vertical {
            Label { "Hello, World!" },
            _new<ATextField>(),
            Button {
                .content = Label { "Click me" },
                .onClick = [] {
                    ALogger::info("Test") << "Hello world!";
                }
            }
        }
    });
    /// [UI]

    /// [MainLoop]
    while (!window.close) {
        /// [EventProcessing]
        SDL_Event event;
        while (SDL_PollEvent(&event)) {
            handleSDLEvent(&event, window);
        }
        AThread::processMessages();
        /// [EventProcessing]

        /// [Rendering]
        if (window.requiresRedraw()) {
            ARenderContext render_context {
                .clippingRects = {},
                .render = *renderer,
            };
            window.render(render_context);
            SDL_GL_SwapWindow(window.sdl_window);
        }
        /// [Rendering]

        /// [FrameRateLimit]
        const SDL_DisplayMode* dm = SDL_GetCurrentDisplayMode(SDL_GetDisplayForWindow(window.sdl_window));
        Sint32 refresh_ms = static_cast<Sint32>(1000.0f / dm->refresh_rate);
        SDL_Delay(refresh_ms);
        /// [FrameRateLimit]
    }
    /// [MainLoop]

    return 0;
}