1use egui::StrokeKind;
2
3use crate::{BooleanStyle, EguiProbe, Style, option_probe_with};
4
5pub struct ToggleSwitch<'a, T>(pub &'a mut T);
6
7impl EguiProbe for bool {
8 #[inline(always)]
9 fn probe(&mut self, ui: &mut egui::Ui, style: &Style) -> egui::Response {
10 match style.boolean {
11 BooleanStyle::Checkbox => ui.add(egui::Checkbox::without_text(self)),
12 BooleanStyle::ToggleSwitch => toggle_switch(self, ui),
13 }
14 }
15}
16
17impl EguiProbe for ToggleSwitch<'_, bool> {
18 #[inline(always)]
19 fn probe(&mut self, ui: &mut egui::Ui, _style: &Style) -> egui::Response {
20 toggle_switch(self.0, ui)
21 }
22}
23
24impl EguiProbe for ToggleSwitch<'_, Option<bool>> {
25 #[inline(always)]
26 fn probe(&mut self, ui: &mut egui::Ui, style: &Style) -> egui::Response {
27 option_probe_with(
28 self.0,
29 ui,
30 style,
31 || false,
32 |value, ui, _style| toggle_switch(value, ui),
33 )
34 }
35}
36
37pub fn toggle_switch(on: &mut bool, ui: &mut egui::Ui) -> egui::Response {
40 let desired_size = ui.spacing().interact_size.y * egui::vec2(2.0, 1.0);
41 let (rect, mut response) = ui.allocate_exact_size(desired_size, egui::Sense::click());
42 if response.clicked() {
43 *on = !*on;
44 response.mark_changed();
45 }
46 response.widget_info(|| egui::WidgetInfo::selected(egui::WidgetType::Checkbox, true, *on, ""));
47
48 if ui.is_rect_visible(rect) {
49 let how_on = ui.ctx().animate_bool(response.id, *on);
50 let visuals = ui.style().interact_selectable(&response, *on);
51 let rect = rect.expand(visuals.expansion);
52 let radius = 0.5 * rect.height();
53 ui.painter().rect(
54 rect,
55 radius,
56 visuals.bg_fill,
57 visuals.bg_stroke,
58 StrokeKind::Inside,
59 );
60 let circle_x = egui::lerp((rect.left() + radius)..=(rect.right() - radius), how_on);
61 let center = egui::pos2(circle_x, rect.center().y);
62 ui.painter()
63 .circle(center, 0.75 * radius, visuals.bg_fill, visuals.fg_stroke);
64 }
65
66 response
67}