0

I am painting text and other objects on a canvas in flutter.

I would like to detect when the mouse is over some text that is rendered on the canvas.

The TextSpan class has the recognizer property as well as onEnter and onExit.

they don't seem to be getting called when the text span is painted on the canvas.

Do I need to write the hit test logic for text on Canvas myself?

xerotolerant
  • 1,955
  • 4
  • 21
  • 39

1 Answers1

2

I couldn't find a way of doing it as you wanted, but I've been able to do it with a different approach. In this example I'm wrapping the CustomPaint with a MouseRegion (because I'm testing it with flutter web, you could recreate it for mobiles with InkWell or GestureDetector) and then I'm checking if the localPosition of the mouse in inside the rect of the TextPainter. I'm creating the TextPainter outside CustomPainter because I wanted the full canvas to be twice the TextPainter's size.

class GestureText extends StatefulWidget {
  const GestureText(this.text, {required this.style, this.maxWidth, Key? key})
      : super(key: key);
  final String text;
  final TextStyle style;
  final double? maxWidth;

  @override
  _GestureTextState createState() => _GestureTextState();
}

class _GestureTextState extends State<GestureText> {

  Offset? mousePosition;

  @override
  Widget build(BuildContext context) {
    final TextPainter textPainter = TextPainter(
      text: TextSpan(
        text: widget.text,
        style: widget.style,
      ),
      textDirection: TextDirection.ltr,
    )..layout(maxWidth: widget.maxWidth ?? double.infinity);

    return Container(
      child: MouseRegion(
        onHover: (e){
          setState(() {
            mousePosition = e.localPosition;
          });
        },
        onExit: (e){
          setState(() {
            mousePosition = null;
          });
        },
        child: CustomPaint(
          size: Size(textPainter.size.width*2, textPainter.size.width*2),
          painter: GestureTextPainter(textPainter, mousePosition),
        ),
      ),
    );
  }
}

class GestureTextPainter extends CustomPainter {
  const GestureTextPainter(this.textPainter, this.mousePosition);

  final TextPainter textPainter;
  final Offset? mousePosition;

  @override
  void paint(Canvas canvas, Size size) {

    final textOffset = Offset(size.width/2  - textPainter.width/2, size.height/2 - textPainter.height/2); 
// So that the text is in the middle of the canvas

    final textRect = Rect.fromLTWH(textOffset.dx, textOffset.dy, textPainter.width, textPainter.height);


    if(mousePosition != null && textRect.contains(mousePosition!)){
      canvas.drawRect(textRect, Paint()..color=Colors.amber);
    }

    textPainter.paint(canvas, textOffset);

  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}
  • I figured this is what I would have to do. In my case I have many text elements on the canvas so I'll have to keep track of all of them. I can then check the mouse region to see if it overlaps with any of them. I was just hoping there the text span listeners would handle that. Thanks for your reply, – xerotolerant Jul 11 '21 at 20:47
  • You could even do the algorithm more efficient by dividing the canvas in four (or more) main sections and check if the mouse is on it, if so then iterate through the texts inside that section. – Gustavo Guzman Jul 12 '21 at 11:32