fui-rs 0.2.3

Retained-mode Rust UI SDK for EffinDOM
Documentation
use super::internal::pressable_labeled_control::{
    PressableLabeledControlState, WeakPressableLabeledControl,
};
use super::internal::switch_indicator_presenter::{
    create_default_switch_indicator_presenter, SwitchIndicatorPresenter, SwitchIndicatorTemplate,
    SwitchIndicatorVisualState,
};
use super::*;
use std::cell::{Cell, RefCell};
use std::rc::Rc;

fn create_indicator_presenter(
    template: Option<Rc<dyn SwitchIndicatorTemplate>>,
    sizing: Option<LabeledControlSizing>,
) -> Rc<dyn SwitchIndicatorPresenter> {
    if let Some(template) = template {
        return template.create(sizing);
    }
    if let Some(template_set) = get_control_templates() {
        if let Some(template) = template_set.switch_indicator {
            return template.create(sizing);
        }
    }
    create_default_switch_indicator_presenter(sizing)
}

#[derive(Clone)]
pub struct Switch {
    base: PressableLabeledControl,
    root: FlexBox,
    indicator_presenter: Rc<RefCell<Rc<dyn SwitchIndicatorPresenter>>>,
    template_override: Rc<RefCell<Option<Rc<dyn SwitchIndicatorTemplate>>>>,
    sizing_value: Rc<Cell<Option<LabeledControlSizing>>>,
    colors_value: Rc<Cell<Option<LabeledControlColors>>>,
    checked: Rc<Cell<bool>>,
    changed: Rc<RefCell<Option<SwitchChangedCallback>>>,
    weak_root: Rc<WeakNodeRef>,
}

impl Switch {
    pub fn new(label: impl Into<String>) -> Self {
        let label = label.into();
        let indicator_presenter = create_indicator_presenter(None, None);
        let base =
            PressableLabeledControl::new(SemanticRole::Switch, label, indicator_presenter.root());
        let root = base.root();
        let weak_root = Rc::new(root.node_ref().downgrade());
        let control = Self {
            base,
            root,
            indicator_presenter: Rc::new(RefCell::new(indicator_presenter)),
            template_override: Rc::new(RefCell::new(None)),
            sizing_value: Rc::new(Cell::new(None)),
            colors_value: Rc::new(Cell::new(None)),
            checked: Rc::new(Cell::new(false)),
            changed: Rc::new(RefCell::new(None)),
            weak_root,
        };
        control.root.semantic_checked(SemanticCheckedState::False);
        let activation = SwitchEventTarget::from_switch(&control);
        control.base.set_activation(move |state| {
            activation.activate(state);
        });
        let state_sync = SwitchEventTarget::from_switch(&control);
        control.base.set_state_changed(move |state| {
            state_sync.sync_visual(state, false, false);
        });
        let snapshot = control.base.state_snapshotter();
        let presenter = control.indicator_presenter.clone();
        let weak_root = control.weak_root.clone();
        let checked = control.checked.clone();
        let changed = control.changed.clone();
        let colors_value = control.colors_value.clone();
        control.persist_state(crate::persisted::persisted_value_adapter(
            "switch-checked",
            crate::persisted::PersistedBoolCodec,
            1,
            {
                let checked = checked.clone();
                move || Some(checked.get())
            },
            move |next| {
                apply_switch_checked(
                    &presenter,
                    &weak_root,
                    &checked,
                    next,
                    true,
                    false,
                    &changed,
                    colors_value.get(),
                    snapshot(),
                );
            },
        ));
        control.sync_visual(control.base.snapshot_state(), false, false);
        control
    }

    pub fn checked(&self, checked: bool) -> &Self {
        self.check(checked);
        self
    }

    pub fn check(&self, checked: bool) -> &Self {
        apply_switch_checked(
            &self.indicator_presenter,
            &self.weak_root,
            &self.checked,
            checked,
            true,
            false,
            &self.changed,
            self.colors_value.get(),
            self.base.snapshot_state(),
        );
        self
    }

    pub fn sizing(&self, sizing: LabeledControlSizing) -> &Self {
        self.set_sizing(Some(sizing))
    }

    pub fn clear_sizing(&self) -> &Self {
        self.set_sizing(None)
    }

    fn set_sizing(&self, sizing: Option<LabeledControlSizing>) -> &Self {
        self.sizing_value.set(sizing);
        self.base.set_label_font_size_override(
            sizing
                .filter(|sizing| sizing.has_label_font_size())
                .map(|sizing| sizing.label_font_size_px())
                .unwrap_or(0.0),
        );
        self.replace_indicator_presenter(create_indicator_presenter(
            self.template_override.borrow().clone(),
            self.sizing_value.get(),
        ));
        self.sync_visual(self.base.snapshot_state(), false, false);
        self
    }

    pub fn template(&self, template: Rc<dyn SwitchIndicatorTemplate>) -> &Self {
        self.set_template(Some(template))
    }

    pub fn clear_template(&self) -> &Self {
        self.set_template(None)
    }

    fn set_template(&self, template: Option<Rc<dyn SwitchIndicatorTemplate>>) -> &Self {
        self.template_override.replace(template.clone());
        self.replace_indicator_presenter(create_indicator_presenter(
            template,
            self.sizing_value.get(),
        ));
        self.sync_visual(self.base.snapshot_state(), false, false);
        self
    }

    pub fn colors(&self, colors: LabeledControlColors) -> &Self {
        self.set_colors(Some(colors))
    }

    pub fn clear_colors(&self) -> &Self {
        self.set_colors(None)
    }

    fn set_colors(&self, colors: Option<LabeledControlColors>) -> &Self {
        self.colors_value.set(colors);
        self.base.colors(colors);
        self.sync_visual(self.base.snapshot_state(), false, false);
        self
    }

    pub fn is_checked(&self) -> bool {
        self.checked.get()
    }

    pub fn enabled(&self, enabled: bool) -> &Self {
        self.base.enabled(enabled);
        self
    }

    pub fn on_changed(&self, handler: impl Fn(SwitchChangedEventArgs) + 'static) -> &Self {
        *self.changed.borrow_mut() = Some(Rc::new(handler));
        self
    }

    fn replace_indicator_presenter(&self, next_presenter: Rc<dyn SwitchIndicatorPresenter>) {
        let previous = self.indicator_presenter.borrow().clone();
        if Rc::ptr_eq(&previous, &next_presenter) {
            return;
        }
        self.base.replace_indicator_root(next_presenter.root());
        self.indicator_presenter.replace(next_presenter);
    }

    fn sync_visual(&self, base_state: PressableLabeledControlState, emit: bool, announce: bool) {
        apply_switch_checked(
            &self.indicator_presenter,
            &self.weak_root,
            &self.checked,
            self.checked.get(),
            emit,
            announce,
            &self.changed,
            self.colors_value.get(),
            base_state,
        );
    }
}

impl Clickable for Switch {
    fn on_click(&self, handler: impl Fn(ClickEventArgs) + 'static) -> &Self {
        self.base.on_click(handler);
        self
    }
}

impl LabeledControlTextStyle for Switch {
    fn set_label_font_family(&self, family: crate::FontFamily) {
        self.base.font_family(family);
    }

    fn set_label_font_size(&self, size: f32) {
        self.base.font_size(size);
    }

    fn set_label_text_color(&self, color: u32) {
        self.base.text_color(color);
    }
}

impl Node for Switch {
    fn retained_node_ref(&self) -> NodeRef {
        self.root.retained_node_ref()
    }

    fn build_self(&self) {
        self.sync_visual(self.base.snapshot_state(), false, false);
        self.root.build_self();
    }
}

impl HasFlexBoxRoot for Switch {
    fn flex_box_root(&self) -> &FlexBox {
        &self.root
    }
}

impl ThemeBindable for Switch {
    fn theme_binding_node(&self) -> NodeRef {
        self.root.retained_node_ref()
    }

    fn weak_theme_target(&self) -> Box<dyn Fn() -> Option<Self>> {
        let target = SwitchEventTarget::from_switch(self);
        Box::new(move || target.upgrade())
    }
}

#[derive(Clone)]
struct SwitchEventTarget {
    base: WeakPressableLabeledControl,
    presenter: Rc<RefCell<Rc<dyn SwitchIndicatorPresenter>>>,
    template_override: Rc<RefCell<Option<Rc<dyn SwitchIndicatorTemplate>>>>,
    sizing_value: Rc<Cell<Option<LabeledControlSizing>>>,
    checked: Rc<Cell<bool>>,
    colors_value: Rc<Cell<Option<LabeledControlColors>>>,
    changed: Rc<RefCell<Option<SwitchChangedCallback>>>,
    weak_root: Rc<WeakNodeRef>,
}

impl SwitchEventTarget {
    fn from_switch(switch: &Switch) -> Self {
        Self {
            base: switch.base.downgrade(),
            presenter: switch.indicator_presenter.clone(),
            template_override: switch.template_override.clone(),
            sizing_value: switch.sizing_value.clone(),
            checked: switch.checked.clone(),
            colors_value: switch.colors_value.clone(),
            changed: switch.changed.clone(),
            weak_root: switch.weak_root.clone(),
        }
    }

    fn upgrade(&self) -> Option<Switch> {
        let base = self.base.upgrade()?;
        Some(Switch {
            root: base.root(),
            base,
            indicator_presenter: self.presenter.clone(),
            template_override: self.template_override.clone(),
            sizing_value: self.sizing_value.clone(),
            colors_value: self.colors_value.clone(),
            checked: self.checked.clone(),
            changed: self.changed.clone(),
            weak_root: self.weak_root.clone(),
        })
    }

    fn activate(&self, base_state: PressableLabeledControlState) {
        apply_switch_checked(
            &self.presenter,
            &self.weak_root,
            &self.checked,
            !self.checked.get(),
            true,
            true,
            &self.changed,
            self.colors_value.get(),
            base_state,
        );
    }

    fn sync_visual(&self, base_state: PressableLabeledControlState, emit: bool, announce: bool) {
        apply_switch_checked(
            &self.presenter,
            &self.weak_root,
            &self.checked,
            self.checked.get(),
            emit,
            announce,
            &self.changed,
            self.colors_value.get(),
            base_state,
        );
    }
}

#[allow(clippy::too_many_arguments)]
fn apply_switch_checked(
    presenter: &Rc<RefCell<Rc<dyn SwitchIndicatorPresenter>>>,
    weak_root: &Rc<WeakNodeRef>,
    checked_cell: &Rc<Cell<bool>>,
    checked: bool,
    emit: bool,
    announce: bool,
    changed: &Rc<RefCell<Option<SwitchChangedCallback>>>,
    colors: Option<LabeledControlColors>,
    base_state: PressableLabeledControlState,
) {
    let previous = checked_cell.get();
    checked_cell.set(checked);
    update_semantic_checked(
        weak_root,
        if checked {
            SemanticCheckedState::True
        } else {
            SemanticCheckedState::False
        },
        announce && previous != checked,
    );
    presenter.borrow().apply(
        current_theme(),
        SwitchIndicatorVisualState::new(
            checked,
            base_state.hovered,
            base_state.pressed,
            base_state.focused,
            base_state.enabled,
        ),
        colors,
    );
    if emit && previous != checked {
        if let Some(callback) = changed.borrow().clone() {
            callback(SwitchChangedEventArgs { checked });
        }
    }
}