/******************************************************************************* * Copyright (c) 2004, 2015 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.jface.util; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.widgets.Control; /** * Contains static methods for performing simple geometric operations * on the SWT geometry classes. * * @since 3.0 */ public class Geometry { /** * Prevent this class from being instantiated. * * @since 3.0 */ private Geometry() { //This is not instantiated } /** * Returns the square of the distance between two points. * <p>This is preferred over the real distance when searching * for the closest point, since it avoids square roots.</p> * * @param p1 first endpoint * @param p2 second endpoint * @return the square of the distance between the two points * * @since 3.0 */ public static int distanceSquared(Point p1, Point p2) { int term1 = p1.x - p2.x; int term2 = p1.y - p2.y; return term1 * term1 + term2 * term2; } /** * Returns the magnitude of the given 2d vector (represented as a Point) * * @param p point representing the 2d vector whose magnitude is being computed * @return the magnitude of the given 2d vector * @since 3.0 */ public static double magnitude(Point p) { return Math.sqrt(magnitudeSquared(p)); } /** * Returns the square of the magnitude of the given 2-space vector (represented * using a point) * * @param p the point whose magnitude is being computed * @return the square of the magnitude of the given vector * @since 3.0 */ public static int magnitudeSquared(Point p) { return p.x * p.x + p.y * p.y; } /** * Returns the dot product of the given vectors (expressed as Points) * * @param p1 the first vector * @param p2 the second vector * @return the dot product of the two vectors * @since 3.0 */ public static int dotProduct(Point p1, Point p2) { return p1.x * p2.x + p1.y * p2.y; } /** * Returns a new point whose coordinates are the minimum of the coordinates of the * given points * * @param p1 a Point * @param p2 a Point * @return a new point whose coordinates are the minimum of the coordinates of the * given points * @since 3.0 */ public static Point min(Point p1, Point p2) { return new Point(Math.min(p1.x, p2.x), Math.min(p1.y, p2.y)); } /** * Returns a new point whose coordinates are the maximum of the coordinates * of the given points * @param p1 a Point * @param p2 a Point * @return point a new point whose coordinates are the maximum of the coordinates * @since 3.0 */ public static Point max(Point p1, Point p2) { return new Point(Math.max(p1.x, p2.x), Math.max(p1.y, p2.y)); } /** * Returns a vector in the given direction with the given * magnitude. Directions are given using SWT direction constants, and * the resulting vector is in the screen's coordinate system. That is, * the vector (0, 1) is down and the vector (1, 0) is right. * * @param distance magnitude of the vector * @param direction one of SWT.TOP, SWT.BOTTOM, SWT.LEFT, or SWT.RIGHT * @return a point representing a vector in the given direction with the given magnitude * @since 3.0 */ public static Point getDirectionVector(int distance, int direction) { switch (direction) { case SWT.TOP: return new Point(0, -distance); case SWT.BOTTOM: return new Point(0, distance); case SWT.LEFT: return new Point(-distance, 0); case SWT.RIGHT: return new Point(distance, 0); } return new Point(0, 0); } /** * Returns the point in the center of the given rectangle. * * @param rect rectangle being computed * @return a Point at the center of the given rectangle. * @since 3.0 */ public static Point centerPoint(Rectangle rect) { return new Point(rect.x + rect.width / 2, rect.y + rect.height / 2); } /** * Returns a copy of the given point * * @param toCopy point to copy * @return a copy of the given point */ public static Point copy(Point toCopy) { return new Point(toCopy.x, toCopy.y); } /** * Sets result equal to toCopy * * @param result object that will be modified * @param toCopy object that will be copied * @since 3.1 */ public static void set(Point result, Point toCopy) { result.x = toCopy.x; result.y = toCopy.y; } /** * Sets result equal to toCopy * * @param result object that will be modified * @param toCopy object that will be copied * @since 3.1 */ public static void set(Rectangle result, Rectangle toCopy) { result.x = toCopy.x; result.y = toCopy.y; result.width = toCopy.width; result.height = toCopy.height; } /** * <p>Returns a new difference Rectangle whose x, y, width, and height are equal to the difference of the corresponding * attributes from the given rectangles</p> * * <p></p> * <b>Example: Compute the margins for a given Composite, and apply those same margins to a new GridLayout</b> * * <code><pre> * // Compute the client area, in the coordinate system of the input composite's parent * Rectangle clientArea = Display.getCurrent().map(inputComposite, * inputComposite.getParent(), inputComposite.getClientArea()); * * // Compute the margins for a given Composite by subtracting the client area from the composite's bounds * Rectangle margins = Geometry.subtract(inputComposite.getBounds(), clientArea); * * // Now apply these margins to a new GridLayout * GridLayout layout = GridLayoutFactory.fillDefaults().margins(margins).create(); * </pre></code> * * @param rect1 first rectangle * @param rect2 rectangle to subtract * @return the difference between the two rectangles (computed as rect1 - rect2) * @since 3.3 */ public static Rectangle subtract(Rectangle rect1, Rectangle rect2) { return new Rectangle(rect1.x - rect2.x, rect1.y - rect2.y, rect1.width - rect2.width, rect1.height - rect2.height); } /** * <p>Returns a new Rectangle whose x, y, width, and height is the sum of the x, y, width, and height values of * both rectangles respectively.</p> * * @param rect1 first rectangle to add * @param rect2 second rectangle to add * @return a new rectangle whose x, y, height, and width attributes are the sum of the corresponding attributes from * the arguments. * @since 3.3 */ public static Rectangle add(Rectangle rect1, Rectangle rect2) { return new Rectangle(rect1.x + rect2.x, rect1.y + rect2.y, rect1.width + rect2.width, rect1.height + rect2.height); } /** * Adds two points as 2d vectors. Returns a new point whose coordinates are * the sum of the original two points. * * @param point1 the first point (not null) * @param point2 the second point (not null) * @return a new point whose coordinates are the sum of the given points * @since 3.0 */ public static Point add(Point point1, Point point2) { return new Point(point1.x + point2.x, point1.y + point2.y); } /** * Divides both coordinates of the given point by the given scalar. * * @since 3.1 * * @param toDivide point to divide * @param scalar denominator * @return a new Point whose coordinates are equal to the original point divided by the scalar */ public static Point divide(Point toDivide, int scalar) { return new Point(toDivide.x / scalar, toDivide.y / scalar); } /** * Performs vector subtraction on two points. Returns a new point equal to * (point1 - point2). * * @param point1 initial point * @param point2 vector to subtract * @return the difference (point1 - point2) * @since 3.0 */ public static Point subtract(Point point1, Point point2) { return new Point(point1.x - point2.x, point1.y - point2.y); } /** * Swaps the X and Y coordinates of the given point. * * @param toFlip modifies this point * @since 3.1 */ public static void flipXY(Point toFlip) { int temp = toFlip.x; toFlip.x = toFlip.y; toFlip.y = temp; } /** * Swaps the X and Y coordinates of the given rectangle, along with the height and width. * * @param toFlip modifies this rectangle * @since 3.1 */ public static void flipXY(Rectangle toFlip) { int temp = toFlip.x; toFlip.x = toFlip.y; toFlip.y = temp; temp = toFlip.width; toFlip.width = toFlip.height; toFlip.height = temp; } /** * Returns the height or width of the given rectangle. * * @param toMeasure rectangle to measure * @param width returns the width if true, and the height if false * @return the width or height of the given rectangle * @since 3.0 */ public static int getDimension(Rectangle toMeasure, boolean width) { if (width) { return toMeasure.width; } return toMeasure.height; } /** * Returns the x or y coordinates of the given point. * * @param toMeasure point being measured * @param width if true, returns x. Otherwise, returns y. * @return the x or y coordinate * @since 3.1 */ public static int getCoordinate(Point toMeasure, boolean width) { return width ? toMeasure.x : toMeasure.y; } /** * Returns the x or y coordinates of the given rectangle. * * @param toMeasure rectangle being measured * @param width if true, returns x. Otherwise, returns y. * @return the x or y coordinate * @since 3.1 */ public static int getCoordinate(Rectangle toMeasure, boolean width) { return width ? toMeasure.x : toMeasure.y; } /** * Sets one dimension of the given rectangle. Modifies the given rectangle. * * @param toSet rectangle to modify * @param width if true, the width is modified. If false, the height is modified. * @param newCoordinate new value of the width or height * @since 3.1 */ public static void setDimension(Rectangle toSet, boolean width, int newCoordinate) { if (width) { toSet.width = newCoordinate; } else { toSet.height = newCoordinate; } } /** * Sets one coordinate of the given rectangle. Modifies the given rectangle. * * @param toSet rectangle to modify * @param width if true, the x coordinate is modified. If false, the y coordinate is modified. * @param newCoordinate new value of the x or y coordinates * @since 3.1 */ public static void setCoordinate(Rectangle toSet, boolean width, int newCoordinate) { if (width) { toSet.x = newCoordinate; } else { toSet.y = newCoordinate; } } /** * Sets one coordinate of the given point. Modifies the given point. * * @param toSet point to modify * @param width if true, the x coordinate is modified. If false, the y coordinate is modified. * @param newCoordinate new value of the x or y coordinates * @since 3.1 */ public static void setCoordinate(Point toSet, boolean width, int newCoordinate) { if (width) { toSet.x = newCoordinate; } else { toSet.y = newCoordinate; } } /** * Returns the distance of the given point from a particular side of the given rectangle. * Returns negative values for points outside the rectangle. * * @param rectangle a bounding rectangle * @param testPoint a point to test * @param edgeOfInterest side of the rectangle to test against * @return the distance of the given point from the given edge of the rectangle * @since 3.0 */ public static int getDistanceFromEdge(Rectangle rectangle, Point testPoint, int edgeOfInterest) { switch (edgeOfInterest) { case SWT.TOP: return testPoint.y - rectangle.y; case SWT.BOTTOM: return rectangle.y + rectangle.height - testPoint.y; case SWT.LEFT: return testPoint.x - rectangle.x; case SWT.RIGHT: return rectangle.x + rectangle.width - testPoint.x; } return 0; } /** * Extrudes the given edge inward by the given distance. That is, if one side of the rectangle * was sliced off with a given thickness, this returns the rectangle that forms the slice. Note * that the returned rectangle will be inside the given rectangle if size > 0. * * @param toExtrude the rectangle to extrude. The resulting rectangle will share three sides * with this rectangle. * @param size distance to extrude. A negative size will extrude outwards (that is, the resulting * rectangle will overlap the original iff this is positive). * @param orientation the side to extrude. One of SWT.LEFT, SWT.RIGHT, SWT.TOP, or SWT.BOTTOM. The * resulting rectangle will always share this side with the original rectangle. * @return a rectangle formed by extruding the given side of the rectangle by the given distance. * @since 3.0 */ public static Rectangle getExtrudedEdge(Rectangle toExtrude, int size, int orientation) { Rectangle bounds = new Rectangle(toExtrude.x, toExtrude.y, toExtrude.width, toExtrude.height); if (!isHorizontal(orientation)) { bounds.width = size; } else { bounds.height = size; } switch (orientation) { case SWT.RIGHT: bounds.x = toExtrude.x + toExtrude.width - bounds.width; break; case SWT.BOTTOM: bounds.y = toExtrude.y + toExtrude.height - bounds.height; break; } normalize(bounds); return bounds; } /** * Returns the opposite of the given direction. That is, returns SWT.LEFT if * given SWT.RIGHT and visa-versa. * * @param swtDirectionConstant one of SWT.LEFT, SWT.RIGHT, SWT.TOP, or SWT.BOTTOM * @return one of SWT.LEFT, SWT.RIGHT, SWT.TOP, or SWT.BOTTOM * @since 3.0 */ public static int getOppositeSide(int swtDirectionConstant) { switch (swtDirectionConstant) { case SWT.TOP: return SWT.BOTTOM; case SWT.BOTTOM: return SWT.TOP; case SWT.LEFT: return SWT.RIGHT; case SWT.RIGHT: return SWT.LEFT; } return swtDirectionConstant; } /** * Converts the given boolean into an SWT orientation constant. * * @param horizontal if true, returns SWT.HORIZONTAL. If false, returns SWT.VERTICAL * @return SWT.HORIZONTAL or SWT.VERTICAL. * @since 3.0 */ public static int getSwtHorizontalOrVerticalConstant(boolean horizontal) { if (horizontal) { return SWT.HORIZONTAL; } return SWT.VERTICAL; } /** * Returns true iff the given SWT side constant corresponds to a horizontal side * of a rectangle. That is, returns true for the top and bottom but false for the * left and right. * * @param swtSideConstant one of SWT.TOP, SWT.BOTTOM, SWT.LEFT, or SWT.RIGHT * @return true iff the given side is horizontal. * @since 3.0 */ public static boolean isHorizontal(int swtSideConstant) { return !(swtSideConstant == SWT.LEFT || swtSideConstant == SWT.RIGHT); } /** * Moves the given rectangle by the given delta. * * @param rect rectangle to move (will be modified) * @param delta direction vector to move the rectangle by * @since 3.0 */ public static void moveRectangle(Rectangle rect, Point delta) { rect.x += delta.x; rect.y += delta.y; } /** * Moves each edge of the given rectangle outward by the given amount. Negative values * cause the rectangle to contract. Does not allow the rectangle's width or height to be * reduced below zero. * * @param rect normalized rectangle to modify * @param differenceRect difference rectangle to be added to rect * @since 3.3 */ public static void expand(Rectangle rect, Rectangle differenceRect) { rect.x += differenceRect.x; rect.y += differenceRect.y; rect.height = Math.max(0, rect.height + differenceRect.height); rect.width = Math.max(0, rect.width + differenceRect.width); } /** * <p>Returns a rectangle which, when added to another rectangle, will expand each side * by the given number of units.</p> * * <p>This is commonly used to store margin sizes. For example:</p> * * <code><pre> * // Expands the left, right, top, and bottom * // of the given control by 10, 5, 1, and 15 units respectively * * Rectangle margins = Geometry.createDifferenceRect(10,5,1,15); * Rectangle bounds = someControl.getBounds(); * someControl.setBounds(Geometry.add(bounds, margins)); * </pre></code> * * @param left distance to expand the left side (negative values move the edge inward) * @param right distance to expand the right side (negative values move the edge inward) * @param top distance to expand the top (negative values move the edge inward) * @param bottom distance to expand the bottom (negative values move the edge inward) * * @return a difference rectangle that, when added to another rectangle, will cause each * side to expand by the given number of units * @since 3.3 */ public static Rectangle createDiffRectangle(int left, int right, int top, int bottom) { return new Rectangle(-left, -top, left + right, top + bottom); } /** * Moves each edge of the given rectangle outward by the given amount. Negative values * cause the rectangle to contract. Does not allow the rectangle's width or height to be * reduced below zero. * * @param rect normalized rectangle to modify * @param left distance to move the left edge outward (negative values move the edge inward) * @param right distance to move the right edge outward (negative values move the edge inward) * @param top distance to move the top edge outward (negative values move the edge inward) * @param bottom distance to move the bottom edge outward (negative values move the edge inward) * @since 3.1 */ public static void expand(Rectangle rect, int left, int right, int top, int bottom) { rect.x -= left; rect.width = Math.max(0, rect.width + left + right); rect.y -= top; rect.height = Math.max(0, rect.height + top + bottom); } /** * Normalizes the given rectangle. That is, any rectangle with * negative width or height becomes a rectangle with positive * width or height that extends to the upper-left of the original * rectangle. * * @param rect rectangle to modify * @since 3.0 */ public static void normalize(Rectangle rect) { if (rect.width < 0) { rect.width = -rect.width; rect.x -= rect.width; } if (rect.height < 0) { rect.height = -rect.height; rect.y -= rect.height; } } /** * Converts the given rectangle from display coordinates to the local coordinate system * of the given object into display coordinates. * * @param coordinateSystem local coordinate system being converted to * @param toConvert rectangle to convert * @return a rectangle in control coordinates * @since 3.0 */ public static Rectangle toControl(Control coordinateSystem, Rectangle toConvert) { return(coordinateSystem.getDisplay().map (null,coordinateSystem,toConvert)); } /** * Converts the given rectangle from the local coordinate system of the given object * into display coordinates. * * @param coordinateSystem local coordinate system being converted from * @param toConvert rectangle to convert * @return a rectangle in display coordinates * @since 3.0 */ public static Rectangle toDisplay(Control coordinateSystem, Rectangle toConvert) { return(coordinateSystem.getDisplay().map (coordinateSystem,null,toConvert)); } /** * Determines where the given point lies with respect to the given rectangle. * Returns a combination of SWT.LEFT, SWT.RIGHT, SWT.TOP, and SWT.BOTTOM, combined * with bitwise or (for example, returns SWT.TOP | SWT.LEFT if the point is to the * upper-left of the rectangle). Returns 0 if the point lies within the rectangle. * Positions are in screen coordinates (ie: a point is to the upper-left of the * rectangle if its x and y coordinates are smaller than any point in the rectangle) * * @param boundary normalized boundary rectangle * @param toTest point whose relative position to the rectangle is being computed * @return one of SWT.LEFT | SWT.TOP, SWT.TOP, SWT.RIGHT | SWT.TOP, SWT.LEFT, 0, * SWT.RIGHT, SWT.LEFT | SWT.BOTTOM, SWT.BOTTOM, SWT.RIGHT | SWT.BOTTOM * @since 3.0 */ public static int getRelativePosition(Rectangle boundary, Point toTest) { int result = 0; if (toTest.x < boundary.x) { result |= SWT.LEFT; } else if (toTest.x >= boundary.x + boundary.width) { result |= SWT.RIGHT; } if (toTest.y < boundary.y) { result |= SWT.TOP; } else if (toTest.y >= boundary.y + boundary.height) { result |= SWT.BOTTOM; } return result; } /** * Returns the distance from the point to the nearest edge of the given * rectangle. Returns negative values if the point lies outside the rectangle. * * @param boundary rectangle to test * @param toTest point to test * @return the distance between the given point and the nearest edge of the rectangle. * Returns positive values for points inside the rectangle and negative values for points * outside the rectangle. * @since 3.1 */ public static int getDistanceFrom(Rectangle boundary, Point toTest) { int side = getClosestSide(boundary, toTest); return getDistanceFromEdge(boundary, toTest, side); } /** * Returns the edge of the given rectangle is closest to the given * point. * * @param boundary rectangle to test * @param toTest point to compare * @return one of SWT.LEFT, SWT.RIGHT, SWT.TOP, or SWT.BOTTOM * * @since 3.0 */ public static int getClosestSide(Rectangle boundary, Point toTest) { int[] sides = new int[] { SWT.LEFT, SWT.RIGHT, SWT.TOP, SWT.BOTTOM }; int closestSide = SWT.LEFT; int closestDistance = Integer.MAX_VALUE; for (int side : sides) { int distance = getDistanceFromEdge(boundary, toTest, side); if (distance < closestDistance) { closestDistance = distance; closestSide = side; } } return closestSide; } /** * Returns a copy of the given rectangle * * @param toCopy rectangle to copy * @return a copy of the given rectangle * @since 3.0 */ public static Rectangle copy(Rectangle toCopy) { return new Rectangle(toCopy.x, toCopy.y, toCopy.width, toCopy.height); } /** * Returns the size of the rectangle, as a Point * * @param rectangle rectangle whose size is being computed * @return the size of the given rectangle * @since 3.0 */ public static Point getSize(Rectangle rectangle) { return new Point(rectangle.width, rectangle.height); } /** * Sets the size of the given rectangle to the given size * * @param rectangle rectangle to modify * @param newSize new size of the rectangle * @since 3.0 */ public static void setSize(Rectangle rectangle, Point newSize) { rectangle.width = newSize.x; rectangle.height = newSize.y; } /** * Sets the x,y position of the given rectangle. For a normalized * rectangle (a rectangle with positive width and height), this will * be the upper-left corner of the rectangle. * * @param rectangle rectangle to modify * @param newLocation new location of the rectangle * * @since 3.0 */ public static void setLocation(Rectangle rectangle, Point newLocation) { rectangle.x = newLocation.x; rectangle.y = newLocation.y; } /** * Returns the x,y position of the given rectangle. For normalized rectangles * (rectangles with positive width and height), this is the upper-left * corner of the rectangle. * * @param toQuery rectangle to query * @return a Point containing the x,y position of the rectangle * * @since 3.0 */ public static Point getLocation(Rectangle toQuery) { return new Point(toQuery.x, toQuery.y); } /** * Returns a new rectangle with the given position and dimensions, expressed * as points. * * @param position the (x,y) position of the rectangle * @param size the size of the new rectangle, where (x,y) -> (width, height) * @return a new Rectangle with the given position and size * * @since 3.0 */ public static Rectangle createRectangle(Point position, Point size) { return new Rectangle(position.x, position.y, size.x, size.y); } /** * Repositions the 'inner' rectangle to lie completely within the bounds of the 'outer' * rectangle if possible. One use for this is to ensure that, when setting a control's bounds, * that they will always lie within its parent's client area (to avoid clipping). * * @param inner The 'inner' rectangle to be repositioned (should be smaller than the 'outer' rectangle) * @param outer The 'outer' rectangle */ public static void moveInside(Rectangle inner, Rectangle outer) { // adjust X if (inner.x < outer.x) { inner.x = outer.x; } if ((inner.x + inner.width) > (outer.x + outer.width)) { inner.x -= (inner.x + inner.width) - (outer.x + outer.width); } // Adjust Y if (inner.y < outer.y) { inner.y = outer.y; } if ((inner.y + inner.height) > (outer.y + outer.height)) { inner.y -= (inner.y + inner.height) - (outer.y + outer.height); } } }