WIP: UI编辑器自动分图层切图标 #304
@@ -16,6 +16,7 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::ui_editor::component::image::{ImageComponent, ImageType};
|
||||
use crate::ui_editor::component::Component;
|
||||
use crate::ui_editor::component::text::TextComponent;
|
||||
use crate::ui_editor::layout::children_display_mode::ChildrenDisplayMode;
|
||||
use crate::ui_editor::layout::control_layout::ControlLayout;
|
||||
use crate::ui_editor::layout::node::Node;
|
||||
@@ -106,6 +107,69 @@ mod tests {
|
||||
assert_eq!(tree.root.id.as_str(), "root-image");
|
||||
assert!(tree.root_extractable);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn construction_attaches_text_mask_to_nearest_unbound_image() {
|
||||
let image = Component::Image(ImageComponent {
|
||||
target_graphic: None,
|
||||
image_type: ImageType::Simple {
|
||||
preserve_aspect: false,
|
||||
},
|
||||
});
|
||||
let text = Component::Text(TextComponent::new("按钮"));
|
||||
let root = node(
|
||||
"root",
|
||||
vec![],
|
||||
vec![node(
|
||||
"outer-image",
|
||||
vec![image.clone()],
|
||||
vec![node("text", vec![text.clone()], vec![])],
|
||||
)],
|
||||
);
|
||||
let result = construct_separation_state(&state(root));
|
||||
let outer = &result.trees[0].root.children[0];
|
||||
assert_eq!(outer.id.as_str(), "outer-image");
|
||||
assert_eq!(outer.text_mask_areas.len(), 1);
|
||||
assert!(outer.children.is_empty());
|
||||
|
||||
let nested_root = node(
|
||||
"root",
|
||||
vec![],
|
||||
vec![node(
|
||||
"outer-image",
|
||||
vec![image.clone()],
|
||||
vec![node(
|
||||
"inner-image",
|
||||
vec![image],
|
||||
vec![node("text", vec![text], vec![])],
|
||||
)],
|
||||
)],
|
||||
);
|
||||
let nested = construct_separation_state(&state(nested_root));
|
||||
let inner = &nested.trees[0].root.children[0].children[0];
|
||||
assert_eq!(inner.text_mask_areas.len(), 1);
|
||||
assert!(nested.trees[0].root.children[0].text_mask_areas.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn root_image_receives_text_mask() {
|
||||
let root = node(
|
||||
"root-image",
|
||||
vec![Component::Image(ImageComponent {
|
||||
target_graphic: None,
|
||||
image_type: ImageType::Simple {
|
||||
preserve_aspect: false,
|
||||
},
|
||||
})],
|
||||
vec![node(
|
||||
"text",
|
||||
vec![Component::Text(TextComponent::new("标题"))],
|
||||
vec![],
|
||||
)],
|
||||
);
|
||||
let result = construct_separation_state(&state(root));
|
||||
assert_eq!(result.trees[0].root.text_mask_areas.len(), 1);
|
||||
}
|
||||
#[test]
|
||||
fn binding_validation_requires_exact_batch_coverage() {
|
||||
let node = SeparationNode {
|
||||
|
||||
@@ -3,6 +3,7 @@ use crate::ui_editor::component::{image::ImageComponent, Component};
|
||||
use crate::ui_editor::layout::node::Node;
|
||||
use crate::ui_editor::state::State;
|
||||
use crate::ui_editor::utils::NodeId;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
|
||||
fn is_unbound_image(node: &Node) -> bool {
|
||||
@@ -17,6 +18,18 @@ fn is_unbound_image(node: &Node) -> bool {
|
||||
})
|
||||
}
|
||||
|
||||
fn has_image_component(node: &Node) -> bool {
|
||||
node.components
|
||||
.iter()
|
||||
.any(|component| matches!(component, Component::Image(_)))
|
||||
}
|
||||
|
||||
fn has_text_component(node: &Node) -> bool {
|
||||
node.components
|
||||
.iter()
|
||||
.any(|component| matches!(component, Component::Text(_)))
|
||||
}
|
||||
|
||||
fn node_pixel_rect(
|
||||
node: &Node,
|
||||
parent: &crate::ui_editor::layout::dimension::UIRect,
|
||||
@@ -47,11 +60,12 @@ fn collect_todo_nodes(
|
||||
parent: &crate::ui_editor::layout::dimension::UIRect,
|
||||
ppu: f32,
|
||||
output: &mut Vec<SeparationNode>,
|
||||
text_masks: &mut HashMap<NodeId, Vec<TextMaskArea>>,
|
||||
) {
|
||||
let rect = node.layout.transform.resolve(parent);
|
||||
let mut children = Vec::new();
|
||||
for child in &node.children {
|
||||
collect_todo_nodes(child, &rect, ppu, &mut children);
|
||||
collect_todo_nodes(child, &rect, ppu, &mut children, text_masks);
|
||||
}
|
||||
if is_unbound_image(node) {
|
||||
let (x, y, w, h) = node_pixel_rect(node, parent, ppu);
|
||||
@@ -65,6 +79,7 @@ fn collect_todo_nodes(
|
||||
description: node_description(node),
|
||||
rework_notes: Vec::new(),
|
||||
},
|
||||
text_mask_areas: text_masks.remove(&node.id).unwrap_or_default(),
|
||||
children,
|
||||
rework_count: 0,
|
||||
});
|
||||
@@ -73,6 +88,36 @@ fn collect_todo_nodes(
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_text_masks(
|
||||
node: &Node,
|
||||
parent: &crate::ui_editor::layout::dimension::UIRect,
|
||||
ppu: f32,
|
||||
nearest_image: Option<NodeId>,
|
||||
output: &mut HashMap<NodeId, Vec<TextMaskArea>>,
|
||||
) {
|
||||
let node_is_image = is_unbound_image(node);
|
||||
let nearest_image = if node_is_image {
|
||||
Some(node.id.clone())
|
||||
} else {
|
||||
nearest_image
|
||||
};
|
||||
if has_text_component(node) && !has_image_component(node) {
|
||||
if let Some(image_id) = nearest_image.clone() {
|
||||
let (x, y, w, h) = node_pixel_rect(node, parent, ppu);
|
||||
output.entry(image_id).or_default().push(TextMaskArea {
|
||||
global_pos_x_px: x,
|
||||
global_pos_y_px: y,
|
||||
width_px: w,
|
||||
height_px: h,
|
||||
});
|
||||
}
|
||||
}
|
||||
let rect = node.layout.transform.resolve(parent);
|
||||
for child in &node.children {
|
||||
collect_text_masks(child, &rect, ppu, nearest_image.clone(), output);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn construct_separation_state(state: &State) -> SeparationState {
|
||||
let trees = state
|
||||
.ui_trees
|
||||
@@ -83,9 +128,11 @@ pub fn construct_separation_state(state: &State) -> SeparationState {
|
||||
let size = image.pixel_size / ppu;
|
||||
let root_rect =
|
||||
crate::ui_editor::layout::dimension::UIRect::new(nalgebra::Point2::origin(), size);
|
||||
let mut text_masks = HashMap::new();
|
||||
collect_text_masks(&tree.root, &root_rect, ppu, None, &mut text_masks);
|
||||
let mut children = Vec::new();
|
||||
for child in &tree.root.children {
|
||||
collect_todo_nodes(child, &root_rect, ppu, &mut children);
|
||||
collect_todo_nodes(child, &root_rect, ppu, &mut children, &mut text_masks);
|
||||
}
|
||||
let root_extractable = is_unbound_image(&tree.root);
|
||||
if !root_extractable && children.is_empty() {
|
||||
@@ -104,6 +151,7 @@ pub fn construct_separation_state(state: &State) -> SeparationState {
|
||||
description: node_description(&tree.root),
|
||||
rework_notes: Vec::new(),
|
||||
},
|
||||
text_mask_areas: text_masks.remove(&tree.root.id).unwrap_or_default(),
|
||||
children,
|
||||
rework_count: 0,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user