From 7549925c8c44dea92c7bc75be676c17b7613f87f Mon Sep 17 00:00:00 2001 From: Casey Hillers Date: Sat, 10 Dec 2022 18:23:40 -0800 Subject: [PATCH] Revert "Adds API in semanticsconfiguration to decide how to merge child semanticsConfigurations (#110730)" (#116839) This reverts commit 352ad3a9efccc8c54deb72eb0d7164bfb42b2a49. --- .../lib/src/material/input_decorator.dart | 104 +----- .../flutter/lib/src/rendering/object.dart | 329 +++--------------- .../flutter/lib/src/semantics/semantics.dart | 122 ------- .../test/material/text_field_test.dart | 41 --- .../flutter/test/widgets/scrollable_test.dart | 45 --- ...semantics_child_configs_delegate_test.dart | 259 -------------- 6 files changed, 67 insertions(+), 833 deletions(-) delete mode 100644 packages/flutter/test/widgets/semantics_child_configs_delegate_test.dart diff --git a/packages/flutter/lib/src/material/input_decorator.dart b/packages/flutter/lib/src/material/input_decorator.dart index 773eb235dcb9..4685d88bb606 100644 --- a/packages/flutter/lib/src/material/input_decorator.dart +++ b/packages/flutter/lib/src/material/input_decorator.dart @@ -1326,35 +1326,6 @@ class _RenderDecoration extends RenderBox with SlottedContainerRenderObjectMixin return Size.zero; } - ChildSemanticsConfigurationsResult _childSemanticsConfigurationDelegate(List childConfigs) { - final ChildSemanticsConfigurationsResultBuilder builder = ChildSemanticsConfigurationsResultBuilder(); - List? prefixMergeGroup; - List? suffixMergeGroup; - for (final SemanticsConfiguration childConfig in childConfigs) { - if (childConfig.tagsChildrenWith(_InputDecoratorState._kPrefixSemanticsTag)) { - prefixMergeGroup ??= []; - prefixMergeGroup.add(childConfig); - } else if (childConfig.tagsChildrenWith(_InputDecoratorState._kSuffixSemanticsTag)) { - suffixMergeGroup ??= []; - suffixMergeGroup.add(childConfig); - } else { - builder.markAsMergeUp(childConfig); - } - } - if (prefixMergeGroup != null) { - builder.markAsSiblingMergeGroup(prefixMergeGroup); - } - if (suffixMergeGroup != null) { - builder.markAsSiblingMergeGroup(suffixMergeGroup); - } - return builder.build(); - } - - @override - void describeSemanticsConfiguration(SemanticsConfiguration config) { - config.childConfigurationsDelegate = _childSemanticsConfigurationDelegate; - } - @override void performLayout() { final BoxConstraints constraints = this.constraints; @@ -1742,16 +1713,12 @@ class _AffixText extends StatelessWidget { this.text, this.style, this.child, - this.semanticsSortKey, - required this.semanticsTag, }); final bool labelIsFloating; final String? text; final TextStyle? style; final Widget? child; - final SemanticsSortKey? semanticsSortKey; - final SemanticsTag semanticsTag; @override Widget build(BuildContext context) { @@ -1761,11 +1728,7 @@ class _AffixText extends StatelessWidget { duration: _kTransitionDuration, curve: _kTransitionCurve, opacity: labelIsFloating ? 1.0 : 0.0, - child: Semantics( - sortKey: semanticsSortKey, - tagForChildren: semanticsTag, - child: child ?? (text == null ? null : Text(text!, style: style)), - ), + child: child ?? (text == null ? null : Text(text!, style: style)), ), ); } @@ -1936,11 +1899,6 @@ class _InputDecoratorState extends State with TickerProviderStat late AnimationController _floatingLabelController; late AnimationController _shakingLabelController; final _InputBorderGap _borderGap = _InputBorderGap(); - static const OrdinalSortKey _kPrefixSemanticsSortOrder = OrdinalSortKey(0); - static const OrdinalSortKey _kInputSemanticsSortOrder = OrdinalSortKey(1); - static const OrdinalSortKey _kSuffixSemanticsSortOrder = OrdinalSortKey(2); - static const SemanticsTag _kPrefixSemanticsTag = SemanticsTag('_InputDecoratorState.prefix'); - static const SemanticsTag _kSuffixSemanticsTag = SemanticsTag('_InputDecoratorState.suffix'); @override void initState() { @@ -2260,42 +2218,22 @@ class _InputDecoratorState extends State with TickerProviderStat ), ); - final bool hasPrefix = decoration.prefix != null || decoration.prefixText != null; - final bool hasSuffix = decoration.suffix != null || decoration.suffixText != null; - - Widget? input = widget.child; - // If at least two out of the three are visible, it needs semantics sort - // order. - final bool needsSemanticsSortOrder = widget._labelShouldWithdraw && (input != null ? (hasPrefix || hasSuffix) : (hasPrefix && hasSuffix)); - - final Widget? prefix = hasPrefix - ? _AffixText( - labelIsFloating: widget._labelShouldWithdraw, - text: decoration.prefixText, - style: MaterialStateProperty.resolveAs(decoration.prefixStyle, materialState) ?? hintStyle, - semanticsSortKey: needsSemanticsSortOrder ? _kPrefixSemanticsSortOrder : null, - semanticsTag: _kPrefixSemanticsTag, - child: decoration.prefix, - ) - : null; - - final Widget? suffix = hasSuffix - ? _AffixText( - labelIsFloating: widget._labelShouldWithdraw, - text: decoration.suffixText, - style: MaterialStateProperty.resolveAs(decoration.suffixStyle, materialState) ?? hintStyle, - semanticsSortKey: needsSemanticsSortOrder ? _kSuffixSemanticsSortOrder : null, - semanticsTag: _kSuffixSemanticsTag, - child: decoration.suffix, - ) - : null; - - if (input != null && needsSemanticsSortOrder) { - input = Semantics( - sortKey: _kInputSemanticsSortOrder, - child: input, + final Widget? prefix = decoration.prefix == null && decoration.prefixText == null ? null : + _AffixText( + labelIsFloating: widget._labelShouldWithdraw, + text: decoration.prefixText, + style: MaterialStateProperty.resolveAs(decoration.prefixStyle, materialState) ?? hintStyle, + child: decoration.prefix, ); - } + + final Widget? suffix = decoration.suffix == null && decoration.suffixText == null ? null : + _AffixText( + labelIsFloating: widget._labelShouldWithdraw, + text: decoration.suffixText, + style: MaterialStateProperty.resolveAs(decoration.suffixStyle, materialState) ?? hintStyle, + child: decoration.suffix, + ); + final bool decorationIsDense = decoration.isDense ?? false; final double iconSize = decorationIsDense ? 18.0 : 24.0; @@ -2334,9 +2272,7 @@ class _InputDecoratorState extends State with TickerProviderStat color: _getPrefixIconColor(themeData, defaults), size: iconSize, ), - child: Semantics( - child: decoration.prefixIcon, - ), + child: decoration.prefixIcon!, ), ), ), @@ -2361,9 +2297,7 @@ class _InputDecoratorState extends State with TickerProviderStat color: _getSuffixIconColor(themeData, defaults), size: iconSize, ), - child: Semantics( - child: decoration.suffixIcon, - ), + child: decoration.suffixIcon!, ), ), ), @@ -2440,7 +2374,7 @@ class _InputDecoratorState extends State with TickerProviderStat isDense: decoration.isDense, visualDensity: themeData.visualDensity, icon: icon, - input: input, + input: widget.child, label: label, hint: hint, prefix: prefix, diff --git a/packages/flutter/lib/src/rendering/object.dart b/packages/flutter/lib/src/rendering/object.dart index 7449016c4692..c3aa820850dd 100644 --- a/packages/flutter/lib/src/rendering/object.dart +++ b/packages/flutter/lib/src/rendering/object.dart @@ -3100,10 +3100,6 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im if (_cachedSemanticsConfiguration == null) { _cachedSemanticsConfiguration = SemanticsConfiguration(); describeSemanticsConfiguration(_cachedSemanticsConfiguration!); - assert( - !_cachedSemanticsConfiguration!.explicitChildNodes || _cachedSemanticsConfiguration!.childConfigurationsDelegate == null, - 'A SemanticsConfiguration with explicitChildNode set to true cannot have a non-null childConfigsDelegate.', - ); } return _cachedSemanticsConfiguration!; } @@ -3165,13 +3161,7 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im final bool wasSemanticsBoundary = _semantics != null && (_cachedSemanticsConfiguration?.isSemanticBoundary ?? false); _cachedSemanticsConfiguration = null; - // The childConfigurationsDelegate may produce sibling nodes to be attached - // to the parent of this semantics node, thus it can't be a semantics - // boundary. - bool isEffectiveSemanticsBoundary = - _semanticsConfiguration.childConfigurationsDelegate == null && - _semanticsConfiguration.isSemanticBoundary && - wasSemanticsBoundary; + bool isEffectiveSemanticsBoundary = _semanticsConfiguration.isSemanticBoundary && wasSemanticsBoundary; RenderObject node = this; while (!isEffectiveSemanticsBoundary && node.parent is RenderObject) { @@ -3223,13 +3213,11 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im assert(fragment is _InterestingSemanticsFragment); final _InterestingSemanticsFragment interestingFragment = fragment as _InterestingSemanticsFragment; final List result = []; - final List siblingNodes = []; interestingFragment.compileChildren( parentSemanticsClipRect: _semantics?.parentSemanticsClipRect, parentPaintClipRect: _semantics?.parentPaintClipRect, elevationAdjustment: _semantics?.elevationAdjustment ?? 0.0, result: result, - siblingNodes: siblingNodes, ); final SemanticsNode node = result.single; // Fragment only wants to add this node's SemanticsNode to the parent. @@ -3247,94 +3235,70 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im bool dropSemanticsOfPreviousSiblings = config.isBlockingSemanticsOfPreviouslyPaintedNodes; final bool producesForkingFragment = !config.hasBeenAnnotated && !config.isSemanticBoundary; + final List<_InterestingSemanticsFragment> fragments = <_InterestingSemanticsFragment>[]; + final Set<_InterestingSemanticsFragment> toBeMarkedExplicit = <_InterestingSemanticsFragment>{}; final bool childrenMergeIntoParent = mergeIntoParent || config.isMergingSemanticsOfDescendants; - final List childConfigurations = []; - final bool explicitChildNode = config.explicitChildNodes || parent is! RenderObject; - final bool hasChildConfigurationsDelegate = config.childConfigurationsDelegate != null; - final Map configToFragment = {}; - final List<_InterestingSemanticsFragment> mergeUpFragments = <_InterestingSemanticsFragment>[]; - final List> siblingMergeFragmentGroups = >[]; + visitChildrenForSemantics((RenderObject renderChild) { assert(!_needsLayout); final _SemanticsFragment parentFragment = renderChild._getSemanticsForParent( mergeIntoParent: childrenMergeIntoParent, ); if (parentFragment.dropsSemanticsOfPreviousSiblings) { - childConfigurations.clear(); - mergeUpFragments.clear(); - siblingMergeFragmentGroups.clear(); + fragments.clear(); + toBeMarkedExplicit.clear(); if (!config.isSemanticBoundary) { dropSemanticsOfPreviousSiblings = true; } } - for (final _InterestingSemanticsFragment fragment in parentFragment.mergeUpFragments) { + // Figure out which child fragments are to be made explicit. + for (final _InterestingSemanticsFragment fragment in parentFragment.interestingFragments) { + fragments.add(fragment); fragment.addAncestor(this); fragment.addTags(config.tagsForChildren); - if (hasChildConfigurationsDelegate && fragment.config != null) { - // This fragment need to go through delegate to determine whether it - // merge up or not. - childConfigurations.add(fragment.config!); - configToFragment[fragment.config!] = fragment; - } else { - mergeUpFragments.add(fragment); + if (config.explicitChildNodes || parent is! RenderObject) { + fragment.markAsExplicit(); + continue; } - } - if (parentFragment is _ContainerSemanticsFragment) { - // Container fragments needs to propagate sibling merge group to be - // compiled by _SwitchableSemanticsFragment. - for (final List<_InterestingSemanticsFragment> siblingMergeGroup in parentFragment.siblingMergeGroups) { - for (final _InterestingSemanticsFragment siblingMergingFragment in siblingMergeGroup) { - siblingMergingFragment.addAncestor(this); - siblingMergingFragment.addTags(config.tagsForChildren); + if (!fragment.hasConfigForParent || producesForkingFragment) { + continue; + } + if (!config.isCompatibleWith(fragment.config)) { + toBeMarkedExplicit.add(fragment); + } + final int siblingLength = fragments.length - 1; + for (int i = 0; i < siblingLength; i += 1) { + final _InterestingSemanticsFragment siblingFragment = fragments[i]; + if (!fragment.config!.isCompatibleWith(siblingFragment.config)) { + toBeMarkedExplicit.add(fragment); + toBeMarkedExplicit.add(siblingFragment); } - siblingMergeFragmentGroups.add(siblingMergeGroup); } } }); - assert(hasChildConfigurationsDelegate || configToFragment.isEmpty); - - if (explicitChildNode) { - for (final _InterestingSemanticsFragment fragment in mergeUpFragments) { - fragment.markAsExplicit(); - } - } else if (hasChildConfigurationsDelegate && childConfigurations.isNotEmpty) { - final ChildSemanticsConfigurationsResult result = config.childConfigurationsDelegate!(childConfigurations); - mergeUpFragments.addAll( - result.mergeUp.map<_InterestingSemanticsFragment>((SemanticsConfiguration config) => configToFragment[config]!), - ); - for (final Iterable group in result.siblingMergeGroups) { - siblingMergeFragmentGroups.add( - group.map<_InterestingSemanticsFragment>((SemanticsConfiguration config) => configToFragment[config]!).toList() - ); - } + for (final _InterestingSemanticsFragment fragment in toBeMarkedExplicit) { + fragment.markAsExplicit(); } _needsSemanticsUpdate = false; - final _SemanticsFragment result; + _SemanticsFragment result; if (parent is! RenderObject) { assert(!config.hasBeenAnnotated); assert(!mergeIntoParent); - assert(siblingMergeFragmentGroups.isEmpty); - _marksExplicitInMergeGroup(mergeUpFragments, isMergeUp: true); - siblingMergeFragmentGroups.forEach(_marksExplicitInMergeGroup); result = _RootSemanticsFragment( owner: this, dropsSemanticsOfPreviousSiblings: dropSemanticsOfPreviousSiblings, ); } else if (producesForkingFragment) { result = _ContainerSemanticsFragment( - siblingMergeGroups: siblingMergeFragmentGroups, dropsSemanticsOfPreviousSiblings: dropSemanticsOfPreviousSiblings, ); } else { - _marksExplicitInMergeGroup(mergeUpFragments, isMergeUp: true); - siblingMergeFragmentGroups.forEach(_marksExplicitInMergeGroup); result = _SwitchableSemanticsFragment( config: config, mergeIntoParent: mergeIntoParent, - siblingMergeGroups: siblingMergeFragmentGroups, owner: this, dropsSemanticsOfPreviousSiblings: dropSemanticsOfPreviousSiblings, ); @@ -3343,32 +3307,10 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im fragment.markAsExplicit(); } } - result.addAll(mergeUpFragments); - return result; - } - void _marksExplicitInMergeGroup(List<_InterestingSemanticsFragment> mergeGroup, {bool isMergeUp = false}) { - final Set<_InterestingSemanticsFragment> toBeExplicit = <_InterestingSemanticsFragment>{}; - for (int i = 0; i < mergeGroup.length; i += 1) { - final _InterestingSemanticsFragment fragment = mergeGroup[i]; - if (!fragment.hasConfigForParent) { - continue; - } - if (isMergeUp && !_semanticsConfiguration.isCompatibleWith(fragment.config)) { - toBeExplicit.add(fragment); - } - final int siblingLength = i; - for (int j = 0; j < siblingLength; j += 1) { - final _InterestingSemanticsFragment siblingFragment = mergeGroup[j]; - if (!fragment.config!.isCompatibleWith(siblingFragment.config)) { - toBeExplicit.add(fragment); - toBeExplicit.add(siblingFragment); - } - } - } - for (final _InterestingSemanticsFragment fragment in toBeExplicit) { - fragment.markAsExplicit(); - } + result.addAll(fragments); + + return result; } /// Called when collecting the semantics of this node. @@ -4043,9 +3985,8 @@ mixin RelayoutWhenSystemFontsChangeMixin on RenderObject { /// * [_ContainerSemanticsFragment]: a container class to transport the semantic /// information of multiple [_InterestingSemanticsFragment] to a parent. abstract class _SemanticsFragment { - _SemanticsFragment({ - required this.dropsSemanticsOfPreviousSiblings, - }) : assert (dropsSemanticsOfPreviousSiblings != null); + _SemanticsFragment({ required this.dropsSemanticsOfPreviousSiblings }) + : assert (dropsSemanticsOfPreviousSiblings != null); /// Incorporate the fragments of children into this fragment. void addAll(Iterable<_InterestingSemanticsFragment> fragments); @@ -4061,29 +4002,25 @@ abstract class _SemanticsFragment { /// Returns [_InterestingSemanticsFragment] describing the actual semantic /// information that this fragment wants to add to the parent. - List<_InterestingSemanticsFragment> get mergeUpFragments; + List<_InterestingSemanticsFragment> get interestingFragments; } /// A container used when a [RenderObject] wants to add multiple independent /// [_InterestingSemanticsFragment] to its parent. /// /// The [_InterestingSemanticsFragment] to be added to the parent can be -/// obtained via [mergeUpFragments]. +/// obtained via [interestingFragments]. class _ContainerSemanticsFragment extends _SemanticsFragment { - _ContainerSemanticsFragment({ - required super.dropsSemanticsOfPreviousSiblings, - required this.siblingMergeGroups, - }); - final List> siblingMergeGroups; + _ContainerSemanticsFragment({ required super.dropsSemanticsOfPreviousSiblings }); @override void addAll(Iterable<_InterestingSemanticsFragment> fragments) { - mergeUpFragments.addAll(fragments); + interestingFragments.addAll(fragments); } @override - final List<_InterestingSemanticsFragment> mergeUpFragments = <_InterestingSemanticsFragment>[]; + final List<_InterestingSemanticsFragment> interestingFragments = <_InterestingSemanticsFragment>[]; } /// A [_SemanticsFragment] that describes which concrete semantic information @@ -4120,7 +4057,6 @@ abstract class _InterestingSemanticsFragment extends _SemanticsFragment { required Rect? parentPaintClipRect, required double elevationAdjustment, required List result, - required List siblingNodes, }); /// The [SemanticsConfiguration] the child wants to merge into the parent's @@ -4150,7 +4086,7 @@ abstract class _InterestingSemanticsFragment extends _SemanticsFragment { bool get hasConfigForParent => config != null; @override - List<_InterestingSemanticsFragment> get mergeUpFragments => <_InterestingSemanticsFragment>[this]; + List<_InterestingSemanticsFragment> get interestingFragments => <_InterestingSemanticsFragment>[this]; Set? _tagsForChildren; @@ -4188,13 +4124,7 @@ class _RootSemanticsFragment extends _InterestingSemanticsFragment { }); @override - void compileChildren({ - Rect? parentSemanticsClipRect, - Rect? parentPaintClipRect, - required double elevationAdjustment, - required List result, - required List siblingNodes, - }) { + void compileChildren({ Rect? parentSemanticsClipRect, Rect? parentPaintClipRect, required double elevationAdjustment, required List result }) { assert(_tagsForChildren == null || _tagsForChildren!.isEmpty); assert(parentSemanticsClipRect == null); assert(parentPaintClipRect == null); @@ -4220,11 +4150,8 @@ class _RootSemanticsFragment extends _InterestingSemanticsFragment { parentPaintClipRect: parentPaintClipRect, elevationAdjustment: 0.0, result: children, - siblingNodes: siblingNodes, ); } - // Root node does not have a parent and thus can't attach sibling nodes. - assert(siblingNodes.isEmpty); node.updateWith(config: null, childrenInInversePaintOrder: children); // The root node is the only semantics node allowed to be invisible. This @@ -4274,11 +4201,9 @@ class _SwitchableSemanticsFragment extends _InterestingSemanticsFragment { _SwitchableSemanticsFragment({ required bool mergeIntoParent, required SemanticsConfiguration config, - required List> siblingMergeGroups, required super.owner, required super.dropsSemanticsOfPreviousSiblings, - }) : _siblingMergeGroups = siblingMergeGroups, - _mergeIntoParent = mergeIntoParent, + }) : _mergeIntoParent = mergeIntoParent, _config = config, assert(mergeIntoParent != null), assert(config != null); @@ -4286,126 +4211,14 @@ class _SwitchableSemanticsFragment extends _InterestingSemanticsFragment { final bool _mergeIntoParent; SemanticsConfiguration _config; bool _isConfigWritable = false; - bool _mergesToSibling = false; - - final List> _siblingMergeGroups; - - void _mergeSiblingGroup(Rect? parentSemanticsClipRect, Rect? parentPaintClipRect, List result, Set usedSemanticsIds) { - for (final List<_InterestingSemanticsFragment> group in _siblingMergeGroups) { - Rect? rect; - Rect? semanticsClipRect; - Rect? paintClipRect; - SemanticsConfiguration? configuration; - // Use empty set because the _tagsForChildren may not contains all of the - // tags if this fragment is not explicit. The _tagsForChildren are added - // to sibling nodes at the end of compileChildren if this fragment is - // explicit. - final Set tags = {}; - SemanticsNode? node; - for (final _InterestingSemanticsFragment fragment in group) { - if (fragment.config != null) { - final _SwitchableSemanticsFragment switchableFragment = fragment as _SwitchableSemanticsFragment; - switchableFragment._mergesToSibling = true; - node ??= fragment.owner._semantics; - if (configuration == null) { - switchableFragment._ensureConfigIsWritable(); - configuration = switchableFragment.config; - } else { - configuration.absorb(switchableFragment.config!); - } - // It is a child fragment of a _SwitchableFragment, it must have a - // geometry. - final _SemanticsGeometry geometry = switchableFragment._computeSemanticsGeometry( - parentSemanticsClipRect: parentSemanticsClipRect, - parentPaintClipRect: parentPaintClipRect, - )!; - final Rect fragmentRect = MatrixUtils.transformRect(geometry.transform, geometry.rect); - if (rect == null) { - rect = fragmentRect; - } else { - rect = rect.expandToInclude(fragmentRect); - } - if (geometry.semanticsClipRect != null) { - final Rect rect = MatrixUtils.transformRect(geometry.transform, geometry.semanticsClipRect!); - if (semanticsClipRect == null) { - semanticsClipRect = rect; - } else { - semanticsClipRect = semanticsClipRect.intersect(rect); - } - } - if (geometry.paintClipRect != null) { - final Rect rect = MatrixUtils.transformRect(geometry.transform, geometry.paintClipRect!); - if (paintClipRect == null) { - paintClipRect = rect; - } else { - paintClipRect = paintClipRect.intersect(rect); - } - } - if (switchableFragment._tagsForChildren != null) { - tags.addAll(switchableFragment._tagsForChildren!); - } - } - } - // Can be null if all fragments in group are marked as explicit. - if (configuration != null && !rect!.isEmpty) { - if (node == null || usedSemanticsIds.contains(node.id)) { - node = SemanticsNode(showOnScreen: owner.showOnScreen); - } - usedSemanticsIds.add(node.id); - node - ..tags = tags - ..rect = rect - ..transform = null // Will be set when compiling immediate parent node. - ..parentSemanticsClipRect = semanticsClipRect - ..parentPaintClipRect = paintClipRect; - for (final _InterestingSemanticsFragment fragment in group) { - if (fragment.config != null) { - fragment.owner._semantics = node; - } - } - node.updateWith(config: configuration); - result.add(node); - } - } - } - final List<_InterestingSemanticsFragment> _children = <_InterestingSemanticsFragment>[]; @override - void compileChildren({ - Rect? parentSemanticsClipRect, - Rect? parentPaintClipRect, - required double elevationAdjustment, - required List result, - required List siblingNodes, - }) { - final Set usedSemanticsIds = {}; - Iterable<_InterestingSemanticsFragment> compilingFragments = _children; - for (final List<_InterestingSemanticsFragment> siblingGroup in _siblingMergeGroups) { - compilingFragments = compilingFragments.followedBy(siblingGroup); - } + void compileChildren({ Rect? parentSemanticsClipRect, Rect? parentPaintClipRect, required double elevationAdjustment, required List result }) { if (!_isExplicit) { - if (!_mergesToSibling) { - owner._semantics = null; - } - _mergeSiblingGroup( - parentSemanticsClipRect, - parentPaintClipRect, - siblingNodes, - usedSemanticsIds, - ); - for (final _InterestingSemanticsFragment fragment in compilingFragments) { + owner._semantics = null; + for (final _InterestingSemanticsFragment fragment in _children) { assert(_ancestorChain.first == fragment._ancestorChain.last); - if (fragment is _SwitchableSemanticsFragment) { - // Cached semantics node may be part of sibling merging group prior - // to this update. In this case, the semantics node may continue to - // be reused in that sibling merging group. - if (fragment._isExplicit && - fragment.owner._semantics != null && - usedSemanticsIds.contains(fragment.owner._semantics!.id)) { - fragment.owner._semantics = null; - } - } fragment._ancestorChain.addAll(_ancestorChain.skip(1)); fragment.compileChildren( parentSemanticsClipRect: parentSemanticsClipRect, @@ -4415,16 +4228,14 @@ class _SwitchableSemanticsFragment extends _InterestingSemanticsFragment { // its children are placed at the elevation dictated by this config. elevationAdjustment: elevationAdjustment + _config.elevation, result: result, - siblingNodes: siblingNodes, ); } return; } - final _SemanticsGeometry? geometry = _computeSemanticsGeometry( - parentSemanticsClipRect: parentSemanticsClipRect, - parentPaintClipRect: parentPaintClipRect, - ); + final _SemanticsGeometry? geometry = _needsGeometryUpdate + ? _SemanticsGeometry(parentSemanticsClipRect: parentSemanticsClipRect, parentPaintClipRect: parentPaintClipRect, ancestors: _ancestorChain) + : null; if (!_mergeIntoParent && (geometry?.dropFromTree ?? false)) { return; // Drop the node, it's not going to be visible. @@ -4453,66 +4264,22 @@ class _SwitchableSemanticsFragment extends _InterestingSemanticsFragment { _config.isHidden = true; } } + final List children = []; - _mergeSiblingGroup( - node.parentSemanticsClipRect, - node.parentPaintClipRect, - siblingNodes, - usedSemanticsIds, - ); - for (final _InterestingSemanticsFragment fragment in compilingFragments) { - if (fragment is _SwitchableSemanticsFragment) { - // Cached semantics node may be part of sibling merging group prior - // to this update. In this case, the semantics node may continue to - // be reused in that sibling merging group. - if (fragment._isExplicit && - fragment.owner._semantics != null && - usedSemanticsIds.contains(fragment.owner._semantics!.id)) { - fragment.owner._semantics = null; - } - } - final List childSiblingNodes = []; + for (final _InterestingSemanticsFragment fragment in _children) { fragment.compileChildren( parentSemanticsClipRect: node.parentSemanticsClipRect, parentPaintClipRect: node.parentPaintClipRect, elevationAdjustment: 0.0, result: children, - siblingNodes: childSiblingNodes, ); - siblingNodes.addAll(childSiblingNodes); } - if (_config.isSemanticBoundary) { owner.assembleSemanticsNode(node, _config, children); } else { node.updateWith(config: _config, childrenInInversePaintOrder: children); } result.add(node); - // Sibling node needs to attach to the parent of an explicit node. - for (final SemanticsNode siblingNode in siblingNodes) { - // sibling nodes are in the same coordinate of the immediate explicit node. - // They need to share the same transform if they are going to attach to the - // parent of the immediate explicit node. - assert(siblingNode.transform == null); - siblingNode - ..transform = node.transform - ..isMergedIntoParent = node.isMergedIntoParent; - if (_tagsForChildren != null) { - siblingNode.tags ??= {}; - siblingNode.tags!.addAll(_tagsForChildren!); - } - } - result.addAll(siblingNodes); - siblingNodes.clear(); - } - - _SemanticsGeometry? _computeSemanticsGeometry({ - required Rect? parentSemanticsClipRect, - required Rect? parentPaintClipRect, - }) { - return _needsGeometryUpdate - ? _SemanticsGeometry(parentSemanticsClipRect: parentSemanticsClipRect, parentPaintClipRect: parentPaintClipRect, ancestors: _ancestorChain) - : null; } @override diff --git a/packages/flutter/lib/src/semantics/semantics.dart b/packages/flutter/lib/src/semantics/semantics.dart index 041c70df676d..bed9eea8cb33 100644 --- a/packages/flutter/lib/src/semantics/semantics.dart +++ b/packages/flutter/lib/src/semantics/semantics.dart @@ -6,7 +6,6 @@ import 'dart:math' as math; import 'dart:ui' as ui; import 'dart:ui' show Offset, Rect, SemanticsAction, SemanticsFlag, StringAttribute, TextDirection; -import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/painting.dart' show MatrixUtils, TransformProperty; import 'package:flutter/services.dart'; @@ -54,20 +53,6 @@ typedef SemanticsActionHandler = void Function(Object? args); /// Used by [SemanticsOwner.onSemanticsUpdate]. typedef SemanticsUpdateCallback = void Function(ui.SemanticsUpdate update); -/// Signature for the [SemanticsConfiguration.childConfigurationsDelegate]. -/// -/// The input list contains all [SemanticsConfiguration]s that rendering -/// children want to merge upward. One can tag a render child with a -/// [SemanticsTag] and look up its [SemanticsConfiguration]s through -/// [SemanticsConfiguration.tagsChildrenWith]. -/// -/// The return value is the arrangement of these configs, including which -/// configs continue to merge upward and which configs form sibling merge group. -/// -/// Use [ChildSemanticsConfigurationsResultBuilder] to generate the return -/// value. -typedef ChildSemanticsConfigurationsDelegate = ChildSemanticsConfigurationsResult Function(List); - /// A tag for a [SemanticsNode]. /// /// Tags can be interpreted by the parent of a [SemanticsNode] @@ -100,89 +85,6 @@ class SemanticsTag { String toString() => '${objectRuntimeType(this, 'SemanticsTag')}($name)'; } -/// The result that contains the arrangement for the child -/// [SemanticsConfiguration]s. -/// -/// When the [PipelineOwner] builds the semantics tree, it uses the returned -/// [ChildSemanticsConfigurationsResult] from -/// [SemanticsConfiguration.childConfigurationsDelegate] to decide how semantics nodes -/// should form. -/// -/// Use [ChildSemanticsConfigurationsResultBuilder] to build the result. -class ChildSemanticsConfigurationsResult { - ChildSemanticsConfigurationsResult._(this.mergeUp, this.siblingMergeGroups); - - /// Returns the [SemanticsConfiguration]s that are supposed to be merged into - /// the parent semantics node. - /// - /// [SemanticsConfiguration]s that are either semantics boundaries or are - /// conflicting with other [SemanticsConfiguration]s will form explicit - /// semantics nodes. All others will be merged into the parent. - final List mergeUp; - - /// The groups of child semantics configurations that want to merge together - /// and form a sibling [SemanticsNode]. - /// - /// All the [SemanticsConfiguration]s in a given group that are either - /// semantics boundaries or are conflicting with other - /// [SemanticsConfiguration]s of the same group will be excluded from the - /// sibling merge group and form independent semantics nodes as usual. - /// - /// The result [SemanticsNode]s from the merges are attached as the sibling - /// nodes of the immediate parent semantics node. For example, a `RenderObjectA` - /// has a rendering child, `RenderObjectB`. If both of them form their own - /// semantics nodes, `SemanticsNodeA` and `SemanticsNodeB`, any semantics node - /// created from sibling merge groups of `RenderObjectB` will be attach to - /// `SemanticsNodeA` as a sibling of `SemanticsNodeB`. - final List> siblingMergeGroups; -} - -/// The builder to build a [ChildSemanticsConfigurationsResult] based on its -/// annotations. -/// -/// To use this builder, one can use [markAsMergeUp] and -/// [markAsSiblingMergeGroup] to annotate the arrangement of -/// [SemanticsConfiguration]s. Once all the configs are annotated, use [build] -/// to generate the [ChildSemanticsConfigurationsResult]. -class ChildSemanticsConfigurationsResultBuilder { - /// Creates a [ChildSemanticsConfigurationsResultBuilder]. - ChildSemanticsConfigurationsResultBuilder(); - - final List _mergeUp = []; - final List> _siblingMergeGroups = >[]; - - /// Marks the [SemanticsConfiguration] to be merged into the parent semantics - /// node. - /// - /// The [SemanticsConfiguration] will be added to the - /// [ChildSemanticsConfigurationsResult.mergeUp] that this builder builds. - void markAsMergeUp(SemanticsConfiguration config) => _mergeUp.add(config); - - /// Marks a group of [SemanticsConfiguration]s to merge together - /// and form a sibling [SemanticsNode]. - /// - /// The group of [SemanticsConfiguration]s will be added to the - /// [ChildSemanticsConfigurationsResult.siblingMergeGroups] that this builder builds. - void markAsSiblingMergeGroup(List configs) => _siblingMergeGroups.add(configs); - - /// Builds a [ChildSemanticsConfigurationsResult] contains the arrangement. - ChildSemanticsConfigurationsResult build() { - assert((){ - final Set seenConfigs = {}; - for (final SemanticsConfiguration config in [..._mergeUp, ..._siblingMergeGroups.flattened]) { - assert( - seenConfigs.add(config), - 'Duplicated SemanticsConfigurations. This can happen if the same ' - 'SemanticsConfiguration was marked twice in markAsMergeUp and/or ' - 'markAsSiblingMergeGroup' - ); - } - return true; - }()); - return ChildSemanticsConfigurationsResult._(_mergeUp, _siblingMergeGroups); - } -} - /// An identifier of a custom semantics action. /// /// Custom semantics actions can be provided to make complex user @@ -3822,25 +3724,6 @@ class SemanticsConfiguration { _onDidLoseAccessibilityFocus = value; } - /// A delegate that decides how to handle [SemanticsConfiguration]s produced - /// in the widget subtree. - /// - /// The [SemanticsConfiguration]s are produced by rendering objects in the - /// subtree and want to merge up to their parent. This delegate can decide - /// which of these should be merged together to form sibling SemanticsNodes and - /// which of them should be merged upwards into the parent SemanticsNode. - /// - /// The input list of [SemanticsConfiguration]s can be empty if the rendering - /// object of this semantics configuration is a leaf node. - ChildSemanticsConfigurationsDelegate? get childConfigurationsDelegate => _childConfigurationsDelegate; - ChildSemanticsConfigurationsDelegate? _childConfigurationsDelegate; - set childConfigurationsDelegate(ChildSemanticsConfigurationsDelegate? value) { - assert(value != null); - _childConfigurationsDelegate = value; - // Setting the childConfigsDelegate does not annotate any meaningful - // semantics information of the config. - } - /// Returns the action handler registered for [action] or null if none was /// registered. SemanticsActionHandler? getActionHandler(SemanticsAction action) => _actions[action]; @@ -4565,11 +4448,6 @@ class SemanticsConfiguration { /// * [addTagForChildren] to add a tag and for more information about their /// usage. Iterable? get tagsForChildren => _tagsForChildren; - - /// Whether this configuration will tag the child semantics nodes with a - /// given [SemanticsTag]. - bool tagsChildrenWith(SemanticsTag tag) => _tagsForChildren?.contains(tag) ?? false; - Set? _tagsForChildren; /// Specifies a [SemanticsTag] that this configuration wants to apply to all diff --git a/packages/flutter/test/material/text_field_test.dart b/packages/flutter/test/material/text_field_test.dart index 1cf7a24f7f50..944e444d1186 100644 --- a/packages/flutter/test/material/text_field_test.dart +++ b/packages/flutter/test/material/text_field_test.dart @@ -4375,47 +4375,6 @@ void main() { expect(prefixText.style, prefixStyle); }); - testWidgets('TextField prefix and suffix create a sibling node', (WidgetTester tester) async { - final SemanticsTester semantics = SemanticsTester(tester); - await tester.pumpWidget( - overlay( - child: TextField( - controller: TextEditingController(text: 'some text'), - decoration: const InputDecoration( - prefixText: 'Prefix', - suffixText: 'Suffix', - ), - ), - ), - ); - - expect(semantics, hasSemantics(TestSemantics.root( - children: [ - TestSemantics.rootChild( - id: 2, - textDirection: TextDirection.ltr, - label: 'Prefix', - ), - TestSemantics.rootChild( - id: 1, - textDirection: TextDirection.ltr, - value: 'some text', - actions: [ - SemanticsAction.tap, - ], - flags: [ - SemanticsFlag.isTextField, - ], - ), - TestSemantics.rootChild( - id: 3, - textDirection: TextDirection.ltr, - label: 'Suffix', - ), - ], - ), ignoreTransform: true, ignoreRect: true)); - }); - testWidgets('TextField with specified suffixStyle', (WidgetTester tester) async { final TextStyle suffixStyle = TextStyle( color: Colors.pink[500], diff --git a/packages/flutter/test/widgets/scrollable_test.dart b/packages/flutter/test/widgets/scrollable_test.dart index 4b5e3792904d..12b1839e0341 100644 --- a/packages/flutter/test/widgets/scrollable_test.dart +++ b/packages/flutter/test/widgets/scrollable_test.dart @@ -1429,51 +1429,6 @@ void main() { handle.dispose(); }); - testWidgets('Two panel semantics is added to the sibling nodes of direct children', (WidgetTester tester) async { - final SemanticsHandle handle = tester.ensureSemantics(); - final UniqueKey key = UniqueKey(); - await tester.pumpWidget(MaterialApp( - home: Scaffold( - body: ListView( - key: key, - children: const [ - TextField( - autofocus: true, - decoration: InputDecoration( - prefixText: 'prefix', - ), - ), - ], - ), - ), - )); - // Wait for focus. - await tester.pumpAndSettle(); - - final SemanticsNode scrollableNode = tester.getSemantics(find.byKey(key)); - SemanticsNode? intermediateNode; - scrollableNode.visitChildren((SemanticsNode node) { - intermediateNode = node; - return true; - }); - SemanticsNode? syntheticScrollableNode; - intermediateNode!.visitChildren((SemanticsNode node) { - syntheticScrollableNode = node; - return true; - }); - expect(syntheticScrollableNode!.hasFlag(ui.SemanticsFlag.hasImplicitScrolling), isTrue); - - int numberOfChild = 0; - syntheticScrollableNode!.visitChildren((SemanticsNode node) { - expect(node.isTagged(RenderViewport.useTwoPaneSemantics), isTrue); - numberOfChild += 1; - return true; - }); - expect(numberOfChild, 2); - - handle.dispose(); - }); - testWidgets('Scroll inertia cancel event', (WidgetTester tester) async { await pumpTest(tester, null); await tester.fling(find.byType(Scrollable), const Offset(0.0, -dragOffset), 1000.0); diff --git a/packages/flutter/test/widgets/semantics_child_configs_delegate_test.dart b/packages/flutter/test/widgets/semantics_child_configs_delegate_test.dart deleted file mode 100644 index b09c4cd2cf07..000000000000 --- a/packages/flutter/test/widgets/semantics_child_configs_delegate_test.dart +++ /dev/null @@ -1,259 +0,0 @@ -// Copyright 2014 The Flutter 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 'package:flutter/rendering.dart'; -import 'package:flutter/widgets.dart'; -import 'package:flutter_test/flutter_test.dart'; - -import 'semantics_tester.dart'; - -void main() { - testWidgets('Semantics can merge sibling group', (WidgetTester tester) async { - final SemanticsTester semantics = SemanticsTester(tester); - const SemanticsTag first = SemanticsTag('1'); - const SemanticsTag second = SemanticsTag('2'); - const SemanticsTag third = SemanticsTag('3'); - ChildSemanticsConfigurationsResult delegate(List configs) { - expect(configs.length, 3); - final ChildSemanticsConfigurationsResultBuilder builder = ChildSemanticsConfigurationsResultBuilder(); - final List sibling = []; - // Merge first and third - for (final SemanticsConfiguration config in configs) { - if (config.tagsChildrenWith(first) || config.tagsChildrenWith(third)) { - sibling.add(config); - } else { - builder.markAsMergeUp(config); - } - } - builder.markAsSiblingMergeGroup(sibling); - return builder.build(); - } - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: Semantics( - label: 'parent', - child: TestConfigDelegate( - delegate: delegate, - child: Column( - children: [ - Semantics( - label: '1', - tagForChildren: first, - child: const SizedBox(width: 100, height: 100), - // this tests that empty nodes disappear - ), - Semantics( - label: '2', - tagForChildren: second, - child: const SizedBox(width: 100, height: 100), - ), - Semantics( - label: '3', - tagForChildren: third, - child: const SizedBox(width: 100, height: 100), - ), - ], - ), - ), - ), - ), - ); - - expect(semantics, hasSemantics(TestSemantics.root( - children: [ - TestSemantics.rootChild( - label: 'parent\n2', - ), - TestSemantics.rootChild( - label: '1\n3', - ), - ], - ), ignoreId: true, ignoreRect: true, ignoreTransform: true)); - }); - - testWidgets('Semantics can drop semantics config', (WidgetTester tester) async { - final SemanticsTester semantics = SemanticsTester(tester); - const SemanticsTag first = SemanticsTag('1'); - const SemanticsTag second = SemanticsTag('2'); - const SemanticsTag third = SemanticsTag('3'); - ChildSemanticsConfigurationsResult delegate(List configs) { - final ChildSemanticsConfigurationsResultBuilder builder = ChildSemanticsConfigurationsResultBuilder(); - // Merge first and third - for (final SemanticsConfiguration config in configs) { - if (config.tagsChildrenWith(first) || config.tagsChildrenWith(third)) { - continue; - } - builder.markAsMergeUp(config); - } - return builder.build(); - } - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: Semantics( - label: 'parent', - child: TestConfigDelegate( - delegate: delegate, - child: Column( - children: [ - Semantics( - label: '1', - tagForChildren: first, - child: const SizedBox(width: 100, height: 100), - // this tests that empty nodes disappear - ), - Semantics( - label: '2', - tagForChildren: second, - child: const SizedBox(width: 100, height: 100), - ), - Semantics( - label: '3', - tagForChildren: third, - child: const SizedBox(width: 100, height: 100), - ), - ], - ), - ), - ), - ), - ); - - expect(semantics, hasSemantics(TestSemantics.root( - children: [ - TestSemantics.rootChild( - label: 'parent\n2', - ), - ], - ), ignoreId: true, ignoreRect: true, ignoreTransform: true)); - }); - - testWidgets('Semantics throws when mark the same config twice case 1', (WidgetTester tester) async { - const SemanticsTag first = SemanticsTag('1'); - const SemanticsTag second = SemanticsTag('2'); - const SemanticsTag third = SemanticsTag('3'); - ChildSemanticsConfigurationsResult delegate(List configs) { - final ChildSemanticsConfigurationsResultBuilder builder = ChildSemanticsConfigurationsResultBuilder(); - // Marks the same one twice. - builder.markAsMergeUp(configs.first); - builder.markAsMergeUp(configs.first); - return builder.build(); - } - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: Semantics( - label: 'parent', - child: TestConfigDelegate( - delegate: delegate, - child: Column( - children: [ - Semantics( - label: '1', - tagForChildren: first, - child: const SizedBox(width: 100, height: 100), - // this tests that empty nodes disappear - ), - Semantics( - label: '2', - tagForChildren: second, - child: const SizedBox(width: 100, height: 100), - ), - Semantics( - label: '3', - tagForChildren: third, - child: const SizedBox(width: 100, height: 100), - ), - ], - ), - ), - ), - ), - ); - - expect(tester.takeException(), isAssertionError); - }); - - testWidgets('Semantics throws when mark the same config twice case 2', (WidgetTester tester) async { - const SemanticsTag first = SemanticsTag('1'); - const SemanticsTag second = SemanticsTag('2'); - const SemanticsTag third = SemanticsTag('3'); - ChildSemanticsConfigurationsResult delegate(List configs) { - final ChildSemanticsConfigurationsResultBuilder builder = ChildSemanticsConfigurationsResultBuilder(); - // Marks the same one twice. - builder.markAsMergeUp(configs.first); - builder.markAsSiblingMergeGroup([configs.first]); - return builder.build(); - } - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: Semantics( - label: 'parent', - child: TestConfigDelegate( - delegate: delegate, - child: Column( - children: [ - Semantics( - label: '1', - tagForChildren: first, - child: const SizedBox(width: 100, height: 100), - // this tests that empty nodes disappear - ), - Semantics( - label: '2', - tagForChildren: second, - child: const SizedBox(width: 100, height: 100), - ), - Semantics( - label: '3', - tagForChildren: third, - child: const SizedBox(width: 100, height: 100), - ), - ], - ), - ), - ), - ), - ); - - expect(tester.takeException(), isAssertionError); - }); -} - -class TestConfigDelegate extends SingleChildRenderObjectWidget { - const TestConfigDelegate({super.key, required this.delegate, super.child}); - final ChildSemanticsConfigurationsDelegate delegate; - - @override - RenderTestConfigDelegate createRenderObject(BuildContext context) => RenderTestConfigDelegate( - delegate: delegate, - ); - - @override - void updateRenderObject(BuildContext context, RenderTestConfigDelegate renderObject) { - renderObject.delegate = delegate; - } -} - -class RenderTestConfigDelegate extends RenderProxyBox { - RenderTestConfigDelegate({ - ChildSemanticsConfigurationsDelegate? delegate, - }) : _delegate = delegate; - - ChildSemanticsConfigurationsDelegate? get delegate => _delegate; - ChildSemanticsConfigurationsDelegate? _delegate; - set delegate(ChildSemanticsConfigurationsDelegate? value) { - if (value != _delegate) { - markNeedsSemanticsUpdate(); - } - _delegate = value; - } - - @override - void describeSemanticsConfiguration(SemanticsConfiguration config) { - config.childConfigurationsDelegate = _delegate; - } -}