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
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
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<InputDecorator> 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<InputDecorator> 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<InputDecorator> with TickerProviderStat
color: _getPrefixIconColor(themeData, defaults),
size: iconSize,
),
child: Semantics(
child: decoration.prefixIcon,
),
child: decoration.prefixIcon!,
),
),
),
......@@ -2361,9 +2297,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
color: _getSuffixIconColor(themeData, defaults),
size: iconSize,
),
child: Semantics(
child: decoration.suffixIcon,
),
child: decoration.suffixIcon!,
),
),
),
......@@ -2440,7 +2374,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
isDense: decoration.isDense,
visualDensity: themeData.visualDensity,
icon: icon,
input: input,
input: widget.child,
label: label,
hint: hint,
prefix: prefix,
......
......@@ -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<SemanticsConfiguration>);
/// 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<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.
///
/// 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<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;
/// Specifies a [SemanticsTag] that this configuration wants to apply to all
......
......@@ -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>[
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 {
final TextStyle suffixStyle = TextStyle(
color: Colors.pink[500],
......
......@@ -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 <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 {
await pumpTest(tester, null);
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