semantics.dart 179 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
Hixie's avatar
Hixie committed
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5
import 'dart:math' as math;
6
import 'dart:typed_data';
7
import 'dart:ui' as ui;
8
import 'dart:ui' show Offset, Rect, SemanticsAction, SemanticsFlag,
9
       TextDirection, StringAttribute;
Hixie's avatar
Hixie committed
10

11
import 'package:flutter/foundation.dart';
12
import 'package:flutter/painting.dart' show MatrixUtils, TransformProperty;
13
import 'package:flutter/services.dart';
Hixie's avatar
Hixie committed
14 15
import 'package:vector_math/vector_math_64.dart';

16
import 'binding.dart' show SemanticsBinding;
17
import 'semantics_event.dart';
Hixie's avatar
Hixie committed
18

19
export 'dart:ui' show SemanticsAction, StringAttribute, SpellOutStringAttribute, LocaleStringAttribute;
20
export 'semantics_event.dart';
Hixie's avatar
Hixie committed
21

22 23 24
/// Signature for a function that is called for each [SemanticsNode].
///
/// Return false to stop visiting nodes.
25 26
///
/// Used by [SemanticsNode.visitChildren].
27
typedef SemanticsNodeVisitor = bool Function(SemanticsNode node);
Hixie's avatar
Hixie committed
28

29 30 31 32
/// Signature for [SemanticsAction]s that move the cursor.
///
/// If `extendSelection` is set to true the cursor movement should extend the
/// current selection or (if nothing is currently selected) start a selection.
33
typedef MoveCursorHandler = void Function(bool extendSelection);
34

35 36
/// Signature for the [SemanticsAction.setSelection] handlers to change the
/// text selection (or re-position the cursor) to `selection`.
37
typedef SetSelectionHandler = void Function(TextSelection selection);
38

39 40 41 42
/// Signature for the [SemanticsAction.setText] handlers to replace the
/// current text with the input `text`.
typedef SetTextHandler = void Function(String text);

43 44 45 46
/// Signature for a handler of a [SemanticsAction].
///
/// Returned by [SemanticsConfiguration.getActionHandler].
typedef SemanticsActionHandler = void Function(Object? args);
47

48 49 50 51
/// A tag for a [SemanticsNode].
///
/// Tags can be interpreted by the parent of a [SemanticsNode]
/// and depending on the presence of a tag the parent can for example decide
52
/// how to add the tagged node as a child. Tags are not sent to the engine.
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
///
/// As an example, the [RenderSemanticsGestureHandler] uses tags to determine
/// if a child node should be excluded from the scrollable area for semantic
/// purposes.
///
/// The provided [name] is only used for debugging. Two tags created with the
/// same [name] and the `new` operator are not considered identical. However,
/// two tags created with the same [name] and the `const` operator are always
/// identical.
class SemanticsTag {
  /// Creates a [SemanticsTag].
  ///
  /// The provided [name] is only used for debugging. Two tags created with the
  /// same [name] and the `new` operator are not considered identical. However,
  /// two tags created with the same [name] and the `const` operator are always
  /// identical.
  const SemanticsTag(this.name);

  /// A human-readable name for this tag used for debugging.
  ///
  /// This string is not used to determine if two tags are identical.
  final String name;

  @override
77
  String toString() => '${objectRuntimeType(this, 'SemanticsTag')}($name)';
78 79
}

80
/// An identifier of a custom semantics action.
81
///
82
/// Custom semantics actions can be provided to make complex user
83
/// interactions more accessible. For instance, if an application has a
84 85 86 87 88
/// drag-and-drop list that requires the user to press and hold an item
/// to move it, users interacting with the application using a hardware
/// switch may have difficulty. This can be made accessible by creating custom
/// actions and pairing them with handlers that move a list item up or down in
/// the list.
89
///
90
/// In Android, these actions are presented in the local context menu. In iOS,
91 92
/// these are presented in the radial context menu.
///
93
/// Localization and text direction do not automatically apply to the provided
94
/// label or hint.
95
///
96 97
/// Instances of this class should either be instantiated with const or
/// new instances cached in static fields.
98
///
99
/// See also:
100
///
101
///  * [SemanticsProperties], where the handler for a custom action is provided.
102 103 104
@immutable
class CustomSemanticsAction {
  /// Creates a new [CustomSemanticsAction].
105
  ///
106
  /// The [label] must not be null or the empty string.
107
  const CustomSemanticsAction({required String this.label})
108
    : assert(label != null),
109 110 111
      assert(label != ''),
      hint = null,
      action = null;
112

113 114 115 116
  /// Creates a new [CustomSemanticsAction] that overrides a standard semantics
  /// action.
  ///
  /// The [hint] must not be null or the empty string.
117
  const CustomSemanticsAction.overridingAction({required String this.hint, required SemanticsAction this.action})
118 119 120 121 122 123
    : assert(hint != null),
      assert(hint != ''),
      assert(action != null),
      label = null;

  /// The user readable name of this custom semantics action.
124
  final String? label;
125

126
  /// The hint description of this custom semantics action.
127
  final String? hint;
128 129

  /// The standard semantics action this action replaces.
130
  final SemanticsAction? action;
131

132
  @override
133
  int get hashCode => Object.hash(label, hint, action);
134 135

  @override
136
  bool operator ==(Object other) {
137 138
    if (other.runtimeType != runtimeType)
      return false;
139 140 141 142
    return other is CustomSemanticsAction
        && other.label == label
        && other.hint == hint
        && other.action == action;
143 144 145 146 147
  }

  @override
  String toString() {
    return 'CustomSemanticsAction(${_ids[this]}, label:$label, hint:$hint, action:$action)';
148 149 150
  }

  // Logic to assign a unique id to each custom action without requiring
151
  // user specification.
152 153 154 155 156 157
  static int _nextId = 0;
  static final Map<int, CustomSemanticsAction> _actions = <int, CustomSemanticsAction>{};
  static final Map<CustomSemanticsAction, int> _ids = <CustomSemanticsAction, int>{};

  /// Get the identifier for a given `action`.
  static int getIdentifier(CustomSemanticsAction action) {
158
    int? result = _ids[action];
159 160 161 162 163 164 165 166 167
    if (result == null) {
      result = _nextId++;
      _ids[action] = result;
      _actions[result] = action;
    }
    return result;
  }

  /// Get the `action` for a given identifier.
168
  static CustomSemanticsAction? getAction(int id) {
169 170 171 172
    return _actions[id];
  }
}

173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219
/// A string that carries a list of [StringAttribute]s.
@immutable
class AttributedString {
  /// Creates a attributed string.
  ///
  /// The [TextRange] in the [attributes] must be inside the length of the
  /// [string].
  ///
  /// The [attributes] must not be changed after the attributed string is
  /// created.
  AttributedString(
    this.string, {
    this.attributes = const <StringAttribute>[],
  }) : assert(string.isNotEmpty || attributes.isEmpty),
       assert(() {
        for (final StringAttribute attribute in attributes) {
          assert(
            string.length >= attribute.range.start &&
            string.length >= attribute.range.end,
            'The range in $attribute is outside of the string $string',
          );
        }
        return true;
      }());

  /// The plain string stored in the attributed string.
  final String string;

  /// The attributes this string carries.
  ///
  /// The list must not be modified after this string is created.
  final List<StringAttribute> attributes;

  /// Returns a new [AttributedString] by concatenate the operands
  ///
  /// The string attribute list of the returned [AttributedString] will contains
  /// the string attributes from both operands with updated text ranges.
  AttributedString operator +(AttributedString other) {
    if (string.isEmpty) {
      return other;
    }
    if (other.string.isEmpty) {
      return this;
    }

    // None of the strings is empty.
    final String newString = string + other.string;
220
    final List<StringAttribute> newAttributes = List<StringAttribute>.of(attributes);
221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244
    if (other.attributes.isNotEmpty) {
      final int offset = string.length;
      for (final StringAttribute attribute in other.attributes) {
        final TextRange newRange = TextRange(
          start: attribute.range.start + offset,
          end: attribute.range.end + offset,
        );
        final StringAttribute adjustedAttribute = attribute.copy(range: newRange);
        newAttributes.add(adjustedAttribute);
      }
    }
    return AttributedString(newString, attributes: newAttributes);
  }

  /// Two [AttributedString]s are equal if their string and attributes are.
  @override
  bool operator ==(Object other) {
    return other.runtimeType == runtimeType
        && other is AttributedString
        && other.string == string
        && listEquals<StringAttribute>(other.attributes, attributes);
  }

  @override
245
  int get hashCode => Object.hash(string, attributes,);
246 247 248 249 250 251 252

  @override
  String toString() {
    return "${objectRuntimeType(this, 'AttributedString')}('$string', attributes: $attributes)";
  }
}

253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303
/// A [DiagnosticsProperty] for [AttributedString]s, which shows a string
/// when there are no attributes, and more details otherwise.
class AttributedStringProperty extends DiagnosticsProperty<AttributedString> {
  /// Create a diagnostics property for an [AttributedString] object.
  ///
  /// Such properties are used with [SemanticsData] objects.
  AttributedStringProperty(
    String name,
    AttributedString? value, {
    bool showName = true,
    this.showWhenEmpty = false,
    Object? defaultValue = kNoDefaultValue,
    DiagnosticLevel level = DiagnosticLevel.info,
    String? description,
  }) : assert(showName != null),
       assert(level != null),
       super(
         name,
         value,
         showName: showName,
         defaultValue: defaultValue,
         level: level,
         description: description,
       );

  /// Whether to show the property when the [value] is an [AttributedString]
  /// whose [AttributedString.string] is the empty string.
  ///
  /// This overrides [defaultValue].
  final bool showWhenEmpty;

  @override
  bool get isInteresting => super.isInteresting && (showWhenEmpty || (value != null && value!.string.isNotEmpty));

  @override
  String valueToString({TextTreeConfiguration? parentConfiguration}) {
    if (value == null)
      return 'null';
    String text = value!.string;
    if (parentConfiguration != null &&
        !parentConfiguration.lineBreakProperties) {
      // This follows a similar pattern to StringProperty.
      text = text.replaceAll('\n', r'\n');
    }
    if (value!.attributes.isEmpty) {
      return '"$text"';
    }
    return '"$text" ${value!.attributes}'; // the attributes will be in square brackets since they're a list
  }
}

304 305 306 307 308 309 310 311
/// Summary information about a [SemanticsNode] object.
///
/// A semantics node might [SemanticsNode.mergeAllDescendantsIntoThisNode],
/// which means the individual fields on the semantics node don't fully describe
/// the semantics at that node. This data structure contains the full semantics
/// for the node.
///
/// Typically obtained from [SemanticsNode.getSemanticsData].
312
@immutable
313
class SemanticsData with Diagnosticable {
314 315
  /// Creates a semantics data object.
  ///
316
  /// The [flags], [actions], [label], and [Rect] arguments must not be null.
Ian Hickson's avatar
Ian Hickson committed
317 318
  ///
  /// If [label] is not empty, then [textDirection] must also not be null.
319
  SemanticsData({
320 321
    required this.flags,
    required this.actions,
322 323 324 325 326
    required this.attributedLabel,
    required this.attributedValue,
    required this.attributedIncreasedValue,
    required this.attributedDecreasedValue,
    required this.attributedHint,
327 328 329 330 331 332 333 334 335 336 337 338 339
    required this.textDirection,
    required this.rect,
    required this.elevation,
    required this.thickness,
    required this.textSelection,
    required this.scrollIndex,
    required this.scrollChildCount,
    required this.scrollPosition,
    required this.scrollExtentMax,
    required this.scrollExtentMin,
    required this.platformViewId,
    required this.maxValueLength,
    required this.currentValueLength,
340
    this.tags,
Ian Hickson's avatar
Ian Hickson committed
341
    this.transform,
342
    this.customSemanticsActionIds,
343 344
  }) : assert(flags != null),
       assert(actions != null),
345 346 347 348 349 350 351 352 353
       assert(attributedLabel != null),
       assert(attributedValue != null),
       assert(attributedDecreasedValue != null),
       assert(attributedIncreasedValue != null),
       assert(attributedHint != null),
       assert(attributedLabel.string == '' || textDirection != null, 'A SemanticsData object with label "${attributedLabel.string}" had a null textDirection.'),
       assert(attributedValue.string == '' || textDirection != null, 'A SemanticsData object with value "${attributedValue.string}" had a null textDirection.'),
       assert(attributedDecreasedValue.string == '' || textDirection != null, 'A SemanticsData object with decreasedValue "${attributedDecreasedValue.string}" had a null textDirection.'),
       assert(attributedIncreasedValue.string == '' || textDirection != null, 'A SemanticsData object with increasedValue "${attributedIncreasedValue.string}" had a null textDirection.'),
354
       assert(attributedHint.string == '' || textDirection != null, 'A SemanticsData object with hint "${attributedHint.string}" had a null textDirection.'),
355
       assert(rect != null);
356

357
  /// A bit field of [SemanticsFlag]s that apply to this node.
358 359
  final int flags;

360
  /// A bit field of [SemanticsAction]s that apply to this node.
361 362
  final int actions;

363 364 365
  /// A textual description for the current label of the node.
  ///
  /// The reading direction is given by [textDirection].
366 367
  ///
  /// This exposes the raw text of the [attributedLabel].
368 369 370 371
  String get label => attributedLabel.string;

  /// A textual description for the current label of the node in
  /// [AttributedString] format.
Ian Hickson's avatar
Ian Hickson committed
372
  ///
373
  /// The reading direction is given by [textDirection].
374 375
  ///
  /// See also [label], which exposes just the raw text.
376
  final AttributedString attributedLabel;
377

378 379
  /// A textual description for the current value of the node.
  ///
380
  /// The reading direction is given by [textDirection].
381 382
  ///
  /// This exposes the raw text of the [attributedValue].
383 384 385 386 387 388
  String get value => attributedValue.string;

  /// A textual description for the current value of the node in
  /// [AttributedString] format.
  ///
  /// The reading direction is given by [textDirection].
389 390
  ///
  /// See also [value], which exposes just the raw text.
391
  final AttributedString attributedValue;
392

393 394 395 396
  /// The value that [value] will become after performing a
  /// [SemanticsAction.increase] action.
  ///
  /// The reading direction is given by [textDirection].
397 398
  ///
  /// This exposes the raw text of the [attributedIncreasedValue].
399 400 401 402 403 404
  String get increasedValue => attributedIncreasedValue.string;

  /// The value that [value] will become after performing a
  /// [SemanticsAction.increase] action in [AttributedString] format.
  ///
  /// The reading direction is given by [textDirection].
405 406
  ///
  /// See also [increasedValue], which exposes just the raw text.
407
  final AttributedString attributedIncreasedValue;
408 409 410 411 412

  /// The value that [value] will become after performing a
  /// [SemanticsAction.decrease] action.
  ///
  /// The reading direction is given by [textDirection].
413 414
  ///
  /// This exposes the raw text of the [attributedDecreasedValue].
415 416 417 418 419 420
  String get decreasedValue => attributedDecreasedValue.string;

  /// The value that [value] will become after performing a
  /// [SemanticsAction.decrease] action in [AttributedString] format.
  ///
  /// The reading direction is given by [textDirection].
421 422
  ///
  /// See also [decreasedValue], which exposes just the raw text.
423
  final AttributedString attributedDecreasedValue;
424

425 426
  /// A brief description of the result of performing an action on this node.
  ///
427
  /// The reading direction is given by [textDirection].
428 429
  ///
  /// This exposes the raw text of the [attributedHint].
430 431 432 433 434 435
  String get hint => attributedHint.string;

  /// A brief description of the result of performing an action on this node
  /// in [AttributedString] format.
  ///
  /// The reading direction is given by [textDirection].
436 437
  ///
  /// See also [hint], which exposes just the raw text.
438
  final AttributedString attributedHint;
439

440 441
  /// The reading direction for the text in [label], [value],
  /// [increasedValue], [decreasedValue], and [hint].
442
  final TextDirection? textDirection;
Ian Hickson's avatar
Ian Hickson committed
443

444 445
  /// The currently selected text (or the position of the cursor) within [value]
  /// if this node represents a text field.
446
  final TextSelection? textSelection;
447

448 449 450 451
  /// The total number of scrollable children that contribute to semantics.
  ///
  /// If the number of children are unknown or unbounded, this value will be
  /// null.
452
  final int? scrollChildCount;
453 454

  /// The index of the first visible semantic child of a scroll node.
455
  final int? scrollIndex;
456

457 458 459 460 461 462 463 464 465 466
  /// Indicates the current scrolling position in logical pixels if the node is
  /// scrollable.
  ///
  /// The properties [scrollExtentMin] and [scrollExtentMax] indicate the valid
  /// in-range values for this property. The value for [scrollPosition] may
  /// (temporarily) be outside that range, e.g. during an overscroll.
  ///
  /// See also:
  ///
  ///  * [ScrollPosition.pixels], from where this value is usually taken.
467
  final double? scrollPosition;
468 469 470 471 472 473 474 475 476

  /// Indicates the maximum in-range value for [scrollPosition] if the node is
  /// scrollable.
  ///
  /// This value may be infinity if the scroll is unbound.
  ///
  /// See also:
  ///
  ///  * [ScrollPosition.maxScrollExtent], from where this value is usually taken.
477
  final double? scrollExtentMax;
478

Josh Soref's avatar
Josh Soref committed
479
  /// Indicates the minimum in-range value for [scrollPosition] if the node is
480 481 482 483 484 485 486
  /// scrollable.
  ///
  /// This value may be infinity if the scroll is unbound.
  ///
  /// See also:
  ///
  ///  * [ScrollPosition.minScrollExtent], from where this value is usually taken.
487
  final double? scrollExtentMin;
488

489 490 491 492 493 494 495 496 497 498 499
  /// The id of the platform view, whose semantics nodes will be added as
  /// children to this node.
  ///
  /// If this value is non-null, the SemanticsNode must not have any children
  /// as those would be replaced by the semantics nodes of the referenced
  /// platform view.
  ///
  /// See also:
  ///
  ///  * [AndroidView], which is the platform view for Android.
  ///  * [UiKitView], which is the platform view for iOS.
500
  final int? platformViewId;
501

502 503 504 505 506 507 508 509
  /// The maximum number of characters that can be entered into an editable
  /// text field.
  ///
  /// For the purpose of this function a character is defined as one Unicode
  /// scalar value.
  ///
  /// This should only be set when [SemanticsFlag.isTextField] is set. Defaults
  /// to null, which means no limit is imposed on the text field.
510
  final int? maxValueLength;
511 512 513 514 515 516 517 518 519

  /// The current number of characters that have been entered into an editable
  /// text field.
  ///
  /// For the purpose of this function a character is defined as one Unicode
  /// scalar value.
  ///
  /// This should only be set when [SemanticsFlag.isTextField] is set. This must
  /// be set when [maxValueLength] is set.
520
  final int? currentValueLength;
521

522 523 524
  /// The bounding box for this node in its coordinate system.
  final Rect rect;

525
  /// The set of [SemanticsTag]s associated with this node.
526
  final Set<SemanticsTag>? tags;
527

528 529 530
  /// The transform from this node's coordinate system to its parent's coordinate system.
  ///
  /// By default, the transform is null, which represents the identity
531
  /// transformation (i.e., that this node has the same coordinate system as its
532
  /// parent).
533
  final Matrix4? transform;
534

535 536 537 538 539 540 541 542 543 544 545 546 547 548 549
  /// The elevation of this node relative to the parent semantics node.
  ///
  /// See also:
  ///
  ///  * [SemanticsConfiguration.elevation] for a detailed discussion regarding
  ///    elevation and semantics.
  final double elevation;

  /// The extent of this node along the z-axis beyond its [elevation]
  ///
  /// See also:
  ///
  ///  * [SemanticsConfiguration.thickness] for a more detailed definition.
  final double thickness;

550 551
  /// The identifiers for the custom semantics actions and standard action
  /// overrides for this node.
552
  ///
553
  /// The list must be sorted in increasing order.
554
  ///
555
  /// See also:
556
  ///
557
  ///  * [CustomSemanticsAction], for an explanation of custom actions.
558
  final List<int>? customSemanticsActionIds;
559

560
  /// Whether [flags] contains the given flag.
561
  bool hasFlag(SemanticsFlag flag) => (flags & flag.index) != 0;
562 563 564

  /// Whether [actions] contains the given action.
  bool hasAction(SemanticsAction action) => (actions & action.index) != 0;
565 566

  @override
567
  String toStringShort() => objectRuntimeType(this, 'SemanticsData');
568 569 570 571

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
572 573
    properties.add(DiagnosticsProperty<Rect>('rect', rect, showName: false));
    properties.add(TransformProperty('transform', transform, showName: false, defaultValue: null));
574 575
    properties.add(DoubleProperty('elevation', elevation, defaultValue: 0.0));
    properties.add(DoubleProperty('thickness', thickness, defaultValue: 0.0));
576
    final List<String> actionSummary = <String>[
577
      for (final SemanticsAction action in SemanticsAction.values.values)
578 579 580
        if ((actions & action.index) != 0)
          describeEnum(action),
    ];
581 582
    final List<String?> customSemanticsActionSummary = customSemanticsActionIds!
      .map<String?>((int actionId) => CustomSemanticsAction.getAction(actionId)!.label)
583
      .toList();
584
    properties.add(IterableProperty<String>('actions', actionSummary, ifEmpty: null));
585
    properties.add(IterableProperty<String?>('customActions', customSemanticsActionSummary, ifEmpty: null));
586

587
    final List<String> flagSummary = <String>[
588
      for (final SemanticsFlag flag in SemanticsFlag.values.values)
589 590 591
        if ((flags & flag.index) != 0)
          describeEnum(flag),
    ];
592
    properties.add(IterableProperty<String>('flags', flagSummary, ifEmpty: null));
593 594 595 596 597
    properties.add(AttributedStringProperty('label', attributedLabel));
    properties.add(AttributedStringProperty('value', attributedValue));
    properties.add(AttributedStringProperty('increasedValue', attributedIncreasedValue));
    properties.add(AttributedStringProperty('decreasedValue', attributedDecreasedValue));
    properties.add(AttributedStringProperty('hint', attributedHint));
598
    properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
599
    if (textSelection?.isValid ?? false)
600
      properties.add(MessageProperty('textSelection', '[${textSelection!.start}, ${textSelection!.end}]'));
601
    properties.add(IntProperty('platformViewId', platformViewId, defaultValue: null));
602 603
    properties.add(IntProperty('maxValueLength', maxValueLength, defaultValue: null));
    properties.add(IntProperty('currentValueLength', currentValueLength, defaultValue: null));
604 605
    properties.add(IntProperty('scrollChildren', scrollChildCount, defaultValue: null));
    properties.add(IntProperty('scrollIndex', scrollIndex, defaultValue: null));
606 607 608
    properties.add(DoubleProperty('scrollExtentMin', scrollExtentMin, defaultValue: null));
    properties.add(DoubleProperty('scrollPosition', scrollPosition, defaultValue: null));
    properties.add(DoubleProperty('scrollExtentMax', scrollExtentMax, defaultValue: null));
609 610 611
  }

  @override
612
  bool operator ==(Object other) {
613 614 615
    return other is SemanticsData
        && other.flags == flags
        && other.actions == actions
616 617 618 619 620
        && other.attributedLabel == attributedLabel
        && other.attributedValue == attributedValue
        && other.attributedIncreasedValue == attributedIncreasedValue
        && other.attributedDecreasedValue == attributedDecreasedValue
        && other.attributedHint == attributedHint
621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636
        && other.textDirection == textDirection
        && other.rect == rect
        && setEquals(other.tags, tags)
        && other.scrollChildCount == scrollChildCount
        && other.scrollIndex == scrollIndex
        && other.textSelection == textSelection
        && other.scrollPosition == scrollPosition
        && other.scrollExtentMax == scrollExtentMax
        && other.scrollExtentMin == scrollExtentMin
        && other.platformViewId == platformViewId
        && other.maxValueLength == maxValueLength
        && other.currentValueLength == currentValueLength
        && other.transform == transform
        && other.elevation == elevation
        && other.thickness == thickness
        && _sortedListsEqual(other.customSemanticsActionIds, customSemanticsActionIds);
637 638 639
  }

  @override
640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661
  int get hashCode => Object.hash(
    flags,
    actions,
    attributedLabel,
    attributedValue,
    attributedIncreasedValue,
    attributedDecreasedValue,
    attributedHint,
    textDirection,
    rect,
    tags,
    textSelection,
    scrollChildCount,
    scrollIndex,
    scrollPosition,
    scrollExtentMax,
    scrollExtentMin,
    platformViewId,
    maxValueLength,
    currentValueLength,
    Object.hash(
      transform,
662 663
      elevation,
      thickness,
664 665 666
      customSemanticsActionIds == null ? null : Object.hashAll(customSemanticsActionIds!),
    ),
  );
667

668
  static bool _sortedListsEqual(List<int>? left, List<int>? right) {
669 670 671 672 673 674 675 676 677 678 679 680
    if (left == null && right == null)
      return true;
    if (left != null && right != null) {
      if (left.length != right.length)
        return false;
      for (int i = 0; i < left.length; i++)
        if (left[i] != right[i])
          return false;
      return true;
    }
    return false;
  }
681 682
}

683 684
class _SemanticsDiagnosticableNode extends DiagnosticableNode<SemanticsNode> {
  _SemanticsDiagnosticableNode({
685 686 687 688
    String? name,
    required SemanticsNode value,
    required DiagnosticsTreeStyle? style,
    required this.childOrder,
689 690 691 692 693 694 695 696 697
  }) : super(
    name: name,
    value: value,
    style: style,
  );

  final DebugSemanticsDumpOrder childOrder;

  @override
698
  List<DiagnosticsNode> getChildren() => value.debugDescribeChildren(childOrder: childOrder);
699 700
}

701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722
/// Provides hint values which override the default hints on supported
/// platforms.
///
/// On iOS, these values are always ignored.
@immutable
class SemanticsHintOverrides extends DiagnosticableTree {
  /// Creates a semantics hint overrides.
  const SemanticsHintOverrides({
    this.onTapHint,
    this.onLongPressHint,
  }) : assert(onTapHint != ''),
       assert(onLongPressHint != '');

  /// The hint text for a tap action.
  ///
  /// If null, the standard hint is used instead.
  ///
  /// The hint should describe what happens when a tap occurs, not the
  /// manner in which a tap is accomplished.
  ///
  /// Bad: 'Double tap to show movies'.
  /// Good: 'show movies'.
723
  final String? onTapHint;
724 725 726 727 728 729 730 731 732 733

  /// The hint text for a long press action.
  ///
  /// If null, the standard hint is used instead.
  ///
  /// The hint should describe what happens when a long press occurs, not
  /// the manner in which the long press is accomplished.
  ///
  /// Bad: 'Double tap and hold to show tooltip'.
  /// Good: 'show tooltip'.
734
  final String? onLongPressHint;
735 736 737 738 739

  /// Whether there are any non-null hint values.
  bool get isNotEmpty => onTapHint != null || onLongPressHint != null;

  @override
740
  int get hashCode => Object.hash(onTapHint, onLongPressHint);
741 742

  @override
743
  bool operator ==(Object other) {
744 745
    if (other.runtimeType != runtimeType)
      return false;
746 747 748
    return other is SemanticsHintOverrides
        && other.onTapHint == onTapHint
        && other.onLongPressHint == onLongPressHint;
749 750 751 752 753
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
754 755
    properties.add(StringProperty('onTapHint', onTapHint, defaultValue: null));
    properties.add(StringProperty('onLongPressHint', onLongPressHint, defaultValue: null));
756 757 758
  }
}

759 760 761 762 763 764 765 766 767
/// Contains properties used by assistive technologies to make the application
/// more accessible.
///
/// The properties of this class are used to generate a [SemanticsNode]s in the
/// semantics tree.
@immutable
class SemanticsProperties extends DiagnosticableTree {
  /// Creates a semantic annotation.
  const SemanticsProperties({
768
    this.enabled,
769 770
    this.checked,
    this.selected,
771
    this.toggled,
772
    this.button,
773
    this.link,
774 775
    this.header,
    this.textField,
776
    this.slider,
777
    this.keyboardKey,
778
    this.readOnly,
779
    this.focusable,
780 781
    this.focused,
    this.inMutuallyExclusiveGroup,
782
    this.hidden,
783
    this.obscured,
784
    this.multiline,
785 786
    this.scopesRoute,
    this.namesRoute,
787 788
    this.image,
    this.liveRegion,
789 790
    this.maxValueLength,
    this.currentValueLength,
791
    this.label,
792
    this.attributedLabel,
793
    this.value,
794
    this.attributedValue,
795
    this.increasedValue,
796
    this.attributedIncreasedValue,
797
    this.decreasedValue,
798
    this.attributedDecreasedValue,
799
    this.hint,
800
    this.attributedHint,
801
    this.hintOverrides,
802
    this.textDirection,
803
    this.sortKey,
804
    this.tagForChildren,
805 806 807 808 809 810 811 812
    this.onTap,
    this.onLongPress,
    this.onScrollLeft,
    this.onScrollRight,
    this.onScrollUp,
    this.onScrollDown,
    this.onIncrease,
    this.onDecrease,
813 814 815
    this.onCopy,
    this.onCut,
    this.onPaste,
816 817
    this.onMoveCursorForwardByCharacter,
    this.onMoveCursorBackwardByCharacter,
818 819
    this.onMoveCursorForwardByWord,
    this.onMoveCursorBackwardByWord,
820
    this.onSetSelection,
821
    this.onSetText,
822 823
    this.onDidGainAccessibilityFocus,
    this.onDidLoseAccessibilityFocus,
824
    this.onDismiss,
825
    this.customSemanticsActions,
826 827 828 829 830
  }) : assert(label == null || attributedLabel == null, 'Only one of label or attributedLabel should be provided'),
       assert(value == null || attributedValue == null, 'Only one of value or attributedValue should be provided'),
       assert(increasedValue == null || attributedIncreasedValue == null, 'Only one of increasedValue or attributedIncreasedValue should be provided'),
       assert(decreasedValue == null || attributedDecreasedValue == null, 'Only one of decreasedValue or attributedDecreasedValue should be provided'),
       assert(hint == null || attributedHint == null, 'Only one of hint or attributedHint should be provided');
831

832 833 834 835 836 837
  /// If non-null, indicates that this subtree represents something that can be
  /// in an enabled or disabled state.
  ///
  /// For example, a button that a user can currently interact with would set
  /// this field to true. A button that currently does not respond to user
  /// interactions would set this field to false.
838
  final bool? enabled;
839

840 841 842
  /// If non-null, indicates that this subtree represents a checkbox
  /// or similar widget with a "checked" state, and what its current
  /// state is.
843 844
  ///
  /// This is mutually exclusive with [toggled].
845
  final bool? checked;
846

847 848 849 850 851
  /// If non-null, indicates that this subtree represents a toggle switch
  /// or similar widget with an "on" state, and what its current
  /// state is.
  ///
  /// This is mutually exclusive with [checked].
852
  final bool? toggled;
853

854 855 856 857 858
  /// If non-null indicates that this subtree represents something that can be
  /// in a selected or unselected state, and what its current state is.
  ///
  /// The active tab in a tab bar for example is considered "selected", whereas
  /// all other tabs are unselected.
859
  final bool? selected;
860 861 862 863 864

  /// If non-null, indicates that this subtree represents a button.
  ///
  /// TalkBack/VoiceOver provides users with the hint "button" when a button
  /// is focused.
865
  final bool? button;
866

867 868 869 870 871
  /// If non-null, indicates that this subtree represents a link.
  ///
  /// iOS's VoiceOver provides users with a unique hint when a link is focused.
  /// Android's Talkback will announce a link hint the same way it does a
  /// button.
872
  final bool? link;
873

874 875 876 877 878
  /// If non-null, indicates that this subtree represents a header.
  ///
  /// A header divides into sections. For example, an address book application
  /// might define headers A, B, C, etc. to divide the list of alphabetically
  /// sorted contacts into sections.
879
  final bool? header;
880 881 882 883 884

  /// If non-null, indicates that this subtree represents a text field.
  ///
  /// TalkBack/VoiceOver provide special affordances to enter text into a
  /// text field.
885
  final bool? textField;
886

887 888 889 890 891 892
  /// If non-null, indicates that this subtree represents a slider.
  ///
  /// Talkback/\VoiceOver provides users with the hint "slider" when a
  /// slider is focused.
  final bool? slider;

893 894 895
  /// If non-null, indicates that this subtree represents a keyboard key.
  final bool? keyboardKey;

896 897
  /// If non-null, indicates that this subtree is read only.
  ///
898
  /// Only applicable when [textField] is true.
899 900
  ///
  /// TalkBack/VoiceOver will treat it as non-editable text field.
901
  final bool? readOnly;
902

903 904 905 906 907 908 909 910
  /// If non-null, whether the node is able to hold input focus.
  ///
  /// If [focusable] is set to false, then [focused] must not be true.
  ///
  /// Input focus indicates that the node will receive keyboard events. It is not
  /// to be confused with accessibility focus. Accessibility focus is the
  /// green/black rectangular highlight that TalkBack/VoiceOver draws around the
  /// element it is reading, and is separate from input focus.
911
  final bool? focusable;
912

913 914
  /// If non-null, whether the node currently holds input focus.
  ///
915 916
  /// At most one node in the tree should hold input focus at any point in time,
  /// and it should not be set to true if [focusable] is false.
917
  ///
918
  /// Input focus indicates that the node will receive keyboard events. It is not
919
  /// to be confused with accessibility focus. Accessibility focus is the
920 921
  /// green/black rectangular highlight that TalkBack/VoiceOver draws around the
  /// element it is reading, and is separate from input focus.
922
  final bool? focused;
923 924 925 926 927

  /// If non-null, whether a semantic node is in a mutually exclusive group.
  ///
  /// For example, a radio button is in a mutually exclusive group because only
  /// one radio button in that group can be marked as [checked].
928
  final bool? inMutuallyExclusiveGroup;
929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945

  /// If non-null, whether the node is considered hidden.
  ///
  /// Hidden elements are currently not visible on screen. They may be covered
  /// by other elements or positioned outside of the visible area of a viewport.
  ///
  /// Hidden elements cannot gain accessibility focus though regular touch. The
  /// only way they can be focused is by moving the focus to them via linear
  /// navigation.
  ///
  /// Platforms are free to completely ignore hidden elements and new platforms
  /// are encouraged to do so.
  ///
  /// Instead of marking an element as hidden it should usually be excluded from
  /// the semantics tree altogether. Hidden elements are only included in the
  /// semantics tree to work around platform limitations and they are mainly
  /// used to implement accessibility scrolling on iOS.
946
  final bool? hidden;
947

948 949 950 951 952
  /// If non-null, whether [value] should be obscured.
  ///
  /// This option is usually set in combination with [textField] to indicate
  /// that the text field contains a password (or other sensitive information).
  /// Doing so instructs screen readers to not read out the [value].
953
  final bool? obscured;
954

955
  /// Whether the [value] is coming from a field that supports multiline text
956 957 958
  /// editing.
  ///
  /// This option is only meaningful when [textField] is true to indicate
959
  /// whether it's a single-line or multiline text field.
960 961
  ///
  /// This option is null when [textField] is false.
962
  final bool? multiline;
963

964 965
  /// If non-null, whether the node corresponds to the root of a subtree for
  /// which a route name should be announced.
966
  ///
967 968 969
  /// Generally, this is set in combination with
  /// [SemanticsConfiguration.explicitChildNodes], since nodes with this flag
  /// are not considered focusable by Android or iOS.
970 971 972 973 974
  ///
  /// See also:
  ///
  ///  * [SemanticsFlag.scopesRoute] for a description of how the announced
  ///    value is selected.
975
  final bool? scopesRoute;
976 977 978 979

  /// If non-null, whether the node contains the semantic label for a route.
  ///
  /// See also:
980
  ///
981
  ///  * [SemanticsFlag.namesRoute] for a description of how the name is used.
982
  final bool? namesRoute;
983

984 985 986 987
  /// If non-null, whether the node represents an image.
  ///
  /// See also:
  ///
988
  ///  * [SemanticsFlag.isImage], for the flag this setting controls.
989
  final bool? image;
990 991 992

  /// If non-null, whether the node should be considered a live region.
  ///
993 994 995
  /// A live region indicates that updates to semantics node are important.
  /// Platforms may use this information to make polite announcements to the
  /// user to inform them of updates to this node.
996
  ///
997 998 999 1000 1001 1002
  /// An example of a live region is a [SnackBar] widget. On Android and iOS,
  /// live region causes a polite announcement to be generated automatically,
  /// even if the widget does not have accessibility focus. This announcement
  /// may not be spoken if the OS accessibility services are already
  /// announcing something else, such as reading the label of a focused widget
  /// or providing a system announcement.
1003 1004
  ///
  /// See also:
1005
  ///
1006
  ///  * [SemanticsFlag.isLiveRegion], the semantics flag this setting controls.
1007
  ///  * [SemanticsConfiguration.liveRegion], for a full description of a live region.
1008
  final bool? liveRegion;
1009

1010 1011 1012 1013 1014 1015 1016 1017
  /// The maximum number of characters that can be entered into an editable
  /// text field.
  ///
  /// For the purpose of this function a character is defined as one Unicode
  /// scalar value.
  ///
  /// This should only be set when [textField] is true. Defaults to null,
  /// which means no limit is imposed on the text field.
1018
  final int? maxValueLength;
1019 1020 1021 1022 1023 1024 1025 1026 1027

  /// The current number of characters that have been entered into an editable
  /// text field.
  ///
  /// For the purpose of this function a character is defined as one Unicode
  /// scalar value.
  ///
  /// This should only be set when [textField] is true. Must be set when
  /// [maxValueLength] is set.
1028
  final int? currentValueLength;
1029

1030 1031 1032 1033 1034
  /// Provides a textual description of the widget.
  ///
  /// If a label is provided, there must either by an ambient [Directionality]
  /// or an explicit [textDirection] should be provided.
  ///
1035 1036 1037
  /// Callers must not provide both [label] and [attributedLabel]. One or both
  /// must be null.
  ///
1038 1039 1040 1041
  /// See also:
  ///
  ///  * [SemanticsConfiguration.label] for a description of how this is exposed
  ///    in TalkBack and VoiceOver.
1042
  ///  * [attributedLabel] for an [AttributedString] version of this property.
1043
  final String? label;
1044

1045
  /// Provides an [AttributedString] version of textual description of the widget.
1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059
  ///
  /// If a [attributedLabel] is provided, there must either by an ambient
  /// [Directionality] or an explicit [textDirection] should be provided.
  ///
  /// Callers must not provide both [label] and [attributedLabel]. One or both
  /// must be null.
  ///
  /// See also:
  ///
  ///  * [SemanticsConfiguration.attributedLabel] for a description of how this
  ///    is exposed in TalkBack and VoiceOver.
  ///  * [label] for a plain string version of this property.
  final AttributedString? attributedLabel;

1060 1061 1062 1063 1064
  /// Provides a textual description of the value of the widget.
  ///
  /// If a value is provided, there must either by an ambient [Directionality]
  /// or an explicit [textDirection] should be provided.
  ///
1065 1066 1067
  /// Callers must not provide both [value] and [attributedValue], One or both
  /// must be null.
  ///
1068 1069 1070 1071
  /// See also:
  ///
  ///  * [SemanticsConfiguration.value] for a description of how this is exposed
  ///    in TalkBack and VoiceOver.
1072
  ///  * [attributedLabel] for an [AttributedString] version of this property.
1073
  final String? value;
1074

1075
  /// Provides an [AttributedString] version of textual description of the value
1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092
  /// of the widget.
  ///
  /// If a [attributedValue] is provided, there must either by an ambient
  /// [Directionality] or an explicit [textDirection] should be provided.
  ///
  /// Callers must not provide both [value] and [attributedValue], One or both
  /// must be null.
  ///
  /// See also:
  ///
  ///  * [SemanticsConfiguration.attributedValue] for a description of how this
  ///    is exposed in TalkBack and VoiceOver.
  ///  * [value] for a plain string version of this property.
  final AttributedString? attributedValue;

  /// The value that [value] or [attributedValue] will become after a
  /// [SemanticsAction.increase] action has been performed on this widget.
1093 1094 1095 1096 1097
  ///
  /// If a value is provided, [onIncrease] must also be set and there must
  /// either be an ambient [Directionality] or an explicit [textDirection]
  /// must be provided.
  ///
1098 1099 1100
  /// Callers must not provide both [increasedValue] and
  /// [attributedIncreasedValue], One or both must be null.
  ///
1101 1102 1103 1104
  /// See also:
  ///
  ///  * [SemanticsConfiguration.increasedValue] for a description of how this
  ///    is exposed in TalkBack and VoiceOver.
1105
  ///  * [attributedIncreasedValue] for an [AttributedString] version of this
1106
  ///    property.
1107
  final String? increasedValue;
1108

1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127
  /// The [AttributedString] that [value] or [attributedValue] will become after
  /// a [SemanticsAction.increase] action has been performed on this widget.
  ///
  /// If a [attributedIncreasedValue] is provided, [onIncrease] must also be set
  /// and there must either be an ambient [Directionality] or an explicit
  /// [textDirection] must be provided.
  ///
  /// Callers must not provide both [increasedValue] and
  /// [attributedIncreasedValue], One or both must be null.
  ///
  /// See also:
  ///
  ///  * [SemanticsConfiguration.attributedIncreasedValue] for a description of
  ///    how this is exposed in TalkBack and VoiceOver.
  ///  * [increasedValue] for a plain string version of this property.
  final AttributedString? attributedIncreasedValue;

  /// The value that [value] or [attributedValue] will become after a
  /// [SemanticsAction.decrease] action has been performed on this widget.
1128 1129 1130 1131 1132
  ///
  /// If a value is provided, [onDecrease] must also be set and there must
  /// either be an ambient [Directionality] or an explicit [textDirection]
  /// must be provided.
  ///
1133 1134 1135
  /// Callers must not provide both [decreasedValue] and
  /// [attributedDecreasedValue], One or both must be null.
  ///
1136 1137 1138 1139
  /// See also:
  ///
  ///  * [SemanticsConfiguration.decreasedValue] for a description of how this
  ///    is exposed in TalkBack and VoiceOver.
1140
  ///  * [attributedDecreasedValue] for an [AttributedString] version of this
1141
  ///    property.
1142
  final String? decreasedValue;
1143

1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160
  /// The [AttributedString] that [value] or [attributedValue] will become after
  /// a [SemanticsAction.decrease] action has been performed on this widget.
  ///
  /// If a [attributedDecreasedValue] is provided, [onDecrease] must also be set
  /// and there must either be an ambient [Directionality] or an explicit
  /// [textDirection] must be provided.
  ///
  /// Callers must not provide both [decreasedValue] and
  /// [attributedDecreasedValue], One or both must be null/// provided.
  ///
  /// See also:
  ///
  ///  * [SemanticsConfiguration.attributedDecreasedValue] for a description of
  ///    how this is exposed in TalkBack and VoiceOver.
  ///  * [decreasedValue] for a plain string version of this property.
  final AttributedString? attributedDecreasedValue;

1161 1162 1163
  /// Provides a brief textual description of the result of an action performed
  /// on the widget.
  ///
1164
  /// If a hint is provided, there must either be an ambient [Directionality]
1165 1166
  /// or an explicit [textDirection] should be provided.
  ///
1167 1168 1169
  /// Callers must not provide both [hint] and [attributedHint], One or both
  /// must be null.
  ///
1170 1171 1172 1173
  /// See also:
  ///
  ///  * [SemanticsConfiguration.hint] for a description of how this is exposed
  ///    in TalkBack and VoiceOver.
1174
  ///  * [attributedHint] for an [AttributedString] version of this property.
1175
  final String? hint;
1176

1177
  /// Provides an [AttributedString] version of brief textual description of the
1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192
  /// result of an action performed on the widget.
  ///
  /// If a [attributedHint] is provided, there must either by an ambient
  /// [Directionality] or an explicit [textDirection] should be provided.
  ///
  /// Callers must not provide both [hint] and [attributedHint], One or both
  /// must be null.
  ///
  /// See also:
  ///
  ///  * [SemanticsConfiguration.attributedHint] for a description of how this
  ///    is exposed in TalkBack and VoiceOver.
  ///  * [hint] for a plain string version of this property.
  final AttributedString? attributedHint;

1193 1194 1195 1196 1197 1198 1199 1200
  /// Provides hint values which override the default hints on supported
  /// platforms.
  ///
  /// On Android, If no hint overrides are used then default [hint] will be
  /// combined with the [label]. Otherwise, the [hint] will be ignored as long
  /// as there as at least one non-null hint override.
  ///
  /// On iOS, these are always ignored and the default [hint] is used instead.
1201
  final SemanticsHintOverrides? hintOverrides;
1202

1203 1204
  /// The reading direction of the [label], [value], [increasedValue],
  /// [decreasedValue], and [hint].
1205 1206
  ///
  /// Defaults to the ambient [Directionality].
1207
  final TextDirection? textDirection;
1208

1209 1210
  /// Determines the position of this node among its siblings in the traversal
  /// sort order.
1211 1212 1213 1214
  ///
  /// This is used to describe the order in which the semantic node should be
  /// traversed by the accessibility services on the platform (e.g. VoiceOver
  /// on iOS and TalkBack on Android).
1215
  final SemanticsSortKey? sortKey;
1216

1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232
  /// A tag to be applied to the child [SemanticsNode]s of this widget.
  ///
  /// The tag is added to all child [SemanticsNode]s that pass through the
  /// [RenderObject] corresponding to this widget while looking to be attached
  /// to a parent SemanticsNode.
  ///
  /// Tags are used to communicate to a parent SemanticsNode that a child
  /// SemanticsNode was passed through a particular RenderObject. The parent can
  /// use this information to determine the shape of the semantics tree.
  ///
  /// See also:
  ///
  ///  * [SemanticsConfiguration.addTagForChildren], to which the tags provided
  ///    here will be passed.
  final SemanticsTag? tagForChildren;

1233 1234 1235 1236 1237 1238 1239 1240
  /// The handler for [SemanticsAction.tap].
  ///
  /// This is the semantic equivalent of a user briefly tapping the screen with
  /// the finger without moving it. For example, a button should implement this
  /// action.
  ///
  /// VoiceOver users on iOS and TalkBack users on Android can trigger this
  /// action by double-tapping the screen while an element is focused.
1241
  final VoidCallback? onTap;
1242 1243 1244 1245 1246 1247 1248 1249 1250

  /// The handler for [SemanticsAction.longPress].
  ///
  /// This is the semantic equivalent of a user pressing and holding the screen
  /// with the finger for a few seconds without moving it.
  ///
  /// VoiceOver users on iOS and TalkBack users on Android can trigger this
  /// action by double-tapping the screen without lifting the finger after the
  /// second tap.
1251
  final VoidCallback? onLongPress;
1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263

  /// The handler for [SemanticsAction.scrollLeft].
  ///
  /// This is the semantic equivalent of a user moving their finger across the
  /// screen from right to left. It should be recognized by controls that are
  /// horizontally scrollable.
  ///
  /// VoiceOver users on iOS can trigger this action by swiping left with three
  /// fingers. TalkBack users on Android can trigger this action by swiping
  /// right and then left in one motion path. On Android, [onScrollUp] and
  /// [onScrollLeft] share the same gesture. Therefore, only on of them should
  /// be provided.
1264
  final VoidCallback? onScrollLeft;
1265 1266 1267 1268 1269 1270 1271 1272 1273

  /// The handler for [SemanticsAction.scrollRight].
  ///
  /// This is the semantic equivalent of a user moving their finger across the
  /// screen from left to right. It should be recognized by controls that are
  /// horizontally scrollable.
  ///
  /// VoiceOver users on iOS can trigger this action by swiping right with three
  /// fingers. TalkBack users on Android can trigger this action by swiping
1274
  /// left and then right in one motion path. On Android, [onScrollDown] and
1275 1276
  /// [onScrollRight] share the same gesture. Therefore, only on of them should
  /// be provided.
1277
  final VoidCallback? onScrollRight;
1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289

  /// The handler for [SemanticsAction.scrollUp].
  ///
  /// This is the semantic equivalent of a user moving their finger across the
  /// screen from bottom to top. It should be recognized by controls that are
  /// vertically scrollable.
  ///
  /// VoiceOver users on iOS can trigger this action by swiping up with three
  /// fingers. TalkBack users on Android can trigger this action by swiping
  /// right and then left in one motion path. On Android, [onScrollUp] and
  /// [onScrollLeft] share the same gesture. Therefore, only on of them should
  /// be provided.
1290
  final VoidCallback? onScrollUp;
1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302

  /// The handler for [SemanticsAction.scrollDown].
  ///
  /// This is the semantic equivalent of a user moving their finger across the
  /// screen from top to bottom. It should be recognized by controls that are
  /// vertically scrollable.
  ///
  /// VoiceOver users on iOS can trigger this action by swiping down with three
  /// fingers. TalkBack users on Android can trigger this action by swiping
  /// left and then right in one motion path. On Android, [onScrollDown] and
  /// [onScrollRight] share the same gesture. Therefore, only on of them should
  /// be provided.
1303
  final VoidCallback? onScrollDown;
1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315

  /// The handler for [SemanticsAction.increase].
  ///
  /// This is a request to increase the value represented by the widget. For
  /// example, this action might be recognized by a slider control.
  ///
  /// If a [value] is set, [increasedValue] must also be provided and
  /// [onIncrease] must ensure that [value] will be set to [increasedValue].
  ///
  /// VoiceOver users on iOS can trigger this action by swiping up with one
  /// finger. TalkBack users on Android can trigger this action by pressing the
  /// volume up button.
1316
  final VoidCallback? onIncrease;
1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328

  /// The handler for [SemanticsAction.decrease].
  ///
  /// This is a request to decrease the value represented by the widget. For
  /// example, this action might be recognized by a slider control.
  ///
  /// If a [value] is set, [decreasedValue] must also be provided and
  /// [onDecrease] must ensure that [value] will be set to [decreasedValue].
  ///
  /// VoiceOver users on iOS can trigger this action by swiping down with one
  /// finger. TalkBack users on Android can trigger this action by pressing the
  /// volume down button.
1329
  final VoidCallback? onDecrease;
1330

1331 1332 1333 1334 1335 1336
  /// The handler for [SemanticsAction.copy].
  ///
  /// This is a request to copy the current selection to the clipboard.
  ///
  /// TalkBack users on Android can trigger this action from the local context
  /// menu of a text field, for example.
1337
  final VoidCallback? onCopy;
1338 1339 1340 1341 1342 1343 1344 1345

