Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--source/blender/blenkernel/intern/screen.c12
-rw-r--r--source/blender/editors/include/UI_interface.h31
-rw-r--r--source/blender/editors/interface/interface_templates.c843
-rw-r--r--source/blender/editors/space_node/drawnode.cc6
-rw-r--r--source/blender/makesdna/DNA_screen_types.h12
-rw-r--r--source/blender/makesrna/intern/rna_ui_api.c42
-rw-r--r--source/blender/windowmanager/WM_api.h4
-rw-r--r--source/blender/windowmanager/intern/wm_uilist_type.c21
8 files changed, 649 insertions, 322 deletions
diff --git a/source/blender/blenkernel/intern/screen.c b/source/blender/blenkernel/intern/screen.c
index 73658c3184e..c3885b5dcf7 100644
--- a/source/blender/blenkernel/intern/screen.c
+++ b/source/blender/blenkernel/intern/screen.c
@@ -682,19 +682,13 @@ void BKE_area_region_free(SpaceType *st, ARegion *region)
BKE_area_region_panels_free(&region->panels);
LISTBASE_FOREACH (uiList *, uilst, &region->ui_lists) {
- if (uilst->dyn_data) {
- uiListDyn *dyn_data = uilst->dyn_data;
- if (dyn_data->items_filter_flags) {
- MEM_freeN(dyn_data->items_filter_flags);
- }
- if (dyn_data->items_filter_neworder) {
- MEM_freeN(dyn_data->items_filter_neworder);
- }
- MEM_freeN(dyn_data);
+ if (uilst->dyn_data && uilst->dyn_data->free_runtime_data_fn) {
+ uilst->dyn_data->free_runtime_data_fn(uilst);
}
if (uilst->properties) {
IDP_FreeProperty(uilst->properties);
}
+ MEM_SAFE_FREE(uilst->dyn_data);
}
if (region->gizmo_map != NULL) {
diff --git a/source/blender/editors/include/UI_interface.h b/source/blender/editors/include/UI_interface.h
index 802c175492f..55d0c5c4342 100644
--- a/source/blender/editors/include/UI_interface.h
+++ b/source/blender/editors/include/UI_interface.h
@@ -25,6 +25,7 @@
#include "BLI_compiler_attrs.h"
#include "BLI_sys_types.h" /* size_t */
+#include "BLI_utildefines.h"
#include "UI_interface_icons.h"
#ifdef __cplusplus
@@ -2194,6 +2195,17 @@ void uiTemplateCacheFile(uiLayout *layout,
/* Default UIList class name, keep in sync with its declaration in bl_ui/__init__.py */
#define UI_UL_DEFAULT_CLASS_NAME "UI_UL_list"
+enum uiTemplateListFlags {
+ UI_TEMPLATE_LIST_FLAG_NONE = 0,
+ UI_TEMPLATE_LIST_SORT_REVERSE = (1 << 0),
+ UI_TEMPLATE_LIST_SORT_LOCK = (1 << 1),
+ /* Don't allow resizing the list, i.e. don't add the grip button. */
+ UI_TEMPLATE_LIST_NO_GRIP = (1 << 2),
+
+ UI_TEMPLATE_LIST_FLAGS_LAST
+};
+ENUM_OPERATORS(enum uiTemplateListFlags, UI_TEMPLATE_LIST_FLAGS_LAST);
+
void uiTemplateList(uiLayout *layout,
struct bContext *C,
const char *listtype_name,
@@ -2207,8 +2219,23 @@ void uiTemplateList(uiLayout *layout,
int maxrows,
int layout_type,
int columns,
- bool sort_reverse,
- bool sort_lock);
+ enum uiTemplateListFlags flags);
+struct uiList *uiTemplateList_ex(uiLayout *layout,
+ struct bContext *C,
+ const char *listtype_name,
+ const char *list_id,
+ struct PointerRNA *dataptr,
+ const char *propname,
+ struct PointerRNA *active_dataptr,
+ const char *active_propname,
+ const char *item_dyntip_propname,
+ int rows,
+ int maxrows,
+ int layout_type,
+ int columns,
+ enum uiTemplateListFlags flags,
+ void *customdata);
+
void uiTemplateNodeLink(uiLayout *layout,
struct bContext *C,
struct bNodeTree *ntree,
diff --git a/source/blender/editors/interface/interface_templates.c b/source/blender/editors/interface/interface_templates.c
index 9c17486aea4..30f72e75979 100644
--- a/source/blender/editors/interface/interface_templates.c
+++ b/source/blender/editors/interface/interface_templates.c
@@ -5828,36 +5828,283 @@ static void uilist_filter_items_default(struct uiList *ui_list,
}
}
+static void uilist_free_dyn_data(uiList *ui_list)
+{
+ uiListDyn *dyn_data = ui_list->dyn_data;
+ if (!dyn_data) {
+ return;
+ }
+
+ MEM_SAFE_FREE(dyn_data->items_filter_flags);
+ MEM_SAFE_FREE(dyn_data->items_filter_neworder);
+ MEM_SAFE_FREE(dyn_data->customdata);
+}
+
+/**
+ * The validated data that was passed to #uiTemplateList (typically through Python).
+ * Populated through #ui_template_list_data_retrieve().
+ */
+typedef struct {
+ PointerRNA dataptr;
+ PropertyRNA *prop;
+ PointerRNA active_dataptr;
+ PropertyRNA *activeprop;
+ const char *item_dyntip_propname;
+
+ /* Index as stored in the input property. I.e. the index before sorting. */
+ int active_item_idx;
+} TemplateListInputData;
+
+/**
+ * Internal wrapper for a single item in the list (well, actually stored as a vector).
+ */
typedef struct {
PointerRNA item;
int org_idx;
int flt_flag;
} _uilist_item;
+/**
+ * Container for the item vector and additional info.
+ */
+typedef struct {
+ _uilist_item *item_vec;
+ /* Index of the active item following visual order. I.e. unlike
+ * TemplateListInputData.active_item_idx, this is the index after sorting. */
+ int active_item_idx;
+ int tot_items;
+} TemplateListItems;
+
+typedef struct {
+ uiListDrawItemFunc draw_item;
+ uiListDrawFilterFunc draw_filter;
+
+ int rows;
+ int maxrows;
+ int columns;
+} TemplateListLayoutDrawData;
+
typedef struct {
int visual_items; /* Visual number of items (i.e. number of items we have room to display). */
int start_idx; /* Index of first item to display. */
int end_idx; /* Index of last item to display + 1. */
-} uiListLayoutdata;
+} TemplateListVisualInfo;
+
+/**
+ * Validate input parameters and initialize \a r_data from that. Plus find the list-type and return
+ * it in \a r_list_type.
+ *
+ * \return false if the input data isn't valid. Will also raise an RNA warning in that case.
+ */
+static bool ui_template_list_data_retrieve(const char *listtype_name,
+ const char *list_id,
+ PointerRNA *dataptr,
+ const char *propname,
+ PointerRNA *active_dataptr,
+ const char *active_propname,
+ const char *item_dyntip_propname,
+ TemplateListInputData *r_input_data,
+ uiListType **r_list_type)
+{
+ memset(r_input_data, 0, sizeof(*r_input_data));
+
+ /* Forbid default UI_UL_DEFAULT_CLASS_NAME list class without a custom list_id! */
+ if (STREQ(UI_UL_DEFAULT_CLASS_NAME, listtype_name) && !(list_id && list_id[0])) {
+ RNA_warning("template_list using default '%s' UIList class must provide a custom list_id",
+ UI_UL_DEFAULT_CLASS_NAME);
+ return false;
+ }
+
+ if (!active_dataptr->data) {
+ RNA_warning("No active data");
+ return false;
+ }
+
+ r_input_data->dataptr = *dataptr;
+ if (dataptr->data) {
+ r_input_data->prop = RNA_struct_find_property(dataptr, propname);
+ if (!r_input_data->prop) {
+ RNA_warning("Property not found: %s.%s", RNA_struct_identifier(dataptr->type), propname);
+ return false;
+ }
+ }
+
+ r_input_data->active_dataptr = *active_dataptr;
+ r_input_data->activeprop = RNA_struct_find_property(active_dataptr, active_propname);
+ if (!r_input_data->activeprop) {
+ RNA_warning(
+ "Property not found: %s.%s", RNA_struct_identifier(active_dataptr->type), active_propname);
+ return false;
+ }
+
+ if (r_input_data->prop) {
+ const PropertyType type = RNA_property_type(r_input_data->prop);
+ if (type != PROP_COLLECTION) {
+ RNA_warning("Expected a collection data property");
+ return false;
+ }
+ }
+
+ const PropertyType activetype = RNA_property_type(r_input_data->activeprop);
+ if (activetype != PROP_INT) {
+ RNA_warning("Expected an integer active data property");
+ return false;
+ }
+
+ /* Find the uiList type. */
+ if (!(*r_list_type = WM_uilisttype_find(listtype_name, false))) {
+ RNA_warning("List type %s not found", listtype_name);
+ return false;
+ }
+
+ r_input_data->active_item_idx = RNA_property_int_get(&r_input_data->active_dataptr,
+ r_input_data->activeprop);
+ r_input_data->item_dyntip_propname = item_dyntip_propname;
+
+ return true;
+}
+
+static void ui_template_list_collect_items(PointerRNA *list_ptr,
+ PropertyRNA *list_prop,
+ uiListDyn *dyn_data,
+ int filter_exclude,
+ bool order_reverse,
+ int activei,
+ TemplateListItems *r_items)
+{
+ int i = 0;
+ int reorder_i = 0;
+ bool activei_mapping_pending = true;
+
+ RNA_PROP_BEGIN (list_ptr, itemptr, list_prop) {
+ if (!dyn_data->items_filter_flags ||
+ ((dyn_data->items_filter_flags[i] & UILST_FLT_ITEM) ^ filter_exclude)) {
+ int new_order_idx;
+ if (dyn_data->items_filter_neworder) {
+ new_order_idx = dyn_data->items_filter_neworder[reorder_i++];
+ new_order_idx = order_reverse ? dyn_data->items_shown - new_order_idx - 1 : new_order_idx;
+ }
+ else {
+ new_order_idx = order_reverse ? dyn_data->items_shown - ++reorder_i : reorder_i++;
+ }
+ // printf("%s: ii: %d\n", __func__, ii);
+ r_items->item_vec[new_order_idx].item = itemptr;
+ r_items->item_vec[new_order_idx].org_idx = i;
+ r_items->item_vec[new_order_idx].flt_flag = dyn_data->items_filter_flags ?
+ dyn_data->items_filter_flags[i] :
+ 0;
+
+ if (activei_mapping_pending && activei == i) {
+ activei = new_order_idx;
+ /* So that we do not map again activei! */
+ activei_mapping_pending = false;
+ }
+#if 0 /* For now, do not alter active element, even if it will be hidden... */
+ else if (activei < i) {
+ /* We do not want an active but invisible item!
+ * Only exception is when all items are filtered out...
+ */
+ if (prev_order_idx >= 0) {
+ activei = prev_order_idx;
+ RNA_property_int_set(active_dataptr, activeprop, prev_i);
+ }
+ else {
+ activei = new_order_idx;
+ RNA_property_int_set(active_dataptr, activeprop, i);
+ }
+ }
+ prev_i = i;
+ prev_ii = new_order_idx;
+#endif
+ }
+ i++;
+ }
+ RNA_PROP_END;
+
+ /* If mapping is still pending, no active item was found. Mark as invalid (-1) */
+ r_items->active_item_idx = activei_mapping_pending ? -1 : activei;
+}
+
+/**
+ * Create the UI-list representation of the list items, sorted and filtered if needed.
+ */
+static void ui_template_list_collect_display_items(bContext *C,
+ uiList *ui_list,
+ TemplateListInputData *input_data,
+ const uiListFilterItemsFunc filter_items_fn,
+ TemplateListItems *r_items)
+{
+ uiListDyn *dyn_data = ui_list->dyn_data;
+ memset(r_items, 0, sizeof(*r_items));
+
+ /* Filter list items! (not for compact layout, though) */
+ if (input_data->dataptr.data && input_data->prop) {
+ const int filter_exclude = ui_list->filter_flag & UILST_FLT_EXCLUDE;
+ const bool order_reverse = (ui_list->filter_sort_flag & UILST_FLT_SORT_REVERSE) != 0;
+ int items_shown;
+#if 0
+ int prev_ii = -1, prev_i;
+#endif
+
+ if (ui_list->layout_type == UILST_LAYOUT_COMPACT) {
+ dyn_data->items_len = dyn_data->items_shown = RNA_property_collection_length(
+ &input_data->dataptr, input_data->prop);
+ }
+ else {
+ // printf("%s: filtering...\n", __func__);
+ filter_items_fn(ui_list, C, &input_data->dataptr, RNA_property_identifier(input_data->prop));
+ // printf("%s: filtering done.\n", __func__);
+ }
+
+ items_shown = dyn_data->items_shown;
+ if (items_shown >= 0) {
+ r_items->item_vec = MEM_mallocN(sizeof(*r_items->item_vec) * items_shown, __func__);
+ // printf("%s: items shown: %d.\n", __func__, items_shown);
+
+ ui_template_list_collect_items(&input_data->dataptr,
+ input_data->prop,
+ dyn_data,
+ filter_exclude,
+ order_reverse,
+ input_data->active_item_idx,
+ r_items);
+ }
+ if (dyn_data->items_shown >= 0) {
+ r_items->tot_items = dyn_data->items_shown;
+ }
+ else {
+ r_items->tot_items = dyn_data->items_len;
+ }
+ }
+}
+
+static void ui_template_list_free_items(TemplateListItems *items)
+{
+ if (items->item_vec) {
+ MEM_freeN(items->item_vec);
+ }
+}
static void uilist_prepare(uiList *ui_list,
- int len,
- int activei,
- int rows,
- int maxrows,
- int columns,
- uiListLayoutdata *layoutdata)
+ const TemplateListItems *items,
+ const TemplateListLayoutDrawData *layout_data,
+ TemplateListVisualInfo *r_visual_info)
{
uiListDyn *dyn_data = ui_list->dyn_data;
- const bool use_auto_size = (ui_list->list_grip < (rows - UI_LIST_AUTO_SIZE_THRESHOLD));
+ const bool use_auto_size = (ui_list->list_grip <
+ (layout_data->rows - UI_LIST_AUTO_SIZE_THRESHOLD));
+
+ int actual_rows = layout_data->rows;
+ int actual_maxrows = layout_data->maxrows;
+ int columns = layout_data->columns;
/* default rows */
- if (rows <= 0) {
- rows = 5;
+ if (actual_rows <= 0) {
+ actual_rows = 5;
}
- dyn_data->visual_height_min = rows;
- if (maxrows < rows) {
- maxrows = max_ii(rows, 5);
+ dyn_data->visual_height_min = actual_rows;
+ if (actual_maxrows < actual_rows) {
+ actual_maxrows = max_ii(actual_rows, 5);
}
if (columns <= 0) {
columns = 9;
@@ -5865,42 +6112,46 @@ static void uilist_prepare(uiList *ui_list,
int activei_row;
if (columns > 1) {
- dyn_data->height = (int)ceil((double)len / (double)columns);
- activei_row = (int)floor((double)activei / (double)columns);
+ dyn_data->height = (int)ceil((double)items->tot_items / (double)columns);
+ activei_row = (int)floor((double)items->active_item_idx / (double)columns);
}
else {
- dyn_data->height = len;
- activei_row = activei;
+ dyn_data->height = items->tot_items;
+ activei_row = items->active_item_idx;
}
+ dyn_data->columns = columns;
+
if (!use_auto_size) {
/* No auto-size, yet we clamp at min size! */
- maxrows = rows = max_ii(ui_list->list_grip, rows);
+ actual_rows = max_ii(ui_list->list_grip, actual_rows);
}
- else if ((rows != maxrows) && (dyn_data->height > rows)) {
+ else if ((actual_rows != actual_maxrows) && (dyn_data->height > actual_rows)) {
/* Expand size if needed and possible. */
- rows = min_ii(dyn_data->height, maxrows);
+ actual_rows = min_ii(dyn_data->height, actual_maxrows);
}
/* If list length changes or list is tagged to check this,
* and active is out of view, scroll to it. */
- if (ui_list->list_last_len != len || ui_list->flag & UILST_SCROLL_TO_ACTIVE_ITEM) {
+ if ((ui_list->list_last_len != items->tot_items) ||
+ (ui_list->flag & UILST_SCROLL_TO_ACTIVE_ITEM)) {
if (activei_row < ui_list->list_scroll) {
ui_list->list_scroll = activei_row;
}
- else if (activei_row >= ui_list->list_scroll + rows) {
- ui_list->list_scroll = activei_row - rows + 1;
+ else if (activei_row >= ui_list->list_scroll + actual_rows) {
+ ui_list->list_scroll = activei_row - actual_rows + 1;
}
ui_list->flag &= ~UILST_SCROLL_TO_ACTIVE_ITEM;
}
- const int max_scroll = max_ii(0, dyn_data->height - rows);
+ const int max_scroll = max_ii(0, dyn_data->height - actual_rows);
CLAMP(ui_list->list_scroll, 0, max_scroll);
- ui_list->list_last_len = len;
- dyn_data->visual_height = rows;
- layoutdata->visual_items = rows * columns;
- layoutdata->start_idx = ui_list->list_scroll * columns;
- layoutdata->end_idx = min_ii(layoutdata->start_idx + rows * columns, len);
+ ui_list->list_last_len = items->tot_items;
+ dyn_data->visual_height = actual_rows;
+ r_visual_info->visual_items = actual_rows * columns;
+ r_visual_info->start_idx = ui_list->list_scroll * columns;
+ r_visual_info->end_idx = min_ii(r_visual_info->start_idx + actual_rows * columns,
+ items->tot_items);
}
static void uilist_resize_update_cb(bContext *C, void *arg1, void *UNUSED(arg2))
@@ -5940,117 +6191,32 @@ static char *uilist_item_tooltip_func(bContext *UNUSED(C), void *argN, const cha
return BLI_sprintfN("%s - %s", tip, dyn_tooltip);
}
-void uiTemplateList(uiLayout *layout,
- bContext *C,
- const char *listtype_name,
- const char *list_id,
- PointerRNA *dataptr,
- const char *propname,
- PointerRNA *active_dataptr,
- const char *active_propname,
- const char *item_dyntip_propname,
- int rows,
- int maxrows,
- int layout_type,
- int columns,
- bool sort_reverse,
- bool sort_lock)
+/**
+ * \note Note that \a layout_type may be NULL.
+ */
+static uiList *ui_list_ensure(bContext *C,
+ uiListType *ui_list_type,
+ const char *list_id,
+ int layout_type,
+ bool sort_reverse,
+ bool sort_lock)
{
- PropertyRNA *prop = NULL, *activeprop;
- _uilist_item *items_ptr = NULL;
- uiLayout *glob = NULL, *box, *row, *col, *subrow, *sub, *overlap;
- uiBut *but;
-
- uiListLayoutdata layoutdata;
- char ui_list_id[UI_MAX_NAME_STR];
- char numstr[32];
- int rnaicon = ICON_NONE, icon = ICON_NONE;
- int i = 0, activei = 0;
- int len = 0;
-
- /* validate arguments */
- /* Forbid default UI_UL_DEFAULT_CLASS_NAME list class without a custom list_id! */
- if (STREQ(UI_UL_DEFAULT_CLASS_NAME, listtype_name) && !(list_id && list_id[0])) {
- RNA_warning("template_list using default '%s' UIList class must provide a custom list_id",
- UI_UL_DEFAULT_CLASS_NAME);
- return;
- }
-
- uiBlock *block = uiLayoutGetBlock(layout);
-
- if (!active_dataptr->data) {
- RNA_warning("No active data");
- return;
- }
-
- if (dataptr->data) {
- prop = RNA_struct_find_property(dataptr, propname);
- if (!prop) {
- RNA_warning("Property not found: %s.%s", RNA_struct_identifier(dataptr->type), propname);
- return;
- }
- }
-
- activeprop = RNA_struct_find_property(active_dataptr, active_propname);
- if (!activeprop) {
- RNA_warning(
- "Property not found: %s.%s", RNA_struct_identifier(active_dataptr->type), active_propname);
- return;
- }
-
- if (prop) {
- const PropertyType type = RNA_property_type(prop);
- if (type != PROP_COLLECTION) {
- RNA_warning("Expected a collection data property");
- return;
- }
- }
-
- const PropertyType activetype = RNA_property_type(activeprop);
- if (activetype != PROP_INT) {
- RNA_warning("Expected an integer active data property");
- return;
- }
-
- /* get icon */
- if (dataptr->data && prop) {
- StructRNA *ptype = RNA_property_pointer_type(dataptr, prop);
- rnaicon = RNA_struct_ui_icon(ptype);
- }
-
- /* get active data */
- activei = RNA_property_int_get(active_dataptr, activeprop);
-
- /* Find the uiList type. */
- uiListType *ui_list_type = WM_uilisttype_find(listtype_name, false);
-
- if (ui_list_type == NULL) {
- RNA_warning("List type %s not found", listtype_name);
- return;
- }
-
- uiListDrawItemFunc draw_item = ui_list_type->draw_item ? ui_list_type->draw_item :
- uilist_draw_item_default;
- uiListDrawFilterFunc draw_filter = ui_list_type->draw_filter ? ui_list_type->draw_filter :
- uilist_draw_filter_default;
- uiListFilterItemsFunc filter_items = ui_list_type->filter_items ? ui_list_type->filter_items :
- uilist_filter_items_default;
-
- /* Find or add the uiList to the current Region. */
- /* We tag the list id with the list type... */
- BLI_snprintf(
- ui_list_id, sizeof(ui_list_id), "%s_%s", ui_list_type->idname, list_id ? list_id : "");
-
/* Allows to work in popups. */
ARegion *region = CTX_wm_menu(C);
if (region == NULL) {
region = CTX_wm_region(C);
}
- uiList *ui_list = BLI_findstring(&region->ui_lists, ui_list_id, offsetof(uiList, list_id));
+
+ /* Find or add the uiList to the current Region. */
+
+ char full_list_id[UI_MAX_NAME_STR];
+ WM_uilisttype_to_full_list_id(ui_list_type, list_id, full_list_id);
+
+ uiList *ui_list = BLI_findstring(&region->ui_lists, full_list_id, offsetof(uiList, list_id));
if (!ui_list) {
ui_list = MEM_callocN(sizeof(uiList), "uiList");
- BLI_strncpy(ui_list->list_id, ui_list_id, sizeof(ui_list->list_id));
+ BLI_strncpy(ui_list->list_id, full_list_id, sizeof(ui_list->list_id));
BLI_addtail(&region->ui_lists, ui_list);
ui_list->list_grip = -UI_LIST_AUTO_SIZE_THRESHOLD; /* Force auto size by default. */
if (sort_reverse) {
@@ -6065,6 +6231,9 @@ void uiTemplateList(uiLayout *layout,
ui_list->dyn_data = MEM_callocN(sizeof(uiListDyn), "uiList.dyn_data");
}
uiListDyn *dyn_data = ui_list->dyn_data;
+ /* Note that this isn't a `uiListType` callback, it's stored in the runtime list data. Otherwise
+ * the runtime data could leak when the type is unregistered (e.g. on "Reload Scripts"). */
+ dyn_data->free_runtime_data_fn = uilist_free_dyn_data;
/* Because we can't actually pass type across save&load... */
ui_list->type = ui_list_type;
@@ -6075,110 +6244,55 @@ void uiTemplateList(uiLayout *layout,
MEM_SAFE_FREE(dyn_data->items_filter_neworder);
dyn_data->items_len = dyn_data->items_shown = -1;
- /* When active item changed since last draw, scroll to it. */
- if (activei != ui_list->list_last_activei) {
- ui_list->flag |= UILST_SCROLL_TO_ACTIVE_ITEM;
- ui_list->list_last_activei = activei;
- }
+ return ui_list;
+}
- /* Filter list items! (not for compact layout, though) */
- if (dataptr->data && prop) {
- const int filter_exclude = ui_list->filter_flag & UILST_FLT_EXCLUDE;
- const bool order_reverse = (ui_list->filter_sort_flag & UILST_FLT_SORT_REVERSE) != 0;
- int items_shown, idx = 0;
-#if 0
- int prev_ii = -1, prev_i;
-#endif
+static void ui_template_list_layout_draw(bContext *C,
+ uiList *ui_list,
+ uiLayout *layout,
+ TemplateListInputData *input_data,
+ TemplateListItems *items,
+ const TemplateListLayoutDrawData *layout_data,
+ const enum uiTemplateListFlags flags)
+{
+ uiListDyn *dyn_data = ui_list->dyn_data;
+ const char *active_propname = RNA_property_identifier(input_data->activeprop);
- if (layout_type == UILST_LAYOUT_COMPACT) {
- dyn_data->items_len = dyn_data->items_shown = RNA_property_collection_length(dataptr, prop);
- }
- else {
- // printf("%s: filtering...\n", __func__);
- filter_items(ui_list, C, dataptr, propname);
- // printf("%s: filtering done.\n", __func__);
- }
+ uiLayout *glob = NULL, *box, *row, *col, *subrow, *sub, *overlap;
+ char numstr[32];
+ int rnaicon = ICON_NONE, icon = ICON_NONE;
+ uiBut *but;
- items_shown = dyn_data->items_shown;
- if (items_shown >= 0) {
- bool activei_mapping_pending = true;
- items_ptr = MEM_mallocN(sizeof(_uilist_item) * items_shown, __func__);
- // printf("%s: items shown: %d.\n", __func__, items_shown);
- RNA_PROP_BEGIN (dataptr, itemptr, prop) {
- if (!dyn_data->items_filter_flags ||
- ((dyn_data->items_filter_flags[i] & UILST_FLT_ITEM) ^ filter_exclude)) {
- int ii;
- if (dyn_data->items_filter_neworder) {
- ii = dyn_data->items_filter_neworder[idx++];
- ii = order_reverse ? items_shown - ii - 1 : ii;
- }
- else {
- ii = order_reverse ? items_shown - ++idx : idx++;
- }
- // printf("%s: ii: %d\n", __func__, ii);
- items_ptr[ii].item = itemptr;
- items_ptr[ii].org_idx = i;
- items_ptr[ii].flt_flag = dyn_data->items_filter_flags ? dyn_data->items_filter_flags[i] :
- 0;
-
- if (activei_mapping_pending && activei == i) {
- activei = ii;
- /* So that we do not map again activei! */
- activei_mapping_pending = false;
- }
-#if 0 /* For now, do not alter active element, even if it will be hidden... */
- else if (activei < i) {
- /* We do not want an active but invisible item!
- * Only exception is when all items are filtered out...
- */
- if (prev_ii >= 0) {
- activei = prev_ii;
- RNA_property_int_set(active_dataptr, activeprop, prev_i);
- }
- else {
- activei = ii;
- RNA_property_int_set(active_dataptr, activeprop, i);
- }
- }
- prev_i = i;
- prev_ii = ii;
-#endif
- }
- i++;
- }
- RNA_PROP_END;
+ uiBlock *block = uiLayoutGetBlock(layout);
- if (activei_mapping_pending) {
- /* No active item found, set to 'invalid' -1 value... */
- activei = -1;
- }
- }
- if (dyn_data->items_shown >= 0) {
- len = dyn_data->items_shown;
- }
- else {
- len = dyn_data->items_len;
- }
+ /* get icon */
+ if (input_data->dataptr.data && input_data->prop) {
+ StructRNA *ptype = RNA_property_pointer_type(&input_data->dataptr, input_data->prop);
+ rnaicon = RNA_struct_ui_icon(ptype);
}
- switch (layout_type) {
- case UILST_LAYOUT_DEFAULT:
+ TemplateListVisualInfo visual_info;
+ switch (ui_list->layout_type) {
+ case UILST_LAYOUT_DEFAULT: {
/* layout */
- box = uiLayoutListBox(layout, ui_list, active_dataptr, activeprop);
+ box = uiLayoutListBox(layout, ui_list, &input_data->active_dataptr, input_data->activeprop);
glob = uiLayoutColumn(box, true);
row = uiLayoutRow(glob, false);
col = uiLayoutColumn(row, true);
+ TemplateListLayoutDrawData adjusted_layout_data = *layout_data;
+ adjusted_layout_data.columns = 1;
/* init numbers */
- uilist_prepare(ui_list, len, activei, rows, maxrows, 1, &layoutdata);
+ uilist_prepare(ui_list, items, &adjusted_layout_data, &visual_info);
- if (dataptr->data && prop) {
+ int i = 0;
+ if (input_data->dataptr.data && input_data->prop) {
/* create list items */
- for (i = layoutdata.start_idx; i < layoutdata.end_idx; i++) {
- PointerRNA *itemptr = &items_ptr[i].item;
+ for (i = visual_info.start_idx; i < visual_info.end_idx; i++) {
+ PointerRNA *itemptr = &items->item_vec[i].item;
void *dyntip_data;
- const int org_i = items_ptr[i].org_idx;
- const int flt_flag = items_ptr[i].flt_flag;
+ const int org_i = items->item_vec[i].org_idx;
+ const int flt_flag = items->item_vec[i].flt_flag;
uiBlock *subblock = uiLayoutGetBlock(col);
overlap = uiLayoutOverlap(col);
@@ -6186,7 +6300,7 @@ void uiTemplateList(uiLayout *layout,
UI_block_flag_enable(subblock, UI_BLOCK_LIST_ITEM);
/* list item behind label & other buttons */
- sub = uiLayoutRow(overlap, false);
+ uiLayoutRow(overlap, false);
but = uiDefButR_prop(subblock,
UI_BTYPE_LISTROW,
@@ -6196,15 +6310,16 @@ void uiTemplateList(uiLayout *layout,
0,
UI_UNIT_X * 10,
UI_UNIT_Y,
- active_dataptr,
- activeprop,
+ &input_data->active_dataptr,
+ input_data->activeprop,
0,
0,
org_i,
0,
0,
TIP_("Double click to rename"));
- if ((dyntip_data = uilist_item_use_dynamic_tooltip(itemptr, item_dyntip_propname))) {
+ if ((dyntip_data = uilist_item_use_dynamic_tooltip(itemptr,
+ input_data->item_dyntip_propname))) {
UI_but_func_tooltip_set(but, uilist_item_tooltip_func, dyntip_data, MEM_freeN);
}
@@ -6214,23 +6329,23 @@ void uiTemplateList(uiLayout *layout,
if (icon == ICON_DOT) {
icon = ICON_NONE;
}
- draw_item(ui_list,
- C,
- sub,
- dataptr,
- itemptr,
- icon,
- active_dataptr,
- active_propname,
- org_i,
- flt_flag);
+ layout_data->draw_item(ui_list,
+ C,
+ sub,
+ &input_data->dataptr,
+ itemptr,
+ icon,
+ &input_data->active_dataptr,
+ active_propname,
+ org_i,
+ flt_flag);
/* Items should be able to set context pointers for the layout. But the list-row button
* swallows events, so it needs the context storage too for handlers to see it. */
but->context = uiLayoutGetContextStore(sub);
/* If we are "drawing" active item, set all labels as active. */
- if (i == activei) {
+ if (i == items->active_item_idx) {
ui_layout_list_set_labels_active(sub);
}
@@ -6239,13 +6354,13 @@ void uiTemplateList(uiLayout *layout,
}
/* add dummy buttons to fill space */
- for (; i < layoutdata.start_idx + layoutdata.visual_items; i++) {
+ for (; i < visual_info.start_idx + visual_info.visual_items; i++) {
uiItemL(col, "", ICON_NONE);
}
/* add scrollbar */
- if (len > layoutdata.visual_items) {
- col = uiLayoutColumn(row, false);
+ if (items->tot_items > visual_info.visual_items) {
+ uiLayoutColumn(row, false);
uiDefButI(block,
UI_BTYPE_SCROLL,
0,
@@ -6261,21 +6376,29 @@ void uiTemplateList(uiLayout *layout,
0,
"");
}
- break;
+ } break;
case UILST_LAYOUT_COMPACT:
row = uiLayoutRow(layout, true);
- if ((dataptr->data && prop) && (dyn_data->items_shown > 0) && (activei >= 0) &&
- (activei < dyn_data->items_shown)) {
- PointerRNA *itemptr = &items_ptr[activei].item;
- const int org_i = items_ptr[activei].org_idx;
+ if ((input_data->dataptr.data && input_data->prop) && (dyn_data->items_shown > 0) &&
+ (items->active_item_idx >= 0) && (items->active_item_idx < dyn_data->items_shown)) {
+ PointerRNA *itemptr = &items->item_vec[items->active_item_idx].item;
+ const int org_i = items->item_vec[items->active_item_idx].org_idx;
icon = UI_icon_from_rnaptr(C, itemptr, rnaicon, false);
if (icon == ICON_DOT) {
icon = ICON_NONE;
}
- draw_item(
- ui_list, C, row, dataptr, itemptr, icon, active_dataptr, active_propname, org_i, 0);
+ layout_data->draw_item(ui_list,
+ C,
+ row,
+ &input_data->dataptr,
+ itemptr,
+ icon,
+ &input_data->active_dataptr,
+ active_propname,
+ org_i,
+ 0);
}
/* if list is empty, add in dummy button */
else {
@@ -6293,8 +6416,8 @@ void uiTemplateList(uiLayout *layout,
0,
UI_UNIT_X * 5,
UI_UNIT_Y,
- active_dataptr,
- activeprop,
+ &input_data->active_dataptr,
+ input_data->activeprop,
0,
0,
0,
@@ -6305,24 +6428,25 @@ void uiTemplateList(uiLayout *layout,
UI_but_flag_enable(but, UI_BUT_DISABLED);
}
break;
- case UILST_LAYOUT_GRID:
- box = uiLayoutListBox(layout, ui_list, active_dataptr, activeprop);
+ case UILST_LAYOUT_GRID: {
+ box = uiLayoutListBox(layout, ui_list, &input_data->active_dataptr, input_data->activeprop);
glob = uiLayoutColumn(box, true);
row = uiLayoutRow(glob, false);
col = uiLayoutColumn(row, true);
subrow = NULL; /* Quite gcc warning! */
- uilist_prepare(ui_list, len, activei, rows, maxrows, columns, &layoutdata);
+ uilist_prepare(ui_list, items, layout_data, &visual_info);
- if (dataptr->data && prop) {
+ int i = 0;
+ if (input_data->dataptr.data && input_data->prop) {
/* create list items */
- for (i = layoutdata.start_idx; i < layoutdata.end_idx; i++) {
- PointerRNA *itemptr = &items_ptr[i].item;
- const int org_i = items_ptr[i].org_idx;
- const int flt_flag = items_ptr[i].flt_flag;
+ for (i = visual_info.start_idx; i < visual_info.end_idx; i++) {
+ PointerRNA *itemptr = &items->item_vec[i].item;
+ const int org_i = items->item_vec[i].org_idx;
+ const int flt_flag = items->item_vec[i].flt_flag;
/* create button */
- if (!(i % columns)) {
+ if (!(i % layout_data->columns)) {
subrow = uiLayoutRow(col, false);
}
@@ -6332,7 +6456,7 @@ void uiTemplateList(uiLayout *layout,
UI_block_flag_enable(subblock, UI_BLOCK_LIST_ITEM);
/* list item behind label & other buttons */
- sub = uiLayoutRow(overlap, false);
+ uiLayoutRow(overlap, false);
but = uiDefButR_prop(subblock,
UI_BTYPE_LISTROW,
@@ -6342,8 +6466,8 @@ void uiTemplateList(uiLayout *layout,
0,
UI_UNIT_X * 10,
UI_UNIT_Y,
- active_dataptr,
- activeprop,
+ &input_data->active_dataptr,
+ input_data->activeprop,
0,
0,
org_i,
@@ -6355,19 +6479,19 @@ void uiTemplateList(uiLayout *layout,
sub = uiLayoutRow(overlap, false);
icon = UI_icon_from_rnaptr(C, itemptr, rnaicon, false);
- draw_item(ui_list,
- C,
- sub,
- dataptr,
- itemptr,
- icon,
- active_dataptr,
- active_propname,
- org_i,
- flt_flag);
+ layout_data->draw_item(ui_list,
+ C,
+ sub,
+ &input_data->dataptr,
+ itemptr,
+ icon,
+ &input_data->active_dataptr,
+ active_propname,
+ org_i,
+ flt_flag);
/* If we are "drawing" active item, set all labels as active. */
- if (i == activei) {
+ if (i == items->active_item_idx) {
ui_layout_list_set_labels_active(sub);
}
@@ -6376,15 +6500,15 @@ void uiTemplateList(uiLayout *layout,
}
/* add dummy buttons to fill space */
- for (; i < layoutdata.start_idx + layoutdata.visual_items; i++) {
- if (!(i % columns)) {
+ for (; i < visual_info.start_idx + visual_info.visual_items; i++) {
+ if (!(i % layout_data->columns)) {
subrow = uiLayoutRow(col, false);
}
uiItemL(subrow, "", ICON_NONE);
}
/* add scrollbar */
- if (len > layoutdata.visual_items) {
+ if (items->tot_items > visual_info.visual_items) {
/* col = */ uiLayoutColumn(row, false);
uiDefButI(block,
UI_BTYPE_SCROLL,
@@ -6402,9 +6526,12 @@ void uiTemplateList(uiLayout *layout,
"");
}
break;
+ }
}
if (glob) {
+ const bool add_grip_but = (flags & UI_TEMPLATE_LIST_NO_GRIP) == 0;
+
/* About #UI_BTYPE_GRIP drag-resize:
* We can't directly use results from a grip button, since we have a
* rather complex behavior here (sizing by discrete steps and, overall, auto-size feature).
@@ -6443,21 +6570,23 @@ void uiTemplateList(uiLayout *layout,
TIP_("Hide filtering options"));
UI_but_flag_disable(but, UI_BUT_UNDO); /* skip undo on screen buttons */
- but = uiDefIconButI(subblock,
- UI_BTYPE_GRIP,
- 0,
- ICON_GRIP,
- 0,
- 0,
- UI_UNIT_X * 10.0f,
- UI_UNIT_Y * 0.5f,
- &dyn_data->resize,
- 0.0,
- 0.0,
- 0,
- 0,
- "");
- UI_but_func_set(but, uilist_resize_update_cb, ui_list, NULL);
+ if (add_grip_but) {
+ but = uiDefIconButI(subblock,
+ UI_BTYPE_GRIP,
+ 0,
+ ICON_GRIP,
+ 0,
+ 0,
+ UI_UNIT_X * 10.0f,
+ UI_UNIT_Y * 0.5f,
+ &dyn_data->resize,
+ 0.0,
+ 0.0,
+ 0,
+ 0,
+ "");
+ UI_but_func_set(but, uilist_resize_update_cb, ui_list, NULL);
+ }
UI_block_emboss_set(subblock, UI_EMBOSS);
@@ -6478,7 +6607,7 @@ void uiTemplateList(uiLayout *layout,
0,
"");
- draw_filter(ui_list, C, col);
+ layout_data->draw_filter(ui_list, C, col);
}
else {
but = uiDefIconButBitI(subblock,
@@ -6498,29 +6627,131 @@ void uiTemplateList(uiLayout *layout,
TIP_("Show filtering options"));
UI_but_flag_disable(but, UI_BUT_UNDO); /* skip undo on screen buttons */
- but = uiDefIconButI(subblock,
- UI_BTYPE_GRIP,
- 0,
- ICON_GRIP,
- 0,
- 0,
- UI_UNIT_X * 10.0f,
- UI_UNIT_Y * 0.5f,
- &dyn_data->resize,
- 0.0,
- 0.0,
- 0,
- 0,
- "");
- UI_but_func_set(but, uilist_resize_update_cb, ui_list, NULL);
+ if (add_grip_but) {
+ but = uiDefIconButI(subblock,
+ UI_BTYPE_GRIP,
+ 0,
+ ICON_GRIP,
+ 0,
+ 0,
+ UI_UNIT_X * 10.0f,
+ UI_UNIT_Y * 0.5f,
+ &dyn_data->resize,
+ 0.0,
+ 0.0,
+ 0,
+ 0,
+ "");
+ UI_but_func_set(but, uilist_resize_update_cb, ui_list, NULL);
+ }
UI_block_emboss_set(subblock, UI_EMBOSS);
}
}
+}
+
+uiList *uiTemplateList_ex(uiLayout *layout,
+ bContext *C,
+ const char *listtype_name,
+ const char *list_id,
+ PointerRNA *dataptr,
+ const char *propname,
+ PointerRNA *active_dataptr,
+ const char *active_propname,
+ const char *item_dyntip_propname,
+ int rows,
+ int maxrows,
+ int layout_type,
+ int columns,
+ enum uiTemplateListFlags flags,
+ void *customdata)
+{
+ TemplateListInputData input_data = {0};
+ uiListType *ui_list_type;
+ if (!ui_template_list_data_retrieve(listtype_name,
+ list_id,
+ dataptr,
+ propname,
+ active_dataptr,
+ active_propname,
+ item_dyntip_propname,
+ &input_data,
+ &ui_list_type)) {
+ return NULL;
+ }
+
+ uiListDrawItemFunc draw_item = ui_list_type->draw_item ? ui_list_type->draw_item :
+ uilist_draw_item_default;
+ uiListDrawFilterFunc draw_filter = ui_list_type->draw_filter ? ui_list_type->draw_filter :
+ uilist_draw_filter_default;
+ uiListFilterItemsFunc filter_items = ui_list_type->filter_items ? ui_list_type->filter_items :
+ uilist_filter_items_default;
+
+ uiList *ui_list = ui_list_ensure(C,
+ ui_list_type,
+ list_id,
+ layout_type,
+ flags & UI_TEMPLATE_LIST_SORT_REVERSE,
+ flags & UI_TEMPLATE_LIST_SORT_LOCK);
+ uiListDyn *dyn_data = ui_list->dyn_data;
+
+ MEM_SAFE_FREE(dyn_data->customdata);
+ dyn_data->customdata = customdata;
- if (items_ptr) {
- MEM_freeN(items_ptr);
+ /* When active item changed since last draw, scroll to it. */
+ if (input_data.active_item_idx != ui_list->list_last_activei) {
+ ui_list->flag |= UILST_SCROLL_TO_ACTIVE_ITEM;
+ ui_list->list_last_activei = input_data.active_item_idx;
}
+
+ TemplateListItems items;
+ ui_template_list_collect_display_items(C, ui_list, &input_data, filter_items, &items);
+
+ TemplateListLayoutDrawData layout_data = {
+ .draw_item = draw_item,
+ .draw_filter = draw_filter,
+ .rows = rows,
+ .maxrows = maxrows,
+ .columns = columns,
+ };
+
+ ui_template_list_layout_draw(C, ui_list, layout, &input_data, &items, &layout_data, flags);
+
+ ui_template_list_free_items(&items);
+
+ return ui_list;
+}
+
+void uiTemplateList(uiLayout *layout,
+ bContext *C,
+ const char *listtype_name,
+ const char *list_id,
+ PointerRNA *dataptr,
+ const char *propname,
+ PointerRNA *active_dataptr,
+ const char *active_propname,
+ const char *item_dyntip_propname,
+ int rows,
+ int maxrows,
+ int layout_type,
+ int columns,
+ enum uiTemplateListFlags flags)
+{
+ uiTemplateList_ex(layout,
+ C,
+ listtype_name,
+ list_id,
+ dataptr,
+ propname,
+ active_dataptr,
+ active_propname,
+ item_dyntip_propname,
+ rows,
+ maxrows,
+ layout_type,
+ columns,
+ flags,
+ NULL);
}
/** \} */
diff --git a/source/blender/editors/space_node/drawnode.cc b/source/blender/editors/space_node/drawnode.cc
index 878a4e7d2c6..df3afb42ab2 100644
--- a/source/blender/editors/space_node/drawnode.cc
+++ b/source/blender/editors/space_node/drawnode.cc
@@ -1953,8 +1953,7 @@ static void node_composit_buts_file_output_ex(uiLayout *layout, bContext *C, Poi
0,
0,
0,
- false,
- false);
+ UI_TEMPLATE_LIST_FLAG_NONE);
RNA_property_collection_lookup_int(
ptr, RNA_struct_find_property(ptr, "layer_slots"), active_index, &active_input_ptr);
}
@@ -1972,8 +1971,7 @@ static void node_composit_buts_file_output_ex(uiLayout *layout, bContext *C, Poi
0,
0,
0,
- false,
- false);
+ UI_TEMPLATE_LIST_FLAG_NONE);
RNA_property_collection_lookup_int(
ptr, RNA_struct_find_property(ptr, "file_slots"), active_index, &active_input_ptr);
}
diff --git a/source/blender/makesdna/DNA_screen_types.h b/source/blender/makesdna/DNA_screen_types.h
index 670e84e0c7a..840685b81f6 100644
--- a/source/blender/makesdna/DNA_screen_types.h
+++ b/source/blender/makesdna/DNA_screen_types.h
@@ -43,6 +43,7 @@ struct SpaceLink;
struct SpaceType;
struct uiBlock;
struct uiLayout;
+struct uiList;
struct wmDrawBuffer;
struct wmTimer;
struct wmTooltipState;
@@ -246,11 +247,16 @@ typedef struct PanelCategoryStack {
char idname[64];
} PanelCategoryStack;
+typedef void (*uiListFreeRuntimeDataFunc)(struct uiList *ui_list);
+
/* uiList dynamic data... */
/* These two Lines with # tell makesdna this struct can be excluded. */
#
#
typedef struct uiListDyn {
+ /** Callback to free UI data when freeing UI-Lists in BKE. */
+ uiListFreeRuntimeDataFunc free_runtime_data_fn;
+
/** Number of rows needed to draw all elements. */
int height;
/** Actual visual height of the list (in rows). */
@@ -258,6 +264,9 @@ typedef struct uiListDyn {
/** Minimal visual height of the list (in rows). */
int visual_height_min;
+ /** Number of columns drawn for grid layouts. */
+ int columns;
+
/** Number of items in collection. */
int items_len;
/** Number of items actually visible after filtering. */
@@ -270,6 +279,9 @@ typedef struct uiListDyn {
int resize;
int resize_prev;
+ /* Allocated custom data. Free'ed together with the uiList (and when re-assigning). */
+ void *customdata;
+
/* Filtering data. */
/** Items_len length. */
int *items_filter_flags;
diff --git a/source/blender/makesrna/intern/rna_ui_api.c b/source/blender/makesrna/intern/rna_ui_api.c
index aa235b599b7..4b8938053ba 100644
--- a/source/blender/makesrna/intern/rna_ui_api.c
+++ b/source/blender/makesrna/intern/rna_ui_api.c
@@ -525,6 +525,46 @@ static void rna_uiTemplateAnyID(uiLayout *layout,
uiTemplateAnyID(layout, ptr, propname, proptypename, name);
}
+void rna_uiTemplateList(uiLayout *layout,
+ struct bContext *C,
+ const char *listtype_name,
+ const char *list_id,
+ struct PointerRNA *dataptr,
+ const char *propname,
+ struct PointerRNA *active_dataptr,
+ const char *active_propname,
+ const char *item_dyntip_propname,
+ const int rows,
+ const int maxrows,
+ const int layout_type,
+ const int columns,
+ const bool sort_reverse,
+ const bool sort_lock)
+{
+ int flags = UI_TEMPLATE_LIST_FLAG_NONE;
+ if (sort_reverse) {
+ flags |= UI_TEMPLATE_LIST_SORT_REVERSE;
+ }
+ if (sort_lock) {
+ flags |= UI_TEMPLATE_LIST_SORT_LOCK;
+ }
+
+ uiTemplateList(layout,
+ C,
+ listtype_name,
+ list_id,
+ dataptr,
+ propname,
+ active_dataptr,
+ active_propname,
+ item_dyntip_propname,
+ rows,
+ maxrows,
+ layout_type,
+ columns,
+ flags);
+}
+
static void rna_uiTemplateCacheFile(uiLayout *layout,
bContext *C,
PointerRNA *ptr,
@@ -1508,7 +1548,7 @@ void RNA_api_ui_layout(StructRNA *srna)
parm = RNA_def_pointer(func, "clip_user", "MovieClipUser", "", "");
RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED | PARM_RNAPTR);
- func = RNA_def_function(srna, "template_list", "uiTemplateList");
+ func = RNA_def_function(srna, "template_list", "rna_uiTemplateList");
RNA_def_function_ui_description(func, "Item. A list widget to display data, e.g. vertexgroups.");
RNA_def_function_flag(func, FUNC_USE_CONTEXT);
parm = RNA_def_string(func, "listtype_name", NULL, 0, "", "Identifier of the list type to use");
diff --git a/source/blender/windowmanager/WM_api.h b/source/blender/windowmanager/WM_api.h
index 1f4598d33fe..601db427381 100644
--- a/source/blender/windowmanager/WM_api.h
+++ b/source/blender/windowmanager/WM_api.h
@@ -621,6 +621,10 @@ bool WM_uilisttype_add(struct uiListType *ult);
void WM_uilisttype_freelink(struct uiListType *ult);
void WM_uilisttype_free(void);
+void WM_uilisttype_to_full_list_id(const struct uiListType *ult,
+ const char *list_id,
+ char *r_ui_list_id);
+
/* wm_menu_type.c */
void WM_menutype_init(void);
struct MenuType *WM_menutype_find(const char *idname, bool quiet);
diff --git a/source/blender/windowmanager/intern/wm_uilist_type.c b/source/blender/windowmanager/intern/wm_uilist_type.c
index 45c14c0bbe9..434131903fc 100644
--- a/source/blender/windowmanager/intern/wm_uilist_type.c
+++ b/source/blender/windowmanager/intern/wm_uilist_type.c
@@ -28,7 +28,10 @@
#include "MEM_guardedalloc.h"
+#include "UI_interface.h"
+
#include "BLI_ghash.h"
+#include "BLI_string.h"
#include "BLI_utildefines.h"
#include "BKE_screen.h"
@@ -88,3 +91,21 @@ void WM_uilisttype_free(void)
BLI_ghash_free(uilisttypes_hash, NULL, MEM_freeN);
uilisttypes_hash = NULL;
}
+
+/**
+ * The "full" list-ID is an internal name used for storing and identifying a list. It is built like
+ * this:
+ * "{uiListType.idname}_{list_id}", wherby "list_id" is an optional parameter passed to
+ * `UILayout.template_list()`. If it is not set, the full list-ID is just "{uiListType.idname}_".
+ *
+ * Note that whenever the Python API refers to the list-ID, it's the short, "non-full" one it
+ * passed to `UILayout.template_list()`. C code can query that through
+ * #WM_uilisttype_list_id_get().
+ */
+void WM_uilisttype_to_full_list_id(const uiListType *ult,
+ const char *list_id,
+ char r_full_list_id[UI_MAX_NAME_STR])
+{
+ /* We tag the list id with the list type... */
+ BLI_snprintf(r_full_list_id, UI_MAX_NAME_STR, "%s_%s", ult->idname, list_id ? list_id : "");
+}