ClassiCube/src/Window_SDL3.c

539 lines
17 KiB
C

#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) {
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;
}
}
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