tool extends HSplitContainer var editor_reference var timeline_name: String = '' var timeline_file: String = '' var current_timeline: Dictionary = {} onready var master_tree = get_node('../MasterTreeContainer/MasterTree') onready var timeline = $TimelineArea/TimeLine onready var events_warning = $ScrollContainer/EventContainer/EventsWarning var hovered_item = null var selected_style : StyleBoxFlat = load("res://addons/dialogic/Editor/Events/selected_styleboxflat.tres") var selected_style_text : StyleBoxFlat = load("res://addons/dialogic/Editor/Events/selected_styleboxflat_text_event.tres") var saved_style : StyleBoxFlat var selected_item : Node var moving_piece = null var piece_was_dragged = false func _ready(): var modifier = '' var _scale = get_constant("inspector_margin", "Editor") _scale = _scale * 0.125 $ScrollContainer.rect_min_size.x = 180 if _scale == 1.25: modifier = '-1.25' $ScrollContainer.rect_min_size.x = 200 if _scale == 1.5: modifier = '-1.25' $ScrollContainer.rect_min_size.x = 200 if _scale == 1.75: modifier = '-1.25' $ScrollContainer.rect_min_size.x = 390 if _scale == 2: modifier = '-2' $ScrollContainer.rect_min_size.x = 390 # We connect all the event buttons to the event creation functions for b in $ScrollContainer/EventContainer.get_children(): if b is Button: if b.name == 'ButtonQuestion': b.connect('pressed', self, "_on_ButtonQuestion_pressed", []) elif b.name == 'IfCondition': b.connect('pressed', self, "_on_ButtonCondition_pressed", []) else: b.connect('pressed', self, "_create_event_button_pressed", [b.name]) var style = $TimelineArea.get('custom_styles/bg') style.set('bg_color', get_color("dark_color_1", "Editor")) func delete_event(): # get next element var next = min(timeline.get_child_count() - 1, selected_item.get_index() + 1) var next_node = timeline.get_child(next) if (next_node == selected_item): next_node = null # remove current selected_item.get_parent().remove_child(selected_item) selected_item.queue_free() selected_item = null # select next if (next_node != null): _select_item(next_node) else: if (timeline.get_child_count() > 0): next_node = timeline.get_child(max(0, timeline.get_child_count() - 1)) if (next_node != null): _select_item(next_node) indent_events() func _input(event): # some shortcuts need to get handled in the common input event # especially CTRL-based # because certain godot controls swallow events (like textedit) # we protect this with is_visible_in_tree to not # invoke a shortcut by accident if (event is InputEventKey and event is InputEventWithModifiers and is_visible_in_tree()): # CTRL UP if (event.pressed and event.alt == false and event.shift == false and event.control == true and event.scancode == KEY_UP and event.echo == false ): # select previous if (selected_item != null): var prev = max(0, selected_item.get_index() - 1) var prev_node = timeline.get_child(prev) if (prev_node != selected_item): _select_item(prev_node) get_tree().set_input_as_handled() pass # CTRL DOWN if (event.pressed and event.alt == false and event.shift == false and event.control == true and event.scancode == KEY_DOWN and event.echo == false ): # select next if (selected_item != null): var next = min(timeline.get_child_count() - 1, selected_item.get_index() + 1) var next_node = timeline.get_child(next) if (next_node != selected_item): _select_item(next_node) get_tree().set_input_as_handled() pass # CTRL DELETE if (event.pressed and event.alt == false and event.shift == false and event.control == true and event.scancode == KEY_DELETE and event.echo == false ): if (selected_item != null): delete_event() get_tree().set_input_as_handled() pass # CTRL T if (event.pressed and event.alt == false and event.shift == false and event.control == true and event.scancode == KEY_T and event.echo == false ): var new_text = create_event("TextBlock") _select_item(new_text) indent_events() get_tree().set_input_as_handled() pass func _unhandled_key_input(event): if (event is InputEventWithModifiers): # ALT UP if (event.pressed and event.alt == true and event.shift == false and event.control == false and event.scancode == KEY_UP and event.echo == false ): # move selected up if (selected_item != null): move_block(selected_item, "up") indent_events() get_tree().set_input_as_handled() pass # ALT DOWN if (event.pressed and event.alt == true and event.shift == false and event.control == false and event.scancode == KEY_DOWN and event.echo == false ): # move selected down if (selected_item != null): move_block(selected_item, "down") indent_events() get_tree().set_input_as_handled() pass pass func _process(delta): if moving_piece != null: var current_position = get_global_mouse_position() var node_position = moving_piece.rect_global_position.y var height = get_block_height(moving_piece) var up_offset = get_block_height(get_block_above(moving_piece)) var down_offset = get_block_height(get_block_below(moving_piece)) if up_offset != null: up_offset = (up_offset / 2) + 5 if current_position.y < node_position - up_offset: move_block(moving_piece, 'up') piece_was_dragged = true if down_offset != null: down_offset = height + (down_offset / 2) + 5 if current_position.y > node_position + down_offset: move_block(moving_piece, 'down') piece_was_dragged = true func _clear_selection(): if selected_item != null and saved_style != null: var selected_panel: PanelContainer = selected_item.get_node("PanelContainer") if selected_panel != null: selected_panel.set('custom_styles/panel', saved_style) selected_item = null saved_style = null func _is_item_selected(item: Node): return item == selected_item func _select_item(item: Node): if item != null and not _is_item_selected(item): _clear_selection() var panel: PanelContainer = item.get_node("PanelContainer") if panel != null: saved_style = panel.get('custom_styles/panel') selected_item = item if selected_item.event_data.has('text') and selected_item.event_data.has('character'): panel.set('custom_styles/panel', selected_style_text) else: panel.set('custom_styles/panel', selected_style) # allow event panels to do additional operation when getting selected if (selected_item.has_method("on_timeline_selected")): selected_item.on_timeline_selected() else: _clear_selection() func _on_gui_input(event, item: Node): if event is InputEventMouseButton and event.button_index == 1: if (not event.is_pressed()): if (not piece_was_dragged and moving_piece != null): _clear_selection() if (moving_piece != null): indent_events() moving_piece = null elif event.is_pressed(): moving_piece = item if not _is_item_selected(item): _select_item(item) piece_was_dragged = true else: piece_was_dragged = false # Event Creation signal for buttons func _create_event_button_pressed(button_name): create_event(button_name) indent_events() func _on_ButtonQuestion_pressed() -> void: if selected_item != null: # Events are added bellow the selected node # So we must reverse the adding order create_event("EndBranch", {'no-data': true}, true) create_event("Choice", {'no-data': true}, true) create_event("Choice", {'no-data': true}, true) create_event("Question", {'no-data': true}, true) else: create_event("Question", {'no-data': true}, true) create_event("Choice", {'no-data': true}, true) create_event("Choice", {'no-data': true}, true) create_event("EndBranch", {'no-data': true}, true) func _on_ButtonCondition_pressed() -> void: if selected_item != null: # Events are added bellow the selected node # So we must reverse the adding order create_event("EndBranch", {'no-data': true}, true) create_event("IfCondition", {'no-data': true}, true) else: create_event("IfCondition", {'no-data': true}, true) create_event("EndBranch", {'no-data': true}, true) # Adding an event to the timeline func create_event(scene: String, data: Dictionary = {'no-data': true} , indent: bool = false): # This function will create an event in the timeline. var piece = load("res://addons/dialogic/Editor/Events/" + scene + ".tscn").instance() piece.editor_reference = editor_reference if selected_item != null: timeline.add_child_below_node(selected_item, piece) else: timeline.add_child(piece) if data.has('no-data') == false: piece.load_data(data) piece.connect("gui_input", self, '_on_gui_input', [piece]) events_warning.visible = false # Indent on create if indent: indent_events() return piece # Event Indenting func indent_events() -> void: var indent: int = 0 var starter: bool = false var event_list: Array = timeline.get_children() var question_index: int = 0 var question_indent = {} if event_list.size() < 2: return # Resetting all the indents for event in event_list: var indent_node = event.get_node("Indent") indent_node.visible = false # Adding new indents for event in event_list: # since there are indicators now, not all elements # in this list have an event_data property if (not "event_data" in event): continue if event.event_data.has('question') or event.event_data.has('condition'): indent += 1 starter = true question_index += 1 question_indent[question_index] = indent if event.event_data.has('choice'): if question_index > 0: indent = question_indent[question_index] + 1 starter = true if event.event_data.has('endbranch'): if question_indent.has(question_index): indent = question_indent[question_index] indent -= 1 question_index -= 1 if indent < 0: indent = 0 if indent > 0: var indent_node = event.get_node("Indent") indent_node.rect_min_size = Vector2(25 * indent, 0) indent_node.visible = true if starter: indent_node.rect_min_size = Vector2(25 * (indent - 1), 0) if indent - 1 == 0: indent_node.visible = false starter = false func load_timeline(filename: String): #print('---------------------------') #print('Loading: ', filename) clear_timeline() var start_time = OS.get_system_time_msecs() timeline_file = filename var data = DialogicResources.get_timeline_json(filename) if data['metadata'].has('name'): timeline_name = data['metadata']['name'] else: timeline_name = data['metadata']['file'] data = data['events'] for i in data: match i: {'text', 'character', 'portrait'}: create_event("TextBlock", i) {'background'}: create_event("SceneEvent", i) {'character', 'action', 'position', 'portrait'}: create_event("CharacterJoinBlock", i) {'audio', 'file'}: create_event("AudioBlock", i) {'background-music', 'file'}: create_event("BackgroundMusic", i) {'question', 'options'}: create_event("Question", i) {'choice'}: create_event("Choice", i) {'endbranch'}: create_event("EndBranch", i) {'character', 'action'}: create_event("CharacterLeaveBlock", i) {'change_timeline'}: create_event("ChangeTimeline", i) {'emit_signal'}: create_event("EmitSignal", i) {'change_scene'}: create_event("ChangeScene", i) {'close_dialog'}: create_event("CloseDialog", i) {'wait_seconds'}: create_event("WaitSeconds", i) {'condition', 'definition', 'value'}: create_event("IfCondition", i) {'set_value', 'definition', ..}: create_event("SetValue", i) {'set_theme'}: create_event("SetTheme", i) {'call_node'}: create_event("CallNode", i) if data.size() < 1: events_warning.visible = true else: events_warning.visible = false indent_events() #fold_all_nodes() var elapsed_time = (OS.get_system_time_msecs() - start_time) * 0.001 #editor_reference.dprint("Loading time: " + str(elapsed_time)) func clear_timeline(): _clear_selection() for event in timeline.get_children(): event.free() func get_block_above(block): var block_index = block.get_index() var item = null if block_index > 0: item = timeline.get_child(block_index - 1) return item func get_block_below(block): var block_index = block.get_index() var item = null if block_index < timeline.get_child_count() - 1: item = timeline.get_child(block_index + 1) return item func get_block_height(block): if block != null: return block.get_node("PanelContainer").rect_size.y else: return null # ordering blocks in timeline func move_block(block, direction): var block_index = block.get_index() if direction == 'up': if block_index > 0: timeline.move_child(block, block_index - 1) return true if direction == 'down': timeline.move_child(block, block_index + 1) return true return false func create_timeline(): timeline_file = 'timeline-' + str(OS.get_unix_time()) + '.json' var timeline = { "events": [], "metadata":{ "dialogic-version": editor_reference.version_string, "file": timeline_file } } DialogicResources.set_timeline(timeline) return timeline func new_timeline(): # This event creates and selects the new timeline master_tree.build_timelines(create_timeline()['metadata']['file']) # Saving func generate_save_data(): var info_to_save = { 'metadata': { 'dialogic-version': editor_reference.version_string, 'name': timeline_name, 'file': timeline_file }, 'events': [] } for event in timeline.get_children(): # check that event has event_data (e.g. drag drop indicators) if (not "event_data" in event): continue if event.is_queued_for_deletion() == false: # Checking that the event is not waiting to be removed info_to_save['events'].append(event.event_data) return info_to_save func save_timeline() -> void: if timeline_file != '': var info_to_save = generate_save_data() DialogicResources.set_timeline(info_to_save) #print('[+] Saving: ' , timeline_file) # Utilities func fold_all_nodes(): for event in timeline.get_children(): if event.has_node("PanelContainer/VBoxContainer/Header/VisibleToggle"): event.get_node("PanelContainer/VBoxContainer/Header/VisibleToggle").set_pressed(false) func unfold_all_nodes(): for event in timeline.get_children(): if event.has_node("PanelContainer/VBoxContainer/Header/VisibleToggle"): event.get_node("PanelContainer/VBoxContainer/Header/VisibleToggle").set_pressed(true)