buildHeaderColumns method

HeaderRow buildHeaderColumns(
  1. List<HeaderRow> headerRows,
  2. int rowIndex, {
  3. double? parentMaxWidth,
  4. Map<int, double> widthOverrides = const <int, double>{},
})

Builds header columns for the given headerRows and rowIndex. parentMaxWidth is the available width for the header row.

Implementation

hr.HeaderRow buildHeaderColumns(
  List<HeaderRow> headerRows,
  int rowIndex, {
  double? parentMaxWidth,
  Map<int, double> widthOverrides = const <int, double>{},
}) {
  final columnsDef = headerRows[rowIndex].columns;

  // Calculate total fixed width and collect flex values
  double totalFixedWidth = 0.0;
  double totalFlex = 0;
  List<double> flexValues = [];

  for (final entry in columnsDef.asMap().entries) {
    final col = entry.value;
    final widthOverride = widthOverrides[entry.key];
    if (widthOverride != null) {
      totalFixedWidth += widthOverride;
      flexValues.add(0);
    } else if (col.width != null) {
      totalFixedWidth += col.width!;
      flexValues.add(0);
    } else if (col.minimumWidth != null && col.flex == null) {
      // treat minimum-only columns as fixed for layout purposes
      totalFixedWidth += col.minimumWidth!;
      flexValues.add(0);
    } else if (col.flex != null) {
      totalFlex += col.flex!;
      flexValues.add(col.flex!);
    }
  }

  // Subtract column separator width(s) from available width
  final int separatorCount = (columnsDef.isNotEmpty) ? columnsDef.length : 0;
  final double totalSeparatorWidth = separatorCount * kColumnSeparatorWidth;

  // Calculate available width for flexible columns (never negative)
  double availableWidth = max(0.0, (parentMaxWidth ?? 0) - totalFixedWidth - totalSeparatorWidth);

  // Prepare assigned widths array
  final int colCount = columnsDef.length;
  List<double> assignedWidths = List<double>.filled(colCount, 0.0);

  // Assign fixed / minimum-only columns immediately
  for (int i = 0; i < colCount; i++) {
    final col = columnsDef[i];
    if (col.width != null) {
      assignedWidths[i] = col.width!;
    } else if (col.minimumWidth != null && col.flex == null) {
      assignedWidths[i] = col.minimumWidth!;
    }
  }

  // Distribute availableWidth among flexible columns with iterative clamping
  final remainingIndices = <int>{};
  for (int i = 0; i < colCount; i++) {
    if (flexValues[i] > 0) remainingIndices.add(i);
  }

  double remainingWidth = availableWidth;
  double remainingFlex = totalFlex;

  // If there are no flexible columns, nothing to distribute
  if (remainingIndices.isNotEmpty && remainingFlex > 0) {
    // Iteratively clamp columns that hit min/max and redistribute
    while (remainingIndices.isNotEmpty) {
      bool anyClampedThisRound = false;

      for (final idx in remainingIndices.toList()) {
        final col = columnsDef[idx];
        final double flex = flexValues[idx];
        final double minW = col.minimumWidth ?? 50.0;
        final double maxW = col.maximumWidth ?? double.infinity;

        // Proposed width based on remaining flex share
        final double proposed = remainingFlex > 0 ? remainingWidth * (flex / remainingFlex) : 0.0;
        final double clamped = proposed.clamp(minW, maxW);

        // If clamped to a bound, finalize this column and remove from remaining set
        if (clamped == minW || clamped == maxW) {
          assignedWidths[idx] = clamped;
          remainingWidth -= clamped;
          remainingFlex -= flex;
          remainingIndices.remove(idx);
          anyClampedThisRound = true;
        }
      }

      if (!anyClampedThisRound) {
        // No further clamping -> distribute remaining proportionally
        for (final idx in remainingIndices) {
          final col = columnsDef[idx];
          final double flex = flexValues[idx];
          final double minW = col.minimumWidth ?? 50.0;
          final double maxW = col.maximumWidth ?? double.infinity;
          final double w = remainingFlex > 0 ? (remainingWidth * (flex / remainingFlex)) : 0.0;
          assignedWidths[idx] = w.clamp(minW, maxW);
        }
        break;
      }
    }
  }

  List<hc.HeaderColumn> columns = [];

  for (int columnIndex = 0; columnIndex < columnsDef.length; columnIndex++) {
    final headerCol = columnsDef[columnIndex];
    bool pinLeft = headerCol.pinLeft;
    bool pinRight = headerCol.pinRight;

    double width;
    if (widthOverrides[columnIndex] case final widthOverride?) {
      width = widthOverride;
    } else if (headerCol.width != null) {
      width = headerCol.width!;
    } else if (flexValues[columnIndex] > 0) {
      // use the computed assigned width for flexible columns
      width = assignedWidths[columnIndex] > 0 ? assignedWidths[columnIndex] : (headerCol.minimumWidth ?? 50);
    } else {
      double maxWidth = headerCol.maximumWidth ?? double.infinity;
      width = (headerCol.minimumWidth ?? 50).clamp(0, maxWidth);
    }

    columns.add(
      hc.HeaderColumn(
        widget: _buildHeaderColumn(headerRows[rowIndex], columnIndex),
        autoSizeWidget: headerCol.widget,
        autoSize: headerCol.autoSize,
        autoSizeWidth: headerCol.autoSizeWidth,
        autoSizeWidthBuilder: headerCol.autoSizeWidthBuilder,
        textVisibilityDialog: headerCol.textVisibilityDialog,
        width: ValueNotifier(width),
        minimumWidth: headerCol.minimumWidth ?? 50,
        maximumWidth: headerCol.maximumWidth,
        height: headerCol.height,
        visible: ValueNotifier(headerCol.visible),
        resizable: headerCol.resizable,
        combinedColumns: headerCol.combinedColumns,
        pinLeft: ValueNotifier(pinLeft),
        pinRight: ValueNotifier(pinRight),
        canHide: headerCol.canHide,
        sortable: headerCol.sortable,
        filterable: headerCol.filterable,
        filterType: headerCol.filterType,
        reorderable: headerCol.reorderable,
        sortComparator: headerCol.sortComparator,
        sourceColumnIndex: columnIndex,
      ),
    );
  }

  return hr.HeaderRow(columns: columns);
}