blob: 065f58f6c84c4621d7f6cf1bda80d78d48454462 [file] [log] [blame]
// 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:html' as html;
import 'package:ui/ui.dart' as ui;
import '../browser_detection.dart';
import '../dom.dart';
import '../util.dart';
import '../vector_math.dart';
import 'shaders/shader.dart';
import 'surface.dart';
import 'surface_stats.dart';
/// A surface that applies an image filter to background.
class PersistedBackdropFilter extends PersistedContainerSurface
implements ui.BackdropFilterEngineLayer {
PersistedBackdropFilter(PersistedBackdropFilter? oldLayer, this.filter)
: super(oldLayer);
final EngineImageFilter filter;
/// The dedicated child container element that's separate from the
/// [rootElement] is used to host child in front of [filterElement] that
/// is transformed to cover background.
@override
html.Element? get childContainer => _childContainer as html.Element?;
DomElement? _childContainer;
DomElement? _filterElement;
ui.Rect? _activeClipBounds;
// Cached inverted transform for [transform].
late Matrix4 _invertedTransform;
// Reference to transform last used to cache [_invertedTransform].
Matrix4? _previousTransform;
@override
void adoptElements(PersistedBackdropFilter oldSurface) {
super.adoptElements(oldSurface);
_childContainer = oldSurface._childContainer;
_filterElement = oldSurface._filterElement;
oldSurface._childContainer = null;
}
@override
html.Element createElement() {
final DomElement element = defaultCreateElement('flt-backdrop') as
DomElement;
element.style.transformOrigin = '0 0 0';
_childContainer = createDomElement('flt-backdrop-interior');
_childContainer!.style.position = 'absolute';
if (debugExplainSurfaceStats) {
// This creates an additional interior element. Count it too.
surfaceStatsFor(this).allocatedDomNodeCount++;
}
_filterElement = defaultCreateElement('flt-backdrop-filter') as DomElement;
_filterElement!.style.transformOrigin = '0 0 0';
element..append(_filterElement!)..append(_childContainer!);
return element as html.Element;
}
@override
void discard() {
super.discard();
// Do not detach the child container from the root. It is permanently
// attached. The elements are reused together and are detached from the DOM
// together.
_childContainer = null;
_filterElement = null;
}
@override
void apply() {
if (_previousTransform != transform) {
_invertedTransform = Matrix4.inverted(transform!);
_previousTransform = transform;
}
// https://api.flutter.dev/flutter/widgets/BackdropFilter-class.html
// Defines the effective area as the parent/ancestor clip or if not
// available, the whole screen.
//
// The CSS backdrop-filter will use isolation boundary defined in
// https://drafts.fxtf.org/filter-effects-2/#BackdropFilterProperty
// Therefore we need to use parent clip element bounds for
// backdrop boundary.
final double dpr = ui.window.devicePixelRatio;
final ui.Rect rect = transformRect(_invertedTransform, ui.Rect.fromLTRB(0, 0,
ui.window.physicalSize.width * dpr,
ui.window.physicalSize.height * dpr));
double left = rect.left;
double top = rect.top;
double width = rect.width;
double height = rect.height;
PersistedContainerSurface? parentSurface = parent;
while (parentSurface != null) {
if (parentSurface.isClipping) {
final ui.Rect activeClipBounds = (_activeClipBounds = parentSurface.localClipBounds)!;
left = activeClipBounds.left;
top = activeClipBounds.top;
width = activeClipBounds.width;
height = activeClipBounds.height;
break;
}
parentSurface = parentSurface.parent;
}
final DomCSSStyleDeclaration filterElementStyle = _filterElement!.style;
filterElementStyle
..position = 'absolute'
..left = '${left}px'
..top = '${top}px'
..width = '${width}px'
..height = '${height}px';
if (browserEngine == BrowserEngine.firefox) {
// For FireFox for now render transparent black background.
// TODO(ferhat): Switch code to use filter when
// See https://caniuse.com/#feat=css-backdrop-filter.
filterElementStyle
..backgroundColor = '#000'
..opacity = '0.2';
} else {
// CSS uses pixel radius for blur. Flutter & SVG use sigma parameters. For
// Gaussian blur with standard deviation (normal distribution),
// the blur will fall within 2 * sigma pixels.
if (browserEngine == BrowserEngine.webkit) {
setElementStyle(_filterElement!, '-webkit-backdrop-filter',
filter.filterAttribute);
}
setElementStyle(_filterElement!, 'backdrop-filter', filter.filterAttribute);
}
}
@override
void update(PersistedBackdropFilter oldSurface) {
super.update(oldSurface);
if (filter != oldSurface.filter) {
apply();
} else {
_checkForUpdatedAncestorClipElement();
}
}
void _checkForUpdatedAncestorClipElement() {
// If parent clip element has moved, adjust bounds.
PersistedContainerSurface? parentSurface = parent;
while (parentSurface != null) {
if (parentSurface.isClipping) {
if (parentSurface.localClipBounds != _activeClipBounds) {
apply();
}
break;
}
parentSurface = parentSurface.parent;
}
}
@override
void retain() {
super.retain();
_checkForUpdatedAncestorClipElement();
}
}