view 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
line wrap: on
line source

extern "C" {
#include "infinite_canvas/web_surface.h"
}

#include "include/cef_app.h"
#include "include/cef_browser.h"
#include "include/cef_client.h"
#include "include/cef_display_handler.h"
#include "include/cef_render_handler.h"
#include "include/capi/cef_app_capi.h"

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <new>

#if defined(OS_LINUX)
#include <dlfcn.h>
#include <limits.h>
#endif

namespace {

constexpr int32 kResolutionStep = 64;
constexpr int32 kMinTextureWidth = 160;
constexpr int32 kMinTextureHeight = 120;
constexpr int32 kMaxTextureWidth = 1280;
constexpr int32 kMaxTextureHeight = 800;
int g_argc = 0;
char **g_argv = nullptr;
char *g_cef_argv[128] = {};
#if defined(OS_LINUX)
char g_resources_path[PATH_MAX + 32] = {};
char g_locales_path[PATH_MAX + 32] = {};
char g_resources_switch[PATH_MAX + 64] = {};
char g_locales_switch[PATH_MAX + 64] = {};
#endif

void FindResourcePaths();

void ApplyColorScheme(CefRefPtr<CefBrowser> browser, boolean dark_mode) {
  if (!browser) return;
  CefRefPtr<CefDictionaryValue> params = CefDictionaryValue::Create();
  CefRefPtr<CefListValue> features = CefListValue::Create();
  CefRefPtr<CefDictionaryValue> feature = CefDictionaryValue::Create();
  feature->SetString("name", "prefers-color-scheme");
  feature->SetString("value", dark_mode ? "dark" : "light");
  features->SetDictionary(0, feature);
  params->SetList("features", features);
  browser->GetHost()->ExecuteDevToolsMethod(
      0,
      "Emulation.setEmulatedMedia",
      params);

  CefRefPtr<CefFrame> frame = browser->GetMainFrame();
  const char *script = dark_mode
      ? "document.documentElement.style.colorScheme='dark'"
      : "document.documentElement.style.colorScheme='light'";
  frame->ExecuteJavaScript(script, frame->GetURL(), 0);
}

const Canvas_Entity *FindSurfaceEntity(
    const Canvas_Scene *scene,
    uint32 entity_id) {
  for (size_t index = 0; index < Dowa_Array_Length(scene->p_entities); ++index) {
    const Canvas_Entity *entity = &scene->p_entities[index];
    if ((entity->type == CANVAS_ENTITY_WEB_CONTENT ||
         entity->type == CANVAS_ENTITY_IMAGE) &&
        entity->id == entity_id) {
      return entity;
    }
  }
  return nullptr;
}

Rectangle ContentBounds(
    const Canvas_Camera *camera,
    const Canvas_Scene *scene,
    const Canvas_Entity *entity) {
  (void)scene;
  float frame_inset = CANVAS_WEB_FRAME_INSET / camera->zoom;
  float amount = std::clamp(entity->web_chrome_amount, 0.0f, 1.0f);
  float eased = amount * amount * (3.0f - 2.0f * amount);
  float toolbar_height = CANVAS_WEB_TOOLBAR_HEIGHT * eased / camera->zoom;
  float resize_handle = CANVAS_WEB_RESIZE_HANDLE / camera->zoom;
  Vector2 position = Canvas_Camera_World_To_Screen(
      camera,
      Vector2{
          entity->position.x + frame_inset,
          entity->position.y + frame_inset + toolbar_height});
  return Rectangle{
      position.x,
      position.y,
      (entity->size.x - frame_inset - resize_handle) * camera->zoom,
      (entity->size.y - frame_inset - resize_handle - toolbar_height) *
          camera->zoom,
  };
}

boolean IsVisible(const Canvas_Camera *camera, Rectangle bounds) {
  return camera->zoom >= 0.30f &&
      bounds.x + bounds.width > 0.0f &&
      bounds.y + bounds.height > 0.0f &&
      bounds.x < static_cast<float>(camera->viewport_width) &&
      bounds.y < static_cast<float>(camera->viewport_height);
}

float EntityOpacity(const Canvas_Entity *entity) {
  float amount = std::clamp(entity->visibility_amount, 0.0f, 1.0f);
  return amount * amount * (3.0f - 2.0f * amount);
}

int32 FrameRateForView(
    const Canvas_Web_Surface *surface,
    const Canvas_Web_View *view,
    Rectangle bounds) {
  if (surface->focused_entity_id == view->entity_id) return 30;
  float area = std::max(bounds.width, 0.0f) * std::max(bounds.height, 0.0f);
  return area <= 220000.0f ? 12 : 20;
}

int32 BucketDimension(float value, int32 minimum, int32 maximum) {
  int32 rounded = static_cast<int32>(
      std::ceil(std::max(value, 1.0f) / kResolutionStep)) * kResolutionStep;
  return std::clamp(rounded, minimum, maximum);
}

class CanvasCefApp : public CefApp,
                     public CefBrowserProcessHandler {
 public:
  CanvasCefApp() = default;

  CefRefPtr<CefBrowserProcessHandler> GetBrowserProcessHandler() override {
    return this;
  }

  void OnBeforeCommandLineProcessing(
      const CefString &process_type,
      CefRefPtr<CefCommandLine> command_line) override {
    (void)process_type;
    command_line->AppendSwitch("no-sandbox");
    command_line->AppendSwitch("disable-gpu");
    command_line->AppendSwitch("disable-gpu-compositing");
    command_line->AppendSwitch("allow-file-access-from-files");
    command_line->AppendSwitch("allow-universal-access-from-files");
    command_line->AppendSwitchWithValue("log-severity", "disable");
#if defined(OS_LINUX)
    if (g_resources_path[0]) {
      command_line->AppendSwitchWithValue(
          "resources-dir-path",
          g_resources_path);
      command_line->AppendSwitchWithValue(
          "locales-dir-path",
          g_locales_path);
    }
#endif
  }

 private:
  IMPLEMENT_REFCOUNTING(CanvasCefApp);
  DISALLOW_COPY_AND_ASSIGN(CanvasCefApp);
};

class CanvasCefClient : public CefClient,
                        public CefRenderHandler,
                        public CefDisplayHandler,
                        public CefLifeSpanHandler {
 public:
  explicit CanvasCefClient(Canvas_Web_View *view) : view_(view) {}

  CefRefPtr<CefRenderHandler> GetRenderHandler() override { return this; }
  CefRefPtr<CefDisplayHandler> GetDisplayHandler() override { return this; }
  CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler() override { return this; }

  void OnAddressChange(
      CefRefPtr<CefBrowser> browser,
      CefRefPtr<CefFrame> frame,
      const CefString &url) override {
    if (!frame->IsMain()) return;
    std::snprintf(
        view_->url,
        sizeof(view_->url),
        "%s",
        url.ToString().c_str());
    view_->can_go_back = browser->CanGoBack() ? TRUE : FALSE;
    view_->can_go_forward = browser->CanGoForward() ? TRUE : FALSE;
  }

  void GetViewRect(CefRefPtr<CefBrowser> browser, CefRect &rect) override {
    (void)browser;
    rect = CefRect(0, 0, view_->texture_width, view_->texture_height);
  }

  void OnPaint(
      CefRefPtr<CefBrowser> browser,
      PaintElementType type,
      const RectList &dirty_rects,
      const void *buffer,
      int width,
      int height) override {
    (void)browser;
    (void)dirty_rects;
    if (type != PET_VIEW || !view_->p_pixels ||
        width != view_->texture_width ||
        height != view_->texture_height) {
      return;
    }

    const uint8 *source = static_cast<const uint8 *>(buffer);
    uint8 *target = view_->p_pixels;
    const size_t pixel_count = static_cast<size_t>(width) * height;
    for (size_t index = 0; index < pixel_count; ++index) {
      target[index * 4 + 0] = source[index * 4 + 2];
      target[index * 4 + 1] = source[index * 4 + 1];
      target[index * 4 + 2] = source[index * 4 + 0];
      target[index * 4 + 3] = source[index * 4 + 3];
    }
    view_->pixels_dirty = TRUE;
  }

  void OnAfterCreated(CefRefPtr<CefBrowser> browser) override {
    browser_ = browser;
    browser_->GetHost()->WasHidden(!view_->active);
  }

  void OnBeforeClose(CefRefPtr<CefBrowser> browser) override {
    (void)browser;
    browser_ = nullptr;
  }

  CefRefPtr<CefBrowser> browser() const { return browser_; }

 private:
  Canvas_Web_View *view_;
  CefRefPtr<CefBrowser> browser_;

  IMPLEMENT_REFCOUNTING(CanvasCefClient);
  DISALLOW_COPY_AND_ASSIGN(CanvasCefClient);
};

struct NativeState {
  CefRefPtr<CanvasCefClient> clients[CANVAS_MAX_WEB_VIEWS];
  boolean cef_initialized;
};

CefMainArgs MainArgs(int argc, char **argv) {
#if defined(OS_WIN)
  (void)argc;
  (void)argv;
  return CefMainArgs(GetModuleHandle(nullptr));
#else
  return CefMainArgs(argc, argv);
#endif
}

#if defined(OS_LINUX)
void FindResourcePaths() {
  if (g_resources_path[0]) return;
  Dl_info info = {};
  if (!dladdr(reinterpret_cast<void *>(&cef_initialize), &info) ||
      !info.dli_fname) {
    return;
  }

  char root[PATH_MAX] = {};
  if (!realpath(info.dli_fname, root)) return;
  char *release = std::strstr(root, "/Release/libcef.so");
  if (!release) return;
  *release = '\0';

  std::snprintf(
      g_resources_path,
      sizeof(g_resources_path),
      "%s/Resources",
      root);
  std::snprintf(
      g_locales_path,
      sizeof(g_locales_path),
      "%s/Resources/locales",
      root);
}

void ConfigureLinuxResourcePaths(CefSettings &settings) {
  FindResourcePaths();
  CefString(&settings.resources_dir_path) = g_resources_path;
  CefString(&settings.locales_dir_path) = g_locales_path;
}
#else
void FindResourcePaths() {}
#endif

int32 ViewIndex(
    const Canvas_Web_Surface *surface,
    const Canvas_Web_View *view) {
  return static_cast<int32>(view - surface->views);
}

Canvas_Web_View *FindView(Canvas_Web_Surface *surface, uint32 entity_id) {
  for (int32 index = 0; index < CANVAS_MAX_WEB_VIEWS; ++index) {
    if (surface->views[index].entity_id == entity_id) {
      return &surface->views[index];
    }
  }
  return nullptr;
}

const Canvas_Web_View *FindView(
    const Canvas_Web_Surface *surface,
    uint32 entity_id) {
  for (int32 index = 0; index < CANVAS_MAX_WEB_VIEWS; ++index) {
    if (surface->views[index].entity_id == entity_id) {
      return &surface->views[index];
    }
  }
  return nullptr;
}

Canvas_Web_View *FindInactiveView(Canvas_Web_Surface *surface) {
  for (int32 index = 0; index < CANVAS_MAX_WEB_VIEWS; ++index) {
    if (!surface->views[index].active) return &surface->views[index];
  }
  return nullptr;
}

boolean SceneHasVisibleEntity(
    const Canvas_Scene *scene,
    const Canvas_Camera *camera,
    uint32 entity_id) {
  const Canvas_Entity *entity = FindSurfaceEntity(scene, entity_id);
  return entity && IsVisible(camera, ContentBounds(camera, scene, entity));
}

boolean EnsureViewResolution(
    Canvas_Web_Surface *surface,
    Canvas_Web_View *view,
    Rectangle bounds) {
  int32 width = BucketDimension(
      bounds.width,
      kMinTextureWidth,
      kMaxTextureWidth);
  int32 height = BucketDimension(
      bounds.height,
      kMinTextureHeight,
      kMaxTextureHeight);
  if (view->p_pixels &&
      view->texture_width == width &&
      view->texture_height == height) {
    return TRUE;
  }

  size_t pixel_capacity =
      static_cast<size_t>(width) * static_cast<size_t>(height) * 4;
  void *resized_pixels = std::realloc(view->p_pixels, pixel_capacity);
  if (!resized_pixels) return FALSE;
  view->p_pixels = static_cast<uint8 *>(resized_pixels);
  view->pixel_capacity = pixel_capacity;
  view->texture_width = width;
  view->texture_height = height;
  std::memset(view->p_pixels, 255, pixel_capacity);
  view->pixels_dirty = FALSE;

  if (view->texture_ready) {
    UnloadTexture(view->texture);
    view->texture = {};
    view->texture_ready = FALSE;
  }

  NativeState *state = static_cast<NativeState *>(surface->p_native);
  int32 index = ViewIndex(surface, view);
  CefRefPtr<CanvasCefClient> client = state->clients[index];
  if (client && client->browser()) {
    client->browser()->GetHost()->WasResized();
  }
  return TRUE;
}

boolean CreateBrowser(
    Canvas_Web_Surface *surface,
    Canvas_Web_View *view,
    const char *url,
    Rectangle bounds) {
  NativeState *state = static_cast<NativeState *>(surface->p_native);
  int32 index = ViewIndex(surface, view);
  if (!EnsureViewResolution(surface, view, bounds)) return FALSE;

  state->clients[index] = new CanvasCefClient(view);
  CefWindowInfo window_info;
  window_info.SetAsWindowless(kNullWindowHandle);
  CefBrowserSettings browser_settings;
  browser_settings.windowless_frame_rate = 30;
  browser_settings.background_color = surface->dark_mode
      ? CefColorSetARGB(255, 15, 15, 17)
      : CefColorSetARGB(255, 255, 255, 255);
  const char *initial_url =
      view->entity_type == CANVAS_ENTITY_IMAGE ? "about:blank" : url;
  if (!CefBrowserHost::CreateBrowser(
          window_info,
          state->clients[index],
          initial_url,
          browser_settings,
          nullptr,
          nullptr)) {
    state->clients[index] = nullptr;
    return FALSE;
  }
  view->browser_created = TRUE;
  return TRUE;
}

void PercentEncode(
    const char *source,
    char *target,
    size_t target_size) {
  static const char hex[] = "0123456789ABCDEF";
  size_t output = 0;
  for (size_t index = 0; source[index] && output + 4 < target_size; ++index) {
    uint8 value = static_cast<uint8>(source[index]);
    boolean unreserved =
        (value >= 'a' && value <= 'z') ||
        (value >= 'A' && value <= 'Z') ||
        (value >= '0' && value <= '9') ||
        value == '-' || value == '_' || value == '.' || value == '~';
    if (unreserved) {
      target[output++] = static_cast<char>(value);
    } else {
      target[output++] = '%';
      target[output++] = hex[value >> 4];
      target[output++] = hex[value & 15];
    }
  }
  target[output] = '\0';
}

void LoadImageSource(
    CefRefPtr<CefBrowser> browser,
    const char *source) {
  char resolved[512] = {};
  boolean local_file =
      !std::strstr(source, "://") && std::strncmp(source, "data:", 5) != 0;
  if (!local_file) {
    std::snprintf(resolved, sizeof(resolved), "%s", source);
  } else {
    std::error_code error;
    std::filesystem::path absolute = std::filesystem::absolute(source, error);
    if (error) {
      std::snprintf(resolved, sizeof(resolved), "%s", source);
    } else {
      std::snprintf(
          resolved,
          sizeof(resolved),
          "file://%s",
          absolute.generic_string().c_str());
    }
  }
  std::error_code viewer_error;
  std::filesystem::path viewer_path = std::filesystem::absolute(
      "infinite_canvas/assets/image-view.html",
      viewer_error);
  if (viewer_error) {
    browser->GetMainFrame()->LoadURL(resolved);
    return;
  }
  char encoded_source[2048] = {};
  PercentEncode(resolved, encoded_source, sizeof(encoded_source));
  char viewer_url[3072] = {};
  std::snprintf(
      viewer_url,
      sizeof(viewer_url),
      "file://%s?src=%s",
      viewer_path.generic_string().c_str(),
      encoded_source);
  browser->GetMainFrame()->LoadURL(viewer_url);
}

void BindView(
    Canvas_Web_Surface *surface,
    Canvas_Web_View *view,
    const Canvas_Entity *entity,
    Rectangle bounds) {
  NativeState *state = static_cast<NativeState *>(surface->p_native);
  int32 index = ViewIndex(surface, view);
  boolean type_changed = view->entity_type != entity->type;
  view->active = TRUE;
  view->entity_id = entity->id;
  view->entity_type = entity->type;
  if (!view->browser_created) {
    if (!CreateBrowser(surface, view, entity->text, bounds)) {
      view->active = FALSE;
      view->entity_id = 0;
      return;
    }
    if (entity->type == CANVAS_ENTITY_WEB_CONTENT) {
      std::snprintf(
          view->source_url,
          sizeof(view->source_url),
          "%s",
          entity->text);
      std::snprintf(view->url, sizeof(view->url), "%s", entity->text);
    }
    return;
  }
  EnsureViewResolution(surface, view, bounds);

  if (type_changed || std::strcmp(view->source_url, entity->text) != 0) {
    CefRefPtr<CanvasCefClient> client = state->clients[index];
    if (client && client->browser()) {
      if (entity->type == CANVAS_ENTITY_IMAGE) {
        LoadImageSource(client->browser(), entity->text);
      } else {
        client->browser()->GetMainFrame()->LoadURL(entity->text);
      }
      std::snprintf(
          view->source_url,
          sizeof(view->source_url),
          "%s",
          entity->text);
      std::snprintf(view->url, sizeof(view->url), "%s", entity->text);
    }
  }
}

void ReconcileViews(
    Canvas_Web_Surface *surface,
    const Canvas_Camera *camera,
    const Canvas_Scene *scene) {
  for (int32 index = 0; index < CANVAS_MAX_WEB_VIEWS; ++index) {
    Canvas_Web_View *view = &surface->views[index];
    view->active = view->entity_id != 0 &&
        SceneHasVisibleEntity(scene, camera, view->entity_id);
  }

  for (size_t index = 0; index < Dowa_Array_Length(scene->p_entities); ++index) {
    const Canvas_Entity *entity = &scene->p_entities[index];
    if ((entity->type != CANVAS_ENTITY_WEB_CONTENT &&
         entity->type != CANVAS_ENTITY_IMAGE) ||
        !IsVisible(camera, ContentBounds(camera, scene, entity))) {
      continue;
    }
    Rectangle bounds = ContentBounds(camera, scene, entity);
    Canvas_Web_View *view = FindView(surface, entity->id);
    if (view) {
      BindView(surface, view, entity, bounds);
      continue;
    }
    view = FindInactiveView(surface);
    if (!view) break;
    BindView(surface, view, entity, bounds);
  }

  if (surface->focused_entity_id != 0) {
    Canvas_Web_View *focused = FindView(surface, surface->focused_entity_id);
    if (!focused || !focused->active) surface->focused_entity_id = 0;
  }

  NativeState *state = static_cast<NativeState *>(surface->p_native);
  for (int32 index = 0; index < CANVAS_MAX_WEB_VIEWS; ++index) {
    CefRefPtr<CanvasCefClient> client = state->clients[index];
    if (client && client->browser()) {
      Canvas_Web_View *view = &surface->views[index];
      CefRefPtr<CefBrowserHost> host = client->browser()->GetHost();
      host->WasHidden(!view->active);
      if (view->active) {
        const Canvas_Entity *entity = FindSurfaceEntity(scene, view->entity_id);
        if (entity) {
          host->SetWindowlessFrameRate(
              FrameRateForView(
                  surface,
                  view,
                  ContentBounds(camera, scene, entity)));
        }
      }
    }
  }
}

}  // namespace

