diff --git a/gpui/src/color.rs b/gpui/src/color.rs index 9e31530b27f8b9f93c42df203aa88f99d9b9eff0..5adf03daef73e3a9449255bb425d197427ebfba5 100644 --- a/gpui/src/color.rs +++ b/gpui/src/color.rs @@ -33,6 +33,14 @@ impl Color { Self(ColorU::from_u32(0xff0000ff)) } + pub fn green() -> Self { + Self(ColorU::from_u32(0x00ff00ff)) + } + + pub fn blue() -> Self { + Self(ColorU::from_u32(0x0000ffff)) + } + pub fn new(r: u8, g: u8, b: u8, a: u8) -> Self { Self(ColorU::new(r, g, b, a)) } diff --git a/gpui/src/elements.rs b/gpui/src/elements.rs index 42e9810cfbff21ea9c96017b54689cb5a77f48cd..08f8732e0e594679d6693c34263b27d4e38d679e 100644 --- a/gpui/src/elements.rs +++ b/gpui/src/elements.rs @@ -8,7 +8,6 @@ mod flex; mod hook; mod image; mod label; -mod line_box; mod list; mod mouse_event_handler; mod overlay; @@ -19,8 +18,8 @@ mod uniform_list; pub use self::{ align::*, canvas::*, constrained_box::*, container::*, empty::*, event_handler::*, flex::*, - hook::*, image::*, label::*, line_box::*, list::*, mouse_event_handler::*, overlay::*, - stack::*, svg::*, text::*, uniform_list::*, + hook::*, image::*, label::*, list::*, mouse_event_handler::*, overlay::*, stack::*, svg::*, + text::*, uniform_list::*, }; pub use crate::presenter::ChildView; use crate::{ diff --git a/gpui/src/elements/label.rs b/gpui/src/elements/label.rs index acfbb5abd9e7dfa9c33004bc34522b16a7bb59a7..c1e048eb93e0c945db450f2112e7b20a1d735cda 100644 --- a/gpui/src/elements/label.rs +++ b/gpui/src/elements/label.rs @@ -137,8 +137,7 @@ impl Element for Label { let size = vec2f( line.width().max(constraint.min.x()).min(constraint.max.x()), cx.font_cache - .line_height(self.style.text.font_id, self.style.text.font_size) - .ceil(), + .line_height(self.style.text.font_id, self.style.text.font_size), ); (size, line) diff --git a/gpui/src/elements/line_box.rs b/gpui/src/elements/line_box.rs deleted file mode 100644 index 33fd2510c8869fce725cf4ab7482e5468b142c9a..0000000000000000000000000000000000000000 --- a/gpui/src/elements/line_box.rs +++ /dev/null @@ -1,87 +0,0 @@ -use crate::{ - fonts::TextStyle, - geometry::{ - rect::RectF, - vector::{vec2f, Vector2F}, - }, - json::{json, ToJson}, - DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, PaintContext, - SizeConstraint, -}; - -pub struct LineBox { - child: ElementBox, - style: TextStyle, -} - -impl LineBox { - pub fn new(child: ElementBox, style: TextStyle) -> Self { - Self { child, style } - } -} - -impl Element for LineBox { - type LayoutState = f32; - type PaintState = (); - - fn layout( - &mut self, - constraint: SizeConstraint, - cx: &mut LayoutContext, - ) -> (Vector2F, Self::LayoutState) { - let line_height = cx - .font_cache - .line_height(self.style.font_id, self.style.font_size); - let character_height = cx - .font_cache - .ascent(self.style.font_id, self.style.font_size) - + cx.font_cache - .descent(self.style.font_id, self.style.font_size); - let child_max = vec2f(constraint.max.x(), character_height); - let child_size = self.child.layout( - SizeConstraint::new(constraint.min.min(child_max), child_max), - cx, - ); - let size = vec2f(child_size.x(), line_height); - (size, (line_height - character_height) / 2.) - } - - fn paint( - &mut self, - bounds: RectF, - visible_bounds: RectF, - padding_top: &mut f32, - cx: &mut PaintContext, - ) -> Self::PaintState { - self.child.paint( - bounds.origin() + vec2f(0., *padding_top), - visible_bounds, - cx, - ); - } - - fn dispatch_event( - &mut self, - event: &Event, - _: RectF, - _: &mut Self::LayoutState, - _: &mut Self::PaintState, - cx: &mut EventContext, - ) -> bool { - self.child.dispatch_event(event, cx) - } - - fn debug( - &self, - bounds: RectF, - _: &Self::LayoutState, - _: &Self::PaintState, - cx: &DebugContext, - ) -> serde_json::Value { - json!({ - "bounds": bounds.to_json(), - "style": self.style.to_json(), - "child": self.child.debug(cx), - }) - } -} diff --git a/gpui/src/font_cache.rs b/gpui/src/font_cache.rs index c0255a7af5f251b9828e4788dba6443f30efcc5b..0509ecd437d134bd05f3f3c22cf9063286ae1140 100644 --- a/gpui/src/font_cache.rs +++ b/gpui/src/font_cache.rs @@ -166,6 +166,10 @@ impl FontCache { self.metric(font_id, |m| m.cap_height) * self.em_scale(font_id, font_size) } + pub fn x_height(&self, font_id: FontId, font_size: f32) -> f32 { + self.metric(font_id, |m| m.x_height) * self.em_scale(font_id, font_size) + } + pub fn ascent(&self, font_id: FontId, font_size: f32) -> f32 { self.metric(font_id, |m| m.ascent) * self.em_scale(font_id, font_size) } @@ -178,6 +182,14 @@ impl FontCache { font_size / self.metric(font_id, |m| m.units_per_em as f32) } + pub fn baseline_offset(&self, font_id: FontId, font_size: f32) -> f32 { + let line_height = self.line_height(font_id, font_size); + let ascent = self.ascent(font_id, font_size); + let descent = self.descent(font_id, font_size); + let padding_top = (line_height - ascent - descent) / 2.; + padding_top + ascent + } + pub fn line_wrapper(self: &Arc, font_id: FontId, font_size: f32) -> LineWrapperHandle { let mut state = self.0.write(); let wrappers = state diff --git a/gpui/src/fonts.rs b/gpui/src/fonts.rs index 3ec8aad9626bf78e4beb79709b89e525be679ad9..f2bbd04477bdfb630196d06abf23fa258fce2951 100644 --- a/gpui/src/fonts.rs +++ b/gpui/src/fonts.rs @@ -132,6 +132,14 @@ impl TextStyle { font_cache.line_height(self.font_id, self.font_size) } + pub fn cap_height(&self, font_cache: &FontCache) -> f32 { + font_cache.cap_height(self.font_id, self.font_size) + } + + pub fn x_height(&self, font_cache: &FontCache) -> f32 { + font_cache.x_height(self.font_id, self.font_size) + } + pub fn em_width(&self, font_cache: &FontCache) -> f32 { font_cache.em_width(self.font_id, self.font_size) } @@ -140,6 +148,10 @@ impl TextStyle { font_cache.metric(self.font_id, |m| m.descent) * self.em_scale(font_cache) } + pub fn baseline_offset(&self, font_cache: &FontCache) -> f32 { + font_cache.baseline_offset(self.font_id, self.font_size) + } + fn em_scale(&self, font_cache: &FontCache) -> f32 { font_cache.em_scale(self.font_id, self.font_size) } diff --git a/zed/assets/themes/_base.toml b/zed/assets/themes/_base.toml index 3c2181ae071bd5abc1475975b6fd420c383b4302..eda656b267ec03866ae51e7879075076294e5cf8 100644 --- a/zed/assets/themes/_base.toml +++ b/zed/assets/themes/_base.toml @@ -56,10 +56,13 @@ border = { width = 1, color = "$border.0", right = true } extends = "$workspace.sidebar" border = { width = 1, color = "$border.0", left = true } +[panel] +padding = 12 + [chat_panel] +extends = "$panel" channel_name = { extends = "$text.0", weight = "bold" } channel_name_hash = { text = "$text.2", padding.right = 8 } -padding = 12 [chat_panel.message] body = "$text.1" @@ -121,12 +124,18 @@ extends = "$chat_panel.sign_in_prompt" color = "$text.1.color" [people_panel] -host_username = "$text.0" +extends = "$panel" +host_username = { extends = "$text.0", padding.left = 5 } worktree_host_avatar = { corner_radius = 10 } worktree_guest_avatar = { corner_radius = 8 } [people_panel.worktree_name] extends = "$text.0" +padding = { left = 5 } + +[people_panel.tree_branch] +width = 1 +color = "$surface.2" [selector] background = "$surface.0" diff --git a/zed/src/people_panel.rs b/zed/src/people_panel.rs index fc459825de043f3a7f96f759cc385d31e6117502..771fd6713fb945604003e355b7ebe60e1e9d3163 100644 --- a/zed/src/people_panel.rs +++ b/zed/src/people_panel.rs @@ -4,7 +4,9 @@ use crate::{ Settings, }; use gpui::{ - elements::*, Element, ElementBox, Entity, ModelHandle, RenderContext, Subscription, View, + elements::*, + geometry::{rect::RectF, vector::vec2f}, + Element, ElementBox, Entity, FontCache, ModelHandle, RenderContext, Subscription, View, ViewContext, }; use postage::watch; @@ -12,6 +14,7 @@ use postage::watch; pub struct PeoplePanel { collaborators: ListState, user_store: ModelHandle, + settings: watch::Receiver, _maintain_collaborators: Subscription, } @@ -28,15 +31,19 @@ impl PeoplePanel { 1000., { let user_store = user_store.clone(); + let settings = settings.clone(); move |ix, cx| { - let user_store = user_store.read(cx); - let settings = settings.borrow(); - Self::render_collaborator(&user_store.collaborators()[ix], &settings.theme) + Self::render_collaborator( + &user_store.read(cx).collaborators()[ix], + &settings.borrow().theme, + cx.font_cache(), + ) } }, ), _maintain_collaborators: cx.observe(&user_store, Self::update_collaborators), user_store, + settings, } } @@ -46,54 +53,117 @@ impl PeoplePanel { cx.notify(); } - fn render_collaborator(collaborator: &Collaborator, theme: &Theme) -> ElementBox { + fn render_collaborator( + collaborator: &Collaborator, + theme: &Theme, + font_cache: &FontCache, + ) -> ElementBox { + let theme = &theme.people_panel; + let worktree_count = collaborator.worktrees.len(); + let line_height = theme.worktree_name.text.line_height(font_cache); + let cap_height = theme.worktree_name.text.cap_height(font_cache); + let baseline_offset = theme.worktree_name.text.baseline_offset(font_cache); + let tree_branch = theme.tree_branch; + Flex::column() .with_child( Flex::row() .with_children(collaborator.user.avatar.clone().map(|avatar| { ConstrainedBox::new( Image::new(avatar) - .with_style(theme.people_panel.worktree_host_avatar) + .with_style(theme.worktree_host_avatar) .boxed(), ) .with_width(20.) .boxed() })) - .with_child( - Label::new( - collaborator.user.github_login.clone(), - theme.people_panel.host_username.clone(), - ) - .boxed(), - ) - .boxed(), - ) - .with_children(collaborator.worktrees.iter().map(|worktree| { - Flex::row() .with_child( Container::new( Label::new( - worktree.root_name.clone(), - theme.people_panel.worktree_name.text.clone(), + collaborator.user.github_login.clone(), + theme.host_username.text.clone(), ) .boxed(), ) - .with_style(theme.people_panel.worktree_name.container) + .with_style(theme.host_username.container) .boxed(), ) - .with_children(worktree.participants.iter().filter_map(|participant| { - participant.avatar.clone().map(|avatar| { - ConstrainedBox::new( - Image::new(avatar) - .with_style(theme.people_panel.worktree_guest_avatar) + .boxed(), + ) + .with_children( + collaborator + .worktrees + .iter() + .enumerate() + .map(|(ix, worktree)| { + Flex::row() + .with_child( + ConstrainedBox::new( + Canvas::new(move |bounds, _, cx| { + let start_x = bounds.min_x() + (bounds.width() / 2.) + - (tree_branch.width / 2.); + let end_x = bounds.max_x(); + let start_y = bounds.min_y(); + let end_y = + bounds.min_y() + baseline_offset - (cap_height / 2.); + + cx.scene.push_quad(gpui::Quad { + bounds: RectF::from_points( + vec2f(start_x, start_y), + vec2f( + start_x + tree_branch.width, + if ix + 1 == worktree_count { + end_y + } else { + bounds.max_y() + }, + ), + ), + background: Some(tree_branch.color), + border: gpui::Border::default(), + corner_radius: 0., + }); + cx.scene.push_quad(gpui::Quad { + bounds: RectF::from_points( + vec2f(start_x, end_y), + vec2f(end_x, end_y + tree_branch.width), + ), + background: Some(tree_branch.color), + border: gpui::Border::default(), + corner_radius: 0., + }); + }) .boxed(), + ) + .with_width(20.) + .with_height(line_height) + .boxed(), ) - .with_width(16.) + .with_child( + Container::new( + Label::new( + worktree.root_name.clone(), + theme.worktree_name.text.clone(), + ) + .boxed(), + ) + .with_style(theme.worktree_name.container) + .boxed(), + ) + .with_children(worktree.participants.iter().filter_map(|participant| { + participant.avatar.clone().map(|avatar| { + ConstrainedBox::new( + Image::new(avatar) + .with_style(theme.worktree_guest_avatar) + .boxed(), + ) + .with_width(16.) + .boxed() + }) + })) .boxed() - }) - })) - .boxed() - })) + }), + ) .boxed() } } @@ -110,6 +180,9 @@ impl View for PeoplePanel { } fn render(&mut self, _: &mut RenderContext) -> ElementBox { - List::new(self.collaborators.clone()).boxed() + let theme = &self.settings.borrow().theme.people_panel; + Container::new(List::new(self.collaborators.clone()).boxed()) + .with_style(theme.container) + .boxed() } } diff --git a/zed/src/theme.rs b/zed/src/theme.rs index 59a1ec96343e48d6177ba6405c589ab95dbc567f..b7a4f41023dda27fcd7cdee3e32c404e4967bb14 100644 --- a/zed/src/theme.rs +++ b/zed/src/theme.rs @@ -109,10 +109,17 @@ pub struct ChatPanel { pub struct PeoplePanel { #[serde(flatten)] pub container: ContainerStyle, - pub host_username: TextStyle, + pub host_username: ContainedText, pub worktree_name: ContainedText, pub worktree_host_avatar: ImageStyle, pub worktree_guest_avatar: ImageStyle, + pub tree_branch: TreeBranch, +} + +#[derive(Copy, Clone, Deserialize)] +pub struct TreeBranch { + pub width: f32, + pub color: Color, } #[derive(Deserialize)]