TextSelectionTheme support (step 1 of 3) (#62014)
Create a new TextSelectionTheme for text selection properties.
This commit is contained in:
parent
3b887bec47
commit
bebc119ce5
@ -129,6 +129,7 @@ export 'src/material/text_button_theme.dart';
|
||||
export 'src/material/text_field.dart';
|
||||
export 'src/material/text_form_field.dart';
|
||||
export 'src/material/text_selection.dart';
|
||||
export 'src/material/text_selection_theme.dart';
|
||||
export 'src/material/text_theme.dart';
|
||||
export 'src/material/theme.dart';
|
||||
export 'src/material/theme_data.dart';
|
||||
|
@ -13,6 +13,7 @@ import 'package:flutter/gestures.dart';
|
||||
|
||||
import 'feedback.dart';
|
||||
import 'text_selection.dart';
|
||||
import 'text_selection_theme.dart';
|
||||
import 'theme.dart';
|
||||
|
||||
/// An eyeballed value that moves the cursor slightly left of where it is
|
||||
@ -584,7 +585,8 @@ class _SelectableTextState extends State<SelectableText> with AutomaticKeepAlive
|
||||
'inherit false style must supply fontSize and textBaseline',
|
||||
);
|
||||
|
||||
final ThemeData themeData = Theme.of(context);
|
||||
final ThemeData theme = Theme.of(context);
|
||||
final TextSelectionThemeData selectionTheme = TextSelectionTheme.of(context);
|
||||
final FocusNode focusNode = _effectiveFocusNode;
|
||||
|
||||
TextSelectionControls textSelectionControls;
|
||||
@ -592,16 +594,23 @@ class _SelectableTextState extends State<SelectableText> with AutomaticKeepAlive
|
||||
bool cursorOpacityAnimates;
|
||||
Offset cursorOffset;
|
||||
Color cursorColor = widget.cursorColor;
|
||||
Color selectionColor;
|
||||
Radius cursorRadius = widget.cursorRadius;
|
||||
|
||||
switch (themeData.platform) {
|
||||
switch (theme.platform) {
|
||||
case TargetPlatform.iOS:
|
||||
case TargetPlatform.macOS:
|
||||
forcePressEnabled = true;
|
||||
textSelectionControls = cupertinoTextSelectionControls;
|
||||
paintCursorAboveText = true;
|
||||
cursorOpacityAnimates = true;
|
||||
cursorColor ??= CupertinoTheme.of(context).primaryColor;
|
||||
if (theme.useTextSelectionTheme) {
|
||||
cursorColor ??= selectionTheme.cursorColor ?? CupertinoTheme.of(context).primaryColor;
|
||||
selectionColor = selectionTheme.selectionColor ?? CupertinoTheme.of(context).primaryColor;
|
||||
} else {
|
||||
cursorColor ??= CupertinoTheme.of(context).primaryColor;
|
||||
selectionColor = theme.textSelectionColor;
|
||||
}
|
||||
cursorRadius ??= const Radius.circular(2.0);
|
||||
cursorOffset = Offset(iOSHorizontalOffset / MediaQuery.of(context).devicePixelRatio, 0);
|
||||
break;
|
||||
@ -614,7 +623,13 @@ class _SelectableTextState extends State<SelectableText> with AutomaticKeepAlive
|
||||
textSelectionControls = materialTextSelectionControls;
|
||||
paintCursorAboveText = false;
|
||||
cursorOpacityAnimates = false;
|
||||
cursorColor ??= themeData.cursorColor;
|
||||
if (theme.useTextSelectionTheme) {
|
||||
cursorColor ??= selectionTheme.cursorColor ?? theme.colorScheme.primary;
|
||||
selectionColor = selectionTheme.selectionColor ?? theme.colorScheme.primary;
|
||||
} else {
|
||||
cursorColor ??= theme.cursorColor;
|
||||
selectionColor = theme.textSelectionColor;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@ -644,7 +659,7 @@ class _SelectableTextState extends State<SelectableText> with AutomaticKeepAlive
|
||||
toolbarOptions: widget.toolbarOptions,
|
||||
minLines: widget.minLines,
|
||||
maxLines: widget.maxLines ?? defaultTextStyle.maxLines,
|
||||
selectionColor: themeData.textSelectionColor,
|
||||
selectionColor: selectionColor,
|
||||
selectionControls: widget.selectionEnabled ? textSelectionControls : null,
|
||||
onSelectionChanged: _handleSelectionChanged,
|
||||
onSelectionHandleTapped: _handleSelectionHandleTapped,
|
||||
|
@ -22,6 +22,7 @@ import 'material_localizations.dart';
|
||||
import 'material_state.dart';
|
||||
import 'selectable_text.dart' show iOSHorizontalOffset;
|
||||
import 'text_selection.dart';
|
||||
import 'text_selection_theme.dart';
|
||||
import 'theme.dart';
|
||||
|
||||
export 'package:flutter/services.dart' show TextInputType, TextInputAction, TextCapitalization, SmartQuotesType, SmartDashesType;
|
||||
@ -637,10 +638,22 @@ class TextField extends StatefulWidget {
|
||||
/// {@macro flutter.widgets.editableText.cursorRadius}
|
||||
final Radius cursorRadius;
|
||||
|
||||
/// The color to use when painting the cursor.
|
||||
/// The color of the cursor.
|
||||
///
|
||||
/// Defaults to [ThemeData.cursorColor] or [CupertinoThemeData.primaryColor]
|
||||
/// depending on [ThemeData.platform].
|
||||
/// The cursor indicates the current location of text insertion point in
|
||||
/// the field.
|
||||
///
|
||||
/// If this is null it will default to a value based on the following:
|
||||
///
|
||||
/// * If the ambient [ThemeData.useTextSelectionTheme] is true then it
|
||||
/// will use the value of the ambient [TextSelectionThemeData.cursorColor].
|
||||
/// If that is null then if the [ThemeData.platform] is [TargetPlatform.iOS]
|
||||
/// or [TargetPlatform.macOS] then it will use [CupertinoThemeData.primaryColor].
|
||||
/// Otherwise it will use the value of [ColorScheme.primary] of [ThemeData.colorScheme].
|
||||
///
|
||||
/// * If the ambient [ThemeData.useTextSelectionTheme] is false then it
|
||||
/// will use either [ThemeData.cursorColor] or [CupertinoThemeData.primaryColor]
|
||||
/// depending on [ThemeData.platform].
|
||||
final Color cursorColor;
|
||||
|
||||
/// Controls how tall the selection highlight boxes are computed to be.
|
||||
@ -1017,6 +1030,11 @@ class _TextFieldState extends State<TextField> implements TextSelectionGestureDe
|
||||
}
|
||||
}
|
||||
|
||||
Color _defaultSelectionColor(BuildContext context, Color primary) {
|
||||
final bool isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
return primary.withOpacity(isDark ? 0.40 : 0.12);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
assert(debugCheckHasMaterial(context));
|
||||
@ -1028,9 +1046,10 @@ class _TextFieldState extends State<TextField> implements TextSelectionGestureDe
|
||||
'inherit false style must supply fontSize and textBaseline',
|
||||
);
|
||||
|
||||
final ThemeData themeData = Theme.of(context);
|
||||
final TextStyle style = themeData.textTheme.subtitle1.merge(widget.style);
|
||||
final Brightness keyboardAppearance = widget.keyboardAppearance ?? themeData.primaryColorBrightness;
|
||||
final ThemeData theme = Theme.of(context);
|
||||
final TextSelectionThemeData selectionTheme = TextSelectionTheme.of(context);
|
||||
final TextStyle style = theme.textTheme.subtitle1.merge(widget.style);
|
||||
final Brightness keyboardAppearance = widget.keyboardAppearance ?? theme.primaryColorBrightness;
|
||||
final TextEditingController controller = _effectiveController;
|
||||
final FocusNode focusNode = _effectiveFocusNode;
|
||||
final List<TextInputFormatter> formatters = widget.inputFormatters ?? <TextInputFormatter>[];
|
||||
@ -1042,20 +1061,27 @@ class _TextFieldState extends State<TextField> implements TextSelectionGestureDe
|
||||
bool cursorOpacityAnimates;
|
||||
Offset cursorOffset;
|
||||
Color cursorColor = widget.cursorColor;
|
||||
Color selectionColor;
|
||||
Color autocorrectionTextRectColor;
|
||||
Radius cursorRadius = widget.cursorRadius;
|
||||
|
||||
switch (themeData.platform) {
|
||||
switch (theme.platform) {
|
||||
case TargetPlatform.iOS:
|
||||
case TargetPlatform.macOS:
|
||||
forcePressEnabled = true;
|
||||
textSelectionControls = cupertinoTextSelectionControls;
|
||||
paintCursorAboveText = true;
|
||||
cursorOpacityAnimates = true;
|
||||
cursorColor ??= CupertinoTheme.of(context).primaryColor;
|
||||
if (theme.useTextSelectionTheme) {
|
||||
cursorColor ??= selectionTheme.cursorColor ?? CupertinoTheme.of(context).primaryColor;
|
||||
selectionColor = selectionTheme.selectionColor ?? _defaultSelectionColor(context, CupertinoTheme.of(context).primaryColor);
|
||||
} else {
|
||||
cursorColor ??= CupertinoTheme.of(context).primaryColor;
|
||||
selectionColor = theme.textSelectionColor;
|
||||
}
|
||||
cursorRadius ??= const Radius.circular(2.0);
|
||||
cursorOffset = Offset(iOSHorizontalOffset / MediaQuery.of(context).devicePixelRatio, 0);
|
||||
autocorrectionTextRectColor = themeData.textSelectionColor;
|
||||
autocorrectionTextRectColor = selectionColor;
|
||||
break;
|
||||
|
||||
case TargetPlatform.android:
|
||||
@ -1066,7 +1092,13 @@ class _TextFieldState extends State<TextField> implements TextSelectionGestureDe
|
||||
textSelectionControls = materialTextSelectionControls;
|
||||
paintCursorAboveText = false;
|
||||
cursorOpacityAnimates = false;
|
||||
cursorColor ??= themeData.cursorColor;
|
||||
if (theme.useTextSelectionTheme) {
|
||||
cursorColor ??= selectionTheme.cursorColor ?? theme.colorScheme.primary;
|
||||
selectionColor = selectionTheme.selectionColor ?? _defaultSelectionColor(context, theme.colorScheme.primary);
|
||||
} else {
|
||||
cursorColor ??= theme.cursorColor;
|
||||
selectionColor = theme.textSelectionColor;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@ -1096,7 +1128,7 @@ class _TextFieldState extends State<TextField> implements TextSelectionGestureDe
|
||||
maxLines: widget.maxLines,
|
||||
minLines: widget.minLines,
|
||||
expands: widget.expands,
|
||||
selectionColor: themeData.textSelectionColor,
|
||||
selectionColor: selectionColor,
|
||||
selectionControls: widget.selectionEnabled ? textSelectionControls : null,
|
||||
onChanged: widget.onChanged,
|
||||
onSelectionChanged: _handleSelectionChanged,
|
||||
|
@ -20,6 +20,7 @@ import 'icon_button.dart';
|
||||
import 'icons.dart';
|
||||
import 'material.dart';
|
||||
import 'material_localizations.dart';
|
||||
import 'text_selection_theme.dart';
|
||||
import 'theme.dart';
|
||||
|
||||
const double _kHandleSize = 22.0;
|
||||
@ -795,12 +796,16 @@ class _MaterialTextSelectionControls extends TextSelectionControls {
|
||||
/// Builder for material-style text selection handles.
|
||||
@override
|
||||
Widget buildHandle(BuildContext context, TextSelectionHandleType type, double textHeight) {
|
||||
final ThemeData theme = Theme.of(context);
|
||||
final Color handleColor = theme.useTextSelectionTheme ?
|
||||
TextSelectionTheme.of(context).selectionHandleColor ?? theme.colorScheme.primary :
|
||||
theme.textSelectionHandleColor;
|
||||
final Widget handle = SizedBox(
|
||||
width: _kHandleSize,
|
||||
height: _kHandleSize,
|
||||
child: CustomPaint(
|
||||
painter: _TextSelectionHandlePainter(
|
||||
color: Theme.of(context).textSelectionHandleColor,
|
||||
color: handleColor,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
168
packages/flutter/lib/src/material/text_selection_theme.dart
Normal file
168
packages/flutter/lib/src/material/text_selection_theme.dart
Normal file
@ -0,0 +1,168 @@
|
||||
// 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.
|
||||
|
||||
// @dart = 2.8
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'theme.dart';
|
||||
|
||||
/// Defines the visual properties needed for text selection in [TextField] and
|
||||
/// [SelectableText] widgets.
|
||||
///
|
||||
/// Used by [TextSelectionTheme] to control the visual properties of text
|
||||
/// selection in a widget subtree.
|
||||
///
|
||||
/// Use [TextSelectionTheme.of] to access the closest ancestor
|
||||
/// [TextSelectionTheme] of the current [BuildContext].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [TextSelectionTheme], an [InheritedWidget] that propagates the theme down its
|
||||
/// subtree.
|
||||
/// * [InputDecorationTheme], which defines most other visual properties of
|
||||
/// text fields.
|
||||
@immutable
|
||||
class TextSelectionThemeData with Diagnosticable {
|
||||
/// Creates the set of properties used to configure [TextField]s.
|
||||
const TextSelectionThemeData({
|
||||
this.cursorColor,
|
||||
this.selectionColor,
|
||||
this.selectionHandleColor,
|
||||
});
|
||||
|
||||
/// The color of the cursor in the text field.
|
||||
///
|
||||
/// The cursor indicates the current location of text insertion point in
|
||||
/// the field.
|
||||
final Color cursorColor;
|
||||
|
||||
/// The background color of selected text.
|
||||
final Color selectionColor;
|
||||
|
||||
/// The color of the selection handles on the text field.
|
||||
///
|
||||
/// Selection handles are used to indicate the bounds of the selected text,
|
||||
/// or as a handle to drag the cursor to a new location in the text.
|
||||
final Color selectionHandleColor;
|
||||
|
||||
/// Creates a copy of this object with the given fields replaced with the
|
||||
/// specified values.
|
||||
TextSelectionThemeData copyWith({
|
||||
Color cursorColor,
|
||||
Color selectionColor,
|
||||
Color selectionHandleColor,
|
||||
}) {
|
||||
return TextSelectionThemeData(
|
||||
cursorColor: cursorColor ?? this.cursorColor,
|
||||
selectionColor: selectionColor ?? this.selectionColor,
|
||||
selectionHandleColor: selectionHandleColor ?? this.selectionHandleColor,
|
||||
);
|
||||
}
|
||||
|
||||
/// Linearly interpolate between two text field themes.
|
||||
///
|
||||
/// If both arguments are null, then null is returned.
|
||||
///
|
||||
/// {@macro dart.ui.shadow.lerp}
|
||||
static TextSelectionThemeData lerp(TextSelectionThemeData a, TextSelectionThemeData b, double t) {
|
||||
if (a == null && b == null)
|
||||
return null;
|
||||
assert(t != null);
|
||||
return TextSelectionThemeData(
|
||||
cursorColor: Color.lerp(a?.cursorColor, b?.cursorColor, t),
|
||||
selectionColor: Color.lerp(a?.selectionColor, b?.selectionColor, t),
|
||||
selectionHandleColor: Color.lerp(a?.selectionHandleColor, b?.selectionHandleColor, t),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return hashValues(
|
||||
cursorColor,
|
||||
selectionColor,
|
||||
selectionHandleColor,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator==(Object other) {
|
||||
if (identical(this, other))
|
||||
return true;
|
||||
if (other.runtimeType != runtimeType)
|
||||
return false;
|
||||
return other is TextSelectionThemeData
|
||||
&& other.cursorColor == cursorColor
|
||||
&& other.selectionColor == selectionColor
|
||||
&& other.selectionHandleColor == selectionHandleColor;
|
||||
}
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties.add(ColorProperty('cursorColor', cursorColor, defaultValue: null));
|
||||
properties.add(ColorProperty('selectionColor', selectionColor, defaultValue: null));
|
||||
properties.add(ColorProperty('selectionHandleColor', selectionHandleColor, defaultValue: null));
|
||||
}
|
||||
}
|
||||
|
||||
/// An inherited widget that defines the appearance of text selection in
|
||||
/// this widget's subtree.
|
||||
///
|
||||
/// Values specified here are used for [TextField] and [SelectableText]
|
||||
/// properties that are not given an explicit non-null value.
|
||||
///
|
||||
/// {@tool snippet}
|
||||
///
|
||||
/// Here is an example of a text selection theme that applies a blue cursor
|
||||
/// color with light blue selection handles to the child text field.
|
||||
///
|
||||
/// ```dart
|
||||
/// TextSelectionTheme(
|
||||
/// data: TextSelectionThemeData(
|
||||
/// cursorColor: Colors.blue,
|
||||
/// selectionHandleColor: Colors.lightBlue,
|
||||
/// ),
|
||||
/// child: TextField(),
|
||||
/// ),
|
||||
/// ```
|
||||
/// {@end-tool}
|
||||
class TextSelectionTheme extends InheritedTheme {
|
||||
/// Creates a text selection theme widget that specifies the text
|
||||
/// selection properties for all widgets below it in the widget tree.
|
||||
///
|
||||
/// The data argument must not be null.
|
||||
const TextSelectionTheme({
|
||||
Key key,
|
||||
@required this.data,
|
||||
Widget child,
|
||||
}) : assert(data != null), super(key: key, child: child);
|
||||
|
||||
/// The properties for descendant [TextField] and [SelectableText] widgets.
|
||||
final TextSelectionThemeData data;
|
||||
|
||||
/// Returns the [data] from the closest [TextSelectionTheme] ancestor. If
|
||||
/// there is no ancestor, it returns [ThemeData.textSelectionTheme].
|
||||
/// Applications can assume that the returned value will not be null.
|
||||
///
|
||||
/// Typical usage is as follows:
|
||||
///
|
||||
/// ```dart
|
||||
/// TextSelectionThemeData theme = TextSelectionTheme.of(context);
|
||||
/// ```
|
||||
static TextSelectionThemeData of(BuildContext context) {
|
||||
final TextSelectionTheme selectionTheme = context.dependOnInheritedWidgetOfExactType<TextSelectionTheme>();
|
||||
return selectionTheme?.data ?? Theme.of(context).textSelectionTheme;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget wrap(BuildContext context, Widget child) {
|
||||
final TextSelectionTheme ancestorTheme = context.findAncestorWidgetOfExactType<TextSelectionTheme>();
|
||||
return identical(this, ancestorTheme) ? child : TextSelectionTheme(data: data, child: child);
|
||||
}
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(TextSelectionTheme oldWidget) => data != oldWidget.data;
|
||||
}
|
@ -37,6 +37,7 @@ import 'slider_theme.dart';
|
||||
import 'snack_bar_theme.dart';
|
||||
import 'tab_bar_theme.dart';
|
||||
import 'text_button_theme.dart';
|
||||
import 'text_selection_theme.dart';
|
||||
import 'text_theme.dart';
|
||||
import 'time_picker_theme.dart';
|
||||
import 'toggle_buttons_theme.dart';
|
||||
@ -281,7 +282,9 @@ class ThemeData with Diagnosticable {
|
||||
TextButtonThemeData textButtonTheme,
|
||||
ElevatedButtonThemeData elevatedButtonTheme,
|
||||
OutlinedButtonThemeData outlinedButtonTheme,
|
||||
TextSelectionThemeData textSelectionTheme,
|
||||
bool fixTextFieldOutlineLabel,
|
||||
bool useTextSelectionTheme,
|
||||
}) {
|
||||
assert(colorScheme?.brightness == null || brightness == null || colorScheme.brightness == brightness);
|
||||
final Brightness _brightness = brightness ?? colorScheme?.brightness ?? Brightness.light;
|
||||
@ -322,7 +325,6 @@ class ThemeData with Diagnosticable {
|
||||
// Spec doesn't specify a dark theme secondaryHeaderColor, this is a guess.
|
||||
secondaryHeaderColor ??= isDark ? Colors.grey[700] : primarySwatch[50];
|
||||
textSelectionColor ??= isDark ? accentColor : primarySwatch[200];
|
||||
// TODO(hansmuller): We need a TextFieldTheme to handle this instead, https://github.com/flutter/flutter/issues/56082
|
||||
cursorColor = cursorColor ?? const Color.fromRGBO(66, 133, 244, 1.0);
|
||||
textSelectionHandleColor ??= isDark ? Colors.tealAccent[400] : primarySwatch[300];
|
||||
backgroundColor ??= isDark ? Colors.grey[700] : primarySwatch[200];
|
||||
@ -397,7 +399,10 @@ class ThemeData with Diagnosticable {
|
||||
textButtonTheme ??= const TextButtonThemeData();
|
||||
elevatedButtonTheme ??= const ElevatedButtonThemeData();
|
||||
outlinedButtonTheme ??= const OutlinedButtonThemeData();
|
||||
textSelectionTheme ??= const TextSelectionThemeData();
|
||||
|
||||
fixTextFieldOutlineLabel ??= false;
|
||||
useTextSelectionTheme ??= false;
|
||||
|
||||
return ThemeData.raw(
|
||||
visualDensity: visualDensity,
|
||||
@ -469,7 +474,9 @@ class ThemeData with Diagnosticable {
|
||||
textButtonTheme: textButtonTheme,
|
||||
elevatedButtonTheme: elevatedButtonTheme,
|
||||
outlinedButtonTheme: outlinedButtonTheme,
|
||||
textSelectionTheme: textSelectionTheme,
|
||||
fixTextFieldOutlineLabel: fixTextFieldOutlineLabel,
|
||||
useTextSelectionTheme: useTextSelectionTheme,
|
||||
);
|
||||
}
|
||||
|
||||
@ -553,7 +560,9 @@ class ThemeData with Diagnosticable {
|
||||
@required this.textButtonTheme,
|
||||
@required this.elevatedButtonTheme,
|
||||
@required this.outlinedButtonTheme,
|
||||
@required this.textSelectionTheme,
|
||||
@required this.fixTextFieldOutlineLabel,
|
||||
@required this.useTextSelectionTheme,
|
||||
}) : assert(visualDensity != null),
|
||||
assert(primaryColor != null),
|
||||
assert(primaryColorBrightness != null),
|
||||
@ -620,7 +629,10 @@ class ThemeData with Diagnosticable {
|
||||
assert(textButtonTheme != null),
|
||||
assert(elevatedButtonTheme != null),
|
||||
assert(outlinedButtonTheme != null),
|
||||
assert(fixTextFieldOutlineLabel != null);
|
||||
assert(fixTextFieldOutlineLabel != null),
|
||||
assert(textSelectionTheme != null),
|
||||
assert(fixTextFieldOutlineLabel != null),
|
||||
assert(useTextSelectionTheme != null);
|
||||
|
||||
/// Create a [ThemeData] based on the colors in the given [colorScheme] and
|
||||
/// text styles of the optional [textTheme].
|
||||
@ -1092,6 +1104,9 @@ class ThemeData with Diagnosticable {
|
||||
/// [OutlinedButton]s.
|
||||
final OutlinedButtonThemeData outlinedButtonTheme;
|
||||
|
||||
/// A theme for customizing the appearance and layout of [TextField] widgets.
|
||||
final TextSelectionThemeData textSelectionTheme;
|
||||
|
||||
/// A temporary flag to allow apps to opt-in to a
|
||||
/// [small fix](https://github.com/flutter/flutter/issues/54028) for the Y
|
||||
/// coordinate of the floating label in a [TextField] [OutlineInputBorder].
|
||||
@ -1104,6 +1119,18 @@ class ThemeData with Diagnosticable {
|
||||
/// stable release (1.19).
|
||||
final bool fixTextFieldOutlineLabel;
|
||||
|
||||
/// A temporary flag to allow apps to opt-in to the new [TextSelectionTheme], with
|
||||
/// its new defaults for the [cursorColor] and [textSelectionHandleColor].
|
||||
///
|
||||
/// Setting this flag to true will cause the [textSelectionTheme] to be used
|
||||
/// instead of the [cursorColor] and [textSelectionHandleColor] by [TextField]
|
||||
/// and [SelectableText] widgets. In addition, the default values of these
|
||||
/// colors have changed to [ColorScheme.primary].
|
||||
///
|
||||
/// The flag is currently false by default. It will be removed after migration
|
||||
/// to the [TextSelectionTheme] has been completed.
|
||||
final bool useTextSelectionTheme;
|
||||
|
||||
/// Creates a copy of this theme but with the given fields replaced with the new values.
|
||||
///
|
||||
/// The [brightness] value is applied to the [colorScheme].
|
||||
@ -1178,7 +1205,9 @@ class ThemeData with Diagnosticable {
|
||||
TextButtonThemeData textButtonTheme,
|
||||
ElevatedButtonThemeData elevatedButtonTheme,
|
||||
OutlinedButtonThemeData outlinedButtonTheme,
|
||||
TextSelectionThemeData textSelectionTheme,
|
||||
bool fixTextFieldOutlineLabel,
|
||||
bool useTextSelectionTheme,
|
||||
}) {
|
||||
cupertinoOverrideTheme = cupertinoOverrideTheme?.noDefault();
|
||||
return ThemeData.raw(
|
||||
@ -1251,7 +1280,9 @@ class ThemeData with Diagnosticable {
|
||||
textButtonTheme: textButtonTheme ?? this.textButtonTheme,
|
||||
elevatedButtonTheme: elevatedButtonTheme ?? this.elevatedButtonTheme,
|
||||
outlinedButtonTheme: outlinedButtonTheme ?? this.outlinedButtonTheme,
|
||||
textSelectionTheme: textSelectionTheme ?? this.textSelectionTheme,
|
||||
fixTextFieldOutlineLabel: fixTextFieldOutlineLabel ?? this.fixTextFieldOutlineLabel,
|
||||
useTextSelectionTheme: useTextSelectionTheme ?? this.useTextSelectionTheme,
|
||||
);
|
||||
}
|
||||
|
||||
@ -1402,7 +1433,9 @@ class ThemeData with Diagnosticable {
|
||||
textButtonTheme: TextButtonThemeData.lerp(a.textButtonTheme, b.textButtonTheme, t),
|
||||
elevatedButtonTheme: ElevatedButtonThemeData.lerp(a.elevatedButtonTheme, b.elevatedButtonTheme, t),
|
||||
outlinedButtonTheme: OutlinedButtonThemeData.lerp(a.outlinedButtonTheme, b.outlinedButtonTheme, t),
|
||||
textSelectionTheme: TextSelectionThemeData .lerp(a.textSelectionTheme, b.textSelectionTheme, t),
|
||||
fixTextFieldOutlineLabel: t < 0.5 ? a.fixTextFieldOutlineLabel : b.fixTextFieldOutlineLabel,
|
||||
useTextSelectionTheme: t < 0.5 ? a.useTextSelectionTheme : b.useTextSelectionTheme,
|
||||
);
|
||||
}
|
||||
|
||||
@ -1481,7 +1514,9 @@ class ThemeData with Diagnosticable {
|
||||
&& other.textButtonTheme == textButtonTheme
|
||||
&& other.elevatedButtonTheme == elevatedButtonTheme
|
||||
&& other.outlinedButtonTheme == outlinedButtonTheme
|
||||
&& other.fixTextFieldOutlineLabel == fixTextFieldOutlineLabel;
|
||||
&& other.textSelectionTheme == textSelectionTheme
|
||||
&& other.fixTextFieldOutlineLabel == fixTextFieldOutlineLabel
|
||||
&& other.useTextSelectionTheme == useTextSelectionTheme;
|
||||
}
|
||||
|
||||
@override
|
||||
@ -1559,7 +1594,9 @@ class ThemeData with Diagnosticable {
|
||||
textButtonTheme,
|
||||
elevatedButtonTheme,
|
||||
outlinedButtonTheme,
|
||||
textSelectionTheme,
|
||||
fixTextFieldOutlineLabel,
|
||||
useTextSelectionTheme,
|
||||
];
|
||||
return hashList(values);
|
||||
}
|
||||
@ -1630,6 +1667,8 @@ class ThemeData with Diagnosticable {
|
||||
properties.add(DiagnosticsProperty<DividerThemeData>('dividerTheme', dividerTheme, defaultValue: defaultData.dividerTheme, level: DiagnosticLevel.debug));
|
||||
properties.add(DiagnosticsProperty<ButtonBarThemeData>('buttonBarTheme', buttonBarTheme, defaultValue: defaultData.buttonBarTheme, level: DiagnosticLevel.debug));
|
||||
properties.add(DiagnosticsProperty<TimePickerThemeData>('timePickerTheme', timePickerTheme, defaultValue: defaultData.timePickerTheme, level: DiagnosticLevel.debug));
|
||||
properties.add(DiagnosticsProperty<TextSelectionThemeData>('textSelectionTheme', textSelectionTheme, defaultValue: defaultData.textSelectionTheme, level: DiagnosticLevel.debug));
|
||||
properties.add(DiagnosticsProperty<TextSelectionThemeData>('textSelectionTheme', textSelectionTheme, defaultValue: defaultData.textSelectionTheme, level: DiagnosticLevel.debug));
|
||||
properties.add(DiagnosticsProperty<BottomNavigationBarThemeData>('bottomNavigationBarTheme', bottomNavigationBarTheme, defaultValue: defaultData.bottomNavigationBarTheme, level: DiagnosticLevel.debug));
|
||||
properties.add(DiagnosticsProperty<TextButtonThemeData>('textButtonTheme', textButtonTheme, defaultValue: defaultData.textButtonTheme, level: DiagnosticLevel.debug));
|
||||
properties.add(DiagnosticsProperty<ElevatedButtonThemeData>('elevatedButtonTheme', elevatedButtonTheme, defaultValue: defaultData.elevatedButtonTheme, level: DiagnosticLevel.debug));
|
||||
|
286
packages/flutter/test/material/text_selection_theme_test.dart
Normal file
286
packages/flutter/test/material/text_selection_theme_test.dart
Normal file
@ -0,0 +1,286 @@
|
||||
// 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.
|
||||
|
||||
// @dart = 2.8
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/src/material/selectable_text.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../rendering/mock_canvas.dart';
|
||||
|
||||
void main() {
|
||||
test('TextSelectionThemeData copyWith, ==, hashCode basics', () {
|
||||
expect(const TextSelectionThemeData(), const TextSelectionThemeData().copyWith());
|
||||
expect(const TextSelectionThemeData().hashCode, const TextSelectionThemeData().copyWith().hashCode);
|
||||
});
|
||||
|
||||
test('TextSelectionThemeData null fields by default', () {
|
||||
const TextSelectionThemeData theme = TextSelectionThemeData();
|
||||
expect(theme.cursorColor, null);
|
||||
expect(theme.selectionColor, null);
|
||||
expect(theme.selectionHandleColor, null);
|
||||
});
|
||||
|
||||
testWidgets('Default TextSelectionThemeData debugFillProperties', (WidgetTester tester) async {
|
||||
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
|
||||
const TextSelectionThemeData().debugFillProperties(builder);
|
||||
|
||||
final List<String> description = builder.properties
|
||||
.where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
|
||||
.map((DiagnosticsNode node) => node.toString())
|
||||
.toList();
|
||||
|
||||
expect(description, <String>[]);
|
||||
});
|
||||
|
||||
testWidgets('TextSelectionThemeData implements debugFillProperties', (WidgetTester tester) async {
|
||||
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
|
||||
const TextSelectionThemeData(
|
||||
cursorColor: Color(0xffeeffaa),
|
||||
selectionColor: Color(0x88888888),
|
||||
selectionHandleColor: Color(0xaabbccdd),
|
||||
).debugFillProperties(builder);
|
||||
|
||||
final List<String> description = builder.properties
|
||||
.where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
|
||||
.map((DiagnosticsNode node) => node.toString())
|
||||
.toList();
|
||||
|
||||
expect(description, <String>[
|
||||
'cursorColor: Color(0xffeeffaa)',
|
||||
'selectionColor: Color(0x88888888)',
|
||||
'selectionHandleColor: Color(0xaabbccdd)',
|
||||
]);
|
||||
});
|
||||
|
||||
testWidgets('Empty textSelectionTheme will use defaults', (WidgetTester tester) async {
|
||||
// Test TextField's cursor & selection color.
|
||||
await tester.pumpWidget(
|
||||
const MaterialApp(
|
||||
home: Material(
|
||||
child: TextField(),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
final EditableTextState editableTextState = tester.firstState(find.byType(EditableText));
|
||||
final RenderEditable renderEditable = editableTextState.renderEditable;
|
||||
expect(renderEditable.cursorColor, const Color(0x004285f4));
|
||||
expect(renderEditable.selectionColor, const Color(0xFF90CAF9));
|
||||
|
||||
// Test the selection handle color.
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Material(
|
||||
child: Builder(
|
||||
builder: (BuildContext context) {
|
||||
return materialTextSelectionControls.buildHandle(
|
||||
context, TextSelectionHandleType.left, 10.0
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
final RenderBox handle = tester.firstRenderObject<RenderBox>(find.byType(CustomPaint));
|
||||
expect(handle, paints..path(color: Colors.blue[300]));
|
||||
|
||||
});
|
||||
|
||||
testWidgets('Empty textSelectionTheme with useTextSelectionTheme set will use new defaults', (WidgetTester tester) async {
|
||||
final ThemeData theme = ThemeData.fallback().copyWith(useTextSelectionTheme: true);
|
||||
final Color primaryColor = Color(theme.colorScheme.primary.value);
|
||||
|
||||
// Test TextField's cursor & selection color.
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: theme,
|
||||
home: const Material(
|
||||
child: TextField(),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
final EditableTextState editableTextState = tester.firstState(find.byType(EditableText));
|
||||
final RenderEditable renderEditable = editableTextState.renderEditable;
|
||||
expect(renderEditable.cursorColor, primaryColor.withAlpha(0));
|
||||
expect(Color(renderEditable.selectionColor.value), primaryColor.withOpacity(0.12));
|
||||
|
||||
// Test the selection handle color.
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: theme,
|
||||
home: Material(
|
||||
child: Builder(
|
||||
builder: (BuildContext context) {
|
||||
return materialTextSelectionControls.buildHandle(
|
||||
context, TextSelectionHandleType.left, 10.0
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
final RenderBox handle = tester.firstRenderObject<RenderBox>(find.byType(CustomPaint));
|
||||
expect(handle, paints..path(color: primaryColor));
|
||||
});
|
||||
|
||||
testWidgets('ThemeDate.textSelectionTheme will be used if provided', (WidgetTester tester) async {
|
||||
const TextSelectionThemeData textSelectionTheme = TextSelectionThemeData(
|
||||
cursorColor: Color(0xffaabbcc),
|
||||
selectionColor: Color(0x88888888),
|
||||
selectionHandleColor: Color(0x00ccbbaa),
|
||||
);
|
||||
final ThemeData theme = ThemeData.fallback().copyWith(
|
||||
useTextSelectionTheme: true,
|
||||
textSelectionTheme: textSelectionTheme,
|
||||
);
|
||||
|
||||
// Test TextField's cursor & selection color.
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: theme,
|
||||
home: const Material(
|
||||
child: TextField(),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
final EditableTextState editableTextState = tester.firstState(find.byType(EditableText));
|
||||
final RenderEditable renderEditable = editableTextState.renderEditable;
|
||||
expect(renderEditable.cursorColor, textSelectionTheme.cursorColor.withAlpha(0));
|
||||
expect(renderEditable.selectionColor, textSelectionTheme.selectionColor);
|
||||
|
||||
// Test the selection handle color.
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: theme,
|
||||
home: Material(
|
||||
child: Builder(
|
||||
builder: (BuildContext context) {
|
||||
return materialTextSelectionControls.buildHandle(
|
||||
context, TextSelectionHandleType.left, 10.0
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
final RenderBox handle = tester.firstRenderObject<RenderBox>(find.byType(CustomPaint));
|
||||
expect(handle, paints..path(color: textSelectionTheme.selectionHandleColor));
|
||||
});
|
||||
|
||||
testWidgets('TextSelectionTheme widget will override ThemeDate.textSelectionTheme', (WidgetTester tester) async {
|
||||
const TextSelectionThemeData defaultTextSelectionTheme = TextSelectionThemeData(
|
||||
cursorColor: Color(0xffaabbcc),
|
||||
selectionColor: Color(0x88888888),
|
||||
selectionHandleColor: Color(0x00ccbbaa),
|
||||
);
|
||||
final ThemeData theme = ThemeData.fallback().copyWith(
|
||||
useTextSelectionTheme: true,
|
||||
textSelectionTheme: defaultTextSelectionTheme,
|
||||
);
|
||||
const TextSelectionThemeData widgetTextSelectionTheme = TextSelectionThemeData(
|
||||
cursorColor: Color(0xffddeeff),
|
||||
selectionColor: Color(0x44444444),
|
||||
selectionHandleColor: Color(0x00ffeedd),
|
||||
);
|
||||
|
||||
// Test TextField's cursor & selection color.
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: theme,
|
||||
home: const Material(
|
||||
child: TextSelectionTheme(
|
||||
data: widgetTextSelectionTheme,
|
||||
child: TextField(),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
final EditableTextState editableTextState = tester.firstState(find.byType(EditableText));
|
||||
final RenderEditable renderEditable = editableTextState.renderEditable;
|
||||
expect(renderEditable.cursorColor, widgetTextSelectionTheme.cursorColor.withAlpha(0));
|
||||
expect(renderEditable.selectionColor, widgetTextSelectionTheme.selectionColor);
|
||||
|
||||
// Test the selection handle color.
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: theme,
|
||||
home: Material(
|
||||
child: TextSelectionTheme(
|
||||
data: widgetTextSelectionTheme,
|
||||
child: Builder(
|
||||
builder: (BuildContext context) {
|
||||
return materialTextSelectionControls.buildHandle(
|
||||
context, TextSelectionHandleType.left, 10.0
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
final RenderBox handle = tester.firstRenderObject<RenderBox>(find.byType(CustomPaint));
|
||||
expect(handle, paints..path(color: widgetTextSelectionTheme.selectionHandleColor));
|
||||
});
|
||||
|
||||
testWidgets('TextField parameters will override theme settings', (WidgetTester tester) async {
|
||||
const TextSelectionThemeData defaultTextSelectionTheme = TextSelectionThemeData(
|
||||
cursorColor: Color(0xffaabbcc),
|
||||
selectionHandleColor: Color(0x00ccbbaa),
|
||||
);
|
||||
final ThemeData theme = ThemeData.fallback().copyWith(
|
||||
useTextSelectionTheme: true,
|
||||
textSelectionTheme: defaultTextSelectionTheme,
|
||||
);
|
||||
const TextSelectionThemeData widgetTextSelectionTheme = TextSelectionThemeData(
|
||||
cursorColor: Color(0xffddeeff),
|
||||
selectionHandleColor: Color(0x00ffeedd),
|
||||
);
|
||||
const Color cursorColor = Color(0x88888888);
|
||||
|
||||
// Test TextField's cursor color.
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: theme,
|
||||
home: const Material(
|
||||
child: TextSelectionTheme(
|
||||
data: widgetTextSelectionTheme,
|
||||
child: TextField(cursorColor: cursorColor),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
final EditableTextState editableTextState = tester.firstState(find.byType(EditableText));
|
||||
final RenderEditable renderEditable = editableTextState.renderEditable;
|
||||
expect(renderEditable.cursorColor, cursorColor.withAlpha(0));
|
||||
|
||||
// Test SelectableText's cursor color.
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: theme,
|
||||
home: const Material(
|
||||
child: TextSelectionTheme(
|
||||
data: widgetTextSelectionTheme,
|
||||
child: SelectableText('foobar', cursorColor: cursorColor),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
final EditableTextState selectableTextState = tester.firstState(find.byType(EditableText));
|
||||
final RenderEditable renderSelectable = selectableTextState.renderEditable;
|
||||
expect(renderSelectable.cursorColor, cursorColor.withAlpha(0));
|
||||
});
|
||||
}
|
@ -287,7 +287,9 @@ void main() {
|
||||
textButtonTheme: TextButtonThemeData(style: TextButton.styleFrom(primary: Colors.red)),
|
||||
elevatedButtonTheme: ElevatedButtonThemeData(style: ElevatedButton.styleFrom(primary: Colors.green)),
|
||||
outlinedButtonTheme: OutlinedButtonThemeData(style: OutlinedButton.styleFrom(primary: Colors.blue)),
|
||||
textSelectionTheme: const TextSelectionThemeData(cursorColor: Colors.black),
|
||||
fixTextFieldOutlineLabel: false,
|
||||
useTextSelectionTheme: false,
|
||||
);
|
||||
|
||||
final SliderThemeData otherSliderTheme = SliderThemeData.fromPrimaryColors(
|
||||
@ -373,7 +375,9 @@ void main() {
|
||||
textButtonTheme: const TextButtonThemeData(),
|
||||
elevatedButtonTheme: const ElevatedButtonThemeData(),
|
||||
outlinedButtonTheme: const OutlinedButtonThemeData(),
|
||||
textSelectionTheme: const TextSelectionThemeData(cursorColor: Colors.white),
|
||||
fixTextFieldOutlineLabel: true,
|
||||
useTextSelectionTheme: true,
|
||||
);
|
||||
|
||||
final ThemeData themeDataCopy = theme.copyWith(
|
||||
@ -445,7 +449,9 @@ void main() {
|
||||
textButtonTheme: otherTheme.textButtonTheme,
|
||||
elevatedButtonTheme: otherTheme.elevatedButtonTheme,
|
||||
outlinedButtonTheme: otherTheme.outlinedButtonTheme,
|
||||
textSelectionTheme: otherTheme.textSelectionTheme,
|
||||
fixTextFieldOutlineLabel: otherTheme.fixTextFieldOutlineLabel,
|
||||
useTextSelectionTheme: otherTheme.useTextSelectionTheme,
|
||||
);
|
||||
|
||||
expect(themeDataCopy.brightness, equals(otherTheme.brightness));
|
||||
@ -516,7 +522,9 @@ void main() {
|
||||
expect(themeDataCopy.textButtonTheme, equals(otherTheme.textButtonTheme));
|
||||
expect(themeDataCopy.elevatedButtonTheme, equals(otherTheme.elevatedButtonTheme));
|
||||
expect(themeDataCopy.outlinedButtonTheme, equals(otherTheme.outlinedButtonTheme));
|
||||
expect(themeDataCopy.textSelectionTheme, equals(otherTheme.textSelectionTheme));
|
||||
expect(themeDataCopy.fixTextFieldOutlineLabel, equals(otherTheme.fixTextFieldOutlineLabel));
|
||||
expect(themeDataCopy.useTextSelectionTheme, equals(otherTheme.useTextSelectionTheme));
|
||||
});
|
||||
|
||||
testWidgets('ThemeData.toString has less than 200 characters output', (WidgetTester tester) async {
|
||||
|
Loading…
x
Reference in New Issue
Block a user