Skip to content

Commit

Permalink
Expose Window.viewInsets in dart:ui (flutter#4403)
Browse files Browse the repository at this point in the history
Window.viewInsets is the set of window-relative insets that describe the
area of the window that an application may want to treat as effectively
reducing the size of the content. Typically this is due to system UI
that fully obscures underlying content, such as the keyboard.

This area differs from padding in that padding is the set of insets that
describe the area of the window that may be partially (or fully)
obscured by system UI or physical intrusions into the view area (e.g.
iPhone X sensor housing, status bar, or the iPhone X home indicator
widget).

This patch does not yet enable the iOS bottom edge safe area. Once the
framework has been updated to use viewInsets for bottom-edge occlusions
(today, the keyboard), the bottom safe area will be enabled and
framework patches that depend on it, landed.
  • Loading branch information
cbracken authored Nov 30, 2017
1 parent fbeb2b2 commit 93648ef
Show file tree
Hide file tree
Showing 10 changed files with 149 additions and 31 deletions.
22 changes: 17 additions & 5 deletions lib/ui/hooks.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,27 @@ dynamic _decodeJSON(String message) {
void _updateWindowMetrics(double devicePixelRatio,
double width,
double height,
double top,
double right,
double bottom,
double left) {
double paddingTop,
double paddingRight,
double paddingBottom,
double paddingLeft,
double viewInsetTop,
double viewInsetRight,
double viewInsetBottom,
double viewInsetLeft) {
window
.._devicePixelRatio = devicePixelRatio
.._physicalSize = new Size(width, height)
.._padding = new WindowPadding._(
top: top, right: right, bottom: bottom, left: left);
top: paddingTop,
right: paddingRight,
bottom: paddingBottom,
left: paddingLeft)
.._viewInsets = new ViewInsets._(
top: viewInsetTop,
right: viewInsetRight,
bottom: viewInsetBottom,
left: viewInsetLeft);
_invoke(window.onMetricsChanged, window._onMetricsChangedZone);
}

Expand Down
69 changes: 60 additions & 9 deletions lib/ui/window.dart
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,45 @@ enum AppLifecycleState {
suspending,
}

/// A representation of the insets relative to each side of the window into
/// which the application can render, but over which the operating system will
/// likely place system UI, such as the keyboard, that fully obscures any
/// content. These insets are exposed by [Window.viewInsets] and preferrably
/// read via [MediaQuery.of].
///
/// For a generic class that represents distances around a rectangle, see the
/// [EdgeInsets] class.
///
/// See also:
///
/// * [WidgetsBindingObserver], for a widgets layer mechanism to receive
/// notifications when the margin changes.
/// * [MediaQuery.of], for the preferred mechanism for accessing this value.
/// * [Scaffold], which automatically applies the view insets in material
/// design applications.
class ViewInsets {
const ViewInsets._({ this.left, this.top, this.right, this.bottom });

/// The distance from the left edge to the first unobscured pixel, in physical pixels.
final double left;

/// The distance from the top edge to the first unobscured pixel, in physical pixels.
final double top;

/// The distance from the right edge to the first unobscured pixel, in physical pixels.
final double right;

/// The distance from the bottom edge to the first unobscured pixel, in physical pixels.
final double bottom;

/// A view inset that has zeros for each edge.
static const ViewInsets zero = const ViewInsets._(left: 0.0, top: 0.0, right: 0.0, bottom: 0.0);
}

/// A representation of distances for each of the four edges of a rectangle,
/// used to encode the padding that applications should place around their user
/// interface, as exposed by [Window.padding].
/// interface, as exposed by [Window.padding] and preferrably read via
/// [MediaQuery.of].
///
/// For a generic class that represents distances around a rectangle, see the
/// [EdgeInsets] class.
Expand All @@ -83,7 +119,7 @@ enum AppLifecycleState {
///
/// * [WidgetsBindingObserver], for a widgets layer mechanism to receive
/// notifications when the padding changes.
/// * [MediaQuery.of], a simpler mechanism for the same.
/// * [MediaQuery.of], for the preferred mechanism for accessing this value.
/// * [Scaffold], which automatically applies the padding in material design
/// applications.
class WindowPadding {
Expand Down Expand Up @@ -193,10 +229,25 @@ class Window {
Size _physicalSize = Size.zero;

/// The number of physical pixels on each side of the display rectangle into
/// which the application can render, but over which the operating system will
/// likely place system UI (such as the Android system notification area), or
/// which might be rendered outside of the physical display (e.g. overscan
/// regions on television screens).
/// which the application can render, but over which the operating system
/// will likely place system UI, such as the keyboard, that fully obscures
/// any content.
///
/// 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 view insets in material
/// design applications.
ViewInsets get viewInsets => _viewInsets;
ViewInsets _viewInsets = ViewInsets.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).
///
/// See also:
///
Expand All @@ -209,9 +260,9 @@ class Window {
WindowPadding _padding = WindowPadding.zero;

/// A callback that is invoked whenever the [devicePixelRatio],
/// [physicalSize], or [padding] values change, for example when the device is
/// rotated or when the application is resized (e.g. when showing applications
/// side-by-side on Android).
/// [physicalSize], [padding], or [viewInsets] values change, for example
/// when the device is rotated or when the application is resized (e.g. when
/// showing applications side-by-side on Android).
///
/// The framework invokes this callback in the same zone in which the
/// callback was set.
Expand Down
4 changes: 4 additions & 0 deletions lib/ui/window/viewport_metrics.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ struct ViewportMetrics {
int32_t physical_padding_right = 0;
int32_t physical_padding_bottom = 0;
int32_t physical_padding_left = 0;
int32_t physical_view_inset_top = 0;
int32_t physical_view_inset_right = 0;
int32_t physical_view_inset_bottom = 0;
int32_t physical_view_inset_left = 0;
};

} // namespace blink
Expand Down
4 changes: 4 additions & 0 deletions lib/ui/window/window.cc
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,10 @@ void Window::UpdateWindowMetrics(const ViewportMetrics& metrics) {
ToDart(static_cast<double>(metrics.physical_padding_right)),
ToDart(static_cast<double>(metrics.physical_padding_bottom)),
ToDart(static_cast<double>(metrics.physical_padding_left)),
ToDart(static_cast<double>(metrics.physical_view_inset_top)),
ToDart(static_cast<double>(metrics.physical_view_inset_right)),
ToDart(static_cast<double>(metrics.physical_view_inset_bottom)),
ToDart(static_cast<double>(metrics.physical_view_inset_left)),
});
}

Expand Down
26 changes: 24 additions & 2 deletions shell/platform/android/io/flutter/view/FlutterView.java
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ static final class ViewportMetrics {
int physicalPaddingRight = 0;
int physicalPaddingBottom = 0;
int physicalPaddingLeft = 0;
int physicalViewInsetTop = 0;
int physicalViewInsetRight = 0;
int physicalViewInsetBottom = 0;
int physicalViewInsetLeft = 0;
}

private final TextInputPlugin mTextInputPlugin;
Expand Down Expand Up @@ -548,10 +552,15 @@ protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight)

