From 9a8de58ae5b44bbef3961c841cb6955181313bf7 Mon Sep 17 00:00:00 2001 From: Dario <77837029+dve00@users.noreply.github.com> Date: Wed, 1 Apr 2026 23:44:04 +0200 Subject: [PATCH] zed: Open About window as standalone window (#52523) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Context Replaces the native system prompt used for "About Zed" with a custom standalone GPUI window which can open without the need for opening a Zed editor window of none are currently open. Closes #49157 ## How to Review Single file change in `crates/zed/src/zed.rs` — the `about` function is replaced by `open_about_window`. The new window deduplicates itself, matches the old layout (title, version info, Ok + Copy buttons), and closes on either button or Escape. ## Screenshots ### Old Behavior Opening the about window when no other Zed window is open will open a new empty zed window and display the about window on top: image ### New Behavior If there is no open Zed window we just open an instance of the new About window: image If there is at least one open Zed window, we display the About window on top of it: image In bright mode: image Additionally, the ESC button will now act like clicking the Ok button and close the about window without copying its content. ## Self-Review Checklist - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content is consistent with the [UI/UX checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) - [x] Tests cover the new/changed behavior - no new tests have been implemented - [x] Performance impact has been considered and is acceptable Release Notes: - Improved the About Zed dialog to open as a standalone window instead of a native system prompt --------- Co-authored-by: Danilo Leal --- crates/zed/src/zed.rs | 261 +++++++++++++++++++++++++++++++++++------- 1 file changed, 217 insertions(+), 44 deletions(-) diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 01e2354849f3a70399c680c44bd1a3cfbeb64dc4..75fe04feff794f21ff3fdd0e763084e4887a040b 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -33,10 +33,11 @@ use git_ui::commit_view::CommitViewToolbar; use git_ui::git_panel::GitPanel; use git_ui::project_diff::{BranchDiffToolbar, ProjectDiffToolbar}; use gpui::{ - Action, App, AppContext as _, AsyncWindowContext, Context, DismissEvent, Element, Entity, - Focusable, KeyBinding, ParentElement, PathPromptOptions, PromptLevel, ReadGlobal, SharedString, - Task, TitlebarOptions, UpdateGlobal, WeakEntity, Window, WindowHandle, WindowKind, - WindowOptions, actions, image_cache, point, px, retain_all, + Action, App, AppContext as _, AsyncWindowContext, ClipboardItem, Context, DismissEvent, + Element, Entity, FocusHandle, Focusable, Image, ImageFormat, KeyBinding, ParentElement, + PathPromptOptions, PromptLevel, ReadGlobal, SharedString, Size, Task, TitlebarOptions, + UpdateGlobal, WeakEntity, Window, WindowBounds, WindowHandle, WindowKind, WindowOptions, + actions, image_cache, img, point, px, retain_all, }; use image_viewer::ImageInfo; use language::Capability; @@ -78,7 +79,7 @@ use std::{ use terminal_view::terminal_panel::{self, TerminalPanel}; use theme::{ActiveTheme, SystemAppearance, ThemeRegistry, deserialize_icon_theme}; use theme_settings::{ThemeSettings, load_user_theme}; -use ui::{PopoverMenuHandle, prelude::*}; +use ui::{Navigable, NavigableEntry, PopoverMenuHandle, TintColor, prelude::*}; use util::markdown::MarkdownString; use util::rel_path::RelPath; use util::{ResultExt, asset_str, maybe}; @@ -96,8 +97,8 @@ use workspace::{ }; use workspace::{Pane, notifications::DetachAndPromptErr}; use zed_actions::{ - OpenAccountSettings, OpenBrowser, OpenDocs, OpenServerSettings, OpenSettingsFile, OpenZedUrl, - Quit, + About, OpenAccountSettings, OpenBrowser, OpenDocs, OpenServerSettings, OpenSettingsFile, + OpenZedUrl, Quit, }; actions!( @@ -277,10 +278,8 @@ pub fn init(cx: &mut App) { ); }); }) - .on_action(|_: &zed_actions::About, cx| { - with_active_or_new_workspace(cx, |workspace, window, cx| { - about(workspace, window, cx); - }); + .on_action(|_: &About, cx| { + open_about_window(cx); }); } @@ -1249,44 +1248,218 @@ fn initialize_pane( }); } -fn about(_: &mut Workspace, window: &mut Window, cx: &mut Context) { - use std::fmt::Write; - let release_channel = ReleaseChannel::global(cx).display_name(); - let full_version = AppVersion::global(cx); - let version = env!("CARGO_PKG_VERSION"); - let debug = if cfg!(debug_assertions) { - "(debug)" - } else { - "" - }; - let message = format!("{release_channel} {version} {debug}"); +fn open_about_window(cx: &mut App) { + fn about_window_icon(release_channel: ReleaseChannel) -> Arc { + let bytes = match release_channel { + ReleaseChannel::Dev => include_bytes!("../resources/app-icon-dev.png").as_slice(), + ReleaseChannel::Nightly => { + include_bytes!("../resources/app-icon-nightly.png").as_slice() + } + ReleaseChannel::Preview => { + include_bytes!("../resources/app-icon-preview.png").as_slice() + } + ReleaseChannel::Stable => include_bytes!("../resources/app-icon.png").as_slice(), + }; - let mut detail = AppCommitSha::try_global(cx) - .map(|sha| sha.full()) - .unwrap_or_default(); - if !detail.is_empty() { - detail.push('\n'); + Arc::new(Image::from_bytes(ImageFormat::Png, bytes.to_vec())) } - _ = write!(&mut detail, "\n{full_version}"); - let detail = Some(detail); + struct AboutWindow { + focus_handle: FocusHandle, + ok_entry: NavigableEntry, + copy_entry: NavigableEntry, + app_icon: Arc, + message: SharedString, + commit: Option, + full_version: SharedString, + } - let prompt = window.prompt( - PromptLevel::Info, - &message, - detail.as_deref(), - &["Copy", "OK"], - cx, - ); - cx.spawn(async move |_, cx| { - if let Ok(0) = prompt.await { - let content = format!("{}\n{}", message, detail.as_deref().unwrap_or("")); - cx.update(|cx| { - cx.write_to_clipboard(gpui::ClipboardItem::new_string(content)); - }); + impl AboutWindow { + fn new(cx: &mut Context) -> Self { + let release_channel = ReleaseChannel::global(cx); + let release_channel_name = release_channel.display_name(); + let full_version: SharedString = AppVersion::global(cx).to_string().into(); + let version = env!("CARGO_PKG_VERSION"); + + let debug = if cfg!(debug_assertions) { + "(debug)" + } else { + "" + }; + let message: SharedString = format!("{release_channel_name} {version} {debug}").into(); + let commit = AppCommitSha::try_global(cx) + .map(|sha| sha.full()) + .filter(|commit| !commit.is_empty()) + .map(SharedString::from); + + Self { + focus_handle: cx.focus_handle(), + ok_entry: NavigableEntry::focusable(cx), + copy_entry: NavigableEntry::focusable(cx), + app_icon: about_window_icon(release_channel), + message, + commit, + full_version, + } } - }) - .detach(); + + fn copy_details(&self, window: &mut Window, cx: &mut Context) { + let content = match self.commit.as_ref() { + Some(commit) => { + format!( + "{}\nCommit: {}\nVersion: {}", + self.message, commit, self.full_version + ) + } + None => format!("{}\nVersion: {}", self.message, self.full_version), + }; + cx.write_to_clipboard(ClipboardItem::new_string(content)); + window.remove_window(); + } + } + + impl Render for AboutWindow { + fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { + let ok_is_focused = self.ok_entry.focus_handle.contains_focused(window, cx); + let copy_is_focused = self.copy_entry.focus_handle.contains_focused(window, cx); + + Navigable::new( + v_flex() + .id("about-window") + .track_focus(&self.focus_handle) + .on_action(cx.listener(|_, _: &menu::Cancel, window, _cx| { + window.remove_window(); + })) + .min_w_0() + .size_full() + .bg(cx.theme().colors().editor_background) + .text_color(cx.theme().colors().text) + .p_4() + .when(cfg!(target_os = "macos"), |this| this.pt_10()) + .gap_4() + .text_center() + .justify_between() + .child( + v_flex() + .w_full() + .gap_2() + .items_center() + .child(img(self.app_icon.clone()).size_16().flex_none()) + .child(Headline::new(self.message.clone())) + .when_some(self.commit.clone(), |this, commit| { + this.child( + Label::new("Commit") + .color(Color::Muted) + .size(LabelSize::XSmall), + ) + .child(Label::new(commit).size(LabelSize::Small)) + }) + .child( + Label::new("Version") + .color(Color::Muted) + .size(LabelSize::XSmall), + ) + .child(Label::new(self.full_version.clone()).size(LabelSize::Small)), + ) + .child( + h_flex() + .w_full() + .gap_1() + .child( + div() + .flex_1() + .track_focus(&self.ok_entry.focus_handle) + .on_action(cx.listener(|_, _: &menu::Confirm, window, _cx| { + window.remove_window(); + })) + .child( + Button::new("ok", "Ok") + .full_width() + .style(ButtonStyle::OutlinedGhost) + .toggle_state(ok_is_focused) + .selected_style(ButtonStyle::Tinted(TintColor::Accent)) + .on_click(cx.listener(|_, _, window, _cx| { + window.remove_window(); + })), + ), + ) + .child( + div() + .flex_1() + .track_focus(&self.copy_entry.focus_handle) + .on_action(cx.listener( + |this, _: &menu::Confirm, window, cx| { + this.copy_details(window, cx); + }, + )) + .child( + Button::new("copy", "Copy") + .full_width() + .style(ButtonStyle::Tinted(TintColor::Accent)) + .toggle_state(copy_is_focused) + .selected_style(ButtonStyle::Tinted(TintColor::Accent)) + .on_click(cx.listener(|this, _event, window, cx| { + this.copy_details(window, cx); + })), + ), + ), + ) + .into_any_element(), + ) + .entry(self.ok_entry.clone()) + .entry(self.copy_entry.clone()) + } + } + + impl Focusable for AboutWindow { + fn focus_handle(&self, _cx: &App) -> FocusHandle { + self.ok_entry.focus_handle.clone() + } + } + + // Don't open about window twice + if let Some(existing) = cx + .windows() + .into_iter() + .find_map(|w| w.downcast::()) + { + existing + .update(cx, |about_window, window, cx| { + window.activate_window(); + about_window.ok_entry.focus_handle.focus(window, cx); + }) + .log_err(); + return; + } + + let window_size = Size { + width: px(440.), + height: px(300.), + }; + + cx.open_window( + WindowOptions { + titlebar: Some(TitlebarOptions { + title: Some("About Zed".into()), + appears_transparent: true, + traffic_light_position: Some(point(px(12.), px(12.))), + }), + window_bounds: Some(WindowBounds::centered(window_size, cx)), + is_resizable: false, + is_minimizable: false, + kind: WindowKind::Normal, + app_id: Some(ReleaseChannel::global(cx).app_id().to_owned()), + ..Default::default() + }, + |window, cx| { + let about_window = cx.new(AboutWindow::new); + let focus_handle = about_window.read(cx).ok_entry.focus_handle.clone(); + window.activate_window(); + focus_handle.focus(window, cx); + about_window + }, + ) + .log_err(); } #[cfg(not(target_os = "windows"))]