Skip to content

Commit 438f7e7

Browse files
committed
Rotation #4
1 parent edf8235 commit 438f7e7

File tree

6 files changed

+338
-159
lines changed

6 files changed

+338
-159
lines changed

packages/box_transform/lib/src/geometry.dart

+69-22
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,9 @@ class Dimension {
308308
/// smallest integer values.
309309
Dimension floor() => Dimension(width.floorToDouble(), height.floorToDouble());
310310

311+
/// Returns a [Vector2] representation of this [Dimension].
312+
Vector2 toVector() => Vector2(width, height);
313+
311314
/// Linearly interpolate between two sizes
312315
///
313316
/// If either size is null, this function interpolates from [Dimension.zero].
@@ -412,28 +415,26 @@ class Box {
412415
/// Construct a rectangle from given handle and its origin.
413416
factory Box.fromHandle(
414417
Vector2 origin, HandlePosition handle, double width, double height) {
415-
switch (handle) {
416-
case HandlePosition.none:
417-
throw ArgumentError('HandlePosition.none is not supported!');
418-
case HandlePosition.topLeft:
419-
return Box.fromLTWH(origin.x - width, origin.y - height, width, height);
420-
case HandlePosition.topRight:
421-
return Box.fromLTWH(origin.x, origin.y - height, width, height);
422-
case HandlePosition.bottomLeft:
423-
return Box.fromLTWH(origin.x - width, origin.y, width, height);
424-
case HandlePosition.bottomRight:
425-
return Box.fromLTWH(origin.x, origin.y, width, height);
426-
case HandlePosition.left:
427-
return Box.fromLTWH(
428-
origin.x - width, origin.y - height / 2, width, height);
429-
case HandlePosition.top:
430-
return Box.fromLTWH(
431-
origin.x - width / 2, origin.y - height, width, height);
432-
case HandlePosition.right:
433-
return Box.fromLTWH(origin.x, origin.y - height / 2, width, height);
434-
case HandlePosition.bottom:
435-
return Box.fromLTWH(origin.x - width / 2, origin.y, width, height);
436-
}
418+
return switch (handle) {
419+
HandlePosition.none =>
420+
throw ArgumentError('HandlePosition.none is not supported!'),
421+
HandlePosition.topLeft =>
422+
Box.fromLTWH(origin.x - width, origin.y - height, width, height),
423+
HandlePosition.topRight =>
424+
Box.fromLTWH(origin.x, origin.y - height, width, height),
425+
HandlePosition.bottomLeft =>
426+
Box.fromLTWH(origin.x - width, origin.y, width, height),
427+
HandlePosition.bottomRight =>
428+
Box.fromLTWH(origin.x, origin.y, width, height),
429+
HandlePosition.left =>
430+
Box.fromLTWH(origin.x - width, origin.y - height / 2, width, height),
431+
HandlePosition.top =>
432+
Box.fromLTWH(origin.x - width / 2, origin.y - height, width, height),
433+
HandlePosition.right =>
434+
Box.fromLTWH(origin.x, origin.y - height / 2, width, height),
435+
HandlePosition.bottom =>
436+
Box.fromLTWH(origin.x - width / 2, origin.y, width, height)
437+
};
437438
}
438439

439440
/// The Vector2 of the left edge of this rectangle from the x axis.
@@ -607,6 +608,23 @@ class Box {
607608
_ => size.aspectRatio,
608609
};
609610

611+
/// Returns a list of the four corners of this rectangle.
612+
List<Vector2> get points => [
613+
topLeft,
614+
topRight,
615+
bottomRight,
616+
bottomLeft,
617+
];
618+
619+
/// Returns a map of the four corners of this rectangle mapped as
620+
/// a [Quadrant] to a [Vector2].
621+
Map<Quadrant, Vector2> get sidedPoints => {
622+
Quadrant.topLeft: topLeft,
623+
Quadrant.topRight: topRight,
624+
Quadrant.bottomRight: bottomRight,
625+
Quadrant.bottomLeft: bottomLeft,
626+
};
627+
610628
/// Whether the point specified by the given Vector2 (which is assumed to be
611629
/// relative to the origin) lies between the left and right and the top and
612630
/// bottom edges of this rectangle.
@@ -669,6 +687,35 @@ class Box {
669687
bottom.floorToDouble(),
670688
);
671689

690+
/// Returns the relevant corner of this [Box] based on the given [quadrant].
691+
Vector2 pointFromQuadrant(Quadrant quadrant) => switch (quadrant) {
692+
Quadrant.topLeft => topLeft,
693+
Quadrant.topRight => topRight,
694+
Quadrant.bottomRight => bottomRight,
695+
Quadrant.bottomLeft => bottomLeft,
696+
};
697+
698+
/// Returns a value that represents the distances of the passed
699+
/// [point] relative to the closest edge of this [Box]. If the point is
700+
/// inside the box, the distance will be positive. If the point is outside
701+
/// the box, the distance will be negative.
702+
///
703+
/// Returns the [side] that the point is closest to and the distance to that
704+
/// side.
705+
(Side side, double) distanceOfPoint(Vector2 point) {
706+
final double left = point.x - this.left;
707+
final double right = this.right - point.x;
708+
final double top = point.y - this.top;
709+
final double bottom = this.bottom - point.y;
710+
711+
final double min = math.min(left, math.min(right, math.min(top, bottom)));
712+
713+
if (min == left) return (Side.left, left);
714+
if (min == right) return (Side.right, right);
715+
if (min == top) return (Side.top, top);
716+
return (Side.bottom, bottom);
717+
}
718+
672719
/// Linearly interpolate between two rectangles.
673720
///
674721
/// If either rect is null, [Box.zero] is used as a substitute.

packages/box_transform/lib/src/resizers/freeform_resizing.dart

+13-26
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,6 @@ final class FreeformResizer extends Resizer {
2727
final HandlePosition flippedHandle = handle.flip(flip);
2828

2929
Box newRect = explodedRect;
30-
// Box.fromLTRB(
31-
// max(explodedRect.left, clampingRect.left),
32-
// max(explodedRect.top, clampingRect.top),
33-
// min(explodedRect.right, clampingRect.right),
34-
// min(explodedRect.bottom, clampingRect.bottom),
35-
// );
3630
newRect = repositionRotatedResizedBox(
3731
newRect: newRect,
3832
initialRect: initialRect,
@@ -43,33 +37,25 @@ final class FreeformResizer extends Resizer {
4337
unrotatedBox: newRect,
4438
);
4539
final bool isClamped = isRectClamped(
46-
switch (bindingStrategy) {
47-
BindingStrategy.originalBox => newRect,
48-
BindingStrategy.boundingBox => newBoundingRect,
49-
},
40+
newBoundingRect,
41+
// switch (bindingStrategy) {
42+
// BindingStrategy.originalBox => newRect,
43+
// BindingStrategy.boundingBox => newBoundingRect,
44+
// },
5045
clampingRect,
5146
);
52-
bool isBound = true;
5347
if (!isClamped) {
54-
final Box initialBinding = switch (bindingStrategy) {
55-
BindingStrategy.originalBox => initialRect,
56-
BindingStrategy.boundingBox => initialBoundingRect,
57-
};
58-
final Box newBinding = switch (bindingStrategy) {
59-
BindingStrategy.originalBox => newRect,
60-
BindingStrategy.boundingBox => newBoundingRect,
61-
};
62-
6348
final Vector2 correctiveDelta = BoxTransformer.stopRectAtClampingRect(
64-
rect: newBinding,
49+
rect: newRect,
6550
clampingRect: clampingRect,
51+
rotation: rotation,
6652
);
6753

6854
newRect = BoxTransformer.applyDelta(
6955
initialRect: newRect,
7056
delta: correctiveDelta,
7157
handle: handle,
72-
resizeMode: ResizeMode.freeform,
58+
resizeMode: ResizeMode.scale,
7359
allowFlipping: false,
7460
);
7561

@@ -78,6 +64,8 @@ final class FreeformResizer extends Resizer {
7864
unrotatedBox: newRect,
7965
);
8066
}
67+
68+
bool isBound = false;
8169
if (!constraints.isUnconstrained) {
8270
final Dimension constrainedSize = Dimension(
8371
newRect.width.clamp(constraints.minWidth, constraints.maxWidth),
@@ -103,11 +91,10 @@ final class FreeformResizer extends Resizer {
10391
rotation: rotation,
10492
unrotatedBox: newRect,
10593
);
106-
isBound = isRectBound(
107-
newBoundingRect,
94+
95+
isBound = isRectConstrained(
96+
newRect,
10897
constraints,
109-
clampingRect,
110-
checkConstraints: newRect,
11198
);
11299

113100
if (!isBound) {

packages/box_transform/lib/src/transformer.dart

+94-64
Original file line numberDiff line numberDiff line change
@@ -119,36 +119,66 @@ class BoxTransformer {
119119
static Vector2 stopRectAtClampingRect({
120120
required Box rect,
121121
required Box clampingRect,
122+
required double rotation,
122123
}) {
123-
final (:side, :amount, :singleIntersection) =
124-
getLargestIntersectionDelta(rect, clampingRect);
125-
126-
if (singleIntersection) {
127-
return switch (side) {
128-
Side.top || Side.bottom => Vector2(0, -amount),
129-
Side.left || Side.right => Vector2(-amount, 0),
130-
};
124+
final Map<Quadrant, Vector2> rotatedPoints = {
125+
for (final MapEntry(key: quadrant, value: point)
126+
in rect.sidedPoints.entries)
127+
quadrant: rotatePointAroundVec(rect.center, rotation, point)
128+
};
129+
130+
// Check if any rotated point is outside the clamping rect.
131+
(
132+
Side side,
133+
Quadrant quadrant,
134+
Vector2 point,
135+
double dist
136+
)? biggestOutOfBounds;
137+
for (final MapEntry(key: quadrant, value: point) in rotatedPoints.entries) {
138+
if (biggestOutOfBounds == null) {
139+
final (side, dist) = clampingRect.distanceOfPoint(point);
140+
biggestOutOfBounds = (side, quadrant, point, dist);
141+
} else {
142+
final (side, dist) = clampingRect.distanceOfPoint(point);
143+
final (_, biggestDist) =
144+
clampingRect.distanceOfPoint(biggestOutOfBounds.$3);
145+
if (dist < biggestDist) {
146+
biggestOutOfBounds = (side, quadrant, point, dist);
147+
}
148+
}
131149
}
132150

133-
if (side.isVertical) {
134-
final correctedHeight = rect.height - amount;
135-
final aspectRatio = rect.width / correctedHeight;
136-
final correctedWidth = rect.width * aspectRatio;
151+
assert(biggestOutOfBounds != null);
137152

138-
final verticalDelta = rect.height - correctedHeight;
139-
final horizontalDelta = rect.width - correctedWidth;
153+
final side = biggestOutOfBounds!.$1;
154+
final quadrant = biggestOutOfBounds.$2;
155+
final point = biggestOutOfBounds.$3;
156+
final dist = biggestOutOfBounds.$4;
140157

141-
return Vector2(horizontalDelta, verticalDelta) * -1;
142-
} else {
143-
final correctedWidth = rect.width - amount;
144-
final aspectRatio = rect.height / correctedWidth;
145-
final correctedHeight = rect.height * aspectRatio;
158+
// Move the out of bounds vector by the perpendicular vector of the side
159+
// that was hit.
160+
final cardinalCorrection = switch (side) {
161+
Side.left => Vector2(-dist, 0),
162+
Side.right => Vector2(dist, 0),
163+
Side.top => Vector2(0, -dist),
164+
Side.bottom => Vector2(0, dist),
165+
};
146166

147-
final horizontalDelta = rect.width - correctedWidth;
148-
final verticalDelta = rect.height - correctedHeight;
167+
final correctedRotatedPoint =
168+
Vector2(point.x + cardinalCorrection.x, point.y + cardinalCorrection.y);
149169

150-
return Vector2(horizontalDelta, verticalDelta) * -1;
151-
}
170+
// Rotate back
171+
final unrotated =
172+
rotatePointAroundVec(rect.center, -rotation, correctedRotatedPoint);
173+
final delta = unrotated - rect.pointFromQuadrant(quadrant);
174+
175+
print(' quad: ${rect.pointFromQuadrant(quadrant)..round()}');
176+
print('unrotated: ${unrotated..round()}');
177+
178+
// Matrix2.rotation(rotation).transform(delta);
179+
print('delta: $delta');
180+
181+
return delta;
152182
}
153183

154184
/// Resizes the given [initialRect] with given [initialLocalPosition] of
@@ -233,17 +263,17 @@ class BoxTransformer {
233263
);
234264
}
235265

236-
final Box bindingRect = switch (bindingStrategy) {
266+
final Box initialBindingRect = switch (bindingStrategy) {
237267
BindingStrategy.originalBox => initialRect,
238268
BindingStrategy.boundingBox => initialBoundingRect,
239269
};
240-
final double bindingWidth = bindingRect.width;
241-
final double bindingHeight = bindingRect.height;
270+
final double initialBindingWidth = initialBindingRect.width;
271+
final double initialBindingHeight = initialBindingRect.height;
242272

243273
// Check if clampingRect is smaller than initialRect.
244274
// If it is, then we return the initialRect and not resize it.
245-
if (clampingRect.width < bindingWidth ||
246-
clampingRect.height < bindingHeight) {
275+
if (clampingRect.width < initialBindingWidth ||
276+
clampingRect.height < initialBindingHeight) {
247277
return ResizeResult(
248278
rect: initialRect,
249279
oldRect: initialRect,
@@ -569,40 +599,40 @@ class BoxTransformer {
569599
);
570600
}
571601

572-
static Dimension calculateBoundingSize({
573-
required double rotation,
574-
required Dimension unrotatedSize,
575-
}) {
576-
final double sinA = sin(rotation);
577-
final double cosA = cos(rotation);
578-
579-
final double width = unrotatedSize.width;
580-
final double height = unrotatedSize.height;
581-
final double boundingWidth = (width * cosA).abs() + (height * sinA).abs();
582-
final double boundingHeight = (width * sinA).abs() + (height * cosA).abs();
583-
584-
return Dimension(boundingWidth, boundingHeight);
585-
}
586-
587-
static Box calculateUnrotatedRect({
588-
required Box boundingBox,
589-
required double rotation,
590-
required double aspectRatio,
591-
}) {
592-
final Vector2 center = boundingBox.center;
593-
594-
final double width = boundingBox.width * cos(-rotation) +
595-
boundingBox.height * sin(-rotation);
596-
597-
// derive from aspect ratio.
598-
final double height = width / aspectRatio;
599-
600-
final Box unrotatedRect = Box.fromCenter(
601-
center: center,
602-
width: width,
603-
height: height,
604-
);
605-
606-
return unrotatedRect;
607-
}
602+
// static Dimension calculateBoundingSize({
603+
// required double rotation,
604+
// required Dimension unrotatedSize,
605+
// }) {
606+
// final double sinA = sin(rotation);
607+
// final double cosA = cos(rotation);
608+
//
609+
// final double width = unrotatedSize.width;
610+
// final double height = unrotatedSize.height;
611+
// final double boundingWidth = (width * cosA).abs() + (height * sinA).abs();
612+
// final double boundingHeight = (width * sinA).abs() + (height * cosA).abs();
613+
//
614+
// return Dimension(boundingWidth, boundingHeight);
615+
// }
616+
617+
// static Box calculateUnrotatedRect({
618+
// required Box boundingBox,
619+
// required double rotation,
620+
// required double aspectRatio,
621+
// }) {
622+
// final Vector2 center = boundingBox.center;
623+
//
624+
// final double width = boundingBox.width * cos(-rotation) +
625+
// boundingBox.height * sin(-rotation);
626+
//
627+
// // derive from aspect ratio.
628+
// final double height = width / aspectRatio;
629+
//
630+
// final Box unrotatedRect = Box.fromCenter(
631+
// center: center,
632+
// width: width,
633+
// height: height,
634+
// );
635+
//
636+
// return unrotatedRect;
637+
// }
608638
}

0 commit comments

Comments
 (0)