Mercurial
comparison 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 |
comparison
equal
deleted
inserted
replaced
| 280:49e9e591c9bb | 281:c57149ad216e |
|---|---|
| 1 #include "infinite_canvas/agent_response.h" | |
| 1 #include "infinite_canvas/agent_service.h" | 2 #include "infinite_canvas/agent_service.h" |
| 2 #include "infinite_canvas/canvas.h" | 3 #include "infinite_canvas/canvas.h" |
| 4 #include "infinite_canvas/scene_store.h" | |
| 3 #include "infinite_canvas/web_surface.h" | 5 #include "infinite_canvas/web_surface.h" |
| 4 | 6 |
| 5 #if defined(INFINITE_CANVAS_DEV_UI) | 7 #if defined(INFINITE_CANVAS_DEV_UI) |
| 6 #include "infinite_canvas/dev_ui.h" | 8 #include "infinite_canvas/dev_ui.h" |
| 7 #endif | 9 #endif |
| 25 Canvas_Theme theme; | 27 Canvas_Theme theme; |
| 26 boolean owns_font; | 28 boolean owns_font; |
| 27 const char *p_screenshot_path; | 29 const char *p_screenshot_path; |
| 28 uint32 frame_count; | 30 uint32 frame_count; |
| 29 uint32 screenshot_frame; | 31 uint32 screenshot_frame; |
| 30 uint32 pending_conversation_id; | 32 Vector2 pending_indicator_position; |
| 33 Vector2 pending_indicator_drag_offset; | |
| 31 uint32 dictation_entity_id; | 34 uint32 dictation_entity_id; |
| 32 boolean agent_initialized; | 35 boolean agent_initialized; |
| 36 boolean pending_indicator_visible; | |
| 37 boolean pending_indicator_hovered; | |
| 38 boolean pending_indicator_dragging; | |
| 39 float pending_indicator_hover_amount; | |
| 33 boolean dictation_overlay_visible; | 40 boolean dictation_overlay_visible; |
| 34 boolean dictation_listening; | 41 boolean dictation_listening; |
| 35 boolean dictation_requested_active; | 42 boolean dictation_requested_active; |
| 36 boolean dictation_send_pending; | 43 boolean dictation_send_pending; |
| 37 char dictation_status[128]; | 44 char dictation_status[128]; |
| 44 #endif | 51 #endif |
| 45 } Canvas_App; | 52 } Canvas_App; |
| 46 | 53 |
| 47 static Canvas_App *g_app = NULL; | 54 static Canvas_App *g_app = NULL; |
| 48 | 55 |
| 49 static const char *Canvas_App_JSON_String( | |
| 50 const char *p_json, | |
| 51 const char *p_key, | |
| 52 char *p_output, | |
| 53 size_t output_capacity) | |
| 54 { | |
| 55 char needle[128]; | |
| 56 snprintf(needle, sizeof(needle), "\"%s\"", p_key); | |
| 57 const char *p_cursor = strstr(p_json, needle); | |
| 58 if (!p_cursor) return NULL; | |
| 59 p_cursor = strchr(p_cursor + strlen(needle), ':'); | |
| 60 if (!p_cursor) return NULL; | |
| 61 while (*++p_cursor == ' ') {} | |
| 62 if (*p_cursor != '"') return NULL; | |
| 63 p_cursor++; | |
| 64 size_t output = 0; | |
| 65 while (*p_cursor && *p_cursor != '"' && output + 1 < output_capacity) { | |
| 66 if (*p_cursor == '\\' && p_cursor[1]) { | |
| 67 p_cursor++; | |
| 68 if (*p_cursor == 'n') p_output[output++] = '\n'; | |
| 69 else if (*p_cursor == 't') p_output[output++] = '\t'; | |
| 70 else if (*p_cursor != 'r') p_output[output++] = *p_cursor; | |
| 71 } else { | |
| 72 p_output[output++] = *p_cursor; | |
| 73 } | |
| 74 p_cursor++; | |
| 75 } | |
| 76 p_output[output] = '\0'; | |
| 77 return p_output; | |
| 78 } | |
| 79 | |
| 80 static uint32 Canvas_App_JSON_Uint(const char *p_json, const char *p_key) | |
| 81 { | |
| 82 char needle[128]; | |
| 83 snprintf(needle, sizeof(needle), "\"%s\"", p_key); | |
| 84 const char *p_cursor = strstr(p_json, needle); | |
| 85 if (!p_cursor) return 0; | |
| 86 p_cursor = strchr(p_cursor + strlen(needle), ':'); | |
| 87 if (!p_cursor) return 0; | |
| 88 return (uint32)strtoul(p_cursor + 1, NULL, 10); | |
| 89 } | |
| 90 | |
| 91 static Canvas_Entity *Canvas_App_Find_Entity(uint32 entity_id) | 56 static Canvas_Entity *Canvas_App_Find_Entity(uint32 entity_id) |
| 92 { | 57 { |
| 93 for (size_t index = 0; | 58 for (size_t index = 0; |
| 94 index < Dowa_Array_Length(g_app->scene.p_entities); | 59 index < Dowa_Array_Length(g_app->scene.p_entities); |
| 95 index++) { | 60 index++) { |
| 96 if (g_app->scene.p_entities[index].id == entity_id) { | 61 if (g_app->scene.p_entities[index].id == entity_id) { |
| 97 return &g_app->scene.p_entities[index]; | 62 return &g_app->scene.p_entities[index]; |
| 98 } | 63 } |
| 99 } | 64 } |
| 100 return NULL; | 65 return NULL; |
| 101 } | |
| 102 | |
| 103 static void Canvas_App_Set_Conversation_Text( | |
| 104 Canvas_Entity *p_entity, | |
| 105 const char *p_prompt, | |
| 106 const char *p_speaker, | |
| 107 const char *p_response) | |
| 108 { | |
| 109 size_t length = 0; | |
| 110 int32 written = snprintf( | |
| 111 p_entity->text, | |
| 112 sizeof(p_entity->text), | |
| 113 "You\n"); | |
| 114 if (written > 0) length = (size_t)written; | |
| 115 written = snprintf( | |
| 116 p_entity->text + length, | |
| 117 sizeof(p_entity->text) - length, | |
| 118 "%.*s\n\n%s\n", | |
| 119 900, | |
| 120 p_prompt, | |
| 121 p_speaker); | |
| 122 if (written > 0) length += (size_t)written; | |
| 123 if (length >= sizeof(p_entity->text)) { | |
| 124 length = sizeof(p_entity->text) - 1; | |
| 125 } | |
| 126 snprintf( | |
| 127 p_entity->text + length, | |
| 128 sizeof(p_entity->text) - length, | |
| 129 "%.*s", | |
| 130 900, | |
| 131 p_response); | |
| 132 Canvas_Conversation_Scroll_To_End(p_entity, g_app->font); | |
| 133 } | 66 } |
| 134 | 67 |
| 135 static boolean Canvas_App_Submit_Prompt(const char *p_prompt) | 68 static boolean Canvas_App_Submit_Prompt(const char *p_prompt) |
| 136 { | 69 { |
| 137 if (!p_prompt || !p_prompt[0]) return FALSE; | 70 if (!p_prompt || !p_prompt[0]) return FALSE; |
| 149 snprintf( | 82 snprintf( |
| 150 g_app->pending_prompt, | 83 g_app->pending_prompt, |
| 151 sizeof(g_app->pending_prompt), | 84 sizeof(g_app->pending_prompt), |
| 152 "%s", | 85 "%s", |
| 153 p_prompt); | 86 p_prompt); |
| 154 Vector2 position = Canvas_Camera_Screen_To_World( | 87 g_app->pending_indicator_position = Canvas_Camera_Screen_To_World( |
| 155 &g_app->camera, | 88 &g_app->camera, |
| 156 (Vector2){ | 89 (Vector2){ |
| 157 (float)g_app->camera.viewport_width * 0.5f - 210.0f, | 90 (float)g_app->camera.viewport_width * 0.5f + 240.0f, |
| 158 (float)g_app->camera.viewport_height * 0.5f - 150.0f, | 91 (float)g_app->camera.viewport_height * 0.5f - 150.0f, |
| 159 }); | 92 }); |
| 160 g_app->pending_conversation_id = Canvas_Scene_Add_Conversation( | 93 g_app->pending_indicator_visible = TRUE; |
| 161 &g_app->scene, | 94 g_app->pending_indicator_hovered = FALSE; |
| 162 "Routing thought...", | 95 g_app->pending_indicator_dragging = FALSE; |
| 163 p_prompt, | 96 g_app->pending_indicator_hover_amount = 0.0f; |
| 164 "Copilot is deciding whether to create or extend a session.", | |
| 165 position); | |
| 166 Canvas_Entity *p_pending = | |
| 167 Canvas_App_Find_Entity(g_app->pending_conversation_id); | |
| 168 if (p_pending) p_pending->agent_working = TRUE; | |
| 169 return TRUE; | 97 return TRUE; |
| 170 } | 98 } |
| 171 | 99 |
| 100 static uint32 Canvas_App_Materialize_Agent_Entities( | |
| 101 Canvas_Agent_Response *p_response) | |
| 102 { | |
| 103 uint32 created = 0; | |
| 104 for (uint32 index = 0; index < p_response->entity_count; index++) { | |
| 105 Canvas_Agent_Entity_Spec *p_spec = &p_response->entities[index]; | |
| 106 Vector2 position = p_spec->has_position | |
| 107 ? p_spec->position | |
| 108 : (Vector2){ | |
| 109 g_app->pending_indicator_position.x + | |
| 110 (float)(index % 2) * 280.0f, | |
| 111 g_app->pending_indicator_position.y + | |
| 112 (float)(index / 2) * 240.0f, | |
| 113 }; | |
| 114 if (!Canvas_Scene_Add(&g_app->scene, p_spec->type, position)) { | |
| 115 break; | |
| 116 } | |
| 117 Canvas_Entity *p_entity = &g_app->scene.p_entities[ | |
| 118 Dowa_Array_Length(g_app->scene.p_entities) - 1]; | |
| 119 if (p_spec->label[0]) { | |
| 120 snprintf( | |
| 121 p_entity->label, | |
| 122 sizeof(p_entity->label), | |
| 123 "%s", | |
| 124 p_spec->label); | |
| 125 } | |
| 126 if (p_spec->text[0]) { | |
| 127 snprintf( | |
| 128 p_entity->text, | |
| 129 sizeof(p_entity->text), | |
| 130 "%s", | |
| 131 p_spec->text); | |
| 132 p_entity->text_cursor = (int32)strlen(p_entity->text); | |
| 133 p_entity->text_selection_anchor = p_entity->text_cursor; | |
| 134 } | |
| 135 if (p_spec->has_size) p_entity->size = p_spec->size; | |
| 136 if (p_spec->has_value) p_entity->value = p_spec->value; | |
| 137 created++; | |
| 138 } | |
| 139 return created; | |
| 140 } | |
| 141 | |
| 142 static void Canvas_App_Show_Agent_Notification( | |
| 143 const char *p_title, | |
| 144 const char *p_text) | |
| 145 { | |
| 146 if (!Canvas_Scene_Add( | |
| 147 &g_app->scene, | |
| 148 CANVAS_ENTITY_NOTIFICATION, | |
| 149 g_app->pending_indicator_position)) { | |
| 150 return; | |
| 151 } | |
| 152 Canvas_Entity *p_entity = &g_app->scene.p_entities[ | |
| 153 Dowa_Array_Length(g_app->scene.p_entities) - 1]; | |
| 154 if (p_title && p_title[0]) { | |
| 155 snprintf( | |
| 156 p_entity->label, | |
| 157 sizeof(p_entity->label), | |
| 158 "%s", | |
| 159 p_title); | |
| 160 } | |
| 161 snprintf( | |
| 162 p_entity->text, | |
| 163 sizeof(p_entity->text), | |
| 164 "%s", | |
| 165 p_text ? p_text : ""); | |
| 166 } | |
| 167 | |
| 172 static void Canvas_App_Apply_Agent_Response(const char *p_json) | 168 static void Canvas_App_Apply_Agent_Response(const char *p_json) |
| 173 { | 169 { |
| 174 char action[32] = {0}; | 170 Canvas_Agent_Response parsed; |
| 175 char title[128] = {0}; | 171 boolean valid = Canvas_Agent_Response_Parse(p_json, &parsed); |
| 176 char response[CANVAS_ENTITY_TEXT_CAPACITY] = {0}; | 172 if (!valid) { |
| 177 Canvas_App_JSON_String(p_json, "action", action, sizeof(action)); | 173 memset(&parsed, 0, sizeof(parsed)); |
| 178 Canvas_App_JSON_String(p_json, "title", title, sizeof(title)); | 174 snprintf(parsed.action, sizeof(parsed.action), "create"); |
| 179 if (!Canvas_App_JSON_String( | 175 snprintf( |
| 180 p_json, | 176 parsed.presentation, |
| 181 "response", | 177 sizeof(parsed.presentation), |
| 182 response, | 178 "conversation"); |
| 183 sizeof(response))) { | 179 snprintf( |
| 184 snprintf(response, sizeof(response), "%s", p_json); | 180 parsed.response, |
| 185 } | 181 sizeof(parsed.response), |
| 186 uint32 conversation_id = | 182 "%s", |
| 187 Canvas_App_JSON_Uint(p_json, "conversation_id"); | 183 p_json); |
| 188 Canvas_Entity *p_pending = | 184 } |
| 189 Canvas_App_Find_Entity(g_app->pending_conversation_id); | 185 uint32 created_entities = |
| 190 if (strcmp(action, "append") == 0 && | 186 Canvas_App_Materialize_Agent_Entities(&parsed); |
| 191 conversation_id != 0 && | 187 if (strcmp(parsed.action, "append") == 0 && |
| 188 parsed.conversation_id != 0 && | |
| 192 Canvas_Scene_Append_Conversation( | 189 Canvas_Scene_Append_Conversation( |
| 193 &g_app->scene, | 190 &g_app->scene, |
| 194 conversation_id, | 191 parsed.conversation_id, |
| 195 g_app->pending_prompt, | 192 g_app->pending_prompt, |
| 196 response)) { | 193 parsed.response)) { |
| 197 Canvas_Entity *p_conversation = | 194 Canvas_Entity *p_conversation = |
| 198 Canvas_App_Find_Entity(conversation_id); | 195 Canvas_App_Find_Entity(parsed.conversation_id); |
| 199 Canvas_Conversation_Scroll_To_End( | 196 Canvas_Conversation_Scroll_To_End( |
| 200 p_conversation, | 197 p_conversation, |
| 201 g_app->font); | 198 g_app->font); |
| 202 if (p_pending) p_pending->removing = TRUE; | 199 } else if (strcmp(parsed.presentation, "action") == 0) { |
| 203 } else if (p_pending) { | 200 if (created_entities == 0) { |
| 204 snprintf( | 201 Canvas_App_Show_Agent_Notification( |
| 205 p_pending->label, | 202 parsed.title[0] ? parsed.title : "Copilot", |
| 206 sizeof(p_pending->label), | 203 parsed.response); |
| 207 "%s", | 204 } |
| 208 title[0] ? title : "Copilot session"); | |
| 209 Canvas_App_Set_Conversation_Text( | |
| 210 p_pending, | |
| 211 g_app->pending_prompt, | |
| 212 "Copilot", | |
| 213 response); | |
| 214 p_pending->agent_working = FALSE; | |
| 215 } else { | 205 } else { |
| 216 Vector2 position = Canvas_Camera_Screen_To_World( | 206 Vector2 position = { |
| 217 &g_app->camera, | 207 g_app->pending_indicator_position.x - 210.0f, |
| 218 (Vector2){ | 208 g_app->pending_indicator_position.y - 150.0f, |
| 219 (float)g_app->camera.viewport_width * 0.5f - 210.0f, | 209 }; |
| 220 (float)g_app->camera.viewport_height * 0.5f - 150.0f, | |
| 221 }); | |
| 222 uint32 created_id = Canvas_Scene_Add_Conversation( | 210 uint32 created_id = Canvas_Scene_Add_Conversation( |
| 223 &g_app->scene, | 211 &g_app->scene, |
| 224 title[0] ? title : "Copilot session", | 212 parsed.title[0] ? parsed.title : "Copilot session", |
| 225 g_app->pending_prompt, | 213 g_app->pending_prompt, |
| 226 response, | 214 parsed.response, |
| 227 position); | 215 position); |
| 228 Canvas_Conversation_Scroll_To_End( | 216 Canvas_Conversation_Scroll_To_End( |
| 229 Canvas_App_Find_Entity(created_id), | 217 Canvas_App_Find_Entity(created_id), |
| 230 g_app->font); | 218 g_app->font); |
| 231 } | 219 } |
| 232 g_app->pending_conversation_id = 0; | 220 g_app->pending_indicator_visible = FALSE; |
| 233 g_app->pending_prompt[0] = '\0'; | 221 g_app->pending_prompt[0] = '\0'; |
| 234 } | 222 } |
| 235 | 223 |
| 236 static Canvas_Entity *Canvas_App_Ensure_Dictation_Entity(void) | 224 static Canvas_Entity *Canvas_App_Ensure_Dictation_Entity(void) |
| 237 { | 225 { |
| 419 14.0f, | 407 14.0f, |
| 420 0.0f, | 408 0.0f, |
| 421 g_app->theme.text_muted); | 409 g_app->theme.text_muted); |
| 422 } | 410 } |
| 423 | 411 |
| 412 static void Canvas_App_Draw_Agent_Indicator(void) | |
| 413 { | |
| 414 if (!g_app->pending_indicator_visible) return; | |
| 415 Vector2 screen = Canvas_Camera_World_To_Screen( | |
| 416 &g_app->camera, | |
| 417 g_app->pending_indicator_position); | |
| 418 float pulse = 0.5f + 0.5f * sinf((float)GetTime() * 5.0f); | |
| 419 float hover = g_app->pending_indicator_hover_amount; | |
| 420 float radius = 22.0f + hover * 2.0f; | |
| 421 Rectangle bounds = { | |
| 422 screen.x - radius, | |
| 423 screen.y - radius, | |
| 424 radius * 2.0f, | |
| 425 radius * 2.0f, | |
| 426 }; | |
| 427 Canvas_Theme_Draw_Shadow( | |
| 428 &g_app->theme, | |
| 429 bounds, | |
| 430 0.5f, | |
| 431 16, | |
| 432 1.0f, | |
| 433 1.0f); | |
| 434 DrawCircleV(screen, radius, g_app->theme.surface); | |
| 435 DrawCircleLinesV( | |
| 436 screen, | |
| 437 20.0f + pulse * 2.0f, | |
| 438 Fade(g_app->theme.accent, 0.45f + pulse * 0.35f)); | |
| 439 Canvas_Draw_Lucide_Icon( | |
| 440 "sparkles", | |
| 441 (Vector2){screen.x - 9.0f, screen.y - 9.0f}, | |
| 442 0.75f, | |
| 443 1.8f, | |
| 444 g_app->theme.accent); | |
| 445 if (hover > 0.01f) { | |
| 446 Rectangle label = { | |
| 447 screen.x + 28.0f - (1.0f - hover) * 8.0f, | |
| 448 screen.y - 17.0f, | |
| 449 118.0f, | |
| 450 34.0f, | |
| 451 }; | |
| 452 DrawRectangleRounded( | |
| 453 label, | |
| 454 0.35f, | |
| 455 10, | |
| 456 Fade(g_app->theme.surface, hover)); | |
| 457 DrawRectangleRoundedLinesEx( | |
| 458 label, | |
| 459 0.35f, | |
| 460 10, | |
| 461 1.0f, | |
| 462 Fade(g_app->theme.border, hover)); | |
| 463 DrawTextEx( | |
| 464 g_app->font, | |
| 465 g_app->pending_indicator_dragging | |
| 466 ? "Move result" | |
| 467 : "Copilot working", | |
| 468 (Vector2){label.x + 12.0f, label.y + 10.0f}, | |
| 469 13.0f, | |
| 470 0.0f, | |
| 471 Fade(g_app->theme.text, hover)); | |
| 472 } | |
| 473 } | |
| 474 | |
| 475 static boolean Canvas_App_Update_Agent_Indicator_Input(boolean blocked) | |
| 476 { | |
| 477 if (!g_app->pending_indicator_visible) { | |
| 478 g_app->pending_indicator_hovered = FALSE; | |
| 479 g_app->pending_indicator_dragging = FALSE; | |
| 480 g_app->pending_indicator_hover_amount = 0.0f; | |
| 481 return FALSE; | |
| 482 } | |
| 483 Vector2 pointer = GetMousePosition(); | |
| 484 Vector2 screen = Canvas_Camera_World_To_Screen( | |
| 485 &g_app->camera, | |
| 486 g_app->pending_indicator_position); | |
| 487 if (g_app->pending_indicator_dragging) { | |
| 488 Vector2 target_screen = { | |
| 489 pointer.x - g_app->pending_indicator_drag_offset.x, | |
| 490 pointer.y - g_app->pending_indicator_drag_offset.y, | |
| 491 }; | |
| 492 g_app->pending_indicator_position = | |
| 493 Canvas_Camera_Screen_To_World( | |
| 494 &g_app->camera, | |
| 495 target_screen); | |
| 496 if (IsMouseButtonReleased(MOUSE_BUTTON_LEFT)) { | |
| 497 g_app->pending_indicator_dragging = FALSE; | |
| 498 } | |
| 499 g_app->pending_indicator_hovered = TRUE; | |
| 500 } else { | |
| 501 g_app->pending_indicator_hovered = | |
| 502 !blocked && | |
| 503 CheckCollisionPointCircle(pointer, screen, 26.0f); | |
| 504 if (g_app->pending_indicator_hovered && | |
| 505 IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) { | |
| 506 g_app->pending_indicator_dragging = TRUE; | |
| 507 g_app->pending_indicator_drag_offset = (Vector2){ | |
| 508 pointer.x - screen.x, | |
| 509 pointer.y - screen.y, | |
| 510 }; | |
| 511 } | |
| 512 } | |
| 513 float target = g_app->pending_indicator_hovered ? 1.0f : 0.0f; | |
| 514 float step = GetFrameTime() * 10.0f; | |
| 515 if (g_app->pending_indicator_hover_amount < target) { | |
| 516 g_app->pending_indicator_hover_amount = fminf( | |
| 517 target, | |
| 518 g_app->pending_indicator_hover_amount + step); | |
| 519 } else { | |
| 520 g_app->pending_indicator_hover_amount = fmaxf( | |
| 521 target, | |
| 522 g_app->pending_indicator_hover_amount - step); | |
| 523 } | |
| 524 return g_app->pending_indicator_hovered || | |
| 525 g_app->pending_indicator_dragging; | |
| 526 } | |
| 527 | |
| 528 #if defined(INFINITE_CANVAS_DEV_UI) | |
| 529 static void Canvas_App_Handle_Dev_Action(Canvas_Dev_UI_Action action) | |
| 530 { | |
| 531 if (action == CANVAS_DEV_UI_ACTION_NONE) return; | |
| 532 char path[CANVAS_SCENE_STORE_PATH_CAPACITY]; | |
| 533 if (!Canvas_Scene_Store_Path( | |
| 534 g_app->dev_ui.snapshot_name, | |
| 535 path, | |
| 536 sizeof(path))) { | |
| 537 Canvas_Dev_UI_Set_Snapshot_Status( | |
| 538 &g_app->dev_ui, | |
| 539 "Use only letters, numbers, - or _"); | |
| 540 return; | |
| 541 } | |
| 542 if (action == CANVAS_DEV_UI_ACTION_SAVE_SNAPSHOT) { | |
| 543 Canvas_Dev_UI_Set_Snapshot_Status( | |
| 544 &g_app->dev_ui, | |
| 545 Canvas_Scene_Store_Save( | |
| 546 path, | |
| 547 &g_app->scene, | |
| 548 &g_app->camera) | |
| 549 ? "Snapshot saved" | |
| 550 : "Snapshot save failed"); | |
| 551 return; | |
| 552 } | |
| 553 if (g_app->agent_initialized && | |
| 554 g_app->agent_service.status == CANVAS_AGENT_WORKING) { | |
| 555 Canvas_Dev_UI_Set_Snapshot_Status( | |
| 556 &g_app->dev_ui, | |
| 557 "Wait for the active agent turn before loading"); | |
| 558 return; | |
| 559 } | |
| 560 if (!Canvas_Scene_Store_Load( | |
| 561 path, | |
| 562 &g_app->scene, | |
| 563 &g_app->camera)) { | |
| 564 Canvas_Dev_UI_Set_Snapshot_Status( | |
| 565 &g_app->dev_ui, | |
| 566 "Snapshot missing, incompatible, or corrupt"); | |
| 567 return; | |
| 568 } | |
| 569 g_app->pending_indicator_visible = FALSE; | |
| 570 g_app->pending_prompt[0] = '\0'; | |
| 571 g_app->dictation_entity_id = 0; | |
| 572 g_app->dictation_transcript[0] = '\0'; | |
| 573 g_app->dictation_partial[0] = '\0'; | |
| 574 for (size_t index = 0; | |
| 575 index < Dowa_Array_Length(g_app->scene.p_entities); | |
| 576 index++) { | |
| 577 Canvas_Entity *p_entity = &g_app->scene.p_entities[index]; | |
| 578 if (p_entity->type == CANVAS_ENTITY_TEXT_AREA && | |
| 579 strcmp(p_entity->label, "Dictation") == 0) { | |
| 580 g_app->dictation_entity_id = p_entity->id; | |
| 581 snprintf( | |
| 582 p_entity->text_muted | |
| 583 ? g_app->dictation_partial | |
| 584 : g_app->dictation_transcript, | |
| 585 CANVAS_ENTITY_TEXT_CAPACITY, | |
| 586 "%s", | |
| 587 p_entity->text); | |
| 588 break; | |
| 589 } | |
| 590 } | |
| 591 g_app->dictation_overlay_visible = | |
| 592 g_app->dictation_transcript[0] || | |
| 593 g_app->dictation_partial[0]; | |
| 594 Canvas_Dev_UI_Set_Snapshot_Status( | |
| 595 &g_app->dev_ui, | |
| 596 "Snapshot loaded"); | |
| 597 } | |
| 598 #endif | |
| 599 | |
| 424 static void Canvas_App_Draw_Frame(void) | 600 static void Canvas_App_Draw_Frame(void) |
| 425 { | 601 { |
| 426 int32 width = GetScreenWidth(); | 602 int32 width = GetScreenWidth(); |
| 427 int32 height = GetScreenHeight(); | 603 int32 height = GetScreenHeight(); |
| 428 Canvas_Camera_Set_Viewport(&g_app->camera, width, height); | 604 Canvas_Camera_Set_Viewport(&g_app->camera, width, height); |
| 429 | 605 |
| 430 boolean block_pointer_input = FALSE; | 606 boolean block_pointer_input = FALSE; |
| 607 boolean dev_ui_editing = FALSE; | |
| 431 #if defined(INFINITE_CANVAS_DEV_UI) | 608 #if defined(INFINITE_CANVAS_DEV_UI) |
| 432 block_pointer_input = Canvas_Dev_UI_Contains_Pointer(&g_app->dev_ui); | 609 block_pointer_input = Canvas_Dev_UI_Contains_Pointer(&g_app->dev_ui); |
| 433 #endif | 610 dev_ui_editing = Canvas_Dev_UI_Is_Editing(&g_app->dev_ui); |
| 611 #endif | |
| 612 block_pointer_input = | |
| 613 Canvas_App_Update_Agent_Indicator_Input(block_pointer_input) || | |
| 614 block_pointer_input; | |
| 434 Canvas_Web_Surface_Set_Dark_Mode( | 615 Canvas_Web_Surface_Set_Dark_Mode( |
| 435 &g_app->web_surface, | 616 &g_app->web_surface, |
| 436 g_app->theme.resolved_mode == CANVAS_THEME_DARK ? TRUE : FALSE); | 617 g_app->theme.resolved_mode == CANVAS_THEME_DARK ? TRUE : FALSE); |
| 437 boolean dictation_hotkey = | 618 boolean dictation_hotkey = |
| 619 !dev_ui_editing && | |
| 438 !Canvas_Scene_Is_Text_Editing(&g_app->scene) && | 620 !Canvas_Scene_Is_Text_Editing(&g_app->scene) && |
| 439 !Canvas_Web_Chrome_Is_Editing(&g_app->scene) && | 621 !Canvas_Web_Chrome_Is_Editing(&g_app->scene) && |
| 440 IsKeyPressed(KEY_M); | 622 IsKeyPressed(KEY_M); |
| 441 if (dictation_hotkey) { | 623 if (dictation_hotkey) { |
| 442 Canvas_App_Set_Dictation_Active( | 624 Canvas_App_Set_Dictation_Active( |
| 494 g_app->font, | 676 g_app->font, |
| 495 block_pointer_input); | 677 block_pointer_input); |
| 496 boolean keyboard_focus = | 678 boolean keyboard_focus = |
| 497 Canvas_Scene_Is_Text_Editing(&g_app->scene) || | 679 Canvas_Scene_Is_Text_Editing(&g_app->scene) || |
| 498 Canvas_Web_Chrome_Is_Editing(&g_app->scene) || | 680 Canvas_Web_Chrome_Is_Editing(&g_app->scene) || |
| 681 dev_ui_editing || | |
| 499 Canvas_Web_Surface_Is_Focused(&g_app->web_surface); | 682 Canvas_Web_Surface_Is_Focused(&g_app->web_surface); |
| 500 if (!keyboard_focus && IsKeyPressed(KEY_P)) { | 683 if (!keyboard_focus && IsKeyPressed(KEY_P)) { |
| 501 Canvas_Scene_Toggle_Selected_Pin(&g_app->scene, &g_app->camera); | 684 Canvas_Scene_Toggle_Selected_Pin(&g_app->scene, &g_app->camera); |
| 502 } | 685 } |
| 503 if (!keyboard_focus && | 686 if (!keyboard_focus && |
| 612 sizeof(agent_error)) : | 795 sizeof(agent_error)) : |
| 613 CANVAS_AGENT_IDLE; | 796 CANVAS_AGENT_IDLE; |
| 614 if (agent_status == CANVAS_AGENT_READY) { | 797 if (agent_status == CANVAS_AGENT_READY) { |
| 615 Canvas_App_Apply_Agent_Response(agent_response); | 798 Canvas_App_Apply_Agent_Response(agent_response); |
| 616 } else if (agent_status == CANVAS_AGENT_ERROR) { | 799 } else if (agent_status == CANVAS_AGENT_ERROR) { |
| 617 Canvas_Entity *p_pending = | 800 if (g_app->pending_prompt[0]) { |
| 618 Canvas_App_Find_Entity(g_app->pending_conversation_id); | 801 Canvas_App_Show_Agent_Notification( |
| 619 if (p_pending) { | 802 "Copilot unavailable", |
| 620 snprintf(p_pending->label, sizeof(p_pending->label), "Copilot unavailable"); | |
| 621 Canvas_App_Set_Conversation_Text( | |
| 622 p_pending, | |
| 623 g_app->pending_prompt, | |
| 624 "System", | |
| 625 agent_error); | 803 agent_error); |
| 626 p_pending->agent_working = FALSE; | 804 } |
| 627 } else if (g_app->pending_prompt[0]) { | 805 g_app->pending_indicator_visible = FALSE; |
| 628 Vector2 position = Canvas_Camera_Screen_To_World( | |
| 629 &g_app->camera, | |
| 630 (Vector2){ | |
| 631 (float)g_app->camera.viewport_width * 0.5f - 210.0f, | |
| 632 (float)g_app->camera.viewport_height * 0.5f - 150.0f, | |
| 633 }); | |
| 634 Canvas_Scene_Add_Conversation( | |
| 635 &g_app->scene, | |
| 636 "Copilot unavailable", | |
| 637 g_app->pending_prompt, | |
| 638 agent_error, | |
| 639 position); | |
| 640 } | |
| 641 g_app->pending_conversation_id = 0; | |
| 642 g_app->pending_prompt[0] = '\0'; | 806 g_app->pending_prompt[0] = '\0'; |
| 643 } | 807 } |
| 644 | 808 |
| 645 Canvas_Web_Surface_Sync(&g_app->web_surface, &g_app->camera, &g_app->scene); | 809 Canvas_Web_Surface_Sync(&g_app->web_surface, &g_app->camera, &g_app->scene); |
| 646 | 810 |
| 685 Canvas_Draw_World_Overlays( | 849 Canvas_Draw_World_Overlays( |
| 686 &g_app->camera, | 850 &g_app->camera, |
| 687 &g_app->scene, | 851 &g_app->scene, |
| 688 g_app->font, | 852 g_app->font, |
| 689 &g_app->theme); | 853 &g_app->theme); |
| 854 Canvas_App_Draw_Agent_Indicator(); | |
| 690 #if defined(INFINITE_CANVAS_DEV_UI) | 855 #if defined(INFINITE_CANVAS_DEV_UI) |
| 691 Canvas_Dev_UI_Draw( | 856 Canvas_Dev_UI_Action dev_action = Canvas_Dev_UI_Draw( |
| 692 &g_app->dev_ui, | 857 &g_app->dev_ui, |
| 693 &g_app->camera, | 858 &g_app->camera, |
| 694 &g_app->scene, | 859 &g_app->scene, |
| 695 g_app->font, | 860 g_app->font, |
| 696 &g_app->theme); | 861 &g_app->theme); |
| 697 #endif | 862 #endif |
| 698 Canvas_App_Draw_Listening_Indicator(); | 863 Canvas_App_Draw_Listening_Indicator(); |
| 699 EndDrawing(); | 864 EndDrawing(); |
| 865 #if defined(INFINITE_CANVAS_DEV_UI) | |
| 866 Canvas_App_Handle_Dev_Action(dev_action); | |
| 867 #endif | |
| 700 g_app->frame_count++; | 868 g_app->frame_count++; |
| 701 #if !defined(PLATFORM_WEB) | 869 #if !defined(PLATFORM_WEB) |
| 702 if (g_app->frame_count == 30) { | 870 if (g_app->frame_count == 30) { |
| 703 RestoreWindow(); | 871 RestoreWindow(); |
| 704 SetWindowFocused(); | 872 SetWindowFocused(); |