1
1
//! Show popup windows, tooltips, context menus etc.
2
2
3
- use crate :: * ;
4
-
5
- // ----------------------------------------------------------------------------
6
-
7
- /// Same state for all tooltips.
8
- #[ derive( Clone , Debug , Default ) ]
9
- pub ( crate ) struct TooltipState {
10
- last_common_id : Option < Id > ,
11
- individual_ids_and_sizes : ahash:: HashMap < usize , ( Id , Vec2 ) > ,
12
- }
13
-
14
- impl TooltipState {
15
- pub fn load ( ctx : & Context ) -> Option < Self > {
16
- ctx. data_mut ( |d| d. get_temp ( Id :: NULL ) )
17
- }
18
-
19
- fn store ( self , ctx : & Context ) {
20
- ctx. data_mut ( |d| d. insert_temp ( Id :: NULL , self ) ) ;
21
- }
3
+ use frame_state:: PerWidgetTooltipState ;
22
4
23
- fn individual_tooltip_size ( & self , common_id : Id , index : usize ) -> Option < Vec2 > {
24
- if self . last_common_id == Some ( common_id) {
25
- Some ( self . individual_ids_and_sizes . get ( & index) ?. 1 )
26
- } else {
27
- None
28
- }
29
- }
30
-
31
- fn set_individual_tooltip (
32
- & mut self ,
33
- common_id : Id ,
34
- index : usize ,
35
- individual_id : Id ,
36
- size : Vec2 ,
37
- ) {
38
- if self . last_common_id != Some ( common_id) {
39
- self . last_common_id = Some ( common_id) ;
40
- self . individual_ids_and_sizes . clear ( ) ;
41
- }
42
-
43
- self . individual_ids_and_sizes
44
- . insert ( index, ( individual_id, size) ) ;
45
- }
46
- }
5
+ use crate :: * ;
47
6
48
7
// ----------------------------------------------------------------------------
49
8
@@ -94,10 +53,8 @@ pub fn show_tooltip_at_pointer<R>(
94
53
id : Id ,
95
54
add_contents : impl FnOnce ( & mut Ui ) -> R ,
96
55
) -> Option < R > {
97
- let suggested_pos = ctx
98
- . input ( |i| i. pointer . hover_pos ( ) )
99
- . map ( |pointer_pos| pointer_pos + vec2 ( 16.0 , 16.0 ) ) ;
100
- show_tooltip_at ( ctx, id, suggested_pos, add_contents)
56
+ ctx. input ( |i| i. pointer . hover_pos ( ) )
57
+ . map ( |pointer_pos| show_tooltip_at ( ctx, id, pointer_pos + vec2 ( 16.0 , 16.0 ) , add_contents) )
101
58
}
102
59
103
60
/// Show a tooltip under the given area.
@@ -106,21 +63,16 @@ pub fn show_tooltip_at_pointer<R>(
106
63
pub fn show_tooltip_for < R > (
107
64
ctx : & Context ,
108
65
id : Id ,
109
- rect : & Rect ,
66
+ widget_rect : & Rect ,
110
67
add_contents : impl FnOnce ( & mut Ui ) -> R ,
111
- ) -> Option < R > {
112
- let expanded_rect = rect. expand2 ( vec2 ( 2.0 , 4.0 ) ) ;
113
- let ( above, position) = if ctx. input ( |i| i. any_touches ( ) ) {
114
- ( true , expanded_rect. left_top ( ) )
115
- } else {
116
- ( false , expanded_rect. left_bottom ( ) )
117
- } ;
68
+ ) -> R {
69
+ let is_touch_screen = ctx. input ( |i| i. any_touches ( ) ) ;
70
+ let allow_placing_below = !is_touch_screen; // There is a finger below.
118
71
show_tooltip_at_avoid_dyn (
119
72
ctx,
120
73
id,
121
- Some ( position) ,
122
- above,
123
- expanded_rect,
74
+ allow_placing_below,
75
+ widget_rect,
124
76
Box :: new ( add_contents) ,
125
77
)
126
78
}
@@ -131,101 +83,119 @@ pub fn show_tooltip_for<R>(
131
83
pub fn show_tooltip_at < R > (
132
84
ctx : & Context ,
133
85
id : Id ,
134
- suggested_position : Option < Pos2 > ,
86
+ suggested_position : Pos2 ,
135
87
add_contents : impl FnOnce ( & mut Ui ) -> R ,
136
- ) -> Option < R > {
137
- let above = false ;
138
- show_tooltip_at_avoid_dyn (
139
- ctx,
140
- id,
141
- suggested_position,
142
- above,
143
- Rect :: NOTHING ,
144
- Box :: new ( add_contents) ,
145
- )
88
+ ) -> R {
89
+ let allow_placing_below = true ;
90
+ let rect = Rect :: from_center_size ( suggested_position, Vec2 :: ZERO ) ;
91
+ show_tooltip_at_avoid_dyn ( ctx, id, allow_placing_below, & rect, Box :: new ( add_contents) )
146
92
}
147
93
148
94
fn show_tooltip_at_avoid_dyn < ' c , R > (
149
95
ctx : & Context ,
150
- individual_id : Id ,
151
- suggested_position : Option < Pos2 > ,
152
- above : bool ,
153
- mut avoid_rect : Rect ,
96
+ widget_id : Id ,
97
+ allow_placing_below : bool ,
98
+ widget_rect : & Rect ,
154
99
add_contents : Box < dyn FnOnce ( & mut Ui ) -> R + ' c > ,
155
- ) -> Option < R > {
156
- let spacing = 4.0 ;
157
-
100
+ ) -> R {
158
101
// if there are multiple tooltips open they should use the same common_id for the `tooltip_size` caching to work.
159
- let mut frame_state =
160
- ctx. frame_state ( |fs| fs. tooltip_state )
161
- . unwrap_or ( crate :: frame_state:: TooltipFrameState {
162
- common_id : individual_id,
163
- rect : Rect :: NOTHING ,
164
- count : 0 ,
165
- } ) ;
102
+ let mut state = ctx. frame_state ( |fs| {
103
+ fs. tooltip_state
104
+ . widget_tooltips
105
+ . get ( & widget_id)
106
+ . copied ( )
107
+ . unwrap_or ( PerWidgetTooltipState {
108
+ bounding_rect : * widget_rect,
109
+ tooltip_count : 0 ,
110
+ } )
111
+ } ) ;
166
112
167
- let mut position = if frame_state. rect . is_positive ( ) {
168
- avoid_rect = avoid_rect. union ( frame_state. rect ) ;
169
- if above {
170
- frame_state. rect . left_top ( ) - spacing * Vec2 :: Y
171
- } else {
172
- frame_state. rect . left_bottom ( ) + spacing * Vec2 :: Y
173
- }
174
- } else if let Some ( position) = suggested_position {
175
- position
176
- } else if ctx. memory ( |mem| mem. everything_is_visible ( ) ) {
177
- Pos2 :: ZERO
178
- } else {
179
- return None ; // No good place for a tooltip :(
180
- } ;
113
+ let tooltip_area_id = tooltip_id ( widget_id, state. tooltip_count ) ;
114
+ let expected_tooltip_size =
115
+ AreaState :: load ( ctx, tooltip_area_id) . map_or ( vec2 ( 64.0 , 32.0 ) , |area| area. size ) ;
181
116
182
- let mut long_state = TooltipState :: load ( ctx) . unwrap_or_default ( ) ;
183
- let expected_size =
184
- long_state. individual_tooltip_size ( frame_state. common_id , frame_state. count ) ;
185
- let expected_size = expected_size. unwrap_or_else ( || vec2 ( 64.0 , 32.0 ) ) ;
117
+ let screen_rect = ctx. screen_rect ( ) ;
186
118
187
- if above {
188
- position. y -= expected_size. y ;
189
- }
119
+ let ( pivot, anchor) = find_tooltip_position (
120
+ screen_rect,
121
+ state. bounding_rect ,
122
+ allow_placing_below,
123
+ expected_tooltip_size,
124
+ ) ;
190
125
191
- position = position. at_most ( ctx. screen_rect ( ) . max - expected_size) ;
126
+ let InnerResponse { inner, response } = Area :: new ( tooltip_area_id)
127
+ . order ( Order :: Tooltip )
128
+ . pivot ( pivot)
129
+ . fixed_pos ( anchor)
130
+ . default_width ( ctx. style ( ) . spacing . tooltip_width )
131
+ . constrain_to ( screen_rect)
132
+ . interactable ( false )
133
+ . show ( ctx, |ui| {
134
+ Frame :: popup ( & ctx. style ( ) ) . show_dyn ( ui, add_contents) . inner
135
+ } ) ;
192
136
193
- // check if we intersect the avoid_rect
194
- {
195
- let new_rect = Rect :: from_min_size ( position , expected_size ) ;
137
+ state . tooltip_count += 1 ;
138
+ state . bounding_rect = state . bounding_rect . union ( response . rect ) ;
139
+ ctx . frame_state_mut ( |fs| fs . tooltip_state . widget_tooltips . insert ( widget_id , state ) ) ;
196
140
197
- // Note: We use shrink so that we don't get false positives when the rects just touch
198
- if new_rect. shrink ( 1.0 ) . intersects ( avoid_rect) {
199
- if above {
200
- // place below instead:
201
- position = avoid_rect. left_bottom ( ) + spacing * Vec2 :: Y ;
202
- } else {
203
- // place above instead:
204
- position = Pos2 :: new ( position. x , avoid_rect. min . y - expected_size. y - spacing) ;
205
- }
206
- }
207
- }
141
+ inner
142
+ }
208
143
209
- let position = position. at_least ( ctx. screen_rect ( ) . min ) ;
144
+ fn tooltip_id ( widget_id : Id , tooltip_count : usize ) -> Id {
145
+ widget_id. with ( tooltip_count)
146
+ }
210
147
211
- let area_id = frame_state. common_id . with ( frame_state. count ) ;
148
+ /// Returns `(PIVOT, POS)` to mean: put the `PIVOT` corner of the tooltip at `POS`.
149
+ ///
150
+ /// Note: the position might need to be constrained to the screen,
151
+ /// (e.g. moved sideways if shown under the widget)
152
+ /// but the `Area` will take care of that.
153
+ fn find_tooltip_position (
154
+ screen_rect : Rect ,
155
+ widget_rect : Rect ,
156
+ allow_placing_below : bool ,
157
+ tooltip_size : Vec2 ,
158
+ ) -> ( Align2 , Pos2 ) {
159
+ let spacing = 4.0 ;
212
160
213
- let InnerResponse { inner, response } =
214
- show_tooltip_area_dyn ( ctx, area_id, position, add_contents) ;
161
+ // Does it fit below?
162
+ if allow_placing_below
163
+ && widget_rect. bottom ( ) + spacing + tooltip_size. y <= screen_rect. bottom ( )
164
+ {
165
+ return (
166
+ Align2 :: LEFT_TOP ,
167
+ widget_rect. left_bottom ( ) + spacing * Vec2 :: DOWN ,
168
+ ) ;
169
+ }
215
170
216
- long_state . set_individual_tooltip (
217
- frame_state . common_id ,
218
- frame_state . count ,
219
- individual_id ,
220
- response . rect . size ( ) ,
221
- ) ;
222
- long_state . store ( ctx ) ;
171
+ // Does it fit above?
172
+ if screen_rect . top ( ) + tooltip_size . y + spacing <= widget_rect . top ( ) {
173
+ return (
174
+ Align2 :: LEFT_BOTTOM ,
175
+ widget_rect . left_top ( ) + spacing * Vec2 :: UP ,
176
+ ) ;
177
+ }
223
178
224
- frame_state. count += 1 ;
225
- frame_state. rect = frame_state. rect . union ( response. rect ) ;
226
- ctx. frame_state_mut ( |fs| fs. tooltip_state = Some ( frame_state) ) ;
179
+ // Does it fit to the right?
180
+ if widget_rect. right ( ) + spacing + tooltip_size. x <= screen_rect. right ( ) {
181
+ return (
182
+ Align2 :: LEFT_TOP ,
183
+ widget_rect. right_top ( ) + spacing * Vec2 :: RIGHT ,
184
+ ) ;
185
+ }
227
186
228
- Some ( inner)
187
+ // Does it fit to the left?
188
+ if screen_rect. left ( ) + tooltip_size. x + spacing <= widget_rect. left ( ) {
189
+ return (
190
+ Align2 :: RIGHT_TOP ,
191
+ widget_rect. left_top ( ) + spacing * Vec2 :: LEFT ,
192
+ ) ;
193
+ }
194
+
195
+ // It doesn't fit anywhere :(
196
+
197
+ // Just show it anyway:
198
+ ( Align2 :: LEFT_TOP , screen_rect. left_top ( ) )
229
199
}
230
200
231
201
/// Show some text at the current pointer position (if any).
@@ -249,42 +219,13 @@ pub fn show_tooltip_text(ctx: &Context, id: Id, text: impl Into<WidgetText>) ->
249
219
} )
250
220
}
251
221
252
- /// Show a pop-over window.
253
- fn show_tooltip_area_dyn < ' c , R > (
254
- ctx : & Context ,
255
- area_id : Id ,
256
- window_pos : Pos2 ,
257
- add_contents : Box < dyn FnOnce ( & mut Ui ) -> R + ' c > ,
258
- ) -> InnerResponse < R > {
259
- use containers:: * ;
260
- Area :: new ( area_id)
261
- . order ( Order :: Tooltip )
262
- . fixed_pos ( window_pos)
263
- . default_width ( ctx. style ( ) . spacing . tooltip_width )
264
- . constrain_to ( ctx. screen_rect ( ) )
265
- . interactable ( false )
266
- . show ( ctx, |ui| {
267
- Frame :: popup ( & ctx. style ( ) ) . show_dyn ( ui, add_contents) . inner
268
- } )
269
- }
270
-
271
222
/// Was this popup visible last frame?
272
- pub fn was_tooltip_open_last_frame ( ctx : & Context , tooltip_id : Id ) -> bool {
273
- if let Some ( state) = TooltipState :: load ( ctx) {
274
- if let Some ( common_id) = state. last_common_id {
275
- for ( count, ( individual_id, _size) ) in & state. individual_ids_and_sizes {
276
- if * individual_id == tooltip_id {
277
- let area_id = common_id. with ( count) ;
278
- let layer_id = LayerId :: new ( Order :: Tooltip , area_id) ;
279
- if ctx. memory ( |mem| mem. areas ( ) . visible_last_frame ( & layer_id) ) {
280
- return true ;
281
- }
282
- }
283
- }
284
- }
285
- }
286
-
287
- false
223
+ pub fn was_tooltip_open_last_frame ( ctx : & Context , widget_id : Id ) -> bool {
224
+ let primary_tooltip_area_id = tooltip_id ( widget_id, 0 ) ;
225
+ ctx. memory ( |mem| {
226
+ mem. areas ( )
227
+ . visible_last_frame ( & LayerId :: new ( Order :: Tooltip , primary_tooltip_area_id) )
228
+ } )
288
229
}
289
230
290
231
/// Helper for [`popup_above_or_below_widget`].
0 commit comments