banner.dart 6.06 KB
Newer Older
1 2 3 4 5 6
// 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;

7
import 'package:flutter/foundation.dart';
8

9 10 11
import 'basic.dart';
import 'framework.dart';

12 13 14 15
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);
16 17 18

const Color _kColor = const Color(0xA0B71C1C);
const TextStyle _kTextStyle = const TextStyle(
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
  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,
}
39

40
/// Paints a [Banner].
41
class BannerPainter extends CustomPainter {
42 43
  /// Creates a banner painter.
  ///
44
  /// The [message] and [location] arguments must not be null.
45
  BannerPainter({
46
    @required this.message,
47 48 49 50 51 52 53 54 55
    @required this.location,
    this.color: _kColor,
    this.textStyle: _kTextStyle,
  }) {
    assert(message != null);
    assert(location != null);
    assert(color != null);
    assert(textStyle != null);
  }
56

57
  /// The message to show in the banner.
58 59
  final String message;

60 61
  /// Where to show the banner (e.g., the upper right corder).
  final BannerLocation location;
62

63 64 65
  /// The color to paint behind the [message].
  ///
  /// Defaults to a dark red.
66 67
  final Color color;

68 69 70
  /// The text style to use for the [message].
  ///
  /// Defaults to bold, white text.
71 72 73 74 75 76 77 78 79
  final TextStyle textStyle;

  bool _prepared = false;
  TextPainter _textPainter;
  Paint _paintShadow;
  Paint _paintBanner;

  void _prepare() {
    _paintShadow = new Paint()
80
      ..color = const Color(0x7F000000)
81
      ..maskFilter = new MaskFilter.blur(BlurStyle.normal, 4.0);
82 83 84 85 86 87 88 89 90 91 92 93 94
    _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();
95 96 97
    canvas
      ..translate(_translationX(size.width), _translationY(size.height))
      ..rotate(_rotation)
98 99
      ..drawRect(_kRect, _paintShadow)
      ..drawRect(_kRect, _paintBanner);
100
    final double width = _kOffset * 2.0;
101
    _textPainter.layout(minWidth: width, maxWidth: width);
102
    _textPainter.paint(canvas, _kRect.topLeft + new Offset(0.0, (_kRect.height - _textPainter.height) / 2.0));
103 104 105
  }

  @override
106 107 108 109 110 111
  bool shouldRepaint(BannerPainter oldPainter) {
    return message != oldPainter.message
        || location != oldPainter.location
        || color != oldPainter.color
        || textStyle != oldPainter.textStyle;
  }
112 113

  @override
114
  bool hitTest(Offset position) => false;
115 116

  double _translationX(double width) {
117
    assert(location != null);
118 119
    switch (location) {
      case BannerLocation.bottomRight:
120
        return width - _kBottomOffset;
121 122 123
      case BannerLocation.topRight:
        return width;
      case BannerLocation.bottomLeft:
124
        return _kBottomOffset;
125 126 127
      case BannerLocation.topLeft:
        return 0.0;
    }
pq's avatar
pq committed
128
    return null;
129 130 131
  }

  double _translationY(double height) {
132
    assert(location != null);
133 134 135
    switch (location) {
      case BannerLocation.bottomRight:
      case BannerLocation.bottomLeft:
136
        return height - _kBottomOffset;
137 138 139 140
      case BannerLocation.topRight:
      case BannerLocation.topLeft:
        return 0.0;
    }
pq's avatar
pq committed
141
    return null;
142 143 144
  }

  double get _rotation {
145
    assert(location != null);
146 147 148 149 150 151 152 153
    switch (location) {
      case BannerLocation.bottomLeft:
      case BannerLocation.topRight:
        return math.PI / 4.0;
      case BannerLocation.bottomRight:
      case BannerLocation.topLeft:
        return -math.PI / 4.0;
    }
pq's avatar
pq committed
154
    return null;
155 156 157
  }
}

158 159 160 161 162 163 164
/// 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:
///
165
///  * [CheckedModeBanner].
166
class Banner extends StatelessWidget {
167 168
  /// Creates a banner.
  ///
169
  /// The [message] and [location] arguments must not be null.
170
  const Banner({
171 172
    Key key,
    this.child,
173 174 175 176
    @required this.message,
    @required this.location,
    this.color: _kColor,
    this.textStyle: _kTextStyle,
177 178 179 180 181
  }) : assert(message != null),
       assert(location != null),
       assert(color != null),
       assert(textStyle != null),
       super(key: key);
182

183
  /// The widget to show behind the banner.
184
  final Widget child;
185 186

  /// The message to show in the banner.
187
  final String message;
188 189

  /// Where to show the banner (e.g., the upper right corder).
190 191
  final BannerLocation location;

192 193 194 195 196 197
  /// The color of the banner.
  final Color color;

  /// The style of the text shown on the banner.
  final TextStyle textStyle;

198 199 200
  @override
  Widget build(BuildContext context) {
    return new CustomPaint(
201 202 203 204 205 206 207
      foregroundPainter: new BannerPainter(
        message: message,
        location: location,
        color: color,
        textStyle: textStyle,
      ),
      child: child,
208 209 210 211
    );
  }
}

212
/// Displays a [Banner] saying "SLOW MODE" when running in checked mode.
213
/// [MaterialApp] builds one of these by default.
214 215
/// Does nothing in release mode.
class CheckedModeBanner extends StatelessWidget {
216
  /// Creates a checked mode banner.
217
  const CheckedModeBanner({
218
    Key key,
219
    @required this.child
220 221
  }) : super(key: key);

222
  /// The widget to show behind the banner.
223 224 225 226 227 228 229 230 231 232 233 234 235 236 237
  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;
  }
}