fui-rs 0.2.3

Retained-mode Rust UI SDK for EffinDOM
Documentation
use crate::controls::{DropdownColors, DropdownSizing};
use crate::ffi::{AlignItems, TextVerticalAlign};
use crate::node::{flex_box, text, FlexBox, TextNode};
use crate::theme::Theme;
use std::rc::Rc;

#[derive(Clone, Copy, Debug, PartialEq)]
pub struct DropdownOptionRowMetrics {
    pub height: f32,
    pub padding_left: f32,
    pub padding_right: f32,
    pub font_size: f32,
}

impl DropdownOptionRowMetrics {
    pub const fn new(height: f32, padding_left: f32, padding_right: f32, font_size: f32) -> Self {
        Self {
            height,
            padding_left,
            padding_right,
            font_size,
        }
    }
}

pub const DEFAULT_DROPDOWN_OPTION_ROW_METRICS: DropdownOptionRowMetrics =
    DropdownOptionRowMetrics::new(34.0, 10.0, 10.0, 16.0);

fn resolve_option_row_metrics(sizing: Option<DropdownSizing>) -> DropdownOptionRowMetrics {
    let Some(sizing) = sizing else {
        return DEFAULT_DROPDOWN_OPTION_ROW_METRICS;
    };
    if !sizing.has_option_height() && !sizing.has_option_font_size() {
        return DEFAULT_DROPDOWN_OPTION_ROW_METRICS;
    }
    let font_size = if sizing.has_option_font_size() {
        sizing.option_font_size_px()
    } else {
        DEFAULT_DROPDOWN_OPTION_ROW_METRICS.font_size
    };
    let height = if sizing.has_option_height() {
        sizing.option_height_px()
    } else {
        DEFAULT_DROPDOWN_OPTION_ROW_METRICS.height
    };
    DropdownOptionRowMetrics::new(
        height,
        DEFAULT_DROPDOWN_OPTION_ROW_METRICS.padding_left,
        DEFAULT_DROPDOWN_OPTION_ROW_METRICS.padding_right,
        font_size,
    )
}

#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct DropdownOptionRowVisualState {
    pub highlighted: bool,
    pub selected: bool,
    pub enabled: bool,
}

impl DropdownOptionRowVisualState {
    pub const fn new(highlighted: bool, selected: bool, enabled: bool) -> Self {
        Self {
            highlighted,
            selected,
            enabled,
        }
    }
}

pub trait DropdownOptionRowPresenter {
    fn root(&self) -> FlexBox;
    fn label_node(&self) -> TextNode;
    fn metrics(&self) -> DropdownOptionRowMetrics;
    fn apply(
        &self,
        theme: Theme,
        state: DropdownOptionRowVisualState,
        colors: Option<DropdownColors>,
    );
}

pub trait DropdownOptionRowTemplate {
    fn create(&self, sizing: Option<DropdownSizing>) -> Rc<dyn DropdownOptionRowPresenter>;
}

#[derive(Clone)]
pub struct DefaultDropdownOptionRowPresenter {
    root: FlexBox,
    label_node: TextNode,
    metrics: DropdownOptionRowMetrics,
}

impl DefaultDropdownOptionRowPresenter {
    pub fn new(metrics: DropdownOptionRowMetrics) -> Self {
        let label_node = text("");
        label_node
            .selectable(false)
            .fill_size()
            .text_limits(0, 1)
            .wrapping(false);
        label_node
            .text_overflow_fade(true, false)
            .text_vertical_align(TextVerticalAlign::Center);
        let root = flex_box();
        root.fill_size()
            .align_items(AlignItems::Center)
            .child(&label_node);
        Self {
            root,
            label_node: label_node.clone(),
            metrics,
        }
    }
}

impl DropdownOptionRowPresenter for DefaultDropdownOptionRowPresenter {
    fn root(&self) -> FlexBox {
        self.root.clone()
    }

    fn label_node(&self) -> TextNode {
        self.label_node.clone()
    }

    fn metrics(&self) -> DropdownOptionRowMetrics {
        self.metrics
    }

    fn apply(
        &self,
        theme: Theme,
        state: DropdownOptionRowVisualState,
        colors: Option<DropdownColors>,
    ) {
        let metrics = self.metrics;
        self.root
            .padding(metrics.padding_left, 0.0, metrics.padding_right, 0.0)
            .corner_radius(theme.spacing.xs)
            .bg_color(if state.highlighted {
                theme.context_menu.item.hover_background
            } else {
                0x00000000
            });
        let label_color = if !state.enabled {
            theme.colors.text_muted
        } else if state.selected {
            colors
                .filter(|colors| colors.has_accent())
                .map(|colors| colors.accent_color())
                .unwrap_or(theme.colors.accent)
        } else {
            colors
                .filter(|colors| colors.has_text_primary())
                .map(|colors| colors.text_primary_color())
                .unwrap_or(theme.colors.text_primary)
        };
        self.label_node
            .font_family(theme.fonts.body_family.clone())
            .font_size(metrics.font_size)
            .text_color(label_color);
    }
}

#[derive(Clone, Copy, Debug, Default)]
pub struct DefaultDropdownOptionRowTemplate;

impl DropdownOptionRowTemplate for DefaultDropdownOptionRowTemplate {
    fn create(&self, sizing: Option<DropdownSizing>) -> Rc<dyn DropdownOptionRowPresenter> {
        create_default_dropdown_option_row_presenter(sizing)
    }
}

pub const DEFAULT_DROPDOWN_OPTION_ROW_TEMPLATE: DefaultDropdownOptionRowTemplate =
    DefaultDropdownOptionRowTemplate;

pub fn create_default_dropdown_option_row_presenter(
    sizing: Option<DropdownSizing>,
) -> Rc<dyn DropdownOptionRowPresenter> {
    Rc::new(DefaultDropdownOptionRowPresenter::new(
        resolve_option_row_metrics(sizing),
    ))
}