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