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