1mod base_keymap_picker;
2
3use std::{borrow::Cow, sync::Arc};
4
5use db::kvp::KEY_VALUE_STORE;
6use gpui::{
7 elements::{Flex, Label, MouseEventHandler, ParentElement},
8 Action, Element, ElementBox, Entity, MouseButton, MutableAppContext, RenderContext,
9 Subscription, View, ViewContext,
10};
11use settings::{settings_file::SettingsFile, Settings};
12
13use workspace::{
14 item::Item, open_new, sidebar::SidebarSide, AppState, PaneBackdrop, Welcome, Workspace,
15 WorkspaceId,
16};
17
18use crate::base_keymap_picker::ToggleBaseKeymapSelector;
19
20pub const FIRST_OPEN: &str = "first_open";
21
22pub fn init(cx: &mut MutableAppContext) {
23 cx.add_action(|workspace: &mut Workspace, _: &Welcome, cx| {
24 let welcome_page = cx.add_view(WelcomePage::new);
25 workspace.add_item(Box::new(welcome_page), cx)
26 });
27
28 base_keymap_picker::init(cx);
29}
30
31pub fn show_welcome_experience(app_state: &Arc<AppState>, cx: &mut MutableAppContext) {
32 open_new(&app_state, cx, |workspace, cx| {
33 workspace.toggle_sidebar(SidebarSide::Left, cx);
34 let welcome_page = cx.add_view(|cx| WelcomePage::new(cx));
35 workspace.add_item_to_center(Box::new(welcome_page.clone()), cx);
36 cx.focus(welcome_page);
37 cx.notify();
38 })
39 .detach();
40
41 db::write_and_log(cx, || {
42 KEY_VALUE_STORE.write_kvp(FIRST_OPEN.to_string(), "false".to_string())
43 });
44}
45
46pub struct WelcomePage {
47 _settings_subscription: Subscription,
48}
49
50impl Entity for WelcomePage {
51 type Event = ();
52}
53
54impl View for WelcomePage {
55 fn ui_name() -> &'static str {
56 "WelcomePage"
57 }
58
59 fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> ElementBox {
60 let self_handle = cx.handle();
61 let settings = cx.global::<Settings>();
62 let theme = settings.theme.clone();
63
64 let width = theme.welcome.page_width;
65
66 let (diagnostics, metrics) = {
67 let telemetry = settings.telemetry();
68 (telemetry.diagnostics(), telemetry.metrics())
69 };
70
71 enum Metrics {}
72 enum Diagnostics {}
73
74 PaneBackdrop::new(
75 self_handle.id(),
76 Flex::column()
77 .with_children([
78 Flex::column()
79 .with_children([
80 theme::ui::icon(&theme.welcome.logo)
81 .aligned()
82 .contained()
83 .aligned()
84 .boxed(),
85 Label::new(
86 "Code at the speed of thought",
87 theme.welcome.logo_subheading.text.clone(),
88 )
89 .aligned()
90 .contained()
91 .with_style(theme.welcome.logo_subheading.container)
92 .boxed(),
93 ])
94 .contained()
95 .with_style(theme.welcome.heading_group)
96 .constrained()
97 .with_width(width)
98 .boxed(),
99 Flex::column()
100 .with_children([
101 self.render_cta_button(
102 "Choose a theme",
103 theme_selector::Toggle,
104 width,
105 cx,
106 ),
107 self.render_cta_button(
108 "Choose a keymap",
109 ToggleBaseKeymapSelector,
110 width,
111 cx,
112 ),
113 self.render_cta_button(
114 "Install the CLI",
115 install_cli::Install,
116 width,
117 cx,
118 ),
119 ])
120 .contained()
121 .with_style(theme.welcome.button_group)
122 .constrained()
123 .with_width(width)
124 .boxed(),
125 Flex::column()
126 .with_children([
127 theme::ui::checkbox_with_label::<Metrics, Self>(
128 Flex::column()
129 .with_children([
130 Label::new(
131 "Send anonymous usage data",
132 theme.welcome.checkbox.label.text.clone(),
133 )
134 .contained()
135 .with_style(theme.welcome.checkbox.label.container)
136 .boxed(),
137 Label::new(
138 "Help > View Telemetry",
139 theme.welcome.usage_note.text.clone(),
140 )
141 .contained()
142 .with_style(theme.welcome.usage_note.container)
143 .boxed(),
144 ])
145 .boxed(),
146 &theme.welcome.checkbox,
147 metrics,
148 cx,
149 |checked, cx| {
150 SettingsFile::update(cx, move |file| {
151 file.telemetry.set_metrics(checked)
152 })
153 },
154 )
155 .contained()
156 .with_style(theme.welcome.checkbox_container)
157 .boxed(),
158 theme::ui::checkbox::<Diagnostics, Self>(
159 "Send crash reports",
160 &theme.welcome.checkbox,
161 diagnostics,
162 cx,
163 |checked, cx| {
164 SettingsFile::update(cx, move |file| {
165 file.telemetry.set_diagnostics(checked)
166 })
167 },
168 )
169 .contained()
170 .with_style(theme.welcome.checkbox_container)
171 .boxed(),
172 ])
173 .contained()
174 .with_style(theme.welcome.checkbox_group)
175 .constrained()
176 .with_width(width)
177 .boxed(),
178 ])
179 .constrained()
180 .with_max_width(width)
181 .contained()
182 .with_uniform_padding(10.)
183 .aligned()
184 .boxed(),
185 )
186 .boxed()
187 }
188}
189
190impl WelcomePage {
191 pub fn new(cx: &mut ViewContext<Self>) -> Self {
192 let handle = cx.weak_handle();
193
194 let settings_subscription = cx.observe_global::<Settings, _>(move |cx| {
195 if let Some(handle) = handle.upgrade(cx) {
196 handle.update(cx, |_, cx| cx.notify())
197 }
198 });
199
200 WelcomePage {
201 _settings_subscription: settings_subscription,
202 }
203 }
204
205 fn render_cta_button<L, A>(
206 &self,
207 label: L,
208 action: A,
209 width: f32,
210 cx: &mut RenderContext<Self>,
211 ) -> ElementBox
212 where
213 L: Into<Cow<'static, str>>,
214 A: 'static + Action + Clone,
215 {
216 let theme = cx.global::<Settings>().theme.clone();
217 MouseEventHandler::<A>::new(0, cx, |state, _| {
218 let style = theme.welcome.button.style_for(state, false);
219 Label::new(label, style.text.clone())
220 .aligned()
221 .contained()
222 .with_style(style.container)
223 .constrained()
224 .with_max_width(width)
225 .boxed()
226 })
227 .on_click(MouseButton::Left, move |_, cx| {
228 cx.dispatch_action(action.clone())
229 })
230 .with_cursor_style(gpui::CursorStyle::PointingHand)
231 .boxed()
232 }
233
234 // fn render_settings_checkbox<T: 'static>(
235 // &self,
236 // label: &'static str,
237 // style: &CheckboxStyle,
238 // checked: bool,
239 // cx: &mut RenderContext<Self>,
240 // set_value: fn(&mut SettingsFileContent, checked: bool) -> (),
241 // ) -> ElementBox {
242 // MouseEventHandler::<T>::new(0, cx, |state, _| {
243 // let indicator = if checked {
244 // Svg::new(style.check_icon.clone())
245 // .with_color(style.check_icon_color)
246 // .constrained()
247 // } else {
248 // Empty::new().constrained()
249 // };
250
251 // Flex::row()
252 // .with_children([
253 // indicator
254 // .with_width(style.width)
255 // .with_height(style.height)
256 // .contained()
257 // .with_style(if checked {
258 // if state.hovered() {
259 // style.hovered_and_checked
260 // } else {
261 // style.checked
262 // }
263 // } else {
264 // if state.hovered() {
265 // style.hovered
266 // } else {
267 // style.default
268 // }
269 // })
270 // .boxed(),
271 // Label::new(label, style.label.text.clone())
272 // .contained()
273 // .with_style(style.label.container)
274 // .boxed(),
275 // ])
276 // .align_children_center()
277 // .boxed()
278 // })
279 // .on_click(gpui::MouseButton::Left, move |_, cx| {
280 // SettingsFile::update(cx, move |content| set_value(content, !checked))
281 // })
282 // .with_cursor_style(gpui::CursorStyle::PointingHand)
283 // .contained()
284 // .with_style(style.container)
285 // .boxed()
286 // }
287}
288
289impl Item for WelcomePage {
290 fn tab_content(
291 &self,
292 _detail: Option<usize>,
293 style: &theme::Tab,
294 _cx: &gpui::AppContext,
295 ) -> gpui::ElementBox {
296 Flex::row()
297 .with_child(
298 Label::new("Welcome to Zed!", style.label.clone())
299 .aligned()
300 .contained()
301 .boxed(),
302 )
303 .boxed()
304 }
305
306 fn show_toolbar(&self) -> bool {
307 false
308 }
309 fn clone_on_split(
310 &self,
311 _workspace_id: WorkspaceId,
312 cx: &mut ViewContext<Self>,
313 ) -> Option<Self> {
314 Some(WelcomePage::new(cx))
315 }
316}