Commit f1df2bd7 authored by Hans Muller's avatar Hans Muller

Gallery Menu Demo etc

parent 0c05666e
......@@ -27,6 +27,8 @@ material-design-icons:
- name: action/account_circle
- name: action/alarm
- name: action/android
- name: action/delete
- name: action/done
- name: action/event
- name: action/face
- name: action/home
......@@ -58,3 +60,4 @@ material-design-icons:
- name: navigation/menu
- name: navigation/more_horiz
- name: navigation/more_vert
- name: social/person_add
......@@ -129,6 +129,8 @@ class GridListDemoGridDelegate extends FixedColumnCountGridDelegate {
}
class GridListDemo extends StatefulComponent {
GridListDemo({ Key key }) : super(key: key);
GridListDemoState createState() => new GridListDemoState();
}
......
......@@ -175,11 +175,9 @@ class ListDemoState extends State<ListDemo> {
)
]
),
body: new Padding(
padding: const EdgeDims.all(8.0),
child: new Block(
children: items.map((String item) => buildListItem(context, item)).toList()
)
body: new Block(
padding: new EdgeDims.all(_isDense ? 4.0 : 8.0),
children: items.map((String item) => buildListItem(context, item)).toList()
)
);
}
......
// Copyright 2016 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:flutter/material.dart';
class MenuDemo extends StatefulComponent {
MenuDemo({ Key key }) : super(key: key);
MenuDemoState createState() => new MenuDemoState();
}
class MenuDemoState extends State<MenuDemo> {
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
final String _simpleValue1 = 'Menu item value one';
final String _simpleValue2 = 'Menu item value two';
final String _simpleValue3 = 'Menu item value three';
String _simpleValue;
final String _checkedValue1 = 'One';
final String _checkedValue2 = 'Two';
final String _checkedValue3 = 'Free';
final String _checkedValue4 = 'Four';
List<String> _checkedValues;
void initState() {
super.initState();
_simpleValue = _simpleValue2;
_checkedValues = <String>[_checkedValue3];
}
void showInSnackBar(String value) {
_scaffoldKey.currentState.showSnackBar(new SnackBar(
content: new Text(value)
));
}
void showMenuSelection(String value) {
if (<String>[_simpleValue1, _simpleValue2, _simpleValue3].contains(value))
_simpleValue = value;
showInSnackBar('You selected: $value');
}
void showCheckedMenuSelections(String value) {
if (_checkedValues.contains(value))
_checkedValues.remove(value);
else
_checkedValues.add(value);
showInSnackBar('Checked $_checkedValues');
}
bool isChecked(String value) => _checkedValues.contains(value);
Widget build(BuildContext context) {
return new Scaffold(
key: _scaffoldKey,
toolBar: new ToolBar(
center: new Text('Menus'),
right: <Widget>[
new PopupMenuButton<String>(
onSelected: showMenuSelection,
items: <PopupMenuItem>[
new PopupMenuItem(
value: 'ToolBar Menu',
child: new Text('ToolBar Menu')
),
new PopupMenuItem(
value: 'Right Here',
child: new Text('Right Here')
),
new PopupMenuItem(
value: 'Hooray!',
child: new Text('Hooray!')
),
]
)
]
),
body: new Block(
padding: const EdgeDims.all(8.0),
children: <Widget>[
// Pressing the PopupMenuButton on the right of this item shows
// a simple menu with one disabled item. Typically the contents
// of this "contextual menu" would reflect the app's state.
new ListItem(
primary: new Text('An item with a context menu button'),
right: new PopupMenuButton<String>(
onSelected: showMenuSelection,
items: <PopupMenuItem>[
new PopupMenuItem(
value: _simpleValue1,
child: new Text('Context menu item one')
),
new PopupMenuItem(
disabled: true,
child: new Text('A disabled menu item')
),
new PopupMenuItem(
value: _simpleValue3,
child: new Text('Context menu item three')
),
]
)
),
// Pressing the PopupMenuButton on the right of this item shows
// a menu whose items have text labels and icons and a divider
// That separates the first three items from the last one.
new ListItem(
primary: new Text('An item with a sectioned menu'),
right: new PopupMenuButton<String>(
onSelected: showMenuSelection,
items: <PopupMenuItem>[
new PopupMenuItem(
value: 'Preview',
child: new ListItem(
left: new Icon(icon: 'action/visibility'),
primary: new Text('Preview')
)
),
new PopupMenuItem(
value: 'Share',
child: new ListItem(
left: new Icon(icon: 'social/person_add'),
primary: new Text('Share')
)
),
new PopupMenuItem(
value: 'Get Link',
hasDivider: true,
child: new ListItem(
left: new Icon(icon: 'content/link'),
primary: new Text('Get Link')
)
),
new PopupMenuItem(
value: 'Remove',
child: new ListItem(
left: new Icon(icon: 'action/delete'),
primary: new Text('Remove')
)
)
]
)
),
// This entire list item is a PopupMenuButton. Tapping anywhere shows
// a menu whose current value is highlighted and aligned over the
// list item's center line.
new PopupMenuButton<String>(
initialValue: _simpleValue,
onSelected: showMenuSelection,
child: new ListItem(
primary: new Text('An item with a simple menu'),
secondary: new Text(_simpleValue)
),
items: <PopupMenuItem>[
new PopupMenuItem(
value: _simpleValue1,
child: new Text(_simpleValue1)
),
new PopupMenuItem(
value: _simpleValue2,
child: new Text(_simpleValue2)
),
new PopupMenuItem(
value: _simpleValue3,
child: new Text(_simpleValue3)
)
]
),
// Pressing the PopupMenuButton on the right of this item shows a menu
// whose items have checked icons that reflect this app's state.
new ListItem(
primary: new Text('An item with a checklist menu'),
right: new PopupMenuButton<String>(
onSelected: showCheckedMenuSelections,
items: <PopupMenuItem>[
new PopupMenuItem(
value: _checkedValue1,
child: new ListItem(
left: new Icon(icon: isChecked(_checkedValue1) ? 'action/done' : null),
primary: new Text(_checkedValue1)
)
),
new PopupMenuItem(
value: _checkedValue2,
child: new ListItem(
left: new Icon(icon: isChecked(_checkedValue2) ? 'action/done' : null),
primary: new Text(_checkedValue2)
)
),
new PopupMenuItem(
value: _checkedValue3,
child: new ListItem(
left: new Icon(icon: isChecked(_checkedValue3) ? 'action/done' : null),
primary: new Text(_checkedValue3)
)
),
new PopupMenuItem(
value: _checkedValue4,
child: new ListItem(
left: new Icon(icon: isChecked(_checkedValue4) ? 'action/done' : null),
primary: new Text(_checkedValue4)
)
),
]
)
)
]
)
);
}
}
......@@ -21,6 +21,7 @@ import '../demo/grid_list_demo.dart';
import '../demo/icons_demo.dart';
import '../demo/list_demo.dart';
import '../demo/modal_bottom_sheet_demo.dart';
import '../demo/menu_demo.dart';
import '../demo/page_selector_demo.dart';
import '../demo/persistent_bottom_sheet_demo.dart';
import '../demo/progress_indicator_demo.dart';
......@@ -108,6 +109,7 @@ class GalleryHomeState extends State<GalleryHome> {
new GalleryDemo(title: 'Icons', builder: () => new IconsDemo()),
new GalleryDemo(title: 'List', builder: () => new ListDemo()),
new GalleryDemo(title: 'Modal Bottom Sheet', builder: () => new ModalBottomSheetDemo()),
new GalleryDemo(title: 'Menus', builder: () => new MenuDemo()),
new GalleryDemo(title: 'Page Selector', builder: () => new PageSelectorDemo()),
new GalleryDemo(title: 'Persistent Bottom Sheet', builder: () => new PersistentBottomSheetDemo()),
new GalleryDemo(title: 'Progress Indicators', builder: () => new ProgressIndicatorDemo()),
......
......@@ -27,12 +27,11 @@ class Icon extends StatelessComponent {
Icon({
Key key,
this.size: IconSize.s24,
this.icon: '',
this.icon,
this.colorTheme,
this.color
}) : super(key: key) {
assert(size != null);
assert(icon != null);
}
final IconSize size;
......@@ -54,6 +53,14 @@ class Icon extends StatelessComponent {
}
Widget build(BuildContext context) {
final int iconSize = _kIconSize[size];
if (icon == null) {
return new SizedBox(
width: iconSize.toDouble(),
height: iconSize.toDouble()
);
}
String category = '';
String subtype = '';
List<String> parts = icon.split('/');
......@@ -62,7 +69,6 @@ class Icon extends StatelessComponent {
subtype = parts[1];
}
final IconThemeColor iconThemeColor = _getIconThemeColor(context);
final int iconSize = _kIconSize[size];
String colorSuffix;
switch(iconThemeColor) {
......
......@@ -6,6 +6,7 @@ import 'dart:async';
import 'package:flutter/widgets.dart';
import 'icon_button.dart';
import 'ink_well.dart';
import 'material.dart';
import 'theme.dart';
......@@ -19,24 +20,37 @@ const double _kMenuMaxWidth = 5.0 * _kMenuWidthStep;
const double _kMenuMinWidth = 2.0 * _kMenuWidthStep;
const double _kMenuVerticalPadding = 8.0;
const double _kMenuWidthStep = 56.0;
const double _kMenuScreenPadding = 8.0;
class PopupMenuItem<T> extends StatelessComponent {
PopupMenuItem({
Key key,
this.value,
this.disabled: false,
this.hasDivider: false,
this.child
}) : super(key: key);
final Widget child;
final T value;
final bool disabled;
final bool hasDivider;
final Widget child;
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
TextStyle style = theme.text.subhead;
if (disabled)
style = style.copyWith(color: theme.disabledColor);
return new MergeSemantics(
child: new Container(
height: _kMenuItemHeight,
padding: const EdgeDims.symmetric(horizontal: _kMenuHorizontalPadding),
decoration: !hasDivider ? null : new BoxDecoration(
border: new Border(bottom: new BorderSide(color: theme.dividerColor))
),
child: new DefaultTextStyle(
style: Theme.of(context).text.subhead,
style: style,
child: new Baseline(
baseline: _kMenuItemHeight - _kBaselineOffsetFromBottom,
child: child
......@@ -60,19 +74,27 @@ class _PopupMenu<T> extends StatelessComponent {
List<Widget> children = <Widget>[];
for (int i = 0; i < route.items.length; ++i) {
double start = (i + 1) * unit;
double end = (start + 1.5 * unit).clamp(0.0, 1.0);
final double start = (i + 1) * unit;
final double end = (start + 1.5 * unit).clamp(0.0, 1.0);
CurvedAnimation opacity = new CurvedAnimation(
parent: route.animation,
curve: new Interval(start, end)
);
final bool disabled = route.items[i].disabled;
Widget item = route.items[i];
if (route.initialValue != null && route.initialValue == route.items[i].value) {
item = new Container(
decoration: new BoxDecoration(backgroundColor: Theme.of(context).highlightColor),
child: item
);
}
children.add(new FadeTransition(
opacity: opacity,
child: new InkWell(
onTap: () => Navigator.pop(context, route.items[i].value),
child: route.items[i]
))
);
onTap: disabled ? null : () { Navigator.pop(context, route.items[i].value); },
child: item
)
));
}
final CurveTween opacity = new CurveTween(curve: new Interval(0.0, 1.0 / 3.0));
......@@ -117,21 +139,64 @@ class _PopupMenu<T> extends StatelessComponent {
}
}
class _PopupMenuRouteLayout extends OneChildLayoutDelegate {
_PopupMenuRouteLayout(this.position, this.selectedIndex);
final ModalPosition position;
final int selectedIndex;
BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
return new BoxConstraints(
minWidth: 0.0,
maxWidth: constraints.maxWidth,
minHeight: 0.0,
maxHeight: constraints.maxHeight
);
}
// Put the child wherever position specifies, so long as it will fit within the
// specified parent size padded (inset) by 8. If necessary, adjust the child's
// position so that it fits.
Offset getPositionForChild(Size size, Size childSize) {
double x = position?.left
?? (position?.right != null ? size.width - (position.right + childSize.width) : _kMenuScreenPadding);
double y = position?.top
?? (position?.bottom != null ? size.height - (position.bottom - childSize.height) : _kMenuScreenPadding);
if (selectedIndex != -1)
y -= (_kMenuItemHeight * selectedIndex) + _kMenuVerticalPadding + _kMenuItemHeight / 2.0;
if (x < _kMenuScreenPadding)
x = _kMenuScreenPadding;
else if (x + childSize.width > size.width - 2 * _kMenuScreenPadding)
x = size.width - childSize.width - _kMenuScreenPadding;
if (y < _kMenuScreenPadding)
y = _kMenuScreenPadding;
else if (y + childSize.height > size.height - 2 * _kMenuScreenPadding)
y = size.height - childSize.height - _kMenuScreenPadding;
return new Offset(x, y);
}
bool shouldRelayout(_PopupMenuRouteLayout oldDelegate) {
return position != oldDelegate.position;
}
}
class _PopupMenuRoute<T> extends PopupRoute<T> {
_PopupMenuRoute({
Completer<T> completer,
this.position,
this.items,
this.initialValue,
this.elevation
}) : super(completer: completer);
final ModalPosition position;
final List<PopupMenuItem<T>> items;
final dynamic initialValue;
final int elevation;
ModalPosition getPosition(BuildContext context) {
return position;
}
ModalPosition getPosition(BuildContext context) => null;
Animation<double> createAnimation() {
return new CurvedAnimation(
......@@ -145,17 +210,110 @@ class _PopupMenuRoute<T> extends PopupRoute<T> {
Color get barrierColor => null;
Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> forwardAnimation) {
return new _PopupMenu(route: this);
int selectedIndex = -1;
if (initialValue != null) {
for (int i = 0; i < items.length; i++)
if (initialValue == items[i].value) {
selectedIndex = i;
break;
}
}
final Size screenSize = MediaQuery.of(context).size;
return new ConstrainedBox(
constraints: new BoxConstraints(maxWidth: screenSize.width, maxHeight: screenSize.height),
child: new CustomOneChildLayout(
delegate: new _PopupMenuRouteLayout(position, selectedIndex),
child: new _PopupMenu(route: this)
)
);
}
}
Future showMenu({ BuildContext context, ModalPosition position, List<PopupMenuItem> items, int elevation: 8 }) {
Completer completer = new Completer();
Navigator.push(context, new _PopupMenuRoute(
/// Show a popup menu that contains the [items] at [position]. If [initialValue]
/// is specified then the first item with a matching value will be highlighted
/// and the value of [position] implies where the left, center point of the
/// highlighted item should appear. If [initialValue] is not specified then position
/// implies the menu's origin.
Future/*<T>*/ showMenu/*<T>*/({
BuildContext context,
ModalPosition position,
List<PopupMenuItem/*<T>*/> items,
dynamic/*=T*/ initialValue,
int elevation: 8
}) {
assert(context != null);
assert(items != null && items.length > 0);
Completer completer = new Completer/*<T>*/();
Navigator.push(context, new _PopupMenuRoute/*<T>*/(
completer: completer,
position: position,
items: items,
initialValue: initialValue,
elevation: elevation
));
return completer.future;
}
/// A callback that is passed the value of the PopupMenuItem that caused
/// its menu to be dismissed.
typedef void PopupMenuItemSelected<T>(T value);
/// Displays a menu when pressed and calls [onSelected] when the menu is dismissed
/// because an item was selected. The value passed to [onSelected] is the value of
/// the selected menu item. If child is null then a standard 'navigation/more_vert'
/// icon is created.
class PopupMenuButton<T> extends StatefulComponent {
PopupMenuButton({
Key key,
this.items,
this.initialValue,
this.onSelected,
this.tooltip: 'Show menu',
this.elevation: 8,
this.child
}) : super(key: key);
final List<PopupMenuItem<T>> items;
final T initialValue;
final PopupMenuItemSelected<T> onSelected;
final String tooltip;
final int elevation;
final Widget child;
_PopupMenuButtonState<T> createState() => new _PopupMenuButtonState<T>();
}
class _PopupMenuButtonState<T> extends State<PopupMenuButton<T>> {
void showButtonMenu(BuildContext context) {
final RenderBox renderBox = context.findRenderObject();
final Point topLeft = renderBox.localToGlobal(Point.origin);
showMenu/*<T>*/(
context: context,
elevation: config.elevation,
items: config.items,
initialValue: config.initialValue,
position: new ModalPosition(
left: topLeft.x,
top: topLeft.y + (config.initialValue != null ? renderBox.size.height / 2.0 : 0.0)
)
)
.then((T value) {
if (config.onSelected != null)
config.onSelected(value);
});
}
Widget build(BuildContext context) {
if (config.child == null) {
return new IconButton(
icon: 'navigation/more_vert',
tooltip: config.tooltip,
onPressed: () { showButtonMenu(context); }
);
}
return new InkWell(
onTap: () { showButtonMenu(context); },
child: config.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