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:
Max Brunsfeld
2023-12-04 18:01:11 -08:00
committed by GitHub
22 changed files with 1473 additions and 1781 deletions
+88 -13
View File
@@ -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)
// };
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)
});
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()
// .with_child(pane_element.contained().with_border(leader_border))
// .with_children(leader_status_box)
// .into_any()
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()
+114
View File
@@ -0,0 +1,114 @@
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::{
div, img, AppContext, Div, Element, EventEmitter, FocusHandle, Focusable, FocusableView,
InteractiveElement, ParentElement, Render, SharedString, Styled, Task, View, ViewContext,
VisualContext, WindowContext,
};
use std::sync::{Arc, Weak};
use ui::{h_stack, Icon, IconElement};
pub enum Event {
Close,
}
pub struct SharedScreen {
track: Weak<RemoteVideoTrack>,
frame: Option<Frame>,
pub peer_id: PeerId,
user: Arc<User>,
nav_history: Option<ItemNavHistory>,
_maintain_frame: Task<Result<()>>,
focus: FocusHandle,
}
impl SharedScreen {
pub fn new(
track: &Arc<RemoteVideoTrack>,
peer_id: PeerId,
user: Arc<User>,
cx: &mut ViewContext<Self>,
) -> Self {
cx.focus_handle();
let mut frames = track.frames();
Self {
track: Arc::downgrade(track),
frame: None,
peer_id,
user,
nav_history: Default::default(),
_maintain_frame: cx.spawn(|this, mut cx| async move {
while let Some(frame) = frames.next().await {
this.update(&mut cx, |this, cx| {
this.frame = Some(frame);
cx.notify();
})?;
}
this.update(&mut cx, |_, cx| cx.emit(Event::Close))?;
Ok(())
}),
focus: cx.focus_handle(),
}
}
}
impl EventEmitter<Event> for SharedScreen {}
impl EventEmitter<ItemEvent> for SharedScreen {}
impl FocusableView for SharedScreen {
fn focus_handle(&self, _: &AppContext) -> FocusHandle {
self.focus.clone()
}
}
impl Render for SharedScreen {
type Element = Focusable<Div>;
fn render(&mut self, _: &mut ViewContext<Self>) -> Self::Element {
div().track_focus(&self.focus).size_full().children(
self.frame
.as_ref()
.map(|frame| img(frame.image()).size_full()),
)
}
}
impl Item for SharedScreen {
fn tab_tooltip_text(&self, _: &AppContext) -> Option<SharedString> {
Some(format!("{}'s screen", self.user.github_login).into())
}
fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
if let Some(nav_history) = self.nav_history.as_mut() {
nav_history.push::<()>(None, cx);
}
}
fn tab_content(&self, _: Option<usize>, _: &WindowContext<'_>) -> gpui::AnyElement {
h_stack()
.gap_1()
.child(IconElement::new(Icon::Screen))
.child(SharedString::from(format!(
"{}'s screen",
self.user.github_login
)))
.into_any()
}
fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext<Self>) {
self.nav_history = Some(history);
}
fn clone_on_split(
&self,
_workspace_id: WorkspaceId,
cx: &mut ViewContext<Self>,
) -> Option<View<Self>> {
let track = self.track.upgrade()?;
Some(cx.build_view(|cx| Self::new(&track, self.peer_id, self.user.clone(), cx)))
}
}
File diff suppressed because it is too large Load Diff