UI: support context menu in menu search popup

This commit is contained in:
Campbell Barton 2020-05-07 23:16:22 +10:00
parent daf10d17f4
commit 82704ac3ed
Notes: blender-bot 2023-02-14 08:07:50 +01:00
Referenced by issue #74157, Use menus for operator search & various improvements
Referenced by issue #64365, Support Context menu for operator search items
7 changed files with 161 additions and 5 deletions

View File

@ -512,6 +512,10 @@ typedef void (*uiButSearchUpdateFn)(const struct bContext *C,
const char *str,
uiSearchItems *items);
typedef void (*uiButSearchArgFreeFn)(void *arg);
typedef bool (*uiButSearchContextMenuFn)(struct bContext *C,
void *arg,
void *active,
const struct wmEvent *event);
/* Must return allocated string. */
typedef char *(*uiButToolTipFunc)(struct bContext *C, void *argN, const char *tip);
@ -1579,6 +1583,7 @@ void UI_but_func_search_set(uiBut *but,
uiButSearchArgFreeFn search_arg_free_fn,
uiButHandleFunc handle_fn,
void *active);
void UI_but_func_search_set_context_menu(uiBut *but, uiButSearchContextMenuFn context_menu_fn);
void UI_but_func_search_set_sep_string(uiBut *but, const char *search_sep_string);
/* height in pixels, it's using hardcoded values still */

View File

@ -6409,6 +6409,12 @@ void UI_but_func_search_set(uiBut *but,
}
}
void UI_but_func_search_set_context_menu(uiBut *but, uiButSearchContextMenuFn context_menu_fn)
{
struct uiButSearchData *search = but->search;
search->context_menu_fn = context_menu_fn;
}
void UI_but_func_search_set_sep_string(uiBut *but, const char *search_sep_string)
{
struct uiButSearchData *search = but->search;

View File

@ -3473,6 +3473,16 @@ static void ui_do_but_textedit(
case RIGHTMOUSE:
case EVT_ESCKEY:
if (event->val == KM_PRESS) {
/* Support search context menu. */
if (event->type == RIGHTMOUSE) {
if (data->searchbox) {
if (ui_searchbox_event(C, data->searchbox, but, event)) {
/* Only break if the event was handled. */
break;
}
}
}
#ifdef WITH_INPUT_IME
/* skips button handling since it is not wanted */
if (is_ime_composing) {
@ -9333,6 +9343,11 @@ static int ui_handle_menu_button(bContext *C, const wmEvent *event, uiPopupBlock
if (event->val == KM_RELEASE) {
/* pass, needed so we can exit active menu-items when click-dragging out of them */
}
else if (but->type == UI_BTYPE_SEARCH_MENU) {
/* Pass, needed so search popup can have RMB context menu.
* This may be useful for other interactions which happen in the search popup
* without being directly over the search button. */
}
else if (!ui_block_is_menu(but->block) || ui_block_is_pie_menu(but->block)) {
/* pass, skip for dialogs */
}

View File

@ -150,6 +150,8 @@ struct uiButSearchData {
uiButSearchUpdateFn update_fn;
void *arg;
uiButSearchArgFreeFn arg_free_fn;
uiButSearchContextMenuFn context_menu_fn;
const char *sep_string;
};
@ -658,7 +660,7 @@ bool ui_searchbox_inside(struct ARegion *region, int x, int y);
int ui_searchbox_find_index(struct ARegion *region, const char *name);
void ui_searchbox_update(struct bContext *C, struct ARegion *region, uiBut *but, const bool reset);
int ui_searchbox_autocomplete(struct bContext *C, struct ARegion *region, uiBut *but, char *str);
void ui_searchbox_event(struct bContext *C,
bool ui_searchbox_event(struct bContext *C,
struct ARegion *region,
uiBut *but,
const struct wmEvent *event);

View File

@ -153,7 +153,8 @@ bool UI_search_item_add(uiSearchItems *items, const char *name, void *poin, int
/* Limit flags that can be set so flags such as 'UI_SELECT' aren't accidentally set
* which will cause problems, add others as needed. */
BLI_assert((state & ~(UI_BUT_DISABLED | UI_BUT_INACTIVE | UI_BUT_REDALERT)) == 0);
BLI_assert(
(state & ~(UI_BUT_DISABLED | UI_BUT_INACTIVE | UI_BUT_REDALERT | UI_BUT_HAS_SEP_CHAR)) == 0);
if (items->states) {
items->states[items->totitem] = state;
}
@ -295,10 +296,11 @@ bool ui_searchbox_apply(uiBut *but, ARegion *region)
}
}
void ui_searchbox_event(bContext *C, ARegion *region, uiBut *but, const wmEvent *event)
bool ui_searchbox_event(bContext *C, ARegion *region, uiBut *but, const wmEvent *event)
{
uiSearchboxData *data = region->regiondata;
int type = event->type, val = event->val;
bool handled = false;
if (type == MOUSEPAN) {
ui_pan_to_scroll(event, &type, &val);
@ -308,10 +310,32 @@ void ui_searchbox_event(bContext *C, ARegion *region, uiBut *but, const wmEvent
case WHEELUPMOUSE:
case EVT_UPARROWKEY:
ui_searchbox_select(C, region, but, -1);
handled = true;
break;
case WHEELDOWNMOUSE:
case EVT_DOWNARROWKEY:
ui_searchbox_select(C, region, but, 1);
handled = true;
break;
case RIGHTMOUSE:
if (val) {
if (but->search->context_menu_fn) {
if (data->active != -1) {
/* Check the cursor is over the active element
* (a little confusing if this isn't the case, although it does work). */
rcti rect;
ui_searchbox_butrect(&rect, data, data->active);
if (BLI_rcti_isect_pt(
&rect, event->x - region->winrct.xmin, event->y - region->winrct.ymin)) {
void *active = data->items.pointers[data->active];
if (but->search->context_menu_fn(C, but->search->arg, active, event)) {
handled = true;
}
}
}
}
}
break;
case MOUSEMOVE:
if (BLI_rcti_isect_pt(&region->winrct, event->x, event->y)) {
@ -325,6 +349,7 @@ void ui_searchbox_event(bContext *C, ARegion *region, uiBut *but, const wmEvent
if (data->active != a) {
data->active = a;
ui_searchbox_select(C, region, but, 0);
handled = true;
break;
}
}
@ -332,6 +357,7 @@ void ui_searchbox_event(bContext *C, ARegion *region, uiBut *but, const wmEvent
}
break;
}
return handled;
}
/* region is the search box itself */

View File

@ -137,6 +137,12 @@ struct MenuSearch_Data {
ListBase items;
/** Use for all small allocations. */
MemArena *memarena;
/** Use for context menu, to fake a button to create a context menu. */
struct {
uiBut but;
uiBlock block;
} context_menu_data;
};
static int menu_item_sort_by_drawstr_full(const void *menu_item_a_v, const void *menu_item_b_v)
@ -208,7 +214,8 @@ static bool menu_items_from_ui_create_item_from_button(struct MenuSearch_Data *d
/* Handle shared settings. */
item->drawstr = strdup_memarena(memarena, but->drawstr);
item->icon = ui_but_icon(but);
item->state = (but->flag & (UI_BUT_DISABLED | UI_BUT_INACTIVE | UI_BUT_REDALERT));
item->state = (but->flag &
(UI_BUT_DISABLED | UI_BUT_INACTIVE | UI_BUT_REDALERT | UI_BUT_HAS_SEP_CHAR));
item->mt = mt;
item->drawstr_submenu = drawstr_submenu ? strdup_memarena(memarena, drawstr_submenu) : NULL;
@ -221,6 +228,51 @@ static bool menu_items_from_ui_create_item_from_button(struct MenuSearch_Data *d
return false;
}
/**
* Populate a fake button from a menu item (use for context menu).
*/
static bool menu_items_to_ui_button(struct MenuSearch_Item *item, uiBut *but)
{
bool changed = false;
switch (item->type) {
case MENU_SEARCH_TYPE_OP: {
but->optype = item->op.type;
but->opcontext = item->op.opcontext;
but->context = item->op.context;
but->opptr = item->op.opptr;
changed = true;
break;
}
case MENU_SEARCH_TYPE_RNA: {
const int prop_type = RNA_property_type(item->rna.prop);
but->rnapoin = item->rna.ptr;
but->rnaprop = item->rna.prop;
but->rnaindex = item->rna.index;
if (prop_type == PROP_ENUM) {
but->hardmax = item->rna.enum_value;
}
changed = true;
break;
}
}
if (changed) {
STRNCPY(but->drawstr, item->drawstr);
char *drawstr_sep = (item->state & UI_BUT_HAS_SEP_CHAR) ? strrchr(but->drawstr, UI_SEP_CHAR) :
NULL;
if (drawstr_sep) {
*drawstr_sep = '\0';
}
but->icon = item->icon;
but->str = but->strdata;
}
return changed;
}
/**
* Populate \a menu_stack with menus from inspecting active key-maps for this context.
*/
@ -318,6 +370,7 @@ static void menu_items_from_all_operators(bContext *C, struct MenuSearch_Data *d
SNPRINTF(uiname, "%s " MENU_SEP "%s", idname_as_py, ot_ui_name);
item->drawwstr_full = strdup_memarena(memarena, uiname);
item->drawstr = ot_ui_name;
item->wm_context = NULL;
@ -895,6 +948,53 @@ static void menu_search_cb(const bContext *UNUSED(C),
/** \} */
/* -------------------------------------------------------------------- */
/** \name Context Menu
*
* This uses a fake button to create a context menu,
* if this ever causes hard to solve bugs we may need to create
* a separate context menu just for the search, however this is fairly involved.
* \{ */
static bool menu_search_context_menu_fn(struct bContext *C,
void *arg,
void *active,
const struct wmEvent *UNUSED(event))
{
struct MenuSearch_Data *data = arg;
struct MenuSearch_Item *item = active;
bool has_menu = false;
memset(&data->context_menu_data, 0x0, sizeof(data->context_menu_data));
uiBut *but = &data->context_menu_data.but;
uiBlock *block = &data->context_menu_data.block;
but->block = block;
if (menu_items_to_ui_button(item, but)) {
ScrArea *area_prev = CTX_wm_area(C);
ARegion *region_prev = CTX_wm_region(C);
if (item->wm_context != NULL) {
CTX_wm_area_set(C, item->wm_context->area);
CTX_wm_region_set(C, item->wm_context->region);
}
if (ui_popup_context_menu_for_button(C, but)) {
has_menu = true;
}
if (item->wm_context != NULL) {
CTX_wm_area_set(C, area_prev);
CTX_wm_region_set(C, region_prev);
}
}
return has_menu;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Menu Search Template Public API
* \{ */
@ -916,6 +1016,8 @@ void UI_but_func_menu_search(uiBut *but)
menu_items_from_ui_destroy,
menu_call_fn,
NULL);
UI_but_func_search_set_context_menu(but, menu_search_context_menu_fn);
UI_but_func_search_set_sep_string(but, MENU_SEP);
}

View File

@ -1799,7 +1799,7 @@ static int wm_search_menu_invoke(bContext *C, wmOperator *op, const wmEvent *eve
.size = {UI_searchbox_size_x() * 2, UI_searchbox_size_y()},
};
UI_popup_block_invoke(C, wm_block_search_menu, &data, NULL);
UI_popup_block_invoke_ex(C, wm_block_search_menu, &data, NULL, false);
return OPERATOR_INTERFACE;
}