1use gpui::{
2 App, Context, EventEmitter, IntoElement, PlatformDisplay, Size, Window,
3 WindowBackgroundAppearance, WindowBounds, WindowDecorations, WindowKind, WindowOptions,
4 linear_color_stop, linear_gradient, point,
5};
6use release_channel::ReleaseChannel;
7use std::rc::Rc;
8use theme;
9use ui::{Render, prelude::*};
10
11pub struct AgentNotification {
12 title: SharedString,
13 caption: SharedString,
14 icon: IconName,
15 project_name: Option<SharedString>,
16}
17
18impl AgentNotification {
19 pub fn new(
20 title: impl Into<SharedString>,
21 caption: impl Into<SharedString>,
22 icon: IconName,
23 project_name: Option<impl Into<SharedString>>,
24 ) -> Self {
25 Self {
26 title: title.into(),
27 caption: caption.into(),
28 icon,
29 project_name: project_name.map(|name| name.into()),
30 }
31 }
32
33 pub fn window_options(screen: Rc<dyn PlatformDisplay>, cx: &App) -> WindowOptions {
34 let size = Size {
35 width: px(450.),
36 height: px(72.),
37 };
38
39 let notification_margin_width = px(16.);
40 let notification_margin_height = px(-48.);
41
42 let bounds = gpui::Bounds::<Pixels> {
43 origin: screen.bounds().top_right()
44 - point(
45 size.width + notification_margin_width,
46 notification_margin_height,
47 ),
48 size,
49 };
50
51 let app_id = ReleaseChannel::global(cx).app_id();
52
53 WindowOptions {
54 window_bounds: Some(WindowBounds::Windowed(bounds)),
55 titlebar: None,
56 focus: false,
57 show: true,
58 kind: WindowKind::PopUp,
59 is_movable: false,
60 display_id: Some(screen.id()),
61 window_background: WindowBackgroundAppearance::Transparent,
62 app_id: Some(app_id.to_owned()),
63 window_min_size: None,
64 window_decorations: Some(WindowDecorations::Client),
65 tabbing_identifier: None,
66 ..Default::default()
67 }
68 }
69}
70
71pub enum AgentNotificationEvent {
72 Accepted,
73 Dismissed,
74}
75
76impl EventEmitter<AgentNotificationEvent> for AgentNotification {}
77
78impl AgentNotification {
79 pub fn accept(&mut self, cx: &mut Context<Self>) {
80 cx.emit(AgentNotificationEvent::Accepted);
81 }
82
83 pub fn dismiss(&mut self, cx: &mut Context<Self>) {
84 cx.emit(AgentNotificationEvent::Dismissed);
85 }
86}
87
88impl Render for AgentNotification {
89 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
90 let ui_font = theme::setup_ui_font(window, cx);
91 let line_height = window.line_height();
92
93 let bg = cx.theme().colors().elevated_surface_background;
94 let gradient_overflow = || {
95 div()
96 .h_full()
97 .absolute()
98 .w_8()
99 .bottom_0()
100 .right_0()
101 .bg(linear_gradient(
102 90.,
103 linear_color_stop(bg, 1.),
104 linear_color_stop(bg.opacity(0.2), 0.),
105 ))
106 };
107
108 h_flex()
109 .id("agent-notification")
110 .size_full()
111 .p_3()
112 .gap_4()
113 .justify_between()
114 .elevation_3(cx)
115 .text_ui(cx)
116 .font(ui_font)
117 .border_color(cx.theme().colors().border)
118 .rounded_xl()
119 .child(
120 h_flex()
121 .items_start()
122 .gap_2()
123 .flex_1()
124 .child(
125 h_flex().h(line_height).justify_center().child(
126 Icon::new(self.icon)
127 .color(Color::Muted)
128 .size(IconSize::Small),
129 ),
130 )
131 .child(
132 v_flex()
133 .flex_1()
134 .max_w(px(300.))
135 .child(
136 div()
137 .relative()
138 .text_size(px(14.))
139 .text_color(cx.theme().colors().text)
140 .truncate()
141 .child(self.title.clone())
142 .child(gradient_overflow()),
143 )
144 .child(
145 h_flex()
146 .relative()
147 .gap_1p5()
148 .text_size(px(12.))
149 .text_color(cx.theme().colors().text_muted)
150 .truncate()
151 .when_some(
152 self.project_name.clone(),
153 |description, project_name| {
154 description.child(
155 h_flex()
156 .gap_1p5()
157 .child(
158 div()
159 .max_w_16()
160 .truncate()
161 .child(project_name),
162 )
163 .child(
164 div().size(px(3.)).rounded_full().bg(cx
165 .theme()
166 .colors()
167 .text
168 .opacity(0.5)),
169 ),
170 )
171 },
172 )
173 .child(self.caption.clone())
174 .child(gradient_overflow()),
175 ),
176 ),
177 )
178 .child(
179 v_flex()
180 .gap_1()
181 .items_center()
182 .child(
183 Button::new("open", "View Panel")
184 .style(ButtonStyle::Tinted(ui::TintColor::Accent))
185 .full_width()
186 .on_click({
187 cx.listener(move |this, _event, _, cx| {
188 this.accept(cx);
189 })
190 }),
191 )
192 .child(Button::new("dismiss", "Dismiss").full_width().on_click({
193 cx.listener(move |this, _event, _, cx| {
194 this.dismiss(cx);
195 })
196 })),
197 )
198 }
199}