extern "C" int32 Canvas_Web_Surface_Execute_Subprocess(
    int argc,
    char **p_argv) {
  g_argc = argc;
  FindResourcePaths();
  int32 cef_argc = 0;
  for (; cef_argc < argc && cef_argc < 120; ++cef_argc) {
    g_cef_argv[cef_argc] = p_argv[cef_argc];
  }
#if defined(OS_LINUX)
  if (g_resources_path[0]) {
    std::snprintf(
        g_resources_switch,
        sizeof(g_resources_switch),
        "--resources-dir-path=%s",
        g_resources_path);
    std::snprintf(
        g_locales_switch,
        sizeof(g_locales_switch),
        "--locales-dir-path=%s",
        g_locales_path);
    g_cef_argv[cef_argc++] = g_resources_switch;
    g_cef_argv[cef_argc++] = g_locales_switch;
  }
#endif
  g_cef_argv[cef_argc++] = const_cast<char *>("--no-sandbox");
  g_cef_argv[cef_argc++] = const_cast<char *>("--disable-gpu");
  g_cef_argv[cef_argc++] = const_cast<char *>("--disable-gpu-compositing");
  g_argc = cef_argc;
  g_argv = g_cef_argv;
  CefMainArgs args = MainArgs(g_argc, g_argv);
  CefRefPtr<CanvasCefApp> app = new CanvasCefApp();
  return CefExecuteProcess(args, app, nullptr);
}

