diff infinite_canvas/web_surface_web.c @ 278:8d560f50ed4c

Improve infinite canvas interactions and browser chrome Render Lucide icons directly with Raylib, add searchable icon browsing, robust text editing, entity lifecycle animations, z-order-safe input, semantic themes, and animated editable browser controls. Document rendering, pinning, context, and component extension for future agents. Co-authored-by: Copilot <[email protected]> Copilot-Session: f68442b1-fa8f-46a0-9689-81710613bbd4
author MrJuneJune <me@mrjunejune.com>
date Mon, 17 Aug 2026 22:16:14 -0700
parents b55c22cff335
children 49e9e591c9bb
line wrap: on
line diff
--- a/infinite_canvas/web_surface_web.c	Mon Aug 17 17:01:40 2026 -0700
+++ b/infinite_canvas/web_surface_web.c	Mon Aug 17 22:16:14 2026 -0700
@@ -15,7 +15,9 @@
     float x,
     float y,
     float width,
-    float height), {
+    float height,
+    float opacity,
+    int dark_mode), {
     const canvas = Module.canvas;
     const surfaceId = `canvas-web-surface-${entity_id}`;
     let surface = document.getElementById(surfaceId);
@@ -27,7 +29,7 @@
         surface.style.zIndex = "4";
         surface.style.overflow = "hidden";
         surface.style.borderRadius = "8px";
-        surface.style.background = "#fff";
+        surface.style.background = dark_mode ? "#0f0f11" : "#fff";
 
         const frame = document.createElement("iframe");
         frame.title = `Canvas web content ${entity_id}`;
@@ -43,6 +45,10 @@
 
     surface.dataset.canvasActive = "true";
     const frame = surface.firstElementChild;
+    const colorScheme = dark_mode ? "dark" : "light";
+    surface.style.colorScheme = colorScheme;
+    surface.style.background = dark_mode ? "#0f0f11" : "#fff";
+    frame.style.colorScheme = colorScheme;
     const url = UTF8ToString(p_url);
     if (frame.dataset.url !== url) {
         frame.dataset.url = url;
@@ -56,6 +62,25 @@
     surface.style.top = `${canvasBounds.top + y * scaleY}px`;
     surface.style.width = `${Math.max(1, width * scaleX)}px`;
     surface.style.height = `${Math.max(1, height * scaleY)}px`;
+    surface.style.opacity = `${opacity}`;
+    surface.style.pointerEvents = opacity >= 0.99 ? "auto" : "none";
+});
+
+EM_JS(void, Canvas_Web_History_Go, (uint32 entity_id, int direction), {
+    const surface = document.getElementById(
+        `canvas-web-surface-${entity_id}`);
+    const frame = surface && surface.firstElementChild;
+    if (!frame || !frame.contentWindow) return;
+    try {
+        frame.contentWindow.history.go(direction);
+    } catch (error) {
+        console.warn("Canvas iframe history navigation is unavailable", error);
+    }
+});
+
+EM_JS(int, Canvas_Web_Has_Surface, (uint32 entity_id), {
+    return document.getElementById(
+        `canvas-web-surface-${entity_id}`) ? 1 : 0;
 });
 
 EM_JS(void, Canvas_Image_Create_Or_Update, (
@@ -64,7 +89,8 @@
     float x,
     float y,
     float width,
-    float height), {
+    float height,
+    float opacity), {
     const canvas = Module.canvas;
     const surfaceId = `canvas-web-surface-${entity_id}`;
     let surface = document.getElementById(surfaceId);
@@ -121,6 +147,7 @@
     surface.style.top = `${canvasBounds.top + y * scaleY}px`;
     surface.style.width = `${Math.max(1, width * scaleX)}px`;
     surface.style.height = `${Math.max(1, height * scaleY)}px`;
+    surface.style.opacity = `${opacity}`;
 });
 
 EM_JS(void, Canvas_Web_End_Frame, (), {
@@ -147,21 +174,30 @@
 
 static Rectangle Canvas_Web_Content_Bounds(
     const Canvas_Camera *p_camera,
+    const Canvas_Scene *p_scene,
     const Canvas_Entity *p_entity)
 {
+    (void)p_scene;
     float frame_inset = CANVAS_WEB_FRAME_INSET / p_camera->zoom;
+    float amount = p_entity->web_chrome_amount;
+    if (amount < 0.0f) amount = 0.0f;
+    if (amount > 1.0f) amount = 1.0f;
+    float eased = amount * amount * (3.0f - 2.0f * amount);
+    float toolbar_height =
+        CANVAS_WEB_TOOLBAR_HEIGHT * eased / p_camera->zoom;
     float resize_handle = CANVAS_WEB_RESIZE_HANDLE / p_camera->zoom;
     Vector2 position = Canvas_Camera_World_To_Screen(
         p_camera,
         (Vector2){
             p_entity->position.x + frame_inset,
-            p_entity->position.y + frame_inset,
+            p_entity->position.y + frame_inset + toolbar_height,
         });
     return (Rectangle){
         position.x,
         position.y,
         (p_entity->size.x - frame_inset - resize_handle) * p_camera->zoom,
-        (p_entity->size.y - frame_inset - resize_handle) * p_camera->zoom,
+        (p_entity->size.y - frame_inset - resize_handle - toolbar_height) *
+            p_camera->zoom,
     };
 }
 
@@ -176,6 +212,14 @@
         bounds.y < (float)p_camera->viewport_height;
 }
 
+static float Canvas_Web_Entity_Opacity(const Canvas_Entity *p_entity)
+{
+    float amount = p_entity->visibility_amount;
+    if (amount < 0.0f) amount = 0.0f;
+    if (amount > 1.0f) amount = 1.0f;
+    return amount * amount * (3.0f - 2.0f * amount);
+}
+
 int32 Canvas_Web_Surface_Execute_Subprocess(int argc, char **p_argv)
 {
     (void)argc;
@@ -185,14 +229,23 @@
 
 boolean Canvas_Web_Surface_Init(
     Canvas_Web_Surface *p_surface,
-    Dowa_Arena *p_arena)
+    Dowa_Arena *p_arena,
+    boolean dark_mode)
 {
     memset(p_surface, 0, sizeof(*p_surface));
     p_surface->p_arena = p_arena;
+    p_surface->dark_mode = dark_mode;
     p_surface->initialized = TRUE;
     return TRUE;
 }
 
+void Canvas_Web_Surface_Set_Dark_Mode(
+    Canvas_Web_Surface *p_surface,
+    boolean dark_mode)
+{
+    p_surface->dark_mode = dark_mode;
+}
+
 boolean Canvas_Web_Surface_Update_Input(
     Canvas_Web_Surface *p_surface,
     const Canvas_Camera *p_camera,
@@ -222,8 +275,12 @@
             p_entity->type != CANVAS_ENTITY_IMAGE) {
             continue;
         }
-        Rectangle bounds = Canvas_Web_Content_Bounds(p_camera, p_entity);
+        Rectangle bounds = Canvas_Web_Content_Bounds(
+            p_camera,
+            p_scene,
+            p_entity);
         if (!Canvas_Web_Is_Visible(p_camera, bounds)) continue;
+        float opacity = Canvas_Web_Entity_Opacity(p_entity);
         if (p_entity->type == CANVAS_ENTITY_IMAGE) {
             Canvas_Image_Create_Or_Update(
                 p_entity->id,
@@ -231,7 +288,8 @@
                 bounds.x,
                 bounds.y,
                 bounds.width,
-                bounds.height);
+                bounds.height,
+                opacity);
         } else {
             Canvas_Web_Create_Or_Update(
                 p_entity->id,
@@ -239,7 +297,9 @@
                 bounds.x,
                 bounds.y,
                 bounds.width,
-                bounds.height);
+                bounds.height,
+                opacity,
+                p_surface->dark_mode ? 1 : 0);
         }
         visible_views++;
     }