  /// The handler for [SemanticsAction.cut].
  ///
  /// This is a request to cut the current selection and place it in the
  /// clipboard.
  ///
  /// TalkBack users on Android can trigger this action from the local context
  /// menu of a text field, for example.
1346
  final VoidCallback? onCut;
1347 1348 1349 1350 1351 1352 1353

  /// The handler for [SemanticsAction.paste].
  ///
  /// This is a request to paste the current content of the clipboard.
  ///
  /// TalkBack users on Android can trigger this action from the local context
  /// menu of a text field, for example.
1354
  final VoidCallback? onPaste;
1355

1356
  /// The handler for [SemanticsAction.moveCursorForwardByCharacter].
1357 1358 1359 1360 1361 1362
  ///
  /// This handler is invoked when the user wants to move the cursor in a
  /// text field forward by one character.
  ///
  /// TalkBack users can trigger this by pressing the volume up key while the
  /// input focus is in a text field.
1363
  final MoveCursorHandler? onMoveCursorForwardByCharacter;
1364

1365
  /// The handler for [SemanticsAction.moveCursorBackwardByCharacter].
1366 1367 1368 1369 1370 1371
  ///
  /// This handler is invoked when the user wants to move the cursor in a
  /// text field backward by one character.
  ///
  /// TalkBack users can trigger this by pressing the volume down key while the
  /// input focus is in a text field.
1372
  final MoveCursorHandler? onMoveCursorBackwardByCharacter;
1373

1374
  /// The handler for [SemanticsAction.moveCursorForwardByWord].
1375 1376 1377 1378 1379 1380
  ///
  /// This handler is invoked when the user wants to move the cursor in a
  /// text field backward by one word.
  ///
  /// TalkBack users can trigger this by pressing the volume down key while the
  /// input focus is in a text field.
1381
  final MoveCursorHandler? onMoveCursorForwardByWord;
1382

1383
  /// The handler for [SemanticsAction.moveCursorBackwardByWord].
1384 1385 1386 1387 1388 1389
  ///
  /// This handler is invoked when the user wants to move the cursor in a
  /// text field backward by one word.
  ///
  /// TalkBack users can trigger this by pressing the volume down key while the
  /// input focus is in a text field.
1390
  final MoveCursorHandler? onMoveCursorBackwardByWord;
1391

1392 1393 1394 1395 1396 1397 1398
  /// The handler for [SemanticsAction.setSelection].
  ///
  /// This handler is invoked when the user either wants to change the currently
  /// selected text in a text field or change the position of the cursor.
  ///
  /// TalkBack users can trigger this handler by selecting "Move cursor to
  /// beginning/end" or "Select all" from the local context menu.
1399
  final SetSelectionHandler? onSetSelection;
1400

1401 1402 1403 1404 1405 1406 1407 1408 1409
  /// The handler for [SemanticsAction.setText].
  ///
  /// This handler is invoked when the user wants to replace the current text in
  /// the text field with a new text.
  ///
  /// Voice access users can trigger this handler by speaking "type <text>" to
  /// their Android devices.
  final SetTextHandler? onSetText;

1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424
  /// The handler for [SemanticsAction.didGainAccessibilityFocus].
  ///
  /// This handler is invoked when the node annotated with this handler gains
  /// the accessibility focus. The accessibility focus is the
  /// green (on Android with TalkBack) or black (on iOS with VoiceOver)
  /// rectangle shown on screen to indicate what element an accessibility
  /// user is currently interacting with.
  ///
  /// The accessibility focus is different from the input focus. The input focus
  /// is usually held by the element that currently responds to keyboard inputs.
  /// Accessibility focus and input focus can be held by two different nodes!
  ///
  /// See also:
  ///
  ///  * [onDidLoseAccessibilityFocus], which is invoked when the accessibility
1425 1426
  ///    focus is removed from the node.
  ///  * [FocusNode], [FocusScope], [FocusManager], which manage the input focus.
1427
  final VoidCallback? onDidGainAccessibilityFocus;
1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443

  /// The handler for [SemanticsAction.didLoseAccessibilityFocus].
  ///
  /// This handler is invoked when the node annotated with this handler
  /// loses the accessibility focus. The accessibility focus is
  /// the green (on Android with TalkBack) or black (on iOS with VoiceOver)
  /// rectangle shown on screen to indicate what element an accessibility
  /// user is currently interacting with.
  ///
  /// The accessibility focus is different from the input focus. The input focus
  /// is usually held by the element that currently responds to keyboard inputs.
  /// Accessibility focus and input focus can be held by two different nodes!
  ///
  /// See also:
  ///
  ///  * [onDidGainAccessibilityFocus], which is invoked when the node gains
1444 1445
  ///    accessibility focus.
  ///  * [FocusNode], [FocusScope], [FocusManager], which manage the input focus.
1446
  final VoidCallback? onDidLoseAccessibilityFocus;
1447

1448 1449 1450 1451 1452 1453 1454
  /// The handler for [SemanticsAction.dismiss].
  ///
  /// This is a request to dismiss the currently focused node.
  ///
  /// TalkBack users on Android can trigger this action in the local context
  /// menu, and VoiceOver users on iOS can trigger this action with a standard
  /// gesture or menu option.
1455
  final VoidCallback? onDismiss;
1456

1457
  /// A map from each supported [CustomSemanticsAction] to a provided handler.
1458
  ///
1459
  /// The handler associated with each custom action is called whenever a
1460
  /// semantics action of type [SemanticsAction.customAction] is received. The
1461 1462
  /// provided argument will be an identifier used to retrieve an instance of
  /// a custom action which can then retrieve the correct handler from this map.
1463
  ///
1464
  /// See also:
1465
  ///
1466
  ///  * [CustomSemanticsAction], for an explanation of custom actions.
1467
  final Map<CustomSemanticsAction, VoidCallback>? customSemanticsActions;
1468

1469
  @override
1470 1471
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
1472 1473
    properties.add(DiagnosticsProperty<bool>('checked', checked, defaultValue: null));
    properties.add(DiagnosticsProperty<bool>('selected', selected, defaultValue: null));
1474 1475 1476 1477 1478 1479 1480 1481 1482 1483
    properties.add(StringProperty('label', label, defaultValue: null));
    properties.add(AttributedStringProperty('attributedLabel', attributedLabel, defaultValue: null));
    properties.add(StringProperty('value', value, defaultValue: null));
    properties.add(AttributedStringProperty('attributedValue', attributedValue, defaultValue: null));
    properties.add(StringProperty('increasedValue', value, defaultValue: null));
    properties.add(AttributedStringProperty('attributedIncreasedValue', attributedIncreasedValue, defaultValue: null));
    properties.add(StringProperty('decreasedValue', value, defaultValue: null));
    properties.add(AttributedStringProperty('attributedDecreasedValue', attributedDecreasedValue, defaultValue: null));
    properties.add(StringProperty('hint', hint, defaultValue: null));
    properties.add(AttributedStringProperty('attributedHint', attributedHint, defaultValue: null));
1484 1485
    properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
    properties.add(DiagnosticsProperty<SemanticsSortKey>('sortKey', sortKey, defaultValue: null));
1486
    properties.add(DiagnosticsProperty<SemanticsHintOverrides>('hintOverrides', hintOverrides, defaultValue: null));
1487
  }
1488 1489

  @override
1490
  String toStringShort() => objectRuntimeType(this, 'SemanticsProperties'); // the hashCode isn't important since we're immutable
1491 1492 1493 1494 1495 1496 1497 1498
}

/// In tests use this function to reset the counter used to generate
/// [SemanticsNode.id].
void debugResetSemanticsIdCounter() {
  SemanticsNode._lastIdentifier = 0;
}

1499 1500 1501 1502 1503 1504
/// A node that represents some semantic data.
///
/// The semantics tree is maintained during the semantics phase of the pipeline
/// (i.e., during [PipelineOwner.flushSemantics]), which happens after
/// compositing. The semantics tree is then uploaded into the engine for use
/// by assistive technology.
1505
class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
1506 1507 1508 1509
  /// Creates a semantic node.
  ///
  /// Each semantic node has a unique identifier that is assigned when the node
  /// is created.
Hixie's avatar
Hixie committed
1510
  SemanticsNode({
1511
    this.key,
1512
    VoidCallback? showOnScreen,
1513
  }) : _id = _generateNewId(),
1514
       _showOnScreen = showOnScreen;
Hixie's avatar
Hixie committed
1515

1516 1517 1518
  /// Creates a semantic node to represent the root of the semantics tree.
  ///
  /// The root node is assigned an identifier of zero.
Hixie's avatar
Hixie committed
1519
  SemanticsNode.root({
1520
    this.key,
1521 1522
    VoidCallback? showOnScreen,
    required SemanticsOwner owner,
1523
  }) : _id = 0,
1524
       _showOnScreen = showOnScreen {
1525
    attach(owner);
Hixie's avatar
Hixie committed
1526 1527
  }

1528 1529 1530 1531 1532 1533 1534 1535

  // The maximal semantic node identifier generated by the framework.
  //
  // The identifier range for semantic node IDs is split into 2, the least significant 16 bits are
  // reserved for framework generated IDs(generated with _generateNewId), and most significant 32
  // bits are reserved for engine generated IDs.
  static const int _maxFrameworkAccessibilityIdentifier = (1<<16) - 1;

Hixie's avatar
Hixie committed
1536 1537
  static int _lastIdentifier = 0;
  static int _generateNewId() {
1538
    _lastIdentifier = (_lastIdentifier + 1) % _maxFrameworkAccessibilityIdentifier;
Hixie's avatar
Hixie committed
1539 1540 1541
    return _lastIdentifier;
  }

1542 1543 1544 1545
  /// Uniquely identifies this node in the list of sibling nodes.
  ///
  /// Keys are used during the construction of the semantics tree. They are not
  /// transferred to the engine.
1546
  final Key? key;
1547

1548 1549
  /// The unique identifier for this node.
  ///
1550 1551 1552 1553 1554 1555 1556 1557 1558
  /// The root node has an id of zero. Other nodes are given a unique id
  /// when they are attached to a [SemanticsOwner]. If they are detached, their
  /// ids are invalid and should not be used.
  ///
  /// In rare circumstances, id may change if this node is detached and
  /// re-attached to the [SemanticsOwner]. This should only happen when the
  /// application has generated too many semantics nodes.
  int get id => _id;
  int _id;
Hixie's avatar
Hixie committed
1559

1560
  final VoidCallback? _showOnScreen;
Hixie's avatar
Hixie committed
1561 1562 1563

  // GEOMETRY

1564 1565 1566
  /// The transform from this node's coordinate system to its parent's coordinate system.
  ///
  /// By default, the transform is null, which represents the identity
1567
  /// transformation (i.e., that this node has the same coordinate system as its
1568
  /// parent).
1569 1570 1571
  Matrix4? get transform => _transform;
  Matrix4? _transform;
  set transform(Matrix4? value) {
Hixie's avatar
Hixie committed
1572
    if (!MatrixUtils.matrixEquals(_transform, value)) {
1573
      _transform = value == null || MatrixUtils.isIdentity(value) ? null : value;
Hixie's avatar
Hixie committed
1574 1575 1576 1577
      _markDirty();
    }
  }

1578
  /// The bounding box for this node in its coordinate system.
Hixie's avatar
Hixie committed
1579 1580
  Rect get rect => _rect;
  Rect _rect = Rect.zero;
1581
  set rect(Rect value) {
Hixie's avatar
Hixie committed
1582
    assert(value != null);
1583
    assert(value.isFinite, '$this (with $owner) tried to set a non-finite rect.');
Hixie's avatar
Hixie committed
1584 1585 1586 1587 1588 1589
    if (_rect != value) {
      _rect = value;
      _markDirty();
    }
  }

1590
  /// The semantic clip from an ancestor that was applied to this node.
1591
  ///
1592 1593
  /// Expressed in the coordinate system of the node. May be null if no clip has
  /// been applied.
1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606
  ///
  /// Descendant [SemanticsNode]s that are positioned outside of this rect will
  /// be excluded from the semantics tree. Descendant [SemanticsNode]s that are
  /// overlapping with this rect, but are outside of [parentPaintClipRect] will
  /// be included in the tree, but they will be marked as hidden because they
  /// are assumed to be not visible on screen.
  ///
  /// If this rect is null, all descendant [SemanticsNode]s outside of
  /// [parentPaintClipRect] will be excluded from the tree.
  ///
  /// If this rect is non-null it has to completely enclose
  /// [parentPaintClipRect]. If [parentPaintClipRect] is null this property is
  /// also null.
1607
  Rect? parentSemanticsClipRect;
1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621

  /// The paint clip from an ancestor that was applied to this node.
  ///
  /// Expressed in the coordinate system of the node. May be null if no clip has
  /// been applied.
  ///
  /// Descendant [SemanticsNode]s that are positioned outside of this rect will
  /// either be excluded from the semantics tree (if they have no overlap with
  /// [parentSemanticsClipRect]) or they will be included and marked as hidden
  /// (if they are overlapping with [parentSemanticsClipRect]).
  ///
  /// This rect is completely enclosed by [parentSemanticsClipRect].
  ///
  /// If this rect is null [parentSemanticsClipRect] also has to be null.
1622
  Rect? parentPaintClipRect;
Hixie's avatar
Hixie committed
1623

1624 1625 1626 1627
  /// The elevation adjustment that the parent imposes on this node.
  ///
  /// The [elevation] property is relative to the elevation of the parent
  /// [SemanticsNode]. However, as [SemanticsConfiguration]s from various
1628
  /// ascending [RenderObject]s are merged into each other to form that
1629 1630 1631 1632 1633 1634 1635 1636 1637 1638
  /// [SemanticsNode] the parent’s elevation may change. This requires an
  /// adjustment of the child’s relative elevation which is represented by this
  /// value.
  ///
  /// The value is rarely accessed directly. Instead, for most use cases the
  /// [elevation] value should be used, which includes this adjustment.
  ///
  /// See also:
  ///
  ///  * [elevation], the actual elevation of this [SemanticsNode].
1639
  double? elevationAdjustment;
1640

1641 1642 1643 1644 1645 1646
  /// The index of this node within the parent's list of semantic children.
  ///
  /// This includes all semantic nodes, not just those currently in the
  /// child list. For example, if a scrollable has five children but the first
  /// two are not visible (and thus not included in the list of children), then
  /// the index of the last node will still be 4.
1647
  int? indexInParent;
1648

1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659
  /// Whether the node is invisible.
  ///
  /// A node whose [rect] is outside of the bounds of the screen and hence not
  /// reachable for users is considered invisible if its semantic information
  /// is not merged into a (partially) visible parent as indicated by
  /// [isMergedIntoParent].
  ///
  /// An invisible node can be safely dropped from the semantic tree without
  /// loosing semantic information that is relevant for describing the content
  /// currently shown on screen.
  bool get isInvisible => !isMergedIntoParent && rect.isEmpty;
Hixie's avatar
Hixie committed
1660

1661
  // MERGING
Hixie's avatar
Hixie committed
1662

1663 1664 1665 1666
  /// Whether this node merges its semantic information into an ancestor node.
  bool get isMergedIntoParent => _isMergedIntoParent;
  bool _isMergedIntoParent = false;
  set isMergedIntoParent(bool value) {
1667
    assert(value != null);
1668
    if (_isMergedIntoParent == value)
1669
      return;
1670
    _isMergedIntoParent = value;
1671 1672
    _markDirty();
  }
1673

1674 1675 1676 1677 1678 1679
  /// Whether this node is taking part in a merge of semantic information.
  ///
  /// This returns true if the node is either merged into an ancestor node or if
  /// decedent nodes are merged into this node.
  ///
  /// See also:
1680
  ///
1681 1682 1683
  ///  * [isMergedIntoParent]
  ///  * [mergeAllDescendantsIntoThisNode]
  bool get isPartOfNodeMerging => mergeAllDescendantsIntoThisNode || isMergedIntoParent;
1684

1685 1686 1687
  /// Whether this node and all of its descendants should be treated as one logical entity.
  bool get mergeAllDescendantsIntoThisNode => _mergeAllDescendantsIntoThisNode;
  bool _mergeAllDescendantsIntoThisNode = _kEmptyConfig.isMergingSemanticsOfDescendants;
1688 1689


1690
  // CHILDREN
Hixie's avatar
Hixie committed
1691

1692
  /// Contains the children in inverse hit test order (i.e. paint order).
1693
  List<SemanticsNode>? _children;
1694

1695 1696 1697
  /// A snapshot of `newChildren` passed to [_replaceChildren] that we keep in
  /// debug mode. It supports the assertion that user does not mutate the list
  /// of children.
1698
  late List<SemanticsNode> _debugPreviousSnapshot;
1699

1700 1701
  void _replaceChildren(List<SemanticsNode> newChildren) {
    assert(!newChildren.any((SemanticsNode child) => child == this));
Hixie's avatar
Hixie committed
1702
    assert(() {
1703
      if (identical(newChildren, _children)) {
1704
        final List<DiagnosticsNode> mutationErrors = <DiagnosticsNode>[];
1705
        if (newChildren.length != _debugPreviousSnapshot.length) {
1706
          mutationErrors.add(ErrorDescription(
1707
            "The list's length has changed from ${_debugPreviousSnapshot.length} "
1708
            'to ${newChildren.length}.',
1709
          ));
1710 1711 1712
        } else {
          for (int i = 0; i < newChildren.length; i++) {
            if (!identical(newChildren[i], _debugPreviousSnapshot[i])) {
1713 1714 1715 1716 1717 1718
              if (mutationErrors.isNotEmpty) {
                mutationErrors.add(ErrorSpacer());
              }
              mutationErrors.add(ErrorDescription('Child node at position $i was replaced:'));
              mutationErrors.add(newChildren[i].toDiagnosticsNode(name: 'Previous child', style: DiagnosticsTreeStyle.singleLine));
              mutationErrors.add(_debugPreviousSnapshot[i].toDiagnosticsNode(name: 'New child', style: DiagnosticsTreeStyle.singleLine));
1719 1720 1721 1722
            }
          }
        }
        if (mutationErrors.isNotEmpty) {
1723 1724 1725 1726
          throw FlutterError.fromParts(<DiagnosticsNode>[
            ErrorSummary('Failed to replace child semantics nodes because the list of `SemanticsNode`s was mutated.'),
            ErrorHint('Instead of mutating the existing list, create a new list containing the desired `SemanticsNode`s.'),
            ErrorDescription('Error details:'),
1727
            ...mutationErrors,
1728
          ]);
1729 1730
        }
      }
1731
      assert(!newChildren.any((SemanticsNode node) => node.isMergedIntoParent) || isPartOfNodeMerging);
1732

1733
      _debugPreviousSnapshot = List<SemanticsNode>.of(newChildren);
1734

Hixie's avatar
Hixie committed
1735 1736
      SemanticsNode ancestor = this;
      while (ancestor.parent is SemanticsNode)
1737
        ancestor = ancestor.parent!;
1738
      assert(!newChildren.any((SemanticsNode child) => child == ancestor));
Hixie's avatar
Hixie committed
1739
      return true;
1740
    }());
Hixie's avatar
Hixie committed
1741
    assert(() {
1742
      final Set<SemanticsNode> seenChildren = <SemanticsNode>{};
1743
      for (final SemanticsNode child in newChildren)
Hixie's avatar
Hixie committed
1744 1745
        assert(seenChildren.add(child)); // check for duplicate adds
      return true;
1746
    }());
Hixie's avatar
Hixie committed
1747

1748
    // The goal of this function is updating sawChange.
Hixie's avatar
Hixie committed
1749
    if (_children != null) {
1750
      for (final SemanticsNode child in _children!)
Hixie's avatar
Hixie committed
1751 1752
        child._dead = true;
    }
1753 1754 1755
    for (final SemanticsNode child in newChildren) {
      assert(!child.isInvisible, 'Child $child is invisible and should not be added as a child of $this.');
      child._dead = false;
Hixie's avatar
Hixie committed
1756 1757 1758
    }
    bool sawChange = false;
    if (_children != null) {
1759
      for (final SemanticsNode child in _children!) {
Hixie's avatar
Hixie committed
1760 1761 1762 1763 1764 1765 1766 1767 1768 1769
        if (child._dead) {
          if (child.parent == this) {
            // we might have already had our child stolen from us by
            // another node that is deeper in the tree.
            dropChild(child);
          }
          sawChange = true;
        }
      }
    }
1770 1771 1772 1773 1774 1775 1776 1777 1778
    for (final SemanticsNode child in newChildren) {
      if (child.parent != this) {
        if (child.parent != null) {
          // we're rebuilding the tree from the bottom up, so it's possible
          // that our child was, in the last pass, a child of one of our
          // ancestors. In that case, we drop the child eagerly here.
          // TODO(ianh): Find a way to assert that the same node didn't
          // actually appear in the tree in two places.
          child.parent?.dropChild(child);
Hixie's avatar
Hixie committed
1779
        }
1780 1781 1782
        assert(!child.attached);
        adoptChild(child);
        sawChange = true;
Hixie's avatar
Hixie committed
1783 1784
      }
    }
1785
    if (!sawChange && _children != null) {
1786
      assert(newChildren != null);
1787
      assert(newChildren.length == _children!.length);
1788
      // Did the order change?
1789 1790
      for (int i = 0; i < _children!.length; i++) {
        if (_children![i].id != newChildren[i].id) {
1791 1792 1793 1794 1795
          sawChange = true;
          break;
        }
      }
    }
1796
    _children = newChildren;
Hixie's avatar
Hixie committed
1797 1798 1799 1800
    if (sawChange)
      _markDirty();
  }

1801 1802 1803
  /// Whether this node has a non-zero number of children.
  bool get hasChildren => _children?.isNotEmpty ?? false;
  bool _dead = false;
1804

1805
  /// The number of children this node has.
1806
  int get childrenCount => hasChildren ? _children!.length : 0;
1807

1808 1809
  /// Visits the immediate children of this node.
  ///
1810 1811 1812
  /// This function calls visitor for each immediate child until visitor returns
  /// false. Returns true if all the visitor calls returned true, otherwise
  /// returns false.
1813
  void visitChildren(SemanticsNodeVisitor visitor) {
Hixie's avatar
Hixie committed
1814
    if (_children != null) {
1815
      for (final SemanticsNode child in _children!) {
1816 1817 1818
        if (!visitor(child))
          return;
      }
Hixie's avatar
Hixie committed
1819 1820 1821
    }
  }

1822 1823
  /// Visit all the descendants of this node.
  ///
1824
  /// This function calls visitor for each descendant in a pre-order traversal
1825 1826
  /// until visitor returns false. Returns true if all the visitor calls
  /// returned true, otherwise returns false.
Hixie's avatar
Hixie committed
1827 1828
  bool _visitDescendants(SemanticsNodeVisitor visitor) {
    if (_children != null) {
1829
      for (final SemanticsNode child in _children!) {
Hixie's avatar
Hixie committed
1830 1831 1832 1833 1834 1835 1836
        if (!visitor(child) || !child._visitDescendants(visitor))
          return false;
      }
    }
    return true;
  }

1837 1838 1839
  // AbstractNode OVERRIDES

  @override
1840
  SemanticsOwner? get owner => super.owner as SemanticsOwner?;
1841 1842

  @override
1843
  SemanticsNode? get parent => super.parent as SemanticsNode?;
1844 1845 1846

  @override
  void redepthChildren() {
1847
    _children?.forEach(redepthChild);
1848 1849
  }

1850
  @override
1851
  void attach(SemanticsOwner owner) {
1852
    super.attach(owner);
1853 1854 1855 1856 1857
    while (owner._nodes.containsKey(id)) {
      // Ids may repeat if the Flutter has generated > 2^16 ids. We need to keep
      // regenerating the id until we found an id that is not used.
      _id = _generateNewId();
    }
1858
    owner._nodes[id] = this;
1859 1860 1861 1862 1863
    owner._detachedNodes.remove(this);
    if (_dirty) {
      _dirty = false;
      _markDirty();
    }
Hixie's avatar
Hixie committed
1864
    if (_children != null) {
1865
      for (final SemanticsNode child in _children!)
1866
        child.attach(owner);
Hixie's avatar
Hixie committed
1867 1868
    }
  }
1869 1870

  @override
Hixie's avatar
Hixie committed
1871
  void detach() {
1872 1873 1874 1875
    assert(owner!._nodes.containsKey(id));
    assert(!owner!._detachedNodes.contains(this));
    owner!._nodes.remove(id);
    owner!._detachedNodes.add(this);
Hixie's avatar
Hixie committed
1876
    super.detach();
1877
    assert(owner == null);
Hixie's avatar
Hixie committed
1878
    if (_children != null) {
1879
      for (final SemanticsNode child in _children!) {
1880 1881 1882 1883 1884
        // The list of children may be stale and may contain nodes that have
        // been assigned to a different parent.
        if (child.parent == this)
          child.detach();
      }
Hixie's avatar
Hixie committed
1885
    }
1886 1887 1888 1889
    // The other side will have forgotten this node if we ever send
    // it again, so make sure to mark it dirty so that it'll get
    // sent if it is resurrected.
    _markDirty();
Hixie's avatar
Hixie committed
1890 1891
  }

1892 1893
  // DIRTY MANAGEMENT

Hixie's avatar
Hixie committed
1894 1895 1896 1897 1898
  bool _dirty = false;
  void _markDirty() {
    if (_dirty)
      return;
    _dirty = true;
1899
    if (attached) {
1900 1901
      assert(!owner!._detachedNodes.contains(this));
      owner!._dirtyNodes.add(this);
1902
    }
Hixie's avatar
Hixie committed
1903 1904
  }

1905
  bool _isDifferentFromCurrentSemanticAnnotation(SemanticsConfiguration config) {
1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925
    return _attributedLabel != config.attributedLabel
        || _attributedHint != config.attributedHint
        || _elevation != config.elevation
        || _thickness != config.thickness
        || _attributedValue != config.attributedValue
        || _attributedIncreasedValue != config.attributedIncreasedValue
        || _attributedDecreasedValue != config.attributedDecreasedValue
        || _flags != config._flags
        || _textDirection != config.textDirection
        || _sortKey != config._sortKey
        || _textSelection != config._textSelection
        || _scrollPosition != config._scrollPosition
        || _scrollExtentMax != config._scrollExtentMax
        || _scrollExtentMin != config._scrollExtentMin
        || _actionsAsBits != config._actionsAsBits
        || indexInParent != config.indexInParent
        || platformViewId != config.platformViewId
        || _maxValueLength != config._maxValueLength
        || _currentValueLength != config._currentValueLength
        || _mergeAllDescendantsIntoThisNode != config.isMergingSemanticsOfDescendants;
1926 1927 1928 1929
  }

  // TAGS, LABELS, ACTIONS

1930
  Map<SemanticsAction, SemanticsActionHandler> _actions = _kEmptyConfig._actions;
1931
  Map<CustomSemanticsAction, VoidCallback> _customSemanticsActions = _kEmptyConfig._customSemanticsActions;
1932

1933 1934
  int _actionsAsBits = _kEmptyConfig._actionsAsBits;

1935 1936 1937
  /// The [SemanticsTag]s this node is tagged with.
  ///
  /// Tags are used during the construction of the semantics tree. They are not
1938
  /// transferred to the engine.
1939
  Set<SemanticsTag>? tags;
1940 1941

  /// Whether this node is tagged with `tag`.
1942
  bool isTagged(SemanticsTag tag) => tags != null && tags!.contains(tag);
1943 1944 1945

  int _flags = _kEmptyConfig._flags;

1946 1947
  /// Whether this node currently has a given [SemanticsFlag].
  bool hasFlag(SemanticsFlag flag) => _flags & flag.index != 0;
1948 1949 1950

  /// A textual description of this node.
  ///
1951
  /// The reading direction is given by [textDirection].
1952 1953
  ///
  /// This exposes the raw text of the [attributedLabel].
1954 1955 1956 1957 1958
  String get label => _attributedLabel.string;

  /// A textual description of this node in [AttributedString] format.
  ///
  /// The reading direction is given by [textDirection].
1959 1960
  ///
  /// See also [label], which exposes just the raw text.
1961 1962
  AttributedString get attributedLabel => _attributedLabel;
  AttributedString _attributedLabel = _kEmptyConfig.attributedLabel;
1963

1964 1965
  /// A textual description for the current value of the node.
  ///
1966
  /// The reading direction is given by [textDirection].
1967 1968
  ///
  /// This exposes the raw text of the [attributedValue].
1969 1970 1971 1972 1973 1974
  String get value => _attributedValue.string;

  /// A textual description for the current value of the node in
  /// [AttributedString] format.
  ///
  /// The reading direction is given by [textDirection].
1975 1976
  ///
  /// See also [value], which exposes just the raw text.
1977 1978
  AttributedString get attributedValue => _attributedValue;
  AttributedString _attributedValue = _kEmptyConfig.attributedValue;
1979

1980
  /// The value that [value] will have after a [SemanticsAction.increase] action
1981 1982
  /// has been performed.
  ///
1983
  /// This property is only valid if the [SemanticsAction.increase] action is
1984 1985 1986
  /// available on this node.
  ///
  /// The reading direction is given by [textDirection].
1987 1988 1989
  ///
  /// This exposes the raw text of the [attributedIncreasedValue].
  String get increasedValue => _attributedIncreasedValue.string;
1990 1991

  /// The value in [AttributedString] format that [value] or [attributedValue]
1992
  /// will have after a [SemanticsAction.increase] action has been performed.
1993
  ///
1994
  /// This property is only valid if the [SemanticsAction.increase] action is
1995 1996 1997
  /// available on this node.
  ///
  /// The reading direction is given by [textDirection].
1998 1999 2000 2001
  ///
  /// See also [increasedValue], which exposes just the raw text.
  AttributedString get attributedIncreasedValue => _attributedIncreasedValue;
  AttributedString _attributedIncreasedValue = _kEmptyConfig.attributedIncreasedValue;
2002

2003
  /// The value that [value] will have after a [SemanticsAction.decrease] action
2004 2005
  /// has been performed.
  ///
2006
  /// This property is only valid if the [SemanticsAction.decrease] action is
2007 2008 2009
  /// available on this node.
  ///
  /// The reading direction is given by [textDirection].
2010 2011 2012
  ///
  /// This exposes the raw text of the [attributedDecreasedValue].
  String get decreasedValue => _attributedDecreasedValue.string;
2013 2014

  /// The value in [AttributedString] format that [value] or [attributedValue]
2015
  /// will have after a [SemanticsAction.decrease] action has been performed.
2016
  ///
2017
  /// This property is only valid if the [SemanticsAction.decrease] action is
2018 2019 2020
  /// available on this node.
  ///
  /// The reading direction is given by [textDirection].
2021 2022 2023 2024
  ///
  /// See also [decreasedValue], which exposes just the raw text.
  AttributedString get attributedDecreasedValue => _attributedDecreasedValue;
  AttributedString _attributedDecreasedValue = _kEmptyConfig.attributedDecreasedValue;
2025

2026 2027
  /// A brief description of the result of performing an action on this node.
  ///
2028
  /// The reading direction is given by [textDirection].
2029 2030
  ///
  /// This exposes the raw text of the [attributedHint].
2031 2032 2033 2034 2035 2036
  String get hint => _attributedHint.string;

  /// A brief description of the result of performing an action on this node
  /// in [AttributedString] format.
  ///
  /// The reading direction is given by [textDirection].
2037 2038
  ///
  /// See also [hint], which exposes just the raw text.
2039 2040
  AttributedString get attributedHint => _attributedHint;
  AttributedString _attributedHint = _kEmptyConfig.attributedHint;
2041

2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065
  /// The elevation along the z-axis at which the [rect] of this [SemanticsNode]
  /// is located above its parent.
  ///
  /// The value is relative to the parent's [elevation]. The sum of the
  /// [elevation]s of all ancestor node plus this value determines the absolute
  /// elevation of this [SemanticsNode].
  ///
  /// See also:
  ///
  ///  * [thickness], which describes how much space in z-direction this
  ///    [SemanticsNode] occupies starting at this [elevation].
  ///  * [elevationAdjustment], which has been used to calculate this value.
  double get elevation => _elevation;
  double _elevation = _kEmptyConfig.elevation;

  /// Describes how much space the [SemanticsNode] takes up along the z-axis.
  ///
  /// A [SemanticsNode] represents multiple [RenderObject]s, which can be
  /// located at various elevations in 3D. The [thickness] is the difference
  /// between the absolute elevations of the lowest and highest [RenderObject]
  /// represented by this [SemanticsNode]. In other words, the thickness
  /// describes how high the box is that this [SemanticsNode] occupies in three
  /// dimensional space. The two other dimensions are defined by [rect].
  ///
2066
  /// {@tool snippet}
2067
  /// The following code stacks three [PhysicalModel]s on top of each other
2068 2069 2070 2071 2072 2073 2074 2075
  /// separated by non-zero elevations.
  ///
  /// [PhysicalModel] C is elevated 10.0 above [PhysicalModel] B, which in turn
  /// is elevated 5.0 above [PhysicalModel] A. The side view of this
  /// constellation looks as follows:
  ///
  /// ![A diagram illustrating the elevations of three PhysicalModels and their
  /// corresponding SemanticsNodes.](https://flutter.github.io/assets-for-api-docs/assets/semantics/SemanticsNode.thickness.png)
2076
  ///
2077 2078 2079 2080
  /// In this example the [RenderObject]s for [PhysicalModel] C and B share one
  /// [SemanticsNode] Y. Given the elevations of those [RenderObject]s, this
  /// [SemanticsNode] has a [thickness] of 10.0 and an elevation of 5.0 over
  /// its parent [SemanticsNode] X.
2081 2082 2083 2084 2085 2086
  /// ```dart
  /// PhysicalModel( // A
  ///   color: Colors.amber,
  ///   elevation: 0.0,
  ///   child: Semantics(
  ///     explicitChildNodes: true,
2087
  ///     child: const PhysicalModel( // B
2088 2089 2090 2091 2092 2093 2094 2095 2096
  ///       color: Colors.brown,
  ///       elevation: 5.0,
  ///       child: PhysicalModel( // C
  ///         color: Colors.cyan,
  ///         elevation: 10.0,
  ///         child: Placeholder(),
  ///       ),
  ///     ),
  ///   ),
2097
  /// )
2098
  /// ```
2099
  /// {@end-tool}
2100 2101 2102 2103 2104 2105 2106 2107
  ///
  /// See also:
  ///
  ///  * [elevation], which describes the elevation of the box defined by
  ///    [thickness] and [rect] relative to the parent of this [SemanticsNode].
  double get thickness => _thickness;
  double _thickness = _kEmptyConfig.thickness;

2108 2109
  /// Provides hint values which override the default hints on supported
  /// platforms.
2110 2111
  SemanticsHintOverrides? get hintOverrides => _hintOverrides;
  SemanticsHintOverrides? _hintOverrides;
2112

2113 2114
  /// The reading direction for [label], [value], [hint], [increasedValue], and
  /// [decreasedValue].
2115 2116
  TextDirection? get textDirection => _textDirection;
  TextDirection? _textDirection = _kEmptyConfig.textDirection;
2117

2118 2119 2120 2121 2122 2123
  /// Determines the position of this node among its siblings in the traversal
  /// sort order.
  ///
  /// This is used to describe the order in which the semantic node should be
  /// traversed by the accessibility services on the platform (e.g. VoiceOver
  /// on iOS and TalkBack on Android).
2124 2125
  SemanticsSortKey? get sortKey => _sortKey;
  SemanticsSortKey? _sortKey;
2126

2127 2128
  /// The currently selected text (or the position of the cursor) within [value]
  /// if this node represents a text field.
2129 2130
  TextSelection? get textSelection => _textSelection;
  TextSelection? _textSelection;
2131

2132
  /// If this node represents a text field, this indicates whether or not it's
2133
  /// a multiline text field.
2134 2135
  bool? get isMultiline => _isMultiline;
  bool? _isMultiline;
2136

2137 2138 2139 2140
  /// The total number of scrollable children that contribute to semantics.
  ///
  /// If the number of children are unknown or unbounded, this value will be
  /// null.
2141 2142
  int? get scrollChildCount => _scrollChildCount;
  int? _scrollChildCount;
2143 2144

  /// The index of the first visible semantic child of a scroll node.
2145 2146
  int? get scrollIndex => _scrollIndex;
  int? _scrollIndex;
2147

2148 2149 2150 2151 2152 2153 2154 2155 2156 2157
  /// Indicates the current scrolling position in logical pixels if the node is
  /// scrollable.
  ///
  /// The properties [scrollExtentMin] and [scrollExtentMax] indicate the valid
  /// in-range values for this property. The value for [scrollPosition] may
  /// (temporarily) be outside that range, e.g. during an overscroll.
  ///
  /// See also:
  ///
  ///  * [ScrollPosition.pixels], from where this value is usually taken.
2158 2159
  double? get scrollPosition => _scrollPosition;
  double? _scrollPosition;
2160 2161 2162 2163 2164 2165 2166 2167 2168

  /// Indicates the maximum in-range value for [scrollPosition] if the node is
  /// scrollable.
  ///
  /// This value may be infinity if the scroll is unbound.
  ///
  /// See also:
  ///
  ///  * [ScrollPosition.maxScrollExtent], from where this value is usually taken.
2169 2170
  double? get scrollExtentMax => _scrollExtentMax;
  double? _scrollExtentMax;
2171

Josh Soref's avatar
Josh Soref committed
2172
  /// Indicates the minimum in-range value for [scrollPosition] if the node is
2173 2174 2175 2176 2177 2178 2179
  /// scrollable.
  ///
  /// This value may be infinity if the scroll is unbound.
  ///
  /// See also:
  ///
  ///  * [ScrollPosition.minScrollExtent] from where this value is usually taken.
2180 2181
  double? get scrollExtentMin => _scrollExtentMin;
  double? _scrollExtentMin;
2182

2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193
  /// The id of the platform view, whose semantics nodes will be added as
  /// children to this node.
  ///
  /// If this value is non-null, the SemanticsNode must not have any children
  /// as those would be replaced by the semantics nodes of the referenced
  /// platform view.
  ///
  /// See also:
  ///
  ///  * [AndroidView], which is the platform view for Android.
  ///  * [UiKitView], which is the platform view for iOS.
2194 2195
  int? get platformViewId => _platformViewId;
  int? _platformViewId;
2196

2197 2198 2199 2200 2201 2202 2203 2204
  /// The maximum number of characters that can be entered into an editable
  /// text field.
  ///
  /// For the purpose of this function a character is defined as one Unicode
  /// scalar value.
  ///
  /// This should only be set when [SemanticsFlag.isTextField] is set. Defaults
  /// to null, which means no limit is imposed on the text field.
2205 2206
  int? get maxValueLength => _maxValueLength;
  int? _maxValueLength;
2207 2208 2209 2210 2211 2212 2213 2214 2215

  /// The current number of characters that have been entered into an editable
  /// text field.
  ///
  /// For the purpose of this function a character is defined as one Unicode
  /// scalar value.
  ///
  /// This should only be set when [SemanticsFlag.isTextField] is set. Must be
  /// set when [maxValueLength] is set.
2216 2217
  int? get currentValueLength => _currentValueLength;
  int? _currentValueLength;
2218

2219 2220
  bool _canPerformAction(SemanticsAction action) => _actions.containsKey(action);

2221
  static final SemanticsConfiguration _kEmptyConfig = SemanticsConfiguration();
2222

2223
  /// Reconfigures the properties of this object to describe the configuration
2224
  /// provided in the `config` argument and the children listed in the
2225 2226 2227 2228 2229 2230 2231
  /// `childrenInInversePaintOrder` argument.
  ///
  /// The arguments may be null; this represents an empty configuration (all
  /// values at their defaults, no children).
  ///
  /// No reference is kept to the [SemanticsConfiguration] object, but the child
  /// list is used as-is and should therefore not be changed after this call.
2232
  void updateWith({
2233 2234
    required SemanticsConfiguration? config,
    List<SemanticsNode>? childrenInInversePaintOrder,
2235 2236 2237 2238 2239
  }) {
    config ??= _kEmptyConfig;
    if (_isDifferentFromCurrentSemanticAnnotation(config))
      _markDirty();

2240
    assert(
2241
      config.platformViewId == null || childrenInInversePaintOrder == null || childrenInInversePaintOrder.isEmpty,
2242
      'SemanticsNodes with children must not specify a platformViewId.',
2243 2244
    );

2245 2246 2247 2248 2249
    _attributedLabel = config.attributedLabel;
    _attributedValue = config.attributedValue;
    _attributedIncreasedValue = config.attributedIncreasedValue;
    _attributedDecreasedValue = config.attributedDecreasedValue;
    _attributedHint = config.attributedHint;
2250
    _hintOverrides = config.hintOverrides;
2251 2252
    _elevation = config.elevation;
    _thickness = config.thickness;
2253 2254
    _flags = config._flags;
    _textDirection = config.textDirection;
2255
    _sortKey = config.sortKey;
2256 2257
    _actions = Map<SemanticsAction, SemanticsActionHandler>.of(config._actions);
    _customSemanticsActions = Map<CustomSemanticsAction, VoidCallback>.of(config._customSemanticsActions);
2258
    _actionsAsBits = config._actionsAsBits;
2259
    _textSelection = config._textSelection;
2260
    _isMultiline = config.isMultiline;
2261 2262 2263
    _scrollPosition = config._scrollPosition;
    _scrollExtentMax = config._scrollExtentMax;
    _scrollExtentMin = config._scrollExtentMin;
2264
    _mergeAllDescendantsIntoThisNode = config.isMergingSemanticsOfDescendants;
2265 2266 2267
    _scrollChildCount = config.scrollChildCount;
    _scrollIndex = config.scrollIndex;
    indexInParent = config.indexInParent;
2268
    _platformViewId = config._platformViewId;
2269 2270
    _maxValueLength = config._maxValueLength;
    _currentValueLength = config._currentValueLength;
2271
    _replaceChildren(childrenInInversePaintOrder ?? const <SemanticsNode>[]);
2272 2273

    assert(
2274
      !_canPerformAction(SemanticsAction.increase) || (value == '') == (increasedValue == ''),
2275 2276 2277
      'A SemanticsNode with action "increase" needs to be annotated with either both "value" and "increasedValue" or neither',
    );
    assert(
2278
      !_canPerformAction(SemanticsAction.decrease) || (value == '') == (decreasedValue == ''),
2279 2280
      'A SemanticsNode with action "increase" needs to be annotated with either both "value" and "decreasedValue" or neither',
    );
2281 2282 2283
  }


2284 2285 2286 2287 2288 2289 2290
  /// Returns a summary of the semantics for this node.
  ///
  /// If this node has [mergeAllDescendantsIntoThisNode], then the returned data
  /// includes the information from this node's descendants. Otherwise, the
  /// returned data matches the data on this node.
  SemanticsData getSemanticsData() {
    int flags = _flags;
2291
    int actions = _actionsAsBits;
2292 2293 2294 2295 2296
    AttributedString attributedLabel = _attributedLabel;
    AttributedString attributedValue = _attributedValue;
    AttributedString attributedIncreasedValue = _attributedIncreasedValue;
    AttributedString attributedDecreasedValue = _attributedDecreasedValue;
    AttributedString attributedHint = _attributedHint;
2297
    TextDirection? textDirection = _textDirection;
2298
    Set<SemanticsTag>? mergedTags = tags == null ? null : Set<SemanticsTag>.of(tags!);
2299 2300 2301 2302 2303 2304 2305 2306 2307
    TextSelection? textSelection = _textSelection;
    int? scrollChildCount = _scrollChildCount;
    int? scrollIndex = _scrollIndex;
    double? scrollPosition = _scrollPosition;
    double? scrollExtentMax = _scrollExtentMax;
    double? scrollExtentMin = _scrollExtentMin;
    int? platformViewId = _platformViewId;
    int? maxValueLength = _maxValueLength;
    int? currentValueLength = _currentValueLength;
2308 2309
    final double elevation = _elevation;
    double thickness = _thickness;
2310
    final Set<int> customSemanticsActionIds = <int>{};
2311
    for (final CustomSemanticsAction action in _customSemanticsActions.keys)
2312
      customSemanticsActionIds.add(CustomSemanticsAction.getIdentifier(action));
2313
    if (hintOverrides != null) {
2314
      if (hintOverrides!.onTapHint != null) {
2315
        final CustomSemanticsAction action = CustomSemanticsAction.overridingAction(
2316
          hint: hintOverrides!.onTapHint!,
2317 2318 2319 2320
          action: SemanticsAction.tap,
        );
        customSemanticsActionIds.add(CustomSemanticsAction.getIdentifier(action));
      }
2321
      if (hintOverrides!.onLongPressHint != null) {
2322
        final CustomSemanticsAction action = CustomSemanticsAction.overridingAction(
2323
          hint: hintOverrides!.onLongPressHint!,
2324 2325 2326 2327 2328
          action: SemanticsAction.longPress,
        );
        customSemanticsActionIds.add(CustomSemanticsAction.getIdentifier(action));
      }
    }
2329 2330 2331

    if (mergeAllDescendantsIntoThisNode) {
      _visitDescendants((SemanticsNode node) {
2332
        assert(node.isMergedIntoParent);
2333
        flags |= node._flags;
2334
        actions |= node._actionsAsBits;
Ian Hickson's avatar
Ian Hickson committed
2335
        textDirection ??= node._textDirection;
2336
        textSelection ??= node._textSelection;
2337 2338
        scrollChildCount ??= node._scrollChildCount;
        scrollIndex ??= node._scrollIndex;
2339 2340 2341
        scrollPosition ??= node._scrollPosition;
        scrollExtentMax ??= node._scrollExtentMax;
        scrollExtentMin ??= node._scrollExtentMin;
2342
        platformViewId ??= node._platformViewId;
2343 2344
        maxValueLength ??= node._maxValueLength;
        currentValueLength ??= node._currentValueLength;
2345 2346 2347 2348 2349 2350
        if (attributedValue == null || attributedValue.string == '')
          attributedValue = node._attributedValue;
        if (attributedIncreasedValue == null || attributedIncreasedValue.string == '')
          attributedIncreasedValue = node._attributedIncreasedValue;
        if (attributedDecreasedValue == null || attributedDecreasedValue.string == '')
          attributedDecreasedValue = node._attributedDecreasedValue;
2351
        if (node.tags != null) {
2352
          mergedTags ??= <SemanticsTag>{};
2353
          mergedTags!.addAll(node.tags!);
2354
        }
2355 2356
        for (final CustomSemanticsAction action in _customSemanticsActions.keys)
          customSemanticsActionIds.add(CustomSemanticsAction.getIdentifier(action));
2357
        if (node.hintOverrides != null) {
2358
          if (node.hintOverrides!.onTapHint != null) {
2359
            final CustomSemanticsAction action = CustomSemanticsAction.overridingAction(
2360
              hint: node.hintOverrides!.onTapHint!,
2361 2362 2363 2364
              action: SemanticsAction.tap,
            );
            customSemanticsActionIds.add(CustomSemanticsAction.getIdentifier(action));
          }
2365
          if (node.hintOverrides!.onLongPressHint != null) {
2366
            final CustomSemanticsAction action = CustomSemanticsAction.overridingAction(
2367
              hint: node.hintOverrides!.onLongPressHint!,
2368 2369 2370 2371 2372
              action: SemanticsAction.longPress,
            );
            customSemanticsActionIds.add(CustomSemanticsAction.getIdentifier(action));
          }
        }
2373 2374
        attributedLabel = _concatAttributedString(
          thisAttributedString: attributedLabel,
2375
          thisTextDirection: textDirection,
2376
          otherAttributedString: node._attributedLabel,
2377 2378
          otherTextDirection: node._textDirection,
        );
2379 2380
        attributedHint = _concatAttributedString(
          thisAttributedString: attributedHint,
2381
          thisTextDirection: textDirection,
2382
          otherAttributedString: node._attributedHint,
2383 2384
          otherTextDirection: node._textDirection,
        );
2385 2386 2387

        thickness = math.max(thickness, node._thickness + node._elevation);

2388 2389 2390 2391
        return true;
      });
    }

2392
    return SemanticsData(
2393 2394
      flags: flags,
      actions: actions,
2395 2396 2397 2398 2399
      attributedLabel: attributedLabel,
      attributedValue: attributedValue,
      attributedIncreasedValue: attributedIncreasedValue,
      attributedDecreasedValue: attributedDecreasedValue,
      attributedHint: attributedHint,
Ian Hickson's avatar
Ian Hickson committed
2400
      textDirection: textDirection,
2401
      rect: rect,
2402
      transform: transform,
2403 2404
      elevation: elevation,
      thickness: thickness,
2405
      tags: mergedTags,
2406
      textSelection: textSelection,
2407 2408
      scrollChildCount: scrollChildCount,
      scrollIndex: scrollIndex,
2409 2410 2411
      scrollPosition: scrollPosition,
      scrollExtentMax: scrollExtentMax,
      scrollExtentMin: scrollExtentMin,
2412
      platformViewId: platformViewId,
2413 2414
      maxValueLength: maxValueLength,
      currentValueLength: currentValueLength,
2415
      customSemanticsActionIds: customSemanticsActionIds.toList()..sort(),
2416 2417 2418
    );
  }

2419
  static Float64List _initIdentityTransform() {
2420
    return Matrix4.identity().storage;
2421 2422
  }

2423 2424
  static final Int32List _kEmptyChildList = Int32List(0);
  static final Int32List _kEmptyCustomSemanticsActionsList = Int32List(0);
2425 2426
  static final Float64List _kIdentityTransform = _initIdentityTransform();

