blob: 362abde8ad2cf384119a7f5ce90ee297403a3c6c [file]
// Copyright 2022 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:math';
import 'package:devtools_app_shared/ui.dart';
import '../primitives/trees.dart';
import '../primitives/utils.dart';
import 'table_controller.dart';
import 'table_data.dart';
// TODO(https://github.com/flutter/devtools/issues/2047): build flexible column
// widths into the tables so that we do not have to do in depth calculations
// like this.
const columnGroupSpacing = 4.0;
const columnGroupSpacingWithPadding = columnGroupSpacing + 2 * defaultSpacing;
const columnSpacing = defaultSpacing;
/// The minimum width that can be used for variable width columns.
final defaultVariableWidthColumnSize = scaleByFontFactor(1200.0);
extension FlatColumnWidthExtension<T> on FlatTableController<T> {
/// Calculates [FlatTable] column widths when the columns should be sized as
/// either 1) the [fixedWidthPx] specified by the column, or
/// 2) [defaultVariableWidthColumnSize], the minimum width that can be used
/// for variable width columns.
///
/// If the sum of the column widths exceeds the available screen space, the
/// table will scroll horizontally.
List<double> computeColumnWidthsSizeToContent() {
return columns
.map(
(c) =>
c.fixedWidthPx ??
(c.minWidthPx ?? defaultVariableWidthColumnSize),
)
.toList();
}
/// Calculates [FlatTable] column widths such that they will take up the
/// entire width available [maxWidth].
///
/// When this calculation method is used, the table will not scroll
/// horizontally since there will not be any content outside of the viewport.
List<double> computeColumnWidthsSizeToFit(double maxWidth) {
// Subtract width from outer padding around table.
maxWidth -= 2 * defaultSpacing;
final numColumnGroupSpacers = columnGroups?.numSpacers ?? 0;
final numColumnSpacers = columns.numSpacers - numColumnGroupSpacers;
maxWidth -= numColumnSpacers * columnSpacing;
maxWidth -= numColumnGroupSpacers * columnGroupSpacingWithPadding;
maxWidth = max(0, maxWidth);
double available = maxWidth;
// Columns sorted by increasing minWidth.
final sortedColumns =
columns.toList()..sort((a, b) {
if (a.minWidthPx != null && b.minWidthPx != null) {
return a.minWidthPx!.compareTo(b.minWidthPx!);
}
if (a.minWidthPx != null) return -1;
if (b.minWidthPx != null) return 1;
return 0;
});
for (final col in columns) {
if (col.fixedWidthPx != null) {
available -= col.fixedWidthPx!;
} else if (col.minWidthPx != null) {
available -= col.minWidthPx!;
}
}
available = max(available, 0);
int unconstrainedCount = 0;
for (final column in sortedColumns) {
if (column.fixedWidthPx == null && column.minWidthPx == null) {
unconstrainedCount++;
}
}
if (available > 0) {
// We need to find how many columns with minWidth constraints can actually
// be treated as unconstrained as their minWidth constraint is satisfied
// by the width given to all unconstrained columns.
// We use a greedy algorithm to accurately compute this by iterating
// through the columns from the smallest minWidth to largest minWidth
// incrementally adding columns where the minWidth constraint can be
// satisfied using the width given to unconstrained columns.
for (final column in sortedColumns) {
if (column.fixedWidthPx == null && column.minWidthPx != null) {
// Width of this column if it was not clamped to its min width.
// We add column.minWidthPx to the available width because
// available is currently not considering the space reserved for this
// column's min width as available.
final widthIfUnconstrainedByMinWidth =
(available + column.minWidthPx!) / (unconstrainedCount + 1);
if (widthIfUnconstrainedByMinWidth < column.minWidthPx!) {
// We have found the first column that will have to be clamped to
// its min width.
break;
}
// As this column's width in the final layout is greater than its
// min width, we can treat it as unconstrained and give its min width
// back to the available pool.
unconstrainedCount++;
available += column.minWidthPx!;
}
}
}
final unconstrainedWidth =
unconstrainedCount > 0 ? available / unconstrainedCount : available;
int unconstrainedFound = 0;
final widths = <double>[];
for (final column in columns) {
double? width = column.fixedWidthPx;
if (width == null) {
if (column.minWidthPx != null &&
column.minWidthPx! > unconstrainedWidth) {
width = column.minWidthPx!;
} else {
width = unconstrainedWidth;
unconstrainedFound++;
}
}
widths.add(width);
}
assert(unconstrainedCount == unconstrainedFound);
return widths;
}
}
extension TreeColumnWidthExtension<T extends TreeNode<T>>
on TreeTableController<T> {
/// Calculates [TreeTable] column widths.
///
/// Non-tree columns will be sized with their specified [fixedWidthPx]. The
/// tree column width will include space for the max indentation of the fully
/// expanded tree and [defaultVariableWidthColumnSize] for displaying the
/// tree column content.
///
/// If the sum of the column widths exceeds the available screen space, the
/// table will scroll horizontally.
List<double> computeColumnWidths(int maxTableDepth) {
return columns
.map(
(c) =>
c.fixedWidthPx ??
maxTableDepth * TreeColumnData.treeToggleWidth +
defaultVariableWidthColumnSize,
)
.toList();
}
}