From 8758e72da575a2d2b5ae1ebbd367cbeb6529f337 Mon Sep 17 00:00:00 2001 From: Collin Jackson Date: Mon, 15 Jun 2015 12:53:44 -0700 Subject: [PATCH] Really basic Switch implementation in Sky. Could definitely use some polish R=abarth@chromium.org, abarth Review URL: https://codereview.chromium.org/1185173002. --- sdk/BUILD.gn | 2 + sdk/lib/widgets/checkbox.dart | 163 +++++++++++--------------------- sdk/lib/widgets/switch.dart | 69 ++++++++++++++ sdk/lib/widgets/toggleable.dart | 74 +++++++++++++++ 4 files changed, 201 insertions(+), 107 deletions(-) create mode 100644 sdk/lib/widgets/switch.dart create mode 100644 sdk/lib/widgets/toggleable.dart diff --git a/sdk/BUILD.gn b/sdk/BUILD.gn index 54927b0d03d..da3579ebbc7 100644 --- a/sdk/BUILD.gn +++ b/sdk/BUILD.gn @@ -120,6 +120,8 @@ dart_pkg("sdk") { "lib/widgets/raised_button.dart", "lib/widgets/scaffold.dart", "lib/widgets/scrollable.dart", + "lib/widgets/switch.dart", + "lib/widgets/toggleable.dart", "lib/widgets/tool_bar.dart", "lib/widgets/ui_node.dart", "pubspec.yaml", diff --git a/sdk/lib/widgets/checkbox.dart b/sdk/lib/widgets/checkbox.dart index 9f709c0241e..54a49bf9c1a 100644 --- a/sdk/lib/widgets/checkbox.dart +++ b/sdk/lib/widgets/checkbox.dart @@ -6,130 +6,79 @@ import 'dart:sky' as sky; import 'package:sky/theme2/colors.dart' as colors; -import '../framework/animation/animated_value.dart'; -import '../framework/animation/curves.dart'; -import '../rendering/box.dart'; -import 'animated_component.dart'; import 'basic.dart'; - -typedef void ValueChanged(value); +import 'toggleable.dart'; const double _kMidpoint = 0.5; -const double _kCheckDuration = 200.0; const sky.Color _kUncheckedColor = const sky.Color(0x8A000000); // TODO(jackson): This should change colors with the theme sky.Color _kCheckedColor = colors.Purple[500]; const double _kEdgeSize = 20.0; const double _kEdgeRadius = 1.0; -class Checkbox extends AnimatedComponent { +class Checkbox extends Toggleable { Checkbox({ Object key, - this.checked, - this.onChanged - }) : super(key: key) { - _checkedAnimation = new AnimatedValue(checked ? 1.0 : 0.0); - } + bool value, + ValueChanged onChanged + }) : super(key: key, value: value, onChanged: onChanged); - bool checked; - AnimatedValue _checkedAnimation; - ValueChanged onChanged; + Size get size => const Size(_kEdgeSize + 2.0, _kEdgeSize + 2.0); - void syncFields(Checkbox source) { - onChanged = source.onChanged; - if (checked != source.checked) { - checked = source.checked; - double targetValue = checked ? 1.0 : 0.0; - double difference = (_checkedAnimation.value - targetValue).abs(); - if (difference > 0) { - double duration = difference * _kCheckDuration; - _checkedAnimation.stop(); - Curve curve; - if (targetValue > _checkedAnimation.value) { - curve = easeIn; - } else { - curve = easeOut; - } - _checkedAnimation.animateTo(targetValue, duration, curve: curve); - } - } - super.syncFields(source); - } + void customPaintCallback(sky.Canvas canvas, Size size) { + // Choose a color between grey and the theme color + sky.Paint paint = new sky.Paint()..strokeWidth = 2.0 + ..color = _kUncheckedColor; - void _handleClick(sky.Event e) { - onChanged(!checked); - } + // The rrect contracts slightly during the animation + double inset = 2.0 - (toggleAnimation.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); - UINode build() { - return new EventListenerNode( - new Container( - margin: const EdgeDims.symmetric(horizontal: 5.0), - width: _kEdgeSize + 2.0, - height: _kEdgeSize + 2.0, - child: new CustomPaint( - token: _checkedAnimation.value, - callback: (sky.Canvas canvas, Size size) { - // Choose a color between grey and the theme color - sky.Paint paint = new sky.Paint()..strokeWidth = 2.0 - ..color = _kUncheckedColor; + // Outline of the empty rrect + paint.setStyle(sky.PaintingStyle.stroke); + canvas.drawRRect(rrect, paint); - // The rrect contracts slightly during the animation - double inset = 2.0 - (_checkedAnimation.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); - - - // Outline of the empty rrect - paint.setStyle(sky.PaintingStyle.stroke); - canvas.drawRRect(rrect, paint); - - // Radial gradient that changes size - if (_checkedAnimation.value > 0) { - paint.setStyle(sky.PaintingStyle.fill); - paint.setShader( - new sky.Gradient.radial( - new Point(_kEdgeSize / 2.0, _kEdgeSize / 2.0), - _kEdgeSize * (_kMidpoint - _checkedAnimation.value) * 8.0, - [const sky.Color(0x00000000), _kUncheckedColor], - [0.0, 1.0] - ) - ); - canvas.drawRRect(rrect, paint); - } - - if (_checkedAnimation.value > _kMidpoint) { - double t = (_checkedAnimation.value - _kMidpoint) / (1.0 - _kMidpoint); - - // Solid filled rrect - paint.setStyle(sky.PaintingStyle.strokeAndFill); - paint.color = new Color.fromARGB((t * 255).floor(), - _kCheckedColor.red, - _kCheckedColor.green, - _kCheckedColor.blue); - canvas.drawRRect(rrect, paint); - - // White inner check - paint.color = const sky.Color(0xFFFFFFFF); - paint.setStyle(sky.PaintingStyle.stroke); - sky.Path path = new sky.Path(); - 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); - 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); - canvas.drawPath(path, paint); - } - } + // Radial gradient that changes size + if (toggleAnimation.value > 0) { + paint.setStyle(sky.PaintingStyle.fill); + paint.setShader( + new sky.Gradient.radial( + new Point(_kEdgeSize / 2.0, _kEdgeSize / 2.0), + _kEdgeSize * (_kMidpoint - toggleAnimation.value) * 8.0, + [const sky.Color(0x00000000), _kUncheckedColor] ) - ), - onGestureTap: _handleClick - ); - } + ); + canvas.drawRRect(rrect, paint); + } + if (toggleAnimation.value > _kMidpoint) { + double t = (toggleAnimation.value - _kMidpoint) / (1.0 - _kMidpoint); + + // Solid filled rrect + paint.setStyle(sky.PaintingStyle.strokeAndFill); + paint.color = new Color.fromARGB((t * 255).floor(), + _kCheckedColor.red, + _kCheckedColor.green, + _kCheckedColor.blue); + canvas.drawRRect(rrect, paint); + + // White inner check + paint.color = const sky.Color(0xFFFFFFFF); + paint.setStyle(sky.PaintingStyle.stroke); + sky.Path path = new sky.Path(); + 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); + 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); + canvas.drawPath(path, paint); + } + } } diff --git a/sdk/lib/widgets/switch.dart b/sdk/lib/widgets/switch.dart new file mode 100644 index 00000000000..39b2979707a --- /dev/null +++ b/sdk/lib/widgets/switch.dart @@ -0,0 +1,69 @@ +// 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/theme2/colors.dart' as colors; +import 'package:sky/theme2/shadows.dart'; + +import '../painting/shadows.dart'; +import '../rendering/box.dart'; +import 'basic.dart'; +import 'toggleable.dart'; + +// TODO(jackson): This should change colors with the theme +sky.Color _kThumbOnColor = colors.Purple[500]; +const sky.Color _kThumbOffColor = const sky.Color(0xFFFAFAFA); +sky.Color _kTrackOnColor = new sky.Color(_kThumbOnColor.value & (0x80 << 24)); +const sky.Color _kTrackOffColor = const sky.Color(0x42000000); +const double _kSwitchWidth = 35.0; +const double _kThumbRadius = 10.0; +const double _kSwitchHeight = _kThumbRadius * 2.0; +const double _kTrackHeight = 14.0; +const double _kTrackRadius = _kTrackHeight / 2.0; +const double _kTrackWidth = _kSwitchWidth - (_kThumbRadius - _kTrackRadius) * 2.0; + +class Switch extends Toggleable { + // TODO(jackson): Hit-test the switch so that it can respond to both taps and swipe gestures + + Switch({ + Object key, + bool value, + ValueChanged onChanged + }) : super(key: key, value: value, onChanged: onChanged); + + Size get size => const Size(_kSwitchWidth + 2.0, _kSwitchHeight + 2.0); + + void customPaintCallback(sky.Canvas canvas, Size size) { + sky.Color thumbColor = value ? _kThumbOnColor : _kThumbOffColor; + sky.Color trackColor = value ? _kTrackOnColor : _kTrackOffColor; + + // Draw the track rrect + sky.Paint paint = new sky.Paint()..color = trackColor; + paint.setStyle(sky.PaintingStyle.fill); + sky.Rect rect = new sky.Rect.fromLTRB( + 0.0, + _kSwitchHeight / 2.0 - _kTrackHeight / 2.0, + _kTrackWidth, + _kSwitchHeight / 2.0 + _kTrackHeight / 2.0 + ); + sky.RRect rrect = new sky.RRect()..setRectXY(rect, _kTrackRadius, _kTrackRadius); + canvas.drawRRect(rrect, paint); + + // Draw the raised thumb with a shadow + paint.color = thumbColor; + var builder = new ShadowDrawLooperBuilder(); + for (BoxShadow boxShadow in shadows[1]) + builder.addShadow(boxShadow.offset, boxShadow.color, boxShadow.blur); + paint.setDrawLooper(builder.build()); + + // The thumb contracts slightly during the animation + double inset = 2.0 - (toggleAnimation.value - 0.5).abs() * 2.0; + Point thumbPos = new Point( + _kTrackRadius + toggleAnimation.value * (_kTrackWidth - _kTrackRadius * 2), + _kSwitchHeight / 2.0 + ); + canvas.drawCircle(thumbPos.x, thumbPos.y, _kThumbRadius - inset, paint); + } +} diff --git a/sdk/lib/widgets/toggleable.dart b/sdk/lib/widgets/toggleable.dart new file mode 100644 index 00000000000..621804c29d6 --- /dev/null +++ b/sdk/lib/widgets/toggleable.dart @@ -0,0 +1,74 @@ +// 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 'animated_component.dart'; +import 'basic.dart'; +import '../framework/animation/animated_value.dart'; +import '../framework/animation/curves.dart'; + +typedef void ValueChanged(value); + +const double _kCheckDuration = 200.0; + +abstract class Toggleable extends AnimatedComponent { + + Toggleable({ + Object key, + this.value, + this.onChanged + }) : super(key: key) { + toggleAnimation = new AnimatedValue(value ? 1.0 : 0.0); + } + + bool value; + AnimatedValue toggleAnimation; + ValueChanged onChanged; + + void syncFields(Toggleable source) { + onChanged = source.onChanged; + if (value != source.value) { + value = source.value; + double targetValue = value ? 1.0 : 0.0; + double difference = (toggleAnimation.value - targetValue).abs(); + if (difference > 0) { + toggleAnimation.stop(); + double t = difference * duration; + Curve curve = targetValue > toggleAnimation.value ? curveUp : curveDown; + toggleAnimation.animateTo(targetValue, t, curve: curve); + } + } + super.syncFields(source); + } + + void _handleClick(sky.Event e) { + onChanged(!value); + } + + // Override these methods to draw yourself + void customPaintCallback(sky.Canvas canvas, Size size) { + assert(false); + } + Size get size => const Size.zero; + EdgeDims get margin => const EdgeDims.symmetric(horizontal: 5.0); + double get duration => 200.0; + Curve get curveUp => easeIn; + Curve get curveDown => easeOut; + + UINode build() { + return new EventListenerNode( + new Container( + margin: margin, + width: size.width, + height: size.height, + child: new CustomPaint( + token: toggleAnimation.value, + callback: customPaintCallback + ) + ), + onGestureTap: _handleClick + ); + } +}