UI: support context menu in menu search popup
This commit is contained in:
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
|
@ -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 */
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 */
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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(®ion->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 */
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue