diff --git a/scripts/startup/bl_ui/properties_data_mesh.py b/scripts/startup/bl_ui/properties_data_mesh.py index b2198cb8358..e6619599318 100644 --- a/scripts/startup/bl_ui/properties_data_mesh.py +++ b/scripts/startup/bl_ui/properties_data_mesh.py @@ -118,33 +118,6 @@ class MESH_UL_vgroups(UIList): layout.label(text="", icon_value=icon) -class MESH_UL_shape_keys(UIList): - def draw_item(self, _context, layout, _data, item, icon, active_data, _active_propname, index): - # assert(isinstance(item, bpy.types.ShapeKey)) - obj = active_data - # key = data - key_block = item - if self.layout_type in {'DEFAULT', 'COMPACT'}: - split = layout.split(factor=0.5, align=True) - split.prop(key_block, "name", text="", emboss=False, icon_value=icon) - row = split.row(align=True) - row.emboss = 'NONE_OR_STATUS' - row.alignment = 'RIGHT' - if key_block.mute or (obj.mode == 'EDIT' and not (obj.use_shape_key_edit_mode and obj.type == 'MESH')): - split.active = False - if not item.id_data.use_relative: - row.prop(key_block, "frame", text="") - elif index > 0: - row.prop(key_block, "value", text="") - else: - row.label(text="") - row.prop(key_block, "mute", text="", emboss=False) - row.prop(key_block, "lock_shape", text="", emboss=False) - elif self.layout_type == 'GRID': - layout.alignment = 'CENTER' - layout.label(text="", icon_value=icon) - - class MESH_UL_uvmaps(UIList): def draw_item(self, _context, layout, _data, item, icon, _active_data, _active_propname, _index): # assert(isinstance(item, (bpy.types.MeshTexturePolyLayer, bpy.types.MeshLoopColorLayer))) @@ -314,11 +287,7 @@ class DATA_PT_shape_keys(MeshButtonsPanel, Panel): row = layout.row() - rows = 3 - if kb: - rows = 5 - - row.template_list("MESH_UL_shape_keys", "", key, "key_blocks", ob, "active_shape_key_index", rows=rows) + row.template_shape_key_tree() col = row.column(align=True) @@ -731,7 +700,6 @@ classes = ( MESH_MT_color_attribute_context_menu, MESH_MT_attribute_context_menu, MESH_UL_vgroups, - MESH_UL_shape_keys, MESH_UL_uvmaps, MESH_UL_attributes, DATA_PT_context_mesh, diff --git a/source/blender/editors/include/UI_interface_c.hh b/source/blender/editors/include/UI_interface_c.hh index 6c45c9fec71..3c41dcc813c 100644 --- a/source/blender/editors/include/UI_interface_c.hh +++ b/source/blender/editors/include/UI_interface_c.hh @@ -2659,6 +2659,9 @@ void uiTemplateNodeInputs(uiLayout *layout, bContext *C, PointerRNA *ptr); void uiTemplateCollectionExporters(uiLayout *layout, bContext *C); +namespace blender::ed::object::shapekey { +void template_tree(uiLayout *layout, bContext *C); +} /** * \return: True if the list item with unfiltered, unordered index \a item_idx is visible given the * current filter settings. diff --git a/source/blender/editors/object/CMakeLists.txt b/source/blender/editors/object/CMakeLists.txt index 920cf25dbf2..55b2da49d19 100644 --- a/source/blender/editors/object/CMakeLists.txt +++ b/source/blender/editors/object/CMakeLists.txt @@ -20,6 +20,7 @@ set(INC_SYS set(SRC add_modifier_assets.cc + interface_template_shape_key_tree.cc object_add.cc object_bake.cc object_bake_api.cc diff --git a/source/blender/editors/object/interface_template_shape_key_tree.cc b/source/blender/editors/object/interface_template_shape_key_tree.cc new file mode 100644 index 00000000000..e423ac33882 --- /dev/null +++ b/source/blender/editors/object/interface_template_shape_key_tree.cc @@ -0,0 +1,255 @@ +/* SPDX-FileCopyrightText: 2025 Blender Foundation + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup edinterface + */ + +#include "BKE_context.hh" +#include "BKE_key.hh" + +#include "BLI_listbase.h" +#include "BLT_translation.hh" + +#include "UI_interface.hh" +#include "UI_tree_view.hh" + +#include "RNA_access.hh" +#include "RNA_prototypes.hh" + +#include "DEG_depsgraph.hh" +#include "DNA_key_types.h" +#include "WM_api.hh" + +#include "ED_undo.hh" +#include "WM_types.hh" +#include + +namespace blender::ed::object::shapekey { + +class ShapeKeyTreeView : public ui::AbstractTreeView { + protected: + Object &object_; + + public: + ShapeKeyTreeView(Object &ob) : object_(ob){}; + + void build_tree() override; +}; + +struct ShapeKey { + Object *object; + Key *key; + KeyBlock *kb; + int index; +}; + +class ShapeKeyDragController : public ui::AbstractViewItemDragController { + private: + ShapeKey drag_key_; + + public: + ShapeKeyDragController() = default; + ShapeKeyDragController(ShapeKeyTreeView &view, ShapeKey drag_key) + : AbstractViewItemDragController(view), drag_key_(drag_key) + { + } + + eWM_DragDataType get_drag_type() const override + { + return WM_DRAG_SHAPE_KEY; + } + + void *create_drag_data() const override + { + ShapeKey *drag_data = MEM_callocN(__func__); + *drag_data = drag_key_; + return drag_data; + } + void on_drag_start() override + { + drag_key_.object->shapenr = drag_key_.index + 1; + } +}; + +class ShapeKeyDropTarget : public ui::TreeViewItemDropTarget { + private: + KeyBlock &drop_kb_; + int drop_index_; + + public: + ShapeKeyDropTarget(ui::AbstractTreeViewItem &item, + ui::DropBehavior behavior, + KeyBlock &drop_kb, + int index) + : TreeViewItemDropTarget(item, behavior), drop_kb_(drop_kb), drop_index_(index) + { + } + + bool can_drop(const wmDrag &drag, const char ** /*r_disabled_hint*/) const override + { + if (drag.type != WM_DRAG_SHAPE_KEY) { + return false; + } + const ShapeKey *drag_shapekey = static_cast(drag.poin); + if (drag_shapekey->index == drop_index_) { + return false; + } + return true; + } + + std::string drop_tooltip(const ui::DragInfo &drag_info) const override + { + const ShapeKey *drag_shapekey = static_cast(drag_info.drag_data.poin); + const StringRef drag_name = drag_shapekey->kb->name; + const StringRef drop_name = drop_kb_.name; + + switch (drag_info.drop_location) { + case ui::DropLocation::Into: + BLI_assert_unreachable(); + break; + case ui::DropLocation::Before: + return fmt::format(fmt::runtime(TIP_("Move {} above {}")), drag_name, drop_name); + case ui::DropLocation::After: + return fmt::format(fmt::runtime(TIP_("Move {} below {}")), drag_name, drop_name); + default: + BLI_assert_unreachable(); + break; + } + + return ""; + } + + bool on_drop(bContext *C, const ui::DragInfo &drag_info) const override + { + const ShapeKey *drag_shapekey = static_cast(drag_info.drag_data.poin); + int drop_index = drop_index_; + const int drag_index = drag_shapekey->index; + + switch (drag_info.drop_location) { + case ui::DropLocation::Into: + BLI_assert_unreachable(); + break; + case ui::DropLocation::Before: + drop_index -= int(drag_index < drop_index); + break; + case ui::DropLocation::After: + drop_index += int(drag_index > drop_index); + break; + } + Object *object = drag_shapekey->object; + BKE_keyblock_move(object, drag_shapekey->index, drop_index); + + DEG_id_tag_update(static_cast(object->data), ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, object); + ED_undo_push(C, "Drop Active Shape Key"); + + return true; + } +}; + +class ShapeKeyItem : public ui::AbstractTreeViewItem { + private: + ShapeKey shape_key_; + + public: + ShapeKeyItem(Object *object, Key *key, KeyBlock *kb, int index) + { + label_ = kb->name; + shape_key_.object = object; + shape_key_.key = key; + shape_key_.kb = kb; + shape_key_.index = index; + }; + + void build_row(uiLayout &row) override + { + uiItemL_ex(&row, this->label_, ICON_SHAPEKEY_DATA, false, false); + uiLayout *sub = &row.row(true); + uiLayoutSetPropDecorate(sub, false); + PointerRNA shapekey_ptr = RNA_pointer_create_discrete( + &shape_key_.key->id, &RNA_ShapeKey, shape_key_.kb); + sub->prop(&shapekey_ptr, "mute", UI_ITEM_R_ICON_ONLY, std::nullopt, ICON_NONE); + sub->prop(&shapekey_ptr, "lock_shape", UI_ITEM_R_ICON_ONLY, std::nullopt, ICON_NONE); + } + + std::optional should_be_active() const override + { + return shape_key_.object->shapenr == shape_key_.index + 1; + } + + void on_activate(bContext &C) override + { + PointerRNA object_ptr = RNA_pointer_create_discrete( + &shape_key_.object->id, &RNA_Object, shape_key_.object); + PropertyRNA *prop = RNA_struct_find_property(&object_ptr, "active_shape_key_index"); + RNA_property_int_set(&object_ptr, prop, shape_key_.index); + RNA_property_update(&C, &object_ptr, prop); + + ED_undo_push(&C, "Set Active Shape Key"); + } + + bool supports_renaming() const override + { + return true; + } + + bool rename(const bContext &C, StringRefNull new_name) override + { + PointerRNA shapekey_ptr = RNA_pointer_create_discrete( + &shape_key_.key->id, &RNA_ShapeKey, shape_key_.kb); + RNA_string_set(&shapekey_ptr, "name", new_name.c_str()); + ED_undo_push(const_cast(&C), "Rename shape key"); + return true; + } + + StringRef get_rename_string() const override + { + return label_; + } + + std::unique_ptr create_drag_controller() const override + { + return std::make_unique( + static_cast(get_tree_view()), shape_key_); + } + + std::unique_ptr create_drop_target() override + { + return std::make_unique( + *this, ui::DropBehavior::Reorder, *shape_key_.kb, shape_key_.index); + } +}; + +void ShapeKeyTreeView::build_tree() +{ + Key *key = BKE_key_from_object(&object_); + if (key == nullptr) { + return; + } + int index = 1; + LISTBASE_FOREACH_INDEX (KeyBlock *, kb, &key->block, index) { + this->add_tree_item(&object_, key, kb, index); + } +} + +void template_tree(uiLayout *layout, bContext *C) +{ + Object *ob = CTX_data_active_object(C); + if (ob == nullptr) { + return; + } + + uiBlock *block = uiLayoutGetBlock(layout); + + ui::AbstractTreeView *tree_view = UI_block_add_view( + *block, + "Shape Key Tree View", + std::make_unique(*ob)); + tree_view->set_context_menu_title("Shape Key"); + tree_view->set_default_rows(4); + + ui::TreeViewBuilder::build_tree_view(*C, *tree_view, *layout); +} +} // namespace blender::ed::object::shapekey diff --git a/source/blender/makesrna/intern/rna_ui_api.cc b/source/blender/makesrna/intern/rna_ui_api.cc index 9f6c5f01b37..0329d6eec07 100644 --- a/source/blender/makesrna/intern/rna_ui_api.cc +++ b/source/blender/makesrna/intern/rna_ui_api.cc @@ -2457,6 +2457,11 @@ void RNA_api_ui_layout(StructRNA *srna) func, "properties", "OperatorProperties", "", "Operator properties to fill in"); RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED | PARM_RNAPTR); RNA_def_function_return(func, parm); + + func = RNA_def_function( + srna, "template_shape_key_tree", "blender::ed::object::shapekey::template_tree"); + RNA_def_function_ui_description(func, "Shape Key tree view"); + RNA_def_function_flag(func, FUNC_USE_CONTEXT); } #endif diff --git a/source/blender/windowmanager/WM_types.hh b/source/blender/windowmanager/WM_types.hh index d11c83af714..4e3f21501ab 100644 --- a/source/blender/windowmanager/WM_types.hh +++ b/source/blender/windowmanager/WM_types.hh @@ -1219,6 +1219,7 @@ enum eWM_DragDataType { WM_DRAG_GREASE_PENCIL_GROUP, WM_DRAG_NODE_TREE_INTERFACE, WM_DRAG_BONE_COLLECTION, + WM_DRAG_SHAPE_KEY, }; enum eWM_DragFlags {