1use gpui::prelude::*;
6use gpui::*;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
10pub enum ToggleSize {
11 Sm,
13 #[default]
15 Md,
16 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
54pub 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 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 pub fn checked(mut self, checked: bool) -> Self {
79 self.checked = checked;
80 self
81 }
82
83 pub fn label(mut self, label: impl Into<SharedString>) -> Self {
85 self.label = Some(label.into());
86 self
87 }
88
89 pub fn size(mut self, size: ToggleSize) -> Self {
91 self.size = size;
92 self
93 }
94
95 pub fn disabled(mut self, disabled: bool) -> Self {
97 self.disabled = disabled;
98 self
99 }
100
101 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 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 let mut track = div()
140 .relative()
141 .w(track_width)
142 .h(track_height)
143 .rounded_full()
144 .bg(track_bg);
145
146 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 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 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}