mottes-floem-components 0.1.0

Some styled components for floem.
Documentation
//! # Classes
//!
//! Classes that you can apply to floem components. Make sure you use `construct_theme`
//! or the classes won't be usable!
//!
//! # Example
//!
//! ```no_run
//! # use mottes_floem_components::classes::{construct_theme, Button};
//! # use mottes_floem_components::theme::Theme;
//! use floem::views::{button, Decorators};
//!
//! button("Button").class(Button).style(|_| construct_theme(Theme::dark()));
//! ```

use floem::{
    peniko::Color,
    style::{CursorStyle, Style, StyleValue},
    style_class,
    views::{PlaceholderTextClass, RadioButtonClass, RadioButtonDotClass},
};

use crate::{theme::Theme, BORDER_RADIUS};

style_class!(pub Button);
style_class!(pub Label);
style_class!(pub TextInput);
style_class!(pub ToggleButton);
style_class!(pub RadioButton);
style_class!(pub BadgeOne);
style_class!(pub BadgeTwo);
style_class!(pub BadgeThree);
style_class!(pub BadgeFour);
style_class!(pub BadgeFive);

fn parse_color(color: &str) -> Color {
    #[allow(clippy::expect_fun_call)]
    Color::parse(color).expect(format!("failed to parse {color}").as_str())
}

/// Make classes available to a component using a given color theme.
pub fn construct_theme(theme: Theme) -> Style {
    Style::new()
        .class(Button, button(theme))
        .class(Label, label(theme))
        .class(TextInput, text_input(theme))
        .class(ToggleButton, toggle_button(theme))
        .class(RadioButton, radio_button(theme))
        .class(
            BadgeOne,
            badge(parse_color(theme.base00), parse_color(theme.base0a)),
        )
        .class(
            BadgeTwo,
            badge(parse_color(theme.base00), parse_color(theme.base0b)),
        )
        .class(
            BadgeThree,
            badge(parse_color(theme.base00), parse_color(theme.base0c)),
        )
        .class(
            BadgeFour,
            badge(parse_color(theme.base00), parse_color(theme.base0d)),
        )
        .class(BadgeFive, |s| {
            let badge_style =
                badge(parse_color(theme.base03), parse_color(theme.base00))(s.clone());
            s.apply(badge_style)
                .border(0.5)
                .border_color(parse_color(theme.base03))
        })
}

fn button(theme: Theme) -> impl FnOnce(Style) -> Style {
    |s| {
        s.border_radius(BORDER_RADIUS)
            .border(1.0)
            .border_color(parse_color(theme.base03))
            .background(parse_color(theme.base00))
            .color(parse_color(theme.base05))
            .hover(|s| {
                s.background(parse_color(theme.base01))
                    .color(parse_color(theme.base05))
            })
            .active(|s| {
                s.background(parse_color(theme.base0a))
                    .color(parse_color(theme.base00))
            })
            .focus(|s| {
                s.background(parse_color(theme.base01))
                    .color(parse_color(theme.base05))
                    .hover(|s| s.background(parse_color(theme.base00)))
                    .border_color(parse_color(theme.base03))
            })
            .disabled(|s| s.background(parse_color(theme.base01).multiply_alpha(0.6)))
    }
}

fn label(theme: Theme) -> impl FnOnce(Style) -> Style {
    |s| s.color(parse_color(theme.base05))
}

fn text_input(theme: Theme) -> impl FnOnce(Style) -> Style {
    |s| {
        s.color(parse_color(theme.base05))
            .border_radius(BORDER_RADIUS)
            .border_color(parse_color(theme.base03))
            .background(parse_color(theme.base00))
            .hover(|s| s.background(parse_color(theme.base00)))
            .focus(|s| {
                s.hover(|s| s.background(parse_color(theme.base00)))
                    .border_color(parse_color(theme.base0a))
            })
            .cursor_color(StyleValue::Val(parse_color(theme.base05).into()))
            .cursor(CursorStyle::Text)
            .disabled(|s| {
                s.color(parse_color(theme.base05).multiply_alpha(0.6))
                    .background(parse_color(theme.base03).multiply_alpha(0.6))
            })
            .class(PlaceholderTextClass, |s| s.color(parse_color(theme.base03)))
    }
}

fn toggle_button(theme: Theme) -> impl FnOnce(Style) -> Style {
    |s| {
        {
            s.border_radius(BORDER_RADIUS)
                .background(parse_color(theme.base00))
                .color(parse_color(theme.base05))
                .border_color(parse_color(theme.base03))
                .hover(|s| s.background(parse_color(theme.base00)))
                .focus(|s| {
                    s.hover(|s| s.background(parse_color(theme.base00)))
                        .border_color(parse_color(theme.base0a))
                })
                .disabled(|s| {
                    s.color(parse_color(theme.base05).multiply_alpha(0.6))
                        .background(parse_color(theme.base03).multiply_alpha(0.6))
                })
                .active(|s| s.background(parse_color(theme.base02)))
            // TODO: find a way to change the background color if toggled
        }
    }
}

fn radio_button(theme: Theme) -> impl FnOnce(Style) -> Style {
    |s| {
        s.class(RadioButtonClass, |s| {
            s.background(parse_color(theme.base00))
                .border_color(parse_color(theme.base03))
                .hover(|s| s.background(parse_color(theme.base00)))
                .focus(|s| {
                    s.hover(|s| s.background(parse_color(theme.base00)))
                        .border_color(parse_color(theme.base0a))
                })
                .disabled(|s| {
                    s.color(parse_color(theme.base05).multiply_alpha(0.6))
                        .background(parse_color(theme.base03).multiply_alpha(0.6))
                })
                .active(|s| s.background(parse_color(theme.base02)))
        })
        .class(RadioButtonDotClass, |s| {
            s.background(parse_color(theme.base0a))
        })
    }
}

fn badge(text_color: Color, badge_color: Color) -> impl FnOnce(Style) -> Style {
    move |s| {
        s.border_radius(BORDER_RADIUS)
            .color(text_color)
            .background(badge_color)
            .height(17)
            .width(75)
            .padding_top(2)
            .padding_left(5)
    }
}