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 |