Refactor: UI: Add uiLayout op_menu_hold, popover and separator_spacer

This replaces `uiItemFullOMenuHold_ptr`, `uiItemFullR_with_popover`,
`uiItemFullR_with_menu`, `uiItemPopoverPanel`* and `uiItemSpacer` API
with methods following uiLayout refactors and the Python API.

Part of: #117604

Pull Request: https://projects.blender.org/blender/blender/pulls/141050
This commit is contained in:
Guillermo Venegas
2025-06-26 21:48:52 +02:00
committed by Hans Goudey
parent 524e4879e5
commit 86d6855ee8
10 changed files with 128 additions and 130 deletions

View File

@@ -842,7 +842,7 @@ static void asset_shelf_header_draw(const bContext *C, Header *header)
list::storage_fetch(library_ref, C);
UI_block_emboss_set(block, blender::ui::EmbossType::None);
uiItemPopoverPanel(layout, C, "ASSETSHELF_PT_catalog_selector", "", ICON_COLLAPSEMENU);
layout->popover(C, "ASSETSHELF_PT_catalog_selector", "", ICON_COLLAPSEMENU);
UI_block_emboss_set(block, blender::ui::EmbossType::Emboss);
layout->separator();
@@ -852,9 +852,9 @@ static void asset_shelf_header_draw(const bContext *C, Header *header)
add_catalog_tabs(*shelf, *layout);
}
uiItemSpacer(layout);
layout->separator_spacer();
uiItemPopoverPanel(layout, C, "ASSETSHELF_PT_display", "", ICON_IMGDISPLAY);
layout->popover(C, "ASSETSHELF_PT_display", "", ICON_IMGDISPLAY);
uiLayout *sub = &layout->row(false);
/* Same as file/asset browser header. */
sub->ui_units_x_set(8);

View File

@@ -954,10 +954,10 @@ void UI_block_lock_clear(uiBlock *block);
enum class uiButtonSectionsAlign : int8_t { None = 1, Top, Bottom };
/**
* Draw a background with rounded corners behind each visual group of buttons. The visual groups
* are separated by spacer buttons (#uiItemSpacer()). Button groups that are closer than
* #UI_BUTTON_SECTION_MERGE_DISTANCE will be merged into one visual section. If the group is closer
* than that to a region edge, it will also be extended to that, and the rounded corners will be
* removed on that edge.
* are separated by spacer buttons (#uiLayout::separator_spacer()). Button groups that are closer
* than #UI_BUTTON_SECTION_MERGE_DISTANCE will be merged into one visual section. If the group is
* closer than that to a region edge, it will also be extended to that, and the rounded corners
* will be removed on that edge.
*
* \note This currently only works well for horizontal, header like regions.
*/

View File

