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.
Files changed (78) hide show
  1. package/build/cli.js +4668 -3229
  2. package/build/godot/addons/gdharness_editor/animation_reads.gd +115 -4
  3. package/build/godot/addons/gdharness_editor/declared_values.gd +272 -0
  4. package/build/godot/addons/gdharness_editor/file_reads.gd +115 -18
  5. package/build/godot/addons/gdharness_editor/headless_check.gd +49 -2
  6. package/build/godot/addons/gdharness_editor/property_paths.gd +89 -16
  7. package/build/godot/addons/gdharness_editor/property_values.gd +180 -121
  8. package/build/godot/addons/gdharness_editor/reading.gd +112 -6
  9. package/build/godot/addons/gdharness_editor/scene_file.gd +117 -174
  10. package/build/godot/addons/gdharness_editor/scene_uids.gd +107 -0
  11. package/build/godot/addons/gdharness_editor/scene_uids.gd.uid +1 -0
  12. package/build/godot/addons/gdharness_editor/serialisation.gd +19 -29
  13. package/build/godot/addons/gdharness_editor/setting_reads.gd +121 -0
  14. package/build/godot/addons/gdharness_editor/setting_reads.gd.uid +1 -0
  15. package/build/godot/addons/gdharness_editor/tool_executor.gd +39 -6
  16. package/build/godot/addons/gdharness_editor/tools/animation_states.gd +263 -0
  17. package/build/godot/addons/gdharness_editor/tools/animation_states.gd.uid +1 -0
  18. package/build/godot/addons/gdharness_editor/tools/animation_tools.gd +49 -414
  19. package/build/godot/addons/gdharness_editor/tools/animation_tracks.gd +464 -0
  20. package/build/godot/addons/gdharness_editor/tools/animation_tracks.gd.uid +1 -0
  21. package/build/godot/addons/gdharness_editor/tools/class_tools.gd +37 -0
  22. package/build/godot/addons/gdharness_editor/tools/navigation_bake.gd +221 -0
  23. package/build/godot/addons/gdharness_editor/tools/navigation_bake.gd.uid +1 -0
  24. package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +162 -483
  25. package/build/godot/addons/gdharness_editor/tools/scene_groups.gd +16 -0
  26. package/build/godot/addons/gdharness_editor/tools/scene_kin.gd +8 -4
  27. package/build/godot/addons/gdharness_editor/tools/scene_paths.gd +2 -1
  28. package/build/godot/addons/gdharness_editor/tools/scene_placing.gd +10 -0
  29. package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +151 -8
  30. package/build/godot/addons/gdharness_editor/tools/settings_tools.gd +7 -30
  31. package/build/godot/addons/gdharness_editor/tools/signal_tools.gd +190 -38
  32. package/build/godot/addons/gdharness_editor/tools/tileset_tools.gd +485 -0
  33. package/build/godot/addons/gdharness_editor/tools/tileset_tools.gd.uid +1 -0
  34. package/build/godot/addons/gdharness_editor/value_shapes.gd +128 -1
  35. package/build/godot/addons/gdharness_runtime/call_errors.gd +8 -2
  36. package/build/godot/addons/gdharness_runtime/declared_values.gd +272 -0
  37. package/build/godot/addons/gdharness_runtime/reading.gd +112 -6
  38. package/build/godot/addons/gdharness_runtime/runtime_autoload.gd +35 -3
  39. package/build/godot/addons/gdharness_runtime/runtime_capture.gd +5 -0
  40. package/build/godot/addons/gdharness_runtime/runtime_changes.gd +188 -30
  41. package/build/godot/addons/gdharness_runtime/runtime_input.gd +107 -104
  42. package/build/godot/addons/gdharness_runtime/runtime_mouse.gd +17 -0
  43. package/build/godot/addons/gdharness_runtime/runtime_pad.gd +13 -4
  44. package/build/godot/addons/gdharness_runtime/runtime_paths.gd +320 -27
  45. package/build/godot/addons/gdharness_runtime/runtime_queries.gd +165 -20
  46. package/build/godot/addons/gdharness_runtime/runtime_screen.gd +49 -3
  47. package/build/godot/addons/gdharness_runtime/runtime_targets.gd +31 -2
  48. package/build/godot/addons/gdharness_runtime/runtime_values.gd +250 -10
  49. package/build/godot/addons/gdharness_runtime/runtime_waits.gd +279 -11
  50. package/build/godot/addons/gdharness_runtime/runtime_words.gd +27 -0
  51. package/build/godot/addons/gdharness_runtime/serialisation.gd +19 -29
  52. package/build/godot/addons/gdharness_runtime/value_shapes.gd +128 -1
  53. package/build/godot/operations/animation_reads.gd +115 -4
  54. package/build/godot/operations/audio_buses.gd +93 -7
  55. package/build/godot/operations/classdb_queries.gd +119 -7
  56. package/build/godot/operations/declared_values.gd +272 -0
  57. package/build/godot/operations/dependencies.gd +154 -41
  58. package/build/godot/operations/editor_tools.gd +29 -13
  59. package/build/godot/operations/export_presets.gd +602 -4
  60. package/build/godot/operations/file_reads.gd +115 -18
  61. package/build/godot/operations/gdscript_analysis.gd +14 -0
  62. package/build/godot/operations/gdscript_authoring.gd +12 -2
  63. package/build/godot/operations/godot_operations.gd +18 -2
  64. package/build/godot/operations/import_pipeline.gd +20 -221
  65. package/build/godot/operations/input_actions.gd +127 -51
  66. package/build/godot/operations/project_config.gd +113 -68
  67. package/build/godot/operations/project_diagnostics.gd +4 -0
  68. package/build/godot/operations/project_validation.gd +266 -0
  69. package/build/godot/operations/property_paths.gd +89 -16
  70. package/build/godot/operations/property_values.gd +180 -121
  71. package/build/godot/operations/reading.gd +112 -6
  72. package/build/godot/operations/scene_connections.gd +222 -0
  73. package/build/godot/operations/serialisation.gd +19 -29
  74. package/build/godot/operations/setting_reads.gd +121 -0
  75. package/build/godot/operations/value_shapes.gd +128 -1
  76. package/build/godot/tests/listed.gd +20 -13
  77. package/build/keeper.js +1402 -707
  78. package/package.json +1 -1
