use std::collections::BTreeMap;
use zenith_core::{
Diagnostic, PatternLayout, PatternNode, ResolvedToken, Severity, dim_to_px, pattern_positions,
};
use crate::ir::{Paint, SceneCommand};
use super::NodeCtx;
use super::RenderCtx;
use super::anchor::AnchorMap;
use super::compile_node;
use super::paint::{apply_gradient_opacity, resolve_property_color, resolve_property_gradient};
use super::util::{
AxisTarget, resolve_anchored_axis, resolve_geometry_px, resolve_property_dimension_px,
};
pub(in crate::compile) fn compile_pattern(
pattern: &PatternNode,
cx: NodeCtx,
commands: &mut Vec<SceneCommand>,
diagnostics: &mut Vec<Diagnostic>,
ctx: RenderCtx,
) -> f64 {
if pattern.visible == Some(false) {
return 0.0;
}
let Some((bx, by, bw, bh)) = resolve_bounds(pattern, cx.anchors, cx.resolved) else {
return 0.0;
};
let mut scratch_cmds: Vec<SceneCommand> = Vec::new();
let mut scratch_diags: Vec<Diagnostic> = Vec::new();
let probe_ctx = RenderCtx {
dx: ctx.dx + bx,
dy: ctx.dy + by,
..ctx
};
compile_node(
&pattern.motif,
cx,
&mut scratch_cmds,
&mut scratch_diags,
&mut Vec::new(),
probe_ctx,
);
if scratch_diags.iter().any(|d| d.severity == Severity::Error) {
diagnostics.extend(scratch_diags);
return 0.0;
}
diagnostics.extend(scratch_diags);
emit_background(pattern, cx, commands, diagnostics, ctx, (bx, by, bw, bh));
commands.push(SceneCommand::PushClip {
x: ctx.dx + bx,
y: ctx.dy + by,
w: bw,
h: bh,
});
let seed = pattern.seed.unwrap_or(0);
let spacing = pattern
.spacing
.as_ref()
.and_then(|d| dim_to_px(d.value, &d.unit));
let layout = PatternLayout {
kind: pattern.kind.as_str(),
bounds_w: bw,
bounds_h: bh,
spacing,
count: pattern.count,
seed,
jitter: pattern.jitter.unwrap_or(0.0),
};
for (ox, oy) in pattern_positions(layout) {
emit_instance(pattern, cx, commands, ctx, bx + ox, by + oy);
}
commands.push(SceneCommand::PopClip);
0.0
}
fn emit_background(
pattern: &PatternNode,
cx: NodeCtx,
commands: &mut Vec<SceneCommand>,
diagnostics: &mut Vec<Diagnostic>,
ctx: RenderCtx,
box_local: (f64, f64, f64, f64),
) {
let (bx, by, bw, bh) = box_local;
let x = ctx.dx + bx;
let y = ctx.dy + by;
let color_op = pattern.opacity.unwrap_or(1.0).clamp(0.0, 1.0) * ctx.opacity;
let radius = resolve_property_dimension_px(pattern.radius.as_ref(), cx.resolved, 0.0);
let is_rounded = radius > 0.0;
let radii: Option<[f64; 4]> = None;
if let Some(fill_prop) = pattern.fill.as_ref() {
if let Some(mut gradient) = resolve_property_gradient(fill_prop, cx.resolved, &pattern.id) {
apply_gradient_opacity(&mut gradient, color_op, 1.0);
let paint = Paint::Gradient(gradient);
if is_rounded {
commands.push(SceneCommand::FillRoundedRect {
x,
y,
w: bw,
h: bh,
radius,
radii,
paint,
});
} else {
commands.push(SceneCommand::FillRect {
x,
y,
w: bw,
h: bh,
paint,
});
}
} else if let Some(mut color) =
resolve_property_color(fill_prop, cx.resolved, diagnostics, &pattern.id)
{
color.a = (color.a as f64 * color_op).round() as u8;
let paint = Paint::solid(color);
if is_rounded {
commands.push(SceneCommand::FillRoundedRect {
x,
y,
w: bw,
h: bh,
radius,
radii,
paint,
});
} else {
commands.push(SceneCommand::FillRect {
x,
y,
w: bw,
h: bh,
paint,
});
}
}
}
if let Some(stroke_prop) = pattern.stroke.as_ref()
&& let Some(mut color) =
resolve_property_color(stroke_prop, cx.resolved, diagnostics, &pattern.id)
{
color.a = (color.a as f64 * color_op).round() as u8;
let stroke_width =
resolve_property_dimension_px(pattern.stroke_width.as_ref(), cx.resolved, 1.0);
if is_rounded {
commands.push(SceneCommand::StrokeRoundedRect {
x,
y,
w: bw,
h: bh,
radius,
radii,
color,
stroke_width,
stroke_dash: None,
stroke_gap: None,
stroke_linecap: None,
});
} else {
commands.push(SceneCommand::StrokeRect {
x,
y,
w: bw,
h: bh,
color,
stroke_width,
stroke_dash: None,
stroke_gap: None,
stroke_linecap: None,
});
}
}
}
fn resolve_bounds(
pattern: &PatternNode,
anchors: &AnchorMap,
resolved: &BTreeMap<String, ResolvedToken>,
) -> Option<(f64, f64, f64, f64)> {
let bw = resolve_geometry_px(pattern.w.as_ref(), resolved)?;
let bh = resolve_geometry_px(pattern.h.as_ref(), resolved)?;
if bw <= 0.0 || bh <= 0.0 {
return None;
}
let anchor_xy = anchors.get(&pattern.id).copied();
let mut sink: Vec<Diagnostic> = Vec::new();
let bx = resolve_anchored_axis(
AxisTarget {
kind: "pattern",
node_id: &pattern.id,
axis: "x",
},
pattern.x.as_ref(),
resolved,
anchor_xy.map(|(ax, _)| ax),
pattern.source_span,
&mut sink,
)
.unwrap_or(0.0);
let by = resolve_anchored_axis(
AxisTarget {
kind: "pattern",
node_id: &pattern.id,
axis: "y",
},
pattern.y.as_ref(),
resolved,
anchor_xy.map(|(_, ay)| ay),
pattern.source_span,
&mut sink,
)
.unwrap_or(0.0);
Some((bx, by, bw, bh))
}
fn emit_instance(
pattern: &PatternNode,
cx: NodeCtx,
commands: &mut Vec<SceneCommand>,
ctx: RenderCtx,
ox: f64,
oy: f64,
) {
let inst_ctx = RenderCtx {
dx: ctx.dx + ox,
dy: ctx.dy + oy,
..ctx
};
let mut throwaway: Vec<Diagnostic> = Vec::new();
compile_node(
&pattern.motif,
cx,
commands,
&mut throwaway,
&mut Vec::new(),
inst_ctx,
);
}