@Override
public final WindowInsets onApplyWindowInsets(WindowInsets insets) {
// On Android, we do not differentiate between 'safe areas' and view insets.
mMetrics.physicalPaddingTop = insets.getSystemWindowInsetTop();
mMetrics.physicalPaddingRight = insets.getSystemWindowInsetRight();
mMetrics.physicalPaddingBottom = insets.getSystemWindowInsetBottom();
mMetrics.physicalPaddingLeft = insets.getSystemWindowInsetLeft();
mMetrics.physicalViewInsetTop = insets.getSystemWindowInsetTop();
mMetrics.physicalViewInsetRight = insets.getSystemWindowInsetRight();
mMetrics.physicalViewInsetBottom = insets.getSystemWindowInsetBottom();
mMetrics.physicalViewInsetLeft = insets.getSystemWindowInsetLeft();
updateViewportMetrics();
return super.onApplyWindowInsets(insets);
}
Expand All @@ -560,10 +569,15 @@ public final WindowInsets onApplyWindowInsets(WindowInsets insets) {
@SuppressWarnings("deprecation")
protected boolean fitSystemWindows(Rect insets) {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
// On Android, we do not differentiate between 'safe areas' and view insets.
mMetrics.physicalPaddingTop = insets.top;
mMetrics.physicalPaddingRight = insets.right;
mMetrics.physicalPaddingBottom = insets.bottom;
mMetrics.physicalPaddingLeft = insets.left;
mMetrics.physicalViewInsetTop = insets.top;
mMetrics.physicalViewInsetRight = insets.right;
mMetrics.physicalViewInsetBottom = insets.bottom;
mMetrics.physicalViewInsetLeft = insets.left;
updateViewportMetrics();
return true;
} else {
Expand Down Expand Up @@ -653,7 +667,11 @@ private static native void nativeSetViewportMetrics(long nativePlatformViewAndro
int physicalPaddingTop,
int physicalPaddingRight,
int physicalPaddingBottom,
int physicalPaddingLeft);
int physicalPaddingLeft,
int physicalViewInsetTop,
int physicalViewInsetRight,
int physicalViewInsetBottom,
int physicalViewInsetLeft);

private static native Bitmap nativeGetBitmap(long nativePlatformViewAndroid);

Expand Down Expand Up @@ -684,7 +702,11 @@ private void updateViewportMetrics() {
mMetrics.physicalPaddingTop,
mMetrics.physicalPaddingRight,
mMetrics.physicalPaddingBottom,
mMetrics.physicalPaddingLeft);
mMetrics.physicalPaddingLeft,
mMetrics.physicalViewInsetTop,
mMetrics.physicalViewInsetRight,
mMetrics.physicalViewInsetBottom,
mMetrics.physicalViewInsetLeft);

