fui-rs 0.2.3-alpha1

Retained-mode Rust UI SDK for EffinDOM
Documentation
use super::pressable_indicator_presenter::{
    PressableIndicatorMetrics, PressableIndicatorPresenter, PressableIndicatorVisualState,
};
use crate::controls::{LabeledControlColors, LabeledControlSizing};
use crate::ffi::{AlignItems, PositionType, Unit};
use crate::node::{flex_box, FlexBox};
use crate::theme::Theme;
use std::rc::Rc;

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct SwitchIndicatorVisualState {
    pub checked: bool,
    pub hovered: bool,
    pub pressed: bool,
    pub focused: bool,
    pub enabled: bool,
}

impl SwitchIndicatorVisualState {
    pub fn new(checked: bool, hovered: bool, pressed: bool, focused: bool, enabled: bool) -> Self {
        Self {
            checked,
            hovered,
            pressed,
            focused,
            enabled,
        }
    }

    pub fn pressable_state(&self) -> PressableIndicatorVisualState {
        PressableIndicatorVisualState {
            hovered: self.hovered,
            pressed: self.pressed,
            focused: self.focused,
            enabled: self.enabled,
        }
    }
}

pub trait SwitchIndicatorPresenter: PressableIndicatorPresenter {
    fn apply(
        &self,
        theme: Theme,
        state: SwitchIndicatorVisualState,
        colors: Option<LabeledControlColors>,
    );
}

pub trait SwitchIndicatorTemplate {
    fn create(&self, sizing: Option<LabeledControlSizing>) -> Rc<dyn SwitchIndicatorPresenter>;
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub struct SwitchIndicatorMetrics {
    pub track_width: f32,
    pub track_height: f32,
    pub thumb_size: f32,
    pub thumb_x: f32,
    pub thumb_checked_x: f32,
    pub thumb_y: f32,
}

impl SwitchIndicatorMetrics {
    pub const fn new(
        track_width: f32,
        track_height: f32,
        thumb_size: f32,
        thumb_x: f32,
        thumb_checked_x: f32,
        thumb_y: f32,
    ) -> Self {
        Self {
            track_width,
            track_height,
            thumb_size,
            thumb_x,
            thumb_checked_x,
            thumb_y,
        }
    }
}

const DEFAULT_SWITCH_METRICS: SwitchIndicatorMetrics =
    SwitchIndicatorMetrics::new(44.0, 26.0, 20.0, 3.0, 21.0, 2.0);

pub fn resolve_switch_metrics(sizing: Option<LabeledControlSizing>) -> SwitchIndicatorMetrics {
    let Some(sizing) = sizing else {
        return DEFAULT_SWITCH_METRICS;
    };
    if !sizing.has_indicator_size() {
        return DEFAULT_SWITCH_METRICS;
    }
    let scale = sizing.indicator_size_px() / DEFAULT_SWITCH_METRICS.track_height;
    let track_width = DEFAULT_SWITCH_METRICS.track_width * scale;
    let track_height = DEFAULT_SWITCH_METRICS.track_height * scale;
    let thumb_size = DEFAULT_SWITCH_METRICS.thumb_size * scale;
    let thumb_x = DEFAULT_SWITCH_METRICS.thumb_x * scale;
    let thumb_y = DEFAULT_SWITCH_METRICS.thumb_y * scale;
    SwitchIndicatorMetrics::new(
        track_width,
        track_height,
        thumb_size,
        thumb_x,
        track_width - thumb_size - thumb_x,
        thumb_y,
    )
}

#[derive(Clone)]
pub struct DefaultSwitchIndicatorPresenter {
    root: FlexBox,
    metrics: SwitchIndicatorMetrics,
    thumb_node: FlexBox,
}

impl DefaultSwitchIndicatorPresenter {
    pub fn new(metrics: SwitchIndicatorMetrics) -> Self {
        let root = flex_box();
        root.width(metrics.track_width, Unit::Pixel)
            .height(metrics.track_height, Unit::Pixel)
            .clip_to_bounds(true);
        let thumb_node = flex_box();
        thumb_node
            .position_type(PositionType::Absolute)
            .position(metrics.thumb_x, metrics.thumb_y)
            .width(metrics.thumb_size, Unit::Pixel)
            .height(metrics.thumb_size, Unit::Pixel);
        root.align_items(AlignItems::Center).child(&thumb_node);
        Self {
            root,
            metrics,
            thumb_node,
        }
    }
}

impl PressableIndicatorPresenter for DefaultSwitchIndicatorPresenter {
    fn root(&self) -> FlexBox {
        self.root.clone()
    }

    fn metrics(&self) -> PressableIndicatorMetrics {
        PressableIndicatorMetrics::new(self.metrics.track_width, self.metrics.track_height)
    }
}

impl SwitchIndicatorPresenter for DefaultSwitchIndicatorPresenter {
    fn apply(
        &self,
        theme: Theme,
        state: SwitchIndicatorVisualState,
        colors: Option<LabeledControlColors>,
    ) {
        let accent = colors
            .filter(|colors| colors.has_accent())
            .map(|colors| colors.accent_color())
            .unwrap_or_else(|| {
                if state.pressed {
                    theme.colors.accent_pressed
                } else if state.hovered {
                    theme.colors.accent_hovered
                } else {
                    theme.colors.accent
                }
            });
        let track_color = if state.checked {
            accent
        } else {
            colors
                .filter(|colors| colors.has_background())
                .map(|colors| colors.background_color())
                .unwrap_or_else(|| {
                    if state.hovered {
                        theme.colors.background
                    } else {
                        theme.colors.surface
                    }
                })
        };
        let border_color = colors
            .filter(|colors| colors.has_border())
            .map(|colors| colors.border_color())
            .unwrap_or_else(|| {
                if state.checked {
                    track_color
                } else {
                    theme.colors.border
                }
            });
        let metrics = self.metrics;
        self.root
            .corner_radius(metrics.track_height * 0.5)
            .border(1.0, border_color)
            .bg_color(track_color);
        self.thumb_node
            .position(
                if state.checked {
                    metrics.thumb_checked_x
                } else {
                    metrics.thumb_x
                },
                metrics.thumb_y,
            )
            .width(metrics.thumb_size, Unit::Pixel)
            .height(metrics.thumb_size, Unit::Pixel)
            .corner_radius(metrics.thumb_size * 0.5)
            .bg_color(
                colors
                    .filter(|colors| colors.has_background())
                    .map(|colors| colors.background_color())
                    .unwrap_or(theme.colors.surface),
            )
            .border(1.0, border_color);
    }
}

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

impl SwitchIndicatorTemplate for DefaultSwitchIndicatorTemplate {
    fn create(&self, sizing: Option<LabeledControlSizing>) -> Rc<dyn SwitchIndicatorPresenter> {
        create_default_switch_indicator_presenter(sizing)
    }
}

pub const DEFAULT_SWITCH_INDICATOR_TEMPLATE: DefaultSwitchIndicatorTemplate =
    DefaultSwitchIndicatorTemplate;

pub fn create_default_switch_indicator_presenter(
    sizing: Option<LabeledControlSizing>,
) -> Rc<dyn SwitchIndicatorPresenter> {
    Rc::new(DefaultSwitchIndicatorPresenter::new(
        resolve_switch_metrics(sizing),
    ))
}