Commit 0206f1a6 authored by Adam Barth's avatar Adam Barth

Port PopupMenu to fn3

This changes how PopupMenu works slightly because there's no long an onPressed
callback on PopupMenuItem. Instead, callers should use showPopupMenu.
parent 82dd9d65
// 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:async';
import 'dart:sky' as sky;
import 'package:sky/animation.dart';
import 'package:sky/painting.dart';
import 'package:sky/material.dart';
import 'package:sky/src/fn3/basic.dart';
import 'package:sky/src/fn3/focus.dart';
import 'package:sky/src/fn3/framework.dart';
import 'package:sky/src/fn3/gesture_detector.dart';
import 'package:sky/src/fn3/navigator.dart';
import 'package:sky/src/fn3/popup_menu_item.dart';
import 'package:sky/src/fn3/scrollable.dart';
import 'package:sky/src/fn3/transitions.dart';
const Duration _kMenuDuration = const Duration(milliseconds: 300);
const double _kMenuCloseIntervalEnd = 2.0 / 3.0;
const double _kMenuWidthStep = 56.0;
const double _kMenuMargin = 16.0; // 24.0 on tablet
const double _kMenuMinWidth = 2.0 * _kMenuWidthStep;
const double _kMenuMaxWidth = 5.0 * _kMenuWidthStep;
const double _kMenuHorizontalPadding = 16.0;
const double _kMenuVerticalPadding = 8.0;
class PopupMenu extends StatefulComponent {
PopupMenu({
Key key,
this.items,
this.level: 4,
this.navigator,
this.performance
}) : super(key: key) {
assert(items != null);
assert(performance != null);
}
final List<PopupMenuItem> items;
final int level;
final NavigatorState navigator;
final WatchableAnimationPerformance performance;
PopupMenuState createState() => new PopupMenuState(this);
}
class PopupMenuState extends ComponentState<PopupMenu> {
PopupMenuState(PopupMenu config) : super(config) {
_updateBoxPainter();
config.performance.addListener(_performanceChanged);
}
BoxPainter _painter;
void _updateBoxPainter() {
_painter = new BoxPainter(
new BoxDecoration(
backgroundColor: Colors.grey[50],
borderRadius: 2.0,
boxShadow: shadows[config.level]
)
);
}
void didUpdateConfig(PopupMenu oldConfig) {
if (config.level != config.level)
_updateBoxPainter();
if (config.performance != oldConfig.performance) {
oldConfig.performance.removeListener(_performanceChanged);
config.performance.addListener(_performanceChanged);
}
}
void dispose() {
config.performance.removeListener(_performanceChanged);
}
void _performanceChanged() {
setState(() {
// the performance changed, and our state is tied up with the performance
});
}
Widget build(BuildContext context) {
double unit = 1.0 / (config.items.length + 1.5); // 1.0 for the width and 0.5 for the last item's fade.
List<Widget> children = [];
for (int i = 0; i < config.items.length; ++i) {
double start = (i + 1) * unit;
double end = (start + 1.5 * unit).clamp(0.0, 1.0);
children.add(new FadeTransition(
performance: config.performance,
opacity: new AnimatedValue<double>(0.0, end: 1.0, interval: new Interval(start, end)),
child: new GestureDetector(
onTap: () { config.navigator.pop(config.items[i].value); },
child: config.items[i]
))
);
}
final width = new AnimatedValue<double>(0.0, end: 1.0, interval: new Interval(0.0, unit));
final height = new AnimatedValue<double>(0.0, end: 1.0, interval: new Interval(0.0, unit * config.items.length));
return new FadeTransition(
performance: config.performance,
opacity: new AnimatedValue<double>(0.0, end: 1.0, interval: new Interval(0.0, 1.0 / 3.0)),
child: new Container(
margin: new EdgeDims.all(_kMenuMargin),
child: new BuilderTransition(
performance: config.performance,
variables: [width, height],
builder: (BuildContext context) {
return new CustomPaint(
callback: (sky.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,
child: new ScrollableViewport(
child: new Container(
padding: const EdgeDims.symmetric(
horizontal: _kMenuHorizontalPadding,
vertical: _kMenuVerticalPadding
),
child: new BlockBody(children)
)
)
)
)
);
}
)
)
);
}
}
class MenuPosition {
const MenuPosition({ this.top, this.right, this.bottom, this.left });
final double top;
final double right;
final double bottom;
final double left;
}
class MenuRoute extends RouteBase {
MenuRoute({ this.completer, this.position, this.builder, this.level });
final Completer completer;
final MenuPosition position;
final PopupMenuItemsBuilder builder;
final int level;
AnimationPerformance createPerformance() {
AnimationPerformance result = super.createPerformance();
AnimationTiming timing = new AnimationTiming();
timing.reverseInterval = new Interval(0.0, _kMenuCloseIntervalEnd);
result.timing = timing;
return result;
}
Duration get transitionDuration => _kMenuDuration;
bool get isOpaque => false;
Widget build(Key key, NavigatorState navigator, WatchableAnimationPerformance performance) {
return new Positioned(
top: position?.top,
right: position?.right,
bottom: position?.bottom,
left: position?.left,
child: new Focus(
key: new GlobalObjectKey(this),
autofocus: true,
child: new PopupMenu(
key: key,
items: builder != null ? builder(navigator) : const <PopupMenuItem>[],
level: level,
navigator: navigator,
performance: performance
)
)
);
}
void popState([dynamic result]) {
completer.complete(result);
}
}
typedef List<PopupMenuItem> PopupMenuItemsBuilder(NavigatorState navigator);
Future showMenu({ NavigatorState navigator, MenuPosition position, PopupMenuItemsBuilder builder, int level: 4 }) {
Completer completer = new Completer();
navigator.push(new MenuRoute(
completer: completer,
position: position,
builder: builder,
level: level
));
return completer.future;
}
// 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/src/fn3/basic.dart';
import 'package:sky/src/fn3/framework.dart';
import 'package:sky/src/fn3/ink_well.dart';
import 'package:sky/src/fn3/theme.dart';
const double _kMenuItemHeight = 48.0;
const double _kBaselineOffsetFromBottom = 20.0;
class PopupMenuItem extends StatelessComponent {
PopupMenuItem({
Key key,
this.value,
this.child
}) : super(key: key);
final Widget child;
final dynamic value;
Widget build(BuildContext context) {
return new InkWell(
child: new Container(
height: _kMenuItemHeight,
child: new DefaultTextStyle(
style: Theme.of(context).text.subhead,
child: new Baseline(
baseline: _kMenuItemHeight - _kBaselineOffsetFromBottom,
child: child
)
)
)
);
}
}
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