freya_components/
switch.rs1use accesskit::Toggled;
2use freya_animation::prelude::*;
3use freya_core::prelude::*;
4use torin::{
5 alignment::Alignment,
6 gaps::Gaps,
7 size::Size,
8};
9
10use crate::{
11 get_theme,
12 theming::component_themes::SwitchThemePartial,
13};
14
15#[cfg_attr(feature = "docs",
48 doc = embed_doc_image::embed_image!(
49 "gallery_toggled_switch",
50 "images/gallery_toggled_switch.png"
51 ),
52 doc = embed_doc_image::embed_image!("gallery_not_toggled_switch", "images/gallery_not_toggled_switch.png")
53)]
54#[derive(Clone, PartialEq)]
55pub struct Switch {
56 pub(crate) theme: Option<SwitchThemePartial>,
57 toggled: ReadState<bool>,
58 on_toggle: Option<EventHandler<()>>,
59 enabled: bool,
60 key: DiffKey,
61}
62
63impl KeyExt for Switch {
64 fn write_key(&mut self) -> &mut DiffKey {
65 &mut self.key
66 }
67}
68
69impl Default for Switch {
70 fn default() -> Self {
71 Self::new()
72 }
73}
74
75impl Switch {
76 pub fn new() -> Self {
77 Self {
78 toggled: false.into(),
79 on_toggle: None,
80 theme: None,
81 enabled: true,
82 key: DiffKey::None,
83 }
84 }
85
86 pub fn toggled(mut self, toggled: impl Into<ReadState<bool>>) -> Self {
87 self.toggled = toggled.into();
88 self
89 }
90
91 pub fn on_toggle(mut self, on_toggle: impl Into<EventHandler<()>>) -> Self {
92 self.on_toggle = Some(on_toggle.into());
93 self
94 }
95
96 pub fn enabled(mut self, enabled: impl Into<bool>) -> Self {
97 self.enabled = enabled.into();
98 self
99 }
100}
101
102impl Component for Switch {
103 fn render(self: &Switch) -> impl IntoElement {
104 let theme = get_theme!(&self.theme, switch);
105 let mut hovering = use_state(|| false);
106 let focus = use_focus();
107 let focus_status = use_focus_status(focus);
108
109 let toggled = *self.toggled.read();
110
111 let animation = use_animation_with_dependencies(
112 &(theme.clone(), toggled),
113 |conf, (switch_theme, toggled)| {
114 conf.on_creation(OnCreation::Finish);
115 conf.on_change(OnChange::Rerun);
116
117 let value = (
118 AnimNum::new(2., 22.)
119 .time(300)
120 .function(Function::Expo)
121 .ease(Ease::Out),
122 AnimNum::new(14., 18.)
123 .time(300)
124 .function(Function::Expo)
125 .ease(Ease::Out),
126 AnimColor::new(switch_theme.background, switch_theme.toggled_background)
127 .time(300)
128 .function(Function::Expo)
129 .ease(Ease::Out),
130 AnimColor::new(
131 switch_theme.thumb_background,
132 switch_theme.toggled_thumb_background,
133 )
134 .time(300)
135 .function(Function::Expo)
136 .ease(Ease::Out),
137 );
138
139 if *toggled {
140 value
141 } else {
142 value.into_reversed()
143 }
144 },
145 );
146
147 let enabled = use_reactive(&self.enabled);
148 use_drop(move || {
149 if hovering() && enabled() {
150 Cursor::set(CursorIcon::default());
151 }
152 });
153
154 let border = if focus_status() == FocusStatus::Keyboard {
155 Border::new()
156 .width(2.)
157 .alignment(BorderAlignment::Inner)
158 .fill(theme.focus_border_fill.mul_if(!self.enabled, 0.9))
159 } else {
160 Border::new()
161 };
162 let (offset_x, size, background, thumb) = animation.get().value();
163
164 rect()
165 .a11y_id(focus.a11y_id())
166 .a11y_focusable(self.enabled)
167 .a11y_role(AccessibilityRole::Switch)
168 .a11y_builder(|builder| builder.set_toggled(Toggled::from(toggled)))
169 .width(Size::px(48.))
170 .height(Size::px(25.))
171 .padding(Gaps::new_all(4.0))
172 .main_align(Alignment::center())
173 .offset_x(offset_x)
174 .corner_radius(CornerRadius::new_all(50.))
175 .background(background.mul_if(!self.enabled, 0.85))
176 .border(border)
177 .maybe(self.enabled, |rect| {
178 rect.on_press({
179 let on_toggle = self.on_toggle.clone();
180 move |_| {
181 if let Some(on_toggle) = &on_toggle {
182 on_toggle.call(())
183 }
184 focus.request_focus();
185 }
186 })
187 })
188 .on_pointer_enter(move |_| {
189 hovering.set(true);
190 if enabled() {
191 Cursor::set(CursorIcon::Pointer);
192 } else {
193 Cursor::set(CursorIcon::NotAllowed);
194 }
195 })
196 .on_pointer_leave(move |_| {
197 if hovering() {
198 Cursor::set(CursorIcon::default());
199 hovering.set(false);
200 }
201 })
202 .child(
203 rect()
204 .width(Size::px(size))
205 .height(Size::px(size))
206 .background(thumb.mul_if(!self.enabled, 0.85))
207 .corner_radius(CornerRadius::new_all(50.)),
208 )
209 }
210
211 fn render_key(&self) -> DiffKey {
212 self.key.clone().or(self.default_key())
213 }
214}