-
Notifications
You must be signed in to change notification settings - Fork 6k
[web] Fix Scene clip bounds. Trigger resize on DPR Change. #50161
Changes from all commits
f4286eb
cf027d4
6124efb
4246857
f0348c9
fdd3d5c
96b0ff6
8f1a7e8
90c71e4
b061ada
c4933d1
3f19731
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 |
---|---|---|
|
@@ -4,6 +4,7 @@ | |
|
||
import 'dart:async'; | ||
|
||
import 'package:ui/src/engine/display.dart'; | ||
import 'package:ui/src/engine/dom.dart'; | ||
import 'package:ui/src/engine/window.dart'; | ||
import 'package:ui/ui.dart' as ui show Size; | ||
|
@@ -12,21 +13,36 @@ import 'dimensions_provider.dart'; | |
|
||
/// This class provides observable, real-time dimensions of a host element. | ||
/// | ||
/// This class needs a `Stream` of `devicePixelRatio` changes, like the one | ||
/// provided by [DisplayDprStream], because html resize observers do not report | ||
/// DPR changes. | ||
/// | ||
/// All the measurements returned from this class are potentially *expensive*, | ||
/// and should be cached as needed. Every call to every method on this class | ||
/// WILL perform actual DOM measurements. | ||
/// | ||
/// This broadcasts `null` size events, to match the implementation of the | ||
/// FullPageDimensionsProvider, but it could broadcast the size coming from the | ||
/// DomResizeObserverEntry. Further changes in the engine are required for this | ||
/// to be effective. | ||
ditman marked this conversation as resolved.
Show resolved
Hide resolved
|
||
class CustomElementDimensionsProvider extends DimensionsProvider { | ||
/// Creates a [CustomElementDimensionsProvider] from a [_hostElement]. | ||
CustomElementDimensionsProvider(this._hostElement) { | ||
CustomElementDimensionsProvider(this._hostElement, { | ||
Stream<double>? onDprChange, | ||
}) { | ||
// Send a resize event when the page DPR changes. | ||
_dprChangeStreamSubscription = onDprChange?.listen((_) { | ||
_broadcastSize(null); | ||
}); | ||
|
||
// Hook up a resize observer on the hostElement (if supported!). | ||
_hostElementResizeObserver = createDomResizeObserver(( | ||
List<DomResizeObserverEntry> entries, | ||
DomResizeObserver _, | ||
) { | ||
entries | ||
.map((DomResizeObserverEntry entry) => | ||
ui.Size(entry.contentRect.width, entry.contentRect.height)) | ||
.forEach(_broadcastSize); | ||
for (final DomResizeObserverEntry _ in entries) { | ||
_broadcastSize(null); | ||
} | ||
}); | ||
|
||
assert(() { | ||
|
@@ -45,11 +61,12 @@ class CustomElementDimensionsProvider extends DimensionsProvider { | |
|
||
// Handle resize events | ||
late DomResizeObserver? _hostElementResizeObserver; | ||
final StreamController<ui.Size> _onResizeStreamController = | ||
StreamController<ui.Size>.broadcast(); | ||
late StreamSubscription<double>? _dprChangeStreamSubscription; | ||
final StreamController<ui.Size?> _onResizeStreamController = | ||
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. Should we also change the type to 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. Nope, I really really want this to be a |
||
StreamController<ui.Size?>.broadcast(); | ||
|
||
// Broadcasts the last seen `Size`. | ||
void _broadcastSize(ui.Size size) { | ||
void _broadcastSize(ui.Size? size) { | ||
_onResizeStreamController.add(size); | ||
} | ||
|
||
|
@@ -58,16 +75,17 @@ class CustomElementDimensionsProvider extends DimensionsProvider { | |
super.close(); | ||
_hostElementResizeObserver?.disconnect(); | ||
// ignore:unawaited_futures | ||
_dprChangeStreamSubscription?.cancel(); | ||
// ignore:unawaited_futures | ||
_onResizeStreamController.close(); | ||
} | ||
|
||
@override | ||
Stream<ui.Size> get onResize => _onResizeStreamController.stream; | ||
Stream<ui.Size?> get onResize => _onResizeStreamController.stream; | ||
|
||
@override | ||
ui.Size computePhysicalSize() { | ||
final double devicePixelRatio = getDevicePixelRatio(); | ||
|
||
final double devicePixelRatio = EngineFlutterDisplay.instance.devicePixelRatio; | ||
return ui.Size( | ||
_hostElement.clientWidth * devicePixelRatio, | ||
_hostElement.clientHeight * devicePixelRatio, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
// Copyright 2013 The Flutter Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style license that can be | ||
// found in the LICENSE file. | ||
|
||
import 'dart:async'; | ||
import 'dart:js_interop'; | ||
|
||
import 'package:meta/meta.dart'; | ||
import 'package:ui/src/engine/display.dart'; | ||
import 'package:ui/src/engine/dom.dart'; | ||
import 'package:ui/ui.dart' as ui show Display; | ||
|
||
/// Provides a stream of `devicePixelRatio` changes for the given display. | ||
/// | ||
/// Note that until the Window Management API is generally available, this class | ||
/// only monitors the global `devicePixelRatio` property, provided by the default | ||
/// [EngineFlutterDisplay.instance]. | ||
/// | ||
/// See: https://developer.mozilla.org/en-US/docs/Web/API/Window_Management_API | ||
class DisplayDprStream { | ||
ditman marked this conversation as resolved.
Show resolved
Hide resolved
|
||
DisplayDprStream( | ||
this._display, { | ||
@visibleForTesting DebugDisplayDprStreamOverrides? overrides, | ||
}) : _currentDpr = _display.devicePixelRatio, | ||
_debugOverrides = overrides { | ||
// Start listening to DPR changes. | ||
_subscribeToMediaQuery(); | ||
} | ||
|
||
/// A singleton instance of DisplayDprStream. | ||
static DisplayDprStream instance = | ||
DisplayDprStream(EngineFlutterDisplay.instance); | ||
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. Do we want to continue with the singleton pattern? I was under the impression that we were removing all singletons except one top-level class (was it 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. Yeah, we're still using the singleton pattern for things that are ONE only (browser globals). (I tried to make this instance-based (by passing a Display), hoping that this API1 will be eventually usable, but it's still unclear to me from the docs how it can be used to monitor DPR changes, though!) FootnotesThere 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. In one of our recent-ish discussions, we entertained the idea of keeping only ONE singleton in the engine (e.g. E.g. instead of It was just a discussion though. We haven't made any real changes in the code base following that discussion. But I think at some point we should start moving to that pattern. 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. Ah, I understand now! Yes, I could move this to a singleton of the EnginePlatformDispatcher instead of being a loose object. 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. I think the litmus test is that code that consumes an external service should not go through a static to get a reference to that service. I don't insist that this is fixed in this PR, unless we already switched modes to a singleton-free design. 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. I changed this a little bit. The CustomElement dimensions provider now just requires a Stream passed by its constructor. For now, the abstract factory class of DimensionsProviders knows where to get that stream from (the singleton instance), but it's very likely that it could just be created from there. Anyway, I'm going to |
||
|
||
// The display object that will provide the DPR information. | ||
final ui.Display _display; | ||
|
||
// Last reported value of DPR. | ||
double _currentDpr; | ||
|
||
// Controls the [dprChanged] broadcast Stream. | ||
final StreamController<double> _dprStreamController = | ||
StreamController<double>.broadcast(); | ||
|
||
// Object that fires a `change` event for the `_currentDpr`. | ||
late DomEventTarget _dprMediaQuery; | ||
|
||
// Creates the media query for the latest known DPR value, and adds a change listener to it. | ||
void _subscribeToMediaQuery() { | ||
if (_debugOverrides?.getMediaQuery != null) { | ||
_dprMediaQuery = _debugOverrides!.getMediaQuery!(_currentDpr); | ||
} else { | ||
_dprMediaQuery = domWindow.matchMedia('(resolution: ${_currentDpr}dppx)'); | ||
} | ||
_dprMediaQuery.addEventListenerWithOptions( | ||
'change', | ||
createDomEventListener(_onDprMediaQueryChange), | ||
<String, Object>{ | ||
// We only listen `once` because this event only triggers once when the | ||
// DPR changes from `_currentDpr`. Once that happens, we need a new | ||
// `_dprMediaQuery` that is watching the new `_currentDpr`. | ||
// | ||
// By using `once`, we don't need to worry about detaching the event | ||
// listener from the old mediaQuery after we're done with it. | ||
'once': true, | ||
'passive': true, | ||
}, | ||
); | ||
} | ||
|
||
// Handler of the _dprMediaQuery 'change' event. | ||
// | ||
// This calls subscribe again because events are listened to with `once: true`. | ||
ditman marked this conversation as resolved.
Show resolved
Hide resolved
|
||
JSVoid _onDprMediaQueryChange(DomEvent _) { | ||
_currentDpr = _display.devicePixelRatio; | ||
_dprStreamController.add(_currentDpr); | ||
// Re-subscribe... | ||
_subscribeToMediaQuery(); | ||
} | ||
|
||
/// A stream that emits the latest value of [EngineFlutterDisplay.instance.devicePixelRatio]. | ||
Stream<double> get dprChanged => _dprStreamController.stream; | ||
|
||
// The overrides object that is used for testing. | ||
final DebugDisplayDprStreamOverrides? _debugOverrides; | ||
} | ||
|
||
@visibleForTesting | ||
class DebugDisplayDprStreamOverrides { | ||
DebugDisplayDprStreamOverrides({ | ||
this.getMediaQuery, | ||
}); | ||
final DomEventTarget Function(double currentValue)? getMediaQuery; | ||
} |
Uh oh!
There was an error while loading. Please reload this page.