use std::{fmt, rc::Rc};
use svg::{self, node::element as svg_element};
use super::{RectangleDefinition, ShapeDefinition, rectangle};
use crate::{
color::Color,
draw::{StrokeDefinition, TextDefinition, text_positioning::TextPositioningStrategy},
geometry::{Insets, Point, Size},
};
pub type ComponentDefinition = RectangleWithIconDefinition<ComponentIcon>;
impl ComponentDefinition {
pub fn new() -> Self {
Self {
rectangle_definition: rectangle::RectangleDefinition::default(),
icon: ComponentIcon,
}
}
}
impl Default for ComponentDefinition {
fn default() -> Self {
let mut rectangle_definition = RectangleDefinition::default();
rectangle_definition
.set_fill_color(Some(
Color::new("#FEFECE").expect("#FEFECE is a valid color"),
))
.expect("Failed to set fill color");
rectangle_definition
.set_rounded(10)
.expect("Failed to set rounded");
Self {
rectangle_definition,
icon: ComponentIcon,
}
}
}
pub trait Icon {
fn render_to_svg(
&self,
stroke: &StrokeDefinition,
fill_color: Option<Color>,
) -> Box<dyn svg::Node>;
fn size(&self) -> Size;
}
#[derive(Debug, Clone)]
pub struct ComponentIcon;
impl Icon for ComponentIcon {
fn render_to_svg(
&self,
stroke: &StrokeDefinition,
fill_color: Option<Color>,
) -> Box<dyn svg::Node> {
let path_data = "M 8 0 L 40 0 L 40 30 L 8 30 L 8 24 L 12 24 L 12 18 L 8 18 L 8 12 L 12 12
L 12 6 L 8 6 L 8 0 Z M 0 6 L 0 12 L 12 12 L 12 6 L 0 6 Z M 0 18 L 0 24 L 12 24 L 12 18 L 0 18 Z";
let component_icon = svg_element::Path::new()
.set("d", path_data)
.set("fill", "white")
.set("fill-rule", "evenodd");
let mut component_icon = crate::apply_stroke!(component_icon, stroke);
if let Some(fill_color) = fill_color {
component_icon = component_icon
.set("fill", fill_color.to_string())
.set("fill-opacity", fill_color.alpha());
}
component_icon.into()
}
fn size(&self) -> Size {
Size::new(40.0, 30.0)
}
}
#[derive(Debug, Clone)]
pub struct RectangleWithIconDefinition<I>
where
I: Icon + fmt::Debug + Clone + 'static,
{
rectangle_definition: rectangle::RectangleDefinition,
icon: I,
}
impl<I> RectangleWithIconDefinition<I>
where
I: Icon + fmt::Debug + Clone + 'static,
{
fn fill_color(&self) -> Option<Color> {
self.rectangle_definition.fill_color()
}
fn rounded(&self) -> usize {
self.rectangle_definition.rounded()
}
}
impl<I> ShapeDefinition for RectangleWithIconDefinition<I>
where
I: Icon + fmt::Debug + Clone + 'static,
{
fn supports_content(&self) -> bool {
true
}
fn find_intersection(&self, a: Point, b: Point, a_size: Size) -> Point {
self.rectangle_definition.find_intersection(a, b, a_size)
}
fn calculate_inner_size(&self, content_size: Size, padding: Insets) -> Size {
let min_size = self.icon.size().add_padding(Insets::uniform(10.0));
let padded_icon_size = self
.icon
.size()
.add_padding(Insets::new(10.0, 10.0, 0.0, 0.0));
let padded_content_size = content_size.add_padding(padding);
padded_content_size
.merge_horizontal(padded_icon_size)
.max(min_size)
}
fn clone_box(&self) -> Box<dyn ShapeDefinition> {
Box::new(self.clone())
}
fn stroke(&self) -> &Rc<StrokeDefinition> {
self.rectangle_definition.stroke()
}
fn text(&self) -> &Rc<TextDefinition> {
self.rectangle_definition.text()
}
fn set_text(&mut self, text: Rc<TextDefinition>) {
self.rectangle_definition.set_text(text);
}
fn set_stroke(&mut self, stroke: Rc<StrokeDefinition>) {
self.rectangle_definition.set_stroke(stroke);
}
fn set_fill_color(&mut self, color: Option<Color>) -> Result<(), &'static str> {
self.rectangle_definition.set_fill_color(color)
}
fn set_rounded(&mut self, radius: usize) -> Result<(), &'static str> {
self.rectangle_definition.set_rounded(radius)
}
fn text_positioning_strategy(&self) -> TextPositioningStrategy {
TextPositioningStrategy::InContent
}
fn render_to_svg(&self, size: Size, position: Point) -> Box<dyn svg::Node> {
let bounds = position.to_bounds(size);
let padded_icon_size = self
.icon
.size()
.add_padding(Insets::new(10.0, 10.0, 0.0, 0.0));
let top_right_point = Point::new(bounds.max_x(), bounds.min_y());
let icon_position = Point::new(
top_right_point.x() - padded_icon_size.width(),
top_right_point.y() + 10.0,
);
let mut group = svg_element::Group::new().set("id", "component-group");
let rect = svg_element::Rectangle::new()
.set("x", bounds.min_x())
.set("y", bounds.min_y())
.set("width", size.width())
.set("height", size.height())
.set("fill", "white")
.set("rx", self.rounded());
let mut rect = crate::apply_stroke!(rect, self.stroke());
if let Some(fill_color) = self.fill_color() {
rect = rect
.set("fill", fill_color.to_string())
.set("fill-opacity", fill_color.alpha());
}
group = group.add(rect);
let component_icon = self.icon.render_to_svg(self.stroke(), self.fill_color());
let icon_group = svg_element::Group::new()
.set(
"transform",
format!("translate({}, {})", icon_position.x(), icon_position.y()),
)
.add(component_icon);
group = group.add(icon_group);
group.into()
}
}