extern "C" boolean Canvas_Web_Surface_Init(
    Canvas_Web_Surface *p_surface,
    Dowa_Arena *p_arena,
    boolean dark_mode) {
  std::memset(p_surface, 0, sizeof(*p_surface));
  p_surface->p_arena = p_arena;
  p_surface->dark_mode = dark_mode;
  NativeState *state = static_cast<NativeState *>(
      Dowa_Arena_Allocate(p_arena, sizeof(NativeState)));
  if (!state) return FALSE;

  new (state) NativeState();
  p_surface->p_native = state;

  CefSettings settings;
  settings.windowless_rendering_enabled = true;
  settings.no_sandbox = true;
  settings.multi_threaded_message_loop = false;
  settings.external_message_pump = false;
  settings.log_severity = LOGSEVERITY_DISABLE;
  const char *cache_path = std::getenv("INFINITE_CANVAS_CEF_CACHE");
  if (cache_path) {
    CefString(&settings.root_cache_path) = cache_path;
    CefString(&settings.cache_path) = cache_path;
  }
#if defined(OS_LINUX)
  ConfigureLinuxResourcePaths(settings);
#endif

  CefMainArgs args = MainArgs(g_argc, g_argv);
  CefRefPtr<CanvasCefApp> app = new CanvasCefApp();
  if (!CefInitialize(args, settings, app, nullptr)) {
    state->~NativeState();
    p_surface->p_native = nullptr;
    return FALSE;
  }
  state->cef_initialized = TRUE;
  p_surface->initialized = TRUE;
  return TRUE;
}

extern "C" void Canvas_Web_Surface_Set_Dark_Mode(
    Canvas_Web_Surface *p_surface,
    boolean dark_mode) {
  if (!p_surface->initialized || p_surface->dark_mode == dark_mode) return;
  p_surface->dark_mode = dark_mode;
}

extern "C" boolean Canvas_Web_Surface_Update_Input(
    Canvas_Web_Surface *p_surface,
    const Canvas_Camera *p_camera,
    const Canvas_Scene *p_scene) {
  if (!p_surface->initialized || !p_surface->p_native) return FALSE;
  NativeState *state = static_cast<NativeState *>(p_surface->p_native);
  Vector2 mouse = GetMousePosition();
  Canvas_Web_View *pointer_view = nullptr;
  const Canvas_Entity *pointer_entity = nullptr;
  Rectangle pointer_bounds = {};

  Vector2 world_mouse = Canvas_Camera_Screen_To_World(p_camera, mouse);
  int32 top_index = Canvas_Scene_Topmost_Visual(
      p_scene,
      world_mouse,
      p_camera->zoom);
  if (top_index >= 0) {
    const Canvas_Entity *entity = &p_scene->p_entities[top_index];
    if (entity->type == CANVAS_ENTITY_WEB_CONTENT &&
        !entity->removing &&
        EntityOpacity(entity) >= 0.99f) {
      Canvas_Web_View *view = FindView(p_surface, entity->id);
      Rectangle bounds = ContentBounds(p_camera, p_scene, entity);
      if (view &&
          view->active &&
          CheckCollisionPointRec(mouse, bounds)) {
        pointer_view = view;
        pointer_entity = entity;
        pointer_bounds = bounds;
      }
    }
  }

  if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) {
    p_surface->focused_entity_id = pointer_view ? pointer_view->entity_id : 0;
    for (int32 index = 0; index < CANVAS_MAX_WEB_VIEWS; ++index) {
      CefRefPtr<CanvasCefClient> client = state->clients[index];
      if (client && client->browser()) {
        client->browser()->GetHost()->SetFocus(
            pointer_view == &p_surface->views[index]);
      }
    }
  }

  Canvas_Web_View *focused_view =
      FindView(p_surface, p_surface->focused_entity_id);
  Canvas_Web_View *input_view = pointer_view ? pointer_view : focused_view;
  if (!input_view || !input_view->active) return FALSE;
  int32 view_index = ViewIndex(p_surface, input_view);
  CefRefPtr<CanvasCefClient> client = state->clients[view_index];
  CefRefPtr<CefBrowser> browser = client ? client->browser() : nullptr;
  const Canvas_Entity *input_entity = pointer_entity ?
      pointer_entity :
      FindSurfaceEntity(p_scene, input_view->entity_id);
  if (!browser || !input_entity) return pointer_view != nullptr;

  Rectangle bounds = pointer_view
      ? pointer_bounds
      : ContentBounds(p_camera, p_scene, input_entity);
  boolean inside = pointer_view == input_view;
  float scale_x = static_cast<float>(input_view->texture_width) /
      std::max(bounds.width, 1.0f);
  float scale_y = static_cast<float>(input_view->texture_height) /
      std::max(bounds.height, 1.0f);
  CefMouseEvent event;
  event.x = static_cast<int>((mouse.x - bounds.x) * scale_x);
  event.y = static_cast<int>((mouse.y - bounds.y) * scale_y);
  event.modifiers = 0;
  CefRefPtr<CefBrowserHost> host = browser->GetHost();
  host->SendMouseMoveEvent(event, !inside);
  if (inside && IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) {
    host->SendMouseClickEvent(event, MBT_LEFT, false, 1);
  }
  if (IsMouseButtonReleased(MOUSE_BUTTON_LEFT) &&
      p_surface->focused_entity_id == input_view->entity_id) {
    host->SendMouseClickEvent(event, MBT_LEFT, true, 1);
  }
  if (inside) {
    float wheel = GetMouseWheelMove();
    if (wheel != 0.0f) {
      boolean control_zoom =
          IsKeyDown(KEY_LEFT_CONTROL) ||
          IsKeyDown(KEY_RIGHT_CONTROL) ||
          IsKeyDown(KEY_LEFT_SUPER) ||
          IsKeyDown(KEY_RIGHT_SUPER);
      if (!control_zoom) {
        host->SendMouseWheelEvent(event, 0, static_cast<int>(wheel * 80.0f));
      }
    }
  }

  if (p_surface->focused_entity_id == input_view->entity_id) {
    int32 codepoint = GetCharPressed();
    while (codepoint > 0) {
      CefKeyEvent key;
      key.type = KEYEVENT_CHAR;
      key.windows_key_code = codepoint;
      key.native_key_code = codepoint;
      key.character = static_cast<char16_t>(codepoint);
      key.unmodified_character = static_cast<char16_t>(codepoint);
      host->SendKeyEvent(key);
      codepoint = GetCharPressed();
    }

    struct {
      int32 raylib_key;
      int32 browser_key;
    } keys[] = {
        {KEY_BACKSPACE, 0x08},
        {KEY_ENTER, 0x0D},
        {KEY_LEFT, 0x25},
        {KEY_UP, 0x26},
        {KEY_RIGHT, 0x27},
        {KEY_DOWN, 0x28},
        {KEY_DELETE, 0x2E},
    };
    for (const auto &key_mapping : keys) {
      if (!IsKeyPressed(key_mapping.raylib_key)) continue;
      CefKeyEvent key;
      key.windows_key_code = key_mapping.browser_key;
      key.native_key_code = key_mapping.browser_key;
      key.type = KEYEVENT_RAWKEYDOWN;
      host->SendKeyEvent(key);
      key.type = KEYEVENT_KEYUP;
      host->SendKeyEvent(key);
    }
  }
  if (inside &&
      (IsKeyDown(KEY_LEFT_CONTROL) ||
       IsKeyDown(KEY_RIGHT_CONTROL) ||
       IsKeyDown(KEY_LEFT_SUPER) ||
       IsKeyDown(KEY_RIGHT_SUPER)) &&
      GetMouseWheelMove() != 0.0f) {
    return FALSE;
  }
  return inside;
}

