Start bringing back the current call section of the collab panel

Max Brunsfeld and Nathan created

Co-authored-by: Nathan <nathan@zed.dev>

Change summary

crates/collab_ui2/src/collab_panel.rs       | 1031 ++++++++++------------
crates/editor2/src/element.rs               |    4 
crates/gpui2/src/elements/canvas.rs         |   48 +
crates/gpui2/src/elements/mod.rs            |    2 
crates/gpui2/src/text_system.rs             |   32 
crates/gpui2/src/text_system/line.rs        |    4 
crates/ui2/src/components/list/list_item.rs |   11 
crates/workspace2/src/workspace2.rs         |  185 ++--
8 files changed, 659 insertions(+), 658 deletions(-)

Detailed changes

crates/collab_ui2/src/collab_panel.rs 🔗

@@ -18,7 +18,7 @@ mod contact_finder;
 // };
 use contact_finder::ContactFinder;
 use menu::{Cancel, Confirm, SelectNext, SelectPrev};
-use rpc::proto;
+use rpc::proto::{self, PeerId};
 use theme::{ActiveTheme, ThemeSettings};
 // use context_menu::{ContextMenu, ContextMenuItem};
 // use db::kvp::KEY_VALUE_STORE;
@@ -169,11 +169,12 @@ use editor::Editor;
 use feature_flags::{ChannelsAlpha, FeatureFlagAppExt, FeatureFlagViewExt};
 use fuzzy::{match_strings, StringMatchCandidate};
 use gpui::{
-    actions, div, img, overlay, prelude::*, px, rems, serde_json, Action, AppContext,
-    AsyncWindowContext, Bounds, ClipboardItem, DismissEvent, Div, EventEmitter, FocusHandle,
-    Focusable, FocusableView, InteractiveElement, IntoElement, Model, MouseDownEvent,
-    ParentElement, Pixels, Point, PromptLevel, Render, RenderOnce, ScrollHandle, SharedString,
-    Stateful, Styled, Subscription, Task, View, ViewContext, VisualContext, WeakView,
+    actions, canvas, div, img, overlay, point, prelude::*, px, rems, serde_json, Action,
+    AppContext, AsyncWindowContext, Bounds, ClipboardItem, DismissEvent, Div, EventEmitter,
+    FocusHandle, Focusable, FocusableView, Hsla, InteractiveElement, IntoElement, Length, Model,
+    MouseDownEvent, ParentElement, Pixels, Point, PromptLevel, Quad, Render, RenderOnce,
+    ScrollHandle, SharedString, Size, Stateful, Styled, Subscription, Task, View, ViewContext,
+    VisualContext, WeakView,
 };
 use project::{Fs, Project};
 use serde_derive::{Deserialize, Serialize};
