first commit

This commit is contained in:
Ugric
2026-03-02 02:17:04 +00:00
commit 5d56860f3a
813 changed files with 41799 additions and 0 deletions

View File

@@ -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)

View File

@@ -0,0 +1 @@
uid://cvy70epqlf1d8

View File

@@ -0,0 +1,48 @@
[gd_scene load_steps=2 format=3 uid="uid://do3c3jse5p7hx"]
[ext_resource type="Script" uid="uid://cvy70epqlf1d8" 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"]