Skip to content

Commit b5271df

Browse files
Improve custom portrait UX (#2315)
Adds a custom portrait scene browser that shows available portrait scenes and portrait scene Presets. There are four types of entries: "DEFAULT" will simply set the portrait scene to "" (empty string), and thus default to the scene set in the settings. "CUSTOM" allows selecting any scene from the file system. A "GENERAL" item will use the specified scene. A "PRESET" item will create a copy of the scene and then use that copy. This makes it easy to create certain scenes from a base preset. This moves the Layered Portrait and the SimpleHighlightPortrait into individual modules for a bit better organization. Also: - Some code cleanup - Changes the layered_portrait scene to be a cleaner Preset - Adds a "fix offset" setting to the layered portrait script that attempts to correct the offset of the portrait based on the first node, there is probably situations where you wouldn't want this, so it can be turned off, but it makes the creation a bit more fool-proof.
1 parent 1fcb9ad commit b5271df

35 files changed

+924
-169
lines changed

addons/dialogic/Core/index_class.gd

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,11 @@ func _get_special_resources() -> Dictionary:
6363
return {}
6464

6565

66+
## Return a list of dictionaries, each
67+
func _get_portrait_scene_presets() -> Array[Dictionary]:
68+
return []
69+
70+
6671
#region HELPERS
6772
################################################################################
6873

addons/dialogic/Editor/CharacterEditor/char_edit_p_section_exports.gd

Lines changed: 33 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,75 +5,77 @@ extends DialogicCharacterEditorPortraitSection
55
## for custom portrait scenes
66

77
var current_portrait_data := {}
8-
8+
var last_scene := ""
99

1010
func _get_title() -> String:
1111
return "Settings"
1212

1313

14-
func _init() -> void:
15-
hint_text = "The settings here are @export variables from the used scene."
16-
17-
1814
func _load_portrait_data(data:Dictionary) -> void:
19-
_recheck(data)
15+
_recheck(data, true)
2016

2117

2218
## Recheck section visibility and reload export fields.
2319
## This allows reacting to changes of the portrait_scene setting.
24-
func _recheck(data:Dictionary):
25-
if data.get('scene', '').is_empty() and ProjectSettings.get_setting('dialogic/portraits/default_portrait', '').is_empty():
26-
hide()
27-
get_parent().get_child(get_index()-1).hide()
28-
get_parent().get_child(get_index()+1).hide()
29-
else:
30-
get_parent().get_child(get_index()-1).show()
31-
20+
func _recheck(data: Dictionary, force:=false):
21+
if last_scene == data.get("scene", "") and not force:
3222
current_portrait_data = data
33-
load_portrait_scene_export_variables()
23+
last_scene = data.get("scene", "")
24+
return
3425

26+
last_scene = data.get("scene", "")
27+
current_portrait_data = data
3528

36-
func load_portrait_scene_export_variables() -> void:
37-
var scene = null
38-
if !current_portrait_data.get('scene', '').is_empty():
39-
scene = load(current_portrait_data.get('scene'))
40-
elif !ProjectSettings.get_setting('dialogic/portraits/default_portrait', '').is_empty():
41-
scene = load(ProjectSettings.get_setting('dialogic/portraits/default_portrait', ''))
29+
for child in $Grid.get_children():
30+
child.get_parent().remove_child(child)
31+
child.queue_free()
32+
33+
var scene: Variant = null
34+
35+
if current_portrait_data.get('scene', '').is_empty():
36+
if ProjectSettings.get_setting('dialogic/portraits/default_portrait', '').is_empty():
37+
scene = load(character_editor.def_portrait_path)
38+
else:
39+
scene = load(ProjectSettings.get_setting('dialogic/portraits/default_portrait', ''))
4240
else:
43-
scene = load(character_editor.def_portrait_path)
41+
scene = load(current_portrait_data.get('scene'))
4442

45-
if !scene:
43+
if not scene:
4644
return
4745

48-
for child in $Grid.get_children():
49-
child.queue_free()
50-
5146
scene = scene.instantiate()
47+
5248
var skip := false
5349
for i in scene.script.get_script_property_list():
5450
if i['usage'] & PROPERTY_USAGE_EDITOR and !skip:
55-
var label = Label.new()
51+
var label := Label.new()
5652
label.text = i['name'].capitalize()
5753
$Grid.add_child(label)
5854

59-
var current_value :Variant = scene.get(i['name'])
55+
var current_value: Variant = scene.get(i['name'])
6056
if current_portrait_data.has('export_overrides') and current_portrait_data['export_overrides'].has(i['name']):
6157
current_value = str_to_var(current_portrait_data.export_overrides[i['name']])
6258
if current_value == null and typeof(scene.get(i['name'])) == TYPE_STRING:
6359
current_value = current_portrait_data['export_overrides'][i['name']]
6460

65-
var input :Node = DialogicUtil.setup_script_property_edit_node(i, current_value, set_export_override)
61+
var input: Node = DialogicUtil.setup_script_property_edit_node(i, current_value, set_export_override)
6662
input.size_flags_horizontal = SIZE_EXPAND_FILL
6763
$Grid.add_child(input)
6864

6965
if i['usage'] & PROPERTY_USAGE_GROUP:
70-
if i['name'] == 'Main':
66+
if i['name'] == 'Main' or i["name"] == "Private":
7167
skip = true
7268
continue
7369
else:
7470
skip = false
7571

76-
$Label.visible = $Grid.get_child_count() == 0
72+
if $Grid.get_child_count():
73+
get_parent().get_child(get_index()-1).show()
74+
show()
75+
else:
76+
hide()
77+
get_parent().get_child(get_index()-1).hide()
78+
get_parent().get_child(get_index()+1).hide()
7779

7880

7981
## On any change, save the export override to the portrait items metadata.

addons/dialogic/Editor/CharacterEditor/char_edit_p_section_exports.tscn

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,6 @@ offset_right = 367.0
88
offset_bottom = 82.0
99
script = ExtResource("1_isys8")
1010

11-
[node name="Label" type="Label" parent="."]
12-
layout_mode = 2
13-
theme_type_variation = &"DialogicHintText"
14-
theme_override_colors/font_color = Color(0, 0, 0, 1)
15-
text = "There are no exported variables to override. Add @export properties to the root script of your scene and make sure it's in @tool mode."
16-
autowrap_mode = 3
17-
1811
[node name="Grid" type="GridContainer" parent="."]
1912
layout_mode = 2
2013
size_flags_horizontal = 3

addons/dialogic/Editor/CharacterEditor/char_edit_p_section_main.gd

Lines changed: 98 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@ func _get_title() -> String:
99
func _init() -> void:
1010
hint_text = "You can use a custom scene for this portrait."
1111

12+
func _start_opened() -> bool:
13+
return true
14+
1215
func _ready() -> void:
16+
%ChangeSceneButton.icon = get_theme_icon("Loop", "EditorIcons")
1317
%ScenePicker.file_filter = "*.tscn, *.scn; Scenes"
1418
%ScenePicker.resource_icon = get_theme_icon('PackedScene', 'EditorIcons')
1519
%ScenePicker.placeholder = 'Default scene'
@@ -18,19 +22,103 @@ func _ready() -> void:
1822

1923

2024
func _load_portrait_data(data:Dictionary) -> void:
21-
%ScenePicker.set_value(data.get('scene', ''))
22-
%OpenSceneButton.visible = !data.get('scene', '').is_empty()
25+
reload_ui(data)
26+
27+
28+
func _on_open_scene_button_pressed() -> void:
29+
var data: Dictionary = selected_item.get_metadata(0)
30+
if ResourceLoader.exists(data.get("scene", "")):
31+
DialogicUtil.get_dialogic_plugin().get_editor_interface().open_scene_from_path(data.get("scene", ""))
32+
await get_tree().process_frame
33+
EditorInterface.set_main_screen_editor("2D")
34+
35+
36+
func _on_change_scene_button_pressed() -> void:
37+
%PortraitSceneBrowserWindow.popup_centered_ratio(0.6)
38+
39+
40+
func _on_portrait_scene_browser_activate_part(part_info: Dictionary) -> void:
41+
%PortraitSceneBrowserWindow.hide()
42+
match part_info.type:
43+
"General":
44+
set_scene_path(part_info.path)
45+
"Preset":
46+
find_parent("EditorView").godot_file_dialog(
47+
create_new_portrait_scene.bind(part_info),
48+
'*.tscn,*.scn',
49+
EditorFileDialog.FILE_MODE_SAVE_FILE,
50+
"Select where to save the new scene",
51+
part_info.path.get_file().trim_suffix("."+part_info.path.get_extension())+"_"+character_editor.current_resource.get_character_name().to_lower())
52+
"Custom":
53+
find_parent("EditorView").godot_file_dialog(
54+
set_scene_path,
55+
'*.tscn, *.scn',
56+
EditorFileDialog.FILE_MODE_OPEN_FILE,
57+
"Select custom portrait scene",)
58+
"Default":
59+
set_scene_path("")
60+
61+
62+
func create_new_portrait_scene(target_file: String, info: Dictionary) -> void:
63+
var path := make_portrait_preset_custom(target_file, info)
64+
set_scene_path(path)
65+
66+
67+
func make_portrait_preset_custom(target_file:String, info: Dictionary) -> String:
68+
var previous_file: String = info.path
2369

70+
var target_folder := target_file.get_base_dir()
71+
target_file = target_file.get_file()
2472

25-
func _on_scene_picker_value_changed(prop_name:String, value:String) -> void:
26-
var data:Dictionary = selected_item.get_metadata(0)
27-
data['scene'] = value
73+
DirAccess.make_dir_absolute(target_folder)
74+
75+
DirAccess.copy_absolute(previous_file, target_folder.path_join(target_file))
76+
77+
var file := FileAccess.open(target_folder.path_join(target_file), FileAccess.READ)
78+
var scene_text := file.get_as_text()
79+
file.close()
80+
if scene_text.begins_with('[gd_scene'):
81+
var base_path: String = previous_file.get_base_dir()
82+
83+
var result := RegEx.create_from_string("\\Q\""+base_path+"\\E(?<file>[^\"]*)\"").search(scene_text)
84+
while result:
85+
DirAccess.copy_absolute(base_path.path_join(result.get_string('file')), target_folder.path_join(result.get_string('file')))
86+
scene_text = scene_text.replace(base_path.path_join(result.get_string('file')), target_folder.path_join(result.get_string('file')))
87+
result = RegEx.create_from_string("\\Q\""+base_path+"\\E(?<file>[^\"]*)\"").search(scene_text)
88+
89+
file = FileAccess.open(target_folder.path_join(target_file), FileAccess.WRITE)
90+
file.store_string(scene_text)
91+
file.close()
92+
93+
find_parent('EditorView').plugin_reference.get_editor_interface().get_resource_filesystem().scan_sources()
94+
return target_folder.path_join(target_file)
95+
96+
97+
func set_scene_path(path:String) -> void:
98+
var data: Dictionary = selected_item.get_metadata(0)
99+
data['scene'] = path
28100
update_preview.emit()
29101
changed.emit()
30-
%OpenSceneButton.visible = !data.get('scene', '').is_empty()
102+
reload_ui(data)
31103

32104

33-
func _on_open_scene_button_pressed() -> void:
34-
if !%ScenePicker.current_value.is_empty() and ResourceLoader.exists(%ScenePicker.current_value):
35-
DialogicUtil.get_dialogic_plugin().get_editor_interface().open_scene_from_path(%ScenePicker.current_value)
36-
EditorInterface.set_main_screen_editor("2D")
105+
func reload_ui(data: Dictionary) -> void:
106+
var path: String = data.get('scene', '')
107+
%OpenSceneButton.hide()
108+
109+
if path.is_empty():
110+
%SceneLabel.text = "Default Portrait Scene"
111+
%SceneLabel.tooltip_text = "Can be changed in the settings."
112+
%SceneLabel.add_theme_color_override("font_color", get_theme_color("readonly_color", "Editor"))
113+
114+
elif %PortraitSceneBrowser.is_premade_portrait_scene(path):
115+
%SceneLabel.text = %PortraitSceneBrowser.portrait_scenes_info[path].name
116+
%SceneLabel.tooltip_text = path
117+
%SceneLabel.add_theme_color_override("font_color", get_theme_color("accent_color", "Editor"))
118+
119+
else:
120+
%SceneLabel.text = path.get_file()
121+
%SceneLabel.tooltip_text = path
122+
%SceneLabel.add_theme_color_override("font_color", get_theme_color("property_color_x", "Editor"))
123+
%OpenSceneButton.show()
124+
Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
[gd_scene load_steps=5 format=3 uid="uid://djq4aasoihexj"]
1+
[gd_scene load_steps=6 format=3 uid="uid://djq4aasoihexj"]
22

33
[ext_resource type="Script" path="res://addons/dialogic/Editor/CharacterEditor/char_edit_p_section_main.gd" id="1_ht8lu"]
44
[ext_resource type="PackedScene" uid="uid://7mvxuaulctcq" path="res://addons/dialogic/Editor/Events/Fields/field_file.tscn" id="2_k8xs0"]
5+
[ext_resource type="PackedScene" uid="uid://b1wn8r84uh11b" path="res://addons/dialogic/Editor/CharacterEditor/portrait_scene_browser.tscn" id="3_ngvgq"]
56

6-
[sub_resource type="Image" id="Image_sbh6e"]
7+
[sub_resource type="Image" id="Image_tg5pd"]
78
data = {
89
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
910
"format": "RGBA8",
@@ -12,8 +13,8 @@ data = {
1213
"width": 16
1314
}
1415

15-
[sub_resource type="ImageTexture" id="ImageTexture_mbv6v"]
16-
image = SubResource("Image_sbh6e")
16+
[sub_resource type="ImageTexture" id="ImageTexture_f5xt2"]
17+
image = SubResource("Image_tg5pd")
1718

1819
[node name="Scene" type="GridContainer"]
1920
offset_right = 298.0
@@ -25,18 +26,47 @@ script = ExtResource("1_ht8lu")
2526
layout_mode = 2
2627
size_flags_horizontal = 3
2728

29+
[node name="ChangeSceneButton" type="Button" parent="HBox"]
30+
unique_name_in_owner = true
31+
layout_mode = 2
32+
tooltip_text = "Change Scene"
33+
icon = SubResource("ImageTexture_f5xt2")
34+
35+
[node name="SceneLabel" type="Label" parent="HBox"]
36+
unique_name_in_owner = true
37+
layout_mode = 2
38+
size_flags_horizontal = 3
39+
mouse_filter = 0
40+
theme_override_colors/font_color = Color(0, 0, 0, 1)
41+
text = "asdsdasdasd"
42+
clip_text = true
43+
2844
[node name="ScenePicker" parent="HBox" instance=ExtResource("2_k8xs0")]
2945
unique_name_in_owner = true
46+
visible = false
3047
layout_mode = 2
3148
size_flags_horizontal = 3
3249
file_filter = "*.tscn, *.scn; Scenes"
3350
placeholder = "Default scene"
34-
resource_icon = SubResource("ImageTexture_mbv6v")
3551

3652
[node name="OpenSceneButton" type="Button" parent="HBox"]
3753
unique_name_in_owner = true
3854
layout_mode = 2
39-
icon = SubResource("ImageTexture_mbv6v")
55+
tooltip_text = "Open/Edit Scene"
56+
icon = SubResource("ImageTexture_f5xt2")
57+
58+
[node name="PortraitSceneBrowserWindow" type="Window" parent="."]
59+
unique_name_in_owner = true
60+
title = "Portrait Scene Browser"
61+
position = Vector2i(0, 36)
62+
visible = false
63+
wrap_controls = true
64+
transient = true
65+
popup_window = true
66+
67+
[node name="PortraitSceneBrowser" parent="PortraitSceneBrowserWindow" instance=ExtResource("3_ngvgq")]
68+
unique_name_in_owner = true
4069

41-
[connection signal="value_changed" from="HBox/ScenePicker" to="." method="_on_scene_picker_value_changed"]
70+
[connection signal="pressed" from="HBox/ChangeSceneButton" to="." method="_on_change_scene_button_pressed"]
4271
[connection signal="pressed" from="HBox/OpenSceneButton" to="." method="_on_open_scene_button_pressed"]
72+
[connection signal="activate_part" from="PortraitSceneBrowserWindow/PortraitSceneBrowser" to="." method="_on_portrait_scene_browser_activate_part"]

0 commit comments

Comments
 (0)