gdharness 1.1.44 → 1.1.45
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
CHANGED
|
@@ -18407,7 +18407,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
18407
18407
|
requires: [],
|
|
18408
18408
|
operations: {
|
|
18409
18409
|
click: {
|
|
18410
|
-
summary:
|
|
18410
|
+
summary: `press and release on a Control, a frame apart, and answer with what was under the pointer and what became of the control: in_tree, removed or freed. The control is named by nodePath, or by says with the words on it, found under nodePath when both are given, and the answer then says under found which match it was. Words that are part of a RichTextLabel are pressed where they are drawn rather than at the label's middle, so a link among them is the one pressed: found.pressedOn says "the words", or "the middle of the label" with a note saying why they could not be placed, and link_pressed names the meta the label reports clicked. A control out of sight inside a ScrollContainer is scrolled to first, each container out from it a frame after the one inside, and scrolled_into_view says whether a scroll value changed. A disabled button, and a control that does not process (the game paused, say), are refused: the pointer reaches them and the press does nothing. A 3D node is clicked where it is drawn, and landed then says the interface did not swallow the press: no control under the pointer stopped it, a control letting the pointer pass included, and every viewport on the way out from the room had the pointer over what shows the room`,
|
|
18411
18411
|
requires: []
|
|
18412
18412
|
},
|
|
18413
18413
|
choose: {
|
|
@@ -408,9 +408,27 @@ func click(params: Dictionary) -> Dictionary:
|
|
|
408
408
|
if not unmoved.is_empty():
|
|
409
409
|
return {"type": "error", "message": unmoved}
|
|
410
410
|
|
|
411
|
+
# Words that are part of a rich label are pressed where they are drawn, since its middle can be on
|
|
412
|
+
# other words, a link among them. Said either way, so a press on the middle is never read as one
|
|
413
|
+
# on the words.
|
|
414
|
+
var aim: Vector2 = control.size * 0.5
|
|
415
|
+
var rich: RichTextLabel = control as RichTextLabel
|
|
416
|
+
if found != null and rich != null:
|
|
417
|
+
var placed: Dictionary = Targets.point_on_words(rich, wanted)
|
|
418
|
+
var found_here: Dictionary = found
|
|
419
|
+
if placed.has("point"):
|
|
420
|
+
aim = placed["point"]
|
|
421
|
+
found_here["pressedOn"] = "the words"
|
|
422
|
+
elif placed.has("unplaced"):
|
|
423
|
+
found_here["pressedOn"] = "the middle of the label"
|
|
424
|
+
found_here["note"] = (
|
|
425
|
+
"the words are part of a longer label and %s, so the press went to its middle"
|
|
426
|
+
% placed["unplaced"]
|
|
427
|
+
)
|
|
428
|
+
|
|
411
429
|
# In the control's own viewport first, which is the space its scrolling happens in.
|
|
412
430
|
var own: Viewport = control.get_viewport()
|
|
413
|
-
var local: Vector2 = control.get_global_transform_with_canvas() *
|
|
431
|
+
var local: Vector2 = control.get_global_transform_with_canvas() * aim
|
|
414
432
|
|
|
415
433
|
# A control out of sight inside a ScrollContainer is not out of reach, it is one scroll away,
|
|
416
434
|
# which is what a person does without thinking about it before they click. Refusing it
|
|
@@ -434,7 +452,7 @@ func click(params: Dictionary) -> Dictionary:
|
|
|
434
452
|
% [node_path, "freed" if left == "freed" else "taken out of the tree"]
|
|
435
453
|
)
|
|
436
454
|
}
|
|
437
|
-
local = control.get_global_transform_with_canvas() *
|
|
455
|
+
local = control.get_global_transform_with_canvas() * aim
|
|
438
456
|
|
|
439
457
|
var reached: Dictionary = Screen.reach(own, local)
|
|
440
458
|
var viewport: Viewport = reached["viewport"]
|
|
@@ -472,12 +490,19 @@ func click(params: Dictionary) -> Dictionary:
|
|
|
472
490
|
hovered_path = str(hovered.get_path())
|
|
473
491
|
var landed: bool = hovered == control or (hovered != null and control.is_ancestor_of(hovered))
|
|
474
492
|
|
|
493
|
+
# The link a rich label says was pressed, which is its own word for what the press reached.
|
|
494
|
+
var links: Array[String] = []
|
|
495
|
+
var heard: Callable = func(meta: Variant) -> void: links.append(str(meta))
|
|
496
|
+
if rich != null:
|
|
497
|
+
var _listening: int = rich.meta_clicked.connect(heard)
|
|
475
498
|
_press_button(viewport, _button(position, button, true, double))
|
|
476
499
|
await _host.get_tree().process_frame
|
|
477
500
|
_press_button(viewport, _button(position, button, false, false))
|
|
478
501
|
# The release is what a button acts on, and a queue_free it causes lands at the end of
|
|
479
502
|
# this frame; the frame passes so the answer describes the control as the click left it.
|
|
480
503
|
await _host.get_tree().process_frame
|
|
504
|
+
if rich != null and is_instance_valid(rich) and rich.meta_clicked.is_connected(heard):
|
|
505
|
+
rich.meta_clicked.disconnect(heard)
|
|
481
506
|
|
|
482
507
|
# What became of the control: still in the tree, taken out of it, or freed. A button that
|
|
483
508
|
# opened another screen is the second or the third, and the caller wants to hear that
|
|
@@ -497,6 +522,8 @@ func click(params: Dictionary) -> Dictionary:
|
|
|
497
522
|
}
|
|
498
523
|
if found != null:
|
|
499
524
|
answer["found"] = found
|
|
525
|
+
if not links.is_empty():
|
|
526
|
+
answer["link_pressed"] = links.back()
|
|
500
527
|
return _pointer_noted(answer)
|
|
501
528
|
|
|
502
529
|
|
|
@@ -450,6 +450,60 @@ static func _clipped_away(
|
|
|
450
450
|
return false
|
|
451
451
|
|
|
452
452
|
|
|
453
|
+
## Where [param label] draws the words [param wanted] matched in it, in the label's own space, when
|
|
454
|
+
## they are part of its text: {"point"} on their middle, {"unplaced"} saying why there is none, or
|
|
455
|
+
## {} when the words are the whole text, whose middle is the label's.
|
|
456
|
+
##
|
|
457
|
+
## A line of a dispatch names two people, each a link to their page, and a press on the label's
|
|
458
|
+
## middle landed on the second name when the first was asked for, so the game opened the wrong page.
|
|
459
|
+
## Godot answers which line a character is on, where that line is and how wide it is drawn, and
|
|
460
|
+
## nothing narrower, so the place along the line is the font's measure of the text before the
|
|
461
|
+
## words. A line in another font or holding an image measures off from what is drawn, which a wide
|
|
462
|
+
## line with nothing between the words and its start hardly does.
|
|
463
|
+
static func point_on_words(label: RichTextLabel, wanted: String) -> Dictionary:
|
|
464
|
+
var text: String = Words.said_by(label)
|
|
465
|
+
var start: int = -1
|
|
466
|
+
var length: int = 0
|
|
467
|
+
for words: String in Says.alternatives(wanted):
|
|
468
|
+
if not Says.is_plain(words):
|
|
469
|
+
continue
|
|
470
|
+
var at: int = text.findn(words)
|
|
471
|
+
if at >= 0 and (start < 0 or at < start):
|
|
472
|
+
start = at
|
|
473
|
+
length = words.length()
|
|
474
|
+
if start < 0:
|
|
475
|
+
return {"unplaced": "the words were matched as a pattern, which names no one place in the text"}
|
|
476
|
+
if text.strip_edges().length() == length:
|
|
477
|
+
return {}
|
|
478
|
+
var line: int = label.get_character_line(start)
|
|
479
|
+
var shown: Vector2i = label.get_line_range(line)
|
|
480
|
+
if line < 0 or shown.x > start:
|
|
481
|
+
return {"unplaced": "the label could not say which line the words are on"}
|
|
482
|
+
var on_line: int = mini(length, shown.y - start) if shown.y > start else length
|
|
483
|
+
var font: Font = label.get_theme_font("normal_font")
|
|
484
|
+
var font_size: int = label.get_theme_font_size("normal_font_size")
|
|
485
|
+
var before: float = _width(font, font_size, text.substr(shown.x, start - shown.x))
|
|
486
|
+
var across: float = _width(font, font_size, text.substr(start, on_line))
|
|
487
|
+
var frame: StyleBox = label.get_theme_stylebox("normal")
|
|
488
|
+
var left: float = frame.get_margin(SIDE_LEFT)
|
|
489
|
+
var room: float = label.size.x - left - frame.get_margin(SIDE_RIGHT)
|
|
490
|
+
var spare: float = maxf(room - label.get_line_width(line), 0.0)
|
|
491
|
+
var aligned: float = 0.0
|
|
492
|
+
if label.horizontal_alignment == HORIZONTAL_ALIGNMENT_CENTER:
|
|
493
|
+
aligned = spare * 0.5
|
|
494
|
+
elif label.horizontal_alignment == HORIZONTAL_ALIGNMENT_RIGHT:
|
|
495
|
+
aligned = spare
|
|
496
|
+
var scrolled: float = label.get_v_scroll_bar().value if label.scroll_active else 0.0
|
|
497
|
+
var x: float = clampf(left + aligned + before + across * 0.5, left, left + room)
|
|
498
|
+
var top: float = frame.get_margin(SIDE_TOP) + label.get_line_offset(line) - scrolled
|
|
499
|
+
return {"point": Vector2(x, top + label.get_line_height(line) * 0.5)}
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
## How wide [param font] at [param font_size] draws [param words] on one line.
|
|
503
|
+
static func _width(font: Font, font_size: int, words: String) -> float:
|
|
504
|
+
return font.get_string_size(words, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).x
|
|
505
|
+
|
|
506
|
+
|
|
453
507
|
## The canvas layer [param control] is drawn on, 0 for the viewport's own canvas.
|
|
454
508
|
static func _layer_of(control: Control) -> int:
|
|
455
509
|
var layer: CanvasLayer = control.get_canvas_layer_node()
|
package/build/index.js
CHANGED
|
@@ -33904,7 +33904,7 @@ var TOOL_SPECS = [
|
|
|
33904
33904
|
requires: [],
|
|
33905
33905
|
operations: {
|
|
33906
33906
|
click: {
|
|
33907
|
-
summary:
|
|
33907
|
+
summary: `press and release on a Control, a frame apart, and answer with what was under the pointer and what became of the control: in_tree, removed or freed. The control is named by nodePath, or by says with the words on it, found under nodePath when both are given, and the answer then says under found which match it was. Words that are part of a RichTextLabel are pressed where they are drawn rather than at the label's middle, so a link among them is the one pressed: found.pressedOn says "the words", or "the middle of the label" with a note saying why they could not be placed, and link_pressed names the meta the label reports clicked. A control out of sight inside a ScrollContainer is scrolled to first, each container out from it a frame after the one inside, and scrolled_into_view says whether a scroll value changed. A disabled button, and a control that does not process (the game paused, say), are refused: the pointer reaches them and the press does nothing. A 3D node is clicked where it is drawn, and landed then says the interface did not swallow the press: no control under the pointer stopped it, a control letting the pointer pass included, and every viewport on the way out from the room had the pointer over what shows the room`,
|
|
33908
33908
|
requires: []
|
|
33909
33909
|
},
|
|
33910
33910
|
choose: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdharness",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.45",
|
|
4
4
|
"mcpName": "io.github.Aureliolo/gdharness",
|
|
5
5
|
"description": "A harness for driving a Godot 4 project from an agent: editor addons, a runtime bridge into the running game, an MCP server in front of them, and a CLI that installs and diagnoses the Godot side.",
|
|
6
6
|
"type": "module",
|