]> Untitled Git - wolf-seeking-sheep.git/blob - addons/dialogic/Modules/StyleEditor/style_layer_editor.gd
Initial Godot project with Dialogic 2.0-Alpha-17
[wolf-seeking-sheep.git] / addons / dialogic / Modules / StyleEditor / style_layer_editor.gd
1 @tool
2 extends HSplitContainer
3
4 ## Script that handles the style editor.
5
6
7 var current_style: DialogicStyle = null
8
9 var customization_editor_info := {}
10
11 ## The id of the currently selected layer.
12 ## "" is the base scene.
13 var current_layer_id := ""
14
15 var _minimum_tree_item_height: int
16
17 @onready var tree: Tree = %LayerTree
18
19
20 func _ready() -> void:
21         # Styling
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")
27
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)
34
35
36 func load_style(style:DialogicStyle) -> void:
37         current_style = style
38
39         if current_style.has_meta("_latest_layer"):
40                 current_layer_id = str(current_style.get_meta("_latest_layer", ""))
41         else:
42                 current_layer_id = ""
43
44         %AddLayerButton.disabled = style.inherits_anything()
45         %ReplaceLayerButton.disabled = style.inherits_anything()
46         %MakeCustomButton.disabled = style.inherits_anything()
47         %DeleteLayerButton.disabled = style.inherits_anything()
48
49         load_style_layer_list()
50
51
52 func load_style_layer_list() -> void:
53         tree.clear()
54
55         var root := tree.create_item()
56
57         var base_layer_info := current_style.get_layer_inherited_info("")
58         setup_layer_tree_item(base_layer_info, root)
59
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)
64
65         select_layer(current_layer_id)
66
67
68 func select_layer(id:String) -> void:
69         if id == "":
70                 tree.get_root().select(0)
71         else:
72                 for child in tree.get_root().get_children():
73                         if child.get_meta("id", "") == id:
74                                 child.select(0)
75                                 return
76
77
78 func setup_layer_tree_item(info:Dictionary, item:TreeItem) -> void:
79         item.custom_minimum_height = _minimum_tree_item_height
80
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"))
85
86         else:
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)
92
93
94 func _on_layer_selected() -> void:
95         var item: TreeItem = %LayerTree.get_selected()
96         load_layer(item.get_meta("id", ""))
97
98
99 func load_layer(layer_id:=""):
100         current_layer_id = layer_id
101         current_style.set_meta('_latest_layer', current_layer_id)
102
103         var layer_info := current_style.get_layer_inherited_info(layer_id)
104
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', '')
111
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()
115
116         else:
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')
120
121         %DeleteLayerButton.disabled = layer_id == "" or current_style.inherits_anything()
122
123         %SmallLayerScene.text = layer_info.get('path', 'Unkown Layer').get_file()
124         %SmallLayerScene.tooltip_text = layer_info.get('path', '')
125
126         var inherited_layer_info := current_style.get_layer_inherited_info(layer_id, true)
127         load_layout_scene_customization(
128                         layer_info.path,
129                         layer_info.overrides,
130                         inherited_layer_info.overrides)
131
132
133
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)
139
140
141 func delete_layer() -> void:
142         if current_layer_id == "":
143                 return
144
145         current_style.delete_layer(current_layer_id)
146         load_style_layer_list()
147         %LayerTree.get_root().select(0)
148
149
150 func move_layer(from_idx:int, to_idx:int) -> void:
151         current_style.move_layer(from_idx, to_idx)
152
153         load_style_layer_list()
154         select_layer(current_style.get_layer_id_at_index(to_idx))
155
156
157 func replace_layer(layer_id:String, scene_path:String) -> void:
158         current_style.set_layer_scene(layer_id, scene_path)
159
160         load_style_layer_list()
161         select_layer(layer_id)
162
163
164 func _on_add_layer_menu_pressed(index:int) -> void:
165         # Adding a premade layer
166         if index == 2:
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', ''))
173
174         # Adding a custom scene as a layer
175         else:
176                 find_parent('EditorView').godot_file_dialog(
177                         _on_add_custom_layer_file_selected,
178                         '*.tscn, Scenes',
179                         EditorFileDialog.FILE_MODE_OPEN_FILE,
180                         "Open custom layer scene")
181
182
183 func _on_replace_layer_menu_pressed(index:int) -> void:
184         # Adding a premade layer
185         if index == 2:
186                 %StyleBrowserWindow.popup_centered_ratio(0.6)
187                 if %LayerTree.get_selected() == %LayerTree.get_root():
188                         %StyleBrowser.current_type = 3
189                 else:
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', ''))
195
196         # Adding a custom scene as a layer
197         else:
198                 find_parent('EditorView').godot_file_dialog(
199                         _on_replace_custom_layer_file_selected,
200                         '*.tscn, Scenes',
201                         EditorFileDialog.FILE_MODE_OPEN_FILE,
202                         "Open custom layer scene")
203
204
205 func _on_add_custom_layer_file_selected(file_path:String) -> void:
206         add_layer(file_path)
207
208
209 func _on_replace_custom_layer_file_selected(file_path:String) -> void:
210         replace_layer(%LayerTree.get_selected().get_meta("id", ""), file_path)
211
212
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)
216
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)
219
220
221 func _on_make_custom_menu_pressed(index:int) -> void:
222         # This layer only
223         if index == 2:
224                 find_parent('EditorView').godot_file_dialog(
225                         _on_make_custom_layer_file_selected,
226                         '',
227                         EditorFileDialog.FILE_MODE_OPEN_DIR,
228                         "Select folder for new copy of layer")
229         # The full layout
230         if index == 3:
231                 find_parent('EditorView').godot_file_dialog(
232                         _on_make_custom_layout_file_selected,
233                         '',
234                         EditorFileDialog.FILE_MODE_OPEN_DIR,
235                         "Select folder for new layout scene")
236
237
238 func _on_make_custom_layer_file_selected(file:String) -> void:
239         make_layer_custom(file)
240
241
242 func _on_make_custom_layout_file_selected(file:String) -> void:
243         make_layout_custom(file)
244
245
246 func make_layer_custom(target_folder:String, custom_name := "") -> void:
247
248         var original_file: String = current_style.get_layer_info(current_layer_id).path
249         var custom_new_folder := ""
250
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()
254
255         var result_path := DialogicUtil.make_file_custom(
256                 original_file,
257                 target_folder,
258                 custom_name,
259                 custom_new_folder,
260                 )
261
262         current_style.set_layer_scene(current_layer_id, result_path)
263
264         load_style_layer_list()
265
266         if %LayerTree.get_selected() == %LayerTree.get_root():
267                 %LayerTree.get_root().select(0)
268         else:
269                 %LayerTree.get_root().get_child(%LayerTree.get_selected().get_index()).select(0)
270
271
272 func make_layout_custom(target_folder:String) -> void:
273         target_folder = target_folder.path_join("Custom" + current_style.name.to_pascal_case())
274
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())
278
279
280         var base_layer_info := current_style.get_layer_info("")
281         var target_path: String = base_layer_info.path
282
283         # Load base scene
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()
287
288         # Load layers
289         for layer_id in current_style.get_layer_inherited_list():
290                 var layer_info := current_style.get_layer_inherited_info(layer_id)
291
292                 if not ResourceLoader.exists(layer_info.path):
293                         continue
294
295                 var layer_scene: DialogicLayoutLayer = load(layer_info.path).instantiate()
296
297                 base_scene.add_layer(layer_scene)
298                 layer_scene.owner = base_scene
299                 layer_scene.apply_overrides_on_ready = true
300
301                 # Apply layer overrides
302                 DialogicUtil.apply_scene_export_overrides(layer_scene, layer_info.overrides, false)
303
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)
308
309         current_style.base_scene = load(target_path)
310         current_style.inherits = null
311         current_style.layers = []
312         current_style.changed.emit()
313
314         load_style_layer_list()
315
316         %LayerTree.get_root().select(0)
317         find_parent('EditorView').plugin_reference.get_editor_interface().get_resource_filesystem().scan_sources()
318
319
320
321 func _on_delete_layer_button_pressed() -> void:
322         delete_layer()
323
324
325 #region Layer Settings
326 ####### LAYER SETTINGS #########################################################
327
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)
331                 child.queue_free()
332
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)
336                 if pck_scn:
337                         scene = pck_scn.instantiate()
338
339         var settings := []
340         if scene and scene.script:
341                 settings = collect_settings(scene.script.get_script_property_list())
342
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"
352                 return
353
354         var current_grid: GridContainer = null
355
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
360
361         var current_group_name := ""
362         var current_subgroup_name := ""
363         customization_editor_info = {}
364
365         for i in settings:
366                 match i['id']:
367                         &"GROUP":
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)
373
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 = ""
380
381                         &"SUBGROUP":
382
383                                 # add separator
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())
389
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)
395
396                                 # add spaced to the grid
397                                 current_grid.add_child(Control.new())
398                                 current_grid.add_child(Control.new())
399
400                                 current_subgroup_name = i['name'].to_snake_case()
401
402                         &"SETTING":
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)
406
407                                 var scene_value: Variant = scene.get(i['name'])
408                                 customization_editor_info[i['name']] = {}
409
410                                 if i['name'] in inherited_overrides:
411                                         customization_editor_info[i['name']]['orig'] = str_to_var(inherited_overrides.get(i['name']))
412                                 else:
413                                         customization_editor_info[i['name']]['orig'] = scene_value
414
415                                 var current_value: Variant
416                                 if i['name'] in overrides:
417                                         current_value = str_to_var(overrides.get(i['name']))
418                                 else:
419                                         current_value = customization_editor_info[i['name']]['orig']
420
421                                 var input: Node = DialogicUtil.setup_script_property_edit_node(i, current_value, set_export_override)
422
423                                 input.size_flags_horizontal = SIZE_EXPAND_FILL
424                                 customization_editor_info[i['name']]['node'] = input
425
426                                 var reset := Button.new()
427                                 reset.flat = true
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)
435
436         if scene:
437                 scene.queue_free()
438
439
440 func collect_settings(properties:Array[Dictionary]) -> Array[Dictionary]:
441         var settings: Array[Dictionary] = []
442
443         var current_group := {}
444         var current_subgroup := {}
445
446         for i in properties:
447                 if i['usage'] & PROPERTY_USAGE_CATEGORY:
448                         continue
449
450                 if (i['usage'] & PROPERTY_USAGE_GROUP):
451                         current_group = i
452                         current_group['added'] = false
453                         current_group['id'] = &'GROUP'
454                         current_subgroup = {}
455
456                 elif i['usage'] & PROPERTY_USAGE_SUBGROUP:
457                         current_subgroup = i
458                         current_subgroup['added'] = false
459                         current_subgroup['id'] = &'SUBGROUP'
460
461                 elif i['usage'] & PROPERTY_USAGE_EDITOR:
462                         if current_group.get('name', '') == 'Private':
463                                 continue
464
465                         if current_group.is_empty():
466                                 current_group = {'name':'General', 'added':false, 'id':&"GROUP"}
467
468                         if current_group.get('added', true) == false:
469                                 settings.append(current_group)
470                                 current_group['added'] = true
471
472                         if current_subgroup.is_empty():
473                                 current_subgroup = {'name':current_group['name'], 'added':false, 'id':&"SUBGROUP"}
474
475                         if current_subgroup.get('added', true) == false:
476                                 settings.append(current_subgroup)
477                                 current_subgroup['added'] = true
478
479                         i['id'] = &'SETTING'
480                         settings.append(i)
481         return settings
482
483
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
488         else:
489                 current_style.remove_layer_setting(current_layer_id, property_name)
490                 customization_editor_info[property_name]['reset'].disabled = true
491
492
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'])
497
498
499 func set_customization_value(property_name:String, value:Variant) -> void:
500         var node: Node = customization_editor_info[property_name]['node']
501         if node is CheckBox:
502                 node.button_pressed = value
503         elif node is LineEdit:
504                 node.text = value
505         elif node.has_method('set_value'):
506                 node.set_value(value)
507         elif node is ColorPickerButton:
508                 node.color = value
509         elif node is OptionButton:
510                 node.select(value)
511         elif node is SpinBox:
512                 node.value = value
513
514 #endregion
515
516
517 func _on_expand_layer_info_pressed() -> void:
518         if %LayerInfoBody.visible:
519                 %LayerInfoBody.hide()
520                 %ExpandLayerInfo.icon = get_theme_icon("CodeFoldedRightArrow", "EditorIcons")
521         else:
522                 %LayerInfoBody.show()
523                 %ExpandLayerInfo.icon = get_theme_icon("CodeFoldDownArrow", "EditorIcons")
524
525
526 func _on_layer_tree_layer_moved(from: int, to: int) -> void:
527         move_layer(from, to)
528
529
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")
534
535
536 #region Helpers
537 ####### HELPERS ################################################################
538
539 func clean_scene_name(file_path:String) -> String:
540         return file_path.get_file().trim_suffix('.tscn').capitalize()
541
542 #endregion