comparison 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
comparison
equal deleted inserted replaced
277:1d99147f520c 278:8d560f50ed4c
13 uint32 entity_id, 13 uint32 entity_id,
14 const char *p_url, 14 const char *p_url,
15 float x, 15 float x,
16 float y, 16 float y,
17 float width, 17 float width,
18 float height), { 18 float height,
19 float opacity,
20 int dark_mode), {
19 const canvas = Module.canvas; 21 const canvas = Module.canvas;
20 const surfaceId = `canvas-web-surface-${entity_id}`; 22 const surfaceId = `canvas-web-surface-${entity_id}`;
21 let surface = document.getElementById(surfaceId); 23 let surface = document.getElementById(surfaceId);
22 if (!surface) { 24 if (!surface) {
23 surface = document.createElement("div"); 25 surface = document.createElement("div");
25 surface.dataset.canvasWebSurface = "true"; 27 surface.dataset.canvasWebSurface = "true";
26 surface.style.position = "fixed"; 28 surface.style.position = "fixed";
27 surface.style.zIndex = "4"; 29 surface.style.zIndex = "4";
28 surface.style.overflow = "hidden"; 30 surface.style.overflow = "hidden";
29 surface.style.borderRadius = "8px"; 31 surface.style.borderRadius = "8px";
30 surface.style.background = "#fff"; 32 surface.style.background = dark_mode ? "#0f0f11" : "#fff";
31 33
32 const frame = document.createElement("iframe"); 34 const frame = document.createElement("iframe");
33 frame.title = `Canvas web content ${entity_id}`; 35 frame.title = `Canvas web content ${entity_id}`;
34 frame.referrerPolicy = "strict-origin-when-cross-origin"; 36 frame.referrerPolicy = "strict-origin-when-cross-origin";
35 frame.sandbox = "allow-downloads allow-forms allow-modals allow-popups allow-same-origin allow-scripts"; 37 frame.sandbox = "allow-downloads allow-forms allow-modals allow-popups allow-same-origin allow-scripts";
41 document.body.appendChild(surface); 43 document.body.appendChild(surface);
42 } 44 }
43 45
44 surface.dataset.canvasActive = "true"; 46 surface.dataset.canvasActive = "true";
45 const frame = surface.firstElementChild; 47 const frame = surface.firstElementChild;
48 const colorScheme = dark_mode ? "dark" : "light";
49 surface.style.colorScheme = colorScheme;
50 surface.style.background = dark_mode ? "#0f0f11" : "#fff";
51 frame.style.colorScheme = colorScheme;
46 const url = UTF8ToString(p_url); 52 const url = UTF8ToString(p_url);
47 if (frame.dataset.url !== url) { 53 if (frame.dataset.url !== url) {
48 frame.dataset.url = url; 54 frame.dataset.url = url;
49 frame.src = url; 55 frame.src = url;
50 } 56 }
54 const scaleY = canvasBounds.height / Math.max(1, canvas.height); 60 const scaleY = canvasBounds.height / Math.max(1, canvas.height);
55 surface.style.left = `${canvasBounds.left + x * scaleX}px`; 61 surface.style.left = `${canvasBounds.left + x * scaleX}px`;
56 surface.style.top = `${canvasBounds.top + y * scaleY}px`; 62 surface.style.top = `${canvasBounds.top + y * scaleY}px`;
57 surface.style.width = `${Math.max(1, width * scaleX)}px`; 63 surface.style.width = `${Math.max(1, width * scaleX)}px`;
58 surface.style.height = `${Math.max(1, height * scaleY)}px`; 64 surface.style.height = `${Math.max(1, height * scaleY)}px`;
65 surface.style.opacity = `${opacity}`;
66 surface.style.pointerEvents = opacity >= 0.99 ? "auto" : "none";
67 });
68
69 EM_JS(void, Canvas_Web_History_Go, (uint32 entity_id, int direction), {
70 const surface = document.getElementById(
71 `canvas-web-surface-${entity_id}`);
72 const frame = surface && surface.firstElementChild;
73 if (!frame || !frame.contentWindow) return;
74 try {
75 frame.contentWindow.history.go(direction);
76 } catch (error) {
77 console.warn("Canvas iframe history navigation is unavailable", error);
78 }
79 });
80
81 EM_JS(int, Canvas_Web_Has_Surface, (uint32 entity_id), {
82 return document.getElementById(
83 `canvas-web-surface-${entity_id}`) ? 1 : 0;
59 }); 84 });
60 85
61 EM_JS(void, Canvas_Image_Create_Or_Update, ( 86 EM_JS(void, Canvas_Image_Create_Or_Update, (
62 uint32 entity_id, 87 uint32 entity_id,
63 const char *p_source, 88 const char *p_source,
64 float x, 89 float x,
65 float y, 90 float y,
66 float width, 91 float width,
67 float height), { 92 float height,
93 float opacity), {
68 const canvas = Module.canvas; 94 const canvas = Module.canvas;
69 const surfaceId = `canvas-web-surface-${entity_id}`; 95 const surfaceId = `canvas-web-surface-${entity_id}`;
70 let surface = document.getElementById(surfaceId); 96 let surface = document.getElementById(surfaceId);
71 if (!surface) { 97 if (!surface) {
72 surface = document.createElement("div"); 98 surface = document.createElement("div");
119 const scaleY = canvasBounds.height / Math.max(1, canvas.height); 145 const scaleY = canvasBounds.height / Math.max(1, canvas.height);
120 surface.style.left = `${canvasBounds.left + x * scaleX}px`; 146 surface.style.left = `${canvasBounds.left + x * scaleX}px`;
121 surface.style.top = `${canvasBounds.top + y * scaleY}px`; 147 surface.style.top = `${canvasBounds.top + y * scaleY}px`;
122 surface.style.width = `${Math.max(1, width * scaleX)}px`; 148 surface.style.width = `${Math.max(1, width * scaleX)}px`;
123 surface.style.height = `${Math.max(1, height * scaleY)}px`; 149 surface.style.height = `${Math.max(1, height * scaleY)}px`;
150 surface.style.opacity = `${opacity}`;
124 }); 151 });
125 152
126 EM_JS(void, Canvas_Web_End_Frame, (), { 153 EM_JS(void, Canvas_Web_End_Frame, (), {
127 document.querySelectorAll("[data-canvas-web-surface]").forEach((surface) => { 154 document.querySelectorAll("[data-canvas-web-surface]").forEach((surface) => {
128 if (surface.dataset.canvasActive !== "true") { 155 if (surface.dataset.canvasActive !== "true") {
145 }); 172 });
146 }); 173 });
147 174
148 static Rectangle Canvas_Web_Content_Bounds( 175 static Rectangle Canvas_Web_Content_Bounds(
149 const Canvas_Camera *p_camera, 176 const Canvas_Camera *p_camera,
177 const Canvas_Scene *p_scene,
150 const Canvas_Entity *p_entity) 178 const Canvas_Entity *p_entity)
151 { 179 {
180 (void)p_scene;
152 float frame_inset = CANVAS_WEB_FRAME_INSET / p_camera->zoom; 181 float frame_inset = CANVAS_WEB_FRAME_INSET / p_camera->zoom;
182 float amount = p_entity->web_chrome_amount;
183 if (amount < 0.0f) amount = 0.0f;
184 if (amount > 1.0f) amount = 1.0f;
185 float eased = amount * amount * (3.0f - 2.0f * amount);
186 float toolbar_height =
187 CANVAS_WEB_TOOLBAR_HEIGHT * eased / p_camera->zoom;
153 float resize_handle = CANVAS_WEB_RESIZE_HANDLE / p_camera->zoom; 188 float resize_handle = CANVAS_WEB_RESIZE_HANDLE / p_camera->zoom;
154 Vector2 position = Canvas_Camera_World_To_Screen( 189 Vector2 position = Canvas_Camera_World_To_Screen(
155 p_camera, 190 p_camera,
156 (Vector2){ 191 (Vector2){
157 p_entity->position.x + frame_inset, 192 p_entity->position.x + frame_inset,
158 p_entity->position.y + frame_inset, 193 p_entity->position.y + frame_inset + toolbar_height,
159 }); 194 });
160 return (Rectangle){ 195 return (Rectangle){
161 position.x, 196 position.x,
162 position.y, 197 position.y,
163 (p_entity->size.x - frame_inset - resize_handle) * p_camera->zoom, 198 (p_entity->size.x - frame_inset - resize_handle) * p_camera->zoom,
164 (p_entity->size.y - frame_inset - resize_handle) * p_camera->zoom, 199 (p_entity->size.y - frame_inset - resize_handle - toolbar_height) *
200 p_camera->zoom,
165 }; 201 };
166 } 202 }
167 203
168 static boolean Canvas_Web_Is_Visible( 204 static boolean Canvas_Web_Is_Visible(
169 const Canvas_Camera *p_camera, 205 const Canvas_Camera *p_camera,
174 bounds.y + bounds.height > 0.0f && 210 bounds.y + bounds.height > 0.0f &&
175 bounds.x < (float)p_camera->viewport_width && 211 bounds.x < (float)p_camera->viewport_width &&
176 bounds.y < (float)p_camera->viewport_height; 212 bounds.y < (float)p_camera->viewport_height;
177 } 213 }
178 214
215 static float Canvas_Web_Entity_Opacity(const Canvas_Entity *p_entity)
216 {
217 float amount = p_entity->visibility_amount;
218 if (amount < 0.0f) amount = 0.0f;
219 if (amount > 1.0f) amount = 1.0f;
220 return amount * amount * (3.0f - 2.0f * amount);
221 }
222
179 int32 Canvas_Web_Surface_Execute_Subprocess(int argc, char **p_argv) 223 int32 Canvas_Web_Surface_Execute_Subprocess(int argc, char **p_argv)
180 { 224 {
181 (void)argc; 225 (void)argc;
182 (void)p_argv; 226 (void)p_argv;
183 return -1; 227 return -1;
184 } 228 }
185 229
186 boolean Canvas_Web_Surface_Init( 230 boolean Canvas_Web_Surface_Init(
187 Canvas_Web_Surface *p_surface, 231 Canvas_Web_Surface *p_surface,
188 Dowa_Arena *p_arena) 232 Dowa_Arena *p_arena,
233 boolean dark_mode)
189 { 234 {
190 memset(p_surface, 0, sizeof(*p_surface)); 235 memset(p_surface, 0, sizeof(*p_surface));
191 p_surface->p_arena = p_arena; 236 p_surface->p_arena = p_arena;
237 p_surface->dark_mode = dark_mode;
192 p_surface->initialized = TRUE; 238 p_surface->initialized = TRUE;
193 return TRUE; 239 return TRUE;
240 }
241
242 void Canvas_Web_Surface_Set_Dark_Mode(
243 Canvas_Web_Surface *p_surface,
244 boolean dark_mode)
245 {
246 p_surface->dark_mode = dark_mode;
194 } 247 }
195 248
196 boolean Canvas_Web_Surface_Update_Input( 249 boolean Canvas_Web_Surface_Update_Input(
197 Canvas_Web_Surface *p_surface, 250 Canvas_Web_Surface *p_surface,
198 const Canvas_Camera *p_camera, 251 const Canvas_Camera *p_camera,
220 const Canvas_Entity *p_entity = &p_scene->p_entities[index]; 273 const Canvas_Entity *p_entity = &p_scene->p_entities[index];
221 if (p_entity->type != CANVAS_ENTITY_WEB_CONTENT && 274 if (p_entity->type != CANVAS_ENTITY_WEB_CONTENT &&
222 p_entity->type != CANVAS_ENTITY_IMAGE) { 275 p_entity->type != CANVAS_ENTITY_IMAGE) {
223 continue; 276 continue;
224 } 277 }
225 Rectangle bounds = Canvas_Web_Content_Bounds(p_camera, p_entity); 278 Rectangle bounds = Canvas_Web_Content_Bounds(
279 p_camera,
280 p_scene,
281 p_entity);
226 if (!Canvas_Web_Is_Visible(p_camera, bounds)) continue; 282 if (!Canvas_Web_Is_Visible(p_camera, bounds)) continue;
283 float opacity = Canvas_Web_Entity_Opacity(p_entity);
227 if (p_entity->type == CANVAS_ENTITY_IMAGE) { 284 if (p_entity->type == CANVAS_ENTITY_IMAGE) {
228 Canvas_Image_Create_Or_Update( 285 Canvas_Image_Create_Or_Update(
229 p_entity->id, 286 p_entity->id,
230 p_entity->text, 287 p_entity->text,
231 bounds.x, 288 bounds.x,
232 bounds.y, 289 bounds.y,
233 bounds.width, 290 bounds.width,
234 bounds.height); 291 bounds.height,
292 opacity);
235 } else { 293 } else {
236 Canvas_Web_Create_Or_Update( 294 Canvas_Web_Create_Or_Update(
237 p_entity->id, 295 p_entity->id,
238 p_entity->text, 296 p_entity->text,
239 bounds.x, 297 bounds.x,
240 bounds.y, 298 bounds.y,
241 bounds.width, 299 bounds.width,
242 bounds.height); 300 bounds.height,
301 opacity,
302 p_surface->dark_mode ? 1 : 0);
243 } 303 }
244 visible_views++; 304 visible_views++;
245 } 305 }
246 Canvas_Web_End_Frame(); 306 Canvas_Web_End_Frame();
247 } 307 }
252 const Canvas_Scene *p_scene) 312 const Canvas_Scene *p_scene)
253 { 313 {
254 (void)p_surface; 314 (void)p_surface;
255 (void)p_camera; 315 (void)p_camera;
256 (void)p_scene; 316 (void)p_scene;
317 }
318
319 void Canvas_Web_Surface_Draw_Entity(
320 const Canvas_Web_Surface *p_surface,
321 const Canvas_Camera *p_camera,
322 const Canvas_Scene *p_scene,
323 size_t entity_index)
324 {
325 (void)p_surface;
326 (void)p_camera;
327 (void)p_scene;
328 (void)entity_index;
329 }
330
331 const char *Canvas_Web_Surface_URL(
332 const Canvas_Web_Surface *p_surface,
333 uint32 entity_id)
334 {
335 (void)p_surface;
336 (void)entity_id;
337 return NULL;
338 }
339
340 boolean Canvas_Web_Surface_Can_Go_Back(
341 const Canvas_Web_Surface *p_surface,
342 uint32 entity_id)
343 {
344 (void)p_surface;
345 return Canvas_Web_Has_Surface(entity_id) ? TRUE : FALSE;
346 }
347
348 boolean Canvas_Web_Surface_Can_Go_Forward(
349 const Canvas_Web_Surface *p_surface,
350 uint32 entity_id)
351 {
352 (void)p_surface;
353 return Canvas_Web_Has_Surface(entity_id) ? TRUE : FALSE;
354 }
355
356 void Canvas_Web_Surface_Go_Back(
357 Canvas_Web_Surface *p_surface,
358 uint32 entity_id)
359 {
360 (void)p_surface;
361 Canvas_Web_History_Go(entity_id, -1);
362 }
363
364 void Canvas_Web_Surface_Go_Forward(
365 Canvas_Web_Surface *p_surface,
366 uint32 entity_id)
367 {
368 (void)p_surface;
369 Canvas_Web_History_Go(entity_id, 1);
257 } 370 }
258 371
259 boolean Canvas_Web_Surface_Is_Focused(const Canvas_Web_Surface *p_surface) 372 boolean Canvas_Web_Surface_Is_Focused(const Canvas_Web_Surface *p_surface)
260 { 373 {
261 (void)p_surface; 374 (void)p_surface;