Files
aego/platform/sdl/aego_sdl.c
sanspie ef59ae297f Stage 11d: light theme (D345)
Theme tokens for every editor color with a literal check, System/Dark/Light preference that follows the OS, light goldens, Scene View overlays that keep viewport colors.
2026-10-25 15:31:22 +03:00

591 lines
17 KiB
C

#include <stdlib.h>
#include <string.h>
#include "aego_sdl.h"
static char aego_title_buf[1024];
static SDL_Cursor* aego_cursors[AEGO_CURSOR_COUNT];
static Uint32 aego_dialog_type = 0;
typedef struct {
char mime[128];
void* data;
size_t len;
char* text;
size_t textlen;
} aego_clip;
static Uint32 aego_mods(SDL_Keymod m) {
Uint32 r = 0;
if (m & SDL_KMOD_SHIFT) r |= AEGO_MOD_SHIFT;
if (m & SDL_KMOD_CTRL) r |= AEGO_MOD_CTRL;
if (m & SDL_KMOD_ALT) r |= AEGO_MOD_ALT;
if (m & SDL_KMOD_GUI) r |= AEGO_MOD_SUPER;
if (m & SDL_KMOD_CAPS) r |= AEGO_MOD_CAPS;
if (m & SDL_KMOD_NUM) r |= AEGO_MOD_NUM;
return r;
}
static int aego_button(Uint8 b) {
switch (b) {
case SDL_BUTTON_LEFT: return AEGO_BTN_LEFT;
case SDL_BUTTON_RIGHT: return AEGO_BTN_RIGHT;
case SDL_BUTTON_MIDDLE: return AEGO_BTN_MIDDLE;
case SDL_BUTTON_X1: return AEGO_BTN_X1;
case SDL_BUTTON_X2: return AEGO_BTN_X2;
}
return AEGO_BTN_OTHER;
}
bool aego_init(void) {
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS)) return false;
SDL_InitSubSystem(SDL_INIT_GAMEPAD);
return true;
}
void aego_quit(void) {
aego_free_cursors();
SDL_Quit();
}
const char* aego_error(void) {
return SDL_GetError();
}
const char* aego_video_driver(void) {
const char* d = SDL_GetCurrentVideoDriver();
return d ? d : "unknown";
}
Uint64 aego_ticks_ns(void) {
return SDL_GetTicksNS();
}
int aego_version(void) { return SDL_GetVersion(); }
int aego_version_major(int v) { return SDL_VERSIONNUM_MAJOR(v); }
int aego_version_minor(int v) { return SDL_VERSIONNUM_MINOR(v); }
int aego_version_micro(int v) { return SDL_VERSIONNUM_MICRO(v); }
bool aego_gl_setup(int debug, int srgb) {
bool ok = true;
int flags = SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG;
if (debug) flags |= SDL_GL_CONTEXT_DEBUG_FLAG;
ok = SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3) && ok;
ok = SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3) && ok;
ok = SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE) && ok;
ok = SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, flags) && ok;
ok = SDL_GL_SetAttribute(SDL_GL_FRAMEBUFFER_SRGB_CAPABLE, srgb ? 1 : 0) && ok;
ok = SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1) && ok;
ok = SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8) && ok;
ok = SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8) && ok;
ok = SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8) && ok;
ok = SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8) && ok;
ok = SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24) && ok;
ok = SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8) && ok;
return ok;
}
SDL_Window* aego_create_window(const char* title, int w, int h, int resizable, int fullscreen, int highdpi) {
SDL_WindowFlags flags = SDL_WINDOW_OPENGL | SDL_WINDOW_HIDDEN;
if (resizable) flags |= SDL_WINDOW_RESIZABLE;
if (fullscreen) flags |= SDL_WINDOW_FULLSCREEN;
if (highdpi) flags |= SDL_WINDOW_HIGH_PIXEL_DENSITY;
return SDL_CreateWindow(title, w, h, flags);
}
aego_metrics aego_window_metrics(SDL_Window* w) {
aego_metrics m;
memset(&m, 0, sizeof(m));
SDL_GetWindowSize(w, &m.lw, &m.lh);
SDL_GetWindowSizeInPixels(w, &m.pw, &m.ph);
m.density = SDL_GetWindowPixelDensity(w);
m.scale = SDL_GetWindowDisplayScale(w);
return m;
}
void aego_set_title(SDL_Window* w, const char* s, size_t n) {
if (n >= sizeof(aego_title_buf)) n = sizeof(aego_title_buf) - 1;
if (n > 0) memcpy(aego_title_buf, s, n);
aego_title_buf[n] = 0;
SDL_SetWindowTitle(w, aego_title_buf);
}
int aego_set_swap_interval(int interval) {
return SDL_GL_SetSwapInterval(interval) ? 1 : 0;
}
int aego_get_swap_interval(void) {
int v = 0;
SDL_GL_GetSwapInterval(&v);
return v;
}
void* aego_gl_proc(const char* name) {
return (void*)SDL_GL_GetProcAddress(name);
}
int aego_poll(aego_event* e) {
SDL_Event ev;
if (!SDL_PollEvent(&ev)) return 0;
memset(e, 0, sizeof(*e));
e->kind = AEGO_EV_NONE;
switch (ev.type) {
case SDL_EVENT_QUIT:
case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
e->kind = AEGO_EV_QUIT;
break;
case SDL_EVENT_WINDOW_RESIZED:
case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
case SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED:
case SDL_EVENT_WINDOW_ENTER_FULLSCREEN:
case SDL_EVENT_WINDOW_LEAVE_FULLSCREEN:
e->kind = AEGO_EV_RESIZED;
break;
case SDL_EVENT_WINDOW_FOCUS_GAINED:
e->kind = AEGO_EV_FOCUS_GAINED;
break;
case SDL_EVENT_WINDOW_FOCUS_LOST:
e->kind = AEGO_EV_FOCUS_LOST;
break;
case SDL_EVENT_WINDOW_MINIMIZED:
e->kind = AEGO_EV_MINIMIZED;
break;
case SDL_EVENT_WINDOW_RESTORED:
e->kind = AEGO_EV_RESTORED;
break;
case SDL_EVENT_KEY_DOWN:
case SDL_EVENT_KEY_UP:
e->kind = ev.type == SDL_EVENT_KEY_DOWN ? AEGO_EV_KEY_DOWN : AEGO_EV_KEY_UP;
e->scancode = (Uint32)ev.key.scancode;
e->key = (Uint32)ev.key.key;
e->mod = aego_mods(ev.key.mod);
e->repeat = ev.key.repeat ? 1 : 0;
break;
case SDL_EVENT_TEXT_INPUT:
e->kind = AEGO_EV_TEXT;
if (ev.text.text) {
size_t n = strlen(ev.text.text);
if (n > AEGO_TEXT_CAPACITY) n = AEGO_TEXT_CAPACITY;
memcpy(e->text, ev.text.text, n);
e->textn = (int)n;
}
break;
case SDL_EVENT_TEXT_EDITING:
e->kind = AEGO_EV_TEXT_EDITING;
e->data = ev.edit.text;
e->start = ev.edit.start;
e->length = ev.edit.length;
break;
case SDL_EVENT_DROP_BEGIN:
case SDL_EVENT_DROP_FILE:
case SDL_EVENT_DROP_TEXT:
case SDL_EVENT_DROP_COMPLETE:
switch (ev.type) {
case SDL_EVENT_DROP_BEGIN: e->kind = AEGO_EV_DROP_BEGIN; break;
case SDL_EVENT_DROP_FILE: e->kind = AEGO_EV_DROP_FILE; break;
case SDL_EVENT_DROP_TEXT: e->kind = AEGO_EV_DROP_TEXT; break;
default: e->kind = AEGO_EV_DROP_COMPLETE; break;
}
e->x = ev.drop.x;
e->y = ev.drop.y;
e->data = ev.drop.data;
break;
case SDL_EVENT_MOUSE_MOTION:
e->kind = AEGO_EV_MOUSE_MOVE;
e->x = ev.motion.x;
e->y = ev.motion.y;
e->dx = ev.motion.xrel;
e->dy = ev.motion.yrel;
break;
case SDL_EVENT_MOUSE_BUTTON_DOWN:
case SDL_EVENT_MOUSE_BUTTON_UP:
e->kind = ev.type == SDL_EVENT_MOUSE_BUTTON_DOWN ? AEGO_EV_MOUSE_DOWN : AEGO_EV_MOUSE_UP;
e->x = ev.button.x;
e->y = ev.button.y;
e->button = aego_button(ev.button.button);
break;
case SDL_EVENT_MOUSE_WHEEL:
e->kind = AEGO_EV_WHEEL;
e->wx = ev.wheel.x;
e->wy = ev.wheel.y;
e->flipped = ev.wheel.direction == SDL_MOUSEWHEEL_FLIPPED ? 1 : 0;
e->x = ev.wheel.mouse_x;
e->y = ev.wheel.mouse_y;
break;
case SDL_EVENT_GAMEPAD_ADDED: {
SDL_Gamepad* g = SDL_OpenGamepad(ev.gdevice.which);
if (!g) break;
e->kind = AEGO_EV_PAD_ADDED;
e->pad = (int)ev.gdevice.which;
const char* n = SDL_GetGamepadName(g);
if (n) {
size_t k = strlen(n);
if (k > AEGO_TEXT_CAPACITY) k = AEGO_TEXT_CAPACITY;
memcpy(e->text, n, k);
e->textn = (int)k;
}
break;
}
case SDL_EVENT_GAMEPAD_REMOVED: {
SDL_Gamepad* g = SDL_GetGamepadFromID(ev.gdevice.which);
if (g) SDL_CloseGamepad(g);
e->kind = AEGO_EV_PAD_REMOVED;
e->pad = (int)ev.gdevice.which;
break;
}
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
case SDL_EVENT_GAMEPAD_BUTTON_UP:
if (ev.gbutton.button >= AEGO_PAD_BUTTONS) break;
e->kind = ev.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN ? AEGO_EV_PAD_DOWN : AEGO_EV_PAD_UP;
e->pad = (int)ev.gbutton.which;
e->button = ev.gbutton.button;
break;
case SDL_EVENT_GAMEPAD_AXIS_MOTION:
if (ev.gaxis.axis >= AEGO_PAD_AXES) break;
e->kind = AEGO_EV_PAD_AXIS;
e->pad = (int)ev.gaxis.which;
e->button = ev.gaxis.axis;
e->wx = ev.gaxis.value < 0 ? ev.gaxis.value / 32768.0f : ev.gaxis.value / 32767.0f;
break;
case SDL_EVENT_AUDIO_DEVICE_ADDED:
case SDL_EVENT_AUDIO_DEVICE_REMOVED:
case SDL_EVENT_AUDIO_DEVICE_FORMAT_CHANGED:
if (ev.adevice.recording) break;
if (ev.type == SDL_EVENT_AUDIO_DEVICE_ADDED) e->kind = AEGO_EV_AUDIO_ADDED;
else if (ev.type == SDL_EVENT_AUDIO_DEVICE_REMOVED) e->kind = AEGO_EV_AUDIO_REMOVED;
else e->kind = AEGO_EV_AUDIO_FORMAT;
e->device = ev.adevice.which;
break;
case SDL_EVENT_SYSTEM_THEME_CHANGED:
e->kind = AEGO_EV_SYSTEM_THEME;
break;
default:
if (aego_dialog_type != 0 && ev.type == aego_dialog_type) {
e->kind = AEGO_EV_DIALOG;
e->start = ev.user.code;
e->length = (int)(intptr_t)ev.user.data2;
e->data = (const char*)ev.user.data1;
}
break;
}
return 1;
}
void aego_text_input_area(SDL_Window* w, int x, int y, int ww, int hh, int cursor) {
SDL_Rect r;
r.x = x;
r.y = y;
r.w = ww;
r.h = hh;
SDL_SetTextInputArea(w, &r, cursor);
}
static const void* aego_clip_data(void* userdata, const char* mime, size_t* size) {
aego_clip* c = (aego_clip*)userdata;
if (mime && c->mime[0] && strcmp(mime, c->mime) == 0) {
*size = c->len;
return c->data;
}
if (mime && (strcmp(mime, "text/plain;charset=utf-8") == 0 || strcmp(mime, "text/plain") == 0)) {
*size = c->textlen;
return c->text;
}
*size = 0;
return NULL;
}
static void aego_clip_cleanup(void* userdata) {
aego_clip* c = (aego_clip*)userdata;
free(c->data);
free(c->text);
free(c);
}
bool aego_set_clipboard(const char* mime, const void* data, size_t len, const char* text, size_t textlen) {
aego_clip* c = (aego_clip*)calloc(1, sizeof(aego_clip));
if (!c) return false;
if (mime) {
strncpy(c->mime, mime, sizeof(c->mime) - 1);
}
if (len > 0) {
c->data = malloc(len);
if (!c->data) { aego_clip_cleanup(c); return false; }
memcpy(c->data, data, len);
c->len = len;
}
c->text = (char*)malloc(textlen + 1);
if (!c->text) { aego_clip_cleanup(c); return false; }
if (textlen > 0) memcpy(c->text, text, textlen);
c->text[textlen] = 0;
c->textlen = textlen;
const char* types[3] = { c->mime, "text/plain;charset=utf-8", "text/plain" };
if (!SDL_SetClipboardData(aego_clip_data, aego_clip_cleanup, c, types, 3)) {
aego_clip_cleanup(c);
return false;
}
return true;
}
void* aego_get_clipboard(const char* mime, size_t* len) {
*len = 0;
if (!SDL_HasClipboardData(mime)) return NULL;
return SDL_GetClipboardData(mime, len);
}
void aego_set_cursor(int shape) {
SDL_SystemCursor id = SDL_SYSTEM_CURSOR_DEFAULT;
switch (shape) {
case 1: id = SDL_SYSTEM_CURSOR_TEXT; break;
case 2: id = SDL_SYSTEM_CURSOR_POINTER; break;
case 3: id = SDL_SYSTEM_CURSOR_MOVE; break;
case 4: id = SDL_SYSTEM_CURSOR_EW_RESIZE; break;
case 5: id = SDL_SYSTEM_CURSOR_NS_RESIZE; break;
case 6: id = SDL_SYSTEM_CURSOR_NWSE_RESIZE; break;
case 7: id = SDL_SYSTEM_CURSOR_NESW_RESIZE; break;
case 8: id = SDL_SYSTEM_CURSOR_NOT_ALLOWED; break;
case 9: id = SDL_SYSTEM_CURSOR_CROSSHAIR; break;
default: shape = 0; break;
}
if (!aego_cursors[shape]) aego_cursors[shape] = SDL_CreateSystemCursor(id);
if (aego_cursors[shape]) SDL_SetCursor(aego_cursors[shape]);
}
void aego_free_cursors(void) {
for (int i = 0; i < AEGO_CURSOR_COUNT; i++) {
if (aego_cursors[i]) {
SDL_DestroyCursor(aego_cursors[i]);
aego_cursors[i] = NULL;
}
}
}
int aego_displays(SDL_DisplayID* out, int max) {
int n = 0;
SDL_DisplayID* ids = SDL_GetDisplays(&n);
if (!ids) return 0;
if (n > max) n = max;
for (int i = 0; i < n; i++) out[i] = ids[i];
SDL_free(ids);
return n;
}
bool aego_display_current(SDL_DisplayID id, aego_mode* out) {
const SDL_DisplayMode* m = SDL_GetCurrentDisplayMode(id);
if (!m) return false;
out->w = m->w;
out->h = m->h;
out->hz = m->refresh_rate;
return true;
}
int aego_display_modes(SDL_DisplayID id, aego_mode* out, int max) {
int n = 0;
SDL_DisplayMode** modes = SDL_GetFullscreenDisplayModes(id, &n);
if (!modes) return 0;
if (n > max) n = max;
for (int i = 0; i < n; i++) {
out[i].w = modes[i]->w;
out[i].h = modes[i]->h;
out[i].hz = modes[i]->refresh_rate;
}
SDL_free(modes);
return n;
}
bool aego_set_window_mode(SDL_Window* w, int mode, SDL_DisplayID id, int width, int height, float hz) {
SDL_Rect b;
if (!SDL_GetDisplayBounds(id, &b)) return false;
switch (mode) {
case 2: {
SDL_DisplayMode m;
if (!SDL_GetClosestFullscreenDisplayMode(id, width, height, hz, true, &m)) return false;
SDL_SetWindowPosition(w, SDL_WINDOWPOS_CENTERED_DISPLAY(id), SDL_WINDOWPOS_CENTERED_DISPLAY(id));
if (!SDL_SetWindowFullscreenMode(w, &m)) return false;
if (!SDL_SetWindowFullscreen(w, true)) return false;
break;
}
case 1:
SDL_SetWindowPosition(w, b.x, b.y);
if (!SDL_SetWindowFullscreenMode(w, NULL)) return false;
if (!SDL_SetWindowFullscreen(w, true)) return false;
break;
default:
if (!SDL_SetWindowFullscreen(w, false)) return false;
SDL_SetWindowBordered(w, true);
if (width > 0 && height > 0) SDL_SetWindowSize(w, width, height);
SDL_SetWindowPosition(w, SDL_WINDOWPOS_CENTERED_DISPLAY(id), SDL_WINDOWPOS_CENTERED_DISPLAY(id));
break;
}
SDL_SyncWindow(w);
return true;
}
SDL_AudioStream* aego_audio_open(int freq, int ch, int frames, int* got_freq, int* got_frames) {
if (!SDL_WasInit(SDL_INIT_AUDIO) && !SDL_InitSubSystem(SDL_INIT_AUDIO)) return NULL;
SDL_AudioSpec dev;
int dev_frames = 0;
if (freq <= 0) {
freq = SDL_GetAudioDeviceFormat(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &dev, &dev_frames) ? dev.freq : 48000;
}
if (frames > 0) {
char buf[16];
SDL_snprintf(buf, sizeof(buf), "%d", frames);
SDL_SetHint(SDL_HINT_AUDIO_DEVICE_SAMPLE_FRAMES, buf);
}
SDL_AudioSpec spec;
spec.format = SDL_AUDIO_F32;
spec.channels = ch;
spec.freq = freq;
SDL_AudioStream* s = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &spec, NULL, NULL);
if (!s) return NULL;
*got_freq = freq;
*got_frames = frames;
if (SDL_GetAudioDeviceFormat(SDL_GetAudioStreamDevice(s), &dev, &dev_frames) && dev_frames > 0) *got_frames = dev_frames;
SDL_ResumeAudioStreamDevice(s);
return s;
}
bool aego_audio_put(SDL_AudioStream* s, const float* data, int bytes) {
return SDL_PutAudioStreamData(s, data, bytes);
}
int aego_audio_queued(SDL_AudioStream* s) {
int n = SDL_GetAudioStreamQueued(s);
return n < 0 ? 0 : n;
}
void aego_audio_pause(SDL_AudioStream* s, int on) {
if (on) SDL_PauseAudioStreamDevice(s);
else SDL_ResumeAudioStreamDevice(s);
}
void aego_audio_close(SDL_AudioStream* s) {
SDL_DestroyAudioStream(s);
}
Uint32 aego_audio_device(SDL_AudioStream* s) {
return SDL_GetAudioStreamDevice(s);
}
typedef struct {
int id;
char* start;
char* name;
char* pattern;
SDL_DialogFileFilter filter;
} aego_dialog_req;
static char* aego_dup(const char* s) {
return s && *s ? SDL_strdup(s) : NULL;
}
static void SDLCALL aego_dialog_done(void* userdata, const char* const* list, int filter) {
aego_dialog_req* r = (aego_dialog_req*)userdata;
SDL_Event ev;
SDL_zero(ev);
ev.type = aego_dialog_type;
ev.user.code = r->id;
if (!list) {
ev.user.data2 = (void*)(intptr_t)-1;
} else if (!list[0]) {
ev.user.data2 = (void*)(intptr_t)0;
} else {
ev.user.data1 = SDL_strdup(list[0]);
ev.user.data2 = (void*)(intptr_t)1;
}
(void)filter;
SDL_free(r->start);
SDL_free(r->name);
SDL_free(r->pattern);
SDL_free(r);
SDL_PushEvent(&ev);
}
bool aego_dialog(int id, int kind, SDL_Window* w, const char* start, const char* name, const char* pattern) {
if (aego_dialog_type == 0) {
aego_dialog_type = SDL_RegisterEvents(1);
if (aego_dialog_type == 0) return false;
}
aego_dialog_req* r = (aego_dialog_req*)SDL_calloc(1, sizeof(*r));
if (!r) return false;
r->id = id;
r->start = aego_dup(start);
r->name = aego_dup(name);
r->pattern = aego_dup(pattern);
r->filter.name = r->name ? r->name : "Files";
r->filter.pattern = r->pattern;
int n = r->pattern ? 1 : 0;
switch (kind) {
case 0:
SDL_ShowOpenFolderDialog(aego_dialog_done, r, w, r->start, false);
break;
case 1:
SDL_ShowOpenFileDialog(aego_dialog_done, r, w, n ? &r->filter : NULL, n, r->start, false);
break;
default:
SDL_ShowSaveFileDialog(aego_dialog_done, r, w, n ? &r->filter : NULL, n, r->start);
break;
}
return true;
}
bool aego_alert(const char* title, const char* msg) {
return SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, title, msg, NULL);
}
SDL_Surface* aego_icon_surface(int w, int h, const void* rgba) {
SDL_Surface* s = SDL_CreateSurface(w, h, SDL_PIXELFORMAT_RGBA32);
if (!s) {
return NULL;
}
for (int y = 0; y < h; y++) {
SDL_memcpy((Uint8*)s->pixels + y * s->pitch, (const Uint8*)rgba + (size_t)y * w * 4, (size_t)w * 4);
}
return s;
}
bool aego_icon_alternate(SDL_Surface* base, SDL_Surface* alt) {
bool ok = SDL_AddSurfaceAlternateImage(base, alt);
SDL_DestroySurface(alt);
return ok;
}
bool aego_set_icon(SDL_Window* w, SDL_Surface* s) {
bool ok = SDL_SetWindowIcon(w, s);
SDL_DestroySurface(s);
return ok;
}
#if defined(_WIN32)
#define AEGO_GLAPI __stdcall
#else
#define AEGO_GLAPI
#endif
typedef const unsigned char*(AEGO_GLAPI* aego_getstring)(unsigned int);
const char* aego_gl_string(unsigned int name) {
aego_getstring fn = (aego_getstring)SDL_GL_GetProcAddress("glGetString");
if (!fn) {
return "";
}
const unsigned char* v = fn(name);
return v ? (const char*)v : "";
}
const char* aego_gl_probe(SDL_Window* w) {
static char buf[256];
buf[0] = 0;
SDL_GL_ResetAttributes();
SDL_GLContext c = SDL_GL_CreateContext(w);
if (!c) {
return buf;
}
if (SDL_GL_MakeCurrent(w, c)) {
SDL_snprintf(buf, sizeof buf, "%s (%s)", aego_gl_string(0x1F02), aego_gl_string(0x1F01));
}
SDL_GL_MakeCurrent(w, NULL);
SDL_GL_DestroyContext(c);
return buf;
}