customiconbutton.dart 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101
  1. // Copyright 2014 The Flutter Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. import 'dart:math' as math;
  5. import 'package:flutter/foundation.dart';
  6. import 'package:flutter/material.dart';
  7. import 'package:flutter/rendering.dart';
  8. import 'package:flyinsonolite/controls/custom/custombuttonstylebutton.dart';
  9. import 'package:flyinsonolite/controls/custom/customtooltip.dart';
  10. import 'package:flyinsonolite/infrastructure/scale.dart';
  11. // Examples can assume:
  12. // late BuildContext context;
  13. // Minimum logical pixel size of the IconButton.
  14. // See: <https://material.io/design/usability/accessibility.html#layout-typography>.
  15. double get _kMinButtonSize => kMinInteractiveDimension.s;
  16. /// A Material Design icon button.
  17. ///
  18. /// An icon button is a picture printed on a [Material] widget that reacts to
  19. /// touches by filling with color (ink).
  20. ///
  21. /// Icon buttons are commonly used in the [AppBar.actions] field, but they can
  22. /// be used in many other places as well.
  23. ///
  24. /// If the [onPressed] callback is null, then the button will be disabled and
  25. /// will not react to touch.
  26. ///
  27. /// Requires one of its ancestors to be a [Material] widget. In Material Design 3,
  28. /// this requirement no longer exists because this widget builds a subclass of
  29. /// [CustomButtonStyleButton].
  30. ///
  31. /// The hit region of an icon button will, if possible, be at least
  32. /// kMinInteractiveDimension pixels in size, regardless of the actual
  33. /// [iconSize], to satisfy the [touch target size](https://material.io/design/layout/spacing-methods.html#touch-targets)
  34. /// requirements in the Material Design specification. The [alignment] controls
  35. /// how the icon itself is positioned within the hit region.
  36. ///
  37. /// {@tool dartpad}
  38. /// This sample shows an [CustomIconButton] that uses the Material icon "volume_up" to
  39. /// increase the volume.
  40. ///
  41. /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/icon_button.png)
  42. ///
  43. /// ** See code in examples/api/lib/material/icon_button/icon_button.0.dart **
  44. /// {@end-tool}
  45. ///
  46. /// ### Icon sizes
  47. ///
  48. /// When creating an icon button with an [Icon], do not override the
  49. /// icon's size with its [Icon.size] parameter, use the icon button's
  50. /// [iconSize] parameter instead. For example do this:
  51. ///
  52. /// ```dart
  53. /// IconButton(
  54. /// iconSize: 72,
  55. /// icon: const Icon(Icons.favorite),
  56. /// onPressed: () {
  57. /// // ...
  58. /// },
  59. /// ),
  60. /// ```
  61. ///
  62. /// Avoid doing this:
  63. ///
  64. /// ```dart
  65. /// IconButton(
  66. /// icon: const Icon(Icons.favorite, size: 72),
  67. /// onPressed: () {
  68. /// // ...
  69. /// },
  70. /// ),
  71. /// ```
  72. ///
  73. /// If you do, the button's size will be based on the default icon
  74. /// size, not 72, which may produce unexpected layouts and clipping
  75. /// issues.
  76. ///
  77. /// ### Adding a filled background
  78. ///
  79. /// Icon buttons don't support specifying a background color or other
  80. /// background decoration because typically the icon is just displayed
  81. /// on top of the parent widget's background. Icon buttons that appear
  82. /// in [AppBar.actions] are an example of this.
  83. ///
  84. /// It's easy enough to create an icon button with a filled background
  85. /// using the [Ink] widget. The [Ink] widget renders a decoration on
  86. /// the underlying [Material] along with the splash and highlight
  87. /// [InkResponse] contributed by descendant widgets.
  88. ///
  89. /// {@tool dartpad}
  90. /// In this sample the icon button's background color is defined with an [Ink]
  91. /// widget whose child is an [CustomIconButton]. The icon button's filled background
  92. /// is a light shade of blue, it's a filled circle, and it's as big as the
  93. /// button is.
  94. ///
  95. /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/icon_button_background.png)
  96. ///
  97. /// ** See code in examples/api/lib/material/icon_button/icon_button.1.dart **
  98. /// {@end-tool}
  99. ///
  100. /// Material Design 3 introduced new types (standard and contained) of [CustomIconButton]s.
  101. /// The default [CustomIconButton] is the standard type, and contained icon buttons can be produced
  102. /// by configuring the [CustomIconButton] widget's properties.
  103. ///
  104. /// Material Design 3 also treats [CustomIconButton]s as toggle buttons. In order
  105. /// to not break existing apps, the toggle feature can be optionally controlled
  106. /// by the [isSelected] property.
  107. ///
  108. /// If [isSelected] is null it will behave as a normal button. If [isSelected] is not
  109. /// null then it will behave as a toggle button. If [isSelected] is true then it will
  110. /// show [selectedIcon], if it false it will show the normal [icon].
  111. ///
  112. /// In Material Design 3, both [IconTheme] and [IconButtonTheme] are used to override the default style
  113. /// of [CustomIconButton]. If both themes exist, the [IconButtonTheme] will override [IconTheme] no matter
  114. /// which is closer to the [CustomIconButton]. Each [CustomIconButton]'s property is resolved by the order of
  115. /// precedence: widget property, [IconButtonTheme] property, [IconTheme] property and
  116. /// internal default property value.
  117. ///
  118. /// In Material Design 3, the [CustomIconButton.visualDensity] defaults to [VisualDensity.standard]
  119. /// for all platforms; otherwise the button will have a rounded rectangle shape if
  120. /// the [CustomIconButton.visualDensity] is set to [VisualDensity.compact]. Users can
  121. /// customize it by using [IconButtonTheme], [CustomIconButton.style] or [CustomIconButton.visualDensity].
  122. ///
  123. /// {@tool dartpad}
  124. /// This sample shows creation of [CustomIconButton] widgets for standard, filled,
  125. /// filled tonal and outlined types, as described in: https://m3.material.io/components/icon-buttons/overview
  126. ///
  127. /// ** See code in examples/api/lib/material/icon_button/icon_button.2.dart **
  128. /// {@end-tool}
  129. ///
  130. /// {@tool dartpad}
  131. /// This sample shows creation of [CustomIconButton] widgets with toggle feature for
  132. /// standard, filled, filled tonal and outlined types, as described
  133. /// in: https://m3.material.io/components/icon-buttons/overview
  134. ///
  135. /// ** See code in examples/api/lib/material/icon_button/icon_button.3.dart **
  136. /// {@end-tool}
  137. ///
  138. /// See also:
  139. ///
  140. /// * [Icons], the library of Material Icons.
  141. /// * [BackButton], an icon button for a "back" affordance which adapts to the
  142. /// current platform's conventions.
  143. /// * [CloseButton], an icon button for closing pages.
  144. /// * [AppBar], to show a toolbar at the top of an application.
  145. /// * [CustomTextButton], [CustomElevatedButton], [OutlinedButton], for buttons with text labels and an optional icon.
  146. /// * [InkResponse] and [InkWell], for the ink splash effect itself.
  147. class CustomIconButton extends StatelessWidget {
  148. /// Creates an icon button.
  149. ///
  150. /// Icon buttons are commonly used in the [AppBar.actions] field, but they can
  151. /// be used in many other places as well.
  152. ///
  153. /// Requires one of its ancestors to be a [Material] widget. This requirement
  154. /// no longer exists if [ThemeData.useMaterial3] is set to true.
  155. ///
  156. /// [autofocus] argument must not be null (though it has default value).
  157. ///
  158. /// The [icon] argument must be specified, and is typically either an [Icon]
  159. /// or an [ImageIcon].
  160. const CustomIconButton({
  161. super.key,
  162. this.iconSize,
  163. this.visualDensity,
  164. this.padding,
  165. this.alignment,
  166. this.splashRadius,
  167. this.color,
  168. this.focusColor,
  169. this.hoverColor,
  170. this.highlightColor,
  171. this.splashColor,
  172. this.disabledColor,
  173. required this.onPressed,
  174. this.mouseCursor,
  175. this.focusNode,
  176. this.autofocus = false,
  177. this.tooltip,
  178. this.enableFeedback,
  179. this.constraints,
  180. this.style,
  181. this.isSelected,
  182. this.selectedIcon,
  183. required this.icon,
  184. }) : assert(splashRadius == null || splashRadius > 0),
  185. assert(autofocus != null),
  186. assert(icon != null);
  187. /// The size of the icon inside the button.
  188. ///
  189. /// If null, uses [IconThemeData.size]. If it is also null, the default size
  190. /// is 24.0.
  191. ///
  192. /// The size given here is passed down to the widget in the [icon] property
  193. /// via an [IconTheme]. Setting the size here instead of in, for example, the
  194. /// [Icon.size] property allows the [CustomIconButton] to size the splash area to
  195. /// fit the [Icon]. If you were to set the size of the [Icon] using
  196. /// [Icon.size] instead, then the [CustomIconButton] would default to 24.0 and then
  197. /// the [Icon] itself would likely get clipped.
  198. ///
  199. /// If [ThemeData.useMaterial3] is set to true and this is null, the size of the
  200. /// [CustomIconButton] would default to 24.0. The size given here is passed down to the
  201. /// [ButtonStyle.iconSize] property.
  202. final double? iconSize;
  203. /// Defines how compact the icon button's layout will be.
  204. ///
  205. /// {@macro flutter.material.themedata.visualDensity}
  206. ///
  207. /// This property can be null. If null, it defaults to [VisualDensity.standard]
  208. /// in Material Design 3 to make sure the button will be circular on all platforms.
  209. ///
  210. /// See also:
  211. ///
  212. /// * [ThemeData.visualDensity], which specifies the [visualDensity] for all
  213. /// widgets within a [Theme].
  214. final VisualDensity? visualDensity;
  215. /// The padding around the button's icon. The entire padded icon will react
  216. /// to input gestures.
  217. ///
  218. /// This property can be null. If null, it defaults to 8.0 padding on all sides.
  219. final EdgeInsetsGeometry? padding;
  220. /// Defines how the icon is positioned within the IconButton.
  221. ///
  222. /// This property can be null. If null, it defaults to [Alignment.center].
  223. ///
  224. /// See also:
  225. ///
  226. /// * [Alignment], a class with convenient constants typically used to
  227. /// specify an [AlignmentGeometry].
  228. /// * [AlignmentDirectional], like [Alignment] for specifying alignments
  229. /// relative to text direction.
  230. final AlignmentGeometry? alignment;
  231. /// The splash radius.
  232. ///
  233. /// If [ThemeData.useMaterial3] is set to true, this will not be used.
  234. ///
  235. /// If null, default splash radius of [Material.defaultSplashRadius] is used.
  236. final double? splashRadius;
  237. /// The icon to display inside the button.
  238. ///
  239. /// The [Icon.size] and [Icon.color] of the icon is configured automatically
  240. /// based on the [iconSize] and [color] properties of _this_ widget using an
  241. /// [IconTheme] and therefore should not be explicitly given in the icon
  242. /// widget.
  243. ///
  244. /// This property must not be null.
  245. ///
  246. /// See [Icon], [ImageIcon].
  247. final Widget icon;
  248. /// The color for the button when it has the input focus.
  249. ///
  250. /// If [ThemeData.useMaterial3] is set to true, this [focusColor] will be mapped
  251. /// to be the [ButtonStyle.overlayColor] in focused state, which paints on top of
  252. /// the button, as an overlay. Therefore, using a color with some transparency
  253. /// is recommended. For example, one could customize the [focusColor] below:
  254. ///
  255. /// ```dart
  256. /// IconButton(
  257. /// focusColor: Colors.orange.withOpacity(0.3),
  258. /// icon: const Icon(Icons.sunny),
  259. /// onPressed: () {
  260. /// // ...
  261. /// },
  262. /// )
  263. /// ```
  264. ///
  265. /// Defaults to [ThemeData.focusColor] of the ambient theme.
  266. final Color? focusColor;
  267. /// The color for the button when a pointer is hovering over it.
  268. ///
  269. /// If [ThemeData.useMaterial3] is set to true, this [hoverColor] will be mapped
  270. /// to be the [ButtonStyle.overlayColor] in hovered state, which paints on top of
  271. /// the button, as an overlay. Therefore, using a color with some transparency
  272. /// is recommended. For example, one could customize the [hoverColor] below:
  273. ///
  274. /// ```dart
  275. /// IconButton(
  276. /// hoverColor: Colors.orange.withOpacity(0.3),
  277. /// icon: const Icon(Icons.ac_unit),
  278. /// onPressed: () {
  279. /// // ...
  280. /// },
  281. /// )
  282. /// ```
  283. ///
  284. /// Defaults to [ThemeData.hoverColor] of the ambient theme.
  285. final Color? hoverColor;
  286. /// The color to use for the icon inside the button, if the icon is enabled.
  287. /// Defaults to leaving this up to the [icon] widget.
  288. ///
  289. /// The icon is enabled if [onPressed] is not null.
  290. ///
  291. /// ```dart
  292. /// IconButton(
  293. /// color: Colors.blue,
  294. /// icon: const Icon(Icons.sunny_snowing),
  295. /// onPressed: () {
  296. /// // ...
  297. /// },
  298. /// )
  299. /// ```
  300. final Color? color;
  301. /// The primary color of the button when the button is in the down (pressed) state.
  302. /// The splash is represented as a circular overlay that appears above the
  303. /// [highlightColor] overlay. The splash overlay has a center point that matches
  304. /// the hit point of the user touch event. The splash overlay will expand to
  305. /// fill the button area if the touch is held for long enough time. If the splash
  306. /// color has transparency then the highlight and button color will show through.
  307. ///
  308. /// If [ThemeData.useMaterial3] is set to true, this will not be used. Use
  309. /// [highlightColor] instead to show the overlay color of the button when the button
  310. /// is in the pressed state.
  311. ///
  312. /// Defaults to the Theme's splash color, [ThemeData.splashColor].
  313. final Color? splashColor;
  314. /// The secondary color of the button when the button is in the down (pressed)
  315. /// state. The highlight color is represented as a solid color that is overlaid over the
  316. /// button color (if any). If the highlight color has transparency, the button color
  317. /// will show through. The highlight fades in quickly as the button is held down.
  318. ///
  319. /// If [ThemeData.useMaterial3] is set to true, this [highlightColor] will be mapped
  320. /// to be the [ButtonStyle.overlayColor] in pressed state, which paints on top
  321. /// of the button, as an overlay. Therefore, using a color with some transparency
  322. /// is recommended. For example, one could customize the [highlightColor] below:
  323. ///
  324. /// ```dart
  325. /// IconButton(
  326. /// highlightColor: Colors.orange.withOpacity(0.3),
  327. /// icon: const Icon(Icons.question_mark),
  328. /// onPressed: () {
  329. /// // ...
  330. /// },
  331. /// )
  332. /// ```
  333. ///
  334. /// Defaults to the Theme's highlight color, [ThemeData.highlightColor].
  335. final Color? highlightColor;
  336. /// The color to use for the icon inside the button, if the icon is disabled.
  337. /// Defaults to the [ThemeData.disabledColor] of the current [Theme].
  338. ///
  339. /// The icon is disabled if [onPressed] is null.
  340. final Color? disabledColor;
  341. /// The callback that is called when the button is tapped or otherwise activated.
  342. ///
  343. /// If this is set to null, the button will be disabled.
  344. final VoidCallback? onPressed;
  345. /// {@macro flutter.material.RawMaterialButton.mouseCursor}
  346. ///
  347. /// If set to null, will default to
  348. /// - [SystemMouseCursors.basic], if [onPressed] is null
  349. /// - [SystemMouseCursors.click], otherwise
  350. final MouseCursor? mouseCursor;
  351. /// {@macro flutter.widgets.Focus.focusNode}
  352. final FocusNode? focusNode;
  353. /// {@macro flutter.widgets.Focus.autofocus}
  354. final bool autofocus;
  355. /// Text that describes the action that will occur when the button is pressed.
  356. ///
  357. /// This text is displayed when the user long-presses on the button and is
  358. /// used for accessibility.
  359. final String? tooltip;
  360. /// Whether detected gestures should provide acoustic and/or haptic feedback.
  361. ///
  362. /// For example, on Android a tap will produce a clicking sound and a
  363. /// long-press will produce a short vibration, when feedback is enabled.
  364. ///
  365. /// See also:
  366. ///
  367. /// * [Feedback] for providing platform-specific feedback to certain actions.
  368. final bool? enableFeedback;
  369. /// Optional size constraints for the button.
  370. ///
  371. /// When unspecified, defaults to:
  372. /// ```dart
  373. /// const BoxConstraints(
  374. /// minWidth: kMinInteractiveDimension,
  375. /// minHeight: kMinInteractiveDimension,
  376. /// )
  377. /// ```
  378. /// where [kMinInteractiveDimension] is 48.0, and then with visual density
  379. /// applied.
  380. ///
  381. /// The default constraints ensure that the button is accessible.
  382. /// Specifying this parameter enables creation of buttons smaller than
  383. /// the minimum size, but it is not recommended.
  384. ///
  385. /// The visual density uses the [visualDensity] parameter if specified,
  386. /// and `Theme.of(context).visualDensity` otherwise.
  387. final BoxConstraints? constraints;
  388. /// Customizes this button's appearance.
  389. ///
  390. /// Non-null properties of this style override the corresponding
  391. /// properties in [_IconButtonM3.themeStyleOf] and [_IconButtonM3.defaultStyleOf].
  392. /// [MaterialStateProperty]s that resolve to non-null values will similarly
  393. /// override the corresponding [MaterialStateProperty]s in [_IconButtonM3.themeStyleOf]
  394. /// and [_IconButtonM3.defaultStyleOf].
  395. ///
  396. /// The [style] is only used for Material 3 [CustomIconButton]. If [ThemeData.useMaterial3]
  397. /// is set to true, [style] is preferred for icon button customization, and any
  398. /// parameters defined in [style] will override the same parameters in [CustomIconButton].
  399. ///
  400. /// For example, if [CustomIconButton]'s [visualDensity] is set to [VisualDensity.standard]
  401. /// and [style]'s [visualDensity] is set to [VisualDensity.compact],
  402. /// the icon button will have [VisualDensity.compact] to define the button's layout.
  403. ///
  404. /// Null by default.
  405. final ButtonStyle? style;
  406. /// The optional selection state of the icon button.
  407. ///
  408. /// If this property is null, the button will behave as a normal push button,
  409. /// otherwise, the button will toggle between showing [icon] and [selectedIcon]
  410. /// based on the value of [isSelected]. If true, it will show [selectedIcon],
  411. /// if false it will show [icon].
  412. ///
  413. /// This property is only used if [ThemeData.useMaterial3] is true.
  414. final bool? isSelected;
  415. /// The icon to display inside the button when [isSelected] is true. This property
  416. /// can be null. The original [icon] will be used for both selected and unselected
  417. /// status if it is null.
  418. ///
  419. /// The [Icon.size] and [Icon.color] of the icon is configured automatically
  420. /// based on the [iconSize] and [color] properties using an [IconTheme] and
  421. /// therefore should not be explicitly configured in the icon widget.
  422. ///
  423. /// This property is only used if [ThemeData.useMaterial3] is true.
  424. ///
  425. /// See also:
  426. ///
  427. /// * [Icon], for icons based on glyphs from fonts instead of images.
  428. /// * [ImageIcon], for showing icons from [AssetImage]s or other [ImageProvider]s.
  429. final Widget? selectedIcon;
  430. /// A static convenience method that constructs an icon button
  431. /// [ButtonStyle] given simple values. This method is only used for Material 3.
  432. ///
  433. /// The [foregroundColor] color is used to create a [MaterialStateProperty]
  434. /// [ButtonStyle.foregroundColor] value. Specify a value for [foregroundColor]
  435. /// to specify the color of the button's icons. The [hoverColor], [focusColor]
  436. /// and [highlightColor] colors are used to indicate the hover, focus,
  437. /// and pressed states. Use [backgroundColor] for the button's background
  438. /// fill color. Use [disabledForegroundColor] and [disabledBackgroundColor]
  439. /// to specify the button's disabled icon and fill color.
  440. ///
  441. /// Similarly, the [enabledMouseCursor] and [disabledMouseCursor]
  442. /// parameters are used to construct [ButtonStyle].mouseCursor.
  443. ///
  444. /// All of the other parameters are either used directly or used to
  445. /// create a [MaterialStateProperty] with a single value for all
  446. /// states.
  447. ///
  448. /// All parameters default to null, by default this method returns
  449. /// a [ButtonStyle] that doesn't override anything.
  450. ///
  451. /// For example, to override the default icon color for a
  452. /// [CustomIconButton], as well as its overlay color, with all of the
  453. /// standard opacity adjustments for the pressed, focused, and
  454. /// hovered states, one could write:
  455. ///
  456. /// ```dart
  457. /// IconButton(
  458. /// icon: const Icon(Icons.pets),
  459. /// style: IconButton.styleFrom(foregroundColor: Colors.green),
  460. /// onPressed: () {
  461. /// // ...
  462. /// },
  463. /// ),
  464. /// ```
  465. static ButtonStyle styleFrom({
  466. Color? foregroundColor,
  467. Color? backgroundColor,
  468. Color? disabledForegroundColor,
  469. Color? disabledBackgroundColor,
  470. Color? focusColor,
  471. Color? hoverColor,
  472. Color? highlightColor,
  473. Color? shadowColor,
  474. Color? surfaceTintColor,
  475. double? elevation,
  476. Size? minimumSize,
  477. Size? fixedSize,
  478. Size? maximumSize,
  479. double? iconSize,
  480. BorderSide? side,
  481. OutlinedBorder? shape,
  482. EdgeInsetsGeometry? padding,
  483. MouseCursor? enabledMouseCursor,
  484. MouseCursor? disabledMouseCursor,
  485. VisualDensity? visualDensity,
  486. MaterialTapTargetSize? tapTargetSize,
  487. Duration? animationDuration,
  488. bool? enableFeedback,
  489. AlignmentGeometry? alignment,
  490. InteractiveInkFeatureFactory? splashFactory,
  491. }) {
  492. final MaterialStateProperty<Color?>? buttonBackgroundColor =
  493. (backgroundColor == null && disabledBackgroundColor == null)
  494. ? null
  495. : _IconButtonDefaultBackground(
  496. backgroundColor, disabledBackgroundColor);
  497. final MaterialStateProperty<Color?>? buttonForegroundColor =
  498. (foregroundColor == null && disabledForegroundColor == null)
  499. ? null
  500. : _IconButtonDefaultForeground(
  501. foregroundColor, disabledForegroundColor);
  502. final MaterialStateProperty<Color?>? overlayColor =
  503. (foregroundColor == null &&
  504. hoverColor == null &&
  505. focusColor == null &&
  506. highlightColor == null)
  507. ? null
  508. : _IconButtonDefaultOverlay(
  509. foregroundColor, focusColor, hoverColor, highlightColor);
  510. final MaterialStateProperty<MouseCursor>? mouseCursor =
  511. (enabledMouseCursor == null && disabledMouseCursor == null)
  512. ? null
  513. : _IconButtonDefaultMouseCursor(
  514. enabledMouseCursor!, disabledMouseCursor!);
  515. return ButtonStyle(
  516. backgroundColor: buttonBackgroundColor,
  517. foregroundColor: buttonForegroundColor,
  518. overlayColor: overlayColor,
  519. shadowColor: CustomButtonStyleButton.allOrNull<Color>(shadowColor),
  520. surfaceTintColor:
  521. CustomButtonStyleButton.allOrNull<Color>(surfaceTintColor),
  522. elevation: CustomButtonStyleButton.allOrNull<double>(elevation),
  523. padding: CustomButtonStyleButton.allOrNull<EdgeInsetsGeometry>(padding),
  524. minimumSize: CustomButtonStyleButton.allOrNull<Size>(minimumSize),
  525. fixedSize: CustomButtonStyleButton.allOrNull<Size>(fixedSize),
  526. maximumSize: CustomButtonStyleButton.allOrNull<Size>(maximumSize),
  527. iconSize: CustomButtonStyleButton.allOrNull<double>(iconSize),
  528. side: CustomButtonStyleButton.allOrNull<BorderSide>(side),
  529. shape: CustomButtonStyleButton.allOrNull<OutlinedBorder>(shape),
  530. mouseCursor: mouseCursor,
  531. visualDensity: visualDensity,
  532. tapTargetSize: tapTargetSize,
  533. animationDuration: animationDuration,
  534. enableFeedback: enableFeedback,
  535. alignment: alignment,
  536. splashFactory: splashFactory,
  537. );
  538. }
  539. @override
  540. Widget build(BuildContext context) {
  541. final ThemeData theme = Theme.of(context);
  542. if (theme.useMaterial3) {
  543. final Size? minSize = constraints == null
  544. ? null
  545. : Size(constraints!.minWidth, constraints!.minHeight);
  546. final Size? maxSize = constraints == null
  547. ? null
  548. : Size(constraints!.maxWidth, constraints!.maxHeight);
  549. ButtonStyle adjustedStyle = styleFrom(
  550. visualDensity: visualDensity,
  551. foregroundColor: color,
  552. disabledForegroundColor: disabledColor,
  553. focusColor: focusColor,
  554. hoverColor: hoverColor,
  555. highlightColor: highlightColor,
  556. padding: padding,
  557. minimumSize: minSize,
  558. maximumSize: maxSize,
  559. iconSize: iconSize,
  560. alignment: alignment,
  561. enabledMouseCursor: mouseCursor,
  562. disabledMouseCursor: mouseCursor,
  563. enableFeedback: enableFeedback,
  564. );
  565. if (style != null) {
  566. adjustedStyle = style!.merge(adjustedStyle);
  567. }
  568. Widget effectiveIcon = icon;
  569. if ((isSelected ?? false) && selectedIcon != null) {
  570. effectiveIcon = selectedIcon!;
  571. }
  572. Widget iconButton = effectiveIcon;
  573. if (tooltip != null) {
  574. iconButton = CustomTooltip(
  575. message: tooltip,
  576. child: effectiveIcon,
  577. );
  578. }
  579. return _SelectableIconButton(
  580. style: adjustedStyle,
  581. onPressed: onPressed,
  582. autofocus: autofocus,
  583. focusNode: focusNode,
  584. isSelected: isSelected,
  585. child: iconButton,
  586. );
  587. }
  588. assert(debugCheckHasMaterial(context));
  589. Color? currentColor;
  590. if (onPressed != null) {
  591. currentColor = color;
  592. } else {
  593. currentColor = disabledColor ?? theme.disabledColor;
  594. }
  595. final VisualDensity effectiveVisualDensity =
  596. visualDensity ?? theme.visualDensity;
  597. final BoxConstraints unadjustedConstraints = constraints ??
  598. BoxConstraints(
  599. minWidth: _kMinButtonSize,
  600. minHeight: _kMinButtonSize,
  601. );
  602. final BoxConstraints adjustedConstraints =
  603. effectiveVisualDensity.effectiveConstraints(unadjustedConstraints);
  604. final double effectiveIconSize =
  605. iconSize ?? IconTheme.of(context).size ?? 24.s;
  606. final EdgeInsetsGeometry effectivePadding = padding ?? EdgeInsets.all(8.s);
  607. final AlignmentGeometry effectiveAlignment = alignment ?? Alignment.center;
  608. final bool effectiveEnableFeedback = enableFeedback ?? true;
  609. Widget result = ConstrainedBox(
  610. constraints: adjustedConstraints,
  611. child: Padding(
  612. padding: effectivePadding,
  613. child: SizedBox(
  614. height: effectiveIconSize,
  615. width: effectiveIconSize,
  616. child: Align(
  617. alignment: effectiveAlignment,
  618. child: IconTheme.merge(
  619. data: IconThemeData(
  620. size: effectiveIconSize,
  621. color: currentColor,
  622. ),
  623. child: icon,
  624. ),
  625. ),
  626. ),
  627. ),
  628. );
  629. if (tooltip != null) {
  630. result = CustomTooltip(
  631. message: tooltip,
  632. child: result,
  633. );
  634. }
  635. return Semantics(
  636. button: true,
  637. enabled: onPressed != null,
  638. child: InkResponse(
  639. focusNode: focusNode,
  640. autofocus: autofocus,
  641. canRequestFocus: onPressed != null,
  642. onTap: onPressed,
  643. mouseCursor: mouseCursor ??
  644. (onPressed == null
  645. ? SystemMouseCursors.basic
  646. : SystemMouseCursors.click),
  647. enableFeedback: effectiveEnableFeedback,
  648. focusColor: focusColor ?? theme.focusColor,
  649. hoverColor: hoverColor ?? theme.hoverColor,
  650. highlightColor: highlightColor ?? theme.highlightColor,
  651. splashColor: splashColor ?? theme.splashColor,
  652. radius: splashRadius ??
  653. math.max(
  654. Material.defaultSplashRadius.s,
  655. (effectiveIconSize +
  656. math.min(effectivePadding.horizontal,
  657. effectivePadding.vertical)) *
  658. 0.7,
  659. // x 0.5 for diameter -> radius and + 40% overflow derived from other Material apps.
  660. ),
  661. child: result,
  662. ),
  663. );
  664. }
  665. @override
  666. void debugFillProperties(DiagnosticPropertiesBuilder properties) {
  667. super.debugFillProperties(properties);
  668. properties.add(DiagnosticsProperty<Widget>('icon', icon, showName: false));
  669. properties.add(
  670. StringProperty('tooltip', tooltip, defaultValue: null, quoted: false));
  671. properties.add(ObjectFlagProperty<VoidCallback>('onPressed', onPressed,
  672. ifNull: 'disabled'));
  673. properties.add(ColorProperty('color', color, defaultValue: null));
  674. properties
  675. .add(ColorProperty('disabledColor', disabledColor, defaultValue: null));
  676. properties.add(ColorProperty('focusColor', focusColor, defaultValue: null));
  677. properties.add(ColorProperty('hoverColor', hoverColor, defaultValue: null));
  678. properties.add(
  679. ColorProperty('highlightColor', highlightColor, defaultValue: null));
  680. properties
  681. .add(ColorProperty('splashColor', splashColor, defaultValue: null));
  682. properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding,
  683. defaultValue: null));
  684. properties.add(DiagnosticsProperty<FocusNode>('focusNode', focusNode,
  685. defaultValue: null));
  686. }
  687. }
  688. class _SelectableIconButton extends StatefulWidget {
  689. const _SelectableIconButton({
  690. this.isSelected,
  691. this.style,
  692. this.focusNode,
  693. required this.autofocus,
  694. required this.onPressed,
  695. required this.child,
  696. });
  697. final bool? isSelected;
  698. final ButtonStyle? style;
  699. final FocusNode? focusNode;
  700. final bool autofocus;
  701. final VoidCallback? onPressed;
  702. final Widget child;
  703. @override
  704. State<_SelectableIconButton> createState() => _SelectableIconButtonState();
  705. }
  706. class _SelectableIconButtonState extends State<_SelectableIconButton> {
  707. late final MaterialStatesController statesController;
  708. @override
  709. void initState() {
  710. super.initState();
  711. if (widget.isSelected == null) {
  712. statesController = MaterialStatesController();
  713. } else {
  714. statesController = MaterialStatesController(
  715. <MaterialState>{if (widget.isSelected!) MaterialState.selected});
  716. }
  717. }
  718. @override
  719. void didUpdateWidget(_SelectableIconButton oldWidget) {
  720. super.didUpdateWidget(oldWidget);
  721. if (widget.isSelected == null) {
  722. if (statesController.value.contains(MaterialState.selected)) {
  723. statesController.update(MaterialState.selected, false);
  724. }
  725. return;
  726. }
  727. if (widget.isSelected != oldWidget.isSelected) {
  728. statesController.update(MaterialState.selected, widget.isSelected!);
  729. }
  730. }
  731. @override
  732. Widget build(BuildContext context) {
  733. return _IconButtonM3(
  734. statesController: statesController,
  735. style: widget.style,
  736. autofocus: widget.autofocus,
  737. focusNode: widget.focusNode,
  738. onPressed: widget.onPressed,
  739. child: widget.child,
  740. );
  741. }
  742. }
  743. class _IconButtonM3 extends CustomButtonStyleButton {
  744. const _IconButtonM3({
  745. required super.onPressed,
  746. super.style,
  747. super.focusNode,
  748. super.autofocus = false,
  749. super.statesController,
  750. required Widget super.child,
  751. }) : super(
  752. onLongPress: null,
  753. onHover: null,
  754. onFocusChange: null,
  755. clipBehavior: Clip.none);
  756. /// ## Material 3 defaults
  757. ///
  758. /// If [ThemeData.useMaterial3] is set to true the following defaults will
  759. /// be used:
  760. ///
  761. /// * `textStyle` - null
  762. /// * `backgroundColor` - transparent
  763. /// * `foregroundColor`
  764. /// * disabled - Theme.colorScheme.onSurface(0.38)
  765. /// * selected - Theme.colorScheme.primary
  766. /// * others - Theme.colorScheme.onSurfaceVariant
  767. /// * `overlayColor`
  768. /// * selected
  769. /// * hovered - Theme.colorScheme.primary(0.08)
  770. /// * focused or pressed - Theme.colorScheme.primary(0.12)
  771. /// * hovered or focused - Theme.colorScheme.onSurfaceVariant(0.08)
  772. /// * pressed - Theme.colorScheme.onSurfaceVariant(0.12)
  773. /// * others - null
  774. /// * `shadowColor` - null
  775. /// * `surfaceTintColor` - null
  776. /// * `elevation` - 0
  777. /// * `padding` - all(8)
  778. /// * `minimumSize` - Size(40, 40)
  779. /// * `fixedSize` - null
  780. /// * `maximumSize` - Size.infinite
  781. /// * `iconSize` - 24
  782. /// * `side` - null
  783. /// * `shape` - StadiumBorder()
  784. /// * `mouseCursor`
  785. /// * disabled - SystemMouseCursors.basic
  786. /// * others - SystemMouseCursors.click
  787. /// * `visualDensity` - VisualDensity.standard
  788. /// * `tapTargetSize` - theme.materialTapTargetSize
  789. /// * `animationDuration` - kThemeChangeDuration
  790. /// * `enableFeedback` - true
  791. /// * `alignment` - Alignment.center
  792. /// * `splashFactory` - Theme.splashFactory
  793. @override
  794. ButtonStyle defaultStyleOf(BuildContext context) {
  795. return _IconButtonDefaultsM3(context);
  796. }
  797. /// Returns the [IconButtonThemeData.style] of the closest [IconButtonTheme] ancestor.
  798. /// The color and icon size can also be configured by the [IconTheme] if the same property
  799. /// has a null value in [IconButtonTheme]. However, if any of the properties exist
  800. /// in both [IconButtonTheme] and [IconTheme], [IconTheme] will be overridden.
  801. @override
  802. ButtonStyle? themeStyleOf(BuildContext context) {
  803. final IconThemeData iconTheme = IconTheme.of(context);
  804. final bool isDark = Theme.of(context).brightness == Brightness.dark;
  805. bool isIconThemeDefault(Color? color) {
  806. if (isDark) {
  807. return color == kDefaultIconLightColor;
  808. }
  809. return color == kDefaultIconDarkColor;
  810. }
  811. final bool isDefaultColor = isIconThemeDefault(iconTheme.color);
  812. final bool isDefaultSize =
  813. iconTheme.size == const IconThemeData.fallback().size;
  814. final ButtonStyle iconThemeStyle = CustomIconButton.styleFrom(
  815. foregroundColor: isDefaultColor ? null : iconTheme.color,
  816. iconSize: isDefaultSize ? null : iconTheme.size);
  817. return IconButtonTheme.of(context).style?.merge(iconThemeStyle) ??
  818. iconThemeStyle;
  819. }
  820. }
  821. @immutable
  822. class _IconButtonDefaultBackground extends MaterialStateProperty<Color?> {
  823. _IconButtonDefaultBackground(this.background, this.disabledBackground);
  824. final Color? background;
  825. final Color? disabledBackground;
  826. @override
  827. Color? resolve(Set<MaterialState> states) {
  828. if (states.contains(MaterialState.disabled)) {
  829. return disabledBackground;
  830. }
  831. return background;
  832. }
  833. @override
  834. String toString() {
  835. return '{disabled: $disabledBackground, otherwise: $background}';
  836. }
  837. }
  838. @immutable
  839. class _IconButtonDefaultForeground extends MaterialStateProperty<Color?> {
  840. _IconButtonDefaultForeground(
  841. this.foregroundColor, this.disabledForegroundColor);
  842. final Color? foregroundColor;
  843. final Color? disabledForegroundColor;
  844. @override
  845. Color? resolve(Set<MaterialState> states) {
  846. if (states.contains(MaterialState.disabled)) {
  847. return disabledForegroundColor;
  848. }
  849. return foregroundColor;
  850. }
  851. @override
  852. String toString() {
  853. return '{disabled: $disabledForegroundColor, otherwise: $foregroundColor}';
  854. }
  855. }
  856. @immutable
  857. class _IconButtonDefaultOverlay extends MaterialStateProperty<Color?> {
  858. _IconButtonDefaultOverlay(this.foregroundColor, this.focusColor,
  859. this.hoverColor, this.highlightColor);
  860. final Color? foregroundColor;
  861. final Color? focusColor;
  862. final Color? hoverColor;
  863. final Color? highlightColor;
  864. @override
  865. Color? resolve(Set<MaterialState> states) {
  866. if (states.contains(MaterialState.selected)) {
  867. if (states.contains(MaterialState.pressed)) {
  868. return highlightColor ?? foregroundColor?.withOpacity(0.12);
  869. }
  870. if (states.contains(MaterialState.hovered)) {
  871. return hoverColor ?? foregroundColor?.withOpacity(0.08);
  872. }
  873. if (states.contains(MaterialState.focused)) {
  874. return focusColor ?? foregroundColor?.withOpacity(0.12);
  875. }
  876. }
  877. if (states.contains(MaterialState.pressed)) {
  878. return highlightColor ?? foregroundColor?.withOpacity(0.12);
  879. }
  880. if (states.contains(MaterialState.hovered)) {
  881. return hoverColor ?? foregroundColor?.withOpacity(0.08);
  882. }
  883. if (states.contains(MaterialState.focused)) {
  884. return focusColor ?? foregroundColor?.withOpacity(0.08);
  885. }
  886. return null;
  887. }
  888. @override
  889. String toString() {
  890. return '{hovered: $hoverColor, focused: $focusColor, pressed: $highlightColor, otherwise: null}';
  891. }
  892. }
  893. @immutable
  894. class _IconButtonDefaultMouseCursor extends MaterialStateProperty<MouseCursor>
  895. with Diagnosticable {
  896. _IconButtonDefaultMouseCursor(this.enabledCursor, this.disabledCursor);
  897. final MouseCursor enabledCursor;
  898. final MouseCursor disabledCursor;
  899. @override
  900. MouseCursor resolve(Set<MaterialState> states) {
  901. if (states.contains(MaterialState.disabled)) {
  902. return disabledCursor;
  903. }
  904. return enabledCursor;
  905. }
  906. }
  907. // BEGIN GENERATED TOKEN PROPERTIES - IconButton
  908. // Do not edit by hand. The code between the "BEGIN GENERATED" and
  909. // "END GENERATED" comments are generated from data in the Material
  910. // Design token database by the script:
  911. // dev/tools/gen_defaults/bin/gen_defaults.dart.
  912. // Token database version: v0_132
  913. class _IconButtonDefaultsM3 extends ButtonStyle {
  914. _IconButtonDefaultsM3(this.context)
  915. : super(
  916. animationDuration: kThemeChangeDuration,
  917. enableFeedback: true,
  918. alignment: Alignment.center,
  919. );
  920. final BuildContext context;
  921. late final ColorScheme _colors = Theme.of(context).colorScheme;
  922. // No default text style
  923. @override
  924. MaterialStateProperty<Color?>? get backgroundColor =>
  925. const MaterialStatePropertyAll<Color?>(Colors.transparent);
  926. @override
  927. MaterialStateProperty<Color?>? get foregroundColor =>
  928. MaterialStateProperty.resolveWith((Set<MaterialState> states) {
  929. if (states.contains(MaterialState.disabled)) {
  930. return _colors.onSurface.withOpacity(0.38);
  931. }
  932. if (states.contains(MaterialState.selected)) {
  933. return _colors.primary;
  934. }
  935. return _colors.onSurfaceVariant;
  936. });
  937. @override
  938. MaterialStateProperty<Color?>? get overlayColor =>
  939. MaterialStateProperty.resolveWith((Set<MaterialState> states) {
  940. if (states.contains(MaterialState.selected)) {
  941. if (states.contains(MaterialState.hovered)) {
  942. return _colors.primary.withOpacity(0.08);
  943. }
  944. if (states.contains(MaterialState.focused)) {
  945. return _colors.primary.withOpacity(0.12);
  946. }
  947. if (states.contains(MaterialState.pressed)) {
  948. return _colors.primary.withOpacity(0.12);
  949. }
  950. }
  951. if (states.contains(MaterialState.hovered)) {
  952. return _colors.onSurfaceVariant.withOpacity(0.08);
  953. }
  954. if (states.contains(MaterialState.focused)) {
  955. return _colors.onSurfaceVariant.withOpacity(0.08);
  956. }
  957. if (states.contains(MaterialState.pressed)) {
  958. return _colors.onSurfaceVariant.withOpacity(0.12);
  959. }
  960. return null;
  961. });
  962. @override
  963. MaterialStateProperty<double>? get elevation =>
  964. const MaterialStatePropertyAll<double>(0.0);
  965. @override
  966. MaterialStateProperty<Color>? get shadowColor =>
  967. const MaterialStatePropertyAll<Color>(Colors.transparent);
  968. @override
  969. MaterialStateProperty<Color>? get surfaceTintColor =>
  970. const MaterialStatePropertyAll<Color>(Colors.transparent);
  971. @override
  972. MaterialStateProperty<EdgeInsetsGeometry>? get padding =>
  973. MaterialStatePropertyAll<EdgeInsetsGeometry>(EdgeInsets.all(8.s));
  974. @override
  975. MaterialStateProperty<Size>? get minimumSize =>
  976. MaterialStatePropertyAll<Size>(Size(40.s, 40.s));
  977. // No default fixedSize
  978. @override
  979. MaterialStateProperty<Size>? get maximumSize =>
  980. const MaterialStatePropertyAll<Size>(Size.infinite);
  981. @override
  982. MaterialStateProperty<double>? get iconSize =>
  983. MaterialStatePropertyAll<double>(24.s);
  984. // No default side
  985. @override
  986. MaterialStateProperty<OutlinedBorder>? get shape =>
  987. const MaterialStatePropertyAll<OutlinedBorder>(StadiumBorder());
  988. @override
  989. MaterialStateProperty<MouseCursor?>? get mouseCursor =>
  990. MaterialStateProperty.resolveWith((Set<MaterialState> states) {
  991. if (states.contains(MaterialState.disabled)) {
  992. return SystemMouseCursors.basic;
  993. }
  994. return SystemMouseCursors.click;
  995. });
  996. @override
  997. VisualDensity? get visualDensity => VisualDensity.standard;
  998. @override
  999. MaterialTapTargetSize? get tapTargetSize =>
  1000. Theme.of(context).materialTapTargetSize;
  1001. @override
  1002. InteractiveInkFeatureFactory? get splashFactory =>
  1003. Theme.of(context).splashFactory;
  1004. }
  1005. // END GENERATED TOKEN PROPERTIES - IconButton