tooltip_theme.dart 8.6 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:ui' show lerpDouble;

import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';

import 'theme.dart';

/// Defines the visual properties of [Tooltip] widgets.
///
/// Used by [TooltipTheme] to control the visual properties of tooltips in a
/// widget subtree.
///
/// To obtain this configuration, use [TooltipTheme.of] to access the closest
/// ancestor [TooltipTheme] of the current [BuildContext].
///
/// See also:
///
///  * [TooltipTheme], an [InheritedWidget] that propagates the theme down its
///    subtree.
///  * [TooltipThemeData], which describes the actual configuration of a
///    tooltip theme.
26
@immutable
27
class TooltipThemeData with Diagnosticable {
28 29 30 31
  /// Creates the set of properties used to configure [Tooltip]s.
  const TooltipThemeData({
    this.height,
    this.padding,
32
    this.margin,
33 34 35 36 37 38 39 40 41 42
    this.verticalOffset,
    this.preferBelow,
    this.excludeFromSemantics,
    this.decoration,
    this.textStyle,
    this.waitDuration,
    this.showDuration,
  });

  /// The height of [Tooltip.child].
43
  final double? height;
44 45

  /// If provided, the amount of space by which to inset [Tooltip.child].
46
  final EdgeInsetsGeometry? padding;
47

48
  /// If provided, the amount of empty space to surround the [Tooltip].
49
  final EdgeInsetsGeometry? margin;
50

51 52 53 54 55 56 57
  /// The vertical gap between the widget and the displayed tooltip.
  ///
  /// When [preferBelow] is set to true and tooltips have sufficient space to
  /// display themselves, this property defines how much vertical space
  /// tooltips will position themselves under their corresponding widgets.
  /// Otherwise, tooltips will position themselves above their corresponding
  /// widgets with the given offset.
58
  final double? verticalOffset;
59 60 61 62 63

  /// Whether the tooltip is displayed below its widget by default.
  ///
  /// If there is insufficient space to display the tooltip in the preferred
  /// direction, the tooltip will be displayed in the opposite direction.
64
  final bool? preferBelow;
65

66
  /// Whether the [Tooltip.message] should be excluded from the semantics
67 68
  /// tree.
  ///
69
  /// By default, [Tooltip]s will add a [Semantics] label that is set to
70 71
  /// [Tooltip.message]. Set this property to true if the app is going to
  /// provide its own custom semantics label.
72
  final bool? excludeFromSemantics;
73 74

  /// The [Tooltip]'s shape and background color.
75
  final Decoration? decoration;
76 77

  /// The style to use for the message of [Tooltip]s.
78
  final TextStyle? textStyle;
79 80 81

  /// The length of time that a pointer must hover over a tooltip's widget
  /// before the tooltip will be shown.
82
  final Duration? waitDuration;
83 84

  /// The length of time that the tooltip will be shown once it has appeared.
85
  final Duration? showDuration;
86 87 88 89

  /// Creates a copy of this object but with the given fields replaced with the
  /// new values.
  TooltipThemeData copyWith({
90 91 92 93 94 95 96 97 98 99
    double? height,
    EdgeInsetsGeometry? padding,
    EdgeInsetsGeometry? margin,
    double? verticalOffset,
    bool? preferBelow,
    bool? excludeFromSemantics,
    Decoration? decoration,
    TextStyle? textStyle,
    Duration? waitDuration,
    Duration? showDuration,
100 101 102 103
  }) {
    return TooltipThemeData(
      height: height ?? this.height,
      padding: padding ?? this.padding,
104
      margin: margin ?? this.margin,
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
      verticalOffset: verticalOffset ?? this.verticalOffset,
      preferBelow: preferBelow ?? this.preferBelow,
      excludeFromSemantics: excludeFromSemantics ?? this.excludeFromSemantics,
      decoration: decoration ?? this.decoration,
      textStyle: textStyle ?? this.textStyle,
      waitDuration: waitDuration ?? this.waitDuration,
      showDuration: showDuration ?? this.showDuration,
    );
  }

  /// Linearly interpolate between two tooltip themes.
  ///
  /// If both arguments are null, then null is returned.
  ///
  /// {@macro dart.ui.shadow.lerp}
120
  static TooltipThemeData? lerp(TooltipThemeData? a, TooltipThemeData? b, double t) {
121 122 123 124 125
    if (a == null && b == null)
      return null;
    assert(t != null);
    return TooltipThemeData(
      height: lerpDouble(a?.height, b?.height, t),
126 127
      padding: EdgeInsetsGeometry.lerp(a?.padding, b?.padding, t),
      margin: EdgeInsetsGeometry.lerp(a?.margin, b?.margin, t),
128
      verticalOffset: lerpDouble(a?.verticalOffset, b?.verticalOffset, t),
129 130
      preferBelow: t < 0.5 ? a?.preferBelow: b?.preferBelow,
      excludeFromSemantics: t < 0.5 ? a?.excludeFromSemantics : b?.excludeFromSemantics,
131 132 133 134 135 136 137 138 139 140
      decoration: Decoration.lerp(a?.decoration, b?.decoration, t),
      textStyle: TextStyle.lerp(a?.textStyle, b?.textStyle, t),
    );
  }