extern "C" void Canvas_Web_Surface_Sync(
    Canvas_Web_Surface *p_surface,
    const Canvas_Camera *p_camera,
    const Canvas_Scene *p_scene) {
  if (!p_surface->initialized) return;
  CefDoMessageLoopWork();
  ReconcileViews(p_surface, p_camera, p_scene);

  for (int32 index = 0; index < CANVAS_MAX_WEB_VIEWS; ++index) {
    Canvas_Web_View *view = &p_surface->views[index];
    if (!view->browser_created) continue;
    if (!view->texture_ready && IsWindowReady()) {
      Image image = {
          .data = view->p_pixels,
          .width = view->texture_width,
          .height = view->texture_height,
          .mipmaps = 1,
          .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8,
      };
      view->texture = LoadTextureFromImage(image);
      view->texture_ready = view->texture.id != 0 ? TRUE : FALSE;
      if (view->texture_ready) {
        SetTextureFilter(view->texture, TEXTURE_FILTER_BILINEAR);
      }
    }
    if (view->texture_ready && view->pixels_dirty) {
      UpdateTexture(view->texture, view->p_pixels);
      view->pixels_dirty = FALSE;
    }
    NativeState *state = static_cast<NativeState *>(p_surface->p_native);
    CefRefPtr<CanvasCefClient> client = state->clients[index];
    if (client && client->browser() &&
        view->entity_type == CANVAS_ENTITY_WEB_CONTENT) {
      if (!view->color_scheme_applied ||
          view->applied_dark_mode != p_surface->dark_mode) {
        ApplyColorScheme(client->browser(), p_surface->dark_mode);
        view->color_scheme_applied = TRUE;
        view->applied_dark_mode = p_surface->dark_mode;
      }
      view->can_go_back = client->browser()->CanGoBack() ? TRUE : FALSE;
      view->can_go_forward = client->browser()->CanGoForward() ? TRUE : FALSE;
    }
  }
}

