Skip to content
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

Inspector widget selection improvements (#3489) #3525

Merged
merged 60 commits into from Jan 4, 2022
Merged
Show file tree
Hide file tree
Changes from 45 commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
5009433
Improve widget selection in the inspector (Related ticket: #3489)
msavela Nov 18, 2021
0607595
Removed details tree search (#3489)
alexlindroos Nov 19, 2021
4203f88
Highlight search matches with orange color (#3489)
alexlindroos Nov 19, 2021
c5e15bd
Changed the orange color of search highlight (#3489)
alexlindroos Nov 19, 2021
75b3046
Highlight preview only instead of the whole item (flutter#3489)
aednlaxer Nov 23, 2021
8808813
Use different text background colors depending on whether the node is…
aednlaxer Nov 23, 2021
f6bf52e
Highlight searchValue in text preview (flutter#3489)
aednlaxer Nov 24, 2021
3b72790
Reformat code (flutter#3489)
aednlaxer Nov 24, 2021
a8dd2d9
Use colors from ui/colors.dart for search matches (flutter#3489)
aednlaxer Nov 24, 2021
2ee4c21
Find widget by tooltip instead of text in inspector_screen_test (flut…
aednlaxer Nov 30, 2021
ee4f81a
Add breadcrumbs widget and show it in the Widget detail tree (#3489)
aednlaxer Dec 2, 2021
b752bca
Merge pull request #26 from CodemateLtd/widget-search-alex
msavela Dec 2, 2021
580ea85
Fix inspector_integration_test.dart (flutter#3489)
aednlaxer Dec 2, 2021
ec063b1
Update goldens (flutter#3489)
aednlaxer Dec 2, 2021
17e117d
Remove unnecessary import (flutter#3489)
aednlaxer Dec 2, 2021
3b79477
Revert goldens update (flutter#3489)
aednlaxer Dec 7, 2021
87c6fae
Put FakeServiceManager to the global storage before running tests in …
aednlaxer Dec 7, 2021
8df337a
Mock searchInProgressNotifier (flutter#3489)
aednlaxer Dec 7, 2021
cc61b2d
Update goldens (flutter#3489)
aednlaxer Dec 7, 2021
ff60690
Fix several nits (flutter#3489)
aednlaxer Dec 8, 2021
7719e8f
Use regular text style when searchValue is null, empty or there's no …
aednlaxer Dec 8, 2021
9ca2161
Add extra null checks to isChevron and isEllipsis (flutter#3489)
aednlaxer Dec 8, 2021
7207269
Use Container's alignment instead of Align (flutter#3489)
aednlaxer Dec 8, 2021
774a89d
Remove SizedBox (flutter#3489)
aednlaxer Dec 8, 2021
6903577
Add constant for the max number of breadcrumbs (flutter#3489)
aednlaxer Dec 9, 2021
ae76768
Move search controls to InspectorSummaryTreeControls (flutter#3489)
aednlaxer Dec 9, 2021
1e3f6c9
Add TODO comment regarding other values of SearchTargetType (flutter#…
aednlaxer Dec 9, 2021
162dce7
Add named constructor to PaddedDivider for setting symmetric vertical…
aednlaxer Dec 9, 2021
b14178f
Add SmallCircularProgressIndicator and reuse it (flutter#3489)
aednlaxer Dec 9, 2021
4ac247f
Use constants (flutter#3489)
aednlaxer Dec 9, 2021
c65d079
Prefix debug variables (flutter#3489)
aednlaxer Dec 9, 2021
247d509
Cache selected inspector tree row (flutter#3489)
aednlaxer Dec 9, 2021
9d43a70
Simplify the way search works in inspector_tree_controller.dart (flut…
aednlaxer Dec 10, 2021
0951e4f
Rename searchPreventCloseOnBlur to searchPreventClose (flutter#3489)
aednlaxer Dec 10, 2021
fc71a46
Remove irrelevant comments (flutter#3489)
aednlaxer Dec 10, 2021
b1063f7
Change max number of breadcrumbs and the way displayed items are calc…
aednlaxer Dec 13, 2021
243cfa8
Change isChevron and isEllipsis checks (flutter#3489)
aednlaxer Dec 13, 2021
6987e58
Doc: wrap variables in brackets (flutter#3489)
aednlaxer Dec 13, 2021
4d08128
Simplify horizontal padding (flutter#3489)
aednlaxer Dec 13, 2021
0c6e704
Add tests for caseInsensitiveEquals and caseInsensitiveAllMatches (fl…
aednlaxer Dec 13, 2021
fbdbb5a
Merge branch 'master' into widget-selection-search
aednlaxer Dec 13, 2021
ea299df
Pull out search debounce to SearchControllerMixin (flutter#3489)
aednlaxer Dec 13, 2021
1c0b711
Use refreshSearchMatches instead of matchesForSearch (flutter#3489)
aednlaxer Dec 13, 2021
d72570a
Search previous matches in widget search when asked to (flutter#3489)
aednlaxer Dec 13, 2021
337bab1
Perform a global search when search inside previous matches fails (fl…
aednlaxer Dec 13, 2021
2c42d9f
Fix nits (flutter#3489)
aednlaxer Dec 15, 2021
5374d37
Remove setSearchMatch function (flutter#3489)
aednlaxer Dec 15, 2021
6ab7258
Expand every node of the tree before search (flutter#3489)
aednlaxer Dec 15, 2021
a9ac6b6
Add a list of all cached tree rows for search (flutter#3489)
aednlaxer Dec 16, 2021
6347a52
Use tree nodes instead of rows in InspectorBreadcrumbNavigator. Simpl…
aednlaxer Dec 17, 2021
eafc8bf
Fix nits (flutter#3489)
aednlaxer Dec 17, 2021
20b2d60
Merge branch 'master' into widget-selection-search
aednlaxer Dec 17, 2021
74a32a3
Optimize usage of variables (flutter#3489)
aednlaxer Dec 20, 2021
7af5098
Update goldens (flutter#3489)
aednlaxer Dec 21, 2021
72c9b5e
Merge branch 'master' into widget-selection-search
aednlaxer Dec 21, 2021
1937d24
Update matchIndex when activeMatchIndex is greater than number of ite…
aednlaxer Dec 21, 2021
90922d0
Set searchInProgress to false before calling _updateMatches (flutter#…
aednlaxer Dec 21, 2021
1506ea9
Update memory_* goldens (flutter#3489)
aednlaxer Dec 23, 2021
001cdd4
Update memory_* goldens (flutter#3489)
aednlaxer Dec 23, 2021
035082f
Update memory_* goldens (flutter#3489)
aednlaxer Dec 23, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
46 changes: 40 additions & 6 deletions packages/devtools_app/lib/src/common_widgets.dart
Expand Up @@ -41,6 +41,9 @@ class PaddedDivider extends StatelessWidget {
const PaddedDivider.thin({Key key})
: padding = const EdgeInsets.only(bottom: 4.0);

PaddedDivider.vertical({Key key, double padding = densePadding})
: padding = EdgeInsets.symmetric(vertical: padding);

/// The padding to place around the divider.
final EdgeInsets padding;

Expand Down Expand Up @@ -118,6 +121,7 @@ class IconLabelButton extends StatelessWidget {
this.elevatedButton = false,
this.tooltip,
this.tooltipPadding,
this.outlined = true,
}) : assert((icon == null) != (imageIcon == null)),
super(key: key);

Expand All @@ -140,6 +144,8 @@ class IconLabelButton extends StatelessWidget {

final EdgeInsetsGeometry tooltipPadding;

final bool outlined;

@override
Widget build(BuildContext context) {
final iconLabel = MaterialIconLabel(
Expand Down Expand Up @@ -169,12 +175,23 @@ class IconLabelButton extends StatelessWidget {
width: !includeText(context, minScreenWidthForTextBeforeScaling)
? buttonMinWidth
: null,
child: OutlinedButton(
style: denseAwareOutlinedButtonStyle(
context, minScreenWidthForTextBeforeScaling),
onPressed: onPressed,
child: iconLabel,
),
child: outlined
? OutlinedButton(
style: denseAwareOutlinedButtonStyle(
context,
minScreenWidthForTextBeforeScaling,
),
onPressed: onPressed,
child: iconLabel,
)
: TextButton(
onPressed: onPressed,
aednlaxer marked this conversation as resolved.
Show resolved Hide resolved
child: iconLabel,
style: denseAwareOutlinedButtonStyle(
context,
minScreenWidthForTextBeforeScaling,
),
),
),
);
}
Expand Down Expand Up @@ -1816,3 +1833,20 @@ class _BlinkingIconState extends State<BlinkingIcon> {
);
}
}

class SmallCircularProgressIndicator extends StatelessWidget {
const SmallCircularProgressIndicator({
Key key,
@required this.valueColor,
}) : super(key: key);

final Animation<Color> valueColor;

@override
Widget build(BuildContext context) {
return CircularProgressIndicator(
strokeWidth: 2,
valueColor: valueColor,
);
}
}
74 changes: 72 additions & 2 deletions packages/devtools_app/lib/src/inspector/diagnostics.dart
Expand Up @@ -35,18 +35,22 @@ class DiagnosticsNodeDescription extends StatelessWidget {
const DiagnosticsNodeDescription(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc @matt-ragonese in case you are using this file for your custom screen

this.diagnostic, {
this.isSelected,
this.searchValue,
this.errorText,
this.multiline = false,
this.style,
@required this.debuggerController,
this.nodeDescriptionHighlightStyle,
});

final RemoteDiagnosticsNode diagnostic;
final bool isSelected;
final String errorText;
final String searchValue;
final bool multiline;
final TextStyle style;
final DebuggerController debuggerController;
final TextStyle nodeDescriptionHighlightStyle;

Widget _paddedIcon(Widget icon) {
return Padding(
Expand Down Expand Up @@ -90,8 +94,18 @@ class DiagnosticsNodeDescription extends StatelessWidget {
if (textPreview is String) {
final preview = textPreview.replaceAll('\n', ' ');
yield TextSpan(
text: ': "$preview"',
style: textStyle.merge(inspector_text_styles.unimportant(colorScheme)),
children: [
TextSpan(
text: ': ',
style: textStyle,
),
_buildHighlightedSearchPreview(
preview,
searchValue,
textStyle,
textStyle.merge(nodeDescriptionHighlightStyle),
),
],
);
}
}
Expand Down Expand Up @@ -350,4 +364,60 @@ class DiagnosticsNodeDescription extends StatelessWidget {
),
);
}

TextSpan _buildHighlightedSearchPreview(
String textPreview,
String searchValue,
TextStyle textStyle,
TextStyle highlightTextStyle,
) {
if (searchValue == null || searchValue.isEmpty) {
return TextSpan(
text: '"$textPreview"',
style: textStyle,
);
}

if (textPreview.caseInsensitiveEquals(searchValue)) {
return TextSpan(
text: '"$textPreview"',
style: highlightTextStyle,
aednlaxer marked this conversation as resolved.
Show resolved Hide resolved
);
}

final matches = searchValue.caseInsensitiveAllMatches(textPreview);
if (matches.isEmpty) {
return TextSpan(
text: '"$textPreview"',
style: textStyle,
);
}

final quoteSpan = TextSpan(text: '"', style: textStyle);
final spans = <TextSpan>[quoteSpan];
var previousItemEnd = 0;
for (final match in matches) {
if (match.start > previousItemEnd) {
spans.add(TextSpan(
text: textPreview.substring(previousItemEnd, match.start),
style: textStyle,
));
}

spans.add(TextSpan(
text: textPreview.substring(match.start, match.end),
style: highlightTextStyle,
));

previousItemEnd = match.end;
}

spans.add(TextSpan(
text: textPreview.substring(previousItemEnd, textPreview.length),
style: textStyle,
));
spans.add(quoteSpan);

return TextSpan(children: spans);
}
}
207 changes: 207 additions & 0 deletions packages/devtools_app/lib/src/inspector/inspector_breadcrumbs.dart
@@ -0,0 +1,207 @@
import 'package:flutter/material.dart';

import '../theme.dart';
import '../utils.dart';
import 'inspector_text_styles.dart';
import 'inspector_tree.dart';

class InspectorBreadcrumbNavigator extends StatelessWidget {
const InspectorBreadcrumbNavigator({
Key key,
@required this.rows,
@required this.onTap,
}) : super(key: key);

/// Max number of visible breadcrumbs including root item but not 'more' item.
/// E.g. value 5 means root and 4 breadcrumbs can be displayed, other
/// breadcrumbs (if any) will be replaced by '...' item.
static const _maxNumberOfBreadcrumbs = 5;

final List<InspectorTreeRow> rows;
final Function(InspectorTreeRow) onTap;

@override
Widget build(BuildContext context) {
if (rows.isEmpty) {
return const SizedBox();
}

final breadcrumbs = _generateBreadcrumbs(rows);
return SizedBox(
height: isDense() ? 24 : 28,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 6),
child: Row(
children: breadcrumbs.map((item) {
if (item.isChevron) {
return Icon(
Icons.chevron_right,
size: defaultIconSize,
);
}

return Flexible(
child: _InspectorBreadcrumb(
data: item,
onTap: () => onTap(item.row),
),
);
}).toList(),
),
),
);
}

List<_InspectorBreadcrumbData> _generateBreadcrumbs(
List<InspectorTreeRow> rows,
) {
final List<_InspectorBreadcrumbData> items = rows.map((row) {
return _InspectorBreadcrumbData.wrap(
row: row,
isSelected: row == rows.safeLast,
);
}).toList();
List<_InspectorBreadcrumbData> breadcrumbs;
if (items.length > _maxNumberOfBreadcrumbs) {
breadcrumbs = [
items[0],
_InspectorBreadcrumbData.more(),
...items.sublist(
items.length - _maxNumberOfBreadcrumbs + 1, items.length),
aednlaxer marked this conversation as resolved.
Show resolved Hide resolved
];
} else {
breadcrumbs = items;
}

return breadcrumbs.joinWith(_InspectorBreadcrumbData.chevron());
}
}

class _InspectorBreadcrumb extends StatelessWidget {
const _InspectorBreadcrumb({
Key key,
@required this.data,
@required this.onTap,
}) : assert(data != null),
super(key: key);

static const BorderRadius _borderRadius =
BorderRadius.all(Radius.circular(defaultBorderRadius));

static const _iconScale = .75;

final _InspectorBreadcrumbData data;
final VoidCallback onTap;

@override
Widget build(BuildContext context) {
final text = Text(
data.text,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: regular.copyWith(fontSize: scaleByFontFactor(11)),
);

final icon = data.icon == null
? null
: Transform.scale(
scale: _iconScale,
child: Padding(
padding: const EdgeInsets.only(right: iconPadding),
child: data.icon,
),
);

return InkWell(
onTap: data.isClickable ? onTap : null,
borderRadius: _borderRadius,
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: densePadding,
vertical: borderPadding,
),
decoration: BoxDecoration(
borderRadius: _borderRadius,
color: data.isSelected
? Theme.of(context).colorScheme.selectedRowBackgroundColor
: Colors.transparent,
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (icon != null) icon,
Flexible(child: text),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use Expanded instead

],
),
),
);
}
}

class _InspectorBreadcrumbData {
const _InspectorBreadcrumbData._({
@required this.row,
@required this.isSelected,
@required this.alternativeText,
@required this.alternativeIcon,
});

factory _InspectorBreadcrumbData.wrap({
@required InspectorTreeRow row,
@required bool isSelected,
}) {
return _InspectorBreadcrumbData._(
row: row,
isSelected: isSelected,
alternativeText: null,
alternativeIcon: null,
);
}

/// Construct a special item for showing '…' symbol between other items
factory _InspectorBreadcrumbData.more() {
return const _InspectorBreadcrumbData._(
row: null,
isSelected: false,
alternativeText: ellipsisValue,
alternativeIcon: null,
);
}

factory _InspectorBreadcrumbData.chevron() {
return const _InspectorBreadcrumbData._(
row: null,
isSelected: false,
alternativeText: null,
alternativeIcon: breadcrumbSeparatorIcon,
);
}

static const String ellipsisValue = '…';
static const IconData breadcrumbSeparatorIcon = Icons.chevron_right;
aednlaxer marked this conversation as resolved.
Show resolved Hide resolved

final InspectorTreeRow row;
final IconData alternativeIcon;
final String alternativeText;
final bool isSelected;

String get text => alternativeText ?? row?.node?.diagnostic?.description;

Widget get icon {
if (alternativeIcon != null) {
return Icon(
Icons.chevron_right,
aednlaxer marked this conversation as resolved.
Show resolved Hide resolved
size: defaultIconSize,
);
}

return row?.node?.diagnostic?.icon;
}

bool get isChevron =>
aednlaxer marked this conversation as resolved.
Show resolved Hide resolved
row == null && alternativeIcon == breadcrumbSeparatorIcon;

bool get isEllipsis => row == null && alternativeText == ellipsisValue;

bool get isClickable => !isSelected && !isEllipsis;
}