/* SPDX-FileCopyrightText: 2024 Blender Authors * * SPDX-License-Identifier: GPL-2.0-or-later */ /** \file * \ingroup edinterface */ #include "BKE_anim_data.hh" #include "BKE_collection.hh" #include "BKE_context.hh" #include "BKE_idtype.hh" #include "BKE_layer.hh" #include "BKE_lib_id.hh" #include "BKE_lib_override.hh" #include "BKE_library.hh" #include "BKE_main.hh" #include "BKE_main_invariants.hh" #include "BKE_packedFile.hh" #include "BLI_listbase.h" #include "BLI_string.h" #include "BLI_string_search.hh" #include "BLT_translation.hh" #include "DEG_depsgraph_query.hh" #include "DNA_collection_types.h" #include "DNA_scene_types.h" #include "DNA_workspace_types.h" #include "ED_id_management.hh" #include "ED_node.hh" #include "ED_object.hh" #include "ED_undo.hh" #include "RNA_access.hh" #include "RNA_prototypes.hh" #include "WM_api.hh" #include "UI_interface.hh" #include "UI_string_search.hh" #include "interface_intern.hh" #include "interface_templates_intern.hh" using blender::StringRef; using blender::StringRefNull; struct TemplateID { PointerRNA ptr = {}; PropertyRNA *prop = nullptr; ListBase *idlb = nullptr; short idcode = 0; short filter = 0; int prv_rows = 0; int prv_cols = 0; bool preview = false; float scale = 0.0f; }; /* Search browse menu, assign. */ static void template_ID_set_property_exec_fn(bContext *C, void *arg_template, void *item) { TemplateID *template_ui = (TemplateID *)arg_template; /* ID */ if (item) { PointerRNA idptr = RNA_id_pointer_create(static_cast(item)); RNA_property_pointer_set(&template_ui->ptr, template_ui->prop, idptr, nullptr); RNA_property_update(C, &template_ui->ptr, template_ui->prop); } } static bool id_search_allows_id(TemplateID *template_ui, const int flag, ID *id, const char *query) { ID *id_from = template_ui->ptr.owner_id; /* Do self check. */ if ((flag & PROP_ID_SELF_CHECK) && id == id_from) { return false; } /* Use filter. */ if (RNA_property_type(template_ui->prop) == PROP_POINTER) { PointerRNA ptr = RNA_id_pointer_create(id); if (RNA_property_pointer_poll(&template_ui->ptr, template_ui->prop, &ptr) == 0) { return false; } } /* Hide dot prefixed data-blocks, but only if filter does not force them visible. */ if (U.uiflag & USER_HIDE_DOT) { if ((id->name[2] == '.') && (query[0] != '.')) { return false; } } return true; } static bool id_search_add(const bContext *C, TemplateID *template_ui, uiSearchItems *items, ID *id) { /* +1 is needed because BKE_id_ui_prefix used 3 letter prefix * followed by ID_NAME-2 characters from id->name */ char name_ui[MAX_ID_FULL_NAME_UI]; int iconid = ui_id_icon_get(C, id, template_ui->preview); const bool use_lib_prefix = template_ui->preview || iconid; const bool has_sep_char = ID_IS_LINKED(id); /* When using previews, the library hint (linked, overridden, missing) is added with a * character prefix, otherwise we can use a icon. */ int name_prefix_offset; BKE_id_full_name_ui_prefix_get(name_ui, id, use_lib_prefix, UI_SEP_CHAR, &name_prefix_offset); if (!use_lib_prefix) { iconid = UI_icon_from_library(id); } if (!UI_search_item_add(items, name_ui, id, iconid, has_sep_char ? int(UI_BUT_HAS_SEP_CHAR) : 0, name_prefix_offset)) { return false; } return true; } /* ID Search browse menu, do the search */ static void id_search_cb(const bContext *C, void *arg_template, const char *str, uiSearchItems *items, const bool /*is_first*/) { TemplateID *template_ui = (TemplateID *)arg_template; ListBase *lb = template_ui->idlb; const int flag = RNA_property_flag(template_ui->prop); blender::ui::string_search::StringSearch search; /* ID listbase */ LISTBASE_FOREACH (ID *, id, lb) { if (id_search_allows_id(template_ui, flag, id, str)) { search.add(id->name + 2, id); } } const blender::Vector filtered_ids = search.query(str); for (ID *id : filtered_ids) { if (!id_search_add(C, template_ui, items, id)) { break; } } } /** * Use id tags for filtering. */ static void id_search_cb_tagged(const bContext *C, void *arg_template, const char *str, uiSearchItems *items) { TemplateID *template_ui = (TemplateID *)arg_template; ListBase *lb = template_ui->idlb; const int flag = RNA_property_flag(template_ui->prop); blender::string_search::StringSearch search{nullptr, blender::string_search::MainWordsHeuristic::All}; /* ID listbase */ LISTBASE_FOREACH (ID *, id, lb) { if (id->tag & ID_TAG_DOIT) { if (id_search_allows_id(template_ui, flag, id, str)) { search.add(id->name + 2, id); } id->tag &= ~ID_TAG_DOIT; } } blender::Vector filtered_ids = search.query(str); for (ID *id : filtered_ids) { if (!id_search_add(C, template_ui, items, id)) { break; } } } /** * A version of 'id_search_cb' that lists scene objects. */ static void id_search_cb_objects_from_scene(const bContext *C, void *arg_template, const char *str, uiSearchItems *items, const bool /*is_first*/) { TemplateID *template_ui = (TemplateID *)arg_template; ListBase *lb = template_ui->idlb; Scene *scene = nullptr; ID *id_from = template_ui->ptr.owner_id; if (id_from && GS(id_from->name) == ID_SCE) { scene = (Scene *)id_from; } else { scene = CTX_data_scene(C); } BKE_main_id_flag_listbase(lb, ID_TAG_DOIT, false); FOREACH_SCENE_OBJECT_BEGIN (scene, ob_iter) { ob_iter->id.tag |= ID_TAG_DOIT; } FOREACH_SCENE_OBJECT_END; id_search_cb_tagged(C, arg_template, str, items); } static ARegion *template_ID_search_menu_item_tooltip( bContext *C, ARegion *region, const rcti *item_rect, void * /*arg*/, void *active) { ID *active_id = static_cast(active); return UI_tooltip_create_from_search_item_generic(C, region, item_rect, active_id); } /* ID Search browse menu, open */ static uiBlock *id_search_menu(bContext *C, ARegion *region, void *arg_litem) { static TemplateID template_ui; PointerRNA active_item_ptr; void (*id_search_update_fn)( const bContext *, void *, const char *, uiSearchItems *, const bool) = id_search_cb; /* arg_litem is malloced, can be freed by parent button */ template_ui = *((TemplateID *)arg_litem); active_item_ptr = RNA_property_pointer_get(&template_ui.ptr, template_ui.prop); if (template_ui.filter) { /* Currently only used for objects. */ if (template_ui.idcode == ID_OB) { if (template_ui.filter == UI_TEMPLATE_ID_FILTER_AVAILABLE) { id_search_update_fn = id_search_cb_objects_from_scene; } } } return template_common_search_menu(C, region, id_search_update_fn, &template_ui, template_ID_set_property_exec_fn, active_item_ptr.data, template_ID_search_menu_item_tooltip, template_ui.prv_rows, template_ui.prv_cols, template_ui.scale); } static void template_id_cb(bContext *C, void *arg_litem, void *arg_event); void UI_context_active_but_prop_get_templateID(const bContext *C, PointerRNA *r_ptr, PropertyRNA **r_prop) { uiBut *but = UI_context_active_but_get(C); *r_ptr = {}; *r_prop = nullptr; if (but && (but->funcN == template_id_cb) && but->func_argN) { TemplateID *template_ui = static_cast(but->func_argN); *r_ptr = template_ui->ptr; *r_prop = template_ui->prop; } } static void template_id_liboverride_hierarchy_collection_root_find_recursive( Collection *collection, const int parent_level, Collection **r_collection_parent_best, int *r_parent_level_best) { if (!ID_IS_LINKED(collection) && !ID_IS_OVERRIDE_LIBRARY_REAL(collection)) { return; } if (ID_IS_OVERRIDABLE_LIBRARY(collection) || ID_IS_OVERRIDE_LIBRARY_REAL(collection)) { if (parent_level > *r_parent_level_best) { *r_parent_level_best = parent_level; *r_collection_parent_best = collection; } } for (CollectionParent *iter = static_cast(collection->runtime.parents.first); iter != nullptr; iter = iter->next) { if (iter->collection->id.lib != collection->id.lib && ID_IS_LINKED(iter->collection)) { continue; } template_id_liboverride_hierarchy_collection_root_find_recursive( iter->collection, parent_level + 1, r_collection_parent_best, r_parent_level_best); } } static void template_id_liboverride_hierarchy_collections_tag_recursive( Collection *root_collection, ID *target_id, const bool do_parents) { root_collection->id.tag |= ID_TAG_DOIT; /* Tag all local parents of the root collection, so that usages of the root collection and other * linked ones can be replaced by the local overrides in those parents too. */ if (do_parents) { for (CollectionParent *iter = static_cast(root_collection->runtime.parents.first); iter != nullptr; iter = iter->next) { if (ID_IS_LINKED(iter->collection)) { continue; } iter->collection->id.tag |= ID_TAG_DOIT; } } for (CollectionChild *iter = static_cast(root_collection->children.first); iter != nullptr; iter = iter->next) { if (ID_IS_LINKED(iter->collection) && iter->collection->id.lib != target_id->lib) { continue; } if (GS(target_id->name) == ID_OB && !BKE_collection_has_object_recursive(iter->collection, (Object *)target_id)) { continue; } if (GS(target_id->name) == ID_GR && !BKE_collection_has_collection(iter->collection, (Collection *)target_id)) { continue; } template_id_liboverride_hierarchy_collections_tag_recursive( iter->collection, target_id, false); } } ID *ui_template_id_liboverride_hierarchy_make( bContext *C, Main *bmain, ID *owner_id, ID *id, const char **r_undo_push_label) { const char *undo_push_label; if (r_undo_push_label == nullptr) { r_undo_push_label = &undo_push_label; } /* If this is called on an already local override, 'toggle' between user-editable state, and * system override with reset. */ if (!ID_IS_LINKED(id) && ID_IS_OVERRIDE_LIBRARY(id)) { if (!ID_IS_OVERRIDE_LIBRARY_REAL(id)) { BKE_lib_override_library_get(bmain, id, nullptr, &id); } if (id->override_library->flag & LIBOVERRIDE_FLAG_SYSTEM_DEFINED) { id->override_library->flag &= ~LIBOVERRIDE_FLAG_SYSTEM_DEFINED; *r_undo_push_label = "Make Library Override Hierarchy Editable"; } else { BKE_lib_override_library_id_reset(bmain, id, true); *r_undo_push_label = "Clear Library Override Hierarchy"; } WM_event_add_notifier(C, NC_WM | ND_DATACHANGED, nullptr); WM_event_add_notifier(C, NC_WM | ND_LIB_OVERRIDE_CHANGED, nullptr); WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, nullptr); return id; } /* Attempt to perform a hierarchy override, based on contextual data available. * NOTE: do not attempt to perform such hierarchy override at all cost, if there is not enough * context, better to abort than create random overrides all over the place. */ if (!ID_IS_OVERRIDABLE_LIBRARY_HIERARCHY(id)) { WM_global_reportf(RPT_ERROR, "The data-block %s is not overridable", id->name); return nullptr; } Object *object_active = CTX_data_active_object(C); if (object_active == nullptr && GS(owner_id->name) == ID_OB) { object_active = (Object *)owner_id; } if (object_active != nullptr) { if (ID_IS_LINKED(object_active)) { if (object_active->id.lib != id->lib || !ID_IS_OVERRIDABLE_LIBRARY_HIERARCHY(object_active)) { /* The active object is from a different library than the overridden ID, or otherwise * cannot be used in hierarchy. */ object_active = nullptr; } } else if (!ID_IS_OVERRIDE_LIBRARY_REAL(object_active)) { /* Fully local object cannot be used in override hierarchy either. */ object_active = nullptr; } } Collection *collection_active_context = CTX_data_collection(C); Collection *collection_active = collection_active_context; if (collection_active == nullptr && GS(owner_id->name) == ID_GR) { collection_active = (Collection *)owner_id; } if (collection_active != nullptr) { if (ID_IS_LINKED(collection_active)) { if (collection_active->id.lib != id->lib || !ID_IS_OVERRIDABLE_LIBRARY_HIERARCHY(collection_active)) { /* The active collection is from a different library than the overridden ID, or otherwise * cannot be used in hierarchy. */ collection_active = nullptr; } else { int parent_level_best = -1; Collection *collection_parent_best = nullptr; template_id_liboverride_hierarchy_collection_root_find_recursive( collection_active, 0, &collection_parent_best, &parent_level_best); collection_active = collection_parent_best; } } else if (!ID_IS_OVERRIDE_LIBRARY_REAL(collection_active)) { /* Fully local collection cannot be used in override hierarchy either. */ collection_active = nullptr; } } if (collection_active == nullptr && object_active != nullptr && (ID_IS_LINKED(object_active) || ID_IS_OVERRIDE_LIBRARY_REAL(object_active))) { /* If we failed to find a valid 'active' collection so far for our override hierarchy, but do * have a valid 'active' object, try to find a collection from that object. */ LISTBASE_FOREACH (Collection *, collection_iter, &bmain->collections) { if (ID_IS_LINKED(collection_iter) && collection_iter->id.lib != id->lib) { continue; } if (!ID_IS_OVERRIDE_LIBRARY_REAL(collection_iter)) { continue; } if (!BKE_collection_has_object_recursive(collection_iter, object_active)) { continue; } int parent_level_best = -1; Collection *collection_parent_best = nullptr; template_id_liboverride_hierarchy_collection_root_find_recursive( collection_iter, 0, &collection_parent_best, &parent_level_best); collection_active = collection_parent_best; break; } } ID *id_override = nullptr; Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); switch (GS(id->name)) { case ID_GR: if (collection_active != nullptr && BKE_collection_has_collection(collection_active, (Collection *)id)) { template_id_liboverride_hierarchy_collections_tag_recursive(collection_active, id, true); if (object_active != nullptr) { object_active->id.tag |= ID_TAG_DOIT; } BKE_lib_override_library_create(bmain, scene, view_layer, nullptr, id, &collection_active->id, nullptr, &id_override, false); } else if (object_active != nullptr && !ID_IS_LINKED(object_active) && &object_active->instance_collection->id == id) { object_active->id.tag |= ID_TAG_DOIT; BKE_lib_override_library_create(bmain, scene, view_layer, id->lib, id, &object_active->id, &object_active->id, &id_override, false); } break; case ID_OB: if (collection_active != nullptr && BKE_collection_has_object_recursive(collection_active, (Object *)id)) { template_id_liboverride_hierarchy_collections_tag_recursive(collection_active, id, true); if (object_active != nullptr) { object_active->id.tag |= ID_TAG_DOIT; } BKE_lib_override_library_create(bmain, scene, view_layer, nullptr, id, &collection_active->id, nullptr, &id_override, false); } else { if (object_active != nullptr) { object_active->id.tag |= ID_TAG_DOIT; } BKE_lib_override_library_create( bmain, scene, view_layer, nullptr, id, nullptr, nullptr, &id_override, false); BKE_scene_collections_object_remove(bmain, scene, (Object *)id, true); WM_event_add_notifier(C, NC_ID | NA_REMOVED, nullptr); } break; case ID_ME: case ID_CU_LEGACY: case ID_MB: case ID_LT: case ID_LA: case ID_CA: case ID_SPK: case ID_AR: case ID_GD_LEGACY: case ID_CV: case ID_PT: case ID_VO: case ID_NT: /* Essentially geometry nodes from modifier currently. */ if (object_active != nullptr) { if (collection_active != nullptr && BKE_collection_has_object_recursive(collection_active, object_active)) { template_id_liboverride_hierarchy_collections_tag_recursive(collection_active, id, true); object_active->id.tag |= ID_TAG_DOIT; BKE_lib_override_library_create(bmain, scene, view_layer, nullptr, id, &collection_active->id, nullptr, &id_override, false); } else { object_active->id.tag |= ID_TAG_DOIT; BKE_lib_override_library_create(bmain, scene, view_layer, nullptr, id, &object_active->id, nullptr, &id_override, false); } } else { BKE_lib_override_library_create( bmain, scene, view_layer, nullptr, id, id, nullptr, &id_override, false); } break; case ID_MA: case ID_TE: case ID_IM: WM_global_reportf(RPT_WARNING, "The type of data-block %s is not yet implemented", id->name); break; case ID_WO: WM_global_reportf(RPT_WARNING, "The type of data-block %s is not yet implemented", id->name); break; case ID_PA: WM_global_reportf(RPT_WARNING, "The type of data-block %s is not yet implemented", id->name); break; default: WM_global_reportf(RPT_WARNING, "The type of data-block %s is not yet implemented", id->name); break; } if (id_override != nullptr) { id_override->override_library->flag &= ~LIBOVERRIDE_FLAG_SYSTEM_DEFINED; /* Ensure that the hierarchy root of the newly overridden data is instantiated in the scene, in * case it's a collection or object. */ ID *hierarchy_root = id_override->override_library->hierarchy_root; if (GS(hierarchy_root->name) == ID_OB) { Object *object_hierarchy_root = reinterpret_cast(hierarchy_root); if (!BKE_scene_has_object(scene, object_hierarchy_root)) { if (!ID_IS_LINKED(collection_active_context)) { BKE_collection_object_add(bmain, collection_active_context, object_hierarchy_root); } else { BKE_collection_object_add(bmain, scene->master_collection, object_hierarchy_root); } } } else if (GS(hierarchy_root->name) == ID_GR) { Collection *collection_hierarchy_root = reinterpret_cast(hierarchy_root); if (!BKE_collection_has_collection(scene->master_collection, collection_hierarchy_root)) { if (!ID_IS_LINKED(collection_active_context)) { BKE_collection_child_add(bmain, collection_active_context, collection_hierarchy_root); } else { BKE_collection_child_add(bmain, scene->master_collection, collection_hierarchy_root); } } } *r_undo_push_label = "Make Library Override Hierarchy"; /* In theory we could rely on setting/updating the RNA ID pointer property (as done by calling * code) to be enough. * * However, some rare ID pointers properties (like the 'active object in viewlayer' one used * for the Object templateID in the Object properties) use notifiers that do not enforce a * rebuild of outliner trees, leading to crashes. * * So for now, add some extra notifiers here. */ WM_event_add_notifier(C, NC_ID | NA_ADDED, nullptr); WM_event_add_notifier(C, NC_SPACE | ND_SPACE_OUTLINER, nullptr); } return id_override; } static void template_id_liboverride_hierarchy_make(bContext *C, Main *bmain, TemplateID *template_ui, PointerRNA *idptr, const char **r_undo_push_label) { ID *id = static_cast(idptr->data); ID *owner_id = template_ui->ptr.owner_id; ID *id_override = ui_template_id_liboverride_hierarchy_make( C, bmain, owner_id, id, r_undo_push_label); if (id_override != nullptr) { /* `idptr` is re-assigned to owner property to ensure proper updates etc. Here we also use it * to ensure remapping of the owner property from the linked data to the newly created * liboverride (note that in theory this remapping has already been done by code above), but * only in case owner ID was already local ID (override or pure local data). * * Otherwise, owner ID will also have been overridden, and remapped already to use it's * override of the data too. */ if (!ID_IS_LINKED(owner_id)) { *idptr = RNA_id_pointer_create(id_override); } } else { WM_global_reportf(RPT_ERROR, "The data-block %s could not be overridden", id->name); } } static void template_id_cb(bContext *C, void *arg_litem, void *arg_event) { TemplateID *template_ui = (TemplateID *)arg_litem; PointerRNA idptr = RNA_property_pointer_get(&template_ui->ptr, template_ui->prop); ID *id = static_cast(idptr.data); const int event = POINTER_AS_INT(arg_event); const char *undo_push_label = nullptr; switch (event) { case UI_ID_NOP: /* Don't do anything, typically set for buttons that execute an operator instead. They may * still assign the callback so the button can be identified as part of an ID-template. See * #UI_context_active_but_prop_get_templateID(). */ break; case UI_ID_RENAME: /* Only for the undo push. */ undo_push_label = "Rename Data-Block"; break; case UI_ID_BROWSE: case UI_ID_PIN: RNA_warning("warning, id event %d shouldn't come here", event); break; case UI_ID_OPEN: case UI_ID_ADD_NEW: /* these call UI_context_active_but_prop_get_templateID */ break; case UI_ID_DELETE: idptr = {}; RNA_property_pointer_set(&template_ui->ptr, template_ui->prop, idptr, nullptr); RNA_property_update(C, &template_ui->ptr, template_ui->prop); if (id && CTX_wm_window(C)->eventstate->modifier & KM_SHIFT) { /* only way to force-remove data (on save) */ id_us_clear_real(id); id_fake_user_clear(id); id->us = 0; undo_push_label = "Delete Data-Block"; } else { undo_push_label = "Unlink Data-Block"; } break; case UI_ID_FAKE_USER: if (id) { if (id->flag & ID_FLAG_FAKEUSER) { id_us_plus(id); } else { id_us_min(id); } undo_push_label = "Fake User"; } else { return; } break; case UI_ID_LOCAL: if (id) { Main *bmain = CTX_data_main(C); if (CTX_wm_window(C)->eventstate->modifier & KM_SHIFT) { template_id_liboverride_hierarchy_make(C, bmain, template_ui, &idptr, &undo_push_label); } else { if (BKE_lib_id_make_local(bmain, id, LIB_ID_MAKELOCAL_ASSET_DATA_CLEAR)) { BKE_id_newptr_and_tag_clear(id); /* Reassign to get proper updates/notifiers. */ idptr = RNA_property_pointer_get(&template_ui->ptr, template_ui->prop); undo_push_label = "Make Local"; } } if (undo_push_label != nullptr) { RNA_property_pointer_set(&template_ui->ptr, template_ui->prop, idptr, nullptr); RNA_property_update(C, &template_ui->ptr, template_ui->prop); } } break; case UI_ID_OVERRIDE: if (id && ID_IS_OVERRIDE_LIBRARY(id)) { Main *bmain = CTX_data_main(C); if (CTX_wm_window(C)->eventstate->modifier & KM_SHIFT) { template_id_liboverride_hierarchy_make(C, bmain, template_ui, &idptr, &undo_push_label); } else { BKE_lib_override_library_make_local(bmain, id); /* Reassign to get proper updates/notifiers. */ idptr = RNA_property_pointer_get(&template_ui->ptr, template_ui->prop); RNA_property_pointer_set(&template_ui->ptr, template_ui->prop, idptr, nullptr); RNA_property_update(C, &template_ui->ptr, template_ui->prop); undo_push_label = "Make Local"; } } break; case UI_ID_ALONE: if (id) { const bool do_scene_obj = ((GS(id->name) == ID_OB) && (template_ui->ptr.type == &RNA_LayerObjects)); /* make copy */ if (do_scene_obj) { Main *bmain = CTX_data_main(C); Scene *scene = CTX_data_scene(C); blender::ed::object::object_single_user_make(bmain, scene, (Object *)id); WM_event_add_notifier(C, NC_WINDOW, nullptr); DEG_relations_tag_update(bmain); } else { Main *bmain = CTX_data_main(C); id_single_user(C, id, &template_ui->ptr, template_ui->prop); WM_event_add_notifier(C, NC_SPACE | ND_SPACE_OUTLINER, nullptr); DEG_relations_tag_update(bmain); } BKE_main_ensure_invariants(*CTX_data_main(C)); undo_push_label = "Make Single User"; } break; #if 0 case UI_ID_AUTO_NAME: break; #endif } if (undo_push_label != nullptr) { ED_undo_push(C, undo_push_label); WM_event_add_notifier(C, NC_SPACE | ND_SPACE_OUTLINER, nullptr); } } static StringRef template_id_browse_tip(const StructRNA *type) { if (type) { switch ((ID_Type)RNA_type_to_ID_code(type)) { case ID_SCE: return N_("Browse Scene to be linked"); case ID_OB: return N_("Browse Object to be linked"); case ID_ME: return N_("Browse Mesh Data to be linked"); case ID_CU_LEGACY: return N_("Browse Curve Data to be linked"); case ID_MB: return N_("Browse Metaball Data to be linked"); case ID_MA: return N_("Browse Material to be linked"); case ID_TE: return N_("Browse Texture to be linked"); case ID_IM: return N_("Browse Image to be linked"); case ID_LS: return N_("Browse Line Style Data to be linked"); case ID_LT: return N_("Browse Lattice Data to be linked"); case ID_LA: return N_("Browse Light Data to be linked"); case ID_CA: return N_("Browse Camera Data to be linked"); case ID_WO: return N_("Browse World Settings to be linked"); case ID_SCR: return N_("Choose Screen layout"); case ID_TXT: return N_("Browse Text to be linked"); case ID_SPK: return N_("Browse Speaker Data to be linked"); case ID_SO: return N_("Browse Sound to be linked"); case ID_AR: return N_("Browse Armature data to be linked"); case ID_AC: return N_("Browse Action to be linked"); case ID_NT: return N_("Browse Node Tree to be linked"); case ID_BR: return N_("Browse Brush to be linked"); case ID_PA: return N_("Browse Particle Settings to be linked"); case ID_GD_LEGACY: return N_("Browse Grease Pencil Data to be linked"); case ID_MC: return N_("Browse Movie Clip to be linked"); case ID_MSK: return N_("Browse Mask to be linked"); case ID_PAL: return N_("Browse Palette Data to be linked"); case ID_PC: return N_("Browse Paint Curve Data to be linked"); case ID_CF: return N_("Browse Cache Files to be linked"); case ID_WS: return N_("Browse Workspace to be linked"); case ID_LP: return N_("Browse LightProbe to be linked"); case ID_CV: return N_("Browse Curves Data to be linked"); case ID_PT: return N_("Browse Point Cloud Data to be linked"); case ID_VO: return N_("Browse Volume Data to be linked"); case ID_GP: return N_("Browse Grease Pencil v3 Data to be linked"); /* Use generic text. */ case ID_LI: case ID_IP: case ID_KE: case ID_VF: case ID_GR: case ID_WM: break; } } return N_("Browse ID data to be linked"); } /** * Add a superimposed extra icon to \a but, for workspace pinning. * Rather ugly special handling, but this is really a special case at this point, nothing worth * generalizing. */ static void template_id_workspace_pin_extra_icon(const TemplateID &template_ui, uiBut *but) { if ((template_ui.idcode != ID_SCE) || (template_ui.ptr.type != &RNA_Window)) { return; } const wmWindow *win = static_cast(template_ui.ptr.data); const WorkSpace *workspace = WM_window_get_active_workspace(win); UI_but_extra_operator_icon_add(but, "WORKSPACE_OT_scene_pin_toggle", WM_OP_INVOKE_DEFAULT, (workspace->flags & WORKSPACE_USE_PIN_SCENE) ? ICON_PINNED : ICON_UNPINNED); } /** * \return a type-based i18n context, needed e.g. by "New" button. * In most languages, this adjective takes different form based on gender of type name... */ #ifdef WITH_INTERNATIONAL static const char *template_id_context(StructRNA *type) { if (type) { return BKE_idtype_idcode_to_translation_context(RNA_type_to_ID_code(type)); } return BLT_I18NCONTEXT_DEFAULT; } #else # define template_id_context(type) 0 #endif static uiBut *template_id_def_new_but(uiBlock *block, const ID *id, const TemplateID &template_ui, StructRNA *type, const char *const newop, const bool editable, const bool id_open, const bool use_tab_but, int but_height) { ID *idfrom = template_ui.ptr.owner_id; uiBut *but; const int but_type = use_tab_but ? UI_BTYPE_TAB : UI_BTYPE_BUT; /* i18n markup, does nothing! */ BLT_I18N_MSGID_MULTI_CTXT("New", BLT_I18NCONTEXT_DEFAULT, BLT_I18NCONTEXT_ID_ACTION, BLT_I18NCONTEXT_ID_ARMATURE, BLT_I18NCONTEXT_ID_BRUSH, BLT_I18NCONTEXT_ID_CAMERA, BLT_I18NCONTEXT_ID_CURVES, BLT_I18NCONTEXT_ID_CURVE_LEGACY, BLT_I18NCONTEXT_ID_FREESTYLELINESTYLE, BLT_I18NCONTEXT_ID_GPENCIL, BLT_I18NCONTEXT_ID_IMAGE, BLT_I18NCONTEXT_ID_LATTICE, BLT_I18NCONTEXT_ID_LIGHT, BLT_I18NCONTEXT_ID_LIGHTPROBE, BLT_I18NCONTEXT_ID_MASK, BLT_I18NCONTEXT_ID_MATERIAL, BLT_I18NCONTEXT_ID_MESH, ); BLT_I18N_MSGID_MULTI_CTXT("New", BLT_I18NCONTEXT_ID_METABALL, BLT_I18NCONTEXT_ID_NODETREE, BLT_I18NCONTEXT_ID_OBJECT, BLT_I18NCONTEXT_ID_PAINTCURVE, BLT_I18NCONTEXT_ID_PALETTE, BLT_I18NCONTEXT_ID_PARTICLESETTINGS, BLT_I18NCONTEXT_ID_POINTCLOUD, BLT_I18NCONTEXT_ID_SCENE, BLT_I18NCONTEXT_ID_SCREEN, BLT_I18NCONTEXT_ID_SOUND, BLT_I18NCONTEXT_ID_SPEAKER, BLT_I18NCONTEXT_ID_TEXT, BLT_I18NCONTEXT_ID_TEXTURE, BLT_I18NCONTEXT_ID_VOLUME, BLT_I18NCONTEXT_ID_WORKSPACE, BLT_I18NCONTEXT_ID_WORLD, ); /* NOTE: BLT_I18N_MSGID_MULTI_CTXT takes a maximum number of parameters, * check the definition to see if a new call must be added when the limit * is exceeded. */ const char *button_text = (id) ? "" : CTX_IFACE_(template_id_context(type), "New"); const int icon = (id && !use_tab_but) ? ICON_DUPLICATE : ICON_ADD; const uiFontStyle *fstyle = UI_FSTYLE_WIDGET; int w = id ? UI_UNIT_X : id_open ? UI_UNIT_X * 3 : UI_UNIT_X * 6; if (!id) { w = std::max(UI_fontstyle_string_width(fstyle, button_text) + int(UI_UNIT_X * 1.5f), w); } if (newop) { but = uiDefIconTextButO(block, but_type, newop, WM_OP_INVOKE_DEFAULT, icon, button_text, 0, 0, w, but_height, std::nullopt); UI_but_funcN_set(but, template_id_cb, MEM_new(__func__, template_ui), POINTER_FROM_INT(UI_ID_ADD_NEW), but_func_argN_free, but_func_argN_copy); } else { but = uiDefIconTextBut( block, but_type, 0, icon, button_text, 0, 0, w, but_height, nullptr, 0, 0, std::nullopt); UI_but_funcN_set(but, template_id_cb, MEM_new(__func__, template_ui), POINTER_FROM_INT(UI_ID_ADD_NEW), but_func_argN_free, but_func_argN_copy); } if ((idfrom && !ID_IS_EDITABLE(idfrom)) || !editable) { UI_but_flag_enable(but, UI_BUT_DISABLED); } #ifndef WITH_INTERNATIONAL UNUSED_VARS(type); #endif return but; } static void template_ID(const bContext *C, uiLayout *layout, TemplateID &template_ui, StructRNA *type, int flag, const char *newop, const char *openop, const char *unlinkop, const std::optional text, const bool live_icon, const bool hide_buttons) { uiBut *but; const bool editable = RNA_property_editable(&template_ui.ptr, template_ui.prop); const bool use_previews = template_ui.preview = (flag & UI_ID_PREVIEWS) != 0; PointerRNA idptr = RNA_property_pointer_get(&template_ui.ptr, template_ui.prop); ID *id = static_cast(idptr.data); ID *idfrom = template_ui.ptr.owner_id; // lb = template_ui->idlb; /* Allow operators to take the ID from context. */ uiLayoutSetContextPointer(layout, "id", &idptr); uiBlock *block = uiLayoutGetBlock(layout); UI_block_align_begin(block); if (idptr.type) { type = idptr.type; } if (text && !text->is_empty()) { /* Add label respecting the separated layout property split state. */ uiItemL_respect_property_split(layout, *text, ICON_NONE); } if (flag & UI_ID_BROWSE) { template_add_button_search_menu(C, layout, block, &template_ui.ptr, template_ui.prop, id_search_menu, MEM_new(__func__, template_ui), TIP_(template_id_browse_tip(type)), use_previews, editable, live_icon, but_func_argN_free, but_func_argN_copy); } /* text button with name */ if (id) { char name[UI_MAX_NAME_STR]; const bool user_alert = (id->us <= 0); int width = template_search_textbut_width(&idptr, RNA_struct_find_property(&idptr, "name")); if ((template_ui.idcode == ID_SCE) && (template_ui.ptr.type == &RNA_Window)) { /* More room needed for "pin" icon. */ width += UI_UNIT_X; } const int height = template_search_textbut_height(); // text_idbutton(id, name); name[0] = '\0'; but = uiDefButR(block, UI_BTYPE_TEXT, 0, name, 0, 0, width, height, &idptr, "name", -1, 0, 0, RNA_struct_ui_description(type)); /* Handle undo through the #template_id_cb set below. Default undo handling from the button * code (see #ui_apply_but_undo) would not work here, as the new name is not yet applied to the * ID. */ UI_but_flag_disable(but, UI_BUT_UNDO); Main *bmain = CTX_data_main(C); UI_but_func_rename_full_set( but, [bmain, id](std::string &new_name) { ED_id_rename(*bmain, *id, new_name); }); UI_but_funcN_set(but, template_id_cb, MEM_new(__func__, template_ui), POINTER_FROM_INT(UI_ID_RENAME), but_func_argN_free, but_func_argN_copy); if (user_alert) { UI_but_flag_enable(but, UI_BUT_REDALERT); } template_id_workspace_pin_extra_icon(template_ui, but); if (!hide_buttons && !(idfrom && ID_IS_LINKED(idfrom))) { if (ID_IS_LINKED(id)) { const bool disabled = !BKE_idtype_idcode_is_localizable(GS(id->name)); if (id->tag & ID_TAG_INDIRECT) { but = uiDefIconBut(block, UI_BTYPE_BUT, 0, ICON_LIBRARY_DATA_INDIRECT, 0, 0, UI_UNIT_X, UI_UNIT_Y, nullptr, 0, 0, TIP_("Indirect library data-block, cannot be made local, " "Shift + Click to create a library override hierarchy")); } else { but = uiDefIconBut(block, UI_BTYPE_BUT, 0, ICON_LIBRARY_DATA_DIRECT, 0, 0, UI_UNIT_X, UI_UNIT_Y, nullptr, 0, 0, TIP_("Direct linked library data-block, click to make local, " "Shift + Click to create a library override")); } if (disabled) { UI_but_flag_enable(but, UI_BUT_DISABLED); } else { UI_but_funcN_set(but, template_id_cb, MEM_new(__func__, template_ui), POINTER_FROM_INT(UI_ID_LOCAL), but_func_argN_free, but_func_argN_copy); } } else if (ID_IS_OVERRIDE_LIBRARY(id)) { but = uiDefIconBut( block, UI_BTYPE_BUT, 0, ICON_LIBRARY_DATA_OVERRIDE, 0, 0, UI_UNIT_X, UI_UNIT_Y, nullptr, 0, 0, TIP_("Library override of linked data-block, click to make fully local, " "Shift + Click to clear the library override and toggle if it can be edited")); UI_but_funcN_set(but, template_id_cb, MEM_new(__func__, template_ui), POINTER_FROM_INT(UI_ID_OVERRIDE), but_func_argN_free, but_func_argN_copy); } } if ((ID_REAL_USERS(id) > 1) && (hide_buttons == false)) { char numstr[32]; short numstr_len; numstr_len = SNPRINTF_RLEN(numstr, "%d", ID_REAL_USERS(id)); but = uiDefBut( block, UI_BTYPE_BUT, 0, numstr, 0, 0, numstr_len * 0.2f * UI_UNIT_X + UI_UNIT_X, UI_UNIT_Y, nullptr, 0, 0, TIP_("Display number of users of this data (click to make a single-user copy)")); but->flag |= UI_BUT_UNDO; UI_but_funcN_set(but, template_id_cb, MEM_new(__func__, template_ui), POINTER_FROM_INT(UI_ID_ALONE), but_func_argN_free, but_func_argN_copy); if (!BKE_id_copy_is_allowed(id) || (idfrom && !ID_IS_EDITABLE(idfrom)) || (!editable) || /* object in editmode - don't change data */ (idfrom && GS(idfrom->name) == ID_OB && (((Object *)idfrom)->mode & OB_MODE_EDIT))) { UI_but_flag_enable(but, UI_BUT_DISABLED); } } if (user_alert) { UI_but_flag_enable(but, UI_BUT_REDALERT); } if (!ID_IS_LINKED(id)) { if (ID_IS_ASSET(id)) { uiDefIconButO(block, /* Using `_N` version allows us to get the 'active' state by default. */ UI_BTYPE_ICON_TOGGLE_N, "ASSET_OT_clear_single", WM_OP_INVOKE_DEFAULT, /* 'active' state of a toggle button uses icon + 1, so to get proper asset * icon we need to pass its value - 1 here. */ ICON_ASSET_MANAGER - 1, 0, 0, UI_UNIT_X, UI_UNIT_Y, std::nullopt); } else if (!ELEM(GS(id->name), ID_GR, ID_SCE, ID_SCR, ID_OB, ID_WS) && (hide_buttons == false)) { uiDefIconButR(block, UI_BTYPE_ICON_TOGGLE, 0, ICON_FAKE_USER_OFF, 0, 0, UI_UNIT_X, UI_UNIT_Y, &idptr, "use_fake_user", -1, 0, 0, std::nullopt); } } } if ((flag & UI_ID_ADD_NEW) && (hide_buttons == false)) { template_id_def_new_but( block, id, template_ui, type, newop, editable, flag & UI_ID_OPEN, false, UI_UNIT_X); } /* Due to space limit in UI - skip the "open" icon for packed data, and allow to unpack. * Only for images, sound and fonts */ if (id && BKE_packedfile_id_check(id)) { but = uiDefIconButO(block, UI_BTYPE_BUT, "FILE_OT_unpack_item", WM_OP_INVOKE_REGION_WIN, ICON_PACKAGE, 0, 0, UI_UNIT_X, UI_UNIT_Y, TIP_("Packed File, click to unpack")); UI_but_operator_ptr_ensure(but); RNA_string_set(but->opptr, "id_name", id->name + 2); RNA_int_set(but->opptr, "id_type", GS(id->name)); if (!ID_IS_EDITABLE(id)) { UI_but_flag_enable(but, UI_BUT_DISABLED); } } else if (flag & UI_ID_OPEN) { const char *button_text = (id) ? "" : IFACE_("Open"); const uiFontStyle *fstyle = UI_FSTYLE_WIDGET; int w = id ? UI_UNIT_X : (flag & UI_ID_ADD_NEW) ? UI_UNIT_X * 3 : UI_UNIT_X * 6; if (!id) { w = std::max(UI_fontstyle_string_width(fstyle, button_text) + int(UI_UNIT_X * 1.5f), w); } if (openop) { but = uiDefIconTextButO(block, UI_BTYPE_BUT, openop, WM_OP_INVOKE_DEFAULT, ICON_FILEBROWSER, (id) ? "" : IFACE_("Open"), 0, 0, w, UI_UNIT_Y, std::nullopt); UI_but_funcN_set(but, template_id_cb, MEM_new(__func__, template_ui), POINTER_FROM_INT(UI_ID_OPEN), but_func_argN_free, but_func_argN_copy); } else { but = uiDefIconTextBut(block, UI_BTYPE_BUT, 0, ICON_FILEBROWSER, (id) ? "" : IFACE_("Open"), 0, 0, w, UI_UNIT_Y, nullptr, 0, 0, std::nullopt); UI_but_funcN_set(but, template_id_cb, MEM_new(__func__, template_ui), POINTER_FROM_INT(UI_ID_OPEN), but_func_argN_free, but_func_argN_copy); } if ((idfrom && !ID_IS_EDITABLE(idfrom)) || !editable) { UI_but_flag_enable(but, UI_BUT_DISABLED); } } /* delete button */ /* don't use RNA_property_is_unlink here */ if (id && (flag & UI_ID_DELETE) && (hide_buttons == false)) { /* allow unlink if 'unlinkop' is passed, even when 'PROP_NEVER_UNLINK' is set */ but = nullptr; if (unlinkop) { but = uiDefIconButO(block, UI_BTYPE_BUT, unlinkop, WM_OP_INVOKE_DEFAULT, ICON_X, 0, 0, UI_UNIT_X, UI_UNIT_Y, std::nullopt); /* so we can access the template from operators, font unlinking needs this */ UI_but_funcN_set(but, template_id_cb, MEM_new(__func__, template_ui), POINTER_FROM_INT(UI_ID_NOP), but_func_argN_free, but_func_argN_copy); } else { if ((RNA_property_flag(template_ui.prop) & PROP_NEVER_UNLINK) == 0) { but = uiDefIconBut( block, UI_BTYPE_BUT, 0, ICON_X, 0, 0, UI_UNIT_X, UI_UNIT_Y, nullptr, 0, 0, TIP_("Unlink data-block " "(Shift + Click to set users to zero, data will then not be saved)")); UI_but_funcN_set(but, template_id_cb, MEM_new(__func__, template_ui), POINTER_FROM_INT(UI_ID_DELETE), but_func_argN_free, but_func_argN_copy); if (RNA_property_flag(template_ui.prop) & PROP_NEVER_NULL) { UI_but_flag_enable(but, UI_BUT_DISABLED); } } } if (but) { if ((idfrom && !ID_IS_EDITABLE(idfrom)) || !editable) { UI_but_flag_enable(but, UI_BUT_DISABLED); } } } if (template_ui.idcode == ID_TE) { uiTemplateTextureShow(layout, C, &template_ui.ptr, template_ui.prop); } UI_block_align_end(block); } ID *UI_context_active_but_get_tab_ID(bContext *C) { uiBut *but = UI_context_active_but_get(C); if (but && but->type == UI_BTYPE_TAB) { return static_cast(but->custom_data); } return nullptr; } static void template_ID_tabs(const bContext *C, uiLayout *layout, TemplateID &template_id, StructRNA *type, int flag, const char *newop, const char *menu) { const ARegion *region = CTX_wm_region(C); const PointerRNA active_ptr = RNA_property_pointer_get(&template_id.ptr, template_id.prop); MenuType *mt = menu ? WM_menutype_find(menu, false) : nullptr; const int but_align = ui_but_align_opposite_to_area_align_get(region); const int but_height = UI_UNIT_Y * 1.1; uiBlock *block = uiLayoutGetBlock(layout); const uiStyle *style = UI_style_get_dpi(); for (ID *id : BKE_id_ordered_list(template_id.idlb)) { const int name_width = UI_fontstyle_string_width(&style->widget, id->name + 2); const int but_width = name_width + UI_UNIT_X; uiButTab *tab = (uiButTab *)uiDefButR_prop(block, UI_BTYPE_TAB, 0, id->name + 2, 0, 0, but_width, but_height, &template_id.ptr, template_id.prop, 0, 0.0f, sizeof(id->name) - 2, ""); UI_but_funcN_set(tab, template_ID_set_property_exec_fn, MEM_new(__func__, template_id), id, but_func_argN_free, but_func_argN_copy); UI_but_drag_set_id(tab, id); tab->custom_data = (void *)id; tab->menu = mt; UI_but_drawflag_enable(tab, but_align); } if (flag & UI_ID_ADD_NEW) { const bool editable = RNA_property_editable(&template_id.ptr, template_id.prop); uiBut *but; if (active_ptr.type) { type = active_ptr.type; } but = template_id_def_new_but(block, static_cast(active_ptr.data), template_id, type, newop, editable, flag & UI_ID_OPEN, true, but_height); UI_but_drawflag_enable(but, but_align); } } static void ui_template_id(uiLayout *layout, const bContext *C, PointerRNA *ptr, const StringRefNull propname, const char *newop, const char *openop, const char *unlinkop, /* Only respected by tabs (use_tabs). */ const char *menu, const std::optional text, int flag, int prv_rows, int prv_cols, int filter, bool use_tabs, float scale, const bool live_icon, const bool hide_buttons) { PropertyRNA *prop = RNA_struct_find_property(ptr, propname.c_str()); if (!prop || RNA_property_type(prop) != PROP_POINTER) { RNA_warning( "pointer property not found: %s.%s", RNA_struct_identifier(ptr->type), propname.c_str()); return; } TemplateID template_ui = {}; template_ui.ptr = *ptr; template_ui.prop = prop; template_ui.prv_rows = prv_rows; template_ui.prv_cols = prv_cols; template_ui.scale = scale; if ((flag & UI_ID_PIN) == 0) { template_ui.filter = filter; } else { template_ui.filter = 0; } if (newop) { flag |= UI_ID_ADD_NEW; } if (openop) { flag |= UI_ID_OPEN; } StructRNA *type = RNA_property_pointer_type(ptr, prop); short idcode = RNA_type_to_ID_code(type); template_ui.idcode = idcode; template_ui.idlb = which_libbase(CTX_data_main(C), idcode); /* create UI elements for this template * - template_ID makes a copy of the template data and assigns it to the relevant buttons */ if (template_ui.idlb) { if (use_tabs) { layout = uiLayoutRow(layout, true); template_ID_tabs(C, layout, template_ui, type, flag, newop, menu); } else { layout = uiLayoutRow(layout, true); template_ID(C, layout, template_ui, type, flag, newop, openop, unlinkop, text, live_icon, hide_buttons); } } } void uiTemplateID(uiLayout *layout, const bContext *C, PointerRNA *ptr, const StringRefNull propname, const char *newop, const char *openop, const char *unlinkop, int filter, const bool live_icon, const std::optional text) { ui_template_id(layout, C, ptr, propname, newop, openop, unlinkop, nullptr, text, UI_ID_BROWSE | UI_ID_RENAME | UI_ID_DELETE, 0, 0, filter, false, 1.0f, live_icon, false); } void uiTemplateAction(uiLayout *layout, const bContext *C, ID *id, const char *newop, const char *unlinkop, const std::optional 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` & `setter` that only need the owner ID and are null-safe regarding the `adt` * itself. * FIXME: This is a very dirty hack, would be good to find a way to not rely on typed-but-null * PointerRNA. */ AnimData *adt = BKE_animdata_from_id(id); PointerRNA adt_ptr = PointerRNA{id, &RNA_AnimData, adt, RNA_id_pointer_create(id)}; TemplateID 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, const StringRefNull propname, const char *newop, const char *openop, const char *unlinkop, int filter, const char *text) { ui_template_id(layout, C, ptr, propname, newop, openop, unlinkop, nullptr, text, UI_ID_BROWSE | UI_ID_RENAME, 0, 0, filter, false, 1.0f, false, false); } void uiTemplateIDPreview(uiLayout *layout, bContext *C, PointerRNA *ptr, const StringRefNull propname, const char *newop, const char *openop, const char *unlinkop, int rows, int cols, int filter, const bool hide_buttons) { ui_template_id(layout, C, ptr, propname, newop, openop, unlinkop, nullptr, nullptr, UI_ID_BROWSE | UI_ID_RENAME | UI_ID_DELETE | UI_ID_PREVIEWS, rows, cols, filter, false, 1.0f, false, hide_buttons); } void uiTemplateGpencilColorPreview(uiLayout *layout, bContext *C, PointerRNA *ptr, const StringRefNull propname, int rows, int cols, float scale, int filter) { ui_template_id(layout, C, ptr, propname, nullptr, nullptr, nullptr, nullptr, nullptr, UI_ID_BROWSE | UI_ID_PREVIEWS | UI_ID_DELETE, rows, cols, filter, false, scale < 0.5f ? 0.5f : scale, false, false); } void uiTemplateIDTabs(uiLayout *layout, bContext *C, PointerRNA *ptr, const StringRefNull propname, const char *newop, const char *menu, int filter) { ui_template_id(layout, C, ptr, propname, newop, nullptr, nullptr, menu, nullptr, UI_ID_BROWSE | UI_ID_RENAME, 0, 0, filter, true, 1.0f, false, false); } void uiTemplateAnyID(uiLayout *layout, PointerRNA *ptr, const StringRefNull propname, const StringRefNull proptypename, const std::optional text) { /* get properties... */ PropertyRNA *propID = RNA_struct_find_property(ptr, propname.c_str()); PropertyRNA *propType = RNA_struct_find_property(ptr, proptypename.c_str()); if (!propID || RNA_property_type(propID) != PROP_POINTER) { RNA_warning( "pointer property not found: %s.%s", RNA_struct_identifier(ptr->type), propname.c_str()); return; } if (!propType || RNA_property_type(propType) != PROP_ENUM) { RNA_warning("pointer-type property not found: %s.%s", RNA_struct_identifier(ptr->type), proptypename.c_str()); return; } /* Start drawing UI Elements using standard defines */ /* NOTE: split amount here needs to be synced with normal labels */ uiLayout *split = uiLayoutSplit(layout, 0.33f, false); /* FIRST PART ................................................ */ uiLayout *row = uiLayoutRow(split, false); /* Label - either use the provided text, or will become "ID-Block:" */ if (text) { if (!text->is_empty()) { uiItemL(row, *text, ICON_NONE); } } else { uiItemL(row, IFACE_("ID-Block:"), ICON_NONE); } /* SECOND PART ................................................ */ row = uiLayoutRow(split, true); /* ID-Type Selector - just have a menu of icons */ /* HACK: special group just for the enum, * otherwise we get ugly layout with text included too... */ uiLayout *sub = uiLayoutRow(row, true); uiLayoutSetAlignment(sub, UI_LAYOUT_ALIGN_LEFT); uiItemFullR(sub, ptr, propType, 0, 0, UI_ITEM_R_ICON_ONLY, "", ICON_NONE); /* ID-Block Selector - just use pointer widget... */ /* HACK: special group to counteract the effects of the previous enum, * which now pushes everything too far right. */ sub = uiLayoutRow(row, true); uiLayoutSetAlignment(sub, UI_LAYOUT_ALIGN_EXPAND); uiItemFullR(sub, ptr, propID, 0, 0, UI_ITEM_NONE, "", ICON_NONE); }