Mercurial
view infinite_canvas/main.c @ 281:c57149ad216e default tip
Copilot-Session: f68442b1-fa8f-46a0-9689-81710613bbd4
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Tue, 18 Aug 2026 22:18:15 -0700 |
| parents | 49e9e591c9bb |
| children |
line wrap: on
line source
#include "infinite_canvas/agent_response.h" #include "infinite_canvas/agent_service.h" #include "infinite_canvas/canvas.h" #include "infinite_canvas/scene_store.h" #include "infinite_canvas/web_surface.h" #if defined(INFINITE_CANVAS_DEV_UI) #include "infinite_canvas/dev_ui.h" #endif #if defined(PLATFORM_WEB) #include <emscripten/emscripten.h> #endif #include <math.h> #include <stdio.h> #include <stdlib.h> #include <string.h> typedef struct { Dowa_Arena *p_arena; Canvas_Camera camera; Canvas_Scene scene; Canvas_Web_Surface web_surface; Canvas_Agent_Service agent_service; Font font; Canvas_Theme theme; boolean owns_font; const char *p_screenshot_path; uint32 frame_count; uint32 screenshot_frame; Vector2 pending_indicator_position; Vector2 pending_indicator_drag_offset; uint32 dictation_entity_id; boolean agent_initialized; boolean pending_indicator_visible; boolean pending_indicator_hovered; boolean pending_indicator_dragging; float pending_indicator_hover_amount; boolean dictation_overlay_visible; boolean dictation_listening; boolean dictation_requested_active; boolean dictation_send_pending; char dictation_status[128]; char dictation_partial[CANVAS_ENTITY_TEXT_CAPACITY]; char dictation_transcript[CANVAS_ENTITY_TEXT_CAPACITY]; char pending_prompt[CANVAS_ENTITY_TEXT_CAPACITY]; char agent_context[CANVAS_CONTEXT_MAX_LENGTH]; #if defined(INFINITE_CANVAS_DEV_UI) Canvas_Dev_UI dev_ui; #endif } Canvas_App; static Canvas_App *g_app = NULL; static Canvas_Entity *Canvas_App_Find_Entity(uint32 entity_id) { for (size_t index = 0; index < Dowa_Array_Length(g_app->scene.p_entities); index++) { if (g_app->scene.p_entities[index].id == entity_id) { return &g_app->scene.p_entities[index]; } } return NULL; } static boolean Canvas_App_Submit_Prompt(const char *p_prompt) { if (!p_prompt || !p_prompt[0]) return FALSE; Canvas_Scene_Build_Visible_Context( &g_app->scene, &g_app->camera, g_app->agent_context, sizeof(g_app->agent_context)); if (!Canvas_Agent_Service_Submit( &g_app->agent_service, p_prompt, g_app->agent_context)) { return FALSE; } snprintf( g_app->pending_prompt, sizeof(g_app->pending_prompt), "%s", p_prompt); g_app->pending_indicator_position = Canvas_Camera_Screen_To_World( &g_app->camera, (Vector2){ (float)g_app->camera.viewport_width * 0.5f + 240.0f, (float)g_app->camera.viewport_height * 0.5f - 150.0f, }); g_app->pending_indicator_visible = TRUE; g_app->pending_indicator_hovered = FALSE; g_app->pending_indicator_dragging = FALSE; g_app->pending_indicator_hover_amount = 0.0f; return TRUE; } static uint32 Canvas_App_Materialize_Agent_Entities( Canvas_Agent_Response *p_response) { uint32 created = 0; for (uint32 index = 0; index < p_response->entity_count; index++) { Canvas_Agent_Entity_Spec *p_spec = &p_response->entities[index]; Vector2 position = p_spec->has_position ? p_spec->position : (Vector2){ g_app->pending_indicator_position.x + (float)(index % 2) * 280.0f, g_app->pending_indicator_position.y + (float)(index / 2) * 240.0f, }; if (!Canvas_Scene_Add(&g_app->scene, p_spec->type, position)) { break; } Canvas_Entity *p_entity = &g_app->scene.p_entities[ Dowa_Array_Length(g_app->scene.p_entities) - 1]; if (p_spec->label[0]) { snprintf( p_entity->label, sizeof(p_entity->label), "%s", p_spec->label); } if (p_spec->text[0]) { snprintf( p_entity->text, sizeof(p_entity->text), "%s", p_spec->text); p_entity->text_cursor = (int32)strlen(p_entity->text); p_entity->text_selection_anchor = p_entity->text_cursor; } if (p_spec->has_size) p_entity->size = p_spec->size; if (p_spec->has_value) p_entity->value = p_spec->value; created++; } return created; } static void Canvas_App_Show_Agent_Notification( const char *p_title, const char *p_text) { if (!Canvas_Scene_Add( &g_app->scene, CANVAS_ENTITY_NOTIFICATION, g_app->pending_indicator_position)) { return; } Canvas_Entity *p_entity = &g_app->scene.p_entities[ Dowa_Array_Length(g_app->scene.p_entities) - 1]; if (p_title && p_title[0]) { snprintf( p_entity->label, sizeof(p_entity->label), "%s", p_title); } snprintf( p_entity->text, sizeof(p_entity->text), "%s", p_text ? p_text : ""); } static void Canvas_App_Apply_Agent_Response(const char *p_json) { Canvas_Agent_Response parsed; boolean valid = Canvas_Agent_Response_Parse(p_json, &parsed); if (!valid) { memset(&parsed, 0, sizeof(parsed)); snprintf(parsed.action, sizeof(parsed.action), "create"); snprintf( parsed.presentation, sizeof(parsed.presentation), "conversation"); snprintf( parsed.response, sizeof(parsed.response), "%s", p_json); } uint32 created_entities = Canvas_App_Materialize_Agent_Entities(&parsed); if (strcmp(parsed.action, "append") == 0 && parsed.conversation_id != 0 && Canvas_Scene_Append_Conversation( &g_app->scene, parsed.conversation_id, g_app->pending_prompt, parsed.response)) { Canvas_Entity *p_conversation = Canvas_App_Find_Entity(parsed.conversation_id); Canvas_Conversation_Scroll_To_End( p_conversation, g_app->font); } else if (strcmp(parsed.presentation, "action") == 0) { if (created_entities == 0) { Canvas_App_Show_Agent_Notification( parsed.title[0] ? parsed.title : "Copilot", parsed.response); } } else { Vector2 position = { g_app->pending_indicator_position.x - 210.0f, g_app->pending_indicator_position.y - 150.0f, }; uint32 created_id = Canvas_Scene_Add_Conversation( &g_app->scene, parsed.title[0] ? parsed.title : "Copilot session", g_app->pending_prompt, parsed.response, position); Canvas_Conversation_Scroll_To_End( Canvas_App_Find_Entity(created_id), g_app->font); } g_app->pending_indicator_visible = FALSE; g_app->pending_prompt[0] = '\0'; } static Canvas_Entity *Canvas_App_Ensure_Dictation_Entity(void) { Canvas_Entity *p_entity = Canvas_App_Find_Entity(g_app->dictation_entity_id); if (p_entity) return p_entity; Vector2 center = Canvas_Camera_Screen_To_World( &g_app->camera, (Vector2){ (float)g_app->camera.viewport_width * 0.5f, (float)g_app->camera.viewport_height * 0.5f, }); Vector2 size = {520.0f, 240.0f}; if (!Canvas_Scene_Add( &g_app->scene, CANVAS_ENTITY_TEXT_AREA, (Vector2){ center.x - size.x * 0.5f, center.y - size.y * 0.5f, })) { return NULL; } size_t index = Dowa_Array_Length(g_app->scene.p_entities) - 1; p_entity = &g_app->scene.p_entities[index]; p_entity->size = size; snprintf(p_entity->label, sizeof(p_entity->label), "Dictation"); g_app->dictation_entity_id = p_entity->id; g_app->scene.selected_index = (int32)index; return p_entity; } static void Canvas_App_Sync_Dictation_Entity(void) { Canvas_Entity *p_entity = Canvas_App_Find_Entity(g_app->dictation_entity_id); if (!p_entity) return; char text[CANVAS_ENTITY_TEXT_CAPACITY]; snprintf(text, sizeof(text), "%s", g_app->dictation_transcript); size_t length = strlen(text); if (length > 0 && g_app->dictation_partial[0] && length + 1 < sizeof(text)) { text[length++] = ' '; text[length] = '\0'; } snprintf( text + length, sizeof(text) - length, "%s", g_app->dictation_partial); Canvas_Text_Area_Set_Content( p_entity, g_app->font, text, g_app->dictation_partial[0] ? TRUE : FALSE); } static void Canvas_App_Set_Dictation_Active(boolean active) { g_app->dictation_overlay_visible = TRUE; g_app->dictation_requested_active = active; g_app->dictation_listening = FALSE; g_app->dictation_send_pending = FALSE; if (active) { Canvas_App_Ensure_Dictation_Entity(); Canvas_App_Sync_Dictation_Entity(); } snprintf( g_app->dictation_status, sizeof(g_app->dictation_status), "%s", active ? "Starting microphone..." : "Stopping microphone..."); Canvas_Web_Surface_Set_Dictation_Active( &g_app->web_surface, active); } static boolean Canvas_App_Submit_Dictation(void) { if (!g_app->dictation_transcript[0]) return FALSE; if (!Canvas_App_Submit_Prompt(g_app->dictation_transcript)) { g_app->dictation_send_pending = FALSE; snprintf( g_app->dictation_status, sizeof(g_app->dictation_status), "Copilot is busy; press Enter to retry"); return FALSE; } Canvas_Entity *p_entity = Canvas_App_Find_Entity(g_app->dictation_entity_id); if (p_entity) p_entity->text_muted = FALSE; g_app->dictation_transcript[0] = '\0'; g_app->dictation_partial[0] = '\0'; Canvas_App_Sync_Dictation_Entity(); g_app->dictation_send_pending = FALSE; g_app->dictation_overlay_visible = g_app->dictation_listening; return TRUE; } static void Canvas_App_Append_Dictation(const char *p_text) { if (!p_text || !p_text[0]) return; size_t length = strlen(g_app->dictation_transcript); if (length > 0 && length + 1 < sizeof(g_app->dictation_transcript)) { g_app->dictation_transcript[length++] = ' '; g_app->dictation_transcript[length] = '\0'; } size_t available = sizeof(g_app->dictation_transcript) - length - 1; size_t text_length = strlen(p_text); size_t copy_length = text_length < available ? text_length : available; memcpy(g_app->dictation_transcript + length, p_text, copy_length); g_app->dictation_transcript[length + copy_length] = '\0'; Canvas_App_Sync_Dictation_Entity(); } static void Canvas_App_Update_Dictation_Overlay_Input(void) { if (!g_app->dictation_overlay_visible) return; if (g_app->dictation_listening && IsKeyPressed(KEY_BACKSPACE)) { g_app->dictation_send_pending = FALSE; g_app->dictation_partial[0] = '\0'; g_app->dictation_transcript[0] = '\0'; Canvas_App_Sync_Dictation_Entity(); snprintf( g_app->dictation_status, sizeof(g_app->dictation_status), "Listening"); return; } if (IsKeyPressed(KEY_ESCAPE)) { if (g_app->dictation_listening) { Canvas_Web_Surface_Set_Dictation_Active( &g_app->web_surface, FALSE); g_app->dictation_requested_active = FALSE; g_app->dictation_listening = FALSE; } g_app->dictation_send_pending = FALSE; g_app->dictation_partial[0] = '\0'; g_app->dictation_transcript[0] = '\0'; Canvas_App_Sync_Dictation_Entity(); g_app->dictation_overlay_visible = FALSE; return; } if (!IsKeyPressed(KEY_ENTER)) return; if (g_app->dictation_partial[0]) { g_app->dictation_send_pending = TRUE; snprintf( g_app->dictation_status, sizeof(g_app->dictation_status), "Finishing dictation..."); Canvas_Web_Surface_Commit_Dictation( &g_app->web_surface); } else if (g_app->dictation_transcript[0]) { Canvas_App_Submit_Dictation(); } } static boolean Canvas_App_Dictation_Status_Is_Error(const char *p_status) { return strstr(p_status, "failed") != NULL || strstr(p_status, "Unable") != NULL || strstr(p_status, "closed") != NULL || strstr(p_status, "invalid") != NULL || strstr(p_status, "error") != NULL; } static void Canvas_App_Draw_Listening_Indicator(void) { if (!g_app->dictation_listening) return; float pulse = 0.72f + 0.28f * sinf((float)GetTime() * 5.0f); Vector2 dot = {28.0f, (float)GetScreenHeight() - 28.0f}; DrawCircleV( dot, 5.0f + pulse * 2.0f, Fade(g_app->theme.danger, 0.82f)); DrawTextEx( g_app->font, "Listening", (Vector2){44.0f, dot.y - 8.0f}, 14.0f, 0.0f, g_app->theme.text_muted); } static void Canvas_App_Draw_Agent_Indicator(void) { if (!g_app->pending_indicator_visible) return; Vector2 screen = Canvas_Camera_World_To_Screen( &g_app->camera, g_app->pending_indicator_position); float pulse = 0.5f + 0.5f * sinf((float)GetTime() * 5.0f); float hover = g_app->pending_indicator_hover_amount; float radius = 22.0f + hover * 2.0f; Rectangle bounds = { screen.x - radius, screen.y - radius, radius * 2.0f, radius * 2.0f, }; Canvas_Theme_Draw_Shadow( &g_app->theme, bounds, 0.5f, 16, 1.0f, 1.0f); DrawCircleV(screen, radius, g_app->theme.surface); DrawCircleLinesV( screen, 20.0f + pulse * 2.0f, Fade(g_app->theme.accent, 0.45f + pulse * 0.35f)); Canvas_Draw_Lucide_Icon( "sparkles", (Vector2){screen.x - 9.0f, screen.y - 9.0f}, 0.75f, 1.8f, g_app->theme.accent); if (hover > 0.01f) { Rectangle label = { screen.x + 28.0f - (1.0f - hover) * 8.0f, screen.y - 17.0f, 118.0f, 34.0f, }; DrawRectangleRounded( label, 0.35f, 10, Fade(g_app->theme.surface, hover)); DrawRectangleRoundedLinesEx( label, 0.35f, 10, 1.0f, Fade(g_app->theme.border, hover)); DrawTextEx( g_app->font, g_app->pending_indicator_dragging ? "Move result" : "Copilot working", (Vector2){label.x + 12.0f, label.y + 10.0f}, 13.0f, 0.0f, Fade(g_app->theme.text, hover)); } } static boolean Canvas_App_Update_Agent_Indicator_Input(boolean blocked) { if (!g_app->pending_indicator_visible) { g_app->pending_indicator_hovered = FALSE; g_app->pending_indicator_dragging = FALSE; g_app->pending_indicator_hover_amount = 0.0f; return FALSE; } Vector2 pointer = GetMousePosition(); Vector2 screen = Canvas_Camera_World_To_Screen( &g_app->camera, g_app->pending_indicator_position); if (g_app->pending_indicator_dragging) { Vector2 target_screen = { pointer.x - g_app->pending_indicator_drag_offset.x, pointer.y - g_app->pending_indicator_drag_offset.y, }; g_app->pending_indicator_position = Canvas_Camera_Screen_To_World( &g_app->camera, target_screen); if (IsMouseButtonReleased(MOUSE_BUTTON_LEFT)) { g_app->pending_indicator_dragging = FALSE; } g_app->pending_indicator_hovered = TRUE; } else { g_app->pending_indicator_hovered = !blocked && CheckCollisionPointCircle(pointer, screen, 26.0f); if (g_app->pending_indicator_hovered && IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) { g_app->pending_indicator_dragging = TRUE; g_app->pending_indicator_drag_offset = (Vector2){ pointer.x - screen.x, pointer.y - screen.y, }; } } float target = g_app->pending_indicator_hovered ? 1.0f : 0.0f; float step = GetFrameTime() * 10.0f; if (g_app->pending_indicator_hover_amount < target) { g_app->pending_indicator_hover_amount = fminf( target, g_app->pending_indicator_hover_amount + step); } else { g_app->pending_indicator_hover_amount = fmaxf( target, g_app->pending_indicator_hover_amount - step); } return g_app->pending_indicator_hovered || g_app->pending_indicator_dragging; } #if defined(INFINITE_CANVAS_DEV_UI) static void Canvas_App_Handle_Dev_Action(Canvas_Dev_UI_Action action) { if (action == CANVAS_DEV_UI_ACTION_NONE) return; char path[CANVAS_SCENE_STORE_PATH_CAPACITY]; if (!Canvas_Scene_Store_Path( g_app->dev_ui.snapshot_name, path, sizeof(path))) { Canvas_Dev_UI_Set_Snapshot_Status( &g_app->dev_ui, "Use only letters, numbers, - or _"); return; } if (action == CANVAS_DEV_UI_ACTION_SAVE_SNAPSHOT) { Canvas_Dev_UI_Set_Snapshot_Status( &g_app->dev_ui, Canvas_Scene_Store_Save( path, &g_app->scene, &g_app->camera) ? "Snapshot saved" : "Snapshot save failed"); return; } if (g_app->agent_initialized && g_app->agent_service.status == CANVAS_AGENT_WORKING) { Canvas_Dev_UI_Set_Snapshot_Status( &g_app->dev_ui, "Wait for the active agent turn before loading"); return; } if (!Canvas_Scene_Store_Load( path, &g_app->scene, &g_app->camera)) { Canvas_Dev_UI_Set_Snapshot_Status( &g_app->dev_ui, "Snapshot missing, incompatible, or corrupt"); return; } g_app->pending_indicator_visible = FALSE; g_app->pending_prompt[0] = '\0'; g_app->dictation_entity_id = 0; g_app->dictation_transcript[0] = '\0'; g_app->dictation_partial[0] = '\0'; for (size_t index = 0; index < Dowa_Array_Length(g_app->scene.p_entities); index++) { Canvas_Entity *p_entity = &g_app->scene.p_entities[index]; if (p_entity->type == CANVAS_ENTITY_TEXT_AREA && strcmp(p_entity->label, "Dictation") == 0) { g_app->dictation_entity_id = p_entity->id; snprintf( p_entity->text_muted ? g_app->dictation_partial : g_app->dictation_transcript, CANVAS_ENTITY_TEXT_CAPACITY, "%s", p_entity->text); break; } } g_app->dictation_overlay_visible = g_app->dictation_transcript[0] || g_app->dictation_partial[0]; Canvas_Dev_UI_Set_Snapshot_Status( &g_app->dev_ui, "Snapshot loaded"); } #endif static void Canvas_App_Draw_Frame(void) { int32 width = GetScreenWidth(); int32 height = GetScreenHeight(); Canvas_Camera_Set_Viewport(&g_app->camera, width, height); boolean block_pointer_input = FALSE; boolean dev_ui_editing = FALSE; #if defined(INFINITE_CANVAS_DEV_UI) block_pointer_input = Canvas_Dev_UI_Contains_Pointer(&g_app->dev_ui); dev_ui_editing = Canvas_Dev_UI_Is_Editing(&g_app->dev_ui); #endif block_pointer_input = Canvas_App_Update_Agent_Indicator_Input(block_pointer_input) || block_pointer_input; Canvas_Web_Surface_Set_Dark_Mode( &g_app->web_surface, g_app->theme.resolved_mode == CANVAS_THEME_DARK ? TRUE : FALSE); boolean dictation_hotkey = !dev_ui_editing && !Canvas_Scene_Is_Text_Editing(&g_app->scene) && !Canvas_Web_Chrome_Is_Editing(&g_app->scene) && IsKeyPressed(KEY_M); if (dictation_hotkey) { Canvas_App_Set_Dictation_Active( g_app->dictation_requested_active ? FALSE : TRUE); } Canvas_App_Update_Dictation_Overlay_Input(); Canvas_Web_Chrome_Action chrome_action = CANVAS_WEB_CHROME_NONE; if (!block_pointer_input) { const char *p_current_url = NULL; if (g_app->scene.selected_index >= 0 && g_app->scene.selected_index < (int32)Dowa_Array_Length(g_app->scene.p_entities)) { const Canvas_Entity *p_selected = &g_app->scene.p_entities[g_app->scene.selected_index]; p_current_url = Canvas_Web_Surface_URL( &g_app->web_surface, p_selected->id); } chrome_action = Canvas_Web_Chrome_Update_Input( &g_app->camera, &g_app->scene, g_app->font, p_current_url); } if (chrome_action == CANVAS_WEB_CHROME_BACK || chrome_action == CANVAS_WEB_CHROME_FORWARD) { const Canvas_Entity *p_selected = &g_app->scene.p_entities[g_app->scene.selected_index]; if (chrome_action == CANVAS_WEB_CHROME_BACK) { Canvas_Web_Surface_Go_Back( &g_app->web_surface, p_selected->id); } else { Canvas_Web_Surface_Go_Forward( &g_app->web_surface, p_selected->id); } } boolean web_input = FALSE; if (!block_pointer_input && !dictation_hotkey && chrome_action == CANVAS_WEB_CHROME_NONE) { web_input = Canvas_Web_Surface_Update_Input( &g_app->web_surface, &g_app->camera, &g_app->scene); } block_pointer_input = block_pointer_input || chrome_action != CANVAS_WEB_CHROME_NONE || web_input; boolean entity_input = Canvas_Scene_Update_Interaction( &g_app->scene, &g_app->camera, g_app->font, block_pointer_input); boolean keyboard_focus = Canvas_Scene_Is_Text_Editing(&g_app->scene) || Canvas_Web_Chrome_Is_Editing(&g_app->scene) || dev_ui_editing || Canvas_Web_Surface_Is_Focused(&g_app->web_surface); if (!keyboard_focus && IsKeyPressed(KEY_P)) { Canvas_Scene_Toggle_Selected_Pin(&g_app->scene, &g_app->camera); } if (!keyboard_focus && !g_app->dictation_listening && (IsKeyPressed(KEY_DELETE) || IsKeyPressed(KEY_BACKSPACE))) { Canvas_Scene_Fade_Out_Selected(&g_app->scene); } Canvas_Update_Camera_Input( &g_app->camera, block_pointer_input || entity_input, keyboard_focus); Canvas_Scene_Sync_Pinned(&g_app->scene, &g_app->camera); Canvas_Scene_Update_Notification_Stack(&g_app->scene, &g_app->camera); char dictation_text[CANVAS_ENTITY_TEXT_CAPACITY]; Canvas_Dictation_Event dictation_event = Canvas_Web_Surface_Consume_Dictation( &g_app->web_surface, dictation_text, sizeof(dictation_text)); if (dictation_event == CANVAS_DICTATION_EVENT_STATUS) { snprintf( g_app->dictation_status, sizeof(g_app->dictation_status), "%.127s", dictation_text); if (strcmp(dictation_text, "Idle") == 0) { g_app->dictation_listening = FALSE; if (g_app->dictation_send_pending) { if (g_app->dictation_partial[0]) { Canvas_App_Append_Dictation( g_app->dictation_partial); g_app->dictation_partial[0] = '\0'; } Canvas_App_Submit_Dictation(); } else if (!g_app->dictation_transcript[0] && !g_app->dictation_partial[0]) { g_app->dictation_overlay_visible = FALSE; } } else if (strcmp(dictation_text, "Listening") == 0 || strcmp(dictation_text, "Speech detected") == 0) { g_app->dictation_listening = g_app->dictation_requested_active; if (strcmp(dictation_text, "Listening") == 0 && g_app->dictation_send_pending) { if (g_app->dictation_partial[0]) { Canvas_App_Append_Dictation( g_app->dictation_partial); g_app->dictation_partial[0] = '\0'; } Canvas_App_Submit_Dictation(); } } else if (Canvas_App_Dictation_Status_Is_Error(dictation_text)) { g_app->dictation_listening = FALSE; g_app->dictation_overlay_visible = TRUE; } } else if (dictation_event == CANVAS_DICTATION_EVENT_PARTIAL) { g_app->dictation_overlay_visible = TRUE; snprintf( g_app->dictation_partial, sizeof(g_app->dictation_partial), "%s", dictation_text); Canvas_App_Sync_Dictation_Entity(); } else if (dictation_event == CANVAS_DICTATION_EVENT_FINAL) { g_app->dictation_overlay_visible = TRUE; Canvas_App_Append_Dictation(dictation_text); g_app->dictation_partial[0] = '\0'; Canvas_App_Sync_Dictation_Entity(); if (g_app->dictation_send_pending) { Canvas_App_Submit_Dictation(); } else { snprintf( g_app->dictation_status, sizeof(g_app->dictation_status), "Ready to send"); } } if (!g_app->dictation_overlay_visible && g_app->scene.selected_index >= 0 && g_app->scene.selected_index < (int32)Dowa_Array_Length(g_app->scene.p_entities) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL) || IsKeyDown(KEY_LEFT_SUPER) || IsKeyDown(KEY_RIGHT_SUPER)) && IsKeyPressed(KEY_ENTER)) { Canvas_Entity *p_selected = &g_app->scene.p_entities[g_app->scene.selected_index]; if (p_selected->type == CANVAS_ENTITY_TEXT_AREA && p_selected->text[0]) { size_t length = strlen(p_selected->text); if (length > 0 && p_selected->text[length - 1] == '\n') { p_selected->text[length - 1] = '\0'; } if (Canvas_App_Submit_Prompt(p_selected->text)) { p_selected->text[0] = '\0'; p_selected->text_cursor = 0; p_selected->text_selection_anchor = 0; } } } char agent_response[CANVAS_AGENT_RESPONSE_CAPACITY]; char agent_error[512]; Canvas_Agent_Status agent_status = g_app->agent_initialized ? Canvas_Agent_Service_Poll( &g_app->agent_service, agent_response, sizeof(agent_response), agent_error, sizeof(agent_error)) : CANVAS_AGENT_IDLE; if (agent_status == CANVAS_AGENT_READY) { Canvas_App_Apply_Agent_Response(agent_response); } else if (agent_status == CANVAS_AGENT_ERROR) { if (g_app->pending_prompt[0]) { Canvas_App_Show_Agent_Notification( "Copilot unavailable", agent_error); } g_app->pending_indicator_visible = FALSE; g_app->pending_prompt[0] = '\0'; } Canvas_Web_Surface_Sync(&g_app->web_surface, &g_app->camera, &g_app->scene); BeginDrawing(); ClearBackground(g_app->theme.background); Canvas_Draw_World_Background( &g_app->camera, &g_app->scene, &g_app->theme); for (size_t index = 0; index < Dowa_Array_Length(g_app->scene.p_entities); index++) { Canvas_Draw_World_Entity( &g_app->camera, &g_app->scene, index, g_app->font, &g_app->theme); Canvas_Web_Surface_Draw_Entity( &g_app->web_surface, &g_app->camera, &g_app->scene, index); const Canvas_Entity *p_entity = &g_app->scene.p_entities[index]; const char *p_url = Canvas_Web_Surface_URL( &g_app->web_surface, p_entity->id); Canvas_Draw_Web_Chrome_Entity( &g_app->camera, &g_app->scene, index, g_app->font, &g_app->theme, p_url ? p_url : p_entity->text, Canvas_Web_Surface_Can_Go_Back( &g_app->web_surface, p_entity->id), Canvas_Web_Surface_Can_Go_Forward( &g_app->web_surface, p_entity->id)); } Canvas_Draw_World_Overlays( &g_app->camera, &g_app->scene, g_app->font, &g_app->theme); Canvas_App_Draw_Agent_Indicator(); #if defined(INFINITE_CANVAS_DEV_UI) Canvas_Dev_UI_Action dev_action = Canvas_Dev_UI_Draw( &g_app->dev_ui, &g_app->camera, &g_app->scene, g_app->font, &g_app->theme); #endif Canvas_App_Draw_Listening_Indicator(); EndDrawing(); #if defined(INFINITE_CANVAS_DEV_UI) Canvas_App_Handle_Dev_Action(dev_action); #endif g_app->frame_count++; #if !defined(PLATFORM_WEB) if (g_app->frame_count == 30) { RestoreWindow(); SetWindowFocused(); } #endif if (g_app->p_screenshot_path && g_app->frame_count == g_app->screenshot_frame) { TakeScreenshot(g_app->p_screenshot_path); } } int main(int argc, char **p_argv) { #if !defined(PLATFORM_WEB) int32 subprocess_result = Canvas_Web_Surface_Execute_Subprocess(argc, p_argv); if (subprocess_result >= 0) return subprocess_result; #else (void)argc; (void)p_argv; #endif Dowa_Arena *p_arena = Dowa_Arena_Create(8 * ONE_MEGA_BYTE); if (!p_arena) return 1; g_app = (Canvas_App *)Dowa_Arena_Allocate(p_arena, sizeof(*g_app)); if (!g_app) { Dowa_Arena_Free(p_arena); return 1; } memset(g_app, 0, sizeof(*g_app)); g_app->p_arena = p_arena; snprintf( g_app->dictation_status, sizeof(g_app->dictation_status), "Idle"); g_app->p_screenshot_path = getenv("INFINITE_CANVAS_SCREENSHOT"); g_app->screenshot_frame = 180; const char *p_screenshot_frame = getenv("INFINITE_CANVAS_SCREENSHOT_FRAME"); if (p_screenshot_frame) { uint32 screenshot_frame = (uint32)strtoul(p_screenshot_frame, NULL, 10); if (screenshot_frame > 0) { g_app->screenshot_frame = screenshot_frame; } } Canvas_Theme_Resolve( &g_app->theme, Canvas_Theme_Mode_From_String(getenv("INFINITE_CANVAS_THEME"))); SetConfigFlags(FLAG_WINDOW_RESIZABLE | FLAG_WINDOW_HIGHDPI | FLAG_MSAA_4X_HINT); #if defined(INFINITE_CANVAS_DEV_UI) InitWindow(1920, 1200, "Infinite Canvas"); SetWindowMinSize(1200, 800); #else InitWindow(1280, 720, "Infinite Canvas"); SetWindowMinSize(640, 360); #endif SetTargetFPS(60); #if !defined(PLATFORM_WEB) SetWindowFocused(); #endif if (!Canvas_Web_Surface_Init( &g_app->web_surface, p_arena, g_app->theme.resolved_mode == CANVAS_THEME_DARK ? TRUE : FALSE)) { TraceLog(LOG_WARNING, "Web surface initialization failed"); } g_app->agent_initialized = Canvas_Agent_Service_Init(&g_app->agent_service, p_arena); if (!g_app->agent_initialized) { TraceLog(LOG_WARNING, "Agent service initialization failed"); } g_app->font = LoadFontEx("infinite_canvas/assets/Inter-Variable.ttf", 48, NULL, 0); g_app->owns_font = g_app->font.texture.id != 0; if (!g_app->owns_font) g_app->font = GetFontDefault(); else SetTextureFilter(g_app->font.texture, TEXTURE_FILTER_BILINEAR); Canvas_Camera_Init(&g_app->camera, GetScreenWidth(), GetScreenHeight()); Canvas_Scene_Init(&g_app->scene, p_arena); if (getenv("INFINITE_CANVAS_BROWSER_STRESS")) { Canvas_Scene_Add_Browser_Grid(&g_app->scene); } else { Canvas_Scene_Add_Demo(&g_app->scene); } if (getenv("INFINITE_CANVAS_DICTATION_AUTOSTART")) { Canvas_App_Set_Dictation_Active(TRUE); } const char *p_initial_prompt = getenv("INFINITE_CANVAS_AGENT_PROMPT"); if (p_initial_prompt && p_initial_prompt[0]) { Canvas_App_Submit_Prompt(p_initial_prompt); } const char *p_image_source = getenv("INFINITE_CANVAS_IMAGE_SOURCE"); if (p_image_source) { for (size_t index = 0; index < Dowa_Array_Length(g_app->scene.p_entities); index++) { Canvas_Entity *p_entity = &g_app->scene.p_entities[index]; if (p_entity->type == CANVAS_ENTITY_IMAGE) { snprintf(p_entity->text, sizeof(p_entity->text), "%s", p_image_source); break; } } } #if !defined(PLATFORM_WEB) for (size_t index = 0; index < Dowa_Array_Length(g_app->scene.p_entities); index++) { Canvas_Entity *p_entity = &g_app->scene.p_entities[index]; if (p_entity->type == CANVAS_ENTITY_WEB_CONTENT) { snprintf(p_entity->text, sizeof(p_entity->text), "https://www.google.com"); break; } } #endif #if defined(INFINITE_CANVAS_DEV_UI) Canvas_Dev_UI_Init(&g_app->dev_ui, g_app->font, &g_app->theme); #endif #if defined(PLATFORM_WEB) emscripten_set_main_loop(Canvas_App_Draw_Frame, 0, 1); #else while (!WindowShouldClose()) Canvas_App_Draw_Frame(); if (g_app->agent_initialized) { Canvas_Agent_Service_Shutdown(&g_app->agent_service); } Canvas_Web_Surface_Shutdown(&g_app->web_surface); if (g_app->owns_font) UnloadFont(g_app->font); Dowa_Arena_Free(p_arena); g_app = NULL; CloseWindow(); #endif return 0; }