Commit 6ea86b6a authored by Hixie's avatar Hixie

fn3: Listener

In this new world, Listener is just a wrapper around a node in the
render tree that hooks directly into the event handling logic.
parent b3ebd440
...@@ -852,6 +852,36 @@ class AssetImage extends StatelessComponent { ...@@ -852,6 +852,36 @@ class AssetImage extends StatelessComponent {
// EVENT HANDLING // EVENT HANDLING
class Listener extends OneChildRenderObjectWidget {
Listener({
Key key,
Widget child,
this.onPointerDown,
this.onPointerMove,
this.onPointerUp,
this.onPointerCancel
}): super(key: key, child: child);
final PointerEventListener onPointerDown;
final PointerEventListener onPointerMove;
final PointerEventListener onPointerUp;
final PointerEventListener onPointerCancel;
RenderPointerListener createRenderObject() => new RenderPointerListener(
onPointerDown: onPointerDown,
onPointerMove: onPointerMove,
onPointerUp: onPointerUp,
onPointerCancel: onPointerCancel
);
void updateRenderObject(RenderPointerListener renderObject, Listener oldWidget) {
renderObject.onPointerDown = onPointerDown;
renderObject.onPointerMove = onPointerMove;
renderObject.onPointerUp = onPointerUp;
renderObject.onPointerCancel = onPointerCancel;
}
}
class IgnorePointer extends OneChildRenderObjectWidget { class IgnorePointer extends OneChildRenderObjectWidget {
IgnorePointer({ Key key, Widget child, this.ignoring: true }) IgnorePointer({ Key key, Widget child, this.ignoring: true })
: super(key: key, child: child); : super(key: key, child: child);
......
...@@ -814,12 +814,41 @@ class RenderCustomPaint extends RenderProxyBox { ...@@ -814,12 +814,41 @@ class RenderCustomPaint extends RenderProxyBox {
} }
} }
/// Is invisible during hit testing typedef void PointerEventListener(sky.PointerEvent e);
/// Invokes the callbacks in response to pointer events.
class RenderPointerListener extends RenderProxyBox {
RenderPointerListener({
this.onPointerDown,
this.onPointerMove,
this.onPointerUp,
this.onPointerCancel,
RenderBox child
}) : super(child);
PointerEventListener onPointerDown;
PointerEventListener onPointerMove;
PointerEventListener onPointerUp;
PointerEventListener onPointerCancel;
void handleEvent(sky.Event event, HitTestEntry entry) {
if (onPointerDown != null && event.type == 'pointerdown')
return onPointerDown(event);
if (onPointerMove != null && event.type == 'pointermove')
return onPointerMove(event);
if (onPointerUp != null && event.type == 'pointerup')
return onPointerUp(event);
if (onPointerCancel != null && event.type == 'pointercancel')
return onPointerCancel(event);
}
}
/// Is invisible during hit testing.
/// ///
/// When [ignoring] is true, this render object is invisible to hit testing. It /// When [ignoring] is true, this render object (and its subtree) is invisible
/// still consumes space during layout and paints its child as usual. It just /// to hit testing. It still consumes space during layout and paints its child
/// cannot be the target of located events because it returns false from /// as usual. It just cannot be the target of located events because it returns
/// [hitTest]. /// false from [hitTest].
class RenderIgnorePointer extends RenderProxyBox { class RenderIgnorePointer extends RenderProxyBox {
RenderIgnorePointer({ RenderBox child, bool ignoring: true }) : super(child); RenderIgnorePointer({ RenderBox child, bool ignoring: true }) : super(child);
......
import 'package:sky/src/fn3.dart';
import 'package:test/test.dart';
import 'widget_tester.dart';
void main() {
test('Events bubble up the tree', () {
WidgetTester tester = new WidgetTester();
List<String> log = new List<String>();
tester.pumpFrame(
new Listener(
onPointerDown: (_) {
log.add('top');
},
child: new Listener(
onPointerDown: (_) {
log.add('middle');
},
child: new DecoratedBox(
decoration: const BoxDecoration(),
child: new Listener(
onPointerDown: (_) {
log.add('bottom');
},
child: new Text('X')
)
)
)
)
);
tester.tap(tester.findText('X'));
expect(log, equals([
'bottom',
'middle',
'top',
]));
});
}
import 'dart:sky' as sky;
import 'package:sky/rendering.dart';
import 'package:sky/src/fn3.dart'; import 'package:sky/src/fn3.dart';
import '../engine/mock_events.dart';
class RootComponent extends StatefulComponent { class RootComponent extends StatefulComponent {
RootComponentState createState() => new RootComponentState(this); RootComponentState createState() => new RootComponentState(this);
} }
...@@ -20,6 +25,16 @@ class RootComponentState extends ComponentState<RootComponent> { ...@@ -20,6 +25,16 @@ class RootComponentState extends ComponentState<RootComponent> {
class WidgetTester { class WidgetTester {
void pumpFrame(Widget widget) {
runApp(widget);
WidgetFlutterBinding.instance.beginFrame(0.0); // TODO(ianh): https://github.com/flutter/engine/issues/1084
}
void pumpFrameWithoutChange() {
WidgetFlutterBinding.instance.beginFrame(0.0); // TODO(ianh): https://github.com/flutter/engine/issues/1084
}
void walkElements(ElementVisitor visitor) { void walkElements(ElementVisitor visitor) {
void walk(Element element) { void walk(Element element) {
visitor(element); visitor(element);
...@@ -44,13 +59,60 @@ class WidgetTester { ...@@ -44,13 +59,60 @@ class WidgetTester {
return findElement((Element element) => element.widget.key == key); return findElement((Element element) => element.widget.key == key);
} }
void pumpFrame(Widget widget) { Element findText(String text) {
runApp(widget); return findElement((Element element) {
WidgetFlutterBinding.instance.beginFrame(0.0); // TODO(ianh): https://github.com/flutter/engine/issues/1084 return element.widget is Text && element.widget.data == text;
});
} }
void pumpFrameWithoutChange() {
WidgetFlutterBinding.instance.beginFrame(0.0); // TODO(ianh): https://github.com/flutter/engine/issues/1084 Point getCenter(Element element) {
return _getElementPoint(element, (Size size) => size.center(Point.origin));
}
Point getTopLeft(Element element) {
return _getElementPoint(element, (_) => Point.origin);
}
Point getTopRight(Element element) {
return _getElementPoint(element, (Size size) => size.topRight(Point.origin));
}
Point getBottomLeft(Element element) {
return _getElementPoint(element, (Size size) => size.bottomLeft(Point.origin));
}
Point getBottomRight(Element element) {
return _getElementPoint(element, (Size size) => size.bottomRight(Point.origin));
}
Point _getElementPoint(Element element, Function sizeToPoint) {
assert(element != null);
RenderBox box = element.renderObject as RenderBox;
assert(box != null);
return box.localToGlobal(sizeToPoint(box.size));
}
void tap(Element element, { int pointer: 1 }) {
tapAt(getCenter(element), pointer: pointer);
}
void tapAt(Point location, { int pointer: 1 }) {
HitTestResult result = _hitTest(location);
TestPointer p = new TestPointer(pointer);
_dispatchEvent(p.down(location), result);
_dispatchEvent(p.up(), result);
}
void dispatchEvent(sky.Event event, Point location) {
_dispatchEvent(event, _hitTest(location));
}
HitTestResult _hitTest(Point location) => WidgetFlutterBinding.instance.hitTest(location);
void _dispatchEvent(sky.Event event, HitTestResult result) {
WidgetFlutterBinding.instance.dispatchEvent(event, result);
} }
} }
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