// 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. import 'dart:math' as math; import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'button_theme.dart'; import 'colors.dart'; import 'constants.dart'; import 'debug.dart'; import 'icons.dart'; import 'ink_well.dart'; import 'input_border.dart'; import 'input_decorator.dart'; import 'material.dart'; import 'material_localizations.dart'; import 'material_state.dart'; import 'scrollbar.dart'; import 'shadows.dart'; import 'theme.dart'; const Duration _kDropdownMenuDuration = Duration(milliseconds: 300); const double _kMenuItemHeight = kMinInteractiveDimension; const double _kDenseButtonHeight = 24.0; const EdgeInsets _kMenuItemPadding = EdgeInsets.symmetric(horizontal: 16.0); const EdgeInsetsGeometry _kAlignedButtonPadding = EdgeInsetsDirectional.only(start: 16.0, end: 4.0); const EdgeInsets _kUnalignedButtonPadding = EdgeInsets.zero; const EdgeInsets _kAlignedMenuMargin = EdgeInsets.zero; const EdgeInsetsGeometry _kUnalignedMenuMargin = EdgeInsetsDirectional.only(start: 16.0, end: 24.0); /// A builder to customize dropdown buttons. /// /// Used by [DropdownButton.selectedItemBuilder]. typedef DropdownButtonBuilder = List Function(BuildContext context); class _DropdownMenuPainter extends CustomPainter { _DropdownMenuPainter({ this.color, this.elevation, this.selectedIndex, this.borderRadius, required this.resize, required this.getSelectedItemOffset, }) : _painter = BoxDecoration( // If you add an image here, you must provide a real // configuration in the paint() function and you must provide some sort // of onChanged callback here. color: color, borderRadius: borderRadius ?? const BorderRadius.all(Radius.circular(2.0)), boxShadow: kElevationToShadow[elevation], ).createBoxPainter(), super(repaint: resize); final Color? color; final int? elevation; final int? selectedIndex; final BorderRadius? borderRadius; final Animation resize; final ValueGetter getSelectedItemOffset; final BoxPainter _painter; @override void paint(Canvas canvas, Size size) { final double selectedItemOffset = getSelectedItemOffset(); final Tween top = Tween( begin: clampDouble(selectedItemOffset, 0.0, math.max(size.height - _kMenuItemHeight, 0.0)), end: 0.0, ); final Tween bottom = Tween( begin: clampDouble(top.begin! + _kMenuItemHeight, math.min(_kMenuItemHeight, size.height), size.height), end: size.height, ); final Rect rect = Rect.fromLTRB(0.0, top.evaluate(resize), size.width, bottom.evaluate(resize)); _painter.paint(canvas, rect.topLeft, ImageConfiguration(size: rect.size)); } @override bool shouldRepaint(_DropdownMenuPainter oldPainter) { return oldPainter.color != color || oldPainter.elevation != elevation || oldPainter.selectedIndex != selectedIndex || oldPainter.borderRadius != borderRadius || oldPainter.resize != resize; } } // The widget that is the button wrapping the menu items. class _DropdownMenuItemButton extends StatefulWidget { const _DropdownMenuItemButton({ super.key, this.padding, required this.route, required this.buttonRect, required this.constraints, required this.itemIndex, required this.enableFeedback, required this.scrollController, }); final _DropdownRoute route; final ScrollController scrollController; final EdgeInsets? padding; final Rect buttonRect; final BoxConstraints constraints; final int itemIndex; final bool enableFeedback; @override _DropdownMenuItemButtonState createState() => _DropdownMenuItemButtonState(); } class _DropdownMenuItemButtonState extends State<_DropdownMenuItemButton> { void _handleFocusChange(bool focused) { final bool inTraditionalMode = switch (FocusManager.instance.highlightMode) { FocusHighlightMode.touch => false, FocusHighlightMode.traditional => true, }; if (focused && inTraditionalMode) { final _MenuLimits menuLimits = widget.route.getMenuLimits( widget.buttonRect, widget.constraints.maxHeight, widget.itemIndex, ); widget.scrollController.animateTo( menuLimits.scrollOffset, curve: Curves.easeInOut, duration: const Duration(milliseconds: 100), ); } } void _handleOnTap() { final DropdownMenuItem dropdownMenuItem = widget.route.items[widget.itemIndex].item!; dropdownMenuItem.onTap?.call(); Navigator.pop( context, _DropdownRouteResult(dropdownMenuItem.value), ); } static const Map _webShortcuts = { // On the web, up/down don't change focus, *except* in a