Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Add RawMenuAnchor animation callbacks #167806

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: master
Choose a base branch
from

Conversation

davidhicks980
Copy link
Contributor

@davidhicks980 davidhicks980 commented Apr 25, 2025

Alternative to #163481, #167537, #163481 that uses callbacks.

@dkwingsmt - you inspired me to simplify the menu behavior. I didn't end up using Actions, mainly because nested behavior was unwieldy and capturing BuildContext has drawbacks. This uses a basic callback mechanism to animate the menu open and closed. Check out the examples.


The problem

RawMenuAnchor synchronously shows or hides an overlay menu in response to MenuController.open() and MenuController.close, respectively. Because animations cannot be run on a hidden overlay, there currently is no way for developers to add animations to RawMenuAnchor and its subclasses (MenuAnchor, DropdownMenuButton, etc).

The solution

This PR:

  • Adds a transition flag to MenuController.open() and MenuController.close(). This flag defaults to "true"
  • Adds two callbacks -- onOpenRequested and onCloseRequested -- to RawMenuAnchor.

When MenuController.open() and MenuController.close() are called with transition == true (the default), onOpenRequested and onCloseRequested are invoked, respectively.

Developers who are animating a RawMenuAnchor open within onOpenRequested should call MenuController.open(transition: false) whenever they wish to show their menu overlay. Typically, this is before any animations are run.

Developers who are closing a RawMenuAnchor within onCloseRequested should call MenuController.close(transition: false) once they are finished animating their menu closed. This ensures the menu overlay is only hidden when the entire closing animation is finished.

Precursor for #143416, #135025, #143712

Demo

Screen.Recording.2025-02-17.at.8.58.43.AM.mov
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// ignore_for_file: public_member_api_docs

import 'dart:ui' as ui;

import 'package:flutter/material.dart' hide MenuController, RawMenuAnchor, RawMenuOverlayInfo;

import 'raw_menu_anchor.dart';

/// Flutter code sample for a [RawMenuAnchor] that animates a simple menu using
/// [RawMenuAnchor.onOpenRequested] and [RawMenuAnchor.onCloseRequested].
void main() {
  runApp(const App());
}

class Menu extends StatefulWidget {
  const Menu({super.key});

  @override
  State<Menu> createState() => _MenuState();
}

class _MenuState extends State<Menu> with SingleTickerProviderStateMixin {
  late final AnimationController animationController;
  final MenuController menuController = MenuController();

  @override
  void initState() {
    super.initState();
    animationController = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 300),
    );
  }

  @override
  void dispose() {
    animationController.dispose();
    super.dispose();
  }

  void _handleMenuOpenRequest(Offset? position, void Function({Offset? position}) showOverlay) {
    // Mount or reposition the menu before animating the menu open.
    showOverlay(position: position);

    if (animationController.isForwardOrCompleted) {
      // If the menu is already open or opening, the animation is already
      // running forward.
      return;
    }

    // Animate the menu into view. This will cancel the closing animation.
    animationController.forward();
  }

  void _handleMenuCloseRequest(VoidCallback hideOverlay) {
    if (!animationController.isForwardOrCompleted) {
      // If the menu is already closed or closing, do nothing.
      return;
    }

    // Animate the menu out of view.
    //
    // Be sure to use `whenComplete` so that the closing animation
    // can be interrupted by an opening animation.
    animationController.reverse().whenComplete(() {
      if (mounted) {
        // Hide the menu after the menu has closed
        hideOverlay();
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return RawMenuAnchor(
      controller: menuController,
      onOpenRequested: _handleMenuOpenRequest,
      onCloseRequested: _handleMenuCloseRequest,
      overlayBuilder: (BuildContext context, RawMenuOverlayInfo info) {
        final ui.Offset position = info.anchorRect.bottomLeft;
        return Positioned(
          top: position.dy + 5,
          left: position.dx,
          child: TapRegion(
            groupId: info.tapRegionGroupId,
            child: Material(
              color: ColorScheme.of(context).primaryContainer,
              shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
              elevation: 3,
              child: SizeTransition(
                sizeFactor: animationController,
                child: const SizedBox(
                  height: 200,
                  width: 150,
                  child: Center(child: Text('Howdy', textAlign: TextAlign.center)),
                ),
              ),
            ),
          ),
        );
      },
      builder: (BuildContext context, MenuController menuController, Widget? child) {
        return FilledButton(
          onPressed: () {
            if (animationController.isForwardOrCompleted) {
              menuController.close();
            } else {
              menuController.open();
            }
          },
          child: const Text('Toggle Menu'),
        );
      },
    );
  }
}

class App extends StatelessWidget {
  const App({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue)),
      home: const Scaffold(body: Center(child: Menu())),
    );
  }
}

Pre-launch Checklist

If you need help, consider asking for advice on the #hackers-new channel on Discord.

@github-actions github-actions bot added framework flutter/packages/flutter repository. See also f: labels. d: api docs Issues with https://api.flutter.dev/ d: examples Sample code and demos labels Apr 25, 2025
@davidhicks980 davidhicks980 marked this pull request as draft April 25, 2025 11:44
@davidhicks980 davidhicks980 changed the title Implement callbacks Add RawMenuAnchor animation callbacks Apr 25, 2025
Copy link
Contributor

@dkwingsmt dkwingsmt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice one! I like how this solution targets the core obstacle in a direct and simple way, i.e. how to pass the message from the menu controller to the themed menu.

This entire batch of suggestions focus on the documentation and naming, mostly because I myself was a bit lost while reading everything, especially everything related to "request". I'd like to propose a different set of terms to make the structure a bit clearer, and to also verify my understanding. Feel free to take any or no parts of my suggestion or change it whatever way you think fits.

@@ -186,6 +188,12 @@ class RawMenuAnchor extends StatefulWidget {
/// A callback that is invoked when the menu is closed.
final VoidCallback? onClose;

/// A callback that is invoked when [MenuController.open] is called.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// A callback that is invoked when [MenuController.open] is called.
/// Called when a request is made to open the menu.
///
/// This callback is typically used by themed menu widgets to intercept open
/// requests, for example, to play an animation or delay the operation.
/// If the request is intercepted, it is the responsibility of the handler to
/// eventually call [MenuController.open] with `transition: true`.
/// By default, this callback directly makes the aforementioned call.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added more words since we added the showOverlay/hideOverlay callbacks, but modeled the docs after your suggestion. Let me know what you think.

/// A callback that is invoked when [MenuController.open] is called.
final VoidCallback? onOpenRequested;

/// A callback that is invoked when [MenuController.close] is called.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// A callback that is invoked when [MenuController.close] is called.
/// Called when a request is made to close the menu.
///
/// This callback is typically used by themed menu widgets to intercept close
/// requests, for example, to animate the menu out or delay the dismissal.
/// If the request is intercepted, it is the responsibility of the handler to
/// eventually result in a call to [MenuController.open] with `transition: true`.
/// By default, this callback directly makes the aforementioned call.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added more words since we added the showOverlay/hideOverlay callbacks, but modeled the docs after your suggestion. Let me know what you think.

/// size, then any open menus will automatically close.
void open({Offset? position}) {
/// size, then any open menu will automatically close.
void open({Offset? position, bool transition = true}) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to avoid the same method being called twice with different flags during a single opening/closing action, since this call stack will be confusing and even prone to infinite recursion if not managed correctly.

What if, instead of making onOpenRequested call menuController.open(transition: true), the onOpenRequested is given a callback to finalize the opening? Do you think this will be simpler?

Another approach is to add performOpen and performClose methods to MenuController. We can clearly document them to avoid being miscalled by applications.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was debating between the callback or the transition property.. the recursion problem is a great point. What do you think would be a good name for the callback? For the decorator I used markMenuOpened, but that was a bit confusing. done()/complete()/finishedOpen/finish?

Copy link
Contributor

@dkwingsmt dkwingsmt Apr 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any of them is an improvement, which we can start with.

Another option is to use a name with better meaning, such as showOverlay. Although this isn't the only thing that this callback does, my understanding is that this gives a close enough impression to the user (library developer) what it does.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sweet -- I'll change the callbacks to showOverlay/hideOverlay.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done! Unfortunately, function signatures don't seem to show positional argument names when using intellisense. Currently, I'm passing the position (1st argument in photo) and the showOverlay callback (2nd argument) into onOpenRequested, but I'm not sure if the ambiguity of the function signature warrants using named parameters. Otherwise, seems to work well.

image

/// Close the menu.
/// Close the menu and all of its children.
///
/// If `inDispose` is true, the menu will close without rebuilding its parent.
Copy link
Contributor

@dkwingsmt dkwingsmt Apr 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we rename it triggerRebuild (with negation) for more clarity what it does?

  /// If `triggerRebuild` is false, the menu will close without rebuilding its parent, which is useful when the
  /// menu is closed due to unmounting. Defaults to true.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That sounds good to me, although this was from the original MenuAnchor (not sure if it matters).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you meant to say RawMenuAnchor? That's fine, because these APIs are private.

Copy link
Contributor Author

@davidhicks980 davidhicks980 May 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, the original MenuAnchor that RawMenuAnchor is based off of. But sounds good -- I'll make the change.

Edit: One caveat I just came across. inDispose only blocks rebuilds for the overlay menu, and it also blocks a post-frame callback if the menu was in the process of closing. It may actually be better if we just took out the "If inDispose is true, the menu will close without rebuilding its parent." comment. Let me know...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I don't quite understand. Can you point out the line that implements each usage of inDispose?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instances are below.

  // in _RawMenuAnchorBaseMixin
  @protected
  void closeChildren({bool inDispose = false}) {
    assert(_debugMenuInfo('Closing children of $this${inDispose ? ' (dispose)' : ''}'));
    for (final _RawMenuAnchorBaseMixin child in List<_RawMenuAnchorBaseMixin>.from(
      _anchorChildren,
    )) {
     // ** Skipping the child's closing animation if we are disposing
      if (inDispose) {
        child.close(inDispose: inDispose);
      } else {
        child.handleCloseRequest();
      }
    }
  }

  // In _RawMenuAnchorGroupState
 @override
  void close({bool inDispose = false}) {
    if (!isOpen) {
      return;
    }

    closeChildren(inDispose: inDispose);

     // ** Notifying our parent and rebuilding this widget iff we are not disposing. 
    if (!inDispose) {
      if (SchedulerBinding.instance.schedulerPhase != SchedulerPhase.persistentCallbacks) {
        setState(() {
          // Mark dirty, but only if mounted and not in a build.
        });
      } else {
        SchedulerBinding.instance.addPostFrameCallback((Duration timestamp) {
          if (mounted) {
            setState(() {
              // Mark dirty.
            });
          }
        });
      }
    }
  }

// In _RawMenuAnchorState
  @override
  void close({bool inDispose = false}) {
    assert(_debugMenuInfo('Closing $this'));
    if (!isOpen) {
      return;
    }

    closeChildren(inDispose: inDispose);

    if (SchedulerBinding.instance.schedulerPhase != SchedulerPhase.persistentCallbacks) {
      _overlayController.hide();
    } else if (!inDispose) {
      // ** Adding a post-frame callback to close this overlay iff we are not disposing.
      SchedulerBinding.instance.addPostFrameCallback((_) {
        _overlayController.hide();
      }, debugLabel: 'MenuAnchor.hide');
    }

     // ** Notify our parent and rebuild this widget iff we are not disposing. 
    if (!inDispose) { 
      _parent?._childChangedOpenState();
      widget.onClose?.call();
      if (mounted &&
          SchedulerBinding.instance.schedulerPhase != SchedulerPhase.persistentCallbacks) {
        setState(() {
          // Mark dirty, but only if mounted and not in a build.
        });
      }
    }
  }

///
/// The optional `position` argument should specify the location of the menu
/// in the local coordinates of the [RawMenuAnchor].
void requestOpen({Offset? position}) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest move the implementation of _RawMenuAnchorBaseMixin.requestOpen to _RawMenuAnchorState since this default implementation only affects this widget alone.

Also I suggest renaming this method to either handleControllerOpen or handleOpenRequested to clearly indicate when it is called. In general, I consider the two methods "handlers" because they're basically callbacks to be implemented in the perspective of the menu controller.

(Same suggestions for requestClose.)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another way is to define a getter that returns onOpenRequested, i.e. both the anchor and the anchor group has onOpenRequested for the menu controller to use.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I renamed the callbacks to handleOpenRequested/handleCloseRequested. Moving handleOpenRequested on to only RawMenuAnchor is problematic, since MenuController only has access to _RawMenuAnchorBaseMixin. While we could do a typecheck (check if _anchor is _RawMenuAnchorState), that seems messier than just keeping them on the base class. Let me know what you think.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I mean keeping the handleOpenRequested as pure method and move the implementation to the two state classes. Is it possible?

@dkwingsmt
Copy link
Contributor

Also cc @chunhtai

@davidhicks980 davidhicks980 marked this pull request as ready for review May 5, 2025 02:09
Comment on lines +210 to +217
/// Called when the menu overlay is shown
///
/// This callback is called when the menu overlay is added to the widget tree,
/// typically before opening animations begin. [onOpen] can be used to respond
/// when the menu first becomes interactive, such as by setting focus to the
/// menu.
///
/// An open menu that is repositioned will not trigger [onOpen].
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Slight fix.

Suggested change
/// Called when the menu overlay is shown
///
/// This callback is called when the menu overlay is added to the widget tree,
/// typically before opening animations begin. [onOpen] can be used to respond
/// when the menu first becomes interactive, such as by setting focus to the
/// menu.
///
/// An open menu that is repositioned will not trigger [onOpen].
/// Called when the menu overlay is shown.
///
/// This callback is triggered when the menu overlay is inserted into the widget
/// tree, typically before any opening animations begin. [onOpen] can be used to
/// respond when the menu first becomes interactive, such as by setting focus to
/// a menu item.
///
/// This callback is not called when an already open menu is repositioned.

Comment on lines 220 to 225
/// Called when the menu overlay is hidden.
///
/// This callback is triggered after the menu overlay has been removed from
/// the widget tree, typically after all closing animations have completed. It
/// is typically used by applications to respond when the menu has been
/// dismissed.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Slight fix, I wonder if this is the correct understanding.

Suggested change
/// Called when the menu overlay is hidden.
///
/// This callback is triggered after the menu overlay has been removed from
/// the widget tree, typically after all closing animations have completed. It
/// is typically used by applications to respond when the menu has been
/// dismissed.
/// Called when the menu overlay is hidden.
///
/// This callback is triggered when the menu overlay is removed from the widget
/// tree, typically after any closing animations have completed. It is typically
/// used by applications to respond when the menu has been dismissed, such as
/// by restoring focus to a previously active element.

Also did you remove

  /// It is not called when the menu is unmounted.
  /// By default it does nothing.

intentionally? I think the two sentences are worth adding. (The 2nd sentence also to onOpen).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, this is another confusing aspect of the menu. If the focus moves onto the menu, the menu takes focus when it begins opening (when onOpen is called), and moves off the menu when it begins closing (when onCloseRequested is called). I observed this in several menus. Otherwise, it'd be bad a11y -- the user would have to wait for the menu to open or close before that user could move to another focus node.

For the removal question, I'm not sure why I took out the first part ("It is not called when the menu is unmounted") -- it may have been an accident. The "by default it does nothing" part was removed since the value defaults to null, so I thought it was evident that the callback did nothing, but I do know there are classes that have default behavior if a null value is provided. So, I'll add them both back in.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mind if I rewrite it slightly to "By default, [onOpen] does nothing"? The rewording makes it a bit less ambigious since "menu" is also being referenced in the previous sentence.

/// Close the menu.
/// Close the menu and all of its children.
///
/// If `inDispose` is true, the menu will close without rebuilding its parent.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I don't quite understand. Can you point out the line that implements each usage of inDispose?

///
/// The optional `position` argument should specify the location of the menu
/// in the local coordinates of the [RawMenuAnchor].
void requestOpen({Offset? position}) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I mean keeping the handleOpenRequested as pure method and move the implementation to the two state classes. Is it possible?

Comment on lines +244 to +249
/// The `position` argument is the position passed to [MenuController.open].
/// Handlers should provide this argument to `showOverlay` in order to
/// position the menu relative to the anchor. When a menu is repositioned,
/// [onOpenRequested] may be called with a new `position` value while the menu
/// is still open. In this case, opening delays or animations should be
/// skipped, and `showOverlay` should be called with the new `position` value.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a way to distinguish between repositioning and fresh open? Do you think a repositioning callback would help?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is possible, but I would advise against it since the user has a few ways of figuring out if it's a reposition. If the menu is already open or opening, the user could check if their animation is already running forward or has finished. The user could also store the value of "position" and check for equality.

Also, the separate callback would still need a way of calling showOverlay with the new position, so users would have to write two callbacks that behave almost the same. Likewise, since "position" can be null, it could introduce some confusion as to whether moving from a null to a non-null "position" and vise versa counts as repositioning.

@override
void handleOpenRequest({ui.Offset? position}) {
if (widget.onOpenRequested != null) {
widget.onOpenRequested!(position, open);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems to me that this if clause can be refactored so that widget.onOpenRequested is non null and has a default callback

  onOpenRequested = onOpenRequested ?? (position, open) => open(position)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where would we be assigning onOpenRequested? We can't do the assignment in the RawMenuAnchor constructor since the widget is const.

While we could write something like widget.onOpenRequested?.call(position, open) ?? open(position), I tend to find null coalescing to be a bit unsightly compared to using a boring "if" statement. I may just be getting old.

@davidhicks980
Copy link
Contributor Author

@dkwingsmt

No, I mean keeping the handleOpenRequested as pure method and move the implementation to the two state classes. Is it possible?

I missed this comment. I think I pushed changes that do this -- let me know if it looks right. By pure method, are you referring to an abstract method in _RawMenuAnchorBaseMixin?

@chunhtai chunhtai self-requested a review May 5, 2025 17:21
Copy link
Contributor

@chunhtai chunhtai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall LGTM, I think this pr is cleaner now. Thanks for working on this!

/// Signature for the callback used by [RawMenuAnchor.onOpenRequested] to
/// respond to a request to open a menu.
typedef RawMenuAnchorOpenRequestedCallback =
void Function(Offset? position, void Function({Offset? position}) showOverlay);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should also typedef the parameter type Function({Offset? position})

@@ -117,6 +117,15 @@ typedef RawMenuAnchorOverlayBuilder =
typedef RawMenuAnchorChildBuilder =
Widget Function(BuildContext context, MenuController controller, Widget? child);

/// Signature for the callback used by [RawMenuAnchor.onOpenRequested] to
/// respond to a request to open a menu.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the comment should also explain what each parameter are and how to use them in typical cases

@override
void handleCloseRequest() {
if (widget.onCloseRequested != null) {
if (SchedulerBinding.instance.schedulerPhase != SchedulerPhase.persistentCallbacks) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need a postframe callback? would be good to have some comments on this

/// is still open. In this case, opening delays or animations should be
/// skipped, and `showOverlay` should be called with the new `position` value.
///
/// Defaults to null, which means that the menu will be opened immediately.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Somewhere in here or other place should document the invoking order of onOpenRequested, showOverlay and onOpen same for onCloseRequest and onClose

///
/// Unless the menu needs to be closed immediately, [handleCloseRequest] should be
/// called instead of [close]. Doing so allows subclasses to control how the
/// menu is opened.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// menu is opened.
/// menu is closed.

/// skipped, and `showOverlay` should be called with the new `position` value.
///
/// Defaults to null, which means that the menu will be opened immediately.
final RawMenuAnchorOpenRequestedCallback? onOpenRequested;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this have a default value to call the showOverlay directly?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tong also was wondering this, and at the time I thought it would make RawMenuAnchor non const. I just checked, and it turns out I was wrong. So we can. Do these look good, or should I rename/make public?

static void _defaultOnOpenRequested(
    Offset? position,
    RawMenuAnchorShowOverlayCallback showOverlay,
) {
   showOverlay(position: position);
}

static void _defaultOnCloseRequested(RawMenuAnchorHideOverlayCallback close) {
    close();
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
d: api docs Issues with https://api.flutter.dev/ d: examples Sample code and demos framework flutter/packages/flutter repository. See also f: labels.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants