From: Eduardo Date: Fri, 26 Jan 2024 18:32:11 +0000 (+0100) Subject: added scene manager addon X-Git-Url: http://git.edufdez.es/?a=commitdiff_plain;h=0b4f75fa0bb5d57f8d3393dced2da28a8c2eb1c5;p=ScaryGame.git added scene manager addon --- diff --git a/addons/scene_manager/LICENSE b/addons/scene_manager/LICENSE new file mode 100644 index 0000000..ae50ee7 --- /dev/null +++ b/addons/scene_manager/LICENSE @@ -0,0 +1,21 @@ +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. diff --git a/addons/scene_manager/icons/.gitignore b/addons/scene_manager/icons/.gitignore new file mode 100644 index 0000000..a238273 --- /dev/null +++ b/addons/scene_manager/icons/.gitignore @@ -0,0 +1 @@ +*.import \ No newline at end of file diff --git a/addons/scene_manager/icons/FileDialog.svg b/addons/scene_manager/icons/FileDialog.svg new file mode 100644 index 0000000..c1e5479 --- /dev/null +++ b/addons/scene_manager/icons/FileDialog.svg @@ -0,0 +1 @@ + diff --git a/addons/scene_manager/icons/GuiChecked.svg b/addons/scene_manager/icons/GuiChecked.svg new file mode 100644 index 0000000..31b2995 --- /dev/null +++ b/addons/scene_manager/icons/GuiChecked.svg @@ -0,0 +1 @@ + diff --git a/addons/scene_manager/icons/GuiCheckedDisabled.svg b/addons/scene_manager/icons/GuiCheckedDisabled.svg new file mode 100644 index 0000000..6252176 --- /dev/null +++ b/addons/scene_manager/icons/GuiCheckedDisabled.svg @@ -0,0 +1 @@ + diff --git a/addons/scene_manager/icons/GuiOptionArrowDown.svg b/addons/scene_manager/icons/GuiOptionArrowDown.svg new file mode 100644 index 0000000..8327930 --- /dev/null +++ b/addons/scene_manager/icons/GuiOptionArrowDown.svg @@ -0,0 +1 @@ + diff --git a/addons/scene_manager/icons/GuiOptionArrowRight.png b/addons/scene_manager/icons/GuiOptionArrowRight.png new file mode 100644 index 0000000..3cdafb0 Binary files /dev/null and b/addons/scene_manager/icons/GuiOptionArrowRight.png differ diff --git a/addons/scene_manager/icons/GuiTabMenuHl.svg b/addons/scene_manager/icons/GuiTabMenuHl.svg new file mode 100644 index 0000000..b20bd80 --- /dev/null +++ b/addons/scene_manager/icons/GuiTabMenuHl.svg @@ -0,0 +1 @@ + diff --git a/addons/scene_manager/icons/ImportFail.svg b/addons/scene_manager/icons/ImportFail.svg new file mode 100644 index 0000000..582b244 --- /dev/null +++ b/addons/scene_manager/icons/ImportFail.svg @@ -0,0 +1 @@ + diff --git a/addons/scene_manager/icons/PlayOverlay.png b/addons/scene_manager/icons/PlayOverlay.png new file mode 100644 index 0000000..15b3494 Binary files /dev/null and b/addons/scene_manager/icons/PlayOverlay.png differ diff --git a/addons/scene_manager/icons/eye_close.png b/addons/scene_manager/icons/eye_close.png new file mode 100644 index 0000000..b17c6d2 Binary files /dev/null and b/addons/scene_manager/icons/eye_close.png differ diff --git a/addons/scene_manager/icons/eye_open.png b/addons/scene_manager/icons/eye_open.png new file mode 100644 index 0000000..f6d7261 Binary files /dev/null and b/addons/scene_manager/icons/eye_open.png differ diff --git a/addons/scene_manager/ignore_item.gd b/addons/scene_manager/ignore_item.gd new file mode 100644 index 0000000..e44fe7d --- /dev/null +++ b/addons/scene_manager/ignore_item.gd @@ -0,0 +1,27 @@ +@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) diff --git a/addons/scene_manager/ignore_item.tscn b/addons/scene_manager/ignore_item.tscn new file mode 100644 index 0000000..c7e0e42 --- /dev/null +++ b/addons/scene_manager/ignore_item.tscn @@ -0,0 +1,22 @@ +[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"] diff --git a/addons/scene_manager/item_setting.gd b/addons/scene_manager/item_setting.gd new file mode 100644 index 0000000..0cc2c82 --- /dev/null +++ b/addons/scene_manager/item_setting.gd @@ -0,0 +1,27 @@ +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) diff --git a/addons/scene_manager/label.tscn b/addons/scene_manager/label.tscn new file mode 100644 index 0000000..8211d21 --- /dev/null +++ b/addons/scene_manager/label.tscn @@ -0,0 +1,10 @@ +[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 diff --git a/addons/scene_manager/manager.gd b/addons/scene_manager/manager.gd new file mode 100644 index 0000000..b02ae80 --- /dev/null +++ b/addons/scene_manager/manager.gd @@ -0,0 +1,563 @@ +@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 diff --git a/addons/scene_manager/menu.tscn b/addons/scene_manager/menu.tscn new file mode 100644 index 0000000..0f0d558 --- /dev/null +++ b/addons/scene_manager/menu.tscn @@ -0,0 +1,162 @@ +[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"] diff --git a/addons/scene_manager/plugin.cfg b/addons/scene_manager/plugin.cfg new file mode 100644 index 0000000..26ea4e1 --- /dev/null +++ b/addons/scene_manager/plugin.cfg @@ -0,0 +1,7 @@ +[plugin] + +name="scene_manager" +description="A powerful scene transition manager for godot." +author="Maktoobgar" +version="3.8.0" +script="plugin.gd" diff --git a/addons/scene_manager/plugin.gd b/addons/scene_manager/plugin.gd new file mode 100644 index 0000000..6b6b0a6 --- /dev/null +++ b/addons/scene_manager/plugin.gd @@ -0,0 +1,60 @@ +@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() diff --git a/addons/scene_manager/popup_button.gd b/addons/scene_manager/popup_button.gd new file mode 100644 index 0000000..dd5cc2f --- /dev/null +++ b/addons/scene_manager/popup_button.gd @@ -0,0 +1,6 @@ +@tool +extends Button + +# Get and return drag data +func _get_drag_data(at_position: Vector2) -> Variant: + return get_parent()._get_drag_data(at_position) diff --git a/addons/scene_manager/scene_item.gd b/addons/scene_manager/scene_item.gd new file mode 100644 index 0000000..46c8f12 --- /dev/null +++ b/addons/scene_manager/scene_item.gd @@ -0,0 +1,183 @@ +@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, + } diff --git a/addons/scene_manager/scene_item.tscn b/addons/scene_manager/scene_item.tscn new file mode 100644 index 0000000..7c77d74 --- /dev/null +++ b/addons/scene_manager/scene_item.tscn @@ -0,0 +1,48 @@ +[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"] diff --git a/addons/scene_manager/scene_list.gd b/addons/scene_manager/scene_list.gd new file mode 100644 index 0000000..5d52795 --- /dev/null +++ b/addons/scene_manager/scene_list.gd @@ -0,0 +1,240 @@ +@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() diff --git a/addons/scene_manager/scene_list.tscn b/addons/scene_manager/scene_list.tscn new file mode 100644 index 0000000..40a36c0 --- /dev/null +++ b/addons/scene_manager/scene_list.tscn @@ -0,0 +1,35 @@ +[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"] diff --git a/addons/scene_manager/scene_manager.gd b/addons/scene_manager/scene_manager.gd new file mode 100644 index 0000000..df8824d --- /dev/null +++ b/addons/scene_manager/scene_manager.gd @@ -0,0 +1,474 @@ +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 diff --git a/addons/scene_manager/scene_manager.gdshader b/addons/scene_manager/scene_manager.gdshader new file mode 100644 index 0000000..51770c1 --- /dev/null +++ b/addons/scene_manager/scene_manager.gdshader @@ -0,0 +1,21 @@ +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 diff --git a/addons/scene_manager/scene_manager.tres b/addons/scene_manager/scene_manager.tres new file mode 100644 index 0000000..1dcb98e --- /dev/null +++ b/addons/scene_manager/scene_manager.tres @@ -0,0 +1,11 @@ +[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 diff --git a/addons/scene_manager/scene_manager.tscn b/addons/scene_manager/scene_manager.tscn new file mode 100644 index 0000000..83968d1 --- /dev/null +++ b/addons/scene_manager/scene_manager.tscn @@ -0,0 +1,92 @@ +[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") +} diff --git a/addons/scene_manager/scenes.gd b/addons/scene_manager/scenes.gd new file mode 100644 index 0000000..b6ebcd3 --- /dev/null +++ b/addons/scene_manager/scenes.gd @@ -0,0 +1,8 @@ +# +# 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"}} diff --git a/addons/scene_manager/shader_patterns/circle.png b/addons/scene_manager/shader_patterns/circle.png new file mode 100644 index 0000000..7e3eb48 Binary files /dev/null and b/addons/scene_manager/shader_patterns/circle.png differ diff --git a/addons/scene_manager/shader_patterns/circle.png.import b/addons/scene_manager/shader_patterns/circle.png.import new file mode 100644 index 0000000..1643a02 --- /dev/null +++ b/addons/scene_manager/shader_patterns/circle.png.import @@ -0,0 +1,34 @@ +[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 diff --git a/addons/scene_manager/shader_patterns/crooked_tiles.png b/addons/scene_manager/shader_patterns/crooked_tiles.png new file mode 100644 index 0000000..1e22f29 Binary files /dev/null and b/addons/scene_manager/shader_patterns/crooked_tiles.png differ diff --git a/addons/scene_manager/shader_patterns/crooked_tiles.png.import b/addons/scene_manager/shader_patterns/crooked_tiles.png.import new file mode 100644 index 0000000..b7fc6b9 --- /dev/null +++ b/addons/scene_manager/shader_patterns/crooked_tiles.png.import @@ -0,0 +1,34 @@ +[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 diff --git a/addons/scene_manager/shader_patterns/curtains.png b/addons/scene_manager/shader_patterns/curtains.png new file mode 100644 index 0000000..c9f096e Binary files /dev/null and b/addons/scene_manager/shader_patterns/curtains.png differ diff --git a/addons/scene_manager/shader_patterns/curtains.png.import b/addons/scene_manager/shader_patterns/curtains.png.import new file mode 100644 index 0000000..1559b09 --- /dev/null +++ b/addons/scene_manager/shader_patterns/curtains.png.import @@ -0,0 +1,34 @@ +[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 diff --git a/addons/scene_manager/shader_patterns/diagonal.png b/addons/scene_manager/shader_patterns/diagonal.png new file mode 100644 index 0000000..30ce2f9 Binary files /dev/null and b/addons/scene_manager/shader_patterns/diagonal.png differ diff --git a/addons/scene_manager/shader_patterns/diagonal.png.import b/addons/scene_manager/shader_patterns/diagonal.png.import new file mode 100644 index 0000000..3b35322 --- /dev/null +++ b/addons/scene_manager/shader_patterns/diagonal.png.import @@ -0,0 +1,34 @@ +[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 diff --git a/addons/scene_manager/shader_patterns/dirt.png b/addons/scene_manager/shader_patterns/dirt.png new file mode 100644 index 0000000..3a24233 Binary files /dev/null and b/addons/scene_manager/shader_patterns/dirt.png differ diff --git a/addons/scene_manager/shader_patterns/dirt.png.import b/addons/scene_manager/shader_patterns/dirt.png.import new file mode 100644 index 0000000..2d55670 --- /dev/null +++ b/addons/scene_manager/shader_patterns/dirt.png.import @@ -0,0 +1,34 @@ +[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 diff --git a/addons/scene_manager/shader_patterns/horizontal.png b/addons/scene_manager/shader_patterns/horizontal.png new file mode 100644 index 0000000..6dc32d5 Binary files /dev/null and b/addons/scene_manager/shader_patterns/horizontal.png differ diff --git a/addons/scene_manager/shader_patterns/horizontal.png.import b/addons/scene_manager/shader_patterns/horizontal.png.import new file mode 100644 index 0000000..6eed526 --- /dev/null +++ b/addons/scene_manager/shader_patterns/horizontal.png.import @@ -0,0 +1,34 @@ +[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 diff --git a/addons/scene_manager/shader_patterns/pixel.png b/addons/scene_manager/shader_patterns/pixel.png new file mode 100644 index 0000000..d93ee9f Binary files /dev/null and b/addons/scene_manager/shader_patterns/pixel.png differ diff --git a/addons/scene_manager/shader_patterns/pixel.png.import b/addons/scene_manager/shader_patterns/pixel.png.import new file mode 100644 index 0000000..1e37d2e --- /dev/null +++ b/addons/scene_manager/shader_patterns/pixel.png.import @@ -0,0 +1,34 @@ +[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 diff --git a/addons/scene_manager/shader_patterns/radial.png b/addons/scene_manager/shader_patterns/radial.png new file mode 100644 index 0000000..926330b Binary files /dev/null and b/addons/scene_manager/shader_patterns/radial.png differ diff --git a/addons/scene_manager/shader_patterns/radial.png.import b/addons/scene_manager/shader_patterns/radial.png.import new file mode 100644 index 0000000..97a4780 --- /dev/null +++ b/addons/scene_manager/shader_patterns/radial.png.import @@ -0,0 +1,34 @@ +[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 diff --git a/addons/scene_manager/shader_patterns/scribbles.png b/addons/scene_manager/shader_patterns/scribbles.png new file mode 100644 index 0000000..428b956 Binary files /dev/null and b/addons/scene_manager/shader_patterns/scribbles.png differ diff --git a/addons/scene_manager/shader_patterns/scribbles.png.import b/addons/scene_manager/shader_patterns/scribbles.png.import new file mode 100644 index 0000000..f9b5eb6 --- /dev/null +++ b/addons/scene_manager/shader_patterns/scribbles.png.import @@ -0,0 +1,34 @@ +[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 diff --git a/addons/scene_manager/shader_patterns/splashed_dirt.png b/addons/scene_manager/shader_patterns/splashed_dirt.png new file mode 100644 index 0000000..4d706c2 Binary files /dev/null and b/addons/scene_manager/shader_patterns/splashed_dirt.png differ diff --git a/addons/scene_manager/shader_patterns/splashed_dirt.png.import b/addons/scene_manager/shader_patterns/splashed_dirt.png.import new file mode 100644 index 0000000..f9e3775 --- /dev/null +++ b/addons/scene_manager/shader_patterns/splashed_dirt.png.import @@ -0,0 +1,34 @@ +[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 diff --git a/addons/scene_manager/shader_patterns/squares.png b/addons/scene_manager/shader_patterns/squares.png new file mode 100644 index 0000000..d471ef7 Binary files /dev/null and b/addons/scene_manager/shader_patterns/squares.png differ diff --git a/addons/scene_manager/shader_patterns/squares.png.import b/addons/scene_manager/shader_patterns/squares.png.import new file mode 100644 index 0000000..edf0061 --- /dev/null +++ b/addons/scene_manager/shader_patterns/squares.png.import @@ -0,0 +1,34 @@ +[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 diff --git a/addons/scene_manager/shader_patterns/vertical.png b/addons/scene_manager/shader_patterns/vertical.png new file mode 100644 index 0000000..394ca1e Binary files /dev/null and b/addons/scene_manager/shader_patterns/vertical.png differ diff --git a/addons/scene_manager/shader_patterns/vertical.png.import b/addons/scene_manager/shader_patterns/vertical.png.import new file mode 100644 index 0000000..ec5634e --- /dev/null +++ b/addons/scene_manager/shader_patterns/vertical.png.import @@ -0,0 +1,34 @@ +[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 diff --git a/addons/scene_manager/sub_section.gd b/addons/scene_manager/sub_section.gd new file mode 100644 index 0000000..1b9d65e --- /dev/null +++ b/addons/scene_manager/sub_section.gd @@ -0,0 +1,107 @@ +@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() diff --git a/addons/scene_manager/sub_section.tscn b/addons/scene_manager/sub_section.tscn new file mode 100644 index 0000000..3c15783 --- /dev/null +++ b/addons/scene_manager/sub_section.tscn @@ -0,0 +1,52 @@ +[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"] diff --git a/addons/scene_manager/sub_section_button.gd b/addons/scene_manager/sub_section_button.gd new file mode 100644 index 0000000..fe1a07f --- /dev/null +++ b/addons/scene_manager/sub_section_button.gd @@ -0,0 +1,10 @@ +@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) diff --git a/addons/scene_manager/themes/line_edit_duplicate.tres b/addons/scene_manager/themes/line_edit_duplicate.tres new file mode 100644 index 0000000..a895827 --- /dev/null +++ b/addons/scene_manager/themes/line_edit_duplicate.tres @@ -0,0 +1,15 @@ +[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