feat(desktop): adopt the casement winit fork

`casement` is ZSeven-W's winit fork (sibling repo, referenced by
local path for now). It adds the macOS open-documents Apple-event
hook that upstream winit lacks, needed for Finder double-click open.
The `package = "casement"` key keeps the `winit` import name so all
`use winit::…` stays unchanged.

glutin-winit is dropped: it hard-depends on the upstream `winit`
package, which would pull a second, incompatible winit into the
tree. Its only use — GlWindow::build_surface_attributes — is replaced
by a direct glutin SurfaceAttributesBuilder call in provider.rs.
This commit is contained in:
Kayshen-X 2026-05-18 21:45:09 +08:00
parent 70d37f71e1
commit c1f369f9e0
4 changed files with 41 additions and 44 deletions

View file

@ -68,6 +68,10 @@ op-acp = { path = "../op-acp" }
# `persistence.rs` keeps only the desktop-only `rfd` Save/Open + error
# dialogs.
op-pen-loader = { path = "../op-pen-loader" }
# In-app Git — `git_session.rs` binds an `op_git::GitRepo` to the
# currently-open document so the Git panel can show branch / status
# / history and drive commits.
op-git = { path = "../op-git" }
# Canonical `.op` / `.pen` schema — all persistence + interop goes
# through `PenDocument` rather than the desktop's private DocPayload.
# Falling back to the schema's `load_str` lets us open files saved by
@ -91,7 +95,12 @@ skia-safe = { version = "0.97.0", default-features = false, features = [
"gl",
"pdf",
] }
winit = { version = "0.30.13", default-features = false, features = [
# `casement` — ZSeven-W's winit fork (sibling repo, local path for
# now). It adds `winit::platform::macos::drain_opened_file_urls`, the
# macOS open-documents Apple-event hook upstream winit lacks. The
# `package` key keeps the `winit` import name, so all `use winit::…`
# stays unchanged.
winit = { package = "casement", path = "../../../winit", default-features = false, features = [
"x11",
"wayland",
"wayland-csd-adwaita",

View file

@ -109,8 +109,14 @@ skia-safe = { version = "0.97.0", default-features = false, features = [
"gl",
] }
glutin = "0.32.3"
glutin-winit = "0.5.0"
winit = { version = "0.30.13", default-features = false, features = [
# `casement` — ZSeven-W's winit fork (sibling repo, local path for
# now). The `package` key keeps the `winit` import name. `glutin-winit`
# is intentionally NOT used: it hard-depends on the upstream `winit`
# package, which would pull a second, incompatible winit into the
# 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 = "../../../winit", default-features = false, features = [
"x11",
"wayland",
"wayland-csd-adwaita",

View file

@ -2,12 +2,16 @@
//!
//! Phase C Task 4 deliverable: a minimal winit + `SharedSkiaContext` +
//! `NativeBackend` integration that paints chrome (rect / text / box
//! outline) on every frame and translates pointer events through Jian's
//! `PointerTranslator` directly into `jian_core::gesture::PointerEvent`
//! (re-exported from `op_editor_ui`). Per user 2026-05-05
//! directive there is no OP-specific event translation layer — widget
//! dispatch in Step 1c+ consumes Jian event types directly. The
//! macOS / Linux / Windows runtime is exercised by maintainers manually
//! outline) on every frame.
//!
//! NOTE: this demo used to also route pointer events through Jian's
//! `jian_host_desktop::pointer::PointerTranslator`, but that crate is
//! pinned to the upstream `winit` package while the desktop host now
//! builds on the `casement` winit fork — the two `winit::event` types
//! are not interchangeable. The translator call was discarded smoke-
//! test code (`let _ = jian_event`), so it has simply been dropped.
//!
//! The macOS / Linux / Windows runtime is exercised by maintainers manually
//! (`notes/step-1a-{macos, linux, windows}-manual-smoke.md`); CI only
//! verifies that `cargo build --examples --workspace` compiles on every
//! desktop OS.
@ -29,7 +33,6 @@
#![cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))]
use jian_core::scene::Color as JianColor;
use jian_host_desktop::pointer::PointerTranslator;
use op_editor_ui::{Color, Point2D, Rect, TextLayout};
use op_host_native::{NativeBackend, SharedSkiaContext, SharedSkiaError};
use winit::application::ApplicationHandler;
@ -79,11 +82,6 @@ struct BasicWindowApp {
window: Option<Window>,
ctx: Option<SharedSkiaContext>,
backend: Option<NativeBackend>,
/// Pointer wiring: winit `WindowEvent` → Jian `PointerEvent` (the
/// canonical event type also re-exported from
/// `op_editor_ui`). Widget hit-test + dispatch lands in
/// Step 1c+; the demo only proves the pipeline compiles.
pointer_translator: PointerTranslator,
/// Fatal teardown / surface error captured for post-loop diagnosis.
error: Option<SharedSkiaError>,
}
@ -94,7 +92,6 @@ impl BasicWindowApp {
window: None,
ctx: None,
backend: None,
pointer_translator: PointerTranslator::new(),
error: None,
}
}
@ -147,30 +144,6 @@ impl ApplicationHandler for BasicWindowApp {
_window_id: WindowId,
event: WindowEvent,
) {
// Route pointer-flavoured winit events through Jian's
// PointerTranslator. Non-pointer events (Resized /
// RedrawRequested / CloseRequested / keys) bypass the Jian
// path entirely — they're handled by winit directly in the
// match arms below.
match &event {
WindowEvent::ModifiersChanged(m) => {
self.pointer_translator.update_modifiers(m.state());
}
WindowEvent::CursorMoved { .. }
| WindowEvent::CursorLeft { .. }
| WindowEvent::MouseInput { .. }
| WindowEvent::Touch(_) => {
if let Some(jian_event) = self.pointer_translator.translate(&event) {
// The demo doesn't act on pointer events — it just
// proves the pipeline compiles + runs without
// panicking. Real widget dispatch (hit-test +
// gesture arena) lands in Step 1c+.
let _ = jian_event;
}
}
_ => {}
}
match event {
WindowEvent::CloseRequested => {
event_loop.exit();

View file

@ -140,7 +140,6 @@ impl GlutinProvider {
use glutin::context::ContextAttributesBuilder;
use glutin::display::{Display, GetGlDisplay};
use glutin::prelude::*;
use glutin_winit::GlWindow;
use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
use std::ffi::CString;
@ -190,9 +189,19 @@ impl GlutinProvider {
.map_err(ProviderError::from_error)?
};
let surface_attrs = window
.build_surface_attributes(<_>::default())
.map_err(ProviderError::from_error)?;
// GL window-surface attributes, built directly off the
// window's raw handle + inner size. This is what
// `glutin_winit::GlWindow::build_surface_attributes` does
// internally; we inline it so the crate stays off
// `glutin-winit` (which is pinned to the upstream `winit`
// package). Sizes are clamped to ≥ 1 — a zero-area surface
// is rejected by every GL backend.
let inner = window.inner_size();
let surface_width = std::num::NonZeroU32::new(inner.width.max(1)).unwrap();
let surface_height = std::num::NonZeroU32::new(inner.height.max(1)).unwrap();
let surface_attrs =
glutin::surface::SurfaceAttributesBuilder::<glutin::surface::WindowSurface>::new()
.build(raw_window_handle, surface_width, surface_height);
let surface = unsafe {
gl_config
.display()