focusOnChild method

void focusOnChild(
  1. CanvasChildId id, {
  2. ScalingMode scalingMode = ScalingMode.keepScale,
  3. bool animate = true,
  4. double preferredHorizontalMargin = 16,
  5. Duration? duration,
  6. Size? childSize,
  7. dynamic forceRedraw = false,
})

Focus the viewport on a child by its ID, with a margin in screen-space. If it's already rendered, size will be picked up from the child widget. If not an offstage rendering will be used ( double render ) Preferred horizontal margin used for ScalingMode.fitInViewport. Fits rotated bounds in both viewport dimensions and centers using the resulting scale. Zoom limits take precedence over fitting; the child remains centered.

Implementation

void focusOnChild(
  CanvasChildId id, {
  ScalingMode scalingMode = ScalingMode.keepScale,
  bool animate = true,
  double preferredHorizontalMargin = 16,
  Duration? duration,
  Size? childSize,
  forceRedraw = false,
}) {
  final childInfo = _requireChild(id);

  // try to figure out the size, take from render cache if available
  // else do an offstage render
  childSize ??= childInfo.lastRenderedSize != null && !forceRedraw
      ? childInfo.lastRenderedSize
      : measureWidgetSize(_context, childInfo.widget);

  _validateGeometry(childSize: childSize);
  _updateChild(childInfo, id, childSize: childSize);
  _markDirty = true;

  final bounds = MatrixUtils.transformRect(
    childTransform(childInfo.gsPosition, childSize!, childInfo.rotation),
    Offset.zero & childSize,
  );

  /*
  margin is symmetric on ltrb so
  2mx + cx = screenWidth
  2my + cy = screenHeight
  where c is rotated bounds size in screen space at newScale and m is margin
  centering in grid space is childCenter - screenCenter / newScale
  */

  double newScale = _scale;

  switch (scalingMode) {
    case ScalingMode.keepScale:
      // do nothing
      break;
    case ScalingMode.resetScale:
      newScale = 1;
    case ScalingMode.fitInViewport:
      // the scale needs to be determined in this case
      // the horizontal margin constrains x, viewport height constrains y; use the smaller scale
      final horizontalScale = bounds.width == 0
          ? double.infinity
          : (canvasSize.width - 2 * preferredHorizontalMargin) / bounds.width;
      final verticalScale = bounds.height == 0
          ? double.infinity
          : canvasSize.height / bounds.height;
      final fitScale = min(horizontalScale, verticalScale);
      if (fitScale.isFinite && fitScale > 0) newScale = fitScale;
      break;
  }

  newScale = _clampScale(newScale);
  final newGsTopLeft = bounds.center - _ssCenter / newScale;

  if (animate) {
    animateToOffsetAndScale(
      offset: newGsTopLeft,
      duration: duration,
      scale: newScale,
    );
  } else {
    stopAnimation();
    _gsTopLeftOffset = newGsTopLeft;
    _scale = newScale;
    markDirty();
  }
}