diff options
author | WlodekM <[email protected]> | 2024-06-16 10:35:45 +0300 |
---|---|---|
committer | WlodekM <[email protected]> | 2024-06-16 10:35:45 +0300 |
commit | abef6da56913f1c55528103e60a50451a39628b1 (patch) | |
tree | b3c8092471ecbb73e568cd0d336efa0e7871ee8d /src/Window_SDL3.c |
initial commit
Diffstat (limited to 'src/Window_SDL3.c')
-rw-r--r-- | src/Window_SDL3.c | 541 |
1 files changed, 541 insertions, 0 deletions
diff --git a/src/Window_SDL3.c b/src/Window_SDL3.c new file mode 100644 index 0000000..25f7ea0 --- /dev/null +++ b/src/Window_SDL3.c @@ -0,0 +1,541 @@ +#include "Core.h" +#if CC_WIN_BACKEND == CC_WIN_BACKEND_SDL3 +#include "_WindowBase.h" +#include "Graphics.h" +#include "String.h" +#include "Funcs.h" +#include "Bitmap.h" +#include "Errors.h" +#include <SDL3/SDL.h> +static SDL_Window* win_handle; +static Uint32 dlg_event; + +static void RefreshWindowBounds(void) { + SDL_GetWindowSize(win_handle, &Window_Main.Width, &Window_Main.Height); +} + +static void Window_SDLFail(const char* place) { + char strBuffer[256]; + cc_string str; + String_InitArray_NT(str, strBuffer); + + String_Format2(&str, "Error when %c: %c", place, SDL_GetError()); + str.buffer[str.length] = '\0'; + Logger_Abort(str.buffer); +} + +void Window_PreInit(void) { + SDL_Init(SDL_INIT_VIDEO); + #ifdef CC_BUILD_FLATPAK + SDL_SetHint(SDL_HINT_APP_ID, "net.classicube.flatpak.client"); + #endif +} + +void Window_Init(void) { + int displayID = SDL_GetPrimaryDisplay(); + Input.Sources = INPUT_SOURCE_NORMAL; + + const SDL_DisplayMode* mode = SDL_GetDesktopDisplayMode(displayID); + dlg_event = SDL_RegisterEvents(1); + + DisplayInfo.Width = mode->w; + DisplayInfo.Height = mode->h; + DisplayInfo.Depth = SDL_BITSPERPIXEL(mode->format); + DisplayInfo.ScaleX = 1; + DisplayInfo.ScaleY = 1; +} + +void Window_Free(void) { } + +#ifdef CC_BUILD_ICON +/* See misc/sdl/sdl_icon_gen.cs for how to generate this file */ +#include "../misc/sdl/CCIcon_SDL.h" + +static void ApplyIcon(void) { + SDL_Surface* surface = SDL_CreateSurfaceFrom(CCIcon_Data, CCIcon_Width, CCIcon_Height, + CCIcon_Pitch, SDL_PIXELFORMAT_BGRA8888); + SDL_SetWindowIcon(win_handle, surface); +} +#else +static void ApplyIcon(void) { } +#endif + +static void DoCreateWindow(int width, int height, int flags) { + SDL_PropertiesID props = SDL_CreateProperties(); + SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, SDL_WINDOWPOS_CENTERED); + SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, SDL_WINDOWPOS_CENTERED); + SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, width); + SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, height); + SDL_SetNumberProperty(props, "flags", flags | SDL_WINDOW_RESIZABLE); + + win_handle = SDL_CreateWindowWithProperties(props); + if (!win_handle) Window_SDLFail("creating window"); + SDL_DestroyProperties(props); + + RefreshWindowBounds(); + Window_Main.Exists = true; + Window_Main.Handle = win_handle; + ApplyIcon(); + /* TODO grab using SDL_SetWindowGrab? seems to be unnecessary on Linux at least */ +} + +void Window_Create2D(int width, int height) { DoCreateWindow(width, height, 0); } +#if (CC_GFX_BACKEND & CC_GFX_BACKEND_GL_MASK) +void Window_Create3D(int width, int height) { DoCreateWindow(width, height, SDL_WINDOW_OPENGL); } +#else +void Window_Create3D(int width, int height) { DoCreateWindow(width, height, 0); } +#endif + +void Window_SetTitle(const cc_string* title) { + char str[NATIVE_STR_LEN]; + String_EncodeUtf8(str, title); + SDL_SetWindowTitle(win_handle, str); +} + +void Clipboard_GetText(cc_string* value) { + char* ptr = SDL_GetClipboardText(); + if (!ptr) return; + + int len = String_Length(ptr); + String_AppendUtf8(value, ptr, len); + SDL_free(ptr); +} + +void Clipboard_SetText(const cc_string* value) { + char str[NATIVE_STR_LEN]; + String_EncodeUtf8(str, value); + SDL_SetClipboardText(str); +} + +int Window_GetWindowState(void) { + Uint32 flags = SDL_GetWindowFlags(win_handle); + + if (flags & SDL_WINDOW_MINIMIZED) return WINDOW_STATE_MINIMISED; + if (flags & SDL_WINDOW_FULLSCREEN) return WINDOW_STATE_FULLSCREEN; + return WINDOW_STATE_NORMAL; +} + +cc_result Window_EnterFullscreen(void) { + return SDL_SetWindowFullscreen(win_handle, true); +} +cc_result Window_ExitFullscreen(void) { + return SDL_SetWindowFullscreen(win_handle, false); +} + +int Window_IsObscured(void) { + Uint32 flags = SDL_GetWindowFlags(win_handle); + return flags & SDL_WINDOW_OCCLUDED; +} + +void Window_Show(void) { + SDL_ShowWindow(win_handle); +} + +void Window_SetSize(int width, int height) { + SDL_SetWindowSize(win_handle, width, height); +} + +void Window_RequestClose(void) { + SDL_Event e; + e.type = SDL_EVENT_QUIT; + SDL_PushEvent(&e); +} + +static int MapNativeKey(SDL_Keycode k) { + if (k >= SDLK_0 && k <= SDLK_9) { return '0' + (k - SDLK_0); } + if (k >= SDLK_a && k <= SDLK_z) { return 'A' + (k - SDLK_a); } + if (k >= SDLK_F1 && k <= SDLK_F12) { return CCKEY_F1 + (k - SDLK_F1); } + if (k >= SDLK_F13 && k <= SDLK_F24) { return CCKEY_F13 + (k - SDLK_F13); } + /* SDLK_KP_0 isn't before SDLK_KP_1 */ + if (k >= SDLK_KP_1 && k <= SDLK_KP_9) { return CCKEY_KP1 + (k - SDLK_KP_1); } + + switch (k) { + case SDLK_RETURN: return CCKEY_ENTER; + case SDLK_ESCAPE: return CCKEY_ESCAPE; + case SDLK_BACKSPACE: return CCKEY_BACKSPACE; + case SDLK_TAB: return CCKEY_TAB; + case SDLK_SPACE: return CCKEY_SPACE; + case SDLK_APOSTROPHE: return CCKEY_QUOTE; + case SDLK_EQUALS: return CCKEY_EQUALS; + case SDLK_COMMA: return CCKEY_COMMA; + case SDLK_MINUS: return CCKEY_MINUS; + case SDLK_PERIOD: return CCKEY_PERIOD; + case SDLK_SLASH: return CCKEY_SLASH; + case SDLK_SEMICOLON: return CCKEY_SEMICOLON; + case SDLK_LEFTBRACKET: return CCKEY_LBRACKET; + case SDLK_BACKSLASH: return CCKEY_BACKSLASH; + case SDLK_RIGHTBRACKET: return CCKEY_RBRACKET; + case SDLK_GRAVE: return CCKEY_TILDE; + case SDLK_CAPSLOCK: return CCKEY_CAPSLOCK; + case SDLK_PRINTSCREEN: return CCKEY_PRINTSCREEN; + case SDLK_SCROLLLOCK: return CCKEY_SCROLLLOCK; + case SDLK_PAUSE: return CCKEY_PAUSE; + case SDLK_INSERT: return CCKEY_INSERT; + case SDLK_HOME: return CCKEY_HOME; + case SDLK_PAGEUP: return CCKEY_PAGEUP; + case SDLK_DELETE: return CCKEY_DELETE; + case SDLK_END: return CCKEY_END; + case SDLK_PAGEDOWN: return CCKEY_PAGEDOWN; + case SDLK_RIGHT: return CCKEY_RIGHT; + case SDLK_LEFT: return CCKEY_LEFT; + case SDLK_DOWN: return CCKEY_DOWN; + case SDLK_UP: return CCKEY_UP; + + case SDLK_NUMLOCKCLEAR: return CCKEY_NUMLOCK; + case SDLK_KP_DIVIDE: return CCKEY_KP_DIVIDE; + case SDLK_KP_MULTIPLY: return CCKEY_KP_MULTIPLY; + case SDLK_KP_MINUS: return CCKEY_KP_MINUS; + case SDLK_KP_PLUS: return CCKEY_KP_PLUS; + case SDLK_KP_ENTER: return CCKEY_KP_ENTER; + case SDLK_KP_0: return CCKEY_KP0; + case SDLK_KP_PERIOD: return CCKEY_KP_DECIMAL; + + case SDLK_LCTRL: return CCKEY_LCTRL; + case SDLK_LSHIFT: return CCKEY_LSHIFT; + case SDLK_LALT: return CCKEY_LALT; + case SDLK_LGUI: return CCKEY_LWIN; + case SDLK_RCTRL: return CCKEY_RCTRL; + case SDLK_RSHIFT: return CCKEY_RSHIFT; + case SDLK_RALT: return CCKEY_RALT; + case SDLK_RGUI: return CCKEY_RWIN; + + case SDLK_AUDIONEXT: return CCKEY_MEDIA_NEXT; + case SDLK_AUDIOPREV: return CCKEY_MEDIA_PREV; + case SDLK_AUDIOPLAY: return CCKEY_MEDIA_PLAY; + case SDLK_AUDIOSTOP: return CCKEY_MEDIA_STOP; + + case SDLK_AUDIOMUTE: return CCKEY_VOLUME_MUTE; + case SDLK_VOLUMEDOWN: return CCKEY_VOLUME_DOWN; + case SDLK_VOLUMEUP: return CCKEY_VOLUME_UP; + } + return INPUT_NONE; +} + +static void OnKeyEvent(const SDL_Event* e) { + cc_bool pressed = e->key.state == SDL_PRESSED; + int key = MapNativeKey(e->key.keysym.sym); + if (key) Input_Set(key, pressed); +} + +static void OnMouseEvent(const SDL_Event* e) { + cc_bool pressed = e->button.state == SDL_PRESSED; + int btn; + switch (e->button.button) { + case SDL_BUTTON_LEFT: btn = CCMOUSE_L; break; + case SDL_BUTTON_MIDDLE: btn = CCMOUSE_M; break; + case SDL_BUTTON_RIGHT: btn = CCMOUSE_R; break; + case SDL_BUTTON_X1: btn = CCMOUSE_X1; break; + case SDL_BUTTON_X2: btn = CCMOUSE_X2; break; + default: return; + } + Input_Set(btn, pressed); +} + +static void OnTextEvent(const SDL_Event* e) { + cc_codepoint cp; + const char* src; + int i, len; + + src = e->text.text; + len = String_Length(src); + + while (len > 0) { + i = Convert_Utf8ToCodepoint(&cp, src, len); + if (!i) break; + + Event_RaiseInt(&InputEvents.Press, cp); + src += i; len -= i; + } +} +static void ProcessDialogEvent(SDL_Event* e); + +void Window_ProcessEvents(float delta) { + SDL_Event e; + while (SDL_PollEvent(&e)) { + switch (e.type) { + + case SDL_EVENT_KEY_DOWN: + case SDL_EVENT_KEY_UP: + OnKeyEvent(&e); break; + case SDL_EVENT_MOUSE_BUTTON_DOWN: + case SDL_EVENT_MOUSE_BUTTON_UP: + OnMouseEvent(&e); break; + case SDL_EVENT_MOUSE_WHEEL: + Mouse_ScrollHWheel(e.wheel.x); + Mouse_ScrollVWheel(e.wheel.y); + break; + case SDL_EVENT_MOUSE_MOTION: + Pointer_SetPosition(0, e.motion.x, e.motion.y); + if (Input.RawMode) Event_RaiseRawMove(&PointerEvents.RawMoved, e.motion.xrel, e.motion.yrel); + break; + case SDL_EVENT_TEXT_INPUT: + OnTextEvent(&e); break; + + case SDL_EVENT_QUIT: + Window_Main.Exists = false; + Event_RaiseVoid(&WindowEvents.Closing); + SDL_DestroyWindow(win_handle); + break; + + case SDL_EVENT_RENDER_DEVICE_RESET: + Gfx_LoseContext("SDL device reset event"); + Gfx_RecreateContext(); + break; + + + case SDL_EVENT_WINDOW_EXPOSED: + Event_RaiseVoid(&WindowEvents.RedrawNeeded); + break; + case SDL_EVENT_WINDOW_RESIZED: // TODO SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED + RefreshWindowBounds(); + Event_RaiseVoid(&WindowEvents.Resized); + break; + case SDL_EVENT_WINDOW_MINIMIZED: + case SDL_EVENT_WINDOW_MAXIMIZED: + case SDL_EVENT_WINDOW_RESTORED: + Event_RaiseVoid(&WindowEvents.StateChanged); + break; + case SDL_EVENT_WINDOW_FOCUS_GAINED: + Window_Main.Focused = true; + Event_RaiseVoid(&WindowEvents.FocusChanged); + break; + case SDL_EVENT_WINDOW_FOCUS_LOST: + Window_Main.Focused = false; + Event_RaiseVoid(&WindowEvents.FocusChanged); + break; + case SDL_EVENT_WINDOW_CLOSE_REQUESTED: + Window_RequestClose(); + break; + default: + if (e.type == dlg_event) ProcessDialogEvent(&e); + break; + } + } +} + +void Window_ProcessGamepads(float delta) { } + +static void Cursor_GetRawPos(int* x, int* y) { + float xPos, yPos; + SDL_GetMouseState(&xPos, &yPos); + *x = xPos; *y = yPos; +} +void Cursor_SetPosition(int x, int y) { + SDL_WarpMouseInWindow(win_handle, x, y); +} + +static void Cursor_DoSetVisible(cc_bool visible) { + if (visible) { + SDL_ShowCursor(); + } else { + SDL_HideCursor(); + } +} + +static void ShowDialogCore(const char* title, const char* msg) { + SDL_ShowSimpleMessageBox(0, title, msg, win_handle); +} + +static FileDialogCallback dlgCallback; +static SDL_DialogFileFilter* save_filters; + +static void ProcessDialogEvent(SDL_Event* e) { + char* result = e->user.data1; + int length = e->user.code; + + cc_string path; char pathBuffer[1024]; + String_InitArray(path, pathBuffer); + String_AppendUtf8(&path, result, length); + + dlgCallback(&path); + dlgCallback = NULL; + Mem_Free(result); +} + +static void DialogCallback(void *userdata, const char* const* filelist, int filter) { + if (!filelist) return; /* Error occurred */ + const char* result = filelist[0]; + if (!result) return; /* No file provided */ + + char* path = Mem_Alloc(NATIVE_STR_LEN, 1, "Dialog path"); + cc_string str = String_Init(path, 0, NATIVE_STR_LEN); + String_AppendUtf8(&str, result, String_Length(result)); + + // May need to add file extension when saving, e.g. on Windows + if (save_filters && filter >= 0 && save_filters[filter].pattern) + String_Format1(&str, ".%c", save_filters[filter].pattern); + + // Dialog callback may not be called from the main thread + // (E.g. on windows it is called from a background thread) + SDL_Event e = { 0 }; + e.type = SDL_EVENT_USER; + e.user.code = str.length; + e.user.data1 = path; + SDL_PushEvent(&e); +} + +cc_result Window_OpenFileDialog(const struct OpenFileDialogArgs* args) { + // TODO free memory + char* pattern = Mem_Alloc(301, 1, "OpenDialog pattern"); + SDL_DialogFileFilter* filters = Mem_Alloc(2, sizeof(SDL_DialogFileFilter), "OpenDialog filters"); + int i; + + cc_string str = String_Init(pattern, 0, 300); + for (i = 0; ; i++) + { + if (!args->filters[i]) break; + if (i) String_Append(&str, ';'); + String_AppendConst(&str, args->filters[i] + 1); + } + + pattern[str.length] = '\0'; + filters[0].name = args->description; + filters[0].pattern = pattern; + filters[1].name = NULL; + filters[1].pattern = NULL; + + dlgCallback = args->Callback; + save_filters = NULL; + SDL_ShowOpenFileDialog(DialogCallback, NULL, win_handle, filters, NULL, false); + return 0; +} + +#define MAX_SAVE_DIALOG_FILTERS 10 +cc_result Window_SaveFileDialog(const struct SaveFileDialogArgs* args) { + // TODO free memory + char* defName = Mem_Alloc(NATIVE_STR_LEN, 1, "SaveDialog default"); + SDL_DialogFileFilter* filters = Mem_Alloc(MAX_SAVE_DIALOG_FILTERS + 1, sizeof(SDL_DialogFileFilter), "SaveDialog filters"); + int i; + String_EncodeUtf8(defName, &args->defaultName); + + for (i = 0; i < MAX_SAVE_DIALOG_FILTERS; i++) + { + if (!args->filters[i]) break; + filters[i].name = args->titles[i]; + filters[i].pattern = args->filters[i] + 1; // skip . + } + + filters[i].name = NULL; + filters[i].pattern = NULL; + + dlgCallback = args->Callback; + save_filters = filters; + SDL_ShowSaveFileDialog(DialogCallback, NULL, win_handle, filters, defName); + return 0; +} + +static SDL_Surface* win_surface; +static SDL_Surface* blit_surface; + +void Window_AllocFramebuffer(struct Bitmap* bmp, int width, int height) { + SDL_PixelFormat* fmt; + win_surface = SDL_GetWindowSurface(win_handle); + if (!win_surface) Window_SDLFail("getting window surface"); + + fmt = win_surface->format; + if (fmt->bits_per_pixel != 32) { + /* Slow path: e.g. 15 or 16 bit pixels */ + Platform_Log1("Slow color depth: %b bpp", &fmt->bits_per_pixel); + blit_surface = SDL_CreateSurface(win_surface->w, win_surface->h, SDL_PIXELFORMAT_RGBA32); + if (!blit_surface) Window_SDLFail("creating blit surface"); + + SDL_SetSurfaceBlendMode(blit_surface, SDL_BLENDMODE_NONE); + bmp->scan0 = blit_surface->pixels; + } else { + /* Fast path: 32 bit pixels */ + if (SDL_MUSTLOCK(win_surface)) { + int ret = SDL_LockSurface(win_surface); + if (ret < 0) Window_SDLFail("locking window surface"); + } + bmp->scan0 = win_surface->pixels; + } + /* TODO proper stride */ + bmp->width = width; + bmp->height = height; +} + +void Window_DrawFramebuffer(Rect2D r, struct Bitmap* bmp) { + SDL_Rect rect; + rect.x = r.x; rect.w = r.width; + rect.y = r.y; rect.h = r.height; + + if (blit_surface) SDL_BlitSurface(blit_surface, &rect, win_surface, &rect); + SDL_UpdateWindowSurfaceRects(win_handle, &rect, 1); +} + +void Window_FreeFramebuffer(struct Bitmap* bmp) { + if (blit_surface) SDL_DestroySurface(blit_surface); + blit_surface = NULL; + + /* SDL docs explicitly say to NOT free window surface */ + /* https://wiki.libsdl.org/SDL_GetWindowSurface */ + /* TODO: Do we still need to unlock it though? */ +} + +void OnscreenKeyboard_Open(struct OpenKeyboardArgs* args) { SDL_StartTextInput(); } +void OnscreenKeyboard_SetText(const cc_string* text) { } +void OnscreenKeyboard_Draw2D(Rect2D* r, struct Bitmap* bmp) { } +void OnscreenKeyboard_Draw3D(void) { } +void OnscreenKeyboard_Close(void) { SDL_StopTextInput(); } + +void Window_EnableRawMouse(void) { + RegrabMouse(); + SDL_SetRelativeMouseMode(true); + Input.RawMode = true; +} +void Window_UpdateRawMouse(void) { CentreMousePosition(); } + +void Window_DisableRawMouse(void) { + RegrabMouse(); + SDL_SetRelativeMouseMode(false); + Input.RawMode = false; +} + + +/*########################################################################################################################* +*-----------------------------------------------------OpenGL context------------------------------------------------------* +*#########################################################################################################################*/ +#if (CC_GFX_BACKEND & CC_GFX_BACKEND_GL_MASK) && !defined CC_BUILD_EGL +static SDL_GLContext win_ctx; + +void GLContext_Create(void) { + struct GraphicsMode mode; + InitGraphicsMode(&mode); + SDL_GL_SetAttribute(SDL_GL_RED_SIZE, mode.R); + SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, mode.G); + SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, mode.B); + SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, mode.A); + + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, GLCONTEXT_DEFAULT_DEPTH); + SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 0); + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, true); +#ifdef CC_BUILD_GLES + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); +#endif + + win_ctx = SDL_GL_CreateContext(win_handle); + if (!win_ctx) Window_SDLFail("creating OpenGL context"); +} + +void GLContext_Update(void) { } +cc_bool GLContext_TryRestore(void) { return true; } +void GLContext_Free(void) { + SDL_GL_DeleteContext(win_ctx); + win_ctx = NULL; +} + +void* GLContext_GetAddress(const char* function) { + return SDL_GL_GetProcAddress(function); +} + +cc_bool GLContext_SwapBuffers(void) { + SDL_GL_SwapWindow(win_handle); + return true; +} + +void GLContext_SetFpsLimit(cc_bool vsync, float minFrameMs) { + SDL_GL_SetSwapInterval(vsync); +} +void GLContext_GetApiInfo(cc_string* info) { } +#endif +#endif |