media_query.dart 42.9 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5
import 'dart:math' as math;
6
import 'dart:ui' as ui;
7
import 'dart:ui' show Brightness;
8

9
import 'package:flutter/foundation.dart';
10
import 'package:flutter/gestures.dart';
11

12
import 'basic.dart';
13
import 'binding.dart';
14
import 'debug.dart';
15 16
import 'framework.dart';

17 18 19 20
/// Whether in portrait or landscape.
enum Orientation {
  /// Taller than wide.
  portrait,
21

22 23 24
  /// Wider than tall.
  landscape
}
25

26 27 28 29 30 31 32 33
/// Information about a piece of media (e.g., a window).
///
/// For example, the [MediaQueryData.size] property contains the width and
/// height of the current window.
///
/// To obtain the current [MediaQueryData] for a given [BuildContext], use the
/// [MediaQuery.of] function. For example, to obtain the size of the current
/// window, use `MediaQuery.of(context).size`.
34 35
///
/// If no [MediaQuery] is in scope then the [MediaQuery.of] method will throw an
36 37
/// exception. Alternatively, [MediaQuery.maybeOf] may be used, which returns
/// null instead of throwing if no [MediaQuery] is in scope.
38
///
39
/// ## Insets and Padding
40
///
41 42
/// ![A diagram of padding, viewInsets, and viewPadding in correlation with each
/// other](https://flutter.github.io/assets-for-api-docs/assets/widgets/media_query.png)
43
///
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
/// This diagram illustrates how [padding] relates to [viewPadding] and
/// [viewInsets], shown here in its simplest configuration, as the difference
/// between the two. In cases when the viewInsets exceed the viewPadding, like
/// when a software keyboard is shown below, padding goes to zero rather than a
/// negative value. Therefore, padding is calculated by taking
/// `max(0.0, viewPadding - viewInsets)`.
///
/// {@animation 300 300 https://flutter.github.io/assets-for-api-docs/assets/widgets/window_padding.mp4}
///
/// In this diagram, the black areas represent system UI that the app cannot
/// draw over. The red area represents view padding that the application may not
/// be able to detect gestures in and may not want to draw in. The grey area
/// represents the system keyboard, which can cover over the bottom view padding
/// when visible.
///
/// MediaQueryData includes three [EdgeInsets] values:
/// [padding], [viewPadding], and [viewInsets]. These values reflect the
/// configuration of the device and are used and optionally consumed by widgets
/// that position content within these insets. The padding value defines areas
/// that might not be completely visible, like the display "notch" on the iPhone
/// X. The viewInsets value defines areas that aren't visible at all, typically
/// because they're obscured by the device's keyboard. Similar to viewInsets,
/// viewPadding does not differentiate padding in areas that may be obscured.
/// For example, by using the viewPadding property, padding would defer to the
/// iPhone "safe area" regardless of whether a keyboard is showing.
///
70 71
/// {@youtube 560 315 https://www.youtube.com/watch?v=ceCo8U0XHqw}
///
72 73 74 75 76 77 78
/// The viewInsets and viewPadding are independent values, they're
/// measured from the edges of the MediaQuery widget's bounds. Together they
/// inform the [padding] property. The bounds of the top level MediaQuery
/// created by [WidgetsApp] are the same as the window that contains the app.
///
/// Widgets whose layouts consume space defined by [viewInsets], [viewPadding],
/// or [padding] should enclose their children in secondary MediaQuery
79
/// widgets that reduce those properties by the same amount.
80
/// The [removePadding], [removeViewPadding], and [removeViewInsets] methods are
81
/// useful for this.
82
///
83 84
/// See also:
///
85 86 87
///  * [Scaffold], [SafeArea], [CupertinoTabScaffold], and
///    [CupertinoPageScaffold], all of which are informed by [padding],
///    [viewPadding], and [viewInsets].
88
@immutable
89
class MediaQueryData {
90 91 92
  /// Creates data for a media query with explicit values.
  ///
  /// Consider using [MediaQueryData.fromWindow] to create data based on a
93
  /// [dart:ui.PlatformDispatcher].
94
  const MediaQueryData({
95 96 97
    this.size = Size.zero,
    this.devicePixelRatio = 1.0,
    this.textScaleFactor = 1.0,
98
    this.platformBrightness = Brightness.light,
99 100
    this.padding = EdgeInsets.zero,
    this.viewInsets = EdgeInsets.zero,
101
    this.systemGestureInsets = EdgeInsets.zero,
102
    this.viewPadding = EdgeInsets.zero,
103
    this.alwaysUse24HourFormat = false,
104 105
    this.accessibleNavigation = false,
    this.invertColors = false,
106
    this.highContrast = false,
107
    this.disableAnimations = false,
108
    this.boldText = false,
109
    this.navigationMode = NavigationMode.traditional,
110 111
    this.gestureSettings = const DeviceGestureSettings(touchSlop: kTouchSlop),
    this.displayFeatures = const <ui.DisplayFeature>[],
112 113 114 115 116 117 118 119 120 121 122 123 124
  }) : assert(size != null),
       assert(devicePixelRatio != null),
       assert(textScaleFactor != null),
       assert(platformBrightness != null),
       assert(padding != null),
       assert(viewInsets != null),
       assert(systemGestureInsets != null),
       assert(viewPadding != null),
       assert(alwaysUse24HourFormat != null),
       assert(accessibleNavigation != null),
       assert(invertColors != null),
       assert(highContrast != null),
       assert(disableAnimations != null),
125
       assert(boldText != null),
126
       assert(navigationMode != null),
127 128
       assert(gestureSettings != null),
       assert(displayFeatures != null);
129

130
  /// Creates data for a media query based on the given window.
131 132 133 134
  ///
  /// If you use this, you should ensure that you also register for
  /// notifications so that you can update your [MediaQueryData] when the
  /// window's metrics change. For example, see
135 136 137
  /// [WidgetsBindingObserver.didChangeMetrics] or
  /// [dart:ui.PlatformDispatcher.onMetricsChanged].
  MediaQueryData.fromWindow(ui.SingletonFlutterWindow window)
138
    : size = window.physicalSize / window.devicePixelRatio,
139
      devicePixelRatio = window.devicePixelRatio,
140
      textScaleFactor = window.textScaleFactor,
141
      platformBrightness = window.platformBrightness,
142
      padding = EdgeInsets.fromWindowPadding(window.padding, window.devicePixelRatio),
143
      viewPadding = EdgeInsets.fromWindowPadding(window.viewPadding, window.devicePixelRatio),
144
      viewInsets = EdgeInsets.fromWindowPadding(window.viewInsets, window.devicePixelRatio),
145
      systemGestureInsets = EdgeInsets.fromWindowPadding(window.systemGestureInsets, window.devicePixelRatio),
146
      accessibleNavigation = window.accessibilityFeatures.accessibleNavigation,
147
      invertColors = window.accessibilityFeatures.invertColors,
148
      disableAnimations = window.accessibilityFeatures.disableAnimations,
149
      boldText = window.accessibilityFeatures.boldText,
150
      highContrast = window.accessibilityFeatures.highContrast,
151
      alwaysUse24HourFormat = window.alwaysUse24HourFormat,
152
      navigationMode = NavigationMode.traditional,
153 154
      gestureSettings = DeviceGestureSettings.fromWindow(window),
      displayFeatures = window.displayFeatures;
155

156
  /// The size of the media in logical pixels (e.g, the size of the screen).
157 158 159 160 161
  ///
  /// Logical pixels are roughly the same visual size across devices. Physical
  /// pixels are the size of the actual hardware pixels on the device. The
  /// number of physical pixels per logical pixel is described by the
  /// [devicePixelRatio].
162 163
  final Size size;

164 165 166 167 168
  /// The number of device pixels for each logical pixel. This number might not
  /// be a power of two. Indeed, it might not even be an integer. For example,
  /// the Nexus 6 has a device pixel ratio of 3.5.
  final double devicePixelRatio;

169 170 171 172
  /// The number of font pixels for each logical pixel.
  ///
  /// For example, if the text scale factor is 1.5, text will be 50% larger than
  /// the specified font size.
173 174 175 176 177
  ///
  /// See also:
  ///
  ///  * [MediaQuery.textScaleFactorOf], a convenience method which returns the
  ///    textScaleFactor defined for a [BuildContext].
178 179
  final double textScaleFactor;

180 181 182 183 184 185 186 187 188
  /// The current brightness mode of the host platform.
  ///
  /// For example, starting in Android Pie, battery saver mode asks all apps to
  /// render in a "dark mode".
  ///
  /// Not all platforms necessarily support a concept of brightness mode. Those
  /// platforms will report [Brightness.light] in this property.
  final Brightness platformBrightness;

189 190 191 192 193 194
  /// The parts of the display that are completely obscured by system UI,
  /// typically by the device's keyboard.
  ///
  /// When a mobile device's keyboard is visible `viewInsets.bottom`
  /// corresponds to the top of the keyboard.
  ///
195 196 197 198 199
  /// This value is independent of the [padding] and [viewPadding]. viewPadding
  /// is measured from the edges of the [MediaQuery] widget's bounds. Padding is
  /// calculated based on the viewPadding and viewInsets. The bounds of the top
  /// level MediaQuery created by [WidgetsApp] are the same as the window
  /// (often the mobile device screen) that contains the app.
200 201 202
  ///
  /// See also:
  ///
203 204
  ///  * [ui.window], which provides some additional detail about this property
  ///    and how it relates to [padding] and [viewPadding].
205 206
  final EdgeInsets viewInsets;

207 208
  /// The parts of the display that are partially obscured by system UI,
  /// typically by the hardware display "notches" or the system status bar.
209 210 211 212
  ///
  /// If you consumed this padding (e.g. by building a widget that envelops or
  /// accounts for this padding in its layout in such a way that children are
  /// no longer exposed to this padding), you should remove this padding
213
  /// for subsequent descendants in the widget tree by inserting a new
214 215
  /// [MediaQuery] widget using the [MediaQuery.removePadding] factory.
  ///
216
  /// Padding is derived from the values of [viewInsets] and [viewPadding].
217
  ///
218 219
  /// See also:
  ///
220 221
  ///  * [ui.window], which provides some additional detail about this
  ///    property and how it relates to [viewInsets] and [viewPadding].
222 223
  ///  * [SafeArea], a widget that consumes this padding with a [Padding] widget
  ///    and automatically removes it from the [MediaQuery] for its child.
224
  final EdgeInsets padding;
225

226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
  /// The parts of the display that are partially obscured by system UI,
  /// typically by the hardware display "notches" or the system status bar.
  ///
  /// This value remains the same regardless of whether the system is reporting
  /// other obstructions in the same physical area of the screen. For example, a
  /// software keyboard on the bottom of the screen that may cover and consume
  /// the same area that requires bottom padding will not affect this value.
  ///
  /// This value is independent of the [padding] and [viewInsets]: their values
  /// are measured from the edges of the [MediaQuery] widget's bounds. The
  /// bounds of the top level MediaQuery created by [WidgetsApp] are the
  /// same as the window that contains the app. On mobile devices, this will
  /// typically be the full screen.
  ///
  /// See also:
  ///
242 243
  ///  * [ui.window], which provides some additional detail about this
  ///    property and how it relates to [padding] and [viewInsets].
244 245
  final EdgeInsets viewPadding;

246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262
  /// The areas along the edges of the display where the system consumes
  /// certain input events and blocks delivery of those events to the app.
  ///
  /// Starting with Android Q, simple swipe gestures that start within the
  /// [systemGestureInsets] areas are used by the system for page navigation
  /// and may not be delivered to the app. Taps and swipe gestures that begin
  /// with a long-press are delivered to the app, but simple press-drag-release
  /// swipe gestures which begin within the area defined by [systemGestureInsets]
  /// may not be.
  ///
  /// Apps should avoid locating gesture detectors within the system gesture
  /// insets area. Apps should feel free to put visual elements within
  /// this area.
  ///
  /// This property is currently only expected to be set to a non-default value
  /// on Android starting with version Q.
  ///
263
  /// {@tool dartpad}
264
  /// For apps that might be deployed on Android Q devices with full gesture
265
  /// navigation enabled, use [systemGestureInsets] with [Padding]
266 267 268 269 270 271
  /// to avoid having the left and right edges of the [Slider] from appearing
  /// within the area reserved for system gesture navigation.
  ///
  /// By default, [Slider]s expand to fill the available width. So, we pad the
  /// left and right sides.
  ///
272
  /// ** See code in examples/api/lib/widgets/media_query/media_query_data.system_gesture_insets.0.dart **
273 274 275
  /// {@end-tool}
  final EdgeInsets systemGestureInsets;

276 277 278 279 280 281 282 283 284 285 286 287 288
  /// Whether to use 24-hour format when formatting time.
  ///
  /// The behavior of this flag is different across platforms:
  ///
  /// - On Android this flag is reported directly from the user settings called
  ///   "Use 24-hour format". It applies to any locale used by the application,
  ///   whether it is the system-wide locale, or the custom locale set by the
  ///   application.
  /// - On iOS this flag is set to true when the user setting called "24-Hour
  ///   Time" is set or the system-wide locale's default uses 24-hour
  ///   formatting.
  final bool alwaysUse24HourFormat;

289 290 291 292 293 294 295 296
  /// Whether the user is using an accessibility service like TalkBack or
  /// VoiceOver to interact with the application.
  ///
  /// When this setting is true, features such as timeouts should be disabled or
  /// have minimum durations increased.
  ///
  /// See also:
  ///
297
  ///  * [dart:ui.PlatformDispatcher.accessibilityFeatures], where the setting originates.
298 299 300 301 302 303 304 305
  final bool accessibleNavigation;

