Unverified Commit 7a201485 authored by Dan Field's avatar Dan Field Committed by GitHub

Add colorFilterLayer/Widget (#35468)

parent 539f09f8
b2cef0060add1b33bf65c97aaf80146b54cf7b86 09a19be7d0cd25eb7602b6ead198e8cf234a9ed2
...@@ -1196,6 +1196,45 @@ class ClipPathLayer extends ContainerLayer { ...@@ -1196,6 +1196,45 @@ class ClipPathLayer extends ContainerLayer {
} }
} }
/// A composite layer that applies a [ColorFilter] to its children.
class ColorFilterLayer extends ContainerLayer {
/// Creates a layer that applies a [ColorFilter] to its children.
///
/// The [ColorFilter] property must be non-null before the compositing phase
/// of the pipeline.
ColorFilterLayer({
@required ColorFilter colorFilter,
}) : _colorFilter = colorFilter,
assert(colorFilter != null);
/// The color filter to apply to children.
///
/// The scene must be explicitly recomposited after this property is changed
/// (as described at [Layer]).
ColorFilter get colorFilter => _colorFilter;
ColorFilter _colorFilter;
set colorFilter(ColorFilter value) {
if (value != _colorFilter) {
_colorFilter = value;
markNeedsAddToScene();
}
}
@override
ui.EngineLayer addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]) {
builder.pushColorFilter(colorFilter);
addChildrenToScene(builder, layerOffset);
builder.pop();
return null; // this does not return an engine layer yet.
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<ColorFilter>('colorFilter', colorFilter));
}
}
/// A composited layer that applies a given transformation matrix to its /// A composited layer that applies a given transformation matrix to its
/// children. /// children.
/// ///
......
...@@ -444,6 +444,24 @@ class PaintingContext extends ClipContext { ...@@ -444,6 +444,24 @@ class PaintingContext extends ClipContext {
} }
} }
/// Blend further painting with a color filter.
///
/// * `offset` is the offset from the origin of the canvas' coordinate system
/// to the origin of the caller's coordinate system.
/// * `colorFilter` is the [ColorFilter] value to use when blending the
/// painting done by `painter`.
/// * `painter` is a callback that will paint with the `colorFilter` applied.
/// This function calls the `painter` synchronously.
///
/// A [RenderObject] that uses this function is very likely to require its
/// [RenderObject.alwaysNeedsCompositing] property to return true. That informs
/// ancestor render objects that this render object will include a composited
/// layer, which, for example, causes them to use composited clips.
void pushColorFilter(Offset offset, ColorFilter colorFilter, PaintingContextCallback painter) {
assert(colorFilter != null);
pushLayer(ColorFilterLayer(colorFilter: colorFilter), painter, offset);
}
/// Transform further painting using a matrix. /// Transform further painting using a matrix.
/// ///
/// * `needsCompositing` is whether the child needs compositing. Typically /// * `needsCompositing` is whether the child needs compositing. Typically
......
// Copyright 2019 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:ui';
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'framework.dart';
/// Applies a [ColorFilter] to its child.
@immutable
class ColorFiltered extends SingleChildRenderObjectWidget {
/// Creates a widget that applies a [ColorFilter] to its child.
///
/// The [colorFilter] must not be null.
const ColorFiltered({@required this.colorFilter, Widget child, Key key})
: assert(colorFilter != null),
super(key: key, child: child);
/// The color filter to apply to the child of this widvget.
final ColorFilter colorFilter;
@override
RenderObject createRenderObject(BuildContext context) => _ColorFilterRenderObject(colorFilter);
@override
void updateRenderObject(BuildContext context, _ColorFilterRenderObject renderObject) {
renderObject..colorFilter = colorFilter;
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<ColorFilter>('colorFilter', colorFilter));
}
}
class _ColorFilterRenderObject extends RenderProxyBox {
_ColorFilterRenderObject(this._colorFilter);
ColorFilter get colorFilter => _colorFilter;
ColorFilter _colorFilter;
set colorFilter(ColorFilter value) {
assert(value != null);
if (value != _colorFilter) {
_colorFilter = value;
markNeedsPaint();
}
}
@override
bool get alwaysNeedsCompositing => child != null;
@override
void paint(PaintingContext context, Offset offset) {
context.pushColorFilter(offset, colorFilter, super.paint);
}
}
...@@ -27,6 +27,7 @@ export 'src/widgets/banner.dart'; ...@@ -27,6 +27,7 @@ export 'src/widgets/banner.dart';
export 'src/widgets/basic.dart'; export 'src/widgets/basic.dart';
export 'src/widgets/binding.dart'; export 'src/widgets/binding.dart';
export 'src/widgets/bottom_navigation_bar_item.dart'; export 'src/widgets/bottom_navigation_bar_item.dart';
export 'src/widgets/color_filter.dart';
export 'src/widgets/container.dart'; export 'src/widgets/container.dart';
export 'src/widgets/debug.dart'; export 'src/widgets/debug.dart';
export 'src/widgets/dismissible.dart'; export 'src/widgets/dismissible.dart';
......
...@@ -250,6 +250,15 @@ void main() { ...@@ -250,6 +250,15 @@ void main() {
}); });
}); });
test('mutating ColorFilterLayer fields triggers needsAddToScene', () {
final ColorFilterLayer layer = ColorFilterLayer(
colorFilter: const ColorFilter.mode(Color(0xFFFF0000), BlendMode.color),
);
checkNeedsAddToScene(layer, () {
layer.colorFilter = const ColorFilter.mode(Color(0xFF00FF00), BlendMode.color);
});
});
test('mutating ShaderMaskLayer fields triggers needsAddToScene', () { test('mutating ShaderMaskLayer fields triggers needsAddToScene', () {
const Gradient gradient = RadialGradient(colors: <Color>[Color(0x00000000), Color(0x00000001)]); const Gradient gradient = RadialGradient(colors: <Color>[Color(0x00000000), Color(0x00000001)]);
final Shader shader = gradient.createShader(Rect.zero); final Shader shader = gradient.createShader(Rect.zero);
......
// Copyright 2019 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 'package:flutter_test/flutter_test.dart';
import 'package:flutter/material.dart';
void main() {
testWidgets('Color filter - red', (WidgetTester tester) async {
await tester.pumpWidget(
const RepaintBoundary(
child: ColorFiltered(
colorFilter: ColorFilter.mode(Colors.red, BlendMode.color),
child: Placeholder(),
),
),
);
await expectLater(
find.byType(ColorFiltered),
matchesGoldenFile(
'color_filter_red.png',
version: 1,
),
skip: !isLinux
);
});
testWidgets('Color filter - sepia', (WidgetTester tester) async {
// TODO(dnfield): This should be const. https://github.com/dart-lang/sdk/issues/37503
final ColorFilter sepia = ColorFilter.matrix(<double>[
0.39, 0.769, 0.189, 0, 0, //
0.349, 0.686, 0.168, 0, 0, //
0.272, 0.534, 0.131, 0, 0, //
0, 0, 0, 1, 0, //
]);
await tester.pumpWidget(
RepaintBoundary(
child: ColorFiltered(
colorFilter: sepia,
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(primarySwatch: Colors.blue),
home: Scaffold(
appBar: AppBar(
title: const Text('Sepia ColorFilter Test'),
),
body: const Center(
child:Text('Hooray!'),
),
floatingActionButton: FloatingActionButton(
onPressed: () { },
tooltip: 'Increment',
child: const Icon(Icons.add),
),
),
),
),
)
);
await expectLater(
find.byType(ColorFiltered),
matchesGoldenFile(
'color_filter_sepia.png',
version: 1,
),
skip: !isLinux
);
});
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment