measureColumns<T, K> static method

TTableColumnMeasurements measureColumns<T, K>(
  1. List<TTableHeader<T, K>> headers,
  2. bool selectable,
  3. bool expandable, {
  4. int maxTreeLevel = 0,
  5. bool isHierarchical = false,
  6. List<T> sampleItems = const [],
  7. TextStyle? headerTextStyle,
  8. TextStyle? contentTextStyle,
})

Measures every column once (the expensive, text-layout part) and splits the result into fixed columns (selectable/expandable slots, and any header with an explicit TTableHeader.minWidth/maxWidth — those are honored exactly as given, no auto-sizing applied) versus auto columns that still need their natural width turned into a final pixel width once the available table width is known.

Callers should cache the returned TTableColumnMeasurements and only call TTableColumnMeasurements.resolve on layout/resize — that part is pure arithmetic and re-runs no text measurement.

Implementation

static TTableColumnMeasurements measureColumns<T, K>(
  List<TTableHeader<T, K>> headers,
  bool selectable,
  bool expandable, {
  int maxTreeLevel = 0,
  bool isHierarchical = false,
  List<T> sampleItems = const [],
  TextStyle? headerTextStyle,
  TextStyle? contentTextStyle,
}) {
  final fixedWidths = <int, TableColumnWidth>{};
  int columnIndex = 0;
  double fixedTotal = 0;

  if (selectable) {
    fixedWidths[columnIndex] = const FixedColumnWidth(50);
    fixedTotal += 50;
    columnIndex++;
  }
  if (expandable) {
    fixedWidths[columnIndex] = const FixedColumnWidth(50);
    fixedTotal += 50;
    columnIndex++;
  }

  final effectiveTreeLevel = isHierarchical && maxTreeLevel == 0 ? 1 : maxTreeLevel;
  final treeIndentBonus = (isHierarchical || maxTreeLevel > 0) ? (effectiveTreeLevel * 16.0 + 36.0) : 0.0;

  final autoIndices = <int>[];
  final autoBasisWidths = <double>[]; // weighting basis, already clamped into [min, max]
  final autoMinWidths = <double?>[];
  final autoMaxWidths = <double?>[];

  for (int i = 0; i < headers.length; i++) {
    final header = headers[i];
    final extraWidth = (i == 0) ? treeIndentBonus : 0.0;
    final index = columnIndex + i;

    final hasMin = header.minWidth != null && header.minWidth! > 0;
    final hasMax = header.maxWidth != null && header.maxWidth! != double.infinity;

    // Only skip measurement entirely when min and max pin the SAME value —
    // that's a genuinely fixed column with no range to size within, so
    // there's no point paying for a TextPainter layout.
    if (hasMin && hasMax && header.minWidth == header.maxWidth) {
      final width = header.minWidth! + extraWidth;
      fixedWidths[index] = FixedColumnWidth(width);
      fixedTotal += width;
      continue;
    }

    final natural = _naturalColumnWidth<T, K>(
          header,
          sampleItems,
          headerTextStyle ?? fallbackTextStyle,
          contentTextStyle ?? fallbackTextStyle,
        ) +
        extraWidth;

    final min = hasMin ? header.minWidth! + extraWidth : null;
    final max = hasMax ? header.maxWidth! + extraWidth : null;

    // Weighting basis = natural width clamped into this column's own
    // [min, max] — so a maxWidth cap doesn't inflate what other columns
    // compete for, and a minWidth-only column still measures/grows like
    // any other auto column instead of freezing at exactly minWidth.
    var basis = natural;
    if (min != null && basis < min) basis = min;
    if (max != null && basis > max) basis = max;

    autoIndices.add(index);
    autoBasisWidths.add(basis);
    autoMinWidths.add(min);
    autoMaxWidths.add(max);
  }

  return TTableColumnMeasurements(
    fixedWidths: fixedWidths,
    fixedTotal: fixedTotal,
    autoIndices: autoIndices,
    autoNaturalWidths: autoBasisWidths,
    autoMinWidths: autoMinWidths,
    autoMaxWidths: autoMaxWidths,
  );
}