use super::{
Color, Component, EasingType, Effect, File, LayoutConstraint, Paint, Rectangle, Styles,
TypeStyle,
};
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize, Copy, Clone)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
#[typeshare::typeshare]
pub enum StrokeAlign {
Inside,
Outside,
Center,
}
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
#[typeshare::typeshare]
pub enum LayoutMode {
None,
Horizontal,
Vertical,
}
#[derive(Debug, Deserialize, Serialize, PartialEq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
#[typeshare::typeshare]
pub enum AxisSizingMode {
Fixed,
Auto,
}
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
#[typeshare::typeshare]
pub enum PrimaryAxisAlignItems {
Min,
Center,
Max,
SpaceBetween,
}
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
#[typeshare::typeshare]
pub enum CounterAxisAlignItems {
Min,
Center,
Max,
Baseline,
}
#[derive(Debug, Deserialize, Serialize)]
#[typeshare::typeshare]
pub struct StrokeWeights {
pub top: f64,
pub right: f64,
pub bottom: f64,
pub left: f64,
}
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
#[typeshare::typeshare]
pub enum LayoutAlign {
Inherit,
Stretch,
Min,
Center,
Max,
}
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
#[typeshare::typeshare]
pub enum LayoutPositioning {
Absolute,
}
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
#[typeshare::typeshare]
pub struct Node {
pub id: String,
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub visible: Option<bool>,
pub r#type: NodeType,
#[serde(skip_serializing_if = "Option::is_none")]
pub children: Option<Vec<Node>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub background_color: Option<Color>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fills: Option<Vec<Paint>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub strokes: Option<Vec<Paint>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stroke_weight: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub individual_stroke_weights: Option<StrokeWeights>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stroke_align: Option<StrokeAlign>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stroke_dashes: Option<Vec<f64>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub corner_radius: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub rectangle_corner_radii: Option<[f64; 4]>,
#[serde(skip_serializing_if = "Option::is_none")]
pub transition_duration: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub transition_easing: Option<EasingType>,
#[serde(skip_serializing_if = "Option::is_none")]
pub opacity: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub absolute_bounding_box: Option<Rectangle>,
#[serde(skip_serializing_if = "Option::is_none")]
pub absolute_render_bounds: Option<Rectangle>,
#[serde(skip_serializing_if = "Option::is_none")]
pub primary_axis_sizing_mode: Option<AxisSizingMode>,
#[serde(skip_serializing_if = "Option::is_none")]
pub counter_axis_sizing_mode: Option<AxisSizingMode>,
#[serde(skip_serializing_if = "Option::is_none")]
pub primary_axis_align_items: Option<PrimaryAxisAlignItems>,
#[serde(skip_serializing_if = "Option::is_none")]
pub counter_axis_align_items: Option<CounterAxisAlignItems>,
#[serde(skip_serializing_if = "Option::is_none")]
pub item_spacing: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub layout_positioning: Option<LayoutPositioning>,
#[serde(skip_serializing_if = "Option::is_none")]
pub layout_mode: Option<LayoutMode>,
#[serde(skip_serializing_if = "Option::is_none")]
pub padding_left: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub padding_right: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub padding_top: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub padding_bottom: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub effects: Option<Vec<Effect>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub styles: Option<Styles>,
#[serde(skip_serializing_if = "Option::is_none")]
pub characters: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub style: Option<TypeStyle>,
#[serde(skip_serializing_if = "Option::is_none")]
pub constraints: Option<LayoutConstraint>,
#[serde(skip_serializing_if = "Option::is_none")]
pub layout_align: Option<LayoutAlign>,
#[serde(skip_serializing_if = "Option::is_none")]
pub layout_grow: Option<f64>,
}
impl Node {
pub fn visible(&self) -> bool {
self.visible.unwrap_or(true)
}
pub fn background_color(&self) -> Option<&Color> {
self.background_color.as_ref()
}
pub fn absolute_bounding_box(&self) -> Option<&Rectangle> {
self.absolute_bounding_box.as_ref()
}
pub fn corner_radius(&self) -> Option<f64> {
self.corner_radius
}
pub fn rectangle_corner_radii(&self) -> Option<[f64; 4]> {
self.rectangle_corner_radii
.or_else(|| self.corner_radius.map(|r| [r, r, r, r]))
}
pub fn transition_duration(&self) -> Option<f64> {
self.transition_duration
}
pub fn transition_easing(&self) -> Option<&EasingType> {
self.transition_easing.as_ref()
}
pub fn opacity(&self) -> f64 {
self.opacity.unwrap_or(1.0)
}
pub fn padding_left(&self) -> f64 {
self.padding_left.unwrap_or(0.0)
}
pub fn padding_right(&self) -> f64 {
self.padding_right.unwrap_or(0.0)
}
pub fn padding_top(&self) -> f64 {
self.padding_top.unwrap_or(0.0)
}
pub fn padding_bottom(&self) -> f64 {
self.padding_bottom.unwrap_or(0.0)
}
pub fn children(&self) -> &[Node] {
self.children.as_deref().unwrap_or_default()
}
pub fn enabled_children(&self) -> impl Iterator<Item = &Node> {
self.children().iter().filter(|c| c.visible())
}
pub fn fills(&self) -> &[Paint] {
self.fills.as_deref().unwrap_or_default()
}
pub fn strokes(&self) -> &[Paint] {
self.strokes.as_deref().unwrap_or_default()
}
pub fn depth_first_stack_iter(&self) -> NodeDepthFirstStackIterator {
NodeDepthFirstStackIterator {
stack: vec![self],
iter_stack: vec![self.children().iter()],
}
}
pub fn component<'a>(&self, file: &'a File) -> Option<&'a Component> {
file.components.get(&self.id)
}
pub fn stroke_weight(&self) -> Option<f64> {
self.stroke_weight
}
pub fn stroke_align(&self) -> Option<&StrokeAlign> {
self.stroke_align.as_ref()
}
}
#[derive(Debug, Deserialize, Serialize, PartialEq, Copy, Clone)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
#[typeshare::typeshare]
pub enum NodeType {
Document,
Canvas,
Frame,
Group,
Vector,
BooleanOperation,
Star,
Line,
Ellipse,
RegularPolygon,
Rectangle,
Text,
Slice,
Component,
ComponentSet,
Instance,
Sticky,
ShapeWithText,
Connector,
Section,
}
pub struct NodeDepthFirstStackIterator<'a> {
iter_stack: Vec<std::slice::Iter<'a, Node>>,
stack: Vec<&'a Node>,
}
impl<'a> Iterator for NodeDepthFirstStackIterator<'a> {
type Item = (&'a Node, Vec<&'a Node>);
fn next(&mut self) -> Option<Self::Item> {
loop {
let mut bottom_of_iter_stack = self.iter_stack.pop()?;
let bottom_of_stack = self.stack.pop()?;
if let Some(current) = bottom_of_iter_stack.next() {
self.iter_stack.push(bottom_of_iter_stack);
self.iter_stack.push(current.children().iter());
self.stack.push(bottom_of_stack);
self.stack.push(current);
return Some((current, self.stack.clone()));
}
}
}
}