extern "C" void Canvas_Web_Surface_Draw(
    const Canvas_Web_Surface *p_surface,
    const Canvas_Camera *p_camera,
    const Canvas_Scene *p_scene) {
  for (size_t index = 0;
      index < Dowa_Array_Length(p_scene->p_entities);
      ++index) {
    Canvas_Web_Surface_Draw_Entity(
        p_surface,
        p_camera,
        p_scene,
        index);
  }
}

extern "C" void Canvas_Web_Surface_Draw_Entity(
    const Canvas_Web_Surface *p_surface,
    const Canvas_Camera *p_camera,
    const Canvas_Scene *p_scene,
    size_t entity_index) {
  if (entity_index >= Dowa_Array_Length(p_scene->p_entities)) return;
  const Canvas_Entity *entity = &p_scene->p_entities[entity_index];
  if (entity->type != CANVAS_ENTITY_WEB_CONTENT &&
      entity->type != CANVAS_ENTITY_IMAGE) {
    return;
  }
  const Canvas_Web_View *view = FindView(p_surface, entity->id);
  if (!view || !view->active || !view->texture_ready) return;

  Rectangle bounds = ContentBounds(p_camera, p_scene, entity);
  float left = std::max(bounds.x, 0.0f);
  float top = std::max(bounds.y, 0.0f);
  float right = std::min(
      bounds.x + bounds.width,
      static_cast<float>(p_camera->viewport_width));
  float bottom = std::min(
      bounds.y + bounds.height,
      static_cast<float>(p_camera->viewport_height));
  if (right <= left || bottom <= top) return;

  BeginScissorMode(
      static_cast<int>(left),
      static_cast<int>(top),
      static_cast<int>(right - left),
      static_cast<int>(bottom - top));
  DrawTexturePro(
      view->texture,
      Rectangle{
          0.0f,
          0.0f,
          static_cast<float>(view->texture_width),
          static_cast<float>(view->texture_height),
      },
      bounds,
      Vector2{0.0f, 0.0f},
      0.0f,
      Color{
          255,
          255,
          255,
          static_cast<unsigned char>(EntityOpacity(entity) * 255.0f)});
  EndScissorMode();
}

