gdharness 1.3.41 → 1.3.43
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 +131 -47
- package/build/godot/addons/auto_reload/auto_reload.gd +18 -19
- package/build/godot/addons/gdharness_editor/animation_reads.gd +84 -35
- package/build/godot/addons/gdharness_editor/bridge_client.gd +53 -58
- package/build/godot/addons/gdharness_editor/declared_values.gd +68 -32
- package/build/godot/addons/gdharness_editor/file_reads.gd +114 -39
- package/build/godot/addons/gdharness_editor/file_walk.gd +12 -5
- package/build/godot/addons/gdharness_editor/headless_check.gd +94 -41
- package/build/godot/addons/gdharness_editor/plugin.gd +2 -3
- package/build/godot/addons/gdharness_editor/property_paths.gd +22 -12
- package/build/godot/addons/gdharness_editor/property_values.gd +143 -78
- package/build/godot/addons/gdharness_editor/reading.gd +37 -12
- package/build/godot/addons/gdharness_editor/same_values.gd +4 -4
- package/build/godot/addons/gdharness_editor/scene_file.gd +136 -79
- package/build/godot/addons/gdharness_editor/scene_uids.gd +12 -6
- package/build/godot/addons/gdharness_editor/serialisation.gd +75 -51
- package/build/godot/addons/gdharness_editor/setting_reads.gd +7 -6
- package/build/godot/addons/gdharness_editor/tool_executor.gd +3 -6
- package/build/godot/addons/gdharness_editor/tools/animation_states.gd +41 -28
- package/build/godot/addons/gdharness_editor/tools/animation_tools.gd +84 -50
- package/build/godot/addons/gdharness_editor/tools/animation_tracks.gd +70 -40
- package/build/godot/addons/gdharness_editor/tools/class_tools.gd +34 -30
- package/build/godot/addons/gdharness_editor/tools/filesystem_tools.gd +24 -22
- package/build/godot/addons/gdharness_editor/tools/navigation_bake.gd +49 -29
- package/build/godot/addons/gdharness_editor/tools/play_tools.gd +26 -21
- package/build/godot/addons/gdharness_editor/tools/profile_tools.gd +40 -40
- package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +181 -107
- package/build/godot/addons/gdharness_editor/tools/scene_groups.gd +16 -9
- package/build/godot/addons/gdharness_editor/tools/scene_kin.gd +75 -43
- package/build/godot/addons/gdharness_editor/tools/scene_paths.gd +35 -20
- package/build/godot/addons/gdharness_editor/tools/scene_placing.gd +28 -13
- package/build/godot/addons/gdharness_editor/tools/scene_sharing.gd +66 -0
- package/build/godot/addons/gdharness_editor/tools/scene_sharing.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +171 -62
- package/build/godot/addons/gdharness_editor/tools/script_paths.gd +20 -8
- package/build/godot/addons/gdharness_editor/tools/settings_tools.gd +11 -8
- package/build/godot/addons/gdharness_editor/tools/signal_tools.gd +47 -34
- package/build/godot/addons/gdharness_editor/tools/tileset_tools.gd +77 -50
- package/build/godot/addons/gdharness_editor/value_shapes.gd +5 -6
- package/build/godot/addons/gdharness_runtime/call_errors.gd +1 -2
- package/build/godot/addons/gdharness_runtime/declared_values.gd +68 -32
- package/build/godot/addons/gdharness_runtime/error_report.gd +1 -2
- package/build/godot/addons/gdharness_runtime/reading.gd +37 -12
- package/build/godot/addons/gdharness_runtime/runtime_accessibility.gd +84 -63
- package/build/godot/addons/gdharness_runtime/runtime_autoload.gd +153 -148
- package/build/godot/addons/gdharness_runtime/runtime_capture.gd +64 -42
- package/build/godot/addons/gdharness_runtime/runtime_changes.gd +184 -122
- package/build/godot/addons/gdharness_runtime/runtime_frames.gd +2 -2
- package/build/godot/addons/gdharness_runtime/runtime_hover.gd +24 -20
- package/build/godot/addons/gdharness_runtime/runtime_input.gd +134 -92
- package/build/godot/addons/gdharness_runtime/runtime_items.gd +29 -13
- package/build/godot/addons/gdharness_runtime/runtime_menus.gd +0 -1
- package/build/godot/addons/gdharness_runtime/runtime_mouse.gd +38 -20
- package/build/godot/addons/gdharness_runtime/runtime_pad.gd +17 -13
- package/build/godot/addons/gdharness_runtime/runtime_paths.gd +84 -46
- package/build/godot/addons/gdharness_runtime/runtime_queries.gd +117 -79
- package/build/godot/addons/gdharness_runtime/runtime_says.gd +14 -17
- package/build/godot/addons/gdharness_runtime/runtime_screen.gd +23 -16
- package/build/godot/addons/gdharness_runtime/runtime_targets.gd +117 -66
- package/build/godot/addons/gdharness_runtime/runtime_touch.gd +211 -0
- package/build/godot/addons/gdharness_runtime/runtime_touch.gd.uid +1 -0
- package/build/godot/addons/gdharness_runtime/runtime_typing.gd +44 -27
- package/build/godot/addons/gdharness_runtime/runtime_values.gd +108 -73
- package/build/godot/addons/gdharness_runtime/runtime_waits.gd +111 -84
- package/build/godot/addons/gdharness_runtime/runtime_words.gd +10 -7
- package/build/godot/addons/gdharness_runtime/serialisation.gd +75 -51
- package/build/godot/addons/gdharness_runtime/value_shapes.gd +5 -6
- package/build/godot/addons/gdharness_tests/bench.gd +4 -5
- package/build/godot/addons/gdharness_tests/event.gd +10 -5
- package/build/godot/addons/gdharness_tests/share.gd +38 -18
- package/build/godot/addons/gdharness_tests/suites.gd +3 -3
- package/build/godot/addons/gdharness_tests/wait.gd +3 -2
- package/build/godot/loader/loader.gd +9 -9
- package/build/godot/operations/animation_reads.gd +84 -35
- package/build/godot/operations/audio_buses.gd +48 -19
- package/build/godot/operations/class_cache.gd +16 -11
- package/build/godot/operations/classdb_queries.gd +89 -44
- package/build/godot/operations/declared_values.gd +68 -32
- package/build/godot/operations/dependencies.gd +139 -93
- package/build/godot/operations/editor_tools.gd +9 -10
- package/build/godot/operations/export_presets.gd +100 -54
- package/build/godot/operations/file_reads.gd +114 -39
- package/build/godot/operations/file_walk.gd +12 -5
- package/build/godot/operations/gdscript_analysis.gd +105 -66
- package/build/godot/operations/gdscript_authoring.gd +138 -39
- package/build/godot/operations/godot_operations.gd +17 -11
- package/build/godot/operations/import_pipeline.gd +110 -59
- package/build/godot/operations/input_actions.gd +63 -39
- package/build/godot/operations/patterns.gd +3 -1
- package/build/godot/operations/plugins.gd +36 -16
- package/build/godot/operations/project_config.gd +144 -27
- package/build/godot/operations/project_diagnostics.gd +24 -11
- package/build/godot/operations/project_file.gd +16 -16
- package/build/godot/operations/project_validation.gd +71 -44
- package/build/godot/operations/property_paths.gd +22 -12
- package/build/godot/operations/property_values.gd +143 -78
- package/build/godot/operations/reading.gd +37 -12
- package/build/godot/operations/rename_checks.gd +2 -2
- package/build/godot/operations/resource_files.gd +11 -9
- package/build/godot/operations/same_values.gd +4 -4
- package/build/godot/operations/scene_connections.gd +27 -13
- package/build/godot/operations/script_check.gd +52 -49
- package/build/godot/operations/serialisation.gd +75 -51
- package/build/godot/operations/setting_reads.gd +7 -6
- package/build/godot/operations/value_shapes.gd +5 -6
- package/build/godot/tests/listed.gd +321 -268
- package/package.json +1 -1
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
extends RefCounted
|
|
2
|
-
|
|
3
2
|
## Which control a click means when it names the control by the words on it rather than by path.
|
|
4
3
|
|
|
5
4
|
const GdhItems = preload("runtime_items.gd")
|
|
@@ -9,12 +8,10 @@ const GdhSays = preload("runtime_says.gd")
|
|
|
9
8
|
const GdhScreen = preload("runtime_screen.gd")
|
|
10
9
|
const GdhValues = preload("runtime_values.gd")
|
|
11
10
|
const GdhWords = preload("runtime_words.gd")
|
|
12
|
-
|
|
13
11
|
## How many of the controls saying the same words a refusal names, with the index for each.
|
|
14
12
|
const LISTED_CANDIDATES: int = 10
|
|
15
13
|
## How many hidden controls saying the words a refusal names.
|
|
16
14
|
const NAMED_HIDDEN: int = 3
|
|
17
|
-
|
|
18
15
|
## Node.ProcessMode as its names, for a refusal to say which one is in force.
|
|
19
16
|
const PROCESS_MODES: Dictionary[int, String] = {
|
|
20
17
|
Node.PROCESS_MODE_INHERIT: "INHERIT",
|
|
@@ -23,11 +20,9 @@ const PROCESS_MODES: Dictionary[int, String] = {
|
|
|
23
20
|
Node.PROCESS_MODE_ALWAYS: "ALWAYS",
|
|
24
21
|
Node.PROCESS_MODE_DISABLED: "DISABLED",
|
|
25
22
|
}
|
|
26
|
-
|
|
27
23
|
## What a game started without a window gets whatever the project settings say, and the usual
|
|
28
24
|
## reason a control is out of reach. Only worth telling somebody when it is what they have.
|
|
29
25
|
const HEADLESS_VIEWPORT: Vector2 = Vector2(64, 64)
|
|
30
|
-
|
|
31
26
|
## What a match's rank is made of, one bit each, the highest deciding first.
|
|
32
27
|
const TAKES_POINTER: int = 4
|
|
33
28
|
const ENABLED_BUTTON: int = 2
|
|
@@ -55,7 +50,9 @@ const WHOLE: int = 1
|
|
|
55
50
|
## A control with something drawn over its centre that a pointer there would reach instead is not
|
|
56
51
|
## on screen for a player, and a click at it lands on the thing on top. The screen a game shows
|
|
57
52
|
## over its hall covers the hall's drawers without hiding them, so their words matched as well.
|
|
58
|
-
static func control_saying(
|
|
53
|
+
static func control_saying(
|
|
54
|
+
root: Node, root_path: String, wanted: String, which: Variant
|
|
55
|
+
) -> Dictionary:
|
|
59
56
|
var reached: Dictionary = GdhValues.node_at(root, root_path)
|
|
60
57
|
if reached.has("message"):
|
|
61
58
|
return reached
|
|
@@ -66,8 +63,8 @@ static func control_saying(root: Node, root_path: String, wanted: String, which:
|
|
|
66
63
|
# Whether the words are only inside longer words there, "out" in "Scout".
|
|
67
64
|
var inside: Array[bool] = []
|
|
68
65
|
var hidden: Array[Node] = []
|
|
69
|
-
# Controls saying exactly the words that a click cannot reach, kept to say what a match on
|
|
70
|
-
# that only says them as part of more stands in for.
|
|
66
|
+
# Controls saying exactly the words that a click cannot reach, kept to say what a match on
|
|
67
|
+
# screen that only says them as part of more stands in for.
|
|
71
68
|
var exact_out_of_reach: Array[Node] = []
|
|
72
69
|
var out_of_reach_as: Array[String] = []
|
|
73
70
|
# The item of a control drawing a list that matched, null for a control matched as a whole, and
|
|
@@ -84,8 +81,8 @@ static func control_saying(root: Node, root_path: String, wanted: String, which:
|
|
|
84
81
|
exact_out_of_reach.append(node)
|
|
85
82
|
out_of_reach_as.append("hidden")
|
|
86
83
|
elif GdhWords.draws_a_list(node):
|
|
87
|
-
# Each item a match of its own, so the words of one tab are matched as a whole and
|
|
88
|
-
# index picks between two items saying them as it does between two buttons.
|
|
84
|
+
# Each item a match of its own, so the words of one tab are matched as a whole and
|
|
85
|
+
# the index picks between two items saying them as it does between two buttons.
|
|
89
86
|
var drawing: Control = node
|
|
90
87
|
for entry: Dictionary in GdhWords.items_of(drawing):
|
|
91
88
|
var words: String = entry["says"]
|
|
@@ -98,10 +95,14 @@ static func control_saying(root: Node, root_path: String, wanted: String, which:
|
|
|
98
95
|
exact.append(whole)
|
|
99
96
|
inside.append(not whole and _inside_a_word(words, wanted))
|
|
100
97
|
items.append(entry["item"])
|
|
101
|
-
# An item drawn nowhere yet, scrolled out of its list, is judged at the list's
|
|
102
|
-
# which is where the click scrolls it towards.
|
|
98
|
+
# An item drawn nowhere yet, scrolled out of its list, is judged at the list's
|
|
99
|
+
# middle, which is where the click scrolls it towards.
|
|
103
100
|
aims.append(
|
|
104
|
-
|
|
101
|
+
(
|
|
102
|
+
drawing.size * 0.5
|
|
103
|
+
if drawn_at_now.size == Vector2.ZERO
|
|
104
|
+
else drawn_at_now.get_center()
|
|
105
|
+
)
|
|
105
106
|
)
|
|
106
107
|
else:
|
|
107
108
|
var control: Control = node
|
|
@@ -148,9 +149,9 @@ static func control_saying(root: Node, root_path: String, wanted: String, which:
|
|
|
148
149
|
out_of_reach_as.append("under %s, which is drawn over it" % over.get_path())
|
|
149
150
|
if cover == null:
|
|
150
151
|
cover = over
|
|
151
|
-
# Stable, so matches of one rank keep the order they are drawn in. Words only inside longer
|
|
152
|
-
# come after everything else: ranked as a button saying them as part of more, "Scout, 90"
|
|
153
|
-
# pressed for "Out" over a control saying exactly "Out", and spent a game's silver.
|
|
152
|
+
# Stable, so matches of one rank keep the order they are drawn in. Words only inside longer
|
|
153
|
+
# words come after everything else: ranked as a button saying them as part of more, "Scout, 90"
|
|
154
|
+
# was pressed for "Out" over a control saying exactly "Out", and spent a game's silver.
|
|
154
155
|
open.sort_custom(
|
|
155
156
|
func(a: int, b: int) -> bool:
|
|
156
157
|
if inside[a] != inside[b]:
|
|
@@ -190,7 +191,7 @@ static func control_saying(root: Node, root_path: String, wanted: String, which:
|
|
|
190
191
|
_open_menu_note(root, wanted),
|
|
191
192
|
_offered_note(from, drawn, areas),
|
|
192
193
|
]
|
|
193
|
-
)
|
|
194
|
+
),
|
|
194
195
|
}
|
|
195
196
|
var best: int = 0
|
|
196
197
|
for at: int in targets.size():
|
|
@@ -204,7 +205,7 @@ static func control_saying(root: Node, root_path: String, wanted: String, which:
|
|
|
204
205
|
(
|
|
205
206
|
"%s, so which to click is not clear; pass index, counting from 0%s: %s"
|
|
206
207
|
% [_described(best, ranks[0], wanted, under), order, _candidates(targets, lines)]
|
|
207
|
-
)
|
|
208
|
+
),
|
|
208
209
|
}
|
|
209
210
|
var index: int = 0 if which == null else GdhRead.as_int(which, -1)
|
|
210
211
|
if index < 0 or index >= targets.size():
|
|
@@ -220,31 +221,36 @@ static func control_saying(root: Node, root_path: String, wanted: String, which:
|
|
|
220
221
|
)
|
|
221
222
|
return {
|
|
222
223
|
"type": "error",
|
|
223
|
-
"message":
|
|
224
|
+
"message":
|
|
225
|
+
"index %s picks nothing: %s: %s" % [given, among, _candidates(targets, lines)],
|
|
224
226
|
}
|
|
225
|
-
# A match saying the words as part of more, while another control says exactly them, is a
|
|
226
|
-
# for that one, not the control meant: a tab reading "Out" sat in a collapsed drawer
|
|
227
|
-
# saying somebody "fell out in the hall" was pressed, and with the tab's own
|
|
228
|
-
# control that is not a button, a card reading "Scout, 90" was. Pressed
|
|
229
|
-
# index, which is the caller saying the match is what they meant.
|
|
227
|
+
# A match saying the words as part of more, while another control says exactly them, is a
|
|
228
|
+
# stand-in for that one, not the control meant: a tab reading "Out" sat in a collapsed drawer
|
|
229
|
+
# and a dispatch saying somebody "fell out in the hall" was pressed, and with the tab's own
|
|
230
|
+
# words on screen in a control that is not a button, a card reading "Scout, 90" was. Pressed
|
|
231
|
+
# only when asked for by index, which is the caller saying the match is what they meant.
|
|
230
232
|
var exact_elsewhere: Array[Node] = exact_out_of_reach.duplicate()
|
|
231
233
|
var elsewhere_as: Array[String] = out_of_reach_as.duplicate()
|
|
232
234
|
# On screen only when it takes clicks itself: a heading saying exactly "Onward" beside a button
|
|
233
235
|
# saying "Onward now" is not what a click on "Onward" is for, and the button is pressed.
|
|
234
236
|
for other: int in targets.size():
|
|
235
|
-
var taking: bool =
|
|
237
|
+
var taking: bool = (
|
|
238
|
+
targets[other].get_mouse_filter_with_override() != Control.MOUSE_FILTER_IGNORE
|
|
239
|
+
)
|
|
236
240
|
if other != index and wholes[other] and taking:
|
|
237
241
|
exact_elsewhere.append(targets[other])
|
|
238
242
|
elsewhere_as.append("on screen, taking clicks but not an enabled button")
|
|
239
|
-
var best_one: String =
|
|
243
|
+
var best_one: String = (
|
|
244
|
+
"the one on screen," if targets.size() == 1 else "the best match on screen,"
|
|
245
|
+
)
|
|
240
246
|
if which == null and not wholes[index] and not exact_elsewhere.is_empty():
|
|
241
247
|
return {
|
|
242
248
|
"type": "error",
|
|
243
249
|
"message":
|
|
244
250
|
(
|
|
245
251
|
(
|
|
246
|
-
'no %s on screen%s says exactly "%s", so nothing was clicked: %s %s says it as
|
|
247
|
-
+ ' "%s", while %s; pass index %d to press that one anyway'
|
|
252
|
+
'no %s on screen%s says exactly "%s", so nothing was clicked: %s %s says it as '
|
|
253
|
+
+ 'part of "%s", while %s; pass index %d to press that one anyway'
|
|
248
254
|
)
|
|
249
255
|
% [
|
|
250
256
|
"button" if exact_elsewhere.size() > exact_out_of_reach.size() else "control",
|
|
@@ -256,7 +262,7 @@ static func control_saying(root: Node, root_path: String, wanted: String, which:
|
|
|
256
262
|
_out_of_reach_exactly(exact_elsewhere, elsewhere_as),
|
|
257
263
|
index,
|
|
258
264
|
]
|
|
259
|
-
)
|
|
265
|
+
),
|
|
260
266
|
}
|
|
261
267
|
if which == null and insides[index]:
|
|
262
268
|
return {
|
|
@@ -264,15 +270,23 @@ static func control_saying(root: Node, root_path: String, wanted: String, which:
|
|
|
264
270
|
"message":
|
|
265
271
|
(
|
|
266
272
|
(
|
|
267
|
-
'no control on screen%s says "%s" as a word of its own, so nothing was
|
|
268
|
-
+ ' it only inside a longer word, "%s"; pass index %d to
|
|
273
|
+
'no control on screen%s says "%s" as a word of its own, so nothing was '
|
|
274
|
+
+ 'clicked: %s %s has it only inside a longer word, "%s"; pass index %d to '
|
|
275
|
+
+ "press that one anyway"
|
|
269
276
|
)
|
|
270
|
-
% [
|
|
271
|
-
|
|
277
|
+
% [
|
|
278
|
+
under,
|
|
279
|
+
wanted,
|
|
280
|
+
best_one,
|
|
281
|
+
targets[index].get_path(),
|
|
282
|
+
_shortened(lines[index]),
|
|
283
|
+
index,
|
|
284
|
+
]
|
|
285
|
+
),
|
|
272
286
|
}
|
|
273
287
|
# Words in a sentence on a control that lets the pointer through: the press lands on whatever is
|
|
274
|
-
# under it, which is nothing the words name. A line saying what "is out there" was the best
|
|
275
|
-
# left for "Out" once the cards saying "Scout" were passed over.
|
|
288
|
+
# under it, which is nothing the words name. A line saying what "is out there" was the best
|
|
289
|
+
# match left for "Out" once the cards saying "Scout" were passed over.
|
|
276
290
|
if (
|
|
277
291
|
which == null
|
|
278
292
|
and not wholes[index]
|
|
@@ -283,14 +297,21 @@ static func control_saying(root: Node, root_path: String, wanted: String, which:
|
|
|
283
297
|
"message":
|
|
284
298
|
(
|
|
285
299
|
(
|
|
286
|
-
'no control on screen%s that takes a click says "%s", so nothing was clicked:
|
|
287
|
-
+ ' as part of "%s" and lets the pointer through to what is under
|
|
288
|
-
+ " click there anyway"
|
|
300
|
+
'no control on screen%s that takes a click says "%s", so nothing was clicked: '
|
|
301
|
+
+ '%s %s says it as part of "%s" and lets the pointer through to what is under '
|
|
302
|
+
+ "it; pass index %d to click there anyway"
|
|
289
303
|
)
|
|
290
|
-
% [
|
|
291
|
-
|
|
304
|
+
% [
|
|
305
|
+
under,
|
|
306
|
+
wanted,
|
|
307
|
+
best_one,
|
|
308
|
+
targets[index].get_path(),
|
|
309
|
+
_shortened(lines[index]),
|
|
310
|
+
index,
|
|
311
|
+
]
|
|
312
|
+
),
|
|
292
313
|
}
|
|
293
|
-
var found: Dictionary = {"says": wanted, "index": index, "of": targets.size()}
|
|
314
|
+
var found: Dictionary = { "says": wanted, "index": index, "of": targets.size() }
|
|
294
315
|
if not wholes[index]:
|
|
295
316
|
found["partOf"] = _shortened(lines[index])
|
|
296
317
|
if which == null and targets.size() > 1:
|
|
@@ -305,7 +326,7 @@ static func control_saying(root: Node, root_path: String, wanted: String, which:
|
|
|
305
326
|
found["picked"] = "%s; %s" % [_described(1, ranks[0], wanted, under), others]
|
|
306
327
|
if covered > 0:
|
|
307
328
|
found["covered"] = covered
|
|
308
|
-
var answer: Dictionary = {"path": str(targets[index].get_path()), "found": found}
|
|
329
|
+
var answer: Dictionary = { "path": str(targets[index].get_path()), "found": found }
|
|
309
330
|
if picked_items[index] != null:
|
|
310
331
|
answer["item"] = picked_items[index]
|
|
311
332
|
found["item"] = GdhItems.item_named(targets[index], picked_items[index], lines[index])
|
|
@@ -392,11 +413,14 @@ static func _inside_a_word(written: String, wanted: String) -> bool:
|
|
|
392
413
|
return found
|
|
393
414
|
|
|
394
415
|
|
|
395
|
-
## Whether the character [param code] is part of a word: a letter, a digit or an underscore. A
|
|
396
|
-
## is told by having a case, which leaves scripts without one counted as breaks between
|
|
416
|
+
## Whether the character [param code] is part of a word: a letter, a digit or an underscore. A
|
|
417
|
+
## letter is told by having a case, which leaves scripts without one counted as breaks between
|
|
418
|
+
## words.
|
|
397
419
|
static func _in_a_word(code: int) -> bool:
|
|
398
420
|
var character: String = String.chr(code)
|
|
399
|
-
return
|
|
421
|
+
return (
|
|
422
|
+
character.to_lower() != character.to_upper() or character.is_valid_int() or character == "_"
|
|
423
|
+
)
|
|
400
424
|
|
|
401
425
|
|
|
402
426
|
## [param count] matches of [param rank], as the start of a sentence.
|
|
@@ -432,7 +456,8 @@ static func _passed_over(best: int, others: Array[int], one: bool) -> String:
|
|
|
432
456
|
reasons.append("is not an enabled button" if one else "are not enabled buttons")
|
|
433
457
|
if lacking & WHOLE:
|
|
434
458
|
reasons.append("says it as part of more" if one else "say it as part of more")
|
|
435
|
-
# Ranked level with the best, a match was passed over for having the words only inside longer
|
|
459
|
+
# Ranked level with the best, a match was passed over for having the words only inside longer
|
|
460
|
+
# ones.
|
|
436
461
|
if reasons.is_empty():
|
|
437
462
|
return "says it only inside a longer word" if one else "say it only inside longer words"
|
|
438
463
|
if reasons.size() == 1:
|
|
@@ -476,7 +501,8 @@ static func _outside_note(outside: int, root: Node) -> String:
|
|
|
476
501
|
if outside == 0:
|
|
477
502
|
return ""
|
|
478
503
|
var note: String = (
|
|
479
|
-
"; %d control%s it outside what the screen shows"
|
|
504
|
+
"; %d control%s it outside what the screen shows"
|
|
505
|
+
% [outside, " says" if outside == 1 else "s say"]
|
|
480
506
|
)
|
|
481
507
|
var window: Window = root as Window
|
|
482
508
|
return note if window == null else note + no_window_note(window, window)
|
|
@@ -499,7 +525,9 @@ static func no_window_note(viewport: Viewport, root: Window) -> String:
|
|
|
499
525
|
". This game has no window, and a game with no window has a 64 by 64 viewport whatever "
|
|
500
526
|
+ "the project settings say: run it with a window to reach this control"
|
|
501
527
|
)
|
|
502
|
-
return
|
|
528
|
+
return (
|
|
529
|
+
". This game has no window, and its viewport is %d by %d" % [roundi(size.x), roundi(size.y)]
|
|
530
|
+
)
|
|
503
531
|
|
|
504
532
|
|
|
505
533
|
## What to add to a refusal that found nothing on screen, when the controls saying it are covered.
|
|
@@ -508,7 +536,10 @@ static func _covered_note(covered: int, cover: Node) -> String:
|
|
|
508
536
|
return ""
|
|
509
537
|
if covered == 1:
|
|
510
538
|
return "; 1 control says it under %s, which is drawn over it" % cover.get_path()
|
|
511
|
-
return
|
|
539
|
+
return (
|
|
540
|
+
"; %d controls say it under what is drawn over them, such as %s"
|
|
541
|
+
% [covered, cover.get_path()]
|
|
542
|
+
)
|
|
512
543
|
|
|
513
544
|
|
|
514
545
|
## [param control], or the button holding it when it is not one itself, which is what a click on
|
|
@@ -552,7 +583,10 @@ static func why_hidden(node: Node) -> String:
|
|
|
552
583
|
var control: Control = node as Control
|
|
553
584
|
var instead: Node = null if control == null else drawn_at(control)
|
|
554
585
|
if instead != null:
|
|
555
|
-
return
|
|
586
|
+
return (
|
|
587
|
+
" (%s; a click where it would be reaches %s)"
|
|
588
|
+
% [", and ".join(named), instead.get_path()]
|
|
589
|
+
)
|
|
556
590
|
var outermost: Node = hiding.back()
|
|
557
591
|
var beside: Array[String] = []
|
|
558
592
|
var parent: Node = outermost.get_parent()
|
|
@@ -702,7 +736,9 @@ static func _areas_of(drawn: Array[Node]) -> Array[Rect2]:
|
|
|
702
736
|
areas.append(Rect2(window.position, window.size))
|
|
703
737
|
else:
|
|
704
738
|
var control: Control = each
|
|
705
|
-
areas.append(
|
|
739
|
+
areas.append(
|
|
740
|
+
control.get_global_transform_with_canvas() * Rect2(Vector2.ZERO, control.size)
|
|
741
|
+
)
|
|
706
742
|
return areas
|
|
707
743
|
|
|
708
744
|
|
|
@@ -717,16 +753,18 @@ static func _areas_of(drawn: Array[Node]) -> Array[Rect2]:
|
|
|
717
753
|
## the click scrolls it into view first: what is drawn over its place now is not what it will land
|
|
718
754
|
## under. Judged not covered at all instead, a button below the fold of a roster that a full-screen
|
|
719
755
|
## page covered was picked over the ones in view, scrolled up under the page and pressed there.
|
|
720
|
-
## Whatever else the scroll holds moves with it and is judged where it goes. Judged where it sat,
|
|
721
|
-
## card at the top of a tray scrolled down was drawn over a button above the view, because a
|
|
722
|
-
## card is drawn over an earlier one, and a button below the view was passed by the same card
|
|
723
|
-
## because the cards beside the bottom edge come before it.
|
|
756
|
+
## Whatever else the scroll holds moves with it and is judged where it goes. Judged where it sat,
|
|
757
|
+
## the card at the top of a tray scrolled down was drawn over a button above the view, because a
|
|
758
|
+
## later card is drawn over an earlier one, and a button below the view was passed by the same card
|
|
759
|
+
## only because the cards beside the bottom edge come before it.
|
|
724
760
|
##
|
|
725
761
|
## [param areas] holds the box around each of [param drawn], so everything whose box misses the
|
|
726
762
|
## point is passed over before anything costlier is asked of it. A window's box is in the space of
|
|
727
763
|
## the viewport it is embedded in, which is not the target's when the target is in another window,
|
|
728
764
|
## so a window is judged by [method _window_over] instead.
|
|
729
|
-
static func _cover_of(
|
|
765
|
+
static func _cover_of(
|
|
766
|
+
target: Control, drawn: Array[Node], areas: Array[Rect2], aim: Vector2
|
|
767
|
+
) -> Node:
|
|
730
768
|
var viewport: Viewport = target.get_viewport()
|
|
731
769
|
var moves: Dictionary[ScrollContainer, Vector2] = _scrolls_for(target)
|
|
732
770
|
var point: Vector2 = target.get_global_transform_with_canvas() * aim + _moved_by(target, moves)
|
|
@@ -826,7 +864,9 @@ static func _holds(control: Control, point: Vector2) -> bool:
|
|
|
826
864
|
## inside each, innermost first, which is how one brings a control into view. A target larger than a
|
|
827
865
|
## container is centred in it. Only a ScrollContainer, because that is all a click scrolls: another
|
|
828
866
|
## container clipping what it holds, a collapsed section for one, leaves the target where it is.
|
|
829
|
-
static func _where_it_lands(
|
|
867
|
+
static func _where_it_lands(
|
|
868
|
+
target: Control, point: Vector2
|
|
869
|
+
) -> Dictionary[ScrollContainer, Vector2]:
|
|
830
870
|
var drawn: Transform2D = target.get_global_transform_with_canvas()
|
|
831
871
|
var half: Vector2 = (drawn * Rect2(Vector2.ZERO, target.size)).size * 0.5
|
|
832
872
|
var moves: Dictionary[ScrollContainer, Vector2] = {}
|
|
@@ -835,7 +875,9 @@ static func _where_it_lands(target: Control, point: Vector2) -> Dictionary[Scrol
|
|
|
835
875
|
while walk != null and not (walk is Viewport):
|
|
836
876
|
var holder: ScrollContainer = walk as ScrollContainer
|
|
837
877
|
if holder != null:
|
|
838
|
-
var shown: Rect2 =
|
|
878
|
+
var shown: Rect2 = (
|
|
879
|
+
holder.get_global_transform_with_canvas() * Rect2(Vector2.ZERO, holder.size)
|
|
880
|
+
)
|
|
839
881
|
var low: Vector2 = shown.position + half
|
|
840
882
|
var high: Vector2 = shown.end - half
|
|
841
883
|
var next: Vector2 = Vector2(
|
|
@@ -857,7 +899,10 @@ static func _where_it_lands(target: Control, point: Vector2) -> Dictionary[Scrol
|
|
|
857
899
|
static func _off_screen(target: Control, aim: Vector2) -> bool:
|
|
858
900
|
var moves: Dictionary[ScrollContainer, Vector2] = _scrolls_for(target)
|
|
859
901
|
var point: Vector2 = target.get_global_transform_with_canvas() * aim + _moved_by(target, moves)
|
|
860
|
-
if
|
|
902
|
+
if (
|
|
903
|
+
_clipped_away(target, point, moves)
|
|
904
|
+
or not target.get_viewport().get_visible_rect().has_point(point)
|
|
905
|
+
):
|
|
861
906
|
return true
|
|
862
907
|
var reached: Dictionary = GdhScreen.reach(target.get_viewport(), point)
|
|
863
908
|
var viewport: Viewport = reached["viewport"]
|
|
@@ -877,7 +922,11 @@ static func _clipped_away(
|
|
|
877
922
|
var walk: Node = control.get_parent()
|
|
878
923
|
while walk != null and not (walk is Viewport):
|
|
879
924
|
var holder: Control = walk as Control
|
|
880
|
-
if
|
|
925
|
+
if (
|
|
926
|
+
holder != null
|
|
927
|
+
and holder.clip_contents
|
|
928
|
+
and not _holds(holder, point - _moved_by(holder, moves))
|
|
929
|
+
):
|
|
881
930
|
return true
|
|
882
931
|
walk = walk.get_parent()
|
|
883
932
|
return false
|
|
@@ -901,9 +950,9 @@ static func _candidates(targets: Array[Control], said: Array[String]) -> String:
|
|
|
901
950
|
|
|
902
951
|
|
|
903
952
|
## What to add to a refusal that found nothing on screen, when an open menu has an item saying the
|
|
904
|
-
## words: a menu draws its items rather than holding a control for each, so a click finds none,
|
|
905
|
-
## a wait for the words finds the item. A wait met on "Leave the hall" was followed by a click
|
|
906
|
-
## refused as nothing on screen saying it, with no word of the call that chooses it.
|
|
953
|
+
## words: a menu draws its items rather than holding a control for each, so a click finds none,
|
|
954
|
+
## while a wait for the words finds the item. A wait met on "Leave the hall" was followed by a click
|
|
955
|
+
## on it refused as nothing on screen saying it, with no word of the call that chooses it.
|
|
907
956
|
static func _open_menu_note(root: Node, wanted: String) -> String:
|
|
908
957
|
var pending: Array[Node] = [root]
|
|
909
958
|
while not pending.is_empty():
|
|
@@ -914,11 +963,13 @@ static func _open_menu_note(root: Node, wanted: String) -> String:
|
|
|
914
963
|
var text: String = menu.get_item_text(item)
|
|
915
964
|
if GdhSays.matches(text, wanted):
|
|
916
965
|
var owner: Node = menu.get_parent()
|
|
917
|
-
var named: Node =
|
|
966
|
+
var named: Node = (
|
|
967
|
+
owner if owner is MenuButton or owner is OptionButton else menu
|
|
968
|
+
)
|
|
918
969
|
return (
|
|
919
970
|
(
|
|
920
|
-
'; the open menu %s has an item saying it, "%s", which a click cannot
|
|
921
|
-
+ ' choose it with runtime_input choose, path %s and text "%s"'
|
|
971
|
+
'; the open menu %s has an item saying it, "%s", which a click cannot '
|
|
972
|
+
+ 'reach: choose it with runtime_input choose, path %s and text "%s"'
|
|
922
973
|
)
|
|
923
974
|
% [menu.get_path(), text, named.get_path(), text]
|
|
924
975
|
)
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
extends RefCounted
|
|
2
|
+
## What reaches the running game the way a touchscreen reaches it: a finger pressed, held, lifted
|
|
3
|
+
## and dragged, as InputEventScreenTouch and InputEventScreenDrag.
|
|
4
|
+
##
|
|
5
|
+
## The events themselves rather than a mouse standing in for them: input code tells a finger from a
|
|
6
|
+
## mouse by the event's class, or by the mouse events the engine makes from a touch, which carry
|
|
7
|
+
## InputEvent.DEVICE_ID_EMULATION. A mouse_click carries the mouse's own device, so a game switching
|
|
8
|
+
## its glyphs or focus on a touch never saw one. Sent through Input for the game's window, so the
|
|
9
|
+
## engine makes those mouse events itself where input_devices/pointing/emulate_mouse_from_touch is
|
|
10
|
+
## on.
|
|
11
|
+
|
|
12
|
+
const GdhRead = preload("reading.gd")
|
|
13
|
+
const GdhValues = preload("runtime_values.gd")
|
|
14
|
+
const GdhMouse = preload("runtime_mouse.gd")
|
|
15
|
+
const EMULATE_SETTING: String = "input_devices/pointing/emulate_mouse_from_touch"
|
|
16
|
+
|
|
17
|
+
var _host: Node
|
|
18
|
+
# The finger the engine makes mouse events from, as Input keeps it for the touches sent here: the
|
|
19
|
+
# first one pressed while none is, until it lifts. -1 for none.
|
|
20
|
+
var _emulating: int = -1
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
func _init(host: Node) -> void:
|
|
24
|
+
_host = host
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
## Presses a finger at a position, and lifts it a frame later unless asked to hold it, as
|
|
28
|
+
## inject_mouse_click does a button: a finger left down is a touch that never ends.
|
|
29
|
+
func inject_touch(params: Dictionary) -> Dictionary:
|
|
30
|
+
var root: Window = _host.get_tree().root
|
|
31
|
+
var aimed: Variant = GdhMouse.aim_of(params, root)
|
|
32
|
+
if aimed is String:
|
|
33
|
+
return { "type": "error", "message": aimed }
|
|
34
|
+
var aim: Dictionary = aimed
|
|
35
|
+
var point: Variant = GdhMouse.read_point(params, "x", "y")
|
|
36
|
+
if point is String:
|
|
37
|
+
return { "type": "error", "message": point }
|
|
38
|
+
var finger: Variant = _finger_of(params)
|
|
39
|
+
if finger is String:
|
|
40
|
+
return { "type": "error", "message": finger }
|
|
41
|
+
var index: int = finger
|
|
42
|
+
var holding: Variant = GdhValues.held_for(params)
|
|
43
|
+
if holding is String:
|
|
44
|
+
return { "type": "error", "message": holding }
|
|
45
|
+
var frames: int = holding
|
|
46
|
+
var local: Vector2 = point
|
|
47
|
+
var placed: Dictionary = GdhMouse.carried(aim, local, root)
|
|
48
|
+
var viewport: Viewport = placed["viewport"]
|
|
49
|
+
var position: Vector2 = placed["position"]
|
|
50
|
+
var held: bool = GdhRead.as_bool(params.get("pressed", true), true)
|
|
51
|
+
var whole: bool = not params.has("pressed")
|
|
52
|
+
|
|
53
|
+
var at: Array[Vector2] = [position]
|
|
54
|
+
var where: Dictionary = GdhMouse.where(aim, viewport, at)
|
|
55
|
+
var emulates: bool = _send(viewport, _touch(position, index, whole or held))
|
|
56
|
+
var lasted: Variant = null
|
|
57
|
+
if whole:
|
|
58
|
+
lasted = await GdhValues.hold(_host.get_tree(), frames)
|
|
59
|
+
var _lifted: bool = _send(viewport, _touch(position, index, false))
|
|
60
|
+
await _host.get_tree().process_frame
|
|
61
|
+
|
|
62
|
+
var answer: Dictionary = {
|
|
63
|
+
"type": "input_injected",
|
|
64
|
+
"input_type": "touch",
|
|
65
|
+
"index": index,
|
|
66
|
+
"position": [position.x, position.y],
|
|
67
|
+
"held_frames": GdhValues.held_answer(whole, frames),
|
|
68
|
+
"held_for": lasted,
|
|
69
|
+
"pressed": held and not whole,
|
|
70
|
+
"whole": whole,
|
|
71
|
+
"emulates_mouse": emulates,
|
|
72
|
+
}
|
|
73
|
+
answer.merge(where)
|
|
74
|
+
return answer
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
## Drags a finger from one point to another through any between: the touch, one drag each process
|
|
78
|
+
## frame along the stroke, and the lift where the last drag left it, a frame after it. Each drag
|
|
79
|
+
## goes in a frame of its own because the engine merges the drags it is given within one frame, as
|
|
80
|
+
## it merges a mouse's motions.
|
|
81
|
+
func inject_touch_drag(params: Dictionary) -> Dictionary:
|
|
82
|
+
var root: Window = _host.get_tree().root
|
|
83
|
+
var aimed: Variant = GdhMouse.aim_of(params, root)
|
|
84
|
+
if aimed is String:
|
|
85
|
+
return { "type": "error", "message": aimed }
|
|
86
|
+
var aim: Dictionary = aimed
|
|
87
|
+
var stroke: Variant = GdhMouse.stroke_of(params, "touch_drag")
|
|
88
|
+
if stroke is String:
|
|
89
|
+
return { "type": "error", "message": stroke }
|
|
90
|
+
var finger: Variant = _finger_of(params)
|
|
91
|
+
if finger is String:
|
|
92
|
+
return { "type": "error", "message": finger }
|
|
93
|
+
var index: int = finger
|
|
94
|
+
var frames: int = GdhRead.as_int(params.get("frames", 0), 0)
|
|
95
|
+
if frames < 1 or frames > GdhValues.MOST_HELD:
|
|
96
|
+
return {
|
|
97
|
+
"type": "error",
|
|
98
|
+
"message": "frames is %d, and it takes 1 to %d." % [frames, GdhValues.MOST_HELD],
|
|
99
|
+
}
|
|
100
|
+
# Carried once, before the touch: a finger drags across the screen, not along a node that moves.
|
|
101
|
+
var named: Array[Vector2] = stroke
|
|
102
|
+
var points: Array[Vector2] = []
|
|
103
|
+
var viewport: Viewport = root
|
|
104
|
+
for local: Vector2 in named:
|
|
105
|
+
var placed: Dictionary = GdhMouse.carried(aim, local, root)
|
|
106
|
+
viewport = placed["viewport"]
|
|
107
|
+
points.append(placed["position"])
|
|
108
|
+
|
|
109
|
+
var lengths: Array[float] = GdhMouse.lengths_of(points)
|
|
110
|
+
var start: Vector2 = points[0]
|
|
111
|
+
var end: Vector2 = points[points.size() - 1]
|
|
112
|
+
var where: Dictionary = GdhMouse.where(aim, viewport, points)
|
|
113
|
+
var emulates: bool = _send(viewport, _touch(start, index, true))
|
|
114
|
+
var last: Vector2 = start
|
|
115
|
+
var travelled: Vector2 = Vector2.ZERO
|
|
116
|
+
for step: int in range(1, frames + 1):
|
|
117
|
+
await _host.get_tree().process_frame
|
|
118
|
+
if not is_instance_valid(viewport):
|
|
119
|
+
return {
|
|
120
|
+
"type": "error",
|
|
121
|
+
"message":
|
|
122
|
+
(
|
|
123
|
+
(
|
|
124
|
+
"%s was freed after the touch and %d of %d drags, so the rest of the stroke"
|
|
125
|
+
+ " and the lift went nowhere"
|
|
126
|
+
)
|
|
127
|
+
% [where.get("window", "The window"), step - 1, frames]
|
|
128
|
+
),
|
|
129
|
+
}
|
|
130
|
+
var at: Vector2 = GdhMouse.along(points, lengths, float(step) / frames)
|
|
131
|
+
var relative: Vector2 = at - last
|
|
132
|
+
last = at
|
|
133
|
+
travelled += relative
|
|
134
|
+
var _dragged: bool = _send(viewport, _drag(at, relative, index))
|
|
135
|
+
await _host.get_tree().process_frame
|
|
136
|
+
var _lifted: bool = _send(viewport, _touch(end, index, false))
|
|
137
|
+
await _host.get_tree().process_frame
|
|
138
|
+
|
|
139
|
+
var answer: Dictionary = {
|
|
140
|
+
"type": "input_injected",
|
|
141
|
+
"input_type": "touch_drag",
|
|
142
|
+
"index": index,
|
|
143
|
+
"from": [start.x, start.y],
|
|
144
|
+
"to": [end.x, end.y],
|
|
145
|
+
"motions": frames,
|
|
146
|
+
"relative_total": [travelled.x, travelled.y],
|
|
147
|
+
"emulates_mouse": emulates,
|
|
148
|
+
}
|
|
149
|
+
answer.merge(where)
|
|
150
|
+
return answer
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
## The finger [param params].index names, 0 when none is given, or the String refusing it.
|
|
154
|
+
static func _finger_of(params: Dictionary) -> Variant:
|
|
155
|
+
if not params.has("index"):
|
|
156
|
+
return 0
|
|
157
|
+
var given: Variant = params["index"]
|
|
158
|
+
var number: float = GdhRead.as_float(given)
|
|
159
|
+
if not (given is int or given is float) or number != roundf(number):
|
|
160
|
+
return "index is the finger, a whole number from 0, and was %s." % JSON.stringify(given)
|
|
161
|
+
var index: int = roundi(number)
|
|
162
|
+
if index < 0:
|
|
163
|
+
return "index is the finger, a whole number from 0, and was %d." % index
|
|
164
|
+
return index
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
## Sends [param event] into [param viewport]: through Input for the game's window, and pushed into
|
|
168
|
+
## any other, since Input reaches only the game's window. Answers whether the engine makes a mouse
|
|
169
|
+
## event from it, which it does in Input, with the setting on, for one finger at a time.
|
|
170
|
+
func _send(viewport: Viewport, event: InputEvent) -> bool:
|
|
171
|
+
if viewport != _host.get_tree().root:
|
|
172
|
+
if is_instance_valid(viewport):
|
|
173
|
+
viewport.push_input(event)
|
|
174
|
+
return false
|
|
175
|
+
Input.parse_input_event(event)
|
|
176
|
+
if not GdhRead.as_bool(ProjectSettings.get_setting(EMULATE_SETTING, true), true):
|
|
177
|
+
return false
|
|
178
|
+
var touch: InputEventScreenTouch = event as InputEventScreenTouch
|
|
179
|
+
if touch == null:
|
|
180
|
+
return (event as InputEventScreenDrag).index == _emulating
|
|
181
|
+
if touch.pressed:
|
|
182
|
+
if _emulating != -1:
|
|
183
|
+
return false
|
|
184
|
+
_emulating = touch.index
|
|
185
|
+
return true
|
|
186
|
+
if touch.index != _emulating:
|
|
187
|
+
return false
|
|
188
|
+
_emulating = -1
|
|
189
|
+
return true
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
static func _touch(position: Vector2, index: int, pressed: bool) -> InputEventScreenTouch:
|
|
193
|
+
var event: InputEventScreenTouch = InputEventScreenTouch.new()
|
|
194
|
+
event.index = index
|
|
195
|
+
event.position = position
|
|
196
|
+
event.pressed = pressed
|
|
197
|
+
return event
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
## A drag carrying [param relative] as the movement since the last, and the speed that is at the
|
|
201
|
+
## frame rate the game runs at.
|
|
202
|
+
func _drag(position: Vector2, relative: Vector2, index: int) -> InputEventScreenDrag:
|
|
203
|
+
var event: InputEventScreenDrag = InputEventScreenDrag.new()
|
|
204
|
+
event.index = index
|
|
205
|
+
event.position = position
|
|
206
|
+
event.relative = relative
|
|
207
|
+
event.screen_relative = relative
|
|
208
|
+
var seconds: float = maxf(_host.get_process_delta_time(), 1.0 / 60.0)
|
|
209
|
+
event.velocity = relative / seconds
|
|
210
|
+
event.screen_velocity = event.velocity
|
|
211
|
+
return event
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
uid://ciivqlhs2dev0
|