Show borders around avatars and panes to indicate following state

This commit is contained in:
Max Brunsfeld 2022-03-21 15:12:15 -07:00
parent 06cd9ac664
commit c8f36af823
4 changed files with 82 additions and 19 deletions

View File

@ -35,6 +35,7 @@ pub struct Workspace {
pub tab: Tab,
pub active_tab: Tab,
pub pane_divider: Border,
pub leader_border_opacity: f32,
pub left_sidebar: Sidebar,
pub right_sidebar: Sidebar,
pub status_bar: StatusBar,

View File

@ -1,9 +1,11 @@
use crate::{FollowerStatesByLeader, Pane};
use anyhow::{anyhow, Result};
use gpui::{elements::*, Axis, ViewHandle};
use client::PeerId;
use collections::HashMap;
use gpui::{elements::*, Axis, Border, ViewHandle};
use project::Collaborator;
use theme::Theme;
use crate::Pane;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PaneGroup {
root: Member,
@ -47,8 +49,13 @@ impl PaneGroup {
}
}
pub fn render<'a>(&self, theme: &Theme) -> ElementBox {
self.root.render(theme)
pub(crate) fn render<'a>(
&self,
theme: &Theme,
follower_states: &FollowerStatesByLeader,
collaborators: &HashMap<PeerId, Collaborator>,
) -> ElementBox {
self.root.render(theme, follower_states, collaborators)
}
}
@ -80,10 +87,39 @@ impl Member {
Member::Axis(PaneAxis { axis, members })
}
pub fn render(&self, theme: &Theme) -> ElementBox {
pub fn render(
&self,
theme: &Theme,
follower_states: &FollowerStatesByLeader,
collaborators: &HashMap<PeerId, Collaborator>,
) -> ElementBox {
match self {
Member::Pane(pane) => ChildView::new(pane).boxed(),
Member::Axis(axis) => axis.render(theme),
Member::Pane(pane) => {
let mut border = Border::default();
let leader = follower_states
.iter()
.find_map(|(leader_id, follower_states)| {
if follower_states.contains_key(pane) {
Some(leader_id)
} else {
None
}
})
.and_then(|leader_id| collaborators.get(leader_id));
if let Some(leader) = leader {
let leader_color = theme
.editor
.replica_selection_style(leader.replica_id)
.cursor;
border = Border::all(1.0, leader_color);
border
.color
.fade_out(1. - theme.workspace.leader_border_opacity);
border.overlay = true;
}
ChildView::new(pane).contained().with_border(border).boxed()
}
Member::Axis(axis) => axis.render(theme, follower_states, collaborators),
}
}
}
@ -172,11 +208,16 @@ impl PaneAxis {
}
}
fn render<'a>(&self, theme: &Theme) -> ElementBox {
fn render(
&self,
theme: &Theme,
follower_state: &FollowerStatesByLeader,
collaborators: &HashMap<PeerId, Collaborator>,
) -> ElementBox {
let last_member_ix = self.members.len() - 1;
Flex::new(self.axis)
.with_children(self.members.iter().enumerate().map(|(ix, member)| {
let mut member = member.render(theme);
let mut member = member.render(theme, follower_state, collaborators);
if ix < last_member_ix {
let mut border = theme.workspace.pane_divider;
border.left = false;

View File

@ -20,9 +20,9 @@ use gpui::{
json::{self, to_string_pretty, ToJson},
keymap::Binding,
platform::{CursorStyle, WindowOptions},
AnyModelHandle, AnyViewHandle, AppContext, AsyncAppContext, ClipboardItem, Entity, ImageData,
ModelHandle, MutableAppContext, PathPromptOptions, PromptLevel, RenderContext, Task, View,
ViewContext, ViewHandle, WeakViewHandle,
AnyModelHandle, AnyViewHandle, AppContext, AsyncAppContext, Border, ClipboardItem, Entity,
ImageData, ModelHandle, MutableAppContext, PathPromptOptions, PromptLevel, RenderContext, Task,
View, ViewContext, ViewHandle, WeakViewHandle,
};
use language::LanguageRegistry;
use log::error;
@ -613,7 +613,7 @@ pub struct Workspace {
status_bar: ViewHandle<StatusBar>,
project: ModelHandle<Project>,
leader_state: LeaderState,
follower_states_by_leader: HashMap<PeerId, HashMap<ViewHandle<Pane>, FollowerState>>,
follower_states_by_leader: FollowerStatesByLeader,
_observe_current_user: Task<()>,
}
@ -622,6 +622,8 @@ struct LeaderState {
followers: HashSet<PeerId>,
}
type FollowerStatesByLeader = HashMap<PeerId, HashMap<ViewHandle<Pane>, FollowerState>>;
#[derive(Default)]
struct FollowerState {
active_view_id: Option<u64>,
@ -1262,6 +1264,7 @@ impl Workspace {
cx: &mut ViewContext<Self>,
) -> Option<PeerId> {
for (leader_id, states_by_pane) in &mut self.follower_states_by_leader {
let leader_id = *leader_id;
if let Some(state) = states_by_pane.remove(&pane) {
for (_, item) in state.items_by_leader_view_id {
if let FollowerItem::Loaded(item) = item {
@ -1270,6 +1273,7 @@ impl Workspace {
}
if states_by_pane.is_empty() {
self.follower_states_by_leader.remove(&leader_id);
if let Some(project_id) = self.project.read(cx).remote_id() {
self.client
.send(proto::Unfollow {
@ -1281,7 +1285,7 @@ impl Workspace {
}
cx.notify();
return Some(*leader_id);
return Some(leader_id);
}
}
None
@ -1420,17 +1424,25 @@ impl Workspace {
theme: &Theme,
cx: &mut RenderContext<Self>,
) -> ElementBox {
let replica_color = theme.editor.replica_selection_style(replica_id).cursor;
let is_followed = peer_id.map_or(false, |peer_id| {
self.follower_states_by_leader.contains_key(&peer_id)
});
let mut avatar_style = theme.workspace.titlebar.avatar;
if is_followed {
avatar_style.border = Border::all(1.0, replica_color);
}
let content = Stack::new()
.with_child(
Image::new(avatar)
.with_style(theme.workspace.titlebar.avatar)
.with_style(avatar_style)
.constrained()
.with_width(theme.workspace.titlebar.avatar_width)
.aligned()
.boxed(),
)
.with_child(
AvatarRibbon::new(theme.editor.replica_selection_style(replica_id).cursor)
AvatarRibbon::new(replica_color)
.constrained()
.with_width(theme.workspace.titlebar.avatar_ribbon.width)
.with_height(theme.workspace.titlebar.avatar_ribbon.height)
@ -1800,8 +1812,16 @@ impl View for Workspace {
content.add_child(
Flex::column()
.with_child(
Flexible::new(1., true, self.center.render(&theme))
.boxed(),
Flexible::new(
1.,
true,
self.center.render(
&theme,
&self.follower_states_by_leader,
self.project.read(cx).collaborators(),
),
)
.boxed(),
)
.with_child(ChildView::new(&self.status_bar).boxed())
.flexible(1., true)

View File

@ -4,6 +4,7 @@ base = { family = "Zed Sans", size = 14 }
[workspace]
background = "$surface.0"
pane_divider = { width = 1, color = "$border.0" }
leader_border_opacity = 0.6
[workspace.titlebar]
height = 32