@@ -21,15 +21,6 @@ const GdhMouse = preload("runtime_mouse.gd")
21
21
  ## everything that process starts. Kept in step with `DESKTOP_VARIABLE` in src/desktop.ts.
22
22
  const DESKTOP_VARIABLE: String = "GDHARNESS_DESKTOP"
23
23
 
24
- ## Node.ProcessMode as its names, for a refusal to say which one is in force.
25
- const PROCESS_MODES: Dictionary[int, String] = {
26
- Node.PROCESS_MODE_INHERIT: "INHERIT",
27
- Node.PROCESS_MODE_PAUSABLE: "PAUSABLE",
28
- Node.PROCESS_MODE_WHEN_PAUSED: "WHEN_PAUSED",
29
- Node.PROCESS_MODE_ALWAYS: "ALWAYS",
30
- Node.PROCESS_MODE_DISABLED: "DISABLED",
31
- }
32
-
33
24
  ## Whether this run has had the pointer note in full; the game process is the run.
34
25
  static var _pointer_explained: bool = false
35
26
 
@@ -151,50 +142,38 @@ func inject_action(params: Dictionary) -> Dictionary:
151
142
  return {"type": "error", "message": "Action name required"}
152
143
 
153
144
  if not InputMap.has_action(action):
154
- return {"type": "error", "message": "Action not found: " + action}
145
+ return {"type": "error", "message": GdhValues.no_action(action)}
155
146
  var holding: Variant = GdhValues.held_for(params)
156
147
  if holding is String:
157
148
  return {"type": "error", "message": holding}
158
149
  var frames: int = holding
159
150
 
160
- var focused: String = _focus_owner()
151
+ var focused: String = GdhValues.focus_path(_host)
161
152
  _say_action(action, whole or held, strength)
153
+ var shortcuts: Dictionary = {"shortcuts": [], "shortcut_pressed": null}
154
+ if whole or held:
155
+ shortcuts = await GdhValues.press_shortcuts(_host.get_tree().root, action)
162
156
  var lasted: Variant = null
163
157
  if whole:
164
158
  # A frame between the halves at least, as a click has: a listener that acts on the press and
165
159
  # a listener that acts on the release both get their own frame to do it in.
166
160
  lasted = await GdhValues.hold(_host.get_tree(), frames)
167
161
  _say_action(action, false, strength)
168
- await _host.get_tree().process_frame
169
-
170
- return _focus_noted(
171
- {
172
- "type": "input_injected",
173
- "input_type": "action",
174
- "action": action,
175
- "pressed": held and not whole,
176
- "whole": whole,
177
- "held_frames": GdhValues.held_answer(whole, frames),
178
- "held_for": lasted,
179
- },
180
- focused
181
- )
182
-
183
-
184
- ## The path of the Control holding the keyboard focus, or "" when none does. Read before a key or an
185
- ## action goes in, since pressing a focused button can move the focus.
186
- func _focus_owner() -> String:
187
- var holding: Control = _host.get_viewport().gui_get_focus_owner()
188
- return "" if holding == null else str(holding.get_path())
189
-
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
190
164
 
191
- ## [param said] with [param focused], the Control that had the focus, under focused. A key or an action
192
- ## reaches that Control before the game's _unhandled_input, so Space on a focused Button pressed it
193
- ## and the game's own handler counted the key as well, with nothing in the answer saying why.
194
- static func _focus_noted(said: Dictionary, focused: String) -> Dictionary:
195
- if not focused.is_empty():
196
- said["focused"] = focused
197
- return said
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)
198
177
 
199
178
 
200
179
  func _say_action(action: String, down: bool, strength: float) -> void:
@@ -250,7 +229,7 @@ func inject_key(params: Dictionary) -> Dictionary:
250
229
  # keys are the code points themselves, which is what makes this a mapping and not a table.
251
230
  event.unicode = GdhValues.glyph_of(event.keycode, event.shift_pressed)
252
231
 
253
- var focused: String = _focus_owner()
232
+ var focused: String = GdhValues.focus_path(_host)
254
233
  Input.parse_input_event(event)
255
234
  var lasted: Variant = null
256
235
  if whole:
@@ -258,25 +237,24 @@ func inject_key(params: Dictionary) -> Dictionary:
258
237
  var up: InputEventKey = event.duplicate()
259
238
  up.pressed = false
260
239
  Input.parse_input_event(up)
261
- await _host.get_tree().process_frame
240
+ await _host.get_tree().process_frame
262
241
 
263
- return _focus_noted(
264
- {
265
- "type": "input_injected",
266
- "input_type": "key",
267
- "held_frames": GdhValues.held_answer(whole, frames),
268
- "held_for": lasted,
269
- "keycode": event.keycode,
270
- "physical_keycode": event.physical_keycode,
271
- "shift": event.shift_pressed,
272
- "ctrl": event.ctrl_pressed,
273
- "alt": event.alt_pressed,
274
- "unicode": event.unicode,
275
- "pressed": held and not whole,
276
- "whole": whole,
277
- },
278
- focused
279
- )
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)
280
258
 
281
259
 
282
260
  ## Clicks a mouse button at a position, and lets go of it again unless asked to hold it.
@@ -317,16 +295,28 @@ func inject_mouse_click(params: Dictionary) -> Dictionary:
317
295
  # its drag from the press, which is where a real pointer would be.
318
296
  var _moved: Vector2 = _arrive(position)
319
297
  var supplied: bool = _follow_in_the_window(position)
320
- Input.parse_input_event(_button(position, button, whole or held, double))
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)
305
+ Input.parse_input_event(press)
321
306
  var lasted: Variant = null
322
307
  if notch:
323
- Input.parse_input_event(_button(position, button, false, false))
308
+ Input.parse_input_event(GdhMouse.button_event(position, button, false, false))
324
309
  elif whole:
325
310
  # A frame between the halves at least, as inject_action has, so a control that acts on the
326
311
  # press and one that acts on the release each get a frame to do it in.
327
312
  lasted = await GdhValues.hold(_host.get_tree(), frames)
