2 class_name DialogicNode_PortraitContainer
5 ## Node that defines a position for dialogic portraits and how to display portraits at that position.
8 POSITION, ## This container can be joined/moved to with the Character Event
9 SPEAKER, ## This container is joined/left automatically based on the speaker.
12 @export var mode := PositionModes.POSITION
14 @export_subgroup('Mode: Position')
15 ## The position this node corresponds to.
16 @export var container_ids: PackedStringArray = ["1"]
19 @export_subgroup('Mode: Speaker')
20 ## Can be used to use a different portrait.
21 ## E.g. "Faces/" would mean instead of "happy" it will use portrait "Faces/happy"
22 @export var portrait_prefix := ''
24 @export_subgroup('Portrait Placement')
26 KEEP, ## The height and width of the container have no effect, only the origin.
27 FIT_STRETCH, ## The portrait will be fitted into the container, ignoring it's aspect ratio and the character/portrait scale.
28 FIT_IGNORE_SCALE, ## The portrait will be fitted into the container, ignoring the character/portrait scale, but preserving the aspect ratio.
29 FIT_SCALE_HEIGHT ## Recommended. The portrait will be scaled to fit the container height. A character/portrait scale of 100% means 100% container height. Aspect ratio will be preserved.
31 ## Defines how to affect the scale of the portrait
32 @export var size_mode: SizeModes = SizeModes.FIT_SCALE_HEIGHT :
35 _update_debug_portrait_transform()
37 ## If true, portraits will be mirrored in this position.
38 @export var mirrored := false :
41 _update_debug_portrait_scene()
44 @export_group('Origin', 'origin')
45 enum OriginAnchors {TOP_LEFT, TOP_CENTER, TOP_RIGHT, LEFT_MIDDLE, CENTER, RIGHT_MIDDLE, BOTTOM_LEFT, BOTTOM_CENTER, BOTTOM_RIGHT}
46 ## The portrait will be placed relative to this point in the container.
47 @export var origin_anchor: OriginAnchors = OriginAnchors.BOTTOM_CENTER :
49 origin_anchor = anchor
50 _update_debug_origin()
52 ## An offset to apply to the origin. Rarely useful.
53 @export var origin_offset := Vector2() :
55 origin_offset = offset
56 _update_debug_origin()
58 enum PivotModes {AT_ORIGIN, PERCENTAGE, PIXELS}
59 ## Usually you want to rotate or scale around the portrait origin.
60 ## For the moments where that is not the case, set the mode to PERCENTAGE or PIXELS and use [member pivot_value].
61 @export var pivot_mode: PivotModes = PivotModes.AT_ORIGIN
62 ## Only has an effect when [member pivot_mode] is not AT_ORIGIN. Meaning depends on whether [member pivot_mode] is PERCENTAGE or PIXELS.
63 @export var pivot_value := Vector2()
65 @export_group('Debug', 'debug')
66 ## A character that will be displayed in the editor, useful for getting the right size.
67 @export var debug_character: DialogicCharacter = null:
69 debug_character = character
70 _update_debug_portrait_scene()
71 @export var debug_character_portrait := "":
73 debug_character_portrait = portrait
74 _update_debug_portrait_scene()
76 var debug_character_holder_node: Node2D = null
77 var debug_character_scene_node: Node = null
78 var debug_origin: Sprite2D = null
79 var default_portrait_scene: String = DialogicUtil.get_module_path('Character').path_join("default_portrait.tscn")
80 # Used if no debug character is specified
81 var default_debug_character := load(DialogicUtil.get_module_path('Character').path_join("preview_character.tres"))
83 var ignore_resize := false
86 func _ready() -> void:
88 PositionModes.POSITION:
89 add_to_group('dialogic_portrait_con_position')
90 PositionModes.SPEAKER:
91 add_to_group('dialogic_portrait_con_speaker')
93 if Engine.is_editor_hint():
94 resized.connect(_update_debug_origin)
96 if !ProjectSettings.get_setting('dialogic/portraits/default_portrait', '').is_empty():
97 default_portrait_scene = ProjectSettings.get_setting('dialogic/portraits/default_portrait', '')
99 debug_origin = Sprite2D.new()
100 add_child(debug_origin)
101 debug_origin.texture = load("res://addons/dialogic/Editor/Images/Dropdown/default.svg")
103 _update_debug_origin()
104 _update_debug_portrait_scene()
106 resized.connect(update_portrait_transforms)
109 ################################################################################
111 ################################################################################
113 func update_portrait_transforms() -> void:
118 PivotModes.AT_ORIGIN:
119 pivot_offset = _get_origin_position()
120 PivotModes.PERCENTAGE:
121 pivot_offset = size*pivot_value
123 pivot_offset = pivot_value
125 for child in get_children():
126 DialogicUtil.autoload().Portraits._update_character_transform(child)
129 ## Returns a Rect2 with the position as the position and the scale as the size.
130 func get_local_portrait_transform(portrait_rect:Rect2, character_scale:=1.0) -> Rect2:
131 var transform := Rect2()
132 transform.position = _get_origin_position()
134 # Mode that ignores the containers size
135 if size_mode == SizeModes.KEEP:
136 transform.size = Vector2(1,1) * character_scale
138 # Mode that makes sure neither height nor width go out of container
139 elif size_mode == SizeModes.FIT_IGNORE_SCALE:
140 if size.x/size.y < portrait_rect.size.x/portrait_rect.size.y:
141 transform.size = Vector2(1,1) * size.x/portrait_rect.size.x
143 transform.size = Vector2(1,1) * size.y/portrait_rect.size.y
145 # Mode that stretches the portrait to fill the whole container
146 elif size_mode == SizeModes.FIT_STRETCH:
147 transform.size = size/portrait_rect.size
149 # Mode that size the character so 100% size fills the height
150 elif size_mode == SizeModes.FIT_SCALE_HEIGHT:
151 transform.size = Vector2(1,1) * size.y / portrait_rect.size.y*character_scale
156 ## Returns the current origin position
157 func _get_origin_position(rect_size = null) -> Vector2:
158 if rect_size == null:
160 return rect_size * Vector2(origin_anchor%3 / 2.0, floor(origin_anchor/3.0) / 2.0) + origin_offset
163 func is_container(id:Variant) -> bool:
164 return str(id) in container_ids
166 #region DEBUG METHODS
167 ################################################################################
168 ### USE THIS TO DEBUG THE POSITIONS
170 #draw_rect(Rect2(Vector2(), size), Color(1, 0.3098039329052, 1), false, 2)
171 #draw_string(get_theme_default_font(),get_theme_default_font().get_string_size(container_ids[0], HORIZONTAL_ALIGNMENT_LEFT, 1, get_theme_default_font_size()) , container_ids[0], HORIZONTAL_ALIGNMENT_CENTER)
173 #func _process(delta:float) -> void:
177 ## Loads the debug_character with the debug_character_portrait
178 ## Creates a holder node and applies mirror
179 func _update_debug_portrait_scene() -> void:
180 if !Engine.is_editor_hint():
182 if is_instance_valid(debug_character_holder_node):
183 for child in get_children():
184 if child != debug_origin:
188 var character := _get_debug_character()
189 if not character is DialogicCharacter or character.portraits.is_empty():
193 var debug_portrait := debug_character_portrait
194 if debug_portrait.is_empty():
195 debug_portrait = character.default_portrait
196 if mode == PositionModes.SPEAKER and !portrait_prefix.is_empty():
197 if portrait_prefix+debug_portrait in character.portraits:
198 debug_portrait = portrait_prefix+debug_portrait
199 if not debug_portrait in character.portraits:
200 debug_portrait = character.default_portrait
202 var portrait_info: Dictionary = character.get_portrait_info(debug_portrait)
205 var portrait_scene_path: String = portrait_info.get('scene', default_portrait_scene)
206 if portrait_scene_path.is_empty():
207 portrait_scene_path = default_portrait_scene
209 debug_character_scene_node = load(portrait_scene_path).instantiate()
211 if !is_instance_valid(debug_character_scene_node):
215 DialogicUtil.apply_scene_export_overrides(debug_character_scene_node, character.portraits[debug_portrait].get('export_overrides', {}))
216 debug_character_scene_node._update_portrait(character, debug_portrait)
219 if !is_instance_valid(debug_character_holder_node):
220 debug_character_holder_node = Node2D.new()
221 add_child(debug_character_holder_node)
224 debug_character_holder_node.add_child(debug_character_scene_node)
225 move_child(debug_character_holder_node, 0)
226 debug_character_scene_node._set_mirror(character.mirror != mirrored != portrait_info.get('mirror', false))
228 _update_debug_portrait_transform()
231 ## Set's the size and position of the holder and scene node
232 ## according to the size_mode
233 func _update_debug_portrait_transform() -> void:
234 if !Engine.is_editor_hint() or !is_instance_valid(debug_character_scene_node) or !is_instance_valid(debug_origin):
236 var character := _get_debug_character()
237 var portrait_info := character.get_portrait_info(debug_character_portrait)
238 var transform := get_local_portrait_transform(debug_character_scene_node._get_covered_rect(), character.scale*portrait_info.get('scale', 1))
239 debug_character_holder_node.position = transform.position
240 debug_character_scene_node.position = portrait_info.get('offset', Vector2())+character.offset
242 debug_character_holder_node.scale = transform.size
245 ## Updates the debug origins position. Also calls _update_debug_portrait_transform()
246 func _update_debug_origin() -> void:
247 if !Engine.is_editor_hint() or !is_instance_valid(debug_origin):
249 debug_origin.position = _get_origin_position()
250 _update_debug_portrait_transform()
254 ## Returns the debug character or the default debug character
255 func _get_debug_character() -> DialogicCharacter:
256 return debug_character if debug_character != null else default_debug_character