From 2fbf206491171789a5685145a8eee6ef7c8cb7a3 Mon Sep 17 00:00:00 2001 From: Julian Eisel Date: Mon, 27 May 2024 18:46:19 +0200 Subject: [PATCH] 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 --- source/blender/blenkernel/BKE_context.hh | 13 ++- source/blender/blenkernel/intern/context.cc | 79 ++++++++++++++----- .../blender/editors/include/UI_interface_c.hh | 3 + source/blender/editors/interface/interface.cc | 8 ++ .../editors/interface/interface_layout.cc | 7 ++ source/blender/makesrna/intern/rna_ui_api.cc | 6 ++ source/blender/python/intern/bpy_rna.cc | 16 +++- 7 files changed, 111 insertions(+), 21 deletions(-) diff --git a/source/blender/blenkernel/BKE_context.hh b/source/blender/blenkernel/BKE_context.hh index 92ba8c98ba6..aa1e11591e9 100644 --- a/source/blender/blenkernel/BKE_context.hh +++ b/source/blender/blenkernel/BKE_context.hh @@ -8,6 +8,8 @@ * \ingroup bke */ +#include + /* 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 value; }; struct bContextStore { @@ -158,6 +160,9 @@ bContext *CTX_copy(const bContext *C); bContextStore *CTX_store_add(blender::Vector> &contexts, blender::StringRefNull name, const PointerRNA *ptr); +bContextStore *CTX_store_add(blender::Vector> &contexts, + blender::StringRefNull name, + blender::StringRef str); bContextStore *CTX_store_add_all(blender::Vector> &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 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 CTX_data_collection_get(const bContext *C, const cha void CTX_data_collection_remap_property(blender::MutableSpan collection_pointers, const char *propname); +std::optional 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 *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); diff --git a/source/blender/blenkernel/intern/context.cc b/source/blender/blenkernel/intern/context.cc index e1a4321eab1..75d690896b7 100644 --- a/source/blender/blenkernel/intern/context.cc +++ b/source/blender/blenkernel/intern/context.cc @@ -131,9 +131,10 @@ void CTX_free(bContext *C) /* store */ -bContextStore *CTX_store_add(Vector> &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> &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> &contexts, contexts.append(std::move(new_ctx)); } - bContextStore *ctx = contexts.last().get(); + return contexts.last().get(); +} + +bContextStore *CTX_store_add(Vector> &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> &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> &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()); - } - else if (contexts.last()->used) { - auto new_ctx = std::make_unique(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(entry->value)) { + const PointerRNA &ptr = std::get(entry->value); + if (!type || RNA_struct_is_a(ptr.type, type)) { + return &ptr; } } } return nullptr; } +std::optional 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(entry->value)) { + return std::get(entry->value); + } + } + return {}; +} + /* is python initialized? */ bool CTX_py_init_get(bContext *C) @@ -235,6 +254,7 @@ struct bContextDataResult { Vector 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 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 collect } } +std::optional 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 *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; } diff --git a/source/blender/editors/include/UI_interface_c.hh b/source/blender/editors/include/UI_interface_c.hh index bda8ab26a7b..9218f922fb8 100644 --- a/source/blender/editors/include/UI_interface_c.hh +++ b/source/blender/editors/include/UI_interface_c.hh @@ -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 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); diff --git a/source/blender/editors/interface/interface.cc b/source/blender/editors/interface/interface.cc index f3f25fe18d6..d3071793127 100644 --- a/source/blender/editors/interface/interface.cc +++ b/source/blender/editors/interface/interface.cc @@ -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 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; diff --git a/source/blender/editors/interface/interface_layout.cc b/source/blender/editors/interface/interface_layout.cc index 750be6fc954..4cf7f987d5a 100644 --- a/source/blender/editors/interface/interface_layout.cc +++ b/source/blender/editors/interface/interface_layout.cc @@ -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; diff --git a/source/blender/makesrna/intern/rna_ui_api.cc b/source/blender/makesrna/intern/rna_ui_api.cc index b0f260e0834..4389e6fd11e 100644 --- a/source/blender/makesrna/intern/rna_ui_api.cc +++ b/source/blender/makesrna/intern/rna_ui_api.cc @@ -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); diff --git a/source/blender/python/intern/bpy_rna.cc b/source/blender/python/intern/bpy_rna.cc index 1464ab24a94..2b05eacb0a9 100644 --- a/source/blender/python/intern/bpy_rna.cc +++ b/source/blender/python/intern/bpy_rna.cc @@ -4438,6 +4438,7 @@ static PyObject *pyrna_struct_getattro(BPy_StructRNA *self, PyObject *pyname) blender::Vector 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 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(