  /// Whether the device is inverting the colors of the platform.
  ///
  /// This flag is currently only updated on iOS devices.
  ///
  /// See also:
  ///
306 307
  ///  * [dart:ui.PlatformDispatcher.accessibilityFeatures], where the setting
  ///    originates.
308 309
  final bool invertColors;

310 311 312 313 314 315 316
  /// Whether the user requested a high contrast between foreground and background
  /// content on iOS, via Settings -> Accessibility -> Increase Contrast.
  ///
  /// This flag is currently only updated on iOS devices that are running iOS 13
  /// or above.
  final bool highContrast;

317 318 319
  /// Whether the platform is requesting that animations be disabled or reduced
  /// as much as possible.
  ///
320 321
  /// See also:
  ///
322 323
  ///  * [dart:ui.PlatformDispatcher.accessibilityFeatures], where the setting
  ///    originates.
324 325
  final bool disableAnimations;

326 327 328 329 330
  /// Whether the platform is requesting that text be drawn with a bold font
  /// weight.
  ///
  /// See also:
  ///
331 332
  ///  * [dart:ui.PlatformDispatcher.accessibilityFeatures], where the setting
  ///    originates.
333 334
  final bool boldText;

335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351
  /// Describes the navigation mode requested by the platform.
  ///
  /// Some user interfaces are better navigated using a directional pad (DPAD)
  /// or arrow keys, and for those interfaces, some widgets need to handle these
  /// directional events differently. In order to know when to do that, these
  /// widgets will look for the navigation mode in effect for their context.
  ///
  /// For instance, in a television interface, [NavigationMode.directional]
  /// should be set, so that directional navigation is used to navigate away
  /// from a text field using the DPAD. In contrast, on a regular desktop
  /// application with the `navigationMode` set to [NavigationMode.traditional],
  /// the arrow keys are used to move the cursor instead of navigating away.
  ///
  /// The [NavigationMode] values indicate the type of navigation to be used in
  /// a widget subtree for those widgets sensitive to it.
  final NavigationMode navigationMode;

352 353 354 355 356 357 358
  /// The gesture settings for the view this media query is derived from.
  ///
  /// This contains platform specific configuration for gesture behavior,
  /// such as touch slop. These settings should be favored for configuring
  /// gesture behavior over the framework constants.
  final DeviceGestureSettings gestureSettings;

359 360 361 362 363 364 365 366 367 368 369
  /// {@macro dart.ui.ViewConfiguration.displayFeatures}
  ///
  /// See also:
  ///
  ///  * [dart:ui.DisplayFeatureType], which lists the different types of
  ///  display features and explains the differences between them.
  ///  * [dart:ui.DisplayFeatureState], which lists the possible states for
  ///  folding features ([dart:ui.DisplayFeatureType.fold] and
  ///  [dart:ui.DisplayFeatureType.hinge]).
  final List<ui.DisplayFeature> displayFeatures;

370 371
  /// The orientation of the media (e.g., whether the device is in landscape or
  /// portrait mode).
372 373 374 375
  Orientation get orientation {
    return size.width > size.height ? Orientation.landscape : Orientation.portrait;
  }

376 377
  /// Creates a copy of this media query data but with the given fields replaced
  /// with the new values.
378
  MediaQueryData copyWith({
379 380 381 382 383 384 385 386 387 388 389 390 391 392 393
    Size? size,
    double? devicePixelRatio,
    double? textScaleFactor,
    Brightness? platformBrightness,
    EdgeInsets? padding,
    EdgeInsets? viewPadding,
    EdgeInsets? viewInsets,
    EdgeInsets? systemGestureInsets,
    bool? alwaysUse24HourFormat,
    bool? highContrast,
    bool? disableAnimations,
    bool? invertColors,
    bool? accessibleNavigation,
    bool? boldText,
    NavigationMode? navigationMode,
394
    DeviceGestureSettings? gestureSettings,
395
    List<ui.DisplayFeature>? displayFeatures,
396
  }) {
397
    return MediaQueryData(
398 399 400
      size: size ?? this.size,
      devicePixelRatio: devicePixelRatio ?? this.devicePixelRatio,
      textScaleFactor: textScaleFactor ?? this.textScaleFactor,
401
      platformBrightness: platformBrightness ?? this.platformBrightness,
402
      padding: padding ?? this.padding,
403
      viewPadding: viewPadding ?? this.viewPadding,
404
      viewInsets: viewInsets ?? this.viewInsets,
405
      systemGestureInsets: systemGestureInsets ?? this.systemGestureInsets,
406
      alwaysUse24HourFormat: alwaysUse24HourFormat ?? this.alwaysUse24HourFormat,
407
      invertColors: invertColors ?? this.invertColors,
408
      highContrast: highContrast ?? this.highContrast,
409 410
      disableAnimations: disableAnimations ?? this.disableAnimations,
      accessibleNavigation: accessibleNavigation ?? this.accessibleNavigation,
411
      boldText: boldText ?? this.boldText,
412
      navigationMode: navigationMode ?? this.navigationMode,
413
      gestureSettings: gestureSettings ?? this.gestureSettings,
414
      displayFeatures: displayFeatures ?? this.displayFeatures,
415 416 417
    );
  }

418
  /// Creates a copy of this media query data but with the given [padding]s
Ian Hickson's avatar
Ian Hickson committed
419 420 421 422 423 424 425 426
  /// replaced with zero.
  ///
  /// The `removeLeft`, `removeTop`, `removeRight`, and `removeBottom` arguments
  /// must not be null. If all four are false (the default) then this
  /// [MediaQueryData] is returned unmodified.
  ///
  /// See also:
  ///
427
  ///  * [MediaQuery.removePadding], which uses this method to remove [padding]
Ian Hickson's avatar
Ian Hickson committed
428 429 430
  ///    from the ambient [MediaQuery].
  ///  * [SafeArea], which both removes the padding from the [MediaQuery] and
  ///    adds a [Padding] widget.
431
  ///  * [removeViewInsets], the same thing but for [viewInsets].
432
  ///  * [removeViewPadding], the same thing but for [viewPadding].
Ian Hickson's avatar
Ian Hickson committed
433
  MediaQueryData removePadding({
434 435 436 437
    bool removeLeft = false,
    bool removeTop = false,
    bool removeRight = false,
    bool removeBottom = false,
Ian Hickson's avatar
Ian Hickson committed
438 439 440
  }) {
    if (!(removeLeft || removeTop || removeRight || removeBottom))
      return this;
441
    return MediaQueryData(
Ian Hickson's avatar
Ian Hickson committed
442 443 444
      size: size,
      devicePixelRatio: devicePixelRatio,
      textScaleFactor: textScaleFactor,
445
      platformBrightness: platformBrightness,
Ian Hickson's avatar
Ian Hickson committed
446 447 448 449 450 451
      padding: padding.copyWith(
        left: removeLeft ? 0.0 : null,
        top: removeTop ? 0.0 : null,
        right: removeRight ? 0.0 : null,
        bottom: removeBottom ? 0.0 : null,
      ),
452
      viewPadding: viewPadding.copyWith(
453 454 455 456
        left: removeLeft ? math.max(0.0, viewPadding.left - padding.left) : null,
        top: removeTop ? math.max(0.0, viewPadding.top - padding.top) : null,
        right: removeRight ? math.max(0.0, viewPadding.right - padding.right) : null,
        bottom: removeBottom ? math.max(0.0, viewPadding.bottom - padding.bottom) : null,
457
      ),
458
      viewInsets: viewInsets,
459
      alwaysUse24HourFormat: alwaysUse24HourFormat,
460
      highContrast: highContrast,
461 462 463
      disableAnimations: disableAnimations,
      invertColors: invertColors,
      accessibleNavigation: accessibleNavigation,
464
      boldText: boldText,
465
      gestureSettings: gestureSettings,
466
      displayFeatures: displayFeatures,
Ian Hickson's avatar
Ian Hickson committed
467 468 469
    );
  }

470 471 472 473 474 475 476 477 478
  /// Creates a copy of this media query data but with the given [viewInsets]
  /// replaced with zero.
  ///
  /// The `removeLeft`, `removeTop`, `removeRight`, and `removeBottom` arguments
  /// must not be null. If all four are false (the default) then this
  /// [MediaQueryData] is returned unmodified.
  ///
  /// See also:
  ///
479 480
  ///  * [MediaQuery.removeViewInsets], which uses this method to remove
  ///    [viewInsets] from the ambient [MediaQuery].
481
  ///  * [removePadding], the same thing but for [padding].
482
  ///  * [removeViewPadding], the same thing but for [viewPadding].
483
  MediaQueryData removeViewInsets({
484 485 486 487
    bool removeLeft = false,
    bool removeTop = false,
    bool removeRight = false,
    bool removeBottom = false,
488 489 490
  }) {
    if (!(removeLeft || removeTop || removeRight || removeBottom))
      return this;
491
    return MediaQueryData(
492 493 494
      size: size,
      devicePixelRatio: devicePixelRatio,
      textScaleFactor: textScaleFactor,
495
      platformBrightness: platformBrightness,
496
      padding: padding,
497
      viewPadding: viewPadding.copyWith(
498 499 500 501
        left: removeLeft ? math.max(0.0, viewPadding.left - viewInsets.left) : null,
        top: removeTop ? math.max(0.0, viewPadding.top - viewInsets.top) : null,
        right: removeRight ? math.max(0.0, viewPadding.right - viewInsets.right) : null,
        bottom: removeBottom ? math.max(0.0, viewPadding.bottom - viewInsets.bottom) : null,
502
      ),
503 504 505 506 507 508 509
      viewInsets: viewInsets.copyWith(
        left: removeLeft ? 0.0 : null,
        top: removeTop ? 0.0 : null,
        right: removeRight ? 0.0 : null,
        bottom: removeBottom ? 0.0 : null,
      ),
      alwaysUse24HourFormat: alwaysUse24HourFormat,
510
      highContrast: highContrast,
511 512 513
      disableAnimations: disableAnimations,
      invertColors: invertColors,
      accessibleNavigation: accessibleNavigation,
514
      boldText: boldText,
515
      gestureSettings: gestureSettings,
516
      displayFeatures: displayFeatures,
517 518 519
    );
  }

520 521 522 523 524 525 526 527 528
  /// Creates a copy of this media query data but with the given [viewPadding]
  /// replaced with zero.
  ///
  /// The `removeLeft`, `removeTop`, `removeRight`, and `removeBottom` arguments
  /// must not be null. If all four are false (the default) then this
  /// [MediaQueryData] is returned unmodified.
  ///
  /// See also:
  ///
529 530
  ///  * [MediaQuery.removeViewPadding], which uses this method to remove
  ///    [viewPadding] from the ambient [MediaQuery].
531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559
  ///  * [removePadding], the same thing but for [padding].
  ///  * [removeViewInsets], the same thing but for [viewInsets].
  MediaQueryData removeViewPadding({
    bool removeLeft = false,
    bool removeTop = false,
    bool removeRight = false,
    bool removeBottom = false,
  }) {
    if (!(removeLeft || removeTop || removeRight || removeBottom))
      return this;
    return MediaQueryData(
      size: size,
      devicePixelRatio: devicePixelRatio,
      textScaleFactor: textScaleFactor,
      platformBrightness: platformBrightness,
      padding: padding.copyWith(
        left: removeLeft ? 0.0 : null,
        top: removeTop ? 0.0 : null,
        right: removeRight ? 0.0 : null,
        bottom: removeBottom ? 0.0 : null,
      ),
      viewInsets: viewInsets,
      viewPadding: viewPadding.copyWith(
        left: removeLeft ? 0.0 : null,
        top: removeTop ? 0.0 : null,
        right: removeRight ? 0.0 : null,
        bottom: removeBottom ? 0.0 : null,
      ),
      alwaysUse24HourFormat: alwaysUse24HourFormat,
560
      highContrast: highContrast,
561 562 563 564
      disableAnimations: disableAnimations,
      invertColors: invertColors,
      accessibleNavigation: accessibleNavigation,
      boldText: boldText,
565
      gestureSettings: gestureSettings,
566
      displayFeatures: displayFeatures,
567 568
    );
  }
569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617

