Commit e502e9c8 authored by Ian Hickson's avatar Ian Hickson Committed by GitHub

ImageIcon (#4649)

Anywhere that accepted IconData now accepts either an Icon or an
ImageIcon.

Places that used to take an IconData in an `icon` argument, notably
IconButton and DrawerItem, now take a Widget in that slot. You can wrap
the value that used to be passed in in an Icon constructor to get the
same result.

Icon itself now takes the icon as a positional argument, for brevity.

ThemeData now has an iconTheme as well as a primaryIconTheme, the same
way it has had a textTheme and primaryTextTheme for a while.

IconTheme.of() always returns a value now (though that value itself may
have nulls in it). It defaults to the ThemeData.iconTheme.

IconThemeData.fallback() is a new method that returns an icon theme data
structure with all fields filled in.

IconTheme.merge() is a new constructor that takes a context and creates
a widget that mixes in the new values with the inherited values.

Most places that introduced an IconTheme widget now use IconTheme.merge.

IconThemeData.merge and IconThemeData.copyWith act in a way analogous to
the similarly-named members of TextStyle.

ImageIcon is introduced. It acts like Icon but takes an ImageProvider
instead of an IconData.

Also: Fix the analyzer to actually check the stocks app.
parent e071f0ba
......@@ -75,7 +75,7 @@ class ComplexLayoutState extends State<ComplexLayout> {
title: new Text('Advanced Layout'),
actions: <Widget>[
new IconButton(
icon: Icons.create,
icon: new Icon(Icons.create),
tooltip: 'Search',
onPressed: () {
print('Pressed search');
......@@ -162,7 +162,7 @@ class MenuItemWithIcon extends StatelessWidget {
Widget build(BuildContext context) {
return new Row(
children: <Widget>[
new Icon(icon: icon),
new Icon(icon),
new Padding(
padding: new EdgeInsets.only(left: 8.0, right: 8.0),
child: new Text(title)
......@@ -263,7 +263,10 @@ class IconWithText extends StatelessWidget {
return new Row(
mainAxisAlignment: MainAxisAlignment.collapse,
children: <Widget>[
new IconButton(icon: icon, onPressed: () { print('Pressed $title button'); } ),
new IconButton(
icon: new Icon(icon),
onPressed: () { print('Pressed $title button'); }
),
new Text(title)
]
);
......@@ -290,7 +293,7 @@ class MiniIconWithText extends StatelessWidget {
backgroundColor: Theme.of(context).primaryColor,
shape: BoxShape.circle
),
child: new Icon(icon: icon, color: Colors.white, size: 12.0)
child: new Icon(icon, color: Colors.white, size: 12.0)
)
),
new Text(title, style: Theme.of(context).textTheme.caption)
......@@ -347,7 +350,7 @@ class UserHeader extends StatelessWidget {
new Row(
children: <Widget>[
new Text('Yesterday at 11:55 • ', style: Theme.of(context).textTheme.caption),
new Icon(icon: Icons.people, size: 16.0, color: Theme.of(context).textTheme.caption.color)
new Icon(Icons.people, size: 16.0, color: Theme.of(context).textTheme.caption.color)
]
)
]
......@@ -392,8 +395,14 @@ class ItemImageBox extends StatelessWidget {
child: new Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
new IconButton(icon: Icons.edit, onPressed: () { print('Pressed edit button'); }),
new IconButton(icon: Icons.zoom_in, onPressed: () { print('Pressed zoom button'); })
new IconButton(
icon: new Icon(Icons.edit),
onPressed: () { print('Pressed edit button'); }
),
new IconButton(
icon: new Icon(Icons.zoom_in),
onPressed: () { print('Pressed zoom button'); }
),
]
)
),
......@@ -483,11 +492,11 @@ class ItemGalleryBox extends StatelessWidget {
new Row(
children: <Widget>[
new IconButton(
icon: Icons.share,
icon: new Icon(Icons.share),
onPressed: () { print('Pressed share'); }
),
new IconButton(
icon: Icons.event,
icon: new Icon(Icons.event),
onPressed: () { print('Pressed event'); }
),
new Flexible(
......@@ -555,7 +564,7 @@ class BottomBarButton extends StatelessWidget {
child: new Column(
children: <Widget>[
new IconButton(
icon: icon,
icon: new Icon(icon),
onPressed: () { print('Pressed: $title'); }
),
new Text(title, style: Theme.of(context).textTheme.caption)
......@@ -579,7 +588,7 @@ class GalleryDrawer extends StatelessWidget {
children: <Widget>[
new FancyDrawerHeader(),
new DrawerItem(
icon: Icons.brightness_5,
icon: new Icon(Icons.brightness_5),
onPressed: () { _changeTheme(context, true); },
selected: ComplexLayoutApp.of(context).lightTheme,
child: new Row(
......@@ -594,7 +603,7 @@ class GalleryDrawer extends StatelessWidget {
)
),
new DrawerItem(
icon: Icons.brightness_7,
icon: new Icon(Icons.brightness_7),
onPressed: () { _changeTheme(context, false); },
selected: !ComplexLayoutApp.of(context).lightTheme,
child: new Row(
......@@ -610,7 +619,7 @@ class GalleryDrawer extends StatelessWidget {
),
new Divider(),
new DrawerItem(
icon: Icons.hourglass_empty,
icon: new Icon(Icons.hourglass_empty),
selected: timeDilation != 1.0,
onPressed: () { ComplexLayoutApp.of(context).toggleAnimationSpeed(); },
child: new Row(
......
......@@ -147,7 +147,7 @@ class CardCollectionState extends State<CardCollection> {
buildFontRadioItem("Right-align text", TextAlign.right, _textAlign, _changeTextAlign, icon: Icons.format_align_right, enabled: !_editable),
new Divider(),
new DrawerItem(
icon: Icons.dvr,
icon: new Icon(Icons.dvr),
onPressed: () { debugDumpApp(); debugDumpRenderTree(); },
child: new Text('Dump App to Console')
),
......@@ -227,7 +227,7 @@ class CardCollectionState extends State<CardCollection> {
Widget buildDrawerColorRadioItem(String label, Map<int, Color> itemValue, Map<int, Color> currentValue, ValueChanged<Map<int, Color>> onChanged, { IconData icon, bool enabled: true }) {
return new DrawerItem(
icon: icon,
icon: new Icon(icon),
onPressed: enabled ? () { onChanged(itemValue); } : null,
child: new Row(
children: <Widget>[
......@@ -244,7 +244,7 @@ class CardCollectionState extends State<CardCollection> {
Widget buildDrawerDirectionRadioItem(String label, DismissDirection itemValue, DismissDirection currentValue, ValueChanged<DismissDirection> onChanged, { IconData icon, bool enabled: true }) {
return new DrawerItem(
icon: icon,
icon: new Icon(icon),
onPressed: enabled ? () { onChanged(itemValue); } : null,
child: new Row(
children: <Widget>[
......@@ -261,7 +261,7 @@ class CardCollectionState extends State<CardCollection> {
Widget buildFontRadioItem(String label, TextAlign itemValue, TextAlign currentValue, ValueChanged<TextAlign> onChanged, { IconData icon, bool enabled: true }) {
return new DrawerItem(
icon: icon,
icon: new Icon(icon),
onPressed: enabled ? () { onChanged(itemValue); } : null,
child: new Row(
children: <Widget>[
......@@ -351,12 +351,12 @@ class CardCollectionState extends State<CardCollection> {
}
// TODO(abarth): This icon is wrong in RTL.
Widget leftArrowIcon = new Icon(icon: Icons.arrow_back, size: 36.0);
Widget leftArrowIcon = new Icon(Icons.arrow_back, size: 36.0);
if (_dismissDirection == DismissDirection.startToEnd)
leftArrowIcon = new Opacity(opacity: 0.1, child: leftArrowIcon);
// TODO(abarth): This icon is wrong in RTL.
Widget rightArrowIcon = new Icon(icon: Icons.arrow_forward, size: 36.0);
Widget rightArrowIcon = new Icon(Icons.arrow_forward, size: 36.0);
if (_dismissDirection == DismissDirection.endToStart)
rightArrowIcon = new Opacity(opacity: 0.1, child: rightArrowIcon);
......
......@@ -153,7 +153,7 @@ class _FitnessDemoContentsState extends State<_FitnessDemoContents> {
child: new Center(
child: new Column(
children: <Widget>[
new Icon(icon: icon, size: 48.0, color: color),
new Icon(icon, size: 48.0, color: color),
new Text(value, style: new TextStyle(fontSize: 24.0, color: color)),
new Text(description, style: new TextStyle(color: color))
]
......
......@@ -87,13 +87,13 @@ class PageableListAppState extends State<PageableListApp> {
child: new Block(children: <Widget>[
new DrawerHeader(content: new Center(child: new Text('Options'))),
new DrawerItem(
icon: Icons.more_horiz,
icon: new Icon(Icons.more_horiz),
selected: scrollDirection == Axis.horizontal,
child: new Text('Horizontal Layout'),
onPressed: switchScrollDirection
),
new DrawerItem(
icon: Icons.more_vert,
icon: new Icon(Icons.more_vert),
selected: scrollDirection == Axis.vertical,
child: new Text('Vertical Layout'),
onPressed: switchScrollDirection
......
......@@ -168,14 +168,14 @@ class _ButtonsDemoState extends State<ButtonsDemo> {
mainAxisAlignment: MainAxisAlignment.collapse,
children: <Widget>[
new IconButton(
icon: Icons.thumb_up,
icon: new Icon(Icons.thumb_up),
onPressed: () {
setState(() => iconButtonToggle = !iconButtonToggle);
},
color: iconButtonToggle ? Theme.of(context).primaryColor : null
),
new IconButton(
icon: Icons.thumb_up,
icon: new Icon(Icons.thumb_up),
onPressed: null
)
]
......@@ -189,7 +189,7 @@ class _ButtonsDemoState extends State<ButtonsDemo> {
return new Align(
alignment: new FractionalOffset(0.5, 0.4),
child: new FloatingActionButton(
child: new Icon(icon: Icons.add),
child: new Icon(Icons.add),
onPressed: () {
// Perform some action
}
......
......@@ -23,7 +23,7 @@ class _ContactCategory extends StatelessWidget {
children: <Widget>[
new SizedBox(
width: 72.0,
child: new Icon(icon: icon, color: Theme.of(context).primaryColor)
child: new Icon(icon, color: Theme.of(context).primaryColor)
),
new Flexible(child: new Column(children: children))
]
......@@ -59,7 +59,7 @@ class _ContactItem extends StatelessWidget {
if (icon != null) {
rowChildren.add(new SizedBox(
width: 72.0,
child: new IconButton(icon: icon, onPressed: onPressed)
child: new IconButton(icon: new Icon(icon), onPressed: onPressed)
));
}
return new Padding(
......@@ -99,7 +99,7 @@ class ContactsDemoState extends State<ContactsDemo> {
expandedHeight: _appBarHeight,
actions: <Widget>[
new IconButton(
icon: Icons.create,
icon: new Icon(Icons.create),
tooltip: 'Edit',
onPressed: () {
_scaffoldKey.currentState.showSnackBar(new SnackBar(
......
......@@ -39,8 +39,8 @@ class DialogDemoItem extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
new Icon(
icon,
size: 36.0,
icon: icon,
color: color
),
new Padding(
......
......@@ -57,7 +57,7 @@ class DateTimeItem extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
new Text(new DateFormat('EEE, MMM d yyyy').format(date)),
new Icon(icon: Icons.arrow_drop_down, color: Colors.black54),
new Icon(Icons.arrow_drop_down, color: Colors.black54),
]
)
)
......@@ -82,7 +82,7 @@ class DateTimeItem extends StatelessWidget {
child: new Row(
children: <Widget>[
new Text('$time'),
new Icon(icon: Icons.arrow_drop_down, color: Colors.black54),
new Icon(Icons.arrow_drop_down, color: Colors.black54),
]
)
)
......@@ -146,7 +146,7 @@ class FullScreenDialogDemoState extends State<FullScreenDialogDemo> {
return new Scaffold(
appBar: new AppBar(
leading: new IconButton(
icon: Icons.clear,
icon: new Icon(Icons.clear),
onPressed: () { handleDismissButton(context); }
),
title: new Text('New event'),
......
......@@ -105,7 +105,7 @@ class GridDemoPhotoItem extends StatelessWidget {
title: new Text(photo.title),
backgroundColor: Colors.black45,
leading: new Icon(
icon: icon,
icon,
color: Colors.white
)
)
......@@ -122,7 +122,7 @@ class GridDemoPhotoItem extends StatelessWidget {
title: new Text(photo.title),
subtitle: new Text(photo.caption),
trailing: new Icon(
icon: icon,
icon,
color: Colors.white
)
)
......
......@@ -47,8 +47,8 @@ class IconsDemoState extends State<IconsDemo> {
Widget buildIconButton(double size, IconData icon, bool enabled) {
return new IconButton(
icon: new Icon(icon),
size: size,
icon: icon,
color: iconColor,
tooltip: "${enabled ? 'Enabled' : 'Disabled'} icon button",
onPressed: enabled ? handleIconButtonPress : null
......@@ -130,7 +130,7 @@ class IconsDemoState extends State<IconsDemo> {
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Icon(
icon: Icons.brightness_7,
Icons.brightness_7,
color: iconColor.withAlpha(0x33) // 0.2 * 255 = 0x33
),
new Slider(
......@@ -145,7 +145,7 @@ class IconsDemoState extends State<IconsDemo> {
}
),
new Icon(
icon: Icons.brightness_7,
Icons.brightness_7,
color: iconColor.withAlpha(0xFF)
),
]
......
......@@ -104,13 +104,13 @@ class LeaveBehindDemoState extends State<LeaveBehindDemo> {
background: new Container(
decoration: new BoxDecoration(backgroundColor: theme.primaryColor),
child: new ListItem(
leading: new Icon(icon: Icons.delete, color: Colors.white, size: 36.0)
leading: new Icon(Icons.delete, color: Colors.white, size: 36.0)
)
),
secondaryBackground: new Container(
decoration: new BoxDecoration(backgroundColor: theme.primaryColor),
child: new ListItem(
trailing: new Icon(icon: Icons.archive, color: Colors.white, size: 36.0)
trailing: new Icon(Icons.archive, color: Colors.white, size: 36.0)
)
),
child: new Container(
......
......@@ -144,7 +144,7 @@ class ListDemoState extends State<ListDemo> {
leading: _showAvatars ? new CircleAvatar(child: new Text(item)) : null,
title: new Text('This item represents $item.'),
subtitle: secondary,
trailing: _showIcons ? new Icon(icon: Icons.info, color: Theme.of(context).disabledColor) : null
trailing: _showIcons ? new Icon(Icons.info, color: Theme.of(context).disabledColor) : null
);
}
......@@ -175,7 +175,7 @@ class ListDemoState extends State<ListDemo> {
title: new Text('Scrolling list\n$itemTypeText$layoutText'),
actions: <Widget>[
new IconButton(
icon: Icons.sort_by_alpha,
icon: new Icon(Icons.sort_by_alpha),
tooltip: 'Sort',
onPressed: () {
setState(() {
......@@ -185,7 +185,7 @@ class ListDemoState extends State<ListDemo> {
}
),
new IconButton(
icon: Icons.more_vert,
icon: new Icon(Icons.more_vert),
tooltip: 'Show menu',
onPressed: () { showConfigurationSheet(context); }
)
......
......@@ -122,21 +122,21 @@ class MenuDemoState extends State<MenuDemo> {
new PopupMenuItem<String>(
value: 'Preview',
child: new ListItem(
leading: new Icon(icon: Icons.visibility),
leading: new Icon(Icons.visibility),
title: new Text('Preview')
)
),
new PopupMenuItem<String>(
value: 'Share',
child: new ListItem(
leading: new Icon(icon: Icons.person_add),
leading: new Icon(Icons.person_add),
title: new Text('Share')
)
),
new PopupMenuItem<String>(
value: 'Get Link',
child: new ListItem(
leading: new Icon(icon: Icons.link),
leading: new Icon(Icons.link),
title: new Text('Get link')
)
),
......@@ -144,7 +144,7 @@ class MenuDemoState extends State<MenuDemo> {
new PopupMenuItem<String>(
value: 'Remove',
child: new ListItem(
leading: new Icon(icon: Icons.delete),
leading: new Icon(Icons.delete),
title: new Text('Remove')
)
)
......
......@@ -74,7 +74,7 @@ class OverscrollDemoState extends State<OverscrollDemo> {
title: new Text('$indicatorTypeText'),
actions: <Widget>[
new IconButton(
icon: Icons.refresh,
icon: new Icon(Icons.refresh),
tooltip: 'Pull to refresh',
onPressed: () {
setState(() {
......@@ -83,7 +83,7 @@ class OverscrollDemoState extends State<OverscrollDemo> {
}
),
new IconButton(
icon: Icons.play_for_work,
icon: new Icon(Icons.play_for_work),
tooltip: 'Over-scroll indicator',
onPressed: () {
setState(() {
......
......@@ -39,14 +39,14 @@ class PageSelectorDemo extends StatelessWidget {
child: new Row(
children: <Widget>[
new IconButton(
icon: Icons.chevron_left,
icon: new Icon(Icons.chevron_left),
color: color,
onPressed: () { _handleArrowButtonPress(context, -1); },
tooltip: 'Page back'
),
new TabPageSelector<IconData>(),
new IconButton(
icon: Icons.chevron_right,
icon: new Icon(Icons.chevron_right),
color: color,
onPressed: () { _handleArrowButtonPress(context, 1); },
tooltip: 'Page forward'
......@@ -63,7 +63,7 @@ class PageSelectorDemo extends StatelessWidget {
padding: const EdgeInsets.all(12.0),
child: new Card(
child: new Center(
child: new Icon(icon: icon, size: 128.0, color: color)
child: new Icon(icon, size: 128.0, color: color)
)
)
);
......
......@@ -61,7 +61,7 @@ class _PersistentBottomSheetDemoState extends State<PersistentBottomSheetDemo> {
floatingActionButton: new FloatingActionButton(
onPressed: null,
backgroundColor: Colors.redAccent[200],
child: new Icon(icon: Icons.add)
child: new Icon(Icons.add)
),
body: new Center(
child: new RaisedButton(
......
......@@ -66,7 +66,7 @@ class _PestoDemoState extends State<PestoDemo> {
appBar: _buildAppBar(context),
drawer: _buildDrawer(context),
floatingActionButton: new FloatingActionButton(
child: new Icon(icon: Icons.edit),
child: new Icon(Icons.edit),
onPressed: () { }
),
body: _buildBody(context)
......@@ -80,7 +80,7 @@ class _PestoDemoState extends State<PestoDemo> {
expandedHeight: _kAppBarHeight,
actions: <Widget>[
new IconButton(
icon: Icons.search,
icon: new Icon(Icons.search),
tooltip: 'Search',
onPressed: () {
_scaffoldKey.currentState.showSnackBar(new SnackBar(
......@@ -305,7 +305,7 @@ class _RecipePageState extends State<_RecipePage> {
backgroundColor: Colors.transparent,
elevation: 0,
leading: new IconButton(
icon: Icons.arrow_back,
icon: new Icon(Icons.arrow_back),
onPressed: () => Navigator.pop(context),
tooltip: 'Back'
),
......@@ -364,7 +364,7 @@ class _RecipePageState extends State<_RecipePage> {
new Positioned(
right: 16.0,
child: new FloatingActionButton(
child: new Icon(icon: isFavorite ? Icons.favorite : Icons.favorite_border),
child: new Icon(isFavorite ? Icons.favorite : Icons.favorite_border),
onPressed: _toggleFavorite
)
)
......@@ -384,7 +384,7 @@ class _RecipePageState extends State<_RecipePage> {
children: <Widget>[
new Padding(
padding: const EdgeInsets.only(right: 24.0),
child: new Icon(icon: icon, color: Colors.black54)
child: new Icon(icon, color: Colors.black54)
),
new Text(label, style: menuItemStyle)
]
......
......@@ -78,9 +78,9 @@ class ScrollableTabsDemoState extends State<ScrollableTabsDemo> {
value: (IconData icon) {
switch(_demoStyle) {
case TabsDemoStyle.iconsAndText:
return new TabLabel(text: labels[icon], icon: icon);
return new TabLabel(text: labels[icon], icon: new Icon(icon));
case TabsDemoStyle.iconsOnly:
return new TabLabel(icon: icon);
return new TabLabel(icon: new Icon(icon));
case TabsDemoStyle.textOnly:
return new TabLabel(text: labels[icon]);
}
......@@ -96,7 +96,7 @@ class ScrollableTabsDemoState extends State<ScrollableTabsDemo> {
child:new Card(
child: new Center(
child: new Icon(
icon: icon,
icon,
color: iconColor,
size: 128.0
)
......
......@@ -55,7 +55,7 @@ class OrderItem extends StatelessWidget {
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: new Center(
child: new Icon(
icon: Icons.info_outline,
Icons.info_outline,
size: 24.0,
color: const Color(0xFFFFE0E0)
)
......@@ -175,7 +175,7 @@ class _OrderPageState extends State<OrderPage> {
},
backgroundColor: const Color(0xFF16F0F0),
child: new Icon(
icon: Icons.add_shopping_cart,
Icons.add_shopping_cart,
color: Colors.black
)
),
......
......@@ -100,7 +100,7 @@ class ShrinePageState extends State<ShrinePage> {
),
actions: <Widget>[
new IconButton(
icon: Icons.shopping_cart,
icon: new Icon(Icons.shopping_cart),
tooltip: 'Shopping cart',
onPressed: () {
_showShoppingCart();
......
......@@ -15,7 +15,7 @@ class _Page {
Color get labelColor => colors != null ? colors[300] : Colors.grey[300];
bool get fabDefined => colors != null && icon != null;
Color get fabColor => colors[400];
Icon get fabIcon => new Icon(icon: icon);
Icon get fabIcon => new Icon(icon);
Key get fabKey => new ValueKey<Color>(fabColor);
}
......
......@@ -31,8 +31,8 @@ class TooltipDemo extends StatelessWidget {
new Tooltip(
message: 'call icon',
child: new Icon(
Icons.call,
size: 18.0,
icon: Icons.call,
color: theme.primaryColor
)
),
......@@ -42,7 +42,7 @@ class TooltipDemo extends StatelessWidget {
new Center(
child: new IconButton(
size: 48.0,
icon: Icons.call,
icon: new Icon(Icons.call),
color: theme.primaryColor,
tooltip: 'Place a phone call',
onPressed: () {
......
......@@ -158,7 +158,7 @@ class DemoBottomBar extends StatelessWidget {
children: <Widget>[
new Padding(
padding: new EdgeInsets.only(right: 8.0),
child: new Icon(icon: Icons.code)
child: new Icon(Icons.code)
),
new Text('VIEW CODE')
]
......@@ -170,7 +170,7 @@ class DemoBottomBar extends StatelessWidget {
children: <Widget>[
new Padding(
padding: new EdgeInsets.only(right: 8.0),
child: new Icon(icon: Icons.star)
child: new Icon(Icons.star)
),
new Text('LIVE DEMO')
]
......@@ -267,7 +267,7 @@ class FullScreenCodeDialogState extends State<FullScreenCodeDialog> {
return new Scaffold(
appBar: new AppBar(
leading: new IconButton(
icon: Icons.clear,
icon: new Icon(Icons.clear),
onPressed: () { Navigator.pop(context); }
),
title: new Text('Example code')
......
......@@ -36,7 +36,7 @@ class GalleryDrawer extends StatelessWidget {
content: new Center(child: new Text('Flutter gallery'))
),
new DrawerItem(
icon: Icons.brightness_5,
icon: new Icon(Icons.brightness_5),
onPressed: () { onThemeChanged(true); },
selected: useLightTheme,
child: new Row(
......@@ -51,7 +51,7 @@ class GalleryDrawer extends StatelessWidget {
)
),
new DrawerItem(
icon: Icons.brightness_7,
icon: new Icon(Icons.brightness_7),
onPressed: () { onThemeChanged(false); },
selected: useLightTheme,
child: new Row(
......@@ -67,7 +67,7 @@ class GalleryDrawer extends StatelessWidget {
),
new Divider(),
new DrawerItem(
icon: Icons.hourglass_empty,
icon: new Icon(Icons.hourglass_empty),
selected: timeDilation != 1.0,
onPressed: () { onTimeDilationChanged(timeDilation != 1.0 ? 1.0 : 20.0); },
child: new Row(
......@@ -81,7 +81,7 @@ class GalleryDrawer extends StatelessWidget {
)
),
new DrawerItem(
icon: Icons.assessment,
icon: new Icon(Icons.assessment),
onPressed: () { onShowPerformanceOverlayChanged(!showPerformanceOverlay); },
selected: showPerformanceOverlay,
child: new Row(
......
......@@ -83,7 +83,7 @@ bool value;
// Toggleable icon button.
new IconButton(
icon: Icons.thumb_up,
icon: new Icon(Icons.thumb_up),
onPressed: () {
setState(() => value = !value);
},
......@@ -99,7 +99,7 @@ new Scaffold(
title: new Text('Demo')
),
floatingActionButton: new FloatingActionButton(
child: new Icon(icon: Icons.add),
child: new Icon(Icons.add),
onPressed: null
)
);
......
......@@ -80,17 +80,17 @@ class GalleryHomeState extends State<GalleryHome> {
type: MaterialListType.oneLine,
children: <Widget>[
new TwoLevelSublist(
leading: new Icon(icon: Icons.star),
leading: new Icon(Icons.star),
title: new Text('Demos'),
children: _demoItems
),
new TwoLevelSublist(
leading: new Icon(icon: Icons.extension),
leading: new Icon(Icons.extension),
title: new Text('Components'),
children: _componentItems
),
new TwoLevelSublist(
leading: new Icon(icon: Icons.color_lens),
leading: new Icon(Icons.color_lens),
title: new Text('Style'),
children: _styleItems
)
......
......@@ -52,7 +52,7 @@ class AdaptedGridItem extends StatelessWidget {
child: new Text(name)
),
new IconButton(
icon: Icons.more_vert,
icon: new Icon(Icons.more_vert),
onPressed: null
)
]
......
......@@ -26,10 +26,11 @@ class _NotImplementedDialog extends StatelessWidget {
content: new Text('This feature has not yet been implemented.'),
actions: <Widget>[
new FlatButton(
onPressed: () { debugDumpApp(); },
child: new Row(
children: <Widget>[
new Icon(
icon: Icons.dvr,
Icons.dvr,
size: 18.0
),
new Container(
......@@ -37,14 +38,13 @@ class _NotImplementedDialog extends StatelessWidget {
),
new Text('DUMP APP TO CONSOLE'),
]
),
onPressed: () { debugDumpApp(); }
)
),
new FlatButton(
child: new Text('OH WELL'),
onPressed: () {
Navigator.pop(context, false);
}
},
child: new Text('OH WELL')
)
]
);
......@@ -126,12 +126,12 @@ class StockHomeState extends State<StockHome> {
child: new Block(children: <Widget>[
new DrawerHeader(content: new Center(child: new Text('Stocks'))),
new DrawerItem(
icon: Icons.assessment,
icon: new Icon(Icons.assessment),
selected: true,
child: new Text('Stock List')
),
new DrawerItem(
icon: Icons.account_balance,
icon: new Icon(Icons.account_balance),
onPressed: () {
showDialog(
context: context,
......@@ -158,7 +158,7 @@ class StockHomeState extends State<StockHome> {
child: new Text('Account Balance')
),
new DrawerItem(
icon: Icons.dvr,
icon: new Icon(Icons.dvr),
onPressed: () {
try {
debugDumpApp();
......@@ -173,7 +173,7 @@ class StockHomeState extends State<StockHome> {
),
new Divider(),
new DrawerItem(
icon: Icons.thumb_up,
icon: new Icon(Icons.thumb_up),
onPressed: () => _handleStockModeChange(StockMode.optimistic),
child: new Row(
children: <Widget>[
......@@ -183,7 +183,7 @@ class StockHomeState extends State<StockHome> {
)
),
new DrawerItem(
icon: Icons.thumb_down,
icon: new Icon(Icons.thumb_down),
onPressed: () => _handleStockModeChange(StockMode.pessimistic),
child: new Row(
children: <Widget>[
......@@ -194,11 +194,11 @@ class StockHomeState extends State<StockHome> {
),
new Divider(),
new DrawerItem(
icon: Icons.settings,
icon: new Icon(Icons.settings),
onPressed: _handleShowSettings,
child: new Text('Settings')),
new DrawerItem(
icon: Icons.help,
icon: new Icon(Icons.help),
child: new Text('Help & Feedback'))
])
);
......@@ -214,7 +214,7 @@ class StockHomeState extends State<StockHome> {
title: new Text(StockStrings.of(context).title()),
actions: <Widget>[
new IconButton(
icon: Icons.search,
icon: new Icon(Icons.search),
onPressed: _handleSearchBegin,
tooltip: 'Search'
),
......@@ -307,7 +307,7 @@ class StockHomeState extends State<StockHome> {
Widget buildSearchBar() {
return new AppBar(
leading: new IconButton(
icon: Icons.arrow_back,
icon: new Icon(Icons.arrow_back),
color: Theme.of(context).accentColor,
onPressed: _handleSearchEnd,
tooltip: 'Back'
......@@ -332,7 +332,7 @@ class StockHomeState extends State<StockHome> {
Widget buildFloatingActionButton() {
return new FloatingActionButton(
tooltip: 'Create company',
child: new Icon(icon: Icons.add),
child: new Icon(Icons.add),
backgroundColor: Colors.redAccent[200],
onPressed: _handleCreateCompany
);
......
......@@ -104,7 +104,7 @@ class StockSettingsState extends State<StockSettings> {
Widget buildSettingsPane(BuildContext context) {
List<Widget> rows = <Widget>[
new DrawerItem(
icon: Icons.thumb_up,
icon: new Icon(Icons.thumb_up),
onPressed: () => _confirmOptimismChange(),
child: new Row(
children: <Widget>[
......@@ -117,7 +117,7 @@ class StockSettingsState extends State<StockSettings> {
)
),
new DrawerItem(
icon: Icons.backup,
icon: new Icon(Icons.backup),
onPressed: () { _handleBackupChanged(!(config.configuration.backupMode == BackupMode.enabled)); },
child: new Row(
children: <Widget>[
......@@ -130,7 +130,7 @@ class StockSettingsState extends State<StockSettings> {
)
),
new DrawerItem(
icon: Icons.picture_in_picture,
icon: new Icon(Icons.picture_in_picture),
onPressed: () { _handleShowPerformanceOverlayChanged(!config.configuration.showPerformanceOverlay); },
child: new Row(
children: <Widget>[
......@@ -143,7 +143,7 @@ class StockSettingsState extends State<StockSettings> {
)
),
new DrawerItem(
icon: Icons.accessibility,
icon: new Icon(Icons.accessibility),
onPressed: () { _handleShowSemanticsDebuggerChanged(!config.configuration.showSemanticsDebugger); },
child: new Row(
children: <Widget>[
......@@ -158,9 +158,9 @@ class StockSettingsState extends State<StockSettings> {
];
assert(() {
// material grid and size construction lines are only available in checked mode
rows.addAll([
rows.addAll(<Widget>[
new DrawerItem(
icon: Icons.border_clear,
icon: new Icon(Icons.border_clear),
onPressed: () { _handleShowGridChanged(!config.configuration.debugShowGrid); },
child: new Row(
children: <Widget>[
......@@ -173,7 +173,7 @@ class StockSettingsState extends State<StockSettings> {
)
),
new DrawerItem(
icon: Icons.border_all,
icon: new Icon(Icons.border_all),
onPressed: () { _handleShowSizesChanged(!config.configuration.debugShowSizes); },
child: new Row(
children: <Widget>[
......@@ -186,7 +186,7 @@ class StockSettingsState extends State<StockSettings> {
)
),
new DrawerItem(
icon: Icons.format_color_text,
icon: new Icon(Icons.format_color_text),
onPressed: () { _handleShowBaselinesChanged(!config.configuration.debugShowBaselines); },
child: new Row(
children: <Widget>[
......@@ -199,7 +199,7 @@ class StockSettingsState extends State<StockSettings> {
)
),
new DrawerItem(
icon: Icons.filter_none,
icon: new Icon(Icons.filter_none),
onPressed: () { _handleShowLayersChanged(!config.configuration.debugShowLayers); },
child: new Row(
children: <Widget>[
......@@ -212,7 +212,7 @@ class StockSettingsState extends State<StockSettings> {
)
),
new DrawerItem(
icon: Icons.mouse,
icon: new Icon(Icons.mouse),
onPressed: () { _handleShowPointersChanged(!config.configuration.debugShowPointers); },
child: new Row(
children: <Widget>[
......@@ -225,7 +225,7 @@ class StockSettingsState extends State<StockSettings> {
)
),
new DrawerItem(
icon: Icons.gradient,
icon: new Icon(Icons.gradient),
onPressed: () { _handleShowRainbowChanged(!config.configuration.debugShowRainbow); },
child: new Row(
children: <Widget>[
......
......@@ -42,6 +42,7 @@ export 'src/material/icon_button.dart';
export 'src/material/icon_theme.dart';
export 'src/material/icon_theme_data.dart';
export 'src/material/icons.dart';
export 'src/material/image_icon.dart';
export 'src/material/ink_well.dart';
export 'src/material/input.dart';
export 'src/material/list.dart';
......
......@@ -46,6 +46,10 @@ abstract class AppBarBottomWidget extends Widget {
/// AppBar's [collapsedHeight] and [bottomHeight] define how small the app bar
/// will become when the application is scrolled.
///
/// By default, icons within an app bar will use the
/// [ThemeData.primaryIconTheme]. This can be overridden by nesting an
/// [IconTheme] inside the [AppBar].
///
/// See also:
///
/// * [Scaffold]
......@@ -126,6 +130,10 @@ class AppBar extends StatelessWidget {
/// tandem with [backgroundColor].
///
/// Defaults to [ThemeData.brightness].
///
/// Icons within the app bar will continue to use
/// [ThemeData.primaryIconTheme]. If this clashes with the brightness
/// specified here, consider using an [IconTheme] inside the [AppBar].
final Brightness brightness;
/// The typographic style to use for text in the app bar.
......@@ -207,7 +215,7 @@ class AppBar extends StatelessWidget {
final double statusBarHeight = MediaQuery.of(context).padding.top;
final ThemeData theme = Theme.of(context);
IconThemeData iconTheme = IconTheme.of(context) ?? theme.primaryIconTheme;
IconThemeData iconTheme = theme.primaryIconTheme;
TextStyle centerStyle = textTheme?.title ?? theme.primaryTextTheme.title;
TextStyle sideStyle = textTheme?.body1 ?? theme.primaryTextTheme.body1;
......@@ -223,13 +231,9 @@ class AppBar extends StatelessWidget {
centerStyle = centerStyle.copyWith(color: centerStyle.color.withOpacity(opacity));
if (sideStyle?.color != null)
sideStyle = sideStyle.copyWith(color: sideStyle.color.withOpacity(opacity));
if (iconTheme != null) {
iconTheme = new IconThemeData(
opacity: opacity * iconTheme.opacity,
color: iconTheme.color
);
}
iconTheme = iconTheme.copyWith(
opacity: opacity * (iconTheme.opacity ?? 1.0)
);
}
final List<Widget> toolBarRow = <Widget>[];
......@@ -256,7 +260,8 @@ class AppBar extends StatelessWidget {
Widget appBar = new SizedBox(
height: kToolBarHeight,
child: new IconTheme(
child: new IconTheme.merge(
context: context,
data: iconTheme,
child: new DefaultTextStyle(
style: sideStyle,
......
......@@ -245,7 +245,8 @@ class _MaterialButtonState extends State<MaterialButton> {
final ButtonTheme buttonTheme = ButtonTheme.of(context);
final double height = config.height ?? buttonTheme.height;
final int elevation = (_highlight ? config.highlightElevation : config.elevation) ?? 0;
Widget contents = new IconTheme(
Widget contents = new IconTheme.merge(
context: context,
data: new IconThemeData(
color: textColor
),
......
......@@ -96,7 +96,7 @@ class Chip extends StatelessWidget {
child: new Container(
padding: const EdgeInsets.symmetric(horizontal: 4.0),
child: new Icon(
icon: Icons.cancel,
Icons.cancel,
size: 18.0,
color: Colors.black54
)
......
......@@ -436,6 +436,7 @@ class DataTable extends StatelessWidget {
}
Widget _buildDataCell({
BuildContext context,
EdgeInsets padding,
Widget label,
bool numeric,
......@@ -445,7 +446,7 @@ class DataTable extends StatelessWidget {
VoidCallback onSelectChanged
}) {
if (showEditIcon) {
final Widget icon = new Icon(icon: Icons.edit, size: 18.0);
final Widget icon = new Icon(Icons.edit, size: 18.0);
label = new Flexible(child: label);
label = new Row(children: numeric ? <Widget>[ icon, label ] : <Widget>[ label, icon ]);
}
......@@ -460,7 +461,8 @@ class DataTable extends StatelessWidget {
fontSize: 13.0,
color: placeholder ? Colors.black38 : Colors.black87 // TODO(ianh): defer to theme, since this won't work in e.g. the dark theme
),
child: new IconTheme(
child: new IconTheme.merge(
context: context,
data: new IconThemeData(
color: Colors.black54
),
......@@ -561,6 +563,7 @@ class DataTable extends StatelessWidget {
for (DataRow row in rows) {
DataCell cell = row.cells[dataColumnIndex];
tableRows[rowIndex].children[displayColumnIndex] = _buildDataCell(
context: context,
padding: padding,
label: cell.widget,
numeric: column.numeric,
......@@ -765,7 +768,7 @@ class _SortArrowState extends State<_SortArrow> {
..setTranslationRaw(0.0, _kArrowIconBaselineOffset, 0.0),
alignment: FractionalOffset.center,
child: new Icon(
icon: Icons.arrow_downward,
Icons.arrow_downward,
size: _kArrowIconSize,
color: Colors.black87
)
......
......@@ -12,8 +12,9 @@ import 'package:meta/meta.dart';
import 'colors.dart';
import 'debug.dart';
import 'icons.dart';
import 'icon.dart';
import 'icon_button.dart';
import 'icons.dart';
import 'ink_well.dart';
import 'theme.dart';
import 'typography.dart';
......@@ -471,7 +472,7 @@ class _MonthPickerState extends State<MonthPicker> {
top: 0.0,
left: 8.0,
child: new IconButton(
icon: Icons.chevron_left,
icon: new Icon(Icons.chevron_left),
tooltip: 'Previous month',
onPressed: _handlePreviousMonth
)
......@@ -480,7 +481,7 @@ class _MonthPickerState extends State<MonthPicker> {
top: 0.0,
right: 8.0,
child: new IconButton(
icon: Icons.chevron_right,
icon: new Icon(Icons.chevron_right),
tooltip: 'Next month',
onPressed: _handleNextMonth
)
......
......@@ -8,7 +8,9 @@ import 'colors.dart';
import 'constants.dart';
import 'debug.dart';
import 'icon.dart';
import 'icons.dart';
import 'icon_theme.dart';
import 'icon_theme_data.dart';
import 'image_icon.dart';
import 'ink_well.dart';
import 'theme.dart';
......@@ -36,7 +38,13 @@ class DrawerItem extends StatelessWidget {
}) : super(key: key);
/// The icon to display before the child widget.
final IconData icon;
///
/// The size and color of the icon is configured automatically using an
/// [IconTheme] and therefore do not need to be explicitly given in the
/// icon widget.
///
/// See [Icon], [ImageIcon].
final Widget icon;
/// The widget below this widget in the tree.
final Widget child;
......@@ -94,9 +102,13 @@ class DrawerItem extends StatelessWidget {
children.add(
new Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: new Icon(
icon: icon,
color: _getIconColor(themeData)
child: new IconTheme.merge(
context: context,
data: new IconThemeData(
color: _getIconColor(themeData),
size: 24.0
),
child: icon
)
)
);
......
......@@ -495,7 +495,7 @@ class _DropDownButtonState<T> extends State<DropDownButton<T>> {
alignment: FractionalOffset.centerLeft,
children: config.items
),
new Icon(icon: Icons.arrow_drop_down, size: config.iconSize)
new Icon(Icons.arrow_drop_down, size: config.iconSize)
]
)
);
......
......@@ -104,13 +104,14 @@ class _FloatingActionButtonState extends State<FloatingActionButton> {
Color iconColor = Colors.white;
Color materialColor = config.backgroundColor;
if (materialColor == null) {
ThemeData themeData = Theme.of(context);
final ThemeData themeData = Theme.of(context);
materialColor = themeData.accentColor;
iconColor = themeData.accentColorBrightness == Brightness.dark ? Colors.white : Colors.black;
}
Widget result = new Center(
child: new IconTheme(
child: new IconTheme.merge(
context: context,
data: new IconThemeData(color: iconColor),
child: config.child
)
......
......@@ -118,7 +118,8 @@ class GridTileBar extends StatelessWidget {
return new Container(
padding: padding,
decoration: decoration,
child: new IconTheme(
child: new IconTheme.merge(
context: context,
data: new IconThemeData(color: Colors.white),
child: new Row(
crossAxisAlignment: CrossAxisAlignment.center,
......
......@@ -4,10 +4,10 @@
import 'package:flutter/widgets.dart';
import 'colors.dart';
import 'icons.dart';
import 'icon_button.dart';
import 'icon_theme.dart';
import 'icon_theme_data.dart';
import 'theme.dart';
/// A material design icon.
......@@ -29,20 +29,21 @@ import 'theme.dart';
/// * [IconButton], for interactive icons.
/// * [Icons], for the list of available icons for use with this class.
/// * [IconTheme], which provides ambient configuration for icons.
/// * [ImageIcon], for showing icons from [AssetImage]s or other [ImageProvider]s.
class Icon extends StatelessWidget {
/// Creates an icon.
///
/// The [size] and [color] default to the value given by the current [IconTheme].
const Icon({
const Icon(this.icon, {
Key key,
this.icon,
this.size,
this.color
}) : super(key: key);
/// The icon to display. The available icons are described in [Icons].
///
/// If null, no icon is shown.
/// The icon can be null, in which case the widget will render as an empty
/// space of the specified [size].
final IconData icon;
/// The size of the icon in logical pixels.
......@@ -66,30 +67,17 @@ class Icon extends StatelessWidget {
/// [IconTheme], if any.
final Color color;
Color _getDefaultColorForBrightness(Brightness brightness) {
switch (brightness) {
case Brightness.dark:
return Colors.white;
case Brightness.light:
return Colors.black;
}
assert(brightness != null);
return null;
}
Color _getDefaultColor(BuildContext context) {
return IconTheme.of(context)?.color ?? _getDefaultColorForBrightness(Theme.of(context).brightness);
}
@override
Widget build(BuildContext context) {
final double iconSize = size ?? IconTheme.of(context)?.size ?? 24.0;
final IconThemeData iconTheme = IconTheme.of(context).fallback();
final double iconSize = size ?? iconTheme.size;
if (icon == null)
return new SizedBox(width: iconSize, height: iconSize);
final double iconOpacity = IconTheme.of(context)?.opacity ?? 1.0;
Color iconColor = color ?? _getDefaultColor(context);
final double iconOpacity = iconTheme.opacity;
Color iconColor = color ?? iconTheme.color;
if (iconOpacity != 1.0)
iconColor = iconColor.withOpacity(iconColor.opacity * iconOpacity);
......
......@@ -7,6 +7,8 @@ import 'package:meta/meta.dart';
import 'debug.dart';
import 'icon.dart';
import 'icon_theme.dart';
import 'icon_theme_data.dart';
import 'icons.dart';
import 'ink_well.dart';
import 'theme.dart';
......@@ -40,8 +42,8 @@ class IconButton extends StatelessWidget {
/// The [size], [padding], and [alignment] arguments must not be null (though
/// they each have default values).
///
/// The [icon] argument must be specified. See [Icons] for a list of icons to
/// use for this argument.
/// The [icon] argument must be specified, and is typically either an [Icon]
/// or an [ImageIcon].
const IconButton({
Key key,
this.size: 24.0,
......@@ -70,13 +72,19 @@ class IconButton extends StatelessWidget {
/// This property must not be null. It defaults to [FractionalOffset.center].
final FractionalOffset alignment;
/// The icon to display inside the button, from the list in [Icons].
/// The icon to display inside the button.
///
/// The size and color of the icon is configured automatically using an
/// [IconTheme] and therefore does not need to be explicitly given in the
/// icon widget.
///
/// This property must not be null.
final IconData icon;
///
/// See [Icon], [ImageIcon].
final Widget icon;
/// The color to use for the icon inside the button, if the icon is enabled.
/// Defaults to the current color, as defined by [Icon.color].
/// Defaults to leaving this up to the [icon] widget.
///
/// The icon is enabled if [onPressed] is not null.
///
......@@ -117,10 +125,13 @@ class IconButton extends StatelessWidget {
maxHeight: size,
child: new Align(
alignment: alignment,
child: new Icon(
size: size,
icon: icon,
color: currentColor
child: new IconTheme.merge(
context: context,
data: new IconThemeData(
size: size,
color: currentColor
),
child: icon
)
)
)
......
......@@ -6,8 +6,11 @@ import 'package:flutter/widgets.dart';
import 'package:meta/meta.dart';
import 'icon_theme_data.dart';
import 'theme.dart';
/// Controls the default color, opacity, and size of icons in a widget subtree.
///
/// The icon theme is honored by [Icon] and [ImageIcon] widgets.
class IconTheme extends InheritedWidget {
/// Creates an icon theme that controls the color, opacity, and size of
/// descendant widgets.
......@@ -16,19 +19,39 @@ class IconTheme extends InheritedWidget {
IconTheme({
Key key,
@required this.data,
Widget child
@required Widget child
}) : super(key: key, child: child) {
assert(data != null);
assert(child != null);
}
/// Creates an icon theme that controls the color, opacity, and size of
/// descendant widgets, and merges in the current icon theme, if any.
///
/// The [context], [data], and [child] arguments must not be null.
factory IconTheme.merge({
Key key,
@required BuildContext context,
@required IconThemeData data,
@required Widget child
}) {
return new IconTheme(
key: key,
data: IconTheme.of(context).merge(data),
child: child
);
}
/// The color, opacity, and size to use for icons in this subtree.
final IconThemeData data;
/// The data from the closest instance of this class that encloses the given context.
/// The data from the closest instance of this class that encloses the given
/// context.
///
/// Defaults to the current [ThemeData.iconTheme].
static IconThemeData of(BuildContext context) {
IconTheme result = context.inheritFromWidgetOfExactType(IconTheme);
return result?.data;
return result?.data ?? Theme.of(context).iconTheme;
}
@override
......
......@@ -9,18 +9,61 @@ import 'dart:ui' show Color, hashValues;
///
/// Used by [IconTheme] to control the color, opacity, and size of icons in a
/// widget subtree.
///
/// To obtain the current icon theme, use [IconTheme.of]. To convert an icon
/// theme to a version with all the fields filled in, use [fallback].
class IconThemeData {
/// Creates an icon theme data.
///
/// The opacity applies to both explicit and default icon colors. The value
/// is clamped between 0.0 and 1.0.
const IconThemeData({ this.color, double opacity: 1.0, this.size }) : _opacity = opacity;
const IconThemeData({ this.color, double opacity, this.size }) : _opacity = opacity;
/// Creates a copy of this icon theme but with the given fields replaced with
/// the new values.
IconThemeData copyWith({ Color color, double opacity, double size }) {
return new IconThemeData(
color: color ?? this.color,
opacity: opacity ?? this.opacity,
size: size ?? this.size
);
}
/// Returns a new icon theme that matches this icon theme but with some values
/// replaced by the non-null parameters of the given icon theme. If the given
/// icon theme is null, simply returns this icon theme.
IconThemeData merge(IconThemeData other) {
if (other == null)
return this;
return copyWith(
color: other.color,
opacity: other.opacity,
size: other.size
);
}
/// Creates an icon theme that is identical to this icon theme but with
/// any null fields filled in. Specific fallbacks can be given, but in their
/// absence, this method defaults to black, fully opaque, and size 24.0.
IconThemeData fallback({
Color color: const Color(0xFF000000),
double opacity: 1.0,
double size: 24.0
}) {
if (this.color != null && this.opacity != null && this.size != null)
return this;
return new IconThemeData(
color: this.color ?? color,
opacity: this.opacity ?? opacity,
size: this.size ?? size
);
}
/// The default color for icons.
final Color color;
/// An opacity to apply to both explicit and default icon colors.
double get opacity => (_opacity ?? 1.0).clamp(0.0, 1.0);
double get opacity => _opacity?.clamp(0.0, 1.0);
final double _opacity;
/// The default size for icons.
......@@ -53,8 +96,8 @@ class IconThemeData {
List<String> result = <String>[];
if (color != null)
result.add('color: $color');
if (opacity != 1.0)
result.add('opacity: $opacity');
if (_opacity != null)
result.add('opacity: $_opacity');
if (size != null)
result.add('size: $size');
if (result.length == 0)
......
// 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:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'icons.dart';
import 'icon_button.dart';
import 'icon_theme.dart';
import 'icon_theme_data.dart';
/// An icon that comes from an [ImageProvider], e.g. an [AssetImage].
///
/// See also:
///
/// * [IconButton], for interactive icons.
/// * [IconTheme], which provides ambient configuration for icons.
/// * [Icon] and [Icons], for icons from the material design library.
class ImageIcon extends StatelessWidget {
/// Creates an image icon.
///
/// The [size] and [color] default to the value given by the current [IconTheme].
const ImageIcon(this.image, {
Key key,
this.size,
this.color
}) : super(key: key);
/// The image to display as the icon.
///
/// The icon can be null, in which case the widget will render as an empty
/// space of the specified [size].
final ImageProvider image;
/// The size of the icon in logical pixels.
///
/// Icons occupy a square with width and height equal to size.
///
/// Defaults to the current [IconTheme] size, if any. If there is no
/// [IconTheme], or it does not specify an explicit size, then it defaults to
/// 24.0.
final double size;
/// The color to use when drawing the icon.
///
/// Defaults to the current [IconTheme] color, if any. If there is
/// no [IconTheme], then it defaults to not recolorizing the image.
///
/// The image will additionally be adjusted by the opacity of the current
/// [IconTheme], if any.
final Color color;
@override
Widget build(BuildContext context) {
final IconThemeData iconTheme = IconTheme.of(context).fallback();
final double iconSize = size ?? iconTheme.size;
if (image == null)
return new SizedBox(width: iconSize, height: iconSize);
final double iconOpacity = iconTheme.opacity;
final Color iconColor = color ?? iconTheme.color;
Widget result = new Image(
image: image,
width: iconSize,
height: iconSize,
color: iconColor,
fit: ImageFit.scaleDown,
alignment: FractionalOffset.center
);
if (iconOpacity != 1.0) {
result = new Opacity(
opacity: iconOpacity,
child: result
);
}
return result;
}
@override
void debugFillDescription(List<String> description) {
super.debugFillDescription(description);
if (image != null) {
description.add('$image');
} else {
description.add('<empty>');
}
if (size != null)
description.add('size: $size');
if (color != null)
description.add('color: $color');
}
}
......@@ -8,7 +8,8 @@ import 'package:flutter/widgets.dart';
import 'colors.dart';
import 'debug.dart';
import 'icon.dart';
import 'icons.dart';
import 'icon_theme.dart';
import 'icon_theme_data.dart';
import 'material.dart';
import 'text_selection.dart';
import 'theme.dart';
......@@ -50,7 +51,13 @@ class Input extends StatefulWidget {
final KeyboardType keyboardType;
/// An icon to show adjacent to the input field.
final IconData icon;
///
/// The size and color of the icon is configured automatically using an
/// [IconTheme] and therefore does not need to be explicitly given in the
/// icon widget.
///
/// See [Icon], [ImageIcon].
final Widget icon;
/// Text to show above the input field.
final String labelText;
......@@ -224,10 +231,13 @@ class _InputState extends State<Input> {
new Container(
margin: new EdgeInsets.only(right: 16.0, top: iconTop),
width: config.isDense ? 40.0 : 48.0,
child: new Icon(
icon: config.icon,
color: focused ? activeColor : Colors.black45,
size: config.isDense ? 18.0 : 24.0
child: new IconTheme.merge(
context: context,
data: new IconThemeData(
color: focused ? activeColor : Colors.black45,
size: config.isDense ? 18.0 : 24.0
),
child: config.icon
)
),
new Flexible(child: child)
......
......@@ -14,6 +14,7 @@ import 'card.dart';
import 'data_table.dart';
import 'data_table_source.dart';
import 'drop_down.dart';
import 'icon.dart';
import 'icon_button.dart';
import 'icon_theme.dart';
import 'icon_theme_data.dart';
......@@ -333,16 +334,16 @@ class PaginatedDataTableState extends State<PaginatedDataTable> {
),
new Container(width: 32.0),
new IconButton(
icon: new Icon(Icons.chevron_left),
padding: EdgeInsets.zero,
icon: Icons.chevron_left,
onPressed: _firstRowIndex <= 0 ? null : () {
pageTo(math.max(_firstRowIndex - config.rowsPerPage, 0));
}
),
new Container(width: 24.0),
new IconButton(
icon: new Icon(Icons.chevron_right),
padding: EdgeInsets.zero,
icon: Icons.chevron_right,
onPressed: (!_rowCountApproximate && (_firstRowIndex + config.rowsPerPage >= _rowCount)) ? null : () {
pageTo(_firstRowIndex + config.rowsPerPage);
}
......@@ -360,7 +361,8 @@ class PaginatedDataTableState extends State<PaginatedDataTable> {
// See https://www.google.com/design/spec/components/data-tables.html#data-tables-tables-within-cards
style: _selectedRowCount > 0 ? themeData.textTheme.subhead.copyWith(color: themeData.accentColor)
: themeData.textTheme.title.copyWith(fontWeight: FontWeight.w400),
child: new IconTheme(
child: new IconTheme.merge(
context: context,
data: new IconThemeData(
opacity: 0.54
),
......@@ -395,7 +397,8 @@ class PaginatedDataTableState extends State<PaginatedDataTable> {
),
new DefaultTextStyle(
style: footerTextStyle,
child: new IconTheme(
child: new IconTheme.merge(
context: context,
data: new IconThemeData(
opacity: 0.54
),
......
......@@ -160,7 +160,8 @@ class _PopupMenuItemState<T extends PopupMenuItem<dynamic>> extends State<T> {
);
if (!config.enabled) {
final bool isDark = theme.brightness == Brightness.dark;
item = new IconTheme(
item = new IconTheme.merge(
context: context,
data: new IconThemeData(opacity: isDark ? 0.5 : 0.38),
child: item
);
......@@ -244,7 +245,7 @@ class _CheckedPopupMenuItemState<T> extends _PopupMenuItemState<CheckedPopupMenu
enabled: config.enabled,
leading: new FadeTransition(
opacity: _opacity,
child: new Icon(icon: _controller.isDismissed ? null : Icons.done)
child: new Icon(_controller.isDismissed ? null : Icons.done)
),
title: config.child
);
......@@ -526,7 +527,7 @@ class _PopupMenuButtonState<T> extends State<PopupMenuButton<T>> {
Widget build(BuildContext context) {
if (config.child == null) {
return new IconButton(
icon: Icons.more_vert,
icon: new Icon(Icons.more_vert),
padding: config.padding,
tooltip: config.tooltip,
onPressed: () { showButtonMenu(context); }
......
......@@ -11,8 +11,9 @@ import 'package:flutter/widgets.dart';
import 'app_bar.dart';
import 'bottom_sheet.dart';
import 'drawer.dart';
import 'icons.dart';
import 'icon.dart';
import 'icon_button.dart';
import 'icons.dart';
import 'material.dart';
import 'snack_bar.dart';
......@@ -556,7 +557,7 @@ class ScaffoldState extends State<Scaffold> {
if (leading == null) {
if (config.drawer != null) {
leading = new IconButton(
icon: Icons.menu,
icon: new Icon(Icons.menu),
alignment: FractionalOffset.centerLeft,
onPressed: openDrawer,
tooltip: 'Open navigation menu' // TODO(ianh): Figure out how to localize this string
......@@ -565,7 +566,7 @@ class ScaffoldState extends State<Scaffold> {
_shouldShowBackArrow ??= Navigator.canPop(context);
if (_shouldShowBackArrow) {
leading = new IconButton(
icon: Icons.arrow_back,
icon: new Icon(Icons.arrow_back),
alignment: FractionalOffset.centerLeft,
onPressed: () => Navigator.pop(context),
tooltip: 'Back' // TODO(ianh): Figure out how to localize this string
......
......@@ -14,7 +14,6 @@ import 'app_bar.dart';
import 'colors.dart';
import 'debug.dart';
import 'icon.dart';
import 'icons.dart';
import 'icon_theme.dart';
import 'icon_theme_data.dart';
import 'ink_well.dart';
......@@ -312,8 +311,14 @@ class TabLabel {
/// The text to display as the label of the tab.
final String text;
/// Data for an [Icon] to display as the label of the tab.
final IconData icon;
/// The icon to display as the label of the tab.
///
/// The size and color of the icon is configured automatically using an
/// [IconTheme] and therefore does not need to be explicitly given in the
/// icon widget.
///
/// See [Icon], [ImageIcon].
final Widget icon;
/// Called if [icon] is null to build an icon as a label for this tab.
///
......@@ -322,6 +327,12 @@ class TabLabel {
///
/// Return value must be non-null.
final TabLabelIconBuilder iconBuilder;
/// Whether this label has any text (specified using [text]).
bool get hasText => text != null;
/// Whether this label has an icon (specified either using [icon] or [iconBuilder]).
bool get hasIcon => icon != null || iconBuilder != null;
}
class _Tab extends StatelessWidget {
......@@ -331,7 +342,7 @@ class _Tab extends StatelessWidget {
this.label,
this.color
}) : super(key: key) {
assert(label.text != null || label.icon != null || label.iconBuilder != null);
assert(label.hasText || label.hasIcon);
}
final VoidCallback onSelected;
......@@ -350,9 +361,16 @@ class _Tab extends StatelessWidget {
}
Widget _buildLabelIcon(BuildContext context) {
assert(label.icon != null || label.iconBuilder != null);
assert(label.hasIcon);
if (label.icon != null) {
return new Icon(icon: label.icon, color: color);
return new IconTheme.merge(
context: context,
data: new IconThemeData(
color: color,
size: 24.0
),
child: label.icon
);
} else {
return new SizedBox(
width: 24.0,
......@@ -366,9 +384,9 @@ class _Tab extends StatelessWidget {
Widget build(BuildContext context) {
assert(debugCheckHasMaterial(context));
Widget labelContent;
if (label.icon == null && label.iconBuilder == null) {
if (!label.hasIcon) {
labelContent = _buildLabelText();
} else if (label.text == null) {
} else if (!label.hasText) {
labelContent = _buildLabelIcon(context);
} else {
labelContent = new Column(
......@@ -676,7 +694,7 @@ class TabBar<T> extends Scrollable implements AppBarBottomWidget {
@override
double get bottomHeight {
for (TabLabel label in labels.values) {
if (label.text != null && (label.icon != null || label.iconBuilder != null))
if (label.hasText && label.hasIcon)
return _kTextAndIconTabHeight + _kTabIndicatorHeight;
}
return _kTabHeight + _kTabIndicatorHeight;
......@@ -922,7 +940,6 @@ class _TabBarState<T> extends ScrollableState<TabBar<T>> implements TabBarSelect
}
final TextStyle textStyle = themeData.primaryTextTheme.body2;
final IconThemeData iconTheme = themeData.primaryIconTheme;
final Color selectedLabelColor = config.labelColor ?? themeData.primaryTextTheme.body2.color;
final Color labelColor = selectedLabelColor.withAlpha(0xB2); // 70% alpha
......@@ -931,23 +948,20 @@ class _TabBarState<T> extends ScrollableState<TabBar<T>> implements TabBarSelect
int tabIndex = 0;
for (TabLabel label in config.labels.values) {
tabs.add(_toTab(label, tabIndex++, labelColor, selectedLabelColor));
if (label.text != null && (label.icon != null || label.iconBuilder != null))
if (label.hasText && label.hasIcon)
textAndIcons = true;
}
Widget contents = new IconTheme(
data: iconTheme,
child: new DefaultTextStyle(
style: textStyle,
child: new _TabBarWrapper(
children: tabs,
selectedIndex: _selection?.index,
indicatorColor: indicatorColor,
indicatorRect: _indicatorRect,
textAndIcons: textAndIcons,
isScrollable: config.isScrollable,
onLayoutChanged: _layoutChanged
)
Widget contents = new DefaultTextStyle(
style: textStyle,
child: new _TabBarWrapper(
children: tabs,
selectedIndex: _selection?.index,
indicatorColor: indicatorColor,
indicatorRect: _indicatorRect,
textAndIcons: textAndIcons,
isScrollable: config.isScrollable,
onLayoutChanged: _layoutChanged
)
);
......
......@@ -89,6 +89,7 @@ class ThemeData {
Color errorColor,
TextTheme textTheme,
TextTheme primaryTextTheme,
IconThemeData iconTheme,
IconThemeData primaryIconTheme
}) {
brightness ??= Brightness.light;
......@@ -96,6 +97,7 @@ class ThemeData {
primarySwatch ??= Colors.blue;
primaryColor ??= isDark ? Colors.grey[900] : primarySwatch[500];
primaryColorBrightness ??= Brightness.dark;
final bool primaryIsDark = primaryColorBrightness == Brightness.dark;
accentColor ??= isDark ? Colors.tealAccent[200] : primarySwatch[500];
accentColorBrightness ??= Brightness.dark;
canvasColor ??= isDark ? Colors.grey[850] : Colors.grey[50];
......@@ -115,8 +117,9 @@ class ThemeData {
hintColor ??= isDark ? const Color(0x42FFFFFF) : const Color(0x4C000000);
errorColor ??= Colors.red[700];
textTheme ??= isDark ? Typography.white : Typography.black;
primaryTextTheme ??= primaryColorBrightness == Brightness.dark ? Typography.white : Typography.black;
primaryIconTheme ??= primaryColorBrightness == Brightness.dark ? const IconThemeData(color: Colors.white) : const IconThemeData(color: Colors.black);
primaryTextTheme ??= primaryIsDark ? Typography.white : Typography.black;
iconTheme ??= isDark ? const IconThemeData(color: Colors.white) : const IconThemeData(color: Colors.black);
primaryIconTheme ??= primaryIsDark ? const IconThemeData(color: Colors.white) : const IconThemeData(color: Colors.black);
return new ThemeData.raw(
brightness: brightness,
primaryColor: primaryColor,
......@@ -141,6 +144,7 @@ class ThemeData {
errorColor: errorColor,
textTheme: textTheme,
primaryTextTheme: primaryTextTheme,
iconTheme: iconTheme,
primaryIconTheme: primaryIconTheme
);
}
......@@ -175,6 +179,7 @@ class ThemeData {
this.errorColor,
this.textTheme,
this.primaryTextTheme,
this.iconTheme,
this.primaryIconTheme
}) {
assert(brightness != null);
......@@ -200,6 +205,7 @@ class ThemeData {
assert(errorColor != null);
assert(textTheme != null);
assert(primaryTextTheme != null);
assert(iconTheme != null);
assert(primaryIconTheme != null);
}
......@@ -307,6 +313,9 @@ class ThemeData {
/// A text theme that contrasts with the primary color.
final TextTheme primaryTextTheme;
/// An icon theme that contrasts with the card and canvas colors.
final IconThemeData iconTheme;
/// An icon theme that contrasts with the primary color.
final IconThemeData primaryIconTheme;
......@@ -336,6 +345,7 @@ class ThemeData {
errorColor: Color.lerp(begin.errorColor, end.errorColor, t),
textTheme: TextTheme.lerp(begin.textTheme, end.textTheme, t),
primaryTextTheme: TextTheme.lerp(begin.primaryTextTheme, end.primaryTextTheme, t),
iconTheme: IconThemeData.lerp(begin.iconTheme, end.iconTheme, t),
primaryIconTheme: IconThemeData.lerp(begin.primaryIconTheme, end.primaryIconTheme, t)
);
}
......@@ -368,6 +378,7 @@ class ThemeData {
(otherData.errorColor == errorColor) &&
(otherData.textTheme == textTheme) &&
(otherData.primaryTextTheme == primaryTextTheme) &&
(otherData.iconTheme == iconTheme) &&
(otherData.primaryIconTheme == primaryIconTheme);
}
......@@ -398,6 +409,7 @@ class ThemeData {
errorColor,
textTheme,
primaryTextTheme,
iconTheme,
primaryIconTheme
)
);
......
......@@ -111,7 +111,8 @@ class _TwoLevelSublistState extends State<TwoLevelSublist> {
),
child: new Column(
children: <Widget>[
new IconTheme(
new IconTheme.merge(
context: context,
data: new IconThemeData(color: _iconColor.evaluate(_easeInAnimation)),
child: new TwoLevelListItem(
onTap: _handleOnTap,
......@@ -122,9 +123,7 @@ class _TwoLevelSublistState extends State<TwoLevelSublist> {
),
trailing: new RotationTransition(
turns: _iconTurns,
child: new Icon(
icon: Icons.expand_more
)
child: new Icon(Icons.expand_more)
)
)
),
......
......@@ -85,18 +85,18 @@ class TextStyle {
}) {
return new TextStyle(
inherit: inherit,
color: color != null ? color : this.color,
fontFamily: fontFamily != null ? fontFamily : this.fontFamily,
fontSize: fontSize != null ? fontSize : this.fontSize,
fontWeight: fontWeight != null ? fontWeight : this.fontWeight,
fontStyle: fontStyle != null ? fontStyle : this.fontStyle,
letterSpacing: letterSpacing != null ? letterSpacing : this.letterSpacing,
wordSpacing: wordSpacing != null ? wordSpacing : this.wordSpacing,
textBaseline: textBaseline != null ? textBaseline : this.textBaseline,
height: height != null ? height : this.height,
decoration: decoration != null ? decoration : this.decoration,
decorationColor: decorationColor != null ? decorationColor : this.decorationColor,
decorationStyle: decorationStyle != null ? decorationStyle : this.decorationStyle
color: color ?? this.color,
fontFamily: fontFamily ?? this.fontFamily,
fontSize: fontSize ?? this.fontSize,
fontWeight: fontWeight ?? this.fontWeight,
fontStyle: fontStyle ?? this.fontStyle,
letterSpacing: letterSpacing ?? this.letterSpacing,
wordSpacing: wordSpacing ?? this.wordSpacing,
textBaseline: textBaseline ?? this.textBaseline,
height: height ?? this.height,
decoration: decoration ?? this.decoration,
decorationColor: decorationColor ?? this.decorationColor,
decorationStyle: decorationStyle ?? this.decorationStyle
);
}
......
......@@ -11,7 +11,7 @@ void main() {
testWidgets('Icon sizing - no theme, default size', (WidgetTester tester) async {
await tester.pumpWidget(
new Center(
child: new Icon()
child: new Icon(null)
)
);
......@@ -23,6 +23,7 @@ void main() {
await tester.pumpWidget(
new Center(
child: new Icon(
null,
size: 96.0
)
)
......@@ -37,7 +38,7 @@ void main() {
new Center(
child: new IconTheme(
data: new IconThemeData(size: 36.0),
child: new Icon()
child: new Icon(null)
)
)
);
......@@ -52,6 +53,7 @@ void main() {
child: new IconTheme(
data: new IconThemeData(size: 36.0),
child: new Icon(
null,
size: 48.0
)
)
......@@ -67,7 +69,7 @@ void main() {
new Center(
child: new IconTheme(
data: new IconThemeData(),
child: new Icon()
child: new Icon(null)
)
)
);
......
// 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:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('ImageIcon sizing - no theme, default size', (WidgetTester tester) async {
await tester.pumpWidget(
new Center(
child: new ImageIcon(null)
)
);
RenderBox renderObject = tester.renderObject(find.byType(ImageIcon));
expect(renderObject.size, equals(const Size.square(24.0)));
});
testWidgets('ImageIcon sizing - no theme, explicit size', (WidgetTester tester) async {
await tester.pumpWidget(
new Center(
child: new ImageIcon(
null,
size: 96.0
)
)
);
RenderBox renderObject = tester.renderObject(find.byType(ImageIcon));
expect(renderObject.size, equals(const Size.square(96.0)));
});
testWidgets('ImageIcon sizing - sized theme', (WidgetTester tester) async {
await tester.pumpWidget(
new Center(
child: new IconTheme(
data: new IconThemeData(size: 36.0),
child: new ImageIcon(null)
)
)
);
RenderBox renderObject = tester.renderObject(find.byType(ImageIcon));
expect(renderObject.size, equals(const Size.square(36.0)));
});
testWidgets('ImageIcon sizing - sized theme, explicit size', (WidgetTester tester) async {
await tester.pumpWidget(
new Center(
child: new IconTheme(
data: new IconThemeData(size: 36.0),
child: new ImageIcon(
null,
size: 48.0
)
)
)
);
RenderBox renderObject = tester.renderObject(find.byType(ImageIcon));
expect(renderObject.size, equals(const Size.square(48.0)));
});
testWidgets('ImageIcon sizing - sizeless theme, default size', (WidgetTester tester) async {
await tester.pumpWidget(
new Center(
child: new IconTheme(
data: new IconThemeData(),
child: new ImageIcon(null)
)
)
);
RenderBox renderObject = tester.renderObject(find.byType(ImageIcon));
expect(renderObject.size, equals(const Size.square(24.0)));
});
}
......@@ -13,7 +13,7 @@ void main() {
color: Colors.green[500],
opacity: 0.5
),
child: new Icon(icon: Icons.add)
child: new Icon(Icons.add)
)
);
Text text = tester.widget(find.byType(Text));
......
......@@ -131,9 +131,9 @@ class AnalyzeCommand extends FlutterCommand {
pubSpecDirectories.add(currentDirectory);
}
//TODO (ianh): Fix the intl package resource generator
//TODO (pq): extract this regexp from the exclude in options
RegExp stockExampleFiles = new RegExp('examples/stocks/lib/.*\.dart\$');
// TODO(ianh): Fix the intl package resource generator
// TODO(pq): extract this regexp from the exclude in options
RegExp stockExampleFiles = new RegExp('examples/stocks/lib/i18n/.*\.dart\$');
if (flutterRepo) {
for (Directory dir in runner.getRepoPackages()) {
......
......@@ -48,9 +48,7 @@ class _FlutterDemoState extends State<FlutterDemo> {
floatingActionButton: new FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: new Icon(
icon: Icons.add
)
child: new Icon(Icons.add)
)
);
}
......
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