Generalize find smallest size solution for all four sides of rectangle

Viewed 38

In an app, a user can click the edge of a rectangular box to resize it to the "smallest valid size" in that direction. For example, if they clicked the right edge, the box would resize to the smallest valid width, leaving the left edge where it is. If they clicked the bottom, it would shrink the bottom edge as far as possible without becoming invalid. The criteria for what "valid" is doesn't matter, but it takes some work to calculate.

Now I have written some code to make this work for the right edge of the box, using a binary search to close in on the smallest valid size. But I need a general solution which does not involve repeating my code in four different variations, and I can't figure out how to do it.

The code as it is now (supporting right edge only):

interface Zone {
  x: number;
  y: number;
  width: number;
  height: number;
}

enum Side {
  'top',
  'left',
  'bottom',
  'right',
}

enum ResizeDirection {
  NEGATIVE = -1,
  POSITIVE = 1,
}

async function findMinValidZone(
  zone: Zone,
  minWidth: number,
  maxWidth: number,
  side: Side,
  validator: (z: Zone) => Promise<boolean>
): Promise<Zone> {
  const area = {...zone};
  let d = maxWidth;
  let step = maxWidth - minWidth;
  let currentDirection = ResizeDirection.NEGATIVE; // positive means widen the area, negative means narrow it
  let lastGood: number = d;
  do {
    d += step * currentDirection;
    if (d < minWidth || d > maxWidth) {
      break;
    }
    area.width = d;
    const ok = await validator(area);
    if (ok) {
      currentDirection = ResizeDirection.NEGATIVE;
      lastGood = d;
    } else {
      currentDirection = ResizeDirection.POSITIVE;
    }
    step = step / 2;
  } while (step >= 1);
  area.width = lastGood;
  return area;
}
1 Answers

One way I can think to do this is to rotate the rectangle by whatever multiple of 90 degrees is required to transform the rectangle so that you are always working with the right edge, than rotate it back for the validation and when returning the result. The actual rotation could be done using matrix transformations, but for now I'll leave the actual rotate function for you to write. Then your generalised code would look like this:

function findMinValidZone(
  zone: Zone,
  minwidth: number,
  maxWidth: number,
  side: Side,
  validator: (z: Zone) => Promise<boolean>
): Promise<Zone> {
  let rotationAngle: number;
  switch (side) {
    case Side.bottom:
      rotationAngle = -Math.PI / 2;
      break;
    case Side.left:
      rotationAngle = Math.PI;
      break;
    case Side.right:
      rotationAngle = 0;
      break;
    case Side.top:
      rotationAngle = Math.PI / 2;
  }
  let d = maxWidth;
  let step = maxWidth - minwidth;
  // rotate the area so that you are always working on the width dimension
  // while keeping other dimensions fixed. Then rotate area back again
  // when validating and when returning the results
  const area = rotateZone({...zone}, rotationAngle);
  let currentDirection = ResizeDirection.NEGATIVE; // positive means widen the area, negative means narrow it
  let lastGood: number = d;
  do {
    d += step * currentDirection;
    if (d < minwidth || d > maxWidth) {
      break;
    }
    area.width = d;
    const ok = await validator(rotateZone(area, -rotationAngle));
    if (ok) {
      currentDirection = ResizeDirection.NEGATIVE;
      lastGood = d;
    } else {
      currentDirection = ResizeDirection.POSITIVE;
    }
    step = step / 2;
  } while (step >= 1);
  area.width = lastGood;
  return rotateZone(area, -rotationAngle);
}

I'll think about the rotateZone function itself, but in principle it should be simple enough using a library like https://www.npmjs.com/package/transformation-matrix

Related