Skip to main content

guise/input/
switch.rs

1//! `Switch` — a controlled on/off toggle styled as a sliding track.
2
3use gpui::prelude::*;
4use gpui::{div, px, App, ClickEvent, ElementId, IntoElement, SharedString, Window};
5
6use super::ClickHandler;
7use crate::theme::{theme, ColorName, Size};
8
9/// A controlled switch. The Mantine `Switch`.
10#[derive(IntoElement)]
11pub struct Switch {
12    id: ElementId,
13    checked: bool,
14    label: Option<SharedString>,
15    size: Size,
16    color: ColorName,
17    disabled: bool,
18    on_change: Option<ClickHandler>,
19}
20
21impl Switch {
22    pub fn new(id: impl Into<ElementId>) -> Self {
23        Switch {
24            id: id.into(),
25            checked: false,
26            label: None,
27            size: Size::Md,
28            color: ColorName::Blue,
29            disabled: false,
30            on_change: None,
31        }
32    }
33
34    pub fn checked(mut self, checked: bool) -> Self {
35        self.checked = checked;
36        self
37    }
38
39    pub fn label(mut self, label: impl Into<SharedString>) -> Self {
40        self.label = Some(label.into());
41        self
42    }
43
44    pub fn size(mut self, size: Size) -> Self {
45        self.size = size;
46        self
47    }
48
49    pub fn color(mut self, color: ColorName) -> Self {
50        self.color = color;
51        self
52    }
53
54    pub fn disabled(mut self, disabled: bool) -> Self {
55        self.disabled = disabled;
56        self
57    }
58
59    pub fn on_change(
60        mut self,
61        handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
62    ) -> Self {
63        self.on_change = Some(Box::new(handler));
64        self
65    }
66
67    fn track_height(&self) -> f32 {
68        match self.size {
69            Size::Xs => 16.0,
70            Size::Sm => 20.0,
71            Size::Md => 24.0,
72            Size::Lg => 30.0,
73            Size::Xl => 36.0,
74        }
75    }
76}
77
78impl RenderOnce for Switch {
79    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
80        let t = theme(cx);
81        let height = self.track_height();
82        let width = (height * 1.85).round();
83        let knob = height - 4.0;
84        let accent = t.color(self.color, t.primary_shade());
85
86        let track_bg = if self.checked {
87            accent.hsla()
88        } else {
89            t.color(ColorName::Gray, if t.scheme.is_dark() { 6 } else { 4 }).hsla()
90        };
91        let knob_x = if self.checked { width - knob - 2.0 } else { 2.0 };
92
93        let track = div()
94            .w(px(width))
95            .h(px(height))
96            .rounded(px(height))
97            .bg(track_bg)
98            .relative()
99            .child(
100                div()
101                    .absolute()
102                    .top(px(2.0))
103                    .left(px(knob_x))
104                    .w(px(knob))
105                    .h(px(knob))
106                    .rounded(px(knob))
107                    .bg(t.white.hsla()),
108            );
109
110        let mut row = div().id(self.id).flex().items_center().gap(px(8.0)).child(track);
111        if let Some(label) = self.label {
112            row = row.child(
113                div()
114                    .text_size(px(t.font_size(self.size)))
115                    .text_color(t.text().hsla())
116                    .child(label),
117            );
118        }
119
120        if self.disabled {
121            row.opacity(0.5)
122        } else {
123            if let Some(handler) = self.on_change {
124                row = row.on_click(handler);
125            }
126            row
127        }
128    }
129}