popup_menu.dart 4.7 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

7 8
import 'package:flutter/animation.dart';
import 'package:flutter/widgets.dart';
9 10

import 'ink_well.dart';
11
import 'material.dart';
12
import 'theme.dart';
13 14

const Duration _kMenuDuration = const Duration(milliseconds: 300);
15
const double _kBaselineOffsetFromBottom = 20.0;
16
const double _kMenuCloseIntervalEnd = 2.0 / 3.0;
17
const double _kMenuHorizontalPadding = 16.0;
18 19 20
const double _kMenuItemHeight = 48.0;
const double _kMenuMaxWidth = 5.0 * _kMenuWidthStep;
const double _kMenuMinWidth = 2.0 * _kMenuWidthStep;
21
const double _kMenuVerticalPadding = 8.0;
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
const double _kMenuWidthStep = 56.0;

class PopupMenuItem<T> extends StatelessComponent {
  PopupMenuItem({
    Key key,
    this.value,
    this.child
  }) : super(key: key);

  final Widget child;
  final T value;

  Widget build(BuildContext context) {
    return new Container(
      height: _kMenuItemHeight,
      padding: const EdgeDims.symmetric(horizontal: _kMenuHorizontalPadding),
      child: new DefaultTextStyle(
        style: Theme.of(context).text.subhead,
        child: new Baseline(
          baseline: _kMenuItemHeight - _kBaselineOffsetFromBottom,
          child: child
        )
      )
    );
  }
}
48

Hixie's avatar
Hixie committed
49
class _PopupMenu<T> extends StatelessComponent {
Adam Barth's avatar
Adam Barth committed
50
  _PopupMenu({
51
    Key key,
Adam Barth's avatar
Adam Barth committed
52 53
    this.route
  }) : super(key: key);
54

Hixie's avatar
Hixie committed
55
  final _PopupMenuRoute<T> route;
56

57
  Widget build(BuildContext context) {
Adam Barth's avatar
Adam Barth committed
58
    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
59
    List<Widget> children = <Widget>[];
60

Adam Barth's avatar
Adam Barth committed
61
    for (int i = 0; i < route.items.length; ++i) {
62 63 64
      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
65
        performance: route.performance,
66
        opacity: new AnimatedValue<double>(0.0, end: 1.0, curve: new Interval(start, end)),
67
        child: new InkWell(
Hixie's avatar
Hixie committed
68
          onTap: () => Navigator.pop(context, route.items[i].value),
Adam Barth's avatar
Adam Barth committed
69
          child: route.items[i]
70
        ))
71
      );
72
    }
73

74
    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
75
    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
76 77
    final AnimatedValue<double> height = new AnimatedValue<double>(0.0, end: 1.0, curve: new Interval(0.0, unit * route.items.length));

78 79 80 81 82 83
    return new BuilderTransition(
      performance: route.performance,
      variables: <AnimatedValue<double>>[opacity, width, height],
      builder: (BuildContext context) {
        return new Opacity(
          opacity: opacity.value,
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
          child: new Material(
            type: MaterialType.card,
            elevation: route.elevation,
            child: new Align(
              alignment: const FractionalOffset(1.0, 0.0),
              widthFactor: width.value,
              heightFactor: height.value,
              child: new ConstrainedBox(
                constraints: new BoxConstraints(
                  minWidth: _kMenuMinWidth,
                  maxWidth: _kMenuMaxWidth
                ),
                child: new IntrinsicWidth(
                  stepWidth: _kMenuWidthStep,
                  child: new Block(
                    children,
                    padding: const EdgeDims.symmetric(
                      vertical: _kMenuVerticalPadding
                    )
103
                  )
104
                )
105 106
              )
            )
Adam Barth's avatar
Adam Barth committed
107
          )
108 109
        );
      }
110 111 112
    );
  }
}
113

Hixie's avatar
Hixie committed
114 115 116 117 118 119 120
class _PopupMenuRoute<T> extends PopupRoute<T> {
  _PopupMenuRoute({
    Completer<T> completer,
    this.position,
    this.items,
    this.elevation
  }) : super(completer: completer);
121

122
  final ModalPosition position;
Hixie's avatar
Hixie committed
123
  final List<PopupMenuItem<T>> items;
Hans Muller's avatar
Hans Muller committed
124
  final int elevation;
125

126 127 128 129
  ModalPosition getPosition(BuildContext context) {
    return position;
  }

130 131 132 133 134
  PerformanceView createPerformance() {
    return new CurvedPerformance(
      super.createPerformance(),
      reverseCurve: new Interval(0.0, _kMenuCloseIntervalEnd)
     );
135 136
  }

137
  Duration get transitionDuration => _kMenuDuration;
Hixie's avatar
Hixie committed
138 139
  bool get barrierDismissable => true;
  Color get barrierColor => null;
140

141 142 143
  Widget buildPage(BuildContext context, PerformanceView performance, PerformanceView forwardPerformance) {
    return new _PopupMenu(route: this);
  }
144 145
}

Hans Muller's avatar
Hans Muller committed
146
Future showMenu({ BuildContext context, ModalPosition position, List<PopupMenuItem> items, int elevation: 8 }) {
147
  Completer completer = new Completer();
Hixie's avatar
Hixie committed
148
  Navigator.push(context, new _PopupMenuRoute(
149 150
    completer: completer,
    position: position,
Adam Barth's avatar
Adam Barth committed
151
    items: items,
Hans Muller's avatar
Hans Muller committed
152
    elevation: elevation
153 154 155
  ));
  return completer.future;
}