Detailed changes
@@ -1222,7 +1222,6 @@ version = "0.1.0"
dependencies = [
"anyhow",
"async-broadcast",
- "async-trait",
"audio2",
"client2",
"collections",
@@ -1242,9 +1241,7 @@ dependencies = [
"serde_json",
"settings2",
"smallvec",
- "ui2",
"util",
- "workspace2",
]
[[package]]
@@ -11534,7 +11531,6 @@ version = "0.1.0"
dependencies = [
"anyhow",
"async-recursion 1.0.5",
- "async-trait",
"bincode",
"call2",
"client2",
@@ -31,9 +31,7 @@ media = { path = "../media" }
project = { package = "project2", path = "../project2" }
settings = { package = "settings2", path = "../settings2" }
util = { path = "../util" }
-ui = {package = "ui2", path = "../ui2"}
-workspace = {package = "workspace2", path = "../workspace2"}
-async-trait.workspace = true
+
anyhow.workspace = true
async-broadcast = "0.4"
futures.workspace = true
@@ -1,32 +1,25 @@
pub mod call_settings;
pub mod participant;
pub mod room;
-mod shared_screen;
use anyhow::{anyhow, Result};
-use async_trait::async_trait;
use audio::Audio;
use call_settings::CallSettings;
-use client::{
- proto::{self, PeerId},
- Client, TelemetrySettings, TypedEnvelope, User, UserStore, ZED_ALWAYS_ACTIVE,
-};
+use client::{proto, Client, TelemetrySettings, TypedEnvelope, User, UserStore, ZED_ALWAYS_ACTIVE};
use collections::HashSet;
use futures::{channel::oneshot, future::Shared, Future, FutureExt};
use gpui::{
- AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, PromptLevel,
- Subscription, Task, View, ViewContext, VisualContext, WeakModel, WindowHandle,
+ AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Subscription, Task,
+ WeakModel,
};
-pub use participant::ParticipantLocation;
use postage::watch;
use project::Project;
use room::Event;
-pub use room::Room;
use settings::Settings;
-use shared_screen::SharedScreen;
use std::sync::Arc;
-use util::ResultExt;
-use workspace::{item::ItemHandle, CallHandler, Pane, Workspace};
+
+pub use participant::ParticipantLocation;
+pub use room::Room;
pub fn init(client: Arc<Client>, user_store: Model<UserStore>, cx: &mut AppContext) {
CallSettings::register(cx);
@@ -334,55 +327,12 @@ impl ActiveCall {
pub fn join_channel(
&mut self,
channel_id: u64,
- requesting_window: Option<WindowHandle<Workspace>>,
cx: &mut ModelContext<Self>,
) -> Task<Result<Option<Model<Room>>>> {
if let Some(room) = self.room().cloned() {
if room.read(cx).channel_id() == Some(channel_id) {
- return cx.spawn(|_, _| async move {
- todo!();
- // let future = room.update(&mut cx, |room, cx| {
- // room.most_active_project(cx).map(|(host, project)| {
- // room.join_project(project, host, app_state.clone(), cx)
- // })
- // })
-
- // if let Some(future) = future {
- // future.await?;
- // }
-
- // Ok(Some(room))
- });
- }
-
- let should_prompt = room.update(cx, |room, _| {
- room.channel_id().is_some()
- && room.is_sharing_project()
- && room.remote_participants().len() > 0
- });
- if should_prompt && requesting_window.is_some() {
- return cx.spawn(|this, mut cx| async move {
- let answer = requesting_window.unwrap().update(&mut cx, |_, cx| {
- cx.prompt(
- PromptLevel::Warning,
- "Leaving this call will unshare your current project.\nDo you want to switch channels?",
- &["Yes, Join Channel", "Cancel"],
- )
- })?;
- if answer.await? == 1 {
- return Ok(None);
- }
-
- room.update(&mut cx, |room, cx| room.clear_state(cx))?;
-
- this.update(&mut cx, |this, cx| {
- this.join_channel(channel_id, requesting_window, cx)
- })?
- .await
- });
- }
-
- if room.read(cx).channel_id().is_some() {
+ return Task::ready(Ok(Some(room)));
+ } else {
room.update(cx, |room, cx| room.clear_state(cx));
}
}
@@ -555,197 +505,6 @@ pub fn report_call_event_for_channel(
)
}
-pub struct Call {
- active_call: Option<(Model<ActiveCall>, Vec<Subscription>)>,
-}
-
-impl Call {
- pub fn new(cx: &mut ViewContext<'_, Workspace>) -> Box<dyn CallHandler> {
- let mut active_call = None;
- if cx.has_global::<Model<ActiveCall>>() {
- let call = cx.global::<Model<ActiveCall>>().clone();
- let subscriptions = vec![cx.subscribe(&call, Self::on_active_call_event)];
- active_call = Some((call, subscriptions));
- }
- Box::new(Self { active_call })
- }
- fn on_active_call_event(
- workspace: &mut Workspace,
- _: Model<ActiveCall>,
- event: &room::Event,
- cx: &mut ViewContext<Workspace>,
- ) {
- match event {
- room::Event::ParticipantLocationChanged { participant_id }
- | room::Event::RemoteVideoTracksChanged { participant_id } => {
- workspace.leader_updated(*participant_id, cx);
- }
- _ => {}
- }
- }
-}
-
-#[async_trait(?Send)]
-impl CallHandler for Call {
- fn peer_state(
- &mut self,
- leader_id: PeerId,
- project: &Model<Project>,
- cx: &mut ViewContext<Workspace>,
- ) -> Option<(bool, bool)> {
- let (call, _) = self.active_call.as_ref()?;
- let room = call.read(cx).room()?.read(cx);
- let participant = room.remote_participant_for_peer_id(leader_id)?;
-
- let leader_in_this_app;
- let leader_in_this_project;
- match participant.location {
- ParticipantLocation::SharedProject { project_id } => {
- leader_in_this_app = true;
- leader_in_this_project = Some(project_id) == project.read(cx).remote_id();
- }
- ParticipantLocation::UnsharedProject => {
- leader_in_this_app = true;
- leader_in_this_project = false;
- }
- ParticipantLocation::External => {
- leader_in_this_app = false;
- leader_in_this_project = false;
- }
- };
-
- Some((leader_in_this_project, leader_in_this_app))
- }
-
- fn shared_screen_for_peer(
- &self,
- peer_id: PeerId,
- pane: &View<Pane>,
- cx: &mut ViewContext<Workspace>,
- ) -> Option<Box<dyn ItemHandle>> {
- let (call, _) = self.active_call.as_ref()?;
- let room = call.read(cx).room()?.read(cx);
- let participant = room.remote_participant_for_peer_id(peer_id)?;
- let track = participant.video_tracks.values().next()?.clone();
- let user = participant.user.clone();
- for item in pane.read(cx).items_of_type::<SharedScreen>() {
- if item.read(cx).peer_id == peer_id {
- return Some(Box::new(item));
- }
- }
-
- Some(Box::new(cx.build_view(|cx| {
- SharedScreen::new(&track, peer_id, user.clone(), cx)
- })))
- }
- fn room_id(&self, cx: &AppContext) -> Option<u64> {
- Some(self.active_call.as_ref()?.0.read(cx).room()?.read(cx).id())
- }
- fn hang_up(&self, cx: &mut AppContext) -> Task<Result<()>> {
- let Some((call, _)) = self.active_call.as_ref() else {
- return Task::ready(Err(anyhow!("Cannot exit a call; not in a call")));
- };
-
- call.update(cx, |this, cx| this.hang_up(cx))
- }
- fn active_project(&self, cx: &AppContext) -> Option<WeakModel<Project>> {
- ActiveCall::global(cx).read(cx).location().cloned()
- }
- fn invite(
- &mut self,
- called_user_id: u64,
- initial_project: Option<Model<Project>>,
- cx: &mut AppContext,
- ) -> Task<Result<()>> {
- ActiveCall::global(cx).update(cx, |this, cx| {
- this.invite(called_user_id, initial_project, cx)
- })
- }
- fn remote_participants(&self, cx: &AppContext) -> Option<Vec<(Arc<User>, PeerId)>> {
- self.active_call
- .as_ref()
- .map(|call| {
- call.0.read(cx).room().map(|room| {
- room.read(cx)
- .remote_participants()
- .iter()
- .map(|participant| {
- (participant.1.user.clone(), participant.1.peer_id.clone())
- })
- .collect()
- })
- })
- .flatten()
- }
- fn is_muted(&self, cx: &AppContext) -> Option<bool> {
- self.active_call
- .as_ref()
- .map(|call| {
- call.0
- .read(cx)
- .room()
- .map(|room| room.read(cx).is_muted(cx))
- })
- .flatten()
- }
- fn toggle_mute(&self, cx: &mut AppContext) {
- self.active_call.as_ref().map(|call| {
- call.0.update(cx, |this, cx| {
- this.room().map(|room| {
- let room = room.clone();
- cx.spawn(|_, mut cx| async move {
- room.update(&mut cx, |this, cx| this.toggle_mute(cx))??
- .await
- })
- .detach_and_log_err(cx);
- })
- })
- });
- }
- fn toggle_screen_share(&self, cx: &mut AppContext) {
- self.active_call.as_ref().map(|call| {
- call.0.update(cx, |this, cx| {
- this.room().map(|room| {
- room.update(cx, |this, cx| {
- if this.is_screen_sharing() {
- this.unshare_screen(cx).log_err();
- } else {
- let t = this.share_screen(cx);
- cx.spawn(move |_, _| async move {
- t.await.log_err();
- })
- .detach();
- }
- })
- })
- })
- });
- }
- fn toggle_deafen(&self, cx: &mut AppContext) {
- self.active_call.as_ref().map(|call| {
- call.0.update(cx, |this, cx| {
- this.room().map(|room| {
- room.update(cx, |this, cx| {
- this.toggle_deafen(cx).log_err();
- })
- })
- })
- });
- }
- fn is_deafened(&self, cx: &AppContext) -> Option<bool> {
- self.active_call
- .as_ref()
- .map(|call| {
- call.0
- .read(cx)
- .room()
- .map(|room| room.read(cx).is_deafened())
- })
- .flatten()
- .flatten()
- }
-}
-
#[cfg(test)]
mod test {
use gpui::TestAppContext;
@@ -4,7 +4,7 @@ use client::{proto, User};
use collections::HashMap;
use gpui::WeakModel;
pub use live_kit_client::Frame;
-pub(crate) use live_kit_client::{RemoteAudioTrack, RemoteVideoTrack};
+pub use live_kit_client::{RemoteAudioTrack, RemoteVideoTrack};
use project::Project;
use std::sync::Arc;
@@ -364,8 +364,7 @@ async fn test_joining_channel_ancestor_member(
let active_call_b = cx_b.read(ActiveCall::global);
assert!(active_call_b
- .update(cx_b, |active_call, cx| active_call
- .join_channel(sub_id, None, cx))
+ .update(cx_b, |active_call, cx| active_call.join_channel(sub_id, cx))
.await
.is_ok());
}
@@ -395,9 +394,7 @@ async fn test_channel_room(
let active_call_b = cx_b.read(ActiveCall::global);
active_call_a
- .update(cx_a, |active_call, cx| {
- active_call.join_channel(zed_id, None, cx)
- })
+ .update(cx_a, |active_call, cx| active_call.join_channel(zed_id, cx))
.await
.unwrap();
@@ -445,9 +442,7 @@ async fn test_channel_room(
});
active_call_b
- .update(cx_b, |active_call, cx| {
- active_call.join_channel(zed_id, None, cx)
- })
+ .update(cx_b, |active_call, cx| active_call.join_channel(zed_id, cx))
.await
.unwrap();
@@ -564,16 +559,12 @@ async fn test_channel_room(
});
active_call_a
- .update(cx_a, |active_call, cx| {
- active_call.join_channel(zed_id, None, cx)
- })
+ .update(cx_a, |active_call, cx| active_call.join_channel(zed_id, cx))
.await
.unwrap();
active_call_b
- .update(cx_b, |active_call, cx| {
- active_call.join_channel(zed_id, None, cx)
- })
+ .update(cx_b, |active_call, cx| active_call.join_channel(zed_id, cx))
.await
.unwrap();
@@ -617,9 +608,7 @@ async fn test_channel_jumping(executor: BackgroundExecutor, cx_a: &mut TestAppCo
let active_call_a = cx_a.read(ActiveCall::global);
active_call_a
- .update(cx_a, |active_call, cx| {
- active_call.join_channel(zed_id, None, cx)
- })
+ .update(cx_a, |active_call, cx| active_call.join_channel(zed_id, cx))
.await
.unwrap();
@@ -638,7 +627,7 @@ async fn test_channel_jumping(executor: BackgroundExecutor, cx_a: &mut TestAppCo
active_call_a
.update(cx_a, |active_call, cx| {
- active_call.join_channel(rust_id, None, cx)
+ active_call.join_channel(rust_id, cx)
})
.await
.unwrap();
@@ -804,7 +793,7 @@ async fn test_call_from_channel(
let active_call_b = cx_b.read(ActiveCall::global);
active_call_a
- .update(cx_a, |call, cx| call.join_channel(channel_id, None, cx))
+ .update(cx_a, |call, cx| call.join_channel(channel_id, cx))
.await
.unwrap();
@@ -1297,7 +1286,7 @@ async fn test_guest_access(
// Non-members should not be allowed to join
assert!(active_call_b
- .update(cx_b, |call, cx| call.join_channel(channel_a, None, cx))
+ .update(cx_b, |call, cx| call.join_channel(channel_a, cx))
.await
.is_err());
@@ -1319,7 +1308,7 @@ async fn test_guest_access(
// Client B joins channel A as a guest
active_call_b
- .update(cx_b, |call, cx| call.join_channel(channel_a, None, cx))
+ .update(cx_b, |call, cx| call.join_channel(channel_a, cx))
.await
.unwrap();
@@ -1352,7 +1341,7 @@ async fn test_guest_access(
assert_channels_list_shape(client_b.channel_store(), cx_b, &[]);
active_call_b
- .update(cx_b, |call, cx| call.join_channel(channel_b, None, cx))
+ .update(cx_b, |call, cx| call.join_channel(channel_b, cx))
.await
.unwrap();
@@ -1383,7 +1372,7 @@ async fn test_invite_access(
// should not be allowed to join
assert!(active_call_b
- .update(cx_b, |call, cx| call.join_channel(channel_b_id, None, cx))
+ .update(cx_b, |call, cx| call.join_channel(channel_b_id, cx))
.await
.is_err());
@@ -1401,7 +1390,7 @@ async fn test_invite_access(
.unwrap();
active_call_b
- .update(cx_b, |call, cx| call.join_channel(channel_b_id, None, cx))
+ .update(cx_b, |call, cx| call.join_channel(channel_b_id, cx))
.await
.unwrap();
@@ -510,10 +510,9 @@ async fn test_joining_channels_and_calling_multiple_users_simultaneously(
// Simultaneously join channel 1 and then channel 2
active_call_a
- .update(cx_a, |call, cx| call.join_channel(channel_1, None, cx))
+ .update(cx_a, |call, cx| call.join_channel(channel_1, cx))
.detach();
- let join_channel_2 =
- active_call_a.update(cx_a, |call, cx| call.join_channel(channel_2, None, cx));
+ let join_channel_2 = active_call_a.update(cx_a, |call, cx| call.join_channel(channel_2, cx));
join_channel_2.await.unwrap();
@@ -539,8 +538,7 @@ async fn test_joining_channels_and_calling_multiple_users_simultaneously(
call.invite(client_c.user_id().unwrap(), None, cx)
});
- let join_channel =
- active_call_a.update(cx_a, |call, cx| call.join_channel(channel_1, None, cx));
+ let join_channel = active_call_a.update(cx_a, |call, cx| call.join_channel(channel_1, cx));
b_invite.await.unwrap();
c_invite.await.unwrap();
@@ -569,8 +567,7 @@ async fn test_joining_channels_and_calling_multiple_users_simultaneously(
.unwrap();
// Simultaneously join channel 1 and call user B and user C from client A.
- let join_channel =
- active_call_a.update(cx_a, |call, cx| call.join_channel(channel_1, None, cx));
+ let join_channel = active_call_a.update(cx_a, |call, cx| call.join_channel(channel_1, cx));
let b_invite = active_call_a.update(cx_a, |call, cx| {
call.invite(client_b.user_id().unwrap(), None, cx)
@@ -221,7 +221,6 @@ impl TestServer {
fs: fs.clone(),
build_window_options: |_, _, _| Default::default(),
node_runtime: FakeNodeRuntime::new(),
- call_factory: |_| Box::new(workspace::TestCallHandler),
});
cx.update(|cx| {
@@ -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};
@@ -347,21 +348,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>),
@@ -370,12 +371,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,
},
@@ -708,136 +709,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();
@@ -1135,290 +1136,234 @@ 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_some(peer_id, |this, peer_id| {
+ 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() {
@@ -1465,117 +1410,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>,
@@ -31,9 +31,9 @@ use std::sync::Arc;
use call::ActiveCall;
use client::{Client, UserStore};
use gpui::{
- div, px, rems, AppContext, Div, Element, InteractiveElement, IntoElement, Model, MouseButton,
- ParentElement, Render, RenderOnce, Stateful, StatefulInteractiveElement, Styled, Subscription,
- ViewContext, VisualContext, WeakView, WindowBounds,
+ actions, div, px, rems, AppContext, Div, Element, InteractiveElement, IntoElement, Model,
+ MouseButton, ParentElement, Render, RenderOnce, Stateful, StatefulInteractiveElement, Styled,
+ Subscription, ViewContext, VisualContext, WeakView, WindowBounds,
};
use project::{Project, RepositoryEntry};
use theme::ActiveTheme;
@@ -49,6 +49,14 @@ use crate::face_pile::FacePile;
const MAX_PROJECT_NAME_LENGTH: usize = 40;
const MAX_BRANCH_NAME_LENGTH: usize = 40;
+actions!(
+ ShareProject,
+ UnshareProject,
+ ToggleUserMenu,
+ ToggleProjectMenu,
+ SwitchBranch
+);
+
// actions!(
// collab,
// [
@@ -91,37 +99,23 @@ impl Render for CollabTitlebarItem {
type Element = Stateful<Div>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
- let is_in_room = self
- .workspace
- .update(cx, |this, cx| this.call_state().is_in_room(cx))
- .unwrap_or_default();
+ let room = ActiveCall::global(cx).read(cx).room();
+ let is_in_room = room.is_some();
let is_shared = is_in_room && self.project.read(cx).is_shared();
let current_user = self.user_store.read(cx).current_user();
let client = self.client.clone();
- let users = self
- .workspace
- .update(cx, |this, cx| this.call_state().remote_participants(cx))
- .log_err()
- .flatten();
- let is_muted = self
- .workspace
- .update(cx, |this, cx| this.call_state().is_muted(cx))
- .log_err()
- .flatten()
- .unwrap_or_default();
- let is_deafened = self
- .workspace
- .update(cx, |this, cx| this.call_state().is_deafened(cx))
- .log_err()
- .flatten()
- .unwrap_or_default();
- let speakers_icon = if self
- .workspace
- .update(cx, |this, cx| this.call_state().is_deafened(cx))
- .log_err()
- .flatten()
- .unwrap_or_default()
- {
+ let remote_participants = room.map(|room| {
+ room.read(cx)
+ .remote_participants()
+ .values()
+ .map(|participant| (participant.user.clone(), participant.peer_id))
+ .collect::<Vec<_>>()
+ });
+ let is_muted = room.map_or(false, |room| room.read(cx).is_muted(cx));
+ let is_deafened = room
+ .and_then(|room| room.read(cx).is_deafened())
+ .unwrap_or(false);
+ let speakers_icon = if is_deafened {
ui::Icon::AudioOff
} else {
ui::Icon::AudioOn
@@ -157,7 +151,7 @@ impl Render for CollabTitlebarItem {
.children(self.render_project_branch(cx)),
)
.when_some(
- users.zip(current_user.clone()),
+ remote_participants.zip(current_user.clone()),
|this, (remote_participants, current_user)| {
let mut pile = FacePile::default();
pile.extend(
@@ -168,25 +162,30 @@ impl Render for CollabTitlebarItem {
div().child(Avatar::data(avatar.clone())).into_any_element()
})
.into_iter()
- .chain(remote_participants.into_iter().flat_map(|(user, peer_id)| {
- user.avatar.as_ref().map(|avatar| {
- div()
- .child(
- Avatar::data(avatar.clone()).into_element().into_any(),
- )
- .on_mouse_down(MouseButton::Left, {
- let workspace = workspace.clone();
- move |_, cx| {
- workspace
- .update(cx, |this, cx| {
- this.open_shared_screen(peer_id, cx);
- })
- .log_err();
- }
- })
- .into_any_element()
- })
- })),
+ .chain(remote_participants.into_iter().filter_map(
+ |(user, peer_id)| {
+ let avatar = user.avatar.as_ref()?;
+ Some(
+ div()
+ .child(
+ Avatar::data(avatar.clone())
+ .into_element()
+ .into_any(),
+ )
+ .on_mouse_down(MouseButton::Left, {
+ let workspace = workspace.clone();
+ move |_, cx| {
+ workspace
+ .update(cx, |this, cx| {
+ this.open_shared_screen(peer_id, cx);
+ })
+ .log_err();
+ }
+ })
+ .into_any_element(),
+ )
+ },
+ )),
);
this.child(pile.render(cx))
},
@@ -204,20 +203,24 @@ impl Render for CollabTitlebarItem {
"toggle_sharing",
if is_shared { "Unshare" } else { "Share" },
)
- .style(ButtonStyle::Subtle),
+ .style(ButtonStyle::Subtle)
+ .on_click(cx.listener(
+ move |this, _, cx| {
+ if is_shared {
+ this.unshare_project(&Default::default(), cx);
+ } else {
+ this.share_project(&Default::default(), cx);
+ }
+ },
+ )),
)
.child(
IconButton::new("leave-call", ui::Icon::Exit)
.style(ButtonStyle::Subtle)
- .on_click({
- let workspace = workspace.clone();
- move |_, cx| {
- workspace
- .update(cx, |this, cx| {
- this.call_state().hang_up(cx).detach();
- })
- .log_err();
- }
+ .on_click(move |_, cx| {
+ ActiveCall::global(cx)
+ .update(cx, |call, cx| call.hang_up(cx))
+ .detach_and_log_err(cx);
}),
),
)
@@ -235,15 +238,8 @@ impl Render for CollabTitlebarItem {
)
.style(ButtonStyle::Subtle)
.selected(is_muted)
- .on_click({
- let workspace = workspace.clone();
- move |_, cx| {
- workspace
- .update(cx, |this, cx| {
- this.call_state().toggle_mute(cx);
- })
- .log_err();
- }
+ .on_click(move |_, cx| {
+ crate::toggle_mute(&Default::default(), cx)
}),
)
.child(
@@ -258,26 +254,15 @@ impl Render for CollabTitlebarItem {
cx,
)
})
- .on_click({
- let workspace = workspace.clone();
- move |_, cx| {
- workspace
- .update(cx, |this, cx| {
- this.call_state().toggle_deafen(cx);
- })
- .log_err();
- }
+ .on_click(move |_, cx| {
+ crate::toggle_mute(&Default::default(), cx)
}),
)
.child(
IconButton::new("screen-share", ui::Icon::Screen)
.style(ButtonStyle::Subtle)
.on_click(move |_, cx| {
- workspace
- .update(cx, |this, cx| {
- this.call_state().toggle_screen_share(cx);
- })
- .log_err();
+ crate::toggle_screen_sharing(&Default::default(), cx)
}),
)
.pl_2(),
@@ -451,46 +436,19 @@ impl CollabTitlebarItem {
// render_project_owner -> resolve if you are in a room -> Option<foo>
pub fn render_project_owner(&self, cx: &mut ViewContext<Self>) -> Option<impl Element> {
- // TODO: We can't finish implementing this until project sharing works
- // - [ ] Show the project owner when the project is remote (maybe done)
- // - [x] Show the project owner when the project is local
- // - [ ] Show the project owner with a lock icon when the project is local and unshared
-
- let remote_id = self.project.read(cx).remote_id();
- let is_local = remote_id.is_none();
- let is_shared = self.project.read(cx).is_shared();
- let (user_name, participant_index) = {
- if let Some(host) = self.project.read(cx).host() {
- debug_assert!(!is_local);
- let (Some(host_user), Some(participant_index)) = (
- self.user_store.read(cx).get_cached_user(host.user_id),
- self.user_store
- .read(cx)
- .participant_indices()
- .get(&host.user_id),
- ) else {
- return None;
- };
- (host_user.github_login.clone(), participant_index.0)
- } else {
- debug_assert!(is_local);
- let name = self
- .user_store
- .read(cx)
- .current_user()
- .map(|user| user.github_login.clone())?;
- (name, 0)
- }
- };
+ let host = self.project.read(cx).host()?;
+ let host = self.user_store.read(cx).get_cached_user(host.user_id)?;
+ let participant_index = self
+ .user_store
+ .read(cx)
+ .participant_indices()
+ .get(&host.id)?;
Some(
div().border().border_color(gpui::red()).child(
- Button::new(
- "project_owner_trigger",
- format!("{user_name} ({})", !is_shared),
- )
- .color(Color::Player(participant_index))
- .style(ButtonStyle::Subtle)
- .tooltip(move |cx| Tooltip::text("Toggle following", cx)),
+ Button::new("project_owner_trigger", host.github_login.clone())
+ .color(Color::Player(participant_index.0))
+ .style(ButtonStyle::Subtle)
+ .tooltip(move |cx| Tooltip::text("Toggle following", cx)),
),
)
}
@@ -730,21 +688,21 @@ impl CollabTitlebarItem {
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 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 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();
+ }
// pub fn toggle_user_menu(&mut self, _: &ToggleUserMenu, cx: &mut ViewContext<Self>) {
// self.user_menu.update(cx, |user_menu, cx| {
@@ -9,22 +9,21 @@ mod panel_settings;
use std::{rc::Rc, sync::Arc};
+use call::{report_call_event_for_room, ActiveCall, Room};
pub use collab_panel::CollabPanel;
pub use collab_titlebar_item::CollabTitlebarItem;
use gpui::{
- point, AppContext, GlobalPixels, Pixels, PlatformDisplay, Size, WindowBounds, WindowKind,
- WindowOptions,
+ actions, point, AppContext, GlobalPixels, Pixels, PlatformDisplay, Size, Task, WindowBounds,
+ WindowKind, WindowOptions,
};
pub use panel_settings::{
ChatPanelSettings, CollaborationPanelSettings, NotificationPanelSettings,
};
use settings::Settings;
+use util::ResultExt;
use workspace::AppState;
-// actions!(
-// collab,
-// [ToggleScreenSharing, ToggleMute, ToggleDeafen, LeaveCall]
-// );
+actions!(ToggleScreenSharing, ToggleMute, ToggleDeafen, LeaveCall);
pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
CollaborationPanelSettings::register(cx);
@@ -42,61 +41,61 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
// cx.add_global_action(toggle_deafen);
}
-// pub fn toggle_screen_sharing(_: &ToggleScreenSharing, cx: &mut AppContext) {
-// let call = ActiveCall::global(cx).read(cx);
-// if let Some(room) = call.room().cloned() {
-// let client = call.client();
-// let toggle_screen_sharing = room.update(cx, |room, cx| {
-// if room.is_screen_sharing() {
-// report_call_event_for_room(
-// "disable screen share",
-// room.id(),
-// room.channel_id(),
-// &client,
-// cx,
-// );
-// Task::ready(room.unshare_screen(cx))
-// } else {
-// report_call_event_for_room(
-// "enable screen share",
-// room.id(),
-// room.channel_id(),
-// &client,
-// cx,
-// );
-// room.share_screen(cx)
-// }
-// });
-// toggle_screen_sharing.detach_and_log_err(cx);
-// }
-// }
+pub fn toggle_screen_sharing(_: &ToggleScreenSharing, cx: &mut AppContext) {
+ let call = ActiveCall::global(cx).read(cx);
+ if let Some(room) = call.room().cloned() {
+ let client = call.client();
+ let toggle_screen_sharing = room.update(cx, |room, cx| {
+ if room.is_screen_sharing() {
+ report_call_event_for_room(
+ "disable screen share",
+ room.id(),
+ room.channel_id(),
+ &client,
+ cx,
+ );
+ Task::ready(room.unshare_screen(cx))
+ } else {
+ report_call_event_for_room(
+ "enable screen share",
+ room.id(),
+ room.channel_id(),
+ &client,
+ cx,
+ );
+ room.share_screen(cx)
+ }
+ });
+ toggle_screen_sharing.detach_and_log_err(cx);
+ }
+}
-// pub fn toggle_mute(_: &ToggleMute, cx: &mut AppContext) {
-// let call = ActiveCall::global(cx).read(cx);
-// if let Some(room) = call.room().cloned() {
-// let client = call.client();
-// room.update(cx, |room, cx| {
-// let operation = if room.is_muted(cx) {
-// "enable microphone"
-// } else {
-// "disable microphone"
-// };
-// report_call_event_for_room(operation, room.id(), room.channel_id(), &client, cx);
+pub fn toggle_mute(_: &ToggleMute, cx: &mut AppContext) {
+ let call = ActiveCall::global(cx).read(cx);
+ if let Some(room) = call.room().cloned() {
+ let client = call.client();
+ room.update(cx, |room, cx| {
+ let operation = if room.is_muted(cx) {
+ "enable microphone"
+ } else {
+ "disable microphone"
+ };
+ report_call_event_for_room(operation, room.id(), room.channel_id(), &client, cx);
-// room.toggle_mute(cx)
-// })
-// .map(|task| task.detach_and_log_err(cx))
-// .log_err();
-// }
-// }
+ room.toggle_mute(cx)
+ })
+ .map(|task| task.detach_and_log_err(cx))
+ .log_err();
+ }
+}
-// pub fn toggle_deafen(_: &ToggleDeafen, cx: &mut AppContext) {
-// if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
-// room.update(cx, Room::toggle_deafen)
-// .map(|task| task.detach_and_log_err(cx))
-// .log_err();
-// }
-// }
+pub fn toggle_deafen(_: &ToggleDeafen, cx: &mut AppContext) {
+ if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
+ room.update(cx, Room::toggle_deafen)
+ .map(|task| task.detach_and_log_err(cx))
+ .log_err();
+ }
+}
fn notification_window_options(
screen: Rc<dyn PlatformDisplay>,
@@ -1755,7 +1755,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();
@@ -3714,7 +3714,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;
@@ -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
+ }
+}
@@ -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::*;
@@ -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)
}
}
@@ -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;
@@ -2734,6 +2734,7 @@ pub enum ElementId {
Integer(usize),
Name(SharedString),
FocusHandle(FocusId),
+ NamedInteger(SharedString, usize),
}
impl ElementId {
@@ -2783,3 +2784,9 @@ impl<'a> From<&'a FocusHandle> for ElementId {
ElementId::FocusHandle(handle.id)
}
}
+
+impl From<(&'static str, EntityId)> for ElementId {
+ fn from((name, id): (&'static str, EntityId)) -> Self {
+ ElementId::NamedInteger(name.into(), id.as_u64() as usize)
+ }
+}
@@ -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())
@@ -20,6 +20,7 @@ test-support = [
[dependencies]
db = { path = "../db2", package = "db2" }
+call = { path = "../call2", package = "call2" }
client = { path = "../client2", package = "client2" }
collections = { path = "../collections" }
# context_menu = { path = "../context_menu" }
@@ -36,7 +37,6 @@ theme = { path = "../theme2", package = "theme2" }
util = { path = "../util" }
ui = { package = "ui2", path = "../ui2" }
-async-trait.workspace = true
async-recursion = "1.0.0"
itertools = "0.10"
bincode = "1.2.1"
@@ -1,18 +1,20 @@
use crate::{AppState, FollowerState, Pane, Workspace};
use anyhow::{anyhow, bail, Result};
+use call::{ActiveCall, ParticipantLocation};
use collections::HashMap;
use db::sqlez::{
bindable::{Bind, Column, StaticColumnCount},
statement::Statement,
};
use gpui::{
- point, size, AnyWeakView, Bounds, Div, IntoElement, Model, Pixels, Point, View, ViewContext,
+ point, size, AnyWeakView, Bounds, Div, Entity as _, IntoElement, Model, Pixels, Point, View,
+ ViewContext,
};
use parking_lot::Mutex;
use project::Project;
use serde::Deserialize;
use std::sync::Arc;
-use ui::prelude::*;
+use ui::{prelude::*, Button};
const HANDLE_HITBOX_SIZE: f32 = 4.0;
const HORIZONTAL_MIN_SIZE: f32 = 80.;
@@ -126,6 +128,7 @@ impl PaneGroup {
&self,
project: &Model<Project>,
follower_states: &HashMap<View<Pane>, FollowerState>,
+ active_call: Option<&Model<ActiveCall>>,
active_pane: &View<Pane>,
zoomed: Option<&AnyWeakView>,
app_state: &Arc<AppState>,
@@ -135,6 +138,7 @@ impl PaneGroup {
project,
0,
follower_states,
+ active_call,
active_pane,
zoomed,
app_state,
@@ -196,6 +200,7 @@ impl Member {
project: &Model<Project>,
basis: usize,
follower_states: &HashMap<View<Pane>, FollowerState>,
+ active_call: Option<&Model<ActiveCall>>,
active_pane: &View<Pane>,
zoomed: Option<&AnyWeakView>,
app_state: &Arc<AppState>,
@@ -203,19 +208,89 @@ impl Member {
) -> impl IntoElement {
match self {
Member::Pane(pane) => {
- // todo!()
- // let pane_element = if Some(pane.into()) == zoomed {
- // None
- // } else {
- // Some(pane)
- // };
-
- div().size_full().child(pane.clone()).into_any()
-
- // Stack::new()
- // .with_child(pane_element.contained().with_border(leader_border))
- // .with_children(leader_status_box)
- // .into_any()
+ let leader = follower_states.get(pane).and_then(|state| {
+ let room = active_call?.read(cx).room()?.read(cx);
+ room.remote_participant_for_peer_id(state.leader_id)
+ });
+
+ let mut leader_border = None;
+ let mut leader_status_box = None;
+ if let Some(leader) = &leader {
+ let mut leader_color = cx
+ .theme()
+ .players()
+ .color_for_participant(leader.participant_index.0)
+ .cursor;
+ leader_color.fade_out(0.3);
+ leader_border = Some(leader_color);
+
+ leader_status_box = match leader.location {
+ ParticipantLocation::SharedProject {
+ project_id: leader_project_id,
+ } => {
+ if Some(leader_project_id) == project.read(cx).remote_id() {
+ None
+ } else {
+ let leader_user = leader.user.clone();
+ let leader_user_id = leader.user.id;
+ Some(
+ Button::new(
+ ("leader-status", pane.entity_id()),
+ format!(
+ "Follow {} to their active project",
+ leader_user.github_login,
+ ),
+ )
+ .on_click(cx.listener(
+ move |this, _, cx| {
+ crate::join_remote_project(
+ leader_project_id,
+ leader_user_id,
+ this.app_state().clone(),
+ cx,
+ )
+ .detach_and_log_err(cx);
+ },
+ )),
+ )
+ }
+ }
+ ParticipantLocation::UnsharedProject => Some(Button::new(
+ ("leader-status", pane.entity_id()),
+ format!(
+ "{} is viewing an unshared Zed project",
+ leader.user.github_login
+ ),
+ )),
+ ParticipantLocation::External => Some(Button::new(
+ ("leader-status", pane.entity_id()),
+ format!(
+ "{} is viewing a window outside of Zed",
+ leader.user.github_login
+ ),
+ )),
+ };
+ }
+
+ div()
+ .relative()
+ .size_full()
+ .child(pane.clone())
+ .when_some(leader_border, |this, color| {
+ this.border_2().border_color(color)
+ })
+ .when_some(leader_status_box, |this, status_box| {
+ this.child(
+ div()
+ .absolute()
+ .w_96()
+ .bottom_3()
+ .right_3()
+ .z_index(1)
+ .child(status_box),
+ )
+ })
+ .into_any()
// let el = div()
// .flex()
@@ -1,5 +1,9 @@
-use crate::participant::{Frame, RemoteVideoTrack};
+use crate::{
+ item::{Item, ItemEvent},
+ ItemNavHistory, WorkspaceId,
+};
use anyhow::Result;
+use call::participant::{Frame, RemoteVideoTrack};
use client::{proto::PeerId, User};
use futures::StreamExt;
use gpui::{
@@ -9,7 +13,6 @@ use gpui::{
};
use std::sync::{Arc, Weak};
use ui::{h_stack, Icon, IconElement};
-use workspace::{item::Item, ItemNavHistory, WorkspaceId};
pub enum Event {
Close,
@@ -56,7 +59,7 @@ impl SharedScreen {
}
impl EventEmitter<Event> for SharedScreen {}
-impl EventEmitter<workspace::item::ItemEvent> for SharedScreen {}
+impl EventEmitter<ItemEvent> for SharedScreen {}
impl FocusableView for SharedScreen {
fn focus_handle(&self, _: &AppContext) -> FocusHandle {
@@ -10,15 +10,16 @@ mod persistence;
pub mod searchable;
// todo!()
mod modal_layer;
+mod shared_screen;
mod status_bar;
mod toolbar;
mod workspace_settings;
use anyhow::{anyhow, Context as _, Result};
-use async_trait::async_trait;
+use call::ActiveCall;
use client::{
proto::{self, PeerId},
- Client, TypedEnvelope, User, UserStore,
+ Client, Status, TypedEnvelope, UserStore,
};
use collections::{hash_map, HashMap, HashSet};
use dock::{Dock, DockPosition, Panel, PanelButtons, PanelHandle};
@@ -28,11 +29,11 @@ use futures::{
Future, FutureExt, StreamExt,
};
use gpui::{
- actions, div, point, size, Action, AnyModel, AnyView, AnyWeakView, AppContext, AsyncAppContext,
- AsyncWindowContext, Bounds, Context, Div, Entity, EntityId, EventEmitter, FocusHandle,
- FocusableView, GlobalPixels, InteractiveElement, KeyContext, ManagedView, Model, ModelContext,
- ParentElement, PathPromptOptions, Point, PromptLevel, Render, Size, Styled, Subscription, Task,
- View, ViewContext, VisualContext, WeakModel, WeakView, WindowBounds, WindowContext,
+ actions, div, point, size, Action, AnyModel, AnyView, AnyWeakView, AnyWindowHandle, AppContext,
+ AsyncAppContext, AsyncWindowContext, Bounds, Context, Div, Entity, EntityId, EventEmitter,
+ FocusHandle, FocusableView, GlobalPixels, InteractiveElement, KeyContext, ManagedView, Model,
+ ModelContext, ParentElement, PathPromptOptions, Point, PromptLevel, Render, Size, Styled,
+ Subscription, Task, View, ViewContext, VisualContext, WeakView, WindowBounds, WindowContext,
WindowHandle, WindowOptions,
};
use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem};
@@ -52,6 +53,7 @@ use postage::stream::Stream;
use project::{Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId};
use serde::Deserialize;
use settings::Settings;
+use shared_screen::SharedScreen;
use status_bar::StatusBar;
pub use status_bar::StatusItemView;
use std::{
@@ -209,6 +211,7 @@ pub fn init_settings(cx: &mut AppContext) {
pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
init_settings(cx);
notifications::init(cx);
+
// cx.add_global_action({
// let app_state = Arc::downgrade(&app_state);
// move |_: &Open, cx: &mut AppContext| {
@@ -302,7 +305,6 @@ pub struct AppState {
pub user_store: Model<UserStore>,
pub workspace_store: Model<WorkspaceStore>,
pub fs: Arc<dyn fs::Fs>,
- pub call_factory: CallFactory,
pub build_window_options:
fn(Option<WindowBounds>, Option<Uuid>, &mut AppContext) -> WindowOptions,
pub node_runtime: Arc<dyn NodeRuntime>,
@@ -321,69 +323,6 @@ struct Follower {
peer_id: PeerId,
}
-#[cfg(any(test, feature = "test-support"))]
-pub struct TestCallHandler;
-
-#[cfg(any(test, feature = "test-support"))]
-impl CallHandler for TestCallHandler {
- fn peer_state(
- &mut self,
- id: PeerId,
- project: &Model<Project>,
- cx: &mut ViewContext<Workspace>,
- ) -> Option<(bool, bool)> {
- None
- }
-
- fn shared_screen_for_peer(
- &self,
- peer_id: PeerId,
- pane: &View<Pane>,
- cx: &mut ViewContext<Workspace>,
- ) -> Option<Box<dyn ItemHandle>> {
- None
- }
-
- fn room_id(&self, cx: &AppContext) -> Option<u64> {
- None
- }
-
- fn hang_up(&self, cx: &mut AppContext) -> Task<Result<()>> {
- Task::ready(Err(anyhow!("TestCallHandler should not be hanging up")))
- }
-
- fn active_project(&self, cx: &AppContext) -> Option<WeakModel<Project>> {
- None
- }
-
- fn invite(
- &mut self,
- called_user_id: u64,
- initial_project: Option<Model<Project>>,
- cx: &mut AppContext,
- ) -> Task<Result<()>> {
- unimplemented!()
- }
-
- fn remote_participants(&self, cx: &AppContext) -> Option<Vec<(Arc<User>, PeerId)>> {
- None
- }
-
- fn is_muted(&self, cx: &AppContext) -> Option<bool> {
- None
- }
-
- fn toggle_mute(&self, cx: &mut AppContext) {}
-
- fn toggle_screen_share(&self, cx: &mut AppContext) {}
-
- fn toggle_deafen(&self, cx: &mut AppContext) {}
-
- fn is_deafened(&self, cx: &AppContext) -> Option<bool> {
- None
- }
-}
-
impl AppState {
#[cfg(any(test, feature = "test-support"))]
pub fn test(cx: &mut AppContext) -> Arc<Self> {
@@ -414,7 +353,6 @@ impl AppState {
workspace_store,
node_runtime: FakeNodeRuntime::new(),
build_window_options: |_, _, _| Default::default(),
- call_factory: |_| Box::new(TestCallHandler),
})
}
}
@@ -471,40 +409,6 @@ pub enum Event {
WorkspaceCreated(WeakView<Workspace>),
}
-#[async_trait(?Send)]
-pub trait CallHandler {
- fn peer_state(
- &mut self,
- id: PeerId,
- project: &Model<Project>,
- cx: &mut ViewContext<Workspace>,
- ) -> Option<(bool, bool)>;
- fn shared_screen_for_peer(
- &self,
- peer_id: PeerId,
- pane: &View<Pane>,
- cx: &mut ViewContext<Workspace>,
- ) -> Option<Box<dyn ItemHandle>>;
- fn room_id(&self, cx: &AppContext) -> Option<u64>;
- fn is_in_room(&self, cx: &mut ViewContext<Workspace>) -> bool {
- self.room_id(cx).is_some()
- }
- fn hang_up(&self, cx: &mut AppContext) -> Task<Result<()>>;
- fn active_project(&self, cx: &AppContext) -> Option<WeakModel<Project>>;
- fn invite(
- &mut self,
- called_user_id: u64,
- initial_project: Option<Model<Project>>,
- cx: &mut AppContext,
- ) -> Task<Result<()>>;
- fn remote_participants(&self, cx: &AppContext) -> Option<Vec<(Arc<User>, PeerId)>>;
- fn is_muted(&self, cx: &AppContext) -> Option<bool>;
- fn is_deafened(&self, cx: &AppContext) -> Option<bool>;
- fn toggle_mute(&self, cx: &mut AppContext);
- fn toggle_deafen(&self, cx: &mut AppContext);
- fn toggle_screen_share(&self, cx: &mut AppContext);
-}
-
pub struct Workspace {
window_self: WindowHandle<Self>,
weak_self: WeakView<Self>,
@@ -525,10 +429,10 @@ pub struct Workspace {
titlebar_item: Option<AnyView>,
notifications: Vec<(TypeId, usize, Box<dyn NotificationHandle>)>,
project: Model<Project>,
- call_handler: Box<dyn CallHandler>,
follower_states: HashMap<View<Pane>, FollowerState>,
last_leaders_by_pane: HashMap<WeakView<Pane>, PeerId>,
window_edited: bool,
+ active_call: Option<(Model<ActiveCall>, Vec<Subscription>)>,
leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>,
database_id: WorkspaceId,
app_state: Arc<AppState>,
@@ -556,7 +460,6 @@ struct FollowerState {
enum WorkspaceBounds {}
-type CallFactory = fn(&mut ViewContext<Workspace>) -> Box<dyn CallHandler>;
impl Workspace {
pub fn new(
workspace_id: WorkspaceId,
@@ -648,19 +551,9 @@ impl Workspace {
mpsc::unbounded::<(PeerId, proto::UpdateFollowers)>();
let _apply_leader_updates = cx.spawn(|this, mut cx| async move {
while let Some((leader_id, update)) = leader_updates_rx.next().await {
- let mut cx2 = cx.clone();
- let t = this.clone();
-
- Workspace::process_leader_update(&this, leader_id, update, &mut cx)
+ Self::process_leader_update(&this, leader_id, update, &mut cx)
.await
.log_err();
-
- // this.update(&mut cx, |this, cxx| {
- // this.call_handler
- // .process_leader_update(leader_id, update, cx2)
- // })?
- // .await
- // .log_err();
}
Ok(())
@@ -693,6 +586,14 @@ impl Workspace {
// drag_and_drop.register_container(weak_handle.clone());
// });
+ let mut active_call = None;
+ if cx.has_global::<Model<ActiveCall>>() {
+ let call = cx.global::<Model<ActiveCall>>().clone();
+ let mut subscriptions = Vec::new();
+ subscriptions.push(cx.subscribe(&call, Self::on_active_call_event));
+ active_call = Some((call, subscriptions));
+ }
+
let subscriptions = vec![
cx.observe_window_activation(Self::on_window_activation_changed),
cx.observe_window_bounds(move |_, cx| {
@@ -769,8 +670,7 @@ impl Workspace {
follower_states: Default::default(),
last_leaders_by_pane: Default::default(),
window_edited: false,
-
- call_handler: (app_state.call_factory)(cx),
+ active_call,
database_id: workspace_id,
app_state,
_observe_current_user,
@@ -1217,7 +1117,7 @@ impl Workspace {
cx: &mut ViewContext<Self>,
) -> Task<Result<bool>> {
//todo!(saveing)
-
+ let active_call = self.active_call().cloned();
let window = cx.window_handle();
cx.spawn(|this, mut cx| async move {
@@ -1228,27 +1128,27 @@ impl Workspace {
.count()
})?;
- if !quitting
- && workspace_count == 1
- && this
- .update(&mut cx, |this, cx| this.call_handler.is_in_room(cx))
- .log_err()
- .unwrap_or_default()
- {
- let answer = window.update(&mut cx, |_, cx| {
- cx.prompt(
- PromptLevel::Warning,
- "Do you want to leave the current call?",
- &["Close window and hang up", "Cancel"],
- )
- })?;
+ if let Some(active_call) = active_call {
+ if !quitting
+ && workspace_count == 1
+ && active_call.read_with(&cx, |call, _| call.room().is_some())?
+ {
+ let answer = window.update(&mut cx, |_, cx| {
+ cx.prompt(
+ PromptLevel::Warning,
+ "Do you want to leave the current call?",
+ &["Close window and hang up", "Cancel"],
+ )
+ })?;
- if answer.await.log_err() == Some(1) {
- return anyhow::Ok(false);
- } else {
- this.update(&mut cx, |this, cx| this.call_handler.hang_up(cx))?
- .await
- .log_err();
+ if answer.await.log_err() == Some(1) {
+ return anyhow::Ok(false);
+ } else {
+ active_call
+ .update(&mut cx, |call, cx| call.hang_up(cx))?
+ .await
+ .log_err();
+ }
}
}
@@ -2032,7 +1932,7 @@ impl Workspace {
pub fn open_shared_screen(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
if let Some(shared_screen) = self.shared_screen_for_peer(peer_id, &self.active_pane, cx) {
self.active_pane.update(cx, |pane, cx| {
- pane.add_item(shared_screen, false, true, None, cx)
+ pane.add_item(Box::new(shared_screen), false, true, None, cx)
});
}
}
@@ -2370,60 +2270,60 @@ impl Workspace {
cx.notify();
}
- // fn start_following(
- // &mut self,
- // leader_id: PeerId,
- // cx: &mut ViewContext<Self>,
- // ) -> Option<Task<Result<()>>> {
- // let pane = self.active_pane().clone();
-
- // self.last_leaders_by_pane
- // .insert(pane.downgrade(), leader_id);
- // self.unfollow(&pane, cx);
- // self.follower_states.insert(
- // pane.clone(),
- // FollowerState {
- // leader_id,
- // active_view_id: None,
- // items_by_leader_view_id: Default::default(),
- // },
- // );
- // cx.notify();
-
- // let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
- // let project_id = self.project.read(cx).remote_id();
- // let request = self.app_state.client.request(proto::Follow {
- // room_id,
- // project_id,
- // leader_id: Some(leader_id),
- // });
+ fn start_following(
+ &mut self,
+ leader_id: PeerId,
+ cx: &mut ViewContext<Self>,
+ ) -> Option<Task<Result<()>>> {
+ let pane = self.active_pane().clone();
+
+ self.last_leaders_by_pane
+ .insert(pane.downgrade(), leader_id);
+ self.unfollow(&pane, cx);
+ self.follower_states.insert(
+ pane.clone(),
+ FollowerState {
+ leader_id,
+ active_view_id: None,
+ items_by_leader_view_id: Default::default(),
+ },
+ );
+ cx.notify();
- // Some(cx.spawn(|this, mut cx| async move {
- // let response = request.await?;
- // this.update(&mut cx, |this, _| {
- // let state = this
- // .follower_states
- // .get_mut(&pane)
- // .ok_or_else(|| anyhow!("following interrupted"))?;
- // state.active_view_id = if let Some(active_view_id) = response.active_view_id {
- // Some(ViewId::from_proto(active_view_id)?)
- // } else {
- // None
- // };
- // Ok::<_, anyhow::Error>(())
- // })??;
- // Self::add_views_from_leader(
- // this.clone(),
- // leader_id,
- // vec![pane],
- // response.views,
- // &mut cx,
- // )
- // .await?;
- // this.update(&mut cx, |this, cx| this.leader_updated(leader_id, cx))?;
- // Ok(())
- // }))
- // }
+ let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
+ let project_id = self.project.read(cx).remote_id();
+ let request = self.app_state.client.request(proto::Follow {
+ room_id,
+ project_id,
+ leader_id: Some(leader_id),
+ });
+
+ Some(cx.spawn(|this, mut cx| async move {
+ let response = request.await?;
+ this.update(&mut cx, |this, _| {
+ let state = this
+ .follower_states
+ .get_mut(&pane)
+ .ok_or_else(|| anyhow!("following interrupted"))?;
+ state.active_view_id = if let Some(active_view_id) = response.active_view_id {
+ Some(ViewId::from_proto(active_view_id)?)
+ } else {
+ None
+ };
+ Ok::<_, anyhow::Error>(())
+ })??;
+ Self::add_views_from_leader(
+ this.clone(),
+ leader_id,
+ vec![pane],
+ response.views,
+ &mut cx,
+ )
+ .await?;
+ this.update(&mut cx, |this, cx| this.leader_updated(leader_id, cx))?;
+ Ok(())
+ }))
+ }
// pub fn follow_next_collaborator(
// &mut self,
@@ -2462,67 +2362,67 @@ impl Workspace {
// self.follow(leader_id, cx)
// }
- // pub fn follow(
- // &mut self,
- // leader_id: PeerId,
- // cx: &mut ViewContext<Self>,
- // ) -> Option<Task<Result<()>>> {
- // let room = ActiveCall::global(cx).read(cx).room()?.read(cx);
- // let project = self.project.read(cx);
+ pub fn follow(
+ &mut self,
+ leader_id: PeerId,
+ cx: &mut ViewContext<Self>,
+ ) -> Option<Task<Result<()>>> {
+ let room = ActiveCall::global(cx).read(cx).room()?.read(cx);
+ let project = self.project.read(cx);
- // let Some(remote_participant) = room.remote_participant_for_peer_id(leader_id) else {
- // return None;
- // };
+ let Some(remote_participant) = room.remote_participant_for_peer_id(leader_id) else {
+ return None;
+ };
- // let other_project_id = match remote_participant.location {
- // call::ParticipantLocation::External => None,
- // call::ParticipantLocation::UnsharedProject => None,
- // call::ParticipantLocation::SharedProject { project_id } => {
- // if Some(project_id) == project.remote_id() {
- // None
- // } else {
- // Some(project_id)
- // }
- // }
- // };
+ let other_project_id = match remote_participant.location {
+ call::ParticipantLocation::External => None,
+ call::ParticipantLocation::UnsharedProject => None,
+ call::ParticipantLocation::SharedProject { project_id } => {
+ if Some(project_id) == project.remote_id() {
+ None
+ } else {
+ Some(project_id)
+ }
+ }
+ };
- // // if they are active in another project, follow there.
- // if let Some(project_id) = other_project_id {
- // let app_state = self.app_state.clone();
- // return Some(crate::join_remote_project(
- // project_id,
- // remote_participant.user.id,
- // app_state,
- // cx,
- // ));
- // }
+ // if they are active in another project, follow there.
+ if let Some(project_id) = other_project_id {
+ let app_state = self.app_state.clone();
+ return Some(crate::join_remote_project(
+ project_id,
+ remote_participant.user.id,
+ app_state,
+ cx,
+ ));
+ }
- // // if you're already following, find the right pane and focus it.
- // for (pane, state) in &self.follower_states {
- // if leader_id == state.leader_id {
- // cx.focus(pane);
- // return None;
- // }
- // }
+ // if you're already following, find the right pane and focus it.
+ for (pane, state) in &self.follower_states {
+ if leader_id == state.leader_id {
+ cx.focus_view(pane);
+ return None;
+ }
+ }
- // // Otherwise, follow.
- // self.start_following(leader_id, cx)
- // }
+ // Otherwise, follow.
+ self.start_following(leader_id, cx)
+ }
pub fn unfollow(&mut self, pane: &View<Pane>, cx: &mut ViewContext<Self>) -> Option<PeerId> {
- let follower_states = &mut self.follower_states;
- let state = follower_states.remove(pane)?;
+ let state = self.follower_states.remove(pane)?;
let leader_id = state.leader_id;
for (_, item) in state.items_by_leader_view_id {
item.set_leader_peer_id(None, cx);
}
- if follower_states
+ if self
+ .follower_states
.values()
.all(|state| state.leader_id != state.leader_id)
{
let project_id = self.project.read(cx).remote_id();
- let room_id = self.call_handler.room_id(cx)?;
+ let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
self.app_state
.client
.send(proto::Unfollow {
@@ -2657,57 +2557,55 @@ impl Workspace {
}
}
- // // RPC handlers
+ // RPC handlers
fn handle_follow(
&mut self,
- _follower_project_id: Option<u64>,
- _cx: &mut ViewContext<Self>,
+ follower_project_id: Option<u64>,
+ cx: &mut ViewContext<Self>,
) -> proto::FollowResponse {
- todo!()
+ let client = &self.app_state.client;
+ let project_id = self.project.read(cx).remote_id();
- // let client = &self.app_state.client;
- // let project_id = self.project.read(cx).remote_id();
+ let active_view_id = self.active_item(cx).and_then(|i| {
+ Some(
+ i.to_followable_item_handle(cx)?
+ .remote_id(client, cx)?
+ .to_proto(),
+ )
+ });
- // let active_view_id = self.active_item(cx).and_then(|i| {
- // Some(
- // i.to_followable_item_handle(cx)?
- // .remote_id(client, cx)?
- // .to_proto(),
- // )
- // });
+ cx.notify();
- // cx.notify();
-
- // self.last_active_view_id = active_view_id.clone();
- // proto::FollowResponse {
- // active_view_id,
- // views: self
- // .panes()
- // .iter()
- // .flat_map(|pane| {
- // let leader_id = self.leader_for_pane(pane);
- // pane.read(cx).items().filter_map({
- // let cx = &cx;
- // move |item| {
- // let item = item.to_followable_item_handle(cx)?;
- // if (project_id.is_none() || project_id != follower_project_id)
- // && item.is_project_item(cx)
- // {
- // return None;
- // }
- // let id = item.remote_id(client, cx)?.to_proto();
- // let variant = item.to_state_proto(cx)?;
- // Some(proto::View {
- // id: Some(id),
- // leader_id,
- // variant: Some(variant),
- // })
- // }
- // })
- // })
- // .collect(),
- // }
+ self.last_active_view_id = active_view_id.clone();
+ proto::FollowResponse {
+ active_view_id,
+ views: self
+ .panes()
+ .iter()
+ .flat_map(|pane| {
+ let leader_id = self.leader_for_pane(pane);
+ pane.read(cx).items().filter_map({
+ let cx = &cx;
+ move |item| {
+ let item = item.to_followable_item_handle(cx)?;
+ if (project_id.is_none() || project_id != follower_project_id)
+ && item.is_project_item(cx)
+ {
+ return None;
+ }
+ let id = item.remote_id(client, cx)?.to_proto();
+ let variant = item.to_state_proto(cx)?;
+ Some(proto::View {
+ id: Some(id),
+ leader_id,
+ variant: Some(variant),
+ })
+ }
+ })
+ })
+ .collect(),
+ }
}
fn handle_update_followers(
@@ -2727,6 +2625,8 @@ impl Workspace {
update: proto::UpdateFollowers,
cx: &mut AsyncWindowContext,
) -> Result<()> {
+ dbg!("process_leader_update", &update);
+
match update.variant.ok_or_else(|| anyhow!("invalid update"))? {
proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
this.update(cx, |this, _| {
@@ -2878,9 +2778,8 @@ impl Workspace {
} else {
None
};
- let room_id = self.call_handler.room_id(cx)?;
self.app_state().workspace_store.update(cx, |store, cx| {
- store.update_followers(project_id, room_id, update, cx)
+ store.update_followers(project_id, update, cx)
})
}
@@ -2888,12 +2787,31 @@ impl Workspace {
self.follower_states.get(pane).map(|state| state.leader_id)
}
- pub fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
+ fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
cx.notify();
- let (leader_in_this_project, leader_in_this_app) =
- self.call_handler.peer_state(leader_id, &self.project, cx)?;
+ let call = self.active_call()?;
+ let room = call.read(cx).room()?.read(cx);
+ let participant = room.remote_participant_for_peer_id(leader_id)?;
let mut items_to_activate = Vec::new();
+
+ let leader_in_this_app;
+ let leader_in_this_project;
+ match participant.location {
+ call::ParticipantLocation::SharedProject { project_id } => {
+ leader_in_this_app = true;
+ leader_in_this_project = Some(project_id) == self.project.read(cx).remote_id();
+ }
+ call::ParticipantLocation::UnsharedProject => {
+ leader_in_this_app = true;
+ leader_in_this_project = false;
+ }
+ call::ParticipantLocation::External => {
+ leader_in_this_app = false;
+ leader_in_this_project = false;
+ }
+ };
+
for (pane, state) in &self.follower_states {
if state.leader_id != leader_id {
continue;
@@ -2914,7 +2832,7 @@ impl Workspace {
}
if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) {
- items_to_activate.push((pane.clone(), shared_screen));
+ items_to_activate.push((pane.clone(), Box::new(shared_screen)));
}
}
@@ -2923,8 +2841,8 @@ impl Workspace {
if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) {
pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx));
} else {
- pane.update(cx, |pane, mut cx| {
- pane.add_item(item.boxed_clone(), false, false, None, &mut cx)
+ pane.update(cx, |pane, cx| {
+ pane.add_item(item.boxed_clone(), false, false, None, cx)
});
}
@@ -2941,21 +2859,20 @@ impl Workspace {
peer_id: PeerId,
pane: &View<Pane>,
cx: &mut ViewContext<Self>,
- ) -> Option<Box<dyn ItemHandle>> {
- self.call_handler.shared_screen_for_peer(peer_id, pane, cx)
- // let call = self.active_call()?;
- // let room = call.read(cx).room()?.read(cx);
- // let participant = room.remote_participant_for_peer_id(peer_id)?;
- // let track = participant.video_tracks.values().next()?.clone();
- // let user = participant.user.clone();
-
- // for item in pane.read(cx).items_of_type::<SharedScreen>() {
- // if item.read(cx).peer_id == peer_id {
- // return Some(item);
- // }
- // }
+ ) -> Option<View<SharedScreen>> {
+ let call = self.active_call()?;
+ let room = call.read(cx).room()?.read(cx);
+ let participant = room.remote_participant_for_peer_id(peer_id)?;
+ let track = participant.video_tracks.values().next()?.clone();
+ let user = participant.user.clone();
+
+ for item in pane.read(cx).items_of_type::<SharedScreen>() {
+ if item.read(cx).peer_id == peer_id {
+ return Some(item);
+ }
+ }
- // Some(cx.build_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx)))
+ Some(cx.build_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx)))
}
pub fn on_window_activation_changed(&mut self, cx: &mut ViewContext<Self>) {
@@ -2984,6 +2901,25 @@ impl Workspace {
}
}
+ fn active_call(&self) -> Option<&Model<ActiveCall>> {
+ self.active_call.as_ref().map(|(call, _)| call)
+ }
+
+ fn on_active_call_event(
+ &mut self,
+ _: Model<ActiveCall>,
+ event: &call::room::Event,
+ cx: &mut ViewContext<Self>,
+ ) {
+ match event {
+ call::room::Event::ParticipantLocationChanged { participant_id }
+ | call::room::Event::RemoteVideoTracksChanged { participant_id } => {
+ self.leader_updated(*participant_id, cx);
+ }
+ _ => {}
+ }
+ }
+
pub fn database_id(&self) -> WorkspaceId {
self.database_id
}
@@ -3393,7 +3329,6 @@ impl Workspace {
fs: project.read(cx).fs().clone(),
build_window_options: |_, _, _| Default::default(),
node_runtime: FakeNodeRuntime::new(),
- call_factory: |_| Box::new(TestCallHandler),
});
let workspace = Self::new(0, project, app_state, cx);
workspace.active_pane.update(cx, |pane, cx| pane.focus(cx));
@@ -3472,10 +3407,6 @@ impl Workspace {
self.modal_layer
.update(cx, |modal_layer, cx| modal_layer.toggle_modal(cx, build))
}
-
- pub fn call_state(&mut self) -> &mut dyn CallHandler {
- &mut *self.call_handler
- }
}
fn window_bounds_env_override(cx: &AsyncAppContext) -> Option<WindowBounds> {
@@ -3676,6 +3607,7 @@ impl Render for Workspace {
.child(self.center.render(
&self.project,
&self.follower_states,
+ self.active_call(),
&self.active_pane,
self.zoomed.as_ref(),
&self.app_state,
@@ -3830,15 +3762,15 @@ impl Render for Workspace {
// }
impl WorkspaceStore {
- pub fn new(client: Arc<Client>, _cx: &mut ModelContext<Self>) -> Self {
+ pub fn new(client: Arc<Client>, cx: &mut ModelContext<Self>) -> Self {
Self {
workspaces: Default::default(),
followers: Default::default(),
- _subscriptions: vec![],
- // client.add_request_handler(cx.weak_model(), Self::handle_follow),
- // client.add_message_handler(cx.weak_model(), Self::handle_unfollow),
- // client.add_message_handler(cx.weak_model(), Self::handle_update_followers),
- // ],
+ _subscriptions: vec![
+ client.add_request_handler(cx.weak_model(), Self::handle_follow),
+ client.add_message_handler(cx.weak_model(), Self::handle_unfollow),
+ client.add_message_handler(cx.weak_model(), Self::handle_update_followers),
+ ],
client,
}
}
@@ -3846,10 +3778,14 @@ impl WorkspaceStore {
pub fn update_followers(
&self,
project_id: Option<u64>,
- room_id: u64,
update: proto::update_followers::Variant,
cx: &AppContext,
) -> Option<()> {
+ if !cx.has_global::<Model<ActiveCall>>() {
+ return None;
+ }
+
+ let room_id = ActiveCall::global(cx).read(cx).room()?.read(cx).id();
let follower_ids: Vec<_> = self
.followers
.iter()
@@ -3885,17 +3821,9 @@ impl WorkspaceStore {
project_id: envelope.payload.project_id,
peer_id: envelope.original_sender_id()?,
};
+ let active_project = ActiveCall::global(cx).read(cx).location().cloned();
+
let mut response = proto::FollowResponse::default();
- let active_project = this
- .workspaces
- .iter()
- .next()
- .and_then(|workspace| {
- workspace
- .read_with(cx, |this, cx| this.call_handler.active_project(cx))
- .log_err()
- })
- .flatten();
for workspace in &this.workspaces {
workspace
.update(cx, |workspace, cx| {
@@ -3947,11 +3875,13 @@ impl WorkspaceStore {
this: Model<Self>,
envelope: TypedEnvelope<proto::UpdateFollowers>,
_: Arc<Client>,
- mut cx: AsyncWindowContext,
+ mut cx: AsyncAppContext,
) -> Result<()> {
let leader_id = envelope.original_sender_id()?;
let update = envelope.payload;
+ dbg!("handle_upate_followers");
+
this.update(&mut cx, |this, cx| {
for workspace in &this.workspaces {
workspace.update(cx, |workspace, cx| {
@@ -4048,187 +3978,184 @@ pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
DB.last_workspace().await.log_err().flatten()
}
-// async fn join_channel_internal(
-// channel_id: u64,
-// app_state: &Arc<AppState>,
-// requesting_window: Option<WindowHandle<Workspace>>,
-// active_call: &ModelHandle<ActiveCall>,
-// cx: &mut AsyncAppContext,
-// ) -> Result<bool> {
-// let (should_prompt, open_room) = active_call.read_with(cx, |active_call, cx| {
-// let Some(room) = active_call.room().map(|room| room.read(cx)) else {
-// return (false, None);
-// };
-
-// let already_in_channel = room.channel_id() == Some(channel_id);
-// let should_prompt = room.is_sharing_project()
-// && room.remote_participants().len() > 0
-// && !already_in_channel;
-// let open_room = if already_in_channel {
-// active_call.room().cloned()
-// } else {
-// None
-// };
-// (should_prompt, open_room)
-// });
-
-// if let Some(room) = open_room {
-// let task = room.update(cx, |room, cx| {
-// if let Some((project, host)) = room.most_active_project(cx) {
-// return Some(join_remote_project(project, host, app_state.clone(), cx));
-// }
-
-// None
-// });
-// if let Some(task) = task {
-// task.await?;
-// }
-// return anyhow::Ok(true);
-// }
+async fn join_channel_internal(
+ channel_id: u64,
+ app_state: &Arc<AppState>,
+ requesting_window: Option<WindowHandle<Workspace>>,
+ active_call: &Model<ActiveCall>,
+ cx: &mut AsyncAppContext,
+) -> Result<bool> {
+ let (should_prompt, open_room) = active_call.read_with(cx, |active_call, cx| {
+ let Some(room) = active_call.room().map(|room| room.read(cx)) else {
+ return (false, None);
+ };
-// if should_prompt {
-// if let Some(workspace) = requesting_window {
-// if let Some(window) = workspace.update(cx, |cx| cx.window()) {
-// let answer = window.prompt(
-// PromptLevel::Warning,
-// "Leaving this call will unshare your current project.\nDo you want to switch channels?",
-// &["Yes, Join Channel", "Cancel"],
-// cx,
-// );
-
-// if let Some(mut answer) = answer {
-// if answer.next().await == Some(1) {
-// return Ok(false);
-// }
-// }
-// } else {
-// return Ok(false); // unreachable!() hopefully
-// }
-// } else {
-// return Ok(false); // unreachable!() hopefully
-// }
-// }
+ let already_in_channel = room.channel_id() == Some(channel_id);
+ let should_prompt = room.is_sharing_project()
+ && room.remote_participants().len() > 0
+ && !already_in_channel;
+ let open_room = if already_in_channel {
+ active_call.room().cloned()
+ } else {
+ None
+ };
+ (should_prompt, open_room)
+ })?;
-// let client = cx.read(|cx| active_call.read(cx).client());
-
-// let mut client_status = client.status();
-
-// // this loop will terminate within client::CONNECTION_TIMEOUT seconds.
-// 'outer: loop {
-// let Some(status) = client_status.recv().await else {
-// return Err(anyhow!("error connecting"));
-// };
-
-// match status {
-// Status::Connecting
-// | Status::Authenticating
-// | Status::Reconnecting
-// | Status::Reauthenticating => continue,
-// Status::Connected { .. } => break 'outer,
-// Status::SignedOut => return Err(anyhow!("not signed in")),
-// Status::UpgradeRequired => return Err(anyhow!("zed is out of date")),
-// Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => {
-// return Err(anyhow!("zed is offline"))
-// }
-// }
-// }
+ if let Some(room) = open_room {
+ let task = room.update(cx, |room, cx| {
+ if let Some((project, host)) = room.most_active_project(cx) {
+ return Some(join_remote_project(project, host, app_state.clone(), cx));
+ }
-// let room = active_call
-// .update(cx, |active_call, cx| {
-// active_call.join_channel(channel_id, cx)
-// })
-// .await?;
-
-// room.update(cx, |room, _| room.room_update_completed())
-// .await;
-
-// let task = room.update(cx, |room, cx| {
-// if let Some((project, host)) = room.most_active_project(cx) {
-// return Some(join_remote_project(project, host, app_state.clone(), cx));
-// }
-
-// None
-// });
-// if let Some(task) = task {
-// task.await?;
-// return anyhow::Ok(true);
-// }
-// anyhow::Ok(false)
-// }
+ None
+ })?;
+ if let Some(task) = task {
+ task.await?;
+ }
+ return anyhow::Ok(true);
+ }
-// pub fn join_channel(
-// channel_id: u64,
-// app_state: Arc<AppState>,
-// requesting_window: Option<WindowHandle<Workspace>>,
-// cx: &mut AppContext,
-// ) -> Task<Result<()>> {
-// let active_call = ActiveCall::global(cx);
-// cx.spawn(|mut cx| async move {
-// let result = join_channel_internal(
-// channel_id,
-// &app_state,
-// requesting_window,
-// &active_call,
-// &mut cx,
-// )
-// .await;
-
-// // join channel succeeded, and opened a window
-// if matches!(result, Ok(true)) {
-// return anyhow::Ok(());
-// }
-
-// if requesting_window.is_some() {
-// return anyhow::Ok(());
-// }
-
-// // find an existing workspace to focus and show call controls
-// let mut active_window = activate_any_workspace_window(&mut cx);
-// if active_window.is_none() {
-// // no open workspaces, make one to show the error in (blergh)
-// cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), requesting_window, cx))
-// .await;
-// }
-
-// active_window = activate_any_workspace_window(&mut cx);
-// if active_window.is_none() {
-// return result.map(|_| ()); // unreachable!() assuming new_local always opens a window
-// }
-
-// if let Err(err) = result {
-// let prompt = active_window.unwrap().prompt(
-// PromptLevel::Critical,
-// &format!("Failed to join channel: {}", err),
-// &["Ok"],
-// &mut cx,
-// );
-// if let Some(mut prompt) = prompt {
-// prompt.next().await;
-// } else {
-// return Err(err);
-// }
-// }
-
-// // return ok, we showed the error to the user.
-// return anyhow::Ok(());
-// })
-// }
+ if should_prompt {
+ if let Some(workspace) = requesting_window {
+ let answer = workspace.update(cx, |_, cx| {
+ cx.prompt(
+ PromptLevel::Warning,
+ "Leaving this call will unshare your current project.\nDo you want to switch channels?",
+ &["Yes, Join Channel", "Cancel"],
+ )
+ })?.await;
-// pub fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option<AnyWindowHandle> {
-// for window in cx.windows() {
-// let found = window.update(cx, |cx| {
-// let is_workspace = cx.root_view().clone().downcast::<Workspace>().is_some();
-// if is_workspace {
-// cx.activate_window();
-// }
-// is_workspace
-// });
-// if found == Some(true) {
-// return Some(window);
-// }
-// }
-// None
-// }
+ if answer == Ok(1) {
+ return Ok(false);
+ }
+ } else {
+ return Ok(false); // unreachable!() hopefully
+ }
+ }
+
+ let client = cx.update(|cx| active_call.read(cx).client())?;
+
+ let mut client_status = client.status();
+
+ // this loop will terminate within client::CONNECTION_TIMEOUT seconds.
+ 'outer: loop {
+ let Some(status) = client_status.recv().await else {
+ return Err(anyhow!("error connecting"));
+ };
+
+ match status {
+ Status::Connecting
+ | Status::Authenticating
+ | Status::Reconnecting
+ | Status::Reauthenticating => continue,
+ Status::Connected { .. } => break 'outer,
+ Status::SignedOut => return Err(anyhow!("not signed in")),
+ Status::UpgradeRequired => return Err(anyhow!("zed is out of date")),
+ Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => {
+ return Err(anyhow!("zed is offline"))
+ }
+ }
+ }
+
+ let room = active_call
+ .update(cx, |active_call, cx| {
+ active_call.join_channel(channel_id, cx)
+ })?
+ .await?;
+
+ let Some(room) = room else {
+ return anyhow::Ok(true);
+ };
+
+ room.update(cx, |room, _| room.room_update_completed())?
+ .await;
+
+ let task = room.update(cx, |room, cx| {
+ if let Some((project, host)) = room.most_active_project(cx) {
+ return Some(join_remote_project(project, host, app_state.clone(), cx));
+ }
+
+ None
+ })?;
+ if let Some(task) = task {
+ task.await?;
+ return anyhow::Ok(true);
+ }
+ anyhow::Ok(false)
+}
+
+pub fn join_channel(
+ channel_id: u64,
+ app_state: Arc<AppState>,
+ requesting_window: Option<WindowHandle<Workspace>>,
+ cx: &mut AppContext,
+) -> Task<Result<()>> {
+ let active_call = ActiveCall::global(cx);
+ cx.spawn(|mut cx| async move {
+ let result = join_channel_internal(
+ channel_id,
+ &app_state,
+ requesting_window,
+ &active_call,
+ &mut cx,
+ )
+ .await;
+
+ // join channel succeeded, and opened a window
+ if matches!(result, Ok(true)) {
+ return anyhow::Ok(());
+ }
+
+ if requesting_window.is_some() {
+ return anyhow::Ok(());
+ }
+
+ // find an existing workspace to focus and show call controls
+ let mut active_window = activate_any_workspace_window(&mut cx);
+ if active_window.is_none() {
+ // no open workspaces, make one to show the error in (blergh)
+ cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), requesting_window, cx))?
+ .await?;
+ }
+
+ active_window = activate_any_workspace_window(&mut cx);
+ let Some(active_window) = active_window else {
+ return anyhow::Ok(());
+ };
+
+ if let Err(err) = result {
+ active_window
+ .update(&mut cx, |_, cx| {
+ cx.prompt(
+ PromptLevel::Critical,
+ &format!("Failed to join channel: {}", err),
+ &["Ok"],
+ )
+ })?
+ .await
+ .ok();
+ }
+
+ // return ok, we showed the error to the user.
+ return anyhow::Ok(());
+ })
+}
+
+pub fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option<AnyWindowHandle> {
+ cx.update(|cx| {
+ for window in cx.windows() {
+ let is_workspace = window.downcast::<Workspace>().is_some();
+ if is_workspace {
+ window.update(cx, |_, cx| cx.activate_window()).ok();
+ return Some(window);
+ }
+ }
+ None
+ })
+ .ok()
+ .flatten()
+}
#[allow(clippy::type_complexity)]
pub fn open_paths(
@@ -191,7 +191,6 @@ fn main() {
user_store: user_store.clone(),
fs,
build_window_options,
- call_factory: call::Call::new,
workspace_store,
node_runtime,
});