table.dart 43.7 KB
Newer Older
Hixie's avatar
Hixie committed
1 2 3 4 5 6 7
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:collection';
import 'dart:math' as math;

8 9
import 'package:flutter/foundation.dart';

Hixie's avatar
Hixie committed
10 11
import 'box.dart';
import 'object.dart';
12
import 'table_border.dart';
Hixie's avatar
Hixie committed
13 14 15

/// Parent data used by [RenderTable] for its children.
class TableCellParentData extends BoxParentData {
16
  /// Where this cell should be placed vertically.
Hixie's avatar
Hixie committed
17 18
  TableCellVerticalAlignment verticalAlignment;

Hixie's avatar
Hixie committed
19 20 21 22 23 24
  /// The column that the child was in the last time it was laid out.
  int x;

  /// The row that the child was in the last time it was laid out.
  int y;

Hixie's avatar
Hixie committed
25
  @override
26
  String toString() => '${super.toString()}; ${verticalAlignment == null ? "default vertical alignment" : "$verticalAlignment"}';
Hixie's avatar
Hixie committed
27 28 29
}

/// Base class to describe how wide a column in a [RenderTable] should be.
30 31 32 33 34 35 36 37
///
/// To size a column to a specific number of pixels, use a [FixedColumnWidth].
/// This is the cheapest way to size a column.
///
/// Other algorithms that are relatively cheap include [FlexColumnWidth], which
/// distributes the space equally among the flexible columns,
/// [FractionColumnWidth], which sizes a column based on the size of the
/// table's container.
38
@immutable
Hixie's avatar
Hixie committed
39
abstract class TableColumnWidth {
40 41
  /// Abstract const constructor. This constructor enables subclasses to provide
  /// const constructors so that they can be used in const expressions.
Hixie's avatar
Hixie committed
42 43
  const TableColumnWidth();

44 45 46 47 48 49 50 51
  /// The smallest width that the column can have.
  ///
  /// The `cells` argument is an iterable that provides all the cells
  /// in the table for this column. Walking the cells is by definition
  /// O(N), so algorithms that do that should be considered expensive.
  ///
  /// The `containerWidth` argument is the `maxWidth` of the incoming
  /// constraints for the table, and might be infinite.
Hixie's avatar
Hixie committed
52 53
  double minIntrinsicWidth(Iterable<RenderBox> cells, double containerWidth);

54 55 56 57 58 59 60 61 62 63 64 65
  /// The ideal width that the column should have. This must be equal
  /// to or greater than the [minIntrinsicWidth]. The column might be
  /// bigger than this width, e.g. if the column is flexible or if the
  /// table's width ends up being forced to be bigger than the sum of
  /// all the maxIntrinsicWidth values.
  ///
  /// The `cells` argument is an iterable that provides all the cells
  /// in the table for this column. Walking the cells is by definition
  /// O(N), so algorithms that do that should be considered expensive.
  ///
  /// The `containerWidth` argument is the `maxWidth` of the incoming
  /// constraints for the table, and might be infinite.
Hixie's avatar
Hixie committed
66 67
  double maxIntrinsicWidth(Iterable<RenderBox> cells, double containerWidth);

68 69 70 71 72 73 74 75
  /// The flex factor to apply to the cell if there is any room left
  /// over when laying out the table. The remaining space is
  /// distributed to any columns with flex in proportion to their flex
  /// value (higher values get more space).
  ///
  /// The `cells` argument is an iterable that provides all the cells
  /// in the table for this column. Walking the cells is by definition
  /// O(N), so algorithms that do that should be considered expensive.
Hixie's avatar
Hixie committed
76 77 78 79 80 81 82 83 84 85
  double flex(Iterable<RenderBox> cells) => null;

  @override
  String toString() => '$runtimeType';
}

/// Sizes the column according to the intrinsic dimensions of all the
/// cells in that column.
///
/// This is a very expensive way to size a column.
86 87 88 89
///
/// A flex value can be provided. If specified (and non-null), the
/// column will participate in the distribution of remaining space
/// once all the non-flexible columns have been sized.
90
class IntrinsicColumnWidth extends TableColumnWidth {
91 92 93
  /// Creates a column width based on intrinsic sizing.
  ///
  /// This sizing algorithm is very expensive.
94
  const IntrinsicColumnWidth({ double flex }) : _flex = flex;
Hixie's avatar
Hixie committed
95 96 97 98 99

  @override
  double minIntrinsicWidth(Iterable<RenderBox> cells, double containerWidth) {
    double result = 0.0;
    for (RenderBox cell in cells)
100
      result = math.max(result, cell.getMinIntrinsicWidth(double.infinity));
Hixie's avatar
Hixie committed
101 102 103 104 105 106 107
    return result;
  }

  @override
  double maxIntrinsicWidth(Iterable<RenderBox> cells, double containerWidth) {
    double result = 0.0;
    for (RenderBox cell in cells)
108
      result = math.max(result, cell.getMaxIntrinsicWidth(double.infinity));
Hixie's avatar
Hixie committed
109 110
    return result;
  }
111 112 113 114 115

  final double _flex;

  @override
  double flex(Iterable<RenderBox> cells) => _flex;
116 117 118

  @override
  String toString() => '$runtimeType(flex: ${_flex?.toStringAsFixed(1)})';
Hixie's avatar
Hixie committed
119 120 121 122 123 124
}

/// Sizes the column to a specific number of pixels.
///
/// This is the cheapest way to size a column.
class FixedColumnWidth extends TableColumnWidth {
125 126 127
  /// Creates a column width based on a fixed number of logical pixels.
  ///
  /// The [value] argument must not be null.
128
  const FixedColumnWidth(this.value) : assert(value != null);
129 130

  /// The width the column should occupy in logical pixels.
Hixie's avatar
Hixie committed
131 132 133 134 135 136 137 138 139 140 141 142 143
  final double value;

  @override
  double minIntrinsicWidth(Iterable<RenderBox> cells, double containerWidth) {
    return value;
  }

  @override
  double maxIntrinsicWidth(Iterable<RenderBox> cells, double containerWidth) {
    return value;
  }

  @override
144
  String toString() => '$runtimeType(${debugFormatDouble(value)})';
Hixie's avatar
Hixie committed
145 146 147 148 149 150
}

/// Sizes the column to a fraction of the table's constraints' maxWidth.
///
/// This is a cheap way to size a column.
class FractionColumnWidth extends TableColumnWidth {
151 152 153 154
  /// Creates a column width based on a fraction of the table's constraints'
  /// maxWidth.
  ///
  /// The [value] argument must not be null.
155
  const FractionColumnWidth(this.value) : assert(value != null);
156 157 158

  /// The fraction of the table's constraints' maxWidth that this column should
  /// occupy.
Hixie's avatar
Hixie committed
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181
  final double value;