extern "C" const char *Canvas_Web_Surface_URL(
    const Canvas_Web_Surface *p_surface,
    uint32 entity_id) {
  const Canvas_Web_View *view = FindView(p_surface, entity_id);
  return view && view->url[0] ? view->url : nullptr;
}

extern "C" boolean Canvas_Web_Surface_Can_Go_Back(
    const Canvas_Web_Surface *p_surface,
    uint32 entity_id) {
  const Canvas_Web_View *view = FindView(p_surface, entity_id);
  return view ? view->can_go_back : FALSE;
}

extern "C" boolean Canvas_Web_Surface_Can_Go_Forward(
    const Canvas_Web_Surface *p_surface,
    uint32 entity_id) {
  const Canvas_Web_View *view = FindView(p_surface, entity_id);
  return view ? view->can_go_forward : FALSE;
}

extern "C" void Canvas_Web_Surface_Go_Back(
    Canvas_Web_Surface *p_surface,
    uint32 entity_id) {
  if (!p_surface->p_native) return;
  Canvas_Web_View *view = FindView(p_surface, entity_id);
  if (!view) return;
  NativeState *state = static_cast<NativeState *>(p_surface->p_native);
  CefRefPtr<CanvasCefClient> client = state->clients[ViewIndex(p_surface, view)];
  if (client && client->browser() && client->browser()->CanGoBack()) {
    client->browser()->GoBack();
  }
}

