From 9d8a163f5b17dc75b190cf9e8e2b85ae83f3b1cb Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Fri, 28 Feb 2025 15:00:39 -0500 Subject: [PATCH] git_ui: New panel design (#25821) 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: ![CleanShot 2025-02-28 at 12 18 15@2x](https://github.com/user-attachments/assets/0260c122-405f-46fc-8cc8-d6beac782b9d) 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 ![CleanShot 2025-02-28 at 12 22 18@2x](https://github.com/user-attachments/assets/4c1e4ac9-b975-487f-bf4e-8815a8da4f4f) (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 Co-authored-by: Cole Miller <53574922+cole-miller@users.noreply.github.com> Co-authored-by: Cole Miller --- Cargo.lock | 3 + Cargo.toml | 2 +- assets/icons/git_branch_small.svg | 6 + crates/component/src/component.rs | 23 +- .../src/component_preview.rs | 2 +- crates/git/src/git.rs | 1 + crates/git/src/repository.rs | 6 + crates/git_ui/Cargo.toml | 3 + crates/git_ui/src/commit_modal.rs | 10 +- crates/git_ui/src/git_panel.rs | 1103 ++++++++++++----- crates/panel/src/panel.rs | 2 +- crates/ui/src/components/avatar.rs | 2 +- crates/ui/src/components/button/button.rs | 2 +- .../ui/src/components/button/icon_button.rs | 2 +- .../ui/src/components/button/toggle_button.rs | 2 +- crates/ui/src/components/content_group.rs | 2 +- crates/ui/src/components/facepile.rs | 2 +- crates/ui/src/components/icon.rs | 3 +- .../ui/src/components/icon/decorated_icon.rs | 2 +- crates/ui/src/components/keybinding_hint.rs | 2 +- crates/ui/src/components/label/label.rs | 2 +- crates/ui/src/components/tab.rs | 2 +- crates/ui/src/components/table.rs | 2 +- crates/ui/src/components/toggle.rs | 6 +- crates/ui/src/components/tooltip.rs | 2 +- crates/ui/src/styles/typography.rs | 2 +- 26 files changed, 828 insertions(+), 368 deletions(-) create mode 100644 assets/icons/git_branch_small.svg diff --git a/Cargo.lock b/Cargo.lock index 04cfb6d6a7580807c32ece5645852a0b5d44e4ee..821a8f9e476b2ca7d1c6c5e957aaccc1473ccce0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5395,6 +5395,7 @@ dependencies = [ "anyhow", "buffer_diff", "collections", + "component", "db", "editor", "feature_flags", @@ -5404,6 +5405,7 @@ dependencies = [ "gpui", "itertools 0.14.0", "language", + "linkme", "menu", "multi_buffer", "panel", @@ -5415,6 +5417,7 @@ dependencies = [ "serde_derive", "serde_json", "settings", + "smallvec", "strum", "theme", "time", diff --git a/Cargo.toml b/Cargo.toml index 5d958f3bb7a5a853c45ffab8902b7c8e6034ae4b..5a69ac7ff7436eed87e3341d9a876145611c198b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -749,4 +749,4 @@ should_implement_trait = { level = "allow" } let_underscore_future = "allow" [workspace.metadata.cargo-machete] -ignored = ["bindgen", "cbindgen", "prost_build", "serde"] +ignored = ["bindgen", "cbindgen", "prost_build", "serde", "component", "linkme"] diff --git a/assets/icons/git_branch_small.svg b/assets/icons/git_branch_small.svg new file mode 100644 index 0000000000000000000000000000000000000000..d23fc176ac797fff35c6c9d35176d5e03c6170fe --- /dev/null +++ b/assets/icons/git_branch_small.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/crates/component/src/component.rs b/crates/component/src/component.rs index d53a951f2ad78e493c44f2c716dd898169ed0294..b0e92ce500dc0d6181f78dea4cbe16d98c88ee47 100644 --- a/crates/component/src/component.rs +++ b/crates/component/src/component.rs @@ -18,7 +18,7 @@ pub trait Component { } pub trait ComponentPreview: Component { - fn preview(_window: &mut Window, _cx: &App) -> AnyElement; + fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement; } #[distributed_slice] @@ -32,7 +32,7 @@ pub static COMPONENT_DATA: LazyLock> = pub struct ComponentRegistry { components: Vec<(Option<&'static str>, &'static str, Option<&'static str>)>, - previews: HashMap<&'static str, fn(&mut Window, &App) -> AnyElement>, + previews: HashMap<&'static str, fn(&mut Window, &mut App) -> AnyElement>, } impl ComponentRegistry { @@ -62,7 +62,10 @@ pub fn register_component() { } pub fn register_preview() { - let preview_data = (T::name(), T::preview as fn(&mut Window, &App) -> AnyElement); + let preview_data = ( + T::name(), + T::preview as fn(&mut Window, &mut App) -> AnyElement, + ); COMPONENT_DATA .write() .previews @@ -77,7 +80,7 @@ pub struct ComponentMetadata { name: SharedString, scope: Option, description: Option, - preview: Option AnyElement>, + preview: Option AnyElement>, } impl ComponentMetadata { @@ -93,7 +96,7 @@ impl ComponentMetadata { self.description.clone() } - pub fn preview(&self) -> Option AnyElement> { + pub fn preview(&self) -> Option AnyElement> { self.preview } } @@ -235,6 +238,7 @@ pub struct ComponentExampleGroup { pub title: Option, pub examples: Vec, pub grow: bool, + pub vertical: bool, } impl RenderOnce for ComponentExampleGroup { @@ -270,6 +274,7 @@ impl RenderOnce for ComponentExampleGroup { .child( div() .flex() + .when(self.vertical, |this| this.flex_col()) .items_start() .w_full() .gap_6() @@ -287,6 +292,7 @@ impl ComponentExampleGroup { title: None, examples, grow: false, + vertical: false, } } @@ -296,6 +302,7 @@ impl ComponentExampleGroup { title: Some(title.into()), examples, grow: false, + vertical: false, } } @@ -304,6 +311,12 @@ impl ComponentExampleGroup { self.grow = true; self } + + /// Lay the group out vertically. + pub fn vertical(mut self) -> Self { + self.vertical = true; + self + } } /// Create a single example diff --git a/crates/component_preview/src/component_preview.rs b/crates/component_preview/src/component_preview.rs index 6a3dcfc406aa174825bf738f632c8e690d3d6fc6..bc4f390a61ce3839e495721493b3bfe7d763b789 100644 --- a/crates/component_preview/src/component_preview.rs +++ b/crates/component_preview/src/component_preview.rs @@ -93,7 +93,7 @@ impl ComponentPreview { &self, ix: usize, window: &mut Window, - cx: &Context, + cx: &mut Context, ) -> impl IntoElement { let component = self.get_component(ix); diff --git a/crates/git/src/git.rs b/crates/git/src/git.rs index d68d9f7b655e589a9821ee6a1c1e54e0bec7127e..511138249349b06c77811c86eeb8f70fd7661e81 100644 --- a/crates/git/src/git.rs +++ b/crates/git/src/git.rs @@ -56,6 +56,7 @@ actions!( Pull, Fetch, Commit, + ExpandCommitEditor, ] ); action_with_deprecated_aliases!(git, RestoreFile, ["editor::RevertFile"]); diff --git a/crates/git/src/repository.rs b/crates/git/src/repository.rs index 0edbd62fb9a02a790869c297fca580b72482c768..c2c5173fa11d4e83ea8052cf889ae335f45724e8 100644 --- a/crates/git/src/repository.rs +++ b/crates/git/src/repository.rs @@ -74,6 +74,12 @@ impl UpstreamTracking { } } +impl From for UpstreamTracking { + fn from(status: UpstreamTrackingStatus) -> Self { + UpstreamTracking::Tracked(status) + } +} + #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] pub struct UpstreamTrackingStatus { pub ahead: u32, diff --git a/crates/git_ui/Cargo.toml b/crates/git_ui/Cargo.toml index f0bf4225d694bdd24cce5c673e39731c37573796..66845c939f8df2cd0c8768bf775e0a5355684866 100644 --- a/crates/git_ui/Cargo.toml +++ b/crates/git_ui/Cargo.toml @@ -20,6 +20,7 @@ test-support = ["multi_buffer/test-support"] anyhow.workspace = true buffer_diff.workspace = true collections.workspace = true +component.workspace = true db.workspace = true editor.workspace = true feature_flags.workspace = true @@ -29,6 +30,7 @@ git.workspace = true gpui.workspace = true itertools.workspace = true language.workspace = true +linkme.workspace = true menu.workspace = true multi_buffer.workspace = true panel.workspace = true @@ -40,6 +42,7 @@ serde.workspace = true serde_derive.workspace = true serde_json.workspace = true settings.workspace = true +smallvec.workspace = true strum.workspace = true theme.workspace = true time.workspace = true diff --git a/crates/git_ui/src/commit_modal.rs b/crates/git_ui/src/commit_modal.rs index 6b0e84929f3fcf450124679538861072bd5d7d9a..8df0d38a6ff3eb577129a413d07060442a0c9138 100644 --- a/crates/git_ui/src/commit_modal.rs +++ b/crates/git_ui/src/commit_modal.rs @@ -2,7 +2,7 @@ use crate::branch_picker::{self, BranchList}; use crate::git_panel::{commit_message_editor, GitPanel}; -use git::Commit; +use git::{Commit, ExpandCommitEditor}; use panel::{panel_button, panel_editor_style, panel_filled_button}; use project::Project; use ui::{prelude::*, KeybindingHint, PopoverButton, Tooltip, TriggerablePopover}; @@ -110,14 +110,17 @@ struct RestoreDock { impl CommitModal { pub fn register(workspace: &mut Workspace, _: &mut Window, _cx: &mut Context) { - workspace.register_action(|workspace, _: &Commit, window, cx| { + workspace.register_action(|workspace, _: &ExpandCommitEditor, window, cx| { let Some(git_panel) = workspace.panel::(cx) else { return; }; - let (can_commit, conflict) = git_panel.update(cx, |git_panel, _cx| { + let (can_commit, conflict) = git_panel.update(cx, |git_panel, cx| { let can_commit = git_panel.can_commit(); let conflict = git_panel.has_unstaged_conflicts(); + if can_commit { + git_panel.set_modal_open(true, cx); + } (can_commit, conflict) }); if !can_commit { @@ -131,6 +134,7 @@ impl CommitModal { prompt.await.ok(); }) .detach(); + return; } let dock = workspace.dock_at_position(git_panel.position(window, cx)); diff --git a/crates/git_ui/src/git_panel.rs b/crates/git_ui/src/git_panel.rs index f5a45f45f2140bf1f356dede41dc2e6cff5412d1..cecd4411e3482bb65564336ae782a1cc09c649d0 100644 --- a/crates/git_ui/src/git_panel.rs +++ b/crates/git_ui/src/git_panel.rs @@ -1,5 +1,4 @@ use crate::git_panel_settings::StatusStyle; -use crate::project_diff::Diff; use crate::repository_selector::RepositorySelectorPopoverMenu; use crate::{ git_panel_settings::GitPanelSettings, git_status_icon, repository_selector::RepositorySelector, @@ -11,21 +10,27 @@ use editor::{ scroll::ScrollbarAutoHide, Editor, EditorElement, EditorMode, EditorSettings, MultiBuffer, ShowScrollbar, }; -use git::repository::{Branch, CommitDetails, PushOptions, Remote, ResetMode, UpstreamTracking}; +use git::repository::{ + Branch, CommitDetails, CommitSummary, PushOptions, Remote, ResetMode, Upstream, + UpstreamTracking, UpstreamTrackingStatus, +}; use git::{repository::RepoPath, status::FileStatus, Commit, ToggleStaged}; -use git::{Push, RestoreTrackedFiles, StageAll, TrashUntrackedFiles, UnstageAll}; +use git::{RestoreTrackedFiles, StageAll, TrashUntrackedFiles, UnstageAll}; use gpui::*; use itertools::Itertools; use language::{Buffer, File}; use menu::{Confirm, SecondaryConfirm, SelectFirst, SelectLast, SelectNext, SelectPrev}; use multi_buffer::ExcerptInfo; -use panel::{panel_editor_container, panel_editor_style, panel_filled_button, PanelHeader}; +use panel::{ + panel_editor_container, panel_editor_style, panel_filled_button, panel_icon_button, PanelHeader, +}; use project::{ git::{GitEvent, Repository}, Fs, Project, ProjectPath, }; use serde::{Deserialize, Serialize}; use settings::Settings as _; +use smallvec::smallvec; use std::cell::RefCell; use std::future::Future; use std::rc::Rc; @@ -33,8 +38,8 @@ use std::{collections::HashSet, sync::Arc, time::Duration, usize}; use strum::{IntoEnumIterator, VariantNames}; use time::OffsetDateTime; use ui::{ - prelude::*, ButtonLike, Checkbox, ContextMenu, Divider, DividerColor, ElevationIndex, ListItem, - ListItemSpacing, PopoverMenu, Scrollbar, ScrollbarState, Tooltip, + prelude::*, ButtonLike, Checkbox, ContextMenu, ElevationIndex, ListItem, ListItemSpacing, + PopoverMenu, Scrollbar, ScrollbarState, Tooltip, }; use util::{maybe, post_inc, ResultExt, TryFutureExt}; use workspace::{ @@ -70,6 +75,19 @@ enum TrashCancel { Cancel, } +fn git_panel_context_menu(window: &mut Window, cx: &mut App) -> Entity { + ContextMenu::build(window, cx, |context_menu, _, _| { + context_menu + .action("Stage All", StageAll.boxed_clone()) + .action("Unstage All", UnstageAll.boxed_clone()) + .separator() + .action("Open Diff", project_diff::Diff.boxed_clone()) + .separator() + .action("Discard Tracked Changes", RestoreTrackedFiles.boxed_clone()) + .action("Trash Untracked Files", TrashUntrackedFiles.boxed_clone()) + }) +} + const GIT_PANEL_KEY: &str = "GitPanel"; const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50); @@ -1779,88 +1797,7 @@ impl GitPanel { }); } - pub fn panel_button( - &self, - id: impl Into, - label: impl Into, - ) -> Button { - let id = id.into().clone(); - let label = label.into().clone(); - - Button::new(id, label) - .label_size(LabelSize::Small) - .layer(ElevationIndex::ElevatedSurface) - .size(ButtonSize::Compact) - .style(ButtonStyle::Filled) - } - - pub fn indent_size(&self, window: &Window, cx: &mut Context) -> Pixels { - Checkbox::container_size(cx).to_pixels(window.rem_size()) - } - - pub fn render_divider(&self, _cx: &mut Context) -> impl IntoElement { - h_flex() - .items_center() - .h(px(8.)) - .child(Divider::horizontal_dashed().color(DividerColor::Border)) - } - - pub fn render_panel_header( - &self, - window: &mut Window, - cx: &mut Context, - ) -> Option { - let all_repositories = self - .project - .read(cx) - .git_store() - .read(cx) - .all_repositories(); - - let has_repo_above = all_repositories.iter().any(|repo| { - repo.read(cx) - .repository_entry - .work_directory - .is_above_project() - }); - - let has_visible_repo = all_repositories.len() > 0 || has_repo_above; - - if has_visible_repo { - Some( - self.panel_header_container(window, cx) - .child( - Label::new("Repository") - .size(LabelSize::Small) - .color(Color::Muted), - ) - .child(self.render_repository_selector(cx)) - .child(div().flex_grow()) // spacer - .child( - div() - .h_flex() - .gap_1() - .children(self.render_spinner(cx)) - .children(self.render_sync_button(cx)) - .children(self.render_pull_button(cx)) - .child( - Button::new("diff", "+/-") - .tooltip(Tooltip::for_action_title("Open diff", &Diff)) - .on_click(|_, _, cx| { - cx.defer(|cx| { - cx.dispatch_action(&Diff); - }) - }), - ) - .child(self.render_overflow_menu()), - ), - ) - } else { - None - } - } - - pub fn render_spinner(&self, _cx: &mut Context) -> Option { + fn render_spinner(&self) -> Option { (!self.pending_remote_operations.borrow().is_empty()).then(|| { Icon::new(IconName::ArrowCircle) .size(IconSize::XSmall) @@ -1874,83 +1811,6 @@ impl GitPanel { }) } - pub fn render_overflow_menu(&self) -> impl IntoElement { - PopoverMenu::new("overflow-menu") - .trigger(IconButton::new("overflow-menu-trigger", IconName::Ellipsis)) - .menu(move |window, cx| Some(Self::panel_context_menu(window, cx))) - .anchor(Corner::TopRight) - } - - pub fn render_sync_button(&self, cx: &mut Context) -> Option { - let active_repository = self.project.read(cx).active_repository(cx); - active_repository.as_ref().map(|_| { - panel_filled_button("Fetch") - .icon(IconName::ArrowCircle) - .icon_size(IconSize::Small) - .icon_color(Color::Muted) - .icon_position(IconPosition::Start) - .tooltip(Tooltip::for_action_title("git fetch", &git::Fetch)) - .on_click( - cx.listener(move |this, _, window, cx| this.fetch(&git::Fetch, window, cx)), - ) - .into_any_element() - }) - } - - pub fn render_pull_button(&self, cx: &mut Context) -> Option { - let active_repository = self.project.read(cx).active_repository(cx); - active_repository - .as_ref() - .and_then(|repo| repo.read(cx).current_branch()) - .and_then(|branch| { - branch.upstream.as_ref().map(|upstream| { - let status = &upstream.tracking; - - let disabled = status.is_gone(); - - panel_filled_button(match status { - git::repository::UpstreamTracking::Tracked(status) if status.behind > 0 => { - format!("Pull ({})", status.behind) - } - _ => "Pull".to_string(), - }) - .icon(IconName::ArrowDown) - .icon_size(IconSize::Small) - .icon_color(Color::Muted) - .icon_position(IconPosition::Start) - .disabled(status.is_gone()) - .tooltip(move |window, cx| { - if disabled { - Tooltip::simple("Upstream is gone", cx) - } else { - // TODO: Add and argument substitutions to this - Tooltip::for_action("git pull", &git::Pull, window, cx) - } - }) - .on_click( - cx.listener(move |this, _, window, cx| this.pull(&git::Pull, window, cx)), - ) - .into_any_element() - }) - }) - } - - pub fn render_repository_selector(&self, cx: &mut Context) -> impl IntoElement { - let active_repository = self.project.read(cx).active_repository(cx); - let repository_display_name = active_repository - .as_ref() - .map(|repo| repo.read(cx).display_name(self.project.read(cx), cx)) - .unwrap_or_default(); - - RepositorySelectorPopoverMenu::new( - self.repository_selector.clone(), - ButtonLike::new("active-repository") - .style(ButtonStyle::Subtle) - .child(Label::new(repository_display_name).size(LabelSize::Small)), - Tooltip::text("Select a repository"), - ) - } - pub fn can_commit(&self) -> bool { (self.has_staged_changes() || self.has_tracked_changes()) && !self.has_unstaged_conflicts() } @@ -1997,103 +1857,131 @@ impl GitPanel { } } - pub fn render_commit_editor( + pub fn render_footer( &self, window: &mut Window, cx: &mut Context, - ) -> impl IntoElement { - let editor = self.commit_editor.clone(); - let can_commit = self.can_commit() - && self.pending_commit.is_none() - && !editor.read(cx).is_empty(cx) - && self.has_write_access(cx); - + ) -> Option { + let project = self.project.clone().read(cx); + let active_repository = self.active_repository.clone(); let panel_editor_style = panel_editor_style(true, window, cx); - let enable_coauthors = self.render_co_authors(cx); - let tooltip = if self.has_staged_changes() { - "git commit" - } else { - "git commit --all" - }; - let title = if self.has_staged_changes() { - "Commit" - } else { - "Commit Tracked" - }; - let editor_focus_handle = self.commit_editor.focus_handle(cx); + if let Some(active_repo) = active_repository { + let editor = self.commit_editor.clone(); + let can_commit = self.can_commit() + && self.pending_commit.is_none() + && !editor.read(cx).is_empty(cx) + && self.has_write_access(cx); - let commit_button = panel_filled_button(title) - .tooltip(move |window, cx| { - Tooltip::for_action_in(tooltip, &Commit, &editor_focus_handle, window, cx) - }) - .disabled(!can_commit) - .on_click({ - cx.listener(move |this, _: &ClickEvent, window, cx| this.commit_changes(window, cx)) - }); + let enable_coauthors = self.render_co_authors(cx); - let branch = self - .active_repository - .as_ref() - .and_then(|repo| repo.read(cx).current_branch().map(|b| b.name.clone())) - .unwrap_or_else(|| "".into()); - - let branch_selector = Button::new("branch-selector", branch) - .color(Color::Muted) - .style(ButtonStyle::Subtle) - .icon(IconName::GitBranch) - .icon_size(IconSize::Small) - .icon_color(Color::Muted) - .size(ButtonSize::Compact) - .icon_position(IconPosition::Start) - .tooltip(Tooltip::for_action_title( - "Switch Branch", - &zed_actions::git::Branch, - )) - .on_click(cx.listener(|_, _, window, cx| { - window.dispatch_action(zed_actions::git::Branch.boxed_clone(), cx); - })) - .style(ButtonStyle::Transparent); + let tooltip = if self.has_staged_changes() { + "git commit" + } else { + "git commit --all" + }; + let title = if self.has_staged_changes() { + "Commit" + } else { + "Commit Tracked" + }; + let editor_focus_handle = self.commit_editor.focus_handle(cx); - let footer_size = px(32.); - let gap = px(16.0); + let branch = active_repo.read(cx).current_branch()?; - let max_height = window.line_height() * 6. + gap + footer_size; + let footer_size = px(32.); + let gap = px(8.0); - panel_editor_container(window, cx) - .id("commit-editor-container") - .relative() - .h(max_height) - .w_full() - .border_t_1() - .border_color(cx.theme().colors().border) - .bg(cx.theme().colors().editor_background) - .cursor_text() - .on_click(cx.listener(move |this, _: &ClickEvent, window, cx| { - window.focus(&this.commit_editor.focus_handle(cx)); - })) - .when(!self.modal_open, |el| { - el.child(EditorElement::new(&self.commit_editor, panel_editor_style)) - .child( - h_flex() - .absolute() - .bottom_0() - .left_2() - .h(footer_size) - .flex_none() - .child(branch_selector), - ) - .child( - h_flex() - .absolute() - .bottom_0() - .right_2() - .h(footer_size) - .flex_none() - .children(enable_coauthors) - .child(commit_button), - ) - }) + let max_height = window.line_height() * 5. + gap + footer_size; + + let expand_button_size = px(16.); + + let git_panel = cx.entity().clone(); + let display_name = SharedString::from(Arc::from( + active_repo + .read(cx) + .display_name(project, cx) + .trim_end_matches("/"), + )); + let footer = v_flex() + .child(PanelRepoHeader::new( + "header-button", + display_name, + Some(branch.clone()), + Some(git_panel), + )) + .child( + panel_editor_container(window, cx) + .id("commit-editor-container") + .relative() + .h(max_height) + // .w_full() + // .border_t_1() + // .border_color(cx.theme().colors().border) + .bg(cx.theme().colors().editor_background) + .cursor_text() + .on_click(cx.listener(move |this, _: &ClickEvent, window, cx| { + window.focus(&this.commit_editor.focus_handle(cx)); + })) + .child( + h_flex() + .id("commit-footer") + .absolute() + .bottom_0() + .right_2() + .h(footer_size) + .flex_none() + .children(enable_coauthors) + .child( + panel_filled_button(title) + .tooltip(move |window, cx| { + Tooltip::for_action_in( + tooltip, + &Commit, + &editor_focus_handle, + window, + cx, + ) + }) + .disabled(!can_commit || self.modal_open) + .on_click({ + cx.listener(move |this, _: &ClickEvent, window, cx| { + this.commit_changes(window, cx) + }) + }), + ), + ) + // .when(!self.modal_open, |el| { + .child(EditorElement::new(&self.commit_editor, panel_editor_style)) + .child( + div() + .absolute() + .top_1() + .right_2() + .opacity(0.5) + .hover(|this| this.opacity(1.0)) + .w(expand_button_size) + .child( + panel_icon_button("expand-commit-editor", IconName::Maximize) + .icon_size(IconSize::Small) + .style(ButtonStyle::Transparent) + .width(expand_button_size.into()) + .on_click(cx.listener({ + move |_, _, window, cx| { + window.dispatch_action( + git::ExpandCommitEditor.boxed_clone(), + cx, + ) + } + })), + ), + ), + ); + + Some(footer) + } else { + None + } } fn render_previous_commit(&self, cx: &mut Context) -> Option { @@ -2105,10 +1993,10 @@ impl GitPanel { Some( h_flex() .items_center() - .py_1p5() + .py_2() .px(px(8.)) - .bg(cx.theme().colors().background) - .border_t_1() + // .bg(cx.theme().colors().background) + // .border_t_1() .border_color(cx.theme().colors().border) .gap_1p5() .child( @@ -2135,11 +2023,9 @@ impl GitPanel { ) .child(div().flex_1()) .child( - panel_filled_button("Uncommit") - .icon(IconName::Undo) + panel_icon_button("undo", IconName::Undo) .icon_size(IconSize::Small) .icon_color(Color::Muted) - .icon_position(IconPosition::Start) .tooltip(Tooltip::for_action_title( if self.has_staged_changes() { "git reset HEAD^ --soft" @@ -2149,8 +2035,7 @@ impl GitPanel { &git::Uncommit, )) .on_click(cx.listener(|this, _, window, cx| this.uncommit(window, cx))), - ) - .child(self.render_push_button(branch, cx)), + ), ) } @@ -2228,7 +2113,7 @@ impl GitPanel { ) } - pub fn render_buffer_header_controls( + fn render_buffer_header_controls( &self, entity: &Entity, file: &Arc, @@ -2402,26 +2287,13 @@ impl GitPanel { self.set_context_menu(context_menu, position, window, cx); } - fn panel_context_menu(window: &mut Window, cx: &mut App) -> Entity { - ContextMenu::build(window, cx, |context_menu, _, _| { - context_menu - .action("Stage All", StageAll.boxed_clone()) - .action("Unstage All", UnstageAll.boxed_clone()) - .separator() - .action("Open Diff", project_diff::Diff.boxed_clone()) - .separator() - .action("Restore Tracked Files", RestoreTrackedFiles.boxed_clone()) - .action("Trash Untracked Files", TrashUntrackedFiles.boxed_clone()) - }) - } - fn deploy_panel_context_menu( &mut self, position: Point, window: &mut Window, cx: &mut Context, ) { - let context_menu = Self::panel_context_menu(window, cx); + let context_menu = git_panel_context_menu(window, cx); self.set_context_menu(context_menu, position, window, cx); } @@ -2584,69 +2456,6 @@ impl GitPanel { .into_any_element() } - fn render_push_button(&self, branch: &Branch, cx: &Context) -> AnyElement { - let mut disabled = false; - - // TODO: Add and argument substitutions to this - let button: SharedString; - let tooltip: SharedString; - let action: Option; - if let Some(upstream) = &branch.upstream { - match upstream.tracking { - UpstreamTracking::Gone => { - button = "Republish".into(); - tooltip = "git push --set-upstream".into(); - action = Some(git::Push { - options: Some(PushOptions::SetUpstream), - }); - } - UpstreamTracking::Tracked(tracking) => { - if tracking.behind > 0 { - disabled = true; - button = "Push".into(); - tooltip = "Upstream is ahead of local branch".into(); - action = None; - } else if tracking.ahead > 0 { - button = format!("Push ({})", tracking.ahead).into(); - tooltip = "git push".into(); - action = Some(git::Push { options: None }); - } else { - disabled = true; - button = "Push".into(); - tooltip = "Upstream matches local branch".into(); - action = None; - } - } - } - } else { - button = "Publish".into(); - tooltip = "git push --set-upstream".into(); - action = Some(git::Push { - options: Some(PushOptions::SetUpstream), - }); - }; - - panel_filled_button(button) - .icon(IconName::ArrowUp) - .icon_size(IconSize::Small) - .icon_color(Color::Muted) - .icon_position(IconPosition::Start) - .disabled(disabled) - .when_some(action, |this, action| { - this.on_click( - cx.listener(move |this, _, window, cx| this.push(&action, window, cx)), - ) - }) - .tooltip(move |window, cx| { - if let Some(action) = action.as_ref() { - Tooltip::for_action(tooltip.clone(), action, window, cx) - } else { - Tooltip::simple(tooltip.clone(), cx) - } - }) - .into_any_element() - } - fn has_write_access(&self, cx: &App) -> bool { !self.project.read(cx).is_read_only(cx) } @@ -2718,7 +2527,6 @@ impl Render for GitPanel { .child( v_flex() .size_full() - .children(self.render_panel_header(window, cx)) .map(|this| { if has_entries { this.child(self.render_entries(has_write_access, window, cx)) @@ -2726,8 +2534,8 @@ impl Render for GitPanel { this.child(self.render_empty_state(cx).into_any_element()) } }) + .children(self.render_footer(window, cx)) .children(self.render_previous_commit(cx)) - .child(self.render_commit_editor(window, cx)) .into_any_element(), ) .children(self.context_menu.as_ref().map(|(menu, position, _)| { @@ -2881,3 +2689,618 @@ impl Render for GitPanelMessageTooltip { } } } + +fn git_action_tooltip( + label: impl Into, + action: &dyn Action, + command: impl Into, + focus_handle: Option, + window: &mut Window, + cx: &mut App, +) -> AnyView { + let label = label.into(); + let command = command.into(); + + if let Some(handle) = focus_handle { + Tooltip::with_meta_in( + label.clone(), + Some(action), + command.clone(), + &handle, + window, + cx, + ) + } else { + Tooltip::with_meta(label.clone(), Some(action), command.clone(), window, cx) + } +} + +#[derive(IntoElement)] +struct SplitButton { + pub left: ButtonLike, + pub right: AnyElement, +} + +impl SplitButton { + fn new( + id: impl Into, + left_label: impl Into, + ahead_count: usize, + behind_count: usize, + left_icon: Option, + left_on_click: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static, + tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static, + ) -> Self { + let id = id.into(); + + fn count(count: usize) -> impl IntoElement { + h_flex() + .ml_neg_px() + .h(rems(0.875)) + .items_center() + .overflow_hidden() + .px_0p5() + .child( + Label::new(count.to_string()) + .size(LabelSize::XSmall) + .line_height_style(LineHeightStyle::UiLabel), + ) + } + + let should_render_counts = left_icon.is_none() && (ahead_count > 0 || behind_count > 0); + + let left = ui::ButtonLike::new_rounded_left(ElementId::Name( + format!("split-button-left-{}", id).into(), + )) + .layer(ui::ElevationIndex::ModalSurface) + .size(ui::ButtonSize::Compact) + .when(should_render_counts, |this| { + this.child( + h_flex() + .ml_neg_0p5() + .mr_1() + .when(behind_count > 0, |this| { + this.child(Icon::new(IconName::ArrowDown).size(IconSize::XSmall)) + .child(count(behind_count)) + }) + .when(ahead_count > 0, |this| { + this.child(Icon::new(IconName::ArrowUp).size(IconSize::XSmall)) + .child(count(ahead_count)) + }), + ) + }) + .when_some(left_icon, |this, left_icon| { + this.child( + h_flex() + .ml_neg_0p5() + .mr_1() + .child(Icon::new(left_icon).size(IconSize::XSmall)), + ) + }) + .child( + div() + .child(Label::new(left_label).size(LabelSize::Small)) + .mr_0p5(), + ) + .on_click(left_on_click) + .tooltip(tooltip); + + let right = + render_git_action_menu(ElementId::Name(format!("split-button-right-{}", id).into())) + .into_any_element(); + // .on_click(right_on_click); + + Self { left, right } + } +} + +impl RenderOnce for SplitButton { + fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement { + h_flex() + .rounded_md() + .border_1() + .border_color(cx.theme().colors().text_muted.alpha(0.12)) + .child(self.left) + .child( + div() + .h_full() + .w_px() + .bg(cx.theme().colors().text_muted.alpha(0.16)), + ) + .child(self.right) + .bg(ElevationIndex::Surface.on_elevation_bg(cx)) + .shadow(smallvec![BoxShadow { + color: hsla(0.0, 0.0, 0.0, 0.16), + offset: point(px(0.), px(1.)), + blur_radius: px(0.), + spread_radius: px(0.), + }]) + } +} + +fn render_git_action_menu(id: impl Into) -> impl IntoElement { + PopoverMenu::new(id.into()) + .trigger( + ui::ButtonLike::new_rounded_right("split-button-right") + .layer(ui::ElevationIndex::ModalSurface) + .size(ui::ButtonSize::None) + .child( + div() + .px_1() + .child(Icon::new(IconName::ChevronDownSmall).size(IconSize::XSmall)), + ), + ) + .menu(move |window, cx| { + Some(ContextMenu::build(window, cx, |context_menu, _, _| { + context_menu + .action("Fetch", git::Fetch.boxed_clone()) + .action("Pull", git::Pull.boxed_clone()) + .separator() + .action("Push", git::Push { options: None }.boxed_clone()) + .action( + "Force Push", + git::Push { + options: Some(PushOptions::Force), + } + .boxed_clone(), + ) + })) + }) + .anchor(Corner::TopRight) +} + +#[derive(IntoElement, IntoComponent)] +#[component(scope = "git_panel")] +pub struct PanelRepoHeader { + id: SharedString, + active_repository: SharedString, + branch: Option, + // Getting a GitPanel in previews will be difficult. + // + // For now just take an option here, and we won't bind handlers to buttons in previews. + git_panel: Option>, +} + +impl PanelRepoHeader { + pub fn new( + id: impl Into, + active_repository: SharedString, + branch: Option, + git_panel: Option>, + ) -> Self { + Self { + id: id.into(), + active_repository, + branch, + git_panel, + } + } + + pub fn new_preview( + id: impl Into, + active_repository: SharedString, + branch: Option, + ) -> Self { + Self { + id: id.into(), + active_repository, + branch, + git_panel: None, + } + } + + fn render_overflow_menu(&self, id: impl Into) -> impl IntoElement { + PopoverMenu::new(id.into()) + .trigger( + IconButton::new("overflow-menu-trigger", IconName::EllipsisVertical) + .icon_size(IconSize::Small) + .icon_color(Color::Muted), + ) + .menu(move |window, cx| Some(git_panel_context_menu(window, cx))) + .anchor(Corner::TopRight) + } + + fn panel_focus_handle(&self, cx: &App) -> Option { + if let Some(git_panel) = self.git_panel.clone() { + Some(git_panel.focus_handle(cx)) + } else { + None + } + } + + fn render_push_button(&self, id: SharedString, ahead: u32, cx: &mut App) -> SplitButton { + let panel_focus_handle = self.panel_focus_handle(cx); + + SplitButton::new( + id, + "Push", + ahead as usize, + 0, + None, + |_, _, cx| cx.dispatch_action(&git::Push { options: None }), + move |window, cx| { + git_action_tooltip( + "Push committed changes to remote", + &git::Push { options: None }, + "git push", + panel_focus_handle.clone(), + window, + cx, + ) + }, + ) + } + + fn render_pull_button( + &self, + id: SharedString, + ahead: u32, + behind: u32, + cx: &mut App, + ) -> SplitButton { + let panel_focus_handle = self.panel_focus_handle(cx); + + SplitButton::new( + id, + "Pull", + ahead as usize, + behind as usize, + None, + |_, _, cx| cx.dispatch_action(&git::Pull), + move |window, cx| { + git_action_tooltip( + "Pull", + &git::Pull, + "git pull", + panel_focus_handle.clone(), + window, + cx, + ) + }, + ) + } + + fn render_fetch_button(&self, id: SharedString, cx: &mut App) -> SplitButton { + let panel_focus_handle = self.panel_focus_handle(cx); + + SplitButton::new( + id, + "Fetch", + 0, + 0, + Some(IconName::ArrowCircle), + |_, _, cx| cx.dispatch_action(&git::Fetch), + move |window, cx| { + git_action_tooltip( + "Fetch updates from remote", + &git::Fetch, + "git fetch", + panel_focus_handle.clone(), + window, + cx, + ) + }, + ) + } + + fn render_publish_button(&self, id: SharedString, cx: &mut App) -> SplitButton { + let panel_focus_handle = self.panel_focus_handle(cx); + + SplitButton::new( + id, + "Publish", + 0, + 0, + Some(IconName::ArrowUpFromLine), + |_, _, cx| { + cx.dispatch_action(&git::Push { + options: Some(PushOptions::SetUpstream), + }) + }, + move |window, cx| { + git_action_tooltip( + "Publish branch to remote", + &git::Push { + options: Some(PushOptions::SetUpstream), + }, + "git push --set-upstream", + panel_focus_handle.clone(), + window, + cx, + ) + }, + ) + } + + fn render_republish_button(&self, id: SharedString, cx: &mut App) -> SplitButton { + let panel_focus_handle = self.panel_focus_handle(cx); + + SplitButton::new( + id, + "Republish", + 0, + 0, + Some(IconName::ArrowUpFromLine), + |_, _, cx| { + cx.dispatch_action(&git::Push { + options: Some(PushOptions::SetUpstream), + }) + }, + move |window, cx| { + git_action_tooltip( + "Re-publish branch to remote", + &git::Push { + options: Some(PushOptions::SetUpstream), + }, + "git push --set-upstream", + panel_focus_handle.clone(), + window, + cx, + ) + }, + ) + } + + fn render_relevant_button( + &self, + id: impl Into, + branch: &Branch, + cx: &mut App, + ) -> impl IntoElement { + let id = id.into(); + let upstream = branch.upstream.as_ref(); + match upstream { + Some(Upstream { + tracking: UpstreamTracking::Tracked(UpstreamTrackingStatus { ahead, behind }), + .. + }) => match (*ahead, *behind) { + (0, 0) => self.render_fetch_button(id, cx), + (ahead, 0) => self.render_push_button(id, ahead, cx), + (ahead, behind) => self.render_pull_button(id, ahead, behind, cx), + }, + Some(Upstream { + tracking: UpstreamTracking::Gone, + .. + }) => self.render_republish_button(id, cx), + None => self.render_publish_button(id, cx), + } + } +} + +impl RenderOnce for PanelRepoHeader { + fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement { + let active_repo = self.active_repository.clone(); + let overflow_menu_id: SharedString = format!("overflow-menu-{}", active_repo).into(); + + let repo_selector = if let Some(panel) = self.git_panel.clone() { + RepositorySelectorPopoverMenu::new( + panel.read(cx).repository_selector.clone(), + Button::new("repo-selector", active_repo.clone()) + .style(ButtonStyle::Transparent) + .size(ButtonSize::None) + .label_size(LabelSize::Small) + .color(Color::Muted), + Tooltip::text("Choose a repository"), + ) + .into_any_element() + } else { + Button::new("repo-selector", active_repo.clone()) + .style(ButtonStyle::Transparent) + .size(ButtonSize::None) + .label_size(LabelSize::Small) + .color(Color::Muted) + .into_any_element() + }; + + let branch = self.branch.clone(); + let branch_name = branch + .as_ref() + .map_or("".into(), |branch| branch.name.clone()); + + let branch_selector = Button::new("branch-selector", branch_name) + .style(ButtonStyle::Transparent) + .size(ButtonSize::None) + .label_size(LabelSize::Small) + .tooltip(Tooltip::for_action_title( + "Switch Branch", + &zed_actions::git::Branch, + )) + .on_click(|_, window, cx| { + window.dispatch_action(zed_actions::git::Branch.boxed_clone(), cx); + }); + + let spinner = self + .git_panel + .as_ref() + .and_then(|git_panel| git_panel.read(cx).render_spinner()); + + h_flex() + .w_full() + .px_2() + .h(px(36.)) + .items_center() + .justify_between() + .child( + h_flex() + .relative() + .items_center() + .gap_0p5() + .child( + div() + // .when(repo_or_branch_has_uppercase, |this| { + // this.relative().pt(px(2.)) + // }) + .child( + Icon::new(IconName::GitBranchSmall) + .size(IconSize::Small) + .color(Color::Muted), + ), + ) + .child( + h_flex() + .gap_0p5() + .child(repo_selector) + .child( + div() + .text_color(cx.theme().colors().text_muted) + .text_sm() + .child("/"), + ) + .child(branch_selector), + ), + ) + .child( + h_flex() + .gap_1() + .children(spinner) + .child(self.render_overflow_menu(overflow_menu_id)) + .when_some(branch, |this, branch| { + let button = self.render_relevant_button(self.id.clone(), &branch, cx); + this.child(button) + }), + ) + } +} + +impl ComponentPreview for PanelRepoHeader { + fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement { + let unknown_upstream = None; + let no_remote_upstream = Some(UpstreamTracking::Gone); + let ahead_of_upstream = Some( + UpstreamTrackingStatus { + ahead: 2, + behind: 0, + } + .into(), + ); + let behind_upstream = Some( + UpstreamTrackingStatus { + ahead: 0, + behind: 2, + } + .into(), + ); + let ahead_and_behind_upstream = Some( + UpstreamTrackingStatus { + ahead: 3, + behind: 1, + } + .into(), + ); + + let not_ahead_or_behind_upstream = Some( + UpstreamTrackingStatus { + ahead: 0, + behind: 0, + } + .into(), + ); + + fn branch(upstream: Option) -> Branch { + Branch { + is_head: true, + name: "some-branch".into(), + upstream: upstream.map(|tracking| Upstream { + ref_name: "origin/some-branch".into(), + tracking, + }), + most_recent_commit: Some(CommitSummary { + sha: "abc123".into(), + subject: "Modify stuff".into(), + commit_timestamp: 1710932954, + }), + } + } + + fn active_repository(id: usize) -> SharedString { + format!("repo-{}", id).into() + } + + v_flex() + .gap_6() + .children(vec![example_group_with_title( + "Action Button States", + vec![ + single_example( + "No Branch", + div() + .w(px(180.)) + .child(PanelRepoHeader::new_preview( + "no-branch", + active_repository(1).clone(), + None, + )) + .into_any_element(), + ), + single_example( + "Remote status unknown", + div() + .w(px(180.)) + .child(PanelRepoHeader::new_preview( + "unknown-upstream", + active_repository(2).clone(), + Some(branch(unknown_upstream)), + )) + .into_any_element(), + ), + single_example( + "No Remote Upstream", + div() + .w(px(180.)) + .child(PanelRepoHeader::new_preview( + "no-remote-upstream", + active_repository(3).clone(), + Some(branch(no_remote_upstream)), + )) + .into_any_element(), + ), + single_example( + "Not Ahead or Behind", + div() + .w(px(180.)) + .child(PanelRepoHeader::new_preview( + "not-ahead-or-behind", + active_repository(4).clone(), + Some(branch(not_ahead_or_behind_upstream)), + )) + .into_any_element(), + ), + single_example( + "Behind remote", + div() + .w(px(180.)) + .child(PanelRepoHeader::new_preview( + "behind-remote", + active_repository(5).clone(), + Some(branch(behind_upstream)), + )) + .into_any_element(), + ), + single_example( + "Ahead of remote", + div() + .w(px(180.)) + .child(PanelRepoHeader::new_preview( + "ahead-of-remote", + active_repository(6).clone(), + Some(branch(ahead_of_upstream)), + )) + .into_any_element(), + ), + single_example( + "Ahead and behind remote", + div() + .w(px(180.)) + .child(PanelRepoHeader::new_preview( + "ahead-and-behind", + active_repository(7).clone(), + Some(branch(ahead_and_behind_upstream)), + )) + .into_any_element(), + ), + ], + ) + .vertical()]) + .into_any_element() + } +} diff --git a/crates/panel/src/panel.rs b/crates/panel/src/panel.rs index 59572c402be3eaa98ad76e7a04eda6f2a60bc752..6aa4d85a38b45763e333477b41bf200ca0f3ce1a 100644 --- a/crates/panel/src/panel.rs +++ b/crates/panel/src/panel.rs @@ -79,7 +79,7 @@ pub fn panel_editor_container(_window: &mut Window, cx: &mut App) -> Div { .bg(cx.theme().colors().editor_background) } -pub fn panel_editor_style(monospace: bool, window: &mut Window, cx: &mut App) -> EditorStyle { +pub fn panel_editor_style(monospace: bool, window: &Window, cx: &App) -> EditorStyle { let settings = ThemeSettings::get_global(cx); let font_size = TextSize::Small.rems(cx).to_pixels(window.rem_size()); diff --git a/crates/ui/src/components/avatar.rs b/crates/ui/src/components/avatar.rs index b0670d04c056de8284db2fa6a6e69315bb591732..19422e7204c3f06da1af8d3ba6f0794b909760a6 100644 --- a/crates/ui/src/components/avatar.rs +++ b/crates/ui/src/components/avatar.rs @@ -220,7 +220,7 @@ impl RenderOnce for AvatarAvailabilityIndicator { // View this component preview using `workspace: open component-preview` impl ComponentPreview for Avatar { - fn preview(_window: &mut Window, cx: &App) -> AnyElement { + fn preview(_window: &mut Window, cx: &mut App) -> AnyElement { let example_avatar = "https://avatars.githubusercontent.com/u/1714999?v=4"; v_flex() diff --git a/crates/ui/src/components/button/button.rs b/crates/ui/src/components/button/button.rs index e7112aa8aec5099142f6f0323f51693599425e3e..25a797a6a49df4a66c253f8e87716bd41a4b0699 100644 --- a/crates/ui/src/components/button/button.rs +++ b/crates/ui/src/components/button/button.rs @@ -458,7 +458,7 @@ impl RenderOnce for Button { // View this component preview using `workspace: open component-preview` impl ComponentPreview for Button { - fn preview(_window: &mut Window, _cx: &App) -> AnyElement { + fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement { v_flex() .gap_6() .children(vec![ diff --git a/crates/ui/src/components/button/icon_button.rs b/crates/ui/src/components/button/icon_button.rs index 97084116057e5ee99f23b35366b4edfd12f2e0f1..5326137efc1f83a7d1c1e85a84aff989a45a4902 100644 --- a/crates/ui/src/components/button/icon_button.rs +++ b/crates/ui/src/components/button/icon_button.rs @@ -202,7 +202,7 @@ impl RenderOnce for IconButton { } impl ComponentPreview for IconButton { - fn preview(_window: &mut Window, _cx: &App) -> AnyElement { + fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement { v_flex() .gap_6() .children(vec![ diff --git a/crates/ui/src/components/button/toggle_button.rs b/crates/ui/src/components/button/toggle_button.rs index 1fb8a2c01633ff59d794bc277749d416495c55bb..618fa176bda17ff191aa8a889635a39ff951d12a 100644 --- a/crates/ui/src/components/button/toggle_button.rs +++ b/crates/ui/src/components/button/toggle_button.rs @@ -144,7 +144,7 @@ impl RenderOnce for ToggleButton { } impl ComponentPreview for ToggleButton { - fn preview(_window: &mut Window, _cx: &App) -> AnyElement { + fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement { v_flex() .gap_6() .children(vec![ diff --git a/crates/ui/src/components/content_group.rs b/crates/ui/src/components/content_group.rs index e372580745931dc952e47eed49a7c2fd62534cad..8d4a528e208ee9dc8c235a0c0e7fbefc634f46a9 100644 --- a/crates/ui/src/components/content_group.rs +++ b/crates/ui/src/components/content_group.rs @@ -90,7 +90,7 @@ impl RenderOnce for ContentGroup { // View this component preview using `workspace: open component-preview` impl ComponentPreview for ContentGroup { - fn preview(_window: &mut Window, _cx: &App) -> AnyElement { + fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement { example_group(vec![ single_example( "Default", diff --git a/crates/ui/src/components/facepile.rs b/crates/ui/src/components/facepile.rs index 59df3f4c005c9a7909f9bd811a8d5c4e44b3d352..fb610f0cbf703853cbdb3132393f789c973d29fa 100644 --- a/crates/ui/src/components/facepile.rs +++ b/crates/ui/src/components/facepile.rs @@ -61,7 +61,7 @@ impl RenderOnce for Facepile { } impl ComponentPreview for Facepile { - fn preview(_window: &mut Window, _cx: &App) -> AnyElement { + fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement { let faces: [&'static str; 6] = [ "https://avatars.githubusercontent.com/u/326587?s=60&v=4", "https://avatars.githubusercontent.com/u/2280405?s=60&v=4", diff --git a/crates/ui/src/components/icon.rs b/crates/ui/src/components/icon.rs index e61f3cb88fc5eef1165a2be205cd5ef41d647e83..f5bd6ed7aa0f73e71295f2eb1d7b838cffbf8d06 100644 --- a/crates/ui/src/components/icon.rs +++ b/crates/ui/src/components/icon.rs @@ -218,6 +218,7 @@ pub enum IconName { Github, Globe, GitBranch, + GitBranchSmall, Hash, HistoryRerun, Indicator, @@ -492,7 +493,7 @@ impl RenderOnce for IconWithIndicator { // View this component preview using `workspace: open component-preview` impl ComponentPreview for Icon { - fn preview(_window: &mut Window, _cx: &App) -> AnyElement { + fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement { v_flex() .gap_6() .children(vec![ diff --git a/crates/ui/src/components/icon/decorated_icon.rs b/crates/ui/src/components/icon/decorated_icon.rs index 641fb82f193e1768b47055aedc926547c77da74b..fbe0a095630008a81c735d73296274640dcc062c 100644 --- a/crates/ui/src/components/icon/decorated_icon.rs +++ b/crates/ui/src/components/icon/decorated_icon.rs @@ -26,7 +26,7 @@ impl RenderOnce for DecoratedIcon { // View this component preview using `workspace: open component-preview` impl ComponentPreview for DecoratedIcon { - fn preview(_window: &mut Window, cx: &App) -> AnyElement { + fn preview(_window: &mut Window, cx: &mut App) -> AnyElement { let decoration_x = IconDecoration::new( IconDecorationKind::X, cx.theme().colors().surface_background, diff --git a/crates/ui/src/components/keybinding_hint.rs b/crates/ui/src/components/keybinding_hint.rs index 7b9d5538485a191adf266900712f13eddc57049e..9dabefd67df3e105b3d2ad9bc74c15e78a078549 100644 --- a/crates/ui/src/components/keybinding_hint.rs +++ b/crates/ui/src/components/keybinding_hint.rs @@ -207,7 +207,7 @@ impl RenderOnce for KeybindingHint { // View this component preview using `workspace: open component-preview` impl ComponentPreview for KeybindingHint { - fn preview(window: &mut Window, cx: &App) -> AnyElement { + fn preview(window: &mut Window, cx: &mut App) -> AnyElement { let enter_fallback = gpui::KeyBinding::new("enter", menu::Confirm, None); let enter = KeyBinding::for_action(&menu::Confirm, window, cx) .unwrap_or(KeyBinding::new(enter_fallback, cx)); diff --git a/crates/ui/src/components/label/label.rs b/crates/ui/src/components/label/label.rs index f724b707113900b425f4f7ccff362b9ee29c6496..490e3e721bbd20487ba19126aedc3d1430c04c9c 100644 --- a/crates/ui/src/components/label/label.rs +++ b/crates/ui/src/components/label/label.rs @@ -199,7 +199,7 @@ mod label_preview { // View this component preview using `workspace: open component-preview` impl ComponentPreview for Label { - fn preview(_window: &mut Window, _cx: &App) -> AnyElement { + fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement { v_flex() .gap_6() .children(vec![ diff --git a/crates/ui/src/components/tab.rs b/crates/ui/src/components/tab.rs index 3d16bdc84b34611b7e8ecb8b3d0c17808eef70a1..60090f12670fe70b2c0b2dbf76825a1aa47f585d 100644 --- a/crates/ui/src/components/tab.rs +++ b/crates/ui/src/components/tab.rs @@ -173,7 +173,7 @@ impl RenderOnce for Tab { // View this component preview using `workspace: open component-preview` impl ComponentPreview for Tab { - fn preview(_window: &mut Window, _cx: &App) -> AnyElement { + fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement { v_flex() .gap_6() .children(vec![example_group_with_title( diff --git a/crates/ui/src/components/table.rs b/crates/ui/src/components/table.rs index 9b1bfb5cbfed720b1ff3013083e0e4f30150aa9e..e0979613582952d1faae15fb847c8e11b50a4b97 100644 --- a/crates/ui/src/components/table.rs +++ b/crates/ui/src/components/table.rs @@ -153,7 +153,7 @@ where // View this component preview using `workspace: open component-preview` impl ComponentPreview for Table { - fn preview(_window: &mut Window, _cx: &App) -> AnyElement { + fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement { v_flex() .gap_6() .children(vec![ diff --git a/crates/ui/src/components/toggle.rs b/crates/ui/src/components/toggle.rs index 0b5591bf2033c20c2de41c7cc6b8eb2c2ea48148..4ea7bd34ccccff1095fc1a3afa0566f7dca4003d 100644 --- a/crates/ui/src/components/toggle.rs +++ b/crates/ui/src/components/toggle.rs @@ -510,7 +510,7 @@ impl RenderOnce for SwitchWithLabel { // View this component preview using `workspace: open component-preview` impl ComponentPreview for Checkbox { - fn preview(_window: &mut Window, _cx: &App) -> AnyElement { + fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement { v_flex() .gap_6() .children(vec![ @@ -595,7 +595,7 @@ impl ComponentPreview for Checkbox { // View this component preview using `workspace: open component-preview` impl ComponentPreview for Switch { - fn preview(_window: &mut Window, _cx: &App) -> AnyElement { + fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement { v_flex() .gap_6() .children(vec![ @@ -658,7 +658,7 @@ impl ComponentPreview for Switch { // View this component preview using `workspace: open component-preview` impl ComponentPreview for CheckboxWithLabel { - fn preview(_window: &mut Window, _cx: &App) -> AnyElement { + fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement { v_flex() .gap_6() .children(vec![example_group_with_title( diff --git a/crates/ui/src/components/tooltip.rs b/crates/ui/src/components/tooltip.rs index b20f6c101357c699cd8ed96880412e40d0ad7e23..26634c25c09ae6903f24f78d76a94a5386849eca 100644 --- a/crates/ui/src/components/tooltip.rs +++ b/crates/ui/src/components/tooltip.rs @@ -224,7 +224,7 @@ impl Render for LinkPreview { // View this component preview using `workspace: open component-preview` impl ComponentPreview for Tooltip { - fn preview(_window: &mut Window, _cx: &App) -> AnyElement { + fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement { example_group(vec![single_example( "Text only", Button::new("delete-example", "Delete") diff --git a/crates/ui/src/styles/typography.rs b/crates/ui/src/styles/typography.rs index 08573a13074aaade20fcaa7d478a63c3593fe852..09146fe67b38fb55a08d59f5d9290a5bac1526ee 100644 --- a/crates/ui/src/styles/typography.rs +++ b/crates/ui/src/styles/typography.rs @@ -235,7 +235,7 @@ impl Headline { // View this component preview using `workspace: open component-preview` impl ComponentPreview for Headline { - fn preview(_window: &mut Window, _cx: &App) -> AnyElement { + fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement { v_flex() .gap_1() .children(vec![