  @override
  double minIntrinsicWidth(Iterable<RenderBox> cells, double containerWidth) {
    if (!containerWidth.isFinite)
      return 0.0;
    return value * containerWidth;
  }

  @override
  double maxIntrinsicWidth(Iterable<RenderBox> cells, double containerWidth) {
    if (!containerWidth.isFinite)
      return 0.0;
    return value * containerWidth;
  }

  @override
  String toString() => '$runtimeType($value)';
}

/// Sizes the column by taking a part of the remaining space once all
/// the other columns have been laid out.
///
182
/// For example, if two columns have a [FlexColumnWidth], then half the
Hixie's avatar
Hixie committed
183 184 185 186
/// space will go to one and half the space will go to the other.
///
/// This is a cheap way to size a column.
class FlexColumnWidth extends TableColumnWidth {
187 188 189 190
  /// Creates a column width based on a fraction of the remaining space once all
  /// the other columns have been laid out.
  ///
  /// The [value] argument must not be null.
191
  const FlexColumnWidth([this.value = 1.0]) : assert(value != null);
192 193 194

  /// The reaction of the of the remaining space once all the other columns have
  /// been laid out that this column should occupy.
Hixie's avatar
Hixie committed
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212
  final double value;

  @override
  double minIntrinsicWidth(Iterable<RenderBox> cells, double containerWidth) {
    return 0.0;
  }

  @override
  double maxIntrinsicWidth(Iterable<RenderBox> cells, double containerWidth) {
    return 0.0;
  }

  @override
  double flex(Iterable<RenderBox> cells) {
    return value;
  }

  @override
213
  String toString() => '$runtimeType(${debugFormatDouble(value)})';
Hixie's avatar
Hixie committed
214 215 216 217 218 219 220 221 222 223 224 225 226
}

/// Sizes the column such that it is the size that is the maximum of
/// two column width specifications.
///
/// For example, to have a column be 10% of the container width or
/// 100px, whichever is bigger, you could use:
///
///     const MaxColumnWidth(const FixedColumnWidth(100.0), FractionColumnWidth(0.1))
///
/// Both specifications are evaluated, so if either specification is
/// expensive, so is this.
class MaxColumnWidth extends TableColumnWidth {
227
  /// Creates a column width that is the maximum of two other column widths.
Hixie's avatar
Hixie committed
228
  const MaxColumnWidth(this.a, this.b);
229 230

  /// A lower bound for the width of this column.
Hixie's avatar
Hixie committed
231
  final TableColumnWidth a;
232 233

  /// Another lower bound for the width of this column.
Hixie's avatar
Hixie committed
234 235 236 237 238 239
  final TableColumnWidth b;

  @override
  double minIntrinsicWidth(Iterable<RenderBox> cells, double containerWidth) {
    return math.max(
      a.minIntrinsicWidth(cells, containerWidth),
240
      b.minIntrinsicWidth(cells, containerWidth),
Hixie's avatar
Hixie committed
241 242 243 244 245 246 247
    );
  }

  @override
  double maxIntrinsicWidth(Iterable<RenderBox> cells, double containerWidth) {
    return math.max(
      a.maxIntrinsicWidth(cells, containerWidth),
248
      b.maxIntrinsicWidth(cells, containerWidth),
Hixie's avatar
Hixie committed
249 250 251 252 253
    );
  }

  @override
  double flex(Iterable<RenderBox> cells) {
254
    final double aFlex = a.flex(cells);
Hixie's avatar
Hixie committed
255 256
    if (aFlex == null)
      return b.flex(cells);
257 258 259 260
    final double bFlex = b.flex(cells);
    if (bFlex == null)
      return null;
    return math.max(aFlex, bFlex);
Hixie's avatar
Hixie committed
261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277
  }

  @override
  String toString() => '$runtimeType($a, $b)';
}

/// Sizes the column such that it is the size that is the minimum of
/// two column width specifications.
///
/// For example, to have a column be 10% of the container width but
/// never bigger than 100px, you could use:
///
///     const MinColumnWidth(const FixedColumnWidth(100.0), FractionColumnWidth(0.1))
///
/// Both specifications are evaluated, so if either specification is
/// expensive, so is this.
class MinColumnWidth extends TableColumnWidth {
278 279 280 281
  /// Creates a column width that is the minimum of two other column widths.
  const MinColumnWidth(this.a, this.b);

  /// An upper bound for the width of this column.
Hixie's avatar
Hixie committed
282
  final TableColumnWidth a;
283 284

  /// Another upper bound for the width of this column.
Hixie's avatar
Hixie committed
285 286 287 288 289 290
  final TableColumnWidth b;

  @override
  double minIntrinsicWidth(Iterable<RenderBox> cells, double containerWidth) {
    return math.min(
      a.minIntrinsicWidth(cells, containerWidth),
291
      b.minIntrinsicWidth(cells, containerWidth),
Hixie's avatar
Hixie committed
292 293 294 295 296 297 298
    );
  }

  @override
  double maxIntrinsicWidth(Iterable<RenderBox> cells, double containerWidth) {
    return math.min(
      a.maxIntrinsicWidth(cells, containerWidth),
299
      b.maxIntrinsicWidth(cells, containerWidth),
Hixie's avatar
Hixie committed
300 301 302 303 304
    );
  }

  @override
  double flex(Iterable<RenderBox> cells) {
305
    final double aFlex = a.flex(cells);
Hixie's avatar
Hixie committed
306 307
    if (aFlex == null)
      return b.flex(cells);
308
    final double bFlex = b.flex(cells);
309 310 311
    if (bFlex == null)
      return null;
    return math.min(aFlex, bFlex);
Hixie's avatar
Hixie committed
312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333
  }

  @override
  String toString() => '$runtimeType($a, $b)';
}

/// Vertical alignment options for cells in [RenderTable] objects.
///
/// This is specified using [TableCellParentData] objects on the
/// [RenderObject.parentData] of the children of the [RenderTable].
enum TableCellVerticalAlignment {
  /// Cells with this alignment are placed with their top at the top of the row.
  top,

  /// Cells with this alignment are vertically centered in the row.
  middle,

  /// Cells with this alignment are placed with their bottom at the bottom of the row.
  bottom,

  /// Cells with this alignment are aligned such that they all share the same
  /// baseline. Cells with no baseline are top-aligned instead. The baseline
Ian Hickson's avatar
Ian Hickson committed
334 335
  /// used is specified by [RenderTable.textBaseline]. It is not valid to use
  /// the baseline value if [RenderTable.textBaseline] is not specified.
336
  ///
337
  /// This vertical alignment is relatively expensive because it causes the table
338
  /// to compute the baseline for each cell in the row.
Hixie's avatar
Hixie committed
339 340 341 342 343 344 345 346 347
  baseline,

  /// Cells with this alignment are sized to be as tall as the row, then made to fit the row.
  /// If all the cells have this alignment, then the row will have zero height.
  fill
}

