LogoIgnis

Inputs

Hit areas that recognize pointer gestures, and how they compose.

Partial. What is here is accurate, but the page is below its depth floor.

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.

Loading the scene…

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#

InputPurposeSignals
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.

By the way...

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.autofocus defaults to true, 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 passes autofocus: false.
  • HoverInput reports hover state, and the piece is what turns that into a highlight. Mapping it to a MouseCursor instead is yours to write, since the cursor is Flutter's to set.