Skip to main content

ui/components/collab/
update_button.rs

1use gpui::{AnyElement, ClickEvent, prelude::*};
2
3use crate::{ButtonLike, CommonAnimationExt, Tooltip, prelude::*};
4
5/// A button component displayed in the title bar to show auto-update status.
6#[derive(IntoElement, RegisterComponent)]
7pub struct UpdateButton {
8    icon: IconName,
9    icon_animate: bool,
10    icon_color: Option<Color>,
11    message: SharedString,
12    tooltip: Option<SharedString>,
13    show_dismiss: bool,
14    on_click: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
15    on_dismiss: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
16}
17
18impl UpdateButton {
19    pub fn new(icon: IconName, message: impl Into<SharedString>) -> Self {
20        Self {
21            icon,
22            icon_animate: false,
23            icon_color: None,
24            message: message.into(),
25            tooltip: None,
26            show_dismiss: false,
27            on_click: None,
28            on_dismiss: None,
29        }
30    }
31
32    /// Sets whether the icon should have a rotation animation (for progress states).
33    pub fn icon_animate(mut self, animate: bool) -> Self {
34        self.icon_animate = animate;
35        self
36    }
37
38    /// Sets the icon color (e.g., for warning/error states).
39    pub fn icon_color(mut self, color: impl Into<Option<Color>>) -> Self {
40        self.icon_color = color.into();
41        self
42    }
43
44    /// Sets the tooltip text shown on hover.
45    pub fn tooltip(mut self, tooltip: impl Into<SharedString>) -> Self {
46        self.tooltip = Some(tooltip.into());
47        self
48    }
49
50    /// Shows a dismiss button on the right side.
51    pub fn with_dismiss(mut self) -> Self {
52        self.show_dismiss = true;
53        self
54    }
55
56    /// Sets the click handler for the main button area.
57    pub fn on_click(
58        mut self,
59        handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
60    ) -> Self {
61        self.on_click = Some(Box::new(handler));
62        self
63    }
64
65    /// Sets the click handler for the dismiss button.
66    pub fn on_dismiss(
67        mut self,
68        handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
69    ) -> Self {
70        self.on_dismiss = Some(Box::new(handler));
71        self
72    }
73
74    pub fn checking() -> Self {
75        Self::new(IconName::ArrowCircle, "Checking for app updates…").icon_animate(true)
76    }
77
78    pub fn downloading(version: impl Into<SharedString>) -> Self {
79        Self::new(IconName::Download, "Downloading app update…").tooltip(version)
80    }
81
82    pub fn installing(version: impl Into<SharedString>) -> Self {
83        Self::new(IconName::ArrowCircle, "Installing app update…")
84            .icon_animate(true)
85            .tooltip(version)
86    }
87
88    pub fn updated(version: impl Into<SharedString>) -> Self {
89        Self::new(IconName::Download, "Restart to Update")
90            .tooltip(version)
91            .with_dismiss()
92    }
93
94    pub fn errored(error: impl Into<SharedString>) -> Self {
95        Self::new(IconName::Warning, "Failed to update the app")
96            .icon_color(Color::Warning)
97            .tooltip(error)
98            .with_dismiss()
99    }
100}
101
102impl RenderOnce for UpdateButton {
103    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
104        let border_color = cx.theme().colors().border;
105
106        let icon = Icon::new(self.icon)
107            .size(IconSize::XSmall)
108            .when_some(self.icon_color, |this, color| this.color(color));
109        let icon_element = if self.icon_animate {
110            icon.with_rotate_animation(3).into_any_element()
111        } else {
112            icon.into_any_element()
113        };
114
115        let tooltip = self.tooltip.clone();
116
117        h_flex()
118            .mr_2()
119            .rounded_sm()
120            .border_1()
121            .border_color(border_color)
122            .child(
123                ButtonLike::new("update-button")
124                    .child(
125                        h_flex()
126                            .h_full()
127                            .gap_1()
128                            .child(icon_element)
129                            .child(Label::new(self.message).size(LabelSize::Small)),
130                    )
131                    .when_some(tooltip, |this, tooltip| {
132                        this.tooltip(Tooltip::text(tooltip))
133                    })
134                    .when_some(self.on_click, |this, handler| this.on_click(handler)),
135            )
136            .when(self.show_dismiss, |this| {
137                this.child(
138                    div().border_l_1().border_color(border_color).child(
139                        IconButton::new("dismiss-update-button", IconName::Close)
140                            .icon_size(IconSize::Indicator)
141                            .when_some(self.on_dismiss, |this, handler| this.on_click(handler))
142                            .tooltip(Tooltip::text("Dismiss")),
143                    ),
144                )
145            })
146    }
147}
148
149impl Component for UpdateButton {
150    fn scope() -> ComponentScope {
151        ComponentScope::Collaboration
152    }
153
154    fn name() -> &'static str {
155        "UpdateButton"
156    }
157
158    fn description() -> Option<&'static str> {
159        Some(
160            "A button component displayed in the title bar to show auto-update status and allow users to restart the app.",
161        )
162    }
163
164    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
165        let version = "1.99.0";
166
167        Some(
168            v_flex()
169                .gap_6()
170                .children(vec![
171                    example_group_with_title(
172                        "Progress States",
173                        vec![
174                            single_example("Checking", UpdateButton::checking().into_any_element()),
175                            single_example(
176                                "Downloading",
177                                UpdateButton::downloading(version).into_any_element(),
178                            ),
179                            single_example(
180                                "Installing",
181                                UpdateButton::installing(version).into_any_element(),
182                            ),
183                        ],
184                    ),
185                    example_group_with_title(
186                        "Actionable States",
187                        vec![
188                            single_example(
189                                "Ready to Update",
190                                UpdateButton::updated(version).into_any_element(),
191                            ),
192                            single_example(
193                                "Error",
194                                UpdateButton::errored("Network timeout").into_any_element(),
195                            ),
196                        ],
197                    ),
198                ])
199                .into_any_element(),
200        )
201    }
202}