diff --git a/bin/internal/engine.version b/bin/internal/engine.version index a02f7d06f3c..dd902d4cae9 100644 --- a/bin/internal/engine.version +++ b/bin/internal/engine.version @@ -1 +1 @@ -05fe72d068e19c7886e8d27f9b004201d5ad1300 +e7e94c6307bcefdbd4835a6f1a594a70df5dfe9a diff --git a/packages/flutter/lib/src/material/radio.dart b/packages/flutter/lib/src/material/radio.dart index 25cbd498b2b..29539a52b9d 100644 --- a/packages/flutter/lib/src/material/radio.dart +++ b/packages/flutter/lib/src/material/radio.dart @@ -118,15 +118,12 @@ class _RadioState extends State> with TickerProviderStateMixin { Widget build(BuildContext context) { assert(debugCheckHasMaterial(context)); final ThemeData themeData = Theme.of(context); - return new Semantics( - checked: widget.value == widget.groupValue, - child: new _RadioRenderObjectWidget( - selected: widget.value == widget.groupValue, - activeColor: widget.activeColor ?? themeData.accentColor, - inactiveColor: _getInactiveColor(themeData), - onChanged: _enabled ? _handleChanged : null, - vsync: this, - ) + return new _RadioRenderObjectWidget( + selected: widget.value == widget.groupValue, + activeColor: widget.activeColor ?? themeData.accentColor, + inactiveColor: _getInactiveColor(themeData), + onChanged: _enabled ? _handleChanged : null, + vsync: this, ); } } @@ -187,9 +184,6 @@ class _RenderRadio extends RenderToggleable { vsync: vsync, ); - @override - bool get isInteractive => super.isInteractive && !value; - @override void paint(PaintingContext context, Offset offset) { final Canvas canvas = context.canvas; @@ -212,4 +206,10 @@ class _RenderRadio extends RenderToggleable { canvas.drawCircle(center, _kInnerRadius * position.value, paint); } } + + @override + void describeSemanticsConfiguration(SemanticsConfiguration config) { + super.describeSemanticsConfiguration(config); + config.isInMutuallyExclusiveGroup = true; + } } diff --git a/packages/flutter/lib/src/material/toggleable.dart b/packages/flutter/lib/src/material/toggleable.dart index 2e6d7fbc63d..94970f141f9 100644 --- a/packages/flutter/lib/src/material/toggleable.dart +++ b/packages/flutter/lib/src/material/toggleable.dart @@ -286,7 +286,8 @@ abstract class RenderToggleable extends RenderConstrainedBox { void describeSemanticsConfiguration(SemanticsConfiguration config) { super.describeSemanticsConfiguration(config); - config.isSemanticBoundary = isInteractive; + config.isSemanticBoundary = true; + config.isEnabled = isInteractive; if (isInteractive) config.onTap = _handleTap; config.isChecked = _value; diff --git a/packages/flutter/lib/src/semantics/semantics.dart b/packages/flutter/lib/src/semantics/semantics.dart index 3678e20f2be..11ad6f0dcf8 100644 --- a/packages/flutter/lib/src/semantics/semantics.dart +++ b/packages/flutter/lib/src/semantics/semantics.dart @@ -1099,6 +1099,7 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin { properties.add(new FlagProperty('isEnabled', value: _hasFlag(SemanticsFlag.isEnabled), ifFalse: 'disabled')); if (_hasFlag(SemanticsFlag.hasCheckedState)) properties.add(new FlagProperty('isChecked', value: _hasFlag(SemanticsFlag.isChecked), ifTrue: 'checked', ifFalse: 'unchecked')); + properties.add(new FlagProperty('isInMutuallyExcusiveGroup', value: _hasFlag(SemanticsFlag.isInMutuallyExclusiveGroup), ifTrue: 'mutually-exclusive')); properties.add(new FlagProperty('isSelected', value: _hasFlag(SemanticsFlag.isSelected), ifTrue: 'selected')); properties.add(new FlagProperty('isFocused', value: _hasFlag(SemanticsFlag.isFocused), ifTrue: 'focused')); properties.add(new FlagProperty('isButton', value: _hasFlag(SemanticsFlag.isButton), ifTrue: 'button')); @@ -1790,6 +1791,16 @@ class SemanticsConfiguration { _setFlag(SemanticsFlag.isChecked, value); } + /// Whether the owning RenderObject corresponds to UI that allows the user to + /// pick one of several mutually exclusive options. + /// + /// For example, a [Radio] button is in a mutually exclusive group because + /// only one radio button in that group can be marked as [isChecked]. + bool get isInMutuallyExclusiveGroup => _hasFlag(SemanticsFlag.isInMutuallyExclusiveGroup); + set isInMutuallyExclusiveGroup(bool value) { + _setFlag(SemanticsFlag.isInMutuallyExclusiveGroup, value); + } + /// Whether the owning [RenderObject] currently holds the user's focus. bool get isFocused => _hasFlag(SemanticsFlag.isFocused); set isFocused(bool value) { diff --git a/packages/flutter/lib/src/widgets/banner.dart b/packages/flutter/lib/src/widgets/banner.dart index 5d3c7bc14dc..7e713d1c297 100644 --- a/packages/flutter/lib/src/widgets/banner.dart +++ b/packages/flutter/lib/src/widgets/banner.dart @@ -115,7 +115,7 @@ class BannerPainter extends CustomPainter { void _prepare() { _paintShadow = new Paint() ..color = const Color(0x7F000000) - ..maskFilter = new MaskFilter.blur(BlurStyle.normal, 4.0); + ..maskFilter = const MaskFilter.blur(BlurStyle.normal, 4.0); _paintBanner = new Paint() ..color = color; _textPainter = new TextPainter( diff --git a/packages/flutter/test/material/checkbox_test.dart b/packages/flutter/test/material/checkbox_test.dart new file mode 100644 index 00000000000..afa3aac6e76 --- /dev/null +++ b/packages/flutter/test/material/checkbox_test.dart @@ -0,0 +1,105 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:ui'; + +import 'package:flutter/rendering.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter/material.dart'; + +import '../widgets/semantics_tester.dart'; + +void main() { + testWidgets('CheckBox semantics', (WidgetTester tester) async { + final SemanticsTester semantics = new SemanticsTester(tester); + + await tester.pumpWidget(new Material( + child: new Checkbox( + value: false, + onChanged: (bool b) { }, + ), + )); + + expect(semantics, hasSemantics(new TestSemantics.root( + children: [ + new TestSemantics.rootChild( + id: 1, + flags: [ + SemanticsFlag.hasCheckedState, + SemanticsFlag.hasEnabledState, + SemanticsFlag.isEnabled, + ], + actions: [ + SemanticsAction.tap, + ], + ), + ], + ), ignoreRect: true, ignoreTransform: true)); + + await tester.pumpWidget(new Material( + child: new Checkbox( + value: true, + onChanged: (bool b) { }, + ), + )); + + expect(semantics, hasSemantics(new TestSemantics.root( + children: [ + new TestSemantics.rootChild( + id: 1, + flags: [ + SemanticsFlag.hasCheckedState, + SemanticsFlag.isChecked, + SemanticsFlag.hasEnabledState, + SemanticsFlag.isEnabled, + ], + actions: [ + SemanticsAction.tap, + ], + ), + ], + ), ignoreRect: true, ignoreTransform: true)); + + await tester.pumpWidget(const Material( + child: const Checkbox( + value: false, + onChanged: null, + ), + )); + + expect(semantics, hasSemantics(new TestSemantics.root( + children: [ + new TestSemantics.rootChild( + id: 1, + flags: [ + SemanticsFlag.hasCheckedState, + SemanticsFlag.hasEnabledState, + ], + ), + ], + ), ignoreRect: true, ignoreTransform: true)); + + await tester.pumpWidget(const Material( + child: const Checkbox( + value: true, + onChanged: null, + ), + )); + + expect(semantics, hasSemantics(new TestSemantics.root( + children: [ + new TestSemantics.rootChild( + id: 1, + flags: [ + SemanticsFlag.hasCheckedState, + SemanticsFlag.isChecked, + SemanticsFlag.hasEnabledState, + ], + ), + ], + ), ignoreRect: true, ignoreTransform: true)); + + semantics.dispose(); + }); +} diff --git a/packages/flutter/test/material/control_list_tile_test.dart b/packages/flutter/test/material/control_list_tile_test.dart index 16e93f60cac..e1383f49445 100644 --- a/packages/flutter/test/material/control_list_tile_test.dart +++ b/packages/flutter/test/material/control_list_tile_test.dart @@ -99,6 +99,7 @@ void main() { expect(semantics, hasSemantics(new TestSemantics.root( children: [ new TestSemantics.rootChild( + id: 1, rect: new Rect.fromLTWH(0.0, 0.0, 800.0, 56.0), transform: null, flags: [ @@ -111,6 +112,7 @@ void main() { label: 'aaa\nAAA', ), new TestSemantics.rootChild( + id: 3, rect: new Rect.fromLTWH(0.0, 0.0, 800.0, 56.0), transform: new Matrix4.translationValues(0.0, 56.0, 0.0), flags: [ @@ -123,18 +125,20 @@ void main() { label: 'bbb\nBBB', ), new TestSemantics.rootChild( + id: 5, rect: new Rect.fromLTWH(0.0, 0.0, 800.0, 56.0), transform: new Matrix4.translationValues(0.0, 112.0, 0.0), flags: [ SemanticsFlag.hasCheckedState, SemanticsFlag.hasEnabledState, - SemanticsFlag.isEnabled + SemanticsFlag.isEnabled, + SemanticsFlag.isInMutuallyExclusiveGroup, ], actions: SemanticsAction.tap.index, label: 'CCC\nccc', ), ], - ), ignoreId: true)); + ))); }); } diff --git a/packages/flutter/test/material/radio_test.dart b/packages/flutter/test/material/radio_test.dart index 634e832082e..20c4d227199 100644 --- a/packages/flutter/test/material/radio_test.dart +++ b/packages/flutter/test/material/radio_test.dart @@ -2,9 +2,14 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:ui'; + +import 'package:flutter/rendering.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:flutter/material.dart'; +import '../widgets/semantics_tester.dart'; + void main() { testWidgets('Radio control test', (WidgetTester tester) async { final Key key = new UniqueKey(); @@ -57,4 +62,104 @@ void main() { expect(log, isEmpty); }); + + testWidgets('Radio semantics', (WidgetTester tester) async { + final SemanticsTester semantics = new SemanticsTester(tester); + + await tester.pumpWidget(new Material( + child: new Radio( + value: 1, + groupValue: 2, + onChanged: (int i) { }, + ), + )); + + expect(semantics, hasSemantics(new TestSemantics.root( + children: [ + new TestSemantics.rootChild( + id: 1, + flags: [ + SemanticsFlag.isInMutuallyExclusiveGroup, + SemanticsFlag.hasCheckedState, + SemanticsFlag.hasEnabledState, + SemanticsFlag.isEnabled, + ], + actions: [ + SemanticsAction.tap, + ], + ), + ], + ), ignoreRect: true, ignoreTransform: true)); + + await tester.pumpWidget(new Material( + child: new Radio( + value: 2, + groupValue: 2, + onChanged: (int i) { }, + ), + )); + + expect(semantics, hasSemantics(new TestSemantics.root( + children: [ + new TestSemantics.rootChild( + id: 1, + flags: [ + SemanticsFlag.isInMutuallyExclusiveGroup, + SemanticsFlag.hasCheckedState, + SemanticsFlag.isChecked, + SemanticsFlag.hasEnabledState, + SemanticsFlag.isEnabled, + ], + actions: [ + SemanticsAction.tap, + ], + ), + ], + ), ignoreRect: true, ignoreTransform: true)); + + await tester.pumpWidget(const Material( + child: const Radio( + value: 1, + groupValue: 2, + onChanged: null, + ), + )); + + expect(semantics, hasSemantics(new TestSemantics.root( + children: [ + new TestSemantics.rootChild( + id: 1, + flags: [ + SemanticsFlag.isInMutuallyExclusiveGroup, + SemanticsFlag.hasCheckedState, + SemanticsFlag.hasEnabledState, + ], + ), + ], + ), ignoreRect: true, ignoreTransform: true)); + + await tester.pumpWidget(const Material( + child: const Radio( + value: 2, + groupValue: 2, + onChanged: null, + ), + )); + + expect(semantics, hasSemantics(new TestSemantics.root( + children: [ + new TestSemantics.rootChild( + id: 1, + flags: [ + SemanticsFlag.isInMutuallyExclusiveGroup, + SemanticsFlag.hasCheckedState, + SemanticsFlag.isChecked, + SemanticsFlag.hasEnabledState, + ], + ), + ], + ), ignoreRect: true, ignoreTransform: true)); + + semantics.dispose(); + }); } diff --git a/packages/flutter/test/semantics/semantics_test.dart b/packages/flutter/test/semantics/semantics_test.dart index 34ba425efa3..a047c484c28 100644 --- a/packages/flutter/test/semantics/semantics_test.dart +++ b/packages/flutter/test/semantics/semantics_test.dart @@ -206,7 +206,7 @@ void main() { expect( minimalProperties.toStringDeep(minLevel: DiagnosticLevel.hidden), - 'SemanticsNode#1(owner: null, isMergedIntoParent: false, mergeAllDescendantsIntoThisNode: false, Rect.fromLTRB(0.0, 0.0, 0.0, 0.0), actions: [], isSelected: false, isFocused: false, isButton: false, isTextField: false, label: "", value: "", increasedValue: "", decreasedValue: "", hint: "", textDirection: null)\n' + 'SemanticsNode#1(owner: null, isMergedIntoParent: false, mergeAllDescendantsIntoThisNode: false, Rect.fromLTRB(0.0, 0.0, 0.0, 0.0), actions: [], isInMutuallyExcusiveGroup: false, isSelected: false, isFocused: false, isButton: false, isTextField: false, label: "", value: "", increasedValue: "", decreasedValue: "", hint: "", textDirection: null)\n' ); final SemanticsConfiguration config = new SemanticsConfiguration()