You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

415 lines
14 KiB

6 months ago
@tool
extends MarginContainer
## Scene that represents an event in the visual timeline editor.
signal content_changed()
## REFERENCES
var resource : DialogicEvent
var editor_reference
# for choice and condition
var end_node: Node = null:
get:
return end_node
set(node):
end_node = node
%ToggleChildrenVisibilityButton.visible = true if end_node else false
## FLAGS
var selected := false
# Whether the body is visible
var expanded := true
var body_was_build := false
var has_any_enabled_body_content := false
# Whether contained events (e.g. in choices) are visible
var collapsed := false
## CONSTANTS
const icon_size := 28
const indent_size := 22
## STATE
# List that stores visibility conditions
var field_list := []
var current_indent_level := 1
#region UI AND LOGIC INITIALIZATION
################################################################################
func _ready():
if get_parent() is SubViewport:
return
if not resource:
printerr("[Dialogic] Event block was added without a resource specified.")
return
initialize_ui()
initialize_logic()
func initialize_ui() -> void:
var _scale := DialogicUtil.get_editor_scale()
$PanelContainer.self_modulate = get_theme_color("accent_color", "Editor")
# Warning Icon
%Warning.texture = get_theme_icon("NodeWarning", "EditorIcons")
%Warning.size = Vector2(16 * _scale, 16 * _scale)
%Warning.position = Vector2(-5 * _scale, -10 * _scale)
# Expand Button
%ToggleBodyVisibilityButton.icon = get_theme_icon("CodeFoldedRightArrow", "EditorIcons")
%ToggleBodyVisibilityButton.modulate = get_theme_color("contrast_color_1", "Editor")
%ToggleBodyVisibilityButton.set("theme_override_colors/icon_normal_color", get_theme_color("contrast_color_1", "Editor"))
%ToggleBodyVisibilityButton.set("theme_override_colors/icon_pressed_color", get_theme_color("contrast_color_1", "Editor"))
# Icon Panel
%IconPanel.tooltip_text = resource.event_name
%IconPanel.self_modulate = resource.event_color
# Event Icon
%IconTexture.texture = resource._get_icon()
%IconPanel.custom_minimum_size = Vector2(icon_size, icon_size) * _scale
%IconTexture.custom_minimum_size = %IconPanel.custom_minimum_size
var custom_style: StyleBoxFlat = %IconPanel.get_theme_stylebox('panel')
custom_style.set_corner_radius_all(5 * _scale)
# Focus Mode
set_focus_mode(1) # Allowing this node to grab focus
# Separation on the header
%Header.add_theme_constant_override("custom_constants/separation", 5 * _scale)
# Collapse Button
%ToggleChildrenVisibilityButton.toggled.connect(_on_collapse_toggled)
%ToggleChildrenVisibilityButton.icon = get_theme_icon("Collapse", "EditorIcons")
%ToggleChildrenVisibilityButton.hide()
%Body.add_theme_constant_override("margin_left", icon_size * _scale)
visual_deselect()
func initialize_logic() -> void:
resized.connect(get_parent().get_parent().queue_redraw)
resource.ui_update_needed.connect(_on_resource_ui_update_needed)
resource.ui_update_warning.connect(set_warning)
content_changed.connect(recalculate_field_visibility)
_on_ToggleBodyVisibility_toggled(resource.expand_by_default or resource.created_by_button)
#endregion
#region VISUAL METHODS
################################################################################
func visual_select() -> void:
$PanelContainer.add_theme_stylebox_override('panel', load("res://addons/dialogic/Editor/Events/styles/selected_styleboxflat.tres"))
selected = true
%IconPanel.self_modulate = resource.event_color
%IconTexture.modulate = get_theme_color("icon_saturation", "Editor")
func visual_deselect() -> void:
$PanelContainer.add_theme_stylebox_override('panel', load("res://addons/dialogic/Editor/Events/styles/unselected_stylebox.tres"))
selected = false
%IconPanel.self_modulate = resource.event_color.lerp(Color.DARK_SLATE_GRAY, 0.1)
%IconTexture.modulate = get_theme_color('font_color', 'Label')
func is_selected() -> bool:
return selected
func set_warning(text:String= "") -> void:
if !text.is_empty():
%Warning.show()
%Warning.tooltip_text = text
else:
%Warning.hide()
func set_indent(indent: int) -> void:
add_theme_constant_override("margin_left", indent_size * indent * DialogicUtil.get_editor_scale())
current_indent_level = indent
#endregion
#region EVENT FIELDS
################################################################################
var FIELD_SCENES := {
DialogicEvent.ValueType.MULTILINE_TEXT: "res://addons/dialogic/Editor/Events/Fields/field_text_multiline.tscn",
DialogicEvent.ValueType.SINGLELINE_TEXT: "res://addons/dialogic/Editor/Events/Fields/field_text_singleline.tscn",
DialogicEvent.ValueType.FILE: "res://addons/dialogic/Editor/Events/Fields/field_file.tscn",
DialogicEvent.ValueType.BOOL: "res://addons/dialogic/Editor/Events/Fields/field_bool_check.tscn",
DialogicEvent.ValueType.BOOL_BUTTON: "res://addons/dialogic/Editor/Events/Fields/field_bool_button.tscn",
DialogicEvent.ValueType.CONDITION: "res://addons/dialogic/Editor/Events/Fields/field_condition.tscn",
DialogicEvent.ValueType.ARRAY: "res://addons/dialogic/Editor/Events/Fields/field_array.tscn",
DialogicEvent.ValueType.DICTIONARY: "res://addons/dialogic/Editor/Events/Fields/field_dictionary.tscn",
DialogicEvent.ValueType.DYNAMIC_OPTIONS: "res://addons/dialogic/Editor/Events/Fields/field_options_dynamic.tscn",
DialogicEvent.ValueType.FIXED_OPTIONS : "res://addons/dialogic/Editor/Events/Fields/field_options_fixed.tscn",
DialogicEvent.ValueType.NUMBER: "res://addons/dialogic/Editor/Events/Fields/field_number.tscn",
DialogicEvent.ValueType.VECTOR2: "res://addons/dialogic/Editor/Events/Fields/field_vector2.tscn",
DialogicEvent.ValueType.VECTOR3: "res://addons/dialogic/Editor/Events/Fields/field_vector3.tscn",
DialogicEvent.ValueType.VECTOR4: "res://addons/dialogic/Editor/Events/Fields/field_vector4.tscn",
DialogicEvent.ValueType.COLOR: "res://addons/dialogic/Editor/Events/Fields/field_color.tscn"
}
func build_editor(build_header:bool = true, build_body:bool = false) -> void:
var current_body_container: HFlowContainer = null
if build_body and body_was_build:
build_body = false
if build_body:
if body_was_build:
return
current_body_container = HFlowContainer.new()
%BodyContent.add_child(current_body_container)
body_was_build = true
for p in resource.get_event_editor_info():
field_list.append({'node':null, 'location':p.location})
if p.has('condition'):
field_list[-1]['condition'] = p.condition
if !build_body and p.location == 1:
continue
elif !build_header and p.location == 0:
continue
### --------------------------------------------------------------------
### 1. CREATE A NODE OF THE CORRECT TYPE FOR THE PROPERTY
var editor_node : Control
### LINEBREAK
if p.name == "linebreak":
field_list.remove_at(field_list.size()-1)
if !current_body_container.get_child_count():
current_body_container.queue_free()
current_body_container = HFlowContainer.new()
%BodyContent.add_child(current_body_container)
continue
elif p.field_type in FIELD_SCENES:
editor_node = load(FIELD_SCENES[p.field_type]).instantiate()
elif p.field_type == resource.ValueType.LABEL:
editor_node = Label.new()
editor_node.text = p.display_info.text
editor_node.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
editor_node.set('custom_colors/font_color', Color("#7b7b7b"))
editor_node.add_theme_color_override('font_color', resource.event_color.lerp(get_theme_color("font_color", "Editor"), 0.8))
elif p.field_type == resource.ValueType.BUTTON:
editor_node = Button.new()
editor_node.text = p.display_info.text
if typeof(p.display_info.icon) == TYPE_ARRAY:
editor_node.icon = callv('get_theme_icon', p.display_info.icon)
else:
editor_node.icon = p.display_info.icon
editor_node.flat = true
editor_node.custom_minimum_size.x = 30 * DialogicUtil.get_editor_scale()
editor_node.pressed.connect(p.display_info.callable)
## CUSTOM
elif p.field_type == resource.ValueType.CUSTOM:
if p.display_info.has('path'):
editor_node = load(p.display_info.path).instantiate()
## ELSE
else:
editor_node = Label.new()
editor_node.text = p.name
editor_node.add_theme_color_override('font_color', resource.event_color.lerp(get_theme_color("font_color", "Editor"), 0.8))
field_list[-1]['node'] = editor_node
### --------------------------------------------------------------------
# Some things need to be called BEFORE the field is added to the tree
if editor_node is DialogicVisualEditorField:
editor_node.event_resource = resource
editor_node.property_name = p.name
field_list[-1]['property'] = p.name
editor_node._load_display_info(p.display_info)
var location: Control = %HeaderContent
if p.location == 1:
location = current_body_container
location.add_child(editor_node)
# Some things need to be called AFTER the field is added to the tree
if editor_node is DialogicVisualEditorField:
# Only set the value if the field is visible
#
# This prevents events with varied value types (event_setting, event_variable)
# from injecting incorrect types into hidden fields, which then throw errors
# in the console.
if p.has('condition') and not p.condition.is_empty():
if _evaluate_visibility_condition(p):
editor_node._set_value(resource.get(p.name))
else:
editor_node._set_value(resource.get(p.name))
editor_node.value_changed.connect(set_property)
editor_node.tooltip_text = p.display_info.get('tooltip', '')
# Apply autofocus
if resource.created_by_button and p.display_info.get('autofocus', false):
editor_node.call_deferred('take_autofocus')
### --------------------------------------------------------------------
### 4. ADD LEFT AND RIGHT TEXT
var left_label: Label = null
var right_label: Label = null
if !p.get('left_text', '').is_empty():
left_label = Label.new()
left_label.text = p.get('left_text')
left_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
left_label.add_theme_color_override('font_color', resource.event_color.lerp(get_theme_color("font_color", "Editor"), 0.8))
location.add_child(left_label)
location.move_child(left_label, editor_node.get_index())
if !p.get('right_text', '').is_empty():
right_label = Label.new()
right_label.text = p.get('right_text')
right_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
right_label.add_theme_color_override('font_color', resource.event_color.lerp(get_theme_color("font_color", "Editor"), 0.8))
location.add_child(right_label)
location.move_child(right_label, editor_node.get_index()+1)
### --------------------------------------------------------------------
### 5. REGISTER CONDITION
if p.has('condition'):
field_list[-1]['condition'] = p.condition
if left_label:
field_list.append({'node': left_label, 'condition':p.condition, 'location':p.location})
if right_label:
field_list.append({'node': right_label, 'condition':p.condition, 'location':p.location})
if build_body:
if current_body_container.get_child_count() == 0:
expanded = false
%Body.visible = false
recalculate_field_visibility()
func recalculate_field_visibility() -> void:
has_any_enabled_body_content = false
for p in field_list:
if !p.has('condition') or p.condition.is_empty():
if p.node != null:
p.node.show()
if p.location == 1:
has_any_enabled_body_content = true
else:
if _evaluate_visibility_condition(p):
if p.node != null:
p.node.show()
if p.location == 1:
has_any_enabled_body_content = true
else:
if p.node != null:
p.node.hide()
%ToggleBodyVisibilityButton.visible = has_any_enabled_body_content
func set_property(property_name:String, value:Variant) -> void:
resource.set(property_name, value)
content_changed.emit()
if end_node:
end_node.parent_node_changed()
func _evaluate_visibility_condition(p: Dictionary) -> bool:
var expr := Expression.new()
expr.parse(p.condition)
var result: bool
if expr.execute([], resource):
result = true
else:
result = false
if expr.has_execute_failed():
printerr("[Dialogic] Failed executing visibility condition for '",p.get('property', 'unnamed'),"': " + expr.get_error_text())
return result
func _on_resource_ui_update_needed() -> void:
for node_info in field_list:
if node_info.node and node_info.node.has_method('set_value'):
# Only set the value if the field is visible
#
# This prevents events with varied value types (event_setting, event_variable)
# from injecting incorrect types into hidden fields, which then throw errors
# in the console.
if node_info.has('condition') and not node_info.condition.is_empty():
if _evaluate_visibility_condition(node_info):
node_info.node.set_value(resource.get(node_info.property))
else:
node_info.node.set_value(resource.get(node_info.property))
recalculate_field_visibility()
#region SIGNALS
################################################################################
func _on_collapse_toggled(toggled:bool) -> void:
collapsed = toggled
var timeline_editor = find_parent('VisualEditor')
if (timeline_editor != null):
# @todo select item and clear selection is marked as "private" in TimelineEditor.gd
# consider to make it "public" or add a public helper function
timeline_editor.indent_events()
func _on_ToggleBodyVisibility_toggled(button_pressed:bool) -> void:
if button_pressed and !body_was_build:
build_editor(false, true)
%ToggleBodyVisibilityButton.set_pressed_no_signal(button_pressed)
if button_pressed:
%ToggleBodyVisibilityButton.icon = get_theme_icon("CodeFoldDownArrow", "EditorIcons")
else:
%ToggleBodyVisibilityButton.icon = get_theme_icon("CodeFoldedRightArrow", "EditorIcons")
expanded = button_pressed
%Body.visible = button_pressed
if find_parent('VisualEditor') != null:
find_parent('VisualEditor').indent_events()
func _on_EventNode_gui_input(event:InputEvent) -> void:
if event is InputEventMouseButton and event.is_pressed() and event.button_index == 1:
grab_focus() # Grab focus to avoid copy pasting text or events
if event.double_click:
if has_any_enabled_body_content:
_on_ToggleBodyVisibility_toggled(!expanded)
# For opening the context menu
if event is InputEventMouseButton:
if event.button_index == MOUSE_BUTTON_RIGHT and event.pressed:
var popup :PopupMenu = get_parent().get_parent().get_node('EventPopupMenu')
popup.current_event = self
popup.popup_on_parent(Rect2(get_global_mouse_position(),Vector2()))
if resource.help_page_path == "":
popup.set_item_disabled(2, true)
else:
popup.set_item_disabled(2, false)