Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 import 'dart:math' as math; | |
| 6 import 'dart:sky' as sky; | |
| 7 | |
| 8 import 'package:sky/painting/text_style.dart'; | |
| 9 import 'package:sky/rendering/box.dart'; | |
| 10 import 'package:sky/rendering/object.dart'; | |
| 11 import 'package:sky/theme/colors.dart'; | |
| 12 import 'package:sky/widgets/basic.dart'; | |
| 13 import 'package:sky/widgets/icon.dart'; | |
| 14 import 'package:sky/widgets/ink_well.dart'; | |
| 15 import 'package:sky/widgets/widget.dart'; | |
| 16 | |
| 17 typedef void SelectedIndexChanged(int selectedIndex); | |
| 18 | |
| 19 const double _kTabHeight = 46.0; | |
| 20 const double _kTabIndicatorHeight = 2.0; | |
| 21 const double _kTabBarHeight = _kTabHeight + _kTabIndicatorHeight; | |
| 22 const double _kMinTabWidth = 72.0; | |
| 23 | |
| 24 class TabBarParentData extends BoxParentData with | |
| 25 ContainerParentDataMixin<RenderBox> { } | |
| 26 | |
| 27 class RenderTabBar extends RenderBox with | |
| 28 ContainerRenderObjectMixin<RenderBox, TabBarParentData>, | |
| 29 RenderBoxContainerDefaultsMixin<RenderBox, TabBarParentData> { | |
| 30 | |
| 31 int _selectedIndex; | |
| 32 int get selectedIndex => _selectedIndex; | |
| 33 void set selectedIndex(int value) { | |
| 34 if (_selectedIndex != value) { | |
| 35 _selectedIndex = value; | |
| 36 markNeedsPaint(); | |
| 37 } | |
| 38 } | |
| 39 | |
| 40 void setParentData(RenderBox child) { | |
| 41 if (child.parentData is! TabBarParentData) { | |
| 42 child.parentData = new TabBarParentData(); | |
| 43 } | |
| 44 } | |
| 45 | |
| 46 double getMinIntrinsicWidth(BoxConstraints constraints) { | |
| 47 BoxConstraints widthConstraints = constraints.widthConstraints(); | |
| 48 double maxWidth = 0.0; | |
| 49 int childCount = 0; | |
| 50 for (RenderBox child = firstChild; child != null; child = child.parentData.n extSibling) { | |
| 51 maxWidth = math.max(maxWidth, child.getMinIntrinsicWidth(widthConstraints) ); | |
| 52 ++childCount; | |
| 53 } | |
| 54 return maxWidth * childCount.toDouble(); | |
| 55 } | |
| 56 | |
| 57 double getMaxIntrinsicWidth(BoxConstraints constraints) { | |
| 58 BoxConstraints widthConstraints = constraints.widthConstraints(); | |
| 59 double maxWidth = 0.0; | |
| 60 int childCount = 0; | |
| 61 for (RenderBox child = firstChild; child != null; child = child.parentData.n extSibling) { | |
| 62 maxWidth = math.max(maxWidth, child.getMaxIntrinsicWidth(widthConstraints) ); | |
| 63 ++childCount; | |
| 64 } | |
| 65 return maxWidth * childCount.toDouble(); | |
| 66 } | |
| 67 | |
| 68 double _getIntrinsicHeight(BoxConstraints constraints) => constraints.constrai nHeight(_kTabBarHeight); | |
| 69 | |
| 70 double getMinIntrinsicHeight(BoxConstraints constraints) => _getIntrinsicHeigh t(constraints); | |
| 71 | |
| 72 double getMaxIntrinsicHeight(BoxConstraints constraints) => _getIntrinsicHeigh t(constraints); | |
| 73 | |
| 74 int _childCount() { | |
| 75 int childCount = 0; | |
| 76 for (RenderBox child = firstChild; child != null; child = child.parentData.n extSibling) { | |
| 77 ++childCount; | |
| 78 } | |
| 79 return childCount; | |
| 80 } | |
| 81 | |
| 82 void performLayout() { | |
| 83 assert(constraints is BoxConstraints); | |
| 84 | |
| 85 size = constraints.constrain(new Size(constraints.maxWidth, _kTabBarHeight)) ; | |
| 86 assert(size.width < double.INFINITY); | |
| 87 assert(size.height < double.INFINITY); | |
| 88 | |
| 89 int childCount = _childCount(); | |
| 90 if (childCount == 0) return; | |
| 91 | |
| 92 double tabWidth = constraints.constrainWidth(constraints.maxWidth) / childCo unt.toDouble(); | |
| 93 BoxConstraints tabConstraints = | |
| 94 new BoxConstraints.tightFor(width: tabWidth, height: size.height); | |
| 95 double x = 0.0; | |
| 96 for (RenderBox child = firstChild; child != null; child = child.parentData.n extSibling) { | |
| 97 child.layout(tabConstraints, parentUsesSize: true); | |
|
abarth-chromium
2015/06/25 18:25:49
Rather than passing |parentUsesSize: true| here, w
hansmuller
2015/06/25 19:45:50
Sorry, I wasn't disagreeing with this feedback; It
| |
| 98 assert(child.parentData is TabBarParentData); | |
| 99 child.parentData.position = new Point(x, 0.0); | |
| 100 x += child.size.width; | |
| 101 } | |
| 102 } | |
| 103 | |
| 104 void hitTestChildren(HitTestResult result, { Point position }) { | |
| 105 defaultHitTestChildren(result, position: position); | |
| 106 } | |
| 107 | |
| 108 void _paintBackground(RenderObjectDisplayList canvas) { | |
| 109 // TODO(hansmuller): background color should be based on the theme. | |
| 110 Paint paint = new Paint() | |
| 111 ..color = Blue[500] | |
| 112 ..setStyle(sky.PaintingStyle.fill); | |
| 113 Rect rect = new Rect.fromSize(size); | |
| 114 canvas.drawRect(rect, paint); | |
| 115 } | |
| 116 | |
| 117 void _paintIndicator(RenderObjectDisplayList canvas, RenderBox selectedTab) { | |
| 118 var size = new Size(selectedTab.size.width, 2.0); | |
| 119 var point = new Point(selectedTab.parentData.position.x, 46.0); | |
| 120 Rect rect = new Rect.fromPointAndSize(point, size); | |
| 121 // TODO(hansmuller): indicator color should be based on the theme. | |
| 122 Paint paint = new Paint() | |
| 123 ..color = White | |
| 124 ..setStyle(sky.PaintingStyle.fill); | |
| 125 canvas.drawRect(rect, paint); | |
| 126 } | |
| 127 | |
| 128 void paint(RenderObjectDisplayList canvas) { | |
| 129 _paintBackground(canvas); | |
| 130 | |
| 131 var index = 0; | |
| 132 for (RenderBox child = firstChild; child != null; child = child.parentData.n extSibling) { | |
| 133 assert(child.parentData is TabBarParentData); | |
| 134 canvas.paintChild(child, child.parentData.position); | |
| 135 if (index++ == selectedIndex) _paintIndicator(canvas, child); | |
| 136 } | |
| 137 } | |
| 138 } | |
| 139 | |
| 140 class TabBarWrapper extends MultiChildRenderObjectWrapper { | |
| 141 TabBarWrapper(List<Widget> children, int this.selectedIndex, { String key }) | |
| 142 : super(key: key, children: children); | |
| 143 | |
| 144 final int selectedIndex; | |
| 145 | |
| 146 RenderTabBar get root => super.root; | |
| 147 RenderTabBar createNode() => new RenderTabBar(); | |
| 148 | |
| 149 void syncRenderObject(Widget old) { | |
| 150 super.syncRenderObject(old); | |
| 151 root.selectedIndex = selectedIndex; | |
| 152 } | |
| 153 } | |
| 154 | |
| 155 class TabLabel { | |
| 156 const TabLabel({ this.text, this.icon }); | |
| 157 | |
| 158 final String text; | |
| 159 final String icon; | |
| 160 } | |
| 161 | |
| 162 class Tab extends Component { | |
| 163 Tab({ | |
| 164 String key, | |
| 165 this.label, | |
| 166 this.selected: false | |
| 167 }) : super(key: key) { | |
| 168 assert(label.text != null || label.icon != null); | |
| 169 } | |
| 170 | |
| 171 final TabLabel label; | |
| 172 final bool selected; | |
| 173 | |
| 174 // TODO(hansmuller): use themes here. | |
| 175 static const TextStyle selectedStyle = const TextStyle(color: const Color(0xFF FFFFFF)); | |
| 176 static const TextStyle style = const TextStyle(color: const Color(0xB2FFFFFF)) ; | |
| 177 | |
| 178 Widget _buildLabelText() { | |
| 179 assert(label.text != null); | |
| 180 return new Text(label.text, style: style); | |
| 181 } | |
| 182 | |
| 183 Widget _buildLabelIcon() { | |
| 184 assert(label.icon != null); | |
| 185 return new Icon(type: label.icon, size: 24); | |
| 186 } | |
| 187 | |
| 188 Widget build() { | |
| 189 Widget labelContents; | |
| 190 if (label.icon == null) { | |
| 191 labelContents = _buildLabelText(); | |
| 192 } else if (label.text == null) { | |
| 193 labelContents = _buildLabelIcon(); | |
| 194 } else { | |
| 195 labelContents = new Flex( | |
| 196 <Widget>[_buildLabelText(), _buildLabelIcon()], | |
| 197 justifyContent: FlexJustifyContent.center, | |
| 198 alignItems: FlexAlignItems.center, | |
| 199 direction: FlexDirection.vertical | |
| 200 ); | |
| 201 } | |
| 202 | |
| 203 Widget highlightedLabel = new Opacity( | |
| 204 child: labelContents, | |
| 205 opacity: selected ? 1.0 : 0.7 | |
| 206 ); | |
| 207 | |
| 208 Container centeredLabel = new Container( | |
| 209 child: new Center(child: highlightedLabel), | |
| 210 constraints: new BoxConstraints(minWidth: _kMinTabWidth) | |
| 211 ); | |
| 212 | |
| 213 return new InkWell(child: centeredLabel); | |
| 214 } | |
| 215 } | |
| 216 | |
| 217 class TabBar extends Component { | |
| 218 TabBar({ | |
| 219 String key, | |
| 220 this.labels, | |
| 221 this.selectedIndex: 0, | |
| 222 this.onChanged | |
| 223 }) : super(key: key, stateful: true); | |
| 224 | |
| 225 List<TabLabel> labels; | |
| 226 int selectedIndex; | |
| 227 SelectedIndexChanged onChanged; | |
| 228 | |
| 229 void syncFields(TabBar source) { | |
| 230 labels = source.labels; | |
| 231 selectedIndex = source.selectedIndex; | |
| 232 onChanged = source.onChanged; | |
| 233 super.syncFields(source); | |
| 234 } | |
| 235 | |
| 236 void _handleTap(int tabIndex) { | |
| 237 if (tabIndex != selectedIndex) { | |
| 238 setState(() { | |
| 239 selectedIndex = tabIndex; | |
| 240 }); | |
| 241 if (onChanged != null) onChanged(selectedIndex); | |
| 242 } | |
| 243 } | |
| 244 | |
| 245 Widget _toTab(TabLabel label, int tabIndex) { | |
| 246 Tab tab = new Tab( | |
| 247 label: label, | |
| 248 selected: tabIndex == selectedIndex, | |
| 249 key: label.text == null ? label.icon : label.text | |
| 250 ); | |
| 251 return new Listener( | |
| 252 child: tab, | |
| 253 onGestureTap: (_) => _handleTap(tabIndex) | |
| 254 ); | |
| 255 } | |
| 256 | |
| 257 Widget build() { | |
| 258 assert(labels != null && labels.isNotEmpty); | |
| 259 List<Widget> tabs = <Widget>[]; | |
| 260 for (int tabIndex = 0; tabIndex < labels.length; tabIndex++) { | |
| 261 tabs.add(_toTab(labels[tabIndex], tabIndex)); | |
| 262 } | |
| 263 return new TabBarWrapper(tabs, selectedIndex); | |
| 264 } | |
| 265 } | |
| 266 | |
| 267 | |
| OLD | NEW |