/// A table where the columns and rows are sized to fit the contents of the cells.
class RenderTable extends RenderBox {
348 349 350 351 352 353 354 355 356 357 358
  /// Creates a table render object.
  ///
  ///  * `columns` must either be null or non-negative. If `columns` is null,
  ///    the number of columns will be inferred from length of the first sublist
  ///    of `children`.
  ///  * `rows` must either be null or non-negative. If `rows` is null, the
  ///    number of rows will be inferred from the `children`. If `rows` is not
  ///    null, then `children` must be null.
  ///  * `children` must either be null or contain lists of all the same length.
  ///    if `children` is not null, then `rows` must be null.
  ///  * [defaultColumnWidth] must not be null.
359
  ///  * [configuration] must not be null (but has a default value).
Hixie's avatar
Hixie committed
360 361 362 363
  RenderTable({
    int columns,
    int rows,
    Map<int, TableColumnWidth> columnWidths,
364
    TableColumnWidth defaultColumnWidth = const FlexColumnWidth(1.0),
365
    @required TextDirection textDirection,
Hixie's avatar
Hixie committed
366
    TableBorder border,
367
    List<Decoration> rowDecorations,
368 369
    ImageConfiguration configuration = ImageConfiguration.empty,
    TableCellVerticalAlignment defaultVerticalAlignment = TableCellVerticalAlignment.top,
Hixie's avatar
Hixie committed
370
    TextBaseline textBaseline,
371
    List<List<RenderBox>> children,
372 373 374 375
  }) : assert(columns == null || columns >= 0),
       assert(rows == null || rows >= 0),
       assert(rows == null || children == null),
       assert(defaultColumnWidth != null),
376 377 378
       assert(textDirection != null),
       assert(configuration != null),
       _textDirection = textDirection {
379
    _columns = columns ?? (children != null && children.isNotEmpty ? children.first.length : 0);
Hixie's avatar
Hixie committed
380
    _rows = rows ?? 0;
381
    _children = <RenderBox>[]..length = _columns * _rows;
382
    _columnWidths = columnWidths ?? HashMap<int, TableColumnWidth>();
Hixie's avatar
Hixie committed
383 384
    _defaultColumnWidth = defaultColumnWidth;
    _border = border;
385 386
    this.rowDecorations = rowDecorations; // must use setter to initialize box painters array
    _configuration = configuration;
Hixie's avatar
Hixie committed
387 388
    _defaultVerticalAlignment = defaultVerticalAlignment;
    _textBaseline = textBaseline;
389
    children?.forEach(addRow);
Hixie's avatar
Hixie committed
390 391 392 393 394 395
  }

  // Children are stored in row-major order.
  // _children.length must be rows * columns
  List<RenderBox> _children = const <RenderBox>[];

396 397 398 399 400 401
  /// The number of vertical alignment lines in this table.
  ///
  /// Changing the number of columns will remove any children that no longer fit
  /// in the table.
  ///
  /// Changing the number of columns is an expensive operation because the table
402
  /// needs to rearrange its internal representation.
Hixie's avatar
Hixie committed
403 404
  int get columns => _columns;
  int _columns;
405
  set columns(int value) {
Hixie's avatar
Hixie committed
406 407 408 409
    assert(value != null);
    assert(value >= 0);
    if (value == columns)
      return;
410 411
    final int oldColumns = columns;
    final List<RenderBox> oldChildren = _children;
Hixie's avatar
Hixie committed
412
    _columns = value;
413
    _children = <RenderBox>[]..length = columns * rows;
414
    final int columnsToCopy = math.min(columns, oldColumns);
Hixie's avatar
Hixie committed
415 416 417 418
    for (int y = 0; y < rows; y += 1) {
      for (int x = 0; x < columnsToCopy; x += 1)
        _children[x + y * columns] = oldChildren[x + y * oldColumns];
    }
Hixie's avatar
Hixie committed
419 420 421
    if (oldColumns > columns) {
      for (int y = 0; y < rows; y += 1) {
        for (int x = columns; x < oldColumns; x += 1) {
422
          final int xy = x + y * oldColumns;
Hixie's avatar
Hixie committed
423 424 425 426 427
          if (oldChildren[xy] != null)
            dropChild(oldChildren[xy]);
        }
      }
    }
Hixie's avatar
Hixie committed
428 429 430
    markNeedsLayout();
  }

431 432 433 434
  /// The number of horizontal alignment lines in this table.
  ///
  /// Changing the number of rows will remove any children that no longer fit
  /// in the table.
Hixie's avatar
Hixie committed
435 436
  int get rows => _rows;
  int _rows;
437
  set rows(int value) {
Hixie's avatar
Hixie committed
438 439 440 441
    assert(value != null);
    assert(value >= 0);
    if (value == rows)
      return;
Hixie's avatar
Hixie committed
442 443 444 445 446 447
    if (_rows > value) {
      for (int xy = columns * value; xy < _children.length; xy += 1) {
        if (_children[xy] != null)
          dropChild(_children[xy]);
      }
    }
Hixie's avatar
Hixie committed
448 449 450 451 452
    _rows = value;
    _children.length = columns * rows;
    markNeedsLayout();
  }

453 454 455 456 457 458 459 460 461
  /// How the horizontal extents of the columns of this table should be determined.
  ///
  /// If the [Map] has a null entry for a given column, the table uses the
  /// [defaultColumnWidth] instead.
  ///
  /// The layout performance of the table depends critically on which column
  /// sizing algorithms are used here. In particular, [IntrinsicColumnWidth] is
  /// quite expensive because it needs to measure each cell in the column to
  /// determine the intrinsic size of the column.
462
  Map<int, TableColumnWidth> get columnWidths => Map<int, TableColumnWidth>.unmodifiable(_columnWidths);
463
  Map<int, TableColumnWidth> _columnWidths;
464
  set columnWidths(Map<int, TableColumnWidth> value) {
465
    value ??= HashMap<int, TableColumnWidth>();
Hixie's avatar
Hixie committed
466 467 468 469 470 471
    if (_columnWidths == value)
      return;
    _columnWidths = value;
    markNeedsLayout();
  }

472
  /// Determines how the width of column with the given index is determined.
Hixie's avatar
Hixie committed
473 474 475 476 477 478 479
  void setColumnWidth(int column, TableColumnWidth value) {
    if (_columnWidths[column] == value)
      return;
    _columnWidths[column] = value;
    markNeedsLayout();
  }

480 481 482 483
  /// How to determine with widths of columns that don't have an explicit sizing algorithm.
  ///
  /// Specifically, the [defaultColumnWidth] is used for column `i` if
  /// `columnWidths[i]` is null.
Hixie's avatar
Hixie committed
484 485
  TableColumnWidth get defaultColumnWidth => _defaultColumnWidth;
  TableColumnWidth _defaultColumnWidth;
486
  set defaultColumnWidth(TableColumnWidth value) {
Hixie's avatar
Hixie committed
487 488 489 490 491 492 493
    assert(value != null);
    if (defaultColumnWidth == value)
      return;
    _defaultColumnWidth = value;
    markNeedsLayout();
  }

494 495 496 497 498 499 500 501 502 503 504
  /// The direction in which the columns are ordered.
  TextDirection get textDirection => _textDirection;
  TextDirection _textDirection;
  set textDirection(TextDirection value) {
    assert(value != null);
    if (_textDirection == value)
      return;
    _textDirection = value;
    markNeedsLayout();
  }

505
  /// The style to use when painting the boundary and interior divisions of the table.
Hixie's avatar
Hixie committed
506 507
  TableBorder get border => _border;
  TableBorder _border;
508
  set border(TableBorder value) {
Hixie's avatar
Hixie committed
509 510 511 512 513
    if (border == value)
      return;
    _border = value;
    markNeedsPaint();
  }
514

515 516 517 518 519
  /// The decorations to use for each row of the table.
  ///
  /// Row decorations fill the horizontal and vertical extent of each row in
  /// the table, unlike decorations for individual cells, which might not fill
  /// either.
520
  List<Decoration> get rowDecorations => List<Decoration>.unmodifiable(_rowDecorations ?? const <Decoration>[]);
521 522
  List<Decoration> _rowDecorations;
  List<BoxPainter> _rowDecorationPainters;
523
  set rowDecorations(List<Decoration> value) {
524 525 526
    if (_rowDecorations == value)
      return;
    _rowDecorations = value;
527 528 529
    if (_rowDecorationPainters != null) {
      for (BoxPainter painter in _rowDecorationPainters)
        painter?.dispose();
530
    }
531
    _rowDecorationPainters = _rowDecorations != null ? List<BoxPainter>(_rowDecorations.length) : null;
532 533
  }

534 535 536 537 538
  /// The settings to pass to the [rowDecorations] when painting, so that they
  /// can resolve images appropriately. See [ImageProvider.resolve] and
  /// [BoxPainter.paint].
  ImageConfiguration get configuration => _configuration;
  ImageConfiguration _configuration;
539
  set configuration(ImageConfiguration value) {
540 541 542 543 544
    assert(value != null);
    if (value == _configuration)
      return;
    _configuration = value;
    markNeedsPaint();
545
  }
Hixie's avatar
Hixie committed
546

547
  /// How cells that do not explicitly specify a vertical alignment are aligned vertically.
Hixie's avatar
Hixie committed
548 549
  TableCellVerticalAlignment get defaultVerticalAlignment => _defaultVerticalAlignment;
  TableCellVerticalAlignment _defaultVerticalAlignment;
550
  set defaultVerticalAlignment(TableCellVerticalAlignment value) {
Hixie's avatar
Hixie committed
551 552 553 554 555 556
    if (_defaultVerticalAlignment == value)
      return;
    _defaultVerticalAlignment = value;
    markNeedsLayout();
  }

557
  /// The text baseline to use when aligning rows using [TableCellVerticalAlignment.baseline].
Hixie's avatar
Hixie committed
558 559
  TextBaseline get textBaseline => _textBaseline;
  TextBaseline _textBaseline;
560
  set textBaseline(TextBaseline value) {
Hixie's avatar
Hixie committed
561 562 563 564 565 566 567 568 569
    if (_textBaseline == value)
      return;
    _textBaseline = value;
    markNeedsLayout();
  }