  @override
  int get hashCode {
    return hashValues(
      height,
      padding,
141
      margin,
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
      verticalOffset,
      preferBelow,
      excludeFromSemantics,
      decoration,
      textStyle,
      waitDuration,
      showDuration,
    );
  }

  @override
  bool operator==(Object other) {
    if (identical(this, other))
      return true;
    if (other.runtimeType != runtimeType)
      return false;
158 159 160 161 162 163 164 165 166 167 168
    return other is TooltipThemeData
        && other.height == height
        && other.padding == padding
        && other.margin == margin
        && other.verticalOffset == verticalOffset
        && other.preferBelow == preferBelow
        && other.excludeFromSemantics == excludeFromSemantics
        && other.decoration == decoration
        && other.textStyle == textStyle
        && other.waitDuration == waitDuration
        && other.showDuration == showDuration;
169 170 171 172 173 174 175
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(DoubleProperty('height', height, defaultValue: null));
    properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding, defaultValue: null));
176
    properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('margin', margin, defaultValue: null));
177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192
    properties.add(DoubleProperty('vertical offset', verticalOffset, defaultValue: null));
    properties.add(FlagProperty('position', value: preferBelow, ifTrue: 'below', ifFalse: 'above', showName: true, defaultValue: null));
    properties.add(FlagProperty('semantics', value: excludeFromSemantics, ifTrue: 'excluded', showName: true, defaultValue: null));
    properties.add(DiagnosticsProperty<Decoration>('decoration', decoration, defaultValue: null));
    properties.add(DiagnosticsProperty<TextStyle>('textStyle', textStyle, defaultValue: null));
    properties.add(DiagnosticsProperty<Duration>('wait duration', waitDuration, defaultValue: null));
    properties.add(DiagnosticsProperty<Duration>('show duration', showDuration, defaultValue: null));
  }
}

/// An inherited widget that defines the configuration for
/// [Tooltip]s in this widget's subtree.
///
/// Values specified here are used for [Tooltip] properties that are not
/// given an explicit non-null value.
///
193
/// {@tool snippet}
194 195 196 197 198 199
///
/// Here is an example of a tooltip theme that applies a blue foreground
/// with non-rounded corners.
///
/// ```dart
/// TooltipTheme(
200 201 202 203 204
///   data: TooltipThemeData(
///     decoration: BoxDecoration(
///       color: Colors.blue.withOpacity(0.9),
///       borderRadius: BorderRadius.zero,
///     ),
205 206 207 208 209 210 211 212 213 214 215 216
///   ),
///   child: Tooltip(
///     message: 'Example tooltip',
///     child: IconButton(
///       iconSize: 36.0,
///       icon: Icon(Icons.touch_app),
///       onPressed: () {},
///     ),
///   ),
/// ),
/// ```
/// {@end-tool}
217
class TooltipTheme extends InheritedTheme {
218 219
  /// Creates a tooltip theme that controls the configurations for
  /// [Tooltip].
220 221 222
  ///
  /// The data argument must not be null.
  const TooltipTheme({
223 224 225
    Key? key,
    required this.data,
    required Widget child,
226
  }) : assert(data != null), super(key: key, child: child);
227 228 229 230 231 232 233 234 235 236 237 238 239 240

  /// The properties for descendant [Tooltip] widgets.
  final TooltipThemeData data;

  /// Returns the [data] from the closest [TooltipTheme] ancestor. If there is
  /// no ancestor, it returns [ThemeData.tooltipTheme]. Applications can assume
  /// that the returned value will not be null.
  ///
  /// Typical usage is as follows:
  ///
  /// ```dart
  /// TooltipThemeData theme = TooltipTheme.of(context);
  /// ```
  static TooltipThemeData of(BuildContext context) {
241
    final TooltipTheme? tooltipTheme = context.dependOnInheritedWidgetOfExactType<TooltipTheme>();
242
    return tooltipTheme?.data ?? Theme.of(context).tooltipTheme;
243 244
  }

245 246
  @override
  Widget wrap(BuildContext context, Widget child) {
247
    return TooltipTheme(data: data, child: child);
248 249
  }

250 251 252
  @override
  bool updateShouldNotify(TooltipTheme oldWidget) => data != oldWidget.data;
}