Anim: UI template for Action selectors

This PR is just taken over from @dr.sybren with
minor comments addressed.

Original PR: #125493

Co-authored-by: Sybren A. Stüvel <sybren@blender.org>
Pull Request: https://projects.blender.org/blender/blender/pulls/126561
This commit is contained in:
Christoph Lendenfeld
2024-08-23 11:07:16 +02:00
committed by Christoph Lendenfeld
parent 02e721275f
commit 51fd355c01
6 changed files with 168 additions and 7 deletions

View File

@@ -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:

View File

@@ -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);

View File

@@ -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<TemplateID>(__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,

View File

@@ -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<AnimData *>(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. */

View File

@@ -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<AnimData *>(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");

View File

@@ -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);