  /// Creates a copy of this media query data by removing [displayFeatures] that
  /// are completely outside the given sub-screen and adjusting the [padding],
  /// [viewInsets] and [viewPadding] to be zero on the sides that are not
  /// included in the sub-screen.
  ///
  /// Returns unmodified [MediaQueryData] if the sub-screen coincides with the
  /// available screen space.
  ///
  /// Asserts in debug mode, if the given sub-screen is outside the available
  /// screen space.
  ///
  /// See also:
  ///
  ///  * [DisplayFeatureSubScreen], which removes the display features that
  ///    split the screen, from the [MediaQuery] and adds a [Padding] widget to
  ///    position the child to match the selected sub-screen.
  MediaQueryData removeDisplayFeatures(Rect subScreen) {
    assert(subScreen.left >= 0.0 && subScreen.top >= 0.0 &&
        subScreen.right <= size.width && subScreen.bottom <= size.height,
        "'subScreen' argument cannot be outside the bounds of the screen");
    if (subScreen.size == size && subScreen.topLeft == Offset.zero)
      return this;
    final double rightInset = size.width - subScreen.right;
    final double bottomInset = size.height - subScreen.bottom;
    return copyWith(
      padding: EdgeInsets.only(
        left: math.max(0.0, padding.left - subScreen.left),
        top: math.max(0.0, padding.top - subScreen.top),
        right: math.max(0.0, padding.right - rightInset),
        bottom: math.max(0.0, padding.bottom - bottomInset),
      ),
      viewPadding: EdgeInsets.only(
        left: math.max(0.0, viewPadding.left - subScreen.left),
        top: math.max(0.0, viewPadding.top - subScreen.top),
        right: math.max(0.0, viewPadding.right - rightInset),
        bottom: math.max(0.0, viewPadding.bottom - bottomInset),
      ),
      viewInsets: EdgeInsets.only(
        left: math.max(0.0, viewInsets.left - subScreen.left),
        top: math.max(0.0, viewInsets.top - subScreen.top),
        right: math.max(0.0, viewInsets.right - rightInset),
        bottom: math.max(0.0, viewInsets.bottom - bottomInset),
      ),
      displayFeatures: displayFeatures.where(
        (ui.DisplayFeature displayFeature) => subScreen.overlaps(displayFeature.bounds)
      ).toList(),
    );
  }
618

619
  @override
620
  bool operator ==(Object other) {
621 622
    if (other.runtimeType != runtimeType)
      return false;
623 624 625 626 627 628 629 630 631 632 633 634 635
    return other is MediaQueryData
        && other.size == size
        && other.devicePixelRatio == devicePixelRatio
        && other.textScaleFactor == textScaleFactor
        && other.platformBrightness == platformBrightness
        && other.padding == padding
        && other.viewPadding == viewPadding
        && other.viewInsets == viewInsets
        && other.alwaysUse24HourFormat == alwaysUse24HourFormat
        && other.highContrast == highContrast
        && other.disableAnimations == disableAnimations
        && other.invertColors == invertColors
        && other.accessibleNavigation == accessibleNavigation
636
        && other.boldText == boldText
637
        && other.navigationMode == navigationMode
638 639
        && other.gestureSettings == gestureSettings
        && listEquals(other.displayFeatures, displayFeatures);
640 641
  }

642
  @override
643 644 645 646 647
  int get hashCode {
    return hashValues(
      size,
      devicePixelRatio,
      textScaleFactor,
648
      platformBrightness,
649
      padding,
650
      viewPadding,
651 652
      viewInsets,
      alwaysUse24HourFormat,
653
      highContrast,
654 655 656
      disableAnimations,
      invertColors,
      accessibleNavigation,
657
      boldText,
658
      navigationMode,
659
      gestureSettings,
660
      hashList(displayFeatures),
661 662
    );
  }
663

664
  @override
665
  String toString() {
666 667 668 669 670 671 672 673 674 675 676 677 678 679
    final List<String> properties = <String>[
      'size: $size',
      'devicePixelRatio: ${devicePixelRatio.toStringAsFixed(1)}',
      'textScaleFactor: ${textScaleFactor.toStringAsFixed(1)}',
      'platformBrightness: $platformBrightness',
      'padding: $padding',
      'viewPadding: $viewPadding',
      'viewInsets: $viewInsets',
      'alwaysUse24HourFormat: $alwaysUse24HourFormat',
      'accessibleNavigation: $accessibleNavigation',
      'highContrast: $highContrast',
      'disableAnimations: $disableAnimations',
      'invertColors: $invertColors',
      'boldText: $boldText',
680
      'navigationMode: ${navigationMode.name}',
681
      'gestureSettings: $gestureSettings',
682
      'displayFeatures: $displayFeatures',
683 684
    ];
    return '${objectRuntimeType(this, 'MediaQueryData')}(${properties.join(', ')})';
685
  }
686 687
}

