diff --git a/scripts/presets/keyconfig/keymap_data/blender_default.py b/scripts/presets/keyconfig/keymap_data/blender_default.py index 5ac459d1d7c..914a6daf812 100644 --- a/scripts/presets/keyconfig/keymap_data/blender_default.py +++ b/scripts/presets/keyconfig/keymap_data/blender_default.py @@ -1042,7 +1042,7 @@ def km_user_interface(_params): ("ui.view_scroll", {"type": 'WHEELUPMOUSE', "value": 'ANY'}, None), ("ui.view_scroll", {"type": 'WHEELDOWNMOUSE', "value": 'ANY'}, None), ("ui.view_scroll", {"type": 'TRACKPADPAN', "value": 'ANY'}, None), - ("ui.view_item_select", {"type": 'LEFTMOUSE', "value": 'CLICK'}, None), + ("ui.view_item_select", {"type": 'LEFTMOUSE', "value": 'PRESS'}, None), ("ui.view_item_select", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True}, {"properties": [("extend", True)]}), ("ui.view_item_select", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True}, diff --git a/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py b/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py index 2a0f3345a10..08bff5411ea 100644 --- a/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py +++ b/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py @@ -420,7 +420,7 @@ def km_user_interface(params): ("anim.driver_button_remove", {"type": 'D', "value": 'PRESS', "alt": True}, None), ("anim.keyingset_button_add", {"type": 'K', "value": 'PRESS'}, None), ("anim.keyingset_button_remove", {"type": 'K', "value": 'PRESS', "alt": True}, None), - ("ui.view_item_select", {"type": 'LEFTMOUSE', "value": 'CLICK'}, None), + ("ui.view_item_select", {"type": 'LEFTMOUSE', "value": 'PRESS'}, None), ]) return keymap diff --git a/source/blender/editors/asset/intern/asset_shelf_asset_view.cc b/source/blender/editors/asset/intern/asset_shelf_asset_view.cc index 88c37db87d9..403bc69f71c 100644 --- a/source/blender/editors/asset/intern/asset_shelf_asset_view.cc +++ b/source/blender/editors/asset/intern/asset_shelf_asset_view.cc @@ -124,6 +124,12 @@ void AssetView::build_items() if (shelf_.type->flag & ASSET_SHELF_TYPE_FLAG_NO_ASSET_DRAG) { item.disable_asset_drag(); } + if (!shelf_.type->drag_operator.empty()) { + /* For now always select/activate items on click instead of press when there's a drag + * operator set. Important for pose library blending. Maybe we want to make this an explicit + * option of the asset shelf instead. */ + item.select_on_click_set(); + } /* Make sure every click calls the #bl_activate_operator. We might want to add a flag to * enable/disable this. Or we only call #bl_activate_operator when an item becomes active, and * add a #bl_click_operator for repeated execution on every click. So far it seems like every diff --git a/source/blender/editors/include/UI_abstract_view.hh b/source/blender/editors/include/UI_abstract_view.hh index 2978291a7f4..1ee42112181 100644 --- a/source/blender/editors/include/UI_abstract_view.hh +++ b/source/blender/editors/include/UI_abstract_view.hh @@ -216,6 +216,8 @@ class AbstractViewItem { * children currently. */ bool is_always_collapsible_ = false; + /** See #select_on_click_set(). */ + bool select_on_click_ = false; /** See #always_reactivate_on_click(). */ bool reactivate_on_click_ = false; /** See #activate_for_context_menu_set(). */ @@ -312,6 +314,13 @@ class AbstractViewItem { bool is_interactive() const; void disable_activatable(); + /** + * Configure this view item to only select/activate on mouse-click (i.e. when the mouse is + * pressed and released without much movement in-between); the default is to select/activate on + * mouse-press. + */ + void select_on_click_set(); + bool is_select_on_click() const; /** Call #on_activate() on every click on the item, even when the item was active before. */ void always_reactivate_on_click(); /** Call #on_activate() when spawning a context menu. Otherwise the item will only be highlighted diff --git a/source/blender/editors/interface/interface.cc b/source/blender/editors/interface/interface.cc index bd3673d4381..70b1dc40776 100644 --- a/source/blender/editors/interface/interface.cc +++ b/source/blender/editors/interface/interface.cc @@ -264,6 +264,13 @@ void ui_region_to_window(const ARegion *region, int *x, int *y) *y += region->winrct.ymin; } +void ui_region_to_window( + const ARegion *region, int region_x, int region_y, int *r_window_x, int *r_window_y) +{ + *r_window_x = region_x + region->winrct.xmin; + *r_window_y = region_y + region->winrct.ymin; +} + int uiBlock::but_index(const uiBut *but) const { BLI_assert(!buttons.is_empty() && but); diff --git a/source/blender/editors/interface/interface_handlers.cc b/source/blender/editors/interface/interface_handlers.cc index a128fc0f7ca..1291e459929 100644 --- a/source/blender/editors/interface/interface_handlers.cc +++ b/source/blender/editors/interface/interface_handlers.cc @@ -5142,7 +5142,7 @@ static int ui_do_but_VIEW_ITEM(bContext *C, BLI_assert(view_item_but->type == ButType::ViewItem); if (data->state == BUTTON_STATE_HIGHLIGHT) { - if ((event->type == LEFTMOUSE) && (event->modifier == 0)) { + if (event->type == LEFTMOUSE) { switch (event->val) { case KM_PRESS: /* Extra icons have priority, don't mess with them. */ @@ -5155,9 +5155,6 @@ static int ui_do_but_VIEW_ITEM(bContext *C, data->dragstartx = event->xy[0]; data->dragstarty = event->xy[1]; } - else { - force_activate_view_item_but(C, data->region, view_item_but); - } /* Always continue for drag and drop handling. Also for cases where keymap items are * registered to add custom activate or drag operators (the pose library does this for diff --git a/source/blender/editors/interface/interface_intern.hh b/source/blender/editors/interface/interface_intern.hh index 7f31beb1122..7399695230c 100644 --- a/source/blender/editors/interface/interface_intern.hh +++ b/source/blender/editors/interface/interface_intern.hh @@ -756,6 +756,8 @@ void ui_window_to_region(const ARegion *region, int *x, int *y); void ui_window_to_region_rcti(const ARegion *region, rcti *rect_dst, const rcti *rct_src); void ui_window_to_region_rctf(const ARegion *region, rctf *rect_dst, const rctf *rct_src); void ui_region_to_window(const ARegion *region, int *x, int *y); +void ui_region_to_window( + const ARegion *region, int region_x, int region_y, int *r_window_x, int *r_window_y); /** * Popups will add a margin to #ARegion.winrct for shadow, * for interactivity (point-inside tests for eg), we want the winrct without the margin added. diff --git a/source/blender/editors/interface/interface_ops.cc b/source/blender/editors/interface/interface_ops.cc index 36ec88cae43..dd469f84976 100644 --- a/source/blender/editors/interface/interface_ops.cc +++ b/source/blender/editors/interface/interface_ops.cc @@ -2771,27 +2771,32 @@ static void UI_OT_view_item_rename(wmOperatorType *ot) ot->flag = OPTYPE_INTERNAL; } -static wmOperatorStatus ui_view_item_select_invoke(bContext *C, - wmOperator *op, - const wmEvent *event) +static wmOperatorStatus view_item_click_select(bContext &C, + AbstractViewItem *clicked_item, + const AbstractView &view, + const bool extend, + const bool range_select, + bool wait_to_deselect_others) { - ARegion ®ion = *CTX_wm_region(C); + const bool already_selected = clicked_item && clicked_item->is_selected(); - AbstractViewItem *clicked_item = UI_region_views_find_item_at(region, event->xy); - if (clicked_item == nullptr) { - return OPERATOR_CANCELLED; + if (extend || range_select) { + wait_to_deselect_others = false; } - AbstractView &view = clicked_item->get_view(); - const bool is_multiselect = view.is_multiselect_supported(); - const bool extend = RNA_boolean_get(op->ptr, "extend") && is_multiselect; - const bool range_select = RNA_boolean_get(op->ptr, "range_select") && is_multiselect; + if (clicked_item && already_selected && wait_to_deselect_others) { + return OPERATOR_RUNNING_MODAL; + } if (!extend) { - /* Keep previous selection for extend selection, see: !138979. */ view.foreach_view_item([](AbstractViewItem &item) { item.set_selected(false); }); } + if (clicked_item == nullptr) { + /* Only clear selection (if needed). */ + return OPERATOR_FINISHED; + } + if (range_select) { bool is_inside_range = false; view.foreach_view_item([&](AbstractViewItem &item) { @@ -2805,26 +2810,85 @@ static wmOperatorStatus ui_view_item_select_invoke(bContext *C, item.set_selected(true); } }); - ED_region_tag_redraw(®ion); return OPERATOR_FINISHED; } - clicked_item->activate(*C); + clicked_item->activate(C); return OPERATOR_FINISHED; } +static std::pair select_operator_view_and_item_find_xy( + const ARegion ®ion, const wmOperator &op) +{ + /* Mouse coordinates in window space. */ + int window_xy[2]; + { + /* Mouse coordinates in region space. */ + int region_xy[2]; + region_xy[0] = RNA_int_get(op.ptr, "mouse_x"); + region_xy[1] = RNA_int_get(op.ptr, "mouse_y"); + ui_region_to_window(®ion, region_xy[0], region_xy[1], &window_xy[0], &window_xy[1]); + } + + AbstractView *view = UI_region_view_find_at(®ion, window_xy, 0); + AbstractViewItem *item = UI_region_views_find_item_at(region, window_xy); + BLI_assert(!item || &item->get_view() == view); + + return std::make_pair(view, item); +} + +static wmOperatorStatus ui_view_item_select_exec(bContext *C, wmOperator *op) +{ + ARegion ®ion = *CTX_wm_region(C); + auto [view, clicked_item] = select_operator_view_and_item_find_xy(region, *op); + + if (!view) { + return OPERATOR_CANCELLED; + } + + const bool is_multiselect = view->is_multiselect_supported(); + const bool extend = RNA_boolean_get(op->ptr, "extend") && is_multiselect; + const bool range_select = RNA_boolean_get(op->ptr, "range_select") && is_multiselect; + const bool wait_to_deselect_others = RNA_boolean_get(op->ptr, "wait_to_deselect_others"); + + const wmOperatorStatus status = view_item_click_select( + *C, clicked_item, *view, extend, range_select, wait_to_deselect_others); + + ED_region_tag_redraw(®ion); + + return status; +} + +static wmOperatorStatus ui_view_item_select_invoke(bContext *C, + wmOperator *op, + const wmEvent *event) +{ + const ARegion ®ion = *CTX_wm_region(C); + const AbstractViewItem *clicked_item = UI_region_views_find_item_at(region, event->xy); + + /* Wait with selecting to see if there's a click or drag event, if requested by the view item. */ + if (clicked_item && clicked_item->is_select_on_click()) { + RNA_boolean_set(op->ptr, "use_select_on_click", true); + } + + return WM_generic_select_invoke(C, op, event); +} + static void UI_OT_view_item_select(wmOperatorType *ot) { ot->name = "Select View Item"; ot->idname = "UI_OT_view_item_select"; ot->description = "Activate selected view item"; + ot->exec = ui_view_item_select_exec; ot->invoke = ui_view_item_select_invoke; + ot->modal = WM_generic_select_modal; ot->poll = ui_view_focused_poll; ot->flag = OPTYPE_INTERNAL; + WM_operator_properties_generic_select(ot); PropertyRNA *prop = RNA_def_boolean(ot->srna, "extend", false, "extend", "Extend Selection"); RNA_def_property_flag(prop, PROP_SKIP_SAVE); prop = RNA_def_boolean(ot->srna, diff --git a/source/blender/editors/interface/views/abstract_view_item.cc b/source/blender/editors/interface/views/abstract_view_item.cc index 07afc96987e..6a8c4488cf5 100644 --- a/source/blender/editors/interface/views/abstract_view_item.cc +++ b/source/blender/editors/interface/views/abstract_view_item.cc @@ -340,6 +340,16 @@ void AbstractViewItem::disable_activatable() is_activatable_ = false; } +void AbstractViewItem::select_on_click_set() +{ + select_on_click_ = true; +} + +bool AbstractViewItem::is_select_on_click() const +{ + return select_on_click_; +} + void AbstractViewItem::always_reactivate_on_click() { reactivate_on_click_ = true; diff --git a/source/blender/windowmanager/intern/wm_operator_props.cc b/source/blender/windowmanager/intern/wm_operator_props.cc index 04ed0a6c460..5b016887737 100644 --- a/source/blender/windowmanager/intern/wm_operator_props.cc +++ b/source/blender/windowmanager/intern/wm_operator_props.cc @@ -512,6 +512,16 @@ void WM_operator_properties_generic_select(wmOperatorType *ot) ot->srna, "wait_to_deselect_others", false, "Wait to Deselect Others", ""); RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); + /* Force the selection to act on mouse click, not press. Necessary for some cases, but isn't used + * much. */ + prop = RNA_def_boolean(ot->srna, + "use_select_on_click", + false, + "Act on Click", + "Instead of selecting on mouse press, wait to see if there's drag event. " + "Otherwise select on mouse release"); + RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); + RNA_def_int(ot->srna, "mouse_x", 0, INT_MIN, INT_MAX, "Mouse X", "", INT_MIN, INT_MAX); RNA_def_int(ot->srna, "mouse_y", 0, INT_MIN, INT_MAX, "Mouse Y", "", INT_MIN, INT_MAX); } diff --git a/source/blender/windowmanager/intern/wm_operators.cc b/source/blender/windowmanager/intern/wm_operators.cc index 08d73a7d3a7..5d79e2e2a21 100644 --- a/source/blender/windowmanager/intern/wm_operators.cc +++ b/source/blender/windowmanager/intern/wm_operators.cc @@ -982,19 +982,27 @@ wmOperatorStatus WM_generic_select_modal(bContext *C, wmOperator *op, const wmEv { PropertyRNA *wait_to_deselect_prop = RNA_struct_find_property(op->ptr, "wait_to_deselect_others"); + const bool use_select_on_click = RNA_struct_property_is_set(op->ptr, "use_select_on_click"); const short init_event_type = short(POINTER_AS_INT(op->customdata)); /* Get settings from RNA properties for operator. */ const int mval[2] = {RNA_int_get(op->ptr, "mouse_x"), RNA_int_get(op->ptr, "mouse_y")}; if (init_event_type == 0) { + op->customdata = POINTER_FROM_INT(int(event->type)); + + if (use_select_on_click) { + /* Don't do any selection yet. Wait to see if there's a drag or click (release) event. */ + WM_event_add_modal_handler(C, op); + return OPERATOR_RUNNING_MODAL | OPERATOR_PASS_THROUGH; + } + if (event->val == KM_PRESS) { RNA_property_boolean_set(op->ptr, wait_to_deselect_prop, true); wmOperatorStatus retval = op->type->exec(C, op); OPERATOR_RETVAL_CHECK(retval); - op->customdata = POINTER_FROM_INT(int(event->type)); if (retval & OPERATOR_RUNNING_MODAL) { WM_event_add_modal_handler(C, op); }