gdharness 1.3.8 → 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 +4668 -3229
- 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 +115 -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 +112 -6
- package/build/godot/addons/gdharness_editor/scene_file.gd +117 -174
- 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 +19 -29
- 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 +39 -6
- package/build/godot/addons/gdharness_editor/tools/animation_states.gd +263 -0
- package/build/godot/addons/gdharness_editor/tools/animation_states.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/tools/animation_tools.gd +49 -414
- 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 +162 -483
- package/build/godot/addons/gdharness_editor/tools/scene_groups.gd +16 -0
- package/build/godot/addons/gdharness_editor/tools/scene_kin.gd +8 -4
- package/build/godot/addons/gdharness_editor/tools/scene_paths.gd +2 -1
- package/build/godot/addons/gdharness_editor/tools/scene_placing.gd +10 -0
- package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +151 -8
- package/build/godot/addons/gdharness_editor/tools/settings_tools.gd +7 -30
- package/build/godot/addons/gdharness_editor/tools/signal_tools.gd +190 -38
- 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/call_errors.gd +8 -2
- package/build/godot/addons/gdharness_runtime/declared_values.gd +272 -0
- package/build/godot/addons/gdharness_runtime/reading.gd +112 -6
- package/build/godot/addons/gdharness_runtime/runtime_autoload.gd +35 -3
- package/build/godot/addons/gdharness_runtime/runtime_capture.gd +5 -0
- package/build/godot/addons/gdharness_runtime/runtime_changes.gd +188 -30
- package/build/godot/addons/gdharness_runtime/runtime_input.gd +107 -104
- 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 +320 -27
- package/build/godot/addons/gdharness_runtime/runtime_queries.gd +165 -20
- package/build/godot/addons/gdharness_runtime/runtime_screen.gd +49 -3
- package/build/godot/addons/gdharness_runtime/runtime_targets.gd +31 -2
- package/build/godot/addons/gdharness_runtime/runtime_values.gd +250 -10
- 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 +19 -29
- 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 +29 -13
- package/build/godot/operations/export_presets.gd +602 -4
- package/build/godot/operations/file_reads.gd +115 -18
- package/build/godot/operations/gdscript_analysis.gd +14 -0
- package/build/godot/operations/gdscript_authoring.gd +12 -2
- package/build/godot/operations/godot_operations.gd +18 -2
- package/build/godot/operations/import_pipeline.gd +20 -221
- package/build/godot/operations/input_actions.gd +127 -51
- package/build/godot/operations/project_config.gd +113 -68
- 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 +112 -6
- package/build/godot/operations/scene_connections.gd +222 -0
- package/build/godot/operations/serialisation.gd +19 -29
- 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 +1402 -707
- package/package.json +1 -1
|
@@ -125,6 +125,7 @@ func find_nodes(params: Dictionary) -> Dictionary:
|
|
|
125
125
|
var root: Node = reached["node"]
|
|
126
126
|
|
|
127
127
|
var found: Array[Dictionary] = []
|
|
128
|
+
var found_nodes: Array[Node] = []
|
|
128
129
|
var pending: Array[Node] = [root]
|
|
129
130
|
var truncated: bool = false
|
|
130
131
|
# Counted while the tree is already being walked, for the answer below: how many nodes this
|
|
@@ -148,24 +149,28 @@ func find_nodes(params: Dictionary) -> Dictionary:
|
|
|
148
149
|
var nearly_hidden: int = 0
|
|
149
150
|
var nearly_said_hidden: int = 0
|
|
150
151
|
var hidden: int = 0
|
|
152
|
+
var out_of_view: int = 0
|
|
151
153
|
while not pending.is_empty():
|
|
152
154
|
var node: Node = pending.pop_front()
|
|
153
155
|
var rest: bool = _matches_apart_from_name(node, wanted)
|
|
154
156
|
if rest and _named(node, names):
|
|
155
157
|
if not include_hidden and not shown(node):
|
|
156
158
|
hidden += 1
|
|
159
|
+
elif not include_hidden and not GdhScreen.in_view(node):
|
|
160
|
+
out_of_view += 1
|
|
157
161
|
elif found.size() >= limit:
|
|
158
162
|
truncated = true
|
|
159
163
|
break
|
|
160
164
|
else:
|
|
161
165
|
found.append(_found(node, wanted_property))
|
|
166
|
+
found_nodes.append(node)
|
|
162
167
|
elif rest and _contains_any(str(node.name), plain_names):
|
|
163
|
-
if include_hidden or
|
|
168
|
+
if include_hidden or readable(node):
|
|
164
169
|
nearly += 1
|
|
165
170
|
else:
|
|
166
171
|
nearly_hidden += 1
|
|
167
172
|
elif not widened_says.is_empty() and _named(node, names) and _matches_apart_from_name(node, widened):
|
|
168
|
-
if include_hidden or
|
|
173
|
+
if include_hidden or readable(node):
|
|
169
174
|
nearly_said += 1
|
|
170
175
|
else:
|
|
171
176
|
nearly_said_hidden += 1
|
|
@@ -181,6 +186,39 @@ func find_nodes(params: Dictionary) -> Dictionary:
|
|
|
181
186
|
|
|
182
187
|
var answer: Dictionary = {"type": "nodes", "count": found.size(), "truncated": truncated, "nodes": found}
|
|
183
188
|
var notes: Array[String] = []
|
|
189
|
+
# Each still listed, since a caller may mean one of them, and marked, so one sign drawn as a label
|
|
190
|
+
# and its outline copies is not read as four signs. The outermost match it copies, since a find
|
|
191
|
+
# under one of the copies has left the label they all copy outside it.
|
|
192
|
+
var copies: int = 0
|
|
193
|
+
for index: int in range(found_nodes.size()):
|
|
194
|
+
var at: int = -1
|
|
195
|
+
for original: Node in GdhWords.copied_through(found_nodes[index]):
|
|
196
|
+
var matched: int = found_nodes.find(original)
|
|
197
|
+
if matched != -1:
|
|
198
|
+
at = matched
|
|
199
|
+
if at != -1:
|
|
200
|
+
found[index]["copyOf"] = found[at]["path"]
|
|
201
|
+
copies += 1
|
|
202
|
+
if copies > 0:
|
|
203
|
+
answer["distinct"] = found.size() - copies
|
|
204
|
+
(
|
|
205
|
+
notes
|
|
206
|
+
. append(
|
|
207
|
+
(
|
|
208
|
+
(
|
|
209
|
+
"%d of the %d matches %s drawn under a match saying the same words, as an outline or a"
|
|
210
|
+
+ " glow is drawn, and %s it under copyOf: %d distinct"
|
|
211
|
+
)
|
|
212
|
+
% [
|
|
213
|
+
copies,
|
|
214
|
+
found.size(),
|
|
215
|
+
"is a copy" if copies == 1 else "are copies",
|
|
216
|
+
"names" if copies == 1 else "name",
|
|
217
|
+
found.size() - copies,
|
|
218
|
+
]
|
|
219
|
+
)
|
|
220
|
+
)
|
|
221
|
+
)
|
|
184
222
|
# Nothing found is the one answer that cannot be told apart from having asked the wrong
|
|
185
223
|
# question, and a name written without a wildcard is the way an agent writes "contains".
|
|
186
224
|
# Said only when it changes the answer, so a genuine nothing stays a plain nothing.
|
|
@@ -200,7 +238,7 @@ func find_nodes(params: Dictionary) -> Dictionary:
|
|
|
200
238
|
'name is matched as a glob against the whole name; %d %snode %s %s, which "%s"%s would find'
|
|
201
239
|
% [
|
|
202
240
|
named_near,
|
|
203
|
-
"" if nearly > 0 else "hidden ",
|
|
241
|
+
"" if nearly > 0 else "hidden or out-of-view ",
|
|
204
242
|
holding,
|
|
205
243
|
" or ".join(PackedStringArray(near_words)),
|
|
206
244
|
GdhSays.joined_at_bars(opened),
|
|
@@ -220,7 +258,7 @@ func find_nodes(params: Dictionary) -> Dictionary:
|
|
|
220
258
|
widened_says,
|
|
221
259
|
"" if nearly_said > 0 else " with includeHidden true",
|
|
222
260
|
said_near,
|
|
223
|
-
"" if nearly_said > 0 else "hidden ",
|
|
261
|
+
"" if nearly_said > 0 else "hidden or out-of-view ",
|
|
224
262
|
"" if said_near == 1 else "s",
|
|
225
263
|
]
|
|
226
264
|
)
|
|
@@ -236,6 +274,17 @@ func find_nodes(params: Dictionary) -> Dictionary:
|
|
|
236
274
|
% [hidden, "" if hidden == 1 else "s"]
|
|
237
275
|
)
|
|
238
276
|
)
|
|
277
|
+
if out_of_view > 0:
|
|
278
|
+
answer["out_of_view"] = out_of_view
|
|
279
|
+
notes.append(
|
|
280
|
+
(
|
|
281
|
+
(
|
|
282
|
+
"%d matching node%s drawn outside the viewport or scrolled out of a ScrollContainer"
|
|
283
|
+
+ " and left out; includeHidden true answers with them too"
|
|
284
|
+
)
|
|
285
|
+
% [out_of_view, "" if out_of_view == 1 else "s"]
|
|
286
|
+
)
|
|
287
|
+
)
|
|
239
288
|
if not notes.is_empty():
|
|
240
289
|
answer["note"] = " ".join(notes)
|
|
241
290
|
return answer
|
|
@@ -268,6 +317,13 @@ static func shown(node: Node) -> bool:
|
|
|
268
317
|
return parent == null or shown(parent)
|
|
269
318
|
|
|
270
319
|
|
|
320
|
+
## Whether the player can read [param node]: [method shown], and drawn inside its viewport and every
|
|
321
|
+
## ScrollContainer holding it, see [method GdhScreen.in_view]. A click by words asks [method shown]
|
|
322
|
+
## alone, since it scrolls what it presses into view.
|
|
323
|
+
static func readable(node: Node) -> bool:
|
|
324
|
+
return shown(node) and GdhScreen.in_view(node)
|
|
325
|
+
|
|
326
|
+
|
|
271
327
|
## Whether the canvas [param item] is drawn on is showing: its layer, and the windows around it.
|
|
272
328
|
static func _on_a_shown_canvas(item: CanvasItem) -> bool:
|
|
273
329
|
var walk: Node = item.get_parent()
|
|
@@ -426,15 +482,46 @@ func read_text(params: Dictionary) -> Dictionary:
|
|
|
426
482
|
# and a bare `truncated: true` was looked straight past twice before the limit was suspected.
|
|
427
483
|
# A number is what sends somebody back.
|
|
428
484
|
var lines: Array[String] = []
|
|
429
|
-
var
|
|
430
|
-
|
|
485
|
+
var tally: Dictionary[String, int] = {"omitted": 0, "out_of_view": 0, "copies": 0}
|
|
486
|
+
_read_into(root, include_hidden, limit, lines, tally)
|
|
487
|
+
var answer: Dictionary = {
|
|
431
488
|
"type": "text",
|
|
432
489
|
"root": root_path,
|
|
433
490
|
"lines": lines,
|
|
434
491
|
"count": lines.size(),
|
|
435
|
-
"truncated":
|
|
436
|
-
"omitted":
|
|
492
|
+
"truncated": tally["omitted"] > 0,
|
|
493
|
+
"omitted": tally["omitted"],
|
|
437
494
|
}
|
|
495
|
+
var notes: Array[String] = []
|
|
496
|
+
if tally["out_of_view"] > 0:
|
|
497
|
+
answer["out_of_view"] = tally["out_of_view"]
|
|
498
|
+
notes.append(
|
|
499
|
+
(
|
|
500
|
+
(
|
|
501
|
+
"%d line%s drawn outside the viewport or scrolled out of a ScrollContainer, where the"
|
|
502
|
+
+ " player cannot read them, left out; includeHidden true answers with them too"
|
|
503
|
+
)
|
|
504
|
+
% [tally["out_of_view"], "" if tally["out_of_view"] == 1 else "s"]
|
|
505
|
+
)
|
|
506
|
+
)
|
|
507
|
+
if tally["copies"] > 0:
|
|
508
|
+
answer["copies"] = tally["copies"]
|
|
509
|
+
notes.append(
|
|
510
|
+
(
|
|
511
|
+
(
|
|
512
|
+
"%d line%s drawn as a copy under a node saying the same words, as an outline or a glow"
|
|
513
|
+
+ " is drawn, read once with the words %s"
|
|
514
|
+
)
|
|
515
|
+
% [
|
|
516
|
+
tally["copies"],
|
|
517
|
+
"" if tally["copies"] == 1 else "s",
|
|
518
|
+
"it copies" if tally["copies"] == 1 else "they copy"
|
|
519
|
+
]
|
|
520
|
+
)
|
|
521
|
+
)
|
|
522
|
+
if not notes.is_empty():
|
|
523
|
+
answer["note"] = " ".join(notes)
|
|
524
|
+
return answer
|
|
438
525
|
|
|
439
526
|
|
|
440
527
|
## Walks [param node] depth first, which is the order the screen is laid out in and the order a
|
|
@@ -445,22 +532,33 @@ func read_text(params: Dictionary) -> Dictionary:
|
|
|
445
532
|
## screen full of forms answered with every label on it and none of the values in it. The hidden
|
|
446
533
|
## check covers a [Window] for the same walk: a dropdown's popup is a child that is not drawn
|
|
447
534
|
## until it is opened, and reading a closed menu would put every item on the screen.
|
|
448
|
-
## Fills [param into] up to [param most] lines and
|
|
449
|
-
## the subtree says,
|
|
535
|
+
## Fills [param into] up to [param most] lines and counts in [param tally] how many further lines the
|
|
536
|
+
## rest of the subtree says, under omitted, and how many it says out of view, under out_of_view, which
|
|
537
|
+
## is what the caller is told rather than left to infer.
|
|
450
538
|
##
|
|
451
539
|
## Asked of each node rather than cut off at the first hidden one, since a hidden panel does not
|
|
452
540
|
## hide a label reached through a plain Node: see [method shown].
|
|
453
|
-
func _read_into(
|
|
454
|
-
|
|
541
|
+
func _read_into(
|
|
542
|
+
node: Node, include_hidden: bool, most: int, into: Array[String], tally: Dictionary[String, int]
|
|
543
|
+
) -> void:
|
|
455
544
|
if include_hidden or shown(node):
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
545
|
+
var said: Array[String] = GdhWords.lines_said_by(node)
|
|
546
|
+
# A copy drawn under the words it copies, as an outline or a glow is, reads once. Only while
|
|
547
|
+
# what it copies is itself read, so an original hidden or drawn out of view does not take the
|
|
548
|
+
# words with it.
|
|
549
|
+
var original: Node = GdhWords.copied_from(node) if not said.is_empty() else null
|
|
550
|
+
if original != null and (include_hidden or readable(original)):
|
|
551
|
+
tally["copies"] += said.size()
|
|
552
|
+
elif not include_hidden and not GdhScreen.in_view(node):
|
|
553
|
+
tally["out_of_view"] += said.size()
|
|
554
|
+
else:
|
|
555
|
+
for line: String in said:
|
|
556
|
+
if into.size() < most:
|
|
557
|
+
into.append(line)
|
|
558
|
+
else:
|
|
559
|
+
tally["omitted"] += 1
|
|
461
560
|
for child: Node in node.get_children(true):
|
|
462
|
-
|
|
463
|
-
return left_out
|
|
561
|
+
_read_into(child, include_hidden, most, into, tally)
|
|
464
562
|
|
|
465
563
|
|
|
466
564
|
## Where a node is on screen: a Control's rectangle, a Node2D's position, or the place a 3D node
|
|
@@ -488,6 +586,7 @@ func get_rect(params: Dictionary) -> Dictionary:
|
|
|
488
586
|
"type": "rect",
|
|
489
587
|
"path": node_path,
|
|
490
588
|
"visible": control.is_visible_in_tree(),
|
|
589
|
+
"in_view": GdhScreen.in_view(control),
|
|
491
590
|
"canvas": _values.serialize(canvas_rect),
|
|
492
591
|
"window":
|
|
493
592
|
_values.serialize(
|
|
@@ -501,13 +600,20 @@ func get_rect(params: Dictionary) -> Dictionary:
|
|
|
501
600
|
var item: Node2D = node
|
|
502
601
|
var canvas_position: Vector2 = item.get_global_transform_with_canvas().origin
|
|
503
602
|
var window_position: Vector2 = GdhScreen.in_window(item.get_viewport(), canvas_position)
|
|
504
|
-
|
|
603
|
+
var at: Dictionary = {
|
|
505
604
|
"type": "point",
|
|
506
605
|
"path": node_path,
|
|
507
606
|
"visible": item.is_visible_in_tree(),
|
|
508
607
|
"canvas": _values.serialize(canvas_position),
|
|
509
608
|
"window": _values.serialize(window_position),
|
|
510
609
|
}
|
|
610
|
+
for local: Rect2 in _drawn_rect(item):
|
|
611
|
+
var covered: Rect2 = item.get_global_transform_with_canvas() * local
|
|
612
|
+
at["covers"] = {
|
|
613
|
+
"canvas": _values.serialize(covered),
|
|
614
|
+
"window": _values.serialize(GdhScreen.rect_in_window(item.get_viewport(), covered)),
|
|
615
|
+
}
|
|
616
|
+
return at
|
|
511
617
|
if node is Node3D:
|
|
512
618
|
var spatial: Node3D = node
|
|
513
619
|
return _in_the_frame(node_path, spatial)
|
|
@@ -516,6 +622,43 @@ func get_rect(params: Dictionary) -> Dictionary:
|
|
|
516
622
|
}
|
|
517
623
|
|
|
518
624
|
|
|
625
|
+
## The rectangle a 2D node's own geometry takes up in its own coordinates, as the one entry of the
|
|
626
|
+
## list, which is empty for a node that has none of its own: a sprite's texture, a collision shape's
|
|
627
|
+
## shape, the points of a polygon or a line. A Node2D answered only where it stands, so the extent of
|
|
628
|
+
## a floor or a sprite to aim inside was a read of its shape and some arithmetic.
|
|
629
|
+
static func _drawn_rect(item: Node2D) -> Array[Rect2]:
|
|
630
|
+
var drawn: Array[Rect2] = []
|
|
631
|
+
var sprite: Sprite2D = item as Sprite2D
|
|
632
|
+
if sprite != null:
|
|
633
|
+
if sprite.texture != null:
|
|
634
|
+
drawn.append(sprite.get_rect())
|
|
635
|
+
return drawn
|
|
636
|
+
var shaped: CollisionShape2D = item as CollisionShape2D
|
|
637
|
+
if shaped != null:
|
|
638
|
+
if shaped.shape != null:
|
|
639
|
+
drawn.append(shaped.shape.get_rect())
|
|
640
|
+
return drawn
|
|
641
|
+
var points: PackedVector2Array = []
|
|
642
|
+
var offset: Vector2 = Vector2.ZERO
|
|
643
|
+
if item is Polygon2D:
|
|
644
|
+
var polygon: Polygon2D = item
|
|
645
|
+
points = polygon.polygon
|
|
646
|
+
offset = polygon.offset
|
|
647
|
+
elif item is CollisionPolygon2D:
|
|
648
|
+
var outline: CollisionPolygon2D = item
|
|
649
|
+
points = outline.polygon
|
|
650
|
+
elif item is Line2D:
|
|
651
|
+
var line: Line2D = item
|
|
652
|
+
points = line.points
|
|
653
|
+
if points.is_empty():
|
|
654
|
+
return drawn
|
|
655
|
+
var bounds: Rect2 = Rect2(points[0] + offset, Vector2.ZERO)
|
|
656
|
+
for point: Vector2 in points:
|
|
657
|
+
bounds = bounds.expand(point + offset)
|
|
658
|
+
drawn.append(bounds)
|
|
659
|
+
return drawn
|
|
660
|
+
|
|
661
|
+
|
|
519
662
|
## Where a 3D node is in the frame drawing it: the point to aim at, and the rectangle its own
|
|
520
663
|
## geometry covers, each in canvas coordinates and in window pixels.
|
|
521
664
|
##
|
|
@@ -723,6 +866,8 @@ func get_metrics(params: Dictionary) -> Dictionary:
|
|
|
723
866
|
"frame_time": frame_time,
|
|
724
867
|
"frame_time_max": frame_time_max,
|
|
725
868
|
"frames_timed": timed,
|
|
869
|
+
"process_frames": Engine.get_process_frames(),
|
|
870
|
+
"physics_frames": Engine.get_physics_frames(),
|
|
726
871
|
"physics_steps_per_second": physics_steps_per_second,
|
|
727
872
|
"physics_ticks_per_second": Engine.physics_ticks_per_second,
|
|
728
873
|
"max_physics_steps_per_frame": Engine.max_physics_steps_per_frame,
|
|
@@ -29,9 +29,13 @@ static func steps_out(viewport: Viewport, point: Vector2) -> Array[Dictionary]:
|
|
|
29
29
|
var out: Array[Dictionary] = [{"viewport": viewport, "point": point, "through": null}]
|
|
30
30
|
while true:
|
|
31
31
|
var window: Window = viewport as Window
|
|
32
|
-
if window != null and window.is_embedded()
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
var host: Viewport = embedder_of(window) if window != null and window.is_embedded() else null
|
|
33
|
+
if host != null:
|
|
34
|
+
# A window's canvas is drawn into its embedder through its own final transform, which is
|
|
35
|
+
# where a content scale goes: one at 4 drew a button at four times the point it was clicked
|
|
36
|
+
# at, and the click missed it.
|
|
37
|
+
point = Vector2(window.position) + window.get_final_transform() * point
|
|
38
|
+
viewport = host
|
|
35
39
|
out.append({"viewport": viewport, "point": point, "through": window})
|
|
36
40
|
continue
|
|
37
41
|
var shown_by: SubViewportContainer = viewport.get_parent() as SubViewportContainer
|
|
@@ -45,6 +49,19 @@ static func steps_out(viewport: Viewport, point: Vector2) -> Array[Dictionary]:
|
|
|
45
49
|
return out
|
|
46
50
|
|
|
47
51
|
|
|
52
|
+
## The viewport [param window] is embedded in and placed by, found as the engine finds it, which
|
|
53
|
+
## does not offer it to scripts: the nearest viewport above that embeds subwindows. That is not
|
|
54
|
+
## always its parent's: a window under a window that does not embed is embedded in the one above.
|
|
55
|
+
static func embedder_of(window: Window) -> Viewport:
|
|
56
|
+
var above: Node = window.get_parent()
|
|
57
|
+
# A viewport's get_viewport is itself, so a window's parent that is one is asked first.
|
|
58
|
+
var viewport: Viewport = above.get_viewport() if above != null else null
|
|
59
|
+
while viewport != null and not viewport.is_embedding_subwindows():
|
|
60
|
+
above = viewport.get_parent()
|
|
61
|
+
viewport = above.get_viewport() if above != null else null
|
|
62
|
+
return viewport
|
|
63
|
+
|
|
64
|
+
|
|
48
65
|
## Where [param point], in [param viewport]'s own coordinates, is in the pixels of the window the
|
|
49
66
|
## game is shown in, which is what a mouse event names.
|
|
50
67
|
static func in_window(viewport: Viewport, point: Vector2) -> Vector2:
|
|
@@ -60,3 +77,32 @@ static func rect_in_window(viewport: Viewport, rect: Rect2) -> Rect2:
|
|
|
60
77
|
var start: Vector2 = in_window(viewport, rect.position)
|
|
61
78
|
var end: Vector2 = in_window(viewport, rect.end)
|
|
62
79
|
return Rect2(start.min(end), (end - start).abs())
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
## Whether any of [param node] is drawn where the player can see it: inside its viewport, and inside
|
|
83
|
+
## every ScrollContainer between it and the viewport, each of which clips what it holds. A label
|
|
84
|
+
## placed past the window's edge was read out as written on the screen. Only a Control has a box to
|
|
85
|
+
## ask about, so anything else is in view.
|
|
86
|
+
static func in_view(node: Node) -> bool:
|
|
87
|
+
var control: Control = node as Control
|
|
88
|
+
return control == null or shown_part(control) != null
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
## The part of [param control] the player can see, in its viewport's canvas, or null when there is
|
|
92
|
+
## none: its drawn box cut to the viewport and to every ScrollContainer between, each cut taken from
|
|
93
|
+
## what the last one left. A control in view of an inner list and of the outer one can still sit
|
|
94
|
+
## where the two do not overlap.
|
|
95
|
+
static func shown_part(control: Control) -> Variant:
|
|
96
|
+
var part: Rect2 = control.get_global_transform_with_canvas() * Rect2(Vector2.ZERO, control.size)
|
|
97
|
+
var clips: Array[Rect2] = [control.get_viewport().get_visible_rect()]
|
|
98
|
+
var walking: Node = control.get_parent()
|
|
99
|
+
while walking != null and not (walking is Viewport):
|
|
100
|
+
var holder: ScrollContainer = walking as ScrollContainer
|
|
101
|
+
if holder != null:
|
|
102
|
+
clips.append(holder.get_global_transform_with_canvas() * Rect2(Vector2.ZERO, holder.size))
|
|
103
|
+
walking = walking.get_parent()
|
|
104
|
+
for clip: Rect2 in clips:
|
|
105
|
+
if not part.intersects(clip, true):
|
|
106
|
+
return null
|
|
107
|
+
part = part.intersection(clip)
|
|
108
|
+
return part
|
|
@@ -13,6 +13,15 @@ const GdhWords = preload("runtime_words.gd")
|
|
|
13
13
|
## How many of the controls saying the same words a refusal names, with the index for each.
|
|
14
14
|
const LISTED_CANDIDATES: int = 10
|
|
15
15
|
|
|
16
|
+
## Node.ProcessMode as its names, for a refusal to say which one is in force.
|
|
17
|
+
const PROCESS_MODES: Dictionary[int, String] = {
|
|
18
|
+
Node.PROCESS_MODE_INHERIT: "INHERIT",
|
|
19
|
+
Node.PROCESS_MODE_PAUSABLE: "PAUSABLE",
|
|
20
|
+
Node.PROCESS_MODE_WHEN_PAUSED: "WHEN_PAUSED",
|
|
21
|
+
Node.PROCESS_MODE_ALWAYS: "ALWAYS",
|
|
22
|
+
Node.PROCESS_MODE_DISABLED: "DISABLED",
|
|
23
|
+
}
|
|
24
|
+
|
|
16
25
|
## What a game started without a window gets whatever the project settings say, and the usual
|
|
17
26
|
## reason a control is out of reach. Only worth telling somebody when it is what they have.
|
|
18
27
|
const HEADLESS_VIEWPORT: Vector2 = Vector2(64, 64)
|
|
@@ -753,9 +762,12 @@ static func _moved_by(node: Node, moves: Dictionary[ScrollContainer, Vector2]) -
|
|
|
753
762
|
## target's own viewport, a confirmation over a settings dialog did not cover the dialog's OK, and
|
|
754
763
|
## a click by words tied it with the confirmation's own.
|
|
755
764
|
static func _window_over(target: Control, point: Vector2, window: Window) -> bool:
|
|
756
|
-
|
|
765
|
+
# Not passed over for holding the target in the tree: a window under an embedded window is
|
|
766
|
+
# embedded beside it, not in it, so the one holding it can be stacked over it. The target's own
|
|
767
|
+
# window is the step out below, and is never over it.
|
|
768
|
+
if window.get_parent() == null:
|
|
757
769
|
return false
|
|
758
|
-
var host: Viewport =
|
|
770
|
+
var host: Viewport = GdhScreen.embedder_of(window)
|
|
759
771
|
for step: Dictionary in GdhScreen.steps_out(target.get_viewport(), point):
|
|
760
772
|
if step["viewport"] != host:
|
|
761
773
|
continue
|
|
@@ -954,3 +966,20 @@ static func _out_of_reach_exactly(nodes: Array[Node], why: Array[String]) -> Str
|
|
|
954
966
|
if nodes.size() == 1:
|
|
955
967
|
return "1 control says exactly that and cannot be clicked: %s" % first
|
|
956
968
|
return "%d controls say exactly that and cannot be clicked, such as %s" % [nodes.size(), first]
|
|
969
|
+
|
|
970
|
+
|
|
971
|
+
## Which node's process_mode keeps [param control] from processing, and what it is. A node set to
|
|
972
|
+
## inherit takes its parent's, so the one deciding can be any node above it: the game turning a
|
|
973
|
+
## whole screen off while a menu shows over it reads, on the button, as a button turned off.
|
|
974
|
+
static func why_it_does_not_process(control: Control, node_path: String) -> String:
|
|
975
|
+
var deciding: Node = control
|
|
976
|
+
while deciding.process_mode == Node.PROCESS_MODE_INHERIT and deciding.get_parent() != null:
|
|
977
|
+
deciding = deciding.get_parent()
|
|
978
|
+
# The root inherits from nothing, which processes as pausable.
|
|
979
|
+
var value: int = deciding.process_mode
|
|
980
|
+
if value == Node.PROCESS_MODE_INHERIT:
|
|
981
|
+
value = Node.PROCESS_MODE_PAUSABLE
|
|
982
|
+
var mode: String = PROCESS_MODES.get(value, str(value))
|
|
983
|
+
if deciding == control:
|
|
984
|
+
return "its process_mode is %s" % mode
|
|
985
|
+
return "%s inherits process_mode %s from %s" % [node_path, mode, deciding.get_path()]
|