gdharness 1.3.35 → 1.3.36
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 +226 -57
- package/build/godot/addons/gdharness_editor/animation_reads.gd +17 -1
- package/build/godot/addons/gdharness_editor/reading.gd +10 -2
- package/build/godot/addons/gdharness_editor/scene_file.gd +12 -6
- package/build/godot/addons/gdharness_editor/tools/animation_tracks.gd +57 -0
- package/build/godot/addons/gdharness_editor/tools/filesystem_tools.gd +16 -0
- package/build/godot/addons/gdharness_editor/tools/navigation_bake.gd +81 -4
- package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +3 -3
- package/build/godot/addons/gdharness_editor/tools/scene_kin.gd +4 -2
- package/build/godot/addons/gdharness_editor/tools/scene_paths.gd +59 -0
- package/build/godot/addons/gdharness_editor/tools/script_paths.gd +148 -0
- package/build/godot/addons/gdharness_editor/tools/script_paths.gd.uid +1 -0
- package/build/godot/addons/gdharness_runtime/reading.gd +10 -2
- package/build/godot/addons/gdharness_runtime/runtime_hover.gd +70 -0
- package/build/godot/addons/gdharness_runtime/runtime_input.gd +223 -248
- package/build/godot/addons/gdharness_runtime/runtime_mouse.gd +128 -5
- package/build/godot/addons/gdharness_runtime/runtime_screen.gd +107 -0
- package/build/godot/addons/gdharness_runtime/runtime_targets.gd +15 -1
- package/build/godot/addons/gdharness_runtime/runtime_waits.gd +8 -0
- package/build/godot/addons/gdharness_runtime/runtime_words.gd +22 -0
- package/build/godot/operations/animation_reads.gd +17 -1
- package/build/godot/operations/classdb_queries.gd +16 -0
- package/build/godot/operations/dependencies.gd +46 -4
- package/build/godot/operations/editor_tools.gd +5 -5
- package/build/godot/operations/gdscript_authoring.gd +7 -3
- package/build/godot/operations/input_actions.gd +105 -0
- package/build/godot/operations/project_diagnostics.gd +4 -0
- package/build/godot/operations/project_validation.gd +15 -0
- package/build/godot/operations/reading.gd +10 -2
- package/package.json +1 -1
|
@@ -46,6 +46,9 @@ var _pointer_placed: bool = false
|
|
|
46
46
|
var _pointer_at_usec: int = 0
|
|
47
47
|
var _arrival_seconds: float = 1.0 / 60.0
|
|
48
48
|
|
|
49
|
+
## The buttons a press pushed into a window other than the game's holds, which Input never hears of.
|
|
50
|
+
var _pushed_held: int = 0
|
|
51
|
+
|
|
49
52
|
|
|
50
53
|
func _init(host: Node, values: GdhValues) -> void:
|
|
51
54
|
_host = host
|
|
@@ -94,18 +97,61 @@ static func _hover_noted(answer: Dictionary, supplied: bool) -> Dictionary:
|
|
|
94
97
|
"This window has had no pointer enter from the operating system, and with subwindow"
|
|
95
98
|
+ " embedding off Godot hovers nothing in a native window until it has, so a Button there"
|
|
96
99
|
+ " takes no press. The runtime gave the controls under the pointer the hover the engine"
|
|
97
|
-
+ " would have: their mouse_entered and mouse_exited, and the hover a Button presses on
|
|
100
|
+
+ " would have: their mouse_entered and mouse_exited, and the hover a Button presses on,"
|
|
101
|
+
+ " and told the window the pointer is in it, so a motion reaches the control under it."
|
|
98
102
|
+ " Viewport.gui_get_hovered_control() still answers null in this window. Said once a run;"
|
|
99
103
|
+ " hover_supplied marks every answer it applies to."
|
|
100
104
|
)
|
|
101
105
|
return answer
|
|
102
106
|
|
|
103
107
|
|
|
104
|
-
## Hovers what is under [param position] in
|
|
105
|
-
##
|
|
106
|
-
func
|
|
107
|
-
|
|
108
|
-
|
|
108
|
+
## Hovers what is under [param position], in [param viewport]'s pixels, when the engine cannot: see
|
|
109
|
+
## runtime_hover.gd.
|
|
110
|
+
func _follow_in(viewport: Viewport, position: Vector2) -> bool:
|
|
111
|
+
return _hover.follow(viewport, viewport.get_final_transform().affine_inverse() * position)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
## Sends one event of a mouse_click, mouse_motion or mouse_drag into [param viewport]: through Input
|
|
115
|
+
## for the game's own window, and pushed into any other, as a click sends one, since Input reaches
|
|
116
|
+
## only the game's window. Input keeps the buttons held from the events it is given, so the ones a
|
|
117
|
+
## pushed press holds are kept here for the motions and the release after it.
|
|
118
|
+
func _send(viewport: Viewport, event: InputEventMouse) -> void:
|
|
119
|
+
if viewport == _host.get_tree().root:
|
|
120
|
+
Input.parse_input_event(event)
|
|
121
|
+
return
|
|
122
|
+
# A press can close the window it went to, and its release then has nowhere to go.
|
|
123
|
+
if not is_instance_valid(viewport):
|
|
124
|
+
return
|
|
125
|
+
if event is InputEventMouseButton:
|
|
126
|
+
_pushed_held = event.button_mask
|
|
127
|
+
viewport.push_input(event)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
## The buttons held in [param viewport] before the next event sent there.
|
|
131
|
+
func _held_in(viewport: Viewport) -> int:
|
|
132
|
+
return Input.get_mouse_button_mask() if viewport == _host.get_tree().root else _pushed_held
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
## The viewport whose GUI says what is under the pointer: the named node's own, which for a node in
|
|
136
|
+
## an embedded window is not [param viewport], the one the events went into. Read before the events,
|
|
137
|
+
## since they may free the node.
|
|
138
|
+
static func _own_of(aim: Dictionary, viewport: Viewport) -> Viewport:
|
|
139
|
+
if not aim.has("node"):
|
|
140
|
+
return viewport
|
|
141
|
+
var named: Node = aim["node"]
|
|
142
|
+
return named.get_viewport()
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
## The path of the control under the pointer in [param own], or null, read once the events are
|
|
146
|
+
## handled: a motion and a drag answered nothing of where they left the pointer, so a hover could be
|
|
147
|
+
## checked only by what the game did about it.
|
|
148
|
+
func _hovered_path(own: Viewport) -> Variant:
|
|
149
|
+
if not is_instance_valid(own):
|
|
150
|
+
return null
|
|
151
|
+
var hovered: Control = _hover.hovered_in(own)
|
|
152
|
+
if hovered == null:
|
|
153
|
+
return null
|
|
154
|
+
return str(hovered.get_path())
|
|
109
155
|
|
|
110
156
|
|
|
111
157
|
## The movement a pointer arriving at [param position] carries, which is the distance from where
|
|
@@ -266,10 +312,17 @@ func inject_key(params: Dictionary) -> Dictionary:
|
|
|
266
312
|
## `pressed` is how a caller asks for one half, the way a drag is made: true holds the button down,
|
|
267
313
|
## false lets go of one being held.
|
|
268
314
|
func inject_mouse_click(params: Dictionary) -> Dictionary:
|
|
315
|
+
var aimed: Variant = GdhMouse.aim_of(params, _host.get_tree().root)
|
|
316
|
+
if aimed is String:
|
|
317
|
+
return {"type": "error", "message": aimed}
|
|
318
|
+
var aim: Dictionary = aimed
|
|
269
319
|
var point: Variant = GdhMouse.read_point(params, "x", "y")
|
|
270
320
|
if point is String:
|
|
271
321
|
return {"type": "error", "message": point}
|
|
272
|
-
var
|
|
322
|
+
var local: Vector2 = point
|
|
323
|
+
var placed: Dictionary = GdhMouse.carried(aim, local, _host.get_tree().root)
|
|
324
|
+
var viewport: Viewport = placed["viewport"]
|
|
325
|
+
var position: Vector2 = placed["position"]
|
|
273
326
|
var button: int = GdhMouse.button_of(params.get("button", MOUSE_BUTTON_LEFT))
|
|
274
327
|
if button < 0:
|
|
275
328
|
return GdhMouse.no_such_button(params.get("button"))
|
|
@@ -294,50 +347,49 @@ func inject_mouse_click(params: Dictionary) -> Dictionary:
|
|
|
294
347
|
# A button pressed somewhere puts the pointer there: the motion after a held press measures
|
|
295
348
|
# its drag from the press, which is where a real pointer would be.
|
|
296
349
|
var _moved: Vector2 = _arrive(position)
|
|
297
|
-
var supplied: bool =
|
|
350
|
+
var supplied: bool = _follow_in(viewport, position)
|
|
351
|
+
var at: Array[Vector2] = [position]
|
|
352
|
+
var where: Dictionary = GdhMouse.where(aim, viewport, at)
|
|
353
|
+
var own: Viewport = _own_of(aim, viewport)
|
|
354
|
+
var pressing: Dictionary = _hover.press_of(aim, viewport, position) if held else {}
|
|
298
355
|
# A double click's first click goes whole before it, as [method _click_button] sends one.
|
|
299
356
|
if double and held and not notch:
|
|
300
|
-
|
|
357
|
+
_send(viewport, GdhMouse.button_event(position, button, true, false, _held_in(viewport)))
|
|
301
358
|
await _host.get_tree().process_frame
|
|
302
|
-
|
|
359
|
+
_send(viewport, GdhMouse.button_event(position, button, false, false, _held_in(viewport)))
|
|
303
360
|
await _host.get_tree().process_frame
|
|
304
|
-
var press: InputEventMouseButton = GdhMouse.button_event(
|
|
305
|
-
|
|
361
|
+
var press: InputEventMouseButton = GdhMouse.button_event(
|
|
362
|
+
position, button, whole or held, double, _held_in(viewport)
|
|
363
|
+
)
|
|
364
|
+
_send(viewport, press)
|
|
306
365
|
var lasted: Variant = null
|
|
307
366
|
if notch:
|
|
308
|
-
|
|
367
|
+
_send(viewport, GdhMouse.button_event(position, button, false, false, _held_in(viewport)))
|
|
309
368
|
elif whole:
|
|
310
369
|
# A frame between the halves at least, as inject_action has, so a control that acts on the
|
|
311
370
|
# press and one that acts on the release each get a frame to do it in.
|
|
312
371
|
lasted = await GdhValues.hold(_host.get_tree(), frames)
|
|
313
|
-
|
|
372
|
+
_send(viewport, GdhMouse.button_event(position, button, false, false, _held_in(viewport)))
|
|
314
373
|
# Input hands the events on at the next frame, so the control under the pointer is read after it.
|
|
315
374
|
await _host.get_tree().process_frame
|
|
316
|
-
var
|
|
317
|
-
var hovered_path: Variant = null
|
|
318
|
-
if hovered != null:
|
|
319
|
-
hovered_path = str(hovered.get_path())
|
|
375
|
+
var hovered_path: Variant = _hovered_path(own)
|
|
320
376
|
|
|
321
|
-
var
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
supplied
|
|
338
|
-
)
|
|
339
|
-
)
|
|
340
|
-
return GdhMouse.outside_noted(said, _host.get_viewport(), at)
|
|
377
|
+
var answer: Dictionary = {
|
|
378
|
+
"type": "input_injected",
|
|
379
|
+
"input_type": "mouse_click",
|
|
380
|
+
"held_frames": GdhValues.held_answer(whole and not notch, frames),
|
|
381
|
+
"held_for": lasted,
|
|
382
|
+
"position": [position.x, position.y],
|
|
383
|
+
"button": button,
|
|
384
|
+
"hovered": hovered_path,
|
|
385
|
+
"actions": GdhValues.actions_of(press),
|
|
386
|
+
"pressed": held and not whole,
|
|
387
|
+
"whole": whole,
|
|
388
|
+
"double": double
|
|
389
|
+
}
|
|
390
|
+
answer.merge(where)
|
|
391
|
+
answer.merge(pressing)
|
|
392
|
+
return _pointer_noted(_hover_noted(answer, supplied))
|
|
341
393
|
|
|
342
394
|
|
|
343
395
|
## Moves the pointer to a position, with the movement the event carries taken from where the
|
|
@@ -349,32 +401,40 @@ func inject_mouse_click(params: Dictionary) -> Dictionary:
|
|
|
349
401
|
## would have carried. A relative the caller gives is kept, since a game may want to read a motion
|
|
350
402
|
## the position does not show.
|
|
351
403
|
func inject_mouse_motion(params: Dictionary) -> Dictionary:
|
|
404
|
+
var aimed: Variant = GdhMouse.aim_of(params, _host.get_tree().root)
|
|
405
|
+
if aimed is String:
|
|
406
|
+
return {"type": "error", "message": aimed}
|
|
407
|
+
var aim: Dictionary = aimed
|
|
352
408
|
var point: Variant = GdhMouse.read_point(params, "x", "y")
|
|
353
409
|
if point is String:
|
|
354
410
|
return {"type": "error", "message": point}
|
|
355
|
-
var
|
|
411
|
+
var local: Vector2 = point
|
|
412
|
+
var placed: Dictionary = GdhMouse.carried(aim, local, _host.get_tree().root)
|
|
413
|
+
var viewport: Viewport = placed["viewport"]
|
|
414
|
+
var position: Vector2 = placed["position"]
|
|
356
415
|
var relative: Vector2 = _arrive(position)
|
|
357
416
|
if params.has("relativeX") or params.has("relativeY"):
|
|
358
417
|
relative = Vector2(
|
|
359
418
|
GdhRead.as_float(params.get("relativeX", 0.0)), GdhRead.as_float(params.get("relativeY", 0.0))
|
|
360
419
|
)
|
|
361
420
|
|
|
362
|
-
var supplied: bool =
|
|
363
|
-
Input.parse_input_event(_motion(position, relative))
|
|
364
|
-
|
|
421
|
+
var supplied: bool = _follow_in(viewport, position)
|
|
365
422
|
var at: Array[Vector2] = [position]
|
|
366
|
-
var
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
423
|
+
var where: Dictionary = GdhMouse.where(aim, viewport, at)
|
|
424
|
+
var own: Viewport = _own_of(aim, viewport)
|
|
425
|
+
_send(viewport, _motion(position, relative, _held_in(viewport)))
|
|
426
|
+
# Input hands the motion on at the next frame, and the hover follows it there.
|
|
427
|
+
await _host.get_tree().process_frame
|
|
428
|
+
|
|
429
|
+
var answer: Dictionary = {
|
|
430
|
+
"type": "input_injected",
|
|
431
|
+
"input_type": "mouse_motion",
|
|
432
|
+
"position": [position.x, position.y],
|
|
433
|
+
"relative": [relative.x, relative.y],
|
|
434
|
+
"hovered": _hovered_path(own),
|
|
435
|
+
}
|
|
436
|
+
answer.merge(where)
|
|
437
|
+
return _pointer_noted(_hover_noted(answer, supplied))
|
|
378
438
|
|
|
379
439
|
|
|
380
440
|
## Drags with a mouse button from one point to another, through any points between, as a hand does:
|
|
@@ -387,10 +447,21 @@ func inject_mouse_motion(params: Dictionary) -> Dictionary:
|
|
|
387
447
|
## Each motion goes in a frame of its own because the engine merges the motions it is given within
|
|
388
448
|
## one frame into a single event, as it does a real mouse's, and a game counting motions counts one.
|
|
389
449
|
func inject_mouse_drag(params: Dictionary) -> Dictionary:
|
|
450
|
+
var aimed: Variant = GdhMouse.aim_of(params, _host.get_tree().root)
|
|
451
|
+
if aimed is String:
|
|
452
|
+
return {"type": "error", "message": aimed}
|
|
453
|
+
var aim: Dictionary = aimed
|
|
390
454
|
var stroke: Variant = GdhMouse.stroke_of(params)
|
|
391
455
|
if stroke is String:
|
|
392
456
|
return {"type": "error", "message": stroke}
|
|
393
|
-
|
|
457
|
+
# Carried once, before the press: a hand drags across the screen, not along a node that moves.
|
|
458
|
+
var named: Array[Vector2] = stroke
|
|
459
|
+
var points: Array[Vector2] = []
|
|
460
|
+
var viewport: Viewport = _host.get_tree().root
|
|
461
|
+
for local: Vector2 in named:
|
|
462
|
+
var placed: Dictionary = GdhMouse.carried(aim, local, _host.get_tree().root)
|
|
463
|
+
viewport = placed["viewport"]
|
|
464
|
+
points.append(placed["position"])
|
|
394
465
|
var button: int = GdhMouse.button_of(params.get("button", MOUSE_BUTTON_LEFT))
|
|
395
466
|
if button < 0:
|
|
396
467
|
return GdhMouse.no_such_button(params.get("button"))
|
|
@@ -410,85 +481,53 @@ func inject_mouse_drag(params: Dictionary) -> Dictionary:
|
|
|
410
481
|
var start: Vector2 = points[0]
|
|
411
482
|
var end: Vector2 = points[points.size() - 1]
|
|
412
483
|
var _moved: Vector2 = _arrive(start)
|
|
413
|
-
var supplied: bool =
|
|
414
|
-
|
|
484
|
+
var supplied: bool = _follow_in(viewport, start)
|
|
485
|
+
var where: Dictionary = GdhMouse.where(aim, viewport, points)
|
|
486
|
+
var hovered_in: Viewport = _own_of(aim, viewport)
|
|
487
|
+
var pressing: Dictionary = _hover.press_of(aim, viewport, start)
|
|
488
|
+
_send(viewport, GdhMouse.button_event(start, button, true, false, _held_in(viewport)))
|
|
415
489
|
var travelled: Vector2 = Vector2.ZERO
|
|
416
|
-
|
|
490
|
+
var sent: int = 0
|
|
491
|
+
for step: int in range(1, frames + 2):
|
|
417
492
|
await _host.get_tree().process_frame
|
|
493
|
+
# A window other than the game's can be closed by what the stroke moves, and nothing is left
|
|
494
|
+
# for the rest of it to go to.
|
|
495
|
+
if not is_instance_valid(viewport):
|
|
496
|
+
return {
|
|
497
|
+
"type": "error",
|
|
498
|
+
"message":
|
|
499
|
+
(
|
|
500
|
+
(
|
|
501
|
+
"%s was freed after the press and %d of %d motions, so the rest of the stroke"
|
|
502
|
+
+ " and the release went nowhere"
|
|
503
|
+
)
|
|
504
|
+
% [where["window"], sent, frames]
|
|
505
|
+
)
|
|
506
|
+
}
|
|
507
|
+
if step > frames:
|
|
508
|
+
break
|
|
418
509
|
var at: Vector2 = GdhMouse.along(points, lengths, float(step) / frames)
|
|
419
510
|
var relative: Vector2 = _arrive(at)
|
|
420
511
|
travelled += relative
|
|
421
|
-
supplied =
|
|
422
|
-
|
|
512
|
+
supplied = _follow_in(viewport, at) or supplied
|
|
513
|
+
_send(viewport, _motion(at, relative, _held_in(viewport)))
|
|
514
|
+
sent += 1
|
|
515
|
+
_send(viewport, GdhMouse.button_event(end, button, false, false, _held_in(viewport)))
|
|
423
516
|
await _host.get_tree().process_frame
|
|
424
|
-
Input.parse_input_event(GdhMouse.button_event(end, button, false, false))
|
|
425
|
-
await _host.get_tree().process_frame
|
|
426
|
-
|
|
427
|
-
var said: Dictionary = _pointer_noted(
|
|
428
|
-
_hover_noted(
|
|
429
|
-
{
|
|
430
|
-
"type": "input_injected",
|
|
431
|
-
"input_type": "mouse_drag",
|
|
432
|
-
"from": [start.x, start.y],
|
|
433
|
-
"to": [end.x, end.y],
|
|
434
|
-
"button": button,
|
|
435
|
-
"motions": frames,
|
|
436
|
-
"relative_total": [travelled.x, travelled.y],
|
|
437
|
-
},
|
|
438
|
-
supplied
|
|
439
|
-
)
|
|
440
|
-
)
|
|
441
|
-
return GdhMouse.outside_noted(said, _host.get_viewport(), points)
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
## Whether [param centre] is somewhere a click can reach it: inside the viewport, and inside every
|
|
445
|
-
## ScrollContainer between the control and the root, each of which clips what it holds.
|
|
446
|
-
static func _in_sight(control: Control, viewport: Viewport, centre: Vector2) -> bool:
|
|
447
|
-
return viewport.get_visible_rect().has_point(centre) and _clipped_by(control, centre) == null
|
|
448
517
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
walking = walking.get_parent()
|
|
463
|
-
return null
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
## Scrolls whatever is holding [param control] until it is on screen, and answers whether the view
|
|
467
|
-
## moved.
|
|
468
|
-
##
|
|
469
|
-
## Innermost container first and outwards, a frame apart. A container moves what it holds on the
|
|
470
|
-
## next layout pass rather than inside the call, and the next one out works out where the control is
|
|
471
|
-
## from where it is drawn, so asked in the same frame it scrolled to where the control had been.
|
|
472
|
-
## Moved means a scroll value changed: a container asked to show what it already shows moves
|
|
473
|
-
## nothing, and answering true for it said the view had moved when it had not. Only the containers in
|
|
474
|
-
## the control's own viewport: one holding a dialog scrolls the page behind it, not the dialog.
|
|
475
|
-
func _scroll_into_view(control: Control) -> bool:
|
|
476
|
-
var moved: bool = false
|
|
477
|
-
var walking: Variant = control.get_parent()
|
|
478
|
-
while walking != null and not (walking is Viewport):
|
|
479
|
-
var step: Node = walking
|
|
480
|
-
var holder: ScrollContainer = step as ScrollContainer
|
|
481
|
-
if holder != null:
|
|
482
|
-
var before: Vector2i = Vector2i(holder.scroll_horizontal, holder.scroll_vertical)
|
|
483
|
-
holder.ensure_control_visible(control)
|
|
484
|
-
if Vector2i(holder.scroll_horizontal, holder.scroll_vertical) != before:
|
|
485
|
-
moved = true
|
|
486
|
-
await _host.get_tree().process_frame
|
|
487
|
-
# A panel that rebuilds itself can go in the frame this waited.
|
|
488
|
-
if not is_instance_valid(control) or not is_instance_valid(holder):
|
|
489
|
-
return moved
|
|
490
|
-
walking = step.get_parent()
|
|
491
|
-
return moved
|
|
518
|
+
var answer: Dictionary = {
|
|
519
|
+
"type": "input_injected",
|
|
520
|
+
"input_type": "mouse_drag",
|
|
521
|
+
"from": [start.x, start.y],
|
|
522
|
+
"to": [end.x, end.y],
|
|
523
|
+
"button": button,
|
|
524
|
+
"motions": frames,
|
|
525
|
+
"relative_total": [travelled.x, travelled.y],
|
|
526
|
+
"hovered": _hovered_path(hovered_in),
|
|
527
|
+
}
|
|
528
|
+
answer.merge(where)
|
|
529
|
+
answer.merge(pressing)
|
|
530
|
+
return _pointer_noted(_hover_noted(answer, supplied))
|
|
492
531
|
|
|
493
532
|
|
|
494
533
|
## A whole click on a Control: the pointer moves onto it, the button goes down, a frame passes,
|
|
@@ -525,6 +564,9 @@ func click(params: Dictionary) -> Dictionary:
|
|
|
525
564
|
if standing.has("message"):
|
|
526
565
|
return standing
|
|
527
566
|
var node: Node = standing["node"]
|
|
567
|
+
var named_point: Variant = GdhMouse.point_in(params, node, node_path, item != null)
|
|
568
|
+
if named_point is String:
|
|
569
|
+
return {"type": "error", "message": named_point}
|
|
528
570
|
if node is Node3D:
|
|
529
571
|
var spatial: Node3D = node
|
|
530
572
|
return await _click_in_the_world(node_path, spatial, params)
|
|
@@ -534,7 +576,7 @@ func click(params: Dictionary) -> Dictionary:
|
|
|
534
576
|
"message": "%s is a %s, not a Control or a Node3D" % [node_path, node.get_class()]
|
|
535
577
|
}
|
|
536
578
|
var control: Control = node
|
|
537
|
-
var unmoved: String =
|
|
579
|
+
var unmoved: String = GdhMouse.takes_no_click(control, node_path, _host.get_tree().paused)
|
|
538
580
|
if not unmoved.is_empty():
|
|
539
581
|
return {"type": "error", "message": unmoved}
|
|
540
582
|
|
|
@@ -544,6 +586,9 @@ func click(params: Dictionary) -> Dictionary:
|
|
|
544
586
|
var aim: Vector2 = control.size * 0.5
|
|
545
587
|
var at_centre: bool = true
|
|
546
588
|
var item_scrolled: bool = false
|
|
589
|
+
if named_point != null:
|
|
590
|
+
aim = named_point
|
|
591
|
+
at_centre = false
|
|
547
592
|
if item != null:
|
|
548
593
|
var found_by_words: Dictionary = found
|
|
549
594
|
var placed_item: Dictionary = await GdhItems.in_view(
|
|
@@ -555,7 +600,7 @@ func click(params: Dictionary) -> Dictionary:
|
|
|
555
600
|
at_centre = false
|
|
556
601
|
item_scrolled = placed_item["scrolled"]
|
|
557
602
|
var rich: RichTextLabel = control as RichTextLabel
|
|
558
|
-
if found != null and rich != null:
|
|
603
|
+
if found != null and rich != null and named_point == null:
|
|
559
604
|
var placed: Dictionary = GdhTargets.point_on_words(rich, wanted)
|
|
560
605
|
var found_here: Dictionary = found
|
|
561
606
|
if placed.has("point"):
|
|
@@ -583,8 +628,8 @@ func click(params: Dictionary) -> Dictionary:
|
|
|
583
628
|
# the click went to that instead and said so. Measured on a hall whose staff panel had been
|
|
584
629
|
# scrolled past: the button was at y 69 and its container started at y 166.
|
|
585
630
|
var scrolled: bool = item_scrolled
|
|
586
|
-
if not
|
|
587
|
-
scrolled = await
|
|
631
|
+
if not GdhScreen.in_sight(control, own, local):
|
|
632
|
+
scrolled = await GdhScreen.scroll_into_view(control, _host.get_tree()) or item_scrolled
|
|
588
633
|
var left: String = GdhValues.afterwards(control)
|
|
589
634
|
if left != "in_tree":
|
|
590
635
|
return {
|
|
@@ -600,7 +645,7 @@ func click(params: Dictionary) -> Dictionary:
|
|
|
600
645
|
# A control taller or wider than what shows it has its centre out of sight at every scroll, and
|
|
601
646
|
# a player clicks the part they can see. Refused, it was called a control nothing could click.
|
|
602
647
|
var on_part: bool = false
|
|
603
|
-
if at_centre and not
|
|
648
|
+
if at_centre and not GdhScreen.in_sight(control, own, local):
|
|
604
649
|
var part: Variant = GdhScreen.shown_part(control)
|
|
605
650
|
if part is Rect2:
|
|
606
651
|
var seen: Rect2 = part
|
|
@@ -619,7 +664,8 @@ func click(params: Dictionary) -> Dictionary:
|
|
|
619
664
|
# that silently reached nothing: outside the viewport it is drawn in, outside the one that shows
|
|
620
665
|
# that on the screen, or clipped away by a ScrollContainer that would not scroll it into view.
|
|
621
666
|
# Each is said of the viewport or container at fault, with its own rect and the point in it.
|
|
622
|
-
var
|
|
667
|
+
var aimed: String = "its centre" if named_point == null else "the point %s clicked" % [named_point]
|
|
668
|
+
var out_there: String = _out_of_reach(control, node_path, local, reached, scrolled, aimed)
|
|
623
669
|
if not out_there.is_empty():
|
|
624
670
|
return {"type": "error", "message": out_there}
|
|
625
671
|
var button: int = GdhMouse.button_of(params.get("button", MOUSE_BUTTON_LEFT))
|
|
@@ -647,8 +693,21 @@ func click(params: Dictionary) -> Dictionary:
|
|
|
647
693
|
if hovered != null:
|
|
648
694
|
hovered_path = str(hovered.get_path())
|
|
649
695
|
var landed: bool = hovered == control or (hovered != null and control.is_ancestor_of(hovered))
|
|
650
|
-
#
|
|
651
|
-
|
|
696
|
+
# Why a miss missed, said before the press: the press can free the control and what covers it, and
|
|
697
|
+
# a click on a pointer spot that rebuilt its room errored reading the freed spot's filter and
|
|
698
|
+
# answered nothing. The cover is asked before the press restacks windows too: hovered missed a
|
|
699
|
+
# window over it.
|
|
700
|
+
var why_not_landed: String = ""
|
|
701
|
+
# A Label and a TextureRect ignore the pointer unless told otherwise, so the click goes to what is
|
|
702
|
+
# under them, and landed false with that control as hovered said nothing of why.
|
|
703
|
+
if not landed and control.mouse_filter == Control.MOUSE_FILTER_IGNORE:
|
|
704
|
+
why_not_landed = (
|
|
705
|
+
"%s's mouse_filter is ignore, so the pointer passes through it to what is under it" % node_path
|
|
706
|
+
)
|
|
707
|
+
elif not landed:
|
|
708
|
+
var cover: Node = GdhTargets.drawn_over(control)
|
|
709
|
+
if cover != null:
|
|
710
|
+
why_not_landed = "%s is drawn over it at the point clicked" % cover.get_path()
|
|
652
711
|
|
|
653
712
|
# The link a rich label says was pressed, which is its own word for what the press reached.
|
|
654
713
|
var links: Array = []
|
|
@@ -664,12 +723,21 @@ func click(params: Dictionary) -> Dictionary:
|
|
|
664
723
|
var was_pressed: Variant = null
|
|
665
724
|
if toggle != null and toggle.toggle_mode:
|
|
666
725
|
was_pressed = toggle.button_pressed
|
|
726
|
+
# Whether the button's own pressed signal came, which landing does not say either: a click that
|
|
727
|
+
# landed on Options answered nothing of whether the game heard it, and the panel it opened had
|
|
728
|
+
# closed again by the next read.
|
|
729
|
+
var fired: Array[bool] = [false]
|
|
730
|
+
var heard_press: Callable = func() -> void: fired[0] = true
|
|
731
|
+
if toggle != null:
|
|
732
|
+
var _hearing: int = toggle.pressed.connect(heard_press)
|
|
667
733
|
var press: InputEventMouseButton = await _click_button(viewport, position, button, double)
|
|
668
734
|
# The release is what a button acts on, and a queue_free it causes lands at the end of
|
|
669
735
|
# this frame; the frame passes so the answer describes the control as the click left it.
|
|
670
736
|
await _host.get_tree().process_frame
|
|
671
737
|
if rich != null and is_instance_valid(rich) and rich.meta_clicked.is_connected(heard):
|
|
672
738
|
rich.meta_clicked.disconnect(heard)
|
|
739
|
+
if toggle != null and is_instance_valid(toggle) and toggle.pressed.is_connected(heard_press):
|
|
740
|
+
toggle.pressed.disconnect(heard_press)
|
|
673
741
|
|
|
674
742
|
# What became of the control: still in the tree, taken out of it, or freed. A button that
|
|
675
743
|
# opened another screen is the second or the third, and the caller wants to hear that
|
|
@@ -690,14 +758,8 @@ func click(params: Dictionary) -> Dictionary:
|
|
|
690
758
|
}
|
|
691
759
|
if on_part:
|
|
692
760
|
answer["pressed_on"] = "the middle of the part on screen, since no scroll brings its centre into view"
|
|
693
|
-
|
|
694
|
-
|
|
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()
|
|
761
|
+
if not why_not_landed.is_empty():
|
|
762
|
+
answer["why_not_landed"] = why_not_landed
|
|
701
763
|
if found != null:
|
|
702
764
|
answer["found"] = found
|
|
703
765
|
if was_pressed != null:
|
|
@@ -705,6 +767,8 @@ func click(params: Dictionary) -> Dictionary:
|
|
|
705
767
|
if is_instance_valid(toggle):
|
|
706
768
|
now_pressed = toggle.button_pressed
|
|
707
769
|
answer["pressed"] = {"before": was_pressed, "after": now_pressed}
|
|
770
|
+
if toggle != null:
|
|
771
|
+
answer["pressed_signal"] = fired[0]
|
|
708
772
|
# Whether a link was pressed apart from what it carries: a bare [url] reports an empty meta,
|
|
709
773
|
# and given as the meta alone that press read exactly like one that reached no link.
|
|
710
774
|
if on_rich_text:
|
|
@@ -714,50 +778,15 @@ func click(params: Dictionary) -> Dictionary:
|
|
|
714
778
|
return _pointer_noted(_hover_noted(answer, supplied))
|
|
715
779
|
|
|
716
780
|
|
|
717
|
-
## Why [param control]
|
|
718
|
-
##
|
|
719
|
-
##
|
|
720
|
-
##
|
|
721
|
-
##
|
|
722
|
-
##
|
|
723
|
-
##
|
|
724
|
-
func _takes_no_click(control: Control, node_path: String) -> String:
|
|
725
|
-
if not control.is_visible_in_tree():
|
|
726
|
-
return "%s is not visible%s, so nothing can click it" % [node_path, GdhTargets.why_hidden(control)]
|
|
727
|
-
var button: BaseButton = control as BaseButton
|
|
728
|
-
if button != null and button.disabled:
|
|
729
|
-
return "%s is disabled, so clicking it presses nothing" % node_path
|
|
730
|
-
var turned_off: Control = GdhTargets.pointer_turned_off_by(control)
|
|
731
|
-
if turned_off != null:
|
|
732
|
-
var on: String = "itself" if turned_off == control else str(turned_off.get_path())
|
|
733
|
-
return (
|
|
734
|
-
(
|
|
735
|
-
"%s takes no pointer input, since mouse_behavior_recursive is disabled on %s,"
|
|
736
|
-
+ " so clicking it presses nothing"
|
|
737
|
-
)
|
|
738
|
-
% [node_path, on]
|
|
739
|
-
)
|
|
740
|
-
if not control.can_process():
|
|
741
|
-
var why: String = GdhTargets.why_it_does_not_process(control, node_path)
|
|
742
|
-
var over: Node = GdhTargets.drawn_over(control)
|
|
743
|
-
var cover: String = "" if over == null else "; %s is drawn over it" % over.get_path()
|
|
744
|
-
if _host.get_tree().paused:
|
|
745
|
-
return (
|
|
746
|
-
"the game is paused and %s does not process while it is (%s), so clicking it does nothing%s"
|
|
747
|
-
% [node_path, why, cover]
|
|
748
|
-
)
|
|
749
|
-
return "%s does not process (%s), so clicking it does nothing%s" % [node_path, why, cover]
|
|
750
|
-
return ""
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
## Why a click at [param control]'s centre would reach nothing, or "" when it would reach it: the
|
|
754
|
-
## centre outside the viewport it is drawn in, at [param local]; outside the viewport showing that
|
|
755
|
-
## one on the screen, which [param reached] holds with the centre there; or clipped away by a
|
|
756
|
-
## ScrollContainer holding it. A refusal names the viewport or container at fault and the point in
|
|
757
|
-
## that one's own space, since naming another printed a point inside the rect it was said to be
|
|
758
|
-
## outside. The note on a game with no window goes only on the viewport that is the window's.
|
|
781
|
+
## Why a click at [param control]'s centre, or at the point [param aimed] names, would reach nothing,
|
|
782
|
+
## or "" when it would reach it: the point outside the viewport it is drawn in, at [param local];
|
|
783
|
+
## outside the viewport showing that one on the screen, which [param reached] holds with the point
|
|
784
|
+
## there; or clipped away by a ScrollContainer holding it. A refusal names the viewport or container
|
|
785
|
+
## at fault and the point in that one's own space, since naming another printed a point inside the
|
|
786
|
+
## rect it was said to be outside. The note on a game with no window goes only on the viewport that
|
|
787
|
+
## is the window's.
|
|
759
788
|
func _out_of_reach(
|
|
760
|
-
control: Control, node_path: String, local: Vector2, reached: Dictionary, scrolled: bool
|
|
789
|
+
control: Control, node_path: String, local: Vector2, reached: Dictionary, scrolled: bool, aimed: String
|
|
761
790
|
) -> String:
|
|
762
791
|
var after: String = (
|
|
763
792
|
". It was scrolled as far as what holds it goes and is still out there" if scrolled else ""
|
|
@@ -769,16 +798,17 @@ func _out_of_reach(
|
|
|
769
798
|
continue
|
|
770
799
|
var which: String = "" if viewport == _host.get_tree().root else " of %s" % viewport.get_path()
|
|
771
800
|
return (
|
|
772
|
-
"%s has
|
|
773
|
-
% [node_path, point, which, viewport.get_visible_rect(), after, _no_window_note(viewport)]
|
|
801
|
+
"%s has %s at %s, outside the viewport%s %s, so nothing can click it%s%s"
|
|
802
|
+
% [node_path, aimed, point, which, viewport.get_visible_rect(), after, _no_window_note(viewport)]
|
|
774
803
|
)
|
|
775
|
-
var holder: ScrollContainer =
|
|
804
|
+
var holder: ScrollContainer = GdhScreen.clipped_by(control, local)
|
|
776
805
|
if holder == null:
|
|
777
806
|
return ""
|
|
778
807
|
return (
|
|
779
|
-
"%s has
|
|
808
|
+
"%s has %s at %s, outside the part of %s that shows it, so nothing can click it%s"
|
|
780
809
|
% [
|
|
781
810
|
node_path,
|
|
811
|
+
aimed,
|
|
782
812
|
local,
|
|
783
813
|
holder.get_path(),
|
|
784
814
|
after if scrolled else ". It could not be scrolled into view",
|
|
@@ -898,7 +928,7 @@ func _click_in_the_world(node_path: String, item: Node3D, params: Dictionary) ->
|
|
|
898
928
|
var supplied: bool = _hover.follow(arriving, reached_point)
|
|
899
929
|
# Read before the press, for the reason the Control click reads it: what the caller needs to
|
|
900
930
|
# know is whether a panel is sitting over the room, and the press is what would change it.
|
|
901
|
-
var through: Dictionary =
|
|
931
|
+
var through: Dictionary = GdhScreen.through_the_interface(out, _hover)
|
|
902
932
|
var hovered_path: Variant = through["hovered"]
|
|
903
933
|
|
|
904
934
|
var _press: InputEventMouseButton = await _click_button(arriving, position, button, double)
|
|
@@ -924,61 +954,6 @@ func _click_in_the_world(node_path: String, item: Node3D, params: Dictionary) ->
|
|
|
924
954
|
)
|
|
925
955
|
|
|
926
956
|
|
|
927
|
-
## Whether a pointer on its way out through [param out], the steps [method GdhScreen.steps_out]
|
|
928
|
-
## answers, gets past the interface in every viewport, as {"landed", "hovered"}: the path of the
|
|
929
|
-
## control that took it, or of the one under it that let it by, or null.
|
|
930
|
-
##
|
|
931
|
-
## In the viewport the node is drawn in, a control under the pointer takes it unless it and every
|
|
932
|
-
## control holding it let the pointer pass. In each viewport outside that one, the pointer has to be
|
|
933
|
-
## over what shows the one inside: the SubViewportContainer, with nothing drawn over it, or inside
|
|
934
|
-
## the embedded window. Read in the first viewport alone, a click beside a container that is smaller
|
|
935
|
-
## than what it shows, or under a panel drawn over the container, answered landed.
|
|
936
|
-
func _through_the_interface(out: Array[Dictionary]) -> Dictionary:
|
|
937
|
-
var first_under: Variant = null
|
|
938
|
-
for step: Dictionary in out:
|
|
939
|
-
var viewport: Viewport = step["viewport"]
|
|
940
|
-
var through: Variant = step["through"]
|
|
941
|
-
var hovered: Control = _hover.hovered_in(viewport)
|
|
942
|
-
if through is Window:
|
|
943
|
-
var window: Window = through
|
|
944
|
-
var point: Vector2 = step["point"]
|
|
945
|
-
if not Rect2(window.position, window.size).has_point(point):
|
|
946
|
-
return {"landed": false, "hovered": _path_or_null(hovered)}
|
|
947
|
-
continue
|
|
948
|
-
if through is SubViewportContainer:
|
|
949
|
-
if hovered != through:
|
|
950
|
-
return {"landed": false, "hovered": _path_or_null(hovered)}
|
|
951
|
-
continue
|
|
952
|
-
if hovered != null and not _passes_on(hovered):
|
|
953
|
-
return {"landed": false, "hovered": str(hovered.get_path())}
|
|
954
|
-
if hovered != null and first_under == null:
|
|
955
|
-
first_under = str(hovered.get_path())
|
|
956
|
-
return {"landed": true, "hovered": first_under}
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
## Whether a pointer over [param control] goes on past the interface: a control that lets it pass
|
|
960
|
-
## hands it to the one holding it, and one that stops it takes it, up to a control drawn on its own
|
|
961
|
-
## or something that is not a control. Measured on 4.7.2: a HUD over the room letting the pointer
|
|
962
|
-
## pass answered not landed while the game's own input took the press. A script on a control that
|
|
963
|
-
## lets it pass can still take the event for itself, which nothing outside it can see.
|
|
964
|
-
static func _passes_on(control: Control) -> bool:
|
|
965
|
-
var walk: Node = control
|
|
966
|
-
while walk is Control:
|
|
967
|
-
var at: Control = walk
|
|
968
|
-
if at.get_mouse_filter_with_override() == Control.MOUSE_FILTER_STOP:
|
|
969
|
-
return false
|
|
970
|
-
if at.top_level:
|
|
971
|
-
break
|
|
972
|
-
walk = at.get_parent()
|
|
973
|
-
return true
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
static func _path_or_null(node: Node) -> Variant:
|
|
977
|
-
if node == null:
|
|
978
|
-
return null
|
|
979
|
-
return str(node.get_path())
|
|
980
|
-
|
|
981
|
-
|
|
982
957
|
## The buttons a real pointer event carries are the ones held as it happens, and Input keeps that
|
|
983
958
|
## from every button event it is given, injected ones included, so a motion between a held click
|
|
984
959
|
## and its release is a drag to a control that reads the mask rather than remembering the click.
|
|
@@ -987,7 +962,7 @@ static func _path_or_null(node: Node) -> Variant:
|
|
|
987
962
|
## menu ignores a motion with none, so that one opening under a resting pointer lights nothing: a
|
|
988
963
|
## motion over an open drop-down's item answered as injected and the menu highlighted nothing. A
|
|
989
964
|
## motion that goes nowhere still has none, as a resting pointer does.
|
|
990
|
-
func _motion(position: Vector2, relative: Vector2) -> InputEventMouseMotion:
|
|
965
|
+
func _motion(position: Vector2, relative: Vector2, held: int = -1) -> InputEventMouseMotion:
|
|
991
966
|
var event: InputEventMouseMotion = InputEventMouseMotion.new()
|
|
992
967
|
event.position = position
|
|
993
968
|
event.global_position = position
|
|
@@ -995,5 +970,5 @@ func _motion(position: Vector2, relative: Vector2) -> InputEventMouseMotion:
|
|
|
995
970
|
event.screen_relative = relative
|
|
996
971
|
event.velocity = relative / _arrival_seconds
|
|
997
972
|
event.screen_velocity = event.velocity
|
|
998
|
-
event.button_mask = Input.get_mouse_button_mask()
|
|
973
|
+
event.button_mask = Input.get_mouse_button_mask() if held < 0 else held
|
|
999
974
|
return event
|