semantics.dart 155 KB
Newer Older
Hixie's avatar
Hixie committed
1 2 3 4
// Copyright 2015 The Chromium Authors. All rights reserved.
// 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;
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;
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
typedef _SemanticsActionHandler = void Function(dynamic args);
40

41 42 43 44
/// 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
45
/// how to add the tagged node as a child. Tags are not sent to the engine.
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
///
/// 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
  String toString() => '$runtimeType($name)';
}

73
/// An identifier of a custom semantics action.
74
///
75
/// Custom semantics actions can be provided to make complex user
76
/// interactions more accessible. For instance, if an application has a
77 78 79 80 81
/// 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.
82
///
83
/// In Android, these actions are presented in the local context menu. In iOS,
84 85
/// these are presented in the radial context menu.
///
86
/// Localization and text direction do not automatically apply to the provided
87
/// label or hint.
88
///
89 90
/// Instances of this class should either be instantiated with const or
/// new instances cached in static fields.
91
///
92
/// See also:
93
///
94
///  * [SemanticsProperties], where the handler for a custom action is provided.
95 96 97
@immutable
class CustomSemanticsAction {
  /// Creates a new [CustomSemanticsAction].
98
  ///
99
  /// The [label] must not be null or the empty string.
100
  const CustomSemanticsAction({@required this.label})
101
    : assert(label != null),
102 103 104
      assert(label != ''),
      hint = null,
      action = null;
105

106 107 108 109 110 111 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.
  const CustomSemanticsAction.overridingAction({@required this.hint, @required this.action})
    : assert(hint != null),
      assert(hint != ''),
      assert(action != null),
      label = null;

  /// The user readable name of this custom semantics action.
117 118
  final String label;

119 120 121 122 123 124
  /// The hint description of this custom semantics action.
  final String hint;

  /// The standard semantics action this action replaces.
  final SemanticsAction action;

125
  @override
126
  int get hashCode => ui.hashValues(label, hint, action);
127 128 129 130 131 132

  @override
  bool operator ==(dynamic other) {
    if (other.runtimeType != runtimeType)
      return false;
    final CustomSemanticsAction typedOther = other;
133 134 135 136 137 138 139 140
    return typedOther.label == label
      && typedOther.hint == hint
      && typedOther.action == action;
  }

  @override
  String toString() {
    return 'CustomSemanticsAction(${_ids[this]}, label:$label, hint:$hint, action:$action)';
141 142 143
  }

  // Logic to assign a unique id to each custom action without requiring
144
  // user specification.
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
  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) {
    int result = _ids[action];
    if (result == null) {
      result = _nextId++;
      _ids[action] = result;
      _actions[result] = action;
    }
    return result;
  }

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

166 167 168 169 170 171 172 173
/// 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].
174
@immutable
175
class SemanticsData extends Diagnosticable {
176 177
  /// Creates a semantics data object.
  ///
178
  /// The [flags], [actions], [label], and [Rect] arguments must not be null.
Ian Hickson's avatar
Ian Hickson committed
179 180
  ///
  /// If [label] is not empty, then [textDirection] must also not be null.
181
  const SemanticsData({
182 183 184
    @required this.flags,
    @required this.actions,
    @required this.label,
185
    @required this.increasedValue,
186
    @required this.value,
187
    @required this.decreasedValue,
188
    @required this.hint,
Ian Hickson's avatar
Ian Hickson committed
189
    @required this.textDirection,
190
    @required this.rect,
191 192
    @required this.elevation,
    @required this.thickness,
193
    @required this.textSelection,
194 195
    @required this.scrollIndex,
    @required this.scrollChildCount,
196 197 198
    @required this.scrollPosition,
    @required this.scrollExtentMax,
    @required this.scrollExtentMin,
199
    @required this.platformViewId,
200 201
    @required this.maxValueLength,
    @required this.currentValueLength,
202
    this.tags,
Ian Hickson's avatar
Ian Hickson committed
203
    this.transform,
204
    this.customSemanticsActionIds,
205 206 207
  }) : assert(flags != null),
       assert(actions != null),
       assert(label != null),
208 209 210 211
       assert(value != null),
       assert(decreasedValue != null),
       assert(increasedValue != null),
       assert(hint != null),
Ian Hickson's avatar
Ian Hickson committed
212
       assert(label == '' || textDirection != null, 'A SemanticsData object with label "$label" had a null textDirection.'),
213 214 215 216
       assert(value == '' || textDirection != null, 'A SemanticsData object with value "$value" had a null textDirection.'),
       assert(hint == '' || textDirection != null, 'A SemanticsData object with hint "$hint" had a null textDirection.'),
       assert(decreasedValue == '' || textDirection != null, 'A SemanticsData object with decreasedValue "$decreasedValue" had a null textDirection.'),
       assert(increasedValue == '' || textDirection != null, 'A SemanticsData object with increasedValue "$increasedValue" had a null textDirection.'),
217
       assert(rect != null);
218

219
  /// A bit field of [SemanticsFlag]s that apply to this node.
220 221
  final int flags;

222
  /// A bit field of [SemanticsAction]s that apply to this node.
223 224 225
  final int actions;

  /// A textual description of this node.
Ian Hickson's avatar
Ian Hickson committed
226
  ///
227
  /// The reading direction is given by [textDirection].
228 229
  final String label;

230 231
  /// A textual description for the current value of the node.
  ///
232
  /// The reading direction is given by [textDirection].
233 234
  final String value;

235 236 237 238 239 240 241 242 243 244 245 246
  /// The value that [value] will become after performing a
  /// [SemanticsAction.increase] action.
  ///
  /// The reading direction is given by [textDirection].
  final String increasedValue;

  /// The value that [value] will become after performing a
  /// [SemanticsAction.decrease] action.
  ///
  /// The reading direction is given by [textDirection].
  final String decreasedValue;

247 248
  /// A brief description of the result of performing an action on this node.
  ///
249
  /// The reading direction is given by [textDirection].
250 251
  final String hint;

252 253
  /// The reading direction for the text in [label], [value], [hint],
  /// [increasedValue], and [decreasedValue].
Ian Hickson's avatar
Ian Hickson committed
254 255
  final TextDirection textDirection;

256 257 258 259
  /// The currently selected text (or the position of the cursor) within [value]
  /// if this node represents a text field.
  final TextSelection textSelection;

260 261 262 263 264 265 266 267 268
  /// The total number of scrollable children that contribute to semantics.
  ///
  /// If the number of children are unknown or unbounded, this value will be
  /// null.
  final int scrollChildCount;

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

269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290
  /// 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.
  final double scrollPosition;

  /// 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.
  final double scrollExtentMax;

Josh Soref's avatar
Josh Soref committed
291
  /// Indicates the minimum in-range value for [scrollPosition] if the node is
292 293 294 295 296 297 298 299 300
  /// scrollable.
  ///
  /// This value may be infinity if the scroll is unbound.
  ///
  /// See also:
  ///
  ///  * [ScrollPosition.minScrollExtent], from where this value is usually taken.
  final double scrollExtentMin;

301 302 303 304 305 306 307 308 309 310 311 312 313
  /// 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.
  final int platformViewId;

314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333
  /// 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.
  final int maxValueLength;

  /// 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.
  final int currentValueLength;

334 335 336
  /// The bounding box for this node in its coordinate system.
  final Rect rect;

337 338 339
  /// The set of [SemanticsTag]s associated with this node.
  final Set<SemanticsTag> tags;

340 341 342
  /// The transform from this node's coordinate system to its parent's coordinate system.
  ///
  /// By default, the transform is null, which represents the identity
343
  /// transformation (i.e., that this node has the same coordinate system as its
344 345 346
  /// parent).
  final Matrix4 transform;

347 348 349 350 351 352 353 354 355 356 357 358 359 360 361
  /// 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;

362 363
  /// The identifiers for the custom semantics actions and standard action
  /// overrides for this node.
364
  ///
365
  /// The list must be sorted in increasing order.
366
  ///
367
  /// See also:
368
  ///
369
  ///  * [CustomSemanticsAction], for an explanation of custom actions.
370 371
  final List<int> customSemanticsActionIds;

372
  /// Whether [flags] contains the given flag.
373
  bool hasFlag(SemanticsFlag flag) => (flags & flag.index) != 0;
374 375 376

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

  @override
379 380 381 382 383
  String toStringShort() => '$runtimeType';

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
384 385
    properties.add(DiagnosticsProperty<Rect>('rect', rect, showName: false));
    properties.add(TransformProperty('transform', transform, showName: false, defaultValue: null));
386 387
    properties.add(DoubleProperty('elevation', elevation, defaultValue: 0.0));
    properties.add(DoubleProperty('thickness', thickness, defaultValue: 0.0));
388 389 390 391 392
    final List<String> actionSummary = <String>[
      for (SemanticsAction action in SemanticsAction.values.values)
        if ((actions & action.index) != 0)
          describeEnum(action),
    ];
393 394 395
    final List<String> customSemanticsActionSummary = customSemanticsActionIds
      .map<String>((int actionId) => CustomSemanticsAction.getAction(actionId).label)
      .toList();
396 397
    properties.add(IterableProperty<String>('actions', actionSummary, ifEmpty: null));
    properties.add(IterableProperty<String>('customActions', customSemanticsActionSummary, ifEmpty: null));
398

399 400 401 402 403
    final List<String> flagSummary = <String>[
      for (SemanticsFlag flag in SemanticsFlag.values.values)
        if ((flags & flag.index) != 0)
          describeEnum(flag),
    ];
404 405 406 407 408 409 410
    properties.add(IterableProperty<String>('flags', flagSummary, ifEmpty: null));
    properties.add(StringProperty('label', label, defaultValue: ''));
    properties.add(StringProperty('value', value, defaultValue: ''));
    properties.add(StringProperty('increasedValue', increasedValue, defaultValue: ''));
    properties.add(StringProperty('decreasedValue', decreasedValue, defaultValue: ''));
    properties.add(StringProperty('hint', hint, defaultValue: ''));
    properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
411
    if (textSelection?.isValid == true)
412
      properties.add(MessageProperty('textSelection', '[${textSelection.start}, ${textSelection.end}]'));
413
    properties.add(IntProperty('platformViewId', platformViewId, defaultValue: null));
414 415
    properties.add(IntProperty('maxValueLength', maxValueLength, defaultValue: null));
    properties.add(IntProperty('currentValueLength', currentValueLength, defaultValue: null));
416 417
    properties.add(IntProperty('scrollChildren', scrollChildCount, defaultValue: null));
    properties.add(IntProperty('scrollIndex', scrollIndex, defaultValue: null));
418 419 420
    properties.add(DoubleProperty('scrollExtentMin', scrollExtentMin, defaultValue: null));
    properties.add(DoubleProperty('scrollPosition', scrollPosition, defaultValue: null));
    properties.add(DoubleProperty('scrollExtentMax', scrollExtentMax, defaultValue: null));
421 422 423 424 425 426 427 428 429 430
  }

  @override
  bool operator ==(dynamic other) {
    if (other is! SemanticsData)
      return false;
    final SemanticsData typedOther = other;
    return typedOther.flags == flags
        && typedOther.actions == actions
        && typedOther.label == label
431 432 433 434
        && typedOther.value == value
        && typedOther.increasedValue == increasedValue
        && typedOther.decreasedValue == decreasedValue
        && typedOther.hint == hint
Ian Hickson's avatar
Ian Hickson committed
435
        && typedOther.textDirection == textDirection
436
        && typedOther.rect == rect
437
        && setEquals(typedOther.tags, tags)
438 439
        && typedOther.scrollChildCount == scrollChildCount
        && typedOther.scrollIndex == scrollIndex
440
        && typedOther.textSelection == textSelection
441 442 443
        && typedOther.scrollPosition == scrollPosition
        && typedOther.scrollExtentMax == scrollExtentMax
        && typedOther.scrollExtentMin == scrollExtentMin
444
        && typedOther.platformViewId == platformViewId
445 446
        && typedOther.maxValueLength == maxValueLength
        && typedOther.currentValueLength == currentValueLength
447
        && typedOther.transform == transform
448 449
        && typedOther.elevation == elevation
        && typedOther.thickness == thickness
450
        && _sortedListsEqual(typedOther.customSemanticsActionIds, customSemanticsActionIds);
451 452 453
  }

  @override
454 455
  int get hashCode {
    return ui.hashValues(
456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473
      ui.hashValues(
        flags,
        actions,
        label,
        value,
        increasedValue,
        decreasedValue,
        hint,
        textDirection,
        rect,
        tags,
        textSelection,
        scrollChildCount,
        scrollIndex,
        scrollPosition,
        scrollExtentMax,
        scrollExtentMin,
        platformViewId,
474 475
        maxValueLength,
        currentValueLength,
476 477
        transform,
      ),
478 479
      elevation,
      thickness,
480
      ui.hashList(customSemanticsActionIds),
481 482
    );
  }
483

484 485 486 487 488 489 490 491 492 493 494 495 496
  static bool _sortedListsEqual(List<int> left, List<int> right) {
    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;
  }
497 498
}

499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521
class _SemanticsDiagnosticableNode extends DiagnosticableNode<SemanticsNode> {
  _SemanticsDiagnosticableNode({
    String name,
    @required SemanticsNode value,
    @required DiagnosticsTreeStyle style,
    @required this.childOrder,
  }) : super(
    name: name,
    value: value,
    style: style,
  );

  final DebugSemanticsDumpOrder childOrder;

  @override
  List<DiagnosticsNode> getChildren() {
    if (value != null)
      return value.debugDescribeChildren(childOrder: childOrder);

    return const <DiagnosticsNode>[];
  }
}

522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574
/// 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'.
  final String onTapHint;

  /// 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'.
  final String onLongPressHint;

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

  @override
  int get hashCode => ui.hashValues(onTapHint, onLongPressHint);

  @override
  bool operator ==(dynamic other) {
    if (other.runtimeType != runtimeType)
      return false;
    final SemanticsHintOverrides typedOther = other;
    return typedOther.onTapHint == onTapHint
      && typedOther.onLongPressHint == onLongPressHint;
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
575 576
    properties.add(StringProperty('onTapHint', onTapHint, defaultValue: null));
    properties.add(StringProperty('onLongPressHint', onLongPressHint, defaultValue: null));
577 578 579
  }
}

580 581 582 583 584 585 586 587 588
/// 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({
589
    this.enabled,
590 591
    this.checked,
    this.selected,
592
    this.toggled,
593
    this.button,
594
    this.link,
595 596
    this.header,
    this.textField,
597
    this.readOnly,
598
    this.focusable,
599 600
    this.focused,
    this.inMutuallyExclusiveGroup,
601
    this.hidden,
602
    this.obscured,
603
    this.multiline,
604 605
    this.scopesRoute,
    this.namesRoute,
606 607
    this.image,
    this.liveRegion,
608 609
    this.maxValueLength,
    this.currentValueLength,
610 611 612 613 614
    this.label,
    this.value,
    this.increasedValue,
    this.decreasedValue,
    this.hint,
615
    this.hintOverrides,
616
    this.textDirection,
617
    this.sortKey,
618 619 620 621 622 623 624 625
    this.onTap,
    this.onLongPress,
    this.onScrollLeft,
    this.onScrollRight,
    this.onScrollUp,
    this.onScrollDown,
    this.onIncrease,
    this.onDecrease,
626 627 628
    this.onCopy,
    this.onCut,
    this.onPaste,
629 630
    this.onMoveCursorForwardByCharacter,
    this.onMoveCursorBackwardByCharacter,
631 632
    this.onMoveCursorForwardByWord,
    this.onMoveCursorBackwardByWord,
633
    this.onSetSelection,
634 635
    this.onDidGainAccessibilityFocus,
    this.onDidLoseAccessibilityFocus,
636
    this.onDismiss,
637
    this.customSemanticsActions,
638 639
  });

640 641 642 643 644 645 646 647
  /// 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.
  final bool enabled;

648 649 650
  /// If non-null, indicates that this subtree represents a checkbox
  /// or similar widget with a "checked" state, and what its current
  /// state is.
651 652
  ///
  /// This is mutually exclusive with [toggled].
653 654
  final bool checked;

655 656 657 658 659 660 661
  /// 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].
  final bool toggled;

662 663 664 665 666 667 668 669 670 671 672 673 674
  /// 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.
  final bool selected;

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

675 676 677 678 679 680 681
  /// 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.
  final bool link;

682 683 684 685 686 687 688 689 690 691 692 693 694
  /// 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.
  final bool header;

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

695 696 697 698 699 700 701
  /// If non-null, indicates that this subtree is read only.
  ///
  /// Only applicable when [textField] is true
  ///
  /// TalkBack/VoiceOver will treat it as non-editable text field.
  final bool readOnly;

702 703 704 705 706 707 708 709 710 711
  /// 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.
  final bool focusable;

712 713
  /// If non-null, whether the node currently holds input focus.
  ///
714 715
  /// 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.
716
  ///
717
  /// Input focus indicates that the node will receive keyboard events. It is not
718
  /// to be confused with accessibility focus. Accessibility focus is the
719 720
  /// green/black rectangular highlight that TalkBack/VoiceOver draws around the
  /// element it is reading, and is separate from input focus.
721 722 723 724 725 726 727
  final bool focused;

  /// 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].
  final bool inMutuallyExclusiveGroup;
728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746

  /// 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.
  final bool hidden;

747 748 749 750 751 752
  /// 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].
  final bool obscured;
753

754 755 756 757 758 759 760 761 762
  /// Whether the [value] is coming from a field that supports multi-line text
  /// editing.
  ///
  /// This option is only meaningful when [textField] is true to indicate
  /// whether it's a single-line or multi-line text field.
  ///
  /// This option is null when [textField] is false.
  final bool multiline;

763 764
  /// If non-null, whether the node corresponds to the root of a subtree for
  /// which a route name should be announced.
765 766
  ///
  /// Generally, this is set in combination with [explicitChildNodes], since
767 768 769 770 771 772 773 774 775 776 777
  /// nodes with this flag are not considered focusable by Android or iOS.
  ///
  /// See also:
  ///
  ///  * [SemanticsFlag.scopesRoute] for a description of how the announced
  ///    value is selected.
  final bool scopesRoute;

  /// If non-null, whether the node contains the semantic label for a route.
  ///
  /// See also:
778
  ///
779 780 781
  ///  * [SemanticsFlag.namesRoute] for a description of how the name is used.
  final bool namesRoute;

782 783 784 785
  /// If non-null, whether the node represents an image.
  ///
  /// See also:
  ///
786
  ///  * [SemanticsFlag.image], for the flag this setting controls.
787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806
  final bool image;

  /// If non-null, whether the node should be considered a live region.
  ///
  /// On Android, when a live region semantics node is first created TalkBack
  /// will make a polite announcement of the current label. This announcement
  /// occurs even if the node is not focused. Subsequent polite announcements
  /// can be made by sending a [UpdateLiveRegionEvent] semantics event. The
  /// announcement will only be made if the node's label has changed since the
  /// last update.
  ///
  /// On iOS, no announcements are made but the node is marked as
  /// `UIAccessibilityTraitUpdatesFrequently`.
  ///
  /// An example of a live region is the [Snackbar] widget. When it appears
  /// on the screen it may be difficult to focus to read the label. A live
  /// region causes an initial polite announcement to be generated
  /// automatically.
  ///
  /// See also:
807 808 809 810
  ///
  ///  * [SemanticsFlag.liveRegion], the semantics flag this setting controls.
  ///  * [SemanticsConfiguration.liveRegion], for a full description of a live region.
  ///  * [UpdateLiveRegionEvent], to trigger a polite announcement of a live region.
811 812
  final bool liveRegion;

813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832
  /// 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.
  final int maxValueLength;

  /// 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.
  final int currentValueLength;

833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883
  /// 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.
  ///
  /// See also:
  ///
  ///  * [SemanticsConfiguration.label] for a description of how this is exposed
  ///    in TalkBack and VoiceOver.
  final String label;

  /// 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.
  ///
  /// See also:
  ///
  ///  * [SemanticsConfiguration.value] for a description of how this is exposed
  ///    in TalkBack and VoiceOver.
  final String value;

  /// The value that [value] will become after a [SemanticsAction.increase]
  /// action has been performed on this widget.
  ///
  /// 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.
  ///
  /// See also:
  ///
  ///  * [SemanticsConfiguration.increasedValue] for a description of how this
  ///    is exposed in TalkBack and VoiceOver.
  final String increasedValue;

  /// The value that [value] will become after a [SemanticsAction.decrease]
  /// action has been performed on this widget.
  ///
  /// 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.
  ///
  /// See also:
  ///
  ///  * [SemanticsConfiguration.decreasedValue] for a description of how this
  ///    is exposed in TalkBack and VoiceOver.
  final String decreasedValue;

  /// Provides a brief textual description of the result of an action performed
  /// on the widget.
  ///
884
  /// If a hint is provided, there must either be an ambient [Directionality]
885 886 887 888 889 890 891 892
  /// or an explicit [textDirection] should be provided.
  ///
  /// See also:
  ///
  ///  * [SemanticsConfiguration.hint] for a description of how this is exposed
  ///    in TalkBack and VoiceOver.
  final String hint;

893 894 895 896 897 898 899 900 901 902
  /// 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.
  final SemanticsHintOverrides hintOverrides;

903 904 905 906 907 908
  /// The reading direction of the [label], [value], [hint], [increasedValue],
  /// and [decreasedValue].
  ///
  /// Defaults to the ambient [Directionality].
  final TextDirection textDirection;

909 910
  /// Determines the position of this node among its siblings in the traversal
  /// sort order.
911 912 913 914
  ///
  /// 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).
915
  final SemanticsSortKey sortKey;
916

917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957
  /// 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.
  final VoidCallback onTap;

  /// 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.
  final VoidCallback onLongPress;

  /// 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.
  final VoidCallback onScrollLeft;

  /// 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
958
  /// left and then right in one motion path. On Android, [onScrollDown] and
959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014
  /// [onScrollRight] share the same gesture. Therefore, only on of them should
  /// be provided.
  final VoidCallback onScrollRight;

  /// 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.
  final VoidCallback onScrollUp;

  /// 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.
  final VoidCallback onScrollDown;

  /// 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.
  final VoidCallback onIncrease;

  /// 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.
  final VoidCallback onDecrease;

1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039
  /// 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.
  final VoidCallback onCopy;

  /// 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.
  final VoidCallback onCut;

  /// 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.
  final VoidCallback onPaste;

1040 1041 1042 1043 1044 1045 1046
  /// The handler for [SemanticsAction.onMoveCursorForwardByCharacter].
  ///
  /// 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.
1047
  final MoveCursorHandler onMoveCursorForwardByCharacter;
1048 1049 1050 1051 1052 1053 1054 1055

  /// The handler for [SemanticsAction.onMoveCursorBackwardByCharacter].
  ///
  /// 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.
1056
  final MoveCursorHandler onMoveCursorBackwardByCharacter;
1057

1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075
  /// The handler for [SemanticsAction.onMoveCursorForwardByWord].
  ///
  /// 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.
  final MoveCursorHandler onMoveCursorForwardByWord;

  /// The handler for [SemanticsAction.onMoveCursorBackwardByWord].
  ///
  /// 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.
  final MoveCursorHandler onMoveCursorBackwardByWord;

1076 1077 1078 1079 1080 1081 1082 1083 1084
  /// 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.
  final SetSelectionHandler onSetSelection;

1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099
  /// 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
1100 1101
  ///    focus is removed from the node.
  ///  * [FocusNode], [FocusScope], [FocusManager], which manage the input focus.
1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118
  final VoidCallback onDidGainAccessibilityFocus;

  /// 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
1119 1120
  ///    accessibility focus.
  ///  * [FocusNode], [FocusScope], [FocusManager], which manage the input focus.
1121 1122
  final VoidCallback onDidLoseAccessibilityFocus;

1123 1124 1125 1126 1127 1128 1129 1130 1131
  /// 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.
  final VoidCallback onDismiss;

1132
  /// A map from each supported [CustomSemanticsAction] to a provided handler.
1133
  ///
1134 1135 1136 1137
  /// The handler associated with each custom action is called whenever a
  /// semantics event of type [SemanticsEvent.customEvent] is received. The
  /// 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.
1138
  ///
1139
  /// See also:
1140
  ///
1141
  ///  * [CustomSemanticsAction], for an explanation of custom actions.
1142 1143
  final Map<CustomSemanticsAction, VoidCallback> customSemanticsActions;

1144
  @override
1145 1146
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
1147 1148 1149 1150 1151 1152 1153 1154
    properties.add(DiagnosticsProperty<bool>('checked', checked, defaultValue: null));
    properties.add(DiagnosticsProperty<bool>('selected', selected, defaultValue: null));
    properties.add(StringProperty('label', label, defaultValue: ''));
    properties.add(StringProperty('value', value));
    properties.add(StringProperty('hint', hint));
    properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
    properties.add(DiagnosticsProperty<SemanticsSortKey>('sortKey', sortKey, defaultValue: null));
    properties.add(DiagnosticsProperty<SemanticsHintOverrides>('hintOverrides', hintOverrides));
1155
  }
1156 1157 1158

  @override
  String toStringShort() => '$runtimeType'; // the hashCode isn't important since we're immutable
1159 1160 1161 1162 1163 1164 1165 1166
}

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

1167 1168 1169 1170 1171 1172
/// 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.
1173
class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
1174 1175 1176 1177
  /// Creates a semantic node.
  ///
  /// Each semantic node has a unique identifier that is assigned when the node
  /// is created.
Hixie's avatar
Hixie committed
1178
  SemanticsNode({
1179
    this.key,
1180
    VoidCallback showOnScreen,
1181
  }) : id = _generateNewId(),
1182
       _showOnScreen = showOnScreen;
Hixie's avatar
Hixie committed
1183

1184 1185 1186
  /// 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
1187
  SemanticsNode.root({
1188
    this.key,
1189 1190
    VoidCallback showOnScreen,
    SemanticsOwner owner,
1191
  }) : id = 0,
1192
       _showOnScreen = showOnScreen {
1193
    attach(owner);
Hixie's avatar
Hixie committed
1194 1195
  }

1196 1197 1198 1199 1200 1201 1202 1203

  // 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
1204 1205
  static int _lastIdentifier = 0;
  static int _generateNewId() {
1206
    _lastIdentifier = (_lastIdentifier + 1) % _maxFrameworkAccessibilityIdentifier;
Hixie's avatar
Hixie committed
1207 1208 1209
    return _lastIdentifier;
  }

1210 1211 1212 1213 1214 1215
  /// 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.
  final Key key;

1216 1217 1218 1219 1220
  /// The unique identifier for this node.
  ///
  /// The root node has an id of zero. Other nodes are given a unique id when
  /// they are created.
  final int id;
Hixie's avatar
Hixie committed
1221

1222
  final VoidCallback _showOnScreen;
Hixie's avatar
Hixie committed
1223 1224 1225

  // GEOMETRY

1226 1227 1228
  /// The transform from this node's coordinate system to its parent's coordinate system.
  ///
  /// By default, the transform is null, which represents the identity
1229
  /// transformation (i.e., that this node has the same coordinate system as its
1230
  /// parent).
Hixie's avatar
Hixie committed
1231
  Matrix4 get transform => _transform;
1232
  Matrix4 _transform;
1233
  set transform(Matrix4 value) {
Hixie's avatar
Hixie committed
1234
    if (!MatrixUtils.matrixEquals(_transform, value)) {
1235
      _transform = MatrixUtils.isIdentity(value) ? null : value;
Hixie's avatar
Hixie committed
1236 1237 1238 1239
      _markDirty();
    }
  }

1240
  /// The bounding box for this node in its coordinate system.
Hixie's avatar
Hixie committed
1241 1242
  Rect get rect => _rect;
  Rect _rect = Rect.zero;
1243
  set rect(Rect value) {
Hixie's avatar
Hixie committed
1244
    assert(value != null);
1245
    assert(value.isFinite, '$this (with $owner) tried to set a non-finite rect.');
Hixie's avatar
Hixie committed
1246 1247 1248 1249 1250 1251
    if (_rect != value) {
      _rect = value;
      _markDirty();
    }
  }

1252
  /// The semantic clip from an ancestor that was applied to this node.
1253
  ///
1254 1255
  /// Expressed in the coordinate system of the node. May be null if no clip has
  /// been applied.
1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284
  ///
  /// 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.
  Rect parentSemanticsClipRect;

  /// 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.
  Rect parentPaintClipRect;
Hixie's avatar
Hixie committed
1285

1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302
  /// 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
  /// ascending [RenderObjects] are merged into each other to form that
  /// [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].
  double elevationAdjustment;

1303 1304 1305 1306 1307 1308 1309 1310
  /// 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.
  int indexInParent;

1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321
  /// 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
1322

1323
  // MERGING
Hixie's avatar
Hixie committed
1324

1325 1326 1327 1328
  /// Whether this node merges its semantic information into an ancestor node.
  bool get isMergedIntoParent => _isMergedIntoParent;
  bool _isMergedIntoParent = false;
  set isMergedIntoParent(bool value) {
1329
    assert(value != null);
1330
    if (_isMergedIntoParent == value)
1331
      return;
1332
    _isMergedIntoParent = value;
1333 1334
    _markDirty();
  }
1335

1336 1337 1338 1339 1340 1341
  /// 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:
1342
  ///
1343 1344 1345
  ///  * [isMergedIntoParent]
  ///  * [mergeAllDescendantsIntoThisNode]
  bool get isPartOfNodeMerging => mergeAllDescendantsIntoThisNode || isMergedIntoParent;
1346

1347 1348 1349
  /// Whether this node and all of its descendants should be treated as one logical entity.
  bool get mergeAllDescendantsIntoThisNode => _mergeAllDescendantsIntoThisNode;
  bool _mergeAllDescendantsIntoThisNode = _kEmptyConfig.isMergingSemanticsOfDescendants;
1350 1351


1352
  // CHILDREN
Hixie's avatar
Hixie committed
1353

1354 1355
  /// Contains the children in inverse hit test order (i.e. paint order).
  List<SemanticsNode> _children;
1356

1357 1358 1359 1360 1361
  /// 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.
  List<SemanticsNode> _debugPreviousSnapshot;

1362 1363
  void _replaceChildren(List<SemanticsNode> newChildren) {
    assert(!newChildren.any((SemanticsNode child) => child == this));
Hixie's avatar
Hixie committed
1364
    assert(() {
1365
      if (identical(newChildren, _children)) {
1366
        final List<DiagnosticsNode> mutationErrors = <DiagnosticsNode>[];
1367
        if (newChildren.length != _debugPreviousSnapshot.length) {
1368
          mutationErrors.add(ErrorDescription(
1369 1370
            'The list\'s length has changed from ${_debugPreviousSnapshot.length} '
            'to ${newChildren.length}.'
1371
          ));
1372 1373 1374
        } else {
          for (int i = 0; i < newChildren.length; i++) {
            if (!identical(newChildren[i], _debugPreviousSnapshot[i])) {
1375 1376 1377 1378 1379 1380
              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));
1381 1382 1383 1384
            }
          }
        }
        if (mutationErrors.isNotEmpty) {
1385 1386 1387 1388
          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:'),
1389
            ...mutationErrors,
1390
          ]);
1391 1392
        }
      }
1393
      assert(!newChildren.any((SemanticsNode node) => node.isMergedIntoParent) || isPartOfNodeMerging);
1394

1395
      _debugPreviousSnapshot = List<SemanticsNode>.from(newChildren);
1396

Hixie's avatar
Hixie committed
1397 1398 1399
      SemanticsNode ancestor = this;
      while (ancestor.parent is SemanticsNode)
        ancestor = ancestor.parent;
1400
      assert(!newChildren.any((SemanticsNode child) => child == ancestor));
Hixie's avatar
Hixie committed
1401
      return true;
1402
    }());
Hixie's avatar
Hixie committed
1403
    assert(() {
1404
      final Set<SemanticsNode> seenChildren = <SemanticsNode>{};
1405
      for (SemanticsNode child in newChildren)
Hixie's avatar
Hixie committed
1406 1407
        assert(seenChildren.add(child)); // check for duplicate adds
      return true;
1408
    }());
Hixie's avatar
Hixie committed
1409

1410
    // The goal of this function is updating sawChange.
Hixie's avatar
Hixie committed
1411 1412 1413 1414
    if (_children != null) {
      for (SemanticsNode child in _children)
        child._dead = true;
    }
1415 1416
    if (newChildren != null) {
      for (SemanticsNode child in newChildren) {
1417
        assert(!child.isInvisible, 'Child $child is invisible and should not be added as a child of $this.');
Hixie's avatar
Hixie committed
1418
        child._dead = false;
1419
      }
Hixie's avatar
Hixie committed
1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433
    }
    bool sawChange = false;
    if (_children != null) {
      for (SemanticsNode child in _children) {
        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;
        }
      }
    }
1434 1435
    if (newChildren != null) {
      for (SemanticsNode child in newChildren) {
Hixie's avatar
Hixie committed
1436 1437 1438
        if (child.parent != this) {
          if (child.parent != null) {
            // we're rebuilding the tree from the bottom up, so it's possible
1439
            // that our child was, in the last pass, a child of one of our
Hixie's avatar
Hixie committed
1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450
            // 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);
          }
          assert(!child.attached);
          adoptChild(child);
          sawChange = true;
        }
      }
    }
1451
    if (!sawChange && _children != null) {
1452 1453
      assert(newChildren != null);
      assert(newChildren.length == _children.length);
1454 1455
      // Did the order change?
      for (int i = 0; i < _children.length; i++) {
1456
        if (_children[i].id != newChildren[i].id) {
1457 1458 1459 1460 1461
          sawChange = true;
          break;
        }
      }
    }
1462
    _children = newChildren;
Hixie's avatar
Hixie committed
1463 1464 1465 1466
    if (sawChange)
      _markDirty();
  }

1467 1468 1469
  /// Whether this node has a non-zero number of children.
  bool get hasChildren => _children?.isNotEmpty ?? false;
  bool _dead = false;
1470

1471 1472
  /// The number of children this node has.
  int get childrenCount => hasChildren ? _children.length : 0;
1473

1474 1475
  /// Visits the immediate children of this node.
  ///
1476 1477 1478
  /// This function calls visitor for each immediate child until visitor returns
  /// false. Returns true if all the visitor calls returned true, otherwise
  /// returns false.
1479
  void visitChildren(SemanticsNodeVisitor visitor) {
Hixie's avatar
Hixie committed
1480
    if (_children != null) {
1481 1482 1483 1484
      for (SemanticsNode child in _children) {
        if (!visitor(child))
          return;
      }
Hixie's avatar
Hixie committed
1485 1486 1487
    }
  }

1488 1489
  /// Visit all the descendants of this node.
  ///
1490
  /// This function calls visitor for each descendant in a pre-order traversal
1491 1492
  /// until visitor returns false. Returns true if all the visitor calls
  /// returned true, otherwise returns false.
Hixie's avatar
Hixie committed
1493 1494 1495 1496 1497 1498 1499 1500 1501 1502
  bool _visitDescendants(SemanticsNodeVisitor visitor) {
    if (_children != null) {
      for (SemanticsNode child in _children) {
        if (!visitor(child) || !child._visitDescendants(visitor))
          return false;
      }
    }
    return true;
  }

1503 1504 1505 1506 1507 1508 1509 1510 1511 1512
  // AbstractNode OVERRIDES

  @override
  SemanticsOwner get owner => super.owner;

  @override
  SemanticsNode get parent => super.parent;

  @override
  void redepthChildren() {
1513
    _children?.forEach(redepthChild);
1514 1515
  }

1516
  @override
1517
  void attach(SemanticsOwner owner) {
1518
    super.attach(owner);
1519 1520
    assert(!owner._nodes.containsKey(id));
    owner._nodes[id] = this;
1521 1522 1523 1524 1525
    owner._detachedNodes.remove(this);
    if (_dirty) {
      _dirty = false;
      _markDirty();
    }
Hixie's avatar
Hixie committed
1526 1527
    if (_children != null) {
      for (SemanticsNode child in _children)
1528
        child.attach(owner);
Hixie's avatar
Hixie committed
1529 1530
    }
  }
1531 1532

  @override
Hixie's avatar
Hixie committed
1533
  void detach() {
1534
    assert(owner._nodes.containsKey(id));
1535
    assert(!owner._detachedNodes.contains(this));
1536
    owner._nodes.remove(id);
1537
    owner._detachedNodes.add(this);
Hixie's avatar
Hixie committed
1538
    super.detach();
1539
    assert(owner == null);
Hixie's avatar
Hixie committed
1540
    if (_children != null) {
1541 1542 1543 1544 1545 1546
      for (SemanticsNode child in _children) {
        // 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
1547
    }
1548 1549 1550 1551
    // 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
1552 1553
  }

1554 1555
  // DIRTY MANAGEMENT

Hixie's avatar
Hixie committed
1556 1557 1558 1559 1560
  bool _dirty = false;
  void _markDirty() {
    if (_dirty)
      return;
    _dirty = true;
1561 1562 1563 1564
    if (attached) {
      assert(!owner._detachedNodes.contains(this));
      owner._dirtyNodes.add(this);
    }
Hixie's avatar
Hixie committed
1565 1566
  }

1567 1568
  bool _isDifferentFromCurrentSemanticAnnotation(SemanticsConfiguration config) {
    return _label != config.label ||
1569
        _hint != config.hint ||
1570 1571
        _elevation != config.elevation ||
        _thickness != config.thickness ||
1572
        _decreasedValue != config.decreasedValue ||
1573
        _value != config.value ||
1574
        _increasedValue != config.increasedValue ||
1575 1576
        _flags != config._flags ||
        _textDirection != config.textDirection ||
1577
        _sortKey != config._sortKey ||
1578
        _textSelection != config._textSelection ||
1579 1580 1581
        _scrollPosition != config._scrollPosition ||
        _scrollExtentMax != config._scrollExtentMax ||
        _scrollExtentMin != config._scrollExtentMin ||
1582
        _actionsAsBits != config._actionsAsBits ||
1583
        indexInParent != config.indexInParent ||
1584
        platformViewId != config.platformViewId ||
1585 1586
        _maxValueLength != config._maxValueLength ||
        _currentValueLength != config._currentValueLength ||
1587 1588 1589 1590 1591
        _mergeAllDescendantsIntoThisNode != config.isMergingSemanticsOfDescendants;
  }

  // TAGS, LABELS, ACTIONS

1592
  Map<SemanticsAction, _SemanticsActionHandler> _actions = _kEmptyConfig._actions;
1593
  Map<CustomSemanticsAction, VoidCallback> _customSemanticsActions = _kEmptyConfig._customSemanticsActions;
1594

1595 1596
  int _actionsAsBits = _kEmptyConfig._actionsAsBits;

1597 1598 1599
  /// The [SemanticsTag]s this node is tagged with.
  ///
  /// Tags are used during the construction of the semantics tree. They are not
1600
  /// transferred to the engine.
1601 1602 1603 1604 1605 1606 1607
  Set<SemanticsTag> tags;

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

  int _flags = _kEmptyConfig._flags;

1608 1609
  /// Whether this node currently has a given [SemanticsFlag].
  bool hasFlag(SemanticsFlag flag) => _flags & flag.index != 0;
1610 1611 1612

  /// A textual description of this node.
  ///
1613
  /// The reading direction is given by [textDirection].
1614 1615 1616
  String get label => _label;
  String _label = _kEmptyConfig.label;

1617 1618
  /// A textual description for the current value of the node.
  ///
1619
  /// The reading direction is given by [textDirection].
1620 1621 1622
  String get value => _value;
  String _value = _kEmptyConfig.value;

1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642
  /// The value that [value] will have after a [SemanticsAction.decrease] action
  /// has been performed.
  ///
  /// This property is only valid if the [SemanticsAction.decrease] action is
  /// available on this node.
  ///
  /// The reading direction is given by [textDirection].
  String get decreasedValue => _decreasedValue;
  String _decreasedValue = _kEmptyConfig.decreasedValue;

  /// The value that [value] will have after a [SemanticsAction.increase] action
  /// has been performed.
  ///
  /// This property is only valid if the [SemanticsAction.increase] action is
  /// available on this node.
  ///
  /// The reading direction is given by [textDirection].
  String get increasedValue => _increasedValue;
  String _increasedValue = _kEmptyConfig.increasedValue;

1643 1644
  /// A brief description of the result of performing an action on this node.
  ///
1645
  /// The reading direction is given by [textDirection].
1646 1647 1648
  String get hint => _hint;
  String _hint = _kEmptyConfig.hint;

1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672
  /// 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].
  ///
1673
  /// {@tool sample}
1674
  /// The following code stacks three [PhysicalModel]s on top of each other
1675 1676 1677 1678 1679 1680 1681 1682
  /// 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)
1683
  ///
1684 1685 1686 1687
  /// 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.
1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703
  /// ```dart
  /// PhysicalModel( // A
  ///   color: Colors.amber,
  ///   elevation: 0.0,
  ///   child: Semantics(
  ///     explicitChildNodes: true,
  ///     child: PhysicalModel( // B
  ///       color: Colors.brown,
  ///       elevation: 5.0,
  ///       child: PhysicalModel( // C
  ///         color: Colors.cyan,
  ///         elevation: 10.0,
  ///         child: Placeholder(),
  ///       ),
  ///     ),
  ///   ),
1704
  /// )
1705
  /// ```
1706
  /// {@end-tool}
1707 1708 1709 1710 1711 1712 1713 1714
  ///
  /// 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;

1715 1716 1717 1718 1719
  /// Provides hint values which override the default hints on supported
  /// platforms.
  SemanticsHintOverrides get hintOverrides => _hintOverrides;
  SemanticsHintOverrides _hintOverrides;

1720 1721
  /// The reading direction for [label], [value], [hint], [increasedValue], and
  /// [decreasedValue].
1722 1723 1724
  TextDirection get textDirection => _textDirection;
  TextDirection _textDirection = _kEmptyConfig.textDirection;

1725 1726 1727 1728 1729 1730 1731 1732
  /// 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).
  SemanticsSortKey get sortKey => _sortKey;
  SemanticsSortKey _sortKey;
1733

1734 1735 1736 1737 1738
  /// The currently selected text (or the position of the cursor) within [value]
  /// if this node represents a text field.
  TextSelection get textSelection => _textSelection;
  TextSelection _textSelection;

1739 1740 1741 1742 1743
  /// If this node represents a text field, this indicates whether or not it's
  /// a multi-line text field.
  bool get isMultiline => _isMultiline;
  bool _isMultiline;

1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754
  /// The total number of scrollable children that contribute to semantics.
  ///
  /// If the number of children are unknown or unbounded, this value will be
  /// null.
  int get scrollChildCount => _scrollChildCount;
  int _scrollChildCount;

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

1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778
  /// 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.
  double get scrollPosition => _scrollPosition;
  double _scrollPosition;

  /// 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.
  double get scrollExtentMax => _scrollExtentMax;
  double _scrollExtentMax;

Josh Soref's avatar
Josh Soref committed
1779
  /// Indicates the minimum in-range value for [scrollPosition] if the node is
1780 1781 1782 1783 1784 1785 1786 1787 1788 1789
  /// scrollable.
  ///
  /// This value may be infinity if the scroll is unbound.
  ///
  /// See also:
  ///
  ///  * [ScrollPosition.minScrollExtent] from where this value is usually taken.
  double get scrollExtentMin => _scrollExtentMin;
  double _scrollExtentMin;

1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803
  /// 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.
  int get platformViewId => _platformViewId;
  int _platformViewId;

1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825
  /// 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.
  int get maxValueLength => _maxValueLength;
  int _maxValueLength;

  /// 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.
  int get currentValueLength => _currentValueLength;
  int _currentValueLength;

1826 1827
  bool _canPerformAction(SemanticsAction action) => _actions.containsKey(action);

1828
  static final SemanticsConfiguration _kEmptyConfig = SemanticsConfiguration();
1829

1830
  /// Reconfigures the properties of this object to describe the configuration
1831
  /// provided in the `config` argument and the children listed in the
1832 1833 1834 1835 1836 1837 1838
  /// `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.
1839 1840
  void updateWith({
    @required SemanticsConfiguration config,
1841
    List<SemanticsNode> childrenInInversePaintOrder,
1842 1843 1844 1845 1846
  }) {
    config ??= _kEmptyConfig;
    if (_isDifferentFromCurrentSemanticAnnotation(config))
      _markDirty();

1847 1848 1849 1850 1851
    assert(
      config.platformViewId == null || childrenInInversePaintOrder.isEmpty,
      'SemanticsNodes with children must not specify a platformViewId.'
    );

1852
    _label = config.label;
1853
    _decreasedValue = config.decreasedValue;
1854
    _value = config.value;
1855
    _increasedValue = config.increasedValue;
1856
    _hint = config.hint;
1857
    _hintOverrides = config.hintOverrides;
1858 1859
    _elevation = config.elevation;
    _thickness = config.thickness;
1860 1861
    _flags = config._flags;
    _textDirection = config.textDirection;
1862
    _sortKey = config.sortKey;
1863 1864
    _actions = Map<SemanticsAction, _SemanticsActionHandler>.from(config._actions);
    _customSemanticsActions = Map<CustomSemanticsAction, VoidCallback>.from(config._customSemanticsActions);
1865
    _actionsAsBits = config._actionsAsBits;
1866
    _textSelection = config._textSelection;
1867
    _isMultiline = config.isMultiline;
1868 1869 1870
    _scrollPosition = config._scrollPosition;
    _scrollExtentMax = config._scrollExtentMax;
    _scrollExtentMin = config._scrollExtentMin;
1871
    _mergeAllDescendantsIntoThisNode = config.isMergingSemanticsOfDescendants;
1872 1873 1874
    _scrollChildCount = config.scrollChildCount;
    _scrollIndex = config.scrollIndex;
    indexInParent = config.indexInParent;
1875
    _platformViewId = config._platformViewId;
1876 1877
    _maxValueLength = config._maxValueLength;
    _currentValueLength = config._currentValueLength;
1878
    _replaceChildren(childrenInInversePaintOrder ?? const <SemanticsNode>[]);
1879 1880 1881 1882 1883 1884 1885 1886 1887

    assert(
      !_canPerformAction(SemanticsAction.increase) || (_value == '') == (_increasedValue == ''),
      'A SemanticsNode with action "increase" needs to be annotated with either both "value" and "increasedValue" or neither',
    );
    assert(
      !_canPerformAction(SemanticsAction.decrease) || (_value == '') == (_decreasedValue == ''),
      'A SemanticsNode with action "increase" needs to be annotated with either both "value" and "decreasedValue" or neither',
    );
1888 1889 1890
  }


1891 1892 1893 1894 1895 1896 1897
  /// 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;
1898
    int actions = _actionsAsBits;
1899
    String label = _label;
1900 1901
    String hint = _hint;
    String value = _value;
1902 1903
    String increasedValue = _increasedValue;
    String decreasedValue = _decreasedValue;
Ian Hickson's avatar
Ian Hickson committed
1904
    TextDirection textDirection = _textDirection;
1905
    Set<SemanticsTag> mergedTags = tags == null ? null : Set<SemanticsTag>.from(tags);
1906
    TextSelection textSelection = _textSelection;
1907 1908
    int scrollChildCount = _scrollChildCount;
    int scrollIndex = _scrollIndex;
1909 1910 1911
    double scrollPosition = _scrollPosition;
    double scrollExtentMax = _scrollExtentMax;
    double scrollExtentMin = _scrollExtentMin;
1912
    int platformViewId = _platformViewId;
1913 1914
    int maxValueLength = _maxValueLength;
    int currentValueLength = _currentValueLength;
1915 1916
    final double elevation = _elevation;
    double thickness = _thickness;
1917
    final Set<int> customSemanticsActionIds = <int>{};
1918 1919
    for (CustomSemanticsAction action in _customSemanticsActions.keys)
      customSemanticsActionIds.add(CustomSemanticsAction.getIdentifier(action));
1920 1921
    if (hintOverrides != null) {
      if (hintOverrides.onTapHint != null) {
1922
        final CustomSemanticsAction action = CustomSemanticsAction.overridingAction(
1923 1924 1925 1926 1927 1928
          hint: hintOverrides.onTapHint,
          action: SemanticsAction.tap,
        );
        customSemanticsActionIds.add(CustomSemanticsAction.getIdentifier(action));
      }
      if (hintOverrides.onLongPressHint != null) {
1929
        final CustomSemanticsAction action = CustomSemanticsAction.overridingAction(
1930 1931 1932 1933 1934 1935
          hint: hintOverrides.onLongPressHint,
          action: SemanticsAction.longPress,
        );
        customSemanticsActionIds.add(CustomSemanticsAction.getIdentifier(action));
      }
    }
1936 1937 1938

    if (mergeAllDescendantsIntoThisNode) {
      _visitDescendants((SemanticsNode node) {
1939
        assert(node.isMergedIntoParent);
1940
        flags |= node._flags;
1941
        actions |= node._actionsAsBits;
Ian Hickson's avatar
Ian Hickson committed
1942
        textDirection ??= node._textDirection;
1943
        textSelection ??= node._textSelection;
1944 1945
        scrollChildCount ??= node._scrollChildCount;
        scrollIndex ??= node._scrollIndex;
1946 1947 1948
        scrollPosition ??= node._scrollPosition;
        scrollExtentMax ??= node._scrollExtentMax;
        scrollExtentMin ??= node._scrollExtentMin;
1949
        platformViewId ??= node._platformViewId;
1950 1951
        maxValueLength ??= node._maxValueLength;
        currentValueLength ??= node._currentValueLength;
1952 1953
        if (value == '' || value == null)
          value = node._value;
1954 1955 1956 1957
        if (increasedValue == '' || increasedValue == null)
          increasedValue = node._increasedValue;
        if (decreasedValue == '' || decreasedValue == null)
          decreasedValue = node._decreasedValue;
1958
        if (node.tags != null) {
1959
          mergedTags ??= <SemanticsTag>{};
1960 1961
          mergedTags.addAll(node.tags);
        }
1962 1963 1964 1965
        if (node._customSemanticsActions != null) {
          for (CustomSemanticsAction action in _customSemanticsActions.keys)
            customSemanticsActionIds.add(CustomSemanticsAction.getIdentifier(action));
        }
1966 1967
        if (node.hintOverrides != null) {
          if (node.hintOverrides.onTapHint != null) {
1968
            final CustomSemanticsAction action = CustomSemanticsAction.overridingAction(
1969 1970 1971 1972 1973 1974
              hint: node.hintOverrides.onTapHint,
              action: SemanticsAction.tap,
            );
            customSemanticsActionIds.add(CustomSemanticsAction.getIdentifier(action));
          }
          if (node.hintOverrides.onLongPressHint != null) {
1975
            final CustomSemanticsAction action = CustomSemanticsAction.overridingAction(
1976 1977 1978 1979 1980 1981
              hint: node.hintOverrides.onLongPressHint,
              action: SemanticsAction.longPress,
            );
            customSemanticsActionIds.add(CustomSemanticsAction.getIdentifier(action));
          }
        }
1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993
        label = _concatStrings(
          thisString: label,
          thisTextDirection: textDirection,
          otherString: node._label,
          otherTextDirection: node._textDirection,
        );
        hint = _concatStrings(
          thisString: hint,
          thisTextDirection: textDirection,
          otherString: node._hint,
          otherTextDirection: node._textDirection,
        );
1994 1995 1996

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

1997 1998 1999 2000
        return true;
      });
    }

2001
    return SemanticsData(
2002 2003 2004
      flags: flags,
      actions: actions,
      label: label,
2005
      value: value,
2006 2007
      increasedValue: increasedValue,
      decreasedValue: decreasedValue,
2008
      hint: hint,
Ian Hickson's avatar
Ian Hickson committed
2009
      textDirection: textDirection,
2010
      rect: rect,
2011
      transform: transform,
2012 2013
      elevation: elevation,
      thickness: thickness,
2014
      tags: mergedTags,
2015
      textSelection: textSelection,
2016 2017
      scrollChildCount: scrollChildCount,
      scrollIndex: scrollIndex,
2018 2019 2020
      scrollPosition: scrollPosition,
      scrollExtentMax: scrollExtentMax,
      scrollExtentMin: scrollExtentMin,
2021
      platformViewId: platformViewId,
2022 2023
      maxValueLength: maxValueLength,
      currentValueLength: currentValueLength,
2024
      customSemanticsActionIds: customSemanticsActionIds.toList()..sort(),
2025 2026 2027
    );
  }

2028
  static Float64List _initIdentityTransform() {
2029
    return Matrix4.identity().storage;
2030 2031
  }

2032 2033
  static final Int32List _kEmptyChildList = Int32List(0);
  static final Int32List _kEmptyCustomSemanticsActionsList = Int32List(0);
2034 2035
  static final Float64List _kIdentityTransform = _initIdentityTransform();

2036
  void _addToUpdate(ui.SemanticsUpdateBuilder builder, Set<int> customSemanticsActionIdsUpdate) {
2037 2038
    assert(_dirty);
    final SemanticsData data = getSemanticsData();
2039 2040
    Int32List childrenInTraversalOrder;
    Int32List childrenInHitTestOrder;
2041
    if (!hasChildren || mergeAllDescendantsIntoThisNode) {
2042 2043
      childrenInTraversalOrder = _kEmptyChildList;
      childrenInHitTestOrder = _kEmptyChildList;
2044
    } else {
2045
      final int childCount = _children.length;
2046
      final List<SemanticsNode> sortedChildren = _childrenInTraversalOrder();
2047
      childrenInTraversalOrder = Int32List(childCount);
2048 2049 2050 2051 2052
      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.
2053
      childrenInHitTestOrder = Int32List(childCount);
2054
      for (int i = childCount - 1; i >= 0; i -= 1) {
2055
        childrenInHitTestOrder[i] = _children[childCount - i - 1].id;
2056
      }
Hixie's avatar
Hixie committed
2057
    }
2058 2059
    Int32List customSemanticsActionIds;
    if (data.customSemanticsActionIds?.isNotEmpty == true) {
2060
      customSemanticsActionIds = Int32List(data.customSemanticsActionIds.length);
2061 2062 2063 2064 2065
      for (int i = 0; i < data.customSemanticsActionIds.length; i++) {
        customSemanticsActionIds[i] = data.customSemanticsActionIds[i];
        customSemanticsActionIdsUpdate.add(data.customSemanticsActionIds[i]);
      }
    }
2066 2067 2068 2069 2070
    builder.updateNode(
      id: id,
      flags: data.flags,
      actions: data.actions,
      rect: data.rect,
2071
      label: data.label,
2072
      value: data.value,
2073 2074
      decreasedValue: data.decreasedValue,
      increasedValue: data.increasedValue,
2075
      hint: data.hint,
Ian Hickson's avatar
Ian Hickson committed
2076
      textDirection: data.textDirection,
2077 2078
      textSelectionBase: data.textSelection != null ? data.textSelection.baseOffset : -1,
      textSelectionExtent: data.textSelection != null ? data.textSelection.extentOffset : -1,
2079
      platformViewId: data.platformViewId ?? -1,
2080 2081
      maxValueLength: data.maxValueLength ?? -1,
      currentValueLength: data.currentValueLength ?? -1,
2082 2083 2084 2085 2086
      scrollChildren: data.scrollChildCount ?? 0,
      scrollIndex: data.scrollIndex ?? 0 ,
      scrollPosition: data.scrollPosition ?? double.nan,
      scrollExtentMax: data.scrollExtentMax ?? double.nan,
      scrollExtentMin: data.scrollExtentMin ?? double.nan,
2087
      transform: data.transform?.storage ?? _kIdentityTransform,
2088 2089
      elevation: data.elevation,
      thickness: data.thickness,
2090 2091
      childrenInTraversalOrder: childrenInTraversalOrder,
      childrenInHitTestOrder: childrenInHitTestOrder,
2092
      additionalActions: customSemanticsActionIds ?? _kEmptyCustomSemanticsActionsList,
2093 2094
    );
    _dirty = false;
Hixie's avatar
Hixie committed
2095 2096
  }

2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139
  /// Builds a new list made of [_children] sorted in semantic traversal order.
  List<SemanticsNode> _childrenInTraversalOrder() {
    TextDirection inheritedTextDirection = textDirection;
    SemanticsNode ancestor = parent;
    while (inheritedTextDirection == null && ancestor != null) {
      inheritedTextDirection = ancestor.textDirection;
      ancestor = ancestor.parent;
    }

    List<SemanticsNode> childrenInDefaultOrder;
    if (inheritedTextDirection != null) {
      childrenInDefaultOrder = _childrenInDefaultOrder(_children, inheritedTextDirection);
    } 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>[];
    SemanticsSortKey lastSortKey;
    for (int position = 0; position < childrenInDefaultOrder.length; position += 1) {
      final SemanticsNode child = childrenInDefaultOrder[position];
      final SemanticsSortKey sortKey = child.sortKey;
      lastSortKey = position > 0
          ? childrenInDefaultOrder[position - 1].sortKey
          : null;
      final bool isCompatibleWithPreviousSortKey = position == 0 ||
          sortKey.runtimeType == lastSortKey.runtimeType &&
          (sortKey == null || sortKey.name == lastSortKey.name);
      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();
      }

2140
      sortNodes.add(_TraversalSortNode(
2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158
        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();
  }

2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171
  /// 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.
  ///
  /// For example, if this semantics node represents a scrollable list, a
  /// [ScrollCompletedSemanticsEvent] should be sent after a scroll action is completed.
  /// That way, the operating system can give additional feedback to the user
  /// about the state of the UI (e.g. on Android a ping sound is played to
  /// indicate a successful scroll in accessibility mode).
  void sendEvent(SemanticsEvent event) {
    if (!attached)
      return;
2172
    SystemChannels.accessibility.send(event.toMap(nodeId: id));
2173 2174
  }

2175
  @override
2176 2177 2178 2179
  String toStringShort() => '$runtimeType#$id';

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
2180
    super.debugFillProperties(properties);
2181 2182 2183
    bool hideOwner = true;
    if (_dirty) {
      final bool inDirtyNodes = owner != null && owner._dirtyNodes.contains(this);
2184
      properties.add(FlagProperty('inDirtyNodes', value: inDirtyNodes, ifTrue: 'dirty', ifFalse: 'STALE'));
2185 2186
      hideOwner = inDirtyNodes;
    }
2187 2188 2189
    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 ⛔️'));
2190 2191
    final Offset offset = transform != null ? MatrixUtils.getAsTranslation(transform) : null;
    if (offset != null) {
2192
      properties.add(DiagnosticsProperty<Rect>('rect', rect.shift(offset), showName: false));
2193 2194
    } else {
      final double scale = transform != null ? MatrixUtils.getAsScale(transform) : null;
2195
      String description;
2196
      if (scale != null) {
2197
        description = '$rect scaled by ${scale.toStringAsFixed(1)}x';
2198
      } else if (transform != null && !MatrixUtils.isIdentity(transform)) {
2199
        final String matrix = transform.toString().split('\n').take(4).map<String>((String line) => line.substring(4)).join('; ');
2200
        description = '$rect with transform [$matrix]';
2201
      }
2202
      properties.add(DiagnosticsProperty<Rect>('rect', rect, description: description, showName: false));
2203
    }
2204
    properties.add(IterableProperty<String>('tags', tags?.map((SemanticsTag tag) => tag.name), defaultValue: null));
2205
    final List<String> actions = _actions.keys.map<String>((SemanticsAction action) => describeEnum(action)).toList()..sort();
2206 2207 2208
    final List<String> customSemanticsActions = _customSemanticsActions.keys
      .map<String>((CustomSemanticsAction action) => action.label)
      .toList();
2209 2210
    properties.add(IterableProperty<String>('actions', actions, ifEmpty: null));
    properties.add(IterableProperty<String>('customActions', customSemanticsActions, ifEmpty: null));
2211
    final List<String> flags = SemanticsFlag.values.values.where((SemanticsFlag flag) => hasFlag(flag)).map((SemanticsFlag flag) => flag.toString().substring('SemanticsFlag.'.length)).toList();
2212 2213
    properties.add(IterableProperty<String>('flags', flags, ifEmpty: null));
    properties.add(FlagProperty('isInvisible', value: isInvisible, ifTrue: 'invisible'));
2214
    properties.add(FlagProperty('isHidden', value: hasFlag(SemanticsFlag.isHidden), ifTrue: 'HIDDEN'));
2215 2216 2217 2218 2219 2220 2221
    properties.add(StringProperty('label', _label, defaultValue: ''));
    properties.add(StringProperty('value', _value, defaultValue: ''));
    properties.add(StringProperty('increasedValue', _increasedValue, defaultValue: ''));
    properties.add(StringProperty('decreasedValue', _decreasedValue, defaultValue: ''));
    properties.add(StringProperty('hint', _hint, defaultValue: ''));
    properties.add(EnumProperty<TextDirection>('textDirection', _textDirection, defaultValue: null));
    properties.add(DiagnosticsProperty<SemanticsSortKey>('sortKey', sortKey, defaultValue: null));
2222
    if (_textSelection?.isValid == true)
2223
      properties.add(MessageProperty('text selection', '[${_textSelection.start}, ${_textSelection.end}]'));
2224
    properties.add(IntProperty('platformViewId', platformViewId, defaultValue: null));
2225 2226
    properties.add(IntProperty('maxValueLength', maxValueLength, defaultValue: null));
    properties.add(IntProperty('currentValueLength', currentValueLength, defaultValue: null));
2227 2228
    properties.add(IntProperty('scrollChildren', scrollChildCount, defaultValue: null));
    properties.add(IntProperty('scrollIndex', scrollIndex, defaultValue: null));
2229 2230 2231
    properties.add(DoubleProperty('scrollExtentMin', scrollExtentMin, defaultValue: null));
    properties.add(DoubleProperty('scrollPosition', scrollPosition, defaultValue: null));
    properties.add(DoubleProperty('scrollExtentMax', scrollExtentMax, defaultValue: null));
2232
    properties.add(DoubleProperty('elevation', elevation, defaultValue: 0.0));
2233
    properties.add(DoubleProperty('thickness', thickness, defaultValue: 0.0));
2234 2235 2236
  }

  /// Returns a string representation of this node and its descendants.
2237 2238 2239
  ///
  /// The order in which the children of the [SemanticsNode] will be printed is
  /// controlled by the [childOrder] parameter.
2240 2241
  @override
  String toStringDeep({
2242
    String prefixLineOne = '',
2243
    String prefixOtherLines,
2244 2245
    DiagnosticLevel minLevel = DiagnosticLevel.debug,
    DebugSemanticsDumpOrder childOrder = DebugSemanticsDumpOrder.traversalOrder,
2246
  }) {
2247
    assert(childOrder != null);
2248
    return toDiagnosticsNode(childOrder: childOrder).toStringDeep(prefixLineOne: prefixLineOne, prefixOtherLines: prefixOtherLines, minLevel: minLevel);
2249 2250 2251 2252 2253
  }

  @override
  DiagnosticsNode toDiagnosticsNode({
    String name,
2254 2255
    DiagnosticsTreeStyle style = DiagnosticsTreeStyle.sparse,
    DebugSemanticsDumpOrder childOrder = DebugSemanticsDumpOrder.traversalOrder,
2256
  }) {
2257
    return _SemanticsDiagnosticableNode(
2258 2259 2260 2261 2262 2263 2264 2265
      name: name,
      value: this,
      style: style,
      childOrder: childOrder,
    );
  }

  @override
2266
  List<DiagnosticsNode> debugDescribeChildren({ DebugSemanticsDumpOrder childOrder = DebugSemanticsDumpOrder.inverseHitTest }) {
2267
    return debugListChildrenInOrder(childOrder)
2268 2269
      .map<DiagnosticsNode>((SemanticsNode node) => node.toDiagnosticsNode(childOrder: childOrder))
      .toList();
2270 2271
  }

2272 2273
  /// Returns the list of direct children of this node in the specified order.
  List<SemanticsNode> debugListChildrenInOrder(DebugSemanticsDumpOrder childOrder) {
2274
    assert(childOrder != null);
2275 2276 2277
    if (_children == null)
      return const <SemanticsNode>[];

2278
    switch (childOrder) {
2279 2280
      case DebugSemanticsDumpOrder.inverseHitTest:
        return _children;
2281 2282
      case DebugSemanticsDumpOrder.traversalOrder:
        return _childrenInTraversalOrder();
2283 2284 2285 2286
    }
    assert(false);
    return null;
  }
2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303
}

/// 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({
    @required this.isLeadingEdge,
    @required this.offset,
    @required this.node,
  }) : assert(isLeadingEdge != null),
       assert(offset != null),
2304
       assert(offset.isFinite),
2305 2306 2307 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 2345 2346 2347 2348 2349 2350 2351 2352 2353 2354 2355 2356 2357 2358 2359 2360 2361 2362 2363 2364
       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) {
    return (offset - other.offset).sign.toInt();
  }
}

/// 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({
    @required this.startOffset,
    @required this.textDirection,
  }) : 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) {
    return (startOffset - other.startOffset).sign.toInt();
  }

  /// 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>[];
    for (SemanticsNode child in nodes) {
2365 2366
      // Using a small delta to shrink child rects removes overlapping cases.
      final Rect childRect = child.rect.deflate(0.1);
2367
      edges.add(_BoxEdge(
2368
        isLeadingEdge: true,
2369
        offset: _pointInParentCoordinates(child, childRect.topLeft).dx,
2370 2371
        node: child,
      ));
2372
      edges.add(_BoxEdge(
2373
        isLeadingEdge: false,
2374
        offset: _pointInParentCoordinates(child, childRect.bottomRight).dx,
2375 2376 2377 2378 2379 2380 2381 2382 2383 2384 2385
        node: child,
      ));
    }
    edges.sort();

    List<_SemanticsSortGroup> horizontalGroups = <_SemanticsSortGroup>[];
    _SemanticsSortGroup group;
    int depth = 0;
    for (_BoxEdge edge in edges) {
      if (edge.isLeadingEdge) {
        depth += 1;
2386
        group ??= _SemanticsSortGroup(
2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399 2400 2401 2402 2403 2404
          startOffset: edge.offset,
          textDirection: textDirection,
        );
        group.nodes.add(edge.node);
      } else {
        depth -= 1;
      }
      if (depth == 0) {
        horizontalGroups.add(group);
        group = null;
      }
    }
    horizontalGroups.sort();

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

2405 2406 2407
    return horizontalGroups
      .expand((_SemanticsSortGroup group) => group.sortedWithinKnot())
      .toList();
2408
  }
2409

2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426 2427 2428 2429 2430 2431 2432 2433 2434 2435 2436 2437 2438 2439 2440 2441 2442 2443 2444 2445 2446 2447 2448 2449 2450 2451 2452 2453 2454 2455 2456
  /// 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>{};
    for (SemanticsNode node in nodes) {
      nodeMap[node.id] = node;
      final Offset center = _pointInParentCoordinates(node, node.rect.center);
      for (SemanticsNode nextNode in nodes) {
        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>[];
2457
    final Set<int> visitedIds = <int>{};
2458 2459 2460 2461 2462 2463 2464 2465 2466 2467 2468 2469 2470 2471 2472 2473 2474 2475 2476 2477 2478
    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)) {
        search(edges[id]);
      }
      sortedIds.add(id);
    }

2479
    startNodes.map<int>((SemanticsNode node) => node.id).forEach(search);
2480
    return sortedIds.map<SemanticsNode>((int id) => nodeMap[id]).toList().reversed.toList();
2481 2482 2483
  }
}

2484 2485 2486 2487 2488
/// Converts `point` to the `node`'s parent's coordinate system.
Offset _pointInParentCoordinates(SemanticsNode node, Offset point) {
  if (node.transform == null) {
    return point;
  }
2489
  final Vector3 vector = Vector3(point.dx, point.dy, 0.0);
2490
  node.transform.transform3(vector);
2491
  return Offset(vector.x, vector.y);
2492 2493 2494 2495 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 2506 2507
}

/// 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>[];
  for (SemanticsNode child in children) {
2508
    assert(child.rect.isFinite);
2509 2510
    // Using a small delta to shrink child rects removes overlapping cases.
    final Rect childRect = child.rect.deflate(0.1);
2511
    edges.add(_BoxEdge(
2512
      isLeadingEdge: true,
2513
      offset: _pointInParentCoordinates(child, childRect.topLeft).dy,
2514 2515
      node: child,
    ));
2516
    edges.add(_BoxEdge(
2517
      isLeadingEdge: false,
2518
      offset: _pointInParentCoordinates(child, childRect.bottomRight).dy,
2519 2520 2521 2522 2523 2524 2525 2526 2527 2528 2529
      node: child,
    ));
  }
  edges.sort();

  final List<_SemanticsSortGroup> verticalGroups = <_SemanticsSortGroup>[];
  _SemanticsSortGroup group;
  int depth = 0;
  for (_BoxEdge edge in edges) {
    if (edge.isLeadingEdge) {
      depth += 1;
2530
      group ??= _SemanticsSortGroup(
2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544
        startOffset: edge.offset,
        textDirection: textDirection,
      );
      group.nodes.add(edge.node);
    } else {
      depth -= 1;
    }
    if (depth == 0) {
      verticalGroups.add(group);
      group = null;
    }
  }
  verticalGroups.sort();

2545 2546 2547
  return verticalGroups
    .expand((_SemanticsSortGroup group) => group.sortedWithinVerticalGroup())
    .toList();
2548 2549
}

2550 2551 2552 2553
/// 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.
2554
///
2555 2556
/// This implementation considers a [node]'s [sortKey] and its position within
/// the list of its siblings. [sortKey] takes precedence over position.
2557
class _TraversalSortNode implements Comparable<_TraversalSortNode> {
2558 2559 2560 2561 2562
  _TraversalSortNode({
    @required this.node,
    this.sortKey,
    @required this.position,
  })
2563
    : assert(node != null),
2564
      assert(position != null);
2565

2566 2567
  /// The node whose position this sort node determines.
  final SemanticsNode node;
2568

2569 2570 2571
  /// Determines the position of this node among its siblings.
  ///
  /// Sort keys take precedence over other attributes, such as
2572
  /// [position].
2573
  final SemanticsSortKey sortKey;
2574

2575 2576 2577
  /// Position within the list of siblings as determined by the default sort
  /// order.
  final int position;
2578 2579 2580

  @override
  int compareTo(_TraversalSortNode other) {
2581
    if (sortKey == null || other?.sortKey == null) {
2582
      return position - other.position;
2583
    }
2584
    return sortKey.compareTo(other.sortKey);
2585 2586 2587
  }
}

2588 2589 2590
/// Owns [SemanticsNode] objects and notifies listeners of changes to the
/// render tree semantics.
///
2591 2592 2593
/// To listen for semantic updates, call [PipelineOwner.ensureSemantics] to
/// obtain a [SemanticsHandle]. This will create a [SemanticsOwner] if
/// necessary.
2594
class SemanticsOwner extends ChangeNotifier {
2595
  final Set<SemanticsNode> _dirtyNodes = <SemanticsNode>{};
2596
  final Map<int, SemanticsNode> _nodes = <int, SemanticsNode>{};
2597
  final Set<SemanticsNode> _detachedNodes = <SemanticsNode>{};
2598
  final Map<int, CustomSemanticsAction> _actions = <int, CustomSemanticsAction>{};
2599

2600 2601 2602 2603 2604
  /// The root node of the semantics tree, if any.
  ///
  /// If the semantics tree is empty, returns null.
  SemanticsNode get rootSemanticsNode => _nodes[0];

2605
  @override
2606 2607 2608 2609
  void dispose() {
    _dirtyNodes.clear();
    _nodes.clear();
    _detachedNodes.clear();
2610
    super.dispose();
2611
  }
2612

2613
  /// Update the semantics using [Window.updateSemantics].
2614
  void sendSemanticsUpdate() {
Hixie's avatar
Hixie committed
2615 2616
    if (_dirtyNodes.isEmpty)
      return;
2617
    final Set<int> customSemanticsActionIds = <int>{};
2618
    final List<SemanticsNode> visitedNodes = <SemanticsNode>[];
2619
    while (_dirtyNodes.isNotEmpty) {
2620
      final List<SemanticsNode> localDirtyNodes = _dirtyNodes.where((SemanticsNode node) => !_detachedNodes.contains(node)).toList();
2621
      _dirtyNodes.clear();
2622
      _detachedNodes.clear();
2623 2624 2625 2626
      localDirtyNodes.sort((SemanticsNode a, SemanticsNode b) => a.depth - b.depth);
      visitedNodes.addAll(localDirtyNodes);
      for (SemanticsNode node in localDirtyNodes) {
        assert(node._dirty);
2627 2628
        assert(node.parent == null || !node.parent.isPartOfNodeMerging || node.isMergedIntoParent);
        if (node.isPartOfNodeMerging) {
2629
          assert(node.mergeAllDescendantsIntoThisNode || node.parent != null);
2630 2631 2632
          // if we're merged into our parent, make sure our parent is added to the dirty list
          if (node.parent != null && node.parent.isPartOfNodeMerging)
            node.parent._markDirty(); // this can add the node to the dirty list
2633
        }
2634
      }
2635
    }
2636
    visitedNodes.sort((SemanticsNode a, SemanticsNode b) => a.depth - b.depth);
2637
    final ui.SemanticsUpdateBuilder builder = ui.SemanticsUpdateBuilder();
2638
    for (SemanticsNode node in visitedNodes) {
Hixie's avatar
Hixie committed
2639 2640 2641 2642 2643 2644 2645 2646 2647 2648 2649 2650
      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)
2651
        node._addToUpdate(builder, customSemanticsActionIds);
Hixie's avatar
Hixie committed
2652 2653
    }
    _dirtyNodes.clear();
2654 2655 2656 2657
    for (int actionId in customSemanticsActionIds) {
      final CustomSemanticsAction action = CustomSemanticsAction.getAction(actionId);
      builder.updateCustomAction(id: actionId, label: action.label, hint: action.hint, overrideId: action.action?.index ?? -1);
    }
2658
    SemanticsBinding.instance.window.updateSemantics(builder.build());
2659
    notifyListeners();
Hixie's avatar
Hixie committed
2660 2661
  }

2662
  _SemanticsActionHandler _getSemanticsActionHandlerForId(int id, SemanticsAction action) {
Hixie's avatar
Hixie committed
2663
    SemanticsNode result = _nodes[id];
2664
    if (result != null && result.isPartOfNodeMerging && !result._canPerformAction(action)) {
Hixie's avatar
Hixie committed
2665
      result._visitDescendants((SemanticsNode node) {
2666
        if (node._canPerformAction(action)) {
Hixie's avatar
Hixie committed
2667 2668 2669 2670 2671 2672
          result = node;
          return false; // found node, abort walk
        }
        return true; // continue walk
      });
    }
2673
    if (result == null || !result._canPerformAction(action))
Hixie's avatar
Hixie committed
2674
      return null;
2675
    return result._actions[action];
Hixie's avatar
Hixie committed
2676 2677
  }

2678 2679 2680 2681
  /// 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.
2682 2683 2684
  ///
  /// If the given `action` requires arguments they need to be passed in via
  /// the `args` parameter.
2685
  void performAction(int id, SemanticsAction action, [ dynamic args ]) {
2686
    assert(action != null);
2687
    final _SemanticsActionHandler handler = _getSemanticsActionHandlerForId(id, action);
2688
    if (handler != null) {
2689
      handler(args);
2690 2691 2692 2693 2694 2695
      return;
    }

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

2698
  _SemanticsActionHandler _getSemanticsActionHandlerForPosition(SemanticsNode node, Offset position, SemanticsAction action) {
2699
    if (node.transform != null) {
2700
      final Matrix4 inverse = Matrix4.identity();
2701 2702 2703 2704 2705 2706 2707 2708 2709 2710 2711 2712 2713 2714 2715
      if (inverse.copyInverse(node.transform) == 0.0)
        return null;
      position = MatrixUtils.transformPoint(inverse, position);
    }
    if (!node.rect.contains(position))
      return null;
    if (node.mergeAllDescendantsIntoThisNode) {
      SemanticsNode result;
      node._visitDescendants((SemanticsNode child) {
        if (child._canPerformAction(action)) {
          result = child;
          return false;
        }
        return true;
      });
2716
      return result?._actions[action];
2717 2718 2719
    }
    if (node.hasChildren) {
      for (SemanticsNode child in node._children.reversed) {
2720
        final _SemanticsActionHandler handler = _getSemanticsActionHandlerForPosition(child, position, action);
2721 2722 2723 2724
        if (handler != null)
          return handler;
      }
    }
2725
    return node._actions[action];
2726 2727
  }

2728
  /// Asks the [SemanticsNode] at the given position to perform the given action.
2729 2730 2731
  ///
  /// If the [SemanticsNode] has not indicated that it can perform the action,
  /// this function does nothing.
2732 2733 2734
  ///
  /// If the given `action` requires arguments they need to be passed in via
  /// the `args` parameter.
2735
  void performActionAt(Offset position, SemanticsAction action, [ dynamic args ]) {
2736 2737 2738 2739
    assert(action != null);
    final SemanticsNode node = rootSemanticsNode;
    if (node == null)
      return;
2740
    final _SemanticsActionHandler handler = _getSemanticsActionHandlerForPosition(node, position, action);
2741
    if (handler != null)
2742
      handler(args);
Hixie's avatar
Hixie committed
2743
  }
2744 2745

  @override
2746
  String toString() => describeIdentity(this);
Hixie's avatar
Hixie committed
2747
}
2748 2749 2750 2751

/// Describes the semantic information associated with the owning
/// [RenderObject].
///
2752
/// The information provided in the configuration is used to generate the
2753 2754 2755 2756 2757 2758 2759 2760 2761
/// 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]
2762
  /// owner of this configuration or any of its descendants will not leak into
2763 2764 2765 2766 2767 2768 2769 2770 2771 2772 2773
  /// 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].
  ///
  /// This has to be true if [isMergingDescendantsIntoOneNode] is also true.
  bool get isSemanticBoundary => _isSemanticBoundary;
  bool _isSemanticBoundary = false;
  set isSemanticBoundary(bool value) {
2774
    assert(!isMergingSemanticsOfDescendants || value);
2775 2776 2777 2778 2779 2780 2781 2782 2783 2784 2785 2786 2787 2788 2789 2790 2791 2792
    _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
  /// annotate [SemanticNode]s of their parent with the semantic information
  /// 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
  /// new explicit [SemanticNode]s to the tree.
  ///
  /// This setting is often used in combination with [isSemanticBoundary] to
  /// create semantic boundaries that are either writable or not for children.
  bool explicitChildNodes = false;

2793
  /// Whether the owning [RenderObject] makes other [RenderObject]s previously
2794 2795 2796 2797 2798 2799 2800 2801 2802 2803 2804 2805
  /// 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.
  ///
2806 2807
  /// Paint order as established by [RenderObject.visitChildrenForSemantics] is
  /// used to determine if a node is previous to this one.
2808 2809 2810 2811 2812 2813 2814 2815 2816 2817 2818 2819 2820 2821 2822 2823 2824
  bool isBlockingSemanticsOfPreviouslyPaintedNodes = false;

  // SEMANTIC ANNOTATIONS
  // These will end up on [SemanticNode]s generated from
  // [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:
2825
  ///
2826
  ///  * [addAction] to add an action.
2827
  final Map<SemanticsAction, _SemanticsActionHandler> _actions = <SemanticsAction, _SemanticsActionHandler>{};
2828

2829 2830
  int _actionsAsBits = 0;

2831 2832
  /// Adds an `action` to the semantics tree.
  ///
2833 2834 2835
  /// The provided `handler` is called to respond to the user triggered
  /// `action`.
  void _addAction(SemanticsAction action, _SemanticsActionHandler handler) {
2836
    assert(handler != null);
2837
    _actions[action] = handler;
2838
    _actionsAsBits |= action.index;
2839 2840 2841
    _hasBeenAnnotated = true;
  }

2842 2843 2844 2845 2846 2847 2848 2849 2850 2851 2852 2853 2854 2855 2856 2857 2858 2859 2860 2861 2862
  /// 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);
    _addAction(action, (dynamic args) {
      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.
2863 2864 2865 2866 2867 2868 2869 2870 2871 2872 2873 2874
  ///
  /// 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.
2875 2876 2877 2878 2879 2880 2881 2882 2883 2884 2885 2886 2887 2888 2889 2890 2891 2892 2893 2894 2895 2896 2897 2898 2899 2900 2901 2902 2903 2904 2905 2906 2907 2908 2909 2910 2911 2912 2913 2914
  VoidCallback get onTap => _onTap;
  VoidCallback _onTap;
  set onTap(VoidCallback value) {
    _addArgumentlessAction(SemanticsAction.tap, value);
    _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.
  VoidCallback get onLongPress => _onLongPress;
  VoidCallback _onLongPress;
  set onLongPress(VoidCallback value) {
    _addArgumentlessAction(SemanticsAction.longPress, value);
    _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.
  VoidCallback get onScrollLeft => _onScrollLeft;
  VoidCallback _onScrollLeft;
  set onScrollLeft(VoidCallback value) {
    _addArgumentlessAction(SemanticsAction.scrollLeft, value);
    _onScrollLeft = value;
  }

2915 2916 2917 2918 2919 2920 2921 2922 2923 2924 2925 2926 2927 2928
  /// 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.
  VoidCallback get onDismiss => _onDismiss;
  VoidCallback _onDismiss;
  set onDismiss(VoidCallback value) {
    _addArgumentlessAction(SemanticsAction.dismiss, value);
    _onDismiss = value;
  }

2929 2930 2931 2932 2933 2934 2935 2936
  /// 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
2937
  /// left and then right in one motion path. On Android, [onScrollDown] and
2938 2939 2940 2941 2942 2943 2944 2945 2946 2947 2948 2949 2950 2951 2952 2953 2954 2955 2956 2957 2958 2959 2960 2961 2962 2963 2964 2965 2966 2967 2968 2969 2970 2971 2972 2973 2974 2975 2976 2977 2978 2979 2980 2981 2982 2983 2984 2985 2986 2987 2988 2989 2990 2991 2992 2993 2994 2995 2996 2997 2998 2999 3000 3001 3002 3003 3004 3005 3006 3007 3008 3009 3010 3011 3012 3013 3014 3015 3016
  /// [onScrollRight] share the same gesture. Therefore, only on of them should
  /// be provided.
  VoidCallback get onScrollRight => _onScrollRight;
  VoidCallback _onScrollRight;
  set onScrollRight(VoidCallback value) {
    _addArgumentlessAction(SemanticsAction.scrollRight, value);
    _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.
  VoidCallback get onScrollUp => _onScrollUp;
  VoidCallback _onScrollUp;
  set onScrollUp(VoidCallback value) {
    _addArgumentlessAction(SemanticsAction.scrollUp, value);
    _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.
  VoidCallback get onScrollDown => _onScrollDown;
  VoidCallback _onScrollDown;
  set onScrollDown(VoidCallback value) {
    _addArgumentlessAction(SemanticsAction.scrollDown, value);
    _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.
  ///
  /// 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.
  VoidCallback get onIncrease => _onIncrease;
  VoidCallback _onIncrease;
  set onIncrease(VoidCallback value) {
    _addArgumentlessAction(SemanticsAction.increase, value);
    _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.
  ///
  /// 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.
  VoidCallback get onDecrease => _onDecrease;
  VoidCallback _onDecrease;
  set onDecrease(VoidCallback value) {
    _addArgumentlessAction(SemanticsAction.decrease, value);
    _onDecrease = value;
3017 3018 3019 3020 3021 3022 3023 3024 3025 3026 3027 3028 3029 3030 3031 3032 3033 3034 3035 3036 3037 3038 3039 3040 3041 3042 3043 3044 3045 3046 3047 3048 3049 3050 3051 3052 3053 3054 3055 3056
  }

  /// 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.
  VoidCallback get onCopy => _onCopy;
  VoidCallback _onCopy;
  set onCopy(VoidCallback value) {
    _addArgumentlessAction(SemanticsAction.copy, value);
    _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.
  VoidCallback get onCut => _onCut;
  VoidCallback _onCut;
  set onCut(VoidCallback value) {
    _addArgumentlessAction(SemanticsAction.cut, value);
    _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.
  VoidCallback get onPaste => _onPaste;
  VoidCallback _onPaste;
  set onPaste(VoidCallback value) {
    _addArgumentlessAction(SemanticsAction.paste, value);
    _onPaste = value;
3057 3058 3059 3060 3061 3062 3063 3064 3065 3066 3067 3068 3069 3070 3071 3072 3073 3074 3075 3076 3077 3078 3079 3080 3081 3082 3083 3084 3085 3086 3087 3088 3089 3090 3091 3092 3093 3094 3095 3096 3097 3098 3099 3100 3101 3102 3103 3104 3105 3106 3107 3108 3109 3110 3111 3112
  }

  /// 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.
  VoidCallback get onShowOnScreen => _onShowOnScreen;
  VoidCallback _onShowOnScreen;
  set onShowOnScreen(VoidCallback value) {
    _addArgumentlessAction(SemanticsAction.showOnScreen, value);
    _onShowOnScreen = value;
  }

  /// The handler for [SemanticsAction.onMoveCursorForwardByCharacter].
  ///
  /// 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.
  MoveCursorHandler get onMoveCursorForwardByCharacter => _onMoveCursorForwardByCharacter;
  MoveCursorHandler _onMoveCursorForwardByCharacter;
  set onMoveCursorForwardByCharacter(MoveCursorHandler value) {
    assert(value != null);
    _addAction(SemanticsAction.moveCursorForwardByCharacter, (dynamic args) {
      final bool extentSelection = args;
      assert(extentSelection != null);
      value(extentSelection);
    });
    _onMoveCursorForwardByCharacter = value;
  }

  /// The handler for [SemanticsAction.onMoveCursorBackwardByCharacter].
  ///
  /// 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.
  MoveCursorHandler get onMoveCursorBackwardByCharacter => _onMoveCursorBackwardByCharacter;
  MoveCursorHandler _onMoveCursorBackwardByCharacter;
  set onMoveCursorBackwardByCharacter(MoveCursorHandler value) {
    assert(value != null);
    _addAction(SemanticsAction.moveCursorBackwardByCharacter, (dynamic args) {
      final bool extentSelection = args;
      assert(extentSelection != null);
      value(extentSelection);
    });
    _onMoveCursorBackwardByCharacter = value;
  }

3113 3114 3115 3116 3117 3118 3119 3120 3121 3122 3123 3124 3125 3126 3127 3128 3129 3130 3131 3132 3133 3134 3135 3136 3137 3138 3139 3140 3141 3142 3143 3144 3145 3146 3147 3148 3149 3150
  /// The handler for [SemanticsAction.onMoveCursorForwardByWord].
  ///
  /// 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.
  MoveCursorHandler get onMoveCursorForwardByWord => _onMoveCursorForwardByWord;
  MoveCursorHandler _onMoveCursorForwardByWord;
  set onMoveCursorForwardByWord(MoveCursorHandler value) {
    assert(value != null);
    _addAction(SemanticsAction.moveCursorForwardByWord, (dynamic args) {
      final bool extentSelection = args;
      assert(extentSelection != null);
      value(extentSelection);
    });
    _onMoveCursorForwardByCharacter = value;
  }

  /// The handler for [SemanticsAction.onMoveCursorBackwardByWord].
  ///
  /// 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.
  MoveCursorHandler get onMoveCursorBackwardByWord => _onMoveCursorBackwardByWord;
  MoveCursorHandler _onMoveCursorBackwardByWord;
  set onMoveCursorBackwardByWord(MoveCursorHandler value) {
    assert(value != null);
    _addAction(SemanticsAction.moveCursorBackwardByWord, (dynamic args) {
      final bool extentSelection = args;
      assert(extentSelection != null);
      value(extentSelection);
    });
    _onMoveCursorBackwardByCharacter = value;
  }

3151 3152 3153 3154 3155 3156 3157 3158 3159 3160 3161 3162
  /// 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.
  SetSelectionHandler get onSetSelection => _onSetSelection;
  SetSelectionHandler _onSetSelection;
  set onSetSelection(SetSelectionHandler value) {
    assert(value != null);
    _addAction(SemanticsAction.setSelection, (dynamic args) {
3163 3164
      assert(args != null && args is Map);
      final Map<String, int> selection = args.cast<String, int>();
3165
      assert(selection != null && selection['base'] != null && selection['extent'] != null);
3166
      value(TextSelection(
3167 3168 3169 3170 3171 3172 3173
        baseOffset: selection['base'],
        extentOffset: selection['extent'],
      ));
    });
    _onSetSelection = value;
  }

3174 3175 3176 3177 3178 3179 3180 3181 3182 3183 3184 3185 3186 3187 3188
  /// 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
3189 3190
  ///    focus is removed from the node.
  ///  * [FocusNode], [FocusScope], [FocusManager], which manage the input focus.
3191 3192 3193 3194 3195 3196 3197 3198 3199 3200 3201 3202 3203 3204 3205 3206 3207 3208 3209 3210 3211 3212
  VoidCallback get onDidGainAccessibilityFocus => _onDidGainAccessibilityFocus;
  VoidCallback _onDidGainAccessibilityFocus;
  set onDidGainAccessibilityFocus(VoidCallback value) {
    _addArgumentlessAction(SemanticsAction.didGainAccessibilityFocus, value);
    _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
3213 3214
  ///    accessibility focus.
  ///  * [FocusNode], [FocusScope], [FocusManager], which manage the input focus.
3215 3216 3217 3218 3219 3220 3221
  VoidCallback get onDidLoseAccessibilityFocus => _onDidLoseAccessibilityFocus;
  VoidCallback _onDidLoseAccessibilityFocus;
  set onDidLoseAccessibilityFocus(VoidCallback value) {
    _addArgumentlessAction(SemanticsAction.didLoseAccessibilityFocus, value);
    _onDidLoseAccessibilityFocus = value;
  }

3222 3223 3224 3225
  /// Returns the action handler registered for [action] or null if none was
  /// registered.
  ///
  /// See also:
3226
  ///
3227
  ///  * [addAction] to add an action.
3228
  _SemanticsActionHandler getActionHandler(SemanticsAction action) => _actions[action];
3229

3230 3231
  /// Determines the position of this node among its siblings in the traversal
  /// sort order.
3232
  ///
3233 3234 3235
  /// 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).
3236
  ///
3237 3238 3239 3240 3241 3242 3243
  /// 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.
  SemanticsSortKey get sortKey => _sortKey;
  SemanticsSortKey _sortKey;
  set sortKey(SemanticsSortKey value) {
3244
    assert(value != null);
3245
    _sortKey = value;
3246 3247 3248
    _hasBeenAnnotated = true;
  }

3249 3250 3251 3252 3253 3254 3255 3256 3257 3258 3259 3260 3261 3262 3263 3264 3265 3266 3267 3268 3269 3270 3271 3272 3273 3274 3275 3276 3277 3278 3279 3280 3281 3282 3283 3284 3285
  /// 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.
  int get indexInParent => _indexInParent;
  int _indexInParent;
  set indexInParent(int value) {
    _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.
  int get scrollChildCount => _scrollChildCount;
  int _scrollChildCount;
  set scrollChildCount(int value) {
    if (value == scrollChildCount)
      return;
    _scrollChildCount = value;
    _hasBeenAnnotated = true;
  }

  /// The index of the first visible scrollable child that contributes to
  /// semantics.
  int get scrollIndex => _scrollIndex;
  int _scrollIndex;
  set scrollIndex(int value) {
    if (value == scrollIndex)
      return;
    _scrollIndex = value;
    _hasBeenAnnotated = true;
  }

3286 3287 3288 3289 3290 3291 3292 3293 3294 3295
  /// The id of the platform view, whose semantics nodes will be added as
  /// children to this node.
  int get platformViewId => _platformViewId;
  int _platformViewId;
  set platformViewId(int value) {
    if (value == platformViewId)
      return;
    _platformViewId = value;
    _hasBeenAnnotated = true;
  }
3296

3297 3298 3299 3300 3301 3302 3303 3304 3305 3306 3307 3308 3309 3310 3311 3312 3313 3314 3315 3316 3317 3318 3319 3320 3321 3322 3323 3324 3325 3326 3327 3328 3329 3330
  /// 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.
  int get maxValueLength => _maxValueLength;
  int _maxValueLength;
  set maxValueLength(int value) {
    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.
  int get currentValueLength => _currentValueLength;
  int _currentValueLength;
  set currentValueLength(int value) {
    if (value == currentValueLength)
      return;
    _currentValueLength = value;
    _hasBeenAnnotated = true;
  }

3331 3332 3333 3334 3335 3336
  /// 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].
3337 3338
  ///
  /// Setting this to true requires that [isSemanticBoundary] is also true.
3339 3340 3341
  bool get isMergingSemanticsOfDescendants => _isMergingSemanticsOfDescendants;
  bool _isMergingSemanticsOfDescendants = false;
  set isMergingSemanticsOfDescendants(bool value) {
3342
    assert(isSemanticBoundary);
3343 3344 3345 3346
    _isMergingSemanticsOfDescendants = value;
    _hasBeenAnnotated = true;
  }

3347
  /// The handlers for each supported [CustomSemanticsAction].
3348
  ///
3349
  /// Whenever a custom accessibility action is added to a node, the action
3350
  /// [SemanticAction.customAction] is automatically added. A handler is
3351 3352 3353 3354 3355 3356 3357 3358 3359 3360 3361 3362 3363 3364 3365 3366 3367 3368 3369 3370
  /// 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;
  }

  void _onCustomSemanticsAction(dynamic args) {
    final CustomSemanticsAction action = CustomSemanticsAction.getAction(args);
    if (action == null)
      return;
    final VoidCallback callback = _customSemanticsActions[action];
    if (callback != null)
      callback();
  }

3371 3372
  /// A textual description of the owning [RenderObject].
  ///
3373 3374 3375 3376 3377
  /// On iOS this is used for the `accessibilityLabel` property defined in the
  /// `UIAccessibility` Protocol. On Android it is concatenated together with
  /// [value] and [hint] in the following order: [value], [label], [hint].
  /// The concatenated value is then used as the `Text` description.
  ///
3378
  /// The reading direction is given by [textDirection].
3379 3380 3381
  String get label => _label;
  String _label = '';
  set label(String label) {
3382
    assert(label != null);
3383 3384 3385 3386
    _label = label;
    _hasBeenAnnotated = true;
  }

3387 3388 3389 3390 3391 3392 3393
  /// A textual description for the current value of the owning [RenderObject].
  ///
  /// On iOS this is used for the `accessibilityValue` property defined in the
  /// `UIAccessibility` Protocol. On Android it is concatenated together with
  /// [label] and [hint] in the following order: [value], [label], [hint].
  /// The concatenated value is then used as the `Text` description.
  ///
3394 3395 3396
  /// The reading direction is given by [textDirection].
  ///
  /// See also:
3397
  ///
3398
  ///  * [decreasedValue], describes what [value] will be after performing
3399
  ///    [SemanticsAction.decrease].
3400
  ///  * [increasedValue], describes what [value] will be after performing
3401
  ///    [SemanticsAction.increase].
3402 3403 3404
  String get value => _value;
  String _value = '';
  set value(String value) {
3405
    assert(value != null);
3406 3407 3408 3409
    _value = value;
    _hasBeenAnnotated = true;
  }

3410 3411 3412 3413 3414 3415 3416 3417 3418 3419 3420 3421 3422 3423 3424 3425 3426 3427 3428 3429 3430 3431 3432 3433 3434 3435 3436 3437 3438 3439
  /// The value that [value] will have after performing a
  /// [SemanticsAction.decrease] action.
  ///
  /// This must be set if a handler for [SemanticsAction.decrease] is provided
  /// and [value] is set.
  ///
  /// The reading direction is given by [textDirection].
  String get decreasedValue => _decreasedValue;
  String _decreasedValue = '';
  set decreasedValue(String decreasedValue) {
    assert(decreasedValue != null);
    _decreasedValue = decreasedValue;
    _hasBeenAnnotated = true;
  }

  /// The value that [value] will have after performing a
  /// [SemanticsAction.increase] action.
  ///
  /// This must be set if a handler for [SemanticsAction.increase] is provided
  /// and [value] is set.
  ///
  /// The reading direction is given by [textDirection].
  String get increasedValue => _increasedValue;
  String _increasedValue = '';
  set increasedValue(String increasedValue) {
    assert(increasedValue != null);
    _increasedValue = increasedValue;
    _hasBeenAnnotated = true;
  }

3440 3441 3442 3443 3444 3445 3446
  /// A brief description of the result of performing an action on this node.
  ///
  /// On iOS this is used for the `accessibilityHint` property defined in the
  /// `UIAccessibility` Protocol. On Android it is concatenated together with
  /// [label] and [value] in the following order: [value], [label], [hint].
  /// The concatenated value is then used as the `Text` description.
  ///
3447
  /// The reading direction is given by [textDirection].
3448 3449 3450
  String get hint => _hint;
  String _hint = '';
  set hint(String hint) {
3451
    assert(hint != null);
3452 3453 3454 3455
    _hint = hint;
    _hasBeenAnnotated = true;
  }

3456 3457 3458 3459 3460 3461 3462 3463 3464 3465 3466
  /// Provides hint values which override the default hints on supported
  /// platforms.
  SemanticsHintOverrides get hintOverrides => _hintOverrides;
  SemanticsHintOverrides _hintOverrides;
  set hintOverrides(SemanticsHintOverrides value) {
    if (value == null)
      return;
    _hintOverrides = value;
    _hasBeenAnnotated = true;
  }

3467 3468 3469 3470 3471 3472 3473 3474 3475 3476 3477 3478 3479 3480 3481 3482 3483 3484 3485 3486 3487 3488 3489 3490 3491 3492 3493 3494 3495 3496
  /// 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;
  }

3497 3498
  /// Whether the semantics node is the root of a subtree for which values
  /// should be announced.
3499
  ///
3500
  /// See also:
3501
  ///
3502 3503 3504 3505 3506 3507 3508
  ///  * [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.
3509
  ///
3510
  /// See also:
3511
  ///
3512 3513 3514 3515 3516 3517
  ///  * [SemanticsFlag.namesRoute], for a full description of route naming.
  bool get namesRoute => _hasFlag(SemanticsFlag.namesRoute);
  set namesRoute(bool value) {
    _setFlag(SemanticsFlag.namesRoute, value);
  }

3518 3519 3520 3521 3522 3523 3524 3525 3526 3527 3528 3529 3530 3531 3532 3533 3534 3535 3536 3537 3538 3539
  /// 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.
  ///
  /// On Android, when a live region semantics node is first created TalkBack
  /// will make a polite announcement of the current label. This announcement
  /// occurs even if the node is not focused. Subsequent polite announcements
  /// can be made by sending a [UpdateLiveRegionEvent] semantics event. The
  /// announcement will only be made if the node's label has changed since the
  /// last update.
  ///
  /// An example of a live region is the [Snackbar] widget. When it appears
  /// on the screen it may be difficult to focus to read the label. A live
  /// region causes an initial polite announcement to be generated
  /// automatically.
  ///
  /// See also:
  ///
3540
  ///  * [SemanticsFlag.isLiveRegion], the semantics flag that this setting controls.
3541 3542 3543 3544 3545
  bool get liveRegion => _hasFlag(SemanticsFlag.isLiveRegion);
  set liveRegion(bool value) {
    _setFlag(SemanticsFlag.isLiveRegion, value);
  }

3546 3547
  /// The reading direction for the text in [label], [value], [hint],
  /// [increasedValue], and [decreasedValue].
3548 3549 3550 3551 3552 3553 3554 3555
  TextDirection get textDirection => _textDirection;
  TextDirection _textDirection;
  set textDirection(TextDirection textDirection) {
    _textDirection = textDirection;
    _hasBeenAnnotated = true;
  }

  /// Whether the owning [RenderObject] is selected (true) or not (false).
3556 3557 3558 3559 3560
  ///
  /// 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.
3561
  bool get isSelected => _hasFlag(SemanticsFlag.isSelected);
3562
  set isSelected(bool value) {
3563
    _setFlag(SemanticsFlag.isSelected, value);
3564 3565
  }

3566 3567 3568 3569 3570 3571 3572 3573 3574 3575 3576
  /// 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.
3577 3578 3579 3580
  ///
  /// 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.
3581
  bool get isEnabled => _hasFlag(SemanticsFlag.hasEnabledState) ? _hasFlag(SemanticsFlag.isEnabled) : null;
3582
  set isEnabled(bool value) {
3583 3584
    _setFlag(SemanticsFlag.hasEnabledState, true);
    _setFlag(SemanticsFlag.isEnabled, value);
3585 3586
  }

3587
  /// If this node has Boolean state that can be controlled by the user, whether
3588 3589
  /// that state is checked or unchecked, corresponding to true and false,
  /// respectively.
3590
  ///
3591 3592 3593 3594 3595
  /// 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.
3596
  bool get isChecked => _hasFlag(SemanticsFlag.hasCheckedState) ? _hasFlag(SemanticsFlag.isChecked) : null;
3597
  set isChecked(bool value) {
3598 3599
    _setFlag(SemanticsFlag.hasCheckedState, true);
    _setFlag(SemanticsFlag.isChecked, value);
3600 3601
  }

3602 3603 3604 3605 3606 3607 3608 3609 3610 3611 3612 3613 3614 3615
  /// 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.
  bool get isToggled => _hasFlag(SemanticsFlag.hasToggledState) ? _hasFlag(SemanticsFlag.isToggled) : null;
  set isToggled(bool value) {
    _setFlag(SemanticsFlag.hasToggledState, true);
    _setFlag(SemanticsFlag.isToggled, value);
  }

3616 3617 3618 3619 3620 3621 3622 3623 3624 3625
  /// 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);
  }

3626 3627 3628 3629 3630 3631 3632
  /// 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.
3633
  bool get isFocused => _hasFlag(SemanticsFlag.isFocused);
3634
  set isFocused(bool value) {
3635
    _setFlag(SemanticsFlag.isFocused, value);
3636 3637
  }

3638
  /// Whether the owning [RenderObject] is a button (true) or not (false).
3639
  bool get isButton => _hasFlag(SemanticsFlag.isButton);
3640
  set isButton(bool value) {
3641
    _setFlag(SemanticsFlag.isButton, value);
3642 3643
  }

3644 3645 3646 3647 3648 3649
  /// 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);
  }

3650 3651 3652 3653 3654 3655
  /// 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);
  }

3656 3657 3658 3659 3660 3661 3662 3663 3664 3665 3666 3667 3668 3669 3670 3671 3672 3673 3674 3675 3676
  /// 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);
  }

3677
  /// Whether the owning [RenderObject] is a text field.
3678
  bool get isTextField => _hasFlag(SemanticsFlag.isTextField);
3679
  set isTextField(bool value) {
3680
    _setFlag(SemanticsFlag.isTextField, value);
3681 3682
  }

3683 3684 3685 3686 3687 3688 3689 3690
  /// 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);
  }

3691 3692 3693 3694 3695 3696 3697 3698 3699 3700
  /// Whether the [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].
  bool get isObscured => _hasFlag(SemanticsFlag.isObscured);
  set isObscured(bool value) {
    _setFlag(SemanticsFlag.isObscured, value);
  }

3701 3702 3703 3704 3705 3706 3707 3708 3709
  /// Whether the text field is multi-line.
  ///
  /// This option is usually set in combination with [textField] to indicate
  /// that the text field is configured to be multi-line.
  bool get isMultiline => _hasFlag(SemanticsFlag.isMultiline);
  set isMultiline(bool value) {
    _setFlag(SemanticsFlag.isMultiline, value);
  }

3710 3711 3712 3713 3714 3715 3716 3717 3718 3719 3720 3721
  /// 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);
  }

3722 3723 3724 3725 3726 3727 3728 3729 3730 3731
  /// The currently selected text (or the position of the cursor) within [value]
  /// if this node represents a text field.
  TextSelection get textSelection => _textSelection;
  TextSelection _textSelection;
  set textSelection(TextSelection value) {
    assert(value != null);
    _textSelection = value;
    _hasBeenAnnotated = true;
  }

3732 3733 3734 3735 3736 3737 3738 3739 3740 3741 3742 3743 3744 3745 3746 3747 3748 3749 3750 3751 3752 3753 3754 3755 3756 3757 3758 3759 3760 3761 3762 3763 3764 3765 3766 3767 3768 3769 3770 3771 3772 3773 3774 3775 3776 3777 3778 3779 3780 3781
  /// 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.
  double get scrollPosition => _scrollPosition;
  double _scrollPosition;
  set scrollPosition(double value) {
    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.
  double get scrollExtentMax => _scrollExtentMax;
  double _scrollExtentMax;
  set scrollExtentMax(double value) {
    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.
  double get scrollExtentMin => _scrollExtentMin;
  double _scrollExtentMin;
  set scrollExtentMin(double value) {
    assert(value != null);
    _scrollExtentMin = value;
    _hasBeenAnnotated = true;
  }

3782 3783
  // TAGS

3784 3785 3786 3787
  /// The set of tags that this configuration wants to add to all child
  /// [SemanticsNode]s.
  ///
  /// See also:
3788
  ///
3789 3790
  ///  * [addTagForChildren] to add a tag and for more information about their
  ///    usage.
3791 3792 3793
  Iterable<SemanticsTag> get tagsForChildren => _tagsForChildren;
  Set<SemanticsTag> _tagsForChildren;

3794 3795 3796 3797 3798 3799 3800 3801 3802 3803 3804 3805
  /// 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:
3806
  ///
3807 3808
  ///  * [RenderSemanticsGestureHandler.excludeFromScrolling] for an example of
  ///    how tags are used.
3809
  void addTagForChildren(SemanticsTag tag) {
3810
    _tagsForChildren ??= <SemanticsTag>{};
3811 3812 3813 3814 3815 3816
    _tagsForChildren.add(tag);
  }

  // INTERNAL FLAG MANAGEMENT

  int _flags = 0;
3817
  void _setFlag(SemanticsFlag flag, bool value) {
3818 3819 3820 3821 3822 3823 3824 3825
    if (value) {
      _flags |= flag.index;
    } else {
      _flags &= ~flag.index;
    }
    _hasBeenAnnotated = true;
  }

3826
  bool _hasFlag(SemanticsFlag flag) => (_flags & flag.index) != 0;
3827

3828 3829 3830 3831 3832 3833 3834 3835 3836 3837
  // 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.
  bool isCompatibleWith(SemanticsConfiguration other) {
    if (other == null || !other.hasBeenAnnotated || !hasBeenAnnotated)
      return true;
3838
    if (_actionsAsBits & other._actionsAsBits != 0)
3839 3840 3841
      return false;
    if ((_flags & other._flags) != 0)
      return false;
3842 3843 3844
    if (_platformViewId != null && other._platformViewId != null) {
      return false;
    }
3845 3846 3847 3848 3849 3850
    if (_maxValueLength != null && other._maxValueLength != null) {
      return false;
    }
    if (_currentValueLength != null && other._currentValueLength != null) {
      return false;
    }
3851 3852
    if (_value != null && _value.isNotEmpty && other._value != null && other._value.isNotEmpty)
      return false;
3853 3854 3855
    return true;
  }

3856
  /// Absorb the semantic information from `child` into this configuration.
3857 3858 3859 3860
  ///
  /// This adds the semantic information of both configurations and saves the
  /// result in this configuration.
  ///
3861 3862 3863
  /// The [RenderObject] owning the `child` configuration must be a descendant
  /// of the [RenderObject] that owns this configuration.
  ///
3864
  /// Only configurations that have [explicitChildNodes] set to false can
3865
  /// absorb other configurations and it is recommended to only absorb compatible
3866
  /// configurations as determined by [isCompatibleWith].
3867
  void absorb(SemanticsConfiguration child) {
3868 3869
    assert(!explicitChildNodes);

3870
    if (!child.hasBeenAnnotated)
3871 3872
      return;

3873 3874 3875 3876 3877 3878 3879 3880 3881 3882 3883 3884
    _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;
3885
    _platformViewId ??= child._platformViewId;
3886 3887
    _maxValueLength ??= child._maxValueLength;
    _currentValueLength ??= child._currentValueLength;
3888 3889 3890

    textDirection ??= child.textDirection;
    _sortKey ??= child._sortKey;
3891 3892 3893
    _label = _concatStrings(
      thisString: _label,
      thisTextDirection: textDirection,
3894 3895
      otherString: child._label,
      otherTextDirection: child.textDirection,
3896
    );
3897
    if (_decreasedValue == '' || _decreasedValue == null)
3898
      _decreasedValue = child._decreasedValue;
3899
    if (_value == '' || _value == null)
3900
      _value = child._value;
3901
    if (_increasedValue == '' || _increasedValue == null)
3902
      _increasedValue = child._increasedValue;
3903 3904 3905
    _hint = _concatStrings(
      thisString: _hint,
      thisTextDirection: textDirection,
3906 3907
      otherString: child._hint,
      otherTextDirection: child.textDirection,
3908
    );
3909

3910 3911 3912
    _thickness = math.max(_thickness, child._thickness + child._elevation);

    _hasBeenAnnotated = _hasBeenAnnotated || child._hasBeenAnnotated;
3913 3914 3915 3916
  }

  /// Returns an exact copy of this configuration.
  SemanticsConfiguration copy() {
3917
    return SemanticsConfiguration()
3918
      .._isSemanticBoundary = _isSemanticBoundary
3919
      ..explicitChildNodes = explicitChildNodes
3920
      ..isBlockingSemanticsOfPreviouslyPaintedNodes = isBlockingSemanticsOfPreviouslyPaintedNodes
3921
      .._hasBeenAnnotated = _hasBeenAnnotated
3922
      .._isMergingSemanticsOfDescendants = _isMergingSemanticsOfDescendants
3923
      .._textDirection = _textDirection
3924
      .._sortKey = _sortKey
3925
      .._label = _label
3926
      .._increasedValue = _increasedValue
3927
      .._value = _value
3928
      .._decreasedValue = _decreasedValue
3929
      .._hint = _hint
3930
      .._hintOverrides = _hintOverrides
3931 3932
      .._elevation = _elevation
      .._thickness = _thickness
3933
      .._flags = _flags
3934
      .._tagsForChildren = _tagsForChildren
3935
      .._textSelection = _textSelection
3936 3937 3938
      .._scrollPosition = _scrollPosition
      .._scrollExtentMax = _scrollExtentMax
      .._scrollExtentMin = _scrollExtentMin
3939
      .._actionsAsBits = _actionsAsBits
3940 3941 3942
      .._indexInParent = indexInParent
      .._scrollIndex = _scrollIndex
      .._scrollChildCount = _scrollChildCount
3943
      .._platformViewId = _platformViewId
3944 3945
      .._maxValueLength = _maxValueLength
      .._currentValueLength = _currentValueLength
3946 3947
      .._actions.addAll(_actions)
      .._customSemanticsActions.addAll(_customSemanticsActions);
3948 3949
  }
}
3950

3951 3952 3953 3954 3955 3956 3957 3958 3959 3960
/// 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,

3961
  /// Print nodes in semantic traversal order.
3962
  ///
3963 3964 3965
  /// This is the order in which a user would navigate the UI using the "next"
  /// and "previous" gestures.
  traversalOrder,
3966 3967
}

3968 3969 3970 3971
String _concatStrings({
  @required String thisString,
  @required String otherString,
  @required TextDirection thisTextDirection,
3972
  @required TextDirection otherTextDirection,
3973 3974 3975 3976 3977 3978 3979 3980 3981 3982 3983 3984 3985 3986 3987 3988 3989 3990
}) {
  if (otherString.isEmpty)
    return thisString;
  String nestedLabel = otherString;
  if (thisTextDirection != otherTextDirection && otherTextDirection != null) {
    switch (otherTextDirection) {
      case TextDirection.rtl:
        nestedLabel = '${Unicode.RLE}$nestedLabel${Unicode.PDF}';
        break;
      case TextDirection.ltr:
        nestedLabel = '${Unicode.LRE}$nestedLabel${Unicode.PDF}';
        break;
    }
  }
  if (thisString.isEmpty)
    return nestedLabel;
  return '$thisString\n$nestedLabel';
}
3991 3992 3993 3994

/// Base class for all sort keys for [Semantics] accessibility traversal order
/// sorting.
///
3995 3996 3997 3998 3999 4000 4001 4002 4003 4004 4005 4006 4007 4008 4009 4010 4011 4012 4013 4014 4015
/// Only keys of the same type and having matching [name]s are compared. If a
/// list of sibling [SemanticsNode]s contains keys that are not comparable with
/// each other the list is first sorted using the default sorting algorithm.
/// Then the nodes are broken down into groups by moving comparable nodes
/// towards the _earliest_ node in the group. Finally each group is sorted by
/// sort key and the resulting list is made by concatenating the sorted groups
/// back.
///
/// For example, let's take nodes (C, D, B, E, A, F). Let's assign node A key 1,
/// node B key 2, node C key 3. Let's also assume that the default sort order
/// leaves the original list intact. Because nodes A, B, and C, have comparable
/// sort key, they will form a group by pulling all nodes towards the earliest
/// node, which is C. The result is group (C, B, A). The remaining nodes D, E,
/// F, form a second group with sort key being `null`. The first group is sorted
/// using their sort keys becoming (A, B, C). The second group is left as is
/// because it does not specify sort keys. Then we concatenate the two groups -
/// (A, B, C) and (D, E, F) - into the final (A, B, C, D, E, F).
///
/// Because of the complexity introduced by incomparable sort keys among sibling
/// nodes, it is recommended to either use comparable keys for all nodes, or
/// use null for all of them, leaving the sort order to the default algorithm.
4016 4017 4018 4019 4020
///
/// See Also:
///
///  * [OrdinalSortKey] for a sort key that sorts using an ordinal.
abstract class SemanticsSortKey extends Diagnosticable implements Comparable<SemanticsSortKey> {
4021 4022
  /// Abstract const constructor. This constructor enables subclasses to provide
  /// const constructors so that they can be used in const expressions.
4023 4024 4025 4026
  const SemanticsSortKey({this.name});

  /// An optional name that will make this sort key only order itself
  /// with respect to other sort keys of the same [name], as long as
4027
  /// they are of the same [runtimeType].
4028 4029 4030 4031
  final String name;

  @override
  int compareTo(SemanticsSortKey other) {
4032 4033 4034
    // The sorting algorithm must not compare incomparable keys.
    assert(runtimeType == other.runtimeType);
    assert(name == other.name);
4035 4036 4037
    return doCompare(other);
  }

4038 4039
  /// The implementation of [compareTo].
  ///
4040 4041
  /// The argument is guaranteed to be of the same type as this object and have
  /// the same [name].
4042
  ///
4043
  /// The method should return a negative number if this object comes earlier in
4044
  /// the sort order than the argument; and a positive number if it comes later
4045 4046
  /// in the sort order. Returning zero causes the system to use default sort
  /// order.
4047
  @protected
4048
  int doCompare(covariant SemanticsSortKey other);
4049 4050

  @override
4051 4052
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
4053
    properties.add(StringProperty('name', name, defaultValue: null));
4054 4055 4056
  }
}

4057 4058
/// A [SemanticsSortKey] that sorts simply based on the `double` value it is
/// given.
4059 4060 4061 4062
///
/// The [OrdinalSortKey] compares itself with other [OrdinalSortKey]s
/// to sort based on the order it is given.
///
4063 4064 4065 4066
/// The ordinal value `order` is typically a whole number, though it can be
/// 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]).
4067
class OrdinalSortKey extends SemanticsSortKey {
4068
  /// Creates a semantics sort key that uses a [double] as its key value.
4069 4070 4071 4072 4073 4074
  ///
  /// The [order] must be a finite number.
  const OrdinalSortKey(
    this.order, {
    String name,
  }) : assert(order != null),
4075 4076 4077
       assert(order != double.nan),
       assert(order > double.negativeInfinity),
       assert(order < double.infinity),
4078
       super(name: name);
4079

4080 4081 4082 4083 4084
  /// 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.
  ///
  /// Lower values will be traversed first.
4085 4086 4087
  final double order;

  @override
4088 4089
  int doCompare(OrdinalSortKey other) {
    if (other.order == null || order == null || other.order == order)
4090
      return 0;
4091
    return order.compareTo(other.order);
4092 4093 4094
  }

  @override
4095 4096
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
4097
    properties.add(DoubleProperty('order', order, defaultValue: null));
4098 4099
  }
}