From e1c8369b3dbc0e6188f3b758971069d16782f5c2 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 30 Nov 2023 16:39:43 -0700 Subject: [PATCH] Rename `menu_handle` to `right_click_menu` and `child` to `trigger` This makes things more in-line with `popover_menu`. --- crates/collab_ui2/src/collab_titlebar_item.rs | 2 +- crates/ui2/src/components.rs | 2 + crates/ui2/src/components/context_menu.rs | 178 +---------------- crates/ui2/src/components/right_click_menu.rs | 185 ++++++++++++++++++ .../src/components/stories/context_menu.rs | 42 +--- crates/workspace2/src/dock.rs | 12 +- 6 files changed, 206 insertions(+), 215 deletions(-) create mode 100644 crates/ui2/src/components/right_click_menu.rs diff --git a/crates/collab_ui2/src/collab_titlebar_item.rs b/crates/collab_ui2/src/collab_titlebar_item.rs index e4ca42867835aeab69eb8ba9cd2d30f8c2a4249c..a04b6a50aa7faed72b50d45d61ed31dedfeb5346 100644 --- a/crates/collab_ui2/src/collab_titlebar_item.rs +++ b/crates/collab_ui2/src/collab_titlebar_item.rs @@ -291,7 +291,7 @@ impl Render for CollabTitlebarItem { // this.child( popover_menu("user-menu") - .menu(|cx| ContextMenu::build(cx, |menu, cx| menu.header("ADADA"))) + .menu(|cx| ContextMenu::build(cx, |menu, _| menu.header("ADADA"))) .trigger( ButtonLike::new("user-menu") .child( diff --git a/crates/ui2/src/components.rs b/crates/ui2/src/components.rs index 28dc8f3f06a65f9e091ca49146e320c741c162f6..17271de48d4993c111c5cececd50499c6ef801b3 100644 --- a/crates/ui2/src/components.rs +++ b/crates/ui2/src/components.rs @@ -10,6 +10,7 @@ mod label; mod list; mod popover; mod popover_menu; +mod right_click_menu; mod stack; mod tooltip; @@ -28,6 +29,7 @@ pub use label::*; pub use list::*; pub use popover::*; pub use popover_menu::*; +pub use right_click_menu::*; pub use stack::*; pub use tooltip::*; diff --git a/crates/ui2/src/components/context_menu.rs b/crates/ui2/src/components/context_menu.rs index f071d188a12f8dc1b892215c11ba5e4c0ff650f5..562639ec5890a131918a411e239d3f886b196d4d 100644 --- a/crates/ui2/src/components/context_menu.rs +++ b/crates/ui2/src/components/context_menu.rs @@ -2,12 +2,11 @@ use crate::{ h_stack, prelude::*, v_stack, KeyBinding, Label, List, ListItem, ListSeparator, ListSubHeader, }; use gpui::{ - overlay, px, Action, AnchorCorner, AnyElement, AppContext, Bounds, DismissEvent, DispatchPhase, - Div, EventEmitter, FocusHandle, FocusableView, IntoElement, LayoutId, ManagedView, MouseButton, - MouseDownEvent, Pixels, Point, Render, View, VisualContext, + px, Action, AppContext, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView, + IntoElement, Render, View, VisualContext, }; use menu::{SelectFirst, SelectLast, SelectNext, SelectPrev}; -use std::{cell::RefCell, rc::Rc}; +use std::rc::Rc; pub enum ContextMenuItem { Separator, @@ -208,174 +207,3 @@ impl Render for ContextMenu { ) } } - -pub struct MenuHandle { - id: ElementId, - child_builder: Option AnyElement + 'static>>, - menu_builder: Option View + 'static>>, - anchor: Option, - attach: Option, -} - -impl MenuHandle { - pub fn menu(mut self, f: impl Fn(&mut WindowContext) -> View + 'static) -> Self { - self.menu_builder = Some(Rc::new(f)); - self - } - - pub fn child(mut self, f: impl FnOnce(bool) -> R + 'static) -> Self { - self.child_builder = Some(Box::new(|b| f(b).into_element().into_any())); - self - } - - /// anchor defines which corner of the menu to anchor to the attachment point - /// (by default the cursor position, but see attach) - pub fn anchor(mut self, anchor: AnchorCorner) -> Self { - self.anchor = Some(anchor); - self - } - - /// attach defines which corner of the handle to attach the menu's anchor to - pub fn attach(mut self, attach: AnchorCorner) -> Self { - self.attach = Some(attach); - self - } -} - -pub fn menu_handle(id: impl Into) -> MenuHandle { - MenuHandle { - id: id.into(), - child_builder: None, - menu_builder: None, - anchor: None, - attach: None, - } -} - -pub struct MenuHandleState { - menu: Rc>>>, - position: Rc>>, - child_layout_id: Option, - child_element: Option, - menu_element: Option, -} - -impl Element for MenuHandle { - type State = MenuHandleState; - - fn layout( - &mut self, - element_state: Option, - cx: &mut WindowContext, - ) -> (gpui::LayoutId, Self::State) { - let (menu, position) = if let Some(element_state) = element_state { - (element_state.menu, element_state.position) - } else { - (Rc::default(), Rc::default()) - }; - - let mut menu_layout_id = None; - - let menu_element = menu.borrow_mut().as_mut().map(|menu| { - let mut overlay = overlay().snap_to_window(); - if let Some(anchor) = self.anchor { - overlay = overlay.anchor(anchor); - } - overlay = overlay.position(*position.borrow()); - - let mut element = overlay.child(menu.clone()).into_any(); - menu_layout_id = Some(element.layout(cx)); - element - }); - - let mut child_element = self - .child_builder - .take() - .map(|child_builder| (child_builder)(menu.borrow().is_some())); - - let child_layout_id = child_element - .as_mut() - .map(|child_element| child_element.layout(cx)); - - let layout_id = cx.request_layout( - &gpui::Style::default(), - menu_layout_id.into_iter().chain(child_layout_id), - ); - - ( - layout_id, - MenuHandleState { - menu, - position, - child_element, - child_layout_id, - menu_element, - }, - ) - } - - fn paint( - self, - bounds: Bounds, - element_state: &mut Self::State, - cx: &mut WindowContext, - ) { - if let Some(child) = element_state.child_element.take() { - child.paint(cx); - } - - if let Some(menu) = element_state.menu_element.take() { - menu.paint(cx); - return; - } - - let Some(builder) = self.menu_builder else { - return; - }; - let menu = element_state.menu.clone(); - let position = element_state.position.clone(); - let attach = self.attach.clone(); - let child_layout_id = element_state.child_layout_id.clone(); - - cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| { - if phase == DispatchPhase::Bubble - && event.button == MouseButton::Right - && bounds.contains_point(&event.position) - { - cx.stop_propagation(); - cx.prevent_default(); - - let new_menu = (builder)(cx); - let menu2 = menu.clone(); - cx.subscribe(&new_menu, move |_modal, _: &DismissEvent, cx| { - *menu2.borrow_mut() = None; - cx.notify(); - }) - .detach(); - cx.focus_view(&new_menu); - *menu.borrow_mut() = Some(new_menu); - - *position.borrow_mut() = if attach.is_some() && child_layout_id.is_some() { - attach - .unwrap() - .corner(cx.layout_bounds(child_layout_id.unwrap())) - } else { - cx.mouse_position() - }; - cx.notify(); - } - }); - } -} - -impl IntoElement for MenuHandle { - type Element = Self; - - fn element_id(&self) -> Option { - Some(self.id.clone()) - } - - fn into_element(self) -> Self::Element { - self - } -} diff --git a/crates/ui2/src/components/right_click_menu.rs b/crates/ui2/src/components/right_click_menu.rs new file mode 100644 index 0000000000000000000000000000000000000000..27c4fdab960f93bf9ac950c455d62d90f4ce049e --- /dev/null +++ b/crates/ui2/src/components/right_click_menu.rs @@ -0,0 +1,185 @@ +use std::{cell::RefCell, rc::Rc}; + +use gpui::{ + overlay, AnchorCorner, AnyElement, Bounds, DismissEvent, DispatchPhase, Element, ElementId, + IntoElement, LayoutId, ManagedView, MouseButton, MouseDownEvent, ParentElement, Pixels, Point, + View, VisualContext, WindowContext, +}; + +pub struct RightClickMenu { + id: ElementId, + child_builder: Option AnyElement + 'static>>, + menu_builder: Option View + 'static>>, + anchor: Option, + attach: Option, +} + +impl RightClickMenu { + pub fn menu(mut self, f: impl Fn(&mut WindowContext) -> View + 'static) -> Self { + self.menu_builder = Some(Rc::new(f)); + self + } + + pub fn trigger(mut self, e: E) -> Self { + self.child_builder = Some(Box::new(move |_| e.into_any_element())); + self + } + + /// anchor defines which corner of the menu to anchor to the attachment point + /// (by default the cursor position, but see attach) + pub fn anchor(mut self, anchor: AnchorCorner) -> Self { + self.anchor = Some(anchor); + self + } + + /// attach defines which corner of the handle to attach the menu's anchor to + pub fn attach(mut self, attach: AnchorCorner) -> Self { + self.attach = Some(attach); + self + } +} + +pub fn right_click_menu(id: impl Into) -> RightClickMenu { + RightClickMenu { + id: id.into(), + child_builder: None, + menu_builder: None, + anchor: None, + attach: None, + } +} + +pub struct MenuHandleState { + menu: Rc>>>, + position: Rc>>, + child_layout_id: Option, + child_element: Option, + menu_element: Option, +} + +impl Element for RightClickMenu { + type State = MenuHandleState; + + fn layout( + &mut self, + element_state: Option, + cx: &mut WindowContext, + ) -> (gpui::LayoutId, Self::State) { + let (menu, position) = if let Some(element_state) = element_state { + (element_state.menu, element_state.position) + } else { + (Rc::default(), Rc::default()) + }; + + let mut menu_layout_id = None; + + let menu_element = menu.borrow_mut().as_mut().map(|menu| { + let mut overlay = overlay().snap_to_window(); + if let Some(anchor) = self.anchor { + overlay = overlay.anchor(anchor); + } + overlay = overlay.position(*position.borrow()); + + let mut element = overlay.child(menu.clone()).into_any(); + menu_layout_id = Some(element.layout(cx)); + element + }); + + let mut child_element = self + .child_builder + .take() + .map(|child_builder| (child_builder)(menu.borrow().is_some())); + + let child_layout_id = child_element + .as_mut() + .map(|child_element| child_element.layout(cx)); + + let layout_id = cx.request_layout( + &gpui::Style::default(), + menu_layout_id.into_iter().chain(child_layout_id), + ); + + ( + layout_id, + MenuHandleState { + menu, + position, + child_element, + child_layout_id, + menu_element, + }, + ) + } + + fn paint( + self, + bounds: Bounds, + element_state: &mut Self::State, + cx: &mut WindowContext, + ) { + if let Some(child) = element_state.child_element.take() { + child.paint(cx); + } + + if let Some(menu) = element_state.menu_element.take() { + menu.paint(cx); + return; + } + + let Some(builder) = self.menu_builder else { + return; + }; + let menu = element_state.menu.clone(); + let position = element_state.position.clone(); + let attach = self.attach.clone(); + let child_layout_id = element_state.child_layout_id.clone(); + + cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| { + if phase == DispatchPhase::Bubble + && event.button == MouseButton::Right + && bounds.contains_point(&event.position) + { + cx.stop_propagation(); + cx.prevent_default(); + + let new_menu = (builder)(cx); + let menu2 = menu.clone(); + let previous_focus_handle = cx.focused(); + + cx.subscribe(&new_menu, move |modal, _: &DismissEvent, cx| { + if modal.focus_handle(cx).contains_focused(cx) { + if previous_focus_handle.is_some() { + cx.focus(&previous_focus_handle.as_ref().unwrap()) + } + } + *menu2.borrow_mut() = None; + cx.notify(); + }) + .detach(); + cx.focus_view(&new_menu); + *menu.borrow_mut() = Some(new_menu); + + *position.borrow_mut() = if attach.is_some() && child_layout_id.is_some() { + attach + .unwrap() + .corner(cx.layout_bounds(child_layout_id.unwrap())) + } else { + cx.mouse_position() + }; + cx.notify(); + } + }); + } +} + +impl IntoElement for RightClickMenu { + type Element = Self; + + fn element_id(&self) -> Option { + Some(self.id.clone()) + } + + fn into_element(self) -> Self::Element { + self + } +} diff --git a/crates/ui2/src/components/stories/context_menu.rs b/crates/ui2/src/components/stories/context_menu.rs index d5fb94df4f0f413dd117e3bc2321e5251c6dffda..dd1fe8d565adcd479e3605c14eda6e1c5b6e4125 100644 --- a/crates/ui2/src/components/stories/context_menu.rs +++ b/crates/ui2/src/components/stories/context_menu.rs @@ -2,7 +2,7 @@ use gpui::{actions, Action, AnchorCorner, Div, Render, View}; use story::Story; use crate::prelude::*; -use crate::{menu_handle, ContextMenu, Label}; +use crate::{right_click_menu, ContextMenu, Label}; actions!(PrintCurrentDate, PrintBestFood); @@ -45,25 +45,13 @@ impl Render for ContextMenuStory { .flex_col() .justify_between() .child( - menu_handle("test2") - .child(|is_open| { - Label::new(if is_open { - "TOP LEFT" - } else { - "RIGHT CLICK ME" - }) - }) + right_click_menu("test2") + .trigger(Label::new("TOP LEFT")) .menu(move |cx| build_menu(cx, "top left")), ) .child( - menu_handle("test1") - .child(|is_open| { - Label::new(if is_open { - "BOTTOM LEFT" - } else { - "RIGHT CLICK ME" - }) - }) + right_click_menu("test1") + .trigger(Label::new("BOTTOM LEFT")) .anchor(AnchorCorner::BottomLeft) .attach(AnchorCorner::TopLeft) .menu(move |cx| build_menu(cx, "bottom left")), @@ -75,26 +63,14 @@ impl Render for ContextMenuStory { .flex_col() .justify_between() .child( - menu_handle("test3") - .child(|is_open| { - Label::new(if is_open { - "TOP RIGHT" - } else { - "RIGHT CLICK ME" - }) - }) + right_click_menu("test3") + .trigger(Label::new("TOP RIGHT")) .anchor(AnchorCorner::TopRight) .menu(move |cx| build_menu(cx, "top right")), ) .child( - menu_handle("test4") - .child(|is_open| { - Label::new(if is_open { - "BOTTOM RIGHT" - } else { - "RIGHT CLICK ME" - }) - }) + right_click_menu("test4") + .trigger(Label::new("BOTTOM RIGHT")) .anchor(AnchorCorner::BottomRight) .attach(AnchorCorner::TopRight) .menu(move |cx| build_menu(cx, "bottom right")), diff --git a/crates/workspace2/src/dock.rs b/crates/workspace2/src/dock.rs index 06fdf7f9c160407b47d9102daeac97f9cd9d00a1..437e7c01926a460bed320f17b6e943966d714d48 100644 --- a/crates/workspace2/src/dock.rs +++ b/crates/workspace2/src/dock.rs @@ -7,8 +7,8 @@ use gpui::{ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use std::sync::Arc; -use ui::prelude::*; -use ui::{h_stack, menu_handle, ContextMenu, IconButton, Tooltip}; +use ui::{h_stack, ContextMenu, IconButton, Tooltip}; +use ui::{prelude::*, right_click_menu}; pub enum PanelEvent { ChangePosition, @@ -702,7 +702,7 @@ impl Render for PanelButtons { }; Some( - menu_handle(name) + right_click_menu(name) .menu(move |cx| { const POSITIONS: [DockPosition; 3] = [ DockPosition::Left, @@ -726,14 +726,14 @@ impl Render for PanelButtons { }) .anchor(menu_anchor) .attach(menu_attach) - .child(move |_is_open| { + .trigger( IconButton::new(name, icon) .selected(is_active_button) .action(action.boxed_clone()) .tooltip(move |cx| { Tooltip::for_action(tooltip.clone(), &*action, cx) - }) - }), + }), + ), ) });