2 extends MarginContainer
4 ## Scene that represents an event in the visual timeline editor.
6 signal content_changed()
9 var resource: DialogicEvent
11 # for choice and condition
12 var end_node: Node = null:
17 %ToggleChildrenVisibilityButton.visible = true if end_node else false
22 # Whether the body is visible
24 var body_was_build := false
25 var has_any_enabled_body_content := false
26 # Whether contained events (e.g. in choices) are visible
27 var collapsed := false
32 const indent_size := 22
35 # List that stores visibility conditions
37 var current_indent_level := 1
40 #region UI AND LOGIC INITIALIZATION
41 ################################################################################
43 func _ready() -> void:
44 if get_parent() is SubViewport:
48 printerr("[Dialogic] Event block was added without a resource specified.")
55 func initialize_ui() -> void:
56 var _scale := DialogicUtil.get_editor_scale()
58 $PanelContainer.self_modulate = get_theme_color("accent_color", "Editor")
61 %Warning.texture = get_theme_icon("NodeWarning", "EditorIcons")
62 %Warning.size = Vector2(16 * _scale, 16 * _scale)
63 %Warning.position = Vector2(-5 * _scale, -10 * _scale)
66 %ToggleBodyVisibilityButton.icon = get_theme_icon("CodeFoldedRightArrow", "EditorIcons")
67 %ToggleBodyVisibilityButton.set("theme_override_colors/icon_normal_color", get_theme_color("contrast_color_2", "Editor"))
68 %ToggleBodyVisibilityButton.set("theme_override_colors/icon_hover_color", get_theme_color("accent_color", "Editor"))
69 %ToggleBodyVisibilityButton.set("theme_override_colors/icon_pressed_color", get_theme_color("contrast_color_2", "Editor"))
70 %ToggleBodyVisibilityButton.set("theme_override_colors/icon_hover_pressed_color", get_theme_color("accent_color", "Editor"))
71 %ToggleBodyVisibilityButton.add_theme_stylebox_override('hover_pressed', StyleBoxEmpty.new())
74 %IconPanel.tooltip_text = resource.event_name
75 %IconPanel.self_modulate = resource.event_color
78 %IconTexture.texture = resource._get_icon()
80 %IconPanel.custom_minimum_size = Vector2(icon_size, icon_size) * _scale
81 %IconTexture.custom_minimum_size = %IconPanel.custom_minimum_size
83 var custom_style: StyleBoxFlat = %IconPanel.get_theme_stylebox('panel')
84 custom_style.set_corner_radius_all(5 * _scale)
87 set_focus_mode(1) # Allowing this node to grab focus
89 # Separation on the header
90 %Header.add_theme_constant_override("custom_constants/separation", 5 * _scale)
93 %ToggleChildrenVisibilityButton.toggled.connect(_on_collapse_toggled)
94 %ToggleChildrenVisibilityButton.icon = get_theme_icon("Collapse", "EditorIcons")
95 %ToggleChildrenVisibilityButton.hide()
97 %Body.add_theme_constant_override("margin_left", icon_size * _scale)
102 func initialize_logic() -> void:
103 resized.connect(get_parent().get_parent().queue_redraw)
105 resource.ui_update_needed.connect(_on_resource_ui_update_needed)
106 resource.ui_update_warning.connect(set_warning)
108 content_changed.connect(recalculate_field_visibility)
110 _on_ToggleBodyVisibility_toggled(resource.expand_by_default or resource.created_by_button)
115 #region VISUAL METHODS
116 ################################################################################
118 func visual_select() -> void:
119 $PanelContainer.add_theme_stylebox_override('panel', load("res://addons/dialogic/Editor/Events/styles/selected_styleboxflat.tres"))
121 %IconPanel.self_modulate = resource.event_color
122 %IconTexture.modulate = get_theme_color("icon_saturation", "Editor")
125 func visual_deselect() -> void:
126 $PanelContainer.add_theme_stylebox_override('panel', load("res://addons/dialogic/Editor/Events/styles/unselected_stylebox.tres"))
128 %IconPanel.self_modulate = resource.event_color.lerp(Color.DARK_SLATE_GRAY, 0.1)
129 %IconTexture.modulate = get_theme_color('font_color', 'Label')
132 func is_selected() -> bool:
136 func set_warning(text:String= "") -> void:
139 %Warning.tooltip_text = text
144 func set_indent(indent: int) -> void:
145 add_theme_constant_override("margin_left", indent_size * indent * DialogicUtil.get_editor_scale())
146 current_indent_level = indent
152 ################################################################################
154 var FIELD_SCENES := {
155 DialogicEvent.ValueType.MULTILINE_TEXT: "res://addons/dialogic/Editor/Events/Fields/field_text_multiline.tscn",
156 DialogicEvent.ValueType.SINGLELINE_TEXT: "res://addons/dialogic/Editor/Events/Fields/field_text_singleline.tscn",
157 DialogicEvent.ValueType.FILE: "res://addons/dialogic/Editor/Events/Fields/field_file.tscn",
158 DialogicEvent.ValueType.BOOL: "res://addons/dialogic/Editor/Events/Fields/field_bool_check.tscn",
159 DialogicEvent.ValueType.BOOL_BUTTON: "res://addons/dialogic/Editor/Events/Fields/field_bool_button.tscn",
160 DialogicEvent.ValueType.CONDITION: "res://addons/dialogic/Editor/Events/Fields/field_condition.tscn",
161 DialogicEvent.ValueType.ARRAY: "res://addons/dialogic/Editor/Events/Fields/field_array.tscn",
162 DialogicEvent.ValueType.DICTIONARY: "res://addons/dialogic/Editor/Events/Fields/field_dictionary.tscn",
163 DialogicEvent.ValueType.DYNAMIC_OPTIONS: "res://addons/dialogic/Editor/Events/Fields/field_options_dynamic.tscn",
164 DialogicEvent.ValueType.FIXED_OPTIONS : "res://addons/dialogic/Editor/Events/Fields/field_options_fixed.tscn",
165 DialogicEvent.ValueType.NUMBER: "res://addons/dialogic/Editor/Events/Fields/field_number.tscn",
166 DialogicEvent.ValueType.VECTOR2: "res://addons/dialogic/Editor/Events/Fields/field_vector2.tscn",
167 DialogicEvent.ValueType.VECTOR3: "res://addons/dialogic/Editor/Events/Fields/field_vector3.tscn",
168 DialogicEvent.ValueType.VECTOR4: "res://addons/dialogic/Editor/Events/Fields/field_vector4.tscn",
169 DialogicEvent.ValueType.COLOR: "res://addons/dialogic/Editor/Events/Fields/field_color.tscn",
170 DialogicEvent.ValueType.AUDIO_PREVIEW: "res://addons/dialogic/Editor/Events/Fields/field_audio_preview.tscn",
173 func build_editor(build_header:bool = true, build_body:bool = false) -> void:
174 var current_body_container: HFlowContainer = null
176 if build_body and body_was_build:
182 current_body_container = HFlowContainer.new()
183 %BodyContent.add_child(current_body_container)
184 body_was_build = true
186 for p in resource.get_event_editor_info():
187 field_list.append({'node':null, 'location':p.location})
188 if p.has('condition'):
189 field_list[-1]['condition'] = p.condition
191 if !build_body and p.location == 1:
193 elif !build_header and p.location == 0:
196 ### --------------------------------------------------------------------
197 ### 1. CREATE A NODE OF THE CORRECT TYPE FOR THE PROPERTY
198 var editor_node: Control
201 if p.name == "linebreak":
202 field_list.remove_at(field_list.size()-1)
203 if !current_body_container.get_child_count():
204 current_body_container.queue_free()
205 current_body_container = HFlowContainer.new()
206 %BodyContent.add_child(current_body_container)
209 elif p.field_type in FIELD_SCENES:
210 editor_node = load(FIELD_SCENES[p.field_type]).instantiate()
212 elif p.field_type == resource.ValueType.LABEL:
213 editor_node = Label.new()
214 editor_node.text = p.display_info.text
215 editor_node.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
216 editor_node.set('custom_colors/font_color', Color("#7b7b7b"))
217 editor_node.add_theme_color_override('font_color', resource.event_color.lerp(get_theme_color("font_color", "Editor"), 0.8))
219 elif p.field_type == resource.ValueType.BUTTON:
220 editor_node = Button.new()
221 editor_node.text = p.display_info.text
222 editor_node.tooltip_text = p.display_info.get('tooltip', '')
223 if typeof(p.display_info.icon) == TYPE_ARRAY:
224 editor_node.icon = callv('get_theme_icon', p.display_info.icon)
226 editor_node.icon = p.display_info.icon
227 editor_node.flat = true
228 editor_node.custom_minimum_size.x = 30 * DialogicUtil.get_editor_scale()
229 editor_node.pressed.connect(p.display_info.callable)
232 elif p.field_type == resource.ValueType.CUSTOM:
233 if p.display_info.has('path'):
234 editor_node = load(p.display_info.path).instantiate()
238 editor_node = Label.new()
239 editor_node.text = p.name
240 editor_node.add_theme_color_override('font_color', resource.event_color.lerp(get_theme_color("font_color", "Editor"), 0.8))
243 field_list[-1]['node'] = editor_node
244 ### --------------------------------------------------------------------
245 # Some things need to be called BEFORE the field is added to the tree
246 if editor_node is DialogicVisualEditorField:
247 editor_node.event_resource = resource
249 editor_node.property_name = p.name
250 field_list[-1]['property'] = p.name
252 editor_node._load_display_info(p.display_info)
254 var location: Control = %HeaderContent
256 location = current_body_container
257 location.add_child(editor_node)
259 # Some things need to be called AFTER the field is added to the tree
260 if editor_node is DialogicVisualEditorField:
261 # Only set the value if the field is visible
263 # This prevents events with varied value types (event_setting, event_variable)
264 # from injecting incorrect types into hidden fields, which then throw errors
266 if p.has('condition') and not p.condition.is_empty():
267 if _evaluate_visibility_condition(p):
268 editor_node._set_value(resource.get(p.name))
270 editor_node._set_value(resource.get(p.name))
272 editor_node.value_changed.connect(set_property)
274 editor_node.tooltip_text = p.display_info.get('tooltip', '')
277 if resource.created_by_button and p.display_info.get('autofocus', false):
278 editor_node.call_deferred('take_autofocus')
280 ### --------------------------------------------------------------------
281 ### 4. ADD LEFT AND RIGHT TEXT
282 var left_label: Label = null
283 var right_label: Label = null
284 if !p.get('left_text', '').is_empty():
285 left_label = Label.new()
286 left_label.text = p.get('left_text')
287 left_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
288 left_label.add_theme_color_override('font_color', resource.event_color.lerp(get_theme_color("font_color", "Editor"), 0.8))
289 location.add_child(left_label)
290 location.move_child(left_label, editor_node.get_index())
291 if !p.get('right_text', '').is_empty():
292 right_label = Label.new()
293 right_label.text = p.get('right_text')
294 right_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
295 right_label.add_theme_color_override('font_color', resource.event_color.lerp(get_theme_color("font_color", "Editor"), 0.8))
296 location.add_child(right_label)
297 location.move_child(right_label, editor_node.get_index()+1)
299 ### --------------------------------------------------------------------
300 ### 5. REGISTER CONDITION
301 if p.has('condition'):
302 field_list[-1]['condition'] = p.condition
304 field_list.append({'node': left_label, 'condition':p.condition, 'location':p.location})
306 field_list.append({'node': right_label, 'condition':p.condition, 'location':p.location})
310 if current_body_container.get_child_count() == 0:
312 %Body.visible = false
314 recalculate_field_visibility()
317 func recalculate_field_visibility() -> void:
318 has_any_enabled_body_content = false
320 if !p.has('condition') or p.condition.is_empty():
324 has_any_enabled_body_content = true
326 if _evaluate_visibility_condition(p):
328 if p.node.visible == false and p.has("property"):
329 p.node._set_value(resource.get(p.property))
332 has_any_enabled_body_content = true
336 %ToggleBodyVisibilityButton.visible = has_any_enabled_body_content
339 func set_property(property_name:String, value:Variant) -> void:
340 resource.set(property_name, value)
341 content_changed.emit()
343 end_node.parent_node_changed()
346 func _evaluate_visibility_condition(p: Dictionary) -> bool:
347 var expr := Expression.new()
348 expr.parse(p.condition)
350 if expr.execute([], resource):
354 if expr.has_execute_failed():
355 printerr("[Dialogic] Failed executing visibility condition for '",p.get('property', 'unnamed'),"': " + expr.get_error_text())
359 func _on_resource_ui_update_needed() -> void:
360 for node_info in field_list:
361 if node_info.node and node_info.node.has_method('set_value'):
362 # Only set the value if the field is visible
364 # This prevents events with varied value types (event_setting, event_variable)
365 # from injecting incorrect types into hidden fields, which then throw errors
367 if node_info.has('condition') and not node_info.condition.is_empty():
368 if _evaluate_visibility_condition(node_info):
369 node_info.node.set_value(resource.get(node_info.property))
371 node_info.node.set_value(resource.get(node_info.property))
372 recalculate_field_visibility()
376 ################################################################################
378 func _on_collapse_toggled(toggled:bool) -> void:
380 var timeline_editor: Node = find_parent('VisualEditor')
381 if (timeline_editor != null):
382 # @todo select item and clear selection is marked as "private" in TimelineEditor.gd
383 # consider to make it "public" or add a public helper function
384 timeline_editor.indent_events()
388 func _on_ToggleBodyVisibility_toggled(button_pressed:bool) -> void:
389 if button_pressed and !body_was_build:
390 build_editor(false, true)
391 %ToggleBodyVisibilityButton.set_pressed_no_signal(button_pressed)
394 %ToggleBodyVisibilityButton.icon = get_theme_icon("CodeFoldDownArrow", "EditorIcons")
396 %ToggleBodyVisibilityButton.icon = get_theme_icon("CodeFoldedRightArrow", "EditorIcons")
398 expanded = button_pressed
399 %Body.visible = button_pressed
401 if find_parent('VisualEditor') != null:
402 find_parent('VisualEditor').indent_events()
405 func _on_EventNode_gui_input(event:InputEvent) -> void:
406 if event is InputEventMouseButton and event.is_pressed() and event.button_index == 1:
407 grab_focus() # Grab focus to avoid copy pasting text or events
408 if event.double_click:
409 if has_any_enabled_body_content:
410 _on_ToggleBodyVisibility_toggled(!expanded)
411 # For opening the context menu
412 if event is InputEventMouseButton:
413 if event.button_index == MOUSE_BUTTON_RIGHT and event.pressed:
414 var popup: PopupMenu = get_parent().get_parent().get_node('EventPopupMenu')
415 popup.current_event = self
416 popup.popup_on_parent(Rect2(get_global_mouse_position(),Vector2()))
417 if resource.help_page_path == "":
418 popup.set_item_disabled(2, true)
420 popup.set_item_disabled(2, false)