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}