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