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

Provided by KDAB

Privacy Policy
Learn more about Flutter for embedded and desktop on industrialflutter.com