ile-de-test/addons/zylann.hterrain/tools/texture_editor/set_editor/texture_set_editor.gd

530 lines
18 KiB
GDScript3
Raw Normal View History

2023-10-05 18:02:23 +00:00
@tool
extends AcceptDialog
const HTerrainTextureSet = preload("../../../hterrain_texture_set.gd")
const HT_EditorUtil = preload("../../util/editor_util.gd")
const HT_Util = preload("../../../util/util.gd")
const HT_Logger = preload("../../../util/logger.gd")
const HT_ColorShader = preload("../display_color.gdshader")
const HT_ColorSliceShader = preload("../display_color_slice.gdshader")
const HT_AlphaShader = preload("../display_alpha.gdshader")
const HT_AlphaSliceShader = preload("../display_alpha_slice.gdshader")
# TODO Can't preload because it causes the plugin to fail loading if assets aren't imported
#const HT_EmptyTexture = preload("../../icons/empty.png")
const EMPTY_TEXTURE_PATH = "res://addons/zylann.hterrain/tools/icons/empty.png"
signal import_selected
@onready var _slots_list : ItemList = $VB/HS/VB/SlotsList
@onready var _albedo_preview : TextureRect = $VB/HS/VB2/GC/AlbedoPreview
@onready var _bump_preview : TextureRect = $VB/HS/VB2/GC/BumpPreview
@onready var _normal_preview : TextureRect = $VB/HS/VB2/GC/NormalPreview
@onready var _roughness_preview : TextureRect = $VB/HS/VB2/GC/RoughnessPreview
@onready var _load_albedo_button : Button = $VB/HS/VB2/GC/LoadAlbedo
@onready var _load_normal_button : Button = $VB/HS/VB2/GC/LoadNormal
@onready var _clear_albedo_button : Button = $VB/HS/VB2/GC/ClearAlbedo
@onready var _clear_normal_button : Button = $VB/HS/VB2/GC/ClearNormal
@onready var _mode_selector : OptionButton = $VB/HS/VB2/GC2/ModeSelector
@onready var _add_slot_button : Button = $VB/HS/VB/HB/AddSlot
@onready var _remove_slot_button : Button = $VB/HS/VB/HB/RemoveSlot
var _texture_set : HTerrainTextureSet
var _undo_redo_manager : EditorUndoRedoManager
var _mode_confirmation_dialog : ConfirmationDialog
var _delete_slot_confirmation_dialog : ConfirmationDialog
var _load_texture_dialog : ConfirmationDialog
var _load_texture_array_dialog : ConfirmationDialog
var _load_texture_type := -1
var _logger = HT_Logger.get_for(self)
func _init():
get_ok_button().hide()
func _ready():
if HT_Util.is_in_edited_scene(self):
return
for id in HTerrainTextureSet.MODE_COUNT:
var mode_name = HTerrainTextureSet.get_import_mode_name(id)
_mode_selector.add_item(mode_name, id)
func setup_dialogs(parent: Node):
var d = HT_EditorUtil.create_open_texture_dialog()
d.file_selected.connect(_on_LoadTextureDialog_file_selected)
_load_texture_dialog = d
add_child(d)
d = HT_EditorUtil.create_open_texture_array_dialog()
d.file_selected.connect(_on_LoadTextureArrayDialog_file_selected)
_load_texture_array_dialog = d
add_child(d)
d = ConfirmationDialog.new()
d.confirmed.connect(_on_ModeConfirmationDialog_confirmed)
# This is ridiculous.
# See https://github.com/godotengine/godot/issues/17460
# d.connect("modal_closed", self, "_on_ModeConfirmationDialog_cancelled")
# d.get_close_button().connect("pressed", self, "_on_ModeConfirmationDialog_cancelled")
# d.get_cancel().connect("pressed", self, "_on_ModeConfirmationDialog_cancelled")
_mode_confirmation_dialog = d
add_child(d)
func _notification(what: int):
if HT_Util.is_in_edited_scene(self):
return
if what == NOTIFICATION_EXIT_TREE:
# Have to check for null in all of them,
# because otherwise it breaks in the scene editor...
if _load_texture_dialog != null:
_load_texture_dialog.queue_free()
if _load_texture_array_dialog != null:
_load_texture_array_dialog.queue_free()
if what == NOTIFICATION_VISIBILITY_CHANGED:
if not visible:
# Cleanup referenced resources
set_texture_set(null)
func set_undo_redo(ur: EditorUndoRedoManager):
_undo_redo_manager = ur
func set_texture_set(texture_set: HTerrainTextureSet):
if _texture_set == texture_set:
return
if _texture_set != null:
_texture_set.changed.disconnect(_on_texture_set_changed)
_texture_set = texture_set
if _texture_set != null:
_texture_set.changed.connect(_on_texture_set_changed)
_update_ui_from_data()
func _on_texture_set_changed():
_update_ui_from_data()
func _update_ui_from_data():
var prev_selected_items = _slots_list.get_selected_items()
_slots_list.clear()
var slots_count := _texture_set.get_slots_count()
for slot_index in slots_count:
_slots_list.add_item("Texture {0}".format([slot_index]))
_set_selected_id(_mode_selector, _texture_set.get_mode())
if _slots_list.get_item_count() > 0:
if len(prev_selected_items) > 0:
var i : int = prev_selected_items[0]
if i >= _slots_list.get_item_count():
i = _slots_list.get_item_count() - 1
_select_slot(i)
else:
_select_slot(0)
else:
_clear_previews()
var max_slots := HTerrainTextureSet.get_max_slots_for_mode(_texture_set.get_mode())
_add_slot_button.disabled = slots_count >= max_slots
_remove_slot_button.disabled = slots_count == 0
var buttons := [
_load_albedo_button,
_load_normal_button,
_clear_albedo_button,
_clear_normal_button
]
if _texture_set.get_mode() == HTerrainTextureSet.MODE_TEXTURES:
_add_slot_button.visible = true
_remove_slot_button.visible = true
_load_albedo_button.text = "Load..."
_load_normal_button.text = "Load..."
for b in buttons:
b.disabled = (slots_count == 0)
else:
_add_slot_button.visible = false
_remove_slot_button.visible = false
_load_albedo_button.text = "Load Array..."
_load_normal_button.text = "Load Array..."
for b in buttons:
b.disabled = false
static func _set_selected_id(ob: OptionButton, id: int):
for i in ob.get_item_count():
if ob.get_item_id(i) == id:
ob.selected = i
break
func select_slot(slot_index: int):
var count = _texture_set.get_slots_count()
if count > 0:
if slot_index >= count:
slot_index = count - 1
_select_slot(slot_index)
func _clear_previews():
var empty_texture : Texture2D = load(EMPTY_TEXTURE_PATH)
if empty_texture == null:
_logger.error(str("Failed to load empty texture ", EMPTY_TEXTURE_PATH))
_albedo_preview.texture = empty_texture
_bump_preview.texture = empty_texture
_normal_preview.texture = empty_texture
_roughness_preview.texture = empty_texture
_albedo_preview.tooltip_text = _get_resource_path_or_empty(null)
_bump_preview.tooltip_text = _get_resource_path_or_empty(null)
_normal_preview.tooltip_text = _get_resource_path_or_empty(null)
_roughness_preview.tooltip_text = _get_resource_path_or_empty(null)
func _select_slot(slot_index: int):
assert(slot_index >= 0)
assert(slot_index < _texture_set.get_slots_count())
var empty_texture : Texture2D = load(EMPTY_TEXTURE_PATH)
if empty_texture == null:
_logger.error(str("Failed to load empty texture ", EMPTY_TEXTURE_PATH))
if _texture_set.get_mode() == HTerrainTextureSet.MODE_TEXTURES:
var albedo_tex := \
_texture_set.get_texture(slot_index, HTerrainTextureSet.TYPE_ALBEDO_BUMP)
var normal_tex := \
_texture_set.get_texture(slot_index, HTerrainTextureSet.TYPE_NORMAL_ROUGHNESS)
_albedo_preview.texture = albedo_tex if albedo_tex != null else empty_texture
_bump_preview.texture = albedo_tex if albedo_tex != null else empty_texture
_normal_preview.texture = normal_tex if normal_tex != null else empty_texture
_roughness_preview.texture = normal_tex if normal_tex != null else empty_texture
_albedo_preview.tooltip_text = _get_resource_path_or_empty(albedo_tex)
_bump_preview.tooltip_text = _get_resource_path_or_empty(albedo_tex)
_normal_preview.tooltip_text = _get_resource_path_or_empty(normal_tex)
_roughness_preview.tooltip_text = _get_resource_path_or_empty(normal_tex)
_albedo_preview.material.shader = HT_ColorShader
_bump_preview.material.shader = HT_AlphaShader
_normal_preview.material.shader = HT_ColorShader
_roughness_preview.material.shader = HT_AlphaShader
_albedo_preview.material.set_shader_parameter("u_texture_array", null)
_bump_preview.material.set_shader_parameter("u_texture_array", null)
_normal_preview.material.set_shader_parameter("u_texture_array", null)
_roughness_preview.material.set_shader_parameter("u_texture_array", null)
else:
var albedo_tex := _texture_set.get_texture_array(HTerrainTextureSet.TYPE_ALBEDO_BUMP)
var normal_tex := _texture_set.get_texture_array(HTerrainTextureSet.TYPE_NORMAL_ROUGHNESS)
_albedo_preview.texture = empty_texture
_bump_preview.texture = empty_texture
_normal_preview.texture = empty_texture
_roughness_preview.texture = empty_texture
_albedo_preview.tooltip_text = _get_resource_path_or_empty(albedo_tex)
_bump_preview.tooltip_text = _get_resource_path_or_empty(albedo_tex)
_normal_preview.tooltip_text = _get_resource_path_or_empty(normal_tex)
_roughness_preview.tooltip_text = _get_resource_path_or_empty(normal_tex)
_albedo_preview.material.shader = HT_ColorSliceShader
_bump_preview.material.shader = HT_AlphaSliceShader
_normal_preview.material.shader = \
HT_ColorSliceShader if normal_tex != null else HT_ColorShader
_roughness_preview.material.shader = \
HT_AlphaSliceShader if normal_tex != null else HT_AlphaShader
_albedo_preview.material.set_shader_parameter("u_texture_array", albedo_tex)
_bump_preview.material.set_shader_parameter("u_texture_array", albedo_tex)
_normal_preview.material.set_shader_parameter("u_texture_array", normal_tex)
_roughness_preview.material.set_shader_parameter("u_texture_array", normal_tex)
_albedo_preview.material.set_shader_parameter("u_index", slot_index)
_bump_preview.material.set_shader_parameter("u_index", slot_index)
_normal_preview.material.set_shader_parameter("u_index", slot_index)
_roughness_preview.material.set_shader_parameter("u_index", slot_index)
_slots_list.select(slot_index)
static func _get_resource_path_or_empty(res: Resource) -> String:
if res != null:
return res.resource_path
return "<empty>"
func _on_ImportButton_pressed():
import_selected.emit()
func _on_CloseButton_pressed():
hide()
func _get_undo_redo_for_texture_set() -> UndoRedo:
return _undo_redo_manager.get_history_undo_redo(
_undo_redo_manager.get_object_history_id(_texture_set))
func _on_AddSlot_pressed():
assert(_texture_set.get_mode() == HTerrainTextureSet.MODE_TEXTURES)
var slot_index = _texture_set.get_slots_count()
var ur := _get_undo_redo_for_texture_set()
ur.create_action("HTerrainTextureSet: add slot")
ur.add_do_method(_texture_set.insert_slot.bind(-1))
ur.add_undo_method(_texture_set.remove_slot.bind(slot_index))
ur.commit_action()
func _on_RemoveSlot_pressed():
assert(_texture_set.get_mode() == HTerrainTextureSet.MODE_TEXTURES)
var slot_index = _slots_list.get_selected_items()[0]
var textures = []
for type in HTerrainTextureSet.TYPE_COUNT:
textures.append(_texture_set.get_texture(slot_index, type))
var ur := _get_undo_redo_for_texture_set()
ur.create_action("HTerrainTextureSet: remove slot")
ur.add_do_method(_texture_set.remove_slot.bind(slot_index))
ur.add_undo_method(_texture_set.insert_slot.bind(slot_index))
for type in len(textures):
var texture = textures[type]
# TODO This branch only exists because of a flaw in UndoRedo
# See https://github.com/godotengine/godot/issues/36895
if texture == null:
ur.add_undo_method(_texture_set.set_texture_null.bind(slot_index, type))
else:
ur.add_undo_method(_texture_set.set_texture.bind(slot_index, type, texture))
ur.commit_action()
func _on_SlotsList_item_selected(index: int):
_select_slot(index)
func _open_load_texture_dialog(type: int):
_load_texture_type = type
if _texture_set.get_mode() == HTerrainTextureSet.MODE_TEXTURES:
_load_texture_dialog.popup_centered_ratio()
else:
_load_texture_array_dialog.popup_centered_ratio()
func _on_LoadAlbedo_pressed():
_open_load_texture_dialog(HTerrainTextureSet.TYPE_ALBEDO_BUMP)
func _on_LoadNormal_pressed():
_open_load_texture_dialog(HTerrainTextureSet.TYPE_NORMAL_ROUGHNESS)
func _set_texture_action(slot_index: int, texture: Texture, type: int):
var prev_texture = _texture_set.get_texture(slot_index, type)
var ur := _get_undo_redo_for_texture_set()
ur.create_action("HTerrainTextureSet: load texture")
# TODO This branch only exists because of a flaw in UndoRedo
# See https://github.com/godotengine/godot/issues/36895
if texture == null:
ur.add_do_method(_texture_set.set_texture_null.bind(slot_index, type))
else:
ur.add_do_method(_texture_set.set_texture.bind(slot_index, type, texture))
ur.add_do_method(self._select_slot.bind(slot_index))
# TODO This branch only exists because of a flaw in UndoRedo
# See https://github.com/godotengine/godot/issues/36895
if prev_texture == null:
ur.add_undo_method(_texture_set.set_texture_null.bind(slot_index, type))
else:
ur.add_undo_method(_texture_set.set_texture.bind(slot_index, type, prev_texture))
ur.add_undo_method(self._select_slot.bind(slot_index))
ur.commit_action()
func _set_texture_array_action(slot_index: int, texture_array: TextureLayered, type: int):
var prev_texture_array = _texture_set.get_texture_array(type)
var ur := _get_undo_redo_for_texture_set()
ur.create_action("HTerrainTextureSet: load texture array")
# TODO This branch only exists because of a flaw in UndoRedo
# See https://github.com/godotengine/godot/issues/36895
if texture_array == null:
ur.add_do_method(_texture_set.set_texture_array_null.bind(type))
# Can't select a slot after this because there won't be any after the array is removed
else:
ur.add_do_method(_texture_set.set_texture_array.bind(type, texture_array))
ur.add_do_method(self._select_slot.bind(slot_index))
# TODO This branch only exists because of a flaw in UndoRedo
# See https://github.com/godotengine/godot/issues/36895
if prev_texture_array == null:
ur.add_undo_method(_texture_set.set_texture_array_null.bind(type))
# Can't select a slot after this because there won't be any after the array is removed
else:
ur.add_undo_method(_texture_set.set_texture_array.bind(type, prev_texture_array))
ur.add_undo_method(self._select_slot.bind(slot_index))
ur.commit_action()
func _on_LoadTextureDialog_file_selected(fpath: String):
assert(_texture_set.get_mode() == HTerrainTextureSet.MODE_TEXTURES)
var texture = load(fpath)
assert(texture != null)
var slot_index : int = _slots_list.get_selected_items()[0]
_set_texture_action(slot_index, texture, _load_texture_type)
func _on_LoadTextureArrayDialog_file_selected(fpath: String):
assert(_texture_set.get_mode() == HTerrainTextureSet.MODE_TEXTURE_ARRAYS)
var texture_array = load(fpath)
assert(texture_array != null)
# It's possible no slot exists at the moment,
# because there could be no texture array already set.
# The number of slots in the new array might also be different.
# So in this case we'll default to selecting the first slot.
var slot_index := 0
_set_texture_array_action(slot_index, texture_array, _load_texture_type)
func _on_ClearAlbedo_pressed():
var slot_index : int = _slots_list.get_selected_items()[0]
if _texture_set.get_mode() == HTerrainTextureSet.MODE_TEXTURES:
_set_texture_action(slot_index, null, HTerrainTextureSet.TYPE_ALBEDO_BUMP)
else:
_set_texture_array_action(slot_index, null, HTerrainTextureSet.TYPE_ALBEDO_BUMP)
func _on_ClearNormal_pressed():
var slot_index : int = _slots_list.get_selected_items()[0]
if _texture_set.get_mode() == HTerrainTextureSet.MODE_TEXTURES:
_set_texture_action(slot_index, null, HTerrainTextureSet.TYPE_NORMAL_ROUGHNESS)
else:
_set_texture_array_action(slot_index, null, HTerrainTextureSet.TYPE_NORMAL_ROUGHNESS)
func _on_ModeSelector_item_selected(index: int):
var id = _mode_selector.get_selected_id()
if id == _texture_set.get_mode():
return
# Early-cancel the change in OptionButton, so we won't need to rely on
# the (inexistent) cancel signal from ConfirmationDialog
_set_selected_id(_mode_selector, _texture_set.get_mode())
if not _texture_set.has_any_textures():
_switch_mode_action()
else:
if _texture_set.get_mode() == HTerrainTextureSet.MODE_TEXTURES:
_mode_confirmation_dialog.window_title = "Switch to TextureArrays"
_mode_confirmation_dialog.dialog_text = \
"This will unload all textures currently setup. Do you want to continue?"
_mode_confirmation_dialog.popup_centered()
else:
_mode_confirmation_dialog.window_title = "Switch to Textures"
_mode_confirmation_dialog.dialog_text = \
"This will unload all textures currently setup. Do you want to continue?"
_mode_confirmation_dialog.popup_centered()
func _on_ModeConfirmationDialog_confirmed():
_switch_mode_action()
func _switch_mode_action():
var mode := _texture_set.get_mode()
var ur := _get_undo_redo_for_texture_set()
if mode == HTerrainTextureSet.MODE_TEXTURES:
ur.create_action("HTerrainTextureSet: switch to TextureArrays")
ur.add_do_method(_texture_set.set_mode.bind(HTerrainTextureSet.MODE_TEXTURE_ARRAYS))
backup_for_undo(_texture_set, ur)
else:
ur.create_action("HTerrainTextureSet: switch to Textures")
ur.add_do_method(_texture_set.set_mode.bind(HTerrainTextureSet.MODE_TEXTURES))
backup_for_undo(_texture_set, ur)
ur.commit_action()
static func backup_for_undo(texture_set: HTerrainTextureSet, ur: UndoRedo):
var mode := texture_set.get_mode()
ur.add_undo_method(texture_set.clear)
ur.add_undo_method(texture_set.set_mode.bind(mode))
if mode == HTerrainTextureSet.MODE_TEXTURES:
# Backup slots
var slot_count := texture_set.get_slots_count()
var type_textures := []
for type in HTerrainTextureSet.TYPE_COUNT:
var textures := []
for slot_index in slot_count:
textures.append(texture_set.get_texture(slot_index, type))
type_textures.append(textures)
for type in len(type_textures):
var textures = type_textures[type]
for slot_index in len(textures):
ur.add_undo_method(texture_set.insert_slot.bind(slot_index))
var texture = textures[slot_index]
# TODO This branch only exists because of a flaw in UndoRedo
# See https://github.com/godotengine/godot/issues/36895
if texture == null:
ur.add_undo_method(texture_set.set_texture_null.bind(slot_index, type))
else:
ur.add_undo_method(texture_set.set_texture.bind(slot_index, type, texture))
else:
# Backup slots
var type_textures := []
for type in HTerrainTextureSet.TYPE_COUNT:
type_textures.append(texture_set.get_texture_array(type))
for type in len(type_textures):
var texture_array = type_textures[type]
# TODO This branch only exists because of a flaw in UndoRedo
# See https://github.com/godotengine/godot/issues/36895
if texture_array == null:
ur.add_undo_method(texture_set.set_texture_array_null.bind(type))
else:
ur.add_undo_method(texture_set.set_texture_array.bind(type, texture_array))
#func _on_ModeConfirmationDialog_cancelled():
# print("Cancelled")
# _set_selected_id(_mode_selector, _texture_set.get_mode())