└── addons └── tree_maps ├── todo.txt ├── plugin.gd.uid ├── nodes ├── tree_map.gd.uid ├── tree_map_node.gd.uid ├── TreeMap.svg.import ├── TreeMapNode.svg.import ├── TreeMapNode.svg ├── TreeMap.svg ├── tree_map_node.gd └── tree_map.gd ├── icons ├── arrow.png ├── icons.kra ├── icons.png ├── arrow_filled.png ├── editor │ ├── TileUnchecked.svg │ ├── Lock.svg │ ├── Unlock.svg │ ├── EditorPathSmoothHandle.svg │ ├── TileChecked.svg │ ├── EditAddRemove.svg │ ├── BoneMapperHandleSelected.svg │ ├── CurveCreate.svg │ ├── CurveEdit.svg │ ├── CurveDelete.svg │ ├── Lock.svg.import │ ├── Unlock.svg.import │ ├── CurveEdit.svg.import │ ├── CurveCreate.svg.import │ ├── CurveDelete.svg.import │ ├── TileChecked.svg.import │ ├── EditAddRemove.svg.import │ ├── TileUnchecked.svg.import │ ├── EditorPathSmoothHandle.svg.import │ └── BoneMapperHandleSelected.svg.import ├── icons_atlas.tres ├── arrow.png.import ├── icons.png.import └── arrow_filled.png.import ├── plugin.cfg ├── icon.svg ├── icon.svg.import ├── buttons └── editor_tool_button.tscn ├── LICENSE.txt ├── node.tscn2058105126.tmp ├── example.tscn ├── README.md └── plugin.gd /addons/tree_maps/todo.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /addons/tree_maps/plugin.gd.uid: -------------------------------------------------------------------------------- 1 | uid://hfh3532ig52 2 | -------------------------------------------------------------------------------- /addons/tree_maps/nodes/tree_map.gd.uid: -------------------------------------------------------------------------------- 1 | uid://d2m0hkeep073t 2 | -------------------------------------------------------------------------------- /addons/tree_maps/nodes/tree_map_node.gd.uid: -------------------------------------------------------------------------------- 1 | uid://cn4fi27bjgvjq 2 | -------------------------------------------------------------------------------- /addons/tree_maps/icons/arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ToxicStarfall/tree-maps-addon/HEAD/addons/tree_maps/icons/arrow.png -------------------------------------------------------------------------------- /addons/tree_maps/icons/icons.kra: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ToxicStarfall/tree-maps-addon/HEAD/addons/tree_maps/icons/icons.kra -------------------------------------------------------------------------------- /addons/tree_maps/icons/icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ToxicStarfall/tree-maps-addon/HEAD/addons/tree_maps/icons/icons.png -------------------------------------------------------------------------------- /addons/tree_maps/icons/arrow_filled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ToxicStarfall/tree-maps-addon/HEAD/addons/tree_maps/icons/arrow_filled.png -------------------------------------------------------------------------------- /addons/tree_maps/icons/editor/TileUnchecked.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /addons/tree_maps/icons/editor/Lock.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /addons/tree_maps/icons/editor/Unlock.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /addons/tree_maps/icons/editor/EditorPathSmoothHandle.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /addons/tree_maps/icons/editor/TileChecked.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /addons/tree_maps/icons/editor/EditAddRemove.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /addons/tree_maps/plugin.cfg: -------------------------------------------------------------------------------- 1 | [plugin] 2 | 3 | name="Tree Maps - Graphs and Skill Trees" 4 | description="Tree Maps provides useful nodes and functionality to help create graphical maps of tree-like node structures." 5 | author="ToxicStarfall" 6 | version="1.1" 7 | script="plugin.gd" 8 | -------------------------------------------------------------------------------- /addons/tree_maps/icons/editor/BoneMapperHandleSelected.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /addons/tree_maps/icons/icons_atlas.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="AtlasTexture" load_steps=2 format=3 uid="uid://da0wo7pqgegga"] 2 | 3 | [ext_resource type="Texture2D" uid="uid://q6dukgm6v3yv" path="res://addons/tree_maps/icons/icons.png" id="1_e4qyu"] 4 | 5 | [resource] 6 | atlas = ExtResource("1_e4qyu") 7 | -------------------------------------------------------------------------------- /addons/tree_maps/icons/editor/CurveCreate.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /addons/tree_maps/icons/editor/CurveEdit.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /addons/tree_maps/icons/editor/CurveDelete.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /addons/tree_maps/icons/arrow.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://bxh7dktvo38v3" 6 | path="res://.godot/imported/arrow.png-b02acd572faab1cfebed78d85a062ed0.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://addons/tree_maps/icons/arrow.png" 14 | dest_files=["res://.godot/imported/arrow.png-b02acd572faab1cfebed78d85a062ed0.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /addons/tree_maps/icons/icons.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://q6dukgm6v3yv" 6 | path="res://.godot/imported/icons.png-911985b397013ce7cacb794cd60d8c02.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://addons/tree_maps/icons/icons.png" 14 | dest_files=["res://.godot/imported/icons.png-911985b397013ce7cacb794cd60d8c02.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /addons/tree_maps/icons/arrow_filled.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://pf8oexgghvpw" 6 | path="res://.godot/imported/arrow_filled.png-41833e01422ece1f0d1af0648511ccc6.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://addons/tree_maps/icons/arrow_filled.png" 14 | dest_files=["res://.godot/imported/arrow_filled.png-41833e01422ece1f0d1af0648511ccc6.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /addons/tree_maps/icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /addons/tree_maps/icon.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://bmelguaiquwek" 6 | path="res://.godot/imported/icon.svg-4a7087432761558cacb6f6b392a6c424.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://addons/tree_maps/icon.svg" 14 | dest_files=["res://.godot/imported/icon.svg-4a7087432761558cacb6f6b392a6c424.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | svg/scale=1.0 36 | editor/scale_with_editor_scale=false 37 | editor/convert_colors_with_editor_theme=false 38 | -------------------------------------------------------------------------------- /addons/tree_maps/nodes/TreeMap.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://bn217q4ua4td1" 6 | path="res://.godot/imported/TreeMap.svg-3670b200f2c7823acf9f9d677d77544b.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://addons/tree_maps/nodes/TreeMap.svg" 14 | dest_files=["res://.godot/imported/TreeMap.svg-3670b200f2c7823acf9f9d677d77544b.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | svg/scale=1.0 36 | editor/scale_with_editor_scale=false 37 | editor/convert_colors_with_editor_theme=false 38 | -------------------------------------------------------------------------------- /addons/tree_maps/icons/editor/Lock.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://d03crxes6h32y" 6 | path="res://.godot/imported/Lock.svg-96bf81c05bcc1db6af2ba18061f820ae.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://addons/tree_maps/icons/editor/Lock.svg" 14 | dest_files=["res://.godot/imported/Lock.svg-96bf81c05bcc1db6af2ba18061f820ae.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | svg/scale=1.0 36 | editor/scale_with_editor_scale=false 37 | editor/convert_colors_with_editor_theme=false 38 | -------------------------------------------------------------------------------- /addons/tree_maps/icons/editor/Unlock.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://cnulx1xe6rkgs" 6 | path="res://.godot/imported/Unlock.svg-284fee95106f1f4de50b170f4a04ab36.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://addons/tree_maps/icons/editor/Unlock.svg" 14 | dest_files=["res://.godot/imported/Unlock.svg-284fee95106f1f4de50b170f4a04ab36.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | svg/scale=1.0 36 | editor/scale_with_editor_scale=false 37 | editor/convert_colors_with_editor_theme=false 38 | -------------------------------------------------------------------------------- /addons/tree_maps/nodes/TreeMapNode.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://dyn6parc3ur5i" 6 | path="res://.godot/imported/TreeMapNode.svg-6ab533c8c7893d9fda8c8b5a3ff3d05e.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://addons/tree_maps/nodes/TreeMapNode.svg" 14 | dest_files=["res://.godot/imported/TreeMapNode.svg-6ab533c8c7893d9fda8c8b5a3ff3d05e.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | svg/scale=1.0 36 | editor/scale_with_editor_scale=false 37 | editor/convert_colors_with_editor_theme=false 38 | -------------------------------------------------------------------------------- /addons/tree_maps/icons/editor/CurveEdit.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://kwhdwj61pjwr" 6 | path="res://.godot/imported/CurveEdit.svg-49256e74c35862776da13f974217cf9a.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://addons/tree_maps/icons/editor/CurveEdit.svg" 14 | dest_files=["res://.godot/imported/CurveEdit.svg-49256e74c35862776da13f974217cf9a.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | svg/scale=1.0 36 | editor/scale_with_editor_scale=false 37 | editor/convert_colors_with_editor_theme=false 38 | -------------------------------------------------------------------------------- /addons/tree_maps/icons/editor/CurveCreate.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://c2li8il5nmute" 6 | path="res://.godot/imported/CurveCreate.svg-da7679e3286300768832b4d1d9a382e4.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://addons/tree_maps/icons/editor/CurveCreate.svg" 14 | dest_files=["res://.godot/imported/CurveCreate.svg-da7679e3286300768832b4d1d9a382e4.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | svg/scale=1.0 36 | editor/scale_with_editor_scale=false 37 | editor/convert_colors_with_editor_theme=false 38 | -------------------------------------------------------------------------------- /addons/tree_maps/icons/editor/CurveDelete.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://cy2fsjwjvrt88" 6 | path="res://.godot/imported/CurveDelete.svg-c0fcd6f1effd0b550b8dffe772a1ac21.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://addons/tree_maps/icons/editor/CurveDelete.svg" 14 | dest_files=["res://.godot/imported/CurveDelete.svg-c0fcd6f1effd0b550b8dffe772a1ac21.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | svg/scale=1.0 36 | editor/scale_with_editor_scale=false 37 | editor/convert_colors_with_editor_theme=false 38 | -------------------------------------------------------------------------------- /addons/tree_maps/icons/editor/TileChecked.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://hii138rirhow" 6 | path="res://.godot/imported/TileChecked.svg-72ec8b294b468d57b25125f13cea8157.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://addons/tree_maps/icons/editor/TileChecked.svg" 14 | dest_files=["res://.godot/imported/TileChecked.svg-72ec8b294b468d57b25125f13cea8157.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | svg/scale=1.0 36 | editor/scale_with_editor_scale=false 37 | editor/convert_colors_with_editor_theme=false 38 | -------------------------------------------------------------------------------- /addons/tree_maps/icons/editor/EditAddRemove.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://h4bsdvdsrxrw" 6 | path="res://.godot/imported/EditAddRemove.svg-d689346f1ab7bee26368484656a0c18a.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://addons/tree_maps/icons/editor/EditAddRemove.svg" 14 | dest_files=["res://.godot/imported/EditAddRemove.svg-d689346f1ab7bee26368484656a0c18a.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | svg/scale=1.0 36 | editor/scale_with_editor_scale=false 37 | editor/convert_colors_with_editor_theme=false 38 | -------------------------------------------------------------------------------- /addons/tree_maps/icons/editor/TileUnchecked.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://yy33ukacqxo0" 6 | path="res://.godot/imported/TileUnchecked.svg-7c4e2ebff160c8c4c32c4f31421954f6.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://addons/tree_maps/icons/editor/TileUnchecked.svg" 14 | dest_files=["res://.godot/imported/TileUnchecked.svg-7c4e2ebff160c8c4c32c4f31421954f6.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | svg/scale=1.0 36 | editor/scale_with_editor_scale=false 37 | editor/convert_colors_with_editor_theme=false 38 | -------------------------------------------------------------------------------- /addons/tree_maps/icons/editor/EditorPathSmoothHandle.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://cf6khdesbkgyr" 6 | path="res://.godot/imported/EditorPathSmoothHandle.svg-c1a9c7adae2b62efcff74ec615355894.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://addons/tree_maps/icons/editor/EditorPathSmoothHandle.svg" 14 | dest_files=["res://.godot/imported/EditorPathSmoothHandle.svg-c1a9c7adae2b62efcff74ec615355894.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | svg/scale=1.0 36 | editor/scale_with_editor_scale=false 37 | editor/convert_colors_with_editor_theme=false 38 | -------------------------------------------------------------------------------- /addons/tree_maps/icons/editor/BoneMapperHandleSelected.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://d0c1okq1p4l81" 6 | path="res://.godot/imported/BoneMapperHandleSelected.svg-463f08a410f55171f724ba90f9ad6088.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://addons/tree_maps/icons/editor/BoneMapperHandleSelected.svg" 14 | dest_files=["res://.godot/imported/BoneMapperHandleSelected.svg-463f08a410f55171f724ba90f9ad6088.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | svg/scale=1.0 36 | editor/scale_with_editor_scale=false 37 | editor/convert_colors_with_editor_theme=false 38 | -------------------------------------------------------------------------------- /addons/tree_maps/buttons/editor_tool_button.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=3 uid="uid://c3pssfeg4hawj"] 2 | 3 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_xg628"] 4 | bg_color = Color(0.23, 0.23, 0.23, 1) 5 | border_width_left = 2 6 | border_width_top = 2 7 | border_width_right = 2 8 | border_width_bottom = 2 9 | border_color = Color(0.439216, 0.729412, 0.980392, 1) 10 | 11 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_w5nyu"] 12 | bg_color = Color(0.23, 0.23, 0.23, 1) 13 | 14 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_d8l5m"] 15 | bg_color = Color(0.180392, 0.180392, 0.180392, 1) 16 | border_width_left = 2 17 | border_width_top = 2 18 | border_width_right = 2 19 | border_width_bottom = 2 20 | border_color = Color(0.439216, 0.729412, 0.980392, 1) 21 | 22 | [node name="EditorToolButton" type="Button"] 23 | offset_right = 8.0 24 | offset_bottom = 8.0 25 | size_flags_vertical = 3 26 | theme_override_styles/hover_pressed = SubResource("StyleBoxFlat_xg628") 27 | theme_override_styles/hover = SubResource("StyleBoxFlat_w5nyu") 28 | theme_override_styles/pressed = SubResource("StyleBoxFlat_d8l5m") 29 | toggle_mode = true 30 | icon_alignment = 1 31 | -------------------------------------------------------------------------------- /addons/tree_maps/LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 ToxicStarfall 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /addons/tree_maps/nodes/TreeMapNode.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 33 | 40 | 49 | 50 | -------------------------------------------------------------------------------- /addons/tree_maps/nodes/TreeMap.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 33 | 38 | 39 | -------------------------------------------------------------------------------- /addons/tree_maps/node.tscn2058105126.tmp: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=5 format=3 uid="uid://ctluay8kkm6b8"] 2 | 3 | [ext_resource type="Script" uid="uid://d2m0hkeep073t" path="res://addons/new_folder/tree_map.gd" id="2_112p8"] 4 | [ext_resource type="Script" uid="uid://cn4fi27bjgvjq" path="res://addons/new_folder/tree_map_node.gd" id="3_112p8"] 5 | 6 | [sub_resource type="Gradient" id="Gradient_e2u21"] 7 | 8 | [sub_resource type="Curve2D" id="Curve2D_nxiqb"] 9 | _data = { 10 | "points": PackedVector2Array(0, 0, 0, 0, 155, 45, 21, -109, -21, 109, 281, 128, 0, 0, 0, 0, 486, 270, 0, 0, 0, 0, 669, 335) 11 | } 12 | point_count = 4 13 | 14 | [node name="Node" type="Node"] 15 | 16 | [node name="Line2D" type="Line2D" parent="."] 17 | visible = false 18 | position = Vector2(237, -27) 19 | points = PackedVector2Array(37, 29, 323, 91, 276, 286) 20 | gradient = SubResource("Gradient_e2u21") 21 | 22 | [node name="Path2D" type="Path2D" parent="."] 23 | visible = false 24 | position = Vector2(237, -27) 25 | curve = SubResource("Curve2D_nxiqb") 26 | 27 | [node name="NodeTree" type="Node2D" parent="."] 28 | script = ExtResource("2_112p8") 29 | nodes = Array[Vector2]([Vector2(48, 48), Vector2(96, 288), Vector2(432, 56), Vector2(440, 304)]) 30 | 31 | [node name="NodeTreeItem" type="Node2D" parent="NodeTree"] 32 | position = Vector2(48, 48) 33 | script = ExtResource("3_112p8") 34 | outputs = Array[int]([2, 3, 1]) 35 | metadata/_custom_type_script = "uid://cn4fi27bjgvjq" 36 | 37 | [node name="NodeTreeItem2" type="Node2D" parent="NodeTree"] 38 | position = Vector2(96, 216) 39 | script = ExtResource("3_112p8") 40 | editing_state = 1 41 | editing_connections = true 42 | outputs = Array[int]([3]) 43 | inputs = Array[int]([0]) 44 | metadata/_custom_type_script = "uid://cn4fi27bjgvjq" 45 | 46 | [node name="NodeTreeItem3" type="Node2D" parent="NodeTree"] 47 | position = Vector2(432, 56) 48 | script = ExtResource("3_112p8") 49 | outputs = Array[int]([3]) 50 | inputs = Array[int]([0]) 51 | metadata/_custom_type_script = "uid://cn4fi27bjgvjq" 52 | 53 | [node name="NodeTreeItem4" type="Node2D" parent="NodeTree"] 54 | position = Vector2(504, 216) 55 | script = ExtResource("3_112p8") 56 | editing_state = 1 57 | editing_connections = true 58 | inputs = Array[int]([0, 1, 2]) 59 | metadata/_custom_type_script = "uid://cn4fi27bjgvjq" 60 | -------------------------------------------------------------------------------- /addons/tree_maps/example.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=3 uid="uid://ctluay8kkm6b8"] 2 | 3 | [ext_resource type="Script" uid="uid://d2m0hkeep073t" path="res://addons/tree_maps/nodes/tree_map.gd" id="1_pml1y"] 4 | [ext_resource type="Script" uid="uid://cn4fi27bjgvjq" path="res://addons/tree_maps/nodes/tree_map_node.gd" id="2_rkpsp"] 5 | [ext_resource type="Texture2D" uid="uid://bxh7dktvo38v3" path="res://addons/tree_maps/icons/arrow.png" id="3_b1hr1"] 6 | 7 | [node name="Node" type="Node"] 8 | 9 | [node name="TreeMap" type="Node2D" parent="."] 10 | script = ExtResource("1_pml1y") 11 | nodes = Array[Vector2]([Vector2(0, 192), Vector2(192, 192), Vector2(384, 24), Vector2(200, 24), Vector2(496, 192), Vector2(632, 24), Vector2(192, 272), Vector2(400, 272)]) 12 | 13 | [node name="TreeMapNode" type="Node2D" parent="TreeMap"] 14 | position = Vector2(0, 192) 15 | script = ExtResource("2_rkpsp") 16 | outputs = Array[int]([3]) 17 | inputs = Array[int]([1]) 18 | metadata/_custom_type_script = "uid://cn4fi27bjgvjq" 19 | 20 | [node name="TreeMapNode2" type="Node2D" parent="TreeMap"] 21 | position = Vector2(192, 192) 22 | script = ExtResource("2_rkpsp") 23 | outputs = Array[int]([0]) 24 | inputs = Array[int]([3, 2]) 25 | line_color = Color(1, 0, 0, 1) 26 | metadata/_custom_type_script = "uid://cn4fi27bjgvjq" 27 | 28 | [node name="TreeMapNode3" type="Node2D" parent="TreeMap"] 29 | position = Vector2(384, 24) 30 | script = ExtResource("2_rkpsp") 31 | outputs = Array[int]([1, 4]) 32 | inputs = Array[int]([3]) 33 | line_color = Color(0.0833333, 1, 0, 1) 34 | metadata/_custom_type_script = "uid://cn4fi27bjgvjq" 35 | 36 | [node name="TreeMapNode4" type="Node2D" parent="TreeMap"] 37 | position = Vector2(192, 24) 38 | script = ExtResource("2_rkpsp") 39 | outputs = Array[int]([1, 2]) 40 | inputs = Array[int]([0]) 41 | line_color = Color(1, 0.8, 0, 1) 42 | metadata/_custom_type_script = "uid://cn4fi27bjgvjq" 43 | 44 | [node name="TreeMapNode5" type="Node2D" parent="TreeMap"] 45 | position = Vector2(496, 192) 46 | script = ExtResource("2_rkpsp") 47 | outputs = Array[int]([5]) 48 | inputs = Array[int]([2]) 49 | line_color = Color(0, 0.6, 1, 1) 50 | 51 | [node name="TreeMapNode6" type="Node2D" parent="TreeMap"] 52 | position = Vector2(632, 24) 53 | script = ExtResource("2_rkpsp") 54 | inputs = Array[int]([4]) 55 | 56 | [node name="TreeMapNode7" type="Node2D" parent="TreeMap"] 57 | position = Vector2(192, 272) 58 | script = ExtResource("2_rkpsp") 59 | inputs = Array[int]([7]) 60 | node_color = Color(1, 0.466667, 0, 1) 61 | 62 | [node name="TreeMapNode8" type="Node2D" parent="TreeMap"] 63 | position = Vector2(400, 272) 64 | script = ExtResource("2_rkpsp") 65 | outputs = Array[int]([6]) 66 | node_color = Color(1, 0, 1, 1) 67 | line_color = Color(0, 0.533333, 1, 1) 68 | arrow_color = Color(1, 0, 0, 1) 69 | arrow_texture = ExtResource("3_b1hr1") 70 | -------------------------------------------------------------------------------- /addons/tree_maps/nodes/tree_map_node.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | class_name TreeMapNode 3 | extends Node2D 4 | 5 | signal moved 6 | #signal connections_edited 7 | 8 | 9 | @export var outputs: Array[int] = [] 10 | @export var inputs: Array[int] = [] 11 | 12 | @export var locked: bool = false 13 | #@export_category("Customization") 14 | #@export var data: Resource 15 | 16 | 17 | @export_category("Overrides") 18 | # Defaults are overidden by TreeMap parent. 19 | # Default Properties - fallback if parent properties do not exist. 20 | #var default_node_color = Color.WHITE 21 | #var default_line_color = Color.WHITE 22 | #var default_arrow_color = Color.WHITE 23 | #var default_arrow_texture = preload("res://addons/tree_maps/icons/arrow_filled.png") 24 | 25 | # Inherited TreeMap Properties 26 | var parent_node_color: Color 27 | var parent_line_color: Color 28 | var parent_arrow_color: Color 29 | var parent_arrow_texture: Texture2D 30 | # Inheritance only 31 | var parent_node_size: float 32 | var parent_node_shape: String 33 | var parent_node_texture: Texture2D 34 | var parent_line_thickness: float 35 | var parent_line_texture: Texture2D 36 | 37 | # Internal Usage Properties 38 | #var internal_line_color = default_line_color 39 | 40 | # Editable Override Properties 41 | @export_group("Nodes") 42 | @export var node_color: Color = Color.WHITE 43 | @export_group("Lines") 44 | @export var line_color: Color = Color.WHITE 45 | #@export var line_thickness: float = 10.0 46 | @export_group("Arrows") 47 | @export var arrow_color: Color = Color.WHITE ## Modulates default texture color 48 | @export var arrow_texture: Texture2D = preload("res://addons/tree_maps/icons/arrow_filled.png") 49 | 50 | var override_properties = [] 51 | 52 | 53 | func _setup(): 54 | #print("setup") 55 | # If no override property is specified, then use inherited property. 56 | #if !line_color: 57 | #internal_line_color = parent_line_color 58 | # If no inherited property is specified, then use Default property. 59 | #if !parent_line_color: 60 | #internal_line_color = default_line_color 61 | pass 62 | 63 | 64 | func _enter_tree() -> void: 65 | if Engine.is_editor_hint(): 66 | set_notify_transform(true) 67 | EditorInterface.get_inspector().property_edited.connect( _on_property_edited ) 68 | _setup() 69 | 70 | 71 | func _exit_tree() -> void: 72 | if Engine.is_editor_hint(): 73 | EditorInterface.get_inspector().property_edited.disconnect( _on_property_edited ) 74 | 75 | 76 | func _draw() -> void: 77 | _draw_connection() 78 | _draw_node() 79 | 80 | 81 | func _draw_connection(): 82 | for i in outputs: 83 | draw_set_transform(Vector2(0,0), 0) # Reset drawing position 84 | var target_pos = get_parent().get_child(i).global_position 85 | draw_line(Vector2(0,0) , target_pos - self.global_position, line_color, parent_line_thickness) 86 | 87 | var arrow_texture = arrow_texture 88 | var arrow_pos = (target_pos - self.position) / 2 # Get half-way point between nodes 89 | var arrow_ang = (target_pos - position).angle() # Get angle pointing towards next connecting node 90 | draw_set_transform(arrow_pos, arrow_ang) # Set draw offset to arrow position to make it the center rotating point 91 | draw_texture(arrow_texture, -arrow_texture.get_size() / 2, arrow_color) 92 | 93 | 94 | func _draw_node(): 95 | draw_set_transform(Vector2(0,0), 0) 96 | if parent_node_texture: 97 | var texture_offset = -(parent_node_texture.get_size() / 2) 98 | draw_texture(parent_node_texture, texture_offset, node_color) 99 | else: 100 | draw_circle(Vector2(0,0), parent_node_size / 2, node_color, true) 101 | #draw_colored_polygon() 102 | 103 | 104 | func _notification(what) -> void: 105 | if what == NOTIFICATION_TRANSFORM_CHANGED: 106 | moved.emit(self) 107 | 108 | 109 | func _property_can_revert(property: StringName) -> bool: 110 | match property: 111 | "line_color", "node_color", "arrow_color", "arrow_texture": 112 | return true 113 | return false 114 | 115 | 116 | func _property_get_revert(property: StringName) -> Variant: 117 | #match property: 118 | #"line_color": 119 | #return parent_line_color 120 | if get(property): 121 | # Return parent inherited proeprty if available, othewise return fallback defaults 122 | var parent_prop = get("parent_" + property) 123 | if parent_prop: return parent_prop 124 | else: return get("default_" + property) 125 | return 126 | 127 | 128 | # 129 | func _on_property_edited(property: String): 130 | if EditorInterface.get_inspector().get_edited_object() == self: 131 | match property: 132 | "line_color", "node_color", "arrow_color", "arrow_texture": 133 | apply_properties() 134 | 135 | 136 | # Update properties 137 | func apply_properties(): 138 | # If override property is equal to inherited property, update using inherited properties 139 | #if line_color == parent_line_color: internal_line_color = parent_line_color 140 | # Otherwise use override properties 141 | #else: internal_line_color = line_color 142 | if line_color == parent_line_color: line_color = parent_line_color 143 | if node_color == parent_node_color: node_color = parent_node_color 144 | if arrow_color == parent_arrow_color: arrow_color = parent_arrow_color 145 | if arrow_texture == parent_arrow_texture: arrow_texture = parent_arrow_texture 146 | queue_redraw() 147 | 148 | 149 | func toggle_lock(): 150 | pass 151 | 152 | 153 | ## Adds a idx for node connections. 154 | func add_connection(idx: int, connection_array: Array[int]): 155 | connection_array.append(idx) 156 | queue_redraw() 157 | 158 | 159 | ## Removes idx from node's connection_array 160 | func remove_connection(idx: int, connection_array: Array[int]): 161 | connection_array.erase(idx) 162 | queue_redraw() 163 | 164 | 165 | func swap_connection(idx, old_array, new_array): 166 | old_array.erase(idx) 167 | new_array.append(idx) 168 | 169 | 170 | # Returns true/false if the Input/Output array has int value of "idx" 171 | func has_connection(idx: int, connection_array: Array[int]): 172 | return connection_array.has(idx) 173 | 174 | 175 | func extend(): 176 | pass 177 | -------------------------------------------------------------------------------- /addons/tree_maps/README.md: -------------------------------------------------------------------------------- 1 | 2 |

3 | Tree Maps - Graphs and Skill Trees 4 |

5 |

6 | Tree Maps provides useful nodes and functionality to help create graphical maps of tree-like node structures. 7 |
8 | Perfect for skill trees, technology trees, and or dungeon maps. 9 |

10 | image 11 | 12 |
13 |

14 | Godot Asset Library - 15 | Releases 16 |

17 | 18 | # 19 | 20 |

About

21 | One day while trying to make a very large technology tree, I found that I was having trouble creating a system 22 | which would allow me quickly expand and add lots of different upgrades and paths. To simplify this process 23 | I decided to create Tree Maps in order to adress some of the complications behind creating tech/skill trees. 24 |

25 | Currently, this remains a very simple addon, however I plan to continue adding features in order to help with 26 | creating fully fledged skill and technology trees. 27 |

28 | 29 |

Download & Installation

30 | There are two options to install this addon: 31 |
    32 |
  1. Through the built-in AssetLib tab in Godot.
  2. 33 |
  3. Downloading manually, unpack it, and put it in your project's "addons" folder.
  4. 34 |
35 | 36 |

Option 1 - Download through Godot's AssetLib tab

37 | Note: The addon may still be pending in the Asset Library. 38 | 39 |
    40 |
  1. Simply open your Godot project, select the "AssetLib" tab, and search "TreeMap"
  2. 41 |
  3. Select it and download, then install. Afterwards simply enable it in ProjectSettings's Plugins tab.
  4. 42 |