  @override
  void setupParentData(RenderObject child) {
    if (child.parentData is! TableCellParentData)
570
      child.parentData = TableCellParentData();
Hixie's avatar
Hixie committed
571 572
  }

573 574 575 576 577 578 579 580
  /// Replaces the children of this table with the given cells.
  ///
  /// The cells are divided into the specified number of columns before
  /// replacing the existing children.
  ///
  /// If the new cells contain any existing children of the table, those
  /// children are simply moved to their new location in the table rather than
  /// removed from the table and re-added.
Hixie's avatar
Hixie committed
581 582 583 584
  void setFlatChildren(int columns, List<RenderBox> cells) {
    if (cells == _children && columns == _columns)
      return;
    assert(columns >= 0);
Hixie's avatar
Hixie committed
585
    // consider the case of a newly empty table
586 587
    if (columns == 0 || cells.isEmpty) {
      assert(cells == null || cells.isEmpty);
Hixie's avatar
Hixie committed
588
      _columns = columns;
589
      if (_children.isEmpty) {
Hixie's avatar
Hixie committed
590
        assert(_rows == 0);
Hixie's avatar
Hixie committed
591
        return;
Hixie's avatar
Hixie committed
592
      }
Hixie's avatar
Hixie committed
593
      for (RenderBox oldChild in _children) {
Hixie's avatar
Hixie committed
594 595 596
        if (oldChild != null)
          dropChild(oldChild);
      }
Hixie's avatar
Hixie committed
597 598 599 600 601 602 603
      _rows = 0;
      _children.clear();
      markNeedsLayout();
      return;
    }
    assert(cells != null);
    assert(cells.length % columns == 0);
604 605 606
    // fill a set with the cells that are moving (it's important not
    // to dropChild a child that's remaining with us, because that
    // would clear their parentData field)
607
    final Set<RenderBox> lostChildren = HashSet<RenderBox>();
Hixie's avatar
Hixie committed
608 609
    for (int y = 0; y < _rows; y += 1) {
      for (int x = 0; x < _columns; x += 1) {
610 611
        final int xyOld = x + y * _columns;
        final int xyNew = x + y * columns;
Hixie's avatar
Hixie committed
612
        if (_children[xyOld] != null && (x >= columns || xyNew >= cells.length || _children[xyOld] != cells[xyNew]))
613
          lostChildren.add(_children[xyOld]);
Hixie's avatar
Hixie committed
614
      }
Hixie's avatar
Hixie committed
615
    }
616
    // adopt cells that are arriving, and cross cells that are just moving off our list of lostChildren
Hixie's avatar
Hixie committed
617 618 619
    int y = 0;
    while (y * columns < cells.length) {
      for (int x = 0; x < columns; x += 1) {
620 621
        final int xyNew = x + y * columns;
        final int xyOld = x + y * _columns;
622 623 624 625
        if (cells[xyNew] != null && (x >= _columns || y >= _rows || _children[xyOld] != cells[xyNew])) {
          if (!lostChildren.remove(cells[xyNew]))
            adoptChild(cells[xyNew]);
        }
Hixie's avatar
Hixie committed
626 627 628
      }
      y += 1;
    }
629
    // drop all the lost children
630
    lostChildren.forEach(dropChild);
Hixie's avatar
Hixie committed
631 632 633 634 635
    // update our internal values
    _columns = columns;
    _rows = cells.length ~/ columns;
    _children = cells.toList();
    assert(_children.length == rows * columns);
Hixie's avatar
Hixie committed
636 637 638
    markNeedsLayout();
  }

639
  /// Replaces the children of this table with the given cells.
Hixie's avatar
Hixie committed
640
  void setChildren(List<List<RenderBox>> cells) {
Hixie's avatar
Hixie committed
641
    // TODO(ianh): Make this smarter, like setFlatChildren
Hixie's avatar
Hixie committed
642 643 644 645
    if (cells == null) {
      setFlatChildren(0, null);
      return;
    }
Hixie's avatar
Hixie committed
646
    for (RenderBox oldChild in _children) {
Hixie's avatar
Hixie committed
647 648 649
      if (oldChild != null)
        dropChild(oldChild);
    }
Hixie's avatar
Hixie committed
650
    _children.clear();
651
    _columns = cells.isNotEmpty ? cells.first.length : 0;
Hixie's avatar
Hixie committed
652
    _rows = 0;
653
    cells.forEach(addRow);
Hixie's avatar
Hixie committed
654
    assert(_children.length == rows * columns);
Hixie's avatar
Hixie committed
655 656
  }

657 658 659
  /// Adds a row to the end of the table.
  ///
  /// The newly added children must not already have parents.
Hixie's avatar
Hixie committed
660 661
  void addRow(List<RenderBox> cells) {
    assert(cells.length == columns);
Hixie's avatar
Hixie committed
662
    assert(_children.length == rows * columns);
Hixie's avatar
Hixie committed
663 664 665 666 667 668 669 670 671
    _rows += 1;
    _children.addAll(cells);
    for (RenderBox cell in cells) {
      if (cell != null)
        adoptChild(cell);
    }
    markNeedsLayout();
  }

672 673 674 675 676
  /// Replaces the child at the given position with the given child.
  ///
  /// If the given child is already located at the given position, this function
  /// does not modify the table. Otherwise, the given child must not already
  /// have a parent.
Hixie's avatar
Hixie committed
677 678 679 680
  void setChild(int x, int y, RenderBox value) {
    assert(x != null);
    assert(y != null);
    assert(x >= 0 && x < columns && y >= 0 && y < rows);
Hixie's avatar
Hixie committed
681
    assert(_children.length == rows * columns);
Hixie's avatar
Hixie committed
682
    final int xy = x + y * columns;
683
    final RenderBox oldChild = _children[xy];
Hixie's avatar
Hixie committed
684 685
    if (oldChild == value)
      return;
Hixie's avatar
Hixie committed
686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701
    if (oldChild != null)
      dropChild(oldChild);
    _children[xy] = value;
    if (value != null)
      adoptChild(value);
  }

