matrixTranslate method
Return a new matrix representing the given matrix after applying the given translation.
Implementation
@protected
Matrix4 matrixTranslate(Matrix4 matrix, Offset translation) {
if (translation == Offset.zero) {
return matrix.clone();
}
late final Offset alignedTranslation;
if (currentAxis != null) {
switch (widget.panAxis) {
case PanAxis.horizontal:
alignedTranslation = alignAxis(translation, Axis.horizontal);
case PanAxis.vertical:
alignedTranslation = alignAxis(translation, Axis.vertical);
case PanAxis.aligned:
alignedTranslation = alignAxis(translation, currentAxis!);
case PanAxis.free:
alignedTranslation = translation;
}
} else {
alignedTranslation = translation;
}
final Matrix4 nextMatrix = matrix.clone()
..translate(
alignedTranslation.dx,
alignedTranslation.dy,
);
// Transform the viewport to determine where its four corners will be after
// the child has been transformed.
final Quad nextViewport = transformViewport(nextMatrix, widgetViewport);
// If the boundaries are infinite, then no need to check if the translation
// fits within them.
if (childBoundaryRect.isInfinite) {
return nextMatrix;
}
// Expand the boundaries with rotation. This prevents the problem where a
// mismatch in orientation between the viewport and boundaries effectively
// limits translation. With this approach, all points that are visible with
// no rotation are visible after rotation.
final Quad boundariesAabbQuad = getAxisAlignedBoundingBoxWithRotation(
childBoundaryRect,
currentRotation,
);
// If the given translation fits completely within the boundaries, allow it.
final Offset offendingDistance =
exceedsBy(boundariesAabbQuad, nextViewport);
if (offendingDistance == Offset.zero) {
return nextMatrix;
}
// Desired translation goes out of bounds, so translate to the nearest
// in-bounds point instead.
final Offset nextTotalTranslation = getMatrixTranslation(nextMatrix);
final double currentScale = matrix.getScaleOnZAxis();
final Offset correctedTotalTranslation = Offset(
nextTotalTranslation.dx - offendingDistance.dx * currentScale,
nextTotalTranslation.dy - offendingDistance.dy * currentScale,
);
// TODO(justinmc): This needs some work to handle rotation properly. The
// idea is that the boundaries are axis aligned (boundariesAabbQuad), but
// calculating the translation to put the viewport inside that Quad is more
// complicated than this when rotated.
// https://github.com/flutter/flutter/issues/57698
final Matrix4 correctedMatrix = matrix.clone()
..setTranslation(Vector3(
correctedTotalTranslation.dx,
correctedTotalTranslation.dy,
0.0,
));
// Double check that the corrected translation fits.
final Quad correctedViewport =
transformViewport(correctedMatrix, widgetViewport);
final Offset offendingCorrectedDistance =
exceedsBy(boundariesAabbQuad, correctedViewport);
if (offendingCorrectedDistance == Offset.zero) {
return correctedMatrix;
}
// If the corrected translation doesn't fit in either direction, don't allow
// any translation at all. This happens when the viewport is larger than the
// entire boundary.
if (offendingCorrectedDistance.dx != 0.0 &&
offendingCorrectedDistance.dy != 0.0) {
return matrix.clone();
}
// Otherwise, allow translation in only the direction that fits. This
// happens when the viewport is larger than the boundary in one direction.
final Offset unidirectionalCorrectedTotalTranslation = Offset(
offendingCorrectedDistance.dx == 0.0 ? correctedTotalTranslation.dx : 0.0,
offendingCorrectedDistance.dy == 0.0 ? correctedTotalTranslation.dy : 0.0,
);
return matrix.clone()
..setTranslation(Vector3(
unidirectionalCorrectedTotalTranslation.dx,
unidirectionalCorrectedTotalTranslation.dy,
0.0,
));
}