blob: ac038ac538b7add470ae33cd5ecbfdd1b96afb64 [file] [log] [blame]
// Copyright 2020 The Fuchsia 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:async';
import 'dart:math';
import 'package:flutter/material.dart';
const backgroundColor = Color(0xFFEBD4B3);
const hourHandColor = Color(0xFFFD4763);
const minuteHandColor = Color(0x7FFF7284);
const secondHandColor = Colors.white;
const shadowColor = Color(0x0D000000);
const radius = 0.4;
const thickness = radius / 20.0;
const offset = radius / 5.0;
const elevation = 0.01;
void main() => runApp(ClockFace());
class ClockFace extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return ClockFaceState();
}
}
class ClockFaceState extends State<ClockFace> {
DateTime _now;
Timer _timer;
@override
void initState() {
super.initState();
_now = DateTime.now().toUtc();
_timer = Timer.periodic(const Duration(milliseconds: 16), _setNow);
}
void _setNow(Timer _timer) {
setState(() {
_now = DateTime.now().toUtc();
});
}
@override
void dispose() {
_timer.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return CustomPaint(
painter: ClockPainter(_now),
child: Container(),
);
}
}
class Hand {
RRect _hand;
RRect _shadow;
final Color _color;
final Point _pos;
final double _angle;
Hand(this._pos, double thickness, double length, double offset, this._color,
double shadowOffset, this._angle) {
_hand = RRect.fromRectAndRadius(
Rect.fromLTWH(_pos.x - (thickness / 2.0 + offset),
_pos.y - thickness / 2.0, length, thickness),
Radius.circular(thickness / 2.0));
_shadow = RRect.fromRectAndRadius(
_hand.outerRect.translate(shadowOffset, shadowOffset * 2.0),
_hand.tlRadius);
}
void paintHand(Canvas canvas) {
canvas
..save()
..translate(_pos.x, _pos.y)
..rotate(_angle)
..translate(-_pos.x, -_pos.y)
..drawRRect(_hand, Paint()..color = _color)
..restore();
}
Path shadowPath() {
var path = Path()..addRRect(_shadow);
var matrix = Matrix4.identity()
..translate(_pos.x, _pos.y)
..rotateZ(_angle)
..translate(-_pos.x, -_pos.y);
return path.transform(matrix.storage);
}
}
class ClockPainter extends CustomPainter {
final DateTime _now;
ClockPainter(this._now);
double ratioToAngle(double ratio, double total) {
const rewind = -0.25; // account for 3 to 12 o'clock rewinding
return ((rewind + ratio / total) % 1.0) * 2.0 * pi;
}
@override
void paint(Canvas canvas, Size size) {
var background = Offset.zero & size;
canvas.drawRect(background, Paint()..color = backgroundColor);
var scale = min(size.width, size.height);
var center = Point(size.width / 2.0, size.height / 2.0);
var second =
(_now.second + 1).toDouble() + _now.millisecond.toDouble() / 1e+3;
var minute = (_now.minute + 1).toDouble() + second / 60.0;
var hour = (_now.hour % 12 + 1).toDouble() + minute / 60.0;
var hourAngle = ratioToAngle(hour, 12.0);
var minuteAngle = ratioToAngle(minute, 60.0);
var secondAngle = ratioToAngle(second, 60.0);
var secondHand = Hand(
center,
scale * thickness / 2.0,
scale * (radius + offset),
scale * offset,
secondHandColor,
scale * elevation,
secondAngle);
var minuteHand = Hand(center, scale * thickness, scale * radius, 0.0,
minuteHandColor, scale * elevation, minuteAngle);
var hourHand = Hand(center, scale * thickness * 2.0, scale * radius,
scale * offset, hourHandColor, scale * elevation, hourAngle);
var shadowHourSecond = Path.combine(
PathOperation.union, hourHand.shadowPath(), secondHand.shadowPath());
var shadowAll = Path.combine(
PathOperation.union, shadowHourSecond, minuteHand.shadowPath());
canvas
..drawPath(shadowHourSecond, Paint()..color = shadowColor)
..drawPath(shadowAll, Paint()..color = shadowColor);
secondHand.paintHand(canvas);
minuteHand.paintHand(canvas);
hourHand.paintHand(canvas);
}
@override
bool shouldRepaint(ClockPainter oldDelegate) => true;
}