use serde::{Deserialize, Serialize};
use crate::node::{Common, IntoNode, Node};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Direction {
#[default]
Vertical,
Horizontal,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Flex {
Legacy,
#[default]
Start,
End,
Center,
SpaceBetween,
SpaceAround,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
pub enum Constraint {
Length(u16),
Percentage(u16),
Ratio(u32, u32),
Min(u16),
Max(u16),
Fill(u16),
}
#[derive(Deserialize)]
#[serde(untagged)]
enum ConstraintRaw {
Bare(u16),
Tagged(ConstraintTagged),
}
#[derive(Deserialize)]
enum ConstraintTagged {
Length(u16),
Percentage(u16),
Ratio([u32; 2]),
Min(u16),
Max(u16),
Fill(u16),
}
impl<'de> Deserialize<'de> for Constraint {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
match ConstraintRaw::deserialize(deserializer)? {
ConstraintRaw::Bare(n) => Ok(Self::Length(n)),
ConstraintRaw::Tagged(tagged) => Ok(match tagged {
ConstraintTagged::Length(n) => Self::Length(n),
ConstraintTagged::Percentage(p) => Self::Percentage(p),
ConstraintTagged::Ratio([a, b]) => Self::Ratio(a, b),
ConstraintTagged::Min(n) => Self::Min(n),
ConstraintTagged::Max(n) => Self::Max(n),
ConstraintTagged::Fill(w) => Self::Fill(w),
}),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(untagged)]
pub enum Margin {
Uniform(u16),
Axes {
horizontal: u16,
vertical: u16,
},
}
impl Margin {
#[must_use]
pub fn uniform(n: u16) -> Self {
Self::Uniform(n)
}
#[must_use]
pub fn hv(horizontal: u16, vertical: u16) -> Self {
Self::Axes {
horizontal,
vertical,
}
}
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Layout {
#[serde(flatten)]
pub common: Common,
#[serde(skip_serializing_if = "Option::is_none")]
pub direction: Option<Direction>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub constraints: Vec<Constraint>,
pub children: Vec<Node>,
#[serde(skip_serializing_if = "Option::is_none")]
pub spacing: Option<u16>,
#[serde(skip_serializing_if = "Option::is_none")]
pub margin: Option<Margin>,
#[serde(skip_serializing_if = "Option::is_none")]
pub flex: Option<Flex>,
#[serde(skip_serializing_if = "Option::is_none")]
pub collapse: Option<u16>,
}
impl Layout {
#[must_use]
pub fn vertical(constraints: impl IntoIterator<Item = Constraint>) -> Self {
Self {
direction: Some(Direction::Vertical),
constraints: constraints.into_iter().collect(),
..Self::default()
}
}
#[must_use]
pub fn horizontal(constraints: impl IntoIterator<Item = Constraint>) -> Self {
Self {
direction: Some(Direction::Horizontal),
constraints: constraints.into_iter().collect(),
..Self::default()
}
}
#[must_use]
pub fn constraints(mut self, constraints: impl IntoIterator<Item = Constraint>) -> Self {
self.constraints = constraints.into_iter().collect();
self
}
#[must_use]
pub fn children(mut self, children: impl IntoIterator<Item = Node>) -> Self {
self.children = children.into_iter().collect();
self
}
#[must_use]
pub fn spacing(mut self, spacing: u16) -> Self {
self.spacing = Some(spacing);
self
}
#[must_use]
pub fn margin(mut self, margin: Margin) -> Self {
self.margin = Some(margin);
self
}
#[must_use]
pub fn flex(mut self, flex: Flex) -> Self {
self.flex = Some(flex);
self
}
#[must_use]
pub fn collapse(mut self, collapse: u16) -> Self {
self.collapse = Some(collapse);
self
}
}
impl IntoNode for Layout {}
impl From<Layout> for Node {
fn from(l: Layout) -> Self {
Node::Layout(l)
}
}
#[cfg(test)]
mod tests {
#![allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
reason = "tests assert by unwrapping"
)]
use super::*;
#[test]
fn bare_integer_is_length() {
let c: Constraint = serde_json::from_str("3").expect("bare int parses");
assert_eq!(c, Constraint::Length(3));
let c: Constraint = serde_json::from_str(r#"{"Fill":1}"#).expect("tagged parses");
assert_eq!(c, Constraint::Fill(1));
let c: Constraint = serde_json::from_str(r#"{"Ratio":[1,4]}"#).expect("ratio parses");
assert_eq!(c, Constraint::Ratio(1, 4));
}
}