From a5084510a1b2b39166733edfeced078b58b39c1f Mon Sep 17 00:00:00 2001 From: Mikayla Date: Thu, 18 Jan 2024 15:16:09 -0800 Subject: [PATCH 1/3] Fix popin for project panel by pre-resolving keybindings in terms of the project panel --- crates/project_panel/src/project_panel.rs | 114 ++++++++++------------ crates/ui/src/components/context_menu.rs | 44 ++++++++- 2 files changed, 96 insertions(+), 62 deletions(-) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 79c158048ee7e08b79f9adcc1d31fa1eea587e44..eef3921a45092bed933ed2fabda030515513dee8 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -381,67 +381,59 @@ impl ProjectPanel { let is_local = project.is_local(); let is_read_only = project.is_read_only(); - let context_menu = ContextMenu::build(cx, |mut menu, cx| { - if is_read_only { - menu = menu.action("Copy Relative Path", Box::new(CopyRelativePath)); - if is_dir { - menu = menu.action("Search Inside", Box::new(NewSearchInDirectory)) - } - - return menu; - } - - if is_local { - menu = menu.action( - "Add Folder to Project", - Box::new(workspace::AddFolderToProject), - ); - if is_root { - menu = menu.entry( - "Remove from Project", - None, - cx.handler_for(&this, move |this, cx| { - this.project.update(cx, |project, cx| { - project.remove_worktree(worktree_id, cx) - }); - }), - ); - } - } - - menu = menu - .action("New File", Box::new(NewFile)) - .action("New Folder", Box::new(NewDirectory)) - .separator() - .action("Cut", Box::new(Cut)) - .action("Copy", Box::new(Copy)); - - if let Some(clipboard_entry) = self.clipboard_entry { - if clipboard_entry.worktree_id() == worktree_id { - menu = menu.action("Paste", Box::new(Paste)); - } - } - - menu = menu - .separator() - .action("Copy Path", Box::new(CopyPath)) - .action("Copy Relative Path", Box::new(CopyRelativePath)) - .separator() - .action("Reveal in Finder", Box::new(RevealInFinder)); - - if is_dir { - menu = menu - .action("Open in Terminal", Box::new(OpenInTerminal)) - .action("Search Inside", Box::new(NewSearchInDirectory)) - } - - menu = menu.separator().action("Rename", Box::new(Rename)); - - if !is_root { - menu = menu.action("Delete", Box::new(Delete)); - } - - menu + let context_menu = ContextMenu::build(cx, |menu, cx| { + menu.context(self.focus_handle.clone()).then_if_else( + is_read_only, + |menu| { + menu.action("Copy Relative Path", Box::new(CopyRelativePath)) + .then_if(is_dir, |menu| { + menu.action("Search Inside", Box::new(NewSearchInDirectory)) + }) + }, + |menu| { + menu.then_if(is_local, |menu| { + menu.action( + "Add Folder to Project", + Box::new(workspace::AddFolderToProject), + ) + .then_if(is_root, |menu| { + menu.entry( + "Remove from Project", + None, + cx.handler_for(&this, move |this, cx| { + this.project.update(cx, |project, cx| { + project.remove_worktree(worktree_id, cx) + }); + }), + ) + }) + }) + .action("New File", Box::new(NewFile)) + .action("New Folder", Box::new(NewDirectory)) + .separator() + .action("Cut", Box::new(Cut)) + .action("Copy", Box::new(Copy)) + .if_some(self.clipboard_entry, |menu, entry| { + menu.then_if(entry.worktree_id() == worktree_id, |menu| { + menu.action("Paste", Box::new(Paste)) + }) + }) + .separator() + .action("Copy Path", Box::new(CopyPath)) + .action("Copy Relative Path", Box::new(CopyRelativePath)) + .separator() + .action("Reveal in Finder", Box::new(RevealInFinder)) + .then_if(is_dir, |menu| { + menu + .action("Open in Terminal", Box::new(OpenInTerminal)) + .action("Search Inside", Box::new(NewSearchInDirectory)) + }) + .separator().action("Rename", Box::new(Rename)) + .then_if(!is_root, |menu| { + menu.action("Delete", Box::new(Delete)) + }) + }, + ) }); cx.focus_view(&context_menu); diff --git a/crates/ui/src/components/context_menu.rs b/crates/ui/src/components/context_menu.rs index 470483cc0a713cbfc53d1a9e7db343a604918a2e..dbc0394a5bec68edfacdb01ff1b994f84d19fcea 100644 --- a/crates/ui/src/components/context_menu.rs +++ b/crates/ui/src/components/context_menu.rs @@ -27,6 +27,7 @@ enum ContextMenuItem { pub struct ContextMenu { items: Vec, focus_handle: FocusHandle, + action_context: Option, selected_index: Option, delayed: bool, clicked: bool, @@ -56,6 +57,7 @@ impl ContextMenu { Self { items: Default::default(), focus_handle, + action_context: None, selected_index: None, delayed: false, clicked: false, @@ -66,6 +68,39 @@ impl ContextMenu { }) } + pub fn if_some(self, condition: Option, f: impl FnOnce(Self, T) -> Self) -> Self { + if let Some(t) = condition { + f(self, t) + } else { + self + } + } + + pub fn then_if_else(self, condition: bool, then: impl FnOnce(Self) -> Self, otherwise: impl FnOnce(Self) -> Self) -> Self { + if condition { + then(self) + } else { + otherwise(self) + } + } + + pub fn then_if(self, condition: bool, f: impl FnOnce(Self) -> Self) -> Self { + if condition { + f(self) + } else { + self + } + } + + pub fn map(self, f: impl FnOnce(Self) -> Self) -> Self { + f(self) + } + + pub fn context(mut self, focus: FocusHandle) -> Self { + self.action_context = Some(focus); + self + } + pub fn header(mut self, title: impl Into) -> Self { self.items.push(ContextMenuItem::Header(title.into())); self @@ -305,7 +340,14 @@ impl Render for ContextMenu { .child(label_element) .debug_selector(|| format!("MENU_ITEM-{}", label)) .children(action.as_ref().and_then(|action| { - KeyBinding::for_action(&**action, cx) + self.action_context + .as_ref() + .map(|focus| { + KeyBinding::for_action_in(&**action, focus, cx) + }) + .unwrap_or_else(|| { + KeyBinding::for_action(&**action, cx) + }) .map(|binding| div().ml_1().child(binding)) })), ) From 903176d8ff24ff679755745269996f5cd47fea50 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Thu, 18 Jan 2024 15:31:31 -0800 Subject: [PATCH 2/3] Pull out fluent builder helpers into re-usable trait --- crates/collab_ui/src/face_pile.rs | 1 + crates/gpui/src/element.rs | 38 ++------------ crates/gpui/src/prelude.rs | 6 +-- crates/gpui/src/util.rs | 52 +++++++++++++++++++ .../src/active_buffer_language.rs | 2 +- crates/project_panel/src/project_panel.rs | 24 ++++----- crates/ui/src/components/context_menu.rs | 30 +---------- crates/ui/src/components/popover_menu.rs | 6 +-- 8 files changed, 77 insertions(+), 82 deletions(-) diff --git a/crates/collab_ui/src/face_pile.rs b/crates/collab_ui/src/face_pile.rs index fb6c59cc8079073acbb6b481e214b54619f9eea6..b12b11f63f7f842ca07275565bd687883e64e0b3 100644 --- a/crates/collab_ui/src/face_pile.rs +++ b/crates/collab_ui/src/face_pile.rs @@ -1,5 +1,6 @@ use gpui::{div, AnyElement, IntoElement, ParentElement, RenderOnce, Styled, WindowContext}; use smallvec::SmallVec; +use ui::FluentBuilder; #[derive(Default, IntoElement)] pub struct FacePile { diff --git a/crates/gpui/src/element.rs b/crates/gpui/src/element.rs index 3022f9f30a5fa48d3a3d9b14b06011bdde2cc610..47fb7241aba03890120d57312361a0c004237b07 100644 --- a/crates/gpui/src/element.rs +++ b/crates/gpui/src/element.rs @@ -1,6 +1,6 @@ use crate::{ - ArenaBox, AvailableSpace, BorrowWindow, Bounds, ElementId, LayoutId, Pixels, Point, Size, - ViewContext, WindowContext, ELEMENT_ARENA, + util::FluentBuilder, ArenaBox, AvailableSpace, BorrowWindow, Bounds, ElementId, LayoutId, + Pixels, Point, Size, ViewContext, WindowContext, ELEMENT_ARENA, }; use derive_more::{Deref, DerefMut}; pub(crate) use smallvec::SmallVec; @@ -77,40 +77,10 @@ pub trait IntoElement: Sized { }) } } - - /// Convert self to another type by calling the given closure. Useful in rendering code. - fn map(self, f: impl FnOnce(Self) -> U) -> U - where - Self: Sized, - U: IntoElement, - { - f(self) - } - - /// Conditionally chain onto self with the given closure. Useful in rendering code. - fn when(self, condition: bool, then: impl FnOnce(Self) -> Self) -> Self - where - Self: Sized, - { - self.map(|this| if condition { then(this) } else { this }) - } - - /// Conditionally chain onto self with the given closure if the given option is Some. - /// The contents of the option are provided to the closure. - fn when_some(self, option: Option, then: impl FnOnce(Self, T) -> Self) -> Self - where - Self: Sized, - { - self.map(|this| { - if let Some(value) = option { - then(this, value) - } else { - this - } - }) - } } +impl FluentBuilder for T {} + pub trait Render: 'static + Sized { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement; } diff --git a/crates/gpui/src/prelude.rs b/crates/gpui/src/prelude.rs index 90d09b3fc53db18c02dd067dbb8916042aa020fc..218cbd0e282786bacc20b8e76b87fc0358ef5b37 100644 --- a/crates/gpui/src/prelude.rs +++ b/crates/gpui/src/prelude.rs @@ -1,5 +1,5 @@ pub use crate::{ - BorrowAppContext, BorrowWindow, Context, Element, FocusableElement, InteractiveElement, - IntoElement, ParentElement, Refineable, Render, RenderOnce, StatefulInteractiveElement, Styled, - VisualContext, + util::FluentBuilder, BorrowAppContext, BorrowWindow, Context, Element, FocusableElement, + InteractiveElement, IntoElement, ParentElement, Refineable, Render, RenderOnce, + StatefulInteractiveElement, Styled, VisualContext, }; diff --git a/crates/gpui/src/util.rs b/crates/gpui/src/util.rs index cba7ed84b58b77f6491380277ace81341e8041c5..4bff3da740b4d60458f74427cbdfb2eb25418a0e 100644 --- a/crates/gpui/src/util.rs +++ b/crates/gpui/src/util.rs @@ -9,6 +9,58 @@ use smol::future::FutureExt; pub use util::*; +/// A helper trait for building complex objects with imperative conditionals in a fluent style. +pub trait FluentBuilder { + /// Imperatively modify self with the given closure. + fn map(self, f: impl FnOnce(Self) -> U) -> U + where + Self: Sized, + { + f(self) + } + + /// Conditionally modify self with the given closure. + fn when(self, condition: bool, then: impl FnOnce(Self) -> Self) -> Self + where + Self: Sized, + { + self.map(|this| if condition { then(this) } else { this }) + } + + /// Conditionally unwrap and modify self with the given closure, if the given option is Some. + fn when_some(self, option: Option, then: impl FnOnce(Self, T) -> Self) -> Self + where + Self: Sized, + { + self.map(|this| { + if let Some(value) = option { + then(this, value) + } else { + this + } + }) + } + + /// Conditionally modify self with one closure or another + fn when_else( + self, + condition: bool, + then: impl FnOnce(Self) -> Self, + otherwise: impl FnOnce(Self) -> Self, + ) -> Self + where + Self: Sized, + { + self.map(|this| { + if condition { + then(this) + } else { + otherwise(this) + } + }) + } +} + #[cfg(any(test, feature = "test-support"))] pub async fn timeout(timeout: Duration, f: F) -> Result where diff --git a/crates/language_selector/src/active_buffer_language.rs b/crates/language_selector/src/active_buffer_language.rs index d5f177f7d61a649b6b438782e265c0e5eb9a4b77..647ff93b818f37fff6396deb15edd12b59ede9b7 100644 --- a/crates/language_selector/src/active_buffer_language.rs +++ b/crates/language_selector/src/active_buffer_language.rs @@ -1,7 +1,7 @@ use editor::Editor; use gpui::{div, IntoElement, ParentElement, Render, Subscription, View, ViewContext, WeakView}; use std::sync::Arc; -use ui::{Button, ButtonCommon, Clickable, LabelSize, Tooltip}; +use ui::{Button, ButtonCommon, Clickable, FluentBuilder, LabelSize, Tooltip}; use workspace::{item::ItemHandle, StatusItemView, Workspace}; use crate::LanguageSelector; diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index eef3921a45092bed933ed2fabda030515513dee8..abfc2d581883bf865e1cf70cd7721502860668b5 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -382,21 +382,21 @@ impl ProjectPanel { let is_read_only = project.is_read_only(); let context_menu = ContextMenu::build(cx, |menu, cx| { - menu.context(self.focus_handle.clone()).then_if_else( + menu.context(self.focus_handle.clone()).when_else( is_read_only, |menu| { menu.action("Copy Relative Path", Box::new(CopyRelativePath)) - .then_if(is_dir, |menu| { + .when(is_dir, |menu| { menu.action("Search Inside", Box::new(NewSearchInDirectory)) }) }, |menu| { - menu.then_if(is_local, |menu| { + menu.when(is_local, |menu| { menu.action( "Add Folder to Project", Box::new(workspace::AddFolderToProject), ) - .then_if(is_root, |menu| { + .when(is_root, |menu| { menu.entry( "Remove from Project", None, @@ -413,8 +413,8 @@ impl ProjectPanel { .separator() .action("Cut", Box::new(Cut)) .action("Copy", Box::new(Copy)) - .if_some(self.clipboard_entry, |menu, entry| { - menu.then_if(entry.worktree_id() == worktree_id, |menu| { + .when_some(self.clipboard_entry, |menu, entry| { + menu.when(entry.worktree_id() == worktree_id, |menu| { menu.action("Paste", Box::new(Paste)) }) }) @@ -423,15 +423,13 @@ impl ProjectPanel { .action("Copy Relative Path", Box::new(CopyRelativePath)) .separator() .action("Reveal in Finder", Box::new(RevealInFinder)) - .then_if(is_dir, |menu| { - menu - .action("Open in Terminal", Box::new(OpenInTerminal)) + .when(is_dir, |menu| { + menu.action("Open in Terminal", Box::new(OpenInTerminal)) .action("Search Inside", Box::new(NewSearchInDirectory)) }) - .separator().action("Rename", Box::new(Rename)) - .then_if(!is_root, |menu| { - menu.action("Delete", Box::new(Delete)) - }) + .separator() + .action("Rename", Box::new(Rename)) + .when(!is_root, |menu| menu.action("Delete", Box::new(Delete))) }, ) }); diff --git a/crates/ui/src/components/context_menu.rs b/crates/ui/src/components/context_menu.rs index dbc0394a5bec68edfacdb01ff1b994f84d19fcea..e3a7aeefda4ab8ab8ea6c583ba59b2549c98a5f6 100644 --- a/crates/ui/src/components/context_menu.rs +++ b/crates/ui/src/components/context_menu.rs @@ -42,6 +42,8 @@ impl FocusableView for ContextMenu { impl EventEmitter for ContextMenu {} +impl FluentBuilder for ContextMenu {} + impl ContextMenu { pub fn build( cx: &mut WindowContext, @@ -68,34 +70,6 @@ impl ContextMenu { }) } - pub fn if_some(self, condition: Option, f: impl FnOnce(Self, T) -> Self) -> Self { - if let Some(t) = condition { - f(self, t) - } else { - self - } - } - - pub fn then_if_else(self, condition: bool, then: impl FnOnce(Self) -> Self, otherwise: impl FnOnce(Self) -> Self) -> Self { - if condition { - then(self) - } else { - otherwise(self) - } - } - - pub fn then_if(self, condition: bool, f: impl FnOnce(Self) -> Self) -> Self { - if condition { - f(self) - } else { - self - } - } - - pub fn map(self, f: impl FnOnce(Self) -> Self) -> Self { - f(self) - } - pub fn context(mut self, focus: FocusHandle) -> Self { self.action_context = Some(focus); self diff --git a/crates/ui/src/components/popover_menu.rs b/crates/ui/src/components/popover_menu.rs index 39202bf7ef5c68e5722c5b6d55a6cab3676c8ef7..73384321cb9faf8da275455763583706dec663de 100644 --- a/crates/ui/src/components/popover_menu.rs +++ b/crates/ui/src/components/popover_menu.rs @@ -1,9 +1,9 @@ use std::{cell::RefCell, rc::Rc}; use gpui::{ - overlay, point, px, rems, AnchorCorner, AnyElement, Bounds, DismissEvent, DispatchPhase, - Element, ElementId, InteractiveBounds, IntoElement, LayoutId, ManagedView, MouseDownEvent, - ParentElement, Pixels, Point, View, VisualContext, WindowContext, + overlay, point, prelude::FluentBuilder, px, rems, AnchorCorner, AnyElement, Bounds, + DismissEvent, DispatchPhase, Element, ElementId, InteractiveBounds, IntoElement, LayoutId, + ManagedView, MouseDownEvent, ParentElement, Pixels, Point, View, VisualContext, WindowContext, }; use crate::{Clickable, Selectable}; From 0e8bdda249cf25ad4cc5806308f042170875348e Mon Sep 17 00:00:00 2001 From: Mikayla Date: Thu, 18 Jan 2024 15:47:10 -0800 Subject: [PATCH 3/3] ????? --- crates/collab_ui/src/face_pile.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/collab_ui/src/face_pile.rs b/crates/collab_ui/src/face_pile.rs index b12b11f63f7f842ca07275565bd687883e64e0b3..453eedd6e01861c2b61be4696d9696d63f2df7a4 100644 --- a/crates/collab_ui/src/face_pile.rs +++ b/crates/collab_ui/src/face_pile.rs @@ -1,14 +1,14 @@ -use gpui::{div, AnyElement, IntoElement, ParentElement, RenderOnce, Styled, WindowContext}; +use gpui::{div, AnyElement, ParentElement, RenderOnce, Styled, WindowContext}; use smallvec::SmallVec; use ui::FluentBuilder; -#[derive(Default, IntoElement)] +#[derive(Default, gpui::IntoElement)] pub struct FacePile { pub faces: SmallVec<[AnyElement; 2]>, } impl RenderOnce for FacePile { - fn render(self, _: &mut WindowContext) -> impl IntoElement { + fn render(self, _: &mut WindowContext) -> impl gpui::IntoElement { let player_count = self.faces.len(); let player_list = self.faces.into_iter().enumerate().map(|(ix, player)| { let isnt_last = ix < player_count - 1;