gdharness 1.2.31 → 1.2.32
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 +7212 -6230
- package/build/godot/addons/gdharness_editor/bridge_client.gd +14 -0
- package/build/godot/addons/gdharness_editor/file_reads.gd +237 -0
- package/build/godot/addons/gdharness_editor/file_reads.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/plugin.gd +27 -0
- package/build/godot/addons/gdharness_editor/scene_file.gd +38 -1
- package/build/godot/addons/gdharness_editor/tool_executor.gd +1 -0
- package/build/godot/addons/gdharness_editor/tools/class_tools.gd +22 -0
- package/build/godot/addons/gdharness_editor/tools/filesystem_tools.gd +64 -1
- package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +4 -41
- package/build/godot/addons/gdharness_editor/tools/scene_placing.gd +178 -0
- package/build/godot/addons/gdharness_editor/tools/scene_placing.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +54 -120
- package/build/godot/addons/gdharness_runtime/runtime_autoload.gd +39 -0
- package/build/godot/addons/gdharness_runtime/runtime_hover.gd +285 -0
- package/build/godot/addons/gdharness_runtime/runtime_hover.gd.uid +1 -0
- package/build/godot/addons/gdharness_runtime/runtime_input.gd +151 -106
- package/build/godot/addons/gdharness_runtime/runtime_mouse.gd +112 -0
- package/build/godot/addons/gdharness_runtime/runtime_mouse.gd.uid +1 -0
- package/build/godot/operations/file_reads.gd +237 -0
- package/build/godot/operations/gdscript_authoring.gd +136 -19
- package/build/godot/operations/godot_operations.gd +24 -0
- package/build/godot/operations/property_values.gd +767 -0
- package/build/godot/operations/rename_checks.gd +5 -2
- package/build/index.js +5437 -4469
- package/build/keeper.js +184 -71
- package/package.json +1 -1
|
@@ -11,8 +11,10 @@ const Values = preload("runtime_values.gd")
|
|
|
11
11
|
## reports are the same place by construction rather than by agreement.
|
|
12
12
|
const Queries = preload("runtime_queries.gd")
|
|
13
13
|
const Read = preload("reading.gd")
|
|
14
|
+
const Hover = preload("runtime_hover.gd")
|
|
14
15
|
const Screen = preload("runtime_screen.gd")
|
|
15
16
|
const Targets = preload("runtime_targets.gd")
|
|
17
|
+
const Mouse = preload("runtime_mouse.gd")
|
|
16
18
|
|
|
17
19
|
## The distance from a capital letter to its small one in Unicode. A keycode holds the capital.
|
|
18
20
|
const TO_SMALL: int = 32
|
|
@@ -33,8 +35,12 @@ const PROCESS_MODES: Dictionary[int, String] = {
|
|
|
33
35
|
## Whether this run has had the pointer note in full; the game process is the run.
|
|
34
36
|
static var _pointer_explained: bool = false
|
|
35
37
|
|
|
38
|
+
## Whether this run has had the note on a supplied hover in full.
|
|
39
|
+
static var _hover_explained: bool = false
|
|
40
|
+
|
|
36
41
|
var _host: Node
|
|
37
42
|
var _values: Values
|
|
43
|
+
var _hover: Hover = Hover.new()
|
|
38
44
|
|
|
39
45
|
## Where the last injected pointer event put the pointer, once one has.
|
|
40
46
|
##
|
|
@@ -87,6 +93,32 @@ static func _pointer_noted(answer: Dictionary) -> Dictionary:
|
|
|
87
93
|
return answer
|
|
88
94
|
|
|
89
95
|
|
|
96
|
+
## [param answer], saying that the hover it reports was supplied here, when [param supplied]: see
|
|
97
|
+
## runtime_hover.gd.
|
|
98
|
+
static func _hover_noted(answer: Dictionary, supplied: bool) -> Dictionary:
|
|
99
|
+
if not supplied:
|
|
100
|
+
return answer
|
|
101
|
+
answer["hover_supplied"] = true
|
|
102
|
+
if not _hover_explained:
|
|
103
|
+
_hover_explained = true
|
|
104
|
+
answer["hover_note"] = (
|
|
105
|
+
"This window has had no pointer enter from the operating system, and with subwindow"
|
|
106
|
+
+ " embedding off Godot hovers nothing in a native window until it has, so a Button there"
|
|
107
|
+
+ " takes no press. The runtime gave the controls under the pointer the hover the engine"
|
|
108
|
+
+ " would have: their mouse_entered and mouse_exited, and the hover a Button presses on."
|
|
109
|
+
+ " Viewport.gui_get_hovered_control() still answers null in this window. Said once a run;"
|
|
110
|
+
+ " hover_supplied marks every answer it applies to."
|
|
111
|
+
)
|
|
112
|
+
return answer
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
## Hovers what is under [param position] in the game's window, where the pointer events Input is
|
|
116
|
+
## given go, when the engine cannot: see runtime_hover.gd.
|
|
117
|
+
func _follow_in_the_window(position: Vector2) -> bool:
|
|
118
|
+
var root: Window = _host.get_tree().root
|
|
119
|
+
return _hover.follow(root, root.get_final_transform().affine_inverse() * position)
|
|
120
|
+
|
|
121
|
+
|
|
90
122
|
## The movement a pointer arriving at [param position] carries, which is the distance from where
|
|
91
123
|
## the last injected event put it, and none at all for the first. The arrival is recorded, with how
|
|
92
124
|
## long it took: at least a frame, since two motions sent in one frame did not cross the screen in
|
|
@@ -242,22 +274,6 @@ func _glyph_of(keycode: Key, shifted: bool) -> int:
|
|
|
242
274
|
return keycode
|
|
243
275
|
|
|
244
276
|
|
|
245
|
-
## A point the tool schema sends as two flat numbers, or the older form of one [x, y] value.
|
|
246
|
-
## Answers a Vector2, or the String that says what was wrong with it.
|
|
247
|
-
func _read_point(params: Dictionary, x_key: String, y_key: String, pair_key: String) -> Variant:
|
|
248
|
-
if params.has(x_key) and params.has(y_key):
|
|
249
|
-
return Vector2(Read.as_float(params[x_key]), Read.as_float(params[y_key]))
|
|
250
|
-
var raw: Variant = params.get(pair_key, Vector2.ZERO)
|
|
251
|
-
if raw is Vector2:
|
|
252
|
-
return raw
|
|
253
|
-
if raw is Array:
|
|
254
|
-
var pair: Array = raw
|
|
255
|
-
if pair.size() < 2:
|
|
256
|
-
return "%s array must contain [x, y]" % pair_key
|
|
257
|
-
return Vector2(Read.as_float(pair[0]), Read.as_float(pair[1]))
|
|
258
|
-
return "%s must be Vector2 or [x, y]" % pair_key
|
|
259
|
-
|
|
260
|
-
|
|
261
277
|
## Clicks a mouse button at a position, and lets go of it again unless asked to hold it.
|
|
262
278
|
##
|
|
263
279
|
## The whole click by default, for the reason [method inject_action] is the whole press, and
|
|
@@ -267,20 +283,20 @@ func _read_point(params: Dictionary, x_key: String, y_key: String, pair_key: Str
|
|
|
267
283
|
## `pressed` is how a caller asks for one half, the way a drag is made: true holds the button down,
|
|
268
284
|
## false lets go of one being held.
|
|
269
285
|
func inject_mouse_click(params: Dictionary) -> Dictionary:
|
|
270
|
-
var point: Variant =
|
|
286
|
+
var point: Variant = Mouse.read_point(params, "x", "y", "position")
|
|
271
287
|
if point is String:
|
|
272
288
|
return {"type": "error", "message": point}
|
|
273
289
|
var position: Vector2 = point
|
|
274
|
-
var button: int =
|
|
290
|
+
var button: int = Mouse.button_of(params.get("button", MOUSE_BUTTON_LEFT))
|
|
275
291
|
if button < 0:
|
|
276
|
-
return
|
|
292
|
+
return Mouse.no_such_button(params.get("button"))
|
|
277
293
|
var held: bool = Read.as_bool(params.get("pressed", true), true)
|
|
278
294
|
var double: bool = Read.as_bool(params.get("doubleClick", false))
|
|
279
295
|
# A wheel step is a press and a release together, as a mouse sends one: there is no holding a
|
|
280
296
|
# wheel. A lone wheel press left the viewport's mouse focus on the control that took it, with
|
|
281
297
|
# the wheel's bit in the focus mask, and every click after it landed on that control rather
|
|
282
298
|
# than under the pointer, landing true and doing nothing, until a release was sent by hand.
|
|
283
|
-
var notch: bool = held and
|
|
299
|
+
var notch: bool = held and Mouse.mask_of(button) == 0
|
|
284
300
|
var whole: bool = notch or not params.has("pressed")
|
|
285
301
|
var holding: Variant = Values.held_for(params)
|
|
286
302
|
if holding is String:
|
|
@@ -295,6 +311,7 @@ func inject_mouse_click(params: Dictionary) -> Dictionary:
|
|
|
295
311
|
# A button pressed somewhere puts the pointer there: the motion after a held press measures
|
|
296
312
|
# its drag from the press, which is where a real pointer would be.
|
|
297
313
|
var _moved: Vector2 = _arrive(position)
|
|
314
|
+
var supplied: bool = _follow_in_the_window(position)
|
|
298
315
|
Input.parse_input_event(_button(position, button, whole or held, double))
|
|
299
316
|
if notch:
|
|
300
317
|
Input.parse_input_event(_button(position, button, false, false))
|
|
@@ -306,16 +323,19 @@ func inject_mouse_click(params: Dictionary) -> Dictionary:
|
|
|
306
323
|
await _host.get_tree().process_frame
|
|
307
324
|
|
|
308
325
|
return _pointer_noted(
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
326
|
+
_hover_noted(
|
|
327
|
+
{
|
|
328
|
+
"type": "input_injected",
|
|
329
|
+
"input_type": "mouse_click",
|
|
330
|
+
"held_frames": Values.held_answer(whole and not notch, frames),
|
|
331
|
+
"position": [position.x, position.y],
|
|
332
|
+
"button": button,
|
|
333
|
+
"pressed": held and not whole,
|
|
334
|
+
"whole": whole,
|
|
335
|
+
"double": double
|
|
336
|
+
},
|
|
337
|
+
supplied
|
|
338
|
+
)
|
|
319
339
|
)
|
|
320
340
|
|
|
321
341
|
|
|
@@ -328,7 +348,7 @@ func inject_mouse_click(params: Dictionary) -> Dictionary:
|
|
|
328
348
|
## would have carried. A relative the caller gives is kept, since a game may want to read a motion
|
|
329
349
|
## the position does not show.
|
|
330
350
|
func inject_mouse_motion(params: Dictionary) -> Dictionary:
|
|
331
|
-
var point: Variant =
|
|
351
|
+
var point: Variant = Mouse.read_point(params, "x", "y", "position")
|
|
332
352
|
if point is String:
|
|
333
353
|
return {"type": "error", "message": point}
|
|
334
354
|
var position: Vector2 = point
|
|
@@ -338,20 +358,87 @@ func inject_mouse_motion(params: Dictionary) -> Dictionary:
|
|
|
338
358
|
Read.as_float(params.get("relativeX", 0.0)), Read.as_float(params.get("relativeY", 0.0))
|
|
339
359
|
)
|
|
340
360
|
elif params.has("relative"):
|
|
341
|
-
var movement: Variant =
|
|
361
|
+
var movement: Variant = Mouse.read_point(params, "relativeX", "relativeY", "relative")
|
|
342
362
|
if movement is String:
|
|
343
363
|
return {"type": "error", "message": movement}
|
|
344
364
|
relative = movement
|
|
345
365
|
|
|
366
|
+
var supplied: bool = _follow_in_the_window(position)
|
|
346
367
|
Input.parse_input_event(_motion(position, relative))
|
|
347
368
|
|
|
348
369
|
return _pointer_noted(
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
370
|
+
_hover_noted(
|
|
371
|
+
{
|
|
372
|
+
"type": "input_injected",
|
|
373
|
+
"input_type": "mouse_motion",
|
|
374
|
+
"position": [position.x, position.y],
|
|
375
|
+
"relative": [relative.x, relative.y]
|
|
376
|
+
},
|
|
377
|
+
supplied
|
|
378
|
+
)
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
## Drags with a mouse button from one point to another, through any points between, as a hand does:
|
|
383
|
+
## the press, one motion each process frame along the stroke, and the release where the last motion
|
|
384
|
+
## left the pointer, a frame after it. A game acting on every motion it gets sees as many as a real
|
|
385
|
+
## stroke over that many frames sends. Made of calls, a stroke cost a round trip a motion, so the
|
|
386
|
+
## one a caller could afford was three or four motions where a hand sends dozens, and a stir that
|
|
387
|
+
## moves a few cells a motion moved two for a stroke.
|
|
388
|
+
##
|
|
389
|
+
## Each motion goes in a frame of its own because the engine merges the motions it is given within
|
|
390
|
+
## one frame into a single event, as it does a real mouse's, and a game counting motions counts one.
|
|
391
|
+
func inject_mouse_drag(params: Dictionary) -> Dictionary:
|
|
392
|
+
var stroke: Variant = Mouse.stroke_of(params)
|
|
393
|
+
if stroke is String:
|
|
394
|
+
return {"type": "error", "message": stroke}
|
|
395
|
+
var points: Array[Vector2] = stroke
|
|
396
|
+
var button: int = Mouse.button_of(params.get("button", MOUSE_BUTTON_LEFT))
|
|
397
|
+
if button < 0:
|
|
398
|
+
return Mouse.no_such_button(params.get("button"))
|
|
399
|
+
var own: int = Mouse.mask_of(button)
|
|
400
|
+
if own == 0:
|
|
401
|
+
return {
|
|
402
|
+
"type": "error",
|
|
403
|
+
"message": "A wheel step has no held position to drag from: give left, right or middle."
|
|
404
|
+
}
|
|
405
|
+
var frames: int = Read.as_int(params.get("frames", 0), 0)
|
|
406
|
+
if frames < 1 or frames > Values.MOST_HELD:
|
|
407
|
+
return {
|
|
408
|
+
"type": "error", "message": "frames is %d, and it takes 1 to %d." % [frames, Values.MOST_HELD]
|
|
354
409
|
}
|
|
410
|
+
|
|
411
|
+
var lengths: Array[float] = Mouse.lengths_of(points)
|
|
412
|
+
var start: Vector2 = points[0]
|
|
413
|
+
var end: Vector2 = points[points.size() - 1]
|
|
414
|
+
var _moved: Vector2 = _arrive(start)
|
|
415
|
+
var supplied: bool = _follow_in_the_window(start)
|
|
416
|
+
Input.parse_input_event(_button(start, button, true, false))
|
|
417
|
+
var travelled: Vector2 = Vector2.ZERO
|
|
418
|
+
for step: int in range(1, frames + 1):
|
|
419
|
+
await _host.get_tree().process_frame
|
|
420
|
+
var at: Vector2 = Mouse.along(points, lengths, float(step) / frames)
|
|
421
|
+
var relative: Vector2 = _arrive(at)
|
|
422
|
+
travelled += relative
|
|
423
|
+
supplied = _follow_in_the_window(at) or supplied
|
|
424
|
+
Input.parse_input_event(_motion(at, relative))
|
|
425
|
+
await _host.get_tree().process_frame
|
|
426
|
+
Input.parse_input_event(_button(end, button, false, false))
|
|
427
|
+
await _host.get_tree().process_frame
|
|
428
|
+
|
|
429
|
+
return _pointer_noted(
|
|
430
|
+
_hover_noted(
|
|
431
|
+
{
|
|
432
|
+
"type": "input_injected",
|
|
433
|
+
"input_type": "mouse_drag",
|
|
434
|
+
"from": [start.x, start.y],
|
|
435
|
+
"to": [end.x, end.y],
|
|
436
|
+
"button": button,
|
|
437
|
+
"motions": frames,
|
|
438
|
+
"relative_total": [travelled.x, travelled.y],
|
|
439
|
+
},
|
|
440
|
+
supplied
|
|
441
|
+
)
|
|
355
442
|
)
|
|
356
443
|
|
|
357
444
|
|
|
@@ -507,15 +594,16 @@ func click(params: Dictionary) -> Dictionary:
|
|
|
507
594
|
var out_there: String = _out_of_reach(control, node_path, local, reached, scrolled)
|
|
508
595
|
if not out_there.is_empty():
|
|
509
596
|
return {"type": "error", "message": out_there}
|
|
510
|
-
var button: int =
|
|
597
|
+
var button: int = Mouse.button_of(params.get("button", MOUSE_BUTTON_LEFT))
|
|
511
598
|
if button < 0:
|
|
512
|
-
return
|
|
599
|
+
return Mouse.no_such_button(params.get("button"))
|
|
513
600
|
var double: bool = Read.as_bool(params.get("double", false))
|
|
514
601
|
|
|
515
602
|
# Pushed into the viewport rather than through Input: Input accumulates events and flushes
|
|
516
603
|
# them at the next frame, so the hovered control read below would be the one from before
|
|
517
604
|
# the pointer moved. The viewport delivers it to the GUI the same way a real one arrives.
|
|
518
605
|
viewport.push_input(_motion(position, _arrive(position)))
|
|
606
|
+
var supplied: bool = _hover.follow(viewport, centre)
|
|
519
607
|
# What the engine itself thinks is under the pointer, which is the answer to "did it land",
|
|
520
608
|
# read before the press so the caller learns about a control on top rather than a click
|
|
521
609
|
# that went to it. Read in full here, path included, because nothing about that control
|
|
@@ -526,7 +614,7 @@ func click(params: Dictionary) -> Dictionary:
|
|
|
526
614
|
# and the window keeps the GUI state. Reading the parent reported every dialog as not landed
|
|
527
615
|
# while the button it was aimed at pressed perfectly well, which is the worst shape an answer
|
|
528
616
|
# can have, since the caller believes the miss over what the game just did.
|
|
529
|
-
var hovered: Control = control.get_viewport()
|
|
617
|
+
var hovered: Control = _hover.hovered_in(control.get_viewport())
|
|
530
618
|
var hovered_path: Variant = null
|
|
531
619
|
if hovered != null:
|
|
532
620
|
hovered_path = str(hovered.get_path())
|
|
@@ -584,7 +672,7 @@ func click(params: Dictionary) -> Dictionary:
|
|
|
584
672
|
answer["link_pressed"] = not links.is_empty()
|
|
585
673
|
if not links.is_empty():
|
|
586
674
|
answer["link_meta"] = _values.serialize(links.back())
|
|
587
|
-
return _pointer_noted(answer)
|
|
675
|
+
return _pointer_noted(_hover_noted(answer, supplied))
|
|
588
676
|
|
|
589
677
|
|
|
590
678
|
## Why [param control] would take no click a player gave it, or "" when it would.
|
|
@@ -761,12 +849,13 @@ func _click_in_the_world(node_path: String, item: Node3D, params: Dictionary) ->
|
|
|
761
849
|
)
|
|
762
850
|
}
|
|
763
851
|
var position: Vector2 = arriving.get_final_transform() * reached_point
|
|
764
|
-
var button: int =
|
|
852
|
+
var button: int = Mouse.button_of(params.get("button", MOUSE_BUTTON_LEFT))
|
|
765
853
|
if button < 0:
|
|
766
|
-
return
|
|
854
|
+
return Mouse.no_such_button(params.get("button"))
|
|
767
855
|
var double: bool = Read.as_bool(params.get("double", false))
|
|
768
856
|
|
|
769
857
|
arriving.push_input(_motion(position, _arrive(position)))
|
|
858
|
+
var supplied: bool = _hover.follow(arriving, reached_point)
|
|
770
859
|
# Read before the press, for the reason the Control click reads it: what the caller needs to
|
|
771
860
|
# know is whether a panel is sitting over the room, and the press is what would change it.
|
|
772
861
|
var through: Dictionary = _through_the_interface(out)
|
|
@@ -778,19 +867,22 @@ func _click_in_the_world(node_path: String, item: Node3D, params: Dictionary) ->
|
|
|
778
867
|
await _host.get_tree().process_frame
|
|
779
868
|
|
|
780
869
|
return _pointer_noted(
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
870
|
+
_hover_noted(
|
|
871
|
+
{
|
|
872
|
+
"type": "clicked",
|
|
873
|
+
"path": node_path,
|
|
874
|
+
"position": _values.serialize(position),
|
|
875
|
+
"button": button,
|
|
876
|
+
"double": double,
|
|
877
|
+
"hovered": hovered_path,
|
|
878
|
+
# The interface did not take it, so it reached the game's own input. As close to "it
|
|
879
|
+
# landed" as anything outside the game can get, and said in the same word the
|
|
880
|
+
# Control click says it in.
|
|
881
|
+
"landed": through["landed"],
|
|
882
|
+
"camera": found["camera"],
|
|
883
|
+
},
|
|
884
|
+
supplied
|
|
885
|
+
)
|
|
794
886
|
)
|
|
795
887
|
|
|
796
888
|
|
|
@@ -803,12 +895,12 @@ func _click_in_the_world(node_path: String, item: Node3D, params: Dictionary) ->
|
|
|
803
895
|
## over what shows the one inside: the SubViewportContainer, with nothing drawn over it, or inside
|
|
804
896
|
## the embedded window. Read in the first viewport alone, a click beside a container that is smaller
|
|
805
897
|
## than what it shows, or under a panel drawn over the container, answered landed.
|
|
806
|
-
|
|
898
|
+
func _through_the_interface(out: Array[Dictionary]) -> Dictionary:
|
|
807
899
|
var first_under: Variant = null
|
|
808
900
|
for step: Dictionary in out:
|
|
809
901
|
var viewport: Viewport = step["viewport"]
|
|
810
902
|
var through: Variant = step["through"]
|
|
811
|
-
var hovered: Control =
|
|
903
|
+
var hovered: Control = _hover.hovered_in(viewport)
|
|
812
904
|
if through is Window:
|
|
813
905
|
var window: Window = through
|
|
814
906
|
var point: Vector2 = step["point"]
|
|
@@ -879,53 +971,6 @@ func _button(position: Vector2, button: int, pressed: bool, double: bool) -> Inp
|
|
|
879
971
|
event.pressed = pressed
|
|
880
972
|
event.double_click = double
|
|
881
973
|
var held: int = Input.get_mouse_button_mask()
|
|
882
|
-
var own: int =
|
|
974
|
+
var own: int = Mouse.mask_of(button)
|
|
883
975
|
event.button_mask = (held | own) if pressed else (held & ~own)
|
|
884
976
|
return event
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
## The mask bit for a button, which is the engine's own mapping: left 1, right 2, middle 4, the
|
|
888
|
-
## two extra buttons 128 and 256. The wheel steps between have none.
|
|
889
|
-
func _mask_of(button: int) -> int:
|
|
890
|
-
if button >= MOUSE_BUTTON_WHEEL_UP and button <= MOUSE_BUTTON_WHEEL_RIGHT:
|
|
891
|
-
return 0
|
|
892
|
-
return 1 << (button - 1)
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
## What to say about a name that is not a mouse button, with the ones that are.
|
|
896
|
-
func _no_such_button(named: Variant) -> Dictionary:
|
|
897
|
-
return {
|
|
898
|
-
"type": "error",
|
|
899
|
-
"message":
|
|
900
|
-
(
|
|
901
|
-
"%s is not a mouse button. It takes: left, right, middle, wheel_up, wheel_down, or the number of one."
|
|
902
|
-
% JSON.stringify(named)
|
|
903
|
-
)
|
|
904
|
-
}
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
## The button [param raw] names, or -1 for a name that is not one of them.
|
|
908
|
-
##
|
|
909
|
-
## A name nobody recognises used to come back as the left button, so a right click asked for by a
|
|
910
|
-
## spelling this does not know went to the left button and reported the left button. The caller
|
|
911
|
-
## reads the answer and believes it, which is the whole reason nothing here guesses.
|
|
912
|
-
func _resolve_mouse_button(raw: Variant) -> int:
|
|
913
|
-
if raw is String:
|
|
914
|
-
var named: String = raw
|
|
915
|
-
match named.to_lower():
|
|
916
|
-
"left":
|
|
917
|
-
return MOUSE_BUTTON_LEFT
|
|
918
|
-
"right":
|
|
919
|
-
return MOUSE_BUTTON_RIGHT
|
|
920
|
-
"middle":
|
|
921
|
-
return MOUSE_BUTTON_MIDDLE
|
|
922
|
-
"wheel_up", "wheelup":
|
|
923
|
-
return MOUSE_BUTTON_WHEEL_UP
|
|
924
|
-
"wheel_down", "wheeldown":
|
|
925
|
-
return MOUSE_BUTTON_WHEEL_DOWN
|
|
926
|
-
_:
|
|
927
|
-
return -1
|
|
928
|
-
# The engine's buttons run from 1 to 9: 0 is MOUSE_BUTTON_NONE, which no event carries, and a
|
|
929
|
-
# number past the two extra buttons names nothing a mask bit can be made for.
|
|
930
|
-
var number: int = Read.as_int(raw, -1)
|
|
931
|
-
return number if number >= MOUSE_BUTTON_LEFT and number <= MOUSE_BUTTON_XBUTTON2 else -1
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
extends RefCounted
|
|
2
|
+
|
|
3
|
+
## What a pointer call names: a point, the stroke of a drag, a mouse button and its mask bit. Read
|
|
4
|
+
## here and sent from runtime_input.gd, which keeps where the pointer is.
|
|
5
|
+
|
|
6
|
+
const Read = preload("reading.gd")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
## A point the tool schema sends as two flat numbers, or the older form of one [x, y] value.
|
|
10
|
+
## Answers a Vector2, or the String that says what was wrong with it.
|
|
11
|
+
static func read_point(params: Dictionary, x_key: String, y_key: String, pair_key: String) -> Variant:
|
|
12
|
+
if params.has(x_key) and params.has(y_key):
|
|
13
|
+
return Vector2(Read.as_float(params[x_key]), Read.as_float(params[y_key]))
|
|
14
|
+
var raw: Variant = params.get(pair_key, Vector2.ZERO)
|
|
15
|
+
if raw is Vector2:
|
|
16
|
+
return raw
|
|
17
|
+
if raw is Array:
|
|
18
|
+
var pair: Array = raw
|
|
19
|
+
if pair.size() < 2:
|
|
20
|
+
return "%s array must contain [x, y]" % pair_key
|
|
21
|
+
return Vector2(Read.as_float(pair[0]), Read.as_float(pair[1]))
|
|
22
|
+
return "%s must be Vector2 or [x, y]" % pair_key
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
## The points of a drag: x, y, then each of via, then toX, toY. Answers the String saying what is
|
|
26
|
+
## wrong instead, since a point left out would otherwise read as the window's corner.
|
|
27
|
+
static func stroke_of(params: Dictionary) -> Variant:
|
|
28
|
+
for key: String in ["x", "y", "toX", "toY"]:
|
|
29
|
+
if not params.has(key):
|
|
30
|
+
return "mouse_drag needs x, y, toX and toY: %s is missing." % key
|
|
31
|
+
var points: Array[Vector2] = [Vector2(Read.as_float(params["x"]), Read.as_float(params["y"]))]
|
|
32
|
+
var via: Variant = params.get("via", [])
|
|
33
|
+
if not via is Array:
|
|
34
|
+
return "via must be a list of [x, y] points."
|
|
35
|
+
var between: Array = via
|
|
36
|
+
for point: Variant in between:
|
|
37
|
+
var pair: Array = point if point is Array else []
|
|
38
|
+
if pair.size() < 2:
|
|
39
|
+
return "via must be a list of [x, y] points, and holds %s." % JSON.stringify(point)
|
|
40
|
+
points.append(Vector2(Read.as_float(pair[0]), Read.as_float(pair[1])))
|
|
41
|
+
points.append(Vector2(Read.as_float(params["toX"]), Read.as_float(params["toY"])))
|
|
42
|
+
return points
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
## The distance along [param points] to each of them from the first.
|
|
46
|
+
static func lengths_of(points: Array[Vector2]) -> Array[float]:
|
|
47
|
+
var lengths: Array[float] = [0.0]
|
|
48
|
+
for at: int in range(1, points.size()):
|
|
49
|
+
lengths.append(lengths[at - 1] + points[at - 1].distance_to(points[at]))
|
|
50
|
+
return lengths
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
## The point [param share] of the way along the stroke through [param points], by distance, with
|
|
54
|
+
## [param lengths] from [method lengths_of].
|
|
55
|
+
static func along(points: Array[Vector2], lengths: Array[float], share: float) -> Vector2:
|
|
56
|
+
var total: float = lengths[lengths.size() - 1]
|
|
57
|
+
if total == 0.0:
|
|
58
|
+
return points[0]
|
|
59
|
+
var wanted: float = total * share
|
|
60
|
+
for at: int in range(1, points.size()):
|
|
61
|
+
if lengths[at] >= wanted:
|
|
62
|
+
var span: float = lengths[at] - lengths[at - 1]
|
|
63
|
+
var part: float = 0.0 if span == 0.0 else (wanted - lengths[at - 1]) / span
|
|
64
|
+
return points[at - 1].lerp(points[at], part)
|
|
65
|
+
return points[points.size() - 1]
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
## The mask bit for a button, which is the engine's own mapping: left 1, right 2, middle 4, the
|
|
69
|
+
## two extra buttons 128 and 256. The wheel steps between have none.
|
|
70
|
+
static func mask_of(button: int) -> int:
|
|
71
|
+
if button >= MOUSE_BUTTON_WHEEL_UP and button <= MOUSE_BUTTON_WHEEL_RIGHT:
|
|
72
|
+
return 0
|
|
73
|
+
return 1 << (button - 1)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
## What to say about a name that is not a mouse button, with the ones that are.
|
|
77
|
+
static func no_such_button(named: Variant) -> Dictionary:
|
|
78
|
+
return {
|
|
79
|
+
"type": "error",
|
|
80
|
+
"message":
|
|
81
|
+
(
|
|
82
|
+
"%s is not a mouse button. It takes: left, right, middle, wheel_up, wheel_down, or the number of one."
|
|
83
|
+
% JSON.stringify(named)
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
## The button [param raw] names, or -1 for a name that is not one of them.
|
|
89
|
+
##
|
|
90
|
+
## A name nobody recognises used to come back as the left button, so a right click asked for by a
|
|
91
|
+
## spelling this does not know went to the left button and reported the left button. The caller
|
|
92
|
+
## reads the answer and believes it, which is the whole reason nothing here guesses.
|
|
93
|
+
static func button_of(raw: Variant) -> int:
|
|
94
|
+
if raw is String:
|
|
95
|
+
var named: String = raw
|
|
96
|
+
match named.to_lower():
|
|
97
|
+
"left":
|
|
98
|
+
return MOUSE_BUTTON_LEFT
|
|
99
|
+
"right":
|
|
100
|
+
return MOUSE_BUTTON_RIGHT
|
|
101
|
+
"middle":
|
|
102
|
+
return MOUSE_BUTTON_MIDDLE
|
|
103
|
+
"wheel_up", "wheelup":
|
|
104
|
+
return MOUSE_BUTTON_WHEEL_UP
|
|
105
|
+
"wheel_down", "wheeldown":
|
|
106
|
+
return MOUSE_BUTTON_WHEEL_DOWN
|
|
107
|
+
_:
|
|
108
|
+
return -1
|
|
109
|
+
# The engine's buttons run from 1 to 9: 0 is MOUSE_BUTTON_NONE, which no event carries, and a
|
|
110
|
+
# number past the two extra buttons names nothing a mask bit can be made for.
|
|
111
|
+
var number: int = Read.as_int(raw, -1)
|
|
112
|
+
return number if number >= MOUSE_BUTTON_LEFT and number <= MOUSE_BUTTON_XBUTTON2 else -1
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
uid://ceeejawjqqy2j
|