Commit 87445e59 authored by Ian Hickson's avatar Ian Hickson Committed by GitHub

Increase the touch slop. (#11419)

It was 8.0. It's now arbitrarily 18.0.

Changing this required adjusting some tests. Adjusting the tests
required debugging the tests. Debugging the tests required some tools
to help debugging gesture recognizers and gesture arenas, so I added
some. It also required updating some toString() methods which resulted
in some changes to the tree diagnostics logic.

Also I cleaned up some docs while I was at it.
parent 990dae85
...@@ -22,6 +22,7 @@ export 'package:meta/meta.dart' show ...@@ -22,6 +22,7 @@ export 'package:meta/meta.dart' show
// bool _first; // bool _first;
// bool _lights; // bool _lights;
// bool _visible; // bool _visible;
// bool inherit;
// class Cat { } // class Cat { }
// double _volume; // double _volume;
// dynamic _calculation; // dynamic _calculation;
......
...@@ -430,7 +430,7 @@ class _CupertinoEdgeShadowDecoration extends Decoration { ...@@ -430,7 +430,7 @@ class _CupertinoEdgeShadowDecoration extends Decoration {
}) { }) {
return new DiagnosticsNode.lazy( return new DiagnosticsNode.lazy(
name: name, name: name,
object: this, value: this,
style: style, style: style,
description: '$runtimeType', description: '$runtimeType',
fillProperties: (List<DiagnosticsNode> properties) { fillProperties: (List<DiagnosticsNode> properties) {
......
...@@ -67,6 +67,9 @@ enum DiagnosticsTreeStyle { ...@@ -67,6 +67,9 @@ enum DiagnosticsTreeStyle {
/// * [DiagnosticsNode.toStringDeep], for code using [TextTreeConfiguration] /// * [DiagnosticsNode.toStringDeep], for code using [TextTreeConfiguration]
/// to render text art for arbitrary trees of [DiagnosticsNode] objects. /// to render text art for arbitrary trees of [DiagnosticsNode] objects.
class TextTreeConfiguration { class TextTreeConfiguration {
/// Create a configuration object describing how to render a tree as text.
///
/// All of the arguments must not be null.
TextTreeConfiguration({ TextTreeConfiguration({
@required this.prefixLineOne, @required this.prefixLineOne,
@required this.prefixOtherLines, @required this.prefixOtherLines,
...@@ -87,7 +90,26 @@ class TextTreeConfiguration { ...@@ -87,7 +90,26 @@ class TextTreeConfiguration {
this.addBlankLineIfNoChildren: true, this.addBlankLineIfNoChildren: true,
this.isNameOnOwnLine: false, this.isNameOnOwnLine: false,
this.isBlankLineBetweenPropertiesAndChildren: true, this.isBlankLineBetweenPropertiesAndChildren: true,
}) : childLinkSpace = ' ' * linkCharacter.length; }) : assert(prefixLineOne != null),
assert(prefixOtherLines != null),
assert(prefixLastChildLineOne != null),
assert(prefixOtherLinesRootNode != null),
assert(linkCharacter != null),
assert(propertyPrefixIfChildren != null),
assert(propertyPrefixNoChildren != null),
assert(lineBreak != null),
assert(afterName != null),
assert(afterDescriptionIfBody != null),
assert(beforeProperties != null),
assert(afterProperties != null),
assert(propertySeparator != null),
assert(bodyIndent != null),
assert(footer != null),
assert(showChildren != null),
assert(addBlankLineIfNoChildren != null),
assert(isNameOnOwnLine != null),
assert(isBlankLineBetweenPropertiesAndChildren != null),
childLinkSpace = ' ' * linkCharacter.length;
/// Prefix to add to the first line to display a child with this style. /// Prefix to add to the first line to display a child with this style.
final String prefixLineOne; final String prefixLineOne;
...@@ -550,40 +572,48 @@ class _NoDefaultValue { ...@@ -550,40 +572,48 @@ class _NoDefaultValue {
/// Marker object indicating that a DiagnosticNode has no default value. /// Marker object indicating that a DiagnosticNode has no default value.
const _NoDefaultValue kNoDefaultValue = const _NoDefaultValue(); const _NoDefaultValue kNoDefaultValue = const _NoDefaultValue();
/// Defines diagnostics data for an [Object]. /// Defines diagnostics data for a [value].
/// ///
/// DiagnosticsNode provides a high quality multi-line string dump via /// DiagnosticsNode provides a high quality multi-line string dump via
/// [toStringDeep]. The core members are the [name], [description], [getProperties], /// [toStringDeep]. The core members are the [name], [description], [getProperties],
/// [object], and [getChildren]. All other members exist typically to provide /// [value], and [getChildren]. All other members exist typically to provide
/// hints for how [toStringDeep] and debugging tools should format output. /// hints for how [toStringDeep] and debugging tools should format output.
abstract class DiagnosticsNode { abstract class DiagnosticsNode {
/// Initializes the object.
///
/// The [style], [showName] and [showSeparator] arguments must not be null.
DiagnosticsNode({ DiagnosticsNode({
@required this.name, @required this.name,
this.style: DiagnosticsTreeStyle.sparse, this.style: DiagnosticsTreeStyle.sparse,
this.showName: true, this.showName: true,
this.showSeparator: true, this.showSeparator: true,
this.emptyBodyDescription, this.emptyBodyDescription,
}) { }) : assert(style != null),
assert(showName != null),
assert(showSeparator != null) {
// A name ending with ':' indicates that the user forgot that the ':' will // A name ending with ':' indicates that the user forgot that the ':' will
// be automatically added for them when generating descriptions of the // be automatically added for them when generating descriptions of the
// property. // property.
assert(name == null || !name.endsWith(':')); assert(name == null || !name.endsWith(':'), 'Names of diagnostic nodes must not end with colons.');
} }
/// Constructor that creates a [DiagnosticsNode] where properties and children /// Constructor that creates a [DiagnosticsNode] where properties and children
/// are computed lazily. /// are computed lazily.
///
/// The [style] argument must not be null.
factory DiagnosticsNode.lazy({ factory DiagnosticsNode.lazy({
String name, String name,
Object object, Object value,
String description, String description,
FillPropertiesCallback fillProperties, FillPropertiesCallback fillProperties,
GetChildrenCallback getChildren, GetChildrenCallback getChildren,
String emptyBodyDescription, String emptyBodyDescription,
DiagnosticsTreeStyle style: DiagnosticsTreeStyle.sparse, DiagnosticsTreeStyle style: DiagnosticsTreeStyle.sparse,
}) { }) {
assert(style != null);
return new _LazyMembersDiagnosticsNode( return new _LazyMembersDiagnosticsNode(
name: name, name: name,
object: object, value: value,
description: description, description: description,
fillProperties: fillProperties, fillProperties: fillProperties,
getChildren: getChildren, getChildren: getChildren,
...@@ -592,10 +622,11 @@ abstract class DiagnosticsNode { ...@@ -592,10 +622,11 @@ abstract class DiagnosticsNode {
); );
} }
/// Diagnostics containing just a string `message` and not a concrete name or /// Diagnostics containing just a string `message` and not a concrete name or
/// value. /// value.
/// ///
/// The [style] argument must not be null.
///
/// See also: /// See also:
/// ///
/// * [PropertyMessage], which should be used if the message should be /// * [PropertyMessage], which should be used if the message should be
...@@ -604,6 +635,7 @@ abstract class DiagnosticsNode { ...@@ -604,6 +635,7 @@ abstract class DiagnosticsNode {
String message, { String message, {
DiagnosticsTreeStyle style: DiagnosticsTreeStyle.singleLine, DiagnosticsTreeStyle style: DiagnosticsTreeStyle.singleLine,
}) { }) {
assert(style != null);
return new DiagnosticsProperty<Null>( return new DiagnosticsProperty<Null>(
'', '',
null, null,
...@@ -630,13 +662,18 @@ abstract class DiagnosticsNode { ...@@ -630,13 +662,18 @@ abstract class DiagnosticsNode {
/// view of a tree. /// view of a tree.
bool get hidden; bool get hidden;
/// Whether the name of the property should be shown when showing the default
/// view of the tree.
///
/// This could be set to false (hiding the name) if the value's description
/// will make the name self-evident.
final bool showName; final bool showName;
/// Description to show if the node has no displayed properties or children. /// Description to show if the node has no displayed properties or children.
final String emptyBodyDescription; final String emptyBodyDescription;
/// Dart object this is diagnostics data for. /// The actual object this is diagnostics data for.
Object get object; Object get value;
/// Hint for how the node should be displayed. /// Hint for how the node should be displayed.
final DiagnosticsTreeStyle style; final DiagnosticsTreeStyle style;
...@@ -658,17 +695,19 @@ abstract class DiagnosticsNode { ...@@ -658,17 +695,19 @@ abstract class DiagnosticsNode {
@override @override
String toString() { String toString() {
assert(style != null);
if (style == DiagnosticsTreeStyle.singleLine) if (style == DiagnosticsTreeStyle.singleLine)
return toStringDeep(); return toStringDeep();
if (name == null || name.isEmpty || showName == false) if (name == null || name.isEmpty || !showName)
return description; return description;
return description.contains('\n') ? return description.contains('\n') ? '$name$_separator\n$description'
'$name$_separator\n$description' : '$name$_separator $description'; : '$name$_separator $description';
} }
TextTreeConfiguration get textTreeConfiguration { TextTreeConfiguration get textTreeConfiguration {
assert(style != null);
switch (style) { switch (style) {
case DiagnosticsTreeStyle.dense: case DiagnosticsTreeStyle.dense:
return denseTextConfiguration; return denseTextConfiguration;
...@@ -683,7 +722,7 @@ abstract class DiagnosticsNode { ...@@ -683,7 +722,7 @@ abstract class DiagnosticsNode {
case DiagnosticsTreeStyle.singleLine: case DiagnosticsTreeStyle.singleLine:
return singleLineTextConfiguration; return singleLineTextConfiguration;
} }
return sparseTextConfiguration; return null;
} }
/// Text configuration to use to connect this node to a `child`. /// Text configuration to use to connect this node to a `child`.
...@@ -707,8 +746,8 @@ abstract class DiagnosticsNode { ...@@ -707,8 +746,8 @@ abstract class DiagnosticsNode {
/// ///
/// See also: /// See also:
/// ///
/// * [toString], for a brief description of the object but not its children. /// * [toString], for a brief description of the [value] but not its children.
/// * [toStringShallow], for a detailed description of the object but not its /// * [toStringShallow], for a detailed description of the [value] but not its
/// children. /// children.
String toStringDeep([String prefixLineOne = '', String prefixOtherLines = '']) { String toStringDeep([String prefixLineOne = '', String prefixOtherLines = '']) {
prefixOtherLines ??= prefixLineOne; prefixOtherLines ??= prefixLineOne;
...@@ -770,8 +809,7 @@ abstract class DiagnosticsNode { ...@@ -770,8 +809,7 @@ abstract class DiagnosticsNode {
)); ));
continue; continue;
} }
assert (property.style == null || assert(property.style == DiagnosticsTreeStyle.singleLine);
property.style == DiagnosticsTreeStyle.singleLine);
final String message = property == null ? '<null>' : property.toString(); final String message = property == null ? '<null>' : property.toString();
if (config.isSingleLine || message.length < kWrapWidth) { if (config.isSingleLine || message.length < kWrapWidth) {
builder.write(message); builder.write(message);
...@@ -847,9 +885,10 @@ abstract class DiagnosticsNode { ...@@ -847,9 +885,10 @@ abstract class DiagnosticsNode {
/// Debugging message displayed like a property. /// Debugging message displayed like a property.
/// ///
/// The following two properties should be a [MessageProperty] not /// The following two properties should be a [MessageProperty], not
/// [StringProperty] as the intent is to show a message with property style /// [StringProperty], as the intent is to show a message with property style
/// display not to describe the value of an actual property of the object. /// display rather than to describe the value of an actual property of the
/// object.
/// ///
/// ```dart /// ```dart
/// new MessageProperty('table size', '$columns\u00D7$rows')); /// new MessageProperty('table size', '$columns\u00D7$rows'));
...@@ -858,6 +897,7 @@ abstract class DiagnosticsNode { ...@@ -858,6 +897,7 @@ abstract class DiagnosticsNode {
/// ///
/// StringProperty should be used if the property has a concrete value that is /// StringProperty should be used if the property has a concrete value that is
/// a string. /// a string.
///
/// ```dart /// ```dart
/// new StringProperty('fontFamily', fontFamily); /// new StringProperty('fontFamily', fontFamily);
/// new StringProperty('title', title): /// new StringProperty('title', title):
...@@ -870,7 +910,16 @@ abstract class DiagnosticsNode { ...@@ -870,7 +910,16 @@ abstract class DiagnosticsNode {
/// * [StringProperty], which should be used instead for properties with string /// * [StringProperty], which should be used instead for properties with string
/// values. /// values.
class MessageProperty extends DiagnosticsProperty<Null> { class MessageProperty extends DiagnosticsProperty<Null> {
MessageProperty(String name, String message) : super(name, null, description: message); /// Create a diagnostics property that displays a message.
///
/// Messages have no concrete [value] (so [value] will return null). The
/// message is stored in the [description].
///
/// The [name] and `message` arguments must not be null.
MessageProperty(String name, String message)
: assert(name != null),
assert(message != null),
super(name, null, description: message);
} }
/// Property which encloses its string [value] in quotes. /// Property which encloses its string [value] in quotes.
...@@ -880,6 +929,9 @@ class MessageProperty extends DiagnosticsProperty<Null> { ...@@ -880,6 +929,9 @@ class MessageProperty extends DiagnosticsProperty<Null> {
/// * [MessageProperty], which should be used instead if showing a message /// * [MessageProperty], which should be used instead if showing a message
/// instead of describing a property with a string value. /// instead of describing a property with a string value.
class StringProperty extends DiagnosticsProperty<String> { class StringProperty extends DiagnosticsProperty<String> {
/// Create a diagnostics property for strings.
///
/// The [showName], [hidden], and [quoted] arguments must not be null.
StringProperty(String name, String value, { StringProperty(String name, String value, {
String description, String description,
bool showName: true, bool showName: true,
...@@ -887,7 +939,10 @@ class StringProperty extends DiagnosticsProperty<String> { ...@@ -887,7 +939,10 @@ class StringProperty extends DiagnosticsProperty<String> {
bool hidden: false, bool hidden: false,
this.quoted: true, this.quoted: true,
String ifEmpty, String ifEmpty,
}) : super( }) : assert(showName != null),
assert(hidden != null),
assert(quoted != null),
super(
name, name,
value, value,
description: description, description: description,
...@@ -974,7 +1029,6 @@ abstract class _NumProperty<T extends num> extends DiagnosticsProperty<T> { ...@@ -974,7 +1029,6 @@ abstract class _NumProperty<T extends num> extends DiagnosticsProperty<T> {
/// ///
/// Numeric formatting is optimized for debug message readability. /// Numeric formatting is optimized for debug message readability.
class DoubleProperty extends _NumProperty<double> { class DoubleProperty extends _NumProperty<double> {
/// If specified, `unit` describes the unit for the [value] (e.g. px). /// If specified, `unit` describes the unit for the [value] (e.g. px).
DoubleProperty(String name, double value, { DoubleProperty(String name, double value, {
bool hidden: false, bool hidden: false,
...@@ -1017,20 +1071,25 @@ class DoubleProperty extends _NumProperty<double> { ...@@ -1017,20 +1071,25 @@ class DoubleProperty extends _NumProperty<double> {
); );
@override @override
String numberToString() => object?.toStringAsFixed(1); String numberToString() => value?.toStringAsFixed(1);
} }
/// An int valued property with an optional unit the value is measured in. /// An int valued property with an optional unit the value is measured in.
/// ///
/// Examples of units include 'px' and 'ms'. /// Examples of units include 'px' and 'ms'.
class IntProperty extends _NumProperty<int> { class IntProperty extends _NumProperty<int> {
/// Create a diagnostics property for integers.
///
/// The [showName] and [hidden] arguments must not be null.
IntProperty(String name, int value, { IntProperty(String name, int value, {
String ifNull, String ifNull,
bool showName: true, bool showName: true,
String unit, String unit,
Object defaultValue: kNoDefaultValue, Object defaultValue: kNoDefaultValue,
bool hidden: false, bool hidden: false,
}) : super( }) : assert(showName != null),
assert(hidden != null),
super(
name, name,
value, value,
ifNull: ifNull, ifNull: ifNull,
...@@ -1041,31 +1100,42 @@ class IntProperty extends _NumProperty<int> { ...@@ -1041,31 +1100,42 @@ class IntProperty extends _NumProperty<int> {
); );
@override @override
String numberToString() => object.toString(); String numberToString() => value.toString();
} }
/// Property which clamps a [double] to between 0 and 1 and formats it as a /// Property which clamps a [double] to between 0 and 1 and formats it as a
/// percentage. /// percentage.
class PercentProperty extends DoubleProperty { class PercentProperty extends DoubleProperty {
/// Create a diagnostics property for doubles that represent percentages or
/// fractions.
///
/// Setting [showName] to false is often reasonable for [PercentProperty]
/// objects, as the fact that the property is shown as a percentage tends to
/// be sufficient to disambiguate its meaning.
///
/// The [showName] and [hidden] arguments must not be null.
PercentProperty(String name, double fraction, { PercentProperty(String name, double fraction, {
String ifNull, String ifNull,
bool showName: true, bool showName: true,
String tooltip, String tooltip,
String unit, String unit,
}) : super( bool hidden: false,
}) : assert(showName != null),
assert(hidden != null),
super(
name, name,
fraction, fraction,
ifNull: ifNull, ifNull: ifNull,
showName: showName, showName: showName,
tooltip: tooltip, tooltip: tooltip,
unit: unit, unit: unit,
hidden: hidden,
); );
@override @override
String valueToString() { String valueToString() {
if (value == null) if (value == null)
return value.toString(); return value.toString();
return unit != null ? '${numberToString()} $unit' : numberToString(); return unit != null ? '${numberToString()} $unit' : numberToString();
} }
...@@ -1073,19 +1143,18 @@ class PercentProperty extends DoubleProperty { ...@@ -1073,19 +1143,18 @@ class PercentProperty extends DoubleProperty {
String numberToString() { String numberToString() {
if (value == null) if (value == null)
return value.toString(); return value.toString();
return '${(value.clamp(0.0, 1.0) * 100.0).toStringAsFixed(1)}%'; return '${(value.clamp(0.0, 1.0) * 100.0).toStringAsFixed(1)}%';
} }
} }
/// Property where the description is either [ifTrue] or [ifFalse] depending on /// Property where the description is either [ifTrue] or [ifFalse] depending on
/// whether [value] is `true` or `false`. /// whether [value] is true or false.
/// ///
/// Using FlagProperty instead of `DiagnosticsProperty<bool>` can make /// Using [FlagProperty] instead of [DiagnosticsProperty<bool>] can make
/// diagnostics display more polished. For example, Given a property named /// diagnostics display more polished. For example, given a property named
/// `visible` that is typically true, the following code will return 'hidden' /// `visible` that is typically true, the following code will return 'hidden'
/// when `visible` is false and the empty string in contrast to `visible: true` /// when `visible` is false and nothing when visible is true, in contrast to
/// or `visible: false`. /// `visible: true` or `visible: false`.
/// ///
/// ## Sample code /// ## Sample code
/// ///
...@@ -1097,12 +1166,10 @@ class PercentProperty extends DoubleProperty { ...@@ -1097,12 +1166,10 @@ class PercentProperty extends DoubleProperty {
/// ) /// )
/// ``` /// ```
/// ///
/// [FlagProperty] should also be used instead of `DiagnosticsProperty<bool>` /// [FlagProperty] should also be used instead of [DiagnosticsProperty<bool>]
/// if showing the bool value would not clearly indicate the meaning of the /// if showing the bool value would not clearly indicate the meaning of the
/// property value. /// property value.
/// ///
/// ## Sample code
///
/// ```dart /// ```dart
/// new FlagProperty( /// new FlagProperty(
/// 'inherit', /// 'inherit',
...@@ -1115,7 +1182,7 @@ class PercentProperty extends DoubleProperty { ...@@ -1115,7 +1182,7 @@ class PercentProperty extends DoubleProperty {
/// See also: /// See also:
/// ///
/// * [ObjectFlagProperty], which provides similar behavior describing whether /// * [ObjectFlagProperty], which provides similar behavior describing whether
/// a `value` is `null`. /// a [value] is null.
class FlagProperty extends DiagnosticsProperty<bool> { class FlagProperty extends DiagnosticsProperty<bool> {
/// Constructs a FlagProperty with the given descriptions with the specified descriptions. /// Constructs a FlagProperty with the given descriptions with the specified descriptions.
/// ///
...@@ -1138,15 +1205,15 @@ class FlagProperty extends DiagnosticsProperty<bool> { ...@@ -1138,15 +1205,15 @@ class FlagProperty extends DiagnosticsProperty<bool> {
assert(ifTrue != null || ifFalse != null); assert(ifTrue != null || ifFalse != null);
} }
/// Description to use if the property [value] is `true`. /// Description to use if the property [value] is true.
/// ///
/// If not specified and [value] equals `true`, the description is set to the /// If not specified and [value] equals true, the description is set to the
/// empty string and the property is [hidden]. /// empty string and the property is [hidden].
final String ifTrue; final String ifTrue;
/// Description to use if the property value is `false`. /// Description to use if the property value is false.
/// ///
/// If not specified and [value] equals `false`, the description is set to the /// If not specified and [value] equals false, the description is set to the
/// empty string and the property is [hidden]. /// empty string and the property is [hidden].
final String ifFalse; final String ifFalse;
...@@ -1161,13 +1228,12 @@ class FlagProperty extends DiagnosticsProperty<bool> { ...@@ -1161,13 +1228,12 @@ class FlagProperty extends DiagnosticsProperty<bool> {
@override @override
bool get hidden { bool get hidden {
if (_hidden || object == defaultValue) if (_hidden || value == defaultValue)
return true; return true;
if (object == true) if (value == true)
return ifTrue == null; return ifTrue == null;
if (object == false) if (value == false)
return ifFalse == null; return ifFalse == null;
return true; return true;
} }
} }
...@@ -1175,31 +1241,42 @@ class FlagProperty extends DiagnosticsProperty<bool> { ...@@ -1175,31 +1241,42 @@ class FlagProperty extends DiagnosticsProperty<bool> {
/// Property with an `Iterable<T>` [value] that can be displayed with /// Property with an `Iterable<T>` [value] that can be displayed with
/// different [DiagnosticsTreeStyle] for custom rendering. /// different [DiagnosticsTreeStyle] for custom rendering.
/// ///
/// If `style` is [DiagnosticsTreeStyle.singleLine], the iterable is described /// If [style] is [DiagnosticsTreeStyle.singleLine], the iterable is described
/// as a comma separated list, otherwise the iterable is described as a line /// as a comma separated list, otherwise the iterable is described as a line
/// break separated list. /// break separated list.
class IterableProperty<T> extends DiagnosticsProperty<Iterable<T>> { class IterableProperty<T> extends DiagnosticsProperty<Iterable<T>> {
/// Create a diagnostics property for iterables (e.g. lists).
///
/// The [ifEmpty] argument must not be null, as otherwise an iterable
/// value with 0 elements would, confusingly, be displayed as the empty
/// string. It defaults to the string `[]`.
///
/// The [style] and [hidden] arguments must also not be null.
IterableProperty(String name, Iterable<T> value, { IterableProperty(String name, Iterable<T> value, {
Object defaultValue: kNoDefaultValue, Object defaultValue: kNoDefaultValue,
String ifNull, String ifNull,
String ifEmpty = '[]', String ifEmpty: '[]',
DiagnosticsTreeStyle style: DiagnosticsTreeStyle.singleLine, DiagnosticsTreeStyle style: DiagnosticsTreeStyle.singleLine,
}) : super( bool hidden: false,
}) : assert(ifEmpty != null),
assert(style != null),
assert(hidden != null),
super(
name, name,
value, value,
defaultValue: defaultValue, defaultValue: defaultValue,
ifNull: ifNull, ifNull: ifNull,
ifEmpty: ifEmpty, ifEmpty: ifEmpty,
style: style, style: style,
hidden: hidden,
); );
@override @override
String valueToString() { String valueToString() {
if (value == null) if (value == null)
return value.toString(); return value.toString();
return style == DiagnosticsTreeStyle.singleLine ? value.join(', ')
return style == DiagnosticsTreeStyle.singleLine ? : value.join('\n');
value.join(', ') : object.join('\n');
} }
} }
...@@ -1213,6 +1290,9 @@ class IterableProperty<T> extends DiagnosticsProperty<Iterable<T>> { ...@@ -1213,6 +1290,9 @@ class IterableProperty<T> extends DiagnosticsProperty<Iterable<T>> {
/// * [DiagnosticsProperty] which documents named parameters common to all /// * [DiagnosticsProperty] which documents named parameters common to all
/// [DiagnosticsProperty] /// [DiagnosticsProperty]
class EnumProperty<T> extends DiagnosticsProperty<T> { class EnumProperty<T> extends DiagnosticsProperty<T> {
/// Create a diagnostics property that displays an enum.
///
/// The [hidden] argument must not be null.
EnumProperty(String name, T value, { EnumProperty(String name, T value, {
Object defaultValue: kNoDefaultValue, Object defaultValue: kNoDefaultValue,
bool hidden: false, bool hidden: false,
...@@ -1227,37 +1307,50 @@ class EnumProperty<T> extends DiagnosticsProperty<T> { ...@@ -1227,37 +1307,50 @@ class EnumProperty<T> extends DiagnosticsProperty<T> {
String valueToString() { String valueToString() {
if (value == null) if (value == null)
return value.toString(); return value.toString();
return camelCaseToHyphenatedName(describeEnum(value)); return camelCaseToHyphenatedName(describeEnum(value));
} }
} }
/// Flag describing whether a [value] is `null` or not. /// A property where the important diagnostic information is primarily whether
/// the [value] is present (non-null) or absent (null), rather than the actual
/// value of the property itself.
/// ///
/// [ifPresent] and [ifNull] describe the property [value] /// The [ifPresent] and [ifNull] strings describe the property [value] when it
/// when it is present and `null` respectively. If [ifPresent] or [ifNull] is /// is non-null and null respectively. If one of [ifPresent] or [ifNull] is
/// omitted, that is taken to mean that [hidden] should be `true`when [value] is /// omitted, that is taken to mean that [hidden] should be true when [value] is
/// present and null respectively. /// non-null or null respectively.
///
/// This kind of diagnostics property is typically used for values mostly opaque
/// values, like closures, where presenting the actual object is of dubious
/// value but where reporting the presence or absence of the value is much more
/// useful.
/// ///
/// See also: /// See also:
/// ///
/// * [FlagProperty], which provides similar functionality describing whether /// * [FlagProperty], which provides similar functionality describing whether
/// a `value` is `true` or `false`. /// a [value] is true or false.
class ObjectFlagProperty<T> extends DiagnosticsProperty<T> { class ObjectFlagProperty<T> extends DiagnosticsProperty<T> {
/// Create a diagnostics property for values that can be present (non-null) or
/// absent (null), but for which the exact value's [Object.toString]
/// representation is not very transparent (e.g. a callback).
///
/// The [showName] and [hidden] arguments must not be null. Additionally, at
/// least one of [ifPresent] and [ifNull] must not be null.
ObjectFlagProperty(String name, T value, { ObjectFlagProperty(String name, T value, {
this.ifPresent, this.ifPresent,
String ifNull, String ifNull,
bool showName: false, bool showName: false,
bool hidden: false, bool hidden: false,
}) : super( }) : assert(ifPresent != null || ifNull != null),
assert(showName != null),
assert(hidden != null),
super(
name, name,
value, value,
showName: showName, showName: showName,
hidden: hidden, hidden: hidden,
ifNull: ifNull, ifNull: ifNull,
) { );
assert(ifPresent != null || ifNull != null);
}
/// Shorthand constructor to describe whether the property has a value. /// Shorthand constructor to describe whether the property has a value.
/// ///
...@@ -1273,9 +1366,9 @@ class ObjectFlagProperty<T> extends DiagnosticsProperty<T> { ...@@ -1273,9 +1366,9 @@ class ObjectFlagProperty<T> extends DiagnosticsProperty<T> {
showName: false, showName: false,
); );
/// Description to use if the property [value] is not `null`. /// Description to use if the property [value] is not null.
/// ///
/// If the property [value] is not `null` and [ifPresent] is null, the /// If the property [value] is not null and [ifPresent] is null, the
/// [description] is the empty string and the property is [hidden]. /// [description] is the empty string and the property is [hidden].
final String ifPresent; final String ifPresent;
...@@ -1291,10 +1384,8 @@ class ObjectFlagProperty<T> extends DiagnosticsProperty<T> { ...@@ -1291,10 +1384,8 @@ class ObjectFlagProperty<T> extends DiagnosticsProperty<T> {
bool get hidden { bool get hidden {
if (super.hidden) if (super.hidden)
return true; return true;
if (value != null)
if (object != null)
return ifPresent == null; return ifPresent == null;
return ifNull == null; return ifNull == null;
} }
} }
...@@ -1308,12 +1399,18 @@ typedef T ComputePropertyValueCallback<T>(); ...@@ -1308,12 +1399,18 @@ typedef T ComputePropertyValueCallback<T>();
/// Property with a [value] of type [T]. /// Property with a [value] of type [T].
/// ///
/// If the default `object.toString()` does not provide an adequate description /// If the default `value.toString()` does not provide an adequate description
/// of the object, specify `description` defining a custom description. /// of the value, specify [description] defining a custom description.
/// * `hidden` specifies whether the property should be hidden. ///
/// * `showSeparator` indicates whether a separator should be placed /// The [hidden] property specifies whether the property should be hidden from
/// between the property `name` and `object`. /// the default output (e.g. the output given by [toStringDeep]).
///
/// The [showSeparator] property indicates whether a separator should be placed
/// between the property [name] and its [value].
class DiagnosticsProperty<T> extends DiagnosticsNode { class DiagnosticsProperty<T> extends DiagnosticsNode {
/// Create a diagnostics property.
///
/// The [hidden], [showName], [showSeparator], and [style] arguments must not be null.
DiagnosticsProperty( DiagnosticsProperty(
String name, String name,
T value, { T value, {
...@@ -1325,8 +1422,12 @@ class DiagnosticsProperty<T> extends DiagnosticsNode { ...@@ -1325,8 +1422,12 @@ class DiagnosticsProperty<T> extends DiagnosticsNode {
bool showSeparator: true, bool showSeparator: true,
this.defaultValue: kNoDefaultValue, this.defaultValue: kNoDefaultValue,
this.tooltip, this.tooltip,
DiagnosticsTreeStyle style : DiagnosticsTreeStyle.singleLine, DiagnosticsTreeStyle style: DiagnosticsTreeStyle.singleLine,
}) : _description = description, }) : assert(hidden != null),
assert(showName != null),
assert(showSeparator != null),
assert(style != null),
_description = description,
_valueComputed = true, _valueComputed = true,
_value = value, _value = value,
_computeValue = null, _computeValue = null,
...@@ -1342,6 +1443,9 @@ class DiagnosticsProperty<T> extends DiagnosticsNode { ...@@ -1342,6 +1443,9 @@ class DiagnosticsProperty<T> extends DiagnosticsNode {
/// ///
/// Use if computing the property [value] may throw an exception or is /// Use if computing the property [value] may throw an exception or is
/// expensive. /// expensive.
///
/// The [hidden], [showName], [showSeparator], and [style] arguments must not
/// be null.
DiagnosticsProperty.lazy( DiagnosticsProperty.lazy(
String name, String name,
ComputePropertyValueCallback<T> computeValue, { ComputePropertyValueCallback<T> computeValue, {
...@@ -1353,8 +1457,13 @@ class DiagnosticsProperty<T> extends DiagnosticsNode { ...@@ -1353,8 +1457,13 @@ class DiagnosticsProperty<T> extends DiagnosticsNode {
bool showSeparator: true, bool showSeparator: true,
this.defaultValue: kNoDefaultValue, this.defaultValue: kNoDefaultValue,
this.tooltip, this.tooltip,
DiagnosticsTreeStyle style : DiagnosticsTreeStyle.singleLine, DiagnosticsTreeStyle style: DiagnosticsTreeStyle.singleLine,
}) : _description = description, }) : assert(hidden != null),
assert(showName != null),
assert(showSeparator != null),
assert(defaultValue == kNoDefaultValue || defaultValue is T),
assert(style != null),
_description = description,
_valueComputed = false, _valueComputed = false,
_value = null, _value = null,
_computeValue = computeValue, _computeValue = computeValue,
...@@ -1382,21 +1491,27 @@ class DiagnosticsProperty<T> extends DiagnosticsNode { ...@@ -1382,21 +1491,27 @@ class DiagnosticsProperty<T> extends DiagnosticsNode {
@override @override
String get description { String get description {
if (_description != null) if (_description != null)
return addTooltip(_description); return _addTooltip(_description);
if (exception != null) if (exception != null)
return 'EXCEPTION (${exception.runtimeType})'; return 'EXCEPTION (${exception.runtimeType})';
if (ifNull != null && object == null) if (ifNull != null && value == null)
return addTooltip(ifNull); return _addTooltip(ifNull);
String result = valueToString(); String result = valueToString();
if (result.isEmpty && ifEmpty != null) if (result.isEmpty && ifEmpty != null)
result = ifEmpty; result = ifEmpty;
return addTooltip(result); return _addTooltip(result);
} }
String addTooltip(String text) { /// If a [tooltip] is specified, add the tooltip it to the end of `text`
/// enclosing it parenthesis to disambiguate the tooltip from the rest of
/// the text.
///
/// `text` must not be null.
String _addTooltip(String text) {
assert(text != null);
return tooltip == null ? text : '$text ($tooltip)'; return tooltip == null ? text : '$text ($tooltip)';
} }
...@@ -1414,18 +1529,30 @@ class DiagnosticsProperty<T> extends DiagnosticsNode { ...@@ -1414,18 +1529,30 @@ class DiagnosticsProperty<T> extends DiagnosticsNode {
/// generating the string description. /// generating the string description.
final String tooltip; final String tooltip;
/// The type of the property [value].
///
/// This is determined from the type argument `T` used to instantiate the
/// [DiagnosticsProperty] class. This means that the type is available even if
/// [value] is null, but it also means that the [propertyType] is only as
/// accurate as the type provided when invoking the constructor.
///
/// Generally, this is only useful for diagnostic tools that should display
/// null values in a manner consistent with the property type. For example, a
/// tool might display a null [Color] value as an empty rectangle instead of
/// the word "null".
Type get propertyType => T; Type get propertyType => T;
/// Returns the value of the property either from cache or by invoking a /// Returns the value of the property either from cache or by invoking a
/// [ComputePropertyValueCallback]. /// [ComputePropertyValueCallback].
/// ///
/// If an exception is thrown invoking the [ComputePropertyValueCallback], /// If an exception is thrown invoking the [ComputePropertyValueCallback],
/// [value] returns `null` and the exception thrown can be found via the /// [value] returns null and the exception thrown can be found via the
/// [exception] property. /// [exception] property.
/// ///
/// See also: /// See also:
/// ///
/// * [valueToString], which converts the property value to a string. /// * [valueToString], which converts the property value to a string.
@override
T get value { T get value {
_maybeCacheValue(); _maybeCacheValue();
return _value; return _value;
...@@ -1435,9 +1562,6 @@ class DiagnosticsProperty<T> extends DiagnosticsNode { ...@@ -1435,9 +1562,6 @@ class DiagnosticsProperty<T> extends DiagnosticsNode {
bool _valueComputed; bool _valueComputed;
@override
T get object => value;
Object _exception; Object _exception;
/// Exception thrown if accessing the property [value] threw an exception. /// Exception thrown if accessing the property [value] threw an exception.
...@@ -1466,16 +1590,19 @@ class DiagnosticsProperty<T> extends DiagnosticsNode { ...@@ -1466,16 +1590,19 @@ class DiagnosticsProperty<T> extends DiagnosticsNode {
final bool _hidden; final bool _hidden;
/// Whether the property should be hidden when showing the default
/// view of a tree.
///
/// This could be set to true (hiding the property) if another property
/// provides a normally more useful summary of the value.
@override @override
bool get hidden { bool get hidden {
if (_hidden) if (_hidden)
return true; return true;
if (defaultValue != kNoDefaultValue) { if (defaultValue != kNoDefaultValue) {
if (exception != null) if (exception != null)
return false; return false;
return value == defaultValue;
return object == defaultValue;
} }
return false; return false;
} }
...@@ -1509,12 +1636,13 @@ class _LazyMembersDiagnosticsNode extends DiagnosticsNode { ...@@ -1509,12 +1636,13 @@ class _LazyMembersDiagnosticsNode extends DiagnosticsNode {
_LazyMembersDiagnosticsNode({ _LazyMembersDiagnosticsNode({
@required String name, @required String name,
@required String description, @required String description,
@required this.object, @required this.value,
GetChildrenCallback getChildren, GetChildrenCallback getChildren,
FillPropertiesCallback fillProperties, FillPropertiesCallback fillProperties,
String emptyBodyDescription, String emptyBodyDescription,
DiagnosticsTreeStyle style: DiagnosticsTreeStyle.sparse, DiagnosticsTreeStyle style: DiagnosticsTreeStyle.sparse,
}) : _description = description, }) : assert(style != null),
_description = description,
_getChildren = getChildren, _getChildren = getChildren,
_fillProperties = fillProperties, _fillProperties = fillProperties,
super( super(
...@@ -1524,7 +1652,7 @@ class _LazyMembersDiagnosticsNode extends DiagnosticsNode { ...@@ -1524,7 +1652,7 @@ class _LazyMembersDiagnosticsNode extends DiagnosticsNode {
); );
@override @override
final Object object; final Object value;
final GetChildrenCallback _getChildren; final GetChildrenCallback _getChildren;
final FillPropertiesCallback _fillProperties; final FillPropertiesCallback _fillProperties;
...@@ -1535,7 +1663,7 @@ class _LazyMembersDiagnosticsNode extends DiagnosticsNode { ...@@ -1535,7 +1663,7 @@ class _LazyMembersDiagnosticsNode extends DiagnosticsNode {
bool get hidden => false; bool get hidden => false;
@override @override
String get description => _description ?? object.toString(); String get description => _description ?? value.toString();
@override @override
List<DiagnosticsNode> getProperties() { List<DiagnosticsNode> getProperties() {
...@@ -1553,48 +1681,56 @@ class _LazyMembersDiagnosticsNode extends DiagnosticsNode { ...@@ -1553,48 +1681,56 @@ class _LazyMembersDiagnosticsNode extends DiagnosticsNode {
/// [DiagnosticsNode] for an instance of [TreeDiagnosticsMixin]. /// [DiagnosticsNode] for an instance of [TreeDiagnosticsMixin].
class _TreeDiagnosticsMixinNode extends DiagnosticsNode { class _TreeDiagnosticsMixinNode extends DiagnosticsNode {
@override
final TreeDiagnosticsMixin object;
_TreeDiagnosticsMixinNode({ _TreeDiagnosticsMixinNode({
String name, String name,
this.object, this.value,
DiagnosticsTreeStyle style, @required DiagnosticsTreeStyle style,
}) : super( }) : assert(style != null),
super(
name: name, name: name,
style: style, style: style,
); );
@override
final TreeDiagnosticsMixin value;
@override @override
List<DiagnosticsNode> getProperties() { List<DiagnosticsNode> getProperties() {
final List<DiagnosticsNode> description = <DiagnosticsNode>[]; final List<DiagnosticsNode> description = <DiagnosticsNode>[];
if (object != null) if (value != null)
object.debugFillProperties(description); value.debugFillProperties(description);
return description; return description;
} }
@override @override
List<DiagnosticsNode> getChildren() { List<DiagnosticsNode> getChildren() {
if (object != null) if (value != null)
return object.debugDescribeChildren(); return value.debugDescribeChildren();
return <DiagnosticsNode>[]; return <DiagnosticsNode>[];
} }
@override @override
String get description => object.toString(); String get description => value.toString();
@override bool get hidden => false; @override bool get hidden => false;
} }
/// Returns a 5 character long hexadecimal string generated from /// Returns a 5 character long hexadecimal string generated from
/// Object.hashCode's 20 least-significant bits. /// [Object.hashCode]'s 20 least-significant bits.
String shortHash(Object object) { String shortHash(Object object) {
return object.hashCode.toUnsigned(20).toRadixString(16).padLeft(5, '0'); return object.hashCode.toUnsigned(20).toRadixString(16).padLeft(5, '0');
} }
/// Returns a summary of the runtime type and hash code of `object`. /// Returns a summary of the runtime type and hash code of `object`.
String describeIdentity(Object object) => ///
'${object.runtimeType}#${shortHash(object)}'; /// See also:
///
/// * [Object.hashCode], a value used when placing an object in a [Map] or
/// other similar data structure, and which is also used in debug output to
/// distinguish instances of the same class (hash collisions are
/// possible, but rare enough that its use in debug output is useful).
/// * [Object.runtimeType], the [Type] of an object.
String describeIdentity(Object object) => '${object.runtimeType}#${shortHash(object)}';
// This method exists as a workaround for https://github.com/dart-lang/sdk/issues/30021 // This method exists as a workaround for https://github.com/dart-lang/sdk/issues/30021
/// Returns a short description of an enum value. /// Returns a short description of an enum value.
...@@ -1672,6 +1808,8 @@ String camelCaseToHyphenatedName(String word) { ...@@ -1672,6 +1808,8 @@ String camelCaseToHyphenatedName(String word) {
/// * [DiagnosticsNode.lazy], which should be used to create a DiagnosticNode /// * [DiagnosticsNode.lazy], which should be used to create a DiagnosticNode
/// with children and properties where [TreeDiagnosticsMixin] cannot be used. /// with children and properties where [TreeDiagnosticsMixin] cannot be used.
abstract class TreeDiagnostics { abstract class TreeDiagnostics {
/// Abstract const constructor. This constructor enables subclasses to provide
/// const constructors so that they can be used in const expressions.
const TreeDiagnostics(); const TreeDiagnostics();
/// The [toStringDeep] method takes arguments, but those are intended for /// The [toStringDeep] method takes arguments, but those are intended for
...@@ -1686,9 +1824,14 @@ abstract class TreeDiagnostics { ...@@ -1686,9 +1824,14 @@ abstract class TreeDiagnostics {
return toDiagnosticsNode().toStringDeep(prefixLineOne, prefixOtherLines); return toDiagnosticsNode().toStringDeep(prefixLineOne, prefixOtherLines);
} }
DiagnosticsNode toDiagnosticsNode({ String name, DiagnosticsTreeStyle style }); // `name` may be null, but `style` must not be.
// TODO(jacobr): document the semantics of the arguments, including what it means when name is null.
DiagnosticsNode toDiagnosticsNode({ String name, DiagnosticsTreeStyle style: DiagnosticsTreeStyle.sparse });
} }
// Examples can assume:
// class ExampleSuperclass { String message; double stepWidth; double scale; double paintExtent; double hitTestExtent; double paintExtend; double maxWidth; bool primary; double progress; int maxLines; Duration duration; int depth; dynamic boxShadow; dynamic style; bool hasSize; Matrix4 transform; Map<Listenable, VoidCallback> handles; Color color; bool obscureText; ImageRepeat repeat; Size size; Widget widget; bool isCurrent; bool keepAlive; TextAlign textAlign; }
/// A mixin that helps dump string and [DiagnosticsNode] representations of /// A mixin that helps dump string and [DiagnosticsNode] representations of
/// trees. /// trees.
/// ///
...@@ -1751,10 +1894,11 @@ abstract class TreeDiagnosticsMixin implements TreeDiagnostics { ...@@ -1751,10 +1894,11 @@ abstract class TreeDiagnosticsMixin implements TreeDiagnostics {
} }
@override @override
DiagnosticsNode toDiagnosticsNode({ String name, DiagnosticsTreeStyle style }) { DiagnosticsNode toDiagnosticsNode({ String name, DiagnosticsTreeStyle style: DiagnosticsTreeStyle.sparse }) {
assert(style != null);
return new _TreeDiagnosticsMixinNode( return new _TreeDiagnosticsMixinNode(
name: name, name: name,
object: this, value: this,
style: style, style: style,
); );
} }
...@@ -1774,8 +1918,8 @@ abstract class TreeDiagnosticsMixin implements TreeDiagnostics { ...@@ -1774,8 +1918,8 @@ abstract class TreeDiagnosticsMixin implements TreeDiagnostics {
/// very useful. /// very useful.
/// ///
/// * Use `defaultValue` any time the default value of a property is /// * Use `defaultValue` any time the default value of a property is
/// uninteresting. For example, specify a default value of `null` any time /// uninteresting. For example, specify a default value of null any time
/// a property being `null` does not indicate an error. /// a property being null does not indicate an error.
/// * Avoid specifying the `hidden` parameter unless the result you want /// * Avoid specifying the `hidden` parameter unless the result you want
/// cannot be be achieved by using the `defaultValue` parameter or using /// cannot be be achieved by using the `defaultValue` parameter or using
/// the [ObjectFlagProperty] class to conditionally display the property /// the [ObjectFlagProperty] class to conditionally display the property
...@@ -1839,7 +1983,10 @@ abstract class TreeDiagnosticsMixin implements TreeDiagnostics { ...@@ -1839,7 +1983,10 @@ abstract class TreeDiagnosticsMixin implements TreeDiagnostics {
/// common [DiagnosticsProperty] parameters. /// common [DiagnosticsProperty] parameters.
/// ///
/// ```dart /// ```dart
/// abstract class ExampleObject extends Object with TreeDiagnosticsMixin { /// abstract class ExampleObject extends ExampleSuperclass with TreeDiagnosticsMixin {
///
/// // ...various members and properties...
///
/// @override /// @override
/// void debugFillProperties(List<DiagnosticsNode> description) { /// void debugFillProperties(List<DiagnosticsNode> description) {
/// // Always add properties from the base class first. /// // Always add properties from the base class first.
...@@ -1956,32 +2103,6 @@ abstract class TreeDiagnosticsMixin implements TreeDiagnostics { ...@@ -1956,32 +2103,6 @@ abstract class TreeDiagnosticsMixin implements TreeDiagnostics {
/// showName: false, /// showName: false,
/// )); /// ));
/// } /// }
///
/// String message;
/// double stepWidth;
/// double scale;
/// double paintExtent;
/// double hitTestExtent;
/// double paintExtend;
/// double maxWidth;
/// bool primary;
/// double progress;
/// int maxLines;
/// Duration duration;
/// int depth;
/// dynamic boxShadow;
/// dynamic style;
/// bool hasSize;
/// Matrix4 transform;
/// Map<Listenable, VoidCallback> handles;
/// Color color;
/// bool obscureText;
/// ImageRepeat repeat;
/// Size size;
/// Widget widget;
/// bool isCurrent;
/// bool keepAlive;
/// TextAlign textAlign;
/// } /// }
/// ``` /// ```
/// ///
...@@ -2004,6 +2125,6 @@ abstract class TreeDiagnosticsMixin implements TreeDiagnostics { ...@@ -2004,6 +2125,6 @@ abstract class TreeDiagnosticsMixin implements TreeDiagnostics {
/// Used by [toStringDeep], [toDiagnosticsNode] and [toStringShallow]. /// Used by [toStringDeep], [toDiagnosticsNode] and [toStringShallow].
@protected @protected
List<DiagnosticsNode> debugDescribeChildren() { List<DiagnosticsNode> debugDescribeChildren() {
return <DiagnosticsNode>[]; return const <DiagnosticsNode>[];
} }
} }
...@@ -4,6 +4,10 @@ ...@@ -4,6 +4,10 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/foundation.dart';
import 'debug.dart';
/// Whether the gesture was accepted or rejected. /// Whether the gesture was accepted or rejected.
enum GestureDisposition { enum GestureDisposition {
/// This gesture was accepted as the interpretation of the user's input. /// This gesture was accepted as the interpretation of the user's input.
...@@ -42,7 +46,8 @@ class GestureArenaEntry { ...@@ -42,7 +46,8 @@ class GestureArenaEntry {
/// Call this member to claim victory (with accepted) or admit defeat (with rejected). /// Call this member to claim victory (with accepted) or admit defeat (with rejected).
/// ///
/// It's fine to attempt to resolve an arena that is already resolved. /// It's fine to attempt to resolve a gesture recognizer for an arena that is
/// already resolved.
void resolve(GestureDisposition disposition) { void resolve(GestureDisposition disposition) {
_arena._resolve(_pointer, _member, disposition); _arena._resolve(_pointer, _member, disposition);
} }
...@@ -64,19 +69,47 @@ class _GestureArena { ...@@ -64,19 +69,47 @@ class _GestureArena {
assert(isOpen); assert(isOpen);
members.add(member); members.add(member);
} }
@override
String toString() {
final StringBuffer buffer = new StringBuffer();
if (members.isEmpty) {
buffer.write('<empty>');
} else {
buffer.write(members.map<String>((GestureArenaMember member) {
if (member == eagerWinner)
return '$member (eager winner)';
return '$member';
}).join(', '));
}
if (isOpen)
buffer.write(' [open]');
if (isHeld)
buffer.write(' [held]');
if (hasPendingSweep)
buffer.write(' [hasPendingSweep]');
return buffer.toString();
}
} }
/// The first member to accept or the last member to not to reject wins. /// The first member to accept or the last member to not to reject wins.
/// ///
/// See [https://flutter.io/gestures/#gesture-disambiguation] for more /// See [https://flutter.io/gestures/#gesture-disambiguation] for more
/// information about the role this class plays in the gesture system. /// information about the role this class plays in the gesture system.
///
/// To debug problems with gestures, consider using
/// [debugPrintGestureArenaDiagnostics].
class GestureArenaManager { class GestureArenaManager {
final Map<int, _GestureArena> _arenas = <int, _GestureArena>{}; final Map<int, _GestureArena> _arenas = <int, _GestureArena>{};
/// Adds a new member (e.g., gesture recognizer) to the arena. /// Adds a new member (e.g., gesture recognizer) to the arena.
GestureArenaEntry add(int pointer, GestureArenaMember member) { GestureArenaEntry add(int pointer, GestureArenaMember member) {
final _GestureArena state = _arenas.putIfAbsent(pointer, () => new _GestureArena()); final _GestureArena state = _arenas.putIfAbsent(pointer, () {
assert(_debugLogDiagnostic(pointer, '★ Opening new gesture arena.'));
return new _GestureArena();
});
state.add(member); state.add(member);
assert(_debugLogDiagnostic(pointer, 'Adding: $member'));
return new GestureArenaEntry._(this, pointer, member); return new GestureArenaEntry._(this, pointer, member);
} }
...@@ -88,6 +121,7 @@ class GestureArenaManager { ...@@ -88,6 +121,7 @@ class GestureArenaManager {
if (state == null) if (state == null)
return; // This arena either never existed or has been resolved. return; // This arena either never existed or has been resolved.
state.isOpen = false; state.isOpen = false;
assert(_debugLogDiagnostic(pointer, 'Closing', state));
_tryToResolveArena(pointer, state); _tryToResolveArena(pointer, state);
} }
...@@ -111,13 +145,16 @@ class GestureArenaManager { ...@@ -111,13 +145,16 @@ class GestureArenaManager {
assert(!state.isOpen); assert(!state.isOpen);
if (state.isHeld) { if (state.isHeld) {
state.hasPendingSweep = true; state.hasPendingSweep = true;
return; // This arena is being held for a long-lived member assert(_debugLogDiagnostic(pointer, 'Delaying sweep', state));
return; // This arena is being held for a long-lived member.
} }
assert(_debugLogDiagnostic(pointer, 'Sweeping', state));
_arenas.remove(pointer); _arenas.remove(pointer);
if (state.members.isNotEmpty) { if (state.members.isNotEmpty) {
// First member wins // First member wins.
assert(_debugLogDiagnostic(pointer, 'Winner: ${state.members.first}'));
state.members.first.acceptGesture(pointer); state.members.first.acceptGesture(pointer);
// Give all the other members the bad news // Give all the other members the bad news.
for (int i = 1; i < state.members.length; i++) for (int i = 1; i < state.members.length; i++)
state.members[i].rejectGesture(pointer); state.members[i].rejectGesture(pointer);
} }
...@@ -140,6 +177,7 @@ class GestureArenaManager { ...@@ -140,6 +177,7 @@ class GestureArenaManager {
if (state == null) if (state == null)
return; // This arena either never existed or has been resolved. return; // This arena either never existed or has been resolved.
state.isHeld = true; state.isHeld = true;
assert(_debugLogDiagnostic(pointer, 'Holding', state));
} }
/// Releases a hold, allowing the arena to be swept. /// Releases a hold, allowing the arena to be swept.
...@@ -156,37 +194,19 @@ class GestureArenaManager { ...@@ -156,37 +194,19 @@ class GestureArenaManager {
if (state == null) if (state == null)
return; // This arena either never existed or has been resolved. return; // This arena either never existed or has been resolved.
state.isHeld = false; state.isHeld = false;
assert(_debugLogDiagnostic(pointer, 'Releasing', state));
if (state.hasPendingSweep) if (state.hasPendingSweep)
sweep(pointer); sweep(pointer);
} }
void _resolveByDefault(int pointer, _GestureArena state) { /// Reject or accept a gesture recognizer.
if (!_arenas.containsKey(pointer)) ///
return; // Already resolved earlier. /// This is called by calling [GestureArenaEntry.resolve] on the object returned from [add].
assert(_arenas[pointer] == state);
assert(!state.isOpen);
final List<GestureArenaMember> members = state.members;
assert(members.length == 1);
_arenas.remove(pointer);
state.members.first.acceptGesture(pointer);
}
void _tryToResolveArena(int pointer, _GestureArena state) {
assert(_arenas[pointer] == state);
assert(!state.isOpen);
if (state.members.length == 1) {
scheduleMicrotask(() => _resolveByDefault(pointer, state));
} else if (state.members.isEmpty) {
_arenas.remove(pointer);
} else if (state.eagerWinner != null) {
_resolveInFavorOf(pointer, state, state.eagerWinner);
}
}
void _resolve(int pointer, GestureArenaMember member, GestureDisposition disposition) { void _resolve(int pointer, GestureArenaMember member, GestureDisposition disposition) {
final _GestureArena state = _arenas[pointer]; final _GestureArena state = _arenas[pointer];
if (state == null) if (state == null)
return; // This arena has already resolved. return; // This arena has already resolved.
assert(_debugLogDiagnostic(pointer, '${ disposition == GestureDisposition.accepted ? "Accepting" : "Rejecting" }: $member'));
assert(state.members.contains(member)); assert(state.members.contains(member));
if (disposition == GestureDisposition.rejected) { if (disposition == GestureDisposition.rejected) {
state.members.remove(member); state.members.remove(member);
...@@ -198,11 +218,38 @@ class GestureArenaManager { ...@@ -198,11 +218,38 @@ class GestureArenaManager {
if (state.isOpen) { if (state.isOpen) {
state.eagerWinner ??= member; state.eagerWinner ??= member;
} else { } else {
assert(_debugLogDiagnostic(pointer, 'Self-declared winner: $member'));
_resolveInFavorOf(pointer, state, member); _resolveInFavorOf(pointer, state, member);
} }
} }
} }
void _tryToResolveArena(int pointer, _GestureArena state) {
assert(_arenas[pointer] == state);
assert(!state.isOpen);
if (state.members.length == 1) {
scheduleMicrotask(() => _resolveByDefault(pointer, state));
} else if (state.members.isEmpty) {
_arenas.remove(pointer);
assert(_debugLogDiagnostic(pointer, 'Arena empty.'));
} else if (state.eagerWinner != null) {
assert(_debugLogDiagnostic(pointer, 'Eager winner: ${state.eagerWinner}'));
_resolveInFavorOf(pointer, state, state.eagerWinner);
}
}
void _resolveByDefault(int pointer, _GestureArena state) {
if (!_arenas.containsKey(pointer))
return; // Already resolved earlier.
assert(_arenas[pointer] == state);
assert(!state.isOpen);
final List<GestureArenaMember> members = state.members;
assert(members.length == 1);
_arenas.remove(pointer);
assert(_debugLogDiagnostic(pointer, 'Default winner: ${state.members.first}'));
state.members.first.acceptGesture(pointer);
}
void _resolveInFavorOf(int pointer, _GestureArena state, GestureArenaMember member) { void _resolveInFavorOf(int pointer, _GestureArena state, GestureArenaMember member) {
assert(state == _arenas[pointer]); assert(state == _arenas[pointer]);
assert(state != null); assert(state != null);
...@@ -215,4 +262,16 @@ class GestureArenaManager { ...@@ -215,4 +262,16 @@ class GestureArenaManager {
} }
member.acceptGesture(pointer); member.acceptGesture(pointer);
} }
bool _debugLogDiagnostic(int pointer, String message, [ _GestureArena state ]) {
assert(() {
if (debugPrintGestureArenaDiagnostics) {
final int count = state != null ? state.members.length : null;
final String s = count != 1 ? 's' : '';
debugPrint('Gesture arena ${pointer.toString().padRight(4)}$message${ count != null ? " with $count member$s." : ""}');
}
return true;
});
return true;
}
} }
...@@ -50,23 +50,27 @@ const double kDoubleTapSlop = 100.0; // Logical pixels ...@@ -50,23 +50,27 @@ const double kDoubleTapSlop = 100.0; // Logical pixels
/// displayed on the screen, from the moment they were last requested. /// displayed on the screen, from the moment they were last requested.
const Duration kZoomControlsTimeout = const Duration(milliseconds: 3000); const Duration kZoomControlsTimeout = const Duration(milliseconds: 3000);
/// The distance a touch has to travel for us to be confident that the gesture /// The distance a touch has to travel for the framework to be confident that
/// is a scroll gesture. /// the gesture is a scroll gesture, or, inversely, the maximum distance that a
const double kTouchSlop = 8.0; // Logical pixels /// touch can travel before the framework becomes confident that it is not a
/// tap.
/// The distance a touch has to travel for us to be confident that the gesture // This value was empirically derived. We started at 8.0 and increased it to
/// is a paging gesture. (Currently not used, because paging uses a regular drag // 18.0 after getting complaints that it was too difficult to hit targets.
/// gesture, which uses kTouchSlop.) const double kTouchSlop = 18.0; // Logical pixels
/// The distance a touch has to travel for the framework to be confident that
/// the gesture is a paging gesture. (Currently not used, because paging uses a
/// regular drag gesture, which uses kTouchSlop.)
// TODO(ianh): Create variants of HorizontalDragGestureRecognizer et al for // TODO(ianh): Create variants of HorizontalDragGestureRecognizer et al for
// paging, which use this constant. // paging, which use this constant.
const double kPagingTouchSlop = kTouchSlop * 2.0; // Logical pixels const double kPagingTouchSlop = kTouchSlop * 2.0; // Logical pixels
/// The distance a touch has to travel for us to be confident that the gesture /// The distance a touch has to travel for the framework to be confident that
/// is a panning gesture. /// the gesture is a panning gesture.
const double kPanSlop = kTouchSlop * 2.0; // Logical pixels const double kPanSlop = kTouchSlop * 2.0; // Logical pixels
/// The distance a touch has to travel for us to be confident that the gesture /// The distance a touch has to travel for the framework to be confident that
/// is a scale gesture. /// the gesture is a scale gesture.
const double kScaleSlop = kTouchSlop; // Logical pixels const double kScaleSlop = kTouchSlop; // Logical pixels
/// The margin around a dialog, popup menu, or other window-like widget inside /// The margin around a dialog, popup menu, or other window-like widget inside
......
...@@ -15,6 +15,31 @@ import 'package:flutter/foundation.dart'; ...@@ -15,6 +15,31 @@ import 'package:flutter/foundation.dart';
/// This has no effect in release builds. /// This has no effect in release builds.
bool debugPrintHitTestResults = false; bool debugPrintHitTestResults = false;
/// Prints information about gesture recognizers and gesture arenas.
///
/// This flag only has an effect in debug mode.
///
/// See also:
///
/// * [GestureArenaManager], the class that manages gesture arenas.
/// * [debugPrintRecognizerCallbacksTrace], for debugging issues with
/// gesture recognizers.
bool debugPrintGestureArenaDiagnostics = false;
/// Logs a message every time a gesture recognizer callback is invoked.
///
/// This flag only has an effect in debug mode.
///
/// This is specifically used by [GestureRecognizer.invokeCallback]. Gesture
/// recognizers that do not use this method to invoke callbacks may not honor
/// the [debugPrintRecognizerCallbacksTrace] flag.
///
/// See also:
///
/// * [debugPrintGestureArenaDiagnostics], for debugging issues with gesture
/// arenas.
bool debugPrintRecognizerCallbacksTrace = false;
/// Returns true if none of the gestures library debug variables have been changed. /// Returns true if none of the gestures library debug variables have been changed.
/// ///
/// This function is used by the test framework to ensure that debug variables /// This function is used by the test framework to ensure that debug variables
...@@ -24,7 +49,9 @@ bool debugPrintHitTestResults = false; ...@@ -24,7 +49,9 @@ bool debugPrintHitTestResults = false;
/// a complete list. /// a complete list.
bool debugAssertAllGesturesVarsUnset(String reason) { bool debugAssertAllGesturesVarsUnset(String reason) {
assert(() { assert(() {
if (debugPrintHitTestResults) if (debugPrintHitTestResults ||
debugPrintGestureArenaDiagnostics ||
debugPrintRecognizerCallbacksTrace)
throw new FlutterError(reason); throw new FlutterError(reason);
return true; return true;
}); });
......
...@@ -17,7 +17,7 @@ class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer { ...@@ -17,7 +17,7 @@ class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer {
/// Creates a long-press gesture recognizer. /// Creates a long-press gesture recognizer.
/// ///
/// Consider assigning the [onLongPress] callback after creating this object. /// Consider assigning the [onLongPress] callback after creating this object.
LongPressGestureRecognizer() : super(deadline: kLongPressTimeout); LongPressGestureRecognizer({ Object debugOwner }) : super(deadline: kLongPressTimeout, debugOwner: debugOwner);
/// Called when a long-press is recongized. /// Called when a long-press is recongized.
GestureLongPressCallback onLongPress; GestureLongPressCallback onLongPress;
......
...@@ -43,10 +43,13 @@ typedef void GestureDragCancelCallback(); ...@@ -43,10 +43,13 @@ typedef void GestureDragCancelCallback();
/// ///
/// See also: /// See also:
/// ///
/// * [HorizontalDragGestureRecognizer] /// * [HorizontalDragGestureRecognizer], for left and right drags.
/// * [VerticalDragGestureRecognizer] /// * [VerticalDragGestureRecognizer], for up and down drags.
/// * [PanGestureRecognizer] /// * [PanGestureRecognizer], for drags that are not locked to a single axis.
abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer { abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer {
/// Initialize the object.
DragGestureRecognizer({ Object debugOwner }) : super(debugOwner: debugOwner);
/// A pointer has contacted the screen and might begin to move. /// A pointer has contacted the screen and might begin to move.
/// ///
/// The position of the pointer is provided in the callback's `details` /// The position of the pointer is provided in the callback's `details`
...@@ -194,12 +197,18 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer { ...@@ -194,12 +197,18 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer {
invokeCallback<Null>('onEnd', () => onEnd(new DragEndDetails( // ignore: STRONG_MODE_INVALID_CAST_FUNCTION_EXPR, https://github.com/dart-lang/sdk/issues/27504 invokeCallback<Null>('onEnd', () => onEnd(new DragEndDetails( // ignore: STRONG_MODE_INVALID_CAST_FUNCTION_EXPR, https://github.com/dart-lang/sdk/issues/27504
velocity: velocity, velocity: velocity,
primaryVelocity: _getPrimaryValueFromOffset(velocity.pixelsPerSecond), primaryVelocity: _getPrimaryValueFromOffset(velocity.pixelsPerSecond),
))); )), debugReport: () {
return '$estimate; fling at $velocity.';
});
} else { } else {
invokeCallback<Null>('onEnd', () => onEnd(new DragEndDetails( // ignore: STRONG_MODE_INVALID_CAST_FUNCTION_EXPR, https://github.com/dart-lang/sdk/issues/27504 invokeCallback<Null>('onEnd', () => onEnd(new DragEndDetails( // ignore: STRONG_MODE_INVALID_CAST_FUNCTION_EXPR, https://github.com/dart-lang/sdk/issues/27504
velocity: Velocity.zero, velocity: Velocity.zero,
primaryVelocity: 0.0, primaryVelocity: 0.0,
))); )), debugReport: () {
if (estimate == null)
return 'Could not estimate velocity.';
return '$estimate; judged to not be a fling.';
});
} }
} }
_velocityTrackers.clear(); _velocityTrackers.clear();
...@@ -218,8 +227,14 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer { ...@@ -218,8 +227,14 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer {
/// ///
/// See also: /// See also:
/// ///
/// * [VerticalMultiDragGestureRecognizer] /// * [HorizontalDragGestureRecognizer], for a similar recognizer but for
/// horizontal movement.
/// * [MultiDragGestureRecognizer], for a family of gesture recognizers that
/// track each touch point independently.
class VerticalDragGestureRecognizer extends DragGestureRecognizer { class VerticalDragGestureRecognizer extends DragGestureRecognizer {
/// Create a gesture recognizer for interactions in the vertical axis.
VerticalDragGestureRecognizer({ Object debugOwner }) : super(debugOwner: debugOwner);
@override @override
bool _isFlingGesture(VelocityEstimate estimate) { bool _isFlingGesture(VelocityEstimate estimate) {
final double minVelocity = minFlingVelocity ?? kMinFlingVelocity; final double minVelocity = minFlingVelocity ?? kMinFlingVelocity;
...@@ -246,8 +261,14 @@ class VerticalDragGestureRecognizer extends DragGestureRecognizer { ...@@ -246,8 +261,14 @@ class VerticalDragGestureRecognizer extends DragGestureRecognizer {
/// ///
/// See also: /// See also:
/// ///
/// * [HorizontalMultiDragGestureRecognizer] /// * [VerticalDragGestureRecognizer], for a similar recognizer but for
/// vertical movement.
/// * [MultiDragGestureRecognizer], for a family of gesture recognizers that
/// track each touch point independently.
class HorizontalDragGestureRecognizer extends DragGestureRecognizer { class HorizontalDragGestureRecognizer extends DragGestureRecognizer {
/// Create a gesture recognizer for interactions in the horizontal axis.
HorizontalDragGestureRecognizer({ Object debugOwner }) : super(debugOwner: debugOwner);
@override @override
bool _isFlingGesture(VelocityEstimate estimate) { bool _isFlingGesture(VelocityEstimate estimate) {
final double minVelocity = minFlingVelocity ?? kMinFlingVelocity; final double minVelocity = minFlingVelocity ?? kMinFlingVelocity;
...@@ -272,15 +293,21 @@ class HorizontalDragGestureRecognizer extends DragGestureRecognizer { ...@@ -272,15 +293,21 @@ class HorizontalDragGestureRecognizer extends DragGestureRecognizer {
/// ///
/// See also: /// See also:
/// ///
/// * [ImmediateMultiDragGestureRecognizer] /// * [ImmediateMultiDragGestureRecognizer], for a similar recognizer that
/// * [DelayedMultiDragGestureRecognizer] /// tracks each touch point independently.
/// * [DelayedMultiDragGestureRecognizer], for a similar recognizer that
/// tracks each touch point independently, but that doesn't start until
/// some time has passed.
class PanGestureRecognizer extends DragGestureRecognizer { class PanGestureRecognizer extends DragGestureRecognizer {
/// Create a gesture recognizer for tracking movement on a plane.
PanGestureRecognizer({ Object debugOwner }) : super(debugOwner: debugOwner);
@override @override
bool _isFlingGesture(VelocityEstimate estimate) { bool _isFlingGesture(VelocityEstimate estimate) {
final double minVelocity = minFlingVelocity ?? kMinFlingVelocity; final double minVelocity = minFlingVelocity ?? kMinFlingVelocity;
final double minDistance = minFlingDistance ?? kTouchSlop; final double minDistance = minFlingDistance ?? kTouchSlop;
return estimate.pixelsPerSecond.distanceSquared > minVelocity * minVelocity && return estimate.pixelsPerSecond.distanceSquared > minVelocity * minVelocity
estimate.offset.distanceSquared > minDistance * minDistance; && estimate.offset.distanceSquared > minDistance * minDistance;
} }
@override @override
......
...@@ -169,11 +169,18 @@ abstract class MultiDragPointerState { ...@@ -169,11 +169,18 @@ abstract class MultiDragPointerState {
/// ///
/// See also: /// See also:
/// ///
/// * [HorizontalMultiDragGestureRecognizer] /// * [ImmediateMultiDragGestureRecognizer], the most straight-forward variant
/// * [VerticalMultiDragGestureRecognizer] /// of multi-pointer drag gesture recognizer.
/// * [ImmediateMultiDragGestureRecognizer] /// * [HorizontalMultiDragGestureRecognizer], which only recognizes drags that
/// * [DelayedMultiDragGestureRecognizer] /// start horizontally.
/// * [VerticalMultiDragGestureRecognizer], which only recognizes drags that
/// start vertically.
/// * [DelayedMultiDragGestureRecognizer], which only recognizes drags that
/// start after a long-press gesture.
abstract class MultiDragGestureRecognizer<T extends MultiDragPointerState> extends GestureRecognizer { abstract class MultiDragGestureRecognizer<T extends MultiDragPointerState> extends GestureRecognizer {
/// Initialize the object.
MultiDragGestureRecognizer({ @required Object debugOwner }) : super(debugOwner: debugOwner);
/// Called when this class recognizes the start of a drag gesture. /// Called when this class recognizes the start of a drag gesture.
/// ///
/// The remaining notifications for this drag gesture are delivered to the /// The remaining notifications for this drag gesture are delivered to the
...@@ -308,9 +315,18 @@ class _ImmediatePointerState extends MultiDragPointerState { ...@@ -308,9 +315,18 @@ class _ImmediatePointerState extends MultiDragPointerState {
/// ///
/// See also: /// See also:
/// ///
/// * [PanGestureRecognizer] /// * [PanGestureRecognizer], which recognizes only one drag gesture at a time,
/// * [DelayedMultiDragGestureRecognizer] /// regardless of how many fingers are involved.
/// * [HorizontalMultiDragGestureRecognizer], which only recognizes drags that
/// start horizontally.
/// * [VerticalMultiDragGestureRecognizer], which only recognizes drags that
/// start vertically.
/// * [DelayedMultiDragGestureRecognizer], which only recognizes drags that
/// start after a long-press gesture.
class ImmediateMultiDragGestureRecognizer extends MultiDragGestureRecognizer<_ImmediatePointerState> { class ImmediateMultiDragGestureRecognizer extends MultiDragGestureRecognizer<_ImmediatePointerState> {
/// Create a gesture recognizer for tracking multiple pointers at once.
ImmediateMultiDragGestureRecognizer({ Object debugOwner }) : super(debugOwner: debugOwner);
@override @override
_ImmediatePointerState createNewPointerState(PointerDownEvent event) { _ImmediatePointerState createNewPointerState(PointerDownEvent event) {
return new _ImmediatePointerState(event.position); return new _ImmediatePointerState(event.position);
...@@ -346,8 +362,17 @@ class _HorizontalPointerState extends MultiDragPointerState { ...@@ -346,8 +362,17 @@ class _HorizontalPointerState extends MultiDragPointerState {
/// ///
/// See also: /// See also:
/// ///
/// * [HorizontalDragGestureRecognizer] /// * [HorizontalDragGestureRecognizer], a gesture recognizer that just
/// looks at horizontal movement.
/// * [ImmediateMultiDragGestureRecognizer], a similar recognizer, but without
/// the limitation that the drag must start horizontally.
/// * [VerticalMultiDragGestureRecognizer], which only recognizes drags that
/// start vertically.
class HorizontalMultiDragGestureRecognizer extends MultiDragGestureRecognizer<_HorizontalPointerState> { class HorizontalMultiDragGestureRecognizer extends MultiDragGestureRecognizer<_HorizontalPointerState> {
/// Create a gesture recognizer for tracking multiple pointers at once
/// but only if they first move horizontally.
HorizontalMultiDragGestureRecognizer({ Object debugOwner }) : super(debugOwner: debugOwner);
@override @override
_HorizontalPointerState createNewPointerState(PointerDownEvent event) { _HorizontalPointerState createNewPointerState(PointerDownEvent event) {
return new _HorizontalPointerState(event.position); return new _HorizontalPointerState(event.position);
...@@ -383,8 +408,17 @@ class _VerticalPointerState extends MultiDragPointerState { ...@@ -383,8 +408,17 @@ class _VerticalPointerState extends MultiDragPointerState {
/// ///
/// See also: /// See also:
/// ///
/// * [VerticalDragGestureRecognizer] /// * [VerticalDragGestureRecognizer], a gesture recognizer that just
/// looks at vertical movement.
/// * [ImmediateMultiDragGestureRecognizer], a similar recognizer, but without
/// the limitation that the drag must start vertically.
/// * [HorizontalMultiDragGestureRecognizer], which only recognizes drags that
/// start horizontally.
class VerticalMultiDragGestureRecognizer extends MultiDragGestureRecognizer<_VerticalPointerState> { class VerticalMultiDragGestureRecognizer extends MultiDragGestureRecognizer<_VerticalPointerState> {
/// Create a gesture recognizer for tracking multiple pointers at once
/// but only if they first move vertically.
VerticalMultiDragGestureRecognizer({ Object debugOwner }) : super(debugOwner: debugOwner);
@override @override
_VerticalPointerState createNewPointerState(PointerDownEvent event) { _VerticalPointerState createNewPointerState(PointerDownEvent event) {
return new _VerticalPointerState(event.position); return new _VerticalPointerState(event.position);
...@@ -457,7 +491,8 @@ class _DelayedPointerState extends MultiDragPointerState { ...@@ -457,7 +491,8 @@ class _DelayedPointerState extends MultiDragPointerState {
} }
} }
/// Recognizes movement both horizontally and vertically on a per-pointer basis after a delay. /// Recognizes movement both horizontally and vertically on a per-pointer basis
/// after a delay.
/// ///
/// In constrast to [ImmediateMultiDragGestureRecognizer], /// In constrast to [ImmediateMultiDragGestureRecognizer],
/// [DelayedMultiDragGestureRecognizer] waits for a [delay] before recognizing /// [DelayedMultiDragGestureRecognizer] waits for a [delay] before recognizing
...@@ -470,8 +505,10 @@ class _DelayedPointerState extends MultiDragPointerState { ...@@ -470,8 +505,10 @@ class _DelayedPointerState extends MultiDragPointerState {
/// ///
/// See also: /// See also:
/// ///
/// * [PanGestureRecognizer] /// * [ImmediateMultiDragGestureRecognizer], a similar recognizer but without
/// * [ImmediateMultiDragGestureRecognizer] /// the delay.
/// * [PanGestureRecognizer], which recognizes only one drag gesture at a time,
/// regardless of how many fingers are involved.
class DelayedMultiDragGestureRecognizer extends MultiDragGestureRecognizer<_DelayedPointerState> { class DelayedMultiDragGestureRecognizer extends MultiDragGestureRecognizer<_DelayedPointerState> {
/// Creates a drag recognizer that works on a per-pointer basis after a delay. /// Creates a drag recognizer that works on a per-pointer basis after a delay.
/// ///
...@@ -480,8 +517,10 @@ class DelayedMultiDragGestureRecognizer extends MultiDragGestureRecognizer<_Dela ...@@ -480,8 +517,10 @@ class DelayedMultiDragGestureRecognizer extends MultiDragGestureRecognizer<_Dela
/// defaults to [kLongPressTimeout] to match [LongPressGestureRecognizer] but /// defaults to [kLongPressTimeout] to match [LongPressGestureRecognizer] but
/// can be changed for specific behaviors. /// can be changed for specific behaviors.
DelayedMultiDragGestureRecognizer({ DelayedMultiDragGestureRecognizer({
this.delay: kLongPressTimeout this.delay: kLongPressTimeout,
}) : assert(delay != null); Object debugOwner,
}) : assert(delay != null),
super(debugOwner: debugOwner);
/// The amount of time the pointer must remain in the same place for the drag /// The amount of time the pointer must remain in the same place for the drag
/// to be recognized. /// to be recognized.
......
...@@ -68,6 +68,9 @@ class _TapTracker { ...@@ -68,6 +68,9 @@ class _TapTracker {
/// Recognizes when the user has tapped the screen at the same location twice in /// Recognizes when the user has tapped the screen at the same location twice in
/// quick succession. /// quick succession.
class DoubleTapGestureRecognizer extends GestureRecognizer { class DoubleTapGestureRecognizer extends GestureRecognizer {
/// Create a gesture recognizer for double taps.
DoubleTapGestureRecognizer({ Object debugOwner }) : super(debugOwner: debugOwner);
// Implementation notes: // Implementation notes:
// The double tap recognizer can be in one of four states. There's no // The double tap recognizer can be in one of four states. There's no
// explicit enum for the states, because they are already captured by // explicit enum for the states, because they are already captured by
...@@ -315,8 +318,9 @@ class MultiTapGestureRecognizer extends GestureRecognizer { ...@@ -315,8 +318,9 @@ class MultiTapGestureRecognizer extends GestureRecognizer {
/// The [longTapDelay] defaults to [Duration.ZERO], which means /// The [longTapDelay] defaults to [Duration.ZERO], which means
/// [onLongTapDown] is called immediately after [onTapDown]. /// [onLongTapDown] is called immediately after [onTapDown].
MultiTapGestureRecognizer({ MultiTapGestureRecognizer({
this.longTapDelay: Duration.ZERO this.longTapDelay: Duration.ZERO,
}); Object debugOwner,
}) : super(debugOwner: debugOwner);
/// A pointer that might cause a tap has contacted the screen at a particular /// A pointer that might cause a tap has contacted the screen at a particular
/// location. /// location.
......
...@@ -11,6 +11,7 @@ import 'package:flutter/foundation.dart'; ...@@ -11,6 +11,7 @@ import 'package:flutter/foundation.dart';
import 'arena.dart'; import 'arena.dart';
import 'binding.dart'; import 'binding.dart';
import 'constants.dart'; import 'constants.dart';
import 'debug.dart';
import 'events.dart'; import 'events.dart';
import 'pointer_router.dart'; import 'pointer_router.dart';
import 'team.dart'; import 'team.dart';
...@@ -32,7 +33,21 @@ typedef T RecognizerCallback<T>(); ...@@ -32,7 +33,21 @@ typedef T RecognizerCallback<T>();
/// See also: /// See also:
/// ///
/// * [GestureDetector], the widget that is used to detect gestures. /// * [GestureDetector], the widget that is used to detect gestures.
abstract class GestureRecognizer extends GestureArenaMember { /// * [debugPrintRecognizerCallbacksTrace], a flag that can be set to help
/// debug issues with gesture recognizers.
abstract class GestureRecognizer extends GestureArenaMember with TreeDiagnosticsMixin {
/// Initializes the gesture recognizer.
///
/// The argument is optional and is only used for debug purposes (e.g. in the
/// [toString] serialization).
GestureRecognizer({ this.debugOwner });
/// The recognizer's owner.
///
/// This is used in the [toString] serialization to report the object for which
/// this gesture recognizer was created, to aid in debugging.
final Object debugOwner;
/// Registers a new pointer that might be relevant to this gesture /// Registers a new pointer that might be relevant to this gesture
/// detector. /// detector.
/// ///
...@@ -58,16 +73,32 @@ abstract class GestureRecognizer extends GestureArenaMember { ...@@ -58,16 +73,32 @@ abstract class GestureRecognizer extends GestureArenaMember {
/// Returns a very short pretty description of the gesture that the /// Returns a very short pretty description of the gesture that the
/// recognizer looks for, like 'tap' or 'horizontal drag'. /// recognizer looks for, like 'tap' or 'horizontal drag'.
String toStringShort() => toString(); String toStringShort();
/// Invoke a callback provided by the application, catching and logging any /// Invoke a callback provided by the application, catching and logging any
/// exceptions. /// exceptions.
/// ///
/// The `name` argument is ignored except when reporting exceptions. /// The `name` argument is ignored except when reporting exceptions.
///
/// The `debugReport` argument is optional and is used when
/// [debugPrintRecognizerCallbacksTrace] is true. If specified, it must be a
/// callback that returns a string describing useful debugging information,
/// e.g. the arguments passed to the callback.
@protected @protected
T invokeCallback<T>(String name, RecognizerCallback<T> callback) { T invokeCallback<T>(String name, RecognizerCallback<T> callback, { String debugReport() }) {
assert(callback != null);
T result; T result;
try { try {
assert(() {
if (debugPrintRecognizerCallbacksTrace) {
final String report = debugReport != null ? debugReport() : null;
// The 19 in the line below is the width of the prefix used by
// _debugLogDiagnostic in arena.dart.
final String prefix = debugPrintGestureArenaDiagnostics ? ' ' * 19 + '❙ ' : '';
debugPrint('$prefix$this calling $name callback.${ report?.isNotEmpty == true ? " $report" : "" }');
}
return true;
});
result = callback(); result = callback();
} catch (exception, stack) { } catch (exception, stack) {
FlutterError.reportError(new FlutterErrorDetails( FlutterError.reportError(new FlutterErrorDetails(
...@@ -86,7 +117,21 @@ abstract class GestureRecognizer extends GestureArenaMember { ...@@ -86,7 +117,21 @@ abstract class GestureRecognizer extends GestureArenaMember {
} }
@override @override
String toString() => describeIdentity(this); void debugFillProperties(List<DiagnosticsNode> description) {
super.debugFillProperties(description);
description.add(new DiagnosticsProperty<Object>('debugOwner', debugOwner, defaultValue: null));
}
@override
String toString() {
final String name = describeIdentity(this);
List<DiagnosticsNode> data = <DiagnosticsNode>[];
debugFillProperties(data);
data = data.where((DiagnosticsNode n) => !n.hidden).toList();
if (data.isEmpty)
return '$name';
return '$name(${data.join("; ")})';
}
} }
/// Base class for gesture recognizers that can only recognize one /// Base class for gesture recognizers that can only recognize one
...@@ -98,6 +143,9 @@ abstract class GestureRecognizer extends GestureArenaMember { ...@@ -98,6 +143,9 @@ abstract class GestureRecognizer extends GestureArenaMember {
/// which manages each pointer independently and can consider multiple /// which manages each pointer independently and can consider multiple
/// simultaneous touches to each result in a separate tap. /// simultaneous touches to each result in a separate tap.
abstract class OneSequenceGestureRecognizer extends GestureRecognizer { abstract class OneSequenceGestureRecognizer extends GestureRecognizer {
/// Initialize the object.
OneSequenceGestureRecognizer({ Object debugOwner }) : super(debugOwner: debugOwner);
final Map<int, GestureArenaEntry> _entries = <int, GestureArenaEntry>{}; final Map<int, GestureArenaEntry> _entries = <int, GestureArenaEntry>{};
final Set<int> _trackedPointers = new HashSet<int>(); final Set<int> _trackedPointers = new HashSet<int>();
...@@ -227,9 +275,15 @@ enum GestureRecognizerState { ...@@ -227,9 +275,15 @@ enum GestureRecognizerState {
} }
/// A base class for gesture recognizers that track a single primary pointer. /// A base class for gesture recognizers that track a single primary pointer.
///
/// Gestures based on this class will reject the gesture if the primary pointer
/// travels beyond [kTouchSlop] pixels from the original contact point.
abstract class PrimaryPointerGestureRecognizer extends OneSequenceGestureRecognizer { abstract class PrimaryPointerGestureRecognizer extends OneSequenceGestureRecognizer {
/// Initializes the [deadline] field during construction of subclasses. /// Initializes the [deadline] field during construction of subclasses.
PrimaryPointerGestureRecognizer({ this.deadline }); PrimaryPointerGestureRecognizer({
this.deadline,
Object debugOwner,
}) : super(debugOwner: debugOwner);
/// If non-null, the recognizer will call [didExceedDeadline] after this /// If non-null, the recognizer will call [didExceedDeadline] after this
/// amount of time has elapsed since starting to track the primary pointer. /// amount of time has elapsed since starting to track the primary pointer.
...@@ -321,5 +375,8 @@ abstract class PrimaryPointerGestureRecognizer extends OneSequenceGestureRecogni ...@@ -321,5 +375,8 @@ abstract class PrimaryPointerGestureRecognizer extends OneSequenceGestureRecogni
} }
@override @override
String toString() => '${describeIdentity(this)}($state)'; void debugFillProperties(List<DiagnosticsNode> description) {
super.debugFillProperties(description);
description.add(new EnumProperty<GestureRecognizerState>('state', state));
}
} }
...@@ -107,6 +107,9 @@ bool _isFlingGesture(Velocity velocity) { ...@@ -107,6 +107,9 @@ bool _isFlingGesture(Velocity velocity) {
/// change, the recognizer calls [onUpdate]. When the pointers are no longer in /// change, the recognizer calls [onUpdate]. When the pointers are no longer in
/// contact with the screen, the recognizer calls [onEnd]. /// contact with the screen, the recognizer calls [onEnd].
class ScaleGestureRecognizer extends OneSequenceGestureRecognizer { class ScaleGestureRecognizer extends OneSequenceGestureRecognizer {
/// Create a gesture recognizer for interactions intended for scaling content.
ScaleGestureRecognizer({ Object debugOwner }) : super(debugOwner: debugOwner);
/// The pointers in contact with the screen have established a focal point and /// The pointers in contact with the screen have established a focal point and
/// initial scale of 1.0. /// initial scale of 1.0.
GestureScaleStartCallback onStart; GestureScaleStartCallback onStart;
......
...@@ -64,7 +64,7 @@ typedef void GestureTapCancelCallback(); ...@@ -64,7 +64,7 @@ typedef void GestureTapCancelCallback();
/// * [MultiTapGestureRecognizer] /// * [MultiTapGestureRecognizer]
class TapGestureRecognizer extends PrimaryPointerGestureRecognizer { class TapGestureRecognizer extends PrimaryPointerGestureRecognizer {
/// Creates a tap gesture recognizer. /// Creates a tap gesture recognizer.
TapGestureRecognizer() : super(deadline: kPressTimeout); TapGestureRecognizer({ Object debugOwner }) : super(deadline: kPressTimeout, debugOwner: debugOwner);
/// A pointer that might cause a tap has contacted the screen at a particular /// A pointer that might cause a tap has contacted the screen at a particular
/// location. /// location.
......
...@@ -121,7 +121,7 @@ class VelocityEstimate { ...@@ -121,7 +121,7 @@ class VelocityEstimate {
final Offset offset; final Offset offset;
@override @override
String toString() => 'VelocityEstimate(${pixelsPerSecond.dx.toStringAsFixed(1)}, ${pixelsPerSecond.dy.toStringAsFixed(1)})'; String toString() => 'VelocityEstimate(${pixelsPerSecond.dx.toStringAsFixed(1)}, ${pixelsPerSecond.dy.toStringAsFixed(1)}; offset: $offset, duration: $duration, confidence: ${confidence.toStringAsFixed(1)})';
} }
class _PointAtTime { class _PointAtTime {
......
...@@ -1605,7 +1605,7 @@ class BoxDecoration extends Decoration { ...@@ -1605,7 +1605,7 @@ class BoxDecoration extends Decoration {
DiagnosticsNode toDiagnosticsNode({ String name, DiagnosticsTreeStyle style: DiagnosticsTreeStyle.whitespace }) { DiagnosticsNode toDiagnosticsNode({ String name, DiagnosticsTreeStyle style: DiagnosticsTreeStyle.whitespace }) {
return new DiagnosticsNode.lazy( return new DiagnosticsNode.lazy(
name: name, name: name,
object: this, value: this,
description: '', description: '',
style: style, style: style,
emptyBodyDescription: '<no decorations specified>', emptyBodyDescription: '<no decorations specified>',
......
...@@ -215,7 +215,7 @@ class FlutterLogoDecoration extends Decoration { ...@@ -215,7 +215,7 @@ class FlutterLogoDecoration extends Decoration {
return new DiagnosticsNode.lazy( return new DiagnosticsNode.lazy(
name: name, name: name,
description: '$runtimeType', description: '$runtimeType',
object: this, value: this,
style: style, style: style,
fillProperties: (List<DiagnosticsNode> properties) { fillProperties: (List<DiagnosticsNode> properties) {
properties.add(new DiagnosticsNode.message('$lightColor/$darkColor on $textColor')); properties.add(new DiagnosticsNode.message('$lightColor/$darkColor on $textColor'));
......
...@@ -344,7 +344,7 @@ class TextSpan implements TreeDiagnostics { ...@@ -344,7 +344,7 @@ class TextSpan implements TreeDiagnostics {
}) { }) {
return new DiagnosticsNode.lazy( return new DiagnosticsNode.lazy(
name: name, name: name,
object: this, value: this,
description: '$runtimeType', description: '$runtimeType',
style: style, style: style,
fillProperties: (List<DiagnosticsNode> properties) { fillProperties: (List<DiagnosticsNode> properties) {
......
...@@ -462,7 +462,7 @@ class TextStyle extends TreeDiagnostics { ...@@ -462,7 +462,7 @@ class TextStyle extends TreeDiagnostics {
}) { }) {
return new DiagnosticsNode.lazy( return new DiagnosticsNode.lazy(
name: name, name: name,
object: this, value: this,
style: style, style: style,
description: '$runtimeType', description: '$runtimeType',
fillProperties: debugFillProperties, fillProperties: debugFillProperties,
......
...@@ -169,6 +169,7 @@ List<String> debugDescribeTransform(Matrix4 transform) { ...@@ -169,6 +169,7 @@ List<String> debugDescribeTransform(Matrix4 transform) {
/// Property which handles [Matrix4] that represent transforms. /// Property which handles [Matrix4] that represent transforms.
class TransformProperty extends DiagnosticsProperty<Matrix4> { class TransformProperty extends DiagnosticsProperty<Matrix4> {
/// Create a diagnostics property for [Matrix4] objects.
TransformProperty(String name, Matrix4 value, { TransformProperty(String name, Matrix4 value, {
Object defaultValue: kNoDefaultValue, Object defaultValue: kNoDefaultValue,
}) : super( }) : super(
......
...@@ -118,11 +118,11 @@ class RenderEditable extends RenderBox { ...@@ -118,11 +118,11 @@ class RenderEditable extends RenderBox {
_offset = offset { _offset = offset {
assert(_showCursor != null); assert(_showCursor != null);
assert(!_showCursor.value || cursorColor != null); assert(!_showCursor.value || cursorColor != null);
_tap = new TapGestureRecognizer() _tap = new TapGestureRecognizer(debugOwner: this)
..onTapDown = _handleTapDown ..onTapDown = _handleTapDown
..onTap = _handleTap ..onTap = _handleTap
..onTapCancel = _handleTapCancel; ..onTapCancel = _handleTapCancel;
_longPress = new LongPressGestureRecognizer() _longPress = new LongPressGestureRecognizer(debugOwner: this)
..onLongPress = _handleLongPress; ..onLongPress = _handleLongPress;
} }
......
...@@ -636,7 +636,7 @@ class SliverGeometry implements TreeDiagnostics { ...@@ -636,7 +636,7 @@ class SliverGeometry implements TreeDiagnostics {
}) { }) {
return new DiagnosticsNode.lazy( return new DiagnosticsNode.lazy(
name: name, name: name,
object: this, value: this,
description: 'SliverGeometry', description: 'SliverGeometry',
style: style, style: style,
emptyBodyDescription: '<no decorations specified>', emptyBodyDescription: '<no decorations specified>',
......
...@@ -3341,7 +3341,7 @@ abstract class Element implements BuildContext, TreeDiagnostics { ...@@ -3341,7 +3341,7 @@ abstract class Element implements BuildContext, TreeDiagnostics {
DiagnosticsNode toDiagnosticsNode({ String name, DiagnosticsTreeStyle style }) { DiagnosticsNode toDiagnosticsNode({ String name, DiagnosticsTreeStyle style }) {
return new DiagnosticsNode.lazy( return new DiagnosticsNode.lazy(
name: name, name: name,
object: this, value: this,
getChildren: () { getChildren: () {
final List<DiagnosticsNode> children = <DiagnosticsNode>[]; final List<DiagnosticsNode> children = <DiagnosticsNode>[];
visitChildren((Element child) { visitChildren((Element child) {
......
...@@ -296,7 +296,7 @@ class GestureDetector extends StatelessWidget { ...@@ -296,7 +296,7 @@ class GestureDetector extends StatelessWidget {
if (onTapDown != null || onTapUp != null || onTap != null || onTapCancel != null) { if (onTapDown != null || onTapUp != null || onTap != null || onTapCancel != null) {
gestures[TapGestureRecognizer] = new GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>( gestures[TapGestureRecognizer] = new GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
() => new TapGestureRecognizer(), () => new TapGestureRecognizer(debugOwner: this),
(TapGestureRecognizer instance) { (TapGestureRecognizer instance) {
instance instance
..onTapDown = onTapDown ..onTapDown = onTapDown
...@@ -309,7 +309,7 @@ class GestureDetector extends StatelessWidget { ...@@ -309,7 +309,7 @@ class GestureDetector extends StatelessWidget {
if (onDoubleTap != null) { if (onDoubleTap != null) {
gestures[DoubleTapGestureRecognizer] = new GestureRecognizerFactoryWithHandlers<DoubleTapGestureRecognizer>( gestures[DoubleTapGestureRecognizer] = new GestureRecognizerFactoryWithHandlers<DoubleTapGestureRecognizer>(
() => new DoubleTapGestureRecognizer(), () => new DoubleTapGestureRecognizer(debugOwner: this),
(DoubleTapGestureRecognizer instance) { (DoubleTapGestureRecognizer instance) {
instance instance
..onDoubleTap = onDoubleTap; ..onDoubleTap = onDoubleTap;
...@@ -319,7 +319,7 @@ class GestureDetector extends StatelessWidget { ...@@ -319,7 +319,7 @@ class GestureDetector extends StatelessWidget {
if (onLongPress != null) { if (onLongPress != null) {
gestures[LongPressGestureRecognizer] = new GestureRecognizerFactoryWithHandlers<LongPressGestureRecognizer>( gestures[LongPressGestureRecognizer] = new GestureRecognizerFactoryWithHandlers<LongPressGestureRecognizer>(
() => new LongPressGestureRecognizer(), () => new LongPressGestureRecognizer(debugOwner: this),
(LongPressGestureRecognizer instance) { (LongPressGestureRecognizer instance) {
instance instance
..onLongPress = onLongPress; ..onLongPress = onLongPress;
...@@ -333,7 +333,7 @@ class GestureDetector extends StatelessWidget { ...@@ -333,7 +333,7 @@ class GestureDetector extends StatelessWidget {
onVerticalDragEnd != null || onVerticalDragEnd != null ||
onVerticalDragCancel != null) { onVerticalDragCancel != null) {
gestures[VerticalDragGestureRecognizer] = new GestureRecognizerFactoryWithHandlers<VerticalDragGestureRecognizer>( gestures[VerticalDragGestureRecognizer] = new GestureRecognizerFactoryWithHandlers<VerticalDragGestureRecognizer>(
() => new VerticalDragGestureRecognizer(), () => new VerticalDragGestureRecognizer(debugOwner: this),
(VerticalDragGestureRecognizer instance) { (VerticalDragGestureRecognizer instance) {
instance instance
..onDown = onVerticalDragDown ..onDown = onVerticalDragDown
...@@ -351,7 +351,7 @@ class GestureDetector extends StatelessWidget { ...@@ -351,7 +351,7 @@ class GestureDetector extends StatelessWidget {
onHorizontalDragEnd != null || onHorizontalDragEnd != null ||
onHorizontalDragCancel != null) { onHorizontalDragCancel != null) {
gestures[HorizontalDragGestureRecognizer] = new GestureRecognizerFactoryWithHandlers<HorizontalDragGestureRecognizer>( gestures[HorizontalDragGestureRecognizer] = new GestureRecognizerFactoryWithHandlers<HorizontalDragGestureRecognizer>(
() => new HorizontalDragGestureRecognizer(), () => new HorizontalDragGestureRecognizer(debugOwner: this),
(HorizontalDragGestureRecognizer instance) { (HorizontalDragGestureRecognizer instance) {
instance instance
..onDown = onHorizontalDragDown ..onDown = onHorizontalDragDown
...@@ -369,7 +369,7 @@ class GestureDetector extends StatelessWidget { ...@@ -369,7 +369,7 @@ class GestureDetector extends StatelessWidget {
onPanEnd != null || onPanEnd != null ||
onPanCancel != null) { onPanCancel != null) {
gestures[PanGestureRecognizer] = new GestureRecognizerFactoryWithHandlers<PanGestureRecognizer>( gestures[PanGestureRecognizer] = new GestureRecognizerFactoryWithHandlers<PanGestureRecognizer>(
() => new PanGestureRecognizer(), () => new PanGestureRecognizer(debugOwner: this),
(PanGestureRecognizer instance) { (PanGestureRecognizer instance) {
instance instance
..onDown = onPanDown ..onDown = onPanDown
...@@ -383,7 +383,7 @@ class GestureDetector extends StatelessWidget { ...@@ -383,7 +383,7 @@ class GestureDetector extends StatelessWidget {
if (onScaleStart != null || onScaleUpdate != null || onScaleEnd != null) { if (onScaleStart != null || onScaleUpdate != null || onScaleEnd != null) {
gestures[ScaleGestureRecognizer] = new GestureRecognizerFactoryWithHandlers<ScaleGestureRecognizer>( gestures[ScaleGestureRecognizer] = new GestureRecognizerFactoryWithHandlers<ScaleGestureRecognizer>(
() => new ScaleGestureRecognizer(), () => new ScaleGestureRecognizer(debugOwner: this),
(ScaleGestureRecognizer instance) { (ScaleGestureRecognizer instance) {
instance instance
..onStart = onScaleStart ..onStart = onScaleStart
...@@ -397,7 +397,7 @@ class GestureDetector extends StatelessWidget { ...@@ -397,7 +397,7 @@ class GestureDetector extends StatelessWidget {
gestures: gestures, gestures: gestures,
behavior: behavior, behavior: behavior,
excludeFromSemantics: excludeFromSemantics, excludeFromSemantics: excludeFromSemantics,
child: child child: child,
); );
} }
} }
...@@ -622,6 +622,7 @@ class RawGestureDetectorState extends State<RawGestureDetector> { ...@@ -622,6 +622,7 @@ class RawGestureDetectorState extends State<RawGestureDetector> {
if (gestures.isEmpty) if (gestures.isEmpty)
gestures.add('<none>'); gestures.add('<none>');
description.add(new IterableProperty<String>('gestures', gestures)); description.add(new IterableProperty<String>('gestures', gestures));
description.add(new IterableProperty<GestureRecognizer>('recognizers', _recognizers.values, hidden: true));
} }
description.add(new EnumProperty<HitTestBehavior>('behavior', widget.behavior, defaultValue: null)); description.add(new EnumProperty<HitTestBehavior>('behavior', widget.behavior, defaultValue: null));
} }
......
...@@ -85,6 +85,12 @@ abstract class TextSelectionControls { ...@@ -85,6 +85,12 @@ abstract class TextSelectionControls {
/// Returns the size of the selection handle. /// Returns the size of the selection handle.
Size get handleSize; Size get handleSize;
/// Copy the current selection of the text field managed by the given
/// `delegate` to the [Clipboard]. Then, remove the selected text from the
/// text field and hide the toolbar.
///
/// This is called by subclasses when their cut affordance is activated by
/// the user.
void handleCut(TextSelectionDelegate delegate) { void handleCut(TextSelectionDelegate delegate) {
final TextEditingValue value = delegate.textEditingValue; final TextEditingValue value = delegate.textEditingValue;
Clipboard.setData(new ClipboardData( Clipboard.setData(new ClipboardData(
...@@ -100,6 +106,12 @@ abstract class TextSelectionControls { ...@@ -100,6 +106,12 @@ abstract class TextSelectionControls {
delegate.hideToolbar(); delegate.hideToolbar();
} }
/// Copy the current selection of the text field managed by the given
/// `delegate` to the [Clipboard]. Then, move the cursor to the end of the
/// text (collapsing the selection in the process), and hide the toolbar.
///
/// This is called by subclasses when their copy affordance is activated by
/// the user.
void handleCopy(TextSelectionDelegate delegate) { void handleCopy(TextSelectionDelegate delegate) {
final TextEditingValue value = delegate.textEditingValue; final TextEditingValue value = delegate.textEditingValue;
Clipboard.setData(new ClipboardData( Clipboard.setData(new ClipboardData(
...@@ -112,6 +124,17 @@ abstract class TextSelectionControls { ...@@ -112,6 +124,17 @@ abstract class TextSelectionControls {
delegate.hideToolbar(); delegate.hideToolbar();
} }
/// Paste the current clipboard selection (obtained from [Clipboard]) into
/// the text field managed by the given `delegate`, replacing its current
/// selection, if any. Then, hide the toolbar.
///
/// This is called by subclasses when their paste affordance is activated by
/// the user.
///
/// This function is asynchronous since interacting with the clipboard is
/// asynchronous. Race conditions may exist with this API as currently
/// implemented.
// TODO(ianh): https://github.com/flutter/flutter/issues/11427
Future<Null> handlePaste(TextSelectionDelegate delegate) async { Future<Null> handlePaste(TextSelectionDelegate delegate) async {
final TextEditingValue value = delegate.textEditingValue; // Snapshot the input before using `await`. final TextEditingValue value = delegate.textEditingValue; // Snapshot the input before using `await`.
final ClipboardData data = await Clipboard.getData(Clipboard.kTextPlain); final ClipboardData data = await Clipboard.getData(Clipboard.kTextPlain);
...@@ -128,6 +151,13 @@ abstract class TextSelectionControls { ...@@ -128,6 +151,13 @@ abstract class TextSelectionControls {
delegate.hideToolbar(); delegate.hideToolbar();
} }
/// Adjust the selection of the text field managed by the given `delegate` so
/// that everything is selected.
///
/// Does not hide the toolbar.
///
/// This is called by subclasses when their select-all affordance is activated
/// by the user.
void handleSelectAll(TextSelectionDelegate delegate) { void handleSelectAll(TextSelectionDelegate delegate) {
delegate.textEditingValue = new TextEditingValue( delegate.textEditingValue = new TextEditingValue(
text: delegate.textEditingValue.text, text: delegate.textEditingValue.text,
......
...@@ -641,9 +641,9 @@ void main() { ...@@ -641,9 +641,9 @@ void main() {
final DiagnosticsProperty<bool> falseProperty = new DiagnosticsProperty<bool>('name', false); final DiagnosticsProperty<bool> falseProperty = new DiagnosticsProperty<bool>('name', false);
expect(trueProperty.toString(), equals('name: true')); expect(trueProperty.toString(), equals('name: true'));
expect(trueProperty.hidden, isFalse); expect(trueProperty.hidden, isFalse);
expect(trueProperty.object, isTrue); expect(trueProperty.value, isTrue);
expect(falseProperty.toString(), equals('name: false')); expect(falseProperty.toString(), equals('name: false'));
expect(falseProperty.object, isFalse); expect(falseProperty.value, isFalse);
expect(falseProperty.hidden, isFalse); expect(falseProperty.hidden, isFalse);
expect( expect(
new DiagnosticsProperty<bool>( new DiagnosticsProperty<bool>(
...@@ -680,8 +680,8 @@ void main() { ...@@ -680,8 +680,8 @@ void main() {
); );
expect(trueFlag.toString(), equals('myFlag')); expect(trueFlag.toString(), equals('myFlag'));
expect(trueFlag.object, isTrue); expect(trueFlag.value, isTrue);
expect(falseFlag.object, isFalse); expect(falseFlag.value, isFalse);
expect(trueFlag.hidden, isFalse); expect(trueFlag.hidden, isFalse);
expect(falseFlag.hidden, isTrue); expect(falseFlag.hidden, isTrue);
...@@ -697,7 +697,7 @@ void main() { ...@@ -697,7 +697,7 @@ void main() {
withTooltip.toString(), withTooltip.toString(),
equals('name: value (tooltip)'), equals('name: value (tooltip)'),
); );
expect(withTooltip.object, equals('value')); expect(withTooltip.value, equals('value'));
expect(withTooltip.hidden, isFalse); expect(withTooltip.hidden, isFalse);
}); });
...@@ -708,7 +708,7 @@ void main() { ...@@ -708,7 +708,7 @@ void main() {
); );
expect(doubleProperty.toString(), equals('name: 42.0')); expect(doubleProperty.toString(), equals('name: 42.0'));
expect(doubleProperty.hidden, isFalse); expect(doubleProperty.hidden, isFalse);
expect(doubleProperty.object, equals(42.0)); expect(doubleProperty.value, equals(42.0));
expect(new DoubleProperty('name', 1.3333).toString(), equals('name: 1.3')); expect(new DoubleProperty('name', 1.3333).toString(), equals('name: 1.3'));
...@@ -735,7 +735,7 @@ void main() { ...@@ -735,7 +735,7 @@ void main() {
); );
expect(safe.toString(), equals('name: 42.0')); expect(safe.toString(), equals('name: 42.0'));
expect(safe.hidden, isFalse); expect(safe.hidden, isFalse);
expect(safe.object, equals(42.0)); expect(safe.value, equals(42.0));
expect( expect(
new DoubleProperty.lazy('name', () => 1.3333).toString(), new DoubleProperty.lazy('name', () => 1.3333).toString(),
...@@ -757,7 +757,7 @@ void main() { ...@@ -757,7 +757,7 @@ void main() {
); );
// TODO(jacobr): it would be better if throwingProperty.object threw an // TODO(jacobr): it would be better if throwingProperty.object threw an
// exception. // exception.
expect(throwingProperty.object, isNull); expect(throwingProperty.value, isNull);
expect(throwingProperty.hidden, isFalse); expect(throwingProperty.hidden, isFalse);
expect( expect(
throwingProperty.toString(), throwingProperty.toString(),
...@@ -782,7 +782,7 @@ void main() { ...@@ -782,7 +782,7 @@ void main() {
); );
expect( expect(
new PercentProperty('name', 0.4).object, new PercentProperty('name', 0.4).value,
0.4, 0.4,
); );
expect( expect(
...@@ -847,7 +847,7 @@ void main() { ...@@ -847,7 +847,7 @@ void main() {
expect(present.toString(), equals('clickable')); expect(present.toString(), equals('clickable'));
expect(present.hidden, isFalse); expect(present.hidden, isFalse);
expect(present.object, equals(onClick)); expect(present.value, equals(onClick));
expect(missing.toString(), equals('')); expect(missing.toString(), equals(''));
expect(missing.hidden, isTrue); expect(missing.hidden, isTrue);
}); });
...@@ -867,7 +867,7 @@ void main() { ...@@ -867,7 +867,7 @@ void main() {
expect(present.toString(), equals('')); expect(present.toString(), equals(''));
expect(present.hidden, isTrue); expect(present.hidden, isTrue);
expect(present.object, equals(onClick)); expect(present.value, equals(onClick));
expect(missing.toString(), equals('disabled')); expect(missing.toString(), equals('disabled'));
expect(missing.hidden, isFalse); expect(missing.hidden, isFalse);
}); });
...@@ -889,10 +889,10 @@ void main() { ...@@ -889,10 +889,10 @@ void main() {
); );
expect(yes.toString(), equals('name: YES')); expect(yes.toString(), equals('name: YES'));
expect(yes.hidden, isFalse); expect(yes.hidden, isFalse);
expect(yes.object, isTrue); expect(yes.value, isTrue);
expect(no.toString(), equals('name: NO')); expect(no.toString(), equals('name: NO'));
expect(no.hidden, isFalse); expect(no.hidden, isFalse);
expect(no.object, isFalse); expect(no.value, isFalse);
expect( expect(
new FlagProperty( new FlagProperty(
...@@ -945,19 +945,19 @@ void main() { ...@@ -945,19 +945,19 @@ void main() {
null, null,
); );
expect(hello.hidden, isFalse); expect(hello.hidden, isFalse);
expect(hello.object, equals(ExampleEnum.hello)); expect(hello.value, equals(ExampleEnum.hello));
expect(hello.toString(), equals('name: hello')); expect(hello.toString(), equals('name: hello'));
expect(world.hidden, isFalse); expect(world.hidden, isFalse);
expect(world.object, equals(ExampleEnum.world)); expect(world.value, equals(ExampleEnum.world));
expect(world.toString(), equals('name: world')); expect(world.toString(), equals('name: world'));
expect(deferToChild.hidden, isFalse); expect(deferToChild.hidden, isFalse);
expect(deferToChild.object, equals(ExampleEnum.deferToChild)); expect(deferToChild.value, equals(ExampleEnum.deferToChild));
expect(deferToChild.toString(), equals('name: defer-to-child')); expect(deferToChild.toString(), equals('name: defer-to-child'));
expect(nullEnum.hidden, isFalse); expect(nullEnum.hidden, isFalse);
expect(nullEnum.object, isNull); expect(nullEnum.value, isNull);
expect(nullEnum.toString(), equals('name: null')); expect(nullEnum.toString(), equals('name: null'));
final EnumProperty<ExampleEnum> matchesDefault = new EnumProperty<ExampleEnum>( final EnumProperty<ExampleEnum> matchesDefault = new EnumProperty<ExampleEnum>(
...@@ -966,7 +966,7 @@ void main() { ...@@ -966,7 +966,7 @@ void main() {
defaultValue: ExampleEnum.hello, defaultValue: ExampleEnum.hello,
); );
expect(matchesDefault.toString(), equals('name: hello')); expect(matchesDefault.toString(), equals('name: hello'));
expect(matchesDefault.object, equals(ExampleEnum.hello)); expect(matchesDefault.value, equals(ExampleEnum.hello));
expect(matchesDefault.hidden, isTrue); expect(matchesDefault.hidden, isTrue);
...@@ -986,7 +986,7 @@ void main() { ...@@ -986,7 +986,7 @@ void main() {
42, 42,
); );
expect(regular.toString(), equals('name: 42')); expect(regular.toString(), equals('name: 42'));
expect(regular.object, equals(42)); expect(regular.value, equals(42));
expect(regular.hidden, isFalse); expect(regular.hidden, isFalse);
final IntProperty nullValue = new IntProperty( final IntProperty nullValue = new IntProperty(
...@@ -994,7 +994,7 @@ void main() { ...@@ -994,7 +994,7 @@ void main() {
null, null,
); );
expect(nullValue.toString(), equals('name: null')); expect(nullValue.toString(), equals('name: null'));
expect(nullValue.object, isNull); expect(nullValue.value, isNull);
expect(nullValue.hidden, isFalse); expect(nullValue.hidden, isFalse);
final IntProperty hideNull = new IntProperty( final IntProperty hideNull = new IntProperty(
...@@ -1003,7 +1003,7 @@ void main() { ...@@ -1003,7 +1003,7 @@ void main() {
defaultValue: null defaultValue: null
); );
expect(hideNull.toString(), equals('name: null')); expect(hideNull.toString(), equals('name: null'));
expect(hideNull.object, isNull); expect(hideNull.value, isNull);
expect(hideNull.hidden, isTrue); expect(hideNull.hidden, isTrue);
final IntProperty nullDescription = new IntProperty( final IntProperty nullDescription = new IntProperty(
...@@ -1012,7 +1012,7 @@ void main() { ...@@ -1012,7 +1012,7 @@ void main() {
ifNull: 'missing', ifNull: 'missing',
); );
expect(nullDescription.toString(), equals('name: missing')); expect(nullDescription.toString(), equals('name: missing'));
expect(nullDescription.object, isNull); expect(nullDescription.value, isNull);
expect(nullDescription.hidden, isFalse); expect(nullDescription.hidden, isFalse);
final IntProperty hideName = new IntProperty( final IntProperty hideName = new IntProperty(
...@@ -1021,7 +1021,7 @@ void main() { ...@@ -1021,7 +1021,7 @@ void main() {
showName: false, showName: false,
); );
expect(hideName.toString(), equals('42')); expect(hideName.toString(), equals('42'));
expect(hideName.object, equals(42)); expect(hideName.value, equals(42));
expect(hideName.hidden, isFalse); expect(hideName.hidden, isFalse);
final IntProperty withUnit = new IntProperty( final IntProperty withUnit = new IntProperty(
...@@ -1030,7 +1030,7 @@ void main() { ...@@ -1030,7 +1030,7 @@ void main() {
unit: 'pt', unit: 'pt',
); );
expect(withUnit.toString(), equals('name: 42pt')); expect(withUnit.toString(), equals('name: 42pt'));
expect(withUnit.object, equals(42)); expect(withUnit.value, equals(42));
expect(withUnit.hidden, isFalse); expect(withUnit.hidden, isFalse);
final IntProperty defaultValue = new IntProperty( final IntProperty defaultValue = new IntProperty(
...@@ -1039,7 +1039,7 @@ void main() { ...@@ -1039,7 +1039,7 @@ void main() {
defaultValue: 42, defaultValue: 42,
); );
expect(defaultValue.toString(), equals('name: 42')); expect(defaultValue.toString(), equals('name: 42'));
expect(defaultValue.object, equals(42)); expect(defaultValue.value, equals(42));
expect(defaultValue.hidden, isTrue); expect(defaultValue.hidden, isTrue);
final IntProperty notDefaultValue = new IntProperty( final IntProperty notDefaultValue = new IntProperty(
...@@ -1048,7 +1048,7 @@ void main() { ...@@ -1048,7 +1048,7 @@ void main() {
defaultValue: 42, defaultValue: 42,
); );
expect(notDefaultValue.toString(), equals('name: 43')); expect(notDefaultValue.toString(), equals('name: 43'));
expect(notDefaultValue.object, equals(43)); expect(notDefaultValue.value, equals(43));
expect(notDefaultValue.hidden, isFalse); expect(notDefaultValue.hidden, isFalse);
final IntProperty hidden = new IntProperty( final IntProperty hidden = new IntProperty(
...@@ -1057,7 +1057,7 @@ void main() { ...@@ -1057,7 +1057,7 @@ void main() {
hidden: true, hidden: true,
); );
expect(hidden.toString(), equals('name: 42')); expect(hidden.toString(), equals('name: 42'));
expect(hidden.object, equals(42)); expect(hidden.value, equals(42));
expect(hidden.hidden, isTrue); expect(hidden.hidden, isTrue);
}); });
...@@ -1067,7 +1067,7 @@ void main() { ...@@ -1067,7 +1067,7 @@ void main() {
'name', 'name',
rect, rect,
); );
expect(simple.object, equals(rect)); expect(simple.value, equals(rect));
expect(simple.hidden, isFalse); expect(simple.hidden, isFalse);
expect(simple.toString(), equals('name: Rect.fromLTRB(0.0, 0.0, 20.0, 20.0)')); expect(simple.toString(), equals('name: Rect.fromLTRB(0.0, 0.0, 20.0, 20.0)'));
...@@ -1076,7 +1076,7 @@ void main() { ...@@ -1076,7 +1076,7 @@ void main() {
rect, rect,
description: 'small rect', description: 'small rect',
); );
expect(withDescription.object, equals(rect)); expect(withDescription.value, equals(rect));
expect(withDescription.hidden, isFalse); expect(withDescription.hidden, isFalse);
expect(withDescription.toString(), equals('name: small rect')); expect(withDescription.toString(), equals('name: small rect'));
...@@ -1084,7 +1084,7 @@ void main() { ...@@ -1084,7 +1084,7 @@ void main() {
'name', 'name',
null, null,
); );
expect(nullProperty.object, isNull); expect(nullProperty.value, isNull);
expect(nullProperty.hidden, isFalse); expect(nullProperty.hidden, isFalse);
expect(nullProperty.toString(), equals('name: null')); expect(nullProperty.toString(), equals('name: null'));
...@@ -1093,7 +1093,7 @@ void main() { ...@@ -1093,7 +1093,7 @@ void main() {
null, null,
defaultValue: null, defaultValue: null,
); );
expect(hideNullProperty.object, isNull); expect(hideNullProperty.value, isNull);
expect(hideNullProperty.hidden, isTrue); expect(hideNullProperty.hidden, isTrue);
expect(hideNullProperty.toString(), equals('name: null')); expect(hideNullProperty.toString(), equals('name: null'));
...@@ -1102,7 +1102,7 @@ void main() { ...@@ -1102,7 +1102,7 @@ void main() {
null, null,
ifNull: 'missing', ifNull: 'missing',
); );
expect(nullDescription.object, isNull); expect(nullDescription.value, isNull);
expect(nullDescription.hidden, isFalse); expect(nullDescription.hidden, isFalse);
expect(nullDescription.toString(), equals('name: missing')); expect(nullDescription.toString(), equals('name: missing'));
...@@ -1111,7 +1111,7 @@ void main() { ...@@ -1111,7 +1111,7 @@ void main() {
rect, rect,
showName: false, showName: false,
); );
expect(hideName.object, equals(rect)); expect(hideName.value, equals(rect));
expect(hideName.hidden, isFalse); expect(hideName.hidden, isFalse);
expect(hideName.toString(), equals('Rect.fromLTRB(0.0, 0.0, 20.0, 20.0)')); expect(hideName.toString(), equals('Rect.fromLTRB(0.0, 0.0, 20.0, 20.0)'));
...@@ -1120,7 +1120,7 @@ void main() { ...@@ -1120,7 +1120,7 @@ void main() {
rect, rect,
showSeparator: false, showSeparator: false,
); );
expect(hideSeparator.object, equals(rect)); expect(hideSeparator.value, equals(rect));
expect(hideSeparator.hidden, isFalse); expect(hideSeparator.hidden, isFalse);
expect( expect(
hideSeparator.toString(), hideSeparator.toString(),
...@@ -1135,7 +1135,7 @@ void main() { ...@@ -1135,7 +1135,7 @@ void main() {
() => rect, () => rect,
description: 'small rect', description: 'small rect',
); );
expect(simple.object, equals(rect)); expect(simple.value, equals(rect));
expect(simple.hidden, isFalse); expect(simple.hidden, isFalse);
expect(simple.toString(), equals('name: small rect')); expect(simple.toString(), equals('name: small rect'));
...@@ -1144,7 +1144,7 @@ void main() { ...@@ -1144,7 +1144,7 @@ void main() {
() => null, () => null,
description: 'missing', description: 'missing',
); );
expect(nullProperty.object, isNull); expect(nullProperty.value, isNull);
expect(nullProperty.hidden, isFalse); expect(nullProperty.hidden, isFalse);
expect(nullProperty.toString(), equals('name: missing')); expect(nullProperty.toString(), equals('name: missing'));
...@@ -1154,7 +1154,7 @@ void main() { ...@@ -1154,7 +1154,7 @@ void main() {
description: 'missing', description: 'missing',
defaultValue: null, defaultValue: null,
); );
expect(hideNullProperty.object, isNull); expect(hideNullProperty.value, isNull);
expect(hideNullProperty.hidden, isTrue); expect(hideNullProperty.hidden, isTrue);
expect(hideNullProperty.toString(), equals('name: missing')); expect(hideNullProperty.toString(), equals('name: missing'));
...@@ -1164,7 +1164,7 @@ void main() { ...@@ -1164,7 +1164,7 @@ void main() {
description: 'small rect', description: 'small rect',
showName: false, showName: false,
); );
expect(hideName.object, equals(rect)); expect(hideName.value, equals(rect));
expect(hideName.hidden, isFalse); expect(hideName.hidden, isFalse);
expect(hideName.toString(), equals('small rect')); expect(hideName.toString(), equals('small rect'));
...@@ -1174,7 +1174,7 @@ void main() { ...@@ -1174,7 +1174,7 @@ void main() {
description: 'missing', description: 'missing',
defaultValue: null, defaultValue: null,
); );
expect(throwingWithDescription.object, isNull); expect(throwingWithDescription.value, isNull);
expect(throwingWithDescription.exception, isFlutterError); expect(throwingWithDescription.exception, isFlutterError);
expect(throwingWithDescription.hidden, false); expect(throwingWithDescription.hidden, false);
expect(throwingWithDescription.toString(), equals('name: missing')); expect(throwingWithDescription.toString(), equals('name: missing'));
...@@ -1184,7 +1184,7 @@ void main() { ...@@ -1184,7 +1184,7 @@ void main() {
() => throw new FlutterError('Property not available'), () => throw new FlutterError('Property not available'),
defaultValue: null, defaultValue: null,
); );
expect(throwingProperty.object, isNull); expect(throwingProperty.value, isNull);
expect(throwingProperty.exception, isFlutterError); expect(throwingProperty.exception, isFlutterError);
expect(throwingProperty.hidden, false); expect(throwingProperty.hidden, false);
expect(throwingProperty.toString(), equals('name: EXCEPTION (FlutterError)')); expect(throwingProperty.toString(), equals('name: EXCEPTION (FlutterError)'));
...@@ -1200,7 +1200,7 @@ void main() { ...@@ -1200,7 +1200,7 @@ void main() {
color, color,
); );
expect(simple.hidden, isFalse); expect(simple.hidden, isFalse);
expect(simple.object, equals(color)); expect(simple.value, equals(color));
expect(simple.toString(), equals('name: Color(0xffffffff)')); expect(simple.toString(), equals('name: Color(0xffffffff)'));
}); });
...@@ -1211,7 +1211,7 @@ void main() { ...@@ -1211,7 +1211,7 @@ void main() {
ifTrue: 'layout computed', ifTrue: 'layout computed',
); );
expect(show.name, equals('wasLayout')); expect(show.name, equals('wasLayout'));
expect(show.object, isTrue); expect(show.value, isTrue);
expect(show.hidden, isFalse); expect(show.hidden, isFalse);
expect(show.toString(), equals('layout computed')); expect(show.toString(), equals('layout computed'));
...@@ -1221,7 +1221,7 @@ void main() { ...@@ -1221,7 +1221,7 @@ void main() {
ifTrue: 'layout computed', ifTrue: 'layout computed',
); );
expect(hide.name, equals('wasLayout')); expect(hide.name, equals('wasLayout'));
expect(hide.object, isFalse); expect(hide.value, isFalse);
expect(hide.hidden, isTrue); expect(hide.hidden, isTrue);
expect(hide.toString(), equals('')); expect(hide.toString(), equals(''));
}); });
...@@ -1233,7 +1233,7 @@ void main() { ...@@ -1233,7 +1233,7 @@ void main() {
onClick, onClick,
); );
expect(has.name, equals('onClick')); expect(has.name, equals('onClick'));
expect(has.object, equals(onClick)); expect(has.value, equals(onClick));
expect(has.hidden, isFalse); expect(has.hidden, isFalse);
expect(has.toString(), equals('has onClick')); expect(has.toString(), equals('has onClick'));
...@@ -1242,7 +1242,7 @@ void main() { ...@@ -1242,7 +1242,7 @@ void main() {
null, null,
); );
expect(missing.name, equals('onClick')); expect(missing.name, equals('onClick'));
expect(missing.object, isNull); expect(missing.value, isNull);
expect(missing.hidden, isTrue); expect(missing.hidden, isTrue);
expect(missing.toString(), equals('')); expect(missing.toString(), equals(''));
}); });
...@@ -1253,7 +1253,7 @@ void main() { ...@@ -1253,7 +1253,7 @@ void main() {
'ints', 'ints',
ints, ints,
); );
expect(intsProperty.object, equals(ints)); expect(intsProperty.value, equals(ints));
expect(intsProperty.hidden, isFalse); expect(intsProperty.hidden, isFalse);
expect(intsProperty.toString(), equals('ints: 1, 2, 3')); expect(intsProperty.toString(), equals('ints: 1, 2, 3'));
...@@ -1261,7 +1261,7 @@ void main() { ...@@ -1261,7 +1261,7 @@ void main() {
'name', 'name',
<Object>[], <Object>[],
); );
expect(emptyProperty.object, isEmpty); expect(emptyProperty.value, isEmpty);
expect(emptyProperty.hidden, isFalse); expect(emptyProperty.hidden, isFalse);
expect(emptyProperty.toString(), equals('name: []')); expect(emptyProperty.toString(), equals('name: []'));
...@@ -1269,7 +1269,7 @@ void main() { ...@@ -1269,7 +1269,7 @@ void main() {
'list', 'list',
null, null,
); );
expect(nullProperty.object, isNull); expect(nullProperty.value, isNull);
expect(nullProperty.hidden, isFalse); expect(nullProperty.hidden, isFalse);
expect(nullProperty.toString(), equals('list: null')); expect(nullProperty.toString(), equals('list: null'));
...@@ -1278,7 +1278,7 @@ void main() { ...@@ -1278,7 +1278,7 @@ void main() {
null, null,
defaultValue: null, defaultValue: null,
); );
expect(hideNullProperty.object, isNull); expect(hideNullProperty.value, isNull);
expect(hideNullProperty.hidden, isTrue); expect(hideNullProperty.hidden, isTrue);
expect(hideNullProperty.toString(), equals('list: null')); expect(hideNullProperty.toString(), equals('list: null'));
...@@ -1290,7 +1290,7 @@ void main() { ...@@ -1290,7 +1290,7 @@ void main() {
'objects', 'objects',
objects, objects,
); );
expect(objectsProperty.object, equals(objects)); expect(objectsProperty.value, equals(objects));
expect(objectsProperty.hidden, isFalse); expect(objectsProperty.hidden, isFalse);
expect( expect(
objectsProperty.toString(), objectsProperty.toString(),
...@@ -1306,7 +1306,7 @@ void main() { ...@@ -1306,7 +1306,7 @@ void main() {
objects, objects,
style: DiagnosticsTreeStyle.whitespace, style: DiagnosticsTreeStyle.whitespace,
); );
expect(multiLineProperty.object, equals(objects)); expect(multiLineProperty.value, equals(objects));
expect(multiLineProperty.hidden, isFalse); expect(multiLineProperty.hidden, isFalse);
expect( expect(
multiLineProperty.toString(), multiLineProperty.toString(),
...@@ -1347,7 +1347,7 @@ void main() { ...@@ -1347,7 +1347,7 @@ void main() {
singleElementList, singleElementList,
style: DiagnosticsTreeStyle.whitespace, style: DiagnosticsTreeStyle.whitespace,
); );
expect(objectProperty.object, equals(singleElementList)); expect(objectProperty.value, equals(singleElementList));
expect(objectProperty.hidden, isFalse); expect(objectProperty.hidden, isFalse);
expect( expect(
objectProperty.toString(), objectProperty.toString(),
...@@ -1373,13 +1373,13 @@ void main() { ...@@ -1373,13 +1373,13 @@ void main() {
final DiagnosticsNode message = new DiagnosticsNode.message('hello world'); final DiagnosticsNode message = new DiagnosticsNode.message('hello world');
expect(message.toString(), equals('hello world')); expect(message.toString(), equals('hello world'));
expect(message.name, isEmpty); expect(message.name, isEmpty);
expect(message.object, isNull); expect(message.value, isNull);
expect(message.showName, isFalse); expect(message.showName, isFalse);
final DiagnosticsNode messageProperty = new MessageProperty('diagnostics', 'hello world'); final DiagnosticsNode messageProperty = new MessageProperty('diagnostics', 'hello world');
expect(messageProperty.toString(), equals('diagnostics: hello world')); expect(messageProperty.toString(), equals('diagnostics: hello world'));
expect(messageProperty.name, equals('diagnostics')); expect(messageProperty.name, equals('diagnostics'));
expect(messageProperty.object, isNull); expect(messageProperty.value, isNull);
expect(messageProperty.showName, isTrue); expect(messageProperty.showName, isTrue);
}); });
......
// Copyright 2017 The Chromium 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/gestures.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('debugPrintGestureArenaDiagnostics', (WidgetTester tester) {
PointerEvent event;
debugPrintGestureArenaDiagnostics = true;
final DebugPrintCallback oldCallback = debugPrint;
final List<String> log = <String>[];
debugPrint = (String s, { int wrapWidth }) { log.add(s); };
final TapGestureRecognizer tap = new TapGestureRecognizer()
..onTapDown = (TapDownDetails details) { }
..onTapUp = (TapUpDetails details) { }
..onTap = () { }
..onTapCancel = () { };
expect(log, isEmpty);
event = const PointerDownEvent(pointer: 1, position: const Offset(10.0, 10.0));
tap.addPointer(event);
expect(log, hasLength(2));
expect(log[0], equalsIgnoringHashCodes('Gesture arena 1 ❙ ★ Opening new gesture arena.'));
expect(log[1], equalsIgnoringHashCodes('Gesture arena 1 ❙ Adding: TapGestureRecognizer#00000(state: ready)'));
log.clear();
GestureBinding.instance.gestureArena.close(1);
expect(log, hasLength(1));
expect(log[0], equalsIgnoringHashCodes('Gesture arena 1 ❙ Closing with 1 member.'));
log.clear();
GestureBinding.instance.pointerRouter.route(event);
expect(log, isEmpty);
event = const PointerUpEvent(pointer: 1, position: const Offset(12.0, 8.0));
GestureBinding.instance.pointerRouter.route(event);
expect(log, isEmpty);
GestureBinding.instance.gestureArena.sweep(1);
expect(log, hasLength(2));
expect(log[0], equalsIgnoringHashCodes('Gesture arena 1 ❙ Sweeping with 1 member.'));
expect(log[1], equalsIgnoringHashCodes('Gesture arena 1 ❙ Winner: TapGestureRecognizer#00000(state: ready)'));
log.clear();
tap.dispose();
expect(log, isEmpty);
debugPrintGestureArenaDiagnostics = false;
debugPrint = oldCallback;
});
testWidgets('debugPrintRecognizerCallbacksTrace', (WidgetTester tester) {
PointerEvent event;
debugPrintRecognizerCallbacksTrace = true;
final DebugPrintCallback oldCallback = debugPrint;
final List<String> log = <String>[];
debugPrint = (String s, { int wrapWidth }) { log.add(s); };
final TapGestureRecognizer tap = new TapGestureRecognizer()
..onTapDown = (TapDownDetails details) { }
..onTapUp = (TapUpDetails details) { }
..onTap = () { }
..onTapCancel = () { };
expect(log, isEmpty);
event = const PointerDownEvent(pointer: 1, position: const Offset(10.0, 10.0));
tap.addPointer(event);
expect(log, isEmpty);
GestureBinding.instance.gestureArena.close(1);
expect(log, isEmpty);
GestureBinding.instance.pointerRouter.route(event);
expect(log, isEmpty);
event = const PointerUpEvent(pointer: 1, position: const Offset(12.0, 8.0));
GestureBinding.instance.pointerRouter.route(event);
expect(log, isEmpty);
GestureBinding.instance.gestureArena.sweep(1);
expect(log, hasLength(3));
expect(log[0], equalsIgnoringHashCodes('TapGestureRecognizer#00000(state: ready) calling onTapDown callback.'));
expect(log[1], equalsIgnoringHashCodes('TapGestureRecognizer#00000(state: ready) calling onTapUp callback.'));
expect(log[2], equalsIgnoringHashCodes('TapGestureRecognizer#00000(state: ready) calling onTap callback.'));
log.clear();
tap.dispose();
expect(log, isEmpty);
debugPrintRecognizerCallbacksTrace = false;
debugPrint = oldCallback;
});
testWidgets('debugPrintGestureArenaDiagnostics and debugPrintRecognizerCallbacksTrace', (WidgetTester tester) {
PointerEvent event;
debugPrintGestureArenaDiagnostics = true;
debugPrintRecognizerCallbacksTrace = true;
final DebugPrintCallback oldCallback = debugPrint;
final List<String> log = <String>[];
debugPrint = (String s, { int wrapWidth }) { log.add(s); };
final TapGestureRecognizer tap = new TapGestureRecognizer()
..onTapDown = (TapDownDetails details) { }
..onTapUp = (TapUpDetails details) { }
..onTap = () { }
..onTapCancel = () { };
expect(log, isEmpty);
event = const PointerDownEvent(pointer: 1, position: const Offset(10.0, 10.0));
tap.addPointer(event);
expect(log, hasLength(2));
expect(log[0], equalsIgnoringHashCodes('Gesture arena 1 ❙ ★ Opening new gesture arena.'));
expect(log[1], equalsIgnoringHashCodes('Gesture arena 1 ❙ Adding: TapGestureRecognizer#00000(state: ready)'));
log.clear();
GestureBinding.instance.gestureArena.close(1);
expect(log, hasLength(1));
expect(log[0], equalsIgnoringHashCodes('Gesture arena 1 ❙ Closing with 1 member.'));
log.clear();
GestureBinding.instance.pointerRouter.route(event);
expect(log, isEmpty);
event = const PointerUpEvent(pointer: 1, position: const Offset(12.0, 8.0));
GestureBinding.instance.pointerRouter.route(event);
expect(log, isEmpty);
GestureBinding.instance.gestureArena.sweep(1);
expect(log, hasLength(5));
expect(log[0], equalsIgnoringHashCodes('Gesture arena 1 ❙ Sweeping with 1 member.'));
expect(log[1], equalsIgnoringHashCodes('Gesture arena 1 ❙ Winner: TapGestureRecognizer#00000(state: ready)'));
expect(log[2], equalsIgnoringHashCodes(' ❙ TapGestureRecognizer#00000(state: ready) calling onTapDown callback.'));
expect(log[3], equalsIgnoringHashCodes(' ❙ TapGestureRecognizer#00000(state: ready) calling onTapUp callback.'));
expect(log[4], equalsIgnoringHashCodes(' ❙ TapGestureRecognizer#00000(state: ready) calling onTap callback.'));
log.clear();
tap.dispose();
expect(log, isEmpty);
debugPrintGestureArenaDiagnostics = false;
debugPrintRecognizerCallbacksTrace = false;
debugPrint = oldCallback;
});
}
...@@ -50,17 +50,21 @@ void main() { ...@@ -50,17 +50,21 @@ void main() {
expect(didEndPan, isFalse); expect(didEndPan, isFalse);
expect(didTap, isFalse); expect(didTap, isFalse);
tester.route(pointer.move(const Offset(20.0, 20.0))); // touch should give up when it hits kTouchSlop, which was 18.0 when this test was last updated.
expect(didStartPan, isTrue);
tester.route(pointer.move(const Offset(20.0, 20.0))); // moved 10 horizontally and 10 vertically which is 14 total
expect(didStartPan, isFalse); // 14 < 18
tester.route(pointer.move(const Offset(20.0, 30.0))); // moved 10 horizontally and 20 vertically which is 22 total
expect(didStartPan, isTrue); // 22 > 18
didStartPan = false; didStartPan = false;
expect(updatedScrollDelta, const Offset(10.0, 10.0)); expect(updatedScrollDelta, const Offset(10.0, 20.0));
updatedScrollDelta = null; updatedScrollDelta = null;
expect(didEndPan, isFalse); expect(didEndPan, isFalse);
expect(didTap, isFalse); expect(didTap, isFalse);
tester.route(pointer.move(const Offset(20.0, 25.0))); tester.route(pointer.move(const Offset(20.0, 25.0)));
expect(didStartPan, isFalse); expect(didStartPan, isFalse);
expect(updatedScrollDelta, const Offset(0.0, 5.0)); expect(updatedScrollDelta, const Offset(0.0, -5.0));
updatedScrollDelta = null; updatedScrollDelta = null;
expect(didEndPan, isFalse); expect(didEndPan, isFalse);
expect(didTap, isFalse); expect(didTap, isFalse);
......
...@@ -7,13 +7,12 @@ import 'package:flutter/gestures.dart'; ...@@ -7,13 +7,12 @@ import 'package:flutter/gestures.dart';
import 'gesture_tester.dart'; import 'gesture_tester.dart';
class TestDrag extends Drag { class TestDrag extends Drag { }
}
void main() { void main() {
setUp(ensureGestureBinding); setUp(ensureGestureBinding);
testGesture('MultiDrag control test', (GestureTester tester) { testGesture('MultiDrag: moving before delay rejects', (GestureTester tester) {
final DelayedMultiDragGestureRecognizer drag = new DelayedMultiDragGestureRecognizer(); final DelayedMultiDragGestureRecognizer drag = new DelayedMultiDragGestureRecognizer();
bool didStartDrag = false; bool didStartDrag = false;
...@@ -29,12 +28,37 @@ void main() { ...@@ -29,12 +28,37 @@ void main() {
expect(didStartDrag, isFalse); expect(didStartDrag, isFalse);
tester.async.flushMicrotasks(); tester.async.flushMicrotasks();
expect(didStartDrag, isFalse); expect(didStartDrag, isFalse);
tester.route(pointer.move(const Offset(20.0, 20.0))); tester.route(pointer.move(const Offset(20.0, 60.0))); // move more than touch slop before delay expires
expect(didStartDrag, isFalse);
tester.async.elapse(kLongPressTimeout * 2); // expire delay
expect(didStartDrag, isFalse);
tester.route(pointer.move(const Offset(30.0, 120.0))); // move some more after delay expires
expect(didStartDrag, isFalse);
drag.dispose();
});
testGesture('MultiDrag: delay triggers', (GestureTester tester) {
final DelayedMultiDragGestureRecognizer drag = new DelayedMultiDragGestureRecognizer();
bool didStartDrag = false;
drag.onStart = (Offset position) {
didStartDrag = true;
return new TestDrag();
};
final TestPointer pointer = new TestPointer(5);
final PointerDownEvent down = pointer.down(const Offset(10.0, 10.0));
drag.addPointer(down);
tester.closeArena(5);
expect(didStartDrag, isFalse); expect(didStartDrag, isFalse);
tester.async.elapse(kLongPressTimeout * 2); tester.async.flushMicrotasks();
expect(didStartDrag, isFalse); expect(didStartDrag, isFalse);
tester.route(pointer.move(const Offset(30.0, 30.0))); tester.route(pointer.move(const Offset(20.0, 20.0))); // move less than touch slop before delay expires
expect(didStartDrag, isFalse); expect(didStartDrag, isFalse);
tester.async.elapse(kLongPressTimeout * 2); // expire delay
expect(didStartDrag, isTrue);
tester.route(pointer.move(const Offset(30.0, 70.0))); // move more than touch slop after delay expires
expect(didStartDrag, isTrue);
drag.dispose(); drag.dispose();
}); });
} }
...@@ -60,7 +60,7 @@ void main() { ...@@ -60,7 +60,7 @@ void main() {
expect(log, <String>['long-tap-down 6']); expect(log, <String>['long-tap-down 6']);
log.clear(); log.clear();
tester.route(pointer6.move(const Offset(4.0, 3.0))); tester.route(pointer6.move(const Offset(40.0, 30.0))); // move more than kTouchSlop from 15.0,15.0
expect(log, <String>['tap-cancel 6']); expect(log, <String>['tap-cancel 6']);
log.clear(); log.clear();
......
...@@ -6,22 +6,24 @@ import 'package:flutter_test/flutter_test.dart'; ...@@ -6,22 +6,24 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
class TestGestureRecognizer extends GestureRecognizer { class TestGestureRecognizer extends GestureRecognizer {
TestGestureRecognizer({ Object debugOwner }) : super(debugOwner: debugOwner);
@override @override
String toString() => 'toString content'; String toStringShort() => 'toStringShort content';
@override @override
void addPointer(PointerDownEvent event) {} void addPointer(PointerDownEvent event) { }
@override @override
void acceptGesture(int pointer) {} void acceptGesture(int pointer) { }
@override @override
void rejectGesture(int pointer) {} void rejectGesture(int pointer) { }
} }
void main() { void main() {
test('GestureRecognizer.toStringShort defaults to toString', () { test('GestureRecognizer smoketest', () {
final TestGestureRecognizer recognizer = new TestGestureRecognizer(); final TestGestureRecognizer recognizer = new TestGestureRecognizer(debugOwner: 0);
expect(recognizer.toStringShort(), equals(recognizer.toString())); expect(recognizer, hasAGoodToStringDeep);
}); });
} }
...@@ -39,7 +39,7 @@ void main() { ...@@ -39,7 +39,7 @@ void main() {
final TestPointer pointer1 = new TestPointer(1); final TestPointer pointer1 = new TestPointer(1);
final PointerDownEvent down = pointer1.down(const Offset(10.0, 10.0)); final PointerDownEvent down = pointer1.down(const Offset(0.0, 0.0));
scale.addPointer(down); scale.addPointer(down);
tap.addPointer(down); tap.addPointer(down);
...@@ -211,7 +211,9 @@ void main() { ...@@ -211,7 +211,9 @@ void main() {
tester.route(down); tester.route(down);
expect(log, isEmpty); expect(log, isEmpty);
tester.route(pointer1.move(const Offset(10.0, 30.0))); // scale will win if focal point delta exceeds 18.0*2
tester.route(pointer1.move(const Offset(10.0, 50.0))); // delta of 40.0 exceeds 18.0*2
expect(log, equals(<String>['scale-start', 'scale-update'])); expect(log, equals(<String>['scale-start', 'scale-update']));
log.clear(); log.clear();
...@@ -240,7 +242,10 @@ void main() { ...@@ -240,7 +242,10 @@ void main() {
expect(log, isEmpty); expect(log, isEmpty);
log.clear(); log.clear();
// Horizontal moves are drags. // Horizontal moves are either drags or scales, depending on which wins first.
// TODO(ianh): https://github.com/flutter/flutter/issues/11384
// In this case, we move fast, so that the scale wins. If we moved slowly,
// the horizontal drag would win, since it was added first.
final TestPointer pointer3 = new TestPointer(3); final TestPointer pointer3 = new TestPointer(3);
final PointerDownEvent down3 = pointer3.down(const Offset(30.0, 30.0)); final PointerDownEvent down3 = pointer3.down(const Offset(30.0, 30.0));
scale.addPointer(down3); scale.addPointer(down3);
...@@ -250,7 +255,7 @@ void main() { ...@@ -250,7 +255,7 @@ void main() {
expect(log, isEmpty); expect(log, isEmpty);
tester.route(pointer3.move(const Offset(50.0, 30.0))); tester.route(pointer3.move(const Offset(100.0, 30.0)));
expect(log, equals(<String>['scale-start', 'scale-update'])); expect(log, equals(<String>['scale-start', 'scale-update']));
log.clear(); log.clear();
......
...@@ -41,7 +41,7 @@ void main() { ...@@ -41,7 +41,7 @@ void main() {
position: const Offset(31.0, 29.0) position: const Offset(31.0, 29.0)
); );
// Down/move/up sequence 3: intervening motion // Down/move/up sequence 3: intervening motion, more than kTouchSlop. (~21px)
const PointerDownEvent down3 = const PointerDownEvent( const PointerDownEvent down3 = const PointerDownEvent(
pointer: 3, pointer: 3,
position: const Offset(10.0, 10.0) position: const Offset(10.0, 10.0)
...@@ -57,6 +57,22 @@ void main() { ...@@ -57,6 +57,22 @@ void main() {
position: const Offset(25.0, 25.0) position: const Offset(25.0, 25.0)
); );
// Down/move/up sequence 4: intervening motion, less than kTouchSlop. (~17px)
const PointerDownEvent down4 = const PointerDownEvent(
pointer: 4,
position: const Offset(10.0, 10.0)
);
const PointerMoveEvent move4 = const PointerMoveEvent(
pointer: 4,
position: const Offset(22.0, 22.0)
);
const PointerUpEvent up4 = const PointerUpEvent(
pointer: 4,
position: const Offset(22.0, 22.0)
);
testGesture('Should recognize tap', (GestureTester tester) { testGesture('Should recognize tap', (GestureTester tester) {
final TapGestureRecognizer tap = new TapGestureRecognizer(); final TapGestureRecognizer tap = new TapGestureRecognizer();
...@@ -179,6 +195,39 @@ void main() { ...@@ -179,6 +195,39 @@ void main() {
tap.dispose(); tap.dispose();
}); });
testGesture('Short distance does not cancel tap', (GestureTester tester) {
final TapGestureRecognizer tap = new TapGestureRecognizer();
bool tapRecognized = false;
tap.onTap = () {
tapRecognized = true;
};
bool tapCanceled = false;
tap.onTapCancel = () {
tapCanceled = true;
};
tap.addPointer(down4);
tester.closeArena(4);
expect(tapRecognized, isFalse);
expect(tapCanceled, isFalse);
tester.route(down4);
expect(tapRecognized, isFalse);
expect(tapCanceled, isFalse);
tester.route(move4);
expect(tapRecognized, isFalse);
expect(tapCanceled, isFalse);
tester.route(up4);
expect(tapRecognized, isTrue);
expect(tapCanceled, isFalse);
GestureBinding.instance.gestureArena.sweep(4);
expect(tapRecognized, isTrue);
expect(tapCanceled, isFalse);
tap.dispose();
});
testGesture('Timeout does not cancel tap', (GestureTester tester) { testGesture('Timeout does not cancel tap', (GestureTester tester) {
final TapGestureRecognizer tap = new TapGestureRecognizer(); final TapGestureRecognizer tap = new TapGestureRecognizer();
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter/gestures.dart';
void main() { void main() {
testWidgets('Verify that a tap dismisses a modal BottomSheet', (WidgetTester tester) async { testWidgets('Verify that a tap dismisses a modal BottomSheet', (WidgetTester tester) async {
...@@ -102,7 +103,11 @@ void main() { ...@@ -102,7 +103,11 @@ void main() {
expect(showBottomSheetThenCalled, isFalse); expect(showBottomSheetThenCalled, isFalse);
expect(find.text('BottomSheet'), findsOneWidget); expect(find.text('BottomSheet'), findsOneWidget);
await tester.fling(find.text('BottomSheet'), const Offset(0.0, 30.0), 1000.0); // The fling below must be such that the velocity estimation examines an
// offset greater than the kTouchSlop. Too slow or too short a distance, and
// it won't trigger. Also, it musn't be so much that it drags the bottom
// sheet off the screen, or we won't see it after we pump!
await tester.fling(find.text('BottomSheet'), const Offset(0.0, 50.0), 2000.0);
await tester.pump(); // drain the microtask queue (Future completion callback) await tester.pump(); // drain the microtask queue (Future completion callback)
expect(showBottomSheetThenCalled, isTrue); expect(showBottomSheetThenCalled, isTrue);
......
...@@ -28,8 +28,7 @@ void main() { ...@@ -28,8 +28,7 @@ void main() {
transform, transform,
); );
expect(simple.name, equals('transform')); expect(simple.name, equals('transform'));
expect(simple.object, equals(transform)); expect(simple.value, same(transform));
expect(simple.hidden, isFalse);
expect( expect(
simple.toString(), simple.toString(),
equals( equals(
...@@ -46,8 +45,7 @@ void main() { ...@@ -46,8 +45,7 @@ void main() {
null, null,
); );
expect(nullProperty.name, equals('transform')); expect(nullProperty.name, equals('transform'));
expect(nullProperty.object, isNull); expect(nullProperty.value, isNull);
expect(nullProperty.hidden, isFalse);
expect(nullProperty.toString(), equals('transform: null')); expect(nullProperty.toString(), equals('transform: null'));
final TransformProperty hideNull = new TransformProperty( final TransformProperty hideNull = new TransformProperty(
...@@ -55,8 +53,7 @@ void main() { ...@@ -55,8 +53,7 @@ void main() {
null, null,
defaultValue: null, defaultValue: null,
); );
expect(hideNull.object, isNull); expect(hideNull.value, isNull);
expect(hideNull.hidden, isTrue);
expect(hideNull.toString(), equals('transform: null')); expect(hideNull.toString(), equals('transform: null'));
}); });
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter/gestures.dart';
void main() { void main() {
testWidgets('Uncontested scrolls start immediately', (WidgetTester tester) async { testWidgets('Uncontested scrolls start immediately', (WidgetTester tester) async {
...@@ -59,13 +60,13 @@ void main() { ...@@ -59,13 +60,13 @@ void main() {
double dragDistance = 0.0; double dragDistance = 0.0;
final Offset downLocation = const Offset(10.0, 10.0); final Offset downLocation = const Offset(10.0, 10.0);
final Offset upLocation = const Offset(10.0, 20.0); final Offset upLocation = const Offset(10.0, 50.0); // must be far enough to be more than kTouchSlop
final Widget widget = new GestureDetector( final Widget widget = new GestureDetector(
onVerticalDragUpdate: (DragUpdateDetails details) { dragDistance += details.primaryDelta; }, onVerticalDragUpdate: (DragUpdateDetails details) { dragDistance += details.primaryDelta; },
onVerticalDragEnd: (DragEndDetails details) { gestureCount += 1; }, onVerticalDragEnd: (DragEndDetails details) { gestureCount += 1; },
onHorizontalDragUpdate: (DragUpdateDetails details) { fail("gesture should not match"); }, onHorizontalDragUpdate: (DragUpdateDetails details) { fail('gesture should not match'); },
onHorizontalDragEnd: (DragEndDetails details) { fail("gesture should not match"); }, onHorizontalDragEnd: (DragEndDetails details) { fail('gesture should not match'); },
child: new Container( child: new Container(
color: const Color(0xFF00FF00), color: const Color(0xFF00FF00),
) )
...@@ -81,7 +82,7 @@ void main() { ...@@ -81,7 +82,7 @@ void main() {
await gesture.up(); await gesture.up();
expect(gestureCount, 2); expect(gestureCount, 2);
expect(dragDistance, 20.0); expect(dragDistance, 40.0 * 2.0); // delta between down and up, twice
await tester.pumpWidget(new Container()); await tester.pumpWidget(new Container());
}); });
......
...@@ -35,7 +35,7 @@ void main() { ...@@ -35,7 +35,7 @@ void main() {
expect(find.text('Alaska'), findsNothing); expect(find.text('Alaska'), findsNothing);
await tester.drag(find.byType(PageView), const Offset(-10.0, 0.0)); await tester.drag(find.byType(PageView), const Offset(-20.0, 0.0));
await tester.pump(); await tester.pump();
expect(find.text('Alabama'), findsOneWidget); expect(find.text('Alabama'), findsOneWidget);
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
void main() { void main() {
...@@ -78,7 +79,7 @@ void main() { ...@@ -78,7 +79,7 @@ void main() {
final TestGesture gesture = await tester.startGesture(const Offset(100.0, 100.0)); final TestGesture gesture = await tester.startGesture(const Offset(100.0, 100.0));
await tester.pump(const Duration(seconds: 1)); await tester.pump(const Duration(seconds: 1));
await gesture.moveBy(const Offset(-10.0, -10.0)); await gesture.moveBy(const Offset(-10.0, -40.0));
await tester.pump(const Duration(seconds: 1)); await tester.pump(const Duration(seconds: 1));
await gesture.up(); await gesture.up();
await tester.pump(const Duration(seconds: 1)); await tester.pump(const Duration(seconds: 1));
......
...@@ -232,7 +232,12 @@ void main() { ...@@ -232,7 +232,12 @@ void main() {
), ),
); );
await tester.fling(find.byType(Slider), const Offset(-100.0, 0.0), 100.0); // The fling below must be such that the velocity estimation examines an
// offset greater than the kTouchSlop. Too slow or too short a distance, and
// it won't trigger. The actual distance moved doesn't matter since this is
// interpreted as a gesture by the semantics debugger and sent to the widget
// as a semantic action that always moves by 10% of the complete track.
await tester.fling(find.byType(Slider), const Offset(-100.0, 0.0), 2000.0);
expect(value, equals(0.65)); expect(value, equals(0.65));
}); });
......
...@@ -324,6 +324,7 @@ abstract class TestWidgetsFlutterBinding extends BindingBase ...@@ -324,6 +324,7 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
assert(Zone.current == _parentZone); assert(Zone.current == _parentZone);
assert(_currentTestCompleter != null); assert(_currentTestCompleter != null);
if (_pendingExceptionDetails != null) { if (_pendingExceptionDetails != null) {
debugPrint = debugPrintOverride; // just in case the test overrides it -- otherwise we won't see the error!
FlutterError.dumpErrorToConsole(_pendingExceptionDetails, forceReport: true); FlutterError.dumpErrorToConsole(_pendingExceptionDetails, forceReport: true);
// test_package.registerException actually just calls the current zone's error handler (that // test_package.registerException actually just calls the current zone's error handler (that
// is to say, _parentZone's handleUncaughtError function). FakeAsync doesn't add one of those, // is to say, _parentZone's handleUncaughtError function). FakeAsync doesn't add one of those,
...@@ -344,6 +345,7 @@ abstract class TestWidgetsFlutterBinding extends BindingBase ...@@ -344,6 +345,7 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
int _exceptionCount = 0; // number of un-taken exceptions int _exceptionCount = 0; // number of un-taken exceptions
FlutterError.onError = (FlutterErrorDetails details) { FlutterError.onError = (FlutterErrorDetails details) {
if (_pendingExceptionDetails != null) { if (_pendingExceptionDetails != null) {
debugPrint = debugPrintOverride; // just in case the test overrides it -- otherwise we won't see the errors!
if (_exceptionCount == 0) { if (_exceptionCount == 0) {
_exceptionCount = 2; _exceptionCount = 2;
FlutterError.dumpErrorToConsole(_pendingExceptionDetails, forceReport: true); FlutterError.dumpErrorToConsole(_pendingExceptionDetails, forceReport: true);
...@@ -369,6 +371,7 @@ abstract class TestWidgetsFlutterBinding extends BindingBase ...@@ -369,6 +371,7 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
// If we silently dropped these errors on the ground, nobody would ever know. So instead // If we silently dropped these errors on the ground, nobody would ever know. So instead
// we report them to the console. They don't cause test failures, but hopefully someone // we report them to the console. They don't cause test failures, but hopefully someone
// will see them in the logs at some point. // will see them in the logs at some point.
debugPrint = debugPrintOverride; // just in case the test overrides it -- otherwise we won't see the error!
FlutterError.dumpErrorToConsole(new FlutterErrorDetails( FlutterError.dumpErrorToConsole(new FlutterErrorDetails(
exception: exception, exception: exception,
stack: _unmangle(stack), stack: _unmangle(stack),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment