UI: Support tooltips for superimposed icons

In a couple of places in the UI, we show superimposed icons on buttons
to execute an operation (called "Extra Icons" internally). Hovering them
would show the tooltip of the underlying button, which is misleading and
confusing.
There are cases where it's not obvious what an icon does, so a tooltip
would be quite useful here. It's likely we are going to use superimposed
icons in more places in the future, e.g. see D11890.

The extra icon basically acts as an override for the button in the
tooltip code.

Differential Revision: https://developer.blender.org/D11894

Reviewed by: Campbell Barton
This commit is contained in:
Julian Eisel 2021-10-08 14:17:03 +02:00
parent 86643a4e73
commit 2aca08fc1c
4 changed files with 145 additions and 42 deletions

View File

@ -82,6 +82,7 @@ struct wmWindow;
typedef struct uiBlock uiBlock;
typedef struct uiBut uiBut;
typedef struct uiButExtraOpIcon uiButExtraOpIcon;
typedef struct uiLayout uiLayout;
typedef struct uiPopupBlockHandle uiPopupBlockHandle;
/* C handle for C++ #ui::AbstractTreeView type. */
@ -1432,6 +1433,8 @@ typedef struct uiStringInfo {
* Will fill them with translated strings, when possible.
* Strings in uiStringInfo must be MEM_freeN'ed by caller. */
void UI_but_string_info_get(struct bContext *C, uiBut *but, ...) ATTR_SENTINEL(0);
void UI_but_extra_icon_string_info_get(struct bContext *C, uiButExtraOpIcon *extra_icon, ...)
ATTR_SENTINEL(0);
/* Edit i18n stuff. */
/* Name of the main py op from i18n addon. */
@ -1724,6 +1727,8 @@ struct PointerRNA *UI_but_extra_operator_icon_add(uiBut *but,
const char *opname,
short opcontext,
int icon);
struct wmOperatorType *UI_but_extra_operator_icon_optype_get(struct uiButExtraOpIcon *extra_icon);
struct PointerRNA *UI_but_extra_operator_icon_opptr_get(struct uiButExtraOpIcon *extra_icon);
/* Autocomplete
*
@ -2713,6 +2718,11 @@ struct ARegion *UI_tooltip_create_from_button(struct bContext *C,
struct ARegion *butregion,
uiBut *but,
bool is_label);
struct ARegion *UI_tooltip_create_from_button_or_extra_icon(struct bContext *C,
struct ARegion *butregion,
uiBut *but,
uiButExtraOpIcon *extra_icon,
bool is_label);
struct ARegion *UI_tooltip_create_from_gizmo(struct bContext *C, struct wmGizmo *gz);
void UI_tooltip_free(struct bContext *C, struct bScreen *screen, struct ARegion *region);

View File

@ -1224,16 +1224,21 @@ void ui_but_add_shortcut(uiBut *but, const char *shortcut_str, const bool do_str
* \{ */
static bool ui_but_event_operator_string_from_operator(const bContext *C,
uiBut *but,
wmOperatorCallParams *op_call_params,
char *buf,
const size_t buf_len)
{
BLI_assert(but->optype != NULL);
BLI_assert(op_call_params->optype != NULL);
bool found = false;
IDProperty *prop = (but->opptr) ? but->opptr->data : NULL;
IDProperty *prop = (op_call_params->opptr) ? op_call_params->opptr->data : NULL;
if (WM_key_event_operator_string(
C, but->optype->idname, but->opcontext, prop, true, buf, buf_len)) {
if (WM_key_event_operator_string(C,
op_call_params->optype->idname,
op_call_params->opcontext,
prop,
true,
buf,
buf_len)) {
found = true;
}
return found;
@ -1318,7 +1323,12 @@ static bool ui_but_event_operator_string(const bContext *C,
bool found = false;
if (but->optype != NULL) {
found = ui_but_event_operator_string_from_operator(C, but, buf, buf_len);
found = ui_but_event_operator_string_from_operator(
C,
&(wmOperatorCallParams){
.optype = but->optype, .opptr = but->opptr, .opcontext = but->opcontext},
buf,
buf_len);
}
else if (UI_but_menutype_get(but) != NULL) {
found = ui_but_event_operator_string_from_menu(C, but, buf, buf_len);
@ -1330,6 +1340,20 @@ static bool ui_but_event_operator_string(const bContext *C,
return found;
}
static bool ui_but_extra_icon_event_operator_string(const bContext *C,
uiButExtraOpIcon *extra_icon,
char *buf,
const size_t buf_len)
{
wmOperatorType *extra_icon_optype = UI_but_extra_operator_icon_optype_get(extra_icon);
if (extra_icon_optype) {
return ui_but_event_operator_string_from_operator(C, extra_icon->optype_params, buf, buf_len);
}
return false;
}
static bool ui_but_event_property_operator_string(const bContext *C,
uiBut *but,
char *buf,
@ -1713,6 +1737,16 @@ PointerRNA *UI_but_extra_operator_icon_add(uiBut *but,
return NULL;
}
wmOperatorType *UI_but_extra_operator_icon_optype_get(uiButExtraOpIcon *extra_icon)
{
return extra_icon ? extra_icon->optype_params->optype : NULL;
}
PointerRNA *UI_but_extra_operator_icon_opptr_get(uiButExtraOpIcon *extra_icon)
{
return extra_icon->optype_params->opptr;
}
static bool ui_but_icon_extra_is_visible_text_clear(const uiBut *but)
{
BLI_assert(but->type == UI_BTYPE_TEXT);
@ -7262,6 +7296,42 @@ void UI_but_string_info_get(bContext *C, uiBut *but, ...)
}
}
void UI_but_extra_icon_string_info_get(struct bContext *C, uiButExtraOpIcon *extra_icon, ...)
{
va_list args;
uiStringInfo *si;
wmOperatorType *optype = UI_but_extra_operator_icon_optype_get(extra_icon);
PointerRNA *opptr = UI_but_extra_operator_icon_opptr_get(extra_icon);
va_start(args, extra_icon);
while ((si = (uiStringInfo *)va_arg(args, void *))) {
char *tmp = NULL;
switch (si->type) {
case BUT_GET_LABEL:
tmp = BLI_strdup(WM_operatortype_name(optype, opptr));
break;
case BUT_GET_TIP:
tmp = WM_operatortype_description(C, optype, opptr);
break;
case BUT_GET_OP_KEYMAP: {
char buf[128];
if (ui_but_extra_icon_event_operator_string(C, extra_icon, buf, sizeof(buf))) {
tmp = BLI_strdup(buf);
}
}
/* Other types not supported. The caller should expect that outcome, no need to message or
* assert here. */
default:
break;
}
si->strinfo = tmp;
}
va_end(args);
}
/* Program Init/Exit */
void UI_init(void)

View File

@ -8235,7 +8235,11 @@ static ARegion *ui_but_tooltip_init(
uiBut *but = UI_region_active_but_get(region);
*r_exit_on_event = false;
if (but) {
return UI_tooltip_create_from_button(C, region, but, is_label);
const wmWindow *win = CTX_wm_window(C);
uiButExtraOpIcon *extra_icon = ui_but_extra_operator_icon_mouse_over_get(
but, but->active, win->eventstate);
return UI_tooltip_create_from_button_or_extra_icon(C, region, but, extra_icon, is_label);
}
return NULL;
}

View File

@ -761,7 +761,9 @@ static uiTooltipData *ui_tooltip_data_from_tool(bContext *C, uiBut *but, bool is
return data;
}
static uiTooltipData *ui_tooltip_data_from_button(bContext *C, uiBut *but)
static uiTooltipData *ui_tooltip_data_from_button_or_extra_icon(bContext *C,
uiBut *but,
uiButExtraOpIcon *extra_icon)
{
uiStringInfo but_label = {BUT_GET_LABEL, NULL};
uiStringInfo but_tip = {BUT_GET_TIP, NULL};
@ -774,20 +776,29 @@ static uiTooltipData *ui_tooltip_data_from_button(bContext *C, uiBut *but)
char buf[512];
wmOperatorType *optype = extra_icon ? UI_but_extra_operator_icon_optype_get(extra_icon) :
but->optype;
PropertyRNA *rnaprop = extra_icon ? NULL : but->rnaprop;
/* create tooltip data */
uiTooltipData *data = MEM_callocN(sizeof(uiTooltipData), "uiTooltipData");
UI_but_string_info_get(C,
but,
&but_label,
&but_tip,
&enum_label,
&enum_tip,
&op_keymap,
&prop_keymap,
&rna_struct,
&rna_prop,
NULL);
if (extra_icon) {
UI_but_extra_icon_string_info_get(C, extra_icon, &but_label, &but_tip, &op_keymap, NULL);
}
else {
UI_but_string_info_get(C,
but,
&but_label,
&but_tip,
&enum_label,
&enum_tip,
&op_keymap,
&prop_keymap,
&rna_struct,
&rna_prop,
NULL);
}
/* Tip Label (only for buttons not already showing the label).
* Check prefix instead of comparing because the button may include the shortcut. */
@ -818,8 +829,7 @@ static uiTooltipData *ui_tooltip_data_from_button(bContext *C, uiBut *but)
}
/* special case enum rna buttons */
if ((but->type & UI_BTYPE_ROW) && but->rnaprop &&
RNA_property_flag(but->rnaprop) & PROP_ENUM_FLAG) {
if ((but->type & UI_BTYPE_ROW) && rnaprop && RNA_property_flag(rnaprop) & PROP_ENUM_FLAG) {
uiTooltipField *field = text_field_add(data,
&(uiTooltipFormat){
.style = UI_TIP_STYLE_NORMAL,
@ -863,7 +873,7 @@ static uiTooltipData *ui_tooltip_data_from_button(bContext *C, uiBut *but)
if (ELEM(but->type, UI_BTYPE_TEXT, UI_BTYPE_SEARCH_MENU)) {
/* better not show the value of a password */
if ((but->rnaprop && (RNA_property_subtype(but->rnaprop) == PROP_PASSWORD)) == 0) {
if ((rnaprop && (RNA_property_subtype(rnaprop) == PROP_PASSWORD)) == 0) {
/* full string */
ui_but_string_get(but, buf, sizeof(buf));
if (buf[0]) {
@ -878,15 +888,14 @@ static uiTooltipData *ui_tooltip_data_from_button(bContext *C, uiBut *but)
}
}
if (but->rnaprop) {
if (rnaprop) {
const int unit_type = UI_but_unit_type_get(but);
if (unit_type == PROP_UNIT_ROTATION) {
if (RNA_property_type(but->rnaprop) == PROP_FLOAT) {
float value = RNA_property_array_check(but->rnaprop) ?
RNA_property_float_get_index(
&but->rnapoin, but->rnaprop, but->rnaindex) :
RNA_property_float_get(&but->rnapoin, but->rnaprop);
if (RNA_property_type(rnaprop) == PROP_FLOAT) {
float value = RNA_property_array_check(rnaprop) ?
RNA_property_float_get_index(&but->rnapoin, rnaprop, but->rnaindex) :
RNA_property_float_get(&but->rnapoin, rnaprop);
uiTooltipField *field = text_field_add(data,
&(uiTooltipFormat){
@ -920,15 +929,15 @@ static uiTooltipData *ui_tooltip_data_from_button(bContext *C, uiBut *but)
}
}
}
else if (but->optype) {
PointerRNA *opptr;
char *str;
opptr = UI_but_operator_ptr_get(but); /* allocated when needed, the button owns it */
else if (optype) {
PointerRNA *opptr = extra_icon ? UI_but_extra_operator_icon_opptr_get(extra_icon) :
/* allocated when needed, the button owns it */
UI_but_operator_ptr_get(but);
/* so the context is passed to fieldf functions (some py fieldf functions use it) */
WM_operator_properties_sanitize(opptr, false);
str = ui_tooltip_text_python_from_op(C, but->optype, opptr);
char *str = ui_tooltip_text_python_from_op(C, optype, opptr);
/* operator info */
if (U.flag & USER_TOOLTIPS_PYTHON) {
@ -973,7 +982,7 @@ static uiTooltipData *ui_tooltip_data_from_button(bContext *C, uiBut *but)
}
}
if ((U.flag & USER_TOOLTIPS_PYTHON) && !but->optype && rna_struct.strinfo) {
if ((U.flag & USER_TOOLTIPS_PYTHON) && !optype && rna_struct.strinfo) {
{
uiTooltipField *field = text_field_add(data,
&(uiTooltipFormat){
@ -1002,9 +1011,9 @@ static uiTooltipData *ui_tooltip_data_from_button(bContext *C, uiBut *but)
/* never fails */
/* move ownership (no need for re-alloc) */
if (but->rnaprop) {
if (rnaprop) {
field->text = RNA_path_full_property_py_ex(
CTX_data_main(C), &but->rnapoin, but->rnaprop, but->rnaindex, true);
CTX_data_main(C), &but->rnapoin, rnaprop, but->rnaindex, true);
}
else {
field->text = RNA_path_full_struct_py(CTX_data_main(C), &but->rnapoin);
@ -1394,11 +1403,8 @@ static ARegion *ui_tooltip_create_with_data(bContext *C,
/** \name ToolTip Public API
* \{ */
/**
* \param is_label: When true, show a small tip that only shows the name,
* otherwise show the full tooltip.
*/
ARegion *UI_tooltip_create_from_button(bContext *C, ARegion *butregion, uiBut *but, bool is_label)
ARegion *UI_tooltip_create_from_button_or_extra_icon(
bContext *C, ARegion *butregion, uiBut *but, uiButExtraOpIcon *extra_icon, bool is_label)
{
wmWindow *win = CTX_wm_window(C);
/* aspect values that shrink text are likely unreadable */
@ -1415,7 +1421,11 @@ ARegion *UI_tooltip_create_from_button(bContext *C, ARegion *butregion, uiBut *b
}
if (data == NULL) {
data = ui_tooltip_data_from_button(C, but);
data = ui_tooltip_data_from_button_or_extra_icon(C, but, extra_icon);
}
if (data == NULL) {
data = ui_tooltip_data_from_button_or_extra_icon(C, but, NULL);
}
if (data == NULL) {
@ -1453,6 +1463,15 @@ ARegion *UI_tooltip_create_from_button(bContext *C, ARegion *butregion, uiBut *b
return region;
}
/**
* \param is_label: When true, show a small tip that only shows the name, otherwise show the full
* tooltip.
*/
ARegion *UI_tooltip_create_from_button(bContext *C, ARegion *butregion, uiBut *but, bool is_label)
{
return UI_tooltip_create_from_button_or_extra_icon(C, butregion, but, NULL, is_label);
}
ARegion *UI_tooltip_create_from_gizmo(bContext *C, wmGizmo *gz)
{
wmWindow *win = CTX_wm_window(C);