  @override
  void attach(PipelineOwner owner) {
    super.attach(owner);
    for (RenderBox child in _children)
      child?.attach(owner);
  }

  @override
  void detach() {
702
    super.detach();
703 704 705 706 707
    if (_rowDecorationPainters != null) {
      for (BoxPainter painter in _rowDecorationPainters)
        painter?.dispose();
      _rowDecorationPainters = null;
    }
Hixie's avatar
Hixie committed
708 709 710 711 712 713
    for (RenderBox child in _children)
      child?.detach();
  }

  @override
  void visitChildren(RenderObjectVisitor visitor) {
Hixie's avatar
Hixie committed
714
    assert(_children.length == rows * columns);
Hixie's avatar
Hixie committed
715 716 717 718 719 720 721
    for (RenderBox child in _children) {
      if (child != null)
        visitor(child);
    }
  }

  @override
722
  double computeMinIntrinsicWidth(double height) {
Hixie's avatar
Hixie committed
723
    assert(_children.length == rows * columns);
Hixie's avatar
Hixie committed
724 725
    double totalMinWidth = 0.0;
    for (int x = 0; x < columns; x += 1) {
726 727
      final TableColumnWidth columnWidth = _columnWidths[x] ?? defaultColumnWidth;
      final Iterable<RenderBox> columnCells = column(x);
728
      totalMinWidth += columnWidth.minIntrinsicWidth(columnCells, double.infinity);
Hixie's avatar
Hixie committed
729
    }
730
    return totalMinWidth;
Hixie's avatar
Hixie committed
731 732 733
  }

  @override
734
  double computeMaxIntrinsicWidth(double height) {
Hixie's avatar
Hixie committed
735
    assert(_children.length == rows * columns);
Hixie's avatar
Hixie committed
736 737
    double totalMaxWidth = 0.0;
    for (int x = 0; x < columns; x += 1) {
738 739
      final TableColumnWidth columnWidth = _columnWidths[x] ?? defaultColumnWidth;
      final Iterable<RenderBox> columnCells = column(x);
740
      totalMaxWidth += columnWidth.maxIntrinsicWidth(columnCells, double.infinity);
Hixie's avatar
Hixie committed
741
    }
742
    return totalMaxWidth;
Hixie's avatar
Hixie committed
743 744 745
  }

  @override
746
  double computeMinIntrinsicHeight(double width) {
Hixie's avatar
Hixie committed
747 748
    // winner of the 2016 world's most expensive intrinsic dimension function award
    // honorable mention, most likely to improve if taught about memoization award
Hixie's avatar
Hixie committed
749
    assert(_children.length == rows * columns);
750
    final List<double> widths = _computeColumnWidths(BoxConstraints.tightForFinite(width: width));
Hixie's avatar
Hixie committed
751 752 753 754 755
    double rowTop = 0.0;
    for (int y = 0; y < rows; y += 1) {
      double rowHeight = 0.0;
      for (int x = 0; x < columns; x += 1) {
        final int xy = x + y * columns;
756
        final RenderBox child = _children[xy];
Hixie's avatar
Hixie committed
757
        if (child != null)
758
          rowHeight = math.max(rowHeight, child.getMaxIntrinsicHeight(widths[x]));
Hixie's avatar
Hixie committed
759 760 761
      }
      rowTop += rowHeight;
    }
762
    return rowTop;
Hixie's avatar
Hixie committed
763 764 765
  }

  @override
766 767
  double computeMaxIntrinsicHeight(double width) {
    return computeMinIntrinsicHeight(width);
Hixie's avatar
Hixie committed
768 769 770 771 772 773
  }

