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 Zed updates…").icon_animate(true)
 76    }
 77
 78    pub fn downloading(version: impl Into<SharedString>) -> Self {
 79        Self::new(IconName::Download, "Downloading Zed update…").tooltip(version)
 80    }
 81
 82    pub fn installing(version: impl Into<SharedString>) -> Self {
 83        Self::new(IconName::ArrowCircle, "Installing Zed 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 Zed")
 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 Zed.",
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}