Unverified Commit 6ab2445e authored by Michael Goderbauer's avatar Michael Goderbauer Committed by GitHub

Fix Textfields in Semantics Debugger (#37158)

parent d82bca2a
......@@ -194,143 +194,6 @@ class _SemanticsClient extends ChangeNotifier {
}
}
String _getMessage(SemanticsNode node) {
final SemanticsData data = node.getSemanticsData();
final List<String> annotations = <String>[];
bool wantsTap = false;
if (data.hasFlag(SemanticsFlag.hasCheckedState)) {
annotations.add(data.hasFlag(SemanticsFlag.isChecked) ? 'checked' : 'unchecked');
wantsTap = true;
}
if (data.hasAction(SemanticsAction.tap)) {
if (!wantsTap)
annotations.add('button');
} else {
if (wantsTap)
annotations.add('disabled');
}
if (data.hasAction(SemanticsAction.longPress))
annotations.add('long-pressable');
final bool isScrollable = data.hasAction(SemanticsAction.scrollLeft)
|| data.hasAction(SemanticsAction.scrollRight)
|| data.hasAction(SemanticsAction.scrollUp)
|| data.hasAction(SemanticsAction.scrollDown);
final bool isAdjustable = data.hasAction(SemanticsAction.increase)
|| data.hasAction(SemanticsAction.decrease);
if (isScrollable)
annotations.add('scrollable');
if (isAdjustable)
annotations.add('adjustable');
assert(data.label != null);
String message;
if (data.label.isEmpty) {
message = annotations.join('; ');
} else {
String label;
if (data.textDirection == null) {
label = '${Unicode.FSI}${data.label}${Unicode.PDI}';
annotations.insert(0, 'MISSING TEXT DIRECTION');
} else {
switch (data.textDirection) {
case TextDirection.rtl:
label = '${Unicode.RLI}${data.label}${Unicode.PDF}';
break;
case TextDirection.ltr:
label = data.label;
break;
}
}
if (annotations.isEmpty) {
message = label;
} else {
message = '$label (${annotations.join('; ')})';
}
}
return message.trim();
}
const TextStyle _messageStyle = TextStyle(
color: Color(0xFF000000),
fontSize: 10.0,
height: 0.8,
);
void _paintMessage(Canvas canvas, SemanticsNode node) {
final String message = _getMessage(node);
if (message.isEmpty)
return;
final Rect rect = node.rect;
canvas.save();
canvas.clipRect(rect);
final TextPainter textPainter = TextPainter()
..text = TextSpan(
style: _messageStyle,
text: message,
)
..textDirection = TextDirection.ltr // _getMessage always returns LTR text, even if node.label is RTL
..textAlign = TextAlign.center
..layout(maxWidth: rect.width);
textPainter.paint(canvas, Alignment.center.inscribe(textPainter.size, rect).topLeft);
canvas.restore();
}
int _findDepth(SemanticsNode node) {
if (!node.hasChildren || node.mergeAllDescendantsIntoThisNode)
return 1;
int childrenDepth = 0;
node.visitChildren((SemanticsNode child) {
childrenDepth = math.max(childrenDepth, _findDepth(child));
return true;
});
return childrenDepth + 1;
}
void _paint(Canvas canvas, SemanticsNode node, int rank) {
canvas.save();
if (node.transform != null)
canvas.transform(node.transform.storage);
final Rect rect = node.rect;
if (!rect.isEmpty) {
final Color lineColor = Color(0xFF000000 + math.Random(node.id).nextInt(0xFFFFFF));
final Rect innerRect = rect.deflate(rank * 1.0);
if (innerRect.isEmpty) {
final Paint fill = Paint()
..color = lineColor
..style = PaintingStyle.fill;
canvas.drawRect(rect, fill);
} else {
final Paint fill = Paint()
..color = const Color(0xFFFFFFFF)
..style = PaintingStyle.fill;
canvas.drawRect(rect, fill);
final Paint line = Paint()
..strokeWidth = rank * 2.0
..color = lineColor
..style = PaintingStyle.stroke;
canvas.drawRect(innerRect, line);
}
_paintMessage(canvas, node);
}
if (!node.mergeAllDescendantsIntoThisNode) {
final int childRank = rank - 1;
node.visitChildren((SemanticsNode child) {
_paint(canvas, child, childRank);
return true;
});
}
canvas.restore();
}
class _SemanticsDebuggerPainter extends CustomPainter {
const _SemanticsDebuggerPainter(this.owner, this.generation, this.pointerPosition, this.devicePixelRatio);
......@@ -364,4 +227,144 @@ class _SemanticsDebuggerPainter extends CustomPainter {
|| generation != oldDelegate.generation
|| pointerPosition != oldDelegate.pointerPosition;
}
@visibleForTesting
String getMessage(SemanticsNode node) {
final SemanticsData data = node.getSemanticsData();
final List<String> annotations = <String>[];
bool wantsTap = false;
if (data.hasFlag(SemanticsFlag.hasCheckedState)) {
annotations.add(data.hasFlag(SemanticsFlag.isChecked) ? 'checked' : 'unchecked');
wantsTap = true;
}
if (data.hasFlag(SemanticsFlag.isTextField)) {
annotations.add('textfield');
wantsTap = true;
}
if (data.hasAction(SemanticsAction.tap)) {
if (!wantsTap)
annotations.add('button');
} else {
if (wantsTap)
annotations.add('disabled');
}
if (data.hasAction(SemanticsAction.longPress))
annotations.add('long-pressable');
final bool isScrollable = data.hasAction(SemanticsAction.scrollLeft)
|| data.hasAction(SemanticsAction.scrollRight)
|| data.hasAction(SemanticsAction.scrollUp)
|| data.hasAction(SemanticsAction.scrollDown);
final bool isAdjustable = data.hasAction(SemanticsAction.increase)
|| data.hasAction(SemanticsAction.decrease);
if (isScrollable)
annotations.add('scrollable');
if (isAdjustable)
annotations.add('adjustable');
assert(data.label != null);
String message;
if (data.label.isEmpty) {
message = annotations.join('; ');
} else {
String label;
if (data.textDirection == null) {
label = '${Unicode.FSI}${data.label}${Unicode.PDI}';
annotations.insert(0, 'MISSING TEXT DIRECTION');
} else {
switch (data.textDirection) {
case TextDirection.rtl:
label = '${Unicode.RLI}${data.label}${Unicode.PDF}';
break;
case TextDirection.ltr:
label = data.label;
break;
}
}
if (annotations.isEmpty) {
message = label;
} else {
message = '$label (${annotations.join('; ')})';
}
}
return message.trim();
}
void _paintMessage(Canvas canvas, SemanticsNode node) {
final String message = getMessage(node);
if (message.isEmpty)
return;
final Rect rect = node.rect;
canvas.save();
canvas.clipRect(rect);
final TextPainter textPainter = TextPainter()
..text = TextSpan(
style: const TextStyle(
color: Color(0xFF000000),
fontSize: 10.0,
height: 0.8,
),
text: message,
)
..textDirection = TextDirection.ltr // _getMessage always returns LTR text, even if node.label is RTL
..textAlign = TextAlign.center
..layout(maxWidth: rect.width);
textPainter.paint(canvas, Alignment.center.inscribe(textPainter.size, rect).topLeft);
canvas.restore();
}
int _findDepth(SemanticsNode node) {
if (!node.hasChildren || node.mergeAllDescendantsIntoThisNode)
return 1;
int childrenDepth = 0;
node.visitChildren((SemanticsNode child) {
childrenDepth = math.max(childrenDepth, _findDepth(child));
return true;
});
return childrenDepth + 1;
}
void _paint(Canvas canvas, SemanticsNode node, int rank) {
canvas.save();
if (node.transform != null)
canvas.transform(node.transform.storage);
final Rect rect = node.rect;
if (!rect.isEmpty) {
final Color lineColor = Color(0xFF000000 + math.Random(node.id).nextInt(0xFFFFFF));
final Rect innerRect = rect.deflate(rank * 1.0);
if (innerRect.isEmpty) {
final Paint fill = Paint()
..color = lineColor
..style = PaintingStyle.fill;
canvas.drawRect(rect, fill);
} else {
final Paint fill = Paint()
..color = const Color(0xFFFFFFFF)
..style = PaintingStyle.fill;
canvas.drawRect(rect, fill);
final Paint line = Paint()
..strokeWidth = rank * 2.0
..color = lineColor
..style = PaintingStyle.stroke;
canvas.drawRect(innerRect, line);
}
_paintMessage(canvas, node);
}
if (!node.mergeAllDescendantsIntoThisNode) {
final int childRank = rank - 1;
node.visitChildren((SemanticsNode child) {
_paint(canvas, child, childRank);
return true;
});
}
canvas.restore();
}
}
......@@ -362,4 +362,113 @@ void main() {
expect(valueTop, isFalse);
expect(valueTop, isFalse);
});
testWidgets('SemanticsDebugger checkbox message', (WidgetTester tester) async {
final Key checkbox = UniqueKey();
final Key checkboxUnchecked = UniqueKey();
final Key checkboxDisabled = UniqueKey();
final Key checkboxDisabledUnchecked = UniqueKey();
final Key debugger = UniqueKey();
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: SemanticsDebugger(
key: debugger,
child: Material(
child: ListView(
children: <Widget>[
Semantics(
container: true,
key: checkbox,
child: Checkbox(
value: true,
onChanged: (bool _) { },
),
),
Semantics(
container: true,
key: checkboxUnchecked,
child: Checkbox(
value: false,
onChanged: (bool _) { },
),
),
Semantics(
container: true,
key: checkboxDisabled,
child: const Checkbox(
value: true,
onChanged: null,
),
),
Semantics(
container: true,
key: checkboxDisabledUnchecked,
child: const Checkbox(
value: false,
onChanged: null,
),
),
],
),
),
),
),
);
expect(
_getMessageShownInSemanticsDebugger(widgetKey: checkbox, debuggerKey: debugger, tester: tester),
'checked',
);
expect(
_getMessageShownInSemanticsDebugger(widgetKey: checkboxUnchecked, debuggerKey: debugger, tester: tester),
'unchecked',
);
expect(
_getMessageShownInSemanticsDebugger(widgetKey: checkboxDisabled, debuggerKey: debugger, tester: tester),
'checked; disabled',
);
expect(
_getMessageShownInSemanticsDebugger(widgetKey: checkboxDisabledUnchecked, debuggerKey: debugger, tester: tester),
'unchecked; disabled',
);
});
testWidgets('SemanticsDebugger textfield', (WidgetTester tester) async {
final UniqueKey textField = UniqueKey();
final UniqueKey debugger = UniqueKey();
await tester.pumpWidget(
MaterialApp(
home: SemanticsDebugger(
key: debugger,
child: Material(
child: TextField(
key: textField,
),
),
),
),
);
expect(
_getMessageShownInSemanticsDebugger(widgetKey: textField, debuggerKey: debugger, tester: tester),
'textfield',
);
});
}
String _getMessageShownInSemanticsDebugger({
@required Key widgetKey,
@required Key debuggerKey,
@required WidgetTester tester,
}) {
final CustomPaint customPaint = tester.widgetList(find.descendant(
of: find.byKey(debuggerKey),
matching: find.byType(CustomPaint),
)).first;
final dynamic semanticsDebuggerPainter = customPaint.foregroundPainter;
expect(semanticsDebuggerPainter.runtimeType.toString(), '_SemanticsDebuggerPainter');
return semanticsDebuggerPainter.getMessage(tester.renderObject(find.byKey(widgetKey)).debugSemantics);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment