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 | 1111 ++++++++++++----- 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, 832 insertions(+), 372 deletions(-) create mode 100644 assets/icons/git_branch_small.svg diff --git a/Cargo.lock b/Cargo.lock index 04cfb6d6a7..821a8f9e47 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 5d958f3bb7..5a69ac7ff7 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 0000000000..d23fc176ac --- /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 d53a951f2a..b0e92ce500 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 6a3dcfc406..bc4f390a61 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 d68d9f7b65..5111382493 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 0edbd62fb9..c2c5173fa1 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 f0bf4225d6..66845c939f 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 6b0e84929f..8df0d38a6f 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 f5a45f45f2..cecd4411e3 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" + 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 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); + + let branch = active_repo.read(cx).current_branch()?; + + let footer_size = px(32.); + let gap = px(8.0); + + 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 { - "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 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 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 footer_size = px(32.); - let gap = px(16.0); - - let max_height = window.line_height() * 6. + gap + footer_size; - - 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), - ) - }) + 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 59572c402b..6aa4d85a38 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 b0670d04c0..19422e7204 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 e7112aa8ae..25a797a6a4 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 9708411605..5326137efc 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 1fb8a2c016..618fa176bd 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 e372580745..8d4a528e20 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 59df3f4c00..fb610f0cbf 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 e61f3cb88f..f5bd6ed7aa 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 641fb82f19..fbe0a09563 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 7b9d553848..9dabefd67d 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 f724b70711..490e3e721b 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 3d16bdc84b..60090f1267 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 9b1bfb5cbf..e097961358 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 0b5591bf20..4ea7bd34cc 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 b20f6c1013..26634c25c0 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 08573a1307..09146fe67b 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![