beuvy 0.1.0

Facade crate for beuvy-runtime plus optional declarative UI authoring.
Documentation
use super::*;
use beuvy_runtime::utility::{UtilityFontFamilyRole, UtilityFontStyle, UtilityTextTransform};
use beuvy_runtime::text::{
    FontFamilyRole, TypographyFontStyle, TypographyTextTransform,
};

pub(crate) fn parse_node_style(
    node: XmlNode<'_, '_>,
) -> Result<DeclarativeNodeStyle, DeclarativeUiAssetLoadError> {
    let class_patch = parse_utility_class_patch(node)?;
    Ok(DeclarativeNodeStyle {
        width: map_utility_val(class_patch.width),
        height: map_utility_val(class_patch.height),
        min_width: map_utility_val(class_patch.min_width),
        min_height: map_utility_val(class_patch.min_height),
        max_width: map_utility_val(class_patch.max_width),
        max_height: map_utility_val(class_patch.max_height),
        flex_direction: class_patch.flex_direction.map(flex_direction_from_utility),
        justify_content: class_patch.justify_content.map(justify_from_utility),
        align_items: class_patch.align_items.map(align_items_from_utility),
        align_content: class_patch.align_content.map(align_content_from_utility),
        align_self: class_patch.align_self.map(align_self_from_utility),
        flex_wrap: class_patch.flex_wrap.map(flex_wrap_from_utility),
        flex_grow: class_patch.flex_grow,
        flex_shrink: class_patch.flex_shrink,
        flex_basis: map_utility_val(class_patch.flex_basis),
        row_gap: map_utility_val(class_patch.row_gap),
        column_gap: map_utility_val(class_patch.column_gap),
        padding: class_patch.padding.as_ref().map(rect_from_utility),
        margin: class_patch.margin.as_ref().map(rect_from_utility),
        border: class_patch.border.as_ref().map(rect_from_utility),
        border_radius: class_patch.border_radius.map(border_radius_from_utility),
        overflow_x: class_patch.overflow_x.map(overflow_from_utility),
        overflow_y: class_patch.overflow_y.map(overflow_from_utility),
        display: class_patch.display.map(display_from_utility),
        position_type: class_patch.position_type.map(position_from_utility),
        left: map_utility_val(class_patch.left),
        right: map_utility_val(class_patch.right),
        top: map_utility_val(class_patch.top),
        bottom: map_utility_val(class_patch.bottom),
    })
}

pub(crate) fn parse_text_style(
    node: XmlNode<'_, '_>,
    tag: &str,
) -> Result<DeclarativeTextStyle, DeclarativeUiAssetLoadError> {
    let class_patch = parse_utility_class_patch(node)?;
    Ok(DeclarativeTextStyle {
        size: class_patch
            .font_size
            .unwrap_or_else(|| default_text_size_for_tag(tag)),
        family_role: class_patch.font_family_role.map(font_family_role_from_utility),
        weight: class_patch.font_weight,
        font_style: class_patch.font_style.map(font_style_from_utility),
        line_height: class_patch.line_height,
        letter_spacing_em: class_patch.letter_spacing_em,
        text_transform: class_patch.text_transform.map(text_transform_from_utility),
        color: class_patch.visual.text_color.clone(),
        visual_style: visual_style_from_utility(&class_patch.visual),
        state_visual_styles: state_visual_styles_from_utility(&class_patch),
    })
}

pub(crate) fn parse_visual_style(
    node: XmlNode<'_, '_>,
) -> Result<DeclarativeVisualStyle, DeclarativeUiAssetLoadError> {
    let class_patch = parse_utility_class_patch(node)?;
    Ok(visual_style_from_utility(&class_patch.visual))
}

pub(crate) fn parse_state_visual_styles(
    node: XmlNode<'_, '_>,
) -> Result<DeclarativeStateVisualStyles, DeclarativeUiAssetLoadError> {
    let class_patch = parse_utility_class_patch(node)?;
    Ok(state_visual_styles_from_utility(&class_patch))
}

pub(crate) fn parse_utility_class_patch(
    node: XmlNode<'_, '_>,
) -> Result<UtilityStylePatch, DeclarativeUiAssetLoadError> {
    let classes = attr(node, "class").unwrap_or_default();
    crate::style::parse_style_classes(classes).map_err(|error| match error {
        DeclarativeUiAssetLoadError::InvalidDsl(reason) => {
            attr_error(node, "class", classes, &reason)
        }
        other => other,
    })
}

pub(crate) fn parse_class_bindings(
    node: XmlNode<'_, '_>,
    _state_specs: &BTreeMap<String, DeclarativeStateSpec>,
) -> Result<Vec<DeclarativeClassBinding>, DeclarativeUiAssetLoadError> {
    let Some(raw) = bound_attr(node, "class") else {
        return Ok(Vec::new());
    };
    let raw = raw.trim();
    if raw.is_empty() {
        return Ok(Vec::new());
    }
    Ok(vec![DeclarativeClassBinding::RuntimeExpr {
        expr: parse_runtime_expr(node, "v-bind-class", raw)?,
    }])
}

pub(super) fn map_utility_val(value: Option<UtilityVal>) -> Option<DeclarativeVal> {
    match value? {
        UtilityVal::Auto => Some(DeclarativeVal::Auto),
        UtilityVal::Px(value) => Some(DeclarativeVal::Px(value)),
        UtilityVal::Percent(value) => Some(DeclarativeVal::Percent(value)),
        UtilityVal::Vw(value) => Some(DeclarativeVal::Vw(value)),
        UtilityVal::Vh(value) => Some(DeclarativeVal::Vh(value)),
    }
}

pub(super) fn visual_style_from_utility(value: &UtilityVisualStylePatch) -> DeclarativeVisualStyle {
    DeclarativeVisualStyle {
        background_color: value.background_color.clone(),
        text_color: value.text_color.clone(),
        border_color: value.border_color.clone(),
        outline_width: map_utility_val(value.outline_width),
        outline_color: value.outline_color.clone(),
        opacity: value.opacity,
        transition_property: value
            .transition_property
            .map(transition_property_from_utility),
        transition_duration_ms: value.transition_duration_ms,
        transition_timing: value.transition_timing.map(transition_timing_from_utility),
    }
}

pub(super) fn state_visual_styles_from_utility(
    value: &UtilityStylePatch,
) -> DeclarativeStateVisualStyles {
    DeclarativeStateVisualStyles {
        hover: visual_style_from_utility(&value.hover),
        active: visual_style_from_utility(&value.active),
        focus: visual_style_from_utility(&value.focus),
    }
}

pub(super) fn transition_property_from_utility(
    value: UtilityTransitionProperty,
) -> DeclarativeTransitionProperty {
    value.into()
}

pub(super) fn transition_timing_from_utility(
    value: UtilityTransitionTiming,
) -> DeclarativeTransitionTiming {
    value.into()
}

pub(crate) fn font_family_role_from_utility(
    value: UtilityFontFamilyRole,
) -> FontFamilyRole {
    match value {
        UtilityFontFamilyRole::Sans => FontFamilyRole::Sans,
        UtilityFontFamilyRole::Serif => FontFamilyRole::Serif,
        UtilityFontFamilyRole::Mono => FontFamilyRole::Mono,
    }
}

pub(crate) fn font_style_from_utility(value: UtilityFontStyle) -> TypographyFontStyle {
    match value {
        UtilityFontStyle::Normal => TypographyFontStyle::Normal,
        UtilityFontStyle::Italic => TypographyFontStyle::Italic,
    }
}

pub(crate) fn text_transform_from_utility(
    value: UtilityTextTransform,
) -> TypographyTextTransform {
    match value {
        UtilityTextTransform::None => TypographyTextTransform::None,
        UtilityTextTransform::Uppercase => TypographyTextTransform::Uppercase,
        UtilityTextTransform::Lowercase => TypographyTextTransform::Lowercase,
        UtilityTextTransform::Capitalize => TypographyTextTransform::Capitalize,
    }
}

pub(super) fn rect_from_utility(rect: &beuvy_runtime::utility::UtilityRect) -> DeclarativeUiRect {
    DeclarativeUiRect {
        left: map_utility_val(rect.left),
        right: map_utility_val(rect.right),
        top: map_utility_val(rect.top),
        bottom: map_utility_val(rect.bottom),
    }
}

pub(super) fn border_radius_from_utility(value: UtilityVal) -> DeclarativeBorderRadius {
    DeclarativeBorderRadius::All {
        radius: map_utility_val(Some(value)).expect("utility border radius should map"),
    }
}

pub(super) fn flex_direction_from_utility(value: UtilityFlexDirection) -> DeclarativeFlexDirection {
    match value {
        UtilityFlexDirection::Row => DeclarativeFlexDirection::Row,
        UtilityFlexDirection::Column => DeclarativeFlexDirection::Column,
        UtilityFlexDirection::RowReverse => DeclarativeFlexDirection::RowReverse,
        UtilityFlexDirection::ColumnReverse => DeclarativeFlexDirection::ColumnReverse,
    }
}

