Dock.gd 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939
  1. @tool
  2. extends Control
  3. signal update_overlay
  4. signal force_show_terrains
  5. # The maximum individual tiles the overlay will draw before shortcutting the display
  6. # To prevent editor lag when drawing large rectangles or filling large areas
  7. const MAX_CANVAS_RENDER_TILES = 1500
  8. const TERRAIN_PROPERTIES_SCENE := preload("res://addons/better-terrain/editor/TerrainProperties.tscn")
  9. const TERRAIN_ENTRY_SCENE := preload("res://addons/better-terrain/editor/TerrainEntry.tscn")
  10. const MIN_ZOOM_SETTING := "editor/better_terrain/min_zoom_amount"
  11. const MAX_ZOOM_SETTING := "editor/better_terrain/max_zoom_amount"
  12. # Buttons
  13. @onready var draw_button: Button = $VBox/Toolbar/Draw
  14. @onready var line_button: Button = $VBox/Toolbar/Line
  15. @onready var rectangle_button: Button = $VBox/Toolbar/Rectangle
  16. @onready var fill_button: Button = $VBox/Toolbar/Fill
  17. @onready var replace_button: Button = $VBox/Toolbar/Replace
  18. @onready var paint_type: Button = $VBox/Toolbar/PaintType
  19. @onready var paint_terrain: Button = $VBox/Toolbar/PaintTerrain
  20. @onready var select_tiles: Button = $VBox/Toolbar/SelectTiles
  21. @onready var paint_symmetry: Button = $VBox/Toolbar/PaintSymmetry
  22. @onready var symmetry_options: OptionButton = $VBox/Toolbar/SymmetryOptions
  23. @onready var shuffle_random: Button = $VBox/Toolbar/ShuffleRandom
  24. @onready var zoom_slider_container: VBoxContainer = $VBox/Toolbar/ZoomContainer
  25. @onready var source_selector: MenuBar = $VBox/Toolbar/Sources
  26. @onready var source_selector_popup: PopupMenu = $VBox/Toolbar/Sources/Sources
  27. @onready var clean_button: Button = $VBox/Toolbar/Clean
  28. @onready var layer_up: Button = $VBox/Toolbar/LayerUp
  29. @onready var layer_down: Button = $VBox/Toolbar/LayerDown
  30. @onready var layer_highlight: Button = $VBox/Toolbar/LayerHighlight
  31. @onready var layer_grid: Button = $VBox/Toolbar/LayerGrid
  32. @onready var grid_mode_button: Button = $VBox/HSplit/Terrains/LowerToolbar/GridMode
  33. @onready var quick_mode_button: Button = $VBox/HSplit/Terrains/LowerToolbar/QuickMode
  34. @onready var edit_tool_buttons: HBoxContainer = $VBox/HSplit/Terrains/LowerToolbar/EditTools
  35. @onready var add_terrain_button: Button = $VBox/HSplit/Terrains/LowerToolbar/EditTools/AddTerrain
  36. @onready var edit_terrain_button: Button = $VBox/HSplit/Terrains/LowerToolbar/EditTools/EditTerrain
  37. @onready var pick_icon_button: Button = $VBox/HSplit/Terrains/LowerToolbar/EditTools/PickIcon
  38. @onready var move_up_button: Button = $VBox/HSplit/Terrains/LowerToolbar/EditTools/MoveUp
  39. @onready var move_down_button: Button = $VBox/HSplit/Terrains/LowerToolbar/EditTools/MoveDown
  40. @onready var remove_terrain_button: Button = $VBox/HSplit/Terrains/LowerToolbar/EditTools/RemoveTerrain
  41. @onready var scroll_container: ScrollContainer = $VBox/HSplit/Terrains/Panel/ScrollContainer
  42. @onready var terrain_list: HFlowContainer = $VBox/HSplit/Terrains/Panel/ScrollContainer/TerrainList
  43. @onready var tile_view: Control = $VBox/HSplit/Panel/ScrollArea/TileView
  44. var selected_entry := -2
  45. var tilemap : TileMapLayer
  46. var tileset : TileSet
  47. var undo_manager : EditorUndoRedoManager
  48. var terrain_undo
  49. var draw_overlay := false
  50. var initial_click : Vector2i
  51. var prev_position : Vector2i
  52. var current_position : Vector2i
  53. var tileset_dirty := false
  54. var zoom_slider : HSlider
  55. enum PaintMode {
  56. NO_PAINT,
  57. PAINT,
  58. ERASE
  59. }
  60. enum PaintAction {
  61. NO_ACTION,
  62. LINE,
  63. RECT
  64. }
  65. enum SourceSelectors {
  66. ALL = 1000000,
  67. NONE = 1000001,
  68. }
  69. var paint_mode := PaintMode.NO_PAINT
  70. var paint_action := PaintAction.NO_ACTION
  71. # Called when the node enters the scene tree for the first time.
  72. func _ready() -> void:
  73. draw_button.icon = get_theme_icon("Edit", "EditorIcons")
  74. line_button.icon = get_theme_icon("Line", "EditorIcons")
  75. rectangle_button.icon = get_theme_icon("Rectangle", "EditorIcons")
  76. fill_button.icon = get_theme_icon("Bucket", "EditorIcons")
  77. select_tiles.icon = get_theme_icon("ToolSelect", "EditorIcons")
  78. add_terrain_button.icon = get_theme_icon("Add", "EditorIcons")
  79. edit_terrain_button.icon = get_theme_icon("Tools", "EditorIcons")
  80. pick_icon_button.icon = get_theme_icon("ColorPick", "EditorIcons")
  81. move_up_button.icon = get_theme_icon("ArrowUp", "EditorIcons")
  82. move_down_button.icon = get_theme_icon("ArrowDown", "EditorIcons")
  83. remove_terrain_button.icon = get_theme_icon("Remove", "EditorIcons")
  84. grid_mode_button.icon = get_theme_icon("FileThumbnail", "EditorIcons")
  85. quick_mode_button.icon = get_theme_icon("GuiVisibilityVisible", "EditorIcons")
  86. layer_up.icon = get_theme_icon("MoveUp", "EditorIcons")
  87. layer_down.icon = get_theme_icon("MoveDown", "EditorIcons")
  88. layer_highlight.icon = get_theme_icon("TileMapHighlightSelected", "EditorIcons")
  89. layer_grid.icon = get_theme_icon("Grid", "EditorIcons")
  90. select_tiles.button_group.pressed.connect(_on_bit_button_pressed)
  91. terrain_undo = load("res://addons/better-terrain/editor/TerrainUndo.gd").new()
  92. add_child(terrain_undo)
  93. tile_view.undo_manager = undo_manager
  94. tile_view.terrain_undo = terrain_undo
  95. tile_view.paste_occurred.connect(_on_paste_occurred)
  96. tile_view.change_zoom_level.connect(_on_change_zoom_level)
  97. tile_view.terrain_updated.connect(_on_terrain_updated)
  98. # Zoom slider is manipulated by settings, make it at runtime
  99. zoom_slider = HSlider.new()
  100. zoom_slider.custom_minimum_size = Vector2(100, 0)
  101. zoom_slider.value_changed.connect(tile_view._on_zoom_value_changed)
  102. zoom_slider_container.add_child(zoom_slider)
  103. # Init settings if needed
  104. if !ProjectSettings.has_setting(MIN_ZOOM_SETTING):
  105. ProjectSettings.set(MIN_ZOOM_SETTING, 1.0)
  106. ProjectSettings.add_property_info({
  107. "name": MIN_ZOOM_SETTING,
  108. "type": TYPE_FLOAT,
  109. "hint": PROPERTY_HINT_RANGE,
  110. "hint_string": "0.1,1.0,0.1"
  111. })
  112. ProjectSettings.set_initial_value(MIN_ZOOM_SETTING, 1.0)
  113. ProjectSettings.set_as_basic(MIN_ZOOM_SETTING, true)
  114. if !ProjectSettings.has_setting(MAX_ZOOM_SETTING):
  115. ProjectSettings.set(MAX_ZOOM_SETTING, 8.0)
  116. ProjectSettings.add_property_info({
  117. "name": MAX_ZOOM_SETTING,
  118. "type": TYPE_FLOAT,
  119. "hint": PROPERTY_HINT_RANGE,
  120. "hint_string": "2.0,32.0,1.0"
  121. })
  122. ProjectSettings.set_initial_value(MAX_ZOOM_SETTING, 8.0)
  123. ProjectSettings.set_as_basic(MAX_ZOOM_SETTING, true)
  124. ProjectSettings.set_order(MAX_ZOOM_SETTING, ProjectSettings.get_order(MIN_ZOOM_SETTING) + 1)
  125. ProjectSettings.settings_changed.connect(_on_adjust_settings)
  126. _on_adjust_settings()
  127. zoom_slider.value = 1.0
  128. func _process(delta):
  129. scroll_container.scroll_horizontal = 0
  130. func _on_adjust_settings():
  131. zoom_slider.min_value = ProjectSettings.get_setting(MIN_ZOOM_SETTING, 1.0)
  132. zoom_slider.max_value = ProjectSettings.get_setting(MAX_ZOOM_SETTING, 8.0)
  133. zoom_slider.step = (zoom_slider.max_value - zoom_slider.min_value) / 100.0
  134. func _get_fill_cells(target: Vector2i) -> Array:
  135. var pick := BetterTerrain.get_cell(tilemap, target)
  136. var bounds := tilemap.get_used_rect()
  137. var neighbors = BetterTerrain.data.cells_adjacent_for_fill(tileset)
  138. # No sets yet, so use a dictionary
  139. var checked := {}
  140. var pending := [target]
  141. var goal := []
  142. while !pending.is_empty():
  143. var p = pending.pop_front()
  144. if checked.has(p):
  145. continue
  146. checked[p] = true
  147. if !bounds.has_point(p) or BetterTerrain.get_cell(tilemap, p) != pick:
  148. continue
  149. goal.append(p)
  150. pending.append_array(BetterTerrain.data.neighboring_coords(tilemap, p, neighbors))
  151. return goal
  152. func tiles_about_to_change() -> void:
  153. if tileset and tileset.changed.is_connected(queue_tiles_changed):
  154. tileset.changed.disconnect(queue_tiles_changed)
  155. func tiles_changed() -> void:
  156. # ensure up to date
  157. BetterTerrain._update_terrain_data(tileset)
  158. # clear terrains
  159. for c in terrain_list.get_children():
  160. terrain_list.remove_child(c)
  161. c.queue_free()
  162. # load terrains from tileset
  163. var terrain_count := BetterTerrain.terrain_count(tileset)
  164. var item_count = terrain_count + 1
  165. for i in terrain_count:
  166. var terrain := BetterTerrain.get_terrain(tileset, i)
  167. if i >= terrain_list.get_child_count():
  168. add_terrain_entry(terrain, i)
  169. if item_count > terrain_list.get_child_count():
  170. var terrain := BetterTerrain.get_terrain(tileset, BetterTerrain.TileCategory.EMPTY)
  171. if terrain.valid:
  172. add_terrain_entry(terrain, item_count - 1)
  173. while item_count < terrain_list.get_child_count():
  174. var child = terrain_list.get_child(terrain_list.get_child_count() - 1)
  175. terrain_list.remove_child(child)
  176. child.free()
  177. source_selector_popup.clear()
  178. source_selector_popup.add_item("All", SourceSelectors.ALL)
  179. source_selector_popup.add_item("None", SourceSelectors.NONE)
  180. var source_count = tileset.get_source_count() if tileset else 0
  181. for s in source_count:
  182. var source_id = tileset.get_source_id(s)
  183. var source := tileset.get_source(source_id)
  184. if !(source is TileSetAtlasSource):
  185. continue
  186. var name := source.resource_name
  187. if name.is_empty():
  188. var texture := (source as TileSetAtlasSource).texture
  189. var texture_name := texture.resource_name if texture else ""
  190. if !texture_name.is_empty():
  191. name = texture_name
  192. else:
  193. var texture_path := texture.resource_path if texture else ""
  194. if !texture_path.is_empty():
  195. name = texture_path.get_file()
  196. if !name.is_empty():
  197. name += " "
  198. name += " (ID: %d)" % source_id
  199. source_selector_popup.add_check_item(name, source_id)
  200. source_selector_popup.set_item_checked(source_selector_popup.get_item_index(source_id), true)
  201. source_selector.visible = source_selector_popup.item_count > 3 # All, None and more than one source
  202. update_tile_view_paint()
  203. tile_view.refresh_tileset(tileset)
  204. if tileset and !tileset.changed.is_connected(queue_tiles_changed):
  205. tileset.changed.connect(queue_tiles_changed)
  206. clean_button.visible = BetterTerrain._has_invalid_peering_types(tileset)
  207. tileset_dirty = false
  208. _on_grid_mode_pressed()
  209. _on_quick_mode_pressed()
  210. func about_to_be_visible(visible: bool) -> void:
  211. if !visible:
  212. return
  213. if tileset != tilemap.tile_set:
  214. tiles_about_to_change()
  215. tileset = tilemap.tile_set
  216. tiles_changed()
  217. var settings := EditorInterface.get_editor_settings()
  218. layer_highlight.set_pressed_no_signal(settings.get_setting("editors/tiles_editor/highlight_selected_layer"))
  219. layer_grid.set_pressed_no_signal(settings.get_setting("editors/tiles_editor/display_grid"))
  220. func queue_tiles_changed() -> void:
  221. # Bring terrain data up to date with complex tileset changes
  222. if !tileset or tileset_dirty:
  223. return
  224. tileset_dirty = true
  225. tiles_changed.call_deferred()
  226. func _on_entry_select(index:int):
  227. selected_entry = index
  228. if selected_entry >= BetterTerrain.terrain_count(tileset):
  229. selected_entry = BetterTerrain.TileCategory.EMPTY
  230. for i in range(terrain_list.get_child_count()):
  231. if i != index:
  232. terrain_list.get_child(i).set_selected(false)
  233. update_tile_view_paint()
  234. func _on_clean_pressed() -> void:
  235. var confirmed := [false]
  236. var popup := ConfirmationDialog.new()
  237. popup.dialog_text = tr("Tile set changes have caused terrain to become invalid. Remove invalid terrain data?")
  238. popup.dialog_hide_on_ok = false
  239. popup.confirmed.connect(func():
  240. confirmed[0] = true
  241. popup.hide()
  242. )
  243. EditorInterface.popup_dialog_centered(popup)
  244. await popup.visibility_changed
  245. popup.queue_free()
  246. if confirmed[0]:
  247. undo_manager.create_action("Clean invalid terrain peering data", UndoRedo.MERGE_DISABLE, tileset)
  248. undo_manager.add_do_method(BetterTerrain, &"_clear_invalid_peering_types", tileset)
  249. undo_manager.add_do_method(self, &"tiles_changed")
  250. terrain_undo.create_peering_restore_point(undo_manager, tileset)
  251. undo_manager.add_undo_method(self, &"tiles_changed")
  252. undo_manager.commit_action()
  253. func _on_grid_mode_pressed() -> void:
  254. for c in terrain_list.get_children():
  255. c.grid_mode = grid_mode_button.button_pressed
  256. c.update_style()
  257. func _on_quick_mode_pressed() -> void:
  258. edit_tool_buttons.visible = !quick_mode_button.button_pressed
  259. for c in terrain_list.get_children():
  260. c.visible = !quick_mode_button.button_pressed or c.terrain.type in [BetterTerrain.TerrainType.MATCH_TILES, BetterTerrain.TerrainType.MATCH_VERTICES]
  261. func update_tile_view_paint() -> void:
  262. tile_view.paint = selected_entry
  263. tile_view.queue_redraw()
  264. var editable = tile_view.paint != BetterTerrain.TileCategory.EMPTY
  265. edit_terrain_button.disabled = !editable
  266. move_up_button.disabled = !editable or tile_view.paint == 0
  267. move_down_button.disabled = !editable or tile_view.paint == BetterTerrain.terrain_count(tileset) - 1
  268. remove_terrain_button.disabled = !editable
  269. pick_icon_button.disabled = !editable
  270. func _on_add_terrain_pressed() -> void:
  271. if !tileset:
  272. return
  273. var popup := TERRAIN_PROPERTIES_SCENE.instantiate()
  274. popup.set_category_data(BetterTerrain.get_terrain_categories(tileset))
  275. popup.terrain_name = "New terrain"
  276. popup.terrain_color = Color.from_hsv(randf(), 0.3 + 0.7 * randf(), 0.6 + 0.4 * randf())
  277. popup.terrain_icon = ""
  278. popup.terrain_type = 0
  279. EditorInterface.popup_dialog_centered(popup)
  280. await popup.visibility_changed
  281. if popup.accepted:
  282. undo_manager.create_action("Add terrain type", UndoRedo.MERGE_DISABLE, tileset)
  283. 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})
  284. undo_manager.add_undo_method(self, &"perform_remove_terrain", terrain_list.get_child_count() - 1)
  285. undo_manager.commit_action()
  286. popup.queue_free()
  287. func _on_edit_terrain_pressed() -> void:
  288. if !tileset:
  289. return
  290. if selected_entry < 0:
  291. return
  292. var t := BetterTerrain.get_terrain(tileset, selected_entry)
  293. var categories = BetterTerrain.get_terrain_categories(tileset)
  294. categories = categories.filter(func(x): return x.id != selected_entry)
  295. var popup := TERRAIN_PROPERTIES_SCENE.instantiate()
  296. popup.set_category_data(categories)
  297. t.icon = t.icon.duplicate()
  298. popup.terrain_name = t.name
  299. popup.terrain_type = t.type
  300. popup.terrain_color = t.color
  301. if t.has("icon") and t.icon.has("path"):
  302. popup.terrain_icon = t.icon.path
  303. popup.terrain_categories = t.categories
  304. EditorInterface.popup_dialog_centered(popup)
  305. await popup.visibility_changed
  306. if popup.accepted:
  307. undo_manager.create_action("Edit terrain details", UndoRedo.MERGE_DISABLE, tileset)
  308. 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})
  309. undo_manager.add_undo_method(self, &"perform_edit_terrain", selected_entry, t.name, t.color, t.type, t.categories, t.icon)
  310. if t.type != popup.terrain_type:
  311. terrain_undo.create_terrain_type_restore_point(undo_manager, tileset)
  312. terrain_undo.create_peering_restore_point_specific(undo_manager, tileset, selected_entry)
  313. undo_manager.commit_action()
  314. popup.queue_free()
  315. func _on_pick_icon_pressed():
  316. if selected_entry < 0:
  317. return
  318. tile_view.pick_icon_terrain = selected_entry
  319. func _on_pick_icon_focus_exited():
  320. tile_view.pick_icon_terrain_cancel = true
  321. pick_icon_button.button_pressed = false
  322. func _on_move_pressed(down: bool) -> void:
  323. if !tileset:
  324. return
  325. if selected_entry < 0:
  326. return
  327. var index1 = selected_entry
  328. var index2 = index1 + (1 if down else -1)
  329. if index2 < 0 or index2 >= terrain_list.get_child_count():
  330. return
  331. undo_manager.create_action("Reorder terrains", UndoRedo.MERGE_DISABLE, tileset)
  332. undo_manager.add_do_method(self, &"perform_swap_terrain", index1, index2)
  333. undo_manager.add_undo_method(self, &"perform_swap_terrain", index1, index2)
  334. undo_manager.commit_action()
  335. func _on_remove_terrain_pressed() -> void:
  336. if !tileset:
  337. return
  338. if selected_entry < 0:
  339. return
  340. # store confirmation in array to pass by ref
  341. var t := BetterTerrain.get_terrain(tileset, selected_entry)
  342. var confirmed := [false]
  343. var popup := ConfirmationDialog.new()
  344. popup.dialog_text = tr("Are you sure you want to remove {0}?").format([t.name])
  345. popup.dialog_hide_on_ok = false
  346. popup.confirmed.connect(func():
  347. confirmed[0] = true
  348. popup.hide()
  349. )
  350. EditorInterface.popup_dialog_centered(popup)
  351. await popup.visibility_changed
  352. popup.queue_free()
  353. if confirmed[0]:
  354. undo_manager.create_action("Remove terrain type", UndoRedo.MERGE_DISABLE, tileset)
  355. undo_manager.add_do_method(self, &"perform_remove_terrain", selected_entry)
  356. undo_manager.add_undo_method(self, &"perform_add_terrain", t.name, t.color, t.type, t.categories, t.icon)
  357. for n in range(terrain_list.get_child_count() - 2, selected_entry, -1):
  358. undo_manager.add_undo_method(self, &"perform_swap_terrain", n, n - 1)
  359. if t.type == BetterTerrain.TerrainType.CATEGORY:
  360. terrain_undo.create_terrain_type_restore_point(undo_manager, tileset)
  361. terrain_undo.create_peering_restore_point_specific(undo_manager, tileset, selected_entry)
  362. undo_manager.commit_action()
  363. func add_terrain_entry(terrain:Dictionary, index:int = -1):
  364. if index < 0:
  365. index = terrain_list.get_child_count()
  366. var entry = TERRAIN_ENTRY_SCENE.instantiate()
  367. entry.tileset = tileset
  368. entry.terrain = terrain
  369. entry.grid_mode = grid_mode_button.button_pressed
  370. entry.select.connect(_on_entry_select)
  371. terrain_list.add_child(entry)
  372. terrain_list.move_child(entry, index)
  373. func remove_terrain_entry(index: int):
  374. terrain_list.get_child(index).free()
  375. for i in range(index, terrain_list.get_child_count()):
  376. var child = terrain_list.get_child(i)
  377. child.terrain = BetterTerrain.get_terrain(tileset, i)
  378. child.update()
  379. func perform_add_terrain(name: String, color: Color, type: int, categories: Array, icon:Dictionary = {}) -> void:
  380. if BetterTerrain.add_terrain(tileset, name, color, type, categories, icon):
  381. var index = BetterTerrain.terrain_count(tileset) - 1
  382. var terrain = BetterTerrain.get_terrain(tileset, index)
  383. add_terrain_entry(terrain, index)
  384. func perform_remove_terrain(index: int) -> void:
  385. if index >= BetterTerrain.terrain_count(tileset):
  386. return
  387. if BetterTerrain.remove_terrain(tileset, index):
  388. remove_terrain_entry(index)
  389. update_tile_view_paint()
  390. func perform_swap_terrain(index1: int, index2: int) -> void:
  391. var lower := min(index1, index2)
  392. var higher := max(index1, index2)
  393. if lower >= terrain_list.get_child_count() or higher >= terrain_list.get_child_count():
  394. return
  395. var item1 = terrain_list.get_child(lower)
  396. var item2 = terrain_list.get_child(higher)
  397. if BetterTerrain.swap_terrains(tileset, lower, higher):
  398. terrain_list.move_child(item1, higher)
  399. item1.terrain = BetterTerrain.get_terrain(tileset, higher)
  400. item1.update()
  401. item2.terrain = BetterTerrain.get_terrain(tileset, lower)
  402. item2.update()
  403. selected_entry = index2
  404. terrain_list.get_child(index2).set_selected(true)
  405. update_tile_view_paint()
  406. func perform_edit_terrain(index: int, name: String, color: Color, type: int, categories: Array, icon: Dictionary = {}) -> void:
  407. if index >= terrain_list.get_child_count():
  408. return
  409. var entry = terrain_list.get_child(index)
  410. # don't overwrite empty icon
  411. var valid_icon = icon
  412. if icon.has("path") and icon.path.is_empty():
  413. var terrain = BetterTerrain.get_terrain(tileset, index)
  414. valid_icon = terrain.icon
  415. if BetterTerrain.set_terrain(tileset, index, name, color, type, categories, valid_icon):
  416. entry.terrain = BetterTerrain.get_terrain(tileset, index)
  417. entry.update()
  418. tile_view.queue_redraw()
  419. func _on_shuffle_random_pressed():
  420. BetterTerrain.use_seed = !shuffle_random.button_pressed
  421. func _on_bit_button_pressed(button: BaseButton) -> void:
  422. match select_tiles.button_group.get_pressed_button():
  423. select_tiles: tile_view.paint_mode = tile_view.PaintMode.SELECT
  424. paint_type: tile_view.paint_mode = tile_view.PaintMode.PAINT_TYPE
  425. paint_terrain: tile_view.paint_mode = tile_view.PaintMode.PAINT_PEERING
  426. paint_symmetry: tile_view.paint_mode = tile_view.PaintMode.PAINT_SYMMETRY
  427. _: tile_view.paint_mode = tile_view.PaintMode.NO_PAINT
  428. tile_view.queue_redraw()
  429. symmetry_options.visible = paint_symmetry.button_pressed
  430. func _on_symmetry_selected(index):
  431. tile_view.paint_symmetry = index
  432. func _on_paste_occurred():
  433. select_tiles.button_pressed = true
  434. func _on_change_zoom_level(value):
  435. zoom_slider.value = value
  436. func _on_terrain_updated(index):
  437. var entry = terrain_list.get_child(index)
  438. entry.terrain = BetterTerrain.get_terrain(tileset, index)
  439. entry.update()
  440. func canvas_tilemap_transform() -> Transform2D:
  441. var transform := tilemap.get_viewport_transform() * tilemap.global_transform
  442. # Handle subviewport
  443. var editor_viewport := EditorInterface.get_editor_viewport_2d()
  444. if tilemap.get_viewport() != editor_viewport:
  445. var container = tilemap.get_viewport().get_parent() as SubViewportContainer
  446. if container:
  447. transform = editor_viewport.global_canvas_transform * container.get_transform() * transform
  448. return transform
  449. func canvas_draw(overlay: Control) -> void:
  450. if !draw_overlay:
  451. return
  452. if selected_entry < 0:
  453. return
  454. var type = selected_entry
  455. var terrain := BetterTerrain.get_terrain(tileset, type)
  456. if !terrain.valid:
  457. return
  458. var tiles := []
  459. var transform := canvas_tilemap_transform()
  460. if paint_action == PaintAction.RECT and paint_mode != PaintMode.NO_PAINT:
  461. var area := Rect2i(initial_click, current_position - initial_click).abs()
  462. # Shortcut fill for large areas
  463. if area.size.x > 1 and area.size.y > 1 and area.size.x * area.size.y > MAX_CANVAS_RENDER_TILES:
  464. var shortcut := PackedVector2Array([
  465. tilemap.map_to_local(area.position),
  466. tilemap.map_to_local(Vector2i(area.end.x, area.position.y)),
  467. tilemap.map_to_local(area.end),
  468. tilemap.map_to_local(Vector2i(area.position.x, area.end.y))
  469. ])
  470. overlay.draw_colored_polygon(transform * shortcut, Color(terrain.color, 0.5))
  471. return
  472. for y in range(area.position.y, area.end.y + 1):
  473. for x in range(area.position.x, area.end.x + 1):
  474. tiles.append(Vector2i(x, y))
  475. elif paint_action == PaintAction.LINE and paint_mode != PaintMode.NO_PAINT:
  476. var cells := _get_tileset_line(initial_click, current_position, tileset)
  477. var shape = BetterTerrain.data.cell_polygon(tileset)
  478. for c in cells:
  479. var tile_transform := Transform2D(0.0, tilemap.tile_set.tile_size, 0.0, tilemap.map_to_local(c))
  480. overlay.draw_colored_polygon(transform * tile_transform * shape, Color(terrain.color, 0.5))
  481. elif fill_button.button_pressed:
  482. tiles = _get_fill_cells(current_position)
  483. if tiles.size() > MAX_CANVAS_RENDER_TILES:
  484. tiles.resize(MAX_CANVAS_RENDER_TILES)
  485. else:
  486. tiles.append(current_position)
  487. var shape = BetterTerrain.data.cell_polygon(tileset)
  488. for t in tiles:
  489. var tile_transform := Transform2D(0.0, tilemap.tile_set.tile_size, 0.0, tilemap.map_to_local(t))
  490. overlay.draw_colored_polygon(transform * tile_transform * shape, Color(terrain.color, 0.5))
  491. func canvas_input(event: InputEvent) -> bool:
  492. if selected_entry < 0:
  493. return false
  494. draw_overlay = true
  495. if event is InputEventMouseMotion:
  496. var tr := canvas_tilemap_transform()
  497. var pos := tr.affine_inverse() * Vector2(event.position)
  498. var event_position := tilemap.local_to_map(pos)
  499. prev_position = current_position
  500. if event_position == current_position:
  501. return false
  502. current_position = event_position
  503. update_overlay.emit()
  504. var replace_mode = replace_button.button_pressed
  505. var released : bool = event is InputEventMouseButton and !event.pressed
  506. if released:
  507. terrain_undo.finish_action()
  508. var type = selected_entry
  509. if paint_action == PaintAction.RECT and paint_mode != PaintMode.NO_PAINT:
  510. var area := Rect2i(initial_click, current_position - initial_click).abs()
  511. # Fill from initial_target to target
  512. undo_manager.create_action(tr("Draw terrain rectangle"), UndoRedo.MERGE_DISABLE, tilemap)
  513. for y in range(area.position.y, area.end.y + 1):
  514. for x in range(area.position.x, area.end.x + 1):
  515. var coord := Vector2i(x, y)
  516. if paint_mode == PaintMode.PAINT:
  517. if replace_mode:
  518. undo_manager.add_do_method(BetterTerrain, &"replace_cell", tilemap, coord, type)
  519. else:
  520. undo_manager.add_do_method(BetterTerrain, &"set_cell", tilemap, coord, type)
  521. else:
  522. undo_manager.add_do_method(tilemap, &"erase_cell", coord)
  523. undo_manager.add_do_method(BetterTerrain, &"update_terrain_area", tilemap, area)
  524. terrain_undo.create_tile_restore_point_area(undo_manager, tilemap, area)
  525. undo_manager.commit_action()
  526. update_overlay.emit()
  527. elif paint_action == PaintAction.LINE and paint_mode != PaintMode.NO_PAINT:
  528. undo_manager.create_action(tr("Draw terrain line"), UndoRedo.MERGE_DISABLE, tilemap)
  529. var cells := _get_tileset_line(initial_click, current_position, tileset)
  530. if paint_mode == PaintMode.PAINT:
  531. if replace_mode:
  532. undo_manager.add_do_method(BetterTerrain, &"replace_cells", tilemap, cells, type)
  533. else:
  534. undo_manager.add_do_method(BetterTerrain, &"set_cells", tilemap, cells, type)
  535. elif paint_mode == PaintMode.ERASE:
  536. for c in cells:
  537. undo_manager.add_do_method(tilemap, &"erase_cell", c)
  538. undo_manager.add_do_method(BetterTerrain, &"update_terrain_cells", tilemap, cells)
  539. terrain_undo.create_tile_restore_point(undo_manager, tilemap, cells)
  540. undo_manager.commit_action()
  541. update_overlay.emit()
  542. paint_mode = PaintMode.NO_PAINT
  543. return true
  544. var clicked : bool = event is InputEventMouseButton and event.pressed
  545. if clicked:
  546. paint_mode = PaintMode.NO_PAINT
  547. if (event.is_command_or_control_pressed() and !event.shift_pressed):
  548. var pick = BetterTerrain.get_cell(tilemap, current_position)
  549. if pick >= 0:
  550. terrain_list.get_children()[pick]._on_focus_entered()
  551. #_on_entry_select(pick)
  552. return true
  553. paint_action = PaintAction.NO_ACTION
  554. if rectangle_button.button_pressed:
  555. paint_action = PaintAction.RECT
  556. elif line_button.button_pressed:
  557. paint_action = PaintAction.LINE
  558. elif draw_button.button_pressed:
  559. if event.shift_pressed:
  560. paint_action = PaintAction.LINE
  561. if event.is_command_or_control_pressed():
  562. paint_action = PaintAction.RECT
  563. if event.button_index == MOUSE_BUTTON_LEFT:
  564. paint_mode = PaintMode.PAINT
  565. elif event.button_index == MOUSE_BUTTON_RIGHT:
  566. paint_mode = PaintMode.ERASE
  567. else:
  568. return false
  569. if (clicked or event is InputEventMouseMotion) and paint_mode != PaintMode.NO_PAINT:
  570. if clicked:
  571. initial_click = current_position
  572. terrain_undo.action_index += 1
  573. terrain_undo.action_count = 0
  574. var type = selected_entry
  575. if paint_action == PaintAction.LINE or paint_action == PaintAction.RECT:
  576. # if painting as line, execution happens on release.
  577. # prevent other painting actions from running.
  578. pass
  579. elif draw_button.button_pressed:
  580. undo_manager.create_action(tr("Draw terrain") + str(terrain_undo.action_index), UndoRedo.MERGE_ALL, tilemap, true)
  581. var cells := _get_tileset_line(prev_position, current_position, tileset)
  582. if paint_mode == PaintMode.PAINT:
  583. if replace_mode:
  584. terrain_undo.add_do_method(undo_manager, BetterTerrain, &"replace_cells", [tilemap, cells, type])
  585. else:
  586. terrain_undo.add_do_method(undo_manager, BetterTerrain, &"set_cells", [tilemap, cells, type])
  587. elif paint_mode == PaintMode.ERASE:
  588. for c in cells:
  589. terrain_undo.add_do_method(undo_manager, tilemap, &"erase_cell", [c])
  590. terrain_undo.add_do_method(undo_manager, BetterTerrain, &"update_terrain_cells", [tilemap, cells])
  591. terrain_undo.create_tile_restore_point(undo_manager, tilemap, cells)
  592. undo_manager.commit_action()
  593. terrain_undo.action_count += 1
  594. elif fill_button.button_pressed:
  595. var cells := _get_fill_cells(current_position)
  596. undo_manager.create_action(tr("Fill terrain"), UndoRedo.MERGE_DISABLE, tilemap)
  597. if paint_mode == PaintMode.PAINT:
  598. if replace_mode:
  599. undo_manager.add_do_method(BetterTerrain, &"replace_cells", tilemap, cells, type)
  600. else:
  601. undo_manager.add_do_method(BetterTerrain, &"set_cells", tilemap, cells, type)
  602. elif paint_mode == PaintMode.ERASE:
  603. for c in cells:
  604. undo_manager.add_do_method(tilemap, &"erase_cell", c)
  605. undo_manager.add_do_method(BetterTerrain, &"update_terrain_cells", tilemap, cells)
  606. terrain_undo.create_tile_restore_point(undo_manager, tilemap, cells)
  607. undo_manager.commit_action()
  608. update_overlay.emit()
  609. return true
  610. return false
  611. func canvas_mouse_exit() -> void:
  612. draw_overlay = false
  613. update_overlay.emit()
  614. func _shortcut_input(event) -> void:
  615. if event is InputEventKey:
  616. if event.keycode == KEY_C and (event.is_command_or_control_pressed() and not event.echo):
  617. get_viewport().set_input_as_handled()
  618. tile_view.copy_selection()
  619. if event.keycode == KEY_V and (event.is_command_or_control_pressed() and not event.echo):
  620. get_viewport().set_input_as_handled()
  621. tile_view.paste_selection()
  622. ## bresenham alg ported from Geometry2D::bresenham_line()
  623. func _get_line(from:Vector2i, to:Vector2i) -> Array[Vector2i]:
  624. if from == to:
  625. return [to]
  626. var points:Array[Vector2i] = []
  627. var delta := (to - from).abs() * 2
  628. var step := (to - from).sign()
  629. var current := from
  630. if delta.x > delta.y:
  631. var err:int = delta.x / 2
  632. while current.x != to.x:
  633. points.push_back(current);
  634. err -= delta.y
  635. if err < 0:
  636. current.y += step.y
  637. err += delta.x
  638. current.x += step.x
  639. else:
  640. var err:int = delta.y / 2
  641. while current.y != to.y:
  642. points.push_back(current)
  643. err -= delta.x
  644. if err < 0:
  645. current.x += step.x
  646. err += delta.y
  647. current.y += step.y
  648. points.push_back(current);
  649. return points;
  650. ## half-offset bresenham alg ported from TileMapEditor::get_line
  651. func _get_tileset_line(from:Vector2i, to:Vector2i, tileset:TileSet) -> Array[Vector2i]:
  652. if tileset.tile_shape == TileSet.TILE_SHAPE_SQUARE:
  653. return _get_line(from, to)
  654. var points:Array[Vector2i] = []
  655. var transposed := tileset.get_tile_offset_axis() == TileSet.TILE_OFFSET_AXIS_VERTICAL
  656. if transposed:
  657. from = Vector2i(from.y, from.x)
  658. to = Vector2i(to.y, to.x)
  659. var delta:Vector2i = to - from
  660. delta = Vector2i(2 * delta.x + abs(posmod(to.y, 2)) - abs(posmod(from.y, 2)), delta.y)
  661. var sign:Vector2i = delta.sign()
  662. var current := from;
  663. points.push_back(Vector2i(current.y, current.x) if transposed else current)
  664. var err := 0
  665. if abs(delta.y) < abs(delta.x):
  666. var err_step:Vector2i = 3 * delta.abs()
  667. while current != to:
  668. err += err_step.y
  669. if err > abs(delta.x):
  670. if sign.x == 0:
  671. current += Vector2i(sign.y, 0)
  672. else:
  673. current += Vector2i(sign.x if bool(current.y % 2) != (sign.x < 0) else 0, sign.y)
  674. err -= err_step.x
  675. else:
  676. current += Vector2i(sign.x, 0)
  677. err += err_step.y
  678. points.push_back(Vector2i(current.y, current.x) if transposed else current)
  679. else:
  680. var err_step:Vector2i = delta.abs()
  681. while current != to:
  682. err += err_step.x
  683. if err > 0:
  684. if sign.x == 0:
  685. current += Vector2i(0, sign.y)
  686. else:
  687. current += Vector2i(sign.x if bool(current.y % 2) != (sign.x < 0) else 0, sign.y)
  688. err -= err_step.y;
  689. else:
  690. if sign.x == 0:
  691. current += Vector2i(0, sign.y)
  692. else:
  693. current += Vector2i(-sign.x if bool(current.y % 2) != (sign.x > 0) else 0, sign.y)
  694. err += err_step.y
  695. points.push_back(Vector2i(current.y, current.x) if transposed else current)
  696. return points
  697. func _on_terrain_enable_id_pressed(id):
  698. if id in [SourceSelectors.ALL, SourceSelectors.NONE]:
  699. for i in source_selector_popup.item_count:
  700. if source_selector_popup.is_item_checkable(i):
  701. source_selector_popup.set_item_checked(i, id == SourceSelectors.ALL)
  702. else:
  703. var index = source_selector_popup.get_item_index(id)
  704. var checked = source_selector_popup.is_item_checked(index)
  705. source_selector_popup.set_item_checked(index, !checked)
  706. var disabled_sources : Array[int]
  707. for i in source_selector_popup.item_count:
  708. if source_selector_popup.is_item_checkable(i) and !source_selector_popup.is_item_checked(i):
  709. disabled_sources.append(source_selector_popup.get_item_id(i))
  710. tile_view.disabled_sources = disabled_sources
  711. func corresponding_tilemap_editor_button(similar: Button) -> Button:
  712. var editors = EditorInterface.get_base_control().find_children("*", "TileMapLayerEditor", true, false)
  713. var tile_map_layer_editor = editors[0]
  714. var buttons = tile_map_layer_editor.find_children("*", "Button", true, false)
  715. for button: Button in buttons:
  716. if button.icon == similar.icon:
  717. return button
  718. return null
  719. func _on_layer_up_or_down_pressed(button: Button) -> void:
  720. var matching_button = corresponding_tilemap_editor_button(button)
  721. if !matching_button:
  722. return
  723. # Major hack, to reduce flicker hide the tileset editor briefly
  724. var editors = EditorInterface.get_base_control().find_children("*", "TileSetEditor", true, false)
  725. var tile_set_editor = editors[0]
  726. matching_button.pressed.emit()
  727. tile_set_editor.modulate = Color.TRANSPARENT
  728. await get_tree().process_frame
  729. await get_tree().process_frame
  730. force_show_terrains.emit()
  731. tile_set_editor.modulate = Color.WHITE
  732. func _on_layer_up_pressed() -> void:
  733. _on_layer_up_or_down_pressed(layer_up)
  734. func _on_layer_down_pressed() -> void:
  735. _on_layer_up_or_down_pressed(layer_down)
  736. func _on_layer_highlight_toggled(toggled: bool) -> void:
  737. var settings = EditorInterface.get_editor_settings()
  738. settings.set_setting("editors/tiles_editor/highlight_selected_layer", toggled)
  739. var highlight = corresponding_tilemap_editor_button(layer_highlight)
  740. if highlight:
  741. highlight.toggled.emit(toggled)
  742. func _on_layer_grid_toggled(toggled: bool) -> void:
  743. var settings = EditorInterface.get_editor_settings()
  744. settings.set_setting("editors/tiles_editor/display_grid", toggled)
  745. var grid = corresponding_tilemap_editor_button(layer_grid)
  746. if grid:
  747. grid.toggled.emit(toggled)