diff --git a/lib/web_ui/lib/src/engine.dart b/lib/web_ui/lib/src/engine.dart index dd76738945bf4..0b95e83d43aa4 100644 --- a/lib/web_ui/lib/src/engine.dart +++ b/lib/web_ui/lib/src/engine.dart @@ -93,6 +93,7 @@ part 'engine/surface/backdrop_filter.dart'; part 'engine/surface/clip.dart'; part 'engine/surface/debug_canvas_reuse_overlay.dart'; part 'engine/surface/image_filter.dart'; +part 'engine/surface/link.dart'; part 'engine/surface/offset.dart'; part 'engine/surface/opacity.dart'; part 'engine/surface/painting.dart'; diff --git a/lib/web_ui/lib/src/engine/compositor/layer_scene_builder.dart b/lib/web_ui/lib/src/engine/compositor/layer_scene_builder.dart index b2302e92c0040..39a657f150ba7 100644 --- a/lib/web_ui/lib/src/engine/compositor/layer_scene_builder.dart +++ b/lib/web_ui/lib/src/engine/compositor/layer_scene_builder.dart @@ -175,6 +175,18 @@ class LayerSceneBuilder implements ui.SceneBuilder { return layer; } + @override + ui.LinkEngineLayer pushLink({ + String destination, + String label, + int target, + ui.Rect rect, + ui.LinkEngineLayer oldLayer, + }) { + // TODO: implement. + return null; + } + @override ui.PhysicalShapeEngineLayer pushPhysicalShape({ ui.Path path, diff --git a/lib/web_ui/lib/src/engine/surface/link.dart b/lib/web_ui/lib/src/engine/surface/link.dart new file mode 100644 index 0000000000000..10c631618fd42 --- /dev/null +++ b/lib/web_ui/lib/src/engine/surface/link.dart @@ -0,0 +1,124 @@ +// 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. + +// @dart = 2.6 +part of engine; + +/// A surface that translates its children using CSS transform and translate. +class PersistedLink extends PersistedContainerSurface + implements ui.LinkEngineLayer { + PersistedLink( + PersistedLink oldLayer, { + this.destination, + this.label, + this.target, + this.rect, + }) : super(oldLayer); + + /// + final String destination; + + /// + final String label; + + /// + final int target; + + /// + final ui.Rect rect; + + /// + @override + html.Element get childContainer => _childContainer; + html.Element _childContainer; + + @override + void adoptElements(PersistedLink oldSurface) { + super.adoptElements(oldSurface); + _childContainer = oldSurface._childContainer; + oldSurface._childContainer = null; + } + + @override + html.Element createElement() { + final html.Element element = defaultCreateElement('a'); + element.style + ..transformOrigin = '0 0 0' + ..pointerEvents = 'auto'; + if (assertionsEnabled) { + element..setAttribute('data-debug-tag', 'flt-link'); + } + + _childContainer = html.Element.tag('flt-link-interior'); + _childContainer.style.position = 'relative'; + element.append(_childContainer); + if (_debugExplainSurfaceStats) { + // This creates an additional interior element. Count it too. + _surfaceStatsFor(this).allocatedDomNodeCount++; + } + + return element; + } + + @override + void discard() { + super.discard(); + _childContainer = null; + } + + @override + void apply() { + rootElement + ..setAttribute('href', destination) + ..setAttribute('alt', label) + ..setAttribute('target', _getHtmlTarget(target)); + _setRect(rect); + } + + @override + void update(PersistedLink oldSurface) { + super.update(oldSurface); + if (destination != oldSurface.destination) { + rootElement.setAttribute('href', destination); + } + if (label != oldSurface.label) { + rootElement.setAttribute('alt', label); + } + if (target != oldSurface.target) { + rootElement.setAttribute('target', _getHtmlTarget(target)); + } + if (rect != oldSurface.rect) { + _setRect(rect); + } + } + + void _setRect(ui.Rect rect) { + // Because the element is absolutely positioned, it doesn't grow to fit + // its children. We have explicitly set width and height in order for it to + // occupy space on the screen. + rootElement.style + ..transform = 'translate(${rect.left}px, ${rect.top}px)' + ..width = '${rect.width}px' + ..height = '${rect.height}px'; + + // The same offset given to PersistedLink is also given to its children. In + // order to avoid double-offsetting the children, we wrap them in a + // container with an equal, negative offset. + _childContainer.style + ..left = '-${rect.left}px' + ..top = '-${rect.top}px'; + } +} + +String _getHtmlTarget(int target) { + switch (target) { + case 0: // LinkTarget.defaultTarget + case 1: // LinkTarget.self + return '_self'; + case 2: // LinkTarget.blank + return '_blank'; + default: + throw Exception('Unknown LinkTarget value $target.'); + } +} diff --git a/lib/web_ui/lib/src/engine/surface/scene_builder.dart b/lib/web_ui/lib/src/engine/surface/scene_builder.dart index 9c4d8e55c4e98..06da078d5a1ba 100644 --- a/lib/web_ui/lib/src/engine/surface/scene_builder.dart +++ b/lib/web_ui/lib/src/engine/surface/scene_builder.dart @@ -182,6 +182,24 @@ class SurfaceSceneBuilder implements ui.SceneBuilder { return _pushSurface(PersistedOpacity(oldLayer, alpha, offset)); } + /// + @override + ui.LinkEngineLayer pushLink({ + String destination, + String label, + int target, + ui.Rect rect, + ui.LinkEngineLayer oldLayer, + }) { + return _pushSurface(PersistedLink( + oldLayer, + destination: destination, + label: label, + target: target, + rect: rect, + )); + } + /// Pushes a color filter operation onto the operation stack. /// /// The given color is applied to the objects' rasterization using the given diff --git a/lib/web_ui/lib/src/ui/compositing.dart b/lib/web_ui/lib/src/ui/compositing.dart index 0bdd3441a4499..4c78938cc9e5e 100644 --- a/lib/web_ui/lib/src/ui/compositing.dart +++ b/lib/web_ui/lib/src/ui/compositing.dart @@ -40,6 +40,9 @@ abstract class TransformEngineLayer implements EngineLayer {} /// {@macro dart.ui.sceneBuilder.oldLayerCompatibility} abstract class OffsetEngineLayer implements EngineLayer {} +/// +abstract class LinkEngineLayer implements EngineLayer {} + /// An opaque handle to a clip rect engine layer. /// /// Instances of this class are created by [SceneBuilder.pushClipRect]. @@ -131,6 +134,15 @@ abstract class SceneBuilder { OffsetEngineLayer oldLayer, }); + /// + LinkEngineLayer pushLink({ + String destination, + String label, + int target, + Rect rect, + LinkEngineLayer oldLayer, + }); + /// Pushes a transform operation onto the operation stack. /// /// The objects are transformed by the given matrix before rasterization.