UI: Allow passing named strings via context

Sometimes it is necessary to pass additional data through generic UI entities,
to specific implementations. For example to pass additional options to panel
polling & drawing when instantiating a panel through its panel type. Or storing
additional data in a button, without hardcoding it in the button struct/class.

Passing data via context is a simple solution to this, however so far this only
works using hardcoded context queries or RNA pointers. For passing arbitrary
strings we've used workarounds like creating an RNA type to wrap it already.
For example `RNA_AssetCatalogPath`, which is used to dynamically populate menu
items based on an asset catalog path, via a generic menu type.
type instantiation. This makes it possible to invoke specific asset shelves as
popover panels.

Idea is simply to let `bContextStore` entries hold copies of the string (as
`std::string`), avoiding lifetime issues. Context APIs are extended to support
setting/querying strings via a context member name.

Pull Request: https://projects.blender.org/blender/blender/pulls/122113
This commit is contained in:
Julian Eisel
2024-05-27 18:46:19 +02:00
committed by Julian Eisel
parent cdf960ecec
commit 2fbf206491
7 changed files with 111 additions and 21 deletions

View File

@@ -8,6 +8,8 @@
* \ingroup bke
*/
#include <variant>
/* XXX temporary, until AssetHandle is designed properly and queries can return a pointer to it. */
#include "DNA_asset_types.h"
@@ -102,7 +104,7 @@ using bContextDataCallback = int /*eContextResult*/ (*)(const bContext *C,
struct bContextStoreEntry {
std::string name;
PointerRNA ptr;
std::variant<PointerRNA, std::string> value;
};
struct bContextStore {
@@ -158,6 +160,9 @@ bContext *CTX_copy(const bContext *C);
bContextStore *CTX_store_add(blender::Vector<std::unique_ptr<bContextStore>> &contexts,
blender::StringRefNull name,
const PointerRNA *ptr);
bContextStore *CTX_store_add(blender::Vector<std::unique_ptr<bContextStore>> &contexts,
blender::StringRefNull name,
blender::StringRef str);
bContextStore *CTX_store_add_all(blender::Vector<std::unique_ptr<bContextStore>> &contexts,
const bContextStore *context);
const bContextStore *CTX_store_get(const bContext *C);
@@ -165,6 +170,8 @@ void CTX_store_set(bContext *C, const bContextStore *store);
const PointerRNA *CTX_store_ptr_lookup(const bContextStore *store,
blender::StringRefNull name,
const StructRNA *type = nullptr);
std::optional<blender::StringRefNull> CTX_store_string_lookup(const bContextStore *store,
blender::StringRefNull name);
/* need to store if python is initialized or not */
bool CTX_py_init_get(bContext *C);
@@ -262,6 +269,7 @@ enum {
CTX_DATA_TYPE_POINTER = 0,
CTX_DATA_TYPE_COLLECTION,
CTX_DATA_TYPE_PROPERTY,
CTX_DATA_TYPE_STRING,
};
PointerRNA CTX_data_pointer_get(const bContext *C, const char *member);
@@ -282,6 +290,8 @@ blender::Vector<PointerRNA> CTX_data_collection_get(const bContext *C, const cha
void CTX_data_collection_remap_property(blender::MutableSpan<PointerRNA> collection_pointers,
const char *propname);
std::optional<blender::StringRefNull> CTX_data_string_get(const bContext *C, const char *member);
/**
* \param C: Context.
* \param use_store: Use 'C->wm.store'.
@@ -296,6 +306,7 @@ int /*eContextResult*/ CTX_data_get(const bContext *C,
blender::Vector<PointerRNA> *r_lb,
PropertyRNA **r_prop,
int *r_index,
blender::StringRef *r_str,
short *r_type);
void CTX_data_id_pointer_set(bContextDataResult *result, ID *id);

View File

@@ -131,9 +131,10 @@ void CTX_free(bContext *C)
/* store */
bContextStore *CTX_store_add(Vector<std::unique_ptr<bContextStore>> &contexts,
const blender::StringRefNull name,
const PointerRNA *ptr)
/**
* Append a new context store to \a contexts, copying entries from the previous one if any.
*/
static bContextStore *ctx_store_extend(Vector<std::unique_ptr<bContextStore>> &contexts)
{
/* ensure we have a context to put the entry in, if it was already used
* we have to copy the context to ensure */
@@ -145,25 +146,31 @@ bContextStore *CTX_store_add(Vector<std::unique_ptr<bContextStore>> &contexts,
contexts.append(std::move(new_ctx));
}
bContextStore *ctx = contexts.last().get();
return contexts.last().get();
}
bContextStore *CTX_store_add(Vector<std::unique_ptr<bContextStore>> &contexts,
const blender::StringRefNull name,
const PointerRNA *ptr)
{
bContextStore *ctx = ctx_store_extend(contexts);
ctx->entries.append(bContextStoreEntry{name, *ptr});
return ctx;
}
bContextStore *CTX_store_add(Vector<std::unique_ptr<bContextStore>> &contexts,
const blender::StringRefNull name,
const blender::StringRef str)
{
bContextStore *ctx = ctx_store_extend(contexts);
ctx->entries.append(bContextStoreEntry{name, std::string{str}});
return ctx;
}
bContextStore *CTX_store_add_all(Vector<std::unique_ptr<bContextStore>> &contexts,
const bContextStore *context)
{
/* ensure we have a context to put the entry in, if it was already used
* we have to copy the context to ensure */
if (contexts.is_empty()) {
contexts.append(std::make_unique<bContextStore>());
}
else if (contexts.last()->used) {
auto new_ctx = std::make_unique<bContextStore>(bContextStore{contexts.last()->entries, false});
contexts.append(std::move(new_ctx));
}
bContextStore *ctx = contexts.last().get();
bContextStore *ctx = ctx_store_extend(contexts);
for (const bContextStoreEntry &src_entry : context->entries) {
ctx->entries.append(src_entry);
}
@@ -185,15 +192,27 @@ const PointerRNA *CTX_store_ptr_lookup(const bContextStore *store,
const StructRNA *type)
{
for (auto entry = store->entries.rbegin(); entry != store->entries.rend(); ++entry) {
if (entry->name == name) {
if (!type || RNA_struct_is_a(entry->ptr.type, type)) {
return &entry->ptr;
if (entry->name == name && std::holds_alternative<PointerRNA>(entry->value)) {
const PointerRNA &ptr = std::get<PointerRNA>(entry->value);
if (!type || RNA_struct_is_a(ptr.type, type)) {
return &ptr;
}
}
}
return nullptr;
}
std::optional<blender::StringRefNull> CTX_store_string_lookup(const bContextStore *store,
const blender::StringRefNull name)
{
for (auto entry = store->entries.rbegin(); entry != store->entries.rend(); ++entry) {
if (entry->name == name && std::holds_alternative<std::string>(entry->value)) {
return std::get<std::string>(entry->value);
}
}
return {};
}
/* is python initialized? */
bool CTX_py_init_get(bContext *C)
@@ -235,6 +254,7 @@ struct bContextDataResult {
Vector<PointerRNA> list;
PropertyRNA *prop;
int index;
blender::StringRefNull str;
const char **dir;
short type; /* 0: normal, 1: seq */
};
@@ -313,6 +333,15 @@ static eContextResult ctx_data_get(bContext *C, const char *member, bContextData
result->ptr = *ptr;
done = 1;
}
else {
std::optional<blender::StringRefNull> str = CTX_store_string_lookup(C->wm.store, member);
if (str) {
result->str = *str;
result->type = CTX_DATA_TYPE_STRING;
done = 1;
}
}
}
if (done != 1 && recursion < 2 && (region = CTX_wm_region(C))) {
C->data.recursion = 2;
@@ -487,12 +516,24 @@ void CTX_data_collection_remap_property(blender::MutableSpan<PointerRNA> collect
}
}
std::optional<blender::StringRefNull> CTX_data_string_get(const bContext *C, const char *member)
{
bContextDataResult result;
if (ctx_data_get((bContext *)C, member, &result) == CTX_RESULT_OK) {
BLI_assert(result.type == CTX_DATA_TYPE_STRING);
return result.str;
}
return {};
}
int /*eContextResult*/ CTX_data_get(const bContext *C,
const char *member,
PointerRNA *r_ptr,
Vector<PointerRNA> *r_lb,
PropertyRNA **r_prop,
int *r_index,
blender::StringRef *r_str,
short *r_type)
{
bContextDataResult result;
@@ -503,11 +544,13 @@ int /*eContextResult*/ CTX_data_get(const bContext *C,
*r_lb = result.list;
*r_prop = result.prop;
*r_index = result.index;
*r_str = result.str;
*r_type = result.type;
}
else {
memset(r_ptr, 0, sizeof(*r_ptr));
r_lb->clear();
*r_str = "";
*r_type = 0;
}

View File

@@ -1375,6 +1375,8 @@ void UI_but_context_ptr_set(uiBlock *block, uiBut *but, const char *name, const
const PointerRNA *UI_but_context_ptr_get(const uiBut *but,
const char *name,
const StructRNA *type = nullptr);
std::optional<blender::StringRefNull> UI_but_context_string_get(const uiBut *but,
const char *name);
const bContextStore *UI_but_context_get(const uiBut *but);
void UI_but_unit_type_set(uiBut *but, int unit_type);
@@ -2164,6 +2166,7 @@ uiBlock *uiLayoutGetBlock(uiLayout *layout);
void uiLayoutSetFunc(uiLayout *layout, uiMenuHandleFunc handlefunc, void *argv);
void uiLayoutSetContextPointer(uiLayout *layout, const char *name, PointerRNA *ptr);
void uiLayoutSetContextString(uiLayout *layout, const char *name, blender::StringRef value);
bContextStore *uiLayoutGetContextStore(uiLayout *layout);
void uiLayoutContextCopy(uiLayout *layout, const bContextStore *context);

View File

@@ -5769,6 +5769,14 @@ const PointerRNA *UI_but_context_ptr_get(const uiBut *but, const char *name, con
return CTX_store_ptr_lookup(but->context, name, type);
}
std::optional<blender::StringRefNull> UI_but_context_string_get(const uiBut *but, const char *name)
{
if (!but->context) {
return {};
}
return CTX_store_string_lookup(but->context, name);
}
const bContextStore *UI_but_context_get(const uiBut *but)
{
return but->context;

View File

@@ -25,6 +25,7 @@
#include "BLI_memory_utils.hh"
#include "BLI_rect.h"
#include "BLI_string.h"
#include "BLI_string_ref.hh"
#include "BLI_utildefines.h"
#include "BLT_translation.hh"
@@ -5999,6 +6000,12 @@ void uiLayoutSetContextPointer(uiLayout *layout, const char *name, PointerRNA *p
layout->context = CTX_store_add(block->contexts, name, ptr);
}
void uiLayoutSetContextString(uiLayout *layout, const char *name, blender::StringRef value)
{
uiBlock *block = layout->root->block;
layout->context = CTX_store_add(block->contexts, name, value);
}
bContextStore *uiLayoutGetContextStore(uiLayout *layout)
{
return layout->context;

View File

@@ -1587,6 +1587,12 @@ void RNA_api_ui_layout(StructRNA *srna)
parm = RNA_def_pointer(func, "data", "AnyType", "", "Pointer to put in context");
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED | PARM_RNAPTR);
func = RNA_def_function(srna, "context_string_set", "uiLayoutSetContextString");
parm = RNA_def_string(func, "name", nullptr, 0, "Name", "Name of entry in the context");
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED);
parm = RNA_def_string(func, "value", nullptr, 0, "Value", "String to put in context");
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED | PARM_RNAPTR);
/* templates */
func = RNA_def_function(srna, "template_header", "uiTemplateHeader");
RNA_def_function_flag(func, FUNC_USE_CONTEXT);

View File

@@ -4438,6 +4438,7 @@ static PyObject *pyrna_struct_getattro(BPy_StructRNA *self, PyObject *pyname)
blender::Vector<PointerRNA> newlb;
PropertyRNA *newprop;
int newindex;
blender::StringRef newstr;
short newtype;
/* An empty string is used to implement #CTX_data_dir_get,
@@ -4445,7 +4446,7 @@ static PyObject *pyrna_struct_getattro(BPy_StructRNA *self, PyObject *pyname)
eContextResult done;
if (name[0]) {
done = eContextResult(
CTX_data_get(C, name, &newptr, &newlb, &newprop, &newindex, &newtype));
CTX_data_get(C, name, &newptr, &newlb, &newprop, &newindex, &newstr, &newtype));
}
else {
/* Fall through to built-in `getattr`. */
@@ -4463,6 +4464,16 @@ static PyObject *pyrna_struct_getattro(BPy_StructRNA *self, PyObject *pyname)
ret = pyrna_struct_CreatePyObject(&newptr);
}
break;
case CTX_DATA_TYPE_STRING: {
if (newstr.is_empty()) {
ret = Py_None;
Py_INCREF(ret);
}
else {
ret = PyUnicode_FromStringAndSize(newstr.data(), newstr.size());
}
break;
}
case CTX_DATA_TYPE_COLLECTION: {
ret = PyList_New(0);
for (PointerRNA &ptr : newlb) {
@@ -4702,10 +4713,11 @@ static int pyrna_struct_setattro(BPy_StructRNA *self, PyObject *pyname, PyObject
blender::Vector<PointerRNA> newlb;
PropertyRNA *newprop;
int newindex;
blender::StringRef newstr;
short newtype;
const eContextResult done = eContextResult(
CTX_data_get(C, name, &newptr, &newlb, &newprop, &newindex, &newtype));
CTX_data_get(C, name, &newptr, &newlb, &newprop, &newindex, &newstr, &newtype));
if (done == CTX_RESULT_OK) {
PyErr_Format(