Commit b9489678 authored by Hixie's avatar Hixie

fn3: MixedViewport

Also:
- Expose the slot of each Element.
- Minor improvements to HomogeneousViewport.
- Replace TestComponent with FlipComponent in tests.
parent 878775bb
...@@ -24,11 +24,12 @@ export 'fn3/focus.dart'; ...@@ -24,11 +24,12 @@ export 'fn3/focus.dart';
export 'fn3/framework.dart'; export 'fn3/framework.dart';
export 'fn3/gesture_detector.dart'; export 'fn3/gesture_detector.dart';
export 'fn3/homogeneous_viewport.dart'; export 'fn3/homogeneous_viewport.dart';
export 'fn3/icon_button.dart';
export 'fn3/icon.dart'; export 'fn3/icon.dart';
export 'fn3/icon_button.dart';
export 'fn3/ink_well.dart'; export 'fn3/ink_well.dart';
export 'fn3/material_button.dart';
export 'fn3/material.dart'; export 'fn3/material.dart';
export 'fn3/material_button.dart';
export 'fn3/mixed_viewport.dart';
export 'fn3/navigator.dart'; export 'fn3/navigator.dart';
export 'fn3/popup_menu.dart'; export 'fn3/popup_menu.dart';
export 'fn3/popup_menu_item.dart'; export 'fn3/popup_menu_item.dart';
......
...@@ -419,6 +419,7 @@ abstract class Element<T extends Widget> implements BuildContext { ...@@ -419,6 +419,7 @@ abstract class Element<T extends Widget> implements BuildContext {
/// ///
/// Subclasses of Element that only have one child should use null for /// Subclasses of Element that only have one child should use null for
/// the slot for that child. /// the slot for that child.
dynamic get slot => _slot;
dynamic _slot; dynamic _slot;
/// An integer that is guaranteed to be greater than the parent's, if any. /// An integer that is guaranteed to be greater than the parent's, if any.
...@@ -488,12 +489,12 @@ abstract class Element<T extends Widget> implements BuildContext { ...@@ -488,12 +489,12 @@ abstract class Element<T extends Widget> implements BuildContext {
} }
if (child != null) { if (child != null) {
if (child.widget == newWidget) { if (child.widget == newWidget) {
if (child._slot != newSlot) if (child.slot != newSlot)
updateSlotForChild(child, newSlot); updateSlotForChild(child, newSlot);
return child; return child;
} }
if (_canUpdate(child.widget, newWidget)) { if (_canUpdate(child.widget, newWidget)) {
if (child._slot != newSlot) if (child.slot != newSlot)
updateSlotForChild(child, newSlot); updateSlotForChild(child, newSlot);
child.update(newWidget); child.update(newWidget);
assert(child.widget == newWidget); assert(child.widget == newWidget);
...@@ -517,7 +518,7 @@ abstract class Element<T extends Widget> implements BuildContext { ...@@ -517,7 +518,7 @@ abstract class Element<T extends Widget> implements BuildContext {
assert(widget != null); assert(widget != null);
assert(_parent == null); assert(_parent == null);
assert(parent == null || parent._debugLifecycleState == _ElementLifecycle.mounted); assert(parent == null || parent._debugLifecycleState == _ElementLifecycle.mounted);
assert(_slot == null); assert(slot == null);
assert(depth == null); assert(depth == null);
_parent = parent; _parent = parent;
_slot = newSlot; _slot = newSlot;
...@@ -654,12 +655,12 @@ abstract class BuildableElement<T extends Widget> extends Element<T> { ...@@ -654,12 +655,12 @@ abstract class BuildableElement<T extends Widget> extends Element<T> {
} }
try { try {
_child = updateChild(_child, built, _slot); _child = updateChild(_child, built, slot);
assert(_child != null); assert(_child != null);
} catch (e, stack) { } catch (e, stack) {
_debugReportException('building $this', e, stack); _debugReportException('building $this', e, stack);
built = new ErrorWidget(); built = new ErrorWidget();
_child = updateChild(null, built, _slot); _child = updateChild(null, built, slot);
} }
} }
...@@ -735,6 +736,7 @@ class StatefulComponentElement extends BuildableElement<StatefulComponent> { ...@@ -735,6 +736,7 @@ class StatefulComponentElement extends BuildableElement<StatefulComponent> {
StatefulComponentElement(StatefulComponent widget) StatefulComponentElement(StatefulComponent widget)
: _state = widget.createState(), super(widget) { : _state = widget.createState(), super(widget) {
assert(_state._config == widget); assert(_state._config == widget);
assert(_state._element == null);
_state._element = this; _state._element = this;
_builder = _state.build; _builder = _state.build;
} }
...@@ -755,6 +757,7 @@ class StatefulComponentElement extends BuildableElement<StatefulComponent> { ...@@ -755,6 +757,7 @@ class StatefulComponentElement extends BuildableElement<StatefulComponent> {
void unmount() { void unmount() {
super.unmount(); super.unmount();
_state.dispose(); _state.dispose();
_state._element = null;
_state = null; _state = null;
} }
} }
...@@ -1016,6 +1019,7 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Element ...@@ -1016,6 +1019,7 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Element
void unmount() { void unmount() {
super.unmount(); super.unmount();
assert(!renderObject.attached);
widget.didUnmountRenderObject(renderObject); widget.didUnmountRenderObject(renderObject);
} }
...@@ -1024,10 +1028,10 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Element ...@@ -1024,10 +1028,10 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Element
} }
void _updateSlot(dynamic newSlot) { void _updateSlot(dynamic newSlot) {
assert(_slot != newSlot); assert(slot != newSlot);
super._updateSlot(newSlot); super._updateSlot(newSlot);
assert(_slot == newSlot); assert(slot == newSlot);
_ancestorRenderObjectElement.moveChildRenderObject(renderObject, _slot); _ancestorRenderObjectElement.moveChildRenderObject(renderObject, slot);
} }
void detachRenderObject() { void detachRenderObject() {
......
...@@ -30,11 +30,11 @@ class HomogeneousViewport extends RenderObjectWidget { ...@@ -30,11 +30,11 @@ class HomogeneousViewport extends RenderObjectWidget {
final ScrollDirection direction; final ScrollDirection direction;
final double startOffset; final double startOffset;
RenderObjectElement createElement() => new HomogeneousViewportElement(this); HomogeneousViewportElement createElement() => new HomogeneousViewportElement(this);
// we don't pass constructor arguments to the RenderBlockViewport() because until // we don't pass constructor arguments to the RenderBlockViewport() because until
// we know our children, the constructor arguments we could give have no effect // we know our children, the constructor arguments we could give have no effect
RenderObject createRenderObject() => new RenderBlockViewport(); RenderBlockViewport createRenderObject() => new RenderBlockViewport();
bool isLayoutDifferentThan(HomogeneousViewport oldWidget) { bool isLayoutDifferentThan(HomogeneousViewport oldWidget) {
return itemsWrap != oldWidget.itemsWrap || return itemsWrap != oldWidget.itemsWrap ||
...@@ -163,7 +163,8 @@ class HomogeneousViewportElement extends RenderObjectElement<HomogeneousViewport ...@@ -163,7 +163,8 @@ class HomogeneousViewportElement extends RenderObjectElement<HomogeneousViewport
renderObject.add(child, before: nextSibling); renderObject.add(child, before: nextSibling);
} }
void moveChildRenderObject(RenderObject child, dynamic slot) { void moveChildRenderObject(RenderObject child, Element slot) {
assert(child.parent == renderObject);
RenderObject nextSibling = slot?.renderObject; RenderObject nextSibling = slot?.renderObject;
renderObject.move(child, before: nextSibling); renderObject.move(child, before: nextSibling);
} }
......
This diff is collapsed.
...@@ -2,25 +2,7 @@ import 'package:sky/src/fn3.dart'; ...@@ -2,25 +2,7 @@ import 'package:sky/src/fn3.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'widget_tester.dart'; import 'widget_tester.dart';
import 'test_widgets.dart';
class TestComponent extends StatefulComponent {
TestComponent(this.viewport);
final HomogeneousViewport viewport;
TestComponentState createState() => new TestComponentState(this);
}
class TestComponentState extends ComponentState<TestComponent> {
TestComponentState(TestComponent config): super(config);
bool _flag = true;
void go(bool flag) {
setState(() {
_flag = flag;
});
}
Widget build(BuildContext context) {
return _flag ? config.viewport : new Text('Not Today');
}
}
void main() { void main() {
test('HomogeneousViewport mount/dismount smoke test', () { test('HomogeneousViewport mount/dismount smoke test', () {
...@@ -32,39 +14,42 @@ void main() { ...@@ -32,39 +14,42 @@ void main() {
// so if our widget is 100 pixels tall, it should fit exactly 6 times. // so if our widget is 100 pixels tall, it should fit exactly 6 times.
Widget builder() { Widget builder() {
return new TestComponent(new HomogeneousViewport( return new FlipComponent(
builder: (BuildContext context, int start, int count) { left: new HomogeneousViewport(
List<Widget> result = <Widget>[]; builder: (BuildContext context, int start, int count) {
for (int index = start; index < start + count; index += 1) { List<Widget> result = <Widget>[];
callbackTracker.add(index); for (int index = start; index < start + count; index += 1) {
result.add(new Container( callbackTracker.add(index);
key: new ValueKey<int>(index), result.add(new Container(
height: 100.0, key: new ValueKey<int>(index),
child: new Text("$index") height: 100.0,
)); child: new Text("$index")
} ));
return result; }
}, return result;
startOffset: 0.0, },
itemExtent: 100.0 startOffset: 0.0,
)); itemExtent: 100.0
),
right: new Text('Not Today')
);
} }
tester.pumpFrame(builder()); tester.pumpFrame(builder());
StatefulComponentElement testComponent = tester.findElement((element) => element.widget is TestComponent); StatefulComponentElement element = tester.findElement((element) => element.widget is FlipComponent);
TestComponentState testComponentState = testComponent.state; FlipComponentState testComponent = element.state;
expect(callbackTracker, equals([0, 1, 2, 3, 4, 5])); expect(callbackTracker, equals([0, 1, 2, 3, 4, 5]));
callbackTracker.clear(); callbackTracker.clear();
testComponentState.go(false); testComponent.flip();
tester.pumpFrameWithoutChange(); tester.pumpFrameWithoutChange();
expect(callbackTracker, equals([])); expect(callbackTracker, equals([]));
callbackTracker.clear(); callbackTracker.clear();
testComponentState.go(true); testComponent.flip();
tester.pumpFrameWithoutChange(); tester.pumpFrameWithoutChange();
expect(callbackTracker, equals([0, 1, 2, 3, 4, 5])); expect(callbackTracker, equals([0, 1, 2, 3, 4, 5]));
...@@ -95,13 +80,16 @@ void main() { ...@@ -95,13 +80,16 @@ void main() {
return result; return result;
}; };
TestComponent testComponent; FlipComponent testComponent;
Widget builder() { Widget builder() {
testComponent = new TestComponent(new HomogeneousViewport( testComponent = new FlipComponent(
builder: itemBuilder, left: new HomogeneousViewport(
startOffset: offset, builder: itemBuilder,
itemExtent: 200.0 startOffset: offset,
)); itemExtent: 200.0
),
right: new Text('Not Today')
);
return testComponent; return testComponent;
} }
...@@ -145,14 +133,17 @@ void main() { ...@@ -145,14 +133,17 @@ void main() {
return result; return result;
}; };
TestComponent testComponent; FlipComponent testComponent;
Widget builder() { Widget builder() {
testComponent = new TestComponent(new HomogeneousViewport( testComponent = new FlipComponent(
builder: itemBuilder, left: new HomogeneousViewport(
startOffset: offset, builder: itemBuilder,
itemExtent: 200.0, startOffset: offset,
direction: ScrollDirection.horizontal itemExtent: 200.0,
)); direction: ScrollDirection.horizontal
),
right: new Text('Not Today')
);
return testComponent; return testComponent;
} }
......
import 'package:sky/src/fn3.dart';
import 'package:test/test.dart';
import 'widget_tester.dart';
import 'test_widgets.dart';
void main() {
test('MixedViewport mount/dismount smoke test', () {
WidgetTester tester = new WidgetTester();
List<int> callbackTracker = <int>[];
// the root view is 800x600 in the test environment
// so if our widget is 100 pixels tall, it should fit exactly 6 times.
Widget builder() {
return new FlipComponent(
left: new MixedViewport(
builder: (BuildContext context, int i) {
callbackTracker.add(i);
return new Container(
key: new ValueKey<int>(i),
height: 100.0,
child: new Text("$i")
);
},
startOffset: 0.0
),
right: new Text('Not Today')
);
}
tester.pumpFrame(builder());
StatefulComponentElement element = tester.findElement((element) => element.widget is FlipComponent);
FlipComponentState testComponent = element.state;
expect(callbackTracker, equals([0, 1, 2, 3, 4, 5]));
callbackTracker.clear();
testComponent.flip();
tester.pumpFrameWithoutChange();
expect(callbackTracker, equals([]));
callbackTracker.clear();
testComponent.flip();
tester.pumpFrameWithoutChange();
expect(callbackTracker, equals([0, 1, 2, 3, 4, 5]));
});
test('MixedViewport vertical', () {
WidgetTester tester = new WidgetTester();
List<int> callbackTracker = <int>[];
// the root view is 800x600 in the test environment
// so if our widget is 200 pixels tall, it should fit exactly 3 times.
// but if we are offset by 300 pixels, there will be 4, numbered 1-4.
double offset = 300.0;
IndexedBuilder itemBuilder = (BuildContext context, int i) {
callbackTracker.add(i);
return new Container(
key: new ValueKey<int>(i),
width: 500.0, // this should be ignored
height: 200.0,
child: new Text("$i")
);
};
Widget builder() {
return new FlipComponent(
left: new MixedViewport(
builder: itemBuilder,
startOffset: offset
),
right: new Text('Not Today')
);
}
tester.pumpFrame(builder());
// 0 is built to find its width
expect(callbackTracker, equals([0, 1, 2, 3, 4]));
callbackTracker.clear();
offset = 400.0; // now only 3 should fit, numbered 2-4.
tester.pumpFrame(builder());
// 0 and 1 aren't built, we know their size and nothing else changed
expect(callbackTracker, equals([2, 3, 4]));
callbackTracker.clear();
});
test('MixedViewport horizontal', () {
WidgetTester tester = new WidgetTester();
List<int> callbackTracker = <int>[];
// the root view is 800x600 in the test environment
// so if our widget is 200 pixels wide, it should fit exactly 4 times.
// but if we are offset by 300 pixels, there will be 5, numbered 1-5.
double offset = 300.0;
IndexedBuilder itemBuilder = (BuildContext context, int i) {
callbackTracker.add(i);
return new Container(
key: new ValueKey<int>(i),
height: 500.0, // this should be ignored
width: 200.0,
child: new Text("$i")
);
};
Widget builder() {
return new FlipComponent(
left: new MixedViewport(
builder: itemBuilder,
startOffset: offset,
direction: ScrollDirection.horizontal
),
right: new Text('Not Today')
);
}
tester.pumpFrame(builder());
// 0 is built to find its width
expect(callbackTracker, equals([0, 1, 2, 3, 4, 5]));
callbackTracker.clear();
offset = 400.0; // now only 4 should fit, numbered 2-5.
tester.pumpFrame(builder());
// 0 and 1 aren't built, we know their size and nothing else changed
expect(callbackTracker, equals([2, 3, 4, 5]));
callbackTracker.clear();
});
}
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