2 extends HSplitContainer
4 ## Script that handles the style editor.
7 var current_style: DialogicStyle = null
9 var customization_editor_info := {}
11 ## The id of the currently selected layer.
12 ## "" is the base scene.
13 var current_layer_id := ""
15 var _minimum_tree_item_height: int
17 @onready var tree: Tree = %LayerTree
20 func _ready() -> void:
22 %AddLayerButton.icon = get_theme_icon("Add", "EditorIcons")
23 %DeleteLayerButton.icon = get_theme_icon("Remove", "EditorIcons")
24 %ReplaceLayerButton.icon = get_theme_icon("Loop", "EditorIcons")
25 %MakeCustomButton.icon = get_theme_icon("FileAccess", "EditorIcons")
26 %ExpandLayerInfo.icon = get_theme_icon("CodeFoldDownArrow", "EditorIcons")
28 %AddLayerButton.get_popup().index_pressed.connect(_on_add_layer_menu_pressed)
29 %ReplaceLayerButton.get_popup().index_pressed.connect(_on_replace_layer_menu_pressed)
30 %MakeCustomButton.get_popup().index_pressed.connect(_on_make_custom_menu_pressed)
31 %LayerTree.item_selected.connect(_on_layer_selected)
32 _minimum_tree_item_height = int(DialogicUtil.get_editor_scale() * 32)
33 %LayerTree.add_theme_constant_override("icon_max_width", _minimum_tree_item_height)
36 func load_style(style:DialogicStyle) -> void:
39 if current_style.has_meta("_latest_layer"):
40 current_layer_id = str(current_style.get_meta("_latest_layer", ""))
44 %AddLayerButton.disabled = style.inherits_anything()
45 %ReplaceLayerButton.disabled = style.inherits_anything()
46 %MakeCustomButton.disabled = style.inherits_anything()
47 %DeleteLayerButton.disabled = style.inherits_anything()
49 load_style_layer_list()
52 func load_style_layer_list() -> void:
55 var root := tree.create_item()
57 var base_layer_info := current_style.get_layer_inherited_info("")
58 setup_layer_tree_item(base_layer_info, root)
60 for layer_id in current_style.get_layer_inherited_list():
61 var layer_info := current_style.get_layer_inherited_info(layer_id)
62 var layer_item := tree.create_item(root)
63 setup_layer_tree_item(layer_info, layer_item)
65 select_layer(current_layer_id)
68 func select_layer(id:String) -> void:
70 tree.get_root().select(0)
72 for child in tree.get_root().get_children():
73 if child.get_meta("id", "") == id:
78 func setup_layer_tree_item(info:Dictionary, item:TreeItem) -> void:
79 item.custom_minimum_height = _minimum_tree_item_height
81 if %StyleBrowser.is_premade_style_part(info.path):
82 if ResourceLoader.exists(%StyleBrowser.premade_scenes_reference[info.path].get('icon', '')):
83 item.set_icon(0, load(%StyleBrowser.premade_scenes_reference[info.path].get("icon")))
84 item.set_text(0, %StyleBrowser.premade_scenes_reference[info.path].get("name", "Layer"))
87 item.set_text(0, clean_scene_name(info.path))
88 item.add_button(0, get_theme_icon("PackedScene", "EditorIcons"))
89 item.set_button_tooltip_text(0, 0, "Open Scene")
90 item.set_meta("scene", info.path)
91 item.set_meta("id", info.id)
94 func _on_layer_selected() -> void:
95 var item: TreeItem = %LayerTree.get_selected()
96 load_layer(item.get_meta("id", ""))
99 func load_layer(layer_id:=""):
100 current_layer_id = layer_id
101 current_style.set_meta('_latest_layer', current_layer_id)
103 var layer_info := current_style.get_layer_inherited_info(layer_id)
105 %SmallLayerPreview.hide()
106 if %StyleBrowser.is_premade_style_part(layer_info.get('path', 'Unkown Layer')):
107 var premade_infos = %StyleBrowser.premade_scenes_reference[layer_info.get('path')]
108 %LayerName.text = premade_infos.get('name', 'Unknown Layer')
109 %SmallLayerAuthor.text = "by "+premade_infos.get('author', '')
110 %SmallLayerDescription.text = premade_infos.get('description', '')
112 if premade_infos.get('preview_image', null) and ResourceLoader.exists(premade_infos.get('preview_image')[0]):
113 %SmallLayerPreview.texture = load(premade_infos.get('preview_image')[0])
114 %SmallLayerPreview.show()
117 %LayerName.text = clean_scene_name(layer_info.get('path', 'Unkown Layer'))
118 %SmallLayerAuthor.text = "Custom Layer"
119 %SmallLayerDescription.text = layer_info.get('path', 'Unkown Layer')
121 %DeleteLayerButton.disabled = layer_id == "" or current_style.inherits_anything()
123 %SmallLayerScene.text = layer_info.get('path', 'Unkown Layer').get_file()
124 %SmallLayerScene.tooltip_text = layer_info.get('path', '')
126 var inherited_layer_info := current_style.get_layer_inherited_info(layer_id, true)
127 load_layout_scene_customization(
129 layer_info.overrides,
130 inherited_layer_info.overrides)
134 func add_layer(scene_path:="", overrides:= {}):
135 current_style.add_layer(scene_path, overrides)
136 load_style_layer_list()
137 await get_tree().process_frame
138 %LayerTree.get_root().get_child(-1).select(0)
141 func delete_layer() -> void:
142 if current_layer_id == "":
145 current_style.delete_layer(current_layer_id)
146 load_style_layer_list()
147 %LayerTree.get_root().select(0)
150 func move_layer(from_idx:int, to_idx:int) -> void:
151 current_style.move_layer(from_idx, to_idx)
153 load_style_layer_list()
154 select_layer(current_style.get_layer_id_at_index(to_idx))
157 func replace_layer(layer_id:String, scene_path:String) -> void:
158 current_style.set_layer_scene(layer_id, scene_path)
160 load_style_layer_list()
161 select_layer(layer_id)
164 func _on_add_layer_menu_pressed(index:int) -> void:
165 # Adding a premade layer
167 %StyleBrowserWindow.popup_centered_ratio(0.6)
168 %StyleBrowser.current_type = 2
169 %StyleBrowser.load_parts()
170 var picked_info: Dictionary = await %StyleBrowserWindow.get_picked_info()
171 if not picked_info.is_empty():
172 add_layer(picked_info.get('path', ''))
174 # Adding a custom scene as a layer
176 find_parent('EditorView').godot_file_dialog(
177 _on_add_custom_layer_file_selected,
179 EditorFileDialog.FILE_MODE_OPEN_FILE,
180 "Open custom layer scene")
183 func _on_replace_layer_menu_pressed(index:int) -> void:
184 # Adding a premade layer
186 %StyleBrowserWindow.popup_centered_ratio(0.6)
187 if %LayerTree.get_selected() == %LayerTree.get_root():
188 %StyleBrowser.current_type = 3
190 %StyleBrowser.current_type = 2
191 %StyleBrowser.load_parts()
192 var picked_info: Dictionary = await %StyleBrowserWindow.get_picked_info()
193 if not picked_info.is_empty():
194 replace_layer(%LayerTree.get_selected().get_meta("id", ""), picked_info.get('path', ''))
196 # Adding a custom scene as a layer
198 find_parent('EditorView').godot_file_dialog(
199 _on_replace_custom_layer_file_selected,
201 EditorFileDialog.FILE_MODE_OPEN_FILE,
202 "Open custom layer scene")
205 func _on_add_custom_layer_file_selected(file_path:String) -> void:
209 func _on_replace_custom_layer_file_selected(file_path:String) -> void:
210 replace_layer(%LayerTree.get_selected().get_meta("id", ""), file_path)
213 func _on_make_custom_button_about_to_popup() -> void:
214 %MakeCustomButton.get_popup().set_item_disabled(2, false)
215 %MakeCustomButton.get_popup().set_item_disabled(3, false)
217 if not %StyleBrowser.is_premade_style_part(current_style.get_layer_info(current_layer_id).path):
218 %MakeCustomButton.get_popup().set_item_disabled(2, true)
221 func _on_make_custom_menu_pressed(index:int) -> void:
224 find_parent('EditorView').godot_file_dialog(
225 _on_make_custom_layer_file_selected,
227 EditorFileDialog.FILE_MODE_OPEN_DIR,
228 "Select folder for new copy of layer")
231 find_parent('EditorView').godot_file_dialog(
232 _on_make_custom_layout_file_selected,
234 EditorFileDialog.FILE_MODE_OPEN_DIR,
235 "Select folder for new layout scene")
238 func _on_make_custom_layer_file_selected(file:String) -> void:
239 make_layer_custom(file)
242 func _on_make_custom_layout_file_selected(file:String) -> void:
243 make_layout_custom(file)
246 func make_layer_custom(target_folder:String, custom_name := "") -> void:
248 var original_file: String = current_style.get_layer_info(current_layer_id).path
249 var custom_new_folder := ""
251 if custom_name.is_empty():
252 custom_name = "custom_"+%StyleBrowser.premade_scenes_reference[original_file].name.to_snake_case()
253 custom_new_folder = %StyleBrowser.premade_scenes_reference[original_file].name.to_pascal_case()
255 var result_path := DialogicUtil.make_file_custom(
262 current_style.set_layer_scene(current_layer_id, result_path)
264 load_style_layer_list()
266 if %LayerTree.get_selected() == %LayerTree.get_root():
267 %LayerTree.get_root().select(0)
269 %LayerTree.get_root().get_child(%LayerTree.get_selected().get_index()).select(0)
272 func make_layout_custom(target_folder:String) -> void:
273 target_folder = target_folder.path_join("Custom" + current_style.name.to_pascal_case())
275 DirAccess.make_dir_absolute(target_folder)
276 %LayerTree.get_root().select(0)
277 make_layer_custom(target_folder, "custom_" + current_style.name.to_snake_case())
280 var base_layer_info := current_style.get_layer_info("")
281 var target_path: String = base_layer_info.path
284 var base_scene_pck: PackedScene = load(base_layer_info.path).duplicate()
285 var base_scene := base_scene_pck.instantiate()
286 base_scene.name = "Custom" + clean_scene_name(base_scene_pck.resource_path).to_pascal_case()
289 for layer_id in current_style.get_layer_inherited_list():
290 var layer_info := current_style.get_layer_inherited_info(layer_id)
292 if not ResourceLoader.exists(layer_info.path):
295 var layer_scene: DialogicLayoutLayer = load(layer_info.path).instantiate()
297 base_scene.add_layer(layer_scene)
298 layer_scene.owner = base_scene
299 layer_scene.apply_overrides_on_ready = true
301 # Apply layer overrides
302 DialogicUtil.apply_scene_export_overrides(layer_scene, layer_info.overrides, false)
304 var pckd_scn := PackedScene.new()
305 pckd_scn.pack(base_scene)
306 pckd_scn.take_over_path(target_path)
307 ResourceSaver.save(pckd_scn, target_path)
309 current_style.base_scene = load(target_path)
310 current_style.inherits = null
311 current_style.layers = []
312 current_style.changed.emit()
314 load_style_layer_list()
316 %LayerTree.get_root().select(0)
317 find_parent('EditorView').plugin_reference.get_editor_interface().get_resource_filesystem().scan_sources()
321 func _on_delete_layer_button_pressed() -> void:
325 #region Layer Settings
326 ####### LAYER SETTINGS #########################################################
328 func load_layout_scene_customization(custom_scene_path:String, overrides:Dictionary = {}, inherited_overrides:Dictionary = {}) -> void:
329 for child in %LayerSettingsTabs.get_children():
330 child.get_parent().remove_child(child)
333 var scene: Node = null
334 if !custom_scene_path.is_empty() and ResourceLoader.exists(custom_scene_path):
335 var pck_scn := load(custom_scene_path)
337 scene = pck_scn.instantiate()
340 if scene and scene.script:
341 settings = collect_settings(scene.script.get_script_property_list())
343 if settings.is_empty():
344 var note := Label.new()
345 note.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
346 note.text = "This layer has no exposed settings."
347 if not %StyleBrowser.is_premade_style_part(custom_scene_path):
348 note.text += "\n\nIf you want to add settings, make sure to have a root script in @tool mode and expose some @exported variables to show up here."
349 note.theme_type_variation = 'DialogicHintText2'
350 %LayerSettingsTabs.add_child(note)
351 note.name = "General"
354 var current_grid: GridContainer = null
356 var label_bg_style := get_theme_stylebox("CanvasItemInfoOverlay", "EditorStyles").duplicate()
357 label_bg_style.content_margin_left = 5
358 label_bg_style.content_margin_right = 5
359 label_bg_style.content_margin_top = 5
361 var current_group_name := ""
362 var current_subgroup_name := ""
363 customization_editor_info = {}
368 var main_scroll := ScrollContainer.new()
369 main_scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
370 main_scroll.size_flags_horizontal = Control.SIZE_EXPAND_FILL
371 main_scroll.name = i['name']
372 %LayerSettingsTabs.add_child(main_scroll, true)
374 current_grid = GridContainer.new()
375 current_grid.columns = 3
376 current_grid.size_flags_horizontal = Control.SIZE_EXPAND_FILL
377 main_scroll.add_child(current_grid)
378 current_group_name = i['name'].to_snake_case()
379 current_subgroup_name = ""
384 if current_subgroup_name:
385 current_grid.add_child(HSeparator.new())
386 current_grid.get_child(-1).add_theme_constant_override('separation', 20)
387 current_grid.add_child(current_grid.get_child(-1).duplicate())
388 current_grid.add_child(current_grid.get_child(-1).duplicate())
390 var title_label := Label.new()
391 title_label.text = i['name']
392 title_label.theme_type_variation = "DialogicSection"
393 title_label.size_flags_horizontal = SIZE_EXPAND_FILL
394 current_grid.add_child(title_label, true)
396 # add spaced to the grid
397 current_grid.add_child(Control.new())
398 current_grid.add_child(Control.new())
400 current_subgroup_name = i['name'].to_snake_case()
403 var label := Label.new()
404 label.text = str(i['name'].trim_prefix(current_group_name+'_').trim_prefix(current_subgroup_name+'_')).capitalize()
405 current_grid.add_child(label, true)
407 var scene_value: Variant = scene.get(i['name'])
408 customization_editor_info[i['name']] = {}
410 if i['name'] in inherited_overrides:
411 customization_editor_info[i['name']]['orig'] = str_to_var(inherited_overrides.get(i['name']))
413 customization_editor_info[i['name']]['orig'] = scene_value
415 var current_value: Variant
416 if i['name'] in overrides:
417 current_value = str_to_var(overrides.get(i['name']))
419 current_value = customization_editor_info[i['name']]['orig']
421 var input: Node = DialogicUtil.setup_script_property_edit_node(i, current_value, set_export_override)
423 input.size_flags_horizontal = SIZE_EXPAND_FILL
424 customization_editor_info[i['name']]['node'] = input
426 var reset := Button.new()
428 reset.icon = get_theme_icon("Reload", "EditorIcons")
429 reset.tooltip_text = "Remove customization"
430 customization_editor_info[i['name']]['reset'] = reset
431 reset.disabled = current_value == customization_editor_info[i['name']]['orig']
432 current_grid.add_child(reset)
433 reset.pressed.connect(_on_export_override_reset.bind(i['name']))
434 current_grid.add_child(input)
440 func collect_settings(properties:Array[Dictionary]) -> Array[Dictionary]:
441 var settings: Array[Dictionary] = []
443 var current_group := {}
444 var current_subgroup := {}
447 if i['usage'] & PROPERTY_USAGE_CATEGORY:
450 if (i['usage'] & PROPERTY_USAGE_GROUP):
452 current_group['added'] = false
453 current_group['id'] = &'GROUP'
454 current_subgroup = {}
456 elif i['usage'] & PROPERTY_USAGE_SUBGROUP:
458 current_subgroup['added'] = false
459 current_subgroup['id'] = &'SUBGROUP'
461 elif i['usage'] & PROPERTY_USAGE_EDITOR:
462 if current_group.get('name', '') == 'Private':
465 if current_group.is_empty():
466 current_group = {'name':'General', 'added':false, 'id':&"GROUP"}
468 if current_group.get('added', true) == false:
469 settings.append(current_group)
470 current_group['added'] = true
472 if current_subgroup.is_empty():
473 current_subgroup = {'name':current_group['name'], 'added':false, 'id':&"SUBGROUP"}
475 if current_subgroup.get('added', true) == false:
476 settings.append(current_subgroup)
477 current_subgroup['added'] = true
484 func set_export_override(property_name:String, value:String = "") -> void:
485 if str_to_var(value) != customization_editor_info[property_name]['orig']:
486 current_style.set_layer_setting(current_layer_id, property_name, value)
487 customization_editor_info[property_name]['reset'].disabled = false
489 current_style.remove_layer_setting(current_layer_id, property_name)
490 customization_editor_info[property_name]['reset'].disabled = true
493 func _on_export_override_reset(property_name:String) -> void:
494 current_style.remove_layer_setting(current_layer_id, property_name)
495 customization_editor_info[property_name]['reset'].disabled = true
496 set_customization_value(property_name, customization_editor_info[property_name]['orig'])
499 func set_customization_value(property_name:String, value:Variant) -> void:
500 var node: Node = customization_editor_info[property_name]['node']
502 node.button_pressed = value
503 elif node is LineEdit:
505 elif node.has_method('set_value'):
506 node.set_value(value)
507 elif node is ColorPickerButton:
509 elif node is OptionButton:
511 elif node is SpinBox:
517 func _on_expand_layer_info_pressed() -> void:
518 if %LayerInfoBody.visible:
519 %LayerInfoBody.hide()
520 %ExpandLayerInfo.icon = get_theme_icon("CodeFoldedRightArrow", "EditorIcons")
522 %LayerInfoBody.show()
523 %ExpandLayerInfo.icon = get_theme_icon("CodeFoldDownArrow", "EditorIcons")
526 func _on_layer_tree_layer_moved(from: int, to: int) -> void:
530 func _on_layer_tree_button_clicked(item: TreeItem, column: int, id: int, mouse_button_index: int) -> void:
531 if ResourceLoader.exists(item.get_meta('scene')):
532 find_parent('EditorView').plugin_reference.get_editor_interface().open_scene_from_path(item.get_meta('scene'))
533 find_parent('EditorView').plugin_reference.get_editor_interface().set_main_screen_editor("2D")
537 ####### HELPERS ################################################################
539 func clean_scene_name(file_path:String) -> String:
540 return file_path.get_file().trim_suffix('.tscn').capitalize()