diff --git a/source/blender/editors/include/UI_interface.h b/source/blender/editors/include/UI_interface.h index 8fc455e446f..c94a890b357 100644 --- a/source/blender/editors/include/UI_interface.h +++ b/source/blender/editors/include/UI_interface.h @@ -73,6 +73,7 @@ struct bNodeSocket; struct wmDropBox; struct wmDrag; struct wmEvent; +struct wmManipulator; struct wmMsgBus; typedef struct uiBut uiBut; @@ -1137,6 +1138,7 @@ void UI_butstore_unregister(uiButStore *bs_handle, uiBut **but_p); /* ui_interface_region_tooltip.c */ struct ARegion *UI_tooltip_create_from_button(struct bContext *C, struct ARegion *butregion, uiBut *but); +struct ARegion *UI_tooltip_create_from_manipulator(struct bContext *C, struct wmManipulator *mpr); void UI_tooltip_free(struct bContext *C, struct ARegion *ar); /* How long before a tool-tip shows. */ diff --git a/source/blender/editors/interface/interface_region_tooltip.c b/source/blender/editors/interface/interface_region_tooltip.c index bdc13f58226..3ff3e7d7a21 100644 --- a/source/blender/editors/interface/interface_region_tooltip.c +++ b/source/blender/editors/interface/interface_region_tooltip.c @@ -620,6 +620,112 @@ static uiTooltipData *ui_tooltip_data_from_button(bContext *C, uiBut *but) } } +static uiTooltipData *ui_tooltip_data_from_manipulator(bContext *C, wmManipulator *mpr) +{ + uiTooltipData *data = MEM_callocN(sizeof(uiTooltipData), "uiTooltipData"); + + /* TODO(campbell): a way for manipulators to have their own descriptions (low priority). */ + + /* Operator Actions */ + { + bool use_drag = mpr->drag_part != -1 && mpr->highlight_part != mpr->drag_part; + + const struct { + int part; + const char *prefix; + } mpop_actions[] = { + { + .part = mpr->highlight_part, + .prefix = use_drag ? TIP_("Click") : NULL, + }, { + .part = use_drag ? mpr->drag_part : -1, + .prefix = use_drag ? TIP_("Drag") : NULL, + }, + }; + + for (int i = 0; i < ARRAY_SIZE(mpop_actions); i++) { + wmManipulatorOpElem *mpop = (mpop_actions[i].part != -1) ? WM_manipulator_operator_get(mpr, mpop_actions[i].part) : NULL; + if (mpop != NULL) { + /* Description */ + const char *info = RNA_struct_ui_description(mpop->type->srna); + if (!(info && info[0])) { + info = RNA_struct_ui_name(mpop->type->srna); + } + + if (info && info[0]) { + char *text = NULL; + if (mpop_actions[i].prefix != NULL) { + text = BLI_sprintfN("%s: %s", mpop_actions[i].prefix, info); + } + else { + text = BLI_strdup(info); + } + + if (text != NULL) { + uiTooltipField *field = text_field_add( + data, &(uiTooltipFormat){ + .style = UI_TIP_STYLE_HEADER, + .color_id = UI_TIP_LC_VALUE, + .is_pad = true, + }); + field->text = text; + } + } + + /* Shortcut */ + { + bool found = false; + IDProperty *prop = mpop->ptr.data; + char buf[128]; + if (WM_key_event_operator_string( + C, mpop->type->idname, WM_OP_INVOKE_DEFAULT, prop, true, + buf, ARRAY_SIZE(buf))) + { + found = true; + } + uiTooltipField *field = text_field_add( + data, &(uiTooltipFormat){ + .style = UI_TIP_STYLE_NORMAL, + .color_id = UI_TIP_LC_VALUE, + .is_pad = true, + }); + field->text = BLI_sprintfN(TIP_("Shortcut: %s"), found ? buf : "None"); + } + } + } + } + + /* Property Actions */ + if (mpr->type->target_property_defs_len) { + wmManipulatorProperty *mpr_prop_array = WM_manipulator_target_property_array(mpr); + for (int i = 0; i < mpr->type->target_property_defs_len; i++) { + /* TODO(campbell): function callback descriptions. */ + wmManipulatorProperty *mpr_prop = &mpr_prop_array[i]; + if (mpr_prop->prop != NULL) { + const char *info = RNA_property_ui_description(mpr_prop->prop); + if (info && info[0]) { + uiTooltipField *field = text_field_add( + data, &(uiTooltipFormat){ + .style = UI_TIP_STYLE_NORMAL, + .color_id = UI_TIP_LC_VALUE, + .is_pad = true, + }); + field->text = BLI_strdup(info); + } + } + } + } + + if (data->fields_len == 0) { + MEM_freeN(data); + return NULL; + } + else { + return data; + } +} + + static ARegion *ui_tooltip_create_with_data( bContext *C, uiTooltipData *data, const float init_position[2], @@ -827,6 +933,23 @@ ARegion *UI_tooltip_create_from_button(bContext *C, ARegion *butregion, uiBut *b return ui_tooltip_create_with_data(C, data, init_position, aspect); } +ARegion *UI_tooltip_create_from_manipulator(bContext *C, wmManipulator *mpr) +{ + wmWindow *win = CTX_wm_window(C); + const float aspect = 1.0f; + float init_position[2]; + + uiTooltipData *data = ui_tooltip_data_from_manipulator(C, mpr); + if (data == NULL) { + return NULL; + } + + init_position[0] = win->eventstate->x; + init_position[1] = win->eventstate->y; + + return ui_tooltip_create_with_data(C, data, init_position, aspect); +} + void UI_tooltip_free(bContext *C, ARegion *ar) { ui_region_temp_remove(C, CTX_wm_screen(C), ar); diff --git a/source/blender/windowmanager/intern/wm_event_system.c b/source/blender/windowmanager/intern/wm_event_system.c index b1ec1c009df..d50788063e2 100644 --- a/source/blender/windowmanager/intern/wm_event_system.c +++ b/source/blender/windowmanager/intern/wm_event_system.c @@ -2251,13 +2251,32 @@ static int wm_handlers_do_intern(bContext *C, wmEvent *event, ListBase *handlers wm_manipulatormap_handler_context(C, handler); wm_region_mouse_co(C, event); + if (event->type == MOUSEMOVE) { + WM_manipulatormap_tooltip_clear(C, mmap); + } + /* handle manipulator highlighting */ if (event->type == MOUSEMOVE && !wm_manipulatormap_modal_get(mmap)) { int part; mpr = wm_manipulatormap_highlight_find(mmap, C, event, &part); wm_manipulatormap_highlight_set(mmap, C, mpr, part); + if (mpr != NULL) { + WM_manipulatormap_tooltip_timer_init(C, mmap); + } } /* handle user configurable manipulator-map keymap */ + else if ((event->type == TIMER) && + (event->customdata == WM_manipulatormap_tooltip_timer_get(mmap))) + { + if (mpr) { + if (mpr->state & WM_MANIPULATOR_STATE_MODAL) { + WM_manipulatormap_tooltip_clear(C, mmap); + } + else { + WM_manipulatormap_tooltip_create(C, mmap); + } + } + } else { /* Either we operate on a single highlighted item * or groups attached to the selected manipulators. diff --git a/source/blender/windowmanager/manipulators/WM_manipulator_api.h b/source/blender/windowmanager/manipulators/WM_manipulator_api.h index 9214bccb6a0..7a31f4e6f1f 100644 --- a/source/blender/windowmanager/manipulators/WM_manipulator_api.h +++ b/source/blender/windowmanager/manipulators/WM_manipulator_api.h @@ -259,6 +259,15 @@ bool WM_manipulatormap_minmax( const struct wmManipulatorMap *mmap, bool use_hidden, bool use_select, float r_min[3], float r_max[3]); +void WM_manipulatormap_tooltip_create( + struct bContext *C, struct wmManipulatorMap *mmap); +void WM_manipulatormap_tooltip_clear( + struct bContext *C, struct wmManipulatorMap *mmap); +void WM_manipulatormap_tooltip_timer_init( + struct bContext *C, struct wmManipulatorMap *mmap); +const void *WM_manipulatormap_tooltip_timer_get( + struct wmManipulatorMap *mmap); + /* -------------------------------------------------------------------- */ /* wmManipulatorMapType */ diff --git a/source/blender/windowmanager/manipulators/intern/wm_manipulator_intern.h b/source/blender/windowmanager/manipulators/intern/wm_manipulator_intern.h index 419ac7d5521..fc814adaef1 100644 --- a/source/blender/windowmanager/manipulators/intern/wm_manipulator_intern.h +++ b/source/blender/windowmanager/manipulators/intern/wm_manipulator_intern.h @@ -115,6 +115,9 @@ struct wmManipulatorMap { /* cursor location at point of entering modal (see: WM_MANIPULATOR_GRAB_CURSOR) */ int event_xy[2]; short event_grabcursor; + + struct ARegion *tooltip; + struct wmTimer *tooltip_timer; } mmap_context; }; diff --git a/source/blender/windowmanager/manipulators/intern/wm_manipulator_map.c b/source/blender/windowmanager/manipulators/intern/wm_manipulator_map.c index 7e4d4f16646..ab7b4688d49 100644 --- a/source/blender/windowmanager/manipulators/intern/wm_manipulator_map.c +++ b/source/blender/windowmanager/manipulators/intern/wm_manipulator_map.c @@ -51,6 +51,9 @@ #include "WM_types.h" #include "wm_event_system.h" +/* for tool-tips */ +#include "UI_interface.h" + #include "DEG_depsgraph.h" /* own includes */ @@ -888,6 +891,8 @@ void wm_manipulatormap_modal_set( BLI_assert(mmap->mmap_context.modal == NULL); wmWindow *win = CTX_wm_window(C); + WM_manipulatormap_tooltip_clear(C, mmap); + /* For now only grab cursor for 3D manipulators. */ int retval = OPERATOR_RUNNING_MODAL; @@ -996,6 +1001,54 @@ void WM_manipulatormap_message_subscribe( /** \} */ /* wmManipulatorMap */ +/* -------------------------------------------------------------------- */ +/** \name Tooltip Handling + * + * \{ */ + + +void WM_manipulatormap_tooltip_create( + bContext *C, wmManipulatorMap *mmap) +{ + WM_manipulatormap_tooltip_clear(C, mmap); + if (mmap->mmap_context.highlight) { + mmap->mmap_context.tooltip = UI_tooltip_create_from_manipulator(C, mmap->mmap_context.highlight); + } +} + +void WM_manipulatormap_tooltip_clear( + bContext *C, wmManipulatorMap *mmap) +{ + if (mmap->mmap_context.tooltip_timer != NULL) { + wmWindowManager *wm = CTX_wm_manager(C); + wmWindow *win = CTX_wm_window(C); + WM_event_remove_timer(wm, win, mmap->mmap_context.tooltip_timer); + mmap->mmap_context.tooltip_timer = NULL; + } + if (mmap->mmap_context.tooltip != NULL) { + UI_tooltip_free(C, mmap->mmap_context.tooltip); + mmap->mmap_context.tooltip = NULL; + } +} + +void WM_manipulatormap_tooltip_timer_init( + bContext *C, wmManipulatorMap *mmap) +{ + if (mmap->mmap_context.tooltip_timer == NULL) { + wmWindowManager *wm = CTX_wm_manager(C); + wmWindow *win = CTX_wm_window(C); + /* TODO: BUTTON_TOOLTIP_DELAY */ + mmap->mmap_context.tooltip_timer = WM_event_add_timer(wm, win, TIMER, UI_TOOLTIP_DELAY); + } +} + +const void *WM_manipulatormap_tooltip_timer_get(wmManipulatorMap *mmap) +{ + return mmap->mmap_context.tooltip_timer; +} + +/** \} */ /* wmManipulatorMapType */ + /* -------------------------------------------------------------------- */ /** \name wmManipulatorMapType *