refactor(rust): dissolve openpencil-shell-core re-export shim
Phase 7.3 strangler reorg — the final consumer (op-pen-loader) is repointed off the openpencil-shell-core shim onto op-editor-ui (the real source crate for the layout scene / scene-var / render-backend facade), then the shim crate is deleted. - op-pen-loader: openpencil-shell-core dep -> op-editor-ui; every openpencil_shell_core:: path -> op_editor_ui:: - git rm crates/openpencil-shell-core/ (lib + jian.rs module + the two re-export anchor tests, all superseded by op-editor-ui's own surface; the jian.rs module had no consumers outside the shim) - stale shell-core / shell-native comment refs in op-editor-core + op-host-desktop manifests updated
This commit is contained in:
parent
1e0b3cab7a
commit
8733f45e2d
12
Cargo.lock
generated
12
Cargo.lock
generated
|
|
@ -2409,21 +2409,11 @@ dependencies = [
|
|||
"jian-ops-schema",
|
||||
"jian-skia",
|
||||
"op-editor-core",
|
||||
"openpencil-shell-core",
|
||||
"op-editor-ui",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openpencil-shell-core"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"jian-core",
|
||||
"op-editor-core",
|
||||
"op-editor-ui",
|
||||
"op-i18n",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "option-ext"
|
||||
version = "0.2.0"
|
||||
|
|
|
|||
|
|
@ -16,5 +16,5 @@ jian-core = { path = "../../vendor/jian/crates/jian-core" }
|
|||
op-i18n = { path = "../op-i18n" }
|
||||
|
||||
# render_backend.rs uses glam::Vec2 as Point2D (spec §5.2). Pinned to the
|
||||
# same version openpencil-shell-core uses so both crates resolve one glam.
|
||||
# same version op-editor-ui uses so both crates resolve one glam.
|
||||
glam = { version = "0.29", default-features = false, features = ["std"] }
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ op-host-native = { path = "../op-host-native", version = "0.1.0" }
|
|||
# old shell-core `Document`.
|
||||
op-editor-core = { path = "../op-editor-core" }
|
||||
# Phase 7 strangler reorg: the MCP tool registry + JSON-RPC stdio layer
|
||||
# (`mcp.rs` + `mcp/*`) was extracted out of openpencil-shell-core into the
|
||||
# (`mcp.rs` + `mcp/*`) was extracted out of the dissolved shell-core into the
|
||||
# op-mcp crate; `mcp_serve.rs` registers tools through `op_mcp::*`.
|
||||
op-mcp = { path = "../op-mcp" }
|
||||
# Phase 7 strangler reorg: the transport-free AI chat data shapes
|
||||
|
|
@ -37,7 +37,7 @@ op-mcp = { path = "../op-mcp" }
|
|||
op-ai = { path = "../op-ai" }
|
||||
# Canonical `.op` (PenDocument) → shell `Document` loader / adapter.
|
||||
# Extracted out of this binary into a shared library crate so library
|
||||
# crates (openpencil-shell-native) can reuse the conversion;
|
||||
# crates (op-host-native) can reuse the conversion;
|
||||
# `persistence.rs` keeps only the desktop-only `rfd` Save/Open + error
|
||||
# dialogs.
|
||||
op-pen-loader = { path = "../op-pen-loader" }
|
||||
|
|
|
|||
|
|
@ -17,9 +17,10 @@ jian-core = { path = "../../vendor/jian/crates/jian-core" }
|
|||
# `MeasureBackend` plugged into the layout engine. Desktop-side, so
|
||||
# depending on skia is fine; this crate does NOT need to be wasm-clean.
|
||||
jian-skia = { path = "../../vendor/jian/crates/jian-skia", features = ["textlayout"] }
|
||||
# `Document` / `Node` / `VariableTable` etc. — the shell-core model
|
||||
# this crate converts INTO.
|
||||
openpencil-shell-core = { path = "../openpencil-shell-core", version = "0.1.0" }
|
||||
# Phase 7.3 reorg: openpencil-shell-core dissolved. The layout scene /
|
||||
# scene-var / render-backend facade this crate converts INTO now
|
||||
# resolves through the op-editor-ui crate.
|
||||
op-editor-ui = { path = "../op-editor-ui" }
|
||||
# `EditorState` — the canonical editor-state superset this crate
|
||||
# derives a paint-only `Document` (incl. its `var_table`) FROM. Lives
|
||||
# in op-pen-loader, NOT op-editor-core, so op-editor-core stays free of
|
||||
|
|
|
|||
|
|
@ -91,8 +91,8 @@ pub fn pen_document_to_payload(doc: &PenDocument) -> LoadedDoc {
|
|||
/// the supported `VariableDefinition` variants; unknown future
|
||||
/// `VariableKind`s round-trip via their `Color/Number/Boolean/String`
|
||||
/// label since the enums are isomorphic.
|
||||
pub fn build_var_table(doc: &PenDocument) -> openpencil_shell_core::scene_vars::VariableTable {
|
||||
use openpencil_shell_core::scene_vars::{
|
||||
pub fn build_var_table(doc: &PenDocument) -> op_editor_ui::scene_vars::VariableTable {
|
||||
use op_editor_ui::scene_vars::{
|
||||
ThemeAxis, ThemedValue, Variable, VariableKind, VariableTable, VariableValue,
|
||||
};
|
||||
let mut out = VariableTable::default();
|
||||
|
|
@ -137,8 +137,8 @@ pub fn build_var_table(doc: &PenDocument) -> openpencil_shell_core::scene_vars::
|
|||
|
||||
fn map_scalar(
|
||||
s: &jian_ops_schema::variable::VariableScalar,
|
||||
) -> openpencil_shell_core::scene_vars::VariableScalar {
|
||||
use openpencil_shell_core::scene_vars::VariableScalar;
|
||||
) -> op_editor_ui::scene_vars::VariableScalar {
|
||||
use op_editor_ui::scene_vars::VariableScalar;
|
||||
match s {
|
||||
jian_ops_schema::variable::VariableScalar::Bool(b) => VariableScalar::Bool(*b),
|
||||
jian_ops_schema::variable::VariableScalar::Num(n) => VariableScalar::Num(*n),
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
use jian_ops_schema::node::PenNode;
|
||||
use jian_ops_schema::style::PenEffect;
|
||||
use openpencil_shell_core::layout_scene::{DropShadow, Effect};
|
||||
use op_editor_ui::layout_scene::{DropShadow, Effect};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Serializable mirror of `document::DropShadow`.
|
||||
|
|
@ -50,7 +50,7 @@ fn shadow_payload_to_effect(s: &ShadowPayload) -> Effect {
|
|||
offset_x: s.offset_x,
|
||||
offset_y: s.offset_y,
|
||||
blur: s.blur,
|
||||
color: openpencil_shell_core::Color {
|
||||
color: op_editor_ui::Color {
|
||||
r: s.color[0],
|
||||
g: s.color[1],
|
||||
b: s.color[2],
|
||||
|
|
@ -177,7 +177,7 @@ fn parse_hex(s: &str) -> Option<[f32; 4]> {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use openpencil_shell_core::Color;
|
||||
use op_editor_ui::Color;
|
||||
|
||||
#[test]
|
||||
fn parse_css_color_handles_rgba_and_hex() {
|
||||
|
|
|
|||
|
|
@ -21,12 +21,12 @@
|
|||
//! transforms on them are lossless format conversions (colour array →
|
||||
//! struct, kind / fill-type string → enum).
|
||||
|
||||
use openpencil_shell_core::layout_scene::NodeKind;
|
||||
use openpencil_shell_core::scene_vars::VariableTable;
|
||||
use openpencil_shell_core::layout_scene::{
|
||||
use op_editor_ui::layout_scene::NodeKind;
|
||||
use op_editor_ui::scene_vars::VariableTable;
|
||||
use op_editor_ui::layout_scene::{
|
||||
LayoutScene, SceneFillType, SceneNode, SceneStroke, ScenePage,
|
||||
};
|
||||
use openpencil_shell_core::Color;
|
||||
use op_editor_ui::Color;
|
||||
|
||||
use crate::editor_state_var_table;
|
||||
use crate::payload::{DocPayload, NodePayload, StrokePayload};
|
||||
|
|
@ -82,7 +82,7 @@ pub fn editor_state_to_layout_scene(state: &op_editor_core::EditorState) -> Layo
|
|||
/// wins over the node's authored colour, mirroring the canvas
|
||||
/// painter's `var_table.fill_for(id).or(node.fill)`.
|
||||
fn node_payload_to_scene(node: &NodePayload, var_table: &VariableTable) -> SceneNode {
|
||||
use openpencil_shell_core::{Point2D, Rect};
|
||||
use op_editor_ui::{Point2D, Rect};
|
||||
let node_id = op_editor_core::NodeId::new(node.id.clone());
|
||||
SceneNode {
|
||||
id: node.id.clone(),
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
//! Canonical `.op` (`PenDocument`) → `LayoutScene` loader / adapter.
|
||||
//!
|
||||
//! Extracted from `openpencil-desktop` so library crates (notably
|
||||
//! `openpencil-shell-native`) can reuse the conversion without
|
||||
//! Extracted from `op-host-desktop` so library crates (notably
|
||||
//! `op-host-native`) can reuse the conversion without
|
||||
//! depending on a binary crate.
|
||||
//!
|
||||
//! Three layers:
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
//! - [`payload`] holds the `DocPayload` serde DTOs + the strict /
|
||||
//! best-effort `load_canonical` parser.
|
||||
//! - [`layout_scene`] re-shapes a resolved `DocPayload` into the
|
||||
//! paint-only `openpencil_shell_core::layout_scene::LayoutScene`.
|
||||
//! paint-only `op_editor_ui::layout_scene::LayoutScene`.
|
||||
//!
|
||||
//! This crate is desktop-side and may depend on skia; it does NOT
|
||||
//! need to be wasm-clean. The `rfd` Save/Open dialogs + error
|
||||
|
|
@ -63,10 +63,10 @@ pub use variables::{var_table_from_payload, var_table_to_payload, VarTablePayloa
|
|||
/// any shell-core dependency to keep its `wasm32-unknown-unknown`
|
||||
/// invariant.
|
||||
///
|
||||
/// [`VariableTable`]: openpencil_shell_core::scene_vars::VariableTable
|
||||
/// [`VariableTable`]: op_editor_ui::scene_vars::VariableTable
|
||||
pub fn editor_state_var_table(
|
||||
state: &op_editor_core::EditorState,
|
||||
) -> openpencil_shell_core::scene_vars::VariableTable {
|
||||
) -> op_editor_ui::scene_vars::VariableTable {
|
||||
use op_editor_core::NodeId;
|
||||
// Persisted definitions + theme axes — `EditorState.doc` is a
|
||||
// `PenDocument`, so `build_var_table` harvests them directly.
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use openpencil_shell_core::scene_vars::{
|
||||
use op_editor_ui::scene_vars::{
|
||||
ThemeAxis, ThemedValue, Variable, VariableKind, VariableScalar, VariableTable,
|
||||
VariableValue,
|
||||
};
|
||||
|
|
@ -219,7 +219,7 @@ pub fn var_table_from_payload(p: &VarTablePayload) -> VariableTable {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use openpencil_shell_core::scene_vars::VariableValue;
|
||||
use op_editor_ui::scene_vars::VariableValue;
|
||||
|
||||
fn sample_table() -> VariableTable {
|
||||
let mut t = VariableTable::default();
|
||||
|
|
|
|||
|
|
@ -1,29 +0,0 @@
|
|||
[package]
|
||||
name = "openpencil-shell-core"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
description = "OpenPencil shell — re-export shim over op-editor-ui / op-editor-core / op-i18n (Phase 7 strangler reorg; dissolved in Task 7.3)"
|
||||
|
||||
[lib]
|
||||
name = "openpencil_shell_core"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
# v19 pivot: re-export Jian render/geometry/scene types (spec §5.2). The
|
||||
# `jian` module wraps jian-core; the crate root also re-exports
|
||||
# `jian_core::gesture` event types.
|
||||
jian-core = { path = "../../vendor/jian/crates/jian-core", version = "0.0.1" }
|
||||
|
||||
# Phase 3 strangler reorg: i18n extracted into op-i18n; re-exported as `i18n`.
|
||||
op-i18n = { path = "../op-i18n" }
|
||||
|
||||
# Phase 4 strangler reorg: the wasm-clean RenderBackend trait + facade
|
||||
# types live in op-editor-core; re-exported as `render_backend`.
|
||||
op-editor-core = { path = "../op-editor-core" }
|
||||
|
||||
# Phase 7 strangler reorg: the widget facade + theme + layout scene +
|
||||
# scene-var aggregation live in op-editor-ui; re-exported so the hosts
|
||||
# keep resolving `openpencil_shell_core::widgets` / `::theme` / etc.
|
||||
op-editor-ui = { path = "../op-editor-ui" }
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
//! Jian re-export module (spec v19 §2 / §1.2).
|
||||
//!
|
||||
//! `openpencil-shell-core` is a thin Jian wrapper — it re-exports
|
||||
//! `jian_core::render::{DrawOp, Paint, TextRun, …}` through the OP layer for
|
||||
//! shell-native's internal translation. geometry / scene types get a `Jian*`
|
||||
//! prefix to avoid clashing with OP's own `Rect` / `Color` (`render_backend.rs`).
|
||||
//!
|
||||
//! **Contract (spec §5.2.1, line 746)**: inside the NativeBackend impl, the
|
||||
//! v19 wrapper **must not let widget code see `jian_core::render::DrawOp`
|
||||
//! directly** — widgets only call OP `RenderBackend` methods. These re-exports
|
||||
//! are for **shell-native internal** translation, not for widget code.
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
// jian_core::render — DrawOp command buffer + Paint / TextRun / other draw descriptors
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
pub use jian_core::render::{
|
||||
BorderRadii, DrawOp, GradientStop, ImageSource, LinearGradient, Paint, PathCommand,
|
||||
RadialGradient, RenderCommand, ShadowSpec, StrokeOp, TextAlign, TextRun,
|
||||
};
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
// jian_core::geometry — `Jian*` prefix to avoid clashing with OP `Rect`
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Jian's axis-aligned rectangle (`euclid::Rect<f32>`).
|
||||
/// OP's own `crate::render_backend::Rect` is for the widget facade; shell-native
|
||||
/// translates OP `Rect` → `JianRect` internally (`origin/size` → `euclid::Rect::new`).
|
||||
pub type JianRect = jian_core::geometry::Rect;
|
||||
pub type Size = jian_core::geometry::Size;
|
||||
pub type JianPoint = jian_core::geometry::Point;
|
||||
pub type Affine2 = jian_core::geometry::Affine2;
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
// jian_core::scene — Color (packed u32) gets a `Jian*` prefix
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Jian's packed-RGBA color (`pub struct Color(pub u32)`); distinct from OP's
|
||||
/// `crate::render_backend::Color` (RGBA f32 quad). NativeBackend bit-packs when
|
||||
/// translating OP Color → JianColor.
|
||||
pub type JianColor = jian_core::scene::Color;
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
//! OpenPencil shell core — re-export shim.
|
||||
//!
|
||||
//! Per spec v19 §1.2 (FROZEN 2026-05-04): this crate must compile on
|
||||
//! wasm32-unknown-unknown. winit / accesskit_winit / skia-safe live in
|
||||
//! `openpencil-shell-native`; wasm-bindgen / web-sys / CanvasKit live in
|
||||
//! `openpencil-shell-web`.
|
||||
//!
|
||||
//! Phase 7 strangler reorg: the widget facade, theme, layout-resolved
|
||||
//! render scene, and design-variable aggregation were extracted into the
|
||||
//! `op-editor-ui` crate; i18n into `op-i18n`; the `RenderBackend` trait
|
||||
//! into `op-editor-core`; the MCP / codegen / figma / AI-chat modules
|
||||
//! into their own `op-*` crates. What remains here is a thin re-export
|
||||
//! shim so the hosts (`openpencil-shell-native` / `openpencil-shell-web`
|
||||
//! / `openpencil-desktop`) keep resolving `openpencil_shell_core::*`
|
||||
//! paths unchanged until the Task 7.3 host rename dissolves this crate.
|
||||
//!
|
||||
//! The [`jian`] module re-exports `jian_core::render::{DrawOp, Paint,
|
||||
//! TextRun, …}` + geometry/scene aliases for shell-native's internal
|
||||
//! translation (widget code never sees them).
|
||||
|
||||
pub mod jian;
|
||||
|
||||
// Phase 3 strangler reorg: i18n extracted into op-i18n. Re-exported as
|
||||
// `i18n` so `crate::i18n::translate` / `crate::i18n::Locale` resolve.
|
||||
pub use op_i18n as i18n;
|
||||
|
||||
// Phase 4 strangler reorg: the wasm-clean RenderBackend trait moved into
|
||||
// op-editor-core. Re-exported as `render_backend`.
|
||||
pub use op_editor_core::render_backend;
|
||||
|
||||
// Phase 7 strangler reorg: the widget facade + theme + layout scene +
|
||||
// scene-var aggregation moved into op-editor-ui. Re-exported so
|
||||
// `openpencil_shell_core::widgets` / `::theme` / `::layout_scene` /
|
||||
// `::layout_scene_hit` / `::scene_vars` paths still resolve.
|
||||
pub use op_editor_ui::{layout_scene, layout_scene_hit, scene_vars, theme, widgets};
|
||||
|
||||
// Re-export the primary API for upstream crates / widgets / tests.
|
||||
pub use op_editor_core::render_backend::{Color, Point2D, Rect, RenderBackend, TextLayout};
|
||||
pub use op_editor_ui::Theme;
|
||||
|
||||
/// Re-exports of Jian gesture / event types so shell consumers can use the
|
||||
/// canonical Jian types directly without an OP-specific translation layer.
|
||||
pub use jian_core::gesture::{
|
||||
FocusEvent, ImeEvent, ImeKind, KeyCode, KeyEvent, KeyLocation, KeyState, KeyValue, Modifiers,
|
||||
MouseButtons, NamedKey, PointerEvent, PointerId, PointerKind, PointerPhase, ScrollMode,
|
||||
WheelEvent,
|
||||
};
|
||||
|
|
@ -1,111 +0,0 @@
|
|||
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_with_all_w3c_fields() {
|
||||
let event = KeyEvent {
|
||||
key: KeyValue::Named(NamedKey::Enter),
|
||||
code: KeyCode::Enter,
|
||||
location: KeyLocation::Right,
|
||||
modifiers: Modifiers::SHIFT,
|
||||
state: KeyState::Pressed,
|
||||
repeat: true,
|
||||
is_composing: true,
|
||||
};
|
||||
let _: jian_core::gesture::KeyEvent = event.clone();
|
||||
// Round 2 Q5 fix: assert every W3C field reads back the value we set
|
||||
// so cross-crate type identity AND field-level binary compat are
|
||||
// both verified through the OP re-export path.
|
||||
assert_eq!(event.key, KeyValue::Named(NamedKey::Enter));
|
||||
assert_eq!(event.code, KeyCode::Enter);
|
||||
assert_eq!(event.location, KeyLocation::Right);
|
||||
assert!(event.modifiers.contains(Modifiers::SHIFT));
|
||||
assert_eq!(event.state, KeyState::Pressed);
|
||||
assert!(event.repeat);
|
||||
assert!(event.is_composing);
|
||||
}
|
||||
|
||||
#[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();
|
||||
assert_eq!(event.text, "你好");
|
||||
match event.kind {
|
||||
ImeKind::CompositionUpdate { selection } => assert_eq!(selection, Some(0..6)),
|
||||
_ => panic!("expected CompositionUpdate"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn focus_event_is_re_exported_from_jian_with_all_w3c_fields() {
|
||||
let event = FocusEvent {
|
||||
gained: false,
|
||||
node_id_hint: Some(11),
|
||||
related_node_id_hint: Some(7),
|
||||
};
|
||||
let _: jian_core::gesture::FocusEvent = event;
|
||||
// Round 2 Q5 fix: assert all three W3C fields, not just gained.
|
||||
assert!(!event.gained);
|
||||
assert_eq!(event.node_id_hint, Some(11));
|
||||
assert_eq!(event.related_node_id_hint, Some(7));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wheel_event_is_re_exported_from_jian_with_w3c_fields() {
|
||||
let mut event = WheelEvent::simple(
|
||||
jian_core::geometry::Point::new(10.0, 20.0),
|
||||
jian_core::geometry::Point::new(0.0, 120.0),
|
||||
);
|
||||
// Defaults from WheelEvent::simple
|
||||
assert_eq!(event.mode, ScrollMode::Pixel);
|
||||
assert_eq!(event.delta_z, 0.0);
|
||||
// Round 2 Q5 fix: assert mode + delta_z mutability + roundtrip
|
||||
// through the OP re-export path matches Jian's behavior.
|
||||
event.mode = ScrollMode::Line;
|
||||
event.delta_z = -3.0;
|
||||
assert_eq!(event.mode, ScrollMode::Line);
|
||||
assert_eq!(event.delta_z, -3.0);
|
||||
assert_eq!(event.delta.x, 0.0);
|
||||
assert_eq!(event.delta.y, 120.0);
|
||||
}
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
//! Task 1 Step 20-21: prove the Jian re-export wrapper compiles & is usable.
|
||||
//!
|
||||
//! Two anchor invariants for spec v19 §2 / §5.2:
|
||||
//! 1. `DrawOp::Rect` constructible **through the OP re-export path**
|
||||
//! (`openpencil_shell_core::jian::DrawOp` — used by shell-native's internal translation).
|
||||
//! 2. `TextLayout::single_run` produces **exactly one** `TextRun` (spec §5.2,
|
||||
//! confirming the explicit-field replacement for `..Default::default()` is semantically equivalent).
|
||||
|
||||
use openpencil_shell_core::jian::{DrawOp, JianRect, Paint};
|
||||
use openpencil_shell_core::render_backend::{Color, Point2D, TextLayout};
|
||||
|
||||
#[test]
|
||||
fn drawop_rect_constructible_via_re_export() {
|
||||
// Construct a Jian DrawOp::Rect via the OP re-export path — proves shell-core's
|
||||
// jian module exposes jian_core::render::{DrawOp, Paint}.
|
||||
let rect = JianRect::new(
|
||||
jian_core::geometry::Point::new(0.0, 0.0),
|
||||
jian_core::geometry::Size::new(100.0, 50.0),
|
||||
);
|
||||
let paint = Paint::solid(jian_core::scene::Color::rgb(255, 0, 0));
|
||||
let op = DrawOp::Rect { rect, paint };
|
||||
|
||||
match op {
|
||||
DrawOp::Rect { rect, .. } => {
|
||||
assert_eq!(rect.size.width, 100.0);
|
||||
assert_eq!(rect.size.height, 50.0);
|
||||
}
|
||||
_ => panic!("expected DrawOp::Rect"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn text_layout_single_run_creates_one_run() {
|
||||
// Explicit-field construction (TextRun has no Default impl) — proves the
|
||||
// spec §5.2 single_run path produces exactly one run with content /
|
||||
// font_family / font_size set from the caller's arguments.
|
||||
let layout = TextLayout::single_run(
|
||||
"Hello",
|
||||
"system-ui",
|
||||
16.0,
|
||||
jian_core::scene::Color::rgb(0, 0, 0),
|
||||
Point2D::new(10.0, 20.0),
|
||||
);
|
||||
|
||||
assert_eq!(layout.runs().len(), 1);
|
||||
|
||||
let run = &layout.runs()[0];
|
||||
assert_eq!(run.content, "Hello");
|
||||
assert_eq!(run.font_family, "system-ui");
|
||||
assert_eq!(run.font_size, 16.0);
|
||||
assert_eq!(run.font_weight, 400);
|
||||
assert_eq!(run.origin.x, 10.0);
|
||||
assert_eq!(run.origin.y, 20.0);
|
||||
assert_eq!(run.max_width, 0.0);
|
||||
assert_eq!(run.line_height, 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn text_layout_translated_offsets_origin() {
|
||||
// translated() adds offset to each run's origin; the original layout is unchanged.
|
||||
let layout = TextLayout::single_run(
|
||||
"World",
|
||||
"system-ui",
|
||||
14.0,
|
||||
jian_core::scene::Color::rgb(0, 0, 0),
|
||||
Point2D::new(5.0, 10.0),
|
||||
);
|
||||
let shifted = layout.translated(Point2D::new(100.0, 200.0));
|
||||
|
||||
assert_eq!(shifted.runs()[0].origin.x, 105.0);
|
||||
assert_eq!(shifted.runs()[0].origin.y, 210.0);
|
||||
|
||||
// Original layout untouched.
|
||||
assert_eq!(layout.runs()[0].origin.x, 5.0);
|
||||
assert_eq!(layout.runs()[0].origin.y, 10.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn op_color_constants_distinct() {
|
||||
// spec §5.2 names all six constants — RED/GREEN/BLUE/BLACK/WHITE/TRANSPARENT.
|
||||
assert_eq!(Color::RED.r, 1.0);
|
||||
assert_eq!(Color::GREEN.g, 1.0);
|
||||
assert_eq!(Color::BLUE.b, 1.0);
|
||||
assert_eq!(Color::BLACK.r, 0.0);
|
||||
assert_eq!(Color::WHITE.r, 1.0);
|
||||
assert_eq!(Color::TRANSPARENT.a, 0.0);
|
||||
}
|
||||
Loading…
Reference in a new issue