use std::rc::Rc;
use svg::{self, node::element as svg_element};
use crate::{
color::Color,
draw::{Drawable, LayeredOutput, RenderLayer, StrokeDefinition},
geometry::{Bounds, Point, Size},
};
#[derive(Debug, Clone)]
pub struct ActivationBoxDefinition {
width: f32,
nesting_offset: f32,
fill_color: Color,
stroke: Rc<StrokeDefinition>,
}
impl ActivationBoxDefinition {
pub fn new() -> Self {
Self::default()
}
pub fn set_width(&mut self, width: f32) {
self.width = width;
}
pub fn set_nesting_offset(&mut self, offset: f32) {
self.nesting_offset = offset;
}
pub fn set_fill_color(&mut self, color: Color) {
self.fill_color = color;
}
fn width(&self) -> f32 {
self.width
}
fn nesting_offset(&self) -> f32 {
self.nesting_offset
}
fn fill_color(&self) -> Color {
self.fill_color
}
pub fn stroke(&self) -> &Rc<StrokeDefinition> {
&self.stroke
}
pub fn set_stroke(&mut self, stroke: Rc<StrokeDefinition>) {
self.stroke = stroke;
}
}
impl Default for ActivationBoxDefinition {
fn default() -> Self {
Self {
width: 8.0,
nesting_offset: 4.0,
fill_color: Color::new("white").expect("Invalid default fill color"),
stroke: Rc::new(StrokeDefinition::default()),
}
}
}
#[derive(Debug, Clone)]
pub struct ActivationBox {
definition: Rc<ActivationBoxDefinition>,
height: f32,
nesting_level: u32,
}
impl ActivationBox {
pub fn new(definition: Rc<ActivationBoxDefinition>, height: f32, nesting_level: u32) -> Self {
Self {
definition,
height,
nesting_level,
}
}
pub fn height(&self) -> f32 {
self.height
}
pub fn nesting_level(&self) -> u32 {
self.nesting_level
}
pub fn calculate_bounds(&self, position: Point) -> Bounds {
let def = self.definition();
let nesting_offset = self.nesting_level as f32 * def.nesting_offset();
let adjusted_position = position.with_x(position.x() + nesting_offset);
let size = self.size();
adjusted_position.to_bounds(size)
}
fn definition(&self) -> &ActivationBoxDefinition {
&self.definition
}
}
impl Drawable for ActivationBox {
fn render_to_layers(&self, position: Point) -> LayeredOutput {
let mut output = LayeredOutput::new();
let def = self.definition();
let bounds = self.calculate_bounds(position);
let top_left = bounds.min_point();
let activation_rect = svg_element::Rectangle::new()
.set("x", top_left.x())
.set("y", top_left.y())
.set("width", bounds.width())
.set("height", bounds.height())
.set("fill", def.fill_color().to_string())
.set("fill-opacity", def.fill_color().alpha());
let activation_rect = crate::apply_stroke!(activation_rect, def.stroke());
output.add_to_layer(RenderLayer::Activation, Box::new(activation_rect));
output
}
fn size(&self) -> Size {
Size::new(self.definition.width(), self.height)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::geometry::Point;
#[test]
fn test_activation_box_definition_custom_values() {
let mut definition = ActivationBoxDefinition::new();
definition.set_width(12.0);
definition.set_nesting_offset(6.0);
definition.set_fill_color(Color::new("red").unwrap());
assert_eq!(definition.width(), 12.0);
assert_eq!(definition.nesting_offset(), 6.0);
assert_eq!(definition.fill_color().to_string(), "red");
assert_eq!(definition.stroke().color().to_string(), "black");
assert_eq!(definition.stroke().width(), 1.0);
}
#[test]
fn test_activation_box_creation() {
let definition = ActivationBoxDefinition::default();
let height = 50.0;
let nesting_level = 2;
let activation_box = ActivationBox::new(Rc::new(definition), height, nesting_level);
assert_eq!(activation_box.height, 50.0);
assert_eq!(activation_box.nesting_level, 2);
assert_eq!(activation_box.definition().width(), 8.0);
}
#[test]
fn test_nesting_position_calculation() {
let definition = ActivationBoxDefinition::default();
let activation_box = ActivationBox::new(Rc::new(definition), 20.0, 2);
let base_position = Point::new(100.0, 200.0);
let _rendered_output = activation_box.render_to_layers(base_position);
assert_eq!(activation_box.nesting_level, 2);
assert_eq!(activation_box.definition().nesting_offset(), 4.0);
}
#[test]
fn test_render_to_layers_returns_valid_output() {
let activation_box =
ActivationBox::new(Rc::new(ActivationBoxDefinition::default()), 100.0, 0);
let position = Point::new(50.0, 75.0);
let output = activation_box.render_to_layers(position);
assert!(!output.is_empty());
let svg_nodes = output.render();
assert!(!svg_nodes.is_empty());
}
#[test]
fn test_calculate_bounds() {
let mut definition = ActivationBoxDefinition::new();
definition.set_width(12.0);
definition.set_nesting_offset(6.0);
let activation_box = ActivationBox::new(Rc::new(definition), 40.0, 1);
let position = Point::new(150.0, 300.0);
let bounds = activation_box.calculate_bounds(position);
assert_eq!(bounds.min_x(), 150.0); assert_eq!(bounds.max_x(), 162.0); assert_eq!(bounds.min_y(), 280.0); assert_eq!(bounds.max_y(), 320.0); }
}