]> Untitled Git - wolf-seeking-sheep.git/blob - addons/dialogic/Editor/Events/EventBlock/event_block.gd
Updated export config options
[wolf-seeking-sheep.git] / addons / dialogic / Editor / Events / EventBlock / event_block.gd
1 @tool
2 extends MarginContainer
3
4 ## Scene that represents an event in the visual timeline editor.
5
6 signal content_changed()
7
8 ## REFERENCES
9 var resource: DialogicEvent
10 var editor_reference
11 # for choice and condition
12 var end_node: Node = null:
13         get:
14                 return end_node
15         set(node):
16                 end_node = node
17                 %ToggleChildrenVisibilityButton.visible = true if end_node else false
18
19
20 ## FLAGS
21 var selected := false
22 # Whether the body is visible
23 var expanded := true
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
28
29
30 ## CONSTANTS
31 const icon_size := 28
32 const indent_size := 22
33
34 ## STATE
35 # List that stores visibility conditions
36 var field_list := []
37 var current_indent_level := 1
38
39
40 #region UI AND LOGIC INITIALIZATION
41 ################################################################################
42
43 func _ready() -> void:
44         if get_parent() is SubViewport:
45                 return
46
47         if not resource:
48                 printerr("[Dialogic] Event block was added without a resource specified.")
49                 return
50
51         initialize_ui()
52         initialize_logic()
53
54
55 func initialize_ui() -> void:
56         var _scale := DialogicUtil.get_editor_scale()
57
58         $PanelContainer.self_modulate = get_theme_color("accent_color", "Editor")
59
60         # Warning Icon
61         %Warning.texture = get_theme_icon("NodeWarning", "EditorIcons")
62         %Warning.size = Vector2(16 * _scale, 16 * _scale)
63         %Warning.position = Vector2(-5 * _scale, -10 * _scale)
64
65         # Expand Button
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())
72
73         # Icon Panel
74         %IconPanel.tooltip_text = resource.event_name
75         %IconPanel.self_modulate = resource.event_color
76
77         # Event Icon
78         %IconTexture.texture = resource._get_icon()
79
80         %IconPanel.custom_minimum_size = Vector2(icon_size, icon_size) * _scale
81         %IconTexture.custom_minimum_size = %IconPanel.custom_minimum_size
82
83         var custom_style: StyleBoxFlat = %IconPanel.get_theme_stylebox('panel')
84         custom_style.set_corner_radius_all(5 * _scale)
85
86         # Focus Mode
87         set_focus_mode(1) # Allowing this node to grab focus
88
89         # Separation on the header
90         %Header.add_theme_constant_override("custom_constants/separation", 5 * _scale)
91
92         # Collapse Button
93         %ToggleChildrenVisibilityButton.toggled.connect(_on_collapse_toggled)
94         %ToggleChildrenVisibilityButton.icon = get_theme_icon("Collapse", "EditorIcons")
95         %ToggleChildrenVisibilityButton.hide()
96
97         %Body.add_theme_constant_override("margin_left", icon_size * _scale)
98
99         visual_deselect()
100
101
102 func initialize_logic() -> void:
103         resized.connect(get_parent().get_parent().queue_redraw)
104
105         resource.ui_update_needed.connect(_on_resource_ui_update_needed)
106         resource.ui_update_warning.connect(set_warning)
107
108         content_changed.connect(recalculate_field_visibility)
109
110         _on_ToggleBodyVisibility_toggled(resource.expand_by_default or resource.created_by_button)
111
112 #endregion
113
114
115 #region VISUAL METHODS
116 ################################################################################
117
118 func visual_select() -> void:
119         $PanelContainer.add_theme_stylebox_override('panel', load("res://addons/dialogic/Editor/Events/styles/selected_styleboxflat.tres"))
120         selected = true
121         %IconPanel.self_modulate = resource.event_color
122         %IconTexture.modulate = get_theme_color("icon_saturation", "Editor")
123
124
125 func visual_deselect() -> void:
126         $PanelContainer.add_theme_stylebox_override('panel', load("res://addons/dialogic/Editor/Events/styles/unselected_stylebox.tres"))
127         selected = false
128         %IconPanel.self_modulate = resource.event_color.lerp(Color.DARK_SLATE_GRAY, 0.1)
129         %IconTexture.modulate = get_theme_color('font_color', 'Label')
130
131
132 func is_selected() -> bool:
133         return selected
134
135
136 func set_warning(text:String= "") -> void:
137         if !text.is_empty():
138                 %Warning.show()
139                 %Warning.tooltip_text = text
140         else:
141                 %Warning.hide()
142
143
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
147
148 #endregion
149
150
151 #region EVENT FIELDS
152 ################################################################################
153
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",
171         }
172
173 func build_editor(build_header:bool = true, build_body:bool = false) ->  void:
174         var current_body_container: HFlowContainer = null
175
176         if build_body and body_was_build:
177                 build_body = false
178
179         if build_body:
180                 if body_was_build:
181                         return
182                 current_body_container = HFlowContainer.new()
183                 %BodyContent.add_child(current_body_container)
184                 body_was_build = true
185
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
190
191                 if !build_body and p.location == 1:
192                         continue
193                 elif !build_header and p.location == 0:
194                         continue
195
196                 ### --------------------------------------------------------------------
197                 ### 1. CREATE A NODE OF THE CORRECT TYPE FOR THE PROPERTY
198                 var editor_node: Control
199
200                 ### LINEBREAK
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)
207                         continue
208
209                 elif p.field_type in FIELD_SCENES:
210                         editor_node = load(FIELD_SCENES[p.field_type]).instantiate()
211
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))
218
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)
225                         else:
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)
230
231                 ## CUSTOM
232                 elif p.field_type == resource.ValueType.CUSTOM:
233                         if p.display_info.has('path'):
234                                 editor_node = load(p.display_info.path).instantiate()
235
236                 ## ELSE
237                 else:
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))
241
242
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
248
249                         editor_node.property_name = p.name
250                         field_list[-1]['property'] = p.name
251
252                         editor_node._load_display_info(p.display_info)
253
254                 var location: Control = %HeaderContent
255                 if p.location == 1:
256                         location = current_body_container
257                 location.add_child(editor_node)
258
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
262                         #
263                         # This prevents events with varied value types (event_setting, event_variable)
264                         # from injecting incorrect types into hidden fields, which then throw errors
265                         # in the console.
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))
269                         else:
270                                 editor_node._set_value(resource.get(p.name))
271
272                         editor_node.value_changed.connect(set_property)
273
274                         editor_node.tooltip_text = p.display_info.get('tooltip', '')
275
276                         # Apply autofocus
277                         if resource.created_by_button and p.display_info.get('autofocus', false):
278                                 editor_node.call_deferred('take_autofocus')
279
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)
298
299                 ### --------------------------------------------------------------------
300                 ### 5. REGISTER CONDITION
301                 if p.has('condition'):
302                         field_list[-1]['condition'] = p.condition
303                         if left_label:
304                                 field_list.append({'node': left_label, 'condition':p.condition, 'location':p.location})
305                         if right_label:
306                                 field_list.append({'node': right_label, 'condition':p.condition, 'location':p.location})
307
308
309         if build_body:
310                 if current_body_container.get_child_count() == 0:
311                         expanded = false
312                         %Body.visible = false
313
314         recalculate_field_visibility()
315
316
317 func recalculate_field_visibility() -> void:
318         has_any_enabled_body_content = false
319         for p in field_list:
320                 if !p.has('condition') or p.condition.is_empty():
321                         if p.node != null:
322                                 p.node.show()
323                         if p.location == 1:
324                                 has_any_enabled_body_content = true
325                 else:
326                         if _evaluate_visibility_condition(p):
327                                 if p.node != null:
328                                         if p.node.visible == false and p.has("property"):
329                                                 p.node._set_value(resource.get(p.property))
330                                         p.node.show()
331                                 if p.location == 1:
332                                         has_any_enabled_body_content = true
333                         else:
334                                 if p.node != null:
335                                         p.node.hide()
336         %ToggleBodyVisibilityButton.visible = has_any_enabled_body_content
337
338
339 func set_property(property_name:String, value:Variant) -> void:
340         resource.set(property_name, value)
341         content_changed.emit()
342         if end_node:
343                 end_node.parent_node_changed()
344
345
346 func _evaluate_visibility_condition(p: Dictionary) -> bool:
347         var expr := Expression.new()
348         expr.parse(p.condition)
349         var result: bool
350         if expr.execute([], resource):
351                 result = true
352         else:
353                 result = false
354         if expr.has_execute_failed():
355                 printerr("[Dialogic] Failed executing visibility condition for '",p.get('property', 'unnamed'),"': " + expr.get_error_text())
356         return result
357
358
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
363                         #
364                         # This prevents events with varied value types (event_setting, event_variable)
365                         # from injecting incorrect types into hidden fields, which then throw errors
366                         # in the console.
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))
370                         else:
371                                 node_info.node.set_value(resource.get(node_info.property))
372         recalculate_field_visibility()
373
374
375 #region SIGNALS
376 ################################################################################
377
378 func _on_collapse_toggled(toggled:bool) -> void:
379         collapsed = toggled
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()
385
386
387
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)
392
393         if button_pressed:
394                 %ToggleBodyVisibilityButton.icon = get_theme_icon("CodeFoldDownArrow", "EditorIcons")
395         else:
396                 %ToggleBodyVisibilityButton.icon = get_theme_icon("CodeFoldedRightArrow", "EditorIcons")
397
398         expanded = button_pressed
399         %Body.visible = button_pressed
400
401         if find_parent('VisualEditor') != null:
402                 find_parent('VisualEditor').indent_events()
403
404
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)
419                         else:
420                                 popup.set_item_disabled(2, false)