@@ -607,9 +607,191 @@ class RenderTable extends RenderBox {
607
607
void describeSemanticsConfiguration (SemanticsConfiguration config) {
608
608
super .describeSemanticsConfiguration (config);
609
609
config.role = SemanticsRole .table;
610
+ config.isSemanticBoundary = true ;
610
611
config.explicitChildNodes = true ;
611
612
}
612
613
614
+ final Map <int , _Index > _idToIndexMap = < int , _Index > {};
615
+ final Map <int , SemanticsNode > _cachedRows = < int , SemanticsNode > {};
616
+ final Map <_Index , SemanticsNode > _cachedCells = < _Index , SemanticsNode > {};
617
+
618
+ /// Provides custom semantics for tables by generating nodes for rows and maybe cells.
619
+ ///
620
+ /// Table rows are not RenderObjects, so their semantics nodes must be created separately.
621
+ /// And if a cell has mutiple semantics node or has a different semantic role, we create
622
+ /// a new semantics node to wrap it.
623
+ @override
624
+ void assembleSemanticsNode (
625
+ SemanticsNode node,
626
+ SemanticsConfiguration config,
627
+ Iterable <SemanticsNode > children,
628
+ ) {
629
+ final List <SemanticsNode > rows = < SemanticsNode > [];
630
+
631
+ final List <List <List <SemanticsNode >>> rawCells = List <List <List <SemanticsNode >>>.generate (
632
+ _rows,
633
+ (int rowIndex) =>
634
+ List <List <SemanticsNode >>.generate (_columns, (int columnIndex) => < SemanticsNode > []),
635
+ );
636
+
637
+ Rect rectWithOffset (SemanticsNode node) {
638
+ final Offset offset =
639
+ (node.transform != null ? MatrixUtils .getAsTranslation (node.transform! ) : null ) ??
640
+ Offset .zero;
641
+ return node.rect.shift (offset);
642
+ }
643
+
644
+ int findRowIndex (double top) {
645
+ for (int i = _rowTops.length - 1 ; i >= 0 ; i-- ) {
646
+ if (_rowTops[i] <= top) {
647
+ return i;
648
+ }
649
+ }
650
+ return - 1 ;
651
+ }
652
+
653
+ int findColumnIndex (double left) {
654
+ if (_columnLefts == null ) {
655
+ return - 1 ;
656
+ }
657
+ for (int i = _columnLefts! .length - 1 ; i >= 0 ; i-- ) {
658
+ if (_columnLefts! .elementAt (i) <= left) {
659
+ return i;
660
+ }
661
+ }
662
+ return - 1 ;
663
+ }
664
+
665
+ void shiftTransform (SemanticsNode node, double dx, double dy) {
666
+ final Matrix4 ? previousTransform = node.transform;
667
+ final Offset offset =
668
+ (previousTransform != null ? MatrixUtils .getAsTranslation (previousTransform) : null ) ??
669
+ Offset .zero;
670
+ final Matrix4 newTransform = Matrix4 .translationValues (offset.dx + dx, offset.dy + dy, 0 );
671
+ node.transform = newTransform;
672
+ }
673
+
674
+ for (final SemanticsNode child in children) {
675
+ if (_idToIndexMap.containsKey (child.id)) {
676
+ final _Index index = _idToIndexMap[child.id]! ;
677
+ final int y = index.y;
678
+ final int x = index.x;
679
+ if (y < _rows && x < _columns) {
680
+ rawCells[y][x].add (child);
681
+ }
682
+ } else {
683
+ final Rect rect = rectWithOffset (child);
684
+ final int y = findRowIndex (rect.top);
685
+ final int x = findColumnIndex (rect.left);
686
+ if (y != - 1 && x != - 1 ) {
687
+ rawCells[y][x].add (child);
688
+ }
689
+ }
690
+ }
691
+
692
+ for (int y = 0 ; y < _rows; y++ ) {
693
+ final Rect rowBox = getRowBox (y);
694
+ // Skip row if it's empty
695
+ if (rowBox.height == 0 ) {
696
+ continue ;
697
+ }
698
+
699
+ final SemanticsNode newRow =
700
+ _cachedRows[y] ??
701
+ (_cachedRows[y] = SemanticsNode (
702
+ showOnScreen: () {
703
+ showOnScreen (descendant: this , rect: rowBox);
704
+ },
705
+ ));
706
+
707
+ // The list of cells of this Row.
708
+ final List <SemanticsNode > cells = < SemanticsNode > [];
709
+
710
+ for (int x = 0 ; x < columns; x++ ) {
711
+ final List <SemanticsNode > rawChildrens = rawCells[y][x];
712
+ if (rawChildrens.isEmpty) {
713
+ continue ;
714
+ }
715
+
716
+ // If the cell has multiple children or the only child is not a cell or columnHeader,
717
+ // create a new semantic node with role cell to wrap it.
718
+ // This can happen when the cell has a different semantic role, or the cell doesn't have a semantic
719
+ // role because user is not using the `TableCell` widget.
720
+ final bool addCellWrapper =
721
+ rawChildrens.length > 1 ||
722
+ (rawChildrens.single.role != SemanticsRole .cell &&
723
+ rawChildrens.single.role != SemanticsRole .columnHeader);
724
+
725
+ final SemanticsNode cell =
726
+ addCellWrapper
727
+ ? (_cachedCells[_Index (y, x)] ??
728
+ (_cachedCells[_Index (y, x)] =
729
+ SemanticsNode ()..updateWith (
730
+ config: SemanticsConfiguration ()..role = SemanticsRole .cell,
731
+ childrenInInversePaintOrder: rawChildrens,
732
+ )))
733
+ : rawChildrens.single;
734
+
735
+ final double cellWidth =
736
+ x == _columns - 1
737
+ ? rowBox.width - _columnLefts! .elementAt (x)
738
+ : _columnLefts! .elementAt (x + 1 ) - _columnLefts! .elementAt (x);
739
+
740
+ // Skip cell if it's invisible
741
+ if (cellWidth <= 0.0 ) {
742
+ continue ;
743
+ }
744
+ // Add wrapper transform
745
+ if (addCellWrapper) {
746
+ cell
747
+ ..transform = Matrix4 .translationValues (_columnLefts! .elementAt (x), 0 , 0 )
748
+ ..rect = Rect .fromLTWH (0 , 0 , cellWidth, rowBox.height);
749
+ }
750
+ for (final SemanticsNode child in rawChildrens) {
751
+ _idToIndexMap[child.id] = _Index (y, x);
752
+
753
+ // Shift child transform.
754
+ final Rect localRect = rectWithOffset (child);
755
+ // The rect should satisfy 0 <= localRect.top < localRect.bottom <= rowBox.height
756
+ final double dy = localRect.top >= rowBox.height ? - _rowTops.elementAt (y) : 0.0 ;
757
+
758
+ // if addCellWrapper is true, the rect is relative to the cell
759
+ // The rect should satisfy 0 <= localRect.left < localRect.right <= cellWidth
760
+ // if addCellWrapper is false, the rect is relative to the raw
761
+ // The rect should satisfy _columnLefts!.elementAt(x) <= localRect.left < localRect.right <= _columnLefts!.elementAt(x+1)
762
+ final double dx =
763
+ addCellWrapper
764
+ ? ((localRect.left >= cellWidth) ? - _columnLefts! .elementAt (x) : 0.0 )
765
+ : (localRect.right <= _columnLefts! .elementAt (x)
766
+ ? _columnLefts! .elementAt (x)
767
+ : 0.0 );
768
+
769
+ if (dx != 0 || dy != 0 ) {
770
+ shiftTransform (child, dx, dy);
771
+ }
772
+ }
773
+
774
+ cell.indexInParent = x;
775
+ cells.add (cell);
776
+ }
777
+
778
+ newRow
779
+ ..updateWith (
780
+ config:
781
+ SemanticsConfiguration ()
782
+ ..indexInParent = y
783
+ ..role = SemanticsRole .row,
784
+ childrenInInversePaintOrder: cells,
785
+ )
786
+ ..transform = Matrix4 .translationValues (rowBox.left, rowBox.top, 0 )
787
+ ..rect = Rect .fromLTWH (0 , 0 , rowBox.width, rowBox.height);
788
+
789
+ rows.add (newRow);
790
+ }
791
+
792
+ node.updateWith (config: config, childrenInInversePaintOrder: rows);
793
+ }
794
+
613
795
/// Replaces the children of this table with the given cells.
614
796
///
615
797
/// The cells are divided into the specified number of columns before
@@ -1375,3 +1557,10 @@ class RenderTable extends RenderBox {
1375
1557
];
1376
1558
}
1377
1559
}
1560
+
1561
+ /// Index for a cell.
1562
+ class _Index {
1563
+ _Index (this .y, this .x);
1564
+ int y;
1565
+ int x;
1566
+ }
0 commit comments