gpui_ui_kit/
toggle.rs

1//! Toggle/Switch component
2//!
3//! A toggle switch for boolean values.
4
5use gpui::prelude::*;
6use gpui::*;
7
8/// Toggle size variants
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
10pub enum ToggleSize {
11    /// Small
12    Sm,
13    /// Medium (default)
14    #[default]
15    Md,
16    /// Large
17    Lg,
18}
19
20impl ToggleSize {
21    fn track_width(&self) -> Pixels {
22        match self {
23            ToggleSize::Sm => px(32.0),
24            ToggleSize::Md => px(40.0),
25            ToggleSize::Lg => px(52.0),
26        }
27    }
28
29    fn track_height(&self) -> Pixels {
30        match self {
31            ToggleSize::Sm => px(18.0),
32            ToggleSize::Md => px(22.0),
33            ToggleSize::Lg => px(28.0),
34        }
35    }
36
37    fn knob_size(&self) -> Pixels {
38        match self {
39            ToggleSize::Sm => px(14.0),
40            ToggleSize::Md => px(18.0),
41            ToggleSize::Lg => px(24.0),
42        }
43    }
44
45    fn knob_offset(&self) -> Pixels {
46        match self {
47            ToggleSize::Sm => px(2.0),
48            ToggleSize::Md => px(2.0),
49            ToggleSize::Lg => px(2.0),
50        }
51    }
52}
53
54/// A toggle switch component
55pub struct Toggle {
56    id: ElementId,
57    checked: bool,
58    label: Option<SharedString>,
59    size: ToggleSize,
60    disabled: bool,
61    on_change: Option<Box<dyn Fn(bool, &mut Window, &mut App) + 'static>>,
62}
63
64impl Toggle {
65    /// Create a new toggle
66    pub fn new(id: impl Into<ElementId>) -> Self {
67        Self {
68            id: id.into(),
69            checked: false,
70            label: None,
71            size: ToggleSize::default(),
72            disabled: false,
73            on_change: None,
74        }
75    }
76
77    /// Set checked state
78    pub fn checked(mut self, checked: bool) -> Self {
79        self.checked = checked;
80        self
81    }
82
83    /// Set label
84    pub fn label(mut self, label: impl Into<SharedString>) -> Self {
85        self.label = Some(label.into());
86        self
87    }
88
89    /// Set size
90    pub fn size(mut self, size: ToggleSize) -> Self {
91        self.size = size;
92        self
93    }
94
95    /// Set disabled state
96    pub fn disabled(mut self, disabled: bool) -> Self {
97        self.disabled = disabled;
98        self
99    }
100
101    /// Set change handler
102    pub fn on_change(mut self, handler: impl Fn(bool, &mut Window, &mut App) + 'static) -> Self {
103        self.on_change = Some(Box::new(handler));
104        self
105    }
106
107    /// Build into element
108    pub fn build(self) -> Stateful<Div> {
109        let track_width = self.size.track_width();
110        let track_height = self.size.track_height();
111        let knob_size = self.size.knob_size();
112        let knob_offset = self.size.knob_offset();
113        let checked = self.checked;
114
115        let track_bg = if checked {
116            rgb(0x007acc)
117        } else {
118            rgb(0x3a3a3a)
119        };
120
121        let knob_left = if checked {
122            track_width - knob_size - knob_offset
123        } else {
124            knob_offset
125        };
126
127        let mut container = div()
128            .id(self.id)
129            .flex()
130            .items_center()
131            .gap_2()
132            .cursor_pointer();
133
134        if self.disabled {
135            container = container.opacity(0.5).cursor_not_allowed();
136        }
137
138        // Track
139        let mut track = div()
140            .relative()
141            .w(track_width)
142            .h(track_height)
143            .rounded_full()
144            .bg(track_bg);
145
146        // Knob
147        let knob = div()
148            .absolute()
149            .top(knob_offset)
150            .left(knob_left)
151            .w(knob_size)
152            .h(knob_size)
153            .rounded_full()
154            .bg(rgb(0xffffff))
155            .shadow_sm();
156
157        track = track.child(knob);
158        container = container.child(track);
159
160        // Label
161        if let Some(label) = self.label {
162            let label_el = match self.size {
163                ToggleSize::Sm => div().text_xs(),
164                ToggleSize::Md => div().text_sm(),
165                ToggleSize::Lg => div(),
166            };
167            container = container.child(label_el.text_color(rgb(0xcccccc)).child(label));
168        }
169
170        // Click handler
171        if !self.disabled {
172            if let Some(handler) = self.on_change {
173                let new_checked = !checked;
174                container = container.on_mouse_up(MouseButton::Left, move |_event, window, cx| {
175                    handler(new_checked, window, cx);
176                });
177            }
178        }
179
180        container
181    }
182}
183
184impl IntoElement for Toggle {
185    type Element = Stateful<Div>;
186
187    fn into_element(self) -> Self::Element {
188        self.build()
189    }
190}