@@ -230,6 +230,8 @@ void SceneTreeDock::shortcut_input(const Ref<InputEvent> &p_event) {
230230 _tool_selected (TOOL_SHOW_IN_FILE_SYSTEM);
231231 } else if (ED_IS_SHORTCUT (" scene_tree/toggle_unique_name" , p_event)) {
232232 _tool_selected (TOOL_TOGGLE_SCENE_UNIQUE_NAME);
233+ } else if (ED_IS_SHORTCUT (" scene_tree/toggle_expose_node" , p_event)) {
234+ _tool_selected (TOOL_TOGGLE_SCENE_EXPOSE_NODE);
233235 } else if (ED_IS_SHORTCUT (" scene_tree/toggle_editable_children" , p_event)) {
234236 _tool_selected (TOOL_SCENE_EDITABLE_CHILDREN);
235237 } else if (ED_IS_SHORTCUT (" scene_tree/delete" , p_event)) {
@@ -329,6 +331,14 @@ void SceneTreeDock::_perform_instantiate_scenes(const Vector<String> &p_files, N
329331 instantiated_scene->set_scene_file_path (ProjectSettings::get_singleton ()->localize_path (p_files[i]));
330332
331333 instances.push_back (instantiated_scene);
334+ Vector<NodePath> exposed_nodes = sdata->get_state ()->get_exposed_nodes ();
335+ instantiated_scene->set_meta (META_CONTAINS_EXPOSED_NODES, exposed_nodes.size () > 0 );
336+ for (const NodePath &e_path : exposed_nodes) {
337+ Node *ei = instantiated_scene->get_node_or_null (e_path);
338+ if (ei) {
339+ ei->set_meta (META_EXPOSED_IN_INSTANCE, true );
340+ }
341+ }
332342 }
333343
334344 if (error) {
@@ -454,6 +464,15 @@ void SceneTreeDock::_replace_with_branch_scene(const String &p_file, Node *base)
454464 return ;
455465 }
456466
467+ Vector<NodePath> exposed_nodes = sdata->get_state ()->get_exposed_nodes ();
468+ instantiated_scene->set_meta (META_CONTAINS_EXPOSED_NODES, exposed_nodes.size () > 0 );
469+ for (const NodePath &e_path : exposed_nodes) {
470+ Node *ei = instantiated_scene->get_node_or_null (e_path);
471+ if (ei) {
472+ ei->set_meta (META_EXPOSED_IN_INSTANCE, true );
473+ }
474+ }
475+
457476 instantiated_scene->set_unique_name_in_owner (base->is_unique_name_in_owner ());
458477
459478 Node2D *copy_2d = Object::cast_to<Node2D>(instantiated_scene);
@@ -1499,6 +1518,57 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) {
14991518 undo_redo->commit_action ();
15001519 }
15011520 } break ;
1521+ case TOOL_TOGGLE_SCENE_EXPOSE_NODE: {
1522+ const List<Node *>::Element *first_selected = editor_selection->get_top_selected_node_list ().front ();
1523+ if (first_selected == nullptr ) {
1524+ return ;
1525+ }
1526+ if (first_selected->get () == EditorNode::get_singleton ()->get_edited_scene ()) {
1527+ editor_selection->remove_node (first_selected->get ());
1528+ first_selected = editor_selection->get_top_selected_node_list ().front ();
1529+ if (first_selected == nullptr ) {
1530+ return ;
1531+ }
1532+ }
1533+
1534+ const List<Node *> full_selection = editor_selection->get_full_selected_node_list ();
1535+ bool enabling = !first_selected->get ()->has_meta (META_MARKED_FOR_EXPOSURE);
1536+
1537+ EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton ();
1538+ if (enabling) {
1539+ undo_redo->create_action (TTR (" Expose Node(s) In Scene" ));
1540+ } else {
1541+ undo_redo->create_action (TTR (" Unexpose Node(s) In Scene" ));
1542+ }
1543+
1544+ for (Node *node : full_selection) {
1545+ // Only operate on nodes whose state will actually change
1546+ bool is_exposed = node->has_meta (META_MARKED_FOR_EXPOSURE);
1547+
1548+ if (enabling && !is_exposed) {
1549+ // Only expose nodes that are not already exposed
1550+ if (get_tree ()->get_edited_scene_root () == node->get_owner ()) {
1551+ undo_redo->add_do_method (node, " set_meta" , META_EXPOSED_IN_OWNER, true );
1552+ undo_redo->add_undo_method (node, " remove_meta" , META_EXPOSED_IN_OWNER);
1553+ }
1554+ undo_redo->add_do_method (node, " set_meta" , META_MARKED_FOR_EXPOSURE, true );
1555+ undo_redo->add_undo_method (node, " remove_meta" , META_MARKED_FOR_EXPOSURE);
1556+ undo_redo->add_do_method (scene_tree, " update_tree" );
1557+ undo_redo->add_undo_method (scene_tree, " update_tree" );
1558+ } else if (!enabling && is_exposed) {
1559+ // Only unexpose nodes that are currently exposed
1560+ undo_redo->add_do_method (node, " remove_meta" , META_MARKED_FOR_EXPOSURE);
1561+ undo_redo->add_undo_method (node, " set_meta" , META_MARKED_FOR_EXPOSURE, true );
1562+ if (get_tree ()->get_edited_scene_root () == node->get_owner ()) {
1563+ undo_redo->add_do_method (node, " remove_meta" , META_EXPOSED_IN_OWNER);
1564+ undo_redo->add_undo_method (node, " set_meta" , META_EXPOSED_IN_OWNER, true );
1565+ }
1566+ undo_redo->add_do_method (scene_tree, " update_tree" );
1567+ undo_redo->add_undo_method (scene_tree, " update_tree" );
1568+ }
1569+ }
1570+ undo_redo->commit_action ();
1571+ } break ;
15021572 case TOOL_CREATE_2D_SCENE:
15031573 case TOOL_CREATE_3D_SCENE:
15041574 case TOOL_CREATE_USER_INTERFACE:
@@ -2735,6 +2805,11 @@ void SceneTreeDock::_toggle_editable_children(Node *p_node) {
27352805
27362806 for (Node *owned_node : owned) {
27372807 if (owned_node != p_node && owned_node != edited_scene && owned_node->get_owner () == edited_scene && owned_node->get_parent ()->get_owner () != edited_scene) {
2808+ // Preserve children of exposed nodes
2809+ if (owned_node->get_parent ()->has_meta (META_EXPOSED_IN_OWNER)) {
2810+ continue ;
2811+ }
2812+
27382813 owned_nodes_array.push_back (owned_node);
27392814 paths_array.push_back (p_node->get_path_to (owned_node->get_parent ()));
27402815 name_array.push_back (owned_node->get_name ());
@@ -3511,7 +3586,7 @@ static bool _is_node_visible(Node *p_node) {
35113586 if (!p_node->get_owner ()) {
35123587 return false ;
35133588 }
3514- if (p_node->get_owner () != EditorNode::get_singleton ()->get_edited_scene () && !EditorNode::get_singleton ()->get_edited_scene ()->is_editable_instance (p_node->get_owner ())) {
3589+ if (p_node->get_owner () != EditorNode::get_singleton ()->get_edited_scene () && !EditorNode::get_singleton ()->get_edited_scene ()->is_editable_instance (p_node->get_owner ()) && !p_node-> has_meta (META_MARKED_FOR_EXPOSURE) ) {
35153590 return false ;
35163591 }
35173592
@@ -3533,6 +3608,10 @@ static bool _has_visible_children(Node *p_node) {
35333608 return true ;
35343609 }
35353610
3611+ if (p_node->has_exposed_nodes ()) {
3612+ return true ;
3613+ }
3614+
35363615 return false ;
35373616}
35383617
@@ -3545,9 +3624,13 @@ void SceneTreeDock::_normalize_drop(Node *&to_node, int &to_pos, int p_type) {
35453624 to_node = nullptr ;
35463625 ERR_FAIL_MSG (" Cannot perform drop above the root node!" );
35473626 }
3548-
3549- to_pos = to_node->get_index (false );
3550- to_node = to_node->get_parent ();
3627+ if (to_node->has_meta (META_EXPOSED_IN_INSTANCE)) {
3628+ to_node = to_node->get_owner ();
3629+ to_pos = -1 ;
3630+ } else {
3631+ to_pos = to_node->get_index (false );
3632+ to_node = to_node->get_parent ();
3633+ }
35513634
35523635 } else if (p_type == 1 ) {
35533636 // drop at below selected node
@@ -3560,7 +3643,11 @@ void SceneTreeDock::_normalize_drop(Node *&to_node, int &to_pos, int p_type) {
35603643 Node *lower_sibling = nullptr ;
35613644
35623645 if (_has_visible_children (to_node)) {
3563- to_pos = 0 ;
3646+ if (to_node->has_exposed_nodes ()) {
3647+ to_pos = -1 ;
3648+ } else {
3649+ to_pos = 0 ;
3650+ }
35643651 } else {
35653652 for (int i = to_node->get_index (false ) + 1 ; i < to_node->get_parent ()->get_child_count (false ); i++) {
35663653 Node *c = to_node->get_parent ()->get_child (i, false );
@@ -3573,7 +3660,12 @@ void SceneTreeDock::_normalize_drop(Node *&to_node, int &to_pos, int p_type) {
35733660 to_pos = lower_sibling->get_index (false );
35743661 }
35753662
3576- to_node = to_node->get_parent ();
3663+ if (to_node->has_meta (META_EXPOSED_IN_INSTANCE)) {
3664+ to_pos = to_node->get_index (false ) + 1 ;
3665+ to_node = to_node->get_owner ();
3666+ } else {
3667+ to_node = to_node->get_parent ();
3668+ }
35773669 }
35783670 }
35793671}
@@ -3944,16 +4036,20 @@ void SceneTreeDock::_tree_rmb(const Vector2 &p_menu_pos) {
39444036 break ;
39454037 }
39464038 }
4039+
4040+ Node *node = full_selection.front ()->get ();
39474041 if (all_owned) {
39484042 // Group "toggle_unique_name" with "copy_node_path", if it is available.
39494043 if (menu->get_item_index (TOOL_COPY_NODE_PATH) == -1 ) {
39504044 menu->add_separator ();
39514045 }
3952- Node *node = full_selection.front ()->get ();
39534046 menu->add_icon_check_item (get_editor_theme_icon (SNAME (" SceneUniqueName" )), TTRC (" Access as Unique Name" ), TOOL_TOGGLE_SCENE_UNIQUE_NAME);
39544047 menu->set_item_shortcut (menu->get_item_index (TOOL_TOGGLE_SCENE_UNIQUE_NAME), ED_GET_SHORTCUT (" scene_tree/toggle_unique_name" ));
39554048 menu->set_item_checked (menu->get_item_index (TOOL_TOGGLE_SCENE_UNIQUE_NAME), node->is_unique_name_in_owner ());
39564049 }
4050+ menu->add_icon_check_item (get_editor_theme_icon (SNAME (" SceneExposedNode" )), TTRC (" Expose in Instances" ), TOOL_TOGGLE_SCENE_EXPOSE_NODE);
4051+ menu->set_item_shortcut (menu->get_item_index (TOOL_TOGGLE_SCENE_EXPOSE_NODE), ED_GET_SHORTCUT (" scene_tree/toggle_expose_node" ));
4052+ menu->set_item_checked (menu->get_item_index (TOOL_TOGGLE_SCENE_EXPOSE_NODE), node->has_meta (META_MARKED_FOR_EXPOSURE));
39574053 }
39584054
39594055 if (selection.size () == 1 ) {
@@ -4724,6 +4820,7 @@ SceneTreeDock::SceneTreeDock(Node *p_scene_root, EditorSelection *p_editor_selec
47244820 ED_SHORTCUT (" scene_tree/copy_node_path" , TTRC (" Copy Node Path" ), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::C);
47254821 ED_SHORTCUT (" scene_tree/show_in_file_system" , TTRC (" Show in FileSystem" ));
47264822 ED_SHORTCUT (" scene_tree/toggle_unique_name" , TTRC (" Toggle Access as Unique Name" ));
4823+ ED_SHORTCUT (" scene_tree/toggle_expose_node" , TTRC (" Toggle Node Exposure" ));
47274824 ED_SHORTCUT (" scene_tree/toggle_editable_children" , TTRC (" Toggle Editable Children" ));
47284825 ED_SHORTCUT (" scene_tree/delete_no_confirm" , TTRC (" Delete (No Confirm)" ), KeyModifierMask::SHIFT | Key::KEY_DELETE);
47294826 ED_SHORTCUT (" scene_tree/delete" , TTRC (" Delete" ), Key::KEY_DELETE);
0 commit comments