pub(super) fn justify_from_utility(value: UtilityJustifyContent) -> DeclarativeJustifyContent {
    match value {
        UtilityJustifyContent::FlexStart => DeclarativeJustifyContent::FlexStart,
        UtilityJustifyContent::FlexEnd => DeclarativeJustifyContent::FlexEnd,
        UtilityJustifyContent::Center => DeclarativeJustifyContent::Center,
        UtilityJustifyContent::SpaceBetween => DeclarativeJustifyContent::SpaceBetween,
        UtilityJustifyContent::SpaceAround | UtilityJustifyContent::SpaceEvenly => {
            DeclarativeJustifyContent::SpaceAround
        }
    }
}

pub(super) fn align_items_from_utility(value: UtilityAlignItems) -> DeclarativeAlignItems {
    match value {
        UtilityAlignItems::FlexStart => DeclarativeAlignItems::FlexStart,
        UtilityAlignItems::FlexEnd => DeclarativeAlignItems::FlexEnd,
        UtilityAlignItems::Center => DeclarativeAlignItems::Center,
        UtilityAlignItems::Baseline => DeclarativeAlignItems::Baseline,
        UtilityAlignItems::Stretch => DeclarativeAlignItems::Stretch,
    }
}

pub(super) fn align_content_from_utility(value: UtilityAlignContent) -> DeclarativeAlignContent {
    match value {
        UtilityAlignContent::FlexStart => DeclarativeAlignContent::FlexStart,
        UtilityAlignContent::FlexEnd => DeclarativeAlignContent::FlexEnd,
        UtilityAlignContent::Center => DeclarativeAlignContent::Center,
        UtilityAlignContent::Stretch => DeclarativeAlignContent::Stretch,
        UtilityAlignContent::SpaceBetween => DeclarativeAlignContent::SpaceBetween,
        UtilityAlignContent::SpaceAround => DeclarativeAlignContent::SpaceAround,
        UtilityAlignContent::SpaceEvenly => DeclarativeAlignContent::SpaceEvenly,
    }
}

pub(super) fn align_self_from_utility(value: UtilityAlignSelf) -> DeclarativeAlignSelf {
    match value {
        UtilityAlignSelf::Auto => DeclarativeAlignSelf::Auto,
        UtilityAlignSelf::FlexStart => DeclarativeAlignSelf::FlexStart,
        UtilityAlignSelf::FlexEnd => DeclarativeAlignSelf::FlexEnd,
        UtilityAlignSelf::Center => DeclarativeAlignSelf::Center,
        UtilityAlignSelf::Baseline => DeclarativeAlignSelf::Baseline,
        UtilityAlignSelf::Stretch => DeclarativeAlignSelf::Stretch,
    }
}

pub(super) fn flex_wrap_from_utility(value: UtilityFlexWrap) -> DeclarativeFlexWrap {
    match value {
        UtilityFlexWrap::NoWrap => DeclarativeFlexWrap::NoWrap,
        UtilityFlexWrap::Wrap => DeclarativeFlexWrap::Wrap,
        UtilityFlexWrap::WrapReverse => DeclarativeFlexWrap::WrapReverse,
    }
}

pub(super) fn overflow_from_utility(value: UtilityOverflowAxis) -> DeclarativeOverflowAxis {
    match value {
        UtilityOverflowAxis::Visible => DeclarativeOverflowAxis::Visible,
        UtilityOverflowAxis::Clip => DeclarativeOverflowAxis::Clip,
        UtilityOverflowAxis::Hidden => DeclarativeOverflowAxis::Hidden,
        UtilityOverflowAxis::Scroll => DeclarativeOverflowAxis::Scroll,
    }
}

pub(super) fn display_from_utility(value: UtilityDisplay) -> DeclarativeDisplay {
    match value {
        UtilityDisplay::Flex => DeclarativeDisplay::Flex,
        UtilityDisplay::Grid => DeclarativeDisplay::Grid,
        UtilityDisplay::None => DeclarativeDisplay::None,
        UtilityDisplay::Block => DeclarativeDisplay::Block,
    }
}

pub(super) fn position_from_utility(value: UtilityPositionType) -> DeclarativePositionType {
    match value {
        UtilityPositionType::Relative => DeclarativePositionType::Relative,
        UtilityPositionType::Absolute => DeclarativePositionType::Absolute,
    }
}