Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve animation naming/identifying logic #2293

Merged
merged 5 commits into from
Jun 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions Tests/Unit/guess_special_resource_test.gd
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ extends GdUnitTestSuite
## as space-delimited prefix.
func test_fade_in_animation_paths() -> void:
const TYPE := "PortraitAnimation"
var fade_in_1 := DialogicResourceUtil.guess_special_resource(TYPE, "fade in", "")
var fade_in_2 := DialogicResourceUtil.guess_special_resource(TYPE, "fade in out", "")
var fade_in_3 := DialogicResourceUtil.guess_special_resource(TYPE, "fade out", "")
var fade_in_1: String = DialogicResourceUtil.guess_special_resource(TYPE, "fade in").get('path', "")
var fade_in_2: String = DialogicResourceUtil.guess_special_resource(TYPE, "fade cross").get('path', "")
var fade_in_3: String = DialogicResourceUtil.guess_special_resource(TYPE, "fade out").get('path', "")

var is_any_fade_in_empty := fade_in_1.is_empty() or fade_in_2.is_empty() or fade_in_3.is_empty()
assert(is_any_fade_in_empty == false, "Fade In/Out animations are empty.")
Expand All @@ -18,22 +18,22 @@ func test_fade_in_animation_paths() -> void:
## Test if invalid animation paths will return empty strings.
func test_invalid_animation_path() -> void:
const TYPE := "PortraitAnimation"
var invalid_animation_1 := DialogicResourceUtil.guess_special_resource(TYPE, "fade i", "")
var invalid_animation_1: String = DialogicResourceUtil.guess_special_resource(TYPE, "fade i").get('path', "")
assert(invalid_animation_1.is_empty() == true, "Invalid animation 1's path is not empty.")


var invalid_animation_2 := DialogicResourceUtil.guess_special_resource(TYPE, "fade", "")
var invalid_animation_2: String = DialogicResourceUtil.guess_special_resource(TYPE, "fade").get('path', "")
assert(invalid_animation_2.is_empty() == true, "Invalid animation 2's path is not empty.")


## Test if invalid types will return empty strings.
func test_invalid_type_path() -> void:
const INVALID_TYPE := "Portait Animation"
var invalid_animation := DialogicResourceUtil.guess_special_resource(INVALID_TYPE, "fade in", "")
var invalid_animation: String = DialogicResourceUtil.guess_special_resource(INVALID_TYPE, "fade in").get('path', "")
assert(invalid_animation.is_empty() == true, "Invalid animation 1's path is not empty.")

const VALID_TYPE := "PortraitAnimation"
var valid_animation_path := DialogicResourceUtil.guess_special_resource(VALID_TYPE, "fade in", "")
var valid_animation_path: String = DialogicResourceUtil.guess_special_resource(VALID_TYPE, "fade in").get('path', "")
assert(valid_animation_path.is_empty() == false, "Valids animation's path is empty.")

assert(not invalid_animation == valid_animation_path, "Valid and invalid animation paths are equal.")
Expand Down
94 changes: 63 additions & 31 deletions addons/dialogic/Core/DialogicResourceUtil.gd
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ class_name DialogicResourceUtil
static var label_cache := {}
static var event_cache: Array[DialogicEvent] = []

static var special_resources : Array[Dictionary] = []
static var special_resources := {}


static func update() -> void:
Expand Down Expand Up @@ -173,43 +173,75 @@ static func update_event_cache() -> Array:
################################################################################

static func update_special_resources() -> void:
special_resources = []
special_resources.clear()
for indexer in DialogicUtil.get_indexers():
special_resources.append_array(indexer._get_special_resources())
var additions := indexer._get_special_resources()
for resource_type in additions:
if not resource_type in special_resources:
special_resources[resource_type] = {}
special_resources[resource_type].merge(additions[resource_type])


static func list_special_resources_of_type(type:String) -> Array:
static func list_special_resources(type:String, filter := {}) -> Dictionary:
if special_resources.is_empty():
update_special_resources()
return special_resources.filter(func(x:Dictionary): return type == x.get('type','')).map(func(x:Dictionary): return x.get('path', ''))


static func guess_special_resource(type: String, name: String, default := "") -> String:
if type in special_resources:
if filter.is_empty():
return special_resources[type]
else:
var results := {}
for i in special_resources[type]:
if match_resource_filter(special_resources[type][i], filter):
results[i] = special_resources[type][i]
return results
return {}


static func match_resource_filter(dict:Dictionary, filter:Dictionary) -> bool:
for i in filter:
if not i in dict:
return false
if typeof(filter[i]) == TYPE_ARRAY:
if not dict[i] in filter[i]:
return false
else:
if not dict[i] == filter[i]:
return false
return true


static func guess_special_resource(type: String, string: String, default := {}, filter := {}, ignores:PackedStringArray=[]) -> Dictionary:
if special_resources.is_empty():
update_special_resources()

if name.begins_with('res://'):
return name

for path: String in list_special_resources_of_type(type):
var pretty_path := DialogicUtil.pretty_name(path).to_lower()
var pretty_name := name.to_lower()

if pretty_path == pretty_name:
return path

elif pretty_name.ends_with(" in"):
pretty_name = pretty_name + " out"

if pretty_path == pretty_name:
return path

elif pretty_name.ends_with(" out"):
pretty_name = pretty_name.replace("out", "in out")

if pretty_path == pretty_name:
return path

var resources := list_special_resources(type, filter)
if resources.is_empty():
printerr("[Dialogic] No ", type, "s found, but attempted to use one.")
return default

string = string.to_lower()

if string.begins_with('res://'):
for i in resources.values():
if i.path == string:
return i
printerr("[Dialogic] Unable to find ", type, " at path '", string, "'.")
return default

if string in resources:
return resources[string]

if not ignores.is_empty():
var regex := RegEx.create_from_string(r" ?\b(" + "|".join(ignores) + r")\b")
for name in resources:
if regex.sub(name, "") == string:
return resources[name]

## As a last effort check against the unfiltered list
if string in special_resources[type]:
push_warning("[Dialogic] Using ", type, " '", string,"' when not supposed to.")
return special_resources[type][string]

printerr("[Dialogic] Unable to identify ", type, " based on string '", string, "'.")
return default

#endregion
Expand Down
47 changes: 0 additions & 47 deletions addons/dialogic/Core/DialogicUtil.gd
Original file line number Diff line number Diff line change
Expand Up @@ -111,39 +111,6 @@ static func get_indexers(include_custom := true, force_reload := false) -> Array
return indexers


enum AnimationType {ALL, IN, OUT, ACTION}



static func get_portrait_animation_scripts(type := AnimationType.ALL) -> Array:
var animations := DialogicResourceUtil.list_special_resources_of_type("PortraitAnimation")
const CROSS_ANIMATION := "_in_out"
const OUT_ANIMATION := "_out"
const IN_ANIMATION := "_in"

return animations.filter(
func(script: String) -> bool:
match (type):
AnimationType.ALL:
return true

AnimationType.IN:
return IN_ANIMATION in script or CROSS_ANIMATION in script

AnimationType.OUT:
return OUT_ANIMATION in script or CROSS_ANIMATION in script

# All animations that are not IN or OUT.
# Extra check for CROSS animations to prevent parsing parts
# of the name as an IN or OUT animation.
AnimationType.ACTION:
#return CROSS_ANIMATION in script or not (IN_ANIMATION in script or OUT_ANIMATION in script)
return not IN_ANIMATION in script and not OUT_ANIMATION in script

_:
return false
)


## Turns a [param file_path] from `some_file.png` to `Some File`.
static func pretty_name(file_path: String) -> String:
Expand All @@ -153,7 +120,6 @@ static func pretty_name(file_path: String) -> String:

