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