diff --git a/crates/git_ui/src/git_panel.rs b/crates/git_ui/src/git_panel.rs index 7216e1fc46e9d1240d23d8bd18202aa0963f846a..d392813d7602b19f04e66dd7c892cee40e8e243d 100644 --- a/crates/git_ui/src/git_panel.rs +++ b/crates/git_ui/src/git_panel.rs @@ -35,10 +35,9 @@ use git::{ }; use gpui::{ Action, AsyncApp, AsyncWindowContext, Bounds, ClickEvent, Corner, DismissEvent, Entity, - EventEmitter, FocusHandle, Focusable, KeyContext, ListHorizontalSizingBehavior, - ListSizingBehavior, MouseButton, MouseDownEvent, Point, PromptLevel, ScrollStrategy, - Subscription, Task, UniformListScrollHandle, WeakEntity, actions, anchored, deferred, point, - size, uniform_list, + EventEmitter, FocusHandle, Focusable, KeyContext, MouseButton, MouseDownEvent, Point, + PromptLevel, ScrollStrategy, Subscription, Task, UniformListScrollHandle, WeakEntity, actions, + anchored, deferred, point, size, uniform_list, }; use itertools::Itertools; use language::{Buffer, File}; @@ -212,8 +211,7 @@ const GIT_PANEL_KEY: &str = "GitPanel"; const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50); // TODO: We should revise this part. It seems the indentation width is not aligned with the one in project panel -const TREE_INDENT: f32 = 12.0; -const TREE_INDENT_GUIDE_OFFSET: f32 = 16.0; +const TREE_INDENT: f32 = 16.0; pub fn register(workspace: &mut Workspace) { workspace.register_action(|workspace, _: &ToggleFocus, window, cx| { @@ -4697,7 +4695,10 @@ impl GitPanel { }, ) .with_render_fn(cx.entity(), |_, params, _, _| { - let left_offset = px(TREE_INDENT_GUIDE_OFFSET); + // Magic number to align the tree item is 3 here + // because we're using 12px as the left-side padding + // and 3 makes the alignment work with the bounding box of the icon + let left_offset = px(TREE_INDENT + 3_f32); let indent_size = params.indent_size; let item_height = params.item_height; @@ -4725,10 +4726,6 @@ impl GitPanel { }) .size_full() .flex_grow() - .with_sizing_behavior(ListSizingBehavior::Auto) - .with_horizontal_sizing_behavior( - ListHorizontalSizingBehavior::Unconstrained, - ) .with_width_from_item(self.max_width_item_index) .track_scroll(&self.scroll_handle), ) @@ -4752,7 +4749,7 @@ impl GitPanel { } fn entry_label(&self, label: impl Into, color: Color) -> Label { - Label::new(label.into()).color(color).single_line() + Label::new(label.into()).color(color) } fn list_item_height(&self) -> Rems { @@ -4774,8 +4771,8 @@ impl GitPanel { .h(self.list_item_height()) .w_full() .items_end() - .px(rems(0.75)) // ~12px - .pb(rems(0.3125)) // ~ 5px + .px_3() + .pb_1() .child( Label::new(header.title()) .color(Color::Muted) @@ -4963,113 +4960,68 @@ impl GitPanel { let marked_bg_alpha = 0.12; let state_opacity_step = 0.04; + let info_color = cx.theme().status().info; + let base_bg = match (selected, marked) { - (true, true) => cx - .theme() - .status() - .info - .alpha(selected_bg_alpha + marked_bg_alpha), - (true, false) => cx.theme().status().info.alpha(selected_bg_alpha), - (false, true) => cx.theme().status().info.alpha(marked_bg_alpha), + (true, true) => info_color.alpha(selected_bg_alpha + marked_bg_alpha), + (true, false) => info_color.alpha(selected_bg_alpha), + (false, true) => info_color.alpha(marked_bg_alpha), _ => cx.theme().colors().ghost_element_background, }; - let hover_bg = if selected { - cx.theme() - .status() - .info - .alpha(selected_bg_alpha + state_opacity_step) - } else { - cx.theme().colors().ghost_element_hover - }; - - let active_bg = if selected { - cx.theme() - .status() - .info - .alpha(selected_bg_alpha + state_opacity_step * 2.0) + let (hover_bg, active_bg) = if selected { + ( + info_color.alpha(selected_bg_alpha + state_opacity_step), + info_color.alpha(selected_bg_alpha + state_opacity_step * 2.0), + ) } else { - cx.theme().colors().ghost_element_active + ( + cx.theme().colors().ghost_element_hover, + cx.theme().colors().ghost_element_active, + ) }; - let mut name_row = h_flex() - .items_center() - .gap_1() + let name_row = h_flex() + .min_w_0() .flex_1() - .pl(if tree_view { - px(depth as f32 * TREE_INDENT) - } else { - px(0.) - }) - .child(git_status_icon(status)); - - name_row = if tree_view { - name_row.child( - self.entry_label(display_name, label_color) - .when(status.is_deleted(), Label::strikethrough) - .truncate(), - ) - } else { - name_row.child(h_flex().items_center().flex_1().map(|this| { - self.path_formatted( - this, - entry.parent_dir(path_style), - path_color, - display_name, - label_color, - path_style, - git_path_style, - status.is_deleted(), - ) - })) - }; + .gap_1() + .child(git_status_icon(status)) + .map(|this| { + if tree_view { + this.pl(px(depth as f32 * TREE_INDENT)).child( + self.entry_label(display_name, label_color) + .when(status.is_deleted(), Label::strikethrough) + .truncate(), + ) + } else { + this.child(self.path_formatted( + entry.parent_dir(path_style), + path_color, + display_name, + label_color, + path_style, + git_path_style, + status.is_deleted(), + )) + } + }); h_flex() .id(id) .h(self.list_item_height()) .w_full() + .pl_3() + .pr_1() + .gap_1p5() .border_1() .border_r_2() .when(selected && self.focus_handle.is_focused(window), |el| { el.border_color(cx.theme().colors().panel_focused_border) }) - .px(rems(0.75)) // ~12px - .overflow_hidden() - .flex_none() - .gap_1p5() .bg(base_bg) - .hover(|this| this.bg(hover_bg)) - .active(|this| this.bg(active_bg)) - .on_click({ - cx.listener(move |this, event: &ClickEvent, window, cx| { - this.selected_entry = Some(ix); - cx.notify(); - if event.modifiers().secondary() { - this.open_file(&Default::default(), window, cx) - } else { - this.open_diff(&Default::default(), window, cx); - this.focus_handle.focus(window, cx); - } - }) - }) - .on_mouse_down( - MouseButton::Right, - move |event: &MouseDownEvent, window, cx| { - // why isn't this happening automatically? we are passing MouseButton::Right to `on_mouse_down`? - if event.button != MouseButton::Right { - return; - } - - let Some(this) = handle.upgrade() else { - return; - }; - this.update(cx, |this, cx| { - this.deploy_entry_context_menu(event.position, ix, window, cx); - }); - cx.stop_propagation(); - }, - ) - .child(name_row.overflow_x_hidden()) + .hover(|s| s.bg(hover_bg)) + .active(|s| s.bg(active_bg)) + .child(name_row) .child( div() .id(checkbox_wrapper_id) @@ -5119,6 +5071,35 @@ impl GitPanel { }), ), ) + .on_click({ + cx.listener(move |this, event: &ClickEvent, window, cx| { + this.selected_entry = Some(ix); + cx.notify(); + if event.modifiers().secondary() { + this.open_file(&Default::default(), window, cx) + } else { + this.open_diff(&Default::default(), window, cx); + this.focus_handle.focus(window, cx); + } + }) + }) + .on_mouse_down( + MouseButton::Right, + move |event: &MouseDownEvent, window, cx| { + // why isn't this happening automatically? we are passing MouseButton::Right to `on_mouse_down`? + if event.button != MouseButton::Right { + return; + } + + let Some(this) = handle.upgrade() else { + return; + }; + this.update(cx, |this, cx| { + this.deploy_entry_context_menu(event.position, ix, window, cx); + }); + cx.stop_propagation(); + }, + ) .into_any_element() } @@ -5143,29 +5124,23 @@ impl GitPanel { let selected_bg_alpha = 0.08; let state_opacity_step = 0.04; - let base_bg = if selected { - cx.theme().status().info.alpha(selected_bg_alpha) - } else { - cx.theme().colors().ghost_element_background - }; + let info_color = cx.theme().status().info; + let colors = cx.theme().colors(); - let hover_bg = if selected { - cx.theme() - .status() - .info - .alpha(selected_bg_alpha + state_opacity_step) + let (base_bg, hover_bg, active_bg) = if selected { + ( + info_color.alpha(selected_bg_alpha), + info_color.alpha(selected_bg_alpha + state_opacity_step), + info_color.alpha(selected_bg_alpha + state_opacity_step * 2.0), + ) } else { - cx.theme().colors().ghost_element_hover + ( + colors.ghost_element_background, + colors.ghost_element_hover, + colors.ghost_element_active, + ) }; - let active_bg = if selected { - cx.theme() - .status() - .info - .alpha(selected_bg_alpha + state_opacity_step * 2.0) - } else { - cx.theme().colors().ghost_element_active - }; let folder_icon = if entry.expanded { IconName::FolderOpen } else { @@ -5188,9 +5163,8 @@ impl GitPanel { }; let name_row = h_flex() - .items_center() + .min_w_0() .gap_1() - .flex_1() .pl(px(entry.depth as f32 * TREE_INDENT)) .child( Icon::new(folder_icon) @@ -5202,28 +5176,21 @@ impl GitPanel { h_flex() .id(id) .h(self.list_item_height()) + .min_w_0() .w_full() - .items_center() + .pl_3() + .pr_1() + .gap_1p5() + .justify_between() .border_1() .border_r_2() .when(selected && self.focus_handle.is_focused(window), |el| { el.border_color(cx.theme().colors().panel_focused_border) }) - .px(rems(0.75)) - .overflow_hidden() - .flex_none() - .gap_1p5() .bg(base_bg) - .hover(|this| this.bg(hover_bg)) - .active(|this| this.bg(active_bg)) - .on_click({ - let key = entry.key.clone(); - cx.listener(move |this, _event: &ClickEvent, window, cx| { - this.selected_entry = Some(ix); - this.toggle_directory(&key, window, cx); - }) - }) - .child(name_row.overflow_x_hidden()) + .hover(|s| s.bg(hover_bg)) + .active(|s| s.bg(active_bg)) + .child(name_row) .child( div() .id(checkbox_wrapper_id) @@ -5262,12 +5229,18 @@ impl GitPanel { }), ), ) + .on_click({ + let key = entry.key.clone(); + cx.listener(move |this, _event: &ClickEvent, window, cx| { + this.selected_entry = Some(ix); + this.toggle_directory(&key, window, cx); + }) + }) .into_any_element() } fn path_formatted( &self, - parent: Div, directory: Option, path_color: Color, file_name: String, @@ -5276,41 +5249,31 @@ impl GitPanel { git_path_style: GitPathStyle, strikethrough: bool, ) -> Div { - parent - .when(git_path_style == GitPathStyle::FileNameFirst, |this| { - this.child( - self.entry_label( - match directory.as_ref().is_none_or(|d| d.is_empty()) { - true => file_name.clone(), - false => format!("{file_name} "), - }, - label_color, - ) - .when(strikethrough, Label::strikethrough), - ) - }) - .when_some(directory, |this, dir| { - match ( - !dir.is_empty(), - git_path_style == GitPathStyle::FileNameFirst, - ) { - (true, true) => this.child( - self.entry_label(dir, path_color) - .when(strikethrough, Label::strikethrough), - ), - (true, false) => this.child( - self.entry_label( - format!("{dir}{}", path_style.primary_separator()), - path_color, - ) + let file_name_first = git_path_style == GitPathStyle::FileNameFirst; + let file_path_first = git_path_style == GitPathStyle::FilePathFirst; + + let file_name = format!("{} ", file_name); + + h_flex() + .min_w_0() + .overflow_hidden() + .when(file_path_first, |this| this.flex_row_reverse()) + .child( + div().flex_none().child( + self.entry_label(file_name, label_color) .when(strikethrough, Label::strikethrough), - ), - _ => this, - } - }) - .when(git_path_style == GitPathStyle::FilePathFirst, |this| { + ), + ) + .when_some(directory, |this, dir| { + let path_name = if file_name_first { + dir + } else { + format!("{dir}{}", path_style.primary_separator()) + }; + this.child( - self.entry_label(file_name, label_color) + self.entry_label(path_name, path_color) + .truncate() .when(strikethrough, Label::strikethrough), ) })