Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to show a menu at press/finger/mouse/cursor position in flutter

I have this piece of code which I got from Style clipboard in flutter

showMenu(
        context: context,
        // TODO: Position dynamically based on cursor or textfield
        position: RelativeRect.fromLTRB(0.0, 600.0, 300.0, 0.0),
        items: [
          PopupMenuItem(
            child: Row(
              children: <Widget>[
                // TODO: Dynamic items / handle click
                PopupMenuItem(
                  child: Text(
                    "Paste",
                    style: Theme.of(context)
                        .textTheme
                        .body2
                        .copyWith(color: Colors.red),
                  ),
                ),
                PopupMenuItem(
                  child: Text("Select All"),
                ),
              ],
            ),
          ),
        ],
      );

This code works great, except that the popup that is created is at a fixed position, how would I make it so that it pops up at the mouse/press/finger/cursor position or somewhere near that, kind of like when you want to copy and paste on your phone. (This dialog popup will not be used for copy and pasting)

like image 544
Arthur Facredyn Avatar asked Jan 20 '19 19:01

Arthur Facredyn


People also ask

How do I set the popup menu position in flutter?

position property Null safetyoffset is used to change the position of the popup menu relative to the position set by this parameter. When not set, the position defaults to PopupMenuPosition. over which makes the popup menu appear directly over the button that was used to create it.

How do you get the mouse position flutter?

For mouse movements, you may use the MouseRegion widget. It takes 3 callbacks as parameter: onEnter (when the cursor enters an area), onHover (when the cursor moves within the area), onExit (when the cursor leaves the area). You may also get the position of the cursor from the events being passed to the callbacks.

How do I use the popup menu in flutter?

Displays a menu when pressed and calls onSelected when the menu is dismissed because an item was selected. The value passed to onSelected is the value of the selected menu item. One of child or icon may be provided, but not both. If icon is provided, then PopupMenuButton behaves like an IconButton.


2 Answers

Here is a reusable widget which does what you need. Just wrap your Text or other Widget with this CopyableWidget and pass in the onGetCopyTextRequested. It will display the Copy menu when the widget is long pressed, copy the text contents returned to the clipboard, and display a Snackbar on completion.

/// The text to copy to the clipboard should be returned or null if nothing can be copied
typedef GetCopyTextCallback = String Function();

class CopyableWidget extends StatefulWidget {

    final Widget child;
    final GetCopyTextCallback onGetCopyTextRequested;

    const CopyableWidget({
        Key key,
        @required this.child,
        @required this.onGetCopyTextRequested,
    }) : super(key: key);

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

class _CopyableWidgetState extends State<CopyableWidget> {

    Offset _longPressStartPos;

    @override
    Widget build(BuildContext context) {
        return InkWell(
            highlightColor: Colors.transparent,
            onTapDown: _onTapDown,
            onLongPress: () => _onLongPress(context),
            child: widget.child
        );
    }

    void _onTapDown(TapDownDetails details) {
        setState(() {
            _longPressStartPos = details?.globalPosition;
        });
    }

    void _onLongPress(BuildContext context) async {
        if (_longPressStartPos == null)
            return;

        var isCopyPressed = await showCopyMenu(
            context: context,
            pressedPosition: _longPressStartPos
        );
        if (isCopyPressed == true && widget.onGetCopyTextRequested != null) {
            var copyText = widget.onGetCopyTextRequested();
            if (copyText != null) {
                await Clipboard.setData(ClipboardData(text: copyText));

                _showSuccessSnackbar(
                    context: context,
                    text: "Copied to the clipboard"
                );
            }
        }
    }

    void _showSuccessSnackbar({
        @required BuildContext context,
        @required String text
    }) {
        var scaffold = Scaffold.of(context, nullOk: true);
        if (scaffold != null) {
            scaffold.showSnackBar(
                SnackBar(
                    content: Row(
                        children: <Widget>[
                            Icon(
                                Icons.check_circle_outline,
                                size: 24,
                            ),
                            SizedBox(width: 8),
                            Expanded(
                                child: Text(text)
                            )
                        ],
                    )
                )
            );
        }
    }
}

Future<bool> showCopyMenu({
    BuildContext context,
    Offset pressedPosition
}) {
    var x = pressedPosition.dx;
    var y = pressedPosition.dy;

    return showMenu<bool>(
        context: context,
        position: RelativeRect.fromLTRB(x, y, x + 1, y + 1),
        items: [
            PopupMenuItem<bool>(value: true, child: Text("Copy")),
        ]
    );
}
like image 155
dewald Avatar answered Sep 18 '22 04:09

dewald


enter image description here

Use gesture detector's onTapDown like this

 GestureDetector(
        onTapDown: (TapDownDetails details) {
          showPopUpMenu(details.globalPosition);
        },

then in this method we use tap down details to find position

Future<void> showPopUpMenu(Offset globalPosition) async {
double left = globalPosition.dx;
double top = globalPosition.dy;
await showMenu(
  color: Colors.white,
  //add your color
  context: context,
  position: RelativeRect.fromLTRB(left, top, 0, 0),
  items: [
    PopupMenuItem(
      value: 1,
      child: Padding(
        padding: const EdgeInsets.only(left: 0, right: 40),
        child: Row(
          children: [
            Icon(Icons.mail_outline),
            SizedBox(
              width: 10,
            ),
            Text(
              "Menu 1",
              style: TextStyle(color: Colors.black),
            ),
          ],
        ),
      ),
    ),
    PopupMenuItem(
      value: 2,
      child: Padding(
        padding: const EdgeInsets.only(left: 0, right: 40),
        child: Row(
          children: [
            Icon(Icons.vpn_key),
            SizedBox(
              width: 10,
            ),
            Text(
              "Menu 2",
              style: TextStyle(color: Colors.black),
            ),
          ],
        ),
      ),
    ),
    PopupMenuItem(
      value: 3,
      child: Row(
        children: [
          Icon(Icons.power_settings_new_sharp),
          SizedBox(
            width: 10,
          ),
          Text(
            "Menu 3",
            style: TextStyle(color: Colors.black),
          ),
        ],
      ),
    ),
  ],
  elevation: 8.0,
).then((value) {
  print(value);
  if (value == 1) {
    //do your task here for menu 1
  }
  if (value == 2) {
    //do your task here for menu 2
  }
  if (value == 3) {
    //do your task here for menu 3
  }
});

hope it works

like image 44
Sambhav jain Avatar answered Sep 18 '22 04:09

Sambhav jain