diff --git a/crates/auto_update2/src/update_notification.rs b/crates/auto_update2/src/update_notification.rs index d15d82e112e612143ef2aaff509965899818e95a..4a2efcf8076bb882a67c1f25b6300dd4cd48c1d1 100644 --- a/crates/auto_update2/src/update_notification.rs +++ b/crates/auto_update2/src/update_notification.rs @@ -51,6 +51,6 @@ impl UpdateNotification { } pub fn dismiss(&mut self, cx: &mut ViewContext) { - cx.emit(DismissEvent::Dismiss); + cx.emit(DismissEvent); } } diff --git a/crates/collab_ui2/src/collab_panel/contact_finder.rs b/crates/collab_ui2/src/collab_panel/contact_finder.rs index 31f2764b116e630a03f48d33def2b7edcbb65118..fee04ec40c08dfbd5c72e44287695d690ebe472a 100644 --- a/crates/collab_ui2/src/collab_panel/contact_finder.rs +++ b/crates/collab_ui2/src/collab_panel/contact_finder.rs @@ -161,7 +161,7 @@ impl PickerDelegate for ContactFinderDelegate { fn dismissed(&mut self, cx: &mut ViewContext>) { //cx.emit(PickerEvent::Dismiss); self.parent - .update(cx, |_, cx| cx.emit(DismissEvent::Dismiss)) + .update(cx, |_, cx| cx.emit(DismissEvent)) .log_err(); } diff --git a/crates/command_palette2/src/command_palette.rs b/crates/command_palette2/src/command_palette.rs index 75e98bfa252e0f37ac358658a595e6d178bdb227..f9b58b1d56139326192edf7e5fa008d90351283d 100644 --- a/crates/command_palette2/src/command_palette.rs +++ b/crates/command_palette2/src/command_palette.rs @@ -269,7 +269,7 @@ impl PickerDelegate for CommandPaletteDelegate { fn dismissed(&mut self, cx: &mut ViewContext>) { self.command_palette - .update(cx, |_, cx| cx.emit(DismissEvent::Dismiss)) + .update(cx, |_, cx| cx.emit(DismissEvent)) .log_err(); } diff --git a/crates/file_finder2/src/file_finder.rs b/crates/file_finder2/src/file_finder.rs index 2f7b26dfb55c9a548eea0ab4fbc88a5544f29921..a24da580ebea9db1467f5936930558821fbd5220 100644 --- a/crates/file_finder2/src/file_finder.rs +++ b/crates/file_finder2/src/file_finder.rs @@ -687,9 +687,7 @@ impl PickerDelegate for FileFinderDelegate { .log_err(); } } - finder - .update(&mut cx, |_, cx| cx.emit(DismissEvent::Dismiss)) - .ok()?; + finder.update(&mut cx, |_, cx| cx.emit(DismissEvent)).ok()?; Some(()) }) @@ -700,7 +698,7 @@ impl PickerDelegate for FileFinderDelegate { fn dismissed(&mut self, cx: &mut ViewContext>) { self.file_finder - .update(cx, |_, cx| cx.emit(DismissEvent::Dismiss)) + .update(cx, |_, cx| cx.emit(DismissEvent)) .log_err(); } diff --git a/crates/go_to_line2/src/go_to_line.rs b/crates/go_to_line2/src/go_to_line.rs index c734281d22d731b0c55d617c8cf7f58e60077a45..5ad95c1f6ea6cf0fb49e185a044d8be7eaf383fe 100644 --- a/crates/go_to_line2/src/go_to_line.rs +++ b/crates/go_to_line2/src/go_to_line.rs @@ -90,7 +90,7 @@ impl GoToLine { ) { match event { // todo!() this isn't working... - editor::EditorEvent::Blurred => cx.emit(DismissEvent::Dismiss), + editor::EditorEvent::Blurred => cx.emit(DismissEvent), editor::EditorEvent::BufferEdited { .. } => self.highlight_current_line(cx), _ => {} } @@ -125,7 +125,7 @@ impl GoToLine { } fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext) { - cx.emit(DismissEvent::Dismiss); + cx.emit(DismissEvent); } fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext) { @@ -142,7 +142,7 @@ impl GoToLine { self.prev_scroll_position.take(); } - cx.emit(DismissEvent::Dismiss); + cx.emit(DismissEvent); } } diff --git a/crates/gpui2/src/app/async_context.rs b/crates/gpui2/src/app/async_context.rs index 11420bee69d2c8acd9cb179df5e68bcd7c3e9e5b..92ccc118f12826076621629a470dfda5c6a545d1 100644 --- a/crates/gpui2/src/app/async_context.rs +++ b/crates/gpui2/src/app/async_context.rs @@ -325,8 +325,7 @@ impl VisualContext for AsyncWindowContext { where V: crate::ManagedView, { - self.window.update(self, |_, cx| { - view.update(cx, |_, cx| cx.emit(DismissEvent::Dismiss)) - }) + self.window + .update(self, |_, cx| view.update(cx, |_, cx| cx.emit(DismissEvent))) } } diff --git a/crates/gpui2/src/app/test_context.rs b/crates/gpui2/src/app/test_context.rs index 71bc8e3d8172713e1f2c0ab7d7f5ffb33fe4171e..9637720a67e78ba6f735534f924b4028d8d8c75f 100644 --- a/crates/gpui2/src/app/test_context.rs +++ b/crates/gpui2/src/app/test_context.rs @@ -611,7 +611,7 @@ impl<'a> VisualContext for VisualTestContext<'a> { { self.window .update(self.cx, |_, cx| { - view.update(cx, |_, cx| cx.emit(crate::DismissEvent::Dismiss)) + view.update(cx, |_, cx| cx.emit(crate::DismissEvent)) }) .unwrap() } diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index b02005aa78059ce249dc6d5131b862a097c34e38..507c46d067bec9f9fa71939190a0ad3f4748789e 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -197,9 +197,7 @@ pub trait ManagedView: FocusableView + EventEmitter {} impl> ManagedView for M {} -pub enum DismissEvent { - Dismiss, -} +pub struct DismissEvent; // Holds the state for a specific window. pub struct Window { @@ -1482,13 +1480,15 @@ impl<'a> WindowContext<'a> { } } - pub fn constructor_for( + pub fn handler_for( &self, view: &View, - f: impl Fn(&mut V, &mut ViewContext) -> R + 'static, - ) -> impl Fn(&mut WindowContext) -> R + 'static { - let view = view.clone(); - move |cx: &mut WindowContext| view.update(cx, |view, cx| f(view, cx)) + f: impl Fn(&mut V, &mut ViewContext) + 'static, + ) -> impl Fn(&mut WindowContext) { + let view = view.downgrade(); + move |cx: &mut WindowContext| { + view.update(cx, |view, cx| f(view, cx)).ok(); + } } //========== ELEMENT RELATED FUNCTIONS =========== @@ -1699,7 +1699,7 @@ impl VisualContext for WindowContext<'_> { where V: ManagedView, { - self.update_view(view, |_, cx| cx.emit(DismissEvent::Dismiss)) + self.update_view(view, |_, cx| cx.emit(DismissEvent)) } } @@ -2386,7 +2386,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { where V: ManagedView, { - self.defer(|_, cx| cx.emit(DismissEvent::Dismiss)) + self.defer(|_, cx| cx.emit(DismissEvent)) } pub fn listener( diff --git a/crates/project_panel2/src/project_panel.rs b/crates/project_panel2/src/project_panel.rs index 359c5a8e6bcebf769ae6a7a3ffa9d11220fd96c9..e4846af76ce3f2a355976ff2561d9dd0580a5d9c 100644 --- a/crates/project_panel2/src/project_panel.rs +++ b/crates/project_panel2/src/project_panel.rs @@ -8,10 +8,11 @@ use file_associations::FileAssociations; use anyhow::{anyhow, Result}; use gpui::{ - actions, div, px, uniform_list, Action, AppContext, AssetSource, AsyncWindowContext, - ClipboardItem, Div, EventEmitter, FocusHandle, Focusable, FocusableView, InteractiveElement, - Model, MouseDownEvent, ParentElement, Pixels, Point, PromptLevel, Render, Stateful, Styled, - Task, UniformListScrollHandle, View, ViewContext, VisualContext as _, WeakView, WindowContext, + actions, div, overlay, px, uniform_list, Action, AppContext, AssetSource, AsyncWindowContext, + ClipboardItem, DismissEvent, Div, EventEmitter, FocusHandle, Focusable, FocusableView, + InteractiveElement, Model, MouseButton, MouseDownEvent, ParentElement, Pixels, Point, + PromptLevel, Render, Stateful, Styled, Subscription, Task, UniformListScrollHandle, View, + ViewContext, VisualContext as _, WeakView, WindowContext, }; use menu::{Confirm, SelectNext, SelectPrev}; use project::{ @@ -29,7 +30,7 @@ use std::{ sync::Arc, }; use theme::ActiveTheme as _; -use ui::{v_stack, IconElement, Label, ListItem}; +use ui::{v_stack, ContextMenu, IconElement, Label, ListItem}; use unicase::UniCase; use util::{maybe, ResultExt, TryFutureExt}; use workspace::{ @@ -49,6 +50,7 @@ pub struct ProjectPanel { last_worktree_root_id: Option, expanded_dir_ids: HashMap>, selection: Option, + context_menu: Option<(View, Point, Subscription)>, edit_state: Option, filename_editor: View, clipboard_entry: Option, @@ -231,6 +233,7 @@ impl ProjectPanel { expanded_dir_ids: Default::default(), selection: None, edit_state: None, + context_menu: None, filename_editor, clipboard_entry: None, // context_menu: cx.add_view(|cx| ContextMenu::new(view_id, cx)), @@ -366,80 +369,93 @@ impl ProjectPanel { fn deploy_context_menu( &mut self, - _position: Point, - _entry_id: ProjectEntryId, - _cx: &mut ViewContext, + position: Point, + entry_id: ProjectEntryId, + cx: &mut ViewContext, ) { - // todo!() - // let project = self.project.read(cx); + let this = cx.view().clone(); + let project = self.project.read(cx); - // let worktree_id = if let Some(id) = project.worktree_id_for_entry(entry_id, cx) { - // id - // } else { - // return; - // }; + let worktree_id = if let Some(id) = project.worktree_id_for_entry(entry_id, cx) { + id + } else { + return; + }; - // self.selection = Some(Selection { - // worktree_id, - // entry_id, - // }); - - // let mut menu_entries = Vec::new(); - // if let Some((worktree, entry)) = self.selected_entry(cx) { - // let is_root = Some(entry) == worktree.root_entry(); - // if !project.is_remote() { - // menu_entries.push(ContextMenuItem::action( - // "Add Folder to Project", - // workspace::AddFolderToProject, - // )); - // if is_root { - // let project = self.project.clone(); - // menu_entries.push(ContextMenuItem::handler("Remove from Project", move |cx| { - // project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx)); - // })); - // } - // } - // menu_entries.push(ContextMenuItem::action("New File", NewFile)); - // menu_entries.push(ContextMenuItem::action("New Folder", NewDirectory)); - // menu_entries.push(ContextMenuItem::Separator); - // menu_entries.push(ContextMenuItem::action("Cut", Cut)); - // menu_entries.push(ContextMenuItem::action("Copy", Copy)); - // if let Some(clipboard_entry) = self.clipboard_entry { - // if clipboard_entry.worktree_id() == worktree.id() { - // menu_entries.push(ContextMenuItem::action("Paste", Paste)); - // } - // } - // menu_entries.push(ContextMenuItem::Separator); - // menu_entries.push(ContextMenuItem::action("Copy Path", CopyPath)); - // menu_entries.push(ContextMenuItem::action( - // "Copy Relative Path", - // CopyRelativePath, - // )); - - // if entry.is_dir() { - // menu_entries.push(ContextMenuItem::Separator); - // } - // menu_entries.push(ContextMenuItem::action("Reveal in Finder", RevealInFinder)); - // if entry.is_dir() { - // menu_entries.push(ContextMenuItem::action("Open in Terminal", OpenInTerminal)); - // menu_entries.push(ContextMenuItem::action( - // "Search Inside", - // NewSearchInDirectory, - // )); - // } - - // menu_entries.push(ContextMenuItem::Separator); - // menu_entries.push(ContextMenuItem::action("Rename", Rename)); - // if !is_root { - // menu_entries.push(ContextMenuItem::action("Delete", Delete)); - // } - // } - - // // self.context_menu.update(cx, |menu, cx| { - // // menu.show(position, AnchorCorner::TopLeft, menu_entries, cx); - // // }); - - // cx.notify(); + self.selection = Some(Selection { + worktree_id, + entry_id, + }); + + if let Some((worktree, entry)) = self.selected_entry(cx) { + let is_root = Some(entry) == worktree.root_entry(); + let is_dir = entry.is_dir(); + let worktree_id = worktree.id(); + let is_local = project.is_local(); + + let context_menu = ContextMenu::build(cx, |mut menu, cx| { + if is_local { + menu = menu.action( + "Add Folder to Project", + Box::new(workspace::AddFolderToProject), + cx, + ); + if is_root { + menu = menu.entry( + "Remove from Project", + 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), cx) + .action("New Folder", Box::new(NewDirectory), cx) + .separator() + .action("Cut", Box::new(Cut), cx) + .action("Copy", Box::new(Copy), cx); + + if let Some(clipboard_entry) = self.clipboard_entry { + if clipboard_entry.worktree_id() == worktree_id { + menu = menu.action("Paste", Box::new(Paste), cx); + } + } + + menu = menu + .separator() + .action("Copy Path", Box::new(CopyPath), cx) + .action("Copy Relative Path", Box::new(CopyRelativePath), cx) + .separator() + .action("Reveal in Finder", Box::new(RevealInFinder), cx); + + if is_dir { + menu = menu + .action("Open in Terminal", Box::new(OpenInTerminal), cx) + .action("Search Inside", Box::new(NewSearchInDirectory), cx) + } + + menu = menu.separator().action("Rename", Box::new(Rename), cx); + + if !is_root { + menu = menu.action("Delete", Box::new(Delete), cx); + } + + menu + }); + + cx.focus_view(&context_menu); + let subscription = cx.subscribe(&context_menu, |this, _, _: &DismissEvent, cx| { + this.context_menu.take(); + cx.notify(); + }); + self.context_menu = Some((context_menu, position, subscription)); + } + + cx.notify(); } fn expand_selected_entry(&mut self, _: &ExpandSelectedEntry, cx: &mut ViewContext) { @@ -643,7 +659,6 @@ impl ProjectPanel { } fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext) { - dbg!("odd"); self.edit_state = None; self.update_visible_entries(None, cx); cx.focus(&self.focus_handle); @@ -1370,7 +1385,7 @@ impl ProjectPanel { }) .child( if let (Some(editor), true) = (Some(&self.filename_editor), show_editor) { - div().w_full().child(editor.clone()) + div().h_full().w_full().child(editor.clone()) } else { div() .text_color(filename_text_color) @@ -1379,6 +1394,9 @@ impl ProjectPanel { .ml_1(), ) .on_click(cx.listener(move |this, event: &gpui::ClickEvent, cx| { + if event.down.button == MouseButton::Right { + return; + } if !show_editor { if kind.is_dir() { this.toggle_expanded(entry_id, cx); @@ -1415,6 +1433,7 @@ impl Render for ProjectPanel { div() .id("project-panel") .size_full() + .relative() .key_context("ProjectPanel") .on_action(cx.listener(Self::select_next)) .on_action(cx.listener(Self::select_prev)) @@ -1458,6 +1477,12 @@ impl Render for ProjectPanel { .size_full() .track_scroll(self.list.clone()), ) + .children(self.context_menu.as_ref().map(|(menu, position, _)| { + overlay() + .position(*position) + .anchor(gpui::AnchorCorner::BottomLeft) + .child(menu.clone()) + })) } else { v_stack() .id("empty-project_panel") diff --git a/crates/terminal_view2/src/terminal_view.rs b/crates/terminal_view2/src/terminal_view.rs index 9f3ed313880989631c25b7cb7f9fe1460c9224c3..b007d58c34bcb2163f42bd2b88e1979a18152f56 100644 --- a/crates/terminal_view2/src/terminal_view.rs +++ b/crates/terminal_view2/src/terminal_view.rs @@ -298,9 +298,12 @@ impl TerminalView { position: gpui::Point, cx: &mut ViewContext, ) { - self.context_menu = Some(ContextMenu::build(cx, |menu, _| { - menu.action("Clear", Box::new(Clear)) - .action("Close", Box::new(CloseActiveItem { save_intent: None })) + self.context_menu = Some(ContextMenu::build(cx, |menu, cx| { + menu.action("Clear", Box::new(Clear), cx).action( + "Close", + Box::new(CloseActiveItem { save_intent: None }), + cx, + ) })); dbg!(&position); // todo!() diff --git a/crates/theme_selector2/src/theme_selector.rs b/crates/theme_selector2/src/theme_selector.rs index ec38f7083cd93f9e61fc88aaa43dd67306621f7c..7b0a0c3d3ae72c646bf5590a29a272528f312279 100644 --- a/crates/theme_selector2/src/theme_selector.rs +++ b/crates/theme_selector2/src/theme_selector.rs @@ -177,7 +177,7 @@ impl PickerDelegate for ThemeSelectorDelegate { self.view .update(cx, |_, cx| { - cx.emit(DismissEvent::Dismiss); + cx.emit(DismissEvent); }) .ok(); } diff --git a/crates/ui2/src/components/context_menu.rs b/crates/ui2/src/components/context_menu.rs index 8cd519f62916dd351f2056100acb8d396801f8c0..3772fb1bd2681ee635f3cc896568739062280c19 100644 --- a/crates/ui2/src/components/context_menu.rs +++ b/crates/ui2/src/components/context_menu.rs @@ -1,23 +1,28 @@ -use std::cell::RefCell; -use std::rc::Rc; - -use crate::{prelude::*, v_stack, Label, List}; -use crate::{ListItem, ListSeparator, ListSubHeader}; +use crate::{ + h_stack, prelude::*, v_stack, KeyBinding, Label, List, ListItem, ListSeparator, ListSubHeader, +}; use gpui::{ - overlay, px, Action, AnchorCorner, AnyElement, AppContext, Bounds, ClickEvent, DismissEvent, - DispatchPhase, Div, EventEmitter, FocusHandle, FocusableView, IntoElement, LayoutId, - ManagedView, MouseButton, MouseDownEvent, Pixels, Point, Render, View, VisualContext, + overlay, px, Action, AnchorCorner, AnyElement, AppContext, Bounds, DismissEvent, DispatchPhase, + Div, EventEmitter, FocusHandle, FocusableView, IntoElement, LayoutId, ManagedView, MouseButton, + MouseDownEvent, Pixels, Point, Render, View, VisualContext, }; +use menu::{SelectFirst, SelectLast, SelectNext, SelectPrev}; +use std::{cell::RefCell, rc::Rc}; pub enum ContextMenuItem { Separator, Header(SharedString), - Entry(SharedString, Rc), + Entry { + label: SharedString, + handler: Rc, + key_binding: Option, + }, } pub struct ContextMenu { items: Vec, focus_handle: FocusHandle, + selected_index: Option, } impl FocusableView for ContextMenu { @@ -39,6 +44,7 @@ impl ContextMenu { Self { items: Default::default(), focus_handle: cx.focus_handle(), + selected_index: None, }, cx, ) @@ -58,27 +64,90 @@ impl ContextMenu { pub fn entry( mut self, label: impl Into, - on_click: impl Fn(&ClickEvent, &mut WindowContext) + 'static, + on_click: impl Fn(&mut WindowContext) + 'static, ) -> Self { - self.items - .push(ContextMenuItem::Entry(label.into(), Rc::new(on_click))); + self.items.push(ContextMenuItem::Entry { + label: label.into(), + handler: Rc::new(on_click), + key_binding: None, + }); self } - pub fn action(self, label: impl Into, action: Box) -> Self { - // todo: add the keybindings to the list entry - self.entry(label.into(), move |_, cx| { - cx.dispatch_action(action.boxed_clone()) - }) + pub fn action( + mut self, + label: impl Into, + action: Box, + cx: &mut WindowContext, + ) -> Self { + self.items.push(ContextMenuItem::Entry { + label: label.into(), + key_binding: KeyBinding::for_action(&*action, cx), + handler: Rc::new(move |cx| cx.dispatch_action(action.boxed_clone())), + }); + self } pub fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext) { - // todo!() - cx.emit(DismissEvent::Dismiss); + if let Some(ContextMenuItem::Entry { handler, .. }) = + self.selected_index.and_then(|ix| self.items.get(ix)) + { + (handler)(cx) + } + cx.emit(DismissEvent); } pub fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext) { - cx.emit(DismissEvent::Dismiss); + cx.emit(DismissEvent); + } + + fn select_first(&mut self, _: &SelectFirst, cx: &mut ViewContext) { + self.selected_index = self.items.iter().position(|item| item.is_selectable()); + cx.notify(); + } + + fn select_last(&mut self, _: &SelectLast, cx: &mut ViewContext) { + for (ix, item) in self.items.iter().enumerate().rev() { + if item.is_selectable() { + self.selected_index = Some(ix); + cx.notify(); + break; + } + } + } + + fn select_next(&mut self, _: &SelectNext, cx: &mut ViewContext) { + if let Some(ix) = self.selected_index { + for (ix, item) in self.items.iter().enumerate().skip(ix + 1) { + if item.is_selectable() { + self.selected_index = Some(ix); + cx.notify(); + break; + } + } + } else { + self.select_first(&Default::default(), cx); + } + } + + pub fn select_prev(&mut self, _: &SelectPrev, cx: &mut ViewContext) { + if let Some(ix) = self.selected_index { + for (ix, item) in self.items.iter().enumerate().take(ix).rev() { + if item.is_selectable() { + self.selected_index = Some(ix); + cx.notify(); + break; + } + } + } else { + self.select_last(&Default::default(), cx); + } + } +} + +impl ContextMenuItem { + fn is_selectable(&self) -> bool { + matches!(self, Self::Entry { .. }) } } @@ -90,38 +159,51 @@ impl Render for ContextMenu { v_stack() .min_w(px(200.)) .track_focus(&self.focus_handle) - .on_mouse_down_out( - cx.listener(|this: &mut Self, _, cx| this.cancel(&Default::default(), cx)), - ) - // .on_action(ContextMenu::select_first) - // .on_action(ContextMenu::select_last) - // .on_action(ContextMenu::select_next) - // .on_action(ContextMenu::select_prev) + .on_mouse_down_out(cx.listener(|this, _, cx| this.cancel(&Default::default(), cx))) + .key_context("menu") + .on_action(cx.listener(ContextMenu::select_first)) + .on_action(cx.listener(ContextMenu::select_last)) + .on_action(cx.listener(ContextMenu::select_next)) + .on_action(cx.listener(ContextMenu::select_prev)) .on_action(cx.listener(ContextMenu::confirm)) .on_action(cx.listener(ContextMenu::cancel)) .flex_none() - // .bg(cx.theme().colors().elevated_surface_background) - // .border() - // .border_color(cx.theme().colors().border) .child( - List::new().children(self.items.iter().map(|item| match item { - ContextMenuItem::Separator => ListSeparator::new().into_any_element(), - ContextMenuItem::Header(header) => { - ListSubHeader::new(header.clone()).into_any_element() - } - ContextMenuItem::Entry(entry, callback) => { - let callback = callback.clone(); - let dismiss = cx.listener(|_, _, cx| cx.emit(DismissEvent::Dismiss)); - - ListItem::new(entry.clone()) - .child(Label::new(entry.clone())) - .on_click(move |event, cx| { - callback(event, cx); - dismiss(event, cx) - }) - .into_any_element() - } - })), + List::new().children(self.items.iter().enumerate().map( + |(ix, item)| match item { + ContextMenuItem::Separator => ListSeparator::new().into_any_element(), + ContextMenuItem::Header(header) => { + ListSubHeader::new(header.clone()).into_any_element() + } + ContextMenuItem::Entry { + label: entry, + handler: callback, + key_binding, + } => { + let callback = callback.clone(); + let dismiss = cx.listener(|_, _, cx| cx.emit(DismissEvent)); + + ListItem::new(entry.clone()) + .child( + h_stack() + .w_full() + .justify_between() + .child(Label::new(entry.clone())) + .children( + key_binding + .clone() + .map(|binding| div().ml_1().child(binding)), + ), + ) + .selected(Some(ix) == self.selected_index) + .on_click(move |event, cx| { + callback(cx); + dismiss(event, cx) + }) + .into_any_element() + } + }, + )), ), ) } @@ -177,6 +259,7 @@ pub struct MenuHandleState { child_element: Option, menu_element: Option, } + impl Element for MenuHandle { type State = MenuHandleState; @@ -264,11 +347,9 @@ impl Element for MenuHandle { let new_menu = (builder)(cx); let menu2 = menu.clone(); - cx.subscribe(&new_menu, move |_modal, e, cx| match e { - &DismissEvent::Dismiss => { - *menu2.borrow_mut() = None; - cx.notify(); - } + cx.subscribe(&new_menu, move |_modal, _: &DismissEvent, cx| { + *menu2.borrow_mut() = None; + cx.notify(); }) .detach(); cx.focus_view(&new_menu); diff --git a/crates/ui2/src/components/list.rs b/crates/ui2/src/components/list.rs index 6befc1eb8123a6f1e990e033fc3b4c02d0a6033a..5cd9c7f70940c7e720f4cfe02e3b06d3472863c8 100644 --- a/crates/ui2/src/components/list.rs +++ b/crates/ui2/src/components/list.rs @@ -374,17 +374,15 @@ impl RenderOnce for List { type Rendered = Div; fn render(self, _cx: &mut WindowContext) -> Self::Rendered { - let list_content = match (self.children.is_empty(), self.toggle) { - (false, _) => div().children(self.children), - (true, Toggle::Toggled(false)) => div(), - (true, _) => div().child(Label::new(self.empty_message.clone()).color(Color::Muted)), - }; - v_stack() .w_full() .py_1() .children(self.header.map(|header| header)) - .child(list_content) + .map(|this| match (self.children.is_empty(), self.toggle) { + (false, _) => this.children(self.children), + (true, Toggle::Toggled(false)) => this, + (true, _) => this.child(Label::new(self.empty_message.clone()).color(Color::Muted)), + }) } } diff --git a/crates/ui2/src/components/stories/context_menu.rs b/crates/ui2/src/components/stories/context_menu.rs index 9a8b7efbe666bb95317c98299b4ad72d0a1d23cd..d5fb94df4f0f413dd117e3bc2321e5251c6dffda 100644 --- a/crates/ui2/src/components/stories/context_menu.rs +++ b/crates/ui2/src/components/stories/context_menu.rs @@ -10,11 +10,11 @@ fn build_menu(cx: &mut WindowContext, header: impl Into) -> View { - this.dismiss_notification_internal(type_id, id, cx); - } + move |this, handle, event: &DismissEvent, cx| { + this.dismiss_notification_internal(type_id, id, cx); }, ) .detach(); @@ -260,7 +258,7 @@ pub mod simple_message_notification { } pub fn dismiss(&mut self, cx: &mut ViewContext) { - cx.emit(DismissEvent::Dismiss); + cx.emit(DismissEvent); } }