comparison infinite_canvas/web_surface_native.cc @ 276:b55c22cff335

Add interactive infinite canvas prototype
author MrJuneJune <me@mrjunejune.com>
date Mon, 17 Aug 2026 16:57:56 -0700
parents
children 8d560f50ed4c
comparison
equal deleted inserted replaced
274:c9be578316a6 276:b55c22cff335
1 extern "C" {
2 #include "infinite_canvas/web_surface.h"
3 }
4
5 #include "include/cef_app.h"
6 #include "include/cef_browser.h"
7 #include "include/cef_client.h"
8 #include "include/cef_render_handler.h"
9 #include "include/capi/cef_app_capi.h"
10
11 #include <algorithm>
12 #include <cmath>
13 #include <cstdio>
14 #include <cstdlib>
15 #include <cstring>
16 #include <filesystem>
17 #include <new>
18
19 #if defined(OS_LINUX)
20 #include <dlfcn.h>
21 #include <limits.h>
22 #endif
23
24 namespace {
25
26 constexpr int32 kResolutionStep = 64;
27 constexpr int32 kMinTextureWidth = 160;
28 constexpr int32 kMinTextureHeight = 120;
29 constexpr int32 kMaxTextureWidth = 1280;
30 constexpr int32 kMaxTextureHeight = 800;
31 int g_argc = 0;
32 char **g_argv = nullptr;
33 char *g_cef_argv[128] = {};
34 #if defined(OS_LINUX)
35 char g_resources_path[PATH_MAX + 32] = {};
36 char g_locales_path[PATH_MAX + 32] = {};
37 char g_resources_switch[PATH_MAX + 64] = {};
38 char g_locales_switch[PATH_MAX + 64] = {};
39 #endif
40
41 void FindResourcePaths();
42
43 const Canvas_Entity *FindSurfaceEntity(
44 const Canvas_Scene *scene,
45 uint32 entity_id) {
46 for (size_t index = 0; index < Dowa_Array_Length(scene->p_entities); ++index) {
47 const Canvas_Entity *entity = &scene->p_entities[index];
48 if ((entity->type == CANVAS_ENTITY_WEB_CONTENT ||
49 entity->type == CANVAS_ENTITY_IMAGE) &&
50 entity->id == entity_id) {
51 return entity;
52 }
53 }
54 return nullptr;
55 }
56
57 Rectangle ContentBounds(
58 const Canvas_Camera *camera,
59 const Canvas_Entity *entity) {
60 float frame_inset = CANVAS_WEB_FRAME_INSET / camera->zoom;
61 float resize_handle = CANVAS_WEB_RESIZE_HANDLE / camera->zoom;
62 Vector2 position = Canvas_Camera_World_To_Screen(
63 camera,
64 Vector2{
65 entity->position.x + frame_inset,
66 entity->position.y + frame_inset});
67 return Rectangle{
68 position.x,
69 position.y,
70 (entity->size.x - frame_inset - resize_handle) * camera->zoom,
71 (entity->size.y - frame_inset - resize_handle) * camera->zoom,
72 };
73 }
74
75 boolean IsVisible(const Canvas_Camera *camera, Rectangle bounds) {
76 return camera->zoom >= 0.30f &&
77 bounds.x + bounds.width > 0.0f &&
78 bounds.y + bounds.height > 0.0f &&
79 bounds.x < static_cast<float>(camera->viewport_width) &&
80 bounds.y < static_cast<float>(camera->viewport_height);
81 }
82
83 int32 FrameRateForView(
84 const Canvas_Web_Surface *surface,
85 const Canvas_Web_View *view,
86 Rectangle bounds) {
87 if (surface->focused_entity_id == view->entity_id) return 30;
88 float area = std::max(bounds.width, 0.0f) * std::max(bounds.height, 0.0f);
89 return area <= 220000.0f ? 12 : 20;
90 }
91
92 int32 BucketDimension(float value, int32 minimum, int32 maximum) {
93 int32 rounded = static_cast<int32>(
94 std::ceil(std::max(value, 1.0f) / kResolutionStep)) * kResolutionStep;
95 return std::clamp(rounded, minimum, maximum);
96 }
97
98 class CanvasCefApp : public CefApp,
99 public CefBrowserProcessHandler {
100 public:
101 CanvasCefApp() = default;
102
103 CefRefPtr<CefBrowserProcessHandler> GetBrowserProcessHandler() override {
104 return this;
105 }
106
107 void OnBeforeCommandLineProcessing(
108 const CefString &process_type,
109 CefRefPtr<CefCommandLine> command_line) override {
110 (void)process_type;
111 command_line->AppendSwitch("no-sandbox");
112 command_line->AppendSwitch("disable-gpu");
113 command_line->AppendSwitch("disable-gpu-compositing");
114 command_line->AppendSwitch("allow-file-access-from-files");
115 command_line->AppendSwitch("allow-universal-access-from-files");
116 command_line->AppendSwitchWithValue("log-severity", "disable");
117 #if defined(OS_LINUX)
118 if (g_resources_path[0]) {
119 command_line->AppendSwitchWithValue(
120 "resources-dir-path",
121 g_resources_path);
122 command_line->AppendSwitchWithValue(
123 "locales-dir-path",
124 g_locales_path);
125 }
126 #endif
127 }
128
129 private:
130 IMPLEMENT_REFCOUNTING(CanvasCefApp);
131 DISALLOW_COPY_AND_ASSIGN(CanvasCefApp);
132 };
133
134 class CanvasCefClient : public CefClient,
135 public CefRenderHandler,
136 public CefLifeSpanHandler {
137 public:
138 explicit CanvasCefClient(Canvas_Web_View *view) : view_(view) {}
139
140 CefRefPtr<CefRenderHandler> GetRenderHandler() override { return this; }
141 CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler() override { return this; }
142
143 void GetViewRect(CefRefPtr<CefBrowser> browser, CefRect &rect) override {
144 (void)browser;
145 rect = CefRect(0, 0, view_->texture_width, view_->texture_height);
146 }
147
148 void OnPaint(
149 CefRefPtr<CefBrowser> browser,
150 PaintElementType type,
151 const RectList &dirty_rects,
152 const void *buffer,
153 int width,
154 int height) override {
155 (void)browser;
156 (void)dirty_rects;
157 if (type != PET_VIEW || !view_->p_pixels ||
158 width != view_->texture_width ||
159 height != view_->texture_height) {
160 return;
161 }
162
163 const uint8 *source = static_cast<const uint8 *>(buffer);
164 uint8 *target = view_->p_pixels;
165 const size_t pixel_count = static_cast<size_t>(width) * height;
166 for (size_t index = 0; index < pixel_count; ++index) {
167 target[index * 4 + 0] = source[index * 4 + 2];
168 target[index * 4 + 1] = source[index * 4 + 1];
169 target[index * 4 + 2] = source[index * 4 + 0];
170 target[index * 4 + 3] = source[index * 4 + 3];
171 }
172 view_->pixels_dirty = TRUE;
173 }
174
175 void OnAfterCreated(CefRefPtr<CefBrowser> browser) override {
176 browser_ = browser;
177 browser_->GetHost()->WasHidden(!view_->active);
178 }
179
180 void OnBeforeClose(CefRefPtr<CefBrowser> browser) override {
181 (void)browser;
182 browser_ = nullptr;
183 }
184
185 CefRefPtr<CefBrowser> browser() const { return browser_; }
186
187 private:
188 Canvas_Web_View *view_;
189 CefRefPtr<CefBrowser> browser_;
190
191 IMPLEMENT_REFCOUNTING(CanvasCefClient);
192 DISALLOW_COPY_AND_ASSIGN(CanvasCefClient);
193 };
194
195 struct NativeState {
196 CefRefPtr<CanvasCefClient> clients[CANVAS_MAX_WEB_VIEWS];
197 boolean cef_initialized;
198 };
199
200 CefMainArgs MainArgs(int argc, char **argv) {
201 #if defined(OS_WIN)
202 (void)argc;
203 (void)argv;
204 return CefMainArgs(GetModuleHandle(nullptr));
205 #else
206 return CefMainArgs(argc, argv);
207 #endif
208 }
209
210 #if defined(OS_LINUX)
211 void FindResourcePaths() {
212 if (g_resources_path[0]) return;
213 Dl_info info = {};
214 if (!dladdr(reinterpret_cast<void *>(&cef_initialize), &info) ||
215 !info.dli_fname) {
216 return;
217 }
218
219 char root[PATH_MAX] = {};
220 if (!realpath(info.dli_fname, root)) return;
221 char *release = std::strstr(root, "/Release/libcef.so");
222 if (!release) return;
223 *release = '\0';
224
225 std::snprintf(
226 g_resources_path,
227 sizeof(g_resources_path),
228 "%s/Resources",
229 root);
230 std::snprintf(
231 g_locales_path,
232 sizeof(g_locales_path),
233 "%s/Resources/locales",
234 root);
235 }
236
237 void ConfigureLinuxResourcePaths(CefSettings &settings) {
238 FindResourcePaths();
239 CefString(&settings.resources_dir_path) = g_resources_path;
240 CefString(&settings.locales_dir_path) = g_locales_path;
241 }
242 #else
243 void FindResourcePaths() {}
244 #endif
245
246 int32 ViewIndex(
247 const Canvas_Web_Surface *surface,
248 const Canvas_Web_View *view) {
249 return static_cast<int32>(view - surface->views);
250 }
251
252 Canvas_Web_View *FindView(Canvas_Web_Surface *surface, uint32 entity_id) {
253 for (int32 index = 0; index < CANVAS_MAX_WEB_VIEWS; ++index) {
254 if (surface->views[index].entity_id == entity_id) {
255 return &surface->views[index];
256 }
257 }
258 return nullptr;
259 }
260
261 Canvas_Web_View *FindInactiveView(Canvas_Web_Surface *surface) {
262 for (int32 index = 0; index < CANVAS_MAX_WEB_VIEWS; ++index) {
263 if (!surface->views[index].active) return &surface->views[index];
264 }
265 return nullptr;
266 }
267
268 boolean SceneHasVisibleEntity(
269 const Canvas_Scene *scene,
270 const Canvas_Camera *camera,
271 uint32 entity_id) {
272 const Canvas_Entity *entity = FindSurfaceEntity(scene, entity_id);
273 return entity && IsVisible(camera, ContentBounds(camera, entity));
274 }
275
276 boolean EnsureViewResolution(
277 Canvas_Web_Surface *surface,
278 Canvas_Web_View *view,
279 Rectangle bounds) {
280 int32 width = BucketDimension(
281 bounds.width,
282 kMinTextureWidth,
283 kMaxTextureWidth);
284 int32 height = BucketDimension(
285 bounds.height,
286 kMinTextureHeight,
287 kMaxTextureHeight);
288 if (view->p_pixels &&
289 view->texture_width == width &&
290 view->texture_height == height) {
291 return TRUE;
292 }
293
294 size_t pixel_capacity =
295 static_cast<size_t>(width) * static_cast<size_t>(height) * 4;
296 void *resized_pixels = std::realloc(view->p_pixels, pixel_capacity);
297 if (!resized_pixels) return FALSE;
298 view->p_pixels = static_cast<uint8 *>(resized_pixels);
299 view->pixel_capacity = pixel_capacity;
300 view->texture_width = width;
301 view->texture_height = height;
302 std::memset(view->p_pixels, 255, pixel_capacity);
303 view->pixels_dirty = FALSE;
304
305 if (view->texture_ready) {
306 UnloadTexture(view->texture);
307 view->texture = {};
308 view->texture_ready = FALSE;
309 }
310
311 NativeState *state = static_cast<NativeState *>(surface->p_native);
312 int32 index = ViewIndex(surface, view);
313 CefRefPtr<CanvasCefClient> client = state->clients[index];
314 if (client && client->browser()) {
315 client->browser()->GetHost()->WasResized();
316 }
317 return TRUE;
318 }
319
320 boolean CreateBrowser(
321 Canvas_Web_Surface *surface,
322 Canvas_Web_View *view,
323 const char *url,
324 Rectangle bounds) {
325 NativeState *state = static_cast<NativeState *>(surface->p_native);
326 int32 index = ViewIndex(surface, view);
327 if (!EnsureViewResolution(surface, view, bounds)) return FALSE;
328
329 state->clients[index] = new CanvasCefClient(view);
330 CefWindowInfo window_info;
331 window_info.SetAsWindowless(kNullWindowHandle);
332 CefBrowserSettings browser_settings;
333 browser_settings.windowless_frame_rate = 30;
334 browser_settings.background_color = CefColorSetARGB(0, 255, 255, 255);
335 const char *initial_url =
336 view->entity_type == CANVAS_ENTITY_IMAGE ? "about:blank" : url;
337 if (!CefBrowserHost::CreateBrowser(
338 window_info,
339 state->clients[index],
340 initial_url,
341 browser_settings,
342 nullptr,
343 nullptr)) {
344 state->clients[index] = nullptr;
345 return FALSE;
346 }
347 view->browser_created = TRUE;
348 return TRUE;
349 }
350
351 void PercentEncode(
352 const char *source,
353 char *target,
354 size_t target_size) {
355 static const char hex[] = "0123456789ABCDEF";
356 size_t output = 0;
357 for (size_t index = 0; source[index] && output + 4 < target_size; ++index) {
358 uint8 value = static_cast<uint8>(source[index]);
359 boolean unreserved =
360 (value >= 'a' && value <= 'z') ||
361 (value >= 'A' && value <= 'Z') ||
362 (value >= '0' && value <= '9') ||
363 value == '-' || value == '_' || value == '.' || value == '~';
364 if (unreserved) {
365 target[output++] = static_cast<char>(value);
366 } else {
367 target[output++] = '%';
368 target[output++] = hex[value >> 4];
369 target[output++] = hex[value & 15];
370 }
371 }
372 target[output] = '\0';
373 }
374
375 void LoadImageSource(
376 CefRefPtr<CefBrowser> browser,
377 const char *source) {
378 char resolved[512] = {};
379 boolean local_file =
380 !std::strstr(source, "://") && std::strncmp(source, "data:", 5) != 0;
381 if (!local_file) {
382 std::snprintf(resolved, sizeof(resolved), "%s", source);
383 } else {
384 std::error_code error;
385 std::filesystem::path absolute = std::filesystem::absolute(source, error);
386 if (error) {
387 std::snprintf(resolved, sizeof(resolved), "%s", source);
388 } else {
389 std::snprintf(
390 resolved,
391 sizeof(resolved),
392 "file://%s",
393 absolute.generic_string().c_str());
394 }
395 }
396 std::error_code viewer_error;
397 std::filesystem::path viewer_path = std::filesystem::absolute(
398 "infinite_canvas/assets/image-view.html",
399 viewer_error);
400 if (viewer_error) {
401 browser->GetMainFrame()->LoadURL(resolved);
402 return;
403 }
404 char encoded_source[2048] = {};
405 PercentEncode(resolved, encoded_source, sizeof(encoded_source));
406 char viewer_url[3072] = {};
407 std::snprintf(
408 viewer_url,
409 sizeof(viewer_url),
410 "file://%s?src=%s",
411 viewer_path.generic_string().c_str(),
412 encoded_source);
413 browser->GetMainFrame()->LoadURL(viewer_url);
414 }
415
416 void BindView(
417 Canvas_Web_Surface *surface,
418 Canvas_Web_View *view,
419 const Canvas_Entity *entity,
420 Rectangle bounds) {
421 NativeState *state = static_cast<NativeState *>(surface->p_native);
422 int32 index = ViewIndex(surface, view);
423 boolean type_changed = view->entity_type != entity->type;
424 view->active = TRUE;
425 view->entity_id = entity->id;
426 view->entity_type = entity->type;
427 if (!view->browser_created) {
428 if (!CreateBrowser(surface, view, entity->text, bounds)) {
429 view->active = FALSE;
430 view->entity_id = 0;
431 return;
432 }
433 if (entity->type == CANVAS_ENTITY_WEB_CONTENT) {
434 std::snprintf(view->url, sizeof(view->url), "%s", entity->text);
435 }
436 return;
437 }
438 EnsureViewResolution(surface, view, bounds);
439
440 if (type_changed || std::strcmp(view->url, entity->text) != 0) {
441 CefRefPtr<CanvasCefClient> client = state->clients[index];
442 if (client && client->browser()) {
443 if (entity->type == CANVAS_ENTITY_IMAGE) {
444 LoadImageSource(client->browser(), entity->text);
445 } else {
446 client->browser()->GetMainFrame()->LoadURL(entity->text);
447 }
448 std::snprintf(view->url, sizeof(view->url), "%s", entity->text);
449 }
450 }
451 }
452
453 void ReconcileViews(
454 Canvas_Web_Surface *surface,
455 const Canvas_Camera *camera,
456 const Canvas_Scene *scene) {
457 for (int32 index = 0; index < CANVAS_MAX_WEB_VIEWS; ++index) {
458 Canvas_Web_View *view = &surface->views[index];
459 view->active = view->entity_id != 0 &&
460 SceneHasVisibleEntity(scene, camera, view->entity_id);
461 }
462
463 for (size_t index = 0; index < Dowa_Array_Length(scene->p_entities); ++index) {
464 const Canvas_Entity *entity = &scene->p_entities[index];
465 if ((entity->type != CANVAS_ENTITY_WEB_CONTENT &&
466 entity->type != CANVAS_ENTITY_IMAGE) ||
467 !IsVisible(camera, ContentBounds(camera, entity))) {
468 continue;
469 }
470 Rectangle bounds = ContentBounds(camera, entity);
471 Canvas_Web_View *view = FindView(surface, entity->id);
472 if (view) {
473 BindView(surface, view, entity, bounds);
474 continue;
475 }
476 view = FindInactiveView(surface);
477 if (!view) break;
478 BindView(surface, view, entity, bounds);
479 }
480
481 if (surface->focused_entity_id != 0) {
482 Canvas_Web_View *focused = FindView(surface, surface->focused_entity_id);
483 if (!focused || !focused->active) surface->focused_entity_id = 0;
484 }
485
486 NativeState *state = static_cast<NativeState *>(surface->p_native);
487 for (int32 index = 0; index < CANVAS_MAX_WEB_VIEWS; ++index) {
488 CefRefPtr<CanvasCefClient> client = state->clients[index];
489 if (client && client->browser()) {
490 Canvas_Web_View *view = &surface->views[index];
491 CefRefPtr<CefBrowserHost> host = client->browser()->GetHost();
492 host->WasHidden(!view->active);
493 if (view->active) {
494 const Canvas_Entity *entity = FindSurfaceEntity(scene, view->entity_id);
495 if (entity) {
496 host->SetWindowlessFrameRate(
497 FrameRateForView(surface, view, ContentBounds(camera, entity)));
498 }
499 }
500 }
501 }
502 }
503
504 } // namespace
505
506 extern "C" int32 Canvas_Web_Surface_Execute_Subprocess(
507 int argc,
508 char **p_argv) {
509 g_argc = argc;
510 FindResourcePaths();
511 int32 cef_argc = 0;
512 for (; cef_argc < argc && cef_argc < 120; ++cef_argc) {
513 g_cef_argv[cef_argc] = p_argv[cef_argc];
514 }
515 #if defined(OS_LINUX)
516 if (g_resources_path[0]) {
517 std::snprintf(
518 g_resources_switch,
519 sizeof(g_resources_switch),
520 "--resources-dir-path=%s",
521 g_resources_path);
522 std::snprintf(
523 g_locales_switch,
524 sizeof(g_locales_switch),
525 "--locales-dir-path=%s",
526 g_locales_path);
527 g_cef_argv[cef_argc++] = g_resources_switch;
528 g_cef_argv[cef_argc++] = g_locales_switch;
529 }
530 #endif
531 g_cef_argv[cef_argc++] = const_cast<char *>("--no-sandbox");
532 g_cef_argv[cef_argc++] = const_cast<char *>("--disable-gpu");
533 g_cef_argv[cef_argc++] = const_cast<char *>("--disable-gpu-compositing");
534 g_argc = cef_argc;
535 g_argv = g_cef_argv;
536 CefMainArgs args = MainArgs(g_argc, g_argv);
537 CefRefPtr<CanvasCefApp> app = new CanvasCefApp();
538 return CefExecuteProcess(args, app, nullptr);
539 }
540
541 extern "C" boolean Canvas_Web_Surface_Init(
542 Canvas_Web_Surface *p_surface,
543 Dowa_Arena *p_arena) {
544 std::memset(p_surface, 0, sizeof(*p_surface));
545 p_surface->p_arena = p_arena;
546 NativeState *state = static_cast<NativeState *>(
547 Dowa_Arena_Allocate(p_arena, sizeof(NativeState)));
548 if (!state) return FALSE;
549
550 new (state) NativeState();
551 p_surface->p_native = state;
552
553 CefSettings settings;
554 settings.windowless_rendering_enabled = true;
555 settings.no_sandbox = true;
556 settings.multi_threaded_message_loop = false;
557 settings.external_message_pump = false;
558 settings.log_severity = LOGSEVERITY_DISABLE;
559 const char *cache_path = std::getenv("INFINITE_CANVAS_CEF_CACHE");
560 if (cache_path) {
561 CefString(&settings.root_cache_path) = cache_path;
562 CefString(&settings.cache_path) = cache_path;
563 }
564 #if defined(OS_LINUX)
565 ConfigureLinuxResourcePaths(settings);
566 #endif
567
568 CefMainArgs args = MainArgs(g_argc, g_argv);
569 CefRefPtr<CanvasCefApp> app = new CanvasCefApp();
570 if (!CefInitialize(args, settings, app, nullptr)) {
571 state->~NativeState();
572 p_surface->p_native = nullptr;
573 return FALSE;
574 }
575 state->cef_initialized = TRUE;
576 p_surface->initialized = TRUE;
577 return TRUE;
578 }
579
580 extern "C" boolean Canvas_Web_Surface_Update_Input(
581 Canvas_Web_Surface *p_surface,
582 const Canvas_Camera *p_camera,
583 const Canvas_Scene *p_scene) {
584 if (!p_surface->initialized || !p_surface->p_native) return FALSE;
585 NativeState *state = static_cast<NativeState *>(p_surface->p_native);
586 Vector2 mouse = GetMousePosition();
587 Canvas_Web_View *pointer_view = nullptr;
588 const Canvas_Entity *pointer_entity = nullptr;
589 Rectangle pointer_bounds = {};
590
591 for (int32 index = CANVAS_MAX_WEB_VIEWS - 1; index >= 0; --index) {
592 Canvas_Web_View *view = &p_surface->views[index];
593 if (!view->active) continue;
594 const Canvas_Entity *entity = FindSurfaceEntity(p_scene, view->entity_id);
595 if (!entity || entity->type != CANVAS_ENTITY_WEB_CONTENT) continue;
596 Rectangle bounds = ContentBounds(p_camera, entity);
597 if (CheckCollisionPointRec(mouse, bounds)) {
598 pointer_view = view;
599 pointer_entity = entity;
600 pointer_bounds = bounds;
601 break;
602 }
603 }
604
605 if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) {
606 p_surface->focused_entity_id = pointer_view ? pointer_view->entity_id : 0;
607 for (int32 index = 0; index < CANVAS_MAX_WEB_VIEWS; ++index) {
608 CefRefPtr<CanvasCefClient> client = state->clients[index];
609 if (client && client->browser()) {
610 client->browser()->GetHost()->SetFocus(
611 pointer_view == &p_surface->views[index]);
612 }
613 }
614 }
615
616 Canvas_Web_View *focused_view =
617 FindView(p_surface, p_surface->focused_entity_id);
618 Canvas_Web_View *input_view = pointer_view ? pointer_view : focused_view;
619 if (!input_view || !input_view->active) return FALSE;
620 int32 view_index = ViewIndex(p_surface, input_view);
621 CefRefPtr<CanvasCefClient> client = state->clients[view_index];
622 CefRefPtr<CefBrowser> browser = client ? client->browser() : nullptr;
623 const Canvas_Entity *input_entity = pointer_entity ?
624 pointer_entity :
625 FindSurfaceEntity(p_scene, input_view->entity_id);
626 if (!browser || !input_entity) return pointer_view != nullptr;
627
628 Rectangle bounds = pointer_view ? pointer_bounds : ContentBounds(p_camera, input_entity);
629 boolean inside = pointer_view == input_view;
630 float scale_x = static_cast<float>(input_view->texture_width) /
631 std::max(bounds.width, 1.0f);
632 float scale_y = static_cast<float>(input_view->texture_height) /
633 std::max(bounds.height, 1.0f);
634 CefMouseEvent event;
635 event.x = static_cast<int>((mouse.x - bounds.x) * scale_x);
636 event.y = static_cast<int>((mouse.y - bounds.y) * scale_y);
637 event.modifiers = 0;
638 CefRefPtr<CefBrowserHost> host = browser->GetHost();
639 host->SendMouseMoveEvent(event, !inside);
640 if (inside && IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) {
641 host->SendMouseClickEvent(event, MBT_LEFT, false, 1);
642 }
643 if (IsMouseButtonReleased(MOUSE_BUTTON_LEFT) &&
644 p_surface->focused_entity_id == input_view->entity_id) {
645 host->SendMouseClickEvent(event, MBT_LEFT, true, 1);
646 }
647 if (inside) {
648 float wheel = GetMouseWheelMove();
649 if (wheel != 0.0f) {
650 host->SendMouseWheelEvent(event, 0, static_cast<int>(wheel * 80.0f));
651 }
652 }
653
654 if (p_surface->focused_entity_id == input_view->entity_id) {
655 int32 codepoint = GetCharPressed();
656 while (codepoint > 0) {
657 CefKeyEvent key;
658 key.type = KEYEVENT_CHAR;
659 key.windows_key_code = codepoint;
660 key.native_key_code = codepoint;
661 key.character = static_cast<char16_t>(codepoint);
662 key.unmodified_character = static_cast<char16_t>(codepoint);
663 host->SendKeyEvent(key);
664 codepoint = GetCharPressed();
665 }
666
667 struct {
668 int32 raylib_key;
669 int32 browser_key;
670 } keys[] = {
671 {KEY_BACKSPACE, 0x08},
672 {KEY_ENTER, 0x0D},
673 {KEY_LEFT, 0x25},
674 {KEY_UP, 0x26},
675 {KEY_RIGHT, 0x27},
676 {KEY_DOWN, 0x28},
677 {KEY_DELETE, 0x2E},
678 };
679 for (const auto &key_mapping : keys) {
680 if (!IsKeyPressed(key_mapping.raylib_key)) continue;
681 CefKeyEvent key;
682 key.windows_key_code = key_mapping.browser_key;
683 key.native_key_code = key_mapping.browser_key;
684 key.type = KEYEVENT_RAWKEYDOWN;
685 host->SendKeyEvent(key);
686 key.type = KEYEVENT_KEYUP;
687 host->SendKeyEvent(key);
688 }
689 }
690 return inside ||
691 p_surface->focused_entity_id == input_view->entity_id;
692 }
693
694 extern "C" void Canvas_Web_Surface_Sync(
695 Canvas_Web_Surface *p_surface,
696 const Canvas_Camera *p_camera,
697 const Canvas_Scene *p_scene) {
698 if (!p_surface->initialized) return;
699 CefDoMessageLoopWork();
700 ReconcileViews(p_surface, p_camera, p_scene);
701
702 for (int32 index = 0; index < CANVAS_MAX_WEB_VIEWS; ++index) {
703 Canvas_Web_View *view = &p_surface->views[index];
704 if (!view->browser_created) continue;
705 if (!view->texture_ready && IsWindowReady()) {
706 Image image = {
707 .data = view->p_pixels,
708 .width = view->texture_width,
709 .height = view->texture_height,
710 .mipmaps = 1,
711 .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8,
712 };
713 view->texture = LoadTextureFromImage(image);
714 view->texture_ready = view->texture.id != 0 ? TRUE : FALSE;
715 if (view->texture_ready) {
716 SetTextureFilter(view->texture, TEXTURE_FILTER_BILINEAR);
717 }
718 }
719 if (view->texture_ready && view->pixels_dirty) {
720 UpdateTexture(view->texture, view->p_pixels);
721 view->pixels_dirty = FALSE;
722 }
723 }
724 }
725
726 extern "C" void Canvas_Web_Surface_Draw(
727 const Canvas_Web_Surface *p_surface,
728 const Canvas_Camera *p_camera,
729 const Canvas_Scene *p_scene) {
730 for (int32 index = 0; index < CANVAS_MAX_WEB_VIEWS; ++index) {
731 const Canvas_Web_View *view = &p_surface->views[index];
732 if (!view->active || !view->texture_ready) continue;
733 const Canvas_Entity *entity = FindSurfaceEntity(p_scene, view->entity_id);
734 if (!entity) continue;
735 Rectangle bounds = ContentBounds(p_camera, entity);
736 float left = std::max(bounds.x, 0.0f);
737 float top = std::max(bounds.y, 0.0f);
738 float right = std::min(
739 bounds.x + bounds.width,
740 static_cast<float>(p_camera->viewport_width));
741 float bottom = std::min(
742 bounds.y + bounds.height,
743 static_cast<float>(p_camera->viewport_height));
744 if (right <= left || bottom <= top) continue;
745
746 BeginScissorMode(
747 static_cast<int>(left),
748 static_cast<int>(top),
749 static_cast<int>(right - left),
750 static_cast<int>(bottom - top));
751 DrawTexturePro(
752 view->texture,
753 Rectangle{
754 0.0f,
755 0.0f,
756 static_cast<float>(view->texture_width),
757 static_cast<float>(view->texture_height),
758 },
759 bounds,
760 Vector2{0.0f, 0.0f},
761 0.0f,
762 WHITE);
763 EndScissorMode();
764 }
765 }
766
767 extern "C" boolean Canvas_Web_Surface_Is_Focused(
768 const Canvas_Web_Surface *p_surface) {
769 return p_surface->focused_entity_id != 0;
770 }
771
772 extern "C" void Canvas_Web_Surface_Shutdown(Canvas_Web_Surface *p_surface) {
773 if (!p_surface->p_native) return;
774 NativeState *state = static_cast<NativeState *>(p_surface->p_native);
775 for (int32 index = 0; index < CANVAS_MAX_WEB_VIEWS; ++index) {
776 if (state->clients[index] && state->clients[index]->browser()) {
777 state->clients[index]->browser()->GetHost()->CloseBrowser(true);
778 }
779 state->clients[index] = nullptr;
780 if (p_surface->views[index].texture_ready) {
781 UnloadTexture(p_surface->views[index].texture);
782 }
783 Dowa_Free(p_surface->views[index].p_pixels);
784 }
785 CefDoMessageLoopWork();
786 if (state->cef_initialized) CefShutdown();
787 state->~NativeState();
788 p_surface->p_native = nullptr;
789 p_surface->initialized = FALSE;
790 }