diff --git a/scripts/addons_core/pose_library/gui.py b/scripts/addons_core/pose_library/gui.py index 44b4527f0ed..0dad2780d6b 100644 --- a/scripts/addons_core/pose_library/gui.py +++ b/scripts/addons_core/pose_library/gui.py @@ -40,9 +40,8 @@ class PoseLibraryPanel: class VIEW3D_AST_pose_library(bpy.types.AssetShelf): bl_space_type = "VIEW_3D" - # We have own keymap items to add custom drag behavior (pose blending), disable the default - # asset dragging. - bl_options = {'NO_ASSET_DRAG'} + bl_activate_operator = "POSELIB_OT_apply_pose_asset" + bl_drag_operator = "POSELIB_OT_blend_pose_asset" @classmethod def poll(cls, context: Context) -> bool: diff --git a/scripts/addons_core/pose_library/keymaps.py b/scripts/addons_core/pose_library/keymaps.py index 4030a31cf29..68cb3b1b726 100644 --- a/scripts/addons_core/pose_library/keymaps.py +++ b/scripts/addons_core/pose_library/keymaps.py @@ -21,21 +21,6 @@ def register() -> None: kmi = km.keymap_items.new("poselib.apply_pose_asset", "LEFTMOUSE", "DOUBLE_CLICK") addon_keymaps.append((km, kmi)) - # Asset Shelf - km = wm.keyconfigs.addon.keymaps.new(name="Asset Shelf") - # Click to apply pose. - kmi = km.keymap_items.new("poselib.apply_pose_asset", "LEFTMOUSE", "CLICK") - addon_keymaps.append((km, kmi)) - kmi = km.keymap_items.new("poselib.apply_pose_asset", "LEFTMOUSE", "CLICK", ctrl=True) - kmi.properties.flipped = True - addon_keymaps.append((km, kmi)) - # Drag to blend pose. - kmi = km.keymap_items.new("poselib.blend_pose_asset", "LEFTMOUSE", "CLICK_DRAG") - addon_keymaps.append((km, kmi)) - kmi = km.keymap_items.new("poselib.blend_pose_asset", "LEFTMOUSE", "CLICK_DRAG", ctrl=True) - kmi.properties.flipped = True - addon_keymaps.append((km, kmi)) - def unregister() -> None: # Clear shortcuts from the keymap. diff --git a/scripts/presets/keyconfig/keymap_data/blender_default.py b/scripts/presets/keyconfig/keymap_data/blender_default.py index 90318e95f08..2db0707a23f 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": 'PRESS'}, None), + ("ui.view_item_select", {"type": 'LEFTMOUSE', "value": 'CLICK'}, 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 08bff5411ea..2a0f3345a10 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": 'PRESS'}, None), + ("ui.view_item_select", {"type": 'LEFTMOUSE', "value": 'CLICK'}, None), ]) return keymap diff --git a/scripts/startup/bl_ui/properties_paint_common.py b/scripts/startup/bl_ui/properties_paint_common.py index 5e8aafa0706..d92241c480b 100644 --- a/scripts/startup/bl_ui/properties_paint_common.py +++ b/scripts/startup/bl_ui/properties_paint_common.py @@ -12,7 +12,14 @@ from bpy.app.translations import ( class BrushAssetShelf: - bl_options = {'DEFAULT_VISIBLE', 'NO_ASSET_DRAG', 'STORE_ENABLED_CATALOGS_IN_PREFERENCES'} + bl_options = { + 'DEFAULT_VISIBLE', + 'NO_ASSET_DRAG', + 'STORE_ENABLED_CATALOGS_IN_PREFERENCES', + # Ensure `bl_activate_operator` is called when spawning the context menu. Operators there + # rely on the imported, active brush, not just the active asset representation. + 'ACTIVATE_FOR_CONTEXT_MENU', + } bl_activate_operator = "BRUSH_OT_asset_activate" bl_default_preview_size = 48 brush_type_prop = None diff --git a/source/blender/blenkernel/BKE_screen.hh b/source/blender/blenkernel/BKE_screen.hh index d0b2dfdf08b..b3666498e2f 100644 --- a/source/blender/blenkernel/BKE_screen.hh +++ b/source/blender/blenkernel/BKE_screen.hh @@ -641,6 +641,12 @@ enum AssetShelfTypeFlag { ASSET_SHELF_TYPE_FLAG_NO_ASSET_DRAG = (1 << 0), ASSET_SHELF_TYPE_FLAG_DEFAULT_VISIBLE = (1 << 1), ASSET_SHELF_TYPE_FLAG_STORE_CATALOGS_IN_PREFS = (1 << 2), + /** + * When spawning a context menu for an asset, activate the asset and call the activate operator + * (`bl_activate_operator`/#AssetShelfType.activate_operator) if present, rather than just + * highlighting the asset as active. + */ + ASSET_SHELF_TYPE_FLAG_ACTIVATE_FOR_CONTEXT_MENU = (1 << 3), ASSET_SHELF_TYPE_FLAG_MAX }; @@ -656,6 +662,8 @@ struct AssetShelfType { /** Operator to call when activating a grid view item. */ std::string activate_operator; + /** Operator to call when dragging a grid view item. */ + std::string drag_operator; AssetShelfTypeFlag flag; diff --git a/source/blender/editors/armature/pose_lib_2.cc b/source/blender/editors/armature/pose_lib_2.cc index 5f864cab14f..029c9a73d51 100644 --- a/source/blender/editors/armature/pose_lib_2.cc +++ b/source/blender/editors/armature/pose_lib_2.cc @@ -38,6 +38,7 @@ #include "UI_interface.hh" #include "ED_asset.hh" +#include "ED_asset_menu_utils.hh" #include "ED_keyframing.hh" #include "ED_screen.hh" #include "ED_util.hh" @@ -334,7 +335,37 @@ static void poselib_tempload_exit(PoseBlendData *pbd) static bAction *poselib_blend_init_get_action(bContext *C, wmOperator *op) { using namespace blender::ed; - const AssetRepresentationHandle *asset = CTX_wm_asset(C); + + const blender::asset_system::AssetRepresentation *asset = nullptr; + + if (asset::operator_asset_reference_props_is_set(*op->ptr)) { + asset = asset::operator_asset_reference_props_get_asset_from_all_library( + *C, *op->ptr, op->reports); + if (!asset) { + /* Explicit asset reference passed, but cannot be found. Error out. */ + BKE_reportf(op->reports, + RPT_ERROR, + "Asset not found: '%s'", + RNA_string_get(op->ptr, "relative_asset_identifier").c_str()); + return nullptr; + } + } + else { + /* If no explicit asset reference was passed, get asset from context. */ + asset = CTX_wm_asset(C); + if (!asset) { + BKE_report(op->reports, RPT_ERROR, "No asset in context"); + return nullptr; + } + } + + if (asset->get_id_type() != ID_AC) { + BKE_reportf(op->reports, + RPT_ERROR, + "Asset ('%s') is not an action data-block", + asset->get_name().c_str()); + return nullptr; + } PoseBlendData *pbd = static_cast(op->customdata); @@ -378,6 +409,9 @@ static bool poselib_blend_init_data(bContext *C, wmOperator *op, const wmEvent * pbd->act = poselib_blend_init_get_action(C, op); if (pbd->act == nullptr) { + /* No report here. The poll function cannot check if the operator properties have an asset + * reference to determine the asset to operate on, in which case we fallback to getting the + * asset from context. */ return false; } if (pbd->act->wrap().slots().size() == 0) { @@ -385,7 +419,9 @@ static bool poselib_blend_init_data(bContext *C, wmOperator *op, const wmEvent * return false; } - pbd->is_flipped = RNA_boolean_get(op->ptr, "flipped"); + pbd->is_flipped = RNA_struct_property_is_set(op->ptr, "flipped") ? + RNA_boolean_get(op->ptr, "flipped") : + (event && (event->modifier & KM_CTRL)); pbd->blend_factor = RNA_float_get(op->ptr, "blend_factor"); /* Only construct the flipped pose if there is a chance it's actually needed. */ @@ -568,6 +604,20 @@ static wmOperatorStatus poselib_blend_modal(bContext *C, wmOperator *op, const w return operator_result; } +static wmOperatorStatus poselib_apply_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + if (!poselib_blend_init_data(C, op, event)) { + poselib_blend_free(op); + return OPERATOR_CANCELLED; + } + + poselib_blend_apply(C, op); + + PoseBlendData *pbd = static_cast(op->customdata); + pbd->state = POSE_BLEND_CONFIRM; + return poselib_blend_exit(C, op); +} + /* Modal Operator init. */ static wmOperatorStatus poselib_blend_invoke(bContext *C, wmOperator *op, const wmEvent *event) { @@ -589,23 +639,7 @@ static wmOperatorStatus poselib_blend_invoke(bContext *C, wmOperator *op, const /* Single-shot apply. */ static wmOperatorStatus poselib_blend_exec(bContext *C, wmOperator *op) { - if (!poselib_blend_init_data(C, op, nullptr)) { - poselib_blend_free(op); - return OPERATOR_CANCELLED; - } - - poselib_blend_apply(C, op); - - PoseBlendData *pbd = static_cast(op->customdata); - pbd->state = POSE_BLEND_CONFIRM; - return poselib_blend_exit(C, op); -} - -static bool poselib_asset_in_context(bContext *C) -{ - /* Check whether the context provides the asset data needed to add a pose. */ - const AssetRepresentationHandle *asset = CTX_wm_asset(C); - return asset && (asset->get_id_type() == ID_AC); + return poselib_apply_invoke(C, op, nullptr); } /* Poll callback for operators that require existing PoseLib data (with poses) to work. */ @@ -617,9 +651,12 @@ static bool poselib_blend_poll(bContext *C) return false; } - return poselib_asset_in_context(C); + return true; } +/* Operator propreties can set an asset reference to determine the asset to operate on (the pose + * can then be applied via shortcut too, for example). If this isn't set, an active asset from + * context is queried. */ void POSELIB_OT_apply_pose_asset(wmOperatorType *ot) { PropertyRNA *prop; @@ -630,13 +667,14 @@ void POSELIB_OT_apply_pose_asset(wmOperatorType *ot) ot->description = "Apply the given Pose Action to the rig"; /* Callbacks: */ - ot->exec = poselib_blend_exec; + ot->invoke = poselib_apply_invoke; ot->poll = poselib_blend_poll; /* Flags: */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* Properties: */ + blender::ed::asset::operator_asset_reference_props_register(*ot->srna); RNA_def_float_factor(ot->srna, "blend_factor", 1.0f, @@ -655,6 +693,7 @@ void POSELIB_OT_apply_pose_asset(wmOperatorType *ot) RNA_def_property_flag(prop, PROP_SKIP_SAVE); } +/* See comment on #POSELIB_OT_apply_pose_asset. */ void POSELIB_OT_blend_pose_asset(wmOperatorType *ot) { PropertyRNA *prop; @@ -675,6 +714,7 @@ void POSELIB_OT_blend_pose_asset(wmOperatorType *ot) ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR_X; /* Properties: */ + blender::ed::asset::operator_asset_reference_props_register(*ot->srna); prop = RNA_def_float_factor(ot->srna, "blend_factor", 0.0f, diff --git a/source/blender/editors/asset/intern/asset_menu_utils.cc b/source/blender/editors/asset/intern/asset_menu_utils.cc index 7d6a6895d02..b3894b23189 100644 --- a/source/blender/editors/asset/intern/asset_menu_utils.cc +++ b/source/blender/editors/asset/intern/asset_menu_utils.cc @@ -56,6 +56,13 @@ void operator_asset_reference_props_set(const asset_system::AssetRepresentation RNA_string_set(&ptr, "relative_asset_identifier", weak_ref.relative_asset_identifier); } +bool operator_asset_reference_props_is_set(PointerRNA &ptr) +{ + return RNA_struct_property_is_set(&ptr, "asset_library_type") && + RNA_struct_property_is_set(&ptr, "asset_library_identifier") && + RNA_struct_property_is_set(&ptr, "relative_asset_identifier"); +} + /** * #AssetLibrary::resolve_asset_weak_reference_to_full_path() currently does not support local * assets. 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 6fd2463577e..5c628f93c87 100644 --- a/source/blender/editors/asset/intern/asset_shelf_asset_view.cc +++ b/source/blender/editors/asset/intern/asset_shelf_asset_view.cc @@ -78,8 +78,9 @@ class AssetDragController : public ui::AbstractViewItemDragController { public: AssetDragController(ui::AbstractGridView &view, asset_system::AssetRepresentation &asset); - eWM_DragDataType get_drag_type() const override; + std::optional get_drag_type() const override; void *create_drag_data() const override; + void on_drag_start(bContext &C) override; }; AssetView::AssetView(const AssetLibraryReference &library_ref, const AssetShelf &shelf) @@ -123,6 +124,14 @@ void AssetView::build_items() if (shelf_.type->flag & ASSET_SHELF_TYPE_FLAG_NO_ASSET_DRAG) { item.disable_asset_drag(); } + /* 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 + * asset shelf use case works with activating on every click though. */ + item.always_reactivate_on_click(); + if (shelf_.type->flag & ASSET_SHELF_TYPE_FLAG_ACTIVATE_FOR_CONTEXT_MENU) { + item.activate_for_context_menu_set(); + } return true; }); @@ -185,7 +194,7 @@ void AssetViewItem::disable_asset_drag() * Needs freeing with #WM_operator_properties_free() (will be done by button if passed to that) and * #MEM_freeN(). */ -static std::optional create_activate_operator_params( +static std::optional create_asset_operator_params( const StringRefNull op_name, const asset_system::AssetRepresentation &asset) { if (op_name.is_empty()) { @@ -212,7 +221,7 @@ void AssetViewItem::build_grid_tile(const bContext & /*C*/, uiLayout &layout) co layout.block(), reinterpret_cast(view_item_but_), "asset", &asset_ptr); uiBut *item_but = reinterpret_cast(this->view_item_button()); - if (std::optional activate_op = create_activate_operator_params( + if (std::optional activate_op = create_asset_operator_params( shelf_type.activate_operator, asset_)) { /* Attach the operator, but don't call it through the button. We call it using @@ -287,7 +296,7 @@ void AssetViewItem::on_activate(bContext &C) const AssetView &asset_view = dynamic_cast(this->get_view()); const AssetShelfType &shelf_type = *asset_view.shelf_.type; - if (std::optional activate_op = create_activate_operator_params( + if (std::optional activate_op = create_asset_operator_params( shelf_type.activate_operator, asset_)) { WM_operator_name_call_ptr( @@ -305,7 +314,10 @@ bool AssetViewItem::should_be_filtered_visible(const StringRefNull filter_string std::unique_ptr AssetViewItem::create_drag_controller() const { - if (!allow_asset_drag_) { + const AssetView &asset_view = dynamic_cast(this->get_view()); + const AssetShelfType &shelf_type = *asset_view.shelf_.type; + + if (!allow_asset_drag_ && shelf_type.drag_operator.empty()) { return nullptr; } return std::make_unique(this->get_view(), asset_); @@ -361,11 +373,32 @@ AssetDragController::AssetDragController(ui::AbstractGridView &view, { } -eWM_DragDataType AssetDragController::get_drag_type() const +std::optional AssetDragController::get_drag_type() const { + const AssetView &asset_view = this->get_view(); + const AssetShelfType &shelf_type = *asset_view.shelf_.type; + + /* Disable asset dragging, only call #AssetShelfType::drag_operator in #on_drag_start(). */ + if (!shelf_type.drag_operator.empty()) { + return std::nullopt; + } return asset_.is_local_id() ? WM_DRAG_ID : WM_DRAG_ASSET; } +void AssetDragController::on_drag_start(bContext &C) +{ + const AssetView &asset_view = this->get_view(); + const AssetShelfType &shelf_type = *asset_view.shelf_.type; + + if (std::optional drag_op = create_asset_operator_params( + shelf_type.drag_operator, asset_)) + { + WM_operator_name_call_ptr(&C, drag_op->optype, drag_op->opcontext, drag_op->opptr, nullptr); + WM_operator_properties_free(drag_op->opptr); + MEM_delete(drag_op->opptr); + } +} + void *AssetDragController::create_drag_data() const { ID *local_id = asset_.local_id(); diff --git a/source/blender/editors/include/ED_asset_menu_utils.hh b/source/blender/editors/include/ED_asset_menu_utils.hh index 6ced59e2b8c..ff7d7287a57 100644 --- a/source/blender/editors/include/ED_asset_menu_utils.hh +++ b/source/blender/editors/include/ED_asset_menu_utils.hh @@ -31,6 +31,7 @@ void draw_menu_for_catalog(const asset_system::AssetCatalogTreeItem &item, void operator_asset_reference_props_set(const asset_system::AssetRepresentation &asset, PointerRNA &ptr); +bool operator_asset_reference_props_is_set(PointerRNA &ptr); void operator_asset_reference_props_register(StructRNA &srna); const asset_system::AssetRepresentation *find_asset_from_weak_ref( diff --git a/source/blender/editors/include/UI_abstract_view.hh b/source/blender/editors/include/UI_abstract_view.hh index fa32e2cb9e9..2978291a7f4 100644 --- a/source/blender/editors/include/UI_abstract_view.hh +++ b/source/blender/editors/include/UI_abstract_view.hh @@ -216,12 +216,28 @@ class AbstractViewItem { * children currently. */ bool is_always_collapsible_ = false; + /** See #always_reactivate_on_click(). */ + bool reactivate_on_click_ = false; + /** See #activate_for_context_menu_set(). */ + bool activate_for_context_menu_ = false; public: virtual ~AbstractViewItem() = default; virtual void build_context_menu(bContext &C, uiLayout &column) const; + /** + * Like #activate() but does not call #on_activate(). Use it to reflect changes in the active + * state that happened externally. Or to simply highlight the item as active without triggering + * activation with an `on_activate()` call. E.g. this is done when spawning a context menu if + * #activate_for_context_menu_set() wasn't called, to indicate which item the context menu + * belongs to. + * + * Can be overridden to customize behavior but should always call the base class implementation. + * + * \return true of the item was activated. + */ + virtual bool set_state_active(); /** * Called when the view changes an item's state from inactive to active. Will only be called if * the state change is triggered through the view, not through external changes. E.g. a click on @@ -296,6 +312,11 @@ class AbstractViewItem { bool is_interactive() const; void disable_activatable(); + /** 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 + * as active to indicate where the context menu was spawned from. */ + void activate_for_context_menu_set(); /** * Activates this item, deactivates other items, and calls the #AbstractViewItem::on_activate() * function. Should only be called when the item was activated through the view (e.g. through a @@ -308,6 +329,13 @@ class AbstractViewItem { * actual item state is unknown, possibly calling state-change update functions incorrectly. */ void activate(bContext &C); + /** + * If #activate_for_context_menu_set() was called, properly (re)activates the item including a + * #AbstractViewItem::on_activate() call. Otherwise, the item will only be highlighted as active, + * to indicate which item the context menu belongs to. + * Should be used when spawning a context menu for this item. + */ + void activate_for_context_menu(bContext &C); void deactivate(); /** * Requires the view to have completed reconstruction, see #is_reconstructed(). Otherwise we @@ -349,14 +377,6 @@ class AbstractViewItem { */ virtual void update_from_old(const AbstractViewItem &old); - /** - * Like #activate() but does not call #on_activate(). Use it to reflect changes in the active - * state that happened externally. - * Can be overridden to customize behavior but should always call the base class implementation. - * \return true of the item was activated. - */ - virtual bool set_state_active(); - /** * See #AbstractView::change_state_delayed(). Overrides should call the base class * implementation. @@ -393,9 +413,13 @@ class AbstractViewItemDragController { AbstractViewItemDragController(AbstractView &view); virtual ~AbstractViewItemDragController() = default; - virtual eWM_DragDataType get_drag_type() const = 0; + virtual std::optional get_drag_type() const = 0; virtual void *create_drag_data() const = 0; - virtual void on_drag_start(); + /** + * Called when beginning to drag. Also called when #get_drag_type() doesn't return a value, so an + * arbitrary action can be executed. + */ + virtual void on_drag_start(bContext &C); /** Request the view the item is registered for as type #ViewType. Throws a `std::bad_cast` * exception if the view is not of the requested type. */ diff --git a/source/blender/editors/include/UI_interface_c.hh b/source/blender/editors/include/UI_interface_c.hh index 8b15f33340d..11155ca7a74 100644 --- a/source/blender/editors/include/UI_interface_c.hh +++ b/source/blender/editors/include/UI_interface_c.hh @@ -3023,7 +3023,7 @@ bool UI_view_item_popup_keep_open(const blender::ui::AbstractViewItem &item); * support dragging, i.e. if it won't create a drag-controller upon request. * \return True if dragging started successfully, otherwise false. */ -bool UI_view_item_drag_start(bContext &C, const blender::ui::AbstractViewItem &item); +bool UI_view_item_drag_start(bContext &C, blender::ui::AbstractViewItem &item); /** * \param xy: Coordinate to find a view item at, in window space. diff --git a/source/blender/editors/interface/interface_handlers.cc b/source/blender/editors/interface/interface_handlers.cc index 40583253915..cf803e500e6 100644 --- a/source/blender/editors/interface/interface_handlers.cc +++ b/source/blender/editors/interface/interface_handlers.cc @@ -5088,7 +5088,12 @@ static void force_activate_view_item_but(bContext *C, /* For popups. Other abstract view instances correctly calls the select operator, see: * #141235. */ + if (but->context) { + CTX_store_set(C, but->context); + } but->view_item->activate(*C); + CTX_store_set(C, nullptr); + ED_region_tag_redraw_no_rebuild(region); ED_region_tag_refresh_ui(region); @@ -8316,7 +8321,8 @@ static int ui_do_button(bContext *C, uiBlock *block, uiBut *but, const wmEvent * but->type == ButType::ViewItem ? but : ui_view_item_find_mouse_over(data->region, event->xy)); if (clicked_view_item_but) { - clicked_view_item_but->view_item->activate(*C); + clicked_view_item_but->view_item->activate_for_context_menu(*C); + ED_region_tag_redraw_no_rebuild(data->region); } /* RMB has two options now */ @@ -10142,15 +10148,25 @@ static int ui_handle_view_item_event(bContext *C, } break; case LEFTMOUSE: - if ((event->val == KM_PRESS) && (event->modifier == 0)) { + if (event->modifier == 0) { /* Only bother finding the active view item button if the active button isn't already a * view item. */ uiButViewItem *view_but = static_cast( (active_but && active_but->type == ButType::ViewItem) ? active_but : ui_view_item_find_mouse_over(region, event->xy)); - /* Will free active button if there already is one. */ + if (view_but) { + if (UI_view_item_supports_drag(*view_but->view_item)) { + if (event->val != KM_CLICK) { + break; + } + } + else if (event->val != KM_PRESS) { + break; + } + + /* Will free active button if there already is one. */ /* Close the popup when clicking on the view item directly, not any overlapped button. */ const bool close_popup = view_but == active_but; force_activate_view_item_but(C, region, view_but, close_popup); diff --git a/source/blender/editors/interface/templates/interface_template_bone_collection_tree.cc b/source/blender/editors/interface/templates/interface_template_bone_collection_tree.cc index 2d87e42e6c0..6600884234f 100644 --- a/source/blender/editors/interface/templates/interface_template_bone_collection_tree.cc +++ b/source/blender/editors/interface/templates/interface_template_bone_collection_tree.cc @@ -81,9 +81,9 @@ class BoneCollectionDragController : public AbstractViewItemDragController { bArmature &armature, const int bcoll_index); - eWM_DragDataType get_drag_type() const override; + std::optional get_drag_type() const override; void *create_drag_data() const override; - void on_drag_start() override; + void on_drag_start(bContext &C) override; }; class BoneCollectionDropTarget : public TreeViewItemDropTarget { @@ -443,7 +443,7 @@ BoneCollectionDragController::BoneCollectionDragController(BoneCollectionTreeVie { } -eWM_DragDataType BoneCollectionDragController::get_drag_type() const +std::optional BoneCollectionDragController::get_drag_type() const { return WM_DRAG_BONE_COLLECTION; } @@ -455,7 +455,7 @@ void *BoneCollectionDragController::create_drag_data() const return drag_data; } -void BoneCollectionDragController::on_drag_start() +void BoneCollectionDragController::on_drag_start(bContext & /*C*/) { ANIM_armature_bonecoll_active_index_set(drag_arm_bcoll_.armature, drag_arm_bcoll_.bcoll_index); } diff --git a/source/blender/editors/interface/templates/interface_template_grease_pencil_layer_tree.cc b/source/blender/editors/interface/templates/interface_template_grease_pencil_layer_tree.cc index 0536bd5fa25..862c07c415b 100644 --- a/source/blender/editors/interface/templates/interface_template_grease_pencil_layer_tree.cc +++ b/source/blender/editors/interface/templates/interface_template_grease_pencil_layer_tree.cc @@ -182,7 +182,7 @@ class LayerViewItemDragController : public AbstractViewItemDragController { { } - eWM_DragDataType get_drag_type() const override + std::optional get_drag_type() const override { if (dragged_node_.wrap().is_layer()) { return WM_DRAG_GREASE_PENCIL_LAYER; @@ -198,7 +198,7 @@ class LayerViewItemDragController : public AbstractViewItemDragController { return drag_data; } - void on_drag_start() override + void on_drag_start(bContext & /*C*/) override { grease_pencil_.set_active_node(&dragged_node_); } diff --git a/source/blender/editors/interface/templates/interface_template_light_linking.cc b/source/blender/editors/interface/templates/interface_template_light_linking.cc index 8ffdda4c9d4..d844dc6a6fd 100644 --- a/source/blender/editors/interface/templates/interface_template_light_linking.cc +++ b/source/blender/editors/interface/templates/interface_template_light_linking.cc @@ -201,7 +201,7 @@ class ItemDragController : public AbstractViewItemDragController { { } - eWM_DragDataType get_drag_type() const override + std::optional get_drag_type() const override { return WM_DRAG_ID; } diff --git a/source/blender/editors/interface/templates/interface_template_node_tree_interface.cc b/source/blender/editors/interface/templates/interface_template_node_tree_interface.cc index e2c69fb5751..ed18edf3032 100644 --- a/source/blender/editors/interface/templates/interface_template_node_tree_interface.cc +++ b/source/blender/editors/interface/templates/interface_template_node_tree_interface.cc @@ -52,7 +52,7 @@ class NodeTreeInterfaceDragController : public AbstractViewItemDragController { bNodeTree &tree); ~NodeTreeInterfaceDragController() override = default; - eWM_DragDataType get_drag_type() const override; + std::optional get_drag_type() const override; void *create_drag_data() const override; }; @@ -345,7 +345,7 @@ NodeTreeInterfaceDragController::NodeTreeInterfaceDragController(NodeTreeInterfa { } -eWM_DragDataType NodeTreeInterfaceDragController::get_drag_type() const +std::optional NodeTreeInterfaceDragController::get_drag_type() const { return WM_DRAG_NODE_TREE_INTERFACE; } diff --git a/source/blender/editors/interface/views/abstract_view_item.cc b/source/blender/editors/interface/views/abstract_view_item.cc index 15673b6ac9b..339096ba173 100644 --- a/source/blender/editors/interface/views/abstract_view_item.cc +++ b/source/blender/editors/interface/views/abstract_view_item.cc @@ -70,7 +70,7 @@ bool AbstractViewItem::set_state_active() void AbstractViewItem::activate(bContext &C) { - if (set_state_active()) { + if (set_state_active() || reactivate_on_click_) { on_activate(C); } @@ -80,9 +80,20 @@ void AbstractViewItem::activate(bContext &C) } } +void AbstractViewItem::activate_for_context_menu(bContext &C) +{ + if (activate_for_context_menu_) { + this->activate(C); + } + else { + this->set_state_active(); + } +} + void AbstractViewItem::deactivate() { is_active_ = false; + is_selected_ = false; } std::optional AbstractViewItem::should_be_selected() const @@ -298,7 +309,7 @@ std::optional AbstractViewItem::debug_name() const AbstractViewItemDragController::AbstractViewItemDragController(AbstractView &view) : view_(view) {} -void AbstractViewItemDragController::on_drag_start() +void AbstractViewItemDragController::on_drag_start(bContext & /*C*/) { /* Do nothing by default. */ } @@ -328,6 +339,16 @@ void AbstractViewItem::disable_activatable() is_activatable_ = false; } +void AbstractViewItem::always_reactivate_on_click() +{ + reactivate_on_click_ = true; +} + +void AbstractViewItem::activate_for_context_menu_set() +{ + activate_for_context_menu_ = true; +} + void AbstractViewItem::disable_interaction() { is_interactive_ = false; @@ -423,7 +444,7 @@ bool UI_view_item_popup_keep_open(const AbstractViewItem &item) return item.get_view().get_popup_keep_open(); } -bool UI_view_item_drag_start(bContext &C, const AbstractViewItem &item) +bool UI_view_item_drag_start(bContext &C, AbstractViewItem &item) { const std::unique_ptr drag_controller = item.create_drag_controller(); @@ -431,12 +452,15 @@ bool UI_view_item_drag_start(bContext &C, const AbstractViewItem &item) return false; } - WM_event_start_drag(&C, - ICON_NONE, - drag_controller->get_drag_type(), - drag_controller->create_drag_data(), - WM_DRAG_FREE_DATA); - drag_controller->on_drag_start(); + if (const std::optional drag_type = drag_controller->get_drag_type()) { + WM_event_start_drag( + &C, ICON_NONE, *drag_type, drag_controller->create_drag_data(), WM_DRAG_FREE_DATA); + } + drag_controller->on_drag_start(C); + + /* Make sure the view item is highlighted as active when dragging from it. This is useful user + * feedback. */ + item.set_state_active(); return true; } diff --git a/source/blender/editors/object/interface_template_shape_key_tree.cc b/source/blender/editors/object/interface_template_shape_key_tree.cc index 3e061e024b4..e2f9e2af22e 100644 --- a/source/blender/editors/object/interface_template_shape_key_tree.cc +++ b/source/blender/editors/object/interface_template_shape_key_tree.cc @@ -62,7 +62,7 @@ class ShapeKeyDragController : public ui::AbstractViewItemDragController { { } - eWM_DragDataType get_drag_type() const override + std::optional get_drag_type() const override { return WM_DRAG_SHAPE_KEY; } @@ -73,7 +73,7 @@ class ShapeKeyDragController : public ui::AbstractViewItemDragController { *drag_data = drag_key_; return drag_data; } - void on_drag_start() override + void on_drag_start(bContext & /*C*/) override { drag_key_.object->shapenr = drag_key_.index + 1; } diff --git a/source/blender/editors/space_file/asset_catalog_tree_view.cc b/source/blender/editors/space_file/asset_catalog_tree_view.cc index bd650524e22..ce3365cb269 100644 --- a/source/blender/editors/space_file/asset_catalog_tree_view.cc +++ b/source/blender/editors/space_file/asset_catalog_tree_view.cc @@ -104,9 +104,9 @@ class AssetCatalogDragController : public ui::AbstractViewItemDragController { explicit AssetCatalogDragController(AssetCatalogTreeView &tree_view, const AssetCatalogTreeItem &catalog_item); - eWM_DragDataType get_drag_type() const override; + std::optional get_drag_type() const override; void *create_drag_data() const override; - void on_drag_start() override; + void on_drag_start(bContext &C) override; }; class AssetCatalogDropTarget : public ui::TreeViewItemDropTarget { @@ -553,7 +553,7 @@ AssetCatalogDragController::AssetCatalogDragController(AssetCatalogTreeView &tre { } -eWM_DragDataType AssetCatalogDragController::get_drag_type() const +std::optional AssetCatalogDragController::get_drag_type() const { return WM_DRAG_ASSET_CATALOG; } @@ -566,7 +566,7 @@ void *AssetCatalogDragController::create_drag_data() const return drag_catalog; } -void AssetCatalogDragController::on_drag_start() +void AssetCatalogDragController::on_drag_start(bContext & /*C*/) { AssetCatalogTreeView &tree_view_ = this->get_view(); tree_view_.activate_catalog_by_id(catalog_item_.get_catalog_id()); diff --git a/source/blender/makesrna/intern/rna_ui.cc b/source/blender/makesrna/intern/rna_ui.cc index 143778681cd..6eaa6f37f66 100644 --- a/source/blender/makesrna/intern/rna_ui.cc +++ b/source/blender/makesrna/intern/rna_ui.cc @@ -1311,6 +1311,24 @@ static void rna_AssetShelf_activate_operator_set(PointerRNA *ptr, const char *va shelf->type->activate_operator = value; } +static void rna_AssetShelf_drag_operator_get(PointerRNA *ptr, char *value) +{ + AssetShelf *shelf = static_cast(ptr->data); + strcpy(value, shelf->type->drag_operator.c_str()); +} + +static int rna_AssetShelf_drag_operator_length(PointerRNA *ptr) +{ + AssetShelf *shelf = static_cast(ptr->data); + return shelf->type->drag_operator.size(); +} + +static void rna_AssetShelf_drag_operator_set(PointerRNA *ptr, const char *value) +{ + AssetShelf *shelf = static_cast(ptr->data); + shelf->type->drag_operator = value; +} + static StructRNA *rna_AssetShelf_refine(PointerRNA *shelf_ptr) { AssetShelf *shelf = (AssetShelf *)shelf_ptr->data; @@ -2320,6 +2338,12 @@ static void rna_def_asset_shelf(BlenderRNA *brna) "Store Enabled Catalogs in Preferences", "Store the shelf's enabled catalogs in the preferences rather than the local asset shelf " "settings"}, + {ASSET_SHELF_TYPE_FLAG_ACTIVATE_FOR_CONTEXT_MENU, + "ACTIVATE_FOR_CONTEXT_MENU", + 0, + "", + "When spawning a context menu for an asset, activate the asset and call " + "`bl_activate_operator` if present, rather than just highlighting the asset"}, {0, nullptr, 0, nullptr, nullptr}, }; @@ -2367,6 +2391,17 @@ static void rna_def_asset_shelf(BlenderRNA *brna) "Activate Operator", "Operator to call when activating an item with asset reference properties"); + prop = RNA_def_property(srna, "bl_drag_operator", PROP_STRING, PROP_NONE); + RNA_def_property_string_funcs(prop, + "rna_AssetShelf_drag_operator_get", + "rna_AssetShelf_drag_operator_length", + "rna_AssetShelf_drag_operator_set"); + RNA_def_property_flag(prop, PROP_REGISTER_OPTIONAL); + RNA_def_property_ui_text( + prop, + "Drag Operator", + "Operator to call when dragging an item with asset reference properties"); + prop = RNA_def_property(srna, "bl_default_preview_size", PROP_INT, PROP_UNSIGNED); RNA_def_property_int_sdna(prop, nullptr, "type->default_preview_size"); RNA_def_property_range(prop, 32, 256);