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