688
/// Establishes a subtree in which media queries resolve to the given data.
689 690 691 692 693 694 695 696 697
///
/// For example, to learn the size of the current media (e.g., the window
/// containing your app), you can read the [MediaQueryData.size] property from
/// the [MediaQueryData] returned by [MediaQuery.of]:
/// `MediaQuery.of(context).size`.
///
/// Querying the current media using [MediaQuery.of] will cause your widget to
/// rebuild automatically whenever the [MediaQueryData] changes (e.g., if the
/// user rotates their device).
698 699
///
/// If no [MediaQuery] is in scope then the [MediaQuery.of] method will throw an
700 701
/// exception. Alternatively, [MediaQuery.maybeOf] may be used, which returns
/// null instead of throwing if no [MediaQuery] is in scope.
702
///
703 704
/// {@youtube 560 315 https://www.youtube.com/watch?v=A3WrA4zAaPw}
///
705 706 707 708 709
/// See also:
///
///  * [WidgetsApp] and [MaterialApp], which introduce a [MediaQuery] and keep
///    it up to date with the current screen metrics as they change.
///  * [MediaQueryData], the data structure that represents the metrics.
710
class MediaQuery extends InheritedWidget {
711 712 713
  /// Creates a widget that provides [MediaQueryData] to its descendants.
  ///
  /// The [data] and [child] arguments must not be null.
714
  const MediaQuery({
715 716 717
    Key? key,
    required this.data,
    required Widget child,
718 719 720
  }) : assert(child != null),
       assert(data != null),
       super(key: key, child: child);
721

722 723
  /// Creates a new [MediaQuery] that inherits from the ambient [MediaQuery]
  /// from the given context, but removes the specified padding.
Ian Hickson's avatar
Ian Hickson committed
724
  ///
725
  /// This should be inserted into the widget tree when the [MediaQuery] padding
726
  /// is consumed by a widget in such a way that the padding is no longer
727
  /// exposed to the widget's descendants or siblings.
728
  ///
Ian Hickson's avatar
Ian Hickson committed
729 730 731 732 733 734 735 736 737 738 739 740 741 742
  /// The [context] argument is required, must not be null, and must have a
  /// [MediaQuery] in scope.
  ///
  /// The `removeLeft`, `removeTop`, `removeRight`, and `removeBottom` arguments
  /// must not be null. If all four are false (the default) then the returned
  /// [MediaQuery] reuses the ambient [MediaQueryData] unmodified, which is not
  /// particularly useful.
  ///
  /// The [child] argument is required and must not be null.
  ///
  /// See also:
  ///
  ///  * [SafeArea], which both removes the padding from the [MediaQuery] and
  ///    adds a [Padding] widget.
743 744 745 746 747
  ///  * [MediaQueryData.padding], the affected property of the
  ///    [MediaQueryData].
  ///  * [removeViewInsets], the same thing but for [MediaQueryData.viewInsets].
  ///  * [removeViewPadding], the same thing but for
  ///    [MediaQueryData.viewPadding].
Ian Hickson's avatar
Ian Hickson committed
748
  factory MediaQuery.removePadding({
749 750
    Key? key,
    required BuildContext context,
751 752 753 754
    bool removeLeft = false,
    bool removeTop = false,
    bool removeRight = false,
    bool removeBottom = false,
755
    required Widget child,
Ian Hickson's avatar
Ian Hickson committed
756
  }) {
757
    return MediaQuery(
Ian Hickson's avatar
Ian Hickson committed
758
      key: key,
759
      data: MediaQuery.of(context).removePadding(
Ian Hickson's avatar
Ian Hickson committed
760 761 762 763 764
        removeLeft: removeLeft,
        removeTop: removeTop,
        removeRight: removeRight,
        removeBottom: removeBottom,
      ),
765 766 767 768
      child: child,
    );
  }

769 770
  /// Creates a new [MediaQuery] that inherits from the ambient [MediaQuery]
  /// from the given context, but removes the specified view insets.
771 772 773
  ///
  /// This should be inserted into the widget tree when the [MediaQuery] view
  /// insets are consumed by a widget in such a way that the view insets are no
774
  /// longer exposed to the widget's descendants or siblings.
775 776 777 778 779 780 781 782 783 784 785 786 787
  ///
  /// The [context] argument is required, must not be null, and must have a
  /// [MediaQuery] in scope.
  ///
  /// The `removeLeft`, `removeTop`, `removeRight`, and `removeBottom` arguments
  /// must not be null. If all four are false (the default) then the returned
  /// [MediaQuery] reuses the ambient [MediaQueryData] unmodified, which is not
  /// particularly useful.
  ///
  /// The [child] argument is required and must not be null.
  ///
  /// See also:
  ///
788 789 790 791 792
  ///  * [MediaQueryData.viewInsets], the affected property of the
  ///    [MediaQueryData].
  ///  * [removePadding], the same thing but for [MediaQueryData.padding].
  ///  * [removeViewPadding], the same thing but for
  ///    [MediaQueryData.viewPadding].
793
  factory MediaQuery.removeViewInsets({
794 795
    Key? key,
    required BuildContext context,
796 797 798 799
    bool removeLeft = false,
    bool removeTop = false,
    bool removeRight = false,
    bool removeBottom = false,
800
    required Widget child,
801
  }) {
802
    return MediaQuery(
803
      key: key,
804
      data: MediaQuery.of(context).removeViewInsets(
805 806
        removeLeft: removeLeft,
        removeTop: removeTop,
807 808 809 810 811 812 813
        removeRight: removeRight,
        removeBottom: removeBottom,
      ),
      child: child,
    );
  }

814 815
  /// Creates a new [MediaQuery] that inherits from the ambient [MediaQuery]
  /// from the given context, but removes the specified view padding.
816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833
  ///
  /// This should be inserted into the widget tree when the [MediaQuery] view
  /// padding is consumed by a widget in such a way that the view padding is no
  /// longer exposed to the widget's descendants or siblings.
  ///
  /// The [context] argument is required, must not be null, and must have a
  /// [MediaQuery] in scope.
  ///
  /// The `removeLeft`, `removeTop`, `removeRight`, and `removeBottom` arguments
  /// must not be null. If all four are false (the default) then the returned
  /// [MediaQuery] reuses the ambient [MediaQueryData] unmodified, which is not
  /// particularly useful.
  ///
  /// The [child] argument is required and must not be null.
  ///
  /// See also:
  ///
  ///  * [MediaQueryData.viewPadding], the affected property of the
834
  ///    [MediaQueryData].
835 836
  ///  * [removePadding], the same thing but for [MediaQueryData.padding].
  ///  * [removeViewInsets], the same thing but for [MediaQueryData.viewInsets].
837
  factory MediaQuery.removeViewPadding({
838 839
    Key? key,
    required BuildContext context,
840 841 842 843
    bool removeLeft = false,
    bool removeTop = false,
    bool removeRight = false,
    bool removeBottom = false,
844
    required Widget child,
845 846 847
  }) {
    return MediaQuery(
      key: key,
848
      data: MediaQuery.of(context).removeViewPadding(
849 850
        removeLeft: removeLeft,
        removeTop: removeTop,
851 852 853
        removeRight: removeRight,
        removeBottom: removeBottom,
      ),
Ian Hickson's avatar
Ian Hickson committed
854 855 856 857
      child: child,
    );
  }

858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879
  /// Provides a [MediaQuery] which is built and updated using the latest
  /// [WidgetsBinding.window] values.
  ///
  /// The [MediaQuery] is wrapped in a separate widget to ensure that only it
  /// and its dependents are updated when `window` changes, instead of
  /// rebuilding the whole widget tree.
  ///
  /// This should be inserted into the widget tree when the [MediaQuery] view
  /// padding is consumed by a widget in such a way that the view padding is no
  /// longer exposed to the widget's descendants or siblings.
  ///
  /// The [child] argument is required and must not be null.
  static Widget fromWindow({
    Key? key,
    required Widget child,
  }) {
    return _MediaQueryFromWindow(
      key: key,
      child: child,
    );
  }

880 881 882 883
  /// Contains information about the current media.
  ///
  /// For example, the [MediaQueryData.size] property contains the width and
  /// height of the current window.
884 885
  final MediaQueryData data;

886 887
  /// The data from the closest instance of this class that encloses the given
  /// context.
888
  ///
889 890 891 892
  /// You can use this function to query the size and orientation of the screen,
  /// as well as other media parameters (see [MediaQueryData] for more
  /// examples). When that information changes, your widget will be scheduled to
  /// be rebuilt, keeping your widget up-to-date.
893 894 895 896 897 898
  ///
  /// Typical usage is as follows:
  ///
  /// ```dart
  /// MediaQueryData media = MediaQuery.of(context);
  /// ```
899
  ///
900 901 902
  /// If there is no [MediaQuery] in scope, this will throw a [TypeError]
  /// exception in release builds, and throw a descriptive [FlutterError] in
  /// debug builds.
903
  ///
904 905 906 907 908
  /// See also:
  ///
  ///  * [maybeOf], which doesn't throw or assert if it doesn't find a
  ///    [MediaQuery] ancestor, it returns null instead.
  static MediaQueryData of(BuildContext context) {
Ian Hickson's avatar
Ian Hickson committed
909
    assert(context != null);
910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943
    assert(debugCheckHasMediaQuery(context));
    return context.dependOnInheritedWidgetOfExactType<MediaQuery>()!.data;
  }

  /// The data from the closest instance of this class that encloses the given
  /// context, if any.
  ///
  /// Use this function if you want to allow situations where no [MediaQuery] is
  /// in scope. Prefer using [MediaQuery.of] in situations where a media query
  /// is always expected to exist.
  ///
  /// If there is no [MediaQuery] in scope, then this function will return null.
  ///
  /// You can use this function to query the size and orientation of the screen,
  /// as well as other media parameters (see [MediaQueryData] for more
  /// examples). When that information changes, your widget will be scheduled to
  /// be rebuilt, keeping your widget up-to-date.
  ///
  /// Typical usage is as follows:
  ///
  /// ```dart
  /// MediaQueryData? mediaQuery = MediaQuery.maybeOf(context);
  /// if (mediaQuery == null) {
  ///   // Do something else instead.
  /// }
  /// ```
  ///
  /// See also:
  ///
  ///  * [of], which will throw if it doesn't find a [MediaQuery] ancestor,
  ///    instead of returning null.
  static MediaQueryData? maybeOf(BuildContext context) {
    assert(context != null);
    return context.dependOnInheritedWidgetOfExactType<MediaQuery>()?.data;
944 945
  }

946 947 948
  /// Returns textScaleFactor for the nearest MediaQuery ancestor or 1.0, if
  /// no such ancestor exists.
  static double textScaleFactorOf(BuildContext context) {
949
    return MediaQuery.maybeOf(context)?.textScaleFactor ?? 1.0;
950 951
  }

952 953 954 955 956 957
  /// Returns platformBrightness for the nearest MediaQuery ancestor or
  /// [Brightness.light], if no such ancestor exists.
  ///
  /// Use of this method will cause the given [context] to rebuild any time that
  /// any property of the ancestor [MediaQuery] changes.
  static Brightness platformBrightnessOf(BuildContext context) {
958
    return MediaQuery.maybeOf(context)?.platformBrightness ?? Brightness.light;
959 960
  }

961 962 963 964 965 966 967 968
  /// Returns highContrast for the nearest MediaQuery ancestor or false, if no
  /// such ancestor exists.
  ///
  /// See also:
  ///
  ///  * [MediaQueryData.highContrast], which indicates the platform's
  ///    desire to increase contrast.
  static bool highContrastOf(BuildContext context) {
969
    return MediaQuery.maybeOf(context)?.highContrast ?? false;
970 971
  }

972 973 974
  /// Returns the boldText accessibility setting for the nearest MediaQuery
  /// ancestor, or false if no such ancestor exists.
  static bool boldTextOverride(BuildContext context) {
975
    return MediaQuery.maybeOf(context)?.boldText ?? false;
976 977
  }

978
  @override
979
  bool updateShouldNotify(MediaQuery oldWidget) => data != oldWidget.data;
980

981
  @override
982 983
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
984
    properties.add(DiagnosticsProperty<MediaQueryData>('data', data, showName: false));
985 986
  }
}
987

