mod application_menu;mod collab;mod platforms;mod window_controls; #[cfg(feature = "stories")]mod stories; use crate::application_menu::ApplicationMenu; #[cfg(not(target_os = "macos"))]use crate::application_menu::{ ActivateDirection, ActivateMenuLeft, ActivateMenuRight, OpenApplicationMenu,}; use crate::platforms::{platform_linux, platform_mac, platform_windows};use auto_update::AutoUpdateStatus;use call::ActiveCall;use client::{Client, UserStore};use feature_flags::{FeatureFlagAppExt, ZedPro};use gpui::{ actions, div, px, Action, AnyElement, App, Context, Decorations, Element, Entity, InteractiveElement, Interactivity, IntoElement, MouseButton, ParentElement, Render, Stateful, StatefulInteractiveElement, Styled, Subscription, WeakEntity, Window,};use project::Project;use rpc::proto;use settings::Settings as _;use smallvec::SmallVec;use std::sync::Arc;use theme::ActiveTheme;use ui::{ h_flex, prelude::*, Avatar, Button, ButtonLike, ButtonStyle, ContextMenu, Icon, IconName, IconSize, IconWithIndicator, Indicator, PopoverMenu, Tooltip,};use util::ResultExt;use workspace::{notifications::NotifyResultExt, Workspace};use zed_actions::{OpenBrowser, OpenRecent, OpenRemote};use zeta::ZedPredictBanner; #[cfg(feature = "stories")]pub use stories::*; const MAX_PROJECT_NAME_LENGTH: usize = 40;const MAX_BRANCH_NAME_LENGTH: usize = 40; const BOOK_ONBOARDING: &str = "https://dub.sh/zed-c-onboarding"; actions!( collab, [ ShareProject, UnshareProject, ToggleUserMenu, ToggleProjectMenu, SwitchBranch ]); pub fn init(cx: &mut App) { cx.observe_new(|workspace: &mut Workspace, window, cx| { let Some(window) = window else { return; }; let item = cx.new(|cx| TitleBar::new("title-bar", workspace, window, cx)); workspace.set_titlebar_item(item.into(), window, cx); #[cfg(not(target_os = "macos"))] workspace.register_action(|workspace, action: &OpenApplicationMenu, window, cx| { if let Some(titlebar) = workspace .titlebar_item() .and_then(|item| item.downcast::<TitleBar>().ok()) { titlebar.update(cx, |titlebar, cx| { if let Some(ref menu) = titlebar.application_menu { menu.update(cx, |menu, cx| menu.open_menu(action, window, cx)); } }); } }); #[cfg(not(target_os = "macos"))] workspace.register_action(|workspace, _: &ActivateMenuRight, window, cx| { if let Some(titlebar) = workspace .titlebar_item() .and_then(|item| item.downcast::<TitleBar>().ok()) { titlebar.update(cx, |titlebar, cx| { if let Some(ref menu) = titlebar.application_menu { menu.update(cx, |menu, cx| { menu.navigate_menus_in_direction(ActivateDirection::Right, window, cx) }); } }); } }); #[cfg(not(target_os = "macos"))] workspace.register_action(|workspace, _: &ActivateMenuLeft, window, cx| { if let Some(titlebar) = workspace .titlebar_item() .and_then(|item| item.downcast::<TitleBar>().ok()) { titlebar.update(cx, |titlebar, cx| { if let Some(ref menu) = titlebar.application_menu { menu.update(cx, |menu, cx| { menu.navigate_menus_in_direction(ActivateDirection::Left, window, cx) }); } }); } }); }) .detach();} pub struct TitleBar { platform_style: PlatformStyle, content: Stateful<Div>, children: SmallVec<[AnyElement; 2]>, project: Entity<Project>, user_store: Entity<UserStore>, client: Arc<Client>, workspace: WeakEntity<Workspace>, should_move: bool, application_menu: Option<Entity<ApplicationMenu>>, _subscriptions: Vec<Subscription>, zed_predict_banner: Entity<ZedPredictBanner>,} impl Render for TitleBar { fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { let close_action = Box::new(workspace::CloseWindow); let height = Self::height(window); let supported_controls = window.window_controls(); let decorations = window.window_decorations(); let titlebar_color = if cfg!(any(target_os = "linux", target_os = "freebsd")) { if window.is_window_active() && !self.should_move { cx.theme().colors().title_bar_background } else { cx.theme().colors().title_bar_inactive_background } } else { cx.theme().colors().title_bar_background }; h_flex() .id("titlebar") .w_full() .h(height) .map(|this| { if window.is_fullscreen() { this.pl_2() } else if self.platform_style == PlatformStyle::Mac { this.pl(px(platform_mac::TRAFFIC_LIGHT_PADDING)) } else { this.pl_2() } }) .map(|el| match decorations { Decorations::Server => el, Decorations::Client { tiling, .. } => el .when(!(tiling.top || tiling.right), |el| { el.rounded_tr(theme::CLIENT_SIDE_DECORATION_ROUNDING) }) .when(!(tiling.top || tiling.left), |el| { el.rounded_tl(theme::CLIENT_SIDE_DECORATION_ROUNDING) }) // this border is to avoid a transparent gap in the rounded corners .mt(px(-1.)) .border(px(1.)) .border_color(titlebar_color), }) .bg(titlebar_color) .content_stretch() .child( div() .id("titlebar-content") .flex() .flex_row() .items_center() .justify_between() .w_full() // Note: On Windows the title bar behavior is handled by the platform implementation. .when(self.platform_style != PlatformStyle::Windows, |this| { this.on_click(|event, window, _| { if event.up.click_count == 2 { window.zoom_window(); } }) }) .child( h_flex() .gap_1() .map(|title_bar| { let mut render_project_items = true; title_bar .when_some(self.application_menu.clone(), |title_bar, menu| { render_project_items = !menu.read(cx).all_menus_shown(); title_bar.child(menu) }) .when(render_project_items, |title_bar| { title_bar .children(self.render_project_host(cx)) .child(self.render_project_name(cx)) .children(self.render_project_branch(cx)) }) }) .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation()), ) .child(self.render_collaborator_list(window, cx)) .child(self.zed_predict_banner.clone()) .child( h_flex() .gap_1() .pr_1() .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation()) .children(self.render_call_controls(window, cx)) .map(|el| { let status = self.client.status(); let status = &*status.borrow(); if matches!(status, client::Status::Connected { .. }) { el.child(self.render_user_menu_button(cx)) } else { el.children(self.render_connection_status(status, cx)) .child(self.render_sign_in_button(cx)) .child(self.render_user_menu_button(cx)) } }), ), ) .when(!window.is_fullscreen(), |title_bar| { match self.platform_style { PlatformStyle::Mac => title_bar, PlatformStyle::Linux => { if matches!(decorations, Decorations::Client { .. }) { title_bar .child(platform_linux::LinuxWindowControls::new(close_action)) .when(supported_controls.window_menu, |titlebar| { titlebar.on_mouse_down( gpui::MouseButton::Right, move |ev, window, _| window.show_window_menu(ev.position), ) }) .on_mouse_move(cx.listener(move |this, _ev, window, _| { if this.should_move { this.should_move = false; window.start_window_move(); } })) .on_mouse_down_out(cx.listener(move |this, _ev, _window, _cx| { this.should_move = false; })) .on_mouse_up( gpui::MouseButton::Left, cx.listener(move |this, _ev, _window, _cx| { this.should_move = false; }), ) .on_mouse_down( gpui::MouseButton::Left, cx.listener(move |this, _ev, _window, _cx| { this.should_move = true; }), ) } else { title_bar } } PlatformStyle::Windows => { title_bar.child(platform_windows::WindowsWindowControls::new(height)) } } }) }} impl TitleBar { pub fn new( id: impl Into<ElementId>, workspace: &Workspace, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let project = workspace.project().clone(); let user_store = workspace.app_state().user_store.clone(); let client = workspace.app_state().client.clone(); let active_call = ActiveCall::global(cx); let platform_style = PlatformStyle::platform(); let application_menu = match platform_style { PlatformStyle::Mac => { if option_env!("ZED_USE_CROSS_PLATFORM_MENU").is_some() { Some(cx.new(|cx| ApplicationMenu::new(window, cx))) } else { None } } PlatformStyle::Linux | PlatformStyle::Windows => { Some(cx.new(|cx| ApplicationMenu::new(window, cx))) } }; let mut subscriptions = Vec::new(); subscriptions.push( cx.observe(&workspace.weak_handle().upgrade().unwrap(), |_, _, cx| { cx.notify() }), ); subscriptions.push(cx.observe(&project, |_, _, cx| cx.notify())); subscriptions.push(cx.observe(&active_call, |this, _, cx| this.active_call_changed(cx))); subscriptions.push(cx.observe_window_activation(window, Self::window_activation_changed)); subscriptions.push(cx.observe(&user_store, |_, _, cx| cx.notify())); let zed_predict_banner = cx.new(ZedPredictBanner::new); Self { platform_style, content: div().id(id.into()), children: SmallVec::new(), application_menu, workspace: workspace.weak_handle(), should_move: false, project, user_store, client, _subscriptions: subscriptions, zed_predict_banner, } } #[cfg(not(target_os = "windows"))] pub fn height(window: &mut Window) -> Pixels { (1.75 * window.rem_size()).max(px(34.)) } #[cfg(target_os = "windows")] pub fn height(_window: &mut Window) -> Pixels { // todo(windows) instead of hard coded size report the actual size to the Windows platform API px(32.) } /// Sets the platform style. pub fn platform_style(mut self, style: PlatformStyle) -> Self { self.platform_style = style; self } fn render_ssh_project_host(&self, cx: &mut Context<Self>) -> Option<AnyElement> { let options = self.project.read(cx).ssh_connection_options(cx)?; let host: SharedString = options.connection_string().into(); let nickname = options .nickname .clone() .map(|nick| nick.into()) .unwrap_or_else(|| host.clone()); let (indicator_color, meta) = match self.project.read(cx).ssh_connection_state(cx)? { remote::ConnectionState::Connecting => (Color::Info, format!("Connecting to: {host}")), remote::ConnectionState::Connected => (Color::Success, format!("Connected to: {host}")), remote::ConnectionState::HeartbeatMissed => ( Color::Warning, format!("Connection attempt to {host} missed. Retrying..."), ), remote::ConnectionState::Reconnecting => ( Color::Warning, format!("Lost connection to {host}. Reconnecting..."), ), remote::ConnectionState::Disconnected => { (Color::Error, format!("Disconnected from {host}")) } }; let icon_color = match self.project.read(cx).ssh_connection_state(cx)? { remote::ConnectionState::Connecting => Color::Info, remote::ConnectionState::Connected => Color::Default, remote::ConnectionState::HeartbeatMissed => Color::Warning, remote::ConnectionState::Reconnecting => Color::Warning, remote::ConnectionState::Disconnected => Color::Error, }; let meta = SharedString::from(meta); Some( ButtonLike::new("ssh-server-icon") .child( h_flex() .gap_2() .max_w_32() .child( IconWithIndicator::new( Icon::new(IconName::Server) .size(IconSize::XSmall) .color(icon_color), Some(Indicator::dot().color(indicator_color)), ) .indicator_border_color(Some(cx.theme().colors().title_bar_background)) .into_any_element(), ) .child( Label::new(nickname.clone()) .size(LabelSize::Small) .text_ellipsis(), ), ) .tooltip(move |window, cx| { Tooltip::with_meta( "Remote Project", Some(&OpenRemote), meta.clone(), window, cx, ) }) .on_click(|_, window, cx| { window.dispatch_action(OpenRemote.boxed_clone(), cx); }) .into_any_element(), ) } pub fn render_project_host(&self, cx: &mut Context<Self>) -> Option<AnyElement> { if self.project.read(cx).is_via_ssh() { return self.render_ssh_project_host(cx); } if self.project.read(cx).is_disconnected(cx) { return Some( Button::new("disconnected", "Disconnected") .disabled(true) .color(Color::Disabled) .style(ButtonStyle::Subtle) .label_size(LabelSize::Small) .into_any_element(), ); } let host = self.project.read(cx).host()?; let host_user = self.user_store.read(cx).get_cached_user(host.user_id)?; let participant_index = self .user_store .read(cx) .participant_indices() .get(&host_user.id)?; Some( Button::new("project_owner_trigger", host_user.github_login.clone()) .color(Color::Player(participant_index.0)) .style(ButtonStyle::Subtle) .label_size(LabelSize::Small) .tooltip(Tooltip::text(format!( "{} is sharing this project. Click to follow.", host_user.github_login.clone() ))) .on_click({ let host_peer_id = host.peer_id; cx.listener(move |this, _, window, cx| { this.workspace .update(cx, |workspace, cx| { workspace.follow(host_peer_id, window, cx); }) .log_err(); }) }) .into_any_element(), ) } pub fn render_project_name(&self, cx: &mut Context<Self>) -> impl IntoElement { let name = { let mut names = self.project.read(cx).visible_worktrees(cx).map(|worktree| { let worktree = worktree.read(cx); worktree.root_name() }); names.next() }; let is_project_selected = name.is_some(); let name = if let Some(name) = name { util::truncate_and_trailoff(name, MAX_PROJECT_NAME_LENGTH) } else { "Open recent project".to_string() }; Button::new("project_name_trigger", name) .when(!is_project_selected, |b| b.color(Color::Muted)) .style(ButtonStyle::Subtle) .label_size(LabelSize::Small) .tooltip(move |window, cx| { Tooltip::for_action( "Recent Projects", &zed_actions::OpenRecent { create_new_window: false, }, window, cx, ) }) .on_click(cx.listener(move |_, _, window, cx| { window.dispatch_action(Lines 1–500 of 760. Load more
mod application_menu; mod collab; mod platforms; mod window_controls; #[cfg(feature = "stories")] mod stories; use crate::application_menu::ApplicationMenu; #[cfg(not(target_os = "macos"))] use crate::application_menu::{ ActivateDirection, ActivateMenuLeft, ActivateMenuRight, OpenApplicationMenu, }; use crate::platforms::{platform_linux, platform_mac, platform_windows}; use auto_update::AutoUpdateStatus; use call::ActiveCall; use client::{Client, UserStore}; use feature_flags::{FeatureFlagAppExt, ZedPro}; use gpui::{ actions, div, px, Action, AnyElement, App, Context, Decorations, Element, Entity, InteractiveElement, Interactivity, IntoElement, MouseButton, ParentElement, Render, Stateful, StatefulInteractiveElement, Styled, Subscription, WeakEntity, Window, }; use project::Project; use rpc::proto; use settings::Settings as _; use smallvec::SmallVec; use std::sync::Arc; use theme::ActiveTheme; use ui::{ h_flex, prelude::*, Avatar, Button, ButtonLike, ButtonStyle, ContextMenu, Icon, IconName, IconSize, IconWithIndicator, Indicator, PopoverMenu, Tooltip, }; use util::ResultExt; use workspace::{notifications::NotifyResultExt, Workspace}; use zed_actions::{OpenBrowser, OpenRecent, OpenRemote}; use zeta::ZedPredictBanner; #[cfg(feature = "stories")] pub use stories::*; const MAX_PROJECT_NAME_LENGTH: usize = 40; const MAX_BRANCH_NAME_LENGTH: usize = 40; const BOOK_ONBOARDING: &str = "https://dub.sh/zed-c-onboarding"; actions!( collab, [ ShareProject, UnshareProject, ToggleUserMenu, ToggleProjectMenu, SwitchBranch ] ); pub fn init(cx: &mut App) { cx.observe_new(|workspace: &mut Workspace, window, cx| { let Some(window) = window else { return; }; let item = cx.new(|cx| TitleBar::new("title-bar", workspace, window, cx)); workspace.set_titlebar_item(item.into(), window, cx); #[cfg(not(target_os = "macos"))] workspace.register_action(|workspace, action: &OpenApplicationMenu, window, cx| { if let Some(titlebar) = workspace .titlebar_item() .and_then(|item| item.downcast::<TitleBar>().ok()) { titlebar.update(cx, |titlebar, cx| { if let Some(ref menu) = titlebar.application_menu { menu.update(cx, |menu, cx| menu.open_menu(action, window, cx)); } }); } }); #[cfg(not(target_os = "macos"))] workspace.register_action(|workspace, _: &ActivateMenuRight, window, cx| { if let Some(titlebar) = workspace .titlebar_item() .and_then(|item| item.downcast::<TitleBar>().ok()) { titlebar.update(cx, |titlebar, cx| { if let Some(ref menu) = titlebar.application_menu { menu.update(cx, |menu, cx| { menu.navigate_menus_in_direction(ActivateDirection::Right, window, cx) }); } }); } }); #[cfg(not(target_os = "macos"))] workspace.register_action(|workspace, _: &ActivateMenuLeft, window, cx| { if let Some(titlebar) = workspace .titlebar_item() .and_then(|item| item.downcast::<TitleBar>().ok()) { titlebar.update(cx, |titlebar, cx| { if let Some(ref menu) = titlebar.application_menu { menu.update(cx, |menu, cx| { menu.navigate_menus_in_direction(ActivateDirection::Left, window, cx) }); } }); } }); }) .detach(); } pub struct TitleBar { platform_style: PlatformStyle, content: Stateful<Div>, children: SmallVec<[AnyElement; 2]>, project: Entity<Project>, user_store: Entity<UserStore>, client: Arc<Client>, workspace: WeakEntity<Workspace>, should_move: bool, application_menu: Option<Entity<ApplicationMenu>>, _subscriptions: Vec<Subscription>, zed_predict_banner: Entity<ZedPredictBanner>, } impl Render for TitleBar { fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { let close_action = Box::new(workspace::CloseWindow); let height = Self::height(window); let supported_controls = window.window_controls(); let decorations = window.window_decorations(); let titlebar_color = if cfg!(any(target_os = "linux", target_os = "freebsd")) { if window.is_window_active() && !self.should_move { cx.theme().colors().title_bar_background } else { cx.theme().colors().title_bar_inactive_background } } else { cx.theme().colors().title_bar_background }; h_flex() .id("titlebar") .w_full() .h(height) .map(|this| { if window.is_fullscreen() { this.pl_2() } else if self.platform_style == PlatformStyle::Mac { this.pl(px(platform_mac::TRAFFIC_LIGHT_PADDING)) } else { this.pl_2() } }) .map(|el| match decorations { Decorations::Server => el, Decorations::Client { tiling, .. } => el .when(!(tiling.top || tiling.right), |el| { el.rounded_tr(theme::CLIENT_SIDE_DECORATION_ROUNDING) }) .when(!(tiling.top || tiling.left), |el| { el.rounded_tl(theme::CLIENT_SIDE_DECORATION_ROUNDING) }) // this border is to avoid a transparent gap in the rounded corners .mt(px(-1.)) .border(px(1.)) .border_color(titlebar_color), }) .bg(titlebar_color) .content_stretch() .child( div() .id("titlebar-content") .flex() .flex_row() .items_center() .justify_between() .w_full() // Note: On Windows the title bar behavior is handled by the platform implementation. .when(self.platform_style != PlatformStyle::Windows, |this| { this.on_click(|event, window, _| { if event.up.click_count == 2 { window.zoom_window(); } }) }) .child( h_flex() .gap_1() .map(|title_bar| { let mut render_project_items = true; title_bar .when_some(self.application_menu.clone(), |title_bar, menu| { render_project_items = !menu.read(cx).all_menus_shown(); title_bar.child(menu) }) .when(render_project_items, |title_bar| { title_bar .children(self.render_project_host(cx)) .child(self.render_project_name(cx)) .children(self.render_project_branch(cx)) }) }) .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation()), ) .child(self.render_collaborator_list(window, cx)) .child(self.zed_predict_banner.clone()) .child( h_flex() .gap_1() .pr_1() .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation()) .children(self.render_call_controls(window, cx)) .map(|el| { let status = self.client.status(); let status = &*status.borrow(); if matches!(status, client::Status::Connected { .. }) { el.child(self.render_user_menu_button(cx)) } else { el.children(self.render_connection_status(status, cx)) .child(self.render_sign_in_button(cx)) .child(self.render_user_menu_button(cx)) } }), ), ) .when(!window.is_fullscreen(), |title_bar| { match self.platform_style { PlatformStyle::Mac => title_bar, PlatformStyle::Linux => { if matches!(decorations, Decorations::Client { .. }) { title_bar .child(platform_linux::LinuxWindowControls::new(close_action)) .when(supported_controls.window_menu, |titlebar| { titlebar.on_mouse_down( gpui::MouseButton::Right, move |ev, window, _| window.show_window_menu(ev.position), ) }) .on_mouse_move(cx.listener(move |this, _ev, window, _| { if this.should_move { this.should_move = false; window.start_window_move(); } })) .on_mouse_down_out(cx.listener(move |this, _ev, _window, _cx| { this.should_move = false; })) .on_mouse_up( gpui::MouseButton::Left, cx.listener(move |this, _ev, _window, _cx| { this.should_move = false; }), ) .on_mouse_down( gpui::MouseButton::Left, cx.listener(move |this, _ev, _window, _cx| { this.should_move = true; }), ) } else { title_bar } } PlatformStyle::Windows => { title_bar.child(platform_windows::WindowsWindowControls::new(height)) } } }) } } impl TitleBar { pub fn new( id: impl Into<ElementId>, workspace: &Workspace, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let project = workspace.project().clone(); let user_store = workspace.app_state().user_store.clone(); let client = workspace.app_state().client.clone(); let active_call = ActiveCall::global(cx); let platform_style = PlatformStyle::platform(); let application_menu = match platform_style { PlatformStyle::Mac => { if option_env!("ZED_USE_CROSS_PLATFORM_MENU").is_some() { Some(cx.new(|cx| ApplicationMenu::new(window, cx))) } else { None } } PlatformStyle::Linux | PlatformStyle::Windows => { Some(cx.new(|cx| ApplicationMenu::new(window, cx))) } }; let mut subscriptions = Vec::new(); subscriptions.push( cx.observe(&workspace.weak_handle().upgrade().unwrap(), |_, _, cx| { cx.notify() }), ); subscriptions.push(cx.observe(&project, |_, _, cx| cx.notify())); subscriptions.push(cx.observe(&active_call, |this, _, cx| this.active_call_changed(cx))); subscriptions.push(cx.observe_window_activation(window, Self::window_activation_changed)); subscriptions.push(cx.observe(&user_store, |_, _, cx| cx.notify())); let zed_predict_banner = cx.new(ZedPredictBanner::new); Self { platform_style, content: div().id(id.into()), children: SmallVec::new(), application_menu, workspace: workspace.weak_handle(), should_move: false, project, user_store, client, _subscriptions: subscriptions, zed_predict_banner, } } #[cfg(not(target_os = "windows"))] pub fn height(window: &mut Window) -> Pixels { (1.75 * window.rem_size()).max(px(34.)) } #[cfg(target_os = "windows")] pub fn height(_window: &mut Window) -> Pixels { // todo(windows) instead of hard coded size report the actual size to the Windows platform API px(32.) } /// Sets the platform style. pub fn platform_style(mut self, style: PlatformStyle) -> Self { self.platform_style = style; self } fn render_ssh_project_host(&self, cx: &mut Context<Self>) -> Option<AnyElement> { let options = self.project.read(cx).ssh_connection_options(cx)?; let host: SharedString = options.connection_string().into(); let nickname = options .nickname .clone() .map(|nick| nick.into()) .unwrap_or_else(|| host.clone()); let (indicator_color, meta) = match self.project.read(cx).ssh_connection_state(cx)? { remote::ConnectionState::Connecting => (Color::Info, format!("Connecting to: {host}")), remote::ConnectionState::Connected => (Color::Success, format!("Connected to: {host}")), remote::ConnectionState::HeartbeatMissed => ( Color::Warning, format!("Connection attempt to {host} missed. Retrying..."), ), remote::ConnectionState::Reconnecting => ( Color::Warning, format!("Lost connection to {host}. Reconnecting..."), ), remote::ConnectionState::Disconnected => { (Color::Error, format!("Disconnected from {host}")) } }; let icon_color = match self.project.read(cx).ssh_connection_state(cx)? { remote::ConnectionState::Connecting => Color::Info, remote::ConnectionState::Connected => Color::Default, remote::ConnectionState::HeartbeatMissed => Color::Warning, remote::ConnectionState::Reconnecting => Color::Warning, remote::ConnectionState::Disconnected => Color::Error, }; let meta = SharedString::from(meta); Some( ButtonLike::new("ssh-server-icon") .child( h_flex() .gap_2() .max_w_32() .child( IconWithIndicator::new( Icon::new(IconName::Server) .size(IconSize::XSmall) .color(icon_color), Some(Indicator::dot().color(indicator_color)), ) .indicator_border_color(Some(cx.theme().colors().title_bar_background)) .into_any_element(), ) .child( Label::new(nickname.clone()) .size(LabelSize::Small) .text_ellipsis(), ), ) .tooltip(move |window, cx| { Tooltip::with_meta( "Remote Project", Some(&OpenRemote), meta.clone(), window, cx, ) }) .on_click(|_, window, cx| { window.dispatch_action(OpenRemote.boxed_clone(), cx); }) .into_any_element(), ) } pub fn render_project_host(&self, cx: &mut Context<Self>) -> Option<AnyElement> { if self.project.read(cx).is_via_ssh() { return self.render_ssh_project_host(cx); } if self.project.read(cx).is_disconnected(cx) { return Some( Button::new("disconnected", "Disconnected") .disabled(true) .color(Color::Disabled) .style(ButtonStyle::Subtle) .label_size(LabelSize::Small) .into_any_element(), ); } let host = self.project.read(cx).host()?; let host_user = self.user_store.read(cx).get_cached_user(host.user_id)?; let participant_index = self .user_store .read(cx) .participant_indices() .get(&host_user.id)?; Some( Button::new("project_owner_trigger", host_user.github_login.clone()) .color(Color::Player(participant_index.0)) .style(ButtonStyle::Subtle) .label_size(LabelSize::Small) .tooltip(Tooltip::text(format!( "{} is sharing this project. Click to follow.", host_user.github_login.clone() ))) .on_click({ let host_peer_id = host.peer_id; cx.listener(move |this, _, window, cx| { this.workspace .update(cx, |workspace, cx| { workspace.follow(host_peer_id, window, cx); }) .log_err(); }) }) .into_any_element(), ) } pub fn render_project_name(&self, cx: &mut Context<Self>) -> impl IntoElement { let name = { let mut names = self.project.read(cx).visible_worktrees(cx).map(|worktree| { let worktree = worktree.read(cx); worktree.root_name() }); names.next() }; let is_project_selected = name.is_some(); let name = if let Some(name) = name { util::truncate_and_trailoff(name, MAX_PROJECT_NAME_LENGTH) } else { "Open recent project".to_string() }; Button::new("project_name_trigger", name) .when(!is_project_selected, |b| b.color(Color::Muted)) .style(ButtonStyle::Subtle) .label_size(LabelSize::Small) .tooltip(move |window, cx| { Tooltip::for_action( "Recent Projects", &zed_actions::OpenRecent { create_new_window: false, }, window, cx, ) }) .on_click(cx.listener(move |_, _, window, cx| { window.dispatch_action(