diff 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
line wrap: on
line diff
--- a/infinite_canvas/canvas_test.c	Mon Aug 17 17:01:40 2026 -0700
+++ b/infinite_canvas/canvas_test.c	Mon Aug 17 22:16:14 2026 -0700
@@ -65,6 +65,17 @@
     assert(dark.resolved_mode == CANVAS_THEME_DARK);
     assert(dark.background.r == 15);
     assert(dark.toast_surface.r == 24);
+    assert(light.switch_track_on.g > light.switch_track_on.b);
+    assert(dark.switch_track_on.g > dark.switch_track_on.b);
+    assert(light.switch_track_on.r != light.accent.r);
+    assert(dark.switch_track_on.r != dark.accent.r);
+    assert(light.pin.r > light.pin.b);
+    assert(dark.pin.r > dark.pin.b);
+    assert(light.pin.r != light.accent.r);
+    assert(dark.pin.r != dark.accent.r);
+    assert(light.button.r != dark.button.r);
+    assert(light.selection.r != dark.selection.r);
+    assert(light.danger.r != dark.danger.r);
     assert(Canvas_Theme_Mode_From_String("light") == CANVAS_THEME_LIGHT);
     assert(Canvas_Theme_Mode_From_String("dark") == CANVAS_THEME_DARK);
     assert(Canvas_Theme_Mode_From_String("invalid") == CANVAS_THEME_AUTO);
@@ -157,6 +168,33 @@
     Dowa_Arena_Free(p_arena);
 }
 
+static void Test_Overlapping_Entities_Use_Topmost_Z_Order(void)
+{
+    Dowa_Arena *p_arena = Dowa_Arena_Create(4 * ONE_MEGA_BYTE);
+    assert(p_arena);
+
+    Canvas_Scene scene;
+    Canvas_Scene_Init(&scene, p_arena);
+    Vector2 position = {0.0f, 0.0f};
+    Vector2 pointer = {40.0f, 30.0f};
+    assert(Canvas_Scene_Add(&scene, CANVAS_ENTITY_BUTTON, position));
+    assert(Canvas_Scene_Add(&scene, CANVAS_ENTITY_BUTTON, position));
+    assert(Canvas_Scene_Pick(&scene, pointer, 1.0f) == 1);
+    assert(Canvas_Scene_Topmost_Visual(&scene, pointer, 1.0f) == 1);
+
+    scene.p_entities[1].removing = TRUE;
+    assert(Canvas_Scene_Pick(&scene, pointer, 1.0f) == 0);
+    assert(Canvas_Scene_Topmost_Visual(&scene, pointer, 1.0f) == 0);
+
+    assert(Canvas_Scene_Add(&scene, CANVAS_ENTITY_WEB_CONTENT, position));
+    assert(Canvas_Scene_Topmost_Visual(&scene, pointer, 1.0f) == 2);
+    assert(Canvas_Scene_Add(&scene, CANVAS_ENTITY_BUTTON, position));
+    assert(Canvas_Scene_Pick(&scene, pointer, 1.0f) == 3);
+    assert(Canvas_Scene_Topmost_Visual(&scene, pointer, 1.0f) == 3);
+
+    Dowa_Arena_Free(p_arena);
+}
+
 static void Test_New_Primitive_Defaults(void)
 {
     Dowa_Arena *p_arena = Dowa_Arena_Create(ONE_MEGA_BYTE);
@@ -206,6 +244,9 @@
     Canvas_Scene_Init(&scene, p_arena);
     assert(!Canvas_Scene_Is_Text_Editing(&scene));
     assert(Canvas_Scene_Add(&scene, CANVAS_ENTITY_TEXT_AREA, (Vector2){0.0f, 0.0f}));
+    assert(sizeof(scene.p_entities[0].text) == CANVAS_ENTITY_TEXT_CAPACITY);
+    assert(scene.p_entities[0].text_cursor == 0);
+    assert(scene.p_entities[0].text_selection_anchor == 0);
 
     scene.selected_index = 0;
     assert(!Canvas_Scene_Is_Text_Editing(&scene));
@@ -233,6 +274,37 @@
     Dowa_Arena_Free(p_arena);
 }
 
+static void Test_Entity_Fade_Lifecycle_Commands(void)
+{
+    Dowa_Arena *p_arena = Dowa_Arena_Create(4 * ONE_MEGA_BYTE);
+    assert(p_arena);
+
+    Canvas_Scene scene;
+    Canvas_Scene_Init(&scene, p_arena);
+    assert(Canvas_Scene_Add(
+        &scene,
+        CANVAS_ENTITY_RECTANGLE,
+        (Vector2){0.0f, 0.0f}));
+    assert(Canvas_Scene_Add(
+        &scene,
+        CANVAS_ENTITY_TEXT,
+        (Vector2){200.0f, 0.0f}));
+    assert(Near(scene.p_entities[0].visibility_amount, 0.0f));
+    assert(!scene.p_entities[0].removing);
+
+    scene.selected_index = 0;
+    Canvas_Scene_Fade_Out_Selected(&scene);
+    assert(scene.p_entities[0].removing);
+    assert(scene.selected_index == -1);
+    assert(!scene.p_entities[1].removing);
+
+    Canvas_Scene_Fade_Out_All(&scene);
+    assert(scene.p_entities[0].removing);
+    assert(scene.p_entities[1].removing);
+
+    Dowa_Arena_Free(p_arena);
+}
+
 static void Test_Browser_Grid(void)
 {
     Dowa_Arena *p_arena = Dowa_Arena_Create(ONE_MEGA_BYTE);
@@ -251,6 +323,53 @@
     Dowa_Arena_Free(p_arena);
 }
 
+static void Test_Lucide_Gallery_Reuses_Entity_And_Fits_Camera(void)
+{
+    Dowa_Arena *p_arena = Dowa_Arena_Create(ONE_MEGA_BYTE);
+    assert(p_arena);
+
+    Canvas_Camera camera;
+    Canvas_Camera_Init(&camera, 1920, 1200);
+    Canvas_Scene scene;
+    Canvas_Scene_Init(&scene, p_arena);
+
+    assert(Canvas_Scene_Show_Lucide_Gallery(&scene, &camera));
+    assert(Dowa_Array_Length(scene.p_entities) == 1);
+    assert(scene.selected_index == 0);
+    Canvas_Entity *p_gallery = &scene.p_entities[0];
+    assert(p_gallery->type == CANVAS_ENTITY_LUCIDE_GALLERY);
+    assert(strcmp(p_gallery->text, "") == 0);
+    assert(Near(p_gallery->size.x, 1440.0f));
+    assert(Near(p_gallery->size.y, 1040.0f));
+
+    Vector2 screen_min =
+        Canvas_Camera_World_To_Screen(&camera, p_gallery->position);
+    Vector2 screen_max = Canvas_Camera_World_To_Screen(
+        &camera,
+        (Vector2){
+            p_gallery->position.x + p_gallery->size.x,
+            p_gallery->position.y + p_gallery->size.y,
+        });
+    assert(screen_min.x >= 319.0f);
+    assert(screen_min.y >= 55.0f);
+    assert(screen_max.x <= 1865.0f);
+    assert(screen_max.y <= 1145.0f);
+
+    assert(Canvas_Scene_Show_Lucide_Gallery(&scene, &camera));
+    assert(Dowa_Array_Length(scene.p_entities) == 1);
+
+    Dowa_Arena_Free(p_arena);
+}
+
+static void Test_Lucide_Search_Is_Case_Insensitive(void)
+{
+    assert(Canvas_Lucide_Count_Matches("") == 1767);
+    assert(Canvas_Lucide_Count_Matches("pin") > 0);
+    assert(Canvas_Lucide_Count_Matches("PIN") ==
+        Canvas_Lucide_Count_Matches("pin"));
+    assert(Canvas_Lucide_Count_Matches("not-a-real-lucide-icon") == 0);
+}
+
 static void Test_Demo_Contains_Every_Entity_Type(void)
 {
     Dowa_Arena *p_arena = Dowa_Arena_Create(ONE_MEGA_BYTE);
@@ -259,13 +378,17 @@
     Canvas_Scene scene;
     Canvas_Scene_Init(&scene, p_arena);
     Canvas_Scene_Add_Demo(&scene);
-    assert(Dowa_Array_Length(scene.p_entities) == CANVAS_ENTITY_TYPE_COUNT + 2);
+    assert(Dowa_Array_Length(scene.p_entities) == CANVAS_ENTITY_TYPE_COUNT + 1);
 
     int32 counts[CANVAS_ENTITY_TYPE_COUNT] = {0};
     for (size_t index = 0; index < Dowa_Array_Length(scene.p_entities); index++) {
         counts[scene.p_entities[index].type]++;
     }
     for (int32 type = 0; type < CANVAS_ENTITY_TYPE_COUNT; type++) {
+        if (type == CANVAS_ENTITY_LUCIDE_GALLERY) {
+            assert(counts[type] == 0);
+            continue;
+        }
         int32 expected = type == CANVAS_ENTITY_NOTIFICATION ? 3 : 1;
         assert(counts[type] == expected);
     }
@@ -348,6 +471,8 @@
 
     snprintf(scene.p_entities[0].text, sizeof(scene.p_entities[0].text), "Visible note");
     scene.p_entities[0].active = TRUE;
+    scene.p_entities[0].text_cursor = 7;
+    scene.p_entities[0].text_selection_anchor = 2;
     scene.p_entities[1].value = 2;
     scene.p_entities[1].active = TRUE;
     scene.p_entities[2].active = TRUE;
@@ -362,6 +487,8 @@
     assert(!snapshot.truncated);
     assert(strstr(context, "Visible note"));
     assert(strstr(context, "editing=true"));
+    assert(strstr(context, "cursor=7"));
+    assert(strstr(context, "selection_anchor=2"));
     assert(!strstr(context, "Research"));
     assert(!strstr(context, "checked=true"));
 
@@ -407,10 +534,14 @@
     Test_Viewport_Bounds();
     Test_Grid_Spacing();
     Test_Entity_Picking();
+    Test_Overlapping_Entities_Use_Topmost_Z_Order();
     Test_New_Primitive_Defaults();
     Test_Text_Editing_Focus();
     Test_Entity_Ids_Remain_Unique_After_Clear();
+    Test_Entity_Fade_Lifecycle_Commands();
     Test_Browser_Grid();
+    Test_Lucide_Gallery_Reuses_Entity_And_Fits_Camera();
+    Test_Lucide_Search_Is_Case_Insensitive();
     Test_Demo_Contains_Every_Entity_Type();
     Test_Pinned_Entity_Stays_In_Screen_Space();
     Test_Visible_Context_Tracks_Camera_And_Values();