diff --git a/apps/ai-game-creator-shell/src-tauri/src/ui_editor/design_doc/mod.rs b/apps/ai-game-creator-shell/src-tauri/src/ui_editor/design_doc/mod.rs index 613eaa7be..b44383e96 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/ui_editor/design_doc/mod.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/ui_editor/design_doc/mod.rs @@ -2,6 +2,9 @@ mod checkpoint; mod creation; mod steps; +#[cfg(test)] +mod test_support; + pub(crate) use creation::{ create_ui_design_doc_from_images, next_ui_design_path, CreateUiDesignDocFromImagesInput, UiDesignDocCreated, UiDesignImageReference, diff --git a/apps/ai-game-creator-shell/src-tauri/src/ui_editor/design_doc/steps/recognize.rs b/apps/ai-game-creator-shell/src-tauri/src/ui_editor/design_doc/steps/recognize.rs index 8fa039111..754a7d567 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/ui_editor/design_doc/steps/recognize.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/ui_editor/design_doc/steps/recognize.rs @@ -74,57 +74,8 @@ fn tree_size(state: &State, tree_id: &UIDesignImageId) -> Result<[f32; 2], Strin #[cfg(test)] mod tests { use super::*; - use crate::ui_editor::layout::children_display_mode::ChildrenDisplayMode; - use crate::ui_editor::layout::control_layout::ControlLayout; - use crate::ui_editor::layout::node::{Node, NodeMetadata, NodeSource, StageStatus}; - use crate::ui_editor::resource::ui_design_image::UIDesignImage; - use nalgebra::Vector2; - use std::collections::HashMap; - use typed_floats::tf32::StrictlyPositiveFinite; - - fn image(width: f32, height: f32) -> UIDesignImage { - UIDesignImage { - path: format!("assets/{width}x{height}.png"), - pixel_size: Vector2::new(width, height), - pixels_per_unit: StrictlyPositiveFinite::new(1.0).expect("pixels per unit"), - } - } - - fn state_with(images: &[(&str, f32, f32)]) -> State { - let mut ui_design_images = HashMap::new(); - for (id, width, height) in images { - ui_design_images.insert( - UIDesignImageId::new(id.to_string()).expect("image id"), - image(*width, *height), - ); - } - State { - ui_trees: Vec::new(), - ui_design_images, - sprite_assets: HashMap::new(), - font_assets: HashMap::new(), - } - } - - fn root(id: &str) -> Node { - Node { - id: crate::ui_editor::utils::NodeId::new(id.to_string()).expect("node id"), - layout: ControlLayout::default(), - metadata: NodeMetadata { - name: "页面根节点".to_string(), - description: String::new(), - layout_status: StageStatus::NoProblem, - component_status: StageStatus::NoProblem, - allow_llm_edit_layout: true, - allow_llm_edit_component: true, - source: NodeSource::System, - }, - component: None, - children_display_mode: ChildrenDisplayMode::Stack, - children: Vec::new(), - offset: NodeOffset::default(), - } - } + use crate::ui_editor::design_doc::test_support::{node, state_with}; + use crate::ui_editor::state::UITree; fn dto(trees: &[(&str, &str)]) -> RecognitionDTO { RecognitionDTO { @@ -132,7 +83,7 @@ mod tests { .iter() .map(|(tree_id, node_id)| UITree { src_ui_design: UIDesignImageId::new(tree_id.to_string()).expect("tree id"), - root: root(node_id), + root: node(node_id), }) .collect(), } @@ -159,12 +110,7 @@ mod tests { apply_recognition(&mut state, &dto(&[("page-a", "root-a")])).expect("first apply"); apply_recognition(&mut state, &dto(&[("page-a", "root-b")])).expect("second apply"); assert_eq!(state.ui_trees.len(), 1); - assert_eq!( - state.ui_trees[0].root.id.as_str(), - crate::ui_editor::utils::NodeId::new("root-b".to_string()) - .expect("node id") - .as_str() - ); + assert_eq!(state.ui_trees[0].root.id.as_str(), "root-b"); } #[test] diff --git a/apps/ai-game-creator-shell/src-tauri/src/ui_editor/design_doc/test_support.rs b/apps/ai-game-creator-shell/src-tauri/src/ui_editor/design_doc/test_support.rs new file mode 100644 index 000000000..e8a529705 --- /dev/null +++ b/apps/ai-game-creator-shell/src-tauri/src/ui_editor/design_doc/test_support.rs @@ -0,0 +1,94 @@ +//! design_doc 单测共用夹具:只提供最小可用的 State / 节点 / 素材构造。 + +use crate::ui_editor::component::image::ImageComponent; +use crate::ui_editor::component::Component; +use crate::ui_editor::layout::children_display_mode::ChildrenDisplayMode; +use crate::ui_editor::layout::control_layout::ControlLayout; +use crate::ui_editor::layout::node::{Node, NodeMetadata, NodeSource, StageStatus}; +use crate::ui_editor::layout::offset::NodeOffset; +use crate::ui_editor::resource::sprite::SpriteAsset; +use crate::ui_editor::resource::ui_design_image::UIDesignImage; +use crate::ui_editor::state::{State, UITree}; +use crate::ui_editor::utils::{NodeId, SpriteAssetId, UIDesignImageId}; +use nalgebra::Vector2; +use std::collections::HashMap; +use typed_floats::tf32::StrictlyPositiveFinite; + +pub(crate) fn pixels_per_unit() -> StrictlyPositiveFinite { + StrictlyPositiveFinite::new(1.0).expect("pixels per unit") +} + +pub(crate) fn image(width: f32, height: f32) -> UIDesignImage { + UIDesignImage { + path: format!("assets/{width}x{height}.png"), + pixel_size: Vector2::new(width, height), + pixels_per_unit: pixels_per_unit(), + } +} + +pub(crate) fn state_with(images: &[(&str, f32, f32)]) -> State { + let mut ui_design_images = HashMap::new(); + for (id, width, height) in images { + ui_design_images.insert( + UIDesignImageId::new(id.to_string()).expect("image id"), + image(*width, *height), + ); + } + State { + ui_trees: Vec::new(), + ui_design_images, + sprite_assets: HashMap::new(), + font_assets: HashMap::new(), + } +} + +pub(crate) fn node_metadata() -> NodeMetadata { + NodeMetadata { + name: "页面根节点".to_string(), + description: String::new(), + layout_status: StageStatus::NoProblem, + component_status: StageStatus::NoProblem, + allow_llm_edit_layout: true, + allow_llm_edit_component: true, + source: NodeSource::System, + } +} + +pub(crate) fn node(id: &str) -> Node { + Node { + id: NodeId::new(id.to_string()).expect("node id"), + layout: ControlLayout::default(), + metadata: node_metadata(), + component: None, + children_display_mode: ChildrenDisplayMode::Stack, + children: Vec::new(), + offset: NodeOffset::default(), + } +} + +/// 带 Image 组件的节点,用来覆盖切分回填与问题状态。 +pub(crate) fn image_node(id: &str, target_graphic: Option<&str>) -> Node { + let mut component = ImageComponent::new(); + component.target_graphic = target_graphic + .map(|sprite_id| SpriteAssetId::new(sprite_id.to_string()).expect("sprite id")); + let mut node = node(id); + node.component = Some(Component::Image(component)); + node +} + +pub(crate) fn tree(tree_id: &str, root: Node) -> UITree { + UITree { + src_ui_design: UIDesignImageId::new(tree_id.to_string()).expect("tree id"), + root, + } +} + +pub(crate) fn sprite(id: &str, path: &str) -> SpriteAsset { + SpriteAsset::from_registered_image( + SpriteAssetId::new(id.to_string()).expect("sprite id"), + path.to_string(), + Vector2::new(32.0, 24.0), + pixels_per_unit(), + ) + .expect("sprite asset") +} diff --git a/apps/ai-game-creator-shell/src-tauri/src/ui_editor/resource/sprite.rs b/apps/ai-game-creator-shell/src-tauri/src/ui_editor/resource/sprite.rs index 498fb0437..408a47379 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/ui_editor/resource/sprite.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/ui_editor/resource/sprite.rs @@ -119,6 +119,23 @@ impl SpriteAsset { &self.asset_id } + pub fn path(&self) -> &str { + &self.path + } + + /// 用一张已登记图片建自动切分素材:名称与类型留给人工填写,路径取资源路径, + /// 像素比固定为 1,九宫格为无边框。镜像前端 `prepareSpriteAssetBatch`。 + pub(crate) fn from_registered_image( + asset_id: SpriteAssetId, + path: String, + pixel_size: Vector2, + pixels_per_unit: StrictlyPositiveFinite, + ) -> Result { + let mut sprite = Self::new(asset_id, pixel_size, pixels_per_unit, SpriteBorder::NONE)?; + sprite.path = path; + Ok(sprite) + } + pub fn pixel_size(&self) -> Vector2 { self.pixel_size }