agent_notification.rs

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