diff options
Diffstat (limited to 'game/addons/zylann.hterrain/tools/detail_editor')
-rw-r--r-- | game/addons/zylann.hterrain/tools/detail_editor/detail_editor.gd | 200 | ||||
-rw-r--r-- | game/addons/zylann.hterrain/tools/detail_editor/detail_editor.tscn | 48 |
2 files changed, 248 insertions, 0 deletions
diff --git a/game/addons/zylann.hterrain/tools/detail_editor/detail_editor.gd b/game/addons/zylann.hterrain/tools/detail_editor/detail_editor.gd new file mode 100644 index 0000000..cb3288e --- /dev/null +++ b/game/addons/zylann.hterrain/tools/detail_editor/detail_editor.gd @@ -0,0 +1,200 @@ +@tool +extends Control + +const HTerrain = preload("../../hterrain.gd") +const HTerrainData = preload("../../hterrain_data.gd") +const HTerrainDetailLayer = preload("../../hterrain_detail_layer.gd") +const HT_ImageFileCache = preload("../../util/image_file_cache.gd") +const HT_EditorUtil = preload("../util/editor_util.gd") +const HT_Logger = preload("../../util/logger.gd") + +# TODO Can't preload because it causes the plugin to fail loading if assets aren't imported +const PLACEHOLDER_ICON_TEXTURE = "res://addons/zylann.hterrain/tools/icons/icon_grass.svg" +const DETAIL_LAYER_ICON_TEXTURE = \ + "res://addons/zylann.hterrain/tools/icons/icon_detail_layer_node.svg" + +signal detail_selected(index) +# Emitted when the tool added or removed a detail map +signal detail_list_changed + +@onready var _item_list : ItemList = $ItemList +@onready var _confirmation_dialog : ConfirmationDialog = $ConfirmationDialog + +var _terrain : HTerrain = null +var _dialog_target := -1 +var _undo_redo_manager : EditorUndoRedoManager +var _image_cache : HT_ImageFileCache +var _logger = HT_Logger.get_for(self) + + +func set_terrain(terrain): + if _terrain == terrain: + return + _terrain = terrain + _update_list() + + +func set_undo_redo(ur: EditorUndoRedoManager): + assert(ur != null) + _undo_redo_manager = ur + + +func set_image_cache(image_cache: HT_ImageFileCache): + _image_cache = image_cache + + +func set_layer_index(i: int): + _item_list.select(i, true) + + +func _update_list(): + _item_list.clear() + + if _terrain == null: + return + + var layer_nodes = _terrain.get_detail_layers() + var layer_nodes_by_index := {} + for layer in layer_nodes: + if not layer_nodes_by_index.has(layer.layer_index): + layer_nodes_by_index[layer.layer_index] = [] + layer_nodes_by_index[layer.layer_index].append(layer.name) + + var data = _terrain.get_data() + if data != null: + # Display layers from what terrain data actually contains, + # because layer nodes are just what makes them rendered and aren't much restricted. + var layer_count = data.get_map_count(HTerrainData.CHANNEL_DETAIL) + var placeholder_icon = HT_EditorUtil.load_texture(PLACEHOLDER_ICON_TEXTURE, _logger) + + for i in layer_count: + # TODO Show a preview icon + _item_list.add_item(str("Map ", i), placeholder_icon) + + if layer_nodes_by_index.has(i): + # TODO How to keep names updated with node names? + var names := ", ".join(PackedStringArray(layer_nodes_by_index[i])) + if len(names) == 1: + _item_list.set_item_tooltip(i, "Used by " + names) + else: + _item_list.set_item_tooltip(i, "Used by " + names) + # Remove custom color + # TODO Use fg version when available in Godot 3.1, I want to only highlight text + _item_list.set_item_custom_bg_color(i, Color(0, 0, 0, 0)) + else: + # TODO Use fg version when available in Godot 3.1, I want to only highlight text + _item_list.set_item_custom_bg_color(i, Color(1.0, 0.2, 0.2, 0.3)) + _item_list.set_item_tooltip(i, "This map isn't used by any layer. " \ + + "Add a HTerrainDetailLayer node as child of the terrain.") + + +func _on_Add_pressed(): + _add_layer() + + +func _on_Remove_pressed(): + var selected = _item_list.get_selected_items() + if len(selected) == 0: + return + _dialog_target = _item_list.get_selected_items()[0] + _confirmation_dialog.title = "Removing detail map {0}".format([_dialog_target]) + _confirmation_dialog.popup_centered() + + +func _on_ConfirmationDialog_confirmed(): + _remove_layer(_dialog_target) + + +func _add_layer(): + assert(_terrain != null) + assert(_terrain.get_data() != null) + assert(_undo_redo_manager != null) + var terrain_data : HTerrainData = _terrain.get_data() + + # First, create node and map image + var node := HTerrainDetailLayer.new() + # TODO Workarounds for https://github.com/godotengine/godot/issues/21410 + var detail_layer_icon := HT_EditorUtil.load_texture(DETAIL_LAYER_ICON_TEXTURE, _logger) + node.set_meta("_editor_icon", detail_layer_icon) + node.name = "HTerrainDetailLayer" + var map_index := terrain_data._edit_add_map(HTerrainData.CHANNEL_DETAIL) + var map_image := terrain_data.get_image(HTerrainData.CHANNEL_DETAIL) + var map_image_cache_id := _image_cache.save_image(map_image) + node.layer_index = map_index + + var undo_redo := _undo_redo_manager.get_history_undo_redo( + _undo_redo_manager.get_object_history_id(_terrain)) + + # Then, create an action + undo_redo.create_action("Add Detail Layer {0}".format([map_index])) + + undo_redo.add_do_method(terrain_data._edit_insert_map_from_image_cache.bind( + HTerrainData.CHANNEL_DETAIL, map_index, _image_cache, map_image_cache_id)) + undo_redo.add_do_method(_terrain.add_child.bind(node)) + undo_redo.add_do_property(node, "owner", get_tree().edited_scene_root) + undo_redo.add_do_method(self._update_list) + undo_redo.add_do_reference(node) + + undo_redo.add_undo_method(_terrain.remove_child.bind(node)) + undo_redo.add_undo_method( + terrain_data._edit_remove_map.bind(HTerrainData.CHANNEL_DETAIL, map_index)) + undo_redo.add_undo_method(self._update_list) + + # Yet another instance of this hack, to prevent UndoRedo from running some of the functions, + # which we had to run already + terrain_data._edit_set_disable_apply_undo(true) + undo_redo.commit_action() + terrain_data._edit_set_disable_apply_undo(false) + + #_update_list() + detail_list_changed.emit() + + var index := node.layer_index + _item_list.select(index) + # select() doesn't trigger the signal + detail_selected.emit(index) + + +func _remove_layer(map_index: int): + var terrain_data : HTerrainData = _terrain.get_data() + + # First, cache image data + var image := terrain_data.get_image(HTerrainData.CHANNEL_DETAIL, map_index) + var image_id := _image_cache.save_image(image) + var nodes = _terrain.get_detail_layers() + var using_nodes := [] + # Nodes using this map will be removed from the tree + for node in nodes: + if node.layer_index == map_index: + using_nodes.append(node) + + var undo_redo := _undo_redo_manager.get_history_undo_redo( + _undo_redo_manager.get_object_history_id(_terrain)) + + undo_redo.create_action("Remove Detail Layer {0}".format([map_index])) + + undo_redo.add_do_method( + terrain_data._edit_remove_map.bind(HTerrainData.CHANNEL_DETAIL, map_index)) + for node in using_nodes: + undo_redo.add_do_method(_terrain.remove_child.bind(node)) + undo_redo.add_do_method(self._update_list) + + undo_redo.add_undo_method(terrain_data._edit_insert_map_from_image_cache.bind( + HTerrainData.CHANNEL_DETAIL, map_index, _image_cache, image_id)) + for node in using_nodes: + undo_redo.add_undo_method(_terrain.add_child.bind(node)) + undo_redo.add_undo_property(node, "owner", get_tree().edited_scene_root) + undo_redo.add_undo_reference(node) + undo_redo.add_undo_method(self._update_list) + + undo_redo.commit_action() + + #_update_list() + detail_list_changed.emit() + + +func _on_ItemList_item_selected(index): + detail_selected.emit(index) + + + diff --git a/game/addons/zylann.hterrain/tools/detail_editor/detail_editor.tscn b/game/addons/zylann.hterrain/tools/detail_editor/detail_editor.tscn new file mode 100644 index 0000000..cc5b995 --- /dev/null +++ b/game/addons/zylann.hterrain/tools/detail_editor/detail_editor.tscn @@ -0,0 +1,48 @@ +[gd_scene load_steps=2 format=3 uid="uid://do3c3jse5p7hx"] + +[ext_resource type="Script" path="res://addons/zylann.hterrain/tools/detail_editor/detail_editor.gd" id="1"] + +[node name="DetailEditor" type="Control"] +custom_minimum_size = Vector2(200, 0) +layout_mode = 3 +anchors_preset = 0 +offset_right = 189.0 +offset_bottom = 109.0 +script = ExtResource("1") + +[node name="ItemList" type="ItemList" parent="."] +layout_mode = 0 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_bottom = -26.0 +max_columns = 0 +same_column_width = true +icon_mode = 0 +fixed_icon_size = Vector2i(32, 32) + +[node name="HBoxContainer" type="HBoxContainer" parent="."] +layout_mode = 0 +anchor_top = 1.0 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_top = -24.0 + +[node name="Add" type="Button" parent="HBoxContainer"] +layout_mode = 2 +text = "Add" + +[node name="Remove" type="Button" parent="HBoxContainer"] +layout_mode = 2 +text = "Remove" + +[node name="Label" type="Label" parent="HBoxContainer"] +layout_mode = 2 +text = "Details" + +[node name="ConfirmationDialog" type="ConfirmationDialog" parent="."] +dialog_text = "Are you sure you want to remove this detail map?" + +[connection signal="item_selected" from="ItemList" to="." method="_on_ItemList_item_selected"] +[connection signal="pressed" from="HBoxContainer/Add" to="." method="_on_Add_pressed"] +[connection signal="pressed" from="HBoxContainer/Remove" to="." method="_on_Remove_pressed"] +[connection signal="confirmed" from="ConfirmationDialog" to="." method="_on_ConfirmationDialog_confirmed"] |