aboutsummaryrefslogtreecommitdiff
path: root/game/addons/zylann.hterrain/tools/detail_editor/detail_editor.gd
diff options
context:
space:
mode:
Diffstat (limited to 'game/addons/zylann.hterrain/tools/detail_editor/detail_editor.gd')
-rw-r--r--game/addons/zylann.hterrain/tools/detail_editor/detail_editor.gd200
1 files changed, 200 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)
+
+
+