GodotProjects/Sprout/addons/dialogic/Editor/Events/EventNode/EventNode.gd
2023-01-03 16:54:53 -07:00

408 lines
14 KiB
GDScript

@tool
extends HBoxContainer
signal option_action(action_name)
signal content_changed()
# Resource
var resource : DialogicEvent
### internal node eferences
@onready var selected_style = %SelectedStyle
@onready var warning = %Warning
@onready var title_label = %TitleLabel
@onready var icon_texture = %IconTexture
@onready var header_content_container = %HeaderContent
@onready var body_container = %Body
@onready var body_content_container = %BodyContent
@onready var indent_node = %Indent
# is the body visible
var expanded = true
# does the body have elements?
var has_body_content = false
# for choice and condition
var end_node:Node = null:
get:
return end_node
set(node):
end_node = node
%CollapseButton.visible = true if end_node else false
var collapsed = false
### extarnal node references
var editor_reference
### the indent size
var indent_size = 15
var current_indent_level = 1
# Setting this to true will ignore the event while saving
# Useful for making placeholder events in drag and drop
var ignore_save = false
## *****************************************************************************
## PUBLIC METHODS
## *****************************************************************************
func visual_select():
selected_style.show()
func visual_deselect():
selected_style.hide()
func is_selected() -> bool:
return selected_style.visible
# called by the timeline before adding it to the tree
func load_data(data):
resource = data
func set_warning(text):
warning.show()
warning.tooltip_text = text
func remove_warning(text = ''):
if warning.tooltip_text == text or text == '':
warning.hide()
func set_indent(indent: int):
indent_node.custom_minimum_size = Vector2(indent_size * indent, 0)
indent_node.visible = indent != 0
current_indent_level = indent
queue_redraw()
## *****************************************************************************
## PRIVATE METHODS
## *****************************************************************************
func _set_event_icon(icon: Texture):
icon_texture.texture = icon
var _scale = DialogicUtil.get_editor_scale()
var cpanel = %IconPanelCenterC
var ip = %IconPanel
var ipc = icon_texture
# Resizing the icon acording to the scale
var icon_size = 32
cpanel.custom_minimum_size = Vector2(icon_size, icon_size) * _scale
ip.custom_minimum_size = cpanel.custom_minimum_size
ipc.custom_minimum_size = ip.custom_minimum_size
# Updating the theme properties to scale
var custom_style = ip.get_theme_stylebox('panel')
custom_style.corner_radius_top_left = 5 * _scale
custom_style.corner_radius_top_right = 5 * _scale
custom_style.corner_radius_bottom_left = 5 * _scale
custom_style.corner_radius_bottom_right = 5 * _scale
# Separation on the header
%Header.add_theme_constant_override("custom_constants/separation", 5 * _scale)
%BodySpacing.custom_minimum_size.x = title_label.position.x
func _on_OptionsControl_action(index):
if index == 0:
if not resource.help_page_path.is_empty():
OS.shell_open(resource.help_page_path)
elif index == 2:
emit_signal("option_action", "up")
elif index == 3:
emit_signal("option_action", "down")
elif index == 5:
emit_signal("option_action", "remove")
func _on_Indent_visibility_changed():
if not indent_node:
return
if resource:
if resource.needs_indentation:
if indent_node.visible:
remove_warning("This event needs a question event around it!")
else:
set_warning("This event needs a question event around it!")
func _request_selection():
# TODO doesn't work. I'm sure - JS
var timeline_editor = editor_reference.get_node_or_null('MainPanel/TimelineEditor')
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.select_item(self)
# called to inform event parts, that a focus is wanted
func focus():
pass
func toggle_collapse(toggled):
collapsed = toggled
$PanelContainer/MarginContainer/VBoxContainer/CollapsedBody.visible = toggled
var timeline_editor = find_parent('TimelineVisualEditor')
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 build_editor():
var p_list = resource._get_property_list()
var edit_conditions_list = []
var current_body_container = HFlowContainer.new()
%BodyContent.add_child(current_body_container)
for p in p_list:
### --------------------------------------------------------------------
### 1. CREATE A NODE OF THE CORRECT TYPE FOR THE PROPERTY
var editor_node
### OTHER
if p.name == "linebreak":
current_body_container = HFlowContainer.new()
%BodyContent.add_child(current_body_container)
continue
### STRINGS
elif p.dialogic_type == resource.ValueType.MultilineText:
editor_node = load("res://addons/dialogic/Editor/Events/Fields/MultilineText.tscn").instantiate()
elif p.dialogic_type == resource.ValueType.SinglelineText:
editor_node = load("res://addons/dialogic/Editor/Events/Fields/SinglelineText.tscn").instantiate()
editor_node.placeholder = p.display_info.get('placeholder', '')
elif p.dialogic_type == resource.ValueType.Bool:
editor_node = load("res://addons/dialogic/Editor/Events/Fields/Bool.tscn").instantiate()
elif p.dialogic_type == resource.ValueType.File:
editor_node = load("res://addons/dialogic/Editor/Events/Fields/FilePicker.tscn").instantiate()
editor_node.file_filter = p.display_info.get('file_filter', '')
editor_node.placeholder = p.display_info.get('placeholder', '')
editor_node.resource_icon = p.display_info.get('icon', null)
if editor_node.resource_icon == null and p.display_info.has('editor_icon'):
editor_node.resource_icon = callv('get_theme_icon', p.display_info.editor_icon)
elif p.dialogic_type == resource.ValueType.Condition:
editor_node = load("res://addons/dialogic/Editor/Events/Fields/ConditionPicker.tscn").instantiate()
## Complex Picker
elif p.dialogic_type == resource.ValueType.ComplexPicker:
editor_node = load("res://addons/dialogic/Editor/Events/Fields/ComplexPicker.tscn").instantiate()
editor_node.file_extension = p.display_info.get('file_extension', '')
editor_node.get_suggestions_func = p.display_info.get('suggestions_func', editor_node.get_suggestions_func)
editor_node.empty_text = p.display_info.get('empty_text', '')
editor_node.placeholder_text = p.display_info.get('placeholder', 'Select Resource')
editor_node.resource_icon = p.display_info.get('icon', null)
editor_node.enable_pretty_name = p.display_info.get('enable_pretty_name', false)
if editor_node.resource_icon == null and p.display_info.has('editor_icon'):
editor_node.resource_icon = callv('get_theme_icon', p.display_info.editor_icon)
## INTEGERS
elif p.dialogic_type == resource.ValueType.Integer:
editor_node = load("res://addons/dialogic/Editor/Events/Fields/Number.tscn").instantiate()
editor_node.use_int_mode()
editor_node.max = p.display_info.get('max', 9999)
editor_node.min = p.display_info.get('min', -9999)
elif p.dialogic_type == resource.ValueType.Float:
editor_node = load("res://addons/dialogic/Editor/Events/Fields/Number.tscn").instantiate()
editor_node.use_float_mode()
editor_node.max = p.display_info.get('max', 9999)
editor_node.min = p.display_info.get('min', 0)
elif p.dialogic_type == resource.ValueType.Decibel:
editor_node = load("res://addons/dialogic/Editor/Events/Fields/Number.tscn").instantiate()
editor_node.use_decibel_mode()
elif p.dialogic_type == resource.ValueType.FixedOptionSelector:
editor_node = load("res://addons/dialogic/Editor/Events/Fields/OptionSelector.tscn").instantiate()
if p.display_info.has('selector_options'):
editor_node.options = p.display_info.selector_options
if p.display_info.has('disabled'):
editor_node.disabled = p.display_info.disabled
elif p.dialogic_type == resource.ValueType.Vector2:
editor_node = load("res://addons/dialogic/Editor/Events/Fields/Vector2.tscn").instantiate()
elif p.dialogic_type == resource.ValueType.StringArray:
editor_node = load("res://addons/dialogic/Editor/Events/Fields/Array.tscn").instantiate()
elif p.dialogic_type == resource.ValueType.Label:
editor_node = Label.new()
editor_node.text = p.display_info.text
editor_node.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
elif p.dialogic_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.tooltip_text = p.display_info.tooltip
editor_node.pressed.connect(p.display_info.callable)
## CUSTOM
elif p.dialogic_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
### --------------------------------------------------------------------
### 2. ADD IT TO THE RIGHT PLACE (HEADER/BODY)
var location = %HeaderContent
if p.location == 1:
location = current_body_container
location.add_child(editor_node)
### --------------------------------------------------------------------
### 3. FILL THE NEW NODE WITH INFORMATION AND LISTEN TO CHANGES
if "event_resource" in editor_node:
editor_node.event_resource = resource
if 'property_name' in editor_node:
editor_node.property_name = p.name
if editor_node.has_method('set_value'):
if resource.get(p.name) != null: # Got an error here saying that "Cannot convert argument 1 from Nil to bool." so I'm adding this check
editor_node.set_value(resource.get(p.name))
if editor_node.has_signal('value_changed'):
editor_node.value_changed.connect(set_property)
if editor_node.has_method('set_left_text'):
editor_node.set_left_text(p.get('left_text', ''))
if editor_node.has_method('set_right_text'):
editor_node.set_right_text(p.get('right_text', ''))
if p.has('condition'):
edit_conditions_list.append([editor_node, p.condition])
has_body_content = true
if current_body_container.get_child_count() == 0:
has_body_content = false
expanded = false
body_container.visible = false
content_changed.connect(recalculate_edit_visibility.bind(edit_conditions_list))
recalculate_edit_visibility(edit_conditions_list)
func recalculate_edit_visibility(list):
for node_con in list:
if node_con[1].is_empty():
node_con[0].show()
else:
var expr = Expression.new()
expr.parse(node_con[1])
if expr.execute([], resource):
node_con[0].show()
else:
node_con[0].hide()
if expr.has_execute_failed():
var name = "unnamed" if "property_name" not in node_con[0] else node_con[0].property_name
printerr("(recalculate_edit_visibility) condition expression failed with error: " + expr.get_error_text())
%ExpandButton.visible = false
if body_content_container != null:
for node in body_content_container.get_children():
for sub_node in node.get_children():
if sub_node.visible:
%ExpandButton.visible = true
break
func set_property(property_name, value):
resource.set(property_name, value)
emit_signal('content_changed')
if end_node:
end_node.parent_node_changed()
func _update_color():
if resource.dialogic_color_name != '':
%IconPanel.self_modulate = DialogicUtil.get_color(resource.dialogic_color_name)
## *****************************************************************************
## OVERRIDES
## *****************************************************************************
func _ready():
## DO SOME STYLING
var _scale = DialogicUtil.get_editor_scale()
selected_style.modulate = get_theme_color("accent_color", "Editor")
warning.texture = get_theme_icon("NodeWarning", "EditorIcons")
warning.size = Vector2(16 * _scale, 16 * _scale)
title_label.add_theme_color_override("font_color", Color(1,1,1,1))
if not get_theme_constant("dark_theme", "Editor"):
title_label.add_theme_color_override("font_color", get_theme_color("font_color", "Editor"))
indent_size = indent_size * DialogicUtil.get_editor_scale()
%ExpandButton.icon = get_theme_icon("Tools", "EditorIcons")
if resource:
if resource.event_name:
#title_label.text = DTS.translate(resource.event_name)
title_label.text = resource.event_name
if resource._get_icon() != null:
_set_event_icon(resource._get_icon())
%IconPanel.self_modulate = resource.event_color
_on_ExpandButton_toggled(resource.expand_by_default)
# Only create this if it can collapse children events
if resource.can_contain_events:
var cb:HBoxContainer = HBoxContainer.new()
cb.name = 'CollapsedBody'
cb.visible = false
var cb_label:Label = Label.new()
cb_label.text = 'Contains Events (currently hidden)'
cb_label.size_flags_horizontal = 3
cb_label.horizontal_alignment = 1
cb.add_child(cb_label)
$PanelContainer/MarginContainer/VBoxContainer.add_child(cb)
set_focus_mode(1) # Allowing this node to grab focus
# signals
# TODO godot4 react to changes of the colors, the signal was removed
#ProjectSettings.project_settings_changed.connect(_update_color)
$PopupMenu.index_pressed.connect(_on_OptionsControl_action)
_on_Indent_visibility_changed()
%CollapseButton.toggled.connect(toggle_collapse)
%CollapseButton.icon = get_theme_icon("Collapse", "EditorIcons")
%CollapseButton.hide()
func _on_ExpandButton_toggled(button_pressed):
%ExpandButton.set_pressed_no_signal(button_pressed)
expanded = button_pressed
body_container.visible = button_pressed
get_parent().get_parent().queue_redraw()
func _on_EventNode_gui_input(event):
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_body_content:
_on_ExpandButton_toggled(!expanded)
# For opening the context menu
if event is InputEventMouseButton:
if event.button_index == MOUSE_BUTTON_RIGHT and event.pressed:
$PopupMenu.popup_on_parent(Rect2(get_global_mouse_position(),Vector2()))
if resource.help_page_path == "":
$PopupMenu.set_item_disabled(0, true)
else:
$PopupMenu.set_item_disabled(0, false)