mirror of
https://github.com/zed-industries/zed.git
synced 2025-01-11 21:13:02 +00:00
Show borders around avatars and panes to indicate following state
This commit is contained in:
parent
06cd9ac664
commit
c8f36af823
4 changed files with 82 additions and 19 deletions
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue