@tool extends Panel signal done @onready var n_name_filter := %NameFilter @onready var n_base_asset_names := %BaseAssetNames @onready var n_assets_container := %AssetsContainer var _last_pressed_icon : ControllerIcons_Icon var _last_pressed_timestamp : int var color_text_enabled : Color var color_text_disabled : Color class ControllerIcons_Icon: static var group := ButtonGroup.new() func _init(category: String, path: String): self.category = category self.filtered = true self.path = path.get_slice("/", 1) button = Button.new() button.custom_minimum_size = Vector2(100, 100) button.clip_text = true button.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS button.icon_alignment = HORIZONTAL_ALIGNMENT_CENTER button.vertical_icon_alignment = VERTICAL_ALIGNMENT_TOP button.expand_icon = true button.toggle_mode = true button.button_group = group button.text = self.path var icon = ControllerIconTexture.new() icon.path = path button.icon = icon var button : Button var category : String var path : String var selected: bool: set(_selected): selected = _selected _query_visibility() var filtered: bool: set(_filtered): filtered = _filtered _query_visibility() func _query_visibility(): if is_instance_valid(button): button.visible = selected and filtered var button_nodes := {} var asset_names_root : TreeItem func populate(editor_interface: EditorInterface) -> void: ## Using clear() triggers a signal and uses freed nodes. ## Setting the text directly does not. n_name_filter.text = "" n_base_asset_names.clear() button_nodes.clear() for child in n_assets_container.get_children(): n_assets_container.remove_child(child) child.queue_free() # UPGRADE: In Godot 4.2, there's no need to have an instance to # EditorInterface, since it's now a static call: # var editor_control := EditorInterface.get_base_control() var editor_control := editor_interface.get_base_control() color_text_enabled = editor_control.get_theme_color("font_color", "Editor") color_text_disabled = editor_control.get_theme_color("disabled_font_color", "Editor") n_name_filter.right_icon = editor_control.get_theme_icon("Search", "EditorIcons") asset_names_root = n_base_asset_names.create_item() var base_paths := [ ControllerIcons._settings.custom_asset_dir, "res://addons/controller_icons/assets" ] # UPGRADE: In Godot 4.2, for-loop variables can be # statically typed: # for base_path:String in base_paths: for base_path in base_paths: if base_path.is_empty() or not base_path.begins_with("res://"): continue # Files first handle_files("", base_path) # Directories next for dir in DirAccess.get_directories_at(base_path): handle_files(dir, base_path.path_join(dir)) var child : TreeItem = asset_names_root.get_next_in_tree() if child: child.select(0) func handle_files(category: String, base_path: String): for file in DirAccess.get_files_at(base_path): if file.get_extension() == ControllerIcons._base_extension: create_icon(category, base_path.path_join(file)) func create_icon(category: String, path: String): var map_category := "" if category.is_empty() else category if not button_nodes.has(map_category): button_nodes[map_category] = {} var item : TreeItem = n_base_asset_names.create_item(asset_names_root) item.set_text(0, map_category) var filename := path.get_file() if button_nodes[map_category].has(filename): return var icon_path = ("" if category.is_empty() else category + "/") + path.get_file().get_basename() var icon := ControllerIcons_Icon.new(map_category, icon_path) button_nodes[map_category][filename] = icon n_assets_container.add_child(icon.button) icon.button.pressed.connect(func(): if _last_pressed_icon == icon: if Time.get_ticks_msec() < _last_pressed_timestamp: done.emit() else: _last_pressed_timestamp = Time.get_ticks_msec() + 1000 else: _last_pressed_icon = icon _last_pressed_timestamp = Time.get_ticks_msec() + 1000 ) func get_icon_path() -> String: var button := ControllerIcons_Icon.group.get_pressed_button() if button: return button.icon.path return "" func grab_focus() -> void: n_name_filter.grab_focus() func _on_base_asset_names_item_selected(): var selected : TreeItem = n_base_asset_names.get_selected() if not selected: return var category := selected.get_text(0) if not button_nodes.has(category): return # UPGRADE: In Godot 4.2, for-loop variables can be # statically typed: # for key:String in button_nodes.keys(): # for icon:ControllerIcon_Icon in button_nodes[key].values(): for key in button_nodes.keys(): for icon in button_nodes[key].values(): icon.selected = key == category func _on_name_filter_text_changed(new_text:String): var any_visible := {} var asset_name := asset_names_root.get_next_in_tree() while asset_name: any_visible[asset_name.get_text(0)] = false asset_name = asset_name.get_next_in_tree() var selected_category : TreeItem = n_base_asset_names.get_selected() # UPGRADE: In Godot 4.2, for-loop variables can be # statically typed: # for key:String in button_nodes.keys(): # for icon:Icon in button_nodes[key].values(): for key in button_nodes.keys(): for icon in button_nodes[key].values(): var filtered : bool = true if new_text.is_empty() else icon.path.findn(new_text) != -1 icon.filtered = filtered any_visible[key] = any_visible[key] or filtered asset_name = asset_names_root.get_next_in_tree() while asset_name: var category := asset_name.get_text(0) if any_visible.has(category): var selectable : bool = any_visible[category] asset_name.set_selectable(0, selectable) if not selectable: asset_name.deselect(0) asset_name.set_custom_color(0, color_text_enabled if selectable else color_text_disabled) asset_name = asset_name.get_next_in_tree()