Commit 5aafa65f authored by Adam Barth's avatar Adam Barth

Add a MimicOverlay widget

This widget lets you mimic one of its children in an overlay. The overlay
starts out as the same size of the child and then grows to fill the overlay. In
the future, the mimic will start at the same visual position as the child.
parent 35a2c744
...@@ -3,85 +3,100 @@ ...@@ -3,85 +3,100 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'package:sky/painting/box_painter.dart'; import 'package:sky/painting/box_painter.dart';
import 'package:sky/theme/colors.dart';
import 'package:sky/widgets.dart'; import 'package:sky/widgets.dart';
class Circle extends Component { class GreenCard extends Component {
Circle({ this.child }); GreenCard({ this.child });
Widget child; Widget child;
Widget build() { Widget build() {
return new Container( return new Container(
height: 100.0,
margin: new EdgeDims.symmetric(horizontal: 20.0, vertical: 4.0),
decoration: new BoxDecoration( decoration: new BoxDecoration(
backgroundColor: const Color(0xFF0000FF) backgroundColor: const Color(0xFF0000FF),
border: new Border.all(
color: const Color(0xFF00FF00),
width: 10.0
)
), ),
child: new Center( child: new Center(child: child)
child: child
)
); );
} }
} }
class CircleData { class CardData {
final GlobalKey key; final GlobalKey key;
final String content; final String content;
CircleData({ this.key, this.content }); CardData({ this.key, this.content });
} }
class ExampleApp extends App { class ExampleApp extends App {
ExampleApp() { ExampleApp() {
for (int i = 0; i < 20; ++i) { for (int i = 0; i < 20; ++i) {
_data.add(new CircleData( _data.add(new CardData(
key: new GlobalKey(), key: new GlobalKey(),
content: '$i' content: '$i'
)); ));
} }
} }
final List<CircleData> _data = new List<CircleData>(); final List<CardData> _data = new List<CardData>();
GlobalKey _keyToMimic; GlobalKey _overlay;
Widget _buildCircle(CircleData circleData) { Widget _buildCard(CardData cardData) {
return new Mimicable( return new Listener(
key: circleData.key, onGestureTap: (_) {
child: new Listener( setState(() {
child: new Circle( _overlay = cardData.key;
child: new Text(circleData.content) });
), },
onGestureTap: (_) { child: new Container(
setState(() { height: 100.0,
_keyToMimic = circleData.key; margin: new EdgeDims.symmetric(horizontal: 20.0, vertical: 4.0),
}); child: new Mimicable(
} key: cardData.key,
child: new GreenCard(child: new Text(cardData.content))
)
) )
); );
} }
Widget build() { Widget build() {
List<Widget> circles = new List<Widget>(); List<Widget> cards = new List<Widget>();
for (int i = 0; i < 20; ++i) { for (int i = 0; i < _data.length; ++i) {
circles.add(_buildCircle(_data[i])); cards.add(_buildCard(_data[i]));
} }
List<Widget> layers = new List<Widget>(); return new IconTheme(
layers.add(new ScrollableBlock(circles)); data: const IconThemeData(color: IconThemeColor.white),
child: new Theme(
if (_keyToMimic != null) { data: new ThemeData(
layers.add( brightness: ThemeBrightness.light,
new Positioned( primarySwatch: Blue,
top: 50.0, accentColor: RedAccent[200]
left: 50.0, ),
child: new Mimic( child: new Scaffold(
original: _keyToMimic) toolbar: new ToolBar(
left: new IconButton(
icon: "navigation/arrow_back",
onPressed: () {
setState(() {
_overlay = null;
});
}
)
),
body: new MimicOverlay(
overlay: _overlay,
duration: const Duration(milliseconds: 500),
children: [ new ScrollableBlock(cards) ]
)
) )
); )
} );
return new Stack(layers);
} }
} }
......
...@@ -28,7 +28,7 @@ export 'widgets/ink_well.dart'; ...@@ -28,7 +28,7 @@ export 'widgets/ink_well.dart';
export 'widgets/material.dart'; export 'widgets/material.dart';
export 'widgets/material_button.dart'; export 'widgets/material_button.dart';
export 'widgets/mimic.dart'; export 'widgets/mimic.dart';
export 'widgets/mimic.dart'; export 'widgets/mimic_overlay.dart';
export 'widgets/modal_overlay.dart'; export 'widgets/modal_overlay.dart';
export 'widgets/navigator.dart'; export 'widgets/navigator.dart';
export 'widgets/popup_menu.dart'; export 'widgets/popup_menu.dart';
......
...@@ -4,16 +4,24 @@ ...@@ -4,16 +4,24 @@
import 'package:sky/widgets/basic.dart'; import 'package:sky/widgets/basic.dart';
typedef MimicCallback(Rect globalBounds);
class Mimic extends StatefulComponent { class Mimic extends StatefulComponent {
Mimic({ Key key, this.original }) : super(key: key); Mimic({
Key key,
this.original,
this.callback
}) : super(key: key);
GlobalKey original; GlobalKey original;
MimicCallback callback;
void initState() { void initState() {
_requestToStartMimic(); _requestToStartMimic();
} }
void syncFields(Mimic source) { void syncFields(Mimic source) {
callback = source.callback;
if (original != source.original) { if (original != source.original) {
_stopMimic(); _stopMimic();
original = source.original; original = source.original;
...@@ -34,11 +42,11 @@ class Mimic extends StatefulComponent { ...@@ -34,11 +42,11 @@ class Mimic extends StatefulComponent {
} }
Mimicable _mimicable; Mimicable _mimicable;
Size _size; bool _mimicking = false;
void _requestToStartMimic() { void _requestToStartMimic() {
assert(_mimicable == null); assert(_mimicable == null);
assert(_size == null); assert(!_mimicking);
if (original == null) if (original == null)
return; return;
_mimicable = GlobalKey.getWidget(original) as Mimicable; _mimicable = GlobalKey.getWidget(original) as Mimicable;
...@@ -46,27 +54,25 @@ class Mimic extends StatefulComponent { ...@@ -46,27 +54,25 @@ class Mimic extends StatefulComponent {
_mimicable._requestToStartMimic(this); _mimicable._requestToStartMimic(this);
} }
void _startMimic(GlobalKey key, Size size) { void _startMimic(GlobalKey key, Rect globalBounds) {
assert(key == original); assert(key == original);
setState(() { setState(() {
_size = size; _mimicking = true;
}); });
callback(globalBounds);
} }
void _stopMimic() { void _stopMimic() {
if (_mimicable != null) if (_mimicable != null)
_mimicable._didStopMimic(this); _mimicable._didStopMimic(this);
_mimicable = null; _mimicable = null;
_size = null; _mimicking = false;
} }
Widget build() { Widget build() {
if (_size == null || !_mimicable.mounted) if (!_mimicking || !_mimicable.mounted)
return new Container(); return new Container();
return new ConstrainedBox( return _mimicable.child;
constraints: new BoxConstraints.tight(_size),
child: _mimicable.child
);
} }
} }
...@@ -114,7 +120,9 @@ class Mimicable extends StatefulComponent { ...@@ -114,7 +120,9 @@ class Mimicable extends StatefulComponent {
if (_didStartMimic) if (_didStartMimic)
return; return;
assert(_mimic != null); assert(_mimic != null);
_mimic._startMimic(key, _size); // TODO(abarth): We'll need to convert Point.origin to global coordinates.
Point globalPosition = Point.origin;
_mimic._startMimic(key, globalPosition & _size);
_didStartMimic = true; _didStartMimic = true;
} }
......
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:sky/animation/animated_value.dart';
import 'package:sky/animation/animation_performance.dart';
import 'package:sky/animation/curves.dart';
import 'package:sky/widgets/animated_component.dart';
import 'package:sky/widgets/basic.dart';
import 'package:sky/widgets/mimic.dart';
class MimicOverlay extends AnimatedComponent {
MimicOverlay({
Key key,
this.children,
this.overlay,
this.duration: const Duration(milliseconds: 200),
this.curve: linear
}) : super(key: key);
List<Widget> children;
GlobalKey overlay;
Duration duration;
Curve curve;
void syncFields(MimicOverlay source) {
children = source.children;
duration = source.duration;
_expandPerformance.duration = duration;
curve = source.curve;
_mimicBounds.curve = curve;
if (overlay != source.overlay) {
overlay = source.overlay;
if (_expandPerformance.isDismissed)
_activeOverlay = overlay;
else
_expandPerformance.reverse();
}
}
void initState() {
_mimicBounds = new AnimatedRect(new Rect(), curve: curve);
_expandPerformance = new AnimationPerformance()
..duration = duration
..addVariable(_mimicBounds)
..addStatusListener(_handleAnimationStatusChanged);
watch(_expandPerformance);
}
GlobalKey _activeOverlay;
AnimatedRect _mimicBounds;
AnimationPerformance _expandPerformance;
void _handleAnimationStatusChanged(AnimationStatus status) {
if (status == AnimationStatus.dismissed) {
setState(() {
_activeOverlay = overlay;
});
}
}
void _handleStackSizeChanged(Size size) {
_mimicBounds.end = Point.origin & size;
}
void _handleMimicCallback(Rect globalBounds) {
setState(() {
// TODO(abarth): We need to convert global bounds into local coordinates.
_mimicBounds.begin = globalBounds;
_expandPerformance.forward();
});
}
Widget build() {
List<Widget> layers = new List<Widget>();
if (children != null)
layers.addAll(children);
if (_activeOverlay != null) {
layers.add(
new Positioned(
left: _mimicBounds.value.left,
top: _mimicBounds.value.top,
child: new SizedBox(
width: _mimicBounds.value.width,
height: _mimicBounds.value.height,
child: new Mimic(
callback: _handleMimicCallback,
original: _activeOverlay
)
)
)
);
}
return new SizeObserver(
callback: _handleStackSizeChanged,
child: new Stack(layers)
);
}
}
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