blob: faac24b22d7dcb9b7889bbcd390899144b6ef0ba [file] [log] [blame]
// Copyright 2013 The Flutter 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' as math;
import 'package:flutter/gestures.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'table.dart';
/// Defines the leading and trailing padding values of a [TableSpan].
class TableSpanPadding {
/// Creates a padding configuration for a [TableSpan].
const TableSpanPadding({
this.leading = 0.0,
this.trailing = 0.0,
});
/// Creates padding where both the [leading] and [trailing] are `value`.
const TableSpanPadding.all(double value)
: leading = value,
trailing = value;
/// The leading amount of pixels to pad a [TableSpan] by.
///
/// If the [TableSpan] is a row and the vertical [Axis] is not reversed, this
/// offset will be applied above the row. If the vertical [Axis] is reversed,
/// this will be applied below the row.
///
/// If the [TableSpan] is a column and the horizontal [Axis] is not reversed,
/// this offset will be applied to the left the column. If the horizontal
/// [Axis] is reversed, this will be applied to the right of the column.
final double leading;
/// The trailing amount of pixels to pad a [TableSpan] by.
///
/// If the [TableSpan] is a row and the vertical [Axis] is not reversed, this
/// offset will be applied below the row. If the vertical [Axis] is reversed,
/// this will be applied above the row.
///
/// If the [TableSpan] is a column and the horizontal [Axis] is not reversed,
/// this offset will be applied to the right the column. If the horizontal
/// [Axis] is reversed, this will be applied to the left of the column.
final double trailing;
}
/// Defines the extent, visual appearance, and gesture handling of a row or
/// column in a [TableView].
///
/// A span refers to either a column or a row in a table.
class TableSpan {
/// Creates a [TableSpan].
///
/// The [extent] argument must be provided.
const TableSpan({
required this.extent,
TableSpanPadding? padding,
this.recognizerFactories = const <Type, GestureRecognizerFactory>{},
this.onEnter,
this.onExit,
this.cursor = MouseCursor.defer,
this.backgroundDecoration,
this.foregroundDecoration,
}) : padding = padding ?? const TableSpanPadding();
/// Defines the extent of the span.
///
/// If the span represents a row, this is the height of the row. If it
/// represents a column, this is the width of the column.
final TableSpanExtent extent;
/// Defines the leading and or trailing extent to pad the row or column by.
///
/// Defaults to no padding.
final TableSpanPadding padding;
/// Factory for creating [GestureRecognizer]s that want to compete for
/// gestures within the [extent] of the span.
///
/// If this span represents a row, a factory for a [TapGestureRecognizer]
/// could for example be provided here to recognize taps within the bounds
/// of the row.
///
/// The content of a cell takes precedence in handling pointer events. Next,
/// the recognizers defined for the [TableView.mainAxis], followed by the
/// other [Axis].
final Map<Type, GestureRecognizerFactory> recognizerFactories;
/// Triggers when a mouse pointer, with or without buttons pressed, has
/// entered the region encompassing the row or column described by this span.
///
/// This callback is triggered when the pointer has started to be contained by
/// the region, either due to a pointer event, or due to the movement or
/// appearance of the region. This method is always matched by a later
/// [onExit] call.
final PointerEnterEventListener? onEnter;
/// Triggered when a mouse pointer, with or without buttons pressed, has
/// exited the region encompassing the row or column described by this span.
///
/// This callback is triggered when the pointer has stopped being contained
/// by the region, either due to a pointer event, or due to the movement or
/// disappearance of the region. This method always matches an earlier
/// [onEnter] call.
final PointerExitEventListener? onExit;
/// Mouse cursor to show when the mouse hovers over this span.
///
/// Defaults to [MouseCursor.defer].
final MouseCursor cursor;
/// The [TableSpanDecoration] to paint behind the content of this span.
///
/// The [backgroundDecoration]s of the [TableView.mainAxis] are painted after
/// the [backgroundDecoration]s of the other [Axis]. On top of that,
/// the content of the individual cells in this span are painted, followed by
/// any specified [foregroundDecoration].
///
/// The decorations of pinned rows and columns are painted separately from
/// the decorations of unpinned rows and columns, with the unpinned rows and
/// columns being painted first to account for overlap from pinned rows or
/// columns.
final TableSpanDecoration? backgroundDecoration;
/// The [TableSpanDecoration] to paint in front of the content of this span.
///
/// After painting any [backgroundDecoration]s, and the content of the
/// individual cells, the [foregroundDecoration] of the [TableView.mainAxis]
/// are painted after the [foregroundDecoration]s of the other [Axis]
///
/// The decorations of pinned rows and columns are painted separately from
/// the decorations of unpinned rows and columns, with the unpinned rows and
/// columns being painted first to account for overlap from pinned rows or
/// columns.
final TableSpanDecoration? foregroundDecoration;
}
/// Delegate passed to [TableSpanExtent.calculateExtent] from the
/// [RenderTableViewport] during layout.
///
/// Provides access to metrics from the [TableView] that a [TableSpanExtent] may
/// need to calculate its extent.
///
/// Extents will not be computed for every frame unless the delegate has been
/// updated. Otherwise, after the extents are computed during the first layout
/// passed, they are cached and reused in subsequent frames.
class TableSpanExtentDelegate {
/// Creates a [TableSpanExtentDelegate].
///
/// Usually, only [TableView]s need to create instances of this class.
const TableSpanExtentDelegate({
required this.viewportExtent,
required this.precedingExtent,
});
/// The size of the viewport in the axis-direction of the span.
///
/// If the [TableSpanExtent] calculates the extent of a row, this is the
/// height of the viewport. If it calculates the extent of a column, this
/// is the width of the viewport.
final double viewportExtent;
/// The scroll extent that has already been used up by previous spans.
///
/// If the [TableSpanExtent] calculates the extent of a row, this is the
/// sum of all row extents prior to this row. If it calculates the extent
/// of a column, this is the sum of all previous columns.
final double precedingExtent;
}
/// Defines the extent of a [TableSpan].
///
/// If the span is a row, its extent is the height of the row. If the span is
/// a column, it's the width of that column.
abstract class TableSpanExtent {
/// Creates a [TableSpanExtent].
const TableSpanExtent();
/// Calculates the actual extent of the span in pixels.
///
/// To assist with the calculation, table metrics obtained from the provided
/// [TableSpanExtentDelegate] may be used.
double calculateExtent(TableSpanExtentDelegate delegate);
}
/// A span extent with a fixed [pixels] value.
class FixedTableSpanExtent extends TableSpanExtent {
/// Creates a [FixedTableSpanExtent].
///
/// The provided [pixels] value must be equal to or greater then zero.
const FixedTableSpanExtent(this.pixels) : assert(pixels >= 0.0);
/// The extent of the span in pixels.
final double pixels;
@override
double calculateExtent(TableSpanExtentDelegate delegate) => pixels;
}
/// Specified the span extent as a fraction of the viewport extent.
///
/// For example, a column with a 1.0 as [fraction] will be as wide as the
/// viewport.
class FractionalTableSpanExtent extends TableSpanExtent {
/// Creates a [FractionalTableSpanExtent].
///
/// The provided [fraction] value must be equal to or greater than zero.
const FractionalTableSpanExtent(
this.fraction,
) : assert(fraction >= 0.0);
/// The fraction of the [TableSpanExtentDelegate.viewportExtent] that the
/// span should occupy.
///
/// The provided [fraction] value must be equal to or greater than zero.
final double fraction;
@override
double calculateExtent(TableSpanExtentDelegate delegate) =>
delegate.viewportExtent * fraction;
}
/// Specifies that the span should occupy the remaining space in the viewport.
///
/// If the previous [TableSpan]s can already fill out the viewport, this will
/// evaluate the span's extent to zero. If the previous spans cannot fill out the
/// viewport, this span's extent will be whatever space is left to fill out the
/// viewport.
///
/// To avoid that the span's extent evaluates to zero, consider combining this
/// extent with another extent. The following example will make sure that the
/// span's extent is at least 200 pixels, but if there's more than that available
/// in the viewport, it will fill all that space:
///
/// ```dart
/// const MaxTableSpanExtent(FixedTableSpanExtent(200.0), RemainingTableSpanExtent());
/// ```
class RemainingTableSpanExtent extends TableSpanExtent {
/// Creates a [RemainingTableSpanExtent].
const RemainingTableSpanExtent();
@override
double calculateExtent(TableSpanExtentDelegate delegate) {
return math.max(0.0, delegate.viewportExtent - delegate.precedingExtent);
}
}
/// Signature for a function that combines the result of two
/// [TableSpanExtent.calculateExtent] invocations.
///
/// Used by [CombiningTableSpanExtent];
typedef TableSpanExtentCombiner = double Function(double, double);
/// Runs the result of two [TableSpanExtent]s through a `combiner` function
/// to determine the ultimate pixel extent of a span.
class CombiningTableSpanExtent extends TableSpanExtent {
/// Creates a [CombiningTableSpanExtent];
const CombiningTableSpanExtent(this._extent1, this._extent2, this._combiner);
final TableSpanExtent _extent1;
final TableSpanExtent _extent2;
final TableSpanExtentCombiner _combiner;
@override
double calculateExtent(TableSpanExtentDelegate delegate) {
return _combiner(
_extent1.calculateExtent(delegate),
_extent2.calculateExtent(delegate),
);
}
}
/// Returns the larger pixel extent of the two provided [TableSpanExtent].
class MaxTableSpanExtent extends CombiningTableSpanExtent {
/// Creates a [MaxTableSpanExtent].
const MaxTableSpanExtent(
TableSpanExtent extent1,
TableSpanExtent extent2,
) : super(extent1, extent2, math.max);
}
/// Returns the smaller pixel extent of the two provided [TableSpanExtent].
class MinTableSpanExtent extends CombiningTableSpanExtent {
/// Creates a [MinTableSpanExtent].
const MinTableSpanExtent(
TableSpanExtent extent1,
TableSpanExtent extent2,
) : super(extent1, extent2, math.min);
}
/// A decoration for a [TableSpan].
class TableSpanDecoration {
/// Creates a [TableSpanDecoration].
const TableSpanDecoration({
this.border,
this.color,
this.borderRadius,
this.consumeSpanPadding = true,
});
/// The border drawn around the span.
final TableSpanBorder? border;
/// The radius by which the leading and trailing ends of a row or
/// column will be rounded.
///
/// Applies to the [border] and [color] of the given [TableSpan].
final BorderRadius? borderRadius;
/// The color to fill the bounds of the span with.
final Color? color;
/// Whether or not the decoration should extend to fill the space created by
/// the [TableSpanPadding].
///
/// Defaults to true, meaning if a [TableSpan] is a row, the decoration will
/// apply to the full [TableSpanExtent], including the
/// [TableSpanPadding.leading] and [TableSpanPadding.trailing] for the row.
/// This same row decoration will consume any padding from the column spans so
/// as to decorate the row as one continuous span.
///
/// {@tool snippet}
/// This example illustrates how [consumeSpanPadding] affects
/// [TableSpanDecoration.color]. By default, the color of the decoration
/// consumes the padding, coloring the row fully by including the padding
/// around the row. When [consumeSpanPadding] is false, the padded area of
/// the row is not decorated.
///
/// ```dart
/// TableView.builder(
/// rowCount: 4,
/// columnCount: 4,
/// columnBuilder: (int index) => TableSpan(
/// extent: const FixedTableSpanExtent(150.0),
/// padding: const TableSpanPadding(trailing: 10),
/// ),
/// rowBuilder: (int index) => TableSpan(
/// extent: const FixedTableSpanExtent(150.0),
/// padding: TableSpanPadding(leading: 10, trailing: 10),
/// backgroundDecoration: TableSpanDecoration(
/// color: index.isOdd ? Colors.blue : Colors.green,
/// // The background color will not be applied to the padded area.
/// consumeSpanPadding: false,
/// ),
/// ),
/// cellBuilder: (_, TableVicinity vicinity) {
/// return Container(
/// height: 150,
/// width: 150,
/// child: const Center(child: FlutterLogo()),
/// );
/// },
/// );
/// ```
/// {@end-tool}
final bool consumeSpanPadding;
/// Called to draw the decoration around a span.
///
/// The provided [TableSpanDecorationPaintDetails] describes the bounds and
/// orientation of the span that are currently visible inside the viewport of
/// the table. The extent of the actual span may be larger.
///
/// If a span contains pinned parts, [paint] is invoked separately for the
/// pinned and unpinned parts. For example: If a row contains a pinned column,
/// paint is called with the [TableSpanDecorationPaintDetails.rect] for the
/// cell representing the pinned column and separately with another
/// [TableSpanDecorationPaintDetails.rect] containing all the other unpinned
/// cells.
void paint(TableSpanDecorationPaintDetails details) {
if (color != null) {
final Paint paint = Paint()
..color = color!
..isAntiAlias = borderRadius != null;
if (borderRadius == null || borderRadius == BorderRadius.zero) {
details.canvas.drawRect(details.rect, paint);
} else {
details.canvas.drawRRect(
borderRadius!.toRRect(details.rect),
paint,
);
}
}
if (border != null) {
border!.paint(details, borderRadius);
}
}
}
/// Describes the border for a [TableSpan].
class TableSpanBorder {
/// Creates a [TableSpanBorder].
const TableSpanBorder({
this.trailing = BorderSide.none,
this.leading = BorderSide.none,
});
/// The border to draw on the trailing side of the span, based on the
/// [AxisDirection].
///
/// The trailing side of a row is the bottom when [Axis.vertical] is
/// [AxisDirection.down], the trailing side of a column
/// is its right side when the [Axis.horizontal] is [AxisDirection.right].
final BorderSide trailing;
/// The border to draw on the leading side of the span.
///
/// The leading side of a row is the top when [Axis.vertical] is
/// [AxisDirection.down], the leading side of a column
/// is its left side when the [Axis.horizontal] is [AxisDirection.right].
final BorderSide leading;
/// Called to draw the border around a span.
///
/// If the span represents a row, `axisDirection` will be [AxisDirection.left]
/// or [AxisDirection.right]. For columns, the `axisDirection` will be
/// [AxisDirection.down] or [AxisDirection.up].
///
/// The provided [TableSpanDecorationPaintDetails] describes the bounds and
/// orientation of the span that are currently visible inside the viewport of
/// the table. The extent of the actual span may be larger.
///
/// If a span contains pinned parts, [paint] is invoked separately for the
/// pinned and unpinned parts. For example: If a row contains a pinned column,
/// paint is called with the [TableSpanDecorationPaintDetails.rect] for the
/// cell representing the pinned column and separately with another
/// [TableSpanDecorationPaintDetails.rect] containing all the other unpinned
/// cells.
void paint(
TableSpanDecorationPaintDetails details,
BorderRadius? borderRadius,
) {
final AxisDirection axisDirection = details.axisDirection;
switch (axisDirectionToAxis(axisDirection)) {
case Axis.horizontal:
final Border border = Border(
top: axisDirection == AxisDirection.right ? leading : trailing,
bottom: axisDirection == AxisDirection.right ? trailing : leading,
);
border.paint(
details.canvas,
details.rect,
borderRadius: borderRadius,
);
case Axis.vertical:
final Border border = Border(
left: axisDirection == AxisDirection.down ? leading : trailing,
right: axisDirection == AxisDirection.down ? trailing : leading,
);
border.paint(
details.canvas,
details.rect,
borderRadius: borderRadius,
);
}
}
}
/// Provides the details of a given [TableSpanDecoration] for painting.
///
/// Created during paint by the [RenderTableViewport] for the
/// [TableSpan.foregroundDecoration] and [TableSpan.backgroundDecoration].
class TableSpanDecorationPaintDetails {
/// Creates the details needed to paint a [TableSpanDecoration].
///
/// The [canvas], [rect], and [axisDirection] must be provided.
TableSpanDecorationPaintDetails({
required this.canvas,
required this.rect,
required this.axisDirection,
});
/// The [Canvas] that the [TableSpanDecoration] will be painted to.
final Canvas canvas;
/// A [Rect] representing the visible area of a row or column in the
/// [TableView], as represented by a [TableSpan].
///
/// This Rect contains all of the visible children in a given row or column,
/// which is the area the [TableSpanDecoration] will be applied to.
final Rect rect;
/// The [AxisDirection] of the [Axis] of the [TableSpan].
///
/// When [AxisDirection.down] or [AxisDirection.up], which would be
/// [Axis.vertical], a column is being painted. When [AxisDirection.left] or
/// [AxisDirection.right], which would be [Axis.horizontal], a row is being
/// painted.
final AxisDirection axisDirection;
}