@@ -424,6 +424,22 @@ struct uiLayout : uiItem {
int icon,
wmOperatorCallContext context,
eUI_Item_Flag flag);
/**
* Adds a operator item, places a button in the layout to call the operator, if the button is
* held down, a menu will be displayed instead.
* \param ot: Operator to add.
* \param name: Text to show in the layout.
* \param context: Operator call context for #WM_operator_name_call.
* \param menu_id: menu to show on held down.
* \returns Operator pointer to write properties, might be #PointerRNA_NULL if operator does not
* exists.
*/
PointerRNA op_menu_hold(wmOperatorType *ot,
std::optional<blender::StringRef> name,
int icon,
wmOperatorCallContext context,
eUI_Item_Flag flag,
const char *menu_id);
/**
* Adds a RNA property item, and exposes it into the layout.
* \param ptr: RNA pointer to the struct owner of \a prop.
@@ -446,6 +462,17 @@ struct uiLayout : uiItem {
std::optional<blender::StringRef> name,
int icon);
void popover(const bContext *C,
PanelType *pt,
std::optional<blender::StringRef> name_opt,
int icon);
void popover(const bContext *C,
blender::StringRef panel_type,
std::optional<blender::StringRef> name_opt,
int icon);
void popover_group(
bContext *C, int space_id, int region_id, const char *context, const char *category);
/**
* Add a enum property value item. This button acts like a radio button that are used to chose
* a single enum value from a set of the enum property value items.
@@ -516,8 +543,35 @@ struct uiLayout : uiItem {
std::optional<blender::StringRefNull> name,
int icon);
/**
* Adds a RNA property item, and sets a custom popover to expose its value.
*/
void prop_with_popover(PointerRNA *ptr,
PropertyRNA *prop,
int index,
int value,
eUI_Item_Flag flag,
std::optional<blender::StringRefNull> name,
int icon,
const char *panel_type);
/**
* Adds a RNA property item, and sets a custom menu to expose its value.
*/
void prop_with_menu(PointerRNA *ptr,
PropertyRNA *prop,
int index,
int value,
eUI_Item_Flag flag,
std::optional<blender::StringRefNull> name,
int icon,
const char *menu_type);
/** Adds a separator item, that adds empty space between items. */
void separator(float factor = 1.0f, LayoutSeparatorType type = LayoutSeparatorType::Auto);
/** Adds a spacer item that inserts empty horizontal space between other items in the layout. */
void separator_spacer();
};
inline bool uiLayout::active() const
@@ -786,37 +840,6 @@ void uiItemsEnumO(uiLayout *layout,
blender::StringRefNull opname,
blender::StringRefNull propname);
void uiItemFullOMenuHold_ptr(uiLayout *layout,
wmOperatorType *ot,
std::optional<blender::StringRef> name,
int icon,
wmOperatorCallContext context,
eUI_Item_Flag flag,
const char *menu_id, /* extra menu arg. */
PointerRNA *r_opptr);
/**
* Use a wrapper function since re-implementing all the logic in this function would be messy.
*/
void uiItemFullR_with_popover(uiLayout *layout,
PointerRNA *ptr,
PropertyRNA *prop,
int index,
int value,
eUI_Item_Flag flag,
std::optional<blender::StringRefNull> name,
int icon,
const char *panel_type);
void uiItemFullR_with_menu(uiLayout *layout,
PointerRNA *ptr,
PropertyRNA *prop,
int index,
int value,
eUI_Item_Flag flag,
std::optional<blender::StringRefNull> name,
int icon,
const char *menu_type);
/**
* Create a list of enum items.
*
@@ -900,9 +923,6 @@ void uiItemDecoratorR(uiLayout *layout,
std::optional<blender::StringRefNull> propname,
int index);
/** Flexible spacing. */
void uiItemSpacer(uiLayout *layout);
enum eButProgressType {
UI_BUT_PROGRESS_TYPE_BAR = 0,
UI_BUT_PROGRESS_TYPE_RING = 1,
@@ -913,24 +933,6 @@ void uiItemProgressIndicator(uiLayout *layout,
float factor,
eButProgressType progress_type);
/* popover */
void uiItemPopoverPanel_ptr(uiLayout *layout,
const bContext *C,
PanelType *pt,
std::optional<blender::StringRef> name_opt,
int icon);
void uiItemPopoverPanel(uiLayout *layout,
const bContext *C,
blender::StringRef panel_type,
std::optional<blender::StringRef> name_opt,
int icon);
void uiItemPopoverPanelFromGroup(uiLayout *layout,
bContext *C,
int space_id,
int region_id,
const char *context,
const char *category);
/**
* Level items.
*/

View File

@@ -9642,7 +9642,7 @@ static int ui_handle_button_event(bContext *C, const wmEvent *event, uiBut *but)
/* Cancel because this `but` handles all events and we don't want
* the parent button's update function to do anything.
*
* Causes issues with buttons defined by #uiItemFullR_with_popover. */
* Causes issues with buttons defined by #uiLayout::prop_with_popover. */
block->handle->menuretval = UI_RETURN_CANCEL;
}
else if (ui_but_is_editable_as_text(but)) {

View File

@@ -1366,17 +1366,17 @@ PointerRNA uiLayout::op(wmOperatorType *ot,
return ptr;
}
void uiItemFullOMenuHold_ptr(uiLayout *layout,
wmOperatorType *ot,
std::optional<StringRef> name,
int icon,
const wmOperatorCallContext context,
const eUI_Item_Flag flag,
const char *menu_id,
PointerRNA *r_opptr)
PointerRNA uiLayout::op_menu_hold(wmOperatorType *ot,
std::optional<StringRef> name,
int icon,
const wmOperatorCallContext context,
const eUI_Item_Flag flag,
const char *menu_id)
{
uiBut *but = uiItemFullO_ptr_ex(layout, ot, name, icon, context, flag, r_opptr);
PointerRNA ptr;
uiBut *but = uiItemFullO_ptr_ex(this, ot, name, icon, context, flag, &ptr);
UI_but_func_hold_set(but, ui_item_menu_hold, BLI_strdup(menu_id));
return ptr;
}
PointerRNA uiLayout::op(const blender::StringRefNull opname,
@@ -2410,19 +2410,18 @@ void uiLayout::prop(PointerRNA *ptr,
this->prop(ptr, prop, RNA_NO_INDEX, 0, flag, name, icon);
}
void uiItemFullR_with_popover(uiLayout *layout,
PointerRNA *ptr,
PropertyRNA *prop,
int index,
int value,
const eUI_Item_Flag flag,
const std::optional<StringRefNull> name,
int icon,
const char *panel_type)
void uiLayout::prop_with_popover(PointerRNA *ptr,
PropertyRNA *prop,
int index,
int value,
const eUI_Item_Flag flag,
const std::optional<StringRefNull> name,
int icon,
const char *panel_type)
{
uiBlock *block = layout->block();
uiBlock *block = this->block();
int i = block->buttons.size();
layout->prop(ptr, prop, index, value, flag, name, icon);
this->prop(ptr, prop, index, value, flag, name, icon);
for (; i < block->buttons.size(); i++) {
uiBut *but = block->buttons[i].get();
if (but->rnaprop == prop && ELEM(but->type, UI_BTYPE_MENU, UI_BTYPE_COLOR)) {
@@ -2432,7 +2431,7 @@ void uiItemFullR_with_popover(uiLayout *layout,
}
if (i == block->buttons.size()) {
const StringRefNull propname = RNA_property_identifier(prop);
ui_item_disabled(layout, panel_type);
ui_item_disabled(this, panel_type);
RNA_warning("property could not use a popover: %s.%s (%s)",
RNA_struct_identifier(ptr->type),
propname.c_str(),
@@ -2440,19 +2439,18 @@ void uiItemFullR_with_popover(uiLayout *layout,
}
}
void uiItemFullR_with_menu(uiLayout *layout,
PointerRNA *ptr,
PropertyRNA *prop,
int index,
int value,
const eUI_Item_Flag flag,
const std::optional<StringRefNull> name,
int icon,
const char *menu_type)
void uiLayout::prop_with_menu(PointerRNA *ptr,
PropertyRNA *prop,
int index,
int value,
const eUI_Item_Flag flag,
const std::optional<StringRefNull> name,
int icon,
const char *menu_type)
{
uiBlock *block = layout->block();
uiBlock *block = this->block();
int i = block->buttons.size();
layout->prop(ptr, prop, index, value, flag, name, icon);
this->prop(ptr, prop, index, value, flag, name, icon);
while (i < block->buttons.size()) {
uiBut *but = block->buttons[i].get();
if (but->rnaprop == prop && but->type == UI_BTYPE_MENU) {
@@ -2463,7 +2461,7 @@ void uiItemFullR_with_menu(uiLayout *layout,
}
if (i == block->buttons.size()) {
const StringRefNull propname = RNA_property_identifier(prop);
ui_item_disabled(layout, menu_type);
ui_item_disabled(this, menu_type);
RNA_warning("property could not use a menu: %s.%s (%s)",
RNA_struct_identifier(ptr->type),
propname.c_str(),
@@ -3022,25 +3020,25 @@ void uiItemDecoratorR(uiLayout *layout,
uiItemDecoratorR_prop(layout, ptr, prop, index);
}
void uiItemPopoverPanel_ptr(uiLayout *layout,
const bContext *C,
PanelType *pt,
const std::optional<StringRef> name_opt,
int icon)
void uiLayout::popover(const bContext *C,
PanelType *pt,
const std::optional<StringRef> name_opt,
int icon)
{
uiLayout *layout = this;
const StringRef name = name_opt.value_or(CTX_IFACE_(pt->translation_context, pt->label));
if (layout->root_->type == UI_LAYOUT_MENU && !icon) {
if (root_->type == UI_LAYOUT_MENU && !icon) {
icon = ICON_BLANK1;
}
const bContextStore *previous_ctx = CTX_store_get(C);
/* Set context for polling (and panel header drawing). */
CTX_store_set(const_cast<bContext *>(C), layout->context_);
CTX_store_set(const_cast<bContext *>(C), context_);
const bool ok = (pt->poll == nullptr) || pt->poll(C, pt);
if (ok && (pt->draw_header != nullptr)) {
layout = &layout->row(true);
layout = &this->row(true);
Panel panel{};
Panel_Runtime panel_runtime{};
panel.runtime = &panel_runtime;
@@ -3060,26 +3058,21 @@ void uiItemPopoverPanel_ptr(uiLayout *layout,
}
}
void uiItemPopoverPanel(uiLayout *layout,
const bContext *C,
const StringRef panel_type,
std::optional<blender::StringRef> name_opt,
int icon)
void uiLayout::popover(const bContext *C,
const StringRef panel_type,
std::optional<blender::StringRef> name_opt,
int icon)
{
PanelType *pt = WM_paneltype_find(panel_type, true);
if (pt == nullptr) {
RNA_warning("Panel type not found '%s'", std::string(panel_type).c_str());
return;
}
uiItemPopoverPanel_ptr(layout, C, pt, name_opt, icon);
this->popover(C, pt, name_opt, icon);
}
void uiItemPopoverPanelFromGroup(uiLayout *layout,
bContext *C,
int space_id,
int region_id,
const char *context,
const char *category)
void uiLayout::popover_group(
bContext *C, int space_id, int region_id, const char *context, const char *category)
{
SpaceType *st = BKE_spacetype_from_id(space_id);
if (st == nullptr) {
@@ -3098,7 +3091,7 @@ void uiItemPopoverPanelFromGroup(uiLayout *layout,
if (/* (*context == '\0') || */ STREQ(pt->context, context)) {
if ((*category == '\0') || STREQ(pt->category, category)) {
if (pt->poll == nullptr || pt->poll(C, pt)) {
uiItemPopoverPanel_ptr(layout, C, pt, std::nullopt, ICON_NONE);
this->popover(C, pt, std::nullopt, ICON_NONE);
}
}
}
@@ -3314,9 +3307,9 @@ void uiItemProgressIndicator(uiLayout *layout,
progress_bar->progress_factor = factor;
}
void uiItemSpacer(uiLayout *layout)
void uiLayout::separator_spacer()
{
uiBlock *block = layout->block();
uiBlock *block = this->block();
const bool is_popup = ui_block_is_popup_any(block);
if (is_popup) {
@@ -3329,7 +3322,7 @@ void uiItemSpacer(uiLayout *layout)
return;
}
UI_block_layout_set_current(block, layout);
UI_block_layout_set_current(block, this);
uiDefBut(block,
UI_BTYPE_SEPR_SPACER,
0,
@@ -5993,7 +5986,7 @@ static void ui_paneltype_draw_impl(bContext *C, PanelType *pt, uiLayout *layout,
/* This check may be paranoid, this function might run outside the context of a popup or can run
* in popovers that are not supposed to support refreshing, see #ui_popover_create_block. */
if (block->handle && block->handle->region) {
/* Allow popovers to contain collapsible sections, see #uiItemPopoverPanel. */
/* Allow popovers to contain collapsible sections, see #uiLayout::popover. */
UI_popup_dummy_panel_set(block->handle->region, block);
}

View File

@@ -53,7 +53,7 @@ void template_asset_shelf_popover(uiLayout &layout,
ed::asset::shelf::ensure_asset_library_fetched(C, *shelf_type);
uiItemPopoverPanel(row, &C, "ASSETSHELF_PT_popover_panel", name, icon);
row->popover(&C, "ASSETSHELF_PT_popover_panel", name, icon);
uiBut *but = block->buttons.last().get();
if (use_preview_icon) {
ui_def_but_icon(but, icon, UI_HAS_ICON | UI_BUT_ICON_PREVIEW);

View File

@@ -351,7 +351,7 @@ static void draw_export_controls(
if (valid) {
uiLayout *row = &layout->row(false);
row->emboss_set(blender::ui::EmbossType::None);
uiItemPopoverPanel(row, C, "WM_PT_operator_presets", "", ICON_PRESET);
row->popover(C, "WM_PT_operator_presets", "", ICON_PRESET);
PointerRNA op_ptr = row->op("COLLECTION_OT_exporter_export", "", ICON_EXPORT);
RNA_int_set(&op_ptr, "index", index);
}

View File

@@ -1252,7 +1252,7 @@ static void buttons_panel_context_draw(const bContext *C, Panel *panel)
uiLayout *pin_row = &row->row(false);
pin_row->alignment_set(blender::ui::LayoutAlign::Right);
uiItemSpacer(pin_row);
pin_row->separator_spacer();
pin_row->emboss_set(blender::ui::EmbossType::None);
pin_row->op(
"BUTTONS_OT_toggle_pin", "", (sbuts->flag & SB_PIN_CONTEXT) ? ICON_PINNED : ICON_UNPINNED);

View File

@@ -659,7 +659,7 @@ static void spreadsheet_footer_region_draw(const bContext *C, ARegion *region)
1,
0,
style);
uiItemSpacer(layout);
layout->separator_spacer();
layout->alignment_set(blender::ui::LayoutAlign::Right);
layout->label(stats_str, ICON_NONE);
UI_block_layout_resolve(block, nullptr, nullptr);

View File

@@ -185,7 +185,7 @@ static void rna_uiItemR_with_popover(uiLayout *layout,
/* Get translated name (label). */
std::optional<StringRefNull> text = rna_translate_ui_text(
name, text_ctxt, nullptr, prop, translate);
uiItemFullR_with_popover(layout, ptr, prop, -1, 0, flag, text, icon, panel_type);
layout->prop_with_popover(ptr, prop, -1, 0, flag, text, icon, panel_type);
}
static void rna_uiItemR_with_menu(uiLayout *layout,
@@ -216,7 +216,7 @@ static void rna_uiItemR_with_menu(uiLayout *layout,
/* Get translated name (label). */
std::optional<StringRefNull> text = rna_translate_ui_text(
name, text_ctxt, nullptr, prop, translate);
uiItemFullR_with_menu(layout, ptr, prop, -1, 0, flag, text, icon, menu_type);
layout->prop_with_menu(ptr, prop, -1, 0, flag, text, icon, menu_type);
}
static void rna_uiItemMenuEnumR(uiLayout *layout,
@@ -418,9 +418,7 @@ static PointerRNA rna_uiItemOMenuHold(uiLayout *layout,
flag |= UI_ITEM_O_DEPRESS;
}
PointerRNA opptr;
uiItemFullOMenuHold_ptr(layout, ot, text, icon, layout->operator_context(), flag, menu, &opptr);
return opptr;
return layout->op_menu_hold(ot, text, icon, layout->operator_context(), flag, menu);
}
static void rna_uiItemsEnumO(uiLayout *layout,
@@ -516,7 +514,7 @@ static void rna_uiItemPopoverPanel(uiLayout *layout,
icon = icon_value;
}
uiItemPopoverPanel(layout, C, panel_type, text, icon);
layout->popover(C, panel_type, text, icon);
}
static void rna_uiItemPopoverPanelFromGroup(uiLayout *layout,
@@ -526,7 +524,7 @@ static void rna_uiItemPopoverPanelFromGroup(uiLayout *layout,
const char *context,
const char *category)
{
uiItemPopoverPanelFromGroup(layout, C, space_id, region_id, context, category);
layout->popover_group(C, space_id, region_id, context, category);
}
static void rna_uiItemProgress(uiLayout *layout,
@@ -558,6 +556,11 @@ static void rna_uiLayoutContextStringSet(uiLayout *layout, const char *name, con
layout->context_string_set(name, value);
}
static void rna_uiLayoutSeparatorSpacer(uiLayout *layout)
{
layout->separator_spacer();
}
static void rna_uiTemplateID(uiLayout *layout,
bContext *C,
PointerRNA *ptr,
@@ -1595,7 +1598,7 @@ void RNA_api_ui_layout(StructRNA *srna)
"Type",
"The type of the separator");
func = RNA_def_function(srna, "separator_spacer", "uiItemSpacer");
func = RNA_def_function(srna, "separator_spacer", "rna_uiLayoutSeparatorSpacer");
RNA_def_function_ui_description(
func, "Item. Inserts horizontal spacing empty space into the layout between items.");