Skip to content

Commit

Permalink
Expression evaluation autocomplete overlay is positioned over the las…
Browse files Browse the repository at this point in the history
…t `.` in the expression (#3449)
  • Loading branch information
elliette authored Oct 14, 2021
1 parent 9fb6a25 commit 1ece492
Show file tree
Hide file tree
Showing 2 changed files with 34 additions and 19 deletions.
17 changes: 16 additions & 1 deletion packages/devtools_app/lib/src/debugger/evaluate.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -135,14 +136,28 @@ class _ExpressionEvalFieldState extends State<ExpressionEvalField>
shouldRequestFocus: false,
supportClearField: true,
onSelection: _onSelection,
tracking: true,
decoration: const InputDecoration(
contentPadding: EdgeInsets.all(denseSpacing),
border: OutlineInputBorder(),
focusedBorder: OutlineInputBorder(borderSide: BorderSide.none),
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,
),
);
},
),
),
),
Expand Down
36 changes: 18 additions & 18 deletions packages/devtools_app/lib/src/ui/search.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<T extends StatefulWidget> on State<T> {
TextEditingController searchTextFieldController;
FocusNode _searchFieldFocusNode;
Expand Down Expand Up @@ -601,9 +608,10 @@ mixin SearchFieldMixin<T extends StatefulWidget> on State<T> {
/// [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
Expand All @@ -617,7 +625,7 @@ mixin SearchFieldMixin<T extends StatefulWidget> on State<T> {
HighlightAutoComplete onHighlightDropdown,
InputDecoration decoration,
String label,
bool tracking = false,
OverlayXPositionBuilder overlayXPositionBuilder,
bool supportClearField = false,
Set<LogicalKeyboardKey> keyEventsToPropogate = const {},
VoidCallback onClose,
Expand All @@ -633,7 +641,7 @@ mixin SearchFieldMixin<T extends StatefulWidget> on State<T> {
searchTextFieldController: searchTextFieldController,
decoration: decoration,
label: label,
tracking: tracking,
overlayXPositionBuilder: overlayXPositionBuilder,
onClose: onClose,
);

Expand Down Expand Up @@ -718,6 +726,7 @@ class _SearchField extends StatelessWidget {
this.tracking = false,
this.decoration,
this.onClose,
this.overlayXPositionBuilder,
});

final SearchControllerMixin controller;
Expand All @@ -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;
},
Expand Down

0 comments on commit 1ece492

Please sign in to comment.