UI: add UILayout.template_popup_confirm(..) function
This makes it possible for popups to have their confirm & cancel buttons defined in the operator's draw callback. When used with popups created by: `WindowManager::invoke_props_dialog()` to override the default confirm/cancel buttons. In the case of `WindowManager::popover(..)` & `bpy.ops.wm.call_panel()` this can be used to add confirm/cancel buttons. Details: - When the confirm or cancel text argument is a blank string the button isn't shown, making it possible to only show a single button. - The template is similar to UILayout::operator in that it returns the operator properties for the confirm action. - MS-Windows alternate ordering of Confirm/Cancel is followed. Needed to resolve #124098. Ref !124139
This commit is contained in:
parent
bdf06e6d82
commit
850e715682
@ -674,6 +674,11 @@ bool UI_but_is_utf8(const uiBut *but);
|
|||||||
bool UI_block_is_empty_ex(const uiBlock *block, bool skip_title);
|
bool UI_block_is_empty_ex(const uiBlock *block, bool skip_title);
|
||||||
bool UI_block_is_empty(const uiBlock *block);
|
bool UI_block_is_empty(const uiBlock *block);
|
||||||
bool UI_block_can_add_separator(const uiBlock *block);
|
bool UI_block_can_add_separator(const uiBlock *block);
|
||||||
|
/**
|
||||||
|
* Return true when the block has a default button.
|
||||||
|
* Use this for popups to detect when pressing "Return" will run an action.
|
||||||
|
*/
|
||||||
|
bool UI_block_has_active_default_button(const uiBlock *block);
|
||||||
|
|
||||||
uiList *UI_list_find_mouse_over(const ARegion *region, const wmEvent *event);
|
uiList *UI_list_find_mouse_over(const ARegion *region, const wmEvent *event);
|
||||||
|
|
||||||
@ -801,6 +806,33 @@ void UI_popup_block_ex(bContext *C,
|
|||||||
uiBlockCancelFunc cancel_func,
|
uiBlockCancelFunc cancel_func,
|
||||||
void *arg,
|
void *arg,
|
||||||
wmOperator *op);
|
wmOperator *op);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true when #UI_popup_block_template_confirm and related functions are supported.
|
||||||
|
*/
|
||||||
|
bool UI_popup_block_template_confirm_is_supported(const uiBlock *block);
|
||||||
|
/**
|
||||||
|
* Create confirm & cancel buttons in a popup using callback functions.
|
||||||
|
*/
|
||||||
|
void UI_popup_block_template_confirm(uiBlock *block,
|
||||||
|
bool cancel_default,
|
||||||
|
blender::FunctionRef<uiBut *()> confirm_fn,
|
||||||
|
blender::FunctionRef<uiBut *()> cancel_fn);
|
||||||
|
/**
|
||||||
|
* Create confirm & cancel buttons in a popup using an operator.
|
||||||
|
*
|
||||||
|
* \param confirm_text: The text to confirm, null for default text or an empty string to hide.
|
||||||
|
* \param cancel_text: The text to cancel, null for default text or an empty string to hide.
|
||||||
|
* \param r_ptr: The pointer for operator properties, set a "confirm" button has been created.
|
||||||
|
*/
|
||||||
|
void UI_popup_block_template_confirm_op(uiLayout *layout,
|
||||||
|
wmOperatorType *ot,
|
||||||
|
const char *confirm_text,
|
||||||
|
const char *cancel_text,
|
||||||
|
const int icon,
|
||||||
|
bool cancel_default,
|
||||||
|
PointerRNA *r_ptr);
|
||||||
|
|
||||||
#if 0 /* UNUSED */
|
#if 0 /* UNUSED */
|
||||||
void uiPupBlockOperator(bContext *C,
|
void uiPupBlockOperator(bContext *C,
|
||||||
uiBlockCreateFunc func,
|
uiBlockCreateFunc func,
|
||||||
|
@ -694,6 +694,16 @@ bool UI_block_can_add_separator(const uiBlock *block)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool UI_block_has_active_default_button(const uiBlock *block)
|
||||||
|
{
|
||||||
|
LISTBASE_FOREACH (const uiBut *, but, &block->buttons) {
|
||||||
|
if ((but->flag & UI_BUT_ACTIVE_DEFAULT) && ((but->flag & UI_HIDDEN) == 0)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/** \} */
|
/** \} */
|
||||||
|
|
||||||
/* -------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------- */
|
||||||
|
@ -712,6 +712,142 @@ void UI_popup_block_ex(bContext *C,
|
|||||||
WM_event_add_mousemove(window);
|
WM_event_add_mousemove(window);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void popup_block_template_close_cb(bContext *C, void *arg1, void * /*arg2*/)
|
||||||
|
{
|
||||||
|
uiBlock *block = (uiBlock *)arg1;
|
||||||
|
|
||||||
|
uiPopupBlockHandle *handle = block->handle;
|
||||||
|
if (handle == nullptr) {
|
||||||
|
printf("Error: used outside of a popup!\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
wmWindow *win = CTX_wm_window(C);
|
||||||
|
UI_popup_menu_retval_set(block, UI_RETURN_CANCEL, true);
|
||||||
|
|
||||||
|
if (handle->cancel_func) {
|
||||||
|
handle->cancel_func(C, handle->popup_arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
UI_popup_block_close(C, win, block);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UI_popup_block_template_confirm_is_supported(const uiBlock *block)
|
||||||
|
{
|
||||||
|
if (block->flag & (UI_BLOCK_KEEP_OPEN | UI_BLOCK_POPOVER)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UI_popup_block_template_confirm(uiBlock *block,
|
||||||
|
const bool cancel_default,
|
||||||
|
blender::FunctionRef<uiBut *()> confirm_fn,
|
||||||
|
blender::FunctionRef<uiBut *()> cancel_fn)
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
const bool windows_layout = true;
|
||||||
|
#else
|
||||||
|
const bool windows_layout = false;
|
||||||
|
#endif
|
||||||
|
blender::FunctionRef<uiBut *()> *button_functions[2];
|
||||||
|
if (windows_layout) {
|
||||||
|
ARRAY_SET_ITEMS(button_functions, &confirm_fn, &cancel_fn);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ARRAY_SET_ITEMS(button_functions, &cancel_fn, &confirm_fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < ARRAY_SIZE(button_functions); i++) {
|
||||||
|
blender::FunctionRef<uiBut *()> *but_fn = button_functions[i];
|
||||||
|
if (uiBut *but = (*but_fn)()) {
|
||||||
|
const bool is_cancel = (but_fn == &cancel_fn);
|
||||||
|
if ((block->flag & UI_BLOCK_LOOP) == 0) {
|
||||||
|
UI_but_func_set(but, popup_block_template_close_cb, block, nullptr);
|
||||||
|
}
|
||||||
|
if (is_cancel == cancel_default) {
|
||||||
|
/* An active button shouldn't exist, if it does, never set another. */
|
||||||
|
if (!UI_block_has_active_default_button(block)) {
|
||||||
|
UI_but_flag_enable(but, UI_BUT_ACTIVE_DEFAULT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UI_popup_block_template_confirm_op(uiLayout *layout,
|
||||||
|
wmOperatorType *ot,
|
||||||
|
const char *confirm_text,
|
||||||
|
const char *cancel_text,
|
||||||
|
const int icon,
|
||||||
|
bool cancel_default,
|
||||||
|
PointerRNA *r_ptr)
|
||||||
|
{
|
||||||
|
uiBlock *block = uiLayoutGetBlock(layout);
|
||||||
|
|
||||||
|
if (confirm_text == nullptr) {
|
||||||
|
confirm_text = IFACE_("OK");
|
||||||
|
}
|
||||||
|
if (cancel_text == nullptr) {
|
||||||
|
cancel_text = IFACE_("Cancel");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Use a split so both buttons are the same size. */
|
||||||
|
const bool show_confirm = confirm_text[0] != '\0';
|
||||||
|
const bool show_cancel = cancel_text[0] != '\0';
|
||||||
|
uiLayout *row = (show_confirm && show_cancel) ? uiLayoutSplit(layout, 0.5f, false) : layout;
|
||||||
|
|
||||||
|
/* When only one button is shown, make it default. */
|
||||||
|
if (!show_confirm) {
|
||||||
|
cancel_default = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto confirm_fn = [&row, &ot, &confirm_text, &icon, &r_ptr, &show_confirm]() -> uiBut * {
|
||||||
|
if (!show_confirm) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
uiBlock *block = uiLayoutGetBlock(row);
|
||||||
|
const uiBut *but_ref = (uiBut *)block->buttons.last;
|
||||||
|
uiItemFullO_ptr(row,
|
||||||
|
ot,
|
||||||
|
confirm_text,
|
||||||
|
icon,
|
||||||
|
nullptr,
|
||||||
|
uiLayoutGetOperatorContext(row),
|
||||||
|
UI_ITEM_NONE,
|
||||||
|
r_ptr);
|
||||||
|
|
||||||
|
if (but_ref == block->buttons.last) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return static_cast<uiBut *>(block->buttons.last);
|
||||||
|
};
|
||||||
|
|
||||||
|
auto cancel_fn = [&row, &cancel_text, &show_cancel]() -> uiBut * {
|
||||||
|
if (!show_cancel) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
uiBlock *block = uiLayoutGetBlock(row);
|
||||||
|
uiBut *but = uiDefIconTextBut(block,
|
||||||
|
UI_BTYPE_BUT,
|
||||||
|
1,
|
||||||
|
ICON_NONE,
|
||||||
|
cancel_text,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
UI_UNIT_X, /* Ignored, as a split is used. */
|
||||||
|
UI_UNIT_Y,
|
||||||
|
nullptr,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
"");
|
||||||
|
|
||||||
|
return but;
|
||||||
|
};
|
||||||
|
|
||||||
|
UI_popup_block_template_confirm(block, cancel_default, confirm_fn, cancel_fn);
|
||||||
|
}
|
||||||
|
|
||||||
#if 0 /* UNUSED */
|
#if 0 /* UNUSED */
|
||||||
void uiPupBlockOperator(bContext *C,
|
void uiPupBlockOperator(bContext *C,
|
||||||
uiBlockCreateFunc func,
|
uiBlockCreateFunc func,
|
||||||
|
@ -976,6 +976,46 @@ void rna_uiTemplateAssetShelfPopover(uiLayout *layout,
|
|||||||
blender::ui::template_asset_shelf_popover(*layout, *C, asset_shelf_id, name, icon);
|
blender::ui::template_asset_shelf_popover(*layout, *C, asset_shelf_id, name, icon);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PointerRNA rna_uiTemplatePopupConfirm(uiLayout *layout,
|
||||||
|
ReportList *reports,
|
||||||
|
const char *opname,
|
||||||
|
const char *text,
|
||||||
|
const char *text_ctxt,
|
||||||
|
bool translate,
|
||||||
|
int icon,
|
||||||
|
const char *cancel_text,
|
||||||
|
bool cancel_default)
|
||||||
|
{
|
||||||
|
PointerRNA opptr = PointerRNA_NULL;
|
||||||
|
|
||||||
|
/* This allows overriding buttons in `WM_operator_props_dialog_popup` and other popups. */
|
||||||
|
wmOperatorType *ot = nullptr;
|
||||||
|
if (opname[0]) {
|
||||||
|
/* Confirming is optional. */
|
||||||
|
ot = WM_operatortype_find(opname, false); /* print error next */
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
text = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opname[0] ? (!ot || !ot->srna) : false) {
|
||||||
|
RNA_warning("%s '%s'", ot ? "operator missing srna" : "unknown operator", opname);
|
||||||
|
}
|
||||||
|
else if (!UI_popup_block_template_confirm_is_supported(uiLayoutGetBlock(layout))) {
|
||||||
|
BKE_reportf(reports, RPT_ERROR, "template_popup_confirm used outside of a popup");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
text = rna_translate_ui_text(text, text_ctxt, nullptr, nullptr, translate);
|
||||||
|
if (cancel_text && cancel_text[0]) {
|
||||||
|
cancel_text = rna_translate_ui_text(cancel_text, text_ctxt, nullptr, nullptr, translate);
|
||||||
|
}
|
||||||
|
|
||||||
|
UI_popup_block_template_confirm_op(
|
||||||
|
layout, ot, text, cancel_text, icon, cancel_default, &opptr);
|
||||||
|
}
|
||||||
|
return opptr;
|
||||||
|
}
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
static void api_ui_item_common_heading(FunctionRNA *func)
|
static void api_ui_item_common_heading(FunctionRNA *func)
|
||||||
@ -2324,6 +2364,25 @@ void RNA_api_ui_layout(StructRNA *srna)
|
|||||||
RNA_def_property_ui_text(parm, "Icon", "Override automatic icon of the item");
|
RNA_def_property_ui_text(parm, "Icon", "Override automatic icon of the item");
|
||||||
parm = RNA_def_property(func, "icon_value", PROP_INT, PROP_UNSIGNED);
|
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");
|
RNA_def_property_ui_text(parm, "Icon Value", "Override automatic icon of the item");
|
||||||
|
|
||||||
|
/* A version of `operator` that defines a [Cancel, Confirm] pair of buttons. */
|
||||||
|
func = RNA_def_function(srna, "template_popup_confirm", "rna_uiTemplatePopupConfirm");
|
||||||
|
api_ui_item_op_common(func);
|
||||||
|
parm = RNA_def_string(func,
|
||||||
|
"cancel_text",
|
||||||
|
nullptr,
|
||||||
|
0,
|
||||||
|
"",
|
||||||
|
"Optional text to use for the cancel, not shown when an empty string");
|
||||||
|
RNA_def_boolean(func, "cancel_default", false, "", "Cancel button by default");
|
||||||
|
RNA_def_function_ui_description(
|
||||||
|
func, "Add confirm & cancel buttons into a popup which will close the popup when pressed");
|
||||||
|
RNA_def_function_flag(func, FUNC_USE_REPORTS);
|
||||||
|
|
||||||
|
parm = RNA_def_pointer(
|
||||||
|
func, "properties", "OperatorProperties", "", "Operator properties to fill in");
|
||||||
|
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED | PARM_RNAPTR);
|
||||||
|
RNA_def_function_return(func, parm);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -1589,8 +1589,10 @@ static uiBlock *wm_block_dialog_create(bContext *C, ARegion *region, void *user_
|
|||||||
const bool windows_layout = false;
|
const bool windows_layout = false;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* New column so as not to interfere with custom layouts, see: #26436. */
|
/* Check there are no active default buttons, allowing a dialog to define it's own
|
||||||
{
|
* confirmation buttons which are shown instead of these, see: #124098. */
|
||||||
|
if (!UI_block_has_active_default_button(uiLayoutGetBlock(layout))) {
|
||||||
|
/* New column so as not to interfere with custom layouts, see: #26436. */
|
||||||
uiLayout *col = uiLayoutColumn(layout, false);
|
uiLayout *col = uiLayoutColumn(layout, false);
|
||||||
uiBlock *col_block = uiLayoutGetBlock(col);
|
uiBlock *col_block = uiLayoutGetBlock(col);
|
||||||
uiBut *confirm_but;
|
uiBut *confirm_but;
|
||||||
|
Loading…
Reference in New Issue
Block a user