return _name


#endregion


Expand Down Expand Up @@ -633,16 +599,3 @@ static func get_portrait_position_suggestions(search_text := "") -> Dictionary:

return suggestions


static func get_portrait_animation_suggestions(_search_text := "", empty_text := "Default", action := AnimationType.ALL) -> Dictionary:
var suggestions := {}

suggestions[empty_text] = {'value':"", 'editor_icon':["GuiRadioUnchecked", "EditorIcons"]}

for anim in DialogicUtil.get_portrait_animation_scripts(action):
suggestions[DialogicUtil.pretty_name(anim)] = {
'value' : DialogicUtil.pretty_name(anim),
'editor_icon' : ["Animation", "EditorIcons"]
}

return suggestions
26 changes: 20 additions & 6 deletions addons/dialogic/Core/index_class.gd
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ func _get_text_modifiers() -> Array[Dictionary]:
## These can later be retrieved with DialogicResourceUtil.
## Each dictionary should contain (at least "type" and "path").
## E.g. {"type":"Animation", "path": "res://..."}
func _get_special_resources() -> Array[Dictionary]:
return []
func _get_special_resources() -> Dictionary:
return {}


#region HELPERS
Expand All @@ -70,12 +70,26 @@ func list_dir(subdir:='') -> Array:
return Array(DirAccess.get_files_at(this_folder.path_join(subdir))).map(func(file):return this_folder.path_join(subdir).path_join(file))


func list_special_resources(subdir:='', type:='', extension:="") -> Array[Dictionary]:
var array := []
func list_special_resources(subdir:='', extension:="") -> Dictionary:
var dict := {}
for i in list_dir(subdir):
if extension.is_empty() or i.ends_with(extension):
array.append({'type':type, 'path':i})
return Array(array, TYPE_DICTIONARY, "", null)
dict[DialogicUtil.pretty_name(i).to_lower()] = {"path":i}
return dict


func list_animations(subdir := "") -> Dictionary:
var full_animation_list := {}
for path in list_dir(subdir):
if not path.ends_with(".gd"):
continue
var anim_object: DialogicAnimation = load(path).new()
var versions := anim_object._get_named_variations()
for version_name in versions:
full_animation_list[version_name] = versions[version_name]
full_animation_list[version_name]["path"] = path
anim_object.queue_free()
return full_animation_list

#endregion

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ var completion_word_regex := RegEx.new()
var completion_shortcode_getter_regex := RegEx.new()
# To find the parameter name of the current if typing a value
var completion_shortcode_param_getter_regex := RegEx.new()
# To find the value of a paramater that is being typed
var completion_shortcode_value_regex := RegEx.new()

# Stores references to all shortcode events for parameter and value suggestions
var shortcode_events := {}
Expand All @@ -26,6 +28,7 @@ func _ready() -> void:
completion_word_regex.compile("(?<s>(\\W)|^)(?<word>\\w*)\\x{FFFF}")
completion_shortcode_getter_regex.compile("\\[(?<code>\\w*)")
completion_shortcode_param_getter_regex.compile("(?<param>\\w*)\\W*=\\s*\"?(\\w|\\s)*"+String.chr(0xFFFF))
completion_shortcode_value_regex.compile(r'(\[|\s)[^\[\s=]*="(?<value>[^"$]*)'+String.chr(0xFFFF))

text_syntax_highlighter.mode = text_syntax_highlighter.Modes.TEXT_EVENT_ONLY

Expand All @@ -42,6 +45,11 @@ func get_code_completion_word(text:CodeEdit) -> String:
var result := completion_word_regex.search(get_code_completion_line(text))
return result.get_string('word') if result else ""

# Helper that gets the currently typed parameter
func get_code_completion_parameter_value(text:CodeEdit) -> String:
var result := completion_shortcode_value_regex.search(get_code_completion_line(text))
return result.get_string('value') if result else ""


