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