Commit 7077b1b3 authored by Adam Barth's avatar Adam Barth

Mimic should track Mimicable more completely

We now have Mimic tracking Mimicable through tree structure changes and while
moving around the screen.

Fixes #751 and #756
parent d7ed623e
...@@ -63,6 +63,13 @@ class ExampleApp extends App { ...@@ -63,6 +63,13 @@ class ExampleApp extends App {
); );
} }
Rect _targetRect;
void _handleOverlaySizeChanged(Size size) {
setState(() {
_targetRect = Point.origin & size;
});
}
Widget build() { Widget build() {
List<Widget> cards = new List<Widget>(); List<Widget> cards = new List<Widget>();
for (int i = 0; i < _data.length; ++i) { for (int i = 0; i < _data.length; ++i) {
...@@ -88,13 +95,17 @@ class ExampleApp extends App { ...@@ -88,13 +95,17 @@ class ExampleApp extends App {
} }
) )
), ),
body: new MimicOverlay( body: new SizeObserver(
callback: _handleOverlaySizeChanged,
child: new MimicOverlay(
overlay: _overlay, overlay: _overlay,
duration: const Duration(milliseconds: 500), duration: const Duration(milliseconds: 5000),
targetRect: _targetRect,
children: [ new Block(cards) ] children: [ new Block(cards) ]
) )
) )
) )
)
); );
} }
} }
......
...@@ -4,73 +4,105 @@ ...@@ -4,73 +4,105 @@
import 'package:sky/widgets/basic.dart'; import 'package:sky/widgets/basic.dart';
typedef MimicCallback(Rect globalBounds); abstract class GlobalKeyWatcher extends StatefulComponent {
GlobalKeyWatcher({
class Mimic extends StatefulComponent {
Mimic({
Key key, Key key,
this.original, this.watchedKey
this.callback });
}) : super(key: key);
GlobalKey original; GlobalKey watchedKey;
MimicCallback callback;
void initState() { void syncConstructorArguments(GlobalKeyWatcher source) {
_requestToStartMimic(); if (source != source.watchedKey) {
_removeListeners();
watchedKey = source.watchedKey;
_addListeners();
}
} }
void syncConstructorArguments(Mimic source) { void didMount() {
callback = source.callback; super.didMount();
if (original != source.original) { _addListeners();
_stopMimic(); }
original = source.original;
_requestToStartMimic(); void didUnmount() {
super.didUnmount();
_removeListeners();
} }
void didSyncWatchedKey(GlobalKey key, Widget widget) {
assert(key == watchedKey);
}
void didRemoveWatchedKey(GlobalKey key) {
assert(key == watchedKey);
} }
void _addListeners() {
GlobalKey.registerSyncListener(watchedKey, didSyncWatchedKey);
GlobalKey.registerRemoveListener(watchedKey, didRemoveWatchedKey);
}
void _removeListeners() {
GlobalKey.unregisterSyncListener(watchedKey, didSyncWatchedKey);
GlobalKey.unregisterRemoveListener(watchedKey, didRemoveWatchedKey);
}
}
typedef MimicReadyCallback();
class Mimic extends GlobalKeyWatcher {
Mimic({
Key key,
GlobalKey original,
this.onMimicReady
}) : super(key: key, watchedKey: original);
MimicReadyCallback onMimicReady;
void syncConstructorArguments(Mimic source) {
onMimicReady = source.onMimicReady;
super.syncConstructorArguments(source);
}
Mimicable _mimicable;
void didMount() { void didMount() {
super.didMount(); super.didMount();
// TODO(abarth): Why is didMount being called without a call to didUnmount?
if (_mimicable == null) if (_mimicable == null)
_requestToStartMimic(); _setMimicable(GlobalKey.getWidget(watchedKey));
} }
void didUnmount() { void didUnmount() {
super.didUnmount(); super.didUnmount();
_stopMimic(); if (_mimicable != null) {
_mimicable.stopMimic();
_mimicable = null;
}
} }
Mimicable _mimicable; void didSyncWatchedKey(GlobalKey key, Widget widget) {
bool _mimicking = false; super.didSyncWatchedKey(key, widget);
_setMimicable(widget);
}
void _requestToStartMimic() { void didRemoveWatchedKey(GlobalKey key) {
assert(_mimicable == null); super.didRemoveWatchedKey(key);
assert(!_mimicking); _setMimicable(null);
if (original == null)
return;
_mimicable = GlobalKey.getWidget(original) as Mimicable;
assert(_mimicable != null);
_mimicable._requestToStartMimic(this);
} }
void _startMimic(GlobalKey key, Rect globalBounds) { void _setMimicable(widget) {
assert(key == original); if (_mimicable == null && widget != null)
widget.startMimic();
setState(() { setState(() {
_mimicking = true; _mimicable = widget;
}); });
callback(globalBounds); if (onMimicReady != null && _mimicable != null && _mimicable._didBuildPlaceholder)
} onMimicReady();
void _stopMimic() {
if (_mimicable != null)
_mimicable._didStopMimic(this);
_mimicable = null;
_mimicking = false;
} }
Widget build() { Widget build() {
if (!_mimicking || !_mimicable.mounted) if (_mimicable == null || !_mimicable._didBuildPlaceholder)
return new Container(); return new Container();
return _mimicable.child; return _mimicable.child;
} }
...@@ -84,29 +116,30 @@ class Mimicable extends StatefulComponent { ...@@ -84,29 +116,30 @@ class Mimicable extends StatefulComponent {
Size _size; Size _size;
Size get size => _size; Size get size => _size;
Mimic _mimic;
bool _didStartMimic = false;
void syncConstructorArguments(Mimicable source) { void syncConstructorArguments(Mimicable source) {
child = source.child; child = source.child;
} }
void _requestToStartMimic(Mimic mimic) { bool _didBuildPlaceholder = false;
assert(mounted);
if (_mimic != null) Rect get globalBounds {
return; if (_size == null)
return null;
return localToGlobal(Point.origin) & _size;
}
bool _mimicRequested = false;
void startMimic() {
assert(!_mimicRequested);
setState(() { setState(() {
_mimic = mimic; _mimicRequested = true;
_didStartMimic = false;
}); });
} }
void _didStopMimic(Mimic mimic) { void stopMimic() {
assert(_mimic != null); assert(_mimicRequested);
assert(mimic == _mimic);
setState(() { setState(() {
_mimic = null; _mimicRequested = false;
_didStartMimic = false;
}); });
} }
...@@ -116,19 +149,12 @@ class Mimicable extends StatefulComponent { ...@@ -116,19 +149,12 @@ class Mimicable extends StatefulComponent {
}); });
} }
void _startMimicIfNeeded() {
if (_didStartMimic)
return;
assert(_mimic != null);
Point globalPosition = localToGlobal(Point.origin);
_mimic._startMimic(key, globalPosition & _size);
_didStartMimic = true;
}
Widget build() { Widget build() {
if (_mimic != null) { _didBuildPlaceholder = _mimicRequested && _size != null;
_startMimicIfNeeded(); if (_didBuildPlaceholder) {
return new ConstrainedBox(constraints: new BoxConstraints.tight(_size)); return new ConstrainedBox(
constraints: new BoxConstraints.tight(_size)
);
} }
return new SizeObserver( return new SizeObserver(
callback: _handleSizeChanged, callback: _handleSizeChanged,
......
...@@ -56,6 +56,7 @@ class MimicOverlay extends AnimatedComponent { ...@@ -56,6 +56,7 @@ class MimicOverlay extends AnimatedComponent {
_expandPerformance = new AnimationPerformance() _expandPerformance = new AnimationPerformance()
..duration = duration ..duration = duration
..addVariable(_mimicBounds) ..addVariable(_mimicBounds)
..addListener(_handleAnimationTick)
..addStatusListener(_handleAnimationStatusChanged); ..addStatusListener(_handleAnimationStatusChanged);
watch(_expandPerformance); watch(_expandPerformance);
} }
...@@ -72,22 +73,38 @@ class MimicOverlay extends AnimatedComponent { ...@@ -72,22 +73,38 @@ class MimicOverlay extends AnimatedComponent {
} }
} }
void _handleMimicCallback(Rect globalBounds) { void _handleAnimationTick() {
if (_activeOverlay == null)
return;
_updateMimicBounds();
}
void _updateMimicBounds() {
Mimicable mimicable = GlobalKey.getWidget(_activeOverlay) as Mimicable;
Rect globalBounds = mimicable.globalBounds;
if (globalBounds == null)
return;
Rect localBounds = globalToLocal(globalBounds.topLeft) & globalBounds.size;
if (localBounds == _mimicBounds.begin)
return;
setState(() { setState(() {
// TODO(abarth): We need to convert global bounds into local coordinates. _mimicBounds.begin = localBounds;
_mimicBounds.begin = if (_expandPerformance.isDismissed)
globalToLocal(globalBounds.topLeft) & globalBounds.size;
_mimicBounds.value = _mimicBounds.begin; _mimicBounds.value = _mimicBounds.begin;
}); });
}
void _handleMimicReady() {
_updateMimicBounds();
if (_expandPerformance.isDismissed)
_expandPerformance.forward(); _expandPerformance.forward();
} }
Widget build() { Widget build() {
List<Widget> layers = new List<Widget>(); List<Widget> layers = new List<Widget>();
if (children != null) { if (children != null)
layers.addAll(children); layers.addAll(children);
}
if (_activeOverlay != null) { if (_activeOverlay != null) {
layers.add( layers.add(
...@@ -98,7 +115,7 @@ class MimicOverlay extends AnimatedComponent { ...@@ -98,7 +115,7 @@ class MimicOverlay extends AnimatedComponent {
width: _mimicBounds.value.width, width: _mimicBounds.value.width,
height: _mimicBounds.value.height, height: _mimicBounds.value.height,
child: new Mimic( child: new Mimic(
callback: _handleMimicCallback, onMimicReady: _handleMimicReady,
original: _activeOverlay original: _activeOverlay
) )
) )
......
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