Thanks for the nudge. Here are the relevant scenes.
Instances of VerbosityNodePanel are added to VNodePanelContainer.
(Don’t worry about “Verbosity” or “VNode”, those are just named after the project.)
VNodeText extends RichTextLabel. It’s signals are connected in VerbosityNodePanel.cs, like so:
public void SetVNode(VerbosityMgr verbosityMgr, VNode node)
{
CheckNodeRefs();
_vNodeText.MetaClicked += verbosityMgr.OnMetaClick;
_vNodeText.MetaHoverStarted += verbosityMgr.OnMetaHoverStarted;
_vNodeText.MetaHoverEnded += verbosityMgr.OnMetaHoverEnded;
_vNodeText.MouseEntered += verbosityMgr.OnTextHoverStarted;
_vNodeText.MouseExited += verbosityMgr.OnTextHoverEnded;
_vNode = node;
_vNodeFoldable.Title = _vNode.Title;
RefreshVNodeText();
}
And here’s the whole of GuiPanel3d, where _UnhandledInput and MouseInputEvent are probably the relevant bits.
public partial class GuiPanel3d : Node3D
{
bool _isMouseOverGui = false;
Vector2? _lastEventPos = null;
double? _lastEventTime = null;
SubViewport? _nodeViewport;
MeshInstance3D? _nodeQuad;
Area3D? _nodeArea;
[MemberNotNull(nameof(_nodeViewport), nameof(_nodeQuad), nameof(_nodeArea))]
protected void CheckNodeRefs()
{
_nodeViewport ??= GetNode<SubViewport>("GuiViewport");
_nodeQuad ??= GetNode<MeshInstance3D>("GuiQuad");
_nodeArea ??= GetNode<Area3D>("GuiQuad/GuiArea");
Debug.Assert(_nodeViewport is not null && _nodeQuad is not null && _nodeArea is not null, "Failed to assign a node.");
}
public override void _Ready()
{
SetProcess(false);
CheckNodeRefs();
_nodeArea.MouseEntered += () => SetIsMouseOverGui(true);
_nodeArea.MouseExited += () => SetIsMouseOverGui(false);
_nodeArea.InputEvent += MouseInputEvent;
}
public override void _Process(double delta)
{
base._Process(delta);
if (_isMouseOverGui)
{
// TODO
}
else SetProcess(false);
}
protected void SetIsMouseOverGui(bool over)
{
_isMouseOverGui = over;
//GD.Print($"GuiPanel3d._isMouseOverGui = {_isMouseOverGui}");
SetProcess(_isMouseOverGui);
}
public override void _UnhandledInput(InputEvent inputEvent)
{
if (inputEvent is InputEventMouseButton || inputEvent is InputEventMouseMotion || inputEvent is InputEventScreenDrag || inputEvent is InputEventScreenTouch) return;
CheckNodeRefs();
_nodeViewport.PushInput(inputEvent);
}
private void MouseInputEvent(Node camera, InputEvent inputEvent, Vector3 worldPos3d, Vector3 normal, long shapeIdx)
{
CheckNodeRefs();
double now = Time.GetTicksMsec() / 1000.0;
double delta = (_lastEventTime is null) ? 0.0 : (now - (_lastEventTime ?? 0.0));
//Transform3D cameraTransform = (camera is Node3D nodeCamera) ? nodeCamera.GlobalTransform : Transform3D.Identity;
Vector2 quadSize2d = (((_nodeQuad.Mesh as QuadMesh)?.Size ?? Vector2.One).Append() * _nodeQuad.GlobalTransform.AffineInverse()).Truncate();
Vector3 guiPos3d = worldPos3d * _nodeQuad.GlobalTransform.AffineInverse();
Vector2 guiPos2d, translation, velocity;
if (_isMouseOverGui)
{
guiPos2d = ((new Vector2(guiPos3d.X, -guiPos3d.Y) / quadSize2d) + new Vector2(0.5f, 0.5f)) * _nodeViewport.Size;
translation = guiPos2d - (_lastEventPos ?? Vector2.Zero);
velocity = translation / (float)delta;
}
else
{
guiPos2d = _lastEventPos ?? Vector2.Zero;
translation = Vector2.Zero;
velocity= Vector2.Zero;
}
if (inputEvent is InputEventMouseMotion motionEvent)
{
if (_lastEventPos is null)
{
motionEvent.Relative = Vector2.Zero;
motionEvent.Velocity = Vector2.Zero;
}
else
{
motionEvent.Relative = translation;
motionEvent.Velocity = velocity;
}
}
else if (inputEvent is InputEventScreenDrag touchEvent)
{
if (_lastEventPos is null)
{
touchEvent.Relative = Vector2.Zero;
touchEvent.Velocity = Vector2.Zero;
}
else
{
touchEvent.Relative = translation;
touchEvent.Velocity = velocity;
}
touchEvent.Position = guiPos2d;
_lastEventPos = guiPos2d;
}
if (inputEvent is InputEventMouse mouseEvent)
{
mouseEvent.Position = guiPos2d;
_lastEventPos = guiPos2d;
mouseEvent.GlobalPosition = guiPos2d;
}
_lastEventTime = now;
//GD.Print($"GuiPanel3D.MouseInputEvent: worldPos3d = {worldPos3d}, guiPos2d = {guiPos2d}");
_nodeViewport.PushInput(inputEvent);
}
}
Transforming from screen to world to in-world GUI definitely works. Using breakpoints, I can see that the MouseInputEvent is executed and the event pushed, even when the signal isn’t fired, regardless of whether the mouse button is pressed, so it seems like the “blockage” is after this.
I’m wondering if it’s something to do with how SubViewport passes input. If I was a bit more confident with Godot, I’d take a look at source myself.