# Helper that gets the symbol before the current word
func get_code_completion_prev_symbol(text:CodeEdit) -> String:
Expand Down Expand Up @@ -98,8 +106,8 @@ func request_code_completion(force:bool, text:CodeEdit, mode:=Modes.FULL_HIGHLIG
# word in option

## Note on VALUE key
# The value key is used to store a potential closing letter for the completion.
# The completion will check if the letter is already present and add it otherwise.
# The value key is used to store a potential closing string for the completion.
# The completion will check if the string is already present and add it otherwise.

# Shortcode event suggestions
if mode == Modes.FULL_HIGHLIGHTING and syntax_highlighter.line_is_shortcode_event(text.get_caret_line()):
Expand Down Expand Up @@ -134,7 +142,7 @@ func request_code_completion(force:bool, text:CodeEdit, mode:=Modes.FULL_HIGHLIG
text.add_code_completion_option(CodeEdit.KIND_MEMBER, param, param+'="' , shortcode_events[code].event_color.lerp(syntax_highlighter.normal_color, 0.3), text.get_theme_icon("MemberProperty", "EditorIcons"))

# suggest values
elif symbol == '=' or symbol == '"' or get_code_completion_prev_symbol(text) == '"':
elif symbol == '=' or symbol == '"':
var current_parameter_gex := completion_shortcode_param_getter_regex.search(line)
if !current_parameter_gex:
text.update_code_completion_options(false)
Expand All @@ -148,7 +156,7 @@ func request_code_completion(force:bool, text:CodeEdit, mode:=Modes.FULL_HIGHLIG
if typeof(shortcode_events[code].get_shortcode_parameters()[current_parameter].default) == TYPE_BOOL:
suggest_bool(text, shortcode_events[code].event_color.lerp(syntax_highlighter.normal_color, 0.3))
elif len(word) > 0:
text.add_code_completion_option(CodeEdit.KIND_MEMBER, word, word, shortcode_events[code].event_color.lerp(syntax_highlighter.normal_color, 0.3), text.get_theme_icon("GuiScrollArrowRight", "EditorIcons"), '" ')
text.add_code_completion_option(CodeEdit.KIND_VARIABLE, word, word, shortcode_events[code].event_color.lerp(syntax_highlighter.normal_color, 0.3), text.get_theme_icon("GuiScrollArrowRight", "EditorIcons"), '" ')
text.update_code_completion_options(true)
return

Expand Down Expand Up @@ -218,16 +226,16 @@ func suggest_variables(text:CodeEdit):

# Helper that adds true and false as options
func suggest_bool(text:CodeEdit, color:Color):
text.add_code_completion_option(CodeEdit.KIND_MEMBER, 'true', 'true', color, text.get_theme_icon("GuiChecked", "EditorIcons"), '" ')
text.add_code_completion_option(CodeEdit.KIND_MEMBER, 'false', 'false', color, text.get_theme_icon("GuiUnchecked", "EditorIcons"), '" ')
text.add_code_completion_option(CodeEdit.KIND_VARIABLE, 'true', 'true', color, text.get_theme_icon("GuiChecked", "EditorIcons"), '" ')
text.add_code_completion_option(CodeEdit.KIND_VARIABLE, 'false', 'false', color, text.get_theme_icon("GuiUnchecked", "EditorIcons"), '" ')


func suggest_custom_suggestions(suggestions:Dictionary, text:CodeEdit, color:Color) -> void:
for key in suggestions.keys():
if suggestions[key].has('text_alt'):
text.add_code_completion_option(CodeEdit.KIND_MEMBER, key, suggestions[key].text_alt[0], color, suggestions[key].get('icon', null), '" ')
text.add_code_completion_option(CodeEdit.KIND_VARIABLE, key, suggestions[key].text_alt[0], color, suggestions[key].get('icon', null), '" ')
else:
text.add_code_completion_option(CodeEdit.KIND_MEMBER, key, str(suggestions[key].value), color, suggestions[key].get('icon', null), '" ')
text.add_code_completion_option(CodeEdit.KIND_VARIABLE, key, str(suggestions[key].value), color, suggestions[key].get('icon', null), '" ')


# Filters the list of all possible options, depending on what was typed
Expand All @@ -242,6 +250,10 @@ func filter_code_completion_candidates(candidates:Array, text:CodeEdit) -> Array
elif candidate.kind == text.KIND_MEMBER:
if current_word.is_empty() or current_word.to_lower() in candidate.insert_text.to_lower():
valid_candidates.append(candidate)
elif candidate.kind == text.KIND_VARIABLE:
var current_param_value := get_code_completion_parameter_value(text)
if current_param_value.is_empty() or current_param_value.to_lower() in candidate.insert_text.to_lower():
valid_candidates.append(candidate)
elif candidate.kind == text.KIND_CONSTANT:
if current_word.is_empty() or candidate.insert_text.begins_with(current_word):
valid_candidates.append(candidate)
Expand All @@ -255,17 +267,23 @@ func filter_code_completion_candidates(candidates:Array, text:CodeEdit) -> Array
# Inserts the selected item
func confirm_code_completion(replace:bool, text:CodeEdit) -> void:
# Note: I decided to ALWAYS use replace mode, as dialogic is supposed to be beginner friendly
var word := get_code_completion_word(text)

var code_completion := text.get_code_completion_option(text.get_code_completion_selected_index())

var word := get_code_completion_word(text)
if code_completion.kind == CodeEdit.KIND_VARIABLE:
word = get_code_completion_parameter_value(text)

text.remove_text(text.get_caret_line(), text.get_caret_column()-len(word), text.get_caret_line(), text.get_caret_column())
text.set_caret_column(text.get_caret_column()-len(word))
text.insert_text_at_caret(code_completion.insert_text)#
text.insert_text_at_caret(code_completion.insert_text)

if code_completion.has('default_value') and typeof(code_completion['default_value']) == TYPE_STRING:
var next_letter := text.get_line(text.get_caret_line()).substr(text.get_caret_column(), 1)
if next_letter != code_completion['default_value']:
text.insert_text_at_caret(code_completion['default_value'])
else:
var next_letter := text.get_line(text.get_caret_line()).substr(text.get_caret_column(), len(code_completion['default_value']))
if next_letter == code_completion['default_value'] or next_letter[0] == code_completion['default_value'][0]:
text.set_caret_column(text.get_caret_column()+1)
else:
text.insert_text_at_caret(code_completion['default_value'])


#endregion
Expand Down
2 changes: 1 addition & 1 deletion addons/dialogic/Modules/Background/event_background.gd
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ func build_event_editor() -> void:


func get_transition_suggestions(_filter:String="") -> Dictionary:
var transitions := DialogicResourceUtil.list_special_resources_of_type("BackgroundTransition")
var transitions := DialogicResourceUtil.list_special_resources("BackgroundTransition")
var suggestions := {}
for i in transitions:
suggestions[DialogicUtil.pretty_name(i)] = {'value': DialogicUtil.pretty_name(i), 'editor_icon': ["PopupMenu", "EditorIcons"]}
Expand Down
4 changes: 2 additions & 2 deletions addons/dialogic/Modules/Background/index.gd
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ func _get_subsystems() -> Array:
return [{'name':'Backgrounds', 'script':this_folder.path_join('subsystem_backgrounds.gd')}]


func _get_special_resources() -> Array[Dictionary]:
return list_special_resources("Transitions/Defaults", "BackgroundTransition", ".gd")
func _get_special_resources() -> Dictionary:
return {&"BackgroundTransition":list_special_resources("Transitions/Defaults", ".gd")}
Loading
Loading