diff --git a/source/blender/windowmanager/WM_types.hh b/source/blender/windowmanager/WM_types.hh index 6871fb0369b..7dfe27b742c 100644 --- a/source/blender/windowmanager/WM_types.hh +++ b/source/blender/windowmanager/WM_types.hh @@ -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; diff --git a/source/blender/windowmanager/intern/wm_operators.cc b/source/blender/windowmanager/intern/wm_operators.cc index 0763ec88b88..0dc01dcf9b3 100644 --- a/source/blender/windowmanager/intern/wm_operators.cc +++ b/source/blender/windowmanager/intern/wm_operators.cc @@ -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(arg_op); + uiBlock *block = static_cast(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(arg_op); + uiBlock *block = static_cast(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(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); }