WIP: Node Editor: Improve working with frame nodes #108358

Draft
Leon Schittek wants to merge 7 commits from lone_noel/blender:frame-joining-base-functionality into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
13 changed files with 280 additions and 179 deletions

View File

@ -6138,8 +6138,10 @@ def km_transform_modal_map(params):
("AUTOIK_CHAIN_LEN_UP", {"type": 'WHEELDOWNMOUSE', "value": 'PRESS', "shift": True}, None),
("AUTOIK_CHAIN_LEN_DOWN", {"type": 'WHEELUPMOUSE', "value": 'PRESS', "shift": True}, None),
("INSERTOFS_TOGGLE_DIR", {"type": 'T', "value": 'PRESS'}, None),
("NODE_ATTACH_ON", {"type": 'LEFT_ALT', "value": 'RELEASE', "any": True}, None),
("NODE_ATTACH_OFF", {"type": 'LEFT_ALT', "value": 'PRESS', "any": True}, None),
("NODE_LINK_ATTACH_ON", {"type": 'LEFT_ALT', "value": 'RELEASE', "any": True}, None),
("NODE_LINK_ATTACH_OFF", {"type": 'LEFT_ALT', "value": 'PRESS', "any": True}, None),
("NODE_FRAME_ATTACH_ON", {"type": 'LEFT_SHIFT', "value": 'PRESS', "any": True}, None),
("NODE_FRAME_ATTACH_OFF", {"type": 'LEFT_SHIFT', "value": 'RELEASE', "any": True}, None),
("AUTOCONSTRAIN", {"type": 'MIDDLEMOUSE', "value": 'ANY', **alt_without_navigaton}, None),
("AUTOCONSTRAINPLANE", {"type": 'MIDDLEMOUSE', "value": 'ANY', "shift": True, **alt_without_navigaton}, None),
("PRECISION", {"type": 'LEFT_SHIFT', "value": 'ANY', "any": True}, None),

View File

@ -4270,8 +4270,10 @@ def km_transform_modal_map(_params):
("AUTOIK_CHAIN_LEN_UP", {"type": 'WHEELDOWNMOUSE', "value": 'PRESS', "shift": True}, None),
("AUTOIK_CHAIN_LEN_DOWN", {"type": 'WHEELUPMOUSE', "value": 'PRESS', "shift": True}, None),
("INSERTOFS_TOGGLE_DIR", {"type": 'T', "value": 'PRESS'}, None),
("NODE_ATTACH_ON", {"type": 'LEFT_ALT', "value": 'RELEASE', "any": True}, None),
("NODE_ATTACH_OFF", {"type": 'LEFT_ALT', "value": 'PRESS', "any": True}, None),
("NODE_LINK_ATTACH_ON", {"type": 'LEFT_ALT', "value": 'RELEASE', "any": True}, None),
("NODE_LINK_ATTACH_OFF", {"type": 'LEFT_ALT', "value": 'PRESS', "any": True}, None),
("NODE_FRAME_ATTACH_ON", {"type": 'LEFT_SHIFT', "value": 'PRESS', "any": True}, None),
("NODE_FRAME_ATTACH_OFF", {"type": 'LEFT_SHIFT', "value": 'RELEASE', "any": True}, None),
("AUTOCONSTRAIN", {"type": 'MIDDLEMOUSE', "value": 'ANY'}, None),
("AUTOCONSTRAINPLANE", {"type": 'MIDDLEMOUSE', "value": 'ANY', "shift": True}, None),
("PRECISION", {"type": 'LEFT_SHIFT', "value": 'ANY', "any": True}, None),

View File

@ -31,6 +31,8 @@ void node_insert_on_link_flags_set(SpaceNode &snode, const ARegion &region);
void node_insert_on_link_flags(Main &bmain, SpaceNode &snode);
void node_insert_on_link_flags_clear(bNodeTree &node_tree);
void node_tree_update_frame_attachment(bNodeTree &ntree);
/**
* Draw a single node socket at default size.
* \note this is only called from external code, internally #node_socket_draw_nested() is used for

View File

@ -227,12 +227,6 @@ NodeResizeDirection node_get_resize_direction(const SpaceNode &snode,
const float size = NODE_RESIZE_MARGIN * math::max(snode.runtime->aspect, 1.0f);
if (node->type == NODE_FRAME) {
NodeFrame *data = (NodeFrame *)node->storage;
/* shrinking frame size is determined by child nodes */
if (!(data->flag & NODE_FRAME_RESIZEABLE)) {
return NODE_RESIZE_NONE;
}
NodeResizeDirection dir = NODE_RESIZE_NONE;

