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
// bool _first;
// bool _lights;
// bool _visible;
// bool inherit;
// class Cat { }
// double _volume;
// dynamic _calculation;
......
......@@ -430,7 +430,7 @@ class _CupertinoEdgeShadowDecoration extends Decoration {
}) {
return new DiagnosticsNode.lazy(
name: name,
object: this,
value: this,
style: style,
description: '$runtimeType',
fillProperties: (List<DiagnosticsNode> properties) {
......
......@@ -67,6 +67,9 @@ enum DiagnosticsTreeStyle {
/// * [DiagnosticsNode.toStringDeep], for code using [TextTreeConfiguration]
/// to render text art for arbitrary trees of [DiagnosticsNode] objects.
class TextTreeConfiguration {
/// Create a configuration object describing how to render a tree as text.
///
/// All of the arguments must not be null.
TextTreeConfiguration({
@required this.prefixLineOne,
@required this.prefixOtherLines,
......@@ -87,7 +90,26 @@ class TextTreeConfiguration {
this.addBlankLineIfNoChildren: true,
this.isNameOnOwnLine: false,
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.
final String prefixLineOne;
......@@ -550,40 +572,48 @@ class _NoDefaultValue {
/// Marker object indicating that a DiagnosticNode has no default value.
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
/// [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.
abstract class DiagnosticsNode {
/// Initializes the object.
///
/// The [style], [showName] and [showSeparator] arguments must not be null.
DiagnosticsNode({
@required this.name,
this.style: DiagnosticsTreeStyle.sparse,
this.showName: true,
this.showSeparator: true,
this.emptyBodyDescription,
}) {
}) : assert(style != null),
assert(showName != null),
assert(showSeparator != null) {
// A name ending with ':' indicates that the user forgot that the ':' will
// be automatically added for them when generating descriptions of the
// 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
/// are computed lazily.
///
/// The [style] argument must not be null.
factory DiagnosticsNode.lazy({
String name,
Object object,
Object value,
String description,
FillPropertiesCallback fillProperties,
GetChildrenCallback getChildren,
String emptyBodyDescription,
DiagnosticsTreeStyle style: DiagnosticsTreeStyle.sparse,
}) {
assert(style != null);
return new _LazyMembersDiagnosticsNode(
name: name,
object: object,
value: value,
description: description,
fillProperties: fillProperties,
getChildren: getChildren,
......@@ -592,10 +622,11 @@ abstract class DiagnosticsNode {
);
}
/// Diagnostics containing just a string `message` and not a concrete name or
/// value.
///
/// The [style] argument must not be null.
///
/// See also:
///
/// * [PropertyMessage], which should be used if the message should be
......@@ -604,6 +635,7 @@ abstract class DiagnosticsNode {
String message, {
DiagnosticsTreeStyle style: DiagnosticsTreeStyle.singleLine,
}) {
assert(style != null);
return new DiagnosticsProperty<Null>(
'',
null,
......@@ -630,13 +662,18 @@ abstract class DiagnosticsNode {
/// view of a tree.
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;
/// Description to show if the node has no displayed properties or children.
final String emptyBodyDescription;
/// Dart object this is diagnostics data for.
Object get object;
/// The actual object this is diagnostics data for.
Object get value;
/// Hint for how the node should be displayed.
final DiagnosticsTreeStyle style;
......@@ -658,17 +695,19 @@ abstract class DiagnosticsNode {
@override
String toString() {
assert(style != null);
if (style == DiagnosticsTreeStyle.singleLine)
return toStringDeep();
if (name == null || name.isEmpty || showName == false)
if (name == null || name.isEmpty || !showName)
return description;
return description.contains('\n') ?
'$name$_separator\n$description' : '$name$_separator $description';
return description.contains('\n') ? '$name$_separator\n$description'
: '$name$_separator $description';
}
TextTreeConfiguration get textTreeConfiguration {
assert(style != null);
switch (style) {
case DiagnosticsTreeStyle.dense:
return denseTextConfiguration;
......@@ -683,7 +722,7 @@ abstract class DiagnosticsNode {
case DiagnosticsTreeStyle.singleLine:
return singleLineTextConfiguration;
}
return sparseTextConfiguration;
return null;
}
/// Text configuration to use to connect this node to a `child`.
......@@ -707,8 +746,8 @@ abstract class DiagnosticsNode {
///
/// See also:
///
/// * [toString], for a brief description of the object but not its children.
/// * [toStringShallow], for a detailed description of the object but not its
/// * [toString], for a brief description of the [value] but not its children.
/// * [toStringShallow], for a detailed description of the [value] but not its
/// children.
String toStringDeep([String prefixLineOne = '', String prefixOtherLines = '']) {
prefixOtherLines ??= prefixLineOne;
......@@ -770,8 +809,7 @@ abstract class DiagnosticsNode {
));
continue;
}
assert (property.style == null ||
property.style == DiagnosticsTreeStyle.singleLine);
assert(property.style == DiagnosticsTreeStyle.singleLine);
final String message = property == null ? '<null>' : property.toString();
if (config.isSingleLine || message.length < kWrapWidth) {
builder.write(message);
......@@ -847,9 +885,10 @@ abstract class DiagnosticsNode {
/// Debugging message displayed like a property.
///
/// The following two properties should be a [MessageProperty] not
/// [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.
/// The following two properties should be a [MessageProperty], not
/// [StringProperty], as the intent is to show a message with property style
/// display rather than to describe the value of an actual property of the
/// object.
///
/// ```dart
/// new MessageProperty('table size', '$columns\u00D7$rows'));
......@@ -858,6 +897,7 @@ abstract class DiagnosticsNode {
///
/// StringProperty should be used if the property has a concrete value that is
/// a string.
///
/// ```dart
/// new StringProperty('fontFamily', fontFamily);
/// new StringProperty('title', title):
......@@ -870,7 +910,16 @@ abstract class DiagnosticsNode {
/// * [StringProperty], which should be used instead for properties with string
/// values.
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.
......@@ -880,6 +929,9 @@ class MessageProperty extends DiagnosticsProperty<Null> {
/// * [MessageProperty], which should be used instead if showing a message
/// instead of describing a property with a string value.
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, {
String description,
bool showName: true,
......@@ -887,7 +939,10 @@ class StringProperty extends DiagnosticsProperty<String> {
bool hidden: false,
this.quoted: true,
String ifEmpty,
}) : super(
}) : assert(showName != null),
assert(hidden != null),
assert(quoted != null),
super(
name,
value,
description: description,
......@@ -974,7 +1029,6 @@ abstract class _NumProperty<T extends num> extends DiagnosticsProperty<T> {
///
/// Numeric formatting is optimized for debug message readability.
class DoubleProperty extends _NumProperty<double> {
/// If specified, `unit` describes the unit for the [value] (e.g. px).
DoubleProperty(String name, double value, {
bool hidden: false,
......@@ -1017,20 +1071,25 @@ class DoubleProperty extends _NumProperty<double> {
);
@override
String numberToString() => object?.toStringAsFixed(1);
String numberToString() => value?.toStringAsFixed(1);
}
/// An int valued property with an optional unit the value is measured in.
///
/// Examples of units include 'px' and 'ms'.
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, {
String ifNull,
bool showName: true,
String unit,
Object defaultValue: kNoDefaultValue,
bool hidden: false,
}) : super(
}) : assert(showName != null),
assert(hidden != null),
super(
name,
value,
ifNull: ifNull,
......@@ -1041,31 +1100,42 @@ class IntProperty extends _NumProperty<int> {
);
@override
String numberToString() => object.toString();
String numberToString() => value.toString();
}
/// Property which clamps a [double] to between 0 and 1 and formats it as a
/// percentage.
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, {
String ifNull,
bool showName: true,
String tooltip,
String unit,
}) : super(
bool hidden: false,
}) : assert(showName != null),
assert(hidden != null),
super(
name,
fraction,
ifNull: ifNull,
showName: showName,
tooltip: tooltip,
unit: unit,
hidden: hidden,
);
@override
String valueToString() {
if (value == null)
return value.toString();
return unit != null ? '${numberToString()} $unit' : numberToString();
}
......@@ -1073,19 +1143,18 @@ class PercentProperty extends DoubleProperty {
String numberToString() {
if (value == null)
return value.toString();
return '${(value.clamp(0.0, 1.0) * 100.0).toStringAsFixed(1)}%';
}
}
/// 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
/// diagnostics display more polished. For example, Given a property named
/// Using [FlagProperty] instead of [DiagnosticsProperty<bool>] can make
/// diagnostics display more polished. For example, given a property named
/// `visible` that is typically true, the following code will return 'hidden'
/// when `visible` is false and the empty string in contrast to `visible: true`
/// or `visible: false`.
/// when `visible` is false and nothing when visible is true, in contrast to
/// `visible: true` or `visible: false`.
///
/// ## Sample code
///
......@@ -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
/// property value.
///
/// ## Sample code
///
/// ```dart
/// new FlagProperty(
/// 'inherit',
......@@ -1115,7 +1182,7 @@ class PercentProperty extends DoubleProperty {
/// See also:
///
/// * [ObjectFlagProperty], which provides similar behavior describing whether
/// a `value` is `null`.
/// a [value] is null.
class FlagProperty extends DiagnosticsProperty<bool> {
/// Constructs a FlagProperty with the given descriptions with the specified descriptions.
///
......@@ -1138,15 +1205,15 @@ class FlagProperty extends DiagnosticsProperty<bool> {
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].
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].
final String ifFalse;
......@@ -1161,13 +1228,12 @@ class FlagProperty extends DiagnosticsProperty<bool> {
@override
bool get hidden {
if (_hidden || object == defaultValue)
if (_hidden || value == defaultValue)
return true;
if (object == true)
if (value == true)
return ifTrue == null;
if (object == false)
if (value == false)
return ifFalse == null;
return true;
}
}
......@@ -1175,31 +1241,42 @@ class FlagProperty extends DiagnosticsProperty<bool> {
/// Property with an `Iterable<T>` [value] that can be displayed with
/// 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
/// break separated list.
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, {
Object defaultValue: kNoDefaultValue,
String ifNull,
String ifEmpty = '[]',
String ifEmpty: '[]',
DiagnosticsTreeStyle style: DiagnosticsTreeStyle.singleLine,
}) : super(
bool hidden: false,
}) : assert(ifEmpty != null),
assert(style != null),
assert(hidden != null),
super(
name,
value,
defaultValue: defaultValue,
ifNull: ifNull,
ifEmpty: ifEmpty,
style: style,
hidden: hidden,
);
@override
String valueToString() {
if (value == null)
return value.toString();
return style == DiagnosticsTreeStyle.singleLine ?
value.join(', ') : object.join('\n');
return style == DiagnosticsTreeStyle.singleLine ? value.join(', ')
: value.join('\n');
}
}
......@@ -1213,6 +1290,9 @@ class IterableProperty<T> extends DiagnosticsProperty<Iterable<T>> {
/// * [DiagnosticsProperty] which documents named parameters common to all
/// [DiagnosticsProperty]
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, {
Object defaultValue: kNoDefaultValue,
bool hidden: false,
......@@ -1227,37 +1307,50 @@ class EnumProperty<T> extends DiagnosticsProperty<T> {
String valueToString() {
if (value == null)
return value.toString();
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]
/// when it is present and `null` respectively. If [ifPresent] or [ifNull] is
/// omitted, that is taken to mean that [hidden] should be `true`when [value] is
/// present and null respectively.
/// The [ifPresent] and [ifNull] strings describe the property [value] when it
/// 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
/// 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:
///
/// * [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> {
/// 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, {
this.ifPresent,
String ifNull,
bool showName: false,
bool hidden: false,
}) : super(
}) : assert(ifPresent != null || ifNull != null),
assert(showName != null),
assert(hidden != null),
super(
name,
value,
showName: showName,
hidden: hidden,
ifNull: ifNull,
) {
assert(ifPresent != null || ifNull != null);
}
);
/// Shorthand constructor to describe whether the property has a value.
///
......@@ -1273,9 +1366,9 @@ class ObjectFlagProperty<T> extends DiagnosticsProperty<T> {
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].
final String ifPresent;
......@@ -1291,10 +1384,8 @@ class ObjectFlagProperty<T> extends DiagnosticsProperty<T> {
bool get hidden {
if (super.hidden)
return true;
if (object != null)
if (value != null)
return ifPresent == null;
return ifNull == null;
}
}
......@@ -1308,12 +1399,18 @@ typedef T ComputePropertyValueCallback<T>();
/// Property with a [value] of type [T].
///
/// If the default `object.toString()` does not provide an adequate description
/// of the object, specify `description` defining a custom description.
/// * `hidden` specifies whether the property should be hidden.
/// * `showSeparator` indicates whether a separator should be placed
/// between the property `name` and `object`.
/// If the default `value.toString()` does not provide an adequate description
/// of the value, specify [description] defining a custom description.
///
/// The [hidden] property specifies whether the property should be hidden from
/// 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 {
/// Create a diagnostics property.
///
/// The [hidden], [showName], [showSeparator], and [style] arguments must not be null.
DiagnosticsProperty(
String name,
T value, {
......@@ -1325,8 +1422,12 @@ class DiagnosticsProperty<T> extends DiagnosticsNode {
bool showSeparator: true,
this.defaultValue: kNoDefaultValue,
this.tooltip,
DiagnosticsTreeStyle style : DiagnosticsTreeStyle.singleLine,
}) : _description = description,
DiagnosticsTreeStyle style: DiagnosticsTreeStyle.singleLine,
}) : assert(hidden != null),
assert(showName != null),
assert(showSeparator != null),
assert(style != null),
_description = description,
_valueComputed = true,
_value = value,
_computeValue = null,
......@@ -1342,6 +1443,9 @@ class DiagnosticsProperty<T> extends DiagnosticsNode {
///
/// Use if computing the property [value] may throw an exception or is
/// expensive.
///
/// The [hidden], [showName], [showSeparator], and [style] arguments must not
/// be null.
DiagnosticsProperty.lazy(
String name,
ComputePropertyValueCallback<T> computeValue, {
......@@ -1353,8 +1457,13 @@ class DiagnosticsProperty<T> extends DiagnosticsNode {
bool showSeparator: true,
this.defaultValue: kNoDefaultValue,
this.tooltip,
DiagnosticsTreeStyle style : DiagnosticsTreeStyle.singleLine,
}) : _description = description,
DiagnosticsTreeStyle style: DiagnosticsTreeStyle.singleLine,
}) : assert(hidden != null),
assert(showName != null),
assert(showSeparator != null),
assert(defaultValue == kNoDefaultValue || defaultValue is T),
assert(style != null),
_description = description,
_valueComputed = false,
_value = null,
_computeValue = computeValue,
......@@ -1382,21 +1491,27 @@ class DiagnosticsProperty<T> extends DiagnosticsNode {
@override
String get description {
if (_description != null)
return addTooltip(_description);
return _addTooltip(_description);
if (exception != null)
return 'EXCEPTION (${exception.runtimeType})';
if (ifNull != null && object == null)
return addTooltip(ifNull);
if (ifNull != null && value == null)
return _addTooltip(ifNull);
String result = valueToString();
if (result.isEmpty && ifEmpty != null)
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)';
}
......@@ -1414,18 +1529,30 @@ class DiagnosticsProperty<T> extends DiagnosticsNode {
/// generating the string description.
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;
/// Returns the value of the property either from cache or by invoking a
/// [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.
///
/// See also:
///
/// * [valueToString], which converts the property value to a string.
@override
T get value {
_maybeCacheValue();
return _value;
......@@ -1435,9 +1562,6 @@ class DiagnosticsProperty<T> extends DiagnosticsNode {
bool _valueComputed;
@override
T get object => value;
Object _exception;
/// Exception thrown if accessing the property [value] threw an exception.
......@@ -1466,16 +1590,19 @@ class DiagnosticsProperty<T> extends DiagnosticsNode {
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
bool get hidden {
if (_hidden)
return true;
if (defaultValue != kNoDefaultValue) {
if (exception != null)
return false;
return object == defaultValue;
return value == defaultValue;
}
return false;
}
......@@ -1509,12 +1636,13 @@ class _LazyMembersDiagnosticsNode extends DiagnosticsNode {
_LazyMembersDiagnosticsNode({
@required String name,
@required String description,
@required this.object,
@required this.value,
GetChildrenCallback getChildren,
FillPropertiesCallback fillProperties,
String emptyBodyDescription,
DiagnosticsTreeStyle style: DiagnosticsTreeStyle.sparse,
}) : _description = description,
}) : assert(style != null),
_description = description,
_getChildren = getChildren,
_fillProperties = fillProperties,
super(
......@@ -1524,7 +1652,7 @@ class _LazyMembersDiagnosticsNode extends DiagnosticsNode {
);
@override
final Object object;
final Object value;
final GetChildrenCallback _getChildren;
final FillPropertiesCallback _fillProperties;
......@@ -1535,7 +1663,7 @@ class _LazyMembersDiagnosticsNode extends DiagnosticsNode {
bool get hidden => false;
@override
String get description => _description ?? object.toString();
String get description => _description ?? value.toString();
@override
List<DiagnosticsNode> getProperties() {
......@@ -1553,48 +1681,56 @@ class _LazyMembersDiagnosticsNode extends DiagnosticsNode {
/// [DiagnosticsNode] for an instance of [TreeDiagnosticsMixin].
class _TreeDiagnosticsMixinNode extends DiagnosticsNode {
@override
final TreeDiagnosticsMixin object;
_TreeDiagnosticsMixinNode({
String name,
this.object,
DiagnosticsTreeStyle style,
}) : super(
this.value,
@required DiagnosticsTreeStyle style,
}) : assert(style != null),
super(
name: name,
style: style,
);
@override
final TreeDiagnosticsMixin value;
@override
List<DiagnosticsNode> getProperties() {
final List<DiagnosticsNode> description = <DiagnosticsNode>[];
if (object != null)
object.debugFillProperties(description);
if (value != null)
value.debugFillProperties(description);
return description;
}
@override
List<DiagnosticsNode> getChildren() {
if (object != null)
return object.debugDescribeChildren();
if (value != null)
return value.debugDescribeChildren();
return <DiagnosticsNode>[];
}
@override
String get description => object.toString();
String get description => value.toString();
@override bool get hidden => false;
}
/// 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) {
return object.hashCode.toUnsigned(20).toRadixString(16).padLeft(5, '0');
}
/// 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
/// Returns a short description of an enum value.
......@@ -1672,6 +1808,8 @@ String camelCaseToHyphenatedName(String word) {
/// * [DiagnosticsNode.lazy], which should be used to create a DiagnosticNode
/// with children and properties where [TreeDiagnosticsMixin] cannot be used.
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();
/// The [toStringDeep] method takes arguments, but those are intended for
......@@ -1686,9 +1824,14 @@ abstract class TreeDiagnostics {
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
/// trees.
///
......@@ -1751,10 +1894,11 @@ abstract class TreeDiagnosticsMixin implements TreeDiagnostics {
}
@override
DiagnosticsNode toDiagnosticsNode({ String name, DiagnosticsTreeStyle style }) {
DiagnosticsNode toDiagnosticsNode({ String name, DiagnosticsTreeStyle style: DiagnosticsTreeStyle.sparse }) {
assert(style != null);
return new _TreeDiagnosticsMixinNode(
name: name,
object: this,
value: this,
style: style,
);
}
......@@ -1774,8 +1918,8 @@ abstract class TreeDiagnosticsMixin implements TreeDiagnostics {
/// very useful.
///
/// * Use `defaultValue` any time the default value of a property is
/// uninteresting. For example, specify a default value of `null` any time
/// a property being `null` does not indicate an error.
/// uninteresting. For example, specify a default value of null any time
/// a property being null does not indicate an error.
/// * Avoid specifying the `hidden` parameter unless the result you want
/// cannot be be achieved by using the `defaultValue` parameter or using
/// the [ObjectFlagProperty] class to conditionally display the property
......@@ -1839,7 +1983,10 @@ abstract class TreeDiagnosticsMixin implements TreeDiagnostics {
/// common [DiagnosticsProperty] parameters.
///
/// ```dart
/// abstract class ExampleObject extends Object with TreeDiagnosticsMixin {
/// abstract class ExampleObject extends ExampleSuperclass with TreeDiagnosticsMixin {
///
/// // ...various members and properties...
///
/// @override
/// void debugFillProperties(List<DiagnosticsNode> description) {
/// // Always add properties from the base class first.
......@@ -1956,32 +2103,6 @@ abstract class TreeDiagnosticsMixin implements TreeDiagnostics {
/// 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 {
/// Used by [toStringDeep], [toDiagnosticsNode] and [toStringShallow].
@protected
List<DiagnosticsNode> debugDescribeChildren() {
return <DiagnosticsNode>[];
return const <DiagnosticsNode>[];
}
}
......@@ -4,6 +4,10 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'debug.dart';
/// Whether the gesture was accepted or rejected.
enum GestureDisposition {
/// This gesture was accepted as the interpretation of the user's input.
......@@ -42,7 +46,8 @@ class GestureArenaEntry {
/// 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) {
_arena._resolve(_pointer, _member, disposition);
}
......@@ -64,19 +69,47 @@ class _GestureArena {
assert(isOpen);
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.
///
/// See [https://flutter.io/gestures/#gesture-disambiguation] for more
/// information about the role this class plays in the gesture system.
///
/// To debug problems with gestures, consider using
/// [debugPrintGestureArenaDiagnostics].
class GestureArenaManager {
final Map<int, _GestureArena> _arenas = <int, _GestureArena>{};
/// Adds a new member (e.g., gesture recognizer) to the arena.
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);
assert(_debugLogDiagnostic(pointer, 'Adding: $member'));
return new GestureArenaEntry._(this, pointer, member);
}
......@@ -88,6 +121,7 @@ class GestureArenaManager {
if (state == null)
return; // This arena either never existed or has been resolved.
state.isOpen = false;
assert(_debugLogDiagnostic(pointer, 'Closing', state));
_tryToResolveArena(pointer, state);
}
......@@ -111,13 +145,16 @@ class GestureArenaManager {
assert(!state.isOpen);
if (state.isHeld) {
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);
if (state.members.isNotEmpty) {
// First member wins
// First member wins.
assert(_debugLogDiagnostic(pointer, 'Winner: ${state.members.first}'));
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++)
state.members[i].rejectGesture(pointer);
}
......@@ -140,6 +177,7 @@ class GestureArenaManager {
if (state == null)
return; // This arena either never existed or has been resolved.
state.isHeld = true;
assert(_debugLogDiagnostic(pointer, 'Holding', state));
}
/// Releases a hold, allowing the arena to be swept.
......@@ -156,37 +194,19 @@ class GestureArenaManager {
if (state == null)
return; // This arena either never existed or has been resolved.
state.isHeld = false;
assert(_debugLogDiagnostic(pointer, 'Releasing', state));
if (state.hasPendingSweep)
sweep(pointer);
}
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);
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);
}
}
/// Reject or accept a gesture recognizer.
///
/// This is called by calling [GestureArenaEntry.resolve] on the object returned from [add].
void _resolve(int pointer, GestureArenaMember member, GestureDisposition disposition) {
final _GestureArena state = _arenas[pointer];
if (state == null)
return; // This arena has already resolved.
assert(_debugLogDiagnostic(pointer, '${ disposition == GestureDisposition.accepted ? "Accepting" : "Rejecting" }: $member'));
assert(state.members.contains(member));
if (disposition == GestureDisposition.rejected) {
state.members.remove(member);
......@@ -198,11 +218,38 @@ class GestureArenaManager {
if (state.isOpen) {
state.eagerWinner ??= member;
} else {
assert(_debugLogDiagnostic(pointer, 'Self-declared winner: $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) {
assert(state == _arenas[pointer]);
assert(state != null);
......@@ -215,4 +262,16 @@ class GestureArenaManager {
}
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
/// displayed on the screen, from the moment they were last requested.
const Duration kZoomControlsTimeout = const Duration(milliseconds: 3000);
/// The distance a touch has to travel for us to be confident that the gesture
/// is a scroll gesture.
const double kTouchSlop = 8.0; // Logical pixels
/// The distance a touch has to travel for us to be confident that the gesture
/// is a paging gesture. (Currently not used, because paging uses a regular drag
/// gesture, which uses kTouchSlop.)
/// The distance a touch has to travel for the framework to be confident that
/// the gesture is a scroll gesture, or, inversely, the maximum distance that a
/// touch can travel before the framework becomes confident that it is not a
/// tap.
// This value was empirically derived. We started at 8.0 and increased it to
// 18.0 after getting complaints that it was too difficult to hit targets.
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
// paging, which use this constant.
const double kPagingTouchSlop = kTouchSlop * 2.0; // Logical pixels
/// The distance a touch has to travel for us to be confident that the gesture
/// is a panning gesture.
/// The distance a touch has to travel for the framework to be confident that
/// the gesture is a panning gesture.
const double kPanSlop = kTouchSlop * 2.0; // Logical pixels
/// The distance a touch has to travel for us to be confident that the gesture
/// is a scale gesture.
/// The distance a touch has to travel for the framework to be confident that
/// the gesture is a scale gesture.
const double kScaleSlop = kTouchSlop; // Logical pixels
/// The margin around a dialog, popup menu, or other window-like widget inside
......
......@@ -15,6 +15,31 @@ import 'package:flutter/foundation.dart';
/// This has no effect in release builds.
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.
///
/// This function is used by the test framework to ensure that debug variables
......@@ -24,7 +49,9 @@ bool debugPrintHitTestResults = false;
/// a complete list.
bool debugAssertAllGesturesVarsUnset(String reason) {
assert(() {
if (debugPrintHitTestResults)
if (debugPrintHitTestResults ||
debugPrintGestureArenaDiagnostics ||
debugPrintRecognizerCallbacksTrace)
throw new FlutterError(reason);
return true;
});
......
......@@ -17,7 +17,7 @@ class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer {
/// Creates a long-press gesture recognizer.
///
/// 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.
GestureLongPressCallback onLongPress;
......
......@@ -43,10 +43,13 @@ typedef void GestureDragCancelCallback();
///
/// See also:
///
/// * [HorizontalDragGestureRecognizer]
/// * [VerticalDragGestureRecognizer]
/// * [PanGestureRecognizer]
/// * [HorizontalDragGestureRecognizer], for left and right drags.
/// * [VerticalDragGestureRecognizer], for up and down drags.
/// * [PanGestureRecognizer], for drags that are not locked to a single axis.
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.
///
/// The position of the pointer is provided in the callback's `details`
......@@ -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
velocity: velocity,
primaryVelocity: _getPrimaryValueFromOffset(velocity.pixelsPerSecond),
)));
)), debugReport: () {
return '$estimate; fling at $velocity.';
});
} else {
invokeCallback<Null>('onEnd', () => onEnd(new DragEndDetails( // ignore: STRONG_MODE_INVALID_CAST_FUNCTION_EXPR, https://github.com/dart-lang/sdk/issues/27504
velocity: Velocity.zero,
primaryVelocity: 0.0,
)));
)), debugReport: () {
if (estimate == null)
return 'Could not estimate velocity.';
return '$estimate; judged to not be a fling.';
});
}
}
_velocityTrackers.clear();
......@@ -218,8 +227,14 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer {
///
/// 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 {
/// Create a gesture recognizer for interactions in the vertical axis.
VerticalDragGestureRecognizer({ Object debugOwner }) : super(debugOwner: debugOwner);
@override
bool _isFlingGesture(VelocityEstimate estimate) {
final double minVelocity = minFlingVelocity ?? kMinFlingVelocity;
......@@ -246,8 +261,14 @@ class VerticalDragGestureRecognizer extends DragGestureRecognizer {
///
/// 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 {
/// Create a gesture recognizer for interactions in the horizontal axis.
HorizontalDragGestureRecognizer({ Object debugOwner }) : super(debugOwner: debugOwner);
@override
bool _isFlingGesture(VelocityEstimate estimate) {
final double minVelocity = minFlingVelocity ?? kMinFlingVelocity;
......@@ -272,15 +293,21 @@ class HorizontalDragGestureRecognizer extends DragGestureRecognizer {
///
/// See also:
///
/// * [ImmediateMultiDragGestureRecognizer]
/// * [DelayedMultiDragGestureRecognizer]
/// * [ImmediateMultiDragGestureRecognizer], for a similar recognizer that
/// 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 {
/// Create a gesture recognizer for tracking movement on a plane.
PanGestureRecognizer({ Object debugOwner }) : super(debugOwner: debugOwner);
@override
bool _isFlingGesture(VelocityEstimate estimate) {
final double minVelocity = minFlingVelocity ?? kMinFlingVelocity;
final double minDistance = minFlingDistance ?? kTouchSlop;
return estimate.pixelsPerSecond.distanceSquared > minVelocity * minVelocity &&
estimate.offset.distanceSquared > minDistance * minDistance;
return estimate.pixelsPerSecond.distanceSquared > minVelocity * minVelocity
&& estimate.offset.distanceSquared > minDistance * minDistance;
}
@override
......
......@@ -169,11 +169,18 @@ abstract class MultiDragPointerState {
///
/// See also:
///
/// * [HorizontalMultiDragGestureRecognizer]
/// * [VerticalMultiDragGestureRecognizer]
/// * [ImmediateMultiDragGestureRecognizer]
/// * [DelayedMultiDragGestureRecognizer]
/// * [ImmediateMultiDragGestureRecognizer], the most straight-forward variant
/// of multi-pointer drag gesture recognizer.
/// * [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.
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.
///
/// The remaining notifications for this drag gesture are delivered to the
......@@ -308,9 +315,18 @@ class _ImmediatePointerState extends MultiDragPointerState {
///
/// See also:
///
/// * [PanGestureRecognizer]
/// * [DelayedMultiDragGestureRecognizer]
/// * [PanGestureRecognizer], which recognizes only one drag gesture at a time,
/// 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> {
/// Create a gesture recognizer for tracking multiple pointers at once.
ImmediateMultiDragGestureRecognizer({ Object debugOwner }) : super(debugOwner: debugOwner);
@override
_ImmediatePointerState createNewPointerState(PointerDownEvent event) {
return new _ImmediatePointerState(event.position);
......@@ -346,8 +362,17 @@ class _HorizontalPointerState extends MultiDragPointerState {
///
/// 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> {
/// Create a gesture recognizer for tracking multiple pointers at once
/// but only if they first move horizontally.
HorizontalMultiDragGestureRecognizer({ Object debugOwner }) : super(debugOwner: debugOwner);
@override
_HorizontalPointerState createNewPointerState(PointerDownEvent event) {
return new _HorizontalPointerState(event.position);
......@@ -383,8 +408,17 @@ class _VerticalPointerState extends MultiDragPointerState {
///
/// 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> {
/// Create a gesture recognizer for tracking multiple pointers at once
/// but only if they first move vertically.
VerticalMultiDragGestureRecognizer({ Object debugOwner }) : super(debugOwner: debugOwner);
@override
_VerticalPointerState createNewPointerState(PointerDownEvent event) {
return new _VerticalPointerState(event.position);
......@@ -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],
/// [DelayedMultiDragGestureRecognizer] waits for a [delay] before recognizing
......@@ -470,8 +505,10 @@ class _DelayedPointerState extends MultiDragPointerState {
///
/// See also:
///
/// * [PanGestureRecognizer]
/// * [ImmediateMultiDragGestureRecognizer]
/// * [ImmediateMultiDragGestureRecognizer], a similar recognizer but without
/// the delay.
/// * [PanGestureRecognizer], which recognizes only one drag gesture at a time,
/// regardless of how many fingers are involved.
class DelayedMultiDragGestureRecognizer extends MultiDragGestureRecognizer<_DelayedPointerState> {
/// Creates a drag recognizer that works on a per-pointer basis after a delay.
///
......@@ -480,8 +517,10 @@ class DelayedMultiDragGestureRecognizer extends MultiDragGestureRecognizer<_Dela
/// defaults to [kLongPressTimeout] to match [LongPressGestureRecognizer] but
/// can be changed for specific behaviors.
DelayedMultiDragGestureRecognizer({
this.delay: kLongPressTimeout
}) : assert(delay != null);
this.delay: kLongPressTimeout,
Object debugOwner,
}) : assert(delay != null),
super(debugOwner: debugOwner);
/// The amount of time the pointer must remain in the same place for the drag
/// to be recognized.
......
......@@ -68,6 +68,9 @@ class _TapTracker {
/// Recognizes when the user has tapped the screen at the same location twice in
/// quick succession.
class DoubleTapGestureRecognizer extends GestureRecognizer {
/// Create a gesture recognizer for double taps.
DoubleTapGestureRecognizer({ Object debugOwner }) : super(debugOwner: debugOwner);
// Implementation notes:
// 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
......@@ -315,8 +318,9 @@ class MultiTapGestureRecognizer extends GestureRecognizer {
/// The [longTapDelay] defaults to [Duration.ZERO], which means
/// [onLongTapDown] is called immediately after [onTapDown].
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
/// location.
......
......@@ -11,6 +11,7 @@ import 'package:flutter/foundation.dart';
import 'arena.dart';
import 'binding.dart';
import 'constants.dart';
import 'debug.dart';
import 'events.dart';
import 'pointer_router.dart';
import 'team.dart';
......@@ -32,7 +33,21 @@ typedef T RecognizerCallback<T>();
/// See also:
///
/// * [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
/// detector.
///
......@@ -58,16 +73,32 @@ abstract class GestureRecognizer extends GestureArenaMember {
/// Returns a very short pretty description of the gesture that the
/// recognizer looks for, like 'tap' or 'horizontal drag'.
String toStringShort() => toString();
String toStringShort();
/// Invoke a callback provided by the application, catching and logging any
/// 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
T invokeCallback<T>(String name, RecognizerCallback<T> callback) {
T invokeCallback<T>(String name, RecognizerCallback<T> callback, { String debugReport() }) {
assert(callback != null);
T result;
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();
} catch (exception, stack) {
FlutterError.reportError(new FlutterErrorDetails(
......@@ -86,7 +117,21 @@ abstract class GestureRecognizer extends GestureArenaMember {
}
@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
......@@ -98,6 +143,9 @@ abstract class GestureRecognizer extends GestureArenaMember {
/// which manages each pointer independently and can consider multiple
/// simultaneous touches to each result in a separate tap.
abstract class OneSequenceGestureRecognizer extends GestureRecognizer {
/// Initialize the object.
OneSequenceGestureRecognizer({ Object debugOwner }) : super(debugOwner: debugOwner);
final Map<int, GestureArenaEntry> _entries = <int, GestureArenaEntry>{};
final Set<int> _trackedPointers = new HashSet<int>();
......@@ -227,9 +275,15 @@ enum GestureRecognizerState {
}
/// 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 {
/// 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
/// amount of time has elapsed since starting to track the primary pointer.
......@@ -321,5 +375,8 @@ abstract class PrimaryPointerGestureRecognizer extends OneSequenceGestureRecogni
}
@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) {
/// change, the recognizer calls [onUpdate]. When the pointers are no longer in
/// contact with the screen, the recognizer calls [onEnd].
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
/// initial scale of 1.0.
GestureScaleStartCallback onStart;
......
......@@ -64,7 +64,7 @@ typedef void GestureTapCancelCallback();
/// * [MultiTapGestureRecognizer]
class TapGestureRecognizer extends PrimaryPointerGestureRecognizer {
/// 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
/// location.
......
......@@ -121,7 +121,7 @@ class VelocityEstimate {
final Offset offset;
@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 {
......
......@@ -1605,7 +1605,7 @@ class BoxDecoration extends Decoration {
DiagnosticsNode toDiagnosticsNode({ String name, DiagnosticsTreeStyle style: DiagnosticsTreeStyle.whitespace }) {
return new DiagnosticsNode.lazy(
name: name,
object: this,
value: this,
description: '',
style: style,
emptyBodyDescription: '<no decorations specified>',
......
......@@ -215,7 +215,7 @@ class FlutterLogoDecoration extends Decoration {
return new DiagnosticsNode.lazy(
name: name,
description: '$runtimeType',
object: this,
value: this,
style: style,
fillProperties: (List<DiagnosticsNode> properties) {
properties.add(new DiagnosticsNode.message('$lightColor/$darkColor on $textColor'));
......
......@@ -344,7 +344,7 @@ class TextSpan implements TreeDiagnostics {
}) {
return new DiagnosticsNode.lazy(
name: name,
object: this,
value: this,
description: '$runtimeType',
style: style,
fillProperties: (List<DiagnosticsNode> properties) {
......
......@@ -462,7 +462,7 @@ class TextStyle extends TreeDiagnostics {
}) {
return new DiagnosticsNode.lazy(
name: name,
object: this,
value: this,
style: style,
description: '$runtimeType',
fillProperties: debugFillProperties,
......
......@@ -169,6 +169,7 @@ List<String> debugDescribeTransform(Matrix4 transform) {
/// Property which handles [Matrix4] that represent transforms.
class TransformProperty extends DiagnosticsProperty<Matrix4> {
/// Create a diagnostics property for [Matrix4] objects.
TransformProperty(String name, Matrix4 value, {
Object defaultValue: kNoDefaultValue,
}) : super(
......
......@@ -118,11 +118,11 @@ class RenderEditable extends RenderBox {
_offset = offset {
assert(_showCursor != null);
assert(!_showCursor.value || cursorColor != null);
_tap = new TapGestureRecognizer()
_tap = new TapGestureRecognizer(debugOwner: this)
..onTapDown = _handleTapDown
..onTap = _handleTap
..onTapCancel = _handleTapCancel;
_longPress = new LongPressGestureRecognizer()
_longPress = new LongPressGestureRecognizer(debugOwner: this)
..onLongPress = _handleLongPress;
}
......
......@@ -636,7 +636,7 @@ class SliverGeometry implements TreeDiagnostics {
}) {
return new DiagnosticsNode.lazy(
name: name,
object: this,
value: this,
description: 'SliverGeometry',
style: style,
emptyBodyDescription: '<no decorations specified>',
......
......@@ -3341,7 +3341,7 @@ abstract class Element implements BuildContext, TreeDiagnostics {
DiagnosticsNode toDiagnosticsNode({ String name, DiagnosticsTreeStyle style }) {
return new DiagnosticsNode.lazy(
name: name,
object: this,
value: this,
getChildren: () {
final List<DiagnosticsNode> children = <DiagnosticsNode>[];
visitChildren((Element child) {
......
......@@ -296,7 +296,7 @@ class GestureDetector extends StatelessWidget {
if (onTapDown != null || onTapUp != null || onTap != null || onTapCancel != null) {
gestures[TapGestureRecognizer] = new GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
() => new TapGestureRecognizer(),
() => new TapGestureRecognizer(debugOwner: this),
(TapGestureRecognizer instance) {
instance
..onTapDown = onTapDown
......@@ -309,7 +309,7 @@ class GestureDetector extends StatelessWidget {
if (onDoubleTap != null) {
gestures[DoubleTapGestureRecognizer] = new GestureRecognizerFactoryWithHandlers<DoubleTapGestureRecognizer>(
() => new DoubleTapGestureRecognizer(),
() => new DoubleTapGestureRecognizer(debugOwner: this),
(DoubleTapGestureRecognizer instance) {
instance
..onDoubleTap = onDoubleTap;
......@@ -319,7 +319,7 @@ class GestureDetector extends StatelessWidget {
if (onLongPress != null) {
gestures[LongPressGestureRecognizer] = new GestureRecognizerFactoryWithHandlers<LongPressGestureRecognizer>(
() => new LongPressGestureRecognizer(),
() => new LongPressGestureRecognizer(debugOwner: this),
(LongPressGestureRecognizer instance) {
instance
..onLongPress = onLongPress;
......@@ -333,7 +333,7 @@ class GestureDetector extends StatelessWidget {
onVerticalDragEnd != null ||
onVerticalDragCancel != null) {
gestures[VerticalDragGestureRecognizer] = new GestureRecognizerFactoryWithHandlers<VerticalDragGestureRecognizer>(
() => new VerticalDragGestureRecognizer(),
() => new VerticalDragGestureRecognizer(debugOwner: this),
(VerticalDragGestureRecognizer instance) {
instance
..onDown = onVerticalDragDown
......@@ -351,7 +351,7 @@ class GestureDetector extends StatelessWidget {
onHorizontalDragEnd != null ||
onHorizontalDragCancel != null) {
gestures[HorizontalDragGestureRecognizer] = new GestureRecognizerFactoryWithHandlers<HorizontalDragGestureRecognizer>(
() => new HorizontalDragGestureRecognizer(),
() => new HorizontalDragGestureRecognizer(debugOwner: this),
(HorizontalDragGestureRecognizer instance) {
instance
..onDown = onHorizontalDragDown
......@@ -369,7 +369,7 @@ class GestureDetector extends StatelessWidget {
onPanEnd != null ||
onPanCancel != null) {
gestures[PanGestureRecognizer] = new GestureRecognizerFactoryWithHandlers<PanGestureRecognizer>(
() => new PanGestureRecognizer(),
() => new PanGestureRecognizer(debugOwner: this),
(PanGestureRecognizer instance) {
instance
..onDown = onPanDown
......@@ -383,7 +383,7 @@ class GestureDetector extends StatelessWidget {
if (onScaleStart != null || onScaleUpdate != null || onScaleEnd != null) {
gestures[ScaleGestureRecognizer] = new GestureRecognizerFactoryWithHandlers<ScaleGestureRecognizer>(
() => new ScaleGestureRecognizer(),
() => new ScaleGestureRecognizer(debugOwner: this),
(ScaleGestureRecognizer instance) {
instance
..onStart = onScaleStart
......@@ -397,7 +397,7 @@ class GestureDetector extends StatelessWidget {
gestures: gestures,
behavior: behavior,
excludeFromSemantics: excludeFromSemantics,
child: child
child: child,
);
}
}
......@@ -622,6 +622,7 @@ class RawGestureDetectorState extends State<RawGestureDetector> {
if (gestures.isEmpty)
gestures.add('<none>');
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));
}
......
......@@ -85,6 +85,12 @@ abstract class TextSelectionControls {
/// Returns the size of the selection handle.
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) {
final TextEditingValue value = delegate.textEditingValue;
Clipboard.setData(new ClipboardData(
......@@ -100,6 +106,12 @@ abstract class TextSelectionControls {
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) {
final TextEditingValue value = delegate.textEditingValue;
Clipboard.setData(new ClipboardData(
......@@ -112,6 +124,17 @@ abstract class TextSelectionControls {
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 {
final TextEditingValue value = delegate.textEditingValue; // Snapshot the input before using `await`.
final ClipboardData data = await Clipboard.getData(Clipboard.kTextPlain);
......@@ -128,6 +151,13 @@ abstract class TextSelectionControls {
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) {
delegate.textEditingValue = new TextEditingValue(
text: delegate.textEditingValue.text,
......
......@@ -641,9 +641,9 @@ void main() {
final DiagnosticsProperty<bool> falseProperty = new DiagnosticsProperty<bool>('name', false);
expect(trueProperty.toString(), equals('name: true'));
expect(trueProperty.hidden, isFalse);
expect(trueProperty.object, isTrue);
expect(trueProperty.value, isTrue);
expect(falseProperty.toString(), equals('name: false'));
expect(falseProperty.object, isFalse);
expect(falseProperty.value, isFalse);
expect(falseProperty.hidden, isFalse);
expect(
new DiagnosticsProperty<bool>(
......@@ -680,8 +680,8 @@ void main() {
);
expect(trueFlag.toString(), equals('myFlag'));
expect(trueFlag.object, isTrue);
expect(falseFlag.object, isFalse);
expect(trueFlag.value, isTrue);
expect(falseFlag.value, isFalse);
expect(trueFlag.hidden, isFalse);
expect(falseFlag.hidden, isTrue);
......@@ -697,7 +697,7 @@ void main() {
withTooltip.toString(),
equals('name: value (tooltip)'),
);
expect(withTooltip.object, equals('value'));
expect(withTooltip.value, equals('value'));
expect(withTooltip.hidden, isFalse);
});
......@@ -708,7 +708,7 @@ void main() {
);
expect(doubleProperty.toString(), equals('name: 42.0'));
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'));
......@@ -735,7 +735,7 @@ void main() {
);
expect(safe.toString(), equals('name: 42.0'));
expect(safe.hidden, isFalse);
expect(safe.object, equals(42.0));
expect(safe.value, equals(42.0));
expect(
new DoubleProperty.lazy('name', () => 1.3333).toString(),
......@@ -757,7 +757,7 @@ void main() {
);
// TODO(jacobr): it would be better if throwingProperty.object threw an
// exception.
expect(throwingProperty.object, isNull);
expect(throwingProperty.value, isNull);
expect(throwingProperty.hidden, isFalse);
expect(
throwingProperty.toString(),
......@@ -782,7 +782,7 @@ void main() {
);
expect(
new PercentProperty('name', 0.4).object,
new PercentProperty('name', 0.4).value,
0.4,
);
expect(
......@@ -847,7 +847,7 @@ void main() {
expect(present.toString(), equals('clickable'));
expect(present.hidden, isFalse);
expect(present.object, equals(onClick));
expect(present.value, equals(onClick));
expect(missing.toString(), equals(''));
expect(missing.hidden, isTrue);
});
......@@ -867,7 +867,7 @@ void main() {
expect(present.toString(), equals(''));
expect(present.hidden, isTrue);
expect(present.object, equals(onClick));
expect(present.value, equals(onClick));
expect(missing.toString(), equals('disabled'));
expect(missing.hidden, isFalse);
});
......@@ -889,10 +889,10 @@ void main() {
);
expect(yes.toString(), equals('name: YES'));
expect(yes.hidden, isFalse);
expect(yes.object, isTrue);
expect(yes.value, isTrue);
expect(no.toString(), equals('name: NO'));
expect(no.hidden, isFalse);
expect(no.object, isFalse);
expect(no.value, isFalse);
expect(
new FlagProperty(
......@@ -945,19 +945,19 @@ void main() {
null,
);
expect(hello.hidden, isFalse);
expect(hello.object, equals(ExampleEnum.hello));
expect(hello.value, equals(ExampleEnum.hello));
expect(hello.toString(), equals('name: hello'));
expect(world.hidden, isFalse);
expect(world.object, equals(ExampleEnum.world));
expect(world.value, equals(ExampleEnum.world));
expect(world.toString(), equals('name: world'));
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(nullEnum.hidden, isFalse);
expect(nullEnum.object, isNull);
expect(nullEnum.value, isNull);
expect(nullEnum.toString(), equals('name: null'));
final EnumProperty<ExampleEnum> matchesDefault = new EnumProperty<ExampleEnum>(
......@@ -966,7 +966,7 @@ void main() {
defaultValue: ExampleEnum.hello,
);
expect(matchesDefault.toString(), equals('name: hello'));
expect(matchesDefault.object, equals(ExampleEnum.hello));
expect(matchesDefault.value, equals(ExampleEnum.hello));
expect(matchesDefault.hidden, isTrue);
......@@ -986,7 +986,7 @@ void main() {
42,
);
expect(regular.toString(), equals('name: 42'));
expect(regular.object, equals(42));
expect(regular.value, equals(42));
expect(regular.hidden, isFalse);
final IntProperty nullValue = new IntProperty(
......@@ -994,7 +994,7 @@ void main() {
null,
);
expect(nullValue.toString(), equals('name: null'));
expect(nullValue.object, isNull);
expect(nullValue.value, isNull);
expect(nullValue.hidden, isFalse);
final IntProperty hideNull = new IntProperty(
......@@ -1003,7 +1003,7 @@ void main() {
defaultValue: null
);
expect(hideNull.toString(), equals('name: null'));
expect(hideNull.object, isNull);
expect(hideNull.value, isNull);
expect(hideNull.hidden, isTrue);
final IntProperty nullDescription = new IntProperty(
......@@ -1012,7 +1012,7 @@ void main() {
ifNull: 'missing',
);
expect(nullDescription.toString(), equals('name: missing'));
expect(nullDescription.object, isNull);
expect(nullDescription.value, isNull);
expect(nullDescription.hidden, isFalse);
final IntProperty hideName = new IntProperty(
......@@ -1021,7 +1021,7 @@ void main() {
showName: false,
);
expect(hideName.toString(), equals('42'));
expect(hideName.object, equals(42));
expect(hideName.value, equals(42));
expect(hideName.hidden, isFalse);
final IntProperty withUnit = new IntProperty(
......@@ -1030,7 +1030,7 @@ void main() {
unit: 'pt',
);
expect(withUnit.toString(), equals('name: 42pt'));
expect(withUnit.object, equals(42));
expect(withUnit.value, equals(42));
expect(withUnit.hidden, isFalse);
final IntProperty defaultValue = new IntProperty(
......@@ -1039,7 +1039,7 @@ void main() {
defaultValue: 42,
);
expect(defaultValue.toString(), equals('name: 42'));
expect(defaultValue.object, equals(42));
expect(defaultValue.value, equals(42));
expect(defaultValue.hidden, isTrue);
final IntProperty notDefaultValue = new IntProperty(
......@@ -1048,7 +1048,7 @@ void main() {
defaultValue: 42,
);
expect(notDefaultValue.toString(), equals('name: 43'));
expect(notDefaultValue.object, equals(43));
expect(notDefaultValue.value, equals(43));
expect(notDefaultValue.hidden, isFalse);
final IntProperty hidden = new IntProperty(
......@@ -1057,7 +1057,7 @@ void main() {
hidden: true,
);
expect(hidden.toString(), equals('name: 42'));
expect(hidden.object, equals(42));
expect(hidden.value, equals(42));
expect(hidden.hidden, isTrue);
});
......@@ -1067,7 +1067,7 @@ void main() {
'name',
rect,
);
expect(simple.object, equals(rect));
expect(simple.value, equals(rect));
expect(simple.hidden, isFalse);
expect(simple.toString(), equals('name: Rect.fromLTRB(0.0, 0.0, 20.0, 20.0)'));
......@@ -1076,7 +1076,7 @@ void main() {
rect,
description: 'small rect',
);
expect(withDescription.object, equals(rect));
expect(withDescription.value, equals(rect));
expect(withDescription.hidden, isFalse);
expect(withDescription.toString(), equals('name: small rect'));
......@@ -1084,7 +1084,7 @@ void main() {
'name',
null,
);
expect(nullProperty.object, isNull);
expect(nullProperty.value, isNull);
expect(nullProperty.hidden, isFalse);
expect(nullProperty.toString(), equals('name: null'));
......@@ -1093,7 +1093,7 @@ void main() {
null,
defaultValue: null,
);
expect(hideNullProperty.object, isNull);
expect(hideNullProperty.value, isNull);
expect(hideNullProperty.hidden, isTrue);
expect(hideNullProperty.toString(), equals('name: null'));
......@@ -1102,7 +1102,7 @@ void main() {
null,
ifNull: 'missing',
);
expect(nullDescription.object, isNull);
expect(nullDescription.value, isNull);
expect(nullDescription.hidden, isFalse);
expect(nullDescription.toString(), equals('name: missing'));
......@@ -1111,7 +1111,7 @@ void main() {
rect,
showName: false,
);
expect(hideName.object, equals(rect));
expect(hideName.value, equals(rect));
expect(hideName.hidden, isFalse);
expect(hideName.toString(), equals('Rect.fromLTRB(0.0, 0.0, 20.0, 20.0)'));
......@@ -1120,7 +1120,7 @@ void main() {
rect,
showSeparator: false,
);
expect(hideSeparator.object, equals(rect));
expect(hideSeparator.value, equals(rect));
expect(hideSeparator.hidden, isFalse);
expect(
hideSeparator.toString(),
......@@ -1135,7 +1135,7 @@ void main() {
() => rect,
description: 'small rect',
);
expect(simple.object, equals(rect));
expect(simple.value, equals(rect));
expect(simple.hidden, isFalse);
expect(simple.toString(), equals('name: small rect'));
......@@ -1144,7 +1144,7 @@ void main() {
() => null,
description: 'missing',
);
expect(nullProperty.object, isNull);
expect(nullProperty.value, isNull);
expect(nullProperty.hidden, isFalse);
expect(nullProperty.toString(), equals('name: missing'));
......@@ -1154,7 +1154,7 @@ void main() {
description: 'missing',
defaultValue: null,
);
expect(hideNullProperty.object, isNull);
expect(hideNullProperty.value, isNull);
expect(hideNullProperty.hidden, isTrue);
expect(hideNullProperty.toString(), equals('name: missing'));
......@@ -1164,7 +1164,7 @@ void main() {
description: 'small rect',
showName: false,
);
expect(hideName.object, equals(rect));
expect(hideName.value, equals(rect));
expect(hideName.hidden, isFalse);
expect(hideName.toString(), equals('small rect'));
......@@ -1174,7 +1174,7 @@ void main() {
description: 'missing',
defaultValue: null,
);
expect(throwingWithDescription.object, isNull);
expect(throwingWithDescription.value, isNull);
expect(throwingWithDescription.exception, isFlutterError);
expect(throwingWithDescription.hidden, false);
expect(throwingWithDescription.toString(), equals('name: missing'));
......@@ -1184,7 +1184,7 @@ void main() {
() => throw new FlutterError('Property not available'),
defaultValue: null,
);
expect(throwingProperty.object, isNull);
expect(throwingProperty.value, isNull);
expect(throwingProperty.exception, isFlutterError);
expect(throwingProperty.hidden, false);
expect(throwingProperty.toString(), equals('name: EXCEPTION (FlutterError)'));
......@@ -1200,7 +1200,7 @@ void main() {
color,
);
expect(simple.hidden, isFalse);
expect(simple.object, equals(color));
expect(simple.value, equals(color));
expect(simple.toString(), equals('name: Color(0xffffffff)'));
});
......@@ -1211,7 +1211,7 @@ void main() {
ifTrue: 'layout computed',
);
expect(show.name, equals('wasLayout'));
expect(show.object, isTrue);
expect(show.value, isTrue);
expect(show.hidden, isFalse);
expect(show.toString(), equals('layout computed'));
......@@ -1221,7 +1221,7 @@ void main() {
ifTrue: 'layout computed',
);
expect(hide.name, equals('wasLayout'));
expect(hide.object, isFalse);
expect(hide.value, isFalse);
expect(hide.hidden, isTrue);
expect(hide.toString(), equals(''));
});
......@@ -1233,7 +1233,7 @@ void main() {
onClick,
);
expect(has.name, equals('onClick'));
expect(has.object, equals(onClick));
expect(has.value, equals(onClick));
expect(has.hidden, isFalse);
expect(has.toString(), equals('has onClick'));
......@@ -1242,7 +1242,7 @@ void main() {
null,
);
expect(missing.name, equals('onClick'));
expect(missing.object, isNull);
expect(missing.value, isNull);
expect(missing.hidden, isTrue);
expect(missing.toString(), equals(''));
});
......@@ -1253,7 +1253,7 @@ void main() {
'ints',
ints,
);
expect(intsProperty.object, equals(ints));
expect(intsProperty.value, equals(ints));
expect(intsProperty.hidden, isFalse);
expect(intsProperty.toString(), equals('ints: 1, 2, 3'));
......@@ -1261,7 +1261,7 @@ void main() {
'name',
<Object>[],
);
expect(emptyProperty.object, isEmpty);
expect(emptyProperty.value, isEmpty);
expect(emptyProperty.hidden, isFalse);
expect(emptyProperty.toString(), equals('name: []'));
......@@ -1269,7 +1269,7 @@ void main() {
'list',
null,
);
expect(nullProperty.object, isNull);
expect(nullProperty.value, isNull);
expect(nullProperty.hidden, isFalse);
expect(nullProperty.toString(), equals('list: null'));
......@@ -1278,7 +1278,7 @@ void main() {
null,
defaultValue: null,
);
expect(hideNullProperty.object, isNull);
expect(hideNullProperty.value, isNull);
expect(hideNullProperty.hidden, isTrue);
expect(hideNullProperty.toString(), equals('list: null'));
......@@ -1290,7 +1290,7 @@ void main() {
'objects',
objects,
);
expect(objectsProperty.object, equals(objects));
expect(objectsProperty.value, equals(objects));
expect(objectsProperty.hidden, isFalse);
expect(
objectsProperty.toString(),
......@@ -1306,7 +1306,7 @@ void main() {
objects,
style: DiagnosticsTreeStyle.whitespace,
);
expect(multiLineProperty.object, equals(objects));
expect(multiLineProperty.value, equals(objects));
expect(multiLineProperty.hidden, isFalse);
expect(
multiLineProperty.toString(),
......@@ -1347,7 +1347,7 @@ void main() {
singleElementList,
style: DiagnosticsTreeStyle.whitespace,
);
expect(objectProperty.object, equals(singleElementList));
expect(objectProperty.value, equals(singleElementList));
expect(objectProperty.hidden, isFalse);
expect(
objectProperty.toString(),
......@@ -1373,13 +1373,13 @@ void main() {
final DiagnosticsNode message = new DiagnosticsNode.message('hello world');
expect(message.toString(), equals('hello world'));
expect(message.name, isEmpty);
expect(message.object, isNull);
expect(message.value, isNull);
expect(message.showName, isFalse);
final DiagnosticsNode messageProperty = new MessageProperty('diagnostics', 'hello world');
expect(messageProperty.toString(), equals('diagnostics: hello world'));
expect(messageProperty.name, equals('diagnostics'));
expect(messageProperty.object, isNull);
expect(messageProperty.value, isNull);
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() {
expect(didEndPan, isFalse);
expect(didTap, isFalse);
tester.route(pointer.move(const Offset(20.0, 20.0)));
expect(didStartPan, isTrue);
// touch should give up when it hits kTouchSlop, which was 18.0 when this test was last updated.
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;
expect(updatedScrollDelta, const Offset(10.0, 10.0));
expect(updatedScrollDelta, const Offset(10.0, 20.0));
updatedScrollDelta = null;
expect(didEndPan, isFalse);
expect(didTap, isFalse);
tester.route(pointer.move(const Offset(20.0, 25.0)));
expect(didStartPan, isFalse);
expect(updatedScrollDelta, const Offset(0.0, 5.0));
expect(updatedScrollDelta, const Offset(0.0, -5.0));
updatedScrollDelta = null;
expect(didEndPan, isFalse);
expect(didTap, isFalse);
......
......@@ -7,13 +7,12 @@ import 'package:flutter/gestures.dart';
import 'gesture_tester.dart';
class TestDrag extends Drag {
}
class TestDrag extends Drag { }
void main() {
setUp(ensureGestureBinding);
testGesture('MultiDrag control test', (GestureTester tester) {
testGesture('MultiDrag: moving before delay rejects', (GestureTester tester) {
final DelayedMultiDragGestureRecognizer drag = new DelayedMultiDragGestureRecognizer();
bool didStartDrag = false;
......@@ -29,12 +28,37 @@ void main() {
expect(didStartDrag, isFalse);
tester.async.flushMicrotasks();
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);
tester.async.elapse(kLongPressTimeout * 2);
tester.async.flushMicrotasks();
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);
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();
});
}
......@@ -60,7 +60,7 @@ void main() {
expect(log, <String>['long-tap-down 6']);
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']);
log.clear();
......
......@@ -6,22 +6,24 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/gestures.dart';
class TestGestureRecognizer extends GestureRecognizer {
TestGestureRecognizer({ Object debugOwner }) : super(debugOwner: debugOwner);
@override
String toString() => 'toString content';
String toStringShort() => 'toStringShort content';
@override
void addPointer(PointerDownEvent event) {}
void addPointer(PointerDownEvent event) { }
@override
void acceptGesture(int pointer) {}
void acceptGesture(int pointer) { }
@override
void rejectGesture(int pointer) {}
void rejectGesture(int pointer) { }
}
void main() {
test('GestureRecognizer.toStringShort defaults to toString', () {
final TestGestureRecognizer recognizer = new TestGestureRecognizer();
expect(recognizer.toStringShort(), equals(recognizer.toString()));
test('GestureRecognizer smoketest', () {
final TestGestureRecognizer recognizer = new TestGestureRecognizer(debugOwner: 0);
expect(recognizer, hasAGoodToStringDeep);
});
}
......@@ -39,7 +39,7 @@ void main() {
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);
tap.addPointer(down);
......@@ -211,7 +211,9 @@ void main() {
tester.route(down);
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']));
log.clear();
......@@ -240,7 +242,10 @@ void main() {
expect(log, isEmpty);
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 PointerDownEvent down3 = pointer3.down(const Offset(30.0, 30.0));
scale.addPointer(down3);
......@@ -250,7 +255,7 @@ void main() {
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']));
log.clear();
......
......@@ -41,7 +41,7 @@ void main() {
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(
pointer: 3,
position: const Offset(10.0, 10.0)
......@@ -57,6 +57,22 @@ void main() {
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) {
final TapGestureRecognizer tap = new TapGestureRecognizer();
......@@ -179,6 +195,39 @@ void main() {
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) {
final TapGestureRecognizer tap = new TapGestureRecognizer();
......
......@@ -5,6 +5,7 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter/gestures.dart';
void main() {
testWidgets('Verify that a tap dismisses a modal BottomSheet', (WidgetTester tester) async {
......@@ -102,7 +103,11 @@ void main() {
expect(showBottomSheetThenCalled, isFalse);
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)
expect(showBottomSheetThenCalled, isTrue);
......
......@@ -28,8 +28,7 @@ void main() {
transform,
);
expect(simple.name, equals('transform'));
expect(simple.object, equals(transform));
expect(simple.hidden, isFalse);
expect(simple.value, same(transform));
expect(
simple.toString(),
equals(
......@@ -46,8 +45,7 @@ void main() {
null,
);
expect(nullProperty.name, equals('transform'));
expect(nullProperty.object, isNull);
expect(nullProperty.hidden, isFalse);
expect(nullProperty.value, isNull);
expect(nullProperty.toString(), equals('transform: null'));
final TransformProperty hideNull = new TransformProperty(
......@@ -55,8 +53,7 @@ void main() {
null,
defaultValue: null,
);
expect(hideNull.object, isNull);
expect(hideNull.hidden, isTrue);
expect(hideNull.value, isNull);
expect(hideNull.toString(), equals('transform: null'));
});
......
......@@ -4,6 +4,7 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter/gestures.dart';
void main() {
testWidgets('Uncontested scrolls start immediately', (WidgetTester tester) async {
......@@ -59,13 +60,13 @@ void main() {
double dragDistance = 0.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(
onVerticalDragUpdate: (DragUpdateDetails details) { dragDistance += details.primaryDelta; },
onVerticalDragEnd: (DragEndDetails details) { gestureCount += 1; },
onHorizontalDragUpdate: (DragUpdateDetails details) { fail("gesture should not match"); },
onHorizontalDragEnd: (DragEndDetails details) { fail("gesture should not match"); },
onHorizontalDragUpdate: (DragUpdateDetails details) { fail('gesture should not match'); },
onHorizontalDragEnd: (DragEndDetails details) { fail('gesture should not match'); },
child: new Container(
color: const Color(0xFF00FF00),
)
......@@ -81,7 +82,7 @@ void main() {
await gesture.up();
expect(gestureCount, 2);
expect(dragDistance, 20.0);
expect(dragDistance, 40.0 * 2.0); // delta between down and up, twice
await tester.pumpWidget(new Container());
});
......
......@@ -35,7 +35,7 @@ void main() {
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();
expect(find.text('Alabama'), findsOneWidget);
......
......@@ -3,6 +3,7 @@
// found in the LICENSE file.
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/widgets.dart';
void main() {
......@@ -78,7 +79,7 @@ void main() {
final TestGesture gesture = await tester.startGesture(const Offset(100.0, 100.0));
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 gesture.up();
await tester.pump(const Duration(seconds: 1));
......
......@@ -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));
});
......
......@@ -324,6 +324,7 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
assert(Zone.current == _parentZone);
assert(_currentTestCompleter != 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);
// 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,
......@@ -344,6 +345,7 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
int _exceptionCount = 0; // number of un-taken exceptions
FlutterError.onError = (FlutterErrorDetails details) {
if (_pendingExceptionDetails != null) {
debugPrint = debugPrintOverride; // just in case the test overrides it -- otherwise we won't see the errors!
if (_exceptionCount == 0) {
_exceptionCount = 2;
FlutterError.dumpErrorToConsole(_pendingExceptionDetails, forceReport: true);
......@@ -369,6 +371,7 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
// 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
// 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(
exception: exception,
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