gdharness 1.3.9 → 1.3.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/cli.js +4740 -3212
- package/build/godot/addons/gdharness_editor/animation_reads.gd +115 -4
- package/build/godot/addons/gdharness_editor/declared_values.gd +272 -0
- package/build/godot/addons/gdharness_editor/file_reads.gd +102 -18
- package/build/godot/addons/gdharness_editor/headless_check.gd +49 -2
- package/build/godot/addons/gdharness_editor/property_paths.gd +89 -16
- package/build/godot/addons/gdharness_editor/property_values.gd +180 -121
- package/build/godot/addons/gdharness_editor/reading.gd +51 -1
- package/build/godot/addons/gdharness_editor/scene_file.gd +77 -140
- package/build/godot/addons/gdharness_editor/scene_uids.gd +107 -0
- package/build/godot/addons/gdharness_editor/scene_uids.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/serialisation.gd +11 -22
- package/build/godot/addons/gdharness_editor/setting_reads.gd +121 -0
- package/build/godot/addons/gdharness_editor/setting_reads.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/tool_executor.gd +28 -5
- package/build/godot/addons/gdharness_editor/tools/animation_states.gd +39 -20
- package/build/godot/addons/gdharness_editor/tools/animation_tools.gd +49 -362
- package/build/godot/addons/gdharness_editor/tools/animation_tracks.gd +464 -0
- package/build/godot/addons/gdharness_editor/tools/animation_tracks.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/tools/class_tools.gd +37 -0
- package/build/godot/addons/gdharness_editor/tools/navigation_bake.gd +221 -0
- package/build/godot/addons/gdharness_editor/tools/navigation_bake.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +147 -492
- package/build/godot/addons/gdharness_editor/tools/scene_groups.gd +16 -0
- package/build/godot/addons/gdharness_editor/tools/scene_kin.gd +4 -3
- package/build/godot/addons/gdharness_editor/tools/scene_paths.gd +2 -1
- package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +79 -5
- package/build/godot/addons/gdharness_editor/tools/settings_tools.gd +7 -30
- package/build/godot/addons/gdharness_editor/tools/signal_tools.gd +183 -37
- package/build/godot/addons/gdharness_editor/tools/tileset_tools.gd +485 -0
- package/build/godot/addons/gdharness_editor/tools/tileset_tools.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/value_shapes.gd +128 -1
- package/build/godot/addons/gdharness_runtime/declared_values.gd +272 -0
- package/build/godot/addons/gdharness_runtime/reading.gd +51 -1
- package/build/godot/addons/gdharness_runtime/runtime_autoload.gd +35 -3
- package/build/godot/addons/gdharness_runtime/runtime_changes.gd +185 -30
- package/build/godot/addons/gdharness_runtime/runtime_input.gd +86 -79
- package/build/godot/addons/gdharness_runtime/runtime_mouse.gd +17 -0
- package/build/godot/addons/gdharness_runtime/runtime_pad.gd +13 -4
- package/build/godot/addons/gdharness_runtime/runtime_paths.gd +309 -22
- package/build/godot/addons/gdharness_runtime/runtime_queries.gd +163 -20
- package/build/godot/addons/gdharness_runtime/runtime_screen.gd +49 -3
- package/build/godot/addons/gdharness_runtime/runtime_targets.gd +5 -2
- package/build/godot/addons/gdharness_runtime/runtime_values.gd +146 -5
- package/build/godot/addons/gdharness_runtime/runtime_waits.gd +279 -11
- package/build/godot/addons/gdharness_runtime/runtime_words.gd +27 -0
- package/build/godot/addons/gdharness_runtime/serialisation.gd +11 -22
- package/build/godot/addons/gdharness_runtime/value_shapes.gd +128 -1
- package/build/godot/operations/animation_reads.gd +115 -4
- package/build/godot/operations/audio_buses.gd +93 -7
- package/build/godot/operations/classdb_queries.gd +119 -7
- package/build/godot/operations/declared_values.gd +272 -0
- package/build/godot/operations/dependencies.gd +154 -41
- package/build/godot/operations/editor_tools.gd +22 -14
- package/build/godot/operations/export_presets.gd +602 -4
- package/build/godot/operations/file_reads.gd +102 -18
- package/build/godot/operations/gdscript_authoring.gd +11 -1
- package/build/godot/operations/godot_operations.gd +16 -2
- package/build/godot/operations/import_pipeline.gd +20 -221
- package/build/godot/operations/input_actions.gd +108 -47
- package/build/godot/operations/project_config.gd +83 -69
- package/build/godot/operations/project_diagnostics.gd +4 -0
- package/build/godot/operations/project_validation.gd +266 -0
- package/build/godot/operations/property_paths.gd +89 -16
- package/build/godot/operations/property_values.gd +180 -121
- package/build/godot/operations/reading.gd +51 -1
- package/build/godot/operations/scene_connections.gd +222 -0
- package/build/godot/operations/serialisation.gd +11 -22
- package/build/godot/operations/setting_reads.gd +121 -0
- package/build/godot/operations/value_shapes.gd +128 -1
- package/build/godot/tests/listed.gd +73 -13
- package/build/keeper.js +1371 -706
- package/package.json +1 -1
|
@@ -26,38 +26,109 @@ static func declared_on(target: Object, property: String) -> Dictionary:
|
|
|
26
26
|
## start with the name of one holding a resource built into it, then a slash and a key of that
|
|
27
27
|
## resource: "sources/0/0:0/0/probability" is the probability of tile 0:0 in the atlas source a
|
|
28
28
|
## TileSet keeps as sources/0. A colon path cannot reach it, since the tile's own name holds colons.
|
|
29
|
-
## A
|
|
30
|
-
##
|
|
31
|
-
## file
|
|
29
|
+
## A list of resources is stepped into by an element's index: "lines/1/text" is the text of the
|
|
30
|
+
## second resource in lines. A resource kept in a file of its own is not reached, nor, given the
|
|
31
|
+
## file [param home] that [param target] is saved in, one built into another file, since writing it
|
|
32
|
+
## here would leave that file as it was.
|
|
32
33
|
static func holder_of(target: Object, key: String, home: String = "") -> Array:
|
|
33
34
|
var through: Array[Resource] = []
|
|
34
35
|
var holder: Object = target
|
|
35
36
|
var name: String = key
|
|
36
37
|
while declared_on(holder, name).is_empty():
|
|
37
38
|
var reached: Resource = null
|
|
39
|
+
var rest: int = -1
|
|
38
40
|
var at: int = name.find("/")
|
|
39
41
|
while at > 0:
|
|
40
|
-
var
|
|
41
|
-
|
|
42
|
+
var stepped: Array = _stepped(holder, name, at)
|
|
43
|
+
var held: Resource = stepped[0]
|
|
44
|
+
if held != null and _built_into(held, home):
|
|
42
45
|
reached = held
|
|
46
|
+
rest = GdhRead.as_int(stepped[1])
|
|
43
47
|
break
|
|
44
48
|
at = name.find("/", at + 1)
|
|
45
49
|
if reached == null:
|
|
46
50
|
break
|
|
47
51
|
through.append(reached)
|
|
48
52
|
holder = reached
|
|
49
|
-
name = name.substr(
|
|
53
|
+
name = name.substr(rest)
|
|
50
54
|
return [holder, name, through]
|
|
51
55
|
|
|
52
56
|
|
|
53
|
-
## The resource [param
|
|
54
|
-
##
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
## The resource the part of [param name] before the slash at [param at] reaches on [param target],
|
|
58
|
+
## as [resource, where the rest of [param name] starts], or [null, -1]: the one held under that key,
|
|
59
|
+
## or the element of the list held there that the step after the slash indexes.
|
|
60
|
+
static func _stepped(target: Object, name: String, at: int) -> Array:
|
|
61
|
+
var head: String = name.left(at)
|
|
62
|
+
var held: Resource = held_at(target, head)
|
|
63
|
+
if held != null:
|
|
64
|
+
return [held, at + 1]
|
|
65
|
+
var end: int = name.find("/", at + 1)
|
|
66
|
+
if end < 0 or declared_on(target, head).is_empty():
|
|
67
|
+
return [null, -1]
|
|
68
|
+
var index: String = name.substr(at + 1, end - at - 1)
|
|
69
|
+
var listed: Variant = target.get(head)
|
|
70
|
+
if not listed is Array or not index.is_valid_int():
|
|
71
|
+
return [null, -1]
|
|
72
|
+
var items: Array = listed
|
|
73
|
+
var place: int = index.to_int()
|
|
74
|
+
if place < 0 or place >= items.size() or not items[place] is Resource:
|
|
75
|
+
return [null, -1]
|
|
76
|
+
return [items[place], end + 1]
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
## The refusal for [param name], which [param target] does not declare, when a step of it indexes a
|
|
80
|
+
## list [param target] holds and names no resource in it; or "".
|
|
81
|
+
static func unlisted(target: Object, name: String) -> String:
|
|
82
|
+
var at: int = name.find("/")
|
|
83
|
+
while at > 0:
|
|
84
|
+
var head: String = name.left(at)
|
|
85
|
+
var listed: Variant = null if declared_on(target, head).is_empty() else target.get(head)
|
|
86
|
+
if listed is Array:
|
|
87
|
+
var items: Array = listed
|
|
88
|
+
return _unlisted_in(items, "%s.%s" % [class_called(target), head], name, at)
|
|
89
|
+
at = name.find("/", at + 1)
|
|
90
|
+
return ""
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
static func _unlisted_in(items: Array, called: String, name: String, at: int) -> String:
|
|
94
|
+
var head: String = name.left(at)
|
|
95
|
+
var end: int = name.find("/", at + 1)
|
|
96
|
+
var index: String = name.substr(at + 1) if end < 0 else name.substr(at + 1, end - at - 1)
|
|
97
|
+
if items.is_empty():
|
|
98
|
+
return "%s holds no elements, so %s names none: give %s a whole list first." % [called, name, head]
|
|
99
|
+
var last: int = items.size() - 1
|
|
100
|
+
if not index.is_valid_int():
|
|
101
|
+
return (
|
|
102
|
+
"%s is a list, so the step after it is the index of one of its elements, from 0 to %d, not %s."
|
|
103
|
+
% [called, last, index]
|
|
104
|
+
)
|
|
105
|
+
var place: int = index.to_int()
|
|
106
|
+
if place < 0 or place > last:
|
|
107
|
+
return (
|
|
108
|
+
"%s holds %d element%s, so %s names none: an index goes from 0 to %d."
|
|
109
|
+
% [called, items.size(), "" if items.size() == 1 else "s", name, last]
|
|
110
|
+
)
|
|
111
|
+
if not items[place] is Resource:
|
|
112
|
+
return (
|
|
113
|
+
"%s/%d holds %s, which has no properties to write: give %s a whole list instead."
|
|
114
|
+
% [head, place, type_string(typeof(items[place])), head]
|
|
115
|
+
)
|
|
116
|
+
if end < 0:
|
|
117
|
+
return (
|
|
118
|
+
(
|
|
119
|
+
"%s/%d is an element of %s, which a path does not replace alone: give %s a whole list, or "
|
|
120
|
+
+ "write a property of the element, as %s/%d/<property>."
|
|
121
|
+
)
|
|
122
|
+
% [head, place, called, head, head, place]
|
|
123
|
+
)
|
|
124
|
+
return ""
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
static func _built_into(held: Resource, home: String) -> bool:
|
|
128
|
+
if not held.is_built_in():
|
|
129
|
+
return false
|
|
59
130
|
var file: String = held.resource_path.get_slice("::", 0)
|
|
60
|
-
return
|
|
131
|
+
return home.is_empty() or file.is_empty() or file == home
|
|
61
132
|
|
|
62
133
|
|
|
63
134
|
## The file [param target] is saved in: its scene's for a node, as the nodes of an instance are
|
|
@@ -184,9 +255,11 @@ static func class_called(target: Object) -> String:
|
|
|
184
255
|
static func held_elsewhere(target: Object, name: String, home: String) -> String:
|
|
185
256
|
var at: int = name.find("/")
|
|
186
257
|
while at > 0:
|
|
187
|
-
var
|
|
188
|
-
var
|
|
189
|
-
var
|
|
258
|
+
var stepped: Array = _stepped(target, name, at)
|
|
259
|
+
var held: Resource = stepped[0]
|
|
260
|
+
var past: int = GdhRead.as_int(stepped[1])
|
|
261
|
+
var head: String = "" if held == null else name.left(past - 1)
|
|
262
|
+
var rest: String = "" if held == null else name.substr(past)
|
|
190
263
|
var file: String = "" if held == null else held.resource_path.get_slice("::", 0)
|
|
191
264
|
if held != null and not held.is_built_in():
|
|
192
265
|
return (
|
|
@@ -15,6 +15,7 @@ extends RefCounted
|
|
|
15
15
|
## dictionary shaped like a vector or a bare pair of numbers can be read as one.
|
|
16
16
|
|
|
17
17
|
const GdhDeclared = preload("declared_values.gd")
|
|
18
|
+
const GdhFileWalk = preload("file_walk.gd")
|
|
18
19
|
const GdhPropertyPaths = preload("property_paths.gd")
|
|
19
20
|
const GdhRead = preload("reading.gd")
|
|
20
21
|
const GdhSame = preload("same_values.gd")
|
|
@@ -35,22 +36,27 @@ const THEME_ITEMS: Dictionary[String, Array] = {
|
|
|
35
36
|
# written as a list of pairs, and only the property knows that each pair is a Vector2 rather than a
|
|
36
37
|
# list of two numbers: without being told, every element was read as itself and the engine turned
|
|
37
38
|
# the lot into zeroes on the way into the property.
|
|
38
|
-
const PACKED_ELEMENTS: Dictionary =
|
|
39
|
-
TYPE_PACKED_BYTE_ARRAY: TYPE_INT,
|
|
40
|
-
TYPE_PACKED_INT32_ARRAY: TYPE_INT,
|
|
41
|
-
TYPE_PACKED_INT64_ARRAY: TYPE_INT,
|
|
42
|
-
TYPE_PACKED_FLOAT32_ARRAY: TYPE_FLOAT,
|
|
43
|
-
TYPE_PACKED_FLOAT64_ARRAY: TYPE_FLOAT,
|
|
44
|
-
TYPE_PACKED_STRING_ARRAY: TYPE_STRING,
|
|
45
|
-
TYPE_PACKED_VECTOR2_ARRAY: TYPE_VECTOR2,
|
|
46
|
-
TYPE_PACKED_VECTOR3_ARRAY: TYPE_VECTOR3,
|
|
47
|
-
TYPE_PACKED_VECTOR4_ARRAY: TYPE_VECTOR4,
|
|
48
|
-
TYPE_PACKED_COLOR_ARRAY: TYPE_COLOR,
|
|
49
|
-
}
|
|
39
|
+
const PACKED_ELEMENTS: Dictionary = GdhShapes.PACKED_ELEMENTS
|
|
50
40
|
|
|
51
41
|
# Entries in a property list that only head a section of the inspector. Object.set ignores their
|
|
52
42
|
# names, so a write to "Visibility" or "Transform" changed nothing and was answered as made.
|
|
53
43
|
const HEADINGS: int = PROPERTY_USAGE_CATEGORY | PROPERTY_USAGE_GROUP | PROPERTY_USAGE_SUBGROUP
|
|
44
|
+
## Control's layout_mode values, which the engine does not expose as constants.
|
|
45
|
+
const LAYOUT_MODE_POSITION: int = 0
|
|
46
|
+
const LAYOUT_MODE_ANCHORS: int = 1
|
|
47
|
+
## What a container sets on each child it lays out, beside the anchors and offsets.
|
|
48
|
+
const PLACED_BY_A_CONTAINER: Array[String] = ["position", "size", "global_position"]
|
|
49
|
+
|
|
50
|
+
## The property the last write_all refused because it did not hold what it was given, or "".
|
|
51
|
+
var unheld: String = ""
|
|
52
|
+
|
|
53
|
+
## The stored properties the last write_all's own writes moved beside the ones they were given, as a
|
|
54
|
+
## set: the Anchors mode an anchor needs, and what an engine setter keeps in step, such as the
|
|
55
|
+
## anchors an anchor preset sets. Only writes no script code runs for count. A headless write that
|
|
56
|
+
## attaches a script that is not a tool puts back whatever the call did not name, as that script's
|
|
57
|
+
## doing, which put a panel given a script and a bottom-wide preset back in Position mode, where its
|
|
58
|
+
## anchors are not stored.
|
|
59
|
+
var moved_by_engine: Dictionary = {}
|
|
54
60
|
|
|
55
61
|
var _values: GdhSerialisation = GdhSerialisation.new()
|
|
56
62
|
|
|
@@ -69,6 +75,10 @@ var _also_changed: Dictionary = {}
|
|
|
69
75
|
# the inspector would not have let it be entered, so a write answered as made said nothing of it.
|
|
70
76
|
var _outside_declared: Dictionary = {}
|
|
71
77
|
|
|
78
|
+
# The node paths the last write_all wrote that name no node from the node written, by property, as
|
|
79
|
+
# a sentence saying so.
|
|
80
|
+
var _names_no_node: Dictionary = {}
|
|
81
|
+
|
|
72
82
|
|
|
73
83
|
## Turns what arrived over the wire into the Godot value a property wants.
|
|
74
84
|
##
|
|
@@ -115,7 +125,7 @@ func cannot_hold(
|
|
|
115
125
|
if not GdhSerialisation.acceptable(value, expected_type):
|
|
116
126
|
var shape: String = "%s.%s is %s and the value given is %s, which cannot become one."
|
|
117
127
|
var remedy: String = GdhShapes.remedy(value, expected_type)
|
|
118
|
-
return shape % [holder, property, held,
|
|
128
|
+
return shape % [holder, property, held, _kind(value)] + remedy + cost
|
|
119
129
|
|
|
120
130
|
# And the same question of each element, because a list is accepted for a packed or typed array
|
|
121
131
|
# whatever is in it: a polygon written as [{"x": 1}] passes the check above and lands as one zero
|
|
@@ -128,10 +138,20 @@ func cannot_hold(
|
|
|
128
138
|
for index: int in items.size():
|
|
129
139
|
if not GdhSerialisation.acceptable(items[index], element):
|
|
130
140
|
var shape: String = "%s.%s is %s and item %d of the list given is %s, not %s."
|
|
131
|
-
var named: Array = [
|
|
132
|
-
holder, property, held, index, type_string(typeof(items[index])), type_string(element)
|
|
133
|
-
]
|
|
141
|
+
var named: Array = [holder, property, held, index, _kind(items[index]), type_string(element)]
|
|
134
142
|
return shape % named + GdhShapes.remedy(items[index], element) + cost
|
|
143
|
+
# A list of polygons is a list of lists of points, and each point is read as one too.
|
|
144
|
+
var inner: int = GdhRead.as_int(PACKED_ELEMENTS.get(element, TYPE_NIL), TYPE_NIL)
|
|
145
|
+
if inner == TYPE_NIL or not items[index] is Array:
|
|
146
|
+
continue
|
|
147
|
+
var points: Array = items[index]
|
|
148
|
+
for at: int in points.size():
|
|
149
|
+
if not GdhSerialisation.acceptable(points[at], inner):
|
|
150
|
+
var nested: String = "%s.%s holds lists of %s, and item %d of list %d given is %s, not %s."
|
|
151
|
+
var said: Array = [
|
|
152
|
+
holder, property, type_string(inner), at, index, _kind(points[at]), type_string(inner)
|
|
153
|
+
]
|
|
154
|
+
return nested % said + GdhShapes.remedy(points[at], inner) + cost
|
|
135
155
|
if expected_type == TYPE_DICTIONARY and elements.size() == 2 and value is Dictionary:
|
|
136
156
|
var entries: Dictionary = value
|
|
137
157
|
var key_type: int = GdhRead.as_int(elements[0], TYPE_NIL)
|
|
@@ -148,6 +168,14 @@ func cannot_hold(
|
|
|
148
168
|
return ""
|
|
149
169
|
|
|
150
170
|
|
|
171
|
+
## What [param value] is, as a refusal names it. A number arrives from JSON as a float whether or not
|
|
172
|
+
## it has a fraction, so [1, 2] given for a list of words was called a list of floats.
|
|
173
|
+
static func _kind(value: Variant) -> String:
|
|
174
|
+
if value is int or value is float:
|
|
175
|
+
return "a number"
|
|
176
|
+
return type_string(typeof(value))
|
|
177
|
+
|
|
178
|
+
|
|
151
179
|
## Writes each of [param properties] onto [param target], stopping at the first it cannot write and
|
|
152
180
|
## answering with why, or "" when every one of them went on and holds what was given.
|
|
153
181
|
##
|
|
@@ -170,12 +198,24 @@ func cannot_hold(
|
|
|
170
198
|
func write_all(target: Object, properties: Dictionary) -> String:
|
|
171
199
|
_also_changed = {}
|
|
172
200
|
_outside_declared = {}
|
|
201
|
+
_names_no_node = {}
|
|
202
|
+
moved_by_engine = {}
|
|
203
|
+
unheld = ""
|
|
173
204
|
var named: Dictionary = {}
|
|
174
205
|
for key: Variant in properties:
|
|
175
206
|
named[str(key).get_slice(":", 0)] = true
|
|
176
207
|
var before: Dictionary = _stored(target, named)
|
|
177
208
|
var given: Dictionary = {}
|
|
178
209
|
var declarations: Dictionary = {}
|
|
210
|
+
# How many writes had been made when each property was last written. One written last is not
|
|
211
|
+
# written again: the node is as that write left it, so the engine refuses it the same way and
|
|
212
|
+
# says so a second time.
|
|
213
|
+
var written_at: Dictionary[String, int] = {}
|
|
214
|
+
var writes: int = 0
|
|
215
|
+
# Which other property's write last changed each one already written, by name. A Gradient keeps
|
|
216
|
+
# one offset for each colour, so offsets given after colors cut them back, and the refusal said
|
|
217
|
+
# only that the engine had changed the colours.
|
|
218
|
+
var undone_by: Dictionary[String, String] = {}
|
|
179
219
|
# A refusal of one of several leaves the others unwritten too, which a refusal naming only the
|
|
180
220
|
# one it refused left a caller to guess.
|
|
181
221
|
var unwritten: String = (
|
|
@@ -198,6 +238,9 @@ func write_all(target: Object, properties: Dictionary) -> String:
|
|
|
198
238
|
var elsewhere: String = GdhPropertyPaths.held_elsewhere(holder, name, home)
|
|
199
239
|
if not elsewhere.is_empty():
|
|
200
240
|
return elsewhere + unwritten
|
|
241
|
+
var unlisted: String = GdhPropertyPaths.unlisted(holder, name)
|
|
242
|
+
if not unlisted.is_empty():
|
|
243
|
+
return unlisted + unwritten
|
|
201
244
|
var ungrown: String = GdhPropertyPaths.grow(holder, name, GdhPropertyPaths.class_called(holder))
|
|
202
245
|
if not ungrown.is_empty():
|
|
203
246
|
return ungrown + unwritten
|
|
@@ -207,9 +250,16 @@ func write_all(target: Object, properties: Dictionary) -> String:
|
|
|
207
250
|
if declared.has("refused"):
|
|
208
251
|
return str(declared["refused"]) + unwritten
|
|
209
252
|
if declared.is_empty() and holder is ShaderMaterial and name.begins_with("shader_parameter/"):
|
|
210
|
-
return
|
|
253
|
+
return GdhDeclared.no_uniform(holder as ShaderMaterial, name) + unwritten
|
|
211
254
|
if declared.is_empty():
|
|
212
255
|
return no_property(holder, name) + unwritten
|
|
256
|
+
var unscripted: Variant = null
|
|
257
|
+
if holder == target and not GdhDeclared.runs_script_code(target, name):
|
|
258
|
+
unscripted = _stored(target, {})
|
|
259
|
+
if holder is Control:
|
|
260
|
+
var unplaced: String = _laid_out(holder as Control, name)
|
|
261
|
+
if not unplaced.is_empty():
|
|
262
|
+
return unplaced + unwritten
|
|
213
263
|
var value: Array = value_for(holder, declared, properties[key])
|
|
214
264
|
if not value[0]:
|
|
215
265
|
return str(value[1]) + unwritten
|
|
@@ -217,91 +267,91 @@ func write_all(target: Object, properties: Dictionary) -> String:
|
|
|
217
267
|
if not outside.is_empty():
|
|
218
268
|
_outside_declared[property] = outside
|
|
219
269
|
_write(target, property, value[1])
|
|
270
|
+
if unscripted is Dictionary:
|
|
271
|
+
var was: Dictionary = unscripted
|
|
272
|
+
var now: Dictionary = _stored(target, {})
|
|
273
|
+
for moved: String in now:
|
|
274
|
+
if moved != name.get_slice(":", 0) and not GdhSame.same(now[moved], was.get(moved)):
|
|
275
|
+
moved_by_engine[moved] = true
|
|
276
|
+
writes += 1
|
|
277
|
+
written_at[property] = writes
|
|
278
|
+
_note_undone(target, property, given, undone_by)
|
|
220
279
|
given[property] = value[1]
|
|
221
280
|
declarations[property] = declared
|
|
222
281
|
|
|
223
282
|
for property: String in given:
|
|
224
|
-
if not GdhSame.same(read_at(target, property), given[property]):
|
|
283
|
+
if written_at[property] < writes and not GdhSame.same(read_at(target, property), given[property]):
|
|
225
284
|
_write(target, property, given[property])
|
|
285
|
+
writes += 1
|
|
286
|
+
written_at[property] = writes
|
|
287
|
+
_note_undone(target, property, given, undone_by)
|
|
226
288
|
for property: String in given:
|
|
227
289
|
var held: Variant = read_at(target, property)
|
|
228
290
|
if not GdhSame.same(held, given[property]):
|
|
291
|
+
unheld = property
|
|
292
|
+
if undone_by.has(property):
|
|
293
|
+
return (
|
|
294
|
+
(
|
|
295
|
+
"%s.%s was given %s and holds %s once %s was set as well: the engine keeps the two "
|
|
296
|
+
+ "in step, so they cannot hold these values together. Nothing was written."
|
|
297
|
+
)
|
|
298
|
+
% [
|
|
299
|
+
GdhPropertyPaths.class_called(target),
|
|
300
|
+
property,
|
|
301
|
+
shown(given[property]),
|
|
302
|
+
shown(held),
|
|
303
|
+
undone_by[property]
|
|
304
|
+
]
|
|
305
|
+
)
|
|
229
306
|
var declared: Dictionary = declarations[property]
|
|
230
|
-
return
|
|
307
|
+
return GdhDeclared.not_held(
|
|
308
|
+
GdhPropertyPaths.class_called(target),
|
|
309
|
+
target.get_class(),
|
|
310
|
+
property,
|
|
311
|
+
declared,
|
|
312
|
+
[given[property], shown(given[property])],
|
|
313
|
+
shown(held)
|
|
314
|
+
)
|
|
231
315
|
for property: String in before:
|
|
232
316
|
var now: Variant = target.get(property)
|
|
233
317
|
if not GdhSame.same(now, before[property]):
|
|
234
318
|
_also_changed[property] = [before[property], now]
|
|
319
|
+
if target is Node:
|
|
320
|
+
_names_no_node = GdhDeclared.naming_nothing(target as Node, given)
|
|
235
321
|
return ""
|
|
236
322
|
|
|
237
323
|
|
|
238
|
-
##
|
|
239
|
-
##
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
var
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
for index: int in AudioServer.bus_count:
|
|
259
|
-
var _added: bool = names.append(AudioServer.get_bus_name(index))
|
|
260
|
-
if names.is_empty() or names.has(str(value)):
|
|
261
|
-
return said + ": the engine changed or refused the value, so nothing was written."
|
|
262
|
-
var close: PackedStringArray = GdhRead.nearest(str(value), names)
|
|
263
|
-
var nearest: String = (
|
|
264
|
-
"%s %s" % ["is" if close.size() == 1 else "are", ", ".join(close)] if not close.is_empty() else ""
|
|
265
|
-
)
|
|
266
|
-
if bus:
|
|
324
|
+
## Notes in [param undone_by] each property of [param given] that writing [param written] on
|
|
325
|
+
## [param target] left holding something else.
|
|
326
|
+
func _note_undone(
|
|
327
|
+
target: Object, written: String, given: Dictionary, undone_by: Dictionary[String, String]
|
|
328
|
+
) -> void:
|
|
329
|
+
for other: String in given:
|
|
330
|
+
if other != written and not GdhSame.same(read_at(target, other), given[other]):
|
|
331
|
+
undone_by[other] = written
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
## Why [param name] cannot be written on [param control], or "" once it can. A container places and
|
|
335
|
+
## sizes its children and the scene keeps none of that for them, so their anchors, offsets, position
|
|
336
|
+
## and size are refused with what a child does set. A control in Position mode ignores an anchor
|
|
337
|
+
## preset, and the inspector offers anchors only in Anchors mode, so writing one puts it there first,
|
|
338
|
+
## as picking a preset in the editor does; alsoChanged then names layout_mode.
|
|
339
|
+
func _laid_out(control: Control, name: String) -> String:
|
|
340
|
+
var base: String = name.get_slice(":", 0)
|
|
341
|
+
var anchoring: bool = base == "anchors_preset" or base.begins_with("anchor_")
|
|
342
|
+
var parent: Node = control.get_parent()
|
|
343
|
+
if parent is Container and (anchoring or base.begins_with("offset_") or base in PLACED_BY_A_CONTAINER):
|
|
267
344
|
return (
|
|
268
345
|
(
|
|
269
|
-
"%s
|
|
270
|
-
+ "
|
|
271
|
-
+ "
|
|
346
|
+
"%s's parent, %s, is a %s, which places and sizes it, so its %s is the container's to set "
|
|
347
|
+
+ "and the scene keeps none of it. A child of a container is laid out with "
|
|
348
|
+
+ "size_flags_horizontal, size_flags_vertical and custom_minimum_size."
|
|
272
349
|
)
|
|
273
|
-
% [
|
|
274
|
-
said,
|
|
275
|
-
shown(value),
|
|
276
|
-
"the buses are " + ", ".join(names) if nearest.is_empty() else "the nearest " + nearest
|
|
277
|
-
]
|
|
350
|
+
% [control.name, parent.name, parent.get_class(), base]
|
|
278
351
|
)
|
|
279
|
-
|
|
280
|
-
"
|
|
281
|
-
|
|
282
|
-
said,
|
|
283
|
-
shown(value),
|
|
284
|
-
"it lists " + ", ".join(names) if nearest.is_empty() else "the nearest it lists " + nearest
|
|
285
|
-
]
|
|
286
|
-
)
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
## Why [param material] has no [param property]: a ShaderMaterial's parameters are its shader's
|
|
290
|
-
## uniforms, so the shader is what lacks one, and "ShaderMaterial has no property" named the
|
|
291
|
-
## material.
|
|
292
|
-
static func _no_uniform(material: ShaderMaterial, property: String) -> String:
|
|
293
|
-
var uniform: String = property.trim_prefix("shader_parameter/")
|
|
294
|
-
if material.shader == null:
|
|
295
|
-
return "The ShaderMaterial has no shader, so it has no parameter %s yet; set shader first." % uniform
|
|
296
|
-
var shader: String = material.shader.resource_path
|
|
297
|
-
var named: String = shader if not shader.is_empty() else "Its shader"
|
|
298
|
-
var uniforms: Array = []
|
|
299
|
-
for entry: Dictionary in material.shader.get_shader_uniform_list():
|
|
300
|
-
uniforms.append(str(entry["name"]))
|
|
301
|
-
var said: String = "%s declares no uniform %s, which %s would set" % [named, uniform, property]
|
|
302
|
-
if uniforms.is_empty():
|
|
303
|
-
return said + "; it declares no uniforms."
|
|
304
|
-
return GdhRead.offered(said, uniform, uniforms) + "."
|
|
352
|
+
if anchoring and GdhRead.as_int(control.get("layout_mode")) == LAYOUT_MODE_POSITION:
|
|
353
|
+
control.set("layout_mode", LAYOUT_MODE_ANCHORS)
|
|
354
|
+
return ""
|
|
305
355
|
|
|
306
356
|
|
|
307
357
|
## Puts under alsoChanged in [param answer] each property the last [method write_all] changed
|
|
@@ -312,6 +362,8 @@ static func _no_uniform(material: ShaderMaterial, property: String) -> String:
|
|
|
312
362
|
func note_write(answer: Dictionary, relative_to: Node = null) -> void:
|
|
313
363
|
if not _outside_declared.is_empty():
|
|
314
364
|
answer["outsideDeclared"] = _outside_declared.duplicate()
|
|
365
|
+
if not _names_no_node.is_empty():
|
|
366
|
+
answer["namesNoNode"] = _names_no_node.duplicate()
|
|
315
367
|
if _also_changed.is_empty():
|
|
316
368
|
return
|
|
317
369
|
var noted: Dictionary = {}
|
|
@@ -393,16 +445,25 @@ func _component_slot(target: Object, path: String) -> Dictionary:
|
|
|
393
445
|
var has: PackedStringArray = GdhSerialisation.components_of(value)
|
|
394
446
|
var sort: String = GdhRead.with_article(type_string(typeof(value)))
|
|
395
447
|
if has.is_empty():
|
|
396
|
-
|
|
397
|
-
"refused":
|
|
448
|
+
var refused: String = (
|
|
398
449
|
(
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
+ "vector, a colour, a rectangle or a transform."
|
|
402
|
-
)
|
|
403
|
-
% [owner, walked, sort]
|
|
450
|
+
"%s.%s is %s, which a path does not step into: a path reaches the components of a "
|
|
451
|
+
+ "vector, a colour, a rectangle or a transform."
|
|
404
452
|
)
|
|
405
|
-
|
|
453
|
+
% [owner, walked, sort]
|
|
454
|
+
)
|
|
455
|
+
# A resource is reached with a slash, which a caller writing a colon had no way to know.
|
|
456
|
+
if value is Resource:
|
|
457
|
+
refused += (
|
|
458
|
+
" A property of the resource it holds is reached with a slash: %s/%s."
|
|
459
|
+
% [walked, ":".join(steps.slice(index))]
|
|
460
|
+
)
|
|
461
|
+
elif value is Array and index + 1 < steps.size():
|
|
462
|
+
refused += (
|
|
463
|
+
" A property of a resource in it is reached with slashes and the element's index: %s/%s/%s."
|
|
464
|
+
% [walked, step, ":".join(steps.slice(index + 1))]
|
|
465
|
+
)
|
|
466
|
+
return {"refused": refused}
|
|
406
467
|
if not step in has:
|
|
407
468
|
return {
|
|
408
469
|
"refused":
|
|
@@ -437,7 +498,16 @@ func value_for(target: Object, declared: Dictionary, raw: Variant) -> Array:
|
|
|
437
498
|
value = found[1]
|
|
438
499
|
else:
|
|
439
500
|
var elements: Array = GdhDeclared.elements_of(declared)
|
|
440
|
-
|
|
501
|
+
# Some engine properties declare a bare Array and hold a typed one: NavigationPolygon's
|
|
502
|
+
# outlines hold PackedVector2Arrays, and points written as {"x", "y"} inside them landed as
|
|
503
|
+
# zero vectors, since nothing said what the inner lists hold.
|
|
504
|
+
if elements.is_empty() and expected_type == TYPE_ARRAY:
|
|
505
|
+
var current: Variant = target.get(property)
|
|
506
|
+
if current is Array:
|
|
507
|
+
var typed: Array = current
|
|
508
|
+
if typed.is_typed() and typed.get_typed_builtin() != TYPE_OBJECT:
|
|
509
|
+
elements = [typed.get_typed_builtin()]
|
|
510
|
+
if GdhShapes.fraction(raw, expected_type, elements):
|
|
441
511
|
return [false, _fraction_refusal(target, property, raw)]
|
|
442
512
|
var rounded: String = GdhSerialisation.inexact(raw, expected_type)
|
|
443
513
|
if not rounded.is_empty():
|
|
@@ -610,34 +680,6 @@ func _object_item(target: Object, given: Variant, kind: Array, holding: String,
|
|
|
610
680
|
return [false, "%s, and %s is %s." % [holding, place[0], named]]
|
|
611
681
|
|
|
612
682
|
|
|
613
|
-
## Whether [param value] holds a number with a fractional part where an integer is wanted, in a
|
|
614
|
-
## list or a dictionary as much as bare. The conversion would cut 2.7 to 2 without a word, and a
|
|
615
|
-
## dictionary key arrives as the text "2.7", which reads as the same number.
|
|
616
|
-
func _fraction(value: Variant, type: int, elements: Array = []) -> bool:
|
|
617
|
-
if type == TYPE_INT:
|
|
618
|
-
var number: Variant = value
|
|
619
|
-
if typeof(value) == TYPE_STRING:
|
|
620
|
-
number = GdhRead.json_or_null(str(value))
|
|
621
|
-
if typeof(number) != TYPE_FLOAT:
|
|
622
|
-
return false
|
|
623
|
-
var whole: float = number
|
|
624
|
-
return is_finite(whole) and whole != floorf(whole)
|
|
625
|
-
var element: int = GdhRead.as_int(PACKED_ELEMENTS.get(type, TYPE_NIL), TYPE_NIL)
|
|
626
|
-
if type == TYPE_ARRAY and not elements.is_empty():
|
|
627
|
-
element = GdhRead.as_int(elements[0], TYPE_NIL)
|
|
628
|
-
if value is Array and element != TYPE_NIL:
|
|
629
|
-
var items: Array = value
|
|
630
|
-
return items.any(func(item: Variant) -> bool: return _fraction(item, element))
|
|
631
|
-
if type == TYPE_DICTIONARY and elements.size() == 2 and value is Dictionary:
|
|
632
|
-
var entries: Dictionary = value
|
|
633
|
-
var key_type: int = GdhRead.as_int(elements[0], TYPE_NIL)
|
|
634
|
-
var value_type: int = GdhRead.as_int(elements[1], TYPE_NIL)
|
|
635
|
-
for key: Variant in entries:
|
|
636
|
-
if _fraction(key, key_type) or _fraction(entries[key], value_type):
|
|
637
|
-
return true
|
|
638
|
-
return false
|
|
639
|
-
|
|
640
|
-
|
|
641
683
|
func _fraction_refusal(target: Object, property: String, raw: Variant) -> String:
|
|
642
684
|
return (
|
|
643
685
|
(
|
|
@@ -672,12 +714,15 @@ func colour_from(raw: Variant, named: String) -> Array:
|
|
|
672
714
|
## The refusal for [param property], which [param target] does not declare, naming the declared
|
|
673
715
|
## ones nearest it: "StyleBoxFlat has no property shadow_sizee" left the caller nothing to send.
|
|
674
716
|
static func no_property(target: Object, property: String) -> String:
|
|
675
|
-
|
|
717
|
+
var said: String = (
|
|
676
718
|
GdhRead.near_property(
|
|
677
719
|
"%s has no property %s" % [GdhPropertyPaths.class_called(target), property], target, property
|
|
678
720
|
)
|
|
679
721
|
+ "."
|
|
680
722
|
)
|
|
723
|
+
if not target is Node:
|
|
724
|
+
return said
|
|
725
|
+
return said + GdhDeclared.held_by_children(target as Node, property)
|
|
681
726
|
|
|
682
727
|
|
|
683
728
|
## The resource [param path] names, as [found, resource or why not].
|
|
@@ -716,7 +761,11 @@ func _resource_at(path: String, property: String) -> Array:
|
|
|
716
761
|
% [path, property, path]
|
|
717
762
|
)
|
|
718
763
|
]
|
|
719
|
-
|
|
764
|
+
if path.begins_with("uid://"):
|
|
765
|
+
return [false, "No resource at %s for %s" % [path, property]]
|
|
766
|
+
return [
|
|
767
|
+
false, GdhFileWalk.new().with_nearest_files("No resource at %s for %s" % [path, property], path)
|
|
768
|
+
]
|
|
720
769
|
return [true, load(path)]
|
|
721
770
|
|
|
722
771
|
|
|
@@ -893,6 +942,9 @@ func _parse_array(value: Array, expected_type: int, elements: Array) -> Variant:
|
|
|
893
942
|
var element: int = GdhRead.as_int(PACKED_ELEMENTS[expected_type], TYPE_NIL)
|
|
894
943
|
return value.map(func(item: Variant) -> Variant: return parse(item, element))
|
|
895
944
|
|
|
945
|
+
# Read by its places, a word was read as 0 and ["a", "b"] written as (0, 0).
|
|
946
|
+
if GdhShapes.NUMBER_FIELDS.has(type_string(expected_type)) and not GdhShapes.all_numbers(value):
|
|
947
|
+
return value
|
|
896
948
|
match expected_type:
|
|
897
949
|
TYPE_VECTOR2:
|
|
898
950
|
if value.size() == 2:
|
|
@@ -933,5 +985,12 @@ func _parse_array(value: Array, expected_type: int, elements: Array) -> Variant:
|
|
|
933
985
|
return items
|
|
934
986
|
if not items.all(func(item: Variant) -> bool: return GdhSerialisation.acceptable(item, element_type)):
|
|
935
987
|
return items
|
|
988
|
+
# Converted, a list of points holding a word became a packed list of zeroes before anything read it.
|
|
989
|
+
var inner: int = GdhRead.as_int(PACKED_ELEMENTS.get(element_type, TYPE_NIL), TYPE_NIL)
|
|
990
|
+
if inner != TYPE_NIL:
|
|
991
|
+
for item: Variant in items:
|
|
992
|
+
var points: Array = item if item is Array else []
|
|
993
|
+
if not points.all(func(point: Variant) -> bool: return GdhSerialisation.acceptable(point, inner)):
|
|
994
|
+
return items
|
|
936
995
|
var fitted: Array = items.map(func(item: Variant) -> Variant: return type_convert(item, element_type))
|
|
937
996
|
return Array(fitted, element_type, &"", null)
|