From 329b116980ed7831b925844667002b9044c08628 Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Sat, 29 Jun 2024 18:04:47 +0800 Subject: [PATCH] Use Element to paint DropdownMenu to fix layer priority. --- crates/ui-story/src/lib.rs | 4 +- crates/ui-story/src/popover_story.rs | 4 +- .../{tooltip_stroy.rs => tooltip_story.rs} | 0 crates/ui/src/dropdown.rs | 158 +++++++++++++++--- crates/ui/src/popover.rs | 6 +- 5 files changed, 145 insertions(+), 27 deletions(-) rename crates/ui-story/src/{tooltip_stroy.rs => tooltip_story.rs} (100%) diff --git a/crates/ui-story/src/lib.rs b/crates/ui-story/src/lib.rs index d8bf76e7..0aa3f562 100644 --- a/crates/ui-story/src/lib.rs +++ b/crates/ui-story/src/lib.rs @@ -5,7 +5,7 @@ mod input_story; mod picker_story; mod popover_story; mod switch_story; -mod tooltip_stroy; +mod tooltip_story; use gpui::{ div, prelude::FluentBuilder as _, px, AnyElement, IntoElement, ParentElement, Render, @@ -28,7 +28,7 @@ use input_story::InputStory; use picker_story::PickerStory; use popover_story::PopoverStory; use switch_story::SwitchStory; -use tooltip_stroy::TooltipStory; +use tooltip_story::TooltipStory; pub fn story_case(name: &'static str, description: &'static str) -> StoryContainer { StoryContainer::new(name, description) diff --git a/crates/ui-story/src/popover_story.rs b/crates/ui-story/src/popover_story.rs index cab5e2d5..80472ab2 100644 --- a/crates/ui-story/src/popover_story.rs +++ b/crates/ui-story/src/popover_story.rs @@ -25,6 +25,7 @@ impl Render for PopoverStory { ) .child( h_flex().items_center().justify_between().child( + v_flex().gap_4().child( Popover::new("info-top-left") .trigger(Button::new("info", "Top Left").width(px(300.))) .content(|cx| { @@ -39,7 +40,8 @@ impl Render for PopoverStory { }, cx, ) - }), + }) + ).child("This is background text, should be covered by Popover.") ).child( Popover::new("info-top-right") .trigger(Button::new("info", "Top Right").width(px(300.))) diff --git a/crates/ui-story/src/tooltip_stroy.rs b/crates/ui-story/src/tooltip_story.rs similarity index 100% rename from crates/ui-story/src/tooltip_stroy.rs rename to crates/ui-story/src/tooltip_story.rs diff --git a/crates/ui/src/dropdown.rs b/crates/ui/src/dropdown.rs index 8eb5285d..73a50211 100644 --- a/crates/ui/src/dropdown.rs +++ b/crates/ui/src/dropdown.rs @@ -1,8 +1,9 @@ use gpui::{ - actions, div, prelude::FluentBuilder as _, px, AppContext, ElementId, FocusHandle, - FocusableView, InteractiveElement, IntoElement, KeyBinding, ParentElement as _, Render, - SharedString, StatefulInteractiveElement as _, Styled as _, View, ViewContext, - VisualContext as _, WeakView, + actions, deferred, div, prelude::FluentBuilder as _, px, AnyElement, AppContext, DismissEvent, + Element, ElementId, EventEmitter, FocusHandle, FocusableView, InteractiveElement, IntoElement, + KeyBinding, LayoutId, ParentElement as _, Render, SharedString, + StatefulInteractiveElement as _, Styled as _, View, ViewContext, VisualContext as _, WeakView, + WindowContext, }; actions!(dropdown, [Up, Down, Enter, Escape]); @@ -172,6 +173,9 @@ where if !self.open { self.open = true; cx.notify(); + } else { + self.picker.focus_handle(cx).focus(cx); + cx.dispatch_action(Box::new(picker::Confirm)); } } @@ -179,8 +183,25 @@ where self.open = false; cx.notify(); } + + fn render_menu_content(&self, cx: &WindowContext) -> impl IntoElement { + div() + .absolute() + .mt_1p5() + .bg(cx.theme().background) + .border_1() + .border_color(cx.theme().input) + .rounded(px(cx.theme().radius)) + .shadow_md() + .track_focus(&self.picker.focus_handle(cx)) + .child(self.picker.clone()) + .on_mouse_down_out(|_, cx| { + cx.dispatch_action(Box::new(Escape)); + }) + } } +impl EventEmitter for Dropdown where D: DropdownDelegate + 'static {} impl FocusableView for Dropdown where D: DropdownDelegate + 'static, @@ -242,23 +263,118 @@ where ), ), ) - .when(self.open, |this| { - this.child( - div() - .absolute() - .mt_1p5() - .bg(cx.theme().background) - .border_1() - .border_color(cx.theme().input) - .rounded(px(cx.theme().radius)) - .shadow_md() - .track_focus(&self.picker.focus_handle(cx)) - .child(self.picker.clone()) - .on_mouse_down_out(cx.listener(|view, _, cx| { - view.open = false; - cx.notify(); - })), - ) + .child(DropdownMenuElement { + id: "dropdown-menu".into(), + dropdown: cx.view().clone(), }) } } + +struct DropdownMenuElement { + id: ElementId, + dropdown: View>, +} + +struct DropdownMenuElementState { + menu_element: Option, + layout_id: Option, +} + +impl Default for DropdownMenuElementState { + fn default() -> Self { + Self { + menu_element: None, + layout_id: None, + } + } +} + +impl IntoElement for DropdownMenuElement +where + D: DropdownDelegate + 'static, +{ + type Element = Self; + + fn into_element(self) -> Self::Element { + self + } +} + +impl Element for DropdownMenuElement +where + D: DropdownDelegate + 'static, +{ + type RequestLayoutState = DropdownMenuElementState; + + type PrepaintState = (); + + fn id(&self) -> Option { + Some(self.id.clone()) + } + + fn request_layout( + &mut self, + id: Option<&gpui::GlobalElementId>, + cx: &mut gpui::WindowContext, + ) -> (gpui::LayoutId, Self::RequestLayoutState) { + cx.with_element_state( + id.unwrap(), + |element_state: Option, cx| { + let state = element_state.unwrap_or_default(); + + let menu = self + .dropdown + .read(cx) + .render_menu_content(cx) + .into_any_element(); + + let mut element = deferred(menu).with_priority(1).into_any(); + let layout_id = element.request_layout(cx); + ( + ( + layout_id.clone(), + DropdownMenuElementState { + layout_id: Some(layout_id), + menu_element: Some(element), + }, + ), + state, + ) + }, + ) + } + + fn prepaint( + &mut self, + _: Option<&gpui::GlobalElementId>, + _: gpui::Bounds, + request_layout: &mut DropdownMenuElementState, + cx: &mut gpui::WindowContext, + ) -> Self::PrepaintState { + if self.dropdown.read(cx).open { + if let Some(element) = &mut request_layout.menu_element { + element.prepaint(cx); + } + + if let Some(layout_id) = request_layout.layout_id { + let bounds = cx.layout_bounds(layout_id); + cx.insert_hitbox(bounds, false); + } + } + } + + fn paint( + &mut self, + _: Option<&gpui::GlobalElementId>, + _: gpui::Bounds, + request_layout: &mut Self::RequestLayoutState, + _: &mut Self::PrepaintState, + cx: &mut gpui::WindowContext, + ) { + if self.dropdown.read(cx).open { + if let Some(element) = &mut request_layout.menu_element { + element.paint(cx); + } + } + } +} diff --git a/crates/ui/src/popover.rs b/crates/ui/src/popover.rs index 2dc5b77b..7a8111fc 100644 --- a/crates/ui/src/popover.rs +++ b/crates/ui/src/popover.rs @@ -145,13 +145,14 @@ impl Popover { cx.refresh(); } - fn resolved_attach(&self) -> AnchorCorner { + fn resolved_corner(&self, bounds: Bounds) -> Point { match self.anchor { AnchorCorner::TopLeft => AnchorCorner::BottomLeft, AnchorCorner::TopRight => AnchorCorner::BottomRight, AnchorCorner::BottomLeft => AnchorCorner::TopLeft, AnchorCorner::BottomRight => AnchorCorner::TopRight, } + .corner(bounds) } fn resolved_offset(&self, _cx: &WindowContext) -> Point { @@ -223,8 +224,7 @@ impl Element for Popover { let mut anchored = anchored().snap_to_window().anchor(self.anchor); if let Some(trigger_bounds) = element_state.trigger_bounds { anchored = anchored.position( - self.resolved_attach().corner(trigger_bounds) - + self.resolved_offset(cx), + self.resolved_corner(trigger_bounds) + self.resolved_offset(cx), ); } let mut element = deferred(anchored.child(popover.clone()))