diff --git a/scripts/presets/keyconfig/keymap_data/blender_default.py b/scripts/presets/keyconfig/keymap_data/blender_default.py index c452b658cc9..c7a192cb9a4 100644 --- a/scripts/presets/keyconfig/keymap_data/blender_default.py +++ b/scripts/presets/keyconfig/keymap_data/blender_default.py @@ -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), diff --git a/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py b/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py index c9879a6af32..8c8ef8e0016 100644 --- a/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py +++ b/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py @@ -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), diff --git a/source/blender/editors/include/ED_node.hh b/source/blender/editors/include/ED_node.hh index 3ab0d2d8098..da5013692e8 100644 --- a/source/blender/editors/include/ED_node.hh +++ b/source/blender/editors/include/ED_node.hh @@ -31,6 +31,8 @@ void node_insert_on_link_flags_set(SpaceNode &snode, const ARegion ®ion); 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 diff --git a/source/blender/editors/space_node/drawnode.cc b/source/blender/editors/space_node/drawnode.cc index 84d4d42f29b..fa6549c5465 100644 --- a/source/blender/editors/space_node/drawnode.cc +++ b/source/blender/editors/space_node/drawnode.cc @@ -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; diff --git a/source/blender/editors/space_node/node_draw.cc b/source/blender/editors/space_node/node_draw.cc index d1ba61e8dd9..a2f85c48804 100644 --- a/source/blender/editors/space_node/node_draw.cc +++ b/source/blender/editors/space_node/node_draw.cc @@ -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(a->storage)->flag & NODE_FRAME_RESIZING) { + return true; + } + if (static_cast(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 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); + } } } diff --git a/source/blender/editors/space_node/node_edit.cc b/source/blender/editors/space_node/node_edit.cc index 56f8d85fb68..54991d381a0 100644 --- a/source/blender/editors/space_node/node_edit.cc +++ b/source/blender/editors/space_node/node_edit.cc @@ -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(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(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; } diff --git a/source/blender/editors/space_node/node_intern.hh b/source/blender/editors/space_node/node_intern.hh index cd420627a01..2b1eb8c1857 100644 --- a/source/blender/editors/space_node/node_intern.hh +++ b/source/blender/editors/space_node/node_intern.hh @@ -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); diff --git a/source/blender/editors/space_node/node_ops.cc b/source/blender/editors/space_node/node_ops.cc index bcd91e3c106..5835d4dc009 100644 --- a/source/blender/editors/space_node/node_ops.cc +++ b/source/blender/editors/space_node/node_ops.cc @@ -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", diff --git a/source/blender/editors/space_node/node_relationships.cc b/source/blender/editors/space_node/node_relationships.cc index 0b90bf7d78c..ce7865cf35d 100644 --- a/source/blender/editors/space_node/node_relationships.cc +++ b/source/blender/editors/space_node/node_relationships.cc @@ -1897,83 +1897,66 @@ void NODE_OT_join(wmOperatorType *ot) /** \name Attach Operator * \{ */ -static bNode *node_find_frame_to_attach(ARegion ®ion, - 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(®ion.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 ®ion = *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); diff --git a/source/blender/editors/transform/transform.cc b/source/blender/editors/transform/transform.cc index 52b76c8f9ac..c580b78d188 100644 --- a/source/blender/editors/transform/transform.cc +++ b/source/blender/editors/transform/transform.cc @@ -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; } diff --git a/source/blender/editors/transform/transform.hh b/source/blender/editors/transform/transform.hh index 71d40860c9b..2dea26e6bb9 100644 --- a/source/blender/editors/transform/transform.hh +++ b/source/blender/editors/transform/transform.hh @@ -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, }; /** \} */ diff --git a/source/blender/editors/transform/transform_convert_node.cc b/source/blender/editors/transform/transform_convert_node.cc index 2d18269d22f..7610f789c79 100644 --- a/source/blender/editors/transform/transform_convert_node.cc +++ b/source/blender/editors/transform/transform_convert_node.cc @@ -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(td->extra); + + TransCustomDataNode *tdc = static_cast(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(__func__); + TransInfoCustomDataNode *customdata = MEM_cnew(__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(tc->data_len, __func__); tc->data_2d = MEM_cnew_array(tc->data_len, __func__); + tc->custom.type.data = MEM_cnew_array(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(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(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); diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h index 463df977389..ca6b016d0e4 100644 --- a/source/blender/makesdna/DNA_node_types.h +++ b/source/blender/makesdna/DNA_node_types.h @@ -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. */