Mercurial
view infinite_canvas/web_surface_web.c @ 279:b3b547563ec7
Add Google connector service and agent wiki
Implement the C/Seobeo Google Drive and Gmail connector with encrypted OAuth storage, Zenbu authentication, browser testing, AI tool discovery, chunked HTTP decoding, and Bazel coverage. Consolidate repository guidance into progressive wiki documentation and enforce arena-first allocation for new first-party C code.
Co-authored-by: Copilot <[email protected]>
Copilot-Session: 84c338fd-0939-4bb3-b7f3-1062eb213e5d
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Mon, 17 Aug 2026 22:22:36 -0700 |
| parents | 8d560f50ed4c |
| children | 49e9e591c9bb |
line wrap: on
line source
#include "infinite_canvas/web_surface.h" #include <emscripten/emscripten.h> #include <string.h> EM_JS(void, Canvas_Web_Begin_Frame, (), { document.querySelectorAll("[data-canvas-web-surface]").forEach((surface) => { surface.dataset.canvasActive = "false"; }); }); EM_JS(void, Canvas_Web_Create_Or_Update, ( uint32 entity_id, const char *p_url, float x, float y, float width, float height, float opacity, int dark_mode), { const canvas = Module.canvas; const surfaceId = `canvas-web-surface-${entity_id}`; let surface = document.getElementById(surfaceId); if (!surface) { surface = document.createElement("div"); surface.id = surfaceId; surface.dataset.canvasWebSurface = "true"; surface.style.position = "fixed"; surface.style.zIndex = "4"; surface.style.overflow = "hidden"; surface.style.borderRadius = "8px"; surface.style.background = dark_mode ? "#0f0f11" : "#fff"; const frame = document.createElement("iframe"); frame.title = `Canvas web content ${entity_id}`; frame.referrerPolicy = "strict-origin-when-cross-origin"; frame.sandbox = "allow-downloads allow-forms allow-modals allow-popups allow-same-origin allow-scripts"; frame.style.width = "100%"; frame.style.height = "100%"; frame.style.border = "0"; frame.style.display = "block"; surface.appendChild(frame); document.body.appendChild(surface); } 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; frame.src = url; } const canvasBounds = canvas.getBoundingClientRect(); const scaleX = canvasBounds.width / Math.max(1, canvas.width); const scaleY = canvasBounds.height / Math.max(1, canvas.height); surface.style.left = `${canvasBounds.left + x * scaleX}px`; 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, ( uint32 entity_id, const char *p_source, float x, float y, float width, float height, float opacity), { const canvas = Module.canvas; const surfaceId = `canvas-web-surface-${entity_id}`; let surface = document.getElementById(surfaceId); if (!surface) { surface = document.createElement("div"); surface.id = surfaceId; surface.dataset.canvasWebSurface = "true"; surface.style.position = "fixed"; surface.style.zIndex = "4"; surface.style.overflow = "hidden"; surface.style.borderRadius = "8px"; surface.style.pointerEvents = "none"; const image = document.createElement("img"); image.alt = "Canvas image"; image.style.width = "100%"; image.style.height = "100%"; image.style.objectFit = "contain"; image.style.display = "block"; surface.appendChild(image); document.body.appendChild(surface); } surface.dataset.canvasActive = "true"; const image = surface.firstElementChild; const source = UTF8ToString(p_source); if (image.dataset.url !== source) { if (image.dataset.blobUrl) URL.revokeObjectURL(image.dataset.blobUrl); image.dataset.url = source; delete image.dataset.blobUrl; if (/^(https?:|data:|blob:)/i.test(source)) { image.src = source; } else { try { const bytes = FS.readFile(source); const extension = source.split(".").pop().toLowerCase(); const mime = extension === "svg" ? "image/svg+xml" : extension === "png" ? "image/png" : extension === "webp" ? "image/webp" : extension === "gif" ? "image/gif" : "image/jpeg"; const blobUrl = URL.createObjectURL(new Blob([bytes], {type: mime})); image.dataset.blobUrl = blobUrl; image.src = blobUrl; } catch (error) { image.src = source; } } } const canvasBounds = canvas.getBoundingClientRect(); const scaleX = canvasBounds.width / Math.max(1, canvas.width); const scaleY = canvasBounds.height / Math.max(1, canvas.height); surface.style.left = `${canvasBounds.left + x * scaleX}px`; 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, (), { document.querySelectorAll("[data-canvas-web-surface]").forEach((surface) => { if (surface.dataset.canvasActive !== "true") { const content = surface.firstElementChild; if (content && content.dataset.blobUrl) { URL.revokeObjectURL(content.dataset.blobUrl); } surface.remove(); } }); }); EM_JS(void, Canvas_Web_Destroy, (), { document.querySelectorAll("[data-canvas-web-surface]").forEach((surface) => { const content = surface.firstElementChild; if (content && content.dataset.blobUrl) { URL.revokeObjectURL(content.dataset.blobUrl); } surface.remove(); }); }); 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 + 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 - toolbar_height) * p_camera->zoom, }; } static boolean Canvas_Web_Is_Visible( const Canvas_Camera *p_camera, Rectangle bounds) { return p_camera->zoom >= 0.30f && bounds.x + bounds.width > 0.0f && bounds.y + bounds.height > 0.0f && bounds.x < (float)p_camera->viewport_width && 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; (void)p_argv; return -1; } boolean Canvas_Web_Surface_Init( Canvas_Web_Surface *p_surface, 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, const Canvas_Scene *p_scene) { (void)p_surface; (void)p_camera; (void)p_scene; return FALSE; } void Canvas_Web_Surface_Sync( Canvas_Web_Surface *p_surface, const Canvas_Camera *p_camera, const Canvas_Scene *p_scene) { if (!p_surface->initialized) return; Canvas_Web_Begin_Frame(); uint32 visible_views = 0; for (size_t index = 0; index < Dowa_Array_Length(p_scene->p_entities) && visible_views < CANVAS_MAX_WEB_VIEWS; index++) { const Canvas_Entity *p_entity = &p_scene->p_entities[index]; if (p_entity->type != CANVAS_ENTITY_WEB_CONTENT && p_entity->type != CANVAS_ENTITY_IMAGE) { continue; } 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, p_entity->text, bounds.x, bounds.y, bounds.width, bounds.height, opacity); } else { Canvas_Web_Create_Or_Update( p_entity->id, p_entity->text, bounds.x, bounds.y, bounds.width, bounds.height, opacity, p_surface->dark_mode ? 1 : 0); } visible_views++; } Canvas_Web_End_Frame(); } void Canvas_Web_Surface_Draw( const Canvas_Web_Surface *p_surface, const Canvas_Camera *p_camera, const Canvas_Scene *p_scene) { (void)p_surface; (void)p_camera; (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; return FALSE; } void Canvas_Web_Surface_Shutdown(Canvas_Web_Surface *p_surface) { if (!p_surface->initialized) return; Canvas_Web_Destroy(); p_surface->initialized = FALSE; }