extern "C" void Canvas_Web_Surface_Go_Forward(
    Canvas_Web_Surface *p_surface,
    uint32 entity_id) {
  if (!p_surface->p_native) return;
  Canvas_Web_View *view = FindView(p_surface, entity_id);
  if (!view) return;
  NativeState *state = static_cast<NativeState *>(p_surface->p_native);
  CefRefPtr<CanvasCefClient> client = state->clients[ViewIndex(p_surface, view)];
  if (client && client->browser() && client->browser()->CanGoForward()) {
    client->browser()->GoForward();
  }
}

extern "C" boolean Canvas_Web_Surface_Is_Focused(
    const Canvas_Web_Surface *p_surface) {
  return p_surface->focused_entity_id != 0;
}

extern "C" void Canvas_Web_Surface_Shutdown(Canvas_Web_Surface *p_surface) {
  if (!p_surface->p_native) return;
  NativeState *state = static_cast<NativeState *>(p_surface->p_native);
  for (int32 index = 0; index < CANVAS_MAX_WEB_VIEWS; ++index) {
    if (state->clients[index] && state->clients[index]->browser()) {
      state->clients[index]->browser()->GetHost()->CloseBrowser(true);
    }
    state->clients[index] = nullptr;
    if (p_surface->views[index].texture_ready) {
      UnloadTexture(p_surface->views[index].texture);
    }
    Dowa_Free(p_surface->views[index].p_pixels);
  }
  CefDoMessageLoopWork();
  if (state->cef_initialized) CefShutdown();
  state->~NativeState();
  p_surface->p_native = nullptr;
  p_surface->initialized = FALSE;
}