Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Prev Previous commit
Next Next commit
Wire viewConstraints.
  • Loading branch information
ditman committed Feb 14, 2024
commit 5ec00e490c11e6aa92f54969f1987ef0e793b908
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,11 @@ class FlutterViewManager {
EngineFlutterView createAndRegisterView(
JsFlutterViewOptions jsViewOptions,
) {
final EngineFlutterView view =
EngineFlutterView(_dispatcher, jsViewOptions.hostElement);
final EngineFlutterView view = EngineFlutterView(
_dispatcher,
jsViewOptions.hostElement,
viewConstraints: jsViewOptions.viewConstraints,
);
registerView(view, jsViewOptions: jsViewOptions);
return view;
}
Expand Down
67 changes: 60 additions & 7 deletions lib/web_ui/lib/src/engine/window.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import 'configuration.dart';
import 'display.dart';
import 'dom.dart';
import 'initialization.dart';
import 'js_interop/js_app.dart';
import 'mouse/context_menu.dart';
import 'mouse/cursor.dart';
import 'navigation/history.dart';
Expand Down Expand Up @@ -50,7 +51,9 @@ base class EngineFlutterView implements ui.FlutterView {
/// the Flutter view will be rendered.
factory EngineFlutterView(
EnginePlatformDispatcher platformDispatcher,
DomElement hostElement,
DomElement hostElement, {
JsViewConstraints? viewConstraints,
}
) = _EngineFlutterViewImpl;

EngineFlutterView._(
Expand All @@ -59,8 +62,11 @@ base class EngineFlutterView implements ui.FlutterView {
// This is nullable to accommodate the legacy `EngineFlutterWindow`. In
// multi-view mode, the host element is required for each view (as reflected
// by the public `EngineFlutterView` constructor).
DomElement? hostElement,
) : embeddingStrategy = EmbeddingStrategy.create(hostElement: hostElement),
DomElement? hostElement, {
JsViewConstraints? viewConstraints,
}
) : _jsViewConstraints = viewConstraints,
embeddingStrategy = EmbeddingStrategy.create(hostElement: hostElement),
dimensionsProvider = DimensionsProvider.create(hostElement: hostElement) {
// The embeddingStrategy will take care of cleaning up the rootElement on
// hot restart.
Expand Down Expand Up @@ -144,9 +150,13 @@ base class EngineFlutterView implements ui.FlutterView {

late final PointerBinding pointerBinding;

// TODO(goderbauer): Provide API to configure constraints. See also TODO in "render".
@override
ViewConstraints get physicalConstraints => ViewConstraints.tight(physicalSize);
ViewConstraints get physicalConstraints {
final ui.Size currentSize = _computePhysicalSize();
return ViewConstraints.fromJs(_jsViewConstraints, currentSize);
}

final JsViewConstraints? _jsViewConstraints;

late final EngineSemanticsOwner semantics = EngineSemanticsOwner(dom.semanticsHost);

Expand All @@ -156,6 +166,7 @@ base class EngineFlutterView implements ui.FlutterView {
}

void resize(ui.Size newPhysicalSize) {
// The browser wants logical sizes!
Copy link
Contributor

Choose a reason for hiding this comment

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

We should really have separate PhysicalSize and LogicalSize classes so we don't confuse the two (with methods to convert from one to the other e.g. PhysicalSize.toLogicalSize(dpr) and LogicalSize.toPhysicalSize(dpr)). Maybe when extension type is a thing we can give this a try!

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, Size is very generic, but valid most of the time. We really only care about this in the interfaces between systems:

Framework (logical, most of the time) <-> Engine (physical, most of the time) <-> Browser (logical, always?)

This sounds like an error waiting to happen (well, my earlier HTML renderer PR is an example of that exact error, I guess! :P)

Copy link
Member Author

Choose a reason for hiding this comment

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

It isn't the same as having two separate types, but I've started being very careful documenting the type of size required depending on where in the code we are: if values come from JS/CSS/the browser in general -> logical size; if we're passing things to the framework -> physical size. Also attempted to reflect that in the variable/parameter names.

final ui.Size logicalSize = newPhysicalSize / devicePixelRatio;
dom.rootElement.style
..width = '${logicalSize.width}px'
Expand Down Expand Up @@ -284,8 +295,10 @@ base class EngineFlutterView implements ui.FlutterView {
final class _EngineFlutterViewImpl extends EngineFlutterView {
_EngineFlutterViewImpl(
EnginePlatformDispatcher platformDispatcher,
DomElement hostElement,
) : super._(_nextViewId++, platformDispatcher, hostElement);
DomElement hostElement, {
JsViewConstraints? viewConstraints,
}
) : super._(_nextViewId++, platformDispatcher, hostElement, viewConstraints: viewConstraints);
}

/// The Web implementation of [ui.SingletonFlutterWindow].
Expand Down Expand Up @@ -714,6 +727,21 @@ class ViewConstraints implements ui.ViewConstraints {
minHeight = size.height,
maxHeight = size.height;

factory ViewConstraints.fromJs(JsViewConstraints? constraints, ui.Size? size) {
if (size == null) {
return const ViewConstraints();
}
if (constraints == null) {
return ViewConstraints.tight(size);
}
return ViewConstraints(
minWidth: _computeMinConstraintValue(constraints.minWidth, size.width),
minHeight: _computeMinConstraintValue(constraints.minHeight, size.height),
maxWidth: _computeMaxConstraintValue(constraints.maxWidth, size.width),
maxHeight: _computeMaxConstraintValue(constraints.maxHeight, size.height),
);
}

@override
final double minWidth;
@override
Expand Down Expand Up @@ -780,3 +808,28 @@ class ViewConstraints implements ui.ViewConstraints {
return 'ViewConstraints($width, $height)';
}
}

// Computes the "min" value for a constraint that takes into account user configuration
// and the actual available size.
//
// Returns the configured userValue, unless it's null (not passed) in which it returns
// the actual physicalSize.
double _computeMinConstraintValue(double? desired, double available) {
assert(desired == null || desired >= 0, 'Minimum constraint cannot be less than 0');
return desired ?? available;
}

// Computes the "max" value for a constraint that takes into account user configuration
// and the available size.
//
// Returns the configured userValue unless:
// * It is null, in which case it returns the physicalSize
// * It is `-1`, in which case it returns "infinity" / unconstrained.
double _computeMaxConstraintValue(double? desired, double available) {
assert(desired == null || desired >= -1, 'Maximum constraint must be greater than 0 (or -1 for unconstrained)');
return switch (desired) {
null => available,
-1 => double.infinity,
_ => desired,
};
}