#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 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