use std::collections::BTreeMap;
use zenith_core::{
Diagnostic, Dimension, Document, GroupNode, Node, PatternLayout, PropertyValue, dim_to_px,
pattern_positions,
};
use super::geometry::node_geometry_mut;
use super::structure::node_set_id_any;
use super::{find_node_any_mut, px, record_affected};
fn resolve_positive_px(pv: &Option<PropertyValue>) -> Option<f64> {
match pv {
Some(PropertyValue::Dimension(d)) => dim_to_px(d.value, &d.unit),
Some(PropertyValue::TokenRef(_)) => None,
Some(PropertyValue::Literal(_)) => None,
Some(PropertyValue::DataRef(_)) => None,
None => None,
}
.filter(|&v| v.is_finite() && v > 0.0)
}
fn resolve_positive_px_dim(dim: &Option<Dimension>) -> Option<f64> {
dim.as_ref()
.and_then(|d| dim_to_px(d.value, &d.unit))
.filter(|&v| v.is_finite() && v > 0.0)
}
pub(super) fn apply_detach_pattern(
node_id: &str,
doc: &mut Document,
diagnostics: &mut Vec<Diagnostic>,
affected: &mut Vec<String>,
) {
let Some(slot) = find_node_any_mut(doc, node_id) else {
diagnostics.push(Diagnostic::error(
"tx.unknown_node",
format!("detach_pattern: node {node_id:?} not found in document"),
None,
Some(node_id.to_owned()),
));
return;
};
let Node::Pattern(p) = &*slot else {
diagnostics.push(Diagnostic::error(
"tx.not_a_pattern",
format!("detach_pattern: node {node_id:?} is not a pattern"),
None,
Some(node_id.to_owned()),
));
return;
};
let (Some(bw), Some(bh)) = (resolve_positive_px(&p.w), resolve_positive_px(&p.h)) else {
diagnostics.push(Diagnostic::error(
"tx.pattern_unresolved_bounds",
format!(
"detach_pattern: pattern {node_id:?} has unresolved or non-positive \
bounds; both w and h must resolve to a positive pixel size"
),
None,
Some(node_id.to_owned()),
));
return;
};
let spacing = resolve_positive_px_dim(&p.spacing);
let seed = p.seed.unwrap_or(0);
let jitter = p.jitter.unwrap_or(0.0);
let count = p.count;
let positions = pattern_positions(PatternLayout {
kind: &p.kind,
bounds_w: bw,
bounds_h: bh,
spacing,
count,
seed,
jitter,
});
if positions.is_empty() {
diagnostics.push(Diagnostic::error(
"tx.pattern_not_expandable",
format!(
"detach_pattern: pattern {node_id:?} expands to no instances; its \
kind may be unknown or a required parameter is missing"
),
None,
Some(node_id.to_owned()),
));
return;
}
let mut children: Vec<Node> = Vec::with_capacity(positions.len());
for (i, (ox, oy)) in positions.iter().enumerate() {
let mut child = (*p.motif).clone();
node_set_id_any(&mut child, format!("{node_id}.{i}"));
if let Some((cx, cy, _, _)) = node_geometry_mut(&mut child) {
*cx = Some(PropertyValue::Dimension(px(*ox)));
*cy = Some(PropertyValue::Dimension(px(*oy)));
}
children.push(child);
}
let group = GroupNode {
id: p.id.clone(),
name: p.name.clone(),
role: p.role.clone(),
x: p.x.clone(),
y: p.y.clone(),
w: p.w.clone(),
h: p.h.clone(),
opacity: None,
visible: None,
locked: None,
rotate: None,
blend_mode: None,
shadow: None,
filter: None,
mask: None,
blur: None,
style: None,
semantic_role: None,
intensity: None,
layer_priority: None,
children,
protected_regions: Vec::new(),
editable_param_ids: Vec::new(),
anchor: None,
anchor_zone: None,
anchor_sibling: None,
anchor_edge: None,
anchor_gap: None,
anchor_parent: None,
source_span: None,
unknown_props: BTreeMap::new(),
};
*slot = Node::Group(group);
record_affected(node_id, affected);
}