11use gpui:: {
2- actions, anchored, deferred, div, prelude:: FluentBuilder as _, px, AnyElement , App , Bounds ,
3- Context , Corner , DismissEvent , DispatchPhase , Element , ElementId , Entity , EventEmitter ,
4- FocusHandle , Focusable , GlobalElementId , Hitbox , InteractiveElement as _, IntoElement ,
5- KeyBinding , LayoutId , ManagedView , MouseButton , MouseDownEvent , ParentElement , Pixels , Point ,
6- Render , Style , StyleRefinement , Styled , Window ,
2+ actions, anchored, canvas , deferred, div, prelude:: FluentBuilder as _, px, AnyElement , App ,
3+ Axis , Bounds , Context , Corner , DismissEvent , DispatchPhase , Element , ElementId , Entity ,
4+ EventEmitter , FocusHandle , Focusable , GlobalElementId , Hitbox , InteractiveElement as _,
5+ IntoElement , KeyBinding , LayoutId , ManagedView , MouseButton , MouseDownEvent , ParentElement ,
6+ Pixels , Point , Render , Style , StyleRefinement , Styled , Window ,
77} ;
88use std:: { cell:: RefCell , rc:: Rc } ;
99
1010use crate :: { Selectable , StyledExt as _} ;
1111
1212const CONTEXT : & str = "Popover" ;
13+ const WINDOW_EDGE_MARGIN : Pixels = px ( 8. ) ;
1314
1415actions ! ( popover, [ Escape ] ) ;
1516
9192 }
9293 }
9394
95+ /// The anchor point of the popover relative to the trigger element.
96+ ///
97+ /// Default: [`Corner::TopLeft`]
98+ ///
99+ /// The anchor you can imagine an arrow corner of the Popover.
100+ ///
101+ /// For example if use [`Corner::TopRight`]
102+ ///
103+ /// Then the Popover will placement on trigger element's bottom left corner, like this:
104+ ///
105+ /// ```
106+ /// [Trigger Button]
107+ /// |------------------------------------|
108+ /// | Popover with Corner::TopRight |
109+ /// |------------------------------------|
110+ /// ```
94111 pub fn anchor ( mut self , anchor : Corner ) -> Self {
95112 self . anchor = anchor;
96113 self
@@ -102,6 +119,10 @@ where
102119 self
103120 }
104121
122+ /// Set the trigger element of the popover.
123+ ///
124+ /// The Trigger must impl [`Selectable`] trait,
125+ /// used for display selected state when popover is open.
105126 pub fn trigger < T > ( mut self , trigger : T ) -> Self
106127 where
107128 T : Selectable + IntoElement + ' static ,
@@ -119,12 +140,12 @@ where
119140
120141 /// Set the content of the popover.
121142 ///
122- /// The `content ` is a closure that returns an `AnyElement`.
123- pub fn content < C > ( mut self , content : C ) -> Self
143+ /// The `builder ` is a closure that returns an `AnyElement`.
144+ pub fn content < C > ( mut self , builder : C ) -> Self
124145 where
125146 C : Fn ( & mut Window , & mut App ) -> Entity < M > + ' static ,
126147 {
127- self . content = Some ( Rc :: new ( content ) ) ;
148+ self . content = Some ( Rc :: new ( builder ) ) ;
128149 self
129150 }
130151
@@ -147,8 +168,56 @@ where
147168 ( trigger) ( open, window, cx)
148169 }
149170
150- fn resolved_corner ( & self , bounds : Bounds < Pixels > ) -> Point < Pixels > {
151- bounds. corner ( match self . anchor {
171+ fn resolved_corner (
172+ & self ,
173+ trigger_bounds : Bounds < Pixels > ,
174+ content_bounds : Option < Bounds < Pixels > > ,
175+ window : & Window ,
176+ ) -> Point < Pixels > {
177+ let mut anchor = self . anchor ;
178+
179+ // Switch corner based on content bounds if it overflows the window bounds.
180+ if let Some ( content_bounds) = content_bounds {
181+ let window_size =
182+ window. bounds ( ) . size - gpui:: size ( WINDOW_EDGE_MARGIN , WINDOW_EDGE_MARGIN ) ;
183+
184+ match anchor {
185+ Corner :: TopLeft => {
186+ if content_bounds. right ( ) >= window_size. width {
187+ anchor = anchor. other_side_corner_along ( Axis :: Horizontal ) ;
188+ } ;
189+ if content_bounds. bottom ( ) >= window_size. height {
190+ anchor = anchor. other_side_corner_along ( Axis :: Vertical ) ;
191+ } ;
192+ }
193+ Corner :: TopRight => {
194+ if content_bounds. left ( ) <= WINDOW_EDGE_MARGIN {
195+ anchor = anchor. other_side_corner_along ( Axis :: Horizontal ) ;
196+ } ;
197+ if content_bounds. bottom ( ) >= window_size. height {
198+ anchor = anchor. other_side_corner_along ( Axis :: Vertical ) ;
199+ } ;
200+ }
201+ Corner :: BottomLeft => {
202+ if content_bounds. right ( ) >= window_size. width {
203+ anchor = anchor. other_side_corner_along ( Axis :: Horizontal ) ;
204+ } ;
205+ if content_bounds. top ( ) <= WINDOW_EDGE_MARGIN {
206+ anchor = anchor. other_side_corner_along ( Axis :: Vertical ) ;
207+ } ;
208+ }
209+ Corner :: BottomRight => {
210+ if content_bounds. left ( ) <= WINDOW_EDGE_MARGIN {
211+ anchor = anchor. other_side_corner_along ( Axis :: Horizontal ) ;
212+ } ;
213+ if content_bounds. top ( ) <= WINDOW_EDGE_MARGIN {
214+ anchor = anchor. other_side_corner_along ( Axis :: Vertical ) ;
215+ } ;
216+ }
217+ }
218+ }
219+
220+ trigger_bounds. corner ( match anchor {
152221 Corner :: TopLeft => Corner :: BottomLeft ,
153222 Corner :: TopRight => Corner :: BottomRight ,
154223 Corner :: BottomLeft => Corner :: TopLeft ,
@@ -193,6 +262,7 @@ pub struct PopoverElementState<M> {
193262 content_view : Rc < RefCell < Option < Entity < M > > > > ,
194263 /// Trigger bounds for positioning the popover.
195264 trigger_bounds : Option < Bounds < Pixels > > ,
265+ content_bounds : Rc < RefCell < Option < Bounds < Pixels > > > > ,
196266}
197267
198268impl < M > Default for PopoverElementState < M > {
@@ -204,6 +274,7 @@ impl<M> Default for PopoverElementState<M> {
204274 trigger_element : None ,
205275 content_view : Rc :: new ( RefCell :: new ( None ) ) ,
206276 trigger_bounds : None ,
277+ content_bounds : Rc :: new ( RefCell :: new ( None ) ) ,
207278 }
208279 }
209280}
@@ -255,39 +326,56 @@ impl<M: ManagedView> Element for Popover<M> {
255326 if let Some ( content_view) = element_state. content_view . borrow_mut ( ) . as_mut ( ) {
256327 is_open = true ;
257328
258- let mut anchored = anchored ( )
259- . snap_to_window_with_margin ( px ( 8. ) )
260- . anchor ( view. anchor ) ;
329+ let mut anchored = anchored ( ) . anchor ( view. anchor ) ;
261330 if let Some ( trigger_bounds) = element_state. trigger_bounds {
262- anchored = anchored. position ( view. resolved_corner ( trigger_bounds) ) ;
331+ let content_bounds = element_state. content_bounds . borrow ( ) ;
332+ anchored = anchored. position ( view. resolved_corner (
333+ trigger_bounds,
334+ * content_bounds,
335+ window,
336+ ) ) ;
263337 }
264338
265339 let mut element = {
266340 let content_view_mut = element_state. content_view . clone ( ) ;
267341 let anchor = view. anchor ;
268342 let no_style = view. no_style ;
343+ let content_bounds = element_state. content_bounds . clone ( ) ;
344+
269345 deferred (
270- anchored. child (
271- div ( )
272- . size_full ( )
273- . occlude ( )
274- . when ( !no_style, |this| this. popover_style ( cx) )
275- . map ( |this| match anchor {
276- Corner :: TopLeft | Corner :: TopRight => this. top_1p5 ( ) ,
277- Corner :: BottomLeft | Corner :: BottomRight => {
278- this. bottom_1p5 ( )
279- }
280- } )
281- . child ( content_view. clone ( ) )
282- . when ( !no_style, |this| {
283- this. on_mouse_down_out ( move |_, window, _| {
284- // Update the element_state.content_view to `None`,
285- // so that the `paint`` method will not paint it.
286- * content_view_mut. borrow_mut ( ) = None ;
287- window. refresh ( ) ;
346+ anchored
347+ . snap_to_window_with_margin ( WINDOW_EDGE_MARGIN )
348+ . child (
349+ div ( )
350+ . size_full ( )
351+ . occlude ( )
352+ . when ( !no_style, |this| this. popover_style ( cx) )
353+ . map ( |this| match anchor {
354+ Corner :: TopLeft | Corner :: TopRight => this. top_1p5 ( ) ,
355+ Corner :: BottomLeft | Corner :: BottomRight => {
356+ this. bottom_1p5 ( )
357+ }
358+ } )
359+ . child ( content_view. clone ( ) )
360+ . when ( !no_style, |this| {
361+ this. on_mouse_down_out ( move |_, window, _| {
362+ // Update the element_state.content_view to `None`,
363+ // so that the `paint`` method will not paint it.
364+ * content_view_mut. borrow_mut ( ) = None ;
365+ window. refresh ( ) ;
366+ } )
288367 } )
289- } ) ,
290- ) ,
368+ . child (
369+ canvas (
370+ |_, _, _| { } ,
371+ move |bounds, _, _, _| {
372+ content_bounds. borrow_mut ( ) . replace ( bounds) ;
373+ } ,
374+ )
375+ . size_full ( )
376+ . absolute ( ) ,
377+ ) ,
378+ ) ,
291379 )
292380 . with_priority ( 1 )
293381 . into_any ( )
0 commit comments