use std::{collections::BTreeMap, fmt};
use serde::{
Deserialize,
de::{self, Deserializer, Visitor},
};
#[derive(Debug, Clone, PartialEq)]
pub enum Color {
Hex(u32),
Named(String),
}
impl<'de> Deserialize<'de> for Color {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
struct ColorVisitor;
impl Visitor<'_> for ColorVisitor {
type Value = Color;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "a color (hex integer, \"#RRGGBB\", or name)")
}
fn visit_u64<E: de::Error>(self, v: u64) -> Result<Color, E> {
Ok(Color::Hex(v as u32))
}
fn visit_i64<E: de::Error>(self, v: i64) -> Result<Color, E> {
Ok(Color::Hex(v as u32))
}
fn visit_str<E: de::Error>(self, v: &str) -> Result<Color, E> {
if let Some(hex) = v.strip_prefix('#') {
let expanded = match hex.len() {
3 => {
let mut s = String::with_capacity(6);
for c in hex.chars() {
s.push(c);
s.push(c);
}
s
}
6 => hex.to_string(),
_ => return Err(de::Error::custom(format!("invalid CSS hex color: {v}"))),
};
let val = u32::from_str_radix(&expanded, 16)
.map_err(|_| de::Error::custom(format!("invalid hex digits: {v}")))?;
Ok(Color::Hex(val))
} else {
Ok(Color::Named(v.to_string()))
}
}
}
deserializer.deserialize_any(ColorVisitor)
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Dimension {
Pixels(i32),
Percent(i32),
SizeContent,
}
impl<'de> Deserialize<'de> for Dimension {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
struct DimVisitor;
impl Visitor<'_> for DimVisitor {
type Value = Dimension;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "pixels (int), \"Npct\", or \"size_content\"")
}
fn visit_i64<E: de::Error>(self, v: i64) -> Result<Dimension, E> {
Ok(Dimension::Pixels(v as i32))
}
fn visit_u64<E: de::Error>(self, v: u64) -> Result<Dimension, E> {
Ok(Dimension::Pixels(v as i32))
}
fn visit_str<E: de::Error>(self, v: &str) -> Result<Dimension, E> {
if v == "size_content" {
Ok(Dimension::SizeContent)
} else if let Some(n) = v.strip_suffix("pct") {
let val: i32 = n.parse().map_err(|_| de::Error::custom(format!("invalid percent: {v}")))?;
Ok(Dimension::Percent(val))
} else {
Err(de::Error::custom(format!("invalid dimension: {v}")))
}
}
}
deserializer.deserialize_any(DimVisitor)
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Opacity {
Value(u8),
Named(String),
}
impl<'de> Deserialize<'de> for Opacity {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
struct OpaVisitor;
impl Visitor<'_> for OpaVisitor {
type Value = Opacity;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "opacity (0-255, 0.0-1.0, or \"transp\"/\"cover\")")
}
fn visit_i64<E: de::Error>(self, v: i64) -> Result<Opacity, E> {
Ok(Opacity::Value(v as u8))
}
fn visit_u64<E: de::Error>(self, v: u64) -> Result<Opacity, E> {
Ok(Opacity::Value(v as u8))
}
fn visit_f64<E: de::Error>(self, v: f64) -> Result<Opacity, E> {
Ok(Opacity::Value((v * 255.0) as u8))
}
fn visit_str<E: de::Error>(self, v: &str) -> Result<Opacity, E> {
Ok(Opacity::Named(v.to_string()))
}
}
deserializer.deserialize_any(OpaVisitor)
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum TrackSize {
Pixels(i32),
Fr(i32),
Content,
}
impl<'de> Deserialize<'de> for TrackSize {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
struct TrackVisitor;
impl Visitor<'_> for TrackVisitor {
type Value = TrackSize;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "pixels (int), \"Nfr\", or \"content\"")
}
fn visit_i64<E: de::Error>(self, v: i64) -> Result<TrackSize, E> {
Ok(TrackSize::Pixels(v as i32))
}
fn visit_u64<E: de::Error>(self, v: u64) -> Result<TrackSize, E> {
Ok(TrackSize::Pixels(v as i32))
}
fn visit_str<E: de::Error>(self, v: &str) -> Result<TrackSize, E> {
if v == "content" {
Ok(TrackSize::Content)
} else if let Some(n) = v.strip_suffix("fr") {
let val: i32 = n.parse().map_err(|_| de::Error::custom(format!("invalid fr: {v}")))?;
Ok(TrackSize::Fr(val))
} else {
Err(de::Error::custom(format!("invalid track size: {v}")))
}
}
}
deserializer.deserialize_any(TrackVisitor)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum Alignment {
TopLeft,
TopMid,
TopRight,
LeftMid,
Center,
RightMid,
BottomLeft,
BottomMid,
BottomRight,
OutTopLeft,
OutTopMid,
OutTopRight,
OutBottomLeft,
OutBottomMid,
OutBottomRight,
OutLeftTop,
OutLeftMid,
OutLeftBottom,
OutRightTop,
OutRightMid,
OutRightBottom,
}
#[derive(Debug, Clone, Copy, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum FlexFlow {
Row,
Column,
RowWrap,
ColumnWrap,
RowReverse,
ColumnReverse,
RowWrapReverse,
ColumnWrapReverse,
}
#[derive(Debug, Clone, Copy, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum FlexAlign {
Start,
End,
Center,
SpaceEvenly,
SpaceAround,
SpaceBetween,
}
#[derive(Debug, Clone, Copy, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum GridAlign {
Start,
Center,
End,
Stretch,
SpaceEvenly,
SpaceAround,
SpaceBetween,
}
#[derive(Debug, Clone, Copy, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum TextAlign {
Auto,
Left,
Center,
Right,
}
#[derive(Debug, Clone, Copy, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum LabelLongMode {
Wrap,
Expand,
Clip,
ScrollCircular,
}
#[derive(Debug, Clone, Copy, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum ScaleMode {
HorizontalTop,
HorizontalBottom,
VerticalLeft,
VerticalRight,
RoundInner,
RoundOuter,
}
#[derive(Debug, Clone, Copy, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum ScrollDir {
None,
Hor,
Ver,
All,
}
#[derive(Debug, Clone, Copy, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum ScrollbarMode {
Off,
On,
Active,
Auto,
}
#[derive(Debug, Clone, Copy, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum TextDecor {
None,
Underline,
Strikethrough,
}
#[derive(Debug, Clone, Copy, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum BorderSide {
None,
Top,
Bottom,
Left,
Right,
Full,
}
#[derive(Debug, Clone, Copy, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum DdDir {
Bottom,
Top,
Left,
Right,
}
#[derive(Debug, Clone, Copy, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum RollerMode {
Normal,
Infinite,
}
#[derive(Debug, Clone, Copy, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum BarMode {
Normal,
Range,
Symmetrical,
}
#[derive(Debug, Clone, Copy, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum ArcMode {
Normal,
Reverse,
Symmetrical,
}
#[derive(Debug, Clone, Copy, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum LayoutKind {
None,
Flex,
Grid,
}
#[derive(Debug, Clone, Copy, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum BarOrientation {
Auto,
Horizontal,
Vertical,
}
#[derive(Debug, Clone, Copy, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum SliderOrientation {
Auto,
Horizontal,
Vertical,
}
#[cfg(feature = "widget-keyboard")]
#[derive(Debug, Clone, Copy, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum KeyboardMode {
TextLower,
TextUpper,
Special,
Number,
}
#[cfg(feature = "widget-chart")]
#[derive(Debug, Clone, Copy, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum ChartType {
None,
Line,
Curve,
Bar,
Stacked,
Scatter,
}
#[cfg(feature = "widget-arc-label")]
#[derive(Debug, Clone, Copy, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum ArcLabelDir {
Clockwise,
CounterClockwise,
}
#[cfg(feature = "widget-spangroup")]
#[derive(Debug, Clone, Copy, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum SpanOverflow {
Clip,
Ellipsis,
}
#[cfg(feature = "widget-spangroup")]
#[derive(Debug, Clone, Copy, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum SpanMode {
Fixed,
Expand,
Break,
}
#[derive(Debug, Clone, Default, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct StyleProps {
pub opa: Option<Opacity>,
pub bg_color: Option<Color>,
pub bg_opa: Option<Opacity>,
pub bg_grad: Option<String>,
pub border_color: Option<Color>,
pub border_width: Option<i16>,
pub border_opa: Option<Opacity>,
pub border_side: Option<BorderSide>,
pub outline_color: Option<Color>,
pub outline_width: Option<i16>,
pub outline_opa: Option<Opacity>,
pub outline_pad: Option<i16>,
pub shadow_color: Option<Color>,
pub shadow_width: Option<i16>,
pub shadow_ofs_x: Option<i16>,
pub shadow_ofs_y: Option<i16>,
pub shadow_spread: Option<i16>,
pub shadow_opa: Option<Opacity>,
pub pad_all: Option<i16>,
pub pad_top: Option<i16>,
pub pad_bottom: Option<i16>,
pub pad_left: Option<i16>,
pub pad_right: Option<i16>,
pub pad_row: Option<i16>,
pub pad_column: Option<i16>,
pub radius: Option<i16>,
pub clip_corner: Option<bool>,
pub translate_x: Option<i16>,
pub translate_y: Option<i16>,
pub text_color: Option<Color>,
pub text_opa: Option<Opacity>,
pub text_font: Option<String>,
pub text_align: Option<TextAlign>,
pub text_decor: Option<TextDecor>,
pub line_color: Option<Color>,
pub line_width: Option<i16>,
pub line_opa: Option<Opacity>,
pub line_rounded: Option<bool>,
pub arc_color: Option<Color>,
pub arc_width: Option<i16>,
pub arc_opa: Option<Opacity>,
pub arc_rounded: Option<bool>,
pub transform_angle: Option<i16>,
pub transform_zoom: Option<u16>,
pub bg_image_src: Option<String>,
pub bg_image_opa: Option<Opacity>,
pub bg_image_recolor: Option<Color>,
pub bg_image_recolor_opa: Option<Opacity>,
pub bg_image_tiled: Option<bool>,
pub styles: Option<StyleRef>,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(untagged)]
pub enum StyleRef {
Single(String),
Multiple(Vec<String>),
}
impl StyleRef {
pub fn names(&self) -> Vec<&str> {
match self {
StyleRef::Single(s) => vec![s.as_str()],
StyleRef::Multiple(v) => v.iter().map(|s| s.as_str()).collect(),
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct GridCell {
pub column: u16,
pub row: u16,
#[serde(default = "default_span")]
pub column_span: u16,
#[serde(default = "default_span")]
pub row_span: u16,
pub column_align: Option<GridAlign>,
pub row_align: Option<GridAlign>,
}
fn default_span() -> u16 {
1
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum EventCodeKind {
All,
Pressed,
Released,
Pressing,
ShortClicked,
SingleClicked,
DoubleClicked,
TripleClicked,
LongPressed,
LongPressedRepeat,
Clicked,
ValueChanged,
Scroll,
DrawTaskAdded,
DrawMainEnd,
Focused,
Defocused,
Ready,
Key,
}
impl EventCodeKind {
pub fn as_event_code_ident(self) -> &'static str {
match self {
Self::All => "ALL",
Self::Pressed => "PRESSED",
Self::Released => "RELEASED",
Self::Pressing => "PRESSING",
Self::ShortClicked => "SHORT_CLICKED",
Self::SingleClicked => "SINGLE_CLICKED",
Self::DoubleClicked => "DOUBLE_CLICKED",
Self::TripleClicked => "TRIPLE_CLICKED",
Self::LongPressed => "LONG_PRESSED",
Self::LongPressedRepeat => "LONG_PRESSED_REPEAT",
Self::Clicked => "CLICKED",
Self::ValueChanged => "VALUE_CHANGED",
Self::Scroll => "SCROLL",
Self::DrawTaskAdded => "DRAW_TASK_ADDED",
Self::DrawMainEnd => "DRAW_MAIN_END",
Self::Focused => "FOCUSED",
Self::Defocused => "DEFOCUSED",
Self::Ready => "READY",
Self::Key => "KEY",
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct CommonProps {
pub id: Option<String>,
pub x: Option<Dimension>,
pub y: Option<Dimension>,
pub width: Option<Dimension>,
pub height: Option<Dimension>,
pub align: Option<Alignment>,
pub hidden: Option<bool>,
pub clickable: Option<bool>,
pub checkable: Option<bool>,
pub scrollable: Option<bool>,
pub scroll_dir: Option<ScrollDir>,
pub scrollbar_mode: Option<ScrollbarMode>,
pub flex_grow: Option<u8>,
pub grid_cell: Option<GridCell>,
pub widgets: Option<Vec<WidgetKind>>,
pub styles: Option<StyleRef>,
pub layout: Option<LayoutKind>,
pub flex_flow: Option<FlexFlow>,
pub flex_align_main: Option<FlexAlign>,
pub flex_align_cross: Option<FlexAlign>,
pub flex_align_track: Option<FlexAlign>,
pub grid_columns: Option<Vec<TrackSize>>,
pub grid_rows: Option<Vec<TrackSize>>,
pub grid_align_column: Option<GridAlign>,
pub grid_align_row: Option<GridAlign>,
#[serde(flatten)]
pub style: StyleProps,
pub state_default: Option<StyleProps>,
pub state_pressed: Option<StyleProps>,
pub state_focused: Option<StyleProps>,
pub state_checked: Option<StyleProps>,
pub state_disabled: Option<StyleProps>,
pub state_scrolled: Option<StyleProps>,
pub part_indicator: Option<StyleProps>,
pub part_knob: Option<StyleProps>,
pub part_items: Option<StyleProps>,
pub part_scrollbar: Option<StyleProps>,
pub part_selected: Option<StyleProps>,
pub part_cursor: Option<StyleProps>,
pub on: Option<BTreeMap<EventCodeKind, EventAction>>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(untagged)]
pub enum EventAction {
Handler(String),
ShowToast(ShowToastAction),
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct ShowToastAction {
pub show_toast: String,
#[serde(default)]
pub args: Vec<String>,
}
#[derive(Debug, Clone, Default, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct WidgetProps {
#[serde(flatten)]
pub common: CommonProps,
}
#[derive(Debug, Clone, Default, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct LabelProps {
#[serde(flatten)]
pub common: CommonProps,
pub text: Option<String>,
pub bind_text: Option<String>,
pub long_mode: Option<LabelLongMode>,
}
#[derive(Debug, Clone, Default, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct CheckboxProps {
#[serde(flatten)]
pub common: CommonProps,
pub text: Option<String>,
}
#[derive(Debug, Clone, Default, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct SliderProps {
#[serde(flatten)]
pub common: CommonProps,
pub min_value: Option<i32>,
pub max_value: Option<i32>,
pub value: Option<i32>,
pub orientation: Option<SliderOrientation>,
}
#[derive(Debug, Clone, Default, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct ArcProps {
#[serde(flatten)]
pub common: CommonProps,
pub min_value: Option<f32>,
pub max_value: Option<f32>,
pub value: Option<f32>,
pub start_angle: Option<u16>,
pub end_angle: Option<u16>,
pub rotation: Option<i16>,
pub mode: Option<ArcMode>,
pub adjustable: Option<bool>,
}
#[derive(Debug, Clone, Default, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct BarProps {
#[serde(flatten)]
pub common: CommonProps,
pub min_value: Option<f32>,
pub max_value: Option<f32>,
pub value: Option<f32>,
pub mode: Option<BarMode>,
pub orientation: Option<BarOrientation>,
}
#[derive(Debug, Clone, Default, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct DropdownProps {
#[serde(flatten)]
pub common: CommonProps,
pub options: Option<Vec<String>>,
pub selected: Option<u32>,
pub dir: Option<DdDir>,
pub symbol: Option<String>,
}
#[derive(Debug, Clone, Default, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct RollerProps {
#[serde(flatten)]
pub common: CommonProps,
pub options: Option<Vec<String>>,
pub selected: Option<u32>,
pub visible_row_count: Option<u32>,
pub mode: Option<RollerMode>,
}
#[derive(Debug, Clone, Default, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct ScaleProps {
#[serde(flatten)]
pub common: CommonProps,
pub mode: Option<ScaleMode>,
pub total_tick_count: Option<u16>,
pub major_tick_every: Option<u16>,
pub label_show: Option<bool>,
pub range_min: Option<i32>,
pub range_max: Option<i32>,
pub rotation: Option<i32>,
pub angle_range: Option<u32>,
pub major_tick_length: Option<i32>,
pub minor_tick_length: Option<i32>,
}
#[derive(Debug, Clone, Default, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct LedProps {
#[serde(flatten)]
pub common: CommonProps,
pub color: Option<Color>,
pub brightness: Option<u8>,
}
#[derive(Debug, Clone, Default, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct ImageProps {
#[serde(flatten)]
pub common: CommonProps,
pub src: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct ValueLabelProps {
#[serde(flatten)]
pub common: CommonProps,
pub text: Option<String>,
pub unit: String,
}
#[derive(Debug, Clone, Default, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct LineProps {
#[serde(flatten)]
pub common: CommonProps,
pub points: Option<Vec<[f32; 2]>>,
}
#[cfg(feature = "widget-spinner")]
#[derive(Debug, Clone, Default, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct SpinnerProps {
#[serde(flatten)]
pub common: CommonProps,
pub anim_time: Option<u32>,
pub arc_length: Option<u32>,
}
#[cfg(feature = "widget-spinbox")]
#[derive(Debug, Clone, Default, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct SpinboxProps {
#[serde(flatten)]
pub common: CommonProps,
pub value: Option<i32>,
pub min_value: Option<i32>,
pub max_value: Option<i32>,
pub digit_count: Option<u32>,
pub decimal_point: Option<u32>,
pub step: Option<u32>,
pub rollover: Option<bool>,
}
#[cfg(feature = "widget-textarea")]
#[derive(Debug, Clone, Default, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct TextareaProps {
#[serde(flatten)]
pub common: CommonProps,
pub text: Option<String>,
pub placeholder: Option<String>,
pub password_mode: Option<bool>,
pub one_line: Option<bool>,
pub max_length: Option<u32>,
}
#[cfg(feature = "widget-keyboard")]
#[derive(Debug, Clone, Default, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct KeyboardProps {
#[serde(flatten)]
pub common: CommonProps,
pub mode: Option<KeyboardMode>,
}
#[cfg(feature = "widget-chart")]
#[derive(Debug, Clone, Default, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct ChartProps {
#[serde(flatten)]
pub common: CommonProps,
pub chart_type: Option<ChartType>,
pub point_count: Option<u32>,
pub div_lines_h: Option<u32>,
pub div_lines_v: Option<u32>,
}
#[cfg(feature = "widget-tabview")]
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct Tab {
pub name: String,
#[serde(flatten)]
pub common: CommonProps,
}
#[cfg(feature = "widget-tabview")]
#[derive(Debug, Clone, Default, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct TabviewProps {
#[serde(flatten)]
pub common: CommonProps,
pub tab_bar_position: Option<DdDir>,
pub tab_bar_size: Option<i32>,
pub tabs: Option<Vec<Tab>>,
}
#[cfg(feature = "widget-canvas")]
#[derive(Debug, Clone, Copy, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum CanvasColorFormat {
Rgb565,
Argb8888,
}
#[cfg(feature = "widget-imagebutton")]
#[derive(Debug, Clone, Default, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct ImagebuttonSrc {
pub left: Option<String>,
pub mid: Option<String>,
pub right: Option<String>,
}
#[cfg(feature = "widget-imagebutton")]
#[derive(Debug, Clone, Default, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct ImagebuttonProps {
#[serde(flatten)]
pub common: CommonProps,
pub src_released: Option<ImagebuttonSrc>,
pub src_pressed: Option<ImagebuttonSrc>,
pub src_disabled: Option<ImagebuttonSrc>,
}
#[cfg(feature = "widget-animimg")]
#[derive(Debug, Clone, Default, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct AnimImgProps {
#[serde(flatten)]
pub common: CommonProps,
pub frames: Option<Vec<String>>,
pub duration: Option<u32>,
pub repeat_count: Option<u32>,
pub auto_start: Option<bool>,
}
#[cfg(feature = "widget-canvas")]
#[derive(Debug, Clone, Default, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct CanvasProps {
#[serde(flatten)]
pub common: CommonProps,
pub buf_width: Option<u32>,
pub buf_height: Option<u32>,
pub color_format: Option<CanvasColorFormat>,
}
#[cfg(feature = "widget-arc-label")]
#[derive(Debug, Clone, Default, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct ArcLabelProps {
#[serde(flatten)]
pub common: CommonProps,
pub text: Option<String>,
pub radius: Option<u32>,
pub angle_start: Option<f32>,
pub angle_size: Option<f32>,
pub dir: Option<ArcLabelDir>,
}
#[cfg(feature = "widget-calendar")]
#[derive(Debug, Clone, Default, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct CalendarProps {
#[serde(flatten)]
pub common: CommonProps,
pub today_year: Option<u16>,
pub today_month: Option<u8>,
pub today_day: Option<u8>,
pub shown_year: Option<u16>,
pub shown_month: Option<u8>,
}
#[cfg(feature = "widget-list")]
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum ListItem {
Text(String),
Button(String),
}
#[cfg(feature = "widget-list")]
#[derive(Debug, Clone, Default, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct ListProps {
#[serde(flatten)]
pub common: CommonProps,
pub items: Option<Vec<ListItem>>,
}
#[cfg(feature = "widget-table")]
#[derive(Debug, Clone, Default, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct TableProps {
#[serde(flatten)]
pub common: CommonProps,
pub columns: Option<u32>,
pub column_widths: Option<Vec<i32>>,
pub rows: Option<Vec<Vec<String>>>,
}
#[cfg(feature = "widget-tileview")]
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct Tile {
pub col: u8,
pub row: u8,
pub scroll_dir: Option<ScrollDir>,
#[serde(flatten)]
pub common: CommonProps,
}
#[cfg(feature = "widget-tileview")]
#[derive(Debug, Clone, Default, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct TileviewProps {
#[serde(flatten)]
pub common: CommonProps,
pub tiles: Option<Vec<Tile>>,
}
#[cfg(feature = "widget-win")]
#[derive(Debug, Clone, Default, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct WinProps {
#[serde(flatten)]
pub common: CommonProps,
pub title: Option<String>,
}
#[cfg(feature = "widget-msgbox")]
#[derive(Debug, Clone, Default, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct MsgboxProps {
#[serde(flatten)]
pub common: CommonProps,
pub title: Option<String>,
pub text: Option<String>,
pub close_button: Option<bool>,
pub buttons: Option<Vec<String>>,
}
#[cfg(feature = "widget-spangroup")]
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct SpanDef {
pub text: String,
pub text_color: Option<Color>,
pub text_opa: Option<u8>,
pub text_decor: Option<TextDecor>,
}
#[cfg(feature = "widget-spangroup")]
#[derive(Debug, Clone, Default, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct SpangroupProps {
#[serde(flatten)]
pub common: CommonProps,
pub overflow: Option<SpanOverflow>,
pub span_mode: Option<SpanMode>,
pub max_lines: Option<i32>,
pub indent: Option<i32>,
pub spans: Option<Vec<SpanDef>>,
}
#[cfg(feature = "widget-buttonmatrix")]
#[derive(Debug, Clone, Default, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct ButtonmatrixProps {
#[serde(flatten)]
pub common: CommonProps,
pub button_rows: Option<Vec<Vec<String>>>,
pub one_checked: Option<bool>,
}
#[cfg(feature = "widget-menu")]
#[derive(Debug, Clone, Copy, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum MenuHeaderMode {
TopFixed,
TopUnfixed,
BottomFixed,
}
#[cfg(feature = "widget-menu")]
#[derive(Debug, Clone, Default, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct MenuProps {
#[serde(flatten)]
pub common: CommonProps,
pub header_mode: Option<MenuHeaderMode>,
pub root_back_button: Option<bool>,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum WidgetKind {
Obj(WidgetProps),
Label(LabelProps),
Button(WidgetProps),
Switch(WidgetProps),
Checkbox(CheckboxProps),
Slider(SliderProps),
Arc(ArcProps),
Bar(BarProps),
Dropdown(DropdownProps),
Roller(RollerProps),
Scale(ScaleProps),
Led(LedProps),
Image(ImageProps),
Line(LineProps),
ValueLabel(ValueLabelProps),
#[cfg(feature = "widget-spinner")]
Spinner(SpinnerProps),
#[cfg(feature = "widget-spinbox")]
Spinbox(SpinboxProps),
#[cfg(feature = "widget-textarea")]
Textarea(TextareaProps),
#[cfg(feature = "widget-keyboard")]
Keyboard(KeyboardProps),
#[cfg(feature = "widget-chart")]
Chart(ChartProps),
#[cfg(feature = "widget-tabview")]
Tabview(TabviewProps),
#[cfg(feature = "widget-arc-label")]
ArcLabel(ArcLabelProps),
#[cfg(feature = "widget-calendar")]
Calendar(CalendarProps),
#[cfg(feature = "widget-list")]
List(ListProps),
#[cfg(feature = "widget-table")]
Table(TableProps),
#[cfg(feature = "widget-tileview")]
Tileview(TileviewProps),
#[cfg(feature = "widget-win")]
Win(WinProps),
#[cfg(feature = "widget-msgbox")]
Msgbox(MsgboxProps),
#[cfg(feature = "widget-spangroup")]
Spangroup(SpangroupProps),
#[cfg(feature = "widget-buttonmatrix")]
Buttonmatrix(ButtonmatrixProps),
#[cfg(feature = "widget-canvas")]
Canvas(CanvasProps),
#[cfg(feature = "widget-imagebutton")]
Imagebutton(ImagebuttonProps),
#[cfg(feature = "widget-animimg")]
AnimImg(AnimImgProps),
#[cfg(feature = "widget-menu")]
Menu(MenuProps),
}
impl WidgetKind {
pub fn common(&self) -> &CommonProps {
match self {
WidgetKind::Obj(p) | WidgetKind::Button(p) | WidgetKind::Switch(p) => &p.common,
WidgetKind::Label(p) => &p.common,
WidgetKind::Checkbox(p) => &p.common,
WidgetKind::Slider(p) => &p.common,
WidgetKind::Arc(p) => &p.common,
WidgetKind::Bar(p) => &p.common,
WidgetKind::Dropdown(p) => &p.common,
WidgetKind::Roller(p) => &p.common,
WidgetKind::Scale(p) => &p.common,
WidgetKind::Led(p) => &p.common,
WidgetKind::Image(p) => &p.common,
WidgetKind::Line(p) => &p.common,
WidgetKind::ValueLabel(p) => &p.common,
#[cfg(feature = "widget-spinner")]
WidgetKind::Spinner(p) => &p.common,
#[cfg(feature = "widget-spinbox")]
WidgetKind::Spinbox(p) => &p.common,
#[cfg(feature = "widget-textarea")]
WidgetKind::Textarea(p) => &p.common,
#[cfg(feature = "widget-keyboard")]
WidgetKind::Keyboard(p) => &p.common,
#[cfg(feature = "widget-chart")]
WidgetKind::Chart(p) => &p.common,
#[cfg(feature = "widget-tabview")]
WidgetKind::Tabview(p) => &p.common,
#[cfg(feature = "widget-arc-label")]
WidgetKind::ArcLabel(p) => &p.common,
#[cfg(feature = "widget-calendar")]
WidgetKind::Calendar(p) => &p.common,
#[cfg(feature = "widget-list")]
WidgetKind::List(p) => &p.common,
#[cfg(feature = "widget-table")]
WidgetKind::Table(p) => &p.common,
#[cfg(feature = "widget-tileview")]
WidgetKind::Tileview(p) => &p.common,
#[cfg(feature = "widget-win")]
WidgetKind::Win(p) => &p.common,
#[cfg(feature = "widget-msgbox")]
WidgetKind::Msgbox(p) => &p.common,
#[cfg(feature = "widget-spangroup")]
WidgetKind::Spangroup(p) => &p.common,
#[cfg(feature = "widget-buttonmatrix")]
WidgetKind::Buttonmatrix(p) => &p.common,
#[cfg(feature = "widget-canvas")]
WidgetKind::Canvas(p) => &p.common,
#[cfg(feature = "widget-imagebutton")]
WidgetKind::Imagebutton(p) => &p.common,
#[cfg(feature = "widget-animimg")]
WidgetKind::AnimImg(p) => &p.common,
#[cfg(feature = "widget-menu")]
WidgetKind::Menu(p) => &p.common,
}
}
pub fn type_name(&self) -> &'static str {
match self {
WidgetKind::Obj(_) => "obj",
WidgetKind::Label(_) => "label",
WidgetKind::Button(_) => "button",
WidgetKind::Switch(_) => "switch",
WidgetKind::Checkbox(_) => "checkbox",
WidgetKind::Slider(_) => "slider",
WidgetKind::Arc(_) => "arc",
WidgetKind::Bar(_) => "bar",
WidgetKind::Dropdown(_) => "dropdown",
WidgetKind::Roller(_) => "roller",
WidgetKind::Scale(_) => "scale",
WidgetKind::Led(_) => "led",
WidgetKind::Image(_) => "image",
WidgetKind::Line(_) => "line",
WidgetKind::ValueLabel(_) => "value_label",
#[cfg(feature = "widget-spinner")]
WidgetKind::Spinner(_) => "spinner",
#[cfg(feature = "widget-spinbox")]
WidgetKind::Spinbox(_) => "spinbox",
#[cfg(feature = "widget-textarea")]
WidgetKind::Textarea(_) => "textarea",
#[cfg(feature = "widget-keyboard")]
WidgetKind::Keyboard(_) => "keyboard",
#[cfg(feature = "widget-chart")]
WidgetKind::Chart(_) => "chart",
#[cfg(feature = "widget-tabview")]
WidgetKind::Tabview(_) => "tabview",
#[cfg(feature = "widget-arc-label")]
WidgetKind::ArcLabel(_) => "arc_label",
#[cfg(feature = "widget-calendar")]
WidgetKind::Calendar(_) => "calendar",
#[cfg(feature = "widget-list")]
WidgetKind::List(_) => "list",
#[cfg(feature = "widget-table")]
WidgetKind::Table(_) => "table",
#[cfg(feature = "widget-tileview")]
WidgetKind::Tileview(_) => "tileview",
#[cfg(feature = "widget-win")]
WidgetKind::Win(_) => "win",
#[cfg(feature = "widget-msgbox")]
WidgetKind::Msgbox(_) => "msgbox",
#[cfg(feature = "widget-spangroup")]
WidgetKind::Spangroup(_) => "spangroup",
#[cfg(feature = "widget-buttonmatrix")]
WidgetKind::Buttonmatrix(_) => "buttonmatrix",
#[cfg(feature = "widget-canvas")]
WidgetKind::Canvas(_) => "canvas",
#[cfg(feature = "widget-imagebutton")]
WidgetKind::Imagebutton(_) => "imagebutton",
#[cfg(feature = "widget-animimg")]
WidgetKind::AnimImg(_) => "anim_img",
#[cfg(feature = "widget-menu")]
WidgetKind::Menu(_) => "menu",
}
}
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct StyleDef {
pub id: String,
#[serde(flatten)]
pub style: StyleProps,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct GradientDef {
pub id: String,
pub dir: String,
pub stops: Vec<GradientStop>,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct GradientStop {
pub color: Color,
pub position: u8,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct Page {
pub id: String,
pub skip: Option<bool>,
pub update_fn: Option<String>,
pub on_event_fn: Option<String>,
pub input_group: Option<InputGroup>,
pub input_group_fn: Option<String>,
pub budget: Option<BudgetDef>,
#[serde(flatten)]
pub common: CommonProps,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct BudgetDef {
#[cfg_attr(feature = "schema", schemars(range(min = 1)))]
pub max_objects: u32,
#[cfg_attr(feature = "schema", schemars(range(min = 1)))]
pub max_depth: Option<u16>,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct InputGroup {
pub members: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct ToastDef {
pub id: String,
pub duration_ms: Option<u32>,
pub persistent: Option<bool>,
pub params: Option<Vec<String>>,
pub budget: Option<BudgetDef>,
#[serde(flatten)]
pub common: CommonProps,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum ToastPreset {
Info,
Warn,
Error,
}
impl ToastPreset {
pub fn id(self) -> &'static str {
match self {
Self::Info => "info",
Self::Warn => "warn",
Self::Error => "error",
}
}
pub fn bg(self) -> u32 {
match self {
Self::Info => 0x2266cc,
Self::Warn => 0xcc7a00,
Self::Error => 0xaa2222,
}
}
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct UiDoc {
pub lvgl: LvglConfig,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct LvglConfig {
pub default_font: Option<String>,
pub color_depth: Option<u8>,
pub style_definitions: Option<Vec<StyleDef>>,
pub gradients: Option<Vec<GradientDef>>,
#[cfg_attr(feature = "schema", schemars(with = "Option<serde_json::Value>"))]
pub theme: Option<serde_yaml::Value>,
pub generate_view: Option<bool>,
pub toasts: Option<Vec<ToastDef>>,
pub toast_presets: Option<Vec<ToastPreset>>,
pub app_name: Option<String>,
#[serde(default)]
pub pages: Vec<Page>,
#[serde(skip)]
pub spdx_license: Option<String>,
}
#[cfg(feature = "schema")]
mod json_schema_impls {
use std::borrow::Cow;
use super::*;
impl schemars::JsonSchema for Color {
fn schema_name() -> Cow<'static, str> {
"Color".into()
}
fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
schemars::json_schema!({
"description": "Color: hex integer (0xRRGGBB), \"#RGB\"/\"#RRGGBB\" string, or palette name",
"anyOf": [
{ "type": "integer", "minimum": 0, "maximum": 16777215 },
{ "type": "string", "pattern": "^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$" },
{ "type": "string", "description": "Named palette color (e.g. red, blue)" }
]
})
}
}
impl schemars::JsonSchema for Dimension {
fn schema_name() -> Cow<'static, str> {
"Dimension".into()
}
fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
schemars::json_schema!({
"description": "Dimension: pixels (int), \"Npct\", or \"size_content\"",
"anyOf": [
{ "type": "integer" },
{ "type": "string", "pattern": "^-?\\d+pct$" },
{ "type": "string", "const": "size_content" }
]
})
}
}
impl schemars::JsonSchema for Opacity {
fn schema_name() -> Cow<'static, str> {
"Opacity".into()
}
fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
schemars::json_schema!({
"description": "Opacity: 0-255 integer, 0.0-1.0 float, or \"transp\"/\"cover\"",
"anyOf": [
{ "type": "integer", "minimum": 0, "maximum": 255 },
{ "type": "number", "minimum": 0.0, "maximum": 1.0 },
{ "type": "string" }
]
})
}
}
impl schemars::JsonSchema for TrackSize {
fn schema_name() -> Cow<'static, str> {
"TrackSize".into()
}
fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
schemars::json_schema!({
"description": "Grid track: pixels (int), \"Nfr\", or \"content\"",
"anyOf": [
{ "type": "integer" },
{ "type": "string", "pattern": "^\\d+fr$" },
{ "type": "string", "const": "content" }
]
})
}
}
}
#[cfg(feature = "schema")]
pub fn generated_json_schema() -> String {
let schema = schemars::schema_for!(UiDoc);
let mut value = serde_json::to_value(&schema).expect("schema serializes to JSON");
close_objects(&mut value);
let obj = value.as_object_mut().expect("schema root is an object");
obj.insert("$id".into(), "https://github.com/emobotics-dev/oxiforge/schemas/oxiforge.schema.json".into());
obj.insert("title".into(), "Oxiforge YAML UI Description".into());
obj.insert(
"description".into(),
"Schema for oxiforge YAML UI files that generate Rust code for oxivgl/LVGL UIs".into(),
);
let mut s = serde_json::to_string_pretty(&value).expect("schema renders");
s.push('\n');
s
}
#[cfg(feature = "schema")]
fn close_objects(v: &mut serde_json::Value) {
match v {
serde_json::Value::Object(map) => {
if map.contains_key("properties") && !map.contains_key("additionalProperties") {
map.insert("additionalProperties".into(), false.into());
}
for val in map.values_mut() {
close_objects(val);
}
}
serde_json::Value::Array(arr) => {
for val in arr {
close_objects(val);
}
}
_ => {}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_color_hex_int() {
let c: Color = serde_yaml::from_str("0xFF0000").unwrap();
assert_eq!(c, Color::Hex(0xFF0000));
}
#[test]
fn parse_color_css_hex6() {
let c: Color = serde_yaml::from_str("\"#FF0000\"").unwrap();
assert_eq!(c, Color::Hex(0xFF0000));
}
#[test]
fn parse_color_css_hex3() {
let c: Color = serde_yaml::from_str("\"#F00\"").unwrap();
assert_eq!(c, Color::Hex(0xFF0000));
}
#[test]
fn parse_color_named() {
let c: Color = serde_yaml::from_str("red").unwrap();
assert_eq!(c, Color::Named("red".into()));
}
#[test]
fn parse_dimension_pixels() {
let d: Dimension = serde_yaml::from_str("100").unwrap();
assert_eq!(d, Dimension::Pixels(100));
}
#[test]
fn parse_dimension_percent() {
let d: Dimension = serde_yaml::from_str("50pct").unwrap();
assert_eq!(d, Dimension::Percent(50));
}
#[test]
fn parse_dimension_size_content() {
let d: Dimension = serde_yaml::from_str("size_content").unwrap();
assert_eq!(d, Dimension::SizeContent);
}
#[test]
fn parse_alignment() {
let a: Alignment = serde_yaml::from_str("center").unwrap();
assert_eq!(a, Alignment::Center);
}
#[test]
fn parse_track_size_fr() {
let t: TrackSize = serde_yaml::from_str("2fr").unwrap();
assert_eq!(t, TrackSize::Fr(2));
}
#[test]
fn parse_track_size_content() {
let t: TrackSize = serde_yaml::from_str("content").unwrap();
assert_eq!(t, TrackSize::Content);
}
#[test]
fn parse_track_size_pixels() {
let t: TrackSize = serde_yaml::from_str("80").unwrap();
assert_eq!(t, TrackSize::Pixels(80));
}
#[test]
fn parse_opacity_float() {
let o: Opacity = serde_yaml::from_str("0.5").unwrap();
assert_eq!(o, Opacity::Value(127));
}
#[test]
fn parse_opacity_named() {
let o: Opacity = serde_yaml::from_str("cover").unwrap();
assert_eq!(o, Opacity::Named("cover".into()));
}
#[test]
fn parse_opacity_int() {
let o: Opacity = serde_yaml::from_str("128").unwrap();
assert_eq!(o, Opacity::Value(128));
}
#[test]
fn parse_color_negative_int() {
let c: Color = serde_yaml::from_str("-1").unwrap();
assert_eq!(c, Color::Hex(u32::MAX));
}
#[test]
fn parse_dimension_negative() {
let d: Dimension = serde_yaml::from_str("-5").unwrap();
assert_eq!(d, Dimension::Pixels(-5));
}
#[test]
fn parse_track_size_fr_large() {
let t: TrackSize = serde_yaml::from_str("3fr").unwrap();
assert_eq!(t, TrackSize::Fr(3));
}
#[test]
fn parse_all_widget_kinds_via_doc() {
let yaml = r#"
lvgl:
pages:
- id: main
widgets:
- obj: { id: a }
- label: { text: hi }
- button: { id: b }
- switch: { id: s }
- checkbox: { text: cb }
- slider: { value: 50 }
- arc: { value: 1.0 }
- bar: { value: 1.0 }
- dropdown: { selected: 0 }
- roller: { selected: 0 }
- scale: { range_min: 0 }
- led: { brightness: 200 }
- image: { src: "test.bin" }
- line: { id: l }
"#;
let doc: UiDoc = serde_yaml::from_str(yaml).unwrap();
let widgets = doc.lvgl.pages[0].common.widgets.as_ref().unwrap();
let names: Vec<&str> = widgets.iter().map(|w| w.type_name()).collect();
assert_eq!(
names,
vec![
"obj", "label", "button", "switch", "checkbox", "slider", "arc", "bar", "dropdown", "roller", "scale",
"led", "image", "line"
]
);
}
#[test]
fn parse_style_ref_single() {
let sr: StyleRef = serde_yaml::from_str("card").unwrap();
assert_eq!(sr.names(), vec!["card"]);
}
#[test]
fn parse_style_ref_multiple() {
let sr: StyleRef = serde_yaml::from_str("[card, dark]").unwrap();
assert_eq!(sr.names(), vec!["card", "dark"]);
}
#[test]
fn parse_grid_cell_defaults() {
let gc: GridCell = serde_yaml::from_str("column: 0\nrow: 1\n").unwrap();
assert_eq!(gc.column_span, 1);
assert_eq!(gc.row_span, 1);
}
}