Index: sky/sdk/lib/widgets/tabs.dart |
diff --git a/sky/sdk/lib/widgets/tabs.dart b/sky/sdk/lib/widgets/tabs.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..2dcec21919b7b971406c0b5f4b5bca62b338eb94 |
--- /dev/null |
+++ b/sky/sdk/lib/widgets/tabs.dart |
@@ -0,0 +1,267 @@ |
+// 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 'dart:math' as math; |
+import 'dart:sky' as sky; |
+ |
+import 'package:sky/painting/text_style.dart'; |
+import 'package:sky/rendering/box.dart'; |
+import 'package:sky/rendering/object.dart'; |
+import 'package:sky/theme/colors.dart'; |
+import 'package:sky/widgets/basic.dart'; |
+import 'package:sky/widgets/icon.dart'; |
+import 'package:sky/widgets/ink_well.dart'; |
+import 'package:sky/widgets/widget.dart'; |
+ |
+typedef void SelectedIndexChanged(int selectedIndex); |
+ |
+const double _kTabHeight = 46.0; |
+const double _kTabIndicatorHeight = 2.0; |
+const double _kTabBarHeight = _kTabHeight + _kTabIndicatorHeight; |
+const double _kMinTabWidth = 72.0; |
+ |
+class TabBarParentData extends BoxParentData with |
+ ContainerParentDataMixin<RenderBox> { } |
+ |
+class RenderTabBar extends RenderBox with |
+ ContainerRenderObjectMixin<RenderBox, TabBarParentData>, |
+ RenderBoxContainerDefaultsMixin<RenderBox, TabBarParentData> { |
+ |
+ int _selectedIndex; |
+ int get selectedIndex => _selectedIndex; |
+ void set selectedIndex(int value) { |
+ if (_selectedIndex != value) { |
+ _selectedIndex = value; |
+ markNeedsPaint(); |
+ } |
+ } |
+ |
+ void setParentData(RenderBox child) { |
+ if (child.parentData is! TabBarParentData) { |
+ child.parentData = new TabBarParentData(); |
+ } |
+ } |
+ |
+ double getMinIntrinsicWidth(BoxConstraints constraints) { |
+ BoxConstraints widthConstraints = constraints.widthConstraints(); |
+ double maxWidth = 0.0; |
+ int childCount = 0; |
+ for (RenderBox child = firstChild; child != null; child = child.parentData.nextSibling) { |
+ maxWidth = math.max(maxWidth, child.getMinIntrinsicWidth(widthConstraints)); |
+ ++childCount; |
+ } |
+ return maxWidth * childCount.toDouble(); |
+ } |
+ |
+ double getMaxIntrinsicWidth(BoxConstraints constraints) { |
+ BoxConstraints widthConstraints = constraints.widthConstraints(); |
+ double maxWidth = 0.0; |
+ int childCount = 0; |
+ for (RenderBox child = firstChild; child != null; child = child.parentData.nextSibling) { |
+ maxWidth = math.max(maxWidth, child.getMaxIntrinsicWidth(widthConstraints)); |
+ ++childCount; |
+ } |
+ return maxWidth * childCount.toDouble(); |
+ } |
+ |
+ double _getIntrinsicHeight(BoxConstraints constraints) => constraints.constrainHeight(_kTabBarHeight); |
+ |
+ double getMinIntrinsicHeight(BoxConstraints constraints) => _getIntrinsicHeight(constraints); |
+ |
+ double getMaxIntrinsicHeight(BoxConstraints constraints) => _getIntrinsicHeight(constraints); |
+ |
+ int _childCount() { |
+ int childCount = 0; |
+ for (RenderBox child = firstChild; child != null; child = child.parentData.nextSibling) { |
+ ++childCount; |
+ } |
+ return childCount; |
+ } |
+ |
+ void performLayout() { |
+ assert(constraints is BoxConstraints); |
+ |
+ size = constraints.constrain(new Size(constraints.maxWidth, _kTabBarHeight)); |
+ assert(size.width < double.INFINITY); |
+ assert(size.height < double.INFINITY); |
+ |
+ int childCount = _childCount(); |
+ if (childCount == 0) return; |
+ |
+ double tabWidth = constraints.constrainWidth(constraints.maxWidth) / childCount.toDouble(); |
+ BoxConstraints tabConstraints = |
+ new BoxConstraints.tightFor(width: tabWidth, height: size.height); |
+ double x = 0.0; |
+ for (RenderBox child = firstChild; child != null; child = child.parentData.nextSibling) { |
+ 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
|
+ assert(child.parentData is TabBarParentData); |
+ child.parentData.position = new Point(x, 0.0); |
+ x += child.size.width; |
+ } |
+ } |
+ |
+ void hitTestChildren(HitTestResult result, { Point position }) { |
+ defaultHitTestChildren(result, position: position); |
+ } |
+ |
+ void _paintBackground(RenderObjectDisplayList canvas) { |
+ // TODO(hansmuller): background color should be based on the theme. |
+ Paint paint = new Paint() |
+ ..color = Blue[500] |
+ ..setStyle(sky.PaintingStyle.fill); |
+ Rect rect = new Rect.fromSize(size); |
+ canvas.drawRect(rect, paint); |
+ } |
+ |
+ void _paintIndicator(RenderObjectDisplayList canvas, RenderBox selectedTab) { |
+ var size = new Size(selectedTab.size.width, 2.0); |
+ var point = new Point(selectedTab.parentData.position.x, 46.0); |
+ Rect rect = new Rect.fromPointAndSize(point, size); |
+ // TODO(hansmuller): indicator color should be based on the theme. |
+ Paint paint = new Paint() |
+ ..color = White |
+ ..setStyle(sky.PaintingStyle.fill); |
+ canvas.drawRect(rect, paint); |
+ } |
+ |
+ void paint(RenderObjectDisplayList canvas) { |
+ _paintBackground(canvas); |
+ |
+ var index = 0; |
+ for (RenderBox child = firstChild; child != null; child = child.parentData.nextSibling) { |
+ assert(child.parentData is TabBarParentData); |
+ canvas.paintChild(child, child.parentData.position); |
+ if (index++ == selectedIndex) _paintIndicator(canvas, child); |
+ } |
+ } |
+} |
+ |
+class TabBarWrapper extends MultiChildRenderObjectWrapper { |
+ TabBarWrapper(List<Widget> children, int this.selectedIndex, { String key }) |
+ : super(key: key, children: children); |
+ |
+ final int selectedIndex; |
+ |
+ RenderTabBar get root => super.root; |
+ RenderTabBar createNode() => new RenderTabBar(); |
+ |
+ void syncRenderObject(Widget old) { |
+ super.syncRenderObject(old); |
+ root.selectedIndex = selectedIndex; |
+ } |
+} |
+ |
+class TabLabel { |
+ const TabLabel({ this.text, this.icon }); |
+ |
+ final String text; |
+ final String icon; |
+} |
+ |
+class Tab extends Component { |
+ Tab({ |
+ String key, |
+ this.label, |
+ this.selected: false |
+ }) : super(key: key) { |
+ assert(label.text != null || label.icon != null); |
+ } |
+ |
+ final TabLabel label; |
+ final bool selected; |
+ |
+ // TODO(hansmuller): use themes here. |
+ static const TextStyle selectedStyle = const TextStyle(color: const Color(0xFFFFFFFF)); |
+ static const TextStyle style = const TextStyle(color: const Color(0xB2FFFFFF)); |
+ |
+ Widget _buildLabelText() { |
+ assert(label.text != null); |
+ return new Text(label.text, style: style); |
+ } |
+ |
+ Widget _buildLabelIcon() { |
+ assert(label.icon != null); |
+ return new Icon(type: label.icon, size: 24); |
+ } |
+ |
+ Widget build() { |
+ Widget labelContents; |
+ if (label.icon == null) { |
+ labelContents = _buildLabelText(); |
+ } else if (label.text == null) { |
+ labelContents = _buildLabelIcon(); |
+ } else { |
+ labelContents = new Flex( |
+ <Widget>[_buildLabelText(), _buildLabelIcon()], |
+ justifyContent: FlexJustifyContent.center, |
+ alignItems: FlexAlignItems.center, |
+ direction: FlexDirection.vertical |
+ ); |
+ } |
+ |
+ Widget highlightedLabel = new Opacity( |
+ child: labelContents, |
+ opacity: selected ? 1.0 : 0.7 |
+ ); |
+ |
+ Container centeredLabel = new Container( |
+ child: new Center(child: highlightedLabel), |
+ constraints: new BoxConstraints(minWidth: _kMinTabWidth) |
+ ); |
+ |
+ return new InkWell(child: centeredLabel); |
+ } |
+} |
+ |
+class TabBar extends Component { |
+ TabBar({ |
+ String key, |
+ this.labels, |
+ this.selectedIndex: 0, |
+ this.onChanged |
+ }) : super(key: key, stateful: true); |
+ |
+ List<TabLabel> labels; |
+ int selectedIndex; |
+ SelectedIndexChanged onChanged; |
+ |
+ void syncFields(TabBar source) { |
+ labels = source.labels; |
+ selectedIndex = source.selectedIndex; |
+ onChanged = source.onChanged; |
+ super.syncFields(source); |
+ } |
+ |
+ void _handleTap(int tabIndex) { |
+ if (tabIndex != selectedIndex) { |
+ setState(() { |
+ selectedIndex = tabIndex; |
+ }); |
+ if (onChanged != null) onChanged(selectedIndex); |
+ } |
+ } |
+ |
+ Widget _toTab(TabLabel label, int tabIndex) { |
+ Tab tab = new Tab( |
+ label: label, |
+ selected: tabIndex == selectedIndex, |
+ key: label.text == null ? label.icon : label.text |
+ ); |
+ return new Listener( |
+ child: tab, |
+ onGestureTap: (_) => _handleTap(tabIndex) |
+ ); |
+ } |
+ |
+ Widget build() { |
+ assert(labels != null && labels.isNotEmpty); |
+ List<Widget> tabs = <Widget>[]; |
+ for (int tabIndex = 0; tabIndex < labels.length; tabIndex++) { |
+ tabs.add(_toTab(labels[tabIndex], tabIndex)); |
+ } |
+ return new TabBarWrapper(tabs, selectedIndex); |
+ } |
+} |
+ |
+ |