diff --git a/crates/client/src/user.rs b/crates/client/src/user.rs index 13439c6893..a885b3b1ad 100644 --- a/crates/client/src/user.rs +++ b/crates/client/src/user.rs @@ -501,6 +501,12 @@ impl Contact { } Ok(Self { user, projects }) } + + pub fn non_empty_projects(&self) -> impl Iterator { + self.projects + .iter() + .filter(|project| !project.worktree_root_names.is_empty()) + } } async fn fetch_avatar(http: &dyn HttpClient, url: &str) -> Result> { diff --git a/crates/contacts_panel/src/contacts_panel.rs b/crates/contacts_panel/src/contacts_panel.rs index e87888816e..4c354c5644 100644 --- a/crates/contacts_panel/src/contacts_panel.rs +++ b/crates/contacts_panel/src/contacts_panel.rs @@ -149,7 +149,7 @@ impl ContactsPanel { theme: &theme::ContactsPanel, cx: &mut LayoutContext, ) -> ElementBox { - let project_count = contact.projects.len(); + let project_count = contact.non_empty_projects().count(); let font_cache = cx.font_cache(); let line_height = theme.unshared_project.name.text.line_height(font_cache); let cap_height = theme.unshared_project.name.text.cap_height(font_cache); @@ -188,121 +188,128 @@ impl ContactsPanel { .with_height(theme.row_height) .boxed(), ) - .with_children(contact.projects.iter().enumerate().map(|(ix, project)| { - let project_id = project.id; + .with_children( + contact + .non_empty_projects() + .enumerate() + .map(|(ix, project)| { + let project_id = project.id; + Flex::row() + .with_child( + Canvas::new(move |bounds, _, cx| { + let start_x = bounds.min_x() + (bounds.width() / 2.) + - (tree_branch_width / 2.); + let end_x = bounds.max_x(); + let start_y = bounds.min_y(); + let end_y = + bounds.min_y() + baseline_offset - (cap_height / 2.); - Flex::row() - .with_child( - Canvas::new(move |bounds, _, cx| { - let start_x = - bounds.min_x() + (bounds.width() / 2.) - (tree_branch_width / 2.); - let end_x = bounds.max_x(); - let start_y = bounds.min_y(); - let end_y = bounds.min_y() + baseline_offset - (cap_height / 2.); + cx.scene.push_quad(gpui::Quad { + bounds: RectF::from_points( + vec2f(start_x, start_y), + vec2f( + start_x + tree_branch_width, + if ix + 1 == project_count { + end_y + } else { + bounds.max_y() + }, + ), + ), + background: Some(tree_branch_color), + border: gpui::Border::default(), + corner_radius: 0., + }); + cx.scene.push_quad(gpui::Quad { + bounds: RectF::from_points( + vec2f(start_x, end_y), + vec2f(end_x, end_y + tree_branch_width), + ), + background: Some(tree_branch_color), + border: gpui::Border::default(), + corner_radius: 0., + }); + }) + .constrained() + .with_width(host_avatar_height) + .boxed(), + ) + .with_child({ + let is_host = Some(contact.user.id) == current_user_id; + let is_guest = !is_host + && project + .guests + .iter() + .any(|guest| Some(guest.id) == current_user_id); + let is_shared = project.is_shared; + let app_state = app_state.clone(); - cx.scene.push_quad(gpui::Quad { - bounds: RectF::from_points( - vec2f(start_x, start_y), - vec2f( - start_x + tree_branch_width, - if ix + 1 == project_count { - end_y - } else { - bounds.max_y() - }, - ), - ), - background: Some(tree_branch_color), - border: gpui::Border::default(), - corner_radius: 0., - }); - cx.scene.push_quad(gpui::Quad { - bounds: RectF::from_points( - vec2f(start_x, end_y), - vec2f(end_x, end_y + tree_branch_width), - ), - background: Some(tree_branch_color), - border: gpui::Border::default(), - corner_radius: 0., - }); - }) - .constrained() - .with_width(host_avatar_height) - .boxed(), - ) - .with_child({ - let is_host = Some(contact.user.id) == current_user_id; - let is_guest = !is_host - && project - .guests - .iter() - .any(|guest| Some(guest.id) == current_user_id); - let is_shared = project.is_shared; - let app_state = app_state.clone(); + MouseEventHandler::new::( + project_id as usize, + cx, + |mouse_state, _| { + let style = match (project.is_shared, mouse_state.hovered) { + (false, false) => &theme.unshared_project, + (false, true) => &theme.hovered_unshared_project, + (true, false) => &theme.shared_project, + (true, true) => &theme.hovered_shared_project, + }; - MouseEventHandler::new::( - project_id as usize, - cx, - |mouse_state, _| { - let style = match (project.is_shared, mouse_state.hovered) { - (false, false) => &theme.unshared_project, - (false, true) => &theme.hovered_unshared_project, - (true, false) => &theme.shared_project, - (true, true) => &theme.hovered_shared_project, - }; - - Flex::row() - .with_child( - Label::new( - project.worktree_root_names.join(", "), - style.name.text.clone(), - ) - .aligned() - .left() - .contained() - .with_style(style.name.container) - .boxed(), - ) - .with_children(project.guests.iter().filter_map( - |participant| { - participant.avatar.clone().map(|avatar| { - Image::new(avatar) - .with_style(style.guest_avatar) - .aligned() - .left() - .contained() - .with_margin_right(style.guest_avatar_spacing) - .boxed() - }) - }, - )) - .contained() - .with_style(style.container) - .constrained() - .with_height(style.height) - .boxed() - }, - ) - .with_cursor_style(if is_host || is_shared { - CursorStyle::PointingHand - } else { - CursorStyle::Arrow - }) - .on_click(move |_, cx| { - if !is_host && !is_guest { - cx.dispatch_global_action(JoinProject { - project_id, - app_state: app_state.clone(), - }); - } - }) - .flex(1., true) - .boxed() - }) - .constrained() - .with_height(theme.unshared_project.height) - .boxed() - })) + Flex::row() + .with_child( + Label::new( + project.worktree_root_names.join(", "), + style.name.text.clone(), + ) + .aligned() + .left() + .contained() + .with_style(style.name.container) + .boxed(), + ) + .with_children(project.guests.iter().filter_map( + |participant| { + participant.avatar.clone().map(|avatar| { + Image::new(avatar) + .with_style(style.guest_avatar) + .aligned() + .left() + .contained() + .with_margin_right( + style.guest_avatar_spacing, + ) + .boxed() + }) + }, + )) + .contained() + .with_style(style.container) + .constrained() + .with_height(style.height) + .boxed() + }, + ) + .with_cursor_style(if is_host || is_shared { + CursorStyle::PointingHand + } else { + CursorStyle::Arrow + }) + .on_click(move |_, cx| { + if !is_host && !is_guest { + cx.dispatch_global_action(JoinProject { + project_id, + app_state: app_state.clone(), + }); + } + }) + .flex(1., true) + .boxed() + }) + .constrained() + .with_height(theme.unshared_project.height) + .boxed() + }), + ) .boxed() }