beuvy-runtime 0.1.0

A low-level Bevy UI kit with reusable controls and utility-class styling.
Documentation
use crate::style::{
    ButtonTheme, InteractionStatePalette, InteractionVisualTheme, PopupConfig, UiThemeConfig,
};
use crate::stylesheet::{
    StyleSheetError, default_style_sheet, parse_style_classes_with_sheet, runtime_style_sheet,
};
use crate::utility::UtilityStylePatch;
use bevy::prelude::*;
use bevy::ui::BoxShadow;
use bevy::ui::Val::Px;

fn palette(config: &crate::style::StatePaletteConfig) -> InteractionStatePalette {
    InteractionStatePalette {
        idle: config.idle.to_bevy(),
        hover: config.hover.to_bevy(),
        active: config.active.to_bevy(),
        active_hover: config.active_hover.to_bevy(),
        disabled: config.disabled.to_bevy(),
    }
}

pub fn button_theme(config: &crate::style::ButtonConfig) -> ButtonTheme {
    ButtonTheme {
        background: palette(&config.background),
        border: palette(&config.border),
        text: palette(&config.text),
    }
}

pub fn control_interaction_theme() -> InteractionVisualTheme {
    let sheet = runtime_style_sheet();
    let interaction = &sheet.config().control.interaction;
    InteractionVisualTheme {
        hover_outline: interaction.hover_outline.to_bevy(),
        focus_outline: interaction.focus_outline.to_bevy(),
        focus_hover_outline: interaction.focus_hover_outline.to_bevy(),
    }
}

pub fn ui_theme() -> &'static UiThemeConfig {
    default_style_sheet().config()
}

pub fn regular_border_width() -> f32 {
    runtime_style_sheet().config().border.regular
}

pub fn font_size_control() -> f32 {
    runtime_style_sheet().config().typography.control
}

pub fn font_size_control_compact() -> f32 {
    runtime_style_sheet().config().typography.control_compact
}

pub fn tab_border_width() -> f32 {
    runtime_style_sheet().config().border.tab
}

pub fn emphasis_border_width() -> f32 {
    runtime_style_sheet().config().border.emphasis
}

pub fn focus_outline_width() -> f32 {
    runtime_style_sheet().config().border.focus_outline
}

pub fn divider_width() -> f32 {
    runtime_style_sheet().config().border.divider
}

pub fn scrollbar_width() -> f32 {
    runtime_style_sheet().config().spacing.scrollbar_width
}

pub fn regular_border() -> UiRect {
    UiRect::all(Px(regular_border_width()))
}

pub fn tab_border() -> UiRect {
    UiRect::all(Px(tab_border_width()))
}

pub fn emphasis_border() -> UiRect {
    UiRect::all(Px(emphasis_border_width()))
}

pub fn control_radius() -> BorderRadius {
    BorderRadius::all(Px(runtime_style_sheet().config().radius.control))
}

pub fn panel_radius() -> BorderRadius {
    BorderRadius::all(Px(runtime_style_sheet().config().radius.panel))
}

pub fn pill_radius() -> BorderRadius {
    BorderRadius::all(Px(runtime_style_sheet().config().radius.pill))
}

pub fn prompt_shadow() -> BoxShadow {
    let sheet = runtime_style_sheet();
    popup_shadow(&sheet.config().panel.popup)
}

pub fn input_caret_width() -> f32 {
    runtime_style_sheet().config().control.input.caret_width
}

pub fn input_caret_color() -> Color {
    runtime_style_sheet()
        .config()
        .control
        .input
        .caret_color
        .to_bevy()
}

pub fn input_selection_color() -> Color {
    runtime_style_sheet()
        .config()
        .control
        .input
        .selection_color
        .to_bevy()
}

pub fn panel_surface_background() -> BackgroundColor {
    BackgroundColor(
        runtime_style_sheet()
            .config()
            .panel
            .main
            .background
            .to_bevy(),
    )
}

pub fn subtle_surface_background() -> BackgroundColor {
    BackgroundColor(
        runtime_style_sheet()
            .config()
            .panel
            .subtle
            .background
            .to_bevy(),
    )
}

pub fn prompt_surface_background() -> BackgroundColor {
    BackgroundColor(
        runtime_style_sheet()
            .config()
            .panel
            .prompt
            .background
            .to_bevy(),
    )
}

pub fn text_primary_color() -> Color {
    runtime_style_sheet().config().text.primary.to_bevy()
}

pub fn text_secondary_color() -> Color {
    runtime_style_sheet().config().text.secondary.to_bevy()
}

pub fn text_disabled_color() -> Color {
    runtime_style_sheet().config().text.disabled.to_bevy()
}

pub fn text_placeholder_color() -> Color {
    runtime_style_sheet().config().text.placeholder.to_bevy()
}

pub fn checkbox_border_color() -> Color {
    runtime_style_sheet()
        .config()
        .control
        .checkbox
        .border
        .to_bevy()
}

pub fn checkbox_indicator_color() -> Color {
    runtime_style_sheet()
        .config()
        .control
        .checkbox
        .indicator
        .to_bevy()
}

pub fn tab_active_text_color() -> Color {
    runtime_style_sheet().config().text.tab_active.to_bevy()
}

pub fn ui_hover_outline_color() -> Color {
    control_interaction_theme().hover_outline
}

pub fn ui_focus_outline_color() -> Color {
    control_interaction_theme().focus_outline
}

pub fn ui_focus_outline_hover_color() -> Color {
    control_interaction_theme().focus_hover_outline
}

pub fn font_size_hint() -> f32 {
    runtime_style_sheet().config().typography.hint
}

pub fn font_size_meta() -> f32 {
    runtime_style_sheet().config().typography.meta
}

pub fn font_size_body() -> f32 {
    runtime_style_sheet().config().typography.body
}

pub fn font_size_title() -> f32 {
    runtime_style_sheet().config().typography.title
}

pub fn font_size_display() -> f32 {
    runtime_style_sheet().config().typography.display
}

pub fn resolve_color_value(raw: &str) -> Option<Color> {
    let sheet = runtime_style_sheet();
    resolve_color_value_with_config(sheet.config(), raw)
}

pub fn resolve_color_value_with_config(config: &UiThemeConfig, raw: &str) -> Option<Color> {
    crate::theme_config::resolve_theme_color_value_in(config, raw).map(|color| color.to_bevy())
}

pub fn merge_classes(default_classes: &str, extra_classes: Option<&str>) -> String {
    let extra_classes = extra_classes.unwrap_or_default().trim();
    if extra_classes.is_empty() {
        default_classes.trim().to_string()
    } else {
        format!("{} {}", default_classes.trim(), extra_classes)
    }
}

pub fn resolve_class_patch(classes: &str) -> Result<UtilityStylePatch, StyleSheetError> {
    let sheet = runtime_style_sheet();
    parse_style_classes_with_sheet(&sheet, classes)
}

pub fn sans_font_asset_path() -> String {
    let sheet = runtime_style_sheet();
    crate::theme_config::ui_theme_asset_path(&sheet.config().font.sans)
}

pub fn serif_font_asset_path() -> String {
    let sheet = runtime_style_sheet();
    crate::theme_config::ui_theme_asset_path(&sheet.config().font.serif)
}

pub fn mono_font_asset_path() -> String {
    let sheet = runtime_style_sheet();
    crate::theme_config::ui_theme_asset_path(&sheet.config().font.mono)
}

pub fn form_item_compact_width() -> f32 {
    runtime_style_sheet()
        .config()
        .responsive
        .form_item_compact_width
}

pub fn resolve_classes_with_fallback(
    default_classes: &str,
    extra_classes: Option<&str>,
    context: &str,
) -> UtilityStylePatch {
    let merged = merge_classes(default_classes, extra_classes);
    match resolve_class_patch(&merged) {
        Ok(patch) => patch,
        Err(error) => {
            let extra_classes = extra_classes.unwrap_or_default().trim();
            if !extra_classes.is_empty() {
                warn!(
                    "failed to resolve {context} classes `{merged}`: {}; falling back to `{default_classes}`",
                    error.reason
                );
                return resolve_class_patch_or_empty(default_classes, context);
            }

            warn!(
                "failed to resolve default {context} classes `{default_classes}`: {}; using empty patch",
                error.reason
            );
            UtilityStylePatch::default()
        }
    }
}

pub fn resolve_class_patch_or_empty(classes: &str, context: &str) -> UtilityStylePatch {
    match resolve_class_patch(classes) {
        Ok(patch) => patch,
        Err(error) => {
            warn!(
                "failed to resolve {context} classes `{classes}`: {}; using empty patch",
                error.reason
            );
            UtilityStylePatch::default()
        }
    }
}

fn popup_shadow(config: &PopupConfig) -> BoxShadow {
    BoxShadow::new(
        config.shadow_color.to_bevy(),
        Px(0.0),
        Px(0.0),
        Px(0.0),
        Px(config.shadow_blur),
    )
}