328
- Input.parse_input_event(_button(position, button, false, false))
329
- await _host.get_tree().process_frame
313
+ Input.parse_input_event(GdhMouse.button_event(position, button, false, false))
314
+ # Input hands the events on at the next frame, so the control under the pointer is read after it.
315
+ await _host.get_tree().process_frame
316
+ var hovered: Control = _hover.hovered_in(_host.get_viewport())
317
+ var hovered_path: Variant = null
318
+ if hovered != null:
319
+ hovered_path = str(hovered.get_path())
330
320
 
331
321
  var at: Array[Vector2] = [position]
332
322
  var said: Dictionary = _pointer_noted(
@@ -338,6 +328,8 @@ func inject_mouse_click(params: Dictionary) -> Dictionary:
338
328
  "held_for": lasted,
339
329
  "position": [position.x, position.y],
340
330
  "button": button,
331
+ "hovered": hovered_path,
332
+ "actions": GdhValues.actions_of(press),
341
333
  "pressed": held and not whole,
342
334
  "whole": whole,
343
335
  "double": double
@@ -419,7 +411,7 @@ func inject_mouse_drag(params: Dictionary) -> Dictionary:
419
411
  var end: Vector2 = points[points.size() - 1]
420
412
  var _moved: Vector2 = _arrive(start)
421
413
  var supplied: bool = _follow_in_the_window(start)
422
- Input.parse_input_event(_button(start, button, true, false))
414
+ Input.parse_input_event(GdhMouse.button_event(start, button, true, false))
423
415
  var travelled: Vector2 = Vector2.ZERO
424
416
  for step: int in range(1, frames + 1):
425
417
  await _host.get_tree().process_frame
@@ -429,7 +421,7 @@ func inject_mouse_drag(params: Dictionary) -> Dictionary:
429
421
  supplied = _follow_in_the_window(at) or supplied
430
422
  Input.parse_input_event(_motion(at, relative))
431
423
  await _host.get_tree().process_frame
432
- Input.parse_input_event(_button(end, button, false, false))
424
+ Input.parse_input_event(GdhMouse.button_event(end, button, false, false))
433
425
  await _host.get_tree().process_frame
434
426
 
435
427
  var said: Dictionary = _pointer_noted(
@@ -550,6 +542,7 @@ func click(params: Dictionary) -> Dictionary:
550
542
  # other words, a link among them. Said either way, so a press on the middle is never read as one
551
543
  # on the words.
552
544
  var aim: Vector2 = control.size * 0.5
545
+ var at_centre: bool = true
553
546
  var item_scrolled: bool = false
554
547
  if item != null:
555
548
  var found_by_words: Dictionary = found
@@ -559,6 +552,7 @@ func click(params: Dictionary) -> Dictionary:
559
552
  if placed_item.has("message"):
560
553
  return placed_item
561
554
  aim = placed_item["point"]
555
+ at_centre = false
562
556
  item_scrolled = placed_item["scrolled"]
563
557
  var rich: RichTextLabel = control as RichTextLabel
564
558
  if found != null and rich != null:
@@ -566,6 +560,7 @@ func click(params: Dictionary) -> Dictionary:
566
560
  var found_here: Dictionary = found
567
561
  if placed.has("point"):
568
562
  aim = placed["point"]
563
+ at_centre = false
569
564
  found_here["pressedOn"] = "the words"
570
565
  elif placed.has("unplaced"):
571
566
  found_here["pressedOn"] = "the middle of the label"
@@ -602,6 +597,20 @@ func click(params: Dictionary) -> Dictionary:
602
597
  }
603
598
  local = control.get_global_transform_with_canvas() * aim
604
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
+
605
614
  var reached: Dictionary = GdhScreen.reach(own, local)
606
615
  var viewport: Viewport = reached["viewport"]
607
616
  var centre: Vector2 = reached["point"]
@@ -638,6 +647,8 @@ func click(params: Dictionary) -> Dictionary:
638
647
  if hovered != null:
639
648
  hovered_path = str(hovered.get_path())
640
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)
641
652
 
642
653
  # The link a rich label says was pressed, which is its own word for what the press reached.
643
654
  var links: Array = []
@@ -653,9 +664,7 @@ func click(params: Dictionary) -> Dictionary:
653
664
  var was_pressed: Variant = null
654
665
  if toggle != null and toggle.toggle_mode:
