UI: Improved Operator Confirmations

Allow our current simple confirmations to be customized and extended
with callbacks, allowing different behaviors, different icons, and
showing more information. Makes no changes to any existing dialogs.

Pull Request: https://projects.blender.org/blender/blender/pulls/104670
This commit is contained in:
Harley Acheson 2024-01-04 18:42:21 +01:00 committed by Harley Acheson
parent 00ef4f9309
commit f40d4e0bda
2 changed files with 232 additions and 0 deletions

View File

@ -919,6 +919,31 @@ struct wmTimer {
bool sleep;
};
typedef enum wmWarningSize {
WM_WARNING_SIZE_SMALL = 0,
WM_WARNING_SIZE_LARGE,
} wmWarningSize;
typedef enum wmWarningPosition {
WM_WARNING_POSITION_MOUSE = 0,
WM_WARNING_POSITION_CENTER,
} wmWarningPosition;
typedef struct wmWarningDetails {
char title[1024];
char message[1024];
char message2[1024];
char confirm_button[256];
char cancel_button[256];
int icon;
wmWarningSize size;
wmWarningPosition position;
bool confirm_default;
bool cancel_default;
bool mouse_move_quit;
bool red_alert;
} wmWarningDetails;
/**
* Communication/status data owned by the wmJob, and passed to the worker code when calling
* `startjob` callback.
@ -1046,6 +1071,11 @@ struct wmOperatorType {
*/
std::string (*get_description)(bContext *C, wmOperatorType *ot, PointerRNA *ptr);
/**
* If using WM_operator_confirm the following can override all parts of the dialog.
*/
void (*warning)(struct bContext *C, struct wmOperator *, wmWarningDetails *warning);
/** RNA for properties */
StructRNA *srna;

View File

@ -1181,6 +1181,203 @@ int WM_enum_search_invoke(bContext *C, wmOperator *op, const wmEvent * /*event*/
return OPERATOR_INTERFACE;
}
static void wm_operator_block_cancel(bContext *C, void *arg_op, void *arg_block)
{
wmOperator *op = static_cast<wmOperator *>(arg_op);
uiBlock *block = static_cast<uiBlock *>(arg_block);
UI_popup_block_close(C, CTX_wm_window(C), block);
WM_redraw_windows(C);
if (op) {
if (op->type->cancel) {
op->type->cancel(C, op);
}
WM_operator_free(op);
}
}
static void wm_operator_block_confirm(bContext *C, void *arg_op, void *arg_block)
{
wmOperator *op = static_cast<wmOperator *>(arg_op);
uiBlock *block = static_cast<uiBlock *>(arg_block);
UI_popup_block_close(C, CTX_wm_window(C), block);
WM_redraw_windows(C);
if (op) {
WM_operator_call_ex(C, op, true);
}
}
static uiBlock *wm_block_confirm_create(bContext *C, ARegion *region, void *arg_op)
{
wmOperator *op = static_cast<wmOperator *>(arg_op);
wmWarningDetails warning = {{0}};
STRNCPY(warning.title, WM_operatortype_description(C, op->type, op->ptr).c_str());
STRNCPY(warning.confirm_button, WM_operatortype_name(op->type, op->ptr).c_str());
STRNCPY(warning.cancel_button, IFACE_("Cancel"));
warning.icon = ALERT_ICON_WARNING;
warning.size = WM_WARNING_SIZE_SMALL;
warning.position = WM_WARNING_POSITION_MOUSE;
warning.confirm_default = true;
warning.cancel_default = false;
warning.mouse_move_quit = false;
warning.red_alert = false;
/* uiBlock.flag */
int block_flags = UI_BLOCK_KEEP_OPEN | UI_BLOCK_NO_WIN_CLIP | UI_BLOCK_NUMSELECT;
if (op->type->warning) {
op->type->warning(C, op, &warning);
}
if (warning.mouse_move_quit) {
block_flags |= UI_BLOCK_MOVEMOUSE_QUIT;
}
if (warning.icon < ALERT_ICON_WARNING || warning.icon >= ALERT_ICON_MAX) {
warning.icon = ALERT_ICON_QUESTION;
}
uiBlock *block = UI_block_begin(C, region, __func__, UI_EMBOSS);
UI_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP);
UI_block_flag_enable(block, block_flags);
const uiStyle *style = UI_style_get_dpi();
int text_width = MAX2(
120 * UI_SCALE_FAC,
BLF_width(style->widget.uifont_id, warning.title, ARRAY_SIZE(warning.title)));
if (warning.message[0]) {
text_width = MAX2(
text_width,
BLF_width(style->widget.uifont_id, warning.message, ARRAY_SIZE(warning.message)));
}
if (warning.message2[0]) {
text_width = MAX2(
text_width,
BLF_width(style->widget.uifont_id, warning.message2, ARRAY_SIZE(warning.message2)));
}
const bool small = warning.size == WM_WARNING_SIZE_SMALL;
const int padding = (small ? 7 : 14) * UI_SCALE_FAC;
const short icon_size = (small ? (warning.message[0] ? 48 : 32) : 64) * UI_SCALE_FAC;
const int dialog_width = icon_size + text_width + (style->columnspace * 2.5);
const float split_factor = (float)icon_size / (float)(dialog_width - style->columnspace);
uiLayout *block_layout = UI_block_layout(
block, UI_LAYOUT_VERTICAL, UI_LAYOUT_PANEL, 0, 0, dialog_width, UI_UNIT_Y, 0, style);
/* Split layout to put alert icon on left side. */
uiLayout *split_block = uiLayoutSplit(block_layout, split_factor, false);
/* Alert icon on the left. */
uiLayout *layout = uiLayoutRow(split_block, true);
/* Using 'align_left' with 'row' avoids stretching the icon along the width of column. */
uiLayoutSetAlignment(layout, UI_LAYOUT_ALIGN_LEFT);
uiDefButAlert(block, warning.icon, 0, 0, icon_size, icon_size);
/* The rest of the content on the right. */
layout = uiLayoutColumn(split_block, true);
if (warning.title[0]) {
if (!warning.message[0]) {
uiItemS(layout);
}
uiItemL_ex(layout, warning.title, ICON_NONE, true, false);
}
if (warning.message[0]) {
uiItemL(layout, warning.message, ICON_NONE);
}
if (warning.message2[0]) {
uiItemL(layout, warning.message2, ICON_NONE);
}
uiItemS_ex(layout, small ? 0.5f : 4.0f);
/* Buttons. */
#ifdef _WIN32
const bool windows_layout = true;
#else
const bool windows_layout = false;
#endif
uiBut *confirm = nullptr;
uiBut *cancel = nullptr;
int height = UI_UNIT_Y;
uiLayout *split = uiLayoutSplit(small ? block_layout : layout, 0.0f, true);
uiLayoutSetScaleY(split, small ? 1.1f : 1.2f);
uiLayoutColumn(split, false);
if (windows_layout) {
confirm = uiDefIconTextBut(block,
UI_BTYPE_BUT,
0,
0,
warning.confirm_button,
0,
0,
0,
height,
0,
0,
0,
0,
0,
nullptr);
uiLayoutColumn(split, false);
}
cancel = uiDefIconTextBut(
block, UI_BTYPE_BUT, 0, 0, warning.cancel_button, 0, 0, 0, height, 0, 0, 0, 0, 0, nullptr);
if (!windows_layout) {
uiLayoutColumn(split, false);
confirm = uiDefIconTextBut(block,
UI_BTYPE_BUT,
0,
0,
warning.confirm_button,
0,
0,
0,
height,
0,
0,
0,
0,
0,
nullptr);
}
UI_block_func_set(block, nullptr, nullptr, nullptr);
UI_but_func_set(confirm, wm_operator_block_confirm, op, block);
UI_but_func_set(cancel, wm_operator_block_cancel, op, block);
UI_but_drawflag_disable(confirm, UI_BUT_TEXT_LEFT);
UI_but_drawflag_disable(cancel, UI_BUT_TEXT_LEFT);
if (warning.red_alert) {
UI_but_flag_enable(confirm, UI_BUT_REDALERT);
}
else {
if (warning.cancel_default) {
UI_but_flag_enable(cancel, UI_BUT_ACTIVE_DEFAULT);
}
else if (warning.confirm_default) {
UI_but_flag_enable(confirm, UI_BUT_ACTIVE_DEFAULT);
}
}
if (warning.position == WM_WARNING_POSITION_MOUSE) {
int bounds_offset[2];
bounds_offset[0] = uiLayoutGetWidth(layout) * (windows_layout ? -0.33f : -0.66f);
bounds_offset[1] = UI_UNIT_Y * (warning.message[0] ? 3.1 : 2.5);
UI_block_bounds_set_popup(block, padding, bounds_offset);
}
else if (warning.position == WM_WARNING_POSITION_CENTER) {
UI_block_bounds_set_centered(block, padding);
}
return block;
}
int WM_operator_confirm_message_ex(bContext *C,
wmOperator *op,
const char *title,
@ -1208,6 +1405,11 @@ int WM_operator_confirm_message_ex(bContext *C,
int WM_operator_confirm_message(bContext *C, wmOperator *op, const char *message)
{
if (op->type->warning) {
UI_popup_block_invoke(C, wm_block_confirm_create, op, nullptr);
return OPERATOR_RUNNING_MODAL;
}
return WM_operator_confirm_message_ex(
C, op, IFACE_("OK?"), ICON_QUESTION, message, WM_OP_EXEC_REGION_WIN);
}