popup_menu.dart 4.24 KB
Newer Older
1 2 3 4
// 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.

5
import 'dart:async';
6
import 'dart:ui' as ui;
7

8 9 10
import 'package:flutter/animation.dart';
import 'package:flutter/painting.dart';
import 'package:flutter/widgets.dart';
11 12 13

import 'ink_well.dart';
import 'popup_menu_item.dart';
14
import 'shadows.dart';
15
import 'theme.dart';
16 17

const Duration _kMenuDuration = const Duration(milliseconds: 300);
18
const double _kMenuCloseIntervalEnd = 2.0 / 3.0;
19 20 21 22 23 24
const double _kMenuWidthStep = 56.0;
const double _kMenuMinWidth = 2.0 * _kMenuWidthStep;
const double _kMenuMaxWidth = 5.0 * _kMenuWidthStep;
const double _kMenuHorizontalPadding = 16.0;
const double _kMenuVerticalPadding = 8.0;

Adam Barth's avatar
Adam Barth committed
25 26
class _PopupMenu extends StatelessComponent {
  _PopupMenu({
27
    Key key,
Adam Barth's avatar
Adam Barth committed
28 29
    this.route
  }) : super(key: key);
30

Adam Barth's avatar
Adam Barth committed
31
  final _MenuRoute route;
32

33
  Widget build(BuildContext context) {
34
    final BoxPainter painter = new BoxPainter(new BoxDecoration(
35 36
      backgroundColor: Theme.of(context).canvasColor,
      borderRadius: 2.0,
Hans Muller's avatar
Hans Muller committed
37
      boxShadow: elevationToShadow[route.elevation]
38
    ));
39

Adam Barth's avatar
Adam Barth committed
40
    double unit = 1.0 / (route.items.length + 1.5); // 1.0 for the width and 0.5 for the last item's fade.
Hixie's avatar
Hixie committed
41
    List<Widget> children = <Widget>[];
42

Adam Barth's avatar
Adam Barth committed
43
    for (int i = 0; i < route.items.length; ++i) {
44 45 46
      double start = (i + 1) * unit;
      double end = (start + 1.5 * unit).clamp(0.0, 1.0);
      children.add(new FadeTransition(
Adam Barth's avatar
Adam Barth committed
47
        performance: route.performance,
48
        opacity: new AnimatedValue<double>(0.0, end: 1.0, curve: new Interval(start, end)),
49
        child: new InkWell(
Adam Barth's avatar
Adam Barth committed
50 51
          onTap: () { Navigator.of(context).pop(route.items[i].value); },
          child: route.items[i]
52
        ))
53
      );
54
    }
55

56
    final AnimatedValue<double> opacity = new AnimatedValue<double>(0.0, end: 1.0, curve: new Interval(0.0, 1.0 / 3.0));
Hixie's avatar
Hixie committed
57
    final AnimatedValue<double> width = new AnimatedValue<double>(0.0, end: 1.0, curve: new Interval(0.0, unit));
Adam Barth's avatar
Adam Barth committed
58 59
    final AnimatedValue<double> height = new AnimatedValue<double>(0.0, end: 1.0, curve: new Interval(0.0, unit * route.items.length));

60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
    return new BuilderTransition(
      performance: route.performance,
      variables: <AnimatedValue<double>>[opacity, width, height],
      builder: (BuildContext context) {
        return new Opacity(
          opacity: opacity.value,
          child: new CustomPaint(
            onPaint: (ui.Canvas canvas, Size size) {
              double widthValue = width.value * size.width;
              double heightValue = height.value * size.height;
              painter.paint(canvas, new Rect.fromLTWH(size.width - widthValue, 0.0, widthValue, heightValue));
            },
            child: new ConstrainedBox(
              constraints: new BoxConstraints(
                minWidth: _kMenuMinWidth,
                maxWidth: _kMenuMaxWidth
              ),
              child: new IntrinsicWidth(
                stepWidth: _kMenuWidthStep,
Hixie's avatar
Hixie committed
79 80 81 82 83
                child: new Block(
                  children,
                  padding: const EdgeDims.symmetric(
                    horizontal: _kMenuHorizontalPadding,
                    vertical: _kMenuVerticalPadding
84
                  )
85
                )
86 87
              )
            )
Adam Barth's avatar
Adam Barth committed
88
          )
89 90
        );
      }
91 92 93
    );
  }
}
94

95
class _MenuRoute extends ModalRoute {
Hans Muller's avatar
Hans Muller committed
96
  _MenuRoute({ Completer completer, this.position, this.items, this.elevation }) : super(completer: completer);
97

98
  final ModalPosition position;
Adam Barth's avatar
Adam Barth committed
99
  final List<PopupMenuItem> items;
Hans Muller's avatar
Hans Muller committed
100
  final int elevation;
101

102 103
  Performance createPerformance() {
    Performance result = super.createPerformance();
104
    AnimationTiming timing = new AnimationTiming();
105
    timing.reverseCurve = new Interval(0.0, _kMenuCloseIntervalEnd);
106 107 108 109
    result.timing = timing;
    return result;
  }

110
  bool get opaque => false;
111 112
  Duration get transitionDuration => _kMenuDuration;

113
  Widget buildPage(BuildContext context) => new _PopupMenu(route: this);
114 115
}

Hans Muller's avatar
Hans Muller committed
116
Future showMenu({ BuildContext context, ModalPosition position, List<PopupMenuItem> items, int elevation: 8 }) {
117
  Completer completer = new Completer();
Adam Barth's avatar
Adam Barth committed
118
  Navigator.of(context).pushEphemeral(new _MenuRoute(
119 120
    completer: completer,
    position: position,
Adam Barth's avatar
Adam Barth committed
121
    items: items,
Hans Muller's avatar
Hans Muller committed
122
    elevation: elevation
123 124 125
  ));
  return completer.future;
}