Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Background images #1057

Merged
merged 8 commits into from
Oct 27, 2023
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
38 changes: 36 additions & 2 deletions packages/core/lib/src/core_widget_factory.dart
Original file line number Diff line number Diff line change
Expand Up @@ -146,8 +146,12 @@ class WidgetFactory extends WidgetFactoryResetter with AnchorWidgetFactory {
BoxBorder? border,
BorderRadius? borderRadius,
Color? color,
String? bgImageUrl,
}) {
if (border == null && borderRadius == null && color == null) {
if (border == null &&
borderRadius == null &&
color == null &&
bgImageUrl == null) {
return child;
}

Expand All @@ -156,7 +160,11 @@ class WidgetFactory extends WidgetFactoryResetter with AnchorWidgetFactory {
final prevDeco = container?.decoration;
final baseDeco =
prevDeco is BoxDecoration ? prevDeco : const BoxDecoration();
var decoration = baseDeco.copyWith(border: border, color: color);
var decoration = baseDeco.copyWith(
border: border,
color: color,
image: buildDecorationImage(bgImageUrl),
);

var clipBehavior = Clip.none;
if (borderRadius != null) {
Expand All @@ -177,6 +185,31 @@ class WidgetFactory extends WidgetFactoryResetter with AnchorWidgetFactory {
);
}

/// Builds decoration image from [url]
DecorationImage? buildDecorationImage(String? url) {
if (url == null) {
return null;
}

ImageProvider? provider;

if (url.startsWith('asset:')) {
provider = imageProviderFromAsset(url);
} else if (url.startsWith('data:image/')) {
provider = imageProviderFromDataUri(url);
} else if (url.startsWith('file:')) {
provider = imageProviderFromFileUri(url);
} else {
provider = imageProviderFromNetwork(url);
}

if (provider == null) {
return null;
}

return DecorationImage(image: provider);
}

/// Builds [GestureDetector].
///
/// Only [TapGestureRecognizer] is supported for now.
Expand Down Expand Up @@ -977,6 +1010,7 @@ class WidgetFactory extends WidgetFactoryResetter with AnchorWidgetFactory {
switch (key) {
case kCssBackground:
case kCssBackgroundColor:
case kCssBackgroundImage:
tree.register(_styleBackground ??= StyleBackground(this).buildOp);
break;

Expand Down
35 changes: 33 additions & 2 deletions packages/core/lib/src/internal/ops/style_background.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ part of '../core_ops.dart';

const kCssBackground = 'background';
const kCssBackgroundColor = 'background-color';
const kCssBackgroundImage = 'background-image';

class StyleBackground {
final WidgetFactory wf;
Expand All @@ -13,12 +14,19 @@ class StyleBackground {
debugLabel: kCssBackground,
onRenderBlock: (tree, placeholder) {
final color = _parseColor(tree);
if (color == null) {
final bgImageUrl = _parseBackgroundImageUrl(wf, tree);

if (color == null && bgImageUrl == null) {
return placeholder;
}

return placeholder.wrapWith(
(_, child) => wf.buildDecoration(tree, child, color: color),
(_, child) => wf.buildDecoration(
tree,
child,
color: color,
bgImageUrl: bgImageUrl,
),
);
},
onRenderInline: (tree) {
Expand Down Expand Up @@ -60,4 +68,27 @@ class StyleBackground {

return color;
}

/// Attempts to parse the background image URL from the [tree] styles.
String? _parseBackgroundImageUrl(WidgetFactory wf, BuildTree tree) {
for (final style in tree.styles) {
final styleValue = style.value;
if (styleValue == null) {
continue;
}

switch (style.property) {
case kCssBackground:
case kCssBackgroundImage:
for (final expression in style.values) {
if (expression is css.UriTerm) {
return expression.text;
}
}
break;
}
}

return null;
}
}
5 changes: 5 additions & 0 deletions packages/core/test/_.dart
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,11 @@ class Explainer {
if (borderRadius != null && borderRadius is BorderRadius) {
attr.add('radius=${_borderRadius(borderRadius)}');
}

final image = d.image;
if (image != null) {
attr.add("bgimage=$image");
}
}

return attr;
Expand Down
52 changes: 52 additions & 0 deletions packages/core/test/core_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -693,6 +693,58 @@ Future<void> main() async {
});
});

group('background-image', () {
testWidgets('asset', (WidgetTester tester) async {
const assetName = 'test/images/logo.png';
const html =
'<div style="background-image: url(asset:$assetName)">Foo</div>';
final explained = await explain(tester, html);

expect(
explained,
equals(
'[Container:bgimage='
'DecorationImage('
'AssetImage(bundle: null, name: "test/images/logo.png"), Alignment.center, scale 1.0, opacity 1.0, FilterQuality.low),child='
'[CssBlock:child='
'[RichText:(:Foo)]]]',
),
);
});

testWidgets('data uri', (WidgetTester tester) async {
const html = '<div style="background-image: url($kDataUri)">Foo</div>';
final explained = await explain(tester, html);

expect(
explained,
matches(
r'^\[Container:bgimage=DecorationImage\(MemoryImage\(Uint8List#[0-9a-fA-F]+, scale: 1\.0\), '
r'Alignment\.center, scale 1\.0, opacity 1\.0, FilterQuality\.low\),child='
r'\[CssBlock:child='
r'\[RichText:\(:Foo\)\]\]\]$'),
);
});

testWidgets('file', (WidgetTester tester) async {
const fileName = 'test/images/logo.png';
const html =
'<div style="background-image: url(file:$fileName)">Foo</div>';
final explained = await explain(tester, html);

expect(
explained,
equals(
'[Container:bgimage='
'DecorationImage('
'FileImage("/test/images/logo.png", scale: 1.0), Alignment.center, scale 1.0, opacity 1.0, FilterQuality.low),child='
'[CssBlock:child='
'[RichText:(:Foo)]]]',
),
);
});
});

group('color (inline style)', () {
testWidgets('renders hex values', (WidgetTester tester) async {
const html = '<span style="color: #F00">red</span>'
Expand Down