package info.u250.c2d.box2deditor.ui.util; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.Insets; import java.awt.LayoutManager2; import java.awt.Point; import java.util.ArrayList; import java.util.HashMap; import java.util.List; /** * The <code>OverlapLayout</code> class is a layout manager that * lays out a container's components in an overlapping fashion. A * component can be painted "above" or "below" the previous component * in the container. * * Like the GridLayout, each component is sized to the largest width and * height of any component in the container. The amount of overlap is * controlled by specifying the overlap postion of each component. You can * simulate, left-to-right, right-to-left, top-to-bottom, bottom-to-top and * diagonal layouts. As well you can "stack" components completely on top of * one another. In this case the components are sized to the space available * in the container. * * A main usage for this layout might be in the creation of "card games". A * few features have been added that might be handy in these cases: * * a) a "popup" feature - when a component is selected in can "popup" from * its regular location so it visibly stands out. To accomplish this some * extra space must be reserved in the container for the popup. This is * done by using the setPopupInsets method which allow you to control the * popup direction. In addition you can add/remove a simple constraint to * the component. POP_UP will popup the component. POP_DOWN or null * will paint the component in its regular location. * b) when a component is made "invisible" you can reserve its location in the * container so all the other components don't shift. * * Note: this layout is achieved by changing the ZOrder of components in the * container. It will not work for all components as some compnents will * always paint themselves on the top of others. This seems to happen with * components like JButton as rollover effects are painted when a mouse moves * over the components. */ public class OverlapLayout implements LayoutManager2, java.io.Serializable { private static final long serialVersionUID = 1L; public static Boolean POP_UP = Boolean.TRUE; public static Boolean POP_DOWN = Boolean.FALSE; private static final int PREFERRED = 0; private static final int MINIMUM = 1; // Indicates how a component is painted private boolean overlapAbove; private Point overlapPosition; // Reserve space for invisible components in the Container private boolean includeInvisible = true; // Reserve extra space so a component can "popup" private Insets popupInsets = new Insets(0, 0, 0, 0); // Track original order in which the components where added private List<Component> components = new ArrayList<Component>(); // Track a constraint added to a component private HashMap<Component, Boolean> constraints = new HashMap<Component, Boolean>(); // Track maximum dimension of any component for easier layout private Dimension maximumSize = new Dimension(); /** * Convenience constructor to provide for "stacking" of components. Each * component will be stacked above the previous component and sized to * fill the space of the parent container. */ public OverlapLayout() { this( new Point(0, 0) ); } /** * Convenience constructor. Each component will overlap above the previous * component. * * @param overlapPosition a Point defining the relative amount of overlap */ public OverlapLayout(Point overlapPosition) { this(overlapPosition, true); } /** * Create an overlapping layout. * * @param overlapPosition a Point defining the relative amount of overlap * @param overlayAbove when true components are painted above the previous * component, otherwise they are painted below. */ public OverlapLayout(Point overlapPosition, boolean overlapAbove) { setOverlapPosition( overlapPosition ); this.overlapAbove = overlapAbove; } /** * When components are overlapped above the ZOrder of each component is * changed resulting in the components position in the container being * changed. For example when you add a component to the end of the * container it will be moved to the beginning. If you then try to access * the component using Component.componentAt(), you will get the first * component, not the last. * * This method will convert the index to you access the proper component. * * @param index the index to convert */ public int convertIndex(int index) { if (overlapAbove) return components.size() - index - 1; else return index; } /** * Get the include invisible property * * @returns the include invisible property */ public boolean isIncludeInvisible() { return includeInvisible; } /** * Controls whether spaces should reserved for invisible components in * the container * * @param includeInvisible when true, space is reserved otherwise the * component is not included in the layout sizing */ public void setIncludeInvisible(boolean includeInvisible) { this.includeInvisible = includeInvisible; } /** * Get the overlapping position of each component * * @returns the Point representing the overlapped position */ public Point getOverlapPosition() { return overlapPosition; } /** * Specify the position where the overlapped component should be painted. * * @param overlapPosition the position where the next component is painted */ public void setOverlapPosition(Point overlapPosition) { this.overlapPosition = overlapPosition; } /** * Get the popup insets * * @returns the popup insets */ public Insets getPopupInsets() { return popupInsets; } /** * Define extra space to be reserved by the container. This will allow * components to be "popped up" if required. Generally space would only be * reserved on one side of the container. * * @param popupInsets Insets defining extra space for a particular side * of the container. */ public void setPopupInsets(Insets popupInsets) { this.popupInsets = popupInsets; } /** * 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 Boolean getConstraints(Component component) { return (Boolean)constraints.get(component); } /** * Adds the specified component with the specified name to the layout. * @param name the name of the component * @param comp the component to be added */ public void addLayoutComponent(String name, Component comp) {} /* * Keep track of any specified constraint for the component. */ public void addLayoutComponent(Component component, Object constraint) { // Support simple Boolean constraint for painting a Component in // its "popped up" position if (constraint == null) { constraints.remove(component); } else if (constraint instanceof Boolean) { constraints.put(component, (Boolean)constraint); } else { String message = "Constraint parameter must be of type Boolean"; throw new IllegalArgumentException( message ); } // Keep a separate List of components in the order in which they where // added to the Container. This makes layout easier. First we need // to find the position the component was added at. We can't depend // on the component order in the parent Container as changing the // Z-Order also changes the order in the Container if (!components.contains(component)) { Container parent = component.getParent(); int size = parent.getComponentCount(); for (int i = 0 ; i < size ; i++) { Component c = parent.getComponent(i); if (c == component) { components.add(i, component); // Need to change Z-Order so added components are painted // above the previously added component. if (overlapAbove) { parent.setComponentZOrder(component, size - i - 1); } break; } } } } /** * Removes the specified component from the layout. * * @param comp the component to be removed */ public void removeLayoutComponent(Component component) { components.remove( component ); constraints.remove( component ); } /** * Determine the minimum size on the 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 */ public Dimension minimumLayoutSize(Container parent) { synchronized (parent.getTreeLock()) { return getLayoutSize(parent, MINIMUM); } } /** * Determine the preferred size on the Container * * @param parent the container in which to do the layout * @return the preferred dimensions to lay out the * subcomponents of the specified container */ public Dimension preferredLayoutSize(Container parent) { synchronized (parent.getTreeLock()) { return getLayoutSize(parent, PREFERRED); } } /* * The calculation for minimum/preferred size it the same. The only * difference is the need to use the minimum or preferred size of the * component in the calculation. * * @param parent the container in which to do the layout * @param type either MINIMUM or PREFERRED */ private Dimension getLayoutSize(Container parent, int type) { int visibleComponents = 0; int width = 0; int height = 0; // All components will be resized to the maximum dimension for (Component component: components) { if (component.isVisible() || includeInvisible) { Dimension size = getDimension(component, type); width = Math.max(size.width, width); height = Math.max(size.height, height); visibleComponents++; } } if (visibleComponents == 0) return new Dimension(0, 0); // Keep maximum dimension for easy access when laying out components if (type == PREFERRED) { maximumSize.width = width; maximumSize.height = height; } // Adjust size for each overlapping component visibleComponents--; width += visibleComponents * Math.abs(overlapPosition.x); height += visibleComponents * Math.abs(overlapPosition.y); // Adjust for parent Container and popup insets Insets parentInsets = parent.getInsets(); width += parentInsets.left + parentInsets.right; height += parentInsets.top + parentInsets.bottom; width += popupInsets.left + popupInsets.right; height += popupInsets.top + popupInsets.bottom; return new Dimension(width, height); } 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); } } /** * Lays out the specified container using this layout. * <p> * * @param target the container in which to do the layout */ public void layoutContainer(Container parent) { synchronized (parent.getTreeLock()) { int size = components.size(); if (size == 0) return; // Determine location of first component Point location = new Point(0, 0); Insets parentInsets = parent.getInsets(); // Layout right-to-left, else left-to-right if (overlapPosition.x < 0) location.x = parent.getWidth() - maximumSize.width - parentInsets.right - popupInsets.right; else location.x = parentInsets.left + popupInsets.left; // Layout bottom-to-top, else top-to-bottom if (overlapPosition.y < 0) location.y = parent.getHeight() - maximumSize.height - parentInsets.bottom - popupInsets.bottom; else location.y = parentInsets.top + popupInsets.top; // Set the size and location for each component for (int i = 0 ; i < size ; i++) { Component component = components.get(i); if (component.isVisible() || includeInvisible) { // When components are "stacked" resize each component to fill // the size of the parent container if (overlapPosition.x == 0 && overlapPosition.y == 0) { int width = parent.getWidth() - parentInsets.left - parentInsets.right; int height = parent.getHeight() - parentInsets.top - parentInsets.bottom; component.setSize(width, height); } else // resize each component to be the same size { component.setSize( maximumSize ); } // Set location of the component int x = location.x; int y = location.y; // Adjust location when component is "popped up" Boolean constraint = constraints.get(component); if (constraint != null && constraint == Boolean.TRUE) { x += popupInsets.right - popupInsets.left; y += popupInsets.bottom - popupInsets.top; } component.setLocation(x, y); // Calculate location of next component using the overlap offsets location.x += overlapPosition.x; location.y += overlapPosition.y; } } }} /** * 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() + "[overlapAbove=" + overlapAbove + ",overlapPosition=" + overlapPosition + "]"; } }