669 lines · 26.2 KB · Rust
RawDownload
mod application_menu;mod collab;mod platforms;mod window_controls; #[cfg(feature = "stories")]mod stories; use crate::application_menu::ApplicationMenu;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, AppContext, Decorations, Element, InteractiveElement,    Interactivity, IntoElement, Model, MouseButton, ParentElement, Render, Stateful,    StatefulInteractiveElement, Styled, Subscription, View, ViewContext, VisualContext, WeakView,};use project::{Project, RepositoryEntry};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}; #[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-onboarding"; actions!(    collab,    [        ShareProject,        UnshareProject,        ToggleUserMenu,        ToggleProjectMenu,        SwitchBranch    ]); pub fn init(cx: &mut AppContext) {    cx.observe_new_views(|workspace: &mut Workspace, cx| {        let item = cx.new_view(|cx| TitleBar::new("title-bar", workspace, cx));        workspace.set_titlebar_item(item.into(), cx)    })    .detach();} pub struct TitleBar {    platform_style: PlatformStyle,    content: Stateful<Div>,    children: SmallVec<[AnyElement; 2]>,    project: Model<Project>,    user_store: Model<UserStore>,    client: Arc<Client>,    workspace: WeakView<Workspace>,    should_move: bool,    application_menu: Option<View<ApplicationMenu>>,    _subscriptions: Vec<Subscription>,} impl Render for TitleBar {    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {        let close_action = Box::new(workspace::CloseWindow);        let height = Self::height(cx);        let supported_controls = cx.window_controls();        let decorations = cx.window_decorations();        let titlebar_color = if cfg!(any(target_os = "linux", target_os = "freebsd")) {            if cx.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 cx.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()                    .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, cx| {                            if event.up.click_count == 2 {                                cx.zoom_window();                            }                        })                    })                    .child(                        h_flex()                            .gap_1()                            .when_some(self.application_menu.clone(), |this, menu| this.child(menu))                            .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(cx))                    .child(                        h_flex()                            .gap_1()                            .pr_1()                            .on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation())                            .children(self.render_call_controls(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(!cx.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, cx| {                                    cx.show_window_menu(ev.position)                                })                            })                            .on_mouse_move(cx.listener(move |this, _ev, cx| {                                if this.should_move {                                    this.should_move = false;                                    cx.start_window_move();                                }                            }))                            .on_mouse_down_out(cx.listener(move |this, _ev, _cx| {                                this.should_move = false;                            }))                            .on_mouse_up(                                gpui::MouseButton::Left,                                cx.listener(move |this, _ev, _cx| {                                    this.should_move = false;                                }),                            )                            .on_mouse_down(                                gpui::MouseButton::Left,                                cx.listener(move |this, _ev, _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,        cx: &mut ViewContext<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 => None,            PlatformStyle::Linux | PlatformStyle::Windows => {                Some(cx.new_view(ApplicationMenu::new))            }        };         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(Self::window_activation_changed));        subscriptions.push(cx.observe(&user_store, |_, _, cx| cx.notify()));         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,        }    }     #[cfg(not(target_os = "windows"))]    pub fn height(cx: &mut WindowContext) -> Pixels {        (1.75 * cx.rem_size()).max(px(34.))    }     #[cfg(target_os = "windows")]    pub fn height(_cx: &mut WindowContext) -> 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 ViewContext<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(                    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(                    div()                        .max_w_32()                        .overflow_hidden()                        .text_ellipsis()                        .child(Label::new(nickname.clone()).size(LabelSize::Small)),                )                .tooltip(move |cx| {                    Tooltip::with_meta("Remote Project", Some(&OpenRemote), meta.clone(), cx)                })                .on_click(|_, cx| {                    cx.dispatch_action(OpenRemote.boxed_clone());                })                .into_any_element(),        )    }     pub fn render_project_host(&self, cx: &mut ViewContext<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(move |cx| {                    Tooltip::text(                        format!(                            "{} is sharing this project. Click to follow.",                            host_user.github_login.clone()                        ),                        cx,                    )                })                .on_click({                    let host_peer_id = host.peer_id;                    cx.listener(move |this, _, cx| {                        this.workspace                            .update(cx, |workspace, cx| {                                workspace.follow(host_peer_id, cx);                            })                            .log_err();                    })                })                .into_any_element(),        )    }     pub fn render_project_name(&self, cx: &mut ViewContext<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 |cx| {                Tooltip::for_action(                    "Recent Projects",                    &zed_actions::OpenRecent {                        create_new_window: false,                    },                    cx,                )            })            .on_click(cx.listener(move |_, _, cx| {                cx.dispatch_action(                    OpenRecent {                        create_new_window: false,                    }                    .boxed_clone(),                );            }))    }     pub fn render_project_branch(&self, cx: &mut ViewContext<Self>) -> Option<impl IntoElement> {        let entry = {            let mut names_and_branches =                self.project.read(cx).visible_worktrees(cx).map(|worktree| {                    let worktree = worktree.read(cx);                    worktree.root_git_entry()                });             names_and_branches.next().flatten()        };        let workspace = self.workspace.upgrade()?;        let branch_name = entry            .as_ref()            .and_then(RepositoryEntry::branch)            .map(|branch| util::truncate_and_trailoff(&branch, MAX_BRANCH_NAME_LENGTH))?;        Some(            Button::new("project_branch_trigger", branch_name)                .color(Color::Muted)                .style(ButtonStyle::Subtle)                .label_size(LabelSize::Small)                .tooltip(move |cx| {                    Tooltip::with_meta(                        "Recent Branches",                        Some(&zed_actions::branches::OpenRecent),                        "Local branches only",                        cx,                    )                })                .on_click(move |_, cx| {                    let _ = workspace.update(cx, |_this, cx| {                        cx.dispatch_action(zed_actions::branches::OpenRecent.boxed_clone());                    });                }),        )    }     fn window_activation_changed(&mut self, cx: &mut ViewContext<Self>) {        if cx.is_window_active() {            ActiveCall::global(cx)                .update(cx, |call, cx| call.set_location(Some(&self.project), cx))                .detach_and_log_err(cx);        } else if cx.active_window().is_none() {            ActiveCall::global(cx)                .update(cx, |call, cx| call.set_location(None, cx))                .detach_and_log_err(cx);        }        self.workspace            .update(cx, |workspace, cx| {                workspace.update_active_view_for_followers(cx);            })            .ok();    }     fn active_call_changed(&mut self, cx: &mut ViewContext<Self>) {        cx.notify();    }     fn share_project(&mut self, _: &ShareProject, cx: &mut ViewContext<Self>) {        let active_call = ActiveCall::global(cx);        let project = self.project.clone();        active_call            .update(cx, |call, cx| call.share_project(project, cx))            .detach_and_log_err(cx);    }     fn unshare_project(&mut self, _: &UnshareProject, cx: &mut ViewContext<Self>) {        let active_call = ActiveCall::global(cx);        let project = self.project.clone();        active_call            .update(cx, |call, cx| call.unshare_project(project, cx))            .log_err();    }     fn render_connection_status(        &self,        status: &client::Status,        cx: &mut ViewContext<Self>,    ) -> Option<AnyElement> {        match status {

Lines 1–500 of 669.