655
666
  was_pressed = toggle.button_pressed
656
- _press_button(viewport, _button(position, button, true, double))
657
- await _host.get_tree().process_frame
658
- _press_button(viewport, _button(position, button, false, false))
667
+ var press: InputEventMouseButton = await _click_button(viewport, position, button, double)
659
668
  # The release is what a button acts on, and a queue_free it causes lands at the end of
660
669
  # this frame; the frame passes so the answer describes the control as the click left it.
661
670
  await _host.get_tree().process_frame
@@ -675,9 +684,20 @@ func click(params: Dictionary) -> Dictionary:
675
684
  "double": double,
676
685
  "hovered": hovered_path,
677
686
  "landed": landed,
687
+ "actions": GdhValues.actions_of(press),
678
688
  "control_afterwards": afterwards,
679
689
  "scrolled_into_view": scrolled,
680
690
  }
691
+ if on_part:
692
+ answer["pressed_on"] = "the middle of the part on screen, since no scroll brings its centre into view"
693
+ # A Label and a TextureRect ignore the pointer unless told otherwise, so the click goes to what is
694
+ # under them, and landed false with that control as hovered said nothing of why.
695
+ if not landed and control.mouse_filter == Control.MOUSE_FILTER_IGNORE:
696
+ answer["why_not_landed"] = (
697
+ "%s's mouse_filter is ignore, so the pointer passes through it to what is under it" % node_path
698
+ )
699
+ elif cover != null:
700
+ answer["why_not_landed"] = "%s is drawn over it at the point clicked" % cover.get_path()
681
701
  if found != null:
682
702
  answer["found"] = found
683
703
  if was_pressed != null:
@@ -718,7 +738,7 @@ func _takes_no_click(control: Control, node_path: String) -> String:
718
738
  % [node_path, on]
719
739
  )
720
740
  if not control.can_process():
721
- var why: String = _why_it_does_not_process(control, node_path)
741
+ var why: String = GdhTargets.why_it_does_not_process(control, node_path)
722
742
  var over: Node = GdhTargets.drawn_over(control)
723
743
  var cover: String = "" if over == null else "; %s is drawn over it" % over.get_path()
724
744
  if _host.get_tree().paused:
@@ -730,23 +750,6 @@ func _takes_no_click(control: Control, node_path: String) -> String:
730
750
  return ""
731
751
 
732
752
 
733
- ## Which node's process_mode keeps [param control] from processing, and what it is. A node set to
734
- ## inherit takes its parent's, so the one deciding can be any node above it: the game turning a
735
- ## whole screen off while a menu shows over it reads, on the button, as a button turned off.
736
- static func _why_it_does_not_process(control: Control, node_path: String) -> String:
737
- var deciding: Node = control
738
- while deciding.process_mode == Node.PROCESS_MODE_INHERIT and deciding.get_parent() != null:
739
- deciding = deciding.get_parent()
740
- # The root inherits from nothing, which processes as pausable.
741
- var value: int = deciding.process_mode
742
- if value == Node.PROCESS_MODE_INHERIT:
743
- value = Node.PROCESS_MODE_PAUSABLE
744
- var mode: String = PROCESS_MODES.get(value, str(value))
745
- if deciding == control:
746
- return "its process_mode is %s" % mode
747
- return "%s inherits process_mode %s from %s" % [node_path, mode, deciding.get_path()]
748
-
749
-
750
753
  ## Why a click at [param control]'s centre would reach nothing, or "" when it would reach it: the
751
754
  ## centre outside the viewport it is drawn in, at [param local]; outside the viewport showing that
752
755
  ## one on the screen, which [param reached] holds with the centre there; or clipped away by a
@@ -799,6 +802,23 @@ func _no_window_note(viewport: Viewport) -> String:
799
802
  ##
800
803
  ## Any other viewport is pushed into directly: Input reaches only the game's window. That is a
801
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
+
802
822
  func _press_button(viewport: Viewport, event: InputEventMouseButton) -> void:
803
823
  if viewport == _host.get_tree().root:
804
824
  Input.parse_input_event(event)
@@ -881,9 +901,7 @@ func _click_in_the_world(node_path: String, item: Node3D, params: Dictionary) ->
881
901
  var through: Dictionary = _through_the_interface(out)
