// 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 'package:flutter/widgets.dart'; import 'color_scheme.dart'; import 'constants.dart'; import 'text_button.dart'; import 'theme.dart'; enum _TextSelectionToolbarItemPosition { /// The first item among multiple in the menu. first, /// One of several items, not the first or last. middle, /// The last item among multiple in the menu. last, /// The only item in the menu. only, } /// A button styled like a Material native Android text selection menu button. class TextSelectionToolbarTextButton extends StatelessWidget { /// Creates an instance of TextSelectionToolbarTextButton. const TextSelectionToolbarTextButton({ super.key, required this.child, required this.padding, this.onPressed, this.alignment, }); // These values were eyeballed to match the native text selection menu on a // Pixel 2 running Android 10. static const double _kMiddlePadding = 9.5; static const double _kEndPadding = 14.5; /// {@template flutter.material.TextSelectionToolbarTextButton.child} /// The child of this button. /// /// Usually a [Text]. /// {@endtemplate} final Widget child; /// {@template flutter.material.TextSelectionToolbarTextButton.onPressed} /// Called when this button is pressed. /// {@endtemplate} final VoidCallback? onPressed; /// The padding between the button's edge and its child. /// /// In a standard Material [TextSelectionToolbar], the padding depends on the /// button's position within the toolbar. /// /// See also: /// /// * [getPadding], which calculates the standard padding based on the /// button's position. /// * [ButtonStyle.padding], which is where this padding is applied. final EdgeInsets padding; /// The alignment of the button's child. /// /// By default, this will be [Alignment.center]. /// /// See also: /// /// * [ButtonStyle.alignment], which is where this alignment is applied. final AlignmentGeometry? alignment; /// Returns the standard padding for a button at index out of a total number /// of buttons. /// /// Standard Material [TextSelectionToolbar]s have buttons with different /// padding depending on their position in the toolbar. static EdgeInsets getPadding(int index, int total) { assert(total > 0 && index >= 0 && index < total); final _TextSelectionToolbarItemPosition position = _getPosition(index, total); return EdgeInsets.only( left: _getLeftPadding(position), right: _getRightPadding(position), ); } static double _getLeftPadding(_TextSelectionToolbarItemPosition position) { if (position == _TextSelectionToolbarItemPosition.first || position == _TextSelectionToolbarItemPosition.only) { return _kEndPadding; } return _kMiddlePadding; } static double _getRightPadding(_TextSelectionToolbarItemPosition position) { if (position == _TextSelectionToolbarItemPosition.last || position == _TextSelectionToolbarItemPosition.only) { return _kEndPadding; } return _kMiddlePadding; } static _TextSelectionToolbarItemPosition _getPosition(int index, int total) { if (index == 0) { return total == 1 ? _TextSelectionToolbarItemPosition.only : _TextSelectionToolbarItemPosition.first; } if (index == total - 1) { return _TextSelectionToolbarItemPosition.last; } return _TextSelectionToolbarItemPosition.middle; } /// Returns a copy of the current [TextSelectionToolbarTextButton] instance /// with specific overrides. TextSelectionToolbarTextButton copyWith({ Widget? child, VoidCallback? onPressed, EdgeInsets? padding, AlignmentGeometry? alignment, }) { return TextSelectionToolbarTextButton( onPressed: onPressed ?? this.onPressed, padding: padding ?? this.padding, alignment: alignment ?? this.alignment, child: child ?? this.child, ); } // These colors were taken from a screenshot of a Pixel 6 emulator running // Android API level 34. static const Color _defaultForegroundColorLight = Color(0xff000000); static const Color _defaultForegroundColorDark = Color(0xffffffff); // The background color is hardcoded to transparent by default so the buttons // are the color of the container behind them. For example TextSelectionToolbar // hardcodes the color value, and TextSelectionToolbarTextButtons that are its // children become that color. static const Color _defaultBackgroundColorTransparent = Color(0x00000000); static Color _getForegroundColor(ColorScheme colorScheme) { final bool isDefaultOnSurface = switch (colorScheme.brightness) { Brightness.light => identical(ThemeData().colorScheme.onSurface, colorScheme.onSurface), Brightness.dark => identical(ThemeData.dark().colorScheme.onSurface, colorScheme.onSurface), }; if (!isDefaultOnSurface) { return colorScheme.onSurface; } return switch (colorScheme.brightness) { Brightness.light => _defaultForegroundColorLight, Brightness.dark => _defaultForegroundColorDark, }; } @override Widget build(BuildContext context) { final ColorScheme colorScheme = Theme.of(context).colorScheme; return TextButton( style: TextButton.styleFrom( backgroundColor: _defaultBackgroundColorTransparent, foregroundColor: _getForegroundColor(colorScheme), shape: const RoundedRectangleBorder(), minimumSize: const Size(kMinInteractiveDimension, kMinInteractiveDimension), padding: padding, alignment: alignment, textStyle: const TextStyle( // This value was eyeballed from a screenshot of a Pixel 6 emulator // running Android API level 34. fontWeight: FontWeight.w400, ), ), onPressed: onPressed, child: child, ); } }