  double _baselineDistance;
  @override
  double computeDistanceToActualBaseline(TextBaseline baseline) {
    // returns the baseline of the first cell that has a baseline in the first row
774
    assert(!debugNeedsLayout);
Hixie's avatar
Hixie committed
775 776 777
    return _baselineDistance;
  }

778 779 780 781
  /// Returns the list of [RenderBox] objects that are in the given
  /// column, in row order, starting from the first row.
  ///
  /// This is a lazily-evaluated iterable.
Hixie's avatar
Hixie committed
782 783 784
  Iterable<RenderBox> column(int x) sync* {
    for (int y = 0; y < rows; y += 1) {
      final int xy = x + y * columns;
785
      final RenderBox child = _children[xy];
Hixie's avatar
Hixie committed
786 787 788 789 790
      if (child != null)
        yield child;
    }
  }

791 792 793 794
  /// Returns the list of [RenderBox] objects that are on the given
  /// row, in column order, starting with the first column.
  ///
  /// This is a lazily-evaluated iterable.
Hixie's avatar
Hixie committed
795 796 797 798
  Iterable<RenderBox> row(int y) sync* {
    final int start = y * columns;
    final int end = (y + 1) * columns;
    for (int xy = start; xy < end; xy += 1) {
799
      final RenderBox child = _children[xy];
Hixie's avatar
Hixie committed
800 801 802 803 804
      if (child != null)
        yield child;
    }
  }

805
  List<double> _computeColumnWidths(BoxConstraints constraints) {
806
    assert(constraints != null);
Hixie's avatar
Hixie committed
807
    assert(_children.length == rows * columns);
808 809 810 811 812 813 814 815 816 817 818
    // We apply the constraints to the column widths in the order of
    // least important to most important:
    // 1. apply the ideal widths (maxIntrinsicWidth)
    // 2. grow the flex columns so that the table has the maxWidth (if
    //    finite) or the minWidth (if not)
    // 3. if there were no flex columns, then grow the table to the
    //    minWidth.
    // 4. apply the maximum width of the table, shrinking columns as
    //    necessary, applying minimum column widths as we go

    // 1. apply ideal widths, and collect information we'll need later
819 820 821
    final List<double> widths = List<double>(columns);
    final List<double> minWidths = List<double>(columns);
    final List<double> flexes = List<double>(columns);
822 823
    double tableWidth = 0.0; // running tally of the sum of widths[x] for all x
    double unflexedTableWidth = 0.0; // sum of the maxIntrinsicWidths of any column that has null flex
Hixie's avatar
Hixie committed
824 825
    double totalFlex = 0.0;
    for (int x = 0; x < columns; x += 1) {
826 827
      final TableColumnWidth columnWidth = _columnWidths[x] ?? defaultColumnWidth;
      final Iterable<RenderBox> columnCells = column(x);
828 829 830 831 832 833 834 835 836 837 838 839 840
      // apply ideal width (maxIntrinsicWidth)
      final double maxIntrinsicWidth = columnWidth.maxIntrinsicWidth(columnCells, constraints.maxWidth);
      assert(maxIntrinsicWidth.isFinite);
      assert(maxIntrinsicWidth >= 0.0);
      widths[x] = maxIntrinsicWidth;
      tableWidth += maxIntrinsicWidth;
      // collect min width information while we're at it
      final double minIntrinsicWidth = columnWidth.minIntrinsicWidth(columnCells, constraints.maxWidth);
      assert(minIntrinsicWidth.isFinite);
      assert(minIntrinsicWidth >= 0.0);
      minWidths[x] = minIntrinsicWidth;
      assert(maxIntrinsicWidth >= minIntrinsicWidth);
      // collect flex information while we're at it
841
      final double flex = columnWidth.flex(columnCells);
Hixie's avatar
Hixie committed
842
      if (flex != null) {
843 844
        assert(flex.isFinite);
        assert(flex > 0.0);
Hixie's avatar
Hixie committed
845 846
        flexes[x] = flex;
        totalFlex += flex;
847 848
      } else {
        unflexedTableWidth += maxIntrinsicWidth;
Hixie's avatar
Hixie committed
849 850 851
      }
    }
    assert(!widths.any((double value) => value == null));
852 853 854 855 856 857 858 859 860 861
    final double maxWidthConstraint = constraints.maxWidth;
    final double minWidthConstraint = constraints.minWidth;

    // 2. grow the flex columns so that the table has the maxWidth (if
    //    finite) or the minWidth (if not)
    if (totalFlex > 0.0) {
      // this can only grow the table, but it _will_ grow the table at
      // least as big as the target width.
      double targetWidth;
      if (maxWidthConstraint.isFinite) {
862
        targetWidth = maxWidthConstraint;
863 864 865 866 867 868 869
      } else {
        targetWidth = minWidthConstraint;
      }
      if (tableWidth < targetWidth) {
        final double remainingWidth = targetWidth - unflexedTableWidth;
        assert(remainingWidth.isFinite);
        assert(remainingWidth >= 0.0);
Hixie's avatar
Hixie committed
870 871
        for (int x = 0; x < columns; x += 1) {
          if (flexes[x] != null) {
872 873 874 875 876 877 878 879
            final double flexedWidth = remainingWidth * flexes[x] / totalFlex;
            assert(flexedWidth.isFinite);
            assert(flexedWidth >= 0.0);
            if (widths[x] < flexedWidth) {
              final double delta = flexedWidth - widths[x];
              tableWidth += delta;
              widths[x] = flexedWidth;
            }
Hixie's avatar
Hixie committed
880 881
          }
        }
882
        assert(tableWidth + precisionErrorTolerance >= targetWidth);
883
      }
884
    } // step 2 and 3 are mutually exclusive
885 886 887

    // 3. if there were no flex columns, then grow the table to the
    //    minWidth.
888
    else if (tableWidth < minWidthConstraint) {
889 890 891 892 893 894 895
      final double delta = (minWidthConstraint - tableWidth) / columns;
      for (int x = 0; x < columns; x += 1)
        widths[x] += delta;
      tableWidth = minWidthConstraint;
    }

    // beyond this point, unflexedTableWidth is no longer valid
896
    assert(() { unflexedTableWidth = null; return true; }());
897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918

    // 4. apply the maximum width of the table, shrinking columns as
    //    necessary, applying minimum column widths as we go
    if (tableWidth > maxWidthConstraint) {
      double deficit = tableWidth - maxWidthConstraint;
      // Some columns may have low flex but have all the free space.
      // (Consider a case with a 1px wide column of flex 1000.0 and
      // a 1000px wide column of flex 1.0; the sizes coming from the
      // maxIntrinsicWidths. If the maximum table width is 2px, then
      // just applying the flexes to the deficit would result in a
      // table with one column at -998px and one column at 990px,
      // which is wildly unhelpful.)
      // Similarly, some columns may be flexible, but not actually
      // be shrinkable due to a large minimum width. (Consider a
      // case with two columns, one is flex and one isn't, both have
      // 1000px maxIntrinsicWidths, but the flex one has 1000px
      // minIntrinsicWidth also. The whole deficit will have to come
      // from the non-flex column.)
      // So what we do is we repeatedly iterate through the flexible
      // columns shrinking them proportionally until we have no
      // available columns, then do the same to the non-flexible ones.
      int availableColumns = columns;
919 920
      // Handle double precision errors which causes this loop to become
      // stuck in certain configurations.
921
      const double minimumDeficit = precisionErrorTolerance;
922
      while (deficit > minimumDeficit && totalFlex > minimumDeficit) {
923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938
        double newTotalFlex = 0.0;
        for (int x = 0; x < columns; x += 1) {
          if (flexes[x] != null) {
            final double newWidth = widths[x] - deficit * flexes[x] / totalFlex;
            assert(newWidth.isFinite);
            if (newWidth <= minWidths[x]) {
              // shrank to minimum
              deficit -= widths[x] - minWidths[x];
              widths[x] = minWidths[x];
              flexes[x] = null;
              availableColumns -= 1;
            } else {
              deficit -= widths[x] - newWidth;
              widths[x] = newWidth;
              newTotalFlex += flexes[x];
            }
939
            assert(widths[x] >= 0.0);
940 941 942 943 944 945 946 947 948 949 950 951 952 953
          }
        }
        totalFlex = newTotalFlex;
      }
      if (deficit > 0.0) {
        // Now we have to take out the remaining space from the
        // columns that aren't minimum sized.
        // To make this fair, we repeatedly remove equal amounts from
        // each column, clamped to the minimum width, until we run out
        // of columns that aren't at their minWidth.
        do {
          final double delta = deficit / availableColumns;
          int newAvailableColumns = 0;
          for (int x = 0; x < columns; x += 1) {
954
            final double availableDelta = widths[x] - minWidths[x];
955 956 957 958 959 960
            if (availableDelta > 0.0) {
              if (availableDelta <= delta) {
                // shrank to minimum
                deficit -= widths[x] - minWidths[x];
                widths[x] = minWidths[x];
              } else {
961 962
                deficit -= delta;
                widths[x] -= delta;
963 964 965 966 967 968
                newAvailableColumns += 1;
              }
            }
          }
          availableColumns = newAvailableColumns;
        } while (deficit > 0.0 && availableColumns > 0);
Hixie's avatar
Hixie committed
969 970 971 972 973 974
      }
    }
    return widths;
  }

