| 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..198e1b4818bd5f33b1e20e489da4a09d58208c3a
|
| --- /dev/null
|
| +++ b/sky/sdk/lib/widgets/tabs.dart
|
| @@ -0,0 +1,262 @@
|
| +// 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 =
|
| + new BoxConstraints(maxWidth: constraints.maxWidth, maxHeight: constraints.maxHeight);
|
| + double maxWidth = 0.0;
|
| + int childCount = 0;
|
| + RenderBox child = firstChild;
|
| + while (child != null) {
|
| + maxWidth = math.max(maxWidth, child.getMinIntrinsicWidth(widthConstraints));
|
| + ++childCount;
|
| + assert(child.parentData is TabBarParentData);
|
| + child = child.parentData.nextSibling;
|
| + }
|
| + return constraints.constrainWidth(maxWidth * childCount);
|
| + }
|
| +
|
| + double getMaxIntrinsicWidth(BoxConstraints constraints) {
|
| + BoxConstraints widthConstraints =
|
| + new BoxConstraints(maxWidth: constraints.maxWidth, maxHeight: constraints.maxHeight);
|
| + double maxWidth = 0.0;
|
| + int childCount = 0;
|
| + RenderBox child = firstChild;
|
| + while (child != null) {
|
| + maxWidth = math.max(maxWidth, child.getMaxIntrinsicWidth(widthConstraints));
|
| + ++childCount;
|
| + assert(child.parentData is TabBarParentData);
|
| + child = child.parentData.nextSibling;
|
| + }
|
| + return constraints.constrainWidth(maxWidth * childCount);
|
| + }
|
| +
|
| + double _getIntrinsicHeight(BoxConstraints constraints) => constraints.constrainHeight(_kTabBarHeight);
|
| +
|
| + double getMinIntrinsicHeight(BoxConstraints constraints) => _getIntrinsicHeight(constraints);
|
| +
|
| + double getMaxIntrinsicHeight(BoxConstraints constraints) => _getIntrinsicHeight(constraints);
|
| +
|
| + // TODO(hansmuller): track this value in the parent rather than computing it.
|
| + int _childCount() {
|
| + int childCount = 0;
|
| + RenderBox child = firstChild;
|
| + while (child != null) {
|
| + ++childCount;
|
| + assert(child.parentData is TabBarParentData);
|
| + child = child.parentData.nextSibling;
|
| + }
|
| + 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 = size.width / childCount;
|
| + BoxConstraints tabConstraints =
|
| + new BoxConstraints.tightFor(width: tabWidth, height: size.height);
|
| + double x = 0.0;
|
| + RenderBox child = firstChild;
|
| + while (child != null) {
|
| + child.layout(tabConstraints);
|
| + assert(child.parentData is TabBarParentData);
|
| + child.parentData.position = new Point(x, 0.0);
|
| + x += tabWidth;
|
| + child = child.parentData.nextSibling;
|
| + }
|
| + }
|
| +
|
| + void hitTestChildren(HitTestResult result, { Point position }) {
|
| + defaultHitTestChildren(result, position: position);
|
| + }
|
| +
|
| + void _paintIndicator(RenderCanvas canvas, RenderBox selectedTab) {
|
| + var size = new Size(selectedTab.size.width, _kTabIndicatorHeight);
|
| + var point = new Point(selectedTab.parentData.position.x, _kTabHeight);
|
| + Rect rect = new Rect.fromPointAndSize(point, size);
|
| + // TODO(hansmuller): indicator color should be based on the theme.
|
| + canvas.drawRect(rect, new Paint()..color = White);
|
| + }
|
| +
|
| + void paint(RenderCanvas canvas) {
|
| + Rect rect = new Rect.fromSize(size);
|
| + canvas.drawRect(rect, new Paint()..color = Blue[500]);
|
| +
|
| + int index = 0;
|
| + RenderBox child = firstChild;
|
| + while (child != null) {
|
| + assert(child.parentData is TabBarParentData);
|
| + canvas.paintChild(child, child.parentData.position);
|
| + if (index++ == selectedIndex)
|
| + _paintIndicator(canvas, child);
|
| + child = child.parentData.nextSibling;
|
| + }
|
| + }
|
| +}
|
| +
|
| +class TabBarWrapper extends MultiChildRenderObjectWrapper {
|
| + TabBarWrapper(List<Widget> children, 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);
|
| +
|
| + final List<TabLabel> labels;
|
| + final int selectedIndex;
|
| + final SelectedIndexChanged onChanged;
|
| +
|
| + void _handleTap(int tabIndex) {
|
| + if (tabIndex != selectedIndex && onChanged != null)
|
| + onChanged(tabIndex);
|
| + }
|
| +
|
| + 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);
|
| + }
|
| +}
|
| +
|
| +
|
|
|