// 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 'package:flutter/foundation.dart'; import 'basic.dart'; import 'framework.dart'; const double _kOffset = 40.0; // distance to bottom of banner, at a 45 degree angle inwards const double _kHeight = 12.0; // height of banner const double _kBottomOffset = _kOffset + 0.707 * _kHeight; // offset plus sqrt(2)/2 * banner height final Rect _kRect = new Rect.fromLTWH(-_kOffset, _kOffset - _kHeight, _kOffset * 2.0, _kHeight); const Color _kColor = const Color(0xA0B71C1C); const TextStyle _kTextStyle = const TextStyle( color: const Color(0xFFFFFFFF), fontSize: _kHeight * 0.85, fontWeight: FontWeight.w900, height: 1.0 ); /// Where to show a [Banner]. enum BannerLocation { /// Show the banner in the top right corner. topRight, /// Show the banner in the top left corner. topLeft, /// Show the banner in the bottom right corner. bottomRight, /// Show the banner in the bottom left corner. bottomLeft, } /// Paints a [Banner]. class BannerPainter extends CustomPainter { /// Creates a banner painter. /// /// The [message] and [location] arguments must not be null. BannerPainter({ @required this.message, @required this.location, this.color: _kColor, this.textStyle: _kTextStyle, }) { assert(message != null); assert(location != null); assert(color != null); assert(textStyle != null); } /// The message to show in the banner. final String message; /// Where to show the banner (e.g., the upper right corder). final BannerLocation location; /// The color to paint behind the [message]. /// /// Defaults to a dark red. final Color color; /// The text style to use for the [message]. /// /// Defaults to bold, white text. final TextStyle textStyle; bool _prepared = false; TextPainter _textPainter; Paint _paintShadow; Paint _paintBanner; void _prepare() { _paintShadow = new Paint() ..color = const Color(0x7F000000) ..maskFilter = new MaskFilter.blur(BlurStyle.normal, 4.0); _paintBanner = new Paint() ..color = color; _textPainter = new TextPainter( text: new TextSpan(style: textStyle, text: message), textAlign: TextAlign.center, ); _prepared = true; } @override void paint(Canvas canvas, Size size) { if (!_prepared) _prepare(); canvas ..translate(_translationX(size.width), _translationY(size.height)) ..rotate(_rotation) ..drawRect(_kRect, _paintShadow) ..drawRect(_kRect, _paintBanner); final double width = _kOffset * 2.0; _textPainter.layout(minWidth: width, maxWidth: width); _textPainter.paint(canvas, _kRect.topLeft + new Offset(0.0, (_kRect.height - _textPainter.height) / 2.0)); } @override bool shouldRepaint(BannerPainter oldPainter) { return message != oldPainter.message || location != oldPainter.location || color != oldPainter.color || textStyle != oldPainter.textStyle; } @override bool hitTest(Offset position) => false; double _translationX(double width) { assert(location != null); switch (location) { case BannerLocation.bottomRight: return width - _kBottomOffset; case BannerLocation.topRight: return width; case BannerLocation.bottomLeft: return _kBottomOffset; case BannerLocation.topLeft: return 0.0; } return null; } double _translationY(double height) { assert(location != null); switch (location) { case BannerLocation.bottomRight: case BannerLocation.bottomLeft: return height - _kBottomOffset; case BannerLocation.topRight: case BannerLocation.topLeft: return 0.0; } return null; } double get _rotation { assert(location != null); switch (location) { case BannerLocation.bottomLeft: case BannerLocation.topRight: return math.PI / 4.0; case BannerLocation.bottomRight: case BannerLocation.topLeft: return -math.PI / 4.0; } return null; } } /// Displays a diagonal message above the corner of another widget. /// /// Useful for showing the execution mode of an app (e.g., that asserts are /// enabled.) /// /// See also: /// /// * [CheckedModeBanner]. class Banner extends StatelessWidget { /// Creates a banner. /// /// The [message] and [location] arguments must not be null. const Banner({ Key key, this.child, @required this.message, @required this.location, this.color: _kColor, this.textStyle: _kTextStyle, }) : assert(message != null), assert(location != null), assert(color != null), assert(textStyle != null), super(key: key); /// The widget to show behind the banner. final Widget child; /// The message to show in the banner. final String message; /// Where to show the banner (e.g., the upper right corder). final BannerLocation location; /// The color of the banner. final Color color; /// The style of the text shown on the banner. final TextStyle textStyle; @override Widget build(BuildContext context) { return new CustomPaint( foregroundPainter: new BannerPainter( message: message, location: location, color: color, textStyle: textStyle, ), child: child, ); } @override void debugFillDescription(List<String> description) { super.debugFillDescription(description); description.add('"$message"'); description.add('$location'); description.add('$color'); '$textStyle'.split('\n').map((String value) => 'text $value').forEach(description.add); } } /// Displays a [Banner] saying "SLOW MODE" when running in checked mode. /// [MaterialApp] builds one of these by default. /// Does nothing in release mode. class CheckedModeBanner extends StatelessWidget { /// Creates a checked mode banner. const CheckedModeBanner({ Key key, @required this.child }) : super(key: key); /// The widget to show behind the banner. final Widget child; @override Widget build(BuildContext context) { Widget result = child; assert(() { result = new Banner( child: result, message: 'SLOW MODE', location: BannerLocation.topRight); return true; }); return result; } @override void debugFillDescription(List<String> description) { super.debugFillDescription(description); String message = 'disabled'; assert(() { message = '"SLOW MODE"'; return true; }); description.add(message); } }