diff infinite_canvas/main.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/main.c	Mon Aug 17 16:57:56 2026 -0700
@@ -0,0 +1,184 @@
+#include "infinite_canvas/canvas.h"
+#include "infinite_canvas/web_surface.h"
+
+#if defined(INFINITE_CANVAS_DEV_UI)
+#include "infinite_canvas/dev_ui.h"
+#endif
+
+#if defined(PLATFORM_WEB)
+#include <emscripten/emscripten.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+typedef struct {
+    Dowa_Arena *p_arena;
+    Canvas_Camera camera;
+    Canvas_Scene scene;
+    Canvas_Web_Surface web_surface;
+    Font font;
+    Canvas_Theme theme;
+    boolean owns_font;
+    const char *p_screenshot_path;
+    uint32 frame_count;
+#if defined(INFINITE_CANVAS_DEV_UI)
+    Canvas_Dev_UI dev_ui;
+#endif
+} Canvas_App;
+
+static Canvas_App *g_app = NULL;
+
+static void Canvas_App_Draw_Frame(void)
+{
+    int32 width = GetScreenWidth();
+    int32 height = GetScreenHeight();
+    Canvas_Camera_Set_Viewport(&g_app->camera, width, height);
+
+    boolean block_pointer_input = FALSE;
+#if defined(INFINITE_CANVAS_DEV_UI)
+    block_pointer_input = Canvas_Dev_UI_Contains_Pointer(&g_app->dev_ui);
+#endif
+    boolean web_input = Canvas_Web_Surface_Update_Input(
+        &g_app->web_surface,
+        &g_app->camera,
+        &g_app->scene);
+    block_pointer_input = block_pointer_input || web_input;
+    boolean entity_input = Canvas_Scene_Update_Interaction(
+        &g_app->scene,
+        &g_app->camera,
+        block_pointer_input);
+    boolean keyboard_focus =
+        Canvas_Scene_Is_Text_Editing(&g_app->scene) ||
+        Canvas_Web_Surface_Is_Focused(&g_app->web_surface);
+    if (!keyboard_focus && IsKeyPressed(KEY_P)) {
+        Canvas_Scene_Toggle_Selected_Pin(&g_app->scene, &g_app->camera);
+    }
+    Canvas_Update_Camera_Input(
+        &g_app->camera,
+        block_pointer_input || entity_input,
+        keyboard_focus);
+    Canvas_Scene_Sync_Pinned(&g_app->scene, &g_app->camera);
+    Canvas_Scene_Update_Notification_Stack(&g_app->scene, &g_app->camera);
+
+    Canvas_Web_Surface_Sync(&g_app->web_surface, &g_app->camera, &g_app->scene);
+
+    BeginDrawing();
+    ClearBackground(g_app->theme.background);
+    Canvas_Draw_World(
+        &g_app->camera,
+        &g_app->scene,
+        g_app->font,
+        &g_app->theme);
+    Canvas_Web_Surface_Draw(&g_app->web_surface, &g_app->camera, &g_app->scene);
+#if defined(INFINITE_CANVAS_DEV_UI)
+    Canvas_Dev_UI_Draw(
+        &g_app->dev_ui,
+        &g_app->camera,
+        &g_app->scene,
+        g_app->font,
+        &g_app->theme);
+#endif
+    EndDrawing();
+    g_app->frame_count++;
+#if !defined(PLATFORM_WEB)
+    if (g_app->frame_count == 30) {
+        RestoreWindow();
+        SetWindowFocused();
+    }
+#endif
+    if (g_app->p_screenshot_path &&
+        g_app->frame_count == 180) {
+        TakeScreenshot(g_app->p_screenshot_path);
+    }
+}
+
+int main(int argc, char **p_argv)
+{
+#if !defined(PLATFORM_WEB)
+    int32 subprocess_result = Canvas_Web_Surface_Execute_Subprocess(argc, p_argv);
+    if (subprocess_result >= 0) return subprocess_result;
+#else
+    (void)argc;
+    (void)p_argv;
+#endif
+
+    Dowa_Arena *p_arena = Dowa_Arena_Create(8 * ONE_MEGA_BYTE);
+    if (!p_arena) return 1;
+
+    g_app = (Canvas_App *)Dowa_Arena_Allocate(p_arena, sizeof(*g_app));
+    if (!g_app) {
+        Dowa_Arena_Free(p_arena);
+        return 1;
+    }
+    memset(g_app, 0, sizeof(*g_app));
+    g_app->p_arena = p_arena;
+    g_app->p_screenshot_path = getenv("INFINITE_CANVAS_SCREENSHOT");
+    Canvas_Theme_Resolve(
+        &g_app->theme,
+        Canvas_Theme_Mode_From_String(getenv("INFINITE_CANVAS_THEME")));
+
+    SetConfigFlags(FLAG_WINDOW_RESIZABLE | FLAG_WINDOW_HIGHDPI | FLAG_MSAA_4X_HINT);
+#if defined(INFINITE_CANVAS_DEV_UI)
+    InitWindow(1920, 1200, "Infinite Canvas");
+    SetWindowMinSize(1200, 800);
+#else
+    InitWindow(1280, 720, "Infinite Canvas");
+    SetWindowMinSize(640, 360);
+#endif
+    SetTargetFPS(60);
+#if !defined(PLATFORM_WEB)
+    SetWindowFocused();
+#endif
+    if (!Canvas_Web_Surface_Init(&g_app->web_surface, p_arena)) {
+        TraceLog(LOG_WARNING, "Web surface initialization failed");
+    }
+
+    g_app->font = LoadFontEx("infinite_canvas/assets/Inter-Variable.ttf", 48, NULL, 0);
+    g_app->owns_font = g_app->font.texture.id != 0;
+    if (!g_app->owns_font) g_app->font = GetFontDefault();
+    else SetTextureFilter(g_app->font.texture, TEXTURE_FILTER_BILINEAR);
+    Canvas_Camera_Init(&g_app->camera, GetScreenWidth(), GetScreenHeight());
+    Canvas_Scene_Init(&g_app->scene, p_arena);
+    if (getenv("INFINITE_CANVAS_BROWSER_STRESS")) {
+        Canvas_Scene_Add_Browser_Grid(&g_app->scene);
+    } else {
+        Canvas_Scene_Add_Demo(&g_app->scene);
+    }
+    const char *p_image_source = getenv("INFINITE_CANVAS_IMAGE_SOURCE");
+    if (p_image_source) {
+        for (size_t index = 0; index < Dowa_Array_Length(g_app->scene.p_entities); index++) {
+            Canvas_Entity *p_entity = &g_app->scene.p_entities[index];
+            if (p_entity->type == CANVAS_ENTITY_IMAGE) {
+                snprintf(p_entity->text, sizeof(p_entity->text), "%s", p_image_source);
+                break;
+            }
+        }
+    }
+#if !defined(PLATFORM_WEB)
+    for (size_t index = 0; index < Dowa_Array_Length(g_app->scene.p_entities); index++) {
+        Canvas_Entity *p_entity = &g_app->scene.p_entities[index];
+        if (p_entity->type == CANVAS_ENTITY_WEB_CONTENT) {
+            snprintf(p_entity->text, sizeof(p_entity->text), "https://www.google.com");
+            break;
+        }
+    }
+#endif
+#if defined(INFINITE_CANVAS_DEV_UI)
+    Canvas_Dev_UI_Init(&g_app->dev_ui, g_app->font, &g_app->theme);
+#endif
+
+#if defined(PLATFORM_WEB)
+    emscripten_set_main_loop(Canvas_App_Draw_Frame, 0, 1);
+#else
+    while (!WindowShouldClose()) Canvas_App_Draw_Frame();
+    Canvas_Web_Surface_Shutdown(&g_app->web_surface);
+    if (g_app->owns_font) UnloadFont(g_app->font);
+    Dowa_Arena_Free(p_arena);
+    g_app = NULL;
+    CloseWindow();
+#endif
+
+    return 0;
+}