diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 3fdca06..931430e 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -33,6 +33,10 @@ jobs: id: "hide_private_properties", name: "Hide Private Properties", } + - { + id: "icon_explorer", + name: "Icon Explorer", + } - { id: "icons_patcher", name: "Icons Patcher", diff --git a/README.md b/README.md index b0d057b..f6f8267 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,8 @@ If you import any plugin or open a Godot project for the first time, the plugins Add Git SHA as project setting. - [Hide Private Properties](#hide-private-properties) Hide private properties of instantiated child scenes. +- [Icon Explorer](#icon-explorer) + Browse different icons and save them. - [Icons Patcher](#icons-patcher) Patch Pictogrammers icons to white. - [License Manager](#license-manager) @@ -59,6 +61,10 @@ You are also not able to use the property `custom_minimum_size` anymore as it is ### Changelog +#### 3.1.1 + +- Code improvement + #### 3.1.0 - Require Godot 4.2 @@ -187,6 +193,38 @@ This plugin will hide exported private properties in the inspector for instantia --- +## Icon Explorer + +Browse and save icons from popular icon collections. + +Install or update them via the options menu in the right upper corner. This can take several minutes. + +The following collections are available: + +- [Bootstrap Icons](https://github.com/twbs/icons) +- [Font Awesome 6](https://github.com/FortAwesome/Font-Awesome) +- [Material Design](https://github.com/Templarian/MaterialDesign-SVG) +- [Simple Icons](https://github.com/simple-icons/simple-icons) +- [tabler Icons](https://github.com/tabler/tabler-icons) + +Downloaded data is saved into `.godot/cache/icon_explorer` to avoid importing it. + +### Compatibility + +- Godot 4.2 + +### Screenshot + +![Icon Explorer screenshot](./doc/icon_explorer.png "Icon Explorer") + +### Changelog + +#### 1.0.0 + +- Add icon explorer + +--- + ## Icons Patcher If you use Material Design icons from [Pictogrammers](https://pictogrammers.com/library/mdi/), they come without any fill color, automatically rendered black. This is not a convenient color as it makes it impossible to modulate the color. The icon patcher provides a utility to automatically patch the icons to white color. @@ -201,6 +239,14 @@ Then use `Project` -> `Tools` -> `Icons Patcher` to patch the icons. ### Changelog +#### 1.3.2 + +- Code improvement + +#### 1.3.1 + +- Replace legacy code + #### 1.3.0 - Require Godot 4.2 @@ -251,6 +297,11 @@ License class. ### Changelog +#### 1.7.6 + +- Fix scene id +- Code improvement + #### 1.7.5 - Fix license file existing check @@ -375,6 +426,10 @@ If not log level is set, the log level of the parent logger will be used. ### Changelog +#### 1.5.1 + +- Code improvement + #### 1.5.0 - Require Godot 4.2 @@ -500,6 +555,10 @@ Shift JIS encoding utility. ### Changelog +#### 1.1.1 + +- Code optimizing + #### 1.1.0 - Require Godot 4.2 @@ -543,6 +602,10 @@ Let you apply the icon color theme properties for the texture button. Uses `self ### Changelog +#### 1.3.1 + +- Code improvement + #### 1.3.0 - Require Godot 4.2 diff --git a/addons/aspect_ratio_resize_container/aspect_ratio_resize_container.gd b/addons/aspect_ratio_resize_container/aspect_ratio_resize_container.gd index 62661eb..a95bc2d 100644 --- a/addons/aspect_ratio_resize_container/aspect_ratio_resize_container.gd +++ b/addons/aspect_ratio_resize_container/aspect_ratio_resize_container.gd @@ -18,16 +18,16 @@ func _get_children_min_size() -> Vector2: if !(child is Control) || !child.visible: continue var child_min: Vector2 = child.get_combined_minimum_size() - min_size.x = max(min_size.x, child_min.x) - min_size.y = max(min_size.y, child_min.y) + min_size.x = maxf(min_size.x, child_min.x) + min_size.y = maxf(min_size.y, child_min.y) return min_size func _get_minimum_size() -> Vector2: var min_size: Vector2 = self._get_children_min_size() if self.stretch_mode == STRETCH_WIDTH_CONTROLS_HEIGHT: - var width: float = max(min_size.x, self.size.x) + var width: float = maxf(min_size.x, self.size.x) min_size.y = width * self.ratio elif self.stretch_mode == STRETCH_HEIGHT_CONTROLS_WIDTH: - var height: float = max(min_size.y, self.size.y) + var height: float = maxf(min_size.y, self.size.y) min_size.x = height * self.ratio return min_size diff --git a/addons/aspect_ratio_resize_container/plugin.cfg b/addons/aspect_ratio_resize_container/plugin.cfg index bd78ebe..5bdaf94 100644 --- a/addons/aspect_ratio_resize_container/plugin.cfg +++ b/addons/aspect_ratio_resize_container/plugin.cfg @@ -3,7 +3,7 @@ name="Aspect Ratio Resize Container" description="Extending `AspectRatioContainer` and update it's own minimum size based on the children." author="Iceflower S" -version="3.1.0" +version="3.1.1" script="plugin.gd" license="MIT" repository="https://github.com/kenyoni-software/godot-addons" diff --git a/addons/glogging/glogging.gd b/addons/glogging/glogging.gd index de831f7..6ff3991 100644 --- a/addons/glogging/glogging.gd +++ b/addons/glogging/glogging.gd @@ -28,7 +28,7 @@ class Logger: self._log_level = level func log_level() -> int: - if self.parent != null and self._log_level == LEVEL_NOTSET: + if self.parent != null && self._log_level == LEVEL_NOTSET: return self.parent.log_level() return self._log_level diff --git a/addons/glogging/plugin.cfg b/addons/glogging/plugin.cfg index 2199947..2989e76 100644 --- a/addons/glogging/plugin.cfg +++ b/addons/glogging/plugin.cfg @@ -3,7 +3,7 @@ name="GLogging" description="Adds a 'GLogging' singleton." author="Iceflower S" -version="1.5.0" +version="1.5.1" script="plugin.gd" license="MIT" repository="https://github.com/kenyoni-software/godot-addons" diff --git a/addons/icon_explorer/internal/scripts/collection.gd b/addons/icon_explorer/internal/scripts/collection.gd new file mode 100644 index 0000000..21d0dd9 --- /dev/null +++ b/addons/icon_explorer/internal/scripts/collection.gd @@ -0,0 +1,66 @@ +extends RefCounted + +const Io := preload("res://addons/icon_explorer/internal/scripts/tools/io.gd") +const Icon := preload("res://addons/icon_explorer/internal/scripts/icon.gd") + +# target svg texture size +const TEXTURE_SIZE: float = 128.0 + +var name: String +var version: String +var author: String +var license: String +var license_text: String +var web: String + +## base size of svg +var svg_size: float + +# is set on registering it at the IconDatabase +var _id: int = -1 + +func id() -> int: + return self._id + +func is_installed() -> bool: + return DirAccess.dir_exists_absolute(self.icon_directory()) + +# VIRTUAL +func convert_icon_colored(buffer: String, color: String) -> String: + return "" + +# VIRTUAL +# called in a thread +func load() -> Array[Icon]: + assert(false, "virtual function") + return [] + +# VIRTUAL +# called in a thread +func install(http: HTTPRequest, version: String) -> Error: + assert(false, "virtual function") + return Error.FAILED + +# VIRTUAL +# called in a thread +func remove() -> Error: + if Io.rrm_dir(self.directory()): + return Error.OK + return Error.FAILED + +# VIRTUAL +func icon_directory() -> String: + assert(false, "virtual function") + return "" + +func directory() -> String: + if Engine.is_editor_hint(): + if ProjectSettings.get_setting("application/config/use_hidden_project_data_directory", true): + return "res://.godot/cache/icon_explorer".path_join(self.directory_name()) + else: + return "res://godot/cache/icon_explorer".path_join(self.directory_name()) + assert(false, "TODO") + return "" + +func directory_name() -> String: + return self.name.to_snake_case() diff --git a/addons/icon_explorer/internal/scripts/collections/bootstrap.gd b/addons/icon_explorer/internal/scripts/collections/bootstrap.gd new file mode 100644 index 0000000..f072e76 --- /dev/null +++ b/addons/icon_explorer/internal/scripts/collections/bootstrap.gd @@ -0,0 +1,124 @@ +extends "res://addons/icon_explorer/internal/scripts/collection.gd" + +const IconBootstrap := preload("res://addons/icon_explorer/internal/scripts/collections/icon_bootstrap.gd") +const ZipUnpacker := preload("res://addons/icon_explorer/internal/scripts/tools/zip_unpacker.gd") + +const _DOWNLOAD_FILE: String = "https://github.com/twbs/icons/archive/main.zip" + +func _init() -> void: + self.name = "Bootstrap Icons" + self.version = "" + self.author = "The Bootstrap Authors" + self.license = "MIT" + self.web = "https://github.com/twbs/icons" + self.svg_size = 16.0 + +# OVERRIDE +func convert_icon_colored(buffer: String, color: String) -> String: + return buffer.replace("currentColor", "#" + color) + +# OVERRIDE +func load() -> Array: + var dir: DirAccess = DirAccess.open(self._meta_directory()) + if !dir: + return [] + + var icons: Array[Icon] = [] + var buffers: PackedStringArray = PackedStringArray() + dir.list_dir_begin() + var file_name: String = dir.get_next() + while file_name != "": + if dir.current_is_dir(): + continue + var res: Array = self._load_item(file_name) + if res.size() == 2: + icons.append(res[0]) + buffers.append(res[1]) + file_name = dir.get_next() + + var parser_version: JSON = JSON.new() + var res_version: int = parser_version.parse(FileAccess.get_file_as_string(self.directory().path_join("icons-main/package.json"))) + if res_version != OK: + push_warning("could not parse bootstrap package.json: '%s'", [parser_version.get_error_message()]) + return [[], PackedStringArray()] + self.version = parser_version.data["version"] + + return [icons, buffers] + +func _load_item(file_name: String) -> Array: + var icon: IconBootstrap = IconBootstrap.new() + icon.collection = self + + var meta: String = FileAccess.get_file_as_string(self._meta_directory().path_join(file_name)) + if meta == "": + push_warning("could not read bootstrap meta file '", file_name, "'") + return [] + + var cur_token: int = 0 + for line: String in meta.split("\n"): + if line.begins_with("title: "): + icon.name = line.lstrip("title: ") + cur_token = 0 + continue + if line == "categories:": + cur_token = 1 + continue + if line == "tags:": + cur_token = 2 + continue + if line.begins_with(" - "): + match cur_token: + 1: + icon.categories.append(line.lstrip(" - ")) + 2: + icon.tags.append(line.lstrip(" - ")) + continue + if line.begins_with("added:"): + icon.version_added = line.lstrip("added: ") + + if icon.name == "": + push_warning("bootstrap '", file_name, "' has no name") + return [] + + icon.icon_path = self.icon_directory().path_join(file_name.get_basename() + ".svg") + var buffer: String = FileAccess.get_file_as_string(icon.icon_path) + if buffer == "": + push_warning("could not load '" + icon.icon_path + "'") + return [] + + return [icon, self.convert_icon_colored(buffer, "FFFFFF")] + +# OVERRIDE +func install(http: HTTPRequest, _version: String) -> Error: + DirAccess.make_dir_recursive_absolute(self.directory()) + var zip_path: String = self.directory().path_join("icons.zip") + http.download_file = zip_path + var downloader: Io.FileDownloader = Io.FileDownloader.new(http) + downloader.request.bind(_DOWNLOAD_FILE).call_deferred() + + downloader.wait() + if downloader.result != HTTPRequest.RESULT_SUCCESS: + return Error.FAILED + + var unzipper: ZipUnpacker = ZipUnpacker.new(zip_path, self.directory(), [ + "icons-main/package.json", + "icons-main/icons/", + "icons-main/docs/content/icons/", + "icons-main/LICENSE", + ]) + if !unzipper.unpack_mt(maxi(OS.get_processor_count() / 2, 1)): + return Error.FAILED + DirAccess.remove_absolute(zip_path) + return Error.OK + +# OVERRIDE +func remove() -> Error: + self.version = "" + return super.remove() + +# OVERRIDE +func icon_directory() -> String: + return self.directory().path_join("icons-main/icons/") + +func _meta_directory() -> String: + return self.directory().path_join("icons-main/docs/content/icons/") diff --git a/addons/icon_explorer/internal/scripts/collections/font_awesome.gd b/addons/icon_explorer/internal/scripts/collections/font_awesome.gd new file mode 100644 index 0000000..3390882 --- /dev/null +++ b/addons/icon_explorer/internal/scripts/collections/font_awesome.gd @@ -0,0 +1,91 @@ +extends "res://addons/icon_explorer/internal/scripts/collection.gd" + +const IconFontAwesome := preload("res://addons/icon_explorer/internal/scripts/collections/icon_font_awesome.gd") +const ZipUnpacker := preload("res://addons/icon_explorer/internal/scripts/tools/zip_unpacker.gd") + +const _DOWNLOAD_FILE: String = "https://github.com/FortAwesome/Font-Awesome/archive/6.x.zip" + +func _init() -> void: + self.name = "Font Awesome 6" + self.version = "" + self.author = "The Font Awesome Team" + self.license = "CC BY 4.0" + self.web = "https://github.com/FortAwesome/Font-Awesome" + self.svg_size = 512.0 + +# OVERRIDE +func convert_icon_colored(buffer: String, color: String) -> String: + return ' Array: + var parser_version: JSON = JSON.new() + var res_version: int = parser_version.parse(FileAccess.get_file_as_string(self.directory().path_join("Font-Awesome-6.x/js-packages/@fortawesome/fontawesome-free/package.json"))) + if res_version != OK: + push_warning("could not parse font awesome package.json: '%s'", [parser_version.get_error_message()]) + return [[], PackedStringArray()] + self.version = parser_version.data["version"] + + var meta_string: String = FileAccess.get_file_as_string(self.directory().path_join("Font-Awesome-6.x/metadata/icons.json")) + var icons: Array[Icon] = [] + var buffers: PackedStringArray = PackedStringArray() + var start_idx: int = meta_string.find("\n \"") + var parser: JSON = JSON.new() + while start_idx > 0: + var end_idx: int = meta_string.find("\n \"", start_idx + 1) + var meta: String + if end_idx != -1: + meta = "{" + meta_string.substr(start_idx, end_idx - start_idx - 1) + "}" + else: + meta = "{" + meta_string.substr(start_idx) + "}" + start_idx = end_idx + var res: int = parser.parse(meta) + if res != OK: + push_warning("could not parse font awesome meta: '%s'", [parser.get_error_message()]) + continue + var icon_id: String = parser.data.keys()[0] + var item: Dictionary = parser.data.values()[0] + for style: String in item.get("styles", []): + var icon: IconFontAwesome = IconFontAwesome.new() + icon.collection = self + icon.name = item["label"] + icon.icon_path = self.icon_directory().path_join(style + "/" + icon_id + ".svg") + + icon.style = style + icon.aliases = item.get("aliases", {}).get("names", PackedStringArray()) + icon.search_terms = item.get("search", {}).get("terms", PackedStringArray()) + icons.append(icon) + buffers.append(self.convert_icon_colored(item["svg"][style]["raw"], "FFFFFF")) + + return [icons, buffers] + +# OVERRIDE +func install(http: HTTPRequest, _version: String) -> Error: + DirAccess.make_dir_recursive_absolute(self.directory()) + var zip_path: String = self.directory().path_join("icons.zip") + http.download_file = zip_path + var downloader: Io.FileDownloader = Io.FileDownloader.new(http) + downloader.request.bind(_DOWNLOAD_FILE).call_deferred() + downloader.wait() + if downloader.result != HTTPRequest.RESULT_SUCCESS: + return Error.FAILED + + var unzipper: ZipUnpacker = ZipUnpacker.new(zip_path, self.directory(), [ + "Font-Awesome-6.x/js-packages/@fortawesome/fontawesome-free/package.json", + "Font-Awesome-6.x/svgs/", + "Font-Awesome-6.x/metadata/icons.json", + "Font-Awesome-6.x/LICENSE.txt", + ]) + if !unzipper.unpack_mt(maxi(OS.get_processor_count() / 2, 1)): + return Error.FAILED + DirAccess.remove_absolute(zip_path) + return Error.OK + +# OVERRIDE +func remove() -> Error: + self.version = "" + return super.remove() + +# OVERRIDE +func icon_directory() -> String: + return self.directory().path_join("Font-Awesome-6.x/svgs/") diff --git a/addons/icon_explorer/internal/scripts/collections/icon_bootstrap.gd b/addons/icon_explorer/internal/scripts/collections/icon_bootstrap.gd new file mode 100644 index 0000000..30167d7 --- /dev/null +++ b/addons/icon_explorer/internal/scripts/collections/icon_bootstrap.gd @@ -0,0 +1,17 @@ +extends "res://addons/icon_explorer/internal/scripts/icon.gd" + +var tags: PackedStringArray +var categories: PackedStringArray +var version_added: String + +func match(keyword: String) -> int: + var name_match: int = self.get_name_match(keyword) + if name_match != 0: + return name_match + for tag: String in self.tags: + if tag.to_lower().contains(keyword): + return 7 + for category: String in self.categories: + if category.to_lower().contains(keyword): + return 5 + return 0 diff --git a/addons/icon_explorer/internal/scripts/collections/icon_font_awesome.gd b/addons/icon_explorer/internal/scripts/collections/icon_font_awesome.gd new file mode 100644 index 0000000..4c9ed79 --- /dev/null +++ b/addons/icon_explorer/internal/scripts/collections/icon_font_awesome.gd @@ -0,0 +1,19 @@ +extends "res://addons/icon_explorer/internal/scripts/icon.gd" + +var style: String +var aliases: PackedStringArray +var search_terms: PackedStringArray + +func match(keyword: String) -> int: + var name_match: int = self.get_name_match(keyword) + if name_match != 0: + return name_match + for alias: String in self.aliases: + if alias.to_lower().contains(keyword): + return 7 + for term: String in self.search_terms: + if term.to_lower().contains(keyword): + return 5 + if self.style.to_lower().contains(keyword): + return 3 + return 0 diff --git a/addons/icon_explorer/internal/scripts/collections/icon_material_design.gd b/addons/icon_explorer/internal/scripts/collections/icon_material_design.gd new file mode 100644 index 0000000..9346302 --- /dev/null +++ b/addons/icon_explorer/internal/scripts/collections/icon_material_design.gd @@ -0,0 +1,22 @@ +extends "res://addons/icon_explorer/internal/scripts/icon.gd" + +var aliases: PackedStringArray +var tags: PackedStringArray +var author: String +var version: String +var deprecated: bool + + +func match(keyword: String) -> int: + var name_match: int = self.get_name_match(keyword) + if name_match != 0: + return name_match + for alias: String in self.aliases: + if alias.to_lower().contains(keyword): + return 7 + for tag: String in self.tags: + if tag.to_lower().contains(keyword): + return 7 + if keyword == "deprecated" && self.deprecated: + return 1 + return 0 diff --git a/addons/icon_explorer/internal/scripts/collections/icon_simple_icons.gd b/addons/icon_explorer/internal/scripts/collections/icon_simple_icons.gd new file mode 100644 index 0000000..4e0e605 --- /dev/null +++ b/addons/icon_explorer/internal/scripts/collections/icon_simple_icons.gd @@ -0,0 +1,20 @@ +extends "res://addons/icon_explorer/internal/scripts/icon.gd" + +var hex: Color +var source: String +var aliases: PackedStringArray +var license: String +var license_link: String +var guidelines: String + + +func match(keyword: String) -> int: + var name_match: int = self.get_name_match(keyword) + if name_match != 0: + return name_match + for alias: String in self.aliases: + if alias.to_lower().contains(keyword): + return 7 + if self.hex.to_html().to_lower() == keyword || "#"+self.hex.to_html().to_lower() == keyword: + return 1 + return 0 diff --git a/addons/icon_explorer/internal/scripts/collections/icon_tabler.gd b/addons/icon_explorer/internal/scripts/collections/icon_tabler.gd new file mode 100644 index 0000000..ed8dd6a --- /dev/null +++ b/addons/icon_explorer/internal/scripts/collections/icon_tabler.gd @@ -0,0 +1,16 @@ +extends "res://addons/icon_explorer/internal/scripts/icon.gd" + +var category: String +var tags: PackedStringArray +var version: String + +func match(keyword: String) -> int: + var name_match: int = self.get_name_match(keyword) + if name_match != 0: + return name_match + for tag: String in self.tags: + if tag.to_lower().contains(keyword): + return 7 + if self.category.to_lower().contains(keyword): + return 5 + return 0 diff --git a/addons/icon_explorer/internal/scripts/collections/material_design.gd b/addons/icon_explorer/internal/scripts/collections/material_design.gd new file mode 100644 index 0000000..a560bad --- /dev/null +++ b/addons/icon_explorer/internal/scripts/collections/material_design.gd @@ -0,0 +1,88 @@ +extends "res://addons/icon_explorer/internal/scripts/collection.gd" + +const IconMaterialDesign := preload("res://addons/icon_explorer/internal/scripts/collections/icon_material_design.gd") +const ZipUnpacker := preload("res://addons/icon_explorer/internal/scripts/tools/zip_unpacker.gd") + +const _DOWNLOAD_FILE: String = "https://github.com/Templarian/MaterialDesign-SVG/archive/master.zip" + +func _init() -> void: + self.name = "Material Design" + self.version = "" + self.author = "Austin Andrews" + self.license = "Apache 2.0" + self.web = "https://github.com/Templarian/MaterialDesign-SVG" + self.svg_size = 24.0 + +# OVERRIDE +func convert_icon_colored(buffer: String, color: String) -> String: + return ' Array: + var parser_version: JSON = JSON.new() + var res_version: int = parser_version.parse(FileAccess.get_file_as_string(self.directory().path_join("MaterialDesign-SVG-master/package.json"))) + if res_version != OK: + push_warning("could not parse MDI package.json: '%s'", [parser_version.get_error_message()]) + return [[], PackedStringArray()] + self.version = parser_version.data["version"] + + var parser: JSON = JSON.new() + var res: int = parser.parse(FileAccess.get_file_as_string(self.directory().path_join("MaterialDesign-SVG-master/meta.json"))) + if res != OK: + push_warning("could not parse MDI meta.json: '%s'", [parser.get_error_message()]) + return [[], PackedStringArray()] + + var icon_path: String = self.icon_directory() + var icons: Array[Icon] = [] + var buffers: PackedStringArray = PackedStringArray() + for item: Dictionary in parser.data: + var icon: IconMaterialDesign = IconMaterialDesign.new() + icon.collection = self + icon.name = item["name"] + icon.icon_path = icon_path.path_join(icon.name + ".svg") + + icon.author = item.get("author", "") + icon.version = item.get("version", "") + icon.aliases = item.get("aliases", PackedStringArray()) + icon.tags = item.get("tags", PackedStringArray()) + icon.deprecated = item.get("deprecated", false) + + var buffer: String = FileAccess.get_file_as_string(icon.icon_path) + if buffer == "": + push_warning("could not load '" + icon.icon_path + "'") + continue + icons.append(icon) + buffers.append(self.convert_icon_colored(buffer, "FFFFFF")) + return [icons, buffers] + +# OVERRIDE +func install(http: HTTPRequest, _version: String) -> Error: + DirAccess.make_dir_recursive_absolute(self.directory()) + var zip_path: String = self.directory().path_join("icons.zip") + http.download_file = zip_path + var downloader: Io.FileDownloader = Io.FileDownloader.new(http) + downloader.request.bind(_DOWNLOAD_FILE).call_deferred() + + downloader.wait() + if downloader.result != HTTPRequest.RESULT_SUCCESS: + return Error.FAILED + + var unzipper: ZipUnpacker = ZipUnpacker.new(zip_path, self.directory(), [ + "MaterialDesign-SVG-master/package.json", + "MaterialDesign-SVG-master/svg/", + "MaterialDesign-SVG-master/meta.json", + "MaterialDesign-SVG-master/LICENSE", + ]) + if !unzipper.unpack_mt(maxi(OS.get_processor_count() / 2, 1)): + return Error.FAILED + DirAccess.remove_absolute(zip_path) + return Error.OK + +# OVERRIDE +func remove() -> Error: + self.version = "" + return super.remove() + +# OVERRIDE +func icon_directory() -> String: + return self.directory().path_join("MaterialDesign-SVG-master/svg/") diff --git a/addons/icon_explorer/internal/scripts/collections/simple_icons.gd b/addons/icon_explorer/internal/scripts/collections/simple_icons.gd new file mode 100644 index 0000000..3e8187c --- /dev/null +++ b/addons/icon_explorer/internal/scripts/collections/simple_icons.gd @@ -0,0 +1,149 @@ +extends "res://addons/icon_explorer/internal/scripts/collection.gd" + +const IconSimpleIcons := preload("res://addons/icon_explorer/internal/scripts/collections/icon_simple_icons.gd") +const ZipUnpacker := preload("res://addons/icon_explorer/internal/scripts/tools/zip_unpacker.gd") + +const _DOWNLOAD_FILE: String = "https://github.com/simple-icons/simple-icons/archive/master.zip" +var _TITLE_TO_SLUG_REPLACEMENTS: Dictionary = { + "+": "plus", + ".": "dot", + "&": "and", + "đ": "d", + "ħ": "h", + "ı": "i", + "ĸ": "k", + "ŀ": "l", + "ł": "l", + "ß": "ss", + "ŧ": "t", + + "ä": "a", + "ã": "a", + "á": "a", + "é": "e", + "è": "e", + "ë": "e", + "î": "i", + "ö": "o", + "ř": "r", + "š": "s", + "ü": "u", + "ż": "z", +} + +var _title_to_slug_range_rx: RegEx = RegEx.new() + +func _init() -> void: + self.name = "Simple Icons" + self.version = "" + self.author = "" + self.license = "CC0 1.0 Universal / Others" + self.web = "https://github.com/simple-icons/simple-icons" + self.svg_size = 24.0 + + self._title_to_slug_range_rx.compile("[^a-z0-9]") + +# OVERRIDE +func convert_icon_colored(buffer: String, color: String) -> String: + return ' String: + title = title.to_lower() + for src: String in _TITLE_TO_SLUG_REPLACEMENTS.keys(): + title = title.replace(src, _TITLE_TO_SLUG_REPLACEMENTS[src]) + title = self._title_to_slug_range_rx.sub(title, '', true) + return title + +# OVERRIDE +func load() -> Array: + var parser_version: JSON = JSON.new() + var res_version: int = parser_version.parse(FileAccess.get_file_as_string(self.directory().path_join("simple-icons-master/package.json"))) + if res_version != OK: + push_warning("could not parse simple icons package.json: '%s'", [parser_version.get_error_message()]) + return [[], PackedStringArray()] + self.version = parser_version.data["version"] + + var parser: JSON = JSON.new() + var res: int = parser.parse(FileAccess.get_file_as_string(self.directory().path_join("simple-icons-master/_data/simple-icons.json"))) + if res != OK: + push_warning("could not parse simple icons simple-icons.json: '%s'", [parser.get_error_message()]) + return [[], PackedStringArray()] + + var icons: Array[Icon] = [] + var buffers: PackedStringArray = PackedStringArray() + for item: Dictionary in parser.data.get("icons", []): + var arr_res: Array = self._load_item(item) + if arr_res.size() == 0: + continue + icons.append(arr_res[0]) + buffers.append(arr_res[1]) + for dup: Dictionary in item.get("aliases", {}).get("dup", []): + var dup_item: Dictionary = item.duplicate(true) + dup_item.merge(dup, true) + arr_res = self._load_item(item) + if arr_res.size() == 2: + icons.append(arr_res[0]) + buffers.append(arr_res[1]) + + return [icons, buffers] + +func _load_item(item: Dictionary) -> Array: + var icon: IconSimpleIcons = IconSimpleIcons.new() + icon.collection = self + icon.name = item["title"] + + if item.has("slug"): + icon.icon_path = self.icon_directory().path_join(item["slug"] + ".svg") + else: + icon.icon_path = self.icon_directory().path_join(self._title_to_slug(icon.name) + ".svg") + + var hex: String = item.get("hex", "") + if hex != "": + icon.hex = Color.from_string(hex, Color()) + icon.source = item.get("source", "") + var aliases: Dictionary = item.get("aliases", {}) + icon.aliases = aliases.get("aka", PackedStringArray()) + icon.aliases.append_array(aliases.get("loc", {}).values()) + icon.aliases.append_array(aliases.get("old", PackedStringArray())) + icon.license = item.get("license", {}).get("type", "") + icon.license_link = item.get("license", {}).get("url", "") + icon.guidelines = item.get("guidelines", "") + + var buffer: String = FileAccess.get_file_as_string(icon.icon_path) + if buffer == "": + push_warning("could not load '" + icon.icon_path + "'") + return [] + + return [icon, self.convert_icon_colored(buffer, "FFFFFF")] + +# OVERRIDE +func install(http: HTTPRequest, _version: String) -> Error: + DirAccess.make_dir_recursive_absolute(self.directory()) + var zip_path: String = self.directory().path_join("icons.zip") + http.download_file = zip_path + var downloader: Io.FileDownloader = Io.FileDownloader.new(http) + downloader.request.bind(_DOWNLOAD_FILE).call_deferred() + + downloader.wait() + if downloader.result != HTTPRequest.RESULT_SUCCESS: + return Error.FAILED + + var unzipper: ZipUnpacker = ZipUnpacker.new(zip_path, self.directory(), [ + "simple-icons-master/package.json", + "simple-icons-master/icons/", + "simple-icons-master/_data/simple-icons.json", + "simple-icons-master/LICENSE.md", + ]) + if !unzipper.unpack_mt(maxi(OS.get_processor_count() / 2, 1)): + return Error.FAILED + DirAccess.remove_absolute(zip_path) + return Error.OK + +# OVERRIDE +func remove() -> Error: + self.version = "" + return super.remove() + +# OVERRIDE +func icon_directory() -> String: + return self.directory().path_join("simple-icons-master/icons/") diff --git a/addons/icon_explorer/internal/scripts/collections/tabler.gd b/addons/icon_explorer/internal/scripts/collections/tabler.gd new file mode 100644 index 0000000..a143d7d --- /dev/null +++ b/addons/icon_explorer/internal/scripts/collections/tabler.gd @@ -0,0 +1,86 @@ +extends "res://addons/icon_explorer/internal/scripts/collection.gd" + +const IconTabler := preload("res://addons/icon_explorer/internal/scripts/collections/icon_tabler.gd") +const ZipUnpacker := preload("res://addons/icon_explorer/internal/scripts/tools/zip_unpacker.gd") + +const _DOWNLOAD_FILE: String = "https://github.com/tabler/tabler-icons/archive/master.zip" + +func _init() -> void: + self.name = "tabler Icons" + self.version = "" + self.author = "Paweł Kuna" + self.license = "MIT" + self.web = "https://github.com/tabler/tabler-icons" + self.svg_size = 24.0 + +# OVERRIDE +func convert_icon_colored(buffer: String, color: String) -> String: + return buffer.replace("currentColor", "#" + color) + +# OVERRIDE +func load() -> Array: + var parser_version: JSON = JSON.new() + var res_version: int = parser_version.parse(FileAccess.get_file_as_string(self.directory().path_join("tabler-icons-master/package.json"))) + if res_version != OK: + push_warning("could not parse tabler icons package.json: '%s'", [parser_version.get_error_message()]) + return [[], PackedStringArray()] + self.version = parser_version.data["version"] + + var parser: JSON = JSON.new() + var res: int = parser.parse(FileAccess.get_file_as_string(self.directory().path_join("tabler-icons-master/tags.json"))) + if res != OK: + push_warning("could not parse tabler icons tags.json: '%s'", [parser.get_error_message()]) + return [[], PackedStringArray()] + + var icon_path: String = self.icon_directory() + var icons: Array[Icon] = [] + var buffers: PackedStringArray = PackedStringArray() + for item: Dictionary in parser.data.values(): + var icon: IconTabler = IconTabler.new() + icon.collection = self + icon.name = item["name"] + icon.icon_path = icon_path.path_join(icon.name + ".svg") + + icon.category = item.get("category", "") + icon.tags = item.get("tags", PackedStringArray()) + icon.version = item.get("version", "") + + var buffer: String = FileAccess.get_file_as_string(icon.icon_path) + if buffer == "": + push_warning("could not load '" + icon.icon_path + "'") + continue + icons.append(icon) + buffers.append(self.convert_icon_colored(buffer, "FFFFFF")) + return [icons, buffers] + +# OVERRIDE +func install(http: HTTPRequest, _version: String) -> Error: + DirAccess.make_dir_recursive_absolute(self.directory()) + var zip_path: String = self.directory().path_join("icons.zip") + http.download_file = zip_path + var downloader: Io.FileDownloader = Io.FileDownloader.new(http) + downloader.request.bind(_DOWNLOAD_FILE).call_deferred() + + downloader.wait() + if downloader.result != HTTPRequest.RESULT_SUCCESS: + return Error.FAILED + + var unzipper: ZipUnpacker = ZipUnpacker.new(zip_path, self.directory(), [ + "tabler-icons-master/package.json", + "tabler-icons-master/icons/", + "tabler-icons-master/tags.json", + "tabler-icons-master/LICENSE", + ]) + if !unzipper.unpack_mt(maxi(OS.get_processor_count() / 2, 1)): + return Error.FAILED + DirAccess.remove_absolute(zip_path) + return Error.OK + +# OVERRIDE +func remove() -> Error: + self.version = "" + return super.remove() + +# OVERRIDE +func icon_directory() -> String: + return self.directory().path_join("tabler-icons-master/icons/") diff --git a/addons/icon_explorer/internal/scripts/database.gd b/addons/icon_explorer/internal/scripts/database.gd new file mode 100644 index 0000000..482b240 --- /dev/null +++ b/addons/icon_explorer/internal/scripts/database.gd @@ -0,0 +1,152 @@ +extends RefCounted +## This class is not thread safe. +## Do not call install, remove or load at the same time. +## Any texture loading has to be done on the main thread: https://github.com/godotengine/godot/issues/86796 + +const Collection := preload("res://addons/icon_explorer/internal/scripts/collection.gd") +const CollectionBootstrap := preload("res://addons/icon_explorer/internal/scripts/collections/bootstrap.gd") +const CollectionFontAwesome := preload("res://addons/icon_explorer/internal/scripts/collections/font_awesome.gd") +const CollectionMaterialDesign := preload("res://addons/icon_explorer/internal/scripts/collections/material_design.gd") +const CollectionSimpleIcons := preload("res://addons/icon_explorer/internal/scripts/collections/simple_icons.gd") +const CollectionTabler := preload("res://addons/icon_explorer/internal/scripts/collections/tabler.gd") +const Icon := preload("res://addons/icon_explorer/internal/scripts/icon.gd") + +signal collection_installed(id: int, status: Error) +signal collection_removed(id: int, status: Error) +## emitted after calling load() +signal loaded() + +var _loaded_collections: Array[int] = [] +var _collections: Array[Collection] = [] +var _icons: Array[Icon] = [] + +## used to await next frame on texture loading +var _scene_tree: SceneTree +## no mutex required as loading is suspended while main thread runs +## as this progress is exclusively used in loading the textures +var _load_progress: float +var _processing_thread: Thread + +func load_progress() -> float: + return self._load_progress + +func _init(scene_tree: SceneTree) -> void: + self._scene_tree = scene_tree + self.register(CollectionBootstrap.new()) + self.register(CollectionFontAwesome.new()) + self.register(CollectionMaterialDesign.new()) + self.register(CollectionSimpleIcons.new()) + self.register(CollectionTabler.new()) + +func _notification(what): + if what == NOTIFICATION_PREDELETE: + if self._processing_thread != null: + self._processing_thread.wait_to_finish() + +func collections() -> Array[Collection]: + return self._collections + +func get_collection(id: int) -> Collection: + for coll: Collection in self._collections: + if coll.id() == id: + return coll + + return null + +func installed_collections() -> Array[Collection]: + return self._collections.filter(func(coll: Collection) -> bool: return coll.is_installed()) + +func icons() -> Array[Icon]: + return self._icons + +# register each collection only once +func register(coll: Collection) -> void: + self._collections.append(coll) + coll._id = self._collections.size() - 1 + +func install(coll: Collection, http: HTTPRequest, version: String) -> void: + if self._processing_thread != null && self._processing_thread.is_alive(): + return + if self._processing_thread != null: + self._processing_thread.wait_to_finish() + + self._processing_thread = Thread.new() + self._processing_thread.start(self._install.bind(coll, http, version)) + +# THREAD FUNCTION +func _install(coll: Collection, http: HTTPRequest, version: String) -> void: + var status: Error = coll.install(http, version) + if status != Error.OK: + self._install_done.bind(coll.id(), status).call_deferred() + return + if self._loaded_collections.has(coll.id()): + self._icons = self._icons.filter(func (icon: Icon) -> bool: return icon.collection.id() != coll.id()) + self._load() + self._install_done.bind(coll.id(), status).call_deferred() + +func _install_done(id: int, status: Error) -> void: + if self._loaded_collections.has(id): + self._icons = self._icons.filter(func (icon: Icon) -> bool: return icon.collection.id() != id) + if !self._loaded_collections.has(id): + self._loaded_collections.append(id) + self.collection_installed.emit(id, status) + +func remove(coll: Collection) -> void: + if self._processing_thread != null && self._processing_thread.is_alive(): + return + if self._processing_thread != null: + self._processing_thread.wait_to_finish() + + self._processing_thread = Thread.new() + self._processing_thread.start(self._remove.bind(coll)) + +# THREAD FUNCTION +func _remove(coll: Collection) -> void: + var status: Error = coll.remove() + self._remove_done.bind(coll.id(), status).call_deferred() + +func _remove_done(id: int, status: Error) -> void: + self._icons = self._icons.filter(func (icon: Icon) -> bool: return icon.collection.id() != id) + self.collection_removed.emit(id, status) + +func load() -> void: + if self._processing_thread != null && self._processing_thread.is_alive(): + return + if self._processing_thread != null: + self._processing_thread.wait_to_finish() + + self._processing_thread = Thread.new() + self._processing_thread.start(self._load) + +# thread function +func _load() -> void: + var loaded_icons: Array[Icon] = [] + var buffers: PackedStringArray = PackedStringArray() + self._load_progress = 0.0 + for idx: int in range(self._collections.size()): + var coll: Collection = self._collections[idx] + if !self._loaded_collections.has(coll.id()) && coll.is_installed(): + var res: Array = coll.load() + loaded_icons.append_array(res[0]) + buffers.append_array(res[1]) + self._loaded_collections.append(coll.id()) + self._load_done.bind(loaded_icons, buffers).call_deferred() + +func _load_done(loaded_icons: Array[Icon], buffers: PackedStringArray) -> void: + for idx: int in range(loaded_icons.size()): + if idx % 50 == 0: + self._load_progress = float(idx + 1) / loaded_icons.size() * 100.0 + await self._scene_tree.process_frame + _load_texture(loaded_icons[idx], buffers[idx]) + self._icons.append_array(loaded_icons) + self.loaded.emit() + +static func _load_texture(icon: Icon, buffer: String) -> void: + var img: Image = Image.new() + # scale texture to 128 + var success: int = img.load_svg_from_string(buffer, Collection.TEXTURE_SIZE / icon.collection.svg_size) + if success != OK: + push_warning("could not load '" + icon.icon_path + "'") + return + img.fix_alpha_edges() + icon.texture = ImageTexture.create_from_image(img) diff --git a/addons/icon_explorer/internal/scripts/icon.gd b/addons/icon_explorer/internal/scripts/icon.gd new file mode 100644 index 0000000..bd43b78 --- /dev/null +++ b/addons/icon_explorer/internal/scripts/icon.gd @@ -0,0 +1,37 @@ +extends RefCounted + +const Collection := preload("res://addons/icon_explorer/internal/scripts/collection.gd") + +var name: String +var collection: Collection +var texture: Texture2D +var icon_path: String + +# used by the GUI to sort multiple icons, cached value to optimize sorting +var sort_priority: int + +# VIRTUAL +# return a value between 0 and 10 +# 10: +# - keyword is contained in name +# 7: +# - keyword is part of an alias +# 5: +# - keyword is part of category or search term +# 0: +# - will not be displayed +func match(keyword: String) -> int: + return 0 + +# return either 10, 9 or 0 +func get_name_match(keyword: String) -> int: + if self.name.to_lower() == keyword: + return 10 + if self.name.to_lower().contains(keyword): + return 9 + if self.name.similarity(keyword) > 0.8: + return 9 + return 0 + +static func compare(lhs, rhs) -> bool: + return lhs.sort_priority > rhs.sort_priority || (lhs.sort_priority == rhs.sort_priority && lhs.name.to_lower() < rhs.name.to_lower()) diff --git a/addons/icon_explorer/internal/scripts/tools/io.gd b/addons/icon_explorer/internal/scripts/tools/io.gd new file mode 100644 index 0000000..83567c2 --- /dev/null +++ b/addons/icon_explorer/internal/scripts/tools/io.gd @@ -0,0 +1,50 @@ +## recursive remove directory +static func rrm_dir(dir_path: String) -> bool: + var dir: DirAccess = DirAccess.open(dir_path) + if !dir: + return false + + dir.list_dir_begin() + var file_name: String = dir.get_next() + while file_name != "": + if dir.current_is_dir(): + if !rrm_dir(dir_path.path_join(file_name)): + return false + else: + if dir.remove(file_name) != Error.OK: + return false + file_name = dir.get_next() + DirAccess.remove_absolute(dir_path) + return true + +class FileDownloader: + extends RefCounted + + var result: int + var response_code: int + var headers: PackedStringArray + var body: PackedByteArray + + var _http: HTTPRequest + var _sema: Semaphore + + func _init(http: HTTPRequest) -> void: + self._http = http + self._sema = Semaphore.new() + + func from_array(res_arr: Array): + self.result = res_arr[0] + self.response_code = res_arr[1] + self.headers = res_arr[2] + self.body = res_arr[3] + return self + + # call on main thread + func request(uri: String) -> void: + self._http.request(uri) + var res: Array = await self._http.request_completed + self.from_array(res) + self._sema.post() + + func wait() -> void: + self._sema.wait() diff --git a/addons/icon_explorer/internal/scripts/tools/zip_unpacker.gd b/addons/icon_explorer/internal/scripts/tools/zip_unpacker.gd new file mode 100644 index 0000000..4708342 --- /dev/null +++ b/addons/icon_explorer/internal/scripts/tools/zip_unpacker.gd @@ -0,0 +1,101 @@ +extends RefCounted + +var _zip_path: String +var _output_path: String +var _unpack_only: PackedStringArray + +## Filter is compared with begins_with +func _init(zip_path: String, output_path: String, unpack_only: PackedStringArray = PackedStringArray()) -> void: + self._zip_path = zip_path + self._output_path = output_path + self._unpack_only = unpack_only + +func _has_filter() -> bool: + return self._unpack_only.size() != 0 + +func _is_in_filter(path: String) -> bool: + if !self._has_filter(): + return true + for filter: String in self._unpack_only: + if path.begins_with(filter): + return true + return false + +func unpack() -> bool: + var reader := ZIPReader.new() + var err: Error = reader.open(self._zip_path) + if err != Error.OK: + return false + + if !self._create_directories(reader.get_files()): + return Error.FAILED + var files: PackedStringArray = reader.get_files() + for idx: int in range(files.size()): + var path: String = files[idx] + if !self._is_in_filter(path): + continue + var buffer: PackedByteArray = reader.read_file(path) + var file: FileAccess = FileAccess.open(self._output_path.path_join(path), FileAccess.WRITE) + if file == null: + reader.close() + return false + file.store_buffer(buffer) + file = null + reader.close() + return true + +# Unpack with multiple threads. Is a blocking call nontheless. +func unpack_mt(thread_count: int) -> bool: + var threads: Array[Thread] = [] + for idx: int in range(thread_count): + threads.append(Thread.new()) + + var reader := ZIPReader.new() + var err: Error = reader.open(self._zip_path) + if err != Error.OK: + return false + + if !self._create_directories(reader.get_files()): + return Error.FAILED + var file_count: int = reader.get_files().size() + reader.close() + + var steps: int = file_count / threads.size() + for thread_idx: int in range(threads.size()): + var start_idx: int = thread_idx * steps + var end_idx: int = start_idx + steps + if thread_idx == threads.size() - 1: + end_idx = file_count + threads[thread_idx].start(self._unpack_fn.bind(start_idx, end_idx)) + for thread: Thread in threads: + thread.wait_to_finish() + return true + +func _create_directories(paths: PackedStringArray) -> bool: + var created: Dictionary = {} + for path: String in paths: + var dir_path: String = path.get_base_dir() + if (!self._has_filter() || self._is_in_filter(path)) && !(dir_path in created): + if DirAccess.make_dir_recursive_absolute(self._output_path.path_join(path.get_base_dir())) != Error.OK: + return false + created[dir_path] = null + return true + +func _unpack_fn(from: int, to: int) -> void: + var reader := ZIPReader.new() + var err: Error = reader.open(self._zip_path) + if err != Error.OK: + return + + var files: PackedStringArray = reader.get_files() + for idx: int in range(from, to): + var path: String = files[idx] + if !self._is_in_filter(path): + continue + if path.ends_with("/"): + continue + var file: FileAccess = FileAccess.open(self._output_path.path_join(path), FileAccess.WRITE) + if file == null: + continue + file.store_buffer(reader.read_file(path)) + file = null diff --git a/addons/icon_explorer/internal/ui/detail_panel/color_field.gd b/addons/icon_explorer/internal/ui/detail_panel/color_field.gd new file mode 100644 index 0000000..876d8d3 --- /dev/null +++ b/addons/icon_explorer/internal/ui/detail_panel/color_field.gd @@ -0,0 +1,29 @@ +@tool +extends "res://addons/icon_explorer/internal/ui/detail_panel/field.gd" + +@export var color: Color = Color.WHITE: + set = set_color + +@export var _color_rect: ColorRect +@export var _color_label: Label + +func set_color(new_color: Color) -> void: + color = new_color + if self._color_rect != null: + self._color_rect.color = new_color + self._color_label.text = "#" + new_color.to_html(false).to_upper() + if (new_color.r * 0.299 + new_color.g * 0.587 + new_color.b * 0.114) > 186.0 / 255.0: + self._color_label.add_theme_color_override(&"font_color", Color.BLACK) + else: + self._color_label.add_theme_color_override(&"font_color", Color.WHITE) + +func _ready() -> void: + super._ready() + self.color = self.color + self._color_rect.gui_input.connect(self._on_color_panel_gui_input) + +func _on_color_panel_gui_input(event: InputEvent) -> void: + if !((event is InputEventMouseButton) && (event as InputEventMouseButton).pressed && (event as InputEventMouseButton).button_index == MOUSE_BUTTON_LEFT): + return + self._color_rect.accept_event() + DisplayServer.clipboard_set("#" + self._color_rect.color.to_html(false).to_upper()) diff --git a/addons/icon_explorer/internal/ui/detail_panel/color_field.tscn b/addons/icon_explorer/internal/ui/detail_panel/color_field.tscn new file mode 100644 index 0000000..c3eb29e --- /dev/null +++ b/addons/icon_explorer/internal/ui/detail_panel/color_field.tscn @@ -0,0 +1,30 @@ +[gd_scene load_steps=3 format=3 uid="uid://cadjan8ev877o"] + +[ext_resource type="Script" path="res://addons/icon_explorer/internal/ui/detail_panel/color_field.gd" id="1_vl8db"] +[ext_resource type="PackedScene" uid="uid://bonqki0uorlhq" path="res://addons/icon_explorer/internal/ui/detail_panel/field_title.tscn" id="2_2clk4"] + +[node name="color" type="VBoxContainer" node_paths=PackedStringArray("_color_rect", "_color_label")] +script = ExtResource("1_vl8db") +_color_rect = NodePath("margin_container/color_rect") +_color_label = NodePath("margin_container/color") +_title_path = NodePath("title_panel") + +[node name="title_panel" parent="." instance=ExtResource("2_2clk4")] +layout_mode = 2 + +[node name="margin_container" type="MarginContainer" parent="."] +layout_mode = 2 +theme_override_constants/margin_left = 16 + +[node name="color_rect" type="ColorRect" parent="margin_container"] +custom_minimum_size = Vector2(0, 32) +layout_mode = 2 +tooltip_text = "Click to copy!" +focus_mode = 2 +mouse_default_cursor_shape = 2 + +[node name="color" type="Label" parent="margin_container"] +layout_mode = 2 +size_flags_vertical = 1 +horizontal_alignment = 1 +vertical_alignment = 1 diff --git a/addons/icon_explorer/internal/ui/detail_panel/detail_panel.gd b/addons/icon_explorer/internal/ui/detail_panel/detail_panel.gd new file mode 100644 index 0000000..7c07f66 --- /dev/null +++ b/addons/icon_explorer/internal/ui/detail_panel/detail_panel.gd @@ -0,0 +1,124 @@ +@tool +extends PanelContainer + +const Toolbar := preload("res://addons/icon_explorer/internal/ui/detail_panel/toolbar.gd") + +const Collection := preload("res://addons/icon_explorer/internal/scripts/collection.gd") +const Icon := preload("res://addons/icon_explorer/internal/scripts/icon.gd") +const TextField := preload("res://addons/icon_explorer/internal/ui/detail_panel/text_field.gd") + +@export var _detail_container: VBoxContainer +@export var _hint_container: CenterContainer +@export var _toolbar_path: NodePath +@onready var _toolbar: Toolbar = self.get_node(self._toolbar_path) +@export var _detail_tabs: TabContainer + +@export var _icon: TextureRect +@export var _preview_background: TextureRect +@export var _preview_panel: PanelContainer +@export var _name: Label +@export var _collection: Label +@export var _size: Label + +@export var _toolbar_panel: PanelContainer + +var preview_size: int = 64: + set = set_preview_size + +var preview_color: Color = Color.WHITE: + set = set_preview_color + +var _cur_icon: Icon + +func set_preview_size(new_size: int) -> void: + preview_size = new_size + if self._icon != null: + self._preview_panel.custom_minimum_size = Vector2(0, new_size) + #if self._preview_background != null: + #self._preview_background.texture = gen_checkboard_texture(new_size, 8) + +func set_preview_color(new_color: Color) -> void: + preview_color = new_color + if self._icon != null: + self._icon.self_modulate = new_color + +static func gen_checkboard_texture(size: int, check_size: int) -> Texture2D: + var img: Image = Image.create(size, size, false, Image.FORMAT_RGBA8) + var white: Color = Color(1, 1, 1, 0.2) + var black: Color = Color(0, 0, 0, 0.2) + for x: int in range(size): + for y: int in range(size): + if (int(x / check_size) % 2 + int(y / check_size) % 2) % 2 == 0: + img.set_pixel(x, y, white) + else: + img.set_pixel(x, y, black) + return ImageTexture.create_from_image(img) + +func _ready() -> void: + if Engine.is_editor_hint(): + self._toolbar_panel.add_theme_stylebox_override(&"panel", self.get_theme_stylebox(&"LaunchPadNormal", &"EditorStyles")) + self._toolbar.save_pressed.connect(self._on_save_pressed.bind(false)) + self._toolbar.save_colored_pressed.connect(self._on_save_pressed.bind(true)) + self._icon.self_modulate = self.preview_color + self.display(null) + +func display(icon: Icon) -> void: + self._cur_icon = icon + + self._detail_container.visible = icon != null + self._hint_container.visible = icon == null + if icon == null: + return + + self._collection.text = icon.collection.name + self._name.text = icon.name + self._icon.texture = icon.texture + self._size.text = "%dx%d" % [ + icon.texture.get_size().x / Collection.TEXTURE_SIZE * icon.collection.svg_size, + icon.texture.get_size().y / Collection.TEXTURE_SIZE * icon.collection.svg_size + ] + + self._detail_tabs.current_tab = icon.collection.id() + self._detail_tabs.get_child(icon.collection.id()).display(icon) + +func _on_save_pressed(colored: bool) -> void: + if Engine.is_editor_hint(): + var dialog: EditorFileDialog = EditorFileDialog.new() + self.add_child(dialog) + dialog.access = EditorFileDialog.ACCESS_RESOURCES + dialog.file_mode = EditorFileDialog.FILE_MODE_SAVE_FILE + dialog.current_file = self._cur_icon.name + ".svg" + dialog.close_requested.connect(dialog.queue_free) + dialog.file_selected.connect(self._on_filepath_selected.bind(colored)) + dialog.popup_centered_ratio(0.4) + else: + var dialog: FileDialog = FileDialog.new() + self.add_child(dialog) + dialog.access = FileDialog.ACCESS_FILESYSTEM + dialog.file_mode = FileDialog.FILE_MODE_SAVE_FILE + dialog.current_file = self._cur_icon.name + ".svg" + dialog.close_requested.connect(dialog.queue_free) + dialog.file_selected.connect(self._on_filepath_selected.bind(colored)) + dialog.popup_centered_ratio(0.4) + +func _on_filepath_selected(path: String, colored: bool) -> void: + if colored: + var buffer: String = FileAccess.get_file_as_string(self._cur_icon.icon_path) + if buffer == "": + push_warning("could not load '" + self._cur_icon.icon_path + "'") + return + buffer = self._cur_icon.collection.convert_icon_colored(buffer, self.preview_color.to_html(false)) + var writer: FileAccess = FileAccess.open(path, FileAccess.WRITE) + if writer == null: + writer = null + push_warning("could not save '" + path + "'") + return + writer.store_string(buffer) + writer = null + else: + var err: Error = DirAccess.copy_absolute(self._cur_icon.icon_path, path) + if err != OK: + push_error(err) + return + if Engine.is_editor_hint(): + EditorInterface.get_resource_filesystem().scan() diff --git a/addons/icon_explorer/internal/ui/detail_panel/detail_panel.tscn b/addons/icon_explorer/internal/ui/detail_panel/detail_panel.tscn new file mode 100644 index 0000000..19c9908 --- /dev/null +++ b/addons/icon_explorer/internal/ui/detail_panel/detail_panel.tscn @@ -0,0 +1,150 @@ +[gd_scene load_steps=11 format=3 uid="uid://c3s1t8hiu6xll"] + +[ext_resource type="Script" path="res://addons/icon_explorer/internal/ui/detail_panel/detail_panel.gd" id="1_2iwjs"] +[ext_resource type="PackedScene" uid="uid://bf2b6v68rufpn" path="res://addons/icon_explorer/internal/ui/detail_panel/panels/material_design.tscn" id="2_ea6yg"] +[ext_resource type="Script" path="res://addons/icon_explorer/internal/ui/detail_panel/toolbar.gd" id="2_rtuoh"] +[ext_resource type="PackedScene" uid="uid://3hl6t03xllbp" path="res://addons/icon_explorer/internal/ui/detail_panel/panels/simple_icons.tscn" id="4_em72e"] +[ext_resource type="PackedScene" uid="uid://dtcstt4vepa17" path="res://addons/icon_explorer/internal/ui/detail_panel/panels/bootstrap.tscn" id="5_mxg1a"] +[ext_resource type="PackedScene" uid="uid://lk45nf0tqm80" path="res://addons/icon_explorer/internal/ui/detail_panel/panels/tabler.tscn" id="7_2jw0k"] +[ext_resource type="PackedScene" uid="uid://wqlpitsynlfk" path="res://addons/icon_explorer/internal/ui/detail_panel/panels/font_awesome.tscn" id="8_1041k"] + +[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_p1g3s"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_0v6yn"] +draw_center = false +border_width_left = 1 +border_width_top = 1 +border_width_right = 1 +border_width_bottom = 1 +border_color = Color(0.439216, 0.729412, 0.980392, 1) + +[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_brt2g"] + +[node name="detail_panel" type="PanelContainer" node_paths=PackedStringArray("_detail_container", "_hint_container", "_detail_tabs", "_icon", "_preview_background", "_preview_panel", "_name", "_collection", "_size", "_toolbar_panel")] +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 +theme_override_styles/panel = SubResource("StyleBoxEmpty_p1g3s") +script = ExtResource("1_2iwjs") +_detail_container = NodePath("detail_container") +_hint_container = NodePath("hint_container") +_toolbar_path = NodePath("detail_container/toolbar_panel/toolbar") +_detail_tabs = NodePath("detail_container/v_box_container/scroll_container/detail_tabs") +_icon = NodePath("detail_container/v_box_container/preview/icon") +_preview_background = NodePath("detail_container/v_box_container/preview/background") +_preview_panel = NodePath("detail_container/v_box_container/preview") +_name = NodePath("detail_container/v_box_container/name") +_collection = NodePath("detail_container/toolbar_panel/toolbar/collection") +_size = NodePath("detail_container/v_box_container/size") +_toolbar_panel = NodePath("detail_container/toolbar_panel") + +[node name="detail_container" type="VBoxContainer" parent="."] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +theme_override_constants/separation = 0 + +[node name="toolbar_panel" type="PanelContainer" parent="detail_container"] +layout_mode = 2 + +[node name="toolbar" type="HBoxContainer" parent="detail_container/toolbar_panel" node_paths=PackedStringArray("_save_button", "_save_colored_button")] +layout_mode = 2 +script = ExtResource("2_rtuoh") +_save_button = NodePath("save") +_save_colored_button = NodePath("save_in_color") + +[node name="save" type="Button" parent="detail_container/toolbar_panel/toolbar"] +layout_mode = 2 +tooltip_text = "Save original" + +[node name="save_in_color" type="Button" parent="detail_container/toolbar_panel/toolbar"] +layout_mode = 2 +tooltip_text = "Save colored" + +[node name="control" type="Control" parent="detail_container/toolbar_panel/toolbar"] +custom_minimum_size = Vector2(16, 0) +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="collection" type="Label" parent="detail_container/toolbar_panel/toolbar"] +layout_mode = 2 + +[node name="v_box_container" type="VBoxContainer" parent="detail_container"] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="size" type="Label" parent="detail_container/v_box_container"] +layout_mode = 2 + +[node name="preview" type="PanelContainer" parent="detail_container/v_box_container"] +custom_minimum_size = Vector2(0, 64) +layout_mode = 2 +size_flags_horizontal = 4 +size_flags_vertical = 4 +theme_override_styles/panel = SubResource("StyleBoxFlat_0v6yn") + +[node name="background" type="TextureRect" parent="detail_container/v_box_container/preview"] +visible = false +layout_mode = 2 +expand_mode = 3 +stretch_mode = 2 + +[node name="icon" type="TextureRect" parent="detail_container/v_box_container/preview"] +layout_mode = 2 +expand_mode = 3 +stretch_mode = 5 + +[node name="name" type="Label" parent="detail_container/v_box_container"] +layout_mode = 2 +horizontal_alignment = 1 +autowrap_mode = 2 + +[node name="h_separator" type="HSeparator" parent="detail_container/v_box_container"] +layout_mode = 2 +theme_override_constants/separation = 16 + +[node name="scroll_container" type="ScrollContainer" parent="detail_container/v_box_container"] +layout_mode = 2 +size_flags_vertical = 3 +horizontal_scroll_mode = 3 + +[node name="detail_tabs" type="TabContainer" parent="detail_container/v_box_container/scroll_container"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +theme_override_styles/panel = SubResource("StyleBoxEmpty_brt2g") +current_tab = 2 +tabs_visible = false + +[node name="bootstrap" parent="detail_container/v_box_container/scroll_container/detail_tabs" instance=ExtResource("5_mxg1a")] +visible = false +layout_mode = 2 + +[node name="font_awesome" parent="detail_container/v_box_container/scroll_container/detail_tabs" instance=ExtResource("8_1041k")] +visible = false +layout_mode = 2 + +[node name="material_design" parent="detail_container/v_box_container/scroll_container/detail_tabs" instance=ExtResource("2_ea6yg")] +layout_mode = 2 + +[node name="simple_icons" parent="detail_container/v_box_container/scroll_container/detail_tabs" instance=ExtResource("4_em72e")] +visible = false +layout_mode = 2 + +[node name="tabler" parent="detail_container/v_box_container/scroll_container/detail_tabs" instance=ExtResource("7_2jw0k")] +visible = false +layout_mode = 2 + +[node name="hint_container" type="CenterContainer" parent="."] +visible = false +layout_mode = 2 + +[node name="label" type="Label" parent="hint_container"] +layout_mode = 2 +text = "Select an icon" +horizontal_alignment = 1 +vertical_alignment = 1 diff --git a/addons/icon_explorer/internal/ui/detail_panel/field.gd b/addons/icon_explorer/internal/ui/detail_panel/field.gd new file mode 100644 index 0000000..2190683 --- /dev/null +++ b/addons/icon_explorer/internal/ui/detail_panel/field.gd @@ -0,0 +1,18 @@ +@tool +extends VBoxContainer + +const FieldTitle := preload("res://addons/icon_explorer/internal/ui/detail_panel/field_title.gd") + +@export var title: String: + set = set_title + +@export var _title_path: NodePath +@onready var _title: FieldTitle = self.get_node(self._title_path) + +func set_title(new_title: String) -> void: + title = new_title + if self._title != null: + self._title.text = new_title + +func _ready() -> void: + self.title = self.title diff --git a/addons/icon_explorer/internal/ui/detail_panel/field_title.gd b/addons/icon_explorer/internal/ui/detail_panel/field_title.gd new file mode 100644 index 0000000..a208b5a --- /dev/null +++ b/addons/icon_explorer/internal/ui/detail_panel/field_title.gd @@ -0,0 +1,21 @@ +@tool +extends PanelContainer + +@export var text: String: + set = set_text + +@export var _text_label: Label + +func set_text(new_text: String) -> void: + text = new_text + if self._text_label != null: + self._text_label.text = new_text + +func _ready() -> void: + if Engine.is_editor_hint(): + self._text_label.add_theme_font_override(&"font", self.get_theme_font(&"title", &"EditorFonts")) + var stylebox: StyleBox = self.get_theme_stylebox(&"PanelForeground", &"EditorStyles").duplicate() + stylebox.content_margin_bottom = 0 + stylebox.content_margin_top = 0 + self.add_theme_stylebox_override(&"panel", stylebox) + self.text = self.text diff --git a/addons/icon_explorer/internal/ui/detail_panel/field_title.tscn b/addons/icon_explorer/internal/ui/detail_panel/field_title.tscn new file mode 100644 index 0000000..d386055 --- /dev/null +++ b/addons/icon_explorer/internal/ui/detail_panel/field_title.tscn @@ -0,0 +1,15 @@ +[gd_scene load_steps=2 format=3 uid="uid://bonqki0uorlhq"] + +[ext_resource type="Script" path="res://addons/icon_explorer/internal/ui/detail_panel/field_title.gd" id="1_unl3l"] + +[node name="title_panel" type="PanelContainer" node_paths=PackedStringArray("_text_label")] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_unl3l") +_text_label = NodePath("title") + +[node name="title" type="Label" parent="."] +layout_mode = 2 diff --git a/addons/icon_explorer/internal/ui/detail_panel/list_field.gd b/addons/icon_explorer/internal/ui/detail_panel/list_field.gd new file mode 100644 index 0000000..7757c6e --- /dev/null +++ b/addons/icon_explorer/internal/ui/detail_panel/list_field.gd @@ -0,0 +1,19 @@ +@tool +extends "res://addons/icon_explorer/internal/ui/detail_panel/field.gd" + +@export var items: PackedStringArray = []: + set = set_items + +@export var _list: ItemList + +func set_items(new_items: PackedStringArray) -> void: + items = new_items + if self._list != null: + self._list.clear() + for item: String in new_items: + self._list.add_item(item) + self.visible = new_items.size() > 0 + +func _ready() -> void: + super._ready() + self.items = self.items diff --git a/addons/icon_explorer/internal/ui/detail_panel/list_field.tscn b/addons/icon_explorer/internal/ui/detail_panel/list_field.tscn new file mode 100644 index 0000000..89665b1 --- /dev/null +++ b/addons/icon_explorer/internal/ui/detail_panel/list_field.tscn @@ -0,0 +1,30 @@ +[gd_scene load_steps=4 format=3 uid="uid://b813qk6u7eveh"] + +[ext_resource type="Script" path="res://addons/icon_explorer/internal/ui/detail_panel/list_field.gd" id="1_wb7ty"] +[ext_resource type="PackedScene" uid="uid://bonqki0uorlhq" path="res://addons/icon_explorer/internal/ui/detail_panel/field_title.tscn" id="2_gcg2e"] + +[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_wghjy"] + +[node name="list_field" type="VBoxContainer" node_paths=PackedStringArray("_list")] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_wb7ty") +_title_path = NodePath("title_panel") +_list = NodePath("margin_container/item_list") + +[node name="title_panel" parent="." instance=ExtResource("2_gcg2e")] +layout_mode = 2 + +[node name="margin_container" type="MarginContainer" parent="."] +layout_mode = 2 +theme_override_constants/margin_left = 16 + +[node name="item_list" type="ItemList" parent="margin_container"] +layout_mode = 2 +focus_mode = 0 +theme_override_styles/panel = SubResource("StyleBoxEmpty_wghjy") +allow_search = false +auto_height = true diff --git a/addons/icon_explorer/internal/ui/detail_panel/panels/bootstrap.gd b/addons/icon_explorer/internal/ui/detail_panel/panels/bootstrap.gd new file mode 100644 index 0000000..3b0bc19 --- /dev/null +++ b/addons/icon_explorer/internal/ui/detail_panel/panels/bootstrap.gd @@ -0,0 +1,18 @@ +@tool +extends VBoxContainer + +const IconBootstrap := preload("res://addons/icon_explorer/internal/scripts/collections/icon_bootstrap.gd") +const TextField := preload("res://addons/icon_explorer/internal/ui/detail_panel/text_field.gd") +const ListField := preload("res://addons/icon_explorer/internal/ui/detail_panel/list_field.gd") + +@export var _categories_path: NodePath +@onready var _categories: ListField = self.get_node(self._categories_path) +@export var _tags_path: NodePath +@onready var _tags: ListField = self.get_node(self._tags_path) +@export var _version_added_path: NodePath +@onready var _version_added: TextField = self.get_node(self._version_added_path) + +func display(icon: IconBootstrap) -> void: + self._categories.set_items(icon.categories) + self._tags.set_items(icon.tags) + self._version_added.text = icon.version_added diff --git a/addons/icon_explorer/internal/ui/detail_panel/panels/bootstrap.tscn b/addons/icon_explorer/internal/ui/detail_panel/panels/bootstrap.tscn new file mode 100644 index 0000000..8b5a4ab --- /dev/null +++ b/addons/icon_explorer/internal/ui/detail_panel/panels/bootstrap.tscn @@ -0,0 +1,27 @@ +[gd_scene load_steps=4 format=3 uid="uid://dtcstt4vepa17"] + +[ext_resource type="Script" path="res://addons/icon_explorer/internal/ui/detail_panel/panels/bootstrap.gd" id="1_s6ueg"] +[ext_resource type="PackedScene" uid="uid://b813qk6u7eveh" path="res://addons/icon_explorer/internal/ui/detail_panel/list_field.tscn" id="2_n1r08"] +[ext_resource type="PackedScene" uid="uid://b64tcvn5sw03h" path="res://addons/icon_explorer/internal/ui/detail_panel/text_field.tscn" id="3_5y6bb"] + +[node name="bootstrap" type="VBoxContainer"] +anchors_preset = 10 +anchor_right = 1.0 +offset_bottom = 205.0 +grow_horizontal = 2 +script = ExtResource("1_s6ueg") +_categories_path = NodePath("categories") +_tags_path = NodePath("aliases") +_version_added_path = NodePath("version_added") + +[node name="categories" parent="." instance=ExtResource("2_n1r08")] +layout_mode = 2 +title = "Categories" + +[node name="aliases" parent="." instance=ExtResource("2_n1r08")] +layout_mode = 2 +title = "Aliases" + +[node name="version_added" parent="." instance=ExtResource("3_5y6bb")] +layout_mode = 2 +title = "Version added" diff --git a/addons/icon_explorer/internal/ui/detail_panel/panels/font_awesome.gd b/addons/icon_explorer/internal/ui/detail_panel/panels/font_awesome.gd new file mode 100644 index 0000000..4bd75a0 --- /dev/null +++ b/addons/icon_explorer/internal/ui/detail_panel/panels/font_awesome.gd @@ -0,0 +1,15 @@ +@tool +extends VBoxContainer + +const IconFontAwesome := preload("res://addons/icon_explorer/internal/scripts/collections/icon_font_awesome.gd") +const TextField := preload("res://addons/icon_explorer/internal/ui/detail_panel/text_field.gd") +const ListField := preload("res://addons/icon_explorer/internal/ui/detail_panel/list_field.gd") + +@export var _style_path: NodePath +@onready var _style: TextField = self.get_node(self._style_path) +@export var _aliases_path: NodePath +@onready var _aliases: ListField = self.get_node(self._aliases_path) + +func display(icon: IconFontAwesome) -> void: + self._aliases.set_items(icon.aliases) + self._style.text = icon.style diff --git a/addons/icon_explorer/internal/ui/detail_panel/panels/font_awesome.tscn b/addons/icon_explorer/internal/ui/detail_panel/panels/font_awesome.tscn new file mode 100644 index 0000000..6d1f4aa --- /dev/null +++ b/addons/icon_explorer/internal/ui/detail_panel/panels/font_awesome.tscn @@ -0,0 +1,23 @@ +[gd_scene load_steps=4 format=3 uid="uid://wqlpitsynlfk"] + +[ext_resource type="Script" path="res://addons/icon_explorer/internal/ui/detail_panel/panels/font_awesome.gd" id="1_uh5pp"] +[ext_resource type="PackedScene" uid="uid://b64tcvn5sw03h" path="res://addons/icon_explorer/internal/ui/detail_panel/text_field.tscn" id="2_8xir3"] +[ext_resource type="PackedScene" uid="uid://b813qk6u7eveh" path="res://addons/icon_explorer/internal/ui/detail_panel/list_field.tscn" id="3_fm2h0"] + +[node name="font_awesome" type="VBoxContainer"] +anchors_preset = 10 +anchor_right = 1.0 +offset_bottom = 136.0 +grow_horizontal = 2 +script = ExtResource("1_uh5pp") +_style_path = NodePath("style") +_aliases_path = NodePath("aliases") + +[node name="style" parent="." instance=ExtResource("2_8xir3")] +layout_mode = 2 +title = "Style" + +[node name="aliases" parent="." instance=ExtResource("3_fm2h0")] +layout_mode = 2 +title = "Aliases" +items = [] diff --git a/addons/icon_explorer/internal/ui/detail_panel/panels/material_design.gd b/addons/icon_explorer/internal/ui/detail_panel/panels/material_design.gd new file mode 100644 index 0000000..af5f027 --- /dev/null +++ b/addons/icon_explorer/internal/ui/detail_panel/panels/material_design.gd @@ -0,0 +1,23 @@ +@tool +extends VBoxContainer + +const IconMaterialDesign := preload("res://addons/icon_explorer/internal/scripts/collections/icon_material_design.gd") +const TextField := preload("res://addons/icon_explorer/internal/ui/detail_panel/text_field.gd") +const ListField := preload("res://addons/icon_explorer/internal/ui/detail_panel/list_field.gd") + +@export var _deprecated_banner: Label +@export var _aliases_path: NodePath +@onready var _aliases: ListField = self.get_node(self._aliases_path) +@export var _tags_path: NodePath +@onready var _tags: ListField = self.get_node(self._tags_path) +@export var _author_path: NodePath +@onready var _author: TextField = self.get_node(self._author_path) +@export var _version_path: NodePath +@onready var _version: TextField = self.get_node(self._version_path) + +func display(icon: IconMaterialDesign) -> void: + self._deprecated_banner.visible = icon.deprecated + self._aliases.set_items(icon.aliases) + self._tags.set_items(icon.tags) + self._author.text = icon.author + self._version.text = icon.version diff --git a/addons/icon_explorer/internal/ui/detail_panel/panels/material_design.tscn b/addons/icon_explorer/internal/ui/detail_panel/panels/material_design.tscn new file mode 100644 index 0000000..de29fa8 --- /dev/null +++ b/addons/icon_explorer/internal/ui/detail_panel/panels/material_design.tscn @@ -0,0 +1,48 @@ +[gd_scene load_steps=5 format=3 uid="uid://bf2b6v68rufpn"] + +[ext_resource type="Script" path="res://addons/icon_explorer/internal/ui/detail_panel/panels/material_design.gd" id="1_7oq86"] +[ext_resource type="PackedScene" uid="uid://b813qk6u7eveh" path="res://addons/icon_explorer/internal/ui/detail_panel/list_field.tscn" id="2_sgh8u"] +[ext_resource type="PackedScene" uid="uid://b64tcvn5sw03h" path="res://addons/icon_explorer/internal/ui/detail_panel/text_field.tscn" id="3_7hl4l"] + + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_2aicr"] +bg_color = Color(0.670588, 0, 0.0901961, 1) +border_width_left = 4 +border_width_top = 4 +border_width_right = 4 +border_width_bottom = 4 +border_color = Color(0.670588, 0, 0.0901961, 1) + +[node name="material_design" type="VBoxContainer" node_paths=PackedStringArray("_deprecated_banner")] +anchors_preset = 10 +anchor_right = 1.0 +offset_bottom = 227.0 +grow_horizontal = 2 +script = ExtResource("1_7oq86") +_deprecated_banner = NodePath("deprecated_banner") +_aliases_path = NodePath("aliases") +_tags_path = NodePath("tags") +_author_path = NodePath("author") +_version_path = NodePath("version") + +[node name="deprecated_banner" type="Label" parent="."] +layout_mode = 2 +theme_override_styles/normal = SubResource("StyleBoxFlat_2aicr") +text = "Deprecated" +horizontal_alignment = 1 + +[node name="aliases" parent="." instance=ExtResource("2_sgh8u")] +layout_mode = 2 +title = "Aliases" + +[node name="tags" parent="." instance=ExtResource("2_sgh8u")] +layout_mode = 2 +title = "Tags" + +[node name="author" parent="." instance=ExtResource("3_7hl4l")] +layout_mode = 2 +title = "Created by" + +[node name="version" parent="." instance=ExtResource("3_7hl4l")] +layout_mode = 2 +title = "Version added" diff --git a/addons/icon_explorer/internal/ui/detail_panel/panels/simple_icons.gd b/addons/icon_explorer/internal/ui/detail_panel/panels/simple_icons.gd new file mode 100644 index 0000000..72572cb --- /dev/null +++ b/addons/icon_explorer/internal/ui/detail_panel/panels/simple_icons.gd @@ -0,0 +1,28 @@ +@tool +extends VBoxContainer + +const IconSimpleIcons := preload("res://addons/icon_explorer/internal/scripts/collections/icon_simple_icons.gd") +const ColorField := preload("res://addons/icon_explorer/internal/ui/detail_panel/color_field.gd") +const TextField := preload("res://addons/icon_explorer/internal/ui/detail_panel/text_field.gd") +const ListField := preload("res://addons/icon_explorer/internal/ui/detail_panel/list_field.gd") + +@export var _color_path: NodePath +@onready var _color: ColorField = self.get_node(self._color_path) +@export var _aliases_path: NodePath +@onready var _aliases: ListField = self.get_node(self._aliases_path) +@export var _guidelines_path: NodePath +@onready var _guidelines: TextField = self.get_node(self._guidelines_path) +@export var _license_path: NodePath +@onready var _license: TextField = self.get_node(self._license_path) +@export var _source_path: NodePath +@onready var _source: TextField = self.get_node(self._source_path) + +func display(icon: IconSimpleIcons) -> void: + self._color.color = icon.hex + self._aliases.set_items(icon.aliases) + self._guidelines.text = icon.guidelines + self._guidelines.uri = icon.guidelines + self._license.text = icon.license + self._license.uri = icon.license_link + self._source.text = icon.source + self._source.uri = icon.source diff --git a/addons/icon_explorer/internal/ui/detail_panel/panels/simple_icons.tscn b/addons/icon_explorer/internal/ui/detail_panel/panels/simple_icons.tscn new file mode 100644 index 0000000..cb31d10 --- /dev/null +++ b/addons/icon_explorer/internal/ui/detail_panel/panels/simple_icons.tscn @@ -0,0 +1,38 @@ +[gd_scene load_steps=5 format=3 uid="uid://3hl6t03xllbp"] + +[ext_resource type="Script" path="res://addons/icon_explorer/internal/ui/detail_panel/panels/simple_icons.gd" id="1_icjlg"] +[ext_resource type="PackedScene" uid="uid://cadjan8ev877o" path="res://addons/icon_explorer/internal/ui/detail_panel/color_field.tscn" id="2_q8ihc"] +[ext_resource type="PackedScene" uid="uid://b813qk6u7eveh" path="res://addons/icon_explorer/internal/ui/detail_panel/list_field.tscn" id="3_ps7ly"] +[ext_resource type="PackedScene" uid="uid://b64tcvn5sw03h" path="res://addons/icon_explorer/internal/ui/detail_panel/text_field.tscn" id="4_soj3e"] + +[node name="simple_icons" type="VBoxContainer"] +anchors_preset = 10 +anchor_right = 1.0 +offset_bottom = 297.0 +grow_horizontal = 2 +script = ExtResource("1_icjlg") +_color_path = NodePath("color") +_aliases_path = NodePath("aliases") +_guidelines_path = NodePath("guidelines") +_license_path = NodePath("license") +_source_path = NodePath("source") + +[node name="color" parent="." instance=ExtResource("2_q8ihc")] +layout_mode = 2 +title = "Color" + +[node name="aliases" parent="." instance=ExtResource("3_ps7ly")] +layout_mode = 2 +title = "Aliases" + +[node name="guidelines" parent="." instance=ExtResource("4_soj3e")] +layout_mode = 2 +title = "Brand Guidelines" + +[node name="license" parent="." instance=ExtResource("4_soj3e")] +layout_mode = 2 +title = "License" + +[node name="source" parent="." instance=ExtResource("4_soj3e")] +layout_mode = 2 +title = "Source" diff --git a/addons/icon_explorer/internal/ui/detail_panel/panels/tabler.gd b/addons/icon_explorer/internal/ui/detail_panel/panels/tabler.gd new file mode 100644 index 0000000..9a6e6c7 --- /dev/null +++ b/addons/icon_explorer/internal/ui/detail_panel/panels/tabler.gd @@ -0,0 +1,18 @@ +@tool +extends VBoxContainer + +const IconTabler := preload("res://addons/icon_explorer/internal/scripts/collections/icon_tabler.gd") +const TextField := preload("res://addons/icon_explorer/internal/ui/detail_panel/text_field.gd") +const ListField := preload("res://addons/icon_explorer/internal/ui/detail_panel/list_field.gd") + +@export var _category_path: NodePath +@onready var _category: TextField = self.get_node(self._category_path) +@export var _tags_path: NodePath +@onready var _tags: ListField = self.get_node(self._tags_path) +@export var _version_path: NodePath +@onready var _version: TextField = self.get_node(self._version_path) + +func display(icon: IconTabler) -> void: + self._tags.set_items(icon.tags) + self._category.text = icon.category + self._version.text = icon.version diff --git a/addons/icon_explorer/internal/ui/detail_panel/panels/tabler.tscn b/addons/icon_explorer/internal/ui/detail_panel/panels/tabler.tscn new file mode 100644 index 0000000..71c7627 --- /dev/null +++ b/addons/icon_explorer/internal/ui/detail_panel/panels/tabler.tscn @@ -0,0 +1,28 @@ +[gd_scene load_steps=4 format=3 uid="uid://lk45nf0tqm80"] + +[ext_resource type="PackedScene" uid="uid://b813qk6u7eveh" path="res://addons/icon_explorer/internal/ui/detail_panel/list_field.tscn" id="1_muhea"] +[ext_resource type="Script" path="res://addons/icon_explorer/internal/ui/detail_panel/panels/tabler.gd" id="1_te45c"] +[ext_resource type="PackedScene" uid="uid://b64tcvn5sw03h" path="res://addons/icon_explorer/internal/ui/detail_panel/text_field.tscn" id="2_u3ntg"] + + +[node name="tabler" type="VBoxContainer"] +anchors_preset = 10 +anchor_right = 1.0 +offset_bottom = 136.0 +grow_horizontal = 2 +script = ExtResource("1_te45c") +_category_path = NodePath("category") +_tags_path = NodePath("tags") +_version_path = NodePath("version_added") + +[node name="category" parent="." instance=ExtResource("2_u3ntg")] +layout_mode = 2 +title = "Category" + +[node name="tags" parent="." instance=ExtResource("1_muhea")] +layout_mode = 2 +title = "Tags" + +[node name="version_added" parent="." instance=ExtResource("2_u3ntg")] +layout_mode = 2 +title = "Version added" diff --git a/addons/icon_explorer/internal/ui/detail_panel/text_field.gd b/addons/icon_explorer/internal/ui/detail_panel/text_field.gd new file mode 100644 index 0000000..023ec19 --- /dev/null +++ b/addons/icon_explorer/internal/ui/detail_panel/text_field.gd @@ -0,0 +1,35 @@ +@tool +extends "res://addons/icon_explorer/internal/ui/detail_panel/field.gd" + +@export var text: String: + set = set_text +@export var uri: String: + set = set_uri + +@export var _label: Label +@export var _button: Button + +func set_text(new_text: String) -> void: + text = new_text + if self._label != null: + self._label.text = new_text + self._label.tooltip_text = new_text + self.visible = new_text != "" || self.uri != "" + +func set_uri(new_uri: String) -> void: + uri = new_uri + if self._button != null: + self._button.tooltip_text = new_uri + self._button.visible = new_uri != "" + self.visible = new_uri != "" || self.text != "" + +func _ready() -> void: + super._ready() + if Engine.is_editor_hint(): + self._button.icon = self.get_theme_icon(&"ExternalLink", &"EditorIcons") + self._button.pressed.connect(self._on_pressed) + self.text = self.text + self.uri = self.uri + +func _on_pressed() -> void: + OS.shell_open(self.uri) diff --git a/addons/icon_explorer/internal/ui/detail_panel/text_field.tscn b/addons/icon_explorer/internal/ui/detail_panel/text_field.tscn new file mode 100644 index 0000000..dfa5740 --- /dev/null +++ b/addons/icon_explorer/internal/ui/detail_panel/text_field.tscn @@ -0,0 +1,36 @@ +[gd_scene load_steps=3 format=3 uid="uid://b64tcvn5sw03h"] + +[ext_resource type="Script" path="res://addons/icon_explorer/internal/ui/detail_panel/text_field.gd" id="1_fhni0"] +[ext_resource type="PackedScene" uid="uid://bonqki0uorlhq" path="res://addons/icon_explorer/internal/ui/detail_panel/field_title.tscn" id="2_vjbmh"] + +[node name="text_field" type="VBoxContainer" node_paths=PackedStringArray("_label", "_button")] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_fhni0") +_title_path = NodePath("title_panel") +_label = NodePath("margin_container/h_box_container/label") +_button = NodePath("margin_container/h_box_container/button") + +[node name="title_panel" parent="." instance=ExtResource("2_vjbmh")] +layout_mode = 2 + +[node name="margin_container" type="MarginContainer" parent="."] +layout_mode = 2 +theme_override_constants/margin_left = 16 + +[node name="h_box_container" type="HBoxContainer" parent="margin_container"] +layout_mode = 2 + +[node name="button" type="Button" parent="margin_container/h_box_container"] +layout_mode = 2 +mouse_default_cursor_shape = 2 +flat = true + +[node name="label" type="Label" parent="margin_container/h_box_container"] +layout_mode = 2 +size_flags_horizontal = 3 +mouse_filter = 1 +text_overrun_behavior = 3 diff --git a/addons/icon_explorer/internal/ui/detail_panel/toolbar.gd b/addons/icon_explorer/internal/ui/detail_panel/toolbar.gd new file mode 100644 index 0000000..26860fa --- /dev/null +++ b/addons/icon_explorer/internal/ui/detail_panel/toolbar.gd @@ -0,0 +1,24 @@ +@tool +extends HBoxContainer + +signal save_pressed() +signal save_colored_pressed() + +@export var _save_button: Button +@export var _save_colored_button: Button + +func _ready() -> void: + if Engine.is_editor_hint(): + self._save_button.icon = self.get_theme_icon(&"Save", &"EditorIcons") + var save_colored_img: Image = self.get_theme_icon(&"Save", &"EditorIcons").get_image() + var color_img: Image = self.get_theme_icon(&"StyleBoxFlat", &"EditorIcons").get_image() + save_colored_img.blend_rect_mask(color_img, save_colored_img, Rect2i(Vector2i.ZERO, color_img.get_size()), Vector2i.ZERO) + self._save_colored_button.icon = ImageTexture.create_from_image(save_colored_img) + self._save_button.pressed.connect(self._on_save_button_pressed) + self._save_colored_button.pressed.connect(self._on_save_colored_button_pressed) + +func _on_save_button_pressed() -> void: + self.save_pressed.emit() + +func _on_save_colored_button_pressed() -> void: + self.save_colored_pressed.emit() diff --git a/addons/icon_explorer/internal/ui/explorer/explorer.gd b/addons/icon_explorer/internal/ui/explorer/explorer.gd new file mode 100644 index 0000000..702e5ef --- /dev/null +++ b/addons/icon_explorer/internal/ui/explorer/explorer.gd @@ -0,0 +1,172 @@ +@tool +extends PanelContainer + +const Collection := preload("res://addons/icon_explorer/internal/scripts/collection.gd") +const FilterOptions := preload("res://addons/icon_explorer/internal/ui/explorer/filter_options.gd") +const Icon := preload("res://addons/icon_explorer/internal/scripts/icon.gd") +const IconDatabase := preload("res://addons/icon_explorer/internal/scripts/database.gd") +const DetailPanel := preload("res://addons/icon_explorer/internal/ui/detail_panel/detail_panel.gd") + +const Options := preload("res://addons/icon_explorer/internal/ui/options/options.gd") + +@export var _filter_icon: TextureRect +@export var _filter: LineEdit +@export var _filter_options_path: NodePath +@onready var _filter_options: FilterOptions = self.get_node(self._filter_options_path) +@export var _preview_color: ColorPickerButton +@export var _preview_size: HSlider +@export var _icon_list: ItemList +@export var _options_button: Button +@export var _options_popup: Window +@export var _options_path: NodePath +@onready var _options: Options = self.get_node(self._options_path) + +@export var _progress_bar: ProgressBar + +@export var _detail_panel_path: NodePath +@onready var _detail_panel: DetailPanel = self.get_node(self._detail_panel_path) + +@export var _toolbar_panel: PanelContainer +@export var _preview_options_panel: PanelContainer + +var _db: IconDatabase +var _db_loaded: bool + +# timer which starts after filter change and will fire the update +# is updated on each input again to not update the list on each change but wait a short time +var _update_timer: Timer + +func _ready() -> void: + if Engine.is_editor_hint(): + self.add_theme_stylebox_override(&"panel", self.get_theme_stylebox(&"Background", &"EditorStyles")) + self._toolbar_panel.add_theme_stylebox_override(&"panel", self.get_theme_stylebox(&"PanelForeground", &"EditorStyles")) + self._preview_options_panel.add_theme_stylebox_override(&"panel", self.get_theme_stylebox(&"LaunchPadNormal", &"EditorStyles")) + + self._update_timer = Timer.new() + self._update_timer.one_shot = true + self._update_timer.timeout.connect(self.update) + self.add_child(self._update_timer, true) + self.get_viewport().gui_embed_subwindows = false + self._filter_icon.texture = self.get_theme_icon(&"Search", &"EditorIcons") + self._filter_options.icon = self.get_theme_icon(&"AnimationFilter", &"EditorIcons") + self._filter_options.options_changed.connect(self.update) + self._icon_list.item_selected.connect(self._on_icon_selected) + self._filter.text_changed.connect(self._on_filter_changed) + self._filter.text_submitted.connect(self._on_filter_submitted) + self._preview_color.popup_closed.connect(self._update_preview_color) + self._update_preview_size(ProjectSettings.get_setting("plugins/icon_explorer/preview_size_exp")) + if Engine.is_editor_hint(): + ProjectSettings.settings_changed.connect( + func () -> void: + self._update_preview_size(ProjectSettings.get_setting("plugins/icon_explorer/preview_size_exp")) + ) + self._preview_size.value_changed.connect(self._on_preview_size_changed) + self._detail_panel.preview_color = self._preview_color.color + + self._options_button.icon = self.get_theme_icon(&"Tools", &"EditorIcons") + self._options_button.pressed.connect(self._on_option_pressed) + + self._db = IconDatabase.new(self.get_tree()) + self._db.loaded.connect(self._on_icon_database_loaded) + self._db.collection_installed.connect(self._on_database_changed) + self._db.collection_removed.connect(self._on_database_changed) + + self._options.db = self._db + + if !Engine.is_editor_hint() || (Engine.is_editor_hint() && ProjectSettings.get_setting("plugins/icon_explorer/load_on_startup", false)): + self.load_db() + +func _process(_delta: float) -> void: + self._progress_bar.value = self._db.load_progress() + +func load_db() -> void: + if self._db_loaded: + return + self._db_loaded = true + self.set_process(true) + self._db.load() + +func update() -> void: + var filter: String = self._filter.text.to_lower() + self._clear() + + var icons: Array[Icon] = self._db.icons().duplicate() + var filter_popup: PopupMenu = self._filter_options.get_popup() + for icon in icons: + if !filter_popup.is_item_checked(filter_popup.get_item_index(icon.collection.id())): + icon.sort_priority = 0 + continue + if filter == "": + icon.sort_priority = 5 + else: + icon.sort_priority = icon.match(filter) + icons.sort_custom(Icon.compare) + var color: Color = self._preview_color.color + for icon in icons: + if icon.sort_priority == 0: + continue + var idx: int = self._icon_list.add_item(icon.name, icon.texture) + self._icon_list.set_item_tooltip(idx, icon.collection.name) + self._icon_list.set_item_metadata(idx, icon) + self._icon_list.set_item_icon_modulate(idx, color) + self._icon_list.get_v_scroll_bar().value = 0 + +func _clear() -> void: + self._icon_list.clear() + self._detail_panel.display(null) + +func _update_preview_color() -> void: + var color: Color = self._preview_color.color + self._detail_panel.preview_color = color + for idx: int in range(self._icon_list.item_count): + self._icon_list.set_item_icon_modulate(idx, color) + +func _on_preview_size_changed(expo: float) -> void: + self._update_preview_size(expo) + ProjectSettings.set_setting("plugins/icon_explorer/preview_size_exp", expo) + +func _update_preview_size(expo: float) -> void: + var icon_size: int = int(pow(2.0, expo)) + self._icon_list.fixed_icon_size = Vector2i(icon_size, icon_size) + self._icon_list.fixed_column_width = 2 * icon_size + self._detail_panel.preview_size = icon_size + +func _on_database_changed(_id: int, status: Error) -> void: + var filter_popup: PopupMenu = self._filter_options.get_popup() + filter_popup.clear() + for coll: Collection in self._db.installed_collections(): + filter_popup.add_check_item(coll.name, int(coll.id())) + for idx: int in range(filter_popup.item_count): + filter_popup.set_item_checked(idx, true) + self._filter_options.disabled = filter_popup.item_count == 0 + if status == Error.OK: + self.update() + +func _on_icon_database_loaded() -> void: + self.set_process(false) + self._progress_bar.value = 100.0 + self._progress_bar.visible = false + self._filter.editable = true + self._options_button.disabled = false + + var filter_popup: PopupMenu = self._filter_options.get_popup() + filter_popup.clear() + for coll: Collection in self._db.installed_collections(): + filter_popup.add_check_item(coll.name, int(coll.id())) + for idx: int in range(filter_popup.item_count): + filter_popup.set_item_checked(idx, true) + self._filter_options.disabled = filter_popup.item_count == 0 + self.update() + self._options.update() + +func _on_icon_selected(idx: int) -> void: + self._detail_panel.display(self._icon_list.get_item_metadata(idx) as Icon) + +func _on_filter_changed(_text: String) -> void: + self._update_timer.start(0.3) + +func _on_filter_submitted(_text: String) -> void: + self._update_timer.start(0.05) + +func _on_option_pressed() -> void: + self._options_popup.popup_centered_ratio(0.35) diff --git a/addons/icon_explorer/internal/ui/explorer/explorer.tscn b/addons/icon_explorer/internal/ui/explorer/explorer.tscn new file mode 100644 index 0000000..21adbf3 --- /dev/null +++ b/addons/icon_explorer/internal/ui/explorer/explorer.tscn @@ -0,0 +1,123 @@ +[gd_scene load_steps=7 format=3 uid="uid://dnxwdqwt2eqfi"] + +[ext_resource type="Script" path="res://addons/icon_explorer/internal/ui/explorer/explorer.gd" id="1_2oqmk"] +[ext_resource type="Script" path="res://addons/icon_explorer/internal/ui/explorer/filter_options.gd" id="2_dkuya"] +[ext_resource type="PackedScene" uid="uid://c3s1t8hiu6xll" path="res://addons/icon_explorer/internal/ui/detail_panel/detail_panel.tscn" id="3_w4j77"] +[ext_resource type="Script" path="res://addons/icon_explorer/internal/ui/explorer/option_popup.gd" id="4_03l1v"] +[ext_resource type="PackedScene" uid="uid://bfmh2kaf2qbrx" path="res://addons/icon_explorer/internal/ui/options/options.tscn" id="5_wngaf"] + +[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_gnh63"] + +[node name="explorer" type="PanelContainer" node_paths=PackedStringArray("_filter_icon", "_filter", "_preview_color", "_preview_size", "_icon_list", "_options_button", "_options_popup", "_progress_bar", "_toolbar_panel", "_preview_options_panel")] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_2oqmk") +_filter_icon = NodePath("icon_viewer/toolbar_panel/toolbar/filter_icon") +_filter = NodePath("icon_viewer/toolbar_panel/toolbar/filter") +_filter_options_path = NodePath("icon_viewer/toolbar_panel/toolbar/filter_options") +_preview_color = NodePath("icon_viewer/HSplitContainer/v_box_container/preview_options_panel/preview_options/preview_color") +_preview_size = NodePath("icon_viewer/HSplitContainer/v_box_container/preview_options_panel/preview_options/preview_size") +_icon_list = NodePath("icon_viewer/HSplitContainer/icons") +_options_button = NodePath("icon_viewer/toolbar_panel/toolbar/options") +_options_popup = NodePath("option_popup") +_options_path = NodePath("option_popup/options") +_progress_bar = NodePath("icon_viewer/progress_bar") +_detail_panel_path = NodePath("icon_viewer/HSplitContainer/v_box_container/detail_panel") +_toolbar_panel = NodePath("icon_viewer/toolbar_panel") +_preview_options_panel = NodePath("icon_viewer/HSplitContainer/v_box_container/preview_options_panel") + +[node name="icon_viewer" type="VBoxContainer" parent="."] +layout_mode = 2 + +[node name="toolbar_panel" type="PanelContainer" parent="icon_viewer"] +layout_mode = 2 + +[node name="toolbar" type="HBoxContainer" parent="icon_viewer/toolbar_panel"] +layout_mode = 2 + +[node name="filter_icon" type="TextureRect" parent="icon_viewer/toolbar_panel/toolbar"] +layout_mode = 2 +stretch_mode = 5 + +[node name="filter" type="LineEdit" parent="icon_viewer/toolbar_panel/toolbar"] +layout_mode = 2 +size_flags_horizontal = 3 +auto_translate = false +localize_numeral_system = false +placeholder_text = "search icons..." +editable = false +clear_button_enabled = true + +[node name="filter_options" type="MenuButton" parent="icon_viewer/toolbar_panel/toolbar"] +layout_mode = 2 +disabled = true +script = ExtResource("2_dkuya") + +[node name="options" type="Button" parent="icon_viewer/toolbar_panel/toolbar"] +layout_mode = 2 +disabled = true +flat = true + +[node name="progress_bar" type="ProgressBar" parent="icon_viewer"] +layout_mode = 2 +size_flags_vertical = 1 +show_percentage = false + +[node name="HSplitContainer" type="HSplitContainer" parent="icon_viewer"] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="icons" type="ItemList" parent="icon_viewer/HSplitContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +theme_override_styles/focus = SubResource("StyleBoxEmpty_gnh63") +allow_search = false +max_text_lines = 2 +max_columns = 0 +same_column_width = true +fixed_column_width = 128 +icon_mode = 0 +fixed_icon_size = Vector2i(64, 64) + +[node name="v_box_container" type="VBoxContainer" parent="icon_viewer/HSplitContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 0.34 + +[node name="preview_options_panel" type="PanelContainer" parent="icon_viewer/HSplitContainer/v_box_container"] +layout_mode = 2 + +[node name="preview_options" type="HBoxContainer" parent="icon_viewer/HSplitContainer/v_box_container/preview_options_panel"] +layout_mode = 2 + +[node name="preview_size" type="HSlider" parent="icon_viewer/HSplitContainer/v_box_container/preview_options_panel/preview_options"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 1 +min_value = 4.0 +max_value = 8.0 +value = 6.0 + +[node name="preview_color" type="ColorPickerButton" parent="icon_viewer/HSplitContainer/v_box_container/preview_options_panel/preview_options"] +custom_minimum_size = Vector2(32, 24) +layout_mode = 2 +color = Color(1, 1, 1, 1) +edit_alpha = false + +[node name="detail_panel" parent="icon_viewer/HSplitContainer/v_box_container" instance=ExtResource("3_w4j77")] +layout_mode = 2 +size_flags_stretch_ratio = 0.34 + +[node name="option_popup" type="Window" parent="."] +title = "Icon Explorer Options" +visible = false +wrap_controls = true +transient = true +exclusive = true +script = ExtResource("4_03l1v") + +[node name="options" parent="option_popup" instance=ExtResource("5_wngaf")] diff --git a/addons/icon_explorer/internal/ui/explorer/filter_options.gd b/addons/icon_explorer/internal/ui/explorer/filter_options.gd new file mode 100644 index 0000000..64cab69 --- /dev/null +++ b/addons/icon_explorer/internal/ui/explorer/filter_options.gd @@ -0,0 +1,14 @@ +@tool +extends MenuButton + +signal options_changed() + +func _ready() -> void: + var popup: PopupMenu = self.get_popup() + popup.hide_on_checkable_item_selection = false + popup.index_pressed.connect(self._on_index_pressed) + +func _on_index_pressed(idx: int) -> void: + var popup: PopupMenu = self.get_popup() + popup.set_item_checked(idx, !popup.is_item_checked(idx)) + self.options_changed.emit() diff --git a/addons/icon_explorer/internal/ui/explorer/option_popup.gd b/addons/icon_explorer/internal/ui/explorer/option_popup.gd new file mode 100644 index 0000000..7d12a14 --- /dev/null +++ b/addons/icon_explorer/internal/ui/explorer/option_popup.gd @@ -0,0 +1,6 @@ +@tool +extends Window + +func _notification(what: int) -> void: + if (what == NOTIFICATION_WM_CLOSE_REQUEST): + self.hide() diff --git a/addons/icon_explorer/internal/ui/explorer_dialog.gd b/addons/icon_explorer/internal/ui/explorer_dialog.gd new file mode 100644 index 0000000..f1da979 --- /dev/null +++ b/addons/icon_explorer/internal/ui/explorer_dialog.gd @@ -0,0 +1,15 @@ +@tool +extends Window + +const Explorer := preload("res://addons/icon_explorer/internal/ui/explorer/explorer.gd") + +@export var _explorer_path: NodePath +@onready var _explorer: Explorer = self.get_node(self._explorer_path) + +func _notification(what: int) -> void: + if (what == NOTIFICATION_WM_CLOSE_REQUEST): + self.hide() + +func _on_about_to_popup() -> void: + if Engine.is_editor_hint() && !ProjectSettings.get_setting("plugins/icon_explorer/load_on_startup", false): + self._explorer.load_db() diff --git a/addons/icon_explorer/internal/ui/explorer_dialog.tscn b/addons/icon_explorer/internal/ui/explorer_dialog.tscn new file mode 100644 index 0000000..2273e8f --- /dev/null +++ b/addons/icon_explorer/internal/ui/explorer_dialog.tscn @@ -0,0 +1,19 @@ +[gd_scene load_steps=3 format=3 uid="uid://dyfr78rcugqaa"] + +[ext_resource type="Script" path="res://addons/icon_explorer/internal/ui/explorer_dialog.gd" id="1_yupxp"] +[ext_resource type="PackedScene" uid="uid://dnxwdqwt2eqfi" path="res://addons/icon_explorer/internal/ui/explorer/explorer.tscn" id="2_3tinl"] + +[node name="explorer_dialog" type="Window"] +disable_3d = true +title = "Icon Explorer" +position = Vector2i(210, 133) +size = Vector2i(1920, 1080) +visible = false +wrap_controls = true +transient = true +script = ExtResource("1_yupxp") +_explorer_path = NodePath("explorer") + +[node name="explorer" parent="." instance=ExtResource("2_3tinl")] + +[connection signal="about_to_popup" from="." to="." method="_on_about_to_popup"] diff --git a/addons/icon_explorer/internal/ui/options/options.gd b/addons/icon_explorer/internal/ui/options/options.gd new file mode 100644 index 0000000..af58a0d --- /dev/null +++ b/addons/icon_explorer/internal/ui/options/options.gd @@ -0,0 +1,138 @@ +@tool +extends PanelContainer + +const IconDatabase := preload("res://addons/icon_explorer/internal/scripts/database.gd") +const Collection := preload("res://addons/icon_explorer/internal/scripts/collection.gd") + +enum BUTTON_ID { + INSTALL, + REMOVE, + OPEN_DIR, + WEB +} + +@export var _load_on_startup: CheckBox +@export var _collection_tree: Tree +@export var _options_panel: PanelContainer +@export var _options_label: Label +@export var _collections_panel: PanelContainer +@export var _collections_label: Label + +var _http_request: HTTPRequest + +var db: IconDatabase: + set = set_db + +var _processing: int = -1 +var _process_spinner_frame: int +var _process_spinner_msec: float + +func set_db(db_: IconDatabase) -> void: + if db != null: + db.collection_installed.disconnect(self._on_processing_finished) + db.collection_removed.disconnect(self._on_processing_finished) + db = db_ + db.collection_installed.connect(self._on_processing_finished) + db.collection_removed.connect(self._on_processing_finished) + self.update() + +func _process(delta: float) -> void: + self._process_spinner_msec += delta + if self._process_spinner_msec > 0.2: + self._process_spinner_msec = fmod(self._process_spinner_msec, 0.2) + self._process_spinner_frame = (self._process_spinner_frame + 1) % 8 + if self._processing != -1: + for item: TreeItem in _collection_tree.get_root().get_children(): + if (item.get_metadata(0) as Collection).id() == self._processing: + item.set_icon(0, self.get_theme_icon("Progress"+str(self._process_spinner_frame + 1), &"EditorIcons")) + +func _ready() -> void: + if Engine.is_editor_hint(): + self._options_label.add_theme_font_override(&"font", self.get_theme_font(&"title", &"EditorFonts")) + self._collections_label.add_theme_font_override(&"font", self.get_theme_font(&"title", &"EditorFonts")) + self.add_theme_stylebox_override(&"panel", self.get_theme_stylebox(&"Background", &"EditorStyles")) + self._collections_panel.add_theme_stylebox_override(&"panel", self.get_theme_stylebox(&"PanelForeground", &"EditorStyles")) + self._options_panel.add_theme_stylebox_override(&"panel", self.get_theme_stylebox(&"PanelForeground", &"EditorStyles")) + + self._load_on_startup.toggled.connect(self._on_startup_changed) + + self._collection_tree.columns = 6 + self._collection_tree.set_column_title(0, "Installed") + self._collection_tree.set_column_title(1, "Collection") + self._collection_tree.set_column_title(2, "Version") + self._collection_tree.set_column_title(3, "License") + self._collection_tree.set_column_title(4, "Web") + self._collection_tree.set_column_title(5, "Actions") + self._collection_tree.set_column_expand(0, false) + self._collection_tree.set_column_expand(1, true) + self._collection_tree.set_column_expand(2, true) + self._collection_tree.set_column_expand(3, true) + self._collection_tree.set_column_expand(4, false) + self._collection_tree.set_column_expand(5, false) + self._collection_tree.button_clicked.connect(self._on_button_clicked) + + if Engine.is_editor_hint(): + ProjectSettings.settings_changed.connect(self.update) + +func update() -> void: + self._load_on_startup.button_pressed = ProjectSettings.get_setting("plugins/icon_explorer/load_on_startup", false) + + self._collection_tree.clear() + self._collection_tree.create_item() + for coll: Collection in self.db.collections(): + var item: TreeItem = self._collection_tree.create_item() + item.set_metadata(0, coll) + item.set_text(1, coll.name) + item.set_text(2, coll.version) + item.set_text(3, coll.license) + item.set_text_alignment(0, HORIZONTAL_ALIGNMENT_CENTER) + item.set_text_alignment(4, HORIZONTAL_ALIGNMENT_CENTER) + item.add_button(4, self.get_theme_icon(&"ExternalLink", &"EditorIcons"), BUTTON_ID.WEB, coll.web == "", "Open in Browser") + var is_processed: bool = self._processing == coll.id() + if is_processed: + pass + elif coll.is_installed(): + item.set_icon(0, self.get_theme_icon(&"StatusSuccess", &"EditorIcons")) + else: + item.set_icon(0, self.get_theme_icon(&"Node", &"EditorIcons")) + + var is_one_processed: bool = self._processing != -1 + if coll.is_installed(): + item.add_button(5, self.get_theme_icon(&"Reload", &"EditorIcons"), BUTTON_ID.INSTALL, is_one_processed, "Update") + else: + item.add_button(5, self.get_theme_icon(&"AssetLib", &"EditorIcons"), BUTTON_ID.INSTALL, is_one_processed, "Install") + item.add_button(5, self.get_theme_icon(&"Remove", &"EditorIcons"), BUTTON_ID.REMOVE, is_one_processed || !coll.is_installed(), "Remove") + item.add_button(5, self.get_theme_icon(&"Filesystem", &"EditorIcons"), BUTTON_ID.OPEN_DIR, !coll.is_installed(), "Show in File Explorer") + +func _gen_progress_texture() -> Array[Texture2D]: + var anim: Array[Texture2D] = [] + for idx: int in range(8): + anim.append(self.get_theme_icon("Progress"+str(idx + 1), &"EditorIcons")) + return anim + +func _on_startup_changed(toggled: bool) -> void: + ProjectSettings.set_setting("plugins/icon_explorer/load_on_startup", toggled) + +func _on_button_clicked(item: TreeItem, _column: int, id: int, _mouse_button_index: int) -> void: + var coll: Collection = item.get_metadata(0) + match id: + BUTTON_ID.INSTALL: + self._processing = coll.id() + self.update() + if self._http_request != null: + self._http_request.queue_free() + self._http_request = HTTPRequest.new() + self.add_child(self._http_request, true) + self.db.install(coll, self._http_request, "") + BUTTON_ID.REMOVE: + self._processing = coll.id() + self.update() + self.db.remove(coll) + BUTTON_ID.OPEN_DIR: + OS.shell_show_in_file_manager(coll.icon_directory()) + BUTTON_ID.WEB: + OS.shell_open(coll.web) + +func _on_processing_finished(id: int, status: Error) -> void: + self._processing = -1 + self.update() diff --git a/addons/icon_explorer/internal/ui/options/options.tscn b/addons/icon_explorer/internal/ui/options/options.tscn new file mode 100644 index 0000000..814dedd --- /dev/null +++ b/addons/icon_explorer/internal/ui/options/options.tscn @@ -0,0 +1,74 @@ +[gd_scene load_steps=2 format=3 uid="uid://bfmh2kaf2qbrx"] + +[ext_resource type="Script" path="res://addons/icon_explorer/internal/ui/options/options.gd" id="1_hdn86"] + +[node name="control" type="PanelContainer" node_paths=PackedStringArray("_load_on_startup", "_collection_tree", "_options_panel", "_options_label", "_collections_panel", "_collections_label")] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_hdn86") +_load_on_startup = NodePath("options/options/v_box_container/load_on_startup") +_collection_tree = NodePath("options/options/tree") +_options_panel = NodePath("options/options/options_panel") +_options_label = NodePath("options/options/options_panel/options") +_collections_panel = NodePath("options/options/collections_panel") +_collections_label = NodePath("options/options/collections_panel/collections_title") + +[node name="options" type="ScrollContainer" parent="."] +layout_mode = 2 + +[node name="options" type="VBoxContainer" parent="options"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="options_panel" type="PanelContainer" parent="options/options"] +layout_mode = 2 + +[node name="options" type="Label" parent="options/options/options_panel"] +layout_mode = 2 +text = "Options" + +[node name="v_box_container" type="GridContainer" parent="options/options"] +layout_mode = 2 +columns = 2 + +[node name="label" type="Label" parent="options/options/v_box_container"] +layout_mode = 2 +text = "Load on Godot startup" + +[node name="load_on_startup" type="CheckBox" parent="options/options/v_box_container"] +layout_mode = 2 + +[node name="label2" type="Label" parent="options/options/v_box_container"] +visible = false +layout_mode = 2 +text = "Base SVG texture size" + +[node name="spin_box" type="SpinBox" parent="options/options/v_box_container"] +visible = false +layout_mode = 2 +min_value = 8.0 +max_value = 1024.0 +value = 8.0 +editable = false +suffix = "px" + +[node name="collections_panel" type="PanelContainer" parent="options/options"] +layout_mode = 2 + +[node name="collections_title" type="Label" parent="options/options/collections_panel"] +layout_mode = 2 +text = "Collections" + +[node name="tree" type="Tree" parent="options/options"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +column_titles_visible = true +allow_search = false +hide_folding = true +hide_root = true +select_mode = 1 diff --git a/addons/icon_explorer/plugin.cfg b/addons/icon_explorer/plugin.cfg new file mode 100644 index 0000000..cd66015 --- /dev/null +++ b/addons/icon_explorer/plugin.cfg @@ -0,0 +1,19 @@ +[plugin] + +name="Icon Explorer" +description="Explore icon collections." +author="Iceflower S" +version="1.0.0" +script="plugin.gd" +license="MIT" +repository="https://github.com/kenyoni-software/godot-addons" +keywords=[ + "tool" +] +classifiers=[ + "Development Status :: 5 - Production/Stable", + "License :: OSI Approved :: MIT License" +] + +[plugin.dependencies] +godot=">=4.2" diff --git a/addons/icon_explorer/plugin.gd b/addons/icon_explorer/plugin.gd new file mode 100644 index 0000000..904884f --- /dev/null +++ b/addons/icon_explorer/plugin.gd @@ -0,0 +1,38 @@ +@tool +extends EditorPlugin + +const ExplorerDialogScene: PackedScene = preload("res://addons/icon_explorer/internal/ui/explorer_dialog.tscn") + +var _explorer_dialog: Window + +func _get_plugin_name() -> String: + return "Icon Explorer" + +func _enter_tree() -> void: + set_project_setting("plugins/icon_explorer/load_on_startup", false, TYPE_BOOL, PROPERTY_HINT_NONE) + set_project_setting("plugins/icon_explorer/preview_size_exp", 6, TYPE_INT, PROPERTY_HINT_RANGE, "4,8,1") + + self._explorer_dialog = ExplorerDialogScene.instantiate() + EditorInterface.get_base_control().add_child(self._explorer_dialog) + self.add_tool_menu_item(self._get_plugin_name() + "...", self._show_popup) + +func _exit_tree() -> void: + self.remove_tool_menu_item(self._get_plugin_name() + "...") + self._explorer_dialog.queue_free() + +func _show_popup() -> void: + if self._explorer_dialog.visible: + self._explorer_dialog.grab_focus() + else: + self._explorer_dialog.popup_centered_ratio(0.4) + +static func set_project_setting(key: String, initial_value, type: int, type_hint: int, hint_string: String = "") -> void: + if not ProjectSettings.has_setting(key): + ProjectSettings.set_setting(key, initial_value) + ProjectSettings.set_initial_value(key, initial_value) + ProjectSettings.add_property_info({ + "name": key, + "type": type, + "hint": type_hint, + "hint_string": type_hint, + }) diff --git a/addons/icons_patcher/plugin.cfg b/addons/icons_patcher/plugin.cfg index 636cad4..9155aec 100644 --- a/addons/icons_patcher/plugin.cfg +++ b/addons/icons_patcher/plugin.cfg @@ -3,7 +3,7 @@ name="Icons Patcher" description="Will convert Pictogrammers icon to white." author="Iceflower S" -version="1.3.0" +version="1.3.2" script="plugin.gd" license="MIT" repository="https://github.com/kenyoni-software/godot-addons" diff --git a/addons/icons_patcher/plugin.gd b/addons/icons_patcher/plugin.gd index 98e9508..8cfe538 100644 --- a/addons/icons_patcher/plugin.gd +++ b/addons/icons_patcher/plugin.gd @@ -11,7 +11,6 @@ func _enter_tree() -> void: Utils.init_project_setting(Utils.MDI_DIRECTORY_PATH, "", TYPE_STRING, PROPERTY_HINT_DIR) self.tool_menu = ToolMenu.new() - tool_menu.editor_filesystem = self.get_editor_interface().get_resource_filesystem() self.add_tool_submenu_item("Icons Patcher", self.tool_menu) func _exit_tree() -> void: diff --git a/addons/icons_patcher/tool_menu.gd b/addons/icons_patcher/tool_menu.gd index 022e9e0..b2400ad 100644 --- a/addons/icons_patcher/tool_menu.gd +++ b/addons/icons_patcher/tool_menu.gd @@ -2,8 +2,6 @@ extends PopupMenu const Utils := preload("utils.gd") -var editor_filesystem: EditorFileSystem - func _ready() -> void: self.add_item("Patch Material Design Icons") @@ -12,13 +10,13 @@ func _ready() -> void: func _set_item_details(idx: int, settings_key: String, tooltip: String) -> void: var path: String = ProjectSettings.get_setting(settings_key) - var can_use: bool = path != "" and DirAccess.dir_exists_absolute(path) + var can_use: bool = path != "" && DirAccess.dir_exists_absolute(path) self.set_item_disabled(idx, !can_use) if can_use: self.set_item_icon(idx, null) self.set_item_tooltip(idx, "") else: - self.set_item_icon(idx, self.get_theme_icon("NodeWarning", "EditorIcons")) + self.set_item_icon(idx, self.get_theme_icon(&"NodeWarning", &"EditorIcons")) self.set_item_tooltip(idx, tooltip) func _on_about_to_popup() -> void: @@ -36,5 +34,5 @@ func _patch_icons_material_design() -> void: var rx: RegEx = RegEx.new() rx.compile('(" fill="#[a-fA-F0-9]{6})?">') var patched_icons: PackedStringArray = Utils.patch_icon_dir(base_path, rx, '" fill="#ffffff">') - self.editor_filesystem.reimport_files(patched_icons) + EditorInterface.get_resource_filesystem().reimport_files(patched_icons) print("Patched " + base_path) diff --git a/addons/licenses/component.gd b/addons/licenses/component.gd index 8471d37..eea3514 100644 --- a/addons/licenses/component.gd +++ b/addons/licenses/component.gd @@ -172,7 +172,7 @@ func duplicate(): dup.contact = self.contact dup.web = self.web dup.paths = self.paths.duplicate() - for license in self.licenses: + for license: License in self.licenses: dup.licenses.append(license.duplicate()) return dup diff --git a/addons/licenses/internal/component_detail_tree.gd b/addons/licenses/internal/component_detail_tree.gd index 0ddb359..b8b1f7c 100644 --- a/addons/licenses/internal/component_detail_tree.gd +++ b/addons/licenses/internal/component_detail_tree.gd @@ -59,7 +59,7 @@ func _on_cell_selected() -> void: if self._selected_item != null: self._selected_item.clear_custom_bg_color(0) self._selected_item = self.get_selected() - self._selected_item.set_custom_bg_color(0, self.get_theme_color("box_selection_fill_color", "Editor")) + self._selected_item.set_custom_bg_color(0, self.get_theme_color(&"box_selection_fill_color", &"Editor")) if self.get_selected_column() == 0: self._selected_item.deselect(0) self._selected_item.select(1) diff --git a/addons/licenses/internal/components_tree.gd b/addons/licenses/internal/components_tree.gd index 23c99da..61c54ae 100644 --- a/addons/licenses/internal/components_tree.gd +++ b/addons/licenses/internal/components_tree.gd @@ -51,18 +51,18 @@ func reload(scroll_to: Component = null) -> void: # count current added custom components var idx: int = 0 - while idx < self._components.count() or readonly_idx < len(self._readonly_components): + while idx < self._components.count() || readonly_idx < len(self._readonly_components): var component: Component = null var cur_idx: int = 0 var cmp: bool = false # compare readonly items with editable, to determine which one to show first - if idx < self._components.count() and readonly_idx < len(self._readonly_components): + if idx < self._components.count() && readonly_idx < len(self._readonly_components): cmp = Licenses.compare_components_ascending(self._components.get_at(idx), self._readonly_components[readonly_idx]) - if readonly_idx >= len(self._readonly_components) or cmp: + if readonly_idx >= len(self._readonly_components) || cmp: component = self._components.get_at(idx) cur_idx = idx idx = idx + 1 - elif idx >= self._components.count() or not cmp: + elif idx >= self._components.count() || not cmp: component = self._readonly_components[readonly_idx] cur_idx = readonly_idx readonly_idx = readonly_idx + 1 @@ -77,8 +77,8 @@ func _create_item_menu() -> void: self._item_menu = PopupMenu.new() self._item_menu.name = "item_menu" self._item_menu.id_pressed.connect(self._on_item_menu_pressed) - self._item_menu.add_icon_item(self.get_theme_icon("Duplicate", "EditorIcons"), "Duplicate") - self._item_menu.add_icon_item(self.get_theme_icon("Remove", "EditorIcons"), "Delete") + self._item_menu.add_icon_item(self.get_theme_icon(&"Duplicate", &"EditorIcons"), "Duplicate") + self._item_menu.add_icon_item(self.get_theme_icon(&"Remove", &"EditorIcons"), "Delete") self._item_menu.size = self._item_menu.get_contents_minimum_size() self.add_child(self._item_menu) @@ -99,12 +99,12 @@ func _add_tree_item(component: Component, idx: int, parent: TreeItem) -> TreeIte item.set_meta("idx", idx) item.set_meta("readonly", component.readonly) if not component.readonly: - item.add_button(0, self.get_theme_icon("Remove", "EditorIcons"), _BTN_ID_REMOVE) + item.add_button(0, self.get_theme_icon(&"Remove", &"EditorIcons"), _BTN_ID_REMOVE) var tooltip: String = component.name var comp_warnings: PackedStringArray = component.get_warnings() if comp_warnings.size() != 0: tooltip += "\n- " + "\n- ".join(comp_warnings) - item.set_icon(0, self.get_theme_icon("NodeWarning", "EditorIcons")) + item.set_icon(0, self.get_theme_icon(&"NodeWarning", &"EditorIcons")) item.set_tooltip_text(0, tooltip) return item @@ -116,7 +116,7 @@ func _get_drag_data(at_position: Vector2) -> Variant: var item: TreeItem = self.get_item_at_position(at_position) if item == null: return null - if not item.has_meta("idx") or (item.get_meta("readonly") as bool): + if not item.has_meta("idx") || (item.get_meta("readonly") as bool): return null self.set_drop_mode_flags(Tree.DROP_MODE_ON_ITEM) @@ -125,7 +125,7 @@ func _get_drag_data(at_position: Vector2) -> Variant: var tree_item: TreeItem = self.get_root().get_next_in_tree() while tree_item != null: if tree_item.has_meta("category"): - tree_item.set_custom_color(0, self.get_theme_color("accent_color", "Editor")) + tree_item.set_custom_color(0, self.get_theme_color(&"accent_color", &"Editor")) tree_item = tree_item.get_next() var preview: Label = Label.new() diff --git a/addons/licenses/internal/file_system_watcher.gd b/addons/licenses/internal/file_system_watcher.gd index 49b41cb..ff73d36 100644 --- a/addons/licenses/internal/file_system_watcher.gd +++ b/addons/licenses/internal/file_system_watcher.gd @@ -13,8 +13,8 @@ func _init(components: ComponentsContainer): func _on_file_moved(old_file: String, new_file: String) -> void: var changed: bool = false - for comp in self._components.components(): - for idx in range(comp.paths.size()): + for comp: Component in self._components.components(): + for idx: int in range(comp.paths.size()): if comp.paths[idx] == old_file: changed = true comp.paths[idx] = new_file @@ -28,8 +28,8 @@ func _on_file_moved(old_file: String, new_file: String) -> void: func _on_folder_moved(old_folder: String, new_folder: String) -> void: var old_folder_no_slash: String = old_folder.rstrip("/") var changed: bool = false - for comp in self._components.components(): - for idx in range(comp.paths.size()): + for comp: Component in self._components.components(): + for idx: int in range(comp.paths.size()): var path: String = comp.paths[idx] if path == old_folder_no_slash: changed = true diff --git a/addons/licenses/internal/handler/array.gd b/addons/licenses/internal/handler/array.gd index 10b5234..3e9e74b 100644 --- a/addons/licenses/internal/handler/array.gd +++ b/addons/licenses/internal/handler/array.gd @@ -8,8 +8,8 @@ func _init(tree_: ComponentDetailTree, item_: TreeItem, value_: Variant, propert self.item.set_text(1, "[ " + str(len(self.value)) + " ]") self.item.set_text_alignment(1, HORIZONTAL_ALIGNMENT_CENTER) self._update_reset_button() - self.item.add_button(1, self.tree.get_theme_icon("Add", "EditorIcons"), 1) - self.item.add_button(1, self.tree.get_theme_icon("Remove", "EditorIcons"), 2) + self.item.add_button(1, self.tree.get_theme_icon(&"Add", &"EditorIcons"), 1) + self.item.add_button(1, self.tree.get_theme_icon(&"Remove", &"EditorIcons"), 2) match self._get_child_property("")["type"]: TYPE_STRING: @@ -25,18 +25,18 @@ func _get_child_property(name: String) -> Dictionary: match self.property["type"]: TYPE_PACKED_STRING_ARRAY: type = TYPE_STRING - if self.property["type"] == TYPE_ARRAY and self.property.get("constructor") != null: + if self.property["type"] == TYPE_ARRAY && self.property.get("constructor") != null: type = TYPE_OBJECT return {"name": name, "type": type, "hint": self.property.get("hint", PROPERTY_HINT_NONE), "hint_text": self.property.get("hint_text", ""), "constructor": self.property.get("constructor", null)} static func can_handle(property: Dictionary) -> bool: - return property["type"] == TYPE_PACKED_STRING_ARRAY or property["type"] == TYPE_ARRAY and property.get("constructor") != null + return property["type"] == TYPE_PACKED_STRING_ARRAY || property["type"] == TYPE_ARRAY && property.get("constructor") != null func _update_reset_button() -> void: var button_id: int = self.item.get_button_by_id(0, 0) - if !self.value.is_empty() and button_id == -1: - self.item.add_button(0, self.tree.get_theme_icon("Reload", "EditorIcons"), 0) - elif self.value.is_empty() and button_id != -1: + if !self.value.is_empty() && button_id == -1: + self.item.add_button(0, self.tree.get_theme_icon(&"Reload", &"EditorIcons"), 0) + elif self.value.is_empty() && button_id != -1: self.item.erase_button(0, button_id) func button_clicked(column: int, id: int, mouse_button_idx: int) -> void: @@ -62,7 +62,7 @@ func button_clicked(column: int, id: int, mouse_button_idx: int) -> void: # remove 2: var selected_item: TreeItem = self.tree._selected_item - if selected_item == null or selected_item.get_parent() != self.item: + if selected_item == null || selected_item.get_parent() != self.item: return self.value.remove_at(int(selected_item.get_text(0))) diff --git a/addons/licenses/internal/handler/object.gd b/addons/licenses/internal/handler/object.gd index 482ba8e..c71a3c3 100644 --- a/addons/licenses/internal/handler/object.gd +++ b/addons/licenses/internal/handler/object.gd @@ -14,7 +14,7 @@ func _init(tree_: ComponentDetailTree, item_: TreeItem, value_: Variant, propert self.tree._add_item(self.item, (self.value as Object).get(prop["name"]), prop) static func can_handle(property: Dictionary) -> bool: - return property["type"] == TYPE_OBJECT and property.get("class_name", "") != "Script" + return property["type"] == TYPE_OBJECT && property.get("class_name", "") != "Script" func child_edited(item: TreeItem) -> void: var child = item.get_meta("handler") diff --git a/addons/licenses/internal/handler/string.gd b/addons/licenses/internal/handler/string.gd index 5b62727..9f004fb 100644 --- a/addons/licenses/internal/handler/string.gd +++ b/addons/licenses/internal/handler/string.gd @@ -12,9 +12,9 @@ static func can_handle(property: Dictionary) -> bool: func _update_reset_button() -> void: var button_id: int = self.item.get_button_by_id(0, 0) - if self.value != "" and button_id == -1: - self.item.add_button(0, self.tree.get_theme_icon("Reload", "EditorIcons"), 0) - elif self.value == "" and button_id != -1: + if self.value != "" && button_id == -1: + self.item.add_button(0, self.tree.get_theme_icon(&"Reload", &"EditorIcons"), 0) + elif self.value == "" && button_id != -1: self.item.erase_button(0, button_id) func button_clicked(column: int, id: int, mouse_button_idx: int) -> void: diff --git a/addons/licenses/internal/handler/string_file.gd b/addons/licenses/internal/handler/string_file.gd index a8a9619..65c0327 100644 --- a/addons/licenses/internal/handler/string_file.gd +++ b/addons/licenses/internal/handler/string_file.gd @@ -6,16 +6,16 @@ func _init(tree_: ComponentDetailTree, item_: TreeItem, value_: Variant, propert self.item.set_text(1, self.value) self.item.set_editable(1, true) self._update_reset_button() - self.item.add_button(1, self.tree.get_theme_icon("Load", "EditorIcons"), 1) + self.item.add_button(1, self.tree.get_theme_icon(&"Load", &"EditorIcons"), 1) static func can_handle(property: Dictionary) -> bool: - return property["type"] == TYPE_STRING and property.get("hint", PROPERTY_HINT_NONE) == PROPERTY_HINT_FILE + return property["type"] == TYPE_STRING && property.get("hint", PROPERTY_HINT_NONE) == PROPERTY_HINT_FILE func _update_reset_button() -> void: var button_id: int = self.item.get_button_by_id(0, 0) - if self.value != "" and button_id == -1: - self.item.add_button(0, self.tree.get_theme_icon("Reload", "EditorIcons"), 0) - elif self.value == "" and button_id != -1: + if self.value != "" && button_id == -1: + self.item.add_button(0, self.tree.get_theme_icon(&"Reload", &"EditorIcons"), 0) + elif self.value == "" && button_id != -1: self.item.erase_button(0, button_id) func button_clicked(column: int, id: int, mouse_button_idx: int) -> void: @@ -26,17 +26,16 @@ func button_clicked(column: int, id: int, mouse_button_idx: int) -> void: self._update_reset_button() self.tree._on_item_edited(self.item) 1: - var dialog: FileDialog = FileDialog.new() + var dialog: EditorFileDialog = EditorFileDialog.new() + self.tree.add_child(dialog) if property.get("hint_text", "") == "*": - dialog.file_mode = FileDialog.FILE_MODE_OPEN_ANY + dialog.file_mode = EditorFileDialog.FILE_MODE_OPEN_ANY else: - dialog.file_mode = FileDialog.FILE_MODE_OPEN_FILE - self.tree.add_child(dialog) - var view_size: Vector2 = self.tree.get_viewport().size - dialog.popup_centered_ratio(0.4) + dialog.file_mode = EditorFileDialog.FILE_MODE_OPEN_FILE dialog.close_requested.connect(dialog.queue_free) dialog.file_selected.connect(self._on_path_selected) dialog.dir_selected.connect(self._on_path_selected) + dialog.popup_centered_ratio(0.4) func _on_path_selected(path: String) -> void: self.value = path diff --git a/addons/licenses/internal/handler/string_multiline.gd b/addons/licenses/internal/handler/string_multiline.gd index b5505ea..6a39db6 100644 --- a/addons/licenses/internal/handler/string_multiline.gd +++ b/addons/licenses/internal/handler/string_multiline.gd @@ -11,16 +11,16 @@ func _init(tree_: ComponentDetailTree, item_: TreeItem, value_: Variant, propert tooltip_text += "..." self.item.set_tooltip_text(1, tooltip_text) self._update_reset_button() - self.item.add_button(1, self.tree.get_theme_icon("DistractionFree", "EditorIcons"), 1) + self.item.add_button(1, self.tree.get_theme_icon(&"DistractionFree", &"EditorIcons"), 1) static func can_handle(property: Dictionary) -> bool: - return property["type"] == TYPE_STRING and property.get("hint", PROPERTY_HINT_NONE) == PROPERTY_HINT_MULTILINE_TEXT + return property["type"] == TYPE_STRING && property.get("hint", PROPERTY_HINT_NONE) == PROPERTY_HINT_MULTILINE_TEXT func _update_reset_button() -> void: var button_id: int = self.item.get_button_by_id(0, 0) - if self.value != "" and button_id == -1: - self.item.add_button(0, self.tree.get_theme_icon("Reload", "EditorIcons"), 0) - elif self.value == "" and button_id != -1: + if self.value != "" && button_id == -1: + self.item.add_button(0, self.tree.get_theme_icon(&"Reload", &"EditorIcons"), 0) + elif self.value == "" && button_id != -1: self.item.erase_button(0, button_id) func button_clicked(column: int, id: int, mouse_button_idx: int) -> void: diff --git a/addons/licenses/internal/licenses.gd b/addons/licenses/internal/licenses.gd index 7196d52..e40af8e 100644 --- a/addons/licenses/internal/licenses.gd +++ b/addons/licenses/internal/licenses.gd @@ -30,9 +30,9 @@ var _components: ComponentsContainer = ComponentsContainer.new() var _file_watcher: FileSystemWatcher func _ready() -> void: - self._license_file_load_button.icon = self.get_theme_icon("Load", "EditorIcons") + self._license_file_load_button.icon = self.get_theme_icon(&"Load", &"EditorIcons") self._license_file_load_button.pressed.connect(self._on_data_file_load_button_clicked) - self._set_license_filepath_button.icon = self.get_theme_icon("ImportCheck", "EditorIcons") + self._set_license_filepath_button.icon = self.get_theme_icon(&"ImportCheck", &"EditorIcons") self._set_license_filepath_button.pressed.connect(self._on_set_license_filepath_clicked) self._license_file_edit.text_submitted.connect(self._on_data_file_edit_changed) self._license_file_edit.text = Licenses.get_license_data_filepath() @@ -62,7 +62,7 @@ func reload() -> void: self._license_file_edit.right_icon = null self._license_file_edit.tooltip_text = "" else: - self._license_file_edit.right_icon = self.get_theme_icon("NodeWarning", "EditorIcons") + self._license_file_edit.right_icon = self.get_theme_icon(&"NodeWarning", &"EditorIcons") self._license_file_edit.tooltip_text = res.err_msg self._components.set_components(res.components) @@ -71,22 +71,22 @@ func reload() -> void: func _update_set_license_filepath_button() -> void: if Licenses.get_license_data_filepath() == self._license_file_edit.text: - self._set_license_filepath_button.icon = self.get_theme_icon("ImportCheck", "EditorIcons") + self._set_license_filepath_button.icon = self.get_theme_icon(&"ImportCheck", &"EditorIcons") self._set_license_filepath_button.tooltip_text = "Selected file is set as the project license file." self._set_license_filepath_button.disabled = true else: - self._set_license_filepath_button.icon = self.get_theme_icon("ImportFail", "EditorIcons") + self._set_license_filepath_button.icon = self.get_theme_icon(&"ImportFail", &"EditorIcons") self._set_license_filepath_button.tooltip_text = "Set the current file as project license file." self._set_license_filepath_button.disabled = false func _on_data_file_load_button_clicked() -> void: - var dialog: FileDialog = FileDialog.new() - dialog.file_mode = FileDialog.FILE_MODE_OPEN_FILE - dialog.current_path = self._license_file_edit.text + var dialog: EditorFileDialog = EditorFileDialog.new() self.add_child(dialog) - dialog.popup_centered_ratio(0.4) + dialog.file_mode = EditorFileDialog.FILE_MODE_OPEN_FILE + dialog.current_path = self._license_file_edit.text dialog.close_requested.connect(dialog.queue_free) dialog.file_selected.connect(self._on_data_file_selected) + dialog.popup_centered_ratio(0.4) func _on_data_file_edit_changed(new_text: String) -> void: self._on_data_file_selected(new_text) @@ -126,7 +126,7 @@ func _on_component_detail_edited(component: Component) -> void: self._components.sort_custom(Licenses.compare_components_ascending) # we cannot reload the tree while it is processing any kind of input/signals # https://github.com/godotengine/godot/issues/50084 - call_deferred("_emit_changed") + self._emit_changed.call_deferred() func _on_components_changed() -> void: Licenses.save(self._components.components(), self._license_file_edit.text) diff --git a/addons/licenses/internal/licenses.tscn b/addons/licenses/internal/licenses.tscn index 563e92c..46c6025 100644 --- a/addons/licenses/internal/licenses.tscn +++ b/addons/licenses/internal/licenses.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=8 format=3 uid="uid://bpqvyf7kssc70"] +[gd_scene load_steps=8 format=3 uid="uid://dfj2mhwrs1oss"] [ext_resource type="Script" path="res://addons/licenses/internal/component_detail_tree.gd" id="1"] [ext_resource type="Script" path="res://addons/licenses/internal/components_tree.gd" id="2"] diff --git a/addons/licenses/internal/toolbar.gd b/addons/licenses/internal/toolbar.gd index 955e023..b1c803e 100644 --- a/addons/licenses/internal/toolbar.gd +++ b/addons/licenses/internal/toolbar.gd @@ -17,9 +17,9 @@ var _add_plugin_menu: PopupMenu var _add_engine_menu: PopupMenu func _ready() -> void: - self._menu_button.icon = self.get_theme_icon("GuiTabMenuHl", "EditorIcons") + self._menu_button.icon = self.get_theme_icon(&"GuiTabMenuHl", &"EditorIcons") self._menu_button.pressed.connect(self._on_menu_pressed) - self._add_button.icon = self.get_theme_icon("Add", "EditorIcons") + self._add_button.icon = self.get_theme_icon(&"Add", &"EditorIcons") self._add_button.pressed.connect(self._on_add_pressed) self._menu = PopupMenu.new() @@ -29,11 +29,11 @@ func _ready() -> void: self._add_menu = PopupMenu.new() self._add_menu.add_item("New Component", 0) - self._add_menu.set_item_icon(0, get_theme_icon("New", "EditorIcons")) + self._add_menu.set_item_icon(0, get_theme_icon(&"New", &"EditorIcons")) self._add_menu.add_submenu_item("Generate from Plugin", "menu_plugin", 1) - self._add_menu.set_item_icon(1, get_theme_icon("EditorPlugin", "EditorIcons")) + self._add_menu.set_item_icon(1, get_theme_icon(&"EditorPlugin", &"EditorIcons")) self._add_menu.add_submenu_item("Generate from Engine", "menu_engine", 2) - self._add_menu.set_item_icon(2, get_theme_icon("Godot", "EditorIcons")) + self._add_menu.set_item_icon(2, get_theme_icon(&"Godot", &"EditorIcons")) self._add_menu.id_pressed.connect(self._on_add_id_pressed) self.add_child(self._add_menu) diff --git a/addons/licenses/licenses.gd b/addons/licenses/licenses.gd index fe7d9c5..7adc19e 100644 --- a/addons/licenses/licenses.gd +++ b/addons/licenses/licenses.gd @@ -1,5 +1,5 @@ extends RefCounted -# DO NOT USE THE CLASS NAME, it will be removed later +# TODO: DO NOT USE THE CLASS NAME, it will be removed later class_name __LicenseManager const Component := preload("component.gd") @@ -9,7 +9,7 @@ const DATA_FILE: String = "plugins/licenses/data_file" static func compare_components_ascending(lhs: Component, rhs: Component) -> bool: var lhs_cat_lower: String = lhs.category.to_lower() var rhs_cat_lower: String = rhs.category.to_lower() - return lhs_cat_lower < rhs_cat_lower or (lhs_cat_lower == rhs_cat_lower and lhs.name.to_lower() < rhs.name.to_lower()) + return lhs_cat_lower < rhs_cat_lower || (lhs_cat_lower == rhs_cat_lower && lhs.name.to_lower() < rhs.name.to_lower()) static func get_engine_component(name: String) -> Component: var license_keys: Array = Engine.get_license_info().keys() diff --git a/addons/licenses/plugin.cfg b/addons/licenses/plugin.cfg index 45996e3..79c7859 100644 --- a/addons/licenses/plugin.cfg +++ b/addons/licenses/plugin.cfg @@ -3,7 +3,7 @@ name="License Manager" description="Manage license and copyright for third party graphics, software or libraries." author="Iceflower S" -version="1.7.5" +version="1.7.6" script="plugin.gd" license="MIT" repository="https://github.com/kenyoni-software/godot-addons" diff --git a/addons/licenses/plugin.gd b/addons/licenses/plugin.gd index 02c8fe4..b6b28d2 100644 --- a/addons/licenses/plugin.gd +++ b/addons/licenses/plugin.gd @@ -5,8 +5,8 @@ const LicensesDialogScene: PackedScene = preload("internal/licenses_dialog.tscn" const Licenses := preload("licenses.gd") const ExportPlugin := preload("export_plugin.gd") -var export_plugin: ExportPlugin -var licenses_dialog: Window +var _export_plugin: ExportPlugin +var _licenses_dialog: Window func _get_plugin_name() -> String: return "Licenses" @@ -14,23 +14,23 @@ func _get_plugin_name() -> String: func _enter_tree() -> void: set_project_setting(Licenses.DATA_FILE, "res://licenses.json", TYPE_STRING, PROPERTY_HINT_FILE) - self.export_plugin = ExportPlugin.new() - self.add_export_plugin(self.export_plugin) + self._export_plugin = ExportPlugin.new() + self.add_export_plugin(self._export_plugin) - self.licenses_dialog = LicensesDialogScene.instantiate() - EditorInterface.get_base_control().add_child(self.licenses_dialog) + self._licenses_dialog = LicensesDialogScene.instantiate() + EditorInterface.get_base_control().add_child(self._licenses_dialog) self.add_tool_menu_item(self._get_plugin_name() + "...", self._show_popup) func _exit_tree() -> void: self.remove_tool_menu_item(self._get_plugin_name() + "...") - self.licenses_dialog.queue_free() - self.remove_export_plugin(self.export_plugin) + self._licenses_dialog.queue_free() + self.remove_export_plugin(self._export_plugin) func _show_popup() -> void: - if licenses_dialog.visible: - self.licenses_dialog.grab_focus() + if _licenses_dialog.visible: + self._licenses_dialog.grab_focus() else: - self.licenses_dialog.popup_centered_ratio(0.4) + self._licenses_dialog.popup_centered_ratio(0.4) static func set_project_setting(key: String, initial_value, type: int, type_hint: int) -> void: if not ProjectSettings.has_setting(key): diff --git a/addons/qr_code/plugin.cfg b/addons/qr_code/plugin.cfg index eb5274b..417c40a 100644 --- a/addons/qr_code/plugin.cfg +++ b/addons/qr_code/plugin.cfg @@ -3,7 +3,7 @@ name="QR Code" description="QR Code generator." author="Iceflower S" -version="1.1.0" +version="1.1.1" script="plugin.gd" license="MIT" repository="https://github.com/kenyoni-software/godot-addons" diff --git a/addons/qr_code/qr_code.gd b/addons/qr_code/qr_code.gd index ab2e739..cbb951c 100644 --- a/addons/qr_code/qr_code.gd +++ b/addons/qr_code/qr_code.gd @@ -1279,7 +1279,7 @@ static func _calc_mask_rating(data: PackedByteArray, module_count: int) -> int: for x: int in range(module_count - 1): for y: int in range(module_count - 1): var val: int = data[x + y * module_count] + data[x + 1 + y * module_count] + data[x + (y + 1) * module_count] + data[x + 1 + (y + 1) * module_count] - if val == 0 or val == 4: + if val == 0 || val == 4: rating += 3 # condition 3 diff --git a/addons/texture_button_colored/plugin.cfg b/addons/texture_button_colored/plugin.cfg index edb2023..612d74d 100644 --- a/addons/texture_button_colored/plugin.cfg +++ b/addons/texture_button_colored/plugin.cfg @@ -3,7 +3,7 @@ name="TextureButtonColored" description="TextureButton with color theme." author="Iceflower S" -version="1.3.0" +version="1.3.1" script="plugin.gd" license="MIT" repository="https://github.com/kenyoni-software/godot-addons" diff --git a/addons/texture_button_colored/texture_button_colored.gd b/addons/texture_button_colored/texture_button_colored.gd index 066e940..e9e6847 100644 --- a/addons/texture_button_colored/texture_button_colored.gd +++ b/addons/texture_button_colored/texture_button_colored.gd @@ -25,7 +25,7 @@ var _theme_overrides = CustomThemeOverrides.new([ func get_theme_coloring(name: StringName, theme_type: StringName = "") -> Color: if self.has_theme_color(name, theme_type): return super.get_theme_color(name, theme_type) - return super.get_theme_color(name, "TextureButtonColored") + return super.get_theme_color(name, &"TextureButtonColored") func _get_property_list() -> Array[Dictionary]: return self._theme_overrides.theme_property_list(self) @@ -51,14 +51,14 @@ func _draw() -> void: func _update_layout() -> void: var draw_mode: int = self.get_draw_mode() if draw_mode == DRAW_HOVER_PRESSED || (self._is_hovered && self.button_pressed): - self.self_modulate = self.get_theme_coloring("icon_hover_pressed_color") + self.self_modulate = self.get_theme_coloring(&"icon_hover_pressed_color") elif draw_mode == DRAW_NORMAL: - self.self_modulate = self.get_theme_coloring("icon_normal_color") + self.self_modulate = self.get_theme_coloring(&"icon_normal_color") elif draw_mode == DRAW_PRESSED: - self.self_modulate = self.get_theme_coloring("icon_pressed_color") + self.self_modulate = self.get_theme_coloring(&"icon_pressed_color") elif draw_mode == DRAW_HOVER: - self.self_modulate = self.get_theme_coloring("icon_hover_color") + self.self_modulate = self.get_theme_coloring(&"icon_hover_color") elif draw_mode == DRAW_DISABLED: - self.self_modulate = self.get_theme_coloring("icon_disabled_color") + self.self_modulate = self.get_theme_coloring(&"icon_disabled_color") elif self.has_focus(): - self.self_modulate = self.get_theme_coloring("icon_hover_pressed_color") + self.self_modulate = self.get_theme_coloring(&"icon_hover_pressed_color") diff --git a/doc/icon_explorer.png b/doc/icon_explorer.png new file mode 100644 index 0000000..7c2ec5b Binary files /dev/null and b/doc/icon_explorer.png differ diff --git a/examples/custom_theme_overrides/main.gd b/examples/custom_theme_overrides/main.gd index 3505f72..05e2541 100644 --- a/examples/custom_theme_overrides/main.gd +++ b/examples/custom_theme_overrides/main.gd @@ -43,9 +43,9 @@ func _notification(what: int) -> void: self._update_layout() func _update_layout() -> void: - for child in self.get_children(): + for child: Node in self.get_children(): if child is Label: - if self.has_theme_color_override("my_font_color"): - child.add_theme_color_override("font_color", self.get_theme_color("my_font_color")) + if self.has_theme_color_override(&"my_font_color"): + child.add_theme_color_override(&"font_color", self.get_theme_color(&"my_font_color")) else: - child.remove_theme_color_override("font_color") + child.remove_theme_color_override(&"font_color") diff --git a/examples/glogging/main.gd b/examples/glogging/main.gd index d48b8f8..5d1315c 100644 --- a/examples/glogging/main.gd +++ b/examples/glogging/main.gd @@ -11,7 +11,7 @@ func _ready() -> void: GLogging.info("ready and initialize GUI") self._logger.append(GLogging.root_logger.create_child("network")) self._logger.append(GLogging.root_logger.create_child("gui", GLogging.LEVEL_WARNING)) - for logger in self._logger: + for logger: GLogging.Logger in self._logger: self._logger_options.add_item(logger.name) self._log_level_options.select(GLogging.root_logger.log_level() / 10) GLogging.info("initialized logger %s %s", ["root", "and other"]) diff --git a/examples/licenses/license_container.gd b/examples/licenses/license_container.gd index f905f1a..341b34a 100644 --- a/examples/licenses/license_container.gd +++ b/examples/licenses/license_container.gd @@ -18,7 +18,7 @@ func set_component(component: Component) -> void: self._web.text = component.web self._license.text = "" self._license_text.text = "" - for idx in range(len(component.licenses)): + for idx: int in range(len(component.licenses)): var license: Component.License = component.licenses[idx] if idx > 0: self._license.text = self._license.text + " & " diff --git a/project.godot b/project.godot index 0c56908..a2300df 100644 --- a/project.godot +++ b/project.godot @@ -29,7 +29,7 @@ gdscript/warnings/unsafe_call_argument=1 [editor_plugins] -enabled=PackedStringArray("res://addons/aspect_ratio_resize_container/plugin.cfg", "res://addons/custom_theme_overrides/plugin.cfg", "res://addons/git_sha_project_setting/plugin.cfg", "res://addons/glogging/plugin.cfg", "res://addons/hide_private_properties/plugin.cfg", "res://addons/icons_patcher/plugin.cfg", "res://addons/licenses/plugin.cfg", "res://addons/qr_code/plugin.cfg", "res://addons/texture_button_colored/plugin.cfg") +enabled=PackedStringArray("res://addons/aspect_ratio_resize_container/plugin.cfg", "res://addons/custom_theme_overrides/plugin.cfg", "res://addons/explore-editor-theme/plugin.cfg", "res://addons/git_sha_project_setting/plugin.cfg", "res://addons/glogging/plugin.cfg", "res://addons/hide_private_properties/plugin.cfg", "res://addons/icons_patcher/plugin.cfg", "res://addons/licenses/plugin.cfg", "res://addons/qr_code/plugin.cfg", "res://addons/texture_button_colored/plugin.cfg") [plugins] diff --git a/publisher/addon.go b/publisher/addon.go index 364228a..31d9aab 100644 --- a/publisher/addon.go +++ b/publisher/addon.go @@ -75,9 +75,12 @@ func (addon *addon) Zip(outputFile string) error { return err } exampleDir := filepath.Join(addon.ProjectPath(), "examples", addon.Id()) - err = zipDir(zw, exampleDir, filepath.Join("examples", addon.Id())) - if err != nil { - return err + // zip example directory only if it exists + if _, err := os.Stat(exampleDir); err == nil { + err = zipDir(zw, exampleDir, filepath.Join("examples", addon.Id())) + if err != nil { + return err + } } err = zipFile(zw, filepath.Join(addon.ProjectPath(), "LICENSE.md"), filepath.Join("addons", addon.Id(), "LICENSE.md")) diff --git a/publisher/utils.go b/publisher/utils.go index 1db78e0..1f62d96 100644 --- a/publisher/utils.go +++ b/publisher/utils.go @@ -10,6 +10,9 @@ import ( func zipDir(zw *zip.Writer, src string, dest string) error { return filepath.WalkDir(src, func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } rel, _ := filepath.Rel(src, path) destPath := filepath.Join(dest, rel) if d.IsDir() {