Unverified Commit 7549925c authored by Casey Hillers's avatar Casey Hillers Committed by GitHub

Revert "Adds API in semanticsconfiguration to decide how to merge child...

Revert "Adds API in semanticsconfiguration to decide how to merge child semanticsConfigurations (#110730)" (#116839)

This reverts commit 352ad3a9.
parent be5c389e
...@@ -1326,35 +1326,6 @@ class _RenderDecoration extends RenderBox with SlottedContainerRenderObjectMixin ...@@ -1326,35 +1326,6 @@ class _RenderDecoration extends RenderBox with SlottedContainerRenderObjectMixin
return Size.zero; return Size.zero;
} }
ChildSemanticsConfigurationsResult _childSemanticsConfigurationDelegate(List<SemanticsConfiguration> childConfigs) {
final ChildSemanticsConfigurationsResultBuilder builder = ChildSemanticsConfigurationsResultBuilder();
List<SemanticsConfiguration>? prefixMergeGroup;
List<SemanticsConfiguration>? suffixMergeGroup;
for (final SemanticsConfiguration childConfig in childConfigs) {
if (childConfig.tagsChildrenWith(_InputDecoratorState._kPrefixSemanticsTag)) {
prefixMergeGroup ??= <SemanticsConfiguration>[];
prefixMergeGroup.add(childConfig);
} else if (childConfig.tagsChildrenWith(_InputDecoratorState._kSuffixSemanticsTag)) {
suffixMergeGroup ??= <SemanticsConfiguration>[];
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 @override
void performLayout() { void performLayout() {
final BoxConstraints constraints = this.constraints; final BoxConstraints constraints = this.constraints;
...@@ -1742,16 +1713,12 @@ class _AffixText extends StatelessWidget { ...@@ -1742,16 +1713,12 @@ class _AffixText extends StatelessWidget {
this.text, this.text,
this.style, this.style,
this.child, this.child,
this.semanticsSortKey,
required this.semanticsTag,
}); });
final bool labelIsFloating; final bool labelIsFloating;
final String? text; final String? text;
final TextStyle? style; final TextStyle? style;
final Widget? child; final Widget? child;
final SemanticsSortKey? semanticsSortKey;
final SemanticsTag semanticsTag;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
...@@ -1761,11 +1728,7 @@ class _AffixText extends StatelessWidget { ...@@ -1761,11 +1728,7 @@ class _AffixText extends StatelessWidget {
duration: _kTransitionDuration, duration: _kTransitionDuration,
curve: _kTransitionCurve, curve: _kTransitionCurve,
opacity: labelIsFloating ? 1.0 : 0.0, opacity: labelIsFloating ? 1.0 : 0.0,
child: Semantics( child: child ?? (text == null ? null : Text(text!, style: style)),
sortKey: semanticsSortKey,
tagForChildren: semanticsTag,
child: child ?? (text == null ? null : Text(text!, style: style)),
),
), ),
); );
} }
...@@ -1936,11 +1899,6 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat ...@@ -1936,11 +1899,6 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
late AnimationController _floatingLabelController; late AnimationController _floatingLabelController;
late AnimationController _shakingLabelController; late AnimationController _shakingLabelController;
final _InputBorderGap _borderGap = _InputBorderGap(); 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 @override
void initState() { void initState() {
...@@ -2260,42 +2218,22 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat ...@@ -2260,42 +2218,22 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
), ),
); );
final bool hasPrefix = decoration.prefix != null || decoration.prefixText != null; final Widget? prefix = decoration.prefix == null && decoration.prefixText == null ? null :
final bool hasSuffix = decoration.suffix != null || decoration.suffixText != null; _AffixText(
labelIsFloating: widget._labelShouldWithdraw,
Widget? input = widget.child; text: decoration.prefixText,
// If at least two out of the three are visible, it needs semantics sort style: MaterialStateProperty.resolveAs(decoration.prefixStyle, materialState) ?? hintStyle,
// order. child: decoration.prefix,
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? 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 bool decorationIsDense = decoration.isDense ?? false;
final double iconSize = decorationIsDense ? 18.0 : 24.0; final double iconSize = decorationIsDense ? 18.0 : 24.0;
...@@ -2334,9 +2272,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat ...@@ -2334,9 +2272,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
color: _getPrefixIconColor(themeData, defaults), color: _getPrefixIconColor(themeData, defaults),
size: iconSize, size: iconSize,
), ),
child: Semantics( child: decoration.prefixIcon!,
child: decoration.prefixIcon,
),
), ),
), ),
), ),
...@@ -2361,9 +2297,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat ...@@ -2361,9 +2297,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
color: _getSuffixIconColor(themeData, defaults), color: _getSuffixIconColor(themeData, defaults),
size: iconSize, size: iconSize,
), ),
child: Semantics( child: decoration.suffixIcon!,
child: decoration.suffixIcon,
),
), ),
), ),
), ),
...@@ -2440,7 +2374,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat ...@@ -2440,7 +2374,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
isDense: decoration.isDense, isDense: decoration.isDense,
visualDensity: themeData.visualDensity, visualDensity: themeData.visualDensity,
icon: icon, icon: icon,
input: input, input: widget.child,
label: label, label: label,
hint: hint, hint: hint,
prefix: prefix, prefix: prefix,
......
...@@ -3100,10 +3100,6 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im ...@@ -3100,10 +3100,6 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im
if (_cachedSemanticsConfiguration == null) { if (_cachedSemanticsConfiguration == null) {
_cachedSemanticsConfiguration = SemanticsConfiguration(); _cachedSemanticsConfiguration = SemanticsConfiguration();
describeSemanticsConfiguration(_cachedSemanticsConfiguration!); describeSemanticsConfiguration(_cachedSemanticsConfiguration!);
assert(
!_cachedSemanticsConfiguration!.explicitChildNodes || _cachedSemanticsConfiguration!.childConfigurationsDelegate == null,
'A SemanticsConfiguration with explicitChildNode set to true cannot have a non-null childConfigsDelegate.',
);
} }
return _cachedSemanticsConfiguration!; return _cachedSemanticsConfiguration!;
} }
...@@ -3165,13 +3161,7 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im ...@@ -3165,13 +3161,7 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im
final bool wasSemanticsBoundary = _semantics != null && (_cachedSemanticsConfiguration?.isSemanticBoundary ?? false); final bool wasSemanticsBoundary = _semantics != null && (_cachedSemanticsConfiguration?.isSemanticBoundary ?? false);
_cachedSemanticsConfiguration = null; _cachedSemanticsConfiguration = null;
// The childConfigurationsDelegate may produce sibling nodes to be attached bool isEffectiveSemanticsBoundary = _semanticsConfiguration.isSemanticBoundary && wasSemanticsBoundary;
// to the parent of this semantics node, thus it can't be a semantics
// boundary.
bool isEffectiveSemanticsBoundary =
_semanticsConfiguration.childConfigurationsDelegate == null &&
_semanticsConfiguration.isSemanticBoundary &&
wasSemanticsBoundary;
RenderObject node = this; RenderObject node = this;
while (!isEffectiveSemanticsBoundary && node.parent is RenderObject) { while (!isEffectiveSemanticsBoundary && node.parent is RenderObject) {
...@@ -3223,13 +3213,11 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im ...@@ -3223,13 +3213,11 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im
assert(fragment is _InterestingSemanticsFragment); assert(fragment is _InterestingSemanticsFragment);
final _InterestingSemanticsFragment interestingFragment = fragment as _InterestingSemanticsFragment; final _InterestingSemanticsFragment interestingFragment = fragment as _InterestingSemanticsFragment;
final List<SemanticsNode> result = <SemanticsNode>[]; final List<SemanticsNode> result = <SemanticsNode>[];
final List<SemanticsNode> siblingNodes = <SemanticsNode>[];
interestingFragment.compileChildren( interestingFragment.compileChildren(
parentSemanticsClipRect: _semantics?.parentSemanticsClipRect, parentSemanticsClipRect: _semantics?.parentSemanticsClipRect,
parentPaintClipRect: _semantics?.parentPaintClipRect, parentPaintClipRect: _semantics?.parentPaintClipRect,
elevationAdjustment: _semantics?.elevationAdjustment ?? 0.0, elevationAdjustment: _semantics?.elevationAdjustment ?? 0.0,
result: result, result: result,
siblingNodes: siblingNodes,
); );
final SemanticsNode node = result.single; final SemanticsNode node = result.single;
// Fragment only wants to add this node's SemanticsNode to the parent. // Fragment only wants to add this node's SemanticsNode to the parent.
...@@ -3247,94 +3235,70 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im ...@@ -3247,94 +3235,70 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im
bool dropSemanticsOfPreviousSiblings = config.isBlockingSemanticsOfPreviouslyPaintedNodes; bool dropSemanticsOfPreviousSiblings = config.isBlockingSemanticsOfPreviouslyPaintedNodes;
final bool producesForkingFragment = !config.hasBeenAnnotated && !config.isSemanticBoundary; final bool producesForkingFragment = !config.hasBeenAnnotated && !config.isSemanticBoundary;
final List<_InterestingSemanticsFragment> fragments = <_InterestingSemanticsFragment>[];
final Set<_InterestingSemanticsFragment> toBeMarkedExplicit = <_InterestingSemanticsFragment>{};
final bool childrenMergeIntoParent = mergeIntoParent || config.isMergingSemanticsOfDescendants; final bool childrenMergeIntoParent = mergeIntoParent || config.isMergingSemanticsOfDescendants;
final List<SemanticsConfiguration> childConfigurations = <SemanticsConfiguration>[];
final bool explicitChildNode = config.explicitChildNodes || parent is! RenderObject;
final bool hasChildConfigurationsDelegate = config.childConfigurationsDelegate != null;
final Map<SemanticsConfiguration, _InterestingSemanticsFragment> configToFragment = <SemanticsConfiguration, _InterestingSemanticsFragment>{};
final List<_InterestingSemanticsFragment> mergeUpFragments = <_InterestingSemanticsFragment>[];
final List<List<_InterestingSemanticsFragment>> siblingMergeFragmentGroups = <List<_InterestingSemanticsFragment>>[];
visitChildrenForSemantics((RenderObject renderChild) { visitChildrenForSemantics((RenderObject renderChild) {
assert(!_needsLayout); assert(!_needsLayout);
final _SemanticsFragment parentFragment = renderChild._getSemanticsForParent( final _SemanticsFragment parentFragment = renderChild._getSemanticsForParent(
mergeIntoParent: childrenMergeIntoParent, mergeIntoParent: childrenMergeIntoParent,
); );
if (parentFragment.dropsSemanticsOfPreviousSiblings) { if (parentFragment.dropsSemanticsOfPreviousSiblings) {
childConfigurations.clear(); fragments.clear();
mergeUpFragments.clear(); toBeMarkedExplicit.clear();
siblingMergeFragmentGroups.clear();
if (!config.isSemanticBoundary) { if (!config.isSemanticBoundary) {
dropSemanticsOfPreviousSiblings = true; 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.addAncestor(this);
fragment.addTags(config.tagsForChildren); fragment.addTags(config.tagsForChildren);
if (hasChildConfigurationsDelegate && fragment.config != null) { if (config.explicitChildNodes || parent is! RenderObject) {
// This fragment need to go through delegate to determine whether it fragment.markAsExplicit();
// merge up or not. continue;
childConfigurations.add(fragment.config!);
configToFragment[fragment.config!] = fragment;
} else {
mergeUpFragments.add(fragment);
} }
} if (!fragment.hasConfigForParent || producesForkingFragment) {
if (parentFragment is _ContainerSemanticsFragment) { continue;
// Container fragments needs to propagate sibling merge group to be }
// compiled by _SwitchableSemanticsFragment. if (!config.isCompatibleWith(fragment.config)) {
for (final List<_InterestingSemanticsFragment> siblingMergeGroup in parentFragment.siblingMergeGroups) { toBeMarkedExplicit.add(fragment);
for (final _InterestingSemanticsFragment siblingMergingFragment in siblingMergeGroup) { }
siblingMergingFragment.addAncestor(this); final int siblingLength = fragments.length - 1;
siblingMergingFragment.addTags(config.tagsForChildren); 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); for (final _InterestingSemanticsFragment fragment in toBeMarkedExplicit) {
fragment.markAsExplicit();
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<SemanticsConfiguration> group in result.siblingMergeGroups) {
siblingMergeFragmentGroups.add(
group.map<_InterestingSemanticsFragment>((SemanticsConfiguration config) => configToFragment[config]!).toList()
);
}
} }
_needsSemanticsUpdate = false; _needsSemanticsUpdate = false;
final _SemanticsFragment result; _SemanticsFragment result;
if (parent is! RenderObject) { if (parent is! RenderObject) {
assert(!config.hasBeenAnnotated); assert(!config.hasBeenAnnotated);
assert(!mergeIntoParent); assert(!mergeIntoParent);
assert(siblingMergeFragmentGroups.isEmpty);
_marksExplicitInMergeGroup(mergeUpFragments, isMergeUp: true);
siblingMergeFragmentGroups.forEach(_marksExplicitInMergeGroup);
result = _RootSemanticsFragment( result = _RootSemanticsFragment(
owner: this, owner: this,
dropsSemanticsOfPreviousSiblings: dropSemanticsOfPreviousSiblings, dropsSemanticsOfPreviousSiblings: dropSemanticsOfPreviousSiblings,
); );
} else if (producesForkingFragment) { } else if (producesForkingFragment) {
result = _ContainerSemanticsFragment( result = _ContainerSemanticsFragment(
siblingMergeGroups: siblingMergeFragmentGroups,
dropsSemanticsOfPreviousSiblings: dropSemanticsOfPreviousSiblings, dropsSemanticsOfPreviousSiblings: dropSemanticsOfPreviousSiblings,
); );
} else { } else {
_marksExplicitInMergeGroup(mergeUpFragments, isMergeUp: true);
siblingMergeFragmentGroups.forEach(_marksExplicitInMergeGroup);
result = _SwitchableSemanticsFragment( result = _SwitchableSemanticsFragment(
config: config, config: config,
mergeIntoParent: mergeIntoParent, mergeIntoParent: mergeIntoParent,
siblingMergeGroups: siblingMergeFragmentGroups,
owner: this, owner: this,
dropsSemanticsOfPreviousSiblings: dropSemanticsOfPreviousSiblings, dropsSemanticsOfPreviousSiblings: dropSemanticsOfPreviousSiblings,
); );
...@@ -3343,32 +3307,10 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im ...@@ -3343,32 +3307,10 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im
fragment.markAsExplicit(); fragment.markAsExplicit();
} }
} }
result.addAll(mergeUpFragments);
return result;
}
void _marksExplicitInMergeGroup(List<_InterestingSemanticsFragment> mergeGroup, {bool isMergeUp = false}) { result.addAll(fragments);
final Set<_InterestingSemanticsFragment> toBeExplicit = <_InterestingSemanticsFragment>{};
for (int i = 0; i < mergeGroup.length; i += 1) { return result;
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();
}
} }
/// Called when collecting the semantics of this node. /// Called when collecting the semantics of this node.
...@@ -4043,9 +3985,8 @@ mixin RelayoutWhenSystemFontsChangeMixin on RenderObject { ...@@ -4043,9 +3985,8 @@ mixin RelayoutWhenSystemFontsChangeMixin on RenderObject {
/// * [_ContainerSemanticsFragment]: a container class to transport the semantic /// * [_ContainerSemanticsFragment]: a container class to transport the semantic
/// information of multiple [_InterestingSemanticsFragment] to a parent. /// information of multiple [_InterestingSemanticsFragment] to a parent.
abstract class _SemanticsFragment { abstract class _SemanticsFragment {
_SemanticsFragment({ _SemanticsFragment({ required this.dropsSemanticsOfPreviousSiblings })
required this.dropsSemanticsOfPreviousSiblings, : assert (dropsSemanticsOfPreviousSiblings != null);
}) : assert (dropsSemanticsOfPreviousSiblings != null);
/// Incorporate the fragments of children into this fragment. /// Incorporate the fragments of children into this fragment.
void addAll(Iterable<_InterestingSemanticsFragment> fragments); void addAll(Iterable<_InterestingSemanticsFragment> fragments);
...@@ -4061,29 +4002,25 @@ abstract class _SemanticsFragment { ...@@ -4061,29 +4002,25 @@ abstract class _SemanticsFragment {
/// Returns [_InterestingSemanticsFragment] describing the actual semantic /// Returns [_InterestingSemanticsFragment] describing the actual semantic
/// information that this fragment wants to add to the parent. /// 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 /// A container used when a [RenderObject] wants to add multiple independent
/// [_InterestingSemanticsFragment] to its parent. /// [_InterestingSemanticsFragment] to its parent.
/// ///
/// The [_InterestingSemanticsFragment] to be added to the parent can be /// The [_InterestingSemanticsFragment] to be added to the parent can be
/// obtained via [mergeUpFragments]. /// obtained via [interestingFragments].
class _ContainerSemanticsFragment extends _SemanticsFragment { class _ContainerSemanticsFragment extends _SemanticsFragment {
_ContainerSemanticsFragment({
required super.dropsSemanticsOfPreviousSiblings,
required this.siblingMergeGroups,
});
final List<List<_InterestingSemanticsFragment>> siblingMergeGroups; _ContainerSemanticsFragment({ required super.dropsSemanticsOfPreviousSiblings });
@override @override
void addAll(Iterable<_InterestingSemanticsFragment> fragments) { void addAll(Iterable<_InterestingSemanticsFragment> fragments) {
mergeUpFragments.addAll(fragments); interestingFragments.addAll(fragments);
} }
@override @override
final List<_InterestingSemanticsFragment> mergeUpFragments = <_InterestingSemanticsFragment>[]; final List<_InterestingSemanticsFragment> interestingFragments = <_InterestingSemanticsFragment>[];
} }
/// A [_SemanticsFragment] that describes which concrete semantic information /// A [_SemanticsFragment] that describes which concrete semantic information
...@@ -4120,7 +4057,6 @@ abstract class _InterestingSemanticsFragment extends _SemanticsFragment { ...@@ -4120,7 +4057,6 @@ abstract class _InterestingSemanticsFragment extends _SemanticsFragment {
required Rect? parentPaintClipRect, required Rect? parentPaintClipRect,
required double elevationAdjustment, required double elevationAdjustment,
required List<SemanticsNode> result, required List<SemanticsNode> result,
required List<SemanticsNode> siblingNodes,
}); });
/// The [SemanticsConfiguration] the child wants to merge into the parent's /// The [SemanticsConfiguration] the child wants to merge into the parent's
...@@ -4150,7 +4086,7 @@ abstract class _InterestingSemanticsFragment extends _SemanticsFragment { ...@@ -4150,7 +4086,7 @@ abstract class _InterestingSemanticsFragment extends _SemanticsFragment {
bool get hasConfigForParent => config != null; bool get hasConfigForParent => config != null;
@override @override
List<_InterestingSemanticsFragment> get mergeUpFragments => <_InterestingSemanticsFragment>[this]; List<_InterestingSemanticsFragment> get interestingFragments => <_InterestingSemanticsFragment>[this];
Set<SemanticsTag>? _tagsForChildren; Set<SemanticsTag>? _tagsForChildren;
...@@ -4188,13 +4124,7 @@ class _RootSemanticsFragment extends _InterestingSemanticsFragment { ...@@ -4188,13 +4124,7 @@ class _RootSemanticsFragment extends _InterestingSemanticsFragment {
}); });
@override @override
void compileChildren({ void compileChildren({ Rect? parentSemanticsClipRect, Rect? parentPaintClipRect, required double elevationAdjustment, required List<SemanticsNode> result }) {
Rect? parentSemanticsClipRect,
Rect? parentPaintClipRect,
required double elevationAdjustment,
required List<SemanticsNode> result,
required List<SemanticsNode> siblingNodes,
}) {
assert(_tagsForChildren == null || _tagsForChildren!.isEmpty); assert(_tagsForChildren == null || _tagsForChildren!.isEmpty);
assert(parentSemanticsClipRect == null); assert(parentSemanticsClipRect == null);
assert(parentPaintClipRect == null); assert(parentPaintClipRect == null);
...@@ -4220,11 +4150,8 @@ class _RootSemanticsFragment extends _InterestingSemanticsFragment { ...@@ -4220,11 +4150,8 @@ class _RootSemanticsFragment extends _InterestingSemanticsFragment {
parentPaintClipRect: parentPaintClipRect, parentPaintClipRect: parentPaintClipRect,
elevationAdjustment: 0.0, elevationAdjustment: 0.0,
result: children, 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); node.updateWith(config: null, childrenInInversePaintOrder: children);
// The root node is the only semantics node allowed to be invisible. This // The root node is the only semantics node allowed to be invisible. This
...@@ -4274,11 +4201,9 @@ class _SwitchableSemanticsFragment extends _InterestingSemanticsFragment { ...@@ -4274,11 +4201,9 @@ class _SwitchableSemanticsFragment extends _InterestingSemanticsFragment {
_SwitchableSemanticsFragment({ _SwitchableSemanticsFragment({
required bool mergeIntoParent, required bool mergeIntoParent,
required SemanticsConfiguration config, required SemanticsConfiguration config,
required List<List<_InterestingSemanticsFragment>> siblingMergeGroups,
required super.owner, required super.owner,
required super.dropsSemanticsOfPreviousSiblings, required super.dropsSemanticsOfPreviousSiblings,
}) : _siblingMergeGroups = siblingMergeGroups, }) : _mergeIntoParent = mergeIntoParent,
_mergeIntoParent = mergeIntoParent,
_config = config, _config = config,
assert(mergeIntoParent != null), assert(mergeIntoParent != null),
assert(config != null); assert(config != null);
...@@ -4286,126 +4211,14 @@ class _SwitchableSemanticsFragment extends _InterestingSemanticsFragment { ...@@ -4286,126 +4211,14 @@ class _SwitchableSemanticsFragment extends _InterestingSemanticsFragment {
final bool _mergeIntoParent; final bool _mergeIntoParent;
SemanticsConfiguration _config; SemanticsConfiguration _config;
bool _isConfigWritable = false; bool _isConfigWritable = false;
bool _mergesToSibling = false;
final List<List<_InterestingSemanticsFragment>> _siblingMergeGroups;
void _mergeSiblingGroup(Rect? parentSemanticsClipRect, Rect? parentPaintClipRect, List<SemanticsNode> result, Set<int> 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<SemanticsTag> tags = <SemanticsTag>{};
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>[]; final List<_InterestingSemanticsFragment> _children = <_InterestingSemanticsFragment>[];
@override @override
void compileChildren({ void compileChildren({ Rect? parentSemanticsClipRect, Rect? parentPaintClipRect, required double elevationAdjustment, required List<SemanticsNode> result }) {
Rect? parentSemanticsClipRect,
Rect? parentPaintClipRect,
required double elevationAdjustment,
required List<SemanticsNode> result,
required List<SemanticsNode> siblingNodes,
}) {
final Set<int> usedSemanticsIds = <int>{};
Iterable<_InterestingSemanticsFragment> compilingFragments = _children;
for (final List<_InterestingSemanticsFragment> siblingGroup in _siblingMergeGroups) {
compilingFragments = compilingFragments.followedBy(siblingGroup);
}
if (!_isExplicit) { if (!_isExplicit) {
if (!_mergesToSibling) { owner._semantics = null;
owner._semantics = null; for (final _InterestingSemanticsFragment fragment in _children) {
}
_mergeSiblingGroup(
parentSemanticsClipRect,
parentPaintClipRect,
siblingNodes,
usedSemanticsIds,
);
for (final _InterestingSemanticsFragment fragment in compilingFragments) {
assert(_ancestorChain.first == fragment._ancestorChain.last); 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._ancestorChain.addAll(_ancestorChain.skip(1));
fragment.compileChildren( fragment.compileChildren(
parentSemanticsClipRect: parentSemanticsClipRect, parentSemanticsClipRect: parentSemanticsClipRect,
...@@ -4415,16 +4228,14 @@ class _SwitchableSemanticsFragment extends _InterestingSemanticsFragment { ...@@ -4415,16 +4228,14 @@ class _SwitchableSemanticsFragment extends _InterestingSemanticsFragment {
// its children are placed at the elevation dictated by this config. // its children are placed at the elevation dictated by this config.
elevationAdjustment: elevationAdjustment + _config.elevation, elevationAdjustment: elevationAdjustment + _config.elevation,
result: result, result: result,
siblingNodes: siblingNodes,
); );
} }
return; return;
} }
final _SemanticsGeometry? geometry = _computeSemanticsGeometry( final _SemanticsGeometry? geometry = _needsGeometryUpdate
parentSemanticsClipRect: parentSemanticsClipRect, ? _SemanticsGeometry(parentSemanticsClipRect: parentSemanticsClipRect, parentPaintClipRect: parentPaintClipRect, ancestors: _ancestorChain)
parentPaintClipRect: parentPaintClipRect, : null;
);
if (!_mergeIntoParent && (geometry?.dropFromTree ?? false)) { if (!_mergeIntoParent && (geometry?.dropFromTree ?? false)) {
return; // Drop the node, it's not going to be visible. return; // Drop the node, it's not going to be visible.
...@@ -4453,66 +4264,22 @@ class _SwitchableSemanticsFragment extends _InterestingSemanticsFragment { ...@@ -4453,66 +4264,22 @@ class _SwitchableSemanticsFragment extends _InterestingSemanticsFragment {
_config.isHidden = true; _config.isHidden = true;
} }
} }
final List<SemanticsNode> children = <SemanticsNode>[]; final List<SemanticsNode> children = <SemanticsNode>[];
_mergeSiblingGroup( for (final _InterestingSemanticsFragment fragment in _children) {
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<SemanticsNode> childSiblingNodes = <SemanticsNode>[];
fragment.compileChildren( fragment.compileChildren(
parentSemanticsClipRect: node.parentSemanticsClipRect, parentSemanticsClipRect: node.parentSemanticsClipRect,
parentPaintClipRect: node.parentPaintClipRect, parentPaintClipRect: node.parentPaintClipRect,
elevationAdjustment: 0.0, elevationAdjustment: 0.0,
result: children, result: children,
siblingNodes: childSiblingNodes,
); );
siblingNodes.addAll(childSiblingNodes);
} }
if (_config.isSemanticBoundary) { if (_config.isSemanticBoundary) {
owner.assembleSemanticsNode(node, _config, children); owner.assembleSemanticsNode(node, _config, children);
} else { } else {
node.updateWith(config: _config, childrenInInversePaintOrder: children); node.updateWith(config: _config, childrenInInversePaintOrder: children);
} }
result.add(node); 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 ??= <SemanticsTag>{};
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 @override
......
...@@ -6,7 +6,6 @@ import 'dart:math' as math; ...@@ -6,7 +6,6 @@ import 'dart:math' as math;
import 'dart:ui' as ui; import 'dart:ui' as ui;
import 'dart:ui' show Offset, Rect, SemanticsAction, SemanticsFlag, StringAttribute, TextDirection; import 'dart:ui' show Offset, Rect, SemanticsAction, SemanticsFlag, StringAttribute, TextDirection;
import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/painting.dart' show MatrixUtils, TransformProperty; import 'package:flutter/painting.dart' show MatrixUtils, TransformProperty;
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
...@@ -54,20 +53,6 @@ typedef SemanticsActionHandler = void Function(Object? args); ...@@ -54,20 +53,6 @@ typedef SemanticsActionHandler = void Function(Object? args);
/// Used by [SemanticsOwner.onSemanticsUpdate]. /// Used by [SemanticsOwner.onSemanticsUpdate].
typedef SemanticsUpdateCallback = void Function(ui.SemanticsUpdate update); 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<SemanticsConfiguration>);
/// A tag for a [SemanticsNode]. /// A tag for a [SemanticsNode].
/// ///
/// Tags can be interpreted by the parent of a [SemanticsNode] /// Tags can be interpreted by the parent of a [SemanticsNode]
...@@ -100,89 +85,6 @@ class SemanticsTag { ...@@ -100,89 +85,6 @@ class SemanticsTag {
String toString() => '${objectRuntimeType(this, 'SemanticsTag')}($name)'; 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<SemanticsConfiguration> 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<List<SemanticsConfiguration>> 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<SemanticsConfiguration> _mergeUp = <SemanticsConfiguration>[];
final List<List<SemanticsConfiguration>> _siblingMergeGroups = <List<SemanticsConfiguration>>[];
/// 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<SemanticsConfiguration> configs) => _siblingMergeGroups.add(configs);
/// Builds a [ChildSemanticsConfigurationsResult] contains the arrangement.
ChildSemanticsConfigurationsResult build() {
assert((){
final Set<SemanticsConfiguration> seenConfigs = <SemanticsConfiguration>{};
for (final SemanticsConfiguration config in <SemanticsConfiguration>[..._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. /// An identifier of a custom semantics action.
/// ///
/// Custom semantics actions can be provided to make complex user /// Custom semantics actions can be provided to make complex user
...@@ -3822,25 +3724,6 @@ class SemanticsConfiguration { ...@@ -3822,25 +3724,6 @@ class SemanticsConfiguration {
_onDidLoseAccessibilityFocus = value; _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 /// Returns the action handler registered for [action] or null if none was
/// registered. /// registered.
SemanticsActionHandler? getActionHandler(SemanticsAction action) => _actions[action]; SemanticsActionHandler? getActionHandler(SemanticsAction action) => _actions[action];
...@@ -4565,11 +4448,6 @@ class SemanticsConfiguration { ...@@ -4565,11 +4448,6 @@ class SemanticsConfiguration {
/// * [addTagForChildren] to add a tag and for more information about their /// * [addTagForChildren] to add a tag and for more information about their
/// usage. /// usage.
Iterable<SemanticsTag>? get tagsForChildren => _tagsForChildren; Iterable<SemanticsTag>? 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<SemanticsTag>? _tagsForChildren; Set<SemanticsTag>? _tagsForChildren;
/// Specifies a [SemanticsTag] that this configuration wants to apply to all /// Specifies a [SemanticsTag] that this configuration wants to apply to all
......
...@@ -4375,47 +4375,6 @@ void main() { ...@@ -4375,47 +4375,6 @@ void main() {
expect(prefixText.style, prefixStyle); 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>[
TestSemantics.rootChild(
id: 2,
textDirection: TextDirection.ltr,
label: 'Prefix',
),
TestSemantics.rootChild(
id: 1,
textDirection: TextDirection.ltr,
value: 'some text',
actions: <SemanticsAction>[
SemanticsAction.tap,
],
flags: <SemanticsFlag>[
SemanticsFlag.isTextField,
],
),
TestSemantics.rootChild(
id: 3,
textDirection: TextDirection.ltr,
label: 'Suffix',
),
],
), ignoreTransform: true, ignoreRect: true));
});
testWidgets('TextField with specified suffixStyle', (WidgetTester tester) async { testWidgets('TextField with specified suffixStyle', (WidgetTester tester) async {
final TextStyle suffixStyle = TextStyle( final TextStyle suffixStyle = TextStyle(
color: Colors.pink[500], color: Colors.pink[500],
......
...@@ -1429,51 +1429,6 @@ void main() { ...@@ -1429,51 +1429,6 @@ void main() {
handle.dispose(); 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 <Widget>[
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 { testWidgets('Scroll inertia cancel event', (WidgetTester tester) async {
await pumpTest(tester, null); await pumpTest(tester, null);
await tester.fling(find.byType(Scrollable), const Offset(0.0, -dragOffset), 1000.0); await tester.fling(find.byType(Scrollable), const Offset(0.0, -dragOffset), 1000.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<SemanticsConfiguration> configs) {
expect(configs.length, 3);
final ChildSemanticsConfigurationsResultBuilder builder = ChildSemanticsConfigurationsResultBuilder();
final List<SemanticsConfiguration> sibling = <SemanticsConfiguration>[];
// 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: <Widget>[
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>[
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<SemanticsConfiguration> 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: <Widget>[
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>[
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<SemanticsConfiguration> 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: <Widget>[
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<SemanticsConfiguration> configs) {
final ChildSemanticsConfigurationsResultBuilder builder = ChildSemanticsConfigurationsResultBuilder();
// Marks the same one twice.
builder.markAsMergeUp(configs.first);
builder.markAsSiblingMergeGroup(<SemanticsConfiguration>[configs.first]);
return builder.build();
}
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Semantics(
label: 'parent',
child: TestConfigDelegate(
delegate: delegate,
child: Column(
children: <Widget>[
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;
}
}
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