blob: e4ac077c14a5debd32e6df26887ed8c392e09e8f [file] [log] [blame] [edit]
// 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);
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(
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,
);
}
}