built more assets and started playing with foliage painting

This commit is contained in:
derek
2024-12-04 17:02:46 -06:00
parent dd960cc00e
commit 478e2822d2
359 changed files with 34172 additions and 178 deletions

View File

@@ -0,0 +1,706 @@
@tool
extends Resource
#-------------------------------------------------------------------------------
# A base class for resources bound with InputFields and suporting UndoRedo
# All properties are suppposed to be set using PropAction
# That helps to easily update UI and do/undo actions in editor
# There's also a bit of property management sprinkled on top (conditional display, modified values, etc.)
#
# TODO: reduce amount of abstractions and indirections.
# overhead for function calls and container usage is the most demanding part of this thing
#-------------------------------------------------------------------------------
enum PropActionLifecycle {BEFORE_DO, AFTER_DO, AFTER_UNDO}
const Logger = preload("../logger.gd")
const FunLib = preload("../fun_lib.gd")
const PropAction = preload("prop_action.gd")
const PA_PropSet = preload("pa_prop_set.gd")
const PA_PropEdit = preload("pa_prop_edit.gd")
const PA_ArrayInsert = preload("pa_array_insert.gd")
const PA_ArrayRemove = preload("pa_array_remove.gd")
const PA_ArraySet = preload("pa_array_set.gd")
const UI_ActionThumbnail_GD = preload("../../controls/input_fields/action_thumbnail/ui_action_thumbnail.gd")
const UI_InputField = preload("../../controls/input_fields/ui_input_field.gd")
const UI_IF_Bool = preload("../../controls/input_fields/ui_if_bool.gd")
const UI_IF_Enum = preload("../../controls/input_fields/ui_if_enum.gd")
const UI_IF_MultiRange = preload("../../controls/input_fields/ui_if_multi_range.gd")
const UI_IF_RealSlider = preload("../../controls/input_fields/ui_if_real_slider.gd")
const UI_IF_IntLineEdit = preload("../../controls/input_fields/ui_if_int_line_edit.gd")
const UI_IF_ThumbnailArray = preload("../../controls/input_fields/ui_if_thumbnail_array.gd")
const UI_IF_ApplyChanges = preload("../../controls/input_fields/ui_if_apply_changes.gd")
const UI_IF_Button = preload("../../controls/input_fields/ui_if_button.gd")
const UI_IF_PlainText = preload("../../controls/input_fields/ui_if_plain_text.gd")
const UI_IF_Object = preload("../../controls/input_fields/ui_if_object.gd")
const UI_IF_ThumbnailObject = preload("../../controls/input_fields/ui_if_thumbnail_object.gd")
const UndoRedoInterface = preload("../../utility/undo_redo_interface.gd")
var _undo_redo = null : set = set_undo_redo
# Backups that can be restored when using non-destructive PA_PropEdit
var prop_edit_backups:Dictionary = {}
# Properties added here will be ignored when creating input fields
# NOTE: this is meant to exclude properties from generating an input field AT ALL
# it's NOT a conditional check to show/hide fields
# it will be used once when generating a UI layout, but not to modify it
# NOTE: for conditional checks see 'visibility_tracked_properties' in ui_input_filed.gd
# to hide properties from editor's inspector see _get_prop_dictionary()
var input_field_blacklist:Array = []
# All properties that are linked together for showing an element of an Array
var res_edit_data:Array = []
# All properties that are affected by other properties
var prop_dependency_data:Array = []
var logger = null
signal prop_action_executed(prop_action, final_val)
signal req_change_interaction_feature(prop, index, feature, val)
signal prop_list_changed(prop_names)
#-------------------------------------------------------------------------------
# Initialization
#-------------------------------------------------------------------------------
func _init():
set_meta("class", "InputFieldResource")
resource_name = "InputFieldResource"
logger = Logger.get_for(self)
FunLib.ensure_signal(self.prop_action_executed, _on_prop_action_executed)
func set_undo_redo(val):
_undo_redo = val
# This doesn't account for resources inside nested Arrays/Dictionaries (i.e. [[Resource:1, Resource:2], [Resource:3]])
func duplicate_ifr(subresources:bool = false, ifr_subresources:bool = false) -> Resource:
var copy = super.duplicate(false)
if subresources || ifr_subresources:
var property_list = copy.get_property_list()
for prop_dict in property_list:
var prop = prop_dict.name
var prop_val = copy.get(prop)
if prop_val is Array || prop_val is Dictionary:
prop_val = prop_val.duplicate(true)
copy._set(prop, prop_val)
if prop_val is Array:
for i in range(0, prop_val.size()):
var element = prop_val[i]
if is_instance_of(element, Resource):
if element.has_method("duplicate_ifr") && ifr_subresources:
prop_val[i] = element.duplicate_ifr(subresources, ifr_subresources)
elif subresources:
prop_val[i] = element.duplicate(subresources)
elif prop_val is Dictionary:
for key in prop_val.keys():
var element = prop_val[key]
if is_instance_of(element, Resource):
if element.has_method("duplicate_ifr") && ifr_subresources:
prop_val[key] = element.duplicate_ifr(subresources, ifr_subresources)
elif subresources:
prop_val[key] = element.duplicate(subresources)
# Script check makes sure we don't try to duplicate Script properties
# This... shouldn't be happening normally
# TODO the whole InputFieldResource is kind of a mess, would be great if we could fit that into existing inspector workflow
elif is_instance_of(prop_val, Resource) && !is_instance_of(prop_val, Script):
if prop_val.has_method("duplicate_ifr") && ifr_subresources:
prop_val = prop_val.duplicate_ifr(subresources, ifr_subresources)
elif subresources:
prop_val = prop_val.duplicate(subresources)
copy._set(prop, prop_val)
return copy
func duplicate(subresources:bool = false):
var copy = duplicate_ifr(subresources, true)
_fix_duplicate_signals(copy)
return copy
# Convert Input Field Resource to a dictionary
func ifr_to_dict(ifr_subresources:bool = false):
var dict = {}
for prop_dict in _get_property_list():
if find_res_edit_by_res_prop(prop_dict.name):
continue
var prop_val = _get(prop_dict.name)
if prop_val is Array || prop_val is Dictionary:
prop_val = prop_val.duplicate(true)
if prop_val is Array:
for i in range(0, prop_val.size()):
var element = prop_val[i]
prop_val[i] = _ifr_val_to_dict_compatible(element, ifr_subresources)
elif prop_val is Dictionary:
for key in prop_val.keys():
var element = prop_val[key]
prop_val[key] = _ifr_val_to_dict_compatible(element, ifr_subresources)
else:
prop_val = _ifr_val_to_dict_compatible(prop_val, ifr_subresources)
dict[prop_dict.name] = prop_val
return dict
# Convert Input Field Resource value from native to dictionary-compatible (and independent of native var_to_str)
func _ifr_val_to_dict_compatible(val, ifr_subresources):
if ifr_subresources && is_instance_of(val, Resource) && !is_instance_of(val, Script):
if val.has_method("ifr_to_dict") && ifr_subresources:
val = val.ifr_to_dict(ifr_subresources)
else:
val = val.resource_path
elif typeof(val) == TYPE_VECTOR3:
val = FunLib.vec3_to_str(val)
elif typeof(val) == TYPE_TRANSFORM3D:
val = FunLib.transform3d_to_str(val)
return val
# Convert dictionary to an Input Field Resource
func ifr_from_dict(dict: Dictionary, ifr_subresources:bool = false, str_version: int = 1) -> Resource:
for prop_dict in _get_property_list():
if find_res_edit_by_res_prop(prop_dict.name):
continue
var prop_val = dict.get(prop_dict.name, null)
var existing_prop_val = _get(prop_dict.name)
if prop_val is Array:
existing_prop_val.resize(prop_val.size())
# Trigger automatic creation of default Resource
_set(prop_dict.name, existing_prop_val)
for i in range(0, prop_val.size()):
var element = existing_prop_val[i]
prop_val[i] = _dict_compatible_to_ifr_val(element, prop_val[i], ifr_subresources, str_version)
elif prop_val is Dictionary:
prop_val = _dict_compatible_to_ifr_val(existing_prop_val, prop_val, ifr_subresources, str_version)
if prop_val is Dictionary:
for key in prop_val.keys():
existing_prop_val[key] = prop_val.get(key, null)
# Trigger automatic creation of default Resource
_set(prop_dict.name, existing_prop_val)
for key in prop_val.keys():
var element = existing_prop_val[key]
prop_val[key] = _dict_compatible_to_ifr_val(element, prop_val[key], ifr_subresources, str_version)
else:
prop_val = _dict_compatible_to_ifr_val(existing_prop_val, prop_val, ifr_subresources, str_version)
_set(prop_dict.name, prop_val)
return self
# Convert dictionary-compatible (and independent of native var_to_str) value to an Input Field Resource value
func _dict_compatible_to_ifr_val(template_val, val, ifr_subresources, str_version):
if ifr_subresources && is_instance_of(template_val, Resource) && !is_instance_of(template_val, Script):
if template_val.has_method("ifr_from_dict") && ifr_subresources:
val = template_val.ifr_from_dict(val, ifr_subresources, str_version)
elif val is String && ResourceLoader.exists(val):
val = ResourceLoader.load(val)
elif typeof(template_val) == TYPE_VECTOR3:
val = FunLib.str_to_vec3(val, str_version)
elif typeof(template_val) == TYPE_TRANSFORM3D:
val = FunLib.str_to_transform3d(val, str_version)
return val
# It turns out, duplicating subresources implies we need to reconnect them to any *other* duplicated resources
# e.g. brushes to the toolshed (Obvious in retrospective, I know)
# Ideally they would reconnect automatically, and possibly that's what Godot's native duplicate() does (but I haven't checked)
# For now we will fix this by hand for any resource that inherits from InputFieldResource
# TODO explore if Godot handles subresource signal reconnection. If yes - try to utilize the native code. If not - write my own
func _fix_duplicate_signals(copy):
pass
#-------------------------------------------------------------------------------
# Handling property actions
#-------------------------------------------------------------------------------
# A wrapper with a better name
func request_prop_action(prop_action:PropAction):
on_prop_action_requested(prop_action)
# A callback for any requests to change the properties
func on_prop_action_requested(prop_action:PropAction):
debug_print_prop_action("Requested prop action: %s..." % [str(prop_action)])
if _undo_redo && _can_prop_action_create_history(prop_action):
var prop_action_class = prop_action.get_meta("class")
UndoRedoInterface.create_action(_undo_redo, "%s: on '%s'" % [prop_action_class, prop_action.prop], 0, false, self)
_prop_action_request_lifecycle(prop_action, PropActionLifecycle.BEFORE_DO)
UndoRedoInterface.add_do_method(_undo_redo, self._perform_prop_action.bind(prop_action))
_prop_action_request_lifecycle(prop_action, PropActionLifecycle.AFTER_DO)
UndoRedoInterface.add_undo_method(_undo_redo, self._perform_prop_action.bind(_get_opposite_prop_action(prop_action)))
_prop_action_request_lifecycle(prop_action, PropActionLifecycle.AFTER_UNDO)
UndoRedoInterface.commit_action(_undo_redo, true)
# But we don't *have* to use UndoRedo system
else:
_prop_action_request_lifecycle(prop_action, PropActionLifecycle.BEFORE_DO)
_perform_prop_action(prop_action)
_prop_action_request_lifecycle(prop_action, PropActionLifecycle.AFTER_DO)
# A wrapper for prop_action_request_lifecycle() with default logic
func _prop_action_request_lifecycle(prop_action:PropAction, lifecycle_stage:int):
_handle_res_edit_prop_action_lifecycle(prop_action, lifecycle_stage)
_handle_dependency_prop_action_lifecycle(prop_action, lifecycle_stage)
prop_action_request_lifecycle(prop_action, lifecycle_stage)
# Custom logic after a PropAction was requested/done/undone
# To be overridden
func prop_action_request_lifecycle(prop_action:PropAction, lifecycle_stage:int):
pass
# Can a given prop action create UndoRedo history?
# Most of the time we need this is when using a UI slider
# To avoid commiting dozens of history actions while dragging
func _can_prop_action_create_history(prop_action:PropAction):
var enable_undo_redo = FunLib.get_setting_safe("dreadpons_spatial_gardener/input_and_ui/greenhouse_ui_enable_undo_redo", true)
return prop_action.can_create_history && enable_undo_redo
# Performs the prop action
func _perform_prop_action(prop_action:PropAction):
var prop_action_class = prop_action.get_meta("class")
var current_val_copy = _get_current_val_copy(prop_action.prop)
debug_print_prop_action("Performing prop action: %s..." % [str(prop_action)])
# 'prop_action.val = get(prop_action.prop)' and it's variations
# Account for _modify_prop() modifying the property
# E.g. an array replacing null elements with actual instances
# This does not apply to PA_ArrayRemove since we assume a removed element will not be changed
match prop_action_class:
"PA_PropSet":
_erase_prop_edit_backup(prop_action.prop)
_set(prop_action.prop, prop_action.val)
prop_action.val = get(prop_action.prop)
"PA_PropEdit":
_make_prop_edit_backup(prop_action.prop)
_set(prop_action.prop, prop_action.val)
prop_action.val = get(prop_action.prop)
"PA_ArrayInsert":
current_val_copy.insert(prop_action.index, prop_action.val)
_set(prop_action.prop, current_val_copy)
prop_action.val = get(prop_action.prop)[prop_action.index]
"PA_ArrayRemove":
prop_action.val = current_val_copy[prop_action.index]
current_val_copy.remove_at(prop_action.index)
_set(prop_action.prop, current_val_copy)
"PA_ArraySet":
current_val_copy[prop_action.index] = prop_action.val
_set(prop_action.prop, current_val_copy)
prop_action.val = get(prop_action.prop)[prop_action.index]
_:
logger.error("Error: PropAction class \"%s\" is not accounted for" % [prop_action_class])
return
res_edit_update_interaction_features(prop_action.prop)
prop_action_executed.emit(prop_action, get(prop_action.prop))
# Reverses the prop action (used for undo actions)
func _get_opposite_prop_action(prop_action:PropAction) -> PropAction:
var prop_action_class = prop_action.get_meta("class")
var current_val_copy = _get_current_val_copy(prop_action.prop)
match prop_action_class:
"PA_PropSet":
return PA_PropSet.new(prop_action.prop, current_val_copy)
"PA_PropEdit":
return PA_PropEdit.new(prop_action.prop, current_val_copy)
"PA_ArrayInsert":
return PA_ArrayRemove.new(prop_action.prop, null, prop_action.index)
"PA_ArrayRemove":
return PA_ArrayInsert.new(prop_action.prop, current_val_copy[prop_action.index], prop_action.index)
"PA_ArraySet":
return PA_ArraySet.new(prop_action.prop, current_val_copy[prop_action.index], prop_action.index)
_:
logger.error("Error: PropAction class \"%s\" is not accounted for" % [prop_action_class])
return null
# Backup a current property before a PA_PropEdit
# Since PA_PropEdit is non-destructive to UndoRedo history, we need a separate PA_PropSet to make do/undo actions
# This backup is used to cache the initial property value and retrieve it when setting an undo action
func _make_prop_edit_backup(prop:String):
if prop_edit_backups.has(prop): return
prop_edit_backups[prop] = _get_current_val_copy(prop)
# Cleanup the backup
func _erase_prop_edit_backup(prop:String):
prop_edit_backups.erase(prop)
# Get the copy of CURRENT state of the value
# Does not copy objects because of possible abiguity of intention
func _get_current_val_copy(prop:String):
var copy
if prop_edit_backups.has(prop):
copy = prop_edit_backups[prop]
else:
copy = get(prop)
if copy is Array || copy is Dictionary:
copy = copy.duplicate()
return copy
# A wrapper for on_prop_action_executed() with default logic
func _on_prop_action_executed(prop_action:PropAction, final_val):
on_prop_action_executed(prop_action, final_val)
# A built-in callback for when a PropAction was executed
# To be overridden
func on_prop_action_executed(prop_action:PropAction, final_val):
pass
#-------------------------------------------------------------------------------
# Property export
#-------------------------------------------------------------------------------
# Modify a property
# Mostly used to initialize a newly added array/dictionary value when setting array size from Engine Inspector
# To be overridden and (usually) called inside a _set()
func _modify_prop(prop:String, val):
return val
# Map property info to a dictionary for convinience
# To be overridden and (usually) called inside a _get_property_list()
func _get_prop_dictionary() -> Dictionary:
return {}
# Get property data from a dictionary and filter it
# Allows easier management of hidden/shown properties based on arbitrary conditions in a subclass
# To be overridden and (usually) called inside a _get_property_list()
# With a dictionary created by _get_prop_dictionary()
# Return the same prop_dict passed to it (for convenience in function calls)
func _filter_prop_dictionary(prop_dict: Dictionary) -> Dictionary:
return prop_dict
func _set(property, val):
pass
func _get(property):
pass
# Default functionality for _get_property_list():
# Get all {prop_name: prop_data_dictionary} defined by _get_prop_dictionary()
# Filter them (optionally rejecting some of them based on arbitrary conditions)
# Return a prop_dict values array
func _get_property_list():
var prop_dict = _get_prop_dictionary()
_filter_prop_dictionary(prop_dict)
return prop_dict.values()
# A wrapper around built-in notify_property_list_changed()
# To support a custom signal we can bind manually
func _emit_property_list_changed_notify():
notify_property_list_changed()
prop_list_changed.emit(_filter_prop_dictionary(_get_prop_dictionary()))
#-------------------------------------------------------------------------------
# UI Management
#-------------------------------------------------------------------------------
# Create all the UI input fields
# input_field_blacklist is responsible for excluding certain props
# Optionally specify a whitelist to use instead of an object-wide blacklist
# They both allow to conditionally hide/show input fields
func create_input_fields(_base_control:Control, _resource_previewer, whitelist:Array = []) -> Dictionary:
# print("create_input_fields %s %s %d start" % [str(self), get_meta("class"), Time.get_ticks_msec()])
var prop_names = _get_prop_dictionary().keys()
var input_fields := {}
for prop in prop_names:
# Conditional rejection of a property
if whitelist.is_empty():
if input_field_blacklist.has(prop): continue
else:
if !whitelist.has(prop): continue
var input_field:UI_InputField = create_input_field(_base_control, _resource_previewer, prop)
if input_field:
input_fields[prop] = input_field
return input_fields
func create_input_field(_base_control:Control, _resource_previewer, prop:String) -> UI_InputField:
var input_field = _create_input_field(_base_control, _resource_previewer, prop)
if input_field:
input_field.name = prop
input_field.set_tooltip(get_prop_tooltip(prop))
input_field.on_prop_list_changed(_filter_prop_dictionary(_get_prop_dictionary()))
input_field.prop_action_requested.connect(request_prop_action)
prop_action_executed.connect(input_field.on_prop_action_executed)
prop_list_changed.connect(input_field.on_prop_list_changed)
input_field.tree_entered.connect(on_if_tree_entered.bind(input_field))
if is_instance_of(input_field, UI_IF_ThumbnailArray):
input_field.requested_press.connect(on_if_thumbnail_array_press.bind(input_field))
req_change_interaction_feature.connect(input_field.on_changed_interaction_feature)
# NOTE: below is a leftover abstraction from an attempt to create ui nodes only once and reuse them
# but it introduced to many unknowns to be viable as a part of Godot 3.5 -> Godot 4.0 transition
# yet it stays, as a layer of abstraction
# TODO: implement proper reuse of ui nodes
# or otherwise speed up their creation
input_field.prepare_input_field(_get(prop), _base_control, _resource_previewer)
return input_field
# Creates a specified input field
# To be overridden
func _create_input_field(_base_control:Control, _resource_previewer, prop:String) -> UI_InputField:
return null
# Do something with an input field when it's _ready()
func on_if_tree_entered(input_field:UI_InputField):
var res_edit = find_res_edit_by_array_prop(input_field.prop_name)
if res_edit:
var res_val = get(res_edit.res_prop)
# We assume that input field that displays the resource is initialized during infput field creation
# And hense only update the array interaction features
res_edit_update_interaction_features(res_edit.res_prop)
# An array thumbnail representing a resource was pressed
func on_if_thumbnail_array_press(pressed_index:int, input_field:Control):
var res_edit = find_res_edit_by_array_prop(input_field.prop_name)
if res_edit:
var array_val = get(res_edit.array_prop)
var new_res_val = array_val[pressed_index]
_res_edit_select(res_edit.array_prop, [new_res_val], true)
# Get a tooltip string for each property to be used in it's InputField
func get_prop_tooltip(prop:String) -> String:
return ""
#-------------------------------------------------------------------------------
# Prop dependency
#-------------------------------------------------------------------------------
# Register a property dependency (where any of the controlling_props might change the dependent_prop)
# This is needed for correct UndoRedo functionality
func _add_prop_dependency(dependent_prop:String, controlling_props:Array):
prop_dependency_data.append({"dependent_prop": dependent_prop, "controlling_props": controlling_props})
# React to lifecycle stages for properties that are affected by other properties
func _handle_dependency_prop_action_lifecycle(prop_action:PropAction, lifecycle_stage:int):
var prop_action_class = prop_action.get_meta("class")
var dependency = find_dependency_by_controlling_prop(prop_action.prop)
if dependency && prop_action_class == "PA_PropSet":
var new_prop_action = PA_PropSet.new(dependency.dependent_prop, get(dependency.dependent_prop))
if _undo_redo && _can_prop_action_create_history(new_prop_action):
if lifecycle_stage == PropActionLifecycle.AFTER_DO:
UndoRedoInterface.add_do_method(_undo_redo, self._perform_prop_action.bind(new_prop_action))
elif lifecycle_stage == PropActionLifecycle.AFTER_UNDO:
UndoRedoInterface.add_undo_method(_undo_redo, self._perform_prop_action.bind(_get_opposite_prop_action(new_prop_action)))
else:
if lifecycle_stage == PropActionLifecycle.AFTER_DO:
_perform_prop_action(new_prop_action)
#-------------------------------------------------------------------------------
# Res edit
#-------------------------------------------------------------------------------
# Register a property array with resources that can be individually shown for property editing
# Since new ones are added as 'null' and initialized in _modify_prop(), so they WILL NOT be equal to cached ones in UndoRedo actions
func _add_res_edit_source_array(array_prop:String, res_prop:String):
res_edit_data.append({"array_prop": array_prop, "res_prop": res_prop})
# React to lifecycle stages for actions executed on res_edit_data members
func _handle_res_edit_prop_action_lifecycle(prop_action:PropAction, lifecycle_stage:int):
var prop_action_class = prop_action.get_meta("class")
var res_edit = find_res_edit_by_array_prop(prop_action.prop)
if res_edit:
var array_prop = res_edit.array_prop
var array_val = get(array_prop)
var res_val = get(res_edit.res_prop)
var current_index = array_val.find(res_val)
match prop_action_class:
"PA_ArrayRemove":
if current_index == prop_action.index:
if _undo_redo && _can_prop_action_create_history(prop_action):
if lifecycle_stage == PropActionLifecycle.AFTER_DO:
_undo_redo.add_do_method(self, "_res_edit_select", array_prop, [null])
elif lifecycle_stage == PropActionLifecycle.AFTER_UNDO:
_undo_redo.add_undo_method(self, "_res_edit_select", array_prop, [res_val])
else:
if lifecycle_stage == PropActionLifecycle.AFTER_DO:
_res_edit_select(array_prop, [null])
"PA_ArraySet":
var new_res_val = prop_action.val
if current_index == prop_action.index:
if _undo_redo && _can_prop_action_create_history(prop_action):
if lifecycle_stage == PropActionLifecycle.AFTER_DO:
_undo_redo.add_do_method(self, "_res_edit_select", array_prop, [new_res_val])
elif lifecycle_stage == PropActionLifecycle.AFTER_UNDO:
_undo_redo.add_undo_method(self, "_res_edit_select", array_prop, [res_val])
else:
if lifecycle_stage == PropActionLifecycle.AFTER_DO:
_res_edit_select(array_prop, [new_res_val])
# Requests a prop action that updates the needed property
func _res_edit_select(array_prop:String, new_res_array:Array, create_history:bool = false):
var res_edit = find_res_edit_by_array_prop(array_prop)
if res_edit:
var array_val = get(res_edit.array_prop)
var res_val = get(res_edit.res_prop)
var new_res_val = new_res_array[0]
if res_val == new_res_val:
new_res_val = null
var prop_action = PA_PropSet.new(res_edit.res_prop, new_res_val)
prop_action.can_create_history = create_history
request_prop_action(prop_action)
#-------------------------------------------------------------------------------
# Prop dependency misc
#-------------------------------------------------------------------------------
func find_dependency_by_dependent_prop(dependent_prop:String):
for dependency in prop_dependency_data:
if dependency.dependent_prop == dependent_prop:
return dependency
return null
func find_dependency_by_controlling_prop(controlling_prop:String):
for dependency in prop_dependency_data:
if dependency.controlling_props.has(controlling_prop):
return dependency
return null
#-------------------------------------------------------------------------------
# Res edit misc
#-------------------------------------------------------------------------------
func find_res_edit_by_array_prop(array_prop:String):
for res_edit in res_edit_data:
if res_edit.array_prop == array_prop:
return res_edit
return null
func find_res_edit_by_res_prop(res_prop:String):
for res_edit in res_edit_data:
if res_edit.res_prop == res_prop:
return res_edit
return null
func res_edit_update_interaction_features(res_prop:String):
var res_edit = find_res_edit_by_res_prop(res_prop)
if res_edit == null || res_edit.is_empty(): return
var array_val = get(res_edit.array_prop)
for i in range(0, array_val.size()):
var res_val = get(res_edit.res_prop)
var res_val_at_index = array_val[i]
if res_val_at_index == res_val:
req_change_interaction_feature.emit(res_edit.array_prop, UI_ActionThumbnail_GD.InteractionFlags.PRESS, true, {"index": i})
else:
req_change_interaction_feature.emit(res_edit.array_prop, UI_ActionThumbnail_GD.InteractionFlags.PRESS, false, {"index": i})
#-------------------------------------------------------------------------------
# Debug
#-------------------------------------------------------------------------------
# Debug print with a ProjectSettings check
func debug_print_prop_action(string:String):
if !FunLib.get_setting_safe("dreadpons_spatial_gardener/debug/input_field_resource_log_prop_actions", false): return
logger.info(string)

