408 lines
14 KiB
GDScript
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)
|