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.

Implementation

void focusOnChild(
  CanvasChildId id, {
  ScalingMode scalingMode = ScalingMode.keepScale,
  bool animate = true,
  double preferredHorizontalMargin = 16,
  Duration? duration,
  Size? childSize,
  forceRedraw = false,
}) {
  if (!_children.containsKey(id)) {
    throw _ChildNotFoundException;
  }

  final childInfo = _children[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);

  /*
  margin is symmatric on ltrb so
  2mx + cx = screenWidth
  2my + cy = screenHeight
  where c is child size in screen space and m is margin
  */

  double newScale = _scale;
  Offset newGsTopLeft = _gsTopLeftOffset;

  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
      // and hence a margin is needed to constrain on x, to get the scale, we then center it along y
      newScale = (canvasSize.width - 2 * preferredHorizontalMargin) / childSize!.width;
      break;
  }

  final marginOffset = ((canvasSize.toOffset() - (childSize! * scale).toOffset()) / (2 * scale)).makeAtleast(0);
  newGsTopLeft = childInfo.gsPosition - marginOffset;

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