git_ui: Fix item heights in git panel (#25833)
- Fixes items slightly overlapping in the git panel - Fixes commit button in the project diff not opening modal Release Notes: - N/A --------- Co-authored-by: Nate Butler <iamnbutler@gmail.com> Co-authored-by: Cole Miller <m@cole-miller.net>
This commit is contained in:
parent
e0060b92cc
commit
b34c0fd71b
4 changed files with 166 additions and 92 deletions
|
@ -64,7 +64,7 @@ actions!(
|
||||||
Pull,
|
Pull,
|
||||||
Fetch,
|
Fetch,
|
||||||
Commit,
|
Commit,
|
||||||
ExpandCommitEditor,
|
ShowCommitEditor,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
action_with_deprecated_aliases!(git, RestoreFile, ["editor::RevertFile"]);
|
action_with_deprecated_aliases!(git, RestoreFile, ["editor::RevertFile"]);
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
use crate::branch_picker::{self, BranchList};
|
use crate::branch_picker::{self, BranchList};
|
||||||
use crate::git_panel::{commit_message_editor, GitPanel};
|
use crate::git_panel::{commit_message_editor, GitPanel};
|
||||||
use git::{Commit, ExpandCommitEditor};
|
use git::{Commit, ShowCommitEditor};
|
||||||
use panel::{panel_button, panel_editor_style, panel_filled_button};
|
use panel::{panel_button, panel_editor_style, panel_filled_button};
|
||||||
use project::Project;
|
use project::Project;
|
||||||
use ui::{prelude::*, KeybindingHint, PopoverButton, Tooltip, TriggerablePopover};
|
use ui::{prelude::*, KeybindingHint, PopoverButton, Tooltip, TriggerablePopover};
|
||||||
|
@ -110,7 +110,7 @@ struct RestoreDock {
|
||||||
|
|
||||||
impl CommitModal {
|
impl CommitModal {
|
||||||
pub fn register(workspace: &mut Workspace, _: &mut Window, _cx: &mut Context<Workspace>) {
|
pub fn register(workspace: &mut Workspace, _: &mut Window, _cx: &mut Context<Workspace>) {
|
||||||
workspace.register_action(|workspace, _: &ExpandCommitEditor, window, cx| {
|
workspace.register_action(|workspace, _: &ShowCommitEditor, window, cx| {
|
||||||
let Some(git_panel) = workspace.panel::<GitPanel>(cx) else {
|
let Some(git_panel) = workspace.panel::<GitPanel>(cx) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
|
@ -40,8 +40,8 @@ use std::{collections::HashSet, sync::Arc, time::Duration, usize};
|
||||||
use strum::{IntoEnumIterator, VariantNames};
|
use strum::{IntoEnumIterator, VariantNames};
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
use ui::{
|
use ui::{
|
||||||
prelude::*, ButtonLike, Checkbox, ContextMenu, ElevationIndex, ListItem, ListItemSpacing,
|
prelude::*, ButtonLike, Checkbox, ContextMenu, ElevationIndex, PopoverButton, PopoverMenu,
|
||||||
PopoverButton, PopoverMenu, Scrollbar, ScrollbarState, Tooltip,
|
Scrollbar, ScrollbarState, Tooltip,
|
||||||
};
|
};
|
||||||
use util::{maybe, post_inc, ResultExt, TryFutureExt};
|
use util::{maybe, post_inc, ResultExt, TryFutureExt};
|
||||||
|
|
||||||
|
@ -210,6 +210,7 @@ pub struct GitPanel {
|
||||||
scroll_handle: UniformListScrollHandle,
|
scroll_handle: UniformListScrollHandle,
|
||||||
scrollbar_state: ScrollbarState,
|
scrollbar_state: ScrollbarState,
|
||||||
selected_entry: Option<usize>,
|
selected_entry: Option<usize>,
|
||||||
|
marked_entries: Vec<usize>,
|
||||||
show_scrollbar: bool,
|
show_scrollbar: bool,
|
||||||
tracked_count: usize,
|
tracked_count: usize,
|
||||||
tracked_staged_count: usize,
|
tracked_staged_count: usize,
|
||||||
|
@ -337,6 +338,7 @@ impl GitPanel {
|
||||||
scroll_handle,
|
scroll_handle,
|
||||||
scrollbar_state,
|
scrollbar_state,
|
||||||
selected_entry: None,
|
selected_entry: None,
|
||||||
|
marked_entries: Vec::new(),
|
||||||
show_scrollbar: false,
|
show_scrollbar: false,
|
||||||
tracked_count: 0,
|
tracked_count: 0,
|
||||||
tracked_staged_count: 0,
|
tracked_staged_count: 0,
|
||||||
|
@ -2077,7 +2079,7 @@ impl GitPanel {
|
||||||
.on_click(cx.listener({
|
.on_click(cx.listener({
|
||||||
move |_, _, window, cx| {
|
move |_, _, window, cx| {
|
||||||
window.dispatch_action(
|
window.dispatch_action(
|
||||||
git::ExpandCommitEditor.boxed_clone(),
|
git::ShowCommitEditor.boxed_clone(),
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -2309,7 +2311,7 @@ impl GitPanel {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.size_full()
|
.size_full()
|
||||||
.with_sizing_behavior(ListSizingBehavior::Infer)
|
.with_sizing_behavior(ListSizingBehavior::Auto)
|
||||||
.with_horizontal_sizing_behavior(ListHorizontalSizingBehavior::Unconstrained)
|
.with_horizontal_sizing_behavior(ListHorizontalSizingBehavior::Unconstrained)
|
||||||
.track_scroll(self.scroll_handle.clone()),
|
.track_scroll(self.scroll_handle.clone()),
|
||||||
)
|
)
|
||||||
|
@ -2326,6 +2328,10 @@ impl GitPanel {
|
||||||
Label::new(label.into()).color(color).single_line()
|
Label::new(label.into()).color(color).single_line()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn list_item_height(&self) -> Rems {
|
||||||
|
rems(1.75)
|
||||||
|
}
|
||||||
|
|
||||||
fn render_list_header(
|
fn render_list_header(
|
||||||
&self,
|
&self,
|
||||||
ix: usize,
|
ix: usize,
|
||||||
|
@ -2334,18 +2340,21 @@ impl GitPanel {
|
||||||
_: &Window,
|
_: &Window,
|
||||||
_: &Context<Self>,
|
_: &Context<Self>,
|
||||||
) -> AnyElement {
|
) -> AnyElement {
|
||||||
div()
|
let id: ElementId = ElementId::Name(format!("header_{}", ix).into());
|
||||||
|
|
||||||
|
h_flex()
|
||||||
|
.id(id)
|
||||||
|
.h(self.list_item_height())
|
||||||
.w_full()
|
.w_full()
|
||||||
|
.items_end()
|
||||||
|
.px(rems(0.75)) // ~12px
|
||||||
|
.pb(rems(0.3125)) // ~ 5px
|
||||||
.child(
|
.child(
|
||||||
ListItem::new(ix)
|
Label::new(header.title())
|
||||||
.spacing(ListItemSpacing::Sparse)
|
.color(Color::Muted)
|
||||||
.disabled(true)
|
.size(LabelSize::Small)
|
||||||
.child(
|
.line_height_style(LineHeightStyle::UiLabel)
|
||||||
Label::new(header.title())
|
.single_line(),
|
||||||
.color(Color::Muted)
|
|
||||||
.size(LabelSize::Small)
|
|
||||||
.single_line(),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
}
|
}
|
||||||
|
@ -2435,7 +2444,7 @@ impl GitPanel {
|
||||||
ix: usize,
|
ix: usize,
|
||||||
entry: &GitStatusEntry,
|
entry: &GitStatusEntry,
|
||||||
has_write_access: bool,
|
has_write_access: bool,
|
||||||
window: &Window,
|
_: &Window,
|
||||||
cx: &Context<Self>,
|
cx: &Context<Self>,
|
||||||
) -> AnyElement {
|
) -> AnyElement {
|
||||||
let display_name = entry
|
let display_name = entry
|
||||||
|
@ -2446,6 +2455,7 @@ impl GitPanel {
|
||||||
|
|
||||||
let repo_path = entry.repo_path.clone();
|
let repo_path = entry.repo_path.clone();
|
||||||
let selected = self.selected_entry == Some(ix);
|
let selected = self.selected_entry == Some(ix);
|
||||||
|
let marked = self.marked_entries.contains(&ix);
|
||||||
let status_style = GitPanelSettings::get_global(cx).status_style;
|
let status_style = GitPanelSettings::get_global(cx).status_style;
|
||||||
let status = entry.status;
|
let status = entry.status;
|
||||||
let has_conflict = status.is_conflicted();
|
let has_conflict = status.is_conflicted();
|
||||||
|
@ -2473,7 +2483,11 @@ impl GitPanel {
|
||||||
Color::Muted
|
Color::Muted
|
||||||
};
|
};
|
||||||
|
|
||||||
let id: ElementId = ElementId::Name(format!("entry_{}", display_name).into());
|
let id: ElementId = ElementId::Name(format!("entry_{}_{}", display_name, ix).into());
|
||||||
|
let checkbox_wrapper_id: ElementId =
|
||||||
|
ElementId::Name(format!("entry_{}_{}_checkbox_wrapper", display_name, ix).into());
|
||||||
|
let checkbox_id: ElementId =
|
||||||
|
ElementId::Name(format!("entry_{}_{}_checkbox", display_name, ix).into());
|
||||||
|
|
||||||
let is_entry_staged = self.entry_is_staged(entry);
|
let is_entry_staged = self.entry_is_staged(entry);
|
||||||
let mut is_staged: ToggleState = self.entry_is_staged(entry).into();
|
let mut is_staged: ToggleState = self.entry_is_staged(entry).into();
|
||||||
|
@ -2482,84 +2496,143 @@ impl GitPanel {
|
||||||
is_staged = ToggleState::Selected;
|
is_staged = ToggleState::Selected;
|
||||||
}
|
}
|
||||||
|
|
||||||
let checkbox = Checkbox::new(id, is_staged)
|
let handle = cx.weak_entity();
|
||||||
.disabled(!has_write_access)
|
|
||||||
.fill()
|
|
||||||
.placeholder(!self.has_staged_changes() && !self.has_conflicts())
|
|
||||||
.elevation(ElevationIndex::Surface)
|
|
||||||
.on_click({
|
|
||||||
let entry = entry.clone();
|
|
||||||
cx.listener(move |this, _, window, cx| {
|
|
||||||
this.toggle_staged_for_entry(
|
|
||||||
&GitListEntry::GitStatusEntry(entry.clone()),
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
cx.stop_propagation();
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
let start_slot = h_flex()
|
let selected_bg_alpha = 0.08;
|
||||||
.id(("start-slot", ix))
|
let marked_bg_alpha = 0.12;
|
||||||
.gap(DynamicSpacing::Base04.rems(cx))
|
let state_opacity_step = 0.04;
|
||||||
.child(checkbox.tooltip(move |window, cx| {
|
|
||||||
let tooltip_name = if is_entry_staged.unwrap_or(false) {
|
|
||||||
"Unstage"
|
|
||||||
} else {
|
|
||||||
"Stage"
|
|
||||||
};
|
|
||||||
|
|
||||||
Tooltip::for_action(tooltip_name, &ToggleStaged, window, cx)
|
let base_bg = match (selected, marked) {
|
||||||
}))
|
(true, true) => cx
|
||||||
.child(git_status_icon(status, cx))
|
.theme()
|
||||||
.on_mouse_down(MouseButton::Left, |_, _, cx| {
|
.status()
|
||||||
// prevent the list item active state triggering when toggling checkbox
|
.info
|
||||||
cx.stop_propagation();
|
.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),
|
||||||
|
_ => cx.theme().colors().ghost_element_background,
|
||||||
|
};
|
||||||
|
|
||||||
div()
|
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)
|
||||||
|
} else {
|
||||||
|
cx.theme().colors().ghost_element_active
|
||||||
|
};
|
||||||
|
|
||||||
|
h_flex()
|
||||||
|
.id(id)
|
||||||
|
.h(self.list_item_height())
|
||||||
.w_full()
|
.w_full()
|
||||||
|
.items_center()
|
||||||
|
.px(rems(0.75)) // ~12px
|
||||||
|
.overflow_hidden()
|
||||||
|
.flex_none()
|
||||||
|
.gap(DynamicSpacing::Base04.rems(cx))
|
||||||
|
.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);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.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();
|
||||||
|
},
|
||||||
|
)
|
||||||
|
// .on_secondary_mouse_down(cx.listener(
|
||||||
|
// move |this, event: &MouseDownEvent, window, cx| {
|
||||||
|
// this.deploy_entry_context_menu(event.position, ix, window, cx);
|
||||||
|
// cx.stop_propagation();
|
||||||
|
// },
|
||||||
|
// ))
|
||||||
.child(
|
.child(
|
||||||
ListItem::new(ix)
|
div()
|
||||||
.spacing(ListItemSpacing::Sparse)
|
.id(checkbox_wrapper_id)
|
||||||
.start_slot(start_slot)
|
.flex_none()
|
||||||
.toggle_state(selected)
|
.occlude()
|
||||||
.focused(selected && self.focus_handle(cx).is_focused(window))
|
.cursor_pointer()
|
||||||
.disabled(!has_write_access)
|
|
||||||
.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);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.on_secondary_mouse_down(cx.listener(
|
|
||||||
move |this, event: &MouseDownEvent, window, cx| {
|
|
||||||
this.deploy_entry_context_menu(event.position, ix, window, cx);
|
|
||||||
cx.stop_propagation();
|
|
||||||
},
|
|
||||||
))
|
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
Checkbox::new(checkbox_id, is_staged)
|
||||||
.when_some(repo_path.parent(), |this, parent| {
|
.disabled(!has_write_access)
|
||||||
let parent_str = parent.to_string_lossy();
|
.fill()
|
||||||
if !parent_str.is_empty() {
|
.placeholder(!self.has_staged_changes() && !self.has_conflicts())
|
||||||
this.child(
|
.elevation(ElevationIndex::Surface)
|
||||||
self.entry_label(format!("{}/", parent_str), path_color)
|
.on_click({
|
||||||
.when(status.is_deleted(), |this| this.strikethrough()),
|
let entry = entry.clone();
|
||||||
)
|
cx.listener(move |this, _, window, cx| {
|
||||||
} else {
|
if !has_write_access {
|
||||||
this
|
return;
|
||||||
}
|
}
|
||||||
|
this.toggle_staged_for_entry(
|
||||||
|
&GitListEntry::GitStatusEntry(entry.clone()),
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
cx.stop_propagation();
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.child(
|
.tooltip(move |window, cx| {
|
||||||
self.entry_label(display_name.clone(), label_color)
|
let tooltip_name = if is_entry_staged.unwrap_or(false) {
|
||||||
|
"Unstage"
|
||||||
|
} else {
|
||||||
|
"Stage"
|
||||||
|
};
|
||||||
|
|
||||||
|
Tooltip::for_action(tooltip_name, &ToggleStaged, window, cx)
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(git_status_icon(status, cx))
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.items_center()
|
||||||
|
.overflow_hidden()
|
||||||
|
.when_some(repo_path.parent(), |this, parent| {
|
||||||
|
let parent_str = parent.to_string_lossy();
|
||||||
|
if !parent_str.is_empty() {
|
||||||
|
this.child(
|
||||||
|
self.entry_label(format!("{}/", parent_str), path_color)
|
||||||
.when(status.is_deleted(), |this| this.strikethrough()),
|
.when(status.is_deleted(), |this| this.strikethrough()),
|
||||||
),
|
)
|
||||||
|
} else {
|
||||||
|
this
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.child(
|
||||||
|
self.entry_label(display_name.clone(), label_color)
|
||||||
|
.when(status.is_deleted(), |this| this.strikethrough()),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
|
|
|
@ -10,7 +10,8 @@ use editor::{
|
||||||
use feature_flags::FeatureFlagViewExt;
|
use feature_flags::FeatureFlagViewExt;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use git::{
|
use git::{
|
||||||
status::FileStatus, Commit, StageAll, StageAndNext, ToggleStaged, UnstageAll, UnstageAndNext,
|
status::FileStatus, ShowCommitEditor, StageAll, StageAndNext, ToggleStaged, UnstageAll,
|
||||||
|
UnstageAndNext,
|
||||||
};
|
};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, Action, AnyElement, AnyView, App, AppContext as _, AsyncWindowContext, Entity,
|
actions, Action, AnyElement, AnyView, App, AppContext as _, AsyncWindowContext, Entity,
|
||||||
|
@ -952,11 +953,11 @@ impl Render for ProjectDiffToolbar {
|
||||||
.disabled(!button_states.commit)
|
.disabled(!button_states.commit)
|
||||||
.tooltip(Tooltip::for_action_title_in(
|
.tooltip(Tooltip::for_action_title_in(
|
||||||
"Commit",
|
"Commit",
|
||||||
&Commit,
|
&ShowCommitEditor,
|
||||||
&focus_handle,
|
&focus_handle,
|
||||||
))
|
))
|
||||||
.on_click(cx.listener(|this, _, window, cx| {
|
.on_click(cx.listener(|this, _, window, cx| {
|
||||||
this.dispatch_action(&Commit, window, cx);
|
this.dispatch_action(&ShowCommitEditor, window, cx);
|
||||||
})),
|
})),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue