First work on dialogic, resized guild, and started implementing portraits.
This commit is contained in:
@@ -0,0 +1,324 @@
|
||||
@tool
|
||||
extends Node
|
||||
|
||||
enum Modes {TEXT_EVENT_ONLY, FULL_HIGHLIGHTING}
|
||||
|
||||
var syntax_highlighter: SyntaxHighlighter = load("res://addons/dialogic/Editor/TimelineEditor/TextEditor/syntax_highlighter.gd").new()
|
||||
var text_syntax_highlighter: SyntaxHighlighter = load("res://addons/dialogic/Editor/TimelineEditor/TextEditor/syntax_highlighter.gd").new()
|
||||
|
||||
|
||||
# These RegEx's are used to deduce information from the current line for auto-completion
|
||||
|
||||
# To find the currently typed word and the symbol before
|
||||
var completion_word_regex := RegEx.new()
|
||||
# To find the shortcode of the current shortcode event (basically the type)
|
||||
var completion_shortcode_getter_regex := RegEx.new()
|
||||
# To find the parameter name of the current if typing a value
|
||||
var completion_shortcode_param_getter_regex := RegEx.new()
|
||||
# To find the value of a paramater that is being typed
|
||||
var completion_shortcode_value_regex := RegEx.new()
|
||||
|
||||
# Stores references to all shortcode events for parameter and value suggestions
|
||||
var shortcode_events := {}
|
||||
var custom_syntax_events := []
|
||||
var text_event: DialogicTextEvent = null
|
||||
|
||||
func _ready() -> void:
|
||||
# Compile RegEx's
|
||||
completion_word_regex.compile("(?<s>(\\W)|^)(?<word>\\w*)\\x{FFFF}")
|
||||
completion_shortcode_getter_regex.compile("\\[(?<code>\\w*)")
|
||||
completion_shortcode_param_getter_regex.compile("(?<param>\\w*)\\W*=\\s*\"?(\\w|\\s)*"+String.chr(0xFFFF))
|
||||
completion_shortcode_value_regex.compile(r'(\[|\s)[^\[\s=]*="(?<value>[^"$]*)'+String.chr(0xFFFF))
|
||||
|
||||
text_syntax_highlighter.mode = text_syntax_highlighter.Modes.TEXT_EVENT_ONLY
|
||||
|
||||
#region AUTO COMPLETION
|
||||
################################################################################
|
||||
|
||||
# Helper that gets the current line with a special character where the caret is
|
||||
func get_code_completion_line(text:CodeEdit) -> String:
|
||||
return text.get_line(text.get_caret_line()).insert(text.get_caret_column(), String.chr(0xFFFF)).strip_edges()
|
||||
|
||||
|
||||
# Helper that gets the currently typed word
|
||||
func get_code_completion_word(text:CodeEdit) -> String:
|
||||
var result := completion_word_regex.search(get_code_completion_line(text))
|
||||
return result.get_string('word') if result else ""
|
||||
|
||||
# Helper that gets the currently typed parameter
|
||||
func get_code_completion_parameter_value(text:CodeEdit) -> String:
|
||||
var result := completion_shortcode_value_regex.search(get_code_completion_line(text))
|
||||
return result.get_string('value') if result else ""
|
||||
|
||||
|
||||
# Helper that gets the symbol before the current word
|
||||
func get_code_completion_prev_symbol(text:CodeEdit) -> String:
|
||||
var result := completion_word_regex.search(get_code_completion_line(text))
|
||||
return result.get_string('s') if result else ""
|
||||
|
||||
|
||||
func get_line_untill_caret(line:String) -> String:
|
||||
return line.substr(0, line.find(String.chr(0xFFFF)))
|
||||
|
||||
|
||||
# Called if something was typed
|
||||
# Adds all kinds of options depending on the
|
||||
# content of the current line, the last word and the symbol that came before
|
||||
# Triggers opening of the popup
|
||||
func request_code_completion(force:bool, text:CodeEdit, mode:=Modes.FULL_HIGHLIGHTING) -> void:
|
||||
## TODO remove this once https://github.com/godotengine/godot/issues/38560 is fixed
|
||||
if mode != Modes.FULL_HIGHLIGHTING:
|
||||
return
|
||||
|
||||
# make sure shortcode event references are loaded
|
||||
if mode == Modes.FULL_HIGHLIGHTING:
|
||||
var hidden_events: Array = DialogicUtil.get_editor_setting('hidden_event_buttons', [])
|
||||
if shortcode_events.is_empty():
|
||||
for event in DialogicResourceUtil.get_event_cache():
|
||||
if event.get_shortcode() != 'default_shortcode':
|
||||
shortcode_events[event.get_shortcode()] = event
|
||||
|
||||
else:
|
||||
custom_syntax_events.append(event)
|
||||
if event.event_name in hidden_events:
|
||||
event.set_meta('hidden', true)
|
||||
if event is DialogicTextEvent:
|
||||
text_event = event
|
||||
# this is done to force-load the text effects regex which is used below
|
||||
event.load_text_effects()
|
||||
|
||||
# fill helpers
|
||||
var line := get_code_completion_line(text)
|
||||
var word := get_code_completion_word(text)
|
||||
var symbol := get_code_completion_prev_symbol(text)
|
||||
var line_part := get_line_untill_caret(line)
|
||||
|
||||
## Note on use of KIND types for options.
|
||||
# These types are mostly useless for us.
|
||||
# However I decidede to assign some special cases for them:
|
||||
# - KIND_PLAIN_TEXT is only shown if the beginnging of the option is already typed
|
||||
# !word.is_empty() and option.begins_with(word)
|
||||
# - KIND_CLASS is only shown if anything from the options is already typed
|
||||
# !word.is_empty() and word in option
|
||||
# - KIND_CONSTANT is shown and checked against the beginning
|
||||
# option.begins_with(word)
|
||||
# - KIND_MEMBER is shown and searched completely
|
||||
# word in option
|
||||
|
||||
## Note on VALUE key
|
||||
# The value key is used to store a potential closing string for the completion.
|
||||
# The completion will check if the string is already present and add it otherwise.
|
||||
|
||||
# Shortcode event suggestions
|
||||
if mode == Modes.FULL_HIGHLIGHTING and syntax_highlighter.line_is_shortcode_event(text.get_caret_line()):
|
||||
if symbol == '[':
|
||||
# suggest shortcodes if a shortcode event has just begun
|
||||
var shortcodes := shortcode_events.keys()
|
||||
shortcodes.sort()
|
||||
for shortcode in shortcodes:
|
||||
if shortcode_events[shortcode].get_meta('hidden', false):
|
||||
continue
|
||||
if shortcode_events[shortcode].get_shortcode_parameters().is_empty():
|
||||
text.add_code_completion_option(CodeEdit.KIND_MEMBER, shortcode, shortcode, shortcode_events[shortcode].event_color.lerp(syntax_highlighter.normal_color, 0.3), shortcode_events[shortcode]._get_icon())
|
||||
else:
|
||||
text.add_code_completion_option(CodeEdit.KIND_MEMBER, shortcode, shortcode+" ", shortcode_events[shortcode].event_color.lerp(syntax_highlighter.normal_color, 0.3), shortcode_events[shortcode]._get_icon())
|
||||
else:
|
||||
var full_event_text: String = syntax_highlighter.get_full_event(text.get_caret_line())
|
||||
var current_shortcode := completion_shortcode_getter_regex.search(full_event_text)
|
||||
if !current_shortcode:
|
||||
text.update_code_completion_options(false)
|
||||
return
|
||||
|
||||
var code := current_shortcode.get_string('code')
|
||||
if !code in shortcode_events.keys():
|
||||
text.update_code_completion_options(false)
|
||||
return
|
||||
|
||||
# suggest parameters
|
||||
if symbol == ' ' and line.count('"')%2 == 0:
|
||||
var parameters: Array = shortcode_events[code].get_shortcode_parameters().keys()
|
||||
for param in parameters:
|
||||
if !param+'=' in full_event_text:
|
||||
text.add_code_completion_option(CodeEdit.KIND_MEMBER, param, param+'="' , shortcode_events[code].event_color.lerp(syntax_highlighter.normal_color, 0.3), text.get_theme_icon("MemberProperty", "EditorIcons"))
|
||||
|
||||
# suggest values
|
||||
elif symbol == '=' or symbol == '"':
|
||||
var current_parameter_gex := completion_shortcode_param_getter_regex.search(line)
|
||||
if !current_parameter_gex:
|
||||
text.update_code_completion_options(false)
|
||||
return
|
||||
|
||||
var current_parameter := current_parameter_gex.get_string('param')
|
||||
if !shortcode_events[code].get_shortcode_parameters().has(current_parameter):
|
||||
text.update_code_completion_options(false)
|
||||
return
|
||||
if !shortcode_events[code].get_shortcode_parameters()[current_parameter].has('suggestions'):
|
||||
if typeof(shortcode_events[code].get_shortcode_parameters()[current_parameter].default) == TYPE_BOOL:
|
||||
suggest_bool(text, shortcode_events[code].event_color.lerp(syntax_highlighter.normal_color, 0.3))
|
||||
elif len(word) > 0:
|
||||
text.add_code_completion_option(CodeEdit.KIND_VARIABLE, word, word, shortcode_events[code].event_color.lerp(syntax_highlighter.normal_color, 0.3), text.get_theme_icon("GuiScrollArrowRight", "EditorIcons"), '" ')
|
||||
text.update_code_completion_options(true)
|
||||
return
|
||||
|
||||
var suggestions: Dictionary = shortcode_events[code].get_shortcode_parameters()[current_parameter]['suggestions'].call()
|
||||
suggest_custom_suggestions(suggestions, text, shortcode_events[code].event_color.lerp(syntax_highlighter.normal_color, 0.3))
|
||||
|
||||
# Force update and showing of the popup
|
||||
text.update_code_completion_options(true)
|
||||
return
|
||||
|
||||
|
||||
for event in custom_syntax_events:
|
||||
if mode == Modes.TEXT_EVENT_ONLY and !event is DialogicTextEvent:
|
||||
continue
|
||||
|
||||
if ! ' ' in line_part:
|
||||
event._get_start_code_completion(self, text)
|
||||
|
||||
if event.is_valid_event(line):
|
||||
event._get_code_completion(self, text, line, word, symbol)
|
||||
break
|
||||
|
||||
# Force update and showing of the popup
|
||||
text.update_code_completion_options(true)
|
||||
|
||||
|
||||
|
||||
# Helper that adds all characters as options
|
||||
func suggest_characters(text:CodeEdit, type := CodeEdit.KIND_MEMBER, text_event_start:=false) -> void:
|
||||
for character in DialogicResourceUtil.get_character_directory():
|
||||
var result: String = character
|
||||
if " " in character:
|
||||
result = '"'+character+'"'
|
||||
if text_event_start and load(DialogicResourceUtil.get_character_directory()[character]).portraits.is_empty():
|
||||
result += ':'
|
||||
text.add_code_completion_option(type, character, result, syntax_highlighter.character_name_color, load("res://addons/dialogic/Editor/Images/Resources/character.svg"))
|
||||
|
||||
|
||||
# Helper that adds all timelines as options
|
||||
func suggest_timelines(text:CodeEdit, type := CodeEdit.KIND_MEMBER, color:=Color()) -> void:
|
||||
for timeline in DialogicResourceUtil.get_timeline_directory():
|
||||
text.add_code_completion_option(type, timeline, timeline+'/', color, text.get_theme_icon("TripleBar", "EditorIcons"))
|
||||
|
||||
|
||||
func suggest_labels(text:CodeEdit, timeline:String='', end:='', color:=Color()) -> void:
|
||||
if timeline in DialogicResourceUtil.get_label_cache():
|
||||
for i in DialogicResourceUtil.get_label_cache()[timeline]:
|
||||
text.add_code_completion_option(CodeEdit.KIND_MEMBER, i, i+end, color, load("res://addons/dialogic/Modules/Jump/icon_label.png"))
|
||||
|
||||
|
||||
# Helper that adds all portraits of a given character as options
|
||||
func suggest_portraits(text:CodeEdit, character_name:String, end_check:=')') -> void:
|
||||
if !character_name in DialogicResourceUtil.get_character_directory():
|
||||
return
|
||||
var character_resource: DialogicCharacter = load(DialogicResourceUtil.get_character_directory()[character_name])
|
||||
for portrait in character_resource.portraits:
|
||||
text.add_code_completion_option(CodeEdit.KIND_MEMBER, portrait, portrait, syntax_highlighter.character_portrait_color, load("res://addons/dialogic/Editor/Images/Resources/character.svg"), end_check)
|
||||
if character_resource.portraits.is_empty():
|
||||
text.add_code_completion_option(CodeEdit.KIND_MEMBER, 'Has no portraits!', '', syntax_highlighter.character_portrait_color, load("res://addons/dialogic/Editor/Images/Pieces/warning.svg"))
|
||||
|
||||
|
||||
# Helper that adds all variable paths as options
|
||||
func suggest_variables(text:CodeEdit):
|
||||
for variable in DialogicUtil.list_variables(ProjectSettings.get_setting('dialogic/variables')):
|
||||
text.add_code_completion_option(CodeEdit.KIND_MEMBER, variable, variable, syntax_highlighter.variable_color, text.get_theme_icon("MemberProperty", "EditorIcons"), '}')
|
||||
|
||||
|
||||
# Helper that adds true and false as options
|
||||
func suggest_bool(text:CodeEdit, color:Color):
|
||||
text.add_code_completion_option(CodeEdit.KIND_VARIABLE, 'true', 'true', color, text.get_theme_icon("GuiChecked", "EditorIcons"), '" ')
|
||||
text.add_code_completion_option(CodeEdit.KIND_VARIABLE, 'false', 'false', color, text.get_theme_icon("GuiUnchecked", "EditorIcons"), '" ')
|
||||
|
||||
|
||||
func suggest_custom_suggestions(suggestions:Dictionary, text:CodeEdit, color:Color) -> void:
|
||||
for key in suggestions.keys():
|
||||
if suggestions[key].has('text_alt'):
|
||||
text.add_code_completion_option(CodeEdit.KIND_VARIABLE, key, suggestions[key].text_alt[0], color, suggestions[key].get('icon', null), '" ')
|
||||
else:
|
||||
text.add_code_completion_option(CodeEdit.KIND_VARIABLE, key, str(suggestions[key].value), color, suggestions[key].get('icon', null), '" ')
|
||||
|
||||
|
||||
# Filters the list of all possible options, depending on what was typed
|
||||
# Purpose of the different Kinds is explained in [_request_code_completion]
|
||||
func filter_code_completion_candidates(candidates:Array, text:CodeEdit) -> Array:
|
||||
var valid_candidates := []
|
||||
var current_word := get_code_completion_word(text)
|
||||
for candidate in candidates:
|
||||
if candidate.kind == text.KIND_PLAIN_TEXT:
|
||||
if !current_word.is_empty() and candidate.insert_text.begins_with(current_word):
|
||||
valid_candidates.append(candidate)
|
||||
elif candidate.kind == text.KIND_MEMBER:
|
||||
if current_word.is_empty() or current_word.to_lower() in candidate.insert_text.to_lower():
|
||||
valid_candidates.append(candidate)
|
||||
elif candidate.kind == text.KIND_VARIABLE:
|
||||
var current_param_value := get_code_completion_parameter_value(text)
|
||||
if current_param_value.is_empty() or current_param_value.to_lower() in candidate.insert_text.to_lower():
|
||||
valid_candidates.append(candidate)
|
||||
elif candidate.kind == text.KIND_CONSTANT:
|
||||
if current_word.is_empty() or candidate.insert_text.begins_with(current_word):
|
||||
valid_candidates.append(candidate)
|
||||
elif candidate.kind == text.KIND_CLASS:
|
||||
if !current_word.is_empty() and current_word.to_lower() in candidate.insert_text.to_lower():
|
||||
valid_candidates.append(candidate)
|
||||
return valid_candidates
|
||||
|
||||
|
||||
# Called when code completion was activated
|
||||
# Inserts the selected item
|
||||
func confirm_code_completion(replace:bool, text:CodeEdit) -> void:
|
||||
# Note: I decided to ALWAYS use replace mode, as dialogic is supposed to be beginner friendly
|
||||
|
||||
var code_completion := text.get_code_completion_option(text.get_code_completion_selected_index())
|
||||
|
||||
var word := get_code_completion_word(text)
|
||||
if code_completion.kind == CodeEdit.KIND_VARIABLE:
|
||||
word = get_code_completion_parameter_value(text)
|
||||
|
||||
text.remove_text(text.get_caret_line(), text.get_caret_column()-len(word), text.get_caret_line(), text.get_caret_column())
|
||||
|
||||
# Something has changed between 4.2 and 4.3
|
||||
# Probably about how carets are reset when text is removed or idk.
|
||||
# To keep compatibility with 4.2 for at least a while this should do the trick:
|
||||
# TODO: Remove once compatibility for 4.2 is dropped.
|
||||
if Engine.get_version_info().hex >= 0x040300:
|
||||
text.set_caret_column(text.get_caret_column())
|
||||
else:
|
||||
text.set_caret_column(text.get_caret_column()-len(word))
|
||||
|
||||
text.insert_text_at_caret(code_completion.insert_text)
|
||||
|
||||
if code_completion.has('default_value') and typeof(code_completion['default_value']) == TYPE_STRING:
|
||||
var next_letter := text.get_line(text.get_caret_line()).substr(text.get_caret_column(), len(code_completion['default_value']))
|
||||
if next_letter == code_completion['default_value'] or next_letter[0] == code_completion['default_value'][0]:
|
||||
text.set_caret_column(text.get_caret_column()+1)
|
||||
else:
|
||||
text.insert_text_at_caret(code_completion['default_value'])
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region SYMBOL CLICKING
|
||||
################################################################################
|
||||
|
||||
# Performs an action (like opening a link) when a valid symbol was clicked
|
||||
func symbol_lookup(symbol:String, line:int, column:int) -> void:
|
||||
if symbol in shortcode_events.keys():
|
||||
if !shortcode_events[symbol].help_page_path.is_empty():
|
||||
OS.shell_open(shortcode_events[symbol].help_page_path)
|
||||
if symbol in DialogicResourceUtil.get_character_directory():
|
||||
EditorInterface.edit_resource(DialogicResourceUtil.get_resource_from_identifier(symbol, 'dch'))
|
||||
if symbol in DialogicResourceUtil.get_timeline_directory():
|
||||
EditorInterface.edit_resource(DialogicResourceUtil.get_resource_from_identifier(symbol, 'dtl'))
|
||||
|
||||
|
||||
# Called to test if a symbol can be clicked
|
||||
func symbol_validate(symbol:String, text:CodeEdit) -> void:
|
||||
if symbol in shortcode_events.keys():
|
||||
if !shortcode_events[symbol].help_page_path.is_empty():
|
||||
text.set_symbol_lookup_word_as_valid(true)
|
||||
if symbol in DialogicResourceUtil.get_character_directory():
|
||||
text.set_symbol_lookup_word_as_valid(true)
|
||||
if symbol in DialogicResourceUtil.get_timeline_directory():
|
||||
text.set_symbol_lookup_word_as_valid(true)
|
||||
|
||||
#endregion
|
||||
@@ -0,0 +1 @@
|
||||
uid://c1vcrg85ibi42
|
||||
@@ -0,0 +1,201 @@
|
||||
@tool
|
||||
extends SyntaxHighlighter
|
||||
|
||||
## Syntax highlighter for the dialogic text timeline editor and text events in the visual editor.
|
||||
|
||||
enum Modes {TEXT_EVENT_ONLY, FULL_HIGHLIGHTING}
|
||||
var mode := Modes.FULL_HIGHLIGHTING
|
||||
|
||||
|
||||
## RegEx's
|
||||
var word_regex := RegEx.new()
|
||||
var region_regex := RegEx.new()
|
||||
var number_regex := RegEx.create_from_string(r"(\d|\.)+")
|
||||
var shortcode_regex := RegEx.create_from_string(r"\W*\[(?<id>\w*)(?<args>[^\]]*)?")
|
||||
var shortcode_param_regex := RegEx.create_from_string(r'((?<parameter>[^\s=]*)\s*=\s*"(?<value>([^=]|\\=)*)(?<!\\)")')
|
||||
|
||||
## Colors
|
||||
var normal_color: Color
|
||||
var translation_id_color: Color
|
||||
|
||||
var code_flow_color: Color
|
||||
var boolean_operator_color: Color
|
||||
var variable_color: Color
|
||||
var string_color: Color
|
||||
var character_name_color: Color
|
||||
var character_portrait_color: Color
|
||||
|
||||
var shortcode_events := {}
|
||||
var custom_syntax_events := []
|
||||
var text_event: DialogicTextEvent = null
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
update_colors()
|
||||
DialogicUtil.get_dialogic_plugin().get_editor_interface().get_base_control().theme_changed.connect(update_colors)
|
||||
|
||||
|
||||
func update_colors() -> void:
|
||||
if not DialogicUtil.get_dialogic_plugin():
|
||||
return
|
||||
var editor_settings: EditorSettings = DialogicUtil.get_dialogic_plugin().get_editor_interface().get_editor_settings()
|
||||
normal_color = editor_settings.get('text_editor/theme/highlighting/text_color')
|
||||
translation_id_color = editor_settings.get('text_editor/theme/highlighting/comment_color')
|
||||
|
||||
code_flow_color = editor_settings.get("text_editor/theme/highlighting/control_flow_keyword_color")
|
||||
boolean_operator_color = code_flow_color.lightened(0.5)
|
||||
variable_color = editor_settings.get('text_editor/theme/highlighting/engine_type_color')
|
||||
string_color = editor_settings.get('text_editor/theme/highlighting/string_color')
|
||||
character_name_color = editor_settings.get('text_editor/theme/highlighting/symbol_color').lerp(normal_color, 0.3)
|
||||
character_portrait_color = character_name_color.lerp(normal_color, 0.5)
|
||||
|
||||
|
||||
func _get_line_syntax_highlighting(line:int) -> Dictionary:
|
||||
var str_line := get_text_edit().get_line(line)
|
||||
|
||||
if shortcode_events.is_empty():
|
||||
for event in DialogicResourceUtil.get_event_cache():
|
||||
if event.get_shortcode() != 'default_shortcode':
|
||||
shortcode_events[event.get_shortcode()] = event
|
||||
else:
|
||||
custom_syntax_events.append(event)
|
||||
if event is DialogicTextEvent:
|
||||
text_event = event
|
||||
text_event.load_text_effects()
|
||||
|
||||
var dict := {}
|
||||
dict[0] = {'color':normal_color}
|
||||
|
||||
dict = color_translation_id(dict, str_line)
|
||||
|
||||
if mode == Modes.FULL_HIGHLIGHTING:
|
||||
if line_is_shortcode_event(line):
|
||||
var full_event := get_full_event(line)
|
||||
var result := shortcode_regex.search(full_event)
|
||||
if result:
|
||||
if result.get_string('id') in shortcode_events:
|
||||
if full_event.begins_with(str_line):
|
||||
dict[result.get_start('id')] = {"color":shortcode_events[result.get_string('id')].event_color.lerp(normal_color, 0.4)}
|
||||
dict[result.get_end('id')] = {"color":normal_color}
|
||||
|
||||
if result.get_string('args'):
|
||||
color_shortcode_content(dict, str_line, result.get_start('args'), result.get_end('args'), shortcode_events[result.get_string('id')].event_color)
|
||||
else:
|
||||
color_shortcode_content(dict, str_line, 0, 0, shortcode_events[result.get_string('id')].event_color)
|
||||
return fix_dict(dict)
|
||||
|
||||
else:
|
||||
for event in custom_syntax_events:
|
||||
if event.is_valid_event(str_line.strip_edges()):
|
||||
dict = event._get_syntax_highlighting(self, dict, str_line)
|
||||
return fix_dict(dict)
|
||||
|
||||
else:
|
||||
dict = text_event._get_syntax_highlighting(self, dict, str_line)
|
||||
return fix_dict(dict)
|
||||
|
||||
|
||||
func line_is_shortcode_event(line_idx:int) -> bool:
|
||||
var str_line := get_text_edit().get_line(line_idx)
|
||||
if text_event.text_effects_regex.search(str_line.get_slice(' ', 0)):
|
||||
return false
|
||||
|
||||
if str_line.strip_edges().begins_with("["):
|
||||
return true
|
||||
|
||||
if line_idx > 0 and get_text_edit().get_line(line_idx-1).ends_with('\\'):
|
||||
return line_is_shortcode_event(line_idx-1)
|
||||
|
||||
return false
|
||||
|
||||
|
||||
func get_full_event(line_idx:int) -> String:
|
||||
var str_line := get_text_edit().get_line(line_idx)
|
||||
var offset := 1
|
||||
# Add previous lines
|
||||
while get_text_edit().get_line(line_idx-offset).ends_with('\\'):
|
||||
str_line = get_text_edit().get_line(line_idx-offset).trim_suffix('\\')+"\n"+str_line
|
||||
offset += 1
|
||||
|
||||
# This is commented out, as it is not needed right now.
|
||||
# However without it, this isn't actually the full event.
|
||||
# Might need to be included some day.
|
||||
#offset = 0
|
||||
## Add following lines
|
||||
#while get_text_edit().get_line(line_idx+offset).ends_with('\\'):
|
||||
#str_line = str_line.trim_suffix('\\')+"\n"+get_text_edit().get_line(line_idx+offset)
|
||||
#offset += 1
|
||||
|
||||
return str_line
|
||||
|
||||
func fix_dict(dict:Dictionary) -> Dictionary:
|
||||
var d := {}
|
||||
var k := dict.keys()
|
||||
k.sort()
|
||||
for i in k:
|
||||
d[i] = dict[i]
|
||||
return d
|
||||
|
||||
|
||||
func color_condition(dict:Dictionary, line:String, from:int = 0, to:int = 0) -> Dictionary:
|
||||
dict = color_word(dict, code_flow_color, line, 'or', from, to)
|
||||
dict = color_word(dict, code_flow_color, line, 'and', from, to)
|
||||
dict = color_word(dict, code_flow_color, line, '==', from, to)
|
||||
dict = color_word(dict, code_flow_color, line, '!=', from, to)
|
||||
if !">=" in line:
|
||||
dict = color_word(dict, code_flow_color, line, '>', from, to)
|
||||
else:
|
||||
dict = color_word(dict, code_flow_color, line, '>=', from, to)
|
||||
if !"<=" in line:
|
||||
dict = color_word(dict, code_flow_color, line, '<', from, to)
|
||||
else:
|
||||
dict = color_word(dict, code_flow_color, line, '<=', from, to)
|
||||
dict = color_region(dict, variable_color, line, '{', '}', from, to)
|
||||
dict = color_region(dict, string_color, line, '"', '"', from, to)
|
||||
|
||||
|
||||
return dict
|
||||
|
||||
|
||||
func color_translation_id(dict:Dictionary, line:String) -> Dictionary:
|
||||
dict = color_region(dict, translation_id_color, line, '#id:', '')
|
||||
return dict
|
||||
|
||||
|
||||
func color_word(dict:Dictionary, color:Color, line:String, word:String, from:int= 0, to:int = 0) -> Dictionary:
|
||||
word_regex.compile("\\W(?<word>"+word+")\\W")
|
||||
if to <= from:
|
||||
to = len(line)-1
|
||||
for i in word_regex.search_all(line.substr(from, to-from+2)):
|
||||
dict[i.get_start('word')+from] = {'color':color}
|
||||
dict[i.get_end('word')+from] = {'color':normal_color}
|
||||
return dict
|
||||
|
||||
|
||||
func color_region(dict:Dictionary, color:Color, line:String, start:String, end:String, from:int = 0, to:int = 0, base_color:Color=normal_color) -> Dictionary:
|
||||
if start in "()[].":
|
||||
start = "\\"+start
|
||||
if end in "()[].":
|
||||
end = "\\"+end
|
||||
|
||||
if end.is_empty():
|
||||
region_regex.compile("(?<!\\\\)"+start+".*")
|
||||
else:
|
||||
region_regex.compile("(?<!\\\\)"+start+"((?!"+end+").)*"+end)
|
||||
if to <= from:
|
||||
to = len(line)-1
|
||||
for region in region_regex.search_all(line.substr(from, to-from+2)):
|
||||
dict[region.get_start()+from] = {'color':color}
|
||||
dict[region.get_end()+from] = {'color':base_color}
|
||||
return dict
|
||||
|
||||
|
||||
func color_shortcode_content(dict:Dictionary, line:String, from:int = 0, to:int = 0, base_color:=normal_color) -> Dictionary:
|
||||
if to <= from:
|
||||
to = len(line)-1
|
||||
var args_result := shortcode_param_regex.search_all(line.substr(from, to-from+2))
|
||||
for x in args_result:
|
||||
dict[x.get_start()+from] = {"color":base_color.lerp(normal_color, 0.5)}
|
||||
dict[x.get_start('value')+from-1] = {"color":base_color.lerp(normal_color, 0.7)}
|
||||
dict[x.get_end()+from] = {"color":normal_color}
|
||||
return dict
|
||||
@@ -0,0 +1 @@
|
||||
uid://bbiesd7cfjjvg
|
||||
@@ -0,0 +1,308 @@
|
||||
@tool
|
||||
extends CodeEdit
|
||||
|
||||
## Sub-Editor that allows editing timelines in a text format.
|
||||
|
||||
@onready var timeline_editor := get_parent().get_parent()
|
||||
@onready var code_completion_helper: Node= find_parent('EditorsManager').get_node('CodeCompletionHelper')
|
||||
|
||||
var label_regex := RegEx.create_from_string('label +(?<name>[^\n]+)')
|
||||
|
||||
func _ready() -> void:
|
||||
await find_parent('EditorView').ready
|
||||
syntax_highlighter = code_completion_helper.syntax_highlighter
|
||||
timeline_editor.editors_manager.sidebar.content_item_activated.connect(_on_content_item_clicked)
|
||||
|
||||
|
||||
func _on_text_editor_text_changed() -> void:
|
||||
timeline_editor.current_resource_state = DialogicEditor.ResourceStates.UNSAVED
|
||||
request_code_completion(true)
|
||||
$UpdateTimer.start()
|
||||
|
||||
|
||||
func clear_timeline() -> void:
|
||||
text = ''
|
||||
update_content_list()
|
||||
|
||||
|
||||
func load_timeline(timeline:DialogicTimeline) -> void:
|
||||
clear_timeline()
|
||||
|
||||
text = timeline.as_text()
|
||||
|
||||
timeline_editor.current_resource.set_meta("timeline_not_saved", false)
|
||||
clear_undo_history()
|
||||
|
||||
await get_tree().process_frame
|
||||
update_content_list()
|
||||
|
||||
|
||||
func save_timeline() -> void:
|
||||
if !timeline_editor.current_resource:
|
||||
return
|
||||
|
||||
var text_array: Array = text_timeline_to_array(text)
|
||||
|
||||
timeline_editor.current_resource.events = text_array
|
||||
timeline_editor.current_resource.events_processed = false
|
||||
ResourceSaver.save(timeline_editor.current_resource, timeline_editor.current_resource.resource_path)
|
||||
|
||||
timeline_editor.current_resource.set_meta("timeline_not_saved", false)
|
||||
timeline_editor.current_resource_state = DialogicEditor.ResourceStates.SAVED
|
||||
DialogicResourceUtil.update_directory('dtl')
|
||||
|
||||
|
||||
func text_timeline_to_array(text:String) -> Array:
|
||||
# Parse the lines down into an array
|
||||
var events := []
|
||||
|
||||
var lines := text.split('\n', true)
|
||||
var idx := -1
|
||||
|
||||
while idx < len(lines)-1:
|
||||
idx += 1
|
||||
var line: String = lines[idx]
|
||||
var line_stripped: String = line.strip_edges(true, true)
|
||||
events.append(line)
|
||||
|
||||
return events
|
||||
|
||||
|
||||
################################################################################
|
||||
## HELPFUL EDITOR FUNCTIONALITY
|
||||
################################################################################
|
||||
|
||||
func _gui_input(event):
|
||||
if not event is InputEventKey: return
|
||||
if not event.is_pressed(): return
|
||||
match event.as_text():
|
||||
"Ctrl+K":
|
||||
toggle_comment()
|
||||
"Alt+Up":
|
||||
move_line(-1)
|
||||
"Alt+Down":
|
||||
move_line(1)
|
||||
"Ctrl+Shift+D":
|
||||
duplicate_line()
|
||||
_:
|
||||
return
|
||||
get_viewport().set_input_as_handled()
|
||||
|
||||
# Toggle the selected lines as comments
|
||||
func toggle_comment() -> void:
|
||||
var cursor: Vector2 = Vector2(get_caret_column(), get_caret_line())
|
||||
var selection := Rect2i(
|
||||
Vector2i(get_selection_line(), get_selection_column()),
|
||||
# TODO When ditching godot 4.2, switch to this, the above methods have been deprecated in 4.3
|
||||
#Vector2i(get_selection_origin_line(), get_selection_origin_column()),
|
||||
Vector2i(get_caret_line(), get_caret_column()))
|
||||
var from: int = cursor.y
|
||||
var to: int = cursor.y
|
||||
if has_selection():
|
||||
from = get_selection_from_line()
|
||||
to = get_selection_to_line()
|
||||
|
||||
var lines: PackedStringArray = text.split("\n")
|
||||
var will_comment: bool = false
|
||||
for i in range(from, to+1):
|
||||
if not lines[i].begins_with("#"):
|
||||
will_comment = true
|
||||
|
||||
for i in range(from, to + 1):
|
||||
if will_comment:
|
||||
lines[i] = "#" + lines[i]
|
||||
else:
|
||||
lines[i] = lines[i].trim_prefix("#")
|
||||
|
||||
text = "\n".join(lines)
|
||||
if will_comment:
|
||||
cursor.x += 1
|
||||
selection.position.y += 1
|
||||
selection.size.y += 1
|
||||
else:
|
||||
cursor.x -= 1
|
||||
selection.position.y -= 1
|
||||
selection.size.y -= 1
|
||||
select(selection.position.x, selection.position.y, selection.size.x, selection.size.y)
|
||||
text_changed.emit()
|
||||
|
||||
|
||||
# Move the selected lines up or down
|
||||
func move_line(offset: int) -> void:
|
||||
offset = clamp(offset, -1, 1)
|
||||
|
||||
var cursor: Vector2 = Vector2(get_caret_column(), get_caret_line())
|
||||
var reselect: bool = false
|
||||
var from: int = cursor.y
|
||||
var to: int = cursor.y
|
||||
if has_selection():
|
||||
reselect = true
|
||||
from = get_selection_from_line()
|
||||
to = get_selection_to_line()
|
||||
|
||||
var lines := text.split("\n")
|
||||
|
||||
if from + offset < 0 or to + offset >= lines.size(): return
|
||||
|
||||
var target_from_index: int = from - 1 if offset == -1 else to + 1
|
||||
var target_to_index: int = to if offset == -1 else from
|
||||
var line_to_move: String = lines[target_from_index]
|
||||
lines.remove_at(target_from_index)
|
||||
lines.insert(target_to_index, line_to_move)
|
||||
|
||||
text = "\n".join(lines)
|
||||
|
||||
cursor.y += offset
|
||||
from += offset
|
||||
to += offset
|
||||
if reselect:
|
||||
select(from, 0, to, get_line_width(to))
|
||||
set_caret_line(cursor.y)
|
||||
set_caret_column(cursor.x)
|
||||
text_changed.emit()
|
||||
|
||||
|
||||
func duplicate_line() -> void:
|
||||
var cursor: Vector2 = Vector2(get_caret_column(), get_caret_line())
|
||||
var from: int = cursor.y
|
||||
var to: int = cursor.y+1
|
||||
if has_selection():
|
||||
from = get_selection_from_line()
|
||||
to = get_selection_to_line()+1
|
||||
|
||||
var lines := text.split("\n")
|
||||
var lines_to_dupl: PackedStringArray = lines.slice(from, to)
|
||||
|
||||
text = "\n".join(lines.slice(0, from)+lines_to_dupl+lines.slice(from))
|
||||
|
||||
set_caret_line(cursor.y+to-from)
|
||||
set_caret_column(cursor.x)
|
||||
text_changed.emit()
|
||||
|
||||
|
||||
# Allows dragging files into the editor
|
||||
func _can_drop_data(at_position:Vector2, data:Variant) -> bool:
|
||||
if typeof(data) == TYPE_DICTIONARY and 'files' in data.keys() and len(data.files) == 1:
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
# Allows dragging files into the editor
|
||||
func _drop_data(at_position:Vector2, data:Variant) -> void:
|
||||
if typeof(data) == TYPE_DICTIONARY and 'files' in data.keys() and len(data.files) == 1:
|
||||
set_caret_column(get_line_column_at_pos(at_position).x)
|
||||
set_caret_line(get_line_column_at_pos(at_position).y)
|
||||
var result: String = data.files[0]
|
||||
if get_line(get_caret_line())[get_caret_column()-1] != '"':
|
||||
result = '"'+result
|
||||
if get_line(get_caret_line())[get_caret_column()] != '"':
|
||||
result = result+'"'
|
||||
|
||||
insert_text_at_caret(result)
|
||||
|
||||
|
||||
func _on_update_timer_timeout() -> void:
|
||||
update_content_list()
|
||||
|
||||
|
||||
func update_content_list() -> void:
|
||||
var labels: PackedStringArray = []
|
||||
for i in label_regex.search_all(text):
|
||||
labels.append(i.get_string('name'))
|
||||
timeline_editor.editors_manager.sidebar.update_content_list(labels)
|
||||
|
||||
|
||||
func _on_content_item_clicked(label:String) -> void:
|
||||
if label == "~ Top":
|
||||
set_caret_line(0)
|
||||
set_caret_column(0)
|
||||
adjust_viewport_to_caret()
|
||||
return
|
||||
|
||||
for i in label_regex.search_all(text):
|
||||
if i.get_string('name') == label:
|
||||
set_caret_column(0)
|
||||
set_caret_line(text.count('\n', 0, i.get_start()+1))
|
||||
center_viewport_to_caret()
|
||||
return
|
||||
|
||||
|
||||
func _search_timeline(search_text:String) -> bool:
|
||||
set_search_text(search_text)
|
||||
queue_redraw()
|
||||
set_meta("current_search", search_text)
|
||||
|
||||
return search(search_text, 0, 0, 0).y != -1
|
||||
|
||||
|
||||
func _search_navigate_down() -> void:
|
||||
search_navigate(false)
|
||||
|
||||
|
||||
func _search_navigate_up() -> void:
|
||||
search_navigate(true)
|
||||
|
||||
|
||||
func search_navigate(navigate_up := false) -> void:
|
||||
if not has_meta("current_search"):
|
||||
return
|
||||
var pos: Vector2i
|
||||
var search_from_line := 0
|
||||
var search_from_column := 0
|
||||
if has_selection():
|
||||
if navigate_up:
|
||||
search_from_line = get_selection_from_line()
|
||||
search_from_column = get_selection_from_column()-1
|
||||
if search_from_column == -1:
|
||||
if search_from_line == 0:
|
||||
search_from_line = get_line_count()
|
||||
else:
|
||||
search_from_line -= 1
|
||||
search_from_column = max(get_line(search_from_line).length()-1,0)
|
||||
else:
|
||||
search_from_line = get_selection_to_line()
|
||||
search_from_column = get_selection_to_column()
|
||||
else:
|
||||
search_from_line = get_caret_line()
|
||||
search_from_column = get_caret_column()
|
||||
|
||||
pos = search(get_meta("current_search"), 4 if navigate_up else 0, search_from_line, search_from_column)
|
||||
select(pos.y, pos.x, pos.y, pos.x+len(get_meta("current_search")))
|
||||
set_caret_line(pos.y)
|
||||
center_viewport_to_caret()
|
||||
queue_redraw()
|
||||
|
||||
|
||||
################################################################################
|
||||
## AUTO COMPLETION
|
||||
################################################################################
|
||||
|
||||
# Called if something was typed
|
||||
func _request_code_completion(force:bool):
|
||||
code_completion_helper.request_code_completion(force, self)
|
||||
|
||||
|
||||
# Filters the list of all possible options, depending on what was typed
|
||||
# Purpose of the different Kinds is explained in [_request_code_completion]
|
||||
func _filter_code_completion_candidates(candidates:Array) -> Array:
|
||||
return code_completion_helper.filter_code_completion_candidates(candidates, self)
|
||||
|
||||
|
||||
# Called when code completion was activated
|
||||
# Inserts the selected item
|
||||
func _confirm_code_completion(replace:bool) -> void:
|
||||
code_completion_helper.confirm_code_completion(replace, self)
|
||||
|
||||
|
||||
################################################################################
|
||||
## SYMBOL CLICKING
|
||||
################################################################################
|
||||
|
||||
# Performs an action (like opening a link) when a valid symbol was clicked
|
||||
func _on_symbol_lookup(symbol, line, column):
|
||||
code_completion_helper.symbol_lookup(symbol, line, column)
|
||||
|
||||
|
||||
# Called to test if a symbol can be clicked
|
||||
func _on_symbol_validate(symbol:String) -> void:
|
||||
code_completion_helper.symbol_validate(symbol, self)
|
||||
@@ -0,0 +1 @@
|
||||
uid://bhrn8xx7nmb5o
|
||||
@@ -0,0 +1,32 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://defdeav8rli6o"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/dialogic/Editor/TimelineEditor/TextEditor/timeline_editor_text.gd" id="1_1kbx2"]
|
||||
|
||||
[node name="TimelineTextEditor" type="CodeEdit"]
|
||||
offset_top = 592.0
|
||||
offset_right = 1024.0
|
||||
offset_bottom = 600.0
|
||||
theme_override_constants/line_spacing = 10
|
||||
wrap_mode = 1
|
||||
highlight_current_line = true
|
||||
draw_tabs = true
|
||||
minimap_draw = true
|
||||
caret_blink = true
|
||||
line_folding = true
|
||||
gutters_draw_line_numbers = true
|
||||
gutters_draw_fold_gutter = true
|
||||
code_completion_enabled = true
|
||||
code_completion_prefixes = Array[String](["[", "{"])
|
||||
indent_automatic = true
|
||||
auto_brace_completion_enabled = true
|
||||
auto_brace_completion_highlight_matching = true
|
||||
script = ExtResource("1_1kbx2")
|
||||
|
||||
[node name="UpdateTimer" type="Timer" parent="."]
|
||||
one_shot = true
|
||||
|
||||
[connection signal="code_completion_requested" from="." to="." method="_on_code_completion_requested"]
|
||||
[connection signal="symbol_lookup" from="." to="." method="_on_symbol_lookup"]
|
||||
[connection signal="symbol_validate" from="." to="." method="_on_symbol_validate"]
|
||||
[connection signal="text_changed" from="." to="." method="_on_text_editor_text_changed"]
|
||||
[connection signal="timeout" from="UpdateTimer" to="." method="_on_update_timer_timeout"]
|
||||
@@ -0,0 +1,63 @@
|
||||
@tool
|
||||
extends Button
|
||||
|
||||
@export var visible_name := ""
|
||||
@export var event_id := ""
|
||||
@export var event_icon: Texture:
|
||||
get:
|
||||
return event_icon
|
||||
set(texture):
|
||||
event_icon = texture
|
||||
icon = event_icon
|
||||
@export var event_sorting_index: int = 0
|
||||
@export var resource: DialogicEvent
|
||||
@export var dialogic_color_name := ""
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
tooltip_text = visible_name
|
||||
|
||||
custom_minimum_size = Vector2(get_theme_font("font", "Label").get_string_size(text).x+35,30) * DialogicUtil.get_editor_scale()
|
||||
|
||||
add_theme_color_override("font_color", get_theme_color("font_color", "Editor"))
|
||||
add_theme_color_override("font_color_hover", get_theme_color("accent_color", "Editor"))
|
||||
apply_base_button_style()
|
||||
|
||||
|
||||
func apply_base_button_style() -> void:
|
||||
var nstyle: StyleBoxFlat = get_parent().get_theme_stylebox('normal', 'Button').duplicate()
|
||||
nstyle.border_width_left = 5 * DialogicUtil.get_editor_scale()
|
||||
add_theme_stylebox_override('normal', nstyle)
|
||||
var hstyle: StyleBoxFlat = get_parent().get_theme_stylebox('hover', 'Button').duplicate()
|
||||
hstyle.border_width_left = 5 * DialogicUtil.get_editor_scale()
|
||||
add_theme_stylebox_override('hover', hstyle)
|
||||
set_color(resource.event_color)
|
||||
|
||||
|
||||
func set_color(color:Color) -> void:
|
||||
var style := get_theme_stylebox('normal', 'Button')
|
||||
style.border_color = color
|
||||
add_theme_stylebox_override('normal', style)
|
||||
style = get_theme_stylebox('hover', 'Button')
|
||||
style.border_color = color
|
||||
add_theme_stylebox_override('hover', style)
|
||||
|
||||
|
||||
func toggle_name(on:= false) -> void:
|
||||
if !on:
|
||||
text = ""
|
||||
custom_minimum_size = Vector2(40, 40) * DialogicUtil.get_editor_scale()
|
||||
var style := get_theme_stylebox('normal', 'Button')
|
||||
style.bg_color = style.border_color.darkened(0.2)
|
||||
add_theme_stylebox_override('normal', style)
|
||||
style = get_theme_stylebox('hover', 'Button')
|
||||
style.bg_color = style.border_color
|
||||
add_theme_stylebox_override('hover', style)
|
||||
else:
|
||||
text = visible_name
|
||||
custom_minimum_size = Vector2(get_theme_font("font", 'Label').get_string_size(text).x+35,30) * DialogicUtil.get_editor_scale()
|
||||
apply_base_button_style()
|
||||
|
||||
|
||||
func _on_button_down() -> void:
|
||||
find_parent('VisualEditor').get_node('%TimelineArea').start_dragging(1, resource)
|
||||
@@ -0,0 +1 @@
|
||||
uid://bjqbwvg0uvm7f
|
||||
@@ -0,0 +1,46 @@
|
||||
[gd_scene load_steps=4 format=3 uid="uid://depcrpeh3f4rv"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/dialogic/Editor/TimelineEditor/VisualEditor/AddEventButton.gd" id="1_s43sc"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_qx31r"]
|
||||
content_margin_left = 4.0
|
||||
content_margin_top = 4.0
|
||||
content_margin_right = 4.0
|
||||
content_margin_bottom = 4.0
|
||||
bg_color = Color(0.1, 0.1, 0.1, 0.6)
|
||||
border_width_left = 3
|
||||
border_color = Color(0.231373, 0.545098, 0.94902, 1)
|
||||
corner_radius_top_left = 3
|
||||
corner_radius_top_right = 3
|
||||
corner_radius_bottom_right = 3
|
||||
corner_radius_bottom_left = 3
|
||||
corner_detail = 5
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_n1o16"]
|
||||
content_margin_left = 4.0
|
||||
content_margin_top = 4.0
|
||||
content_margin_right = 4.0
|
||||
content_margin_bottom = 4.0
|
||||
bg_color = Color(0.225, 0.225, 0.225, 0.6)
|
||||
border_width_left = 3
|
||||
border_color = Color(0.231373, 0.545098, 0.94902, 1)
|
||||
corner_radius_top_left = 3
|
||||
corner_radius_top_right = 3
|
||||
corner_radius_bottom_right = 3
|
||||
corner_radius_bottom_left = 3
|
||||
corner_detail = 5
|
||||
|
||||
[node name="AddEventButton" type="Button"]
|
||||
custom_minimum_size = Vector2(44, 30)
|
||||
offset_right = 97.0
|
||||
offset_bottom = 42.0
|
||||
tooltip_text = "S"
|
||||
theme_override_colors/font_color = Color(0, 0, 0, 1)
|
||||
theme_override_styles/normal = SubResource("StyleBoxFlat_qx31r")
|
||||
theme_override_styles/hover = SubResource("StyleBoxFlat_n1o16")
|
||||
alignment = 0
|
||||
expand_icon = true
|
||||
script = ExtResource("1_s43sc")
|
||||
visible_name = "S"
|
||||
|
||||
[connection signal="button_down" from="." to="." method="_on_button_down"]
|
||||
@@ -0,0 +1,204 @@
|
||||
@tool
|
||||
extends ScrollContainer
|
||||
|
||||
# Script of the TimelineArea (that contains the event blocks).
|
||||
# Manages the drawing of the event lines and event dragging.
|
||||
|
||||
|
||||
enum DragTypes {NOTHING, NEW_EVENT, EXISTING_EVENTS}
|
||||
|
||||
var drag_type: DragTypes = DragTypes.NOTHING
|
||||
var drag_data: Variant
|
||||
var drag_to_position := 0:
|
||||
set(value):
|
||||
drag_to_position = value
|
||||
drag_to_position_updated = true
|
||||
var dragging := false
|
||||
var drag_to_position_updated := false
|
||||
|
||||
|
||||
signal drag_completed(type, index, data)
|
||||
signal drag_canceled()
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
resized.connect(add_extra_scroll_area_to_timeline)
|
||||
%Timeline.child_entered_tree.connect(add_extra_scroll_area_to_timeline)
|
||||
|
||||
# This prevents the view to turn black if you are editing this scene in Godot
|
||||
if find_parent('EditorView'):
|
||||
%TimelineArea.get_theme_color("background_color", "CodeEdit")
|
||||
|
||||
|
||||
#region EVENT DRAGGING
|
||||
################################################################################
|
||||
|
||||
func start_dragging(type:DragTypes, data:Variant) -> void:
|
||||
dragging = true
|
||||
drag_type = type
|
||||
drag_data = data
|
||||
drag_to_position_updated = false
|
||||
|
||||
|
||||
func _input(event:InputEvent) -> void:
|
||||
if !dragging:
|
||||
return
|
||||
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT:
|
||||
if !event.is_pressed():
|
||||
finish_dragging()
|
||||
|
||||
|
||||
func _process(delta:float) -> void:
|
||||
if !dragging:
|
||||
return
|
||||
|
||||
for child in %Timeline.get_children():
|
||||
if (child.global_position.y < get_global_mouse_position().y) and \
|
||||
(child.global_position.y+child.size.y > get_global_mouse_position().y):
|
||||
|
||||
if get_global_mouse_position().y > child.global_position.y+(child.size.y/2.0):
|
||||
drag_to_position = child.get_index()+1
|
||||
queue_redraw()
|
||||
else:
|
||||
drag_to_position = child.get_index()
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func finish_dragging() -> void:
|
||||
dragging = false
|
||||
if drag_to_position_updated and get_global_rect().has_point(get_global_mouse_position()):
|
||||
drag_completed.emit(drag_type, drag_to_position, drag_data)
|
||||
else:
|
||||
drag_canceled.emit()
|
||||
queue_redraw()
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region LINE DRAWING
|
||||
################################################################################
|
||||
|
||||
func _draw() -> void:
|
||||
var line_width := 5 * DialogicUtil.get_editor_scale()
|
||||
var horizontal_line_length := 100 * DialogicUtil.get_editor_scale()
|
||||
var color_multiplier := Color(1,1,1,0.25)
|
||||
var selected_color_multiplier := Color(1,1,1,1)
|
||||
|
||||
|
||||
## Draw Event Lines
|
||||
for idx in range($Timeline.get_child_count()):
|
||||
var block: Control = $Timeline.get_child(idx)
|
||||
|
||||
if not "resource" in block:
|
||||
continue
|
||||
|
||||
if not block.visible:
|
||||
continue
|
||||
|
||||
if block.resource is DialogicEndBranchEvent:
|
||||
continue
|
||||
|
||||
if not (block.has_any_enabled_body_content or block.resource.can_contain_events):
|
||||
continue
|
||||
|
||||
var icon_panel_height: int = block.get_node('%IconPanel').size.y
|
||||
var rect_position: Vector2 = block.get_node('%IconPanel').global_position+Vector2(0,1)*block.get_node('%IconPanel').size+Vector2(0,-4)
|
||||
var color: Color = block.resource.event_color
|
||||
|
||||
if block.is_selected() or block.end_node and block.end_node.is_selected():
|
||||
color *= selected_color_multiplier
|
||||
else:
|
||||
color *= color_multiplier
|
||||
|
||||
if block.expanded and not block.resource.can_contain_events:
|
||||
draw_rect(Rect2(rect_position-global_position+Vector2(line_width, 0), Vector2(line_width, block.size.y-block.get_node('%IconPanel').size.y)), color)
|
||||
|
||||
## If the indentation has not changed, nothing else happens
|
||||
if idx >= $Timeline.get_child_count()-1 or block.current_indent_level >= $Timeline.get_child(idx+1).current_indent_level:
|
||||
continue
|
||||
|
||||
## Draw connection between opening and end branch events
|
||||
if block.resource.can_contain_events:
|
||||
var end_node: Node = block.end_node
|
||||
|
||||
if end_node != null:
|
||||
var v_length: float = end_node.global_position.y+end_node.size.y/2-rect_position.y
|
||||
#var rect_size := Vector2(line_width, )
|
||||
var offset := Vector2(line_width, 0)
|
||||
|
||||
# Draw vertical line
|
||||
draw_rect(Rect2(rect_position-global_position+offset, Vector2(line_width, v_length)), color)
|
||||
# Draw horizonal line (on END BRANCH event)
|
||||
draw_rect(Rect2(
|
||||
rect_position.x+line_width-global_position.x+offset.x,
|
||||
rect_position.y+v_length-line_width-global_position.y,
|
||||
horizontal_line_length-offset.x,
|
||||
line_width),
|
||||
color)
|
||||
|
||||
if block.resource.wants_to_group:
|
||||
var group_color: Color = block.resource.event_color*color_multiplier
|
||||
var group_starter := true
|
||||
if idx != 0:
|
||||
var block_above := $Timeline.get_child(idx-1)
|
||||
if block_above.resource.event_name == block.resource.event_name:
|
||||
group_starter = false
|
||||
if block_above.resource is DialogicEndBranchEvent and block_above.parent_node.resource.event_name == block.resource.event_name:
|
||||
group_starter = false
|
||||
|
||||
## Draw small horizontal line on any event in group
|
||||
draw_rect(Rect2(
|
||||
rect_position.x-global_position.x-line_width,
|
||||
rect_position.y-global_position.y-icon_panel_height/2,
|
||||
line_width,
|
||||
line_width),
|
||||
group_color)
|
||||
|
||||
if group_starter:
|
||||
## Find the last event in the group (or that events END BRANCH)
|
||||
var sub_idx := idx
|
||||
var group_end_idx := idx
|
||||
while sub_idx < $Timeline.get_child_count()-1:
|
||||
sub_idx += 1
|
||||
if $Timeline.get_child(sub_idx).current_indent_level == block.current_indent_level-1:
|
||||
group_end_idx = sub_idx-1
|
||||
break
|
||||
|
||||
var end_node := $Timeline.get_child(group_end_idx)
|
||||
|
||||
var offset := Vector2(-2*line_width, -icon_panel_height/2)
|
||||
var v_length: float = end_node.global_position.y - rect_position.y + icon_panel_height
|
||||
|
||||
## Draw vertical line
|
||||
draw_rect(Rect2(
|
||||
rect_position.x - global_position.x + offset.x,
|
||||
rect_position.y - global_position.y + offset.y,
|
||||
line_width,
|
||||
v_length),
|
||||
group_color)
|
||||
|
||||
|
||||
## Draw line that indicates the dragging position
|
||||
if dragging and get_global_rect().has_point(get_global_mouse_position()):
|
||||
var height: int = 0
|
||||
if drag_to_position == %Timeline.get_child_count():
|
||||
height = %Timeline.get_child(-1).global_position.y+%Timeline.get_child(-1).size.y-global_position.y-(line_width/2.0)
|
||||
else:
|
||||
height = %Timeline.get_child(drag_to_position).global_position.y-global_position.y-(line_width/2.0)
|
||||
|
||||
draw_line(Vector2(0, height), Vector2(size.x*0.9, height), get_theme_color("accent_color", "Editor"), line_width*.3)
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region SPACE BELOW
|
||||
################################################################################
|
||||
|
||||
func add_extra_scroll_area_to_timeline(fake_arg:Variant=null) -> void:
|
||||
if %Timeline.get_children().size() > 4:
|
||||
%Timeline.custom_minimum_size.y = 0
|
||||
%Timeline.size.y = 0
|
||||
if %Timeline.size.y + 200 > %TimelineArea.size.y:
|
||||
%Timeline.custom_minimum_size = Vector2(0, %Timeline.size.y + 200)
|
||||
|
||||
#endregion
|
||||
@@ -0,0 +1 @@
|
||||
uid://b0v2hiopoyt4k
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1 @@
|
||||
uid://cq544uu526bir
|
||||
@@ -0,0 +1,111 @@
|
||||
[gd_scene load_steps=10 format=3 uid="uid://ysqbusmy0qma"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/dialogic/Editor/TimelineEditor/VisualEditor/timeline_editor_visual.gd" id="1_8smxc"]
|
||||
[ext_resource type="Theme" uid="uid://cqst728xxipcw" path="res://addons/dialogic/Editor/Theme/MainTheme.tres" id="2_x0fhp"]
|
||||
[ext_resource type="Script" path="res://addons/dialogic/Editor/TimelineEditor/VisualEditor/TimelineArea.gd" id="3_sap1x"]
|
||||
[ext_resource type="Script" path="res://addons/dialogic/Editor/Events/EventBlock/event_right_click_menu.gd" id="4_ugiq6"]
|
||||
|
||||
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_phyjj"]
|
||||
content_margin_top = 10.0
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_plab4"]
|
||||
bg_color = Color(0, 0, 0, 1)
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_dov6v"]
|
||||
content_margin_left = 4.0
|
||||
content_margin_top = 4.0
|
||||
content_margin_right = 4.0
|
||||
content_margin_bottom = 4.0
|
||||
bg_color = Color(1, 0.365, 0.365, 1)
|
||||
draw_center = false
|
||||
border_width_left = 2
|
||||
border_width_top = 2
|
||||
border_width_right = 2
|
||||
border_width_bottom = 2
|
||||
corner_detail = 1
|
||||
|
||||
[sub_resource type="Image" id="Image_y3447"]
|
||||
data = {
|
||||
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
|
||||
"format": "RGBA8",
|
||||
"height": 16,
|
||||
"mipmaps": false,
|
||||
"width": 16
|
||||
}
|
||||
|
||||
[sub_resource type="ImageTexture" id="ImageTexture_vg181"]
|
||||
image = SubResource("Image_y3447")
|
||||
|
||||
[node name="TimelineVisualEditor" type="MarginContainer"]
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme_override_constants/margin_left = 5
|
||||
theme_override_constants/margin_right = 5
|
||||
theme_override_constants/margin_bottom = 5
|
||||
script = ExtResource("1_8smxc")
|
||||
|
||||
[node name="View" type="HSplitContainer" parent="."]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
theme = ExtResource("2_x0fhp")
|
||||
|
||||
[node name="TimelineArea" type="ScrollContainer" parent="View"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
theme_override_styles/panel = SubResource("StyleBoxEmpty_phyjj")
|
||||
script = ExtResource("3_sap1x")
|
||||
|
||||
[node name="Timeline" type="VBoxContainer" parent="View/TimelineArea"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="EventPopupMenu" type="PopupMenu" parent="View/TimelineArea"]
|
||||
unique_name_in_owner = true
|
||||
size = Vector2i(165, 124)
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_plab4")
|
||||
theme_override_styles/hover = SubResource("StyleBoxFlat_dov6v")
|
||||
item_count = 6
|
||||
item_0/text = "Documentation"
|
||||
item_0/icon = SubResource("ImageTexture_vg181")
|
||||
item_0/id = 0
|
||||
item_1/text = ""
|
||||
item_1/id = -1
|
||||
item_1/separator = true
|
||||
item_2/text = "Move up"
|
||||
item_2/icon = SubResource("ImageTexture_vg181")
|
||||
item_2/id = 2
|
||||
item_3/text = "Move down"
|
||||
item_3/icon = SubResource("ImageTexture_vg181")
|
||||
item_3/id = 3
|
||||
item_4/text = ""
|
||||
item_4/id = -1
|
||||
item_4/separator = true
|
||||
item_5/text = "Delete"
|
||||
item_5/icon = SubResource("ImageTexture_vg181")
|
||||
item_5/id = 5
|
||||
script = ExtResource("4_ugiq6")
|
||||
|
||||
[node name="RightSidebar" type="ScrollContainer" parent="View"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(50, 0)
|
||||
layout_mode = 2
|
||||
size_flags_stretch_ratio = 0.2
|
||||
horizontal_scroll_mode = 0
|
||||
|
||||
[node name="EventContainer" type="VBoxContainer" parent="View/RightSidebar"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
size_flags_stretch_ratio = 0.2
|
||||
|
||||
[connection signal="drag_completed" from="View/TimelineArea" to="." method="_on_timeline_area_drag_completed"]
|
||||
[connection signal="index_pressed" from="View/TimelineArea/EventPopupMenu" to="." method="_on_event_popup_menu_index_pressed"]
|
||||
[connection signal="resized" from="View/RightSidebar" to="." method="_on_right_sidebar_resized"]
|
||||
44
addons/dialogic/Editor/TimelineEditor/test_timeline_scene.gd
Normal file
44
addons/dialogic/Editor/TimelineEditor/test_timeline_scene.gd
Normal file
@@ -0,0 +1,44 @@
|
||||
extends Control
|
||||
|
||||
func _ready() -> void:
|
||||
print("[Dialogic] Testing scene was started.")
|
||||
if not ProjectSettings.get_setting('internationalization/locale/test', "").is_empty():
|
||||
print("Testing locale is: ", ProjectSettings.get_setting('internationalization/locale/test'))
|
||||
$PauseIndictator.hide()
|
||||
|
||||
var scene: Node = DialogicUtil.autoload().Styles.load_style(DialogicUtil.get_editor_setting('current_test_style', ''))
|
||||
if not scene is CanvasLayer:
|
||||
if scene is Control:
|
||||
scene.position = get_viewport_rect().size/2.0
|
||||
if scene is Node2D:
|
||||
scene.position = get_viewport_rect().size/2.0
|
||||
|
||||
randomize()
|
||||
var current_timeline: String = DialogicUtil.get_editor_setting("current_timeline_path", "")
|
||||
if not current_timeline:
|
||||
get_tree().quit()
|
||||
DialogicUtil.autoload().start(current_timeline)
|
||||
DialogicUtil.autoload().timeline_ended.connect(get_tree().quit)
|
||||
DialogicUtil.autoload().signal_event.connect(receive_event_signal)
|
||||
DialogicUtil.autoload().text_signal.connect(receive_text_signal)
|
||||
|
||||
func receive_event_signal(argument:Variant) -> void:
|
||||
print("[Dialogic] Encountered a signal event: ", argument)
|
||||
|
||||
func receive_text_signal(argument:String) -> void:
|
||||
print("[Dialogic] Encountered a signal in text: ", argument)
|
||||
|
||||
func _input(event:InputEvent) -> void:
|
||||
if event is InputEventKey and event.pressed and event.keycode == KEY_ESCAPE:
|
||||
DialogicUtil.autoload().paused = !DialogicUtil.autoload().paused
|
||||
$PauseIndictator.visible = DialogicUtil.autoload().paused
|
||||
|
||||
if (event is InputEventMouseButton
|
||||
and event.is_pressed()
|
||||
and event.button_index == MOUSE_BUTTON_MIDDLE):
|
||||
var auto_skip: DialogicAutoSkip = DialogicUtil.autoload().Inputs.auto_skip
|
||||
var is_auto_skip_enabled := auto_skip.enabled
|
||||
|
||||
auto_skip.disable_on_unread_text = false
|
||||
auto_skip.enabled = not is_auto_skip_enabled
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
uid://baq2hmi868prw
|
||||
@@ -0,0 +1,25 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://ud18ke1g2nw4"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/dialogic/Editor/TimelineEditor/test_timeline_scene.gd" id="1_bamud"]
|
||||
|
||||
[node name="TestTimelineScene" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("1_bamud")
|
||||
|
||||
[node name="PauseIndictator" type="Label" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 1
|
||||
anchor_left = 1.0
|
||||
anchor_right = 1.0
|
||||
offset_left = -65.0
|
||||
offset_top = 7.0
|
||||
offset_right = -8.0
|
||||
offset_bottom = 33.0
|
||||
grow_horizontal = 0
|
||||
text = "Paused"
|
||||
metadata/_edit_layout_mode = 1
|
||||
239
addons/dialogic/Editor/TimelineEditor/timeline_editor.gd
Normal file
239
addons/dialogic/Editor/TimelineEditor/timeline_editor.gd
Normal file
@@ -0,0 +1,239 @@
|
||||
@tool
|
||||
extends DialogicEditor
|
||||
|
||||
## Editor that holds both the visual and the text timeline editors.
|
||||
|
||||
# references
|
||||
enum EditorMode {VISUAL, TEXT}
|
||||
|
||||
var current_editor_mode := EditorMode.VISUAL
|
||||
var play_timeline_button: Button = null
|
||||
|
||||
|
||||
## Overwrite. Register to the editor manager in here.
|
||||
func _register() -> void:
|
||||
resource_unsaved.connect(_on_resource_unsaved)
|
||||
resource_saved.connect(_on_resource_saved)
|
||||
|
||||
# register editor
|
||||
editors_manager.register_resource_editor('dtl', self)
|
||||
# add timeline button
|
||||
var add_timeline_button: Button = editors_manager.add_icon_button(
|
||||
load("res://addons/dialogic/Editor/Images/Toolbar/add-timeline.svg"),
|
||||
"Add Timeline",
|
||||
self)
|
||||
add_timeline_button.pressed.connect(_on_create_timeline_button_pressed)
|
||||
add_timeline_button.shortcut = Shortcut.new()
|
||||
add_timeline_button.shortcut.events.append(InputEventKey.new())
|
||||
add_timeline_button.shortcut.events[0].keycode = KEY_1
|
||||
add_timeline_button.shortcut.events[0].ctrl_pressed = true
|
||||
# play timeline button
|
||||
play_timeline_button = editors_manager.add_custom_button(
|
||||
"Play Timeline",
|
||||
get_theme_icon("PlayScene", "EditorIcons"),
|
||||
self)
|
||||
play_timeline_button.pressed.connect(play_timeline)
|
||||
play_timeline_button.tooltip_text = "Play the current timeline (CTRL+F5)"
|
||||
if OS.get_name() == "macOS":
|
||||
play_timeline_button.tooltip_text = "Play the current timeline (CTRL+B)"
|
||||
|
||||
%VisualEditor.load_event_buttons()
|
||||
|
||||
current_editor_mode = DialogicUtil.get_editor_setting('timeline_editor_mode', 0)
|
||||
|
||||
match current_editor_mode:
|
||||
EditorMode.VISUAL:
|
||||
%VisualEditor.show()
|
||||
%TextEditor.hide()
|
||||
%SwitchEditorMode.text = "Text Editor"
|
||||
EditorMode.TEXT:
|
||||
%VisualEditor.hide()
|
||||
%TextEditor.show()
|
||||
%SwitchEditorMode.text = "Visual Editor"
|
||||
|
||||
$NoTimelineScreen.show()
|
||||
play_timeline_button.disabled = true
|
||||
|
||||
|
||||
func _get_title() -> String:
|
||||
return "Timeline"
|
||||
|
||||
|
||||
func _get_icon() -> Texture:
|
||||
return get_theme_icon("TripleBar", "EditorIcons")
|
||||
|
||||
|
||||
## If this editor supports editing resources, load them here (overwrite in subclass)
|
||||
func _open_resource(resource:Resource) -> void:
|
||||
current_resource = resource
|
||||
current_resource_state = ResourceStates.SAVED
|
||||
match current_editor_mode:
|
||||
EditorMode.VISUAL:
|
||||
%VisualEditor.load_timeline(current_resource)
|
||||
EditorMode.TEXT:
|
||||
%TextEditor.load_timeline(current_resource)
|
||||
$NoTimelineScreen.hide()
|
||||
%TimelineName.text = DialogicResourceUtil.get_unique_identifier(current_resource.resource_path)
|
||||
play_timeline_button.disabled = false
|
||||
|
||||
|
||||
## If this editor supports editing resources, save them here (overwrite in subclass)
|
||||
func _save() -> void:
|
||||
match current_editor_mode:
|
||||
EditorMode.VISUAL:
|
||||
%VisualEditor.save_timeline()
|
||||
EditorMode.TEXT:
|
||||
%TextEditor.save_timeline()
|
||||
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
if event is InputEventKey:
|
||||
var keycode := KEY_F5
|
||||
if OS.get_name() == "macOS":
|
||||
keycode = KEY_B
|
||||
if event.keycode == keycode and event.pressed:
|
||||
if Input.is_key_pressed(KEY_CTRL):
|
||||
play_timeline()
|
||||
|
||||
if event.keycode == KEY_F and event.pressed:
|
||||
if Input.is_key_pressed(KEY_CTRL):
|
||||
if is_ancestor_of(get_viewport().gui_get_focus_owner()):
|
||||
search_timeline()
|
||||
|
||||
|
||||
## Method to play the current timeline. Connected to the button in the sidebar.
|
||||
func play_timeline() -> void:
|
||||
_save()
|
||||
|
||||
var dialogic_plugin := DialogicUtil.get_dialogic_plugin()
|
||||
|
||||
# Save the current opened timeline
|
||||
DialogicUtil.set_editor_setting('current_timeline_path', current_resource.resource_path)
|
||||
|
||||
DialogicUtil.get_dialogic_plugin().get_editor_interface().play_custom_scene("res://addons/dialogic/Editor/TimelineEditor/test_timeline_scene.tscn")
|
||||
|
||||
|
||||
## Method to switch from visual to text editor (and vice versa). Connected to the button in the sidebar.
|
||||
func toggle_editor_mode() -> void:
|
||||
match current_editor_mode:
|
||||
EditorMode.VISUAL:
|
||||
current_editor_mode = EditorMode.TEXT
|
||||
%VisualEditor.save_timeline()
|
||||
%VisualEditor.hide()
|
||||
%TextEditor.show()
|
||||
%TextEditor.load_timeline(current_resource)
|
||||
%SwitchEditorMode.text = "Visual Editor"
|
||||
EditorMode.TEXT:
|
||||
current_editor_mode = EditorMode.VISUAL
|
||||
%TextEditor.save_timeline()
|
||||
%TextEditor.hide()
|
||||
%VisualEditor.load_timeline(current_resource)
|
||||
%VisualEditor.show()
|
||||
%SwitchEditorMode.text = "Text Editor"
|
||||
_on_search_text_changed(%Search.text)
|
||||
DialogicUtil.set_editor_setting('timeline_editor_mode', current_editor_mode)
|
||||
|
||||
|
||||
func _on_resource_unsaved() -> void:
|
||||
if current_resource:
|
||||
current_resource.set_meta("timeline_not_saved", true)
|
||||
|
||||
|
||||
func _on_resource_saved() -> void:
|
||||
if current_resource:
|
||||
current_resource.set_meta("timeline_not_saved", false)
|
||||
|
||||
|
||||
func new_timeline(path:String) -> void:
|
||||
_save()
|
||||
var new_timeline := DialogicTimeline.new()
|
||||
new_timeline.resource_path = path
|
||||
new_timeline.set_meta('timeline_not_saved', true)
|
||||
var err := ResourceSaver.save(new_timeline)
|
||||
EditorInterface.get_resource_filesystem().update_file(new_timeline.resource_path)
|
||||
DialogicResourceUtil.update_directory('dtl')
|
||||
editors_manager.edit_resource(new_timeline)
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
$NoTimelineScreen.add_theme_stylebox_override("panel", get_theme_stylebox("Background", "EditorStyles"))
|
||||
|
||||
# switch editor mode button
|
||||
%SwitchEditorMode.text = "Text editor"
|
||||
%SwitchEditorMode.icon = get_theme_icon("ArrowRight", "EditorIcons")
|
||||
%SwitchEditorMode.pressed.connect(toggle_editor_mode)
|
||||
%SwitchEditorMode.custom_minimum_size.x = 200 * DialogicUtil.get_editor_scale()
|
||||
|
||||
%SearchClose.icon = get_theme_icon("Close", "EditorIcons")
|
||||
%SearchUp.icon = get_theme_icon("MoveUp", "EditorIcons")
|
||||
%SearchDown.icon = get_theme_icon("MoveDown", "EditorIcons")
|
||||
|
||||
|
||||
|
||||
func _on_create_timeline_button_pressed() -> void:
|
||||
editors_manager.show_add_resource_dialog(
|
||||
new_timeline,
|
||||
'*.dtl; DialogicTimeline',
|
||||
'Create new timeline',
|
||||
'timeline',
|
||||
)
|
||||
|
||||
|
||||
func _clear() -> void:
|
||||
current_resource = null
|
||||
current_resource_state = ResourceStates.SAVED
|
||||
match current_editor_mode:
|
||||
EditorMode.VISUAL:
|
||||
%VisualEditor.clear_timeline_nodes()
|
||||
EditorMode.TEXT:
|
||||
%TextEditor.clear_timeline()
|
||||
$NoTimelineScreen.show()
|
||||
play_timeline_button.disabled = true
|
||||
|
||||
|
||||
func get_current_editor() -> Node:
|
||||
if current_editor_mode == 1:
|
||||
return %TextEditor
|
||||
return %VisualEditor
|
||||
|
||||
#region SEARCH
|
||||
|
||||
func search_timeline() -> void:
|
||||
%SearchSection.show()
|
||||
if get_viewport().gui_get_focus_owner() is TextEdit:
|
||||
%Search.text = get_viewport().gui_get_focus_owner().get_selected_text()
|
||||
_on_search_text_changed(%Search.text)
|
||||
else:
|
||||
%Search.text = ""
|
||||
%Search.grab_focus()
|
||||
|
||||
|
||||
func _on_close_search_pressed() -> void:
|
||||
%SearchSection.hide()
|
||||
%Search.text = ""
|
||||
_on_search_text_changed('')
|
||||
|
||||
|
||||
func _on_search_text_changed(new_text: String) -> void:
|
||||
var editor: Node = null
|
||||
var anything_found: bool = get_current_editor()._search_timeline(new_text)
|
||||
if anything_found or new_text.is_empty():
|
||||
%SearchLabel.hide()
|
||||
%Search.add_theme_color_override("font_color", get_theme_color("font_color", "Editor"))
|
||||
else:
|
||||
%SearchLabel.show()
|
||||
%SearchLabel.add_theme_color_override("font_color", get_theme_color("error_color", "Editor"))
|
||||
%Search.add_theme_color_override("font_color", get_theme_color("error_color", "Editor"))
|
||||
%SearchLabel.text = "No Match"
|
||||
|
||||
|
||||
func _on_search_down_pressed() -> void:
|
||||
get_current_editor()._search_navigate_down()
|
||||
|
||||
|
||||
func _on_search_up_pressed() -> void:
|
||||
get_current_editor()._search_navigate_up()
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
uid://b5mvy2md6bp77
|
||||
163
addons/dialogic/Editor/TimelineEditor/timeline_editor.tscn
Normal file
163
addons/dialogic/Editor/TimelineEditor/timeline_editor.tscn
Normal file
@@ -0,0 +1,163 @@
|
||||
[gd_scene load_steps=10 format=3 uid="uid://crce0na84rhfd"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/dialogic/Editor/TimelineEditor/timeline_editor.gd" id="1_4aceh"]
|
||||
[ext_resource type="PackedScene" uid="uid://ysqbusmy0qma" path="res://addons/dialogic/Editor/TimelineEditor/VisualEditor/timeline_editor_visual.tscn" id="2_qs7vc"]
|
||||
[ext_resource type="PackedScene" uid="uid://dbpkta2tjsqim" path="res://addons/dialogic/Editor/Common/hint_tooltip_icon.tscn" id="2_yqd26"]
|
||||
[ext_resource type="PackedScene" uid="uid://defdeav8rli6o" path="res://addons/dialogic/Editor/TimelineEditor/TextEditor/timeline_editor_text.tscn" id="3_up2bn"]
|
||||
[ext_resource type="Script" path="res://addons/dialogic/Editor/TimelineEditor/TextEditor/syntax_highlighter.gd" id="4_1t6bf"]
|
||||
|
||||
[sub_resource type="Image" id="Image_43fqw"]
|
||||
data = {
|
||||
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
|
||||
"format": "RGBA8",
|
||||
"height": 16,
|
||||
"mipmaps": false,
|
||||
"width": 16
|
||||
}
|
||||
|
||||
[sub_resource type="ImageTexture" id="ImageTexture_lvr8x"]
|
||||
image = SubResource("Image_43fqw")
|
||||
|
||||
[sub_resource type="SyntaxHighlighter" id="SyntaxHighlighter_7lpql"]
|
||||
script = ExtResource("4_1t6bf")
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_lpeon"]
|
||||
content_margin_left = 4.0
|
||||
content_margin_top = 4.0
|
||||
content_margin_right = 4.0
|
||||
content_margin_bottom = 4.0
|
||||
bg_color = Color(1, 0.365, 0.365, 1)
|
||||
draw_center = false
|
||||
border_width_left = 2
|
||||
border_width_top = 2
|
||||
border_width_right = 2
|
||||
border_width_bottom = 2
|
||||
corner_detail = 1
|
||||
|
||||
[node name="Timeline" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("1_4aceh")
|
||||
|
||||
[node name="VBox" type="VBoxContainer" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="HBox" type="HBoxContainer" parent="VBox"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="TimelineName" type="Label" parent="VBox/HBox"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
theme_type_variation = &"DialogicTitle"
|
||||
text = "Cool Name"
|
||||
|
||||
[node name="NameTooltip" parent="VBox/HBox" instance=ExtResource("2_yqd26")]
|
||||
layout_mode = 2
|
||||
tooltip_text = "This unique identifier is based on the file name. You can change it in the Reference Manager.
|
||||
This is what you should use in a jump event to reference this timeline.
|
||||
|
||||
You can also use this name in Dialogic.start()."
|
||||
texture = SubResource("ImageTexture_lvr8x")
|
||||
hint_text = "This unique identifier is based on the file name. You can change it in the Reference Manager.
|
||||
This is what you should use in a jump event to reference this timeline.
|
||||
|
||||
You can also use this name in Dialogic.start()."
|
||||
|
||||
[node name="SwitchEditorMode" type="Button" parent="VBox/HBox"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(200, 0)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 10
|
||||
size_flags_vertical = 4
|
||||
tooltip_text = "Switch between Text Editor and Visual Editor"
|
||||
text = "Text editor"
|
||||
icon = SubResource("ImageTexture_lvr8x")
|
||||
|
||||
[node name="VisualEditor" parent="VBox" instance=ExtResource("2_qs7vc")]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
theme_override_constants/margin_left = 0
|
||||
theme_override_constants/margin_top = 0
|
||||
theme_override_constants/margin_right = 0
|
||||
theme_override_constants/margin_bottom = 0
|
||||
|
||||
[node name="TextEditor" parent="VBox" instance=ExtResource("3_up2bn")]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
syntax_highlighter = SubResource("SyntaxHighlighter_7lpql")
|
||||
symbol_lookup_on_click = true
|
||||
line_folding = false
|
||||
gutters_draw_fold_gutter = false
|
||||
|
||||
[node name="SearchSection" type="HBoxContainer" parent="VBox"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Search" type="LineEdit" parent="VBox/SearchSection"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
placeholder_text = "Search"
|
||||
|
||||
[node name="SearchLabel" type="Label" parent="VBox/SearchSection"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
|
||||
[node name="SearchUp" type="Button" parent="VBox/SearchSection"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
|
||||
[node name="SearchDown" type="Button" parent="VBox/SearchSection"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
|
||||
[node name="SearchClose" type="Button" parent="VBox/SearchSection"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
|
||||
[node name="NoTimelineScreen" type="PanelContainer" parent="."]
|
||||
visible = false
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_lpeon")
|
||||
|
||||
[node name="CenterContainer" type="CenterContainer" parent="NoTimelineScreen"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="NoTimelineScreen/CenterContainer"]
|
||||
custom_minimum_size = Vector2(250, 0)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label" type="Label" parent="NoTimelineScreen/CenterContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "No timeline opened.
|
||||
Create a timeline or double-click one in the file system dock."
|
||||
horizontal_alignment = 1
|
||||
autowrap_mode = 3
|
||||
|
||||
[node name="CreateTimelineButton" type="Button" parent="NoTimelineScreen/CenterContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "Create New Timeline"
|
||||
|
||||
[connection signal="text_changed" from="VBox/SearchSection/Search" to="." method="_on_search_text_changed"]
|
||||
[connection signal="pressed" from="VBox/SearchSection/SearchUp" to="." method="_on_search_up_pressed"]
|
||||
[connection signal="pressed" from="VBox/SearchSection/SearchDown" to="." method="_on_search_down_pressed"]
|
||||
[connection signal="pressed" from="VBox/SearchSection/SearchClose" to="." method="_on_close_search_pressed"]
|
||||
[connection signal="pressed" from="NoTimelineScreen/CenterContainer/VBoxContainer/CreateTimelineButton" to="." method="_on_create_timeline_button_pressed"]
|
||||
Reference in New Issue
Block a user