View File

@ -270,6 +270,24 @@ static bool compare_node_depth(const bNode *a, const bNode *b)
return false;
}
/* Put frames in the back during resizing. Otherwise the bigger frame should be behind. */
if (a->is_frame() && b->is_frame()) {
if (static_cast<NodeFrame *>(a->storage)->flag & NODE_FRAME_RESIZING) {
return true;
}
if (static_cast<NodeFrame *>(b->storage)->flag & NODE_FRAME_RESIZING) {
return false;
}
rctf *rect_a = &a->runtime->totr;
rctf *rect_b = &b->runtime->totr;
const float area_a = BLI_rctf_size_x(rect_a) * BLI_rctf_size_y(rect_a);
const float area_b = BLI_rctf_size_x(rect_b) * BLI_rctf_size_y(rect_b);
return area_a > area_b;
}
/* One has a higher selection state (active > selected > nothing). */
if (a_active && !b_active) {
return false;
@ -3303,31 +3321,34 @@ static void frame_node_prepare_for_draw(bNode &node, Span<bNode *> nodes)
rctf rect;
node_to_updated_rect(node, rect);
/* Frame can be resized manually only if shrinking is disabled or no children are attached. */
data->flag |= NODE_FRAME_RESIZEABLE;
/* Shrinking is disabled while resizing the node. */
const bool shrink = (data->flag & NODE_FRAME_SHRINK) &&
((data->flag & NODE_FRAME_RESIZING) == 0);
/* For shrinking bounding box, initialize the rect from first child node. */
bool bbinit = (data->flag & NODE_FRAME_SHRINK);
/* Fit bounding box to all children. */
for (const bNode *tnode : nodes) {
if (tnode->parent != &node) {
continue;
}
if (shrink) {
bool bbinit = true;
/* Fit bounding box to all children. */
for (const bNode *tnode : nodes) {
if (tnode->parent != &node) {
continue;
}
/* Add margin to node rect. */
rctf noderect = tnode->runtime->totr;
noderect.xmin -= margin;
noderect.xmax += margin;
noderect.ymin -= margin;
noderect.ymax += margin_top;
/* Add margin to node rect. */
rctf noderect = tnode->runtime->totr;
noderect.xmin -= margin;
noderect.xmax += margin;
noderect.ymin -= margin;
noderect.ymax += margin_top;
/* First child initializes frame. */
if (bbinit) {
bbinit = false;
rect = noderect;
data->flag &= ~NODE_FRAME_RESIZEABLE;
}
else {
BLI_rctf_union(&rect, &noderect);
/* First child initializes frame. */
if (bbinit) {
bbinit = false;
rect = noderect;
}
else {
BLI_rctf_union(&rect, &noderect);
}
}
}

View File

@ -911,10 +911,11 @@ static void node_resize_exit(bContext *C, wmOperator *op, bool cancel)
{
WM_cursor_modal_restore(CTX_wm_window(C));
SpaceNode *snode = CTX_wm_space_node(C);
bNode *node = nodeGetActive(snode->edittree);
/* Restore old data on cancel. */
if (cancel) {
SpaceNode *snode = CTX_wm_space_node(C);
bNode *node = nodeGetActive(snode->edittree);
NodeSizeWidget *nsw = (NodeSizeWidget *)op->customdata;
node->locx = nsw->oldlocx;
@ -925,6 +926,15 @@ static void node_resize_exit(bContext *C, wmOperator *op, bool cancel)
node->height = nsw->oldheight;
}
if (node->is_frame()) {
NodeFrame *data = static_cast<NodeFrame *>(node->storage);
data->flag &= ~NODE_FRAME_RESIZING;
}
bNodeTree *node_tree = snode->edittree;
node_tree_update_frame_attachment(*node_tree);
ED_node_tree_propagate_change(C, CTX_data_main(C), node_tree);
MEM_freeN(op->customdata);
op->customdata = nullptr;
}
@ -1053,6 +1063,25 @@ static int node_resize_invoke(bContext *C, wmOperator *op, const wmEvent *event)
return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH;
}
if (node->is_frame()) {
NodeFrame *data = static_cast<NodeFrame *>(node->storage);
data->flag |= NODE_FRAME_RESIZING;
bNodeTree &node_tree = *snode->edittree;
node_sort(node_tree);
/* Move child nodes up one level in the parenting hierarchy during resizing. */
LISTBASE_FOREACH (bNode *, child_node, &node_tree.nodes) {
if (child_node->parent != node) {
continue;
}
nodeDetachNode(&node_tree, child_node);
if (node->parent) {
nodeAttachNode(&node_tree, child_node, node->parent);
}
}
}
node_resize_init(C, op, cursor, node, dir);
return OPERATOR_RUNNING_MODAL;
}

View File

@ -309,7 +309,6 @@ void NODE_OT_links_mute(wmOperatorType *ot);
void NODE_OT_parent_set(wmOperatorType *ot);
void NODE_OT_join(wmOperatorType *ot);
void NODE_OT_attach(wmOperatorType *ot);
void NODE_OT_detach(wmOperatorType *ot);
void NODE_OT_link_viewer(wmOperatorType *ot);

View File

@ -93,7 +93,6 @@ void node_operatortypes()
WM_operatortype_append(NODE_OT_parent_set);
WM_operatortype_append(NODE_OT_join);
WM_operatortype_append(NODE_OT_attach);
WM_operatortype_append(NODE_OT_detach);
WM_operatortype_append(NODE_OT_clipboard_copy);
@ -143,7 +142,6 @@ void ED_operatormacros_node()
"Move nodes and attach to frame",
OPTYPE_UNDO | OPTYPE_REGISTER);
mot = WM_operatortype_macro_define(ot, "TRANSFORM_OT_translate");
WM_operatortype_macro_define(ot, "NODE_OT_attach");
/* NODE_OT_translate_attach with remove_on_cancel set to true. */
ot = WM_operatortype_append_macro("NODE_OT_translate_attach_remove_on_cancel",
@ -153,7 +151,6 @@ void ED_operatormacros_node()
mot = WM_operatortype_macro_define(ot, "TRANSFORM_OT_translate");
RNA_boolean_set(mot->ptr, "remove_on_cancel", true);
RNA_boolean_set(mot->ptr, "view2d_edge_pan", true);
WM_operatortype_macro_define(ot, "NODE_OT_attach");
/* NOTE: Currently not in a default keymap or menu due to messy keymaps
* and tricky invoke functionality.
@ -165,7 +162,6 @@ void ED_operatormacros_node()
OPTYPE_UNDO | OPTYPE_REGISTER);
WM_operatortype_macro_define(ot, "NODE_OT_detach");
mot = WM_operatortype_macro_define(ot, "TRANSFORM_OT_translate");
WM_operatortype_macro_define(ot, "NODE_OT_attach");
ot = WM_operatortype_append_macro("NODE_OT_duplicate_move",
"Duplicate",

View File

@ -1897,83 +1897,66 @@ void NODE_OT_join(wmOperatorType *ot)
/** \name Attach Operator
* \{ */
static bNode *node_find_frame_to_attach(ARegion &region,
const bNodeTree &ntree,
const int2 mouse_xy)
static bool node_can_be_attached_to_frame(const bNode &node, const bNode &frame)
{
/* convert mouse coordinates to v2d space */
float2 cursor;
UI_view2d_region_to_view(&region.v2d, mouse_xy.x, mouse_xy.y, &cursor.x, &cursor.y);
if (&node == &frame) {
return false;
}
if (node.parent == nullptr) {
return true;
}
/* Check if the direct parent of the node is also an ancestor of the frame. */
for (bNode *frame_ancestor = frame.parent; frame_ancestor;
frame_ancestor = frame_ancestor->parent) {
if (frame_ancestor == node.parent) {
return true;
}
}
return false;
}
static bool node_is_inside_parent_frame(const bNode &node)
{
if (node.parent == nullptr) {
return false;
}
rctf *node_rect = &node.runtime->totr;
rctf *frame_rect = &node.parent->runtime->totr;
return BLI_rctf_inside_rctf(frame_rect, node_rect);
}
static void node_attach_to_underlying_frame(bNodeTree &ntree, bNode &node)
{
rctf *node_rect = &node.runtime->totr;
LISTBASE_FOREACH_BACKWARD (bNode *, frame, &ntree.nodes) {
/* skip selected, those are the nodes we want to attach */
if ((frame->type != NODE_FRAME) || (frame->flag & NODE_SELECT)) {
if (!frame->is_frame()) {
continue;
}
if (BLI_rctf_isect_pt_v(&frame->runtime->totr, cursor)) {
return frame;
if (!node_can_be_attached_to_frame(node, *frame)) {
continue;
}
rctf *frame_rect = &frame->runtime->totr;
if (BLI_rctf_inside_rctf(frame_rect, node_rect)) {
nodeAttachNode(&ntree, &node, frame);
}
}
return nullptr;
}
static int node_attach_invoke(bContext *C, wmOperator * /*op*/, const wmEvent *event)
void node_tree_update_frame_attachment(bNodeTree &ntree)
{
ARegion &region = *CTX_wm_region(C);
SpaceNode &snode = *CTX_wm_space_node(C);
bNodeTree &ntree = *snode.edittree;
bNode *frame = node_find_frame_to_attach(region, ntree, event->mval);
if (frame == nullptr) {
/* Return "finished" so that auto offset operator macros can work. */
return OPERATOR_FINISHED;
LISTBASE_FOREACH (bNode *, node, &ntree.nodes) {
if (!node_is_inside_parent_frame(*node)) {
nodeDetachNode(&ntree, node);
}
}
LISTBASE_FOREACH_BACKWARD (bNode *, node, &ntree.nodes) {
if (!(node->flag & NODE_SELECT)) {
continue;
}
/* Disallow moving a parent into its child. */
if (node->is_frame() && nodeIsParentAndChild(node, frame)) {
continue;
}
if (node->parent == nullptr) {
nodeAttachNode(&ntree, node, frame);
continue;
}
/* Attach nodes which share parent with the frame. */
const bool share_parent = nodeIsParentAndChild(node->parent, frame);
if (!share_parent) {
continue;
}
nodeDetachNode(&ntree, node);
nodeAttachNode(&ntree, node, frame);
LISTBASE_FOREACH (bNode *, frame, &ntree.nodes) {
node_attach_to_underlying_frame(ntree, *frame);
}
node_sort(ntree);
WM_event_add_notifier(C, NC_NODE | ND_DISPLAY, nullptr);
return OPERATOR_FINISHED;
}
void NODE_OT_attach(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Attach Nodes";
ot->description = "Attach active node to a frame";
ot->idname = "NODE_OT_attach";
/* api callbacks */
ot->invoke = node_attach_invoke;
ot->poll = ED_operator_node_editable;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/** \} */
@ -2424,8 +2407,7 @@ static bool node_link_insert_offset_chain_cb(bNode *fromnode,
}
static void node_link_insert_offset_ntree(NodeInsertOfsData *iofsd,
ARegion *region,
const int mouse_xy[2],
const SpaceNode &snode,
const bool right_alignment)
{
bNodeTree *ntree = iofsd->ntree;
@ -2448,43 +2430,6 @@ static void node_link_insert_offset_ntree(NodeInsertOfsData *iofsd,
rctf totr_insert;
node_to_updated_rect(insert, totr_insert);
/* Frame attachment wasn't handled yet so we search the frame that the node will be attached to
* later. */
insert.parent = node_find_frame_to_attach(*region, *ntree, mouse_xy);
/* This makes sure nodes are also correctly offset when inserting a node on top of a frame
* without actually making it a part of the frame (because mouse isn't intersecting it)
* - logic here is similar to node_find_frame_to_attach. */
if (!insert.parent ||
(prev->parent && (prev->parent == next->parent) && (prev->parent != insert.parent)))
{
rctf totr_frame;
/* check nodes front to back */
LISTBASE_FOREACH_BACKWARD (bNode *, frame, &ntree->nodes) {
/* skip selected, those are the nodes we want to attach */
if ((frame->type != NODE_FRAME) || (frame->flag & NODE_SELECT)) {
continue;
}
/* for some reason frame y coords aren't correct yet */
node_to_updated_rect(*frame, totr_frame);
if (BLI_rctf_isect_x(&totr_frame, totr_insert.xmin) &&
BLI_rctf_isect_x(&totr_frame, totr_insert.xmax))
{
if (BLI_rctf_isect_y(&totr_frame, totr_insert.ymin) ||
BLI_rctf_isect_y(&totr_frame, totr_insert.ymax))
{
/* frame isn't insert.parent actually, but this is needed to make offsetting
* nodes work correctly for above checked cases (it is restored later) */
insert.parent = frame;
break;
}
}
}
}
/* *** ensure offset at the left (or right for right_alignment case) of insert_node *** */
float dist = right_alignment ? totr_insert.xmin - prev->runtime->totr.xmax :
@ -2616,7 +2561,7 @@ static int node_insert_offset_invoke(bContext *C, wmOperator *op, const wmEvent
iofsd->anim_timer = WM_event_timer_add(CTX_wm_manager(C), CTX_wm_window(C), TIMER, 0.02);
node_link_insert_offset_ntree(
iofsd, CTX_wm_region(C), event->mval, (snode->insert_ofs_dir == SNODE_INSERTOFS_DIR_RIGHT));
iofsd, *snode, (snode->insert_ofs_dir == SNODE_INSERTOFS_DIR_RIGHT));
/* add temp handler */
WM_event_add_modal_handler(C, op);

View File

@ -619,8 +619,10 @@ static bool transform_modal_item_poll(const wmOperator *op, int value)
break;
}
case TFM_MODAL_INSERTOFS_TOGGLE_DIR:
case TFM_MODAL_NODE_ATTACH_ON:
case TFM_MODAL_NODE_ATTACH_OFF: {
case TFM_MODAL_NODE_LINK_ATTACH_ON:
case TFM_MODAL_NODE_LINK_ATTACH_OFF:
case TFM_MODAL_NODE_FRAME_ATTACH_ON:
case TFM_MODAL_NODE_FRAME_ATTACH_OFF: {
if (t->spacetype != SPACE_NODE) {
return false;
}
@ -746,8 +748,18 @@ wmKeyMap *transform_modal_keymap(wmKeyConfig *keyconf)
0,
"Toggle Direction for Node Auto-Offset",
""},
{TFM_MODAL_NODE_ATTACH_ON, "NODE_ATTACH_ON", 0, "Node Attachment", ""},
{TFM_MODAL_NODE_ATTACH_OFF, "NODE_ATTACH_OFF", 0, "Node Attachment (Off)", ""},
{TFM_MODAL_NODE_LINK_ATTACH_ON, "NODE_LINK_ATTACH_ON", 0, "Node Link Attachment", ""},
{TFM_MODAL_NODE_LINK_ATTACH_OFF,
"NODE_LINK_ATTACH_OFF",
0,
"Node Link Attachment (Off)",
""},
{TFM_MODAL_NODE_FRAME_ATTACH_ON, "NODE_FRAME_ATTACH_ON", 0, "Node Frame Attachment", ""},
{TFM_MODAL_NODE_FRAME_ATTACH_OFF,
"NODE_FRAME_ATTACH_OFF",
0,
"Node Frame Attachment (Off)",
""},
{TFM_MODAL_TRANSLATE, "TRANSLATE", 0, "Move", ""},
{TFM_MODAL_VERT_EDGE_SLIDE, "VERT_EDGE_SLIDE", 0, "Vert/Edge Slide", ""},
{TFM_MODAL_ROTATE, "ROTATE", 0, "Rotate", ""},
@ -1209,13 +1221,23 @@ int transformEvent(TransInfo *t, const wmEvent *event)
t->redraw |= TREDRAW_SOFT;
}
break;
case TFM_MODAL_NODE_ATTACH_ON:
t->modifiers |= MOD_NODE_ATTACH;
case TFM_MODAL_NODE_LINK_ATTACH_ON:
t->modifiers |= MOD_NODE_LINK_ATTACH;
t->redraw |= TREDRAW_HARD;
handled = true;
break;
case TFM_MODAL_NODE_ATTACH_OFF:
t->modifiers &= ~MOD_NODE_ATTACH;
case TFM_MODAL_NODE_LINK_ATTACH_OFF:
t->modifiers &= ~MOD_NODE_LINK_ATTACH;
t->redraw |= TREDRAW_HARD;
handled = true;
break;
case TFM_MODAL_NODE_FRAME_ATTACH_ON:
t->modifiers |= MOD_NODE_FRAME_ATTACH;
t->redraw |= TREDRAW_HARD;
handled = true;
break;
case TFM_MODAL_NODE_FRAME_ATTACH_OFF:
t->modifiers &= ~MOD_NODE_FRAME_ATTACH;
t->redraw |= TREDRAW_HARD;
handled = true;
break;
@ -2006,13 +2028,13 @@ bool initTransform(bContext *C, TransInfo *t, wmOperator *op, const wmEvent *eve
if (t->data_type == &TransConvertType_Node) {
/* Set the initial auto-attach flag based on whether the chosen keymap key is pressed at the
* start of the operator. */
t->modifiers |= MOD_NODE_ATTACH;
t->modifiers |= MOD_NODE_LINK_ATTACH;
LISTBASE_FOREACH (const wmKeyMapItem *, kmi, &t->keymap->items) {
if (kmi->flag & KMI_INACTIVE) {
continue;
}
if (kmi->propvalue == TFM_MODAL_NODE_ATTACH_OFF && kmi->val == KM_PRESS) {
if (kmi->propvalue == TFM_MODAL_NODE_LINK_ATTACH_OFF && kmi->val == KM_PRESS) {
if ((ELEM(kmi->type, EVT_LEFTCTRLKEY, EVT_RIGHTCTRLKEY) &&
(event->modifier & KM_CTRL)) ||
(ELEM(kmi->type, EVT_LEFTSHIFTKEY, EVT_RIGHTSHIFTKEY) &&
@ -2020,7 +2042,28 @@ bool initTransform(bContext *C, TransInfo *t, wmOperator *op, const wmEvent *eve
(ELEM(kmi->type, EVT_LEFTALTKEY, EVT_RIGHTALTKEY) && (event->modifier & KM_ALT)) ||
((kmi->type == EVT_OSKEY) && (event->modifier & KM_OSKEY)))
{
t->modifiers &= ~MOD_NODE_ATTACH;
t->modifiers &= ~MOD_NODE_LINK_ATTACH;
}
break;
}
}
/* Set the initial frame-attach flag based on whether the chosen keymap key is pressed at the
* start of the operator. */
t->modifiers &= ~MOD_NODE_FRAME_ATTACH;
LISTBASE_FOREACH (const wmKeyMapItem *, kmi, &t->keymap->items) {
if (kmi->flag & KMI_INACTIVE) {
continue;
}
if (kmi->propvalue == TFM_MODAL_NODE_LINK_ATTACH_ON && kmi->val == KM_PRESS) {
if ((ELEM(kmi->type, EVT_LEFTCTRLKEY, EVT_RIGHTCTRLKEY) &&
(event->modifier & KM_CTRL)) ||
(ELEM(kmi->type, EVT_LEFTSHIFTKEY, EVT_RIGHTSHIFTKEY) &&
(event->modifier & KM_SHIFT)) ||
(ELEM(kmi->type, EVT_LEFTALTKEY, EVT_RIGHTALTKEY) && (event->modifier & KM_ALT)) ||
((kmi->type == EVT_OSKEY) && (event->modifier & KM_OSKEY)))
{
t->modifiers |= MOD_NODE_FRAME_ATTACH;
}
break;
}

View File

@ -163,11 +163,12 @@ enum eTModifier {
MOD_SNAP = 1 << 2,
MOD_SNAP_INVERT = 1 << 3,
MOD_CONSTRAINT_SELECT_PLANE = 1 << 4,
MOD_NODE_ATTACH = 1 << 5,
MOD_SNAP_FORCED = 1 << 6,
MOD_EDIT_SNAP_SOURCE = 1 << 7,
MOD_NODE_LINK_ATTACH = 1 << 5,
MOD_NODE_FRAME_ATTACH = 1 << 6,
MOD_SNAP_FORCED = 1 << 7,
MOD_EDIT_SNAP_SOURCE = 1 << 8,
};
ENUM_OPERATORS(eTModifier, MOD_NODE_ATTACH)
ENUM_OPERATORS(eTModifier, MOD_EDIT_SNAP_SOURCE)
/** #TransSnap.status */
enum eTSnap {
@ -264,8 +265,8 @@ enum {
TFM_MODAL_AUTOIK_LEN_INC = 22,
TFM_MODAL_AUTOIK_LEN_DEC = 23,
TFM_MODAL_NODE_ATTACH_ON = 24,
TFM_MODAL_NODE_ATTACH_OFF = 25,
TFM_MODAL_NODE_LINK_ATTACH_ON = 24,
TFM_MODAL_NODE_LINK_ATTACH_OFF = 25,
/** For analog input, like track-pad. */
TFM_MODAL_PROPSIZE = 26,
@ -283,6 +284,9 @@ enum {
TFM_MODAL_EDIT_SNAP_SOURCE_ON = 34,
TFM_MODAL_EDIT_SNAP_SOURCE_OFF = 35,
TFM_MODAL_NODE_FRAME_ATTACH_ON = 36,
TFM_MODAL_NODE_FRAME_ATTACH_OFF = 37,
};
/** \} */

View File

@ -32,23 +32,26 @@
#include "WM_api.hh"
struct TransCustomDataNode {
struct TransInfoCustomDataNode {
View2DEdgePanData edgepan_data;
/* Compare if the view has changed so we can update with `transformViewUpdate`. */
rctf viewrect_prev;
eTModifier attachment_state;
};
struct TransCustomDataNode {
/* For reversible unparenting during transform. */
bNode *parent;
};
/* -------------------------------------------------------------------- */
/** \name Node Transform Creation
* \{ */
static void create_transform_data_for_node(TransData &td,
TransData2D &td2d,
bNode &node,
const float dpi_fac)
static void create_transform_data_for_node(
TransData &td, TransData2D &td2d, TransCustomDataNode &tdc, bNode &node, const float dpi_fac)
{
/* account for parents (nested nodes) */
const blender::float2 node_offset = {node.offsetx, node.offsety};
blender::float2 loc = blender::bke::nodeToView(&node, blender::math::round(node_offset));
loc *= dpi_fac;
@ -80,6 +83,8 @@ static void create_transform_data_for_node(TransData &td,
unit_m3(td.smtx);
td.extra = &node;
tdc.parent = node.parent;
}
static bool is_node_parent_select(const bNode *node)
@ -92,6 +97,40 @@ static bool is_node_parent_select(const bNode *node)
return false;
}
static void node_transform_restore_parenting_hierarchy(TransDataContainer *tc,
bNodeTree &node_tree)
{
for (int i = 0; i < tc->data_len; i++) {
TransData *td = &tc->data[i];
bNode *node = static_cast<bNode *>(td->extra);
TransCustomDataNode *tdc = static_cast<TransCustomDataNode *>(tc->custom.type.data);
bNode *parent_node = tdc[i].parent;
if (parent_node == nullptr) {
continue;
}
nodeAttachNode(&node_tree, node, parent_node);
}
}
static void node_transform_detach_nodes(bNodeTree &node_tree)
{
for (bNode *node : blender::ed::space_node::get_selected_nodes(node_tree)) {
if (node->parent == nullptr) {
continue;
}
if (is_node_parent_select(node)) {
/* When a parent frame is transformed together with the node we don't need to clear
* the parenting. */
continue;
}
nodeDetachNode(&node_tree, node);
}
}
static void createTransNodeData(bContext * /*C*/, TransInfo *t)
{
using namespace blender;
@ -103,7 +142,7 @@ static void createTransNodeData(bContext * /*C*/, TransInfo *t)
}
/* Custom data to enable edge panning during the node transform */
TransCustomDataNode *customdata = MEM_cnew<TransCustomDataNode>(__func__);
TransInfoCustomDataNode *customdata = MEM_cnew<TransInfoCustomDataNode>(__func__);
UI_view2d_edge_pan_init(t->context,
&customdata->edgepan_data,
NODE_EDGE_PAN_INSIDE_PAD,
@ -114,11 +153,15 @@ static void createTransNodeData(bContext * /*C*/, TransInfo *t)
NODE_EDGE_PAN_ZOOM_INFLUENCE);
customdata->viewrect_prev = customdata->edgepan_data.initial_rect;
if (t->modifiers & MOD_NODE_ATTACH) {
if (t->modifiers & MOD_NODE_LINK_ATTACH) {
space_node::node_insert_on_link_flags_set(*snode, *t->region);
}
else {
space_node::node_insert_on_link_flags_clear(*snode->edittree);
space_node::node_insert_on_link_flags_clear(*node_tree);
}
if (t->modifiers & MOD_NODE_FRAME_ATTACH) {
node_transform_detach_nodes(*node_tree);
}
t->custom.type.data = customdata;
@ -138,9 +181,13 @@ static void createTransNodeData(bContext * /*C*/, TransInfo *t)
tc->data_len = nodes.size();
tc->data = MEM_cnew_array<TransData>(tc->data_len, __func__);
tc->data_2d = MEM_cnew_array<TransData2D>(tc->data_len, __func__);
tc->custom.type.data = MEM_cnew_array<TransCustomDataNode>(tc->data_len, __func__);
tc->custom.type.use_free = true;
for (const int i : nodes.index_range()) {
create_transform_data_for_node(tc->data[i], tc->data_2d[i], *nodes[i], UI_SCALE_FAC);
TransCustomDataNode *data_node = static_cast<TransCustomDataNode *>(tc->custom.type.data);
create_transform_data_for_node(
tc->data[i], tc->data_2d[i], data_node[i], *nodes[i], UI_SCALE_FAC);
}
}
@ -197,8 +244,9 @@ static void flushTransNodes(TransInfo *t)
using namespace blender::ed;
const float dpi_fac = UI_SCALE_FAC;
SpaceNode *snode = static_cast<SpaceNode *>(t->area->spacedata.first);
bNodeTree &node_tree = *snode->edittree;
TransCustomDataNode *customdata = (TransCustomDataNode *)t->custom.type.data;
TransInfoCustomDataNode *customdata = (TransInfoCustomDataNode *)t->custom.type.data;
if (t->options & CTX_VIEW2D_EDGE_PAN) {
if (t->state == TRANS_CANCEL) {
@ -247,13 +295,29 @@ static void flushTransNodes(TransInfo *t)
node->locy = location.y;
}
/* handle intersection with noodles */
if (tc->data_len == 1) {
if (t->modifiers & MOD_NODE_ATTACH) {
/* Handle intersection with node links. */
if (t->modifiers & MOD_NODE_LINK_ATTACH) {
if (tc->data_len == 1) {
space_node::node_insert_on_link_flags_set(*snode, *t->region);
}
}
else {
space_node::node_insert_on_link_flags_clear(node_tree);
}
/* Handle detaching nodes from parent frames. */
const bool frame_attachment_state_changed = (t->modifiers & MOD_NODE_FRAME_ATTACH) !=
(customdata->attachment_state &
MOD_NODE_FRAME_ATTACH);
if (frame_attachment_state_changed) {
if (t->modifiers & MOD_NODE_FRAME_ATTACH) {
node_transform_detach_nodes(node_tree);
customdata->attachment_state = MOD_NODE_FRAME_ATTACH;
}
else {
space_node::node_insert_on_link_flags_clear(*snode->edittree);
node_transform_restore_parenting_hierarchy(tc, node_tree);
customdata->attachment_state &= ~MOD_NODE_FRAME_ATTACH;
}
}
}
@ -288,12 +352,13 @@ static void special_aftertrans_update__node(bContext *C, TransInfo *t)
if (!canceled) {
ED_node_post_apply_transform(C, snode->edittree);
if (t->modifiers & MOD_NODE_ATTACH) {
if (t->modifiers & MOD_NODE_LINK_ATTACH) {
space_node::node_insert_on_link_flags(*bmain, *snode);
}
}
space_node::node_insert_on_link_flags_clear(*ntree);
space_node::node_tree_update_frame_attachment(*ntree);
wmOperatorType *ot = WM_operatortype_find("NODE_OT_insert_offset", true);
BLI_assert(ot);

View File

@ -1895,12 +1895,11 @@ enum {
};
/* Frame node flags. */
enum {
/** Keep the bounding box minimal. */
NODE_FRAME_SHRINK = 1,
/** Test flag, if frame can be resized by user. */
NODE_FRAME_RESIZEABLE = 2,
/** Temporary flag for when the frame is resized by user. */
NODE_FRAME_RESIZING = 2,
};
/* Proxy node flags. */