///|
/// A small composition graph for reusable animation scenes.
pub(all) struct AnimationNode {
name : String
animation : Animation
opacity : Byte
enabled : Bool
} derive(Debug)
///|
pub fn AnimationNode::new(
name : String,
animation : Animation,
) -> AnimationNode {
{ name, animation, opacity: 255, enabled: true }
}
///|
pub fn AnimationNode::name(self : AnimationNode) -> String {
self.name
}
///|
pub fn AnimationNode::animation(self : AnimationNode) -> Animation {
self.animation
}
///|
pub fn AnimationNode::opacity(
self : AnimationNode,
value : Byte,
) -> AnimationNode {
{ ..self, opacity: value }
}
///|
pub fn AnimationNode::enabled(
self : AnimationNode,
value : Bool,
) -> AnimationNode {
{ ..self, enabled: value }
}
///|
pub(all) struct AnimationGraph {
width : Int
height : Int
nodes : Array[AnimationNode]
background : Rgba
} derive(Debug)
///|
pub fn AnimationGraph::new(width : Int, height : Int) -> AnimationGraph {
{ width, height, nodes: [], background: rgba(0, 0, 0, 0) }
}
///|
pub fn AnimationGraph::background(
self : AnimationGraph,
color : Rgba,
) -> AnimationGraph {
{ ..self, background: color }
}
///|
pub fn AnimationGraph::add(
self : AnimationGraph,
node : AnimationNode,
) -> AnimationGraph {
self.nodes.push(node)
self
}
///|
pub fn AnimationGraph::nodes(self : AnimationGraph) -> ArrayView[AnimationNode] {
self.nodes[:]
}
///|
pub fn AnimationGraph::frame_count(self : AnimationGraph) -> Int {
let mut count = 0
for node in self.nodes {
if node.enabled && node.animation.frame_count() > count {
count = node.animation.frame_count()
}
}
count
}
///|
/// Render the graph by compositing each enabled node at a cyclic frame index.
pub fn AnimationGraph::render(self : AnimationGraph) -> Animation {
let result = Animation::new(width=self.width, height=self.height).with_background(
background=self.background,
)
let count = self.frame_count()
for index in 0.. 0 {
let frame = node.animation.frames[index % node.animation.frame_count()]
let positioned = frame.canvas().scale_alpha(node.opacity)
current = current.overlay(positioned, left=0, top=0)
if frame.delay_cs() > delay {
delay = frame.delay_cs()
}
}
}
ignore(result.add_frame(Frame::new(canvas=current, delay_cs=delay)))
}
result
}
///|
/// Replace one named node while preserving graph order.
pub fn AnimationGraph::replace(
self : AnimationGraph,
name : String,
node : AnimationNode,
) -> AnimationGraph {
let result : Array[AnimationNode] = []
let mut replaced = false
for existing in self.nodes {
if existing.name() == name {
result.push(node)
replaced = true
} else {
result.push(existing)
}
}
if !replaced {
result.push(node)
}
{ ..self, nodes: result }
}
///|
/// Remove disabled nodes and empty animations before rendering.
pub fn AnimationGraph::compact(self : AnimationGraph) -> AnimationGraph {
let result : Array[AnimationNode] = []
for node in self.nodes {
if node.enabled && node.animation.frame_count() > 0 {
result.push(node)
}
}
{ ..self, nodes: result }
}
///|
/// Return a stable list of node names for diagnostics.
pub fn AnimationGraph::node_names(self : AnimationGraph) -> Array[String] {
let result : Array[String] = []
for node in self.nodes {
result.push(node.name())
}
result
}