Fix that RenderEditable (TextField) ignores offset in painting, making text selections shifted when offset is nonzero (#109287)
Small fix in text layout math, unlikely to affect most normal usages.
diff --git a/packages/flutter/lib/src/rendering/editable.dart b/packages/flutter/lib/src/rendering/editable.dart
index 51e67ec..967376f 100644
--- a/packages/flutter/lib/src/rendering/editable.dart
+++ b/packages/flutter/lib/src/rendering/editable.dart
@@ -2540,14 +2540,14 @@
}
}
- void _paintHandleLayers(PaintingContext context, List<TextSelectionPoint> endpoints) {
+ void _paintHandleLayers(PaintingContext context, List<TextSelectionPoint> endpoints, Offset offset) {
Offset startPoint = endpoints[0].point;
startPoint = Offset(
clampDouble(startPoint.dx, 0.0, size.width),
clampDouble(startPoint.dy, 0.0, size.height),
);
context.pushLayer(
- LeaderLayer(link: startHandleLayerLink, offset: startPoint),
+ LeaderLayer(link: startHandleLayerLink, offset: startPoint + offset),
super.paint,
Offset.zero,
);
@@ -2558,7 +2558,7 @@
clampDouble(endPoint.dy, 0.0, size.height),
);
context.pushLayer(
- LeaderLayer(link: endHandleLayerLink, offset: endPoint),
+ LeaderLayer(link: endHandleLayerLink, offset: endPoint + offset),
super.paint,
Offset.zero,
);
@@ -2582,7 +2582,7 @@
_paintContents(context, offset);
}
if (selection!.isValid) {
- _paintHandleLayers(context, getEndpointsForSelection(selection!));
+ _paintHandleLayers(context, getEndpointsForSelection(selection!), offset);
}
}
diff --git a/packages/flutter/test/rendering/editable_test.dart b/packages/flutter/test/rendering/editable_test.dart
index 2b9b883..8008dbe 100644
--- a/packages/flutter/test/rendering/editable_test.dart
+++ b/packages/flutter/test/rendering/editable_test.dart
@@ -113,6 +113,37 @@
expect(visited, true);
});
+ test('RenderEditable.paint respects offset argument', () {
+ const BoxConstraints viewport = BoxConstraints(maxHeight: 1000.0, maxWidth: 1000.0);
+ final TestPushLayerPaintingContext context = TestPushLayerPaintingContext();
+
+ const Offset paintOffset = Offset(100, 200);
+ const double fontSize = 20.0;
+ const Offset endpoint = Offset(0.0, fontSize);
+
+ final RenderEditable editable = RenderEditable(
+ text: const TextSpan(
+ text: 'text',
+ style: TextStyle(
+ fontSize: fontSize,
+ height: 1.0,
+ ),
+ ),
+ textDirection: TextDirection.ltr,
+ startHandleLayerLink: LayerLink(),
+ endHandleLayerLink: LayerLink(),
+ offset: ViewportOffset.zero(),
+ textSelectionDelegate: _FakeEditableTextState(),
+ selection: const TextSelection(baseOffset: 0, extentOffset: 0),
+ );
+ layout(editable, constraints: viewport, phase: EnginePhase.composite);
+ editable.paint(context, paintOffset);
+
+ final List<LeaderLayer> leaderLayers = context.pushedLayers.whereType<LeaderLayer>().toList();
+ expect(leaderLayers, hasLength(1), reason: '_paintHandleLayers will paint a LeaderLayer');
+ expect(leaderLayers.single.offset, endpoint + paintOffset, reason: 'offset should respect paintOffset');
+ });
+
test('editable intrinsics', () {
final TextSelectionDelegate delegate = _FakeEditableTextState();
final RenderEditable editable = RenderEditable(
diff --git a/packages/flutter/test/rendering/rendering_tester.dart b/packages/flutter/test/rendering/rendering_tester.dart
index e14dbee..2e70054 100644
--- a/packages/flutter/test/rendering/rendering_tester.dart
+++ b/packages/flutter/test/rendering/rendering_tester.dart
@@ -365,6 +365,23 @@
Clip clipBehavior = Clip.none;
}
+class TestPushLayerPaintingContext extends PaintingContext {
+ TestPushLayerPaintingContext() : super(ContainerLayer(), Rect.zero);
+
+ final List<ContainerLayer> pushedLayers = <ContainerLayer>[];
+
+ @override
+ void pushLayer(
+ ContainerLayer childLayer,
+ PaintingContextCallback painter,
+ Offset offset, {
+ Rect? childPaintBounds
+ }) {
+ pushedLayers.add(childLayer);
+ super.pushLayer(childLayer, painter, offset, childPaintBounds: childPaintBounds);
+ }
+}
+
void expectOverflowedErrors() {
final FlutterErrorDetails errorDetails = TestRenderingFlutterBinding.instance.takeFlutterErrorDetails()!;
final bool overflowed = errorDetails.toString().contains('overflowed');