view 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
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_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();

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_Entity *entity) {
  float frame_inset = CANVAS_WEB_FRAME_INSET / 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});
  return Rectangle{
      position.x,
      position.y,
      (entity->size.x - frame_inset - resize_handle) * camera->zoom,
      (entity->size.y - frame_inset - resize_handle) * 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);
}

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 CefLifeSpanHandler {
 public:
  explicit CanvasCefClient(Canvas_Web_View *view) : view_(view) {}

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

  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;
}

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, 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 = CefColorSetARGB(0, 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->url, sizeof(view->url), "%s", entity->text);
    }
    return;
  }
  EnsureViewResolution(surface, view, bounds);

  if (type_changed || std::strcmp(view->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->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, entity))) {
      continue;
    }
    Rectangle bounds = ContentBounds(camera, 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, 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) {
  std::memset(p_surface, 0, sizeof(*p_surface));
  p_surface->p_arena = p_arena;
  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" 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 = {};

  for (int32 index = CANVAS_MAX_WEB_VIEWS - 1; index >= 0; --index) {
    Canvas_Web_View *view = &p_surface->views[index];
    if (!view->active) continue;
    const Canvas_Entity *entity = FindSurfaceEntity(p_scene, view->entity_id);
    if (!entity || entity->type != CANVAS_ENTITY_WEB_CONTENT) continue;
    Rectangle bounds = ContentBounds(p_camera, entity);
    if (CheckCollisionPointRec(mouse, bounds)) {
      pointer_view = view;
      pointer_entity = entity;
      pointer_bounds = bounds;
      break;
    }
  }

  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, 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) {
      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);
    }
  }
  return inside ||
      p_surface->focused_entity_id == input_view->entity_id;
}

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;
    }
  }
}

extern "C" void Canvas_Web_Surface_Draw(
    const Canvas_Web_Surface *p_surface,
    const Canvas_Camera *p_camera,
    const Canvas_Scene *p_scene) {
  for (int32 index = 0; index < CANVAS_MAX_WEB_VIEWS; ++index) {
    const Canvas_Web_View *view = &p_surface->views[index];
    if (!view->active || !view->texture_ready) continue;
    const Canvas_Entity *entity = FindSurfaceEntity(p_scene, view->entity_id);
    if (!entity) continue;
    Rectangle bounds = ContentBounds(p_camera, 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) continue;

    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,
        WHITE);
    EndScissorMode();
  }
}

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;
}