  // cache the table geometry for painting purposes
975
  final List<double> _rowTops = <double>[];
976
  Iterable<double> _columnLefts;
Hixie's avatar
Hixie committed
977

978 979 980 981 982 983 984 985 986 987
  /// Returns the position and dimensions of the box that the given
  /// row covers, in this render object's coordinate space (so the
  /// left coordinate is always 0.0).
  ///
  /// The row being queried must exist.
  ///
  /// This is only valid after layout.
  Rect getRowBox(int row) {
    assert(row >= 0);
    assert(row < rows);
988
    assert(!debugNeedsLayout);
989
    return Rect.fromLTRB(0.0, _rowTops[row], size.width, _rowTops[row + 1]);
990 991
  }

Hixie's avatar
Hixie committed
992 993
  @override
  void performLayout() {
994 995
    final int rows = this.rows;
    final int columns = this.columns;
Hixie's avatar
Hixie committed
996
    assert(_children.length == rows * columns);
Hixie's avatar
Hixie committed
997
    if (rows * columns == 0) {
Hixie's avatar
Hixie committed
998 999
      // TODO(ianh): if columns is zero, this should be zero width
      // TODO(ianh): if columns is not zero, this should be based on the column width specifications
1000
      size = constraints.constrain(const Size(0.0, 0.0));
Hixie's avatar
Hixie committed
1001 1002
      return;
    }
1003
    final List<double> widths = _computeColumnWidths(constraints);
1004
    final List<double> positions = List<double>(columns);
1005 1006 1007 1008 1009 1010
    double tableWidth;
    switch (textDirection) {
      case TextDirection.rtl:
        positions[columns - 1] = 0.0;
        for (int x = columns - 2; x >= 0; x -= 1)
          positions[x] = positions[x+1] + widths[x+1];
1011
        _columnLefts = positions.reversed;
1012 1013 1014 1015 1016 1017 1018 1019 1020 1021
        tableWidth = positions.first + widths.first;
        break;
      case TextDirection.ltr:
        positions[0] = 0.0;
        for (int x = 1; x < columns; x += 1)
          positions[x] = positions[x-1] + widths[x-1];
        _columnLefts = positions;
        tableWidth = positions.last + widths.last;
        break;
    }
Hixie's avatar
Hixie committed
1022
    assert(!positions.any((double value) => value == null));
1023
    _rowTops.clear();
Hixie's avatar
Hixie committed
1024 1025 1026 1027 1028 1029 1030 1031 1032
    _baselineDistance = null;
    // then, lay out each row
    double rowTop = 0.0;
    for (int y = 0; y < rows; y += 1) {
      _rowTops.add(rowTop);
      double rowHeight = 0.0;
      bool haveBaseline = false;
      double beforeBaselineDistance = 0.0;
      double afterBaselineDistance = 0.0;
1033
      final List<double> baselines = List<double>(columns);
Hixie's avatar
Hixie committed
1034 1035
      for (int x = 0; x < columns; x += 1) {
        final int xy = x + y * columns;
1036
        final RenderBox child = _children[xy];
Hixie's avatar
Hixie committed
1037
        if (child != null) {
1038
          final TableCellParentData childParentData = child.parentData;
Hixie's avatar
Hixie committed
1039
          assert(childParentData != null);
Hixie's avatar
Hixie committed
1040 1041
          childParentData.x = x;
          childParentData.y = y;
Hixie's avatar
Hixie committed
1042 1043 1044
          switch (childParentData.verticalAlignment ?? defaultVerticalAlignment) {
            case TableCellVerticalAlignment.baseline:
              assert(textBaseline != null);
1045
              child.layout(BoxConstraints.tightFor(width: widths[x]), parentUsesSize: true);
1046
              final double childBaseline = child.getDistanceToBaseline(textBaseline, onlyReal: true);
Hixie's avatar
Hixie committed
1047 1048 1049 1050 1051 1052 1053
              if (childBaseline != null) {
                beforeBaselineDistance = math.max(beforeBaselineDistance, childBaseline);
                afterBaselineDistance = math.max(afterBaselineDistance, child.size.height - childBaseline);
                baselines[x] = childBaseline;
                haveBaseline = true;
              } else {
                rowHeight = math.max(rowHeight, child.size.height);
1054
                childParentData.offset = Offset(positions[x], rowTop);
Hixie's avatar
Hixie committed
1055 1056 1057 1058 1059
              }
              break;
            case TableCellVerticalAlignment.top:
            case TableCellVerticalAlignment.middle:
            case TableCellVerticalAlignment.bottom:
1060
              child.layout(BoxConstraints.tightFor(width: widths[x]), parentUsesSize: true);
Hixie's avatar
Hixie committed
1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074
              rowHeight = math.max(rowHeight, child.size.height);
              break;
            case TableCellVerticalAlignment.fill:
              break;
          }
        }
      }
      if (haveBaseline) {
        if (y == 0)
          _baselineDistance = beforeBaselineDistance;
        rowHeight = math.max(rowHeight, beforeBaselineDistance + afterBaselineDistance);
      }
      for (int x = 0; x < columns; x += 1) {
        final int xy = x + y * columns;
1075
        final RenderBox child = _children[xy];
Hixie's avatar
Hixie committed
1076 1077 1078 1079 1080
        if (child != null) {
          final TableCellParentData childParentData = child.parentData;
          switch (childParentData.verticalAlignment ?? defaultVerticalAlignment) {
            case TableCellVerticalAlignment.baseline:
              if (baselines[x] != null)
1081
                childParentData.offset = Offset(positions[x], rowTop + beforeBaselineDistance - baselines[x]);
Hixie's avatar
Hixie committed
1082 1083
              break;
            case TableCellVerticalAlignment.top:
1084
              childParentData.offset = Offset(positions[x], rowTop);
Hixie's avatar
Hixie committed
1085 1086
              break;
            case TableCellVerticalAlignment.middle:
1087
              childParentData.offset = Offset(positions[x], rowTop + (rowHeight - child.size.height) / 2.0);
Hixie's avatar
Hixie committed
1088 1089
              break;
            case TableCellVerticalAlignment.bottom:
1090
              childParentData.offset = Offset(positions[x], rowTop + rowHeight - child.size.height);
Hixie's avatar
Hixie committed
1091 1092
              break;
            case TableCellVerticalAlignment.fill:
1093 1094
              child.layout(BoxConstraints.tightFor(width: widths[x], height: rowHeight));
              childParentData.offset = Offset(positions[x], rowTop);
Hixie's avatar
Hixie committed
1095 1096 1097 1098 1099 1100
              break;
          }
        }
      }
      rowTop += rowHeight;
    }
1101
    _rowTops.add(rowTop);
1102
    size = constraints.constrain(Size(tableWidth, rowTop));
1103
    assert(_rowTops.length == rows + 1);
Hixie's avatar
Hixie committed
1104 1105 1106
  }

  @override
1107
  bool hitTestChildren(BoxHitTestResult result, { Offset position }) {
Hixie's avatar
Hixie committed
1108
    assert(_children.length == rows * columns);
Hixie's avatar
Hixie committed
1109
    for (int index = _children.length - 1; index >= 0; index -= 1) {
1110
      final RenderBox child = _children[index];
Hixie's avatar
Hixie committed
1111 1112
      if (child != null) {
        final BoxParentData childParentData = child.parentData;
1113 1114 1115 1116 1117 1118 1119 1120 1121
        final bool isHit = result.addWithPaintOffset(
          offset: childParentData.offset,
          position: position,
          hitTest: (BoxHitTestResult result, Offset transformed) {
            assert(transformed == position - childParentData.offset);
            return child.hitTest(result, position: transformed);
          },
        );
        if (isHit)
Hixie's avatar
Hixie committed
1122 1123 1124 1125 1126 1127 1128 1129
          return true;
      }
    }
    return false;
  }

  @override
  void paint(PaintingContext context, Offset offset) {
Hixie's avatar
Hixie committed
1130
    assert(_children.length == rows * columns);
1131
    if (rows * columns == 0) {
1132
      if (border != null) {
1133
        final Rect borderRect = Rect.fromLTWH(offset.dx, offset.dy, size.width, 0.0);
1134 1135
        border.paint(context.canvas, borderRect, rows: const <double>[], columns: const <double>[]);
      }
Hixie's avatar
Hixie committed
1136
      return;
1137
    }
1138 1139
    assert(_rowTops.length == rows + 1);
    if (_rowDecorations != null) {
1140
      final Canvas canvas = context.canvas;
1141 1142 1143
      for (int y = 0; y < rows; y += 1) {
        if (_rowDecorations.length <= y)
          break;
1144
        if (_rowDecorations[y] != null) {
1145 1146 1147
          _rowDecorationPainters[y] ??= _rowDecorations[y].createBoxPainter(markNeedsPaint);
          _rowDecorationPainters[y].paint(
            canvas,
1148
            Offset(offset.dx, offset.dy + _rowTops[y]),
1149
            configuration.copyWith(size: Size(size.width, _rowTops[y+1] - _rowTops[y])),
1150
          );
1151
        }
1152 1153
      }
    }
Hixie's avatar
Hixie committed
1154
    for (int index = 0; index < _children.length; index += 1) {
1155
      final RenderBox child = _children[index];
Hixie's avatar
Hixie committed
1156
      if (child != null) {
1157
        final BoxParentData childParentData = child.parentData;
Hixie's avatar
Hixie committed
1158 1159 1160
        context.paintChild(child, childParentData.offset + offset);
      }
    }
1161 1162
    assert(_rows == _rowTops.length - 1);
    assert(_columns == _columnLefts.length);
1163 1164 1165 1166
    if (border != null) {
      // The border rect might not fill the entire height of this render object
      // if the rows underflow. We always force the columns to fill the width of
      // the render object, which means the columns cannot underflow.
1167
      final Rect borderRect = Rect.fromLTWH(offset.dx, offset.dy, size.width, _rowTops.last);
1168
      final Iterable<double> rows = _rowTops.getRange(1, _rowTops.length - 1);
1169 1170
      final Iterable<double> columns = _columnLefts.skip(1);
      border.paint(context.canvas, borderRect, rows: rows, columns: columns);
1171
    }
Hixie's avatar
Hixie committed
1172 1173 1174
  }

  @override
1175 1176
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
1177 1178 1179 1180
    properties.add(DiagnosticsProperty<TableBorder>('border', border, defaultValue: null));
    properties.add(DiagnosticsProperty<Map<int, TableColumnWidth>>('specified column widths', _columnWidths, level: _columnWidths.isEmpty ? DiagnosticLevel.hidden : DiagnosticLevel.info));
    properties.add(DiagnosticsProperty<TableColumnWidth>('default column width', defaultColumnWidth));
    properties.add(MessageProperty('table size', '$columns\u00D7$rows'));
1181 1182
    properties.add(IterableProperty<String>('column offsets', _columnLefts?.map(debugFormatDouble), ifNull: 'unknown'));
    properties.add(IterableProperty<String>('row offsets', _rowTops?.map(debugFormatDouble), ifNull: 'unknown'));
Hixie's avatar
Hixie committed
1183 1184 1185
  }

  @override
1186 1187
  List<DiagnosticsNode> debugDescribeChildren() {
    if (_children.isEmpty) {
1188
      return <DiagnosticsNode>[DiagnosticsNode.message('table is empty')];
1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199
    }

    final List<DiagnosticsNode> children = <DiagnosticsNode>[];
    for (int y = 0; y < rows; y += 1) {
      for (int x = 0; x < columns; x += 1) {
        final int xy = x + y * columns;
        final RenderBox child = _children[xy];
        final String name = 'child ($x, $y)';
        if (child != null)
          children.add(child.toDiagnosticsNode(name: name));
        else
1200
          children.add(DiagnosticsProperty<Object>(name, null, ifNull: 'is null', showSeparator: false));
Hixie's avatar
Hixie committed
1201 1202
      }
    }
1203
    return children;
Hixie's avatar
Hixie committed
1204 1205
  }
}