godoru 0.1.0

UI Framework for Rust using Godot
use super::{Color, Style, VisualState, mergeStyle};

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Component {
    Container,
    Text,
    Button,
    Image,
    Slider,
    Scroll,
    TextInput,
    Checkbox,
    ProgressBar,
    Modal,
}

#[derive(Clone, Debug, PartialEq)]
pub struct ThemeClass {
    pub component: Component,
    pub name: String,
    pub style: Style,
}

#[derive(Clone, Debug, PartialEq)]
pub struct AppTheme {
    pub backgroundColor: Color,
    pub classes: Vec<ThemeClass>,
}

impl AppTheme {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn backgroundColor(mut self, color: Color) -> Self {
        self.backgroundColor = color;
        self
    }

    pub fn containerColor(mut self, color: Color) -> Self {
        self = self.class(Component::Container, "default", |style| {
            style.background(color);
        });
        self
    }

    pub fn accentColor(mut self, color: Color) -> Self {
        self = self.class(Component::Container, "accent", |style| {
            style.background(color);
        });
        self
    }

    pub fn class(
        mut self,
        component: Component,
        name: impl Into<String>,
        configure: impl FnOnce(&mut Style),
    ) -> Self {
        let name = name.into();
        let mut style = Style::default();
        configure(&mut style);
        if let Some(existing) = self
            .classes
            .iter_mut()
            .find(|item| item.component == component && item.name == name)
        {
            existing.style = style;
        } else {
            self.classes.push(ThemeClass {
                component,
                name,
                style,
            });
        }
        self
    }

    pub fn styleFor(&self, component: Component, class: Option<&str>) -> Style {
        self.styleForDepth(component, class, 0)
    }

    pub fn styleForState(
        &self,
        component: Component,
        class: Option<&str>,
        state: VisualState,
    ) -> Style {
        let mut style = self.styleFor(component.clone(), class);
        if state == VisualState::Normal {
            return style;
        }
        if let Some(class) = class {
            let pseudo = format!("{class}:{}", state.name());
            let pseudoStyle = self.styleFor(component, Some(&pseudo));
            mergeStyle(&mut style, &pseudoStyle);
        }
        style
    }

    fn styleForDepth(&self, component: Component, class: Option<&str>, depth: usize) -> Style {
        if depth > 16 {
            return Style::default();
        }

        let Some(class) = class else {
            return Style::default();
        };
        let Some(item) = self
            .classes
            .iter()
            .find(|item| item.component == component && item.name == class)
        else {
            return Style::default();
        };
        let mut style =
            self.styleForDepth(component.clone(), item.style.extends.as_deref(), depth + 1);
        mergeStyle(&mut style, &item.style);
        style
    }
}

impl VisualState {
    pub fn name(&self) -> &'static str {
        match self {
            VisualState::Normal => "normal",
            VisualState::Hover => "hover",
            VisualState::Pressed => "pressed",
            VisualState::Focus => "focus",
            VisualState::Checked => "checked",
            VisualState::Disabled => "disabled",
        }
    }
}

impl Default for AppTheme {
    fn default() -> Self {
        Self {
            backgroundColor: Color::rgb(0.08, 0.09, 0.11),
            classes: Vec::new(),
        }
    }
}