Skip to content
Open
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
47 changes: 39 additions & 8 deletions packages/material_ui/lib/src/material.dart
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,13 @@ abstract class MaterialInkController {
/// The ink feature will paint as part of this controller.
void addInkFeature(InkFeature feature);

/// Stop painting the given ink feature and release any references to it.
///
/// Called from [InkFeature.dispose]. Controllers that implement this
/// interface (including custom ones) must remove the feature from any
/// internal list used for painting.
void removeInkFeature(InkFeature feature);

/// Notifies the controller that one of its ink features needs to repaint.
void markNeedsPaint();
}
Expand Down Expand Up @@ -590,14 +597,18 @@ class _RenderInkFeatures extends RenderProxyBox implements MaterialInkController
@override
void addInkFeature(InkFeature feature) {
assert(!feature._debugDisposed);
assert(feature._controller == this);
// Custom controllers that are not RenderObjects may delegate here so ink
// can still paint on the host Material. Only require identity when the
// feature's controller is itself a RenderObject.
assert(feature.controller is! RenderObject || identical(feature.controller, this));
_inkFeatures ??= <InkFeature>[];
assert(!_inkFeatures!.contains(feature));
_inkFeatures!.add(feature);
markNeedsPaint();
}

void _removeFeature(InkFeature feature) {
@override
void removeInkFeature(InkFeature feature) {
assert(_inkFeatures != null);
_inkFeatures!.remove(feature);
markNeedsPaint();
Expand Down Expand Up @@ -670,19 +681,23 @@ class _InkFeatures extends SingleChildRenderObjectWidget {
abstract class InkFeature {
/// Initializes fields for subclasses.
InkFeature({
required MaterialInkController controller,
required this.controller,
required this.referenceBox,
this.onRemoved,
}) : _controller = controller as _RenderInkFeatures {
}) {
assert(debugMaybeDispatchCreated('material', 'InkFeature', this));
}

/// The [MaterialInkController] associated with this [InkFeature].
///
/// Typically used by subclasses to call
/// [MaterialInkController.markNeedsPaint] when they need to repaint.
MaterialInkController get controller => _controller;
final _RenderInkFeatures _controller;
///
/// Controllers returned by [Material.of] are [RenderObject]s and can paint
/// ink. Custom [MaterialInkController] implementations may delegate
/// [MaterialInkController.addInkFeature] to the host controller; painting
/// then uses the enclosing Material render object.
final MaterialInkController controller;

/// The render box whose visual position defines the frame of reference for this ink feature.
final RenderBox referenceBox;
Expand All @@ -701,7 +716,7 @@ abstract class InkFeature {
return true;
}());
assert(debugMaybeDispatchDisposed(this));
_controller._removeFeature(this);
controller.removeInkFeature(this);
onRemoved?.call();
}

Expand Down Expand Up @@ -762,8 +777,24 @@ abstract class InkFeature {
void _paint(Canvas canvas) {
assert(referenceBox.attached);
assert(!_debugDisposed);
// Prefer the controller when it is a RenderObject (Material.of). Custom
// delegating controllers are not RenderObjects, so fall back to the
// enclosing _RenderInkFeatures ancestor of the reference box.
RenderObject? paintContext;
if (controller is RenderObject) {
paintContext = controller as RenderObject;
} else {
RenderObject? ancestor = referenceBox.parent;
while (ancestor != null && ancestor is! _RenderInkFeatures) {
ancestor = ancestor.parent;
}
paintContext = ancestor;
}
if (paintContext == null) {
return;
}
// determine the transform that gets our coordinate system to be like theirs
final Matrix4? transform = _getPaintTransform(_controller, referenceBox);
final Matrix4? transform = _getPaintTransform(paintContext, referenceBox);
if (transform != null) {
paintFeature(canvas, transform);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
changelog: |
- Allows custom `MaterialInkController` implementations by removing a private type cast in `InkFeature` and adding `MaterialInkController.removeInkFeature`.
version: patch
65 changes: 65 additions & 0 deletions packages/material_ui/test/material_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1128,6 +1128,40 @@ void main() {
);
});

testWidgets('InkFeature accepts a custom MaterialInkController', (WidgetTester tester) async {
await tester.pumpWidget(const Material(child: SizedBox(width: 40, height: 40)));

final Element element = tester.element(find.byType(SizedBox));
final MaterialInkController host = Material.of(element);
final referenceBox = element.findRenderObject()! as RenderBox;
final customController = _DelegatingMaterialInkController(host);

expect(
() => _InkFeature(controller: customController, referenceBox: referenceBox),
returnsNormally,
);
expect(customController.added, hasLength(1));

customController.added.single.dispose();
expect(customController.removed, hasLength(1));
expect(identical(customController.added.single, customController.removed.single), isTrue);
});

testWidgets('InkFeature with delegating controller still paints', (WidgetTester tester) async {
await tester.pumpWidget(const Material(child: SizedBox(width: 40, height: 40)));

final Element element = tester.element(find.byType(SizedBox));
final MaterialInkController host = Material.of(element);
final referenceBox = element.findRenderObject()! as RenderBox;
final customController = _DelegatingMaterialInkController(host);
final tracker = TrackPaintInkFeature(controller: customController, referenceBox: referenceBox);
customController.addInkFeature(tracker);

await tester.pump();
expect(tracker.paintCount, greaterThan(0));
tracker.dispose();
});

group('LookupBoundary', () {
testWidgets('hides Material from Material.maybeOf', (WidgetTester tester) async {
MaterialInkController? material;
Expand Down Expand Up @@ -1289,3 +1323,34 @@ class _InkFeature extends InkFeature {
@override
void paintFeature(Canvas canvas, Matrix4 transform) {}
}

class _DelegatingMaterialInkController implements MaterialInkController {
_DelegatingMaterialInkController(this._host);

final MaterialInkController _host;
final List<InkFeature> added = <InkFeature>[];
final List<InkFeature> removed = <InkFeature>[];

@override
Color? get color => _host.color;

@override
TickerProvider get vsync => _host.vsync;

@override
void addInkFeature(InkFeature feature) {
added.add(feature);
_host.addInkFeature(feature);
}

@override
void removeInkFeature(InkFeature feature) {
removed.add(feature);
_host.removeInkFeature(feature);
}

@override
void markNeedsPaint() {
_host.markNeedsPaint();
}
}