-
Notifications
You must be signed in to change notification settings - Fork 6k
Dynamic view sizing [dart:ui] #48090
Changes from all commits
baabadc
350e35c
4477242
a1ae04b
f570dd7
6aafa7f
d951f4d
6352a82
eb7b1c8
6b8542a
9639415
92a4dd2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1479,7 +1479,7 @@ class _ViewConfiguration { | |
| /// The pixel density of the output surface. | ||
| final double devicePixelRatio; | ||
|
|
||
| /// The size requested for the view in logical pixels. | ||
| /// The size requested for the view in physical pixels. | ||
| final Size size; | ||
|
|
||
| /// The number of physical pixels on each side of the display rectangle into | ||
|
|
@@ -1981,6 +1981,113 @@ class ViewPadding { | |
| ) | ||
| typedef WindowPadding = ViewPadding; | ||
|
|
||
| /// Immutable layout constraints for [FlutterView]s. | ||
| /// | ||
| /// Similar to [BoxConstraints], a [Size] respects a [ViewConstraints] if, and | ||
| /// only if, all of the following relations hold: | ||
| /// | ||
| /// * [minWidth] <= [Size.width] <= [maxWidth] | ||
| /// * [minHeight] <= [Size.height] <= [maxHeight] | ||
| /// | ||
| /// The constraints themselves must satisfy these relations: | ||
| /// | ||
| /// * 0.0 <= [minWidth] <= [maxWidth] <= [double.infinity] | ||
| /// * 0.0 <= [minHeight] <= [maxHeight] <= [double.infinity] | ||
| /// | ||
| /// For each constraint, [double.infinity] is a legal value. | ||
| /// | ||
| /// For a generic class that represents these kind of constraints, see the | ||
| /// [BoxConstraints] class. | ||
| class ViewConstraints { | ||
| /// Creates view constraints with the given constraints. | ||
| const ViewConstraints({ | ||
| this.minWidth = 0.0, | ||
| this.maxWidth = double.infinity, | ||
| this.minHeight = 0.0, | ||
| this.maxHeight = double.infinity, | ||
| }); | ||
|
|
||
| /// Creates view constraints that is respected only by the given size. | ||
| ViewConstraints.tight(Size size) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why can't this be
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
(Same applies to the existing
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ahh, right, makes sense. Too bad there's no way to make a final property not be overridable with a getter. |
||
| : minWidth = size.width, | ||
| maxWidth = size.width, | ||
| minHeight = size.height, | ||
| maxHeight = size.height; | ||
|
|
||
| /// The minimum width that satisfies the constraints. | ||
| final double minWidth; | ||
|
|
||
| /// The maximum width that satisfies the constraints. | ||
| /// | ||
| /// Might be [double.infinity]. | ||
| final double maxWidth; | ||
|
|
||
| /// The minimum height that satisfies the constraints. | ||
| final double minHeight; | ||
|
|
||
| /// The maximum height that satisfies the constraints. | ||
| /// | ||
| /// Might be [double.infinity]. | ||
| final double maxHeight; | ||
|
|
||
| /// Whether the given size satisfies the constraints. | ||
| bool isSatisfiedBy(Size size) { | ||
| return (minWidth <= size.width) && (size.width <= maxWidth) && | ||
| (minHeight <= size.height) && (size.height <= maxHeight); | ||
| } | ||
|
|
||
| /// Whether there is exactly one size that satisfies the constraints. | ||
| bool get isTight => minWidth >= maxWidth && minHeight >= maxHeight; | ||
|
|
||
| /// Scales each constraint parameter by the inverse of the given factor. | ||
| ViewConstraints operator/(double factor) { | ||
| return ViewConstraints( | ||
| minWidth: minWidth / factor, | ||
| maxWidth: maxWidth / factor, | ||
| minHeight: minHeight / factor, | ||
| maxHeight: maxHeight / factor, | ||
| ); | ||
| } | ||
|
|
||
| @override | ||
| bool operator ==(Object other) { | ||
| if (identical(this, other)) { | ||
| return true; | ||
| } | ||
| if (other.runtimeType != runtimeType) { | ||
| return false; | ||
| } | ||
| return other is ViewConstraints | ||
| && other.minWidth == minWidth | ||
| && other.maxWidth == maxWidth | ||
| && other.minHeight == minHeight | ||
| && other.maxHeight == maxHeight; | ||
| } | ||
|
|
||
| @override | ||
| int get hashCode => Object.hash(minWidth, maxWidth, minHeight, maxHeight); | ||
|
|
||
| @override | ||
| String toString() { | ||
| if (minWidth == double.infinity && minHeight == double.infinity) { | ||
| return 'ViewConstraints(biggest)'; | ||
| } | ||
| if (minWidth == 0 && maxWidth == double.infinity && | ||
| minHeight == 0 && maxHeight == double.infinity) { | ||
| return 'ViewConstraints(unconstrained)'; | ||
| } | ||
| String describe(double min, double max, String dim) { | ||
| if (min == max) { | ||
| return '$dim=${min.toStringAsFixed(1)}'; | ||
| } | ||
| return '${min.toStringAsFixed(1)}<=$dim<=${max.toStringAsFixed(1)}'; | ||
| } | ||
| final String width = describe(minWidth, maxWidth, 'w'); | ||
| final String height = describe(minHeight, maxHeight, 'h'); | ||
| return 'ViewConstraints($width, $height)'; | ||
| } | ||
| } | ||
|
|
||
| /// Area of the display that may be obstructed by a hardware feature. | ||
| /// | ||
| /// This is populated only on Android. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -138,8 +138,42 @@ class FlutterView { | |
| /// The value here is equal to the value exposed on [display]. | ||
| double get devicePixelRatio => _viewConfiguration.devicePixelRatio; | ||
|
|
||
| /// The dimensions of the rectangle into which the scene rendered in this view | ||
| /// will be drawn on the screen, in physical pixels. | ||
| /// The sizing constraints in physical pixels for this view. | ||
| /// | ||
| /// The view can take on any [Size] that fulfills these constraints. These | ||
| /// constraints are typically used by an UI framework as the input for its | ||
| /// layout algorithm to determine an approrpiate size for the view. To size | ||
| /// the view, the selected size must be provided to the [render] method and it | ||
| /// must satisfy the constraints. | ||
| /// | ||
| /// When this changes, [PlatformDispatcher.onMetricsChanged] is called. | ||
ditman marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| /// | ||
| /// At startup, the constraints for the view may not be known before Dart code | ||
| /// runs. If this value is observed early in the application lifecycle, it may | ||
| /// report constraints with all dimensions set to zero. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does "constraints with all dimensions set to zero" mean the resulting size must be 0? I don't know if this sentence needs to be said, because the sentence alone feels important on the surface but it's really just a temporary state. And the constraints are less like "known" but more like "set" by the engine.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, it means the only size allowed will be zero. It matches what was previously documented on |
||
| /// | ||
| /// This value does not take into account any on-screen keyboards or other | ||
| /// system UI. If the constraints are tight, the [padding] and [viewInsets] | ||
|
Comment on lines
+155
to
+156
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does "not take into account" mean the size include or exclude these system UI? I think "include/exclude" will be clearer.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (This is using the same terms we use on physicalSize to document the same thing). I find include/exclude more confusing because it depends on your perspective whether the padding is included or excluded. |
||
| /// properties provide information about how much of each side of the view may | ||
| /// be obscured by system UI. If the constraints are loose, this information | ||
| /// is not known upfront. | ||
|
Comment on lines
+158
to
+159
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What do you mean by "this information is not known"? Shall we just assert that only tight constraints can have non-zero padding and insets?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is basically describing platform limitations. No platform currently makes it possible to know this information upfront, but in theory a platform could provide it. |
||
| /// | ||
| /// See also: | ||
| /// | ||
| /// * [physicalSize], which returns the current size of the view. | ||
| // TODO(goderbauer): Wire this up so embedders can configure it. This will | ||
| // also require to message the size provided to the render call back to the | ||
| // embedder. | ||
| ViewConstraints get physicalConstraints => ViewConstraints.tight(physicalSize); | ||
|
|
||
| /// The current dimensions of the rectangle as last reported by the platform | ||
| /// into which scenes rendered in this view are drawn. | ||
| /// | ||
| /// If the view is configured with loose [physicalConstraints] this value | ||
| /// may be outdated by a few frames as it only updates when the size chosen | ||
| /// for a frame (as provided to the [render] method) is processed by the | ||
| /// platform. Because of this, [physicalConstraints] should be used instead of | ||
| /// this value as the root input to the layout algorithm of UI frameworks. | ||
| /// | ||
| /// When this changes, [PlatformDispatcher.onMetricsChanged] is called. When | ||
| /// using the Flutter framework, using [MediaQuery.of] to obtain the size (via | ||
|
|
@@ -324,25 +358,32 @@ class FlutterView { | |
| /// then obtain a [Scene] object, which you can display to the user via this | ||
| /// [render] function. | ||
| /// | ||
| /// If the view is configured with loose [physicalConstraints] (i.e. | ||
| /// [ViewConstraints.isTight] returns false) a `size` satisfying those | ||
| /// constraints must be provided. This method does not check that the provided | ||
| /// `size` actually meets the constraints (this should be done in a higher | ||
| /// level), but an illegal `size` may result in undefined rendering behavior. | ||
| /// If no `size` is provided, [physicalSize] is used instead. | ||
| /// | ||
| /// See also: | ||
| /// | ||
| /// * [SchedulerBinding], the Flutter framework class which manages the | ||
| /// scheduling of frames. | ||
| /// * [RendererBinding], the Flutter framework class which manages layout and | ||
| /// painting. | ||
| void render(Scene scene) { | ||
| void render(Scene scene, {Size? size}) { | ||
ditman marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| // Duplicated calls or calls outside of onBeginFrame/onDrawFrame (indicated | ||
| // by _renderedViews being null) are ignored. See _renderedViews. | ||
| // TODO(dkwingsmt): We should change this skip into an assertion. | ||
| // https://github.com/flutter/flutter/issues/137073 | ||
| final bool validRender = platformDispatcher._renderedViews?.add(this) ?? false; | ||
| if (validRender) { | ||
| _render(viewId, scene as _NativeScene); | ||
| _render(viewId, scene as _NativeScene, size?.width ?? physicalSize.width, size?.height ?? physicalSize.height); | ||
| } | ||
| } | ||
|
|
||
| @Native<Void Function(Int64, Pointer<Void>)>(symbol: 'PlatformConfigurationNativeApi::Render') | ||
| external static void _render(int viewId, _NativeScene scene); | ||
| @Native<Void Function(Int64, Pointer<Void>, Double, Double)>(symbol: 'PlatformConfigurationNativeApi::Render') | ||
| external static void _render(int viewId, _NativeScene scene, double width, double height); | ||
|
|
||
| /// Change the retained semantics data about this [FlutterView]. | ||
| /// | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How did we find out this should be physical instead of logical? Is it a bug fix or a behavior change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The value was always in physical pixels, it was just documented wrong. Luckily, it is a doc comment on a private class, so the doc was never actually public.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Backup evidence that this has always been in physical pixels and we just documented it wrong: This value is returned by the physicalSize getter on
FlutterView:engine/lib/ui/window.dart
Line 163 in dba80be
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've found Physical vs Logical pixels is painful in the web, especially if one trusts the docs :P