Skip to content

Commit

Permalink
Preserve safe area (flutter#8848)
Browse files Browse the repository at this point in the history
Preserve safe area on Window regardless of insets.
  • Loading branch information
dnfield authored May 31, 2019
1 parent cbef680 commit 79c6ce1
Show file tree
Hide file tree
Showing 5 changed files with 174 additions and 24 deletions.
17 changes: 14 additions & 3 deletions lib/stub_ui/lib/src/ui/window.dart
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,10 @@ enum AppLifecycleState {

/// A representation of distances for each of the four edges of a rectangle,
/// used to encode the view insets and padding that applications should place
/// around their user interface, as exposed by [Window.viewInsets] and
/// [Window.padding]. View insets and padding are preferably read via
/// [MediaQuery.of].
/// around their user interface, as exposed by [Window.viewInsets],
/// [Window.viewPadding], and [Window.padding]. View insets and padding are
/// preferably read via [MediaQuery.of], since [MediaQuery] will notify its
/// descendants when these values are updated.
///
/// For a generic class that represents distances around a rectangle, see the
/// [EdgeInsets] class.
Expand Down Expand Up @@ -550,12 +551,22 @@ abstract class Window {
/// design applications.
WindowPadding get viewInsets => WindowPadding.zero;

WindowPadding get viewPadding => WindowPadding.zero;

/// The number of physical pixels on each side of the display rectangle into
/// which the application can render, but which may be partially obscured by
/// system UI (such as the system notification area), or or physical
/// intrusions in the display (e.g. overscan regions on television screens or
/// phone sensor housings).
///
/// This value is calculated by taking
/// `max(0.0, Window.viewPadding - Window.viewInsets)`. This will treat a
/// system IME that increases the bottm inset as consuming that much of the
/// bottom padding. For example, on an iPhone X, [Window.padding.bottom] is
/// the same as [Window.viewPadding.bottom] when the soft keyboard is not
/// drawn (to account for the bottom soft button area), but will be `0.0` when
/// the soft keyboard is visible.
///
/// When this changes, [onMetricsChanged] is called.
///
/// See also:
Expand Down
25 changes: 15 additions & 10 deletions lib/ui/hooks.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,27 +21,32 @@ dynamic _decodeJSON(String message) {
void _updateWindowMetrics(double devicePixelRatio,
double width,
double height,
double paddingTop,
double paddingRight,
double paddingBottom,
double paddingLeft,
double viewPaddingTop,
double viewPaddingRight,
double viewPaddingBottom,
double viewPaddingLeft,
double viewInsetTop,
double viewInsetRight,
double viewInsetBottom,
double viewInsetLeft) {
window
.._devicePixelRatio = devicePixelRatio
.._physicalSize = Size(width, height)
.._padding = WindowPadding._(
top: paddingTop,
right: paddingRight,
bottom: paddingBottom,
left: paddingLeft)
.._viewPadding = WindowPadding._(
top: viewPaddingTop,
right: viewPaddingRight,
bottom: viewPaddingBottom,
left: viewPaddingLeft)
.._viewInsets = WindowPadding._(
top: viewInsetTop,
right: viewInsetRight,
bottom: viewInsetBottom,
left: viewInsetLeft);
left: viewInsetLeft)
.._padding = WindowPadding._(
top: math.max(0.0, viewPaddingTop - viewInsetTop),
right: math.max(0.0, viewPaddingRight - viewInsetRight),
bottom: math.max(0.0, viewPaddingBottom - viewInsetBottom),
left: math.max(0.0, viewPaddingLeft - viewInsetLeft));
_invoke(window.onMetricsChanged, window._onMetricsChangedZone);
}

Expand Down
85 changes: 85 additions & 0 deletions lib/ui/window.dart
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,48 @@ class Locale {
///
/// There is a single Window instance in the system, which you can
/// obtain from the [window] property.
///
/// ## Insets and Padding
///
/// {@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.
///
/// The [Window.viewInsets] are the physical pixels which the operating
/// system reserves for system UI, such as the keyboard, which would fully
/// obscure any content drawn in that area.
///
/// The [Window.viewPadding] are the physical pixels on each side of the display
/// that may be partially obscured by system UI or by physical intrusions into
/// the display, such as an overscan region on a television or a "notch" on a
/// phone. Unlike the insets, these areas may have portions that show the user
/// application painted pixels without being obscured, such as a notch at the
/// top of a phone that covers only a subset of the area. Insets, on the other
/// hand, either partially or fully obscure the window, such as an opaque
/// keyboard or a partially transluscent statusbar, which cover an area without
/// gaps.
///
/// The [Window.padding] property is computed from both [Window.viewInsets] and
/// [Window.viewPadding]. It will allow a view inset to consume view padding
/// where appropriate, such as when a phone's keyboard is covering the bottom
/// view padding and so "absorbs" it.
///
/// Clients that want to position elements relative to the view padding
/// regardless of the view insets should use the [Window.viewPadding] property,
/// e.g. if you wish to draw a widget at the center of the screen with respect
/// to the iPhone "safe area" regardless of whether the keyboard is showing.
///
/// [Window.padding] is useful for clients that want to know how much padding
/// should be accounted for without concern for the current inset(s) state, e.g.
/// determining whether a gesture should be considered for scrolling purposes.
/// This value varies based on the current state of the insets. For example, a
/// visible keyboard will consume all gestures in the bottom part of the
/// [Window.viewPadding] anyway, so there is no need to account for that in the
/// [Window.padding], which is always safe to use for such calculations.
class Window {
Window._();

Expand Down Expand Up @@ -467,6 +509,10 @@ class Window {
///
/// When this changes, [onMetricsChanged] is called.
///
/// The relationship between this [Window.viewInsets], [Window.viewPadding],
/// and [Window.padding] are described in more detail in the documentation for
/// [Window].
///
/// See also:
///
/// * [WidgetsBindingObserver], for a mechanism at the widgets layer to
Expand All @@ -483,8 +529,47 @@ class Window {
/// intrusions in the display (e.g. overscan regions on television screens or
/// phone sensor housings).
///
/// Unlike [Window.padding], this value does not change relative to
/// [Window.viewInsets]. For example, on an iPhone X, it will not change in
/// response to the soft keyboard being visible or hidden, whereas
/// [Window.padding] will.
///
/// When this changes, [onMetricsChanged] is called.
///
/// The relationship between this [Window.viewInsets], [Window.viewPadding],
/// and [Window.padding] are described in more detail in the documentation for
/// [Window].
///
/// See also:
///
/// * [WidgetsBindingObserver], for a mechanism at the widgets layer to
/// observe when this value changes.
/// * [MediaQuery.of], a simpler mechanism for the same.
/// * [Scaffold], which automatically applies the padding in material design
/// applications.
WindowPadding get viewPadding => _viewPadding;
WindowPadding _viewPadding = WindowPadding.zero;

/// The number of physical pixels on each side of the display rectangle into
/// which the application can render, but which may be partially obscured by
/// system UI (such as the system notification area), or or physical
/// intrusions in the display (e.g. overscan regions on television screens or
/// phone sensor housings).
///
/// This value is calculated by taking
/// `max(0.0, Window.viewPadding - Window.viewInsets)`. This will treat a
/// system IME that increases the bottom inset as consuming that much of the
/// bottom padding. For example, on an iPhone X, [Window.padding.bottom] is
/// the same as [Window.viewPadding.bottom] when the soft keyboard is not
/// drawn (to account for the bottom soft button area), but will be `0.0` when
/// the soft keyboard is visible.
///
/// When this changes, [onMetricsChanged] is called.
///
/// The relationship between this [Window.viewInsets], [Window.viewPadding],
/// and [Window.padding] are described in more detail in the documentation for
/// [Window].
///
/// See also:
///
/// * [WidgetsBindingObserver], for a mechanism at the widgets layer to
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -727,23 +727,14 @@ - (void)keyboardWillChangeFrame:(NSNotification*)notification {
CGFloat scale = [UIScreen mainScreen].scale;

// The keyboard is treated as an inset since we want to effectively reduce the window size by the
// keyboard height. We also eliminate any bottom safe-area padding since they keyboard 'consumes'
// the home indicator widget.
// keyboard height. The Dart side will compute a value accounting for the keyboard-consuming
// bottom padding.
_viewportMetrics.physical_view_inset_bottom = bottom * scale;
_viewportMetrics.physical_padding_bottom = 0;
[self updateViewportMetrics];
}

- (void)keyboardWillBeHidden:(NSNotification*)notification {
CGFloat scale = [UIScreen mainScreen].scale;
_viewportMetrics.physical_view_inset_bottom = 0;

// Restore any safe area padding that the keyboard had consumed.
if (@available(iOS 11, *)) {
_viewportMetrics.physical_padding_bottom = self.view.safeAreaInsets.bottom * scale;
} else {
_viewportMetrics.physical_padding_top = [self statusBarPadding] * scale;
}
[self updateViewportMetrics];
}

Expand Down
58 changes: 58 additions & 0 deletions testing/dart/window_hooks_integration_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -266,5 +266,63 @@ void main() {
expect(runZone, same(innerZone));
expect(platformBrightness, equals(Brightness.dark));
});


test('Window padding/insets/viewPadding', () {
final double oldDPR = window.devicePixelRatio;
final Size oldSize = window.physicalSize;
final WindowPadding oldPadding = window.viewPadding;
final WindowPadding oldInsets = window.viewInsets;

_updateWindowMetrics(
1.0, // DPR
800.0, // width
600.0, // height
50.0, // padding top
0.0, // padding right
40.0, // padding bottom
0.0, // padding left
0.0, // inset top
0.0, // inset right
0.0, // inset bottom
0.0, // inset left
);

expect(window.viewInsets.bottom, 0.0);
expect(window.viewPadding.bottom, 40.0);
expect(window.padding.bottom, 40.0);

_updateWindowMetrics(
1.0, // DPR
800.0, // width
600.0, // height
50.0, // padding top
0.0, // padding right
40.0, // padding bottom
0.0, // padding left
0.0, // inset top
0.0, // inset right
400.0, // inset bottom
0.0, // inset left
);

expect(window.viewInsets.bottom, 400.0);
expect(window.viewPadding.bottom, 40.0);
expect(window.padding.bottom, 0.0);

_updateWindowMetrics(
oldDPR, // DPR
oldSize.width, // width
oldSize.height, // height
oldPadding.top, // padding top
oldPadding.right, // padding right
oldPadding.bottom, // padding bottom
oldPadding.left, // padding left
oldInsets.top, // inset top
oldInsets.right, // inset right
oldInsets.bottom, // inset bottom
oldInsets.left, // inset left
);
});
});
}

0 comments on commit 79c6ce1

Please sign in to comment.