This PR updates the ui of the git panel. It removes the header from the panel and unifies the repository, branch and commit controls in the bottom section. It also adds a secondary menu to the primary button giving access to a variety of actions for managing local and remote changes:  Known issues (will be fixed in a later pr) - Spinner showing git operation progress was removed, will be re-added - Clicking expand with the panel editor focused will commit (due to shared action name. Already tracked) Before | After  (Also adds `component`, `linkme` to cargo-machete ignore as they are used in the `IntoComponent` proc-macro and will always be incorrectly flagged as unused) Release Notes: - N/A --------- Co-authored-by: Cole Miller <m@cole-miller.net> Co-authored-by: Cole Miller <53574922+cole-miller@users.noreply.github.com> Co-authored-by: Cole Miller <cole@zed.dev>
237 lines
6.7 KiB
Rust
237 lines
6.7 KiB
Rust
use gpui::{Action, AnyElement, AnyView, AppContext as _, FocusHandle, IntoElement, Render};
|
|
use settings::Settings;
|
|
use theme::ThemeSettings;
|
|
|
|
use crate::prelude::*;
|
|
use crate::{h_flex, v_flex, Color, KeyBinding, Label, LabelSize, StyledExt};
|
|
|
|
#[derive(IntoComponent)]
|
|
pub struct Tooltip {
|
|
title: SharedString,
|
|
meta: Option<SharedString>,
|
|
key_binding: Option<KeyBinding>,
|
|
}
|
|
|
|
impl Tooltip {
|
|
pub fn simple(title: impl Into<SharedString>, cx: &mut App) -> AnyView {
|
|
cx.new(|_| Self {
|
|
title: title.into(),
|
|
meta: None,
|
|
key_binding: None,
|
|
})
|
|
.into()
|
|
}
|
|
|
|
pub fn text(title: impl Into<SharedString>) -> impl Fn(&mut Window, &mut App) -> AnyView {
|
|
let title = title.into();
|
|
move |_, cx| {
|
|
cx.new(|_| Self {
|
|
title: title.clone(),
|
|
meta: None,
|
|
key_binding: None,
|
|
})
|
|
.into()
|
|
}
|
|
}
|
|
|
|
pub fn for_action_title(
|
|
title: impl Into<SharedString>,
|
|
action: &dyn Action,
|
|
) -> impl Fn(&mut Window, &mut App) -> AnyView {
|
|
let title = title.into();
|
|
let action = action.boxed_clone();
|
|
move |window, cx| {
|
|
cx.new(|cx| Self {
|
|
title: title.clone(),
|
|
meta: None,
|
|
key_binding: KeyBinding::for_action(action.as_ref(), window, cx),
|
|
})
|
|
.into()
|
|
}
|
|
}
|
|
|
|
pub fn for_action_title_in(
|
|
title: impl Into<SharedString>,
|
|
action: &dyn Action,
|
|
focus_handle: &FocusHandle,
|
|
) -> impl Fn(&mut Window, &mut App) -> AnyView {
|
|
let title = title.into();
|
|
let action = action.boxed_clone();
|
|
let focus_handle = focus_handle.clone();
|
|
move |window, cx| {
|
|
cx.new(|cx| Self {
|
|
title: title.clone(),
|
|
meta: None,
|
|
key_binding: KeyBinding::for_action_in(action.as_ref(), &focus_handle, window, cx),
|
|
})
|
|
.into()
|
|
}
|
|
}
|
|
|
|
pub fn for_action(
|
|
title: impl Into<SharedString>,
|
|
action: &dyn Action,
|
|
window: &mut Window,
|
|
cx: &mut App,
|
|
) -> AnyView {
|
|
cx.new(|cx| Self {
|
|
title: title.into(),
|
|
meta: None,
|
|
key_binding: KeyBinding::for_action(action, window, cx),
|
|
})
|
|
.into()
|
|
}
|
|
|
|
pub fn for_action_in(
|
|
title: impl Into<SharedString>,
|
|
action: &dyn Action,
|
|
focus_handle: &FocusHandle,
|
|
window: &mut Window,
|
|
cx: &mut App,
|
|
) -> AnyView {
|
|
cx.new(|cx| Self {
|
|
title: title.into(),
|
|
meta: None,
|
|
key_binding: KeyBinding::for_action_in(action, focus_handle, window, cx),
|
|
})
|
|
.into()
|
|
}
|
|
|
|
pub fn with_meta(
|
|
title: impl Into<SharedString>,
|
|
action: Option<&dyn Action>,
|
|
meta: impl Into<SharedString>,
|
|
window: &mut Window,
|
|
cx: &mut App,
|
|
) -> AnyView {
|
|
cx.new(|cx| Self {
|
|
title: title.into(),
|
|
meta: Some(meta.into()),
|
|
key_binding: action.and_then(|action| KeyBinding::for_action(action, window, cx)),
|
|
})
|
|
.into()
|
|
}
|
|
|
|
pub fn with_meta_in(
|
|
title: impl Into<SharedString>,
|
|
action: Option<&dyn Action>,
|
|
meta: impl Into<SharedString>,
|
|
focus_handle: &FocusHandle,
|
|
window: &mut Window,
|
|
cx: &mut App,
|
|
) -> AnyView {
|
|
cx.new(|cx| Self {
|
|
title: title.into(),
|
|
meta: Some(meta.into()),
|
|
key_binding: action
|
|
.and_then(|action| KeyBinding::for_action_in(action, focus_handle, window, cx)),
|
|
})
|
|
.into()
|
|
}
|
|
|
|
pub fn new(title: impl Into<SharedString>) -> Self {
|
|
Self {
|
|
title: title.into(),
|
|
meta: None,
|
|
key_binding: None,
|
|
}
|
|
}
|
|
|
|
pub fn meta(mut self, meta: impl Into<SharedString>) -> Self {
|
|
self.meta = Some(meta.into());
|
|
self
|
|
}
|
|
|
|
pub fn key_binding(mut self, key_binding: impl Into<Option<KeyBinding>>) -> Self {
|
|
self.key_binding = key_binding.into();
|
|
self
|
|
}
|
|
}
|
|
|
|
impl Render for Tooltip {
|
|
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
|
tooltip_container(window, cx, |el, _, _| {
|
|
el.child(
|
|
h_flex()
|
|
.gap_4()
|
|
.child(div().max_w_72().child(self.title.clone()))
|
|
.when_some(self.key_binding.clone(), |this, key_binding| {
|
|
this.justify_between().child(key_binding)
|
|
}),
|
|
)
|
|
.when_some(self.meta.clone(), |this, meta| {
|
|
this.child(Label::new(meta).size(LabelSize::Small).color(Color::Muted))
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
pub fn tooltip_container<V>(
|
|
window: &mut Window,
|
|
cx: &mut Context<V>,
|
|
f: impl FnOnce(Div, &mut Window, &mut Context<V>) -> Div,
|
|
) -> impl IntoElement {
|
|
let ui_font = ThemeSettings::get_global(cx).ui_font.clone();
|
|
|
|
// padding to avoid tooltip appearing right below the mouse cursor
|
|
div().pl_2().pt_2p5().child(
|
|
v_flex()
|
|
.elevation_2(cx)
|
|
.font(ui_font)
|
|
.text_ui(cx)
|
|
.text_color(cx.theme().colors().text)
|
|
.py_1()
|
|
.px_2()
|
|
.map(|el| f(el, window, cx)),
|
|
)
|
|
}
|
|
|
|
pub struct LinkPreview {
|
|
link: SharedString,
|
|
}
|
|
|
|
impl LinkPreview {
|
|
pub fn new(url: &str, cx: &mut App) -> AnyView {
|
|
let mut wrapped_url = String::new();
|
|
for (i, ch) in url.chars().enumerate() {
|
|
if i == 500 {
|
|
wrapped_url.push('…');
|
|
break;
|
|
}
|
|
if i % 100 == 0 && i != 0 {
|
|
wrapped_url.push('\n');
|
|
}
|
|
wrapped_url.push(ch);
|
|
}
|
|
cx.new(|_| LinkPreview {
|
|
link: wrapped_url.into(),
|
|
})
|
|
.into()
|
|
}
|
|
}
|
|
|
|
impl Render for LinkPreview {
|
|
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
|
tooltip_container(window, cx, |el, _, _| {
|
|
el.child(
|
|
Label::new(self.link.clone())
|
|
.size(LabelSize::XSmall)
|
|
.color(Color::Muted),
|
|
)
|
|
})
|
|
}
|
|
}
|
|
|
|
// View this component preview using `workspace: open component-preview`
|
|
impl ComponentPreview for Tooltip {
|
|
fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement {
|
|
example_group(vec![single_example(
|
|
"Text only",
|
|
Button::new("delete-example", "Delete")
|
|
.tooltip(Tooltip::text("This is a tooltip!"))
|
|
.into_any_element(),
|
|
)])
|
|
.into_any_element()
|
|
}
|
|
}
|