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:
committed by
Pratik Borhade
parent
11ca8729cc
commit
0eed084cad
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user