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 double getMinIntrinsicWidth(BoxConstraints constraints) { |
| 46 BoxConstraints widthConstraints = |
| 47 new BoxConstraints(maxWidth: constraints.maxWidth, maxHeight: constraint
s.maxHeight); |
| 48 double maxWidth = 0.0; |
| 49 int childCount = 0; |
| 50 RenderBox child = firstChild; |
| 51 while (child != null) { |
| 52 maxWidth = math.max(maxWidth, child.getMinIntrinsicWidth(widthConstraints)
); |
| 53 ++childCount; |
| 54 assert(child.parentData is TabBarParentData); |
| 55 child = child.parentData.nextSibling; |
| 56 } |
| 57 return constraints.constrainWidth(maxWidth * childCount); |
| 58 } |
| 59 |
| 60 double getMaxIntrinsicWidth(BoxConstraints constraints) { |
| 61 BoxConstraints widthConstraints = |
| 62 new BoxConstraints(maxWidth: constraints.maxWidth, maxHeight: constraint
s.maxHeight); |
| 63 double maxWidth = 0.0; |
| 64 int childCount = 0; |
| 65 RenderBox child = firstChild; |
| 66 while (child != null) { |
| 67 maxWidth = math.max(maxWidth, child.getMaxIntrinsicWidth(widthConstraints)
); |
| 68 ++childCount; |
| 69 assert(child.parentData is TabBarParentData); |
| 70 child = child.parentData.nextSibling; |
| 71 } |
| 72 return constraints.constrainWidth(maxWidth * childCount); |
| 73 } |
| 74 |
| 75 double _getIntrinsicHeight(BoxConstraints constraints) => constraints.constrai
nHeight(_kTabBarHeight); |
| 76 |
| 77 double getMinIntrinsicHeight(BoxConstraints constraints) => _getIntrinsicHeigh
t(constraints); |
| 78 |
| 79 double getMaxIntrinsicHeight(BoxConstraints constraints) => _getIntrinsicHeigh
t(constraints); |
| 80 |
| 81 // TODO(hansmuller): track this value in the parent rather than computing it. |
| 82 int _childCount() { |
| 83 int childCount = 0; |
| 84 RenderBox child = firstChild; |
| 85 while (child != null) { |
| 86 ++childCount; |
| 87 assert(child.parentData is TabBarParentData); |
| 88 child = child.parentData.nextSibling; |
| 89 } |
| 90 return childCount; |
| 91 } |
| 92 |
| 93 void performLayout() { |
| 94 assert(constraints is BoxConstraints); |
| 95 |
| 96 size = constraints.constrain(new Size(constraints.maxWidth, _kTabBarHeight))
; |
| 97 assert(size.width < double.INFINITY); |
| 98 assert(size.height < double.INFINITY); |
| 99 |
| 100 int childCount = _childCount(); |
| 101 if (childCount == 0) |
| 102 return; |
| 103 |
| 104 double tabWidth = size.width / childCount; |
| 105 BoxConstraints tabConstraints = |
| 106 new BoxConstraints.tightFor(width: tabWidth, height: size.height); |
| 107 double x = 0.0; |
| 108 RenderBox child = firstChild; |
| 109 while (child != null) { |
| 110 child.layout(tabConstraints); |
| 111 assert(child.parentData is TabBarParentData); |
| 112 child.parentData.position = new Point(x, 0.0); |
| 113 x += tabWidth; |
| 114 child = child.parentData.nextSibling; |
| 115 } |
| 116 } |
| 117 |
| 118 void hitTestChildren(HitTestResult result, { Point position }) { |
| 119 defaultHitTestChildren(result, position: position); |
| 120 } |
| 121 |
| 122 void _paintIndicator(RenderCanvas canvas, RenderBox selectedTab) { |
| 123 var size = new Size(selectedTab.size.width, _kTabIndicatorHeight); |
| 124 var point = new Point(selectedTab.parentData.position.x, _kTabHeight); |
| 125 Rect rect = new Rect.fromPointAndSize(point, size); |
| 126 // TODO(hansmuller): indicator color should be based on the theme. |
| 127 canvas.drawRect(rect, new Paint()..color = White); |
| 128 } |
| 129 |
| 130 void paint(RenderCanvas canvas) { |
| 131 Rect rect = new Rect.fromSize(size); |
| 132 canvas.drawRect(rect, new Paint()..color = Blue[500]); |
| 133 |
| 134 int index = 0; |
| 135 RenderBox child = firstChild; |
| 136 while (child != null) { |
| 137 assert(child.parentData is TabBarParentData); |
| 138 canvas.paintChild(child, child.parentData.position); |
| 139 if (index++ == selectedIndex) |
| 140 _paintIndicator(canvas, child); |
| 141 child = child.parentData.nextSibling; |
| 142 } |
| 143 } |
| 144 } |
| 145 |
| 146 class TabBarWrapper extends MultiChildRenderObjectWrapper { |
| 147 TabBarWrapper(List<Widget> children, this.selectedIndex, { String key }) |
| 148 : super(key: key, children: children); |
| 149 |
| 150 final int selectedIndex; |
| 151 |
| 152 RenderTabBar get root => super.root; |
| 153 RenderTabBar createNode() => new RenderTabBar(); |
| 154 |
| 155 void syncRenderObject(Widget old) { |
| 156 super.syncRenderObject(old); |
| 157 root.selectedIndex = selectedIndex; |
| 158 } |
| 159 } |
| 160 |
| 161 class TabLabel { |
| 162 const TabLabel({ this.text, this.icon }); |
| 163 |
| 164 final String text; |
| 165 final String icon; |
| 166 } |
| 167 |
| 168 class Tab extends Component { |
| 169 Tab({ |
| 170 String key, |
| 171 this.label, |
| 172 this.selected: false |
| 173 }) : super(key: key) { |
| 174 assert(label.text != null || label.icon != null); |
| 175 } |
| 176 |
| 177 final TabLabel label; |
| 178 final bool selected; |
| 179 |
| 180 // TODO(hansmuller): use themes here. |
| 181 static const TextStyle selectedStyle = const TextStyle(color: const Color(0xFF
FFFFFF)); |
| 182 static const TextStyle style = const TextStyle(color: const Color(0xB2FFFFFF))
; |
| 183 |
| 184 Widget _buildLabelText() { |
| 185 assert(label.text != null); |
| 186 return new Text(label.text, style: style); |
| 187 } |
| 188 |
| 189 Widget _buildLabelIcon() { |
| 190 assert(label.icon != null); |
| 191 return new Icon(type: label.icon, size: 24); |
| 192 } |
| 193 |
| 194 Widget build() { |
| 195 Widget labelContents; |
| 196 if (label.icon == null) { |
| 197 labelContents = _buildLabelText(); |
| 198 } else if (label.text == null) { |
| 199 labelContents = _buildLabelIcon(); |
| 200 } else { |
| 201 labelContents = new Flex( |
| 202 <Widget>[_buildLabelText(), _buildLabelIcon()], |
| 203 justifyContent: FlexJustifyContent.center, |
| 204 alignItems: FlexAlignItems.center, |
| 205 direction: FlexDirection.vertical |
| 206 ); |
| 207 } |
| 208 |
| 209 Widget highlightedLabel = new Opacity( |
| 210 child: labelContents, |
| 211 opacity: selected ? 1.0 : 0.7 |
| 212 ); |
| 213 |
| 214 Container centeredLabel = new Container( |
| 215 child: new Center(child: highlightedLabel), |
| 216 constraints: new BoxConstraints(minWidth: _kMinTabWidth) |
| 217 ); |
| 218 |
| 219 return new InkWell(child: centeredLabel); |
| 220 } |
| 221 } |
| 222 |
| 223 class TabBar extends Component { |
| 224 TabBar({ |
| 225 String key, |
| 226 this.labels, |
| 227 this.selectedIndex: 0, |
| 228 this.onChanged |
| 229 }) : super(key: key); |
| 230 |
| 231 final List<TabLabel> labels; |
| 232 final int selectedIndex; |
| 233 final SelectedIndexChanged onChanged; |
| 234 |
| 235 void _handleTap(int tabIndex) { |
| 236 if (tabIndex != selectedIndex && onChanged != null) |
| 237 onChanged(tabIndex); |
| 238 } |
| 239 |
| 240 Widget _toTab(TabLabel label, int tabIndex) { |
| 241 Tab tab = new Tab( |
| 242 label: label, |
| 243 selected: tabIndex == selectedIndex, |
| 244 key: label.text == null ? label.icon : label.text |
| 245 ); |
| 246 return new Listener( |
| 247 child: tab, |
| 248 onGestureTap: (_) => _handleTap(tabIndex) |
| 249 ); |
| 250 } |
| 251 |
| 252 Widget build() { |
| 253 assert(labels != null && labels.isNotEmpty); |
| 254 List<Widget> tabs = <Widget>[]; |
| 255 for (int tabIndex = 0; tabIndex < labels.length; tabIndex++) { |
| 256 tabs.add(_toTab(labels[tabIndex], tabIndex)); |
| 257 } |
| 258 return new TabBarWrapper(tabs, selectedIndex); |
| 259 } |
| 260 } |
| 261 |
| 262 |
OLD | NEW |