--- /dev/null
+MIT License
+
+Copyright (c) 2021 Mahmood Heidari
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
--- /dev/null
+*.import
\ No newline at end of file
--- /dev/null
+<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m3 1c-1.1046 0-2 .8954-2 2v1h14v-1c0-1.1046-.89543-2-2-2zm9 1h1v1h-1zm-11 3v8c0 1.1046.89543 2 2 2h10c1.1046 0 2-.8954 2-2v-8zm3 2h3c1 0 1 2 2 2h3v4h-8z" fill="#e0e0e0"/></svg>
--- /dev/null
+<svg height="16" viewBox="0 0 16 15.999999" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m3.3333333 1c-1.2887 0-2.3333333 1.0446683-2.3333333 2.3333333v9.3333337c0 1.2887 1.0446683 2.333333 2.3333333 2.333333h9.3333337c1.2887 0 2.333333-1.044668 2.333333-2.333333v-9.3333337c0-1.2887-1.044668-2.3333333-2.333333-2.3333333z" fill="#699ce8" stroke-width="1.16667"/><path d="m11.500773 3.7343508-5.6117507 5.6117502-1.7045017-1.6814543-1.4992276 1.4992276 3.2037293 3.1806817 7.1109777-7.1109775z" fill="#fff" stroke-width="1.06023"/></svg>
--- /dev/null
+<svg enable-background="new 0 0 16 16" height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m3.333 1c-1.289 0-2.333 1.045-2.333 2.333v9.333c0 1.289 1.044 2.334 2.333 2.334h9.333c1.289 0 2.334-1.045 2.334-2.334v-9.333c0-1.289-1.045-2.333-2.334-2.333z" fill="#808080"/><path d="m11.501 3.734-5.612 5.612-1.704-1.681-1.5 1.5 3.204 3.181 7.111-7.113z" fill="#b3b3b3"/></svg>
--- /dev/null
+<svg height="12" viewBox="0 0 12 12" width="12" xmlns="http://www.w3.org/2000/svg"><path d="m10 1043.4c-.26378.01-.5144.1165-.69726.3067l-3.293 3.2929-3.293-3.2929c-.18826-.1936-.44679-.3028-.7168-.3028-.89742.0002-1.3404 1.0909-.69727 1.7168l4 4c.39053.3904 1.0235.3904 1.4141 0l4-4c.65734-.6321.19491-1.7422-.7168-1.7207z" fill="#fff" fill-opacity=".78431" transform="translate(0 -1040.4)"/></svg>
--- /dev/null
+<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m8 0a2 2 0 0 0 -2 2 2 2 0 0 0 2 2 2 2 0 0 0 2-2 2 2 0 0 0 -2-2zm0 6a2 2 0 0 0 -2 2 2 2 0 0 0 2 2 2 2 0 0 0 2-2 2 2 0 0 0 -2-2zm0 6a2 2 0 0 0 -2 2 2 2 0 0 0 2 2 2 2 0 0 0 2-2 2 2 0 0 0 -2-2z" fill="#e0e0e0"/></svg>
--- /dev/null
+<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m2.9902 1.9902a1.0001 1.0001 0 0 0 -.69727 1.7168l4.293 4.293-4.293 4.293a1.0001 1.0001 0 1 0 1.4141 1.4141l4.293-4.293 4.293 4.293a1.0001 1.0001 0 1 0 1.4141-1.4141l-4.293-4.293 4.293-4.293a1.0001 1.0001 0 0 0 -.72656-1.7148 1.0001 1.0001 0 0 0 -.6875.30078l-4.293 4.293-4.293-4.293a1.0001 1.0001 0 0 0 -.7168-.30273z" fill="#ff5f5f" fill-rule="evenodd"/></svg>
--- /dev/null
+@tool
+extends HBoxContainer
+
+@onready var _root: Node = self
+
+# Finds and fills `_root` variable properly
+func _ready() -> void:
+ while true:
+ if _root == null:
+ ## If we are here, we are running in editor, so get out
+ break
+ elif _root.name == "Scene Manager" || _root.name == "menu":
+ break
+ _root = _root.get_parent()
+
+# Sets address of current ignore item
+func set_address(addr: String) -> void:
+ get_node("address").text = addr
+ name = addr
+
+# Returns address of current ignore item
+func get_address() -> String:
+ return get_node("address").text
+
+# Remove Button
+func _on_remove_button_up() -> void:
+ _root.emit_signal("delete_ignore_child", self)
--- /dev/null
+[gd_scene load_steps=3 format=3 uid="uid://ciaqe7l3hugns"]
+
+[ext_resource type="Texture2D" uid="uid://dw322nmqpqwfq" path="res://addons/scene_manager/icons/ImportFail.svg" id="1"]
+[ext_resource type="Script" path="res://addons/scene_manager/ignore_item.gd" id="2"]
+
+[node name="item" type="HBoxContainer"]
+offset_top = 24.0
+offset_right = 280.0
+offset_bottom = 48.0
+script = ExtResource("2")
+
+[node name="remove_at" type="Button" parent="."]
+custom_minimum_size = Vector2(28, 28)
+layout_mode = 2
+icon = ExtResource("1")
+
+[node name="address" type="LineEdit" parent="."]
+layout_mode = 2
+size_flags_horizontal = 3
+editable = false
+
+[connection signal="button_up" from="remove_at" to="." method="_on_remove_button_up"]
--- /dev/null
+class_name ItemSetting
+
+var visibility: bool = true
+var categorized: bool = false
+var subsection: String = ""
+
+func _init(visibility = true, categorized = false, subsection = "") -> void:
+ self.visibility = visibility
+ self.categorized = categorized
+ self.subsection = subsection
+
+func as_dictionary() -> Dictionary:
+ return {
+ "visibility": self.visibility,
+ "subsection": self.subsection,
+ }
+
+static func dictionary_to_item_setting(input: Dictionary) -> ItemSetting:
+ var visibility = input["visibility"] if input.has("visibility") else true
+ var subsection = input["subsection"] if input.has("subsection") else ""
+ return ItemSetting.new(visibility, false, subsection)
+
+static func default() -> ItemSetting:
+ return ItemSetting.new()
+
+func duplicate() -> ItemSetting:
+ return new(self.visibility, self.categorized, self.subsection)
--- /dev/null
+[gd_scene format=2]
+
+[node name="value" type="Label"]
+offset_left = 165.0
+offset_right = 280.0
+offset_bottom = 20.0
+size_flags_horizontal = 3
+size_flags_vertical = 1
+text = "Value:"
+valign = 1
--- /dev/null
+@tool
+extends MarginContainer
+
+# Project Settings property name
+const SETTINGS_PROPERTY_NAME := "scene_manager/scenes/scenes_path"
+# paths
+const PATH: String = "res://addons/scene_manager/scenes.gd"
+const ROOT_ADDRESS = "res://"
+# prefile
+const comment: String = "#\n# Please do not edit anything in this script\n#\n# Just use the editor to change everything you want\n#\n"
+const extend_part: String = "extends Node\n\n"
+const var_part: String = "var scenes: Dictionary = "
+# scene item, ignore item
+const _ignore_item = preload("res://addons/scene_manager/ignore_item.tscn")
+const _scene_list_item = preload("res://addons/scene_manager/scene_list.tscn")
+# icons
+const _hide_button_checked = preload("res://addons/scene_manager/icons/GuiChecked.svg")
+const _hide_button_unchecked = preload("res://addons/scene_manager/icons/GuiCheckedDisabled.svg")
+@onready var _ignore_list: Node = self.find_child("ignore_list")
+# add save, refresh
+@onready var _save_button: Button = self.find_child("save")
+@onready var _refresh_button: Button = self.find_child("refresh")
+# add list
+@onready var _add_subsection_button: Button = self.find_child("add_subsection")
+@onready var _add_section_button: Button = self.find_child("add_section")
+@onready var _section_name_line_edit: LineEdit = self.find_child("section_name")
+# add ignore
+@onready var _address_line_edit: LineEdit = self.find_child("address")
+@onready var _file_dialog: FileDialog = self.find_child("file_dialog")
+@onready var _hide_button: Button = self.find_child("hide")
+@onready var _add_button: Button = self.find_child("add")
+# containers
+@onready var _tab_container: TabContainer = self.find_child("tab_container")
+@onready var _ignores_container: Node = self.find_child("ignores")
+# generals
+@onready var _accept_dialog: AcceptDialog = self.find_child("accept_dialog")
+
+# A dictionary which contains every scenes exact addresses as key and an array
+# assigned as values which categories every section name the scene is part of
+#
+# Example: { "res://demo/scene3.tscn": ["Character", "Menu"] }
+var _sections: Dictionary = {}
+var reserved_keys: Array = ["back", "null", "ignore", "refresh",
+ "reload", "restart", "exit", "quit"]
+
+signal delete_ignore_child(node)
+
+# Refreshes the whole UI
+func _ready() -> void:
+ _on_refresh_button_up()
+ self.connect("delete_ignore_child",Callable(self,"_on_delete_ignore_child"))
+
+# Returns absolute current working directory
+func _absolute_current_working_directory() -> String:
+ return ProjectSettings.globalize_path(EditorPlugin.new().get_current_directory())
+
+# Merges two dictionaries together
+func _merge_dict(dest: Dictionary, source: Dictionary) -> void:
+ for key in source:
+ if dest.has(key):
+ var dest_value = dest[key]
+ var source_value = source[key]
+ if typeof(dest_value) == TYPE_DICTIONARY:
+ if typeof(source_value) == TYPE_DICTIONARY:
+ _merge_dict(dest_value, source_value)
+ else:
+ dest[key] = source_value
+ else:
+ dest[key] = source_value
+ else:
+ dest[key] = source[key]
+
+# Returns names of all lists from UI
+func get_all_lists_names_except(excepts: Array = [""]) -> Array:
+ var arr: Array = []
+ for i in range(len(excepts)):
+ excepts[i] = excepts[i].capitalize()
+ for node in _get_lists_nodes():
+ if node.name in excepts:
+ continue
+ arr.append(node.name)
+ return arr
+
+# Returns names of all sublists from UI and active tab
+func get_all_sublists_names_except(excepts: Array = [""]) -> Array:
+ var section = _tab_container.get_child(_tab_container.current_tab)
+ return section.get_all_sublists()
+
+# Shows a dialog message at the middle of screen
+func show_message(title: String, description: String) -> void:
+ _accept_dialog.title = title
+ _accept_dialog.dialog_text = description
+ _accept_dialog.popup_centered(Vector2(400, 100))
+
+# Returns all scenes in current and sub folders of `root_path` address
+func _get_scenes(root_path: String, ignores: Array) -> Dictionary:
+ var files: Dictionary = {}
+ var folders: Array = []
+ var dir = DirAccess.open(root_path)
+ var original_root_path = root_path
+ if root_path[len(root_path) - 1] != "/":
+ root_path = root_path + "/"
+ if !(original_root_path in ignores) && dir:
+ dir.list_dir_begin() # TODOGODOT4 fill missing arguments https://github.com/godotengine/godot/pull/40547
+
+ if dir.file_exists(root_path + ".gdignore"):
+ return files
+ while true:
+ var file_folder = dir.get_next()
+ var exact_address = root_path + file_folder
+ if file_folder == "":
+ break
+ elif dir.current_is_dir():
+ folders.append(file_folder)
+ elif file_folder.get_extension() == "tscn" && !(exact_address in ignores):
+ files[file_folder.replace("."+file_folder.get_extension(), "")] = exact_address
+
+ dir.list_dir_end()
+
+ for folder in folders:
+ var new_files: Dictionary = _get_scenes(root_path + folder, ignores)
+ if len(new_files) != 0:
+ _merge_dict(files, new_files)
+ else:
+ if !(original_root_path in ignores):
+ # If `root_path` was really a file and not a folder, we know the reason and
+ # propably this is comming from `_on_delete_ignore_child`, so just add it to list
+ if (!FileAccess.file_exists(original_root_path)):
+ print("Couldn't open ", original_root_path)
+ else:
+ var splits = original_root_path.split("/", false)
+ var file = splits[len(splits) - 1]
+ if file.get_extension() == "tscn":
+ files[file.replace("."+file.get_extension(), "")] = original_root_path
+
+ return files
+
+# Clears scenes inside a UI list
+func _clear_scenes_list(name: String) -> void:
+ var list: Node = _get_one_list_node_by_name(name)
+ if list != null:
+ list.clear_list()
+
+# Clears scenes inside all UI lists
+func _clear_all_lists() -> void:
+ _sections = {}
+ for list in _get_lists_nodes():
+ list.clear_list()
+
+# Removes all tabs in scene manager
+func _delete_all_tabs() -> void:
+ for node in _get_lists_nodes():
+ node.free()
+
+# Returns nodes of all section lists from UI in `Scene Manager` tool
+func _get_lists_nodes() -> Array:
+ var arr: Array = []
+ for i in range(_tab_container.get_child_count()):
+ arr.append(_tab_container.get_child(i))
+ return arr
+
+# Returns node of a specific list in UI
+func _get_one_list_node_by_name(name: String) -> Node:
+ for node in _get_lists_nodes():
+ if name.capitalize() == node.name:
+ return node
+ return null
+
+# Removes a scene from a specific list
+func remove_scene_from_list(section_name: String, scene_name: String, scene_address: String) -> void:
+ var list: Node = _get_one_list_node_by_name(section_name)
+ list.remove_item(scene_name, scene_address)
+ _section_remove(scene_address, section_name)
+
+ # Removes and add in `All` section too so that it updates its place in list
+ var all_list = _get_one_list_node_by_name("All")
+ var setting = all_list.get_node_by_scene_address(scene_address).get_setting()
+ all_list.remove_item(scene_name, scene_address)
+ setting.categorized = has_sections(scene_address)
+ all_list.add_item(scene_name, scene_address, setting)
+
+# Adds an item to a list
+#
+# Used mainly in this script
+func _add_scene_to_list(list_name: String, scene_name: String, scene_address: String, setting :ItemSetting) -> void:
+ var list: Node = _get_one_list_node_by_name(list_name)
+ list.add_item(scene_name, scene_address, setting)
+ _sections_add(scene_address, list_name)
+
+# Adds an item to a list
+#
+# This function is used in `scene_item.gd` script and plus doing what it is supposed
+# to do, removes and again adds the item in `All` section so that it can be placed
+# in currect place in currect section
+func add_scene_to_list(list_name: String, scene_name: String, scene_address: String, setting :ItemSetting) -> void:
+ _add_scene_to_list(list_name, scene_name, scene_address, setting)
+
+ # Removes and add in `All` section too so that it updates its place in list
+ var all_list = _get_one_list_node_by_name("All")
+ setting = all_list.get_node_by_scene_address(scene_address).get_setting()
+ all_list.remove_item(scene_name, scene_address)
+ setting.categorized = has_sections(scene_address)
+ all_list.add_item(scene_name, scene_address, setting)
+
+# Adds an address to ignore list
+func _add_ignore_item(address: String) -> void:
+ var item = _ignore_item.instantiate()
+ item.set_address(address)
+ _ignore_list.add_child(item)
+
+# Appends all scenes into their assigned UI lists
+#
+# This function gets called just from `_on_delete_ignore_child`
+func _append_scenes(scenes: Dictionary) -> void:
+ _get_one_list_node_by_name("All").append_scenes(scenes)
+ for list in _get_lists_nodes():
+ if list.name == "All":
+ continue
+ for key in scenes:
+ if list.name in get_sections(scenes[key]):
+ list.add_item(key, scenes[key], ItemSetting.default())
+
+# Clears all tabs, UI lists and ignore list
+func _clear_all() -> void:
+ _delete_all_tabs()
+ _clear_all_lists()
+ _clear_ignore_list()
+
+# Reloads all scenes in UI and in this script
+func _reload_scenes() -> void:
+ var data: Dictionary = _load_scenes()
+ var scenes: Dictionary = _get_scenes(ROOT_ADDRESS, _load_ignores())
+ var scenes_values: Array = scenes.values()
+ # Reloads all scenes in `Scenes` script in UI and in this script
+ for key in data:
+ var scene = data[key]
+ var keys = scene.keys()
+ assert (("value" in keys) && ("sections" in keys), "Scene Manager Error: this format is not supported. Every scene item has to have 'value' and 'sections' field inside them.'")
+ if !(scene["value"] in scenes_values):
+ continue
+ for section in scene["sections"]:
+ var setting: ItemSetting = null
+ if "settings" in keys && section in scene["settings"].keys():
+ setting = ItemSetting.dictionary_to_item_setting(scene["settings"][section])
+ else:
+ setting = ItemSetting.default()
+ _sections_add(scene["value"], section)
+ _add_scene_to_list(section, key, scene["value"], setting)
+ var setting: ItemSetting = null
+ if "settings" in keys && "All" in scene["settings"].keys():
+ setting = ItemSetting.dictionary_to_item_setting(scene["settings"]["All"])
+ else:
+ setting = ItemSetting.default()
+ setting.categorized = has_sections(scene["value"])
+ _add_scene_to_list("All", key, scene["value"], setting)
+
+ # Add scenes that are new and are not into `Scenes` script
+ var data_values: Array = []
+ var data_dics = data.values()
+ if data:
+ for i in range(len(data_dics)):
+ data_values.append(data_dics[i]["value"])
+ for key in scenes:
+ if !(scenes[key] in data_values):
+ var setting = ItemSetting.default()
+ _add_scene_to_list("All", key, scenes[key], setting)
+
+# Reloads ignores list in UI and in this script
+func _reload_ignores() -> void:
+ var ignores: Array = _load_ignores()
+ _set_ignores(ignores)
+
+# Reloads tabs in UI
+func _reload_tabs() -> void:
+ var sections: Array = _load_sections()
+ if _get_one_list_node_by_name("All") == null:
+ _add_scene_list("All")
+ for section in sections:
+ _add_scene_list(section)
+
+# Refresh button
+func _on_refresh_button_up() -> void:
+ _clear_all()
+ _reload_tabs()
+ _reload_scenes()
+ _reload_ignores()
+
+# `_sections` variable Manager
+
+# Adds passed `section_name` to array value of passed `scene_address` key in `_sections` variable
+func _sections_add(scene_address: String, section_name: String) -> void:
+ if section_name == "All":
+ return
+ if !_sections.has(scene_address):
+ _sections[scene_address] = []
+ if !(section_name in _sections[scene_address]):
+ _sections[scene_address].append(section_name)
+
+# Removes passed `section_name` from array value of passed `scene_address` key
+func _section_remove(scene_address: String, section_name: String) -> void:
+ if !_sections.has(scene_address):
+ return
+ if section_name in _sections[scene_address]:
+ _sections[scene_address].erase(section_name)
+ if len(_sections[scene_address]) == 0:
+ _sections.erase(scene_address)
+
+# Returns all sections of passed `scene_address`
+func get_sections(scene_address: String) -> Array:
+ if !_sections.has(scene_address):
+ return []
+ return _sections[scene_address]
+
+# Returns true or false if passed `scene_address` has sections
+func has_sections(scene_address: String) -> bool:
+ return _sections.keys().has(scene_address) && _sections[scene_address] != []
+
+# Cleans `_sections` variable from `All` section
+func _clean_sections() -> void:
+ var scenes: Array = get_all_lists_names_except(["All"])
+ for key in _sections:
+ var will_be_deleted: Array = []
+ for section in _sections[key]:
+ if !(section in scenes):
+ will_be_deleted.append(section)
+ for section in will_be_deleted:
+ _sections[key].erase(section)
+
+# End Of `_sections` variable Manager
+
+# Gets called by other nodes in UI
+#
+# Updates name of all scene_key
+func update_all_scene_with_key(scene_key: String, scene_new_key: String, value: String, setting: ItemSetting, except_list: Array = []):
+ for list in _get_lists_nodes():
+ if list not in except_list:
+ list.update_scene_with_key(scene_key, scene_new_key, value, setting)
+
+# Checks for duplications in scenes of lists
+func check_duplication():
+ var list: Array = _get_one_list_node_by_name("All").check_duplication()
+ for node in _get_lists_nodes():
+ node.set_reset_theme_for_all()
+ if list:
+ node.set_duplicate_theme(list)
+
+# Removes `_ignore_list` and `_sections` keys from passed dictionary so that
+# just scene names remain in returned dictionary
+func _remove_ignore_list_and_sections_from_dic(dic: Dictionary) -> Dictionary:
+ dic.erase("_ignore_list")
+ dic.erase("_sections")
+ return dic
+
+# Saves all data in `scenes` variable of `scenes.gd` file
+func _save_all(data: Dictionary) -> void:
+ var file := FileAccess.open(ProjectSettings.get_setting(SETTINGS_PROPERTY_NAME, PATH), FileAccess.WRITE)
+ var write_data: String = comment + extend_part + var_part + JSON.new().stringify(data) + "\n"
+ file.store_string(write_data)
+
+# Returns all data in `scenes` variable of `scenes.gd` file
+func _load_all() -> Dictionary:
+ var data: Dictionary = {}
+
+ if _file_exists(ProjectSettings.get_setting(SETTINGS_PROPERTY_NAME, PATH)):
+ var file := FileAccess.open(ProjectSettings.get_setting(SETTINGS_PROPERTY_NAME, PATH), FileAccess.READ)
+ var string: String = file.get_as_text()
+ string = string.substr(string.find("var"), len(string)).replace(var_part, "").strip_escapes()
+
+ var json = JSON.new()
+ var err = json.parse(string)
+ assert (err == OK, "Scene Manager Error: `scenes.gd` File is corrupted.")
+ data = json.data
+ return data
+
+# Loads and returns just scenes from `scenes` variable of `scenes.gd` file
+func _load_scenes() -> Dictionary:
+ return _remove_ignore_list_and_sections_from_dic(_load_all())
+
+# Loads and returns just array value of `_ignore_list` key from `scenes` variable of `scenes.gd` file
+func _load_ignores() -> Array:
+ var dic: Dictionary = _load_all()
+ if dic.has("_ignore_list"):
+ return dic["_ignore_list"]
+ return []
+
+# Loads and returns just array value of `_sections` key from `scenes` variable of `scenes.gd` file
+func _load_sections() -> Array:
+ var dic: Dictionary = _load_all()
+ if dic.has("_sections"):
+ return dic["_sections"]
+ return []
+
+# Returns true if a file in a specified address exist
+func _file_exists(address: String) -> bool:
+ return FileAccess.file_exists(address)
+
+# Returns all scenes data from UI view in a dictionary
+func _get_scenes_from_ui() -> Dictionary:
+ var list: Node = _get_one_list_node_by_name("All")
+ var data: Dictionary = {}
+ for node in list.get_list_nodes():
+ var value = node.get_value()
+ var sections = get_sections(value)
+ var settings = {}
+ for section in sections:
+ var li = _get_one_list_node_by_name(section)
+ var specific_node = li.get_node_by_scene_address(value)
+ var setting = specific_node.get_setting()
+ settings[section] = setting.as_dictionary()
+ var setting = node.get_setting()
+ settings["All"] = setting.as_dictionary()
+ data[node.get_key()] = {
+ "value": value,
+ "sections": sections,
+ "settings": settings,
+ }
+ return data
+
+# Returns all scenes nodes from `All` UI list and returns in an array
+#
+# Unused method
+func _get_scene_nodes_from_view() -> Array:
+ var list: Node = _get_one_list_node_by_name("All")
+ var nodes: Array = []
+ for i in range(list.get_child_count()):
+ if i == 0: continue
+ var node: Node = list.get_child(i)
+ nodes.append(node)
+ return nodes
+
+# Save button
+func _on_save_button_up():
+ _clean_sections()
+ var dic: Dictionary = _get_scenes_from_ui()
+ dic["_ignore_list"] = _get_ignores_in_ignore_ui()
+ dic["_sections"] = get_all_lists_names_except(["All"])
+ _save_all(dic)
+ _on_refresh_button_up()
+
+# Returns array of ignore nodes from UI view
+func _get_nodes_in_ignore_ui() -> Array:
+ var arr: Array = []
+ for i in range(_ignore_list.get_child_count()):
+ if i == 0: continue
+ arr.append(_ignore_list.get_child(i))
+ return arr
+
+# Returns array of addresses to ignore
+func _get_ignores_in_ignore_ui() -> Array:
+ var arr: Array = []
+ for node in _get_nodes_in_ignore_ui():
+ arr.append(node.get_address())
+ return arr
+
+# Sets current passed list of ignores into UI instead of others
+func _set_ignores(list :Array) -> void:
+ _clear_ignore_list()
+ for text in list:
+ _add_ignore_item(text)
+
+# Clears ignores from UI
+func _clear_ignore_list() -> void:
+ for node in _get_nodes_in_ignore_ui():
+ node.free()
+
+# Returns true if passed address exists in ignore list
+func _ignore_exists_in_list(address: String) -> bool:
+ for node in _get_nodes_in_ignore_ui():
+ if node.get_address() == address:
+ return true
+ return false
+
+# Removes scenes begin with a specific text in all lists
+func _remove_scenes_begin_with(text: String):
+ for node in _get_lists_nodes():
+ node.remove_items_begins_with(text)
+
+# Ignore list Add button up
+func _on_add_button_up():
+ if _ignore_exists_in_list(_address_line_edit.text):
+ _address_line_edit.text = ""
+ return
+ _add_ignore_item(_address_line_edit.text)
+ _remove_scenes_begin_with(_address_line_edit.text)
+ _address_line_edit.text = ""
+ _add_button.disabled = true
+
+# Pops up file dialog to select a ignore folder
+func _on_file_dialog_button_button_up():
+ _file_dialog.popup_centered(Vector2(600, 600))
+
+# When a file or a dir selects by file dialog
+func _on_file_dialog_dir_file_selected(path):
+ _address_line_edit.text = path
+ _on_address_text_changed(path)
+
+# When an ignore item remove button clicks
+func _on_delete_ignore_child(node: Node) -> void:
+ var address: String = node.get_address()
+ node.queue_free()
+ var ignores: Array = []
+ for ignore in _load_ignores():
+ if ignore.begins_with(address) && ignore != address:
+ ignores.append(ignore)
+ _append_scenes(_get_scenes(address, ignores))
+
+# When ignore address bar text changes
+func _on_address_text_changed(new_text: String) -> void:
+ if new_text != "":
+ if DirAccess.dir_exists_absolute(new_text) || FileAccess.file_exists(new_text) && new_text.begins_with("res://"):
+ _add_button.disabled = false
+ else:
+ _add_button.disabled = true
+ else:
+ _add_button.disabled = true
+
+# Adds a new list to other lists
+func _add_scene_list(text: String) -> void:
+ var list = _scene_list_item.instantiate()
+ list.name = text.capitalize()
+ _tab_container.add_child(list)
+
+# Add section Button
+func _on_add_section_button_up():
+ if _section_name_line_edit.text != "":
+ _add_scene_list(_section_name_line_edit.text)
+ _section_name_line_edit.text = ""
+ _add_subsection_button.disabled = true
+ _add_section_button.disabled = true
+
+# When section name text changes
+func _on_section_name_text_changed(new_text):
+ if new_text != "" && !(new_text.capitalize() in get_all_lists_names_except()):
+ _add_section_button.disabled = false
+ else:
+ _add_section_button.disabled = true
+
+ if new_text != "" && _tab_container.get_child(_tab_container.current_tab).name != "All" && !(new_text.capitalize() in get_all_sublists_names_except()):
+ _add_subsection_button.disabled = false
+ else:
+ _add_subsection_button.disabled = true
+
+# Hide Button
+func _on_hide_button_up():
+ if _ignores_container.visible:
+ _hide_button.icon = _hide_button_unchecked
+ _ignores_container.visible = false
+ else:
+ _hide_button.icon = _hide_button_checked
+ _ignores_container.visible = true
+
+# Tab changes
+func _on_tab_container_tab_changed(tab: int):
+ _on_section_name_text_changed(_section_name_line_edit.text)
+
+# Add SubSection Button
+func _on_add_subsection_button_up():
+ if _section_name_line_edit.text != "":
+ var section = _tab_container.get_child(_tab_container.current_tab)
+ section.add_subsection(_section_name_line_edit.text)
+ _section_name_line_edit.text = ""
+ _add_subsection_button.disabled = true
+ _add_section_button.disabled = true
--- /dev/null
+[gd_scene load_steps=5 format=3 uid="uid://crnf0w0s44hxx"]
+
+[ext_resource type="Texture2D" uid="uid://cnhtsuf78gsy7" path="res://addons/scene_manager/icons/GuiChecked.svg" id="1"]
+[ext_resource type="PackedScene" path="res://addons/scene_manager/label.tscn" id="2"]
+[ext_resource type="Texture2D" uid="uid://bt1mtu3gbmwqc" path="res://addons/scene_manager/icons/FileDialog.svg" id="3"]
+[ext_resource type="Script" path="res://addons/scene_manager/manager.gd" id="6"]
+
+[node name="root_container" type="MarginContainer"]
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+script = ExtResource("6")
+
+[node name="main_container" type="VBoxContainer" parent="."]
+layout_mode = 2
+
+[node name="scenes" type="ScrollContainer" parent="main_container"]
+layout_mode = 2
+size_flags_vertical = 3
+
+[node name="tab_container" type="TabContainer" parent="main_container/scenes"]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+drag_to_rearrange_enabled = true
+
+[node name="add_category_container" type="MarginContainer" parent="main_container"]
+layout_mode = 2
+size_flags_vertical = 8
+
+[node name="add_category_container" type="HBoxContainer" parent="main_container/add_category_container"]
+layout_mode = 2
+
+[node name="section_name" type="LineEdit" parent="main_container/add_category_container/add_category_container"]
+layout_mode = 2
+size_flags_horizontal = 3
+
+[node name="HBoxContainer" type="HBoxContainer" parent="main_container/add_category_container/add_category_container"]
+layout_mode = 2
+size_flags_horizontal = 3
+
+[node name="add_subsection" type="Button" parent="main_container/add_category_container/add_category_container/HBoxContainer"]
+layout_mode = 2
+size_flags_horizontal = 3
+disabled = true
+text = "+ SubList"
+
+[node name="add_section" type="Button" parent="main_container/add_category_container/add_category_container/HBoxContainer"]
+layout_mode = 2
+size_flags_horizontal = 3
+disabled = true
+text = "+ List"
+
+[node name="separator" type="HSeparator" parent="main_container"]
+layout_mode = 2
+
+[node name="ignores" type="ScrollContainer" parent="main_container"]
+layout_mode = 2
+size_flags_vertical = 3
+size_flags_stretch_ratio = 0.3
+
+[node name="container" type="VBoxContainer" parent="main_container/ignores"]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+
+[node name="ignore_list" type="VBoxContainer" parent="main_container/ignores/container"]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+
+[node name="_title" type="HBoxContainer" parent="main_container/ignores/container/ignore_list"]
+layout_mode = 2
+
+[node name="delete_list" type="Button" parent="main_container/ignores/container/ignore_list/_title"]
+custom_minimum_size = Vector2(28, 28)
+layout_mode = 2
+disabled = true
+
+[node name="scenes" parent="main_container/ignores/container/ignore_list/_title" instance=ExtResource("2")]
+layout_mode = 2
+text = "Ignores:"
+
+[node name="interactive_section_container" type="VBoxContainer" parent="main_container"]
+layout_mode = 2
+
+[node name="ignore_interactive_section_container" type="MarginContainer" parent="main_container/interactive_section_container"]
+layout_mode = 2
+size_flags_vertical = 8
+
+[node name="add_ignore_container" type="HBoxContainer" parent="main_container/interactive_section_container/ignore_interactive_section_container"]
+layout_mode = 2
+
+[node name="dialog_add_ignore_container" type="HBoxContainer" parent="main_container/interactive_section_container/ignore_interactive_section_container/add_ignore_container"]
+layout_mode = 2
+size_flags_horizontal = 3
+
+[node name="file_dialog" type="FileDialog" parent="main_container/interactive_section_container/ignore_interactive_section_container/add_ignore_container/dialog_add_ignore_container"]
+mode = 2
+title = "Open a File or Directory"
+ok_button_text = "Open"
+file_mode = 3
+
+[node name="file_dialog_button" type="Button" parent="main_container/interactive_section_container/ignore_interactive_section_container/add_ignore_container/dialog_add_ignore_container"]
+custom_minimum_size = Vector2(28, 28)
+layout_mode = 2
+icon = ExtResource("3")
+
+[node name="address" type="LineEdit" parent="main_container/interactive_section_container/ignore_interactive_section_container/add_ignore_container/dialog_add_ignore_container"]
+layout_mode = 2
+size_flags_horizontal = 3
+
+[node name="dialog_add_ignore_container2" type="HBoxContainer" parent="main_container/interactive_section_container/ignore_interactive_section_container/add_ignore_container"]
+layout_mode = 2
+size_flags_horizontal = 3
+
+[node name="add" type="Button" parent="main_container/interactive_section_container/ignore_interactive_section_container/add_ignore_container/dialog_add_ignore_container2"]
+layout_mode = 2
+size_flags_horizontal = 3
+disabled = true
+text = "Add"
+
+[node name="hide" type="Button" parent="main_container/interactive_section_container/ignore_interactive_section_container/add_ignore_container/dialog_add_ignore_container2"]
+layout_mode = 2
+focus_mode = 0
+icon = ExtResource("1")
+
+[node name="margin_refresh_save_container" type="MarginContainer" parent="main_container/interactive_section_container"]
+layout_mode = 2
+size_flags_vertical = 0
+
+[node name="refresh_save_container" type="HBoxContainer" parent="main_container/interactive_section_container/margin_refresh_save_container"]
+layout_mode = 2
+
+[node name="refresh" type="Button" parent="main_container/interactive_section_container/margin_refresh_save_container/refresh_save_container"]
+layout_mode = 2
+size_flags_horizontal = 3
+text = "Refresh"
+
+[node name="save" type="Button" parent="main_container/interactive_section_container/margin_refresh_save_container/refresh_save_container"]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 2
+text = "Save"
+
+[node name="accept_dialog" type="AcceptDialog" parent="."]
+dialog_autowrap = true
+
+[connection signal="tab_changed" from="main_container/scenes/tab_container" to="." method="_on_tab_container_tab_changed"]
+[connection signal="text_changed" from="main_container/add_category_container/add_category_container/section_name" to="." method="_on_section_name_text_changed"]
+[connection signal="button_up" from="main_container/add_category_container/add_category_container/HBoxContainer/add_subsection" to="." method="_on_add_subsection_button_up"]
+[connection signal="button_up" from="main_container/add_category_container/add_category_container/HBoxContainer/add_section" to="." method="_on_add_section_button_up"]
+[connection signal="dir_selected" from="main_container/interactive_section_container/ignore_interactive_section_container/add_ignore_container/dialog_add_ignore_container/file_dialog" to="." method="_on_file_dialog_dir_file_selected"]
+[connection signal="file_selected" from="main_container/interactive_section_container/ignore_interactive_section_container/add_ignore_container/dialog_add_ignore_container/file_dialog" to="." method="_on_file_dialog_dir_file_selected"]
+[connection signal="button_up" from="main_container/interactive_section_container/ignore_interactive_section_container/add_ignore_container/dialog_add_ignore_container/file_dialog_button" to="." method="_on_file_dialog_button_button_up"]
+[connection signal="text_changed" from="main_container/interactive_section_container/ignore_interactive_section_container/add_ignore_container/dialog_add_ignore_container/address" to="." method="_on_address_text_changed"]
+[connection signal="button_up" from="main_container/interactive_section_container/ignore_interactive_section_container/add_ignore_container/dialog_add_ignore_container2/add" to="." method="_on_add_button_up"]
+[connection signal="button_up" from="main_container/interactive_section_container/ignore_interactive_section_container/add_ignore_container/dialog_add_ignore_container2/hide" to="." method="_on_hide_button_up"]
+[connection signal="button_up" from="main_container/interactive_section_container/margin_refresh_save_container/refresh_save_container/refresh" to="." method="_on_refresh_button_up"]
+[connection signal="button_up" from="main_container/interactive_section_container/margin_refresh_save_container/refresh_save_container/save" to="." method="_on_save_button_up"]
--- /dev/null
+[plugin]
+
+name="scene_manager"
+description="A powerful scene transition manager for godot."
+author="Maktoobgar"
+version="3.8.0"
+script="plugin.gd"
--- /dev/null
+@tool
+extends EditorPlugin
+
+var menu: Node
+
+const SETTINGS_PROPERTY_NAME := "scene_manager/scenes/scenes_path"
+const DEFAULT_PATH_TO_SCENES := "res://addons/scene_manager/scenes.gd"
+
+func set_properties_for_setting():
+ var property_info = {
+ "name": SETTINGS_PROPERTY_NAME,
+ "type": TYPE_STRING,
+ "hint": PROPERTY_HINT_FILE,
+ "hint_string": "scenes.gd"
+ }
+ ProjectSettings.add_property_info(property_info)
+
+ ProjectSettings.set_initial_value(SETTINGS_PROPERTY_NAME, DEFAULT_PATH_TO_SCENES)
+ ProjectSettings.set_as_basic(SETTINGS_PROPERTY_NAME, true)
+
+ # Restart is required as path to Scenes singleton has changed
+ ProjectSettings.set_restart_if_changed(SETTINGS_PROPERTY_NAME, true)
+
+ ProjectSettings.save()
+
+# Plugin installation
+func _enter_tree():
+
+ var path_to_scenes = DEFAULT_PATH_TO_SCENES
+
+ # Adding settings property to Project/Settings & loading
+ if !ProjectSettings.has_setting(SETTINGS_PROPERTY_NAME):
+ ProjectSettings.set_setting(SETTINGS_PROPERTY_NAME, DEFAULT_PATH_TO_SCENES)
+ set_properties_for_setting()
+ else:
+ path_to_scenes = ProjectSettings.get_setting(SETTINGS_PROPERTY_NAME)
+ set_properties_for_setting()
+
+ add_autoload_singleton("SceneManager", "res://addons/scene_manager/scene_manager.tscn")
+ add_autoload_singleton("Scenes", path_to_scenes)
+ menu = preload("res://addons/scene_manager/menu.tscn").instantiate()
+ menu.name = "Scene Manager"
+
+ add_control_to_dock(EditorPlugin.DOCK_SLOT_RIGHT_UL, menu)
+
+# Plugin uninstallation
+func _exit_tree():
+ # TODO: We can use this function but it removes the saved value of it
+ # along side with the gui setting, if you want to actually just
+ # restart the plugin, you have to set the value for scenes path again
+ #
+ # So... not a good idea to use this:
+ #
+ # ProjectSettings.clear(SETTINGS_PROPERTY_NAME)
+ #
+ # We just don't remove the settings for now
+ remove_autoload_singleton("SceneManager")
+ remove_autoload_singleton("Scenes")
+ remove_control_from_docks(menu)
+ menu.free()
--- /dev/null
+@tool
+extends Button
+
+# Get and return drag data
+func _get_drag_data(at_position: Vector2) -> Variant:
+ return get_parent()._get_drag_data(at_position)
--- /dev/null
+@tool
+extends HBoxContainer
+
+# Nodes
+@onready var _root: Node = self
+@onready var _popup_menu: PopupMenu = find_child("popup_menu")
+@onready var _key: String = get_node("key").text
+# Variables
+var _setting: ItemSetting
+var _sub_section: Control
+var _list: Control
+var _mouse_is_over_value: bool
+
+# Finds and fills `_root` variable properly
+func _ready() -> void:
+ while true:
+ if _root == null:
+ ## If we are here, we are running in editor, so get out
+ break
+ elif _root.name == "Scene Manager" || _root.name == "menu":
+ break
+ _root = _root.get_parent()
+
+# Sets value of `key`
+func set_key(text: String) -> void:
+ get_node("key").text = text
+ name = text
+ _key = text
+
+# Sets value of `value`
+func set_value(text: String) -> void:
+ get_node("value").text = text
+
+# Return `key` string value
+func get_key() -> String:
+ return get_node("key").text
+
+# Return `value` string value
+func get_value() -> String:
+ return get_node("value").text
+
+# Returns `key` node
+func get_key_node() -> Node:
+ return get_node("key")
+
+# Returns `_setting.visibility` value
+func get_visibility() -> bool:
+ return _setting.visibility
+
+# Sets value of `_setting.visibility`
+func set_visibility(input: bool) -> void:
+ _setting.visibility = input
+ self.visible = _list.determine_item_visibility(_setting)
+
+# Returns `_setting`
+func get_setting() -> ItemSetting:
+ return _setting
+
+# Sets `_setting`
+func set_setting(setting: ItemSetting) -> void:
+ _setting = setting
+
+# Sets subsection for current item
+func set_subsection(node: Control) -> void:
+ _sub_section = node
+
+# Sets passed theme to normal theme of `key` LineEdit
+func custom_set_theme(theme: StyleBox) -> void:
+ get_key_node().add_theme_stylebox_override("normal", theme)
+
+# Removes added custom theme for `key` LineEdit
+func remove_custom_theme() -> void:
+ get_key_node().remove_theme_stylebox_override("normal")
+
+# Popup Button
+func _on_popup_button_button_up():
+ var i: int = 0
+ var sections: Array = _root.get_all_lists_names_except()
+ _popup_menu.clear()
+ _popup_menu.add_separator("Categories")
+ i += 1
+ # Categories have id of 0
+ for section in sections:
+ if section == "All":
+ continue
+ _popup_menu.add_check_item(section)
+ _popup_menu.set_item_id(i, 0)
+ _popup_menu.set_item_checked(i, section in _root.get_sections(get_value()))
+ i += 1
+ _popup_menu.add_separator("General")
+ i += 1
+ # Generals have id of 1
+ _popup_menu.add_check_item("Visible")
+ _popup_menu.set_item_checked(i, _setting.visibility)
+ _popup_menu.set_item_id(i, 1)
+ i += 1
+ var popup_size = _popup_menu.size
+ _popup_menu.popup(Rect2(get_global_mouse_position(), popup_size))
+
+# Happens when open scene button clicks
+func _on_open_scene_button_up():
+ EditorPlugin.new().get_editor_interface().open_scene_from_path(get_value())
+
+# Happens on input on the value element
+func _on_value_gui_input(event: InputEvent):
+ if event is InputEventMouseButton and event.is_released() and event.button_index == MOUSE_BUTTON_LEFT and _mouse_is_over_value:
+ EditorPlugin.new().get_editor_interface().get_file_system_dock().navigate_to_path(get_value())
+
+# Happens when mouse is over value input
+func _on_value_mouse_entered():
+ _mouse_is_over_value = true
+
+# Happens when mouse is out of value input
+func _on_value_mouse_exited():
+ _mouse_is_over_value = false
+
+# Happens when an item is selected
+func _on_popup_menu_index_pressed(index: int):
+ var id = _popup_menu.get_item_id(index)
+ var checked = _popup_menu.is_item_checked(index)
+ var text = _popup_menu.get_item_text(index)
+ _popup_menu.set_item_checked(index, !checked)
+ if id == 0:
+ if !checked:
+ _root.add_scene_to_list(text, get_key(), get_value(), ItemSetting.default())
+ else:
+ _root.remove_scene_from_list(text, get_key(), get_value())
+ elif id == 1:
+ if text == "Visible":
+ set_visibility(!get_visibility())
+
+# Runs by hand in `_on_key_gui_input` function when text of key LineEdit
+# changes and key event of it was released
+func _on_key_value_text_changed() -> void:
+ _root.update_all_scene_with_key(_key, get_key(), get_value(), _setting, [get_parent().get_parent()])
+
+# Shows a popup in UI
+func _show_message() -> void:
+ var reserved_keys: String = ""
+ for i in range(len(_root.reserved_keys)):
+ if i == 0:
+ reserved_keys += "\"" + _root.reserved_keys[0] + "\""
+ continue
+ reserved_keys += ", \"" + _root.reserved_keys[i] + "\""
+ _root.show_message("Error", "\"%s\" and an empty string(\"\"), or every other word which will "%
+ reserved_keys + "begin with an '_', are reserved or not allowed to be used as a scene " +
+ "key so please do not use them to avoid seeing weird reaction from Scene Manager tool.")
+
+# Checks if current value for LineEdit is in reserved keys or not
+func _check_reserved_keys() -> void:
+ if get_key() == "" || get_key().begins_with("_") || get_key() in _root.reserved_keys:
+ _show_message()
+
+# When a gui_input happens on LineEdit, this function triggers
+func _on_key_gui_input(event: InputEvent) -> void:
+ if event is InputEventKey:
+ if event.is_pressed():
+ return
+ # Runs when InputEventKey is released
+ if get_key() == "":
+ _show_message()
+ elif get_key() != _key:
+ _check_reserved_keys()
+ _on_key_value_text_changed()
+ _key = get_key()
+ _root.check_duplication()
+
+# When added
+func _on_tree_entered():
+ if _sub_section:
+ _sub_section.child_entered()
+
+# When deleted
+func _on_tree_exited():
+ if _sub_section:
+ _sub_section.child_exited()
+
+# Returns grab data
+func _get_drag_data(at_position: Vector2) -> Variant:
+ return {
+ "node": self,
+ "parent": _sub_section,
+ }
--- /dev/null
+[gd_scene load_steps=5 format=3 uid="uid://hh0sw1g7upfc"]
+
+[ext_resource type="Script" path="res://addons/scene_manager/scene_item.gd" id="2"]
+[ext_resource type="Texture2D" uid="uid://brxxaey30q7uk" path="res://addons/scene_manager/icons/GuiTabMenuHl.svg" id="3"]
+[ext_resource type="Script" path="res://addons/scene_manager/popup_button.gd" id="3_nwus0"]
+[ext_resource type="Texture2D" uid="uid://b4xi5nvjb3rhr" path="res://addons/scene_manager/icons/PlayOverlay.png" id="4_pt2e1"]
+
+[node name="item" type="HBoxContainer"]
+offset_right = 280.0
+offset_bottom = 20.0
+size_flags_horizontal = 3
+script = ExtResource("2")
+
+[node name="popup_button" type="Button" parent="."]
+custom_minimum_size = Vector2(28, 28)
+layout_mode = 2
+icon = ExtResource("3")
+script = ExtResource("3_nwus0")
+
+[node name="key" type="LineEdit" parent="."]
+layout_mode = 2
+size_flags_horizontal = 3
+
+[node name="value" type="LineEdit" parent="."]
+layout_mode = 2
+size_flags_horizontal = 3
+editable = false
+
+[node name="open_button" type="Button" parent="."]
+texture_filter = 1
+custom_minimum_size = Vector2(28, 28)
+layout_mode = 2
+icon = ExtResource("4_pt2e1")
+
+[node name="popup_menu" type="PopupMenu" parent="."]
+size = Vector2i(100, 28)
+visible = true
+hide_on_item_selection = false
+
+[connection signal="tree_entered" from="." to="." method="_on_tree_entered"]
+[connection signal="tree_exited" from="." to="." method="_on_tree_exited"]
+[connection signal="button_up" from="popup_button" to="." method="_on_popup_button_button_up"]
+[connection signal="gui_input" from="key" to="." method="_on_key_gui_input"]
+[connection signal="gui_input" from="value" to="." method="_on_value_gui_input"]
+[connection signal="mouse_entered" from="value" to="." method="_on_value_mouse_entered"]
+[connection signal="mouse_exited" from="value" to="." method="_on_value_mouse_exited"]
+[connection signal="button_up" from="open_button" to="." method="_on_open_scene_button_up"]
+[connection signal="index_pressed" from="popup_menu" to="." method="_on_popup_menu_index_pressed"]
--- /dev/null
+@tool
+extends ScrollContainer
+
+# Scene itema and sub_section to instance and add in list
+const _scene_item = preload("res://addons/scene_manager/scene_item.tscn")
+const _sub_section = preload("res://addons/scene_manager/sub_section.tscn")
+# Duplicate + normal scene theme
+const _duplicate_line_edit: StyleBox = preload("res://addons/scene_manager/themes/line_edit_duplicate.tres")
+# Open close icons
+const _eye_open = preload("res://addons/scene_manager/icons/eye_open.png")
+const _eye_close = preload("res://addons/scene_manager/icons/eye_close.png")
+# variables
+@onready var _container: VBoxContainer = find_child("container")
+@onready var _delete_list_button: Button = find_child("delete_list")
+@onready var _hidden_button: Button = find_child("hidden")
+var _root: Node = self
+var _main_subsection: Node = null
+var _secondary_subsection: Node = null
+
+# Finds and fills `_root` variable properly
+#
+# Start up of `All` list
+func _ready() -> void:
+ if self.name == "All":
+ _delete_list_button.icon = null
+ _delete_list_button.disabled = true
+ _delete_list_button.focus_mode = Control.FOCUS_NONE
+
+ var sub = _sub_section.instantiate()
+ sub.name = "Uncategorized"
+ _container.add_child(sub)
+ sub.open()
+ sub.hide_delete_button()
+ _main_subsection = sub
+
+ var sub2 = _sub_section.instantiate()
+ sub2.name = "Categorized"
+ _container.add_child(sub2)
+ sub2.hide_delete_button()
+ _secondary_subsection = sub2
+ else:
+ var sub = _sub_section.instantiate()
+ sub.name = "All"
+ sub.visible = false
+ _container.add_child(sub)
+ sub.open()
+ sub.hide_delete_button()
+ _main_subsection = sub
+ while true:
+ if _root == null:
+ ## If we are here, we are running in editor, so get out
+ break
+ elif _root.name == "Scene Manager" || _root.name == "menu":
+ break
+ _root = _root.get_parent()
+
+# Determines item can be visible with current settings or not
+func determine_item_visibility(setting: ItemSetting) -> bool:
+ return true if _hidden_button.icon == _eye_close && !setting.visibility else true if _hidden_button.icon == _eye_open && setting.visibility else false
+
+# Adds an item to list
+func add_item(key: String, value: String, setting: ItemSetting) -> void:
+ var item = _scene_item.instantiate()
+ item.set_key(key)
+ item.set_value(value)
+ item.set_setting(setting)
+ item.visible = determine_item_visibility(setting)
+ item._list = self
+ if name == "All":
+ if !setting.categorized:
+ _main_subsection.add_item(item)
+ else:
+ _secondary_subsection.add_item(item)
+ else:
+ if setting.subsection != "":
+ var subsection = find_subsection(setting.subsection)
+ if subsection:
+ subsection.add_item(item)
+ else:
+ add_subsection(setting.subsection).add_item(item)
+ else:
+ _main_subsection.add_item(item)
+
+# Finds and returns a sub_section in the list
+func find_subsection(key: String) -> Node:
+ for i in range(_container.get_child_count()):
+ if i == 0: continue
+ var element = _container.get_child(i)
+ if element.name == key:
+ return element
+ return null
+
+# Removes an item from list
+func remove_item(key: String, value: String) -> void:
+ for i in range(_container.get_child_count()):
+ if i == 0: continue
+ var children: Array = _container.get_child(i).get_items()
+ for j in range(len(children)):
+ if children[j].get_key() == key && children[j].get_value() == value:
+ children[j].queue_free()
+ return
+
+# Removes items that their value begins with passed value
+func remove_items_begins_with(value: String) -> void:
+ for i in range(_container.get_child_count()):
+ if i == 0: continue
+ var children: Array = _container.get_child(i).get_items()
+ for j in range(len(children)):
+ if children[j].get_value().begins_with(value):
+ children[j].queue_free()
+
+# Clear all scene records from UI list
+func clear_list() -> void:
+ for i in range(_container.get_child_count()):
+ if i == 0: continue
+ _container.get_child(i).queue_free()
+
+# Appends all scenes into UI list
+#
+# This function is used for new items that are new in project directory and are
+# not saved before, so they have no settings
+#
+# Input example:
+# {"scene_key": "scene_address", "scene_key": "scene_address", ...}
+func append_scenes(nodes: Dictionary) -> void:
+ if name == "All":
+ for key in nodes:
+ add_item(key, nodes[key], ItemSetting.new(true, _root.has_sections(nodes[key])))
+ else:
+ for key in nodes:
+ add_item(key, nodes[key], ItemSetting.default())
+
+# Return an array of record nodes from UI list
+func get_list_nodes() -> Array:
+ var arr: Array[Node] = []
+ for i in range(_container.get_child_count()):
+ if i == 0: continue
+ var nodes = _container.get_child(i).get_items()
+ arr.append_array(nodes)
+ return arr
+
+# Returns a specific node from passed scene name
+func get_node_by_scene_name(scene_name: String) -> Node:
+ for i in range(_container.get_child_count()):
+ if i == 0: continue
+ var items = _container.get_child(i).get_items()
+ for j in range(len(items)):
+ if items[j].get_key() == scene_name:
+ return items[j]
+ return null
+
+# Returns a specific node from passed scene address
+func get_node_by_scene_address(scene_address: String) -> Node:
+ for i in range(_container.get_child_count()):
+ if i == 0: continue
+ var items = _container.get_child(i).get_items()
+ for j in range(len(items)):
+ if items[j].get_value() == scene_address:
+ return items[j]
+ return null
+
+# Update a specific scene record with passed data in UI
+func update_scene_with_key(key: String, new_key: String, value: String, setting: ItemSetting) -> void:
+ for i in range(_container.get_child_count()):
+ if i == 0: continue
+ var children: Array[Node] = _container.get_child(i).get_items()
+ for j in range(len(children)):
+ if children[j].get_key() == key && children[j].get_value() == value:
+ children[j].set_key(new_key)
+ children[j].set_setting(setting)
+
+# Checks duplication in current list and return their scene addresses in an array from UI
+func check_duplication() -> Array:
+ var all: Array[Node] = get_list_nodes()
+ var arr: Array[String] = []
+ for i in range(len(all)):
+ var j: int = i + 1
+ while j < len(all):
+ var child1: Node = all[i]
+ var child2: Node = all[j]
+ if child1.get_key() == child2.get_key():
+ if !(child1.get_key() in arr):
+ arr.append(child1.get_key())
+ j += 1
+ return arr
+
+# Reset theme for all children in UI
+func set_reset_theme_for_all() -> void:
+ for i in range(_container.get_child_count()):
+ if i == 0: continue
+ var children: Array[Node] = _container.get_child(i).get_items()
+ for j in range(len(children)):
+ children[j].remove_custom_theme()
+
+# Sets duplicate theme for children in passed list in UI
+func set_duplicate_theme(list: Array) -> void:
+ for i in range(_container.get_child_count()):
+ if i == 0: continue
+ var children: Array[Node] = _container.get_child(i).get_items()
+ for j in range(len(children)):
+ if children[j].get_key() in list:
+ children[j].custom_set_theme(_duplicate_line_edit)
+
+# Returns all names of sublist
+func get_all_sublists() -> Array:
+ var arr: Array[String] = []
+ for i in range(_container.get_child_count()):
+ if i == 0: continue
+ arr.append(_container.get_child(i).name)
+ return arr
+
+# Adds a subsection
+func add_subsection(text: String) -> Control:
+ var sub = _sub_section.instantiate()
+ sub.name = text.capitalize()
+ _container.add_child(sub)
+ return sub
+
+# List deletion
+func _on_delete_list_button_up() -> void:
+ if self.name == "All":
+ return
+ self.queue_free()
+
+# Refreshes `visible` of all items in list
+func _refresh_visible_of_all_items() -> void:
+ for i in range(_container.get_child_count()):
+ if i == 0: continue
+ var children: Array[Node] = _container.get_child(i).get_items()
+ for j in range(len(children)):
+ children[j].visible = determine_item_visibility(children[j].get_setting())
+
+# Hidden Button
+func _on_hidden_button_up():
+ if _hidden_button.icon == _eye_open:
+ _hidden_button.icon = _eye_close
+ _refresh_visible_of_all_items()
+ elif _hidden_button.icon == _eye_close:
+ _hidden_button.icon = _eye_open
+ _refresh_visible_of_all_items()
--- /dev/null
+[gd_scene load_steps=4 format=3 uid="uid://7r0ywsv3ga6g"]
+
+[ext_resource type="Script" path="res://addons/scene_manager/scene_list.gd" id="2"]
+[ext_resource type="Texture2D" uid="uid://dw322nmqpqwfq" path="res://addons/scene_manager/icons/ImportFail.svg" id="3"]
+[ext_resource type="Texture2D" uid="uid://d250i5cu8lgbd" path="res://addons/scene_manager/icons/eye_open.png" id="4_u7g41"]
+
+[node name="scenes" type="ScrollContainer"]
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+script = ExtResource("2")
+
+[node name="container" type="VBoxContainer" parent="."]
+layout_mode = 2
+size_flags_horizontal = 3
+
+[node name="_title" type="HBoxContainer" parent="container"]
+layout_mode = 2
+
+[node name="delete_list" type="Button" parent="container/_title"]
+custom_minimum_size = Vector2(28, 28)
+layout_mode = 2
+icon = ExtResource("3")
+
+[node name="hidden" type="Button" parent="container/_title"]
+custom_minimum_size = Vector2(28, 28)
+layout_mode = 2
+icon = ExtResource("4_u7g41")
+
+[connection signal="button_up" from="container/_title/delete_list" to="." method="_on_delete_list_button_up"]
+[connection signal="button_up" from="container/_title/hidden" to="." method="_on_hidden_button_up"]
--- /dev/null
+extends Node
+
+# consts
+const FADE: String = "fade"
+const COLOR: String = "color"
+const NO_COLOR: String = "no_color"
+const BLACK: Color = Color(0, 0, 0)
+# variables
+@onready var _fade_color_rect: ColorRect = find_child("fade")
+@onready var _animation_player: AnimationPlayer = find_child("animation_player")
+@onready var _in_transition: bool = false
+@onready var _stack: Array = []
+@onready var _stack_limit: int = -1
+@onready var _current_scene: String = ""
+@onready var _first_time: bool = true
+@onready var _patterns: Dictionary = {}
+@onready var _reserved_keys: Array = ["back", "null", "ignore", "refresh",
+ "reload", "restart", "exit", "quit"]
+var _load_scene: String = ""
+var _load_progress: Array = []
+var _recorded_scene: String = ""
+# signals
+signal load_finished
+signal load_percent_changed(value: int)
+signal scene_changed
+signal fade_in_started
+signal fade_out_started
+signal fade_in_finished
+signal fade_out_finished
+
+class Options:
+ # based checked seconds
+ var fade_speed: float = 1
+ var fade_pattern: String = "fade"
+ var smoothness: float = 0.1
+ var inverted: bool = false
+
+class GeneralOptions:
+ var color: Color = Color(0, 0, 0)
+ var timeout: float = 0
+ var clickable: bool = true
+ var add_to_back: bool = true
+
+# sets current scene to starting point (used for `back` functionality)
+func _set_current_scene() -> void:
+ var root_key: String = get_tree().current_scene.scene_file_path
+ _current_scene = _get_scene_key_by_value(root_key)
+ assert (_current_scene != "", "Scene Manager Error: loaded scene is not defined in scene manager tool, to fix this, on Scene Manager UI panel, just once click on refresh and then save buttons.")
+
+# gets patterns from `addons/scene_manager/shader_patterns`
+func _get_patterns() -> void:
+ var root_path: String = "res://addons/scene_manager/shader_patterns/"
+ var dir := DirAccess.open(root_path)
+ if dir:
+ dir.list_dir_begin()
+
+ while true:
+ var file_folder: String = dir.get_next()
+ if file_folder == "":
+ break
+ elif file_folder.get_extension() == "import":
+ file_folder = file_folder.replace(".import", "")
+ if file_folder.get_extension() == "png":
+ var key = file_folder.replace("."+file_folder.get_extension(), "")
+ if !(key in _patterns.keys()):
+ _patterns[key] = load(root_path + file_folder)
+
+ dir.list_dir_end()
+
+# set current scene and get patterns from `addons/scene_manager/shader_patterns` folder
+func _ready() -> void:
+ set_process(false)
+ _set_current_scene()
+ _get_patterns()
+
+# `speed` unit is in seconds
+func _fade_in(speed: float) -> bool:
+ if speed == 0:
+ return false
+ fade_in_started.emit()
+ _animation_player.play(FADE, -1, 1 / speed, false)
+ return true
+
+# `speed` unit is in seconds
+func _fade_out(speed: float) -> bool:
+ if speed == 0:
+ return false
+ fade_out_started.emit()
+ _animation_player.play(FADE, -1, -1 / speed, true)
+ return true
+
+# activates `in_transition` mode
+func _set_in_transition() -> void:
+ _in_transition = true
+
+# deactivates `in_transition` mode
+func _set_out_transition() -> void:
+ _in_transition = false
+
+# adds current scene to `_stack`
+func _append_stack(key: String) -> void:
+ if _stack_limit == -1:
+ _stack.append(_current_scene)
+ elif _stack_limit > 0:
+ if _stack_limit <= len(_stack):
+ for i in range(len(_stack) - _stack_limit + 1):
+ _stack.pop_front()
+ _stack.append(_current_scene)
+ else:
+ _stack.append(_current_scene)
+ _current_scene = key
+
+# pops most recent added scene to `_stack`
+func _pop_stack() -> String:
+ var pop = _stack.pop_back()
+ if pop:
+ _current_scene = pop
+ return _current_scene
+
+# changes scene to the previous scene
+func _back() -> bool:
+ var pop: String = _pop_stack()
+ if pop:
+ get_tree().change_scene_to_file(Scenes.scenes[pop]["value"])
+ return true
+ return false
+
+# returns the scene key of the passed scene value (scene address)
+func _get_scene_key_by_value(path: String) -> String:
+ var found_key = ""
+ for key in Scenes.scenes:
+ if key.begins_with("_"):
+ continue
+ if Scenes.scenes[key]["value"] == path:
+ found_key = key
+ return found_key
+
+# restart the same scene
+func _refresh() -> bool:
+ get_tree().change_scene_to_file(Scenes.scenes[_current_scene]["value"])
+ return true
+
+# checks different states of scene and make actual transitions happen
+func _change_scene(scene, add_to_back: bool) -> bool:
+ # when scenes get instanciate, they will loose their `scene_instance.scene_file_path`
+ # varialbe value which is used to reload the current scene again in this addon and that's
+ # why I'm fixing this up by hand and I'm not using `get_tree().change_scene_to_packed()`
+ # fuction in here
+ if scene is PackedScene:
+ scene.get_local_scene()
+ var scene_instance = scene.instantiate()
+ var root = get_tree().get_root()
+ root.get_child(root.get_child_count() - 1).free()
+ root.add_child(scene_instance)
+ get_tree().set_current_scene(scene_instance)
+ if (_load_scene == ""):
+ assert(false, "Scene Manager Error: please use this addon as described")
+ var path: String = _load_scene
+ var found_key: String = _get_scene_key_by_value(path)
+ scene_instance.scene_file_path = _load_scene
+ if add_to_back && found_key != "":
+ _append_stack(found_key)
+ _load_scene = ""
+ return true
+
+ if scene is Node:
+ var root = get_tree().get_root()
+ root.get_child(root.get_child_count() - 1).free()
+ root.add_child(scene)
+ get_tree().set_current_scene(scene)
+ var path: String = scene.scene_file_path
+ var found_key: String = _get_scene_key_by_value(path)
+ if add_to_back && found_key != "":
+ _append_stack(found_key)
+ return true
+
+ if scene == "back":
+ return _back()
+
+ elif scene == "null" || scene == "ignore" || scene == "":
+ return false
+
+ elif scene == "reload" || scene == "refresh" || scene == "restart":
+ return _refresh()
+
+ elif scene == "exit" || scene == "quit":
+ get_tree().quit(0)
+
+ else:
+ get_tree().change_scene_to_file(Scenes.scenes[scene]["value"])
+ if add_to_back:
+ _append_stack(scene)
+ return true
+ return false
+
+# makes menu clickable or unclickable during transitions
+func _set_clickable(clickable: bool) -> void:
+ if clickable:
+ _fade_color_rect.mouse_filter = Control.MOUSE_FILTER_IGNORE
+ else:
+ _fade_color_rect.mouse_filter = Control.MOUSE_FILTER_STOP
+
+# sets color if timeout exists
+func _timeout(timeout: float) -> bool:
+ if timeout != 0:
+ _animation_player.play(COLOR, -1, 1, false)
+ return true
+ return false
+
+# sets properties for transitions
+func _set_pattern(options: Options, general_options: GeneralOptions) -> void:
+ if !(options.fade_pattern in _patterns):
+ options.fade_pattern = "fade"
+ if options.fade_pattern == "fade":
+ _fade_color_rect.material.set_shader_parameter("linear_fade", true)
+ _fade_color_rect.material.set_shader_parameter("color", Vector3(general_options.color.r, general_options.color.g, general_options.color.b))
+ _fade_color_rect.material.set_shader_parameter("custom_texture", null)
+ else:
+ _fade_color_rect.material.set_shader_parameter("linear_fade", false)
+ _fade_color_rect.material.set_shader_parameter("custom_texture", _patterns[options.fade_pattern])
+ _fade_color_rect.material.set_shader_parameter("inverted", options.inverted)
+ _fade_color_rect.material.set_shader_parameter("smoothness", options.smoothness)
+ _fade_color_rect.material.set_shader_parameter("color", Vector3(general_options.color.r, general_options.color.g, general_options.color.b))
+
+# used for interactive change scene
+func _process(_delta: float):
+ var prevPercent: int = 0
+ if len(_load_progress) != 0:
+ prevPercent = int(_load_progress[0] * 100)
+ var status = ResourceLoader.load_threaded_get_status(_load_scene, _load_progress)
+ var nextPercent: int = int(_load_progress[0] * 100)
+ if prevPercent != nextPercent:
+ load_percent_changed.emit(nextPercent)
+ if status == ResourceLoader.THREAD_LOAD_LOADED:
+ set_process(false)
+ _load_progress = []
+ load_finished.emit()
+ elif status == ResourceLoader.THREAD_LOAD_IN_PROGRESS:
+ pass
+ else:
+ assert(false, "Scene Manager Error: for some reason, loading failed, I don't know why")
+
+# limits how much deep scene manager is allowed to record previous scenes which
+# affects in changing scene to `back`(previous scene) functionality
+#
+# allowed `input` values:
+# input = -1 => unlimited (default)
+# input = 0 => we can not go back to any previos scenes
+# input > 0 => we can go back to `input` or less previous scenes
+func set_back_limit(input: int) -> void:
+ assert(input >= -1, "Scene Manager Error: input must to >= -1")
+ _stack_limit = input
+ if input == 0:
+ _stack.clear()
+ elif input > 0:
+ if input <= len(_stack):
+ for i in range(len(_stack) - input):
+ _stack.pop_front()
+
+# resets the `_current_scene` and clears `_stack`
+func reset_scene_manager() -> void:
+ _set_current_scene()
+ _stack.clear()
+
+# creates options for fade_out or fade_in transition
+func create_options(fade_speed: float = 1.0, fade_pattern: String = "fade", smoothness: float = 0.1, inverted: bool = false) -> Options:
+ var options: Options = Options.new()
+ options.fade_speed = fade_speed
+ options.fade_pattern = fade_pattern
+ options.smoothness = smoothness
+ options.inverted = inverted
+ return options
+
+# creates options for common properties in transition
+# add_to_back means that you can go back to the scene if you
+# change scene to `back` scene
+func create_general_options(color: Color = Color(0, 0, 0), timeout: float = 0.0, clickable: bool = true, add_to_back: bool = true) -> GeneralOptions:
+ var options: GeneralOptions = GeneralOptions.new()
+ options.color = color
+ options.timeout = timeout
+ options.clickable = clickable
+ options.add_to_back = add_to_back
+ return options
+
+# validates passed scene key
+func validate_scene(key: String) -> void:
+ assert((key in _reserved_keys || key == "" || Scenes.scenes.has(key) == true) && !key.begins_with("_"), "Scene Manager Error: `%s` key for scene is not recognized, please double check."% key)
+
+# validates passed scene key
+func safe_validate_scene(key: String) -> bool:
+ return (key in _reserved_keys || key == "" || Scenes.scenes.has(key) == true) && !key.begins_with("_")
+
+# validates passed pattern key
+func validate_pattern(key: String) -> void:
+ var errorPart1 := "Scene Manager Error: `%s` key for shader pattern is not recognizable, please double check.\n"% key
+ var keys := _patterns.keys()
+ var stringKeys := ""
+
+ for i in range(0,keys.size()):
+ if i == 0:
+ stringKeys = "\"%s\"" % keys[0]
+ continue
+ stringKeys += ", \"%s\"" % keys[i]
+ var errorPart2 := "Acceptable keys are \"%s\" , \"fade\"."%stringKeys
+ assert(key in _patterns || key == "fade" || key == "",errorPart1 + errorPart2)
+
+# validates passed pattern key
+func safe_validate_pattern(key: String) -> bool:
+ return key in _patterns || key == "fade" || key == ""
+
+# makes a fade_in transition for the first loaded scene in the game
+func show_first_scene(fade_in_options: Options, general_options: GeneralOptions) -> void:
+ if _first_time:
+ _first_time = false
+ _set_in_transition()
+ _set_clickable(general_options.clickable)
+ _set_pattern(fade_in_options, general_options)
+ if _timeout(general_options.timeout):
+ await get_tree().create_timer(general_options.timeout).timeout
+ if _fade_in(fade_in_options.fade_speed):
+ await _animation_player.animation_finished
+ fade_in_finished.emit()
+ _set_clickable(true)
+ _set_out_transition()
+
+# returns scene instance of passed scene key (blocking)
+func create_scene_instance(key: String) -> Node:
+ return get_scene(key).instantiate()
+
+# returns PackedScene of passed scene key (blocking)
+func get_scene(key: String) -> PackedScene:
+ validate_scene(key)
+ var address = Scenes.scenes[key]["value"]
+ ResourceLoader.load_threaded_request(address, "", true, ResourceLoader.CACHE_MODE_REUSE)
+ return ResourceLoader.load_threaded_get(address)
+
+# changes current scene to the next scene
+func change_scene(scene, fade_out_options: Options, fade_in_options: Options, general_options: GeneralOptions) -> void:
+ if (scene is PackedScene || scene is Node || (typeof(scene) == TYPE_STRING && safe_validate_scene(scene) && !_in_transition)):
+ _first_time = false
+ _set_in_transition()
+ _set_clickable(general_options.clickable)
+ _set_pattern(fade_out_options, general_options)
+ if _fade_out(fade_out_options.fade_speed):
+ await _animation_player.animation_finished
+ fade_out_finished.emit()
+ if _change_scene(scene, general_options.add_to_back):
+ if !(scene is Node || scene is PackedScene):
+ await get_tree().node_added
+ scene_changed.emit()
+ if _timeout(general_options.timeout):
+ await get_tree().create_timer(general_options.timeout).timeout
+ _animation_player.play(NO_COLOR, -1, 1, false)
+ _set_pattern(fade_in_options, general_options)
+ if _fade_in(fade_in_options.fade_speed):
+ await _animation_player.animation_finished
+ fade_in_finished.emit()
+ _set_clickable(true)
+ _set_out_transition()
+
+# Change scene with no effect
+func no_effect_change_scene(scene, hold_timeout: float = 0.0, add_to_back: bool = true):
+ if (scene is PackedScene || scene is Node || (typeof(scene) == TYPE_STRING && safe_validate_scene(scene) && !_in_transition)):
+ _first_time = false
+ _set_in_transition()
+ await get_tree().create_timer(hold_timeout).timeout
+ if _change_scene(scene, add_to_back):
+ if !(scene is Node):
+ await get_tree().node_added
+ _set_out_transition()
+
+# imports loaded scene into the scene tree but doesn't change the scene
+# maily used when your new loaded scene has a loading phase when added to scene tree
+# so to use this, first has to call `load_scene_interactive` to load your scene
+# and then have to listen on `load_finished` signal and after the signal emits,
+# you call this function and this function adds the loaded scene to the scene
+# tree but exactly behind the current scene so that you still can not see the new scene
+func add_loaded_scene_to_scene_tree() -> void:
+ if _load_scene != "":
+ var scene_resource = ResourceLoader.load_threaded_get(_load_scene) as PackedScene
+ if scene_resource:
+ var scene = scene_resource.instantiate()
+ scene.scene_file_path = _load_scene
+ var root = get_tree().get_root()
+ root.add_child(scene)
+ root.move_child(scene, root.get_child_count() - 2)
+ _load_scene = ""
+
+# when you added the loaded scene to the scene tree by `add_loaded_scene_to_scene_tree`
+# function, you call this function after you are sure that the added scene to scene tree
+# is completely ready and functional to change the active scene
+func change_scene_to_existing_scene_in_scene_tree(fade_out_options: Options, fade_in_options: Options, general_options: GeneralOptions) -> void:
+ _set_in_transition()
+ _set_clickable(general_options.clickable)
+ _set_pattern(fade_out_options, general_options)
+ if _fade_out(fade_out_options.fade_speed):
+ await _animation_player.animation_finished
+ fade_out_finished.emit()
+ # actual change scene goes here
+ var root = get_tree().get_root()
+ # delete the loading screen scene
+ root.get_child(root.get_child_count() - 1).free()
+ # get the loaded, completely generated scene
+ var scene = root.get_child(root.get_child_count() - 1)
+ # inform godot which now this is the current scene
+ get_tree().set_current_scene(scene)
+ # keeping the track of current scene and previous scenes
+ var path: String = scene.scene_file_path
+ var found_key: String = _get_scene_key_by_value(path)
+ if general_options.add_to_back && found_key != "":
+ _append_stack(found_key)
+ # timeout and ...
+ if _timeout(general_options.timeout):
+ await get_tree().create_timer(general_options.timeout).timeout
+ _animation_player.play(NO_COLOR, -1, 1, false)
+ _set_pattern(fade_in_options, general_options)
+ if _fade_in(fade_in_options.fade_speed):
+ await _animation_player.animation_finished
+ fade_in_finished.emit()
+ _set_clickable(true)
+ _set_out_transition()
+
+# loads scene interactive
+# connect to `load_percent_changed(value: int)` and `load_finished` signals
+# to listen to updates on your scene loading status
+func load_scene_interactive(key: String) -> void:
+ if safe_validate_scene(key):
+ set_process(true)
+ _load_scene = Scenes.scenes[key]["value"]
+ ResourceLoader.load_threaded_request(_load_scene, "", true, ResourceLoader.CACHE_MODE_IGNORE)
+
+# returns loaded scene
+#
+# If scene is not loaded, blocks and waits until scene is ready. (acts blocking in code
+# and may freeze your game, make sure scene is ready to get)
+func get_loaded_scene() -> PackedScene:
+ if _load_scene != "":
+ return ResourceLoader.load_threaded_get(_load_scene) as PackedScene
+ return null
+
+# changes scene to loaded scene
+func change_scene_to_loaded_scene(fade_out_options: Options, fade_in_options: Options, general_options: GeneralOptions) -> void:
+ if _load_scene != "":
+ var scene = ResourceLoader.load_threaded_get(_load_scene) as PackedScene
+ if scene:
+ change_scene(scene, fade_out_options, fade_in_options, general_options)
+
+# returns previous scene (scene before current scene)
+func get_previous_scene() -> String:
+ return _stack[len(_stack) - 1]
+
+# returns a specific previous scene at an exact index position
+func get_previous_scene_at(index: int) -> String:
+ if index < len(_stack):
+ return _stack[index]
+ return ""
+
+# pops from the back stack and returns previous scene (scene before current scene)
+func pop_previous_scene() -> String:
+ return _pop_stack()
+
+# returns how many scenes there are in list of previous scenes.
+func previous_scenes_length() -> int:
+ return len(_stack)
+
+# records a scene key to be used for loading scenes to know where to go after getting loaded
+# into loading scene or just for next scene to know where to go next
+func set_recorded_scene(key: String) -> void:
+ validate_scene(key)
+ _recorded_scene = key
+
+# returns recorded scene
+func get_recorded_scene() -> String:
+ return _recorded_scene
--- /dev/null
+shader_type canvas_item;
+
+uniform sampler2D custom_texture;
+uniform float cutoff: hint_range(0, 1) = 1;
+uniform float smoothness: hint_range(0, 1) = 0.1;
+uniform vec3 color = vec3(0, 0, 0);
+uniform bool inverted = false;
+uniform bool linear_fade = true;
+
+void fragment() {
+ if (!linear_fade) {
+ float value = texture(custom_texture, UV).r;
+ if (inverted) {
+ value = 1.0 - value;
+ }
+ float alpha = smoothstep(cutoff, cutoff + smoothness, value * (1.0 - smoothness) + smoothness);
+ COLOR = vec4(color.rgb, alpha);
+ } else {
+ COLOR = vec4(color.rgb, (1.0 - cutoff));
+ }
+}
\ No newline at end of file
--- /dev/null
+[gd_resource type="ShaderMaterial" load_steps=2 format=3 uid="uid://0q7ifty8us3v"]
+
+[ext_resource type="Shader" path="res://addons/scene_manager/scene_manager.gdshader" id="2"]
+
+[resource]
+shader = ExtResource("2")
+shader_parameter/cutoff = 1.0
+shader_parameter/smoothness = 0.1
+shader_parameter/color = Vector3(0, 0, 0)
+shader_parameter/inverted = false
+shader_parameter/linear_fade = true
--- /dev/null
+[gd_scene load_steps=8 format=3 uid="uid://2iy8wfgenjka"]
+
+[ext_resource type="Script" path="res://addons/scene_manager/scene_manager.gd" id="1"]
+[ext_resource type="Material" uid="uid://0q7ifty8us3v" path="res://addons/scene_manager/scene_manager.tres" id="2"]
+
+[sub_resource type="Animation" id="8"]
+length = 0.001
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("canvas/fade:material:shader_parameter/cutoff")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [1.0]
+}
+
+[sub_resource type="Animation" id="9"]
+resource_name = "color"
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("canvas/fade:material:shader_parameter/cutoff")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [0.0]
+}
+
+[sub_resource type="Animation" id="7"]
+resource_name = "fade"
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("canvas/fade:material:shader_parameter/cutoff")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0, 1),
+"transitions": PackedFloat32Array(1, 1),
+"update": 0,
+"values": [0.0, 1.0]
+}
+
+[sub_resource type="Animation" id="10"]
+resource_name = "no_color"
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("canvas/fade:material:shader_parameter/cutoff")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [1.0]
+}
+
+[sub_resource type="AnimationLibrary" id="AnimationLibrary_p2st6"]
+_data = {
+"RESET": SubResource("8"),
+"color": SubResource("9"),
+"fade": SubResource("7"),
+"no_color": SubResource("10")
+}
+
+[node name="scene_manager" type="Node2D"]
+script = ExtResource("1")
+
+[node name="canvas" type="CanvasLayer" parent="."]
+
+[node name="fade" type="ColorRect" parent="canvas"]
+material = ExtResource("2")
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+mouse_filter = 2
+color = Color(0, 0, 0, 0)
+
+[node name="animation_player" type="AnimationPlayer" parent="."]
+libraries = {
+"": SubResource("AnimationLibrary_p2st6")
+}
--- /dev/null
+#
+# Please do not edit anything in this script
+#
+# Just use the editor to change everything you want
+#
+extends Node
+
+var scenes: Dictionary = {"MainMenu":{"sections":["Scenes"],"settings":{"All":{"subsection":"","visibility":true},"Scenes":{"subsection":"","visibility":true}},"value":"res://menus/main_menu/MainMenu.tscn"},"Player":{"sections":[],"settings":{"All":{"subsection":"","visibility":false}},"value":"res://player/Player.tscn"},"_ignore_list":["res://addons","res://assets","res://game_settings"],"_sections":["Scenes","Loading Scenes"],"test_debug_msg":{"sections":[],"settings":{"All":{"subsection":"","visibility":true}},"value":"res://examples/test/test.tscn"},"unicorn_map":{"sections":["Scenes"],"settings":{"All":{"subsection":"","visibility":true},"Scenes":{"subsection":"","visibility":true}},"value":"res://levels/unicorn_level/unicorn_map.tscn"}}
--- /dev/null
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bf23tset1p8kk"
+path="res://.godot/imported/circle.png-49deb66a2171d4476d4de5b40b7c62d8.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/scene_manager/shader_patterns/circle.png"
+dest_files=["res://.godot/imported/circle.png-49deb66a2171d4476d4de5b40b7c62d8.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
--- /dev/null
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cufhni2f6bceh"
+path="res://.godot/imported/crooked_tiles.png-e3a12014fe840f5d967093ab510cab52.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/scene_manager/shader_patterns/crooked_tiles.png"
+dest_files=["res://.godot/imported/crooked_tiles.png-e3a12014fe840f5d967093ab510cab52.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
--- /dev/null
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://d3vrxnroxcbu4"
+path="res://.godot/imported/curtains.png-f5a25cbc897d336641cfbab203b5832e.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/scene_manager/shader_patterns/curtains.png"
+dest_files=["res://.godot/imported/curtains.png-f5a25cbc897d336641cfbab203b5832e.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
--- /dev/null
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://ccs0kiq21dif2"
+path="res://.godot/imported/diagonal.png-fda70de082e7ad6bc449e3580ab09c7f.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/scene_manager/shader_patterns/diagonal.png"
+dest_files=["res://.godot/imported/diagonal.png-fda70de082e7ad6bc449e3580ab09c7f.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
--- /dev/null
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bwrlswbdnxbqp"
+path="res://.godot/imported/dirt.png-44901fc6fab758502d86949b4684381b.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/scene_manager/shader_patterns/dirt.png"
+dest_files=["res://.godot/imported/dirt.png-44901fc6fab758502d86949b4684381b.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
--- /dev/null
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://b4i13ugspwbc3"
+path="res://.godot/imported/horizontal.png-350a31fe557936e31f870194c26cef69.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/scene_manager/shader_patterns/horizontal.png"
+dest_files=["res://.godot/imported/horizontal.png-350a31fe557936e31f870194c26cef69.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
--- /dev/null
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://b7f7s7bjmy5se"
+path="res://.godot/imported/pixel.png-33fdff2f0d6b4058cdb2ee40ad421cba.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/scene_manager/shader_patterns/pixel.png"
+dest_files=["res://.godot/imported/pixel.png-33fdff2f0d6b4058cdb2ee40ad421cba.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
--- /dev/null
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bhkm2r21nfdna"
+path="res://.godot/imported/radial.png-45b81257cf212bc197b861ea33052002.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/scene_manager/shader_patterns/radial.png"
+dest_files=["res://.godot/imported/radial.png-45b81257cf212bc197b861ea33052002.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
--- /dev/null
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://drxacqjryk3j8"
+path="res://.godot/imported/scribbles.png-430a0f7331a442eb053961f8e9bb82f0.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/scene_manager/shader_patterns/scribbles.png"
+dest_files=["res://.godot/imported/scribbles.png-430a0f7331a442eb053961f8e9bb82f0.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
--- /dev/null
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://en78afb13d38"
+path="res://.godot/imported/splashed_dirt.png-4f2f3115ccfbe31e3a02d7afe74bb787.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/scene_manager/shader_patterns/splashed_dirt.png"
+dest_files=["res://.godot/imported/splashed_dirt.png-4f2f3115ccfbe31e3a02d7afe74bb787.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
--- /dev/null
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cyv7ud4a4ovkd"
+path="res://.godot/imported/squares.png-a8c8144d665aa3e0d712474304ad6e6f.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/scene_manager/shader_patterns/squares.png"
+dest_files=["res://.godot/imported/squares.png-a8c8144d665aa3e0d712474304ad6e6f.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
--- /dev/null
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://vchumiux3vli"
+path="res://.godot/imported/vertical.png-5d237b9ea3cdd66e653ef032da0bd507.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/scene_manager/shader_patterns/vertical.png"
+dest_files=["res://.godot/imported/vertical.png-5d237b9ea3cdd66e653ef032da0bd507.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
--- /dev/null
+@tool
+extends Control
+
+# Nodes
+@onready var button: Button = find_child("Button")
+@onready var delete_button: Button = find_child("Delete")
+@onready var list: VBoxContainer = find_child("List")
+# Open close icons
+const _open = preload("res://addons/scene_manager/icons/GuiOptionArrowDown.svg")
+const _close = preload("res://addons/scene_manager/icons/GuiOptionArrowRight.png")
+# Instances
+const _scene_item = preload("res://addons/scene_manager/scene_item.tscn")
+
+# If it is "All" subsection, open it
+func _ready() -> void:
+ button.text = name
+ if name == "All" && get_child_count() == 0:
+ visible = false
+
+# Add child
+func add_item(item: Node) -> void:
+ item._sub_section = self
+ list.add_child(item)
+
+# Removes an item from list
+func remove_item(item: Node) -> void:
+ list.remove_child(item)
+
+# Open list
+func open() -> void:
+ list.visible = true
+ button.icon = _open
+
+# Close list
+func close() -> void:
+ list.visible = false
+ button.icon = _close
+
+# Returns list of items
+func get_items() -> Array:
+ return list.get_children()
+
+# Close Open Functionality
+func _on_button_up():
+ if button.icon == _open:
+ close()
+ else:
+ open()
+
+# Action on child counting
+func _check_count():
+ if list.get_child_count() == 0:
+ if name == "All":
+ visible = false
+ else:
+ enable_delete_button()
+ else:
+ if name == "All":
+ visible = true
+ else:
+ disable_delete_button()
+
+# When a node adds
+func child_entered():
+ _check_count()
+
+# When a node removes
+func child_exited():
+ _check_count()
+
+# Hides delete button of subsection
+func hide_delete_button():
+ delete_button.visible = false
+
+# Disables delete button
+func disable_delete_button():
+ delete_button.disabled = true
+
+# Enables delete button
+func enable_delete_button():
+ delete_button.disabled = false
+
+# Returns if we can drop here or not
+func _can_drop_data(at_position: Vector2, data: Variant) -> bool:
+ if !(data is Dictionary):
+ return false
+ data = data as Dictionary
+ return data.has("node") && data.has("parent")
+
+# Function to actually do the dropping
+func _drop_data(at_position: Vector2, data: Variant) -> void:
+ data = data as Dictionary
+ var parent = data["parent"] as Node
+ var node = data["node"] as Node
+ var setting = node.get_setting() as ItemSetting
+ if parent == self:
+ return
+ parent.remove_item(node)
+ setting.subsection = name
+ node.set_setting(setting)
+ node.set_subsection(self)
+ add_item(node)
+ open()
+
+# Button Delete
+func _on_delete_button_up():
+ queue_free()
--- /dev/null
+[gd_scene load_steps=9 format=3 uid="uid://b4edho3whn67t"]
+
+[ext_resource type="Script" path="res://addons/scene_manager/sub_section.gd" id="1_kgwwp"]
+[ext_resource type="Texture2D" uid="uid://dsgnkxtiko66g" path="res://addons/scene_manager/icons/GuiOptionArrowRight.png" id="1_yyg5g"]
+[ext_resource type="Script" path="res://addons/scene_manager/sub_section_button.gd" id="3_smgrs"]
+[ext_resource type="Texture2D" uid="uid://dw322nmqpqwfq" path="res://addons/scene_manager/icons/ImportFail.svg" id="4_chjuj"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_u6qci"]
+bg_color = Color(0.156863, 0.176471, 0.207843, 1)
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_yqp47"]
+bg_color = Color(0.219608, 0.239216, 0.266667, 1)
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_kx1we"]
+bg_color = Color(0.129412, 0.14902, 0.180392, 1)
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_uvnds"]
+bg_color = Color(0.6, 0.6, 0.6, 0)
+
+[node name="All" type="VBoxContainer"]
+offset_right = 1024.0
+offset_bottom = 23.0
+script = ExtResource("1_kgwwp")
+
+[node name="HBoxContainer" type="HBoxContainer" parent="."]
+layout_mode = 2
+
+[node name="Button" type="Button" parent="HBoxContainer"]
+custom_minimum_size = Vector2(0, 28)
+layout_mode = 2
+size_flags_horizontal = 3
+theme_override_styles/normal = SubResource("StyleBoxFlat_u6qci")
+theme_override_styles/hover = SubResource("StyleBoxFlat_yqp47")
+theme_override_styles/pressed = SubResource("StyleBoxFlat_kx1we")
+theme_override_styles/focus = SubResource("StyleBoxFlat_uvnds")
+text = "All"
+icon = ExtResource("1_yyg5g")
+alignment = 0
+script = ExtResource("3_smgrs")
+
+[node name="Delete" type="Button" parent="HBoxContainer"]
+custom_minimum_size = Vector2(28, 28)
+layout_mode = 2
+icon = ExtResource("4_chjuj")
+alignment = 0
+
+[node name="List" type="VBoxContainer" parent="."]
+visible = false
+layout_mode = 2
+
+[connection signal="button_up" from="HBoxContainer/Button" to="." method="_on_button_up"]
+[connection signal="button_up" from="HBoxContainer/Delete" to="." method="_on_delete_button_up"]
--- /dev/null
+@tool
+extends Button
+
+# Can drop here
+func _can_drop_data(at_position: Vector2, data: Variant) -> bool:
+ return get_parent().get_parent()._can_drop_data(at_position, data)
+
+# Drop here
+func _drop_data(at_position: Vector2, data: Variant) -> void:
+ get_parent().get_parent()._drop_data(at_position, data)
--- /dev/null
+[gd_resource type="StyleBoxFlat" format=3 uid="uid://21mjw515mptn"]
+
+[resource]
+content_margin_left = 6.0
+content_margin_top = 4.0
+content_margin_right = 4.0
+content_margin_bottom = 6.0
+bg_color = Color(0.203922, 0.101961, 0.129412, 1)
+border_width_bottom = 2
+border_color = Color(0, 0, 0, 0.6)
+corner_radius_top_left = 3
+corner_radius_top_right = 3
+corner_radius_bottom_right = 3
+corner_radius_bottom_left = 3
+corner_detail = 5