Improve documentation for PointerSignalResolver class (#78738)
This commit is contained in:
parent
cf903d7392
commit
351e319a47
@ -12,6 +12,15 @@ import 'package:process/process.dart';
|
||||
import '../test.dart';
|
||||
import 'common.dart';
|
||||
|
||||
/// Fails a test if the exit code of `result` is not the expected value. This
|
||||
/// is favored over `expect(result.exitCode, expectedExitCode)` because this
|
||||
/// will include the process result's stdio in the failure message.
|
||||
void expectExitCode(ProcessResult result, int expectedExitCode) {
|
||||
if (result.exitCode != expectedExitCode) {
|
||||
fail('Failure due to exit code ${result.exitCode}\nSTDOUT:\n${result.stdout}\nSTDERR:\n${result.stderr}');
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
group('verifyVersion()', () {
|
||||
MemoryFileSystem fileSystem;
|
||||
@ -103,14 +112,14 @@ void main() {
|
||||
ProcessResult result = await runScript(
|
||||
<String, String>{'SHARD': 'smoke_tests', 'SUBSHARD': '1_3'},
|
||||
);
|
||||
expect(result.exitCode, 0);
|
||||
expectExitCode(result, 0);
|
||||
// There are currently 6 smoke tests. This shard should contain test 1 and 2.
|
||||
expect(result.stdout, contains('Selecting subshard 1 of 3 (range 1-2 of 6)'));
|
||||
|
||||
result = await runScript(
|
||||
<String, String>{'SHARD': 'smoke_tests', 'SUBSHARD': '5_6'},
|
||||
);
|
||||
expect(result.exitCode, 0);
|
||||
expectExitCode(result, 0);
|
||||
// This shard should contain only test 5.
|
||||
expect(result.stdout, contains('Selecting subshard 5 of 6 (range 5-5 of 6)'));
|
||||
});
|
||||
@ -119,7 +128,7 @@ void main() {
|
||||
final ProcessResult result = await runScript(
|
||||
<String, String>{'SHARD': 'smoke_tests', 'SUBSHARD': '100_99'},
|
||||
);
|
||||
expect(result.exitCode, 1);
|
||||
expectExitCode(result, 1);
|
||||
expect(result.stdout, contains('Invalid subshard name'));
|
||||
});
|
||||
});
|
||||
|
@ -1847,6 +1847,8 @@ class _TransformedPointerUpEvent extends _TransformedPointerEvent with _CopyPoin
|
||||
///
|
||||
/// * [Listener.onPointerSignal], which allows callers to be notified of these
|
||||
/// events in a widget tree.
|
||||
/// * [PointerSignalResolver], which provides an opt-in mechanism whereby
|
||||
/// participating agents may disambiguate an event's target.
|
||||
abstract class PointerSignalEvent extends PointerEvent {
|
||||
/// Abstract const constructor. This constructor enables subclasses to provide
|
||||
/// const constructors so that they can be used in const expressions.
|
||||
@ -1916,6 +1918,8 @@ mixin _CopyPointerScrollEvent on PointerEvent {
|
||||
///
|
||||
/// * [Listener.onPointerSignal], which allows callers to be notified of these
|
||||
/// events in a widget tree.
|
||||
/// * [PointerSignalResolver], which provides an opt-in mechanism whereby
|
||||
/// participating agents may disambiguate an event's target.
|
||||
class PointerScrollEvent extends PointerSignalEvent with _PointerEventDescription, _CopyPointerScrollEvent {
|
||||
/// Creates a pointer scroll event.
|
||||
///
|
||||
|
@ -15,22 +15,145 @@ bool _isSameEvent(PointerSignalEvent event1, PointerSignalEvent event2) {
|
||||
return (event1.original ?? event1) == (event2.original ?? event2);
|
||||
}
|
||||
|
||||
/// An resolver for pointer signal events.
|
||||
/// Mediates disputes over which listener should handle pointer signal events
|
||||
/// when multiple listeners wish to handle those events.
|
||||
///
|
||||
/// Objects interested in a [PointerSignalEvent] should register a callback to
|
||||
/// be called if they should handle the event. The resolver's purpose is to
|
||||
/// ensure that the same pointer signal is not handled by multiple objects in
|
||||
/// a hierarchy.
|
||||
/// Pointer signals (such as [PointerScrollEvent]) are immediate, so unlike
|
||||
/// events that participate in the gesture arena, pointer signals always
|
||||
/// resolve at the end of event dispatch. Yet if objects interested in handling
|
||||
/// these signal events were to handle them directly, it would cause issues
|
||||
/// such as multiple [Scrollable] widgets in the widget hierarchy responding
|
||||
/// to the same mouse wheel event. Using this class, these events will only
|
||||
/// be dispatched to the the first registered handler, which will in turn
|
||||
/// correspond to the widget that's deepest in the widget hierarchy.
|
||||
///
|
||||
/// Pointer signals are immediate, so unlike a gesture arena it always resolves
|
||||
/// at the end of event dispatch. The first callback registered will be the one
|
||||
/// that is called.
|
||||
/// To use this class, objects should register their event handler like so:
|
||||
///
|
||||
/// {@tool snippet}
|
||||
/// ```dart
|
||||
/// void handleSignalEvent(PointerSignalEvent event) {
|
||||
/// GestureBinding.instance!.pointerSignalResolver.register(event, (PointerSignalEvent event) {
|
||||
/// // handle the event...
|
||||
/// });
|
||||
/// }
|
||||
/// ```
|
||||
/// {@end-tool}
|
||||
///
|
||||
/// {@tool dartpad --template=stateful_widget_material}
|
||||
/// Here is an example that demonstrates the effect of not using the resolver
|
||||
/// versus using it.
|
||||
///
|
||||
/// When this example is set to _not_ use the resolver, then scrolling the
|
||||
/// mouse wheel over the outer box will cause only the outer box to change
|
||||
/// color, but scrolling the mouse wheel over inner box will cause _both_ the
|
||||
/// outer and the inner boxes to change color (because they're both receiving
|
||||
/// the scroll event).
|
||||
///
|
||||
/// When this excample is set to _use_ the resolver, then only the box located
|
||||
/// directly under the cursor will change color when the mouse wheel is
|
||||
/// scrolled.
|
||||
///
|
||||
/// ```dart imports
|
||||
/// import 'package:flutter/gestures.dart';
|
||||
/// ```
|
||||
///
|
||||
/// ```dart
|
||||
/// HSVColor outerColor = const HSVColor.fromAHSV(0.2, 120.0, 1, 1);
|
||||
/// HSVColor innerColor = const HSVColor.fromAHSV(1, 60.0, 1, 1);
|
||||
/// bool useResolver = false;
|
||||
///
|
||||
/// void rotateOuterColor() {
|
||||
/// setState(() {
|
||||
/// outerColor = outerColor.withHue((outerColor.hue + 6) % 360.0);
|
||||
/// });
|
||||
/// }
|
||||
///
|
||||
/// void rotateInnerColor() {
|
||||
/// setState(() {
|
||||
/// innerColor = innerColor.withHue((innerColor.hue + 6) % 360.0);
|
||||
/// });
|
||||
/// }
|
||||
///
|
||||
/// @override
|
||||
/// Widget build(BuildContext context) {
|
||||
/// return Material(
|
||||
/// child: Stack(
|
||||
/// fit: StackFit.expand,
|
||||
/// children: <Widget>[
|
||||
/// Listener(
|
||||
/// onPointerSignal: (PointerSignalEvent event) {
|
||||
/// if (useResolver) {
|
||||
/// GestureBinding.instance!.pointerSignalResolver.register(event, (PointerSignalEvent event) {
|
||||
/// rotateOuterColor();
|
||||
/// });
|
||||
/// } else {
|
||||
/// rotateOuterColor();
|
||||
/// }
|
||||
/// },
|
||||
/// child: DecoratedBox(
|
||||
/// decoration: BoxDecoration(
|
||||
/// border: const Border.fromBorderSide(BorderSide()),
|
||||
/// color: outerColor.toColor(),
|
||||
/// ),
|
||||
/// child: FractionallySizedBox(
|
||||
/// widthFactor: 0.5,
|
||||
/// heightFactor: 0.5,
|
||||
/// child: DecoratedBox(
|
||||
/// decoration: BoxDecoration(
|
||||
/// border: const Border.fromBorderSide(BorderSide()),
|
||||
/// color: innerColor.toColor(),
|
||||
/// ),
|
||||
/// child: Listener(
|
||||
/// onPointerSignal: (PointerSignalEvent event) {
|
||||
/// if (useResolver) {
|
||||
/// GestureBinding.instance!.pointerSignalResolver.register(event, (PointerSignalEvent event) {
|
||||
/// rotateInnerColor();
|
||||
/// });
|
||||
/// } else {
|
||||
/// rotateInnerColor();
|
||||
/// }
|
||||
/// },
|
||||
/// child: const AbsorbPointer(),
|
||||
/// ),
|
||||
/// ),
|
||||
/// ),
|
||||
/// ),
|
||||
/// ),
|
||||
/// Align(
|
||||
/// alignment: Alignment.topLeft,
|
||||
/// child: Row(
|
||||
/// crossAxisAlignment: CrossAxisAlignment.center,
|
||||
/// children: <Widget>[
|
||||
/// Switch(
|
||||
/// value: useResolver,
|
||||
/// onChanged: (bool value) {
|
||||
/// setState(() {
|
||||
/// useResolver = value;
|
||||
/// });
|
||||
/// },
|
||||
/// ),
|
||||
/// Text(
|
||||
/// 'Use the PointerSignalResolver?',
|
||||
/// style: DefaultTextStyle.of(context).style.copyWith(fontWeight: FontWeight.bold),
|
||||
/// ),
|
||||
/// ],
|
||||
/// ),
|
||||
/// ),
|
||||
/// ],
|
||||
/// ),
|
||||
/// );
|
||||
/// }
|
||||
/// ```
|
||||
/// {@end-tool}
|
||||
class PointerSignalResolver {
|
||||
PointerSignalResolvedCallback? _firstRegisteredCallback;
|
||||
|
||||
PointerSignalEvent? _currentEvent;
|
||||
|
||||
/// Registers interest in handling [event].
|
||||
///
|
||||
/// See the documentation for the [PointerSignalResolver] class on when and
|
||||
/// how this method should be used.
|
||||
void register(PointerSignalEvent event, PointerSignalResolvedCallback callback) {
|
||||
assert(event != null);
|
||||
assert(callback != null);
|
||||
@ -45,8 +168,8 @@ class PointerSignalResolver {
|
||||
/// Resolves the event, calling the first registered callback if there was
|
||||
/// one.
|
||||
///
|
||||
/// Called after the framework has finished dispatching the pointer signal
|
||||
/// event.
|
||||
/// This is called by the [GestureBinding] after the framework has finished
|
||||
/// dispatching the pointer signal event.
|
||||
void resolve(PointerSignalEvent event) {
|
||||
if (_firstRegisteredCallback == null) {
|
||||
assert(_currentEvent == null);
|
||||
|
Loading…
x
Reference in New Issue
Block a user