comparison infinite_canvas/canvas_test.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
63 assert(light.background.r == 255); 63 assert(light.background.r == 255);
64 assert(light.toast_surface.r == 255); 64 assert(light.toast_surface.r == 255);
65 assert(dark.resolved_mode == CANVAS_THEME_DARK); 65 assert(dark.resolved_mode == CANVAS_THEME_DARK);
66 assert(dark.background.r == 15); 66 assert(dark.background.r == 15);
67 assert(dark.toast_surface.r == 24); 67 assert(dark.toast_surface.r == 24);
68 assert(light.switch_track_on.g > light.switch_track_on.b);
69 assert(dark.switch_track_on.g > dark.switch_track_on.b);
70 assert(light.switch_track_on.r != light.accent.r);
71 assert(dark.switch_track_on.r != dark.accent.r);
72 assert(light.pin.r > light.pin.b);
73 assert(dark.pin.r > dark.pin.b);
74 assert(light.pin.r != light.accent.r);
75 assert(dark.pin.r != dark.accent.r);
76 assert(light.button.r != dark.button.r);
77 assert(light.selection.r != dark.selection.r);
78 assert(light.danger.r != dark.danger.r);
68 assert(Canvas_Theme_Mode_From_String("light") == CANVAS_THEME_LIGHT); 79 assert(Canvas_Theme_Mode_From_String("light") == CANVAS_THEME_LIGHT);
69 assert(Canvas_Theme_Mode_From_String("dark") == CANVAS_THEME_DARK); 80 assert(Canvas_Theme_Mode_From_String("dark") == CANVAS_THEME_DARK);
70 assert(Canvas_Theme_Mode_From_String("invalid") == CANVAS_THEME_AUTO); 81 assert(Canvas_Theme_Mode_From_String("invalid") == CANVAS_THEME_AUTO);
71 82
72 Canvas_Theme_Toggle(&light); 83 Canvas_Theme_Toggle(&light);
155 assert(Canvas_Scene_Pick(&scene, (Vector2){5500.0f, 500.0f}, 1.0f) == -1); 166 assert(Canvas_Scene_Pick(&scene, (Vector2){5500.0f, 500.0f}, 1.0f) == -1);
156 167
157 Dowa_Arena_Free(p_arena); 168 Dowa_Arena_Free(p_arena);
158 } 169 }
159 170
171 static void Test_Overlapping_Entities_Use_Topmost_Z_Order(void)
172 {
173 Dowa_Arena *p_arena = Dowa_Arena_Create(4 * ONE_MEGA_BYTE);
174 assert(p_arena);
175
176 Canvas_Scene scene;
177 Canvas_Scene_Init(&scene, p_arena);
178 Vector2 position = {0.0f, 0.0f};
179 Vector2 pointer = {40.0f, 30.0f};
180 assert(Canvas_Scene_Add(&scene, CANVAS_ENTITY_BUTTON, position));
181 assert(Canvas_Scene_Add(&scene, CANVAS_ENTITY_BUTTON, position));
182 assert(Canvas_Scene_Pick(&scene, pointer, 1.0f) == 1);
183 assert(Canvas_Scene_Topmost_Visual(&scene, pointer, 1.0f) == 1);
184
185 scene.p_entities[1].removing = TRUE;
186 assert(Canvas_Scene_Pick(&scene, pointer, 1.0f) == 0);
187 assert(Canvas_Scene_Topmost_Visual(&scene, pointer, 1.0f) == 0);
188
189 assert(Canvas_Scene_Add(&scene, CANVAS_ENTITY_WEB_CONTENT, position));
190 assert(Canvas_Scene_Topmost_Visual(&scene, pointer, 1.0f) == 2);
191 assert(Canvas_Scene_Add(&scene, CANVAS_ENTITY_BUTTON, position));
192 assert(Canvas_Scene_Pick(&scene, pointer, 1.0f) == 3);
193 assert(Canvas_Scene_Topmost_Visual(&scene, pointer, 1.0f) == 3);
194
195 Dowa_Arena_Free(p_arena);
196 }
197
160 static void Test_New_Primitive_Defaults(void) 198 static void Test_New_Primitive_Defaults(void)
161 { 199 {
162 Dowa_Arena *p_arena = Dowa_Arena_Create(ONE_MEGA_BYTE); 200 Dowa_Arena *p_arena = Dowa_Arena_Create(ONE_MEGA_BYTE);
163 assert(p_arena); 201 assert(p_arena);
164 202
204 242
205 Canvas_Scene scene; 243 Canvas_Scene scene;
206 Canvas_Scene_Init(&scene, p_arena); 244 Canvas_Scene_Init(&scene, p_arena);
207 assert(!Canvas_Scene_Is_Text_Editing(&scene)); 245 assert(!Canvas_Scene_Is_Text_Editing(&scene));
208 assert(Canvas_Scene_Add(&scene, CANVAS_ENTITY_TEXT_AREA, (Vector2){0.0f, 0.0f})); 246 assert(Canvas_Scene_Add(&scene, CANVAS_ENTITY_TEXT_AREA, (Vector2){0.0f, 0.0f}));
247 assert(sizeof(scene.p_entities[0].text) == CANVAS_ENTITY_TEXT_CAPACITY);
248 assert(scene.p_entities[0].text_cursor == 0);
249 assert(scene.p_entities[0].text_selection_anchor == 0);
209 250
210 scene.selected_index = 0; 251 scene.selected_index = 0;
211 assert(!Canvas_Scene_Is_Text_Editing(&scene)); 252 assert(!Canvas_Scene_Is_Text_Editing(&scene));
212 scene.p_entities[0].active = TRUE; 253 scene.p_entities[0].active = TRUE;
213 assert(Canvas_Scene_Is_Text_Editing(&scene)); 254 assert(Canvas_Scene_Is_Text_Editing(&scene));
227 assert(Canvas_Scene_Add(&scene, CANVAS_ENTITY_WEB_CONTENT, (Vector2){0.0f, 0.0f})); 268 assert(Canvas_Scene_Add(&scene, CANVAS_ENTITY_WEB_CONTENT, (Vector2){0.0f, 0.0f}));
228 uint32 first_id = scene.p_entities[0].id; 269 uint32 first_id = scene.p_entities[0].id;
229 Canvas_Scene_Clear(&scene); 270 Canvas_Scene_Clear(&scene);
230 assert(Canvas_Scene_Add(&scene, CANVAS_ENTITY_WEB_CONTENT, (Vector2){0.0f, 0.0f})); 271 assert(Canvas_Scene_Add(&scene, CANVAS_ENTITY_WEB_CONTENT, (Vector2){0.0f, 0.0f}));
231 assert(scene.p_entities[0].id != first_id); 272 assert(scene.p_entities[0].id != first_id);
273
274 Dowa_Arena_Free(p_arena);
275 }
276
277 static void Test_Entity_Fade_Lifecycle_Commands(void)
278 {
279 Dowa_Arena *p_arena = Dowa_Arena_Create(4 * ONE_MEGA_BYTE);
280 assert(p_arena);
281
282 Canvas_Scene scene;
283 Canvas_Scene_Init(&scene, p_arena);
284 assert(Canvas_Scene_Add(
285 &scene,
286 CANVAS_ENTITY_RECTANGLE,
287 (Vector2){0.0f, 0.0f}));
288 assert(Canvas_Scene_Add(
289 &scene,
290 CANVAS_ENTITY_TEXT,
291 (Vector2){200.0f, 0.0f}));
292 assert(Near(scene.p_entities[0].visibility_amount, 0.0f));
293 assert(!scene.p_entities[0].removing);
294
295 scene.selected_index = 0;
296 Canvas_Scene_Fade_Out_Selected(&scene);
297 assert(scene.p_entities[0].removing);
298 assert(scene.selected_index == -1);
299 assert(!scene.p_entities[1].removing);
300
301 Canvas_Scene_Fade_Out_All(&scene);
302 assert(scene.p_entities[0].removing);
303 assert(scene.p_entities[1].removing);
232 304
233 Dowa_Arena_Free(p_arena); 305 Dowa_Arena_Free(p_arena);
234 } 306 }
235 307
236 static void Test_Browser_Grid(void) 308 static void Test_Browser_Grid(void)
249 } 321 }
250 322
251 Dowa_Arena_Free(p_arena); 323 Dowa_Arena_Free(p_arena);
252 } 324 }
253 325
326 static void Test_Lucide_Gallery_Reuses_Entity_And_Fits_Camera(void)
327 {
328 Dowa_Arena *p_arena = Dowa_Arena_Create(ONE_MEGA_BYTE);
329 assert(p_arena);
330
331 Canvas_Camera camera;
332 Canvas_Camera_Init(&camera, 1920, 1200);
333 Canvas_Scene scene;
334 Canvas_Scene_Init(&scene, p_arena);
335
336 assert(Canvas_Scene_Show_Lucide_Gallery(&scene, &camera));
337 assert(Dowa_Array_Length(scene.p_entities) == 1);
338 assert(scene.selected_index == 0);
339 Canvas_Entity *p_gallery = &scene.p_entities[0];
340 assert(p_gallery->type == CANVAS_ENTITY_LUCIDE_GALLERY);
341 assert(strcmp(p_gallery->text, "") == 0);
342 assert(Near(p_gallery->size.x, 1440.0f));
343 assert(Near(p_gallery->size.y, 1040.0f));
344
345 Vector2 screen_min =
346 Canvas_Camera_World_To_Screen(&camera, p_gallery->position);
347 Vector2 screen_max = Canvas_Camera_World_To_Screen(
348 &camera,
349 (Vector2){
350 p_gallery->position.x + p_gallery->size.x,
351 p_gallery->position.y + p_gallery->size.y,
352 });
353 assert(screen_min.x >= 319.0f);
354 assert(screen_min.y >= 55.0f);
355 assert(screen_max.x <= 1865.0f);
356 assert(screen_max.y <= 1145.0f);
357
358 assert(Canvas_Scene_Show_Lucide_Gallery(&scene, &camera));
359 assert(Dowa_Array_Length(scene.p_entities) == 1);
360
361 Dowa_Arena_Free(p_arena);
362 }
363
364 static void Test_Lucide_Search_Is_Case_Insensitive(void)
365 {
366 assert(Canvas_Lucide_Count_Matches("") == 1767);
367 assert(Canvas_Lucide_Count_Matches("pin") > 0);
368 assert(Canvas_Lucide_Count_Matches("PIN") ==
369 Canvas_Lucide_Count_Matches("pin"));
370 assert(Canvas_Lucide_Count_Matches("not-a-real-lucide-icon") == 0);
371 }
372
254 static void Test_Demo_Contains_Every_Entity_Type(void) 373 static void Test_Demo_Contains_Every_Entity_Type(void)
255 { 374 {
256 Dowa_Arena *p_arena = Dowa_Arena_Create(ONE_MEGA_BYTE); 375 Dowa_Arena *p_arena = Dowa_Arena_Create(ONE_MEGA_BYTE);
257 assert(p_arena); 376 assert(p_arena);
258 377
259 Canvas_Scene scene; 378 Canvas_Scene scene;
260 Canvas_Scene_Init(&scene, p_arena); 379 Canvas_Scene_Init(&scene, p_arena);
261 Canvas_Scene_Add_Demo(&scene); 380 Canvas_Scene_Add_Demo(&scene);
262 assert(Dowa_Array_Length(scene.p_entities) == CANVAS_ENTITY_TYPE_COUNT + 2); 381 assert(Dowa_Array_Length(scene.p_entities) == CANVAS_ENTITY_TYPE_COUNT + 1);
263 382
264 int32 counts[CANVAS_ENTITY_TYPE_COUNT] = {0}; 383 int32 counts[CANVAS_ENTITY_TYPE_COUNT] = {0};
265 for (size_t index = 0; index < Dowa_Array_Length(scene.p_entities); index++) { 384 for (size_t index = 0; index < Dowa_Array_Length(scene.p_entities); index++) {
266 counts[scene.p_entities[index].type]++; 385 counts[scene.p_entities[index].type]++;
267 } 386 }
268 for (int32 type = 0; type < CANVAS_ENTITY_TYPE_COUNT; type++) { 387 for (int32 type = 0; type < CANVAS_ENTITY_TYPE_COUNT; type++) {
388 if (type == CANVAS_ENTITY_LUCIDE_GALLERY) {
389 assert(counts[type] == 0);
390 continue;
391 }
269 int32 expected = type == CANVAS_ENTITY_NOTIFICATION ? 3 : 1; 392 int32 expected = type == CANVAS_ENTITY_NOTIFICATION ? 3 : 1;
270 assert(counts[type] == expected); 393 assert(counts[type] == expected);
271 } 394 }
272 for (size_t index = 0; index < Dowa_Array_Length(scene.p_entities); index++) { 395 for (size_t index = 0; index < Dowa_Array_Length(scene.p_entities); index++) {
273 if (scene.p_entities[index].type == CANVAS_ENTITY_NOTIFICATION) continue; 396 if (scene.p_entities[index].type == CANVAS_ENTITY_NOTIFICATION) continue;
346 CANVAS_ENTITY_SWITCH, 469 CANVAS_ENTITY_SWITCH,
347 (Vector2){700.0f, 0.0f})); 470 (Vector2){700.0f, 0.0f}));
348 471
349 snprintf(scene.p_entities[0].text, sizeof(scene.p_entities[0].text), "Visible note"); 472 snprintf(scene.p_entities[0].text, sizeof(scene.p_entities[0].text), "Visible note");
350 scene.p_entities[0].active = TRUE; 473 scene.p_entities[0].active = TRUE;
474 scene.p_entities[0].text_cursor = 7;
475 scene.p_entities[0].text_selection_anchor = 2;
351 scene.p_entities[1].value = 2; 476 scene.p_entities[1].value = 2;
352 scene.p_entities[1].active = TRUE; 477 scene.p_entities[1].active = TRUE;
353 scene.p_entities[2].active = TRUE; 478 scene.p_entities[2].active = TRUE;
354 479
355 char context[4096]; 480 char context[4096];
360 sizeof(context)); 485 sizeof(context));
361 assert(snapshot.visible_count == 1); 486 assert(snapshot.visible_count == 1);
362 assert(!snapshot.truncated); 487 assert(!snapshot.truncated);
363 assert(strstr(context, "Visible note")); 488 assert(strstr(context, "Visible note"));
364 assert(strstr(context, "editing=true")); 489 assert(strstr(context, "editing=true"));
490 assert(strstr(context, "cursor=7"));
491 assert(strstr(context, "selection_anchor=2"));
365 assert(!strstr(context, "Research")); 492 assert(!strstr(context, "Research"));
366 assert(!strstr(context, "checked=true")); 493 assert(!strstr(context, "checked=true"));
367 494
368 camera.zoom = 0.25f; 495 camera.zoom = 0.25f;
369 snapshot = Canvas_Scene_Build_Visible_Context( 496 snapshot = Canvas_Scene_Build_Visible_Context(
405 Test_Camera_Round_Trip(); 532 Test_Camera_Round_Trip();
406 Test_Anchored_Zoom(); 533 Test_Anchored_Zoom();
407 Test_Viewport_Bounds(); 534 Test_Viewport_Bounds();
408 Test_Grid_Spacing(); 535 Test_Grid_Spacing();
409 Test_Entity_Picking(); 536 Test_Entity_Picking();
537 Test_Overlapping_Entities_Use_Topmost_Z_Order();
410 Test_New_Primitive_Defaults(); 538 Test_New_Primitive_Defaults();
411 Test_Text_Editing_Focus(); 539 Test_Text_Editing_Focus();
412 Test_Entity_Ids_Remain_Unique_After_Clear(); 540 Test_Entity_Ids_Remain_Unique_After_Clear();
541 Test_Entity_Fade_Lifecycle_Commands();
413 Test_Browser_Grid(); 542 Test_Browser_Grid();
543 Test_Lucide_Gallery_Reuses_Entity_And_Fits_Camera();
544 Test_Lucide_Search_Is_Case_Insensitive();
414 Test_Demo_Contains_Every_Entity_Type(); 545 Test_Demo_Contains_Every_Entity_Type();
415 Test_Pinned_Entity_Stays_In_Screen_Space(); 546 Test_Pinned_Entity_Stays_In_Screen_Space();
416 Test_Visible_Context_Tracks_Camera_And_Values(); 547 Test_Visible_Context_Tracks_Camera_And_Values();
417 printf("Infinite canvas tests passed.\n"); 548 printf("Infinite canvas tests passed.\n");
418 return 0; 549 return 0;