assembleSemanticsNode method

  1. @override
void assembleSemanticsNode(
  1. SemanticsNode node,
  2. SemanticsConfiguration config,
  3. Iterable<SemanticsNode> children
)
override

Provides custom semantics for tables by generating nodes for rows and maybe cells.

Table rows are not RenderObjects, so their semantics nodes must be created separately. And if a cell has mutiple semantics node or has a different semantic role, we create a new semantics node to wrap it.

Implementation

@override
void assembleSemanticsNode(
  SemanticsNode node,
  SemanticsConfiguration config,
  Iterable<SemanticsNode> children,
) {
  final List<SemanticsNode> rows = <SemanticsNode>[];

  final List<List<List<SemanticsNode>>> rawCells = List<List<List<SemanticsNode>>>.generate(
    _rows,
    (int rowIndex) =>
        List<List<SemanticsNode>>.generate(_columns, (int columnIndex) => <SemanticsNode>[]),
  );

  Rect rectWithOffset(SemanticsNode node) {
    final Offset offset =
        (node.transform != null ? MatrixUtils.getAsTranslation(node.transform!) : null) ??
        Offset.zero;
    return node.rect.shift(offset);
  }

  int findRowIndex(double top) {
    for (int i = _rowTops.length - 1; i >= 0; i--) {
      if (_rowTops[i] <= top) {
        return i;
      }
    }
    return -1;
  }

  int findColumnIndex(double left) {
    if (_columnLefts == null) {
      return -1;
    }
    for (int i = _columnLefts!.length - 1; i >= 0; i--) {
      if (_columnLefts!.elementAt(i) <= left) {
        return i;
      }
    }
    return -1;
  }

  void shiftTransform(SemanticsNode node, double dx, double dy) {
    final Matrix4? previousTransform = node.transform;
    final Offset offset =
        (previousTransform != null ? MatrixUtils.getAsTranslation(previousTransform) : null) ??
        Offset.zero;
    final Matrix4 newTransform = Matrix4.translationValues(offset.dx + dx, offset.dy + dy, 0);
    node.transform = newTransform;
  }

  for (final SemanticsNode child in children) {
    if (_idToIndexMap.containsKey(child.id)) {
      final _Index index = _idToIndexMap[child.id]!;
      final int y = index.y;
      final int x = index.x;
      if (y < _rows && x < _columns) {
        rawCells[y][x].add(child);
      }
    } else {
      final Rect rect = rectWithOffset(child);
      final int y = findRowIndex(rect.top);
      final int x = findColumnIndex(rect.left);
      if (y != -1 && x != -1) {
        rawCells[y][x].add(child);
      }
    }
  }

  for (int y = 0; y < _rows; y++) {
    final Rect rowBox = getRowBox(y);
    // Skip row if it's empty
    if (rowBox.height == 0) {
      continue;
    }

    final SemanticsNode newRow =
        _cachedRows[y] ??
        (_cachedRows[y] = SemanticsNode(
          showOnScreen: () {
            showOnScreen(descendant: this, rect: rowBox);
          },
        ));

    // The list of cells of this Row.
    final List<SemanticsNode> cells = <SemanticsNode>[];

    for (int x = 0; x < columns; x++) {
      final List<SemanticsNode> rawChildrens = rawCells[y][x];
      if (rawChildrens.isEmpty) {
        continue;
      }

      // If the cell has multiple children or the only child is not a cell or columnHeader,
      // create a new semantic node with role cell to wrap it.
      // This can happen when the cell has a different semantic role, or the cell doesn't have a semantic
      // role because user is not using the `TableCell` widget.
      final bool addCellWrapper =
          rawChildrens.length > 1 ||
          (rawChildrens.single.role != SemanticsRole.cell &&
              rawChildrens.single.role != SemanticsRole.columnHeader);

      final SemanticsNode cell =
          addCellWrapper
              ? (_cachedCells[_Index(y, x)] ??
                  (_cachedCells[_Index(y, x)] =
                      SemanticsNode()..updateWith(
                        config: SemanticsConfiguration()..role = SemanticsRole.cell,
                        childrenInInversePaintOrder: rawChildrens,
                      )))
              : rawChildrens.single;

      final double cellWidth =
          x == _columns - 1
              ? rowBox.width - _columnLefts!.elementAt(x)
              : _columnLefts!.elementAt(x + 1) - _columnLefts!.elementAt(x);

      // Skip cell if it's invisible
      if (cellWidth <= 0.0) {
        continue;
      }
      // Add wrapper transform
      if (addCellWrapper) {
        cell
          ..transform = Matrix4.translationValues(_columnLefts!.elementAt(x), 0, 0)
          ..rect = Rect.fromLTWH(0, 0, cellWidth, rowBox.height);
      }
      for (final SemanticsNode child in rawChildrens) {
        _idToIndexMap[child.id] = _Index(y, x);

        // Shift child transform.
        final Rect localRect = rectWithOffset(child);
        // The rect should satisfy 0 <= localRect.top < localRect.bottom <= rowBox.height
        final double dy = localRect.top >= rowBox.height ? -_rowTops.elementAt(y) : 0.0;

        // if addCellWrapper is true, the rect is relative to the cell
        // The rect should satisfy 0 <= localRect.left < localRect.right <= cellWidth
        // if addCellWrapper is false, the rect is relative to the raw
        // The rect should satisfy _columnLefts!.elementAt(x) <= localRect.left < localRect.right <= _columnLefts!.elementAt(x+1)
        final double dx =
            addCellWrapper
                ? ((localRect.left >= cellWidth) ? -_columnLefts!.elementAt(x) : 0.0)
                : (localRect.right <= _columnLefts!.elementAt(x)
                    ? _columnLefts!.elementAt(x)
                    : 0.0);

        if (dx != 0 || dy != 0) {
          shiftTransform(child, dx, dy);
        }
      }

      cell.indexInParent = x;
      cells.add(cell);
    }

    newRow
      ..updateWith(
        config:
            SemanticsConfiguration()
              ..indexInParent = y
              ..role = SemanticsRole.row,
        childrenInInversePaintOrder: cells,
      )
      ..transform = Matrix4.translationValues(rowBox.left, rowBox.top, 0)
      ..rect = Rect.fromLTWH(0, 0, rowBox.width, rowBox.height);

    rows.add(newRow);
  }

  node.updateWith(config: config, childrenInInversePaintOrder: rows);
}