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