1 /*
2  * This program is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU General Public License
4  * as published by the Free Software Foundation; either version 2
5  * of the License, or (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software Foundation,
14  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15  *
16  * The Original Code is Copyright (C) 2008 Blender Foundation.
17  * All rights reserved.
18  */
19 
20 /** \file
21  * \ingroup edinterface
22  *
23  * Pop-Over Region
24  *
25  * \note This is very close to 'interface_region_menu_popup.c'
26  *
27  * We could even merge them, however menu logic is already over-loaded.
28  * PopOver's have the following differences.
29  *
30  * - UI is not constrained to a list.
31  * - Pressing a button won't close the pop-over.
32  * - Different draw style (to show this is has different behavior from a menu).
33  * - #PanelType are used instead of #MenuType.
34  * - No menu flipping support.
35  * - No moving the menu to fit the mouse cursor.
36  * - No key accelerators to access menu items
37  *   (if we add support they would work differently).
38  * - No arrow key navigation.
39  * - No menu memory.
40  * - No title.
41  */
42 
43 #include "MEM_guardedalloc.h"
44 
45 #include "DNA_userdef_types.h"
46 
47 #include "BLI_listbase.h"
48 
49 #include "BLI_math_vector.h"
50 #include "BLI_rect.h"
51 #include "BLI_utildefines.h"
52 
53 #include "BKE_context.h"
54 #include "BKE_report.h"
55 #include "BKE_screen.h"
56 
57 #include "ED_screen.h"
58 
59 #include "WM_api.h"
60 #include "WM_types.h"
61 
62 #include "UI_interface.h"
63 
64 #include "interface_intern.h"
65 #include "interface_regions_intern.h"
66 
67 /* -------------------------------------------------------------------- */
68 /** \name Popup Menu with Callback or String
69  * \{ */
70 
71 struct uiPopover {
72   uiBlock *block;
73   uiLayout *layout;
74   uiBut *but;
75   ARegion *butregion;
76 
77   /* Needed for keymap removal. */
78   wmWindow *window;
79   wmKeyMap *keymap;
80   struct wmEventHandler_Keymap *keymap_handler;
81 
82   uiMenuCreateFunc menu_func;
83   void *menu_arg;
84 
85   /* Size in pixels (ui scale applied). */
86   int ui_size_x;
87 
88 #ifdef USE_UI_POPOVER_ONCE
89   bool is_once;
90 #endif
91 };
92 
ui_popover_create_block(bContext * C,uiPopover * pup,int opcontext)93 static void ui_popover_create_block(bContext *C, uiPopover *pup, int opcontext)
94 {
95   BLI_assert(pup->ui_size_x != 0);
96 
97   const uiStyle *style = UI_style_get_dpi();
98 
99   pup->block = UI_block_begin(C, NULL, __func__, UI_EMBOSS);
100   UI_block_flag_enable(pup->block, UI_BLOCK_KEEP_OPEN | UI_BLOCK_POPOVER);
101 #ifdef USE_UI_POPOVER_ONCE
102   if (pup->is_once) {
103     UI_block_flag_enable(pup->block, UI_BLOCK_POPOVER_ONCE);
104   }
105 #endif
106 
107   pup->layout = UI_block_layout(
108       pup->block, UI_LAYOUT_VERTICAL, UI_LAYOUT_PANEL, 0, 0, pup->ui_size_x, 0, 0, style);
109 
110   uiLayoutSetOperatorContext(pup->layout, opcontext);
111 
112   if (pup->but) {
113     if (pup->but->context) {
114       uiLayoutContextCopy(pup->layout, pup->but->context);
115     }
116   }
117 
118   pup->block->flag |= UI_BLOCK_NO_FLIP;
119 }
120 
ui_block_func_POPOVER(bContext * C,uiPopupBlockHandle * handle,void * arg_pup)121 static uiBlock *ui_block_func_POPOVER(bContext *C, uiPopupBlockHandle *handle, void *arg_pup)
122 {
123   uiPopover *pup = arg_pup;
124 
125   /* Create UI block and layout now if it wasn't done between begin/end. */
126   if (!pup->layout) {
127     ui_popover_create_block(C, pup, WM_OP_INVOKE_REGION_WIN);
128 
129     if (pup->menu_func) {
130       pup->block->handle = handle;
131       pup->menu_func(C, pup->layout, pup->menu_arg);
132       pup->block->handle = NULL;
133     }
134 
135     pup->layout = NULL;
136   }
137 
138   /* Setup and resolve UI layout for block. */
139   uiBlock *block = pup->block;
140   int width, height;
141 
142   UI_block_region_set(block, handle->region);
143   UI_block_layout_resolve(block, &width, &height);
144   UI_block_direction_set(block, UI_DIR_DOWN | UI_DIR_CENTER_X);
145 
146   const int block_margin = U.widget_unit / 2;
147 
148   if (pup->but) {
149     /* For a header menu we set the direction automatic. */
150     block->minbounds = BLI_rctf_size_x(&pup->but->rect);
151     UI_block_bounds_set_normal(block, block_margin);
152 
153     /* If menu slides out of other menu, override direction. */
154     const bool slideout = ui_block_is_menu(pup->but->block);
155     if (slideout) {
156       UI_block_direction_set(block, UI_DIR_RIGHT);
157     }
158 
159     /* Store the button location for positioning the popover arrow hint. */
160     if (!handle->refresh) {
161       float center[2] = {BLI_rctf_cent_x(&pup->but->rect), BLI_rctf_cent_y(&pup->but->rect)};
162       ui_block_to_window_fl(handle->ctx_region, pup->but->block, &center[0], &center[1]);
163       /* These variables aren't used for popovers,
164        * we could add new variables if there is a conflict. */
165       block->bounds_offset[0] = (int)center[0];
166       block->bounds_offset[1] = (int)center[1];
167       copy_v2_v2_int(handle->prev_bounds_offset, block->bounds_offset);
168     }
169     else {
170       copy_v2_v2_int(block->bounds_offset, handle->prev_bounds_offset);
171     }
172 
173     if (!slideout) {
174       ARegion *region = CTX_wm_region(C);
175 
176       if (region && region->panels.first) {
177         /* For regions with panels, prefer to open to top so we can
178          * see the values of the buttons below changing. */
179         UI_block_direction_set(block, UI_DIR_UP | UI_DIR_CENTER_X);
180       }
181       /* Prefer popover from header to be positioned into the editor. */
182       else if (region) {
183         if (RGN_TYPE_IS_HEADER_ANY(region->regiontype)) {
184           if (RGN_ALIGN_ENUM_FROM_MASK(region->alignment) == RGN_ALIGN_BOTTOM) {
185             UI_block_direction_set(block, UI_DIR_UP | UI_DIR_CENTER_X);
186           }
187         }
188       }
189     }
190 
191     /* Estimated a maximum size so we don't go offscreen for low height
192      * areas near the bottom of the window on refreshes. */
193     handle->max_size_y = UI_UNIT_Y * 16.0f;
194   }
195   else {
196     /* Not attached to a button. */
197     int bounds_offset[2] = {0, 0};
198     UI_block_flag_enable(block, UI_BLOCK_LOOP);
199     UI_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP);
200     UI_block_direction_set(block, block->direction);
201     block->minbounds = UI_MENU_WIDTH_MIN;
202 
203     if (!handle->refresh) {
204       uiBut *but = NULL;
205       uiBut *but_first = NULL;
206       LISTBASE_FOREACH (uiBut *, but_iter, &block->buttons) {
207         if ((but_first == NULL) && ui_but_is_editable(but_iter)) {
208           but_first = but_iter;
209         }
210         if (but_iter->flag & (UI_SELECT | UI_SELECT_DRAW)) {
211           but = but_iter;
212           break;
213         }
214       }
215 
216       if (but) {
217         bounds_offset[0] = -(but->rect.xmin + 0.8f * BLI_rctf_size_x(&but->rect));
218         bounds_offset[1] = -BLI_rctf_cent_y(&but->rect);
219       }
220       else {
221         bounds_offset[0] = -(pup->ui_size_x / 2);
222         bounds_offset[1] = but_first ? -BLI_rctf_cent_y(&but_first->rect) : (UI_UNIT_Y / 2);
223       }
224       copy_v2_v2_int(handle->prev_bounds_offset, bounds_offset);
225     }
226     else {
227       copy_v2_v2_int(bounds_offset, handle->prev_bounds_offset);
228     }
229 
230     UI_block_bounds_set_popup(block, block_margin, bounds_offset);
231   }
232 
233   return block;
234 }
235 
ui_block_free_func_POPOVER(void * arg_pup)236 static void ui_block_free_func_POPOVER(void *arg_pup)
237 {
238   uiPopover *pup = arg_pup;
239   if (pup->keymap != NULL) {
240     wmWindow *window = pup->window;
241     WM_event_remove_keymap_handler(&window->modalhandlers, pup->keymap);
242   }
243   MEM_freeN(pup);
244 }
245 
ui_popover_panel_create(bContext * C,ARegion * butregion,uiBut * but,uiMenuCreateFunc menu_func,void * arg)246 uiPopupBlockHandle *ui_popover_panel_create(
247     bContext *C, ARegion *butregion, uiBut *but, uiMenuCreateFunc menu_func, void *arg)
248 {
249   wmWindow *window = CTX_wm_window(C);
250   const uiStyle *style = UI_style_get_dpi();
251   const PanelType *panel_type = (PanelType *)arg;
252 
253   /* Create popover, buttons are created from callback. */
254   uiPopover *pup = MEM_callocN(sizeof(uiPopover), __func__);
255   pup->but = but;
256 
257   /* FIXME: maybe one day we want non panel popovers? */
258   {
259     const int ui_units_x = (panel_type->ui_units_x == 0) ? UI_POPOVER_WIDTH_UNITS :
260                                                            panel_type->ui_units_x;
261     /* Scale width by changes to Text Style point size. */
262     const int text_points_max = MAX2(style->widget.points, style->widgetlabel.points);
263     pup->ui_size_x = ui_units_x * U.widget_unit *
264                      (text_points_max / (float)UI_DEFAULT_TEXT_POINTS);
265   }
266 
267   pup->menu_func = menu_func;
268   pup->menu_arg = arg;
269 
270 #ifdef USE_UI_POPOVER_ONCE
271   {
272     /* Ideally this would be passed in. */
273     const wmEvent *event = window->eventstate;
274     pup->is_once = (event->type == LEFTMOUSE) && (event->val == KM_PRESS);
275   }
276 #endif
277 
278   /* Create popup block. */
279   uiPopupBlockHandle *handle;
280   handle = ui_popup_block_create(
281       C, butregion, but, NULL, ui_block_func_POPOVER, pup, ui_block_free_func_POPOVER);
282   handle->can_refresh = true;
283 
284   /* Add handlers. If attached to a button, the button will already
285    * add a modal handler and pass on events. */
286   if (!but) {
287     UI_popup_handlers_add(C, &window->modalhandlers, handle, 0);
288     WM_event_add_mousemove(window);
289     handle->popup = true;
290   }
291 
292   return handle;
293 }
294 
295 /** \} */
296 
297 /* -------------------------------------------------------------------- */
298 /** \name Standard Popover Panels
299  * \{ */
300 
UI_popover_panel_invoke(bContext * C,const char * idname,bool keep_open,ReportList * reports)301 int UI_popover_panel_invoke(bContext *C, const char *idname, bool keep_open, ReportList *reports)
302 {
303   uiLayout *layout;
304   PanelType *pt = WM_paneltype_find(idname, true);
305   if (pt == NULL) {
306     BKE_reportf(reports, RPT_ERROR, "Panel \"%s\" not found", idname);
307     return OPERATOR_CANCELLED;
308   }
309 
310   if (pt->poll && (pt->poll(C, pt) == false)) {
311     /* cancel but allow event to pass through, just like operators do */
312     return (OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH);
313   }
314 
315   uiBlock *block = NULL;
316   if (keep_open) {
317     uiPopupBlockHandle *handle = ui_popover_panel_create(
318         C, NULL, NULL, ui_item_paneltype_func, pt);
319     uiPopover *pup = handle->popup_create_vars.arg;
320     block = pup->block;
321   }
322   else {
323     uiPopover *pup = UI_popover_begin(C, U.widget_unit * pt->ui_units_x, false);
324     layout = UI_popover_layout(pup);
325     UI_paneltype_draw(C, pt, layout);
326     UI_popover_end(C, pup, NULL);
327     block = pup->block;
328   }
329 
330   if (block) {
331     uiPopupBlockHandle *handle = block->handle;
332     UI_block_active_only_flagged_buttons(C, handle->region, block);
333   }
334   return OPERATOR_INTERFACE;
335 }
336 
337 /** \} */
338 
339 /* -------------------------------------------------------------------- */
340 /** \name Popup Menu API with begin & end
341  * \{ */
342 
343 /**
344  * Only return handler, and set optional title.
345  *
346  * \param from_active_button: Use the active button for positioning,
347  * use when the popover is activated from an operator instead of directly from the button.
348  */
UI_popover_begin(bContext * C,int ui_menu_width,bool from_active_button)349 uiPopover *UI_popover_begin(bContext *C, int ui_menu_width, bool from_active_button)
350 {
351   uiPopover *pup = MEM_callocN(sizeof(uiPopover), "popover menu");
352   if (ui_menu_width == 0) {
353     ui_menu_width = U.widget_unit * UI_POPOVER_WIDTH_UNITS;
354   }
355   pup->ui_size_x = ui_menu_width;
356 
357   ARegion *butregion = NULL;
358   uiBut *but = NULL;
359 
360   if (from_active_button) {
361     butregion = CTX_wm_region(C);
362     but = UI_region_active_but_get(butregion);
363     if (but == NULL) {
364       butregion = NULL;
365     }
366   }
367 
368   pup->but = but;
369   pup->butregion = butregion;
370 
371   /* Operator context default same as menus, change if needed. */
372   ui_popover_create_block(C, pup, WM_OP_EXEC_REGION_WIN);
373 
374   /* create in advance so we can let buttons point to retval already */
375   pup->block->handle = MEM_callocN(sizeof(uiPopupBlockHandle), "uiPopupBlockHandle");
376 
377   return pup;
378 }
379 
popover_keymap_fn(wmKeyMap * UNUSED (keymap),wmKeyMapItem * UNUSED (kmi),void * user_data)380 static void popover_keymap_fn(wmKeyMap *UNUSED(keymap), wmKeyMapItem *UNUSED(kmi), void *user_data)
381 {
382   uiPopover *pup = user_data;
383   pup->block->handle->menuretval = UI_RETURN_OK;
384 }
385 
386 /* set the whole structure to work */
UI_popover_end(bContext * C,uiPopover * pup,wmKeyMap * keymap)387 void UI_popover_end(bContext *C, uiPopover *pup, wmKeyMap *keymap)
388 {
389   wmWindow *window = CTX_wm_window(C);
390   /* Create popup block. No refresh support since the buttons were created
391    * between begin/end and we have no callback to recreate them. */
392   uiPopupBlockHandle *handle;
393 
394   if (keymap) {
395     /* Add so we get keymaps shown in the buttons. */
396     UI_block_flag_enable(pup->block, UI_BLOCK_SHOW_SHORTCUT_ALWAYS);
397     pup->keymap = keymap;
398     pup->keymap_handler = WM_event_add_keymap_handler_priority(&window->modalhandlers, keymap, 0);
399     WM_event_set_keymap_handler_post_callback(pup->keymap_handler, popover_keymap_fn, pup);
400   }
401 
402   handle = ui_popup_block_create(
403       C, pup->butregion, pup->but, NULL, ui_block_func_POPOVER, pup, ui_block_free_func_POPOVER);
404 
405   /* Add handlers. */
406   UI_popup_handlers_add(C, &window->modalhandlers, handle, 0);
407   WM_event_add_mousemove(window);
408   handle->popup = true;
409 
410   /* Re-add so it gets priority. */
411   if (keymap) {
412     BLI_remlink(&window->modalhandlers, pup->keymap_handler);
413     BLI_addhead(&window->modalhandlers, pup->keymap_handler);
414   }
415 
416   pup->window = window;
417 
418   /* TODO(campbell): we may want to make this configurable.
419    * The begin/end stype of calling popups doesn't allow to 'can_refresh' to be set.
420    * For now close this style of popovers when accessed. */
421   UI_block_flag_disable(pup->block, UI_BLOCK_KEEP_OPEN);
422 
423   /* panels are created flipped (from event handling pov) */
424   pup->block->flag ^= UI_BLOCK_IS_FLIP;
425 }
426 
UI_popover_layout(uiPopover * pup)427 uiLayout *UI_popover_layout(uiPopover *pup)
428 {
429   return pup->layout;
430 }
431 
432 #ifdef USE_UI_POPOVER_ONCE
UI_popover_once_clear(uiPopover * pup)433 void UI_popover_once_clear(uiPopover *pup)
434 {
435   pup->is_once = false;
436 }
437 #endif
438 
439 /** \} */
440