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 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}