988
/// Describes the navigation mode to be set by a [MediaQuery] widget.
989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014
///
/// The different modes indicate the type of navigation to be used in a widget
/// subtree for those widgets sensitive to it.
///
/// Use `MediaQuery.of(context).navigationMode` to determine the navigation mode
/// in effect for the given context. Use a [MediaQuery] widget to set the
/// navigation mode for its descendant widgets.
enum NavigationMode {
  /// This indicates a traditional keyboard-and-mouse navigation modality.
  ///
  /// This navigation mode is where the arrow keys can be used for secondary
  /// modification operations, like moving sliders or cursors, and disabled
  /// controls will lose focus and not be traversable.
  traditional,

  /// This indicates a directional-based navigation mode.
  ///
  /// This navigation mode indicates that arrow keys should be reserved for
  /// navigation operations, and secondary modifications operations, like moving
  /// sliders or cursors, will use alternative bindings or be disabled.
  ///
  /// Some behaviors are also affected by this mode. For instance, disabled
  /// controls will retain focus when disabled, and will be able to receive
  /// focus (although they remain disabled) when traversed.
  directional,
}
1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051

/// Provides a [MediaQuery] which is built and updated using the latest
/// [WidgetsBinding.window] values.
///
/// Receives `window` updates by listening to [WidgetsBinding].
///
/// The standalone widget ensures that it rebuilds **only** [MediaQuery] and
/// its dependents when `window` changes, instead of rebuilding the entire
/// widget tree.
///
/// It is used by [WidgetsApp] if no other [MediaQuery] is available above it.
///
/// See also:
///
///  * [MediaQuery], which establishes a subtree in which media queries resolve
///    to a [MediaQueryData].
class _MediaQueryFromWindow extends StatefulWidget {
  /// Creates a [_MediaQueryFromWindow] that provides a [MediaQuery] to its
  /// descendants using the `window` to keep [MediaQueryData] up to date.
  ///
  /// The [child] must not be null.
  const _MediaQueryFromWindow({
    Key? key,
    required this.child,
  }) : super(key: key);

