Blender V5.0
interface_template_grease_pencil_layer_tree.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#include "BLI_listbase.h"
10
11#include "BKE_context.hh"
12#include "BKE_grease_pencil.hh"
13
14#include "BLT_translation.hh"
15
16#include "DEG_depsgraph.hh"
17
18#include "UI_interface.hh"
20#include "UI_tree_view.hh"
21
22#include "RNA_access.hh"
23#include "RNA_prototypes.hh"
24
25#include "ED_grease_pencil.hh"
26#include "ED_undo.hh"
27
28#include "WM_api.hh"
29#include "WM_message.hh"
30
31#include <fmt/format.h>
32
34
35using namespace blender::bke::greasepencil;
36
38 public:
39 explicit LayerTreeView(GreasePencil &grease_pencil) : grease_pencil_(grease_pencil) {}
40
41 void build_tree() override;
42
43 private:
44 void build_tree_node_recursive(TreeViewOrItem &parent, TreeNode &node);
45 GreasePencil &grease_pencil_;
46};
47
49 TreeNode &drop_tree_node_;
50
51 public:
53 : TreeViewItemDropTarget(item, behavior), drop_tree_node_(drop_tree_node)
54 {
55 }
56
57 bool can_drop(const wmDrag &drag, const char ** /*r_disabled_hint*/) const override
58 {
60 return false;
61 }
62
63 wmDragGreasePencilLayer *active_drag_node = static_cast<wmDragGreasePencilLayer *>(drag.poin);
64 if (active_drag_node->node->wrap().is_layer()) {
65 return true;
66 }
67
68 LayerGroup &group = active_drag_node->node->wrap().as_group();
69 if (drop_tree_node_.is_child_of(group)) {
70 /* Don't drop group node into its child node. */
71 return false;
72 }
73 return true;
74 }
75
76 std::string drop_tooltip(const DragInfo &drag_info) const override
77 {
78 const wmDragGreasePencilLayer *drag_grease_pencil =
79 static_cast<const wmDragGreasePencilLayer *>(drag_info.drag_data.poin);
80 TreeNode &drag_node = drag_grease_pencil->node->wrap();
81
82 const StringRef drag_name = drag_node.name();
83 const StringRef drop_name = drop_tree_node_.name();
84 const StringRef node_type = drag_node.is_layer() ? "layer" : "group";
85
86 switch (drag_info.drop_location) {
88 return fmt::format(
89 fmt::runtime(TIP_("Move {} {} into {}")), node_type, drag_name, drop_name);
91 return fmt::format(
92 fmt::runtime(TIP_("Move {} {} above {}")), node_type, drag_name, drop_name);
94 return fmt::format(
95 fmt::runtime(TIP_("Move {} {} below {}")), node_type, drag_name, drop_name);
96 default:
98 break;
99 }
100
101 return "";
102 }
103
104 bool on_drop(bContext *C, const DragInfo &drag_info) const override
105 {
106 const wmDragGreasePencilLayer *drag_grease_pencil =
107 static_cast<const wmDragGreasePencilLayer *>(drag_info.drag_data.poin);
108 GreasePencil &grease_pencil = *drag_grease_pencil->grease_pencil;
109 TreeNode &drag_node = drag_grease_pencil->node->wrap();
110
111 if (!drop_tree_node_.parent_group()) {
112 /* Root node is not added to the tree view, so there should never be a drop target for this.
113 */
115 return false;
116 }
117
118 if (&drop_tree_node_ == &drag_node) {
119 return false;
120 }
121
122 switch (drag_info.drop_location) {
123 case DropLocation::Into: {
124 BLI_assert_msg(drop_tree_node_.is_group(),
125 "Inserting should not be possible for layers, only for groups, because "
126 "only groups use DropBehavior::Reorder_and_Insert");
127 LayerGroup &drop_group = drop_tree_node_.as_group();
128 grease_pencil.move_node_into(drag_node, drop_group);
129 break;
130 }
132 /* Draw order is inverted, so inserting before (above) means inserting the node after. */
133 grease_pencil.move_node_after(drag_node, drop_tree_node_);
134 break;
135 }
136 case DropLocation::After: {
137 /* Draw order is inverted, so inserting after (below) means inserting the node before. */
138 grease_pencil.move_node_before(drag_node, drop_tree_node_);
139 break;
140 }
141 default: {
143 return false;
144 }
145 }
146
147 if (drag_node.is_layer()) {
149 CTX_wm_message_bus(C), &grease_pencil.id, &grease_pencil, GreasePencilv3Layers, active);
151 CTX_wm_message_bus(C), &grease_pencil.id, &grease_pencil, GreasePencil, layers);
152 }
153 else if (drag_node.is_group()) {
155 &grease_pencil.id,
156 &grease_pencil,
157 GreasePencilv3LayerGroup,
158 active);
160 CTX_wm_message_bus(C), &grease_pencil.id, &grease_pencil, GreasePencil, layer_groups);
161 }
162
163 ED_undo_push(C, "Reorder Layers");
164
165 DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
167 return true;
168 }
169};
170
172 GreasePencil &grease_pencil_;
173 TreeNode &dragged_node_;
174
175 public:
177 GreasePencil &grease_pencil,
180 grease_pencil_(grease_pencil),
181 dragged_node_(node.wrap())
182 {
183 }
184
185 std::optional<eWM_DragDataType> get_drag_type() const override
186 {
187 if (dragged_node_.wrap().is_layer()) {
189 }
191 }
192
193 void *create_drag_data() const override
194 {
196 drag_data->node = &dragged_node_;
197 drag_data->grease_pencil = &grease_pencil_;
198 return drag_data;
199 }
200
201 void on_drag_start(bContext & /*C*/) override
202 {
203 grease_pencil_.set_active_node(&dragged_node_);
204 }
205};
206
208 public:
209 LayerViewItem(GreasePencil &grease_pencil, Layer &layer)
210 : grease_pencil_(grease_pencil), layer_(layer)
211 {
212 this->label_ = layer.name();
213 }
214
215 void build_row(uiLayout &row) override
216 {
217 build_layer_name(row);
218
219 uiLayout *sub = &row.row(true);
220 sub->use_property_decorate_set(false);
221
222 build_layer_buttons(*sub);
223 }
224
225 bool supports_collapsing() const override
226 {
227 /* This is a bit redundant since `LayerViewItem` can't have children.
228 * But being explicit might catch errors. */
229 return false;
230 }
231
232 std::optional<bool> should_be_active() const override
233 {
234 if (this->grease_pencil_.has_active_layer()) {
235 return reinterpret_cast<GreasePencilLayer *>(&layer_) ==
236 reinterpret_cast<GreasePencilLayer *>(this->grease_pencil_.get_active_layer());
237 }
238 return {};
239 }
240
241 void on_activate(bContext &C) override
242 {
244 &grease_pencil_.id, &RNA_GreasePencilv3Layers, nullptr);
246 &grease_pencil_.id, &RNA_GreasePencilLayer, &layer_);
247
248 PropertyRNA *prop = RNA_struct_find_property(&layers_ptr, "active");
249
250 if (grease_pencil_.has_active_group()) {
252 &grease_pencil_.id,
253 &grease_pencil_,
254 GreasePencilv3LayerGroup,
255 active);
256 }
257
258 RNA_property_pointer_set(&layers_ptr, prop, value_ptr, nullptr);
259 RNA_property_update(&C, &layers_ptr, prop);
260
261 ED_undo_push(&C, "Active Grease Pencil Layer");
262 }
263
264 bool supports_renaming() const override
265 {
266 return true;
267 }
268
269 bool rename(const bContext &C, StringRefNull new_name) override
270 {
272 &grease_pencil_.id, &RNA_GreasePencilLayer, &layer_);
273 PropertyRNA *prop = RNA_struct_find_property(&layer_ptr, "name");
274
275 RNA_property_string_set(&layer_ptr, prop, new_name.c_str());
276 RNA_property_update(&const_cast<bContext &>(C), &layer_ptr, prop);
277
278 ED_undo_push(&const_cast<bContext &>(C), "Rename Grease Pencil Layer");
279 return true;
280 }
281
283 {
284 return layer_.name();
285 }
286
287 void delete_item(bContext *C) override
288 {
289 grease_pencil_.remove_layer(layer_);
290 DEG_id_tag_update(&grease_pencil_.id, ID_RECALC_GEOMETRY);
292 ED_undo_push(C, "Delete Grease Pencil Layer");
293 }
294
295 std::unique_ptr<AbstractViewItemDragController> create_drag_controller() const override
296 {
297 return std::make_unique<LayerViewItemDragController>(
298 static_cast<LayerTreeView &>(get_tree_view()), grease_pencil_, layer_.base);
299 }
300
301 std::unique_ptr<TreeViewItemDropTarget> create_drop_target() override
302 {
303 return std::make_unique<LayerNodeDropTarget>(*this, layer_.as_node(), DropBehavior::Reorder);
304 }
305
306 private:
307 GreasePencil &grease_pencil_;
308 Layer &layer_;
309
310 void build_layer_name(uiLayout &row)
311 {
312 uiBut *but = uiItemL_ex(
313 &row, layer_.name().c_str(), ICON_OUTLINER_DATA_GP_LAYER, false, false);
314
315 if (ID_IS_LINKED(&grease_pencil_)) {
317 }
318 else if (!layer_.is_editable()) {
319 UI_but_disable(but, "Layer is locked or not visible");
320 }
321 }
322
323 void build_layer_buttons(uiLayout &row)
324 {
325 uiLayout *sub;
327 &grease_pencil_.id, &RNA_GreasePencilLayer, &layer_);
328
329 sub = &row.row(true);
330 sub->active_set(layer_.parent_group().use_masks());
331 sub->prop(&layer_ptr, "use_masks", UI_ITEM_R_ICON_ONLY, std::nullopt, ICON_NONE);
332
333 sub = &row.row(true);
334 sub->active_set(layer_.parent_group().use_onion_skinning());
335 sub->prop(&layer_ptr, "use_onion_skinning", UI_ITEM_R_ICON_ONLY, std::nullopt, ICON_NONE);
336
337 sub = &row.row(true);
338 sub->active_set(layer_.parent_group().is_visible());
339 sub->prop(&layer_ptr, "hide", UI_ITEM_R_ICON_ONLY, std::nullopt, ICON_NONE);
340
341 sub = &row.row(true);
342 sub->active_set(!layer_.parent_group().is_locked());
343 sub->prop(&layer_ptr, "lock", UI_ITEM_R_ICON_ONLY, std::nullopt, ICON_NONE);
344 }
345};
346
348 public:
350 : grease_pencil_(grease_pencil), group_(group)
351 {
352 this->label_ = group_.name();
353 }
354
355 std::optional<bool> should_be_collapsed() const override
356 {
357 const bool is_collapsed = !group_.is_expanded();
358 return is_collapsed;
359 }
360
361 bool set_collapsed(const bool collapsed) override
362 {
363 if (!AbstractTreeViewItem::set_collapsed(collapsed)) {
364 return false;
365 }
366 group_.set_expanded(!collapsed);
367 return true;
368 }
369
370 void on_collapse_change(bContext &C, const bool is_collapsed) override
371 {
372 const bool is_expanded = !is_collapsed;
373
374 /* Let RNA handle the property change. This makes sure all the notifiers and DEG
375 * update calls are properly called. */
377 &grease_pencil_.id, &RNA_GreasePencilLayerGroup, &group_);
378 PropertyRNA *prop = RNA_struct_find_property(&group_ptr, "is_expanded");
379
380 RNA_property_boolean_set(&group_ptr, prop, is_expanded);
381 RNA_property_update(&C, &group_ptr, prop);
382 }
383
384 void build_row(uiLayout &row) override
385 {
386 build_layer_group_name(row);
387
388 uiLayout *sub = &row.row(true);
389 sub->use_property_decorate_set(false);
390
391 build_layer_group_buttons(*sub);
392 }
393
394 std::optional<bool> should_be_active() const override
395 {
396 if (this->grease_pencil_.has_active_group()) {
397 return &group_ == this->grease_pencil_.get_active_group();
398 }
399 return {};
400 }
401
402 void build_context_menu(bContext &C, uiLayout &layout) const override
403 {
404 MenuType *mt = WM_menutype_find("GREASE_PENCIL_MT_group_context_menu", true);
405 if (!mt) {
406 return;
407 }
408 UI_menutype_draw(&C, mt, &layout);
409 }
410
411 void on_activate(bContext &C) override
412 {
413 PointerRNA grease_pencil_ptr = RNA_pointer_create_discrete(
414 &grease_pencil_.id, &RNA_GreasePencilv3LayerGroup, nullptr);
416 &grease_pencil_.id, &RNA_GreasePencilLayerGroup, &group_);
417
418 PropertyRNA *prop = RNA_struct_find_property(&grease_pencil_ptr, "active");
419
420 if (grease_pencil_.has_active_layer()) {
422 &grease_pencil_.id,
423 &grease_pencil_,
424 GreasePencilv3Layers,
425 active);
426 }
427
428 RNA_property_pointer_set(&grease_pencil_ptr, prop, value_ptr, nullptr);
429 RNA_property_update(&C, &grease_pencil_ptr, prop);
430
431 ED_undo_push(&C, "Active Grease Pencil Group");
432 }
433
434 bool supports_renaming() const override
435 {
436 return true;
437 }
438
439 bool rename(const bContext &C, StringRefNull new_name) override
440 {
442 &grease_pencil_.id, &RNA_GreasePencilLayerGroup, &group_);
443 PropertyRNA *prop = RNA_struct_find_property(&group_ptr, "name");
444
445 RNA_property_string_set(&group_ptr, prop, new_name.c_str());
446 RNA_property_update(&const_cast<bContext &>(C), &group_ptr, prop);
447
448 ED_undo_push(&const_cast<bContext &>(C), "Rename Grease Pencil Layer Group");
449 return true;
450 }
451
453 {
454 return group_.name();
455 }
456
457 void delete_item(bContext *C) override
458 {
459 grease_pencil_.remove_group(group_);
460 DEG_id_tag_update(&grease_pencil_.id, ID_RECALC_GEOMETRY);
462 ED_undo_push(C, "Delete Grease Pencil Group");
463 }
464
465 std::unique_ptr<AbstractViewItemDragController> create_drag_controller() const override
466 {
467 return std::make_unique<LayerViewItemDragController>(
468 static_cast<LayerTreeView &>(get_tree_view()), grease_pencil_, group_.base);
469 }
470
471 std::unique_ptr<TreeViewItemDropTarget> create_drop_target() override
472 {
473 return std::make_unique<LayerNodeDropTarget>(
474 *this, group_.as_node(), DropBehavior::ReorderAndInsert);
475 }
476
477 private:
478 GreasePencil &grease_pencil_;
479 LayerGroup &group_;
480
481 void build_layer_group_name(uiLayout &row)
482 {
483 short icon = ICON_GREASEPENCIL_LAYER_GROUP;
484 if (group_.color_tag != LAYERGROUP_COLOR_NONE) {
485 icon = ICON_LAYERGROUP_COLOR_01 + group_.color_tag;
486 }
487
488 uiBut *but = uiItemL_ex(&row, group_.name(), icon, false, false);
489 if (ID_IS_LINKED(&grease_pencil_)) {
491 }
492 else if (!group_.is_editable()) {
493 UI_but_disable(but, "Layer Group is locked or not visible");
494 }
495 }
496
497 void build_layer_group_buttons(uiLayout &row)
498 {
499 uiLayout *sub;
500 PointerRNA group_ptr = RNA_pointer_create_discrete(
501 &grease_pencil_.id, &RNA_GreasePencilLayerGroup, &group_);
502
503 sub = &row.row(true);
504 if (group_.as_node().parent_group()) {
505 sub->active_set(group_.as_node().parent_group()->use_masks());
506 }
507 sub->prop(&group_ptr, "use_masks", UI_ITEM_R_ICON_ONLY, std::nullopt, ICON_NONE);
508
509 sub = &row.row(true);
510 if (group_.as_node().parent_group()) {
511 sub->active_set(group_.as_node().parent_group()->use_onion_skinning());
512 }
513 sub->prop(&group_ptr, "use_onion_skinning", UI_ITEM_R_ICON_ONLY, std::nullopt, ICON_NONE);
514
515 sub = &row.row(true);
516 if (group_.as_node().parent_group()) {
517 sub->active_set(group_.as_node().parent_group()->is_visible());
518 }
519 sub->prop(&group_ptr, "hide", UI_ITEM_R_ICON_ONLY, std::nullopt, ICON_NONE);
520
521 sub = &row.row(true);
522 if (group_.as_node().parent_group()) {
523 sub->active_set(!group_.as_node().parent_group()->is_locked());
524 }
525 sub->prop(&group_ptr, "lock", UI_ITEM_R_ICON_ONLY, std::nullopt, ICON_NONE);
526 }
527};
528
529void LayerTreeView::build_tree_node_recursive(TreeViewOrItem &parent, TreeNode &node)
530{
531 using namespace blender::bke::greasepencil;
532 if (node.is_layer()) {
533 parent.add_tree_item<LayerViewItem>(this->grease_pencil_, node.as_layer());
534 }
535 else if (node.is_group()) {
536 LayerGroupViewItem &group_item = parent.add_tree_item<LayerGroupViewItem>(this->grease_pencil_,
537 node.as_group());
539 build_tree_node_recursive(group_item, node_->wrap());
540 }
541 }
542}
543
545{
546 using namespace blender::bke::greasepencil;
548 GreasePencilLayerTreeNode *, node, &this->grease_pencil_.root_group_ptr->children)
549 {
550 this->build_tree_node_recursive(*this, node->wrap());
551 }
552}
553
554} // namespace blender::ui::greasepencil
555
557{
558 using namespace blender;
559
561
562 if (grease_pencil == nullptr) {
563 return;
564 }
565
566 uiBlock *block = layout->block();
567
569 *block,
570 "Grease Pencil Layer Tree View",
571 std::make_unique<blender::ui::greasepencil::LayerTreeView>(*grease_pencil));
572 tree_view->set_context_menu_title("Grease Pencil Layer");
573 tree_view->set_default_rows(6);
574
575 ui::TreeViewBuilder::build_tree_view(*C, *tree_view, *layout);
576}
wmMsgBus * CTX_wm_message_bus(const bContext *C)
Low-level operations for grease pencil.
#define BLI_assert_unreachable()
Definition BLI_assert.h:93
#define BLI_assert_msg(a, msg)
Definition BLI_assert.h:53
#define LISTBASE_FOREACH_BACKWARD(type, var, list)
#define ELEM(...)
#define TIP_(msgid)
void DEG_id_tag_update(ID *id, unsigned int flags)
@ ID_RECALC_GEOMETRY
Definition DNA_ID.h:1074
#define ID_IS_LINKED(_id)
Definition DNA_ID.h:694
@ LAYERGROUP_COLOR_NONE
struct GreasePencilLayerTreeNode GreasePencilLayerTreeNode
void ED_undo_push(bContext *C, const char *str)
Definition ed_undo.cc:98
#define C
Definition RandGen.cpp:29
blender::ui::AbstractGridView * UI_block_add_view(uiBlock &block, blender::StringRef idname, std::unique_ptr< blender::ui::AbstractGridView > grid_view)
void UI_but_disable(uiBut *but, const char *disabled_hint)
@ UI_BUT_DISABLED
void uiTemplateGreasePencilLayerTree(uiLayout *layout, bContext *C)
void UI_but_flag_enable(uiBut *but, int flag)
@ UI_ITEM_R_ICON_ONLY
void UI_menutype_draw(bContext *C, MenuType *mt, uiLayout *layout)
uiBut * uiItemL_ex(uiLayout *layout, blender::StringRef name, int icon, bool highlight, bool redalert)
#define ND_DRAW
Definition WM_types.hh:461
@ WM_DRAG_GREASE_PENCIL_LAYER
Definition WM_types.hh:1221
@ WM_DRAG_GREASE_PENCIL_GROUP
Definition WM_types.hh:1222
#define NA_EDITED
Definition WM_types.hh:584
#define NC_GPENCIL
Definition WM_types.hh:399
#define NC_OBJECT
Definition WM_types.hh:379
bool is_group() const
bool is_layer() const
const LayerGroup & as_group() const
const Layer & as_layer() const
constexpr const char * c_str() const
const LayerGroup & parent_group() const
Abstract base class for defining a customizable tree-view item.
AbstractTreeView & get_tree_view() const
Definition tree_view.cc:644
virtual bool set_collapsed(bool collapsed)
Definition tree_view.cc:715
void set_default_rows(int default_rows)
Definition tree_view.cc:141
void set_context_menu_title(const std::string &title)
static void build_tree_view(const bContext &C, AbstractTreeView &tree_view, uiLayout &layout, bool add_box=true)
TreeViewItemDropTarget(AbstractTreeViewItem &view_item, DropBehavior behavior=DropBehavior::Insert)
Definition tree_view.cc:437
std::unique_ptr< AbstractViewItemDragController > create_drag_controller() const override
void on_collapse_change(bContext &C, const bool is_collapsed) override
void build_context_menu(bContext &C, uiLayout &layout) const override
bool rename(const bContext &C, StringRefNull new_name) override
LayerGroupViewItem(GreasePencil &grease_pencil, LayerGroup &group)
std::unique_ptr< TreeViewItemDropTarget > create_drop_target() override
std::string drop_tooltip(const DragInfo &drag_info) const override
bool can_drop(const wmDrag &drag, const char **) const override
LayerNodeDropTarget(AbstractTreeViewItem &item, TreeNode &drop_tree_node, DropBehavior behavior)
bool on_drop(bContext *C, const DragInfo &drag_info) const override
LayerViewItemDragController(LayerTreeView &tree_view, GreasePencil &grease_pencil, GreasePencilLayerTreeNode &node)
bool rename(const bContext &C, StringRefNull new_name) override
std::unique_ptr< AbstractViewItemDragController > create_drag_controller() const override
std::unique_ptr< TreeViewItemDropTarget > create_drop_target() override
#define active
void * MEM_callocN(size_t len, const char *str)
Definition mallocn.cc:118
GreasePencil * from_context(bContext &C)
TreeViewItemContainer TreeViewOrItem
float wrap(float value, float max, float min)
Definition node_math.h:103
PropertyRNA * RNA_struct_find_property(PointerRNA *ptr, const char *identifier)
void RNA_property_pointer_set(PointerRNA *ptr, PropertyRNA *prop, PointerRNA ptr_value, ReportList *reports)
void RNA_property_update(bContext *C, PointerRNA *ptr, PropertyRNA *prop)
void RNA_property_boolean_set(PointerRNA *ptr, PropertyRNA *prop, bool value)
PointerRNA RNA_pointer_create_discrete(ID *id, StructRNA *type, void *data)
void RNA_property_string_set(PointerRNA *ptr, PropertyRNA *prop, const char *value)
GreasePencilLayerTreeGroup * root_group_ptr
const wmDrag & drag_data
const DropLocation drop_location
void use_property_decorate_set(bool is_sep)
uiBlock * block() const
void active_set(bool active)
uiLayout & row(bool align)
void prop(PointerRNA *ptr, PropertyRNA *prop, int index, int value, eUI_Item_Flag flag, std::optional< blender::StringRef > name_opt, int icon, std::optional< blender::StringRef > placeholder=std::nullopt)
GreasePencil * grease_pencil
Definition WM_types.hh:1280
GreasePencilLayerTreeNode * node
Definition WM_types.hh:1281
eWM_DragDataType type
Definition WM_types.hh:1331
void * poin
Definition WM_types.hh:1332
void WM_event_add_notifier(const bContext *C, uint type, void *reference)
MenuType * WM_menutype_find(const StringRef idname, bool quiet)
#define WM_msg_publish_rna_prop(mbus, id_, data_, type_, prop_)