Search code examples
flutterdartcanvaswidgetsprite

Flutter: Rect.fromCenter not drawing square where I click


I am trying to draw a square when I click on the screen. I have the drawing logic working and I can do it onclick but for some reason it's drawing the squares somewhat randomly. Ideally I want it to be drawn from the point of click. Wherever I click should be the center of the new square. Here is what currently happens when I click the iOS simulator.

enter image description here

I was trying to use event.pointerEvent.localPosition.dx and ...dy where event is of type SpriteBoxEvent. I am using them to create an Offset instance which will be used in Rect.fromCenter as I figured that would be best to use with my pointer coordinates since I wanted them to be the center of the square.

Below is my code for my SpriteSquare widget and the handleAdd function that is in the parent widget, MySpriteGame (minus the imports as those aren't causing any issues).

Here is the full GitHub repo.

sprite_square.dart

class SpriteSquare extends NodeWithSize {
  
  Color color;
  Function handler;
  double left;
  double top;

  SpriteSquare(size, this.color, this.handler, this.top, this.left) : super(size){
    userInteractionEnabled = true;
  }

  @override handleEvent(SpriteBoxEvent event) {
    handler(event);
    return true;
  }

  @override
    Future<void> paint(Canvas canvas) async {
      canvas.drawRect(
        Rect.fromLTWH(left, top, size.height, size.width),
        Paint()..color = color
      );
    }
}

handleAdd

handleAdd(SpriteBoxEvent event) {
    final _whitesquare = SpriteSquare(
      const Size(50.0, 50.0), 
      const Color(0xFFFFFFFF), 
      handleRemoveSelf, 
      Offset(event.pointerEvent.localPosition.dx, event.pointerEvent.localPosition.dy)
    );
    _background.addChild(_whitesquare);
    return true;
  }

Solution

  • You're creating your background SpriteSquare with wrong sizes and then you are adding other SpriteSquare with positions computed from touch event positions transformed to SpriteWidget's local position which can be smaller or bigger then your background SpriteSquare. Here is how I solved it:

    1. Removed SingleChildScrollView from your MyHomePage widget.
    2. Changed LayoutBuilder position in widget tree. So the final MyHomePage's build function body is like this:
    return Scaffold(
        appBar: AppBar(
          title: Text(widget.title),
        ),
        body: Center(child: LayoutBuilder(builder: (ctx, constrain
          return Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                const Text(
                  'Select Game Engine',
                ),
                DropdownButton<String>(
                  value: _whichEngine,
                  icon: const Icon(Icons.arrow_downward),
                  iconSize: 24,
                  elevation: 16,
                  style: const TextStyle(color: Colors.deepPurple)
                  underline: Container(
                    height: 2,
                    color: Colors.deepPurpleAccent,
                  ),
                  onChanged: (String? newValue) {
                    setState(() {
                      _whichEngine = newValue!;
                    });
                  },
                  items: <String>[
                    'Flame',
                    'Quill',
                    'SpriteWidget',
                    'Feathers',
                    'Unity'
                  ].map<DropdownMenuItem<String>>((String value) {
                    return DropdownMenuItem<String>(
                      value: value,
                      child: Text(value),
                    );
                  }).toList(),
                ),
                Expanded(
                  child: LayoutBuilder(
                    builder: (context, constraints) {
                      final size =
                          Size(constraints.maxWidth, constraints.m
                      if (_whichEngine == 'Flame') {
                        return GameWidget(
                          game: MyFlameGame(),
                        );
                      } else if (_whichEngine == 'SpriteWidget') {
                        return MySpriteGame(size: size);
                      } else {
                        return MySpriteGame(size: size);
                      }
                    },
                  ),
                ),
              ]);
        })));
    
    1. Fixed wrong order of height and width in SpriteSquare. So this is the body of paint function in SpriteSquare now:
    canvas.drawRect(
      Rect.fromLTWH(left, top, size.height, size.width),
      Paint()..color = color
    );
    
    1. Changed MySpriteGame to take a size from it's parent and construct background with that size and tweaked values in handleAdd:
    class MySpriteGame extends StatelessWidget {
      final _background;
    
      MySpriteGame({Key? key, required Size size})
          : _background = SpriteSquare(size, const Color(0xFF000000), () {}, 0, 0),
            super(key: key) {}
    
      handleAdd(SpriteBoxEvent event) {
        final _whitesquare = SpriteSquare(
          const Size(50.0, 50.0),
          const Color(0xFFFFFFFF),
          handleRemoveSelf,
          event.pointerEvent.localPosition.dy - 25,
          event.pointerEvent.localPosition.dx - 25,
        );
        _background.addChild(_whitesquare);
        return true;
      }
    
      handleRemoveSelf(SpriteBoxEvent event) {
        print('hello');
        return true;
      }
    
      @override
      Widget build(BuildContext context) {
        _background.handler = handleAdd;
        return SpriteWidget(_background);
      }
    }
    

    With these changes it's working correctly now.

    enter image description here