Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Closed
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/web_ui/lib/src/engine.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
12 changes: 12 additions & 0 deletions lib/web_ui/lib/src/engine/compositor/layer_scene_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
124 changes: 124 additions & 0 deletions lib/web_ui/lib/src/engine/surface/link.dart
Original file line number Diff line number Diff line change
@@ -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 <a> 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.');
}
}
18 changes: 18 additions & 0 deletions lib/web_ui/lib/src/engine/surface/scene_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 12 additions & 0 deletions lib/web_ui/lib/src/ui/compositing.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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].
Expand Down Expand Up @@ -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.
Expand Down