LCOV - code coverage report
Current view: top level - button - mongol_text_button.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 61 65 93.8 %
Date: 2021-08-02 17:55:49 Functions: 0 0 -

          Line data    Source code
       1             : // Copyright 2014 The Flutter Authors.
       2             : // Copyright 2021 Suragch.
       3             : // All rights reserved.
       4             : // Use of this source code is governed by a BSD-style license that can be
       5             : // found in the LICENSE file.
       6             : 
       7             : import 'dart:math' as math;
       8             : import 'dart:ui' show lerpDouble;
       9             : 
      10             : import 'package:flutter/foundation.dart';
      11             : import 'package:flutter/material.dart'
      12             :     show
      13             :         ButtonStyle,
      14             :         ButtonStyleButton,
      15             :         ColorScheme,
      16             :         Colors,
      17             :         InkRipple,
      18             :         InteractiveInkFeatureFactory,
      19             :         MaterialStateProperty,
      20             :         MaterialTapTargetSize,
      21             :         MaterialState,
      22             :         TextButtonTheme,
      23             :         Theme,
      24             :         ThemeData,
      25             :         VisualDensity,
      26             :         kThemeChangeDuration;
      27             : import 'package:flutter/widgets.dart';
      28             : 
      29             : /// A vertical Material Design "Text Button".
      30             : ///
      31             : /// Use text buttons on toolbars, in dialogs, or inline with other
      32             : /// content but offset from that content with padding so that the
      33             : /// button's presence is obvious. Text buttons do not have visible
      34             : /// borders and must therefore rely on their position relative to
      35             : /// other content for context. In dialogs and cards, they should be
      36             : /// grouped together in one of the bottom corners. Avoid using text
      37             : /// buttons where they would blend in with other content, for example
      38             : /// in the middle of lists.
      39             : ///
      40             : /// A text button is a label [child] displayed on a (zero elevation)
      41             : /// [Material] widget. The label's [MongolText] and [Icon] widgets are
      42             : /// displayed in the [style]'s [ButtonStyle.foregroundColor]. The
      43             : /// button reacts to touches by filling with the [style]'s
      44             : /// [ButtonStyle.backgroundColor].
      45             : ///
      46             : /// The text button's default style is defined by [defaultStyleOf].
      47             : /// The style of this text button can be overridden with its [style]
      48             : /// parameter. The style of all text buttons in a subtree can be
      49             : /// overridden with the [TextButtonTheme] and the style of all of the
      50             : /// text buttons in an app can be overridden with the [Theme]'s
      51             : /// [ThemeData.textButtonTheme] property.
      52             : ///
      53             : /// The static [styleFrom] method is a convenient way to create a
      54             : /// text button [ButtonStyle] from simple values.
      55             : ///
      56             : /// If the [onPressed] and [onLongPress] callbacks are null, then this
      57             : /// button will be disabled, it will not react to touch.
      58             : ///
      59             : /// {@tool dartpad --template=stateless_widget_scaffold}
      60             : ///
      61             : /// This sample shows how to render a disabled TextButton, an enabled MongolTextButton
      62             : /// and lastly a MongolTextButton with gradient background.
      63             : ///
      64             : /// ```dart
      65             : /// Widget build(BuildContext context) {
      66             : ///   return Center(
      67             : ///     child: Column(
      68             : ///       mainAxisSize: MainAxisSize.min,
      69             : ///       children: <Widget>[
      70             : ///         MongolTextButton(
      71             : ///            style: MongolTextButton.styleFrom(
      72             : ///              textStyle: const TextStyle(fontSize: 20),
      73             : ///            ),
      74             : ///            onPressed: null,
      75             : ///            child: const Text('Disabled'),
      76             : ///         ),
      77             : ///         const SizedBox(height: 30),
      78             : ///         MongolTextButton(
      79             : ///           style: MongolTextButton.styleFrom(
      80             : ///             textStyle: const TextStyle(fontSize: 20),
      81             : ///           ),
      82             : ///           onPressed: () {},
      83             : ///           child: const MongolText('Enabled'),
      84             : ///         ),
      85             : ///         const SizedBox(height: 30),
      86             : ///         ClipRRect(
      87             : ///           borderRadius: BorderRadius.circular(4),
      88             : ///           child: Stack(
      89             : ///             children: <Widget>[
      90             : ///               Positioned.fill(
      91             : ///                 child: Container(
      92             : ///                   decoration: const BoxDecoration(
      93             : ///                     gradient: LinearGradient(
      94             : ///                       colors: <Color>[
      95             : ///                         Color(0xFF0D47A1),
      96             : ///                         Color(0xFF1976D2),
      97             : ///                         Color(0xFF42A5F5),
      98             : ///                       ],
      99             : ///                     ),
     100             : ///                   ),
     101             : ///                 ),
     102             : ///               ),
     103             : ///               MongolTextButton(
     104             : ///                 style: MongolTextButton.styleFrom(
     105             : ///                   padding: const EdgeInsets.all(16.0),
     106             : ///                   primary: Colors.white,
     107             : ///                   textStyle: const TextStyle(fontSize: 20),
     108             : ///                 ),
     109             : ///                 onPressed: () {},
     110             : ///                  child: const MongolText('Gradient'),
     111             : ///               ),
     112             : ///             ],
     113             : ///           ),
     114             : ///         ),
     115             : ///       ],
     116             : ///     ),
     117             : ///   );
     118             : /// }
     119             : ///
     120             : /// ```
     121             : /// {@end-tool}
     122             : ///
     123             : /// See also:
     124             : ///
     125             : ///  * [MongolOutlinedButton], a [MongolTextButton] with a border outline.
     126             : ///  * [MongolElevatedButton], a filled button whose material elevates when pressed.
     127             : ///  * <https://material.io/design/components/buttons.html>
     128             : class MongolTextButton extends ButtonStyleButton {
     129             :   /// Create a MongolTextButton.
     130             :   ///
     131             :   /// The [autofocus] and [clipBehavior] arguments must not be null.
     132           2 :   const MongolTextButton({
     133             :     Key? key,
     134             :     required VoidCallback? onPressed,
     135             :     VoidCallback? onLongPress,
     136             :     ButtonStyle? style,
     137             :     FocusNode? focusNode,
     138             :     bool autofocus = false,
     139             :     Clip clipBehavior = Clip.none,
     140             :     required Widget child,
     141           1 :   }) : super(
     142             :           key: key,
     143             :           onPressed: onPressed,
     144             :           onLongPress: onLongPress,
     145             :           style: style,
     146             :           focusNode: focusNode,
     147             :           autofocus: autofocus,
     148             :           clipBehavior: clipBehavior,
     149             :           child: child,
     150             :         );
     151             : 
     152             :   /// Create a text button from a pair of widgets that serve as the button's
     153             :   /// [icon] and [label].
     154             :   ///
     155             :   /// The icon and label are arranged in a column and padded by 8 logical pixels
     156             :   /// at the ends, with an 8 pixel gap in between.
     157             :   ///
     158             :   /// The [icon] and [label] arguments must not be null.
     159             :   factory MongolTextButton.icon({
     160             :     Key? key,
     161             :     required VoidCallback? onPressed,
     162             :     VoidCallback? onLongPress,
     163             :     ButtonStyle? style,
     164             :     FocusNode? focusNode,
     165             :     bool? autofocus,
     166             :     Clip? clipBehavior,
     167             :     required Widget icon,
     168             :     required Widget label,
     169             :   }) = _MongolTextButtonWithIcon;
     170             : 
     171             :   /// A static convenience method that constructs a text button
     172             :   /// [ButtonStyle] given simple values.
     173             :   ///
     174             :   /// The [primary], and [onSurface] colors are used to create a
     175             :   /// [MaterialStateProperty] [ButtonStyle.foregroundColor] value in the same
     176             :   /// way that [defaultStyleOf] uses the [ColorScheme] colors with the same
     177             :   /// names. Specify a value for [primary] to specify the color of the button's
     178             :   /// text and icons as well as the overlay colors used to indicate the hover,
     179             :   /// focus, and pressed states. Use [onSurface] to specify the button's
     180             :   /// disabled text and icon color.
     181             :   ///
     182             :   /// Similarly, the [enabledMouseCursor] and [disabledMouseCursor]
     183             :   /// parameters are used to construct [ButtonStyle.mouseCursor].
     184             :   ///
     185             :   /// All of the other parameters are either used directly or used to
     186             :   /// create a [MaterialStateProperty] with a single value for all
     187             :   /// states.
     188             :   ///
     189             :   /// All parameters default to null. By default this method returns
     190             :   /// a [ButtonStyle] that doesn't override anything.
     191             :   ///
     192             :   /// For example, to override the default text and icon colors for a
     193             :   /// [MongolTextButton], as well as its overlay color, with all of the
     194             :   /// standard opacity adjustments for the pressed, focused, and
     195             :   /// hovered states, one could write:
     196             :   ///
     197             :   /// ```dart
     198             :   /// MongolTextButton(
     199             :   ///   style: TextButton.styleFrom(primary: Colors.green),
     200             :   /// )
     201             :   /// ```
     202           1 :   static ButtonStyle styleFrom({
     203             :     Color? primary,
     204             :     Color? onSurface,
     205             :     Color? backgroundColor,
     206             :     Color? shadowColor,
     207             :     double? elevation,
     208             :     TextStyle? textStyle,
     209             :     EdgeInsetsGeometry? padding,
     210             :     Size? minimumSize,
     211             :     Size? fixedSize,
     212             :     Size? maximumSize,
     213             :     BorderSide? side,
     214             :     OutlinedBorder? shape,
     215             :     MouseCursor? enabledMouseCursor,
     216             :     MouseCursor? disabledMouseCursor,
     217             :     VisualDensity? visualDensity,
     218             :     MaterialTapTargetSize? tapTargetSize,
     219             :     Duration? animationDuration,
     220             :     bool? enableFeedback,
     221             :     AlignmentGeometry? alignment,
     222             :     InteractiveInkFeatureFactory? splashFactory,
     223             :   }) {
     224             :     final MaterialStateProperty<Color?>? foregroundColor =
     225             :         (onSurface == null && primary == null)
     226             :             ? null
     227           1 :             : _TextButtonDefaultForeground(primary, onSurface);
     228             :     final MaterialStateProperty<Color?>? overlayColor =
     229           1 :         (primary == null) ? null : _TextButtonDefaultOverlay(primary);
     230             :     final MaterialStateProperty<MouseCursor>? mouseCursor =
     231             :         (enabledMouseCursor == null && disabledMouseCursor == null)
     232             :             ? null
     233           1 :             : _TextButtonDefaultMouseCursor(
     234             :                 enabledMouseCursor!, disabledMouseCursor!);
     235             : 
     236           1 :     return ButtonStyle(
     237           1 :       textStyle: ButtonStyleButton.allOrNull<TextStyle>(textStyle),
     238           1 :       backgroundColor: ButtonStyleButton.allOrNull<Color>(backgroundColor),
     239             :       foregroundColor: foregroundColor,
     240             :       overlayColor: overlayColor,
     241           1 :       shadowColor: ButtonStyleButton.allOrNull<Color>(shadowColor),
     242           1 :       elevation: ButtonStyleButton.allOrNull<double>(elevation),
     243           1 :       padding: ButtonStyleButton.allOrNull<EdgeInsetsGeometry>(padding),
     244           1 :       minimumSize: ButtonStyleButton.allOrNull<Size>(minimumSize),
     245           1 :       fixedSize: ButtonStyleButton.allOrNull<Size>(fixedSize),
     246             :       // TODO: add when becomes available in stable channel
     247             :       //maximumSize: ButtonStyleButton.allOrNull<Size>(maximumSize),
     248           1 :       side: ButtonStyleButton.allOrNull<BorderSide>(side),
     249           1 :       shape: ButtonStyleButton.allOrNull<OutlinedBorder>(shape),
     250             :       mouseCursor: mouseCursor,
     251             :       visualDensity: visualDensity,
     252             :       tapTargetSize: tapTargetSize,
     253             :       animationDuration: animationDuration,
     254             :       enableFeedback: enableFeedback,
     255             :       alignment: alignment,
     256             :       splashFactory: splashFactory,
     257             :     );
     258             :   }
     259             : 
     260             :   /// Defines the button's default appearance.
     261             :   ///
     262             :   /// The button [child]'s [MongolText] and [Icon] widgets are rendered with
     263             :   /// the [ButtonStyle]'s foreground color. The button's [InkWell] adds
     264             :   /// the style's overlay color when the button is focused, hovered
     265             :   /// or pressed. The button's background color becomes its [Material]
     266             :   /// color and is transparent by default.
     267             :   ///
     268             :   /// All of the ButtonStyle's defaults appear below.
     269             :   ///
     270             :   /// In this list "Theme.foo" is shorthand for
     271             :   /// `Theme.of(context).foo`. Color scheme values like
     272             :   /// "onSurface(0.38)" are shorthand for
     273             :   /// `onSurface.withOpacity(0.38)`. [MaterialStateProperty] valued
     274             :   /// properties that are not followed by a sublist have the same
     275             :   /// value for all states, otherwise the values are as specified for
     276             :   /// each state and "others" means all other states.
     277             :   ///
     278             :   /// The `textScaleFactor` is the value of
     279             :   /// `MediaQuery.of(context).textScaleFactor` and the names of the
     280             :   /// EdgeInsets constructors and `EdgeInsetsGeometry.lerp` have been
     281             :   /// abbreviated for readability.
     282             :   ///
     283             :   /// The color of the [ButtonStyle.textStyle] is not used, the
     284             :   /// [ButtonStyle.foregroundColor] color is used instead.
     285             :   ///
     286             :   /// * `textStyle` - Theme.textTheme.button
     287             :   /// * `backgroundColor` - transparent
     288             :   /// * `foregroundColor`
     289             :   ///   * disabled - Theme.colorScheme.onSurface(0.38)
     290             :   ///   * others - Theme.colorScheme.primary
     291             :   /// * `overlayColor`
     292             :   ///   * hovered - Theme.colorScheme.primary(0.04)
     293             :   ///   * focused or pressed - Theme.colorScheme.primary(0.12)
     294             :   /// * `shadowColor` - Theme.shadowColor
     295             :   /// * `elevation` - 0
     296             :   /// * `padding`
     297             :   ///   * `textScaleFactor <= 1` - all(8)
     298             :   ///   * `1 < textScaleFactor <= 2` - lerp(all(8), vertical(8))
     299             :   ///   * `2 < textScaleFactor <= 3` - lerp(vertical(8), vertical(4))
     300             :   ///   * `3 < textScaleFactor` - vertical(4)
     301             :   /// * `minimumSize` - Size(36, 64)
     302             :   /// * `fixedSize` - null
     303             :   /// * `maximumSize` - Size.infinite
     304             :   /// * `side` - null
     305             :   /// * `shape` - RoundedRectangleBorder(borderRadius: BorderRadius.circular(4))
     306             :   /// * `mouseCursor`
     307             :   ///   * disabled - SystemMouseCursors.forbidden
     308             :   ///   * others - SystemMouseCursors.click
     309             :   /// * `visualDensity` - theme.visualDensity
     310             :   /// * `tapTargetSize` - theme.materialTapTargetSize
     311             :   /// * `animationDuration` - kThemeChangeDuration
     312             :   /// * `enableFeedback` - true
     313             :   /// * `alignment` - Alignment.center
     314             :   /// * `splashFactory` - InkRipple.splashFactory
     315             :   ///
     316             :   /// The default padding values for the [MongolTextButton.icon] factory are slightly different:
     317             :   ///
     318             :   /// * `padding`
     319             :   ///   * `textScaleFactor <= 1` - all(8)
     320             :   ///   * `1 < textScaleFactor <= 2 `- lerp(all(8), vertical(4))
     321             :   ///   * `2 < textScaleFactor` - vertical(4)
     322             :   ///
     323             :   /// The default value for `side`, which defines the appearance of the button's
     324             :   /// outline, is null. That means that the outline is defined by the button
     325             :   /// shape's [OutlinedBorder.side]. Typically the default value of an
     326             :   /// [OutlinedBorder]'s side is [BorderSide.none], so an outline is not drawn.
     327           1 :   @override
     328             :   ButtonStyle defaultStyleOf(BuildContext context) {
     329           1 :     final ThemeData theme = Theme.of(context);
     330           1 :     final ColorScheme colorScheme = theme.colorScheme;
     331             : 
     332           1 :     final EdgeInsetsGeometry scaledPadding = ButtonStyleButton.scaledPadding(
     333             :       const EdgeInsets.all(8),
     334             :       const EdgeInsets.symmetric(vertical: 8),
     335             :       const EdgeInsets.symmetric(vertical: 4),
     336           2 :       MediaQuery.maybeOf(context)?.textScaleFactor ?? 1,
     337             :     );
     338             : 
     339           1 :     return styleFrom(
     340           1 :       primary: colorScheme.primary,
     341           1 :       onSurface: colorScheme.onSurface,
     342             :       backgroundColor: Colors.transparent,
     343           1 :       shadowColor: theme.shadowColor,
     344             :       elevation: 0,
     345           2 :       textStyle: theme.textTheme.button,
     346             :       padding: scaledPadding,
     347             :       minimumSize: const Size(36, 64),
     348             :       maximumSize: Size.infinite,
     349             :       side: null,
     350             :       shape: const RoundedRectangleBorder(
     351             :           borderRadius: BorderRadius.all(Radius.circular(4))),
     352             :       enabledMouseCursor: SystemMouseCursors.click,
     353             :       disabledMouseCursor: SystemMouseCursors.forbidden,
     354           1 :       visualDensity: theme.visualDensity,
     355           1 :       tapTargetSize: theme.materialTapTargetSize,
     356             :       animationDuration: kThemeChangeDuration,
     357             :       enableFeedback: true,
     358             :       alignment: Alignment.center,
     359             :       splashFactory: InkRipple.splashFactory,
     360             :     );
     361             :   }
     362             : 
     363             :   /// Returns the [TextButtonThemeData.style] of the closest
     364             :   /// [TextButtonTheme] ancestor.
     365           1 :   @override
     366             :   ButtonStyle? themeStyleOf(BuildContext context) {
     367           2 :     return TextButtonTheme.of(context).style;
     368             :   }
     369             : }
     370             : 
     371             : @immutable
     372             : class _TextButtonDefaultForeground extends MaterialStateProperty<Color?> {
     373           1 :   _TextButtonDefaultForeground(this.primary, this.onSurface);
     374             : 
     375             :   final Color? primary;
     376             :   final Color? onSurface;
     377             : 
     378           1 :   @override
     379             :   Color? resolve(Set<MaterialState> states) {
     380           1 :     if (states.contains(MaterialState.disabled)) {
     381           2 :       return onSurface?.withOpacity(0.38);
     382             :     }
     383           1 :     return primary;
     384             :   }
     385             : 
     386           0 :   @override
     387             :   String toString() {
     388           0 :     return '{disabled: ${onSurface?.withOpacity(0.38)}, otherwise: $primary}';
     389             :   }
     390             : }
     391             : 
     392             : @immutable
     393             : class _TextButtonDefaultOverlay extends MaterialStateProperty<Color?> {
     394           1 :   _TextButtonDefaultOverlay(this.primary);
     395             : 
     396             :   final Color primary;
     397             : 
     398           1 :   @override
     399             :   Color? resolve(Set<MaterialState> states) {
     400           1 :     if (states.contains(MaterialState.hovered)) {
     401           2 :       return primary.withOpacity(0.04);
     402             :     }
     403           1 :     if (states.contains(MaterialState.focused) ||
     404           1 :         states.contains(MaterialState.pressed)) {
     405           2 :       return primary.withOpacity(0.12);
     406             :     }
     407             :     return null;
     408             :   }
     409             : 
     410           0 :   @override
     411             :   String toString() {
     412           0 :     return '{hovered: ${primary.withOpacity(0.04)}, focused,pressed: ${primary.withOpacity(0.12)}, otherwise: null}';
     413             :   }
     414             : }
     415             : 
     416             : @immutable
     417             : class _TextButtonDefaultMouseCursor extends MaterialStateProperty<MouseCursor>
     418             :     with Diagnosticable {
     419           1 :   _TextButtonDefaultMouseCursor(this.enabledCursor, this.disabledCursor);
     420             : 
     421             :   final MouseCursor enabledCursor;
     422             :   final MouseCursor disabledCursor;
     423             : 
     424           1 :   @override
     425             :   MouseCursor resolve(Set<MaterialState> states) {
     426           2 :     if (states.contains(MaterialState.disabled)) return disabledCursor;
     427           1 :     return enabledCursor;
     428             :   }
     429             : }
     430             : 
     431             : class _MongolTextButtonWithIcon extends MongolTextButton {
     432           1 :   _MongolTextButtonWithIcon({
     433             :     Key? key,
     434             :     required VoidCallback? onPressed,
     435             :     VoidCallback? onLongPress,
     436             :     ButtonStyle? style,
     437             :     FocusNode? focusNode,
     438             :     bool? autofocus,
     439             :     Clip? clipBehavior,
     440             :     required Widget icon,
     441             :     required Widget label,
     442           1 :   }) : super(
     443             :           key: key,
     444             :           onPressed: onPressed,
     445             :           onLongPress: onLongPress,
     446             :           style: style,
     447             :           focusNode: focusNode,
     448             :           autofocus: autofocus ?? false,
     449             :           clipBehavior: clipBehavior ?? Clip.none,
     450           1 :           child: _MongolTextButtonWithIconChild(icon: icon, label: label),
     451             :         );
     452             : 
     453           1 :   @override
     454             :   ButtonStyle defaultStyleOf(BuildContext context) {
     455           1 :     final EdgeInsetsGeometry scaledPadding = ButtonStyleButton.scaledPadding(
     456             :       const EdgeInsets.all(8),
     457             :       const EdgeInsets.symmetric(vertical: 4),
     458             :       const EdgeInsets.symmetric(vertical: 4),
     459           2 :       MediaQuery.maybeOf(context)?.textScaleFactor ?? 1,
     460             :     );
     461           2 :     return super.defaultStyleOf(context).copyWith(
     462           1 :           padding: MaterialStateProperty.all<EdgeInsetsGeometry>(scaledPadding),
     463             :         );
     464             :   }
     465             : }
     466             : 
     467             : class _MongolTextButtonWithIconChild extends StatelessWidget {
     468           1 :   const _MongolTextButtonWithIconChild({
     469             :     Key? key,
     470             :     required this.label,
     471             :     required this.icon,
     472           1 :   }) : super(key: key);
     473             : 
     474             :   final Widget label;
     475             :   final Widget icon;
     476             : 
     477           1 :   @override
     478             :   Widget build(BuildContext context) {
     479           2 :     final double scale = MediaQuery.maybeOf(context)?.textScaleFactor ?? 1;
     480             :     final double gap =
     481           4 :         scale <= 1 ? 8 : lerpDouble(8, 4, math.min(scale - 1, 1))!;
     482           1 :     return Column(
     483             :       mainAxisSize: MainAxisSize.min,
     484           5 :       children: <Widget>[icon, SizedBox(height: gap), Flexible(child: label)],
     485             :     );
     486             :   }
     487             : }

Generated by: LCOV version 1.15