View File

@@ -0,0 +1,32 @@
@tool
extends "prop_action.gd"
#-------------------------------------------------------------------------------
# Insert an array element at index
#-------------------------------------------------------------------------------
var index:int = -1
func _init(__prop:String, __val, __index:int):
super(__prop, __val)
set_meta("class", "PA_ArrayInsert")
index = __index
can_create_history = true
func _to_string():
return "%s: [prop: %s, val: %s, index: %d, can_create_history: %s]" % [get_meta("class"), prop, str(val), index, str(can_create_history)]
func duplicate(deep:bool = false):
var copy = self.get_script().new(prop, val, index)
copy.can_create_history = can_create_history
return copy

View File

@@ -0,0 +1,32 @@
@tool
extends "prop_action.gd"
#-------------------------------------------------------------------------------
# Remove an array element at index
#-------------------------------------------------------------------------------
var index:int = -1
func _init(__prop:String, __val, __index:int):
super(__prop, __val)
set_meta("class", "PA_ArrayRemove")
index = __index
can_create_history = true
func _to_string():
return "%s: [prop: %s, val: %s, index: %d, can_create_history: %s]" % [get_meta("class"), prop, str(val), index, str(can_create_history)]
func duplicate(deep:bool = false):
var copy = self.get_script().new(prop, val, index)
copy.can_create_history = can_create_history
return copy

