1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'dart:math' as math;
6import 'dart:ui' show Offset, Rect, SemanticsAction, SemanticsFlag, SemanticsUpdate, SemanticsUpdateBuilder, StringAttribute, TextDirection;
7
8import 'package:collection/collection.dart';
9import 'package:flutter/foundation.dart';
10import 'package:flutter/painting.dart' show MatrixUtils, TransformProperty;
11import 'package:flutter/services.dart';
12import 'package:vector_math/vector_math_64.dart';
13
14import 'binding.dart' show SemanticsBinding;
15import 'semantics_event.dart';
16
17export 'dart:ui' show Offset, Rect, SemanticsAction, SemanticsFlag, StringAttribute, TextDirection, VoidCallback;
18
19export 'package:flutter/foundation.dart' show DiagnosticLevel, DiagnosticPropertiesBuilder, DiagnosticsNode, DiagnosticsTreeStyle, Key, TextTreeConfiguration;
20export 'package:flutter/services.dart' show TextSelection;
21export 'package:vector_math/vector_math_64.dart' show Matrix4;
22
23export 'semantics_event.dart' show SemanticsEvent;
24
25/// Signature for a function that is called for each [SemanticsNode].
26///
27/// Return false to stop visiting nodes.
28///
29/// Used by [SemanticsNode.visitChildren].
30typedef SemanticsNodeVisitor = bool Function(SemanticsNode node);
31
32/// Signature for [SemanticsAction]s that move the cursor.
33///
34/// If `extendSelection` is set to true the cursor movement should extend the
35/// current selection or (if nothing is currently selected) start a selection.
36typedef MoveCursorHandler = void Function(bool extendSelection);
37
38/// Signature for the [SemanticsAction.setSelection] handlers to change the
39/// text selection (or re-position the cursor) to `selection`.
40typedef SetSelectionHandler = void Function(TextSelection selection);
41
42/// Signature for the [SemanticsAction.setText] handlers to replace the
43/// current text with the input `text`.
44typedef SetTextHandler = void Function(String text);
45
46/// Signature for a handler of a [SemanticsAction].
47///
48/// Returned by [SemanticsConfiguration.getActionHandler].
49typedef SemanticsActionHandler = void Function(Object? args);
50
51/// Signature for a function that receives a semantics update and returns no result.
52///
53/// Used by [SemanticsOwner.onSemanticsUpdate].
54typedef SemanticsUpdateCallback = void Function(SemanticsUpdate update);
55
56/// Signature for the [SemanticsConfiguration.childConfigurationsDelegate].
57///
58/// The input list contains all [SemanticsConfiguration]s that rendering
59/// children want to merge upward. One can tag a render child with a
60/// [SemanticsTag] and look up its [SemanticsConfiguration]s through
61/// [SemanticsConfiguration.tagsChildrenWith].
62///
63/// The return value is the arrangement of these configs, including which
64/// configs continue to merge upward and which configs form sibling merge group.
65///
66/// Use [ChildSemanticsConfigurationsResultBuilder] to generate the return
67/// value.
68typedef ChildSemanticsConfigurationsDelegate = ChildSemanticsConfigurationsResult Function(List<SemanticsConfiguration>);
69
70final int _kUnblockedUserActions = SemanticsAction.didGainAccessibilityFocus.index
71 | SemanticsAction.didLoseAccessibilityFocus.index;
72
73/// A tag for a [SemanticsNode].
74///
75/// Tags can be interpreted by the parent of a [SemanticsNode]
76/// and depending on the presence of a tag the parent can for example decide
77/// how to add the tagged node as a child. Tags are not sent to the engine.
78///
79/// As an example, the [RenderSemanticsGestureHandler] uses tags to determine
80/// if a child node should be excluded from the scrollable area for semantic
81/// purposes.
82///
83/// The provided [name] is only used for debugging. Two tags created with the
84/// same [name] and the `new` operator are not considered identical. However,
85/// two tags created with the same [name] and the `const` operator are always
86/// identical.
87class SemanticsTag {
88 /// Creates a [SemanticsTag].
89 ///
90 /// The provided [name] is only used for debugging. Two tags created with the
91 /// same [name] and the `new` operator are not considered identical. However,
92 /// two tags created with the same [name] and the `const` operator are always
93 /// identical.
94 const SemanticsTag(this.name);
95
96 /// A human-readable name for this tag used for debugging.
97 ///
98 /// This string is not used to determine if two tags are identical.
99 final String name;
100
101 @override
102 String toString() => '${objectRuntimeType(this, 'SemanticsTag')}($name)';
103}
104
105/// The result that contains the arrangement for the child
106/// [SemanticsConfiguration]s.
107///
108/// When the [PipelineOwner] builds the semantics tree, it uses the returned
109/// [ChildSemanticsConfigurationsResult] from
110/// [SemanticsConfiguration.childConfigurationsDelegate] to decide how semantics nodes
111/// should form.
112///
113/// Use [ChildSemanticsConfigurationsResultBuilder] to build the result.
114class ChildSemanticsConfigurationsResult {
115 ChildSemanticsConfigurationsResult._(this.mergeUp, this.siblingMergeGroups);
116
117 /// Returns the [SemanticsConfiguration]s that are supposed to be merged into
118 /// the parent semantics node.
119 ///
120 /// [SemanticsConfiguration]s that are either semantics boundaries or are
121 /// conflicting with other [SemanticsConfiguration]s will form explicit
122 /// semantics nodes. All others will be merged into the parent.
123 final List<SemanticsConfiguration> mergeUp;
124
125 /// The groups of child semantics configurations that want to merge together
126 /// and form a sibling [SemanticsNode].
127 ///
128 /// All the [SemanticsConfiguration]s in a given group that are either
129 /// semantics boundaries or are conflicting with other
130 /// [SemanticsConfiguration]s of the same group will be excluded from the
131 /// sibling merge group and form independent semantics nodes as usual.
132 ///
133 /// The result [SemanticsNode]s from the merges are attached as the sibling
134 /// nodes of the immediate parent semantics node. For example, a `RenderObjectA`
135 /// has a rendering child, `RenderObjectB`. If both of them form their own
136 /// semantics nodes, `SemanticsNodeA` and `SemanticsNodeB`, any semantics node
137 /// created from sibling merge groups of `RenderObjectB` will be attach to
138 /// `SemanticsNodeA` as a sibling of `SemanticsNodeB`.
139 final List<List<SemanticsConfiguration>> siblingMergeGroups;
140}
141
142/// The builder to build a [ChildSemanticsConfigurationsResult] based on its
143/// annotations.
144///
145/// To use this builder, one can use [markAsMergeUp] and
146/// [markAsSiblingMergeGroup] to annotate the arrangement of
147/// [SemanticsConfiguration]s. Once all the configs are annotated, use [build]
148/// to generate the [ChildSemanticsConfigurationsResult].
149class ChildSemanticsConfigurationsResultBuilder {
150 /// Creates a [ChildSemanticsConfigurationsResultBuilder].
151 ChildSemanticsConfigurationsResultBuilder();
152
153 final List<SemanticsConfiguration> _mergeUp = <SemanticsConfiguration>[];
154 final List<List<SemanticsConfiguration>> _siblingMergeGroups = <List<SemanticsConfiguration>>[];
155
156 /// Marks the [SemanticsConfiguration] to be merged into the parent semantics
157 /// node.
158 ///
159 /// The [SemanticsConfiguration] will be added to the
160 /// [ChildSemanticsConfigurationsResult.mergeUp] that this builder builds.
161 void markAsMergeUp(SemanticsConfiguration config) => _mergeUp.add(config);
162
163 /// Marks a group of [SemanticsConfiguration]s to merge together
164 /// and form a sibling [SemanticsNode].
165 ///
166 /// The group of [SemanticsConfiguration]s will be added to the
167 /// [ChildSemanticsConfigurationsResult.siblingMergeGroups] that this builder builds.
168 void markAsSiblingMergeGroup(List<SemanticsConfiguration> configs) => _siblingMergeGroups.add(configs);
169
170 /// Builds a [ChildSemanticsConfigurationsResult] contains the arrangement.
171 ChildSemanticsConfigurationsResult build() {
172 assert((){
173 final Set<SemanticsConfiguration> seenConfigs = <SemanticsConfiguration>{};
174 for (final SemanticsConfiguration config in <SemanticsConfiguration>[..._mergeUp, ..._siblingMergeGroups.flattened]) {
175 assert(
176 seenConfigs.add(config),
177 'Duplicated SemanticsConfigurations. This can happen if the same '
178 'SemanticsConfiguration was marked twice in markAsMergeUp and/or '
179 'markAsSiblingMergeGroup'
180 );
181 }
182 return true;
183 }());
184 return ChildSemanticsConfigurationsResult._(_mergeUp, _siblingMergeGroups);
185 }
186}
187
188/// An identifier of a custom semantics action.
189///
190/// Custom semantics actions can be provided to make complex user
191/// interactions more accessible. For instance, if an application has a
192/// drag-and-drop list that requires the user to press and hold an item
193/// to move it, users interacting with the application using a hardware
194/// switch may have difficulty. This can be made accessible by creating custom
195/// actions and pairing them with handlers that move a list item up or down in
196/// the list.
197///
198/// In Android, these actions are presented in the local context menu. In iOS,
199/// these are presented in the radial context menu.
200///
201/// Localization and text direction do not automatically apply to the provided
202/// label or hint.
203///
204/// Instances of this class should either be instantiated with const or
205/// new instances cached in static fields.
206///
207/// See also:
208///
209/// * [SemanticsProperties], where the handler for a custom action is provided.
210@immutable
211class CustomSemanticsAction {
212 /// Creates a new [CustomSemanticsAction].
213 ///
214 /// The [label] must not be empty.
215 const CustomSemanticsAction({required String this.label})
216 : assert(label != ''),
217 hint = null,
218 action = null;
219
220 /// Creates a new [CustomSemanticsAction] that overrides a standard semantics
221 /// action.
222 ///
223 /// The [hint] must not be empty.
224 const CustomSemanticsAction.overridingAction({required String this.hint, required SemanticsAction this.action})
225 : assert(hint != ''),
226 label = null;
227
228 /// The user readable name of this custom semantics action.
229 final String? label;
230
231 /// The hint description of this custom semantics action.
232 final String? hint;
233
234 /// The standard semantics action this action replaces.
235 final SemanticsAction? action;
236
237 @override
238 int get hashCode => Object.hash(label, hint, action);
239
240 @override
241 bool operator ==(Object other) {
242 if (other.runtimeType != runtimeType) {
243 return false;
244 }
245 return other is CustomSemanticsAction
246 && other.label == label
247 && other.hint == hint
248 && other.action == action;
249 }
250
251 @override
252 String toString() {
253 return 'CustomSemanticsAction(${_ids[this]}, label:$label, hint:$hint, action:$action)';
254 }
255
256 // Logic to assign a unique id to each custom action without requiring
257 // user specification.
258 static int _nextId = 0;
259 static final Map<int, CustomSemanticsAction> _actions = <int, CustomSemanticsAction>{};
260 static final Map<CustomSemanticsAction, int> _ids = <CustomSemanticsAction, int>{};
261
262 /// Get the identifier for a given `action`.
263 static int getIdentifier(CustomSemanticsAction action) {
264 int? result = _ids[action];
265 if (result == null) {
266 result = _nextId++;
267 _ids[action] = result;
268 _actions[result] = action;
269 }
270 return result;
271 }
272
273 /// Get the `action` for a given identifier.
274 static CustomSemanticsAction? getAction(int id) {
275 return _actions[id];
276 }
277
278 /// Resets internal state between tests. Does nothing if asserts are disabled.
279 @visibleForTesting
280 static void resetForTests() {
281 assert(() {
282 _actions.clear();
283 _ids.clear();
284 _nextId = 0;
285 return true;
286 }());
287 }
288}
289
290/// A string that carries a list of [StringAttribute]s.
291@immutable
292class AttributedString {
293 /// Creates a attributed string.
294 ///
295 /// The [TextRange] in the [attributes] must be inside the length of the
296 /// [string].
297 ///
298 /// The [attributes] must not be changed after the attributed string is
299 /// created.
300 AttributedString(
301 this.string, {
302 this.attributes = const <StringAttribute>[],
303 }) : assert(string.isNotEmpty || attributes.isEmpty),
304 assert(() {
305 for (final StringAttribute attribute in attributes) {
306 assert(
307 string.length >= attribute.range.start &&
308 string.length >= attribute.range.end,
309 'The range in $attribute is outside of the string $string',
310 );
311 }
312 return true;
313 }());
314
315 /// The plain string stored in the attributed string.
316 final String string;
317
318 /// The attributes this string carries.
319 ///
320 /// The list must not be modified after this string is created.
321 final List<StringAttribute> attributes;
322
323 /// Returns a new [AttributedString] by concatenate the operands
324 ///
325 /// The string attribute list of the returned [AttributedString] will contains
326 /// the string attributes from both operands with updated text ranges.
327 AttributedString operator +(AttributedString other) {
328 if (string.isEmpty) {
329 return other;
330 }
331 if (other.string.isEmpty) {
332 return this;
333 }
334
335 // None of the strings is empty.
336 final String newString = string + other.string;
337 final List<StringAttribute> newAttributes = List<StringAttribute>.of(attributes);
338 if (other.attributes.isNotEmpty) {
339 final int offset = string.length;
340 for (final StringAttribute attribute in other.attributes) {
341 final TextRange newRange = TextRange(
342 start: attribute.range.start + offset,
343 end: attribute.range.end + offset,
344 );
345 final StringAttribute adjustedAttribute = attribute.copy(range: newRange);
346 newAttributes.add(adjustedAttribute);
347 }
348 }
349 return AttributedString(newString, attributes: newAttributes);
350 }
351
352 /// Two [AttributedString]s are equal if their string and attributes are.
353 @override
354 bool operator ==(Object other) {
355 return other.runtimeType == runtimeType
356 && other is AttributedString
357 && other.string == string
358 && listEquals<StringAttribute>(other.attributes, attributes);
359 }
360
361 @override
362 int get hashCode => Object.hash(string, attributes);
363
364 @override
365 String toString() {
366 return "${objectRuntimeType(this, 'AttributedString')}('$string', attributes: $attributes)";
367 }
368}
369
370/// A [DiagnosticsProperty] for [AttributedString]s, which shows a string
371/// when there are no attributes, and more details otherwise.
372class AttributedStringProperty extends DiagnosticsProperty<AttributedString> {
373 /// Create a diagnostics property for an [AttributedString] object.
374 ///
375 /// Such properties are used with [SemanticsData] objects.
376 AttributedStringProperty(
377 String super.name,
378 super.value, {
379 super.showName,
380 this.showWhenEmpty = false,
381 super.defaultValue,
382 super.level,
383 super.description,
384 });
385
386 /// Whether to show the property when the [value] is an [AttributedString]
387 /// whose [AttributedString.string] is the empty string.
388 ///
389 /// This overrides [defaultValue].
390 final bool showWhenEmpty;
391
392 @override
393 bool get isInteresting => super.isInteresting && (showWhenEmpty || (value != null && value!.string.isNotEmpty));
394
395 @override
396 String valueToString({TextTreeConfiguration? parentConfiguration}) {
397 if (value == null) {
398 return 'null';
399 }
400 String text = value!.string;
401 if (parentConfiguration != null &&
402 !parentConfiguration.lineBreakProperties) {
403 // This follows a similar pattern to StringProperty.
404 text = text.replaceAll('\n', r'\n');
405 }
406 if (value!.attributes.isEmpty) {
407 return '"$text"';
408 }
409 return '"$text" ${value!.attributes}'; // the attributes will be in square brackets since they're a list
410 }
411}
412
413/// Summary information about a [SemanticsNode] object.
414///
415/// A semantics node might [SemanticsNode.mergeAllDescendantsIntoThisNode],
416/// which means the individual fields on the semantics node don't fully describe
417/// the semantics at that node. This data structure contains the full semantics
418/// for the node.
419///
420/// Typically obtained from [SemanticsNode.getSemanticsData].
421@immutable
422class SemanticsData with Diagnosticable {
423 /// Creates a semantics data object.
424 ///
425 /// If [label] is not empty, then [textDirection] must also not be null.
426 SemanticsData({
427 required this.flags,
428 required this.actions,
429 required this.identifier,
430 required this.attributedLabel,
431 required this.attributedValue,
432 required this.attributedIncreasedValue,
433 required this.attributedDecreasedValue,
434 required this.attributedHint,
435 required this.tooltip,
436 required this.textDirection,
437 required this.rect,
438 required this.elevation,
439 required this.thickness,
440 required this.textSelection,
441 required this.scrollIndex,
442 required this.scrollChildCount,
443 required this.scrollPosition,
444 required this.scrollExtentMax,
445 required this.scrollExtentMin,
446 required this.platformViewId,
447 required this.maxValueLength,
448 required this.currentValueLength,
449 this.tags,
450 this.transform,
451 this.customSemanticsActionIds,
452 }) : assert(tooltip == '' || textDirection != null, 'A SemanticsData object with tooltip "$tooltip" had a null textDirection.'),
453 assert(attributedLabel.string == '' || textDirection != null, 'A SemanticsData object with label "${attributedLabel.string}" had a null textDirection.'),
454 assert(attributedValue.string == '' || textDirection != null, 'A SemanticsData object with value "${attributedValue.string}" had a null textDirection.'),
455 assert(attributedDecreasedValue.string == '' || textDirection != null, 'A SemanticsData object with decreasedValue "${attributedDecreasedValue.string}" had a null textDirection.'),
456 assert(attributedIncreasedValue.string == '' || textDirection != null, 'A SemanticsData object with increasedValue "${attributedIncreasedValue.string}" had a null textDirection.'),
457 assert(attributedHint.string == '' || textDirection != null, 'A SemanticsData object with hint "${attributedHint.string}" had a null textDirection.');
458
459 /// A bit field of [SemanticsFlag]s that apply to this node.
460 final int flags;
461
462 /// A bit field of [SemanticsAction]s that apply to this node.
463 final int actions;
464
465 /// {@macro flutter.semantics.SemanticsProperties.identifier}
466 final String identifier;
467
468 /// A textual description for the current label of the node.
469 ///
470 /// The reading direction is given by [textDirection].
471 ///
472 /// This exposes the raw text of the [attributedLabel].
473 String get label => attributedLabel.string;
474
475 /// A textual description for the current label of the node in
476 /// [AttributedString] format.
477 ///
478 /// The reading direction is given by [textDirection].
479 ///
480 /// See also [label], which exposes just the raw text.
481 final AttributedString attributedLabel;
482
483 /// A textual description for the current value of the node.
484 ///
485 /// The reading direction is given by [textDirection].
486 ///
487 /// This exposes the raw text of the [attributedValue].
488 String get value => attributedValue.string;
489
490 /// A textual description for the current value of the node in
491 /// [AttributedString] format.
492 ///
493 /// The reading direction is given by [textDirection].
494 ///
495 /// See also [value], which exposes just the raw text.
496 final AttributedString attributedValue;
497
498 /// The value that [value] will become after performing a
499 /// [SemanticsAction.increase] action.
500 ///
501 /// The reading direction is given by [textDirection].
502 ///
503 /// This exposes the raw text of the [attributedIncreasedValue].
504 String get increasedValue => attributedIncreasedValue.string;
505
506 /// The value that [value] will become after performing a
507 /// [SemanticsAction.increase] action in [AttributedString] format.
508 ///
509 /// The reading direction is given by [textDirection].
510 ///
511 /// See also [increasedValue], which exposes just the raw text.
512 final AttributedString attributedIncreasedValue;
513
514 /// The value that [value] will become after performing a
515 /// [SemanticsAction.decrease] action.
516 ///
517 /// The reading direction is given by [textDirection].
518 ///
519 /// This exposes the raw text of the [attributedDecreasedValue].
520 String get decreasedValue => attributedDecreasedValue.string;
521
522 /// The value that [value] will become after performing a
523 /// [SemanticsAction.decrease] action in [AttributedString] format.
524 ///
525 /// The reading direction is given by [textDirection].
526 ///
527 /// See also [decreasedValue], which exposes just the raw text.
528 final AttributedString attributedDecreasedValue;
529
530 /// A brief description of the result of performing an action on this node.
531 ///
532 /// The reading direction is given by [textDirection].
533 ///
534 /// This exposes the raw text of the [attributedHint].
535 String get hint => attributedHint.string;
536
537 /// A brief description of the result of performing an action on this node
538 /// in [AttributedString] format.
539 ///
540 /// The reading direction is given by [textDirection].
541 ///
542 /// See also [hint], which exposes just the raw text.
543 final AttributedString attributedHint;
544
545 /// A textual description of the widget's tooltip.
546 ///
547 /// The reading direction is given by [textDirection].
548 final String tooltip;
549
550 /// The reading direction for the text in [label], [value],
551 /// [increasedValue], [decreasedValue], and [hint].
552 final TextDirection? textDirection;
553
554 /// The currently selected text (or the position of the cursor) within [value]
555 /// if this node represents a text field.
556 final TextSelection? textSelection;
557
558 /// The total number of scrollable children that contribute to semantics.
559 ///
560 /// If the number of children are unknown or unbounded, this value will be
561 /// null.
562 final int? scrollChildCount;
563
564 /// The index of the first visible semantic child of a scroll node.
565 final int? scrollIndex;
566
567 /// Indicates the current scrolling position in logical pixels if the node is
568 /// scrollable.
569 ///
570 /// The properties [scrollExtentMin] and [scrollExtentMax] indicate the valid
571 /// in-range values for this property. The value for [scrollPosition] may
572 /// (temporarily) be outside that range, e.g. during an overscroll.
573 ///
574 /// See also:
575 ///
576 /// * [ScrollPosition.pixels], from where this value is usually taken.
577 final double? scrollPosition;
578
579 /// Indicates the maximum in-range value for [scrollPosition] if the node is
580 /// scrollable.
581 ///
582 /// This value may be infinity if the scroll is unbound.
583 ///
584 /// See also:
585 ///
586 /// * [ScrollPosition.maxScrollExtent], from where this value is usually taken.
587 final double? scrollExtentMax;
588
589 /// Indicates the minimum in-range value for [scrollPosition] if the node is
590 /// scrollable.
591 ///
592 /// This value may be infinity if the scroll is unbound.
593 ///
594 /// See also:
595 ///
596 /// * [ScrollPosition.minScrollExtent], from where this value is usually taken.
597 final double? scrollExtentMin;
598
599 /// The id of the platform view, whose semantics nodes will be added as
600 /// children to this node.
601 ///
602 /// If this value is non-null, the SemanticsNode must not have any children
603 /// as those would be replaced by the semantics nodes of the referenced
604 /// platform view.
605 ///
606 /// See also:
607 ///
608 /// * [AndroidView], which is the platform view for Android.
609 /// * [UiKitView], which is the platform view for iOS.
610 final int? platformViewId;
611
612 /// The maximum number of characters that can be entered into an editable
613 /// text field.
614 ///
615 /// For the purpose of this function a character is defined as one Unicode
616 /// scalar value.
617 ///
618 /// This should only be set when [SemanticsFlag.isTextField] is set. Defaults
619 /// to null, which means no limit is imposed on the text field.
620 final int? maxValueLength;
621
622 /// The current number of characters that have been entered into an editable
623 /// text field.
624 ///
625 /// For the purpose of this function a character is defined as one Unicode
626 /// scalar value.
627 ///
628 /// This should only be set when [SemanticsFlag.isTextField] is set. This must
629 /// be set when [maxValueLength] is set.
630 final int? currentValueLength;
631
632 /// The bounding box for this node in its coordinate system.
633 final Rect rect;
634
635 /// The set of [SemanticsTag]s associated with this node.
636 final Set<SemanticsTag>? tags;
637
638 /// The transform from this node's coordinate system to its parent's coordinate system.
639 ///
640 /// By default, the transform is null, which represents the identity
641 /// transformation (i.e., that this node has the same coordinate system as its
642 /// parent).
643 final Matrix4? transform;
644
645 /// The elevation of this node relative to the parent semantics node.
646 ///
647 /// See also:
648 ///
649 /// * [SemanticsConfiguration.elevation] for a detailed discussion regarding
650 /// elevation and semantics.
651 final double elevation;
652
653 /// The extent of this node along the z-axis beyond its [elevation]
654 ///
655 /// See also:
656 ///
657 /// * [SemanticsConfiguration.thickness] for a more detailed definition.
658 final double thickness;
659
660 /// The identifiers for the custom semantics actions and standard action
661 /// overrides for this node.
662 ///
663 /// The list must be sorted in increasing order.
664 ///
665 /// See also:
666 ///
667 /// * [CustomSemanticsAction], for an explanation of custom actions.
668 final List<int>? customSemanticsActionIds;
669
670 /// Whether [flags] contains the given flag.
671 bool hasFlag(SemanticsFlag flag) => (flags & flag.index) != 0;
672
673 /// Whether [actions] contains the given action.
674 bool hasAction(SemanticsAction action) => (actions & action.index) != 0;
675
676 @override
677 String toStringShort() => objectRuntimeType(this, 'SemanticsData');
678
679 @override
680 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
681 super.debugFillProperties(properties);
682 properties.add(DiagnosticsProperty<Rect>('rect', rect, showName: false));
683 properties.add(TransformProperty('transform', transform, showName: false, defaultValue: null));
684 properties.add(DoubleProperty('elevation', elevation, defaultValue: 0.0));
685 properties.add(DoubleProperty('thickness', thickness, defaultValue: 0.0));
686 final List<String> actionSummary = <String>[
687 for (final SemanticsAction action in SemanticsAction.values)
688 if ((actions & action.index) != 0)
689 action.name,
690 ];
691 final List<String?> customSemanticsActionSummary = customSemanticsActionIds!
692 .map<String?>((int actionId) => CustomSemanticsAction.getAction(actionId)!.label)
693 .toList();
694 properties.add(IterableProperty<String>('actions', actionSummary, ifEmpty: null));
695 properties.add(IterableProperty<String?>('customActions', customSemanticsActionSummary, ifEmpty: null));
696
697 final List<String> flagSummary = <String>[
698 for (final SemanticsFlag flag in SemanticsFlag.values)
699 if ((flags & flag.index) != 0)
700 flag.name,
701 ];
702 properties.add(IterableProperty<String>('flags', flagSummary, ifEmpty: null));
703 properties.add(StringProperty('identifier', identifier, defaultValue: ''));
704 properties.add(AttributedStringProperty('label', attributedLabel));
705 properties.add(AttributedStringProperty('value', attributedValue));
706 properties.add(AttributedStringProperty('increasedValue', attributedIncreasedValue));
707 properties.add(AttributedStringProperty('decreasedValue', attributedDecreasedValue));
708 properties.add(AttributedStringProperty('hint', attributedHint));
709 properties.add(StringProperty('tooltip', tooltip, defaultValue: ''));
710 properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
711 if (textSelection?.isValid ?? false) {
712 properties.add(MessageProperty('textSelection', '[${textSelection!.start}, ${textSelection!.end}]'));
713 }
714 properties.add(IntProperty('platformViewId', platformViewId, defaultValue: null));
715 properties.add(IntProperty('maxValueLength', maxValueLength, defaultValue: null));
716 properties.add(IntProperty('currentValueLength', currentValueLength, defaultValue: null));
717 properties.add(IntProperty('scrollChildren', scrollChildCount, defaultValue: null));
718 properties.add(IntProperty('scrollIndex', scrollIndex, defaultValue: null));
719 properties.add(DoubleProperty('scrollExtentMin', scrollExtentMin, defaultValue: null));
720 properties.add(DoubleProperty('scrollPosition', scrollPosition, defaultValue: null));
721 properties.add(DoubleProperty('scrollExtentMax', scrollExtentMax, defaultValue: null));
722 }
723
724 @override
725 bool operator ==(Object other) {
726 return other is SemanticsData
727 && other.flags == flags
728 && other.actions == actions
729 && other.identifier == identifier
730 && other.attributedLabel == attributedLabel
731 && other.attributedValue == attributedValue
732 && other.attributedIncreasedValue == attributedIncreasedValue
733 && other.attributedDecreasedValue == attributedDecreasedValue
734 && other.attributedHint == attributedHint
735 && other.tooltip == tooltip
736 && other.textDirection == textDirection
737 && other.rect == rect
738 && setEquals(other.tags, tags)
739 && other.scrollChildCount == scrollChildCount
740 && other.scrollIndex == scrollIndex
741 && other.textSelection == textSelection
742 && other.scrollPosition == scrollPosition
743 && other.scrollExtentMax == scrollExtentMax
744 && other.scrollExtentMin == scrollExtentMin
745 && other.platformViewId == platformViewId
746 && other.maxValueLength == maxValueLength
747 && other.currentValueLength == currentValueLength
748 && other.transform == transform
749 && other.elevation == elevation
750 && other.thickness == thickness
751 && _sortedListsEqual(other.customSemanticsActionIds, customSemanticsActionIds);
752 }
753
754 @override
755 int get hashCode => Object.hash(
756 flags,
757 actions,
758 identifier,
759 attributedLabel,
760 attributedValue,
761 attributedIncreasedValue,
762 attributedDecreasedValue,
763 attributedHint,
764 tooltip,
765 textDirection,
766 rect,
767 tags,
768 textSelection,
769 scrollChildCount,
770 scrollIndex,
771 scrollPosition,
772 scrollExtentMax,
773 scrollExtentMin,
774 platformViewId,
775 Object.hash(
776 maxValueLength,
777 currentValueLength,
778 transform,
779 elevation,
780 thickness,
781 customSemanticsActionIds == null ? null : Object.hashAll(customSemanticsActionIds!),
782 ),
783 );
784
785 static bool _sortedListsEqual(List<int>? left, List<int>? right) {
786 if (left == null && right == null) {
787 return true;
788 }
789 if (left != null && right != null) {
790 if (left.length != right.length) {
791 return false;
792 }
793 for (int i = 0; i < left.length; i++) {
794 if (left[i] != right[i]) {
795 return false;
796 }
797 }
798 return true;
799 }
800 return false;
801 }
802}
803
804class _SemanticsDiagnosticableNode extends DiagnosticableNode<SemanticsNode> {
805 _SemanticsDiagnosticableNode({
806 super.name,
807 required super.value,
808 required super.style,
809 required this.childOrder,
810 });
811
812 final DebugSemanticsDumpOrder childOrder;
813
814 @override
815 List<DiagnosticsNode> getChildren() => value.debugDescribeChildren(childOrder: childOrder);
816}
817
818/// Provides hint values which override the default hints on supported
819/// platforms.
820///
821/// On iOS, these values are always ignored.
822@immutable
823class SemanticsHintOverrides extends DiagnosticableTree {
824 /// Creates a semantics hint overrides.
825 const SemanticsHintOverrides({
826 this.onTapHint,
827 this.onLongPressHint,
828 }) : assert(onTapHint != ''),
829 assert(onLongPressHint != '');
830
831 /// The hint text for a tap action.
832 ///
833 /// If null, the standard hint is used instead.
834 ///
835 /// The hint should describe what happens when a tap occurs, not the
836 /// manner in which a tap is accomplished.
837 ///
838 /// Bad: 'Double tap to show movies'.
839 /// Good: 'show movies'.
840 final String? onTapHint;
841
842 /// The hint text for a long press action.
843 ///
844 /// If null, the standard hint is used instead.
845 ///
846 /// The hint should describe what happens when a long press occurs, not
847 /// the manner in which the long press is accomplished.
848 ///
849 /// Bad: 'Double tap and hold to show tooltip'.
850 /// Good: 'show tooltip'.
851 final String? onLongPressHint;
852
853 /// Whether there are any non-null hint values.
854 bool get isNotEmpty => onTapHint != null || onLongPressHint != null;
855
856 @override
857 int get hashCode => Object.hash(onTapHint, onLongPressHint);
858
859 @override
860 bool operator ==(Object other) {
861 if (other.runtimeType != runtimeType) {
862 return false;
863 }
864 return other is SemanticsHintOverrides
865 && other.onTapHint == onTapHint
866 && other.onLongPressHint == onLongPressHint;
867 }
868
869 @override
870 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
871 super.debugFillProperties(properties);
872 properties.add(StringProperty('onTapHint', onTapHint, defaultValue: null));
873 properties.add(StringProperty('onLongPressHint', onLongPressHint, defaultValue: null));
874 }
875}
876
877/// Contains properties used by assistive technologies to make the application
878/// more accessible.
879///
880/// The properties of this class are used to generate a [SemanticsNode]s in the
881/// semantics tree.
882@immutable
883class SemanticsProperties extends DiagnosticableTree {
884 /// Creates a semantic annotation.
885 const SemanticsProperties({
886 this.enabled,
887 this.checked,
888 this.mixed,
889 this.expanded,
890 this.selected,
891 this.toggled,
892 this.button,
893 this.link,
894 this.header,
895 this.textField,
896 this.slider,
897 this.keyboardKey,
898 this.readOnly,
899 this.focusable,
900 this.focused,
901 this.inMutuallyExclusiveGroup,
902 this.hidden,
903 this.obscured,
904 this.multiline,
905 this.scopesRoute,
906 this.namesRoute,
907 this.image,
908 this.liveRegion,
909 this.maxValueLength,
910 this.currentValueLength,
911 this.identifier,
912 this.label,
913 this.attributedLabel,
914 this.value,
915 this.attributedValue,
916 this.increasedValue,
917 this.attributedIncreasedValue,
918 this.decreasedValue,
919 this.attributedDecreasedValue,
920 this.hint,
921 this.tooltip,
922 this.attributedHint,
923 this.hintOverrides,
924 this.textDirection,
925 this.sortKey,
926 this.tagForChildren,
927 this.onTap,
928 this.onLongPress,
929 this.onScrollLeft,
930 this.onScrollRight,
931 this.onScrollUp,
932 this.onScrollDown,
933 this.onIncrease,
934 this.onDecrease,
935 this.onCopy,
936 this.onCut,
937 this.onPaste,
938 this.onMoveCursorForwardByCharacter,
939 this.onMoveCursorBackwardByCharacter,
940 this.onMoveCursorForwardByWord,
941 this.onMoveCursorBackwardByWord,
942 this.onSetSelection,
943 this.onSetText,
944 this.onDidGainAccessibilityFocus,
945 this.onDidLoseAccessibilityFocus,
946 this.onDismiss,
947 this.customSemanticsActions,
948 }) : assert(label == null || attributedLabel == null, 'Only one of label or attributedLabel should be provided'),
949 assert(value == null || attributedValue == null, 'Only one of value or attributedValue should be provided'),
950 assert(increasedValue == null || attributedIncreasedValue == null, 'Only one of increasedValue or attributedIncreasedValue should be provided'),
951 assert(decreasedValue == null || attributedDecreasedValue == null, 'Only one of decreasedValue or attributedDecreasedValue should be provided'),
952 assert(hint == null || attributedHint == null, 'Only one of hint or attributedHint should be provided');
953
954 /// If non-null, indicates that this subtree represents something that can be
955 /// in an enabled or disabled state.
956 ///
957 /// For example, a button that a user can currently interact with would set
958 /// this field to true. A button that currently does not respond to user
959 /// interactions would set this field to false.
960 final bool? enabled;
961
962 /// If non-null, indicates that this subtree represents a checkbox
963 /// or similar widget with a "checked" state, and what its current
964 /// state is.
965 ///
966 /// When the [Checkbox.value] of a tristate Checkbox is null,
967 /// indicating a mixed-state, this value shall be false, in which
968 /// case, [mixed] will be true.
969 ///
970 /// This is mutually exclusive with [toggled] and [mixed].
971 final bool? checked;
972
973 /// If non-null, indicates that this subtree represents a checkbox
974 /// or similar widget with a "half-checked" state or similar, and
975 /// whether it is currently in this half-checked state.
976 ///
977 /// This must be null when [Checkbox.tristate] is false, or
978 /// when the widget is not a checkbox. When a tristate
979 /// checkbox is fully unchecked/checked, this value shall
980 /// be false.
981 ///
982 /// This is mutually exclusive with [checked] and [toggled].
983 final bool? mixed;
984
985 /// If non-null, indicates that this subtree represents something
986 /// that can be in an "expanded" or "collapsed" state.
987 ///
988 /// For example, if a [SubmenuButton] is opened, this property
989 /// should be set to true; otherwise, this property should be
990 /// false.
991 final bool? expanded;
992
993 /// If non-null, indicates that this subtree represents a toggle switch
994 /// or similar widget with an "on" state, and what its current
995 /// state is.
996 ///
997 /// This is mutually exclusive with [checked] and [mixed].
998 final bool? toggled;
999
1000 /// If non-null indicates that this subtree represents something that can be
1001 /// in a selected or unselected state, and what its current state is.
1002 ///
1003 /// The active tab in a tab bar for example is considered "selected", whereas
1004 /// all other tabs are unselected.
1005 final bool? selected;
1006
1007 /// If non-null, indicates that this subtree represents a button.
1008 ///
1009 /// TalkBack/VoiceOver provides users with the hint "button" when a button
1010 /// is focused.
1011 final bool? button;
1012
1013 /// If non-null, indicates that this subtree represents a link.
1014 ///
1015 /// iOS's VoiceOver provides users with a unique hint when a link is focused.
1016 /// Android's Talkback will announce a link hint the same way it does a
1017 /// button.
1018 final bool? link;
1019
1020 /// If non-null, indicates that this subtree represents a header.
1021 ///
1022 /// A header divides into sections. For example, an address book application
1023 /// might define headers A, B, C, etc. to divide the list of alphabetically
1024 /// sorted contacts into sections.
1025 final bool? header;
1026
1027 /// If non-null, indicates that this subtree represents a text field.
1028 ///
1029 /// TalkBack/VoiceOver provide special affordances to enter text into a
1030 /// text field.
1031 final bool? textField;
1032
1033 /// If non-null, indicates that this subtree represents a slider.
1034 ///
1035 /// Talkback/\VoiceOver provides users with the hint "slider" when a
1036 /// slider is focused.
1037 final bool? slider;
1038
1039 /// If non-null, indicates that this subtree represents a keyboard key.
1040 final bool? keyboardKey;
1041
1042 /// If non-null, indicates that this subtree is read only.
1043 ///
1044 /// Only applicable when [textField] is true.
1045 ///
1046 /// TalkBack/VoiceOver will treat it as non-editable text field.
1047 final bool? readOnly;
1048
1049 /// If non-null, whether the node is able to hold input focus.
1050 ///
1051 /// If [focusable] is set to false, then [focused] must not be true.
1052 ///
1053 /// Input focus indicates that the node will receive keyboard events. It is not
1054 /// to be confused with accessibility focus. Accessibility focus is the
1055 /// green/black rectangular highlight that TalkBack/VoiceOver draws around the
1056 /// element it is reading, and is separate from input focus.
1057 final bool? focusable;
1058
1059 /// If non-null, whether the node currently holds input focus.
1060 ///
1061 /// At most one node in the tree should hold input focus at any point in time,
1062 /// and it should not be set to true if [focusable] is false.
1063 ///
1064 /// Input focus indicates that the node will receive keyboard events. It is not
1065 /// to be confused with accessibility focus. Accessibility focus is the
1066 /// green/black rectangular highlight that TalkBack/VoiceOver draws around the
1067 /// element it is reading, and is separate from input focus.
1068 final bool? focused;
1069
1070 /// If non-null, whether a semantic node is in a mutually exclusive group.
1071 ///
1072 /// For example, a radio button is in a mutually exclusive group because only
1073 /// one radio button in that group can be marked as [checked].
1074 final bool? inMutuallyExclusiveGroup;
1075
1076 /// If non-null, whether the node is considered hidden.
1077 ///
1078 /// Hidden elements are currently not visible on screen. They may be covered
1079 /// by other elements or positioned outside of the visible area of a viewport.
1080 ///
1081 /// Hidden elements cannot gain accessibility focus though regular touch. The
1082 /// only way they can be focused is by moving the focus to them via linear
1083 /// navigation.
1084 ///
1085 /// Platforms are free to completely ignore hidden elements and new platforms
1086 /// are encouraged to do so.
1087 ///
1088 /// Instead of marking an element as hidden it should usually be excluded from
1089 /// the semantics tree altogether. Hidden elements are only included in the
1090 /// semantics tree to work around platform limitations and they are mainly
1091 /// used to implement accessibility scrolling on iOS.
1092 final bool? hidden;
1093
1094 /// If non-null, whether [value] should be obscured.
1095 ///
1096 /// This option is usually set in combination with [textField] to indicate
1097 /// that the text field contains a password (or other sensitive information).
1098 /// Doing so instructs screen readers to not read out the [value].
1099 final bool? obscured;
1100
1101 /// Whether the [value] is coming from a field that supports multiline text
1102 /// editing.
1103 ///
1104 /// This option is only meaningful when [textField] is true to indicate
1105 /// whether it's a single-line or multiline text field.
1106 ///
1107 /// This option is null when [textField] is false.
1108 final bool? multiline;
1109
1110 /// If non-null, whether the node corresponds to the root of a subtree for
1111 /// which a route name should be announced.
1112 ///
1113 /// Generally, this is set in combination with
1114 /// [SemanticsConfiguration.explicitChildNodes], since nodes with this flag
1115 /// are not considered focusable by Android or iOS.
1116 ///
1117 /// See also:
1118 ///
1119 /// * [SemanticsFlag.scopesRoute] for a description of how the announced
1120 /// value is selected.
1121 final bool? scopesRoute;
1122
1123 /// If non-null, whether the node contains the semantic label for a route.
1124 ///
1125 /// See also:
1126 ///
1127 /// * [SemanticsFlag.namesRoute] for a description of how the name is used.
1128 final bool? namesRoute;
1129
1130 /// If non-null, whether the node represents an image.
1131 ///
1132 /// See also:
1133 ///
1134 /// * [SemanticsFlag.isImage], for the flag this setting controls.
1135 final bool? image;
1136
1137 /// If non-null, whether the node should be considered a live region.
1138 ///
1139 /// A live region indicates that updates to semantics node are important.
1140 /// Platforms may use this information to make polite announcements to the
1141 /// user to inform them of updates to this node.
1142 ///
1143 /// An example of a live region is a [SnackBar] widget. On Android and iOS,
1144 /// live region causes a polite announcement to be generated automatically,
1145 /// even if the widget does not have accessibility focus. This announcement
1146 /// may not be spoken if the OS accessibility services are already
1147 /// announcing something else, such as reading the label of a focused widget
1148 /// or providing a system announcement.
1149 ///
1150 /// See also:
1151 ///
1152 /// * [SemanticsFlag.isLiveRegion], the semantics flag this setting controls.
1153 /// * [SemanticsConfiguration.liveRegion], for a full description of a live region.
1154 final bool? liveRegion;
1155
1156 /// The maximum number of characters that can be entered into an editable
1157 /// text field.
1158 ///
1159 /// For the purpose of this function a character is defined as one Unicode
1160 /// scalar value.
1161 ///
1162 /// This should only be set when [textField] is true. Defaults to null,
1163 /// which means no limit is imposed on the text field.
1164 final int? maxValueLength;
1165
1166 /// The current number of characters that have been entered into an editable
1167 /// text field.
1168 ///
1169 /// For the purpose of this function a character is defined as one Unicode
1170 /// scalar value.
1171 ///
1172 /// This should only be set when [textField] is true. Must be set when
1173 /// [maxValueLength] is set.
1174 final int? currentValueLength;
1175
1176 /// {@template flutter.semantics.SemanticsProperties.identifier}
1177 /// Provides an identifier for the semantics node in native accessibility hierarchy.
1178 ///
1179 /// This value is not exposed to the users of the app.
1180 ///
1181 /// It's usually used for UI testing with tools that work by querying the
1182 /// native accessibility, like UIAutomator, XCUITest, or Appium.
1183 ///
1184 /// On Android, this is used for `AccessibilityNodeInfo.setViewIdResourceName`.
1185 /// It'll be appear in accessibility hierarchy as `resource-id`.
1186 ///
1187 /// On iOS, this will set `UIAccessibilityElement.accessibilityIdentifier`.
1188 /// {@endtemplate}
1189 final String? identifier;
1190
1191 /// Provides a textual description of the widget.
1192 ///
1193 /// If a label is provided, there must either by an ambient [Directionality]
1194 /// or an explicit [textDirection] should be provided.
1195 ///
1196 /// Callers must not provide both [label] and [attributedLabel]. One or both
1197 /// must be null.
1198 ///
1199 /// See also:
1200 ///
1201 /// * [SemanticsConfiguration.label] for a description of how this is exposed
1202 /// in TalkBack and VoiceOver.
1203 /// * [attributedLabel] for an [AttributedString] version of this property.
1204 final String? label;
1205
1206 /// Provides an [AttributedString] version of textual description of the widget.
1207 ///
1208 /// If a [attributedLabel] is provided, there must either by an ambient
1209 /// [Directionality] or an explicit [textDirection] should be provided.
1210 ///
1211 /// Callers must not provide both [label] and [attributedLabel]. One or both
1212 /// must be null.
1213 ///
1214 /// See also:
1215 ///
1216 /// * [SemanticsConfiguration.attributedLabel] for a description of how this
1217 /// is exposed in TalkBack and VoiceOver.
1218 /// * [label] for a plain string version of this property.
1219 final AttributedString? attributedLabel;
1220
1221 /// Provides a textual description of the value of the widget.
1222 ///
1223 /// If a value is provided, there must either by an ambient [Directionality]
1224 /// or an explicit [textDirection] should be provided.
1225 ///
1226 /// Callers must not provide both [value] and [attributedValue], One or both
1227 /// must be null.
1228 ///
1229 /// See also:
1230 ///
1231 /// * [SemanticsConfiguration.value] for a description of how this is exposed
1232 /// in TalkBack and VoiceOver.
1233 /// * [attributedLabel] for an [AttributedString] version of this property.
1234 final String? value;
1235
1236 /// Provides an [AttributedString] version of textual description of the value
1237 /// of the widget.
1238 ///
1239 /// If a [attributedValue] is provided, there must either by an ambient
1240 /// [Directionality] or an explicit [textDirection] should be provided.
1241 ///
1242 /// Callers must not provide both [value] and [attributedValue], One or both
1243 /// must be null.
1244 ///
1245 /// See also:
1246 ///
1247 /// * [SemanticsConfiguration.attributedValue] for a description of how this
1248 /// is exposed in TalkBack and VoiceOver.
1249 /// * [value] for a plain string version of this property.
1250 final AttributedString? attributedValue;
1251
1252 /// The value that [value] or [attributedValue] will become after a
1253 /// [SemanticsAction.increase] action has been performed on this widget.
1254 ///
1255 /// If a value is provided, [onIncrease] must also be set and there must
1256 /// either be an ambient [Directionality] or an explicit [textDirection]
1257 /// must be provided.
1258 ///
1259 /// Callers must not provide both [increasedValue] and
1260 /// [attributedIncreasedValue], One or both must be null.
1261 ///
1262 /// See also:
1263 ///
1264 /// * [SemanticsConfiguration.increasedValue] for a description of how this
1265 /// is exposed in TalkBack and VoiceOver.
1266 /// * [attributedIncreasedValue] for an [AttributedString] version of this
1267 /// property.
1268 final String? increasedValue;
1269
1270 /// The [AttributedString] that [value] or [attributedValue] will become after
1271 /// a [SemanticsAction.increase] action has been performed on this widget.
1272 ///
1273 /// If a [attributedIncreasedValue] is provided, [onIncrease] must also be set
1274 /// and there must either be an ambient [Directionality] or an explicit
1275 /// [textDirection] must be provided.
1276 ///
1277 /// Callers must not provide both [increasedValue] and
1278 /// [attributedIncreasedValue], One or both must be null.
1279 ///
1280 /// See also:
1281 ///
1282 /// * [SemanticsConfiguration.attributedIncreasedValue] for a description of
1283 /// how this is exposed in TalkBack and VoiceOver.
1284 /// * [increasedValue] for a plain string version of this property.
1285 final AttributedString? attributedIncreasedValue;
1286
1287 /// The value that [value] or [attributedValue] will become after a
1288 /// [SemanticsAction.decrease] action has been performed on this widget.
1289 ///
1290 /// If a value is provided, [onDecrease] must also be set and there must
1291 /// either be an ambient [Directionality] or an explicit [textDirection]
1292 /// must be provided.
1293 ///
1294 /// Callers must not provide both [decreasedValue] and
1295 /// [attributedDecreasedValue], One or both must be null.
1296 ///
1297 /// See also:
1298 ///
1299 /// * [SemanticsConfiguration.decreasedValue] for a description of how this
1300 /// is exposed in TalkBack and VoiceOver.
1301 /// * [attributedDecreasedValue] for an [AttributedString] version of this
1302 /// property.
1303 final String? decreasedValue;
1304
1305 /// The [AttributedString] that [value] or [attributedValue] will become after
1306 /// a [SemanticsAction.decrease] action has been performed on this widget.
1307 ///
1308 /// If a [attributedDecreasedValue] is provided, [onDecrease] must also be set
1309 /// and there must either be an ambient [Directionality] or an explicit
1310 /// [textDirection] must be provided.
1311 ///
1312 /// Callers must not provide both [decreasedValue] and
1313 /// [attributedDecreasedValue], One or both must be null/// provided.
1314 ///
1315 /// See also:
1316 ///
1317 /// * [SemanticsConfiguration.attributedDecreasedValue] for a description of
1318 /// how this is exposed in TalkBack and VoiceOver.
1319 /// * [decreasedValue] for a plain string version of this property.
1320 final AttributedString? attributedDecreasedValue;
1321
1322 /// Provides a brief textual description of the result of an action performed
1323 /// on the widget.
1324 ///
1325 /// If a hint is provided, there must either be an ambient [Directionality]
1326 /// or an explicit [textDirection] should be provided.
1327 ///
1328 /// Callers must not provide both [hint] and [attributedHint], One or both
1329 /// must be null.
1330 ///
1331 /// See also:
1332 ///
1333 /// * [SemanticsConfiguration.hint] for a description of how this is exposed
1334 /// in TalkBack and VoiceOver.
1335 /// * [attributedHint] for an [AttributedString] version of this property.
1336 final String? hint;
1337
1338 /// Provides an [AttributedString] version of brief textual description of the
1339 /// result of an action performed on the widget.
1340 ///
1341 /// If a [attributedHint] is provided, there must either by an ambient
1342 /// [Directionality] or an explicit [textDirection] should be provided.
1343 ///
1344 /// Callers must not provide both [hint] and [attributedHint], One or both
1345 /// must be null.
1346 ///
1347 /// See also:
1348 ///
1349 /// * [SemanticsConfiguration.attributedHint] for a description of how this
1350 /// is exposed in TalkBack and VoiceOver.
1351 /// * [hint] for a plain string version of this property.
1352 final AttributedString? attributedHint;
1353
1354 /// Provides a textual description of the widget's tooltip.
1355 ///
1356 /// In Android, this property sets the `AccessibilityNodeInfo.setTooltipText`.
1357 /// In iOS, this property is appended to the end of the
1358 /// `UIAccessibilityElement.accessibilityLabel`.
1359 ///
1360 /// If a [tooltip] is provided, there must either by an ambient
1361 /// [Directionality] or an explicit [textDirection] should be provided.
1362 final String? tooltip;
1363
1364 /// Provides hint values which override the default hints on supported
1365 /// platforms.
1366 ///
1367 /// On Android, If no hint overrides are used then default [hint] will be
1368 /// combined with the [label]. Otherwise, the [hint] will be ignored as long
1369 /// as there as at least one non-null hint override.
1370 ///
1371 /// On iOS, these are always ignored and the default [hint] is used instead.
1372 final SemanticsHintOverrides? hintOverrides;
1373
1374 /// The reading direction of the [label], [value], [increasedValue],
1375 /// [decreasedValue], and [hint].
1376 ///
1377 /// Defaults to the ambient [Directionality].
1378 final TextDirection? textDirection;
1379
1380 /// Determines the position of this node among its siblings in the traversal
1381 /// sort order.
1382 ///
1383 /// This is used to describe the order in which the semantic node should be
1384 /// traversed by the accessibility services on the platform (e.g. VoiceOver
1385 /// on iOS and TalkBack on Android).
1386 final SemanticsSortKey? sortKey;
1387
1388 /// A tag to be applied to the child [SemanticsNode]s of this widget.
1389 ///
1390 /// The tag is added to all child [SemanticsNode]s that pass through the
1391 /// [RenderObject] corresponding to this widget while looking to be attached
1392 /// to a parent SemanticsNode.
1393 ///
1394 /// Tags are used to communicate to a parent SemanticsNode that a child
1395 /// SemanticsNode was passed through a particular RenderObject. The parent can
1396 /// use this information to determine the shape of the semantics tree.
1397 ///
1398 /// See also:
1399 ///
1400 /// * [SemanticsConfiguration.addTagForChildren], to which the tags provided
1401 /// here will be passed.
1402 final SemanticsTag? tagForChildren;
1403
1404 /// The handler for [SemanticsAction.tap].
1405 ///
1406 /// This is the semantic equivalent of a user briefly tapping the screen with
1407 /// the finger without moving it. For example, a button should implement this
1408 /// action.
1409 ///
1410 /// VoiceOver users on iOS and TalkBack users on Android *may* trigger this
1411 /// action by double-tapping the screen while an element is focused.
1412 ///
1413 /// Note: different OSes or assistive technologies may decide to interpret
1414 /// user inputs differently. Some may simulate real screen taps, while others
1415 /// may call semantics tap. One way to handle taps properly is to provide the
1416 /// same handler to both gesture tap and semantics tap.
1417 final VoidCallback? onTap;
1418
1419 /// The handler for [SemanticsAction.longPress].
1420 ///
1421 /// This is the semantic equivalent of a user pressing and holding the screen
1422 /// with the finger for a few seconds without moving it.
1423 ///
1424 /// VoiceOver users on iOS and TalkBack users on Android *may* trigger this
1425 /// action by double-tapping the screen without lifting the finger after the
1426 /// second tap.
1427 ///
1428 /// Note: different OSes or assistive technologies may decide to interpret
1429 /// user inputs differently. Some may simulate real long presses, while others
1430 /// may call semantics long press. One way to handle long press properly is to
1431 /// provide the same handler to both gesture long press and semantics long
1432 /// press.
1433 final VoidCallback? onLongPress;
1434
1435 /// The handler for [SemanticsAction.scrollLeft].
1436 ///
1437 /// This is the semantic equivalent of a user moving their finger across the
1438 /// screen from right to left. It should be recognized by controls that are
1439 /// horizontally scrollable.
1440 ///
1441 /// VoiceOver users on iOS can trigger this action by swiping left with three
1442 /// fingers. TalkBack users on Android can trigger this action by swiping
1443 /// right and then left in one motion path. On Android, [onScrollUp] and
1444 /// [onScrollLeft] share the same gesture. Therefore, only on of them should
1445 /// be provided.
1446 final VoidCallback? onScrollLeft;
1447
1448 /// The handler for [SemanticsAction.scrollRight].
1449 ///
1450 /// This is the semantic equivalent of a user moving their finger across the
1451 /// screen from left to right. It should be recognized by controls that are
1452 /// horizontally scrollable.
1453 ///
1454 /// VoiceOver users on iOS can trigger this action by swiping right with three
1455 /// fingers. TalkBack users on Android can trigger this action by swiping
1456 /// left and then right in one motion path. On Android, [onScrollDown] and
1457 /// [onScrollRight] share the same gesture. Therefore, only on of them should
1458 /// be provided.
1459 final VoidCallback? onScrollRight;
1460
1461 /// The handler for [SemanticsAction.scrollUp].
1462 ///
1463 /// This is the semantic equivalent of a user moving their finger across the
1464 /// screen from bottom to top. It should be recognized by controls that are
1465 /// vertically scrollable.
1466 ///
1467 /// VoiceOver users on iOS can trigger this action by swiping up with three
1468 /// fingers. TalkBack users on Android can trigger this action by swiping
1469 /// right and then left in one motion path. On Android, [onScrollUp] and
1470 /// [onScrollLeft] share the same gesture. Therefore, only on of them should
1471 /// be provided.
1472 final VoidCallback? onScrollUp;
1473
1474 /// The handler for [SemanticsAction.scrollDown].
1475 ///
1476 /// This is the semantic equivalent of a user moving their finger across the
1477 /// screen from top to bottom. It should be recognized by controls that are
1478 /// vertically scrollable.
1479 ///
1480 /// VoiceOver users on iOS can trigger this action by swiping down with three
1481 /// fingers. TalkBack users on Android can trigger this action by swiping
1482 /// left and then right in one motion path. On Android, [onScrollDown] and
1483 /// [onScrollRight] share the same gesture. Therefore, only on of them should
1484 /// be provided.
1485 final VoidCallback? onScrollDown;
1486
1487 /// The handler for [SemanticsAction.increase].
1488 ///
1489 /// This is a request to increase the value represented by the widget. For
1490 /// example, this action might be recognized by a slider control.
1491 ///
1492 /// If a [value] is set, [increasedValue] must also be provided and
1493 /// [onIncrease] must ensure that [value] will be set to [increasedValue].
1494 ///
1495 /// VoiceOver users on iOS can trigger this action by swiping up with one
1496 /// finger. TalkBack users on Android can trigger this action by pressing the
1497 /// volume up button.
1498 final VoidCallback? onIncrease;
1499
1500 /// The handler for [SemanticsAction.decrease].
1501 ///
1502 /// This is a request to decrease the value represented by the widget. For
1503 /// example, this action might be recognized by a slider control.
1504 ///
1505 /// If a [value] is set, [decreasedValue] must also be provided and
1506 /// [onDecrease] must ensure that [value] will be set to [decreasedValue].
1507 ///
1508 /// VoiceOver users on iOS can trigger this action by swiping down with one
1509 /// finger. TalkBack users on Android can trigger this action by pressing the
1510 /// volume down button.
1511 final VoidCallback? onDecrease;
1512
1513 /// The handler for [SemanticsAction.copy].
1514 ///
1515 /// This is a request to copy the current selection to the clipboard.
1516 ///
1517 /// TalkBack users on Android can trigger this action from the local context
1518 /// menu of a text field, for example.
1519 final VoidCallback? onCopy;
1520
1521 /// The handler for [SemanticsAction.cut].
1522 ///
1523 /// This is a request to cut the current selection and place it in the
1524 /// clipboard.
1525 ///
1526 /// TalkBack users on Android can trigger this action from the local context
1527 /// menu of a text field, for example.
1528 final VoidCallback? onCut;
1529
1530 /// The handler for [SemanticsAction.paste].
1531 ///
1532 /// This is a request to paste the current content of the clipboard.
1533 ///
1534 /// TalkBack users on Android can trigger this action from the local context
1535 /// menu of a text field, for example.
1536 final VoidCallback? onPaste;
1537
1538 /// The handler for [SemanticsAction.moveCursorForwardByCharacter].
1539 ///
1540 /// This handler is invoked when the user wants to move the cursor in a
1541 /// text field forward by one character.
1542 ///
1543 /// TalkBack users can trigger this by pressing the volume up key while the
1544 /// input focus is in a text field.
1545 final MoveCursorHandler? onMoveCursorForwardByCharacter;
1546
1547 /// The handler for [SemanticsAction.moveCursorBackwardByCharacter].
1548 ///
1549 /// This handler is invoked when the user wants to move the cursor in a
1550 /// text field backward by one character.
1551 ///
1552 /// TalkBack users can trigger this by pressing the volume down key while the
1553 /// input focus is in a text field.
1554 final MoveCursorHandler? onMoveCursorBackwardByCharacter;
1555
1556 /// The handler for [SemanticsAction.moveCursorForwardByWord].
1557 ///
1558 /// This handler is invoked when the user wants to move the cursor in a
1559 /// text field backward by one word.
1560 ///
1561 /// TalkBack users can trigger this by pressing the volume down key while the
1562 /// input focus is in a text field.
1563 final MoveCursorHandler? onMoveCursorForwardByWord;
1564
1565 /// The handler for [SemanticsAction.moveCursorBackwardByWord].
1566 ///
1567 /// This handler is invoked when the user wants to move the cursor in a
1568 /// text field backward by one word.
1569 ///
1570 /// TalkBack users can trigger this by pressing the volume down key while the
1571 /// input focus is in a text field.
1572 final MoveCursorHandler? onMoveCursorBackwardByWord;
1573
1574 /// The handler for [SemanticsAction.setSelection].
1575 ///
1576 /// This handler is invoked when the user either wants to change the currently
1577 /// selected text in a text field or change the position of the cursor.
1578 ///
1579 /// TalkBack users can trigger this handler by selecting "Move cursor to
1580 /// beginning/end" or "Select all" from the local context menu.
1581 final SetSelectionHandler? onSetSelection;
1582
1583 /// The handler for [SemanticsAction.setText].
1584 ///
1585 /// This handler is invoked when the user wants to replace the current text in
1586 /// the text field with a new text.
1587 ///
1588 /// Voice access users can trigger this handler by speaking "type <text>" to
1589 /// their Android devices.
1590 final SetTextHandler? onSetText;
1591
1592 /// The handler for [SemanticsAction.didGainAccessibilityFocus].
1593 ///
1594 /// This handler is invoked when the node annotated with this handler gains
1595 /// the accessibility focus. The accessibility focus is the
1596 /// green (on Android with TalkBack) or black (on iOS with VoiceOver)
1597 /// rectangle shown on screen to indicate what element an accessibility
1598 /// user is currently interacting with.
1599 ///
1600 /// The accessibility focus is different from the input focus. The input focus
1601 /// is usually held by the element that currently responds to keyboard inputs.
1602 /// Accessibility focus and input focus can be held by two different nodes!
1603 ///
1604 /// See also:
1605 ///
1606 /// * [onDidLoseAccessibilityFocus], which is invoked when the accessibility
1607 /// focus is removed from the node.
1608 /// * [FocusNode], [FocusScope], [FocusManager], which manage the input focus.
1609 final VoidCallback? onDidGainAccessibilityFocus;
1610
1611 /// The handler for [SemanticsAction.didLoseAccessibilityFocus].
1612 ///
1613 /// This handler is invoked when the node annotated with this handler
1614 /// loses the accessibility focus. The accessibility focus is
1615 /// the green (on Android with TalkBack) or black (on iOS with VoiceOver)
1616 /// rectangle shown on screen to indicate what element an accessibility
1617 /// user is currently interacting with.
1618 ///
1619 /// The accessibility focus is different from the input focus. The input focus
1620 /// is usually held by the element that currently responds to keyboard inputs.
1621 /// Accessibility focus and input focus can be held by two different nodes!
1622 ///
1623 /// See also:
1624 ///
1625 /// * [onDidGainAccessibilityFocus], which is invoked when the node gains
1626 /// accessibility focus.
1627 /// * [FocusNode], [FocusScope], [FocusManager], which manage the input focus.
1628 final VoidCallback? onDidLoseAccessibilityFocus;
1629
1630 /// The handler for [SemanticsAction.dismiss].
1631 ///
1632 /// This is a request to dismiss the currently focused node.
1633 ///
1634 /// TalkBack users on Android can trigger this action in the local context
1635 /// menu, and VoiceOver users on iOS can trigger this action with a standard
1636 /// gesture or menu option.
1637 final VoidCallback? onDismiss;
1638
1639 /// A map from each supported [CustomSemanticsAction] to a provided handler.
1640 ///
1641 /// The handler associated with each custom action is called whenever a
1642 /// semantics action of type [SemanticsAction.customAction] is received. The
1643 /// provided argument will be an identifier used to retrieve an instance of
1644 /// a custom action which can then retrieve the correct handler from this map.
1645 ///
1646 /// See also:
1647 ///
1648 /// * [CustomSemanticsAction], for an explanation of custom actions.
1649 final Map<CustomSemanticsAction, VoidCallback>? customSemanticsActions;
1650
1651 @override
1652 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
1653 super.debugFillProperties(properties);
1654 properties.add(DiagnosticsProperty<bool>('checked', checked, defaultValue: null));
1655 properties.add(DiagnosticsProperty<bool>('mixed', mixed, defaultValue: null));
1656 properties.add(DiagnosticsProperty<bool>('expanded', expanded, defaultValue: null));
1657 properties.add(DiagnosticsProperty<bool>('selected', selected, defaultValue: null));
1658 properties.add(StringProperty('identifier', identifier, defaultValue: null));
1659 properties.add(StringProperty('label', label, defaultValue: null));
1660 properties.add(AttributedStringProperty('attributedLabel', attributedLabel, defaultValue: null));
1661 properties.add(StringProperty('value', value, defaultValue: null));
1662 properties.add(AttributedStringProperty('attributedValue', attributedValue, defaultValue: null));
1663 properties.add(StringProperty('increasedValue', value, defaultValue: null));
1664 properties.add(AttributedStringProperty('attributedIncreasedValue', attributedIncreasedValue, defaultValue: null));
1665 properties.add(StringProperty('decreasedValue', value, defaultValue: null));
1666 properties.add(AttributedStringProperty('attributedDecreasedValue', attributedDecreasedValue, defaultValue: null));
1667 properties.add(StringProperty('hint', hint, defaultValue: null));
1668 properties.add(AttributedStringProperty('attributedHint', attributedHint, defaultValue: null));
1669 properties.add(StringProperty('tooltip', tooltip, defaultValue: null));
1670 properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
1671 properties.add(DiagnosticsProperty<SemanticsSortKey>('sortKey', sortKey, defaultValue: null));
1672 properties.add(DiagnosticsProperty<SemanticsHintOverrides>('hintOverrides', hintOverrides, defaultValue: null));
1673 }
1674
1675 @override
1676 String toStringShort() => objectRuntimeType(this, 'SemanticsProperties'); // the hashCode isn't important since we're immutable
1677}
1678
1679/// In tests use this function to reset the counter used to generate
1680/// [SemanticsNode.id].
1681void debugResetSemanticsIdCounter() {
1682 SemanticsNode._lastIdentifier = 0;
1683}
1684
1685/// A node that represents some semantic data.
1686///
1687/// The semantics tree is maintained during the semantics phase of the pipeline
1688/// (i.e., during [PipelineOwner.flushSemantics]), which happens after
1689/// compositing. The semantics tree is then uploaded into the engine for use
1690/// by assistive technology.
1691class SemanticsNode with DiagnosticableTreeMixin {
1692 /// Creates a semantic node.
1693 ///
1694 /// Each semantic node has a unique identifier that is assigned when the node
1695 /// is created.
1696 SemanticsNode({
1697 this.key,
1698 VoidCallback? showOnScreen,
1699 }) : _id = _generateNewId(),
1700 _showOnScreen = showOnScreen;
1701
1702 /// Creates a semantic node to represent the root of the semantics tree.
1703 ///
1704 /// The root node is assigned an identifier of zero.
1705 SemanticsNode.root({
1706 this.key,
1707 VoidCallback? showOnScreen,
1708 required SemanticsOwner owner,
1709 }) : _id = 0,
1710 _showOnScreen = showOnScreen {
1711 attach(owner);
1712 }
1713
1714
1715 // The maximal semantic node identifier generated by the framework.
1716 //
1717 // The identifier range for semantic node IDs is split into 2, the least significant 16 bits are
1718 // reserved for framework generated IDs(generated with _generateNewId), and most significant 32
1719 // bits are reserved for engine generated IDs.
1720 static const int _maxFrameworkAccessibilityIdentifier = (1<<16) - 1;
1721
1722 static int _lastIdentifier = 0;
1723 static int _generateNewId() {
1724 _lastIdentifier = (_lastIdentifier + 1) % _maxFrameworkAccessibilityIdentifier;
1725 return _lastIdentifier;
1726 }
1727
1728 /// Uniquely identifies this node in the list of sibling nodes.
1729 ///
1730 /// Keys are used during the construction of the semantics tree. They are not
1731 /// transferred to the engine.
1732 final Key? key;
1733
1734 /// The unique identifier for this node.
1735 ///
1736 /// The root node has an id of zero. Other nodes are given a unique id
1737 /// when they are attached to a [SemanticsOwner]. If they are detached, their
1738 /// ids are invalid and should not be used.
1739 ///
1740 /// In rare circumstances, id may change if this node is detached and
1741 /// re-attached to the [SemanticsOwner]. This should only happen when the
1742 /// application has generated too many semantics nodes.
1743 int get id => _id;
1744 int _id;
1745
1746 final VoidCallback? _showOnScreen;
1747
1748 // GEOMETRY
1749
1750 /// The transform from this node's coordinate system to its parent's coordinate system.
1751 ///
1752 /// By default, the transform is null, which represents the identity
1753 /// transformation (i.e., that this node has the same coordinate system as its
1754 /// parent).
1755 Matrix4? get transform => _transform;
1756 Matrix4? _transform;
1757 set transform(Matrix4? value) {
1758 if (!MatrixUtils.matrixEquals(_transform, value)) {
1759 _transform = value == null || MatrixUtils.isIdentity(value) ? null : value;
1760 _markDirty();
1761 }
1762 }
1763
1764 /// The bounding box for this node in its coordinate system.
1765 Rect get rect => _rect;
1766 Rect _rect = Rect.zero;
1767 set rect(Rect value) {
1768 assert(value.isFinite, '$this (with $owner) tried to set a non-finite rect.');
1769 if (_rect != value) {
1770 _rect = value;
1771 _markDirty();
1772 }
1773 }
1774
1775 /// The semantic clip from an ancestor that was applied to this node.
1776 ///
1777 /// Expressed in the coordinate system of the node. May be null if no clip has
1778 /// been applied.
1779 ///
1780 /// Descendant [SemanticsNode]s that are positioned outside of this rect will
1781 /// be excluded from the semantics tree. Descendant [SemanticsNode]s that are
1782 /// overlapping with this rect, but are outside of [parentPaintClipRect] will
1783 /// be included in the tree, but they will be marked as hidden because they
1784 /// are assumed to be not visible on screen.
1785 ///
1786 /// If this rect is null, all descendant [SemanticsNode]s outside of
1787 /// [parentPaintClipRect] will be excluded from the tree.
1788 ///
1789 /// If this rect is non-null it has to completely enclose
1790 /// [parentPaintClipRect]. If [parentPaintClipRect] is null this property is
1791 /// also null.
1792 Rect? parentSemanticsClipRect;
1793
1794 /// The paint clip from an ancestor that was applied to this node.
1795 ///
1796 /// Expressed in the coordinate system of the node. May be null if no clip has
1797 /// been applied.
1798 ///
1799 /// Descendant [SemanticsNode]s that are positioned outside of this rect will
1800 /// either be excluded from the semantics tree (if they have no overlap with
1801 /// [parentSemanticsClipRect]) or they will be included and marked as hidden
1802 /// (if they are overlapping with [parentSemanticsClipRect]).
1803 ///
1804 /// This rect is completely enclosed by [parentSemanticsClipRect].
1805 ///
1806 /// If this rect is null [parentSemanticsClipRect] also has to be null.
1807 Rect? parentPaintClipRect;
1808
1809 /// The elevation adjustment that the parent imposes on this node.
1810 ///
1811 /// The [elevation] property is relative to the elevation of the parent
1812 /// [SemanticsNode]. However, as [SemanticsConfiguration]s from various
1813 /// ascending [RenderObject]s are merged into each other to form that
1814 /// [SemanticsNode] the parent’s elevation may change. This requires an
1815 /// adjustment of the child’s relative elevation which is represented by this
1816 /// value.
1817 ///
1818 /// The value is rarely accessed directly. Instead, for most use cases the
1819 /// [elevation] value should be used, which includes this adjustment.
1820 ///
1821 /// See also:
1822 ///
1823 /// * [elevation], the actual elevation of this [SemanticsNode].
1824 double? elevationAdjustment;
1825
1826 /// The index of this node within the parent's list of semantic children.
1827 ///
1828 /// This includes all semantic nodes, not just those currently in the
1829 /// child list. For example, if a scrollable has five children but the first
1830 /// two are not visible (and thus not included in the list of children), then
1831 /// the index of the last node will still be 4.
1832 int? indexInParent;
1833
1834 /// Whether the node is invisible.
1835 ///
1836 /// A node whose [rect] is outside of the bounds of the screen and hence not
1837 /// reachable for users is considered invisible if its semantic information
1838 /// is not merged into a (partially) visible parent as indicated by
1839 /// [isMergedIntoParent].
1840 ///
1841 /// An invisible node can be safely dropped from the semantic tree without
1842 /// loosing semantic information that is relevant for describing the content
1843 /// currently shown on screen.
1844 bool get isInvisible => !isMergedIntoParent && rect.isEmpty;
1845
1846 // MERGING
1847
1848 /// Whether this node merges its semantic information into an ancestor node.
1849 ///
1850 /// This value indicates whether this node has any ancestors with
1851 /// [mergeAllDescendantsIntoThisNode] set to true.
1852 bool get isMergedIntoParent => parent != null && _isMergedIntoParent;
1853 bool _isMergedIntoParent = false;
1854
1855 /// Whether the user can interact with this node in assistive technologies.
1856 ///
1857 /// This node can still receive accessibility focus even if this is true.
1858 /// Setting this to true prevents the user from activating pointer related
1859 /// [SemanticsAction]s, such as [SemanticsAction.tap] or
1860 /// [SemanticsAction.longPress].
1861 bool get areUserActionsBlocked => _areUserActionsBlocked;
1862 bool _areUserActionsBlocked = false;
1863 set areUserActionsBlocked(bool value) {
1864 if (_areUserActionsBlocked == value) {
1865 return;
1866 }
1867 _areUserActionsBlocked = value;
1868 _markDirty();
1869 }
1870
1871 /// Whether this node is taking part in a merge of semantic information.
1872 ///
1873 /// This returns true if the node is either merged into an ancestor node or if
1874 /// decedent nodes are merged into this node.
1875 ///
1876 /// See also:
1877 ///
1878 /// * [isMergedIntoParent]
1879 /// * [mergeAllDescendantsIntoThisNode]
1880 bool get isPartOfNodeMerging => mergeAllDescendantsIntoThisNode || isMergedIntoParent;
1881
1882 /// Whether this node and all of its descendants should be treated as one logical entity.
1883 bool get mergeAllDescendantsIntoThisNode => _mergeAllDescendantsIntoThisNode;
1884 bool _mergeAllDescendantsIntoThisNode = _kEmptyConfig.isMergingSemanticsOfDescendants;
1885
1886
1887 // CHILDREN
1888
1889 /// Contains the children in inverse hit test order (i.e. paint order).
1890 List<SemanticsNode>? _children;
1891
1892 /// A snapshot of `newChildren` passed to [_replaceChildren] that we keep in
1893 /// debug mode. It supports the assertion that user does not mutate the list
1894 /// of children.
1895 late List<SemanticsNode> _debugPreviousSnapshot;
1896
1897 void _replaceChildren(List<SemanticsNode> newChildren) {
1898 assert(!newChildren.any((SemanticsNode child) => child == this));
1899 assert(() {
1900 final Set<SemanticsNode> seenChildren = <SemanticsNode>{};
1901 for (final SemanticsNode child in newChildren) {
1902 assert(seenChildren.add(child));
1903 } // check for duplicate adds
1904 return true;
1905 }());
1906
1907 // The goal of this function is updating sawChange.
1908 if (_children != null) {
1909 for (final SemanticsNode child in _children!) {
1910 child._dead = true;
1911 }
1912 }
1913 for (final SemanticsNode child in newChildren) {
1914 child._dead = false;
1915 }
1916 bool sawChange = false;
1917 if (_children != null) {
1918 for (final SemanticsNode child in _children!) {
1919 if (child._dead) {
1920 if (child.parent == this) {
1921 // we might have already had our child stolen from us by
1922 // another node that is deeper in the tree.
1923 _dropChild(child);
1924 }
1925 sawChange = true;
1926 }
1927 }
1928 }
1929 for (final SemanticsNode child in newChildren) {
1930 if (child.parent != this) {
1931 if (child.parent != null) {
1932 // we're rebuilding the tree from the bottom up, so it's possible
1933 // that our child was, in the last pass, a child of one of our
1934 // ancestors. In that case, we drop the child eagerly here.
1935 // TODO(ianh): Find a way to assert that the same node didn't
1936 // actually appear in the tree in two places.
1937 child.parent?._dropChild(child);
1938 }
1939 assert(!child.attached);
1940 _adoptChild(child);
1941 sawChange = true;
1942 }
1943 }
1944 // Wait until the new children are adopted so isMergedIntoParent becomes
1945 // up-to-date.
1946 assert(() {
1947 if (identical(newChildren, _children)) {
1948 final List<DiagnosticsNode> mutationErrors = <DiagnosticsNode>[];
1949 if (newChildren.length != _debugPreviousSnapshot.length) {
1950 mutationErrors.add(ErrorDescription(
1951 "The list's length has changed from ${_debugPreviousSnapshot.length} "
1952 'to ${newChildren.length}.',
1953 ));
1954 } else {
1955 for (int i = 0; i < newChildren.length; i++) {
1956 if (!identical(newChildren[i], _debugPreviousSnapshot[i])) {
1957 if (mutationErrors.isNotEmpty) {
1958 mutationErrors.add(ErrorSpacer());
1959 }
1960 mutationErrors.add(ErrorDescription('Child node at position $i was replaced:'));
1961 mutationErrors.add(_debugPreviousSnapshot[i].toDiagnosticsNode(name: 'Previous child', style: DiagnosticsTreeStyle.singleLine));
1962 mutationErrors.add(newChildren[i].toDiagnosticsNode(name: 'New child', style: DiagnosticsTreeStyle.singleLine));
1963 }
1964 }
1965 }
1966 if (mutationErrors.isNotEmpty) {
1967 throw FlutterError.fromParts(<DiagnosticsNode>[
1968 ErrorSummary('Failed to replace child semantics nodes because the list of `SemanticsNode`s was mutated.'),
1969 ErrorHint('Instead of mutating the existing list, create a new list containing the desired `SemanticsNode`s.'),
1970 ErrorDescription('Error details:'),
1971 ...mutationErrors,
1972 ]);
1973 }
1974 }
1975 _debugPreviousSnapshot = List<SemanticsNode>.of(newChildren);
1976
1977 SemanticsNode ancestor = this;
1978 while (ancestor.parent is SemanticsNode) {
1979 ancestor = ancestor.parent!;
1980 }
1981 assert(!newChildren.any((SemanticsNode child) => child == ancestor));
1982 return true;
1983 }());
1984
1985 if (!sawChange && _children != null) {
1986 assert(newChildren.length == _children!.length);
1987 // Did the order change?
1988 for (int i = 0; i < _children!.length; i++) {
1989 if (_children![i].id != newChildren[i].id) {
1990 sawChange = true;
1991 break;
1992 }
1993 }
1994 }
1995 _children = newChildren;
1996 if (sawChange) {
1997 _markDirty();
1998 }
1999 }
2000
2001 /// Whether this node has a non-zero number of children.
2002 bool get hasChildren => _children?.isNotEmpty ?? false;
2003 bool _dead = false;
2004
2005 /// The number of children this node has.
2006 int get childrenCount => hasChildren ? _children!.length : 0;
2007
2008 /// Visits the immediate children of this node.
2009 ///
2010 /// This function calls visitor for each immediate child until visitor returns
2011 /// false. Returns true if all the visitor calls returned true, otherwise
2012 /// returns false.
2013 void visitChildren(SemanticsNodeVisitor visitor) {
2014 if (_children != null) {
2015 for (final SemanticsNode child in _children!) {
2016 if (!visitor(child)) {
2017 return;
2018 }
2019 }
2020 }
2021 }
2022
2023 /// Visit all the descendants of this node.
2024 ///
2025 /// This function calls visitor for each descendant in a pre-order traversal
2026 /// until visitor returns false. Returns true if all the visitor calls
2027 /// returned true, otherwise returns false.
2028 bool _visitDescendants(SemanticsNodeVisitor visitor) {
2029 if (_children != null) {
2030 for (final SemanticsNode child in _children!) {
2031 if (!visitor(child) || !child._visitDescendants(visitor)) {
2032 return false;
2033 }
2034 }
2035 }
2036 return true;
2037 }
2038
2039 /// The owner for this node (null if unattached).
2040 ///
2041 /// The entire semantics tree that this node belongs to will have the same owner.
2042 SemanticsOwner? get owner => _owner;
2043 SemanticsOwner? _owner;
2044
2045 /// Whether the semantics tree this node belongs to is attached to a [SemanticsOwner].
2046 ///
2047 /// This becomes true during the call to [attach].
2048 ///
2049 /// This becomes false during the call to [detach].
2050 bool get attached => _owner != null;
2051
2052 /// The parent of this node in the semantics tree.
2053 ///
2054 /// The [parent] of the root node in the semantics tree is null.
2055 SemanticsNode? get parent => _parent;
2056 SemanticsNode? _parent;
2057
2058 /// The depth of this node in the semantics tree.
2059 ///
2060 /// The depth of nodes in a tree monotonically increases as you traverse down
2061 /// the tree. There's no guarantee regarding depth between siblings.
2062 ///
2063 /// The depth is used to ensure that nodes are processed in depth order.
2064 int get depth => _depth;
2065 int _depth = 0;
2066
2067 void _redepthChild(SemanticsNode child) {
2068 assert(child.owner == owner);
2069 if (child._depth <= _depth) {
2070 child._depth = _depth + 1;
2071 child._redepthChildren();
2072 }
2073 }
2074
2075 void _redepthChildren() {
2076 _children?.forEach(_redepthChild);
2077 }
2078
2079 void _updateChildMergeFlagRecursively(SemanticsNode child) {
2080 assert(child.owner == owner);
2081 final bool childShouldMergeToParent = isPartOfNodeMerging;
2082
2083 if (childShouldMergeToParent == child._isMergedIntoParent) {
2084 return;
2085 }
2086
2087 child._isMergedIntoParent = childShouldMergeToParent;
2088 _markDirty();
2089
2090 if (child.mergeAllDescendantsIntoThisNode) {
2091 // No need to update the descendants since `child` has the merge flag set.
2092 } else {
2093 child._updateChildrenMergeFlags();
2094 }
2095 }
2096
2097 void _updateChildrenMergeFlags() {
2098 _children?.forEach(_updateChildMergeFlagRecursively);
2099 }
2100
2101 void _adoptChild(SemanticsNode child) {
2102 assert(child._parent == null);
2103 assert(() {
2104 SemanticsNode node = this;
2105 while (node.parent != null) {
2106 node = node.parent!;
2107 }
2108 assert(node != child); // indicates we are about to create a cycle
2109 return true;
2110 }());
2111 child._parent = this;
2112 if (attached) {
2113 child.attach(_owner!);
2114 }
2115 _redepthChild(child);
2116 _updateChildMergeFlagRecursively(child);
2117 }
2118
2119 void _dropChild(SemanticsNode child) {
2120 assert(child._parent == this);
2121 assert(child.attached == attached);
2122 child._parent = null;
2123 if (attached) {
2124 child.detach();
2125 }
2126 }
2127
2128 /// Mark this node as attached to the given owner.
2129 @visibleForTesting
2130 void attach(SemanticsOwner owner) {
2131 assert(_owner == null);
2132 _owner = owner;
2133 while (owner._nodes.containsKey(id)) {
2134 // Ids may repeat if the Flutter has generated > 2^16 ids. We need to keep
2135 // regenerating the id until we found an id that is not used.
2136 _id = _generateNewId();
2137 }
2138 owner._nodes[id] = this;
2139 owner._detachedNodes.remove(this);
2140 if (_dirty) {
2141 _dirty = false;
2142 _markDirty();
2143 }
2144 if (_children != null) {
2145 for (final SemanticsNode child in _children!) {
2146 child.attach(owner);
2147 }
2148 }
2149 }
2150
2151 /// Mark this node as detached from its owner.
2152 @visibleForTesting
2153 void detach() {
2154 assert(_owner != null);
2155 assert(owner!._nodes.containsKey(id));
2156 assert(!owner!._detachedNodes.contains(this));
2157 owner!._nodes.remove(id);
2158 owner!._detachedNodes.add(this);
2159 _owner = null;
2160 assert(parent == null || attached == parent!.attached);
2161 if (_children != null) {
2162 for (final SemanticsNode child in _children!) {
2163 // The list of children may be stale and may contain nodes that have
2164 // been assigned to a different parent.
2165 if (child.parent == this) {
2166 child.detach();
2167 }
2168 }
2169 }
2170 // The other side will have forgotten this node if we ever send
2171 // it again, so make sure to mark it dirty so that it'll get
2172 // sent if it is resurrected.
2173 _markDirty();
2174 }
2175
2176 // DIRTY MANAGEMENT
2177
2178 bool _dirty = false;
2179 void _markDirty() {
2180 if (_dirty) {
2181 return;
2182 }
2183 _dirty = true;
2184 if (attached) {
2185 assert(!owner!._detachedNodes.contains(this));
2186 owner!._dirtyNodes.add(this);
2187 }
2188 }
2189
2190 bool _isDifferentFromCurrentSemanticAnnotation(SemanticsConfiguration config) {
2191 return _attributedLabel != config.attributedLabel
2192 || _attributedHint != config.attributedHint
2193 || _elevation != config.elevation
2194 || _thickness != config.thickness
2195 || _attributedValue != config.attributedValue
2196 || _attributedIncreasedValue != config.attributedIncreasedValue
2197 || _attributedDecreasedValue != config.attributedDecreasedValue
2198 || _tooltip != config.tooltip
2199 || _flags != config._flags
2200 || _textDirection != config.textDirection
2201 || _sortKey != config._sortKey
2202 || _textSelection != config._textSelection
2203 || _scrollPosition != config._scrollPosition
2204 || _scrollExtentMax != config._scrollExtentMax
2205 || _scrollExtentMin != config._scrollExtentMin
2206 || _actionsAsBits != config._actionsAsBits
2207 || indexInParent != config.indexInParent
2208 || platformViewId != config.platformViewId
2209 || _maxValueLength != config._maxValueLength
2210 || _currentValueLength != config._currentValueLength
2211 || _mergeAllDescendantsIntoThisNode != config.isMergingSemanticsOfDescendants
2212 || _areUserActionsBlocked != config.isBlockingUserActions;
2213 }
2214
2215 // TAGS, LABELS, ACTIONS
2216
2217 Map<SemanticsAction, SemanticsActionHandler> _actions = _kEmptyConfig._actions;
2218 Map<CustomSemanticsAction, VoidCallback> _customSemanticsActions = _kEmptyConfig._customSemanticsActions;
2219
2220 int get _effectiveActionsAsBits => _areUserActionsBlocked ? _actionsAsBits & _kUnblockedUserActions : _actionsAsBits;
2221 int _actionsAsBits = _kEmptyConfig._actionsAsBits;
2222
2223 /// The [SemanticsTag]s this node is tagged with.
2224 ///
2225 /// Tags are used during the construction of the semantics tree. They are not
2226 /// transferred to the engine.
2227 Set<SemanticsTag>? tags;
2228
2229 /// Whether this node is tagged with `tag`.
2230 bool isTagged(SemanticsTag tag) => tags != null && tags!.contains(tag);
2231
2232 int _flags = _kEmptyConfig._flags;
2233
2234 /// Whether this node currently has a given [SemanticsFlag].
2235 bool hasFlag(SemanticsFlag flag) => _flags & flag.index != 0;
2236
2237 /// {@macro flutter.semantics.SemanticsProperties.identifier}
2238 String get identifier => _identifier;
2239 String _identifier = _kEmptyConfig.identifier;
2240
2241 /// A textual description of this node.
2242 ///
2243 /// The reading direction is given by [textDirection].
2244 ///
2245 /// This exposes the raw text of the [attributedLabel].
2246 String get label => _attributedLabel.string;
2247
2248 /// A textual description of this node in [AttributedString] format.
2249 ///
2250 /// The reading direction is given by [textDirection].
2251 ///
2252 /// See also [label], which exposes just the raw text.
2253 AttributedString get attributedLabel => _attributedLabel;
2254 AttributedString _attributedLabel = _kEmptyConfig.attributedLabel;
2255
2256 /// A textual description for the current value of the node.
2257 ///
2258 /// The reading direction is given by [textDirection].
2259 ///
2260 /// This exposes the raw text of the [attributedValue].
2261 String get value => _attributedValue.string;
2262
2263 /// A textual description for the current value of the node in
2264 /// [AttributedString] format.
2265 ///
2266 /// The reading direction is given by [textDirection].
2267 ///
2268 /// See also [value], which exposes just the raw text.
2269 AttributedString get attributedValue => _attributedValue;
2270 AttributedString _attributedValue = _kEmptyConfig.attributedValue;
2271
2272 /// The value that [value] will have after a [SemanticsAction.increase] action
2273 /// has been performed.
2274 ///
2275 /// This property is only valid if the [SemanticsAction.increase] action is
2276 /// available on this node.
2277 ///
2278 /// The reading direction is given by [textDirection].
2279 ///
2280 /// This exposes the raw text of the [attributedIncreasedValue].
2281 String get increasedValue => _attributedIncreasedValue.string;
2282
2283 /// The value in [AttributedString] format that [value] or [attributedValue]
2284 /// will have after a [SemanticsAction.increase] action has been performed.
2285 ///
2286 /// This property is only valid if the [SemanticsAction.increase] action is
2287 /// available on this node.
2288 ///
2289 /// The reading direction is given by [textDirection].
2290 ///
2291 /// See also [increasedValue], which exposes just the raw text.
2292 AttributedString get attributedIncreasedValue => _attributedIncreasedValue;
2293 AttributedString _attributedIncreasedValue = _kEmptyConfig.attributedIncreasedValue;
2294
2295 /// The value that [value] will have after a [SemanticsAction.decrease] action
2296 /// has been performed.
2297 ///
2298 /// This property is only valid if the [SemanticsAction.decrease] action is
2299 /// available on this node.
2300 ///
2301 /// The reading direction is given by [textDirection].
2302 ///
2303 /// This exposes the raw text of the [attributedDecreasedValue].
2304 String get decreasedValue => _attributedDecreasedValue.string;
2305
2306 /// The value in [AttributedString] format that [value] or [attributedValue]
2307 /// will have after a [SemanticsAction.decrease] action has been performed.
2308 ///
2309 /// This property is only valid if the [SemanticsAction.decrease] action is
2310 /// available on this node.
2311 ///
2312 /// The reading direction is given by [textDirection].
2313 ///
2314 /// See also [decreasedValue], which exposes just the raw text.
2315 AttributedString get attributedDecreasedValue => _attributedDecreasedValue;
2316 AttributedString _attributedDecreasedValue = _kEmptyConfig.attributedDecreasedValue;
2317
2318 /// A brief description of the result of performing an action on this node.
2319 ///
2320 /// The reading direction is given by [textDirection].
2321 ///
2322 /// This exposes the raw text of the [attributedHint].
2323 String get hint => _attributedHint.string;
2324
2325 /// A brief description of the result of performing an action on this node
2326 /// in [AttributedString] format.
2327 ///
2328 /// The reading direction is given by [textDirection].
2329 ///
2330 /// See also [hint], which exposes just the raw text.
2331 AttributedString get attributedHint => _attributedHint;
2332 AttributedString _attributedHint = _kEmptyConfig.attributedHint;
2333
2334 /// A textual description of the widget's tooltip.
2335 ///
2336 /// The reading direction is given by [textDirection].
2337 String get tooltip => _tooltip;
2338 String _tooltip = _kEmptyConfig.tooltip;
2339
2340 /// The elevation along the z-axis at which the [rect] of this [SemanticsNode]
2341 /// is located above its parent.
2342 ///
2343 /// The value is relative to the parent's [elevation]. The sum of the
2344 /// [elevation]s of all ancestor node plus this value determines the absolute
2345 /// elevation of this [SemanticsNode].
2346 ///
2347 /// See also:
2348 ///
2349 /// * [thickness], which describes how much space in z-direction this
2350 /// [SemanticsNode] occupies starting at this [elevation].
2351 /// * [elevationAdjustment], which has been used to calculate this value.
2352 double get elevation => _elevation;
2353 double _elevation = _kEmptyConfig.elevation;
2354
2355 /// Describes how much space the [SemanticsNode] takes up along the z-axis.
2356 ///
2357 /// A [SemanticsNode] represents multiple [RenderObject]s, which can be
2358 /// located at various elevations in 3D. The [thickness] is the difference
2359 /// between the absolute elevations of the lowest and highest [RenderObject]
2360 /// represented by this [SemanticsNode]. In other words, the thickness
2361 /// describes how high the box is that this [SemanticsNode] occupies in three
2362 /// dimensional space. The two other dimensions are defined by [rect].
2363 ///
2364 /// {@tool snippet}
2365 /// The following code stacks three [PhysicalModel]s on top of each other
2366 /// separated by non-zero elevations.
2367 ///
2368 /// [PhysicalModel] C is elevated 10.0 above [PhysicalModel] B, which in turn
2369 /// is elevated 5.0 above [PhysicalModel] A. The side view of this
2370 /// constellation looks as follows:
2371 ///
2372 /// ![A diagram illustrating the elevations of three PhysicalModels and their
2373 /// corresponding SemanticsNodes.](https://flutter.github.io/assets-for-api-docs/assets/semantics/SemanticsNode.thickness.png)
2374 ///
2375 /// In this example the [RenderObject]s for [PhysicalModel] C and B share one
2376 /// [SemanticsNode] Y. Given the elevations of those [RenderObject]s, this
2377 /// [SemanticsNode] has a [thickness] of 10.0 and an elevation of 5.0 over
2378 /// its parent [SemanticsNode] X.
2379 /// ```dart
2380 /// PhysicalModel( // A
2381 /// color: Colors.amber,
2382 /// child: Semantics(
2383 /// explicitChildNodes: true,
2384 /// child: const PhysicalModel( // B
2385 /// color: Colors.brown,
2386 /// elevation: 5.0,
2387 /// child: PhysicalModel( // C
2388 /// color: Colors.cyan,
2389 /// elevation: 10.0,
2390 /// child: Placeholder(),
2391 /// ),
2392 /// ),
2393 /// ),
2394 /// )
2395 /// ```
2396 /// {@end-tool}
2397 ///
2398 /// See also:
2399 ///
2400 /// * [elevation], which describes the elevation of the box defined by
2401 /// [thickness] and [rect] relative to the parent of this [SemanticsNode].
2402 double get thickness => _thickness;
2403 double _thickness = _kEmptyConfig.thickness;
2404
2405 /// Provides hint values which override the default hints on supported
2406 /// platforms.
2407 SemanticsHintOverrides? get hintOverrides => _hintOverrides;
2408 SemanticsHintOverrides? _hintOverrides;
2409
2410 /// The reading direction for [label], [value], [hint], [increasedValue], and
2411 /// [decreasedValue].
2412 TextDirection? get textDirection => _textDirection;
2413 TextDirection? _textDirection = _kEmptyConfig.textDirection;
2414
2415 /// Determines the position of this node among its siblings in the traversal
2416 /// sort order.
2417 ///
2418 /// This is used to describe the order in which the semantic node should be
2419 /// traversed by the accessibility services on the platform (e.g. VoiceOver
2420 /// on iOS and TalkBack on Android).
2421 SemanticsSortKey? get sortKey => _sortKey;
2422 SemanticsSortKey? _sortKey;
2423
2424 /// The currently selected text (or the position of the cursor) within [value]
2425 /// if this node represents a text field.
2426 TextSelection? get textSelection => _textSelection;
2427 TextSelection? _textSelection;
2428
2429 /// If this node represents a text field, this indicates whether or not it's
2430 /// a multiline text field.
2431 bool? get isMultiline => _isMultiline;
2432 bool? _isMultiline;
2433
2434 /// The total number of scrollable children that contribute to semantics.
2435 ///
2436 /// If the number of children are unknown or unbounded, this value will be
2437 /// null.
2438 int? get scrollChildCount => _scrollChildCount;
2439 int? _scrollChildCount;
2440
2441 /// The index of the first visible semantic child of a scroll node.
2442 int? get scrollIndex => _scrollIndex;
2443 int? _scrollIndex;
2444
2445 /// Indicates the current scrolling position in logical pixels if the node is
2446 /// scrollable.
2447 ///
2448 /// The properties [scrollExtentMin] and [scrollExtentMax] indicate the valid
2449 /// in-range values for this property. The value for [scrollPosition] may
2450 /// (temporarily) be outside that range, e.g. during an overscroll.
2451 ///
2452 /// See also:
2453 ///
2454 /// * [ScrollPosition.pixels], from where this value is usually taken.
2455 double? get scrollPosition => _scrollPosition;
2456 double? _scrollPosition;
2457
2458 /// Indicates the maximum in-range value for [scrollPosition] if the node is
2459 /// scrollable.
2460 ///
2461 /// This value may be infinity if the scroll is unbound.
2462 ///
2463 /// See also:
2464 ///
2465 /// * [ScrollPosition.maxScrollExtent], from where this value is usually taken.
2466 double? get scrollExtentMax => _scrollExtentMax;
2467 double? _scrollExtentMax;
2468
2469 /// Indicates the minimum in-range value for [scrollPosition] if the node is
2470 /// scrollable.
2471 ///
2472 /// This value may be infinity if the scroll is unbound.
2473 ///
2474 /// See also:
2475 ///
2476 /// * [ScrollPosition.minScrollExtent] from where this value is usually taken.
2477 double? get scrollExtentMin => _scrollExtentMin;
2478 double? _scrollExtentMin;
2479
2480 /// The id of the platform view, whose semantics nodes will be added as
2481 /// children to this node.
2482 ///
2483 /// If this value is non-null, the SemanticsNode must not have any children
2484 /// as those would be replaced by the semantics nodes of the referenced
2485 /// platform view.
2486 ///
2487 /// See also:
2488 ///
2489 /// * [AndroidView], which is the platform view for Android.
2490 /// * [UiKitView], which is the platform view for iOS.
2491 int? get platformViewId => _platformViewId;
2492 int? _platformViewId;
2493
2494 /// The maximum number of characters that can be entered into an editable
2495 /// text field.
2496 ///
2497 /// For the purpose of this function a character is defined as one Unicode
2498 /// scalar value.
2499 ///
2500 /// This should only be set when [SemanticsFlag.isTextField] is set. Defaults
2501 /// to null, which means no limit is imposed on the text field.
2502 int? get maxValueLength => _maxValueLength;
2503 int? _maxValueLength;
2504
2505 /// The current number of characters that have been entered into an editable
2506 /// text field.
2507 ///
2508 /// For the purpose of this function a character is defined as one Unicode
2509 /// scalar value.
2510 ///
2511 /// This should only be set when [SemanticsFlag.isTextField] is set. Must be
2512 /// set when [maxValueLength] is set.
2513 int? get currentValueLength => _currentValueLength;
2514 int? _currentValueLength;
2515
2516 bool _canPerformAction(SemanticsAction action) => _actions.containsKey(action);
2517
2518 static final SemanticsConfiguration _kEmptyConfig = SemanticsConfiguration();
2519
2520 /// Reconfigures the properties of this object to describe the configuration
2521 /// provided in the `config` argument and the children listed in the
2522 /// `childrenInInversePaintOrder` argument.
2523 ///
2524 /// The arguments may be null; this represents an empty configuration (all
2525 /// values at their defaults, no children).
2526 ///
2527 /// No reference is kept to the [SemanticsConfiguration] object, but the child
2528 /// list is used as-is and should therefore not be changed after this call.
2529 void updateWith({
2530 required SemanticsConfiguration? config,
2531 List<SemanticsNode>? childrenInInversePaintOrder,
2532 }) {
2533 config ??= _kEmptyConfig;
2534 if (_isDifferentFromCurrentSemanticAnnotation(config)) {
2535 _markDirty();
2536 }
2537
2538 assert(
2539 config.platformViewId == null || childrenInInversePaintOrder == null || childrenInInversePaintOrder.isEmpty,
2540 'SemanticsNodes with children must not specify a platformViewId.',
2541 );
2542
2543 final bool mergeAllDescendantsIntoThisNodeValueChanged = _mergeAllDescendantsIntoThisNode != config.isMergingSemanticsOfDescendants;
2544
2545 _identifier = config.identifier;
2546 _attributedLabel = config.attributedLabel;
2547 _attributedValue = config.attributedValue;
2548 _attributedIncreasedValue = config.attributedIncreasedValue;
2549 _attributedDecreasedValue = config.attributedDecreasedValue;
2550 _attributedHint = config.attributedHint;
2551 _tooltip = config.tooltip;
2552 _hintOverrides = config.hintOverrides;
2553 _elevation = config.elevation;
2554 _thickness = config.thickness;
2555 _flags = config._flags;
2556 _textDirection = config.textDirection;
2557 _sortKey = config.sortKey;
2558 _actions = Map<SemanticsAction, SemanticsActionHandler>.of(config._actions);
2559 _customSemanticsActions = Map<CustomSemanticsAction, VoidCallback>.of(config._customSemanticsActions);
2560 _actionsAsBits = config._actionsAsBits;
2561 _textSelection = config._textSelection;
2562 _isMultiline = config.isMultiline;
2563 _scrollPosition = config._scrollPosition;
2564 _scrollExtentMax = config._scrollExtentMax;
2565 _scrollExtentMin = config._scrollExtentMin;
2566 _mergeAllDescendantsIntoThisNode = config.isMergingSemanticsOfDescendants;
2567 _scrollChildCount = config.scrollChildCount;
2568 _scrollIndex = config.scrollIndex;
2569 indexInParent = config.indexInParent;
2570 _platformViewId = config._platformViewId;
2571 _maxValueLength = config._maxValueLength;
2572 _currentValueLength = config._currentValueLength;
2573 _areUserActionsBlocked = config.isBlockingUserActions;
2574 _replaceChildren(childrenInInversePaintOrder ?? const <SemanticsNode>[]);
2575
2576 if (mergeAllDescendantsIntoThisNodeValueChanged) {
2577 _updateChildrenMergeFlags();
2578 }
2579
2580 assert(
2581 !_canPerformAction(SemanticsAction.increase) || (value == '') == (increasedValue == ''),
2582 'A SemanticsNode with action "increase" needs to be annotated with either both "value" and "increasedValue" or neither',
2583 );
2584 assert(
2585 !_canPerformAction(SemanticsAction.decrease) || (value == '') == (decreasedValue == ''),
2586 'A SemanticsNode with action "decrease" needs to be annotated with either both "value" and "decreasedValue" or neither',
2587 );
2588 }
2589
2590
2591 /// Returns a summary of the semantics for this node.
2592 ///
2593 /// If this node has [mergeAllDescendantsIntoThisNode], then the returned data
2594 /// includes the information from this node's descendants. Otherwise, the
2595 /// returned data matches the data on this node.
2596 SemanticsData getSemanticsData() {
2597 int flags = _flags;
2598 // Can't use _effectiveActionsAsBits here. The filtering of action bits
2599 // must be done after the merging the its descendants.
2600 int actions = _actionsAsBits;
2601 String identifier = _identifier;
2602 AttributedString attributedLabel = _attributedLabel;
2603 AttributedString attributedValue = _attributedValue;
2604 AttributedString attributedIncreasedValue = _attributedIncreasedValue;
2605 AttributedString attributedDecreasedValue = _attributedDecreasedValue;
2606 AttributedString attributedHint = _attributedHint;
2607 String tooltip = _tooltip;
2608 TextDirection? textDirection = _textDirection;
2609 Set<SemanticsTag>? mergedTags = tags == null ? null : Set<SemanticsTag>.of(tags!);
2610 TextSelection? textSelection = _textSelection;
2611 int? scrollChildCount = _scrollChildCount;
2612 int? scrollIndex = _scrollIndex;
2613 double? scrollPosition = _scrollPosition;
2614 double? scrollExtentMax = _scrollExtentMax;
2615 double? scrollExtentMin = _scrollExtentMin;
2616 int? platformViewId = _platformViewId;
2617 int? maxValueLength = _maxValueLength;
2618 int? currentValueLength = _currentValueLength;
2619 final double elevation = _elevation;
2620 double thickness = _thickness;
2621 final Set<int> customSemanticsActionIds = <int>{};
2622 for (final CustomSemanticsAction action in _customSemanticsActions.keys) {
2623 customSemanticsActionIds.add(CustomSemanticsAction.getIdentifier(action));
2624 }
2625 if (hintOverrides != null) {
2626 if (hintOverrides!.onTapHint != null) {
2627 final CustomSemanticsAction action = CustomSemanticsAction.overridingAction(
2628 hint: hintOverrides!.onTapHint!,
2629 action: SemanticsAction.tap,
2630 );
2631 customSemanticsActionIds.add(CustomSemanticsAction.getIdentifier(action));
2632 }
2633 if (hintOverrides!.onLongPressHint != null) {
2634 final CustomSemanticsAction action = CustomSemanticsAction.overridingAction(
2635 hint: hintOverrides!.onLongPressHint!,
2636 action: SemanticsAction.longPress,
2637 );
2638 customSemanticsActionIds.add(CustomSemanticsAction.getIdentifier(action));
2639 }
2640 }
2641
2642 if (mergeAllDescendantsIntoThisNode) {
2643 _visitDescendants((SemanticsNode node) {
2644 assert(node.isMergedIntoParent);
2645 flags |= node._flags;
2646 actions |= node._effectiveActionsAsBits;
2647
2648 textDirection ??= node._textDirection;
2649 textSelection ??= node._textSelection;
2650 scrollChildCount ??= node._scrollChildCount;
2651 scrollIndex ??= node._scrollIndex;
2652 scrollPosition ??= node._scrollPosition;
2653 scrollExtentMax ??= node._scrollExtentMax;
2654 scrollExtentMin ??= node._scrollExtentMin;
2655 platformViewId ??= node._platformViewId;
2656 maxValueLength ??= node._maxValueLength;
2657 currentValueLength ??= node._currentValueLength;
2658 if (identifier == '') {
2659 identifier = node._identifier;
2660 }
2661 if (attributedValue.string == '') {
2662 attributedValue = node._attributedValue;
2663 }
2664 if (attributedIncreasedValue.string == '') {
2665 attributedIncreasedValue = node._attributedIncreasedValue;
2666 }
2667 if (attributedDecreasedValue.string == '') {
2668 attributedDecreasedValue = node._attributedDecreasedValue;
2669 }
2670 if (tooltip == '') {
2671 tooltip = node._tooltip;
2672 }
2673 if (node.tags != null) {
2674 mergedTags ??= <SemanticsTag>{};
2675 mergedTags!.addAll(node.tags!);
2676 }
2677 for (final CustomSemanticsAction action in _customSemanticsActions.keys) {
2678 customSemanticsActionIds.add(CustomSemanticsAction.getIdentifier(action));
2679 }
2680 if (node.hintOverrides != null) {
2681 if (node.hintOverrides!.onTapHint != null) {
2682 final CustomSemanticsAction action = CustomSemanticsAction.overridingAction(
2683 hint: node.hintOverrides!.onTapHint!,
2684 action: SemanticsAction.tap,
2685 );
2686 customSemanticsActionIds.add(CustomSemanticsAction.getIdentifier(action));
2687 }
2688 if (node.hintOverrides!.onLongPressHint != null) {
2689 final CustomSemanticsAction action = CustomSemanticsAction.overridingAction(
2690 hint: node.hintOverrides!.onLongPressHint!,
2691 action: SemanticsAction.longPress,
2692 );
2693 customSemanticsActionIds.add(CustomSemanticsAction.getIdentifier(action));
2694 }
2695 }
2696 attributedLabel = _concatAttributedString(
2697 thisAttributedString: attributedLabel,
2698 thisTextDirection: textDirection,
2699 otherAttributedString: node._attributedLabel,
2700 otherTextDirection: node._textDirection,
2701 );
2702 attributedHint = _concatAttributedString(
2703 thisAttributedString: attributedHint,
2704 thisTextDirection: textDirection,
2705 otherAttributedString: node._attributedHint,
2706 otherTextDirection: node._textDirection,
2707 );
2708
2709 thickness = math.max(thickness, node._thickness + node._elevation);
2710
2711 return true;
2712 });
2713 }
2714
2715 return SemanticsData(
2716 flags: flags,
2717 actions: _areUserActionsBlocked ? actions & _kUnblockedUserActions : actions,
2718 identifier: identifier,
2719 attributedLabel: attributedLabel,
2720 attributedValue: attributedValue,
2721 attributedIncreasedValue: attributedIncreasedValue,
2722 attributedDecreasedValue: attributedDecreasedValue,
2723 attributedHint: attributedHint,
2724 tooltip: tooltip,
2725 textDirection: textDirection,
2726 rect: rect,
2727 transform: transform,
2728 elevation: elevation,
2729 thickness: thickness,
2730 tags: mergedTags,
2731 textSelection: textSelection,
2732 scrollChildCount: scrollChildCount,
2733 scrollIndex: scrollIndex,
2734 scrollPosition: scrollPosition,
2735 scrollExtentMax: scrollExtentMax,
2736 scrollExtentMin: scrollExtentMin,
2737 platformViewId: platformViewId,
2738 maxValueLength: maxValueLength,
2739 currentValueLength: currentValueLength,
2740 customSemanticsActionIds: customSemanticsActionIds.toList()..sort(),
2741 );
2742 }
2743
2744 static Float64List _initIdentityTransform() {
2745 return Matrix4.identity().storage;
2746 }
2747
2748 static final Int32List _kEmptyChildList = Int32List(0);
2749 static final Int32List _kEmptyCustomSemanticsActionsList = Int32List(0);
2750 static final Float64List _kIdentityTransform = _initIdentityTransform();
2751
2752 void _addToUpdate(SemanticsUpdateBuilder builder, Set<int> customSemanticsActionIdsUpdate) {
2753 assert(_dirty);
2754 final SemanticsData data = getSemanticsData();
2755 final Int32List childrenInTraversalOrder;
2756 final Int32List childrenInHitTestOrder;
2757 if (!hasChildren || mergeAllDescendantsIntoThisNode) {
2758 childrenInTraversalOrder = _kEmptyChildList;
2759 childrenInHitTestOrder = _kEmptyChildList;
2760 } else {
2761 final int childCount = _children!.length;
2762 final List<SemanticsNode> sortedChildren = _childrenInTraversalOrder();
2763 childrenInTraversalOrder = Int32List(childCount);
2764 for (int i = 0; i < childCount; i += 1) {
2765 childrenInTraversalOrder[i] = sortedChildren[i].id;
2766 }
2767 // _children is sorted in paint order, so we invert it to get the hit test
2768 // order.
2769 childrenInHitTestOrder = Int32List(childCount);
2770 for (int i = childCount - 1; i >= 0; i -= 1) {
2771 childrenInHitTestOrder[i] = _children![childCount - i - 1].id;
2772 }
2773 }
2774 Int32List? customSemanticsActionIds;
2775 if (data.customSemanticsActionIds?.isNotEmpty ?? false) {
2776 customSemanticsActionIds = Int32List(data.customSemanticsActionIds!.length);
2777 for (int i = 0; i < data.customSemanticsActionIds!.length; i++) {
2778 customSemanticsActionIds[i] = data.customSemanticsActionIds![i];
2779 customSemanticsActionIdsUpdate.add(data.customSemanticsActionIds![i]);
2780 }
2781 }
2782 builder.updateNode(
2783 id: id,
2784 flags: data.flags,
2785 actions: data.actions,
2786 rect: data.rect,
2787 identifier: data.identifier,
2788 label: data.attributedLabel.string,
2789 labelAttributes: data.attributedLabel.attributes,
2790 value: data.attributedValue.string,
2791 valueAttributes: data.attributedValue.attributes,
2792 increasedValue: data.attributedIncreasedValue.string,
2793 increasedValueAttributes: data.attributedIncreasedValue.attributes,
2794 decreasedValue: data.attributedDecreasedValue.string,
2795 decreasedValueAttributes: data.attributedDecreasedValue.attributes,
2796 hint: data.attributedHint.string,
2797 hintAttributes: data.attributedHint.attributes,
2798 tooltip: data.tooltip,
2799 textDirection: data.textDirection,
2800 textSelectionBase: data.textSelection != null ? data.textSelection!.baseOffset : -1,
2801 textSelectionExtent: data.textSelection != null ? data.textSelection!.extentOffset : -1,
2802 platformViewId: data.platformViewId ?? -1,
2803 maxValueLength: data.maxValueLength ?? -1,
2804 currentValueLength: data.currentValueLength ?? -1,
2805 scrollChildren: data.scrollChildCount ?? 0,
2806 scrollIndex: data.scrollIndex ?? 0 ,
2807 scrollPosition: data.scrollPosition ?? double.nan,
2808 scrollExtentMax: data.scrollExtentMax ?? double.nan,
2809 scrollExtentMin: data.scrollExtentMin ?? double.nan,
2810 transform: data.transform?.storage ?? _kIdentityTransform,
2811 elevation: data.elevation,
2812 thickness: data.thickness,
2813 childrenInTraversalOrder: childrenInTraversalOrder,
2814 childrenInHitTestOrder: childrenInHitTestOrder,
2815 additionalActions: customSemanticsActionIds ?? _kEmptyCustomSemanticsActionsList,
2816 );
2817 _dirty = false;
2818 }
2819
2820 /// Builds a new list made of [_children] sorted in semantic traversal order.
2821 List<SemanticsNode> _childrenInTraversalOrder() {
2822 TextDirection? inheritedTextDirection = textDirection;
2823 SemanticsNode? ancestor = parent;
2824 while (inheritedTextDirection == null && ancestor != null) {
2825 inheritedTextDirection = ancestor.textDirection;
2826 ancestor = ancestor.parent;
2827 }
2828
2829 List<SemanticsNode>? childrenInDefaultOrder;
2830 if (inheritedTextDirection != null) {
2831 childrenInDefaultOrder = _childrenInDefaultOrder(_children!, inheritedTextDirection);
2832 } else {
2833 // In the absence of text direction default to paint order.
2834 childrenInDefaultOrder = _children;
2835 }
2836
2837 // List.sort does not guarantee stable sort order. Therefore, children are
2838 // first partitioned into groups that have compatible sort keys, i.e. keys
2839 // in the same group can be compared to each other. These groups stay in
2840 // the same place. Only children within the same group are sorted.
2841 final List<_TraversalSortNode> everythingSorted = <_TraversalSortNode>[];
2842 final List<_TraversalSortNode> sortNodes = <_TraversalSortNode>[];
2843 SemanticsSortKey? lastSortKey;
2844 for (int position = 0; position < childrenInDefaultOrder!.length; position += 1) {
2845 final SemanticsNode child = childrenInDefaultOrder[position];
2846 final SemanticsSortKey? sortKey = child.sortKey;
2847 lastSortKey = position > 0
2848 ? childrenInDefaultOrder[position - 1].sortKey
2849 : null;
2850 final bool isCompatibleWithPreviousSortKey = position == 0 ||
2851 sortKey.runtimeType == lastSortKey.runtimeType &&
2852 (sortKey == null || sortKey.name == lastSortKey!.name);
2853 if (!isCompatibleWithPreviousSortKey && sortNodes.isNotEmpty) {
2854 // Do not sort groups with null sort keys. List.sort does not guarantee
2855 // a stable sort order.
2856 if (lastSortKey != null) {
2857 sortNodes.sort();
2858 }
2859 everythingSorted.addAll(sortNodes);
2860 sortNodes.clear();
2861 }
2862
2863 sortNodes.add(_TraversalSortNode(
2864 node: child,
2865 sortKey: sortKey,
2866 position: position,
2867 ));
2868 }
2869
2870 // Do not sort groups with null sort keys. List.sort does not guarantee
2871 // a stable sort order.
2872 if (lastSortKey != null) {
2873 sortNodes.sort();
2874 }
2875 everythingSorted.addAll(sortNodes);
2876
2877 return everythingSorted
2878 .map<SemanticsNode>((_TraversalSortNode sortNode) => sortNode.node)
2879 .toList();
2880 }
2881
2882 /// Sends a [SemanticsEvent] associated with this [SemanticsNode].
2883 ///
2884 /// Semantics events should be sent to inform interested parties (like
2885 /// the accessibility system of the operating system) about changes to the UI.
2886 void sendEvent(SemanticsEvent event) {
2887 if (!attached) {
2888 return;
2889 }
2890 SystemChannels.accessibility.send(event.toMap(nodeId: id));
2891 }
2892
2893 bool _debugIsActionBlocked(SemanticsAction action) {
2894 bool result = false;
2895 assert((){
2896 result = (_effectiveActionsAsBits & action.index) == 0;
2897 return true;
2898 }());
2899 return result;
2900 }
2901
2902 @override
2903 String toStringShort() => '${objectRuntimeType(this, 'SemanticsNode')}#$id';
2904
2905 @override
2906 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
2907 super.debugFillProperties(properties);
2908 bool hideOwner = true;
2909 if (_dirty) {
2910 final bool inDirtyNodes = owner != null && owner!._dirtyNodes.contains(this);
2911 properties.add(FlagProperty('inDirtyNodes', value: inDirtyNodes, ifTrue: 'dirty', ifFalse: 'STALE'));
2912 hideOwner = inDirtyNodes;
2913 }
2914 properties.add(DiagnosticsProperty<SemanticsOwner>('owner', owner, level: hideOwner ? DiagnosticLevel.hidden : DiagnosticLevel.info));
2915 properties.add(FlagProperty('isMergedIntoParent', value: isMergedIntoParent, ifTrue: 'merged up ⬆️'));
2916 properties.add(FlagProperty('mergeAllDescendantsIntoThisNode', value: mergeAllDescendantsIntoThisNode, ifTrue: 'merge boundary ⛔️'));
2917 final Offset? offset = transform != null ? MatrixUtils.getAsTranslation(transform!) : null;
2918 if (offset != null) {
2919 properties.add(DiagnosticsProperty<Rect>('rect', rect.shift(offset), showName: false));
2920 } else {
2921 final double? scale = transform != null ? MatrixUtils.getAsScale(transform!) : null;
2922 String? description;
2923 if (scale != null) {
2924 description = '$rect scaled by ${scale.toStringAsFixed(1)}x';
2925 } else if (transform != null && !MatrixUtils.isIdentity(transform!)) {
2926 final String matrix = transform.toString().split('\n').take(4).map<String>((String line) => line.substring(4)).join('; ');
2927 description = '$rect with transform [$matrix]';
2928 }
2929 properties.add(DiagnosticsProperty<Rect>('rect', rect, description: description, showName: false));
2930 }
2931 properties.add(IterableProperty<String>('tags', tags?.map((SemanticsTag tag) => tag.name), defaultValue: null));
2932 final List<String> actions = _actions.keys.map<String>((SemanticsAction action) => '${action.name}${_debugIsActionBlocked(action) ? '🚫️' : ''}').toList()..sort();
2933 final List<String?> customSemanticsActions = _customSemanticsActions.keys
2934 .map<String?>((CustomSemanticsAction action) => action.label)
2935 .toList();
2936 properties.add(IterableProperty<String>('actions', actions, ifEmpty: null));
2937 properties.add(IterableProperty<String?>('customActions', customSemanticsActions, ifEmpty: null));
2938 final List<String> flags = SemanticsFlag.values.where((SemanticsFlag flag) => hasFlag(flag)).map((SemanticsFlag flag) => flag.name).toList();
2939 properties.add(IterableProperty<String>('flags', flags, ifEmpty: null));
2940 properties.add(FlagProperty('isInvisible', value: isInvisible, ifTrue: 'invisible'));
2941 properties.add(FlagProperty('isHidden', value: hasFlag(SemanticsFlag.isHidden), ifTrue: 'HIDDEN'));
2942 properties.add(StringProperty('identifier', _identifier, defaultValue: ''));
2943 properties.add(AttributedStringProperty('label', _attributedLabel));
2944 properties.add(AttributedStringProperty('value', _attributedValue));
2945 properties.add(AttributedStringProperty('increasedValue', _attributedIncreasedValue));
2946 properties.add(AttributedStringProperty('decreasedValue', _attributedDecreasedValue));
2947 properties.add(AttributedStringProperty('hint', _attributedHint));
2948 properties.add(StringProperty('tooltip', _tooltip, defaultValue: ''));
2949 properties.add(EnumProperty<TextDirection>('textDirection', _textDirection, defaultValue: null));
2950 properties.add(DiagnosticsProperty<SemanticsSortKey>('sortKey', sortKey, defaultValue: null));
2951 if (_textSelection?.isValid ?? false) {
2952 properties.add(MessageProperty('text selection', '[${_textSelection!.start}, ${_textSelection!.end}]'));
2953 }
2954 properties.add(IntProperty('platformViewId', platformViewId, defaultValue: null));
2955 properties.add(IntProperty('maxValueLength', maxValueLength, defaultValue: null));
2956 properties.add(IntProperty('currentValueLength', currentValueLength, defaultValue: null));
2957 properties.add(IntProperty('scrollChildren', scrollChildCount, defaultValue: null));
2958 properties.add(IntProperty('scrollIndex', scrollIndex, defaultValue: null));
2959 properties.add(DoubleProperty('scrollExtentMin', scrollExtentMin, defaultValue: null));
2960 properties.add(DoubleProperty('scrollPosition', scrollPosition, defaultValue: null));
2961 properties.add(DoubleProperty('scrollExtentMax', scrollExtentMax, defaultValue: null));
2962 properties.add(DoubleProperty('elevation', elevation, defaultValue: 0.0));
2963 properties.add(DoubleProperty('thickness', thickness, defaultValue: 0.0));
2964 }
2965
2966 /// Returns a string representation of this node and its descendants.
2967 ///
2968 /// The order in which the children of the [SemanticsNode] will be printed is
2969 /// controlled by the [childOrder] parameter.
2970 @override
2971 String toStringDeep({
2972 String prefixLineOne = '',
2973 String? prefixOtherLines,
2974 DiagnosticLevel minLevel = DiagnosticLevel.debug,
2975 DebugSemanticsDumpOrder childOrder = DebugSemanticsDumpOrder.traversalOrder,
2976 }) {
2977 return toDiagnosticsNode(childOrder: childOrder).toStringDeep(prefixLineOne: prefixLineOne, prefixOtherLines: prefixOtherLines, minLevel: minLevel);
2978 }
2979
2980 @override
2981 DiagnosticsNode toDiagnosticsNode({
2982 String? name,
2983 DiagnosticsTreeStyle? style = DiagnosticsTreeStyle.sparse,
2984 DebugSemanticsDumpOrder childOrder = DebugSemanticsDumpOrder.traversalOrder,
2985 }) {
2986 return _SemanticsDiagnosticableNode(
2987 name: name,
2988 value: this,
2989 style: style,
2990 childOrder: childOrder,
2991 );
2992 }
2993
2994 @override
2995 List<DiagnosticsNode> debugDescribeChildren({ DebugSemanticsDumpOrder childOrder = DebugSemanticsDumpOrder.inverseHitTest }) {
2996 return debugListChildrenInOrder(childOrder)
2997 .map<DiagnosticsNode>((SemanticsNode node) => node.toDiagnosticsNode(childOrder: childOrder))
2998 .toList();
2999 }
3000
3001 /// Returns the list of direct children of this node in the specified order.
3002 List<SemanticsNode> debugListChildrenInOrder(DebugSemanticsDumpOrder childOrder) {
3003 if (_children == null) {
3004 return const <SemanticsNode>[];
3005 }
3006
3007 switch (childOrder) {
3008 case DebugSemanticsDumpOrder.inverseHitTest:
3009 return _children!;
3010 case DebugSemanticsDumpOrder.traversalOrder:
3011 return _childrenInTraversalOrder();
3012 }
3013 }
3014}
3015
3016/// An edge of a box, such as top, bottom, left or right, used to compute
3017/// [SemanticsNode]s that overlap vertically or horizontally.
3018///
3019/// For computing horizontal overlap in an LTR setting we create two [_BoxEdge]
3020/// objects for each [SemanticsNode]: one representing the left edge (marked
3021/// with [isLeadingEdge] equal to true) and one for the right edge (with [isLeadingEdge]
3022/// equal to false). Similarly, for vertical overlap we also create two objects
3023/// for each [SemanticsNode], one for the top and one for the bottom edge.
3024class _BoxEdge implements Comparable<_BoxEdge> {
3025 _BoxEdge({
3026 required this.isLeadingEdge,
3027 required this.offset,
3028 required this.node,
3029 }) : assert(offset.isFinite);
3030
3031 /// True if the edge comes before the seconds edge along the traversal
3032 /// direction, and false otherwise.
3033 ///
3034 /// This field is never null.
3035 ///
3036 /// For example, in LTR traversal the left edge's [isLeadingEdge] is set to true,
3037 /// the right edge's [isLeadingEdge] is set to false. When considering vertical
3038 /// ordering of boxes, the top edge is the start edge, and the bottom edge is
3039 /// the end edge.
3040 final bool isLeadingEdge;
3041
3042 /// The offset from the start edge of the parent [SemanticsNode] in the
3043 /// direction of the traversal.
3044 final double offset;
3045
3046 /// The node whom this edge belongs.
3047 final SemanticsNode node;
3048
3049 @override
3050 int compareTo(_BoxEdge other) {
3051 return offset.compareTo(other.offset);
3052 }
3053}
3054
3055/// A group of [nodes] that are disjoint vertically or horizontally from other
3056/// nodes that share the same [SemanticsNode] parent.
3057///
3058/// The [nodes] are sorted among each other separately from other nodes.
3059class _SemanticsSortGroup implements Comparable<_SemanticsSortGroup> {
3060 _SemanticsSortGroup({
3061 required this.startOffset,
3062 required this.textDirection,
3063 });
3064
3065 /// The offset from the start edge of the parent [SemanticsNode] in the
3066 /// direction of the traversal.
3067 ///
3068 /// This value is equal to the [_BoxEdge.offset] of the first node in the
3069 /// [nodes] list being considered.
3070 final double startOffset;
3071
3072 final TextDirection textDirection;
3073
3074 /// The nodes that are sorted among each other.
3075 final List<SemanticsNode> nodes = <SemanticsNode>[];
3076
3077 @override
3078 int compareTo(_SemanticsSortGroup other) {
3079 return startOffset.compareTo(other.startOffset);
3080 }
3081
3082 /// Sorts this group assuming that [nodes] belong to the same vertical group.
3083 ///
3084 /// This method breaks up this group into horizontal [_SemanticsSortGroup]s
3085 /// then sorts them using [sortedWithinKnot].
3086 List<SemanticsNode> sortedWithinVerticalGroup() {
3087 final List<_BoxEdge> edges = <_BoxEdge>[];
3088 for (final SemanticsNode child in nodes) {
3089 // Using a small delta to shrink child rects removes overlapping cases.
3090 final Rect childRect = child.rect.deflate(0.1);
3091 edges.add(_BoxEdge(
3092 isLeadingEdge: true,
3093 offset: _pointInParentCoordinates(child, childRect.topLeft).dx,
3094 node: child,
3095 ));
3096 edges.add(_BoxEdge(
3097 isLeadingEdge: false,
3098 offset: _pointInParentCoordinates(child, childRect.bottomRight).dx,
3099 node: child,
3100 ));
3101 }
3102 edges.sort();
3103
3104 List<_SemanticsSortGroup> horizontalGroups = <_SemanticsSortGroup>[];
3105 _SemanticsSortGroup? group;
3106 int depth = 0;
3107 for (final _BoxEdge edge in edges) {
3108 if (edge.isLeadingEdge) {
3109 depth += 1;
3110 group ??= _SemanticsSortGroup(
3111 startOffset: edge.offset,
3112 textDirection: textDirection,
3113 );
3114 group.nodes.add(edge.node);
3115 } else {
3116 depth -= 1;
3117 }
3118 if (depth == 0) {
3119 horizontalGroups.add(group!);
3120 group = null;
3121 }
3122 }
3123 horizontalGroups.sort();
3124
3125 if (textDirection == TextDirection.rtl) {
3126 horizontalGroups = horizontalGroups.reversed.toList();
3127 }
3128
3129 return horizontalGroups
3130 .expand((_SemanticsSortGroup group) => group.sortedWithinKnot())
3131 .toList();
3132 }
3133
3134 /// Sorts [nodes] where nodes intersect both vertically and horizontally.
3135 ///
3136 /// In the special case when [nodes] contains one or less nodes, this method
3137 /// returns [nodes] unchanged.
3138 ///
3139 /// This method constructs a graph, where vertices are [SemanticsNode]s and
3140 /// edges are "traversed before" relation between pairs of nodes. The sort
3141 /// order is the topological sorting of the graph, with the original order of
3142 /// [nodes] used as the tie breaker.
3143 ///
3144 /// Whether a node is traversed before another node is determined by the
3145 /// vector that connects the two nodes' centers. If the vector "points to the
3146 /// right or down", defined as the [Offset.direction] being between `-pi/4`
3147 /// and `3*pi/4`), then the semantics node whose center is at the end of the
3148 /// vector is said to be traversed after.
3149 List<SemanticsNode> sortedWithinKnot() {
3150 if (nodes.length <= 1) {
3151 // Trivial knot. Nothing to do.
3152 return nodes;
3153 }
3154 final Map<int, SemanticsNode> nodeMap = <int, SemanticsNode>{};
3155 final Map<int, int> edges = <int, int>{};
3156 for (final SemanticsNode node in nodes) {
3157 nodeMap[node.id] = node;
3158 final Offset center = _pointInParentCoordinates(node, node.rect.center);
3159 for (final SemanticsNode nextNode in nodes) {
3160 if (identical(node, nextNode) || edges[nextNode.id] == node.id) {
3161 // Skip self or when we've already established that the next node
3162 // points to current node.
3163 continue;
3164 }
3165
3166 final Offset nextCenter = _pointInParentCoordinates(nextNode, nextNode.rect.center);
3167 final Offset centerDelta = nextCenter - center;
3168 // When centers coincide, direction is 0.0.
3169 final double direction = centerDelta.direction;
3170 final bool isLtrAndForward = textDirection == TextDirection.ltr &&
3171 -math.pi / 4 < direction && direction < 3 * math.pi / 4;
3172 final bool isRtlAndForward = textDirection == TextDirection.rtl &&
3173 (direction < -3 * math.pi / 4 || direction > 3 * math.pi / 4);
3174 if (isLtrAndForward || isRtlAndForward) {
3175 edges[node.id] = nextNode.id;
3176 }
3177 }
3178 }
3179
3180 final List<int> sortedIds = <int>[];
3181 final Set<int> visitedIds = <int>{};
3182 final List<SemanticsNode> startNodes = nodes.toList()..sort((SemanticsNode a, SemanticsNode b) {
3183 final Offset aTopLeft = _pointInParentCoordinates(a, a.rect.topLeft);
3184 final Offset bTopLeft = _pointInParentCoordinates(b, b.rect.topLeft);
3185 final int verticalDiff = aTopLeft.dy.compareTo(bTopLeft.dy);
3186 if (verticalDiff != 0) {
3187 return -verticalDiff;
3188 }
3189 return -aTopLeft.dx.compareTo(bTopLeft.dx);
3190 });
3191
3192 void search(int id) {
3193 if (visitedIds.contains(id)) {
3194 return;
3195 }
3196 visitedIds.add(id);
3197 if (edges.containsKey(id)) {
3198 search(edges[id]!);
3199 }
3200 sortedIds.add(id);
3201 }
3202
3203 startNodes.map<int>((SemanticsNode node) => node.id).forEach(search);
3204 return sortedIds.map<SemanticsNode>((int id) => nodeMap[id]!).toList().reversed.toList();
3205 }
3206}
3207
3208/// Converts `point` to the `node`'s parent's coordinate system.
3209Offset _pointInParentCoordinates(SemanticsNode node, Offset point) {
3210 if (node.transform == null) {
3211 return point;
3212 }
3213 final Vector3 vector = Vector3(point.dx, point.dy, 0.0);
3214 node.transform!.transform3(vector);
3215 return Offset(vector.x, vector.y);
3216}
3217
3218/// Sorts `children` using the default sorting algorithm, and returns them as a
3219/// new list.
3220///
3221/// The algorithm first breaks up children into groups such that no two nodes
3222/// from different groups overlap vertically. These groups are sorted vertically
3223/// according to their [_SemanticsSortGroup.startOffset].
3224///
3225/// Within each group, the nodes are sorted using
3226/// [_SemanticsSortGroup.sortedWithinVerticalGroup].
3227///
3228/// For an illustration of the algorithm see http://bit.ly/flutter-default-traversal.
3229List<SemanticsNode> _childrenInDefaultOrder(List<SemanticsNode> children, TextDirection textDirection) {
3230 final List<_BoxEdge> edges = <_BoxEdge>[];
3231 for (final SemanticsNode child in children) {
3232 assert(child.rect.isFinite);
3233 // Using a small delta to shrink child rects removes overlapping cases.
3234 final Rect childRect = child.rect.deflate(0.1);
3235 edges.add(_BoxEdge(
3236 isLeadingEdge: true,
3237 offset: _pointInParentCoordinates(child, childRect.topLeft).dy,
3238 node: child,
3239 ));
3240 edges.add(_BoxEdge(
3241 isLeadingEdge: false,
3242 offset: _pointInParentCoordinates(child, childRect.bottomRight).dy,
3243 node: child,
3244 ));
3245 }
3246 edges.sort();
3247
3248 final List<_SemanticsSortGroup> verticalGroups = <_SemanticsSortGroup>[];
3249 _SemanticsSortGroup? group;
3250 int depth = 0;
3251 for (final _BoxEdge edge in edges) {
3252 if (edge.isLeadingEdge) {
3253 depth += 1;
3254 group ??= _SemanticsSortGroup(
3255 startOffset: edge.offset,
3256 textDirection: textDirection,
3257 );
3258 group.nodes.add(edge.node);
3259 } else {
3260 depth -= 1;
3261 }
3262 if (depth == 0) {
3263 verticalGroups.add(group!);
3264 group = null;
3265 }
3266 }
3267 verticalGroups.sort();
3268
3269 return verticalGroups
3270 .expand((_SemanticsSortGroup group) => group.sortedWithinVerticalGroup())
3271 .toList();
3272}
3273
3274/// The implementation of [Comparable] that implements the ordering of
3275/// [SemanticsNode]s in the accessibility traversal.
3276///
3277/// [SemanticsNode]s are sorted prior to sending them to the engine side.
3278///
3279/// This implementation considers a [node]'s [sortKey] and its position within
3280/// the list of its siblings. [sortKey] takes precedence over position.
3281class _TraversalSortNode implements Comparable<_TraversalSortNode> {
3282 _TraversalSortNode({
3283 required this.node,
3284 this.sortKey,
3285 required this.position,
3286 });
3287
3288 /// The node whose position this sort node determines.
3289 final SemanticsNode node;
3290
3291 /// Determines the position of this node among its siblings.
3292 ///
3293 /// Sort keys take precedence over other attributes, such as
3294 /// [position].
3295 final SemanticsSortKey? sortKey;
3296
3297 /// Position within the list of siblings as determined by the default sort
3298 /// order.
3299 final int position;
3300
3301 @override
3302 int compareTo(_TraversalSortNode other) {
3303 if (sortKey == null || other.sortKey == null) {
3304 return position - other.position;
3305 }
3306 return sortKey!.compareTo(other.sortKey!);
3307 }
3308}
3309
3310/// Owns [SemanticsNode] objects and notifies listeners of changes to the
3311/// render tree semantics.
3312///
3313/// To listen for semantic updates, call [SemanticsBinding.ensureSemantics] or
3314/// [PipelineOwner.ensureSemantics] to obtain a [SemanticsHandle]. This will
3315/// create a [SemanticsOwner] if necessary.
3316class SemanticsOwner extends ChangeNotifier {
3317 /// Creates a [SemanticsOwner] that manages zero or more [SemanticsNode] objects.
3318 SemanticsOwner({
3319 required this.onSemanticsUpdate,
3320 }){
3321 // TODO(polina-c): stop duplicating code across disposables
3322 // https://github.com/flutter/flutter/issues/137435
3323 if (kFlutterMemoryAllocationsEnabled) {
3324 FlutterMemoryAllocations.instance.dispatchObjectCreated(
3325 library: 'package:flutter/semantics.dart',
3326 className: '$SemanticsOwner',
3327 object: this,
3328 );
3329 }
3330 }
3331
3332 /// The [onSemanticsUpdate] callback is expected to dispatch [SemanticsUpdate]s
3333 /// to the [FlutterView] that is associated with this [PipelineOwner] and/or
3334 /// [SemanticsOwner].
3335 ///
3336 /// A [SemanticsOwner] calls [onSemanticsUpdate] during [sendSemanticsUpdate]
3337 /// after the [SemanticsUpdate] has been build, but before the [SemanticsOwner]'s
3338 /// listeners have been notified.
3339 final SemanticsUpdateCallback onSemanticsUpdate;
3340 final Set<SemanticsNode> _dirtyNodes = <SemanticsNode>{};
3341 final Map<int, SemanticsNode> _nodes = <int, SemanticsNode>{};
3342 final Set<SemanticsNode> _detachedNodes = <SemanticsNode>{};
3343
3344 /// The root node of the semantics tree, if any.
3345 ///
3346 /// If the semantics tree is empty, returns null.
3347 SemanticsNode? get rootSemanticsNode => _nodes[0];
3348
3349 @override
3350 void dispose() {
3351 if (kFlutterMemoryAllocationsEnabled) {
3352 FlutterMemoryAllocations.instance.dispatchObjectDisposed(object: this);
3353 }
3354 _dirtyNodes.clear();
3355 _nodes.clear();
3356 _detachedNodes.clear();
3357 super.dispose();
3358 }
3359
3360 /// Update the semantics using [onSemanticsUpdate].
3361 void sendSemanticsUpdate() {
3362 // Once the tree is up-to-date, verify that every node is visible.
3363 assert(() {
3364 final List<SemanticsNode> invisibleNodes = <SemanticsNode>[];
3365 // Finds the invisible nodes in the tree rooted at `node` and adds them to
3366 // the invisibleNodes list. If a node is itself invisible, all its
3367 // descendants will be skipped.
3368 bool findInvisibleNodes(SemanticsNode node) {
3369 if (node.rect.isEmpty) {
3370 invisibleNodes.add(node);
3371 } else if (!node.mergeAllDescendantsIntoThisNode) {
3372 node.visitChildren(findInvisibleNodes);
3373 }
3374 return true;
3375 }
3376
3377 final SemanticsNode? rootSemanticsNode = this.rootSemanticsNode;
3378 if (rootSemanticsNode != null) {
3379 // The root node is allowed to be invisible when it has no children.
3380 if (rootSemanticsNode.childrenCount > 0 && rootSemanticsNode.rect.isEmpty) {
3381 invisibleNodes.add(rootSemanticsNode);
3382 } else if (!rootSemanticsNode.mergeAllDescendantsIntoThisNode) {
3383 rootSemanticsNode.visitChildren(findInvisibleNodes);
3384 }
3385 }
3386
3387 if (invisibleNodes.isEmpty) {
3388 return true;
3389 }
3390
3391 List<DiagnosticsNode> nodeToMessage(SemanticsNode invisibleNode) {
3392 final SemanticsNode? parent = invisibleNode.parent;
3393 return<DiagnosticsNode>[
3394 invisibleNode.toDiagnosticsNode(style: DiagnosticsTreeStyle.errorProperty),
3395 parent?.toDiagnosticsNode(name: 'which was added as a child of', style: DiagnosticsTreeStyle.errorProperty) ?? ErrorDescription('which was added as the root SemanticsNode'),
3396 ];
3397 }
3398
3399 throw FlutterError.fromParts(<DiagnosticsNode>[
3400 ErrorSummary('Invisible SemanticsNodes should not be added to the tree.'),
3401 ErrorDescription('The following invisible SemanticsNodes were added to the tree:'),
3402 ...invisibleNodes.expand(nodeToMessage),
3403 ErrorHint(
3404 'An invisible SemanticsNode is one whose rect is not on screen hence not reachable for users, '
3405 'and its semantic information is not merged into a visible parent.'
3406 ),
3407 ErrorHint(
3408 'An invisible SemantiscNode makes the accessibility experience confusing, '
3409 'as it does not provide any visual indication when the user selects it '
3410 'via accessibility technologies.'
3411 ),
3412 ErrorHint(
3413 'Consider removing the above invisible SemanticsNodes if they were added by your '
3414 'RenderObject.assembleSemanticsNode implementation, or filing a bug on GitHub:\n'
3415 ' https://github.com/flutter/flutter/issues/new?template=2_bug.yml',
3416 ),
3417 ]);
3418 }());
3419
3420 if (_dirtyNodes.isEmpty) {
3421 return;
3422 }
3423 final Set<int> customSemanticsActionIds = <int>{};
3424 final List<SemanticsNode> visitedNodes = <SemanticsNode>[];
3425 while (_dirtyNodes.isNotEmpty) {
3426 final List<SemanticsNode> localDirtyNodes = _dirtyNodes.where((SemanticsNode node) => !_detachedNodes.contains(node)).toList();
3427 _dirtyNodes.clear();
3428 _detachedNodes.clear();
3429 localDirtyNodes.sort((SemanticsNode a, SemanticsNode b) => a.depth - b.depth);
3430 visitedNodes.addAll(localDirtyNodes);
3431 for (final SemanticsNode node in localDirtyNodes) {
3432 assert(node._dirty);
3433 assert(node.parent == null || !node.parent!.isPartOfNodeMerging || node.isMergedIntoParent);
3434 if (node.isPartOfNodeMerging) {
3435 assert(node.mergeAllDescendantsIntoThisNode || node.parent != null);
3436 // if we're merged into our parent, make sure our parent is added to the dirty list
3437 if (node.parent != null && node.parent!.isPartOfNodeMerging) {
3438 node.parent!._markDirty(); // this can add the node to the dirty list
3439 node._dirty = false; // We don't want to send update for this node.
3440 }
3441 }
3442 }
3443 }
3444 visitedNodes.sort((SemanticsNode a, SemanticsNode b) => a.depth - b.depth);
3445 final SemanticsUpdateBuilder builder = SemanticsBinding.instance.createSemanticsUpdateBuilder();
3446 for (final SemanticsNode node in visitedNodes) {
3447 assert(node.parent?._dirty != true); // could be null (no parent) or false (not dirty)
3448 // The _serialize() method marks the node as not dirty, and
3449 // recurses through the tree to do a deep serialization of all
3450 // contiguous dirty nodes. This means that when we return here,
3451 // it's quite possible that subsequent nodes are no longer
3452 // dirty. We skip these here.
3453 // We also skip any nodes that were reset and subsequently
3454 // dropped entirely (RenderObject.markNeedsSemanticsUpdate()
3455 // calls reset() on its SemanticsNode if onlyChanges isn't set,
3456 // which happens e.g. when the node is no longer contributing
3457 // semantics).
3458 if (node._dirty && node.attached) {
3459 node._addToUpdate(builder, customSemanticsActionIds);
3460 }
3461 }
3462 _dirtyNodes.clear();
3463 for (final int actionId in customSemanticsActionIds) {
3464 final CustomSemanticsAction action = CustomSemanticsAction.getAction(actionId)!;
3465 builder.updateCustomAction(id: actionId, label: action.label, hint: action.hint, overrideId: action.action?.index ?? -1);
3466 }
3467 onSemanticsUpdate(builder.build());
3468 notifyListeners();
3469 }
3470
3471 SemanticsActionHandler? _getSemanticsActionHandlerForId(int id, SemanticsAction action) {
3472 SemanticsNode? result = _nodes[id];
3473 if (result != null && result.isPartOfNodeMerging && !result._canPerformAction(action)) {
3474 result._visitDescendants((SemanticsNode node) {
3475 if (node._canPerformAction(action)) {
3476 result = node;
3477 return false; // found node, abort walk
3478 }
3479 return true; // continue walk
3480 });
3481 }
3482 if (result == null || !result!._canPerformAction(action)) {
3483 return null;
3484 }
3485 return result!._actions[action];
3486 }
3487
3488 /// Asks the [SemanticsNode] with the given id to perform the given action.
3489 ///
3490 /// If the [SemanticsNode] has not indicated that it can perform the action,
3491 /// this function does nothing.
3492 ///
3493 /// If the given `action` requires arguments they need to be passed in via
3494 /// the `args` parameter.
3495 void performAction(int id, SemanticsAction action, [ Object? args ]) {
3496 final SemanticsActionHandler? handler = _getSemanticsActionHandlerForId(id, action);
3497 if (handler != null) {
3498 handler(args);
3499 return;
3500 }
3501
3502 // Default actions if no [handler] was provided.
3503 if (action == SemanticsAction.showOnScreen && _nodes[id]?._showOnScreen != null) {
3504 _nodes[id]!._showOnScreen!();
3505 }
3506 }
3507
3508 SemanticsActionHandler? _getSemanticsActionHandlerForPosition(SemanticsNode node, Offset position, SemanticsAction action) {
3509 if (node.transform != null) {
3510 final Matrix4 inverse = Matrix4.identity();
3511 if (inverse.copyInverse(node.transform!) == 0.0) {
3512 return null;
3513 }
3514 position = MatrixUtils.transformPoint(inverse, position);
3515 }
3516 if (!node.rect.contains(position)) {
3517 return null;
3518 }
3519 if (node.mergeAllDescendantsIntoThisNode) {
3520 SemanticsNode? result;
3521 node._visitDescendants((SemanticsNode child) {
3522 if (child._canPerformAction(action)) {
3523 result = child;
3524 return false;
3525 }
3526 return true;
3527 });
3528 return result?._actions[action];
3529 }
3530 if (node.hasChildren) {
3531 for (final SemanticsNode child in node._children!.reversed) {
3532 final SemanticsActionHandler? handler = _getSemanticsActionHandlerForPosition(child, position, action);
3533 if (handler != null) {
3534 return handler;
3535 }
3536 }
3537 }
3538 return node._actions[action];
3539 }
3540
3541 /// Asks the [SemanticsNode] at the given position to perform the given action.
3542 ///
3543 /// If the [SemanticsNode] has not indicated that it can perform the action,
3544 /// this function does nothing.
3545 ///
3546 /// If the given `action` requires arguments they need to be passed in via
3547 /// the `args` parameter.
3548 void performActionAt(Offset position, SemanticsAction action, [ Object? args ]) {
3549 final SemanticsNode? node = rootSemanticsNode;
3550 if (node == null) {
3551 return;
3552 }
3553 final SemanticsActionHandler? handler = _getSemanticsActionHandlerForPosition(node, position, action);
3554 if (handler != null) {
3555 handler(args);
3556 }
3557 }
3558
3559 @override
3560 String toString() => describeIdentity(this);
3561}
3562
3563/// Describes the semantic information associated with the owning
3564/// [RenderObject].
3565///
3566/// The information provided in the configuration is used to generate the
3567/// semantics tree.
3568class SemanticsConfiguration {
3569
3570 // SEMANTIC BOUNDARY BEHAVIOR
3571
3572 /// Whether the [RenderObject] owner of this configuration wants to own its
3573 /// own [SemanticsNode].
3574 ///
3575 /// When set to true semantic information associated with the [RenderObject]
3576 /// owner of this configuration or any of its descendants will not leak into
3577 /// parents. The [SemanticsNode] generated out of this configuration will
3578 /// act as a boundary.
3579 ///
3580 /// Whether descendants of the owning [RenderObject] can add their semantic
3581 /// information to the [SemanticsNode] introduced by this configuration
3582 /// is controlled by [explicitChildNodes].
3583 ///
3584 /// This has to be true if [isMergingSemanticsOfDescendants] is also true.
3585 bool get isSemanticBoundary => _isSemanticBoundary;
3586 bool _isSemanticBoundary = false;
3587 set isSemanticBoundary(bool value) {
3588 assert(!isMergingSemanticsOfDescendants || value);
3589 _isSemanticBoundary = value;
3590 }
3591
3592 /// Whether to block pointer related user actions for the rendering subtree.
3593 ///
3594 /// Setting this to true will prevent users from interacting with the
3595 /// rendering object produces this semantics configuration and its subtree
3596 /// through pointer-related [SemanticsAction]s in assistive technologies.
3597 ///
3598 /// The [SemanticsNode] created from this semantics configuration is still
3599 /// focusable by assistive technologies. Only pointer-related
3600 /// [SemanticsAction]s, such as [SemanticsAction.tap] or its friends, are
3601 /// blocked.
3602 ///
3603 /// If this semantics configuration is merged into a parent semantics node,
3604 /// only the [SemanticsAction]s from this rendering object and the rendering
3605 /// objects in the subtree are blocked.
3606 bool isBlockingUserActions = false;
3607
3608 /// Whether the configuration forces all children of the owning [RenderObject]
3609 /// that want to contribute semantic information to the semantics tree to do
3610 /// so in the form of explicit [SemanticsNode]s.
3611 ///
3612 /// When set to false children of the owning [RenderObject] are allowed to
3613 /// annotate [SemanticsNode]s of their parent with the semantic information
3614 /// they want to contribute to the semantic tree.
3615 /// When set to true the only way for children of the owning [RenderObject]
3616 /// to contribute semantic information to the semantic tree is to introduce
3617 /// new explicit [SemanticsNode]s to the tree.
3618 ///
3619 /// This setting is often used in combination with [isSemanticBoundary] to
3620 /// create semantic boundaries that are either writable or not for children.
3621 bool explicitChildNodes = false;
3622
3623 /// Whether the owning [RenderObject] makes other [RenderObject]s previously
3624 /// painted within the same semantic boundary unreachable for accessibility
3625 /// purposes.
3626 ///
3627 /// If set to true, the semantic information for all siblings and cousins of
3628 /// this node, that are earlier in a depth-first pre-order traversal, are
3629 /// dropped from the semantics tree up until a semantic boundary (as defined
3630 /// by [isSemanticBoundary]) is reached.
3631 ///
3632 /// If [isSemanticBoundary] and [isBlockingSemanticsOfPreviouslyPaintedNodes]
3633 /// is set on the same node, all previously painted siblings and cousins up
3634 /// until the next ancestor that is a semantic boundary are dropped.
3635 ///
3636 /// Paint order as established by [RenderObject.visitChildrenForSemantics] is
3637 /// used to determine if a node is previous to this one.
3638 bool isBlockingSemanticsOfPreviouslyPaintedNodes = false;
3639
3640 // SEMANTIC ANNOTATIONS
3641 // These will end up on [SemanticsNode]s generated from
3642 // [SemanticsConfiguration]s.
3643
3644 /// Whether this configuration is empty.
3645 ///
3646 /// An empty configuration doesn't contain any semantic information that it
3647 /// wants to contribute to the semantics tree.
3648 bool get hasBeenAnnotated => _hasBeenAnnotated;
3649 bool _hasBeenAnnotated = false;
3650
3651 /// The actions (with associated action handlers) that this configuration
3652 /// would like to contribute to the semantics tree.
3653 ///
3654 /// See also:
3655 ///
3656 /// * [addAction] to add an action.
3657 final Map<SemanticsAction, SemanticsActionHandler> _actions = <SemanticsAction, SemanticsActionHandler>{};
3658
3659 int get _effectiveActionsAsBits => isBlockingUserActions ? _actionsAsBits & _kUnblockedUserActions : _actionsAsBits;
3660 int _actionsAsBits = 0;
3661
3662 /// Adds an `action` to the semantics tree.
3663 ///
3664 /// The provided `handler` is called to respond to the user triggered
3665 /// `action`.
3666 void _addAction(SemanticsAction action, SemanticsActionHandler handler) {
3667 _actions[action] = handler;
3668 _actionsAsBits |= action.index;
3669 _hasBeenAnnotated = true;
3670 }
3671
3672 /// Adds an `action` to the semantics tree, whose `handler` does not expect
3673 /// any arguments.
3674 ///
3675 /// The provided `handler` is called to respond to the user triggered
3676 /// `action`.
3677 void _addArgumentlessAction(SemanticsAction action, VoidCallback handler) {
3678 _addAction(action, (Object? args) {
3679 assert(args == null);
3680 handler();
3681 });
3682 }
3683
3684 /// The handler for [SemanticsAction.tap].
3685 ///
3686 /// This is the semantic equivalent of a user briefly tapping the screen with
3687 /// the finger without moving it. For example, a button should implement this
3688 /// action.
3689 ///
3690 /// VoiceOver users on iOS and TalkBack users on Android can trigger this
3691 /// action by double-tapping the screen while an element is focused.
3692 ///
3693 /// On Android prior to Android Oreo a double-tap on the screen while an
3694 /// element with an [onTap] handler is focused will not call the registered
3695 /// handler. Instead, Android will simulate a pointer down and up event at the
3696 /// center of the focused element. Those pointer events will get dispatched
3697 /// just like a regular tap with TalkBack disabled would: The events will get
3698 /// processed by any [GestureDetector] listening for gestures in the center of
3699 /// the focused element. Therefore, to ensure that [onTap] handlers work
3700 /// properly on Android versions prior to Oreo, a [GestureDetector] with an
3701 /// onTap handler should always be wrapping an element that defines a
3702 /// semantic [onTap] handler. By default a [GestureDetector] will register its
3703 /// own semantic [onTap] handler that follows this principle.
3704 VoidCallback? get onTap => _onTap;
3705 VoidCallback? _onTap;
3706 set onTap(VoidCallback? value) {
3707 _addArgumentlessAction(SemanticsAction.tap, value!);
3708 _onTap = value;
3709 }
3710
3711 /// The handler for [SemanticsAction.longPress].
3712 ///
3713 /// This is the semantic equivalent of a user pressing and holding the screen
3714 /// with the finger for a few seconds without moving it.
3715 ///
3716 /// VoiceOver users on iOS and TalkBack users on Android can trigger this
3717 /// action by double-tapping the screen without lifting the finger after the
3718 /// second tap.
3719 VoidCallback? get onLongPress => _onLongPress;
3720 VoidCallback? _onLongPress;
3721 set onLongPress(VoidCallback? value) {
3722 _addArgumentlessAction(SemanticsAction.longPress, value!);
3723 _onLongPress = value;
3724 }
3725
3726 /// The handler for [SemanticsAction.scrollLeft].
3727 ///
3728 /// This is the semantic equivalent of a user moving their finger across the
3729 /// screen from right to left. It should be recognized by controls that are
3730 /// horizontally scrollable.
3731 ///
3732 /// VoiceOver users on iOS can trigger this action by swiping left with three
3733 /// fingers. TalkBack users on Android can trigger this action by swiping
3734 /// right and then left in one motion path. On Android, [onScrollUp] and
3735 /// [onScrollLeft] share the same gesture. Therefore, only on of them should
3736 /// be provided.
3737 VoidCallback? get onScrollLeft => _onScrollLeft;
3738 VoidCallback? _onScrollLeft;
3739 set onScrollLeft(VoidCallback? value) {
3740 _addArgumentlessAction(SemanticsAction.scrollLeft, value!);
3741 _onScrollLeft = value;
3742 }
3743
3744 /// The handler for [SemanticsAction.dismiss].
3745 ///
3746 /// This is a request to dismiss the currently focused node.
3747 ///
3748 /// TalkBack users on Android can trigger this action in the local context
3749 /// menu, and VoiceOver users on iOS can trigger this action with a standard
3750 /// gesture or menu option.
3751 VoidCallback? get onDismiss => _onDismiss;
3752 VoidCallback? _onDismiss;
3753 set onDismiss(VoidCallback? value) {
3754 _addArgumentlessAction(SemanticsAction.dismiss, value!);
3755 _onDismiss = value;
3756 }
3757
3758 /// The handler for [SemanticsAction.scrollRight].
3759 ///
3760 /// This is the semantic equivalent of a user moving their finger across the
3761 /// screen from left to right. It should be recognized by controls that are
3762 /// horizontally scrollable.
3763 ///
3764 /// VoiceOver users on iOS can trigger this action by swiping right with three
3765 /// fingers. TalkBack users on Android can trigger this action by swiping
3766 /// left and then right in one motion path. On Android, [onScrollDown] and
3767 /// [onScrollRight] share the same gesture. Therefore, only on of them should
3768 /// be provided.
3769 VoidCallback? get onScrollRight => _onScrollRight;
3770 VoidCallback? _onScrollRight;
3771 set onScrollRight(VoidCallback? value) {
3772 _addArgumentlessAction(SemanticsAction.scrollRight, value!);
3773 _onScrollRight = value;
3774 }
3775
3776 /// The handler for [SemanticsAction.scrollUp].
3777 ///
3778 /// This is the semantic equivalent of a user moving their finger across the
3779 /// screen from bottom to top. It should be recognized by controls that are
3780 /// vertically scrollable.
3781 ///
3782 /// VoiceOver users on iOS can trigger this action by swiping up with three
3783 /// fingers. TalkBack users on Android can trigger this action by swiping
3784 /// right and then left in one motion path. On Android, [onScrollUp] and
3785 /// [onScrollLeft] share the same gesture. Therefore, only on of them should
3786 /// be provided.
3787 VoidCallback? get onScrollUp => _onScrollUp;
3788 VoidCallback? _onScrollUp;
3789 set onScrollUp(VoidCallback? value) {
3790 _addArgumentlessAction(SemanticsAction.scrollUp, value!);
3791 _onScrollUp = value;
3792 }
3793
3794 /// The handler for [SemanticsAction.scrollDown].
3795 ///
3796 /// This is the semantic equivalent of a user moving their finger across the
3797 /// screen from top to bottom. It should be recognized by controls that are
3798 /// vertically scrollable.
3799 ///
3800 /// VoiceOver users on iOS can trigger this action by swiping down with three
3801 /// fingers. TalkBack users on Android can trigger this action by swiping
3802 /// left and then right in one motion path. On Android, [onScrollDown] and
3803 /// [onScrollRight] share the same gesture. Therefore, only on of them should
3804 /// be provided.
3805 VoidCallback? get onScrollDown => _onScrollDown;
3806 VoidCallback? _onScrollDown;
3807 set onScrollDown(VoidCallback? value) {
3808 _addArgumentlessAction(SemanticsAction.scrollDown, value!);
3809 _onScrollDown = value;
3810 }
3811
3812 /// The handler for [SemanticsAction.increase].
3813 ///
3814 /// This is a request to increase the value represented by the widget. For
3815 /// example, this action might be recognized by a slider control.
3816 ///
3817 /// If [this.value] is set, [increasedValue] must also be provided and
3818 /// [onIncrease] must ensure that [this.value] will be set to
3819 /// [increasedValue].
3820 ///
3821 /// VoiceOver users on iOS can trigger this action by swiping up with one
3822 /// finger. TalkBack users on Android can trigger this action by pressing the
3823 /// volume up button.
3824 VoidCallback? get onIncrease => _onIncrease;
3825 VoidCallback? _onIncrease;
3826 set onIncrease(VoidCallback? value) {
3827 _addArgumentlessAction(SemanticsAction.increase, value!);
3828 _onIncrease = value;
3829 }
3830
3831 /// The handler for [SemanticsAction.decrease].
3832 ///
3833 /// This is a request to decrease the value represented by the widget. For
3834 /// example, this action might be recognized by a slider control.
3835 ///
3836 /// If [this.value] is set, [decreasedValue] must also be provided and
3837 /// [onDecrease] must ensure that [this.value] will be set to
3838 /// [decreasedValue].
3839 ///
3840 /// VoiceOver users on iOS can trigger this action by swiping down with one
3841 /// finger. TalkBack users on Android can trigger this action by pressing the
3842 /// volume down button.
3843 VoidCallback? get onDecrease => _onDecrease;
3844 VoidCallback? _onDecrease;
3845 set onDecrease(VoidCallback? value) {
3846 _addArgumentlessAction(SemanticsAction.decrease, value!);
3847 _onDecrease = value;
3848 }
3849
3850 /// The handler for [SemanticsAction.copy].
3851 ///
3852 /// This is a request to copy the current selection to the clipboard.
3853 ///
3854 /// TalkBack users on Android can trigger this action from the local context
3855 /// menu of a text field, for example.
3856 VoidCallback? get onCopy => _onCopy;
3857 VoidCallback? _onCopy;
3858 set onCopy(VoidCallback? value) {
3859 _addArgumentlessAction(SemanticsAction.copy, value!);
3860 _onCopy = value;
3861 }
3862
3863 /// The handler for [SemanticsAction.cut].
3864 ///
3865 /// This is a request to cut the current selection and place it in the
3866 /// clipboard.
3867 ///
3868 /// TalkBack users on Android can trigger this action from the local context
3869 /// menu of a text field, for example.
3870 VoidCallback? get onCut => _onCut;
3871 VoidCallback? _onCut;
3872 set onCut(VoidCallback? value) {
3873 _addArgumentlessAction(SemanticsAction.cut, value!);
3874 _onCut = value;
3875 }
3876
3877 /// The handler for [SemanticsAction.paste].
3878 ///
3879 /// This is a request to paste the current content of the clipboard.
3880 ///
3881 /// TalkBack users on Android can trigger this action from the local context
3882 /// menu of a text field, for example.
3883 VoidCallback? get onPaste => _onPaste;
3884 VoidCallback? _onPaste;
3885 set onPaste(VoidCallback? value) {
3886 _addArgumentlessAction(SemanticsAction.paste, value!);
3887 _onPaste = value;
3888 }
3889
3890 /// The handler for [SemanticsAction.showOnScreen].
3891 ///
3892 /// A request to fully show the semantics node on screen. For example, this
3893 /// action might be send to a node in a scrollable list that is partially off
3894 /// screen to bring it on screen.
3895 ///
3896 /// For elements in a scrollable list the framework provides a default
3897 /// implementation for this action and it is not advised to provide a
3898 /// custom one via this setter.
3899 VoidCallback? get onShowOnScreen => _onShowOnScreen;
3900 VoidCallback? _onShowOnScreen;
3901 set onShowOnScreen(VoidCallback? value) {
3902 _addArgumentlessAction(SemanticsAction.showOnScreen, value!);
3903 _onShowOnScreen = value;
3904 }
3905
3906 /// The handler for [SemanticsAction.moveCursorForwardByCharacter].
3907 ///
3908 /// This handler is invoked when the user wants to move the cursor in a
3909 /// text field forward by one character.
3910 ///
3911 /// TalkBack users can trigger this by pressing the volume up key while the
3912 /// input focus is in a text field.
3913 MoveCursorHandler? get onMoveCursorForwardByCharacter => _onMoveCursorForwardByCharacter;
3914 MoveCursorHandler? _onMoveCursorForwardByCharacter;
3915 set onMoveCursorForwardByCharacter(MoveCursorHandler? value) {
3916 assert(value != null);
3917 _addAction(SemanticsAction.moveCursorForwardByCharacter, (Object? args) {
3918 final bool extendSelection = args! as bool;
3919 value!(extendSelection);
3920 });
3921 _onMoveCursorForwardByCharacter = value;
3922 }
3923
3924 /// The handler for [SemanticsAction.moveCursorBackwardByCharacter].
3925 ///
3926 /// This handler is invoked when the user wants to move the cursor in a
3927 /// text field backward by one character.
3928 ///
3929 /// TalkBack users can trigger this by pressing the volume down key while the
3930 /// input focus is in a text field.
3931 MoveCursorHandler? get onMoveCursorBackwardByCharacter => _onMoveCursorBackwardByCharacter;
3932 MoveCursorHandler? _onMoveCursorBackwardByCharacter;
3933 set onMoveCursorBackwardByCharacter(MoveCursorHandler? value) {
3934 assert(value != null);
3935 _addAction(SemanticsAction.moveCursorBackwardByCharacter, (Object? args) {
3936 final bool extendSelection = args! as bool;
3937 value!(extendSelection);
3938 });
3939 _onMoveCursorBackwardByCharacter = value;
3940 }
3941
3942 /// The handler for [SemanticsAction.moveCursorForwardByWord].
3943 ///
3944 /// This handler is invoked when the user wants to move the cursor in a
3945 /// text field backward by one word.
3946 ///
3947 /// TalkBack users can trigger this by pressing the volume down key while the
3948 /// input focus is in a text field.
3949 MoveCursorHandler? get onMoveCursorForwardByWord => _onMoveCursorForwardByWord;
3950 MoveCursorHandler? _onMoveCursorForwardByWord;
3951 set onMoveCursorForwardByWord(MoveCursorHandler? value) {
3952 assert(value != null);
3953 _addAction(SemanticsAction.moveCursorForwardByWord, (Object? args) {
3954 final bool extendSelection = args! as bool;
3955 value!(extendSelection);
3956 });
3957 _onMoveCursorForwardByCharacter = value;
3958 }
3959
3960 /// The handler for [SemanticsAction.moveCursorBackwardByWord].
3961 ///
3962 /// This handler is invoked when the user wants to move the cursor in a
3963 /// text field backward by one word.
3964 ///
3965 /// TalkBack users can trigger this by pressing the volume down key while the
3966 /// input focus is in a text field.
3967 MoveCursorHandler? get onMoveCursorBackwardByWord => _onMoveCursorBackwardByWord;
3968 MoveCursorHandler? _onMoveCursorBackwardByWord;
3969 set onMoveCursorBackwardByWord(MoveCursorHandler? value) {
3970 assert(value != null);
3971 _addAction(SemanticsAction.moveCursorBackwardByWord, (Object? args) {
3972 final bool extendSelection = args! as bool;
3973 value!(extendSelection);
3974 });
3975 _onMoveCursorBackwardByCharacter = value;
3976 }
3977
3978 /// The handler for [SemanticsAction.setSelection].
3979 ///
3980 /// This handler is invoked when the user either wants to change the currently
3981 /// selected text in a text field or change the position of the cursor.
3982 ///
3983 /// TalkBack users can trigger this handler by selecting "Move cursor to
3984 /// beginning/end" or "Select all" from the local context menu.
3985 SetSelectionHandler? get onSetSelection => _onSetSelection;
3986 SetSelectionHandler? _onSetSelection;
3987 set onSetSelection(SetSelectionHandler? value) {
3988 assert(value != null);
3989 _addAction(SemanticsAction.setSelection, (Object? args) {
3990 assert(args != null && args is Map);
3991 final Map<String, int> selection = (args! as Map<dynamic, dynamic>).cast<String, int>();
3992 assert(selection['base'] != null && selection['extent'] != null);
3993 value!(TextSelection(
3994 baseOffset: selection['base']!,
3995 extentOffset: selection['extent']!,
3996 ));
3997 });
3998 _onSetSelection = value;
3999 }
4000
4001 /// The handler for [SemanticsAction.setText].
4002 ///
4003 /// This handler is invoked when the user wants to replace the current text in
4004 /// the text field with a new text.
4005 ///
4006 /// Voice access users can trigger this handler by speaking "type <text>" to
4007 /// their Android devices.
4008 SetTextHandler? get onSetText => _onSetText;
4009 SetTextHandler? _onSetText;
4010 set onSetText(SetTextHandler? value) {
4011 assert(value != null);
4012 _addAction(SemanticsAction.setText, (Object? args) {
4013 assert(args != null && args is String);
4014 final String text = args! as String;
4015 value!(text);
4016 });
4017 _onSetText = value;
4018 }
4019
4020 /// The handler for [SemanticsAction.didGainAccessibilityFocus].
4021 ///
4022 /// This handler is invoked when the node annotated with this handler gains
4023 /// the accessibility focus. The accessibility focus is the
4024 /// green (on Android with TalkBack) or black (on iOS with VoiceOver)
4025 /// rectangle shown on screen to indicate what element an accessibility
4026 /// user is currently interacting with.
4027 ///
4028 /// The accessibility focus is different from the input focus. The input focus
4029 /// is usually held by the element that currently responds to keyboard inputs.
4030 /// Accessibility focus and input focus can be held by two different nodes!
4031 ///
4032 /// See also:
4033 ///
4034 /// * [onDidLoseAccessibilityFocus], which is invoked when the accessibility
4035 /// focus is removed from the node.
4036 /// * [FocusNode], [FocusScope], [FocusManager], which manage the input focus.
4037 VoidCallback? get onDidGainAccessibilityFocus => _onDidGainAccessibilityFocus;
4038 VoidCallback? _onDidGainAccessibilityFocus;
4039 set onDidGainAccessibilityFocus(VoidCallback? value) {
4040 _addArgumentlessAction(SemanticsAction.didGainAccessibilityFocus, value!);
4041 _onDidGainAccessibilityFocus = value;
4042 }
4043
4044 /// The handler for [SemanticsAction.didLoseAccessibilityFocus].
4045 ///
4046 /// This handler is invoked when the node annotated with this handler
4047 /// loses the accessibility focus. The accessibility focus is
4048 /// the green (on Android with TalkBack) or black (on iOS with VoiceOver)
4049 /// rectangle shown on screen to indicate what element an accessibility
4050 /// user is currently interacting with.
4051 ///
4052 /// The accessibility focus is different from the input focus. The input focus
4053 /// is usually held by the element that currently responds to keyboard inputs.
4054 /// Accessibility focus and input focus can be held by two different nodes!
4055 ///
4056 /// See also:
4057 ///
4058 /// * [onDidGainAccessibilityFocus], which is invoked when the node gains
4059 /// accessibility focus.
4060 /// * [FocusNode], [FocusScope], [FocusManager], which manage the input focus.
4061 VoidCallback? get onDidLoseAccessibilityFocus => _onDidLoseAccessibilityFocus;
4062 VoidCallback? _onDidLoseAccessibilityFocus;
4063 set onDidLoseAccessibilityFocus(VoidCallback? value) {
4064 _addArgumentlessAction(SemanticsAction.didLoseAccessibilityFocus, value!);
4065 _onDidLoseAccessibilityFocus = value;
4066 }
4067
4068 /// A delegate that decides how to handle [SemanticsConfiguration]s produced
4069 /// in the widget subtree.
4070 ///
4071 /// The [SemanticsConfiguration]s are produced by rendering objects in the
4072 /// subtree and want to merge up to their parent. This delegate can decide
4073 /// which of these should be merged together to form sibling SemanticsNodes and
4074 /// which of them should be merged upwards into the parent SemanticsNode.
4075 ///
4076 /// The input list of [SemanticsConfiguration]s can be empty if the rendering
4077 /// object of this semantics configuration is a leaf node or child rendering
4078 /// objects do not contribute to the semantics.
4079 ChildSemanticsConfigurationsDelegate? get childConfigurationsDelegate => _childConfigurationsDelegate;
4080 ChildSemanticsConfigurationsDelegate? _childConfigurationsDelegate;
4081 set childConfigurationsDelegate(ChildSemanticsConfigurationsDelegate? value) {
4082 assert(value != null);
4083 _childConfigurationsDelegate = value;
4084 // Setting the childConfigsDelegate does not annotate any meaningful
4085 // semantics information of the config.
4086 }
4087
4088 /// Returns the action handler registered for [action] or null if none was
4089 /// registered.
4090 SemanticsActionHandler? getActionHandler(SemanticsAction action) => _actions[action];
4091
4092 /// Determines the position of this node among its siblings in the traversal
4093 /// sort order.
4094 ///
4095 /// This is used to describe the order in which the semantic node should be
4096 /// traversed by the accessibility services on the platform (e.g. VoiceOver
4097 /// on iOS and TalkBack on Android).
4098 ///
4099 /// Whether this sort key has an effect on the [SemanticsNode] sort order is
4100 /// subject to how this configuration is used. For example, the [absorb]
4101 /// method may decide to not use this key when it combines multiple
4102 /// [SemanticsConfiguration] objects.
4103 SemanticsSortKey? get sortKey => _sortKey;
4104 SemanticsSortKey? _sortKey;
4105 set sortKey(SemanticsSortKey? value) {
4106 assert(value != null);
4107 _sortKey = value;
4108 _hasBeenAnnotated = true;
4109 }
4110
4111 /// The index of this node within the parent's list of semantic children.
4112 ///
4113 /// This includes all semantic nodes, not just those currently in the
4114 /// child list. For example, if a scrollable has five children but the first
4115 /// two are not visible (and thus not included in the list of children), then
4116 /// the index of the last node will still be 4.
4117 int? get indexInParent => _indexInParent;
4118 int? _indexInParent;
4119 set indexInParent(int? value) {
4120 _indexInParent = value;
4121 _hasBeenAnnotated = true;
4122 }
4123
4124 /// The total number of scrollable children that contribute to semantics.
4125 ///
4126 /// If the number of children are unknown or unbounded, this value will be
4127 /// null.
4128 int? get scrollChildCount => _scrollChildCount;
4129 int? _scrollChildCount;
4130 set scrollChildCount(int? value) {
4131 if (value == scrollChildCount) {
4132 return;
4133 }
4134 _scrollChildCount = value;
4135 _hasBeenAnnotated = true;
4136 }
4137
4138 /// The index of the first visible scrollable child that contributes to
4139 /// semantics.
4140 int? get scrollIndex => _scrollIndex;
4141 int? _scrollIndex;
4142 set scrollIndex(int? value) {
4143 if (value == scrollIndex) {
4144 return;
4145 }
4146 _scrollIndex = value;
4147 _hasBeenAnnotated = true;
4148 }
4149
4150 /// The id of the platform view, whose semantics nodes will be added as
4151 /// children to this node.
4152 int? get platformViewId => _platformViewId;
4153 int? _platformViewId;
4154 set platformViewId(int? value) {
4155 if (value == platformViewId) {
4156 return;
4157 }
4158 _platformViewId = value;
4159 _hasBeenAnnotated = true;
4160 }
4161
4162 /// The maximum number of characters that can be entered into an editable
4163 /// text field.
4164 ///
4165 /// For the purpose of this function a character is defined as one Unicode
4166 /// scalar value.
4167 ///
4168 /// This should only be set when [isTextField] is true. Defaults to null,
4169 /// which means no limit is imposed on the text field.
4170 int? get maxValueLength => _maxValueLength;
4171 int? _maxValueLength;
4172 set maxValueLength(int? value) {
4173 if (value == maxValueLength) {
4174 return;
4175 }
4176 _maxValueLength = value;
4177 _hasBeenAnnotated = true;
4178 }
4179
4180 /// The current number of characters that have been entered into an editable
4181 /// text field.
4182 ///
4183 /// For the purpose of this function a character is defined as one Unicode
4184 /// scalar value.
4185 ///
4186 /// This should only be set when [isTextField] is true. Must be set when
4187 /// [maxValueLength] is set.
4188 int? get currentValueLength => _currentValueLength;
4189 int? _currentValueLength;
4190 set currentValueLength(int? value) {
4191 if (value == currentValueLength) {
4192 return;
4193 }
4194 _currentValueLength = value;
4195 _hasBeenAnnotated = true;
4196 }
4197
4198 /// Whether the semantic information provided by the owning [RenderObject] and
4199 /// all of its descendants should be treated as one logical entity.
4200 ///
4201 /// If set to true, the descendants of the owning [RenderObject]'s
4202 /// [SemanticsNode] will merge their semantic information into the
4203 /// [SemanticsNode] representing the owning [RenderObject].
4204 ///
4205 /// Setting this to true requires that [isSemanticBoundary] is also true.
4206 bool get isMergingSemanticsOfDescendants => _isMergingSemanticsOfDescendants;
4207 bool _isMergingSemanticsOfDescendants = false;
4208 set isMergingSemanticsOfDescendants(bool value) {
4209 assert(isSemanticBoundary);
4210 _isMergingSemanticsOfDescendants = value;
4211 _hasBeenAnnotated = true;
4212 }
4213
4214 /// The handlers for each supported [CustomSemanticsAction].
4215 ///
4216 /// Whenever a custom accessibility action is added to a node, the action
4217 /// [SemanticsAction.customAction] is automatically added. A handler is
4218 /// created which uses the passed argument to lookup the custom action
4219 /// handler from this map and invoke it, if present.
4220 Map<CustomSemanticsAction, VoidCallback> get customSemanticsActions => _customSemanticsActions;
4221 Map<CustomSemanticsAction, VoidCallback> _customSemanticsActions = <CustomSemanticsAction, VoidCallback>{};
4222 set customSemanticsActions(Map<CustomSemanticsAction, VoidCallback> value) {
4223 _hasBeenAnnotated = true;
4224 _actionsAsBits |= SemanticsAction.customAction.index;
4225 _customSemanticsActions = value;
4226 _actions[SemanticsAction.customAction] = _onCustomSemanticsAction;
4227 }
4228
4229 void _onCustomSemanticsAction(Object? args) {
4230 final CustomSemanticsAction? action = CustomSemanticsAction.getAction(args! as int);
4231 if (action == null) {
4232 return;
4233 }
4234 final VoidCallback? callback = _customSemanticsActions[action];
4235 if (callback != null) {
4236 callback();
4237 }
4238 }
4239
4240 /// {@macro flutter.semantics.SemanticsProperties.identifier}
4241 String get identifier => _identifier;
4242 String _identifier = '';
4243 set identifier(String identifier) {
4244 _identifier = identifier;
4245 _hasBeenAnnotated = true;
4246 }
4247
4248 /// A textual description of the owning [RenderObject].
4249 ///
4250 /// Setting this attribute will override the [attributedLabel].
4251 ///
4252 /// The reading direction is given by [textDirection].
4253 ///
4254 /// See also:
4255 ///
4256 /// * [attributedLabel], which is the [AttributedString] of this property.
4257 String get label => _attributedLabel.string;
4258 set label(String label) {
4259 _attributedLabel = AttributedString(label);
4260 _hasBeenAnnotated = true;
4261 }
4262
4263 /// A textual description of the owning [RenderObject] in [AttributedString]
4264 /// format.
4265 ///
4266 /// On iOS this is used for the `accessibilityAttributedLabel` property
4267 /// defined in the `UIAccessibility` Protocol. On Android it is concatenated
4268 /// together with [attributedValue] and [attributedHint] in the following
4269 /// order: [attributedValue], [attributedLabel], [attributedHint]. The
4270 /// concatenated value is then used as the `Text` description.
4271 ///
4272 /// The reading direction is given by [textDirection].
4273 ///
4274 /// See also:
4275 ///
4276 /// * [label], which is the raw text of this property.
4277 AttributedString get attributedLabel => _attributedLabel;
4278 AttributedString _attributedLabel = AttributedString('');
4279 set attributedLabel(AttributedString attributedLabel) {
4280 _attributedLabel = attributedLabel;
4281 _hasBeenAnnotated = true;
4282 }
4283
4284 /// A textual description for the current value of the owning [RenderObject].
4285 ///
4286 /// Setting this attribute will override the [attributedValue].
4287 ///
4288 /// The reading direction is given by [textDirection].
4289 ///
4290 /// See also:
4291 ///
4292 /// * [attributedValue], which is the [AttributedString] of this property.
4293 /// * [increasedValue] and [attributedIncreasedValue], which describe what
4294 /// [value] will be after performing [SemanticsAction.increase].
4295 /// * [decreasedValue] and [attributedDecreasedValue], which describe what
4296 /// [value] will be after performing [SemanticsAction.decrease].
4297 String get value => _attributedValue.string;
4298 set value(String value) {
4299 _attributedValue = AttributedString(value);
4300 _hasBeenAnnotated = true;
4301 }
4302
4303 /// A textual description for the current value of the owning [RenderObject]
4304 /// in [AttributedString] format.
4305 ///
4306 /// On iOS this is used for the `accessibilityAttributedValue` property
4307 /// defined in the `UIAccessibility` Protocol. On Android it is concatenated
4308 /// together with [attributedLabel] and [attributedHint] in the following
4309 /// order: [attributedValue], [attributedLabel], [attributedHint]. The
4310 /// concatenated value is then used as the `Text` description.
4311 ///
4312 /// The reading direction is given by [textDirection].
4313 ///
4314 /// See also:
4315 ///
4316 /// * [value], which is the raw text of this property.
4317 /// * [attributedIncreasedValue], which describes what [value] will be after
4318 /// performing [SemanticsAction.increase].
4319 /// * [attributedDecreasedValue], which describes what [value] will be after
4320 /// performing [SemanticsAction.decrease].
4321 AttributedString get attributedValue => _attributedValue;
4322 AttributedString _attributedValue = AttributedString('');
4323 set attributedValue(AttributedString attributedValue) {
4324 _attributedValue = attributedValue;
4325 _hasBeenAnnotated = true;
4326 }
4327
4328 /// The value that [value] will have after performing a
4329 /// [SemanticsAction.increase] action.
4330 ///
4331 /// Setting this attribute will override the [attributedIncreasedValue].
4332 ///
4333 /// One of the [attributedIncreasedValue] or [increasedValue] must be set if
4334 /// a handler for [SemanticsAction.increase] is provided and one of the
4335 /// [value] or [attributedValue] is set.
4336 ///
4337 /// The reading direction is given by [textDirection].
4338 ///
4339 /// See also:
4340 ///
4341 /// * [attributedIncreasedValue], which is the [AttributedString] of this property.
4342 String get increasedValue => _attributedIncreasedValue.string;
4343 set increasedValue(String increasedValue) {
4344 _attributedIncreasedValue = AttributedString(increasedValue);
4345 _hasBeenAnnotated = true;
4346 }
4347
4348 /// The value that [value] will have after performing a
4349 /// [SemanticsAction.increase] action in [AttributedString] format.
4350 ///
4351 /// One of the [attributedIncreasedValue] or [increasedValue] must be set if
4352 /// a handler for [SemanticsAction.increase] is provided and one of the
4353 /// [value] or [attributedValue] is set.
4354 ///
4355 /// The reading direction is given by [textDirection].
4356 ///
4357 /// See also:
4358 ///
4359 /// * [increasedValue], which is the raw text of this property.
4360 AttributedString get attributedIncreasedValue => _attributedIncreasedValue;
4361 AttributedString _attributedIncreasedValue = AttributedString('');
4362 set attributedIncreasedValue(AttributedString attributedIncreasedValue) {
4363 _attributedIncreasedValue = attributedIncreasedValue;
4364 _hasBeenAnnotated = true;
4365 }
4366
4367 /// The value that [value] will have after performing a
4368 /// [SemanticsAction.decrease] action.
4369 ///
4370 /// Setting this attribute will override the [attributedDecreasedValue].
4371 ///
4372 /// One of the [attributedDecreasedValue] or [decreasedValue] must be set if
4373 /// a handler for [SemanticsAction.decrease] is provided and one of the
4374 /// [value] or [attributedValue] is set.
4375 ///
4376 /// The reading direction is given by [textDirection].
4377 ///
4378 /// * [attributedDecreasedValue], which is the [AttributedString] of this property.
4379 String get decreasedValue => _attributedDecreasedValue.string;
4380 set decreasedValue(String decreasedValue) {
4381 _attributedDecreasedValue = AttributedString(decreasedValue);
4382 _hasBeenAnnotated = true;
4383 }
4384
4385 /// The value that [value] will have after performing a
4386 /// [SemanticsAction.decrease] action in [AttributedString] format.
4387 ///
4388 /// One of the [attributedDecreasedValue] or [decreasedValue] must be set if
4389 /// a handler for [SemanticsAction.decrease] is provided and one of the
4390 /// [value] or [attributedValue] is set.
4391 ///
4392 /// The reading direction is given by [textDirection].
4393 ///
4394 /// See also:
4395 ///
4396 /// * [decreasedValue], which is the raw text of this property.
4397 AttributedString get attributedDecreasedValue => _attributedDecreasedValue;
4398 AttributedString _attributedDecreasedValue = AttributedString('');
4399 set attributedDecreasedValue(AttributedString attributedDecreasedValue) {
4400 _attributedDecreasedValue = attributedDecreasedValue;
4401 _hasBeenAnnotated = true;
4402 }
4403
4404 /// A brief description of the result of performing an action on this node.
4405 ///
4406 /// Setting this attribute will override the [attributedHint].
4407 ///
4408 /// The reading direction is given by [textDirection].
4409 ///
4410 /// See also:
4411 ///
4412 /// * [attributedHint], which is the [AttributedString] of this property.
4413 String get hint => _attributedHint.string;
4414 set hint(String hint) {
4415 _attributedHint = AttributedString(hint);
4416 _hasBeenAnnotated = true;
4417 }
4418
4419 /// A brief description of the result of performing an action on this node in
4420 /// [AttributedString] format.
4421 ///
4422 /// On iOS this is used for the `accessibilityAttributedHint` property
4423 /// defined in the `UIAccessibility` Protocol. On Android it is concatenated
4424 /// together with [attributedLabel] and [attributedValue] in the following
4425 /// order: [attributedValue], [attributedLabel], [attributedHint]. The
4426 /// concatenated value is then used as the `Text` description.
4427 ///
4428 /// The reading direction is given by [textDirection].
4429 ///
4430 /// See also:
4431 ///
4432 /// * [hint], which is the raw text of this property.
4433 AttributedString get attributedHint => _attributedHint;
4434 AttributedString _attributedHint = AttributedString('');
4435 set attributedHint(AttributedString attributedHint) {
4436 _attributedHint = attributedHint;
4437 _hasBeenAnnotated = true;
4438 }
4439
4440 /// A textual description of the widget's tooltip.
4441 ///
4442 /// The reading direction is given by [textDirection].
4443 String get tooltip => _tooltip;
4444 String _tooltip = '';
4445 set tooltip(String tooltip) {
4446 _tooltip = tooltip;
4447 _hasBeenAnnotated = true;
4448 }
4449
4450 /// Provides hint values which override the default hints on supported
4451 /// platforms.
4452 SemanticsHintOverrides? get hintOverrides => _hintOverrides;
4453 SemanticsHintOverrides? _hintOverrides;
4454 set hintOverrides(SemanticsHintOverrides? value) {
4455 if (value == null) {
4456 return;
4457 }
4458 _hintOverrides = value;
4459 _hasBeenAnnotated = true;
4460 }
4461
4462 /// The elevation in z-direction at which the owning [RenderObject] is
4463 /// located relative to its parent.
4464 double get elevation => _elevation;
4465 double _elevation = 0.0;
4466 set elevation(double value) {
4467 assert(value >= 0.0);
4468 if (value == _elevation) {
4469 return;
4470 }
4471 _elevation = value;
4472 _hasBeenAnnotated = true;
4473 }
4474
4475 /// The extend that the owning [RenderObject] occupies in z-direction starting
4476 /// at [elevation].
4477 ///
4478 /// It's extremely rare to set this value directly. Instead, it is calculated
4479 /// implicitly when other [SemanticsConfiguration]s are merged into this one
4480 /// via [absorb].
4481 double get thickness => _thickness;
4482 double _thickness = 0.0;
4483 set thickness(double value) {
4484 assert(value >= 0.0);
4485 if (value == _thickness) {
4486 return;
4487 }
4488 _thickness = value;
4489 _hasBeenAnnotated = true;
4490 }
4491
4492 /// Whether the semantics node is the root of a subtree for which values
4493 /// should be announced.
4494 ///
4495 /// See also:
4496 ///
4497 /// * [SemanticsFlag.scopesRoute], for a full description of route scoping.
4498 bool get scopesRoute => _hasFlag(SemanticsFlag.scopesRoute);
4499 set scopesRoute(bool value) {
4500 _setFlag(SemanticsFlag.scopesRoute, value);
4501 }
4502
4503 /// Whether the semantics node contains the label of a route.
4504 ///
4505 /// See also:
4506 ///
4507 /// * [SemanticsFlag.namesRoute], for a full description of route naming.
4508 bool get namesRoute => _hasFlag(SemanticsFlag.namesRoute);
4509 set namesRoute(bool value) {
4510 _setFlag(SemanticsFlag.namesRoute, value);
4511 }
4512
4513 /// Whether the semantics node represents an image.
4514 bool get isImage => _hasFlag(SemanticsFlag.isImage);
4515 set isImage(bool value) {
4516 _setFlag(SemanticsFlag.isImage, value);
4517 }
4518
4519 /// Whether the semantics node is a live region.
4520 ///
4521 /// A live region indicates that updates to semantics node are important.
4522 /// Platforms may use this information to make polite announcements to the
4523 /// user to inform them of updates to this node.
4524 ///
4525 /// An example of a live region is a [SnackBar] widget. On Android and iOS,
4526 /// live region causes a polite announcement to be generated automatically,
4527 /// even if the widget does not have accessibility focus. This announcement
4528 /// may not be spoken if the OS accessibility services are already
4529 /// announcing something else, such as reading the label of a focused widget
4530 /// or providing a system announcement.
4531 ///
4532 /// See also:
4533 ///
4534 /// * [SemanticsFlag.isLiveRegion], the semantics flag that this setting controls.
4535 bool get liveRegion => _hasFlag(SemanticsFlag.isLiveRegion);
4536 set liveRegion(bool value) {
4537 _setFlag(SemanticsFlag.isLiveRegion, value);
4538 }
4539
4540 /// The reading direction for the text in [label], [value], [hint],
4541 /// [increasedValue], and [decreasedValue].
4542 TextDirection? get textDirection => _textDirection;
4543 TextDirection? _textDirection;
4544 set textDirection(TextDirection? textDirection) {
4545 _textDirection = textDirection;
4546 _hasBeenAnnotated = true;
4547 }
4548
4549 /// Whether the owning [RenderObject] is selected (true) or not (false).
4550 ///
4551 /// This is different from having accessibility focus. The element that is
4552 /// accessibility focused may or may not be selected; e.g. a [ListTile] can have
4553 /// accessibility focus but have its [ListTile.selected] property set to false,
4554 /// in which case it will not be flagged as selected.
4555 bool get isSelected => _hasFlag(SemanticsFlag.isSelected);
4556 set isSelected(bool value) {
4557 _setFlag(SemanticsFlag.isSelected, value);
4558 }
4559
4560 /// If this node has Boolean state that can be controlled by the user, whether
4561 /// that state is expanded or collapsed, corresponding to true and false, respectively.
4562 ///
4563 /// Do not call the setter for this field if the owning [RenderObject] doesn't
4564 /// have expanded/collapsed state that can be controlled by the user.
4565 ///
4566 /// The getter returns null if the owning [RenderObject] does not have
4567 /// expanded/collapsed state.
4568 bool? get isExpanded => _hasFlag(SemanticsFlag.hasExpandedState) ? _hasFlag(SemanticsFlag.isExpanded) : null;
4569 set isExpanded(bool? value) {
4570 _setFlag(SemanticsFlag.hasExpandedState, true);
4571 _setFlag(SemanticsFlag.isExpanded, value!);
4572 }
4573
4574 /// Whether the owning [RenderObject] is currently enabled.
4575 ///
4576 /// A disabled object does not respond to user interactions. Only objects that
4577 /// usually respond to user interactions, but which currently do not (like a
4578 /// disabled button) should be marked as disabled.
4579 ///
4580 /// The setter should not be called for objects (like static text) that never
4581 /// respond to user interactions.
4582 ///
4583 /// The getter will return null if the owning [RenderObject] doesn't support
4584 /// the concept of being enabled/disabled.
4585 ///
4586 /// This property does not control whether semantics are enabled. If you wish to
4587 /// disable semantics for a particular widget, you should use an [ExcludeSemantics]
4588 /// widget.
4589 bool? get isEnabled => _hasFlag(SemanticsFlag.hasEnabledState) ? _hasFlag(SemanticsFlag.isEnabled) : null;
4590 set isEnabled(bool? value) {
4591 _setFlag(SemanticsFlag.hasEnabledState, true);
4592 _setFlag(SemanticsFlag.isEnabled, value!);
4593 }
4594
4595 /// If this node has Boolean state that can be controlled by the user, whether
4596 /// that state is checked or unchecked, corresponding to true and false,
4597 /// respectively.
4598 ///
4599 /// Do not call the setter for this field if the owning [RenderObject] doesn't
4600 /// have checked/unchecked state that can be controlled by the user.
4601 ///
4602 /// The getter returns null if the owning [RenderObject] does not have
4603 /// checked/unchecked state.
4604 bool? get isChecked => _hasFlag(SemanticsFlag.hasCheckedState) ? _hasFlag(SemanticsFlag.isChecked) : null;
4605 set isChecked(bool? value) {
4606 assert(value != true || isCheckStateMixed != true);
4607 _setFlag(SemanticsFlag.hasCheckedState, true);
4608 _setFlag(SemanticsFlag.isChecked, value!);
4609 }
4610
4611 /// If this node has tristate that can be controlled by the user, whether
4612 /// that state is in its mixed state.
4613 ///
4614 /// Do not call the setter for this field if the owning [RenderObject] doesn't
4615 /// have checked/unchecked state that can be controlled by the user.
4616 ///
4617 /// The getter returns null if the owning [RenderObject] does not have
4618 /// mixed checked state.
4619 bool? get isCheckStateMixed => _hasFlag(SemanticsFlag.hasCheckedState) ? _hasFlag(SemanticsFlag.isCheckStateMixed) : null;
4620 set isCheckStateMixed(bool? value) {
4621 assert(value != true || isChecked != true);
4622 _setFlag(SemanticsFlag.hasCheckedState, true);
4623 _setFlag(SemanticsFlag.isCheckStateMixed, value!);
4624 }
4625
4626 /// If this node has Boolean state that can be controlled by the user, whether
4627 /// that state is on or off, corresponding to true and false, respectively.
4628 ///
4629 /// Do not call the setter for this field if the owning [RenderObject] doesn't
4630 /// have on/off state that can be controlled by the user.
4631 ///
4632 /// The getter returns null if the owning [RenderObject] does not have
4633 /// on/off state.
4634 bool? get isToggled => _hasFlag(SemanticsFlag.hasToggledState) ? _hasFlag(SemanticsFlag.isToggled) : null;
4635 set isToggled(bool? value) {
4636 _setFlag(SemanticsFlag.hasToggledState, true);
4637 _setFlag(SemanticsFlag.isToggled, value!);
4638 }
4639
4640 /// Whether the owning RenderObject corresponds to UI that allows the user to
4641 /// pick one of several mutually exclusive options.
4642 ///
4643 /// For example, a [Radio] button is in a mutually exclusive group because
4644 /// only one radio button in that group can be marked as [isChecked].
4645 bool get isInMutuallyExclusiveGroup => _hasFlag(SemanticsFlag.isInMutuallyExclusiveGroup);
4646 set isInMutuallyExclusiveGroup(bool value) {
4647 _setFlag(SemanticsFlag.isInMutuallyExclusiveGroup, value);
4648 }
4649
4650 /// Whether the owning [RenderObject] can hold the input focus.
4651 bool get isFocusable => _hasFlag(SemanticsFlag.isFocusable);
4652 set isFocusable(bool value) {
4653 _setFlag(SemanticsFlag.isFocusable, value);
4654 }
4655
4656 /// Whether the owning [RenderObject] currently holds the input focus.
4657 bool get isFocused => _hasFlag(SemanticsFlag.isFocused);
4658 set isFocused(bool value) {
4659 _setFlag(SemanticsFlag.isFocused, value);
4660 }
4661
4662 /// Whether the owning [RenderObject] is a button (true) or not (false).
4663 bool get isButton => _hasFlag(SemanticsFlag.isButton);
4664 set isButton(bool value) {
4665 _setFlag(SemanticsFlag.isButton, value);
4666 }
4667
4668 /// Whether the owning [RenderObject] is a link (true) or not (false).
4669 bool get isLink => _hasFlag(SemanticsFlag.isLink);
4670 set isLink(bool value) {
4671 _setFlag(SemanticsFlag.isLink, value);
4672 }
4673
4674 /// Whether the owning [RenderObject] is a header (true) or not (false).
4675 bool get isHeader => _hasFlag(SemanticsFlag.isHeader);
4676 set isHeader(bool value) {
4677 _setFlag(SemanticsFlag.isHeader, value);
4678 }
4679
4680 /// Whether the owning [RenderObject] is a slider (true) or not (false).
4681 bool get isSlider => _hasFlag(SemanticsFlag.isSlider);
4682 set isSlider(bool value) {
4683 _setFlag(SemanticsFlag.isSlider, value);
4684 }
4685
4686 /// Whether the owning [RenderObject] is a keyboard key (true) or not
4687 //(false).
4688 bool get isKeyboardKey => _hasFlag(SemanticsFlag.isKeyboardKey);
4689 set isKeyboardKey(bool value) {
4690 _setFlag(SemanticsFlag.isKeyboardKey, value);
4691 }
4692
4693 /// Whether the owning [RenderObject] is considered hidden.
4694 ///
4695 /// Hidden elements are currently not visible on screen. They may be covered
4696 /// by other elements or positioned outside of the visible area of a viewport.
4697 ///
4698 /// Hidden elements cannot gain accessibility focus though regular touch. The
4699 /// only way they can be focused is by moving the focus to them via linear
4700 /// navigation.
4701 ///
4702 /// Platforms are free to completely ignore hidden elements and new platforms
4703 /// are encouraged to do so.
4704 ///
4705 /// Instead of marking an element as hidden it should usually be excluded from
4706 /// the semantics tree altogether. Hidden elements are only included in the
4707 /// semantics tree to work around platform limitations and they are mainly
4708 /// used to implement accessibility scrolling on iOS.
4709 bool get isHidden => _hasFlag(SemanticsFlag.isHidden);
4710 set isHidden(bool value) {
4711 _setFlag(SemanticsFlag.isHidden, value);
4712 }
4713
4714 /// Whether the owning [RenderObject] is a text field.
4715 bool get isTextField => _hasFlag(SemanticsFlag.isTextField);
4716 set isTextField(bool value) {
4717 _setFlag(SemanticsFlag.isTextField, value);
4718 }
4719
4720 /// Whether the owning [RenderObject] is read only.
4721 ///
4722 /// Only applicable when [isTextField] is true.
4723 bool get isReadOnly => _hasFlag(SemanticsFlag.isReadOnly);
4724 set isReadOnly(bool value) {
4725 _setFlag(SemanticsFlag.isReadOnly, value);
4726 }
4727
4728 /// Whether [this.value] should be obscured.
4729 ///
4730 /// This option is usually set in combination with [isTextField] to indicate
4731 /// that the text field contains a password (or other sensitive information).
4732 /// Doing so instructs screen readers to not read out [this.value].
4733 bool get isObscured => _hasFlag(SemanticsFlag.isObscured);
4734 set isObscured(bool value) {
4735 _setFlag(SemanticsFlag.isObscured, value);
4736 }
4737
4738 /// Whether the text field is multiline.
4739 ///
4740 /// This option is usually set in combination with [isTextField] to indicate
4741 /// that the text field is configured to be multiline.
4742 bool get isMultiline => _hasFlag(SemanticsFlag.isMultiline);
4743 set isMultiline(bool value) {
4744 _setFlag(SemanticsFlag.isMultiline, value);
4745 }
4746
4747 /// Whether the platform can scroll the semantics node when the user attempts
4748 /// to move focus to an offscreen child.
4749 ///
4750 /// For example, a [ListView] widget has implicit scrolling so that users can
4751 /// easily move to the next visible set of children. A [TabBar] widget does
4752 /// not have implicit scrolling, so that users can navigate into the tab
4753 /// body when reaching the end of the tab bar.
4754 bool get hasImplicitScrolling => _hasFlag(SemanticsFlag.hasImplicitScrolling);
4755 set hasImplicitScrolling(bool value) {
4756 _setFlag(SemanticsFlag.hasImplicitScrolling, value);
4757 }
4758
4759 /// The currently selected text (or the position of the cursor) within
4760 /// [this.value] if this node represents a text field.
4761 TextSelection? get textSelection => _textSelection;
4762 TextSelection? _textSelection;
4763 set textSelection(TextSelection? value) {
4764 assert(value != null);
4765 _textSelection = value;
4766 _hasBeenAnnotated = true;
4767 }
4768
4769 /// Indicates the current scrolling position in logical pixels if the node is
4770 /// scrollable.
4771 ///
4772 /// The properties [scrollExtentMin] and [scrollExtentMax] indicate the valid
4773 /// in-range values for this property. The value for [scrollPosition] may
4774 /// (temporarily) be outside that range, e.g. during an overscroll.
4775 ///
4776 /// See also:
4777 ///
4778 /// * [ScrollPosition.pixels], from where this value is usually taken.
4779 double? get scrollPosition => _scrollPosition;
4780 double? _scrollPosition;
4781 set scrollPosition(double? value) {
4782 assert(value != null);
4783 _scrollPosition = value;
4784 _hasBeenAnnotated = true;
4785 }
4786
4787 /// Indicates the maximum in-range value for [scrollPosition] if the node is
4788 /// scrollable.
4789 ///
4790 /// This value may be infinity if the scroll is unbound.
4791 ///
4792 /// See also:
4793 ///
4794 /// * [ScrollPosition.maxScrollExtent], from where this value is usually taken.
4795 double? get scrollExtentMax => _scrollExtentMax;
4796 double? _scrollExtentMax;
4797 set scrollExtentMax(double? value) {
4798 assert(value != null);
4799 _scrollExtentMax = value;
4800 _hasBeenAnnotated = true;
4801 }
4802
4803 /// Indicates the minimum in-range value for [scrollPosition] if the node is
4804 /// scrollable.
4805 ///
4806 /// This value may be infinity if the scroll is unbound.
4807 ///
4808 /// See also:
4809 ///
4810 /// * [ScrollPosition.minScrollExtent], from where this value is usually taken.
4811 double? get scrollExtentMin => _scrollExtentMin;
4812 double? _scrollExtentMin;
4813 set scrollExtentMin(double? value) {
4814 assert(value != null);
4815 _scrollExtentMin = value;
4816 _hasBeenAnnotated = true;
4817 }
4818
4819 // TAGS
4820
4821 /// The set of tags that this configuration wants to add to all child
4822 /// [SemanticsNode]s.
4823 ///
4824 /// See also:
4825 ///
4826 /// * [addTagForChildren] to add a tag and for more information about their
4827 /// usage.
4828 Iterable<SemanticsTag>? get tagsForChildren => _tagsForChildren;
4829
4830 /// Whether this configuration will tag the child semantics nodes with a
4831 /// given [SemanticsTag].
4832 bool tagsChildrenWith(SemanticsTag tag) => _tagsForChildren?.contains(tag) ?? false;
4833
4834 Set<SemanticsTag>? _tagsForChildren;
4835
4836 /// Specifies a [SemanticsTag] that this configuration wants to apply to all
4837 /// child [SemanticsNode]s.
4838 ///
4839 /// The tag is added to all [SemanticsNode] that pass through the
4840 /// [RenderObject] owning this configuration while looking to be attached to a
4841 /// parent [SemanticsNode].
4842 ///
4843 /// Tags are used to communicate to a parent [SemanticsNode] that a child
4844 /// [SemanticsNode] was passed through a particular [RenderObject]. The parent
4845 /// can use this information to determine the shape of the semantics tree.
4846 ///
4847 /// See also:
4848 ///
4849 /// * [RenderViewport.excludeFromScrolling] for an example of
4850 /// how tags are used.
4851 void addTagForChildren(SemanticsTag tag) {
4852 _tagsForChildren ??= <SemanticsTag>{};
4853 _tagsForChildren!.add(tag);
4854 }
4855
4856 // INTERNAL FLAG MANAGEMENT
4857
4858 int _flags = 0;
4859 void _setFlag(SemanticsFlag flag, bool value) {
4860 if (value) {
4861 _flags |= flag.index;
4862 } else {
4863 _flags &= ~flag.index;
4864 }
4865 _hasBeenAnnotated = true;
4866 }
4867
4868 bool _hasFlag(SemanticsFlag flag) => (_flags & flag.index) != 0;
4869
4870 // CONFIGURATION COMBINATION LOGIC
4871
4872 /// Whether this configuration is compatible with the provided `other`
4873 /// configuration.
4874 ///
4875 /// Two configurations are said to be compatible if they can be added to the
4876 /// same [SemanticsNode] without losing any semantics information.
4877 bool isCompatibleWith(SemanticsConfiguration? other) {
4878 if (other == null || !other.hasBeenAnnotated || !hasBeenAnnotated) {
4879 return true;
4880 }
4881 if (_actionsAsBits & other._actionsAsBits != 0) {
4882 return false;
4883 }
4884 if ((_flags & other._flags) != 0) {
4885 return false;
4886 }
4887 if (_platformViewId != null && other._platformViewId != null) {
4888 return false;
4889 }
4890 if (_maxValueLength != null && other._maxValueLength != null) {
4891 return false;
4892 }
4893 if (_currentValueLength != null && other._currentValueLength != null) {
4894 return false;
4895 }
4896 if (_attributedValue.string.isNotEmpty && other._attributedValue.string.isNotEmpty) {
4897 return false;
4898 }
4899 return true;
4900 }
4901
4902 /// Absorb the semantic information from `child` into this configuration.
4903 ///
4904 /// This adds the semantic information of both configurations and saves the
4905 /// result in this configuration.
4906 ///
4907 /// The [RenderObject] owning the `child` configuration must be a descendant
4908 /// of the [RenderObject] that owns this configuration.
4909 ///
4910 /// Only configurations that have [explicitChildNodes] set to false can
4911 /// absorb other configurations and it is recommended to only absorb compatible
4912 /// configurations as determined by [isCompatibleWith].
4913 void absorb(SemanticsConfiguration child) {
4914 assert(!explicitChildNodes);
4915
4916 if (!child.hasBeenAnnotated) {
4917 return;
4918 }
4919 if (child.isBlockingUserActions) {
4920 child._actions.forEach((SemanticsAction key, SemanticsActionHandler value) {
4921 if (_kUnblockedUserActions & key.index > 0) {
4922 _actions[key] = value;
4923 }
4924 });
4925 } else {
4926 _actions.addAll(child._actions);
4927 }
4928 _actionsAsBits |= child._effectiveActionsAsBits;
4929 _customSemanticsActions.addAll(child._customSemanticsActions);
4930 _flags |= child._flags;
4931 _textSelection ??= child._textSelection;
4932 _scrollPosition ??= child._scrollPosition;
4933 _scrollExtentMax ??= child._scrollExtentMax;
4934 _scrollExtentMin ??= child._scrollExtentMin;
4935 _hintOverrides ??= child._hintOverrides;
4936 _indexInParent ??= child.indexInParent;
4937 _scrollIndex ??= child._scrollIndex;
4938 _scrollChildCount ??= child._scrollChildCount;
4939 _platformViewId ??= child._platformViewId;
4940 _maxValueLength ??= child._maxValueLength;
4941 _currentValueLength ??= child._currentValueLength;
4942
4943 textDirection ??= child.textDirection;
4944 _sortKey ??= child._sortKey;
4945 if (_identifier == '') {
4946 _identifier = child._identifier;
4947 }
4948 _attributedLabel = _concatAttributedString(
4949 thisAttributedString: _attributedLabel,
4950 thisTextDirection: textDirection,
4951 otherAttributedString: child._attributedLabel,
4952 otherTextDirection: child.textDirection,
4953 );
4954 if (_attributedValue.string == '') {
4955 _attributedValue = child._attributedValue;
4956 }
4957 if (_attributedIncreasedValue.string == '') {
4958 _attributedIncreasedValue = child._attributedIncreasedValue;
4959 }
4960 if (_attributedDecreasedValue.string == '') {
4961 _attributedDecreasedValue = child._attributedDecreasedValue;
4962 }
4963 _attributedHint = _concatAttributedString(
4964 thisAttributedString: _attributedHint,
4965 thisTextDirection: textDirection,
4966 otherAttributedString: child._attributedHint,
4967 otherTextDirection: child.textDirection,
4968 );
4969 if (_tooltip == '') {
4970 _tooltip = child._tooltip;
4971 }
4972
4973 _thickness = math.max(_thickness, child._thickness + child._elevation);
4974
4975 _hasBeenAnnotated = _hasBeenAnnotated || child._hasBeenAnnotated;
4976 }
4977
4978 /// Returns an exact copy of this configuration.
4979 SemanticsConfiguration copy() {
4980 return SemanticsConfiguration()
4981 .._isSemanticBoundary = _isSemanticBoundary
4982 ..explicitChildNodes = explicitChildNodes
4983 ..isBlockingSemanticsOfPreviouslyPaintedNodes = isBlockingSemanticsOfPreviouslyPaintedNodes
4984 .._hasBeenAnnotated = _hasBeenAnnotated
4985 .._isMergingSemanticsOfDescendants = _isMergingSemanticsOfDescendants
4986 .._textDirection = _textDirection
4987 .._sortKey = _sortKey
4988 .._identifier = _identifier
4989 .._attributedLabel = _attributedLabel
4990 .._attributedIncreasedValue = _attributedIncreasedValue
4991 .._attributedValue = _attributedValue
4992 .._attributedDecreasedValue = _attributedDecreasedValue
4993 .._attributedHint = _attributedHint
4994 .._hintOverrides = _hintOverrides
4995 .._tooltip = _tooltip
4996 .._elevation = _elevation
4997 .._thickness = _thickness
4998 .._flags = _flags
4999 .._tagsForChildren = _tagsForChildren
5000 .._textSelection = _textSelection
5001 .._scrollPosition = _scrollPosition
5002 .._scrollExtentMax = _scrollExtentMax
5003 .._scrollExtentMin = _scrollExtentMin
5004 .._actionsAsBits = _actionsAsBits
5005 .._indexInParent = indexInParent
5006 .._scrollIndex = _scrollIndex
5007 .._scrollChildCount = _scrollChildCount
5008 .._platformViewId = _platformViewId
5009 .._maxValueLength = _maxValueLength
5010 .._currentValueLength = _currentValueLength
5011 .._actions.addAll(_actions)
5012 .._customSemanticsActions.addAll(_customSemanticsActions)
5013 ..isBlockingUserActions = isBlockingUserActions;
5014 }
5015}
5016
5017/// Used by [debugDumpSemanticsTree] to specify the order in which child nodes
5018/// are printed.
5019enum DebugSemanticsDumpOrder {
5020 /// Print nodes in inverse hit test order.
5021 ///
5022 /// In inverse hit test order, the last child of a [SemanticsNode] will be
5023 /// asked first if it wants to respond to a user's interaction, followed by
5024 /// the second last, etc. until a taker is found.
5025 inverseHitTest,
5026
5027 /// Print nodes in semantic traversal order.
5028 ///
5029 /// This is the order in which a user would navigate the UI using the "next"
5030 /// and "previous" gestures.
5031 traversalOrder,
5032}
5033
5034AttributedString _concatAttributedString({
5035 required AttributedString thisAttributedString,
5036 required AttributedString otherAttributedString,
5037 required TextDirection? thisTextDirection,
5038 required TextDirection? otherTextDirection,
5039}) {
5040 if (otherAttributedString.string.isEmpty) {
5041 return thisAttributedString;
5042 }
5043 if (thisTextDirection != otherTextDirection && otherTextDirection != null) {
5044 switch (otherTextDirection) {
5045 case TextDirection.rtl:
5046 otherAttributedString = AttributedString(Unicode.RLE) + otherAttributedString + AttributedString(Unicode.PDF);
5047 case TextDirection.ltr:
5048 otherAttributedString = AttributedString(Unicode.LRE) + otherAttributedString + AttributedString(Unicode.PDF);
5049 }
5050 }
5051 if (thisAttributedString.string.isEmpty) {
5052 return otherAttributedString;
5053 }
5054
5055 return thisAttributedString + AttributedString('\n') + otherAttributedString;
5056}
5057
5058/// Base class for all sort keys for [SemanticsProperties.sortKey] accessibility
5059/// traversal order sorting.
5060///
5061/// Sort keys are sorted by [name], then by the comparison that the subclass
5062/// implements. If [SemanticsProperties.sortKey] is specified, sort keys within
5063/// the same semantic group must all be of the same type.
5064///
5065/// Keys with no [name] are compared to other keys with no [name], and will
5066/// be traversed before those with a [name].
5067///
5068/// If no sort key is applied to a semantics node, then it will be ordered using
5069/// a platform dependent default algorithm.
5070///
5071/// See also:
5072///
5073/// * [OrdinalSortKey] for a sort key that sorts using an ordinal.
5074abstract class SemanticsSortKey with Diagnosticable implements Comparable<SemanticsSortKey> {
5075 /// Abstract const constructor. This constructor enables subclasses to provide
5076 /// const constructors so that they can be used in const expressions.
5077 const SemanticsSortKey({this.name});
5078
5079 /// An optional name that will group this sort key with other sort keys of the
5080 /// same [name].
5081 ///
5082 /// Sort keys must have the same `runtimeType` when compared.
5083 ///
5084 /// Keys with no [name] are compared to other keys with no [name], and will
5085 /// be traversed before those with a [name].
5086 final String? name;
5087
5088 @override
5089 int compareTo(SemanticsSortKey other) {
5090 // Sort by name first and then subclass ordering.
5091 assert(runtimeType == other.runtimeType, 'Semantics sort keys can only be compared to other sort keys of the same type.');
5092
5093 // Defer to the subclass implementation for ordering only if the names are
5094 // identical (or both null).
5095 if (name == other.name) {
5096 return doCompare(other);
5097 }
5098
5099 // Keys that don't have a name are sorted together and come before those with
5100 // a name.
5101 if (name == null && other.name != null) {
5102 return -1;
5103 } else if (name != null && other.name == null) {
5104 return 1;
5105 }
5106
5107 return name!.compareTo(other.name!);
5108 }
5109
5110 /// The implementation of [compareTo].
5111 ///
5112 /// The argument is guaranteed to be of the same type as this object and have
5113 /// the same [name].
5114 ///
5115 /// The method should return a negative number if this object comes earlier in
5116 /// the sort order than the argument; and a positive number if it comes later
5117 /// in the sort order. Returning zero causes the system to use default sort
5118 /// order.
5119 @protected
5120 int doCompare(covariant SemanticsSortKey other);
5121
5122 @override
5123 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
5124 super.debugFillProperties(properties);
5125 properties.add(StringProperty('name', name, defaultValue: null));
5126 }
5127}
5128
5129/// A [SemanticsSortKey] that sorts based on the `double` value it is
5130/// given.
5131///
5132/// The [OrdinalSortKey] compares itself with other [OrdinalSortKey]s
5133/// to sort based on the order it is given.
5134///
5135/// [OrdinalSortKey]s are sorted by the optional [name], then by their [order].
5136/// If [SemanticsProperties.sortKey] is a [OrdinalSortKey], then all the other
5137/// specified sort keys in the same semantics group must also be
5138/// [OrdinalSortKey]s.
5139///
5140/// Keys with no [name] are compared to other keys with no [name], and will
5141/// be traversed before those with a [name].
5142///
5143/// The ordinal value [order] is typically a whole number, though it can be
5144/// fractional, e.g. in order to fit between two other consecutive whole
5145/// numbers. The value must be finite (it cannot be [double.nan],
5146/// [double.infinity], or [double.negativeInfinity]).
5147class OrdinalSortKey extends SemanticsSortKey {
5148 /// Creates a const semantics sort key that uses a [double] as its key value.
5149 ///
5150 /// The [order] must be a finite number.
5151 const OrdinalSortKey(
5152 this.order, {
5153 super.name,
5154 }) : assert(order > double.negativeInfinity),
5155 assert(order < double.infinity);
5156
5157 /// Determines the placement of this key in a sequence of keys that defines
5158 /// the order in which this node is traversed by the platform's accessibility
5159 /// services.
5160 ///
5161 /// Lower values will be traversed first. Keys with the same [name] will be
5162 /// grouped together and sorted by name first, and then sorted by [order].
5163 final double order;
5164
5165 @override
5166 int doCompare(OrdinalSortKey other) {
5167 if (other.order == order) {
5168 return 0;
5169 }
5170 return order.compareTo(other.order);
5171 }
5172
5173 @override
5174 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
5175 super.debugFillProperties(properties);
5176 properties.add(DoubleProperty('order', order, defaultValue: null));
5177 }
5178}
5179