| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939 |
- @tool
- extends Control
- signal update_overlay
- signal force_show_terrains
- # The maximum individual tiles the overlay will draw before shortcutting the display
- # To prevent editor lag when drawing large rectangles or filling large areas
- const MAX_CANVAS_RENDER_TILES = 1500
- const TERRAIN_PROPERTIES_SCENE := preload("res://addons/better-terrain/editor/TerrainProperties.tscn")
- const TERRAIN_ENTRY_SCENE := preload("res://addons/better-terrain/editor/TerrainEntry.tscn")
- const MIN_ZOOM_SETTING := "editor/better_terrain/min_zoom_amount"
- const MAX_ZOOM_SETTING := "editor/better_terrain/max_zoom_amount"
- # Buttons
- @onready var draw_button: Button = $VBox/Toolbar/Draw
- @onready var line_button: Button = $VBox/Toolbar/Line
- @onready var rectangle_button: Button = $VBox/Toolbar/Rectangle
- @onready var fill_button: Button = $VBox/Toolbar/Fill
- @onready var replace_button: Button = $VBox/Toolbar/Replace
- @onready var paint_type: Button = $VBox/Toolbar/PaintType
- @onready var paint_terrain: Button = $VBox/Toolbar/PaintTerrain
- @onready var select_tiles: Button = $VBox/Toolbar/SelectTiles
- @onready var paint_symmetry: Button = $VBox/Toolbar/PaintSymmetry
- @onready var symmetry_options: OptionButton = $VBox/Toolbar/SymmetryOptions
- @onready var shuffle_random: Button = $VBox/Toolbar/ShuffleRandom
- @onready var zoom_slider_container: VBoxContainer = $VBox/Toolbar/ZoomContainer
- @onready var source_selector: MenuBar = $VBox/Toolbar/Sources
- @onready var source_selector_popup: PopupMenu = $VBox/Toolbar/Sources/Sources
- @onready var clean_button: Button = $VBox/Toolbar/Clean
- @onready var layer_up: Button = $VBox/Toolbar/LayerUp
- @onready var layer_down: Button = $VBox/Toolbar/LayerDown
- @onready var layer_highlight: Button = $VBox/Toolbar/LayerHighlight
- @onready var layer_grid: Button = $VBox/Toolbar/LayerGrid
- @onready var grid_mode_button: Button = $VBox/HSplit/Terrains/LowerToolbar/GridMode
- @onready var quick_mode_button: Button = $VBox/HSplit/Terrains/LowerToolbar/QuickMode
- @onready var edit_tool_buttons: HBoxContainer = $VBox/HSplit/Terrains/LowerToolbar/EditTools
- @onready var add_terrain_button: Button = $VBox/HSplit/Terrains/LowerToolbar/EditTools/AddTerrain
- @onready var edit_terrain_button: Button = $VBox/HSplit/Terrains/LowerToolbar/EditTools/EditTerrain
- @onready var pick_icon_button: Button = $VBox/HSplit/Terrains/LowerToolbar/EditTools/PickIcon
- @onready var move_up_button: Button = $VBox/HSplit/Terrains/LowerToolbar/EditTools/MoveUp
- @onready var move_down_button: Button = $VBox/HSplit/Terrains/LowerToolbar/EditTools/MoveDown
- @onready var remove_terrain_button: Button = $VBox/HSplit/Terrains/LowerToolbar/EditTools/RemoveTerrain
- @onready var scroll_container: ScrollContainer = $VBox/HSplit/Terrains/Panel/ScrollContainer
- @onready var terrain_list: HFlowContainer = $VBox/HSplit/Terrains/Panel/ScrollContainer/TerrainList
- @onready var tile_view: Control = $VBox/HSplit/Panel/ScrollArea/TileView
- var selected_entry := -2
- var tilemap : TileMapLayer
- var tileset : TileSet
- var undo_manager : EditorUndoRedoManager
- var terrain_undo
- var draw_overlay := false
- var initial_click : Vector2i
- var prev_position : Vector2i
- var current_position : Vector2i
- var tileset_dirty := false
- var zoom_slider : HSlider
- enum PaintMode {
- NO_PAINT,
- PAINT,
- ERASE
- }
- enum PaintAction {
- NO_ACTION,
- LINE,
- RECT
- }
- enum SourceSelectors {
- ALL = 1000000,
- NONE = 1000001,
- }
- var paint_mode := PaintMode.NO_PAINT
- var paint_action := PaintAction.NO_ACTION
- # Called when the node enters the scene tree for the first time.
- func _ready() -> void:
- draw_button.icon = get_theme_icon("Edit", "EditorIcons")
- line_button.icon = get_theme_icon("Line", "EditorIcons")
- rectangle_button.icon = get_theme_icon("Rectangle", "EditorIcons")
- fill_button.icon = get_theme_icon("Bucket", "EditorIcons")
- select_tiles.icon = get_theme_icon("ToolSelect", "EditorIcons")
- add_terrain_button.icon = get_theme_icon("Add", "EditorIcons")
- edit_terrain_button.icon = get_theme_icon("Tools", "EditorIcons")
- pick_icon_button.icon = get_theme_icon("ColorPick", "EditorIcons")
- move_up_button.icon = get_theme_icon("ArrowUp", "EditorIcons")
- move_down_button.icon = get_theme_icon("ArrowDown", "EditorIcons")
- remove_terrain_button.icon = get_theme_icon("Remove", "EditorIcons")
- grid_mode_button.icon = get_theme_icon("FileThumbnail", "EditorIcons")
- quick_mode_button.icon = get_theme_icon("GuiVisibilityVisible", "EditorIcons")
- layer_up.icon = get_theme_icon("MoveUp", "EditorIcons")
- layer_down.icon = get_theme_icon("MoveDown", "EditorIcons")
- layer_highlight.icon = get_theme_icon("TileMapHighlightSelected", "EditorIcons")
- layer_grid.icon = get_theme_icon("Grid", "EditorIcons")
-
- select_tiles.button_group.pressed.connect(_on_bit_button_pressed)
-
- terrain_undo = load("res://addons/better-terrain/editor/TerrainUndo.gd").new()
- add_child(terrain_undo)
- tile_view.undo_manager = undo_manager
- tile_view.terrain_undo = terrain_undo
-
- tile_view.paste_occurred.connect(_on_paste_occurred)
- tile_view.change_zoom_level.connect(_on_change_zoom_level)
- tile_view.terrain_updated.connect(_on_terrain_updated)
-
- # Zoom slider is manipulated by settings, make it at runtime
- zoom_slider = HSlider.new()
- zoom_slider.custom_minimum_size = Vector2(100, 0)
- zoom_slider.value_changed.connect(tile_view._on_zoom_value_changed)
- zoom_slider_container.add_child(zoom_slider)
-
- # Init settings if needed
- if !ProjectSettings.has_setting(MIN_ZOOM_SETTING):
- ProjectSettings.set(MIN_ZOOM_SETTING, 1.0)
- ProjectSettings.add_property_info({
- "name": MIN_ZOOM_SETTING,
- "type": TYPE_FLOAT,
- "hint": PROPERTY_HINT_RANGE,
- "hint_string": "0.1,1.0,0.1"
- })
- ProjectSettings.set_initial_value(MIN_ZOOM_SETTING, 1.0)
- ProjectSettings.set_as_basic(MIN_ZOOM_SETTING, true)
-
- if !ProjectSettings.has_setting(MAX_ZOOM_SETTING):
- ProjectSettings.set(MAX_ZOOM_SETTING, 8.0)
- ProjectSettings.add_property_info({
- "name": MAX_ZOOM_SETTING,
- "type": TYPE_FLOAT,
- "hint": PROPERTY_HINT_RANGE,
- "hint_string": "2.0,32.0,1.0"
- })
- ProjectSettings.set_initial_value(MAX_ZOOM_SETTING, 8.0)
- ProjectSettings.set_as_basic(MAX_ZOOM_SETTING, true)
- ProjectSettings.set_order(MAX_ZOOM_SETTING, ProjectSettings.get_order(MIN_ZOOM_SETTING) + 1)
-
- ProjectSettings.settings_changed.connect(_on_adjust_settings)
- _on_adjust_settings()
- zoom_slider.value = 1.0
- func _process(delta):
- scroll_container.scroll_horizontal = 0
- func _on_adjust_settings():
- zoom_slider.min_value = ProjectSettings.get_setting(MIN_ZOOM_SETTING, 1.0)
- zoom_slider.max_value = ProjectSettings.get_setting(MAX_ZOOM_SETTING, 8.0)
- zoom_slider.step = (zoom_slider.max_value - zoom_slider.min_value) / 100.0
- func _get_fill_cells(target: Vector2i) -> Array:
- var pick := BetterTerrain.get_cell(tilemap, target)
- var bounds := tilemap.get_used_rect()
- var neighbors = BetterTerrain.data.cells_adjacent_for_fill(tileset)
-
- # No sets yet, so use a dictionary
- var checked := {}
- var pending := [target]
- var goal := []
-
- while !pending.is_empty():
- var p = pending.pop_front()
- if checked.has(p):
- continue
- checked[p] = true
- if !bounds.has_point(p) or BetterTerrain.get_cell(tilemap, p) != pick:
- continue
-
- goal.append(p)
- pending.append_array(BetterTerrain.data.neighboring_coords(tilemap, p, neighbors))
-
- return goal
- func tiles_about_to_change() -> void:
- if tileset and tileset.changed.is_connected(queue_tiles_changed):
- tileset.changed.disconnect(queue_tiles_changed)
- func tiles_changed() -> void:
- # ensure up to date
- BetterTerrain._update_terrain_data(tileset)
-
- # clear terrains
- for c in terrain_list.get_children():
- terrain_list.remove_child(c)
- c.queue_free()
-
- # load terrains from tileset
- var terrain_count := BetterTerrain.terrain_count(tileset)
- var item_count = terrain_count + 1
- for i in terrain_count:
- var terrain := BetterTerrain.get_terrain(tileset, i)
- if i >= terrain_list.get_child_count():
- add_terrain_entry(terrain, i)
-
- if item_count > terrain_list.get_child_count():
- var terrain := BetterTerrain.get_terrain(tileset, BetterTerrain.TileCategory.EMPTY)
- if terrain.valid:
- add_terrain_entry(terrain, item_count - 1)
-
- while item_count < terrain_list.get_child_count():
- var child = terrain_list.get_child(terrain_list.get_child_count() - 1)
- terrain_list.remove_child(child)
- child.free()
-
- source_selector_popup.clear()
- source_selector_popup.add_item("All", SourceSelectors.ALL)
- source_selector_popup.add_item("None", SourceSelectors.NONE)
- var source_count = tileset.get_source_count() if tileset else 0
- for s in source_count:
- var source_id = tileset.get_source_id(s)
- var source := tileset.get_source(source_id)
- if !(source is TileSetAtlasSource):
- continue
-
- var name := source.resource_name
- if name.is_empty():
- var texture := (source as TileSetAtlasSource).texture
- var texture_name := texture.resource_name if texture else ""
- if !texture_name.is_empty():
- name = texture_name
- else:
- var texture_path := texture.resource_path if texture else ""
- if !texture_path.is_empty():
- name = texture_path.get_file()
-
- if !name.is_empty():
- name += " "
- name += " (ID: %d)" % source_id
-
- source_selector_popup.add_check_item(name, source_id)
- source_selector_popup.set_item_checked(source_selector_popup.get_item_index(source_id), true)
- source_selector.visible = source_selector_popup.item_count > 3 # All, None and more than one source
-
- update_tile_view_paint()
- tile_view.refresh_tileset(tileset)
-
- if tileset and !tileset.changed.is_connected(queue_tiles_changed):
- tileset.changed.connect(queue_tiles_changed)
-
- clean_button.visible = BetterTerrain._has_invalid_peering_types(tileset)
-
- tileset_dirty = false
- _on_grid_mode_pressed()
- _on_quick_mode_pressed()
- func about_to_be_visible(visible: bool) -> void:
- if !visible:
- return
-
- if tileset != tilemap.tile_set:
- tiles_about_to_change()
- tileset = tilemap.tile_set
- tiles_changed()
-
- var settings := EditorInterface.get_editor_settings()
- layer_highlight.set_pressed_no_signal(settings.get_setting("editors/tiles_editor/highlight_selected_layer"))
- layer_grid.set_pressed_no_signal(settings.get_setting("editors/tiles_editor/display_grid"))
- func queue_tiles_changed() -> void:
- # Bring terrain data up to date with complex tileset changes
- if !tileset or tileset_dirty:
- return
-
- tileset_dirty = true
- tiles_changed.call_deferred()
- func _on_entry_select(index:int):
- selected_entry = index
- if selected_entry >= BetterTerrain.terrain_count(tileset):
- selected_entry = BetterTerrain.TileCategory.EMPTY
- for i in range(terrain_list.get_child_count()):
- if i != index:
- terrain_list.get_child(i).set_selected(false)
- update_tile_view_paint()
- func _on_clean_pressed() -> void:
- var confirmed := [false]
- var popup := ConfirmationDialog.new()
- popup.dialog_text = tr("Tile set changes have caused terrain to become invalid. Remove invalid terrain data?")
- popup.dialog_hide_on_ok = false
- popup.confirmed.connect(func():
- confirmed[0] = true
- popup.hide()
- )
- EditorInterface.popup_dialog_centered(popup)
- await popup.visibility_changed
- popup.queue_free()
-
- if confirmed[0]:
- undo_manager.create_action("Clean invalid terrain peering data", UndoRedo.MERGE_DISABLE, tileset)
- undo_manager.add_do_method(BetterTerrain, &"_clear_invalid_peering_types", tileset)
- undo_manager.add_do_method(self, &"tiles_changed")
- terrain_undo.create_peering_restore_point(undo_manager, tileset)
- undo_manager.add_undo_method(self, &"tiles_changed")
- undo_manager.commit_action()
- func _on_grid_mode_pressed() -> void:
- for c in terrain_list.get_children():
- c.grid_mode = grid_mode_button.button_pressed
- c.update_style()
- func _on_quick_mode_pressed() -> void:
- edit_tool_buttons.visible = !quick_mode_button.button_pressed
- for c in terrain_list.get_children():
- c.visible = !quick_mode_button.button_pressed or c.terrain.type in [BetterTerrain.TerrainType.MATCH_TILES, BetterTerrain.TerrainType.MATCH_VERTICES]
- func update_tile_view_paint() -> void:
- tile_view.paint = selected_entry
- tile_view.queue_redraw()
-
- var editable = tile_view.paint != BetterTerrain.TileCategory.EMPTY
- edit_terrain_button.disabled = !editable
- move_up_button.disabled = !editable or tile_view.paint == 0
- move_down_button.disabled = !editable or tile_view.paint == BetterTerrain.terrain_count(tileset) - 1
- remove_terrain_button.disabled = !editable
- pick_icon_button.disabled = !editable
- func _on_add_terrain_pressed() -> void:
- if !tileset:
- return
-
- var popup := TERRAIN_PROPERTIES_SCENE.instantiate()
- popup.set_category_data(BetterTerrain.get_terrain_categories(tileset))
- popup.terrain_name = "New terrain"
- popup.terrain_color = Color.from_hsv(randf(), 0.3 + 0.7 * randf(), 0.6 + 0.4 * randf())
- popup.terrain_icon = ""
- popup.terrain_type = 0
- EditorInterface.popup_dialog_centered(popup)
- await popup.visibility_changed
- if popup.accepted:
- undo_manager.create_action("Add terrain type", UndoRedo.MERGE_DISABLE, tileset)
- undo_manager.add_do_method(self, &"perform_add_terrain", popup.terrain_name, popup.terrain_color, popup.terrain_type, popup.terrain_categories, {path = popup.terrain_icon})
- undo_manager.add_undo_method(self, &"perform_remove_terrain", terrain_list.get_child_count() - 1)
- undo_manager.commit_action()
- popup.queue_free()
- func _on_edit_terrain_pressed() -> void:
- if !tileset:
- return
-
- if selected_entry < 0:
- return
-
- var t := BetterTerrain.get_terrain(tileset, selected_entry)
- var categories = BetterTerrain.get_terrain_categories(tileset)
- categories = categories.filter(func(x): return x.id != selected_entry)
-
- var popup := TERRAIN_PROPERTIES_SCENE.instantiate()
- popup.set_category_data(categories)
-
- t.icon = t.icon.duplicate()
-
- popup.terrain_name = t.name
- popup.terrain_type = t.type
- popup.terrain_color = t.color
- if t.has("icon") and t.icon.has("path"):
- popup.terrain_icon = t.icon.path
- popup.terrain_categories = t.categories
- EditorInterface.popup_dialog_centered(popup)
- await popup.visibility_changed
- if popup.accepted:
- undo_manager.create_action("Edit terrain details", UndoRedo.MERGE_DISABLE, tileset)
- undo_manager.add_do_method(self, &"perform_edit_terrain", selected_entry, popup.terrain_name, popup.terrain_color, popup.terrain_type, popup.terrain_categories, {path = popup.terrain_icon})
- undo_manager.add_undo_method(self, &"perform_edit_terrain", selected_entry, t.name, t.color, t.type, t.categories, t.icon)
- if t.type != popup.terrain_type:
- terrain_undo.create_terrain_type_restore_point(undo_manager, tileset)
- terrain_undo.create_peering_restore_point_specific(undo_manager, tileset, selected_entry)
- undo_manager.commit_action()
- popup.queue_free()
- func _on_pick_icon_pressed():
- if selected_entry < 0:
- return
- tile_view.pick_icon_terrain = selected_entry
- func _on_pick_icon_focus_exited():
- tile_view.pick_icon_terrain_cancel = true
- pick_icon_button.button_pressed = false
- func _on_move_pressed(down: bool) -> void:
- if !tileset:
- return
-
- if selected_entry < 0:
- return
-
- var index1 = selected_entry
- var index2 = index1 + (1 if down else -1)
- if index2 < 0 or index2 >= terrain_list.get_child_count():
- return
-
- undo_manager.create_action("Reorder terrains", UndoRedo.MERGE_DISABLE, tileset)
- undo_manager.add_do_method(self, &"perform_swap_terrain", index1, index2)
- undo_manager.add_undo_method(self, &"perform_swap_terrain", index1, index2)
- undo_manager.commit_action()
- func _on_remove_terrain_pressed() -> void:
- if !tileset:
- return
-
- if selected_entry < 0:
- return
-
- # store confirmation in array to pass by ref
- var t := BetterTerrain.get_terrain(tileset, selected_entry)
- var confirmed := [false]
- var popup := ConfirmationDialog.new()
- popup.dialog_text = tr("Are you sure you want to remove {0}?").format([t.name])
- popup.dialog_hide_on_ok = false
- popup.confirmed.connect(func():
- confirmed[0] = true
- popup.hide()
- )
- EditorInterface.popup_dialog_centered(popup)
- await popup.visibility_changed
- popup.queue_free()
-
- if confirmed[0]:
- undo_manager.create_action("Remove terrain type", UndoRedo.MERGE_DISABLE, tileset)
- undo_manager.add_do_method(self, &"perform_remove_terrain", selected_entry)
- undo_manager.add_undo_method(self, &"perform_add_terrain", t.name, t.color, t.type, t.categories, t.icon)
- for n in range(terrain_list.get_child_count() - 2, selected_entry, -1):
- undo_manager.add_undo_method(self, &"perform_swap_terrain", n, n - 1)
- if t.type == BetterTerrain.TerrainType.CATEGORY:
- terrain_undo.create_terrain_type_restore_point(undo_manager, tileset)
- terrain_undo.create_peering_restore_point_specific(undo_manager, tileset, selected_entry)
- undo_manager.commit_action()
- func add_terrain_entry(terrain:Dictionary, index:int = -1):
- if index < 0:
- index = terrain_list.get_child_count()
-
- var entry = TERRAIN_ENTRY_SCENE.instantiate()
- entry.tileset = tileset
- entry.terrain = terrain
- entry.grid_mode = grid_mode_button.button_pressed
- entry.select.connect(_on_entry_select)
-
- terrain_list.add_child(entry)
- terrain_list.move_child(entry, index)
- func remove_terrain_entry(index: int):
- terrain_list.get_child(index).free()
- for i in range(index, terrain_list.get_child_count()):
- var child = terrain_list.get_child(i)
- child.terrain = BetterTerrain.get_terrain(tileset, i)
- child.update()
- func perform_add_terrain(name: String, color: Color, type: int, categories: Array, icon:Dictionary = {}) -> void:
- if BetterTerrain.add_terrain(tileset, name, color, type, categories, icon):
- var index = BetterTerrain.terrain_count(tileset) - 1
- var terrain = BetterTerrain.get_terrain(tileset, index)
- add_terrain_entry(terrain, index)
- func perform_remove_terrain(index: int) -> void:
- if index >= BetterTerrain.terrain_count(tileset):
- return
- if BetterTerrain.remove_terrain(tileset, index):
- remove_terrain_entry(index)
- update_tile_view_paint()
- func perform_swap_terrain(index1: int, index2: int) -> void:
- var lower := min(index1, index2)
- var higher := max(index1, index2)
- if lower >= terrain_list.get_child_count() or higher >= terrain_list.get_child_count():
- return
- var item1 = terrain_list.get_child(lower)
- var item2 = terrain_list.get_child(higher)
- if BetterTerrain.swap_terrains(tileset, lower, higher):
- terrain_list.move_child(item1, higher)
- item1.terrain = BetterTerrain.get_terrain(tileset, higher)
- item1.update()
- item2.terrain = BetterTerrain.get_terrain(tileset, lower)
- item2.update()
- selected_entry = index2
- terrain_list.get_child(index2).set_selected(true)
- update_tile_view_paint()
- func perform_edit_terrain(index: int, name: String, color: Color, type: int, categories: Array, icon: Dictionary = {}) -> void:
- if index >= terrain_list.get_child_count():
- return
- var entry = terrain_list.get_child(index)
- # don't overwrite empty icon
- var valid_icon = icon
- if icon.has("path") and icon.path.is_empty():
- var terrain = BetterTerrain.get_terrain(tileset, index)
- valid_icon = terrain.icon
- if BetterTerrain.set_terrain(tileset, index, name, color, type, categories, valid_icon):
- entry.terrain = BetterTerrain.get_terrain(tileset, index)
- entry.update()
- tile_view.queue_redraw()
- func _on_shuffle_random_pressed():
- BetterTerrain.use_seed = !shuffle_random.button_pressed
- func _on_bit_button_pressed(button: BaseButton) -> void:
- match select_tiles.button_group.get_pressed_button():
- select_tiles: tile_view.paint_mode = tile_view.PaintMode.SELECT
- paint_type: tile_view.paint_mode = tile_view.PaintMode.PAINT_TYPE
- paint_terrain: tile_view.paint_mode = tile_view.PaintMode.PAINT_PEERING
- paint_symmetry: tile_view.paint_mode = tile_view.PaintMode.PAINT_SYMMETRY
- _: tile_view.paint_mode = tile_view.PaintMode.NO_PAINT
- tile_view.queue_redraw()
-
- symmetry_options.visible = paint_symmetry.button_pressed
- func _on_symmetry_selected(index):
- tile_view.paint_symmetry = index
- func _on_paste_occurred():
- select_tiles.button_pressed = true
- func _on_change_zoom_level(value):
- zoom_slider.value = value
- func _on_terrain_updated(index):
- var entry = terrain_list.get_child(index)
- entry.terrain = BetterTerrain.get_terrain(tileset, index)
- entry.update()
- func canvas_tilemap_transform() -> Transform2D:
- var transform := tilemap.get_viewport_transform() * tilemap.global_transform
-
- # Handle subviewport
- var editor_viewport := EditorInterface.get_editor_viewport_2d()
- if tilemap.get_viewport() != editor_viewport:
- var container = tilemap.get_viewport().get_parent() as SubViewportContainer
- if container:
- transform = editor_viewport.global_canvas_transform * container.get_transform() * transform
-
- return transform
- func canvas_draw(overlay: Control) -> void:
- if !draw_overlay:
- return
-
- if selected_entry < 0:
- return
-
- var type = selected_entry
- var terrain := BetterTerrain.get_terrain(tileset, type)
- if !terrain.valid:
- return
-
- var tiles := []
- var transform := canvas_tilemap_transform()
-
- if paint_action == PaintAction.RECT and paint_mode != PaintMode.NO_PAINT:
- var area := Rect2i(initial_click, current_position - initial_click).abs()
- # Shortcut fill for large areas
- if area.size.x > 1 and area.size.y > 1 and area.size.x * area.size.y > MAX_CANVAS_RENDER_TILES:
- var shortcut := PackedVector2Array([
- tilemap.map_to_local(area.position),
- tilemap.map_to_local(Vector2i(area.end.x, area.position.y)),
- tilemap.map_to_local(area.end),
- tilemap.map_to_local(Vector2i(area.position.x, area.end.y))
- ])
- overlay.draw_colored_polygon(transform * shortcut, Color(terrain.color, 0.5))
- return
-
- for y in range(area.position.y, area.end.y + 1):
- for x in range(area.position.x, area.end.x + 1):
- tiles.append(Vector2i(x, y))
- elif paint_action == PaintAction.LINE and paint_mode != PaintMode.NO_PAINT:
- var cells := _get_tileset_line(initial_click, current_position, tileset)
- var shape = BetterTerrain.data.cell_polygon(tileset)
- for c in cells:
- var tile_transform := Transform2D(0.0, tilemap.tile_set.tile_size, 0.0, tilemap.map_to_local(c))
- overlay.draw_colored_polygon(transform * tile_transform * shape, Color(terrain.color, 0.5))
- elif fill_button.button_pressed:
- tiles = _get_fill_cells(current_position)
- if tiles.size() > MAX_CANVAS_RENDER_TILES:
- tiles.resize(MAX_CANVAS_RENDER_TILES)
- else:
- tiles.append(current_position)
-
- var shape = BetterTerrain.data.cell_polygon(tileset)
- for t in tiles:
- var tile_transform := Transform2D(0.0, tilemap.tile_set.tile_size, 0.0, tilemap.map_to_local(t))
- overlay.draw_colored_polygon(transform * tile_transform * shape, Color(terrain.color, 0.5))
- func canvas_input(event: InputEvent) -> bool:
- if selected_entry < 0:
- return false
-
- draw_overlay = true
- if event is InputEventMouseMotion:
- var tr := canvas_tilemap_transform()
- var pos := tr.affine_inverse() * Vector2(event.position)
- var event_position := tilemap.local_to_map(pos)
- prev_position = current_position
- if event_position == current_position:
- return false
- current_position = event_position
- update_overlay.emit()
-
- var replace_mode = replace_button.button_pressed
-
- var released : bool = event is InputEventMouseButton and !event.pressed
- if released:
- terrain_undo.finish_action()
- var type = selected_entry
- if paint_action == PaintAction.RECT and paint_mode != PaintMode.NO_PAINT:
- var area := Rect2i(initial_click, current_position - initial_click).abs()
- # Fill from initial_target to target
- undo_manager.create_action(tr("Draw terrain rectangle"), UndoRedo.MERGE_DISABLE, tilemap)
- for y in range(area.position.y, area.end.y + 1):
- for x in range(area.position.x, area.end.x + 1):
- var coord := Vector2i(x, y)
- if paint_mode == PaintMode.PAINT:
- if replace_mode:
- undo_manager.add_do_method(BetterTerrain, &"replace_cell", tilemap, coord, type)
- else:
- undo_manager.add_do_method(BetterTerrain, &"set_cell", tilemap, coord, type)
- else:
- undo_manager.add_do_method(tilemap, &"erase_cell", coord)
-
- undo_manager.add_do_method(BetterTerrain, &"update_terrain_area", tilemap, area)
- terrain_undo.create_tile_restore_point_area(undo_manager, tilemap, area)
- undo_manager.commit_action()
- update_overlay.emit()
- elif paint_action == PaintAction.LINE and paint_mode != PaintMode.NO_PAINT:
- undo_manager.create_action(tr("Draw terrain line"), UndoRedo.MERGE_DISABLE, tilemap)
- var cells := _get_tileset_line(initial_click, current_position, tileset)
- if paint_mode == PaintMode.PAINT:
- if replace_mode:
- undo_manager.add_do_method(BetterTerrain, &"replace_cells", tilemap, cells, type)
- else:
- undo_manager.add_do_method(BetterTerrain, &"set_cells", tilemap, cells, type)
- elif paint_mode == PaintMode.ERASE:
- for c in cells:
- undo_manager.add_do_method(tilemap, &"erase_cell", c)
- undo_manager.add_do_method(BetterTerrain, &"update_terrain_cells", tilemap, cells)
- terrain_undo.create_tile_restore_point(undo_manager, tilemap, cells)
- undo_manager.commit_action()
- update_overlay.emit()
-
- paint_mode = PaintMode.NO_PAINT
- return true
-
- var clicked : bool = event is InputEventMouseButton and event.pressed
- if clicked:
- paint_mode = PaintMode.NO_PAINT
-
- if (event.is_command_or_control_pressed() and !event.shift_pressed):
- var pick = BetterTerrain.get_cell(tilemap, current_position)
- if pick >= 0:
- terrain_list.get_children()[pick]._on_focus_entered()
- #_on_entry_select(pick)
- return true
-
- paint_action = PaintAction.NO_ACTION
- if rectangle_button.button_pressed:
- paint_action = PaintAction.RECT
- elif line_button.button_pressed:
- paint_action = PaintAction.LINE
- elif draw_button.button_pressed:
- if event.shift_pressed:
- paint_action = PaintAction.LINE
- if event.is_command_or_control_pressed():
- paint_action = PaintAction.RECT
-
- if event.button_index == MOUSE_BUTTON_LEFT:
- paint_mode = PaintMode.PAINT
- elif event.button_index == MOUSE_BUTTON_RIGHT:
- paint_mode = PaintMode.ERASE
- else:
- return false
-
- if (clicked or event is InputEventMouseMotion) and paint_mode != PaintMode.NO_PAINT:
- if clicked:
- initial_click = current_position
- terrain_undo.action_index += 1
- terrain_undo.action_count = 0
- var type = selected_entry
-
- if paint_action == PaintAction.LINE or paint_action == PaintAction.RECT:
- # if painting as line, execution happens on release.
- # prevent other painting actions from running.
- pass
- elif draw_button.button_pressed:
- undo_manager.create_action(tr("Draw terrain") + str(terrain_undo.action_index), UndoRedo.MERGE_ALL, tilemap, true)
- var cells := _get_tileset_line(prev_position, current_position, tileset)
- if paint_mode == PaintMode.PAINT:
- if replace_mode:
- terrain_undo.add_do_method(undo_manager, BetterTerrain, &"replace_cells", [tilemap, cells, type])
- else:
- terrain_undo.add_do_method(undo_manager, BetterTerrain, &"set_cells", [tilemap, cells, type])
- elif paint_mode == PaintMode.ERASE:
- for c in cells:
- terrain_undo.add_do_method(undo_manager, tilemap, &"erase_cell", [c])
- terrain_undo.add_do_method(undo_manager, BetterTerrain, &"update_terrain_cells", [tilemap, cells])
- terrain_undo.create_tile_restore_point(undo_manager, tilemap, cells)
- undo_manager.commit_action()
- terrain_undo.action_count += 1
- elif fill_button.button_pressed:
- var cells := _get_fill_cells(current_position)
- undo_manager.create_action(tr("Fill terrain"), UndoRedo.MERGE_DISABLE, tilemap)
- if paint_mode == PaintMode.PAINT:
- if replace_mode:
- undo_manager.add_do_method(BetterTerrain, &"replace_cells", tilemap, cells, type)
- else:
- undo_manager.add_do_method(BetterTerrain, &"set_cells", tilemap, cells, type)
- elif paint_mode == PaintMode.ERASE:
- for c in cells:
- undo_manager.add_do_method(tilemap, &"erase_cell", c)
- undo_manager.add_do_method(BetterTerrain, &"update_terrain_cells", tilemap, cells)
- terrain_undo.create_tile_restore_point(undo_manager, tilemap, cells)
- undo_manager.commit_action()
-
- update_overlay.emit()
- return true
-
- return false
- func canvas_mouse_exit() -> void:
- draw_overlay = false
- update_overlay.emit()
- func _shortcut_input(event) -> void:
- if event is InputEventKey:
- if event.keycode == KEY_C and (event.is_command_or_control_pressed() and not event.echo):
- get_viewport().set_input_as_handled()
- tile_view.copy_selection()
- if event.keycode == KEY_V and (event.is_command_or_control_pressed() and not event.echo):
- get_viewport().set_input_as_handled()
- tile_view.paste_selection()
- ## bresenham alg ported from Geometry2D::bresenham_line()
- func _get_line(from:Vector2i, to:Vector2i) -> Array[Vector2i]:
- if from == to:
- return [to]
-
- var points:Array[Vector2i] = []
- var delta := (to - from).abs() * 2
- var step := (to - from).sign()
- var current := from
-
- if delta.x > delta.y:
- var err:int = delta.x / 2
- while current.x != to.x:
- points.push_back(current);
- err -= delta.y
- if err < 0:
- current.y += step.y
- err += delta.x
- current.x += step.x
- else:
- var err:int = delta.y / 2
- while current.y != to.y:
- points.push_back(current)
- err -= delta.x
- if err < 0:
- current.x += step.x
- err += delta.y
- current.y += step.y
-
- points.push_back(current);
- return points;
- ## half-offset bresenham alg ported from TileMapEditor::get_line
- func _get_tileset_line(from:Vector2i, to:Vector2i, tileset:TileSet) -> Array[Vector2i]:
- if tileset.tile_shape == TileSet.TILE_SHAPE_SQUARE:
- return _get_line(from, to)
-
- var points:Array[Vector2i] = []
-
- var transposed := tileset.get_tile_offset_axis() == TileSet.TILE_OFFSET_AXIS_VERTICAL
- if transposed:
- from = Vector2i(from.y, from.x)
- to = Vector2i(to.y, to.x)
- var delta:Vector2i = to - from
- delta = Vector2i(2 * delta.x + abs(posmod(to.y, 2)) - abs(posmod(from.y, 2)), delta.y)
- var sign:Vector2i = delta.sign()
- var current := from;
- points.push_back(Vector2i(current.y, current.x) if transposed else current)
- var err := 0
- if abs(delta.y) < abs(delta.x):
- var err_step:Vector2i = 3 * delta.abs()
- while current != to:
- err += err_step.y
- if err > abs(delta.x):
- if sign.x == 0:
- current += Vector2i(sign.y, 0)
- else:
- current += Vector2i(sign.x if bool(current.y % 2) != (sign.x < 0) else 0, sign.y)
- err -= err_step.x
- else:
- current += Vector2i(sign.x, 0)
- err += err_step.y
- points.push_back(Vector2i(current.y, current.x) if transposed else current)
- else:
- var err_step:Vector2i = delta.abs()
- while current != to:
- err += err_step.x
- if err > 0:
- if sign.x == 0:
- current += Vector2i(0, sign.y)
- else:
- current += Vector2i(sign.x if bool(current.y % 2) != (sign.x < 0) else 0, sign.y)
- err -= err_step.y;
- else:
- if sign.x == 0:
- current += Vector2i(0, sign.y)
- else:
- current += Vector2i(-sign.x if bool(current.y % 2) != (sign.x > 0) else 0, sign.y)
- err += err_step.y
- points.push_back(Vector2i(current.y, current.x) if transposed else current)
-
- return points
- func _on_terrain_enable_id_pressed(id):
- if id in [SourceSelectors.ALL, SourceSelectors.NONE]:
- for i in source_selector_popup.item_count:
- if source_selector_popup.is_item_checkable(i):
- source_selector_popup.set_item_checked(i, id == SourceSelectors.ALL)
- else:
- var index = source_selector_popup.get_item_index(id)
- var checked = source_selector_popup.is_item_checked(index)
- source_selector_popup.set_item_checked(index, !checked)
-
- var disabled_sources : Array[int]
- for i in source_selector_popup.item_count:
- if source_selector_popup.is_item_checkable(i) and !source_selector_popup.is_item_checked(i):
- disabled_sources.append(source_selector_popup.get_item_id(i))
- tile_view.disabled_sources = disabled_sources
- func corresponding_tilemap_editor_button(similar: Button) -> Button:
- var editors = EditorInterface.get_base_control().find_children("*", "TileMapLayerEditor", true, false)
- var tile_map_layer_editor = editors[0]
- var buttons = tile_map_layer_editor.find_children("*", "Button", true, false)
- for button: Button in buttons:
- if button.icon == similar.icon:
- return button
- return null
- func _on_layer_up_or_down_pressed(button: Button) -> void:
- var matching_button = corresponding_tilemap_editor_button(button)
- if !matching_button:
- return
-
- # Major hack, to reduce flicker hide the tileset editor briefly
- var editors = EditorInterface.get_base_control().find_children("*", "TileSetEditor", true, false)
- var tile_set_editor = editors[0]
-
- matching_button.pressed.emit()
- tile_set_editor.modulate = Color.TRANSPARENT
- await get_tree().process_frame
- await get_tree().process_frame
- force_show_terrains.emit()
- tile_set_editor.modulate = Color.WHITE
- func _on_layer_up_pressed() -> void:
- _on_layer_up_or_down_pressed(layer_up)
- func _on_layer_down_pressed() -> void:
- _on_layer_up_or_down_pressed(layer_down)
- func _on_layer_highlight_toggled(toggled: bool) -> void:
- var settings = EditorInterface.get_editor_settings()
- settings.set_setting("editors/tiles_editor/highlight_selected_layer", toggled)
-
- var highlight = corresponding_tilemap_editor_button(layer_highlight)
- if highlight:
- highlight.toggled.emit(toggled)
- func _on_layer_grid_toggled(toggled: bool) -> void:
- var settings = EditorInterface.get_editor_settings()
- settings.set_setting("editors/tiles_editor/display_grid", toggled)
-
- var grid = corresponding_tilemap_editor_button(layer_grid)
- if grid:
- grid.toggled.emit(toggled)
|