View File

@@ -0,0 +1,32 @@
@tool
extends "prop_action.gd"
#-------------------------------------------------------------------------------
# Set an array element at index
#-------------------------------------------------------------------------------
var index:int = -1
func _init(__prop:String, __val, __index:int):
super(__prop, __val)
set_meta("class", "PA_ArraySet")
index = __index
can_create_history = true
func _to_string():
return "%s: [prop: %s, val: %s, index: %d, can_create_history: %s]" % [get_meta("class"), prop, str(val), index, str(can_create_history)]
func duplicate(deep:bool = false):
var copy = self.get_script().new(prop, val, index)
copy.can_create_history = can_create_history
return copy

View File

@@ -0,0 +1,21 @@
@tool
extends "prop_action.gd"
#-------------------------------------------------------------------------------
# Edit a property
# It is implied that these changes are cosmetic/in progress/not permanent
# The value that persists should be set from PA_PropSet
#-------------------------------------------------------------------------------
func _init(__prop:String, __val):
super(__prop, __val)
set_meta("class", "PA_PropEdit")
can_create_history = false
func _to_string():
return "%s: [prop: %s, val: %s, can_create_history: %s]" % [get_meta("class"), prop, str(val), str(can_create_history)]

View File

@@ -0,0 +1,19 @@
@tool
extends "prop_action.gd"
#-------------------------------------------------------------------------------
# Set a property
#-------------------------------------------------------------------------------
func _init(__prop:String, __val):
super(__prop, __val)
set_meta("class", "PA_PropSet")
can_create_history = true
func _to_string():
return "%s: [prop: %s, val: %s, can_create_history: %s]" % [get_meta("class"), prop, str(val), str(can_create_history)]

View File

@@ -0,0 +1,37 @@
@tool
extends RefCounted
#-------------------------------------------------------------------------------
# A base storage object for actions that affect properties in some way
#-------------------------------------------------------------------------------
var prop:String = ""
var val = null
var can_create_history:bool
func _init(__prop:String,__val):
set_meta("class", "PropAction")
prop = __prop
val = __val
can_create_history = true
func _to_string():
return "%s: [prop: %s, val: %s, can_create_history: %s]" % [get_meta("class"), prop, str(val), str(can_create_history)]
func duplicate(deep:bool = false):
var copy = self.get_script().new(prop, val)
copy.can_create_history = can_create_history
if deep:
if copy.val is Array || copy.val is Dictionary:
copy.val = copy.val.duplicate()
return copy