Unverified Commit 351e319a authored by Todd Volkert's avatar Todd Volkert Committed by GitHub

Improve documentation for PointerSignalResolver class (#78738)

parent cf903d73
......@@ -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);
......
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