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
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)
|