diff --git a/release/datafiles/icons_svg/cancel_large.svg b/release/datafiles/icons_svg/cancel_large.svg
index 8d015987075..6f1459c3881 100644
--- a/release/datafiles/icons_svg/cancel_large.svg
+++ b/release/datafiles/icons_svg/cancel_large.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/source/blender/editors/include/UI_interface.hh b/source/blender/editors/include/UI_interface.hh
index e90a435ec81..5bd604feec0 100644
--- a/source/blender/editors/include/UI_interface.hh
+++ b/source/blender/editors/include/UI_interface.hh
@@ -292,3 +292,5 @@ blender::ui::AbstractTreeView *UI_block_add_view(
uiBlock &block,
blender::StringRef idname,
std::unique_ptr tree_view);
+
+void UI_alert(bContext *C, std::string title, std::string message, eAlertIcon icon, bool compact);
diff --git a/source/blender/editors/interface/interface.cc b/source/blender/editors/interface/interface.cc
index 58cdbc6f2ae..8421471bf7b 100644
--- a/source/blender/editors/interface/interface.cc
+++ b/source/blender/editors/interface/interface.cc
@@ -5105,6 +5105,11 @@ uiBut *uiDefButAlert(uiBlock *block, int icon, int x, int y, short width, short
{
ImBuf *ibuf = UI_icon_alert_imbuf_get((eAlertIcon)icon, float(width));
if (ibuf) {
+ if (icon == ALERT_ICON_ERROR) {
+ uchar color[4];
+ UI_GetThemeColor4ubv(TH_ERROR, color);
+ return uiDefButImage(block, ibuf, x, y, ibuf->x, ibuf->y, color);
+ }
bTheme *btheme = UI_GetTheme();
return uiDefButImage(block, ibuf, x, y, ibuf->x, ibuf->y, btheme->tui.wcol_menu_back.text);
}
diff --git a/source/blender/editors/interface/interface_intern.hh b/source/blender/editors/interface/interface_intern.hh
index 257f822a4aa..a80f03ee39a 100644
--- a/source/blender/editors/interface/interface_intern.hh
+++ b/source/blender/editors/interface/interface_intern.hh
@@ -578,6 +578,8 @@ struct uiBlockDynamicListener {
void (*listener_func)(const wmRegionListenerParams *params);
};
+enum class uiBlockAlertLevel : int8_t { None, Info, Success, Warning, Error };
+
struct uiBlock {
uiBlock *next, *prev;
@@ -609,6 +611,8 @@ struct uiBlock {
rctf rect;
float aspect;
+ uiBlockAlertLevel alert_level = uiBlockAlertLevel::None;
+
/** Unique hash used to implement popup menu memory. */
uint puphash;
diff --git a/source/blender/editors/interface/interface_layout.cc b/source/blender/editors/interface/interface_layout.cc
index bfcdc74b453..68f82e3a73c 100644
--- a/source/blender/editors/interface/interface_layout.cc
+++ b/source/blender/editors/interface/interface_layout.cc
@@ -6115,6 +6115,22 @@ uiLayout *uiItemsAlertBox(uiBlock *block,
0,
style);
+ if (icon == ALERT_ICON_INFO) {
+ block->alert_level = uiBlockAlertLevel::Info;
+ }
+ else if (icon == ALERT_ICON_WARNING) {
+ block->alert_level = uiBlockAlertLevel::Warning;
+ }
+ else if (icon == ALERT_ICON_QUESTION) {
+ block->alert_level = uiBlockAlertLevel::Warning;
+ }
+ else if (icon == ALERT_ICON_ERROR) {
+ block->alert_level = uiBlockAlertLevel::Error;
+ }
+ else {
+ block->alert_level = uiBlockAlertLevel::None;
+ }
+
/* Split layout to put alert icon on left side. */
uiLayout *split_block = &block_layout.split(split_factor, false);
diff --git a/source/blender/editors/interface/interface_widgets.cc b/source/blender/editors/interface/interface_widgets.cc
index 9c5434cd595..9ea6c66b3ec 100644
--- a/source/blender/editors/interface/interface_widgets.cc
+++ b/source/blender/editors/interface/interface_widgets.cc
@@ -5359,6 +5359,40 @@ static void ui_draw_clip_tri(uiBlock *block, const rcti *rect, uiWidgetType *wt)
}
}
+static void ui_draw_dialog_alert(uiBlock *block, const rcti *rect)
+{
+ if (block->alert_level != uiBlockAlertLevel::Error) {
+ return;
+ }
+
+ float color[4];
+ switch (block->alert_level) {
+ case uiBlockAlertLevel::Error:
+ UI_GetThemeColor4fv(TH_ERROR, color);
+ break;
+ case uiBlockAlertLevel::Warning:
+ UI_GetThemeColor4fv(TH_WARNING, color);
+ break;
+ case uiBlockAlertLevel::Success:
+ UI_GetThemeColor4fv(TH_SUCCESS, color);
+ break;
+ default:
+ UI_GetThemeColor4fv(TH_INFO, color);
+ }
+
+ bTheme *btheme = UI_GetTheme();
+ const float bg_radius = btheme->tui.wcol_menu_back.roundness * U.widget_unit;
+ const float line_width = 3.0f * UI_SCALE_FAC;
+ const float radius = (bg_radius > (line_width * 2.0f)) ? 0.0f : bg_radius;
+ const float padding = (bg_radius > (line_width * 2.0f)) ? bg_radius : 0.0f;
+ rctf line_rect;
+ BLI_rctf_rcti_copy(&line_rect, rect);
+ line_rect.ymin = line_rect.ymax - line_width;
+ BLI_rctf_pad(&line_rect, -padding, 0.0f);
+ UI_draw_roundbox_corner_set(UI_CNR_TOP_LEFT | UI_CNR_TOP_RIGHT);
+ UI_draw_roundbox_4fv(&line_rect, true, radius, color);
+}
+
void ui_draw_menu_back(uiStyle * /*style*/, uiBlock *block, const rcti *rect)
{
uiWidgetType *wt = widget_type(UI_WTYPE_MENU_BACK);
@@ -5366,7 +5400,14 @@ void ui_draw_menu_back(uiStyle * /*style*/, uiBlock *block, const rcti *rect)
wt->state(wt, &STATE_INFO_NULL, blender::ui::EmbossType::Undefined);
if (block) {
const float zoom = 1.0f / block->aspect;
- wt->draw_block(&wt->wcol, rect, block->flag, block->direction, zoom);
+ wt->draw_block(&wt->wcol,
+ rect,
+ block->flag,
+ block->alert_level == uiBlockAlertLevel::None ? block->direction : UI_DIR_DOWN,
+ zoom);
+ if (block->alert_level != uiBlockAlertLevel::None) {
+ ui_draw_dialog_alert(block, rect);
+ }
}
else {
wt->draw_block(&wt->wcol, rect, 0, 0, 1.0f);
diff --git a/source/blender/editors/interface/regions/interface_region_popup.cc b/source/blender/editors/interface/regions/interface_region_popup.cc
index 85502e84c9e..f64d555a442 100644
--- a/source/blender/editors/interface/regions/interface_region_popup.cc
+++ b/source/blender/editors/interface/regions/interface_region_popup.cc
@@ -17,6 +17,8 @@
#include "DNA_userdef_types.h"
+#include "BLF_api.hh"
+
#include "BLI_listbase.h"
#include "BLI_math_vector.h"
#include "BLI_rect.h"
@@ -1000,4 +1002,138 @@ void ui_popup_block_free(bContext *C, uiPopupBlockHandle *handle)
MEM_delete(handle);
}
+struct ui_alert_data {
+ eAlertIcon icon;
+ std::string title;
+ std::string message;
+ bool compact;
+ bool okay_button;
+ bool mouse_move_quit;
+};
+
+static void ui_alert_ok_cb(bContext *C, void *arg1, void *arg2)
+{
+ ui_alert_data *data = static_cast(arg1);
+ MEM_delete(data);
+ uiBlock *block = static_cast(arg2);
+ UI_popup_menu_retval_set(block, UI_RETURN_OK, true);
+ wmWindow *win = CTX_wm_window(C);
+ UI_popup_block_close(C, win, block);
+}
+
+static void ui_alert_ok(bContext * /*C*/, void *arg, int /*retval*/)
+{
+ ui_alert_data *data = static_cast(arg);
+ MEM_delete(data);
+}
+
+static void ui_alert_cancel(bContext * /*C*/, void *user_data)
+{
+ ui_alert_data *data = static_cast(user_data);
+ MEM_delete(data);
+}
+
+static uiBlock *ui_alert_create(bContext *C, ARegion *region, void *user_data)
+{
+ ui_alert_data *data = static_cast(user_data);
+
+ const uiStyle *style = UI_style_get_dpi();
+ const short icon_size = (data->compact ? 32 : 40) * UI_SCALE_FAC;
+ const int max_width = int((data->compact ? 250.0f : 350.0f) * UI_SCALE_FAC);
+ const int min_width = int(120.0f * UI_SCALE_FAC);
+
+ uiBlock *block = UI_block_begin(C, region, __func__, blender::ui::EmbossType::Emboss);
+ UI_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP);
+ UI_block_flag_disable(block, UI_BLOCK_LOOP);
+ UI_block_emboss_set(block, blender::ui::EmbossType::Emboss);
+ UI_popup_dummy_panel_set(region, block);
+
+ UI_block_flag_enable(block, UI_BLOCK_KEEP_OPEN | UI_BLOCK_NUMSELECT);
+ if (data->mouse_move_quit) {
+ UI_block_flag_enable(block, UI_BLOCK_MOVEMOUSE_QUIT);
+ }
+
+ const uiFontStyle *fstyle = UI_FSTYLE_WIDGET;
+
+ UI_fontstyle_set(&style->widget);
+ /* Width based on the text lengths. */
+ int text_width = BLF_width(style->widget.uifont_id, data->title.c_str(), data->title.size());
+
+ blender::Vector messages = BLF_string_wrap(
+ fstyle->uifont_id, data->message, max_width, BLFWrapMode::Typographical);
+
+ for (auto &st_ref : messages) {
+ const std::string &st = st_ref;
+ text_width = std::max(text_width,
+ int(BLF_width(style->widget.uifont_id, st.c_str(), st.size())));
+ }
+
+ int dialog_width = std::max(text_width + int(style->columnspace * 2.5), min_width);
+
+ uiLayout *layout;
+ layout = uiItemsAlertBox(block, style, dialog_width + icon_size, data->icon, icon_size);
+
+ uiLayout *content = &layout->column(false);
+ content->scale_y_set(0.75f);
+
+ /* Title. */
+ uiItemL_ex(content, data->title, ICON_NONE, true, false);
+
+ content->separator(1.0f);
+
+ /* Message lines. */
+ for (auto &st : messages) {
+ content->label(st, ICON_NONE);
+ }
+
+ if (data->okay_button) {
+
+ layout->separator(2.0f);
+
+ /* Clear so the OK button is left alone. */
+ UI_block_func_set(block, nullptr, nullptr, nullptr);
+
+ const float pad = std::max((1.0f - ((200.0f * UI_SCALE_FAC) / float(text_width))) / 2.0f,
+ 0.01f);
+ uiLayout *split = &layout->split(pad, true);
+ split->column(true);
+ uiLayout *buttons = &split->split(1.0f - (pad * 2.0f), true);
+ buttons->scale_y_set(1.2f);
+
+ uiBlock *buttons_block = layout->block();
+ uiBut *okay_but = uiDefBut(
+ buttons_block, ButType::But, 0, "OK", 0, 0, 0, UI_UNIT_Y, nullptr, 0, 0, "");
+ UI_but_func_set(okay_but, ui_alert_ok_cb, user_data, block);
+ UI_but_flag_enable(okay_but, UI_BUT_ACTIVE_DEFAULT);
+ }
+
+ const int padding = (data->compact ? 10 : 14) * UI_SCALE_FAC;
+
+ if (data->mouse_move_quit) {
+ const float button_center_x = -0.5f;
+ const float button_center_y = data->okay_button ? 4.0f : 2.0f;
+ const int bounds_offset[2] = {int(button_center_x * layout->width()),
+ int(button_center_y * UI_UNIT_X)};
+ UI_block_bounds_set_popup(block, padding, bounds_offset);
+ }
+ else {
+ UI_block_bounds_set_centered(block, padding);
+ }
+
+ return block;
+}
+
+void UI_alert(bContext *C, std::string title, std::string message, eAlertIcon icon, bool compact)
+{
+ ui_alert_data *data = MEM_new(__func__);
+ data->title = title;
+ data->message = message;
+ data->icon = icon;
+ data->compact = compact;
+ data->okay_button = true;
+ data->mouse_move_quit = compact;
+
+ UI_popup_block_ex(C, ui_alert_create, ui_alert_ok, ui_alert_cancel, data, nullptr);
+}
+
/** \} */