ink_well.dart 4.04 KB
Newer Older
1 2 3 4 5 6 7
// 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:math' as math;
import 'dart:sky' as sky;

8 9
import 'package:sky/animation.dart';
import 'package:sky/rendering.dart';
10 11
import 'package:sky/src/widgets/basic.dart';
import 'package:sky/src/widgets/framework.dart';
12

13 14 15
const int _kSplashInitialOpacity = 0x30;
const double _kSplashCancelledVelocity = 0.7;
const double _kSplashConfirmedVelocity = 0.7;
16
const double _kSplashInitialSize = 0.0;
17
const double _kSplashUnconfirmedVelocity = 0.2;
18 19 20 21 22 23 24 25 26 27 28 29

double _getSplashTargetSize(Size bounds, Point position) {
  double d1 = (position - bounds.topLeft(Point.origin)).distance;
  double d2 = (position - bounds.topRight(Point.origin)).distance;
  double d3 = (position - bounds.bottomLeft(Point.origin)).distance;
  double d4 = (position - bounds.bottomRight(Point.origin)).distance;
  return math.max(math.max(d1, d2), math.max(d3, d4)).ceil().toDouble();
}

class InkSplash {
  InkSplash(this.pointer, this.position, this.well) {
    _targetRadius = _getSplashTargetSize(well.size, position);
30
    _radius = new AnimatedValue<double>(
31 32
        _kSplashInitialSize, end: _targetRadius, curve: easeOut);

33 34 35 36 37
    _performance = new ValueAnimation<double>(
      variable: _radius,
      duration: new Duration(milliseconds: (_targetRadius / _kSplashUnconfirmedVelocity).floor())
    )..addListener(_handleRadiusChange)
     ..play();
38 39 40 41 42 43 44 45
  }

  final int pointer;
  final Point position;
  final RenderInkWell well;

  double _targetRadius;
  double _pinnedRadius;
46
  AnimatedValue<double> _radius;
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
  AnimationPerformance _performance;

  void _updateVelocity(double velocity) {
    int duration = (_targetRadius / velocity).floor();
    _performance
      ..duration = new Duration(milliseconds: duration)
      ..play();
  }

  void confirm() {
    _updateVelocity(_kSplashConfirmedVelocity);
  }

  void cancel() {
    _updateVelocity(_kSplashCancelledVelocity);
    _pinnedRadius = _radius.value;
  }

  void _handleRadiusChange() {
    if (_radius.value == _targetRadius)
      well._splashes.remove(this);
    well.markNeedsPaint();
  }

  void paint(PaintingCanvas canvas) {
72
    int opacity = (_kSplashInitialOpacity * (1.1 - (_radius.value / _targetRadius))).floor();
73 74 75 76 77 78 79 80 81 82 83
    sky.Paint paint = new sky.Paint()..color = new sky.Color(opacity << 24);
    double radius = _pinnedRadius == null ? _radius.value : _pinnedRadius;
    canvas.drawCircle(position, radius, paint);
  }
}

class RenderInkWell extends RenderProxyBox {
  RenderInkWell({ RenderBox child }) : super(child);

  final List<InkSplash> _splashes = new List<InkSplash>();

Adam Barth's avatar
Adam Barth committed
84
  void handleEvent(sky.Event event, BoxHitTestEntry entry) {
85 86 87
    // TODO(abarth): We should trigger these effects based on gestures.
    // https://github.com/flutter/engine/issues/1271
    if (event is sky.PointerEvent) {
88
      switch (event.type) {
89 90
        case 'pointerdown':
          _startSplash(event.pointer, entry.localPosition);
Adam Barth's avatar
Adam Barth committed
91
          break;
92 93
        case 'pointerup':
          _confirmSplash(event.pointer);
Adam Barth's avatar
Adam Barth committed
94
          break;
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
      }
    }
  }

  void _startSplash(int pointer, Point position) {
    _splashes.add(new InkSplash(pointer, position, this));
    markNeedsPaint();
  }

  void _forEachSplash(int pointer, Function callback) {
    _splashes.where((splash) => splash.pointer == pointer)
             .forEach(callback);
  }

  void _confirmSplash(int pointer) {
    _forEachSplash(pointer, (splash) { splash.confirm(); });
    markNeedsPaint();
  }

114
  void paint(PaintingContext context, Offset offset) {
115
    if (!_splashes.isEmpty) {
116
      final PaintingCanvas canvas = context.canvas;
117 118 119 120 121 122 123
      canvas.save();
      canvas.translate(offset.dx, offset.dy);
      canvas.clipRect(Point.origin & size);
      for (InkSplash splash in _splashes)
        splash.paint(canvas);
      canvas.restore();
    }
124
    super.paint(context, offset);
125 126 127
  }
}

128 129 130
class InkWell extends OneChildRenderObjectWidget {
  InkWell({ Key key, Widget child }) : super(key: key, child: child);
  RenderInkWell createRenderObject() => new RenderInkWell();
131
}