ccf_gpui_widgets/widgets/
checkbox.rs1use gpui::prelude::*;
26use gpui::*;
27
28use crate::theme::{get_theme_or, Theme};
29use super::focus_navigation::{handle_tab_navigation, with_focus_actions, EnabledCursorExt};
30
31#[derive(Clone, Debug)]
33pub enum CheckboxEvent {
34 Change(bool),
37}
38
39pub struct Checkbox {
41 checked: bool,
42 label: Option<SharedString>,
43 focus_handle: FocusHandle,
44 custom_theme: Option<Theme>,
45 enabled: bool,
47}
48
49impl EventEmitter<CheckboxEvent> for Checkbox {}
50
51impl Focusable for Checkbox {
52 fn focus_handle(&self, _cx: &App) -> FocusHandle {
53 self.focus_handle.clone()
54 }
55}
56
57impl Checkbox {
58 pub fn new(cx: &mut Context<Self>) -> Self {
60 Self {
61 checked: false,
62 label: None,
63 focus_handle: cx.focus_handle().tab_stop(true),
64 custom_theme: None,
65 enabled: true,
66 }
67 }
68
69 #[must_use]
71 pub fn with_checked(mut self, value: bool) -> Self {
72 self.checked = value;
73 self
74 }
75
76 #[must_use]
78 pub fn label(mut self, text: impl Into<SharedString>) -> Self {
79 self.label = Some(text.into());
80 self
81 }
82
83 #[must_use]
85 pub fn theme(mut self, theme: Theme) -> Self {
86 self.custom_theme = Some(theme);
87 self
88 }
89
90 #[must_use]
92 pub fn with_enabled(mut self, enabled: bool) -> Self {
93 self.enabled = enabled;
94 self
95 }
96
97 pub fn is_checked(&self) -> bool {
99 self.checked
100 }
101
102 pub fn set_checked(&mut self, checked: bool, cx: &mut Context<Self>) {
104 if self.checked != checked {
105 self.checked = checked;
106 cx.emit(CheckboxEvent::Change(checked));
107 cx.notify();
108 }
109 }
110
111 pub fn focus_handle(&self) -> &FocusHandle {
113 &self.focus_handle
114 }
115
116 pub fn is_enabled(&self) -> bool {
118 self.enabled
119 }
120
121 pub fn set_enabled(&mut self, enabled: bool, cx: &mut Context<Self>) {
123 if self.enabled != enabled {
124 self.enabled = enabled;
125 cx.notify();
126 }
127 }
128
129 pub fn set_label(&mut self, label: impl Into<SharedString>, cx: &mut Context<Self>) {
131 self.label = Some(label.into());
132 cx.notify();
133 }
134
135 pub fn clear_label(&mut self, cx: &mut Context<Self>) {
137 self.label = None;
138 cx.notify();
139 }
140
141 fn toggle(&mut self, cx: &mut Context<Self>) {
142 self.checked = !self.checked;
143 cx.emit(CheckboxEvent::Change(self.checked));
144 cx.notify();
145 }
146}
147
148impl Render for Checkbox {
149 fn render(&mut self, window: &mut Window, cx: &mut Context<'_, Self>) -> impl IntoElement {
150 let theme = get_theme_or(cx, self.custom_theme.as_ref());
151 let checked = self.checked;
152 let label = self.label.clone();
153 let focus_handle = self.focus_handle.clone();
154 let is_focused = self.focus_handle.is_focused(window);
155 let enabled = self.enabled;
156
157 with_focus_actions(
158 div()
159 .id("ccf_checkbox")
160 .track_focus(&focus_handle)
161 .tab_stop(enabled),
162 cx,
163 )
164 .on_key_down(cx.listener(move |checkbox, event: &KeyDownEvent, window, cx| {
165 if !checkbox.enabled {
166 return;
167 }
168 if handle_tab_navigation(event, window) {
169 return;
170 }
171 if matches!(event.keystroke.key.as_str(), "space" | "enter") {
172 checkbox.toggle(cx);
173 }
174 }))
175 .flex()
176 .flex_row()
177 .gap_2()
178 .items_center()
179 .py_1()
180 .px_1()
181 .rounded_sm()
182 .cursor_for_enabled(enabled)
183 .border_2()
184 .border_color(if is_focused && enabled { rgb(theme.border_focus) } else { rgba(0x00000000) })
185 .when(enabled, |d| {
186 d.on_mouse_down(MouseButton::Left, cx.listener(|checkbox, _event, window, cx| {
187 checkbox.focus_handle.focus(window);
188 checkbox.toggle(cx);
189 }))
190 })
191 .child(
192 div()
194 .w(px(20.))
195 .h(px(20.))
196 .border_1()
197 .rounded_sm()
198 .flex()
199 .items_center()
200 .justify_center()
201 .when(!enabled, |d| {
202 d.bg(rgb(theme.disabled_bg))
204 .border_color(rgb(theme.disabled_bg))
205 .when(checked, |d| {
206 d.child(
207 div()
208 .text_color(rgb(theme.disabled_text))
209 .text_sm()
210 .child("✓")
211 )
212 })
213 })
214 .when(enabled && checked, |d| {
215 d.bg(rgb(theme.primary))
216 .border_color(rgb(theme.primary))
217 .child(
218 div()
219 .text_color(rgb(theme.text_black))
220 .text_sm()
221 .child("✓")
222 )
223 })
224 .when(enabled && !checked, |d| {
225 d.bg(rgb(theme.bg_input))
226 .border_color(rgb(theme.border_input))
227 .hover(|d| d.bg(rgb(theme.bg_input_hover)))
228 })
229 )
230 .when_some(label, |d, label_text| {
231 d.child(
232 div()
233 .text_sm()
234 .font_weight(FontWeight::SEMIBOLD)
235 .when(enabled, |d| d.text_color(rgb(theme.text_label)))
236 .when(!enabled, |d| d.text_color(rgb(theme.disabled_text)))
237 .child(label_text)
238 )
239 })
240 }
241}