Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 108 additions & 1 deletion lib/ui/platform_dispatcher.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Copy link
Contributor

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?

Copy link
Member Author

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.

Copy link
Member Author

@goderbauer goderbauer Nov 21, 2023

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:

Size get physicalSize => _viewConfiguration.size;

Copy link
Member

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

final Size size;

/// The number of physical pixels on each side of the display rectangle into
Expand Down Expand Up @@ -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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why can't this be const?

Copy link
Member Author

@goderbauer goderbauer Nov 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

size.width and size.height are invalid constant values, according to Dart (In theory, they could be getters that do some crazy computation using information only available at runtime.). Since they are used in the initializer list below, the constructor cannot be const, sadly.

(Same applies to the existing BoxConstraints.tight constructor)

Copy link
Contributor

Choose a reason for hiding this comment

The 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.
Expand Down
53 changes: 47 additions & 6 deletions lib/ui/window.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
/// 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.
Copy link
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Member Author

Choose a reason for hiding this comment

The 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 physicalSize.

///
/// 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
Copy link
Contributor

@dkwingsmt dkwingsmt Nov 27, 2023

Choose a reason for hiding this comment

The 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.

Copy link
Member Author

Choose a reason for hiding this comment

The 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
Copy link
Contributor

Choose a reason for hiding this comment

The 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?

Copy link
Member Author

Choose a reason for hiding this comment

The 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
Expand Down Expand Up @@ -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}) {
// 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].
///
Expand Down
9 changes: 6 additions & 3 deletions lib/ui/window/platform_configuration.cc
Original file line number Diff line number Diff line change
Expand Up @@ -449,10 +449,13 @@ void PlatformConfiguration::CompletePlatformMessageResponse(
response->Complete(std::make_unique<fml::DataMapping>(std::move(data)));
}

void PlatformConfigurationNativeApi::Render(int64_t view_id, Scene* scene) {
void PlatformConfigurationNativeApi::Render(int64_t view_id,
Scene* scene,
double width,
double height) {
UIDartState::ThrowIfUIOperationsProhibited();
UIDartState::Current()->platform_configuration()->client()->Render(view_id,
scene);
UIDartState::Current()->platform_configuration()->client()->Render(
view_id, scene, width, height);
}

void PlatformConfigurationNativeApi::SetNeedsReportTimings(bool value) {
Expand Down
10 changes: 8 additions & 2 deletions lib/ui/window/platform_configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,10 @@ class PlatformConfigurationClient {
/// @brief Updates the client's rendering on the GPU with the newly
/// provided Scene.
///
virtual void Render(int64_t view_id, Scene* scene) = 0;
virtual void Render(int64_t view_id,
Scene* scene,
double width,
double height) = 0;

//--------------------------------------------------------------------------
/// @brief Receives an updated semantics tree from the Framework.
Expand Down Expand Up @@ -557,7 +560,10 @@ class PlatformConfigurationNativeApi {

static void ScheduleFrame();

static void Render(int64_t view_id, Scene* scene);
static void Render(int64_t view_id,
Scene* scene,
double width,
double height);

static void UpdateSemantics(SemanticsUpdate* update);

Expand Down
19 changes: 19 additions & 0 deletions lib/web_ui/lib/platform_dispatcher.dart
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,25 @@ abstract class ViewPadding {
}
}

abstract class ViewConstraints {
const factory ViewConstraints({
double minWidth,
double maxWidth,
double minHeight,
double maxHeight,
}) = engine.ViewConstraints;

factory ViewConstraints.tight(Size size) = engine.ViewConstraints.tight;

double get minWidth;
double get maxWidth;
double get minHeight;
double get maxHeight;
bool isSatisfiedBy(Size size);
bool get isTight;
ViewConstraints operator/(double factor);
}

@Deprecated(
'Use ViewPadding instead. '
'This feature was deprecated after v3.8.0-14.0.pre.',
Expand Down
88 changes: 87 additions & 1 deletion lib/web_ui/lib/src/engine/window.dart
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,9 @@ base class EngineFlutterView implements ui.FlutterView {
}

@override
void render(ui.Scene scene) {
void render(ui.Scene scene, {ui.Size? size}) {
assert(!isDisposed, 'Trying to render a disposed EngineFlutterView.');
// TODO(goderbauer): Respect the provided size when "physicalConstraints" are not always tight. See TODO on "physicalConstraints".
platformDispatcher.render(scene, this);
}

Expand All @@ -121,6 +122,10 @@ base class EngineFlutterView implements ui.FlutterView {
late final PlatformViewMessageHandler platformViewMessageHandler =
PlatformViewMessageHandler(platformViewsContainer: dom.platformViewsHost);

// TODO(goderbauer): Provide API to configure constraints. See also TODO in "render".
@override
ViewConstraints get physicalConstraints => ViewConstraints.tight(physicalSize);

@override
ui.Size get physicalSize {
if (_physicalSize == null) {
Expand Down Expand Up @@ -649,3 +654,84 @@ class ViewPadding implements ui.ViewPadding {
@override
final double bottom;
}

class ViewConstraints implements ui.ViewConstraints {
const ViewConstraints({
this.minWidth = 0.0,
this.maxWidth = double.infinity,
this.minHeight = 0.0,
this.maxHeight = double.infinity,
});

ViewConstraints.tight(ui.Size size)
: minWidth = size.width,
maxWidth = size.width,
minHeight = size.height,
maxHeight = size.height;

@override
final double minWidth;
@override
final double maxWidth;
@override
final double minHeight;
@override
final double maxHeight;

@override
bool isSatisfiedBy(ui.Size size) {
return (minWidth <= size.width) && (size.width <= maxWidth) &&
(minHeight <= size.height) && (size.height <= maxHeight);
}

@override
bool get isTight => minWidth >= maxWidth && minHeight >= maxHeight;

@override
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)';
}
}
3 changes: 2 additions & 1 deletion lib/web_ui/lib/window.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ abstract class FlutterView {
PlatformDispatcher get platformDispatcher;
int get viewId;
double get devicePixelRatio;
ViewConstraints get physicalConstraints;
Size get physicalSize;
ViewPadding get viewInsets;
ViewPadding get viewPadding;
Expand All @@ -23,7 +24,7 @@ abstract class FlutterView {
GestureSettings get gestureSettings;
List<DisplayFeature> get displayFeatures;
Display get display;
void render(Scene scene) => platformDispatcher.render(scene, this);
void render(Scene scene, {Size? size});
void updateSemantics(SemanticsUpdate update) => platformDispatcher.updateSemantics(update);
}

Expand Down
Loading