diff --git a/sky/packages/sky/example/stocks/lib/stock_settings.dart b/sky/packages/sky/example/stocks/lib/stock_settings.dart index c3ddb8efbcc..d3a4977d7b8 100644 --- a/sky/packages/sky/example/stocks/lib/stock_settings.dart +++ b/sky/packages/sky/example/stocks/lib/stock_settings.dart @@ -104,7 +104,7 @@ class StockSettings extends StatefulComponent { onPressed: () => _confirmOptimismChange(), children: [ new Flexible(child: new Text('Everything is awesome')), - new Checkbox(value: optimism == StockMode.optimistic, onChanged: _handleOptimismChanged) + new Checkbox(value: optimism == StockMode.optimistic, onChanged: (_) => _confirmOptimismChange()) ] ), new DrawerItem( diff --git a/sky/packages/sky/lib/rendering/toggleable.dart b/sky/packages/sky/lib/rendering/toggleable.dart new file mode 100644 index 00000000000..24ec5c7402a --- /dev/null +++ b/sky/packages/sky/lib/rendering/toggleable.dart @@ -0,0 +1,71 @@ +// 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:sky' as sky; + +import 'package:sky/animation/animated_value.dart'; +import 'package:sky/animation/animation_performance.dart'; +import 'package:sky/animation/curves.dart'; +import 'package:sky/rendering/box.dart'; +import 'package:sky/rendering/object.dart'; + +typedef void ValueChanged(bool value); + +const Duration _kToggleDuration = const Duration(milliseconds: 200); + +// RenderToggleable is a base class for material style toggleable controls with +// toggle animations. It handles storing the current value, dispatching +// ValueChanged on a tap gesture and driving a changed animation. Subclasses are +// responsible for painting. +abstract class RenderToggleable extends RenderConstrainedBox { + RenderToggleable({bool value, Size size, ValueChanged onChanged}) + : _value = value, + _onChanged = onChanged, + super(additionalConstraints: new BoxConstraints.tight(size)) { + _performance = new AnimationPerformance() + ..variable = _position + ..duration = _kToggleDuration + ..progress = _value ? 1.0 : 0.0 + ..addListener(markNeedsPaint); + } + + EventDisposition handleEvent(sky.Event event, BoxHitTestEntry entry) { + if (event is sky.GestureEvent && event.type == 'gesturetap') { + _onChanged(!_value); + return EventDisposition.consumed; + } + return EventDisposition.ignored; + } + + bool _value; + bool get value => _value; + + void set value(bool value) { + if (value == _value) return; + _value = value; + // TODO(abarth): Setting the curve on the position means there's a + // discontinuity when we reverse the timeline. + if (value) { + _position.curve = easeIn; + _performance.play(); + } else { + _position.curve = easeOut; + _performance.reverse(); + } + } + + ValueChanged _onChanged; + ValueChanged get onChanged => _onChanged; + + void set onChanged(ValueChanged onChanged) { + _onChanged = onChanged; + } + + final AnimatedValue _position = + new AnimatedValue(0.0, end: 1.0); + AnimatedValue get position => _position; + + AnimationPerformance _performance; + AnimationPerformance get performance => _performance; +} diff --git a/sky/packages/sky/lib/widgets.dart b/sky/packages/sky/lib/widgets.dart index 0faa7885684..eb376ebbf61 100644 --- a/sky/packages/sky/lib/widgets.dart +++ b/sky/packages/sky/lib/widgets.dart @@ -37,7 +37,6 @@ export 'widgets/switch.dart'; export 'widgets/tabs.dart'; export 'widgets/task_description.dart'; export 'widgets/theme.dart'; -export 'widgets/toggleable.dart'; export 'widgets/tool_bar.dart'; export 'widgets/transitions.dart'; export 'widgets/widget.dart'; diff --git a/sky/packages/sky/lib/widgets/basic.dart b/sky/packages/sky/lib/widgets/basic.dart index 8763cd046e6..1cf1c11b672 100644 --- a/sky/packages/sky/lib/widgets/basic.dart +++ b/sky/packages/sky/lib/widgets/basic.dart @@ -23,10 +23,9 @@ export 'package:sky/base/hit_test.dart' show EventDisposition; export 'package:sky/rendering/box.dart' show BackgroundImage, BoxConstraints, BoxDecoration, Border, BorderSide, EdgeDims; export 'package:sky/rendering/flex.dart' show FlexDirection, FlexJustifyContent, FlexAlignItems; export 'package:sky/rendering/object.dart' show Point, Offset, Size, Rect, Color, Paint, Path; +export 'package:sky/rendering/toggleable.dart' show ValueChanged; export 'package:sky/widgets/widget.dart' show Key, GlobalKey, Widget, Component, StatefulComponent, App, runApp, Listener, ParentDataNode; -typedef void ValueChanged(bool); - // PAINTING NODES class Opacity extends OneChildRenderObjectWrapper { diff --git a/sky/packages/sky/lib/widgets/checkbox.dart b/sky/packages/sky/lib/widgets/checkbox.dart index cdd76b7bbd1..a340a2bb8dd 100644 --- a/sky/packages/sky/lib/widgets/checkbox.dart +++ b/sky/packages/sky/lib/widgets/checkbox.dart @@ -4,17 +4,20 @@ import 'dart:sky' as sky; -import 'package:sky/widgets/theme.dart'; +import 'package:sky/rendering/object.dart'; import 'package:sky/widgets/basic.dart'; -import 'package:sky/widgets/toggleable.dart'; +import 'package:sky/widgets/theme.dart'; +import 'package:sky/widgets/widget.dart'; +import 'package:sky/rendering/toggleable.dart'; -export 'toggleable.dart' show ValueChanged; +export 'package:sky/rendering/toggleable.dart' show ValueChanged; const double _kMidpoint = 0.5; const sky.Color _kLightUncheckedColor = const sky.Color(0x8A000000); const sky.Color _kDarkUncheckedColor = const sky.Color(0xB2FFFFFF); const double _kEdgeSize = 20.0; const double _kEdgeRadius = 1.0; +const Duration _kCheckDuration = const Duration(milliseconds: 200); /// A material design checkbox /// @@ -25,32 +28,91 @@ const double _kEdgeRadius = 1.0; /// the checkbox. /// /// -class Checkbox extends Toggleable { - +class Checkbox extends Component { /// Constructs a checkbox /// /// * `value` determines whether the checkbox is checked. /// * `onChanged` is called whenever the state of the checkbox should change. - Checkbox({ - Key key, - bool value, - ValueChanged onChanged - }) : super(key: key, value: value, onChanged: onChanged); + Checkbox({Key key, this.value, this.onChanged}) : super(key: key); - Size get size => const Size(_kEdgeSize + 2.0, _kEdgeSize + 2.0); + final bool value; + final ValueChanged onChanged; - void customPaintCallback(sky.Canvas canvas, Size size) { + Widget build() { ThemeData themeData = Theme.of(this); - Color uncheckedColor = themeData.brightness == ThemeBrightness.light ? _kLightUncheckedColor : _kDarkUncheckedColor; + Color uncheckedColor = themeData.brightness == ThemeBrightness.light + ? _kLightUncheckedColor + : _kDarkUncheckedColor; + return new _CheckboxWrapper( + value: value, + onChanged: onChanged, + uncheckedColor: uncheckedColor, + accentColor: themeData.accentColor); + } +} +// This wrapper class exists only because Switch needs to be a Component in +// order to get an accent color from a Theme but Components do not know how to +// host RenderObjects. +class _CheckboxWrapper extends LeafRenderObjectWrapper { + _CheckboxWrapper({Key key, this.value, this.onChanged, this.uncheckedColor, + this.accentColor}) + : super(key: key); + + final bool value; + final ValueChanged onChanged; + final Color uncheckedColor; + final Color accentColor; + + _RenderCheckbox get root => super.root; + _RenderCheckbox createNode() => new _RenderCheckbox( + value: value, uncheckedColor: uncheckedColor, onChanged: onChanged); + + void syncRenderObject(_CheckboxWrapper old) { + super.syncRenderObject(old); + root.value = value; + root.onChanged = onChanged; + root.uncheckedColor = uncheckedColor; + root.accentColor = accentColor; + } +} + +class _RenderCheckbox extends RenderToggleable { + _RenderCheckbox({bool value, Color uncheckedColor, ValueChanged onChanged}) + : _uncheckedColor = uncheckedColor, + super( + value: value, + onChanged: onChanged, + size: new Size(_kEdgeSize, _kEdgeSize)) {} + + Color _uncheckedColor; + Color get uncheckedColor => _uncheckedColor; + + void set uncheckedColor(Color value) { + if (value == _uncheckedColor) return; + _uncheckedColor = value; + markNeedsPaint(); + } + + Color _accentColor; + void set accentColor(Color value) { + if (value == _accentColor) return; + _accentColor = value; + markNeedsPaint(); + } + + void paint(PaintingCanvas canvas, Offset offset) { // Choose a color between grey and the theme color - sky.Paint paint = new sky.Paint()..strokeWidth = 2.0 - ..color = uncheckedColor; + sky.Paint paint = new sky.Paint() + ..strokeWidth = 2.0 + ..color = uncheckedColor; // The rrect contracts slightly during the animation double inset = 2.0 - (position.value - _kMidpoint).abs() * 2.0; - sky.Rect rect = new sky.Rect.fromLTRB(inset, inset, _kEdgeSize - inset, _kEdgeSize - inset); - sky.RRect rrect = new sky.RRect()..setRectXY(rect, _kEdgeRadius, _kEdgeRadius); + sky.Rect rect = new sky.Rect.fromLTWH(offset.dx + inset, offset.dy + inset, + _kEdgeSize - inset, _kEdgeSize - inset); + sky.RRect rrect = new sky.RRect() + ..setRectXY(rect, _kEdgeRadius, _kEdgeRadius); // Outline of the empty rrect paint.setStyle(sky.PaintingStyle.stroke); @@ -59,13 +121,12 @@ class Checkbox extends Toggleable { // Radial gradient that changes size if (position.value > 0) { paint.setStyle(sky.PaintingStyle.fill); - paint.setShader( - new sky.Gradient.radial( + paint.setShader(new sky.Gradient.radial( new Point(_kEdgeSize / 2.0, _kEdgeSize / 2.0), - _kEdgeSize * (_kMidpoint - position.value) * 8.0, - [const sky.Color(0x00000000), uncheckedColor] - ) - ); + _kEdgeSize * (_kMidpoint - position.value) * 8.0, [ + const sky.Color(0x00000000), + uncheckedColor + ])); canvas.drawRRect(rrect, paint); } @@ -74,10 +135,8 @@ class Checkbox extends Toggleable { // Solid filled rrect paint.setStyle(sky.PaintingStyle.strokeAndFill); - paint.color = new Color.fromARGB((t * 255).floor(), - themeData.accentColor.red, - themeData.accentColor.green, - themeData.accentColor.blue); + paint.color = new Color.fromARGB((t * 255).floor(), _accentColor.red, + _accentColor.green, _accentColor.blue); canvas.drawRRect(rrect, paint); // White inner check @@ -87,13 +146,13 @@ class Checkbox extends Toggleable { sky.Point start = new sky.Point(_kEdgeSize * 0.2, _kEdgeSize * 0.5); sky.Point mid = new sky.Point(_kEdgeSize * 0.4, _kEdgeSize * 0.7); sky.Point end = new sky.Point(_kEdgeSize * 0.8, _kEdgeSize * 0.3); - Point lerp(Point p1, Point p2, double t) - => new Point(p1.x * (1.0 - t) + p2.x * t, p1.y * (1.0 - t) + p2.y * t); + Point lerp(Point p1, Point p2, double t) => + new Point(p1.x * (1.0 - t) + p2.x * t, p1.y * (1.0 - t) + p2.y * t); sky.Point drawStart = lerp(start, mid, 1.0 - t); sky.Point drawEnd = lerp(mid, end, t); - path.moveTo(drawStart.x, drawStart.y); - path.lineTo(mid.x, mid.y); - path.lineTo(drawEnd.x, drawEnd.y); + path.moveTo(offset.dx + drawStart.x, offset.dy + drawStart.y); + path.lineTo(offset.dx + mid.x, offset.dy + mid.y); + path.lineTo(offset.dx + drawEnd.x, offset.dy + drawEnd.y); canvas.drawPath(path, paint); } } diff --git a/sky/packages/sky/lib/widgets/switch.dart b/sky/packages/sky/lib/widgets/switch.dart index 969936fab2b..fc917c9c039 100644 --- a/sky/packages/sky/lib/widgets/switch.dart +++ b/sky/packages/sky/lib/widgets/switch.dart @@ -4,9 +4,6 @@ import 'dart:sky' as sky; -import 'package:sky/animation/animated_value.dart'; -import 'package:sky/animation/animation_performance.dart'; -import 'package:sky/animation/curves.dart'; import 'package:sky/painting/shadows.dart'; import 'package:sky/rendering/box.dart'; import 'package:sky/rendering/object.dart'; @@ -14,8 +11,9 @@ import 'package:sky/theme/shadows.dart'; import 'package:sky/widgets/basic.dart'; import 'package:sky/widgets/theme.dart'; import 'package:sky/widgets/widget.dart'; +import 'package:sky/rendering/toggleable.dart'; -export 'package:sky/widgets/basic.dart' show ValueChanged; +export 'package:sky/rendering/toggleable.dart' show ValueChanged; const sky.Color _kThumbOffColor = const sky.Color(0xFFFAFAFA); const sky.Color _kTrackOffColor = const sky.Color(0x42000000); @@ -66,47 +64,14 @@ class _SwitchWrapper extends LeafRenderObjectWrapper { } } -class _RenderSwitch extends RenderConstrainedBox { +class _RenderSwitch extends RenderToggleable { _RenderSwitch( {bool value, Color thumbColor: _kThumbOffColor, ValueChanged onChanged}) - : _value = value, - _thumbColor = thumbColor, - _onChanged = onChanged, - super(additionalConstraints: new BoxConstraints.tight(_kSwitchSize)) { - _performance = new AnimationPerformance() - ..variable = _position - ..duration = _kCheckDuration - ..progress = _value ? 1.0 : 0.0 - ..addListener(markNeedsPaint); - } - - EventDisposition handleEvent(sky.Event event, BoxHitTestEntry entry) { - if (event is sky.GestureEvent && event.type == 'gesturetap') { - _onChanged(!_value); - return EventDisposition.consumed; - } - return EventDisposition.ignored; - } - - bool _value; - bool get value => _value; - - void set value(bool value) { - if (value == _value) return; - _value = value; - // TODO(abarth): Setting the curve on the position means there's a - // discontinuity when we reverse the timeline. - if (value) { - _position.curve = easeIn; - _performance.play(); - } else { - _position.curve = easeOut; - _performance.reverse(); - } - } + : _thumbColor = thumbColor, + super(value: value, onChanged: onChanged, size: _kSwitchSize) {} Color _thumbColor; - Color get thumbColor => _thumbColor; + Color get thumbColor => _thumbColor; void set thumbColor(Color value) { if (value == _thumbColor) return; @@ -114,22 +79,10 @@ class _RenderSwitch extends RenderConstrainedBox { markNeedsPaint(); } - ValueChanged _onChanged; - ValueChanged get onChanged => _onChanged; - - void set onChanged(ValueChanged onChanged) { - _onChanged = onChanged; - } - - final AnimatedValue _position = - new AnimatedValue(0.0, end: 1.0); - - AnimationPerformance _performance; - void paint(PaintingCanvas canvas, Offset offset) { sky.Color thumbColor = _kThumbOffColor; sky.Color trackColor = _kTrackOffColor; - if (_value) { + if (value) { thumbColor = _thumbColor; trackColor = new sky.Color(_thumbColor.value & 0x80FFFFFF); } @@ -152,10 +105,10 @@ class _RenderSwitch extends RenderConstrainedBox { paint.setDrawLooper(builder.build()); // The thumb contracts slightly during the animation - double inset = 2.0 - (_position.value - 0.5).abs() * 2.0; + double inset = 2.0 - (position.value - 0.5).abs() * 2.0; Point thumbPos = new Point(offset.dx + _kTrackRadius + - _position.value * (_kTrackWidth - _kTrackRadius * 2), + position.value * (_kTrackWidth - _kTrackRadius * 2), offset.dy + _kSwitchHeight / 2.0); canvas.drawCircle(thumbPos, _kThumbRadius - inset, paint); } diff --git a/sky/packages/sky/lib/widgets/toggleable.dart b/sky/packages/sky/lib/widgets/toggleable.dart deleted file mode 100644 index 347089efd0b..00000000000 --- a/sky/packages/sky/lib/widgets/toggleable.dart +++ /dev/null @@ -1,88 +0,0 @@ -// 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:sky' as sky; - -import 'package:sky/animation/animated_value.dart'; -import 'package:sky/animation/animation_performance.dart'; -import 'package:sky/animation/curves.dart'; -import 'package:sky/widgets/animated_component.dart'; -import 'package:sky/widgets/basic.dart'; - -export 'package:sky/widgets/basic.dart' show ValueChanged; - -const Duration _kCheckDuration = const Duration(milliseconds: 200); - -abstract class Toggleable extends AnimatedComponent { - - Toggleable({ - Key key, - this.value, - this.onChanged - }) : super(key: key); - - bool value; - ValueChanged onChanged; - - AnimatedValue _position; - AnimatedValue get position => _position; - - AnimationPerformance _performance; - AnimationPerformance get performance => _performance; - - void initState() { - _position = new AnimatedValue(0.0, end: 1.0); - _performance = new AnimationPerformance() - ..variable = position - ..duration = _kCheckDuration - ..progress = value ? 1.0 : 0.0; - watch(performance); - } - - void syncFields(Toggleable source) { - onChanged = source.onChanged; - if (value != source.value) { - value = source.value; - // TODO(abarth): Setting the curve on the position means there's a - // discontinuity when we reverse the timeline. - if (value) { - position.curve = curveUp; - performance.play(); - } else { - position.curve = curveDown; - performance.reverse(); - } - } - super.syncFields(source); - } - - EventDisposition _handleClick(sky.Event e) { - onChanged(!value); - return EventDisposition.consumed; - } - - // Override these to draw yourself - void customPaintCallback(sky.Canvas canvas, Size size); - Size get size; - - EdgeDims get margin => const EdgeDims.symmetric(horizontal: 5.0); - double get duration => 200.0; - Curve get curveUp => easeIn; - Curve get curveDown => easeOut; - - Widget build() { - return new Listener( - child: new Container( - margin: margin, - width: size.width, - height: size.height, - child: new CustomPaint( - token: position.value, - callback: customPaintCallback - ) - ), - onGestureTap: _handleClick - ); - } -}