showMorphMenu function
- BuildContext context, {
- Object? from,
- required List<
MorphMenuItem> items, - MorphSurfaceSpec? surface,
- MorphMotion? motion,
- double width = 250,
- double maxScrimOpacity = 0.2,
- String? semanticLabel,
The iOS 26 staple as one call: a control becomes ITS OWN menu.
The control's surface expands into a popover anchored to where the control stands, the rows cascade in on sub-ranges of the SAME spring (MorphReveal - the cascade is the menu's opening, not a separate clock), and closing collapses the menu back into the control. Identity is never broken: the thing you tapped is the thing you use.
context should sit at (or under) the control: its render box
anchors the popover, and inside a MorphTag's subtree the flight
source is inferred - a dragged or scrolled control anchors its menu
wherever it currently stands. The popover surface adopts the
ambient defaults unless surface overrides them.
Implementation
MorphFlight showMorphMenu(
BuildContext context, {
Object? from,
required List<MorphMenuItem> items,
MorphSurfaceSpec? surface,
MorphMotion? motion,
double width = 250,
double maxScrimOpacity = 0.2,
String? semanticLabel,
}) {
assert(items.isNotEmpty, 'a menu needs at least one item.');
// Overlay-relative, not screen-relative: inside a nested navigator
// the two differ, and the popover must anchor in the space the
// flight renders in.
final Rect anchor = morphAnchorRect(context);
// The popover is a fixed box: its height must ride the ambient text
// scale or accessibility sizes overflow the rows.
final double rowHeight =
48 +
2 *
(MediaQuery.textScalerOf(context).scale(_menuFontSize) -
_menuFontSize);
final int count = items.length;
return showMorph(
context,
from: from,
motion: motion,
maxScrimOpacity: maxScrimOpacity,
semanticLabel: semanticLabel,
target: MorphTargetSpec.popover(
anchor: anchor,
size: Size(width, rowHeight * count + 24),
surface: surface,
),
builder: (BuildContext context, MorphFlight flight) => Padding(
padding: const .symmetric(vertical: 10),
child: Column(
mainAxisSize: .min,
children: <Widget>[
// The cascade spreads over the item COUNT: fixed per-row
// steps walked a seventh row past to = 1 and out of the
// reveal contract.
for (int i = 0; i < count; i++)
MorphReveal(
from: count == 1 ? 0.3 : 0.3 + 0.32 * (i / (count - 1)),
to: count == 1 ? 0.7 : 0.7 + 0.3 * (i / (count - 1)),
child: _MorphMenuRow(
item: items[i],
flight: flight,
height: rowHeight,
),
),
],
),
),
);
}