// 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/rendering/object.dart'; import 'package:sky/widgets/basic.dart'; import 'package:sky/widgets/theme.dart'; import 'package:sky/widgets/framework.dart'; import 'package:sky/rendering/toggleable.dart'; 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 /// /// The checkbox itself does not maintain any state. Instead, when the state of /// the checkbox changes, the component calls the `onChange` callback. Most /// components that use a checkbox will listen for the `onChange` callback and /// rebuild the checkbox with a new `value` to update the visual appearance of /// the checkbox. /// /// <https://www.google.com/design/spec/components/lists-controls.html#lists-controls-types-of-list-controls> 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, this.value, this.onChanged}) : super(key: key); final bool value; final ValueChanged onChanged; Widget build() { ThemeData themeData = Theme.of(this); 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 renderObject => super.renderObject; _RenderCheckbox createNode() => new _RenderCheckbox( value: value, uncheckedColor: uncheckedColor, onChanged: onChanged); void syncRenderObject(_CheckboxWrapper old) { super.syncRenderObject(old); renderObject.value = value; renderObject.onChanged = onChanged; renderObject.uncheckedColor = uncheckedColor; renderObject.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(PaintingContext context, Offset offset) { final PaintingCanvas canvas = context.canvas; // Choose a color between grey and the theme color 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.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); canvas.drawRRect(rrect, paint); // Radial gradient that changes size if (position.value > 0) { paint.setStyle(sky.PaintingStyle.fill); 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 ])); canvas.drawRRect(rrect, paint); } if (position.value > _kMidpoint) { double t = (position.value - _kMidpoint) / (1.0 - _kMidpoint); // Solid filled rrect paint.setStyle(sky.PaintingStyle.strokeAndFill); paint.color = new Color.fromARGB((t * 255).floor(), _accentColor.red, _accentColor.green, _accentColor.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(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); } } }