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
|
@@ -78,12 +78,16 @@ func set_property(params: Dictionary) -> Dictionary:
|
|
|
78
78
|
var unwritable: String = GdhPaths.unwritable_copy(chain)
|
|
79
79
|
if not unwritable.is_empty():
|
|
80
80
|
return {"type": "error", "message": unwritable}
|
|
81
|
-
# The write alone, setter included, which is where a property's cost is.
|
|
81
|
+
# The write alone, setter included, which is where a property's cost is. Listened to as a call
|
|
82
|
+
# is, since a setter refusing a value says why only in what it reports.
|
|
83
|
+
var caught: GdhCallErrors = GdhCallErrors.new()
|
|
84
|
+
OS.add_logger(caught)
|
|
82
85
|
var began: int = Time.get_ticks_usec()
|
|
83
86
|
var written: Variant = GdhPaths.write(holder, named, given)
|
|
84
87
|
if copied:
|
|
85
88
|
GdhPaths.write_back(chain, written)
|
|
86
89
|
var elapsed: int = Time.get_ticks_usec() - began
|
|
90
|
+
OS.remove_logger(caught)
|
|
87
91
|
var now: Variant = GdhPaths.read_under(holder, named)
|
|
88
92
|
if copied:
|
|
89
93
|
var again: Dictionary = GdhPaths.walk_to(target, node_path, property)
|
|
@@ -122,20 +126,20 @@ func set_property(params: Dictionary) -> Dictionary:
|
|
|
122
126
|
)
|
|
123
127
|
% [named, str(slot["class"]), GdhValues.class_of(refused_object)]
|
|
124
128
|
)
|
|
125
|
-
|
|
126
|
-
"
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
)
|
|
138
|
-
}
|
|
129
|
+
var refused: String = (
|
|
130
|
+
"%s.%s was given %s and reads %s afterwards, as it did before: %s."
|
|
131
|
+
% [
|
|
132
|
+
reached["called"],
|
|
133
|
+
named,
|
|
134
|
+
JSON.stringify(_values.serialize(given), "", false),
|
|
135
|
+
JSON.stringify(_values.serialize(now), "", false),
|
|
136
|
+
why,
|
|
137
|
+
]
|
|
138
|
+
)
|
|
139
|
+
if not caught.raised.is_empty():
|
|
140
|
+
var reported: PackedStringArray = PackedStringArray(caught.raised.slice(0, ERRORS_KEPT))
|
|
141
|
+
refused += " While it was written the game reported: %s" % "; ".join(reported)
|
|
142
|
+
return {"type": "error", "message": refused}
|
|
139
143
|
|
|
140
144
|
var answer: Dictionary = {
|
|
141
145
|
"type": "property_set",
|
|
@@ -147,6 +151,8 @@ func set_property(params: Dictionary) -> Dictionary:
|
|
|
147
151
|
}
|
|
148
152
|
if already and unmoved:
|
|
149
153
|
answer["unchanged"] = true
|
|
154
|
+
_keep_reported(answer, "errors", caught.raised)
|
|
155
|
+
_keep_reported(answer, "warnings", caught.warned)
|
|
150
156
|
# The engine stores a number past an export range, or one no enum value has, as given; only the
|
|
151
157
|
# inspector holds a property to them, so the game now holds a value its own script never expected.
|
|
152
158
|
if holder is Object:
|
|
@@ -169,6 +175,33 @@ func _argument_for(
|
|
|
169
175
|
var holder: Object = reached["holder"]
|
|
170
176
|
var called: String = reached["called"]
|
|
171
177
|
var wants: int = _values.parameter_type(holder, named, index)
|
|
178
|
+
var choices: Dictionary = _values.parameter_enum(holder, named, index)
|
|
179
|
+
var values: Dictionary = choices.get("values", {})
|
|
180
|
+
if not choices.is_empty() and sent is String and not str(sent).strip_edges().is_valid_int():
|
|
181
|
+
var chosen: Variant = GdhDeclared.enum_named(values, str(sent))
|
|
182
|
+
if chosen == null:
|
|
183
|
+
return {
|
|
184
|
+
"type": "error",
|
|
185
|
+
"message":
|
|
186
|
+
GdhDeclared.enum_argument_refusal(
|
|
187
|
+
"%s.%s" % [called, named], index, str(choices["enum"]), str(sent), values
|
|
188
|
+
)
|
|
189
|
+
}
|
|
190
|
+
return {"value": chosen}
|
|
191
|
+
# A packed list's elements are fitted one by one, and one that cannot become what the list holds
|
|
192
|
+
# is named: converted whole, a word or a fraction among them became 0 or was cut, and the call ran.
|
|
193
|
+
var packed: Variant = GdhValues.packed_template(wants)
|
|
194
|
+
if packed != null and sent is Array:
|
|
195
|
+
var elements: Dictionary = _values.typed_like(sent, packed, _element_object.bind(node, node_path))
|
|
196
|
+
if elements.has("message"):
|
|
197
|
+
return {
|
|
198
|
+
"type": "error",
|
|
199
|
+
"message":
|
|
200
|
+
(
|
|
201
|
+
"%s.%s takes %s as argument %d: %s"
|
|
202
|
+
% [called, named, type_string(wants), index + 1, elements["message"]]
|
|
203
|
+
)
|
|
204
|
+
}
|
|
172
205
|
if GdhValues.cuts(sent, wants):
|
|
173
206
|
return {
|
|
174
207
|
"type": "error",
|
|
@@ -259,6 +292,14 @@ func _value_for(
|
|
|
259
292
|
return {"value": by_name[1]}
|
|
260
293
|
if not str(by_name[1]).is_empty():
|
|
261
294
|
return {"type": "error", "message": by_name[1]}
|
|
295
|
+
var packed: Variant = GdhValues.packed_template(wanted)
|
|
296
|
+
if packed != null and value is Array:
|
|
297
|
+
var elements: Dictionary = _values.typed_like(value, packed, _element_object.bind(node, node_path))
|
|
298
|
+
if elements.has("message"):
|
|
299
|
+
return {
|
|
300
|
+
"type": "error",
|
|
301
|
+
"message": "%s.%s holds %s: %s" % [called, named, type_string(wanted), elements["message"]]
|
|
302
|
+
}
|
|
262
303
|
var holds_objects: bool = (
|
|
263
304
|
declared == TYPE_OBJECT or (declared == TYPE_NIL and (old_value == null or old_value is Object))
|
|
264
305
|
)
|
|
@@ -335,6 +376,52 @@ static func held_already(given: Variant, held: Variant) -> bool:
|
|
|
335
376
|
return PackedFloat32Array([given])[0] == PackedFloat32Array([held])[0]
|
|
336
377
|
|
|
337
378
|
|
|
379
|
+
## What to do instead of the call [param method] makes on [param sort], the list or map [param
|
|
380
|
+
## reached] ends on, which a path does not make. A method of the list itself is the likelier aim
|
|
381
|
+
## than one of what it holds when the list has it: told to call append() on an element, a caller
|
|
382
|
+
## adding to a list has nowhere to go.
|
|
383
|
+
static func _instead_of_a_call(reached: Dictionary, method: String, sort: String) -> String:
|
|
384
|
+
var own: String = GdhPaths.method_of(str(reached["name"]))
|
|
385
|
+
if own.is_empty():
|
|
386
|
+
own = str(reached["name"])
|
|
387
|
+
var steps: PackedStringArray = GdhPaths.steps_of(method)
|
|
388
|
+
var _shortened: int = steps.resize(steps.size() - 1)
|
|
389
|
+
var holding: String = ":".join(steps)
|
|
390
|
+
var through_a_call: bool = false
|
|
391
|
+
for step: String in steps:
|
|
392
|
+
through_a_call = through_a_call or not GdhPaths.method_of(step).is_empty()
|
|
393
|
+
var the: String = "the " + sort.trim_prefix("a ")
|
|
394
|
+
match GdhPaths.container_method(reached["holder"], own):
|
|
395
|
+
"changes":
|
|
396
|
+
if through_a_call:
|
|
397
|
+
return (
|
|
398
|
+
(
|
|
399
|
+
"%s() changes %s: to change it, call a method of the game that changes it, since %s"
|
|
400
|
+
+ " is returned by a call, and runtime_invoke set does not write into what a call returns"
|
|
401
|
+
)
|
|
402
|
+
% [own, the, reached["called"]]
|
|
403
|
+
)
|
|
404
|
+
return (
|
|
405
|
+
(
|
|
406
|
+
"%s() changes %s: to change it, give %s its new value whole with runtime_invoke set,"
|
|
407
|
+
+ " property %s, or call a method of the game that changes it"
|
|
408
|
+
)
|
|
409
|
+
% [own, the, reached["called"], holding]
|
|
410
|
+
)
|
|
411
|
+
"reads":
|
|
412
|
+
return (
|
|
413
|
+
(
|
|
414
|
+
"To look in %s with %s(), read %s whole with runtime_inspect property, property %s, and"
|
|
415
|
+
+ " look in what comes back"
|
|
416
|
+
)
|
|
417
|
+
% [the, own, reached["called"], holding]
|
|
418
|
+
)
|
|
419
|
+
return (
|
|
420
|
+
"A method of what %s holds is called on the element, as in %s:0:%s"
|
|
421
|
+
% [sort, reached["called"], reached["name"]]
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
|
|
338
425
|
func call_method(params: Dictionary) -> Dictionary:
|
|
339
426
|
var node_path: String = str(params.get("path", ""))
|
|
340
427
|
var method: String = str(params.get("method", ""))
|
|
@@ -343,7 +430,33 @@ func call_method(params: Dictionary) -> Dictionary:
|
|
|
343
430
|
if node_path.is_empty() or method.is_empty():
|
|
344
431
|
return {"type": "error", "message": "Node path and method required"}
|
|
345
432
|
|
|
346
|
-
var
|
|
433
|
+
var root: Node = _host.get_tree().root
|
|
434
|
+
var standing: Dictionary = GdhValues.class_at(root, node_path)
|
|
435
|
+
if standing.is_empty():
|
|
436
|
+
standing = GdhValues.target_at(root, node_path)
|
|
437
|
+
if standing.has("message") and node_path.is_valid_ascii_identifier():
|
|
438
|
+
var classes: Array[String] = []
|
|
439
|
+
for declared: Dictionary in ProjectSettings.get_global_class_list():
|
|
440
|
+
classes.append(str(declared.get("class", "")))
|
|
441
|
+
var close: PackedStringArray = GdhRead.nearest(node_path, PackedStringArray(classes))
|
|
442
|
+
standing["message"] = (
|
|
443
|
+
(
|
|
444
|
+
"%s. No global class is called %s either%s; a static function is called with its"
|
|
445
|
+
+ " class's name or its script's res:// path as the path."
|
|
446
|
+
)
|
|
447
|
+
% [
|
|
448
|
+
str(standing["message"]).trim_suffix("."),
|
|
449
|
+
node_path,
|
|
450
|
+
(
|
|
451
|
+
""
|
|
452
|
+
if close.is_empty()
|
|
453
|
+
else (
|
|
454
|
+
", and the nearest %s %s"
|
|
455
|
+
% ["is" if close.size() == 1 else "are", ", ".join(close)]
|
|
456
|
+
)
|
|
457
|
+
),
|
|
458
|
+
]
|
|
459
|
+
)
|
|
347
460
|
if standing.has("message"):
|
|
348
461
|
return standing
|
|
349
462
|
var node: Node = standing["node"]
|
|
@@ -368,13 +481,24 @@ func call_method(params: Dictionary) -> Dictionary:
|
|
|
368
481
|
var spelled: String = str(reached["name"])
|
|
369
482
|
if GdhPaths.method_of(spelled).is_empty():
|
|
370
483
|
spelled += "()"
|
|
371
|
-
# A vector or a transform has no element to call a method on
|
|
372
|
-
|
|
484
|
+
# A vector or a transform has no element to call a method on, so a call it does not answer is
|
|
485
|
+
# refused without the advice about elements below.
|
|
486
|
+
var answers: bool = GdhPaths.can_read(reached["holder"], spelled)
|
|
487
|
+
var called_on: Variant = reached["holder"]
|
|
488
|
+
if (
|
|
489
|
+
(GdhPaths.composite(called_on) or GdhPaths.textual(called_on))
|
|
490
|
+
and not (args.is_empty() and answers)
|
|
491
|
+
):
|
|
373
492
|
return {
|
|
374
493
|
"type": "error",
|
|
375
|
-
"message":
|
|
494
|
+
"message":
|
|
495
|
+
(
|
|
496
|
+
"%s:%s takes no arguments" % [reached["called"], spelled]
|
|
497
|
+
if answers
|
|
498
|
+
else GdhPaths.nothing_there(reached["holder"], spelled, str(reached["called"]))
|
|
499
|
+
)
|
|
376
500
|
}
|
|
377
|
-
if args.is_empty() and
|
|
501
|
+
if args.is_empty() and answers:
|
|
378
502
|
var read_from: int = Time.get_ticks_usec()
|
|
379
503
|
var answered: Variant = GdhPaths.read_under(reached["holder"], spelled)
|
|
380
504
|
var read_for: int = Time.get_ticks_usec() - read_from
|
|
@@ -394,20 +518,19 @@ func call_method(params: Dictionary) -> Dictionary:
|
|
|
394
518
|
if GdhPaths.can_read(reached["holder"], spelled)
|
|
395
519
|
else GdhPaths.nothing_there(reached["holder"], spelled, str(reached["called"]))
|
|
396
520
|
)
|
|
397
|
-
return {
|
|
398
|
-
"type": "error",
|
|
399
|
-
"message":
|
|
400
|
-
(
|
|
401
|
-
"%s. A method of what %s holds is called on the element, as in %s:0:%s"
|
|
402
|
-
% [why, sort, reached["called"], reached["name"]]
|
|
403
|
-
)
|
|
404
|
-
}
|
|
521
|
+
return {"type": "error", "message": "%s. %s" % [why, _instead_of_a_call(reached, method, sort)]}
|
|
405
522
|
var holder: Object = reached["holder"]
|
|
406
523
|
var named: String = reached["name"]
|
|
407
524
|
# The method a call ends on is the one being called whether or not it carries the brackets a
|
|
408
525
|
# step along the way would: "get_viewport():gui_get_focus_owner()" is the same call as without
|
|
409
526
|
# the last pair.
|
|
410
|
-
var not_called: String =
|
|
527
|
+
var not_called: String = _instance_method(
|
|
528
|
+
holder,
|
|
529
|
+
named if GdhPaths.method_of(named).is_empty() else GdhPaths.method_of(named),
|
|
530
|
+
str(reached["called"])
|
|
531
|
+
)
|
|
532
|
+
if not_called.is_empty():
|
|
533
|
+
not_called = _not_a_method_to_call(holder, named, str(reached["called"]))
|
|
411
534
|
if not not_called.is_empty():
|
|
412
535
|
return {"type": "error", "message": not_called}
|
|
413
536
|
if not GdhPaths.method_of(named).is_empty():
|
|
@@ -424,6 +547,9 @@ func call_method(params: Dictionary) -> Dictionary:
|
|
|
424
547
|
# The object arguments checked only against the engine class, by position from 1, for the refusal
|
|
425
548
|
# the engine may still make: see [method GdhValues.slot_declared].
|
|
426
549
|
var by_engine_class: Dictionary = {}
|
|
550
|
+
# The enum arguments given a number none of their values has, said beside the answer: the engine
|
|
551
|
+
# passes any number on, and a write of the same number to a property already said so.
|
|
552
|
+
var outside: PackedStringArray = []
|
|
427
553
|
for index: int in args.size():
|
|
428
554
|
var wants: int = _values.parameter_type(holder, named, index)
|
|
429
555
|
if wants == TYPE_OBJECT and (args[index] is String or args[index] is Dictionary):
|
|
@@ -444,6 +570,14 @@ func call_method(params: Dictionary) -> Dictionary:
|
|
|
444
570
|
if argument.has("message"):
|
|
445
571
|
return argument
|
|
446
572
|
deserialized_args.append(argument["value"])
|
|
573
|
+
var choices: Dictionary = _values.parameter_enum(holder, named, index)
|
|
574
|
+
if not choices.is_empty():
|
|
575
|
+
var values: Dictionary = choices["values"]
|
|
576
|
+
var stray: String = GdhDeclared.outside_enum_argument(
|
|
577
|
+
index, str(choices["enum"]), argument["value"], values
|
|
578
|
+
)
|
|
579
|
+
if not stray.is_empty():
|
|
580
|
+
var _noted: bool = outside.append(stray)
|
|
447
581
|
|
|
448
582
|
# Timed around the call alone, since the bridge's round trip is a second or more and swamps what
|
|
449
583
|
# one click's work costs; the frame metrics read whichever frame is last, which is not this one.
|
|
@@ -491,6 +625,8 @@ func call_method(params: Dictionary) -> Dictionary:
|
|
|
491
625
|
}
|
|
492
626
|
if awaited:
|
|
493
627
|
answer["awaited"] = true
|
|
628
|
+
if not outside.is_empty():
|
|
629
|
+
answer["outside_declared"] = " ".join(outside)
|
|
494
630
|
_keep_reported(answer, "errors", caught.raised)
|
|
495
631
|
_keep_reported(answer, "warnings", caught.warned)
|
|
496
632
|
# Main.explode, ended at a null it called into, answered result 0 as if it had counted something.
|
|
@@ -532,6 +668,25 @@ static func _not_a_method_to_call(holder: Object, named: String, called: String)
|
|
|
532
668
|
return GdhPaths.with_methods_offered("%s has no method %s" % [called, method], holder, method)
|
|
533
669
|
|
|
534
670
|
|
|
671
|
+
## Why [param method] cannot be called on [param holder], a script named by its class or its path,
|
|
672
|
+
## or "": a function that is not static runs on an instance of the class, which the script is not.
|
|
673
|
+
static func _instance_method(holder: Object, method: String, called: String) -> String:
|
|
674
|
+
if not holder is Script:
|
|
675
|
+
return ""
|
|
676
|
+
var script: Script = holder
|
|
677
|
+
for entry: Dictionary in script.get_script_method_list():
|
|
678
|
+
if str(entry.get("name", "")) != method or GdhRead.as_int(entry.get("flags", 0)) & METHOD_FLAG_STATIC:
|
|
679
|
+
continue
|
|
680
|
+
return (
|
|
681
|
+
(
|
|
682
|
+
"%s.%s is not static, so it runs on an instance of the class: call it on a node carrying %s,"
|
|
683
|
+
+ " or on an object a path from one reaches"
|
|
684
|
+
)
|
|
685
|
+
% [called, method, script.resource_path]
|
|
686
|
+
)
|
|
687
|
+
return ""
|
|
688
|
+
|
|
689
|
+
|
|
535
690
|
## Whether a call's `properties` asks for its result to be read: true, or a list of names.
|
|
536
691
|
static func _reads_the_result(asked: Variant) -> bool:
|
|
537
692
|
return asked is Array or (asked is bool and asked)
|
|
@@ -613,7 +768,7 @@ static func _argument_count_refused(holder: Object, method: String, given: int)
|
|
|
613
768
|
# of the game's frame; a call giving exactly the arguments the method declares needs no walk.
|
|
614
769
|
if given == holder.get_method_argument_count(method):
|
|
615
770
|
return ""
|
|
616
|
-
for entry: Dictionary in
|
|
771
|
+
for entry: Dictionary in GdhValues.methods_of(holder):
|
|
617
772
|
if str(entry.get("name", "")) != method:
|
|
618
773
|
continue
|
|
619
774
|
var parameters: Array = entry.get("args", [])
|
|
@@ -148,7 +148,7 @@ func inject_action(params: Dictionary) -> Dictionary:
|
|
|
148
148
|
return {"type": "error", "message": holding}
|
|
149
149
|
var frames: int = holding
|
|
150
150
|
|
|
151
|
-
var focused: String =
|
|
151
|
+
var focused: String = GdhValues.focus_path(_host)
|
|
152
152
|
_say_action(action, whole or held, strength)
|
|
153
153
|
var shortcuts: Dictionary = {"shortcuts": [], "shortcut_pressed": null}
|
|
154
154
|
if whole or held:
|
|
@@ -159,38 +159,21 @@ func inject_action(params: Dictionary) -> Dictionary:
|
|
|
159
159
|
# a listener that acts on the release both get their own frame to do it in.
|
|
160
160
|
lasted = await GdhValues.hold(_host.get_tree(), frames)
|
|
161
161
|
_say_action(action, false, strength)
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
return _focus_noted(
|
|
165
|
-
{
|
|
166
|
-
"type": "input_injected",
|
|
167
|
-
"input_type": "action",
|
|
168
|
-
"action": action,
|
|
169
|
-
"shortcuts": shortcuts["shortcuts"],
|
|
170
|
-
"shortcut_pressed": shortcuts["shortcut_pressed"],
|
|
171
|
-
"pressed": held and not whole,
|
|
172
|
-
"whole": whole,
|
|
173
|
-
"held_frames": GdhValues.held_answer(whole, frames),
|
|
174
|
-
"held_for": lasted,
|
|
175
|
-
},
|
|
176
|
-
focused
|
|
177
|
-
)
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
## The path of the Control holding the keyboard focus, or "" when none does. Read before a key or an
|
|
181
|
-
## action goes in, since pressing a focused button can move the focus.
|
|
182
|
-
func _focus_owner() -> String:
|
|
183
|
-
var holding: Control = _host.get_viewport().gui_get_focus_owner()
|
|
184
|
-
return "" if holding == null else str(holding.get_path())
|
|
185
|
-
|
|
162
|
+
# The engine hands a parsed event on at the next frame, so the focus is read after one.
|
|
163
|
+
await _host.get_tree().process_frame
|
|
186
164
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
165
|
+
var answer: Dictionary = {
|
|
166
|
+
"type": "input_injected",
|
|
167
|
+
"input_type": "action",
|
|
168
|
+
"action": action,
|
|
169
|
+
"shortcuts": shortcuts["shortcuts"],
|
|
170
|
+
"shortcut_pressed": shortcuts["shortcut_pressed"],
|
|
171
|
+
"pressed": held and not whole,
|
|
172
|
+
"whole": whole,
|
|
173
|
+
"held_frames": GdhValues.held_answer(whole, frames),
|
|
174
|
+
"held_for": lasted,
|
|
175
|
+
}
|
|
176
|
+
return GdhValues.focus_noted(answer, focused, _host)
|
|
194
177
|
|
|
195
178
|
|
|
196
179
|
func _say_action(action: String, down: bool, strength: float) -> void:
|
|
@@ -246,7 +229,7 @@ func inject_key(params: Dictionary) -> Dictionary:
|
|
|
246
229
|
# keys are the code points themselves, which is what makes this a mapping and not a table.
|
|
247
230
|
event.unicode = GdhValues.glyph_of(event.keycode, event.shift_pressed)
|
|
248
231
|
|
|
249
|
-
var focused: String =
|
|
232
|
+
var focused: String = GdhValues.focus_path(_host)
|
|
250
233
|
Input.parse_input_event(event)
|
|
251
234
|
var lasted: Variant = null
|
|
252
235
|
if whole:
|
|
@@ -254,26 +237,24 @@ func inject_key(params: Dictionary) -> Dictionary:
|
|
|
254
237
|
var up: InputEventKey = event.duplicate()
|
|
255
238
|
up.pressed = false
|
|
256
239
|
Input.parse_input_event(up)
|
|
257
|
-
|
|
240
|
+
await _host.get_tree().process_frame
|
|
258
241
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
focused
|
|
276
|
-
)
|
|
242
|
+
var answer: Dictionary = {
|
|
243
|
+
"type": "input_injected",
|
|
244
|
+
"input_type": "key",
|
|
245
|
+
"held_frames": GdhValues.held_answer(whole, frames),
|
|
246
|
+
"held_for": lasted,
|
|
247
|
+
"keycode": event.keycode,
|
|
248
|
+
"physical_keycode": event.physical_keycode,
|
|
249
|
+
"shift": event.shift_pressed,
|
|
250
|
+
"ctrl": event.ctrl_pressed,
|
|
251
|
+
"alt": event.alt_pressed,
|
|
252
|
+
"unicode": event.unicode,
|
|
253
|
+
"actions": GdhValues.actions_of(event),
|
|
254
|
+
"pressed": held and not whole,
|
|
255
|
+
"whole": whole,
|
|
256
|
+
}
|
|
257
|
+
return GdhValues.focus_noted(answer, focused, _host)
|
|
277
258
|
|
|
278
259
|
|
|
279
260
|
## Clicks a mouse button at a position, and lets go of it again unless asked to hold it.
|
|
@@ -314,16 +295,22 @@ func inject_mouse_click(params: Dictionary) -> Dictionary:
|
|
|
314
295
|
# its drag from the press, which is where a real pointer would be.
|
|
315
296
|
var _moved: Vector2 = _arrive(position)
|
|
316
297
|
var supplied: bool = _follow_in_the_window(position)
|
|
317
|
-
|
|
298
|
+
# A double click's first click goes whole before it, as [method _click_button] sends one.
|
|
299
|
+
if double and held and not notch:
|
|
300
|
+
Input.parse_input_event(GdhMouse.button_event(position, button, true, false))
|
|
301
|
+
await _host.get_tree().process_frame
|
|
302
|
+
Input.parse_input_event(GdhMouse.button_event(position, button, false, false))
|
|
303
|
+
await _host.get_tree().process_frame
|
|
304
|
+
var press: InputEventMouseButton = GdhMouse.button_event(position, button, whole or held, double)
|
|
318
305
|
Input.parse_input_event(press)
|
|
319
306
|
var lasted: Variant = null
|
|
320
307
|
if notch:
|
|
321
|
-
Input.parse_input_event(
|
|
308
|
+
Input.parse_input_event(GdhMouse.button_event(position, button, false, false))
|
|
322
309
|
elif whole:
|
|
323
310
|
# A frame between the halves at least, as inject_action has, so a control that acts on the
|
|
324
311
|
# press and one that acts on the release each get a frame to do it in.
|
|
325
312
|
lasted = await GdhValues.hold(_host.get_tree(), frames)
|
|
326
|
-
Input.parse_input_event(
|
|
313
|
+
Input.parse_input_event(GdhMouse.button_event(position, button, false, false))
|
|
327
314
|
# Input hands the events on at the next frame, so the control under the pointer is read after it.
|
|
328
315
|
await _host.get_tree().process_frame
|
|
329
316
|
var hovered: Control = _hover.hovered_in(_host.get_viewport())
|
|
@@ -424,7 +411,7 @@ func inject_mouse_drag(params: Dictionary) -> Dictionary:
|
|
|
424
411
|
var end: Vector2 = points[points.size() - 1]
|
|
425
412
|
var _moved: Vector2 = _arrive(start)
|
|
426
413
|
var supplied: bool = _follow_in_the_window(start)
|
|
427
|
-
Input.parse_input_event(
|
|
414
|
+
Input.parse_input_event(GdhMouse.button_event(start, button, true, false))
|
|
428
415
|
var travelled: Vector2 = Vector2.ZERO
|
|
429
416
|
for step: int in range(1, frames + 1):
|
|
430
417
|
await _host.get_tree().process_frame
|
|
@@ -434,7 +421,7 @@ func inject_mouse_drag(params: Dictionary) -> Dictionary:
|
|
|
434
421
|
supplied = _follow_in_the_window(at) or supplied
|
|
435
422
|
Input.parse_input_event(_motion(at, relative))
|
|
436
423
|
await _host.get_tree().process_frame
|
|
437
|
-
Input.parse_input_event(
|
|
424
|
+
Input.parse_input_event(GdhMouse.button_event(end, button, false, false))
|
|
438
425
|
await _host.get_tree().process_frame
|
|
439
426
|
|
|
440
427
|
var said: Dictionary = _pointer_noted(
|
|
@@ -555,6 +542,7 @@ func click(params: Dictionary) -> Dictionary:
|
|
|
555
542
|
# other words, a link among them. Said either way, so a press on the middle is never read as one
|
|
556
543
|
# on the words.
|
|
557
544
|
var aim: Vector2 = control.size * 0.5
|
|
545
|
+
var at_centre: bool = true
|
|
558
546
|
var item_scrolled: bool = false
|
|
559
547
|
if item != null:
|
|
560
548
|
var found_by_words: Dictionary = found
|
|
@@ -564,6 +552,7 @@ func click(params: Dictionary) -> Dictionary:
|
|
|
564
552
|
if placed_item.has("message"):
|
|
565
553
|
return placed_item
|
|
566
554
|
aim = placed_item["point"]
|
|
555
|
+
at_centre = false
|
|
567
556
|
item_scrolled = placed_item["scrolled"]
|
|
568
557
|
var rich: RichTextLabel = control as RichTextLabel
|
|
569
558
|
if found != null and rich != null:
|
|
@@ -571,6 +560,7 @@ func click(params: Dictionary) -> Dictionary:
|
|
|
571
560
|
var found_here: Dictionary = found
|
|
572
561
|
if placed.has("point"):
|
|
573
562
|
aim = placed["point"]
|
|
563
|
+
at_centre = false
|
|
574
564
|
found_here["pressedOn"] = "the words"
|
|
575
565
|
elif placed.has("unplaced"):
|
|
576
566
|
found_here["pressedOn"] = "the middle of the label"
|
|
@@ -607,6 +597,20 @@ func click(params: Dictionary) -> Dictionary:
|
|
|
607
597
|
}
|
|
608
598
|
local = control.get_global_transform_with_canvas() * aim
|
|
609
599
|
|
|
600
|
+
# A control taller or wider than what shows it has its centre out of sight at every scroll, and
|
|
601
|
+
# a player clicks the part they can see. Refused, it was called a control nothing could click.
|
|
602
|
+
var on_part: bool = false
|
|
603
|
+
if at_centre and not _in_sight(control, own, local):
|
|
604
|
+
var part: Variant = GdhScreen.shown_part(control)
|
|
605
|
+
if part is Rect2:
|
|
606
|
+
var seen: Rect2 = part
|
|
607
|
+
var middle: Vector2 = seen.get_center()
|
|
608
|
+
# The box of a turned control is wider than the control, so its middle may be off it.
|
|
609
|
+
var back: Vector2 = control.get_global_transform_with_canvas().affine_inverse() * middle
|
|
610
|
+
if seen.has_area() and Rect2(Vector2.ZERO, control.size).has_point(back):
|
|
611
|
+
local = middle
|
|
612
|
+
on_part = true
|
|
613
|
+
|
|
610
614
|
var reached: Dictionary = GdhScreen.reach(own, local)
|
|
611
615
|
var viewport: Viewport = reached["viewport"]
|
|
612
616
|
var centre: Vector2 = reached["point"]
|
|
@@ -643,6 +647,8 @@ func click(params: Dictionary) -> Dictionary:
|
|
|
643
647
|
if hovered != null:
|
|
644
648
|
hovered_path = str(hovered.get_path())
|
|
645
649
|
var landed: bool = hovered == control or (hovered != null and control.is_ancestor_of(hovered))
|
|
650
|
+
# Asked only of a miss, before the press restacks windows: hovered missed a window over it.
|
|
651
|
+
var cover: Node = null if landed else GdhTargets.drawn_over(control)
|
|
646
652
|
|
|
647
653
|
# The link a rich label says was pressed, which is its own word for what the press reached.
|
|
648
654
|
var links: Array = []
|
|
@@ -658,10 +664,7 @@ func click(params: Dictionary) -> Dictionary:
|
|
|
658
664
|
var was_pressed: Variant = null
|
|
659
665
|
if toggle != null and toggle.toggle_mode:
|
|
660
666
|
was_pressed = toggle.button_pressed
|
|
661
|
-
var press: InputEventMouseButton =
|
|
662
|
-
_press_button(viewport, press)
|
|
663
|
-
await _host.get_tree().process_frame
|
|
664
|
-
_press_button(viewport, _button(position, button, false, false))
|
|
667
|
+
var press: InputEventMouseButton = await _click_button(viewport, position, button, double)
|
|
665
668
|
# The release is what a button acts on, and a queue_free it causes lands at the end of
|
|
666
669
|
# this frame; the frame passes so the answer describes the control as the click left it.
|
|
667
670
|
await _host.get_tree().process_frame
|
|
@@ -685,12 +688,16 @@ func click(params: Dictionary) -> Dictionary:
|
|
|
685
688
|
"control_afterwards": afterwards,
|
|
686
689
|
"scrolled_into_view": scrolled,
|
|
687
690
|
}
|
|
691
|
+
if on_part:
|
|
692
|
+
answer["pressed_on"] = "the middle of the part on screen, since no scroll brings its centre into view"
|
|
688
693
|
# A Label and a TextureRect ignore the pointer unless told otherwise, so the click goes to what is
|
|
689
694
|
# under them, and landed false with that control as hovered said nothing of why.
|
|
690
695
|
if not landed and control.mouse_filter == Control.MOUSE_FILTER_IGNORE:
|
|
691
696
|
answer["why_not_landed"] = (
|
|
692
697
|
"%s's mouse_filter is ignore, so the pointer passes through it to what is under it" % node_path
|
|
693
698
|
)
|
|
699
|
+
elif cover != null:
|
|
700
|
+
answer["why_not_landed"] = "%s is drawn over it at the point clicked" % cover.get_path()
|
|
694
701
|
if found != null:
|
|
695
702
|
answer["found"] = found
|
|
696
703
|
if was_pressed != null:
|
|
@@ -795,6 +802,23 @@ func _no_window_note(viewport: Viewport) -> String:
|
|
|
795
802
|
##
|
|
796
803
|
## Any other viewport is pushed into directly: Input reaches only the game's window. That is a
|
|
797
804
|
## viewport with no place on the screen, since `GdhScreen.reach` carries every other one there.
|
|
805
|
+
## A press and its release a frame apart, answering with the press. A double click arrives as the
|
|
806
|
+
## engine gets one from the system: a whole click, then a second press marked as a double click and
|
|
807
|
+
## its release. Sent as the marked press alone, it pressed a button once where a player's presses it
|
|
808
|
+
## twice.
|
|
809
|
+
func _click_button(viewport: Viewport, position: Vector2, button: int, double: bool) -> InputEventMouseButton:
|
|
810
|
+
if double:
|
|
811
|
+
_press_button(viewport, GdhMouse.button_event(position, button, true, false))
|
|
812
|
+
await _host.get_tree().process_frame
|
|
813
|
+
_press_button(viewport, GdhMouse.button_event(position, button, false, false))
|
|
814
|
+
await _host.get_tree().process_frame
|
|
815
|
+
var press: InputEventMouseButton = GdhMouse.button_event(position, button, true, double)
|
|
816
|
+
_press_button(viewport, press)
|
|
817
|
+
await _host.get_tree().process_frame
|
|
818
|
+
_press_button(viewport, GdhMouse.button_event(position, button, false, false))
|
|
819
|
+
return press
|
|
820
|
+
|
|
821
|
+
|
|
798
822
|
func _press_button(viewport: Viewport, event: InputEventMouseButton) -> void:
|
|
799
823
|
if viewport == _host.get_tree().root:
|
|
800
824
|
Input.parse_input_event(event)
|
|
@@ -877,9 +901,7 @@ func _click_in_the_world(node_path: String, item: Node3D, params: Dictionary) ->
|
|
|
877
901
|
var through: Dictionary = _through_the_interface(out)
|
|
878
902
|
var hovered_path: Variant = through["hovered"]
|
|
879
903
|
|
|
880
|
-
|
|
881
|
-
await _host.get_tree().process_frame
|
|
882
|
-
_press_button(arriving, _button(position, button, false, false))
|
|
904
|
+
var _press: InputEventMouseButton = await _click_button(arriving, position, button, double)
|
|
883
905
|
await _host.get_tree().process_frame
|
|
884
906
|
|
|
885
907
|
return _pointer_noted(
|
|
@@ -975,18 +997,3 @@ func _motion(position: Vector2, relative: Vector2) -> InputEventMouseMotion:
|
|
|
975
997
|
event.screen_velocity = event.velocity
|
|
976
998
|
event.button_mask = Input.get_mouse_button_mask()
|
|
977
999
|
return event
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
## A button event carries the buttons held once it has happened: the pressed one among them, the
|
|
981
|
-
## released one out. A wheel step is a button with no mask, as it is from a real mouse.
|
|
982
|
-
func _button(position: Vector2, button: int, pressed: bool, double: bool) -> InputEventMouseButton:
|
|
983
|
-
var event: InputEventMouseButton = InputEventMouseButton.new()
|
|
984
|
-
event.position = position
|
|
985
|
-
event.global_position = position
|
|
986
|
-
event.button_index = button as MouseButton
|
|
987
|
-
event.pressed = pressed
|
|
988
|
-
event.double_click = double
|
|
989
|
-
var held: int = Input.get_mouse_button_mask()
|
|
990
|
-
var own: int = GdhMouse.mask_of(button)
|
|
991
|
-
event.button_mask = (held | own) if pressed else (held & ~own)
|
|
992
|
-
return event
|
|
@@ -65,6 +65,23 @@ static func mask_of(button: int) -> int:
|
|
|
65
65
|
return 1 << (button - 1)
|
|
66
66
|
|
|
67
67
|
|
|
68
|
+
## A button event carries the buttons held once it has happened: the pressed one among them, the
|
|
69
|
+
## released one out. A wheel step is a button with no mask, as it is from a real mouse.
|
|
70
|
+
static func button_event(
|
|
71
|
+
position: Vector2, button: int, pressed: bool, double: bool
|
|
72
|
+
) -> InputEventMouseButton:
|
|
73
|
+
var event: InputEventMouseButton = InputEventMouseButton.new()
|
|
74
|
+
event.position = position
|
|
75
|
+
event.global_position = position
|
|
76
|
+
event.button_index = button as MouseButton
|
|
77
|
+
event.pressed = pressed
|
|
78
|
+
event.double_click = double
|
|
79
|
+
var held: int = Input.get_mouse_button_mask()
|
|
80
|
+
var own: int = mask_of(button)
|
|
81
|
+
event.button_mask = (held | own) if pressed else (held & ~own)
|
|
82
|
+
return event
|
|
83
|
+
|
|
84
|
+
|
|
68
85
|
## [param answer], saying so when one of [param points] lies outside [param viewport]: the GUI picks
|
|
69
86
|
## what is under the pointer only inside it, so such a press or motion reaches no control, and its
|
|
70
87
|
## answer read the same as one that landed.
|