From 3ec4d0b51bf3a76725e39a57b0f30bec3edc6882 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 2 Nov 2017 04:30:07 +1100 Subject: UI: Add UILayout.operator_menu_hold This is an operator button that opens a menu when the button is held. --- source/blender/editors/include/UI_interface.h | 10 ++++ source/blender/editors/interface/interface.c | 10 ++++ .../blender/editors/interface/interface_handlers.c | 67 +++++++++++++++++----- .../blender/editors/interface/interface_intern.h | 4 ++ .../blender/editors/interface/interface_layout.c | 46 ++++++++++++++- .../blender/editors/interface/interface_regions.c | 21 ++++++- .../blender/editors/interface/interface_widgets.c | 54 ++++++++++++++++- source/blender/makesrna/intern/rna_ui_api.c | 50 +++++++++++++--- 8 files changed, 234 insertions(+), 28 deletions(-) diff --git a/source/blender/editors/include/UI_interface.h b/source/blender/editors/include/UI_interface.h index 8b4f094cb8d..a609a3b51fd 100644 --- a/source/blender/editors/include/UI_interface.h +++ b/source/blender/editors/include/UI_interface.h @@ -136,6 +136,7 @@ enum { /* block->flag bits 14-17 are identical to but->drawflag bits */ +#define UI_BLOCK_POPUP_HOLD (1 << 18) #define UI_BLOCK_LIST_ITEM (1 << 19) #define UI_BLOCK_RADIAL (1 << 20) @@ -354,6 +355,7 @@ typedef struct uiSearchItems uiSearchItems; typedef void (*uiButHandleFunc)(struct bContext *C, void *arg1, void *arg2); typedef void (*uiButHandleRenameFunc)(struct bContext *C, void *arg, char *origstr); typedef void (*uiButHandleNFunc)(struct bContext *C, void *argN, void *arg2); +typedef void (*uiButHandleHoldFunc)(struct bContext *C, struct ARegion *butregion, uiBut *but); typedef int (*uiButCompleteFunc)(struct bContext *C, char *str, void *arg); typedef struct ARegion *(*uiButSearchCreateFunc)(struct bContext *C, struct ARegion *butregion, uiBut *but); typedef void (*uiButSearchFunc)(const struct bContext *C, void *arg, const char *str, uiSearchItems *items); @@ -395,6 +397,7 @@ void UI_popup_menu_reports(struct bContext *C, struct ReportList *reports) ATTR_ int UI_popup_menu_invoke(struct bContext *C, const char *idname, struct ReportList *reports) ATTR_NONNULL(1, 2); void UI_popup_menu_retval_set(const uiBlock *block, const int retval, const bool enable); +void UI_popup_menu_but_set(uiPopupMenu *pup, struct ARegion *butregion, uiBut *but); /* Pie menus */ typedef struct uiPieMenu uiPieMenu; @@ -726,6 +729,8 @@ bool UI_textbutton_activate_but(const struct bContext *C, uiBut *but); void UI_but_focus_on_enter_event(struct wmWindow *win, uiBut *but); +void UI_but_func_hold_set(uiBut *but, uiButHandleHoldFunc func, void *argN); + /* Autocomplete * * Tab complete helper functions, for use in uiButCompleteFunc callbacks. @@ -988,6 +993,11 @@ void uiItemFullO( uiLayout *layout, const char *idname, const char *name, int icon, struct IDProperty *properties, int context, int flag, PointerRNA *r_opptr); +void uiItemFullOMenuHold_ptr( + uiLayout *layout, struct wmOperatorType *ot, const char *name, int icon, + struct IDProperty *properties, int context, int flag, + const char *menu_id, /* extra menu arg. */ + PointerRNA *r_opptr); void uiItemR(uiLayout *layout, struct PointerRNA *ptr, const char *propname, int flag, const char *name, int icon); void uiItemFullR(uiLayout *layout, struct PointerRNA *ptr, struct PropertyRNA *prop, int index, int value, int flag, const char *name, int icon); diff --git a/source/blender/editors/interface/interface.c b/source/blender/editors/interface/interface.c index dc4c1b71066..bc2397d8e47 100644 --- a/source/blender/editors/interface/interface.c +++ b/source/blender/editors/interface/interface.c @@ -2672,6 +2672,10 @@ static void ui_but_free(const bContext *C, uiBut *but) MEM_freeN(but->tip_argN); } + if (but->hold_argN) { + MEM_freeN(but->hold_argN); + } + if (but->active) { /* XXX solve later, buttons should be free-able without context ideally, * however they may have open tooltips or popup windows, which need to @@ -4520,6 +4524,12 @@ void UI_but_focus_on_enter_event(wmWindow *win, uiBut *but) wm_event_add(win, &event); } +void UI_but_func_hold_set(uiBut *but, uiButHandleHoldFunc func, void *argN) +{ + but->hold_func = func; + but->hold_argN = argN; +} + void UI_but_string_info_get(bContext *C, uiBut *but, ...) { va_list args; diff --git a/source/blender/editors/interface/interface_handlers.c b/source/blender/editors/interface/interface_handlers.c index 0dec751526a..57f9b2ae356 100644 --- a/source/blender/editors/interface/interface_handlers.c +++ b/source/blender/editors/interface/interface_handlers.c @@ -304,6 +304,9 @@ typedef struct uiHandleButtonData { bool used_mouse; wmTimer *autoopentimer; + /* auto open (hold) */ + wmTimer *hold_action_timer; + /* text selection/editing */ /* size of 'str' (including terminator) */ int maxlen; @@ -7808,6 +7811,15 @@ static void button_activate_state(bContext *C, uiBut *but, uiHandleButtonState s data->flashtimer = NULL; } + /* add hold timer if it's used */ + if (state == BUTTON_STATE_WAIT_RELEASE && (but->hold_func != NULL)) { + data->hold_action_timer = WM_event_add_timer(data->wm, data->window, TIMER, BUTTON_AUTO_OPEN_THRESH); + } + else if (data->hold_action_timer) { + WM_event_remove_timer(data->wm, data->window, data->hold_action_timer); + data->hold_action_timer = NULL; + } + /* add a blocking ui handler at the window handler for blocking, modal states * but not for popups, because we already have a window level handler*/ if (!(but->block->handle && but->block->handle->popup)) { @@ -8422,6 +8434,25 @@ static int ui_handle_button_event(bContext *C, const wmEvent *event, uiBut *but) button_activate_state(C, but, BUTTON_STATE_EXIT); break; + case TIMER: + { + if (event->customdata == data->hold_action_timer) { + if (true) { + data->cancel = true; + button_activate_state(C, but, BUTTON_STATE_EXIT); + } + else { + /* Do this so we can still mouse-up, closing the menu and running the button. + * This is nice to support but there are times when the button gets left pressed. + * Keep disavled for now. */ + WM_event_remove_timer(data->wm, data->window, data->hold_action_timer); + data->hold_action_timer = NULL; + } + retval = WM_UI_HANDLER_CONTINUE; + but->hold_func(C, data->region, but); + } + break; + } case MOUSEMOVE: if (ELEM(but->type, UI_BTYPE_LINK, UI_BTYPE_INLINK)) { but->flag |= UI_SELECT; @@ -9348,21 +9379,29 @@ static int ui_handle_menu_event( if (inside == 0) { uiSafetyRct *saferct = block->saferct.first; - if (ELEM(event->type, LEFTMOUSE, MIDDLEMOUSE, RIGHTMOUSE) && - ELEM(event->val, KM_PRESS, KM_DBL_CLICK)) - { - if ((is_parent_menu == false) && (U.uiflag & USER_MENUOPENAUTO) == 0) { - /* for root menus, allow clicking to close */ - if (block->flag & (UI_BLOCK_OUT_1)) - menu->menuretval = UI_RETURN_OK; - else - menu->menuretval = UI_RETURN_OUT; + if (ELEM(event->type, LEFTMOUSE, MIDDLEMOUSE, RIGHTMOUSE)) { + if (ELEM(event->val, KM_PRESS, KM_DBL_CLICK)) { + if ((is_parent_menu == false) && (U.uiflag & USER_MENUOPENAUTO) == 0) { + /* for root menus, allow clicking to close */ + if (block->flag & (UI_BLOCK_OUT_1)) + menu->menuretval = UI_RETURN_OK; + else + menu->menuretval = UI_RETURN_OUT; + } + else if (saferct && !BLI_rctf_isect_pt(&saferct->parent, event->x, event->y)) { + if (block->flag & (UI_BLOCK_OUT_1)) + menu->menuretval = UI_RETURN_OK; + else + menu->menuretval = UI_RETURN_OUT; + } } - else if (saferct && !BLI_rctf_isect_pt(&saferct->parent, event->x, event->y)) { - if (block->flag & (UI_BLOCK_OUT_1)) - menu->menuretval = UI_RETURN_OK; - else - menu->menuretval = UI_RETURN_OUT; + else if (ELEM(event->val, KM_RELEASE, KM_CLICK)) { + /* For buttons that use a hold function, exit when mouse-up outside the menu. */ + if (block->flag & UI_BLOCK_POPUP_HOLD) { + /* Note, we could check the cursor is over the parent button. */ + menu->menuretval = UI_RETURN_CANCEL; + retval = WM_UI_HANDLER_CONTINUE; + } } } } diff --git a/source/blender/editors/interface/interface_intern.h b/source/blender/editors/interface/interface_intern.h index ab760c40451..da11c2abab2 100644 --- a/source/blender/editors/interface/interface_intern.h +++ b/source/blender/editors/interface/interface_intern.h @@ -264,6 +264,10 @@ struct uiBut { void *rename_arg1; void *rename_orig; + /* Run an action when holding the button down. */ + uiButHandleHoldFunc hold_func; + void *hold_argN; + uiLink *link; short linkto[2]; /* region relative coords */ diff --git a/source/blender/editors/interface/interface_layout.c b/source/blender/editors/interface/interface_layout.c index 616fc055c03..33dc74cbe23 100644 --- a/source/blender/editors/interface/interface_layout.c +++ b/source/blender/editors/interface/interface_layout.c @@ -795,7 +795,7 @@ static void ui_item_disabled(uiLayout *layout, const char *name) * \param r_opptr: Optional, initialize with operator properties when not NULL. * Will always be written to even in the case of errors. */ -void uiItemFullO_ptr( +static uiBut *uiItemFullO_ptr_ex( uiLayout *layout, wmOperatorType *ot, const char *name, int icon, IDProperty *properties, int context, int flag, PointerRNA *r_opptr) @@ -862,6 +862,50 @@ void uiItemFullO_ptr( *r_opptr = *opptr; } } + + return but; +} + +static void ui_item_hold_menu(struct bContext *C, ARegion *butregion, uiBut *but) +{ + uiPopupMenu *pup = UI_popup_menu_begin(C, "", ICON_NONE); + uiLayout *layout = UI_popup_menu_layout(pup); + uiBlock *block = layout->root->block; + UI_popup_menu_but_set(pup, butregion, but); + + block->flag |= UI_BLOCK_POPUP_HOLD; + + const char *menu_id = but->hold_argN; + MenuType *mt = WM_menutype_find(menu_id, true); + if (mt) { + Menu menu = {NULL}; + menu.layout = layout; + menu.type = mt; + mt->draw(C, &menu); + } + else { + uiItemL(layout, "Menu Missing:", ICON_NONE); + uiItemL(layout, menu_id, ICON_NONE); + } + UI_popup_menu_end(C, pup); +} + +void uiItemFullO_ptr( + uiLayout *layout, wmOperatorType *ot, + const char *name, int icon, IDProperty *properties, int context, int flag, + PointerRNA *r_opptr) +{ + uiItemFullO_ptr_ex(layout, ot, name, icon, properties, context, flag, r_opptr); +} + +void uiItemFullOMenuHold_ptr( + uiLayout *layout, wmOperatorType *ot, + const char *name, int icon, IDProperty *properties, int context, int flag, + const char *menu_id, + PointerRNA *r_opptr) +{ + uiBut *but = uiItemFullO_ptr_ex(layout, ot, name, icon, properties, context, flag, r_opptr); + UI_but_func_hold_set(but, ui_item_hold_menu, BLI_strdup(menu_id)); } void uiItemFullO( diff --git a/source/blender/editors/interface/interface_regions.c b/source/blender/editors/interface/interface_regions.c index 71124cf8eb7..06d5e5a314e 100644 --- a/source/blender/editors/interface/interface_regions.c +++ b/source/blender/editors/interface/interface_regions.c @@ -2627,6 +2627,7 @@ struct uiPopupMenu { uiBlock *block; uiLayout *layout; uiBut *but; + ARegion *butregion; int mx, my; bool popup, slideout; @@ -2873,17 +2874,33 @@ uiPopupMenu *UI_popup_menu_begin(bContext *C, const char *title, int icon) return UI_popup_menu_begin_ex(C, title, __func__, icon); } +/** + * Setting the button makes the popup open from the button instead of the cursor. + */ +void UI_popup_menu_but_set(uiPopupMenu *pup, struct ARegion *butregion, uiBut *but) +{ + pup->but = but; + pup->butregion = butregion; +} + /* set the whole structure to work */ void UI_popup_menu_end(bContext *C, uiPopupMenu *pup) { wmWindow *window = CTX_wm_window(C); uiPopupBlockHandle *menu; + uiBut *but = NULL; + ARegion *butregion = NULL; pup->popup = true; pup->mx = window->eventstate->x; pup->my = window->eventstate->y; - - menu = ui_popup_block_create(C, NULL, NULL, NULL, ui_block_func_POPUP, pup); + + if (pup->but) { + but = pup->but; + butregion = pup->butregion; + } + + menu = ui_popup_block_create(C, butregion, but, NULL, ui_block_func_POPUP, pup); menu->popup = true; UI_popup_handlers_add(C, &window->modalhandlers, menu, 0); diff --git a/source/blender/editors/interface/interface_widgets.c b/source/blender/editors/interface/interface_widgets.c index 51bf09125ba..44a9d0d91b6 100644 --- a/source/blender/editors/interface/interface_widgets.c +++ b/source/blender/editors/interface/interface_widgets.c @@ -66,6 +66,15 @@ /* icons are 80% of height of button (16 pixels inside 20 height) */ #define ICON_SIZE_FROM_BUTRECT(rect) (0.8f * BLI_rcti_size_y(rect)) +#define UI_BUT_FLAGS_PUBLIC \ + (UI_SELECT | UI_SCROLLED | UI_ACTIVE | UI_HAS_ICON | UI_TEXTINPUT | UI_HIDDEN) + +/* Bits 0..5 are from UI_SELECT .. etc */ +enum { + /* Show that holding the button opens a menu. */ + UI_STATE_HOLD_ACTION = (1 << 6), +}; + /* ************** widget base functions ************** */ /** * - in: roundbox codes for corner types and radius @@ -184,6 +193,15 @@ static const unsigned int check_tria_face[4][3] = { {3, 2, 4}, {3, 4, 5}, {1, 0, 3}, {0, 2, 3} }; +#define OY -0.2 +#define SC 0.35 +static const unsigned int hold_action_tri_face[2][3] = {{2, 0, 1}, {3, 5, 4}}; +static const float hold_action_tri_vert[6][2] = { + {-0.5 + SC, 1.0 + OY}, {0.5, 1.0 + OY}, {0.5, 0.0 + OY + SC}, +}; +#undef OY +#undef SC + /* ************************************************* */ void ui_draw_anti_tria(float x1, float y1, float x2, float y2, float x3, float y3) @@ -529,6 +547,14 @@ static void widget_num_tria(uiWidgetTrias *tria, const rcti *rect, float triasiz num_tria_face, ARRAY_SIZE(num_tria_face)); } +static void widget_hold_action_tria(uiWidgetTrias *tria, const rcti *rect, float triasize, char where) +{ + widget_draw_tria_ex( + tria, rect, triasize, where, + hold_action_tri_vert, ARRAY_SIZE(hold_action_tri_vert), + hold_action_tri_face, ARRAY_SIZE(hold_action_tri_face)); +} + static void widget_scroll_circle(uiWidgetTrias *tria, const rcti *rect, float triasize, char where) { widget_draw_tria_ex( @@ -3347,6 +3373,7 @@ static void widget_but(uiWidgetColors *wcol, rcti *rect, int UNUSED(state), int widgetbase_draw(&wtb, wcol); } +#if 0 static void widget_roundbut(uiWidgetColors *wcol, rcti *rect, int UNUSED(state), int roundboxalign) { uiWidgetBase wtb; @@ -3359,6 +3386,25 @@ static void widget_roundbut(uiWidgetColors *wcol, rcti *rect, int UNUSED(state), widgetbase_draw(&wtb, wcol); } +#endif + +static void widget_roundbut_exec(uiWidgetColors *wcol, rcti *rect, int state, int roundboxalign) +{ + uiWidgetBase wtb; + const float rad = 0.25f * U.widget_unit; + + widget_init(&wtb); + + if (state & UI_STATE_HOLD_ACTION) { + /* Show that keeping pressed performs another action (typically a menu). */ + widget_hold_action_tria(&wtb.tria1, rect, 0.75f, 'r'); + } + + /* half rounded */ + round_box_edges(&wtb, roundboxalign, rect, rad); + + widgetbase_draw(&wtb, wcol); +} static void widget_draw_extra_mask(const bContext *C, uiBut *but, uiWidgetType *wt, rcti *rect) { @@ -3439,7 +3485,7 @@ static uiWidgetType *widget_type(uiWidgetTypeEnum type) case UI_WTYPE_EXEC: wt.wcol_theme = &btheme->tui.wcol_tool; - wt.draw = widget_roundbut; + wt.draw = widget_roundbut_exec; break; case UI_WTYPE_TOOLTIP: @@ -3849,7 +3895,7 @@ void ui_draw_but(const bContext *C, ARegion *ar, uiStyle *style, uiBut *but, rct roundboxalign = widget_roundbox_set(but, rect); - state = but->flag; + state = but->flag & UI_BUT_FLAGS_PUBLIC; if ((but->editstr) || (UNLIKELY(but->flag & UI_BUT_DRAG_MULTI) && ui_but_drag_multi_edit_get(but))) @@ -3857,6 +3903,10 @@ void ui_draw_but(const bContext *C, ARegion *ar, uiStyle *style, uiBut *but, rct state |= UI_TEXTINPUT; } + if (but->hold_func) { + state |= UI_STATE_HOLD_ACTION; + } + if (state & (UI_BUT_DISABLED | UI_BUT_INACTIVE)) if (but->dt != UI_EMBOSS_PULLDOWN) disabled = true; diff --git a/source/blender/makesrna/intern/rna_ui_api.c b/source/blender/makesrna/intern/rna_ui_api.c index c3a16c476bc..eb95602010a 100644 --- a/source/blender/makesrna/intern/rna_ui_api.c +++ b/source/blender/makesrna/intern/rna_ui_api.c @@ -202,6 +202,30 @@ static PointerRNA rna_uiItemO(uiLayout *layout, const char *opname, const char * return opptr; } +static PointerRNA rna_uiItemOMenuHold( + uiLayout *layout, const char *opname, const char *name, const char *text_ctxt, + int translate, int icon, int emboss, int icon_value, + const char *menu) +{ + wmOperatorType *ot = WM_operatortype_find(opname, 0); /* print error next */ + if (!ot || !ot->srna) { + RNA_warning("%s '%s'", ot ? "unknown operator" : "operator missing srna", opname); + return PointerRNA_NULL; + } + + /* Get translated name (label). */ + name = rna_translate_ui_text(name, text_ctxt, ot->srna, NULL, translate); + if (icon_value && !icon) { + icon = icon_value; + } + int flag = (emboss) ? 0 : UI_ITEM_R_NO_BG; + + PointerRNA opptr; + uiItemFullOMenuHold_ptr(layout, ot, name, icon, NULL, uiLayoutGetOperatorContext(layout), flag, menu, &opptr); + return opptr; +} + + static void rna_uiItemMenuEnumO(uiLayout *layout, bContext *C, const char *opname, const char *propname, const char *name, const char *text_ctxt, int translate, int icon) { @@ -553,15 +577,23 @@ void RNA_api_ui_layout(StructRNA *srna) RNA_def_parameter_flags(parm, 0, PARM_REQUIRED); api_ui_item_common(func); - func = RNA_def_function(srna, "operator", "rna_uiItemO"); - api_ui_item_op_common(func); - RNA_def_boolean(func, "emboss", true, "", "Draw the button itself, just the icon/text"); - parm = RNA_def_property(func, "icon_value", PROP_INT, PROP_UNSIGNED); - RNA_def_property_ui_text(parm, "Icon Value", "Override automatic icon of the item"); - parm = RNA_def_pointer(func, "properties", "OperatorProperties", "", "Operator properties to fill in"); - RNA_def_parameter_flags(parm, 0, PARM_REQUIRED | PARM_RNAPTR); - RNA_def_function_return(func, parm); - RNA_def_function_ui_description(func, "Item. Places a button into the layout to call an Operator"); + for (int is_menu_hold = 0; is_menu_hold < 2; is_menu_hold++) { + func = (is_menu_hold) ? + RNA_def_function(srna, "operator_menu_hold", "rna_uiItemOMenuHold") : + RNA_def_function(srna, "operator", "rna_uiItemO"); + api_ui_item_op_common(func); + RNA_def_boolean(func, "emboss", true, "", "Draw the button itself, just the icon/text"); + parm = RNA_def_property(func, "icon_value", PROP_INT, PROP_UNSIGNED); + RNA_def_property_ui_text(parm, "Icon Value", "Override automatic icon of the item"); + if (is_menu_hold) { + parm = RNA_def_string(func, "menu", NULL, 0, "", "Identifier of the menu"); + RNA_def_parameter_flags(parm, 0, PARM_REQUIRED); + } + parm = RNA_def_pointer(func, "properties", "OperatorProperties", "", "Operator properties to fill in"); + RNA_def_parameter_flags(parm, 0, PARM_REQUIRED | PARM_RNAPTR); + RNA_def_function_return(func, parm); + RNA_def_function_ui_description(func, "Item. Places a button into the layout to call an Operator"); + } func = RNA_def_function(srna, "operator_enum", "uiItemsEnumO"); parm = RNA_def_string(func, "operator", NULL, 0, "", "Identifier of the operator"); -- cgit v1.2.3