43 | 44 |

Option 2 - Download manually

45 |
    46 |
  1. Go to repository and download a release (tree-maps-addon.zip)
  2. 47 |
  3. Unpack the zip (make sure you don't duplicate the root folder)
  4. 48 |
  5. Simply move the addon (tree-maps) to the addons folder of your project's root (create one if your missing it).
  6. 49 |
50 | 51 | 52 | 53 |
54 |

Usage

55 | 56 | This addon adds two new custom nodes which both inherit from `Node2D`: `TreeMap` and `TreeMapNode` 57 |

58 | 59 | Starting in 2D view, add a new `TreeMap` to your scene, positioned at the origin. 60 | 61 | > Note: Positioning the `TreeMap` node anywhere else will effect drawing of `TreeMapNodes`. 62 | > Will be fixed later. 63 |
64 | 65 | `TreeMap` comes with several custom properties in the Inspector. 66 | By default, these properties will be passed down to any children `TreeMapNode`s. 67 | These properties will effect how `TreeMapNode` childs will be displayed and/or interact. 68 | 69 | From here, you can now add `TreeMapNode` as a child of the `TreeMap`, see [Main Tools](#main-tools) 70 | for the "Add Nodes" tool. 71 | > Note: You can add nodes manually, however you will have to refresh the Scene Tree (Reload scene, or Open and Close the scene). 72 | 73 | > Note: It is highly recommended to have `TreeMapNode` be children of `TreeMap`. 74 | > By continueing without `TreeMap`, there **WILL** be errors. 75 |
76 | 77 | Editing any properties within the "Overrides" section will result in that `TreeMapNode` having its own 78 | property seperate from its parent `TreeMap`. To reset it to its default inherited property, simply 79 | reset the property normally. 80 |

81 | 82 | Upon selecting a `TreeMap` or `TreeMapNode`, you can see in the tool bar at the top will change, 83 | showing some new tool buttons. These will allow you to edit your `TreeMapNode`(s) 84 |
85 | 86 | 87 | 88 |

Main Tools

89 |
90 | Tip: Right click to disable the active main tool.
91 | 92 | > Note: When activating a tool, the currently selected node is your main node, from which tools 93 | > will act from. Selecting another node while your tool is active will make that the target node. 94 | > To select a different node to edit from, simply deactivate the tool, then select your new node 95 | > and reactivate the tool. 96 | 97 | - **Edit Connections**: 98 | Click to create connection. 99 | If there is a existing connection, remove it instead. 100 | If there is a existing connection poiting towards the selected node, swap pointing direction. 101 | - **Add Nodes** - Creates a new `TreeMapNode` at mouse click. 102 | - **Remove Nodes** - Removes the selected node. 103 | 104 | 105 |

Modifiers

106 | These tools change the way Main Tools behave. 107 | 108 | - **Chaining** - selects the targeted node after using a tool (if applicable). 109 | - **Lock/Unlock (WIP)** - disables editing of the selecetd node(s). 110 | 111 |

Miscellaneous

112 | 113 | - **Reset (WIP)** - resets the selected node's properties to the default inherited values. 114 | - **Info (WIP)** - Shows helpful info 115 |

116 | 117 | 118 |
119 |

Examples

120 | 121 | **Demo video** 122 | 123 | https://github.com/user-attachments/assets/fbfc2732-9639-446d-b620-4464e99fa997 124 | 125 | 126 |
127 |

What's Next?

128 | Currently there is a somewhat limited amount of customization options available for TreeMap and TreeMapNode. 129 | 130 | I still plan to continue adding some more customization features along with more helpful tools. I already have several in mind. 131 | 132 | However, if you find that there is a missing feature you want, this is where you can extend the TreeMapNode class and add your own code! 133 | If you think it could be a core feature, feel free to create a issue in the repository. 134 |
135 | 136 |

Planned Features

137 | TreeMap 138 | 139 | - Min/Max line length - prevent node placement or movement within min/max distance of another node. 140 | - NodeInstance - use your own extended node for the "Add Nodes" tool. 141 |
142 |

Potential Features

143 | These features are still being decided on. If enough people really want this, I will consider adding. 144 | 145 | - Bezier curves, and Arcs 146 | -------------------------------------------------------------------------------- /addons/tree_maps/plugin.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends EditorPlugin 3 | 4 | 5 | var tool_buttons = ButtonGroup.new() 6 | 7 | var editor_tool_button = preload("res://addons/tree_maps/buttons/editor_tool_button.tscn") 8 | var editor_tool_button_hbox = HBoxContainer.new() 9 | 10 | var edit_button: Button = editor_tool_button.instantiate() 11 | var add_button: Button = editor_tool_button.instantiate() 12 | var remove_button: Button = editor_tool_button.instantiate() 13 | var chain_button: Button = editor_tool_button.instantiate() 14 | var lock_button: Button = Button.new() 15 | var reset_button: Button = Button.new() 16 | var info_button: Button = Button.new() 17 | #var tools = { 18 | #add = editor_tool_button.instantiate(), 19 | #remove = editor_tool_button.instantiate(), 20 | #} 21 | 22 | var selected_tree_map: TreeMap 23 | 24 | 25 | func _init() -> void: 26 | tool_buttons.allow_unpress = true 27 | tool_buttons.pressed.connect(_on_tool_button_pressed) 28 | _init_tool_buttons() 29 | _init_custom_types() 30 | #main_screen_changed.connect(_on_main_screen_changed) 31 | 32 | 33 | func _enter_tree(): 34 | #add_autoload_singleton("PluginState", "res://addons/tree_maps/plugin_state.gd") 35 | _add_tool_buttons() 36 | 37 | EditorInterface.get_selection().selection_changed.connect( _on_selection_changed ) 38 | #get_tree().node_added.connect( _on_scene_tree_node_added ) 39 | 40 | 41 | func _exit_tree(): 42 | #remove_autoload_singleton("PluginState") 43 | _remove_tool_buttons() 44 | 45 | EditorInterface.get_selection().selection_changed.disconnect( _on_selection_changed ) 46 | #get_tree().node_added.disconnect( _on_scene_tree_node_added ) 47 | 48 | 49 | func _on_main_screen_changed(screen_name): 50 | #if screen_name == "2D": 51 | #viewport_2d_selected = true 52 | #else: 53 | #viewport_2d_selected = false 54 | pass 55 | 56 | 57 | func _has_main_screen(): 58 | return false 59 | 60 | #func _make_visible(visible): 61 | #pass 62 | 63 | #func _get_plugin_name(): 64 | #return "Plugin" 65 | 66 | #func _get_plugin_icon(): 67 | #return EditorInterface.get_editor_theme().get_icon("Node", "EditorIcons") 68 | 69 | 70 | #func _on_scene_tree_node_added(node): 71 | #if node is TreeMap: #or node is TreeMapNode: 72 | #pass 73 | 74 | 75 | func _on_selection_changed(): 76 | var selection = EditorInterface.get_selection().get_transformable_selected_nodes() 77 | var show = false 78 | for node in selection: 79 | if node is TreeMap or node is TreeMapNode: 80 | show = true 81 | break 82 | editor_tool_button_hbox.visible = show 83 | 84 | 85 | func _handles(object: Object) -> bool: 86 | if object is TreeMap or object is TreeMapNode: 87 | if object is TreeMapNode: 88 | selected_tree_map = object.get_parent() 89 | if object is TreeMap: 90 | selected_tree_map = object 91 | # Update tool buttons display to match the selected TreeMap's editing state 92 | if selected_tree_map.edit_state != TreeMap.EditStates.NONE: 93 | tool_buttons.get_buttons()[max(selected_tree_map.edit_state - 1, 0)].button_pressed = true 94 | else: 95 | for b in tool_buttons.get_buttons(): 96 | b.button_pressed = false 97 | chain_button.button_pressed = selected_tree_map.chaining_enabled 98 | return true 99 | else: return false 100 | 101 | 102 | func _forward_canvas_gui_input(event: InputEvent) -> bool: 103 | var intercepted = false 104 | if event is InputEventMouseButton: 105 | if event.button_index == MOUSE_BUTTON_LEFT: 106 | #print("mouse left intercepted") 107 | pass 108 | if event.button_index == MOUSE_BUTTON_RIGHT: 109 | # Disable editing on the selected [TreeMap] on Mouse Right Click 110 | if selected_tree_map.edit_state != TreeMap.EditStates.NONE: 111 | selected_tree_map.edit_state = TreeMap.EditStates.NONE 112 | selected_tree_map.edited_nodes.clear() 113 | tool_buttons.get_pressed_button().button_pressed = false 114 | EditorInterface.get_editor_toaster().push_toast("Editing disabled", EditorToaster.SEVERITY_INFO) 115 | intercepted = true 116 | #print("mouse right intercepted") 117 | return intercepted 118 | 119 | 120 | func editor_add_tool_buttons(): 121 | pass 122 | 123 | 124 | func _init_tool_buttons(): 125 | editor_tool_button_hbox.visible = false 126 | editor_tool_button_hbox.add_child(edit_button) 127 | editor_tool_button_hbox.add_child(add_button) 128 | editor_tool_button_hbox.add_child(remove_button) 129 | editor_tool_button_hbox.add_child(VSeparator.new()) 130 | editor_tool_button_hbox.add_child(chain_button) 131 | editor_tool_button_hbox.add_child(lock_button) 132 | editor_tool_button_hbox.add_child(VSeparator.new()) 133 | editor_tool_button_hbox.add_child(reset_button) 134 | editor_tool_button_hbox.add_child(info_button) 135 | 136 | edit_button.button_group = tool_buttons 137 | add_button.button_group = tool_buttons 138 | remove_button.button_group = tool_buttons 139 | 140 | for b in editor_tool_button_hbox.get_children(): 141 | b.size.x = b.size.y # Make buttons square 142 | 143 | edit_button.icon = EditorInterface.get_editor_theme().get_icon("CurveEdit", "EditorIcons") 144 | edit_button.tooltip_text = "Edit Connections" 145 | 146 | add_button.icon = EditorInterface.get_editor_theme().get_icon("CurveCreate", "EditorIcons") 147 | add_button.tooltip_text = "Add Nodes" 148 | 149 | remove_button.icon = EditorInterface.get_editor_theme().get_icon("CurveDelete", "EditorIcons") 150 | remove_button.tooltip_text = "Remove Nodes" 151 | 152 | chain_button.icon = EditorInterface.get_editor_theme().get_icon("InsertAfter", "EditorIcons") 153 | chain_button.pressed.connect( func(): selected_tree_map.toggle_chaining() ) 154 | chain_button.tooltip_text = "Chaining" 155 | 156 | lock_button.icon = EditorInterface.get_editor_theme().get_icon("Unlock", "EditorIcons") 157 | #chain_button.pressed.connect( func(): selected_tree_map.toggle_chaining() ) 158 | lock_button.tooltip_text = "Lock" 159 | 160 | reset_button.icon = EditorInterface.get_editor_theme().get_icon("RotateLeft", "EditorIcons") 161 | reset_button.tooltip_text = "Reset" 162 | 163 | info_button.icon = EditorInterface.get_editor_theme().get_icon("Info", "EditorIcons") 164 | info_button.tooltip_text = "info" 165 | 166 | 167 | func _on_tool_button_pressed(button): 168 | match button: 169 | edit_button: 170 | selected_tree_map.toggle_editing(button.button_pressed) 171 | add_button: 172 | selected_tree_map.toggle_adding(button.button_pressed) 173 | remove_button: 174 | selected_tree_map.toggle_removing(button.button_pressed) 175 | 176 | if tool_buttons.get_pressed_button() == null: 177 | selected_tree_map.edit_state = TreeMap.EditStates.NONE 178 | 179 | 180 | func _add_tool_buttons(): 181 | add_control_to_container(CONTAINER_CANVAS_EDITOR_MENU, editor_tool_button_hbox) 182 | 183 | 184 | func _remove_tool_buttons(): 185 | remove_control_from_container(CONTAINER_CANVAS_EDITOR_MENU, editor_tool_button_hbox) 186 | 187 | 188 | func _init_custom_types(): 189 | add_custom_type("TreeMap", "Node2D",\ 190 | preload("res://addons/tree_maps/nodes/tree_map.gd"),\ 191 | preload("res://addons/tree_maps/nodes/TreeMap.svg")) 192 | #EditorInterface.get_editor_theme().get_icon("GraphEdit", "EditorIcons")) 193 | add_custom_type("TreeMapNode", "Node2D",\ 194 | preload("res://addons/tree_maps/nodes/tree_map_node.gd"),\ 195 | preload("res://addons/tree_maps/nodes/TreeMapNode.svg")) 196 | #EditorInterface.get_editor_theme().get_icon("GraphElement", "EditorIcons")) 197 | -------------------------------------------------------------------------------- /addons/tree_maps/nodes/tree_map.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | class_name TreeMap 3 | extends Node2D 4 | 5 | 6 | signal notify_cleanup(node) 7 | 8 | enum EditStates { NONE, EDITING, ADDING, REMOVING } 9 | 10 | @export var edit_state: EditStates = EditStates.NONE 11 | @export var chaining_enabled: bool = false 12 | 13 | @export var selected_nodes: Array = [] 14 | @export var edited_nodes: Array[TreeMapNode] = [] 15 | 16 | @export var nodes: Array[Vector2] = [] 17 | 18 | 19 | @export_category("Customization") 20 | @export var node_instance: PackedScene ## (WIP) Specify a custom node type to use instead of the built-in TreeMapNode. 21 | @export var min_length: int = 0 ## (WIP) Prevent placement of nodes within this radius of other nodes. 22 | @export var max_length: int = 0 ## (WIP) Prevent placement of nodes outside this radius of other nodes. 23 | 24 | const default_color = Color.WHITE 25 | const default_arrow_texture = preload("res://addons/tree_maps/icons/arrow_filled.png") 26 | #@export_subgroup("Transforms") 27 | 28 | @export_group("Nodes") 29 | @export var node_color: Color = default_color 30 | @export var node_size: float = 24.0 ## TreeMap only for now 31 | @export_enum("Circle", "Square") var node_shape: String = "Circle" ## (WIP) TreeMap only for now Circle only. 32 | @export var node_texture: Texture2D ## (WIP) TreeMap only for now Overrides node shape. 33 | 34 | @export_group("Lines") 35 | @export var line_color: Color = default_color 36 | @export var line_thickness: float = 10.0 ## TreeMap only for now 37 | @export var line_texture: Texture2D ## (WIP) TreeMap only for now 38 | @export_subgroup("Lines Extra") 39 | #@export var line_border_color: Color 40 | #@export var line_fill_texture: Texture2D 41 | #@export_enum("Normal", "Dashed") var line_style 42 | 43 | 44 | @export_group("Arrows") 45 | @export var arrow_color: Color = default_color 46 | #@export var arrow_border_color: Color 47 | @export var arrow_texture: Texture2D = default_arrow_texture 48 | 49 | # TODO: Properties which are overriden will reset, if its the same as parent when editing parent's properties. 50 | 51 | var setup_properties = [ 52 | "node_color", "node_size", "node_texture", #"node_shape", 53 | "line_color", "line_thickness", 54 | "arrow_color", "arrow_texture" 55 | ] 56 | 57 | func _setup(): 58 | nodes.clear() 59 | for child in get_children(): 60 | if child is TreeMapNode: 61 | nodes.append(child.position) 62 | setup_tree_map_node(child) 63 | 64 | 65 | ## Apply inherited properties to children TreeMapNodes 66 | func setup_tree_map_node(node): 67 | for property in setup_properties: 68 | var parent_value = get(property) 69 | var parent_property = "parent_" + property 70 | # Before updating inherited properties, check if that property was actually inherited. 71 | # If it was, use inheited value as the default revert value for that property. 72 | if node.get(property) == node.get(parent_property): 73 | node.set(parent_property, parent_value) 74 | node.set(property, node.property_get_revert(property)) 75 | # 76 | else: 77 | node.set(parent_property, get(property)) 78 | 79 | ## Before updating inherited properties, check if that property was actually inherited. 80 | #if node.line_color == node.parent_line_color: 81 | #node.parent_line_color = line_color 82 | #node.line_color = node.property_get_revert("line_color") # set to parent_line_color 83 | #else: 84 | #node.parent_line_color = line_color 85 | 86 | #if node.node_color == node.parent_node_color: 87 | #node.parent_node_color = node_color 88 | #node.node_color = node.property_get_revert("node_color") 89 | #else: 90 | #node.parent_node_color = node_color 91 | node.apply_properties() 92 | 93 | 94 | func _enter_tree() -> void: 95 | if Engine.is_editor_hint(): 96 | #set_notify_transform(true) 97 | set_physics_process(true) 98 | EditorInterface.get_inspector().property_edited.connect( _on_property_edited ) 99 | EditorInterface.get_selection().selection_changed.connect( _on_selection_changed ) 100 | child_entered_tree.connect( _on_child_entered_tree ) 101 | child_exiting_tree.connect( _on_child_exiting_tree ) 102 | _setup() 103 | 104 | 105 | func _exit_tree() -> void: 106 | if Engine.is_editor_hint(): 107 | EditorInterface.get_inspector().property_edited.disconnect( _on_property_edited ) 108 | EditorInterface.get_selection().selection_changed.disconnect( _on_selection_changed ) 109 | child_entered_tree.disconnect( _on_child_entered_tree ) 110 | child_exiting_tree.disconnect( _on_child_exiting_tree ) 111 | nodes.clear() 112 | 113 | 114 | ## https://forum.godotengine.org/t/in-godot-how-can-i-listen-for-changes-in-the-properties-of-nodes-within-the-editor-additionally-how-can-this-be-used-in-a-plugin/35330/4 115 | #func _notification(what): 116 | #if what == NOTIFICATION_TRANSFORM_CHANGED: 117 | #pass 118 | 119 | 120 | func _on_child_entered_tree(child: Node) -> void: 121 | if child is TreeMapNode: 122 | child.moved.connect( _on_node_moved ) 123 | #child.connections_edited.connect( _on_node_connections_edited ) 124 | # Adjust saved indexes for child items' connections 125 | 126 | 127 | func _on_child_exiting_tree(child: Node) -> void: 128 | if child is TreeMapNode: 129 | child.moved.disconnect( _on_node_moved ) 130 | #child.connections_edited.disconnect( _on_node_connections_edited ) 131 | #notify_cleanup.emit() 132 | # Adjust saved indexes for child items' connections 133 | 134 | 135 | func _physics_process(delta: float) -> void: 136 | #if viewport_2d_selected: 137 | #if Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT): 138 | #print("ASKDM") 139 | pass 140 | 141 | 142 | # Refresh properties on children 143 | func _on_property_edited(property) -> void: 144 | if EditorInterface.get_inspector().get_edited_object() == self: 145 | #match property: 146 | #"line_color", "node_color", "arrow_color", "arrow_texture": 147 | for prop in setup_properties: 148 | if property == prop: 149 | # Update childrens' properties 150 | for i in get_children(): 151 | if i is TreeMapNode: 152 | setup_tree_map_node(i) 153 | #i.apply_properties() 154 | 155 | 156 | func _on_selection_changed() -> void: 157 | selected_nodes = EditorInterface.get_selection().get_transformable_selected_nodes() 158 | var tree_map_nodes = get_tree_map_nodes_from(selected_nodes) 159 | 160 | match edit_state: 161 | EditStates.EDITING: 162 | # [Check for nodes to connect FROM] and [Check for nodes to connect TO] 163 | if edited_nodes.size() >= 1 and tree_map_nodes.size() >= 1: 164 | for node in edited_nodes: 165 | var target: TreeMapNode = tree_map_nodes[0] 166 | # If [Node] does not have [Target] as a output (not connected). 167 | if not node.has_connection(target.get_index(), node.outputs): 168 | # If [Target] does not have [Node] as a output 169 | if not target.outputs.has(node.get_index()): 170 | if not node == target: 171 | connnect_nodes([node], target) 172 | else: # swap connection directions 173 | node.swap_connection(target.get_index(), node.inputs, node.outputs) 174 | target.swap_connection(node.get_index(), target.outputs, target.inputs) 175 | node.queue_redraw() # Refresh the origin node 176 | target.queue_redraw() 177 | else: # if existing connetion, remove connection 178 | disconnect_nodes([node], target) 179 | if chaining_enabled: 180 | edit_node(target) # select targeted node if chaining is enabled. 181 | else: select_node(node) # select old node if chaining is disabled. 182 | EditStates.ADDING: 183 | # TODO: if TreeMap is selected, add nodes without connections 184 | # TODO: Fix node not applyning inherited colors 185 | if tree_map_nodes.is_empty(): # Empty spot selected 186 | var new_node = create_tree_map_node() 187 | setup_tree_map_node(new_node) 188 | #new_node.apply_properties() 189 | if chaining_enabled: 190 | for node in edited_nodes: 191 | connnect_nodes([node], new_node) 192 | edit_node(new_node) # select newly created node if chaining is enabled. 193 | EditStates.REMOVING: 194 | if !tree_map_nodes.is_empty(): 195 | for target in tree_map_nodes: # Remove all selected nodes 196 | select_node(target.get_parent()) # Reselect parent TreeMap to make removing nodes clean. 197 | remove_tree_map_node(target).queue_free() 198 | 199 | 200 | func _on_node_moved(node): 201 | #print(node) 202 | node.queue_redraw() 203 | for i in node.inputs: 204 | node = get_input_output_node(i) 205 | if node: node.queue_redraw() 206 | nodes[node.get_index()] = node.position 207 | queue_redraw() 208 | 209 | 210 | func toggle_editing(state: bool): 211 | if state == true: 212 | # Add currently selected TreeMapNodes to editing selection 213 | for i in EditorInterface.get_selection().get_transformable_selected_nodes(): 214 | if i is TreeMapNode: self.edited_nodes.append(i) 215 | edit_state = TreeMap.EditStates.EDITING 216 | EditorInterface.get_editor_toaster().push_toast("Editing enabled", EditorToaster.SEVERITY_INFO) 217 | else: 218 | edited_nodes.clear() 219 | EditorInterface.get_editor_toaster().push_toast("Editing disabled", EditorToaster.SEVERITY_INFO) 220 | 221 | 222 | func toggle_adding(state: bool): 223 | if state == true: 224 | # Add currently selected TreeMapNodes to editing selection 225 | for i in EditorInterface.get_selection().get_transformable_selected_nodes(): 226 | if i is TreeMapNode: self.edited_nodes.append(i) 227 | edit_state = TreeMap.EditStates.ADDING 228 | else: 229 | EditorInterface.get_editor_toaster().push_toast("Adding disabled", EditorToaster.SEVERITY_INFO) 230 | 231 | 232 | func toggle_removing(state: bool): 233 | if state == true: 234 | edit_state = TreeMap.EditStates.REMOVING 235 | select_node(self) # Select parent TreeMap to make removing nodes clean. 236 | else: 237 | EditorInterface.get_editor_toaster().push_toast("Removing disabled", EditorToaster.SEVERITY_INFO) 238 | 239 | 240 | func toggle_chaining(): 241 | chaining_enabled = !chaining_enabled 242 | if chaining_enabled: 243 | EditorInterface.get_editor_toaster().push_toast("Chaining enabled", EditorToaster.SEVERITY_INFO) 244 | else: 245 | EditorInterface.get_editor_toaster().push_toast("Chaining disabled", EditorToaster.SEVERITY_INFO) 246 | 247 | 248 | func create_tree_map_node() -> TreeMapNode: 249 | var tree_map_node = TreeMapNode.new() 250 | add_child(tree_map_node) 251 | tree_map_node.global_position = get_global_mouse_position() 252 | tree_map_node.owner = EditorInterface.get_edited_scene_root() 253 | tree_map_node.name = tree_map_node.get_script().get_global_name() 254 | nodes.append(tree_map_node.position) 255 | return tree_map_node 256 | 257 | 258 | func remove_tree_map_node(node) -> TreeMapNode: 259 | var idx = node.get_index() 260 | nodes.erase(node.position) 261 | for i in node.inputs: # Remove idx from output connection lists 262 | get_child(i).outputs.erase(idx) 263 | get_child(i).queue_redraw() 264 | for i in node.outputs: # Remove idx from input connection lists 265 | get_child(i).inputs.erase(idx) 266 | get_child(i).queue_redraw() 267 | remove_child(node) 268 | return node 269 | 270 | 271 | func connnect_nodes(connecting_nodes: Array[TreeMapNode], target_node: TreeMapNode): 272 | for connecting_node in connecting_nodes: 273 | connecting_node.add_connection(target_node.get_index(), connecting_node.outputs) 274 | target_node.add_connection(connecting_node.get_index(), target_node.inputs) 275 | 276 | 277 | func disconnect_nodes(connecting_nodes: Array[TreeMapNode], target_node: TreeMapNode): 278 | for connecting_node in connecting_nodes: 279 | connecting_node.remove_connection(target_node.get_index(), connecting_node.outputs) 280 | target_node.remove_connection(connecting_node.get_index(), target_node.inputs) 281 | 282 | 283 | func select_node(node): 284 | EditorInterface.get_selection().clear() 285 | EditorInterface.get_selection().add_node(node) 286 | 287 | 288 | func select_nodes(nodes: Array): 289 | EditorInterface.get_selection().clear() 290 | for node in nodes: 291 | EditorInterface.get_selection().add_node(node) 292 | 293 | 294 | func edit_node(node): 295 | EditorInterface.get_selection().clear() 296 | EditorInterface.get_selection().add_node(node) 297 | edited_nodes.clear() 298 | edited_nodes.append(node) 299 | 300 | 301 | #func swap_node_connection(idx, old_array, new_array): 302 | #old_array.erase(idx) 303 | #new_array.append(idx) 304 | 305 | 306 | func get_tree_map_nodes_from(array: Array[Node]) -> Array[TreeMapNode]: 307 | var tree_map_nodes: Array[TreeMapNode] = [] 308 | for node in array: 309 | if node is TreeMapNode: 310 | tree_map_nodes.append(node) 311 | return tree_map_nodes 312 | 313 | 314 | func get_last_selected_node() -> TreeMapNode: 315 | var last_selection 316 | var selected_nodes = EditorInterface.get_selection().get_transformable_selected_nodes() 317 | for i in selected_nodes.size(): 318 | last_selection = selected_nodes[-i-1] 319 | if last_selection is TreeMapNode: 320 | break 321 | return last_selection 322 | 323 | 324 | func get_input_output_node(idx): 325 | if self.get_child_count() >= idx + 1: 326 | return self.get_child(idx) 327 | --------------------------------------------------------------------------------