Updated dialogic

This commit is contained in:
MaddoScientisto 2026-01-05 16:00:41 +01:00
commit cbb82512ee
483 changed files with 5743 additions and 2177 deletions

View file

@ -1 +1 @@
uid://ckqar2hsatqrt
uid://dofrrscd4c61l

View file

@ -1,6 +1,6 @@
[gd_scene load_steps=4 format=3 uid="uid://depcrpeh3f4rv"]
[ext_resource type="Script" uid="uid://ckqar2hsatqrt" path="res://addons/dialogic/Editor/TimelineEditor/VisualEditor/AddEventButton.gd" id="1_s43sc"]
[ext_resource type="Script" uid="uid://dofrrscd4c61l" path="res://addons/dialogic/Editor/TimelineEditor/VisualEditor/AddEventButton.gd" id="1_s43sc"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_qx31r"]
content_margin_left = 4.0

View file

@ -1 +1 @@
uid://b1y8xx4okklym
uid://b6ka2avnh1u55

View file

@ -22,7 +22,7 @@ signal timeline_loaded
################################################################################
var _batches := []
var _building_timeline := false
var _timeline_changed_while_loading := false
var _cancel_loading := false
var _initialized := false
################## TIMELINE EVENT MANAGEMENT ###################################
@ -75,11 +75,8 @@ func _notification(what:int) -> void:
func load_timeline(resource:DialogicTimeline) -> void:
if _building_timeline:
_timeline_changed_while_loading = true
await batch_loaded
_timeline_changed_while_loading = false
_building_timeline = false
# In case another timeline is still loading
cancel_loading()
clear_timeline_nodes()
@ -99,11 +96,24 @@ func load_timeline(resource:DialogicTimeline) -> void:
while batch_events(data, batch_size, page).size() != 0:
_batches.append(batch_events(data, batch_size, page))
page += 1
set_meta("batch_count", len(_batches))
batch_loaded.emit()
# Reset the scroll position
%TimelineArea.scroll_vertical = 0
func is_loading_timeline() -> bool:
return _building_timeline
func cancel_loading() -> void:
timeline_editor.set_progress(1)
if _building_timeline:
_cancel_loading = true
await batch_loaded
_cancel_loading = false
_building_timeline = false
func batch_events(array: Array, size: int, batch_number: int) -> Array:
return array.slice((batch_number - 1) * size, batch_number * size)
@ -127,19 +137,27 @@ func load_batch(data:Array) -> void:
func _on_batch_loaded() -> void:
if _timeline_changed_while_loading:
if _cancel_loading:
return
if _batches.size() > 0:
indent_events()
var progress: float = 1-(1.0/get_meta("batch_count")*len(_batches))
timeline_editor.set_progress(progress)
await get_tree().process_frame
load_batch(_batches)
return
# This hides the progress bar again
timeline_editor.set_progress(1)
if opener_events_stack:
for ev in opener_events_stack:
if is_instance_valid(ev):
create_end_branch_event(%Timeline.get_child_count(), ev)
timeline_loaded.emit()
opener_events_stack = []
indent_events()
update_content_list()
@ -195,7 +213,7 @@ func load_event_buttons() -> void:
var button_scene := load("res://addons/dialogic/Editor/TimelineEditor/VisualEditor/AddEventButton.tscn")
var scripts := DialogicResourceUtil.get_event_cache()
var hidden_buttons :Array = DialogicUtil.get_editor_setting('hidden_event_buttons', [])
var hidden_buttons: Array = DialogicUtil.get_editor_setting('hidden_event_buttons', [])
var sections := {}
for event_script in scripts:
@ -283,6 +301,7 @@ func update_content_list() -> void:
if not is_inside_tree():
return
var channels: PackedStringArray = []
var labels: PackedStringArray = []
for event in %Timeline.get_children():
@ -290,7 +309,12 @@ func update_content_list() -> void:
if 'event_name' in event.resource and event.resource is DialogicLabelEvent:
labels.append(event.resource.name)
if 'event_name' in event.resource and event.resource is DialogicAudioEvent:
if not event.resource.channel_name in channels:
channels.append(event.resource.channel_name)
timeline_editor.editors_manager.sidebar.update_content_list(labels)
timeline_editor.update_audio_channel_cache(channels)
#endregion
@ -312,6 +336,11 @@ func _on_event_block_gui_input(event: InputEvent, item: Node) -> void:
drag_allowed = true
if event.is_released() and not %TimelineArea.dragging and not Input.is_key_pressed(KEY_SHIFT):
if len(selected_items) > 1 and item in selected_items and not Input.is_key_pressed(KEY_CTRL):
deselect_all_items()
select_item(item)
if len(selected_items) > 0 and event is InputEventMouseMotion:
if Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT):
if !%TimelineArea.dragging and !get_viewport().gui_is_dragging() and drag_allowed:
@ -357,6 +386,8 @@ func add_event_node(event_resource:DialogicEvent, at_index:int = -1, auto_select
if event_resource.event_name == "Label":
block.content_changed.connect(update_content_list)
if event_resource.event_name == "Audio":
block.content_changed.connect(update_content_list)
if at_index == -1:
if len(selected_items) != 0:
selected_items[0].add_sibling(block)
@ -387,7 +418,7 @@ func create_end_branch_event(at_index:int, parent_node:Node) -> Node:
end_branch_event.gui_input.connect(_on_event_block_gui_input.bind(end_branch_event))
parent_node.end_node = end_branch_event
end_branch_event.parent_node = parent_node
end_branch_event.add_end_control(parent_node.resource.get_end_branch_control())
end_branch_event.add_end_control(parent_node.resource._get_end_branch_control())
%Timeline.add_child(end_branch_event)
%Timeline.move_child(end_branch_event, at_index)
return end_branch_event
@ -425,12 +456,12 @@ func get_events_indexed(events:Array) -> Dictionary:
if event.resource is DialogicEndBranchEvent:
continue
indexed_dict[event.get_index()] = event.resource.to_text()
indexed_dict[event.get_index()] = event.resource._store_as_string()
# store an end branch if it is selected or connected to a selected event
if 'end_node' in event and event.end_node:
event = event.end_node
indexed_dict[event.get_index()] = event.resource.to_text()
indexed_dict[event.get_index()] = event.resource._store_as_string()
elif event.resource is DialogicEndBranchEvent:
if event.parent_node in events: # add local index
indexed_dict[event.get_index()] += str(events.find(event.parent_node))
@ -465,13 +496,13 @@ func add_events_indexed(indexed_events:Dictionary) -> void:
var events := []
for event_idx in indexes:
# first get a new resource from the text version
var event_resource :DialogicEvent
var event_resource: DialogicEvent
for i in DialogicResourceUtil.get_event_cache():
if i._test_event_string(indexed_events[event_idx]):
event_resource = i.duplicate()
break
event_resource.from_text(indexed_events[event_idx])
event_resource._load_from_string(indexed_events[event_idx])
# now create the visual block.
deselect_all_items()
@ -540,14 +571,16 @@ func copy_selected_events() -> void:
if len(selected_items) == 0:
return
sort_selection()
var event_copy_array := []
for item in selected_items:
event_copy_array.append(item.resource.to_text())
event_copy_array.append(item.resource._store_as_string())
if item.resource is DialogicEndBranchEvent:
if item.parent_node in selected_items: # add local index
event_copy_array[-1] += str(selected_items.find(item.parent_node))
else: # add global index
event_copy_array[-1] += '#'+str(item.parent_node.get_index())
DisplayServer.clipboard_set(var_to_str({
"events":event_copy_array,
"project_name": ProjectSettings.get_setting("application/config/name")
@ -905,25 +938,30 @@ func indent_events() -> void:
#region SPECIAL BLOCK OPERATIONS
################################################################################
func _on_event_popup_menu_index_pressed(index:int) -> void:
func _on_event_popup_menu_id_pressed(id:int) -> void:
var item: Control = %EventPopupMenu.current_event
if index == 0:
if id == 0:
if not item in selected_items:
selected_items = [item]
duplicate_selected()
elif index == 2:
elif id == 1:
play_from_here(%EventPopupMenu.current_event.get_index())
elif id == 2:
if not item.resource.help_page_path.is_empty():
OS.shell_open(item.resource.help_page_path)
elif index == 3:
elif id == 3:
find_parent('EditorView').plugin_reference.get_editor_interface().set_main_screen_editor('Script')
find_parent('EditorView').plugin_reference.get_editor_interface().edit_script(item.resource.get_script(), 1, 1)
elif index == 5 or index == 6:
if index == 5:
elif id == 4 or id == 5:
if id == 4:
offset_blocks_by_index(selected_items, -1)
else:
offset_blocks_by_index(selected_items, +1)
elif index == 8:
elif id == 6:
var events_indexed : Dictionary
if item in selected_items:
events_indexed = get_events_indexed(selected_items)
@ -936,6 +974,12 @@ func _on_event_popup_menu_index_pressed(index:int) -> void:
indent_events()
func play_from_here(index:=-1) -> void:
if index == -1:
if not selected_items.is_empty():
index = selected_items[0].get_index()
timeline_editor.play_timeline(index)
func _on_right_sidebar_resized() -> void:
var _scale := DialogicUtil.get_editor_scale()
@ -1045,6 +1089,11 @@ func _input(event:InputEvent) -> void:
_add_event_button_pressed(DialogicLabelEvent.new(), true)
get_viewport().set_input_as_handled()
"Ctrl+F6" when OS.get_name() != "macOS": # Play from here
play_from_here()
"Ctrl+Shift+B" when OS.get_name() == "macOS": # Play from here
play_from_here()
## Some shortcuts should be disabled when writing text.
var focus_owner: Control = get_viewport().gui_get_focus_owner()
if focus_owner is TextEdit or focus_owner is LineEdit or (focus_owner is Button and focus_owner.get_parent_control().name == "Spin"):
@ -1099,16 +1148,17 @@ func _input(event:InputEvent) -> void:
get_viewport().set_input_as_handled()
"Ctrl+C":
select_events_indexed(get_events_indexed(selected_items))
copy_selected_events()
get_viewport().set_input_as_handled()
"Ctrl+V":
var events_list := get_clipboard_data()
var paste_position := -1
var paste_position := 0
if selected_items:
paste_position = selected_items[-1].get_index()+1
else:
paste_position = %Timeline.get_child_count()-1
paste_position = %Timeline.get_child_count()
if events_list:
TimelineUndoRedo.create_action("[D] Pasting "+str(len(events_list))+" event(s).")
TimelineUndoRedo.add_do_method(add_events_at_index.bind(events_list, paste_position))
@ -1178,23 +1228,37 @@ func get_previous_character(double_previous := false) -> DialogicCharacter:
################################################################################
var search_results := {}
func _search_timeline(search_text:String) -> bool:
for event in search_results:
if is_instance_valid(search_results[event]):
search_results[event].set_search_text("")
search_results[event].deselect()
search_results[event].queue_redraw()
func _search_timeline(search_text:String, match_case := false, whole_words := false) -> bool:
var flags := 0
if match_case:
flags = flags | TextEdit.SEARCH_MATCH_CASE
if whole_words:
flags = flags | TextEdit.SEARCH_WHOLE_WORDS
search_results.clear()
# This checks all text events for whether they contain the text.
# If so, the text field is stored in search_results
# which is later used to navigate through only the relevant text fields.
for block in %Timeline.get_children():
if block.resource is DialogicTextEvent:
var text_field: TextEdit = block.get_node("%BodyContent").find_child("Field_Text_Multiline", true, false)
var text_field: TextEdit = block.get_field_node("text")
text_field.deselect()
text_field.set_search_text(search_text)
if text_field.search(search_text, 0, 0, 0).x != -1:
text_field.set_search_flags(flags)
if text_field.search(search_text, flags, 0, 0).x != -1:
search_results[block] = text_field
text_field.queue_redraw()
text_field.queue_redraw()
set_meta("current_search", search_text)
set_meta("current_search_flags", flags)
search_navigate(false)
return not search_results.is_empty()
@ -1207,25 +1271,61 @@ func _search_navigate_up() -> void:
func search_navigate(navigate_up := false) -> void:
var next_pos := get_next_search_position(navigate_up)
if next_pos:
var event: Node = next_pos[0]
var field: TextEdit = next_pos[1]
var result: Vector2i = next_pos[2]
if not event in selected_items:
select_item(next_pos[0], false)
%TimelineArea.ensure_control_visible(event)
event._on_ToggleBodyVisibility_toggled(true)
field.call_deferred("select", result.y, result.x, result.y, result.x+len(get_meta("current_search")))
func get_next_search_position(navigate_up:= false, include_current := false) -> Array:
var search_text: String = get_meta("current_search", "")
var search_flags: int = get_meta("current_search_flags", 0)
if search_results.is_empty() or %Timeline.get_child_count() == 0:
return
if selected_items.is_empty():
select_item(%Timeline.get_child(0), false)
return []
# We start the search on the selected item,
# so these checks make sure something sensible is selected
# Try to select the event that has focus
if get_viewport().gui_get_focus_owner() is TextEdit and get_viewport().gui_get_focus_owner() is DialogicVisualEditorField:
select_item(get_viewport().gui_get_focus_owner().event_resource.editor_node, false)
get_viewport().gui_get_focus_owner().deselect()
# Select the first event if nothing is selected
if selected_items.is_empty():
select_item(search_results.keys()[0], false)
# Loop to the next event that where something was found
if not selected_items[0] in search_results:
var index: int = selected_items[0].get_index()
while not %Timeline.get_child(index) in search_results:
index = wrapi(index+1, 0, %Timeline.get_child_count()-1)
select_item(%Timeline.get_child(index), false)
while not selected_items[0] in search_results:
select_item(%Timeline.get_child(wrapi(selected_items[0].get_index()+1, 0, %Timeline.get_child_count()-1)), false)
var event: Node = selected_items[0]
var counter := 0
var first := true
while true:
counter += 1
var field: TextEdit = search_results[event]
field.queue_redraw()
var result := search_text_field(field, search_text, navigate_up)
# First locates the next result in this field
var result := search_text_field(field, search_text, search_flags, navigate_up, first and include_current)
var current_line := field.get_selection_from_line() if field.has_selection() else -1
var current_column := field.get_selection_from_column() if field.has_selection() else -1
first = false
# Determines if the found result is valid or navigation should continue into the next event
var next_is_in_this_event := false
if result.y == -1:
next_is_in_this_event = false
@ -1234,28 +1334,27 @@ func search_navigate(navigate_up := false) -> void:
current_line = field.get_line_count()-1
current_column = field.get_line(current_line).length()
next_is_in_this_event = result.x < current_column or result.y < current_line
elif include_current:
next_is_in_this_event = true
else:
next_is_in_this_event = result.x > current_column or result.y > current_line
# If the next result was found return it
if next_is_in_this_event:
if not event in selected_items:
select_item(event, false)
%TimelineArea.ensure_control_visible(event)
event._on_ToggleBodyVisibility_toggled(true)
field.select(result.y, result.x, result.y, result.x+len(search_text))
break
return [event, field, result]
else:
field.deselect()
var index := search_results.keys().find(event)
event = search_results.keys()[wrapi(index+(-1 if navigate_up else 1), 0, search_results.size())]
# Otherwise deselct this field and continue in the next/previous
field.deselect()
var index := search_results.keys().find(event)
event = search_results.keys()[wrapi(index+(-1 if navigate_up else 1), 0, search_results.size())]
if counter > 5:
print("[Dialogic] Search failed.")
break
return []
func search_text_field(field:TextEdit, search_text := "", navigate_up:= false) -> Vector2i:
func search_text_field(field:TextEdit, search_text := "", flags:= 0, navigate_up:= false, include_current := false) -> Vector2i:
var search_from_line: int = 0
var search_from_column: int = 0
if field.has_selection():
@ -1267,6 +1366,9 @@ func search_text_field(field:TextEdit, search_text := "", navigate_up:= false) -
if search_from_line == -1:
return Vector2i(-1, -1)
search_from_column = field.get_line(search_from_line).length()-1
elif include_current:
search_from_line = field.get_selection_from_line()
search_from_column = field.get_selection_from_column()
else:
search_from_line = field.get_selection_to_line()
search_from_column = field.get_selection_to_column()
@ -1275,7 +1377,62 @@ func search_text_field(field:TextEdit, search_text := "", navigate_up:= false) -
search_from_line = field.get_line_count()-1
search_from_column = field.get_line(search_from_line).length()-1
var search := field.search(search_text, 4 if navigate_up else 0, search_from_line, search_from_column)
if navigate_up:
flags = flags | TextEdit.SEARCH_BACKWARDS
var search := field.search(search_text, flags, search_from_line, search_from_column)
return search
func replace(replace_text:String) -> void:
var next_pos := get_next_search_position(false, true)
var event: Node = next_pos[0]
var field: TextEdit = next_pos[1]
var result: Vector2i = next_pos[2]
if field.has_selection():
field.set_caret_column(field.get_selection_from_column())
field.set_caret_line(field.get_selection_from_line())
field.begin_complex_operation()
field.insert_text("@@", result.y, result.x)
if get_meta("current_search_flags") & TextEdit.SEARCH_MATCH_CASE:
field.text = field.text.replace("@@"+get_meta("current_search"), replace_text)
else:
field.text = field.text.replacen("@@"+get_meta("current_search"), replace_text)
field.end_complex_operation()
timeline_editor.replace_in_timeline()
func replace_all(replace_text:String) -> void:
var next_pos := get_next_search_position()
if not next_pos:
return
var event: Node = next_pos[0]
var field: TextEdit = next_pos[1]
var result: Vector2i = next_pos[2]
field.begin_complex_operation()
while next_pos:
event = next_pos[0]
if field != next_pos[1]:
field.end_complex_operation()
field = next_pos[1]
field.begin_complex_operation()
result = next_pos[2]
if field.has_selection():
field.set_caret_column(field.get_selection_from_column())
field.set_caret_line(field.get_selection_from_line())
field.insert_text("@@", result.y, result.x)
if get_meta("current_search_flags") & TextEdit.SEARCH_MATCH_CASE:
field.text = field.text.replace("@@"+get_meta("current_search"), replace_text)
else:
field.text = field.text.replacen("@@"+get_meta("current_search"), replace_text)
next_pos = get_next_search_position()
field.end_complex_operation()
timeline_editor.replace_in_timeline()
#endregion

View file

@ -1 +1 @@
uid://csmcfo7lnabpw
uid://cvgok7bxva5e2

View file

@ -1,17 +1,17 @@
[gd_scene load_steps=10 format=3 uid="uid://ysqbusmy0qma"]
[ext_resource type="Script" uid="uid://csmcfo7lnabpw" path="res://addons/dialogic/Editor/TimelineEditor/VisualEditor/timeline_editor_visual.gd" id="1_8smxc"]
[ext_resource type="Script" uid="uid://cvgok7bxva5e2" 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" uid="uid://b1y8xx4okklym" path="res://addons/dialogic/Editor/TimelineEditor/VisualEditor/TimelineArea.gd" id="3_sap1x"]
[ext_resource type="Script" uid="uid://doek0kltvf65w" path="res://addons/dialogic/Editor/Events/EventBlock/event_right_click_menu.gd" id="4_ugiq6"]
[ext_resource type="Script" uid="uid://b6ka2avnh1u55" path="res://addons/dialogic/Editor/TimelineEditor/VisualEditor/TimelineArea.gd" id="3_sap1x"]
[ext_resource type="Script" uid="uid://n1knm2ohcehu" 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"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_6gqu8"]
bg_color = Color(0, 0, 0, 1)
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_dov6v"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_jujwh"]
content_margin_left = 4.0
content_margin_top = 4.0
content_margin_right = 4.0
@ -33,7 +33,7 @@ data = {
"width": 16
}
[sub_resource type="ImageTexture" id="ImageTexture_vg181"]
[sub_resource type="ImageTexture" id="ImageTexture_xe7d2"]
image = SubResource("Image_3temo")
[node name="TimelineVisualEditor" type="MarginContainer"]
@ -70,27 +70,32 @@ 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 = ""
theme_override_styles/panel = SubResource("StyleBoxFlat_6gqu8")
theme_override_styles/hover = SubResource("StyleBoxFlat_jujwh")
item_count = 9
item_0/text = "Duplicate"
item_0/icon = SubResource("ImageTexture_xe7d2")
item_1/id = -1
item_1/separator = true
item_2/text = "Move up"
item_2/icon = SubResource("ImageTexture_vg181")
item_2/text = "Documentation"
item_2/icon = SubResource("ImageTexture_xe7d2")
item_2/id = 2
item_3/text = "Move down"
item_3/icon = SubResource("ImageTexture_vg181")
item_3/text = "Open Code"
item_3/icon = SubResource("ImageTexture_xe7d2")
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/text = "Move up"
item_5/icon = SubResource("ImageTexture_xe7d2")
item_5/id = 5
item_6/text = "Move down"
item_6/icon = SubResource("ImageTexture_xe7d2")
item_6/id = 6
item_7/id = -1
item_7/separator = true
item_8/text = "Delete"
item_8/icon = SubResource("ImageTexture_xe7d2")
item_8/id = 8
script = ExtResource("4_ugiq6")
[node name="RightSidebar" type="ScrollContainer" parent="View"]
@ -107,5 +112,5 @@ 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="id_pressed" from="View/TimelineArea/EventPopupMenu" to="." method="_on_event_popup_menu_id_pressed"]
[connection signal="resized" from="View/RightSidebar" to="." method="_on_right_sidebar_resized"]