From f79d5d14fce9301dc2641a150fd33637b2c7f581 Mon Sep 17 00:00:00 2001 From: Elliott Brooks <21270878+elliette@users.noreply.github.com> Date: Tue, 12 Oct 2021 14:21:57 -0700 Subject: [PATCH 1/3] Autocomplete should align with . --- packages/devtools_app/lib/src/ui/search.dart | 27 +++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/packages/devtools_app/lib/src/ui/search.dart b/packages/devtools_app/lib/src/ui/search.dart index d9af0131a8d..9fcfdb6b151 100644 --- a/packages/devtools_app/lib/src/ui/search.dart +++ b/packages/devtools_app/lib/src/ui/search.dart @@ -742,19 +742,22 @@ class _SearchField extends StatelessWidget { controller: searchTextFieldController, onChanged: (value) { if (tracking) { - // Use a TextPainter to calculate the width of the newly entered text. - // TODO(terry): The TextPainter's TextStyle is default (same as this - // TextField) consider explicitly using a TextStyle of - // this TextField if the TextField needs styling. - final painter = TextPainter( - textDirection: TextDirection.ltr, - text: TextSpan(text: value), + // Calculate the width of the newly entered text + // up to the last "." or the insertion point (cursor). + final textSegment = value.contains('.') + ? value.substring(0, value.lastIndexOf('.') + 1) + : value; + final width = calculateTextSpanWidth( + TextSpan( + text: textSegment, + // Note: This is the default theme used by Flutter's TextField + // widget. If we change the style, we should also update here. + style: Theme.of(context).textTheme.subtitle1, + ), ); - painter.layout(); - - // X coordinate of the pop-up, immediately to the right of the insertion - // point (caret). - controller.xPosition = painter.width; + // X coordinate of the pop-up, immediately to the right of the last + // "." or the insertion point (cursor). + controller.xPosition = width; } controller.search = value; }, From bfd09f1c8b3a1156b73db3577aee72fb0a6db029 Mon Sep 17 00:00:00 2001 From: Elliott Brooks <21270878+elliette@users.noreply.github.com> Date: Wed, 13 Oct 2021 11:39:23 -0700 Subject: [PATCH 2/3] Client should provide the callback --- .../lib/src/debugger/evaluate.dart | 16 +++++++- packages/devtools_app/lib/src/ui/search.dart | 37 ++++++++----------- 2 files changed, 31 insertions(+), 22 deletions(-) diff --git a/packages/devtools_app/lib/src/debugger/evaluate.dart b/packages/devtools_app/lib/src/debugger/evaluate.dart index b82edbe6497..3807bfe13f3 100644 --- a/packages/devtools_app/lib/src/debugger/evaluate.dart +++ b/packages/devtools_app/lib/src/debugger/evaluate.dart @@ -13,6 +13,7 @@ import '../globals.dart'; import '../notifications.dart'; import '../theme.dart'; import '../ui/search.dart'; +import '../ui/utils.dart'; import '../utils.dart'; import 'debugger_controller.dart'; @@ -135,7 +136,6 @@ class _ExpressionEvalFieldState extends State shouldRequestFocus: false, supportClearField: true, onSelection: _onSelection, - tracking: true, decoration: const InputDecoration( contentPadding: EdgeInsets.all(denseSpacing), border: OutlineInputBorder(), @@ -143,6 +143,20 @@ class _ExpressionEvalFieldState extends State enabledBorder: OutlineInputBorder(borderSide: BorderSide.none), labelText: 'Eval', ), + determineOverlayXPosition: + (String inputValue, TextStyle inputStyle) { + // X-coordinate is equivalent to the width of the input text + // up to the last "." or the insertion point (cursor): + final textSegment = inputValue.contains('.') + ? inputValue.substring(0, inputValue.lastIndexOf('.') + 1) + : inputValue; + return calculateTextSpanWidth( + TextSpan( + text: textSegment, + style: inputStyle, + ), + ); + }, ), ), ), diff --git a/packages/devtools_app/lib/src/ui/search.dart b/packages/devtools_app/lib/src/ui/search.dart index 9fcfdb6b151..f02c85e8e68 100644 --- a/packages/devtools_app/lib/src/ui/search.dart +++ b/packages/devtools_app/lib/src/ui/search.dart @@ -556,6 +556,11 @@ typedef ClearSearchField = Function( bool force, }); +/// Provided by clients to specify where the autocomplete overlay should be +/// positioned relative to the input text. +typedef DetermineOverlayXPosition = double Function( + String inputValue, TextStyle inputStyle); + mixin SearchFieldMixin on State { TextEditingController searchTextFieldController; FocusNode _searchFieldFocusNode; @@ -601,9 +606,10 @@ mixin SearchFieldMixin on State { /// [searchFieldKey] /// [searchFieldEnabled] /// [onSelection] - /// [onHilightDropdown] use to override default highlghter. + /// [onHighlightDropdown] use to override default highlghter. /// [decoration] - /// [tracking] if true displays pop-up to the right of the TextField's caret. + /// [determineOverlayXPosition] callback function to determine where the + /// autocomplete overlay should be positioned relative to the input text. /// [supportClearField] if true clear TextField content if pop-up not visible. If /// pop-up is visible close the pop-up on first ESCAPE. /// [keyEventsToPropogate] a set of key events that should be propogated to @@ -617,7 +623,7 @@ mixin SearchFieldMixin on State { HighlightAutoComplete onHighlightDropdown, InputDecoration decoration, String label, - bool tracking = false, + DetermineOverlayXPosition determineOverlayXPosition, bool supportClearField = false, Set keyEventsToPropogate = const {}, VoidCallback onClose, @@ -633,7 +639,7 @@ mixin SearchFieldMixin on State { searchTextFieldController: searchTextFieldController, decoration: decoration, label: label, - tracking: tracking, + determineOverlayXPosition: determineOverlayXPosition, onClose: onClose, ); @@ -718,6 +724,7 @@ class _SearchField extends StatelessWidget { this.tracking = false, this.decoration, this.onClose, + this.determineOverlayXPosition, }); final SearchControllerMixin controller; @@ -731,33 +738,21 @@ class _SearchField extends StatelessWidget { final bool tracking; final InputDecoration decoration; final VoidCallback onClose; + final DetermineOverlayXPosition determineOverlayXPosition; @override Widget build(BuildContext context) { + final textStyle = Theme.of(context).textTheme.subtitle1; final searchField = TextField( key: searchFieldKey, autofocus: true, enabled: searchFieldEnabled, focusNode: searchFieldFocusNode, controller: searchTextFieldController, + style: textStyle, onChanged: (value) { - if (tracking) { - // Calculate the width of the newly entered text - // up to the last "." or the insertion point (cursor). - final textSegment = value.contains('.') - ? value.substring(0, value.lastIndexOf('.') + 1) - : value; - final width = calculateTextSpanWidth( - TextSpan( - text: textSegment, - // Note: This is the default theme used by Flutter's TextField - // widget. If we change the style, we should also update here. - style: Theme.of(context).textTheme.subtitle1, - ), - ); - // X coordinate of the pop-up, immediately to the right of the last - // "." or the insertion point (cursor). - controller.xPosition = width; + if (determineOverlayXPosition != null) { + controller.xPosition = determineOverlayXPosition(value, textStyle); } controller.search = value; }, From 6a03a87fecc9c0771c01e644aa59491c4eeff1de Mon Sep 17 00:00:00 2001 From: Elliott Brooks <21270878+elliette@users.noreply.github.com> Date: Wed, 13 Oct 2021 13:45:23 -0700 Subject: [PATCH 3/3] Responding to PR comments --- .../lib/src/debugger/evaluate.dart | 7 ++++--- packages/devtools_app/lib/src/ui/search.dart | 20 ++++++++++--------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/packages/devtools_app/lib/src/debugger/evaluate.dart b/packages/devtools_app/lib/src/debugger/evaluate.dart index 3807bfe13f3..27d268a9b33 100644 --- a/packages/devtools_app/lib/src/debugger/evaluate.dart +++ b/packages/devtools_app/lib/src/debugger/evaluate.dart @@ -143,12 +143,13 @@ class _ExpressionEvalFieldState extends State enabledBorder: OutlineInputBorder(borderSide: BorderSide.none), labelText: 'Eval', ), - determineOverlayXPosition: + overlayXPositionBuilder: (String inputValue, TextStyle inputStyle) { // X-coordinate is equivalent to the width of the input text // up to the last "." or the insertion point (cursor): - final textSegment = inputValue.contains('.') - ? inputValue.substring(0, inputValue.lastIndexOf('.') + 1) + final indexOfDot = inputValue.lastIndexOf('.'); + final textSegment = indexOfDot != -1 + ? inputValue.substring(0, indexOfDot + 1) : inputValue; return calculateTextSpanWidth( TextSpan( diff --git a/packages/devtools_app/lib/src/ui/search.dart b/packages/devtools_app/lib/src/ui/search.dart index f02c85e8e68..305dcfe0d3d 100644 --- a/packages/devtools_app/lib/src/ui/search.dart +++ b/packages/devtools_app/lib/src/ui/search.dart @@ -558,8 +558,10 @@ typedef ClearSearchField = Function( /// Provided by clients to specify where the autocomplete overlay should be /// positioned relative to the input text. -typedef DetermineOverlayXPosition = double Function( - String inputValue, TextStyle inputStyle); +typedef OverlayXPositionBuilder = double Function( + String inputValue, + TextStyle inputStyle, +); mixin SearchFieldMixin on State { TextEditingController searchTextFieldController; @@ -608,7 +610,7 @@ mixin SearchFieldMixin on State { /// [onSelection] /// [onHighlightDropdown] use to override default highlghter. /// [decoration] - /// [determineOverlayXPosition] callback function to determine where the + /// [overlayXPositionBuilder] callback function to determine where the /// autocomplete overlay should be positioned relative to the input text. /// [supportClearField] if true clear TextField content if pop-up not visible. If /// pop-up is visible close the pop-up on first ESCAPE. @@ -623,7 +625,7 @@ mixin SearchFieldMixin on State { HighlightAutoComplete onHighlightDropdown, InputDecoration decoration, String label, - DetermineOverlayXPosition determineOverlayXPosition, + OverlayXPositionBuilder overlayXPositionBuilder, bool supportClearField = false, Set keyEventsToPropogate = const {}, VoidCallback onClose, @@ -639,7 +641,7 @@ mixin SearchFieldMixin on State { searchTextFieldController: searchTextFieldController, decoration: decoration, label: label, - determineOverlayXPosition: determineOverlayXPosition, + overlayXPositionBuilder: overlayXPositionBuilder, onClose: onClose, ); @@ -724,7 +726,7 @@ class _SearchField extends StatelessWidget { this.tracking = false, this.decoration, this.onClose, - this.determineOverlayXPosition, + this.overlayXPositionBuilder, }); final SearchControllerMixin controller; @@ -738,7 +740,7 @@ class _SearchField extends StatelessWidget { final bool tracking; final InputDecoration decoration; final VoidCallback onClose; - final DetermineOverlayXPosition determineOverlayXPosition; + final OverlayXPositionBuilder overlayXPositionBuilder; @override Widget build(BuildContext context) { @@ -751,8 +753,8 @@ class _SearchField extends StatelessWidget { controller: searchTextFieldController, style: textStyle, onChanged: (value) { - if (determineOverlayXPosition != null) { - controller.xPosition = determineOverlayXPosition(value, textStyle); + if (overlayXPositionBuilder != null) { + controller.xPosition = overlayXPositionBuilder(value, textStyle); } controller.search = value; },