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:
parent
00ef4f9309
commit
f40d4e0bda
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue