diff --git a/crates/op-host-desktop/Cargo.toml b/crates/op-host-desktop/Cargo.toml index 17c99ebe2..dd5c55615 100644 --- a/crates/op-host-desktop/Cargo.toml +++ b/crates/op-host-desktop/Cargo.toml @@ -62,7 +62,7 @@ role = "Editor" # Phase 7.3 reorg: openpencil-shell-core dissolved — widget facade / # render-backend / layout scene paths resolve through op-editor-ui. op-editor-ui = { path = "../op-editor-ui" } -op-host-native = { path = "../op-host-native", version = "0.8.0" } +op-host-native = { path = "../op-host-native", version = "0.8.0", features = ["gl-host"] } # Accessibility (#67) — the platform-free `accesskit` core types # (`TreeUpdate` / `ActionRequest` / `ActivationHandler` …) used by the # desktop a11y adapter (`src/a11y.rs`). The OS-specific subclassing diff --git a/crates/op-host-native/Cargo.toml b/crates/op-host-native/Cargo.toml index b14e7bea5..5cae76664 100644 --- a/crates/op-host-native/Cargo.toml +++ b/crates/op-host-native/Cargo.toml @@ -10,6 +10,40 @@ description = "OpenPencil native host — winit + skia-safe + accesskit backend, name = "op_host_native" path = "src/lib.rs" +[features] +# `gl-host` (NON-default) enables the desktop winit/glutin interactive runner +# tier + skia-safe's `gl` feature + accesskit. Consumers that need the GUI host +# (`op-host-desktop`, `op-app`) opt in via `features = ["gl-host"]`. Left OFF — +# the default, used by `op-web-daemon` (via `default-features = false`) and by +# the mobile `cargo check` (default features) — it yields a raster-only library: +# no winit / glutin / jian-host-desktop / accesskit, and skia-safe WITHOUT `gl` +# (so it emits no native GL link directives). The raster `NativeBackend` + +# `NativeFrameBackend` + offscreen raster + text measurement remain. Mobile is +# safe by construction: default features = no `gl-host`; the gated deps also live +# only in the desktop-only target block. +# +# Why non-default (Approach Y): cargo can't alias skia-safe twice ("multiple +# times with different names") and can't make it optional-in-one-target-block + +# required-in-another; and a `skia-safe/gl` edge in a *default* feature would +# flip gl on for mobile. Making `gl-host` non-default + opt-in is the only +# cargo-valid, mobile-safe way to keep skia's `gl` out of the raster build. +default = [] +gl-host = [ + "skia-safe/gl", + "dep:glutin", + "dep:winit", + "dep:scopeguard", + "dep:jian-host-desktop", + "dep:accesskit", +] + +# The basic_window demo needs the GL host stack (SharedSkiaContext + winit); +# `required-features` makes `cargo build --examples` skip it under +# `--no-default-features` instead of failing on a missing `main`. +[[example]] +name = "basic_window" +required-features = ["gl-host"] + [dependencies] # Phase 7.3 reorg: openpencil-shell-core dissolved — widget facade / # theme / layout scene / scene vars / render-backend / gesture types @@ -20,7 +54,9 @@ op-editor-ui = { path = "../op-editor-ui" } # into an `accesskit::TreeUpdate` (see `widget_host/a11y.rs`) for the # desktop platform adapter to publish. Pinned to 0.24 to match # op-editor-ui + the platform adapter crates' transitive accesskit. -accesskit = "0.24" +# Optional + behind `gl-host` (its only user, `widget_host/a11y.rs`, is +# gl-host-gated) so the raster-only build pulls no accesskit at all. +accesskit = { version = "0.24", optional = true } # Canvas Preview (Play) mode serializes the document to JSON before # building the jian runtime, so the saved doc is never mutated. @@ -113,12 +149,11 @@ jian-skia = { path = "../../vendor/jian/crates/jian-skia", version = "0.0.1", fe # `compile_error!`. macOS / Windows backends auto-enable via # cfg(target_os) and need no feature flag. [target.'cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))'.dependencies] -skia-safe = { version = "0.97.0", default-features = false, features = [ - "binary-cache", - "textlayout", - "gl", -] } -glutin = "0.32.3" +# skia-safe's `gl` feature is added via the `gl-host` feature (`skia-safe/gl`), +# NOT here — so with `gl-host` off the raster/mobile build links the 5-OS +# skia-safe above WITHOUT `gl` and emits no native GL link directives. The 5-OS +# entry is the single skia-safe edge; no desktop-only skia-safe entry is needed. +glutin = { version = "0.32.3", optional = true } # `casement` — ZSeven-W's winit fork (vendored as a submodule under # `vendor/casement` so CI checks out the same source the desktop binary # links). The `package` key keeps the `winit` import name. `glutin-winit` @@ -127,14 +162,14 @@ glutin = "0.32.3" # tree. Its only use here — `GlWindow::build_surface_attributes` — is # replaced by a direct `glutin::surface::SurfaceAttributesBuilder` # call in `context/provider.rs`. -winit = { package = "casement", path = "../../vendor/casement", default-features = false, features = [ +winit = { package = "casement", path = "../../vendor/casement", default-features = false, optional = true, features = [ "x11", "wayland", "wayland-csd-adwaita", "rwh_06", ] } -scopeguard = "1.2" -jian-host-desktop = { path = "../../vendor/jian/crates/jian-host-desktop", version = "0.0.1", default-features = false, features = [ +scopeguard = { version = "1.2", optional = true } +jian-host-desktop = { path = "../../vendor/jian/crates/jian-host-desktop", version = "0.0.1", default-features = false, optional = true, features = [ "textlayout", ] } diff --git a/crates/op-host-native/src/widget_host/frame_backend.rs b/crates/op-host-native/src/backend/frame_backend.rs similarity index 100% rename from crates/op-host-native/src/widget_host/frame_backend.rs rename to crates/op-host-native/src/backend/frame_backend.rs diff --git a/crates/op-host-native/src/backend/mod.rs b/crates/op-host-native/src/backend/mod.rs index 9d73d572f..e5f2aaf91 100644 --- a/crates/op-host-native/src/backend/mod.rs +++ b/crates/op-host-native/src/backend/mod.rs @@ -8,6 +8,8 @@ //! Step 1c+ may wrap `NativeBackend` inside a `WithCanvas<'a>` newtype so //! the OP `RenderBackend` trait gets a real impl. +mod frame_backend; pub mod skia; +pub use frame_backend::NativeFrameBackend; pub use skia::{enumerate_system_font_families, to_jian_rect, NativeBackend}; diff --git a/crates/op-host-native/src/context/mod.rs b/crates/op-host-native/src/context/mod.rs index a0657038a..3a43c76d0 100644 --- a/crates/op-host-native/src/context/mod.rs +++ b/crates/op-host-native/src/context/mod.rs @@ -19,6 +19,7 @@ pub use provider::{GlContextProvider, ProviderError, ProviderResult}; pub use provider::AndroidEglProvider; #[cfg(target_os = "ios")] pub use provider::EaglProvider; +#[cfg(feature = "gl-host")] #[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))] pub use provider::GlutinProvider; @@ -26,7 +27,9 @@ pub use provider::GlutinProvider; // (cfg-gated dependencies). Step 1f mobile wiring will introduce a mobile // twin (or generalize this one) once iOS / Android providers go from // `unimplemented!()` placeholders to real GL contexts. +#[cfg(feature = "gl-host")] #[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))] mod shared; +#[cfg(feature = "gl-host")] #[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))] pub use shared::{SharedSkiaContext, SharedSkiaError, SharedSkiaResult, SurfaceConfig}; diff --git a/crates/op-host-native/src/context/provider.rs b/crates/op-host-native/src/context/provider.rs index b41527ec0..dcbf85e3b 100644 --- a/crates/op-host-native/src/context/provider.rs +++ b/crates/op-host-native/src/context/provider.rs @@ -10,6 +10,7 @@ //! exist as compile-time placeholders so the public API surface is frozen //! before Step 1f real implementations land. +#[cfg(feature = "gl-host")] #[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))] use std::error::Error; use std::sync::Arc; @@ -34,6 +35,7 @@ impl ProviderError { /// desktop `GlutinProvider` to convert glutin / glutin-winit errors; /// `cfg(any(...))`-gated to silence `dead_code` on iOS / Android where /// no in-tree caller exists yet (Step 1f mobile providers will use it). + #[cfg(feature = "gl-host")] #[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))] pub(crate) fn from_error(err: E) -> Self { Self::Failure(err.to_string()) @@ -118,6 +120,7 @@ pub trait GlContextProvider { /// All non-`Send` glutin handles live in [`Option`]s so `release` can /// drop them in a defined order (surface → context → display) without /// requiring `&mut self` to consume `self`. +#[cfg(feature = "gl-host")] #[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))] pub struct GlutinProvider { /// Currently-current context. `None` after `release`. @@ -130,6 +133,7 @@ pub struct GlutinProvider { glow: Arc, } +#[cfg(feature = "gl-host")] #[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))] impl GlutinProvider { /// Construct from an existing winit window. Builds a glutin display @@ -236,6 +240,7 @@ impl GlutinProvider { } } +#[cfg(feature = "gl-host")] #[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))] fn window_surface_size( inner: winit::dpi::PhysicalSize, @@ -251,6 +256,7 @@ fn window_surface_size( Ok((width, height)) } +#[cfg(feature = "gl-host")] #[cfg(target_os = "macos")] fn pick_display_api( _raw: raw_window_handle::RawWindowHandle, @@ -258,6 +264,7 @@ fn pick_display_api( glutin::display::DisplayApiPreference::Cgl } +#[cfg(feature = "gl-host")] #[cfg(target_os = "windows")] fn pick_display_api( raw: raw_window_handle::RawWindowHandle, @@ -265,6 +272,7 @@ fn pick_display_api( glutin::display::DisplayApiPreference::WglThenEgl(Some(raw)) } +#[cfg(feature = "gl-host")] #[cfg(target_os = "linux")] fn pick_display_api( _raw: raw_window_handle::RawWindowHandle, @@ -274,6 +282,7 @@ fn pick_display_api( glutin::display::DisplayApiPreference::Egl } +#[cfg(feature = "gl-host")] #[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))] impl GlContextProvider for GlutinProvider { #[tracing::instrument(skip(self))] @@ -411,7 +420,7 @@ impl GlContextProvider for AndroidEglProvider { } } -#[cfg(test)] +#[cfg(all(test, feature = "gl-host"))] mod tests { use super::*; diff --git a/crates/op-host-native/src/lib.rs b/crates/op-host-native/src/lib.rs index 1c79d456a..1e7231031 100644 --- a/crates/op-host-native/src/lib.rs +++ b/crates/op-host-native/src/lib.rs @@ -57,6 +57,7 @@ pub mod context; target_os = "android" ))] pub mod backend; +#[cfg(feature = "gl-host")] #[cfg(any( target_os = "macos", target_os = "linux", @@ -65,8 +66,10 @@ pub mod backend; 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", @@ -77,6 +80,7 @@ pub mod canvas_view_stub; 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", @@ -93,7 +97,8 @@ pub mod preview; target_os = "ios", target_os = "android" ))] -pub use backend::{to_jian_rect, NativeBackend}; +pub use backend::{to_jian_rect, NativeBackend, NativeFrameBackend}; +#[cfg(feature = "gl-host")] #[cfg(any( target_os = "macos", target_os = "linux", @@ -102,6 +107,7 @@ pub use backend::{to_jian_rect, NativeBackend}; target_os = "android" ))] pub use preview::PreviewSession; +#[cfg(feature = "gl-host")] #[cfg(any( target_os = "macos", target_os = "linux", @@ -109,8 +115,9 @@ pub use preview::PreviewSession; target_os = "ios", target_os = "android" ))] -pub use widget_host::{CursorHint, NativeFrameBackend, WidgetHostNative}; +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; @@ -118,6 +125,7 @@ pub use canvas_view_stub::CanvasViewportStub; 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, diff --git a/crates/op-host-native/src/widget_host.rs b/crates/op-host-native/src/widget_host.rs index 1e720d1c1..284c22ed7 100644 --- a/crates/op-host-native/src/widget_host.rs +++ b/crates/op-host-native/src/widget_host.rs @@ -22,7 +22,7 @@ //! //! This file is the public spine. Implementation methods are split //! across sibling submodules (per the 800-line-per-file ceiling): -//! - [`frame_backend`] — `NativeFrameBackend` (`RenderBackend` impl) +//! - `NativeFrameBackend` (`RenderBackend` impl) — moved to `crate::backend` //! - [`helpers`] — hex parsing + resize-bounds math + constants //! - [`geometry`] — canvas region / cursor hint / picker rect helpers //! - [`input`] — cursor-move / release / panel-resize input handlers @@ -76,7 +76,6 @@ mod design_md_press_tests; #[cfg(test)] mod figma_import_tests; mod font_picker_dispatch; -mod frame_backend; mod geometry; mod geometry_settings_hover; #[cfg(test)] @@ -147,8 +146,6 @@ mod variables_panel_ux_tests; mod variables_preset_press; mod viewport_fit; -pub use frame_backend::NativeFrameBackend; - /// Cursor affordance the host suggests for a given screen point. /// The runner maps each variant to its native cursor (`CursorIcon` /// on desktop, CSS `cursor:` string on web). diff --git a/crates/op-host-native/src/widget_host/paint.rs b/crates/op-host-native/src/widget_host/paint.rs index 39305e7aa..e3acea1f6 100644 --- a/crates/op-host-native/src/widget_host/paint.rs +++ b/crates/op-host-native/src/widget_host/paint.rs @@ -2,11 +2,11 @@ //! Pulled out of `widget_host.rs` to keep the spine file under //! the 800-line ceiling. -use super::frame_backend::NativeFrameBackend; use super::helpers::{ GIT_PANEL_CARET_H, GIT_PANEL_CARET_HALF, STATUS_INSET, TOOLBAR_INSET_X, TOOLBAR_INSET_Y, }; use super::WidgetHostNative; +use crate::backend::NativeFrameBackend; use op_editor_ui::widgets::editor_state_ext::theme_for; use op_editor_ui::widgets::{ variables_panel::VariablesPanel, AIChatPlaceholder, AlignToolbar, CanvasViewport, diff --git a/crates/op-host-native/tests/common/mod.rs b/crates/op-host-native/tests/common/mod.rs index fced26032..7b154b7a0 100644 --- a/crates/op-host-native/tests/common/mod.rs +++ b/crates/op-host-native/tests/common/mod.rs @@ -1,3 +1,4 @@ +#![cfg(feature = "gl-host")] //! Shared test helpers for `op-host-native`. //! //! Spec v19 §9 / plan v7 Task 2 Step 16a-16f: this module supplies diff --git a/crates/op-host-native/tests/gpu_chrome_stub_composition.rs b/crates/op-host-native/tests/gpu_chrome_stub_composition.rs index 96bbbe3f4..fa6bc7a65 100644 --- a/crates/op-host-native/tests/gpu_chrome_stub_composition.rs +++ b/crates/op-host-native/tests/gpu_chrome_stub_composition.rs @@ -1,3 +1,4 @@ +#![cfg(feature = "gl-host")] //! Spec v19 acceptance #2 (chrome + canvas-stub on the same GPU surface) //! / plan v7 Task 2 Step 16d / round 6 BLOCK-R6-2. //! diff --git a/crates/op-host-native/tests/gpu_smoke.rs b/crates/op-host-native/tests/gpu_smoke.rs index 6efc79bfe..7c7bf1b77 100644 --- a/crates/op-host-native/tests/gpu_smoke.rs +++ b/crates/op-host-native/tests/gpu_smoke.rs @@ -1,3 +1,4 @@ +#![cfg(feature = "gl-host")] //! Spec v19 §9.2 / acceptance #3 / plan v7 Task 2 Step 16f. //! //! Per-OS dispatch: diff --git a/crates/op-host-native/tests/memory_loop.rs b/crates/op-host-native/tests/memory_loop.rs index 7108a0c9e..e2b2608e1 100644 --- a/crates/op-host-native/tests/memory_loop.rs +++ b/crates/op-host-native/tests/memory_loop.rs @@ -1,3 +1,4 @@ +#![cfg(feature = "gl-host")] //! Spec v19 §9.3 acceptance #6 / plan v7 Task 2 Step 16b: //! 100 iterations of `{ create + frame + present + teardown × 3 }` must //! not grow RSS by more than 5 % over baseline. diff --git a/crates/op-host-native/tests/preview_raster.rs b/crates/op-host-native/tests/preview_raster.rs index 291751a7c..64b7d16ae 100644 --- a/crates/op-host-native/tests/preview_raster.rs +++ b/crates/op-host-native/tests/preview_raster.rs @@ -1,3 +1,4 @@ +#![cfg(feature = "gl-host")] //! Pixel-level proof that Canvas Preview (Play) renders through the //! design-canvas scene painter — the "orange renders grey / layout //! scattered" bug, verified end-to-end on a real raster surface. diff --git a/crates/op-host-native/tests/raster_composition.rs b/crates/op-host-native/tests/raster_composition.rs index 254195ee6..88c3c3e28 100644 --- a/crates/op-host-native/tests/raster_composition.rs +++ b/crates/op-host-native/tests/raster_composition.rs @@ -1,3 +1,4 @@ +#![cfg(feature = "gl-host")] //! Spec v19 acceptance #2 (chrome region) / plan v7 Task 2 Step 16d. //! //! Round 5 BLOCK-R5-2 fix: this test covers **chrome only** on a raster diff --git a/crates/op-host-native/tests/resize_smoke.rs b/crates/op-host-native/tests/resize_smoke.rs index 2e14855a6..969c1a121 100644 --- a/crates/op-host-native/tests/resize_smoke.rs +++ b/crates/op-host-native/tests/resize_smoke.rs @@ -1,3 +1,4 @@ +#![cfg(feature = "gl-host")] //! Spec v19 acceptance #5 (resize / DPI no-flicker) / Phase A Gate //! round 2 CONCERN 2. //! diff --git a/crates/op-host-native/tests/teardown_idempotent.rs b/crates/op-host-native/tests/teardown_idempotent.rs index 6fb578d65..9cefe6732 100644 --- a/crates/op-host-native/tests/teardown_idempotent.rs +++ b/crates/op-host-native/tests/teardown_idempotent.rs @@ -1,3 +1,4 @@ +#![cfg(feature = "gl-host")] //! Spec v19 §3.3 / plan v7 Task 2 Step 16a: //! `teardown` + lifecycle hooks must be idempotent. //! diff --git a/crates/op-host-native/tests/tracing_spans.rs b/crates/op-host-native/tests/tracing_spans.rs index 6161f8c3c..f8782c0f2 100644 --- a/crates/op-host-native/tests/tracing_spans.rs +++ b/crates/op-host-native/tests/tracing_spans.rs @@ -1,3 +1,4 @@ +#![cfg(feature = "gl-host")] //! Spec v19 acceptance #8 / plan v7 Task 2 Step 16c: //! `tracing` spans must fire at `begin_frame / present / resize / //! teardown` (and the rest of the frame surface). `tracing-test`