refactor(shell-core): drop stale ShellEvent + re-export Jian Step 1b events
Step 1b Phase 0 + §3.2 P0.5A landing on the OP side:
(1) Phase 0 cleanup (spec v19.4 / OP commit f9dd4b56 was reverted by
merge cf61bcc1 ↔ origin/v0.8.0): delete src/event.rs (226-line OP
ShellEvent enum) and tests/event_shape.rs (3 stale unit tests).
Replace with tests/gesture_re_export.rs, which asserts the contract
that shell-core exposes Jian gesture types directly without an
OP-side wrapper.
(2) §3.2 P0.5A re-exports: lib.rs now also re-exports the new Jian
keyboard / IME / focus / wheel event types so widget code can
`use openpencil_shell_core::{KeyEvent, ImeEvent, FocusEvent,
WheelEvent, ScrollMode, ...};` without naming jian_core directly.
Full export list: FocusEvent, ImeEvent, ImeKind, KeyCode, KeyEvent,
KeyLocation, KeyState, KeyValue, NamedKey, Modifiers, MouseButtons,
PointerEvent, PointerId, PointerKind, PointerPhase, ScrollMode,
WheelEvent.
Tests (gesture_re_export.rs, 6 tests):
- pointer_event_is_re_exported_from_jian
- pointer_modifier_and_button_flags_keep_jian_names
- key_event_is_re_exported_from_jian
- ime_event_is_re_exported_from_jian
- focus_event_is_re_exported_from_jian
- wheel_event_is_re_exported_from_jian
cargo test -p openpencil-shell-core --test gesture_re_export → 6/6 PASS.
This commit is contained in:
parent
136274a3ec
commit
ca2308a0d5
|
|
@ -1,226 +0,0 @@
|
|||
//! `ShellEvent` — OP widget-facing primitive event enum (spec v19 §5.1).
|
||||
//!
|
||||
//! Per spec §1.2 (FROZEN 2026-05-04) shell-core must compile on
|
||||
//! `wasm32-unknown-unknown` and remain platform-neutral on iOS / Android.
|
||||
//! This module therefore declares **only OP types** — no winit, no Jian,
|
||||
//! no GL — so the enum is visible everywhere widgets compile (mobile +
|
||||
//! WASM included). The desktop mapper that lifts Jian `PointerEvent` into
|
||||
//! `ShellEvent` lives in `openpencil-shell-native::event` (target-gated to
|
||||
//! macOS / Linux / Windows; Step 1f extends to mobile).
|
||||
//!
|
||||
//! ## Spec invariants (§11 mobile-readiness)
|
||||
//! - 6 variants: `PointerMove / PointerButton / MouseWheel / Touch / Window / Key`.
|
||||
//! - `Touch` carries `TouchForce` (`Calibrated` mirrors winit::Force 1:1
|
||||
//! to avoid Step 1f mobile API break, plus `Normalized` for Android).
|
||||
//! - Newtype id fields are `pub` (spec round 3 BLOCK-R3-4 fix) so callers
|
||||
//! in shell-native can construct them across crate boundaries.
|
||||
|
||||
use crate::render_backend::Point2D;
|
||||
|
||||
/// Stable identity for a single pointer (mouse/pen/stylus/trackpad cursor).
|
||||
///
|
||||
/// Widened to `u64` here so OP can ingest mappers from platforms (iOS, Web)
|
||||
/// whose finger ids exceed Jian's `u32` `PointerId`. Desktop mapper widens
|
||||
/// `jian_core::gesture::PointerId(u32)` → `PointerId(u64)` losslessly.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct PointerId(pub u64);
|
||||
|
||||
/// Stable identity for a single touch/finger contact.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct TouchId(pub u64);
|
||||
|
||||
/// Lifecycle of a touch contact (spec §5.1).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum TouchPhase {
|
||||
/// Finger landed.
|
||||
Started,
|
||||
/// Finger moved while held.
|
||||
Moved,
|
||||
/// Finger lifted normally.
|
||||
Ended,
|
||||
/// System cancelled tracking (focus loss / iOS face-proximity /
|
||||
/// Android system-gesture intercept).
|
||||
Cancelled,
|
||||
}
|
||||
|
||||
/// Pressure / force for a touch contact (mirrors `winit::event::Force`
|
||||
/// 1:1 to avoid Step 1f mobile API break, per spec §11.3 invariant).
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum TouchForce {
|
||||
/// iOS 3D Touch / Apple Pencil. `force` is the raw force value;
|
||||
/// `max_possible_force` is the touch sensor's max; `altitude_angle`
|
||||
/// is the Pencil tilt angle in radians (π/2 = perpendicular).
|
||||
Calibrated {
|
||||
force: f64,
|
||||
max_possible_force: f64,
|
||||
altitude_angle: Option<f64>,
|
||||
},
|
||||
/// Android pressure (already normalized to [0.0, 1.0]).
|
||||
Normalized(f64),
|
||||
}
|
||||
|
||||
/// Mouse buttons (spec §5.1; mirrors winit::event::MouseButton).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum MouseButton {
|
||||
Left,
|
||||
Right,
|
||||
Middle,
|
||||
Back,
|
||||
Forward,
|
||||
Other(u16),
|
||||
}
|
||||
|
||||
/// Pressed/released state for buttons + keys.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ElementState {
|
||||
Pressed,
|
||||
Released,
|
||||
}
|
||||
|
||||
/// Mouse wheel / two-finger trackpad scroll delta.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum ScrollDelta {
|
||||
/// Discrete scroll, in lines (mouse wheel notch).
|
||||
LineDelta { x: f32, y: f32 },
|
||||
/// Continuous scroll, in logical pixels (trackpad).
|
||||
PixelDelta(Point2D),
|
||||
}
|
||||
|
||||
/// Modifier-key state at the moment an event was raised.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub struct Modifiers {
|
||||
pub shift: bool,
|
||||
pub ctrl: bool,
|
||||
pub alt: bool,
|
||||
/// Cmd on macOS, Super/Win on Linux/Windows (mirrors Jian `Modifiers::CMD`).
|
||||
pub meta: bool,
|
||||
}
|
||||
|
||||
/// Subset of keys OP currently surfaces (spec §5.1; expanded as widgets
|
||||
/// need them in Step 1c+). Variant names follow winit::keyboard::KeyCode
|
||||
/// for easy mapping. `Other(u32)` carries the raw scancode so
|
||||
/// shell-native can pass through unmapped keys without losing them.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum KeyCode {
|
||||
// Letters
|
||||
A,
|
||||
B,
|
||||
C,
|
||||
D,
|
||||
E,
|
||||
F,
|
||||
G,
|
||||
H,
|
||||
I,
|
||||
J,
|
||||
K,
|
||||
L,
|
||||
M,
|
||||
N,
|
||||
O,
|
||||
P,
|
||||
Q,
|
||||
R,
|
||||
S,
|
||||
T,
|
||||
U,
|
||||
V,
|
||||
W,
|
||||
X,
|
||||
Y,
|
||||
Z,
|
||||
// Digits
|
||||
Digit0,
|
||||
Digit1,
|
||||
Digit2,
|
||||
Digit3,
|
||||
Digit4,
|
||||
Digit5,
|
||||
Digit6,
|
||||
Digit7,
|
||||
Digit8,
|
||||
Digit9,
|
||||
// Whitespace / control
|
||||
Space,
|
||||
Enter,
|
||||
Tab,
|
||||
Backspace,
|
||||
Escape,
|
||||
Delete,
|
||||
// Arrows
|
||||
ArrowLeft,
|
||||
ArrowRight,
|
||||
ArrowUp,
|
||||
ArrowDown,
|
||||
// Modifiers (released as standalone keys)
|
||||
Shift,
|
||||
Control,
|
||||
Alt,
|
||||
Meta,
|
||||
/// Anything else — raw scancode. Step 1c+ widgets that need a
|
||||
/// specific key add a named variant.
|
||||
Other(u32),
|
||||
}
|
||||
|
||||
/// Window-level event kinds (spec §5.1; the desktop mapper synthesizes
|
||||
/// these directly from winit `WindowEvent`, never via Jian).
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum WindowEventKind {
|
||||
/// Window content area resized (logical pixels via `inner_size`).
|
||||
Resized { width: u32, height: u32 },
|
||||
/// HiDPI scale factor changed (Retina toggle, monitor switch).
|
||||
ScaleFactorChanged(f64),
|
||||
/// User clicked the close button / hit Cmd-W / etc.
|
||||
CloseRequested,
|
||||
/// Focus gained (`true`) or lost (`false`).
|
||||
Focused(bool),
|
||||
}
|
||||
|
||||
/// Widget-facing primitive event (spec v19 §5.1).
|
||||
///
|
||||
/// Layered on top of Jian `PointerEvent` (which only carries
|
||||
/// pointer/touch primitives — pos/buttons-bitset/phase). Window / Key /
|
||||
/// MouseWheel events do **not** route through Jian; they come straight
|
||||
/// from winit on desktop. See [`crate::event`] module docs and spec
|
||||
/// §5.1.1 for the full mapping contract.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum ShellEvent {
|
||||
/// Pointer moved (cursor or pen) — no button state change.
|
||||
PointerMove {
|
||||
id: PointerId,
|
||||
pos: Point2D,
|
||||
modifiers: Modifiers,
|
||||
},
|
||||
/// One pointer button transitioned Pressed/Released.
|
||||
/// Multi-button transitions (e.g. mid-gesture press of an additional
|
||||
/// button) are emitted as one `PointerButton` per changed bit, plus
|
||||
/// a trailing `PointerMove` if Jian phase was `Move` — see spec
|
||||
/// §5.1.1 for the full diff contract.
|
||||
PointerButton {
|
||||
id: PointerId,
|
||||
button: MouseButton,
|
||||
state: ElementState,
|
||||
pos: Point2D,
|
||||
modifiers: Modifiers,
|
||||
},
|
||||
/// Mouse wheel / trackpad scroll.
|
||||
MouseWheel {
|
||||
delta: ScrollDelta,
|
||||
modifiers: Modifiers,
|
||||
},
|
||||
/// Touch contact lifecycle event (mobile-ready; Step 1f).
|
||||
Touch {
|
||||
id: TouchId,
|
||||
phase: TouchPhase,
|
||||
pos: Point2D,
|
||||
force: Option<TouchForce>,
|
||||
},
|
||||
/// Window-level event (resize, scale change, close, focus).
|
||||
Window { kind: WindowEventKind },
|
||||
/// Keyboard event.
|
||||
Key {
|
||||
key: KeyCode,
|
||||
state: ElementState,
|
||||
modifiers: Modifiers,
|
||||
},
|
||||
}
|
||||
|
|
@ -28,6 +28,18 @@ pub use render_backend::{Color, Point2D, Rect, RenderBackend, TextLayout};
|
|||
/// consistent with Jian; OP-specific differentiation is at the canvas
|
||||
/// viewport / chrome layer (single-page + infinite canvas recommended,
|
||||
/// multi-page also supported), not at event-type abstraction.
|
||||
///
|
||||
/// Step 1b §2.4 additions (P0.5A jian PR):
|
||||
/// - `KeyEvent` + supporting `KeyValue` / `NamedKey` / `KeyCode` /
|
||||
/// `KeyLocation` / `KeyState` for browser + winit keyboard input.
|
||||
/// - `ImeEvent` + `ImeKind` for IME composition (UTF-8 byte-indexed
|
||||
/// selection per spec §2.4).
|
||||
/// - `FocusEvent` for window-level / widget-level focus transitions
|
||||
/// (separate from `FocusManager`'s internal Tab-ring chain).
|
||||
/// - `WheelEvent` + `ScrollMode` for W3C `WheelEvent.deltaMode` parity
|
||||
/// on browser + native winit hosts.
|
||||
pub use jian_core::gesture::{
|
||||
Modifiers, MouseButtons, PointerEvent, PointerId, PointerKind, PointerPhase,
|
||||
FocusEvent, ImeEvent, ImeKind, KeyCode, KeyEvent, KeyLocation, KeyState, KeyValue, Modifiers,
|
||||
MouseButtons, NamedKey, PointerEvent, PointerId, PointerKind, PointerPhase, ScrollMode,
|
||||
WheelEvent,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,92 +0,0 @@
|
|||
//! Plan v7 Task 3 Step 12 — proves the `ShellEvent` enum shape (spec
|
||||
//! §5.1) is reachable through the public re-export path and matches the
|
||||
//! 6-variant invariant. Constructed via the cross-platform OP types
|
||||
//! only — no Jian / winit / GL imports — so this test compiles on
|
||||
//! wasm32 and mobile too (verified by `cargo check
|
||||
//! --target wasm32-unknown-unknown -p openpencil-shell-core`).
|
||||
|
||||
use openpencil_shell_core::event::{
|
||||
ElementState, KeyCode, Modifiers, MouseButton, PointerId, ScrollDelta, ShellEvent, TouchForce,
|
||||
TouchId, TouchPhase, WindowEventKind,
|
||||
};
|
||||
use openpencil_shell_core::render_backend::Point2D;
|
||||
|
||||
#[test]
|
||||
fn six_variants_constructible_via_re_export() {
|
||||
let mods = Modifiers {
|
||||
shift: true,
|
||||
ctrl: false,
|
||||
alt: false,
|
||||
meta: false,
|
||||
};
|
||||
let pos = Point2D::new(1.0, 2.0);
|
||||
|
||||
let events = [
|
||||
ShellEvent::PointerMove {
|
||||
id: PointerId(1),
|
||||
pos,
|
||||
modifiers: mods,
|
||||
},
|
||||
ShellEvent::PointerButton {
|
||||
id: PointerId(1),
|
||||
button: MouseButton::Left,
|
||||
state: ElementState::Pressed,
|
||||
pos,
|
||||
modifiers: mods,
|
||||
},
|
||||
ShellEvent::MouseWheel {
|
||||
delta: ScrollDelta::LineDelta { x: 0.0, y: 1.0 },
|
||||
modifiers: mods,
|
||||
},
|
||||
ShellEvent::Touch {
|
||||
id: TouchId(7),
|
||||
phase: TouchPhase::Started,
|
||||
pos,
|
||||
force: Some(TouchForce::Normalized(0.5)),
|
||||
},
|
||||
ShellEvent::Window {
|
||||
kind: WindowEventKind::Resized {
|
||||
width: 800,
|
||||
height: 600,
|
||||
},
|
||||
},
|
||||
ShellEvent::Key {
|
||||
key: KeyCode::Escape,
|
||||
state: ElementState::Released,
|
||||
modifiers: mods,
|
||||
},
|
||||
];
|
||||
assert_eq!(events.len(), 6, "spec §5.1 declares exactly 6 variants");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn touch_force_calibrated_mirrors_winit() {
|
||||
// Spec §11.3 invariant: `TouchForce::Calibrated` mirrors
|
||||
// `winit::event::Force::Calibrated` 1:1 — fields exist with the
|
||||
// declared names so a Step 1f mobile mapper compiles.
|
||||
let f = TouchForce::Calibrated {
|
||||
force: 0.4,
|
||||
max_possible_force: 1.0,
|
||||
altitude_angle: Some(std::f64::consts::FRAC_PI_2),
|
||||
};
|
||||
if let TouchForce::Calibrated {
|
||||
force,
|
||||
max_possible_force,
|
||||
altitude_angle,
|
||||
} = f
|
||||
{
|
||||
assert_eq!(force, 0.4);
|
||||
assert_eq!(max_possible_force, 1.0);
|
||||
assert_eq!(altitude_angle, Some(std::f64::consts::FRAC_PI_2));
|
||||
} else {
|
||||
panic!("expected Calibrated variant");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn newtype_id_fields_pub_constructible() {
|
||||
// Round 3 BLOCK-R3-4 fix: `TouchId(pub u64)` + `PointerId(pub u64)`
|
||||
// constructible across crates so shell-native's mapper works.
|
||||
let _ = TouchId(42);
|
||||
let _ = PointerId(42);
|
||||
}
|
||||
89
crates/openpencil-shell-core/tests/gesture_re_export.rs
Normal file
89
crates/openpencil-shell-core/tests/gesture_re_export.rs
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
use openpencil_shell_core::{
|
||||
FocusEvent, ImeEvent, ImeKind, KeyCode, KeyEvent, KeyLocation, KeyState, KeyValue, Modifiers,
|
||||
MouseButtons, NamedKey, PointerEvent, PointerId, PointerKind, PointerPhase, ScrollMode,
|
||||
WheelEvent,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn pointer_event_is_re_exported_from_jian() {
|
||||
let event = PointerEvent::simple(
|
||||
7,
|
||||
PointerPhase::Down,
|
||||
jian_core::geometry::Point::new(12.0, 34.0),
|
||||
);
|
||||
|
||||
let _: PointerEvent = event.clone();
|
||||
let _: jian_core::gesture::PointerEvent = event.clone();
|
||||
|
||||
assert_eq!(event.id, PointerId(7));
|
||||
assert_eq!(event.kind, PointerKind::Touch);
|
||||
assert_eq!(event.phase, PointerPhase::Down);
|
||||
assert_eq!(event.position.x, 12.0);
|
||||
assert_eq!(event.position.y, 34.0);
|
||||
assert!(event.buttons.contains(MouseButtons::LEFT));
|
||||
assert!(event.modifiers.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pointer_modifier_and_button_flags_keep_jian_names() {
|
||||
let mods = Modifiers::SHIFT | Modifiers::CMD;
|
||||
assert!(mods.contains(Modifiers::SHIFT));
|
||||
assert!(mods.contains(Modifiers::CMD));
|
||||
assert!(!mods.contains(Modifiers::CTRL));
|
||||
|
||||
let buttons = MouseButtons::LEFT | MouseButtons::RIGHT;
|
||||
assert!(buttons.contains(MouseButtons::LEFT));
|
||||
assert!(buttons.contains(MouseButtons::RIGHT));
|
||||
assert!(!buttons.contains(MouseButtons::MIDDLE));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn key_event_is_re_exported_from_jian() {
|
||||
let event = KeyEvent {
|
||||
key: KeyValue::Named(NamedKey::Enter),
|
||||
code: KeyCode::Enter,
|
||||
location: KeyLocation::Standard,
|
||||
modifiers: Modifiers::empty(),
|
||||
state: KeyState::Pressed,
|
||||
repeat: false,
|
||||
is_composing: false,
|
||||
};
|
||||
let _: jian_core::gesture::KeyEvent = event.clone();
|
||||
assert_eq!(event.key, KeyValue::Named(NamedKey::Enter));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ime_event_is_re_exported_from_jian() {
|
||||
let event = ImeEvent {
|
||||
kind: ImeKind::CompositionUpdate {
|
||||
selection: Some(0..6),
|
||||
},
|
||||
text: "你好".to_string(),
|
||||
};
|
||||
let _: jian_core::gesture::ImeEvent = event.clone();
|
||||
match event.kind {
|
||||
ImeKind::CompositionUpdate { selection } => assert_eq!(selection, Some(0..6)),
|
||||
_ => panic!("expected CompositionUpdate"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn focus_event_is_re_exported_from_jian() {
|
||||
let event = FocusEvent {
|
||||
gained: true,
|
||||
node_id_hint: Some(11),
|
||||
related_node_id_hint: Some(7),
|
||||
};
|
||||
let _: jian_core::gesture::FocusEvent = event;
|
||||
assert!(event.gained);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wheel_event_is_re_exported_from_jian() {
|
||||
let event = WheelEvent::simple(
|
||||
jian_core::geometry::Point::new(0.0, 0.0),
|
||||
jian_core::geometry::Point::new(0.0, 120.0),
|
||||
);
|
||||
assert_eq!(event.mode, ScrollMode::Pixel);
|
||||
assert_eq!(event.delta_z, 0.0);
|
||||
}
|
||||
Loading…
Reference in a new issue