-
Notifications
You must be signed in to change notification settings - Fork 26.7k
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
Incorrect text selection toolbar position in scaled Platform Views (also WebView) at least Flutter 3.13.9 on Android #144685
Comments
There are various problems with scale transformations interaction with Overlays such as text selection, I have been too lazy to file issue for each and create PR, mainly don't feel like writing tests, but you can see my fixes here |
On latest master and stable, it gives below results: 144685.movstable, master flutter doctor -v
|
@moffatman your fixes look about right, I'll go ahead and try to open a PR for those if you don't mind. Unassigning @johnmccutchan and assigning myself. |
Unassigning myself as it looks like this needs more work at sniffing out all the places where transforms are not applied properly. My PR will still move ahead but it won't fix this issue completely. The PR includes most of @moffatman's fixes as well as a few of my own that I noticed while trying out a TextField inside of a Transform.scale (which should now work). However, putting a transform above MaterialApp still doesn't work with my PR. |
Fixes bugs in the text selection positioning calculations so that they work even when the field is scaled. In many cases, we were simply translating things around without applying the proper localToGlobal (or vice versa) transform. | Before | After | | --- | --- | | <img src="https://github.com/flutter/flutter/assets/389558/a5a45472-98c5-4cdf-b382-218971fd9404" /> | <img src="https://github.com/flutter/flutter/assets/389558/f396a1af-2546-4e38-a9d9-6c6edfa38d94" /> | Partial fix for: #144685 It looks like there are other problems where transforms aren't applied properly. Putting a transform at the root of the application, above MaterialApp, will expose more problems. <details> <summary>Sample code</summary> ```dart import 'package:flutter/material.dart'; import 'package:webview_flutter/webview_flutter.dart'; void main() => runApp(const _App()); class _App extends StatelessWidget { const _App(); @OverRide Widget build(BuildContext context) { return const MaterialApp(home: _Home()); } } class _Home extends StatefulWidget { const _Home(); @OverRide State<_Home> createState() => _HomeState(); } class _HomeState extends State<_Home> { final _controller = WebViewController(); final TextEditingController textEditingController = TextEditingController( text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', ); final OverlayPortalController overlayPortalController = OverlayPortalController(); @OverRide void initState() { super.initState(); _controller ..setJavaScriptMode(JavaScriptMode.unrestricted) ..loadRequest(Uri.https('api.flutter.dev')); } @OverRide Widget build(BuildContext context) { overlayPortalController.show(); return Scaffold( appBar: AppBar( title: const Text('Scaled WebView Tester'), ), body: Stack( children: <Widget>[ Transform.scale( alignment: Alignment.topLeft, scale: 0.5, child: TextField( controller: textEditingController, maxLines: null, ), ), OverlayPortal( controller: overlayPortalController, overlayChildBuilder: (BuildContext context) { return Positioned( top: 0.0, left: 0.0, child: SizedBox( height: 1000, width: 1000, child: Stack( children: <Widget>[ Positioned( top: 90.0, left: 0.0, child: Container( height: 1.0, width: 1000, color: Colors.blue, ), ), Positioned( top: 102.0, left: 0.0, child: Container( height: 1.0, width: 1000, color: Colors.blue, ), ), ], ), ), ); }, ), ], ), ); } } ``` </details>
Fixes bugs in the text selection positioning calculations so that they work even when the field is scaled. In many cases, we were simply translating things around without applying the proper localToGlobal (or vice versa) transform. | Before | After | | --- | --- | | <img src="https://github.com/flutter/flutter/assets/389558/a5a45472-98c5-4cdf-b382-218971fd9404" /> | <img src="https://github.com/flutter/flutter/assets/389558/f396a1af-2546-4e38-a9d9-6c6edfa38d94" /> | Partial fix for: flutter#144685 It looks like there are other problems where transforms aren't applied properly. Putting a transform at the root of the application, above MaterialApp, will expose more problems. <details> <summary>Sample code</summary> ```dart import 'package:flutter/material.dart'; import 'package:webview_flutter/webview_flutter.dart'; void main() => runApp(const _App()); class _App extends StatelessWidget { const _App(); @OverRide Widget build(BuildContext context) { return const MaterialApp(home: _Home()); } } class _Home extends StatefulWidget { const _Home(); @OverRide State<_Home> createState() => _HomeState(); } class _HomeState extends State<_Home> { final _controller = WebViewController(); final TextEditingController textEditingController = TextEditingController( text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', ); final OverlayPortalController overlayPortalController = OverlayPortalController(); @OverRide void initState() { super.initState(); _controller ..setJavaScriptMode(JavaScriptMode.unrestricted) ..loadRequest(Uri.https('api.flutter.dev')); } @OverRide Widget build(BuildContext context) { overlayPortalController.show(); return Scaffold( appBar: AppBar( title: const Text('Scaled WebView Tester'), ), body: Stack( children: <Widget>[ Transform.scale( alignment: Alignment.topLeft, scale: 0.5, child: TextField( controller: textEditingController, maxLines: null, ), ), OverlayPortal( controller: overlayPortalController, overlayChildBuilder: (BuildContext context) { return Positioned( top: 0.0, left: 0.0, child: SizedBox( height: 1000, width: 1000, child: Stack( children: <Widget>[ Positioned( top: 90.0, left: 0.0, child: Container( height: 1.0, width: 1000, color: Colors.blue, ), ), Positioned( top: 102.0, left: 0.0, child: Container( height: 1.0, width: 1000, color: Colors.blue, ), ), ], ), ), ); }, ), ], ), ); } } ``` </details>
Fixes bugs in the text selection positioning calculations so that they work even when the field is scaled. In many cases, we were simply translating things around without applying the proper localToGlobal (or vice versa) transform. | Before | After | | --- | --- | | <img src="https://github.com/flutter/flutter/assets/389558/a5a45472-98c5-4cdf-b382-218971fd9404" /> | <img src="https://github.com/flutter/flutter/assets/389558/f396a1af-2546-4e38-a9d9-6c6edfa38d94" /> | Partial fix for: flutter#144685 It looks like there are other problems where transforms aren't applied properly. Putting a transform at the root of the application, above MaterialApp, will expose more problems. <details> <summary>Sample code</summary> ```dart import 'package:flutter/material.dart'; import 'package:webview_flutter/webview_flutter.dart'; void main() => runApp(const _App()); class _App extends StatelessWidget { const _App(); @OverRide Widget build(BuildContext context) { return const MaterialApp(home: _Home()); } } class _Home extends StatefulWidget { const _Home(); @OverRide State<_Home> createState() => _HomeState(); } class _HomeState extends State<_Home> { final _controller = WebViewController(); final TextEditingController textEditingController = TextEditingController( text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', ); final OverlayPortalController overlayPortalController = OverlayPortalController(); @OverRide void initState() { super.initState(); _controller ..setJavaScriptMode(JavaScriptMode.unrestricted) ..loadRequest(Uri.https('api.flutter.dev')); } @OverRide Widget build(BuildContext context) { overlayPortalController.show(); return Scaffold( appBar: AppBar( title: const Text('Scaled WebView Tester'), ), body: Stack( children: <Widget>[ Transform.scale( alignment: Alignment.topLeft, scale: 0.5, child: TextField( controller: textEditingController, maxLines: null, ), ), OverlayPortal( controller: overlayPortalController, overlayChildBuilder: (BuildContext context) { return Positioned( top: 0.0, left: 0.0, child: SizedBox( height: 1000, width: 1000, child: Stack( children: <Widget>[ Positioned( top: 90.0, left: 0.0, child: Container( height: 1.0, width: 1000, color: Colors.blue, ), ), Positioned( top: 102.0, left: 0.0, child: Container( height: 1.0, width: 1000, color: Colors.blue, ), ), ], ), ), ); }, ), ], ), ); } } ``` </details>
Steps to reproduce
Related: #144278
Expected results
Text selection toolbar and selection range handles are in the correct place.
Actual results
Text selection toolbar and selection range handles are in the wrong place (see video).
Code sample
Code sample
Screenshots or Video
Video demonstration
3.13.9_text-selection-toolbar.mp4
Logs
No response
Flutter Doctor output
Doctor output
The text was updated successfully, but these errors were encountered: