UI: Use Tree view for shape keys

Shifting to Tree view will now allow drag-drop for shape-keys.
(And also possible to create nested shape keys if we want to include
support for them in future)
With !138979, multi-select support will be implemented for shape keys.

Part of #136838

See video in PR description

Pull Request: https://projects.blender.org/blender/blender/pulls/139323
This commit is contained in:
Pratik Borhade
2025-06-06 12:03:47 +02:00
committed by Pratik Borhade
parent 11ca8729cc
commit 0eed084cad
6 changed files with 266 additions and 33 deletions

View File

@@ -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,

View File

@@ -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.

View File

@@ -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

View File

@@ -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 <fmt/format.h>
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<ShapeKey>(__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<const ShapeKey *>(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<const ShapeKey *>(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<const ShapeKey *>(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<ID *>(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<bool> 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<bContext *>(&C), "Rename shape key");
return true;
}
StringRef get_rename_string() const override
{
return label_;
}
std::unique_ptr<ui::AbstractViewItemDragController> create_drag_controller() const override
{
return std::make_unique<ShapeKeyDragController>(
static_cast<ShapeKeyTreeView &>(get_tree_view()), shape_key_);
}
std::unique_ptr<ui::TreeViewItemDropTarget> create_drop_target() override
{
return std::make_unique<ShapeKeyDropTarget>(
*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<ShapeKeyItem>(&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<ed::object::shapekey::ShapeKeyTreeView>(*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

View File

@@ -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

View File

@@ -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 {