Bounds.java

  1. package swingtree.layout;

  2. import com.google.errorprone.annotations.Immutable;

  3. import java.awt.Rectangle;
  4. import java.util.Objects;

  5. /**
  6.  *  An immutable value object that represents the position and size of a component
  7.  *  in the form of an x and y coordinate modeled by a {@link Position} object
  8.  *  and a width and height modeled by a {@link Size} object.
  9.  *  Note the rectangular bounds object is positioned in a coordinate system
  10.  *  where the y-axis is growing positively downwards and the x-axis is growing
  11.  *  positively to the right. <br>
  12.  *  The bounds object may also be incomplete in the sense that the width and height
  13.  *  may not be defined, in which case the {@link Size#unknown()} object is used.
  14.  *  A bounds object with an unknown size located at the origin is considered
  15.  *  the null object for this class and can be accessed using the {@link #none()} method.
  16.  *  You may use this object instead of {@code null} to represent a missing bounds object.
  17.  *  <p>
  18.  *  Also note that the {@link #equals(Object)} and {@link #hashCode()} methods
  19.  *  are implemented to compare the {@link Position} and {@link Size} objects
  20.  *  for value based equality instead of reference based equality.
  21.  */
  22. @Immutable
  23. public final class Bounds
  24. {
  25.     private final static Bounds EMPTY = new Bounds(Position.origin(), Size.unknown());

  26.     /**
  27.      *  Returns an empty bounds object, which is the null object for this class.
  28.      *  <p>
  29.      *  The returned bounds object has a location of {@link Position#origin()}
  30.      *  and a size of {@link Size#unknown()}.
  31.      *
  32.      *  @return an empty bounds object that is the null object for this class.
  33.      */
  34.     public static Bounds none() {
  35.         return EMPTY;
  36.     }

  37.     private final Position _position;
  38.     private final Size     _size;

  39.     /**
  40.      *  Returns a bounds object with the specified location and size.
  41.      *  <p>
  42.      *  If the location is {@link Position#origin()} and the size is
  43.      *  {@link Size#unknown()} then the {@link #none()} object is returned.
  44.      *
  45.      *  @param position the location of the bounds object.
  46.      *  @param size the size of the bounds object.
  47.      *  @return a bounds object with the specified location and size.
  48.      */
  49.     public static Bounds of(Position position, Size size ) {
  50.         Objects.requireNonNull(position);
  51.         Objects.requireNonNull(size);
  52.         if ( position.equals(Position.origin()) && size.equals(Size.unknown()) )
  53.             return EMPTY;

  54.         return new Bounds(position, size);
  55.     }

  56.     /**
  57.      *  Returns a bounds object with the specified location and size
  58.      *  in the form of x and y coordinates, width and height.
  59.      *  <p>
  60.      *  If the width or height is less than zero then the {@link #none()}
  61.      *  object is returned.
  62.      *
  63.      *  @param x the x coordinate of the location of the bounds object.
  64.      *  @param y the y coordinate of the location of the bounds object.
  65.      *  @param width the width of the bounds object.
  66.      *  @param height the height of the bounds object.
  67.      *  @return a bounds object with the specified location and size
  68.      *  in the form of x and y coordinates, width and height.
  69.      */
  70.     public static Bounds of( int x, int y, int width, int height ) {
  71.         if ( width < 0 || height < 0 )
  72.             return EMPTY;

  73.         return new Bounds(Position.of(x, y), Size.of(width, height));
  74.     }

  75.     /**
  76.      *  Returns a bounds object with the specified location and size
  77.      *  in the form of x and y coordinates, width and height.
  78.      *  <p>
  79.      *  If the width or height is less than zero then the {@link #none()}
  80.      *  object is returned.
  81.      *
  82.      *  @param x the x coordinate of the location of the bounds object.
  83.      *  @param y the y coordinate of the location of the bounds object.
  84.      *  @param width the width of the bounds object.
  85.      *  @param height the height of the bounds object.
  86.      *  @return a bounds object with the specified location and size
  87.      *  in the form of x and y coordinates, width and height.
  88.      */
  89.     public static Bounds of( float x, float y, float width, float height ) {
  90.         if ( width < 0 || height < 0 )
  91.             return EMPTY;

  92.         return new Bounds(Position.of(x, y), Size.of(width, height));
  93.     }

  94.     /**
  95.      *  Creates a bounds object from an AWT {@link Rectangle} object.
  96.      *  <p>
  97.      *  If the width or height is less than zero then the {@link #none()}
  98.      *  object is returned.
  99.      *
  100.      *  @param rectangle an AWT rectangle object to create a bounds object from.
  101.      *  @return a bounds object with the location and size of the AWT rectangle object.
  102.      */
  103.     public static Bounds of( java.awt.Rectangle rectangle ) {
  104.         return of(rectangle.x, rectangle.y, rectangle.width, rectangle.height);
  105.     }

  106.     private Bounds(Position position, Size size ) {
  107.         _position = Objects.requireNonNull(position);
  108.         _size     = Objects.requireNonNull(size);
  109.     }

  110.     /**
  111.      *  If you think of the bounds object as a rectangle, then the
  112.      *  {@link Position} defines the top left corner and the {@link Size}
  113.      *  defines the width and height of the rectangle.
  114.      *  Note that the y-axis is growing positively downwards and the x-axis
  115.      *  is growing positively to the right.
  116.      *
  117.      * @return The {@link Position} of this bounds object,
  118.      *         which contains the x and y coordinates.
  119.      */
  120.     public Position location() {
  121.         return _position;
  122.     }

  123.     /**
  124.      *  Allows you to check weather the bounds object has a width
  125.      *  that is greater than zero.
  126.      *
  127.      * @return The truth value of whether this bounds object has a width,
  128.      *       which is true if the width is greater than zero.
  129.      */
  130.     public boolean hasWidth() { return _size._width > 0; }

  131.     /**
  132.      *  Allows you to check weather the bounds object has a height
  133.      *  that is greater than zero.
  134.      *
  135.      * @return The truth value of whether this bounds object has a height,
  136.      *       which is true if the height is greater than zero.
  137.      */
  138.     public boolean hasHeight() {
  139.         return _size._height > 0;
  140.     }

  141.     /**
  142.      *  The {@link Size} of define the width and height of the bounds
  143.      *  starting from the x and y coordinates of the {@link Position}.
  144.      *  Note that the {@link Position} is always the top left corner
  145.      *  of the bounds object where the y-axis is growing positively downwards
  146.      *  and the x-axis is growing positively to the right.
  147.      *
  148.      * @return The {@link Size} of this bounds object,
  149.      *        which contains the width and height.
  150.      */
  151.     public Size size() {
  152.         return _size;
  153.     }

  154.     /**
  155.      *  Allows you to create an updated version of this bounds object with the
  156.      *  specified x-coordinate and the same y-coordinate and size as this bounds instance.
  157.      *  See also {@link #withY(int)}, {@link #withWidth(int)}, and {@link #withHeight(int)}.
  158.      *
  159.      * @param x A new x coordinate for the location of this bounds object.
  160.      * @return A new bounds object with a new location that has the specified x coordinate.
  161.      */
  162.     public Bounds withX( int x ) {
  163.         return new Bounds(_position.withX(x), _size);
  164.     }

  165.     /**
  166.      *  Allows you to create an updated version of this bounds object with the
  167.      *  specified y-coordinate and the same x-coordinate and size as this bounds instance.
  168.      *  See also {@link #withX(int)}, {@link #withWidth(int)}, and {@link #withHeight(int)}.
  169.      *
  170.      * @param y A new y coordinate for the location of this bounds object.
  171.      * @return A new bounds object with a new location that has the specified y coordinate.
  172.      */
  173.     public Bounds withY( int y ) {
  174.         return new Bounds(_position.withY(y), _size);
  175.     }

  176.     /**
  177.      *  Allows you to create an updated version of this bounds object with the
  178.      *  specified width and the same x and y coordinates as well as height as this bounds instance.
  179.      *  See also {@link #withX(int)}, {@link #withY(int)}, and {@link #withHeight(int)}, and {@link #withSize(int, int)}.
  180.      *
  181.      * @param width A new width for the size of this bounds object.
  182.      * @return A new bounds object with a new size that has the specified width.
  183.      */
  184.     public Bounds withWidth( int width ) {
  185.         return new Bounds(_position, _size.withWidth(width));
  186.     }

  187.     /**
  188.      *  Allows you to create an updated version of this bounds object with the
  189.      *  specified height and the same x and y coordinates as well as width as this bounds instance.
  190.      *  See also {@link #withX(int)}, {@link #withY(int)}, and {@link #withWidth(int)}, and {@link #withSize(int, int)}.
  191.      *
  192.      * @param height A new height for the size of this bounds object.
  193.      * @return A new bounds object with a new size that has the specified height.
  194.      */
  195.     public Bounds withHeight( int height ) {
  196.         return new Bounds(_position, _size.withHeight(height));
  197.     }

  198.     /**
  199.      *  Allows you to create an updated version of this bounds object with the
  200.      *  specified width and height and the same x and y coordinates as this bounds instance.
  201.      *  See also {@link #withX(int)}, {@link #withY(int)}, {@link #withWidth(int)}, and {@link #withHeight(int)}.
  202.      *
  203.      * @param width A new width for the size of this bounds object.
  204.      * @param height A new height for the size of this bounds object.
  205.      * @return A new bounds object with a new size that has the specified width and height.
  206.      */
  207.     public Bounds withSize( int width, int height ) {
  208.         return new Bounds(_position, Size.of(width, height));
  209.     }

  210.     /**
  211.      *  Creates a new bounds object which tightly fits around this bounds object
  212.      *  and the specified bounds object, effectively merging the two bounds objects.
  213.      *  This is done by finding the minimum x and y coordinates and
  214.      *  the maximum width and height of the two bounds objects.
  215.      *
  216.      * @param other The bounds object to merge with this bounds object.
  217.      * @return A new bounds object that tightly fits around this bounds object and the specified bounds object.
  218.      */
  219.     public Bounds merge( Bounds other ) {
  220.         Objects.requireNonNull(other);
  221.         if ( this.equals(other) )
  222.             return this;

  223.         final float thisLeft   = _position.x();
  224.         final float thisTop    = _position.y();
  225.         final float thisRight  = thisLeft + _size._width;
  226.         final float thisBottom = thisTop  + _size._height;

  227.         final float otherLeft   = other._position.x();
  228.         final float otherTop    = other._position.y();
  229.         final float otherRight  = otherLeft + other._size._width;
  230.         final float otherBottom = otherTop  + other._size._height;

  231.         final float left   = Math.min( thisLeft,   otherLeft   );
  232.         final float top    = Math.min( thisTop,    otherTop    );
  233.         final float right  = Math.max( thisRight,  otherRight  );
  234.         final float bottom = Math.max( thisBottom, otherBottom );

  235.         return Bounds.of(left, top, right - left, bottom - top);
  236.     }

  237.     /**
  238.      *  Allows you to check weather the bounds object has the specified
  239.      *  width and height, which is true if the width and height are equal
  240.      *  to the width and height of the {@link Size} of this bounds object
  241.      *  (see {@link #size()}).
  242.      *
  243.      * @param width A new width for the size of this bounds object.
  244.      * @param height A new height for the size of this bounds object.
  245.      * @return A new bounds object with a new size that has the specified width and height.
  246.      */
  247.     public boolean hasSize( int width, int height ) {
  248.         return _size._width == width && _size._height == height;
  249.     }

  250.     /**
  251.      *  Allows you to check weather the bounds object has the specified
  252.      *  width, which is true if the width is equal to the width of the
  253.      *  {@link Size} of this bounds object (see {@link #size()}).
  254.      *
  255.      * @param width An integer value to compare to the width of this bounds object.
  256.      * @return The truth value of whether the specified width is equal to the width of this bounds object.
  257.      */
  258.     public boolean hasWidth( int width ) {
  259.         return _size._width == width;
  260.     }

  261.     /**
  262.      *  Allows you to check weather the bounds object has the specified
  263.      *  height, which is true if the height is equal to the height of the
  264.      *  {@link Size} of this bounds object (see {@link #size()}).
  265.      *
  266.      * @param height An integer value to compare to the height of this bounds object.
  267.      * @return The truth value of whether the specified height is equal to the height of this bounds object.
  268.      */
  269.     public boolean hasHeight( int height ) {
  270.         return _size._height == height;
  271.     }

  272.     /**
  273.      *  The bounds object has a location and size which form a rectangular area
  274.      *  exposed by this method as a float value defined by the width multiplied by the height.
  275.      *
  276.      * @return The area of this bounds object, which is the width multiplied by the height.
  277.      */
  278.     public float area() {
  279.         return _size._width * _size._height;
  280.     }

  281.     /**
  282.      *  The bounds object has a location and size which form a rectangular area
  283.      *  which can easily be converted to a {@link Rectangle} object using this method.
  284.      *
  285.      * @return A {@link Rectangle} object with the same location and size as this bounds object.
  286.      */
  287.     public Rectangle toRectangle() {
  288.         return new Rectangle((int) _position.x(), (int) _position.y(), (int) _size._width, (int) _size._height);
  289.     }

  290.     @Override
  291.     public String toString() {
  292.         return this.getClass().getSimpleName()+"[" +
  293.                     "location=" + _position + ", "+
  294.                     "size="     + _size     +
  295.                 "]";
  296.     }

  297.     /**
  298.      *  A convent method to check if the specified x and y coordinates and width and height
  299.      *  are equal to the location and size of this bounds object.
  300.      *  This is equivalent to calling {@link #equals(Object)} with
  301.      *  a new bounds object created with the specified x and y coordinates and width and height
  302.      *  like so: {@code equals(Bounds.of(x, y, width, height))}.
  303.      *
  304.      * @param x An integer value to compare to the x coordinate of the location of this bounds object.
  305.      * @param y An integer value to compare to the y coordinate of the location of this bounds object.
  306.      * @param width An integer value to compare to the width of this bounds object.
  307.      * @param height An integer value to compare to the height of this bounds object.
  308.      * @return The truth value of whether the specified x and y coordinates and width and height
  309.      *        are equal to the location and size of this bounds object.
  310.      */
  311.     public boolean equals( int x, int y, int width, int height ) {
  312.         return _position.x() == x && _position.y() == y && _size._width == width && _size._height == height;
  313.     }

  314.     @Override
  315.     public boolean equals( Object o ) {
  316.         if ( o == this ) return true;
  317.         if ( o == null ) return false;
  318.         if ( o.getClass() != this.getClass() ) return false;
  319.         Bounds that = (Bounds)o;
  320.         return Objects.equals(this._position, that._position) &&
  321.                Objects.equals(this._size,     that._size);
  322.     }

  323.     @Override
  324.     public int hashCode() {
  325.         return Objects.hash(_position, _size);
  326.     }
  327. }