882
902
  var hovered_path: Variant = through["hovered"]
883
903
 
884
- _press_button(arriving, _button(position, button, true, double))
885
- await _host.get_tree().process_frame
886
- _press_button(arriving, _button(position, button, false, false))
904
+ var _press: InputEventMouseButton = await _click_button(arriving, position, button, double)
887
905
  await _host.get_tree().process_frame
888
906
 
889
907
  return _pointer_noted(
@@ -979,18 +997,3 @@ func _motion(position: Vector2, relative: Vector2) -> InputEventMouseMotion:
979
997
  event.screen_velocity = event.velocity
980
998
  event.button_mask = Input.get_mouse_button_mask()
981
999
  return event
982
-
983
-
984
- ## A button event carries the buttons held once it has happened: the pressed one among them, the
985
- ## released one out. A wheel step is a button with no mask, as it is from a real mouse.
986
- func _button(position: Vector2, button: int, pressed: bool, double: bool) -> InputEventMouseButton:
987
- var event: InputEventMouseButton = InputEventMouseButton.new()
988
- event.position = position
989
- event.global_position = position
990
- event.button_index = button as MouseButton
991
- event.pressed = pressed
992
- event.double_click = double
993
- var held: int = Input.get_mouse_button_mask()
994
- var own: int = GdhMouse.mask_of(button)
995
- event.button_mask = (held | own) if pressed else (held & ~own)
996
- 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.
@@ -68,16 +68,19 @@ func inject_joypad_button(params: Dictionary) -> Dictionary:
68
68
  return {"type": "error", "message": holding}
69
69
  var frames: int = holding
70
70
 
71
- Input.parse_input_event(_button(button, device, whole or held))
71
+ var focused: String = GdhValues.focus_path(_host)
72
+ var event: InputEventJoypadButton = _button(button, device, whole or held)
73
+ Input.parse_input_event(event)
72
74
  var lasted: Variant = null
73
75
  if whole:
74
76
  # A frame between the halves at least, as a key has, so a game that acts on the press and one
75
77
  # that acts on the release each get a frame to do it in.
76
78
  lasted = await GdhValues.hold(_host.get_tree(), frames)
77
79
  Input.parse_input_event(_button(button, device, false))
78
- await _host.get_tree().process_frame
80
+ # The engine hands a parsed event on at the next frame, so the focus is read after one.
81
+ await _host.get_tree().process_frame
79
82
 
80
- return {
83
+ var answer: Dictionary = {
81
84
  "type": "input_injected",
82
85
  "input_type": "joypad_button",
83
86
  "held_frames": GdhValues.held_answer(whole, frames),
@@ -85,9 +88,11 @@ func inject_joypad_button(params: Dictionary) -> Dictionary:
85
88
  "button": button,
86
89
  "button_name": _name_of(button, JOY_BUTTONS),
87
90
  "device": device,
91
+ "actions": GdhValues.actions_of(event),
88
92
  "pressed": held and not whole,
89
93
  "whole": whole,
90
94
  }
95
+ return GdhValues.focus_noted(answer, focused, _host)
91
96
 
92
97
 
93
98
  ## Moves a stick or trigger axis to a value, where it stays until another motion moves it, as a
@@ -110,16 +115,20 @@ func inject_joypad_motion(params: Dictionary) -> Dictionary:
110
115
  event.axis = axis as JoyAxis
111
116
  event.axis_value = value
112
117
  event.device = device
118
+ var focused: String = GdhValues.focus_path(_host)
113
119
  Input.parse_input_event(event)
120
+ await _host.get_tree().process_frame
114
121
 
115
- return {
122
+ var answer: Dictionary = {
116
123
  "type": "input_injected",
117
124
  "input_type": "joypad_motion",
118
125
  "axis": axis,
119
126
  "axis_name": _name_of(axis, JOY_AXES),
120
127
  "value": value,
121
128
  "device": device,
129
+ "actions": GdhValues.actions_of(event),
122
130
  }
131
+ return GdhValues.focus_noted(answer, focused, _host)
123
132
 
124
133
 
125
134
  static func _button(button: int, device: int, down: bool) -> InputEventJoypadButton: