>From c8af098720971342ce8655b993478bcde49a7481 Mon Sep 17 00:00:00 2001
From: Danny Baumann <dannybaumann@web.de>
Date: Wed, 18 Apr 2007 10:29:59 +0200
Subject: [PATCH] Added proper constraining code.
This allows respecting the aspect ratio hint even when resizing from other sides than the lower right corner.
---
 plugins/resize.c |  172 ++++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 files changed, 166 insertions(+), 6 deletions(-)

diff --git a/plugins/resize.c b/plugins/resize.c
index 98eae4f..45d6ec3 100644
--- a/plugins/resize.c
+++ b/plugins/resize.c
@@ -404,6 +404,8 @@ resizeTerminate (CompDisplay	 *d,
     return FALSE;
 }
 
+#define CLAMP(v, min, max) ((v) <= (min) ? (min) : (v) >= (max) ? (max) : (v))
+
 static void
 resizeConstrainMinMax (CompWindow *w,
 		       int        width,
@@ -439,19 +441,177 @@ resizeConstrainMinMax (CompWindow *w,
 	max_height = hints->max_height;
     }
 
-#define CLAMP(v, min, max) ((v) <= (min) ? (min) : (v) >= (max) ? (max) : (v))
-
     /* clamp width and height to min and max values */
     width  = CLAMP (width, min_width, max_width);
     height = CLAMP (height, min_height, max_height);
 
-#undef CLAMP
-
     *newWidth  = width;
     *newHeight = height;
 }
 
 static void
