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![