|  | // 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 'dart:sky' as sky; | 
|  |  | 
|  | import 'package:sky/base/lerp.dart'; | 
|  | import 'package:sky/rendering.dart'; | 
|  | import 'package:sky/theme/colors.dart' as colors; | 
|  | import 'package:sky/widgets.dart'; | 
|  |  | 
|  | class CardModel { | 
|  | CardModel(this.value, this.height, this.color); | 
|  | int value; | 
|  | double height; | 
|  | Color color; | 
|  | String get label => "Card $value"; | 
|  | Key get key => new ObjectKey(this); | 
|  | } | 
|  |  | 
|  | enum MarkerType { topLeft, bottomRight, touch } | 
|  |  | 
|  | class Marker extends Component { | 
|  | Marker({ | 
|  | this.type: MarkerType.touch, | 
|  | this.position, | 
|  | this.size: 40.0, | 
|  | Key key }) : super(key: key); | 
|  |  | 
|  | final Point position; | 
|  | final double size; | 
|  | final MarkerType type; | 
|  |  | 
|  | void paintMarker(sky.Canvas canvas, _) { | 
|  | Paint paint = new Paint()..color = const Color(0x8000FF00); | 
|  | paint.setStyle(sky.PaintingStyle.fill); | 
|  | double r = size / 2.0; | 
|  | canvas.drawCircle(new Point(r, r), r, paint); | 
|  |  | 
|  | paint.color = const Color(0xFFFFFFFF); | 
|  | paint.setStyle(sky.PaintingStyle.stroke); | 
|  | paint.strokeWidth = 1.0; | 
|  | if (type == MarkerType.topLeft) { | 
|  | canvas.drawLine(new Point(r, r), new Point(r + r - 1.0, r), paint); | 
|  | canvas.drawLine(new Point(r, r), new Point(r, r + r - 1.0), paint); | 
|  | } | 
|  | if (type == MarkerType.bottomRight) { | 
|  | canvas.drawLine(new Point(r, r), new Point(1.0, r), paint); | 
|  | canvas.drawLine(new Point(r, r), new Point(r, 1.0), paint); | 
|  | } | 
|  | } | 
|  |  | 
|  | Widget build() { | 
|  | return new Positioned( | 
|  | left: position.x - size / 2.0, | 
|  | top: position.y - size / 2.0, | 
|  | child: new IgnorePointer( | 
|  | child: new Container( | 
|  | width: size, | 
|  | height: size, | 
|  | child: new CustomPaint(callback: paintMarker) | 
|  | ) | 
|  | ) | 
|  | ); | 
|  | } | 
|  | } | 
|  |  | 
|  | class OverlayGeometryApp extends App { | 
|  |  | 
|  | static const TextStyle cardLabelStyle = | 
|  | const TextStyle(color: colors.white, fontSize: 18.0, fontWeight: bold); | 
|  |  | 
|  | List<CardModel> cardModels; | 
|  | MixedViewportLayoutState layoutState = new MixedViewportLayoutState(); | 
|  | Map<MarkerType, Point> markers = new Map<MarkerType, Point>(); | 
|  | double markersScrollOffset; | 
|  | ScrollListener scrollListener; | 
|  |  | 
|  | void initState() { | 
|  | List<double> cardHeights = <double>[ | 
|  | 48.0, 63.0, 82.0, 146.0, 60.0, 55.0, 84.0, 96.0, 50.0, | 
|  | 48.0, 63.0, 82.0, 146.0, 60.0, 55.0, 84.0, 96.0, 50.0, | 
|  | 48.0, 63.0, 82.0, 146.0, 60.0, 55.0, 84.0, 96.0, 50.0 | 
|  | ]; | 
|  | cardModels = new List.generate(cardHeights.length, (i) { | 
|  | Color color = lerpColor(colors.Red[300], colors.Blue[900], i / cardHeights.length); | 
|  | return new CardModel(i, cardHeights[i], color); | 
|  | }); | 
|  | super.initState(); | 
|  | } | 
|  |  | 
|  | void handleScroll(Scrollable scrollable) { | 
|  | setState(() { | 
|  | double dy = markersScrollOffset - scrollable.scrollOffset; | 
|  | markersScrollOffset = scrollable.scrollOffset; | 
|  | for (MarkerType type in markers.keys) { | 
|  | Point oldPosition = markers[type]; | 
|  | markers[type] = new Point(oldPosition.x, oldPosition.y + dy); | 
|  | } | 
|  | }); | 
|  | } | 
|  |  | 
|  | EventDisposition handlePointerDown(Widget target, sky.PointerEvent event) { | 
|  | setState(() { | 
|  | markers[MarkerType.touch] = new Point(event.x, event.y); | 
|  | markers[MarkerType.topLeft] = target.localToGlobal(new Point(0.0, 0.0)); | 
|  | Size size = (target.renderObject as RenderBox).size; | 
|  | markers[MarkerType.bottomRight] = target.localToGlobal(new Point(size.width, size.height)); | 
|  |  | 
|  | Scrollable scrollable = findScrollableAncestor(target: target); | 
|  | markersScrollOffset = scrollable.scrollOffset; | 
|  | if (scrollListener == null) { | 
|  | scrollListener = () { handleScroll(scrollable); }; | 
|  | scrollable.addListener(scrollListener); | 
|  | } | 
|  | }); | 
|  |  | 
|  | return EventDisposition.processed; | 
|  | } | 
|  |  | 
|  | Widget builder(int index) { | 
|  | if (index >= cardModels.length) | 
|  | return null; | 
|  | CardModel cardModel = cardModels[index]; | 
|  | Widget card = new Card( | 
|  | color: cardModel.color, | 
|  | child: new Container( | 
|  | height: cardModel.height, | 
|  | padding: const EdgeDims.all(8.0), | 
|  | child: new Center(child: new Text(cardModel.label, style: cardLabelStyle)) | 
|  | ) | 
|  | ); | 
|  | return new Listener( | 
|  | key: cardModel.key, | 
|  | onPointerDown: (e) { return handlePointerDown(card, e); }, | 
|  | child: card | 
|  | ); | 
|  | } | 
|  |  | 
|  | Widget build() { | 
|  | Scrollable scrollable = new ScrollableMixedWidgetList( | 
|  | builder: builder, | 
|  | token: cardModels.length, | 
|  | layoutState: layoutState | 
|  | ); | 
|  |  | 
|  | Widget cardCollection = new Container( | 
|  | padding: const EdgeDims.symmetric(vertical: 12.0, horizontal: 8.0), | 
|  | decoration: new BoxDecoration(backgroundColor: Theme.of(this).primarySwatch[50]), | 
|  | child: scrollable | 
|  | ); | 
|  |  | 
|  | List<Widget> layers = <Widget>[ | 
|  | new Scaffold( | 
|  | toolbar: new ToolBar(center: new Text('Tap a Card')), | 
|  | body: cardCollection | 
|  | ) | 
|  | ]; | 
|  | for (MarkerType type in markers.keys) | 
|  | layers.add(new Marker(type: type, position: markers[type])); | 
|  |  | 
|  | return new IconTheme( | 
|  | data: const IconThemeData(color: IconThemeColor.white), | 
|  | child: new Theme( | 
|  | data: new ThemeData( | 
|  | brightness: ThemeBrightness.light, | 
|  | primarySwatch: colors.Blue, | 
|  | accentColor: colors.RedAccent[200] | 
|  | ), | 
|  | child: new Title(title: 'Cards', child: new Stack(layers)) | 
|  | ) | 
|  | ); | 
|  | } | 
|  | } | 
|  |  | 
|  | void main() { | 
|  | runApp(new OverlayGeometryApp()); | 
|  | } |