From 886bd640ea606c9cce138e13f05ab9c2dbf80edc Mon Sep 17 00:00:00 2001 From: magenoxx Date: Tue, 24 Jul 2012 14:41:57 +0400 Subject: [PATCH] [UI] RelativeLayout --- .../components/layout/RelativeLayout.java | 856 ++++++++++++++++++ 1 file changed, 856 insertions(+) create mode 100644 Mage.Client/src/main/java/mage/client/components/layout/RelativeLayout.java diff --git a/Mage.Client/src/main/java/mage/client/components/layout/RelativeLayout.java b/Mage.Client/src/main/java/mage/client/components/layout/RelativeLayout.java new file mode 100644 index 0000000000..5c20009665 --- /dev/null +++ b/Mage.Client/src/main/java/mage/client/components/layout/RelativeLayout.java @@ -0,0 +1,856 @@ +package mage.client.components.layout; + +import java.awt.*; +import java.util.*; + +/** + * The RelativeLayout class is a layout manager that + * lays out a container's components on the specified X or Y axis. + * + * Components can be layed out at their preferred size or at a + * relative size. When relative sizing is used the component must be added + * to the container using a relative size constraint, which is simply a + * Float value. + * + * The space available for relative sized components is determined by + * subtracting the preferred size of the other components from the space + * available in the container. Each component is then assigned a size + * based on its relative size value. For example: + * + * container.add(component1, new Float(1)); + * container.add(component2, new Float(2)); + * + * There is a total of 3 relative units. If the container has 300 pixels + * of space available then component1 will get 100 and component2, 200. + * + * It is possible that rounding errors will occur in which case you can + * specify a rounding policy to use to allocate the extra pixels. + * + * By defaults components are center aligned on the secondary axis + * however this can be changed at the container or component level. + */ +public class RelativeLayout implements LayoutManager2, java.io.Serializable +{ + // Used in the constructor + public final static int X_AXIS = 0; + public final static int Y_AXIS = 1; + + // See setAlignment() method + public final static float LEADING = 0.0f; + public final static float CENTER = 0.5f; + public final static float TRAILING = 1.0f; + public final static float COMPONENT = -1.0f; + + // See setRoundingPolicy() method + public final static int DO_NOTHING = 0; + public final static int FIRST = 1; + public final static int LAST = 2; + public final static int LARGEST = 3; + public final static int EQUAL = 4; + + private final static int MINIMUM = 0; + private final static int PREFERRED = 1; + + private HashMap constraints = new HashMap(); + + /** + * The axis of the Components within the Container. + */ + private int axis; + + /** + * The alignment of the Components on the other axis of the Container. + * For X-AXIS this would refer to the Y alignemt. + * For Y-AXIS this would refer to the X alignment. + */ + private float alignment = CENTER; + + /** + * This is the gap (in pixels) which specifies the space between components + * It can be changed at any time and should be a non-negative integer. + */ + private int gap; + + /** + * The gap (in pixels) used before the leading component and after the + * trailing component. + * It can be changed at any time and should be a non-negative integer. + */ + private int borderGap; + + // Fill space available for relative components + private boolean fill = false; + + // Gap to prevent the component from completely filling the space available + private int fillGap; + + // Specify the rounding policy when rounding problems happen. + private int roundingPolicy = LARGEST; + + /** + * Creates a relative layout with the components layed out on the X-Axis + * using the default gap + */ + public RelativeLayout() + { + this(X_AXIS, 0); + } + + /** + * Creates a relative layout with the components layed out on the specified + * axis using the default gap + *

+ * @param axis X-AXIS or Y_AXIS + */ + public RelativeLayout(int axis) + { + this(axis, 0); + } + + /** + * Creates a relative layout with the components layed out on the specified + * axis using the specfied gap + *

+ * All RelativeLayout constructors defer to this one. + * @param axis X-AXIS or Y_AXIS + * @param gap the gap + */ + public RelativeLayout(int axis, int gap) + { + setAxis( axis ); + setGap( gap ); + setBorderGap( gap ); + } + + /** + * Gets the layout axis. + * @return the layout axis + */ + public int getAxis() + { + return axis; + } + + /** + * Sets the layout axis + * @param axis the layout axis + */ + public void setAxis(int axis) + { + if (axis != X_AXIS + && axis != Y_AXIS) + throw new IllegalArgumentException("invalid axis specified"); + + this.axis = axis; + } + + /** + * Gets the gap between components. + * @return the gap between components + */ + public int getGap() + { + return gap; + } + + /** + * Sets the gap between components to the specified value. + * @param gap the gap between components + */ + public void setGap(int gap) + { + this.gap = gap < 0 ? 0 : gap; + } + + /** + * Gets the initial gap. This gap is used before the leading component + * and after the trailing component. + * + * @return the leading/trailing gap + */ + public int getBorderGap() + { + return borderGap; + } + + /** + * Sets the initial gap. This gap is used before the leading component + * and after the trailing component. The default is set to the gap. + * + * @param borderGap the leading/trailing gap + */ + public void setBorderGap(int borderGap) + { + this.borderGap = borderGap < 0 ? 0 : borderGap; + } + + /** + * Gets the alignment of the components on the opposite axis. + * @return the alignment + */ + public float getAlignment() + { + return alignment; + } + + /* + * Set the alignment of the component on the opposite axis. + * + * For X-AXIS this would refer to the Y alignemt. + * For Y-AXIS this would refer to the X alignment. + * + * Must be between 0.0 and 1.0, or -1. Values can be specified using: + * + * RelativeLayout.LEADING + * RelativeLayout.CENTER + * RelativeLayout.TRAILING + * RelativeLayout.COMPONENT - the getAlignemntX/Y method for the + * opposite axis will be used + */ + public void setAlignment(float alignment) + { + this.alignment = alignment > 1.0f ? 1.0f : alignment < 0.0f ? -1.0f : alignment; + } + + /** + * Gets the fill property for the component size on the opposite edge. + * @return the fill property + */ + public boolean isFill() + { + return fill; + } + + /** + * Change size of relative components to fill the space available + * For X-AXIS aligned components the height will be filled. + * For Y-AXIS aligned components the width will be filled. + */ + public void setFill(boolean fill) + { + this.fill = fill; + } + + /** + * Gets the fill gap amount. + * @return the fill gap value + */ + public int getFillGap() + { + return fillGap; + } + + /** + * Specify the number of pixels by which the fill size is decreased when + * setFill(true) has been specified. + */ + public void setFillGap(int fillGap) + { + this.fillGap = fillGap; + } + + /** + * Gets the rounding policy. + * @return the rounding policy + */ + public int getRoundingPolicy() + { + return roundingPolicy; + } + + /** + * Specify the rounding policy to be used when all the avialable pixels + * have not been allocated to a component. + * + * DO_NOTHING + * FIRST - extra pixels added to the first relative component + * LAST - extra pixels added to the last relative component + * LARGEST (default) - extra pixels added to the larger relative component + * EQUAL - a single pixel is added to each relative component + * (until pixels are used up) + */ + public void setRoundingPolicy(int roundingPolicy) + { + this.roundingPolicy = roundingPolicy; + } + + /** + * Gets the constraints for the specified component. + * + * @param component the component to be queried + * @return the constraint for the specified component, or null + * if component is null or is not present in this layout + */ + public Float getConstraints(Component component) + { + return (Float)constraints.get(component); + } + + /** + * Not supported + */ + public void addLayoutComponent(String name, Component component) {} + + /* + * Keep track of any specified constraint for the component. + */ + public void addLayoutComponent(Component component, Object constraint) + { + if (constraint != null) + if (constraint instanceof Float) + { + constraints.put(component, (Float)constraint); + } + else + throw new IllegalArgumentException("Constraint parameter must be of type Float"); + } + + /** + * Removes the specified component from the layout. + * @param comp the component to be removed + */ + public void removeLayoutComponent(Component comp) {} + + /** + * Determines the preferred size of the container argument using + * this column layout. + *

+ * The preferred width of a column layout is the largest preferred + * width of each column in the container, plus the horizontal padding + * times the number of columns minus one, + * plus the left and right insets of the target container. + *

+ * The preferred height of a column layout is the largest preferred + * height of each row in the container, plus the vertical padding + * times the number of rows minus one, + * plus the top and bottom insets of the target container. + * + * @param target the container in which to do the layout + * @return the preferred dimensions to lay out the + * subcomponents of the specified container + * @see java.awt.RelativeLayout#minimumLayoutSize + * @see java.awt.Container#getPreferredSize() + */ + public Dimension preferredLayoutSize(Container parent) + { + synchronized (parent.getTreeLock()) + { + return getLayoutSize(parent, PREFERRED); + } + } + + /** + * Determines the minimum size of the container argument using this + * column layout. + *

+ * The minimum width of a grid layout is the largest minimum width + * of each column in the container, plus the horizontal padding + * times the number of columns minus one, + * plus the left and right insets of the target container. + *

+ * The minimum height of a column layout is the largest minimum height + * of each row in the container, plus the vertical padding + * times the number of rows minus one, + * plus the top and bottom insets of the target container. + * + * @param target the container in which to do the layout + * @return the minimum dimensions needed to lay out the + * subcomponents of the specified container + * @see java.awt.RelativeLayout#preferredLayoutSize + * @see java.awt.Container#doLayout + */ + public Dimension minimumLayoutSize(Container parent) + { + synchronized (parent.getTreeLock()) + { + return getLayoutSize(parent, MINIMUM); + } + } + + /** + * Lays out the specified container using this layout. + *

+ * This method reshapes the components in the specified target + * container in order to satisfy the constraints of the + * RelativeLayout object. + *

+ * The grid layout manager determines the size of individual + * components by dividing the free space in the container into + * equal-sized portions according to the number of rows and columns + * in the layout. The container's free space equals the container's + * size minus any insets and any specified horizontal or vertical + * gap. All components in a grid layout are given the same size. + * + * @param target the container in which to do the layout + * @see java.awt.Container + * @see java.awt.Container#doLayout + */ + public void layoutContainer(Container parent) + { + synchronized (parent.getTreeLock()) + { + if (axis == X_AXIS) + layoutContainerHorizontally(parent); + else + layoutContainerVertically(parent); + } + } + + /* + * Lay out all the components in the Container along the X-Axis + */ + private void layoutContainerHorizontally(Container parent) + { + int components = parent.getComponentCount(); + int visibleComponents = getVisibleComponents( parent ); + + if (components == 0) return; + + // Determine space available for components using relative sizing + + float relativeTotal = 0.0f; + Insets insets = parent.getInsets(); + int spaceAvailable = parent.getSize().width + - insets.left + - insets.right + - ((visibleComponents - 1) * gap) + - (2 * borderGap); + + for (int i = 0 ; i < components ; i++) + { + Component component = parent.getComponent(i); + + if (! component.isVisible()) continue; + + Float constraint = constraints.get(component); + + if (constraint == null) + { + Dimension d = component.getPreferredSize(); + spaceAvailable -= d.width; + } + else + { + relativeTotal += constraint.doubleValue(); + } + } + + // Allocate space to each component using relative sizing + + int[] relativeSpace = allocateRelativeSpace(parent, spaceAvailable, relativeTotal); + + // Position each component in the container + + int x = insets.left + borderGap; + int y = insets.top; + int insetGap = insets.top + insets.bottom; + int parentHeight = parent.getSize().height - insetGap; + + for (int i = 0 ; i < components ; i++) + { + Component component = parent.getComponent(i); + + if (! component.isVisible()) continue; + + if (i > 0) + x += gap; + + Dimension d = component.getPreferredSize(); + + if (fill) + d.height = parentHeight - fillGap; + + Float constraint = constraints.get(component); + + if (constraint == null) + { + component.setSize( d ); + int locationY = getLocationY(component, parentHeight) + y; + component.setLocation(x, locationY); + x += d.width; + } + else + { + int width = relativeSpace[i]; + component.setSize(width, d.height); + int locationY = getLocationY(component, parentHeight) + y; + component.setLocation(x, locationY); + x += width; + } + } + } + + /* + * Align the component on the Y-Axis + */ + private int getLocationY(Component component, int height) + { + // Use the Container alignment policy + + float alignmentY = alignment; + + // Override with the Component alignment + + if (alignmentY == COMPONENT) + alignmentY = component.getAlignmentY(); + + float y = (height - component.getSize().height) * alignmentY; + return (int)y; + } + + /* + * Lay out all the components in the Container along the Y-Axis + */ + private void layoutContainerVertically(Container parent) + { + int components = parent.getComponentCount(); + int visibleComponents = getVisibleComponents( parent ); + + if (components == 0) return; + + // Determine space available for components using relative sizing + + float relativeTotal = 0.0f; + Insets insets = parent.getInsets(); + int spaceAvailable = parent.getSize().height + - insets.top + - insets.bottom + - ((visibleComponents - 1) * gap) + - (2 * borderGap); + + for (int i = 0 ; i < components ; i++) + { + Component component = parent.getComponent(i); + + if (! component.isVisible()) continue; + + Float constraint = constraints.get(component); + + if (constraint == null) + { + Dimension d = component.getPreferredSize(); + spaceAvailable -= d.height; + } + else + { + relativeTotal += constraint.doubleValue(); + } + } + + // Allocate space to each component using relative sizing + + int[] relativeSpace = allocateRelativeSpace(parent, spaceAvailable, relativeTotal); + + // Position each component in the container + + int x = insets.left; + int y = insets.top + borderGap; + int insetGap = insets.left + insets.right; + int parentWidth = parent.getSize().width - insetGap; + + for (int i = 0 ; i < components ; i++) + { + Component component = parent.getComponent(i); + + if (! component.isVisible()) continue; + + if (i > 0) + y += gap; + + Dimension d = component.getPreferredSize(); + + if (fill) + d.width = parentWidth - fillGap; + + Float constraint = constraints.get(component); + + if (constraint == null) + { + component.setSize( d ); + int locationX = getLocationX(component, parentWidth) + x; + component.setLocation(locationX, y); + y += d.height; + } + else + { + int height = relativeSpace[i]; + component.setSize(d.width, height); + int locationX = getLocationX(component, parentWidth) + x; + component.setLocation(locationX, y); + y += height; + } + + } + } + + /* + * Align the component on the X-Axis + */ + private int getLocationX(Component component, int width) + { + // Use the Container alignment policy + + float alignmentX = alignment; + + // Override with the Component alignment + + if (alignmentX == COMPONENT) + alignmentX = component.getAlignmentX(); + + float x = (width - component.getSize().width) * alignmentX; + return (int)x; + } + + /* + * Allocate the space available to each component using relative sizing + */ + private int[] allocateRelativeSpace(Container parent, int spaceAvailable, float relativeTotal) + { + int spaceUsed = 0; + int components = parent.getComponentCount(); + int[] relativeSpace = new int[components]; + + for (int i = 0 ; i < components ; i++) + { + relativeSpace[i] = 0; + + if (relativeTotal > 0 && spaceAvailable > 0) + { + Component component = parent.getComponent(i); + Float constraint = constraints.get(component); + + if (constraint != null) + { + int space = (int)(spaceAvailable * constraint.floatValue() / relativeTotal); + relativeSpace[i] = space; + spaceUsed += space; + } + } + } + + int spaceRemaining = spaceAvailable - spaceUsed; + + if (relativeTotal > 0 && spaceRemaining > 0) + adjustForRounding(relativeSpace, spaceRemaining); + + return relativeSpace; + } + + /* + * Because of rounding, all the space has not been allocated + * Override this method to create a custom rounding policy + */ + protected void adjustForRounding(int[] relativeSpace, int spaceRemaining) + { + switch(roundingPolicy) + { + case DO_NOTHING: + break; + case FIRST: + adjustFirst(relativeSpace, spaceRemaining); + break; + case LAST: + adjustLast(relativeSpace, spaceRemaining); + break; + case LARGEST: + adjustLargest(relativeSpace, spaceRemaining); + break; + case EQUAL: + adjustEqual(relativeSpace, spaceRemaining); + break; + default: + adjustLargest(relativeSpace, spaceRemaining); + } + } + + /* + * First component using relative sizing gets all the space + */ + private void adjustFirst(int[] relativeSpace, int spaceRemaining) + { + for (int i = 0; i < relativeSpace.length; i++) + { + if (relativeSpace[i] > 0) + { + relativeSpace[i] += spaceRemaining; + break; + } + } + } + + /* + * Last component using relative sizing gets all the space + */ + private void adjustLast(int[] relativeSpace, int spaceRemaining) + { + for (int i = relativeSpace.length - 1; i > 0; i--) + { + if (relativeSpace[i] > 0) + { + relativeSpace[i] += spaceRemaining; + break; + } + } + } + + /* + * Largest component using relative sizing gets all the space. + * When multiple components are the same size, the last one found is used. + */ + private void adjustLargest(int[] relativeSpace, int spaceRemaining) + { + int largest = 0; + int largestSpace = 0; + + for (int i = 0; i < relativeSpace.length; i++) + { + int space = relativeSpace[i]; + + if (space > 0) + { + if (largestSpace < space) + { + largestSpace = space; + largest = i; + } + } + } + + relativeSpace[largest] += spaceRemaining; + } + + /* + * Each component using relative sizing gets 1 more pixel + * until all the space is used, starting with the first. + */ + private void adjustEqual(int[] relativeSpace, int spaceRemaining) + { + for (int i = 0; i < relativeSpace.length; i++) + { + if (relativeSpace[i] > 0) + { + relativeSpace[i]++; + spaceRemaining--; + + if (spaceRemaining == 0) + break; + } + } + } + + /* + * Determine the Preferred or Minimum layout size + */ + private Dimension getLayoutSize(Container parent, int type) + { + int width = 0; + int height = 0; + int components = parent.getComponentCount(); + int visibleComponents = getVisibleComponents( parent ); + + for (int i = 0 ; i < components ; i++) + { + Component component = parent.getComponent(i); + + if (! component.isVisible()) continue; + + Dimension d = getDimension(component, type); + + if (axis == X_AXIS) + { + width += d.width; + height = Math.max(height, d.height); + } + else + { + width = Math.max(width, d.width); + height += d.height; + } + } + + Insets insets = parent.getInsets(); + int totalGap = ((visibleComponents - 1) * gap) + (2 * borderGap); + + if (axis == X_AXIS) + { + width += insets.left + insets.right + totalGap; + height += insets.top + insets.bottom; + } + else + { + width += insets.left + insets.right; + height += insets.top + insets.bottom + totalGap; + } + + Dimension size = new Dimension(width, height); + return size; + } + + private int getVisibleComponents(Container container) + { + int visibleComponents = 0; + + for (Component component : container.getComponents()) + { + if (component.isVisible()) + visibleComponents++; + } + + return visibleComponents; + } + + private Dimension getDimension(Component component, int type) + { + switch (type) + { + case PREFERRED: return component.getPreferredSize(); + case MINIMUM: return component.getMinimumSize(); + default: return new Dimension(0, 0); + } + } + + /** + * There is no maximum. + */ + public Dimension maximumLayoutSize(Container target) + { + return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE); + } + + /** + * Returns the alignment along the x axis. Use center alignment. + */ + public float getLayoutAlignmentX(Container parent) + { + return 0.5f; + } + + /** + * Returns the alignment along the y axis. Use center alignment. + */ + public float getLayoutAlignmentY(Container parent) + { + return 0.5f; + } + + /** + * Invalidates the layout, indicating that if the layout manager + * has cached information it should be discarded. + */ + public void invalidateLayout(Container target) + { + // remove constraints here? + } + + /** + * Returns the string representation of this column layout's values. + * @return a string representation of this grid layout + */ + public String toString() + { + return getClass().getName() + + "[axis=" + axis + + ",gap=" + gap + + "]"; + } +}