@@ -256,6 +316,59 @@
     (void)p_scene;
 }
 
+void Canvas_Web_Surface_Draw_Entity(
+    const Canvas_Web_Surface *p_surface,
+    const Canvas_Camera *p_camera,
+    const Canvas_Scene *p_scene,
+    size_t entity_index)
+{
+    (void)p_surface;
+    (void)p_camera;
+    (void)p_scene;
+    (void)entity_index;
+}
+
+const char *Canvas_Web_Surface_URL(
+    const Canvas_Web_Surface *p_surface,
+    uint32 entity_id)
+{
+    (void)p_surface;
+    (void)entity_id;
+    return NULL;
+}
+
+boolean Canvas_Web_Surface_Can_Go_Back(
+    const Canvas_Web_Surface *p_surface,
+    uint32 entity_id)
+{
+    (void)p_surface;
+    return Canvas_Web_Has_Surface(entity_id) ? TRUE : FALSE;
+}
+
+boolean Canvas_Web_Surface_Can_Go_Forward(
+    const Canvas_Web_Surface *p_surface,
+    uint32 entity_id)
+{
+    (void)p_surface;
+    return Canvas_Web_Has_Surface(entity_id) ? TRUE : FALSE;
+}
+
+void Canvas_Web_Surface_Go_Back(
+    Canvas_Web_Surface *p_surface,
+    uint32 entity_id)
+{
+    (void)p_surface;
+    Canvas_Web_History_Go(entity_id, -1);
+}
+
+void Canvas_Web_Surface_Go_Forward(
+    Canvas_Web_Surface *p_surface,
+    uint32 entity_id)
+{
+    (void)p_surface;
+    Canvas_Web_History_Go(entity_id, 1);
+}
+
 boolean Canvas_Web_Surface_Is_Focused(const Canvas_Web_Surface *p_surface)
 {
     (void)p_surface;