Repository: flutter
Issue: Let developer to disable active pointers canceling when pushing route to Navigator
Labels: framework,f: routes,c: proposal,P3,team-framework,triaged-framework

### Is there an existing issue for this?

- [X] I have searched the [existing issues](https://github.com/flutter/flutter/issues)
- [X] I have read the [guide to filing a bug](https://flutter.dev/docs/resources/bug-reports)

### Use case

Hello,

In my project, I have a custom popup, that opens with a long press gesture and must be able to continue getting pointer move events, that started before the modal popup route was pushed to the Navigator.

But, when the route is pushed, the framework intentionally cancels all active pointers.

I saw these issues that clarified a bit the logic behind this decision:
- https://github.com/flutter/flutter/issues/93534
- https://github.com/flutter/flutter/issues/4770

In my case, I want to have the option to disable active pointers canceling, but there is no easy way to do that now as this logic is implemented in the Navigator class as private methods and I don't really want to extend Navigator and override the `_afterNavigation(...)` method somehow.

### Proposal

Is it possible to add an optional argument to push methods of the Navigator to manage these pointers canceling behavior?
As for me, it seems like an effortless and safe way to do that.

Changes in the 'packages/flutter/lib/src/widgets/navigator.dart' file:

```dart
@optionalTypeArgs
Future<T?> push<T extends Object?>(Route<T> route, {bool cancelActivePointers = true}) {
  _pushEntry(
    _RouteEntry(route, pageBased: false, initialState: _RouteLifecycle.push), 
    cancelActivePointers,
  );
  return route.popped;
}
```

```dart
void _pushEntry(_RouteEntry entry, [bool cancelActivePointers = true]) {
  ...
  _afterNavigation(entry.route, cancelActivePointers);
}
```

```dart
void _afterNavigation(Route<dynamic>? route, [bool cancelActivePointers = true]) {
  ...
  if (cancelActivePointers) {
    _cancelActivePointers();
  }
}
```


https://user-images.githubusercontent.com/818612/236464316-f8ca711c-3229-4e43-9f7f-1e4c7a97e061.mp4



<details>

<summary>The demo app code (with the fixes above):</summary>

```dart
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

void main() => runApp(const MainApp());

class MainApp extends StatefulWidget {
  const MainApp({super.key});

  @override
  State<MainApp> createState() => _MainAppState();
}

class _MainAppState extends State<MainApp> {
  bool _cancelActivePointers = true;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData.dark(),
      home: Scaffold(
        body: Stack(
          children: [
            CustomPopup(
              cancelActivePointers: _cancelActivePointers,
              child: const SafeArea(
                child: Center(
                  child: Padding(
                    padding: EdgeInsets.all(24),
                    child: Text(
                      'Click and hold anywhere on the screen to open the custom modal popup.\n\nThen drag you finger around to move the white circle.',
                      textAlign: TextAlign.center,
                    ),
                  ),
                ),
              ),
            ),
            Positioned(
              top: 24,
              left: 24,
              child: ElevatedButton(
                onPressed: () => setState(() {
                  _cancelActivePointers = !_cancelActivePointers;
                }),
                child: Text(
                  _cancelActivePointers ? 'Canceling ON' : 'Canceling OFF',
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class CustomPopup extends StatefulWidget {
  const CustomPopup({
    super.key,
    required this.child,
    this.cancelActivePointers = true,
  });

  final Widget child;
  final bool cancelActivePointers;

  @override
  State<CustomPopup> createState() => _CustomPopupState();
}

class _CustomPopupState extends State<CustomPopup> {
  final _position = ValueNotifier<Offset?>(null);

  @override
  Widget build(BuildContext context) {
    return Listener(
      onPointerDown: (event) => setState(() => _position.value = null),
      onPointerMove: (event) => setState(() {
        print(event.position);
        _position.value = event.position;
      }),
      child: GestureDetector(
        onLongPress: _showPopup,
        behavior: HitTestBehavior.opaque,
        child: widget.child,
      ),
    );
  }

  void _showPopup() {
    Navigator.of(context).push(
      _CustomPopupRoute(_position),
      cancelActivePointers: widget.cancelActivePointers,
    );
  }
}

class _CustomPopupRoute extends PopupRoute {
  _CustomPopupRoute(this.position);

  final ValueListenable<Offset?> position;

  @override
  Color? get barrierColor => Colors.black.withOpacity(0.2);

  @override
  bool get barrierDismissible => true;

  @override
  String? get barrierLabel => null;

  @override
  Duration get transitionDuration => const Duration(milliseconds: 150);

  @override
  Widget buildPage(
    BuildContext context,
    Animation<double> animation,
    Animation<double> secondaryAnimation,
  ) {
    return Stack(
      children: [
        Center(
          child: FadeTransition(
            opacity: animation,
            child: Container(
              width: 200,
              height: 150,
              color: Colors.purple,
              child: Center(
                child: DefaultTextStyle(
                  style: Theme.of(context).textTheme.titleMedium!,
                  child: AnimatedBuilder(
                    animation: position,
                    builder: (context, child) => Text(
                      position.value == null
                          ? 'position unknown'
                          : '{ ${position.value!.dx.toInt()}, ${position.value!.dy.toInt()} }',
                    ),
                  ),
                ),
              ),
            ),
          ),
        ),
        AnimatedBuilder(
          animation: position,
          builder: (context, child) {
            if (position.value == null) return const SizedBox.shrink();
            const side = 50.0;
            const halfSide = side / 2;
            return Positioned(
              top: position.value!.dy - halfSide,
              left: position.value!.dx - halfSide,
              child: Container(
                width: side,
                height: side,
                decoration: const ShapeDecoration(
                  shape: CircleBorder(
                    side: BorderSide(color: Colors.white, width: 2),
                  ),
                ),
              ),
            );
          },
        ),
      ],
    );
  }
}
```

</details>