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
1 change: 1 addition & 0 deletions lib/ui/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ source_set("ui") {

public_deps = [
"//flutter/flow",
"//flutter/shell/common:display",
"//flutter/shell/common:platform_message_handler",
"//flutter/third_party/txt",
]
Expand Down
43 changes: 36 additions & 7 deletions lib/ui/fixtures/ui_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import 'dart:async';
import 'dart:typed_data';
import 'dart:ui';
import 'dart:isolate';
import 'dart:ffi';
import 'dart:ffi' hide Size;

void main() {}

Expand Down Expand Up @@ -444,7 +444,7 @@ void hooksTests() async {
window.onMetricsChanged!();
_callHook(
'_updateWindowMetrics',
20,
21,
0, // window Id
0.1234, // device pixel ratio
0.0, // width
Expand All @@ -465,6 +465,7 @@ void hooksTests() async {
<double>[], // display features bounds
<int>[], // display features types
<int>[], // display features states
0, // Display ID
);

expectIdentical(originalZone, callbackZone);
Expand Down Expand Up @@ -540,7 +541,7 @@ void hooksTests() async {
await test('View padding/insets/viewPadding/systemGestureInsets', () {
_callHook(
'_updateWindowMetrics',
20,
21,
0, // window Id
1.0, // devicePixelRatio
800.0, // width
Expand All @@ -561,6 +562,7 @@ void hooksTests() async {
<double>[], // display features bounds
<int>[], // display features types
<int>[], // display features states
0, // Display ID
);

expectEquals(window.viewInsets.bottom, 0.0);
Expand All @@ -570,7 +572,7 @@ void hooksTests() async {

_callHook(
'_updateWindowMetrics',
20,
21,
0, // window Id
1.0, // devicePixelRatio
800.0, // width
Expand All @@ -591,6 +593,7 @@ void hooksTests() async {
<double>[], // display features bounds
<int>[], // display features types
<int>[], // display features states
0, // Display ID
);

expectEquals(window.viewInsets.bottom, 400.0);
Expand All @@ -602,7 +605,7 @@ void hooksTests() async {
await test('Window physical touch slop', () {
_callHook(
'_updateWindowMetrics',
20,
21,
0, // window Id
1.0, // devicePixelRatio
800.0, // width
Expand All @@ -623,14 +626,15 @@ void hooksTests() async {
<double>[], // display features bounds
<int>[], // display features types
<int>[], // display features states
0, // Display ID
);

expectEquals(window.gestureSettings,
GestureSettings(physicalTouchSlop: 11.0));

_callHook(
'_updateWindowMetrics',
20,
21,
0, // window Id
1.0, // devicePixelRatio
800.0, // width
Expand All @@ -651,14 +655,15 @@ void hooksTests() async {
<double>[], // display features bounds
<int>[], // display features types
<int>[], // display features states
0, // Display ID
);

expectEquals(window.gestureSettings,
GestureSettings(physicalTouchSlop: null));

_callHook(
'_updateWindowMetrics',
20,
21,
0, // window Id
1.0, // devicePixelRatio
800.0, // width
Expand All @@ -679,6 +684,7 @@ void hooksTests() async {
<double>[], // display features bounds
<int>[], // display features types
<int>[], // display features states
0, // Display ID
);

expectEquals(window.gestureSettings,
Expand Down Expand Up @@ -881,6 +887,28 @@ void hooksTests() async {
expectEquals(frameNumber, 2);
});

await test('_updateDisplays preserves callback zone', () {
late Zone innerZone;
late Zone runZone;
late Display display;

runZoned(() {
innerZone = Zone.current;
window.onMetricsChanged = () {
runZone = Zone.current;
display = PlatformDispatcher.instance.displays.first;
};
});

_callHook('_updateDisplays', 5, <int>[0], <double>[800], <double>[600], <double>[1.5], <double>[65]);
expectNotEquals(runZone, null);
expectIdentical(runZone, innerZone);
expectEquals(display.id, 0);
expectEquals(display.size, const Size(800, 600));
expectEquals(display.devicePixelRatio, 1.5);
expectEquals(display.refreshRate, 65);
});

await test('_futureize handles callbacker sync error', () async {
String? callbacker(void Function(Object? arg) cb) {
return 'failure';
Expand Down Expand Up @@ -1043,4 +1071,5 @@ external void _callHook(
Object? arg18,
Object? arg19,
Object? arg20,
Object? arg21,
]);
28 changes: 28 additions & 0 deletions lib/ui/hooks.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,32 @@

part of dart.ui;

@pragma('vm:entry-point')
void _updateDisplays(
List<int> ids,
List<double> widths,
List<double> heights,
List<double> devicePixelRatios,
List<double> refreshRates,
) {
assert(ids.length == widths.length);
assert(ids.length == heights.length);
assert(ids.length == devicePixelRatios.length);
assert(ids.length == refreshRates.length);
final List<Display> displays = <Display>[];
for (int index = 0; index < ids.length; index += 1) {
final int displayId = ids[index];
displays.add(Display._(
id: displayId,
size: Size(widths[index], heights[index]),
devicePixelRatio: devicePixelRatios[index],
refreshRate: refreshRates[index],
));
}

PlatformDispatcher.instance._updateDisplays(displays);
}

@pragma('vm:entry-point')
void _updateWindowMetrics(
Object id,
Expand All @@ -26,6 +52,7 @@ void _updateWindowMetrics(
List<double> displayFeaturesBounds,
List<int> displayFeaturesType,
List<int> displayFeaturesState,
int displayId,
) {
PlatformDispatcher.instance._updateWindowMetrics(
id,
Expand All @@ -48,6 +75,7 @@ void _updateWindowMetrics(
displayFeaturesBounds,
displayFeaturesType,
displayFeaturesState,
displayId,
);
}

Expand Down
40 changes: 40 additions & 0 deletions lib/ui/platform_dispatcher.dart
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,26 @@ class PlatformDispatcher {
_onPlatformConfigurationChangedZone = Zone.current;
}

/// The current list of displays.
///
/// If any of their configurations change, [onMetricsChanged] will be called.
///
Copy link
Member

Choose a reason for hiding this comment

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

Maybe document some of the current platform-specific limitations? Presumably, this is just empty for desktops right now?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I added some docs.

Copy link
Member

Choose a reason for hiding this comment

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

The same caveat should likely also be documented on the FlutterView.display property?

/// To get the display for a [FlutterView], use [FlutterView.display].
///
/// Platforms may limit what information is available to the application with
/// regard to secondary displays and/or displays that do not have an active
/// application window.
///
/// Presently, on Android and Web this collection will only contain the
/// display that the current window is on. On iOS, it will only contains the
/// main display on the phone or tablet. On Desktop, it will contain only
/// a main display with a valid refresh rate but invalid size and device
/// pixel ratio values.
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it possible to make these values missing or null instead of invalid (which I assume means incorrect).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't want to make them null, since it will be harder to migrate them away from nullable later if people start treating them as nullable (e.g. they add ! all over and the compiler refuses to compiile things when we switch it).

The plan is to implement them for desktop sooner than later.

// TODO(dnfield): Update these docs when https://github.com/flutter/flutter/issues/125939
// and https://github.com/flutter/flutter/issues/125938 are resolved.
Iterable<Display> get displays => _displays.values;
final Map<int, Display> _displays = <int, Display>{};

/// The current list of views, including top level platform windows used by
/// the application.
///
Expand Down Expand Up @@ -215,6 +235,17 @@ class PlatformDispatcher {
_onMetricsChangedZone = Zone.current;
}

// Called from the engine, via hooks.dart.
//
// Updates the available displays.
void _updateDisplays(List<Display> displays) {
_displays.clear();
for (final Display display in displays) {
_displays[display.id] = display;
}
_invoke(onMetricsChanged, _onMetricsChangedZone);
}

// Called from the engine, via hooks.dart
//
// Updates the metrics of the window with the given id.
Expand All @@ -239,6 +270,7 @@ class PlatformDispatcher {
List<double> displayFeaturesBounds,
List<int> displayFeaturesType,
List<int> displayFeaturesState,
int displayId,
) {
final _ViewConfiguration previousConfiguration =
_viewConfigurations[id] ?? const _ViewConfiguration();
Expand Down Expand Up @@ -283,6 +315,7 @@ class PlatformDispatcher {
state: displayFeaturesState,
devicePixelRatio: devicePixelRatio,
),
displayId: displayId,
);
_invoke(onMetricsChanged, _onMetricsChangedZone);
}
Expand Down Expand Up @@ -1318,6 +1351,7 @@ class _ViewConfiguration {
this.padding = ViewPadding.zero,
this.gestureSettings = const GestureSettings(),
this.displayFeatures = const <DisplayFeature>[],
this.displayId = 0,
});

/// Copy this configuration with some fields replaced.
Expand All @@ -1332,6 +1366,7 @@ class _ViewConfiguration {
ViewPadding? padding,
GestureSettings? gestureSettings,
List<DisplayFeature>? displayFeatures,
int? displayId,
}) {
return _ViewConfiguration(
view: view ?? this.view,
Expand All @@ -1344,11 +1379,16 @@ class _ViewConfiguration {
padding: padding ?? this.padding,
gestureSettings: gestureSettings ?? this.gestureSettings,
displayFeatures: displayFeatures ?? this.displayFeatures,
displayId: displayId ?? this.displayId,
);
}

final FlutterView? view;

/// The identifier for a display for this view, in
/// [PlatformDispatcher._displays].
final int displayId;

/// The pixel density of the output surface.
final double devicePixelRatio;

Expand Down
42 changes: 42 additions & 0 deletions lib/ui/window.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,40 @@
// found in the LICENSE file.
part of dart.ui;

/// A configurable display that a [FlutterView] renders on.
///
/// Use [FlutterView.display] to get the current display for that view.
class Display {
const Display._({
required this.id,
required this.devicePixelRatio,
required this.size,
required this.refreshRate,
});

/// A unique identifier for this display.
///
/// This identifier is unique among a list of displays the Flutter framework
/// is aware of, and is not derived from any platform specific identifiers for
/// displays.
final int id;

/// The device pixel ratio of this display.
///
/// This value is the same as the value of [FlutterView.devicePixelRatio] for
/// all view objects attached to this display.
final double devicePixelRatio;

/// The physical size of this display.
final Size size;

/// The refresh rate in FPS of this display.
final double refreshRate;

@override
String toString() => 'Display(id: $id, size: $size, devicePixelRatio: $devicePixelRatio, refreshRate: $refreshRate)';
}

/// A view into which a Flutter [Scene] is drawn.
///
/// Each [FlutterView] has its own layer tree that is rendered
Expand Down Expand Up @@ -67,6 +101,12 @@ class FlutterView {
return platformDispatcher._viewConfigurations[viewId]!;
}

/// The [Display] this view is drawn in.
Display get display {
assert(platformDispatcher._displays.containsKey(_viewConfiguration.displayId));
Copy link
Member

@loic-sharma loic-sharma May 3, 2023

Choose a reason for hiding this comment

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

Will this fail if the embedder doesn't provide display information? (I'm not sure Windows, macOS, or Linux provide display information)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right now they provide a display that is not quite valid. But yes, the assert would fire if the embedder failed to provide a display.

Copy link
Member

Choose a reason for hiding this comment

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

Should we make this property nullable and force the caller to have some fallback logic if display information isn't available?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think we should just implement it. Making it nullable now and non-nullable later is harder once people start using it (it results in compilation errors, which make the migration in google's monorepo harder). I don't expect it to take long to implement the desktop portion of this, I just don't want to delay this patch further and it's already fairly large.

Copy link
Member

Choose a reason for hiding this comment

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

So, what does this currently return for unimplemented platforms?

Have we verified that this is implementable on all desktop platforms we want to support? Wayland comes to mind as a platform that may not be able to tell us what display we are on (I haven't verified this, this is just from what I have heard). If that turns out to be true, what would we expect this getter to do? For that case, it may be nicer if this can just be null.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right now it's returning -1 for the DPR, width, and height, and a real refresh rate/display ID.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

On Linux, we can indeed get the width and height of at least the display the application is rendering to, and probably all displays. Even if Wayland doesn't have an API for this, you have to tell Wayland to connect to a particular display adapter, and it will be possible to query the information about the display size from that adapter. GTK also has an API for this.

Copy link
Member

Choose a reason for hiding this comment

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

What's the GTK API?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry, I meant GDK :) What the Linux embedding uses for windowing etc.

return platformDispatcher._displays[_viewConfiguration.displayId]!;
}

/// The number of device pixels for each logical pixel for the screen this
Copy link
Member

Choose a reason for hiding this comment

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

There is a little bit of duplicated information available now as dpr as also available via the display getter now. It's probably not an issue, but a little strange? Presumably, both will always have the same value?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

DPR really belongs on the display. I'm half tempted to deprecate it on View, but that seems like it'd create a lot of unnecessary churn.

But yes, DPR on the view will always match DPR from the display.

Copy link
Contributor

Choose a reason for hiding this comment

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

You might want to add to the dart doc to DPR on view that says window is the prefered way if you indeed think that is the case.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You need the DPR if you want to figure out logical pixel sizes. I'll document that they're the same.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added some docs.

/// view is displayed on.
///
Expand All @@ -92,6 +132,8 @@ class FlutterView {
///
/// * [WidgetsBindingObserver], for a mechanism at the widgets layer to
/// observe when this value changes.
/// * [Display.devicePixelRatio], which reports the DPR of the display.
/// The value here is equal to the value on the [display.devicePixelRatio].
double get devicePixelRatio => _viewConfiguration.devicePixelRatio;

/// The dimensions and location of the rectangle into which the scene rendered
Expand Down
Loading