ragrig_gpui/
attachments.rs1use std::rc::Rc;
4
5use gpui_kit::{
6 AnyElement, App, ElementId, InteractiveElement as _, IntoElement, ParentElement as _,
7 RenderOnce, StatefulInteractiveElement as _, Styled as _, Window,
8 assets::IconName,
9 component::{
10 ActiveTheme as _, Disableable as _, Sizable as _,
11 button::{Button, ButtonVariants as _},
12 h_flex,
13 },
14 div,
15};
16
17use crate::model::{Attachment, AttachmentKind};
18
19#[derive(Clone, Debug, PartialEq, Eq)]
21pub enum AttachmentEvent {
22 Toggled(String),
24 Dropped(String),
26 Accepted(String),
28}
29
30type AttachmentHandler = Rc<dyn Fn(AttachmentEvent, &mut Window, &mut App)>;
31
32#[derive(IntoElement)]
37pub struct AttachmentRows {
38 attachments: Vec<Attachment>,
39 on_event: Option<AttachmentHandler>,
40}
41
42impl AttachmentRows {
43 pub fn new(attachments: impl IntoIterator<Item = Attachment>) -> Self {
45 Self {
46 attachments: attachments.into_iter().collect(),
47 on_event: None,
48 }
49 }
50
51 pub fn on_event(
53 mut self,
54 handler: impl Fn(AttachmentEvent, &mut Window, &mut App) + 'static,
55 ) -> Self {
56 self.on_event = Some(Rc::new(handler));
57 self
58 }
59}
60
61impl RenderOnce for AttachmentRows {
62 fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
63 let handler = self.on_event;
64 h_flex().flex_wrap().gap_2().children(
65 self.attachments
66 .into_iter()
67 .enumerate()
68 .map(|(index, attachment)| chip(index, attachment, handler.clone(), cx)),
69 )
70 }
71}
72
73fn chip(
74 index: usize,
75 attachment: Attachment,
76 handler: Option<AttachmentHandler>,
77 cx: &mut App,
78) -> AnyElement {
79 let theme = cx.theme();
80 let on = attachment.is_on();
81 let enabled = attachment.enabled;
82 let id = attachment.id.clone();
83 let element_id = ElementId::Name(format!("attachment-{index}-{id}").into());
84
85 let mut row = h_flex()
86 .id(element_id)
87 .items_center()
88 .gap_1()
89 .px_2()
90 .py_1()
91 .rounded(theme.radius)
92 .border_1()
93 .border_color(if on { theme.primary } else { theme.border })
94 .bg(if on {
95 theme.primary.opacity(0.08)
96 } else {
97 theme.background
98 })
99 .text_sm()
100 .text_color(if enabled {
101 theme.foreground
102 } else {
103 theme.muted_foreground
104 })
105 .child(
106 div()
107 .text_color(if on {
108 theme.primary
109 } else {
110 theme.muted_foreground
111 })
112 .child(attachment.icon),
113 )
114 .child(attachment.label.clone());
115
116 match &attachment.kind {
117 AttachmentKind::Toggle { .. } => {
118 if enabled {
119 let handler = handler.clone();
120 let id = id.clone();
121 row = row.cursor_pointer().on_click(move |_, window, cx| {
122 if let Some(handler) = &handler {
123 handler(AttachmentEvent::Toggled(id.clone()), window, cx);
124 }
125 });
126 }
127 }
128 AttachmentKind::Pending => {
129 row = row.child(drop_button(index, &id, enabled, handler.clone()));
130 }
131 AttachmentKind::Offer { action } => {
132 let action_id = ElementId::Name(format!("attachment-offer-{index}-{id}").into());
133 let accept_handler = handler.clone();
134 let accept_id = id.clone();
135 row = row
136 .child(
137 Button::new(action_id)
138 .label(action.clone())
139 .xsmall()
140 .ghost()
141 .disabled(!enabled)
142 .on_click(move |_, window, cx| {
143 if let Some(handler) = &accept_handler {
144 handler(AttachmentEvent::Accepted(accept_id.clone()), window, cx);
145 }
146 }),
147 )
148 .child(drop_button(index, &id, enabled, handler));
149 }
150 }
151
152 row.into_any_element()
153}
154
155fn drop_button(
156 index: usize,
157 id: &str,
158 enabled: bool,
159 handler: Option<AttachmentHandler>,
160) -> Button {
161 let element_id = ElementId::Name(format!("attachment-drop-{index}-{id}").into());
162 let drop_id = id.to_owned();
163 Button::new(element_id)
164 .icon(IconName::Close)
165 .xsmall()
166 .ghost()
167 .disabled(!enabled)
168 .tooltip("Drop")
169 .on_click(move |_, window, cx| {
170 if let Some(handler) = &handler {
171 handler(AttachmentEvent::Dropped(drop_id.clone()), window, cx);
172 }
173 })
174}
175
176#[cfg(test)]
177mod tests {
178 use super::*;
179
180 #[test]
181 fn events_name_the_attachment() {
182 assert_eq!(
183 AttachmentEvent::Toggled("draft".to_owned()),
184 AttachmentEvent::Toggled("draft".to_owned())
185 );
186 assert_ne!(
187 AttachmentEvent::Dropped("a".to_owned()),
188 AttachmentEvent::Accepted("a".to_owned())
189 );
190 }
191
192 #[test]
193 fn a_row_accepts_any_iterator() {
194 let rows = AttachmentRows::new([
195 Attachment::document("draft", "draft.md"),
196 Attachment::pending("region", "Selection"),
197 ]);
198 assert_eq!(rows.attachments.len(), 2);
199 }
200}