diff --git a/packages/devtools_app/lib/src/debugger/evaluate.dart b/packages/devtools_app/lib/src/debugger/evaluate.dart index b82edbe6497..27d268a9b33 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,21 @@ class _ExpressionEvalFieldState extends State enabledBorder: OutlineInputBorder(borderSide: BorderSide.none), labelText: 'Eval', ), + 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 indexOfDot = inputValue.lastIndexOf('.'); + final textSegment = indexOfDot != -1 + ? inputValue.substring(0, indexOfDot + 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 d9af0131a8d..305dcfe0d3d 100644 --- a/packages/devtools_app/lib/src/ui/search.dart +++ b/packages/devtools_app/lib/src/ui/search.dart @@ -556,6 +556,13 @@ typedef ClearSearchField = Function( bool force, }); +/// Provided by clients to specify where the autocomplete overlay should be +/// positioned relative to the input text. +typedef OverlayXPositionBuilder = double Function( + String inputValue, + TextStyle inputStyle, +); + mixin SearchFieldMixin on State { TextEditingController searchTextFieldController; FocusNode _searchFieldFocusNode; @@ -601,9 +608,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. + /// [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. /// [keyEventsToPropogate] a set of key events that should be propogated to @@ -617,7 +625,7 @@ mixin SearchFieldMixin on State { HighlightAutoComplete onHighlightDropdown, InputDecoration decoration, String label, - bool tracking = false, + OverlayXPositionBuilder overlayXPositionBuilder, bool supportClearField = false, Set keyEventsToPropogate = const {}, VoidCallback onClose, @@ -633,7 +641,7 @@ mixin SearchFieldMixin on State { searchTextFieldController: searchTextFieldController, decoration: decoration, label: label, - tracking: tracking, + overlayXPositionBuilder: overlayXPositionBuilder, onClose: onClose, ); @@ -718,6 +726,7 @@ class _SearchField extends StatelessWidget { this.tracking = false, this.decoration, this.onClose, + this.overlayXPositionBuilder, }); final SearchControllerMixin controller; @@ -731,30 +740,21 @@ class _SearchField extends StatelessWidget { final bool tracking; final InputDecoration decoration; final VoidCallback onClose; + final OverlayXPositionBuilder overlayXPositionBuilder; @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) { - // 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), - ); - painter.layout(); - - // X coordinate of the pop-up, immediately to the right of the insertion - // point (caret). - controller.xPosition = painter.width; + if (overlayXPositionBuilder != null) { + controller.xPosition = overlayXPositionBuilder(value, textStyle); } controller.search = value; },