  /// {@macro flutter.widgets.ProxyWidget.child}
  final Widget child;

  @override
  State<_MediaQueryFromWindow> createState() => _MediaQueryFromWindowState();
}

class _MediaQueryFromWindowState extends State<_MediaQueryFromWindow> with WidgetsBindingObserver {
  @override
  void initState() {
    super.initState();
1052
    WidgetsBinding.instance.addObserver(this);
1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095
  }

  // ACCESSIBILITY

  @override
  void didChangeAccessibilityFeatures() {
    setState(() {
      // The properties of window have changed. We use them in our build
      // function, so we need setState(), but we don't cache anything locally.
    });
  }

  // METRICS

  @override
  void didChangeMetrics() {
    setState(() {
      // The properties of window have changed. We use them in our build
      // function, so we need setState(), but we don't cache anything locally.
    });
  }

  @override
  void didChangeTextScaleFactor() {
    setState(() {
      // The textScaleFactor property of window has changed. We reference
      // window in our build function, so we need to call setState(), but
      // we don't need to cache anything locally.
    });
  }

  // RENDERING
  @override
  void didChangePlatformBrightness() {
    setState(() {
      // The platformBrightness property of window has changed. We reference
      // window in our build function, so we need to call setState(), but
      // we don't need to cache anything locally.
    });
  }

  @override
  Widget build(BuildContext context) {
1096
    MediaQueryData data = MediaQueryData.fromWindow(WidgetsBinding.instance.window);
1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107
    if (!kReleaseMode) {
      data = data.copyWith(platformBrightness: debugBrightnessOverride);
    }
    return MediaQuery(
      data: data,
      child: widget.child,
    );
  }

  @override
  void dispose() {
1108
    WidgetsBinding.instance.removeObserver(this);
1109 1110 1111
    super.dispose();
  }
}