3

I'm trying to convert pixel-based x and y values of a child inside a parent such to Flutter's Alignment x & y.

This code is adapted and inspired by Flutter's own Alignment.inscribe function:

(typescript)

function convertPositionToAlignment(
  parentWidth: number,
  parentHeight: number,
  childX: number,
  childY: number,
  childWidth: number,
  childHeight: number
): AlignmentModel {
  const halfWidthDelta = (parentWidth - childWidth) / 2;
  const halfHeightDelta = (parentHeight - childHeight) / 2;

  let x;
  if (halfWidthDelta != 0) {
    x = (childX - halfWidthDelta) / halfWidthDelta;
  } else {
    x = 0;
  }
  let y;
  if (halfHeightDelta != 0) {
    y = (childY - halfHeightDelta) / halfHeightDelta;
  } else {
    y = 0;
  }

  return new AlignmentModel(new AlignmentData(x, y));
}

In contrast, here's Flutter's Alignment.inscribe function:

(dart)

  /// Returns a rect of the given size, aligned within given rect as specified
  /// by this alignment.
  ///
  /// For example, a 100×100 size inscribed on a 200×200 rect using
  /// [Alignment.topLeft] would be the 100×100 rect at the top left of
  /// the 200×200 rect.
  Rect inscribe(Size size, Rect rect) {
    final double halfWidthDelta = (rect.width - size.width) / 2.0;
    final double halfHeightDelta = (rect.height - size.height) / 2.0;
    return Rect.fromLTWH(
      rect.left + halfWidthDelta + x * halfWidthDelta,
      rect.top + halfHeightDelta + y * halfHeightDelta,
      size.width,
      size.height,
    );
  }

The code is simple, but there is a single issue with it which is the edge-case where if the child's size is equal to the parent's, then this produces values of zero (it would produce infinite/NaN otherwise without the if statements).

I was wondering if there's any way to calculate the alignment if the sizes happen to be the same. This is not an issue with coordinate spaces, but is when it comes to alignment.

The reason we would want such an edge-case done away with is for cases like so:

Image 1

VERSUS:

Image 2

This edge case where the image is the same size as the container's box will cause a y alignment of zero right now when it is easily possible in the coordinate space.

Saad Ardati
  • 379
  • 6
  • 17
  • Soooo what value *should* it be producing if the child and parent are the same size? – kelsny Jan 11 '23 at 15:10
  • @vera. Ideally, since 0 is the center and -1 repesents the top-edge of the picture aligned to the top-edge of the parent, the expected value would be < -1 for out-of-bounds alignment that goes outside the top-edge, and > +1 for balues that go out of bounds of the bottom-edge of the parent. This is works correctly when the child size is not equal to the parent's. – Saad Ardati Jan 11 '23 at 15:30
  • That didn't answer my question. What is it supposed to output *exactly*, if they are the same size. You just said <-1 or >+1. For example, if I use `convertPositionToAlignment(100, 200, 50, 50, 50, 200)`, where the child and parent height are the same, what should be the result? – kelsny Jan 11 '23 at 15:32
  • so you are saying that [Alignment.inscribe(Size size, Rect rect)](https://api.flutter.dev/flutter/painting/Alignment/inscribe.html) returns wrong `Rect` when `size == rect.size`? If so, what return value would you expect? – pskink Jan 11 '23 at 18:45

1 Answers1

1

Instead, we can consider the offset of the child's center in the space of an inner rectangle where 0,0 is where the child's top left touches the parent's top left and its bottom right touches the parent's bottom right. This way we can avoid that awkward division.

In this case our point

childCenterX = childX + childWidth / 2;
childCenterY = childY + childHeight / 2;

within a rectangle of

minOffsetX = childWidth / 2;
minOffsetY = childHeight / 2;
maxOffsetX = parentWidth - minOffsetX;
maxOffsetY = parentHeight - minOffsetY;

the offset is

offset = childCenter - minOffset;

and to normalize between -1..1

innerWidth = maxOffsetX - minOffsetX;
innerHeight = maxOffsetY - minOffsetY;
offsetNormal = offset / innerSize * 2 - 1; // innerSize ≠ 0

Edit: Inner size can still be zero if parent and child are exactly the same sizes on one or more axis, so a quick and dirty solution:

double x, y;
if (innerWidth == 0) {
  x = child.x / child.width * 2 - 1;
} else {
  x = offsetX / innerWidth * 2 - 1;
}
if (innerHeight == 0) {
  y = child.y / child.height * 2 - 1;
} else {
  y = offsetY / innerHeight * 2 - 1;
}

Edit2: A more compact typescript version:

const deltaWidth = (parentWidth - childWidth) / 2;
const deltaX = deltaWidth === 0 ? childWidth : deltaWidth;
const x = (childX - deltaX) / deltaX;
    
const deltaHeight = (parentHeight - childHeight) / 2;
const deltaY = deltaHeight === 0 ? childHeight : deltaHeight;
const y = (childY - deltaY) / deltaY;
Birju Vachhani
  • 6,072
  • 4
  • 21
  • 43
Producer
  • 68
  • 7