2427
  void _addToUpdate(ui.SemanticsUpdateBuilder builder, Set<int> customSemanticsActionIdsUpdate) {
2428 2429
    assert(_dirty);
    final SemanticsData data = getSemanticsData();
2430 2431
    final Int32List childrenInTraversalOrder;
    final Int32List childrenInHitTestOrder;
2432
    if (!hasChildren || mergeAllDescendantsIntoThisNode) {
2433 2434
      childrenInTraversalOrder = _kEmptyChildList;
      childrenInHitTestOrder = _kEmptyChildList;
2435
    } else {
2436
      final int childCount = _children!.length;
2437
      final List<SemanticsNode> sortedChildren = _childrenInTraversalOrder();
2438
      childrenInTraversalOrder = Int32List(childCount);
2439 2440 2441 2442 2443
      for (int i = 0; i < childCount; i += 1) {
        childrenInTraversalOrder[i] = sortedChildren[i].id;
      }
      // _children is sorted in paint order, so we invert it to get the hit test
      // order.
2444
      childrenInHitTestOrder = Int32List(childCount);
2445
      for (int i = childCount - 1; i >= 0; i -= 1) {
2446
        childrenInHitTestOrder[i] = _children![childCount - i - 1].id;
2447
      }
Hixie's avatar
Hixie committed
2448
    }
2449
    Int32List? customSemanticsActionIds;
2450
    if (data.customSemanticsActionIds?.isNotEmpty ?? false) {
2451 2452 2453 2454
      customSemanticsActionIds = Int32List(data.customSemanticsActionIds!.length);
      for (int i = 0; i < data.customSemanticsActionIds!.length; i++) {
        customSemanticsActionIds[i] = data.customSemanticsActionIds![i];
        customSemanticsActionIdsUpdate.add(data.customSemanticsActionIds![i]);
2455 2456
      }
    }
2457 2458 2459 2460 2461
    builder.updateNode(
      id: id,
      flags: data.flags,
      actions: data.actions,
      rect: data.rect,
2462 2463 2464 2465 2466 2467 2468 2469 2470 2471
      label: data.attributedLabel.string,
      labelAttributes: data.attributedLabel.attributes,
      value: data.attributedValue.string,
      valueAttributes: data.attributedValue.attributes,
      increasedValue: data.attributedIncreasedValue.string,
      increasedValueAttributes: data.attributedIncreasedValue.attributes,
      decreasedValue: data.attributedDecreasedValue.string,
      decreasedValueAttributes: data.attributedDecreasedValue.attributes,
      hint: data.attributedHint.string,
      hintAttributes: data.attributedHint.attributes,
Ian Hickson's avatar
Ian Hickson committed
2472
      textDirection: data.textDirection,
2473 2474
      textSelectionBase: data.textSelection != null ? data.textSelection!.baseOffset : -1,
      textSelectionExtent: data.textSelection != null ? data.textSelection!.extentOffset : -1,
2475
      platformViewId: data.platformViewId ?? -1,
2476 2477
      maxValueLength: data.maxValueLength ?? -1,
      currentValueLength: data.currentValueLength ?? -1,
2478 2479 2480 2481 2482
      scrollChildren: data.scrollChildCount ?? 0,
      scrollIndex: data.scrollIndex ?? 0 ,
      scrollPosition: data.scrollPosition ?? double.nan,
      scrollExtentMax: data.scrollExtentMax ?? double.nan,
      scrollExtentMin: data.scrollExtentMin ?? double.nan,
2483
      transform: data.transform?.storage ?? _kIdentityTransform,
2484 2485
      elevation: data.elevation,
      thickness: data.thickness,
2486 2487
      childrenInTraversalOrder: childrenInTraversalOrder,
      childrenInHitTestOrder: childrenInHitTestOrder,
2488
      additionalActions: customSemanticsActionIds ?? _kEmptyCustomSemanticsActionsList,
2489 2490
    );
    _dirty = false;
Hixie's avatar
Hixie committed
2491 2492
  }

2493 2494
  /// Builds a new list made of [_children] sorted in semantic traversal order.
  List<SemanticsNode> _childrenInTraversalOrder() {
2495 2496
    TextDirection? inheritedTextDirection = textDirection;
    SemanticsNode? ancestor = parent;
2497 2498 2499 2500 2501
    while (inheritedTextDirection == null && ancestor != null) {
      inheritedTextDirection = ancestor.textDirection;
      ancestor = ancestor.parent;
    }

2502
    List<SemanticsNode>? childrenInDefaultOrder;
2503
    if (inheritedTextDirection != null) {
2504
      childrenInDefaultOrder = _childrenInDefaultOrder(_children!, inheritedTextDirection);
2505 2506 2507 2508 2509 2510 2511 2512 2513 2514 2515
    } else {
      // In the absence of text direction default to paint order.
      childrenInDefaultOrder = _children;
    }

    // List.sort does not guarantee stable sort order. Therefore, children are
    // first partitioned into groups that have compatible sort keys, i.e. keys
    // in the same group can be compared to each other. These groups stay in
    // the same place. Only children within the same group are sorted.
    final List<_TraversalSortNode> everythingSorted = <_TraversalSortNode>[];
    final List<_TraversalSortNode> sortNodes = <_TraversalSortNode>[];
2516 2517
    SemanticsSortKey? lastSortKey;
    for (int position = 0; position < childrenInDefaultOrder!.length; position += 1) {
2518
      final SemanticsNode child = childrenInDefaultOrder[position];
2519
      final SemanticsSortKey? sortKey = child.sortKey;
2520 2521 2522 2523 2524
      lastSortKey = position > 0
          ? childrenInDefaultOrder[position - 1].sortKey
          : null;
      final bool isCompatibleWithPreviousSortKey = position == 0 ||
          sortKey.runtimeType == lastSortKey.runtimeType &&
2525
          (sortKey == null || sortKey.name == lastSortKey!.name);
2526 2527 2528 2529 2530 2531 2532 2533 2534 2535
      if (!isCompatibleWithPreviousSortKey && sortNodes.isNotEmpty) {
        // Do not sort groups with null sort keys. List.sort does not guarantee
        // a stable sort order.
        if (lastSortKey != null) {
          sortNodes.sort();
        }
        everythingSorted.addAll(sortNodes);
        sortNodes.clear();
      }

2536
      sortNodes.add(_TraversalSortNode(
2537 2538 2539 2540 2541 2542 2543 2544 2545 2546 2547 2548 2549 2550 2551 2552 2553 2554
        node: child,
        sortKey: sortKey,
        position: position,
      ));
    }

    // Do not sort groups with null sort keys. List.sort does not guarantee
    // a stable sort order.
    if (lastSortKey != null) {
      sortNodes.sort();
    }
    everythingSorted.addAll(sortNodes);

    return everythingSorted
      .map<SemanticsNode>((_TraversalSortNode sortNode) => sortNode.node)
      .toList();
  }

2555 2556 2557 2558 2559 2560 2561
  /// Sends a [SemanticsEvent] associated with this [SemanticsNode].
  ///
  /// Semantics events should be sent to inform interested parties (like
  /// the accessibility system of the operating system) about changes to the UI.
  void sendEvent(SemanticsEvent event) {
    if (!attached)
      return;
2562
    SystemChannels.accessibility.send(event.toMap(nodeId: id));
2563 2564
  }

2565
  @override
2566
  String toStringShort() => '${objectRuntimeType(this, 'SemanticsNode')}#$id';
2567 2568 2569

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
2570
    super.debugFillProperties(properties);
2571 2572
    bool hideOwner = true;
    if (_dirty) {
2573
      final bool inDirtyNodes = owner != null && owner!._dirtyNodes.contains(this);
2574
      properties.add(FlagProperty('inDirtyNodes', value: inDirtyNodes, ifTrue: 'dirty', ifFalse: 'STALE'));
2575 2576
      hideOwner = inDirtyNodes;
    }
2577 2578 2579
    properties.add(DiagnosticsProperty<SemanticsOwner>('owner', owner, level: hideOwner ? DiagnosticLevel.hidden : DiagnosticLevel.info));
    properties.add(FlagProperty('isMergedIntoParent', value: isMergedIntoParent, ifTrue: 'merged up ⬆️'));
    properties.add(FlagProperty('mergeAllDescendantsIntoThisNode', value: mergeAllDescendantsIntoThisNode, ifTrue: 'merge boundary ⛔️'));
2580
    final Offset? offset = transform != null ? MatrixUtils.getAsTranslation(transform!) : null;
2581
    if (offset != null) {
2582
      properties.add(DiagnosticsProperty<Rect>('rect', rect.shift(offset), showName: false));
2583
    } else {
2584 2585
      final double? scale = transform != null ? MatrixUtils.getAsScale(transform!) : null;
      String? description;
2586
      if (scale != null) {
2587
        description = '$rect scaled by ${scale.toStringAsFixed(1)}x';
2588
      } else if (transform != null && !MatrixUtils.isIdentity(transform!)) {
2589
        final String matrix = transform.toString().split('\n').take(4).map<String>((String line) => line.substring(4)).join('; ');
2590
        description = '$rect with transform [$matrix]';
2591
      }
2592
      properties.add(DiagnosticsProperty<Rect>('rect', rect, description: description, showName: false));
2593
    }
2594
    properties.add(IterableProperty<String>('tags', tags?.map((SemanticsTag tag) => tag.name), defaultValue: null));
2595
    final List<String> actions = _actions.keys.map<String>((SemanticsAction action) => describeEnum(action)).toList()..sort();
2596 2597
    final List<String?> customSemanticsActions = _customSemanticsActions.keys
      .map<String?>((CustomSemanticsAction action) => action.label)
2598
      .toList();
2599
    properties.add(IterableProperty<String>('actions', actions, ifEmpty: null));
2600
    properties.add(IterableProperty<String?>('customActions', customSemanticsActions, ifEmpty: null));
2601
    final List<String> flags = SemanticsFlag.values.values.where((SemanticsFlag flag) => hasFlag(flag)).map((SemanticsFlag flag) => flag.toString().substring('SemanticsFlag.'.length)).toList();
2602 2603
    properties.add(IterableProperty<String>('flags', flags, ifEmpty: null));
    properties.add(FlagProperty('isInvisible', value: isInvisible, ifTrue: 'invisible'));
2604
    properties.add(FlagProperty('isHidden', value: hasFlag(SemanticsFlag.isHidden), ifTrue: 'HIDDEN'));
2605 2606 2607 2608 2609
    properties.add(AttributedStringProperty('label', _attributedLabel));
    properties.add(AttributedStringProperty('value', _attributedValue));
    properties.add(AttributedStringProperty('increasedValue', _attributedIncreasedValue));
    properties.add(AttributedStringProperty('decreasedValue', _attributedDecreasedValue));
    properties.add(AttributedStringProperty('hint', _attributedHint));
2610 2611
    properties.add(EnumProperty<TextDirection>('textDirection', _textDirection, defaultValue: null));
    properties.add(DiagnosticsProperty<SemanticsSortKey>('sortKey', sortKey, defaultValue: null));
2612
    if (_textSelection?.isValid ?? false)
2613
      properties.add(MessageProperty('text selection', '[${_textSelection!.start}, ${_textSelection!.end}]'));
2614
    properties.add(IntProperty('platformViewId', platformViewId, defaultValue: null));
2615 2616
    properties.add(IntProperty('maxValueLength', maxValueLength, defaultValue: null));
    properties.add(IntProperty('currentValueLength', currentValueLength, defaultValue: null));
2617 2618
    properties.add(IntProperty('scrollChildren', scrollChildCount, defaultValue: null));
    properties.add(IntProperty('scrollIndex', scrollIndex, defaultValue: null));
2619 2620 2621
    properties.add(DoubleProperty('scrollExtentMin', scrollExtentMin, defaultValue: null));
    properties.add(DoubleProperty('scrollPosition', scrollPosition, defaultValue: null));
    properties.add(DoubleProperty('scrollExtentMax', scrollExtentMax, defaultValue: null));
2622
    properties.add(DoubleProperty('elevation', elevation, defaultValue: 0.0));
2623
    properties.add(DoubleProperty('thickness', thickness, defaultValue: 0.0));
2624 2625 2626
  }

  /// Returns a string representation of this node and its descendants.
2627 2628 2629
  ///
  /// The order in which the children of the [SemanticsNode] will be printed is
  /// controlled by the [childOrder] parameter.
2630 2631
  @override
  String toStringDeep({
2632
    String prefixLineOne = '',
2633
    String? prefixOtherLines,
2634 2635
    DiagnosticLevel minLevel = DiagnosticLevel.debug,
    DebugSemanticsDumpOrder childOrder = DebugSemanticsDumpOrder.traversalOrder,
2636
  }) {
2637
    assert(childOrder != null);
2638
    return toDiagnosticsNode(childOrder: childOrder).toStringDeep(prefixLineOne: prefixLineOne, prefixOtherLines: prefixOtherLines, minLevel: minLevel);
2639 2640 2641 2642
  }

  @override
  DiagnosticsNode toDiagnosticsNode({
2643 2644
    String? name,
    DiagnosticsTreeStyle? style = DiagnosticsTreeStyle.sparse,
2645
    DebugSemanticsDumpOrder childOrder = DebugSemanticsDumpOrder.traversalOrder,
2646
  }) {
2647
    return _SemanticsDiagnosticableNode(
2648 2649 2650 2651 2652 2653 2654 2655
      name: name,
      value: this,
      style: style,
      childOrder: childOrder,
    );
  }

  @override
2656
  List<DiagnosticsNode> debugDescribeChildren({ DebugSemanticsDumpOrder childOrder = DebugSemanticsDumpOrder.inverseHitTest }) {
2657
    return debugListChildrenInOrder(childOrder)
2658 2659
      .map<DiagnosticsNode>((SemanticsNode node) => node.toDiagnosticsNode(childOrder: childOrder))
      .toList();
2660 2661
  }

2662 2663
  /// Returns the list of direct children of this node in the specified order.
  List<SemanticsNode> debugListChildrenInOrder(DebugSemanticsDumpOrder childOrder) {
2664
    assert(childOrder != null);
2665 2666 2667
    if (_children == null)
      return const <SemanticsNode>[];

2668
    switch (childOrder) {
2669
      case DebugSemanticsDumpOrder.inverseHitTest:
2670
        return _children!;
2671 2672
      case DebugSemanticsDumpOrder.traversalOrder:
        return _childrenInTraversalOrder();
2673 2674
    }
  }
2675 2676 2677 2678 2679 2680 2681 2682 2683 2684 2685 2686
}

/// An edge of a box, such as top, bottom, left or right, used to compute
/// [SemanticsNode]s that overlap vertically or horizontally.
///
/// For computing horizontal overlap in an LTR setting we create two [_BoxEdge]
/// objects for each [SemanticsNode]: one representing the left edge (marked
/// with [isLeadingEdge] equal to true) and one for the right edge (with [isLeadingEdge]
/// equal to false). Similarly, for vertical overlap we also create two objects
/// for each [SemanticsNode], one for the top and one for the bottom edge.
class _BoxEdge implements Comparable<_BoxEdge> {
  _BoxEdge({
2687 2688 2689
    required this.isLeadingEdge,
    required this.offset,
    required this.node,
2690 2691
  }) : assert(isLeadingEdge != null),
       assert(offset != null),
2692
       assert(offset.isFinite),
2693 2694 2695 2696 2697 2698 2699 2700 2701 2702 2703 2704 2705 2706 2707 2708 2709 2710 2711 2712 2713 2714
       assert(node != null);

  /// True if the edge comes before the seconds edge along the traversal
  /// direction, and false otherwise.
  ///
  /// This field is never null.
  ///
  /// For example, in LTR traversal the left edge's [isLeadingEdge] is set to true,
  /// the right edge's [isLeadingEdge] is set to false. When considering vertical
  /// ordering of boxes, the top edge is the start edge, and the bottom edge is
  /// the end edge.
  final bool isLeadingEdge;

  /// The offset from the start edge of the parent [SemanticsNode] in the
  /// direction of the traversal.
  final double offset;

  /// The node whom this edge belongs.
  final SemanticsNode node;

  @override
  int compareTo(_BoxEdge other) {
2715
    return offset.compareTo(other.offset);
2716 2717 2718 2719 2720 2721 2722 2723 2724
  }
}

/// A group of [nodes] that are disjoint vertically or horizontally from other
/// nodes that share the same [SemanticsNode] parent.
///
/// The [nodes] are sorted among each other separately from other nodes.
class _SemanticsSortGroup extends Comparable<_SemanticsSortGroup> {
  _SemanticsSortGroup({
2725 2726
    required this.startOffset,
    required this.textDirection,
2727 2728 2729 2730 2731 2732 2733 2734 2735 2736 2737 2738 2739 2740 2741 2742
  }) : assert(startOffset != null);

  /// The offset from the start edge of the parent [SemanticsNode] in the
  /// direction of the traversal.
  ///
  /// This value is equal to the [_BoxEdge.offset] of the first node in the
  /// [nodes] list being considered.
  final double startOffset;

  final TextDirection textDirection;

  /// The nodes that are sorted among each other.
  final List<SemanticsNode> nodes = <SemanticsNode>[];

  @override
  int compareTo(_SemanticsSortGroup other) {
2743
    return startOffset.compareTo(other.startOffset);
2744 2745 2746 2747 2748 2749 2750 2751
  }

  /// Sorts this group assuming that [nodes] belong to the same vertical group.
  ///
  /// This method breaks up this group into horizontal [_SemanticsSortGroup]s
  /// then sorts them using [sortedWithinKnot].
  List<SemanticsNode> sortedWithinVerticalGroup() {
    final List<_BoxEdge> edges = <_BoxEdge>[];
2752
    for (final SemanticsNode child in nodes) {
2753 2754
      // Using a small delta to shrink child rects removes overlapping cases.
      final Rect childRect = child.rect.deflate(0.1);
2755
      edges.add(_BoxEdge(
2756
        isLeadingEdge: true,
2757
        offset: _pointInParentCoordinates(child, childRect.topLeft).dx,
2758 2759
        node: child,
      ));
2760
      edges.add(_BoxEdge(
2761
        isLeadingEdge: false,
2762
        offset: _pointInParentCoordinates(child, childRect.bottomRight).dx,
2763 2764 2765 2766 2767 2768
        node: child,
      ));
    }
    edges.sort();

    List<_SemanticsSortGroup> horizontalGroups = <_SemanticsSortGroup>[];
2769
    _SemanticsSortGroup? group;
2770
    int depth = 0;
2771
    for (final _BoxEdge edge in edges) {
2772 2773
      if (edge.isLeadingEdge) {
        depth += 1;
2774
        group ??= _SemanticsSortGroup(
2775 2776 2777 2778 2779 2780 2781 2782
          startOffset: edge.offset,
          textDirection: textDirection,
        );
        group.nodes.add(edge.node);
      } else {
        depth -= 1;
      }
      if (depth == 0) {
2783
        horizontalGroups.add(group!);
2784 2785 2786 2787 2788 2789 2790 2791 2792
        group = null;
      }
    }
    horizontalGroups.sort();

    if (textDirection == TextDirection.rtl) {
      horizontalGroups = horizontalGroups.reversed.toList();
    }

2793 2794 2795
    return horizontalGroups
      .expand((_SemanticsSortGroup group) => group.sortedWithinKnot())
      .toList();
2796
  }
2797

2798 2799 2800 2801 2802 2803 2804 2805 2806 2807 2808 2809 2810 2811 2812 2813 2814 2815 2816 2817 2818 2819
  /// Sorts [nodes] where nodes intersect both vertically and horizontally.
  ///
  /// In the special case when [nodes] contains one or less nodes, this method
  /// returns [nodes] unchanged.
  ///
  /// This method constructs a graph, where vertices are [SemanticsNode]s and
  /// edges are "traversed before" relation between pairs of nodes. The sort
  /// order is the topological sorting of the graph, with the original order of
  /// [nodes] used as the tie breaker.
  ///
  /// Whether a node is traversed before another node is determined by the
  /// vector that connects the two nodes' centers. If the vector "points to the
  /// right or down", defined as the [Offset.direction] being between `-pi/4`
  /// and `3*pi/4`), then the semantics node whose center is at the end of the
  /// vector is said to be traversed after.
  List<SemanticsNode> sortedWithinKnot() {
    if (nodes.length <= 1) {
      // Trivial knot. Nothing to do.
      return nodes;
    }
    final Map<int, SemanticsNode> nodeMap = <int, SemanticsNode>{};
    final Map<int, int> edges = <int, int>{};
2820
    for (final SemanticsNode node in nodes) {
2821 2822
      nodeMap[node.id] = node;
      final Offset center = _pointInParentCoordinates(node, node.rect.center);
2823
      for (final SemanticsNode nextNode in nodes) {
2824 2825 2826 2827 2828 2829 2830 2831 2832 2833 2834 2835 2836 2837 2838 2839 2840 2841 2842 2843 2844
        if (identical(node, nextNode) || edges[nextNode.id] == node.id) {
          // Skip self or when we've already established that the next node
          // points to current node.
          continue;
        }

        final Offset nextCenter = _pointInParentCoordinates(nextNode, nextNode.rect.center);
        final Offset centerDelta = nextCenter - center;
        // When centers coincide, direction is 0.0.
        final double direction = centerDelta.direction;
        final bool isLtrAndForward = textDirection == TextDirection.ltr &&
            -math.pi / 4 < direction && direction < 3 * math.pi / 4;
        final bool isRtlAndForward = textDirection == TextDirection.rtl &&
            (direction < -3 * math.pi / 4 || direction > 3 * math.pi / 4);
        if (isLtrAndForward || isRtlAndForward) {
          edges[node.id] = nextNode.id;
        }
      }
    }

    final List<int> sortedIds = <int>[];
2845
    final Set<int> visitedIds = <int>{};
2846 2847 2848 2849 2850 2851 2852 2853 2854 2855 2856 2857 2858 2859 2860 2861
    final List<SemanticsNode> startNodes = nodes.toList()..sort((SemanticsNode a, SemanticsNode b) {
      final Offset aTopLeft = _pointInParentCoordinates(a, a.rect.topLeft);
      final Offset bTopLeft = _pointInParentCoordinates(b, b.rect.topLeft);
      final int verticalDiff = aTopLeft.dy.compareTo(bTopLeft.dy);
      if (verticalDiff != 0) {
        return -verticalDiff;
      }
      return -aTopLeft.dx.compareTo(bTopLeft.dx);
    });

    void search(int id) {
      if (visitedIds.contains(id)) {
        return;
      }
      visitedIds.add(id);
      if (edges.containsKey(id)) {
2862
        search(edges[id]!);
2863 2864 2865 2866
      }
      sortedIds.add(id);
    }

2867
    startNodes.map<int>((SemanticsNode node) => node.id).forEach(search);
2868
    return sortedIds.map<SemanticsNode>((int id) => nodeMap[id]!).toList().reversed.toList();
2869 2870 2871
  }
}

2872 2873 2874 2875 2876
/// Converts `point` to the `node`'s parent's coordinate system.
Offset _pointInParentCoordinates(SemanticsNode node, Offset point) {
  if (node.transform == null) {
    return point;
  }
2877
  final Vector3 vector = Vector3(point.dx, point.dy, 0.0);
2878
  node.transform!.transform3(vector);
2879
  return Offset(vector.x, vector.y);
2880 2881 2882 2883 2884 2885 2886 2887 2888 2889 2890 2891 2892 2893 2894
}

/// Sorts `children` using the default sorting algorithm, and returns them as a
/// new list.
///
/// The algorithm first breaks up children into groups such that no two nodes
/// from different groups overlap vertically. These groups are sorted vertically
/// according to their [_SemanticsSortGroup.startOffset].
///
/// Within each group, the nodes are sorted using
/// [_SemanticsSortGroup.sortedWithinVerticalGroup].
///
/// For an illustration of the algorithm see http://bit.ly/flutter-default-traversal.
List<SemanticsNode> _childrenInDefaultOrder(List<SemanticsNode> children, TextDirection textDirection) {
  final List<_BoxEdge> edges = <_BoxEdge>[];
2895
  for (final SemanticsNode child in children) {
2896
    assert(child.rect.isFinite);
2897 2898
    // Using a small delta to shrink child rects removes overlapping cases.
    final Rect childRect = child.rect.deflate(0.1);
2899
    edges.add(_BoxEdge(
2900
      isLeadingEdge: true,
2901
      offset: _pointInParentCoordinates(child, childRect.topLeft).dy,
2902 2903
      node: child,
    ));
2904
    edges.add(_BoxEdge(
2905
      isLeadingEdge: false,
2906
      offset: _pointInParentCoordinates(child, childRect.bottomRight).dy,
2907 2908 2909 2910 2911 2912
      node: child,
    ));
  }
  edges.sort();

  final List<_SemanticsSortGroup> verticalGroups = <_SemanticsSortGroup>[];
2913
  _SemanticsSortGroup? group;
2914
  int depth = 0;
2915
  for (final _BoxEdge edge in edges) {
2916 2917
    if (edge.isLeadingEdge) {
      depth += 1;
2918
      group ??= _SemanticsSortGroup(
2919 2920 2921 2922 2923 2924 2925 2926
        startOffset: edge.offset,
        textDirection: textDirection,
      );
      group.nodes.add(edge.node);
    } else {
      depth -= 1;
    }
    if (depth == 0) {
2927
      verticalGroups.add(group!);
2928 2929 2930 2931 2932
      group = null;
    }
  }
  verticalGroups.sort();

2933 2934 2935
  return verticalGroups
    .expand((_SemanticsSortGroup group) => group.sortedWithinVerticalGroup())
    .toList();
2936 2937
}

2938 2939 2940 2941
/// The implementation of [Comparable] that implements the ordering of
/// [SemanticsNode]s in the accessibility traversal.
///
/// [SemanticsNode]s are sorted prior to sending them to the engine side.
2942
///
2943 2944
/// This implementation considers a [node]'s [sortKey] and its position within
/// the list of its siblings. [sortKey] takes precedence over position.
2945
class _TraversalSortNode implements Comparable<_TraversalSortNode> {
2946
  _TraversalSortNode({
2947
    required this.node,
2948
    this.sortKey,
2949
    required this.position,
2950
  })
2951
    : assert(node != null),
2952
      assert(position != null);
2953

2954 2955
  /// The node whose position this sort node determines.
  final SemanticsNode node;
2956

2957 2958 2959
  /// Determines the position of this node among its siblings.
  ///
  /// Sort keys take precedence over other attributes, such as
2960
  /// [position].
2961
  final SemanticsSortKey? sortKey;
2962

2963 2964 2965
  /// Position within the list of siblings as determined by the default sort
  /// order.
  final int position;
2966 2967 2968

  @override
  int compareTo(_TraversalSortNode other) {
2969
    if (sortKey == null || other.sortKey == null) {
2970
      return position - other.position;
2971
    }
2972
    return sortKey!.compareTo(other.sortKey!);
2973 2974 2975
  }
}

2976 2977 2978
/// Owns [SemanticsNode] objects and notifies listeners of changes to the
/// render tree semantics.
///
2979 2980 2981
/// To listen for semantic updates, call [PipelineOwner.ensureSemantics] to
/// obtain a [SemanticsHandle]. This will create a [SemanticsOwner] if
/// necessary.
2982
class SemanticsOwner extends ChangeNotifier {
2983
  final Set<SemanticsNode> _dirtyNodes = <SemanticsNode>{};
2984
  final Map<int, SemanticsNode> _nodes = <int, SemanticsNode>{};
2985
  final Set<SemanticsNode> _detachedNodes = <SemanticsNode>{};
2986

2987 2988 2989
  /// The root node of the semantics tree, if any.
  ///
  /// If the semantics tree is empty, returns null.
2990
  SemanticsNode? get rootSemanticsNode => _nodes[0];
2991

2992
  @override
2993 2994 2995 2996
  void dispose() {
    _dirtyNodes.clear();
    _nodes.clear();
    _detachedNodes.clear();
2997
    super.dispose();
2998
  }
2999

3000
  /// Update the semantics using [dart:ui.PlatformDispatcher.updateSemantics].
3001
  void sendSemanticsUpdate() {
Hixie's avatar
Hixie committed
3002 3003
    if (_dirtyNodes.isEmpty)
      return;
3004
    final Set<int> customSemanticsActionIds = <int>{};
3005
    final List<SemanticsNode> visitedNodes = <SemanticsNode>[];
3006
    while (_dirtyNodes.isNotEmpty) {
3007
      final List<SemanticsNode> localDirtyNodes = _dirtyNodes.where((SemanticsNode node) => !_detachedNodes.contains(node)).toList();
3008
      _dirtyNodes.clear();
3009
      _detachedNodes.clear();
3010 3011
      localDirtyNodes.sort((SemanticsNode a, SemanticsNode b) => a.depth - b.depth);
      visitedNodes.addAll(localDirtyNodes);
3012
      for (final SemanticsNode node in localDirtyNodes) {
3013
        assert(node._dirty);
3014
        assert(node.parent == null || !node.parent!.isPartOfNodeMerging || node.isMergedIntoParent);
3015
        if (node.isPartOfNodeMerging) {
3016
          assert(node.mergeAllDescendantsIntoThisNode || node.parent != null);
3017
          // if we're merged into our parent, make sure our parent is added to the dirty list
3018 3019
          if (node.parent != null && node.parent!.isPartOfNodeMerging) {
            node.parent!._markDirty(); // this can add the node to the dirty list
3020 3021
            node._dirty = false; // We don't want to send update for this node.
          }
3022
        }
3023
      }
3024
    }
3025
    visitedNodes.sort((SemanticsNode a, SemanticsNode b) => a.depth - b.depth);
3026
    final ui.SemanticsUpdateBuilder builder = SemanticsBinding.instance.createSemanticsUpdateBuilder();
3027
    for (final SemanticsNode node in visitedNodes) {
Hixie's avatar
Hixie committed
3028 3029 3030 3031 3032 3033 3034 3035 3036 3037 3038 3039
      assert(node.parent?._dirty != true); // could be null (no parent) or false (not dirty)
      // The _serialize() method marks the node as not dirty, and
      // recurses through the tree to do a deep serialization of all
      // contiguous dirty nodes. This means that when we return here,
      // it's quite possible that subsequent nodes are no longer
      // dirty. We skip these here.
      // We also skip any nodes that were reset and subsequently
      // dropped entirely (RenderObject.markNeedsSemanticsUpdate()
      // calls reset() on its SemanticsNode if onlyChanges isn't set,
      // which happens e.g. when the node is no longer contributing
      // semantics).
      if (node._dirty && node.attached)
3040
        node._addToUpdate(builder, customSemanticsActionIds);
Hixie's avatar
Hixie committed
3041 3042
    }
    _dirtyNodes.clear();
3043
    for (final int actionId in customSemanticsActionIds) {
3044
      final CustomSemanticsAction action = CustomSemanticsAction.getAction(actionId)!;
3045 3046
      builder.updateCustomAction(id: actionId, label: action.label, hint: action.hint, overrideId: action.action?.index ?? -1);
    }
3047
    SemanticsBinding.instance.platformDispatcher.updateSemantics(builder.build());
3048
    notifyListeners();
Hixie's avatar
Hixie committed
3049 3050
  }

3051
  SemanticsActionHandler? _getSemanticsActionHandlerForId(int id, SemanticsAction action) {
3052
    SemanticsNode? result = _nodes[id];
3053
    if (result != null && result.isPartOfNodeMerging && !result._canPerformAction(action)) {
Hixie's avatar
Hixie committed
3054
      result._visitDescendants((SemanticsNode node) {
3055
        if (node._canPerformAction(action)) {
Hixie's avatar
Hixie committed
3056 3057 3058 3059 3060 3061
          result = node;
          return false; // found node, abort walk
        }
        return true; // continue walk
      });
    }
3062
    if (result == null || !result!._canPerformAction(action))
Hixie's avatar
Hixie committed
3063
      return null;
3064
    return result!._actions[action];
Hixie's avatar
Hixie committed
3065 3066
  }

3067 3068 3069 3070
  /// Asks the [SemanticsNode] with the given id to perform the given action.
  ///
  /// If the [SemanticsNode] has not indicated that it can perform the action,
  /// this function does nothing.
3071 3072 3073
  ///
  /// If the given `action` requires arguments they need to be passed in via
  /// the `args` parameter.
3074
  void performAction(int id, SemanticsAction action, [ Object? args ]) {
3075
    assert(action != null);
3076
    final SemanticsActionHandler? handler = _getSemanticsActionHandlerForId(id, action);
3077
    if (handler != null) {
3078
      handler(args);
3079 3080 3081 3082
      return;
    }

    // Default actions if no [handler] was provided.
3083 3084
    if (action == SemanticsAction.showOnScreen && _nodes[id]!._showOnScreen != null)
      _nodes[id]!._showOnScreen!();
3085 3086
  }

3087
  SemanticsActionHandler? _getSemanticsActionHandlerForPosition(SemanticsNode node, Offset position, SemanticsAction action) {
3088
    if (node.transform != null) {
3089
      final Matrix4 inverse = Matrix4.identity();
3090
      if (inverse.copyInverse(node.transform!) == 0.0)
3091 3092 3093 3094 3095 3096
        return null;
      position = MatrixUtils.transformPoint(inverse, position);
    }
    if (!node.rect.contains(position))
      return null;
    if (node.mergeAllDescendantsIntoThisNode) {
3097
      SemanticsNode? result;
3098 3099 3100 3101 3102 3103 3104
      node._visitDescendants((SemanticsNode child) {
        if (child._canPerformAction(action)) {
          result = child;
          return false;
        }
        return true;
      });
3105
      return result?._actions[action];
3106 3107
    }
    if (node.hasChildren) {
3108
      for (final SemanticsNode child in node._children!.reversed) {
3109
        final SemanticsActionHandler? handler = _getSemanticsActionHandlerForPosition(child, position, action);
3110 3111 3112 3113
        if (handler != null)
          return handler;
      }
    }
3114
    return node._actions[action];
3115 3116
  }

3117
  /// Asks the [SemanticsNode] at the given position to perform the given action.
3118 3119 3120
  ///
  /// If the [SemanticsNode] has not indicated that it can perform the action,
  /// this function does nothing.
3121 3122 3123
  ///
  /// If the given `action` requires arguments they need to be passed in via
  /// the `args` parameter.
3124
  void performActionAt(Offset position, SemanticsAction action, [ Object? args ]) {
3125
    assert(action != null);
3126
    final SemanticsNode? node = rootSemanticsNode;
3127 3128
    if (node == null)
      return;
3129
    final SemanticsActionHandler? handler = _getSemanticsActionHandlerForPosition(node, position, action);
3130
    if (handler != null)
3131
      handler(args);
Hixie's avatar
Hixie committed
3132
  }
3133 3134

  @override
3135
  String toString() => describeIdentity(this);
Hixie's avatar
Hixie committed
3136
}
3137 3138 3139 3140

/// Describes the semantic information associated with the owning
/// [RenderObject].
///
3141
/// The information provided in the configuration is used to generate the
3142 3143 3144 3145 3146 3147 3148 3149 3150
/// semantics tree.
class SemanticsConfiguration {

  // SEMANTIC BOUNDARY BEHAVIOR

  /// Whether the [RenderObject] owner of this configuration wants to own its
  /// own [SemanticsNode].
  ///
  /// When set to true semantic information associated with the [RenderObject]
3151
  /// owner of this configuration or any of its descendants will not leak into
3152 3153 3154 3155 3156 3157 3158
  /// parents. The [SemanticsNode] generated out of this configuration will
  /// act as a boundary.
  ///
  /// Whether descendants of the owning [RenderObject] can add their semantic
  /// information to the [SemanticsNode] introduced by this configuration
  /// is controlled by [explicitChildNodes].
  ///
3159
  /// This has to be true if [isMergingSemanticsOfDescendants] is also true.
3160 3161 3162
  bool get isSemanticBoundary => _isSemanticBoundary;
  bool _isSemanticBoundary = false;
  set isSemanticBoundary(bool value) {
3163
    assert(!isMergingSemanticsOfDescendants || value);
3164 3165 3166 3167 3168 3169 3170 3171
    _isSemanticBoundary = value;
  }

  /// Whether the configuration forces all children of the owning [RenderObject]
  /// that want to contribute semantic information to the semantics tree to do
  /// so in the form of explicit [SemanticsNode]s.
  ///
  /// When set to false children of the owning [RenderObject] are allowed to
Dan Field's avatar
Dan Field committed
3172
  /// annotate [SemanticsNode]s of their parent with the semantic information
3173 3174 3175
  /// they want to contribute to the semantic tree.
  /// When set to true the only way for children of the owning [RenderObject]
  /// to contribute semantic information to the semantic tree is to introduce
Dan Field's avatar
Dan Field committed
3176
  /// new explicit [SemanticsNode]s to the tree.
3177 3178 3179 3180 3181
  ///
  /// This setting is often used in combination with [isSemanticBoundary] to
  /// create semantic boundaries that are either writable or not for children.
  bool explicitChildNodes = false;

3182
  /// Whether the owning [RenderObject] makes other [RenderObject]s previously
3183 3184 3185 3186 3187 3188 3189 3190 3191 3192 3193 3194
  /// painted within the same semantic boundary unreachable for accessibility
  /// purposes.
  ///
  /// If set to true, the semantic information for all siblings and cousins of
  /// this node, that are earlier in a depth-first pre-order traversal, are
  /// dropped from the semantics tree up until a semantic boundary (as defined
  /// by [isSemanticBoundary]) is reached.
  ///
  /// If [isSemanticBoundary] and [isBlockingSemanticsOfPreviouslyPaintedNodes]
  /// is set on the same node, all previously painted siblings and cousins up
  /// until the next ancestor that is a semantic boundary are dropped.
  ///
3195 3196
  /// Paint order as established by [RenderObject.visitChildrenForSemantics] is
  /// used to determine if a node is previous to this one.
3197 3198 3199
  bool isBlockingSemanticsOfPreviouslyPaintedNodes = false;

  // SEMANTIC ANNOTATIONS
Dan Field's avatar
Dan Field committed
3200
  // These will end up on [SemanticsNode]s generated from
3201 3202 3203 3204 3205 3206 3207 3208 3209 3210 3211 3212 3213
  // [SemanticsConfiguration]s.

  /// Whether this configuration is empty.
  ///
  /// An empty configuration doesn't contain any semantic information that it
  /// wants to contribute to the semantics tree.
  bool get hasBeenAnnotated => _hasBeenAnnotated;
  bool _hasBeenAnnotated = false;

  /// The actions (with associated action handlers) that this configuration
  /// would like to contribute to the semantics tree.
  ///
  /// See also:
3214
  ///
3215
  ///  * [addAction] to add an action.
3216
  final Map<SemanticsAction, SemanticsActionHandler> _actions = <SemanticsAction, SemanticsActionHandler>{};
3217

3218 3219
  int _actionsAsBits = 0;

3220 3221
  /// Adds an `action` to the semantics tree.
  ///
3222 3223
  /// The provided `handler` is called to respond to the user triggered
  /// `action`.
3224
  void _addAction(SemanticsAction action, SemanticsActionHandler handler) {
3225
    assert(handler != null);
3226
    _actions[action] = handler;
3227
    _actionsAsBits |= action.index;
3228 3229 3230
    _hasBeenAnnotated = true;
  }

3231 3232 3233 3234 3235 3236 3237
  /// Adds an `action` to the semantics tree, whose `handler` does not expect
  /// any arguments.
  ///
  /// The provided `handler` is called to respond to the user triggered
  /// `action`.
  void _addArgumentlessAction(SemanticsAction action, VoidCallback handler) {
    assert(handler != null);
3238
    _addAction(action, (Object? args) {
3239 3240 3241 3242 3243 3244 3245 3246 3247 3248 3249 3250 3251
      assert(args == null);
      handler();
    });
  }

  /// The handler for [SemanticsAction.tap].
  ///
  /// This is the semantic equivalent of a user briefly tapping the screen with
  /// the finger without moving it. For example, a button should implement this
  /// action.
  ///
  /// VoiceOver users on iOS and TalkBack users on Android can trigger this
  /// action by double-tapping the screen while an element is focused.
3252 3253 3254 3255 3256 3257 3258 3259 3260 3261 3262 3263
  ///
  /// On Android prior to Android Oreo a double-tap on the screen while an
  /// element with an [onTap] handler is focused will not call the registered
  /// handler. Instead, Android will simulate a pointer down and up event at the
  /// center of the focused element. Those pointer events will get dispatched
  /// just like a regular tap with TalkBack disabled would: The events will get
  /// processed by any [GestureDetector] listening for gestures in the center of
  /// the focused element. Therefore, to ensure that [onTap] handlers work
  /// properly on Android versions prior to Oreo, a [GestureDetector] with an
  /// onTap handler should always be wrapping an element that defines a
  /// semantic [onTap] handler. By default a [GestureDetector] will register its
  /// own semantic [onTap] handler that follows this principle.
3264 3265 3266 3267
  VoidCallback? get onTap => _onTap;
  VoidCallback? _onTap;
  set onTap(VoidCallback? value) {
    _addArgumentlessAction(SemanticsAction.tap, value!);
3268 3269 3270 3271 3272 3273 3274 3275 3276 3277 3278
    _onTap = value;
  }

  /// The handler for [SemanticsAction.longPress].
  ///
  /// This is the semantic equivalent of a user pressing and holding the screen
  /// with the finger for a few seconds without moving it.
  ///
  /// VoiceOver users on iOS and TalkBack users on Android can trigger this
  /// action by double-tapping the screen without lifting the finger after the
  /// second tap.
3279 3280 3281 3282
  VoidCallback? get onLongPress => _onLongPress;
  VoidCallback? _onLongPress;
  set onLongPress(VoidCallback? value) {
    _addArgumentlessAction(SemanticsAction.longPress, value!);
3283 3284 3285 3286 3287 3288 3289 3290 3291 3292 3293 3294 3295 3296
    _onLongPress = value;
  }

  /// The handler for [SemanticsAction.scrollLeft].
  ///
  /// This is the semantic equivalent of a user moving their finger across the
  /// screen from right to left. It should be recognized by controls that are
  /// horizontally scrollable.
  ///
  /// VoiceOver users on iOS can trigger this action by swiping left with three
  /// fingers. TalkBack users on Android can trigger this action by swiping
  /// right and then left in one motion path. On Android, [onScrollUp] and
  /// [onScrollLeft] share the same gesture. Therefore, only on of them should
  /// be provided.
3297 3298 3299 3300
  VoidCallback? get onScrollLeft => _onScrollLeft;
  VoidCallback? _onScrollLeft;
  set onScrollLeft(VoidCallback? value) {
    _addArgumentlessAction(SemanticsAction.scrollLeft, value!);
3301 3302 3303
    _onScrollLeft = value;
  }

3304 3305 3306 3307 3308 3309 3310
  /// The handler for [SemanticsAction.dismiss].
  ///
  /// This is a request to dismiss the currently focused node.
  ///
  /// TalkBack users on Android can trigger this action in the local context
  /// menu, and VoiceOver users on iOS can trigger this action with a standard
  /// gesture or menu option.
3311 3312 3313 3314
  VoidCallback? get onDismiss => _onDismiss;
  VoidCallback? _onDismiss;
  set onDismiss(VoidCallback? value) {
    _addArgumentlessAction(SemanticsAction.dismiss, value!);
3315 3316 3317
    _onDismiss = value;
  }

3318 3319 3320 3321 3322 3323 3324 3325
  /// The handler for [SemanticsAction.scrollRight].
  ///
  /// This is the semantic equivalent of a user moving their finger across the
  /// screen from left to right. It should be recognized by controls that are
  /// horizontally scrollable.
  ///
  /// VoiceOver users on iOS can trigger this action by swiping right with three
  /// fingers. TalkBack users on Android can trigger this action by swiping
3326
  /// left and then right in one motion path. On Android, [onScrollDown] and
3327 3328
  /// [onScrollRight] share the same gesture. Therefore, only on of them should
  /// be provided.
3329 3330 3331 3332
  VoidCallback? get onScrollRight => _onScrollRight;
  VoidCallback? _onScrollRight;
  set onScrollRight(VoidCallback? value) {
    _addArgumentlessAction(SemanticsAction.scrollRight, value!);
3333 3334 3335 3336 3337 3338 3339 3340 3341 3342 3343 3344 3345 3346
    _onScrollRight = value;
  }

  /// The handler for [SemanticsAction.scrollUp].
  ///
  /// This is the semantic equivalent of a user moving their finger across the
  /// screen from bottom to top. It should be recognized by controls that are
  /// vertically scrollable.
  ///
  /// VoiceOver users on iOS can trigger this action by swiping up with three
  /// fingers. TalkBack users on Android can trigger this action by swiping
  /// right and then left in one motion path. On Android, [onScrollUp] and
  /// [onScrollLeft] share the same gesture. Therefore, only on of them should
  /// be provided.
3347 3348 3349 3350
  VoidCallback? get onScrollUp => _onScrollUp;
  VoidCallback? _onScrollUp;
  set onScrollUp(VoidCallback? value) {
    _addArgumentlessAction(SemanticsAction.scrollUp, value!);
3351 3352 3353 3354 3355 3356 3357 3358 3359 3360 3361 3362 3363 3364
    _onScrollUp = value;
  }

  /// The handler for [SemanticsAction.scrollDown].
  ///
  /// This is the semantic equivalent of a user moving their finger across the
  /// screen from top to bottom. It should be recognized by controls that are
  /// vertically scrollable.
  ///
  /// VoiceOver users on iOS can trigger this action by swiping down with three
  /// fingers. TalkBack users on Android can trigger this action by swiping
  /// left and then right in one motion path. On Android, [onScrollDown] and
  /// [onScrollRight] share the same gesture. Therefore, only on of them should
  /// be provided.
3365 3366 3367 3368
  VoidCallback? get onScrollDown => _onScrollDown;
  VoidCallback? _onScrollDown;
  set onScrollDown(VoidCallback? value) {
    _addArgumentlessAction(SemanticsAction.scrollDown, value!);
3369 3370 3371 3372 3373 3374 3375 3376
    _onScrollDown = value;
  }

  /// The handler for [SemanticsAction.increase].
  ///
  /// This is a request to increase the value represented by the widget. For
  /// example, this action might be recognized by a slider control.
  ///
Janice Collins's avatar
Janice Collins committed
3377 3378 3379
  /// If [this.value] is set, [increasedValue] must also be provided and
  /// [onIncrease] must ensure that [this.value] will be set to
  /// [increasedValue].
3380 3381 3382 3383
  ///
  /// VoiceOver users on iOS can trigger this action by swiping up with one
  /// finger. TalkBack users on Android can trigger this action by pressing the
  /// volume up button.
3384 3385 3386 3387
  VoidCallback? get onIncrease => _onIncrease;
  VoidCallback? _onIncrease;
  set onIncrease(VoidCallback? value) {
    _addArgumentlessAction(SemanticsAction.increase, value!);
3388 3389 3390 3391 3392 3393 3394 3395
    _onIncrease = value;
  }

  /// The handler for [SemanticsAction.decrease].
  ///
  /// This is a request to decrease the value represented by the widget. For
  /// example, this action might be recognized by a slider control.
  ///
Janice Collins's avatar
Janice Collins committed
3396 3397 3398
  /// If [this.value] is set, [decreasedValue] must also be provided and
  /// [onDecrease] must ensure that [this.value] will be set to
  /// [decreasedValue].
3399 3400 3401 3402
  ///
  /// VoiceOver users on iOS can trigger this action by swiping down with one
  /// finger. TalkBack users on Android can trigger this action by pressing the
  /// volume down button.
3403 3404 3405 3406
  VoidCallback? get onDecrease => _onDecrease;
  VoidCallback? _onDecrease;
  set onDecrease(VoidCallback? value) {
    _addArgumentlessAction(SemanticsAction.decrease, value!);
3407
    _onDecrease = value;
3408 3409 3410 3411 3412 3413 3414 3415
  }

  /// The handler for [SemanticsAction.copy].
  ///
  /// This is a request to copy the current selection to the clipboard.
  ///
  /// TalkBack users on Android can trigger this action from the local context
  /// menu of a text field, for example.
3416 3417 3418 3419
  VoidCallback? get onCopy => _onCopy;
  VoidCallback? _onCopy;
  set onCopy(VoidCallback? value) {
    _addArgumentlessAction(SemanticsAction.copy, value!);
3420 3421 3422 3423 3424 3425 3426 3427 3428 3429
    _onCopy = value;
  }

  /// The handler for [SemanticsAction.cut].
  ///
  /// This is a request to cut the current selection and place it in the
  /// clipboard.
  ///
  /// TalkBack users on Android can trigger this action from the local context
  /// menu of a text field, for example.
3430 3431 3432 3433
  VoidCallback? get onCut => _onCut;
  VoidCallback? _onCut;
  set onCut(VoidCallback? value) {
    _addArgumentlessAction(SemanticsAction.cut, value!);
3434 3435 3436 3437 3438 3439 3440 3441 3442
    _onCut = value;
  }

  /// The handler for [SemanticsAction.paste].
  ///
  /// This is a request to paste the current content of the clipboard.
  ///
  /// TalkBack users on Android can trigger this action from the local context
  /// menu of a text field, for example.
3443 3444 3445 3446
  VoidCallback? get onPaste => _onPaste;
  VoidCallback? _onPaste;
  set onPaste(VoidCallback? value) {
    _addArgumentlessAction(SemanticsAction.paste, value!);
3447
    _onPaste = value;
3448 3449 3450 3451 3452 3453 3454 3455 3456 3457 3458
  }

  /// The handler for [SemanticsAction.showOnScreen].
  ///
  /// A request to fully show the semantics node on screen. For example, this
  /// action might be send to a node in a scrollable list that is partially off
  /// screen to bring it on screen.
  ///
  /// For elements in a scrollable list the framework provides a default
  /// implementation for this action and it is not advised to provide a
  /// custom one via this setter.
3459 3460 3461 3462
  VoidCallback? get onShowOnScreen => _onShowOnScreen;
  VoidCallback? _onShowOnScreen;
  set onShowOnScreen(VoidCallback? value) {
    _addArgumentlessAction(SemanticsAction.showOnScreen, value!);
3463 3464 3465
    _onShowOnScreen = value;
  }

3466
  /// The handler for [SemanticsAction.moveCursorForwardByCharacter].
3467 3468 3469 3470 3471 3472
  ///
  /// This handler is invoked when the user wants to move the cursor in a
  /// text field forward by one character.
  ///
  /// TalkBack users can trigger this by pressing the volume up key while the
  /// input focus is in a text field.
3473 3474 3475
  MoveCursorHandler? get onMoveCursorForwardByCharacter => _onMoveCursorForwardByCharacter;
  MoveCursorHandler? _onMoveCursorForwardByCharacter;
  set onMoveCursorForwardByCharacter(MoveCursorHandler? value) {
3476
    assert(value != null);
3477 3478
    _addAction(SemanticsAction.moveCursorForwardByCharacter, (Object? args) {
      final bool extentSelection = args! as bool;
3479
      value!(extentSelection);
3480 3481 3482 3483
    });
    _onMoveCursorForwardByCharacter = value;
  }

3484
  /// The handler for [SemanticsAction.moveCursorBackwardByCharacter].
3485 3486 3487 3488 3489 3490
  ///
  /// This handler is invoked when the user wants to move the cursor in a
  /// text field backward by one character.
  ///
  /// TalkBack users can trigger this by pressing the volume down key while the
  /// input focus is in a text field.
3491 3492 3493
  MoveCursorHandler? get onMoveCursorBackwardByCharacter => _onMoveCursorBackwardByCharacter;
  MoveCursorHandler? _onMoveCursorBackwardByCharacter;
  set onMoveCursorBackwardByCharacter(MoveCursorHandler? value) {
3494
    assert(value != null);
3495 3496
    _addAction(SemanticsAction.moveCursorBackwardByCharacter, (Object? args) {
      final bool extentSelection = args! as bool;
3497
      value!(extentSelection);
3498 3499 3500 3501
    });
    _onMoveCursorBackwardByCharacter = value;
  }

3502
  /// The handler for [SemanticsAction.moveCursorForwardByWord].
3503 3504 3505 3506 3507 3508
  ///
  /// This handler is invoked when the user wants to move the cursor in a
  /// text field backward by one word.
  ///
  /// TalkBack users can trigger this by pressing the volume down key while the
  /// input focus is in a text field.
3509 3510 3511
  MoveCursorHandler? get onMoveCursorForwardByWord => _onMoveCursorForwardByWord;
  MoveCursorHandler? _onMoveCursorForwardByWord;
  set onMoveCursorForwardByWord(MoveCursorHandler? value) {
3512
    assert(value != null);
3513 3514
    _addAction(SemanticsAction.moveCursorForwardByWord, (Object? args) {
      final bool extentSelection = args! as bool;
3515
      value!(extentSelection);
3516 3517 3518 3519
    });
    _onMoveCursorForwardByCharacter = value;
  }

3520
  /// The handler for [SemanticsAction.moveCursorBackwardByWord].
3521 3522 3523 3524 3525 3526
  ///
  /// This handler is invoked when the user wants to move the cursor in a
  /// text field backward by one word.
  ///
  /// TalkBack users can trigger this by pressing the volume down key while the
  /// input focus is in a text field.
3527 3528 3529
  MoveCursorHandler? get onMoveCursorBackwardByWord => _onMoveCursorBackwardByWord;
  MoveCursorHandler? _onMoveCursorBackwardByWord;
  set onMoveCursorBackwardByWord(MoveCursorHandler? value) {
3530
    assert(value != null);
3531 3532
    _addAction(SemanticsAction.moveCursorBackwardByWord, (Object? args) {
      final bool extentSelection = args! as bool;
3533
      value!(extentSelection);
3534 3535 3536 3537
    });
    _onMoveCursorBackwardByCharacter = value;
  }

3538 3539 3540 3541 3542 3543 3544
  /// The handler for [SemanticsAction.setSelection].
  ///
  /// This handler is invoked when the user either wants to change the currently
  /// selected text in a text field or change the position of the cursor.
  ///
  /// TalkBack users can trigger this handler by selecting "Move cursor to
  /// beginning/end" or "Select all" from the local context menu.
3545 3546 3547
  SetSelectionHandler? get onSetSelection => _onSetSelection;
  SetSelectionHandler? _onSetSelection;
  set onSetSelection(SetSelectionHandler? value) {
3548
    assert(value != null);
3549
    _addAction(SemanticsAction.setSelection, (Object? args) {
3550
      assert(args != null && args is Map);
3551
      final Map<String, int> selection = (args! as Map<dynamic, dynamic>).cast<String, int>();
3552
      assert(selection != null && selection['base'] != null && selection['extent'] != null);
3553 3554 3555
      value!(TextSelection(
        baseOffset: selection['base']!,
        extentOffset: selection['extent']!,
3556 3557 3558 3559 3560
      ));
    });
    _onSetSelection = value;
  }

3561 3562 3563 3564 3565 3566 3567 3568 3569 3570 3571 3572 3573 3574 3575 3576 3577 3578 3579
  /// The handler for [SemanticsAction.setText].
  ///
  /// This handler is invoked when the user wants to replace the current text in
  /// the text field with a new text.
  ///
  /// Voice access users can trigger this handler by speaking "type <text>" to
  /// their Android devices.
  SetTextHandler? get onSetText => _onSetText;
  SetTextHandler? _onSetText;
  set onSetText(SetTextHandler? value) {
    assert(value != null);
    _addAction(SemanticsAction.setText, (Object? args) {
      assert(args != null && args is String);
      final String text = args! as String;
      value!(text);
    });
    _onSetText = value;
  }

3580 3581 3582 3583 3584 3585 3586 3587 3588 3589 3590 3591 3592 3593 3594
  /// The handler for [SemanticsAction.didGainAccessibilityFocus].
  ///
  /// This handler is invoked when the node annotated with this handler gains
  /// the accessibility focus. The accessibility focus is the
  /// green (on Android with TalkBack) or black (on iOS with VoiceOver)
  /// rectangle shown on screen to indicate what element an accessibility
  /// user is currently interacting with.
  ///
  /// The accessibility focus is different from the input focus. The input focus
  /// is usually held by the element that currently responds to keyboard inputs.
  /// Accessibility focus and input focus can be held by two different nodes!
  ///
  /// See also:
  ///
  ///  * [onDidLoseAccessibilityFocus], which is invoked when the accessibility
3595 3596
  ///    focus is removed from the node.
  ///  * [FocusNode], [FocusScope], [FocusManager], which manage the input focus.
3597 3598 3599 3600
  VoidCallback? get onDidGainAccessibilityFocus => _onDidGainAccessibilityFocus;
  VoidCallback? _onDidGainAccessibilityFocus;
  set onDidGainAccessibilityFocus(VoidCallback? value) {
    _addArgumentlessAction(SemanticsAction.didGainAccessibilityFocus, value!);
3601 3602 3603 3604 3605 3606 3607 3608 3609 3610 3611 3612 3613 3614 3615 3616 3617 3618
    _onDidGainAccessibilityFocus = value;
  }

  /// The handler for [SemanticsAction.didLoseAccessibilityFocus].
  ///
  /// This handler is invoked when the node annotated with this handler
  /// loses the accessibility focus. The accessibility focus is
  /// the green (on Android with TalkBack) or black (on iOS with VoiceOver)
  /// rectangle shown on screen to indicate what element an accessibility
  /// user is currently interacting with.
  ///
  /// The accessibility focus is different from the input focus. The input focus
  /// is usually held by the element that currently responds to keyboard inputs.
  /// Accessibility focus and input focus can be held by two different nodes!
  ///
  /// See also:
  ///
  ///  * [onDidGainAccessibilityFocus], which is invoked when the node gains
3619 3620
  ///    accessibility focus.
  ///  * [FocusNode], [FocusScope], [FocusManager], which manage the input focus.
3621 3622 3623 3624
  VoidCallback? get onDidLoseAccessibilityFocus => _onDidLoseAccessibilityFocus;
  VoidCallback? _onDidLoseAccessibilityFocus;
  set onDidLoseAccessibilityFocus(VoidCallback? value) {
    _addArgumentlessAction(SemanticsAction.didLoseAccessibilityFocus, value!);
3625 3626 3627
    _onDidLoseAccessibilityFocus = value;
  }

3628 3629
  /// Returns the action handler registered for [action] or null if none was
  /// registered.
3630
  SemanticsActionHandler? getActionHandler(SemanticsAction action) => _actions[action];
3631

3632 3633
  /// Determines the position of this node among its siblings in the traversal
  /// sort order.
3634
  ///
3635 3636 3637
  /// This is used to describe the order in which the semantic node should be
  /// traversed by the accessibility services on the platform (e.g. VoiceOver
  /// on iOS and TalkBack on Android).
3638
  ///
3639 3640 3641 3642
  /// Whether this sort key has an effect on the [SemanticsNode] sort order is
  /// subject to how this configuration is used. For example, the [absorb]
  /// method may decide to not use this key when it combines multiple
  /// [SemanticsConfiguration] objects.
3643 3644 3645
  SemanticsSortKey? get sortKey => _sortKey;
  SemanticsSortKey? _sortKey;
  set sortKey(SemanticsSortKey? value) {
3646
    assert(value != null);
3647
    _sortKey = value;
3648 3649 3650
    _hasBeenAnnotated = true;
  }

3651 3652 3653 3654 3655 3656
  /// The index of this node within the parent's list of semantic children.
  ///
  /// This includes all semantic nodes, not just those currently in the
  /// child list. For example, if a scrollable has five children but the first
  /// two are not visible (and thus not included in the list of children), then
  /// the index of the last node will still be 4.
3657 3658 3659
  int? get indexInParent => _indexInParent;
  int? _indexInParent;
  set indexInParent(int? value) {
3660 3661 3662 3663 3664 3665 3666 3667
    _indexInParent = value;
    _hasBeenAnnotated = true;
  }

  /// The total number of scrollable children that contribute to semantics.
  ///
  /// If the number of children are unknown or unbounded, this value will be
  /// null.
3668 3669 3670
  int? get scrollChildCount => _scrollChildCount;
  int? _scrollChildCount;
  set scrollChildCount(int? value) {
3671 3672 3673 3674 3675 3676 3677 3678
    if (value == scrollChildCount)
      return;
    _scrollChildCount = value;
    _hasBeenAnnotated = true;
  }

  /// The index of the first visible scrollable child that contributes to
  /// semantics.
3679 3680 3681
  int? get scrollIndex => _scrollIndex;
  int? _scrollIndex;
  set scrollIndex(int? value) {
3682 3683 3684 3685 3686 3687
    if (value == scrollIndex)
      return;
    _scrollIndex = value;
    _hasBeenAnnotated = true;
  }

3688 3689
  /// The id of the platform view, whose semantics nodes will be added as
  /// children to this node.
3690 3691 3692
  int? get platformViewId => _platformViewId;
  int? _platformViewId;
  set platformViewId(int? value) {
3693 3694 3695 3696 3697
    if (value == platformViewId)
      return;
    _platformViewId = value;
    _hasBeenAnnotated = true;
  }
3698

3699 3700 3701 3702 3703 3704 3705 3706
  /// The maximum number of characters that can be entered into an editable
  /// text field.
  ///
  /// For the purpose of this function a character is defined as one Unicode
  /// scalar value.
  ///
  /// This should only be set when [isTextField] is true. Defaults to null,
  /// which means no limit is imposed on the text field.
3707 3708 3709
  int? get maxValueLength => _maxValueLength;
  int? _maxValueLength;
  set maxValueLength(int? value) {
3710 3711 3712 3713 3714 3715 3716 3717 3718 3719 3720 3721 3722 3723
    if (value == maxValueLength)
      return;
    _maxValueLength = value;
    _hasBeenAnnotated = true;
  }

  /// The current number of characters that have been entered into an editable
  /// text field.
  ///
  /// For the purpose of this function a character is defined as one Unicode
  /// scalar value.
  ///
  /// This should only be set when [isTextField] is true. Must be set when
  /// [maxValueLength] is set.
3724 3725 3726
  int? get currentValueLength => _currentValueLength;
  int? _currentValueLength;
  set currentValueLength(int? value) {
3727 3728 3729 3730 3731 3732
    if (value == currentValueLength)
      return;
    _currentValueLength = value;
    _hasBeenAnnotated = true;
  }

3733 3734 3735 3736 3737 3738
  /// Whether the semantic information provided by the owning [RenderObject] and
  /// all of its descendants should be treated as one logical entity.
  ///
  /// If set to true, the descendants of the owning [RenderObject]'s
  /// [SemanticsNode] will merge their semantic information into the
  /// [SemanticsNode] representing the owning [RenderObject].
3739 3740
  ///
  /// Setting this to true requires that [isSemanticBoundary] is also true.
3741 3742 3743
  bool get isMergingSemanticsOfDescendants => _isMergingSemanticsOfDescendants;
  bool _isMergingSemanticsOfDescendants = false;
  set isMergingSemanticsOfDescendants(bool value) {
3744
    assert(isSemanticBoundary);
3745 3746 3747 3748
    _isMergingSemanticsOfDescendants = value;
    _hasBeenAnnotated = true;
  }

3749
  /// The handlers for each supported [CustomSemanticsAction].
3750
  ///
3751
  /// Whenever a custom accessibility action is added to a node, the action
3752
  /// [SemanticsAction.customAction] is automatically added. A handler is
3753 3754 3755 3756 3757 3758 3759 3760 3761 3762 3763
  /// created which uses the passed argument to lookup the custom action
  /// handler from this map and invoke it, if present.
  Map<CustomSemanticsAction, VoidCallback> get customSemanticsActions => _customSemanticsActions;
  Map<CustomSemanticsAction, VoidCallback> _customSemanticsActions = <CustomSemanticsAction, VoidCallback>{};
  set customSemanticsActions(Map<CustomSemanticsAction, VoidCallback> value) {
    _hasBeenAnnotated = true;
    _actionsAsBits |= SemanticsAction.customAction.index;
    _customSemanticsActions = value;
    _actions[SemanticsAction.customAction] = _onCustomSemanticsAction;
  }

3764 3765
  void _onCustomSemanticsAction(Object? args) {
    final CustomSemanticsAction? action = CustomSemanticsAction.getAction(args! as int);
3766 3767
    if (action == null)
      return;
3768
    final VoidCallback? callback = _customSemanticsActions[action];
3769 3770 3771 3772
    if (callback != null)
      callback();
  }

3773 3774
  /// A textual description of the owning [RenderObject].
  ///
3775
  /// Setting this attribute will override the [attributedLabel].
3776
  ///
3777
  /// The reading direction is given by [textDirection].
3778 3779
  ///
  /// See also:
3780 3781
  ///
  ///  * [attributedLabel], which is the [AttributedString] of this property.
3782
  String get label => _attributedLabel.string;
3783
  set label(String label) {
3784
    assert(label != null);
3785 3786 3787 3788 3789 3790 3791 3792 3793 3794 3795 3796 3797 3798
    _attributedLabel = AttributedString(label);
    _hasBeenAnnotated = true;
  }

  /// A textual description of the owning [RenderObject] in [AttributedString]
  /// format.
  ///
  /// On iOS this is used for the `accessibilityAttributedLabel` property
  /// defined in the `UIAccessibility` Protocol. On Android it is concatenated
  /// together with [attributedValue] and [attributedHint] in the following
  /// order: [attributedValue], [attributedLabel], [attributedHint]. The
  /// concatenated value is then used as the `Text` description.
  ///
  /// The reading direction is given by [textDirection].
3799 3800 3801 3802
  ///
  /// See also:
  ///
  ///  * [label], which is the raw text of this property.
3803 3804 3805 3806
  AttributedString get attributedLabel => _attributedLabel;
  AttributedString _attributedLabel = AttributedString('');
  set attributedLabel(AttributedString attributedLabel) {
    _attributedLabel = attributedLabel;
3807 3808 3809
    _hasBeenAnnotated = true;
  }

3810 3811
  /// A textual description for the current value of the owning [RenderObject].
  ///
3812
  /// Setting this attribute will override the [attributedValue].
3813
  ///
3814 3815 3816
  /// The reading direction is given by [textDirection].
  ///
  /// See also:
3817
  ///
3818
  ///  * [attributedValue], which is the [AttributedString] of this property.
3819
  ///  * [increasedValue] and [attributedIncreasedValue], which describe what
3820
  ///    [value] will be after performing [SemanticsAction.increase].
3821 3822
  ///  * [decreasedValue] and [attributedDecreasedValue], which describe what
  ///    [value] will be after performing [SemanticsAction.decrease].
3823
  String get value => _attributedValue.string;
3824
  set value(String value) {
3825
    assert(value != null);
3826 3827 3828 3829 3830 3831 3832 3833 3834 3835 3836 3837 3838 3839 3840 3841 3842
    _attributedValue = AttributedString(value);
    _hasBeenAnnotated = true;
  }

  /// A textual description for the current value of the owning [RenderObject]
  /// in [AttributedString] format.
  ///
  /// On iOS this is used for the `accessibilityAttributedValue` property
  /// defined in the `UIAccessibility` Protocol. On Android it is concatenated
  /// together with [attributedLabel] and [attributedHint] in the following
  /// order: [attributedValue], [attributedLabel], [attributedHint]. The
  /// concatenated value is then used as the `Text` description.
  ///
  /// The reading direction is given by [textDirection].
  ///
  /// See also:
  ///
3843 3844
  ///  * [value], which is the raw text of this property.
  ///  * [attributedIncreasedValue], which describes what [value] will be after
3845
  ///    performing [SemanticsAction.increase].
3846 3847
  ///  * [attributedDecreasedValue], which describes what [value] will be after
  ///    performing [SemanticsAction.decrease].
3848 3849 3850 3851
  AttributedString get attributedValue => _attributedValue;
  AttributedString _attributedValue = AttributedString('');
  set attributedValue(AttributedString attributedValue) {
    _attributedValue = attributedValue;
3852 3853 3854
    _hasBeenAnnotated = true;
  }

3855
  /// The value that [value] will have after performing a
3856
  /// [SemanticsAction.increase] action.
3857
  ///
3858
  /// Setting this attribute will override the [attributedIncreasedValue].
3859
  ///
3860 3861
  /// One of the [attributedIncreasedValue] or [increasedValue] must be set if
  /// a handler for [SemanticsAction.increase] is provided and one of the
3862
  /// [value] or [attributedValue] is set.
3863 3864
  ///
  /// The reading direction is given by [textDirection].
3865 3866 3867 3868 3869 3870 3871 3872
  ///
  /// See also:
  ///
  ///  * [attributedIncreasedValue], which is the [AttributedString] of this property.
  String get increasedValue => _attributedIncreasedValue.string;
  set increasedValue(String increasedValue) {
    assert(increasedValue != null);
    _attributedIncreasedValue = AttributedString(increasedValue);
3873 3874 3875 3876
    _hasBeenAnnotated = true;
  }

  /// The value that [value] will have after performing a
3877
  /// [SemanticsAction.increase] action in [AttributedString] format.
3878
  ///
3879 3880
  /// One of the [attributedIncreasedValue] or [increasedValue] must be set if
  /// a handler for [SemanticsAction.increase] is provided and one of the
3881 3882 3883
  /// [value] or [attributedValue] is set.
  ///
  /// The reading direction is given by [textDirection].
3884 3885 3886 3887 3888 3889 3890 3891
  ///
  /// See also:
  ///
  ///  * [increasedValue], which is the raw text of this property.
  AttributedString get attributedIncreasedValue => _attributedIncreasedValue;
  AttributedString _attributedIncreasedValue = AttributedString('');
  set attributedIncreasedValue(AttributedString attributedIncreasedValue) {
    _attributedIncreasedValue = attributedIncreasedValue;
3892 3893 3894 3895
    _hasBeenAnnotated = true;
  }

  /// The value that [value] will have after performing a
3896
  /// [SemanticsAction.decrease] action.
3897
  ///
3898
  /// Setting this attribute will override the [attributedDecreasedValue].
3899
  ///
3900 3901
  /// One of the [attributedDecreasedValue] or [decreasedValue] must be set if
  /// a handler for [SemanticsAction.decrease] is provided and one of the
3902
  /// [value] or [attributedValue] is set.
3903 3904
  ///
  /// The reading direction is given by [textDirection].
3905 3906 3907 3908 3909 3910
  ///
  ///  * [attributedDecreasedValue], which is the [AttributedString] of this property.
  String get decreasedValue => _attributedDecreasedValue.string;
  set decreasedValue(String decreasedValue) {
    assert(decreasedValue != null);
    _attributedDecreasedValue = AttributedString(decreasedValue);
3911 3912 3913 3914
    _hasBeenAnnotated = true;
  }

  /// The value that [value] will have after performing a
3915
  /// [SemanticsAction.decrease] action in [AttributedString] format.
3916
  ///
3917 3918
  /// One of the [attributedDecreasedValue] or [decreasedValue] must be set if
  /// a handler for [SemanticsAction.decrease] is provided and one of the
3919 3920 3921
  /// [value] or [attributedValue] is set.
  ///
  /// The reading direction is given by [textDirection].
3922 3923 3924 3925 3926 3927 3928 3929
  ///
  /// See also:
  ///
  ///  * [decreasedValue], which is the raw text of this property.
  AttributedString get attributedDecreasedValue => _attributedDecreasedValue;
  AttributedString _attributedDecreasedValue = AttributedString('');
  set attributedDecreasedValue(AttributedString attributedDecreasedValue) {
    _attributedDecreasedValue = attributedDecreasedValue;
3930 3931 3932
    _hasBeenAnnotated = true;
  }

3933 3934
  /// A brief description of the result of performing an action on this node.
  ///
3935
  /// Setting this attribute will override the [attributedHint].
3936
  ///
3937
  /// The reading direction is given by [textDirection].
3938 3939
  ///
  /// See also:
3940 3941
  ///
  ///  * [attributedHint], which is the [AttributedString] of this property.
3942
  String get hint => _attributedHint.string;
3943
  set hint(String hint) {
3944
    assert(hint != null);
3945 3946 3947 3948 3949 3950 3951 3952 3953 3954 3955 3956 3957 3958
    _attributedHint = AttributedString(hint);
    _hasBeenAnnotated = true;
  }

  /// A brief description of the result of performing an action on this node in
  /// [AttributedString] format.
  ///
  /// On iOS this is used for the `accessibilityAttributedHint` property
  /// defined in the `UIAccessibility` Protocol. On Android it is concatenated
  /// together with [attributedLabel] and [attributedValue] in the following
  /// order: [attributedValue], [attributedLabel], [attributedHint]. The
  /// concatenated value is then used as the `Text` description.
  ///
  /// The reading direction is given by [textDirection].
3959 3960 3961 3962
  ///
  /// See also:
  ///
  ///  * [hint], which is the raw text of this property.
3963 3964 3965 3966
  AttributedString get attributedHint => _attributedHint;
  AttributedString _attributedHint = AttributedString('');
  set attributedHint(AttributedString attributedHint) {
    _attributedHint = attributedHint;
3967 3968 3969
    _hasBeenAnnotated = true;
  }

3970 3971
  /// Provides hint values which override the default hints on supported
  /// platforms.
3972 3973 3974
  SemanticsHintOverrides? get hintOverrides => _hintOverrides;
  SemanticsHintOverrides? _hintOverrides;
  set hintOverrides(SemanticsHintOverrides? value) {
3975 3976 3977 3978 3979 3980
    if (value == null)
      return;
    _hintOverrides = value;
    _hasBeenAnnotated = true;
  }

3981 3982 3983 3984 3985 3986 3987 3988 3989 3990 3991 3992 3993 3994 3995 3996 3997 3998 3999 4000 4001 4002 4003 4004 4005 4006 4007 4008 4009 4010
  /// The elevation in z-direction at which the owning [RenderObject] is
  /// located relative to its parent.
  double get elevation => _elevation;
  double _elevation = 0.0;
  set elevation(double value) {
    assert(value != null && value >= 0.0);
    if (value == _elevation) {
      return;
    }
    _elevation = value;
    _hasBeenAnnotated = true;
  }

  /// The extend that the owning [RenderObject] occupies in z-direction starting
  /// at [elevation].
  ///
  /// It's extremely rare to set this value directly. Instead, it is calculated
  /// implicitly when other [SemanticsConfiguration]s are merged into this one
  /// via [absorb].
  double get thickness => _thickness;
  double _thickness = 0.0;
  set thickness(double value) {
    assert(value != null && value >= 0.0);
    if (value == _thickness) {
      return;
    }
    _thickness = value;
    _hasBeenAnnotated = true;
  }

4011 4012
  /// Whether the semantics node is the root of a subtree for which values
  /// should be announced.
4013
  ///
4014
  /// See also:
4015
  ///
4016 4017 4018 4019 4020 4021 4022
  ///  * [SemanticsFlag.scopesRoute], for a full description of route scoping.
  bool get scopesRoute => _hasFlag(SemanticsFlag.scopesRoute);
  set scopesRoute(bool value) {
    _setFlag(SemanticsFlag.scopesRoute, value);
  }

  /// Whether the semantics node contains the label of a route.
4023
  ///
4024
  /// See also:
4025
  ///
4026 4027 4028 4029 4030 4031
  ///  * [SemanticsFlag.namesRoute], for a full description of route naming.
  bool get namesRoute => _hasFlag(SemanticsFlag.namesRoute);
  set namesRoute(bool value) {
    _setFlag(SemanticsFlag.namesRoute, value);
  }

4032 4033 4034 4035 4036 4037 4038 4039
  /// Whether the semantics node represents an image.
  bool get isImage => _hasFlag(SemanticsFlag.isImage);
  set isImage(bool value) {
    _setFlag(SemanticsFlag.isImage, value);
  }

  /// Whether the semantics node is a live region.
  ///
4040 4041 4042
  /// A live region indicates that updates to semantics node are important.
  /// Platforms may use this information to make polite announcements to the
  /// user to inform them of updates to this node.
4043
  ///
4044 4045 4046 4047 4048 4049
  /// An example of a live region is a [SnackBar] widget. On Android and iOS,
  /// live region causes a polite announcement to be generated automatically,
  /// even if the widget does not have accessibility focus. This announcement
  /// may not be spoken if the OS accessibility services are already
  /// announcing something else, such as reading the label of a focused widget
  /// or providing a system announcement.
4050 4051 4052
  ///
  /// See also:
  ///
4053
  ///  * [SemanticsFlag.isLiveRegion], the semantics flag that this setting controls.
4054 4055 4056 4057 4058
  bool get liveRegion => _hasFlag(SemanticsFlag.isLiveRegion);
  set liveRegion(bool value) {
    _setFlag(SemanticsFlag.isLiveRegion, value);
  }

4059 4060
  /// The reading direction for the text in [label], [value], [hint],
  /// [increasedValue], and [decreasedValue].
4061 4062 4063
  TextDirection? get textDirection => _textDirection;
  TextDirection? _textDirection;
  set textDirection(TextDirection? textDirection) {
4064 4065 4066 4067 4068
    _textDirection = textDirection;
    _hasBeenAnnotated = true;
  }

  /// Whether the owning [RenderObject] is selected (true) or not (false).
4069 4070 4071 4072 4073
  ///
  /// This is different from having accessibility focus. The element that is
  /// accessibility focused may or may not be selected; e.g. a [ListTile] can have
  /// accessibility focus but have its [ListTile.selected] property set to false,
  /// in which case it will not be flagged as selected.
4074
  bool get isSelected => _hasFlag(SemanticsFlag.isSelected);
4075
  set isSelected(bool value) {
4076
    _setFlag(SemanticsFlag.isSelected, value);
4077 4078
  }

4079 4080 4081 4082 4083 4084 4085 4086 4087 4088 4089
  /// Whether the owning [RenderObject] is currently enabled.
  ///
  /// A disabled object does not respond to user interactions. Only objects that
  /// usually respond to user interactions, but which currently do not (like a
  /// disabled button) should be marked as disabled.
  ///
  /// The setter should not be called for objects (like static text) that never
  /// respond to user interactions.
  ///
  /// The getter will return null if the owning [RenderObject] doesn't support
  /// the concept of being enabled/disabled.
4090 4091 4092 4093
  ///
  /// This property does not control whether semantics are enabled. If you wish to
  /// disable semantics for a particular widget, you should use an [ExcludeSemantics]
  /// widget.
4094 4095
  bool? get isEnabled => _hasFlag(SemanticsFlag.hasEnabledState) ? _hasFlag(SemanticsFlag.isEnabled) : null;
  set isEnabled(bool? value) {
4096
    _setFlag(SemanticsFlag.hasEnabledState, true);
4097
    _setFlag(SemanticsFlag.isEnabled, value!);
4098 4099
  }

4100
  /// If this node has Boolean state that can be controlled by the user, whether
4101 4102
  /// that state is checked or unchecked, corresponding to true and false,
  /// respectively.
4103
  ///
4104 4105 4106 4107 4108
  /// Do not call the setter for this field if the owning [RenderObject] doesn't
  /// have checked/unchecked state that can be controlled by the user.
  ///
  /// The getter returns null if the owning [RenderObject] does not have
  /// checked/unchecked state.
4109 4110
  bool? get isChecked => _hasFlag(SemanticsFlag.hasCheckedState) ? _hasFlag(SemanticsFlag.isChecked) : null;
  set isChecked(bool? value) {
4111
    _setFlag(SemanticsFlag.hasCheckedState, true);
4112
    _setFlag(SemanticsFlag.isChecked, value!);
4113 4114
  }

4115 4116 4117 4118 4119 4120 4121 4122
  /// If this node has Boolean state that can be controlled by the user, whether
  /// that state is on or off, corresponding to true and false, respectively.
  ///
  /// Do not call the setter for this field if the owning [RenderObject] doesn't
  /// have on/off state that can be controlled by the user.
  ///
  /// The getter returns null if the owning [RenderObject] does not have
  /// on/off state.
4123 4124
  bool? get isToggled => _hasFlag(SemanticsFlag.hasToggledState) ? _hasFlag(SemanticsFlag.isToggled) : null;
  set isToggled(bool? value) {
4125
    _setFlag(SemanticsFlag.hasToggledState, true);
4126
    _setFlag(SemanticsFlag.isToggled, value!);
4127 4128
  }

4129 4130 4131 4132 4133 4134 4135 4136 4137 4138
  /// Whether the owning RenderObject corresponds to UI that allows the user to
  /// pick one of several mutually exclusive options.
  ///
  /// For example, a [Radio] button is in a mutually exclusive group because
  /// only one radio button in that group can be marked as [isChecked].
  bool get isInMutuallyExclusiveGroup => _hasFlag(SemanticsFlag.isInMutuallyExclusiveGroup);
  set isInMutuallyExclusiveGroup(bool value) {
    _setFlag(SemanticsFlag.isInMutuallyExclusiveGroup, value);
  }

4139 4140 4141 4142 4143 4144 4145
  /// Whether the owning [RenderObject] can hold the input focus.
  bool get isFocusable => _hasFlag(SemanticsFlag.isFocusable);
  set isFocusable(bool value) {
    _setFlag(SemanticsFlag.isFocusable, value);
  }

  /// Whether the owning [RenderObject] currently holds the input focus.
4146
  bool get isFocused => _hasFlag(SemanticsFlag.isFocused);
4147
  set isFocused(bool value) {
4148
    _setFlag(SemanticsFlag.isFocused, value);
4149 4150
  }

4151
  /// Whether the owning [RenderObject] is a button (true) or not (false).
4152
  bool get isButton => _hasFlag(SemanticsFlag.isButton);
4153
  set isButton(bool value) {
4154
    _setFlag(SemanticsFlag.isButton, value);
4155 4156
  }

4157 4158 4159 4160 4161 4162
  /// Whether the owning [RenderObject] is a link (true) or not (false).
  bool get isLink => _hasFlag(SemanticsFlag.isLink);
  set isLink(bool value) {
    _setFlag(SemanticsFlag.isLink, value);
  }

4163 4164 4165 4166 4167 4168
  /// Whether the owning [RenderObject] is a header (true) or not (false).
  bool get isHeader => _hasFlag(SemanticsFlag.isHeader);
  set isHeader(bool value) {
    _setFlag(SemanticsFlag.isHeader, value);
  }

4169 4170 4171 4172 4173 4174
  /// Whether the owning [RenderObject] is a slider (true) or not (false).
  bool get isSlider => _hasFlag(SemanticsFlag.isSlider);
  set isSlider(bool value) {
    _setFlag(SemanticsFlag.isSlider, value);
  }

4175 4176 4177 4178 4179 4180 4181
  /// Whether the owning [RenderObject] is a keyboard key (true) or not
  //(false).
  bool get isKeyboardKey => _hasFlag(SemanticsFlag.isKeyboardKey);
  set isKeyboardKey(bool value) {
    _setFlag(SemanticsFlag.isKeyboardKey, value);
  }

4182 4183 4184 4185 4186 4187 4188 4189 4190 4191 4192 4193 4194 4195 4196 4197 4198 4199 4200 4201 4202
  /// Whether the owning [RenderObject] is considered hidden.
  ///
  /// Hidden elements are currently not visible on screen. They may be covered
  /// by other elements or positioned outside of the visible area of a viewport.
  ///
  /// Hidden elements cannot gain accessibility focus though regular touch. The
  /// only way they can be focused is by moving the focus to them via linear
  /// navigation.
  ///
  /// Platforms are free to completely ignore hidden elements and new platforms
  /// are encouraged to do so.
  ///
  /// Instead of marking an element as hidden it should usually be excluded from
  /// the semantics tree altogether. Hidden elements are only included in the
  /// semantics tree to work around platform limitations and they are mainly
  /// used to implement accessibility scrolling on iOS.
  bool get isHidden => _hasFlag(SemanticsFlag.isHidden);
  set isHidden(bool value) {
    _setFlag(SemanticsFlag.isHidden, value);
  }

4203
  /// Whether the owning [RenderObject] is a text field.
4204
  bool get isTextField => _hasFlag(SemanticsFlag.isTextField);
4205
  set isTextField(bool value) {
4206
    _setFlag(SemanticsFlag.isTextField, value);
4207 4208
  }

4209 4210 4211 4212 4213 4214 4215 4216
  /// Whether the owning [RenderObject] is read only.
  ///
  /// Only applicable when [isTextField] is true.
  bool get isReadOnly => _hasFlag(SemanticsFlag.isReadOnly);
  set isReadOnly(bool value) {
    _setFlag(SemanticsFlag.isReadOnly, value);
  }

Janice Collins's avatar
Janice Collins committed
4217
  /// Whether [this.value] should be obscured.
4218
  ///
4219
  /// This option is usually set in combination with [isTextField] to indicate
4220
  /// that the text field contains a password (or other sensitive information).
Janice Collins's avatar
Janice Collins committed
4221
  /// Doing so instructs screen readers to not read out [this.value].
4222 4223 4224 4225 4226
  bool get isObscured => _hasFlag(SemanticsFlag.isObscured);
  set isObscured(bool value) {
    _setFlag(SemanticsFlag.isObscured, value);
  }

4227
  /// Whether the text field is multiline.
4228
  ///
4229
  /// This option is usually set in combination with [isTextField] to indicate
4230
  /// that the text field is configured to be multiline.
4231 4232 4233 4234 4235
  bool get isMultiline => _hasFlag(SemanticsFlag.isMultiline);
  set isMultiline(bool value) {
    _setFlag(SemanticsFlag.isMultiline, value);
  }

4236 4237 4238 4239 4240 4241 4242 4243 4244 4245 4246 4247
  /// Whether the platform can scroll the semantics node when the user attempts
  /// to move focus to an offscreen child.
  ///
  /// For example, a [ListView] widget has implicit scrolling so that users can
  /// easily move to the next visible set of children. A [TabBar] widget does
  /// not have implicit scrolling, so that users can navigate into the tab
  /// body when reaching the end of the tab bar.
  bool get hasImplicitScrolling => _hasFlag(SemanticsFlag.hasImplicitScrolling);
  set hasImplicitScrolling(bool value) {
    _setFlag(SemanticsFlag.hasImplicitScrolling, value);
  }

Janice Collins's avatar
Janice Collins committed
4248 4249
  /// The currently selected text (or the position of the cursor) within
  /// [this.value] if this node represents a text field.
4250 4251 4252
  TextSelection? get textSelection => _textSelection;
  TextSelection? _textSelection;
  set textSelection(TextSelection? value) {
4253 4254 4255 4256 4257
    assert(value != null);
    _textSelection = value;
    _hasBeenAnnotated = true;
  }

4258 4259 4260 4261 4262 4263 4264 4265 4266 4267
  /// Indicates the current scrolling position in logical pixels if the node is
  /// scrollable.
  ///
  /// The properties [scrollExtentMin] and [scrollExtentMax] indicate the valid
  /// in-range values for this property. The value for [scrollPosition] may
  /// (temporarily) be outside that range, e.g. during an overscroll.
  ///
  /// See also:
  ///
  ///  * [ScrollPosition.pixels], from where this value is usually taken.
4268 4269 4270
  double? get scrollPosition => _scrollPosition;
  double? _scrollPosition;
  set scrollPosition(double? value) {
4271 4272 4273 4274 4275 4276 4277 4278 4279 4280 4281 4282 4283
    assert(value != null);
    _scrollPosition = value;
    _hasBeenAnnotated = true;
  }

  /// Indicates the maximum in-range value for [scrollPosition] if the node is
  /// scrollable.
  ///
  /// This value may be infinity if the scroll is unbound.
  ///
  /// See also:
  ///
  ///  * [ScrollPosition.maxScrollExtent], from where this value is usually taken.
4284 4285 4286
  double? get scrollExtentMax => _scrollExtentMax;
  double? _scrollExtentMax;
  set scrollExtentMax(double? value) {
4287 4288 4289 4290 4291 4292 4293 4294 4295 4296 4297 4298 4299
    assert(value != null);
    _scrollExtentMax = value;
    _hasBeenAnnotated = true;
  }

  /// Indicates the minimum in-range value for [scrollPosition] if the node is
  /// scrollable.
  ///
  /// This value may be infinity if the scroll is unbound.
  ///
  /// See also:
  ///
  ///  * [ScrollPosition.minScrollExtent], from where this value is usually taken.
4300 4301 4302
  double? get scrollExtentMin => _scrollExtentMin;
  double? _scrollExtentMin;
  set scrollExtentMin(double? value) {
4303 4304 4305 4306 4307
    assert(value != null);
    _scrollExtentMin = value;
    _hasBeenAnnotated = true;
  }

4308 4309
  // TAGS

4310 4311 4312 4313
  /// The set of tags that this configuration wants to add to all child
  /// [SemanticsNode]s.
  ///
  /// See also:
4314
  ///
4315 4316
  ///  * [addTagForChildren] to add a tag and for more information about their
  ///    usage.
4317 4318
  Iterable<SemanticsTag>? get tagsForChildren => _tagsForChildren;
  Set<SemanticsTag>? _tagsForChildren;
4319

4320 4321 4322 4323 4324 4325 4326 4327 4328 4329 4330 4331
  /// Specifies a [SemanticsTag] that this configuration wants to apply to all
  /// child [SemanticsNode]s.
  ///
  /// The tag is added to all [SemanticsNode] that pass through the
  /// [RenderObject] owning this configuration while looking to be attached to a
  /// parent [SemanticsNode].
  ///
  /// Tags are used to communicate to a parent [SemanticsNode] that a child
  /// [SemanticsNode] was passed through a particular [RenderObject]. The parent
  /// can use this information to determine the shape of the semantics tree.
  ///
  /// See also:
4332
  ///
4333
  ///  * [RenderViewport.excludeFromScrolling] for an example of
4334
  ///    how tags are used.
4335
  void addTagForChildren(SemanticsTag tag) {
4336
    _tagsForChildren ??= <SemanticsTag>{};
4337
    _tagsForChildren!.add(tag);
4338 4339 4340 4341 4342
  }

  // INTERNAL FLAG MANAGEMENT

  int _flags = 0;
4343
  void _setFlag(SemanticsFlag flag, bool value) {
4344 4345 4346 4347 4348 4349 4350 4351
    if (value) {
      _flags |= flag.index;
    } else {
      _flags &= ~flag.index;
    }
    _hasBeenAnnotated = true;
  }

4352
  bool _hasFlag(SemanticsFlag flag) => (_flags & flag.index) != 0;
4353

4354 4355 4356 4357 4358 4359 4360
  // CONFIGURATION COMBINATION LOGIC

  /// Whether this configuration is compatible with the provided `other`
  /// configuration.
  ///
  /// Two configurations are said to be compatible if they can be added to the
  /// same [SemanticsNode] without losing any semantics information.
4361
  bool isCompatibleWith(SemanticsConfiguration? other) {
4362 4363
    if (other == null || !other.hasBeenAnnotated || !hasBeenAnnotated)
      return true;
4364
    if (_actionsAsBits & other._actionsAsBits != 0)
4365 4366 4367
      return false;
    if ((_flags & other._flags) != 0)
      return false;
4368 4369 4370
    if (_platformViewId != null && other._platformViewId != null) {
      return false;
    }
4371 4372 4373 4374 4375 4376
    if (_maxValueLength != null && other._maxValueLength != null) {
      return false;
    }
    if (_currentValueLength != null && other._currentValueLength != null) {
      return false;
    }
4377
    if (_attributedValue != null && _attributedValue.string.isNotEmpty && other._attributedValue != null && other._attributedValue.string.isNotEmpty)
4378
      return false;
4379 4380 4381
    return true;
  }

4382
  /// Absorb the semantic information from `child` into this configuration.
4383 4384 4385 4386
  ///
  /// This adds the semantic information of both configurations and saves the
  /// result in this configuration.
  ///
4387 4388 4389
  /// The [RenderObject] owning the `child` configuration must be a descendant
  /// of the [RenderObject] that owns this configuration.
  ///
4390
  /// Only configurations that have [explicitChildNodes] set to false can
4391
  /// absorb other configurations and it is recommended to only absorb compatible
4392
  /// configurations as determined by [isCompatibleWith].
4393
  void absorb(SemanticsConfiguration child) {
4394 4395
    assert(!explicitChildNodes);

4396
    if (!child.hasBeenAnnotated)
4397 4398
      return;

4399 4400 4401 4402 4403 4404 4405 4406 4407 4408 4409 4410
    _actions.addAll(child._actions);
    _customSemanticsActions.addAll(child._customSemanticsActions);
    _actionsAsBits |= child._actionsAsBits;
    _flags |= child._flags;
    _textSelection ??= child._textSelection;
    _scrollPosition ??= child._scrollPosition;
    _scrollExtentMax ??= child._scrollExtentMax;
    _scrollExtentMin ??= child._scrollExtentMin;
    _hintOverrides ??= child._hintOverrides;
    _indexInParent ??= child.indexInParent;
    _scrollIndex ??= child._scrollIndex;
    _scrollChildCount ??= child._scrollChildCount;
4411
    _platformViewId ??= child._platformViewId;
4412 4413
    _maxValueLength ??= child._maxValueLength;
    _currentValueLength ??= child._currentValueLength;
4414 4415 4416

    textDirection ??= child.textDirection;
    _sortKey ??= child._sortKey;
4417 4418
    _attributedLabel = _concatAttributedString(
      thisAttributedString: _attributedLabel,
4419
      thisTextDirection: textDirection,
4420
      otherAttributedString: child._attributedLabel,
4421
      otherTextDirection: child.textDirection,
4422
    );
4423 4424 4425 4426 4427 4428 4429 4430
    if (_attributedValue == null || _attributedValue.string == '')
      _attributedValue = child._attributedValue;
    if (_attributedIncreasedValue == null || _attributedIncreasedValue.string == '')
      _attributedIncreasedValue = child._attributedIncreasedValue;
    if (_attributedDecreasedValue == null || _attributedDecreasedValue.string == '')
      _attributedDecreasedValue = child._attributedDecreasedValue;
    _attributedHint = _concatAttributedString(
      thisAttributedString: _attributedHint,
4431
      thisTextDirection: textDirection,
4432
      otherAttributedString: child._attributedHint,
4433
      otherTextDirection: child.textDirection,
4434
    );
4435

4436 4437 4438
    _thickness = math.max(_thickness, child._thickness + child._elevation);

    _hasBeenAnnotated = _hasBeenAnnotated || child._hasBeenAnnotated;
4439 4440 4441 4442
  }

  /// Returns an exact copy of this configuration.
  SemanticsConfiguration copy() {
4443
    return SemanticsConfiguration()
4444
      .._isSemanticBoundary = _isSemanticBoundary
4445
      ..explicitChildNodes = explicitChildNodes
4446
      ..isBlockingSemanticsOfPreviouslyPaintedNodes = isBlockingSemanticsOfPreviouslyPaintedNodes
4447
      .._hasBeenAnnotated = _hasBeenAnnotated
4448
      .._isMergingSemanticsOfDescendants = _isMergingSemanticsOfDescendants
4449
      .._textDirection = _textDirection
4450
      .._sortKey = _sortKey
4451 4452 4453 4454 4455
      .._attributedLabel = _attributedLabel
      .._attributedIncreasedValue = _attributedIncreasedValue
      .._attributedValue = _attributedValue
      .._attributedDecreasedValue = _attributedDecreasedValue
      .._attributedHint = _attributedHint
4456
      .._hintOverrides = _hintOverrides
4457 4458
      .._elevation = _elevation
      .._thickness = _thickness
4459
      .._flags = _flags
4460
      .._tagsForChildren = _tagsForChildren
4461
      .._textSelection = _textSelection
4462 4463 4464
      .._scrollPosition = _scrollPosition
      .._scrollExtentMax = _scrollExtentMax
      .._scrollExtentMin = _scrollExtentMin
4465
      .._actionsAsBits = _actionsAsBits
4466 4467 4468
      .._indexInParent = indexInParent
      .._scrollIndex = _scrollIndex
      .._scrollChildCount = _scrollChildCount
4469
      .._platformViewId = _platformViewId
4470 4471
      .._maxValueLength = _maxValueLength
      .._currentValueLength = _currentValueLength
4472 4473
      .._actions.addAll(_actions)
      .._customSemanticsActions.addAll(_customSemanticsActions);
4474 4475
  }
}
4476

4477 4478 4479 4480 4481 4482 4483 4484 4485 4486
/// Used by [debugDumpSemanticsTree] to specify the order in which child nodes
/// are printed.
enum DebugSemanticsDumpOrder {
  /// Print nodes in inverse hit test order.
  ///
  /// In inverse hit test order, the last child of a [SemanticsNode] will be
  /// asked first if it wants to respond to a user's interaction, followed by
  /// the second last, etc. until a taker is found.
  inverseHitTest,

4487
  /// Print nodes in semantic traversal order.
4488
  ///
4489 4490 4491
  /// This is the order in which a user would navigate the UI using the "next"
  /// and "previous" gestures.
  traversalOrder,
4492 4493
}

4494 4495 4496
AttributedString _concatAttributedString({
  required AttributedString thisAttributedString,
  required AttributedString otherAttributedString,
4497 4498
  required TextDirection? thisTextDirection,
  required TextDirection? otherTextDirection,
4499
}) {
4500 4501
  if (otherAttributedString.string.isEmpty)
    return thisAttributedString;
4502 4503 4504
  if (thisTextDirection != otherTextDirection && otherTextDirection != null) {
    switch (otherTextDirection) {
      case TextDirection.rtl:
4505
        otherAttributedString = AttributedString(Unicode.RLE) + otherAttributedString + AttributedString(Unicode.PDF);
4506 4507
        break;
      case TextDirection.ltr:
4508
        otherAttributedString = AttributedString(Unicode.LRE) + otherAttributedString + AttributedString(Unicode.PDF);
4509 4510 4511
        break;
    }
  }
4512 4513 4514 4515
  if (thisAttributedString.string.isEmpty)
    return otherAttributedString;

  return thisAttributedString + AttributedString('\n') + otherAttributedString;
4516
}
4517

4518 4519
/// Base class for all sort keys for [SemanticsProperties.sortKey] accessibility
/// traversal order sorting.
4520
///
4521 4522 4523
/// Sort keys are sorted by [name], then by the comparison that the subclass
/// implements. If [SemanticsProperties.sortKey] is specified, sort keys within
/// the same semantic group must all be of the same type.
4524
///
4525 4526
/// Keys with no [name] are compared to other keys with no [name], and will
/// be traversed before those with a [name].
4527
///
4528 4529
/// If no sort key is applied to a semantics node, then it will be ordered using
/// a platform dependent default algorithm.
4530
///
4531
/// See also:
4532 4533
///
///  * [OrdinalSortKey] for a sort key that sorts using an ordinal.
4534
abstract class SemanticsSortKey with Diagnosticable implements Comparable<SemanticsSortKey> {
4535 4536
  /// Abstract const constructor. This constructor enables subclasses to provide
  /// const constructors so that they can be used in const expressions.
4537 4538
  const SemanticsSortKey({this.name});

4539 4540 4541 4542 4543 4544 4545
  /// An optional name that will group this sort key with other sort keys of the
  /// same [name].
  ///
  /// Sort keys must have the same `runtimeType` when compared.
  ///
  /// Keys with no [name] are compared to other keys with no [name], and will
  /// be traversed before those with a [name].
4546
  final String? name;
4547 4548 4549

  @override
  int compareTo(SemanticsSortKey other) {
4550 4551 4552 4553 4554 4555 4556 4557 4558 4559 4560 4561 4562 4563 4564 4565 4566
    // Sort by name first and then subclass ordering.
    assert(runtimeType == other.runtimeType, 'Semantics sort keys can only be compared to other sort keys of the same type.');

    // Defer to the subclass implementation for ordering only if the names are
    // identical (or both null).
    if (name == other.name) {
      return doCompare(other);
    }

    // Keys that don't have a name are sorted together and come before those with
    // a name.
    if (name == null && other.name != null) {
      return -1;
    } else if (name != null && other.name == null) {
      return 1;
    }

4567
    return name!.compareTo(other.name!);
4568 4569
  }

4570 4571
  /// The implementation of [compareTo].
  ///
4572 4573
  /// The argument is guaranteed to be of the same type as this object and have
  /// the same [name].
4574
  ///
4575
  /// The method should return a negative number if this object comes earlier in
4576
  /// the sort order than the argument; and a positive number if it comes later
4577 4578
  /// in the sort order. Returning zero causes the system to use default sort
  /// order.
4579
  @protected
4580
  int doCompare(covariant SemanticsSortKey other);
4581 4582

  @override
4583 4584
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
4585
    properties.add(StringProperty('name', name, defaultValue: null));
4586 4587 4588
  }
}

4589 4590
/// A [SemanticsSortKey] that sorts simply based on the `double` value it is
/// given.
4591 4592 4593 4594
///
/// The [OrdinalSortKey] compares itself with other [OrdinalSortKey]s
/// to sort based on the order it is given.
///
4595 4596
/// [OrdinalSortKey]s are sorted by the optional [name], then by their [order].
/// If [SemanticsProperties.sortKey] is a [OrdinalSortKey], then all the other
4597
/// specified sort keys in the same semantics group must also be
4598 4599 4600 4601 4602 4603
/// [OrdinalSortKey]s.
///
/// Keys with no [name] are compared to other keys with no [name], and will
/// be traversed before those with a [name].
///
/// The ordinal value [order] is typically a whole number, though it can be
4604 4605 4606
/// fractional, e.g. in order to fit between two other consecutive whole
/// numbers. The value must be finite (it cannot be [double.nan],
/// [double.infinity], or [double.negativeInfinity]).
4607
class OrdinalSortKey extends SemanticsSortKey {
4608
  /// Creates a const semantics sort key that uses a [double] as its key value.
4609
  ///
4610
  /// The [order] must be a finite number, and must not be null.
4611 4612
  const OrdinalSortKey(
    this.order, {
4613
    String? name,
4614
  }) : assert(order != null),
4615 4616 4617
       assert(order != double.nan),
       assert(order > double.negativeInfinity),
       assert(order < double.infinity),
4618
       super(name: name);
4619

4620 4621 4622 4623
  /// Determines the placement of this key in a sequence of keys that defines
  /// the order in which this node is traversed by the platform's accessibility
  /// services.
  ///
4624 4625
  /// Lower values will be traversed first. Keys with the same [name] will be
  /// grouped together and sorted by name first, and then sorted by [order].
4626 4627 4628
  final double order;

  @override
4629 4630
  int doCompare(OrdinalSortKey other) {
    if (other.order == null || order == null || other.order == order)
4631
      return 0;
4632
    return order.compareTo(other.order);
4633 4634 4635
  }

  @override
4636 4637
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
4638
    properties.add(DoubleProperty('order', order, defaultValue: null));
4639 4640
  }
}