Some changes

This commit is contained in:
2026-06-11 19:59:31 -05:00
parent 2f2ea060b1
commit eec429147b
150 changed files with 16615 additions and 262 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1 @@
uid://djeybvlb332mp
@@ -0,0 +1,56 @@
[gd_scene format=3 uid="uid://civ6shmka5e8u"]
[ext_resource type="Script" uid="uid://klpiq4tk3t7a" path="res://addons/dialogue_manager/components/code_edit_syntax_highlighter.gd" id="1_58cfo"]
[ext_resource type="Script" uid="uid://djeybvlb332mp" path="res://addons/dialogue_manager/components/code_edit.gd" id="1_g324i"]
[sub_resource type="SyntaxHighlighter" id="SyntaxHighlighter_crbvo"]
script = ExtResource("1_58cfo")
[node name="CodeEdit" type="CodeEdit" unique_id=236286673]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
text = "~ title_thing
if this = \"that\" or 'this'
Nathan: Something
- Then [if test.thing() == 2.0] => somewhere
- Other => END!
~ somewhere
set has_something = true
=> END"
scroll_past_end_of_file = true
minimap_draw = true
syntax_highlighter = SubResource("SyntaxHighlighter_crbvo")
highlight_all_occurrences = true
highlight_current_line = true
draw_tabs = true
symbol_lookup_on_click = true
line_folding = true
gutters_draw_line_numbers = true
gutters_draw_fold_gutter = true
delimiter_strings = Array[String](["\" \""])
delimiter_comments = Array[String](["#"])
code_completion_enabled = true
code_completion_prefixes = Array[String]([">", "<"])
indent_automatic = true
auto_brace_completion_enabled = true
auto_brace_completion_highlight_matching = true
auto_brace_completion_pairs = {
"\"": "\"",
"(": ")",
"[": "]",
"{": "}"
}
script = ExtResource("1_g324i")
[connection signal="caret_changed" from="." to="." method="_on_code_edit_caret_changed"]
[connection signal="gutter_clicked" from="." to="." method="_on_code_edit_gutter_clicked"]
[connection signal="symbol_lookup" from="." to="." method="_on_code_edit_symbol_lookup"]
[connection signal="symbol_validate" from="." to="." method="_on_code_edit_symbol_validate"]
[connection signal="text_changed" from="." to="." method="_on_code_edit_text_changed"]
[connection signal="text_set" from="." to="." method="_on_code_edit_text_set"]
@@ -0,0 +1,237 @@
@tool
class_name DMSyntaxHighlighter extends SyntaxHighlighter
var regex: DMCompilerRegEx = DMCompilerRegEx.new()
var compilation: DMCompilation = DMCompilation.new()
var expression_parser = DMExpressionParser.new()
var cache: Dictionary = {}
func _clear_highlighting_cache() -> void:
cache.clear()
func _get_line_syntax_highlighting(line: int) -> Dictionary:
expression_parser.include_comments = true
var colors: Dictionary = {}
var text_edit: TextEdit = get_text_edit()
var text: String = text_edit.get_line(line)
# Prevent an error from popping up while developing
if not is_instance_valid(text_edit) or text_edit.theme_overrides.is_empty():
return colors
# Disable this, as well as the line at the bottom of this function to remove the cache.
if text in cache:
return cache[text]
var theme: Dictionary = text_edit.theme_overrides
var index: int = 0
match DMCompiler.get_line_type(text):
DMConstants.TYPE_USING:
colors[index] = { color = theme.conditions_color }
colors[index + "using ".length()] = { color = theme.text_color }
DMConstants.TYPE_IMPORT:
colors[index] = { color = theme.conditions_color }
var import: RegExMatch = regex.IMPORT_REGEX.search(text)
if import:
colors[index + import.get_start("path") - 1] = { color = theme.strings_color }
colors[index + import.get_end("path") + 1] = { color = theme.conditions_color }
colors[index + import.get_start("prefix")] = { color = theme.text_color }
DMConstants.TYPE_COMMENT:
colors[index] = { color = theme.comments_color }
DMConstants.TYPE_TITLE:
colors[index] = { color = theme.titles_color }
DMConstants.TYPE_CONDITION, DMConstants.TYPE_WHILE, DMConstants.TYPE_MATCH, DMConstants.TYPE_WHEN:
colors[0] = { color = theme.conditions_color }
index = text.find(" ")
if index > -1:
var expression: Array = expression_parser.tokenise(text.substr(index), DMConstants.TYPE_CONDITION, 0)
if expression.size() == 0:
colors[index] = { color = theme.critical_color }
else:
_highlight_expression(expression, colors, index)
DMConstants.TYPE_MUTATION:
colors[0] = { color = theme.mutations_line_color }
index = text.find(" ")
var expression: Array = expression_parser.tokenise(text.substr(index), DMConstants.TYPE_MUTATION, 0)
if expression.size() == 0:
colors[index] = { color = theme.critical_color }
else:
_highlight_expression(expression, colors, index)
DMConstants.TYPE_GOTO:
if text.strip_edges().begins_with("%"):
colors[index] = { color = theme.symbols_color }
index = text.find(" ")
_highlight_goto(text, colors, index)
DMConstants.TYPE_RANDOM:
colors[index] = { color = theme.symbols_color }
DMConstants.TYPE_DIALOGUE, DMConstants.TYPE_RESPONSE:
if text.strip_edges().begins_with("%"):
colors[index] = { color = theme.symbols_color }
index = text.find(" ", text.find("%"))
colors[index] = { color = theme.text_color.lerp(theme.symbols_color, 0.5) }
var dialogue_text: String = text.substr(index, text.find("=>"))
# Highlight character name (but ignore ":" within line ID reference)
var split_index: int = dialogue_text.replace("\\:", "??").find(":")
if text.substr(split_index - 3, 3) != "[ID":
colors[index + split_index + 1] = { color = theme.text_color }
else:
# If there's no character name then just highlight the text as dialogue.
colors[index] = { color = theme.text_color }
# Interpolation
var replacements: Array[RegExMatch] = regex.REPLACEMENTS_REGEX.search_all(dialogue_text)
for replacement: RegExMatch in replacements:
var expression_text: String = replacement.get_string().substr(0, replacement.get_string().length() - 2).substr(2)
var expression: Array = expression_parser.tokenise(expression_text, DMConstants.TYPE_MUTATION, replacement.get_start())
var expression_index: int = index + replacement.get_start()
colors[expression_index] = { color = theme.symbols_color }
if expression.size() == 0 or expression[0].type == DMConstants.TYPE_ERROR:
colors[expression_index] = { color = theme.critical_color }
else:
_highlight_expression(expression, colors, index + 2)
colors[expression_index + expression_text.length() + 2] = { color = theme.symbols_color }
colors[expression_index + expression_text.length() + 4] = { color = theme.text_color }
# Tags (and inline mutations)
var resolved_line_data: DMResolvedLineData = DMResolvedLineData.new("")
var bbcodes: Array[Dictionary] = resolved_line_data.find_bbcode_positions_in_string(dialogue_text, true, true)
for bbcode: Dictionary in bbcodes:
var tag: String = bbcode.code
var code: String = bbcode.raw_args
if code.begins_with("["):
colors[index + bbcode.start] = { color = theme.symbols_color }
colors[index + bbcode.start + 2] = { color = theme.text_color }
var pipe_cursor: int = code.find("|")
while pipe_cursor > -1:
colors[index + bbcode.start + pipe_cursor + 1] = { color = theme.symbols_color }
colors[index + bbcode.start + pipe_cursor + 2] = { color = theme.text_color }
pipe_cursor = code.find("|", pipe_cursor + 1)
colors[index + bbcode.end - 1] = { color = theme.symbols_color }
colors[index + bbcode.end + 1] = { color = theme.text_color }
else:
colors[index + bbcode.start] = { color = theme.symbols_color }
if tag.begins_with("$>") or tag.begins_with("do") or tag.begins_with("set") or tag.begins_with("if"):
if tag.begins_with("if"):
colors[index + bbcode.start + 1] = { color = theme.conditions_color }
else:
colors[index + bbcode.start + 1] = { color = theme.mutations_line_color }
var expression: Array = expression_parser.tokenise(code, DMConstants.TYPE_MUTATION, bbcode.start + bbcode.code.length())
if expression.size() == 0 or expression[0].type == DMConstants.TYPE_ERROR:
colors[index + bbcode.start + tag.length() + 1] = { color = theme.critical_color }
else:
_highlight_expression(expression, colors, index + 2)
# else and closing if have no expression
elif tag.begins_with("else") or tag.begins_with("/if"):
colors[index + bbcode.start + 1] = { color = theme.conditions_color }
colors[index + bbcode.end] = { color = theme.symbols_color }
colors[index + bbcode.end + 1] = { color = theme.text_color }
# Jumps
if "=> " in text or "=>< " in text:
_highlight_goto(text, colors, index)
# Order the dictionary keys to prevent CodeEdit from having issues
var ordered_colors: Dictionary = {}
var ordered_keys: Array = colors.keys()
ordered_keys.sort()
for key_index: int in ordered_keys:
ordered_colors[key_index] = colors[key_index]
cache[text] = ordered_colors
return ordered_colors
func _highlight_expression(tokens: Array, colors: Dictionary, index: int) -> int:
var theme: Dictionary = get_text_edit().theme_overrides
var last_index: int = index
for token: Dictionary in tokens:
last_index = token.i
match token.type:
DMConstants.TOKEN_COMMENT:
colors[index + token.i] = { color = theme.comments_color }
DMConstants.TOKEN_CONDITION, DMConstants.TOKEN_AND_OR, DMConstants.TOKEN_NOT:
colors[index + token.i] = { color = theme.conditions_color }
DMConstants.TOKEN_VARIABLE:
if token.value in ["true", "false"]:
colors[index + token.i] = { color = theme.conditions_color }
else:
colors[index + token.i] = { color = theme.members_color }
DMConstants.TOKEN_OPERATOR, DMConstants.TOKEN_COLON, \
DMConstants.TOKEN_COMMA, DMConstants.TOKEN_DOT, DMConstants.TOKEN_NULL_COALESCE, \
DMConstants.TOKEN_ASSIGNMENT, DMConstants.TOKEN_COMPARISON:
if token.get("value", null) == "in":
colors[index + token.i] = { color = theme.conditions_color }
else:
colors[index + token.i] = { color = theme.symbols_color }
DMConstants.TOKEN_NUMBER:
colors[index + token.i] = { color = theme.numbers_color }
DMConstants.TOKEN_STRING:
colors[index + token.i] = { color = theme.strings_color }
DMConstants.TOKEN_FUNCTION:
colors[index + token.i] = { color = theme.mutations_color }
colors[index + token.i + token.function.length()] = { color = theme.symbols_color }
for parameter: Array in token.value:
last_index = _highlight_expression(parameter, colors, index)
DMConstants.TOKEN_PARENS_CLOSE:
colors[index + token.i] = { color = theme.symbols_color }
DMConstants.TOKEN_DICTIONARY_REFERENCE:
colors[index + token.i] = { color = theme.members_color }
colors[index + token.i + token.variable.length()] = { color = theme.symbols_color }
last_index = _highlight_expression(token.value, colors, index)
DMConstants.TOKEN_ARRAY:
colors[index + token.i] = { color = theme.symbols_color }
for item: Array in token.value:
last_index = _highlight_expression(item, colors, index)
DMConstants.TOKEN_BRACKET_CLOSE:
colors[index + token.i] = { color = theme.symbols_color }
DMConstants.TOKEN_DICTIONARY:
colors[index + token.i] = { color = theme.symbols_color }
last_index = _highlight_expression(token.value.keys() + token.value.values(), colors, index)
DMConstants.TOKEN_BRACE_CLOSE:
colors[index + token.i] = { color = theme.symbols_color }
last_index += 1
DMConstants.TOKEN_GROUP:
last_index = _highlight_expression(token.value, colors, index)
return last_index
func _highlight_goto(text: String, colors: Dictionary, index: int) -> int:
var theme: Dictionary = get_text_edit().theme_overrides
var goto_data: DMResolvedGotoData = DMResolvedGotoData.new(text, {})
colors[goto_data.index] = { color = theme.jumps_color }
if "{{" in text:
index = text.find("{{", goto_data.index)
var last_index: int = 0
if goto_data.error:
colors[index + 2] = { color = theme.critical_color }
else:
last_index = _highlight_expression(goto_data.expression, colors, index)
index = text.find("}}", index + last_index)
colors[index] = { color = theme.jumps_color }
return index
@@ -0,0 +1 @@
uid://klpiq4tk3t7a
@@ -0,0 +1,84 @@
@tool
extends Control
signal failed()
signal updated(updated_to_version: String)
const DialogueConstants = preload("../constants.gd")
const TEMP_FILE_NAME = "user://temp.zip"
@onready var logo: TextureRect = %Logo
@onready var label: Label = $VBox/Label
@onready var http_request: HTTPRequest = $HTTPRequest
@onready var download_button: Button = %DownloadButton
var next_version_release: Dictionary:
set(value):
next_version_release = value
label.text = DialogueConstants.translate(&"update.is_available_for_download") % value.tag_name.substr(1)
get:
return next_version_release
func _ready() -> void:
$VBox/Center/DownloadButton.text = DialogueConstants.translate(&"update.download_update")
$VBox/Center2/NotesButton.text = DialogueConstants.translate(&"update.release_notes")
### Signals
func _on_download_button_pressed() -> void:
# Safeguard the actual dialogue manager repo from accidentally updating itself
if FileAccess.file_exists("res://tests/test_basic_dialogue.gd"):
prints("You can't update the addon from within itself.")
failed.emit()
return
http_request.request(next_version_release.zipball_url)
download_button.disabled = true
download_button.text = DialogueConstants.translate(&"update.downloading")
func _on_http_request_request_completed(result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray) -> void:
if result != HTTPRequest.RESULT_SUCCESS:
failed.emit()
return
# Save the downloaded zip
var zip_file: FileAccess = FileAccess.open(TEMP_FILE_NAME, FileAccess.WRITE)
zip_file.store_buffer(body)
zip_file.close()
OS.move_to_trash(ProjectSettings.globalize_path("res://addons/dialogue_manager"))
var zip_reader: ZIPReader = ZIPReader.new()
zip_reader.open(TEMP_FILE_NAME)
var files: PackedStringArray = zip_reader.get_files()
var base_path = files[1]
# Remove archive folder
files.remove_at(0)
# Remove assets folder
files.remove_at(0)
for path in files:
var new_file_path: String = path.replace(base_path, "")
if path.ends_with("/"):
DirAccess.make_dir_recursive_absolute("res://addons/%s" % new_file_path)
else:
var file: FileAccess = FileAccess.open("res://addons/%s" % new_file_path, FileAccess.WRITE)
file.store_buffer(zip_reader.read_file(path))
zip_reader.close()
DirAccess.remove_absolute(TEMP_FILE_NAME)
updated.emit(next_version_release.tag_name.substr(1))
func _on_notes_button_pressed() -> void:
OS.shell_open(next_version_release.html_url)
@@ -0,0 +1 @@
uid://kpwo418lb2t2
@@ -0,0 +1,60 @@
[gd_scene load_steps=3 format=3 uid="uid://qdxrxv3c3hxk"]
[ext_resource type="Script" uid="uid://kpwo418lb2t2" path="res://addons/dialogue_manager/components/download_update_panel.gd" id="1_4tm1k"]
[ext_resource type="Texture2D" uid="uid://d3baj6rygkb3f" path="res://addons/dialogue_manager/assets/update.svg" id="2_4o2m6"]
[node name="DownloadUpdatePanel" 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_4tm1k")
[node name="HTTPRequest" type="HTTPRequest" parent="."]
[node name="VBox" type="VBoxContainer" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = -1.0
offset_top = 9.0
offset_right = -1.0
offset_bottom = 9.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/separation = 10
[node name="Logo" type="TextureRect" parent="VBox"]
unique_name_in_owner = true
clip_contents = true
custom_minimum_size = Vector2(300, 80)
layout_mode = 2
texture = ExtResource("2_4o2m6")
stretch_mode = 5
[node name="Label" type="Label" parent="VBox"]
layout_mode = 2
text = "v1.2.3 is available for download."
horizontal_alignment = 1
[node name="Center" type="CenterContainer" parent="VBox"]
layout_mode = 2
[node name="DownloadButton" type="Button" parent="VBox/Center"]
unique_name_in_owner = true
layout_mode = 2
text = "Download update"
[node name="Center2" type="CenterContainer" parent="VBox"]
layout_mode = 2
[node name="NotesButton" type="LinkButton" parent="VBox/Center2"]
layout_mode = 2
text = "Read release notes"
[connection signal="request_completed" from="HTTPRequest" to="." method="_on_http_request_request_completed"]
[connection signal="pressed" from="VBox/Center/DownloadButton" to="." method="_on_download_button_pressed"]
[connection signal="pressed" from="VBox/Center2/NotesButton" to="." method="_on_notes_button_pressed"]
@@ -0,0 +1,45 @@
@tool
class_name DMDialogueEditorProperty extends EditorProperty
const DialoguePropertyEditorControl: PackedScene = preload("./editor_property_control.tscn")
var control = DialoguePropertyEditorControl.instantiate()
var current_value: DialogueResource
var is_updating: bool = false
func _init() -> void:
add_child(control)
control.resource = current_value
control.resource_changed.connect(_on_resource_changed)
func _update_property() -> void:
var next_value: DialogueResource = get_edited_object()[get_edited_property()]
# The resource might have been deleted elsewhere so check that it's not in a weird state
if is_instance_valid(next_value) and not next_value.resource_path.ends_with(".dialogue"):
emit_changed(get_edited_property(), null)
return
if next_value == current_value: return
is_updating = true
current_value = next_value
control.resource = current_value
is_updating = false
#region Signals
func _on_resource_changed(next_resource: DialogueResource) -> void:
emit_changed(get_edited_property(), next_resource)
#endregion
@@ -0,0 +1 @@
uid://nyypeje1a036
@@ -0,0 +1,153 @@
@tool
extends HBoxContainer
signal pressed()
signal resource_changed(next_resource: DialogueResource)
const ITEM_NEW: int = 100
const ITEM_QUICK_LOAD: int = 200
const ITEM_LOAD: int = 201
const ITEM_EDIT: int = 300
const ITEM_CLEAR: int = 301
const ITEM_FILESYSTEM: int = 400
@onready var button: Button = $ResourceButton
@onready var menu_button: Button = $MenuButton
@onready var menu: PopupMenu = $Menu
@onready var quick_open_dialog: ConfirmationDialog = $QuickOpenDialog
@onready var files_list = $QuickOpenDialog/FilesList
@onready var new_dialog: FileDialog = $NewDialog
@onready var open_dialog: FileDialog = $OpenDialog
var resource: Resource:
set(next_resource):
resource = next_resource
if button:
button.resource = resource
get:
return resource
var is_waiting_for_file: bool = false
var quick_selected_file: String = ""
func _ready() -> void:
menu_button.icon = get_theme_icon("GuiDropdown", "EditorIcons")
func build_menu() -> void:
menu.clear()
menu.add_icon_item(DMPlugin.instance._get_plugin_icon(), "New Dialogue", ITEM_NEW)
menu.add_separator()
menu.add_icon_item(get_theme_icon("Load", "EditorIcons"), "Quick Load", ITEM_QUICK_LOAD)
menu.add_icon_item(get_theme_icon("Load", "EditorIcons"), "Load", ITEM_LOAD)
if resource:
menu.add_icon_item(get_theme_icon("Edit", "EditorIcons"), "Edit", ITEM_EDIT)
menu.add_icon_item(get_theme_icon("Clear", "EditorIcons"), "Clear", ITEM_CLEAR)
menu.add_separator()
menu.add_item("Show in FileSystem", ITEM_FILESYSTEM)
menu.size = Vector2.ZERO
#region Signals
func _on_new_dialog_file_selected(path: String) -> void:
DMPlugin.instance.main_view.new_file(path)
is_waiting_for_file = false
if DMCache.has_file(path):
resource_changed.emit(load(path))
else:
var next_resource: DialogueResource = await DMPlugin.instance.import_plugin.compiled_resource
next_resource.resource_path = path
resource_changed.emit(next_resource)
func _on_open_dialog_file_selected(file: String) -> void:
resource_changed.emit(load(file))
func _on_file_dialog_canceled() -> void:
is_waiting_for_file = false
func _on_resource_button_pressed() -> void:
if is_instance_valid(resource):
EditorInterface.call_deferred("edit_resource", resource)
elif menu.visible:
menu.hide()
else:
build_menu()
menu.position = get_viewport().position + Vector2i(
button.global_position.x + button.size.x - menu.size.x,
2 + menu_button.global_position.y + button.size.y
)
menu.popup()
func _on_resource_button_resource_dropped(next_resource: Resource) -> void:
resource_changed.emit(next_resource)
func _on_menu_button_pressed() -> void:
if menu.visible:
menu.hide()
else:
build_menu()
menu.position = get_viewport().position + Vector2i(
menu_button.global_position.x + menu_button.size.x - menu.size.x,
2 + menu_button.global_position.y + menu_button.size.y
)
menu.popup()
func _on_menu_id_pressed(id: int) -> void:
match id:
ITEM_NEW:
is_waiting_for_file = true
new_dialog.popup_centered()
ITEM_QUICK_LOAD:
quick_selected_file = ""
files_list.files = DMCache.get_files()
if resource:
files_list.select_file(resource.resource_path)
quick_open_dialog.popup_centered()
files_list.focus_filter()
ITEM_LOAD:
is_waiting_for_file = true
open_dialog.popup_centered()
ITEM_EDIT:
EditorInterface.call_deferred("edit_resource", resource)
ITEM_CLEAR:
resource_changed.emit(null)
ITEM_FILESYSTEM:
EditorInterface.get_file_system_dock().navigate_to_path(resource.resource_path)
func _on_files_list_file_double_clicked(file_path: String) -> void:
resource_changed.emit(load(file_path))
quick_open_dialog.hide()
func _on_files_list_file_selected(file_path: String) -> void:
quick_selected_file = file_path
func _on_quick_open_dialog_confirmed() -> void:
if quick_selected_file != "":
resource_changed.emit(load(quick_selected_file))
#endregion
@@ -0,0 +1 @@
uid://dooe2pflnqtve
@@ -0,0 +1,58 @@
[gd_scene load_steps=4 format=3 uid="uid://ycn6uaj7dsrh"]
[ext_resource type="Script" uid="uid://dooe2pflnqtve" path="res://addons/dialogue_manager/components/editor_property/editor_property_control.gd" id="1_het12"]
[ext_resource type="PackedScene" uid="uid://b16uuqjuof3n5" path="res://addons/dialogue_manager/components/editor_property/resource_button.tscn" id="2_hh3d4"]
[ext_resource type="PackedScene" uid="uid://dnufpcdrreva3" path="res://addons/dialogue_manager/components/files_list.tscn" id="3_l8fp6"]
[node name="PropertyEditorButton" type="HBoxContainer"]
offset_right = 40.0
offset_bottom = 40.0
size_flags_horizontal = 3
theme_override_constants/separation = 0
script = ExtResource("1_het12")
[node name="ResourceButton" parent="." instance=ExtResource("2_hh3d4")]
layout_mode = 2
text = "<empty>"
text_overrun_behavior = 3
clip_text = true
[node name="MenuButton" type="Button" parent="."]
layout_mode = 2
[node name="Menu" type="PopupMenu" parent="."]
[node name="QuickOpenDialog" type="ConfirmationDialog" parent="."]
title = "Find Dialogue Resource"
size = Vector2i(400, 600)
min_size = Vector2i(400, 600)
ok_button_text = "Open"
[node name="FilesList" parent="QuickOpenDialog" instance=ExtResource("3_l8fp6")]
[node name="NewDialog" type="FileDialog" parent="."]
size = Vector2i(900, 750)
min_size = Vector2i(900, 750)
dialog_hide_on_ok = true
filters = PackedStringArray("*.dialogue ; Dialogue")
[node name="OpenDialog" type="FileDialog" parent="."]
title = "Open a File"
size = Vector2i(900, 750)
min_size = Vector2i(900, 750)
ok_button_text = "Open"
dialog_hide_on_ok = true
file_mode = 0
filters = PackedStringArray("*.dialogue ; Dialogue")
[connection signal="pressed" from="ResourceButton" to="." method="_on_resource_button_pressed"]
[connection signal="resource_dropped" from="ResourceButton" to="." method="_on_resource_button_resource_dropped"]
[connection signal="pressed" from="MenuButton" to="." method="_on_menu_button_pressed"]
[connection signal="id_pressed" from="Menu" to="." method="_on_menu_id_pressed"]
[connection signal="confirmed" from="QuickOpenDialog" to="." method="_on_quick_open_dialog_confirmed"]
[connection signal="file_double_clicked" from="QuickOpenDialog/FilesList" to="." method="_on_files_list_file_double_clicked"]
[connection signal="file_selected" from="QuickOpenDialog/FilesList" to="." method="_on_files_list_file_selected"]
[connection signal="canceled" from="NewDialog" to="." method="_on_file_dialog_canceled"]
[connection signal="file_selected" from="NewDialog" to="." method="_on_new_dialog_file_selected"]
[connection signal="canceled" from="OpenDialog" to="." method="_on_file_dialog_canceled"]
[connection signal="file_selected" from="OpenDialog" to="." method="_on_open_dialog_file_selected"]
@@ -0,0 +1,48 @@
@tool
extends Button
signal resource_dropped(next_resource: Resource)
var resource: Resource:
set(next_resource):
resource = next_resource
if resource:
icon = DMPlugin.instance._get_plugin_icon()
text = resource.resource_path.get_file().replace(".dialogue", "")
else:
icon = null
text = "<empty>"
get:
return resource
func _notification(what: int) -> void:
match what:
NOTIFICATION_DRAG_BEGIN:
var data = get_viewport().gui_get_drag_data()
if typeof(data) == TYPE_DICTIONARY and data.type == "files" and data.files.size() > 0 and data.files[0].ends_with(".dialogue"):
add_theme_stylebox_override("normal", get_theme_stylebox("focus", "LineEdit"))
add_theme_stylebox_override("hover", get_theme_stylebox("focus", "LineEdit"))
NOTIFICATION_DRAG_END:
self.resource = resource
remove_theme_stylebox_override("normal")
remove_theme_stylebox_override("hover")
func _can_drop_data(at_position: Vector2, data) -> bool:
if typeof(data) != TYPE_DICTIONARY: return false
if data.type != "files": return false
var files: PackedStringArray = Array(data.files).filter(func(f): return f.get_extension() == "dialogue")
return files.size() > 0
func _drop_data(at_position: Vector2, data) -> void:
var files: PackedStringArray = Array(data.files).filter(func(f): return f.get_extension() == "dialogue")
if files.size() == 0: return
resource_dropped.emit(load(files[0]))
@@ -0,0 +1 @@
uid://damhqta55t67c
@@ -0,0 +1,9 @@
[gd_scene load_steps=2 format=3 uid="uid://b16uuqjuof3n5"]
[ext_resource type="Script" uid="uid://damhqta55t67c" path="res://addons/dialogue_manager/components/editor_property/resource_button.gd" id="1_7u2i7"]
[node name="ResourceButton" type="Button"]
offset_right = 8.0
offset_bottom = 8.0
size_flags_horizontal = 3
script = ExtResource("1_7u2i7")
@@ -0,0 +1,85 @@
@tool
extends HBoxContainer
signal error_pressed(line_number)
const DialogueConstants = preload("../constants.gd")
@onready var error_button: Button = $ErrorButton
@onready var next_button: Button = $NextButton
@onready var count_label: Label = $CountLabel
@onready var previous_button: Button = $PreviousButton
## The index of the current error being shown
var error_index: int = 0:
set(next_error_index):
error_index = wrap(next_error_index, 0, errors.size())
show_error()
get:
return error_index
## The list of all errors
var errors: Array = []:
set(next_errors):
errors = next_errors
self.error_index = 0
get:
return errors
func _ready() -> void:
apply_theme()
hide()
## Set up colors and icons
func apply_theme() -> void:
error_button.add_theme_color_override("font_color", get_theme_color("error_color", "Editor"))
error_button.add_theme_color_override("font_hover_color", get_theme_color("error_color", "Editor"))
error_button.icon = get_theme_icon("StatusError", "EditorIcons")
previous_button.icon = get_theme_icon("ArrowLeft", "EditorIcons")
next_button.icon = get_theme_icon("ArrowRight", "EditorIcons")
## Move the error index to match a given line
func show_error_for_line_number(line_number: int) -> void:
for i in range(0, errors.size()):
if errors[i].line_number == line_number:
self.error_index = i
## Show the current error
func show_error() -> void:
if errors.size() == 0:
hide()
else:
show()
count_label.text = DialogueConstants.translate(&"n_of_n").format({ index = error_index + 1, total = errors.size() })
var error = errors[error_index]
error_button.text = DialogueConstants.translate(&"errors.line_and_message").format({ line = error.line_number, column = error.column_number, message = DialogueConstants.get_error_message(error.error) })
if error.has("external_error"):
error_button.text += " " + DialogueConstants.get_error_message(error.external_error)
### Signals
func _on_errors_panel_theme_changed() -> void:
apply_theme()
func _on_error_button_pressed() -> void:
error_pressed.emit(errors[error_index].line_number, errors[error_index].column_number)
func _on_previous_button_pressed() -> void:
self.error_index -= 1
_on_error_button_pressed()
func _on_next_button_pressed() -> void:
self.error_index += 1
_on_error_button_pressed()
@@ -0,0 +1 @@
uid://d2l8nlb6hhrfp
@@ -0,0 +1,56 @@
[gd_scene load_steps=4 format=3 uid="uid://cs8pwrxr5vxix"]
[ext_resource type="Script" uid="uid://d2l8nlb6hhrfp" path="res://addons/dialogue_manager/components/errors_panel.gd" id="1_nfm3c"]
[sub_resource type="Image" id="Image_w0gko"]
data = {
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 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, 93, 93, 131, 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, 93, 93, 131, 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, 93, 93, 131, 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, 252, 255, 93, 93, 252, 255, 93, 93, 252, 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, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 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, 93, 93, 55, 255, 97, 97, 58, 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, 97, 97, 42, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 98, 98, 47, 255, 97, 97, 42, 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, 93, 93, 233, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 94, 94, 46, 255, 93, 93, 236, 255, 93, 93, 233, 255, 97, 97, 42, 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, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 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_s6fxl"]
image = SubResource("Image_w0gko")
[node name="ErrorsPanel" type="HBoxContainer"]
visible = false
offset_right = 1024.0
offset_bottom = 600.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_nfm3c")
metadata/_edit_layout_mode = 1
[node name="ErrorButton" type="Button" parent="."]
layout_mode = 2
size_flags_horizontal = 3
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_colors/font_hover_color = Color(0, 0, 0, 1)
theme_override_constants/h_separation = 3
icon = SubResource("ImageTexture_s6fxl")
flat = true
alignment = 0
text_overrun_behavior = 4
[node name="Spacer" type="Control" parent="."]
custom_minimum_size = Vector2(40, 0)
layout_mode = 2
[node name="PreviousButton" type="Button" parent="."]
layout_mode = 2
icon = SubResource("ImageTexture_s6fxl")
flat = true
[node name="CountLabel" type="Label" parent="."]
layout_mode = 2
[node name="NextButton" type="Button" parent="."]
layout_mode = 2
icon = SubResource("ImageTexture_s6fxl")
flat = true
[connection signal="pressed" from="ErrorButton" to="." method="_on_error_button_pressed"]
[connection signal="pressed" from="PreviousButton" to="." method="_on_previous_button_pressed"]
[connection signal="pressed" from="NextButton" to="." method="_on_next_button_pressed"]
@@ -0,0 +1,150 @@
@tool
extends VBoxContainer
signal file_selected(file_path: String)
signal file_popup_menu_requested(at_position: Vector2)
signal file_double_clicked(file_path: String)
signal file_middle_clicked(file_path: String)
const DialogueConstants = preload("../constants.gd")
const MODIFIED_SUFFIX = "(*)"
@export var icon: Texture2D
@onready var filter_edit: LineEdit = $FilterEdit
@onready var list: ItemList = $List
var file_map: Dictionary = {}
var current_file_path: String = ""
var last_selected_file_path: String = ""
var files: PackedStringArray = []:
set(next_files):
files = next_files
files.sort()
update_file_map()
apply_filter()
get:
return files
var unsaved_files: Array[String] = []
var filter: String = "":
set(next_filter):
filter = next_filter
apply_filter()
get:
return filter
func _ready() -> void:
apply_theme()
filter_edit.placeholder_text = DialogueConstants.translate(&"files_list.filter")
func focus_filter() -> void:
filter_edit.grab_focus()
func select_file(file: String) -> void:
list.deselect_all()
for i in range(0, list.get_item_count()):
var item_text = list.get_item_text(i).replace(MODIFIED_SUFFIX, "")
if item_text == get_nice_file(file, item_text.count("/") + 1):
list.select(i)
last_selected_file_path = file
func mark_file_as_unsaved(file: String, is_unsaved: bool) -> void:
if not file in unsaved_files and is_unsaved:
unsaved_files.append(file)
elif file in unsaved_files and not is_unsaved:
unsaved_files.erase(file)
apply_filter()
func update_file_map() -> void:
file_map = {}
for file in files:
var nice_file: String = get_nice_file(file)
# See if a value with just the file name is already in the map
for key in file_map.keys():
if file_map[key] == nice_file:
var bit_count = nice_file.count("/") + 2
var existing_nice_file = get_nice_file(key, bit_count)
nice_file = get_nice_file(file, bit_count)
while nice_file == existing_nice_file:
bit_count += 1
existing_nice_file = get_nice_file(key, bit_count)
nice_file = get_nice_file(file, bit_count)
file_map[key] = existing_nice_file
file_map[file] = nice_file
func get_nice_file(file_path: String, path_bit_count: int = 1) -> String:
var bits = file_path.replace("res://", "").replace(".dialogue", "").split("/")
bits = bits.slice(-path_bit_count)
return "/".join(bits)
func apply_filter() -> void:
list.clear()
for file in file_map.keys():
if filter == "" or filter.to_lower() in file.to_lower():
var nice_file = file_map[file]
if file in unsaved_files:
nice_file += MODIFIED_SUFFIX
var new_id := list.add_item(nice_file)
list.set_item_icon(new_id, icon)
select_file(current_file_path)
func apply_theme() -> void:
if is_instance_valid(filter_edit):
filter_edit.right_icon = get_theme_icon("Search", "EditorIcons")
if is_instance_valid(list):
list.add_theme_stylebox_override("panel", get_theme_stylebox("panel", "Panel"))
### Signals
func _on_theme_changed() -> void:
apply_theme()
func _on_filter_edit_text_changed(new_text: String) -> void:
self.filter = new_text
func _on_list_item_clicked(index: int, at_position: Vector2, mouse_button_index: int) -> void:
var item_text = list.get_item_text(index).replace(MODIFIED_SUFFIX, "")
var file = file_map.find_key(item_text)
if mouse_button_index == MOUSE_BUTTON_LEFT or mouse_button_index == MOUSE_BUTTON_RIGHT:
select_file(file)
file_selected.emit(file)
if mouse_button_index == MOUSE_BUTTON_RIGHT:
file_popup_menu_requested.emit(at_position)
if mouse_button_index == MOUSE_BUTTON_MIDDLE:
file_middle_clicked.emit(file)
func _on_list_item_activated(index: int) -> void:
var item_text = list.get_item_text(index).replace(MODIFIED_SUFFIX, "")
var file = file_map.find_key(item_text)
select_file(file)
file_double_clicked.emit(file)
@@ -0,0 +1 @@
uid://dqa4a4wwoo0aa
@@ -0,0 +1,29 @@
[gd_scene load_steps=3 format=3 uid="uid://dnufpcdrreva3"]
[ext_resource type="Script" uid="uid://dqa4a4wwoo0aa" path="res://addons/dialogue_manager/components/files_list.gd" id="1_cytii"]
[ext_resource type="Texture2D" uid="uid://d3lr2uas6ax8v" path="res://addons/dialogue_manager/assets/icon.svg" id="2_3ijx1"]
[node name="FilesList" type="VBoxContainer"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
size_flags_vertical = 3
script = ExtResource("1_cytii")
icon = ExtResource("2_3ijx1")
[node name="FilterEdit" type="LineEdit" parent="."]
layout_mode = 2
placeholder_text = "Filter files"
clear_button_enabled = true
[node name="List" type="ItemList" parent="."]
layout_mode = 2
size_flags_vertical = 3
allow_rmb_select = true
[connection signal="theme_changed" from="." to="." method="_on_theme_changed"]
[connection signal="text_changed" from="FilterEdit" to="." method="_on_filter_edit_text_changed"]
[connection signal="item_activated" from="List" to="." method="_on_list_item_activated"]
[connection signal="item_clicked" from="List" to="." method="_on_list_item_clicked"]
@@ -0,0 +1,222 @@
@tool
extends VBoxContainer
signal open_requested()
signal close_requested()
const DialogueConstants = preload("../constants.gd")
@onready var input: LineEdit = $Search/Input
@onready var result_label: Label = $Search/ResultLabel
@onready var previous_button: Button = $Search/PreviousButton
@onready var next_button: Button = $Search/NextButton
@onready var match_case_button: CheckBox = $Search/MatchCaseCheckBox
@onready var replace_check_button: CheckButton = $Search/ReplaceCheckButton
@onready var replace_panel: HBoxContainer = $Replace
@onready var replace_input: LineEdit = $Replace/Input
@onready var replace_button: Button = $Replace/ReplaceButton
@onready var replace_all_button: Button = $Replace/ReplaceAllButton
# The code edit we will be affecting (for some reason exporting this didn't work)
var code_edit: CodeEdit:
set(next_code_edit):
code_edit = next_code_edit
code_edit.gui_input.connect(_on_text_edit_gui_input)
code_edit.text_changed.connect(_on_text_edit_text_changed)
get:
return code_edit
var results: Array = []
var result_index: int = -1:
set(next_result_index):
result_index = next_result_index
if results.size() > 0:
var r = results[result_index]
code_edit.set_caret_line(r[0])
code_edit.select(r[0], r[1], r[0], r[1] + r[2])
code_edit.center_viewport_to_caret()
else:
result_index = -1
if is_instance_valid(code_edit):
code_edit.deselect()
result_label.text = DialogueConstants.translate(&"n_of_n").format({ index = result_index + 1, total = results.size() })
get:
return result_index
func _ready() -> void:
apply_theme()
input.placeholder_text = DialogueConstants.translate(&"search.placeholder")
previous_button.tooltip_text = DialogueConstants.translate(&"search.previous")
next_button.tooltip_text = DialogueConstants.translate(&"search.next")
match_case_button.text = DialogueConstants.translate(&"search.match_case")
$Search/ReplaceCheckButton.text = DialogueConstants.translate(&"search.toggle_replace")
replace_button.text = DialogueConstants.translate(&"search.replace")
replace_all_button.text = DialogueConstants.translate(&"search.replace_all")
$Replace/ReplaceLabel.text = DialogueConstants.translate(&"search.replace_with")
self.result_index = -1
replace_panel.hide()
replace_button.disabled = true
replace_all_button.disabled = true
hide()
func focus_line_edit() -> void:
input.grab_focus()
input.select_all()
search()
func apply_theme() -> void:
if is_instance_valid(previous_button):
previous_button.icon = get_theme_icon("ArrowLeft", "EditorIcons")
if is_instance_valid(next_button):
next_button.icon = get_theme_icon("ArrowRight", "EditorIcons")
# Find text in the code
func search(text: String = "", default_result_index: int = 0) -> void:
results.clear()
if text == "":
text = input.text
var lines = code_edit.text.split("\n")
for line_number in range(0, lines.size()):
var line = lines[line_number]
var column = find_in_line(line, text, 0)
while column > -1:
results.append([line_number, column, text.length()])
column = find_in_line(line, text, column + 1)
if results.size() > 0:
replace_button.disabled = false
replace_all_button.disabled = false
else:
replace_button.disabled = true
replace_all_button.disabled = true
self.result_index = clamp(default_result_index, 0, results.size() - 1)
# Find text in a string and match case if requested
func find_in_line(line: String, text: String, from_index: int = 0) -> int:
if match_case_button.button_pressed:
return line.find(text, from_index)
else:
return line.findn(text, from_index)
#region Signals
func _on_text_edit_gui_input(event: InputEvent) -> void:
if event is InputEventKey and event.is_pressed():
match event.as_text():
"Ctrl+F", "Command+F":
open_requested.emit()
get_viewport().set_input_as_handled()
"Ctrl+Shift+R", "Command+Shift+R":
replace_check_button.set_pressed(true)
open_requested.emit()
get_viewport().set_input_as_handled()
func _on_text_edit_text_changed() -> void:
results.clear()
func _on_search_and_replace_theme_changed() -> void:
apply_theme()
func _on_input_text_changed(new_text: String) -> void:
search(new_text)
func _on_previous_button_pressed() -> void:
self.result_index = wrapi(result_index - 1, 0, results.size())
func _on_next_button_pressed() -> void:
self.result_index = wrapi(result_index + 1, 0, results.size())
func _on_search_and_replace_visibility_changed() -> void:
if is_instance_valid(input):
if visible:
input.grab_focus()
var selection = code_edit.get_selected_text()
if input.text == "" and selection != "":
input.text = selection
search(selection)
else:
search()
else:
input.text = ""
func _on_input_gui_input(event: InputEvent) -> void:
if event is InputEventKey and event.is_pressed():
match event.as_text():
"Enter":
if results.size() == 0:
search(input.text)
self.result_index = wrapi(result_index + 1, 0, results.size())
"Escape":
emit_signal("close_requested")
func _on_replace_button_pressed() -> void:
if result_index == -1: return
# Replace the selection at result index
var r: Array = results[result_index]
code_edit.begin_complex_operation()
var lines: PackedStringArray = code_edit.text.split("\n")
var line: String = lines[r[0]]
line = line.substr(0, r[1]) + replace_input.text + line.substr(r[1] + r[2])
lines[r[0]] = line
code_edit.text = "\n".join(lines)
code_edit.end_complex_operation()
code_edit.text_changed.emit()
search(input.text, result_index)
func _on_replace_all_button_pressed() -> void:
if match_case_button.button_pressed:
code_edit.text = code_edit.text.replace(input.text, replace_input.text)
else:
code_edit.text = code_edit.text.replacen(input.text, replace_input.text)
search()
code_edit.text_changed.emit()
func _on_replace_check_button_toggled(button_pressed: bool) -> void:
replace_panel.visible = button_pressed
if button_pressed:
replace_input.grab_focus()
func _on_input_focus_entered() -> void:
if results.size() == 0:
search()
else:
self.result_index = result_index
func _on_match_case_check_box_toggled(button_pressed: bool) -> void:
search()
#endregion
@@ -0,0 +1 @@
uid://cijsmjkq21cdq
@@ -0,0 +1,87 @@
[gd_scene load_steps=2 format=3 uid="uid://gr8nakpbrhby"]
[ext_resource type="Script" uid="uid://cijsmjkq21cdq" path="res://addons/dialogue_manager/components/search_and_replace.gd" id="1_8oj1f"]
[node name="SearchAndReplace" type="VBoxContainer"]
visible = false
anchors_preset = 10
anchor_right = 1.0
offset_bottom = 31.0
grow_horizontal = 2
size_flags_horizontal = 3
script = ExtResource("1_8oj1f")
[node name="Search" type="HBoxContainer" parent="."]
layout_mode = 2
[node name="Input" type="LineEdit" parent="Search"]
layout_mode = 2
size_flags_horizontal = 3
placeholder_text = "Text to search for"
metadata/_edit_use_custom_anchors = true
[node name="MatchCaseCheckBox" type="CheckBox" parent="Search"]
layout_mode = 2
text = "Match case"
[node name="VSeparator" type="VSeparator" parent="Search"]
layout_mode = 2
[node name="PreviousButton" type="Button" parent="Search"]
layout_mode = 2
tooltip_text = "Previous"
flat = true
[node name="ResultLabel" type="Label" parent="Search"]
layout_mode = 2
text = "0 of 0"
[node name="NextButton" type="Button" parent="Search"]
layout_mode = 2
tooltip_text = "Next"
flat = true
[node name="VSeparator2" type="VSeparator" parent="Search"]
layout_mode = 2
[node name="ReplaceCheckButton" type="CheckButton" parent="Search"]
layout_mode = 2
text = "Replace"
[node name="Replace" type="HBoxContainer" parent="."]
visible = false
layout_mode = 2
[node name="ReplaceLabel" type="Label" parent="Replace"]
layout_mode = 2
text = "Replace with:"
[node name="Input" type="LineEdit" parent="Replace"]
layout_mode = 2
size_flags_horizontal = 3
[node name="ReplaceButton" type="Button" parent="Replace"]
layout_mode = 2
disabled = true
text = "Replace"
flat = true
[node name="ReplaceAllButton" type="Button" parent="Replace"]
layout_mode = 2
disabled = true
text = "Replace all"
flat = true
[connection signal="theme_changed" from="." to="." method="_on_search_and_replace_theme_changed"]
[connection signal="visibility_changed" from="." to="." method="_on_search_and_replace_visibility_changed"]
[connection signal="focus_entered" from="Search/Input" to="." method="_on_input_focus_entered"]
[connection signal="gui_input" from="Search/Input" to="." method="_on_input_gui_input"]
[connection signal="text_changed" from="Search/Input" to="." method="_on_input_text_changed"]
[connection signal="toggled" from="Search/MatchCaseCheckBox" to="." method="_on_match_case_check_box_toggled"]
[connection signal="pressed" from="Search/PreviousButton" to="." method="_on_previous_button_pressed"]
[connection signal="pressed" from="Search/NextButton" to="." method="_on_next_button_pressed"]
[connection signal="toggled" from="Search/ReplaceCheckButton" to="." method="_on_replace_check_button_toggled"]
[connection signal="focus_entered" from="Replace/Input" to="." method="_on_input_focus_entered"]
[connection signal="gui_input" from="Replace/Input" to="." method="_on_input_gui_input"]
[connection signal="pressed" from="Replace/ReplaceButton" to="." method="_on_replace_button_pressed"]
[connection signal="pressed" from="Replace/ReplaceAllButton" to="." method="_on_replace_all_button_pressed"]
@@ -0,0 +1,69 @@
@tool
extends VBoxContainer
signal title_selected(title: String)
const DialogueConstants = preload("../constants.gd")
@onready var filter_edit: LineEdit = $FilterEdit
@onready var list: ItemList = $List
var titles: PackedStringArray:
set(next_titles):
titles = next_titles
apply_filter()
get:
return titles
var filter: String:
set(next_filter):
filter = next_filter
apply_filter()
get:
return filter
func _ready() -> void:
apply_theme()
filter_edit.placeholder_text = DialogueConstants.translate(&"titles_list.filter")
func select_title(title: String) -> void:
list.deselect_all()
for i in range(0, list.get_item_count()):
if list.get_item_text(i) == title.strip_edges():
list.select(i)
func apply_filter() -> void:
list.clear()
for title in titles:
if filter == "" or filter.to_lower() in title.to_lower():
list.add_item(title.strip_edges())
func apply_theme() -> void:
if is_instance_valid(filter_edit):
filter_edit.right_icon = get_theme_icon("Search", "EditorIcons")
if is_instance_valid(list):
list.add_theme_stylebox_override("panel", get_theme_stylebox("panel", "Panel"))
### Signals
func _on_theme_changed() -> void:
apply_theme()
func _on_filter_edit_text_changed(new_text: String) -> void:
self.filter = new_text
func _on_list_item_clicked(index: int, at_position: Vector2, mouse_button_index: int) -> void:
if mouse_button_index == MOUSE_BUTTON_LEFT:
var title = list.get_item_text(index)
title_selected.emit(title)
@@ -0,0 +1 @@
uid://d0k2wndjj0ifm
@@ -0,0 +1,27 @@
[gd_scene format=3 uid="uid://ctns6ouwwd68i"]
[ext_resource type="Script" uid="uid://d0k2wndjj0ifm" path="res://addons/dialogue_manager/components/title_list.gd" id="1_5qqmd"]
[node name="TitleList" type="VBoxContainer"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
size_flags_horizontal = 3
size_flags_vertical = 3
script = ExtResource("1_5qqmd")
[node name="FilterEdit" type="LineEdit" parent="."]
layout_mode = 2
placeholder_text = "Filter titles"
clear_button_enabled = true
[node name="List" type="ItemList" parent="."]
layout_mode = 2
size_flags_vertical = 3
allow_reselect = true
[connection signal="theme_changed" from="." to="." method="_on_theme_changed"]
[connection signal="text_changed" from="FilterEdit" to="." method="_on_filter_edit_text_changed"]
[connection signal="item_clicked" from="List" to="." method="_on_list_item_clicked"]
@@ -0,0 +1,125 @@
@tool
extends Button
const DialogueConstants = preload("../constants.gd")
const DialogueSettings = preload("../settings.gd")
const REMOTE_RELEASES_URL = "https://api.github.com/repos/nathanhoad/godot_dialogue_manager/releases"
@onready var http_request: HTTPRequest = $HTTPRequest
@onready var download_dialog: AcceptDialog = $DownloadDialog
@onready var download_update_panel = $DownloadDialog/DownloadUpdatePanel
@onready var needs_reload_dialog: AcceptDialog = $NeedsReloadDialog
@onready var update_failed_dialog: AcceptDialog = $UpdateFailedDialog
@onready var timer: Timer = $Timer
var needs_reload: bool = false
# A lambda that gets called just before refreshing the plugin. Return false to stop the reload.
var on_before_refresh: Callable = func(): return true
func _ready() -> void:
hide()
apply_theme()
# Check for updates on GitHub
check_for_update()
# Check again every few hours
timer.start(60 * 60 * 12)
# Convert a version number to an actually comparable number
func version_to_number(version: String) -> int:
var bits = version.split(".")
return bits[0].to_int() * 1000000 + bits[1].to_int() * 1000 + bits[2].to_int()
func apply_theme() -> void:
var color: Color = get_theme_color("success_color", "Editor")
if needs_reload:
color = get_theme_color("error_color", "Editor")
icon = get_theme_icon("Reload", "EditorIcons")
add_theme_color_override("icon_normal_color", color)
add_theme_color_override("icon_focus_color", color)
add_theme_color_override("icon_hover_color", color)
add_theme_color_override("font_color", color)
add_theme_color_override("font_focus_color", color)
add_theme_color_override("font_hover_color", color)
func check_for_update() -> void:
if DialogueSettings.get_user_value("check_for_updates", true):
http_request.request(REMOTE_RELEASES_URL)
### Signals
func _on_http_request_request_completed(result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray) -> void:
if result != HTTPRequest.RESULT_SUCCESS: return
var current_version: String = DMPlugin.get_version()
# Work out the next version from the releases information on GitHub
var response = JSON.parse_string(body.get_string_from_utf8())
if typeof(response) != TYPE_ARRAY: return
# GitHub releases are in order of creation, not order of version
var versions = (response as Array).filter(func(release):
var version: String = release.tag_name.substr(1)
var major_version: int = version.split(".")[0].to_int()
var current_major_version: int = current_version.split(".")[0].to_int()
return major_version == current_major_version and version_to_number(version) > version_to_number(current_version)
)
if versions.size() > 0:
download_update_panel.next_version_release = versions[0]
text = DialogueConstants.translate(&"update.available").format({ version = versions[0].tag_name.substr(1) })
show()
func _on_update_button_pressed() -> void:
if needs_reload:
var will_refresh = on_before_refresh.call()
if will_refresh:
EditorInterface.restart_editor(true)
else:
var scale: float = EditorInterface.get_editor_scale()
download_dialog.min_size = Vector2(300, 250) * scale
download_dialog.popup_centered()
func _on_download_dialog_close_requested() -> void:
download_dialog.hide()
func _on_download_update_panel_updated(updated_to_version: String) -> void:
download_dialog.hide()
needs_reload_dialog.dialog_text = DialogueConstants.translate(&"update.needs_reload")
needs_reload_dialog.ok_button_text = DialogueConstants.translate(&"update.reload_ok_button")
needs_reload_dialog.cancel_button_text = DialogueConstants.translate(&"update.reload_cancel_button")
needs_reload_dialog.popup_centered()
needs_reload = true
text = DialogueConstants.translate(&"update.reload_project")
apply_theme()
func _on_download_update_panel_failed() -> void:
download_dialog.hide()
update_failed_dialog.dialog_text = DialogueConstants.translate(&"update.failed")
update_failed_dialog.popup_centered()
func _on_needs_reload_dialog_confirmed() -> void:
EditorInterface.restart_editor(true)
func _on_timer_timeout() -> void:
if not needs_reload:
check_for_update()
@@ -0,0 +1 @@
uid://cr1tt12dh5ecr
@@ -0,0 +1,42 @@
[gd_scene load_steps=3 format=3 uid="uid://co8yl23idiwbi"]
[ext_resource type="Script" uid="uid://cr1tt12dh5ecr" path="res://addons/dialogue_manager/components/update_button.gd" id="1_d2tpb"]
[ext_resource type="PackedScene" uid="uid://qdxrxv3c3hxk" path="res://addons/dialogue_manager/components/download_update_panel.tscn" id="2_iwm7r"]
[node name="UpdateButton" type="Button"]
visible = false
offset_right = 8.0
offset_bottom = 8.0
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_colors/font_hover_color = Color(0, 0, 0, 1)
theme_override_colors/font_focus_color = Color(0, 0, 0, 1)
text = "v2.9.0 available"
flat = true
script = ExtResource("1_d2tpb")
[node name="HTTPRequest" type="HTTPRequest" parent="."]
[node name="DownloadDialog" type="AcceptDialog" parent="."]
title = "Download update"
size = Vector2i(400, 300)
unresizable = true
min_size = Vector2i(300, 250)
ok_button_text = "Close"
[node name="DownloadUpdatePanel" parent="DownloadDialog" instance=ExtResource("2_iwm7r")]
[node name="UpdateFailedDialog" type="AcceptDialog" parent="."]
dialog_text = "You have been updated to version 2.4.3"
[node name="NeedsReloadDialog" type="ConfirmationDialog" parent="."]
[node name="Timer" type="Timer" parent="."]
wait_time = 14400.0
[connection signal="pressed" from="." to="." method="_on_update_button_pressed"]
[connection signal="request_completed" from="HTTPRequest" to="." method="_on_http_request_request_completed"]
[connection signal="close_requested" from="DownloadDialog" to="." method="_on_download_dialog_close_requested"]
[connection signal="failed" from="DownloadDialog/DownloadUpdatePanel" to="." method="_on_download_update_panel_failed"]
[connection signal="updated" from="DownloadDialog/DownloadUpdatePanel" to="." method="_on_download_update_panel_updated"]
[connection signal="confirmed" from="NeedsReloadDialog" to="." method="_on_needs_reload_dialog_confirmed"]
[connection signal="timeout" from="Timer" to="." method="_on_timer_timeout"]