text_selection_theme.dart 5.66 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
// 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/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.
38
  final Color? cursorColor;
39 40

  /// The background color of selected text.
41
  final Color? selectionColor;
42 43 44 45 46

  /// 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.
47 48 49 50
  ///
  /// On iOS [TextField] and [SelectableText] cannot access [selectionHandleColor].
  /// To set the [selectionHandleColor] on iOS, you can change the
  /// [CupertinoThemeData.primaryColor] in [ThemeData.cupertinoOverrideTheme].
51
  final Color? selectionHandleColor;
52 53 54 55

  /// Creates a copy of this object with the given fields replaced with the
  /// specified values.
  TextSelectionThemeData copyWith({
56 57 58
    Color? cursorColor,
    Color? selectionColor,
    Color? selectionHandleColor,
59 60 61 62 63 64 65 66 67 68 69 70 71
  }) {
    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}
72
  static TextSelectionThemeData? lerp(TextSelectionThemeData? a, TextSelectionThemeData? b, double t) {
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
    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
125
/// const TextSelectionTheme(
126 127 128 129 130
///   data: TextSelectionThemeData(
///     cursorColor: Colors.blue,
///     selectionHandleColor: Colors.lightBlue,
///   ),
///   child: TextField(),
131
/// )
132 133 134 135 136 137 138 139
/// ```
/// {@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({
140 141 142
    Key? key,
    required this.data,
    required Widget child,
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
  }) : 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) {
158
    final TextSelectionTheme? selectionTheme = context.dependOnInheritedWidgetOfExactType<TextSelectionTheme>();
159
    return selectionTheme?.data ?? Theme.of(context).textSelectionTheme;
160 161 162 163
  }

  @override
  Widget wrap(BuildContext context, Widget child) {
164
    return TextSelectionTheme(data: data, child: child);
165 166 167 168 169
  }

  @override
  bool updateShouldNotify(TextSelectionTheme oldWidget) => data != oldWidget.data;
}