新增自动切分素材构造器与 design_doc 共用夹具

- SpriteAsset 新增 path 访问器与 from_registered_image,镜像前端 prepareSpriteAssetBatch 的空名称、无边框、像素比 1 口径
- 新增 design_doc/test_support.rs,集中 State、节点、素材的最小构造夹具
- recognize 单测改用共用夹具,删除本地重复的 image/state_with/root 构造
This commit is contained in:
2026-09-23 19:41:01 +08:00
parent b34f1c9b3a
commit b1a356aa0d
4 changed files with 118 additions and 58 deletions
@@ -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,
@@ -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]
@@ -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")
}
@@ -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<f32>,
pixels_per_unit: StrictlyPositiveFinite,
) -> Result<Self, ComponentValueError> {
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<f32> {
self.pixel_size
}