doxygen: add newline after \file
[blender.git] / source / blender / editors / interface / interface_region_popover.c
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_rect.h"
50 #include "BLI_utildefines.h"
51
52 #include "BKE_context.h"
53 #include "BKE_screen.h"
54 #include "BKE_report.h"
55
56 #include "ED_screen.h"
57
58 #include "WM_api.h"
59 #include "WM_types.h"
60
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
76         /* Needed for keymap removal. */
77         wmWindow *window;
78         wmKeyMap *keymap;
79         struct wmEventHandler *keymap_handler;
80
81         uiMenuCreateFunc menu_func;
82         void *menu_arg;
83
84         /* Size in pixels (ui scale applied). */
85         int ui_size_x;
86
87 #ifdef USE_UI_POPOVER_ONCE
88         bool is_once;
89 #endif
90 };
91
92 static void ui_popover_create_block(bContext *C, uiPopover *pup, int opcontext)
93 {
94         BLI_assert(pup->ui_size_x != 0);
95
96         uiStyle *style = UI_style_get_dpi();
97
98         pup->block = UI_block_begin(C, NULL, __func__, UI_EMBOSS);
99         UI_block_flag_enable(pup->block, UI_BLOCK_KEEP_OPEN | UI_BLOCK_POPOVER);
100 #ifdef USE_UI_POPOVER_ONCE
101         if (pup->is_once) {
102                 UI_block_flag_enable(pup->block, UI_BLOCK_POPOVER_ONCE);
103         }
104 #endif
105
106         pup->layout = UI_block_layout(
107                 pup->block, UI_LAYOUT_VERTICAL, UI_LAYOUT_PANEL, 0, 0,
108                 pup->ui_size_x, 0, MENU_PADDING, 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
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                 bool slideout = ui_block_is_menu(pup->but->block);
155                 if (slideout)
156                         UI_block_direction_set(block, UI_DIR_RIGHT);
157
158                 /* Store the button location for positioning the popover arrow hint. */
159                 if (!handle->refresh) {
160                         float center[2] = {BLI_rctf_cent_x(&pup->but->rect), BLI_rctf_cent_y(&pup->but->rect)};
161                         ui_block_to_window_fl(handle->ctx_region, pup->but->block, &center[0], &center[1]);
162                         /* These variables aren't used for popovers,
163                          * we could add new variables if there is a conflict. */
164                         handle->prev_mx = block->mx = (int)center[0];
165                         handle->prev_my = block->my = (int)center[1];
166                 }
167                 else {
168                         block->mx = handle->prev_mx;
169                         block->my = handle->prev_my;
170                 }
171
172                 if (!slideout) {
173                         ScrArea *sa = CTX_wm_area(C);
174                         ARegion *ar = CTX_wm_region(C);
175
176                         if (ar && ar->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                         else if (sa && ED_area_header_alignment(sa) == RGN_ALIGN_BOTTOM) {
182                                 /* Prefer popover from header to be positioned into the editor. */
183                                 if (ar && ar->regiontype == RGN_TYPE_HEADER) {
184                                         UI_block_direction_set(block, UI_DIR_UP | UI_DIR_CENTER_X);
185                                 }
186                         }
187                 }
188
189                 /* Estimated a maximum size so we don't go offscreen for low height
190                  * areas near the bottom of the window on refreshes. */
191                 handle->max_size_y = UI_UNIT_Y * 16.0f;
192         }
193         else {
194                 /* Not attached to a button. */
195                 int offset[2] = {0, 0};
196                 UI_block_flag_enable(block, UI_BLOCK_LOOP);
197                 UI_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP);
198                 UI_block_direction_set(block, block->direction);
199                 block->minbounds = UI_MENU_WIDTH_MIN;
200                 bool use_place_under_active = !handle->refresh;
201
202                 if (use_place_under_active) {
203                         uiBut *but = NULL;
204                         for (but = block->buttons.first; but; but = but->next) {
205                                 if (but->flag & (UI_SELECT | UI_SELECT_DRAW)) {
206                                         break;
207                                 }
208                         }
209
210                         if (but) {
211                                 offset[0] = -(but->rect.xmin + 0.8f * BLI_rctf_size_x(&but->rect));
212                                 offset[1] = -(but->rect.ymin + 0.5f * BLI_rctf_size_y(&but->rect));
213                         }
214                 }
215
216                 UI_block_bounds_set_popup(block, block_margin, offset[0], offset[1]);
217         }
218
219         return block;
220 }
221
222 static void ui_block_free_func_POPOVER(uiPopupBlockHandle *UNUSED(handle), void *arg_pup)
223 {
224         uiPopover *pup = arg_pup;
225         if (pup->keymap != NULL) {
226                 wmWindow *window = pup->window;
227                 WM_event_remove_keymap_handler(&window->modalhandlers, pup->keymap);
228         }
229         MEM_freeN(pup);
230 }
231
232 uiPopupBlockHandle *ui_popover_panel_create(
233         bContext *C, ARegion *butregion, uiBut *but,
234         uiMenuCreateFunc menu_func, void *arg)
235 {
236         /* Create popover, buttons are created from callback. */
237         uiPopover *pup = MEM_callocN(sizeof(uiPopover), __func__);
238         pup->but = but;
239
240         /* FIXME: maybe one day we want non panel popovers? */
241         {
242                 int ui_units_x = ((PanelType *)arg)->ui_units_x;
243                 pup->ui_size_x = U.widget_unit * (ui_units_x ? ui_units_x : UI_POPOVER_WIDTH_UNITS);
244         }
245
246         pup->menu_func = menu_func;
247         pup->menu_arg = arg;
248
249 #ifdef USE_UI_POPOVER_ONCE
250         pup->is_once = true;
251 #endif
252
253         /* Create popup block. */
254         uiPopupBlockHandle *handle;
255         handle = ui_popup_block_create(C, butregion, but, NULL, ui_block_func_POPOVER, pup);
256         handle->popup_create_vars.free_func = ui_block_free_func_POPOVER;
257         handle->can_refresh = true;
258
259         /* Add handlers. If attached to a button, the button will already
260          * add a modal handler and pass on events. */
261         if (!but) {
262                 wmWindow *window = CTX_wm_window(C);
263                 UI_popup_handlers_add(C, &window->modalhandlers, handle, 0);
264                 WM_event_add_mousemove(C);
265                 handle->popup = true;
266         }
267
268         return handle;
269 }
270
271 /** \} */
272
273 /* -------------------------------------------------------------------- */
274 /** \name Standard Popover Panels
275  * \{ */
276
277 int UI_popover_panel_invoke(
278         bContext *C, const char *idname,
279         bool keep_open, ReportList *reports)
280 {
281         uiLayout *layout;
282         PanelType *pt = WM_paneltype_find(idname, true);
283         if (pt == NULL) {
284                 BKE_reportf(reports, RPT_ERROR, "Panel \"%s\" not found", idname);
285                 return OPERATOR_CANCELLED;
286         }
287
288         if (pt->poll && (pt->poll(C, pt) == false)) {
289                 /* cancel but allow event to pass through, just like operators do */
290                 return (OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH);
291         }
292
293         if (keep_open) {
294                 ui_popover_panel_create(C, NULL, NULL, ui_item_paneltype_func, pt);
295         }
296         else {
297                 uiPopover *pup = UI_popover_begin(C, U.widget_unit * pt->ui_units_x);
298                 layout = UI_popover_layout(pup);
299                 UI_paneltype_draw(C, pt, layout);
300                 UI_popover_end(C, pup, NULL);
301         }
302
303         return OPERATOR_INTERFACE;
304 }
305
306 /** \} */
307
308 /* -------------------------------------------------------------------- */
309 /** \name Popup Menu API with begin & end
310  * \{ */
311
312 /**
313  * Only return handler, and set optional title.
314  */
315 uiPopover *UI_popover_begin(bContext *C, int ui_size_x)
316 {
317         uiPopover *pup = MEM_callocN(sizeof(uiPopover), "popover menu");
318         if (ui_size_x == 0) {
319                 ui_size_x = U.widget_unit * UI_POPOVER_WIDTH_UNITS;
320         }
321         pup->ui_size_x = ui_size_x;
322
323         /* Opertor context default same as menus, change if needed. */
324         ui_popover_create_block(C, pup, WM_OP_EXEC_REGION_WIN);
325
326         /* create in advance so we can let buttons point to retval already */
327         pup->block->handle = MEM_callocN(sizeof(uiPopupBlockHandle), "uiPopupBlockHandle");
328
329         return pup;
330 }
331
332 static void popover_keymap_fn(wmKeyMap *UNUSED(keymap), wmKeyMapItem *UNUSED(kmi), void *user_data)
333 {
334         uiPopover *pup = user_data;
335         pup->block->handle->menuretval = UI_RETURN_OK;
336 }
337
338 /* set the whole structure to work */
339 void UI_popover_end(bContext *C, uiPopover *pup, wmKeyMap *keymap)
340 {
341         wmWindow *window = CTX_wm_window(C);
342         /* Create popup block. No refresh support since the buttons were created
343          * between begin/end and we have no callback to recreate them. */
344         uiPopupBlockHandle *handle;
345
346         if (keymap) {
347                 /* Add so we get keymaps shown in the buttons. */
348                 UI_block_flag_enable(pup->block, UI_BLOCK_SHOW_SHORTCUT_ALWAYS);
349                 pup->keymap = keymap;
350                 pup->keymap_handler = WM_event_add_keymap_handler_priority(&window->modalhandlers, keymap, 0);
351                 WM_event_set_keymap_handler_callback(pup->keymap_handler, popover_keymap_fn, pup);
352         }
353
354         handle = ui_popup_block_create(C, NULL, NULL, NULL, ui_block_func_POPOVER, pup);
355         handle->popup_create_vars.free_func = ui_block_free_func_POPOVER;
356
357         /* Add handlers. */
358         UI_popup_handlers_add(C, &window->modalhandlers, handle, 0);
359         WM_event_add_mousemove(C);
360         handle->popup = true;
361
362         /* Re-add so it gets priority. */
363         if (keymap) {
364                 BLI_remlink(&window->modalhandlers, pup->keymap_handler);
365                 BLI_addhead(&window->modalhandlers, pup->keymap_handler);
366         }
367
368         pup->window = window;
369
370         /* TODO(campbell): we may want to make this configurable.
371          * The begin/end stype of calling popups doesn't allow to 'can_refresh' to be set.
372          * For now close this style of popvers when accessed. */
373         UI_block_flag_disable(pup->block, UI_BLOCK_KEEP_OPEN);
374
375         /* panels are created flipped (from event handling pov) */
376         pup->block->flag ^= UI_BLOCK_IS_FLIP;
377 }
378
379 uiLayout *UI_popover_layout(uiPopover *pup)
380 {
381         return pup->layout;
382 }
383
384 #ifdef USE_UI_POPOVER_ONCE
385 void UI_popover_once_clear(uiPopover *pup)
386 {
387         pup->is_once = false;
388 }
389 #endif
390
391 /** \} */