1use gpui::prelude::*;
4use gpui::{div, px, App, ClickEvent, ElementId, IntoElement, SharedString, Window};
5
6use super::ClickHandler;
7use crate::theme::{theme, ColorName, Size};
8
9#[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}