| // 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 'debug.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]. |
| /// |
| /// The start and end locations are relative to the ambient [Directionality] |
| /// (which can be overridden by [Banner.layoutDirection]). |
| enum BannerLocation { |
| /// Show the banner in the top-right corner when the ambient [Directionality] |
| /// (or [Banner.layoutDirection]) is [TextDirection.rtl] and in the top-left |
| /// corner when the ambient [Directionality] is [TextDirection.ltr]. |
| topStart, |
| |
| /// Show the banner in the top-left corner when the ambient [Directionality] |
| /// (or [Banner.layoutDirection]) is [TextDirection.rtl] and in the top-right |
| /// corner when the ambient [Directionality] is [TextDirection.ltr]. |
| topEnd, |
| |
| /// Show the banner in the bottom-right corner when the ambient |
| /// [Directionality] (or [Banner.layoutDirection]) is [TextDirection.rtl] and |
| /// in the bottom-left corner when the ambient [Directionality] is |
| /// [TextDirection.ltr]. |
| bottomStart, |
| |
| /// Show the banner in the bottom-left corner when the ambient |
| /// [Directionality] (or [Banner.layoutDirection]) is [TextDirection.rtl] and |
| /// in the bottom-right corner when the ambient [Directionality] is |
| /// [TextDirection.ltr]. |
| bottomEnd, |
| } |
| |
| /// Paints a [Banner]. |
| class BannerPainter extends CustomPainter { |
| /// Creates a banner painter. |
| /// |
| /// The [message], [textDirection], [location], and [layoutDirection] |
| /// arguments must not be null. |
| BannerPainter({ |
| @required this.message, |
| @required this.textDirection, |
| @required this.location, |
| @required this.layoutDirection, |
| this.color: _kColor, |
| this.textStyle: _kTextStyle, |
| }) : assert(message != null), |
| assert(textDirection != null), |
| assert(location != null), |
| assert(color != null), |
| assert(textStyle != null); |
| |
| /// The message to show in the banner. |
| final String message; |
| |
| /// The directionality of the text. |
| /// |
| /// This value is used to disambiguate how to render bidirectional text. For |
| /// example, if the message is an English phrase followed by a Hebrew phrase, |
| /// in a [TextDirection.ltr] context the English phrase will be on the left |
| /// and the Hebrew phrase to its right, while in a [TextDirection.rtl] |
| /// context, the English phrase will be on the right and the Hebrew phrase on |
| /// its left. |
| /// |
| /// See also [layoutDirection], which controls the interpretation of values in |
| /// [location]. |
| final TextDirection textDirection; |
| |
| /// Where to show the banner (e.g., the upper right corder). |
| final BannerLocation location; |
| |
| /// The directionality of the layout. |
| /// |
| /// This value is used to interpret the [location] of the banner. |
| /// |
| /// See also [textDirection], which controls the reading direction of the |
| /// [message]. |
| final TextDirection layoutDirection; |
| |
| /// 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 = const MaskFilter.blur(BlurStyle.normal, 4.0); |
| _paintBanner = new Paint() |
| ..color = color; |
| _textPainter = new TextPainter( |
| text: new TextSpan(style: textStyle, text: message), |
| textAlign: TextAlign.center, |
| textDirection: textDirection, |
| ); |
| _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); |
| const 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 oldDelegate) { |
| return message != oldDelegate.message |
| || location != oldDelegate.location |
| || color != oldDelegate.color |
| || textStyle != oldDelegate.textStyle; |
| } |
| |
| @override |
| bool hitTest(Offset position) => false; |
| |
| double _translationX(double width) { |
| assert(location != null); |
| assert(layoutDirection != null); |
| switch (layoutDirection) { |
| case TextDirection.rtl: |
| switch (location) { |
| case BannerLocation.bottomEnd: |
| return _kBottomOffset; |
| case BannerLocation.topEnd: |
| return 0.0; |
| case BannerLocation.bottomStart: |
| return width - _kBottomOffset; |
| case BannerLocation.topStart: |
| return width; |
| } |
| break; |
| case TextDirection.ltr: |
| switch (location) { |
| case BannerLocation.bottomEnd: |
| return width - _kBottomOffset; |
| case BannerLocation.topEnd: |
| return width; |
| case BannerLocation.bottomStart: |
| return _kBottomOffset; |
| case BannerLocation.topStart: |
| return 0.0; |
| } |
| break; |
| } |
| return null; |
| } |
| |
| double _translationY(double height) { |
| assert(location != null); |
| switch (location) { |
| case BannerLocation.bottomStart: |
| case BannerLocation.bottomEnd: |
| return height - _kBottomOffset; |
| case BannerLocation.topStart: |
| case BannerLocation.topEnd: |
| return 0.0; |
| } |
| return null; |
| } |
| |
| double get _rotation { |
| assert(location != null); |
| assert(layoutDirection != null); |
| switch (layoutDirection) { |
| case TextDirection.rtl: |
| switch (location) { |
| case BannerLocation.bottomStart: |
| case BannerLocation.topEnd: |
| return -math.pi / 4.0; |
| case BannerLocation.bottomEnd: |
| case BannerLocation.topStart: |
| return math.pi / 4.0; |
| } |
| break; |
| case TextDirection.ltr: |
| switch (location) { |
| case BannerLocation.bottomStart: |
| case BannerLocation.topEnd: |
| return math.pi / 4.0; |
| case BannerLocation.bottomEnd: |
| case BannerLocation.topStart: |
| return -math.pi / 4.0; |
| } |
| break; |
| } |
| 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], which the [WidgetsApp] widget includes by default in |
| /// debug mode, to show a banner that says "DEBUG". |
| 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, |
| this.textDirection, |
| @required this.location, |
| this.layoutDirection, |
| 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. |
| /// |
| /// {@macro flutter.widgets.child} |
| final Widget child; |
| |
| /// The message to show in the banner. |
| final String message; |
| |
| /// The directionality of the text. |
| /// |
| /// This is used to disambiguate how to render bidirectional text. For |
| /// example, if the message is an English phrase followed by a Hebrew phrase, |
| /// in a [TextDirection.ltr] context the English phrase will be on the left |
| /// and the Hebrew phrase to its right, while in a [TextDirection.rtl] |
| /// context, the English phrase will be on the right and the Hebrew phrase on |
| /// its left. |
| /// |
| /// Defaults to the ambient [Directionality], if any. |
| /// |
| /// See also [layoutDirection], which controls the interpretation of the |
| /// [location]. |
| final TextDirection textDirection; |
| |
| /// Where to show the banner (e.g., the upper right corder). |
| final BannerLocation location; |
| |
| /// The directionality of the layout. |
| /// |
| /// This is used to resolve the [location] values. |
| /// |
| /// Defaults to the ambient [Directionality], if any. |
| /// |
| /// See also [textDirection], which controls the reading direction of the |
| /// [message]. |
| final TextDirection layoutDirection; |
| |
| /// 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) { |
| assert((textDirection != null && layoutDirection != null) || debugCheckHasDirectionality(context)); |
| return new CustomPaint( |
| foregroundPainter: new BannerPainter( |
| message: message, |
| textDirection: textDirection ?? Directionality.of(context), |
| location: location, |
| layoutDirection: layoutDirection ?? Directionality.of(context), |
| color: color, |
| textStyle: textStyle, |
| ), |
| child: child, |
| ); |
| } |
| |
| @override |
| void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
| super.debugFillProperties(properties); |
| properties.add(new StringProperty('message', message, showName: false)); |
| properties.add(new EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null)); |
| properties.add(new EnumProperty<BannerLocation>('location', location)); |
| properties.add(new EnumProperty<TextDirection>('layoutDirection', layoutDirection, defaultValue: null)); |
| properties.add(new DiagnosticsProperty<Color>('color', color, showName: false)); |
| textStyle?.debugFillProperties(properties, prefix: 'text '); |
| } |
| } |
| |
| /// Displays a [Banner] saying "DEBUG" 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. |
| /// |
| /// {@macro flutter.widgets.child} |
| final Widget child; |
| |
| @override |
| Widget build(BuildContext context) { |
| Widget result = child; |
| assert(() { |
| result = new Banner( |
| child: result, |
| message: 'DEBUG', |
| textDirection: TextDirection.ltr, |
| location: BannerLocation.topEnd, |
| ); |
| return true; |
| }()); |
| return result; |
| } |
| |
| @override |
| void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
| super.debugFillProperties(properties); |
| String message = 'disabled'; |
| assert(() { |
| message = '"DEBUG"'; |
| return true; |
| }()); |
| properties.add(new DiagnosticsNode.message(message)); |
| } |
| } |