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 @@
// found in the LICENSE file.
import 'package:sky/painting/box_painter.dart';
import 'package:sky/theme/colors.dart';
import 'package:sky/widgets.dart';
class Circle extends Component {
Circle({ this.child });
class GreenCard extends Component {
GreenCard({ this.child });
Widget child;
Widget build() {
return new Container(
height: 100.0,
margin: new EdgeDims.symmetric(horizontal: 20.0, vertical: 4.0),
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: child
)
child: new Center(child: child)
);
}
}
class CircleData {
class CardData {
final GlobalKey key;
final String content;
CircleData({ this.key, this.content });
CardData({ this.key, this.content });
}
class ExampleApp extends App {
ExampleApp() {
for (int i = 0; i < 20; ++i) {
_data.add(new CircleData(
_data.add(new CardData(
key: new GlobalKey(),
content: '$i'
));
}
}
final List<CircleData> _data = new List<CircleData>();
final List<CardData> _data = new List<CardData>();
GlobalKey _keyToMimic;
GlobalKey _overlay;
Widget _buildCircle(CircleData circleData) {
return new Mimicable(
key: circleData.key,
child: new Listener(
child: new Circle(
child: new Text(circleData.content)
),
onGestureTap: (_) {
setState(() {
_keyToMimic = circleData.key;
});
}
Widget _buildCard(CardData cardData) {
return new Listener(
onGestureTap: (_) {
setState(() {
_overlay = cardData.key;
});
},
child: new Container(
height: 100.0,
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() {
List<Widget> circles = new List<Widget>();
for (int i = 0; i < 20; ++i) {
circles.add(_buildCircle(_data[i]));
List<Widget> cards = new List<Widget>();
for (int i = 0; i < _data.length; ++i) {
cards.add(_buildCard(_data[i]));
}
List<Widget> layers = new List<Widget>();
layers.add(new ScrollableBlock(circles));
if (_keyToMimic != null) {
layers.add(
new Positioned(
top: 50.0,
left: 50.0,
child: new Mimic(
original: _keyToMimic)
return new IconTheme(
data: const IconThemeData(color: IconThemeColor.white),
child: new Theme(
data: new ThemeData(
brightness: ThemeBrightness.light,
primarySwatch: Blue,
accentColor: RedAccent[200]
),
child: new Scaffold(
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';
export 'widgets/material.dart';
export 'widgets/material_button.dart';
export 'widgets/mimic.dart';
export 'widgets/mimic.dart';
export 'widgets/mimic_overlay.dart';
export 'widgets/modal_overlay.dart';
export 'widgets/navigator.dart';
export 'widgets/popup_menu.dart';
......
......@@ -4,16 +4,24 @@
import 'package:sky/widgets/basic.dart';
typedef MimicCallback(Rect globalBounds);
class Mimic extends StatefulComponent {
Mimic({ Key key, this.original }) : super(key: key);
Mimic({
Key key,
this.original,
this.callback
}) : super(key: key);
GlobalKey original;
MimicCallback callback;
void initState() {
_requestToStartMimic();
}
void syncFields(Mimic source) {
callback = source.callback;
if (original != source.original) {
_stopMimic();
original = source.original;
......@@ -34,11 +42,11 @@ class Mimic extends StatefulComponent {
}
Mimicable _mimicable;
Size _size;
bool _mimicking = false;
void _requestToStartMimic() {
assert(_mimicable == null);
assert(_size == null);
assert(!_mimicking);
if (original == null)
return;
_mimicable = GlobalKey.getWidget(original) as Mimicable;
......@@ -46,27 +54,25 @@ class Mimic extends StatefulComponent {
_mimicable._requestToStartMimic(this);
}
void _startMimic(GlobalKey key, Size size) {
void _startMimic(GlobalKey key, Rect globalBounds) {
assert(key == original);
setState(() {
_size = size;
_mimicking = true;
});
callback(globalBounds);
}
void _stopMimic() {
if (_mimicable != null)
_mimicable._didStopMimic(this);
_mimicable = null;
_size = null;
_mimicking = false;
}
Widget build() {
if (_size == null || !_mimicable.mounted)
if (!_mimicking || !_mimicable.mounted)
return new Container();
return new ConstrainedBox(
constraints: new BoxConstraints.tight(_size),
child: _mimicable.child
);
return _mimicable.child;
}
}
......@@ -114,7 +120,9 @@ class Mimicable extends StatefulComponent {
if (_didStartMimic)
return;
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;
}
......
// 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