UI: Operator Confirm Dialog Changes #117564

Merged
Harley Acheson merged 13 commits from Harley/blender:NewConfirm into main 2024-01-29 18:52:27 +01:00
8 changed files with 199 additions and 287 deletions

View File

@ -3043,10 +3043,15 @@ void uiItemTabsEnumR_prop(uiLayout *layout,
const char *UI_layout_introspect(uiLayout *layout);
/**
* Helper to add a big icon and create a split layout for alert popups.
* Helpers to add a big icon and create a split layout for alert popups.
* Returns the layout to place further items into the alert box.
*/
uiLayout *uiItemsAlertBox(uiBlock *block, int size, eAlertIcon icon);
uiLayout *uiItemsAlertBox(uiBlock *block,
const uiStyle *style,
const int dialog_width,
const eAlertIcon icon,
const int icon_size);
uiLayout *uiItemsAlertBox(uiBlock *block, const int size, const eAlertIcon icon);
/* UI Operators */
struct uiDragColorHandle {

View File

@ -45,6 +45,7 @@ struct IconTextOverlay {
#define PREVIEW_DEFAULT_HEIGHT 128
enum eAlertIcon {
ALERT_ICON_NONE = -1,
ALERT_ICON_WARNING = 0,
ALERT_ICON_QUESTION = 1,
ALERT_ICON_ERROR = 2,

View File

@ -6396,12 +6396,12 @@ const char *UI_layout_introspect(uiLayout *layout)
/** \name Alert Box with Big Icon
* \{ */
uiLayout *uiItemsAlertBox(uiBlock *block, const int size, const eAlertIcon icon)
uiLayout *uiItemsAlertBox(uiBlock *block,
const uiStyle *style,
const int dialog_width,
const eAlertIcon icon,
const int icon_size)
{
const uiStyle *style = UI_style_get_dpi();
const short icon_size = 64 * UI_SCALE_FAC;
const int text_points_max = std::max(style->widget.points, style->widgetlabel.points);
const int dialog_width = icon_size + (text_points_max * size * UI_SCALE_FAC);
/* By default, the space between icon and text/buttons will be equal to the 'columnspace',
* this extra padding will add some space by increasing the left column width,
* making the icon placement more symmetrical, between the block edge and the text. */
@ -6428,4 +6428,13 @@ uiLayout *uiItemsAlertBox(uiBlock *block, const int size, const eAlertIcon icon)
return layout;
}
uiLayout *uiItemsAlertBox(uiBlock *block, const int size, const eAlertIcon icon)
{
const uiStyle *style = UI_style_get_dpi();
const short icon_size = 64 * UI_SCALE_FAC;
const int text_points_max = std::max(style->widget.points, style->widgetlabel.points);
const int dialog_width = icon_size + (text_points_max * size * UI_SCALE_FAC);
return uiItemsAlertBox(block, style, dialog_width, icon, icon_size);
}
/** \} */

View File

@ -209,10 +209,22 @@ static void rna_progress_end(wmWindowManager *wm)
}
/* wrap these because of 'const wmEvent *' */
static int rna_Operator_confirm(bContext *C, wmOperator *op, wmEvent *event)
static int rna_Operator_confirm(bContext *C,
wmOperator *op,
wmEvent * /*event*/,
const char *title,
const char *message,
const char *confirm_text,
Harley marked this conversation as resolved

This shouldn't be necessary, especially not in the public API. The message can be split by newline characters?

This shouldn't be necessary, especially not in the public API. The message can be split by newline characters?
const int icon,
const char *text_ctxt,
const bool translate)
{
return WM_operator_confirm(C, op, event);
title = RNA_translate_ui_text(title, text_ctxt, nullptr, nullptr, translate);
message = RNA_translate_ui_text(message, text_ctxt, nullptr, nullptr, translate);
brecht marked this conversation as resolved

I'm not sure about having size, position and cancel_default and mouse_move_quit as parameters. Maybe even icon.

It's leaving a lot of choice to each operator, which is not going to give consistency. I would prefer there rather to be an enum for different types of confirmation dialogs that we can style consistently.

At the moment I can only think of:

  • "quick" confirmation under the mouse cursor, relatively small, dismissed with mousemove, question icon.
  • "important" confirmation, center of the window, relatively wide, no quit on mouse move, warning icon.

Does that make sense? Can you think of other types?

I'm not sure about having `size`, `position` and `cancel_default` and `mouse_move_quit` as parameters. Maybe even `icon`. It's leaving a lot of choice to each operator, which is not going to give consistency. I would prefer there rather to be an enum for different types of confirmation dialogs that we can style consistently. At the moment I can only think of: * "quick" confirmation under the mouse cursor, relatively small, dismissed with mousemove, question icon. * "important" confirmation, center of the window, relatively wide, no quit on mouse move, warning icon. Does that make sense? Can you think of other types?
confirm_text = RNA_translate_ui_text(confirm_text, text_ctxt, nullptr, nullptr, translate);
return WM_operator_confirm_ex(C, op, title, message, confirm_text, icon);
}
static int rna_Operator_props_popup(bContext *C, wmOperator *op, wmEvent *event)
{
return WM_operator_props_popup(C, op, event);
@ -788,6 +800,16 @@ void RNA_api_window(StructRNA *srna)
RNA_def_function_return(func, parm);
}
const EnumPropertyItem rna_operator_popup_icon_items[] = {
{ALERT_ICON_NONE, "NONE", 0, "None", ""},
{ALERT_ICON_WARNING, "WARNING", 0, "Warning", ""},
{ALERT_ICON_QUESTION, "QUESTION", 0, "Question", ""},
{ALERT_ICON_ERROR, "ERROR", 0, "Error", ""},
{ALERT_ICON_INFO, "INFO", 0, "Info", ""},
{ALERT_ICON_BLENDER, "BLENDER", 0, "Blender", ""},
{0, nullptr, 0, nullptr, nullptr},
};
void RNA_api_wm(StructRNA *srna)
{
FunctionRNA *func;
@ -911,6 +933,24 @@ void RNA_api_wm(StructRNA *srna)
"(only to let user confirm the execution, no operator properties shown)");
rna_generic_op_invoke(func, WM_GEN_INVOKE_EVENT | WM_GEN_INVOKE_RETURN);
parm = RNA_def_property(func, "title", PROP_STRING, PROP_NONE);
RNA_def_property_ui_text(parm, "Title", "Optional text to show as title of the popup");
parm = RNA_def_property(func, "message", PROP_STRING, PROP_NONE);
RNA_def_property_ui_text(parm, "Message", "Optional first line of content text");
parm = RNA_def_property(func, "confirm_text", PROP_STRING, PROP_NONE);
RNA_def_property_ui_text(
parm,
"Confirm Text",
"Optional text to show instead to the default \"OK\" confirmation button text");
parm = RNA_def_property(func, "icon", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(parm, rna_operator_popup_icon_items);
RNA_def_property_ui_text(parm, "Icon", "Optional icon displayed in the dialog");
api_ui_item_common_translation(func);
/* wrap UI_popup_menu_begin */
func = RNA_def_function(srna, "popmenu_begin__internal", "rna_PopMenuBegin");
RNA_def_function_flag(func, FUNC_NO_SELF | FUNC_USE_CONTEXT);

View File

@ -685,6 +685,18 @@ int WM_enum_search_invoke(bContext *C, wmOperator *op, const wmEvent *event);
*/
int WM_operator_confirm(bContext *C, wmOperator *op, const wmEvent *event);
int WM_operator_confirm_or_exec(bContext *C, wmOperator *op, const wmEvent *event);
/**
* Like WM_operator_confirm, but with more options and can't be used as an invoke directly.
*/
int WM_operator_confirm_ex(bContext *C,
wmOperator *op,
const char *title = nullptr,
const char *message = nullptr,
const char *confirm_text = nullptr,
int icon = 0, /* ALERT_ICON_WARNING. */
bool cancel_default = false);
/**
* Invoke callback, file selector "filepath" unset + exec.
*

View File

@ -919,26 +919,14 @@ struct wmTimer {
bool sleep;
};
enum wmConfirmSize {
WM_WARNING_SIZE_SMALL = 0,
WM_WARNING_SIZE_LARGE,
enum wmPopupSize {
WM_POPUP_SIZE_SMALL = 0,
WM_POPUP_SIZE_LARGE,
};
enum wmConfirmPosition {
WM_WARNING_POSITION_MOUSE = 0,
WM_WARNING_POSITION_CENTER,
};
struct wmConfirmDetails {
std::string title;
std::string message;
std::string message2;
std::string confirm_text;
int icon;
wmConfirmSize size;
wmConfirmPosition position;
bool cancel_default;
bool mouse_move_quit;
enum wmPopupPosition {
WM_POPUP_POSITION_MOUSE = 0,
WM_POPUP_POSITION_CENTER,
};
/**
@ -1068,11 +1056,6 @@ 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 (*confirm)(bContext *C, wmOperator *, wmConfirmDetails *details);
/** RNA for properties */
StructRNA *srna;

View File

@ -3581,15 +3581,15 @@ void WM_OT_save_mainfile(wmOperatorType *ot)
/** \name Clear Recent Files List Operator
* \{ */
static void wm_clear_recent_files_confirm(bContext * /*C*/,
wmOperator * /*op*/,
wmConfirmDetails *confirm)
static int wm_clear_recent_files_invoke(bContext *C, wmOperator *op, const wmEvent * /*event*/)
{
confirm->message = IFACE_("Remove all items from the recent files list");
confirm->confirm_text = IFACE_("Remove All");
confirm->position = WM_WARNING_POSITION_CENTER;
confirm->size = WM_WARNING_SIZE_LARGE;
confirm->cancel_default = true;
return WM_operator_confirm_ex(C,
op,
nullptr,
IFACE_("Remove all items from the recent files list"),
IFACE_("Remove All"),
ALERT_ICON_WARNING,
false);
}
static int wm_clear_recent_files_exec(bContext * /*C*/, wmOperator * /*op*/)
@ -3606,9 +3606,8 @@ void WM_OT_clear_recent_files(wmOperatorType *ot)
ot->idname = "WM_OT_clear_recent_files";
ot->description = "Clear the recent files list";
ot->invoke = WM_operator_confirm;
ot->invoke = wm_clear_recent_files_invoke;
ot->exec = wm_clear_recent_files_exec;
ot->confirm = wm_clear_recent_files_confirm;
}
/** \} */

View File

@ -16,6 +16,8 @@
#include <cstddef>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <sstream>
#ifdef WIN32
# include "GHOST_C-api.h"
@ -1186,205 +1188,6 @@ 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);
wmConfirmDetails confirm = {{0}};
confirm.title = WM_operatortype_description(C, op->type, op->ptr);
confirm.confirm_text = WM_operatortype_name(op->type, op->ptr);
confirm.icon = ALERT_ICON_WARNING;
confirm.size = WM_WARNING_SIZE_SMALL;
confirm.position = WM_WARNING_POSITION_MOUSE;
confirm.cancel_default = false;
confirm.mouse_move_quit = false;
/* uiBlock.flag */
int block_flags = UI_BLOCK_KEEP_OPEN | UI_BLOCK_NO_WIN_CLIP | UI_BLOCK_NUMSELECT;
if (op->type->confirm) {
op->type->confirm(C, op, &confirm);
}
if (confirm.mouse_move_quit) {
block_flags |= UI_BLOCK_MOVEMOUSE_QUIT;
}
if (confirm.icon < ALERT_ICON_WARNING || confirm.icon >= ALERT_ICON_MAX) {
confirm.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 = std::max(
120 * UI_SCALE_FAC,
BLF_width(style->widget.uifont_id, confirm.title.c_str(), confirm.title.length()));
if (!confirm.message.empty()) {
text_width = std::max(text_width,
int(BLF_width(style->widget.uifont_id,
confirm.message.c_str(),
confirm.message.length())));
}
if (!confirm.message2.empty()) {
text_width = std::max(text_width,
int(BLF_width(style->widget.uifont_id,
confirm.message2.c_str(),
confirm.message2.length())));
}
const bool small = confirm.size == WM_WARNING_SIZE_SMALL;
const int padding = (small ? 7 : 14) * UI_SCALE_FAC;
const short icon_size = (small ? (confirm.message.empty() ? 32 : 48) : 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, confirm.icon, 0, 0, icon_size, icon_size);
/* The rest of the content on the right. */
layout = uiLayoutColumn(split_block, true);
if (!confirm.title.empty()) {
if (confirm.message.empty()) {
uiItemS(layout);
}
uiItemL_ex(layout, confirm.title.c_str(), ICON_NONE, true, false);
}
if (!confirm.message.empty()) {
uiItemL(layout, confirm.message.c_str(), ICON_NONE);
}
if (!confirm.message2.empty()) {
uiItemL(layout, confirm.message2.c_str(), 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_but = nullptr;
uiBut *cancel_but = nullptr;
uiLayout *split = uiLayoutSplit(small ? block_layout : layout, 0.0f, true);
uiLayoutSetScaleY(split, small ? 1.1f : 1.2f);
uiLayoutColumn(split, false);
if (windows_layout) {
confirm_but = uiDefIconTextBut(block,
UI_BTYPE_BUT,
0,
0,
confirm.confirm_text.c_str(),
0,
0,
0,
UI_UNIT_Y,
nullptr,
0,
0,
0,
0,
nullptr);
uiLayoutColumn(split, false);
}
cancel_but = uiDefIconTextBut(block,
UI_BTYPE_BUT,
0,
0,
IFACE_("Cancel"),
0,
0,
0,
UI_UNIT_Y,
nullptr,
0,
0,
0,
0,
nullptr);
if (!windows_layout) {
uiLayoutColumn(split, false);
confirm_but = uiDefIconTextBut(block,
UI_BTYPE_BUT,
0,
0,
confirm.confirm_text.c_str(),
0,
0,
0,
UI_UNIT_Y,
nullptr,
0,
0,
0,
0,
nullptr);
}
UI_block_func_set(block, nullptr, nullptr, nullptr);
UI_but_func_set(confirm_but, wm_operator_block_confirm, op, block);
UI_but_func_set(cancel_but, wm_operator_block_cancel, op, block);
Harley marked this conversation as resolved Outdated

Seems more code can be removed.

/home/brecht/dev/blender/source/blender/windowmanager/intern/wm_operators.cc:1191:13: warning: unused function 'wm_operator_block_cancel' [-Wunused-function]
 1191 | static void wm_operator_block_cancel(bContext *C, void *arg_op, void *arg_block)
      |             ^~~~~~~~~~~~~~~~~~~~~~~~
/home/brecht/dev/blender/source/blender/windowmanager/intern/wm_operators.cc:1205:13: warning: unused function 'wm_operator_block_confirm' [-Wunused-function]
 1205 | static void wm_operator_block_confirm(bContext *C, void *arg_op, void *arg_block)
Seems more code can be removed. ``` /home/brecht/dev/blender/source/blender/windowmanager/intern/wm_operators.cc:1191:13: warning: unused function 'wm_operator_block_cancel' [-Wunused-function] 1191 | static void wm_operator_block_cancel(bContext *C, void *arg_op, void *arg_block) | ^~~~~~~~~~~~~~~~~~~~~~~~ /home/brecht/dev/blender/source/blender/windowmanager/intern/wm_operators.cc:1205:13: warning: unused function 'wm_operator_block_confirm' [-Wunused-function] 1205 | static void wm_operator_block_confirm(bContext *C, void *arg_op, void *arg_block) ```
UI_but_drawflag_disable(confirm_but, UI_BUT_TEXT_LEFT);
UI_but_drawflag_disable(cancel_but, UI_BUT_TEXT_LEFT);
UI_but_flag_enable(confirm.cancel_default ? cancel_but : confirm_but, UI_BUT_ACTIVE_DEFAULT);
if (confirm.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 * (confirm.message[0] ? 3.1 : 2.5);
UI_block_bounds_set_popup(block, padding, bounds_offset);
}
else if (confirm.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,
@ -1412,11 +1215,6 @@ int WM_operator_confirm_message_ex(bContext *C,
int WM_operator_confirm_message(bContext *C, wmOperator *op, const char *message)
{
if (op->type->confirm) {
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);
}
@ -1645,11 +1443,10 @@ struct wmOpPopUp {
int free_op;
std::string title;
std::string message;
std::string message2;
std::string confirm_text;
int icon;
wmConfirmSize size;
wmConfirmPosition position;
eAlertIcon icon;
wmPopupSize size;
wmPopupPosition position;
bool cancel_default;
bool mouse_move_quit;
bool include_properties;
@ -1699,6 +1496,8 @@ static uiBlock *wm_block_dialog_create(bContext *C, ARegion *region, void *user_
wmOpPopUp *data = static_cast<wmOpPopUp *>(user_data);
wmOperator *op = data->op;
const uiStyle *style = UI_style_get_dpi();
const bool small = data->size == WM_POPUP_SIZE_SMALL;
const short icon_size = (small ? 32 : 64) * UI_SCALE_FAC;
uiBlock *block = UI_block_begin(C, region, __func__, UI_EMBOSS);
UI_block_flag_disable(block, UI_BLOCK_LOOP);
@ -1707,24 +1506,71 @@ static uiBlock *wm_block_dialog_create(bContext *C, ARegion *region, void *user_
if (data->mouse_move_quit) {
UI_block_flag_enable(block, UI_BLOCK_MOVEMOUSE_QUIT);
}
if (data->icon < ALERT_ICON_NONE || data->icon >= ALERT_ICON_MAX) {
data->icon = ALERT_ICON_QUESTION;
}
UI_block_flag_enable(block, UI_BLOCK_KEEP_OPEN | UI_BLOCK_NUMSELECT);
uiLayout *layout = UI_block_layout(
block, UI_LAYOUT_VERTICAL, UI_LAYOUT_PANEL, 0, 0, data->width, 0, 0, style);
/* Width based on the text lengths. */
int text_width = std::max(
120 * UI_SCALE_FAC,
BLF_width(style->widget.uifont_id, data->title.c_str(), BLF_DRAW_STR_DUMMY_MAX));
uiItemL_ex(layout, data->title.c_str(), ICON_NONE, true, false);
uiItemS_ex(layout, 0.3f);
/* Break Message into multiple lines. */
std::vector<std::string> message_lines;
blender::StringRef messaged_trimmed = blender::StringRef(data->message).trim();
std::istringstream message_stream(messaged_trimmed);
std::string line;
while (std::getline(message_stream, line)) {
message_lines.push_back(line);
text_width = std::max(
text_width, int(BLF_width(style->widget.uifont_id, line.c_str(), BLF_DRAW_STR_DUMMY_MAX)));
}
int dialog_width = std::max(text_width + int(style->columnspace * 2.5), data->width);
/* Adjust width if the button text is long. */
const int longest_button_text = std::max(
BLF_width(style->widget.uifont_id, data->confirm_text.c_str(), BLF_DRAW_STR_DUMMY_MAX),
BLF_width(style->widget.uifont_id, IFACE_("Cancel"), BLF_DRAW_STR_DUMMY_MAX));
dialog_width = std::max(dialog_width, 3 * longest_button_text);
uiLayout *layout;
if (data->icon != ALERT_ICON_NONE) {
layout = uiItemsAlertBox(
block, style, dialog_width + icon_size, eAlertIcon(data->icon), icon_size);
}
else {
layout = UI_block_layout(
block, UI_LAYOUT_VERTICAL, UI_LAYOUT_PANEL, 0, 0, dialog_width, 0, 0, style);
}
Harley marked this conversation as resolved Outdated

origStream -> message_stream

`origStream` -> `message_stream`
/* Title. */
if (!data->title.empty()) {
Harley marked this conversation as resolved Outdated

Might as well keep empty lines if someone wants paragraphs.

You could trim whitespace at the start and end still:

StringRef messaged_trimmed = StringRef(data->message).trim();
std::istringstream message_stream(messaged_trimmed);
...
Might as well keep empty lines if someone wants paragraphs. You could trim whitespace at the start and end still: ``` StringRef messaged_trimmed = StringRef(data->message).trim(); std::istringstream message_stream(messaged_trimmed); ... ```
uiItemL_ex(layout, data->title.c_str(), ICON_NONE, true, false);

This could also break long lines to prevent popups from being too wide (like tooltips do)

Long tool-tip message Long confirm message
image image
This could also break long lines to prevent popups from being too wide (like tooltips do) | Long tool-tip message | Long confirm message | | -------- | -------- | |![image](/attachments/4192bc32-4111-4cdd-a6ac-3aeefdcda686)|![image](/attachments/0e82b141-f935-40a5-8462-82869c16d780)|

@guishe - This could also break long lines to prevent popups from being too wide (like tooltips do)

Okay, I could be wrong about this, but I think that would require a feature that we are missing but need...

Tooltips do that wrapping within BLF. As in the FontBLF has a BLF_WORD_WRAP flag and wrap_width member and when measuring the string or printing it out we run a callback that does wrapping.

I don't think we have anything that helps with wrapping outside of text output. As in give it a string and fontid and get back info on where to break. Or breaks it up into a string vector. Or array of breaking points? That way we could then add labels to the layout for each substring, rather than printing out.

Haven't thought it through much, but I think such a thing could look like blf_font_draw_buffer__wrap but would pass an integer vector or array for the userdata and a callback that would populate it with wrapping points?

Edit: This is probably a perfect use case for this. I'll try to get to this over this weekend.

> @guishe - This could also break long lines to prevent popups from being too wide (like tooltips do) Okay, I could be wrong about this, but I _think_ that would require a feature that we are missing but _need_... Tooltips do that wrapping within BLF. As in the FontBLF has a `BLF_WORD_WRAP` flag and `wrap_width` member and when measuring the string or printing it out we run a callback that does wrapping. I don't think we have anything that helps with wrapping outside of text output. As in give it a string and fontid and get back info on where to break. Or breaks it up into a string vector. Or array of breaking points? That way we could then add labels to the layout for each substring, rather than printing out. Haven't thought it through much, but I think such a thing could look like `blf_font_draw_buffer__wrap` but would pass an integer vector or array for the userdata and a callback that would populate it with wrapping points? Edit: This is probably a perfect use case for this. I'll try to get to this over this weekend.

I think BLF_width_to_strlen may be useful

I think `BLF_width_to_strlen` may be useful

I think BLF_width_to_strlen may be useful

Yes, that gives the string byte index that fits within a pixel width. So you could iterate through the string looking for word delimiters and line breaks.

That is basically what the existing blf_font_wrap_apply does for us, and has a callback at each wrapping location, and can return the numbers of wrapped lines and the width of the last orphan line.

> I think BLF_width_to_strlen may be useful Yes, that gives the string byte index that fits within a pixel width. So you could iterate through the string looking for word delimiters and line breaks. That is basically what the existing `blf_font_wrap_apply` does for us, and has a callback at each wrapping location, and can return the numbers of wrapped lines and the width of the last orphan line.
}
/* Message lines. */
for (auto &st : message_lines) {
uiItemL(layout, st.c_str(), ICON_NONE);
}
if (data->include_properties) {
uiTemplateOperatorPropertyButs(C, layout, op, UI_BUT_LABEL_ALIGN_SPLIT_COLUMN, 0);
}
Harley marked this conversation as resolved Outdated

const int

`const int`
uiItemS_ex(layout, 0.6f);
uiItemS_ex(layout, small ? 0.4f : 2.0f);
Harley marked this conversation as resolved Outdated

Use BLF_DRAW_STR_DUMMY_MAX for BLF_width calls?

Then it will be a more obvious step to port the BLF API to C++ and make BLF_DRAW_STR_DUMMY_MAX the default or use StringRef to simplify this.

Use `BLF_DRAW_STR_DUMMY_MAX` for `BLF_width` calls? Then it will be a more obvious step to port the BLF API to C++ and make `BLF_DRAW_STR_DUMMY_MAX` the default or use `StringRef` to simplify this.

It's a bit unfortunate that all this logic is needed here. Maybe one day this can become a feature of the layout engine instead, but seems ok for now.

It's a bit unfortunate that all this logic is needed here. Maybe one day this can become a feature of the layout engine instead, but seems ok for now.

Actually we did have a uiItemsAlertBox routine in layout that's close, so made an overloaded variation that shares code with it.

Actually we did have a `uiItemsAlertBox` routine in layout that's close, so made an overloaded variation that shares code with it.
/* clear so the OK button is left alone */
UI_block_func_set(block, nullptr, nullptr, nullptr);
#ifdef _WIN32
const bool windows_layout = true;
#else
const bool windows_layout = false;
#endif
/* new column so as not to interfere with custom layouts #26436. */
{
uiLayout *col = uiLayoutColumn(layout, false);
@ -1733,13 +1579,7 @@ static uiBlock *wm_block_dialog_create(bContext *C, ARegion *region, void *user_
uiBut *cancel_but;
col = uiLayoutSplit(col, 0.0f, true);
uiLayoutSetScaleY(col, 1.2f);
#ifdef _WIN32
const bool windows_layout = true;
#else
const bool windows_layout = false;
#endif
uiLayoutSetScaleY(col, small ? 1.0f : 1.2f);
if (windows_layout) {
confirm_but = uiDefBut(col_block,
@ -1747,7 +1587,7 @@ static uiBlock *wm_block_dialog_create(bContext *C, ARegion *region, void *user_
0,
data->confirm_text.c_str(),
0,
-30,
0,
0,
UI_UNIT_Y,
nullptr,
@ -1759,20 +1599,8 @@ static uiBlock *wm_block_dialog_create(bContext *C, ARegion *region, void *user_
uiLayoutColumn(col, false);
}
cancel_but = uiDefBut(col_block,
UI_BTYPE_BUT,
0,
IFACE_("Cancel"),
0,
-30,
0,
UI_UNIT_Y,
nullptr,
0,
0,
0,
0,
"");
cancel_but = uiDefBut(
col_block, UI_BTYPE_BUT, 0, IFACE_("Cancel"), 0, 0, 0, UI_UNIT_Y, nullptr, 0, 0, 0, 0, "");
if (!windows_layout) {
uiLayoutColumn(col, false);
@ -1781,7 +1609,7 @@ static uiBlock *wm_block_dialog_create(bContext *C, ARegion *region, void *user_
0,
data->confirm_text.c_str(),
0,
-30,
0,
0,
UI_UNIT_Y,
nullptr,
@ -1797,14 +1625,17 @@ static uiBlock *wm_block_dialog_create(bContext *C, ARegion *region, void *user_
UI_but_flag_enable((data->cancel_default) ? cancel_but : confirm_but, UI_BUT_ACTIVE_DEFAULT);
}
if (data->position == WM_WARNING_POSITION_MOUSE) {
int bounds_offset[2];
bounds_offset[0] = uiLayoutGetWidth(layout) * -0.66f;
bounds_offset[1] = UI_UNIT_Y * 2;
UI_block_bounds_set_popup(block, 10 * UI_SCALE_FAC, bounds_offset);
const int padding = (small ? 7 : 14) * UI_SCALE_FAC;
if (data->position == WM_POPUP_POSITION_MOUSE) {
const float button_center_x = windows_layout ? -0.33f : -0.66f;
const float button_center_y = small ? 1.9f : 3.1f;
const int bounds_offset[2] = {int(button_center_x * uiLayoutGetWidth(layout)),
int(button_center_y * UI_UNIT_X)};
UI_block_bounds_set_popup(block, padding, bounds_offset);
}
else if (data->position == WM_WARNING_POSITION_CENTER) {
UI_block_bounds_set_centered(block, 10 * UI_SCALE_FAC);
else if (data->position == WM_POPUP_POSITION_CENTER) {
UI_block_bounds_set_centered(block, padding);
}
return block;
@ -1864,6 +1695,35 @@ static void wm_operator_ui_popup_ok(bContext *C, void *arg, int retval)
MEM_delete(data);
}
int WM_operator_confirm_ex(bContext *C,
wmOperator *op,
const char *title,
const char *message,
const char *confirm_text,
int icon,
bool cancel_default)
{
wmOpPopUp *data = MEM_new<wmOpPopUp>(__func__);
data->op = op;
data->width = int(180.0f * UI_SCALE_FAC * UI_style_get()->widgetlabel.points /
UI_DEFAULT_TEXT_POINTS);
data->free_op = true;
data->title = (title == nullptr) ? WM_operatortype_name(op->type, op->ptr) : title;
data->message = (message == nullptr) ? std::string() : message;
data->confirm_text = (confirm_text == nullptr) ? IFACE_("OK") : confirm_text;
data->icon = eAlertIcon(icon);
data->size = (message == nullptr) ? WM_POPUP_SIZE_SMALL : WM_POPUP_SIZE_LARGE;
data->position = (message == nullptr) ? WM_POPUP_POSITION_MOUSE : WM_POPUP_POSITION_CENTER;
data->cancel_default = cancel_default;
data->mouse_move_quit = (message == nullptr) ? true : false;
data->include_properties = false;
UI_popup_block_ex(
C, wm_block_dialog_create, wm_operator_ui_popup_ok, wm_operator_ui_popup_cancel, data, op);
return OPERATOR_RUNNING_MODAL;
}
int WM_operator_ui_popup(bContext *C, wmOperator *op, int width)
{
wmOpPopUp *data = MEM_new<wmOpPopUp>(__func__);
@ -1937,14 +1797,17 @@ int WM_operator_props_dialog_popup(
{
wmOpPopUp *data = MEM_new<wmOpPopUp>(__func__);
data->op = op;
data->width = width * UI_SCALE_FAC;
data->width = int(float(width) * UI_SCALE_FAC * UI_style_get()->widgetlabel.points /
UI_DEFAULT_TEXT_POINTS);
data->free_op = true; /* if this runs and gets registered we may want not to free it */
data->title = (title == nullptr) ? WM_operatortype_name(op->type, op->ptr) : title;
data->confirm_text = (confirm_text == nullptr) ? IFACE_("OK") : confirm_text;
data->icon = ALERT_ICON_NONE;
data->size = WM_POPUP_SIZE_SMALL;
data->position = WM_POPUP_POSITION_MOUSE;
data->cancel_default = false;
data->mouse_move_quit = false;
data->include_properties = true;
data->position = WM_WARNING_POSITION_MOUSE;
/* op is not executed until popup OK but is clicked */
UI_popup_block_ex(