From c169f67dc118b9fe9f10c934e5c180f51770f573 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Fri, 17 Mar 2023 14:40:43 +1100 Subject: [PATCH] Fix #103263: Touchpad gestures changing pivot point of rotation/zooming Auto-depth is no longer reset during consecutive touch-pad motion. Details: - Add wmEvent::flag, WM_EVENT_IS_CONSECUTIVE to detect consecutive track-pad & NDOF motion events. Expose via RNA as Event.is_consecutive. - Consecutive events are broken by button/key presses and mouse motion. - Add `WM_event_consecutive_data_*` functions, so operators can store data between consecutive events. - Add `ED_view3d_autodist_last_*` functions to access the last autodist pivot point for view operators to use. --- source/blender/editors/include/ED_view3d.h | 15 ++++ .../editors/space_view3d/view3d_navigate.c | 23 ++++-- .../editors/space_view3d/view3d_utils.c | 71 +++++++++++++++++++ .../makesdna/DNA_windowmanager_types.h | 25 ++++--- source/blender/makesrna/intern/rna_wm.c | 15 ++++ source/blender/windowmanager/WM_api.h | 12 ++++ source/blender/windowmanager/WM_types.h | 10 ++- source/blender/windowmanager/intern/wm.c | 2 + .../windowmanager/intern/wm_event_query.c | 46 ++++++++++++ .../windowmanager/intern/wm_event_system.cc | 71 +++++++++++++++++++ .../blender/windowmanager/intern/wm_window.c | 3 + source/blender/windowmanager/wm_event_types.h | 6 +- 12 files changed, 283 insertions(+), 16 deletions(-) diff --git a/source/blender/editors/include/ED_view3d.h b/source/blender/editors/include/ED_view3d.h index dc3951ab770..9507ab4d476 100644 --- a/source/blender/editors/include/ED_view3d.h +++ b/source/blender/editors/include/ED_view3d.h @@ -48,6 +48,7 @@ struct bPoseChannel; struct bScreen; struct rctf; struct rcti; +struct wmEvent; struct wmGizmo; struct wmWindow; struct wmWindowManager; @@ -863,6 +864,20 @@ int ED_view3d_backbuf_sample_size_clamp(struct ARegion *region, float dist); void ED_view3d_select_id_validate(struct ViewContext *vc); +/** Check if the last auto-dist can be used. */ +bool ED_view3d_autodist_last_check(struct wmWindow *win, const struct wmEvent *event); +/** + * \return true when `r_ofs` is set. + * \warning #ED_view3d_autodist_last_check should be called first to ensure the data is available. + */ +bool ED_view3d_autodist_last_get(struct wmWindow *win, float r_ofs[3]); +void ED_view3d_autodist_last_set(struct wmWindow *win, + const struct wmEvent *event, + const float ofs[3], + const bool has_depth); +/** Clear and free auto-dist data. */ +void ED_view3d_autodist_last_clear(struct wmWindow *win); + /** * Get the world-space 3d location from a screen-space 2d point. * TODO: Implement #alphaoverride. We don't want to zoom into billboards. diff --git a/source/blender/editors/space_view3d/view3d_navigate.c b/source/blender/editors/space_view3d/view3d_navigate.c index 9f4710b28a0..d47f7325cc5 100644 --- a/source/blender/editors/space_view3d/view3d_navigate.c +++ b/source/blender/editors/space_view3d/view3d_navigate.c @@ -274,16 +274,29 @@ ViewOpsData *viewops_data_create(bContext *C, const wmEvent *event, enum eViewOp /* we need the depth info before changing any viewport options */ if (viewops_flag & VIEWOPS_FLAG_DEPTH_NAVIGATE) { - float fallback_depth_pt[3]; + wmWindow *win = CTX_wm_window(C); + const bool use_depth_last = ED_view3d_autodist_last_check(win, event); - view3d_operator_needs_opengl(C); /* Needed for Z-buffer drawing. */ + if (use_depth_last) { + vod->use_dyn_ofs = ED_view3d_autodist_last_get(win, vod->dyn_ofs); + } + else { + float fallback_depth_pt[3]; - negate_v3_v3(fallback_depth_pt, rv3d->ofs); + view3d_operator_needs_opengl(C); /* Needed for Z-buffer drawing. */ - vod->use_dyn_ofs = ED_view3d_autodist( - depsgraph, vod->region, vod->v3d, event->mval, vod->dyn_ofs, true, fallback_depth_pt); + negate_v3_v3(fallback_depth_pt, rv3d->ofs); + + vod->use_dyn_ofs = ED_view3d_autodist( + depsgraph, vod->region, vod->v3d, event->mval, vod->dyn_ofs, true, fallback_depth_pt); + + ED_view3d_autodist_last_set(win, event, vod->dyn_ofs, vod->use_dyn_ofs); + } } else { + wmWindow *win = CTX_wm_window(C); + ED_view3d_autodist_last_clear(win); + vod->use_dyn_ofs = false; } vod->init.persp = rv3d->persp; diff --git a/source/blender/editors/space_view3d/view3d_utils.c b/source/blender/editors/space_view3d/view3d_utils.c index d87a2730c7b..385ab423155 100644 --- a/source/blender/editors/space_view3d/view3d_utils.c +++ b/source/blender/editors/space_view3d/view3d_utils.c @@ -1005,6 +1005,77 @@ void ED_view3d_quadview_update(ScrArea *area, ARegion *region, bool do_clip) /** \} */ +/* -------------------------------------------------------------------- */ +/** \name View Auto-Depth Last State Access + * + * Calling consecutive track-pad gestures reuses the previous offset to prevent + * each track-pad event using a different offset, see: #103263. + * \{ */ + +static const char *view3d_autodepth_last_id = "view3d_autodist_last"; + +/** + * Auto-depth values for #ED_view3d_autodist_last_check and related functions. + */ +typedef struct View3D_AutoDistLast { + float ofs[3]; + bool has_depth; +} View3D_AutoDistLast; + +bool ED_view3d_autodist_last_check(wmWindow *win, const wmEvent *event) +{ + if (event->flag & WM_EVENT_IS_CONSECUTIVE) { + const View3D_AutoDistLast *autodepth_last = WM_event_consecutive_data_get( + win, view3d_autodepth_last_id); + if (autodepth_last) { + return true; + } + } + return false; +} + +void ED_view3d_autodist_last_clear(wmWindow *win) +{ + WM_event_consecutive_data_free(win); +} + +void ED_view3d_autodist_last_set(wmWindow *win, + const wmEvent *event, + const float ofs[3], + const bool has_depth) +{ + ED_view3d_autodist_last_clear(win); + + if (WM_event_consecutive_gesture_test(event)) { + View3D_AutoDistLast *autodepth_last = MEM_callocN(sizeof(*autodepth_last), __func__); + + autodepth_last->has_depth = has_depth; + copy_v3_v3(autodepth_last->ofs, ofs); + + WM_event_consecutive_data_set(win, view3d_autodepth_last_id, autodepth_last); + } +} + +bool ED_view3d_autodist_last_get(wmWindow *win, float r_ofs[3]) +{ + const View3D_AutoDistLast *autodepth_last = WM_event_consecutive_data_get( + win, view3d_autodepth_last_id); + /* #ED_view3d_autodist_last_check should be called first. */ + BLI_assert(autodepth_last); + if (autodepth_last == NULL) { + return false; + } + + if (autodepth_last->has_depth == false) { + zero_v3(r_ofs); + return false; + } + copy_v3_v3(r_ofs, autodepth_last->ofs); + return true; +} + +/** \} */ + /* -------------------------------------------------------------------- */ /** \name View Auto-Depth Utilities * \{ */ diff --git a/source/blender/makesdna/DNA_windowmanager_types.h b/source/blender/makesdna/DNA_windowmanager_types.h index e6f48c2da46..b1108f2cf76 100644 --- a/source/blender/makesdna/DNA_windowmanager_types.h +++ b/source/blender/makesdna/DNA_windowmanager_types.h @@ -22,6 +22,7 @@ extern "C" { struct wmWindow; struct wmWindowManager; +struct wmEvent_ConsecutiveData; struct wmEvent; struct wmKeyConfig; struct wmKeyMap; @@ -280,6 +281,15 @@ typedef struct wmWindow { short grabcursor; /** Internal: tag this for extra mouse-move event, * makes cursors/buttons active on UI switching. */ + + /** Internal, lock pie creation from this event until released. */ + short pie_event_type_lock; + /** + * Exception to the above rule for nested pies, store last pie event for operators + * that spawn a new pie right after destruction of last pie. + */ + short pie_event_type_last; + char addmousemove; char tag_cursor_refresh; @@ -296,15 +306,12 @@ typedef struct wmWindow { */ char event_queue_check_drag_handled; - char _pad0[1]; - - /** Internal, lock pie creation from this event until released. */ - short pie_event_type_lock; - /** - * Exception to the above rule for nested pies, store last pie event for operators - * that spawn a new pie right after destruction of last pie. - */ - short pie_event_type_last; + /** The last event type (that passed #WM_event_consecutive_gesture_test check). */ + char event_queue_consecutive_gesture_type; + /** The cursor location when `event_queue_consecutive_gesture_type` was set. */ + int event_queue_consecutive_gesture_xy[2]; + /** See #WM_event_consecutive_data_get and related API. Freed when consecutive events end. */ + struct wmEvent_ConsecutiveData *event_queue_consecutive_gesture_data; /** * Storage for event system. diff --git a/source/blender/makesrna/intern/rna_wm.c b/source/blender/makesrna/intern/rna_wm.c index 12440f60b90..646cfed891b 100644 --- a/source/blender/makesrna/intern/rna_wm.c +++ b/source/blender/makesrna/intern/rna_wm.c @@ -664,6 +664,12 @@ static bool rna_Event_is_repeat_get(PointerRNA *ptr) return (event->flag & WM_EVENT_IS_REPEAT) != 0; } +static bool rna_Event_is_consecutive_get(PointerRNA *ptr) +{ + const wmEvent *event = ptr->data; + return (event->flag & WM_EVENT_IS_CONSECUTIVE) != 0; +} + static float rna_Event_pressure_get(PointerRNA *ptr) { const wmEvent *event = ptr->data; @@ -2154,6 +2160,15 @@ static void rna_def_event(BlenderRNA *brna) RNA_def_property_boolean_funcs(prop, "rna_Event_is_repeat_get", NULL); RNA_def_property_ui_text(prop, "Is Repeat", "The event is generated by holding a key down"); + /* Track-pad & NDOF. */ + prop = RNA_def_property(srna, "is_consecutive", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_clear_flag(prop, PROP_EDITABLE); + RNA_def_property_boolean_funcs(prop, "rna_Event_is_consecutive_get", NULL); + RNA_def_property_ui_text(prop, + "Is Consecutive", + "Part of a track-pad or NDOF motion, " + "interrupted by cursor motion, button or key press events"); + /* mouse */ prop = RNA_def_property(srna, "mouse_x", PROP_INT, PROP_NONE); RNA_def_property_int_sdna(prop, NULL, "xy[0]"); diff --git a/source/blender/windowmanager/WM_api.h b/source/blender/windowmanager/WM_api.h index 6c3d72e5406..0d970685582 100644 --- a/source/blender/windowmanager/WM_api.h +++ b/source/blender/windowmanager/WM_api.h @@ -1251,6 +1251,15 @@ bool WM_gesture_is_modal_first(const struct wmGesture *gesture); void WM_event_add_fileselect(struct bContext *C, struct wmOperator *op); void WM_event_fileselect_event(struct wmWindowManager *wm, void *ophandle, int eventval); +/* Event consecutive data. */ + +/** Return a borrowed reference to the custom-data. */ +void *WM_event_consecutive_data_get(wmWindow *win, const char *id); +/** Set the custom-data (and own the pointer), free with #MEM_freeN. */ +void WM_event_consecutive_data_set(wmWindow *win, const char *id, void *custom_data); +/** Clear and free the consecutive custom-data. */ +void WM_event_consecutive_data_free(wmWindow *win); + /** * Sets the active region for this space from the context. * @@ -1630,6 +1639,9 @@ char WM_event_utf8_to_ascii(const struct wmEvent *event) ATTR_NONNULL(1) ATTR_WA */ bool WM_cursor_test_motion_and_update(const int mval[2]) ATTR_NONNULL(1) ATTR_WARN_UNUSED_RESULT; +bool WM_event_consecutive_gesture_test(const wmEvent *event); +bool WM_event_consecutive_gesture_test_break(const wmWindow *win, const wmEvent *event); + int WM_event_drag_threshold(const struct wmEvent *event); bool WM_event_drag_test(const struct wmEvent *event, const int prev_xy[2]); bool WM_event_drag_test_with_delta(const struct wmEvent *event, const int delta[2]); diff --git a/source/blender/windowmanager/WM_types.h b/source/blender/windowmanager/WM_types.h index 06c738c83a3..fa06591a35f 100644 --- a/source/blender/windowmanager/WM_types.h +++ b/source/blender/windowmanager/WM_types.h @@ -614,11 +614,19 @@ typedef enum eWM_EventFlag { * See #KMI_REPEAT_IGNORE for details on how key-map handling uses this. */ WM_EVENT_IS_REPEAT = (1 << 1), + /** + * Generated for consecutive track-pad or NDOF-motion events, + * the repeat chain is broken by key/button events, + * or cursor motion exceeding #WM_EVENT_CURSOR_MOTION_THRESHOLD. + * + * Changing the type of track-pad or gesture event also breaks the chain. + */ + WM_EVENT_IS_CONSECUTIVE = (1 << 2), /** * Mouse-move events may have this flag set to force creating a click-drag event * even when the threshold has not been met. */ - WM_EVENT_FORCE_DRAG_THRESHOLD = (1 << 2), + WM_EVENT_FORCE_DRAG_THRESHOLD = (1 << 3), } eWM_EventFlag; ENUM_OPERATORS(eWM_EventFlag, WM_EVENT_FORCE_DRAG_THRESHOLD); diff --git a/source/blender/windowmanager/intern/wm.c b/source/blender/windowmanager/intern/wm.c index 671a69003dd..16c36a270bf 100644 --- a/source/blender/windowmanager/intern/wm.c +++ b/source/blender/windowmanager/intern/wm.c @@ -179,6 +179,8 @@ static void window_manager_blend_read_data(BlendDataReader *reader, ID *id) win->event_queue_check_click = 0; win->event_queue_check_drag = 0; win->event_queue_check_drag_handled = 0; + win->event_queue_consecutive_gesture_type = 0; + win->event_queue_consecutive_gesture_data = NULL; BLO_read_data_address(reader, &win->stereo3d_format); /* Multi-view always fallback to anaglyph at file opening diff --git a/source/blender/windowmanager/intern/wm_event_query.c b/source/blender/windowmanager/intern/wm_event_query.c index 2e1afe808ad..d39a853ead1 100644 --- a/source/blender/windowmanager/intern/wm_event_query.c +++ b/source/blender/windowmanager/intern/wm_event_query.c @@ -103,6 +103,7 @@ void WM_event_print(const wmEvent *event) struct FlagIdentifierPair flag_data[] = { {"SCROLL_INVERT", WM_EVENT_SCROLL_INVERT}, {"IS_REPEAT", WM_EVENT_IS_REPEAT}, + {"IS_CONSECUTIVE", WM_EVENT_IS_CONSECUTIVE}, {"FORCE_DRAG_THRESHOLD", WM_EVENT_FORCE_DRAG_THRESHOLD}, }; event_ids_from_flag(flag_id, sizeof(flag_id), flag_data, ARRAY_SIZE(flag_data), event->flag); @@ -336,6 +337,51 @@ bool WM_cursor_test_motion_and_update(const int mval[2]) /** \} */ +/* -------------------------------------------------------------------- */ +/** \name Event Consecutive Checks + * \{ */ + +/** + * Return true if this event type is a candidate for being flagged as consecutive. + * + * See: #WM_EVENT_IS_CONSECUTIVE doc-string. + */ +bool WM_event_consecutive_gesture_test(const wmEvent *event) +{ + return ISMOUSE_GESTURE(event->type) || (event->type == NDOF_MOTION); +} + +/** + * Return true if this event should break the chain of consecutive gestures. + * Practically all intentional user input should, key presses or button clicks. + */ +bool WM_event_consecutive_gesture_test_break(const wmWindow *win, const wmEvent *event) +{ + /* Cursor motion breaks the chain. */ + if (ISMOUSE_MOTION(event->type)) { + /* Mouse motion is checked because the user may navigate to a new area + * and perform the same gesture - logically it's best to view this as two separate gestures. */ + if (len_manhattan_v2v2_int(event->xy, win->event_queue_consecutive_gesture_xy) > + WM_EVENT_CURSOR_MOTION_THRESHOLD) { + return true; + } + } + else if (ISKEYBOARD_OR_BUTTON(event->type)) { + /* Modifiers are excluded because from a user perspective, + * releasing a modifier (for e.g.) should not begin a new action. */ + if (!ISKEYMODIFIER(event->type)) { + return true; + } + } + else if (event->type == WINDEACTIVATE) { + return true; + } + + return false; +} + +/** \} */ + /* -------------------------------------------------------------------- */ /** \name Event Click/Drag Checks * diff --git a/source/blender/windowmanager/intern/wm_event_system.cc b/source/blender/windowmanager/intern/wm_event_system.cc index 52e0f43567e..cf779f1bdf9 100644 --- a/source/blender/windowmanager/intern/wm_event_system.cc +++ b/source/blender/windowmanager/intern/wm_event_system.cc @@ -3955,6 +3955,27 @@ void wm_event_do_handlers(bContext *C) } const bool event_queue_check_drag_prev = win->event_queue_check_drag; + { + const bool is_consecutive = WM_event_consecutive_gesture_test(event); + if (win->event_queue_consecutive_gesture_type != 0) { + if (event->type == win->event_queue_consecutive_gesture_type) { + event->flag |= WM_EVENT_IS_CONSECUTIVE; + } + else if (is_consecutive || WM_event_consecutive_gesture_test_break(win, event)) { + CLOG_INFO(WM_LOG_HANDLERS, 1, "consecutive gesture break (%d)", event->type); + win->event_queue_consecutive_gesture_type = 0; + WM_event_consecutive_data_free(win); + } + } + else if (is_consecutive) { + CLOG_INFO(WM_LOG_HANDLERS, 1, "consecutive gesture begin (%d)", event->type); + win->event_queue_consecutive_gesture_type = event->type; + copy_v2_v2_int(win->event_queue_consecutive_gesture_xy, event->xy); + /* While this should not be set, it's harmless to free here. */ + WM_event_consecutive_data_free(win); + } + } + /* Active screen might change during handlers, update pointer. */ screen = WM_window_get_active_screen(win); @@ -4298,6 +4319,56 @@ void WM_event_add_fileselect(bContext *C, wmOperator *op) /** \} */ +/* -------------------------------------------------------------------- */ +/** \name Consecutive Event Access + * \{ */ + +using wmEvent_ConsecutiveData = struct wmEvent_ConsecutiveData { + /** Owned custom-data. */ + void *custom_data; + /** Unique identifier per struct type. */ + char id[0]; +}; + +void *WM_event_consecutive_data_get(wmWindow *win, const char *id) +{ + wmEvent_ConsecutiveData *cdata = win->event_queue_consecutive_gesture_data; + if (cdata && STREQ(cdata->id, id)) { + return cdata->custom_data; + } + return nullptr; +} + +void WM_event_consecutive_data_set(wmWindow *win, const char *id, void *custom_data) +{ + if (win->event_queue_consecutive_gesture_data) { + WM_event_consecutive_data_free(win); + } + + const size_t id_size = strlen(id) + 1; + wmEvent_ConsecutiveData *cdata = static_cast( + MEM_mallocN(sizeof(*cdata) + id_size, __func__)); + cdata->custom_data = custom_data; + memcpy((cdata + 1), id, id_size); + win->event_queue_consecutive_gesture_data = cdata; +} + +void WM_event_consecutive_data_free(wmWindow *win) +{ + wmEvent_ConsecutiveData *cdata = win->event_queue_consecutive_gesture_data; + if (cdata == nullptr) { + return; + } + + if (cdata->custom_data) { + MEM_freeN(cdata->custom_data); + } + MEM_freeN(cdata); + win->event_queue_consecutive_gesture_data = nullptr; +} + +/** \} */ + /* -------------------------------------------------------------------- */ /** \name Modal Operator Handling * \{ */ diff --git a/source/blender/windowmanager/intern/wm_window.c b/source/blender/windowmanager/intern/wm_window.c index d569ca0ecfc..186d74620e4 100644 --- a/source/blender/windowmanager/intern/wm_window.c +++ b/source/blender/windowmanager/intern/wm_window.c @@ -256,6 +256,9 @@ void wm_window_free(bContext *C, wmWindowManager *wm, wmWindow *win) if (win->event_last_handled) { MEM_freeN(win->event_last_handled); } + if (win->event_queue_consecutive_gesture_data) { + WM_event_consecutive_data_free(win); + } if (win->cursor_keymap_status) { MEM_freeN(win->cursor_keymap_status); diff --git a/source/blender/windowmanager/wm_event_types.h b/source/blender/windowmanager/wm_event_types.h index c7678223ce4..e17e1ae512e 100644 --- a/source/blender/windowmanager/wm_event_types.h +++ b/source/blender/windowmanager/wm_event_types.h @@ -60,7 +60,7 @@ enum { /* More mouse buttons - can't use 9 and 10 here (wheel) */ BUTTON6MOUSE = 0x0012, BUTTON7MOUSE = 0x0013, - /* Extra track-pad gestures. */ + /* Extra track-pad gestures (check #WM_EVENT_IS_CONSECUTIVE to detect motion events). */ MOUSEPAN = 0x000e, MOUSEZOOM = 0x000f, MOUSEROTATE = 0x0010, @@ -247,6 +247,10 @@ enum { * These must be kept in sync with `GHOST_NDOFManager.h`. * Ordering matters, exact values do not. */ + /** + * Motion from 3D input (translation & rotation). + * Check #WM_EVENT_IS_CONSECUTIVE to detect motion events. + */ NDOF_MOTION = 0x0190, /* 400 */ #define _NDOF_MIN NDOF_MOTION