WindowManager wm = (WindowManager) getContext()
.getSystemService(Context.WINDOW_SERVICE);
Expand Down
10 changes: 9 additions & 1 deletion shell/platform/android/platform_view_android.cc
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,11 @@ void PlatformViewAndroid::SetViewportMetrics(jfloat device_pixel_ratio,
jint physical_padding_top,
jint physical_padding_right,
jint physical_padding_bottom,
jint physical_padding_left) {
jint physical_padding_left,
jint physical_view_inset_top,
jint physical_view_inset_right,
jint physical_view_inset_bottom,
jint physical_view_inset_left) {
blink::ViewportMetrics metrics;
metrics.device_pixel_ratio = device_pixel_ratio;
metrics.physical_width = physical_width;
Expand All @@ -252,6 +256,10 @@ void PlatformViewAndroid::SetViewportMetrics(jfloat device_pixel_ratio,
metrics.physical_padding_right = physical_padding_right;
metrics.physical_padding_bottom = physical_padding_bottom;
metrics.physical_padding_left = physical_padding_left;
metrics.physical_view_inset_top = physical_view_inset_top;
metrics.physical_view_inset_right = physical_view_inset_right;
metrics.physical_view_inset_bottom = physical_view_inset_bottom;
metrics.physical_view_inset_left = physical_view_inset_left;

blink::Threads::UI()->PostTask([ engine = engine_->GetWeakPtr(), metrics ] {
if (engine)
Expand Down
6 changes: 5 additions & 1 deletion shell/platform/android/platform_view_android.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,11 @@ class PlatformViewAndroid : public PlatformView {
jint physical_padding_top,
jint physical_padding_right,
jint physical_padding_bottom,
jint physical_padding_left);
jint physical_padding_left,
jint physical_view_inset_top,
jint physical_view_inset_right,
jint physical_view_inset_bottom,
jint physical_view_inset_left);

void DispatchPlatformMessage(JNIEnv* env,
std::string name,
Expand Down
26 changes: 17 additions & 9 deletions shell/platform/android/platform_view_android_jni.cc
Original file line number Diff line number Diff line change
Expand Up @@ -181,14 +181,22 @@ static void SetViewportMetrics(JNIEnv* env,
jint physicalPaddingTop,
jint physicalPaddingRight,
jint physicalPaddingBottom,
jint physicalPaddingLeft) {
return PLATFORM_VIEW->SetViewportMetrics(devicePixelRatio, //
physicalWidth, //
physicalHeight, //
physicalPaddingTop, //
physicalPaddingRight, //
physicalPaddingBottom, //
physicalPaddingLeft);
jint physicalPaddingLeft,
jint physicalViewInsetTop,
jint physicalViewInsetRight,
jint physicalViewInsetBottom,
jint physicalViewInsetLeft) {
return PLATFORM_VIEW->SetViewportMetrics(devicePixelRatio, //
physicalWidth, //
physicalHeight, //
physicalPaddingTop, //
physicalPaddingRight, //
physicalPaddingBottom, //
physicalPaddingLeft, //
physicalViewInsetTop, //
physicalViewInsetRight, //
physicalViewInsetBottom, //
physicalViewInsetLeft);
}

static jobject GetBitmap(JNIEnv* env, jobject jcaller, jlong platform_view) {
Expand Down Expand Up @@ -390,7 +398,7 @@ bool PlatformViewAndroid::Register(JNIEnv* env) {
},
{
.name = "nativeSetViewportMetrics",
.signature = "(JFIIIIII)V",
.signature = "(JFIIIIIIIIII)V",
.fnPtr = reinterpret_cast<void*>(&shell::SetViewportMetrics),
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -637,9 +637,8 @@ - (void)updateViewportPadding {
_viewportMetrics.physical_padding_top = self.view.safeAreaInsets.top * scale;
_viewportMetrics.physical_padding_left = self.view.safeAreaInsets.left * scale;
_viewportMetrics.physical_padding_right = self.view.safeAreaInsets.right * scale;
// TODO(cbracken) enable bottom padding once safe area and keyboard view
// occlusion are differentiated as two different Window getters (margin,
// padding).
// TODO(cbracken): Once framework has been updated to use view insets for the keyboard, enable
// bottom safe area padding.
// _viewportMetrics.physical_padding_bottom = self.view.safeAreaInsets.bottom * scale;
#pragma clang diagnostic pop
} else {
Expand All @@ -653,11 +652,17 @@ - (void)keyboardWillChangeFrame:(NSNotification*)notification {
NSDictionary* info = [notification userInfo];
CGFloat bottom = CGRectGetHeight([[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue]);
CGFloat scale = [UIScreen mainScreen].scale;
// TODO(cbracken): Once framework has been updated to use view insets, keyboard should change
// insets rather than padding.
// _viewportMetrics.physical_view_inset_bottom = bottom * scale;
_viewportMetrics.physical_padding_bottom = bottom * scale;
[self updateViewportMetrics];
}

- (void)keyboardWillBeHidden:(NSNotification*)notification {
// TODO(cbracken): Once framework has been updated to use view insets, keyboard should change
// insets rather than padding.
// _viewportMetrics.physical_view_inset_bottom = 0;
_viewportMetrics.physical_padding_bottom = 0;
[self updateViewportMetrics];
}
Expand Down
2 changes: 1 addition & 1 deletion testing/dart/window_hooks_integration_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ void main() {
});

window.onMetricsChanged();
_updateWindowMetrics(0.1234, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
_updateWindowMetrics(0.1234, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
expect(runZone, isNotNull);
expect(runZone, same(innerZone));
expect(devicePixelRatio, equals(0.1234));
Expand Down

0 comments on commit 93648ef

Please sign in to comment.