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, ¢er[0], ¢er[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