Enable more collab UI features (#3496)
* Current Call section of the collab panel * Improve the collab titlebar * Add basic UI for following Following only partially works, but the UI for following is now in place.
This commit is contained in:
commit
ae6ddceb67
22 changed files with 1473 additions and 1781 deletions
4
Cargo.lock
generated
4
Cargo.lock
generated
|
@ -1222,7 +1222,6 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-broadcast",
|
"async-broadcast",
|
||||||
"async-trait",
|
|
||||||
"audio2",
|
"audio2",
|
||||||
"client2",
|
"client2",
|
||||||
"collections",
|
"collections",
|
||||||
|
@ -1242,9 +1241,7 @@ dependencies = [
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"settings2",
|
"settings2",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"ui2",
|
|
||||||
"util",
|
"util",
|
||||||
"workspace2",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -11534,7 +11531,6 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-recursion 1.0.5",
|
"async-recursion 1.0.5",
|
||||||
"async-trait",
|
|
||||||
"bincode",
|
"bincode",
|
||||||
"call2",
|
"call2",
|
||||||
"client2",
|
"client2",
|
||||||
|
|
|
@ -31,9 +31,7 @@ media = { path = "../media" }
|
||||||
project = { package = "project2", path = "../project2" }
|
project = { package = "project2", path = "../project2" }
|
||||||
settings = { package = "settings2", path = "../settings2" }
|
settings = { package = "settings2", path = "../settings2" }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
ui = {package = "ui2", path = "../ui2"}
|
|
||||||
workspace = {package = "workspace2", path = "../workspace2"}
|
|
||||||
async-trait.workspace = true
|
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
async-broadcast = "0.4"
|
async-broadcast = "0.4"
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
|
|
|
@ -1,32 +1,25 @@
|
||||||
pub mod call_settings;
|
pub mod call_settings;
|
||||||
pub mod participant;
|
pub mod participant;
|
||||||
pub mod room;
|
pub mod room;
|
||||||
mod shared_screen;
|
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use async_trait::async_trait;
|
|
||||||
use audio::Audio;
|
use audio::Audio;
|
||||||
use call_settings::CallSettings;
|
use call_settings::CallSettings;
|
||||||
use client::{
|
use client::{proto, Client, TelemetrySettings, TypedEnvelope, User, UserStore, ZED_ALWAYS_ACTIVE};
|
||||||
proto::{self, PeerId},
|
|
||||||
Client, TelemetrySettings, TypedEnvelope, User, UserStore, ZED_ALWAYS_ACTIVE,
|
|
||||||
};
|
|
||||||
use collections::HashSet;
|
use collections::HashSet;
|
||||||
use futures::{channel::oneshot, future::Shared, Future, FutureExt};
|
use futures::{channel::oneshot, future::Shared, Future, FutureExt};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, PromptLevel,
|
AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Subscription, Task,
|
||||||
Subscription, Task, View, ViewContext, VisualContext, WeakModel, WindowHandle,
|
WeakModel,
|
||||||
};
|
};
|
||||||
pub use participant::ParticipantLocation;
|
|
||||||
use postage::watch;
|
use postage::watch;
|
||||||
use project::Project;
|
use project::Project;
|
||||||
use room::Event;
|
use room::Event;
|
||||||
pub use room::Room;
|
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use shared_screen::SharedScreen;
|
|
||||||
use std::sync::Arc;
|
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) {
|
pub fn init(client: Arc<Client>, user_store: Model<UserStore>, cx: &mut AppContext) {
|
||||||
CallSettings::register(cx);
|
CallSettings::register(cx);
|
||||||
|
@ -334,55 +327,12 @@ impl ActiveCall {
|
||||||
pub fn join_channel(
|
pub fn join_channel(
|
||||||
&mut self,
|
&mut self,
|
||||||
channel_id: u64,
|
channel_id: u64,
|
||||||
requesting_window: Option<WindowHandle<Workspace>>,
|
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Task<Result<Option<Model<Room>>>> {
|
) -> Task<Result<Option<Model<Room>>>> {
|
||||||
if let Some(room) = self.room().cloned() {
|
if let Some(room) = self.room().cloned() {
|
||||||
if room.read(cx).channel_id() == Some(channel_id) {
|
if room.read(cx).channel_id() == Some(channel_id) {
|
||||||
return cx.spawn(|_, _| async move {
|
return Task::ready(Ok(Some(room)));
|
||||||
todo!();
|
} else {
|
||||||
// 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() {
|
|
||||||
room.update(cx, |room, cx| room.clear_state(cx));
|
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)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use gpui::TestAppContext;
|
use gpui::TestAppContext;
|
||||||
|
|
|
@ -4,7 +4,7 @@ use client::{proto, User};
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use gpui::WeakModel;
|
use gpui::WeakModel;
|
||||||
pub use live_kit_client::Frame;
|
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 project::Project;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
|
|
@ -364,8 +364,7 @@ async fn test_joining_channel_ancestor_member(
|
||||||
let active_call_b = cx_b.read(ActiveCall::global);
|
let active_call_b = cx_b.read(ActiveCall::global);
|
||||||
|
|
||||||
assert!(active_call_b
|
assert!(active_call_b
|
||||||
.update(cx_b, |active_call, cx| active_call
|
.update(cx_b, |active_call, cx| active_call.join_channel(sub_id, cx))
|
||||||
.join_channel(sub_id, None, cx))
|
|
||||||
.await
|
.await
|
||||||
.is_ok());
|
.is_ok());
|
||||||
}
|
}
|
||||||
|
@ -395,9 +394,7 @@ async fn test_channel_room(
|
||||||
let active_call_b = cx_b.read(ActiveCall::global);
|
let active_call_b = cx_b.read(ActiveCall::global);
|
||||||
|
|
||||||
active_call_a
|
active_call_a
|
||||||
.update(cx_a, |active_call, cx| {
|
.update(cx_a, |active_call, cx| active_call.join_channel(zed_id, cx))
|
||||||
active_call.join_channel(zed_id, None, cx)
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
@ -445,9 +442,7 @@ async fn test_channel_room(
|
||||||
});
|
});
|
||||||
|
|
||||||
active_call_b
|
active_call_b
|
||||||
.update(cx_b, |active_call, cx| {
|
.update(cx_b, |active_call, cx| active_call.join_channel(zed_id, cx))
|
||||||
active_call.join_channel(zed_id, None, cx)
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
@ -564,16 +559,12 @@ async fn test_channel_room(
|
||||||
});
|
});
|
||||||
|
|
||||||
active_call_a
|
active_call_a
|
||||||
.update(cx_a, |active_call, cx| {
|
.update(cx_a, |active_call, cx| active_call.join_channel(zed_id, cx))
|
||||||
active_call.join_channel(zed_id, None, cx)
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
active_call_b
|
active_call_b
|
||||||
.update(cx_b, |active_call, cx| {
|
.update(cx_b, |active_call, cx| active_call.join_channel(zed_id, cx))
|
||||||
active_call.join_channel(zed_id, None, cx)
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.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);
|
let active_call_a = cx_a.read(ActiveCall::global);
|
||||||
|
|
||||||
active_call_a
|
active_call_a
|
||||||
.update(cx_a, |active_call, cx| {
|
.update(cx_a, |active_call, cx| active_call.join_channel(zed_id, cx))
|
||||||
active_call.join_channel(zed_id, None, cx)
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
@ -638,7 +627,7 @@ async fn test_channel_jumping(executor: BackgroundExecutor, cx_a: &mut TestAppCo
|
||||||
|
|
||||||
active_call_a
|
active_call_a
|
||||||
.update(cx_a, |active_call, cx| {
|
.update(cx_a, |active_call, cx| {
|
||||||
active_call.join_channel(rust_id, None, cx)
|
active_call.join_channel(rust_id, cx)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -804,7 +793,7 @@ async fn test_call_from_channel(
|
||||||
let active_call_b = cx_b.read(ActiveCall::global);
|
let active_call_b = cx_b.read(ActiveCall::global);
|
||||||
|
|
||||||
active_call_a
|
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
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
@ -1297,7 +1286,7 @@ async fn test_guest_access(
|
||||||
|
|
||||||
// Non-members should not be allowed to join
|
// Non-members should not be allowed to join
|
||||||
assert!(active_call_b
|
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
|
.await
|
||||||
.is_err());
|
.is_err());
|
||||||
|
|
||||||
|
@ -1319,7 +1308,7 @@ async fn test_guest_access(
|
||||||
|
|
||||||
// Client B joins channel A as a guest
|
// Client B joins channel A as a guest
|
||||||
active_call_b
|
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
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
@ -1352,7 +1341,7 @@ async fn test_guest_access(
|
||||||
assert_channels_list_shape(client_b.channel_store(), cx_b, &[]);
|
assert_channels_list_shape(client_b.channel_store(), cx_b, &[]);
|
||||||
|
|
||||||
active_call_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
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
@ -1383,7 +1372,7 @@ async fn test_invite_access(
|
||||||
|
|
||||||
// should not be allowed to join
|
// should not be allowed to join
|
||||||
assert!(active_call_b
|
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
|
.await
|
||||||
.is_err());
|
.is_err());
|
||||||
|
|
||||||
|
@ -1401,7 +1390,7 @@ async fn test_invite_access(
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
active_call_b
|
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
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|
|
@ -510,10 +510,9 @@ async fn test_joining_channels_and_calling_multiple_users_simultaneously(
|
||||||
|
|
||||||
// Simultaneously join channel 1 and then channel 2
|
// Simultaneously join channel 1 and then channel 2
|
||||||
active_call_a
|
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();
|
.detach();
|
||||||
let join_channel_2 =
|
let join_channel_2 = active_call_a.update(cx_a, |call, cx| call.join_channel(channel_2, cx));
|
||||||
active_call_a.update(cx_a, |call, cx| call.join_channel(channel_2, None, cx));
|
|
||||||
|
|
||||||
join_channel_2.await.unwrap();
|
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)
|
call.invite(client_c.user_id().unwrap(), None, cx)
|
||||||
});
|
});
|
||||||
|
|
||||||
let join_channel =
|
let join_channel = active_call_a.update(cx_a, |call, cx| call.join_channel(channel_1, cx));
|
||||||
active_call_a.update(cx_a, |call, cx| call.join_channel(channel_1, None, cx));
|
|
||||||
|
|
||||||
b_invite.await.unwrap();
|
b_invite.await.unwrap();
|
||||||
c_invite.await.unwrap();
|
c_invite.await.unwrap();
|
||||||
|
@ -569,8 +567,7 @@ async fn test_joining_channels_and_calling_multiple_users_simultaneously(
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Simultaneously join channel 1 and call user B and user C from client A.
|
// Simultaneously join channel 1 and call user B and user C from client A.
|
||||||
let join_channel =
|
let join_channel = active_call_a.update(cx_a, |call, cx| call.join_channel(channel_1, cx));
|
||||||
active_call_a.update(cx_a, |call, cx| call.join_channel(channel_1, None, cx));
|
|
||||||
|
|
||||||
let b_invite = active_call_a.update(cx_a, |call, cx| {
|
let b_invite = active_call_a.update(cx_a, |call, cx| {
|
||||||
call.invite(client_b.user_id().unwrap(), None, cx)
|
call.invite(client_b.user_id().unwrap(), None, cx)
|
||||||
|
|
|
@ -221,7 +221,6 @@ impl TestServer {
|
||||||
fs: fs.clone(),
|
fs: fs.clone(),
|
||||||
build_window_options: |_, _, _| Default::default(),
|
build_window_options: |_, _, _| Default::default(),
|
||||||
node_runtime: FakeNodeRuntime::new(),
|
node_runtime: FakeNodeRuntime::new(),
|
||||||
call_factory: |_| Box::new(workspace::TestCallHandler),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.update(|cx| {
|
cx.update(|cx| {
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -31,9 +31,9 @@ use std::sync::Arc;
|
||||||
use call::ActiveCall;
|
use call::ActiveCall;
|
||||||
use client::{Client, UserStore};
|
use client::{Client, UserStore};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, px, rems, AppContext, Div, Element, InteractiveElement, IntoElement, Model, MouseButton,
|
actions, div, px, rems, AppContext, Div, Element, InteractiveElement, IntoElement, Model,
|
||||||
ParentElement, Render, RenderOnce, Stateful, StatefulInteractiveElement, Styled, Subscription,
|
MouseButton, ParentElement, Render, RenderOnce, Stateful, StatefulInteractiveElement, Styled,
|
||||||
ViewContext, VisualContext, WeakView, WindowBounds,
|
Subscription, ViewContext, VisualContext, WeakView, WindowBounds,
|
||||||
};
|
};
|
||||||
use project::{Project, RepositoryEntry};
|
use project::{Project, RepositoryEntry};
|
||||||
use theme::ActiveTheme;
|
use theme::ActiveTheme;
|
||||||
|
@ -49,6 +49,14 @@ use crate::face_pile::FacePile;
|
||||||
const MAX_PROJECT_NAME_LENGTH: usize = 40;
|
const MAX_PROJECT_NAME_LENGTH: usize = 40;
|
||||||
const MAX_BRANCH_NAME_LENGTH: usize = 40;
|
const MAX_BRANCH_NAME_LENGTH: usize = 40;
|
||||||
|
|
||||||
|
actions!(
|
||||||
|
ShareProject,
|
||||||
|
UnshareProject,
|
||||||
|
ToggleUserMenu,
|
||||||
|
ToggleProjectMenu,
|
||||||
|
SwitchBranch
|
||||||
|
);
|
||||||
|
|
||||||
// actions!(
|
// actions!(
|
||||||
// collab,
|
// collab,
|
||||||
// [
|
// [
|
||||||
|
@ -91,37 +99,23 @@ impl Render for CollabTitlebarItem {
|
||||||
type Element = Stateful<Div>;
|
type Element = Stateful<Div>;
|
||||||
|
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||||
let is_in_room = self
|
let room = ActiveCall::global(cx).read(cx).room();
|
||||||
.workspace
|
let is_in_room = room.is_some();
|
||||||
.update(cx, |this, cx| this.call_state().is_in_room(cx))
|
|
||||||
.unwrap_or_default();
|
|
||||||
let is_shared = is_in_room && self.project.read(cx).is_shared();
|
let is_shared = is_in_room && self.project.read(cx).is_shared();
|
||||||
let current_user = self.user_store.read(cx).current_user();
|
let current_user = self.user_store.read(cx).current_user();
|
||||||
let client = self.client.clone();
|
let client = self.client.clone();
|
||||||
let users = self
|
let remote_participants = room.map(|room| {
|
||||||
.workspace
|
room.read(cx)
|
||||||
.update(cx, |this, cx| this.call_state().remote_participants(cx))
|
.remote_participants()
|
||||||
.log_err()
|
.values()
|
||||||
.flatten();
|
.map(|participant| (participant.user.clone(), participant.peer_id))
|
||||||
let is_muted = self
|
.collect::<Vec<_>>()
|
||||||
.workspace
|
});
|
||||||
.update(cx, |this, cx| this.call_state().is_muted(cx))
|
let is_muted = room.map_or(false, |room| room.read(cx).is_muted(cx));
|
||||||
.log_err()
|
let is_deafened = room
|
||||||
.flatten()
|
.and_then(|room| room.read(cx).is_deafened())
|
||||||
.unwrap_or_default();
|
.unwrap_or(false);
|
||||||
let is_deafened = self
|
let speakers_icon = if is_deafened {
|
||||||
.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()
|
|
||||||
{
|
|
||||||
ui::Icon::AudioOff
|
ui::Icon::AudioOff
|
||||||
} else {
|
} else {
|
||||||
ui::Icon::AudioOn
|
ui::Icon::AudioOn
|
||||||
|
@ -157,7 +151,7 @@ impl Render for CollabTitlebarItem {
|
||||||
.children(self.render_project_branch(cx)),
|
.children(self.render_project_branch(cx)),
|
||||||
)
|
)
|
||||||
.when_some(
|
.when_some(
|
||||||
users.zip(current_user.clone()),
|
remote_participants.zip(current_user.clone()),
|
||||||
|this, (remote_participants, current_user)| {
|
|this, (remote_participants, current_user)| {
|
||||||
let mut pile = FacePile::default();
|
let mut pile = FacePile::default();
|
||||||
pile.extend(
|
pile.extend(
|
||||||
|
@ -168,25 +162,30 @@ impl Render for CollabTitlebarItem {
|
||||||
div().child(Avatar::data(avatar.clone())).into_any_element()
|
div().child(Avatar::data(avatar.clone())).into_any_element()
|
||||||
})
|
})
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.chain(remote_participants.into_iter().flat_map(|(user, peer_id)| {
|
.chain(remote_participants.into_iter().filter_map(
|
||||||
user.avatar.as_ref().map(|avatar| {
|
|(user, peer_id)| {
|
||||||
div()
|
let avatar = user.avatar.as_ref()?;
|
||||||
.child(
|
Some(
|
||||||
Avatar::data(avatar.clone()).into_element().into_any(),
|
div()
|
||||||
)
|
.child(
|
||||||
.on_mouse_down(MouseButton::Left, {
|
Avatar::data(avatar.clone())
|
||||||
let workspace = workspace.clone();
|
.into_element()
|
||||||
move |_, cx| {
|
.into_any(),
|
||||||
workspace
|
)
|
||||||
.update(cx, |this, cx| {
|
.on_mouse_down(MouseButton::Left, {
|
||||||
this.open_shared_screen(peer_id, cx);
|
let workspace = workspace.clone();
|
||||||
})
|
move |_, cx| {
|
||||||
.log_err();
|
workspace
|
||||||
}
|
.update(cx, |this, cx| {
|
||||||
})
|
this.open_shared_screen(peer_id, cx);
|
||||||
.into_any_element()
|
})
|
||||||
})
|
.log_err();
|
||||||
})),
|
}
|
||||||
|
})
|
||||||
|
.into_any_element(),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)),
|
||||||
);
|
);
|
||||||
this.child(pile.render(cx))
|
this.child(pile.render(cx))
|
||||||
},
|
},
|
||||||
|
@ -204,20 +203,24 @@ impl Render for CollabTitlebarItem {
|
||||||
"toggle_sharing",
|
"toggle_sharing",
|
||||||
if is_shared { "Unshare" } else { "Share" },
|
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(
|
.child(
|
||||||
IconButton::new("leave-call", ui::Icon::Exit)
|
IconButton::new("leave-call", ui::Icon::Exit)
|
||||||
.style(ButtonStyle::Subtle)
|
.style(ButtonStyle::Subtle)
|
||||||
.on_click({
|
.on_click(move |_, cx| {
|
||||||
let workspace = workspace.clone();
|
ActiveCall::global(cx)
|
||||||
move |_, cx| {
|
.update(cx, |call, cx| call.hang_up(cx))
|
||||||
workspace
|
.detach_and_log_err(cx);
|
||||||
.update(cx, |this, cx| {
|
|
||||||
this.call_state().hang_up(cx).detach();
|
|
||||||
})
|
|
||||||
.log_err();
|
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -235,15 +238,8 @@ impl Render for CollabTitlebarItem {
|
||||||
)
|
)
|
||||||
.style(ButtonStyle::Subtle)
|
.style(ButtonStyle::Subtle)
|
||||||
.selected(is_muted)
|
.selected(is_muted)
|
||||||
.on_click({
|
.on_click(move |_, cx| {
|
||||||
let workspace = workspace.clone();
|
crate::toggle_mute(&Default::default(), cx)
|
||||||
move |_, cx| {
|
|
||||||
workspace
|
|
||||||
.update(cx, |this, cx| {
|
|
||||||
this.call_state().toggle_mute(cx);
|
|
||||||
})
|
|
||||||
.log_err();
|
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
|
@ -258,26 +254,15 @@ impl Render for CollabTitlebarItem {
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.on_click({
|
.on_click(move |_, cx| {
|
||||||
let workspace = workspace.clone();
|
crate::toggle_mute(&Default::default(), cx)
|
||||||
move |_, cx| {
|
|
||||||
workspace
|
|
||||||
.update(cx, |this, cx| {
|
|
||||||
this.call_state().toggle_deafen(cx);
|
|
||||||
})
|
|
||||||
.log_err();
|
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
IconButton::new("screen-share", ui::Icon::Screen)
|
IconButton::new("screen-share", ui::Icon::Screen)
|
||||||
.style(ButtonStyle::Subtle)
|
.style(ButtonStyle::Subtle)
|
||||||
.on_click(move |_, cx| {
|
.on_click(move |_, cx| {
|
||||||
workspace
|
crate::toggle_screen_sharing(&Default::default(), cx)
|
||||||
.update(cx, |this, cx| {
|
|
||||||
this.call_state().toggle_screen_share(cx);
|
|
||||||
})
|
|
||||||
.log_err();
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.pl_2(),
|
.pl_2(),
|
||||||
|
@ -451,46 +436,19 @@ impl CollabTitlebarItem {
|
||||||
// render_project_owner -> resolve if you are in a room -> Option<foo>
|
// 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> {
|
pub fn render_project_owner(&self, cx: &mut ViewContext<Self>) -> Option<impl Element> {
|
||||||
// TODO: We can't finish implementing this until project sharing works
|
let host = self.project.read(cx).host()?;
|
||||||
// - [ ] Show the project owner when the project is remote (maybe done)
|
let host = self.user_store.read(cx).get_cached_user(host.user_id)?;
|
||||||
// - [x] Show the project owner when the project is local
|
let participant_index = self
|
||||||
// - [ ] Show the project owner with a lock icon when the project is local and unshared
|
.user_store
|
||||||
|
.read(cx)
|
||||||
let remote_id = self.project.read(cx).remote_id();
|
.participant_indices()
|
||||||
let is_local = remote_id.is_none();
|
.get(&host.id)?;
|
||||||
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)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Some(
|
Some(
|
||||||
div().border().border_color(gpui::red()).child(
|
div().border().border_color(gpui::red()).child(
|
||||||
Button::new(
|
Button::new("project_owner_trigger", host.github_login.clone())
|
||||||
"project_owner_trigger",
|
.color(Color::Player(participant_index.0))
|
||||||
format!("{user_name} ({})", !is_shared),
|
.style(ButtonStyle::Subtle)
|
||||||
)
|
.tooltip(move |cx| Tooltip::text("Toggle following", cx)),
|
||||||
.color(Color::Player(participant_index))
|
|
||||||
.style(ButtonStyle::Subtle)
|
|
||||||
.tooltip(move |cx| Tooltip::text("Toggle following", cx)),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -730,21 +688,21 @@ impl CollabTitlebarItem {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
// fn share_project(&mut self, _: &ShareProject, cx: &mut ViewContext<Self>) {
|
fn share_project(&mut self, _: &ShareProject, cx: &mut ViewContext<Self>) {
|
||||||
// let active_call = ActiveCall::global(cx);
|
let active_call = ActiveCall::global(cx);
|
||||||
// let project = self.project.clone();
|
let project = self.project.clone();
|
||||||
// active_call
|
active_call
|
||||||
// .update(cx, |call, cx| call.share_project(project, cx))
|
.update(cx, |call, cx| call.share_project(project, cx))
|
||||||
// .detach_and_log_err(cx);
|
.detach_and_log_err(cx);
|
||||||
// }
|
}
|
||||||
|
|
||||||
// fn unshare_project(&mut self, _: &UnshareProject, cx: &mut ViewContext<Self>) {
|
fn unshare_project(&mut self, _: &UnshareProject, cx: &mut ViewContext<Self>) {
|
||||||
// let active_call = ActiveCall::global(cx);
|
let active_call = ActiveCall::global(cx);
|
||||||
// let project = self.project.clone();
|
let project = self.project.clone();
|
||||||
// active_call
|
active_call
|
||||||
// .update(cx, |call, cx| call.unshare_project(project, cx))
|
.update(cx, |call, cx| call.unshare_project(project, cx))
|
||||||
// .log_err();
|
.log_err();
|
||||||
// }
|
}
|
||||||
|
|
||||||
// pub fn toggle_user_menu(&mut self, _: &ToggleUserMenu, cx: &mut ViewContext<Self>) {
|
// pub fn toggle_user_menu(&mut self, _: &ToggleUserMenu, cx: &mut ViewContext<Self>) {
|
||||||
// self.user_menu.update(cx, |user_menu, cx| {
|
// self.user_menu.update(cx, |user_menu, cx| {
|
||||||
|
|
|
@ -9,22 +9,21 @@ mod panel_settings;
|
||||||
|
|
||||||
use std::{rc::Rc, sync::Arc};
|
use std::{rc::Rc, sync::Arc};
|
||||||
|
|
||||||
|
use call::{report_call_event_for_room, ActiveCall, Room};
|
||||||
pub use collab_panel::CollabPanel;
|
pub use collab_panel::CollabPanel;
|
||||||
pub use collab_titlebar_item::CollabTitlebarItem;
|
pub use collab_titlebar_item::CollabTitlebarItem;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
point, AppContext, GlobalPixels, Pixels, PlatformDisplay, Size, WindowBounds, WindowKind,
|
actions, point, AppContext, GlobalPixels, Pixels, PlatformDisplay, Size, Task, WindowBounds,
|
||||||
WindowOptions,
|
WindowKind, WindowOptions,
|
||||||
};
|
};
|
||||||
pub use panel_settings::{
|
pub use panel_settings::{
|
||||||
ChatPanelSettings, CollaborationPanelSettings, NotificationPanelSettings,
|
ChatPanelSettings, CollaborationPanelSettings, NotificationPanelSettings,
|
||||||
};
|
};
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
|
use util::ResultExt;
|
||||||
use workspace::AppState;
|
use workspace::AppState;
|
||||||
|
|
||||||
// actions!(
|
actions!(ToggleScreenSharing, ToggleMute, ToggleDeafen, LeaveCall);
|
||||||
// collab,
|
|
||||||
// [ToggleScreenSharing, ToggleMute, ToggleDeafen, LeaveCall]
|
|
||||||
// );
|
|
||||||
|
|
||||||
pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
||||||
CollaborationPanelSettings::register(cx);
|
CollaborationPanelSettings::register(cx);
|
||||||
|
@ -42,61 +41,61 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
||||||
// cx.add_global_action(toggle_deafen);
|
// cx.add_global_action(toggle_deafen);
|
||||||
}
|
}
|
||||||
|
|
||||||
// pub fn toggle_screen_sharing(_: &ToggleScreenSharing, cx: &mut AppContext) {
|
pub fn toggle_screen_sharing(_: &ToggleScreenSharing, cx: &mut AppContext) {
|
||||||
// let call = ActiveCall::global(cx).read(cx);
|
let call = ActiveCall::global(cx).read(cx);
|
||||||
// if let Some(room) = call.room().cloned() {
|
if let Some(room) = call.room().cloned() {
|
||||||
// let client = call.client();
|
let client = call.client();
|
||||||
// let toggle_screen_sharing = room.update(cx, |room, cx| {
|
let toggle_screen_sharing = room.update(cx, |room, cx| {
|
||||||
// if room.is_screen_sharing() {
|
if room.is_screen_sharing() {
|
||||||
// report_call_event_for_room(
|
report_call_event_for_room(
|
||||||
// "disable screen share",
|
"disable screen share",
|
||||||
// room.id(),
|
room.id(),
|
||||||
// room.channel_id(),
|
room.channel_id(),
|
||||||
// &client,
|
&client,
|
||||||
// cx,
|
cx,
|
||||||
// );
|
);
|
||||||
// Task::ready(room.unshare_screen(cx))
|
Task::ready(room.unshare_screen(cx))
|
||||||
// } else {
|
} else {
|
||||||
// report_call_event_for_room(
|
report_call_event_for_room(
|
||||||
// "enable screen share",
|
"enable screen share",
|
||||||
// room.id(),
|
room.id(),
|
||||||
// room.channel_id(),
|
room.channel_id(),
|
||||||
// &client,
|
&client,
|
||||||
// cx,
|
cx,
|
||||||
// );
|
);
|
||||||
// room.share_screen(cx)
|
room.share_screen(cx)
|
||||||
// }
|
}
|
||||||
// });
|
});
|
||||||
// toggle_screen_sharing.detach_and_log_err(cx);
|
toggle_screen_sharing.detach_and_log_err(cx);
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
// pub fn toggle_mute(_: &ToggleMute, cx: &mut AppContext) {
|
pub fn toggle_mute(_: &ToggleMute, cx: &mut AppContext) {
|
||||||
// let call = ActiveCall::global(cx).read(cx);
|
let call = ActiveCall::global(cx).read(cx);
|
||||||
// if let Some(room) = call.room().cloned() {
|
if let Some(room) = call.room().cloned() {
|
||||||
// let client = call.client();
|
let client = call.client();
|
||||||
// room.update(cx, |room, cx| {
|
room.update(cx, |room, cx| {
|
||||||
// let operation = if room.is_muted(cx) {
|
let operation = if room.is_muted(cx) {
|
||||||
// "enable microphone"
|
"enable microphone"
|
||||||
// } else {
|
} else {
|
||||||
// "disable microphone"
|
"disable microphone"
|
||||||
// };
|
};
|
||||||
// report_call_event_for_room(operation, room.id(), room.channel_id(), &client, cx);
|
report_call_event_for_room(operation, room.id(), room.channel_id(), &client, cx);
|
||||||
|
|
||||||
// room.toggle_mute(cx)
|
room.toggle_mute(cx)
|
||||||
// })
|
})
|
||||||
// .map(|task| task.detach_and_log_err(cx))
|
.map(|task| task.detach_and_log_err(cx))
|
||||||
// .log_err();
|
.log_err();
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
// pub fn toggle_deafen(_: &ToggleDeafen, cx: &mut AppContext) {
|
pub fn toggle_deafen(_: &ToggleDeafen, cx: &mut AppContext) {
|
||||||
// if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
|
if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
|
||||||
// room.update(cx, Room::toggle_deafen)
|
room.update(cx, Room::toggle_deafen)
|
||||||
// .map(|task| task.detach_and_log_err(cx))
|
.map(|task| task.detach_and_log_err(cx))
|
||||||
// .log_err();
|
.log_err();
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
fn notification_window_options(
|
fn notification_window_options(
|
||||||
screen: Rc<dyn PlatformDisplay>,
|
screen: Rc<dyn PlatformDisplay>,
|
||||||
|
|
|
@ -1755,7 +1755,7 @@ impl EditorElement {
|
||||||
let gutter_width;
|
let gutter_width;
|
||||||
let gutter_margin;
|
let gutter_margin;
|
||||||
if snapshot.show_gutter {
|
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;
|
let gutter_padding_factor = 3.5;
|
||||||
gutter_padding = (em_width * gutter_padding_factor).round();
|
gutter_padding = (em_width * gutter_padding_factor).round();
|
||||||
|
@ -3714,7 +3714,7 @@ fn compute_auto_height_layout(
|
||||||
let gutter_width;
|
let gutter_width;
|
||||||
let gutter_margin;
|
let gutter_margin;
|
||||||
if snapshot.show_gutter {
|
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;
|
let gutter_padding_factor = 3.5;
|
||||||
gutter_padding = (em_width * gutter_padding_factor).round();
|
gutter_padding = (em_width * gutter_padding_factor).round();
|
||||||
gutter_width = max_line_number_width + gutter_padding * 2.0;
|
gutter_width = max_line_number_width + gutter_padding * 2.0;
|
||||||
|
|
48
crates/gpui2/src/elements/canvas.rs
Normal file
48
crates/gpui2/src/elements/canvas.rs
Normal file
|
@ -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 div;
|
||||||
mod img;
|
mod img;
|
||||||
mod overlay;
|
mod overlay;
|
||||||
|
@ -5,6 +6,7 @@ mod svg;
|
||||||
mod text;
|
mod text;
|
||||||
mod uniform_list;
|
mod uniform_list;
|
||||||
|
|
||||||
|
pub use canvas::*;
|
||||||
pub use div::*;
|
pub use div::*;
|
||||||
pub use img::*;
|
pub use img::*;
|
||||||
pub use overlay::*;
|
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))
|
self.read_metrics(font_id, |metrics| metrics.bounding_box(font_size))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,9 +89,9 @@ impl TextSystem {
|
||||||
let bounds = self
|
let bounds = self
|
||||||
.platform_text_system
|
.platform_text_system
|
||||||
.typographic_bounds(font_id, glyph_id)?;
|
.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)
|
(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>> {
|
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)
|
.glyph_for_char(font_id, ch)
|
||||||
.ok_or_else(|| anyhow!("glyph not found for character '{}'", ch))?;
|
.ok_or_else(|| anyhow!("glyph not found for character '{}'", ch))?;
|
||||||
let result = self.platform_text_system.advance(font_id, glyph_id)?
|
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)
|
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)
|
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))
|
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))
|
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))
|
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))
|
self.read_metrics(font_id, |metrics| metrics.descent(font_size))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,24 +130,24 @@ impl TextSystem {
|
||||||
font_id: FontId,
|
font_id: FontId,
|
||||||
font_size: Pixels,
|
font_size: Pixels,
|
||||||
line_height: Pixels,
|
line_height: Pixels,
|
||||||
) -> Result<Pixels> {
|
) -> Pixels {
|
||||||
let ascent = self.ascent(font_id, font_size)?;
|
let ascent = self.ascent(font_id, font_size);
|
||||||
let descent = self.descent(font_id, font_size)?;
|
let descent = self.descent(font_id, font_size);
|
||||||
let padding_top = (line_height - ascent - descent) / 2.;
|
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();
|
let lock = self.font_metrics.upgradable_read();
|
||||||
|
|
||||||
if let Some(metrics) = lock.get(&font_id) {
|
if let Some(metrics) = lock.get(&font_id) {
|
||||||
Ok(read(metrics))
|
read(metrics)
|
||||||
} else {
|
} else {
|
||||||
let mut lock = RwLockUpgradableReadGuard::upgrade(lock);
|
let mut lock = RwLockUpgradableReadGuard::upgrade(lock);
|
||||||
let metrics = lock
|
let metrics = lock
|
||||||
.entry(font_id)
|
.entry(font_id)
|
||||||
.or_insert_with(|| self.platform_text_system.font_metrics(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 glyph_origin = origin;
|
||||||
let mut prev_glyph_position = Point::default();
|
let mut prev_glyph_position = Point::default();
|
||||||
for (run_ix, run) in layout.runs.iter().enumerate() {
|
for (run_ix, run) in layout.runs.iter().enumerate() {
|
||||||
let max_glyph_size = text_system
|
let max_glyph_size = text_system.bounding_box(run.font_id, layout.font_size).size;
|
||||||
.bounding_box(run.font_id, layout.font_size)?
|
|
||||||
.size;
|
|
||||||
|
|
||||||
for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
|
for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
|
||||||
glyph_origin.x += glyph.position.x - prev_glyph_position.x;
|
glyph_origin.x += glyph.position.x - prev_glyph_position.x;
|
||||||
|
|
|
@ -2734,6 +2734,7 @@ pub enum ElementId {
|
||||||
Integer(usize),
|
Integer(usize),
|
||||||
Name(SharedString),
|
Name(SharedString),
|
||||||
FocusHandle(FocusId),
|
FocusHandle(FocusId),
|
||||||
|
NamedInteger(SharedString, usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ElementId {
|
impl ElementId {
|
||||||
|
@ -2783,3 +2784,9 @@ impl<'a> From<&'a FocusHandle> for ElementId {
|
||||||
ElementId::FocusHandle(handle.id)
|
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 std::rc::Rc;
|
||||||
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
px, AnyElement, ClickEvent, Div, ImageSource, MouseButton, MouseDownEvent, Pixels, Stateful,
|
px, AnyElement, AnyView, ClickEvent, Div, ImageSource, MouseButton, MouseDownEvent, Pixels,
|
||||||
|
Stateful,
|
||||||
};
|
};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
|
@ -21,6 +22,7 @@ pub struct ListItem {
|
||||||
inset: bool,
|
inset: bool,
|
||||||
on_click: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
|
on_click: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
|
||||||
on_toggle: 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>>,
|
on_secondary_mouse_down: Option<Rc<dyn Fn(&MouseDownEvent, &mut WindowContext) + 'static>>,
|
||||||
children: SmallVec<[AnyElement; 2]>,
|
children: SmallVec<[AnyElement; 2]>,
|
||||||
}
|
}
|
||||||
|
@ -38,6 +40,7 @@ impl ListItem {
|
||||||
on_click: None,
|
on_click: None,
|
||||||
on_secondary_mouse_down: None,
|
on_secondary_mouse_down: None,
|
||||||
on_toggle: None,
|
on_toggle: None,
|
||||||
|
tooltip: None,
|
||||||
children: SmallVec::new(),
|
children: SmallVec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -55,6 +58,11 @@ impl ListItem {
|
||||||
self
|
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 {
|
pub fn inset(mut self, inset: bool) -> Self {
|
||||||
self.inset = inset;
|
self.inset = inset;
|
||||||
self
|
self
|
||||||
|
@ -149,6 +157,7 @@ impl RenderOnce for ListItem {
|
||||||
(on_mouse_down)(event, cx)
|
(on_mouse_down)(event, cx)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
.when_some(self.tooltip, |this, tooltip| this.tooltip(tooltip))
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.when(self.inset, |this| this.px_2())
|
.when(self.inset, |this| this.px_2())
|
||||||
|
|
|
@ -20,6 +20,7 @@ test-support = [
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
db = { path = "../db2", package = "db2" }
|
db = { path = "../db2", package = "db2" }
|
||||||
|
call = { path = "../call2", package = "call2" }
|
||||||
client = { path = "../client2", package = "client2" }
|
client = { path = "../client2", package = "client2" }
|
||||||
collections = { path = "../collections" }
|
collections = { path = "../collections" }
|
||||||
# context_menu = { path = "../context_menu" }
|
# context_menu = { path = "../context_menu" }
|
||||||
|
@ -36,7 +37,6 @@ theme = { path = "../theme2", package = "theme2" }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
ui = { package = "ui2", path = "../ui2" }
|
ui = { package = "ui2", path = "../ui2" }
|
||||||
|
|
||||||
async-trait.workspace = true
|
|
||||||
async-recursion = "1.0.0"
|
async-recursion = "1.0.0"
|
||||||
itertools = "0.10"
|
itertools = "0.10"
|
||||||
bincode = "1.2.1"
|
bincode = "1.2.1"
|
||||||
|
|
|
@ -1,18 +1,20 @@
|
||||||
use crate::{AppState, FollowerState, Pane, Workspace};
|
use crate::{AppState, FollowerState, Pane, Workspace};
|
||||||
use anyhow::{anyhow, bail, Result};
|
use anyhow::{anyhow, bail, Result};
|
||||||
|
use call::{ActiveCall, ParticipantLocation};
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use db::sqlez::{
|
use db::sqlez::{
|
||||||
bindable::{Bind, Column, StaticColumnCount},
|
bindable::{Bind, Column, StaticColumnCount},
|
||||||
statement::Statement,
|
statement::Statement,
|
||||||
};
|
};
|
||||||
use gpui::{
|
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 parking_lot::Mutex;
|
||||||
use project::Project;
|
use project::Project;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use ui::prelude::*;
|
use ui::{prelude::*, Button};
|
||||||
|
|
||||||
const HANDLE_HITBOX_SIZE: f32 = 4.0;
|
const HANDLE_HITBOX_SIZE: f32 = 4.0;
|
||||||
const HORIZONTAL_MIN_SIZE: f32 = 80.;
|
const HORIZONTAL_MIN_SIZE: f32 = 80.;
|
||||||
|
@ -126,6 +128,7 @@ impl PaneGroup {
|
||||||
&self,
|
&self,
|
||||||
project: &Model<Project>,
|
project: &Model<Project>,
|
||||||
follower_states: &HashMap<View<Pane>, FollowerState>,
|
follower_states: &HashMap<View<Pane>, FollowerState>,
|
||||||
|
active_call: Option<&Model<ActiveCall>>,
|
||||||
active_pane: &View<Pane>,
|
active_pane: &View<Pane>,
|
||||||
zoomed: Option<&AnyWeakView>,
|
zoomed: Option<&AnyWeakView>,
|
||||||
app_state: &Arc<AppState>,
|
app_state: &Arc<AppState>,
|
||||||
|
@ -135,6 +138,7 @@ impl PaneGroup {
|
||||||
project,
|
project,
|
||||||
0,
|
0,
|
||||||
follower_states,
|
follower_states,
|
||||||
|
active_call,
|
||||||
active_pane,
|
active_pane,
|
||||||
zoomed,
|
zoomed,
|
||||||
app_state,
|
app_state,
|
||||||
|
@ -196,6 +200,7 @@ impl Member {
|
||||||
project: &Model<Project>,
|
project: &Model<Project>,
|
||||||
basis: usize,
|
basis: usize,
|
||||||
follower_states: &HashMap<View<Pane>, FollowerState>,
|
follower_states: &HashMap<View<Pane>, FollowerState>,
|
||||||
|
active_call: Option<&Model<ActiveCall>>,
|
||||||
active_pane: &View<Pane>,
|
active_pane: &View<Pane>,
|
||||||
zoomed: Option<&AnyWeakView>,
|
zoomed: Option<&AnyWeakView>,
|
||||||
app_state: &Arc<AppState>,
|
app_state: &Arc<AppState>,
|
||||||
|
@ -203,19 +208,89 @@ impl Member {
|
||||||
) -> impl IntoElement {
|
) -> impl IntoElement {
|
||||||
match self {
|
match self {
|
||||||
Member::Pane(pane) => {
|
Member::Pane(pane) => {
|
||||||
// todo!()
|
let leader = follower_states.get(pane).and_then(|state| {
|
||||||
// let pane_element = if Some(pane.into()) == zoomed {
|
let room = active_call?.read(cx).room()?.read(cx);
|
||||||
// None
|
room.remote_participant_for_peer_id(state.leader_id)
|
||||||
// } else {
|
});
|
||||||
// Some(pane)
|
|
||||||
// };
|
|
||||||
|
|
||||||
div().size_full().child(pane.clone()).into_any()
|
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);
|
||||||
|
|
||||||
// Stack::new()
|
leader_status_box = match leader.location {
|
||||||
// .with_child(pane_element.contained().with_border(leader_border))
|
ParticipantLocation::SharedProject {
|
||||||
// .with_children(leader_status_box)
|
project_id: leader_project_id,
|
||||||
// .into_any()
|
} => {
|
||||||
|
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()
|
// let el = div()
|
||||||
// .flex()
|
// .flex()
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
use crate::participant::{Frame, RemoteVideoTrack};
|
use crate::{
|
||||||
|
item::{Item, ItemEvent},
|
||||||
|
ItemNavHistory, WorkspaceId,
|
||||||
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use call::participant::{Frame, RemoteVideoTrack};
|
||||||
use client::{proto::PeerId, User};
|
use client::{proto::PeerId, User};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
|
@ -9,7 +13,6 @@ use gpui::{
|
||||||
};
|
};
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
use ui::{h_stack, Icon, IconElement};
|
use ui::{h_stack, Icon, IconElement};
|
||||||
use workspace::{item::Item, ItemNavHistory, WorkspaceId};
|
|
||||||
|
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
Close,
|
Close,
|
||||||
|
@ -56,7 +59,7 @@ impl SharedScreen {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventEmitter<Event> for SharedScreen {}
|
impl EventEmitter<Event> for SharedScreen {}
|
||||||
impl EventEmitter<workspace::item::ItemEvent> for SharedScreen {}
|
impl EventEmitter<ItemEvent> for SharedScreen {}
|
||||||
|
|
||||||
impl FocusableView for SharedScreen {
|
impl FocusableView for SharedScreen {
|
||||||
fn focus_handle(&self, _: &AppContext) -> FocusHandle {
|
fn focus_handle(&self, _: &AppContext) -> FocusHandle {
|
File diff suppressed because it is too large
Load diff
|
@ -191,7 +191,6 @@ fn main() {
|
||||||
user_store: user_store.clone(),
|
user_store: user_store.clone(),
|
||||||
fs,
|
fs,
|
||||||
build_window_options,
|
build_window_options,
|
||||||
call_factory: call::Call::new,
|
|
||||||
workspace_store,
|
workspace_store,
|
||||||
node_runtime,
|
node_runtime,
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue