semantics.dart 105 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:typed_data';
6
import 'dart:ui' as ui;
7
import 'dart:ui' show Offset, Rect, SemanticsAction, SemanticsFlag,
8
       TextDirection;
Hixie's avatar
Hixie committed
9

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

15
import 'semantics_event.dart';
Hixie's avatar
Hixie committed
16

17
export 'dart:ui' show SemanticsAction;
18
export 'semantics_event.dart';
Hixie's avatar
Hixie committed
19

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

27 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.
typedef void MoveCursorHandler(bool extendSelection);

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

37 38
typedef void _SemanticsActionHandler(dynamic args);

39 40 41 42
/// 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
43
/// how to add the tagged node as a child. Tags are not sent to the engine.
44 45 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
///
/// 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)';
}

71 72 73 74 75 76 77 78
/// 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].
79
@immutable
80
class SemanticsData extends Diagnosticable {
81 82
  /// Creates a semantics data object.
  ///
83
  /// The [flags], [actions], [label], and [Rect] arguments must not be null.
Ian Hickson's avatar
Ian Hickson committed
84 85
  ///
  /// If [label] is not empty, then [textDirection] must also not be null.
86
  const SemanticsData({
87 88 89
    @required this.flags,
    @required this.actions,
    @required this.label,
90
    @required this.increasedValue,
91
    @required this.value,
92
    @required this.decreasedValue,
93
    @required this.hint,
Ian Hickson's avatar
Ian Hickson committed
94
    @required this.textDirection,
95
    @required this.nextNodeId,
96
    @required this.previousNodeId,
97
    @required this.rect,
98
    @required this.textSelection,
99 100 101
    @required this.scrollPosition,
    @required this.scrollExtentMax,
    @required this.scrollExtentMin,
102
    this.tags,
Ian Hickson's avatar
Ian Hickson committed
103
    this.transform,
104 105 106
  }) : assert(flags != null),
       assert(actions != null),
       assert(label != null),
107 108 109 110
       assert(value != null),
       assert(decreasedValue != null),
       assert(increasedValue != null),
       assert(hint != null),
Ian Hickson's avatar
Ian Hickson committed
111
       assert(label == '' || textDirection != null, 'A SemanticsData object with label "$label" had a null textDirection.'),
112 113 114 115
       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.'),
116
       assert(rect != null);
117

118
  /// A bit field of [SemanticsFlag]s that apply to this node.
119 120
  final int flags;

121
  /// A bit field of [SemanticsAction]s that apply to this node.
122 123 124
  final int actions;

  /// A textual description of this node.
Ian Hickson's avatar
Ian Hickson committed
125
  ///
126
  /// The reading direction is given by [textDirection].
127 128
  final String label;

129 130
  /// A textual description for the current value of the node.
  ///
131
  /// The reading direction is given by [textDirection].
132 133
  final String value;

134 135 136 137 138 139 140 141 142 143 144 145
  /// 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;

146 147
  /// A brief description of the result of performing an action on this node.
  ///
148
  /// The reading direction is given by [textDirection].
149 150
  final String hint;

151 152
  /// The reading direction for the text in [label], [value], [hint],
  /// [increasedValue], and [decreasedValue].
Ian Hickson's avatar
Ian Hickson committed
153 154
  final TextDirection textDirection;

155 156 157 158
  /// The index indicating the ID of the next node in the traversal order after
  /// this node for the platform's accessibility services.
  final int nextNodeId;

159
  /// The index indicating the ID of the previous node in the traversal order before
160
  /// this node for the platform's accessibility services.
161
  final int previousNodeId;
162

163 164 165 166
  /// The currently selected text (or the position of the cursor) within [value]
  /// if this node represents a text field.
  final TextSelection textSelection;

167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
  /// 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
189
  /// Indicates the minimum in-range value for [scrollPosition] if the node is
190 191 192 193 194 195 196 197 198
  /// 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;

199 200 201
  /// The bounding box for this node in its coordinate system.
  final Rect rect;

202 203 204
  /// The set of [SemanticsTag]s associated with this node.
  final Set<SemanticsTag> tags;

205 206 207
  /// The transform from this node's coordinate system to its parent's coordinate system.
  ///
  /// By default, the transform is null, which represents the identity
208
  /// transformation (i.e., that this node has the same coordinate system as its
209 210 211 212
  /// parent).
  final Matrix4 transform;

  /// Whether [flags] contains the given flag.
213
  bool hasFlag(SemanticsFlag flag) => (flags & flag.index) != 0;
214 215 216

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

  @override
219 220 221 222 223 224 225 226
  String toStringShort() => '$runtimeType';

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(new DiagnosticsProperty<Rect>('rect', rect, showName: false));
    properties.add(new TransformProperty('transform', transform, showName: false, defaultValue: null));
    final List<String> actionSummary = <String>[];
227 228
    for (SemanticsAction action in SemanticsAction.values.values) {
      if ((actions & action.index) != 0)
229
        actionSummary.add(describeEnum(action));
230
    }
231
    properties.add(new IterableProperty<String>('actions', actionSummary, ifEmpty: null));
232 233

    final List<String> flagSummary = <String>[];
234
    for (SemanticsFlag flag in SemanticsFlag.values.values) {
235
      if ((flags & flag.index) != 0)
236
        flagSummary.add(describeEnum(flag));
237
    }
238
    properties.add(new IterableProperty<String>('flags', flagSummary, ifEmpty: null));
239
    properties.add(new StringProperty('label', label, defaultValue: ''));
240 241 242 243
    properties.add(new StringProperty('value', value, defaultValue: ''));
    properties.add(new StringProperty('increasedValue', increasedValue, defaultValue: ''));
    properties.add(new StringProperty('decreasedValue', decreasedValue, defaultValue: ''));
    properties.add(new StringProperty('hint', hint, defaultValue: ''));
244
    properties.add(new EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
245
    properties.add(new IntProperty('nextNodeId', nextNodeId, defaultValue: null));
246
    properties.add(new IntProperty('previousNodeId', previousNodeId, defaultValue: null));
247
    if (textSelection?.isValid == true)
248 249 250 251
      properties.add(new MessageProperty('textSelection', '[${textSelection.start}, ${textSelection.end}]'));
    properties.add(new DoubleProperty('scrollExtentMin', scrollExtentMin, defaultValue: null));
    properties.add(new DoubleProperty('scrollPosition', scrollPosition, defaultValue: null));
    properties.add(new DoubleProperty('scrollExtentMax', scrollExtentMax, defaultValue: null));
252 253 254 255 256 257 258 259 260 261
  }

  @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
262 263 264 265
        && typedOther.value == value
        && typedOther.increasedValue == increasedValue
        && typedOther.decreasedValue == decreasedValue
        && typedOther.hint == hint
Ian Hickson's avatar
Ian Hickson committed
266
        && typedOther.textDirection == textDirection
267
        && typedOther.nextNodeId == nextNodeId
268
        && typedOther.previousNodeId == previousNodeId
269
        && typedOther.rect == rect
270
        && setEquals(typedOther.tags, tags)
271
        && typedOther.textSelection == textSelection
272 273 274
        && typedOther.scrollPosition == scrollPosition
        && typedOther.scrollExtentMax == scrollExtentMax
        && typedOther.scrollExtentMin == scrollExtentMin
275 276 277 278
        && typedOther.transform == transform;
  }

  @override
279
  int get hashCode => ui.hashValues(flags, actions, label, value, increasedValue, decreasedValue, hint, textDirection, nextNodeId, previousNodeId, rect, tags, textSelection, scrollPosition, scrollExtentMax, scrollExtentMin, transform);
280 281
}

282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304
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>[];
  }
}

305 306 307 308 309 310 311 312 313
/// 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({
314
    this.enabled,
315 316 317
    this.checked,
    this.selected,
    this.button,
318 319 320 321
    this.header,
    this.textField,
    this.focused,
    this.inMutuallyExclusiveGroup,
322
    this.obscured,
323 324 325 326 327 328
    this.label,
    this.value,
    this.increasedValue,
    this.decreasedValue,
    this.hint,
    this.textDirection,
329
    this.sortKey,
330 331 332 333 334 335 336 337
    this.onTap,
    this.onLongPress,
    this.onScrollLeft,
    this.onScrollRight,
    this.onScrollUp,
    this.onScrollDown,
    this.onIncrease,
    this.onDecrease,
338 339 340
    this.onCopy,
    this.onCut,
    this.onPaste,
341 342
    this.onMoveCursorForwardByCharacter,
    this.onMoveCursorBackwardByCharacter,
343
    this.onSetSelection,
344 345
    this.onDidGainAccessibilityFocus,
    this.onDidLoseAccessibilityFocus,
346 347
  });

348 349 350 351 352 353 354 355
  /// 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;

356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373
  /// If non-null, indicates that this subtree represents a checkbox
  /// or similar widget with a "checked" state, and what its current
  /// state is.
  final bool checked;

  /// 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;

374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401
  /// 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;

  /// If non-null, whether the node currently holds input focus.
  ///
  /// At most one node in the tree should hold input focus at any point in time.
  ///
  /// Input focus (indicates that the node will receive keyboard events) is not
  /// to be confused with accessibility focus. Accessibility focus is the
  /// green/black rectangular that TalkBack/VoiceOver on the screen and is
  /// separate from input focus.
  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;
402 403 404 405 406 407 408
  
  /// 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;
409

410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460
  /// 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.
  ///
461
  /// If a hint is provided, there must either be an ambient [Directionality]
462 463 464 465 466 467 468 469 470 471 472 473 474 475
  /// 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;

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

476 477
  /// Determines the position of this node among its siblings in the traversal
  /// sort order.
478 479 480 481
  ///
  /// 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).
482
  final SemanticsSortKey sortKey;
483

484 485 486 487 488 489 490 491 492 493 494 495 496 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 522 523 524
  /// 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
525
  /// left and then right in one motion path. On Android, [onScrollDown] and
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 575 576 577 578 579 580 581
  /// [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;

582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606
  /// 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;

607 608 609 610 611 612 613
  /// 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.
614
  final MoveCursorHandler onMoveCursorForwardByCharacter;
615 616 617 618 619 620 621 622

  /// 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.
623
  final MoveCursorHandler onMoveCursorBackwardByCharacter;
624

625 626 627 628 629 630 631 632 633
  /// 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;

634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671
  /// 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
  ///    focus is removed from the node
  ///  * [FocusNode], [FocusScope], [FocusManager], which manage the input focus
  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
  ///    accessibility focus
  ///  * [FocusNode], [FocusScope], [FocusManager], which manage the input focus
  final VoidCallback onDidLoseAccessibilityFocus;

672
  @override
673 674 675 676 677 678 679 680 681
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(new DiagnosticsProperty<bool>('checked', checked, defaultValue: null));
    properties.add(new DiagnosticsProperty<bool>('selected', selected, defaultValue: null));
    properties.add(new StringProperty('label', label, defaultValue: ''));
    properties.add(new StringProperty('value', value));
    properties.add(new StringProperty('hint', hint));
    properties.add(new EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
    properties.add(new DiagnosticsProperty<SemanticsSortKey>('sortKey', sortKey, defaultValue: null));
682
  }
683 684 685

  @override
  String toStringShort() => '$runtimeType'; // the hashCode isn't important since we're immutable
686 687 688 689 690 691 692 693
}

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

694 695 696 697 698 699
/// 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.
700
class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
701 702 703 704
  /// Creates a semantic node.
  ///
  /// Each semantic node has a unique identifier that is assigned when the node
  /// is created.
Hixie's avatar
Hixie committed
705
  SemanticsNode({
706
    this.key,
707
    VoidCallback showOnScreen,
708
  }) : id = _generateNewId(),
709
       _showOnScreen = showOnScreen;
Hixie's avatar
Hixie committed
710

711 712 713
  /// 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
714
  SemanticsNode.root({
715
    this.key,
716 717
    VoidCallback showOnScreen,
    SemanticsOwner owner,
718
  }) : id = 0,
719
       _showOnScreen = showOnScreen {
720
    attach(owner);
Hixie's avatar
Hixie committed
721 722 723 724 725 726 727 728
  }

  static int _lastIdentifier = 0;
  static int _generateNewId() {
    _lastIdentifier += 1;
    return _lastIdentifier;
  }

729 730 731 732 733 734
  /// 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;

735 736 737 738 739
  /// 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
740

741
  final VoidCallback _showOnScreen;
Hixie's avatar
Hixie committed
742 743 744

  // GEOMETRY

745 746 747
  /// The transform from this node's coordinate system to its parent's coordinate system.
  ///
  /// By default, the transform is null, which represents the identity
748
  /// transformation (i.e., that this node has the same coordinate system as its
749
  /// parent).
Hixie's avatar
Hixie committed
750
  Matrix4 get transform => _transform;
751
  Matrix4 _transform;
752
  set transform(Matrix4 value) {
Hixie's avatar
Hixie committed
753
    if (!MatrixUtils.matrixEquals(_transform, value)) {
754
      _transform = MatrixUtils.isIdentity(value) ? null : value;
Hixie's avatar
Hixie committed
755 756 757 758
      _markDirty();
    }
  }

759
  /// The bounding box for this node in its coordinate system.
Hixie's avatar
Hixie committed
760 761
  Rect get rect => _rect;
  Rect _rect = Rect.zero;
762
  set rect(Rect value) {
Hixie's avatar
Hixie committed
763 764 765 766 767 768 769
    assert(value != null);
    if (_rect != value) {
      _rect = value;
      _markDirty();
    }
  }

770
  /// The clip rect from an ancestor that was applied to this node.
771
  ///
772 773 774
  /// Expressed in the coordinate system of the node. May be null if no clip has
  /// been applied.
  Rect parentClipRect;
Hixie's avatar
Hixie committed
775

776 777 778 779 780 781 782 783 784 785 786
  /// 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
787

788
  // MERGING
Hixie's avatar
Hixie committed
789

790 791 792 793
  /// Whether this node merges its semantic information into an ancestor node.
  bool get isMergedIntoParent => _isMergedIntoParent;
  bool _isMergedIntoParent = false;
  set isMergedIntoParent(bool value) {
794
    assert(value != null);
795
    if (_isMergedIntoParent == value)
796
      return;
797
    _isMergedIntoParent = value;
798 799
    _markDirty();
  }
800

801 802 803 804 805 806
  /// 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:
807
  ///
808 809 810
  ///  * [isMergedIntoParent]
  ///  * [mergeAllDescendantsIntoThisNode]
  bool get isPartOfNodeMerging => mergeAllDescendantsIntoThisNode || isMergedIntoParent;
811

812 813 814
  /// Whether this node and all of its descendants should be treated as one logical entity.
  bool get mergeAllDescendantsIntoThisNode => _mergeAllDescendantsIntoThisNode;
  bool _mergeAllDescendantsIntoThisNode = _kEmptyConfig.isMergingSemanticsOfDescendants;
815 816


817
  // CHILDREN
Hixie's avatar
Hixie committed
818

819 820
  /// Contains the children in inverse hit test order (i.e. paint order).
  List<SemanticsNode> _children;
821

822 823 824 825 826
  /// 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;

827 828
  void _replaceChildren(List<SemanticsNode> newChildren) {
    assert(!newChildren.any((SemanticsNode child) => child == this));
Hixie's avatar
Hixie committed
829
    assert(() {
830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856
      if (identical(newChildren, _children)) {
        final StringBuffer mutationErrors = new StringBuffer();
        if (newChildren.length != _debugPreviousSnapshot.length) {
          mutationErrors.writeln(
            'The list\'s length has changed from ${_debugPreviousSnapshot.length} '
            'to ${newChildren.length}.'
          );
        } else {
          for (int i = 0; i < newChildren.length; i++) {
            if (!identical(newChildren[i], _debugPreviousSnapshot[i])) {
              mutationErrors.writeln(
                'Child node at position $i was replaced:\n'
                'Previous child: ${newChildren[i]}\n'
                'New child: ${_debugPreviousSnapshot[i]}\n'
              );
            }
          }
        }
        if (mutationErrors.isNotEmpty) {
          throw new FlutterError(
            'Failed to replace child semantics nodes because the list of `SemanticsNode`s was mutated.\n'
            'Instead of mutating the existing list, create a new list containing the desired `SemanticsNode`s.\n'
            'Error details:\n'
            '$mutationErrors'
          );
        }
      }
857
      assert(!newChildren.any((SemanticsNode node) => node.isMergedIntoParent) || isPartOfNodeMerging);
858 859 860

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

Hixie's avatar
Hixie committed
861 862 863
      SemanticsNode ancestor = this;
      while (ancestor.parent is SemanticsNode)
        ancestor = ancestor.parent;
864
      assert(!newChildren.any((SemanticsNode child) => child == ancestor));
Hixie's avatar
Hixie committed
865
      return true;
866
    }());
Hixie's avatar
Hixie committed
867
    assert(() {
868
      final Set<SemanticsNode> seenChildren = new Set<SemanticsNode>();
869
      for (SemanticsNode child in newChildren)
Hixie's avatar
Hixie committed
870 871
        assert(seenChildren.add(child)); // check for duplicate adds
      return true;
872
    }());
Hixie's avatar
Hixie committed
873

874
    // The goal of this function is updating sawChange.
Hixie's avatar
Hixie committed
875 876 877 878
    if (_children != null) {
      for (SemanticsNode child in _children)
        child._dead = true;
    }
879 880
    if (newChildren != null) {
      for (SemanticsNode child in newChildren) {
881
        assert(!child.isInvisible, 'Child $child is invisible and should not be added as a child of $this.');
Hixie's avatar
Hixie committed
882
        child._dead = false;
883
      }
Hixie's avatar
Hixie committed
884 885 886 887 888 889 890 891 892 893 894 895 896 897
    }
    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;
        }
      }
    }
898 899
    if (newChildren != null) {
      for (SemanticsNode child in newChildren) {
Hixie's avatar
Hixie committed
900 901 902
        if (child.parent != this) {
          if (child.parent != null) {
            // we're rebuilding the tree from the bottom up, so it's possible
903
            // that our child was, in the last pass, a child of one of our
Hixie's avatar
Hixie committed
904 905 906 907 908 909 910 911 912 913 914
            // 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;
        }
      }
    }
915
    if (!sawChange && _children != null) {
916 917
      assert(newChildren != null);
      assert(newChildren.length == _children.length);
918 919
      // Did the order change?
      for (int i = 0; i < _children.length; i++) {
920
        if (_children[i].id != newChildren[i].id) {
921 922 923 924 925
          sawChange = true;
          break;
        }
      }
    }
926
    _children = newChildren;
Hixie's avatar
Hixie committed
927 928 929 930
    if (sawChange)
      _markDirty();
  }

931 932 933
  /// Whether this node has a non-zero number of children.
  bool get hasChildren => _children?.isNotEmpty ?? false;
  bool _dead = false;
934

935 936
  /// The number of children this node has.
  int get childrenCount => hasChildren ? _children.length : 0;
937

938 939
  /// Visits the immediate children of this node.
  ///
940
  /// This function calls visitor for each child in a pre-order traversal
941 942 943
  /// until visitor returns false. Returns true if all the visitor calls
  /// returned true, otherwise returns false.
  void visitChildren(SemanticsNodeVisitor visitor) {
Hixie's avatar
Hixie committed
944
    if (_children != null) {
945 946 947 948
      for (SemanticsNode child in _children) {
        if (!visitor(child))
          return;
      }
Hixie's avatar
Hixie committed
949 950 951
    }
  }

952 953
  /// Visit all the descendants of this node.
  ///
954
  /// This function calls visitor for each descendant in a pre-order traversal
955 956
  /// until visitor returns false. Returns true if all the visitor calls
  /// returned true, otherwise returns false.
Hixie's avatar
Hixie committed
957 958 959 960 961 962 963 964 965 966
  bool _visitDescendants(SemanticsNodeVisitor visitor) {
    if (_children != null) {
      for (SemanticsNode child in _children) {
        if (!visitor(child) || !child._visitDescendants(visitor))
          return false;
      }
    }
    return true;
  }

967 968 969 970 971 972 973 974 975 976
  // AbstractNode OVERRIDES

  @override
  SemanticsOwner get owner => super.owner;

  @override
  SemanticsNode get parent => super.parent;

  @override
  void redepthChildren() {
977
    _children?.forEach(redepthChild);
978 979
  }

980
  @override
981
  void attach(SemanticsOwner owner) {
982
    super.attach(owner);
983 984
    assert(!owner._nodes.containsKey(id));
    owner._nodes[id] = this;
985 986 987 988 989
    owner._detachedNodes.remove(this);
    if (_dirty) {
      _dirty = false;
      _markDirty();
    }
Hixie's avatar
Hixie committed
990 991
    if (_children != null) {
      for (SemanticsNode child in _children)
992
        child.attach(owner);
Hixie's avatar
Hixie committed
993 994
    }
  }
995 996

  @override
Hixie's avatar
Hixie committed
997
  void detach() {
998
    assert(owner._nodes.containsKey(id));
999
    assert(!owner._detachedNodes.contains(this));
1000
    owner._nodes.remove(id);
1001
    owner._detachedNodes.add(this);
Hixie's avatar
Hixie committed
1002
    super.detach();
1003
    assert(owner == null);
Hixie's avatar
Hixie committed
1004
    if (_children != null) {
1005 1006 1007 1008 1009 1010
      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
1011
    }
1012 1013 1014 1015
    // 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
1016 1017
  }

1018 1019
  // DIRTY MANAGEMENT

Hixie's avatar
Hixie committed
1020 1021 1022 1023 1024
  bool _dirty = false;
  void _markDirty() {
    if (_dirty)
      return;
    _dirty = true;
1025 1026 1027 1028
    if (attached) {
      assert(!owner._detachedNodes.contains(this));
      owner._dirtyNodes.add(this);
    }
Hixie's avatar
Hixie committed
1029 1030
  }

1031 1032
  bool _isDifferentFromCurrentSemanticAnnotation(SemanticsConfiguration config) {
    return _label != config.label ||
1033
        _hint != config.hint ||
1034
        _decreasedValue != config.decreasedValue ||
1035
        _value != config.value ||
1036
        _increasedValue != config.increasedValue ||
1037 1038
        _flags != config._flags ||
        _textDirection != config.textDirection ||
1039
        _sortKey != config._sortKey ||
1040
        _textSelection != config._textSelection ||
1041 1042 1043
        _scrollPosition != config._scrollPosition ||
        _scrollExtentMax != config._scrollExtentMax ||
        _scrollExtentMin != config._scrollExtentMin ||
1044
        _actionsAsBits != config._actionsAsBits ||
1045 1046 1047 1048 1049
        _mergeAllDescendantsIntoThisNode != config.isMergingSemanticsOfDescendants;
  }

  // TAGS, LABELS, ACTIONS

1050
  Map<SemanticsAction, _SemanticsActionHandler> _actions = _kEmptyConfig._actions;
1051

1052 1053
  int _actionsAsBits = _kEmptyConfig._actionsAsBits;

1054 1055 1056
  /// The [SemanticsTag]s this node is tagged with.
  ///
  /// Tags are used during the construction of the semantics tree. They are not
1057
  /// transferred to the engine.
1058 1059 1060 1061 1062 1063 1064
  Set<SemanticsTag> tags;

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

  int _flags = _kEmptyConfig._flags;

1065
  bool _hasFlag(SemanticsFlag flag) => _flags & flag.index != 0;
1066 1067 1068

  /// A textual description of this node.
  ///
1069
  /// The reading direction is given by [textDirection].
1070 1071 1072
  String get label => _label;
  String _label = _kEmptyConfig.label;

1073 1074
  /// A textual description for the current value of the node.
  ///
1075
  /// The reading direction is given by [textDirection].
1076 1077 1078
  String get value => _value;
  String _value = _kEmptyConfig.value;

1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098
  /// 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;

1099 1100
  /// A brief description of the result of performing an action on this node.
  ///
1101
  /// The reading direction is given by [textDirection].
1102 1103 1104
  String get hint => _hint;
  String _hint = _kEmptyConfig.hint;

1105 1106
  /// The reading direction for [label], [value], [hint], [increasedValue], and
  /// [decreasedValue].
1107 1108 1109
  TextDirection get textDirection => _textDirection;
  TextDirection _textDirection = _kEmptyConfig.textDirection;

1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120
  /// 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).
  ///
  /// This is used to determine the [nextNodeId] and [previousNodeId] during a
  /// semantics update.
  SemanticsSortKey get sortKey => _sortKey;
  SemanticsSortKey _sortKey;
1121

1122 1123 1124 1125 1126 1127 1128 1129 1130 1131
  /// The ID of the next node in the traversal order after this node.
  ///
  /// Only valid after at least one semantics update has been built.
  ///
  /// This is the value passed to the engine to tell it what the order
  /// should be for traversing semantics nodes.
  ///
  /// If this is set to -1, it will indicate that there is no next node to
  /// the engine (i.e. this is the last node in the sort order). When it is
  /// null, it means that no semantics update has been built yet.
1132
  int get nextNodeId => _nextNodeId;
1133 1134 1135 1136 1137 1138 1139 1140
  int _nextNodeId;
  void _updateNextNodeId(int value) {
    if (value == _nextNodeId)
      return;
    _nextNodeId = value;
    _markDirty();
  }

1141
  /// The ID of the previous node in the traversal order before this node.
1142 1143 1144 1145 1146 1147
  ///
  /// Only valid after at least one semantics update has been built.
  ///
  /// This is the value passed to the engine to tell it what the order
  /// should be for traversing semantics nodes.
  ///
1148 1149
  /// If this is set to -1, it will indicate that there is no previous node to
  /// the engine (i.e. this is the first node in the sort order). When it is
1150
  /// null, it means that no semantics update has been built yet.
1151
  int get previousNodeId => _previousNodeId;
1152 1153 1154
  int _previousNodeId;
  void _updatePreviousNodeId(int value) {
    if (value == _previousNodeId)
1155
      return;
1156
    _previousNodeId = value;
1157 1158 1159
    _markDirty();
  }

1160 1161 1162 1163 1164
  /// 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;

1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189
  /// 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
1190
  /// Indicates the minimum in-range value for [scrollPosition] if the node is
1191 1192 1193 1194 1195 1196 1197 1198 1199 1200
  /// 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;

1201 1202 1203 1204
  bool _canPerformAction(SemanticsAction action) => _actions.containsKey(action);

  static final SemanticsConfiguration _kEmptyConfig = new SemanticsConfiguration();

1205
  /// Reconfigures the properties of this object to describe the configuration
1206
  /// provided in the `config` argument and the children listed in the
1207 1208 1209 1210 1211 1212 1213
  /// `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.
1214 1215
  void updateWith({
    @required SemanticsConfiguration config,
1216
    List<SemanticsNode> childrenInInversePaintOrder,
1217 1218 1219 1220 1221 1222
  }) {
    config ??= _kEmptyConfig;
    if (_isDifferentFromCurrentSemanticAnnotation(config))
      _markDirty();

    _label = config.label;
1223
    _decreasedValue = config.decreasedValue;
1224
    _value = config.value;
1225
    _increasedValue = config.increasedValue;
1226
    _hint = config.hint;
1227 1228
    _flags = config._flags;
    _textDirection = config.textDirection;
1229
    _sortKey = config.sortKey;
1230
    _actions = new Map<SemanticsAction, _SemanticsActionHandler>.from(config._actions);
1231
    _actionsAsBits = config._actionsAsBits;
1232
    _textSelection = config._textSelection;
1233 1234 1235
    _scrollPosition = config._scrollPosition;
    _scrollExtentMax = config._scrollExtentMax;
    _scrollExtentMin = config._scrollExtentMin;
1236 1237
    _mergeAllDescendantsIntoThisNode = config.isMergingSemanticsOfDescendants;
    _replaceChildren(childrenInInversePaintOrder ?? const <SemanticsNode>[]);
1238 1239 1240 1241 1242 1243 1244 1245 1246

    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',
    );
1247 1248 1249
  }


1250 1251 1252 1253 1254 1255 1256
  /// 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;
1257
    int actions = _actionsAsBits;
1258
    String label = _label;
1259 1260
    String hint = _hint;
    String value = _value;
1261 1262
    String increasedValue = _increasedValue;
    String decreasedValue = _decreasedValue;
Ian Hickson's avatar
Ian Hickson committed
1263
    TextDirection textDirection = _textDirection;
1264
    int nextNodeId = _nextNodeId;
1265
    int previousNodeId = _previousNodeId;
1266
    Set<SemanticsTag> mergedTags = tags == null ? null : new Set<SemanticsTag>.from(tags);
1267
    TextSelection textSelection = _textSelection;
1268 1269 1270
    double scrollPosition = _scrollPosition;
    double scrollExtentMax = _scrollExtentMax;
    double scrollExtentMin = _scrollExtentMin;
1271 1272 1273

    if (mergeAllDescendantsIntoThisNode) {
      _visitDescendants((SemanticsNode node) {
1274
        assert(node.isMergedIntoParent);
1275
        flags |= node._flags;
1276
        actions |= node._actionsAsBits;
Ian Hickson's avatar
Ian Hickson committed
1277
        textDirection ??= node._textDirection;
1278
        nextNodeId ??= node._nextNodeId;
1279
        previousNodeId ??= node._previousNodeId;
1280
        textSelection ??= node._textSelection;
1281 1282 1283
        scrollPosition ??= node._scrollPosition;
        scrollExtentMax ??= node._scrollExtentMax;
        scrollExtentMin ??= node._scrollExtentMin;
1284 1285
        if (value == '' || value == null)
          value = node._value;
1286 1287 1288 1289
        if (increasedValue == '' || increasedValue == null)
          increasedValue = node._increasedValue;
        if (decreasedValue == '' || decreasedValue == null)
          decreasedValue = node._decreasedValue;
1290 1291 1292 1293
        if (node.tags != null) {
          mergedTags ??= new Set<SemanticsTag>();
          mergedTags.addAll(node.tags);
        }
1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305
        label = _concatStrings(
          thisString: label,
          thisTextDirection: textDirection,
          otherString: node._label,
          otherTextDirection: node._textDirection,
        );
        hint = _concatStrings(
          thisString: hint,
          thisTextDirection: textDirection,
          otherString: node._hint,
          otherTextDirection: node._textDirection,
        );
1306 1307 1308 1309 1310 1311 1312 1313
        return true;
      });
    }

    return new SemanticsData(
      flags: flags,
      actions: actions,
      label: label,
1314
      value: value,
1315 1316
      increasedValue: increasedValue,
      decreasedValue: decreasedValue,
1317
      hint: hint,
Ian Hickson's avatar
Ian Hickson committed
1318
      textDirection: textDirection,
1319
      nextNodeId: nextNodeId,
1320
      previousNodeId: previousNodeId,
1321
      rect: rect,
1322
      transform: transform,
1323
      tags: mergedTags,
1324
      textSelection: textSelection,
1325 1326 1327
      scrollPosition: scrollPosition,
      scrollExtentMax: scrollExtentMax,
      scrollExtentMin: scrollExtentMin,
1328 1329 1330
    );
  }

1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346
  static Float64List _initIdentityTransform() {
    return new Matrix4.identity().storage;
  }

  static final Int32List _kEmptyChildList = new Int32List(0);
  static final Float64List _kIdentityTransform = _initIdentityTransform();

  void _addToUpdate(ui.SemanticsUpdateBuilder builder) {
    assert(_dirty);
    final SemanticsData data = getSemanticsData();
    Int32List children;
    if (!hasChildren || mergeAllDescendantsIntoThisNode) {
      children = _kEmptyChildList;
    } else {
      final int childCount = _children.length;
      children = new Int32List(childCount);
1347
      for (int i = 0; i < childCount; ++i) {
1348
        children[i] = _children[i].id;
1349
      }
Hixie's avatar
Hixie committed
1350
    }
1351 1352 1353 1354 1355
    builder.updateNode(
      id: id,
      flags: data.flags,
      actions: data.actions,
      rect: data.rect,
1356
      label: data.label,
1357
      value: data.value,
1358 1359
      decreasedValue: data.decreasedValue,
      increasedValue: data.increasedValue,
1360
      hint: data.hint,
Ian Hickson's avatar
Ian Hickson committed
1361
      textDirection: data.textDirection,
1362
      nextNodeId: data.nextNodeId,
1363
      previousNodeId: data.previousNodeId,
1364 1365
      textSelectionBase: data.textSelection != null ? data.textSelection.baseOffset : -1,
      textSelectionExtent: data.textSelection != null ? data.textSelection.extentOffset : -1,
1366 1367 1368
      scrollPosition: data.scrollPosition != null ? data.scrollPosition : double.nan,
      scrollExtentMax: data.scrollExtentMax != null ? data.scrollExtentMax : double.nan,
      scrollExtentMin: data.scrollExtentMin != null ? data.scrollExtentMin : double.nan,
1369
      transform: data.transform?.storage ?? _kIdentityTransform,
1370
      children: children,
1371 1372
    );
    _dirty = false;
Hixie's avatar
Hixie committed
1373 1374
  }

1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387
  /// 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;
1388
    SystemChannels.accessibility.send(event.toMap(nodeId: id));
1389 1390
  }

1391
  @override
1392 1393 1394 1395 1396 1397 1398 1399 1400 1401
  String toStringShort() => '$runtimeType#$id';

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    bool hideOwner = true;
    if (_dirty) {
      final bool inDirtyNodes = owner != null && owner._dirtyNodes.contains(this);
      properties.add(new FlagProperty('inDirtyNodes', value: inDirtyNodes, ifTrue: 'dirty', ifFalse: 'STALE'));
      hideOwner = inDirtyNodes;
    }
1402
    properties.add(new DiagnosticsProperty<SemanticsOwner>('owner', owner, level: hideOwner ? DiagnosticLevel.hidden : DiagnosticLevel.info));
1403 1404
    properties.add(new FlagProperty('isMergedIntoParent', value: isMergedIntoParent, ifTrue: 'merged up ⬆️'));
    properties.add(new FlagProperty('mergeAllDescendantsIntoThisNode', value: mergeAllDescendantsIntoThisNode, ifTrue: 'merge boundary ⛔️'));
1405 1406
    final Offset offset = transform != null ? MatrixUtils.getAsTranslation(transform) : null;
    if (offset != null) {
1407
      properties.add(new DiagnosticsProperty<Rect>('rect', rect.shift(offset), showName: false));
1408 1409
    } else {
      final double scale = transform != null ? MatrixUtils.getAsScale(transform) : null;
1410
      String description;
1411
      if (scale != null) {
1412
        description = '$rect scaled by ${scale.toStringAsFixed(1)}x';
1413 1414
      } else if (transform != null && !MatrixUtils.isIdentity(transform)) {
        final String matrix = transform.toString().split('\n').take(4).map((String line) => line.substring(4)).join('; ');
1415
        description = '$rect with transform [$matrix]';
1416
      }
1417
      properties.add(new DiagnosticsProperty<Rect>('rect', rect, description: description, showName: false));
1418
    }
1419
    final List<String> actions = _actions.keys.map((SemanticsAction action) => describeEnum(action)).toList()..sort();
1420
    properties.add(new IterableProperty<String>('actions', actions, ifEmpty: null));
1421 1422
    final List<String> flags = SemanticsFlag.values.values.where((SemanticsFlag flag) => _hasFlag(flag)).map((SemanticsFlag flag) => flag.toString().substring('SemanticsFlag.'.length)).toList();
    properties.add(new IterableProperty<String>('flags', flags, ifEmpty: null));
1423
    properties.add(new FlagProperty('isInvisible', value: isInvisible, ifTrue: 'invisible'));
1424 1425
    properties.add(new StringProperty('label', _label, defaultValue: ''));
    properties.add(new StringProperty('value', _value, defaultValue: ''));
1426 1427
    properties.add(new StringProperty('increasedValue', _increasedValue, defaultValue: ''));
    properties.add(new StringProperty('decreasedValue', _decreasedValue, defaultValue: ''));
1428
    properties.add(new StringProperty('hint', _hint, defaultValue: ''));
1429
    properties.add(new EnumProperty<TextDirection>('textDirection', _textDirection, defaultValue: null));
1430
    properties.add(new IntProperty('nextNodeId', _nextNodeId, defaultValue: null));
1431
    properties.add(new IntProperty('previousNodeId', _previousNodeId, defaultValue: null));
1432
    properties.add(new DiagnosticsProperty<SemanticsSortKey>('sortKey', sortKey, defaultValue: null));
1433 1434
    if (_textSelection?.isValid == true)
      properties.add(new MessageProperty('text selection', '[${_textSelection.start}, ${_textSelection.end}]'));
1435 1436 1437
    properties.add(new DoubleProperty('scrollExtentMin', scrollExtentMin, defaultValue: null));
    properties.add(new DoubleProperty('scrollPosition', scrollPosition, defaultValue: null));
    properties.add(new DoubleProperty('scrollExtentMax', scrollExtentMax, defaultValue: null));
1438 1439 1440
  }

  /// Returns a string representation of this node and its descendants.
1441 1442 1443
  ///
  /// The order in which the children of the [SemanticsNode] will be printed is
  /// controlled by the [childOrder] parameter.
1444 1445 1446 1447
  @override
  String toStringDeep({
    String prefixLineOne: '',
    String prefixOtherLines,
1448
    DiagnosticLevel minLevel: DiagnosticLevel.debug,
1449
    DebugSemanticsDumpOrder childOrder: DebugSemanticsDumpOrder.geometricOrder,
1450
  }) {
1451
    assert(childOrder != null);
1452
    return toDiagnosticsNode(childOrder: childOrder).toStringDeep(prefixLineOne: prefixLineOne, prefixOtherLines: prefixOtherLines, minLevel: minLevel);
1453 1454 1455 1456 1457
  }

  @override
  DiagnosticsNode toDiagnosticsNode({
    String name,
1458
    DiagnosticsTreeStyle style: DiagnosticsTreeStyle.sparse,
1459
    DebugSemanticsDumpOrder childOrder: DebugSemanticsDumpOrder.geometricOrder,
1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473
  }) {
    return new _SemanticsDiagnosticableNode(
      name: name,
      value: this,
      style: style,
      childOrder: childOrder,
    );
  }

  @override
  List<DiagnosticsNode> debugDescribeChildren({ DebugSemanticsDumpOrder childOrder: DebugSemanticsDumpOrder.inverseHitTest }) {
    return _getChildrenInOrder(childOrder)
      .map<DiagnosticsNode>((SemanticsNode node) => node.toDiagnosticsNode(childOrder: childOrder))
      .toList();
1474 1475 1476 1477
  }

  Iterable<SemanticsNode> _getChildrenInOrder(DebugSemanticsDumpOrder childOrder) {
    assert(childOrder != null);
1478 1479 1480
    if (_children == null)
      return const <SemanticsNode>[];

1481
    switch (childOrder) {
1482
      case DebugSemanticsDumpOrder.geometricOrder:
1483 1484 1485 1486 1487 1488 1489 1490 1491
        return new List<SemanticsNode>.from(_children)..sort(_geometryComparator);
      case DebugSemanticsDumpOrder.inverseHitTest:
        return _children;
    }
    assert(false);
    return null;
  }

  static int _geometryComparator(SemanticsNode a, SemanticsNode b) {
1492 1493
    final Rect rectA = a.transform == null ? a.rect : MatrixUtils.transformRect(a.transform, a.rect);
    final Rect rectB = b.transform == null ? b.rect : MatrixUtils.transformRect(b.transform, b.rect);
1494 1495
    final int top = rectA.top.compareTo(rectB.top);
    return top == 0 ? rectA.left.compareTo(rectB.left) : top;
1496 1497 1498
  }
}

1499 1500 1501 1502
/// 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.
1503
///
1504 1505 1506 1507 1508 1509 1510
/// This implementation considers a [node]'s [sortKey], it's parent's text
/// direction ([containerTextDirection]), and its geometric position relative to
/// its siblings ([globalStartCorner]).
///
/// A null value is allowed for [containerTextDirection], because in that case
/// we want to fall back to ordering by child insertion order for nodes that are
/// equal after sorting from top to bottom.
1511
class _TraversalSortNode implements Comparable<_TraversalSortNode> {
1512 1513 1514 1515 1516 1517 1518 1519 1520
  _TraversalSortNode({@required this.node, this.containerTextDirection, this.sortKey, Matrix4 transform})
    : assert(node != null),
      // When containerTextDirection is null, this is set to topLeft, but the x
      // coordinate is also ignored when doing the comparison in that case, so
      // this isn't actually expressing a directionality opinion.
      globalStartCorner = _transformPoint(
        containerTextDirection == TextDirection.rtl ? node.rect.topRight : node.rect.topLeft,
        transform,
      );
1521

1522 1523
  /// The node whose position this sort node determines.
  final SemanticsNode node;
1524 1525 1526

  /// The effective text direction for this node is the directionality that
  /// its container has.
1527
  final TextDirection containerTextDirection;
1528

1529 1530 1531 1532 1533
  /// Determines the position of this node among its siblings.
  ///
  /// Sort keys take precedence over other attributes, such as
  /// [globalStartCorner].
  final SemanticsSortKey sortKey;
1534

1535 1536 1537 1538 1539 1540 1541
  /// The starting corner for the rectangle on this semantics node in
  /// global coordinates.
  ///
  /// When the container has the directionality [TextDirection.ltr], this is the
  /// upper left corner.  When the container has the directionality
  /// [TextDirection.rtl], this is the upper right corner. When the container
  /// has no directionality, this is set, but the x coordinate is ignored.
1542
  final Offset globalStartCorner;
1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559

  static Offset _transformPoint(Offset point, Matrix4 matrix) {
    final Vector3 result = matrix.transform3(new Vector3(point.dx, point.dy, 0.0));
    return new Offset(result.x, result.y);
  }

  /// Compares the node's start corner with that of `other`.
  ///
  /// Sorts top to bottom, and then start to end.
  ///
  /// This takes into account the container text direction, since the
  /// coordinate system has zero on the left, and we need to compare
  /// differently for different text directions.
  ///
  /// If no text direction is available (i.e. [containerTextDirection] is
  /// null), then we sort by vertical position first, and then by child
  /// insertion order.
1560
  int _compareGeometry(_TraversalSortNode other) {
1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571
    final int verticalDiff = globalStartCorner.dy.compareTo(other.globalStartCorner.dy);
    if (verticalDiff != 0) {
      return verticalDiff;
    }
    switch (containerTextDirection) {
      case TextDirection.rtl:
        return other.globalStartCorner.dx.compareTo(globalStartCorner.dx);
      case TextDirection.ltr:
        return globalStartCorner.dx.compareTo(other.globalStartCorner.dx);
    }
    // In case containerTextDirection is null we fall back to child insertion order.
1572 1573 1574 1575 1576
    return 0;
  }

  @override
  int compareTo(_TraversalSortNode other) {
1577
    if (sortKey == null || other?.sortKey == null) {
1578 1579
      return _compareGeometry(other);
    }
1580
    final int comparison = sortKey.compareTo(other.sortKey);
1581 1582 1583 1584 1585 1586 1587
    if (comparison != 0) {
      return comparison;
    }
    return _compareGeometry(other);
  }
}

1588 1589 1590
/// Owns [SemanticsNode] objects and notifies listeners of changes to the
/// render tree semantics.
///
1591 1592 1593
/// To listen for semantic updates, call [PipelineOwner.ensureSemantics] to
/// obtain a [SemanticsHandle]. This will create a [SemanticsOwner] if
/// necessary.
1594
class SemanticsOwner extends ChangeNotifier {
1595
  final Set<SemanticsNode> _dirtyNodes = new Set<SemanticsNode>();
1596 1597 1598
  final Map<int, SemanticsNode> _nodes = <int, SemanticsNode>{};
  final Set<SemanticsNode> _detachedNodes = new Set<SemanticsNode>();

1599 1600 1601 1602 1603
  /// The root node of the semantics tree, if any.
  ///
  /// If the semantics tree is empty, returns null.
  SemanticsNode get rootSemanticsNode => _nodes[0];

1604
  @override
1605 1606 1607 1608
  void dispose() {
    _dirtyNodes.clear();
    _nodes.clear();
    _detachedNodes.clear();
1609
    super.dispose();
1610
  }
1611

1612 1613 1614 1615
  // Updates the nextNodeId and previousNodeId IDs on the semantics nodes. These
  // IDs are used on the platform side to order the nodes for traversal by the
  // accessibility services. If the nextNodeId or previousNodeId for a node
  // changes, the node will be marked as dirty.
1616
  void _updateTraversalOrder() {
1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640
    void updateRecursively(SemanticsNode parent, Matrix4 parentGlobalTransform) {
      assert(parentGlobalTransform != null);

      final List<_TraversalSortNode> children = <_TraversalSortNode>[];

      parent.visitChildren((SemanticsNode child) {
        final Matrix4 childGlobalTransform = child.transform != null
            ? parentGlobalTransform.multiplied(child.transform)
            : parentGlobalTransform;

        children.add(new _TraversalSortNode(
          node: child,
          containerTextDirection: parent.textDirection,
          sortKey: child.sortKey,
          transform: childGlobalTransform,
        ));

        updateRecursively(child, childGlobalTransform);
        return true;
      });

      if (children.isEmpty) {
        // We need at least one node for the following code to work.
        return;
1641
      }
1642 1643 1644 1645 1646 1647 1648 1649 1650

      children.sort();
      _TraversalSortNode node = children.removeLast();
      node.node._updateNextNodeId(-1);
      while (children.isNotEmpty) {
        final _TraversalSortNode previousNode = children.removeLast();
        node.node._updatePreviousNodeId(previousNode.node.id);
        previousNode.node._updateNextNodeId(node.node.id);
        node = previousNode;
1651
      }
1652
      node.node._updatePreviousNodeId(-1);
1653
    }
1654

1655
    updateRecursively(rootSemanticsNode, new Matrix4.identity());
1656 1657
  }

1658
  /// Update the semantics using [Window.updateSemantics].
1659
  void sendSemanticsUpdate() {
Hixie's avatar
Hixie committed
1660 1661
    if (_dirtyNodes.isEmpty)
      return;
1662
    // Nodes that change their previousNodeId will be marked as dirty.
1663
    _updateTraversalOrder();
1664
    final List<SemanticsNode> visitedNodes = <SemanticsNode>[];
1665
    while (_dirtyNodes.isNotEmpty) {
1666
      final List<SemanticsNode> localDirtyNodes = _dirtyNodes.where((SemanticsNode node) => !_detachedNodes.contains(node)).toList();
1667
      _dirtyNodes.clear();
1668
      _detachedNodes.clear();
1669 1670 1671 1672
      localDirtyNodes.sort((SemanticsNode a, SemanticsNode b) => a.depth - b.depth);
      visitedNodes.addAll(localDirtyNodes);
      for (SemanticsNode node in localDirtyNodes) {
        assert(node._dirty);
1673 1674
        assert(node.parent == null || !node.parent.isPartOfNodeMerging || node.isMergedIntoParent);
        if (node.isPartOfNodeMerging) {
1675
          assert(node.mergeAllDescendantsIntoThisNode || node.parent != null);
1676 1677 1678
          // 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
1679
        }
1680
      }
1681
    }
1682
    visitedNodes.sort((SemanticsNode a, SemanticsNode b) => a.depth - b.depth);
1683
    final ui.SemanticsUpdateBuilder builder = new ui.SemanticsUpdateBuilder();
1684
    for (SemanticsNode node in visitedNodes) {
Hixie's avatar
Hixie committed
1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696
      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)
1697
        node._addToUpdate(builder);
Hixie's avatar
Hixie committed
1698 1699
    }
    _dirtyNodes.clear();
1700 1701
    ui.window.updateSemantics(builder.build());
    notifyListeners();
Hixie's avatar
Hixie committed
1702 1703
  }

1704
  _SemanticsActionHandler _getSemanticsActionHandlerForId(int id, SemanticsAction action) {
Hixie's avatar
Hixie committed
1705
    SemanticsNode result = _nodes[id];
1706
    if (result != null && result.isPartOfNodeMerging && !result._canPerformAction(action)) {
Hixie's avatar
Hixie committed
1707
      result._visitDescendants((SemanticsNode node) {
1708
        if (node._canPerformAction(action)) {
Hixie's avatar
Hixie committed
1709 1710 1711 1712 1713 1714
          result = node;
          return false; // found node, abort walk
        }
        return true; // continue walk
      });
    }
1715
    if (result == null || !result._canPerformAction(action))
Hixie's avatar
Hixie committed
1716
      return null;
1717
    return result._actions[action];
Hixie's avatar
Hixie committed
1718 1719
  }

1720 1721 1722 1723
  /// 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.
1724 1725 1726 1727
  ///
  /// If the given `action` requires arguments they need to be passed in via
  /// the `args` parameter.
  void performAction(int id, SemanticsAction action, [dynamic args]) {
1728
    assert(action != null);
1729
    final _SemanticsActionHandler handler = _getSemanticsActionHandlerForId(id, action);
1730
    if (handler != null) {
1731
      handler(args);
1732 1733 1734 1735 1736 1737
      return;
    }

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

1740
  _SemanticsActionHandler _getSemanticsActionHandlerForPosition(SemanticsNode node, Offset position, SemanticsAction action) {
1741
    if (node.transform != null) {
1742
      final Matrix4 inverse = new Matrix4.identity();
1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757
      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;
      });
1758
      return result?._actions[action];
1759 1760 1761
    }
    if (node.hasChildren) {
      for (SemanticsNode child in node._children.reversed) {
1762
        final _SemanticsActionHandler handler = _getSemanticsActionHandlerForPosition(child, position, action);
1763 1764 1765 1766
        if (handler != null)
          return handler;
      }
    }
1767
    return node._actions[action];
1768 1769
  }

1770
  /// Asks the [SemanticsNode] at the given position to perform the given action.
1771 1772 1773
  ///
  /// If the [SemanticsNode] has not indicated that it can perform the action,
  /// this function does nothing.
1774 1775 1776 1777
  ///
  /// If the given `action` requires arguments they need to be passed in via
  /// the `args` parameter.
  void performActionAt(Offset position, SemanticsAction action, [dynamic args]) {
1778 1779 1780 1781
    assert(action != null);
    final SemanticsNode node = rootSemanticsNode;
    if (node == null)
      return;
1782
    final _SemanticsActionHandler handler = _getSemanticsActionHandlerForPosition(node, position, action);
1783
    if (handler != null)
1784
      handler(args);
Hixie's avatar
Hixie committed
1785
  }
1786 1787

  @override
1788
  String toString() => describeIdentity(this);
Hixie's avatar
Hixie committed
1789
}
1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803

/// Describes the semantic information associated with the owning
/// [RenderObject].
///
/// The information provided in the configuration is used to to generate the
/// 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]
1804
  /// owner of this configuration or any of its descendants will not leak into
1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815
  /// 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) {
1816
    assert(!isMergingSemanticsOfDescendants || value);
1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834
    _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;

1835
  /// Whether the owning [RenderObject] makes other [RenderObject]s previously
1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866
  /// 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.
  ///
  /// Paint order as established by [visitChildrenForSemantics] is used to
  /// determine if a node is previous to this one.
  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:
1867
  ///
1868
  /// * [addAction] to add an action.
1869
  final Map<SemanticsAction, _SemanticsActionHandler> _actions = <SemanticsAction, _SemanticsActionHandler>{};
1870

1871 1872
  int _actionsAsBits = 0;

1873 1874
  /// Adds an `action` to the semantics tree.
  ///
1875 1876 1877
  /// The provided `handler` is called to respond to the user triggered
  /// `action`.
  void _addAction(SemanticsAction action, _SemanticsActionHandler handler) {
1878
    assert(handler != null);
1879
    _actions[action] = handler;
1880
    _actionsAsBits |= action.index;
1881 1882 1883
    _hasBeenAnnotated = true;
  }

1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904
  /// 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.
1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916
  ///
  /// 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.
1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964
  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;
  }

  /// 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
1965
  /// left and then right in one motion path. On Android, [onScrollDown] and
1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044
  /// [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;
2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084
  }

  /// 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;
2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 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 2140
  }

  /// 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;
  }

2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162
  /// 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) {
      final Map<String, int> selection = args;
      assert(selection != null && selection['base'] != null && selection['extent'] != null);
      value(new TextSelection(
        baseOffset: selection['base'],
        extentOffset: selection['extent'],
      ));
    });
    _onSetSelection = value;
  }

2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210
  /// 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
  ///    focus is removed from the node
  ///  * [FocusNode], [FocusScope], [FocusManager], which manage the input focus
  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
  ///    accessibility focus
  ///  * [FocusNode], [FocusScope], [FocusManager], which manage the input focus
  VoidCallback get onDidLoseAccessibilityFocus => _onDidLoseAccessibilityFocus;
  VoidCallback _onDidLoseAccessibilityFocus;
  set onDidLoseAccessibilityFocus(VoidCallback value) {
    _addArgumentlessAction(SemanticsAction.didLoseAccessibilityFocus, value);
    _onDidLoseAccessibilityFocus = value;
  }

2211 2212 2213 2214
  /// Returns the action handler registered for [action] or null if none was
  /// registered.
  ///
  /// See also:
2215
  ///
2216
  ///  * [addAction] to add an action.
2217
  _SemanticsActionHandler getActionHandler(SemanticsAction action) => _actions[action];
2218

2219 2220
  /// Determines the position of this node among its siblings in the traversal
  /// sort order.
2221
  ///
2222 2223 2224
  /// 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).
2225
  ///
2226 2227 2228 2229 2230 2231 2232
  /// 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) {
2233
    assert(value != null);
2234
    _sortKey = value;
2235 2236 2237
    _hasBeenAnnotated = true;
  }

2238 2239 2240 2241 2242 2243
  /// 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].
2244 2245
  ///
  /// Setting this to true requires that [isSemanticBoundary] is also true.
2246 2247 2248
  bool get isMergingSemanticsOfDescendants => _isMergingSemanticsOfDescendants;
  bool _isMergingSemanticsOfDescendants = false;
  set isMergingSemanticsOfDescendants(bool value) {
2249
    assert(isSemanticBoundary);
2250 2251 2252 2253 2254 2255
    _isMergingSemanticsOfDescendants = value;
    _hasBeenAnnotated = true;
  }

  /// A textual description of the owning [RenderObject].
  ///
2256 2257 2258 2259 2260
  /// 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.
  ///
2261
  /// The reading direction is given by [textDirection].
2262 2263 2264
  String get label => _label;
  String _label = '';
  set label(String label) {
2265
    assert(label != null);
2266 2267 2268 2269
    _label = label;
    _hasBeenAnnotated = true;
  }

2270 2271 2272 2273 2274 2275 2276
  /// 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.
  ///
2277 2278 2279
  /// The reading direction is given by [textDirection].
  ///
  /// See also:
2280
  ///
2281 2282 2283 2284
  ///  * [decreasedValue], describes what [value] will be after performing
  ///    [SemanticsAction.decrease]
  ///  * [increasedValue], describes what [value] will be after performing
  ///    [SemanticsAction.increase]
2285 2286 2287
  String get value => _value;
  String _value = '';
  set value(String value) {
2288
    assert(value != null);
2289 2290 2291 2292
    _value = value;
    _hasBeenAnnotated = true;
  }

2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 2320 2321 2322
  /// 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;
  }

2323 2324 2325 2326 2327 2328 2329
  /// 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.
  ///
2330
  /// The reading direction is given by [textDirection].
2331 2332 2333
  String get hint => _hint;
  String _hint = '';
  set hint(String hint) {
2334
    assert(hint != null);
2335 2336 2337 2338
    _hint = hint;
    _hasBeenAnnotated = true;
  }

2339 2340
  /// The reading direction for the text in [label], [value], [hint],
  /// [increasedValue], and [decreasedValue].
2341 2342 2343 2344 2345 2346 2347 2348
  TextDirection get textDirection => _textDirection;
  TextDirection _textDirection;
  set textDirection(TextDirection textDirection) {
    _textDirection = textDirection;
    _hasBeenAnnotated = true;
  }

  /// Whether the owning [RenderObject] is selected (true) or not (false).
2349
  bool get isSelected => _hasFlag(SemanticsFlag.isSelected);
2350
  set isSelected(bool value) {
2351
    _setFlag(SemanticsFlag.isSelected, value);
2352 2353
  }

2354 2355 2356 2357 2358 2359 2360 2361 2362 2363 2364
  /// 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.
2365
  bool get isEnabled => _hasFlag(SemanticsFlag.hasEnabledState) ? _hasFlag(SemanticsFlag.isEnabled) : null;
2366
  set isEnabled(bool value) {
2367 2368
    _setFlag(SemanticsFlag.hasEnabledState, true);
    _setFlag(SemanticsFlag.isEnabled, value);
2369 2370
  }

2371 2372 2373
  /// 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.
  ///
2374 2375 2376 2377 2378
  /// 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.
2379
  bool get isChecked => _hasFlag(SemanticsFlag.hasCheckedState) ? _hasFlag(SemanticsFlag.isChecked) : null;
2380
  set isChecked(bool value) {
2381 2382
    _setFlag(SemanticsFlag.hasCheckedState, true);
    _setFlag(SemanticsFlag.isChecked, value);
2383 2384
  }

2385 2386 2387 2388 2389 2390 2391 2392 2393 2394
  /// 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);
  }

2395
  /// Whether the owning [RenderObject] currently holds the user's focus.
2396
  bool get isFocused => _hasFlag(SemanticsFlag.isFocused);
2397
  set isFocused(bool value) {
2398
    _setFlag(SemanticsFlag.isFocused, value);
2399 2400
  }

2401
  /// Whether the owning [RenderObject] is a button (true) or not (false).
2402
  bool get isButton => _hasFlag(SemanticsFlag.isButton);
2403
  set isButton(bool value) {
2404
    _setFlag(SemanticsFlag.isButton, value);
2405 2406
  }

2407 2408 2409 2410 2411 2412
  /// 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);
  }

2413
  /// Whether the owning [RenderObject] is a text field.
2414
  bool get isTextField => _hasFlag(SemanticsFlag.isTextField);
2415
  set isTextField(bool value) {
2416
    _setFlag(SemanticsFlag.isTextField, value);
2417 2418
  }

2419 2420 2421 2422 2423 2424 2425 2426 2427 2428
  /// 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);
  }

2429 2430 2431 2432 2433 2434 2435 2436 2437 2438
  /// 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;
  }

2439 2440 2441 2442 2443 2444 2445 2446 2447 2448 2449 2450 2451 2452 2453 2454 2455 2456 2457 2458 2459 2460 2461 2462 2463 2464 2465 2466 2467 2468 2469 2470 2471 2472 2473 2474 2475 2476 2477 2478 2479 2480 2481 2482 2483 2484 2485 2486 2487 2488
  /// 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;
  }

2489 2490
  // TAGS

2491 2492 2493 2494
  /// The set of tags that this configuration wants to add to all child
  /// [SemanticsNode]s.
  ///
  /// See also:
2495
  ///
2496 2497
  ///  * [addTagForChildren] to add a tag and for more information about their
  ///    usage.
2498 2499 2500
  Iterable<SemanticsTag> get tagsForChildren => _tagsForChildren;
  Set<SemanticsTag> _tagsForChildren;

2501 2502 2503 2504 2505 2506 2507 2508 2509 2510 2511 2512
  /// 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:
2513
  ///
2514 2515
  ///  * [RenderSemanticsGestureHandler.excludeFromScrolling] for an example of
  ///    how tags are used.
2516 2517 2518 2519 2520 2521 2522 2523
  void addTagForChildren(SemanticsTag tag) {
    _tagsForChildren ??= new Set<SemanticsTag>();
    _tagsForChildren.add(tag);
  }

  // INTERNAL FLAG MANAGEMENT

  int _flags = 0;
2524
  void _setFlag(SemanticsFlag flag, bool value) {
2525 2526 2527 2528 2529 2530 2531 2532
    if (value) {
      _flags |= flag.index;
    } else {
      _flags &= ~flag.index;
    }
    _hasBeenAnnotated = true;
  }

2533
  bool _hasFlag(SemanticsFlag flag) => (_flags & flag.index) != 0;
2534

2535 2536 2537 2538 2539 2540 2541 2542 2543 2544
  // 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;
2545
    if (_actionsAsBits & other._actionsAsBits != 0)
2546 2547 2548
      return false;
    if ((_flags & other._flags) != 0)
      return false;
2549 2550
    if (_value != null && _value.isNotEmpty && other._value != null && other._value.isNotEmpty)
      return false;
2551 2552 2553 2554 2555 2556 2557 2558 2559
    return true;
  }

  /// Absorb the semantic information from `other` into this configuration.
  ///
  /// This adds the semantic information of both configurations and saves the
  /// result in this configuration.
  ///
  /// Only configurations that have [explicitChildNodes] set to false can
2560
  /// absorb other configurations and it is recommended to only absorb compatible
2561 2562 2563 2564 2565 2566 2567 2568
  /// configurations as determined by [isCompatibleWith].
  void absorb(SemanticsConfiguration other) {
    assert(!explicitChildNodes);

    if (!other.hasBeenAnnotated)
      return;

    _actions.addAll(other._actions);
2569
    _actionsAsBits |= other._actionsAsBits;
2570
    _flags |= other._flags;
2571
    _textSelection ??= other._textSelection;
2572 2573 2574
    _scrollPosition ??= other._scrollPosition;
    _scrollExtentMax ??= other._scrollExtentMax;
    _scrollExtentMin ??= other._scrollExtentMin;
2575 2576

    textDirection ??= other.textDirection;
2577
    _sortKey ??= other._sortKey;
2578 2579 2580 2581 2582 2583
    _label = _concatStrings(
      thisString: _label,
      thisTextDirection: textDirection,
      otherString: other._label,
      otherTextDirection: other.textDirection,
    );
2584 2585
    if (_decreasedValue == '' || _decreasedValue == null)
      _decreasedValue = other._decreasedValue;
2586 2587
    if (_value == '' || _value == null)
      _value = other._value;
2588 2589
    if (_increasedValue == '' || _increasedValue == null)
      _increasedValue = other._increasedValue;
2590 2591 2592 2593 2594 2595
    _hint = _concatStrings(
      thisString: _hint,
      thisTextDirection: textDirection,
      otherString: other._hint,
      otherTextDirection: other.textDirection,
    );
2596 2597 2598 2599 2600 2601 2602

    _hasBeenAnnotated = _hasBeenAnnotated || other._hasBeenAnnotated;
  }

  /// Returns an exact copy of this configuration.
  SemanticsConfiguration copy() {
    return new SemanticsConfiguration()
2603
      .._isSemanticBoundary = _isSemanticBoundary
2604
      ..explicitChildNodes = explicitChildNodes
2605
      ..isBlockingSemanticsOfPreviouslyPaintedNodes = isBlockingSemanticsOfPreviouslyPaintedNodes
2606
      .._hasBeenAnnotated = _hasBeenAnnotated
2607
      .._isMergingSemanticsOfDescendants = _isMergingSemanticsOfDescendants
2608
      .._textDirection = _textDirection
2609
      .._sortKey = _sortKey
2610
      .._label = _label
2611
      .._increasedValue = _increasedValue
2612
      .._value = _value
2613
      .._decreasedValue = _decreasedValue
2614
      .._hint = _hint
2615
      .._flags = _flags
2616
      .._tagsForChildren = _tagsForChildren
2617
      .._textSelection = _textSelection
2618 2619 2620
      .._scrollPosition = _scrollPosition
      .._scrollExtentMax = _scrollExtentMax
      .._scrollExtentMin = _scrollExtentMin
2621
      .._actionsAsBits = _actionsAsBits
2622 2623 2624
      .._actions.addAll(_actions);
  }
}
2625

2626 2627 2628 2629 2630 2631 2632 2633 2634 2635
/// 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,

2636
  /// Print nodes in geometric traversal order.
2637
  ///
2638 2639 2640 2641 2642 2643 2644 2645 2646
  /// Geometric traversal order is the default traversal order for semantics nodes which
  /// don't have [SemanticsNode.sortOrder] set.  This traversal order ignores the node
  /// sort order, since the diagnostics system follows the widget tree and can only sort
  /// a node's children, and the semantics system sorts nodes globally.
  geometricOrder,

  // TODO(gspencer): Add support to toStringDeep (and others) to print the tree in
  // the actual traversal order that the user will experience.  This requires sorting
  // nodes globally before printing, not just the children.
2647 2648
}

2649 2650 2651 2652 2653 2654 2655 2656 2657 2658 2659 2660 2661 2662 2663 2664 2665 2666 2667 2668 2669 2670 2671
String _concatStrings({
  @required String thisString,
  @required String otherString,
  @required TextDirection thisTextDirection,
  @required TextDirection otherTextDirection
}) {
  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';
}
2672 2673 2674 2675 2676 2677 2678 2679 2680 2681 2682 2683 2684

/// Base class for all sort keys for [Semantics] accessibility traversal order
/// sorting.
///
/// If subclasses of this class compare themselves to another subclass of
/// [SemanticsSortKey], they will compare as "equal" so that keys of the same
/// type are ordered only with respect to one another.
///
/// See Also:
///
///  * [SemanticsSortOrder] which manages a list of sort keys.
///  * [OrdinalSortKey] for a sort key that sorts using an ordinal.
abstract class SemanticsSortKey extends Diagnosticable implements Comparable<SemanticsSortKey> {
2685 2686
  /// Abstract const constructor. This constructor enables subclasses to provide
  /// const constructors so that they can be used in const expressions.
2687 2688 2689 2690 2691 2692 2693 2694 2695 2696 2697
  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
  /// they are of the same [runtimeType]. If compared with a
  /// [SemanticsSortKey] with a different name or type, they will
  /// compare as "equal".
  final String name;

  @override
  int compareTo(SemanticsSortKey other) {
2698
    if (other.runtimeType != runtimeType || other.name != name)
2699 2700 2701 2702
      return 0;
    return doCompare(other);
  }

2703 2704
  /// The implementation of [compareTo].
  ///
2705
  /// The argument is guaranteed to be of the same type as this object.
2706
  ///
2707
  /// The method should return a negative number if this object comes earlier in
2708 2709 2710
  /// the sort order than the argument; and a positive number if it comes later
  /// in the sort order. Returning zero causes the system to default to
  /// comparing the geometry of the nodes.
2711
  @protected
2712
  int doCompare(covariant SemanticsSortKey other);
2713 2714

  @override
2715 2716 2717
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(new StringProperty('name', name, defaultValue: null));
2718 2719 2720
  }
}

2721 2722
/// A [SemanticsSortKey] that sorts simply based on the `double` value it is
/// given.
2723 2724 2725 2726
///
/// The [OrdinalSortKey] compares itself with other [OrdinalSortKey]s
/// to sort based on the order it is given.
///
2727 2728 2729 2730
/// 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]).
2731
///
2732 2733 2734 2735
/// See also:
///
///  * [SemanticsSortOrder] which manages a list of sort keys.
class OrdinalSortKey extends SemanticsSortKey {
2736
  /// Creates a semantics sort key that uses a [double] as its key value.
2737 2738 2739 2740 2741 2742
  ///
  /// The [order] must be a finite number.
  const OrdinalSortKey(
    this.order, {
    String name,
  }) : assert(order != null),
2743 2744 2745
       assert(order != double.nan),
       assert(order > double.negativeInfinity),
       assert(order < double.infinity),
2746
       super(name: name);
2747

2748 2749 2750 2751 2752
  /// 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.
2753 2754 2755
  final double order;

  @override
2756 2757
  int doCompare(OrdinalSortKey other) {
    if (other.order == null || order == null || other.order == order)
2758
      return 0;
2759
    return order.compareTo(other.order);
2760 2761 2762
  }

  @override
2763 2764 2765
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(new DoubleProperty('order', order, defaultValue: null));
2766 2767
  }
}