banner.dart 4.95 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 8
import 'package:meta/meta.dart';

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

12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
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 TextStyle _kTextStyles = 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,
}
37

38
/// Paints a [Banner].
39
class BannerPainter extends CustomPainter {
40 41
  /// Creates a banner painter.
  ///
42
  /// The [message] and [location] arguments must not be null.
43
  const BannerPainter({
44 45
    @required this.message,
    @required this.location
46 47
  });

48
  /// The message to show in the banner.
49 50
  final String message;

51 52
  /// Where to show the banner (e.g., the upper right corder).
  final BannerLocation location;
53 54 55 56 57

  @override
  void paint(Canvas canvas, Size size) {
    final Paint paintShadow = new Paint()
      ..color = const Color(0x7F000000)
58
      ..maskFilter = new MaskFilter.blur(BlurStyle.normal, 4.0);
59
    final Paint paintBanner = new Paint()
60
      ..color = const Color(0xA0B71C1C);
61 62 63
    canvas
      ..translate(_translationX(size.width), _translationY(size.height))
      ..rotate(_rotation)
64 65
      ..drawRect(_kRect, paintShadow)
      ..drawRect(_kRect, paintBanner);
66

67
    final double width = _kOffset * 2.0;
68 69 70 71
    final TextPainter textPainter = new TextPainter(
      text: new TextSpan(style: _kTextStyles, text: message),
      textAlign: TextAlign.center
    )..layout(minWidth: width, maxWidth: width);
72

73
    textPainter.paint(canvas, _kRect.topLeft.toOffset() + new Offset(0.0, (_kRect.height - textPainter.height) / 2.0));
74 75 76 77 78 79 80 81 82 83 84
  }

  @override
  bool shouldRepaint(BannerPainter oldPainter) => false;

  @override
  bool hitTest(Point position) => false;

  double _translationX(double width) {
    switch (location) {
      case BannerLocation.bottomRight:
85
        return width - _kBottomOffset;
86 87 88
      case BannerLocation.topRight:
        return width;
      case BannerLocation.bottomLeft:
89
        return _kBottomOffset;
90 91 92
      case BannerLocation.topLeft:
        return 0.0;
    }
pq's avatar
pq committed
93 94
    assert(location != null);
    return null;
95 96 97 98 99 100
  }

  double _translationY(double height) {
    switch (location) {
      case BannerLocation.bottomRight:
      case BannerLocation.bottomLeft:
101
        return height - _kBottomOffset;
102 103 104 105
      case BannerLocation.topRight:
      case BannerLocation.topLeft:
        return 0.0;
    }
pq's avatar
pq committed
106 107
    assert(location != null);
    return null;
108 109 110 111 112 113 114 115 116 117 118
  }

  double get _rotation {
    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
119
    assert(location != null);
pq's avatar
pq committed
120
    return null;
121 122 123
  }
}

124 125 126 127 128 129 130
/// 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:
///
131
///  * [CheckedModeBanner].
132
class Banner extends StatelessWidget {
133 134
  /// Creates a banner.
  ///
135
  /// The [message] and [location] arguments must not be null.
136 137 138 139 140
  Banner({
    Key key,
    this.child,
    this.message,
    this.location
141 142 143 144
  }) : super(key: key) {
    assert(message != null);
    assert(location != null);
  }
145

146
  /// The widget to show behind the banner.
147
  final Widget child;
148 149

  /// The message to show in the banner.
150
  final String message;
151 152

  /// Where to show the banner (e.g., the upper right corder).
153 154 155 156 157 158 159 160 161 162 163
  final BannerLocation location;

  @override
  Widget build(BuildContext context) {
    return new CustomPaint(
      foregroundPainter: new BannerPainter(message: message, location: location),
      child: child
    );
  }
}

164
/// Displays a [Banner] saying "SLOW MODE" when running in checked mode.
165
/// [MaterialApp] builds one of these by default.
166 167
/// Does nothing in release mode.
class CheckedModeBanner extends StatelessWidget {
168
  /// Creates a checked mode banner.
169 170 171 172 173
  CheckedModeBanner({
    Key key,
    this.child
  }) : super(key: key);

174
  /// The widget to show behind the banner.
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189
  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;
  }
}