diff infinite_canvas/theme.c @ 276:b55c22cff335

Add interactive infinite canvas prototype
author MrJuneJune <me@mrjunejune.com>
date Mon, 17 Aug 2026 16:57:56 -0700
parents
children 8d560f50ed4c
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/infinite_canvas/theme.c	Mon Aug 17 16:57:56 2026 -0700
@@ -0,0 +1,139 @@
+#include "infinite_canvas/theme.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#if defined(PLATFORM_WEB) || defined(__EMSCRIPTEN__)
+#include <emscripten/emscripten.h>
+
+EM_JS(int, Canvas_Web_Prefers_Dark, (), {
+    return window.matchMedia &&
+        window.matchMedia("(prefers-color-scheme: dark)").matches ? 1 : 0;
+});
+#elif defined(_WIN32)
+#include <windows.h>
+#else
+#include <stdio.h>
+#endif
+
+Canvas_Theme_Mode Canvas_Theme_Mode_From_String(const char *p_value)
+{
+    if (!p_value || strcmp(p_value, "auto") == 0) return CANVAS_THEME_AUTO;
+    if (strcmp(p_value, "light") == 0) return CANVAS_THEME_LIGHT;
+    if (strcmp(p_value, "dark") == 0) return CANVAS_THEME_DARK;
+    return CANVAS_THEME_AUTO;
+}
+
+boolean Canvas_System_Prefers_Dark(void)
+{
+#if defined(PLATFORM_WEB) || defined(__EMSCRIPTEN__)
+    return Canvas_Web_Prefers_Dark() ? TRUE : FALSE;
+#elif defined(_WIN32)
+    DWORD light_theme = 1;
+    DWORD size = sizeof(light_theme);
+    LSTATUS status = RegGetValueA(
+        HKEY_CURRENT_USER,
+        "Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
+        "AppsUseLightTheme",
+        RRF_RT_REG_DWORD,
+        NULL,
+        &light_theme,
+        &size);
+    return status == ERROR_SUCCESS && light_theme == 0 ? TRUE : FALSE;
+#elif defined(__APPLE__)
+    FILE *p_pipe = popen("defaults read -g AppleInterfaceStyle 2>/dev/null", "r");
+    if (!p_pipe) return FALSE;
+    char value[32] = {0};
+    boolean dark = fgets(value, sizeof(value), p_pipe) &&
+        strstr(value, "Dark") ? TRUE : FALSE;
+    pclose(p_pipe);
+    return dark;
+#else
+    const char *p_gtk_theme = getenv("GTK_THEME");
+    if (p_gtk_theme &&
+        (strstr(p_gtk_theme, "dark") || strstr(p_gtk_theme, "Dark"))) {
+        return TRUE;
+    }
+
+    FILE *p_pipe = popen(
+        "gsettings get org.gnome.desktop.interface color-scheme 2>/dev/null",
+        "r");
+    if (!p_pipe) return FALSE;
+    char value[64] = {0};
+    boolean dark = fgets(value, sizeof(value), p_pipe) &&
+        strstr(value, "prefer-dark") ? TRUE : FALSE;
+    pclose(p_pipe);
+    return dark;
+#endif
+}
+
+void Canvas_Theme_Resolve(Canvas_Theme *p_theme, Canvas_Theme_Mode mode)
+{
+    Canvas_Theme_Mode resolved = mode;
+    if (resolved == CANVAS_THEME_AUTO) {
+        resolved = Canvas_System_Prefers_Dark() ?
+            CANVAS_THEME_DARK :
+            CANVAS_THEME_LIGHT;
+    }
+
+    *p_theme = (Canvas_Theme){
+        .requested_mode = mode,
+        .resolved_mode = resolved,
+    };
+    if (resolved == CANVAS_THEME_DARK) {
+        p_theme->background = (Color){15, 15, 17, 255};
+        p_theme->surface = (Color){24, 24, 27, 255};
+        p_theme->surface_muted = (Color){39, 39, 42, 255};
+        p_theme->border = (Color){63, 63, 70, 255};
+        p_theme->text = (Color){244, 244, 245, 255};
+        p_theme->text_muted = (Color){161, 161, 170, 255};
+        p_theme->accent = (Color){96, 165, 250, 255};
+        p_theme->accent_soft = (Color){30, 58, 95, 255};
+        p_theme->shadow = (Color){0, 0, 0, 110};
+        p_theme->toast_surface = (Color){24, 24, 27, 255};
+        p_theme->toast_border = (Color){63, 63, 70, 255};
+    } else {
+        p_theme->background = (Color){255, 255, 255, 255};
+        p_theme->surface = (Color){255, 255, 255, 252};
+        p_theme->surface_muted = (Color){244, 247, 251, 255};
+        p_theme->border = (Color){205, 211, 221, 255};
+        p_theme->text = (Color){24, 24, 27, 255};
+        p_theme->text_muted = (Color){113, 113, 122, 255};
+        p_theme->accent = (Color){37, 99, 235, 255};
+        p_theme->accent_soft = (Color){239, 246, 255, 255};
+        p_theme->shadow = (Color){15, 23, 42, 36};
+        p_theme->toast_surface = (Color){255, 255, 255, 255};
+        p_theme->toast_border = (Color){212, 216, 223, 255};
+    }
+}
+
+void Canvas_Theme_Toggle(Canvas_Theme *p_theme)
+{
+    Canvas_Theme_Resolve(
+        p_theme,
+        p_theme->resolved_mode == CANVAS_THEME_DARK ?
+            CANVAS_THEME_LIGHT :
+            CANVAS_THEME_DARK);
+}
+
+void Canvas_Theme_Draw_Shadow(
+    const Canvas_Theme *p_theme,
+    Rectangle bounds,
+    float roundness,
+    int32 segments,
+    float zoom,
+    float opacity)
+{
+    float offset = 6.0f / zoom;
+    float shadow_alpha = ((float)p_theme->shadow.a / 255.0f) * opacity;
+    DrawRectangleRounded(
+        (Rectangle){
+            bounds.x + offset,
+            bounds.y + offset,
+            bounds.width,
+            bounds.height,
+        },
+        roundness,
+        segments,
+        Fade(p_theme->shadow, shadow_alpha));
+}