From 21c88df7c73dc35134de061a89b088985bbccadb Mon Sep 17 00:00:00 2001 From: Julian Eisel Date: Tue, 16 Feb 2016 14:50:26 +0100 Subject: Fix items in mode pie changing position by supporting more than 8 items in operator-enum pies Now a 'More' item is added to the pie when there are too many items. It opens a sub-pie that contains the remaining items. Note that this only touches operator-enum pies (like the object mode pie is), it is not a complete support for pies with more than 8 items. For this further design and code work would be needed, but this is too urgent to wait for that. This is a better fix for T46973, should definitely be applied for 2.77 release. Patch D1800 by myself with some edits by @campbellbarton, thanks! --- source/blender/editors/include/UI_interface.h | 8 +- .../blender/editors/interface/interface_intern.h | 11 ++ .../blender/editors/interface/interface_layout.c | 214 +++++++++++++-------- .../blender/editors/interface/interface_regions.c | 97 ++++++++++ 4 files changed, 247 insertions(+), 83 deletions(-) diff --git a/source/blender/editors/include/UI_interface.h b/source/blender/editors/include/UI_interface.h index 617b0d12684..574c6d93cbf 100644 --- a/source/blender/editors/include/UI_interface.h +++ b/source/blender/editors/include/UI_interface.h @@ -977,7 +977,13 @@ void uiItemEnumR(uiLayout *layout, const char *name, int icon, struct PointerRNA void uiItemEnumR_string(uiLayout *layout, struct PointerRNA *ptr, const char *propname, const char *value, const char *name, int icon); void uiItemsEnumR(uiLayout *layout, struct PointerRNA *ptr, const char *propname); void uiItemPointerR(uiLayout *layout, struct PointerRNA *ptr, const char *propname, struct PointerRNA *searchptr, const char *searchpropname, const char *name, int icon); -void uiItemsFullEnumO(uiLayout *layout, const char *opname, const char *propname, struct IDProperty *properties, int context, int flag); +void uiItemsFullEnumO( + uiLayout *layout, const char *opname, const char *propname, + struct IDProperty *properties, int context, int flag); +void uiItemsFullEnumO_items( + uiLayout *layout, struct wmOperatorType *ot, PointerRNA ptr, PropertyRNA *prop, + IDProperty *properties, int context, int flag, + const EnumPropertyItem *item_array, int totitem); void uiItemL(uiLayout *layout, const char *name, int icon); /* label */ void uiItemLDrag(uiLayout *layout, struct PointerRNA *ptr, const char *name, int icon); /* label icon for dragging */ diff --git a/source/blender/editors/interface/interface_intern.h b/source/blender/editors/interface/interface_intern.h index 06de5f99769..66510fb470d 100644 --- a/source/blender/editors/interface/interface_intern.h +++ b/source/blender/editors/interface/interface_intern.h @@ -182,6 +182,9 @@ enum { #define PIE_CLICK_THRESHOLD_SQ 50.0f +/* max amount of items a radial menu (pie menu) can contain */ +#define PIE_MAX_ITEMS 8 + typedef struct uiLinkLine { /* only for draw/edit */ struct uiLinkLine *next, *prev; struct uiBut *from, *to; @@ -332,6 +335,10 @@ typedef struct ColorPickerData { } ColorPickerData; struct PieMenuData { + /* store title and icon to allow access when pie levels are created */ + const char *title; + int icon; + float pie_dir[2]; float pie_center_init[2]; float pie_center_spawned[2]; @@ -603,6 +610,10 @@ uiPopupBlockHandle *ui_popup_menu_create( struct bContext *C, struct ARegion *butregion, uiBut *but, uiMenuCreateFunc create_func, void *arg); +void ui_pie_menu_level_create( + uiBlock *block, struct wmOperatorType *ot, const char *propname, IDProperty *properties, + const EnumPropertyItem *items, int totitem, int context, int flag); + void ui_popup_block_free(struct bContext *C, uiPopupBlockHandle *handle); int ui_but_menu_step(uiBut *but, int step); diff --git a/source/blender/editors/interface/interface_layout.c b/source/blender/editors/interface/interface_layout.c index 0b53232c29b..4d9c1b387a1 100644 --- a/source/blender/editors/interface/interface_layout.c +++ b/source/blender/editors/interface/interface_layout.c @@ -720,7 +720,7 @@ void UI_context_active_but_prop_get_filebrowser( /** * Update a buttons tip with an enum's description if possible. */ -static void ui_but_tip_from_enum_item(uiBut *but, EnumPropertyItem *item) +static void ui_but_tip_from_enum_item(uiBut *but, const EnumPropertyItem *item) { if (but->tip == NULL || but->tip[0] == '\0') { if (item->description && item->description[0]) { @@ -883,6 +883,125 @@ void uiItemEnumO(uiLayout *layout, const char *opname, const char *name, int ico } +BLI_INLINE bool ui_layout_is_radial(const uiLayout *layout) +{ + return (layout->item.type == ITEM_LAYOUT_RADIAL) || + ((layout->item.type == ITEM_LAYOUT_ROOT) && (layout->root->type == UI_LAYOUT_PIEMENU)); +} + +/** + * Create ui items for enum items in \a item_array. + * + * A version of #uiItemsFullEnumO that takes pre-calculated item array. + */ +void uiItemsFullEnumO_items( + uiLayout *layout, wmOperatorType *ot, PointerRNA ptr, PropertyRNA *prop, IDProperty *properties, + int context, int flag, + const EnumPropertyItem *item_array, int totitem) +{ + const char *propname = RNA_property_identifier(prop); + if (RNA_property_type(prop) != PROP_ENUM) { + RNA_warning("%s.%s, not an enum type", RNA_struct_identifier(ptr.type), propname); + return; + } + + uiLayout *target, *split = NULL; + const EnumPropertyItem *item; + uiBlock *block = layout->root->block; + const bool radial = ui_layout_is_radial(layout); + + if (radial) { + target = uiLayoutRadial(layout); + } + else { + split = uiLayoutSplit(layout, 0.0f, false); + target = uiLayoutColumn(split, layout->align); + } + + + int i; + bool last_iter = false; + + for (i = 1, item = item_array; item->identifier && !last_iter; i++, item++) { + /* handle oversized pies */ + if (radial && (totitem > PIE_MAX_ITEMS) && (i >= PIE_MAX_ITEMS)) { + if (item->name) { /* only visible items */ + const EnumPropertyItem *tmp; + + /* Check if there are more visible items for the next level. If not, we don't + * add a new level and add the remaining item instead of the 'more' button. */ + for (tmp = item + 1; tmp->identifier; tmp++) + if (tmp->name) + break; + + if (tmp->identifier) { /* only true if loop above found item and did early-exit */ + ui_pie_menu_level_create(block, ot, propname, properties, item_array, totitem, context, flag); + /* break since rest of items is handled in new pie level */ + break; + } + else { + last_iter = true; + } + } + else { + continue; + } + } + + if (item->identifier[0]) { + PointerRNA tptr; + + WM_operator_properties_create_ptr(&tptr, ot); + if (properties) { + if (tptr.data) { + IDP_FreeProperty(tptr.data); + MEM_freeN(tptr.data); + } + tptr.data = IDP_CopyProperty(properties); + } + RNA_property_enum_set(&tptr, prop, item->value); + + uiItemFullO_ptr(target, ot, item->name, item->icon, tptr.data, context, flag); + + ui_but_tip_from_enum_item(block->buttons.last, item); + } + else { + if (item->name) { + uiBut *but; + + if (item != item_array && !radial) { + target = uiLayoutColumn(split, layout->align); + + /* inconsistent, but menus with labels do not look good flipped */ + block->flag |= UI_BLOCK_NO_FLIP; + } + + if (item->icon || radial) { + uiItemL(target, item->name, item->icon); + + but = block->buttons.last; + } + else { + /* Do not use uiItemL here, as our root layout is a menu one, it will add a fake blank icon! */ + but = uiDefBut(block, UI_BTYPE_LABEL, 0, item->name, 0, 0, UI_UNIT_X * 5, UI_UNIT_Y, NULL, + 0.0, 0.0, 0, 0, ""); + } + ui_but_tip_from_enum_item(but, item); + } + else { + if (radial) { + /* invisible dummy button to ensure all items are always at the same position */ + uiItemS(target); + } + else { + /* XXX bug here, colums draw bottom item badly */ + uiItemS(target); + } + } + } + } +} + void uiItemsFullEnumO( uiLayout *layout, const char *opname, const char *propname, IDProperty *properties, int context, int flag) @@ -892,8 +1011,6 @@ void uiItemsFullEnumO( PointerRNA ptr; PropertyRNA *prop; uiBlock *block = layout->root->block; - const bool radial = (layout->item.type == ITEM_LAYOUT_RADIAL) || - ((layout->item.type == ITEM_LAYOUT_ROOT) && (layout->root->type == UI_LAYOUT_PIEMENU)); if (!ot || !ot->srna) { ui_item_disabled(layout, opname); @@ -910,89 +1027,22 @@ void uiItemsFullEnumO( BLI_assert((prop == NULL) || (RNA_property_type(prop) == PROP_ENUM)); if (prop && RNA_property_type(prop) == PROP_ENUM) { - EnumPropertyItem *item, *item_array = NULL; + EnumPropertyItem *item_array = NULL; + int totitem; bool free; - uiLayout *split = NULL; - uiLayout *target; - if (radial) { - target = uiLayoutRadial(layout); + if (ui_layout_is_radial(layout)) { + RNA_property_enum_items_gettexted_all(block->evil_C, &ptr, prop, &item_array, &totitem, &free); } else { - split = uiLayoutSplit(layout, 0.0f, false); - target = uiLayoutColumn(split, layout->align); - } - - if (0 && radial) { - /* XXX Disabled as temporary workaround! - * - * Normally, we always draw a button for all items even if they're unavailable (we draw invisible - * dummy buttons then), so items are always at the same position. This causes issues with current - * 'Object Mode' pie since more than 8 modes exist now (see T46973). - * Disabling this until more than 8 items per pie are supported (or a better solution is found). - * We should work on that ASAP though. - * - * - Julian (Dec 2015) - */ - RNA_property_enum_items_gettexted_all(block->evil_C, &ptr, prop, &item_array, NULL, &free); - } - else { - RNA_property_enum_items_gettexted(block->evil_C, &ptr, prop, &item_array, NULL, &free); + RNA_property_enum_items_gettexted(block->evil_C, &ptr, prop, &item_array, &totitem, &free); } - for (item = item_array; item->identifier; item++) { - if (item->identifier[0]) { - PointerRNA tptr; + /* add items */ + uiItemsFullEnumO_items( + layout, ot, ptr, prop, properties, context, flag, + item_array, totitem); - WM_operator_properties_create_ptr(&tptr, ot); - if (properties) { - if (tptr.data) { - IDP_FreeProperty(tptr.data); - MEM_freeN(tptr.data); - } - tptr.data = IDP_CopyProperty(properties); - } - RNA_property_enum_set(&tptr, prop, item->value); - - uiItemFullO_ptr(target, ot, item->name, item->icon, tptr.data, context, flag); - - ui_but_tip_from_enum_item(block->buttons.last, item); - } - else { - if (item->name) { - uiBut *but; - - if (item != item_array && !radial) { - target = uiLayoutColumn(split, layout->align); - - /* inconsistent, but menus with labels do not look good flipped */ - block->flag |= UI_BLOCK_NO_FLIP; - } - - if (item->icon || radial) { - uiItemL(target, item->name, item->icon); - - but = block->buttons.last; - } - else { - /* Do not use uiItemL here, as our root layout is a menu one, it will add a fake blank icon! */ - but = uiDefBut(block, UI_BTYPE_LABEL, 0, item->name, 0, 0, UI_UNIT_X * 5, UI_UNIT_Y, NULL, - 0.0, 0.0, 0, 0, ""); - } - ui_but_tip_from_enum_item(but, item); - } - else { - if (radial) { - /* invisible dummy button to ensure all items are always at the same position */ - uiItemS(target); - } - else { - /* XXX bug here, colums draw bottom item badly */ - uiItemS(target); - } - } - } - } if (free) { MEM_freeN(item_array); } @@ -2178,9 +2228,9 @@ static RadialDirection ui_get_radialbut_vec(float vec[2], short itemnum) { RadialDirection dir; - if (itemnum >= 8) { - itemnum %= 8; - printf("Warning: Pie menus with more than 8 items are currently unsupported\n"); + if (itemnum >= PIE_MAX_ITEMS) { + itemnum %= PIE_MAX_ITEMS; + printf("Warning: Pie menus with more than %i items are currently unsupported\n", PIE_MAX_ITEMS); } dir = ui_radial_dir_order[itemnum]; diff --git a/source/blender/editors/interface/interface_regions.c b/source/blender/editors/interface/interface_regions.c index 877aea9bb95..1f9be649c56 100644 --- a/source/blender/editors/interface/interface_regions.c +++ b/source/blender/editors/interface/interface_regions.c @@ -2840,6 +2840,8 @@ uiPieMenu *UI_pie_menu_begin(struct bContext *C, const char *title, int icon, co } /* do not align left */ but->drawflag &= ~UI_BUT_TEXT_LEFT; + pie->block_radial->pie_data.title = but->str; + pie->block_radial->pie_data.icon = icon; } return pie; @@ -2951,6 +2953,101 @@ int UI_pie_menu_invoke_from_rna_enum( return OPERATOR_INTERFACE; } +/** + * \name Pie Menu Levels + * + * Pie menus can't contain more than 8 items (yet). When using #uiItemsFullEnumO, a "More" button is created that calls + * a new pie menu if the enum has too many items. We call this a new "level". + * Indirect recursion is used, so that a theoretically unlimited number of items is supported. + * + * This is a implementation specifically for operator enums, needed since the object mode pie now has more than 8 + * items. Ideally we'd have some way of handling this for all kinds of pie items, but that's tricky. + * + * - Julian (Feb 2016) + * + * \{ */ + +typedef struct PieMenuLevelData { + char title[UI_MAX_NAME_STR]; /* parent pie title, copied for level */ + int icon; /* parent pie icon, copied for level */ + int totitem; /* total count of *remaining* items */ + + /* needed for calling uiItemsFullEnumO_array again for new level */ + wmOperatorType *ot; + const char *propname; + IDProperty *properties; + int context, flag; +} PieMenuLevelData; + +/** + * Invokes a new pie menu for a new level. + */ +static void ui_pie_menu_level_invoke(bContext *C, void *argN, void *arg2) +{ + EnumPropertyItem *item_array = (EnumPropertyItem *)argN; + PieMenuLevelData *lvl = (PieMenuLevelData *)arg2; + wmWindow *win = CTX_wm_window(C); + + uiPieMenu *pie = UI_pie_menu_begin(C, IFACE_(lvl->title), lvl->icon, win->eventstate); + uiLayout *layout = UI_pie_menu_layout(pie); + + layout = uiLayoutRadial(layout); + + PointerRNA ptr; + + WM_operator_properties_create_ptr(&ptr, lvl->ot); + /* so the context is passed to itemf functions (some need it) */ + WM_operator_properties_sanitize(&ptr, false); + PropertyRNA *prop = RNA_struct_find_property(&ptr, lvl->propname); + + if (prop) { + uiItemsFullEnumO_items( + layout, lvl->ot, ptr, prop, lvl->properties, lvl->context, lvl->flag, + item_array, lvl->totitem); + } + else { + RNA_warning("%s.%s not found", RNA_struct_identifier(ptr.type), lvl->propname); + } + + UI_pie_menu_end(C, pie); +} + +/** + * Set up data for defining a new pie menu level and add button that invokes it. + */ +void ui_pie_menu_level_create( + uiBlock *block, wmOperatorType *ot, const char *propname, IDProperty *properties, + const EnumPropertyItem *items, int totitem, int context, int flag) +{ + const int totitem_parent = PIE_MAX_ITEMS - 1; + const int totitem_remain = totitem - totitem_parent; + size_t array_size = sizeof(EnumPropertyItem) * totitem_remain; + + /* used as but->func_argN so freeing is handled elsewhere */ + EnumPropertyItem *remaining = MEM_mallocN(array_size + sizeof(EnumPropertyItem), "pie_level_item_array"); + memcpy(remaining, items + totitem_parent, array_size); + /* a NULL terminating sentinal element is required */ + memset(&remaining[totitem_remain], 0, sizeof(EnumPropertyItem)); + + + /* yuk, static... issue is we can't reliably free this without doing dangerous changes */ + static PieMenuLevelData lvl; + BLI_strncpy(lvl.title, block->pie_data.title, UI_MAX_NAME_STR); + lvl.totitem = totitem_remain; + lvl.ot = ot; + lvl.propname = propname; + lvl.properties = properties; + lvl.context = context; + lvl.flag = flag; + + /* add a 'more' menu entry */ + uiBut *but = uiDefIconTextBut(block, UI_BTYPE_BUT, 0, ICON_PLUS, "More", 0, 0, UI_UNIT_X * 3, UI_UNIT_Y, NULL, + 0.0f, 0.0f, 0.0f, 0.0f, "Show more items of this menu"); + UI_but_funcN_set(but, ui_pie_menu_level_invoke, remaining, &lvl); +} + +/** \} */ /* Pie Menu Levels */ + /*************************** Standard Popup Menus ****************************/ -- cgit v1.2.3