use freya::{ animation::{AnimColor, AnimNum, Ease, Function, use_animation_transition}, prelude::*, }; use crate::{theme::colors, ui}; const WIDTH: f32 = 40.; const HANDLE_SIZE: f32 = 16.; const TIME: u64 = 180; pub fn toggle(value: State) -> impl IntoElement { let mut v = value; Switch { value, on_press: (move |()| v.toggle()).into(), disabled: false, } } #[allow(dead_code)] pub fn toggle_disabled(value: State) -> impl IntoElement { Switch { value, on_press: (|()| {}).into(), disabled: true, } } pub fn toggle_controlled(on: bool, on_toggle: EventHandler<()>) -> ToggleControlled { ToggleControlled { on, on_toggle } } #[derive(PartialEq)] pub struct ToggleControlled { on: bool, on_toggle: EventHandler<()>, } impl Component for ToggleControlled { fn render(&self) -> impl IntoElement { let mut value = use_state(|| self.on); value.set_if_modified(self.on); Switch { value, on_press: self.on_toggle.clone(), disabled: false, } } } #[derive(PartialEq)] struct Switch { value: State, on_press: EventHandler<()>, disabled: bool, } impl Component for Switch { fn render(&self) -> impl IntoElement { let value = self.value; let on_press = self.on_press.clone(); let on = *value.read(); let disabled = self.disabled; let a11y_id = use_a11y(); let focus = use_focus(a11y_id); let knob_align = if on { Alignment::End } else { Alignment::Start }; let track_border = if focus().is_focused() && !disabled { colors::fg_primary() } else { colors::component_border() }; let background = use_animation_transition(value, |_, on| { if on { AnimColor::new(colors::component_bg(), colors::brand()) } else { AnimColor::new(colors::brand(), colors::component_bg()) } .time(TIME) .function(Function::Expo) .ease(Ease::Out) }); let right_offset = use_animation_transition(value, |_, on| { if on { AnimNum::new(WIDTH - HANDLE_SIZE - 6., 0.) } else { AnimNum::new(0., WIDTH - HANDLE_SIZE - 6.) } .time(TIME) .function(Function::Expo) .ease(Ease::Out) }); let text = if on { "On" } else { "Off" }; rect() .horizontal() .cross_align(Alignment::Center) .spacing(8.) .a11y_id(a11y_id) .a11y_focusable(!disabled) .a11y_role(AccessibilityRole::Button) .maybe(!disabled, |el| { el.cursor(CursorIcon::Pointer).on_all_press(move |_| { on_press.call(()); }) }) .child( label() .text(text) .font_size(12.) .color(Color::from_rgb(167, 176, 190)), ) .child( rect() .width(Size::px(WIDTH)) .height(Size::px(22.)) .corner_radius(CornerRadius::new_all(11.)) .padding(Gaps::new_all(3.)) .main_align(knob_align) .cross_align(Alignment::Center) .background(if disabled { colors::component_bg_disabled() } else { background.read().value() }) .border(ui::border_all_color(1., track_border)) .child( rect() .width(Size::px(HANDLE_SIZE)) .height(Size::px(HANDLE_SIZE)) .corner_radius(CornerRadius::new_all(8.)) .background(if disabled { colors::fg_secondary() } else { Color::WHITE }) .position(Position::new_absolute().right(right_offset.read().value())), ), ) } }