diff --git a/scripts/startup/bl_ui/space_dopesheet.py b/scripts/startup/bl_ui/space_dopesheet.py index 5804584043e..e4e23d107a0 100644 --- a/scripts/startup/bl_ui/space_dopesheet.py +++ b/scripts/startup/bl_ui/space_dopesheet.py @@ -243,7 +243,7 @@ class DOPESHEET_HT_editor_buttons: layout.separator_spacer() - layout.template_ID(st, "action", new="action.new", unlink="action.unlink") + layout.template_action(context.object, new="action.new", unlink="action.unlink") # Show slot selector. if context.preferences.experimental.use_animation_baklava: diff --git a/source/blender/editors/include/UI_interface_c.hh b/source/blender/editors/include/UI_interface_c.hh index fec0ade2743..63446419300 100644 --- a/source/blender/editors/include/UI_interface_c.hh +++ b/source/blender/editors/include/UI_interface_c.hh @@ -2495,6 +2495,23 @@ void uiTemplateAnyID(uiLayout *layout, const char *propname, const char *proptypename, const char *text); + +/** + * Action selector. + * + * This is a specialisation of #uiTemplateID, hard-coded to assign Actions to the given ID. + * Such a specialisation is necessary, as the RNA property (`id.animation_data.action`) does not + * exist when the ID's `adt` pointer is `nullptr`. In that case uiTemplateID will not be able + * to find the RNA type of that property, which in turn it needs to determine the type of IDs to + * show. + */ +void uiTemplateAction(uiLayout *layout, + const bContext *C, + ID *id, + const char *newop, + const char *unlinkop, + const char *text); + /** * Search menu to pick an item from a collection. * A version of uiTemplateID that works for non-ID types. @@ -3236,7 +3253,7 @@ void UI_context_active_but_prop_get_filebrowser(const bContext *C, * * This is for browsing and editing the ID-blocks used. */ -void UI_context_active_but_prop_get_templateID(bContext *C, +void UI_context_active_but_prop_get_templateID(const bContext *C, PointerRNA *r_ptr, PropertyRNA **r_prop); ID *UI_context_active_but_get_tab_ID(bContext *C); diff --git a/source/blender/editors/interface/templates/interface_templates.cc b/source/blender/editors/interface/templates/interface_templates.cc index 19f4310ba8b..38a294667e7 100644 --- a/source/blender/editors/interface/templates/interface_templates.cc +++ b/source/blender/editors/interface/templates/interface_templates.cc @@ -16,6 +16,7 @@ #include "MEM_guardedalloc.h" +#include "DNA_anim_types.h" #include "DNA_brush_types.h" #include "DNA_cachefile_types.h" #include "DNA_collection_types.h" @@ -44,6 +45,7 @@ #include "BLF_api.hh" #include "BLT_translation.hh" +#include "BKE_anim_data.hh" #include "BKE_blender_version.h" #include "BKE_blendfile.hh" #include "BKE_colorband.hh" @@ -545,7 +547,7 @@ static uiBlock *id_search_menu(bContext *C, ARegion *region, void *arg_litem) static void template_id_cb(bContext *C, void *arg_litem, void *arg_event); -void UI_context_active_but_prop_get_templateID(bContext *C, +void UI_context_active_but_prop_get_templateID(const bContext *C, PointerRNA *r_ptr, PropertyRNA **r_prop) { @@ -1818,6 +1820,54 @@ void uiTemplateID(uiLayout *layout, false); } +void uiTemplateAction(uiLayout *layout, + const bContext *C, + ID *id, + const char *newop, + const char *unlinkop, + const char *text) +{ + if (!id_can_have_animdata(id)) { + RNA_warning("Cannot show Action selector for non-animatable ID: %s", id->name + 2); + return; + } + + PropertyRNA *adt_action_prop = RNA_struct_type_find_property(&RNA_AnimData, "action"); + BLI_assert(adt_action_prop); + BLI_assert(RNA_property_type(adt_action_prop) == PROP_POINTER); + + /* Construct a pointer with the animated ID as owner, even when `adt` may be `nullptr`. + * This way it is possible to use this RNA pointer to get/set `adt->action`, as that RNA property + * has a getter and setter that only need the owner ID and are null-safe regarding the `adt` + * itself. */ + AnimData *adt = BKE_animdata_from_id(id); + PointerRNA adt_ptr = RNA_pointer_create(id, &RNA_AnimData, adt); + + /* This must be heap-allocated because template_ID() will call MEM_dupallocN() + * on the pointer we pass, and that doesn't like stack-allocated stuff. */ + TemplateID *template_ui = MEM_cnew(__func__); + BLI_SCOPED_DEFER([&]() { MEM_freeN(template_ui); }); + template_ui->ptr = adt_ptr; + template_ui->prop = adt_action_prop; + template_ui->prv_rows = 0; + template_ui->prv_cols = 0; + template_ui->scale = 1.0f; + template_ui->filter = UI_TEMPLATE_ID_FILTER_ALL; + + int flag = UI_ID_BROWSE | UI_ID_RENAME | UI_ID_DELETE; + if (newop) { + flag |= UI_ID_ADD_NEW; + } + + template_ui->idcode = ID_AC; + template_ui->idlb = which_libbase(CTX_data_main(C), ID_AC); + BLI_assert(template_ui->idlb); + + uiLayout *row = uiLayoutRow(layout, true); + template_ID( + C, row, template_ui, &RNA_Action, flag, newop, nullptr, unlinkop, text, false, false); +} + void uiTemplateIDBrowse(uiLayout *layout, bContext *C, PointerRNA *ptr, diff --git a/source/blender/editors/space_action/action_data.cc b/source/blender/editors/space_action/action_data.cc index 3058599f4b5..852e2985c96 100644 --- a/source/blender/editors/space_action/action_data.cc +++ b/source/blender/editors/space_action/action_data.cc @@ -43,6 +43,7 @@ #include "WM_types.hh" #include "UI_interface.hh" +#include "UI_interface_c.hh" #include "action_intern.hh" @@ -52,7 +53,29 @@ AnimData *ED_actedit_animdata_from_context(const bContext *C, ID **r_adt_id_owner) { + { /* Support use from the layout.template_action() UI template. */ + PointerRNA ptr = {nullptr}; + PropertyRNA *prop = nullptr; + UI_context_active_but_prop_get_templateID(C, &ptr, &prop); + /* template_action() sets a RNA_AnimData pointer, whereas other code may set + * other pointer types. This code here only deals with the former. */ + if (prop && ptr.type == &RNA_AnimData) { + if (!RNA_property_editable(&ptr, prop)) { + return nullptr; + } + if (r_adt_id_owner) { + *r_adt_id_owner = ptr.owner_id; + } + AnimData *adt = static_cast(ptr.data); + return adt; + } + } + SpaceAction *saction = (SpaceAction *)CTX_wm_space_data(C); + if (!saction) { + return nullptr; + } + Object *ob = CTX_data_active_object(C); AnimData *adt = nullptr; @@ -159,6 +182,15 @@ static void actedit_change_action(bContext *C, bAction *act) static bool action_new_poll(bContext *C) { + { /* Support use from the layout.template_action() UI template. */ + PointerRNA ptr = {nullptr}; + PropertyRNA *prop = nullptr; + UI_context_active_but_prop_get_templateID(C, &ptr, &prop); + if (prop) { + return RNA_property_editable(&ptr, prop); + } + } + Scene *scene = CTX_data_scene(C); /* Check tweak-mode is off (as you don't want to be tampering with the action in that case) */ @@ -569,6 +601,7 @@ void ACTION_OT_stash_and_create(wmOperatorType *ot) void ED_animedit_unlink_action( bContext *C, ID *id, AnimData *adt, bAction *act, ReportList *reports, bool force_delete) { + BLI_assert(id); ScrArea *area = CTX_wm_area(C); /* If the old action only has a single user (that it's about to lose), @@ -654,6 +687,20 @@ void ED_animedit_unlink_action( static bool action_unlink_poll(bContext *C) { + { + ID *animated_id = nullptr; + AnimData *adt = ED_actedit_animdata_from_context(C, &animated_id); + if (animated_id) { + if (!BKE_id_is_editable(CTX_data_main(C), animated_id)) { + return false; + } + if (!adt) { + return false; + } + return adt->action != nullptr; + } + } + if (ED_operator_action_active(C)) { SpaceAction *saction = (SpaceAction *)CTX_wm_space_data(C); AnimData *adt = ED_actedit_animdata_from_context(C, nullptr); @@ -670,11 +717,12 @@ static bool action_unlink_poll(bContext *C) static int action_unlink_exec(bContext *C, wmOperator *op) { - AnimData *adt = ED_actedit_animdata_from_context(C, nullptr); + ID *animated_id = nullptr; + AnimData *adt = ED_actedit_animdata_from_context(C, &animated_id); bool force_delete = RNA_boolean_get(op->ptr, "force_delete"); if (adt && adt->action) { - ED_animedit_unlink_action(C, nullptr, adt, adt->action, op->reports, force_delete); + ED_animedit_unlink_action(C, animated_id, adt, adt->action, op->reports, force_delete); } /* Unlink is also abused to exit NLA tweak mode. */ diff --git a/source/blender/makesrna/intern/rna_animation.cc b/source/blender/makesrna/intern/rna_animation.cc index 1298298bd61..f36b52ab93b 100644 --- a/source/blender/makesrna/intern/rna_animation.cc +++ b/source/blender/makesrna/intern/rna_animation.cc @@ -143,10 +143,24 @@ static void rna_AnimData_dependency_update(Main *bmain, Scene *scene, PointerRNA static int rna_AnimData_action_editable(const PointerRNA *ptr, const char ** /*r_info*/) { - AnimData *adt = (AnimData *)ptr->data; + BLI_assert(ptr->type == &RNA_AnimData); + AnimData *adt = static_cast(ptr->data); + if (!adt) { + return PROP_EDITABLE; + } return BKE_animdata_action_editable(adt) ? PROP_EDITABLE : PropertyFlag(0); } +static PointerRNA rna_AnimData_action_get(PointerRNA *ptr) +{ + ID &animated_id = *ptr->owner_id; + animrig::Action *action = animrig::get_action(animated_id); + if (!action) { + return PointerRNA_NULL; + }; + return RNA_id_pointer_create(&action->id); +} + static void rna_AnimData_action_set(PointerRNA *ptr, PointerRNA value, ReportList * /*reports*/) { # ifdef WITH_ANIM_BAKLAVA @@ -1563,10 +1577,21 @@ static void rna_def_animdata(BlenderRNA *brna) /* Active Action */ prop = RNA_def_property(srna, "action", PROP_POINTER, PROP_NONE); + RNA_def_property_struct_type(prop, "Action"); /* this flag as well as the dynamic test must be defined for this to be editable... */ RNA_def_property_flag(prop, PROP_EDITABLE | PROP_ID_REFCOUNT); RNA_def_property_pointer_funcs( - prop, nullptr, "rna_AnimData_action_set", nullptr, "rna_Action_id_poll"); + prop, + /* Define a getter that is NULL-safe, so that an RNA_AnimData prop with `ptr->data = nullptr` + * can still be used to get the property. In that case it will always return nullptr, of + * course, but it won't crash Blender. */ + "rna_AnimData_action_get", + /* Similarly, for the setter, the NULL-safety allows constructing the AnimData struct on + * assignment of this "action" property. This is possible because RNA has typed NULL + * pointers, and thus it knows which setter to call even when `ptr->data` is NULL. */ + "rna_AnimData_action_set", + nullptr, + "rna_Action_id_poll"); RNA_def_property_editable_func(prop, "rna_AnimData_action_editable"); RNA_def_property_ui_text(prop, "Action", "Active Action for this data-block"); RNA_def_property_update(prop, NC_ANIMATION | ND_NLA_ACTCHANGE, "rna_AnimData_dependency_update"); diff --git a/source/blender/makesrna/intern/rna_ui_api.cc b/source/blender/makesrna/intern/rna_ui_api.cc index 6cec82827bb..9d0265f440a 100644 --- a/source/blender/makesrna/intern/rna_ui_api.cc +++ b/source/blender/makesrna/intern/rna_ui_api.cc @@ -576,6 +576,19 @@ static void rna_uiTemplateAnyID(uiLayout *layout, uiTemplateAnyID(layout, ptr, propname, proptypename, name); } +static void rna_uiTemplateAction(uiLayout *layout, + bContext *C, + ID *id, + const char *newop, + const char *unlinkop, + const char *name, + const char *text_ctxt, + const bool translate) +{ + name = rna_translate_ui_text(name, text_ctxt, nullptr, nullptr, translate); + uiTemplateAction(layout, C, id, newop, unlinkop, name); +} + void rna_uiTemplateList(uiLayout *layout, bContext *C, const char *listtype_name, @@ -1716,6 +1729,14 @@ void RNA_api_ui_layout(StructRNA *srna) "", "Optionally limit the items which can be selected"); + func = RNA_def_function(srna, "template_action", "rna_uiTemplateAction"); + RNA_def_function_flag(func, FUNC_USE_CONTEXT); + parm = RNA_def_pointer(func, "id", "ID", "", "The data-block for which to select an Action"); + RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED); + RNA_def_string(func, "new", nullptr, 0, "", "Operator identifier to create a new ID block"); + RNA_def_string(func, "unlink", nullptr, 0, "", "Operator identifier to unlink the ID block"); + api_ui_item_common_text(func); + func = RNA_def_function(srna, "template_search", "uiTemplateSearch"); RNA_def_function_flag(func, FUNC_USE_CONTEXT); api_ui_item_rna_common(func);