tool extends HSplitContainer var editor_reference var timeline_name: String = '' var timeline_file: String = '' var current_timeline: Dictionary = {} var TimelineUndoRedo := UndoRedo.new() onready var master_tree = get_node('../MasterTreeContainer/MasterTree') onready var timeline = $TimelineArea/TimeLine onready var events_warning = $ScrollContainer/EventContainer/EventsWarning onready var custom_events_container = $ScrollContainer/EventContainer/CustomEventsContainer var hovered_item = null var selected_style : StyleBoxFlat = load("res://addons/dialogic/Editor/Events/styles/selected_styleboxflat.tres") var selected_style_text : StyleBoxFlat = load("res://addons/dialogic/Editor/Events/styles/selected_styleboxflat_text_event.tres") var selected_style_template : StyleBoxFlat = load("res://addons/dialogic/Editor/Events/styles/selected_styleboxflat_template.tres") var saved_style : StyleBoxFlat var selected_items : Array = [] var event_scenes : Dictionary = {} var currently_draged_event_type = null var move_start_position = null var moving_piece = null var piece_was_dragged = false var custom_events = {} var id_to_scene_name = { #Main events 'dialogic_001':'TextEvent', 'dialogic_002':'CharacterJoin', 'dialogic_003':'CharacterLeave', #Logic 'dialogic_010':'Question', 'dialogic_011':'Choice', 'dialogic_012':'Condition', 'dialogic_013':'EndBranch', 'dialogic_014':'SetValue', #Timeline 'dialogic_020':'ChangeTimeline', 'dialogic_021':'ChangeBackground', 'dialogic_022':'CloseDialog', 'dialogic_023':'WaitSeconds', 'dialogic_024':'SetTheme', 'dialogic_025':'SetGlossary', 'dialogic_026':'SaveEvent', #Audio 'dialogic_030':'AudioEvent', 'dialogic_031':'BackgroundMusic', #Godot 'dialogic_040':'EmitSignal', 'dialogic_041':'ChangeScene', 'dialogic_042':'CallNode', } var batches = [] var building_timeline = true signal selection_updated signal batch_loaded func _ready(): editor_reference = find_parent('EditorView') connect("batch_loaded", self, '_on_batch_loaded') 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 c in range(0, 5): for button in get_node("ScrollContainer/EventContainer/Grid"+str(c+1)).get_children(): # Question if button.event_id == 'dialogic_010': button.connect('pressed', self, "_on_ButtonQuestion_pressed", []) # Condition elif button.event_id == 'dialogic_012': button.connect('pressed', self, "_on_ButtonCondition_pressed", []) else: button.connect('pressed', self, "_create_event_button_pressed", [button.event_id]) var style = $TimelineArea.get('custom_styles/bg') style.set('bg_color', get_color("dark_color_1", "Editor")) update_custom_events() $TimelineArea.connect('resized', self, 'add_extra_scroll_area_to_timeline', []) # handles dragging/moving of events 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 # SIGNAL handles input on the events mainly for selection and moving events func _on_event_block_gui_input(event, item: Node): if event is InputEventMouseButton and event.button_index == 1: if (not event.is_pressed()): if (piece_was_dragged and moving_piece != null and move_start_position): var to_position = moving_piece.get_index() if move_start_position != to_position: # move it back so the DO action works. (Kinda stupid but whatever) move_block_to_index(to_position, move_start_position) TimelineUndoRedo.create_action("[D] Moved event (type '"+moving_piece.event_data.event_id+"').") TimelineUndoRedo.add_do_method(self, "move_block_to_index", move_start_position, to_position) TimelineUndoRedo.add_undo_method(self, "move_block_to_index", to_position, move_start_position) TimelineUndoRedo.commit_action() move_start_position = null if (moving_piece != null): indent_events() moving_piece = null elif event.is_pressed(): moving_piece = item move_start_position = moving_piece.get_index() if not _is_item_selected(item): pass#piece_was_dragged = true else: piece_was_dragged = false select_item(item) ## ***************************************************************************** ## SHORTCUTS ## ***************************************************************************** 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 get_focus_owner() is TextEdit: return if (event is InputEventKey and event is InputEventWithModifiers and is_visible_in_tree()): # CTRL Z # UNDO if (event.pressed and event.alt == false and event.shift == false and event.control == true and event.scancode == KEY_Z and event.echo == false ): TimelineUndoRedo.undo() indent_events() get_tree().set_input_as_handled() if (event is InputEventKey and event is InputEventWithModifiers and is_visible_in_tree()): # CTRL +SHIFT+ Z # REDO if (event.pressed and event.alt == false and event.shift == true and event.control == true and event.scancode == KEY_Z and event.echo == false ) or (event.pressed and event.alt == false and event.shift == false and event.control == true and event.scancode == KEY_Y and event.echo == false): TimelineUndoRedo.redo() indent_events() get_tree().set_input_as_handled() 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 == false and event.scancode == KEY_UP and event.echo == false ): # select previous if (len(selected_items) == 1): var prev = max(0, selected_items[0].get_index() - 1) var prev_node = timeline.get_child(prev) if (prev_node != selected_items[0]): selected_items = [] select_item(prev_node) get_tree().set_input_as_handled() # CTRL DOWN if (event.pressed and event.alt == false and event.shift == false and event.control == false and event.scancode == KEY_DOWN and event.echo == false ): # select next if (len(selected_items) == 1): var next = min(timeline.get_child_count() - 1, selected_items[0].get_index() + 1) var next_node = timeline.get_child(next) if (next_node != selected_items[0]): selected_items = [] select_item(next_node) get_tree().set_input_as_handled() # CTRL DELETE if (event.pressed and event.alt == false and event.shift == false and event.control == false and event.scancode == KEY_DELETE and event.echo == false ): if (len(selected_items) != 0): var events_indexed = get_events_indexed(selected_items) TimelineUndoRedo.create_action("[D] Deleting "+str(len(selected_items))+" event(s).") TimelineUndoRedo.add_do_method(self, "delete_events_indexed", events_indexed) TimelineUndoRedo.add_undo_method(self, "add_events_indexed", events_indexed) TimelineUndoRedo.commit_action() get_tree().set_input_as_handled() # 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 at_index = -1 if selected_items: at_index = selected_items[-1].get_index()+1 else: at_index = timeline.get_child_count() TimelineUndoRedo.create_action("[D] Add Text event.") TimelineUndoRedo.add_do_method(self, "create_event", "dialogic_000", {'no-data': true}, true, at_index, true) TimelineUndoRedo.add_undo_method(self, "remove_events_at_index", at_index, 1) TimelineUndoRedo.commit_action() get_tree().set_input_as_handled() # CTRL A if (event.pressed and event.alt == false and event.shift == false and event.control == true and event.scancode == KEY_A and event.echo == false ): if (len(selected_items) != 0): select_all_items() get_tree().set_input_as_handled() # CTRL SHIFT A if (event.pressed and event.alt == false and event.shift == true and event.control == true and event.scancode == KEY_A and event.echo == false ): if (len(selected_items) != 0): deselect_all_items() get_tree().set_input_as_handled() # CTRL C if (event.pressed and event.alt == false and event.shift == false and event.control == true and event.scancode == KEY_C and event.echo == false ): copy_selected_events() get_tree().set_input_as_handled() # CTRL V if (event.pressed and event.alt == false and event.shift == false and event.control == true and event.scancode == KEY_V and event.echo == false ): var events_list = paste_check() var paste_position = -1 if selected_items: paste_position = selected_items[-1].get_index() else: paste_position = timeline.get_child_count()-1 TimelineUndoRedo.create_action("[D] Pasting "+str(len(events_list))+" event(s).") TimelineUndoRedo.add_do_method(self, "add_events_at_index", events_list, paste_position) TimelineUndoRedo.add_undo_method(self, "remove_events_at_index", paste_position+1, len(events_list)) TimelineUndoRedo.commit_action() get_tree().set_input_as_handled() # CTRL X if (event.pressed and event.alt == false and event.shift == false and event.control == true and event.scancode == KEY_X and event.echo == false ): var events_indexed = get_events_indexed(selected_items) TimelineUndoRedo.create_action("[D] Cut "+str(len(selected_items))+" event(s).") TimelineUndoRedo.add_do_method(self, "cut_events_indexed", events_indexed) TimelineUndoRedo.add_undo_method(self, "add_events_indexed", events_indexed) TimelineUndoRedo.commit_action() get_tree().set_input_as_handled() # CTRL D if (event.pressed and event.alt == false and event.shift == false and event.control == true and event.scancode == KEY_D and event.echo == false ): if len(selected_items) > 0: var events = get_events_indexed(selected_items).values() var at_index = selected_items[-1].get_index() TimelineUndoRedo.create_action("[D] Duplicate "+str(len(events))+" event(s).") TimelineUndoRedo.add_do_method(self, "add_events_at_index", events, at_index) TimelineUndoRedo.add_undo_method(self, "remove_events_at_index", at_index, len(events)) TimelineUndoRedo.commit_action() get_tree().set_input_as_handled() 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 (len(selected_items) == 1): move_block(selected_items[0], "up") indent_events() get_tree().set_input_as_handled() # 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 (len(selected_items) == 1): move_block(selected_items[0], "down") indent_events() get_tree().set_input_as_handled() ## ***************************************************************************** ## DELETING, COPY, PASTE ## ***************************************************************************** func get_events_indexed(events:Array) -> Dictionary: var indexed_dict = {} for event in events: indexed_dict[event.get_index()] = event.event_data.duplicate() return indexed_dict func select_indexed_events(indexed_events:Dictionary) -> void: selected_items = [] for event_index in indexed_events.keys(): selected_items.append(timeline.get_child(event_index)) func add_events_indexed(indexed_events:Dictionary) -> void: var indexes = indexed_events.keys() indexes.sort() var events = [] for event_idx in indexes: deselect_all_items() events.append(create_event(indexed_events[event_idx].event_id, indexed_events[event_idx])) timeline.move_child(events[-1], event_idx) selected_items = events visual_update_selection() func delete_events_indexed(indexed_events:Dictionary) -> void: select_indexed_events(indexed_events) delete_selected_events() func delete_selected_events(): if len(selected_items) == 0: return # get next element var next = min(timeline.get_child_count() - 1, selected_items[-1].get_index() + 1) var next_node = timeline.get_child(next) if _is_item_selected(next_node): next_node = null for event in selected_items: event.get_parent().remove_child(event) event.queue_free() # select next if (next_node != null): select_item(next_node, false) 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, false) else: deselect_all_items() indent_events() func cut_selected_events(): copy_selected_events() delete_selected_events() func cut_events_indexed(indexed_events:Dictionary) -> void: select_indexed_events(indexed_events) cut_selected_events() func copy_selected_events(): if len(selected_items) == 0: return var event_copy_array = [] for item in selected_items: event_copy_array.append(item.event_data) OS.clipboard = JSON.print( { "events":event_copy_array, "dialogic_version": editor_reference.version_string, "project_name": ProjectSettings.get_setting("application/config/name") }) func paste_check(): var clipboard_parse = JSON.parse(OS.clipboard).result if typeof(clipboard_parse) == TYPE_DICTIONARY: if clipboard_parse.has("dialogic_version"): if clipboard_parse['dialogic_version'] != editor_reference.version_string: print("[D] Be careful when copying from older versions!") if clipboard_parse.has("project_name"): if clipboard_parse['project_name'] != ProjectSettings.get_setting("application/config/name"): print("[D] Be careful when copying from another project!") if clipboard_parse.has('events'): return clipboard_parse['events'] func remove_events_at_index(at_index:int, amount:int = 1)-> void: selected_items = [] for i in range(0, amount): selected_items.append(timeline.get_child(at_index + i)) delete_selected_events() func add_events_at_index(event_list:Array, at_index:int) -> void: if at_index != -1: event_list.invert() selected_items = [timeline.get_child(at_index)] else: selected_items = [] var new_items = [] for item in event_list: if typeof(item) == TYPE_DICTIONARY and item.has('event_id'): new_items.append(create_event(item['event_id'], item)) selected_items = new_items sort_selection() visual_update_selection() indent_events() func paste_events_indexed(indexed_events): pass func duplicate_events_indexed(indexed_events): pass ## ***************************************************************************** ## BLOCK SELECTION ## ***************************************************************************** func _is_item_selected(item: Node): return item in selected_items func select_item(item: Node, multi_possible:bool = true): if item == null: return if Input.is_key_pressed(KEY_CONTROL) and multi_possible: # deselect the item if it is selected if _is_item_selected(item): selected_items.erase(item) else: selected_items.append(item) elif Input.is_key_pressed(KEY_SHIFT) and multi_possible: if len(selected_items) == 0: selected_items = [item] else: var index = selected_items[-1].get_index() var goal_idx = item.get_index() while true: if index < goal_idx: index += 1 else: index -= 1 if not timeline.get_child(index) in selected_items: selected_items.append(timeline.get_child(index)) if index == goal_idx: break else: if len(selected_items) == 1: if _is_item_selected(item): selected_items.erase(item) else: selected_items = [item] else: selected_items = [item] sort_selection() visual_update_selection() # checks all the events and sets their styles (selected/deselected) func visual_update_selection(): for item in timeline.get_children(): item.visual_deselect() for item in selected_items: item.visual_select() ## Sorts the selection using 'custom_sort_selection' func sort_selection(): selected_items.sort_custom(self, 'custom_sort_selection') ## Compares two event blocks based on their position in the timeline func custom_sort_selection(item1, item2): return item1.get_index() < item2.get_index() ## Helpers func select_all_items(): selected_items = [] for event in timeline.get_children(): selected_items.append(event) visual_update_selection() func deselect_all_items(): selected_items = [] visual_update_selection() ## ***************************************************************************** ## SPECIAL BLOCK OPERATIONS ## ***************************************************************************** # SIGNAL handles the actions of the small menu on the right func _on_event_options_action(action: String, item: Node): ### WORK TODO if action == "remove": if len(selected_items) != 1 or (len(selected_items) == 1 and selected_items[0] != item): select_item(item, false) delete_selected_events() else: move_block(item, action) indent_events() func delete_event(event): event.get_parent().remove_child(event) event.queue_free() ## ***************************************************************************** ## CREATING NEW EVENTS USING THE BUTTONS ## ***************************************************************************** # Event Creation signal for buttons func _create_event_button_pressed(event_id): var at_index = -1 if selected_items: at_index = selected_items[-1].get_index()+1 else: at_index = timeline.get_child_count() TimelineUndoRedo.create_action("[D] Add event.") TimelineUndoRedo.add_do_method(self, "create_event", event_id, {'no-data': true}, true, at_index, true) TimelineUndoRedo.add_undo_method(self, "remove_events_at_index", at_index, 1) TimelineUndoRedo.commit_action() scroll_to_piece(at_index) indent_events() # the Question button adds multiple blocks func _on_ButtonQuestion_pressed() -> void: var at_index = -1 if selected_items: at_index = selected_items[-1].get_index()+1 else: at_index = timeline.get_child_count() TimelineUndoRedo.create_action("[D] Add question events.") TimelineUndoRedo.add_do_method(self, "create_question", at_index) TimelineUndoRedo.add_undo_method(self, "remove_events_at_index", at_index, 4) TimelineUndoRedo.commit_action() func create_question(at_position): if at_position == 0: selected_items = [] else: selected_items = [timeline.get_child(at_position-1)] if len(selected_items) != 0: # Events are added bellow the selected node # So we must reverse the adding order create_event("dialogic_013", {'no-data': true}, true) create_event("dialogic_011", {'no-data': true}, true) create_event("dialogic_011", {'no-data': true}, true) create_event("dialogic_010", {'no-data': true}, true) else: create_event("dialogic_010", {'no-data': true}, true) create_event("dialogic_011", {'no-data': true}, true) create_event("dialogic_011", {'no-data': true}, true) create_event("dialogic_013", {'no-data': true}, true) # the Condition button adds multiple blocks func _on_ButtonCondition_pressed() -> void: var at_index = -1 if selected_items: at_index = selected_items[-1].get_index()+1 else: at_index = timeline.get_child_count() TimelineUndoRedo.create_action("[D] Add condition events.") TimelineUndoRedo.add_do_method(self, "create_condition", at_index) TimelineUndoRedo.add_undo_method(self, "remove_events_at_index", at_index, 2) TimelineUndoRedo.commit_action() func create_condition(at_position): if at_position == 0: selected_items = [] else: selected_items = [timeline.get_child(at_position-1)] if len(selected_items) != 0: # Events are added bellow the selected node # So we must reverse the adding order create_event("dialogic_013", {'no-data': true}, true) create_event("dialogic_012", {'no-data': true}, true) else: create_event("dialogic_012", {'no-data': true}, true) create_event("dialogic_013", {'no-data': true}, true) func update_custom_events() -> void: ## CLEANUP custom_events = {} # cleaning the 'old' buttons for child in custom_events_container.get_children(): child.queue_free() var path:String = "res://dialogic/custom-events" var dir = Directory.new() if dir.open(path) == OK: dir.list_dir_begin() var file_name = dir.get_next() # goes through all the folders in the custom events folder while file_name != "": # if it found a folder if dir.current_is_dir() and not file_name in ['.', '..']: # look through that folder #print("Found custom event folder: " + file_name) var event = load(path.plus_file(file_name).plus_file('EventBlock.tscn')).instance() if event: custom_events[event.event_data['event_id']] = { 'event_block_scene' :path.plus_file(file_name).plus_file('EventBlock.tscn'), 'event_name' : event.event_name, 'event_icon' : event.event_icon } event.queue_free() else: print("[D] An error occurred when trying to access a custom event.") else: pass # files in the directory are ignored file_name = dir.get_next() # After we finishing checking, if any events exist, show the panel if custom_events.size() == 0: custom_events_container.hide() $ScrollContainer/EventContainer/CustomEventsHeadline.hide() else: custom_events_container.show() $ScrollContainer/EventContainer/CustomEventsHeadline.show() else: print("[D] An error occurred when trying to access the custom events folder.") ## VISUAL UPDATE # adding new ones for custom_event_id in custom_events.keys(): var button = load('res://addons/dialogic/Editor/TimelineEditor/SmallEventButton.tscn').instance() #button.set_script(preload("EventButton.gd")) button.event_id = custom_event_id button.visible_name = custom_events[custom_event_id]['event_name'] button.self_modulate = Color('#494d58') button.hint_tooltip = custom_events[custom_event_id]['event_name'] if custom_events[custom_event_id]['event_icon']: button.event_icon = custom_events[custom_event_id]['event_icon'] button.connect("pressed", self, "_create_event_button_pressed", [custom_event_id]) custom_events_container.add_child(button) ## ***************************************************************************** ## DRAG AND DROP ## ***************************************************************************** # Creates a ghost event for drag and drop func create_drag_and_drop_event(event_id: String): var index = get_index_under_cursor() var piece = create_event(event_id) currently_draged_event_type = event_id timeline.move_child(piece, index) moving_piece = piece piece_was_dragged = true set_event_ignore_save(piece, true) select_item(piece) return piece func drop_event(): if moving_piece != null: var at_index = moving_piece.get_index() moving_piece.queue_free() TimelineUndoRedo.create_action("[D] Add event.") TimelineUndoRedo.add_do_method(self, "create_event", currently_draged_event_type, {'no-data': true}, true, at_index, true) TimelineUndoRedo.add_undo_method(self, "remove_events_at_index", at_index, 1) TimelineUndoRedo.commit_action() moving_piece = null piece_was_dragged = false indent_events() add_extra_scroll_area_to_timeline() func cancel_drop_event(): if moving_piece != null: moving_piece = null piece_was_dragged = false delete_selected_events() deselect_all_items() ## ***************************************************************************** ## CREATING THE TIMELINE ## ***************************************************************************** # Adding an event to the timeline func create_event(event_id: String, data: Dictionary = {'no-data': true} , indent: bool = false, at_index: int = -1, auto_select: bool = false): var piece = null # check if it's a custom event if event_id in custom_events.keys(): piece = load(custom_events[event_id]['event_block_scene']).instance() # check if it's a builtin event elif event_id in id_to_scene_name.keys(): piece = load("res://addons/dialogic/Editor/Events/" + id_to_scene_name[event_id] + ".tscn").instance() # else use dummy event else: piece = load("res://addons/dialogic/Editor/Events/DummyEvent.tscn").instance() # load the piece with data piece.editor_reference = editor_reference if data.has('no-data') == false: piece.event_data = data if at_index == -1: if len(selected_items) != 0: timeline.add_child_below_node(selected_items[0], piece) else: timeline.add_child(piece) else: timeline.add_child(piece) timeline.move_child(piece, at_index) piece.connect("option_action", self, '_on_event_options_action', [piece]) piece.connect("gui_input", self, '_on_event_block_gui_input', [piece]) events_warning.visible = false if auto_select: select_item(piece, false) # Spacing add_extra_scroll_area_to_timeline() # Indent on create if indent: indent_events() return piece func load_timeline(filename: String): clear_timeline() update_custom_events() if timeline_file != filename: TimelineUndoRedo.clear_history() building_timeline = true 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'] var page = 1 var batch_size = 12 while batch_events(data, batch_size, page).size() != 0: batches.append(batch_events(data, batch_size, page)) page += 1 load_batch(batches) # Reset the scroll position $TimelineArea.scroll_vertical = 0 func batch_events(array, size, batch_number): return array.slice((batch_number - 1) * size, batch_number * size - 1) func load_batch(data): #print('[D] Loading batch') var current_batch = batches.pop_front() if current_batch: for i in current_batch: create_event(i['event_id'], i) emit_signal("batch_loaded") func _on_batch_loaded(): if batches.size() > 0: yield(get_tree().create_timer(0.01), "timeout") load_batch(batches) else: events_warning.visible = false indent_events() building_timeline = false add_extra_scroll_area_to_timeline() func clear_timeline(): deselect_all_items() for event in timeline.get_children(): event.free() ## ***************************************************************************** ## BLOCK GETTERS ## ***************************************************************************** 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.rect_size.y else: return null func get_index_under_cursor(): var current_position = get_global_mouse_position() var top_pos = 0 for i in range(timeline.get_child_count()): var c = timeline.get_child(i) if c.rect_global_position.y < current_position.y: top_pos = i return top_pos # 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 move_block_to_index(block_index, index): timeline.move_child(timeline.get_child(block_index), index) ## ***************************************************************************** ## TIMELINE CREATION AND SAVING ## ***************************************************************************** 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 # 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(): # Checking that the event is not waiting to be removed # or that it is not a drag and drop placeholder if not get_event_ignore_save(event) and event.is_queued_for_deletion() == false: info_to_save['events'].append(event.event_data) return info_to_save func set_event_ignore_save(event: Node, ignore: bool): event.ignore_save = ignore func get_event_ignore_save(event: Node) -> bool: return event.ignore_save func save_timeline() -> void: if timeline_file != '' and building_timeline == false: var info_to_save = generate_save_data() DialogicResources.set_timeline(info_to_save) #print('[+] Saving: ' , timeline_file) ## ***************************************************************************** ## UTILITIES/HELPERS ## ***************************************************************************** # Scrolling func scroll_to_piece(piece_index) -> void: var height = 0 for i in range(0, piece_index): height += $TimelineArea/TimeLine.get_child(i).rect_size.y $TimelineArea.scroll_vertical = height # Event Indenting func indent_events() -> void: # Now indenting 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.set_indent(0) # 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['event_id'] == 'dialogic_011': if question_index > 0: indent = question_indent[question_index] + 1 starter = true elif event.event_data['event_id'] == 'dialogic_010' or event.event_data['event_id'] == 'dialogic_012': indent += 1 starter = true question_index += 1 question_indent[question_index] = indent elif event.event_data['event_id'] == 'dialogic_013': if question_indent.has(question_index): indent = question_indent[question_index] indent -= 1 question_index -= 1 if indent < 0: indent = 0 if indent > 0: # Keep old behavior for items without template if starter: event.set_indent(indent - 1) else: event.set_indent(indent) starter = false # called from the toolbar func fold_all_nodes(): for event in timeline.get_children(): event.set_expanded(false) add_extra_scroll_area_to_timeline() # called from the toolbar func unfold_all_nodes(): for event in timeline.get_children(): event.set_expanded(true) add_extra_scroll_area_to_timeline() func add_extra_scroll_area_to_timeline(): if timeline.get_children().size() > 4: timeline.rect_min_size.y = 0 timeline.rect_size.y = 0 if timeline.rect_size.y + 200 > $TimelineArea.rect_size.y: timeline.rect_min_size = Vector2(0, timeline.rect_size.y + 200)