comparison infinite_canvas/web_surface_native.cc @ 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
3 } 3 }
4 4
5 #include "include/cef_app.h" 5 #include "include/cef_app.h"
6 #include "include/cef_browser.h" 6 #include "include/cef_browser.h"
7 #include "include/cef_client.h" 7 #include "include/cef_client.h"
8 #include "include/cef_display_handler.h"
8 #include "include/cef_render_handler.h" 9 #include "include/cef_render_handler.h"
9 #include "include/capi/cef_app_capi.h" 10 #include "include/capi/cef_app_capi.h"
10 11
11 #include <algorithm> 12 #include <algorithm>
12 #include <cmath> 13 #include <cmath>
38 char g_locales_switch[PATH_MAX + 64] = {}; 39 char g_locales_switch[PATH_MAX + 64] = {};
39 #endif 40 #endif
40 41
41 void FindResourcePaths(); 42 void FindResourcePaths();
42 43
44 void ApplyColorScheme(CefRefPtr<CefBrowser> browser, boolean dark_mode) {
45 if (!browser) return;
46 CefRefPtr<CefDictionaryValue> params = CefDictionaryValue::Create();
47 CefRefPtr<CefListValue> features = CefListValue::Create();
48 CefRefPtr<CefDictionaryValue> feature = CefDictionaryValue::Create();
49 feature->SetString("name", "prefers-color-scheme");
50 feature->SetString("value", dark_mode ? "dark" : "light");
51 features->SetDictionary(0, feature);
52 params->SetList("features", features);
53 browser->GetHost()->ExecuteDevToolsMethod(
54 0,
55 "Emulation.setEmulatedMedia",
56 params);
57
58 CefRefPtr<CefFrame> frame = browser->GetMainFrame();
59 const char *script = dark_mode
60 ? "document.documentElement.style.colorScheme='dark'"
61 : "document.documentElement.style.colorScheme='light'";
62 frame->ExecuteJavaScript(script, frame->GetURL(), 0);
63 }
64
43 const Canvas_Entity *FindSurfaceEntity( 65 const Canvas_Entity *FindSurfaceEntity(
44 const Canvas_Scene *scene, 66 const Canvas_Scene *scene,
45 uint32 entity_id) { 67 uint32 entity_id) {
46 for (size_t index = 0; index < Dowa_Array_Length(scene->p_entities); ++index) { 68 for (size_t index = 0; index < Dowa_Array_Length(scene->p_entities); ++index) {
47 const Canvas_Entity *entity = &scene->p_entities[index]; 69 const Canvas_Entity *entity = &scene->p_entities[index];
54 return nullptr; 76 return nullptr;
55 } 77 }
56 78
57 Rectangle ContentBounds( 79 Rectangle ContentBounds(
58 const Canvas_Camera *camera, 80 const Canvas_Camera *camera,
81 const Canvas_Scene *scene,
59 const Canvas_Entity *entity) { 82 const Canvas_Entity *entity) {
83 (void)scene;
60 float frame_inset = CANVAS_WEB_FRAME_INSET / camera->zoom; 84 float frame_inset = CANVAS_WEB_FRAME_INSET / camera->zoom;
85 float amount = std::clamp(entity->web_chrome_amount, 0.0f, 1.0f);
86 float eased = amount * amount * (3.0f - 2.0f * amount);
87 float toolbar_height = CANVAS_WEB_TOOLBAR_HEIGHT * eased / camera->zoom;
61 float resize_handle = CANVAS_WEB_RESIZE_HANDLE / camera->zoom; 88 float resize_handle = CANVAS_WEB_RESIZE_HANDLE / camera->zoom;
62 Vector2 position = Canvas_Camera_World_To_Screen( 89 Vector2 position = Canvas_Camera_World_To_Screen(
63 camera, 90 camera,
64 Vector2{ 91 Vector2{
65 entity->position.x + frame_inset, 92 entity->position.x + frame_inset,
66 entity->position.y + frame_inset}); 93 entity->position.y + frame_inset + toolbar_height});
67 return Rectangle{ 94 return Rectangle{
68 position.x, 95 position.x,
69 position.y, 96 position.y,
70 (entity->size.x - frame_inset - resize_handle) * camera->zoom, 97 (entity->size.x - frame_inset - resize_handle) * camera->zoom,
71 (entity->size.y - frame_inset - resize_handle) * camera->zoom, 98 (entity->size.y - frame_inset - resize_handle - toolbar_height) *
99 camera->zoom,
72 }; 100 };
73 } 101 }
74 102
75 boolean IsVisible(const Canvas_Camera *camera, Rectangle bounds) { 103 boolean IsVisible(const Canvas_Camera *camera, Rectangle bounds) {
76 return camera->zoom >= 0.30f && 104 return camera->zoom >= 0.30f &&
77 bounds.x + bounds.width > 0.0f && 105 bounds.x + bounds.width > 0.0f &&
78 bounds.y + bounds.height > 0.0f && 106 bounds.y + bounds.height > 0.0f &&
79 bounds.x < static_cast<float>(camera->viewport_width) && 107 bounds.x < static_cast<float>(camera->viewport_width) &&
80 bounds.y < static_cast<float>(camera->viewport_height); 108 bounds.y < static_cast<float>(camera->viewport_height);
109 }
110
111 float EntityOpacity(const Canvas_Entity *entity) {
112 float amount = std::clamp(entity->visibility_amount, 0.0f, 1.0f);
113 return amount * amount * (3.0f - 2.0f * amount);
81 } 114 }
82 115
83 int32 FrameRateForView( 116 int32 FrameRateForView(
84 const Canvas_Web_Surface *surface, 117 const Canvas_Web_Surface *surface,
85 const Canvas_Web_View *view, 118 const Canvas_Web_View *view,
131 DISALLOW_COPY_AND_ASSIGN(CanvasCefApp); 164 DISALLOW_COPY_AND_ASSIGN(CanvasCefApp);
132 }; 165 };
133 166
134 class CanvasCefClient : public CefClient, 167 class CanvasCefClient : public CefClient,
135 public CefRenderHandler, 168 public CefRenderHandler,
169 public CefDisplayHandler,
136 public CefLifeSpanHandler { 170 public CefLifeSpanHandler {
137 public: 171 public:
138 explicit CanvasCefClient(Canvas_Web_View *view) : view_(view) {} 172 explicit CanvasCefClient(Canvas_Web_View *view) : view_(view) {}
139 173
140 CefRefPtr<CefRenderHandler> GetRenderHandler() override { return this; } 174 CefRefPtr<CefRenderHandler> GetRenderHandler() override { return this; }
175 CefRefPtr<CefDisplayHandler> GetDisplayHandler() override { return this; }
141 CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler() override { return this; } 176 CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler() override { return this; }
177
178 void OnAddressChange(
179 CefRefPtr<CefBrowser> browser,
180 CefRefPtr<CefFrame> frame,
181 const CefString &url) override {
182 if (!frame->IsMain()) return;
183 std::snprintf(
184 view_->url,
185 sizeof(view_->url),
186 "%s",
187 url.ToString().c_str());
188 view_->can_go_back = browser->CanGoBack() ? TRUE : FALSE;
189 view_->can_go_forward = browser->CanGoForward() ? TRUE : FALSE;
190 }
142 191
143 void GetViewRect(CefRefPtr<CefBrowser> browser, CefRect &rect) override { 192 void GetViewRect(CefRefPtr<CefBrowser> browser, CefRect &rect) override {
144 (void)browser; 193 (void)browser;
145 rect = CefRect(0, 0, view_->texture_width, view_->texture_height); 194 rect = CefRect(0, 0, view_->texture_width, view_->texture_height);
146 } 195 }
256 } 305 }
257 } 306 }
258 return nullptr; 307 return nullptr;
259 } 308 }
260 309
310 const Canvas_Web_View *FindView(
311 const Canvas_Web_Surface *surface,
312 uint32 entity_id) {
313 for (int32 index = 0; index < CANVAS_MAX_WEB_VIEWS; ++index) {
314 if (surface->views[index].entity_id == entity_id) {
315 return &surface->views[index];
316 }
317 }
318 return nullptr;
319 }
320
261 Canvas_Web_View *FindInactiveView(Canvas_Web_Surface *surface) { 321 Canvas_Web_View *FindInactiveView(Canvas_Web_Surface *surface) {
262 for (int32 index = 0; index < CANVAS_MAX_WEB_VIEWS; ++index) { 322 for (int32 index = 0; index < CANVAS_MAX_WEB_VIEWS; ++index) {
263 if (!surface->views[index].active) return &surface->views[index]; 323 if (!surface->views[index].active) return &surface->views[index];
264 } 324 }
265 return nullptr; 325 return nullptr;
268 boolean SceneHasVisibleEntity( 328 boolean SceneHasVisibleEntity(
269 const Canvas_Scene *scene, 329 const Canvas_Scene *scene,
270 const Canvas_Camera *camera, 330 const Canvas_Camera *camera,
271 uint32 entity_id) { 331 uint32 entity_id) {
272 const Canvas_Entity *entity = FindSurfaceEntity(scene, entity_id); 332 const Canvas_Entity *entity = FindSurfaceEntity(scene, entity_id);
273 return entity && IsVisible(camera, ContentBounds(camera, entity)); 333 return entity && IsVisible(camera, ContentBounds(camera, scene, entity));
274 } 334 }
275 335
276 boolean EnsureViewResolution( 336 boolean EnsureViewResolution(
277 Canvas_Web_Surface *surface, 337 Canvas_Web_Surface *surface,
278 Canvas_Web_View *view, 338 Canvas_Web_View *view,
329 state->clients[index] = new CanvasCefClient(view); 389 state->clients[index] = new CanvasCefClient(view);
330 CefWindowInfo window_info; 390 CefWindowInfo window_info;
331 window_info.SetAsWindowless(kNullWindowHandle); 391 window_info.SetAsWindowless(kNullWindowHandle);
332 CefBrowserSettings browser_settings; 392 CefBrowserSettings browser_settings;
333 browser_settings.windowless_frame_rate = 30; 393 browser_settings.windowless_frame_rate = 30;
334 browser_settings.background_color = CefColorSetARGB(0, 255, 255, 255); 394 browser_settings.background_color = surface->dark_mode
395 ? CefColorSetARGB(255, 15, 15, 17)
396 : CefColorSetARGB(255, 255, 255, 255);
335 const char *initial_url = 397 const char *initial_url =
336 view->entity_type == CANVAS_ENTITY_IMAGE ? "about:blank" : url; 398 view->entity_type == CANVAS_ENTITY_IMAGE ? "about:blank" : url;
337 if (!CefBrowserHost::CreateBrowser( 399 if (!CefBrowserHost::CreateBrowser(
338 window_info, 400 window_info,
339 state->clients[index], 401 state->clients[index],
429 view->active = FALSE; 491 view->active = FALSE;
430 view->entity_id = 0; 492 view->entity_id = 0;
431 return; 493 return;
432 } 494 }
433 if (entity->type == CANVAS_ENTITY_WEB_CONTENT) { 495 if (entity->type == CANVAS_ENTITY_WEB_CONTENT) {
496 std::snprintf(
497 view->source_url,
498 sizeof(view->source_url),
499 "%s",
500 entity->text);
434 std::snprintf(view->url, sizeof(view->url), "%s", entity->text); 501 std::snprintf(view->url, sizeof(view->url), "%s", entity->text);
435 } 502 }
436 return; 503 return;
437 } 504 }
438 EnsureViewResolution(surface, view, bounds); 505 EnsureViewResolution(surface, view, bounds);
439 506
440 if (type_changed || std::strcmp(view->url, entity->text) != 0) { 507 if (type_changed || std::strcmp(view->source_url, entity->text) != 0) {
441 CefRefPtr<CanvasCefClient> client = state->clients[index]; 508 CefRefPtr<CanvasCefClient> client = state->clients[index];
442 if (client && client->browser()) { 509 if (client && client->browser()) {
443 if (entity->type == CANVAS_ENTITY_IMAGE) { 510 if (entity->type == CANVAS_ENTITY_IMAGE) {
444 LoadImageSource(client->browser(), entity->text); 511 LoadImageSource(client->browser(), entity->text);
445 } else { 512 } else {
446 client->browser()->GetMainFrame()->LoadURL(entity->text); 513 client->browser()->GetMainFrame()->LoadURL(entity->text);
447 } 514 }
515 std::snprintf(
516 view->source_url,
517 sizeof(view->source_url),
518 "%s",
519 entity->text);
448 std::snprintf(view->url, sizeof(view->url), "%s", entity->text); 520 std::snprintf(view->url, sizeof(view->url), "%s", entity->text);
449 } 521 }
450 } 522 }
451 } 523 }
452 524
462 534
463 for (size_t index = 0; index < Dowa_Array_Length(scene->p_entities); ++index) { 535 for (size_t index = 0; index < Dowa_Array_Length(scene->p_entities); ++index) {
464 const Canvas_Entity *entity = &scene->p_entities[index]; 536 const Canvas_Entity *entity = &scene->p_entities[index];
465 if ((entity->type != CANVAS_ENTITY_WEB_CONTENT && 537 if ((entity->type != CANVAS_ENTITY_WEB_CONTENT &&
466 entity->type != CANVAS_ENTITY_IMAGE) || 538 entity->type != CANVAS_ENTITY_IMAGE) ||
467 !IsVisible(camera, ContentBounds(camera, entity))) { 539 !IsVisible(camera, ContentBounds(camera, scene, entity))) {
468 continue; 540 continue;
469 } 541 }
470 Rectangle bounds = ContentBounds(camera, entity); 542 Rectangle bounds = ContentBounds(camera, scene, entity);
471 Canvas_Web_View *view = FindView(surface, entity->id); 543 Canvas_Web_View *view = FindView(surface, entity->id);
472 if (view) { 544 if (view) {
473 BindView(surface, view, entity, bounds); 545 BindView(surface, view, entity, bounds);
474 continue; 546 continue;
475 } 547 }
492 host->WasHidden(!view->active); 564 host->WasHidden(!view->active);
493 if (view->active) { 565 if (view->active) {
494 const Canvas_Entity *entity = FindSurfaceEntity(scene, view->entity_id); 566 const Canvas_Entity *entity = FindSurfaceEntity(scene, view->entity_id);
495 if (entity) { 567 if (entity) {
496 host->SetWindowlessFrameRate( 568 host->SetWindowlessFrameRate(
497 FrameRateForView(surface, view, ContentBounds(camera, entity))); 569 FrameRateForView(
570 surface,
571 view,
572 ContentBounds(camera, scene, entity)));
498 } 573 }
499 } 574 }
500 } 575 }
501 } 576 }
502 } 577 }
538 return CefExecuteProcess(args, app, nullptr); 613 return CefExecuteProcess(args, app, nullptr);
539 } 614 }
540 615
541 extern "C" boolean Canvas_Web_Surface_Init( 616 extern "C" boolean Canvas_Web_Surface_Init(
542 Canvas_Web_Surface *p_surface, 617 Canvas_Web_Surface *p_surface,
543 Dowa_Arena *p_arena) { 618 Dowa_Arena *p_arena,
619 boolean dark_mode) {
544 std::memset(p_surface, 0, sizeof(*p_surface)); 620 std::memset(p_surface, 0, sizeof(*p_surface));
545 p_surface->p_arena = p_arena; 621 p_surface->p_arena = p_arena;
622 p_surface->dark_mode = dark_mode;
546 NativeState *state = static_cast<NativeState *>( 623 NativeState *state = static_cast<NativeState *>(
547 Dowa_Arena_Allocate(p_arena, sizeof(NativeState))); 624 Dowa_Arena_Allocate(p_arena, sizeof(NativeState)));
548 if (!state) return FALSE; 625 if (!state) return FALSE;
549 626
550 new (state) NativeState(); 627 new (state) NativeState();
575 state->cef_initialized = TRUE; 652 state->cef_initialized = TRUE;
576 p_surface->initialized = TRUE; 653 p_surface->initialized = TRUE;
577 return TRUE; 654 return TRUE;
578 } 655 }
579 656
657 extern "C" void Canvas_Web_Surface_Set_Dark_Mode(
658 Canvas_Web_Surface *p_surface,
659 boolean dark_mode) {
660 if (!p_surface->initialized || p_surface->dark_mode == dark_mode) return;
661 p_surface->dark_mode = dark_mode;
662 }
663
580 extern "C" boolean Canvas_Web_Surface_Update_Input( 664 extern "C" boolean Canvas_Web_Surface_Update_Input(
581 Canvas_Web_Surface *p_surface, 665 Canvas_Web_Surface *p_surface,
582 const Canvas_Camera *p_camera, 666 const Canvas_Camera *p_camera,
583 const Canvas_Scene *p_scene) { 667 const Canvas_Scene *p_scene) {
584 if (!p_surface->initialized || !p_surface->p_native) return FALSE; 668 if (!p_surface->initialized || !p_surface->p_native) return FALSE;
586 Vector2 mouse = GetMousePosition(); 670 Vector2 mouse = GetMousePosition();
587 Canvas_Web_View *pointer_view = nullptr; 671 Canvas_Web_View *pointer_view = nullptr;
588 const Canvas_Entity *pointer_entity = nullptr; 672 const Canvas_Entity *pointer_entity = nullptr;
589 Rectangle pointer_bounds = {}; 673 Rectangle pointer_bounds = {};
590 674
591 for (int32 index = CANVAS_MAX_WEB_VIEWS - 1; index >= 0; --index) { 675 Vector2 world_mouse = Canvas_Camera_Screen_To_World(p_camera, mouse);
592 Canvas_Web_View *view = &p_surface->views[index]; 676 int32 top_index = Canvas_Scene_Topmost_Visual(
593 if (!view->active) continue; 677 p_scene,
594 const Canvas_Entity *entity = FindSurfaceEntity(p_scene, view->entity_id); 678 world_mouse,
595 if (!entity || entity->type != CANVAS_ENTITY_WEB_CONTENT) continue; 679 p_camera->zoom);
596 Rectangle bounds = ContentBounds(p_camera, entity); 680 if (top_index >= 0) {
597 if (CheckCollisionPointRec(mouse, bounds)) { 681 const Canvas_Entity *entity = &p_scene->p_entities[top_index];
598 pointer_view = view; 682 if (entity->type == CANVAS_ENTITY_WEB_CONTENT &&
599 pointer_entity = entity; 683 !entity->removing &&
600 pointer_bounds = bounds; 684 EntityOpacity(entity) >= 0.99f) {
601 break; 685 Canvas_Web_View *view = FindView(p_surface, entity->id);
686 Rectangle bounds = ContentBounds(p_camera, p_scene, entity);
687 if (view &&
688 view->active &&
689 CheckCollisionPointRec(mouse, bounds)) {
690 pointer_view = view;
691 pointer_entity = entity;
692 pointer_bounds = bounds;
693 }
602 } 694 }
603 } 695 }
604 696
605 if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) { 697 if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) {
606 p_surface->focused_entity_id = pointer_view ? pointer_view->entity_id : 0; 698 p_surface->focused_entity_id = pointer_view ? pointer_view->entity_id : 0;
623 const Canvas_Entity *input_entity = pointer_entity ? 715 const Canvas_Entity *input_entity = pointer_entity ?
624 pointer_entity : 716 pointer_entity :
625 FindSurfaceEntity(p_scene, input_view->entity_id); 717 FindSurfaceEntity(p_scene, input_view->entity_id);
626 if (!browser || !input_entity) return pointer_view != nullptr; 718 if (!browser || !input_entity) return pointer_view != nullptr;
627 719
628 Rectangle bounds = pointer_view ? pointer_bounds : ContentBounds(p_camera, input_entity); 720 Rectangle bounds = pointer_view
721 ? pointer_bounds
722 : ContentBounds(p_camera, p_scene, input_entity);
629 boolean inside = pointer_view == input_view; 723 boolean inside = pointer_view == input_view;
630 float scale_x = static_cast<float>(input_view->texture_width) / 724 float scale_x = static_cast<float>(input_view->texture_width) /
631 std::max(bounds.width, 1.0f); 725 std::max(bounds.width, 1.0f);
632 float scale_y = static_cast<float>(input_view->texture_height) / 726 float scale_y = static_cast<float>(input_view->texture_height) /
633 std::max(bounds.height, 1.0f); 727 std::max(bounds.height, 1.0f);
645 host->SendMouseClickEvent(event, MBT_LEFT, true, 1); 739 host->SendMouseClickEvent(event, MBT_LEFT, true, 1);
646 } 740 }
647 if (inside) { 741 if (inside) {
648 float wheel = GetMouseWheelMove(); 742 float wheel = GetMouseWheelMove();
649 if (wheel != 0.0f) { 743 if (wheel != 0.0f) {
650 host->SendMouseWheelEvent(event, 0, static_cast<int>(wheel * 80.0f)); 744 boolean control_zoom =
745 IsKeyDown(KEY_LEFT_CONTROL) ||
746 IsKeyDown(KEY_RIGHT_CONTROL) ||
747 IsKeyDown(KEY_LEFT_SUPER) ||
748 IsKeyDown(KEY_RIGHT_SUPER);
749 if (!control_zoom) {
750 host->SendMouseWheelEvent(event, 0, static_cast<int>(wheel * 80.0f));
751 }
651 } 752 }
652 } 753 }
653 754
654 if (p_surface->focused_entity_id == input_view->entity_id) { 755 if (p_surface->focused_entity_id == input_view->entity_id) {
655 int32 codepoint = GetCharPressed(); 756 int32 codepoint = GetCharPressed();
685 host->SendKeyEvent(key); 786 host->SendKeyEvent(key);
686 key.type = KEYEVENT_KEYUP; 787 key.type = KEYEVENT_KEYUP;
687 host->SendKeyEvent(key); 788 host->SendKeyEvent(key);
688 } 789 }
689 } 790 }
690 return inside || 791 if (inside &&
691 p_surface->focused_entity_id == input_view->entity_id; 792 (IsKeyDown(KEY_LEFT_CONTROL) ||
793 IsKeyDown(KEY_RIGHT_CONTROL) ||
794 IsKeyDown(KEY_LEFT_SUPER) ||
795 IsKeyDown(KEY_RIGHT_SUPER)) &&
796 GetMouseWheelMove() != 0.0f) {
797 return FALSE;
798 }
799 return inside;
692 } 800 }
693 801
694 extern "C" void Canvas_Web_Surface_Sync( 802 extern "C" void Canvas_Web_Surface_Sync(
695 Canvas_Web_Surface *p_surface, 803 Canvas_Web_Surface *p_surface,
696 const Canvas_Camera *p_camera, 804 const Canvas_Camera *p_camera,
718 } 826 }
719 if (view->texture_ready && view->pixels_dirty) { 827 if (view->texture_ready && view->pixels_dirty) {
720 UpdateTexture(view->texture, view->p_pixels); 828 UpdateTexture(view->texture, view->p_pixels);
721 view->pixels_dirty = FALSE; 829 view->pixels_dirty = FALSE;
722 } 830 }
831 NativeState *state = static_cast<NativeState *>(p_surface->p_native);
832 CefRefPtr<CanvasCefClient> client = state->clients[index];
833 if (client && client->browser() &&
834 view->entity_type == CANVAS_ENTITY_WEB_CONTENT) {
835 if (!view->color_scheme_applied ||
836 view->applied_dark_mode != p_surface->dark_mode) {
837 ApplyColorScheme(client->browser(), p_surface->dark_mode);
838 view->color_scheme_applied = TRUE;
839 view->applied_dark_mode = p_surface->dark_mode;
840 }
841 view->can_go_back = client->browser()->CanGoBack() ? TRUE : FALSE;
842 view->can_go_forward = client->browser()->CanGoForward() ? TRUE : FALSE;
843 }
723 } 844 }
724 } 845 }
725 846
726 extern "C" void Canvas_Web_Surface_Draw( 847 extern "C" void Canvas_Web_Surface_Draw(
727 const Canvas_Web_Surface *p_surface, 848 const Canvas_Web_Surface *p_surface,
728 const Canvas_Camera *p_camera, 849 const Canvas_Camera *p_camera,
729 const Canvas_Scene *p_scene) { 850 const Canvas_Scene *p_scene) {
730 for (int32 index = 0; index < CANVAS_MAX_WEB_VIEWS; ++index) { 851 for (size_t index = 0;
731 const Canvas_Web_View *view = &p_surface->views[index]; 852 index < Dowa_Array_Length(p_scene->p_entities);
732 if (!view->active || !view->texture_ready) continue; 853 ++index) {
733 const Canvas_Entity *entity = FindSurfaceEntity(p_scene, view->entity_id); 854 Canvas_Web_Surface_Draw_Entity(
734 if (!entity) continue; 855 p_surface,
735 Rectangle bounds = ContentBounds(p_camera, entity); 856 p_camera,
736 float left = std::max(bounds.x, 0.0f); 857 p_scene,
737 float top = std::max(bounds.y, 0.0f); 858 index);
738 float right = std::min( 859 }
739 bounds.x + bounds.width, 860 }
740 static_cast<float>(p_camera->viewport_width)); 861
741 float bottom = std::min( 862 extern "C" void Canvas_Web_Surface_Draw_Entity(
742 bounds.y + bounds.height, 863 const Canvas_Web_Surface *p_surface,
743 static_cast<float>(p_camera->viewport_height)); 864 const Canvas_Camera *p_camera,
744 if (right <= left || bottom <= top) continue; 865 const Canvas_Scene *p_scene,
745 866 size_t entity_index) {
746 BeginScissorMode( 867 if (entity_index >= Dowa_Array_Length(p_scene->p_entities)) return;
747 static_cast<int>(left), 868 const Canvas_Entity *entity = &p_scene->p_entities[entity_index];
748 static_cast<int>(top), 869 if (entity->type != CANVAS_ENTITY_WEB_CONTENT &&
749 static_cast<int>(right - left), 870 entity->type != CANVAS_ENTITY_IMAGE) {
750 static_cast<int>(bottom - top)); 871 return;
751 DrawTexturePro( 872 }
752 view->texture, 873 const Canvas_Web_View *view = FindView(p_surface, entity->id);
753 Rectangle{ 874 if (!view || !view->active || !view->texture_ready) return;
754 0.0f, 875
755 0.0f, 876 Rectangle bounds = ContentBounds(p_camera, p_scene, entity);
756 static_cast<float>(view->texture_width), 877 float left = std::max(bounds.x, 0.0f);
757 static_cast<float>(view->texture_height), 878 float top = std::max(bounds.y, 0.0f);
758 }, 879 float right = std::min(
759 bounds, 880 bounds.x + bounds.width,
760 Vector2{0.0f, 0.0f}, 881 static_cast<float>(p_camera->viewport_width));
761 0.0f, 882 float bottom = std::min(
762 WHITE); 883 bounds.y + bounds.height,
763 EndScissorMode(); 884 static_cast<float>(p_camera->viewport_height));
885 if (right <= left || bottom <= top) return;
886
887 BeginScissorMode(
888 static_cast<int>(left),
889 static_cast<int>(top),
890 static_cast<int>(right - left),
891 static_cast<int>(bottom - top));
892 DrawTexturePro(
893 view->texture,
894 Rectangle{
895 0.0f,
896 0.0f,
897 static_cast<float>(view->texture_width),
898 static_cast<float>(view->texture_height),
899 },
900 bounds,
901 Vector2{0.0f, 0.0f},
902 0.0f,
903 Color{
904 255,
905 255,
906 255,
907 static_cast<unsigned char>(EntityOpacity(entity) * 255.0f)});
908 EndScissorMode();
909 }
910
911 extern "C" const char *Canvas_Web_Surface_URL(
912 const Canvas_Web_Surface *p_surface,
913 uint32 entity_id) {
914 const Canvas_Web_View *view = FindView(p_surface, entity_id);
915 return view && view->url[0] ? view->url : nullptr;
916 }
917
918 extern "C" boolean Canvas_Web_Surface_Can_Go_Back(
919 const Canvas_Web_Surface *p_surface,
920 uint32 entity_id) {
921 const Canvas_Web_View *view = FindView(p_surface, entity_id);
922 return view ? view->can_go_back : FALSE;
923 }
924
925 extern "C" boolean Canvas_Web_Surface_Can_Go_Forward(
926 const Canvas_Web_Surface *p_surface,
927 uint32 entity_id) {
928 const Canvas_Web_View *view = FindView(p_surface, entity_id);
929 return view ? view->can_go_forward : FALSE;
930 }
931
932 extern "C" void Canvas_Web_Surface_Go_Back(
933 Canvas_Web_Surface *p_surface,
934 uint32 entity_id) {
935 if (!p_surface->p_native) return;
936 Canvas_Web_View *view = FindView(p_surface, entity_id);
937 if (!view) return;
938 NativeState *state = static_cast<NativeState *>(p_surface->p_native);
939 CefRefPtr<CanvasCefClient> client = state->clients[ViewIndex(p_surface, view)];
940 if (client && client->browser() && client->browser()->CanGoBack()) {
941 client->browser()->GoBack();
942 }
943 }
944
945 extern "C" void Canvas_Web_Surface_Go_Forward(
946 Canvas_Web_Surface *p_surface,
947 uint32 entity_id) {
948 if (!p_surface->p_native) return;
949 Canvas_Web_View *view = FindView(p_surface, entity_id);
950 if (!view) return;
951 NativeState *state = static_cast<NativeState *>(p_surface->p_native);
952 CefRefPtr<CanvasCefClient> client = state->clients[ViewIndex(p_surface, view)];
953 if (client && client->browser() && client->browser()->CanGoForward()) {
954 client->browser()->GoForward();
764 } 955 }
765 } 956 }
766 957
767 extern "C" boolean Canvas_Web_Surface_Is_Focused( 958 extern "C" boolean Canvas_Web_Surface_Is_Focused(
768 const Canvas_Web_Surface *p_surface) { 959 const Canvas_Web_Surface *p_surface) {