No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

TimelineEditor.gd 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514
  1. tool
  2. extends HSplitContainer
  3. var editor_reference
  4. var timeline_name: String = ''
  5. var timeline_file: String = ''
  6. var current_timeline: Dictionary = {}
  7. onready var master_tree = get_node('../MasterTreeContainer/MasterTree')
  8. onready var timeline = $TimelineArea/TimeLine
  9. onready var events_warning = $ScrollContainer/EventContainer/EventsWarning
  10. var hovered_item = null
  11. var selected_style : StyleBoxFlat = load("res://addons/dialogic/Editor/Pieces/selected_styleboxflat.tres")
  12. var selected_style_text : StyleBoxFlat = load("res://addons/dialogic/Editor/Pieces/selected_styleboxflat_text_event.tres")
  13. var saved_style : StyleBoxFlat
  14. var selected_item : Node
  15. var moving_piece = null
  16. var piece_was_dragged = false
  17. func _ready():
  18. var modifier = ''
  19. var _scale = get_constant("inspector_margin", "Editor")
  20. _scale = _scale * 0.125
  21. $ScrollContainer.rect_min_size.x = 180
  22. if _scale == 1.25:
  23. modifier = '-1.25'
  24. $ScrollContainer.rect_min_size.x = 200
  25. if _scale == 1.5:
  26. modifier = '-1.25'
  27. $ScrollContainer.rect_min_size.x = 200
  28. if _scale == 1.75:
  29. modifier = '-1.25'
  30. $ScrollContainer.rect_min_size.x = 390
  31. if _scale == 2:
  32. modifier = '-2'
  33. $ScrollContainer.rect_min_size.x = 390
  34. # We connect all the event buttons to the event creation functions
  35. for b in $ScrollContainer/EventContainer.get_children():
  36. if b is Button:
  37. if b.name == 'ButtonQuestion':
  38. b.connect('pressed', self, "_on_ButtonQuestion_pressed", [])
  39. elif b.name == 'IfCondition':
  40. b.connect('pressed', self, "_on_ButtonCondition_pressed", [])
  41. else:
  42. b.connect('pressed', self, "_create_event_button_pressed", [b.name])
  43. var style = $TimelineArea.get('custom_styles/bg')
  44. style.set('bg_color', get_color("dark_color_1", "Editor"))
  45. func delete_event():
  46. # get next element
  47. var next = min(timeline.get_child_count() - 1, selected_item.get_index() + 1)
  48. var next_node = timeline.get_child(next)
  49. if (next_node == selected_item):
  50. next_node = null
  51. # remove current
  52. selected_item.get_parent().remove_child(selected_item)
  53. selected_item.queue_free()
  54. selected_item = null
  55. # select next
  56. if (next_node != null):
  57. _select_item(next_node)
  58. else:
  59. if (timeline.get_child_count() > 0):
  60. next_node = timeline.get_child(max(0, timeline.get_child_count() - 1))
  61. if (next_node != null):
  62. _select_item(next_node)
  63. indent_events()
  64. func _input(event):
  65. # some shortcuts need to get handled in the common input event
  66. # especially CTRL-based
  67. # because certain godot controls swallow events (like textedit)
  68. # we protect this with is_visible_in_tree to not
  69. # invoke a shortcut by accident
  70. if (event is InputEventKey and event is InputEventWithModifiers and is_visible_in_tree()):
  71. # CTRL UP
  72. if (event.pressed
  73. and event.alt == false
  74. and event.shift == false
  75. and event.control == true
  76. and event.scancode == KEY_UP
  77. and event.echo == false
  78. ):
  79. # select previous
  80. if (selected_item != null):
  81. var prev = max(0, selected_item.get_index() - 1)
  82. var prev_node = timeline.get_child(prev)
  83. if (prev_node != selected_item):
  84. _select_item(prev_node)
  85. get_tree().set_input_as_handled()
  86. pass
  87. # CTRL DOWN
  88. if (event.pressed
  89. and event.alt == false
  90. and event.shift == false
  91. and event.control == true
  92. and event.scancode == KEY_DOWN
  93. and event.echo == false
  94. ):
  95. # select next
  96. if (selected_item != null):
  97. var next = min(timeline.get_child_count() - 1, selected_item.get_index() + 1)
  98. var next_node = timeline.get_child(next)
  99. if (next_node != selected_item):
  100. _select_item(next_node)
  101. get_tree().set_input_as_handled()
  102. pass
  103. # CTRL DELETE
  104. if (event.pressed
  105. and event.alt == false
  106. and event.shift == false
  107. and event.control == true
  108. and event.scancode == KEY_DELETE
  109. and event.echo == false
  110. ):
  111. if (selected_item != null):
  112. delete_event()
  113. get_tree().set_input_as_handled()
  114. pass
  115. # CTRL T
  116. if (event.pressed
  117. and event.alt == false
  118. and event.shift == false
  119. and event.control == true
  120. and event.scancode == KEY_T
  121. and event.echo == false
  122. ):
  123. var new_text = create_event("TextBlock")
  124. _select_item(new_text)
  125. indent_events()
  126. get_tree().set_input_as_handled()
  127. pass
  128. func _unhandled_key_input(event):
  129. if (event is InputEventWithModifiers):
  130. # ALT UP
  131. if (event.pressed
  132. and event.alt == true
  133. and event.shift == false
  134. and event.control == false
  135. and event.scancode == KEY_UP
  136. and event.echo == false
  137. ):
  138. # move selected up
  139. if (selected_item != null):
  140. move_block(selected_item, "up")
  141. indent_events()
  142. get_tree().set_input_as_handled()
  143. pass
  144. # ALT DOWN
  145. if (event.pressed
  146. and event.alt == true
  147. and event.shift == false
  148. and event.control == false
  149. and event.scancode == KEY_DOWN
  150. and event.echo == false
  151. ):
  152. # move selected down
  153. if (selected_item != null):
  154. move_block(selected_item, "down")
  155. indent_events()
  156. get_tree().set_input_as_handled()
  157. pass
  158. pass
  159. func _process(delta):
  160. if moving_piece != null:
  161. var current_position = get_global_mouse_position()
  162. var node_position = moving_piece.rect_global_position.y
  163. var height = get_block_height(moving_piece)
  164. var up_offset = get_block_height(get_block_above(moving_piece))
  165. var down_offset = get_block_height(get_block_below(moving_piece))
  166. if up_offset != null:
  167. up_offset = (up_offset / 2) + 5
  168. if current_position.y < node_position - up_offset:
  169. move_block(moving_piece, 'up')
  170. piece_was_dragged = true
  171. if down_offset != null:
  172. down_offset = height + (down_offset / 2) + 5
  173. if current_position.y > node_position + down_offset:
  174. move_block(moving_piece, 'down')
  175. piece_was_dragged = true
  176. func _clear_selection():
  177. if selected_item != null and saved_style != null:
  178. var selected_panel: PanelContainer = selected_item.get_node("PanelContainer")
  179. if selected_panel != null:
  180. selected_panel.set('custom_styles/panel', saved_style)
  181. selected_item = null
  182. saved_style = null
  183. func _is_item_selected(item: Node):
  184. return item == selected_item
  185. func _select_item(item: Node):
  186. if item != null and not _is_item_selected(item):
  187. _clear_selection()
  188. var panel: PanelContainer = item.get_node("PanelContainer")
  189. if panel != null:
  190. saved_style = panel.get('custom_styles/panel')
  191. selected_item = item
  192. if selected_item.event_data.has('text') and selected_item.event_data.has('character'):
  193. panel.set('custom_styles/panel', selected_style_text)
  194. else:
  195. panel.set('custom_styles/panel', selected_style)
  196. # allow event panels to do additional operation when getting selected
  197. if (selected_item.has_method("on_timeline_selected")):
  198. selected_item.on_timeline_selected()
  199. else:
  200. _clear_selection()
  201. func _on_gui_input(event, item: Node):
  202. if event is InputEventMouseButton and event.button_index == 1:
  203. if (not event.is_pressed()):
  204. if (not piece_was_dragged and moving_piece != null):
  205. _clear_selection()
  206. if (moving_piece != null):
  207. indent_events()
  208. moving_piece = null
  209. elif event.is_pressed():
  210. moving_piece = item
  211. if not _is_item_selected(item):
  212. _select_item(item)
  213. piece_was_dragged = true
  214. else:
  215. piece_was_dragged = false
  216. # Event Creation signal for buttons
  217. func _create_event_button_pressed(button_name):
  218. create_event(button_name)
  219. indent_events()
  220. func _on_ButtonQuestion_pressed() -> void:
  221. if selected_item != null:
  222. # Events are added bellow the selected node
  223. # So we must reverse the adding order
  224. create_event("EndBranch", {'no-data': true}, true)
  225. create_event("Choice", {'no-data': true}, true)
  226. create_event("Choice", {'no-data': true}, true)
  227. create_event("Question", {'no-data': true}, true)
  228. else:
  229. create_event("Question", {'no-data': true}, true)
  230. create_event("Choice", {'no-data': true}, true)
  231. create_event("Choice", {'no-data': true}, true)
  232. create_event("EndBranch", {'no-data': true}, true)
  233. func _on_ButtonCondition_pressed() -> void:
  234. if selected_item != null:
  235. # Events are added bellow the selected node
  236. # So we must reverse the adding order
  237. create_event("EndBranch", {'no-data': true}, true)
  238. create_event("IfCondition", {'no-data': true}, true)
  239. else:
  240. create_event("IfCondition", {'no-data': true}, true)
  241. create_event("EndBranch", {'no-data': true}, true)
  242. # Adding an event to the timeline
  243. func create_event(scene: String, data: Dictionary = {'no-data': true} , indent: bool = false):
  244. # This function will create an event in the timeline.
  245. var piece = load("res://addons/dialogic/Editor/Pieces/" + scene + ".tscn").instance()
  246. piece.editor_reference = editor_reference
  247. if selected_item != null:
  248. timeline.add_child_below_node(selected_item, piece)
  249. else:
  250. timeline.add_child(piece)
  251. if data.has('no-data') == false:
  252. piece.load_data(data)
  253. piece.connect("gui_input", self, '_on_gui_input', [piece])
  254. events_warning.visible = false
  255. # Indent on create
  256. if indent:
  257. indent_events()
  258. return piece
  259. # Event Indenting
  260. func indent_events() -> void:
  261. var indent: int = 0
  262. var starter: bool = false
  263. var event_list: Array = timeline.get_children()
  264. var question_index: int = 0
  265. var question_indent = {}
  266. if event_list.size() < 2:
  267. return
  268. # Resetting all the indents
  269. for event in event_list:
  270. var indent_node = event.get_node("Indent")
  271. indent_node.visible = false
  272. # Adding new indents
  273. for event in event_list:
  274. # since there are indicators now, not all elements
  275. # in this list have an event_data property
  276. if (not "event_data" in event):
  277. continue
  278. if event.event_data.has('question') or event.event_data.has('condition'):
  279. indent += 1
  280. starter = true
  281. question_index += 1
  282. question_indent[question_index] = indent
  283. if event.event_data.has('choice'):
  284. if question_index > 0:
  285. indent = question_indent[question_index] + 1
  286. starter = true
  287. if event.event_data.has('endbranch'):
  288. if question_indent.has(question_index):
  289. indent = question_indent[question_index]
  290. indent -= 1
  291. question_index -= 1
  292. if indent < 0:
  293. indent = 0
  294. if indent > 0:
  295. var indent_node = event.get_node("Indent")
  296. indent_node.rect_min_size = Vector2(25 * indent, 0)
  297. indent_node.visible = true
  298. if starter:
  299. indent_node.rect_min_size = Vector2(25 * (indent - 1), 0)
  300. if indent - 1 == 0:
  301. indent_node.visible = false
  302. starter = false
  303. func load_timeline(filename: String):
  304. #print('---------------------------')
  305. #print('Loading: ', filename)
  306. clear_timeline()
  307. var start_time = OS.get_system_time_msecs()
  308. timeline_file = filename
  309. var data = DialogicResources.get_timeline_json(filename)
  310. if data['metadata'].has('name'):
  311. timeline_name = data['metadata']['name']
  312. else:
  313. timeline_name = data['metadata']['file']
  314. data = data['events']
  315. for i in data:
  316. match i:
  317. {'text', 'character', 'portrait'}:
  318. create_event("TextBlock", i)
  319. {'background'}:
  320. create_event("SceneEvent", i)
  321. {'character', 'action', 'position', 'portrait'}:
  322. create_event("CharacterJoinBlock", i)
  323. {'audio', 'file'}:
  324. create_event("AudioBlock", i)
  325. {'background-music', 'file'}:
  326. create_event("BackgroundMusic", i)
  327. {'question', 'options'}:
  328. create_event("Question", i)
  329. {'choice'}:
  330. create_event("Choice", i)
  331. {'endbranch'}:
  332. create_event("EndBranch", i)
  333. {'character', 'action'}:
  334. create_event("CharacterLeaveBlock", i)
  335. {'change_timeline'}:
  336. create_event("ChangeTimeline", i)
  337. {'emit_signal'}:
  338. create_event("EmitSignal", i)
  339. {'change_scene'}:
  340. create_event("ChangeScene", i)
  341. {'close_dialog'}:
  342. create_event("CloseDialog", i)
  343. {'wait_seconds'}:
  344. create_event("WaitSeconds", i)
  345. {'condition', 'definition', 'value'}:
  346. create_event("IfCondition", i)
  347. {'set_value', 'definition', ..}:
  348. create_event("SetValue", i)
  349. {'set_theme'}:
  350. create_event("SetTheme", i)
  351. {'call_node'}:
  352. create_event("CallNode", i)
  353. if data.size() < 1:
  354. events_warning.visible = true
  355. else:
  356. events_warning.visible = false
  357. indent_events()
  358. #fold_all_nodes()
  359. var elapsed_time = (OS.get_system_time_msecs() - start_time) * 0.001
  360. #editor_reference.dprint("Loading time: " + str(elapsed_time))
  361. func clear_timeline():
  362. _clear_selection()
  363. for event in timeline.get_children():
  364. event.free()
  365. func get_block_above(block):
  366. var block_index = block.get_index()
  367. var item = null
  368. if block_index > 0:
  369. item = timeline.get_child(block_index - 1)
  370. return item
  371. func get_block_below(block):
  372. var block_index = block.get_index()
  373. var item = null
  374. if block_index < timeline.get_child_count() - 1:
  375. item = timeline.get_child(block_index + 1)
  376. return item
  377. func get_block_height(block):
  378. if block != null:
  379. return block.get_node("PanelContainer").rect_size.y
  380. else:
  381. return null
  382. # ordering blocks in timeline
  383. func move_block(block, direction):
  384. var block_index = block.get_index()
  385. if direction == 'up':
  386. if block_index > 0:
  387. timeline.move_child(block, block_index - 1)
  388. return true
  389. if direction == 'down':
  390. timeline.move_child(block, block_index + 1)
  391. return true
  392. return false
  393. func create_timeline():
  394. timeline_file = 'timeline-' + str(OS.get_unix_time()) + '.json'
  395. var timeline = {
  396. "events": [],
  397. "metadata":{
  398. "dialogic-version": editor_reference.version_string,
  399. "file": timeline_file
  400. }
  401. }
  402. DialogicResources.set_timeline(timeline)
  403. return timeline
  404. func new_timeline():
  405. # This event creates and selects the new timeline
  406. master_tree.build_timelines(create_timeline()['metadata']['file'])
  407. # Saving
  408. func generate_save_data():
  409. var info_to_save = {
  410. 'metadata': {
  411. 'dialogic-version': editor_reference.version_string,
  412. 'name': timeline_name,
  413. 'file': timeline_file
  414. },
  415. 'events': []
  416. }
  417. for event in timeline.get_children():
  418. # check that event has event_data (e.g. drag drop indicators)
  419. if (not "event_data" in event):
  420. continue
  421. if event.is_queued_for_deletion() == false: # Checking that the event is not waiting to be removed
  422. info_to_save['events'].append(event.event_data)
  423. return info_to_save
  424. func save_timeline() -> void:
  425. if timeline_file != '':
  426. var info_to_save = generate_save_data()
  427. DialogicResources.set_timeline(info_to_save)
  428. #print('[+] Saving: ' , timeline_file)
  429. # Utilities
  430. func fold_all_nodes():
  431. for event in timeline.get_children():
  432. if event.has_node("PanelContainer/VBoxContainer/Header/VisibleToggle"):
  433. event.get_node("PanelContainer/VBoxContainer/Header/VisibleToggle").set_pressed(false)
  434. func unfold_all_nodes():
  435. for event in timeline.get_children():
  436. if event.has_node("PanelContainer/VBoxContainer/Header/VisibleToggle"):
  437. event.get_node("PanelContainer/VBoxContainer/Header/VisibleToggle").set_pressed(true)