openpencil/crates/op-host-native/src/lib.rs
Kayshen-X 47e9e3cac6 refactor(host): gate op-host-native GL stack behind opt-in gl-host feature
Phase 0 of the op-web-daemon extraction. op-host-native now defaults to a
raster-only library: with the new NON-default gl-host feature off, it pulls no
winit/glutin/jian-host-desktop/accesskit and skia-safe WITHOUT gl (no GL link
directives). GUI consumers (op-host-desktop) opt in via features=[gl-host], so
the future headless op-web-daemon can depend on op-host-native
(default-features=false) for the raster export path without the desktop GUI stack.

- Move NativeFrameBackend widget_host/ -> backend/ (raster; no longer drags the
  interactive host into the raster build).
- Gate widget_host/preview/boolean_ops/canvas_view_stub + GlutinProvider /
  SharedSkiaContext behind gl-host. skia gl arrives only via skia-safe/gl;
  accesskit made optional. Mobile-safe: default features = no gl-host.

Verified: raster + desktop + app build green; 385 lib tests pass under gl-host;
cargo tree confirms the raster build is winit/glutin/skia-gl free. Codex-approved.
2026-06-19 17:47:34 +08:00

147 lines
5.5 KiB
Rust

//! OpenPencil shell — native (desktop) backend.
//!
//! Per spec v19 §1.2 (FROZEN 2026-05-04): this crate must NOT be linked into
//! the wasm32-unknown-unknown web bundle. Even though some deps (winit) compile
//! silently on wasm32 via web-sys, we use an explicit compile_error! guard to
//! make accidental inclusion a hard error.
//!
//! Step 1a Task 2: this crate now ships [`SharedSkiaContext`] (own GL stack +
//! Skia DirectContext + Surface, idempotent teardown, lifecycle hooks),
//! [`CanvasViewportStub`] (GL-state-isolation probe), and [`NativeBackend`]
//! (frame-scoped widget facade that translates OP `RenderBackend` calls into
//! Jian `DrawOp`s and submits via `jian_skia::SkiaBackend::draw_on_canvas`).
//!
//! Module layout (spec v19 §2 / §3 / §5.2.1):
//! - [`context`] — `GlContextProvider` trait + `GlutinProvider` desktop impl
//! + iOS / Android stubs; `SharedSkiaContext` owning the GL stack.
//! - [`canvas_view_stub`] — `CanvasViewportStub::render_into` (deliberately
//! pollutes GL state to verify chrome paint isolation).
//! - [`backend`] — `NativeBackend` exposing frame-scoped methods mirroring the
//! OP `RenderBackend` trait surface (no direct trait impl in Step 1a; see
//! spec §5.2.1).
#[cfg(target_arch = "wasm32")]
compile_error!(
"op-host-native must NOT be compiled for wasm32 targets. \
Use op-host-web for browser builds (spec v19 §1.2)."
);
// Cross-platform context module: re-exports the `GlContextProvider` trait
// + `ProviderError` / `ProviderResult` on every (non-wasm) target so spec
// §11 invariant 2 holds — mobile callers can name the trait. Internal
// cfg-gates select between `GlutinProvider` (desktop), `EaglProvider` (iOS)
// and `AndroidEglProvider` (Android), and `SharedSkiaContext` is only
// compiled in on desktop where the GL + Skia stack is available.
pub mod context;
// Desktop-only modules — pull `skia_safe` / `jian_skia` / `glutin` types
// that aren't fetched on iOS / Android (see Cargo.toml target-gated deps).
// Spec §11 invariants 1 & 3: mobile builds compile shell-native without
// these modules at all; mobile widget rendering lands in Step 1f.
// Cross-platform render stack: `backend` (NativeBackend) and
// `widget_host` (NativeFrameBackend + WidgetHostNative) compile on
// every target where `skia-safe` + `jian-skia` compile — desktop
// trio + iOS + Android. Per the 2026-05-10 user directive
// (jian will eventually need iOS and Android) these used to be
// desktop-only; lifting the gate is the first half of the mobile
// extension. The other half (real `EaglProvider` / `AndroidEglProvider`
// impls + Metal/Vulkan integration) lands in Step 1f.
//
// `canvas_view_stub` stays desktop-only — it pulls `glow` for a
// GL-state-isolation probe that has no mobile equivalent.
#[cfg(any(
target_os = "macos",
target_os = "linux",
target_os = "windows",
target_os = "ios",
target_os = "android"
))]
pub mod backend;
#[cfg(feature = "gl-host")]
#[cfg(any(
target_os = "macos",
target_os = "linux",
target_os = "windows",
target_os = "ios",
target_os = "android"
))]
pub mod boolean_ops;
#[cfg(feature = "gl-host")]
#[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))]
pub mod canvas_view_stub;
#[cfg(feature = "gl-host")]
#[cfg(any(
target_os = "macos",
target_os = "linux",
target_os = "windows",
target_os = "ios",
target_os = "android"
))]
pub mod widget_host;
// Canvas Preview (Play) mode runtime owner — depends on the OP
// `RenderBackend` trait (same gate as `widget_host`).
#[cfg(feature = "gl-host")]
#[cfg(any(
target_os = "macos",
target_os = "linux",
target_os = "windows",
target_os = "ios",
target_os = "android"
))]
pub mod preview;
#[cfg(any(
target_os = "macos",
target_os = "linux",
target_os = "windows",
target_os = "ios",
target_os = "android"
))]
pub use backend::{to_jian_rect, NativeBackend, NativeFrameBackend};
#[cfg(feature = "gl-host")]
#[cfg(any(
target_os = "macos",
target_os = "linux",
target_os = "windows",
target_os = "ios",
target_os = "android"
))]
pub use preview::PreviewSession;
#[cfg(feature = "gl-host")]
#[cfg(any(
target_os = "macos",
target_os = "linux",
target_os = "windows",
target_os = "ios",
target_os = "android"
))]
pub use widget_host::{CursorHint, WidgetHostNative};
// canvas_view_stub stays desktop-only (uses glow GL-isolation probe).
#[cfg(feature = "gl-host")]
#[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))]
pub use canvas_view_stub::CanvasViewportStub;
// Cross-platform re-exports — visible on every (non-wasm) target.
pub use context::{GlContextProvider, ProviderError, ProviderResult};
// Desktop-only re-exports.
#[cfg(feature = "gl-host")]
#[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))]
pub use context::{
GlutinProvider, SharedSkiaContext, SharedSkiaError, SharedSkiaResult, SurfaceConfig,
};
// Mobile stub re-exports — Step 1f real impls; today they're zero-sized
// placeholder structs whose `GlContextProvider` impls `unimplemented!()`.
#[cfg(target_os = "android")]
pub use context::AndroidEglProvider;
#[cfg(target_os = "ios")]
pub use context::EaglProvider;
// `placeholder()` from Task 1 was removed by Codex Phase A Gate round 1
// NIT 1 — Task 2's full re-export chain (`SharedSkiaContext`,
// `NativeBackend`, etc.) already proves the shell-core ↔ shell-native
// link, and Task 3's `AppShell::run_desktop` will be the canonical
// entry. Keeping the dead helper just to satisfy a removed link-check
// is YAGNI.