1use gpui::prelude::*;
4use gpui::{div, px, App, ClickEvent, ElementId, FontWeight, IntoElement, SharedString, Window};
5
6use crate::devtools::Probed;
7use crate::input::ClickHandler;
8use crate::reactive::Binding;
9use crate::style::ColorValue;
10use crate::theme::{theme, Size};
11
12#[derive(IntoElement)]
15pub struct Chip {
16 id: ElementId,
17 label: SharedString,
18 checked: bool,
19 color: ColorValue,
20 size: Size,
21 binding: Option<Binding<bool>>,
22 on_change: Option<ClickHandler>,
23}
24
25impl Chip {
26 pub fn new(id: impl Into<ElementId>, label: impl Into<SharedString>) -> Self {
27 Chip {
28 id: id.into(),
29 label: label.into(),
30 checked: false,
31 color: ColorValue::default(),
32 size: Size::Md,
33 binding: None,
34 on_change: None,
35 }
36 }
37
38 pub fn checked(mut self, checked: bool) -> Self {
39 self.checked = checked;
40 self
41 }
42
43 pub fn color(mut self, color: impl Into<ColorValue>) -> Self {
44 self.color = color.into();
45 self
46 }
47
48 pub fn size(mut self, size: Size) -> Self {
49 self.size = size;
50 self
51 }
52
53 pub fn bind(mut self, binding: Binding<bool>) -> Self {
56 self.binding = Some(binding);
57 self
58 }
59
60 pub fn on_change(
61 mut self,
62 handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
63 ) -> Self {
64 self.on_change = Some(Box::new(handler));
65 self
66 }
67
68 fn metrics(&self) -> (f32, f32, f32) {
69 match self.size {
70 Size::Xs => (24.0, 10.0, 11.0),
71 Size::Sm => (28.0, 12.0, 12.0),
72 Size::Md => (32.0, 16.0, 14.0),
73 Size::Lg => (38.0, 20.0, 16.0),
74 Size::Xl => (44.0, 24.0, 18.0),
75 }
76 }
77}
78
79impl RenderOnce for Chip {
80 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
81 let t = theme(cx);
82 let (height, pad_x, font) = self.metrics();
83 let accent = self.color.accent(t);
84 let checked = self.binding.as_ref().map_or(self.checked, |b| b.get(cx));
85
86 let (bg, fg, border) = if checked {
87 (self.color.soft(t), accent, accent)
88 } else {
89 (t.surface().hsla(), t.text().hsla(), t.border().hsla())
90 };
91 let hover_bg = t.surface_hover().hsla();
92
93 let mut el = div()
94 .id(self.id)
95 .flex()
96 .items_center()
97 .gap(px(6.0))
98 .h(px(height))
99 .px(px(pad_x))
100 .rounded(px(height))
101 .border_1()
102 .border_color(border)
103 .bg(bg)
104 .text_color(fg)
105 .text_size(px(font))
106 .font_weight(FontWeight::MEDIUM);
107 if checked {
108 el = el.child(SharedString::new_static("\u{2713}"));
109 } else {
110 el = el.hover(move |s| s.bg(hover_bg));
111 }
112 el = el.child(self.label);
113 if self.binding.is_some() || self.on_change.is_some() {
114 let binding = self.binding;
115 let handler = self.on_change;
116 let next = !checked;
117 el = el.on_click(move |ev, window, cx| {
118 if let Some(binding) = &binding {
119 binding.set(cx, next);
120 }
121 if let Some(handler) = &handler {
122 handler(ev, window, cx);
123 }
124 });
125 }
126 el.probe("Chip")
127 .attr("size", self.size.label())
128 .attr_if("checked", self.checked)
129 }
130}