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