+resizeConstrainResizeIncrement (CompWindow * w, 
+				int        width, 
+				int        height, 
+				int        *newWidth, 
+				int        *newHeight)
+{
+    int baseWidth, baseHeight;
+    const XSizeHints *hints = &w->sizeHints;
+
+    if (hints->flags & PResizeInc) 
+    {
+	if (hints->flags & PBaseSize) 
+	{
+	    baseWidth = hints->base_width;
+	    baseHeight = hints->base_height;
+	} 
+	else 
+	{
+	    baseWidth = 0;
+	    baseHeight = 0;
+	}
+
+	if ((width - baseWidth) % hints->width_inc) 
+	    width = baseWidth + (((width - baseWidth) / 
+				  hints->width_inc) * hints->width_inc);
+
+	if ((height - baseHeight) % hints->height_inc) 
+	    height = baseHeight + (((height - baseHeight) / 
+				    hints->height_inc) * hints->height_inc);
+    }
+
+    *newWidth = width;
+    *newHeight = height;
+}
+
+/* this code has its origin in metacity's boxes.c */
+static void
+resizeFindLinePointClosestToPoint (double x1, double y1, 
+		   		   double x2, double y2, 
+				   double px, double py, 
+				   double *valx, double *valy)
+{
+    /* I'll use the shorthand rx, ry for the return values, valx & valy.
+     * Now, we need (rx,ry) to be on the line between (x1,y1) and (x2,y2).
+     * For that to happen, we first need the slope of the line from (x1,y1)
+     * to (rx,ry) must match the slope of (x1,y1) to (x2,y2), i.e.:
+     *   (ry-y1)   (y2-y1)
+     *   ------- = -------
+     *   (rx-x1)   (x2-x1)
+     * If x1==x2, though, this gives divide by zero errors, so we want to
+     * rewrite the equation by multiplying both sides by (rx-x1)*(x2-x1):
+     *   (ry-y1)(x2-x1) = (y2-y1)(rx-x1)
+     * This is a valid requirement even when x1==x2 (when x1==x2, this latter
+     * equation will basically just mean that rx must be equal to both x1 and
+     * x2)
+     *
+     * The other requirement that we have is that the line from (rx,ry) to
+     * (px,py) must be perpendicular to the line from (x1,y1) to (x2,y2).  So
+     * we just need to get a vector in the direction of each line, take the
+     * dot product of the two, and ensure that the result is 0:
+     *   (rx-px)*(x2-x1) + (ry-py)*(y2-y1) = 0.
+     *
+     * This gives us two equations and two unknowns:
+     *
+     *   (ry-y1)(x2-x1) = (y2-y1)(rx-x1)
+     *   (rx-px)*(x2-x1) + (ry-py)*(y2-y1) = 0.
+     *
+     * This particular pair of equations is always solvable so long as
+     * (x1,y1) and (x2,y2) are not the same point (and note that anyone who
+     * calls this function that way is braindead because it means that they
+     * really didn't specify a line after all).  However, the caller should
+     * be careful to avoid making (x1,y1) and (x2,y2) too close (e.g. like
+     * 10^{-8} apart in each coordinate), otherwise roundoff error could
+     * cause issues.  Solving these equations by hand (or using Maple(TM) or
+     * Mathematica(TM) or whatever) results in slightly messy expressions,
+     * but that's all the below few lines do.
+     */
+
+    double diffx, diffy, den;
+    diffx = x2 - x1;
+    diffy = y2 - y1;
+    den = diffx * diffx + diffy * diffy;
+
+    *valx = (py * diffx * diffy + px * diffx * diffx +
+	     y2 * x1 * diffy - y1 * x2 * diffy) / den;
+    *valy = (px * diffx * diffy + py * diffy * diffy +
+	     x2 * y1 * diffx - x1 * y2 * diffx) / den;
+}
+
+/* this function is heavily inspired by metacity's constrain_aspect_ratio */
+static Bool
+resizeConstrainAspectRatio(CompWindow * w, 
+			   int        width, 
+			   int        height, 
+			   int        *retWidth, 
+			   int        *retHeight)
+{
+    const XSizeHints *hints = &w->sizeHints;
+    double minRatio, maxRatio;
+    int newWidth, newHeight;
+    double altWidth, altHeight;
+    double bestWidth, bestHeight;
+    int tolerance = 1;
+    Bool resizeLeftRight = FALSE;
+    Bool resizeUpDown = FALSE;
+
+    RESIZE_DISPLAY(w->screen->display);
+
+    newWidth = width;
+    newHeight = height;
+
+    if (hints->flags & PAspect) 
+    {
+	minRatio = hints->min_aspect.x / (double) hints->min_aspect.y;
+	maxRatio = hints->max_aspect.x / (double) hints->max_aspect.y;
+
+	if (minRatio > maxRatio)
+	    return FALSE;
+
+	resizeLeftRight = (rd->mask == (rd->mask & ResizeLeftMask)) ||
+	                  (rd->mask == (rd->mask & ResizeRightMask));
+	resizeUpDown = (rd->mask == (rd->mask & ResizeUpMask)) ||
+	               (rd->mask == (rd->mask & ResizeDownMask));
+
+	if (resizeLeftRight || resizeUpDown)
+	    tolerance = 2;
+
+	/* return if constraint already is satisfied */
+	if (((width - (height * minRatio)) > (-minRatio * tolerance)) &&
+	    ((width - (height * maxRatio)) < (maxRatio * tolerance)))
+	{
+	    return FALSE;
+	}
+
+	if (resizeLeftRight)
+	    newHeight = CLAMP (newHeight, newWidth / maxRatio, newWidth / minRatio);
+	else if (resizeUpDown)
+	    newWidth = CLAMP (newWidth, newHeight * minRatio, newHeight * maxRatio);
+	else 
+	{
+	    altWidth = CLAMP (newWidth, newHeight * minRatio, newHeight * maxRatio);
+	    altHeight = CLAMP (newHeight, newWidth / maxRatio, newWidth / minRatio);
+
+	    resizeFindLinePointClosestToPoint(altWidth, newHeight, 
+					      newWidth, altHeight,
+      					      newWidth, newHeight, 
+					      &bestWidth, &bestHeight);
+
+	    newWidth = bestWidth;
+	    newHeight = bestHeight;
+	}
+    }
+
+    *retWidth = newWidth;
+    *retHeight = newHeight;
+
+    return TRUE;
+}
+
+#undef CLAMP
+
+static void
 resizeUpdateWindowSize (CompDisplay *d, int dx, int dy)
 {
     int width, height;
@@ -493,11 +653,11 @@ resizeUpdateWindowSize (CompDisplay *d, int dx, int dy)
  	height += dy;
 
     /* Apply constraints */
-    //resizeConstrainResizeIncrement (rd->w, width, height, &width, &height);
+    resizeConstrainResizeIncrement (rd->w, width, height, &width, &height);
 
     newWidth = width;
     newHeight = height;
-    //resizeConstrainAspectRatio (rd->w, width, height, &newWidth, &newHeight);
+    resizeConstrainAspectRatio (rd->w, width, height, &newWidth, &newHeight);
     resizeConstrainMinMax (rd->w, newWidth, newHeight, &newWidth, &newHeight);
 
     dx -= newWidth - width;
-- 
1.5.0.6