@@ -345,21 +346,21 @@ enum Section {
 #[derive(Clone, Debug)]
 enum ListEntry {
     Header(Section),
-    //     CallParticipant {
-    //         user: Arc<User>,
-    //         peer_id: Option<PeerId>,
-    //         is_pending: bool,
-    //     },
-    //     ParticipantProject {
-    //         project_id: u64,
-    //         worktree_root_names: Vec<String>,
-    //         host_user_id: u64,
-    //         is_last: bool,
-    //     },
-    //     ParticipantScreen {
-    //         peer_id: Option<PeerId>,
-    //         is_last: bool,
-    //     },
+    CallParticipant {
+        user: Arc<User>,
+        peer_id: Option<PeerId>,
+        is_pending: bool,
+    },
+    ParticipantProject {
+        project_id: u64,
+        worktree_root_names: Vec<String>,
+        host_user_id: u64,
+        is_last: bool,
+    },
+    ParticipantScreen {
+        peer_id: Option<PeerId>,
+        is_last: bool,
+    },
     IncomingRequest(Arc<User>),
     OutgoingRequest(Arc<User>),
     //     ChannelInvite(Arc<Channel>),
@@ -368,12 +369,12 @@ enum ListEntry {
         depth: usize,
         has_children: bool,
     },
-    //     ChannelNotes {
-    //         channel_id: ChannelId,
-    //     },
-    //     ChannelChat {
-    //         channel_id: ChannelId,
-    //     },
+    ChannelNotes {
+        channel_id: ChannelId,
+    },
+    ChannelChat {
+        channel_id: ChannelId,
+    },
     ChannelEditor {
         depth: usize,
     },
@@ -706,136 +707,136 @@ impl CollabPanel {
 
         let prev_selected_entry = self.selection.and_then(|ix| self.entries.get(ix).cloned());
         let old_entries = mem::take(&mut self.entries);
-        let scroll_to_top = false;
-
-        //         if let Some(room) = ActiveCall::global(cx).read(cx).room() {
-        //             self.entries.push(ListEntry::Header(Section::ActiveCall));
-        //             if !old_entries
-        //                 .iter()
-        //                 .any(|entry| matches!(entry, ListEntry::Header(Section::ActiveCall)))
-        //             {
-        //                 scroll_to_top = true;
-        //             }
+        let mut scroll_to_top = false;
 
-        //             if !self.collapsed_sections.contains(&Section::ActiveCall) {
-        //                 let room = room.read(cx);
+        if let Some(room) = ActiveCall::global(cx).read(cx).room() {
+            self.entries.push(ListEntry::Header(Section::ActiveCall));
+            if !old_entries
+                .iter()
+                .any(|entry| matches!(entry, ListEntry::Header(Section::ActiveCall)))
+            {
+                scroll_to_top = true;
+            }
 
-        //                 if let Some(channel_id) = room.channel_id() {
-        //                     self.entries.push(ListEntry::ChannelNotes { channel_id });
-        //                     self.entries.push(ListEntry::ChannelChat { channel_id })
-        //                 }
+            if !self.collapsed_sections.contains(&Section::ActiveCall) {
+                let room = room.read(cx);
 
-        //                 // Populate the active user.
-        //                 if let Some(user) = user_store.current_user() {
-        //                     self.match_candidates.clear();
-        //                     self.match_candidates.push(StringMatchCandidate {
-        //                         id: 0,
-        //                         string: user.github_login.clone(),
-        //                         char_bag: user.github_login.chars().collect(),
-        //                     });
-        //                     let matches = executor.block(match_strings(
-        //                         &self.match_candidates,
-        //                         &query,
-        //                         true,
-        //                         usize::MAX,
-        //                         &Default::default(),
-        //                         executor.clone(),
-        //                     ));
-        //                     if !matches.is_empty() {
-        //                         let user_id = user.id;
-        //                         self.entries.push(ListEntry::CallParticipant {
-        //                             user,
-        //                             peer_id: None,
-        //                             is_pending: false,
-        //                         });
-        //                         let mut projects = room.local_participant().projects.iter().peekable();
-        //                         while let Some(project) = projects.next() {
-        //                             self.entries.push(ListEntry::ParticipantProject {
-        //                                 project_id: project.id,
-        //                                 worktree_root_names: project.worktree_root_names.clone(),
-        //                                 host_user_id: user_id,
-        //                                 is_last: projects.peek().is_none() && !room.is_screen_sharing(),
-        //                             });
-        //                         }
-        //                         if room.is_screen_sharing() {
-        //                             self.entries.push(ListEntry::ParticipantScreen {
-        //                                 peer_id: None,
-        //                                 is_last: true,
-        //                             });
-        //                         }
-        //                     }
-        //                 }
+                if let Some(channel_id) = room.channel_id() {
+                    self.entries.push(ListEntry::ChannelNotes { channel_id });
+                    self.entries.push(ListEntry::ChannelChat { channel_id })
+                }
 
-        //                 // Populate remote participants.
-        //                 self.match_candidates.clear();
-        //                 self.match_candidates
-        //                     .extend(room.remote_participants().iter().map(|(_, participant)| {
-        //                         StringMatchCandidate {
-        //                             id: participant.user.id as usize,
-        //                             string: participant.user.github_login.clone(),
-        //                             char_bag: participant.user.github_login.chars().collect(),
-        //                         }
-        //                     }));
-        //                 let matches = executor.block(match_strings(
-        //                     &self.match_candidates,
-        //                     &query,
-        //                     true,
-        //                     usize::MAX,
-        //                     &Default::default(),
-        //                     executor.clone(),
-        //                 ));
-        //                 for mat in matches {
-        //                     let user_id = mat.candidate_id as u64;
-        //                     let participant = &room.remote_participants()[&user_id];
-        //                     self.entries.push(ListEntry::CallParticipant {
-        //                         user: participant.user.clone(),
-        //                         peer_id: Some(participant.peer_id),
-        //                         is_pending: false,
-        //                     });
-        //                     let mut projects = participant.projects.iter().peekable();
-        //                     while let Some(project) = projects.next() {
-        //                         self.entries.push(ListEntry::ParticipantProject {
-        //                             project_id: project.id,
-        //                             worktree_root_names: project.worktree_root_names.clone(),
-        //                             host_user_id: participant.user.id,
-        //                             is_last: projects.peek().is_none()
-        //                                 && participant.video_tracks.is_empty(),
-        //                         });
-        //                     }
-        //                     if !participant.video_tracks.is_empty() {
-        //                         self.entries.push(ListEntry::ParticipantScreen {
-        //                             peer_id: Some(participant.peer_id),
-        //                             is_last: true,
-        //                         });
-        //                     }
-        //                 }
+                // Populate the active user.
+                if let Some(user) = user_store.current_user() {
+                    self.match_candidates.clear();
+                    self.match_candidates.push(StringMatchCandidate {
+                        id: 0,
+                        string: user.github_login.clone(),
+                        char_bag: user.github_login.chars().collect(),
+                    });
+                    let matches = executor.block(match_strings(
+                        &self.match_candidates,
+                        &query,
+                        true,
+                        usize::MAX,
+                        &Default::default(),
+                        executor.clone(),
+                    ));
+                    if !matches.is_empty() {
+                        let user_id = user.id;
+                        self.entries.push(ListEntry::CallParticipant {
+                            user,
+                            peer_id: None,
+                            is_pending: false,
+                        });
+                        let mut projects = room.local_participant().projects.iter().peekable();
+                        while let Some(project) = projects.next() {
+                            self.entries.push(ListEntry::ParticipantProject {
+                                project_id: project.id,
+                                worktree_root_names: project.worktree_root_names.clone(),
+                                host_user_id: user_id,
+                                is_last: projects.peek().is_none() && !room.is_screen_sharing(),
+                            });
+                        }
+                        if room.is_screen_sharing() {
+                            self.entries.push(ListEntry::ParticipantScreen {
+                                peer_id: None,
+                                is_last: true,
+                            });
+                        }
+                    }
+                }
 
-        //                 // Populate pending participants.
-        //                 self.match_candidates.clear();
-        //                 self.match_candidates
-        //                     .extend(room.pending_participants().iter().enumerate().map(
-        //                         |(id, participant)| StringMatchCandidate {
-        //                             id,
-        //                             string: participant.github_login.clone(),
-        //                             char_bag: participant.github_login.chars().collect(),
-        //                         },
-        //                     ));
-        //                 let matches = executor.block(match_strings(
-        //                     &self.match_candidates,
-        //                     &query,
-        //                     true,
-        //                     usize::MAX,
-        //                     &Default::default(),
-        //                     executor.clone(),
-        //                 ));
-        //                 self.entries
-        //                     .extend(matches.iter().map(|mat| ListEntry::CallParticipant {
-        //                         user: room.pending_participants()[mat.candidate_id].clone(),
-        //                         peer_id: None,
-        //                         is_pending: true,
-        //                     }));
-        //             }
-        //         }
+                // Populate remote participants.
+                self.match_candidates.clear();
+                self.match_candidates
+                    .extend(room.remote_participants().iter().map(|(_, participant)| {
+                        StringMatchCandidate {
+                            id: participant.user.id as usize,
+                            string: participant.user.github_login.clone(),
+                            char_bag: participant.user.github_login.chars().collect(),
+                        }
+                    }));
+                let matches = executor.block(match_strings(
+                    &self.match_candidates,
+                    &query,
+                    true,
+                    usize::MAX,
+                    &Default::default(),
+                    executor.clone(),
+                ));
+                for mat in matches {
+                    let user_id = mat.candidate_id as u64;
+                    let participant = &room.remote_participants()[&user_id];
+                    self.entries.push(ListEntry::CallParticipant {
+                        user: participant.user.clone(),
+                        peer_id: Some(participant.peer_id),
+                        is_pending: false,
+                    });
+                    let mut projects = participant.projects.iter().peekable();
+                    while let Some(project) = projects.next() {
+                        self.entries.push(ListEntry::ParticipantProject {
+                            project_id: project.id,
+                            worktree_root_names: project.worktree_root_names.clone(),
+                            host_user_id: participant.user.id,
+                            is_last: projects.peek().is_none()
+                                && participant.video_tracks.is_empty(),
+                        });
+                    }
+                    if !participant.video_tracks.is_empty() {
+                        self.entries.push(ListEntry::ParticipantScreen {
+                            peer_id: Some(participant.peer_id),
+                            is_last: true,
+                        });
+                    }
+                }
+
+                // Populate pending participants.
+                self.match_candidates.clear();
+                self.match_candidates
+                    .extend(room.pending_participants().iter().enumerate().map(
+                        |(id, participant)| StringMatchCandidate {
+                            id,
+                            string: participant.github_login.clone(),
+                            char_bag: participant.github_login.chars().collect(),
+                        },
+                    ));
+                let matches = executor.block(match_strings(
+                    &self.match_candidates,
+                    &query,
+                    true,
+                    usize::MAX,
+                    &Default::default(),
+                    executor.clone(),
+                ));
+                self.entries
+                    .extend(matches.iter().map(|mat| ListEntry::CallParticipant {
+                        user: room.pending_participants()[mat.candidate_id].clone(),
+                        peer_id: None,
+                        is_pending: true,
+                    }));
+            }
+        }
 
         let mut request_entries = Vec::new();
 
@@ -1133,290 +1134,235 @@ impl CollabPanel {
         cx.notify();
     }
 
-    //     fn render_call_participant(
-    //         user: &User,
-    //         peer_id: Option<PeerId>,
-    //         user_store: ModelHandle<UserStore>,
-    //         is_pending: bool,
-    //         is_selected: bool,
-    //         theme: &theme::Theme,
-    //         cx: &mut ViewContext<Self>,
-    //     ) -> AnyElement<Self> {
-    //         enum CallParticipant {}
-    //         enum CallParticipantTooltip {}
-    //         enum LeaveCallButton {}
-    //         enum LeaveCallTooltip {}
-
-    //         let collab_theme = &theme.collab_panel;
-
-    //         let is_current_user =
-    //             user_store.read(cx).current_user().map(|user| user.id) == Some(user.id);
-
-    //         let content = MouseEventHandler::new::<CallParticipant, _>(
-    //             user.id as usize,
-    //             cx,
-    //             |mouse_state, cx| {
-    //                 let style = if is_current_user {
-    //                     *collab_theme
-    //                         .contact_row
-    //                         .in_state(is_selected)
-    //                         .style_for(&mut Default::default())
-    //                 } else {
-    //                     *collab_theme
-    //                         .contact_row
-    //                         .in_state(is_selected)
-    //                         .style_for(mouse_state)
-    //                 };
-
-    //                 Flex::row()
-    //                     .with_children(user.avatar.clone().map(|avatar| {
-    //                         Image::from_data(avatar)
-    //                             .with_style(collab_theme.contact_avatar)
-    //                             .aligned()
-    //                             .left()
-    //                     }))
-    //                     .with_child(
-    //                         Label::new(
-    //                             user.github_login.clone(),
-    //                             collab_theme.contact_username.text.clone(),
-    //                         )
-    //                         .contained()
-    //                         .with_style(collab_theme.contact_username.container)
-    //                         .aligned()
-    //                         .left()
-    //                         .flex(1., true),
-    //                     )
-    //                     .with_children(if is_pending {
-    //                         Some(
-    //                             Label::new("Calling", collab_theme.calling_indicator.text.clone())
-    //                                 .contained()
-    //                                 .with_style(collab_theme.calling_indicator.container)
-    //                                 .aligned()
-    //                                 .into_any(),
-    //                         )
-    //                     } else if is_current_user {
-    //                         Some(
-    //                             MouseEventHandler::new::<LeaveCallButton, _>(0, cx, |state, _| {
-    //                                 render_icon_button(
-    //                                     theme
-    //                                         .collab_panel
-    //                                         .leave_call_button
-    //                                         .style_for(is_selected, state),
-    //                                     "icons/exit.svg",
-    //                                 )
-    //                             })
-    //                             .with_cursor_style(CursorStyle::PointingHand)
-    //                             .on_click(MouseButton::Left, |_, _, cx| {
-    //                                 Self::leave_call(cx);
-    //                             })
-    //                             .with_tooltip::<LeaveCallTooltip>(
-    //                                 0,
-    //                                 "Leave call",
-    //                                 None,
-    //                                 theme.tooltip.clone(),
-    //                                 cx,
-    //                             )
-    //                             .into_any(),
-    //                         )
-    //                     } else {
-    //                         None
-    //                     })
-    //                     .constrained()
-    //                     .with_height(collab_theme.row_height)
-    //                     .contained()
-    //                     .with_style(style)
-    //             },
-    //         );
-
-    //         if is_current_user || is_pending || peer_id.is_none() {
-    //             return content.into_any();
-    //         }
-
-    //         let tooltip = format!("Follow {}", user.github_login);
-
-    //         content
-    //             .on_click(MouseButton::Left, move |_, this, cx| {
-    //                 if let Some(workspace) = this.workspace.upgrade(cx) {
-    //                     workspace
-    //                         .update(cx, |workspace, cx| workspace.follow(peer_id.unwrap(), cx))
-    //                         .map(|task| task.detach_and_log_err(cx));
-    //                 }
-    //             })
-    //             .with_cursor_style(CursorStyle::PointingHand)
-    //             .with_tooltip::<CallParticipantTooltip>(
-    //                 user.id as usize,
-    //                 tooltip,
-    //                 Some(Box::new(FollowNextCollaborator)),
-    //                 theme.tooltip.clone(),
-    //                 cx,
-    //             )
-    //             .into_any()
-    //     }
+    fn render_call_participant(
+        &self,
+        user: Arc<User>,
+        peer_id: Option<PeerId>,
+        is_pending: bool,
+        cx: &mut ViewContext<Self>,
+    ) -> impl IntoElement {
+        let is_current_user =
+            self.user_store.read(cx).current_user().map(|user| user.id) == Some(user.id);
+        let tooltip = format!("Follow {}", user.github_login);
 
-    //     fn render_participant_project(
-    //         project_id: u64,
-    //         worktree_root_names: &[String],
-    //         host_user_id: u64,
-    //         is_current: bool,
-    //         is_last: bool,
-    //         is_selected: bool,
-    //         theme: &theme::Theme,
-    //         cx: &mut ViewContext<Self>,
-    //     ) -> AnyElement<Self> {
-    //         enum JoinProject {}
-    //         enum JoinProjectTooltip {}
-
-    //         let collab_theme = &theme.collab_panel;
-    //         let host_avatar_width = collab_theme
-    //             .contact_avatar
-    //             .width
-    //             .or(collab_theme.contact_avatar.height)
-    //             .unwrap_or(0.);
-    //         let tree_branch = collab_theme.tree_branch;
-    //         let project_name = if worktree_root_names.is_empty() {
-    //             "untitled".to_string()
-    //         } else {
-    //             worktree_root_names.join(", ")
-    //         };
-
-    //         let content =
-    //             MouseEventHandler::new::<JoinProject, _>(project_id as usize, cx, |mouse_state, cx| {
-    //                 let tree_branch = *tree_branch.in_state(is_selected).style_for(mouse_state);
-    //                 let row = if is_current {
-    //                     collab_theme
-    //                         .project_row
-    //                         .in_state(true)
-    //                         .style_for(&mut Default::default())
-    //                 } else {
-    //                     collab_theme
-    //                         .project_row
-    //                         .in_state(is_selected)
-    //                         .style_for(mouse_state)
-    //                 };
-
-    //                 Flex::row()
-    //                     .with_child(render_tree_branch(
-    //                         tree_branch,
-    //                         &row.name.text,
-    //                         is_last,
-    //                         vec2f(host_avatar_width, collab_theme.row_height),
-    //                         cx.font_cache(),
-    //                     ))
-    //                     .with_child(
-    //                         Svg::new("icons/file_icons/folder.svg")
-    //                             .with_color(collab_theme.channel_hash.color)
-    //                             .constrained()
-    //                             .with_width(collab_theme.channel_hash.width)
-    //                             .aligned()
-    //                             .left(),
-    //                     )
-    //                     .with_child(
-    //                         Label::new(project_name.clone(), row.name.text.clone())
-    //                             .aligned()
-    //                             .left()
-    //                             .contained()
-    //                             .with_style(row.name.container)
-    //                             .flex(1., false),
-    //                     )
-    //                     .constrained()
-    //                     .with_height(collab_theme.row_height)
-    //                     .contained()
-    //                     .with_style(row.container)
-    //             });
-
-    //         if is_current {
-    //             return content.into_any();
-    //         }
-
-    //         content
-    //             .with_cursor_style(CursorStyle::PointingHand)
-    //             .on_click(MouseButton::Left, move |_, this, cx| {
-    //                 if let Some(workspace) = this.workspace.upgrade(cx) {
-    //                     let app_state = workspace.read(cx).app_state().clone();
-    //                     workspace::join_remote_project(project_id, host_user_id, app_state, cx)
-    //                         .detach_and_log_err(cx);
-    //                 }
-    //             })
-    //             .with_tooltip::<JoinProjectTooltip>(
-    //                 project_id as usize,
-    //                 format!("Open {}", project_name),
-    //                 None,
-    //                 theme.tooltip.clone(),
-    //                 cx,
-    //             )
-    //             .into_any()
-    //     }
+        ListItem::new(SharedString::from(user.github_login.clone()))
+            .left_child(Avatar::data(user.avatar.clone().unwrap()))
+            .child(
+                h_stack()
+                    .w_full()
+                    .justify_between()
+                    .child(Label::new(user.github_login.clone()))
+                    .child(if is_pending {
+                        Label::new("Calling").color(Color::Muted).into_any_element()
+                    } else if is_current_user {
+                        IconButton::new("leave-call", Icon::ArrowRight)
+                            .on_click(cx.listener(move |this, _, cx| {
+                                Self::leave_call(cx);
+                            }))
+                            .tooltip(|cx| Tooltip::text("Leave Call", cx))
+                            .into_any_element()
+                    } else {
+                        div().into_any_element()
+                    }),
+            )
+            .when(!is_current_user, |this| {
+                this.tooltip(move |cx| Tooltip::text(tooltip.clone(), cx))
+                    .on_click(cx.listener(move |this, _, cx| {
+                        this.workspace.update(cx, |workspace, cx| {
+                            // workspace.follow(peer_id, cx)
+                        });
+                    }))
+            })
+    }
 
-    //     fn render_participant_screen(
-    //         peer_id: Option<PeerId>,
-    //         is_last: bool,
-    //         is_selected: bool,
-    //         theme: &theme::CollabPanel,
-    //         cx: &mut ViewContext<Self>,
-    //     ) -> AnyElement<Self> {
-    //         enum OpenSharedScreen {}
-
-    //         let host_avatar_width = theme
-    //             .contact_avatar
-    //             .width
-    //             .or(theme.contact_avatar.height)
-    //             .unwrap_or(0.);
-    //         let tree_branch = theme.tree_branch;
-
-    //         let handler = MouseEventHandler::new::<OpenSharedScreen, _>(
-    //             peer_id.map(|id| id.as_u64()).unwrap_or(0) as usize,
-    //             cx,
-    //             |mouse_state, cx| {
-    //                 let tree_branch = *tree_branch.in_state(is_selected).style_for(mouse_state);
-    //                 let row = theme
-    //                     .project_row
-    //                     .in_state(is_selected)
-    //                     .style_for(mouse_state);
-
-    //                 Flex::row()
-    //                     .with_child(render_tree_branch(
-    //                         tree_branch,
-    //                         &row.name.text,
-    //                         is_last,
-    //                         vec2f(host_avatar_width, theme.row_height),
-    //                         cx.font_cache(),
-    //                     ))
-    //                     .with_child(
-    //                         Svg::new("icons/desktop.svg")
-    //                             .with_color(theme.channel_hash.color)
-    //                             .constrained()
-    //                             .with_width(theme.channel_hash.width)
-    //                             .aligned()
-    //                             .left(),
-    //                     )
-    //                     .with_child(
-    //                         Label::new("Screen", row.name.text.clone())
-    //                             .aligned()
-    //                             .left()
-    //                             .contained()
-    //                             .with_style(row.name.container)
-    //                             .flex(1., false),
-    //                     )
-    //                     .constrained()
-    //                     .with_height(theme.row_height)
-    //                     .contained()
-    //                     .with_style(row.container)
-    //             },
-    //         );
-    //         if peer_id.is_none() {
-    //             return handler.into_any();
-    //         }
-    //         handler
-    //             .with_cursor_style(CursorStyle::PointingHand)
-    //             .on_click(MouseButton::Left, move |_, this, cx| {
-    //                 if let Some(workspace) = this.workspace.upgrade(cx) {
-    //                     workspace.update(cx, |workspace, cx| {
-    //                         workspace.open_shared_screen(peer_id.unwrap(), cx)
-    //                     });
-    //                 }
-    //             })
-    //             .into_any()
-    //     }
+    fn render_participant_project(
+        &self,
+        project_id: u64,
+        worktree_root_names: &[String],
+        host_user_id: u64,
+        //         is_current: bool,
+        is_last: bool,
+        //         is_selected: bool,
+        //         theme: &theme::Theme,
+        cx: &mut ViewContext<Self>,
+    ) -> impl IntoElement {
+        let project_name: SharedString = if worktree_root_names.is_empty() {
+            "untitled".to_string()
+        } else {
+            worktree_root_names.join(", ")
+        }
+        .into();
+
+        let theme = cx.theme();
+
+        ListItem::new(project_id as usize)
+            .on_click(cx.listener(move |this, _, cx| {
+                this.workspace.update(cx, |workspace, cx| {
+                    let app_state = workspace.app_state().clone();
+                    workspace::join_remote_project(project_id, host_user_id, app_state, cx)
+                        .detach_and_log_err(cx);
+                });
+            }))
+            .left_child(IconButton::new(0, Icon::Folder))
+            .child(
+                h_stack()
+                    .w_full()
+                    .justify_between()
+                    .child(render_tree_branch(is_last, cx))
+                    .child(Label::new(project_name.clone())),
+            )
+            .tooltip(move |cx| Tooltip::text(format!("Open {}", project_name), cx))
+
+        //         enum JoinProject {}
+        //         enum JoinProjectTooltip {}
+
+        //         let collab_theme = &theme.collab_panel;
+        //         let host_avatar_width = collab_theme
+        //             .contact_avatar
+        //             .width
+        //             .or(collab_theme.contact_avatar.height)
+        //             .unwrap_or(0.);
+        //         let tree_branch = collab_theme.tree_branch;
+
+        //         let content =
+        //             MouseEventHandler::new::<JoinProject, _>(project_id as usize, cx, |mouse_state, cx| {
+        //                 let tree_branch = *tree_branch.in_state(is_selected).style_for(mouse_state);
+        //                 let row = if is_current {
+        //                     collab_theme
+        //                         .project_row
+        //                         .in_state(true)
+        //                         .style_for(&mut Default::default())
+        //                 } else {
+        //                     collab_theme
+        //                         .project_row
+        //                         .in_state(is_selected)
+        //                         .style_for(mouse_state)
+        //                 };
+
+        //                 Flex::row()
+        //                     .with_child(render_tree_branch(
+        //                         tree_branch,
+        //                         &row.name.text,
+        //                         is_last,
+        //                         vec2f(host_avatar_width, collab_theme.row_height),
+        //                         cx.font_cache(),
+        //                     ))
+        //                     .with_child(
+        //                         Svg::new("icons/file_icons/folder.svg")
+        //                             .with_color(collab_theme.channel_hash.color)
+        //                             .constrained()
+        //                             .with_width(collab_theme.channel_hash.width)
+        //                             .aligned()
+        //                             .left(),
+        //                     )
+        //                     .with_child(
+        //                         Label::new(project_name.clone(), row.name.text.clone())
+        //                             .aligned()
+        //                             .left()
+        //                             .contained()
+        //                             .with_style(row.name.container)
+        //                             .flex(1., false),
+        //                     )
+        //                     .constrained()
+        //                     .with_height(collab_theme.row_height)
+        //                     .contained()
+        //                     .with_style(row.container)
+        //             });
+
+        //         if is_current {
+        //             return content.into_any();
+        //         }
+
+        //         content
+        //             .with_cursor_style(CursorStyle::PointingHand)
+        //             .on_click(MouseButton::Left, move |_, this, cx| {
+        //                 if let Some(workspace) = this.workspace.upgrade(cx) {
+        //                     let app_state = workspace.read(cx).app_state().clone();
+        //                     workspace::join_remote_project(project_id, host_user_id, app_state, cx)
+        //                         .detach_and_log_err(cx);
+        //                 }
+        //             })
+        //             .with_tooltip::<JoinProjectTooltip>(
+        //                 project_id as usize,
+        //                 format!("Open {}", project_name),
+        //                 None,
+        //                 theme.tooltip.clone(),
+        //                 cx,
+        //             )
+        //             .into_any()
+    }
+
+    fn render_participant_screen(
+        &self,
+        peer_id: Option<PeerId>,
+        is_last: bool,
+        cx: &mut ViewContext<Self>,
+    ) -> impl IntoElement {
+        //         enum OpenSharedScreen {}
+
+        //         let host_avatar_width = theme
+        //             .contact_avatar
+        //             .width
+        //             .or(theme.contact_avatar.height)
+        //             .unwrap_or(0.);
+        //         let tree_branch = theme.tree_branch;
+
+        //         let handler = MouseEventHandler::new::<OpenSharedScreen, _>(
+        //             peer_id.map(|id| id.as_u64()).unwrap_or(0) as usize,
+        //             cx,
+        //             |mouse_state, cx| {
+        //                 let tree_branch = *tree_branch.in_state(is_selected).style_for(mouse_state);
+        //                 let row = theme
+        //                     .project_row
+        //                     .in_state(is_selected)
+        //                     .style_for(mouse_state);
+
+        //                 Flex::row()
+        //                     .with_child(render_tree_branch(
+        //                         tree_branch,
+        //                         &row.name.text,
+        //                         is_last,
+        //                         vec2f(host_avatar_width, theme.row_height),
+        //                         cx.font_cache(),
+        //                     ))
+        //                     .with_child(
+        //                         Svg::new("icons/desktop.svg")
+        //                             .with_color(theme.channel_hash.color)
+        //                             .constrained()
+        //                             .with_width(theme.channel_hash.width)
+        //                             .aligned()
+        //                             .left(),
+        //                     )
+        //                     .with_child(
+        //                         Label::new("Screen", row.name.text.clone())
+        //                             .aligned()
+        //                             .left()
+        //                             .contained()
+        //                             .with_style(row.name.container)
+        //                             .flex(1., false),
+        //                     )
+        //                     .constrained()
+        //                     .with_height(theme.row_height)
+        //                     .contained()
+        //                     .with_style(row.container)
+        //             },
+        //         );
+        //         if peer_id.is_none() {
+        //             return handler.into_any();
+        //         }
+        //         handler
+        //             .with_cursor_style(CursorStyle::PointingHand)
+        //             .on_click(MouseButton::Left, move |_, this, cx| {
+        //                 if let Some(workspace) = this.workspace.upgrade(cx) {
+        //                     workspace.update(cx, |workspace, cx| {
+        //                         workspace.open_shared_screen(peer_id.unwrap(), cx)
+        //                     });
+        //                 }
+        //             })
+        //             .into_any()
+
+        div()
+    }
 
     fn take_editing_state(&mut self, cx: &mut ViewContext<Self>) -> bool {
         if let Some(_) = self.channel_editing_state.take() {
@@ -1463,117 +1409,114 @@ impl CollabPanel {
     //         .into_any()
     //     }
 
-    //     fn render_channel_notes(
-    //         &self,
-    //         channel_id: ChannelId,
-    //         theme: &theme::CollabPanel,
-    //         is_selected: bool,
-    //         ix: usize,
-    //         cx: &mut ViewContext<Self>,
-    //     ) -> AnyElement<Self> {
-    //         enum ChannelNotes {}
-    //         let host_avatar_width = theme
-    //             .contact_avatar
-    //             .width
-    //             .or(theme.contact_avatar.height)
-    //             .unwrap_or(0.);
-
-    //         MouseEventHandler::new::<ChannelNotes, _>(ix as usize, cx, |state, cx| {
-    //             let tree_branch = *theme.tree_branch.in_state(is_selected).style_for(state);
-    //             let row = theme.project_row.in_state(is_selected).style_for(state);
-
-    //             Flex::<Self>::row()
-    //                 .with_child(render_tree_branch(
-    //                     tree_branch,
-    //                     &row.name.text,
-    //                     false,
-    //                     vec2f(host_avatar_width, theme.row_height),
-    //                     cx.font_cache(),
-    //                 ))
-    //                 .with_child(
-    //                     Svg::new("icons/file.svg")
-    //                         .with_color(theme.channel_hash.color)
-    //                         .constrained()
-    //                         .with_width(theme.channel_hash.width)
-    //                         .aligned()
-    //                         .left(),
-    //                 )
-    //                 .with_child(
-    //                     Label::new("notes", theme.channel_name.text.clone())
-    //                         .contained()
-    //                         .with_style(theme.channel_name.container)
-    //                         .aligned()
-    //                         .left()
-    //                         .flex(1., true),
-    //                 )
-    //                 .constrained()
-    //                 .with_height(theme.row_height)
-    //                 .contained()
-    //                 .with_style(*theme.channel_row.style_for(is_selected, state))
-    //                 .with_padding_left(theme.channel_row.default_style().padding.left)
-    //         })
-    //         .on_click(MouseButton::Left, move |_, this, cx| {
-    //             this.open_channel_notes(&OpenChannelNotes { channel_id }, cx);
-    //         })
-    //         .with_cursor_style(CursorStyle::PointingHand)
-    //         .into_any()
-    //     }
+    fn render_channel_notes(
+        &self,
+        channel_id: ChannelId,
+        cx: &mut ViewContext<Self>,
+    ) -> impl IntoElement {
+        //         enum ChannelNotes {}
+        //         let host_avatar_width = theme
+        //             .contact_avatar
+        //             .width
+        //             .or(theme.contact_avatar.height)
+        //             .unwrap_or(0.);
+
+        //         MouseEventHandler::new::<ChannelNotes, _>(ix as usize, cx, |state, cx| {
+        //             let tree_branch = *theme.tree_branch.in_state(is_selected).style_for(state);
+        //             let row = theme.project_row.in_state(is_selected).style_for(state);
+
+        //             Flex::<Self>::row()
+        //                 .with_child(render_tree_branch(
+        //                     tree_branch,
+        //                     &row.name.text,
+        //                     false,
+        //                     vec2f(host_avatar_width, theme.row_height),
+        //                     cx.font_cache(),
+        //                 ))
+        //                 .with_child(
+        //                     Svg::new("icons/file.svg")
+        //                         .with_color(theme.channel_hash.color)
+        //                         .constrained()
+        //                         .with_width(theme.channel_hash.width)
+        //                         .aligned()
+        //                         .left(),
+        //                 )
+        //                 .with_child(
+        //                     Label::new("notes", theme.channel_name.text.clone())
+        //                         .contained()
+        //                         .with_style(theme.channel_name.container)
+        //                         .aligned()
+        //                         .left()
+        //                         .flex(1., true),
+        //                 )
+        //                 .constrained()
+        //                 .with_height(theme.row_height)
+        //                 .contained()
+        //                 .with_style(*theme.channel_row.style_for(is_selected, state))
+        //                 .with_padding_left(theme.channel_row.default_style().padding.left)
+        //         })
+        //         .on_click(MouseButton::Left, move |_, this, cx| {
+        //             this.open_channel_notes(&OpenChannelNotes { channel_id }, cx);
+        //         })
+        //         .with_cursor_style(CursorStyle::PointingHand)
+        //         .into_any()
 
-    //     fn render_channel_chat(
-    //         &self,
-    //         channel_id: ChannelId,
-    //         theme: &theme::CollabPanel,
-    //         is_selected: bool,
-    //         ix: usize,
-    //         cx: &mut ViewContext<Self>,
-    //     ) -> AnyElement<Self> {
-    //         enum ChannelChat {}
-    //         let host_avatar_width = theme
-    //             .contact_avatar
-    //             .width
-    //             .or(theme.contact_avatar.height)
-    //             .unwrap_or(0.);
-
-    //         MouseEventHandler::new::<ChannelChat, _>(ix as usize, cx, |state, cx| {
-    //             let tree_branch = *theme.tree_branch.in_state(is_selected).style_for(state);
-    //             let row = theme.project_row.in_state(is_selected).style_for(state);
-
-    //             Flex::<Self>::row()
-    //                 .with_child(render_tree_branch(
-    //                     tree_branch,
-    //                     &row.name.text,
-    //                     true,
-    //                     vec2f(host_avatar_width, theme.row_height),
-    //                     cx.font_cache(),
-    //                 ))
-    //                 .with_child(
-    //                     Svg::new("icons/conversations.svg")
-    //                         .with_color(theme.channel_hash.color)
-    //                         .constrained()
-    //                         .with_width(theme.channel_hash.width)
-    //                         .aligned()
-    //                         .left(),
-    //                 )
-    //                 .with_child(
-    //                     Label::new("chat", theme.channel_name.text.clone())
-    //                         .contained()
-    //                         .with_style(theme.channel_name.container)
-    //                         .aligned()
-    //                         .left()
-    //                         .flex(1., true),
-    //                 )
-    //                 .constrained()
-    //                 .with_height(theme.row_height)
-    //                 .contained()
-    //                 .with_style(*theme.channel_row.style_for(is_selected, state))
-    //                 .with_padding_left(theme.channel_row.default_style().padding.left)
-    //         })
-    //         .on_click(MouseButton::Left, move |_, this, cx| {
-    //             this.join_channel_chat(&JoinChannelChat { channel_id }, cx);
-    //         })
-    //         .with_cursor_style(CursorStyle::PointingHand)
-    //         .into_any()
-    //     }
+        div()
+    }
+
+    fn render_channel_chat(
+        &self,
+        channel_id: ChannelId,
+        cx: &mut ViewContext<Self>,
+    ) -> impl IntoElement {
+        //         enum ChannelChat {}
+        //         let host_avatar_width = theme
+        //             .contact_avatar
+        //             .width
+        //             .or(theme.contact_avatar.height)
+        //             .unwrap_or(0.);
+
+        //         MouseEventHandler::new::<ChannelChat, _>(ix as usize, cx, |state, cx| {
+        //             let tree_branch = *theme.tree_branch.in_state(is_selected).style_for(state);
+        //             let row = theme.project_row.in_state(is_selected).style_for(state);
+
+        //             Flex::<Self>::row()
+        //                 .with_child(render_tree_branch(
+        //                     tree_branch,
+        //                     &row.name.text,
+        //                     true,
+        //                     vec2f(host_avatar_width, theme.row_height),
+        //                     cx.font_cache(),
+        //                 ))
+        //                 .with_child(
+        //                     Svg::new("icons/conversations.svg")
+        //                         .with_color(theme.channel_hash.color)
+        //                         .constrained()
+        //                         .with_width(theme.channel_hash.width)
+        //                         .aligned()
+        //                         .left(),
+        //                 )
+        //                 .with_child(
+        //                     Label::new("chat", theme.channel_name.text.clone())
+        //                         .contained()
+        //                         .with_style(theme.channel_name.container)
+        //                         .aligned()
+        //                         .left()
+        //                         .flex(1., true),
+        //                 )
+        //                 .constrained()
+        //                 .with_height(theme.row_height)
+        //                 .contained()
+        //                 .with_style(*theme.channel_row.style_for(is_selected, state))
+        //                 .with_padding_left(theme.channel_row.default_style().padding.left)
+        //         })
+        //         .on_click(MouseButton::Left, move |_, this, cx| {
+        //             this.join_channel_chat(&JoinChannelChat { channel_id }, cx);
+        //         })
+        //         .with_cursor_style(CursorStyle::PointingHand)
+        //         .into_any()
+        div()
+    }
 
     //     fn render_channel_invite(
     //         channel: Arc<Channel>,

crates/editor2/src/element.rs 🔗

@@ -1753,7 +1753,7 @@ impl EditorElement {
             let gutter_width;
             let gutter_margin;
             if snapshot.show_gutter {
-                let descent = cx.text_system().descent(font_id, font_size).unwrap();
+                let descent = cx.text_system().descent(font_id, font_size);
 
                 let gutter_padding_factor = 3.5;
                 gutter_padding = (em_width * gutter_padding_factor).round();
@@ -3628,7 +3628,7 @@ fn compute_auto_height_layout(
     let gutter_width;
     let gutter_margin;
     if snapshot.show_gutter {
-        let descent = cx.text_system().descent(font_id, font_size).unwrap();
+        let descent = cx.text_system().descent(font_id, font_size);
         let gutter_padding_factor = 3.5;
         gutter_padding = (em_width * gutter_padding_factor).round();
         gutter_width = max_line_number_width + gutter_padding * 2.0;

crates/gpui2/src/elements/canvas.rs 🔗

@@ -0,0 +1,48 @@
+use crate::{Bounds, Element, IntoElement, Pixels, StyleRefinement, Styled, WindowContext};
+
+pub fn canvas(callback: impl 'static + FnOnce(Bounds<Pixels>, &mut WindowContext)) -> Canvas {
+    Canvas {
+        paint_callback: Box::new(callback),
+        style: Default::default(),
+    }
+}
+
+pub struct Canvas {
+    paint_callback: Box<dyn FnOnce(Bounds<Pixels>, &mut WindowContext)>,
+    style: StyleRefinement,
+}
+
+impl IntoElement for Canvas {
+    type Element = Self;
+
+    fn element_id(&self) -> Option<crate::ElementId> {
+        None
+    }
+
+    fn into_element(self) -> Self::Element {
+        self
+    }
+}
+
+impl Element for Canvas {
+    type State = ();
+
+    fn layout(
+        &mut self,
+        _: Option<Self::State>,
+        cx: &mut WindowContext,
+    ) -> (crate::LayoutId, Self::State) {
+        let layout_id = cx.request_layout(&self.style.clone().into(), []);
+        (layout_id, ())
+    }
+
+    fn paint(self, bounds: Bounds<Pixels>, _: &mut (), cx: &mut WindowContext) {
+        (self.paint_callback)(bounds, cx)
+    }
+}
+
+impl Styled for Canvas {
+    fn style(&mut self) -> &mut crate::StyleRefinement {
+        &mut self.style
+    }
+}

crates/gpui2/src/elements/mod.rs 🔗

@@ -1,3 +1,4 @@
+mod canvas;
 mod div;
 mod img;
 mod overlay;
@@ -5,6 +6,7 @@ mod svg;
 mod text;
 mod uniform_list;
 
+pub use canvas::*;
 pub use div::*;
 pub use img::*;
 pub use overlay::*;

crates/gpui2/src/text_system.rs 🔗

@@ -72,7 +72,7 @@ impl TextSystem {
         }
     }
 
-    pub fn bounding_box(&self, font_id: FontId, font_size: Pixels) -> Result<Bounds<Pixels>> {
+    pub fn bounding_box(&self, font_id: FontId, font_size: Pixels) -> Bounds<Pixels> {
         self.read_metrics(font_id, |metrics| metrics.bounding_box(font_size))
     }
 
@@ -89,9 +89,9 @@ impl TextSystem {
         let bounds = self
             .platform_text_system
             .typographic_bounds(font_id, glyph_id)?;
-        self.read_metrics(font_id, |metrics| {
+        Ok(self.read_metrics(font_id, |metrics| {
             (bounds / metrics.units_per_em as f32 * font_size.0).map(px)
-        })
+        }))
     }
 
     pub fn advance(&self, font_id: FontId, font_size: Pixels, ch: char) -> Result<Size<Pixels>> {
@@ -100,28 +100,28 @@ impl TextSystem {
             .glyph_for_char(font_id, ch)
             .ok_or_else(|| anyhow!("glyph not found for character '{}'", ch))?;
         let result = self.platform_text_system.advance(font_id, glyph_id)?
-            / self.units_per_em(font_id)? as f32;
+            / self.units_per_em(font_id) as f32;
 
         Ok(result * font_size)
     }
 
-    pub fn units_per_em(&self, font_id: FontId) -> Result<u32> {
+    pub fn units_per_em(&self, font_id: FontId) -> u32 {
         self.read_metrics(font_id, |metrics| metrics.units_per_em as u32)
     }
 
-    pub fn cap_height(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {
+    pub fn cap_height(&self, font_id: FontId, font_size: Pixels) -> Pixels {
         self.read_metrics(font_id, |metrics| metrics.cap_height(font_size))
     }
 
-    pub fn x_height(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {
+    pub fn x_height(&self, font_id: FontId, font_size: Pixels) -> Pixels {
         self.read_metrics(font_id, |metrics| metrics.x_height(font_size))
     }
 
-    pub fn ascent(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {
+    pub fn ascent(&self, font_id: FontId, font_size: Pixels) -> Pixels {
         self.read_metrics(font_id, |metrics| metrics.ascent(font_size))
     }
 
-    pub fn descent(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {
+    pub fn descent(&self, font_id: FontId, font_size: Pixels) -> Pixels {
         self.read_metrics(font_id, |metrics| metrics.descent(font_size))
     }
 
@@ -130,24 +130,24 @@ impl TextSystem {
         font_id: FontId,
         font_size: Pixels,
         line_height: Pixels,
-    ) -> Result<Pixels> {
-        let ascent = self.ascent(font_id, font_size)?;
-        let descent = self.descent(font_id, font_size)?;
+    ) -> Pixels {
+        let ascent = self.ascent(font_id, font_size);
+        let descent = self.descent(font_id, font_size);
         let padding_top = (line_height - ascent - descent) / 2.;
-        Ok(padding_top + ascent)
+        padding_top + ascent
     }
 
-    fn read_metrics<T>(&self, font_id: FontId, read: impl FnOnce(&FontMetrics) -> T) -> Result<T> {
+    fn read_metrics<T>(&self, font_id: FontId, read: impl FnOnce(&FontMetrics) -> T) -> T {
         let lock = self.font_metrics.upgradable_read();
 
         if let Some(metrics) = lock.get(&font_id) {
-            Ok(read(metrics))
+            read(metrics)
         } else {
             let mut lock = RwLockUpgradableReadGuard::upgrade(lock);
             let metrics = lock
                 .entry(font_id)
                 .or_insert_with(|| self.platform_text_system.font_metrics(font_id));
-            Ok(read(metrics))
+            read(metrics)
         }
     }
 

crates/gpui2/src/text_system/line.rs 🔗

@@ -101,9 +101,7 @@ fn paint_line(
     let mut glyph_origin = origin;
     let mut prev_glyph_position = Point::default();
     for (run_ix, run) in layout.runs.iter().enumerate() {
-        let max_glyph_size = text_system
-            .bounding_box(run.font_id, layout.font_size)?
-            .size;
+        let max_glyph_size = text_system.bounding_box(run.font_id, layout.font_size).size;
 
         for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
             glyph_origin.x += glyph.position.x - prev_glyph_position.x;

crates/ui2/src/components/list/list_item.rs 🔗

@@ -1,7 +1,8 @@
 use std::rc::Rc;
 
 use gpui::{
-    px, AnyElement, ClickEvent, Div, ImageSource, MouseButton, MouseDownEvent, Pixels, Stateful,
+    px, AnyElement, AnyView, ClickEvent, Div, ImageSource, MouseButton, MouseDownEvent, Pixels,
+    Stateful,
 };
 use smallvec::SmallVec;
 
@@ -21,6 +22,7 @@ pub struct ListItem {
     inset: bool,
     on_click: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
     on_toggle: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
+    tooltip: Option<Box<dyn Fn(&mut WindowContext) -> AnyView + 'static>>,
     on_secondary_mouse_down: Option<Rc<dyn Fn(&MouseDownEvent, &mut WindowContext) + 'static>>,
     children: SmallVec<[AnyElement; 2]>,
 }
@@ -38,6 +40,7 @@ impl ListItem {
             on_click: None,
             on_secondary_mouse_down: None,
             on_toggle: None,
+            tooltip: None,
             children: SmallVec::new(),
         }
     }
@@ -55,6 +58,11 @@ impl ListItem {
         self
     }
 
+    pub fn tooltip(mut self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self {
+        self.tooltip = Some(Box::new(tooltip));
+        self
+    }
+
     pub fn inset(mut self, inset: bool) -> Self {
         self.inset = inset;
         self
@@ -149,6 +157,7 @@ impl RenderOnce for ListItem {
                     (on_mouse_down)(event, cx)
                 })
             })
+            .when_some(self.tooltip, |this, tooltip| this.tooltip(tooltip))
             .child(
                 div()
                     .when(self.inset, |this| this.px_2())

crates/workspace2/src/workspace2.rs 🔗

@@ -4314,101 +4314,102 @@ pub fn create_and_open_local_file(
     })
 }
 
-// pub fn join_remote_project(
-//     project_id: u64,
-//     follow_user_id: u64,
-//     app_state: Arc<AppState>,
-//     cx: &mut AppContext,
-// ) -> Task<Result<()>> {
-//     cx.spawn(|mut cx| async move {
-//         let windows = cx.windows();
-//         let existing_workspace = windows.into_iter().find_map(|window| {
-//             window.downcast::<Workspace>().and_then(|window| {
-//                 window
-//                     .read_root_with(&cx, |workspace, cx| {
-//                         if workspace.project().read(cx).remote_id() == Some(project_id) {
-//                             Some(cx.handle().downgrade())
-//                         } else {
-//                             None
-//                         }
-//                     })
-//                     .unwrap_or(None)
-//             })
-//         });
-
-//         let workspace = if let Some(existing_workspace) = existing_workspace {
-//             existing_workspace
-//         } else {
-//             let active_call = cx.read(ActiveCall::global);
-//             let room = active_call
-//                 .read_with(&cx, |call, _| call.room().cloned())
-//                 .ok_or_else(|| anyhow!("not in a call"))?;
-//             let project = room
-//                 .update(&mut cx, |room, cx| {
-//                     room.join_project(
-//                         project_id,
-//                         app_state.languages.clone(),
-//                         app_state.fs.clone(),
-//                         cx,
-//                     )
-//                 })
-//                 .await?;
-
-//             let window_bounds_override = window_bounds_env_override(&cx);
-//             let window = cx.add_window(
-//                 (app_state.build_window_options)(
-//                     window_bounds_override,
-//                     None,
-//                     cx.platform().as_ref(),
-//                 ),
-//                 |cx| Workspace::new(0, project, app_state.clone(), cx),
-//             );
-//             let workspace = window.root(&cx).unwrap();
-//             (app_state.initialize_workspace)(
-//                 workspace.downgrade(),
-//                 false,
-//                 app_state.clone(),
-//                 cx.clone(),
-//             )
-//             .await
-//             .log_err();
+pub fn join_remote_project(
+    project_id: u64,
+    follow_user_id: u64,
+    app_state: Arc<AppState>,
+    cx: &mut AppContext,
+) -> Task<Result<()>> {
+    todo!()
+    // let windows = cx.windows();
+    // cx.spawn(|mut cx| async move {
+    //     let existing_workspace = windows.into_iter().find_map(|window| {
+    //         window.downcast::<Workspace>().and_then(|window| {
+    //             window
+    //                 .update(&mut cx, |workspace, cx| {
+    //                     if workspace.project().read(cx).remote_id() == Some(project_id) {
+    //                         Some(cx.view().downgrade())
+    //                     } else {
+    //                         None
+    //                     }
+    //                 })
+    //                 .unwrap_or(None)
+    //         })
+    //     });
 
-//             workspace.downgrade()
-//         };
+    //     let workspace = if let Some(existing_workspace) = existing_workspace {
+    //         existing_workspace
+    //     } else {
+    //         let active_call = cx.update(ActiveCall::global);
+    //         let room = active_call
+    //             .read_with(&cx, |call, _| call.room().cloned())
+    //             .ok_or_else(|| anyhow!("not in a call"))?;
+    //         let project = room
+    //             .update(&mut cx, |room, cx| {
+    //                 room.join_project(
+    //                     project_id,
+    //                     app_state.languages.clone(),
+    //                     app_state.fs.clone(),
+    //                     cx,
+    //                 )
+    //             })
+    //             .await?;
 
-//         workspace.window().activate(&mut cx);
-//         cx.platform().activate(true);
-
-//         workspace.update(&mut cx, |workspace, cx| {
-//             if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
-//                 let follow_peer_id = room
-//                     .read(cx)
-//                     .remote_participants()
-//                     .iter()
-//                     .find(|(_, participant)| participant.user.id == follow_user_id)
-//                     .map(|(_, p)| p.peer_id)
-//                     .or_else(|| {
-//                         // If we couldn't follow the given user, follow the host instead.
-//                         let collaborator = workspace
-//                             .project()
-//                             .read(cx)
-//                             .collaborators()
-//                             .values()
-//                             .find(|collaborator| collaborator.replica_id == 0)?;
-//                         Some(collaborator.peer_id)
-//                     });
-
-//                 if let Some(follow_peer_id) = follow_peer_id {
-//                     workspace
-//                         .follow(follow_peer_id, cx)
-//                         .map(|follow| follow.detach_and_log_err(cx));
-//                 }
-//             }
-//         })?;
+    //         let window_bounds_override = window_bounds_env_override(&cx);
+    //         let window = cx.add_window(
+    //             (app_state.build_window_options)(
+    //                 window_bounds_override,
+    //                 None,
+    //                 cx.platform().as_ref(),
+    //             ),
+    //             |cx| Workspace::new(0, project, app_state.clone(), cx),
+    //         );
+    //         let workspace = window.root(&cx).unwrap();
+    //         (app_state.initialize_workspace)(
+    //             workspace.downgrade(),
+    //             false,
+    //             app_state.clone(),
+    //             cx.clone(),
+    //         )
+    //         .await
+    //         .log_err();
+
+    //         workspace.downgrade()
+    //     };
+
+    //     workspace.window().activate(&mut cx);
+    //     cx.platform().activate(true);
+
+    //     workspace.update(&mut cx, |workspace, cx| {
+    //         if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
+    //             let follow_peer_id = room
+    //                 .read(cx)
+    //                 .remote_participants()
+    //                 .iter()
+    //                 .find(|(_, participant)| participant.user.id == follow_user_id)
+    //                 .map(|(_, p)| p.peer_id)
+    //                 .or_else(|| {
+    //                     // If we couldn't follow the given user, follow the host instead.
+    //                     let collaborator = workspace
+    //                         .project()
+    //                         .read(cx)
+    //                         .collaborators()
+    //                         .values()
+    //                         .find(|collaborator| collaborator.replica_id == 0)?;
+    //                     Some(collaborator.peer_id)
+    //                 });
+
+    //             if let Some(follow_peer_id) = follow_peer_id {
+    //                 workspace
+    //                     .follow(follow_peer_id, cx)
+    //                     .map(|follow| follow.detach_and_log_err(cx));
+    //             }
+    //         }
+    //     })?;
 
-//         anyhow::Ok(())
-//     })
-// }
+    //     anyhow::Ok(())
+    // })
+}
 
 pub fn restart(_: &Restart, cx: &mut AppContext) {
     let should_confirm = WorkspaceSettings::get_global(cx).confirm_quit;