gdharness 1.3.9 → 1.3.10
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 +4563 -3195
- 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 +20 -13
- package/build/keeper.js +1371 -706
- package/package.json +1 -1
|
@@ -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)
|
|
@@ -193,7 +193,13 @@ static func nearest(word: String, names: PackedStringArray) -> PackedStringArray
|
|
|
193
193
|
func(a: String, b: String) -> bool:
|
|
194
194
|
return a.to_lower().similarity(wanted) > b.to_lower().similarity(wanted)
|
|
195
195
|
)
|
|
196
|
-
|
|
196
|
+
# Every name that only adds to the word stays, since cutting at three named three corners of
|
|
197
|
+
# corner_radius and left out the fourth as if it were not there.
|
|
198
|
+
var named: PackedStringArray = PackedStringArray(close.slice(0, 3))
|
|
199
|
+
for name: String in close.slice(3):
|
|
200
|
+
if name.to_lower().begins_with(wanted):
|
|
201
|
+
var _added: bool = named.append(name)
|
|
202
|
+
return named
|
|
197
203
|
|
|
198
204
|
|
|
199
205
|
## [param said], the refusal for [param word], which is not among [param names], with those nearest
|
|
@@ -216,6 +222,10 @@ static func near_property(said: String, target: Object, property: String) -> Str
|
|
|
216
222
|
var scripted: Array[String] = []
|
|
217
223
|
for declared: Dictionary in target.get_property_list():
|
|
218
224
|
var usage: int = as_int(declared.get("usage", 0))
|
|
225
|
+
if usage & PROPERTY_USAGE_ARRAY:
|
|
226
|
+
var listed: String = _list_called(target, declared, property)
|
|
227
|
+
if not listed.is_empty():
|
|
228
|
+
return "%s; %s" % [said, listed]
|
|
219
229
|
if not usage & headings:
|
|
220
230
|
names.append(str(declared.get("name", "")))
|
|
221
231
|
if usage & PROPERTY_USAGE_SCRIPT_VARIABLE and not usage & headings:
|
|
@@ -229,6 +239,46 @@ static func near_property(said: String, target: Object, property: String) -> Str
|
|
|
229
239
|
return "%s; its script declares %s" % [said, ", ".join(PackedStringArray(scripted))]
|
|
230
240
|
|
|
231
241
|
|
|
242
|
+
## How [param target] keeps the list [param property] names, when that is the label of one of the
|
|
243
|
+
## engine's numbered lists, such as an OptionButton's Items; empty when it is not. The engine has no
|
|
244
|
+
## property by that name, only a count and each entry's fields under a numbered prefix, so "items"
|
|
245
|
+
## was refused with nothing near it to offer.
|
|
246
|
+
static func _list_called(target: Object, count: Dictionary, property: String) -> String:
|
|
247
|
+
var parts: PackedStringArray = str(count.get("class_name", "")).split(",")
|
|
248
|
+
if parts.size() != 2 or _bare(parts[0]) != _bare(property):
|
|
249
|
+
return ""
|
|
250
|
+
var counted: String = str(count.get("name", ""))
|
|
251
|
+
var first: String = parts[1] + "0/"
|
|
252
|
+
var fields: PackedStringArray = _fields_at(target, first)
|
|
253
|
+
if fields.is_empty() and ClassDB.can_instantiate(target.get_class()):
|
|
254
|
+
var fresh: Object = ClassDB.instantiate(target.get_class())
|
|
255
|
+
fresh.set(counted, 1)
|
|
256
|
+
fields = _fields_at(fresh, first)
|
|
257
|
+
if not fresh is RefCounted:
|
|
258
|
+
fresh.free()
|
|
259
|
+
var each: String = "each one's fields" if fields.is_empty() else "each one's %s" % ", ".join(fields)
|
|
260
|
+
return (
|
|
261
|
+
(
|
|
262
|
+
"it keeps its %s as %s, how many there are, and %s under %s for the first, %s for the second and so on: "
|
|
263
|
+
+ "give %s before the fields of the entries it makes"
|
|
264
|
+
)
|
|
265
|
+
% [parts[0].to_lower(), counted, each, first, parts[1] + "1/", counted]
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
static func _fields_at(target: Object, prefix: String) -> PackedStringArray:
|
|
270
|
+
var fields: PackedStringArray = PackedStringArray()
|
|
271
|
+
for declared: Dictionary in target.get_property_list():
|
|
272
|
+
var name: String = str(declared.get("name", ""))
|
|
273
|
+
if name.begins_with(prefix):
|
|
274
|
+
var _added: bool = fields.append(name.trim_prefix(prefix))
|
|
275
|
+
return fields
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
static func _bare(word: String) -> String:
|
|
279
|
+
return word.to_lower().replace("_", "").replace(" ", "")
|
|
280
|
+
|
|
281
|
+
|
|
232
282
|
## The signals [param object] has that a caller can connect, its own class's first: the engine
|
|
233
283
|
## lists them from Object down, which puts a Button's pressed after thirty a Control has, and names
|
|
234
284
|
## starting with an underscore are the engine's own.
|