InputNode is a hit area that recognizes pointer gestures by delegating to Flutter's own gesture recognizers. It carries its own shape, so it is free to cover an area larger or smaller than whatever it represents.
The demo below is a DragInput on a piece, and two zones that report collisions with it. Drag the square onto a zone.
Drag the square onto a zone.
That status line is not painted inside the scene. It is HTML on this page, updated by a signal the scene emits - the page and the engine share Dart state directly.
The Inputs#
| Input | Purpose | Signals |
|---|---|---|
TapInput |
Recognizes taps. | onTapDown, onTapUp, onTap, onTapCancel |
DragInput |
Recognizes drags. |
onDragStart
,
onDragUpdate
,
onDragEnd
,
onDragCancel
|
HoverInput |
Tracks mouse hover. | onHoverEnter, onHoverExit |
A node wanting more than one gesture just adds more input nodes.
Wiring One Up#
Inputs are declared in build(), like any other behavior. The piece above is a ShapeNode
that adds a slightly larger DragInput over itself, so it is easier to grab than it is to hit exactly.
class PieceNode extends ShapeNode {
PieceNode({required super.position})
: super(
shape: .square(60),
anchor: .center,
paint: Paint()..color = Color(0xFFFFAB40),
);
@override
void build() {
super.build();
final drags = add(
DragInput(
shape: .square(shape.width + 20),
anchor: anchor,
),
);
drags.onDragUpdate((event) {
position.add(event.delta);
});
}
}
Overlap and Fallthrough#
When multiple input nodes overlap, priority decides who is tried first. An event a node does not apply to - a
HoverInput receiving a tap, say - falls through to the next input node. Once a node does
claim an event the search stops there, unless its behavior is HitBehavior.translucent.
Hit testing walks children in reverse priority order before the node's own hit area, mirroring reverse paint order. The topmost thing you can see is the first thing tried.
A TapInput and a DragInput on the same node are a special case of this, and they sort themselves out without your help. Both are offered the pointer, and Flutter's gesture arena decides between them: the drag claims the pointer the moment real movement starts, which cancels the tap. Hold still and the tap wins on release instead.
That means a TapInput does not give up on its own when the pointer moves. It stays down until it is released or the arena takes it away, so pressing and holding is just
onTapDown with no onTapCancel after it, and isDown stays true throughout. Pass a
slop if you want the tap to quit earlier than the arena would - the distance, in logical pixels, the pointer may drift before it cancels.
Pointer and Focus#
This page is a Flutter view embedded in an HTML document, so the scene and the page negotiate over the pointer:
-
The scene mounts without taking focus.
SceneWidget.autofocusdefaults totrue, which is right for a game filling the window and wrong for a demo inside prose, where it would scroll the reader down on load. This page passesautofocus: false. -
HoverInputreports hover state, and the piece is what turns that into a highlight. Mapping it to aMouseCursorinstead is yours to write, since the cursor is Flutter's to set.
