2024-12-05 03:57:44 +02:00
|
|
|
class_name TileSetPanel
|
|
|
|
extends PanelContainer
|
|
|
|
|
|
|
|
enum TileEditingMode { MANUAL, AUTO, STACK }
|
|
|
|
|
|
|
|
const TRANSPARENT_CHECKER := preload("res://src/UI/Nodes/TransparentChecker.tscn")
|
|
|
|
const MIN_BUTTON_SIZE := 36
|
|
|
|
const MAX_BUTTON_SIZE := 144
|
|
|
|
## A matrix with every possible flip/transpose combination,
|
|
|
|
## sorted by what comes next when you rotate.
|
|
|
|
## Taken from Godot's rotation matrix found in:
|
|
|
|
## https://github.com/godotengine/godot/blob/master/editor/plugins/tiles/tile_map_layer_editor.cpp
|
|
|
|
const ROTATION_MATRIX: Array[bool] = [
|
|
|
|
0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1
|
|
|
|
]
|
|
|
|
|
|
|
|
static var placing_tiles := false:
|
|
|
|
set(value):
|
|
|
|
placing_tiles = value
|
|
|
|
_call_update_brushes()
|
|
|
|
static var tile_editing_mode := TileEditingMode.AUTO
|
|
|
|
static var selected_tile_index := 0:
|
|
|
|
set(value):
|
2025-02-08 21:21:28 +02:00
|
|
|
selected_tiles = [value]
|
2024-12-05 03:57:44 +02:00
|
|
|
_call_update_brushes()
|
2025-02-08 21:21:28 +02:00
|
|
|
get:
|
2025-02-09 03:34:10 +02:00
|
|
|
if is_instance_valid(current_tileset):
|
|
|
|
return current_tileset.pick_random_tile(selected_tiles)
|
|
|
|
return selected_tiles[0]
|
2025-02-08 21:21:28 +02:00
|
|
|
static var selected_tiles: Array[int] = [0]
|
2024-12-05 03:57:44 +02:00
|
|
|
static var is_flipped_h := false:
|
|
|
|
set(value):
|
|
|
|
is_flipped_h = value
|
|
|
|
_call_update_brushes()
|
|
|
|
static var is_flipped_v := false:
|
|
|
|
set(value):
|
|
|
|
is_flipped_v = value
|
|
|
|
_call_update_brushes()
|
|
|
|
static var is_transposed := false:
|
|
|
|
set(value):
|
|
|
|
is_transposed = value
|
|
|
|
_call_update_brushes()
|
2025-02-09 03:34:10 +02:00
|
|
|
static var current_tileset: TileSetCustom
|
2024-12-05 03:57:44 +02:00
|
|
|
var button_size := 36:
|
|
|
|
set(value):
|
|
|
|
if button_size == value:
|
|
|
|
return
|
|
|
|
button_size = clampi(value, MIN_BUTTON_SIZE, MAX_BUTTON_SIZE)
|
2025-01-29 00:02:44 +02:00
|
|
|
tile_size_slider.value = button_size
|
2024-12-05 03:57:44 +02:00
|
|
|
update_minimum_size()
|
|
|
|
Global.config_cache.set_value("tileset_panel", "button_size", button_size)
|
|
|
|
for button: Control in tile_button_container.get_children():
|
|
|
|
button.custom_minimum_size = Vector2(button_size, button_size)
|
|
|
|
button.size = Vector2(button_size, button_size)
|
2025-01-29 00:02:44 +02:00
|
|
|
var show_empty_tile := true
|
2025-01-29 19:15:18 +02:00
|
|
|
var tile_index_menu_popped := 0
|
2024-12-05 03:57:44 +02:00
|
|
|
|
2024-12-14 00:30:03 +05:00
|
|
|
@onready var place_tiles: Button = %PlaceTiles
|
|
|
|
@onready var transform_buttons_container: HFlowContainer = %TransformButtonsContainer
|
2024-12-05 03:57:44 +02:00
|
|
|
@onready var tile_button_container: HFlowContainer = %TileButtonContainer
|
2024-12-14 00:30:03 +05:00
|
|
|
@onready var mode_buttons_container: HFlowContainer = %ModeButtonsContainer
|
2025-01-29 00:02:44 +02:00
|
|
|
@onready var option_button: Button = %OptionButton
|
|
|
|
@onready var options: Popup = $Options
|
|
|
|
@onready var tile_size_slider: ValueSlider = %TileSizeSlider
|
2025-01-29 19:15:18 +02:00
|
|
|
@onready var tile_button_popup_menu: PopupMenu = $TileButtonPopupMenu
|
2025-02-09 03:34:10 +02:00
|
|
|
@onready var tile_properties: AcceptDialog = $TileProperties
|
|
|
|
@onready var tile_probability_slider: ValueSlider = %TileProbabilitySlider
|
2024-12-05 03:57:44 +02:00
|
|
|
|
|
|
|
|
|
|
|
func _ready() -> void:
|
|
|
|
Tools.selected_tile_index_changed.connect(select_tile)
|
|
|
|
Global.cel_switched.connect(_on_cel_switched)
|
|
|
|
for child: Button in transform_buttons_container.get_children():
|
|
|
|
Global.disable_button(child, true)
|
2024-12-14 00:30:03 +05:00
|
|
|
update_tip()
|
2025-01-29 00:02:44 +02:00
|
|
|
tile_size_slider.min_value = MIN_BUTTON_SIZE
|
|
|
|
tile_size_slider.max_value = MAX_BUTTON_SIZE
|
|
|
|
tile_size_slider.value = button_size
|
2024-12-05 03:57:44 +02:00
|
|
|
|
|
|
|
|
|
|
|
func _gui_input(event: InputEvent) -> void:
|
|
|
|
if Input.is_key_pressed(KEY_CTRL):
|
|
|
|
var zoom := 2 * int(event.is_action("zoom_in")) - 2 * int(event.is_action("zoom_out"))
|
|
|
|
button_size += zoom
|
|
|
|
if zoom != 0:
|
|
|
|
get_viewport().set_input_as_handled()
|
|
|
|
|
|
|
|
|
|
|
|
func set_tileset(tileset: TileSetCustom) -> void:
|
|
|
|
if tileset == current_tileset:
|
|
|
|
return
|
|
|
|
if is_instance_valid(current_tileset) and current_tileset.updated.is_connected(_update_tileset):
|
|
|
|
current_tileset.updated.disconnect(_update_tileset)
|
|
|
|
current_tileset = tileset
|
|
|
|
if (
|
|
|
|
is_instance_valid(current_tileset)
|
|
|
|
and not current_tileset.updated.is_connected(_update_tileset)
|
|
|
|
):
|
|
|
|
current_tileset.updated.connect(_update_tileset)
|
|
|
|
|
|
|
|
|
2025-01-29 00:02:44 +02:00
|
|
|
func update_tip() -> void:
|
|
|
|
var tip := %Tip
|
2024-12-14 00:30:03 +05:00
|
|
|
tip.get_parent().visible = true
|
|
|
|
if placing_tiles:
|
2025-01-29 00:02:44 +02:00
|
|
|
tip.text = tr("Select a tile to place it on the canvas.")
|
2024-12-14 00:30:03 +05:00
|
|
|
else:
|
2025-01-29 00:02:44 +02:00
|
|
|
tip.text = tr("Modify tiles on the canvas.")
|
2024-12-14 00:30:03 +05:00
|
|
|
|
|
|
|
|
2024-12-05 03:57:44 +02:00
|
|
|
func _on_cel_switched() -> void:
|
|
|
|
if Global.current_project.get_current_cel() is not CelTileMap:
|
|
|
|
set_tileset(null)
|
|
|
|
_clear_tile_buttons()
|
|
|
|
return
|
|
|
|
var cel := Global.current_project.get_current_cel() as CelTileMap
|
|
|
|
set_tileset(cel.tileset)
|
|
|
|
_update_tileset(cel, -1)
|
|
|
|
|
|
|
|
|
2025-01-29 19:15:18 +02:00
|
|
|
func _update_tileset(_cel: BaseCel, _replace_index: int) -> void:
|
2024-12-05 03:57:44 +02:00
|
|
|
_clear_tile_buttons()
|
2025-02-08 21:21:28 +02:00
|
|
|
for tile_index in selected_tiles:
|
|
|
|
if tile_index >= current_tileset.tiles.size():
|
|
|
|
selected_tiles.erase(tile_index)
|
|
|
|
if selected_tiles.is_empty():
|
2024-12-05 03:57:44 +02:00
|
|
|
selected_tile_index = 0
|
2025-01-29 19:15:18 +02:00
|
|
|
for i in current_tileset.tiles.size():
|
|
|
|
var tile := current_tileset.tiles[i]
|
2024-12-05 03:57:44 +02:00
|
|
|
var texture := ImageTexture.create_from_image(tile.image)
|
2025-02-08 21:21:28 +02:00
|
|
|
var button := _create_tile_button(texture, i)
|
|
|
|
if i in selected_tiles:
|
2024-12-05 03:57:44 +02:00
|
|
|
button.set_pressed_no_signal(true)
|
|
|
|
tile_button_container.add_child(button)
|
|
|
|
|
|
|
|
|
2025-02-08 21:21:28 +02:00
|
|
|
func _create_tile_button(texture: Texture2D, index: int) -> Button:
|
2024-12-05 03:57:44 +02:00
|
|
|
var button := Button.new()
|
|
|
|
button.toggle_mode = true
|
|
|
|
button.custom_minimum_size = Vector2(button_size, button_size)
|
|
|
|
button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
|
|
|
|
var texture_rect := TextureRect.new()
|
|
|
|
texture_rect.texture = texture
|
|
|
|
texture_rect.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
|
|
|
texture_rect.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
|
|
|
texture_rect.set_anchor_and_offset(SIDE_LEFT, 0, 6)
|
|
|
|
texture_rect.set_anchor_and_offset(SIDE_RIGHT, 1, -6)
|
|
|
|
texture_rect.set_anchor_and_offset(SIDE_TOP, 0, 6)
|
|
|
|
texture_rect.set_anchor_and_offset(SIDE_BOTTOM, 1, -6)
|
|
|
|
texture_rect.grow_horizontal = Control.GROW_DIRECTION_BOTH
|
|
|
|
texture_rect.grow_vertical = Control.GROW_DIRECTION_BOTH
|
|
|
|
var transparent_checker := TRANSPARENT_CHECKER.instantiate() as ColorRect
|
|
|
|
transparent_checker.set_anchors_preset(Control.PRESET_FULL_RECT)
|
|
|
|
transparent_checker.show_behind_parent = true
|
|
|
|
texture_rect.add_child(transparent_checker)
|
|
|
|
button.add_child(texture_rect)
|
|
|
|
button.tooltip_text = str(index)
|
|
|
|
button.toggled.connect(_on_tile_button_toggled.bind(index))
|
2025-01-29 19:15:18 +02:00
|
|
|
button.gui_input.connect(_on_tile_button_gui_input.bind(index))
|
2025-01-29 00:02:44 +02:00
|
|
|
if index == 0 and not show_empty_tile:
|
|
|
|
button.visible = false
|
2024-12-05 03:57:44 +02:00
|
|
|
return button
|
|
|
|
|
|
|
|
|
|
|
|
func select_tile(tile_index: int) -> void:
|
|
|
|
tile_button_container.get_child(tile_index).button_pressed = true
|
|
|
|
|
|
|
|
|
|
|
|
static func _call_update_brushes() -> void:
|
|
|
|
for slot in Tools._slots.values():
|
|
|
|
if slot.tool_node is BaseDrawTool:
|
|
|
|
slot.tool_node.update_brush()
|
|
|
|
|
|
|
|
|
2025-02-08 21:21:28 +02:00
|
|
|
func _on_tile_button_toggled(_toggled_on: bool, index: int) -> void:
|
|
|
|
if Input.is_action_pressed("shift"):
|
|
|
|
selected_tiles.sort()
|
|
|
|
var diff_sign := signi(index - selected_tiles[-1])
|
|
|
|
if diff_sign == 0:
|
|
|
|
diff_sign = 1
|
|
|
|
for i in range(selected_tiles[-1], index + diff_sign, diff_sign):
|
|
|
|
if not selected_tiles.has(i):
|
|
|
|
selected_tiles.append(i)
|
|
|
|
tile_button_container.get_child(i).set_pressed_no_signal(true)
|
|
|
|
elif Input.is_action_pressed("ctrl"):
|
|
|
|
if selected_tiles.has(index):
|
|
|
|
if selected_tiles.size() > 1:
|
|
|
|
selected_tiles.erase(index)
|
|
|
|
else:
|
|
|
|
selected_tiles.append(index)
|
|
|
|
else:
|
2024-12-05 03:57:44 +02:00
|
|
|
selected_tile_index = index
|
2025-02-08 21:21:28 +02:00
|
|
|
for i in tile_button_container.get_child_count():
|
|
|
|
var child_button := tile_button_container.get_child(i) as BaseButton
|
|
|
|
child_button.set_pressed_no_signal(i == index)
|
|
|
|
place_tiles.button_pressed = true
|
2024-12-05 03:57:44 +02:00
|
|
|
|
|
|
|
|
2025-01-29 19:15:18 +02:00
|
|
|
func _on_tile_button_gui_input(event: InputEvent, index: int) -> void:
|
|
|
|
if event.is_action(&"right_mouse"):
|
|
|
|
tile_button_popup_menu.popup_on_parent(Rect2(get_global_mouse_position(), Vector2.ONE))
|
|
|
|
tile_index_menu_popped = index
|
|
|
|
tile_button_popup_menu.set_item_disabled(
|
2025-02-09 03:34:10 +02:00
|
|
|
1, not current_tileset.tiles[index].can_be_removed()
|
2025-01-29 19:15:18 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
|
2024-12-05 03:57:44 +02:00
|
|
|
func _clear_tile_buttons() -> void:
|
|
|
|
for child in tile_button_container.get_children():
|
|
|
|
child.queue_free()
|
|
|
|
|
|
|
|
|
|
|
|
func _on_place_tiles_toggled(toggled_on: bool) -> void:
|
|
|
|
placing_tiles = toggled_on
|
2024-12-14 00:30:03 +05:00
|
|
|
transform_buttons_container.visible = placing_tiles
|
|
|
|
mode_buttons_container.visible = !placing_tiles
|
2024-12-05 03:57:44 +02:00
|
|
|
for child: Button in transform_buttons_container.get_children():
|
|
|
|
Global.disable_button(child, not toggled_on)
|
2024-12-14 00:30:03 +05:00
|
|
|
update_tip()
|
2024-12-05 03:57:44 +02:00
|
|
|
|
|
|
|
|
|
|
|
func _on_manual_toggled(toggled_on: bool) -> void:
|
|
|
|
place_tiles.button_pressed = false
|
|
|
|
if toggled_on:
|
|
|
|
tile_editing_mode = TileEditingMode.MANUAL
|
|
|
|
|
|
|
|
|
|
|
|
func _on_auto_toggled(toggled_on: bool) -> void:
|
|
|
|
place_tiles.button_pressed = false
|
|
|
|
if toggled_on:
|
|
|
|
tile_editing_mode = TileEditingMode.AUTO
|
|
|
|
|
|
|
|
|
|
|
|
func _on_stack_toggled(toggled_on: bool) -> void:
|
|
|
|
place_tiles.button_pressed = false
|
|
|
|
if toggled_on:
|
|
|
|
tile_editing_mode = TileEditingMode.STACK
|
|
|
|
|
|
|
|
|
|
|
|
func _on_flip_horizontal_button_pressed() -> void:
|
|
|
|
is_flipped_h = not is_flipped_h
|
|
|
|
|
|
|
|
|
|
|
|
func _on_flip_vertical_button_pressed() -> void:
|
|
|
|
is_flipped_v = not is_flipped_v
|
|
|
|
|
|
|
|
|
|
|
|
func _on_rotate_pressed(clockwise: bool) -> void:
|
|
|
|
for i in ROTATION_MATRIX.size():
|
|
|
|
var final_i := i
|
|
|
|
if (
|
|
|
|
is_flipped_h == ROTATION_MATRIX[i * 3]
|
|
|
|
&& is_flipped_v == ROTATION_MATRIX[i * 3 + 1]
|
|
|
|
&& is_transposed == ROTATION_MATRIX[i * 3 + 2]
|
|
|
|
):
|
|
|
|
if clockwise:
|
|
|
|
@warning_ignore("integer_division")
|
|
|
|
final_i = i / 4 * 4 + posmod(i - 1, 4)
|
|
|
|
else:
|
|
|
|
@warning_ignore("integer_division")
|
|
|
|
final_i = i / 4 * 4 + (i + 1) % 4
|
|
|
|
is_flipped_h = ROTATION_MATRIX[final_i * 3]
|
|
|
|
is_flipped_v = ROTATION_MATRIX[final_i * 3 + 1]
|
|
|
|
is_transposed = ROTATION_MATRIX[final_i * 3 + 2]
|
|
|
|
break
|
2025-01-29 00:02:44 +02:00
|
|
|
|
|
|
|
|
|
|
|
func _on_option_button_pressed() -> void:
|
|
|
|
var pos := Vector2i(option_button.global_position) - options.size
|
|
|
|
options.popup_on_parent(Rect2i(pos.x - 16, pos.y + 32, options.size.x, options.size.y))
|
|
|
|
|
|
|
|
|
|
|
|
func _on_tile_size_slider_value_changed(value: float) -> void:
|
|
|
|
button_size = value
|
|
|
|
|
|
|
|
|
|
|
|
func _on_show_empty_tile_toggled(toggled_on: bool) -> void:
|
|
|
|
show_empty_tile = toggled_on
|
|
|
|
if tile_button_container.get_child_count() > 0:
|
|
|
|
tile_button_container.get_child(0).visible = show_empty_tile
|
2025-01-29 19:15:18 +02:00
|
|
|
|
|
|
|
|
|
|
|
func _on_tile_button_popup_menu_index_pressed(index: int) -> void:
|
2025-02-09 03:34:10 +02:00
|
|
|
if index == 0: # Properties
|
|
|
|
tile_probability_slider.value = current_tileset.tiles[tile_index_menu_popped].probability
|
|
|
|
tile_properties.popup_centered()
|
|
|
|
elif index == 1: # Delete
|
|
|
|
if tile_index_menu_popped == 0:
|
|
|
|
return
|
2025-01-29 19:15:18 +02:00
|
|
|
if current_tileset.tiles[tile_index_menu_popped].can_be_removed():
|
|
|
|
var undo_data := current_tileset.serialize_undo_data()
|
|
|
|
current_tileset.tiles.remove_at(tile_index_menu_popped)
|
|
|
|
var redo_data := current_tileset.serialize_undo_data()
|
|
|
|
var project := Global.current_project
|
|
|
|
project.undo_redo.create_action("Delete tile")
|
|
|
|
project.undo_redo.add_do_method(
|
|
|
|
current_tileset.deserialize_undo_data.bind(redo_data, null)
|
|
|
|
)
|
|
|
|
project.undo_redo.add_undo_method(
|
|
|
|
current_tileset.deserialize_undo_data.bind(undo_data, null)
|
|
|
|
)
|
|
|
|
project.undo_redo.add_undo_method(Global.undo_or_redo.bind(true))
|
|
|
|
project.undo_redo.add_do_method(Global.undo_or_redo.bind(false))
|
|
|
|
project.undo_redo.commit_action()
|
2025-02-09 03:34:10 +02:00
|
|
|
|
|
|
|
|
|
|
|
func _on_tile_probability_slider_value_changed(value: float) -> void:
|
|
|
|
current_tileset.tiles[tile_index_menu_popped].probability = value
|